INTRODUCTION A SIMULA

Jean G. Vaucher
(Mis a jour: aout 1997)
Le but initial de ce document etait de permettre a une personne ayant déjà programmé en Pascal de passer rapidement à SIMULA. L'experience a demontre que meme pour des neophytes de la programmation, il demeure un aide memoire utile qui complémente bien le texte plus étoffé de Kirkerud, Object-Oriented Programming with SIMULA, Addison-Wesley (1989). La référence de base reste la norme du Swedish Standards Institute, le rapport SS 63 61 14, disponible en réserve à la bibliothèque.

Le lecteur trouvera aussi une liste de references sur SIMULA à la fin du texte ainsi que des annexes qui reprennent la documentation UNIX des systemes SIMULA disponibles sur nos machines.

Pour des raisons historiques, certaines portions de ce document ne contiennent pas d'accents.



1. UN PROGRAMME COMPLET

Voici un programme complet qui montre l'allure de SIMULA . Ceci nous permettra de faire quelques commentaires generaux.

begin

! programme simple ; integer i,j; integer carre ; i:= inint; j:= inint;

carre := i*i; outtext(" i carre somme"); outimage; outint(i,5); outint(carre,5); outint(i+j, 6); outimage; end

Un programme SIMULA est un BLOC selon la syntaxe :

   BEGIN
     < declarations >
     < enonces >
   END

A part ce fait, ce programme n'est pas tres different de programmes en Pascal ou on C. Notez, cependant, que les delimiteurs de commentaires[1] ("!" et ";"), et ceux pour les chaines de textes sont différents de ceux de PASCAL.

