Cours Langage MODULA-3 en PDF


Télécharger Cours Langage MODULA-3 en PDF

★★★★★★★★★★3.5 étoiles sur 5 basé sur 1 votes.
Votez ce document:

Télécharger aussi :


Cours Langage MODULA-3 avec exemples [Eng]

...

Larch fournit une famille de langages de spécification qui peut être utilisée pour spécifier des interfaces de programme, plus une collection d'outils pour aider à construire des spécifications correctes. Les spécifications du mélèze sont données en deux parties:

  1. le niveau Langage partagé, qui utilise un langage de spécification algébrique pour décrire les propriétés des types de données de base et des opérateurs utilisés dans la spécification. La langue partagée de mélèze (LSL) [6] est commune à toutes les spécifications de mélèze.
  2. le niveau de langage d'interface, qui est spécifiquement lié à la langue de programmation utilisée. Un langage d'interface contient des constructions appropriées au langage de programmation et utilise les traits spécifiés dans le niveau LSL pour décrire les propriétés de l'interface.

Ce rapport décrit un langage d'interface (LM3) conçu pour être utilisé avec le langage Modula-3 [4]. Il est supposé que le lecteur est déjà familier avec Modula-3, LSL, et les idées générales de spécification d'interface (comme, par exemple, [13]).

Les publications précédentes ont documenté des langages d'interface Larch, par exemple [12]. LM3 suit le style général de ce travail précédent, mais aborde plusieurs fonctionnalités supplémentaires qui deviennent courantes dans les nouveaux langages de programmation.

Modula-3 permet des procédures d'ordre supérieur (c'est-à-dire, celles qui prennent des paramètres de procédure ou des résultats de procédure de retour). Puisque les procédures sont représentées par leurs spécifications plutôt que par leurs valeurs, nous avons besoin d'un moyen d'associer des spécifications à de tels paramètres.

Modula-3 permet des hiérarchies de type d'objet. Cela signifie que LM3 doit hériter des spécifications des supertypes.

Modula-3 prend en charge l'exécution simultanée de threads de contrôle, par conséquent, LM3 doit être capable de spécifier des procédures non-atomiques.

LM3 aborde chacun de ces domaines. Lorsque cela est possible, les caractéristiques supplémentaires suivent le style et la philosophie du travail précédent de mélèze.

1.2 La relation entre Modula-3 et LM3

Un module Modula-3 qui fournit une interface utilisable à l'extérieur se compose généralement de deux fichiers:

  1. un fichier d'interface, avec l'extension .i3, qui définit les définitions exportées du module;
  2. un fichier d'implémentation, avec l'extension .m3, qui donne le code complet du module.

Les spécifications LM3 sont placées dans le fichier d'interface. Les clients du module ne voient que le fichier .i3. Toutes les informations nécessaires à la compréhension et à l'utilisation du module doivent être présentées dans ce fichier. Sans LM3, l'interface est généralement complétée par des commentaires décrivant l'action prévue des procédures, et ainsi de suite. Du point de vue des clients, la spécification LM3 fournit une description plus précise de la fonctionnalité de l'interface et peut remplacer certains des commentaires textuels détaillés, bien que des commentaires généraux soient toujours utilisés pour compléter le texte formel. Du point de vue des programmeurs du fichier .m3, la spécification LM3 fournit un contrat à implémenter.

Les annotations LM3 décorent la norme Modula-3 en tant que pragmas. L'objectif principal des pragmas, selon le rapport Modula-3, est de fournir des «conseils pour la mise en œuvre; ils n'affectent pas la sémantique du langage. Puisque les pragmas non reconnus sont ignorés par le compilateur, ils fournissent un moyen pratique d'attacher les informations de spécification.

Nous croyons que les spécifications doivent être conservées avec les programmes qu'ils sont destinés à décrire. Les spécifications de l'interface LM3 sont des interfaces légales Modula-3. En faisant de la spécification une partie intégrante du programme, nous avons l'intention de rappeler au programmeur son existence à toutes les étapes de la vie de l'interface. Les précédentes langues d'interface Larch ont placé les spécifications dans les commentaires, mais cela a eu tendance à minimiser leur importance dans l'esprit de certains. Nous nous attendons à ce que les pragmas soient pris plus au sérieux.

