OpenScheme : Plus loin avec les ports
Par :
Guilhem de Wailly (gdw@erian-concept.com)

et :
Fernand Boéri (boeri@unice.fr)

Résumé
Notre connaissance du langage Scheme nous permet maintenant d'entrevoir toute la puissance de ce langage en concevant des applications plus complexes. Cela est rendu possible en exploitant les possibilités d'OpenScheme couvrant un large spectre de fonctionnalités.
Ici, nous examinerons les extensions d'OSM en matière de ports, puis nous programmerons une petite application permettant de connaître le nombre de mails électroniques en attente sur un serveur POP (Cette application ne fonctionnera qu'avec la version 1.3.4 de l'environnement).
L'environnement OpenScheme est disponible sur le CDROM de Linux Magazine. Il est aussi disponible sur www.open-scheme.com. Cet environnement existe en version libre ou commerciale.
Port Scheme
Les ports sont des objets Scheme rattachés à des fichiers. Le langage Scheme standard ne propose que peu de fonctions pour manipuler les ports. Nous avons vu dans Linux Magazine n° 5 d'avril 1999 les fonctions standard d'entrées/sortie proposées par la norme du langage Scheme. L'article est disponibles en ligne à l'adresse http://www.linuxmag-france.org/lm5/sommaire.html.
Ces fonctions sont suffisantes pour effectuer des travaux simples sur les fichiers. 
Ce mois-ci, nous proposons d'examiner les extensions d'OpenScheme pour manipuler les fichiers. Notamment, OSM dispose de puissantes fonctions de manipulation de ports en mémoires ou de clients FTP ou HTTP. Dans un prochain article, nous utiliserons ces possibilités pour programmer des clients simples de services internet et/ou d'un environnement d'exécution parallèle sur plusieurs machines.
Le lecture se reportera à Linux Magazine n°5. Les fonctions définies par la norme du langage Scheme sont:
(call-with-input-file fichier proc)
(call-with-output-file fichier proc)
(current-input-port)
(current-output-port)
(with-input-from-file fichier proc)
(with-output-to-file fichier proc)
(open-input-file fichier)
(open-output-file fichier)
(close-input-port port)
(close-output-port port)
(input-port? objet)
(output-port? objet)
(read [port])
(eof-object? object)
(read-char [port])
(peek-char [port])
(char-ready? [port])
(load fichier)
(write objet [port])
(display objet [port])
(newline [port])
(write-char caractère [port]) 
Extensions OpenScheme
L'environnement OSM étend la notion de port aux ports virtuels. Parmi les ports virtuels, on trouve les chaînes de caractères (ports d'entrées/sorties en mémoire), les connexions réseau par sockets, les clients FTP ou HTTP... De plus, l'environnement propose une puissante fonction de sortie formatée ainsi que des fonctions d'entrée/sortie bas niveau.
Fonctions d'entrée / sorties de bas niveau
Dans la spécification du langage Scheme, les ports ne peuvent être accédés que de manière séquentielle de manière à lire des caractère ou des objets Scheme. OSM permet de lire toutes sorte de données et de le faire en accès direct.
A chaque port est associé un pointeur de fichier. Pour les ports en correspondance avec un fichier sur le disque, ce pointeur de fichier peut être consulté et/ou déplacé à loisir, permettant un accès direct à toutes les parties du fichier.
·	La fonction (port-tell [port]) retourne la valeur du pointeur de fichier sous la forme d'un entier. Si le port est omis, le port d'entrée standard est utilisé. 
·	La fonction (port-seek n [port]) permet de modifier la valeur d'un pointeur de fichier. Si port n'est pas renseigné, le port d'entrée standard est utilisé.
·	La fonction (port-length [port]) retourne la valeur maximale d'un pointeur de fichier. Cette valeur correspond en fait à la taille en octets du fichier. Si le port n'est pas indiqué, le port d'entrée standard est utilisé.
·	La fonction (port-read n [port]) lit n octets d'un port et retourne la chaîne de caractères formée des octets lus à partir du pointeur de fichier. Si port n'est pas indiqué, le port d'entrée standard est utilisé.
·	Enfin, la fonction (port-write str n [port]) écrit n octets de la chaîne de caractères str à la position du pointeur de fichier d'un port. Si port est omis, le port de sortie standard est utilisé.
Caractère spéciaux
Les chaînes de caractères destinées aux sorties peuvent contenir tous les caractères imprimables classiques. OSM reconnaît aussi des caractères spéciaux. Ceci sont préfixés par le caractères d'échappement anti-slash :
Caractère	Signification	
\n	Saut de ligne	
\r	Début de ligne	
\t	Tabulation	
\b	Signal sonore	