Au DIRO, nous avonc deux compilateurs pour Simula: le compilateur industriel de Lund Software et le partagiciel portable d'OSLO (cim). Pour faciliter l'emploi des ces systemes ( et l'acces a nos librairies graphiques), il est recommande de ne pas utiliser ces compilateurs directement; mais de passer par des scripts (en PERL): sim pour LUND et sc pour CIM.

Si ce programme est situe dans le ficher "simple.sim", on pourra le compiler et l'executer avec les commandes suivantes:

	% sim  simple
ou
		% sc simple

2. DECLARATIONS DE BASE

Les types de base sont: entier, reel, booleen, caractere, reference (pointeur) et texte (chaine de caracteres). Toute variable est initialisee automatiquement a une valeur neutre: 0, false, none (nil) ou notext (chaine vide). Voici le style des diverses declarations:

Pascal Simula Constantes
I: integer; integer I; 12
R: real;
real R ;
long LR;
13.4 1.3&-5
1.3&&-5
B: boolean; boolean B; true false
C: car; character C; 'C'
P: ^xxx; ref(xxx) P; none
T: alfa; text T; "chaine"

On peut aussi declarer des constantes en ajoutant une valeur dans la declaration:

Simula Pascal
	  
	integer  nMax = 100;
	real  Pi = 3.1416;	
const
     nMax = 100;
     Pi = 3.1416;

Le compilateur utilise ces déclarations pour:

  1. vérifier le sens des opérations (p.e. empécher l'addition d'un texte et d'un booléen)
  2. réserver des zones de mémoire pour stocker les informations

3. ENTREE/SORTIES

Les entrees/sorties sont en format libre et portent sur les entiers, reels, caracteres et chaines. Pour chaque type on a une fonction pour l'entree et une procedure pour la sortie qui transferent une valeur a la fois. Pour les ecritures, on doit specifier la taille des champs.

Entrées/Sorties en Simula
Lecture Impression
Integer I:= inint ; outint(I,10) ;
Real X:= inreal ; outreal(X,2,10);
Character C:= inchar ; outchar( C ) ;
Text T:= intext(20) ; outtext(" OK !") ;

Autre operations
Sauter a la ligne Outimage ;
Afficher sans saut de ligne BreakOutimage ;
Test de fin de fichier B := endfile ;
Test de fin de données B := lastitem ;


Comparaison avec PASCAL
Pascal Simula
read(I,R,C); I:= inint;
R:= inreal;
C:= inchar;
readln; inimage; inimage;
B:= eof; B:= endfile; (vrai si on est a la fin)
B:= lastitem; (on saute les blancs avant de decider)
writeln(I,R:10:2,C,'OK');
outint(I,5);
outreal(R,2,10); ...gare a l'ordre
outchar(C);
outtext("OK");
outimage;

4. ARITHMETIQUE

Les calculs se font par des énoncés d'affectations de la forme suivante:

	variable := expression

Cet énoncé veut dire qu'on calcule d'abord la valeur de l'expression; puis on stocke cette valeur dans la variable. Par exemple:

	y := x + 1
veut dire: ajouter 1 à la valeur de x et mettre le résultat dans la variable y. Si x valait 4, alors, après cet énoncé, y vaudra 5. Dans le cas d'expressions de valeurs numériques, les variables ( x et y dans l'exemple) doivent être de type integer ou real.

Notez que les caractères de l'opérateur d'affection, ":=", doivent être collés côte à côte et la combinaison ":=" compte comme un seul symbole en Simula.

Dans les expressions arithmétiques, les opérateurs sont sensiblement les mêmes qu'en mathématiques:

	addition:         "+"
	soustraction:     "-"
	multiplication:   "*"
	division réelle:  "/"
	division entière: "//"   ( et la fraction est tronquée )
	exponentiation:   "**"     X ** i veut dire X à la puissance i

Ici encore les caractères des opérateurs composés, "//" et "**", doivent être côte à côte.

Remarquez que la multiplication, qui est souvent implicite en notation mathématique, doit être notée explicitement. Donc l'expression mathématique (4a + b)(3c + d) devra être écrite comme suit en Simula:

	(4 * a + b) * ( 3 * c + d )

En Simula, il y a deux opérateurs de division, "/" et "//". L'opérateur "/" dénote la division usuelle et le résultat est toujours de type real:

	4  /  3   donne   1.333333...
et	4  /  2   donne   2.0

L'opérateur "//" dénote la division entière. Les opérandes doivent être de type integer et le résultat sera un integer. Si résultat exact de la division comporte une fraction, celle-ci est ignorée. Donc,

	 4  //  2	  donne   2
	11  //  3	  donne   3
	-7  //  3	  donne  -2
	 7  // -3	  donne  -2
	 5  //  1.3	  donne ERREUR car 1.3 n'est pas un entier

On peut récupérer le reste de la division de p // q avec la fonction rem(p, q). Reprenant les mêmes exemples de division:

	rem( 4,2 )	donne   0
	rem(11,3 )	donne   2
	rem(-7,3 )	donne   -1
	rem(7,-3 )	donne   1

Note:

  1. Le RESTE de la division a toujours le même signe que le dividende.
  2. La relation suivante existe entre les opérateurs "//" et "rem":
    	(A//B) * B + rem(A,B)  =   A
    
    C.a.d.	Quotient * Diviseur + Reste = Dividende
    
Il existe une autre fonction qui est reliée à la division; c'est mod(P,Q) qui donne la fonction mathématique du modulo. Si P et Q sont positifs alors rem(p,q) et mod(p,q) donne le même résultat; autrement, la relation suivante est vraie:
	entier(A/B) * B + mod(A,B)  =   A
Note: entier(X) est équivalent à floor(X) et est décrit dans la section sur les types.

Le dernier opérateurs que nous avons étudié est l'exponentiation: "**". En général, le résultat de l'exponentiation est une valeur real. Le seul cas où le résultat est un integer, c'est quand les deux opérandes sont de type integer. Dans ce cas, l'exposant doit être zéro ou positif.

	2.0 ** 3	donne   8.0
	9   ** 0.5	donne   3.0
	9.1 ** 0	donne   1.0
	4.0 ** (-2)	donne   0.5
	2   ** 3	donne   8
	2   ** 0  	donne   1
	2   ** (-1)  	donne   ERREUR

Voici la priorité des opérateurs (y compris les opérations logiques):

          1.  **
          2.  *  /   //
          3.  + - &
          4.  =  <> > >= < <=  ==  =/=
          5.  not
          6.  and
          7.  or
          8.  impl         9.  eqv
         10.  and then    11.  or else
Note:

Une expression peut aussi utiliser des procédures utilitaires comme sin (sinus), sqrt (racine carrée) ou ln (logarithme naturel).

L'annexe 2 donne une brève description des procédures utilitaires qui sont fournies avec tout système Simula. L'exemple ci-dessous fait des calculs numériques:

	begin
		real x, y, z;
		

outtext("Y et Z svp: "); breakoutimage; y := inreal; z := inreal; x := sqrt( y ** 2 + ln(z) ** 2); outtext("X = "); outreal(x,3,10); outimage; end

5. TYPES NUMERIQUES

En Simula, nous avons 2 types numériques de base: les entiers (integer) et les réels (real). Les valeurs entières s'écrivent avec juste des chiffres (et possiblement un signe) tandis que les réelles s'écrivent avec soit un point décimal ou un exposant ("&"). Par exemple:

	entiers:   0  12   -456
	réels:     0.0   1.234&-4   &6

Les entiers servent à compter les choses ou faire des calculs d'adresses. Les réels sont utilisés pour des calculs scientifiques, où pour lesquels on utilise des fractions (p.e. 3.14159), des valeurs très grandes comme la constante d'Avogadro (6.024&23 atomes/mole) ou très petites, comme la masse d'un électron (9.1083&-28 g).

Conversion de type

Typiquement, on mettra des valeurs entières dans des integer et des réels dans des real; mais on peut mélanger les types dans une expression ou affecter une valeur d'un type à une variable de l'autre type. Dans ce cas, il y a conversion automatique de la valeur d'un type à l'autre. En général, quand on passe d'un entier à un réel, la valeur est inchangée. Par contre, quand on passe d'un réel à un entier, la fraction est enlevée pour faire un entier. Dans certain langage la valeur est tronquée; en Simula, la valeur est arrondie et on retourne l'entier le plus proche.

	integer i;
	real    x;
	

	i := 123.45;      !  i vaut 123   ;
	i := 123.5;       !  i vaut 124   ;
	i := -3.6  ;      !  i vaut -4    ;
	x := 3;           !  x vaut 3.0   ;
Il existe une fonction en Simula, entier, qui permet de passer d'un réel à un entier d'une façon différente. entier implante la fonction connue parfois sous le nom de floor: entier(x) retourne l'entier maximum qui est plus petit ou égal à "x":
	entier( 3.4 )  ==>   3
	entier(-3.4 )  ==>  -4 

Expressions de type mixte

Pour les 3 opérateurs 'simples' (+, - et *), le type du résultat dépend de celui des opérandes. Si les deux opérandes sont de type integer alors le résultat est integer; si une des opérandes est real alors le résultat est real.

   1   + 2   ==>  3
   1.0 + 2   ==>  3.0

par contre, nous avons vu que certains opérateurs arithmétiques exigent et/ou retournent des valeurs d'un certains type. Par exemple, la division "/" retourne toujours un real; la division entière exige des opérandes entiers et retourne un entier.

   4 / 2         ==>  2.0
   rem( 4.0, 2)  ==>  Erreur car 4.0 n'est pas un entier 
Une catégorie importante de fonctions standards sont disponibles pour faire des conversions entre le type integer et les autres types.

Limites sur les valeurs

Toute information sur ordinateur est stockée dans des cases mémoire de taille fixe et donc le nombre de valeurs distinctes qui peuvent être représenté est fini.

Pour les entiers sur nos machines, ceci veut dire que 2^32 valeurs sont possibles et elles vont de -2147483648 à 2147483647. Deux constantes prédéfinies, maxInt et minInt, représentent ces valeurs.

Les réels sont stockés avec (environ) 7 chiffres de précision et les valeurs vont de 3.4 x 10^38 à 1.1 x 10^-38.

7. AUTRES DECLARATIONS

Pascal a une structure tres elaboree de declarations. SIMULA est moins riche mais plus tolerant. L'ordre des declarations importe peu et on n'a pas besoin d'eviter les references FORWARD.

Les tableaux SIMULA sont dynamiques et les bornes peuvent etre des expressions quelconques qui sont evaluees a l'entree du bloc ou le tableau est declare.

PASCAL:
A,B : ARRAY [1..10,2..5] OF REAL;
SIMULA:
REAL ARRAY A,B (1:10,2:5);
Mais aussi:
       BEGIN
         INTEGER N; .....  N:=10;
         BEGIN
           INTEGER ARRAY A (1:N);
           INTEGER ARRAY B (1+ININT : 2*N);
           ...
         END
       END
Par contre, les index sont limites a des valeurs entieres et l'affectation entre tableaux ( A:= B ) n'est pas permise.

Le seul constructeur de type, CLASS, declare des types de structures dynamiques. On montre ici une application simple mais nous verrons que le concept de CLASS fait la force de SIMULA.

  SIMULA                 PASCAL
     
     CLASS COMPLEX; TYPE
     BEGIN                  COMPLEX= STRUCT
       REAL R,I;                R,I: REAL;
     END;                    END;
        VAR
     REF(COMPLEX) C;        C: ^COMPLEX;
     ...                    ...
     C:- NEW COMPLEX;       NEW (C);
     
Nous verrons les classes en plus de dŽtails plus tard ainsi que la dŽclaration de procŽdures.

Les etiquettes SIMULA sont des identificateurs declares implicitement par leur apparition devant un enonce:

     L: ;........; GOTO L;

7. ENONCES

AFFECTATION
I:= I+1; mais aussi, I:= J:= K:= I+1;

Pour les pointeurs on utilise ":-" : P1 :- P2;

IF:
if C then <enonce autre que IF> else <enonce> Note: on permet aussi des expressions conditionnelles: I := if A>B then I+1 else I-1;
BLOC:
begin <possiblement des declarations>; <suite d'enonces > end
GOTO:
TOTO:......; goto TOTO;
WHILE:
while I<10 do I:=I+1;
FOR:
En gros il y a la boucle avec un pas (variable) et celle avec une liste de valeurs. Avec une liste, la variable de controle peut etre de type arithmetique, caractere, texte, booleen, ou pointeur. for I:= 1 step I+1 until 100 do ...; for I:= 100 step -1 until 1 do ...; for I:= 1,I+3 while I**2 < 100 do ...; for I:= 2,3,5,7,11 step 4 until 51 do ...; for C:= 'A','E','I','O','U','Y' do ... ; for P:- P1,P2,P3 do ...;

9. PROCEDURES

Par defaut, les parametres simples sont passes par valeur et les tableaux sont passes par reference. On peut modifier ceci avec les listes VALUE et NAME (noter que le NAME d'ALGOL est plus puissant que le VAR de PASCAL). Pour plus de details voir SIMULA REF MANUAL p. 3.62.
PASCAL: 

     PROCEDURE ADD (A,B: INTEGER; VAR C:INTEGER);
     BEGIN
       C:= A+B;
     END;
     
     FUNCTION SUM (A,B:INTEGER) : INTEGER;
     BEGIN   SUM:= A+B   END;


SIMULA: 

     PROCEDURE ADD (A,B,C);
       NAME C; INTEGER A,B,C;
     BEGIN
       C:= A+B;
     END;
     
     INTEGER PROCEDURE SUM (A,B); INTEGER A,B;
       SUM:= A+B;

L'utilisation dans les deux cas serait: 
     ADD (1, SUM(I,J), K);
Le corps d'une procedure est un seul enonce (voir SUM). Assez souvent cet enonce est un bloc (voir ADD). Quand on passe un tableau, on ne doit pas specifier les bornes:
     PROCEDURE MATMUL (A,B,C) ; REAL ARRAY A,B,C ;
Voici maintenant plus de details. En SIMULA, il y a 3 modes de passage de parametres:
     par VALUE    : 	comme en PASCAL
     par REFerence: 	a peu pres comme le VAR de PASCAL
     par NAME     : 	comme en ALGOL 60

Le passage par nom (NAME) est equivalent a un remplacement textuel de chaque occurence d'un parametre formel (PF) par le texte du parametre actuel (PA) correspondant. Chaque reference a un PF implique donc une re-evaluation du PA. Avec le passage par valeur ou par reference, il existe pour chaque PF une variable locale initialisee selon la valeur du PA calculee une fois a l'appel de la procedure (ou creation d'objet).

Par defaut, les variables simples (int,real,bool et char) sont passees par valeur et les autres par REFerence. Le mode par defaut pour un parametre peut etre change en mettant l'identificateur du parametre dans des listes NAME ou VALUE avant la specification du type des parametres.

Pour les classes, NAME ne peut pas etre employe comme mode de passage. On ne peut pas non plus passer des procedures, etiquettes ou switch. Ceci evite la possibilite de reference a des variables locales de blocs disparus.

Voici 2 tableaux qui resument les modes de passage possibles. Un "D" indique le mode par Defaut, un "O" indique un mode Optionnel obtenu avec une specification NAME ou VALUE, et "X" indique un mode impossible ou defendu.

   PARAMETRES DE PROCEDURES 
   -------------------------
                                mode de passage
     type de parametre          VALUE  REF NAME
 
  - real,int,bool ou char           D   X   O
  - text ou
     array de (real,int,bool,char)  O   D   O
  - ref(..) ou
     array de (ref,text)            X   D   O
  - procedure,label ou switch       X   D   O
 
 
  PARAMETRES DE CLASS
  -------------------
                               mode de passage
     type de parametre           VALUE   REF   
 
 - real,int,bool ou char            D   X
 - text ou
    array de (real,int,bool,char)   O   D
 - ref(..) ou
    array de (ref,text)             X   D

Ces tableaux semblent a premiere vue un peu compliques sans raison. Par exemple, pourquoi ne peut-on pas passer une REFerence par VALUE ou quelle est la difference entre le passage d'un texte par VALUE ou par REFerence? La reponse est que la notion de passage par valeur, dans le cas d'une structure de liste ou de structures pointees, est quelque peu ambigue. Le passage par valeur est suppose empecher les effets de bords en creant une copie locale du PA. En fait le passage par value d'un pointeur en PASCAL protege efficacement la variable pointeur passee comme parametre; mais le contenu de l'objet pointe n'est nullement protege. Dans ce cas, un VRAI passage par valeur exigerait une copie complete non seulement de l'objet pointe mais aussi de tout autre objet atteignable par des pointeurs dans cet objet. Pour les pointeurs (variables REF), ce qui serait appele en PASCAL le passage par valeur est appele en SIMULA passage par reference et le passage par VALUE est interdit, C.A.D. le pointeur utilise en parametre est protege, mais la structure pointee ne l'est pas.

A peu pres la meme chose s'applique au TEXTs. Une variable TEXT en SIMULA est un descripteur qui contient, entre autre, un pointeur vers une chaine de caracteres et un indicateur, POS, de l'endroit ou on est rendu dans le traitement de cette chaine. Dans ce cas, le passage par defaut (mode REF) implique une copie locale du descripteur: le descripteur (PA) est donc protege mais la chaine elle meme peut etre changee. Pour le passage par VALUE, on fait une copie et de la chaine et du descripteur (avec POS remis à 0).

10. TEXTES

Les chaines de caracteres en SIMULA sont gerees dynamiquement et les procedures d'acces et de traitement des chaines favorisent le traitement sequentiel des caracteres. On remarquera donc une analogie aux entrees/sorties. A chaque chaine est associe un descripteur contenant l'adresse de la chaine, sa longueur et l'index du prochain caractere a traiter. Une variable de type TEXT designe ce descripteur. Initiallement, les variables de texte designent NOTEXT, la chaine vide (equivalent a NONE).

La creation de chaines se fait avec une de deux fonctions:

   BLANKS(N)  : qui cree une chaine de N caracteres blancs
   COPY (T)   : qui cree une copie de T.
 
	 TEXT T1,T2;
	 ...
	 T1 :- BLANKS(80);
	 T2 :- COPY("** IFT 3040 **");

Les operateurs d'affectation et de comparaison peuvent porter sur les descripteurs ( :-, ==, =/= ) ou sur le contenu des chaines ( :=, =, NE ). Voici l'quivalent PASCAL ou on aurait

	T1,T2 : ^ ALFA .
 
	 SIMULA 		PASCAL
	 
	 T1:- T2;		T1 := T2;
	 T1:= T2; 		T1^:= T2^;
	 
	 T1 == T2		T1 = T2
	 T1 NE T2		T1^ <> T2^

On peut aussi tester l'ordre lexicographique de chaines avec >,>=,< et <=.

Operateur de concatenation de chaine: &

	text T;
	...
	outtext( "Error:" & T  & "in system") ;

La manipulation du contenu de chaines se fait a l'aide de procedures et fonctions et la notation (que nous reverrons avec les CLASS) est celle utilisee en PASCAL pour l'acces a des champs de STRUCT: la notation de point.

T.LENGTH:
donne la longueur de la chaine T

T.POS:
donne l'index du prochain caractere a traiter

T.MORE:
TRUE si T.POS > T.LENGTH, FALSE autrement

T.GETCHAR:
retourne prochain caractere et incremente T.POS

T.PUTCHAR(C):
place le caractere C a l'endroit POS et incremente POS.
Remarquer la ressemblance avec INCHAR et OUTCHAR.

T.SETPOS(N):
donne la valeur N a T.POS

T.GETINT et T.PUTINT(I):
a peu pres equivalent a ININT et OUTINT

T.SUB(I,N):
designe une sous chaine de T de longueur N debutant a la position I. Remarquer qu'on ne cree pas de copie. SUB est utile pour avoir acces au champs de record formates.

T.STRIP:
retourne la sous chaine T.SUB(1,N) ou tous les caracteres apres le n-ieme sont blancs.

11. CLASS et objets

La puissance de SIMULA reside dans son concept de CLASS: la specification d'un ensemble d'objets du meme type..

Notez que Simula a ete le premier langage a utiliser le concept d'OBJET; et ce depuis 1967. Le concept a ensuite ete repris pour la programmation graphique interactive par Smalltalk. Ce qui a vraimment lance la programmation objet sur le marche nord-americain c'est C++, un C ameliore concu par B. Stroustrup, un fan de Simula qui voulait faire de la simulation dans l'environnement C de Bell Labs. Et donc, 20 apres son invention par Dahl et Nygaard, la programmation objet a redecouverte par le reste du monde.

Une declaration de CLASS definit un type d'OBJET dynamique. Ces objets ressemblent au RECORDs de PASCAL dans le sens qu'ils ont des attributs (champs) accessibles par notation pointee (ex: P.A ) et ils ressemblent aux procedures dans le sens qu'une CLASS peut avoir des enonces et des parametres. En plus, la PREFIXATION permet le traitement rationnel d'objets avec variantes et les objets peuvent fonctionner en COROUTINES. Finallement, SIMULA gere l'allocation dynamique de facon tres securitaire en initialisant tout pointeur a NONE et en recuperant automatiquement l'espace occupe par des objets inaccessibles.

Prenons un premier exemple graphique. Un POINT est defini par ses coordonnees rectangulaires X et Y. Un autre attribut est sa distance DIST de l'origine. On peut declarer le type POINT avec

	 CLASS POINT;
	 BEGIN
	   REAL X,Y,DIST;
	 END;
Les objets specifies par des classes doivent etre crees dynamiquement par l'operateur NEW et on les accede par des pointeurs (REFERENCEs).
	 REF(POINT) P;
	 ...
	 P:- NEW POINT;
	 P.X:= 1;   P.Y:= 3.0;
	 P.DIST:= SQRT (P.X**2 + P.Y**2);
Voyons l'analogie avec les procedures avec une autre version de POINT. Ici on met X et Y en position parametres pour exiger une valeur initiale a la creation et on met un enonce pour calculer la veleur de DIST.
	 CLASS POINT (X,Y); REAL X,Y;
	 BEGIN
	   REAL DIST;
	 
	   DIST := SQRT (X**2+Y**2);
	 END;
	 
	 P :- NEW POINT (1.0,3.0);
A la creation, X et Y recoivent les valeurs 1 et 3 et le corps de POINT est execute avant de revenir a l'enonce de creation. POINT fonctionne comme une procedure a la difference que le "bloc d'activation" n'est pas detruit au retour et qu'on peut acceder au variables locales par le pointeur: P.X, P.Y et P.DIST .

Les attributs accessibles d'un objet ne sont pas limites a des variables; on peut aussi avoir des procedures et fonctions locales. Ceci permet d'integrer les operations permises sur un type d'objet a la declaration de sa classe. Ici l'attribut DIST depend des valeurs X et Y et un changement de X ou Y entraine un changement de DIST. Il est plus elegant de declarer DIST comme attribut fonctionnel qui calcule la bonne valeur chaque fois qu'on en a besoin:

	 CLASS POINT (X,Y); REAL X,Y;
	 BEGIN
	   REAL PROCEDURE DIST;  
	     BEGIN   DIST:= SQRT(X**2 + Y**2) END;
	 END;
La distance du point P est donnee par P.DIST et remarquer que DIST etant une fonction on ne peut pas lui affecter une valeur avec P.DIST:=5 .

12. VARIANTES D'OBJETS

Soient trois types d'objets T1, T2 et T3 ou chacun a l'attribut X mais T2 a l'attribut Y en plus et T3 a l'attribut Z en plus de X et Y. PASCAL traiterait ceci avec un RECORD a variantes. SIMULA utilise la PREFIXATION pour faire ressortir la hierarchie des types. On declare
     CLASS T1; BEGIN  REAL X ; END;
Pour T2 on declare une class contenant seulement l'attribut additionnel Y et on indique que T2 a tous les attributs de T1 en mettant l'identificateur T1 comme prefixe a la declaration:
		T1 CLASS T2; BEGIN REAL Y; END;
De meme:
		T2 CLASS T3; BEGIN REAL Z; END;
En fait la prefixation ajoute a la classe prefixee tous les variables, declarations, parametres, procedures et enonces du prefixe. Dans notre exemple graphique, prenons le type SEGMENT defini par un point, une longueur R et un angle THETA. Disons qu'on veut aussi definir une operation SHIFT sur les segments qui les deplace en X et Y.
	 POINT CLASS SEGMENT (R,THETA); REAL R,THETA;
	 BEGIN
	    PROCEDURE SHIFT(DX,DY); REAL DX,DY;
	     BEGIN
	       X:=X+DX;  Y:= Y+DY;
	     END;
	 END;
	 REF (SEGMENT) S;
A la creation, il faut fournir les parametres du prefixe ainsi que ceux de la classe prefixee:
	S:- NEW SEGMENT (1,3,10,PI);
	S.SHIFT(-10,+4);

13. QUALIFICATION

Par qualification on entend le type des references. Tout comme PASCAL utilise la notion de type pour faire beaucoup de verifications des la compilation, SIMULA utilise la qualification pour depister des erreurs, augmenter la securite et l'efficacite. Soit
	 CLASS T1; BEGIN REAL X; END;
	 CLASS C ; BEGIN REAL TOTO; END;
 
	 REF(T1) P1;  REF(C) PC;
A la compilation, on traitera P1:-PC comme une erreur et de meme avec P1.TOTO ou PC.X . Avec la prefixation, le controle doit etre plus fin et SIMULA vise un compromis entre securite et efficacite. Soit
	 T1 CLASS T2; BEGIN REAL Y; END;
	 T2 CLASS T3; BEGIN REAL Z; END;
 
	 REF(T2) P2;  REF(T3) P3;
Les classes T2 et T3 sont des raffinements de T1 et les objets de T1, T2 et T3 ont tous l'attribut X. On permet donc a P1 de designer des objets T1,T2 ou T3 et dans tous cas P1.X sera permis. Par contre, pour eviter des verifications de types a l'execution, on interdit P1.Y et P1.Z . De meme, on permet P2.X et P2.Y mais P2.Z est interdit, etc... Noter que meme si P1.X est correct syntaxiquement et semantiquement, l'acces P1.X peut mener a une erreur a l'execution si P1==NONE.

Pour l'affectation entre pointeurs de qualifications reliees, SIMULA est plus tolerant et fait les verifications necessaires a l'execution:

     P1:- P3;     (aucune verification necessaire)
     P3:- P1;     (verification que P1 designe un T3 ou NONE)
L'usager peut verifier le type des objets designes avec IS et IN:

P IS C : vrai si l'objet designe par P est de type C (cree par NEW C), et faux autrement ou si P==NONE.
P IN C: VRAI si l'objet designe est de type C ou si C apparait dans sa suite de prefixes.

On peut aussi forcer la verification et modification de qualification avec QUA.

     P1 QUA T3 . Z   
 
est equivalent a

	ref(T3) temp;
		temp:-P1;   temp.Z
Il est souvent utile a un objet de s'autoreferencer (c.a.d. d'obtenir un pointeur sur lui-meme). On pourrait imaginer une fonction du style MOI, mais SIMULA prefere indiquer explicitement la qualification de cette autoreference avec THIS . Par exemple, dans T3, on pourrait ecrire THIS T1, THIS T2 ou THIS T3.

Finallement regardons l'enonce INSPECT dont l'intention est le meme que le WITH de PASCAL: alleger l'ecriture. Au lieu d'ecrire

     P1.X := P1.X +1;
 
 on peut dire
 
     INSPECT P1 DO X:=X+1 ;   
ou si P1==NONE, l'enonce est saute. Une clause OTHERWISE permet de traiter le cas ou P1 est NONE et on peut aussi utiliser les clauses WHEN pour traiter les variantes.
	INSPECT P1
	   WHEN T1 DO X:=3
	   WHEN T2 DO X:= Y+6
	   WHEN T3 DO X:= Y+Z
	   OTHERWISE   OUTTEXT(" P1 EST NONE ");

14. SIMSET

Nous avons vu que la prefixation d'une classe par le nom d'une autre lui ajoutait tous les attributs, declarations et enonces du prefixe. La prefixation s'applique aussi au BLOCs et aux programmes. Par ce biais, SIMULA permet une extension du langage. SIMULA a deux classes predefinies, SIMSET et SIMULATION, que l'usager peut utiliser comme prefixe. SIMSET contient des declarations utiles pour le traitement de listes et SIMULATION sert a la simulation. *Les listes de SIMSET sont circulaires a deux sens avec tetes de listes. Trois classes sont definies: HEAD qui decrit les tetes de listes et comprend les procedures et fonctions s'appliquant aux listes, par exemple FIRST pour donner le premier element d'une liste; LINK que l'usager utilise comme prefixe aux objets qui seront membres de listes; et LINKAGE, prefixe commun a HEAD et LINK afin de permettre le chainage. Voici le squelette de SIMSET.
	 CLASS SIMSET;
	 BEGIN
	    CLASS LINKAGE;
	    BEGIN
	      REF(LINK)    PROC SUC ; ...;
	      REF(LINK)    PROC PRED; ...;
	      REF(LINKAGE) PROC PREV; ...;
	    END;
	 
	    LINKAGE CLASS HEAD;
	    BEGIN
	      REF(LINK) PROC FIRST ; ...;
	      REF(LINK) PROC LAST  ; ...;
	      BOOLEAN   PROC EMPTY ; ...;
	      INTEGER   PROC CARDINAL;..;
	                PROC CLEAR ; ...;
	    END;
	 
	    LINKAGE CLASS LINK;
	    BEGIN
	      PROC OUT    ;...
	      PROC FOLLOW (X); REF(LINKAGE) X; ...
	      PROC PRECEDE(X); REF(LINKAGE) X; ...
	      PROC INTO   (L); REF(HEAD) L   ; ...
	    END;
	 END;
Les noms sont assez evocateurs des operations implantees. Remarquons qu'un LINK ne peut etre que dans une seule liste a la fois. Un objet dans une liste qui est insere dans une autre en sera retire au prealable. En general, une operation sans sens comme mettre un objet devant un autre qui est soit NONE soit hors d'une liste ne donne pas d'erreur; l'operation n'a pas d'effet.

Soit REF(HEAD) L et REF(LINK) A,B,

L.EMPTY VRAI si L est vide
L.FIRST donne le premier element de la liste L (ou NONE)
L.LAST donne le dernier element
L.CARDINAL donne le nombre d'elements dans L
L.CLEAR vide la liste
A.SUC donne le successeur de A s'il existe, mais L.LAST.SUC donne NONE
A.PRED donne le predecesseur (L.FIRST.PRED==NONE)
A.PREV donne le predecesseur qu'il soit HEAD OU LINK
A.OUT sort A de sa liste s'il y est
A.FOLLOW(B) met A derriere B
A.PRECEDE(B) met A devant B
A.INTO(L) met A en queue de la liste L

Voici un exemple d'un programme qui utilise SIMSET pour imprimer a l'envers tous les caracteres non blancs sur INPUT.

	 SIMSET BEGIN
	 
	   LINK CLASS ELEMENT (C);
	     CHARACTER C;  ;
	 
	   REF(HEAD) LISTE;
	   REF(ELEMENT) P;
	 
	   LISTE :- NEW HEAD;
	 
	   WHILE NOT LASTITEM DO
	     NEW ELEMENT( INCHAR ) . INTO (LISTE);
	 
	   P:- LISTE.LAST;
	   WHILE P=/= NONE DO
	   BEGIN
	      OUTCHAR(P.C); P:-P.PRED;
	   END;
	 END

15. COROUTINES

Les objets de SIMULA fonctionnent comme des coroutines. Pour comprendre les coroutines, regardons d'abord comment marche l'appel de procedure. Quand une procedure P1 appelle une procedure P2: 1) une copie de P2 est cree, 2) le controle passe au debut de P2, 3) P2 execute jusqu'a la fin, 4) on detruit P2 et 5) le controle revient a P1. Il existe une relation maitre-esclave entre P1 et P2.