Le chapitre 2 introduit les constructions de LM3 et illustre leur utilisation avec des exemples simples. La syntaxe complète de LM3 est donnée au chapitre 3. Le chapitre 4 définit les traits LSL utilisés par LM3 et donne les fonctions de traduction pour les constructions LM3. Le chapitre 5 donne des exemples de spécifications complètes d'interface LM3.

Chapitre 2 La spécification LM3

la langue

Une spécification d'interface LM3 se compose d'une définition d'interface Modula-3 avec quelques annotations dans les crochets de pragma. Une spécification contient des spécifications de type, des déclarations variables et constantes, des spécifications de procédure et un invariant d'interface. Dans les sections suivantes, nous les considérons à leur tour.

Nous introduisons les conventions grammaticales suivantes:

Symbole des terminaux

Mots-clés · LM3 et Modula-3 KEYWORD

  • Non-terminaux nonTerm

 · foo, * signifie zéro ou plusieurs foo séparés par un,

 · foo; + signifie un ou plusieurs trucs séparés par a;

 · [foo] signifie zéro ou un foo

Les extensions de la grammaire Modula-3 sont indiquées comme foo

2.1 Interfaces

L'unité de niveau supérieur dans une spécification LM3, comme celle d'un programme Modula-3, est l'interface. Une interface Modula-3 est annotée avec une liste de traits qui définissent la signification des symboles utilisés dans les prédicats pour spécifier la fonctionnalité des composants de l'interface. Tout changement de nom nécessaire pour supprimer l'ambiguïté doit être fait ici, en utilisant les mécanismes LSL.

Une spécification d'interface a la forme suivante:

interface INTERFACE ident; [traitUse] importations *

déclaration [intConstraints] * END ident.

traitUse <* UTILISATION de traitRef, + *>

importations [FROM ident] IMPORT ident, +;

intConstraints <* initial *> j <* invar *> j <* invar initial *>

déclaration constDecl j varDecl j typeDecl j exceptionDecl

j procDecl j privateVarDecl j ...

initiale INITIALLY lm3Predicate

invar INVARIANT lm3Predicate

Il peut y avoir une condition initiale et un invariant associé à l'interface. Dans une interface qui déclare des variables, la condition initiale contraint la valeur initiale des variables. L'invariant serait normalement utilisé pour indiquer les relations qui doivent toujours être maintenues entre ces variables. Il peut également être utilisé pour spécifier les relations entre les procédures dans l'interface.

Puisque les fragments de grammaire ne sont pas la manière la plus éclairante de sous-estimer une langue, nous développons un exemple simple en introduisant chaque nouvelle construction. Un squelette de l'interface est donné comme:

INTERFACE Pile;

<* UTILISATION de Stack (Real pour E, RealStack pour C) *>

(* déclarations du type exporté et des procédures - voir ci-dessous *) END Stack.

La clause USING associe tous les symboles utilisés dans la spécification avec ceux trouvés dans l'attribut nommé Stack, avec le changement de nom approprié. Toutes les interfaces utilisent implicitement un trait LM3Trait qui donne les définitions des opérations primitives de Modula-3. Voir le chapitre 4. Le trait Stack, tiré du Larch Shared Language Handbook [8], est donné à l'annexe A.

2.2 Déclarations



Les déclarations dans les interfaces Modula-3 incluent des constantes, des types, des variables, des exceptions et des procédures. Dans cette section, nous décrivons les spécifications LM3 pour chacune d'entre elles puisque la forme de la spécification dépend du type de déclaration.

2.2.1 Constantes

Une interface peut exporter n'importe quel nombre de constantes. La grammaire pour les déclarations constantes est:

constDecl ident [: type] = constExpr

Une déclaration constante pour LM3 est juste une déclaration de constante Modula-3, avec la restriction que le constExpr doit être un terme du langage d'expression LM3.

Par exemple, si nous voulions donner une limite supérieure à la pile, nous pourrions écrire:

CONST MaxSize = 100;

2.2.2 Variables

LM3 n'ajoute aucune information supplémentaire à la déclaration des variables exportées. Toutes les restrictions peuvent être placées dans le type ou l'invariant d'interface. La grammaire pour une déclaration de variable est:

varDecl VAR vd +

vd ident, +: type [valeur initiale];

initialVal: = expr

