Ravi
Welcome
Documentation
Documentation
About Ravi
Documentation
Introduction
Premiers pas avec Ravi
ravitool
Scheme Tutorial
Objets Scheme
Le shell ravi
Starting Ravi
Le module trace
Les ports d'E/S
The C Parser
load, require, modules
Système d'interruptions
Scheme compiler
C++ mode
Generating C++ Modules
La déclaration struct
Le type "C-object"
More information
Installation

[PREV][SUIV]

Scheme compiler

On trouve sur cette page:

The scheme byte code compiler

On peut compiler un ou plusieurs fichiers avec des programmes en Scheme et créer ainsi un module Ravi.

Commande de base (sous Unix) pour un fichier nom_fichier.scm:


	ravi -mod compile nom_fichier {options}

Les options sont

  • -cs compile in case sensitive mode. C'est le mode par défaut dans l'équipe Prima, en contradiction avec le standard Scheme!
  • -ci case insensitive
  • -system system compilation (active le mode ci).

La compilation (on voit que le compilateur est lui-même un module Ravi) crée un fichier nom_fichier.mobj avec le code objet. On rappele que la fonction load, appelée par


	(load "nom_fichier")
va charger en priorité la version compilée, si les deux versions .scm et .mobj existent (cf. description de load).

Très généralement, la compilation apporte plusieurs avantages:

  • gain de vitesse à l'exécution, par un facteur 5-15.
  • Modularité: partant d'un ou de plusieurs fichiers source, la compilation crée un module qui exporte certains symboles seulement; cela simplifie beaucoup le problème de collision de noms dans une application intégrée.

Utilisation simple du compilateur

Si le fichier à compiler contient rien que des définitions de fonctions, la compilation ne demande aucune précaution particulière.

Dans d'autres situations, le fichier à compiler contient des déclarations permettant de cibler le travail du compilateur.

Normalement, la compilation crée un module qui exporte certaines fonctions seulement : c'est la déclaration export, la plus importante.

La forme du fichier à compiler est alors


	(declare (export f1 f2 f3 ..))
	(define f1 ...)
	...

Si le fichier contient des "initialisations" - des expressions autres que des définitions de fonctions - une précaution simple est exigée: toute utilisation d'une variable doit se trouver après le define qui introduit la variable.

Par exemple


	(declare (export f1 f2 f3 ..))
	(define col 13)  ; nb : col n'est pas exporté
	(define f1 (+ col 3))
	...

Généralités sur le compilateur

On compile seulement du code bien mis au point.

Restrictions

  • Ordre des déclarations, macros
  • include
  • require

  • Les appels de fonctions à l'intérieur du module deviennent inaccessibles : on ne peut pas les tracer, ni redéfinir les fonctions; la déclaration export-only permet d'empêcher cela.
  • Limitations dans le code interprété.

La fonction include

La fonction include a pour effet d'inclure, textuellement, le code figurant dans un fichier:

(include "file_name")

Le code d'un module à compiler peut donc être découpé en plusieurs fichiers.

Remarques:

  • Le include ne peut pas être imbriqué dans une expression, il est nécessairement au niveau 0
  • L'interpréteur traite le include comme un load.

Structure d'un module

Le chargement du code de module à proprement parler demande parfois quelques précautions. Il peut être complété par un "prélude" et un "postlude".

Pour cela on utilise des déclarations

(open-prelude)
Cette déclaration doit être placeé en tête de fichier, avant toute fonction.
(open-module)
Annonce la fin du préfixe, et le début du module à proprement parler.
(close-module)
Fermeture du module, début du postlude.

Le prélude est une séquence d'expressions interprétées, en dehors de l'environnement du module, et avant le chargement du module. Etant donné que le chargeur Ravi n'est pas ré-entrant, le compilateur construit automatiquement un prélude, avec tous les require et load rencontrés dans le fichier source. On peut contrôler cela plus finement avec un prélude explicite. En particulier, tout require ou load conditionnel doit se trouver dans le préfixe.