Avec deux coroutines P1 et P2, le passage de controle se fait d'egal a egal. Pour passer le controle a P2, P1 fera RESUME (P2). Pour revenir a P1, P2 fera RESUME(P1). Noter qu'une coroutine activee par RESUME continue la ou elle etait quand elle a passe le controle a quelqu'un d'autre, pas au debut. Noter aussi qu'un "retour" n'implique pas la destruction de la routine.

La situation est legerement compliquee par le probleme de creation et de fin des coroutines. Quand une coroutine passe a travers son END final, a qui passer le controle? Pour expliquer ceci, on suppose que chaque coroutine a un pointeur cache RETOUR qui peut designer soit une autre objet, soit le bloc englobant ou se trouve sa declaration. Un objet passe le controle a RETOUR avec l'intruction DETACH qui a l'effet suivant:

      temp :- RETOUR;
      RETOUR:- "bloc englobant";
      RESUME (temp);
Notons que la fin d'une coroutine est equivalente a un DETACH.

Maintenant voyons la creation. Quand P1 execute

      P2 :- NEW C;
 
 ceci est equivalent a
 
      temp :- new C ;
      temp.RETOUR :- P1;
      RESUME (temp);
      P2:- temp;
Dans les exemples que nous avons vu, les objets crees ne faisaient ni RESUME ni DETACH mais executaient jusqu'au END final et le retour etait effectue a l'enonce de creation. Noter que dans un objet qui fait plusieurs DETACH, le premier rend le controle au createur mais les autres rendront le controle au bloc englobant, normallement le programme principal.