L'exportation de variables n'est pas courante dans les interfaces Modula-3.

2.2.3 Variables privées

Il arrive souvent que la spécification utilise des informations qui ne doivent pas être accessibles à l'implémentation.

Pour faciliter cela, LM3 permet la déclaration de variables privées, qui font théoriquement partie de l'état mais qui ne peuvent être référencées que dans les spécifications. Ces variables existent uniquement dans le domaine de spécification et sont associées aux tris LSL, pas aux types. Par commodité, les sortes évidentes sont disponibles pour représenter les types de langage de programmation courants.

La grammaire pour les variables privées est:

privateVarDecl G * PRIVATE (ident, +: tri; varSpec) + *>

varSpec [initial] [invar]

2.2.4 Types

Modula-3 a un riche espace de types, y compris une notion de sous-typage. Tous les types de base et les constructeurs de type du langage sont associés aux sortes LSL dans LMMrait.

Une déclaration de type peut être annotée avec un certain nombre de choses. Tous ne seront pas appropriés dans tous les cas. Une déclaration de type entièrement annotée a la forme suivante:

typeDecl TYPE td +

td ident [typeSpec] type de sous-typeReln;

typeSpec G * BASE SUR [ident:] tri [initial] [invar] *>

subtypeReln = j G:

type ident j tableauType j recordType j ...

La première clause associe le type à un tri, qui doit être défini dans l'un des traits de la liste USING. En particulier, pour toute variable v de type T qui est basée sur le tri S, la valeur de v doit être égale à un terme de type S pour lequel l'invariant de T est vrai.

La clause initiale, indiquée par INITIALLY, introduit un prédicat qui doit contenir les valeurs initiales d'un type. Pour un type simple, ce prédicat contraint les valeurs initiales fournies dans les déclarations de variables. Pour un type d'objet, il s'agit d'une contrainte sur le résultat des appels à NEW. La satisfaction de cette contrainte est une obligation des clients qui utilisent le type.

La clause invar, indiquée par INVARIANT, introduit un prédicat théoriquement conjoint aux pré et post-conditions de toutes les procédures

peut référencer un élément du type. Une telle procédure peut supposer l'invariant à l'invocation et doit le garantir à la sortie. Les types d'objets, où les invariants sont hérités, sont discutés séparément dans la section 2.3.3.

L'ident représente une variable liée par quantification universelle sur le type.

Chaque type déclaré dans l'interface est associé à un tri. Il y a deux catégories de types:

  1. types avec des valeurs modifiables (types REF). C'est T
  2. types avec des valeurs non modifiables (types VAL). Dans ce cas, il n'y a pas d'indirection et le tri est celui donné dans le BASED ON. Les paramètres d'un tel type sont non modifiables (et c'est une erreur vérifiée pour essayer de modifier un paramètre d'un type VAL). Les modifications de ces paramètres ne sont autorisées que si elles sont déclarées comme VAR. Dans ce cas, le type du paramètre est SVar qui peut être considéré comme introduisant implicitement un niveau de référence supplémentaire.

Si nous développons l'exemple Stack pour inclure la déclaration du type, nous voyons:

INTERFACE Pile;

<* UTILISATION de Stack (Real pour E, RealStack pour C) *>

TYPE T <* BASE SUR RealStack *>

(* déclarations des procédures exportées - voir ci-dessous *) END Stack.

Pour l'exemple ci-dessus, toute variable de type Stack a le type RealStackRef. Pour obtenir la valeur d'une telle variable, qui a le type RealStack (qui est produit en effectuant le renommage donné sur le trait Stack), nous déréférons la variable dans l'état approprié en utilisant soit __ \ pre soit __ \ post.

2.2.5 Procédures

Modula-3 permet l'utilisation d'une variété de constructions dans les procédures, y compris les exceptions et les threads. Par conséquent, les déclarations de procédure

être annoté avec un certain nombre de prédicats. La signification d'une spécification de procédure est donnée en tant que prédicat sur une séquence de paires d'états2.

Procédures atomiques

La plupart des procédures écrites en Modula-3 sont atomiques. Puisque la spécification d'une procédure atomique est significativement plus simple que dans le cas non-atomique, nous considérons ceci en premier.

S'il n'y a pas d'exceptions et que la procédure est atomique et non synchronisée, la grammaire est la suivante:

procDecl PROCEDURE ident ([signature]) [: type];

[G * procSpec *>]

signature {[paramType] ident, +: type [initialVal];} +

paramType VALUE I VAR I READONLY

procSpec [globals] [privates] [letDecl]

[prePred] [modifie] postPred

globals ((WR I RD) ident: type;) +

privés PRIVATE varDecl +

letDecl LET laisser entrer

let ident BE terme, +

prePred nécessite lm3Predicate

modifie le terme MODIFIES, +

postPred atomicPostPred

atomicPostPred ASSURE lm3Predicate

Les composants de la spécification de la procédure sont:

globals Déclarations de toutes les variables de l'état global qui sont référencées par cette procédure. Dans la plupart des cas, cela peut être considéré comme une extension implicite de la liste des paramètres. Les variables globales sont annotées pour indiquer leur utilisation prévue: les globales peuvent être indiquées comme WR (inscriptibles) si elles peuvent être modifiées ou RD (lecture seule) si elles ne sont pas destinées à changer la valeur. C'est une erreur de modifier une variable globale qui a été déclarée être RD.

private Variables privées locales à chaque invocation.



letDecl Les raccourcis locaux définis par l'utilisation de la construction LET. C'est purement un mécanisme de substitution syntaxique.

prePred une clause REQUIRES qui définit la condition préalable de la procédure. C'est un prédicat que l'appelant doit s'assurer est vrai dans l'état à partir duquel la procédure est invoquée. Si ce n'est pas le cas, rien n'est garanti sur le résultat, y compris la résiliation. Si cette clause est omise, sa valeur par défaut est true, ce qui implique que la procédure peut être appelée à partir de n'importe quel état. La clause REQUIRES peut référencer uniquement les variables dans l'état antérieur.

modifie une clause MODIFIES qui identifie les composants d'état que la procédure est autorisée à modifier. S'il n'y a pas de clause MODIFIES, la procédure ne peut rien modifier. Si la procédure est autorisée à modifier tout ce à quoi elle a accès, le raccourci est MODIFIES ALL. La modification doit être cohérente avec le type du paramètre. Par exemple, c'est une erreur de mentionner un paramètre VAL ou READONLY dans la liste MODIFIES, sachant que la valeur par défaut pour les paramètres M3 est VAL. Seules les variables globales déclarées comme WR peuvent être mentionnées dans la clause MODIFIES. Cette clause est une liste de termes dont les valeurs sont des éléments d'un type modifiable (normalement, un REF). A partir de cette liste, un prédicat affirmant la validité de telles modifications est dérivé.

