# Introduction

Ce didacticiel est destiné aux nouveaux utilisateurs de la Simple Virtual Machine.

Dans ce didacticiel, vous allez utiliser les entrées/sorties de base.

Le temps de lecture de ce didacticiel est estimé à 20 minutes si les extensions ont été abordées.

# Mise en place

Pour commencer, créez le canevas de l'application dans le fichier exécutable peripheriques.svm en utilisant ce code :

    #!/usr/bin/env svm
    DESCRIPTION
    Input/output example
    END
    LOG
    DEBUG "Input/output" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmrun.so"
    OPTION -f STR file
    OPTION -p STR port
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    	END
    	MEMORY ".*"
    END

# Terminal

## Ouverture et fermeture

Modifiez le code pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Input/output example
    END
    LOG
    DEBUG "Input/output" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmrun.so"
    OPTION -f STR file
    OPTION -p STR port
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory com.device/stdin, com.device/stdout, com.device/stderr
    		:com.open com.terminal STDIN -> &stdin
    		:com.open com.terminal STDOUT -> &stdout
    		:com.open com.terminal STDERR -> &stderr
    		[ ] -> stdin
    		[ ] -> stdout
    		[ ] -> stderr
    	END
    	MEMORY ".*"
    END

Lancez l'application en mode débugueur. Après la dernière instruction `:com.open`, la mémoire ressemble à :

Input/output
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | STR |
&1 | STR |
&2 | com.device | Terminal STDIN
&3 | com.device | Terminal STDOUT
&4 | com.device | Terminal STDERR

Alias | Pointer
--- | ---
file | &0*1
port | &1*1
stderr | &4*1
stdin | &2*1
stdout | &3*1

Address:
Display
Aliases
P
Focus
Back
Clear