La derniere instruction de controle entre coroutines est CALL. Utilisee avec DETACH cette instruction permet le controle procedural entre coroutines. C'est a dire, une coroutine peut retourner a celle qui l'a activee sans la nommer explicitement. L'effet de CALL(P2) par P1 est le suivant:

      P2.RETOUR:- P1;   RESUME(P2);
On remarque que l'execution de DETACH par P2 rend le controle ˆ P1.

Utilisees de facon disciplinee, les instructions RESUME, CALL et DETACH permettent souvent une structuration souple et elegante de programmes complexes. Par contre comme avec les GOTO, on peut facilement creer la pagaille. Il sera donc souvent preferable d'utiliser les procedures de controle implantees dans la classe SIMULATION (a partir de RESUME etc... evidemment).

Voici un exemple d'un jeu avec N joueurs. Chacun, a son tour, roule un de et cumule les points. Le premier arrive a 100 gagne.

	 BEGIN
	   INTEGER U,N;
	   U:= 12345;
	   N:= ININT;
	 
	   BEGIN
	     REF(J) ARRAY JOUEUR (1:N);
	     INTEGER I;
	 
	     CLASS J (MOI); INTEGER MOI;
	     BEGIN
	       INTEGER SUIVANT, SUM;
	 
	       SUIVANT := MOD(MOI,N)+1;
	       DETACH;
	       WHILE SUM < 100 DO BEGIN
	         SUM := SUM + RANDINT (1,6,U);
	         RESUME(JOUEUR(SUIVANT));
	       END;
	       OUTINT(MOI,5); OUTTEXT(" A GAGNE"); OUTIMAGE;
	     END;
	 
	     FOR I:= 1 STEP 1 UNTIL N DO JOUEUR(I) :- NEW J (I);
	     RESUME(JOUEUR(1));
	     OUTTEXT("-- FIN DU JEU --");
	   END;
	 END
	 