postPred une clause ENSURES qui donne la postcondition de la procédure. C'est un prédicat sur la paire d'états (l'état à l'invocation et l'état à la fin) qui doit être vrai à la sortie de la procédure spécifiée. Les variables de la clause ENSURES peuvent faire référence à des valeurs dans les deux états et sont donc qualifiées avec __ \ pre ou __ \ post. La valeur de retour sans nom d'une procédure est représentée par la pseudo variable RESULT qui n'a de sens que dans l'état final et doit donc être qualifiée avec __ \ post.

Formellement, la signification d'une telle spécification d'une procédure, lorsqu'elle est complètement développée, est donnée par le prédicat:

prePred => (modPred / \ postPred)

À titre d'exemple, nous ajoutons certaines fonctionnalités à notre pile en évolution. Puisque nous suivons l'exemple qui peut être trouvé dans le rapport Modula-3, dans lequel toutes les procédures renvoient une nouvelle pile plutôt que de modifier la précédente, la spécification est:

PROCÉDURE Pop (VAR s: T): REAL;

<* REQUIS NON (isEmpty (s \ pre \ pre))

MODIFIES s

ASSURE s \ post \ post = pop (s \ pre \ pre) ET RÉSULTAT \ post = top (s \ pré \ pre) *>

Cela nous indique que cette procédure ne doit être appelée que lorsque la valeur de s n'est pas la pile vide, que la valeur de s peut être modifiée et que le résultat et la valeur finale de s seront le top et pop (du caractère Stack ) de la valeur initiale de s, respectivement.

Puisque s est déclaré être un paramètre VAR, cela ajoute implicitement un niveau d'indirection. En d'autres termes, s est associé au type SRef plutôt qu'à S. Cela explique les deux utilisations de __ \ pre pour arriver à une valeur de pile: la première à déréférencer le VAR donnant un SRef; la seconde pour déréférencer ceci en donnant un S (en se rappelant que T

Un aparté

Cette spécification de Stack est quelque peu inhabituelle. Comme cela a causé une certaine confusion chez certains lecteurs de ce rapport, nous en discuterons un peu.

Plus généralement, on pourrait s'attendre à ce que la procédure modifie la pile existante plutôt que d'en livrer une nouvelle. Pour comprendre le contraste entre ces deux styles, la spécification d'une fonction Pop1 dans laquelle le Stack n'est pas un paramètre VAR est:

PROCÉDURE Pop1 (s: T): REAL;

<* REQUIS NON (isEmpty (s \ pre))

MODIFIES s

ASSURE s \ post = pop (s \ pre)

ET RÉSULTAT \ post = top (s \ pre) *>

Ici, la pile donnée est modifiée. Ceci est autorisé puisque Stack est un type REF. Nous suivons le premier exemple dans le reste de ce rapport, car le niveau d'indirection supplémentaire impose une plus grande attention dans la spécification et sert ainsi mieux notre but pédagogique, mais nous devons souligner que l'étrangeté est due au comportement souhaité de l'exemple plutôt que pour le langage de spécification.

Mots-clés

Puisque l'exemple suivant les utilise, c'est un endroit approprié pour mentionner les prédicats de mots-clés de LM3. Ces mots clés forment une partie importante des termes utilisés pour créer des prédicats. Les prédicats de mots clés sont les suivants:

UNCHANGED (v1 ... v ,,,), qui affirme que les valeurs finales de ces variables sont égales à leurs valeurs initiales.

FRESH (foo), ce qui signifie que le stockage assigné à foo n'est partagé avec rien d'autre dans l'état.

CHECKEDRTE, qui est sémantiquement équivalent à true mais avertit l'implémenteur qu'une erreur d'exécution vérifiée se produirait

Un interlude: la pile

À ce stade, nous avons un mécanisme suffisant pour compléter notre exemple Stack. Assembler les pièces vues précédemment et ajouter les procédures évidentes que nous obtenons:

INTERFACE Pile;

<* UTILISATION de Stack (REAL pour E, RealStack pour C) *>

TYPE T <* BASE SUR RealStack *>

PROCÉDURE Pop (VAR s: T): REAL;

<* REQUIS NON (isEmpty (s \ pre \ pre))

MODIFIES s

ASSURE s \ post \ post = pop (s \ pre \ pre) ET RÉSULTAT \ post = top (s \ pré \ pre)

*>

PROCÉDURE Appuyez sur (VAR s: T; x: REAL); <* MODIFIE s

ASSURE s \ post \ post = pousser (s \ pre \ pre, x)

*>

PROCÉDURE Créer (): T;

<* ENSURES isEmpty (RÉSULTAT \ post) ET FRAIS (RÉSULTAT) *>

END pile.

Cela donne toutes les informations fonctionnelles qu'un client de cette interface devrait jamais avoir besoin de savoir. En pratique, la spécification devrait être complétée par des commentaires textuels, indiquant aux clients les choses non fonctionnelles (tout aussi importantes) dont ils ont besoin.

Procédures avec exceptions

L'une des techniques de programmation courantes dans Modula-3 est l'utilisation d'exceptions pour la fin anormale d'une procédure. Le langage de programmation vous permet de déclarer l'ensemble des exceptions pouvant être déclenchées par une procédure. Le langage de spécification vous permet de décrire un résultat alternatif en utilisant une clause EXCEPT, avec des prédicats gardés qui peuvent être satisfaits à la place de la condition de publication normale. Si des protections sont vraies, la procédure doit satisfaire le prédicat d'exception de l'une d'entre elles, plutôt que le prédicat ENSURES normal.

Il est également possible qu'une exception soit soulevée par un niveau inférieur du programme. Les circonstances qui conduisent à une telle exception «panne d'abstraction» ne peuvent généralement pas être spécifiées en termes d'état accessible à l'appelant. En ce qui concerne l'appelant, ces exceptions peuvent être soulevées "arbitrairement". Ils sont spécifiés par des clauses UNLESS.



56