L'instruction `:com.open` permet d'ouvrir un périphérique. Ici, les trois flux du terminal (respectivement entrée standard, sortie standard et sortie d'erreur) sont ouverts et pourront être utilisés pour les interactions avec le terminal.

Continuez l'exécution du code jusqu'à sa fin en mode pas à pas : les différents flux du terminal sont fermés en détruisant le périphérique correspondant. Notez que si les flux seront également fermés si une libération mémoire arrive sur le périphérique correspondant.

## Lecture

Modifiez le code pour ajouter une lecture depuis l'entrée standard :

    #!/usr/bin/env svm
    DESCRIPTION
    Input/output example
    END
    LOG
    DEBUG "Input/output" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmrun.so"
    OPTION -f STR file
    OPTION -p STR port
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory com.device/stdin, com.device/stdout, com.device/stderr, STR/line
    		:com.open com.terminal STDIN -> &stdin
    		:com.open com.terminal STDOUT -> &stdout
    		:com.open com.terminal STDERR -> &stderr
    		:com.read @&stdin com.line -> &line
    		:com.message "<" @&line ">"
    		[ ] -> stdin
    		[ ] -> stdout
    		[ ] -> stderr
    	END
    	MEMORY ".*"
    END

Puis exécutez le code :

    ./peripheriques.svm
    text
    <text
    >

L'instruction `:com.read` ici lit sur l'entrée standard jusqu'à ce qu'une ligne soit entrée (ceci est spécifié par le point d'entrée d'extension `com.line`, puis le texte lu est reproduit sur le terminal avec l'instruction `:com.message`.

## Ecriture

Maintenant, modifiez le code pour utiliser une écriture directe sur le terminal :

    #!/usr/bin/env svm
    DESCRIPTION
    Input/output example
    END
    LOG
    DEBUG "Input/output" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmrun.so"
    OPTION -f STR file
    OPTION -p STR port
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory com.device/stdin, com.device/stdout, com.device/stderr, STR/line
    		:com.open com.terminal STDIN -> &stdin
    		:com.open com.terminal STDOUT -> &stdout
    		:com.open com.terminal STDERR -> &stderr
    		:com.read @&stdin com.line -> &line
    		:com.write @&stderr "<" @&line ">\n"
    		[ ] -> stdin
    		[ ] -> stdout
    		[ ] -> stderr
    	END
    	MEMORY ".*"
    END

Lancez l'application à nouveau. Le résultat sera identique, même si la méthode d'écriture est plus générique que `:com.message` spécifique au terminal.

# Fichiers

En plus du terminal, l'extension com permet d'utiliser les fichiers au travers de l'abstraction d'un périphérique.

## Opérations de base

Modifiez le code :

    #!/usr/bin/env svm
    DESCRIPTION
    Input/output example
    END
    LOG
    DEBUG "Input/output" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmrun.so"
    OPTION -f STR file
    OPTION -p STR port
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory com.device/device, STR/text
    		:com.open com.file > @&file -> &device
    		:com.write @&device "text"
    		:com.open com.file < @&file -> &device
    		:com.read @&device com.all -> &text
    		:com.message @&text
    	END
    	MEMORY ".*"
    END

Lancez l'application avec l'option -f :

    ./peripheriques.svm -f /tmp/file
    text
    cat /tmp/file
    text

Le fichier (ici /tmp/file) a bien été écrit puis lu par le code grâce aux instructions `:com.read` et `:com.write`. L'instruction de lecture utilise le point d'entrée d'extension `com.all` pour lire l'intégralité du fichier.

Le fichier est ici ouvert deux fois :

1. Le marqueur > de la première instruction `:com.open` indique que le fichier est ouvert en écriture seule,
2. Le marqueur < de la première instruction `:com.open` indique que le fichier est ouvert en lecture seule.

La seconde instruction `:com.open` écrit son résultat à l'adresse où se trouvait le périphérique d'accès au fichier en écriture : cet accès est immédiatement fermé, et remplacé par celui en lecture seule. Relancez l'application en mode débugueur. Avant la seconde instruction `:com.open`, vous devez obtenir :

Input/output
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | STR | "/tmp/file"
&1 | STR |
&2 | com.device | File > /tmp/file (11)
&3 | STR |

Alias | Pointer
--- | ---
file | &0*1
port | &1*1
device | &2*1
text | &3*1

Address:
Display
Aliases
P
Focus
Back
Clear

Et après l'instruction :

Input/output
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | STR | "/tmp/file"
&1 | STR |
&2 | com.device | File < /tmp/file (6)
&3 | STR |

Alias | Pointer
--- | ---
file | &0*1
port | &1*1
device | &2*1
text | &3*1

Address:
Display
Aliases
P
Focus
Back
Clear

## Commandes

Dans certaines circonstances, il peut être utile de lire ou écrire dans un fichier à une position différente. Modifiez le code pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Input/output example
    END
    LOG
    DEBUG "Input/output" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmrun.so"
    OPTION -f STR file
    OPTION -p STR port
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory com.device/device, STR/text
    		:com.open com.file > @&file -> &device
    		:com.write @&device "text    text"
    		:com.command @&device SEEK BEGIN 4
    		:com.write @&device "TEXT"
    		:com.open com.file < @&file -> &device
    		:com.read @&device com.all -> &text
    		:com.message @&text
    	END
    	MEMORY ".*"
    END

Puis lancez l'application :

    ./peripheriques.svm -f /tmp/file
    textTEXTtext

L'instruction `:com.command` permet de replacer le pointeur de lecture/écriture dans le fichier, ici placé sur le cinquième octet en partant du début du fichier.

# Connexions TCP/IP

Un autre type de périphérique fourni avec l'extension com permet d'utiliser les connexions réseaux TCP/IP.

## Client

Modifiez à nouveau le code :

    #!/usr/bin/env svm
    DESCRIPTION
    Input/output example
    END
    LOG
    DEBUG "Input/output" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmrun.so"
    OPTION -f STR file
    OPTION -p STR port
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory com.device/device, STR/text
    		:com.open com.tcp > "localhost" @&port -> &device
    		:com.write @&device "query"
    		:com.read @&device com.line -> &text
    		:com.message @&text
    	END
    	MEMORY ".*"
    END

Dans un premier terminal, lancez cette commande pour simuler un serveur réseau :

    echo reply | netcat -l -p 31415

Puis lancez l'application dans un autre terminal :

    ./peripheriques.svm -p 31415
    reply

Vous noterez la présence du mot "query" dans le terminal du serveur TCP/IP après l'exécution de l'application.

Ici, l'application a ouvert une connexion réseau en tant que client, a lancé une requête et obtenu une réponse.

Les seules différences avec un périphérique sur un fichier sont :

- L'instruction `:com.open` mentionne le type `com.tcp`, suivi de ">" pour indiquer une connexion en tant que client sur l'IP "localhost" et le port donné en argument de l'application,
- Malgré le symbole ">" dans l'instruction d'ouverture de périphérique, ce périphérique supporte les opérations de lecture et d'écriture.

## Serveur

Modifiez le code pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Input/output example
    END
    LOG
    DEBUG "Input/output" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmrun.so"
    OPTION -f STR file
    OPTION -p STR port
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory com.device/server, com.device/device, STR/text
    		:com.open com.tcp < "localhost" @&port -> &server
    		:com.command @&server CLIENT -> &device
    		:com.read @&device com.line -> &text
    		:com.message @&text
    		:com.write @&device "reply"
    	END
    	MEMORY ".*"
    END

Dans un premier terminal, lancez l'application :

    ./peripheriques.svm -p 31415
    query

Dans un autre terminal, lancez cette commande pour simuler un client réseau :

    echo query | netcat localhost 31415
    reply

Cette fois-ci, l'application se comporte en serveur réseau : elle attend une connexion venant d'un client sur l'instruction `:com.command`, puis attend une requête sur une ligne et répond.

Notez la présence de deux périphériques : le premier sert à attendre qu'un client se connecte, et le second sert à la communication avec le client. Lancée avec le débugueur, la mémoire contient après la lecture de la requête client :

Input/output
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | STR |
&1 | STR | "31415"
&2 | com.device | TCP socket < localhost:31415 (13)
&3 | com.device | TCP socket > < :::35150 (14)
&4 | STR | "query\n"

Alias | Pointer
--- | ---
file | &0*1
port | &1*1
device | &3*1
server | &2*1
text | &4*1

Address:
Display
Aliases
P
Focus
Back
Clear

Les deux périphériques sont ici visibles :

- Le premier, noté avec "<", est le périphérique permettant d'attendre les connexions depuis les clients, avec l'indication de l'IP et du port l'attente est réalisée,
- Le second, noté avec "> <", est le périphérique permettant d'échanger des données avec le client, avec l'indication de l'IP et du port éphémère de connexion.

Ici, l'application n'accepte qu'une seule connexion. Il est possible de placer le code dans une boucle pour traiter plusieurs clients successivement. Pour les traiter en parallèle, il faut jouer avec l'architecture de la machine. Cela est possible mais dépasse largement le cadre de ce didacticiel.

# Périphériques

Les fichiers et les connexions réseaux TCP/IP ne sont pas les seuls périphériques disponibles. D'autres extensions peuvent fournir des implémentations de périphérique.

Pour les utiliser, il suffit de préciser le type de périphérique avec ses arguments au moment de l'ouverture du périphérique.

Ensuite, les opérations de lecture et/ou d'écriture sont réalisées de la même manière que les fichiers. Les commandes en revanche peuvent varier pour d'autres périphériques.

- Les entrées/sorties fournies avec la machine virtuelle sont implémentées dans l'extension com. D'autres implémentations peuvent être créées et utilisées si nécessaire.
- L'extension com définit les entrées/sorties au travers de périphériques dont l'interface est commune à tous les périphériques, mais dont l'implémentation varie d'un type de périphérique à l'autre.
- D'autres extensions peuvent fournir l'implémentation de types de périphériques, qui seront utilisées à travers l'extension com :

- L'ouverture d'un périphérique est faite avec l'instruction `:com.open` suivie du type de périphérique et de ses arguments d'ouverture,
- La fermeture d'un périphérique est réalisée lors de la destruction de la valeur de type `com.device` donnant accès au canal d'entrée/sortie,
- Une lecture sur un périphérique est faite avec l'instruction `:com.read`, où la quantité d'octets à lire est indiqué par un point d'entrée d'extension (appellé protocole, car il indique comment découper le flux d'octets entrant en blocs atomiques et compréhensible par la suite du code) en argument,
- Une écriture sur un périphérique est faite avec l'instruction `:com.write`,
- Des commandes peuvent être envoyées aux périphériques avec l'instruction `:com.command`, où la commande et ses arguments sont spécifiques au type de périphérique.

# Conclusion

Vous venez de voir comment manipuler les entrées/sorties dans une application.

Une application complète utilise fréquemment des entrées/sorties pour réaliser des tâches, et les maitriser permet de créer des applications ayant une intégration fluide avec leur environnement.

L'extension com étant une interface avec quelques implémentations, il est vraiment utile de regarder attentivement les documentations de cette extension (avec la commande `man svm_plugin_com`) ainsi que la documentation des extensions proposant des périphériques et des protocoles.