16. SIMULATION

SIMULATION est une classe standard qui introduit le concept de PROCESS afin de decrire des objets dont la synchronisation est basee sur l'evolution du temps. Avec cette class comme prefixe, l'usager n'utilise plus les primitives RESUME, DETACH et CALL qui font des transferts de controle explicites. Par contre, des procedures de plus haut niveau sont fournies pour qu'un PROCESS qui veut s'arreter temporairement puisse indiquer l'heure (EVTIME) a laquelle il desire continuer. Tous les PROCESS actifs sont chaines dans un echeancier SQS par ordre croissant de EVTIME et l'arret d'un process entraine l'activation automatique du process en tete de SQS. (Noter que le "temps" est simule et n'a aucun raport avec l'ecoulement reel du temps de calcul; les evtimes servent a imposer une relation d'ordre entre le phases de calcul des differents process). Voici le squelette de la classe SIMULATION.
	 SIMSET CLASS SIMULATION;
	 BEGIN
	 
	   LINK CLASS PROCESS;
	   BEGIN
	     BOOL PROC TERMINATED;...
	     BOOL PROC IDLE      ;...
	     REAL PROC EVTIME    ;...
	     REF(PROCESS) PROC NEXTEV;...
	 
	     DETACH;....
	   END;
	 
	   { REF(HEAD) SQS ; }
	   REF(PROCESS) PROC	CURRENT;...
	   REAL         PROC 	TIME   ;...
	   REF(PROCESS)      MAIN   ;
	 
	   PROC HOLD (DT); 	REAL DT;...
	   PROC PASSIVATE ;...
	   PROC WAIT (L); 		REF(HEAD) L;...
	   PROC CANCEL(P);	REF(PROCESS) P;...
	 
	 END;
	 