Toutes les déclarations introduites dans le postlude sont mémorisées par le compilateur, pas besoind de les repéter dans un declare.

L'exécution du postlude se fait en interprété, après chargement complet du module. L'exécution du postlude peut donc conduire à un nouveau chargement sans problème; notamment, un postlude permet le lancement d'un module.

Les déclarations

Les déclarations permettent de préciser quelques choix ; seule la déclaration export est utile pour tout le monde, les autres servent dans des cas assez particuliers, ou carrement pour les développeurs.

(export n1 ... )
Liste des exports: fonctions, macros, ou variables
(export-only n1 ...)
empêche toute utilisation directe à l'intérieur du module: permet de tracer ensuite des appels internes, ou de redéfinir la fonction.
(use m)
use module à faire
(external n1 ...)
Les noms n1 ... ne seront pas inclus dans l'avertissement final.
(case-sensitive #t/#f)
(eval expr)
Provoque l'évaluation de expr par le compilateur

(variable valeur)
Modification d'une variable interne du compilateur.

Les variables modifiables sont:

gen-code
switch de génération de code
header-file

	#t : entête dans .mobj
	#f : pas d'entête
	string : nom du fichier d'entête
	
*trace-level*
#f : pas de trace >0 : dégré de trace
*export-all*
exporter toutes les définitions qui suivent
*system-compilation*
*optimize* *no-optimise*
*epilogue*
fonction appelée à la sortie du compilateur
*xtended-i-set*
*compress*
*new-case*
*histogramme*
*extract* *type* *graphe* *lifting*
*restructletrec*
*case-sensitive*
*compiler-timing*
*compiler-profile*

Les subtilités des déclarations d'export

Traitement non-homogène fonction - variable

On compile seulement les lambda-expression, ce qui introduit une distinction entre "définition de fonction" et "définition de variable", distinction qui est totalement contraire aux principes de Scheme. {Selon les principes, une variable possède une valeur, laquelle valeur peut être une procédure/fonction, ou autre chose - les fonctions sont des objets "1ière classe"}.

(export n)

Si n est une variable "non-compilée", elle sera une variable globale. Toute affectation à n par un set! modife sa valeur.

Si n est une fonction, il y a définition globale de n, mais il y a aussi une définition "privée" dans le module: une affectation à n ne va pas changer cette "définition interne".

(export-only n)

Si n est une fonction, c'est une vraie fonction globale - toute affectation à n modifie aussi les appels internes au module.

Si n est une macro, sa définition est exportée, mais elle n'est pas utilisée pour la compilation du module!

Macros

Il existe 3 configurations pour une définition de macro-fonction.

Pas de déclaration: la macro est utilisée pour la compilation du module, mais elle ne sera pas exportée. C'est le cas normal.

Déclaration export: la macro est utilisée pour le module, et également exportée.

Déclaration export-only: la macro est seulement exportée, elle n'est pas utilisée pour le module. Cas rar.

Dans tous les cas, la définition d'une macro doit précéder sa première utilisation dans le texte du programme. Sinon le compilateur sort un warning obscur.

Si une macro est utilisée sans être définie à la compilation, c'est le code compilé qui va planter:

apply : illegal function | It is a kScTypeCons

D'une facon générale, l'emploi de macros est périlleux - c'est pour des bonnes raisons qu'elles ne figurent pas dans la définition de Scheme, où elles sont remplacées par les syntax-transformer

Voir le livre de Chazarain pour une bonne explication.

Utilisation manuelle du compilateur

Pour deboguer ou modifier le compilateur, on l'utilise "à la main". Il y a alors un nombre considérable de traces et autres possibilités de suivre de prés ce qui se passe.

Si la compilation provoque une erreur Scheme


  (require 'ccsm)
  (csm "fichier1")  ; compilation de fichier1
  (csm "fichier2")  ; de fichier2

Si le compilateur plante, utiliser (error-frame) pour localiser l'endroit.

==============================================