Par exemple :
Osm> (display "123\r456\t789\n012\n")
456 789
012
  => #unspecified
Sortie formatée
La fonction format permet l'affichage formaté des ses arguments sur le port spécifié. Sa syntaxe générale est (format port pilote arguments...). 
Les arguments :
·	port est soit un port de sortie, soit la valeur #t pour désigner le port de sortie courant, soit la valeur #f pour indiquer que la sortie doit s'effectuer dans une chaîne de caractères qui sera retournée.
·	pilote est la chaîne de format proprement dite. Tous les caractères qu'elle contient sont affichés tel quels. Certains caractères préfixés par le caractère tilde indique que l'argument correspondant doit être affiché selon la spécification désirée. 
Les spécifications sont :
·	~a ou ~A est remplacé par la représentation imprimable de l'argument correspondant.
·	~s ou ~S est remplacé par la représentation réelle de l'argument correspondant.
·	~d ou ~D est remplacé par la représentation imprimable de l'argument correspondant. Lorsque l'argument est une structure cyclique, le cycle est cassé et remplacé par trois points.
·	~w ou ~W est remplacé par la représentation réelle de l'argument correspondant dans lequel les cycles sont cassés et remplacés par trois points.
·	~x or ~X est remplacé par la représentation en hexadécimal de l'objet (valeur ou référence en mémoire).
·	~~ est remplacé par un simple tilde.
Par exemple :
Osm> (format #f "A test.")
  => A test.#unspecified
Osm> (format #f "A ~a." "test") 
  => "A test."#unspecified
Osm> (define l '(1 2 3))
  => #unspecified
Osm> (set-car! (cdr l) l)
  => #unspecified
     ; une liste récursive est construite
Osm> (format #t "list = ~w\n)
(1 ... 3)
  => #unspecified
Osm> (display l)
 => Segmentation fault
Lecture de lignes entières
Il est souvent utile de pouvoir lire un fichier lignes à lignes. Pour cela, OSM définit la fonction (read-line [port]) qui retourne une chaîne de caractères contenant la ligne en cours dans le port, s'il est spécifié ou dans le port d'entrée standard, sinon. 
Lorsque la fin du fichier est atteinte, la fonction retourne un objet validant le prédicat eof-object?.
Port en mémoire
Les ports virtuels ouverts en mémoire permettent de bénéficier des fonctions standard d'entrée/sortie. Le port en mémoire peut être ouvert en lecture avec (open-input-string chaîne). La fonction retourne un port d'entrée accessible par les fonctions standard :
Osm> (define port (open-input-string "12345"))
  => #unspecified
Osm> (let loop ([i 1])
      (let ([c (read-char port)])
       (if (not (eof-object? c))
        (begin
         (format #t "~a: ~a\n" i c)
         (loop (+ i 1))))))
1: #\1
2: #\2
3: #\3
4: #\4
5: #\5
=> #unspecified
Osm> (close-input-port port)
 => #unspecified
Lorsque tous les caractères ont été lus, les fonctions de lecture retournent la fin de fichier qui peut être testée avec la fonction eof-object?. Le port ouvert se referme avec la fonction standard.
Les fonctions call-with-input-string et with-input-from-string sont aussi disponibles et fonctionnent comme leurs homologues opérant sur les fichiers.
La fonction input-string? retourne #t si son argument est un port d'entrée en mémoire et #f sinon.
Un port en mémoire en sortie est ouvert avec la fonction (open-output-string). Cette fonction retourne un port de sortie sur lequel les fonctions de sorties standard sont utilisables. Lorsque le travail est fini, il est possible de convertir le port en chaîne de caractères avec la fonction (get-output-string port).
Osm> (define port (open-output-string))
  => #unspecified
Osm> (display "message" port)
  => #unspecified
Osm> (display "- autre message" port)
  => #unspecified
Osm> (get-output-string port)
  => "message - autre message"
Osm> (close-output-port port)
  => #unspecified
Les fonctions call-with-output-string et with-output-to-string se comportent comme leurs homologues sur les fichiers.
Répertoires locaux
Les fonctions d'ouverture de port d'OSM opèrent aussi sur les répertoires. Le comportement des fonctions d'entrée/sorties voient leur comportement adapté à ce type de port.
Un répertoire est ouvert simplement en indiquant son nom à l'une des fonctions opérant à l'ouverture des fichiers. Par exemple :
Osm> (define tmp (open-input-port "/tmp"))
  => #unspecified
Lorsque le port associé à un répertoire est ouvert en lecture, il retourne les noms des fichiers contenus dans ce répertoire, séparés par des retour chariot :
Osm> (let loop ([rep (read-line tmp)])
      (if (not (eof-object? rep))
       (begin
        (format #t "entrée: ~a\n" rep)
        (loop (read-line tmp)))))
entrée: 93499~df
entrée: 9e3499~df.tmp
entrée: 943327993-gtkrc-41306685
entrée: IMG_PARAMS.db
entrée: archive.500
entrée: axinstall.log
entrée: emacsEMFYx6
entrée: fvwmrcTPYp4h
  => #unspecified
La fonction port-seek permet de positionner le pointeur de fichier sur le numéro de l'entrée du répertoire correspondante, à partir de zéro. La fonction port-tell retourne le numéro de l'entrée courante.
Informations sur les fichiers d'un répertoire
Lorsque qu'un répertoire est ouvert, il est pratique de pouvoir obtenir des informations sur les fichiers qu'il contient. Pour cela, nous disposons de la fonction (port-stat nom [répertoire]). Cette fonction retourne un vecteur contenant les informations relatives au fichier nommé nom du répertoire spécifié. Si aucun répertoire n'est spécifié, le fichier d'entrée standard est utilisé. Si une erreur se produit #f est la valeur de retour.
Le vecteur retourné contient les éléments suivants :
·	#t si nom désigne un répertoire, #f sinon.
·	#t si nom désigne un fichier régulier, #f sinon.
·	#t si nom désigne un lien, #f sinon.
·	Une chaîne de caractères représentant les droits sur le fichiers, selon le format Unix.
·	La taille du fichier nom.
·	Date de dernière modification du fichier, sous la forme d'un réel. Ce réel peut être manipulé avec les fonctions date->string et date->vector.
·	Date de création du fichier, sous la forme d'un réel. 
·	Date de dernier accès du fichier, sous la forme d'un réel.
Le plugin OS offre plus de fonctions pour manipuler les fichiers et les répertoires, comme la copies de hiérarchies, le déplacement, la suppression, la gestions des droits, etc.
Port en lecture ET écriture
Par défaut, le langage Scheme permet d'ouvrir des fichiers en lecture ou en écriture mais pas les deux. Lorsque le fichier est ouvert en écriture alors qu'il existe déjà, le contenu précédant est perdu et la taille du fichier est fixée à zéro.
L'environnement OSM permet d'ouvrir des ports en lecture et écriture. Si le fichier existe, son contenu n'est pas perdu. Cependant, il sera écrasé si le pointeur de fichier n'est pas positionné à la fin de celui-ci. Les fonctions relatives à ces ports sont open-input-output-file, open-input-output-string et close-input-output-port.
De plus, les ports peuvent être ouverts en écriture de manière à ce que le contenu précédent ne soit pas altéré. Ces ports sont ouverts et fermés avec les fonctions open-append-file, close-append-port, call-with-append-file et with-append-to-file.
Connexion réseau
Les communications inter machines utilisent souvent les ports au sens de Unix ; l'objet ouvert est souvent connu sous le nom de socket. L'utilisation des sockets est maintenant disponibles sous d'autres environnement que Unix, comme des machines Windows ou Macintoch.
L'environnement OSM permet ouvrir des ports distants et d'y accéder avec les fonctions d'entrées/sorties standards.
Pour ouvrir un socket, on utilise les fonctions standard d'ouverture de fichier, comme open-input-port en précisant le nom du fichier de la manière suivante : socket://machine:port. Les sockets sont des ports permettant par nature l'écriture et la lecture. Aussi, on les ouvrira de préférence avec des fonctions bidirectionnelles comme open-input-output-file.
Les fonctions pour manipuler le pointeur de fichier sont inactives sur ce type de port. Les fonctions de lecture/écriture en mode caractères sont utilisables comme si le socket était un fichier local.
Connexions FTP / HTTP
Le protocole FTP (File Transfert Protocol) et le protocole HTTP (Hyper Text Transfert Protocol) sont très utilisés pour transférer des fichiers depuis ou vers un hôte distant. OSM permet d'accéder à ces protocoles en utilisant les fonctions standards d'ouverture. Pour cela, le fichier est spécifié de la manière suivante : ftp://[utilisateur[:pass]@]hôte/fichier ou http://[utilisateur[:pass]@]hôte/fichier
Le client HTTP ne sera disponible que dans la version 1.3.4 d'OSM prévue pour l'été.
Le nom d'utilisateur et le mot de passe sont optionnels. Le fichier peut spécifié soit un fichier ouvert en lecture soit en écriture. Les fonctions manipulant le pointeur de fichier sont actives.
Le plugin NET offre des clients plus complets sur les principaux protocoles réseau comme le mail, ftp, http. Il permet aussi de concevoir des scripts CGI de manière très simples.
Exemple
Dans cet exemple, nous utilisons les ports sockets pour nous connecter à un serveur de mail POP afin de savoir si nous avons des mails disponibles.
Le protocole POP est très simple. Voici à la main, comment nous pouvons connaître le nombre de mail en attente. 
Supposons que votre serveur de mail soit pop.serveur.fr et votre nom de login pour le mail soit cmoi et votre mot de passe mdp. Avec telnet, nous ouvrons le port 110 du serveur POP comme suit :
$ telnet pop.serveur.fr 110
Trying 192.101.21.253...
Connected to pop.serveur.fr.
Escape character is '^]'.
+OK POP3 pop.serveur.fr v5.12 server ready
USER cmoi
+OK User name accepted, password please
PASS mdp
+OK Mailbox open, 1 messages
LIST
+OK Mailbox scan listing follows
1 7503
. 
QUIT
+OK Sayonara
Connection closed by foreign host.
$
Sont marquées en gras les commandes que nous tapons. Nous commençons par nous identifier avec les commandes USER et PASS ; le nombre des messages est indiqués par le serveur. Avec la commande LISTE, nous obtenons la liste des messages ; lorsqu'il n'y a plus de message, un point est affiché. Enfin, la commande QUIT termine la session avec le serveur.
Tout cela est très simple, et nous allons programmer cela en OSM ! Notez que ce programme ne peut fonctionner qu'avec la version 1.3.4 d'OSM disponible sur le WEB ou dans le CDROM.
#! /usr/local/bin/osm --banner=off --exec

(define (mail? server user pass)
(let* (; port du serveur POP
       [port 110]
       ; adresse à ouvrir
       [url (format #f
                    "socket://~a:~a"
                    server
                    port)]
       ; le port lié à la socket
       [port (open-input-output-file url)]
       ; port pour le debug: 
       ;  #t écran, 
       ;  #f pas de message
       [debug #t])
  (if port
   (begin
    ; lire l'invite du serveur
    (format debug "~a\n" (read-line port))
     
    ; envoyer le nom d'utilisateur
    (format port "USER ~a\r\n" user)
    ; lire le résultat
    (format debug "~a\n" (read-line port))
     
    ; envoyer le mot de passe
    (format port "PASS ~a\r\n" pass)
    ; lire le résultat
    (format debug "~a\n" (read-line port))
     
    ; demander la liste des messages
    (display "LIST\r\n" port)
    ; lire le résultat
    (format debug "~a\n" (read-line port))
     
    ; lire la liste des messages
    (let ([n (let loop ()
              (let ([l (read-line port)])
               (format debug "~a\n" l)
               (if (or (eof-object? l)
                       ; si c'est un point
                       ; terminer
                       (eqv? l "."))
                 0
                 ; ajouter un et continuer
                 (+ 1 (loop)))))])
       
      ; terminer la session
      (display "QUIT\r\n" port)
      ; lire le résultat
      (format debug "~a\n" (read-line port))
      
      ; fermer le port
      (close-input-output-port port)
       
      ; retourner la valeur
      n))
    (error "Incapable d'ouvrir l'URL ~a"
           url))))

; programme principal
(let ([n (mail? "pop.serveur.fr" "cmoi" "mdp")
  (format 
   #t
   "Vous avez ~a mail~a en attente\n"
   n
   (if (> n 1) "s" "")))
Le lecteur pourra s'il le désire consulter les RFC (Request For Comments) concernant le protocole POP et réaliser un client POP complet avec réception et émission de mail.
Nous sommes maintenant parés pour utiliser toutes les fonctionnalités concernant les ports et nous en ferons abondamment usage par la suite.
Les auteurs

Guilhem de Wailly, directeur de la société Erian Concept : support, formations, configurations, administration, développements Linux. Environnement OpenScheme.
http://www.erian-concept.com

Pr Fernand Boéri, Professeur à l'Université de Nice - Sophia-Antipolis, membre de l'unité SPORTS du laboratoire I3S, spécialiste en modélisation, évaluation de performances et architectures parallèles.
http://www.i3s.unice.fr
Références
Scheme
¨	Structure et Interprétation des programmes informatiques
H. Abelson, GJ. Sussman
InterEdition
¨	The Scheme Programming Languages - Ansi Scheme 
R. Kent Dybvig
Prentice Hall
¨	Les langages Lisps - Christian Queinnec
InterEdition
¨	Programmer avec Scheme - J. Chazarin
Thomson Publishing
¨	Revised4 Report on the Algorithmic Language Scheme
W. Clinger, J. Rees
ftp://ftp.nj.nec.com/pub/kelsey
Environnements Scheme
¨	Scm - A. Jaffer
http://www-swiss.ai.mit.edu/~jaffer
La référence des interprètes Scheme. Très petit, rapide, pour beaucoup de plates-formes, extensible.
¨	PCScheme - Texas Instrument
ftp://cui.unige.ch/public/pcs/pcscheme.exe
Un très bon environnement de programmation Scheme pour DOS, avec éditeur intégré.
¨	DrScheme - Rice University
http://www.cs.rice.edu/CS/PLT/
Environnement Scheme libre très avancé.
¨	ChezScheme - Cadence, Inc
http://www.scheme.com/
Environnement Scheme très performant.
¨	Inlab Scheme - Inlab Software GmbH
http://www.munich.net/inlab/scheme/
Environnement commercial
¨	EdScheme, 3Dscheme - Scheme, Inc
http://www.schemers.com/
Environnement de programmation Scheme pour Windows.
¨	OpenScheme - Erian Concept
http://www.open-scheme.com
Environnement professionnel de programmation Scheme comprenant un interprète, un compilateur et un débogueur symbolique. 
Existe en version libre et commerciale, pour Linux, FreeBSD, Solaris, Windows et BeOS, sur systèmes Intel.