NOTES: Voyons les attributs de PROCESS. Soit REF(PROCESS) P,
	 P.IDLE :          TRUE si P est hors de SQS
	 P.TERMINATED:     TRUE si P a passe son END final
	 P.EVTIME :        erreur si P.IDLE, l'heure de reactivation 
		                 de P autrement
	 P.NEXTEV :        successeur de P dans SQS ou NONE.
Le premier enonce de PROCESS est DETACH. La creation d'un process ne le fait donc pas demarrer. SIMULATION contient aussi un certain nombre de procedures de "scheduling" qui manipulent les process dans SQS. Ces procedures operent par defaut sur CURRENT et toutes se terminent en passant le controle au premier process de SQS (en general un nouveau CURRENT).

HOLD (DT); ajoute DT a current.evtime et deplace current en consequence
PASSIVATE; enleve CURRENT de SQS
CANCEL(P); enleve P de SQS
WAIT(L); equivalent a: CURRENT.INTO(L); PASSIVATE;
Il existe aussi 2 enonces de scheduling, REACTIVATE et ACTIVATE. Ces enonces ont plusieures options. Soit
REF(PROCESS) P,P1 :
REACTIVATE P AT T; P est cedule (place dans SQS) pour l'heure T
REACTIVATE P DELAY DT; P est cedule pour l'heure TIME+DT
REACTIVATE P BEFORE P2;
REACTIVATE P AFTER P2;
P est cedule devant ou apres P2 avec la meme heure que P2
REACTIVATE P; P est place devant current et prend le controle immediatement

Avec les options AT et DELAY, P est place derriere tout autre process cedule pour la meme heure (FIFO). Cependant, on peut demander a le mettre devant les autres avec le mot cle PRIOR:

	REACTIVATE P DELAY 10 PRIOR;
Noter que les trois enonces suivants ont tous le meme effet:
	REACTIVATE P DELAY 0 PRIOR;
	REACTIVATE P;
	REACTIVATE P BEFORE CURRENT;
Les cas bizarres, P==NONE, P2==NONE, P2.IDLE, P==P2 etc... ne produisent aucun effet.

Le deuxieme enonce d'activation ACTIVATE est identique a REACTIVATE si P.IDLE est vrai (P n'est pas dans SQS), autrement si P est deja dans SQS, ACTIVATE n'a aucun effet et P reste ou il etait.

Voici un programme qui simule une file d'attente avec des PROCESS client. Pour montrer l'evolution de la taille de la queue, on utilise un proces reporter qui imprime le nombre de personnes dans le systeme a toute les 10 unites de temps. La simulation dure 200 unites de temps. L'exemple montre aussi l'emploi de procedures de traitement de listes: into, out et empty.

    ! Exemple de la simulation d'une file d'attente ;
    
    Simulation begin
    
        ref(head) Q;
        INteger   U, N;
        Boolean   Guichet_Occupe;
        
        process class client;
        begin
            activate new client delay negexp(1/10, U);
            
            N := N+1;
            if Guichet_occupe then begin
               into(Q);
               passivate;
               out;
            end;
            
            Guichet_occupe := TRUE;
            hold( uniform( 0,18, U ) );
            Guichet_occupe := FALSE;
            
            if not Q.empty then activate Q.first;
            N := N-1;
            
        end;
        
        process class reporter;
        while TRUE do
        begin   integer i;
        
           outint(time, -6);
           for i := 1 step 1 until N do outchar('*');
           outimage;
           hold(10);
        end;
        
        Q :- new Head;
        U := clocktime;
        
        activate new reporter delay 0 ;
        activate new client delay randint(0,5,U) ;
        
        hold(200);
        outtext("=== Fin de la simulation ===");
    
    end
PARALLELISME.

Les processus de SIMULA travaillent en quasi-parallele: a tout moment, un seul est actif. De plus, un processus qui n'appelle pas de procedure ou d'enonce de scheduling est ininterruptible et TIME reste fixe durant son execution. Pour modelliser le fait que le travail d'un processus prend un certain temps et que d'autres travaillent en parallele, il faut inserer des enonces HOLD(...) aux endroits appropries ou ... represente la "duree" des actions du processus.

L'exemple suivant demontre les problemes de synchronisation dans les systemes d'exploitation. On simule 5 processus qui incrementent chacun la variable globale N 10 fois. On s'attendrait a ce que N vaille 50 a la fin de la simulation. En fait, a cause du delai entre l'acces et la mise a jour de N, cette valeur sera moindre. On simule ce delai avec une procedure TRANSFER qui fait un HOLD aleatoire et on insere un HOLD dans le processus.

	 simulation begin
	    integer i,n,u;
	    integer procedure transfer(i); integer i;
	    begin
	       transfer := i;
	       hold(uniform(0,2,u));
	    end;
	  
	    process class proc;
	    begin integer k;
	       for k:= 1 step 1 until 10 do
	       begin
	          n:= transfer(n+1);
	          hold(1);
	       end;
	    end;
	  
	    u:= 47;
	    for i:= 1 step 1 until 5 do 
	       activate new proc;
	    outtext("n devrait valoir 50"); outimage;
	    hold(10000);
	    outtext("n="); outint(n,5); outimage;
	 end;
L'execution donne
     N DEVRAIT VALOIR 50
     N=   14

REFERENCES

  1. Birtwistle, G.M., O.-J. Dahl, B. Myhrhaug and K. Nygaard. "SIMULA Begin." p. 391, AUERBACH Publishers Inc, 1973.
  2. Dahl, O.-J. and C.A.R. Hoare. "Hierarchical Program Structures." Structured Programming, (Ed.) C. A. R. Hoare, p. 175-220, London, Academic Press, 1972.
  3. Dahl, O.-J., B. Myhrhaug and K. Nygaard. "SIMULA-67 Common Base Language." Technical Report Nž S-22, Norwegian Computer Centre, Oslo, Norway, 1970.
  4. Kirkerud, B. "Object-Oriented Programming sith SIMULA." p. 515, Addison-Wesley, 1989.
  5. Lapalme, Bronsard, "SIMULA-67 par l'exemple", Document de travail #144, Dept. IRO, Universite de Montreal, octobre 1983.
  6. Nygaard, K. "Basic Concepts in Object Oriented Programming." SIGPLAN Notices, 1986 (October), p. 128-132, 1986.
  7. Pooley, R.J. "An Introduction to Programming in SIMULA." p. 333, Oxford, Blackwell Scientific Publications, 1987.
  8. SIS. "Data Processing, Programming languages: SIMULA." p. 178, Swedish Standards Institute, 1987.
  9. Vaucher, La programmation avec SIMULA 67, env. 200 pages (1973), document iro #36 (disponible: secretariat V-240 ou bibliotheque d'info)

A1: sim - script pour compiler et executer SIMULA


	Usage: sim [option...] file-name...

	The following options are available:
	  -c        Omit linking...just compile.
	  -C        Compile only the files that really need recompiling.
	  -d        Include the debugger.
	  -h        High speed version (no debugging Hooks)
	  -help     Print this help message and exit.
	  -o file   Name of compiled program file.
	
	  -q        Make sim more quiet.
	  -v        State version and make sim more verbose.
	
	  -r        Run the program and remove temp. files (default).
	  -nr       Don't run the program.
	
	Run time Options:  These are used when the program is run
	----------------
	  -input=  Takes input from FILE instead of terminal
	  -output=     Outputs   to FILE instead of terminal
	
	  -k=xxx    Run-time data space is set to xxx Kilobytes
	  -m=xxx    Run-time data space is set to xxx Megabytes
	              ( start with '-m=1' )
	
	Object Librairies:
	------------------
	  -diro     Include the basic DIRO object libraries.
	  -simwin   Include the 'windowtools' library.
	  -simlib   Include the 'simlib' library (Lund).
	  -libsim   Include the 'libsim' library (Lund).
	  -simsocket     ...other
	  -simioprocess       libraries
	  -simxlib               from Lund
	  -all      Include all the Lund libraries.
	
	Linker options:
	  -l  get external modules from 'lib.a' or 'lib.so'
	  -L  look in  for .a and .so libraries
	
	Search Paths:
	  -L=path1:path2....
	  -I=lib1:lib2...
	
	  These 'simcomp' mechanisms are supported and extended to linking.
	
	SIMULA DOCUMENTATION:  Don't forget to use man & simman !
	
	   % man simula cim sc
	   % simman simula
	   % simman simlib