Serveur © IRCAM - CENTRE POMPIDOU 1996-2005. Tous droits réservés pour tous pays. All rights reserved. |
Rapport Ircam 24/80, 1980
Copyright © Ircam - Centre Georges-Pompidou 1980
Nous allons exposer comment, à partir de considérations générales relatives à la représentation discrète des phénoménes continus, il se dégage un ensemble de conditions qui fixent les grandes lignes de l'architecture d'un tel système. Dans le cadre ainsi défini nous examinerons ensuite l'incidence des contraintes imposées par la mise en oeuvre sur ordinateur, ces contraintes déterminant elles aussi une partie des caractéristiques finales. Enfin, la notion de transcription d'un flot d'informations en un autre amènera à choisir des moyens de commande qui facilitent l'analyse syntaxique des informations d'entrée, considérées comme un langage défini par une grammaire LL(1).
Cette analyse aboutit à la définition d'un langage par lequel il est possible de déclancher et de contrôler la production de flots d'échantillons en se référant au contenu d'un ou plusieurs flots d'informations en entrée, aussi bien qu'à l'écoulement d'un temps courant exprimé par rapport à une origine arbitraire 0, ou à des critères propres aux étapes intermédiaires des calculs.
Supposons donc, sans à priori quand à la nature des travaux que nous désirons effectuer, que nous voulions exprimer l'évolution des valeurs d'un ensemble de variables le long d'une échelle de temps discrète. Il suffira pour chaque variable de spécifier à chaque pas l'expression qui décrit sa valeur, cette expression pourra faire référence aux valeurs d'autres variables, soit telles qu'elles étaient au pas précédent, soit telles qu'elles doivent être au présent pas. Ce dernier cas implique un ordre d'élaboration des valeurs qui n'introduise pas de cercle vicieux, certaines variables devant contenir en quelque sorte des résultats intermédiaires qui concourent à l'élaboration d'autres valeurs, elles ne peuvent elles-mêmes être définies par rapport au résultat auquel elles participent.
Bien que l'on puisse imaginer que l'expression qui décrit la valeur que doit prendre une variable varie arbitrairement à chaque pas, faisons l'hypothèse que pour des durées notables, l'expression qui décrit le contenu de telle ou telle variable soit la même. Les fluctuations éventuelles de la valeur résultante découleront donc des changements de valeurs des opérandes référencées dans l'expression et non pas de l'altération de l'expression elle-même.
Deux conséquences importantes apparaissent :
L'usage de <expression> signifie non pas seulement que la variable apparaissant à gauche du signe ':=' reçoit la valeur que dénote à ce pas l'expression apparaissant à droite, mais également qu'elle recevra désormais à chaque « pas » la valeur correspondante.<variable>:=<expression>
La succession des valeurs de chaque variable dépend donc d'une part de la valeur initiale (que l'on pourra supposer non significative) et de la nature et de la répartition dans le temps des affectations qui définissent son contenu. Nous pouvons alors par le choix de ces affectations et, tenant compte des interactions entre les définitions des différentes variables, construire n'importe quelle « trajectoire » possible du système représenté par l'ensemble des variables en jeu. Une telle « trajectoire » étant obtenue en calculant successivement pour chaque « pas » les valeurs prises par les variables.
Pour ce faire il suffit au cours d'un même pas de calculer les divers résultats intermédiaires dans l'ordre dans lequel ils sont nécessaires et de recommencer pour le pas suivant. La forme du programme est donc une simple boucle formée d'appels aux sous-routines de calcul, chaque sous-routine pouvant être conçue comme un processus indépendant.
La procédure qui décide de la prise d'effet de chaque redéfinition de variable peut donc être vue elle aussi comme une sous-routine répétitive qui, au lieu de délivrer une valeur à chaque pas teste si l'instant de prise d'effet d'une redéfinition particulière est atteint et si oui effectue la liaison correspondante entre la variable redéfinie et sa nouvelle valeur.
Nous allons donc être amenés à définir un mécanisme qui puisse nous permettre de garder toute la généralité qui découle des concepts précédemment exposés mais qui également minimise l'usage des ressources de calcul.
Un résultat qui ne dérive pas immédiatement d'un effet de seuil est stable pour un nombre de pas égal à la durée de stabilité de son opérande le moins stable.
Un résultat qui dérive d'un effet de seuil est stable pour la durée estimée nécessaire à ce que le franchissement devienne plausible, et qui se calcule de manière ad hoc selon la nature de l'effet de seuil, par exemple la durée de stabilité de la comparaison d'une progression arithmétique et d'un autre type de valeur est égale :
Cette gestion des stabilisations de valeurs sera effectuée grâce à des liens qui identifient dans chaque sous-routine élémentaire quels sont les opérandes de celle-ci et quels sont les usages du résultat. Pour que cette technique soit réellement efficace il est exclu d'avoir à tester les conditions de stabilité à chaque calcul c'est pourquoi on procédera à l'envers, c'est à dire que seules certaines routines auront l'initiative de ces tests de stabilité et propageront cette information vers celles qui font usage de leur résultat.
Ayant cette structure à notre disposition nous nous apercevons qu'une autre amélioration est possible : si tous les usages d'un résultat sont suspendus il est inutile de le calculer pendant la période correspondant à la durée minimum de suspension en vigueur parmi ses usages.
Si l'impossibilité de suspendre des structures circulaires est sans grand inconvénient il n'en va pas de même en ce qui concerne leur cessation d'usage, mais ce problème sera traité de façon légèrement différente par une technique de récupération (« garbage collection »), selon la méthode suivante :
Ensuite une série d'instructions décrivant l'évolution du contenu de ces variables, qui seront exécutées l'une après l'autre dès le début de l'exécution du programme, jusqu'à la dernière ou jusqu'à ce que l'une de ces instructions suspende sa propre exécution et celle des instructions qui la suivent en faisant dépendre son déroulement d'une condition qui ne soit pas remplie au départ et qui puisse l'être par la suite.
Afin de pouvoir décrire de façon précise et non ambigue les constructions syntaxiques utilisées nous utiliseront la convention suivante, variante de ce qui est appellé notation BNF (Backus-Naur Form).
Les déclarations permettent non seulement de définir des variables mais aussi des expressions et fonctions plus complexes que celles permises par l'usage des opérateurs habituels (+, -,*, /) et auxquelles on pourra se référer en plusieurs endroits dans les instructions subséquentes sans avoir à en réécrire le détail.programme = 'PROGRAM' { déclaration ';' } { instruction separ }. separ = ',' | ';'.
Les instructions consistent soit en des instructions d'affectation telles que celles que nous avons évoquées plus haut soit en des constructions plus complexes qui permettent d'exprimer les conditions de prise d'effet de ces affectations relativement au temps, aux valeurs atteintes par certaines variables, etc...
Par contre ces variables sont réparties en deux catégories significatives, les variables échantillonnées qui correspondent exactement à la notion de variable telle que nous l'avons définie et les variables non échantillonnées qui, devant servir à l'élaboration de résultats intermédiaires et n'étant pas censées transmettre une valeur d'un pas à l'autre, ne sont pas soumises à la gestion usages/opérandes, et pour lesquelles l'instruction d'affectation retrouve son sens habituel d'altération immédiate et unique de la valeur.
Une déclaration de variables est de la forme :
La séquence d'au plus 10 lettres et/ou chiffres qui forme le nom d'une variable doit être composée de caractères contigus. Le mot-clé 'SAMPL' identifie les variables échantillonnées.declvar = [ 'SAMPL'] [ 'LONG' ] typevar ident { ',' ident }. typevar = 'INT' | 'FRAC'. ident = lettre [ a [ a [ a [ a [ a [ a [ a [ a [ a ] ]]]]]]]]. lettre = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' |'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' |'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' . a = lettre | chiffre. chiffre = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' .
Enfin, ces fonctions et procédures échantillonnées pouvant nécessiter des calculs intermédiaires non triviaux, il est possible de rassembler, découper, et en un mot, de structurer, ces calculs intermédiaires sous forme de fonctions et procédures non échantillonnées qui, de même que les variables non échantillonnés, auront une signification classique n'impliquant pas l'itération implicite de leur exécution mais simplement une action immédiate et unique à leur point et instant d'invocation.
Le format de ces déclarations de procédures et fonctions est le suivant :
Avec la restriction supplémentaire qu'à l'intérieur d'une fonction ou procédure non échantillonnée on ne peut déclarer de variable échantillonnée.procédure = [ 'SAMPL' ] 'PROC' ident arguments ';' corpsproc. arguments = '(' declvar { ';' declvar } ')' |. corpsproc = 'EXTERNAL' | { declaration } corpsbloc 'END'. déclaration = declvar | procédure | fonction. fonction = [ 'SAMPL' ] [ 'LONG' ] typevar 'FUNC' ident arguments ';' corpsfunc. corpsfunc = 'EXTERNAL' | { déclaration } specvaleur 'END'.
En effet, tandis qu'une fonction ou procédure échantillonnée présente une rémanence d'un pas de calcul à l'autre, qui lui permet donc de détenir les informations qui constituent les références à des variables échantillonnées, une fonction ou procédure non échantillonnée peut terminer son exécution à tout moment et donc ne garantit pas la persistance des données qui lui sont propres.
On constate que les fonctions et procédures peuvent être imbriquées. Dans ce cas les déclarations de variables, fonctions et procédures propres à une fonction ou procédure ne sont effectives que depuis leur point de déclaration jusqu'au symbole 'END' qui marque la fin de la fonction ou procédure. Cette étendue constitue la portée d'une déclaration. La portée des déclarations contenues dans l'entête du programme s'étend jusqu'à la fin du programme.
D'autre part si certaines de ces déclarations locales définissent des symboles déjà utilisés dans les déclarations de l'entête du programme ou de procédures ou fonctions qui englobent le contexte où elles apparaissent, les déclarations locales prennent le pas, pour l'étendue de leur propre portée, sur les déclarations antérieures.
Cette construction constituant à elle seule une procédure échantillonnée qui à partir de son lancement testerait à chaque pas la condition voulue, et, si elle est remplie, exécuterait l'instruction d'affectation.<condition> <affectation>
Cependant l'utilisation d'une méthode aussi primitive serait extrêmement mal commode, et, nous basant sur le genre de travaux que nous avons en vue, nous allons définir un système de contrôle des calculs beaucoup plus riche.
Notre but principal se ramène, ainsi que nous l'avons dit, à une transcription d'informations initialement sous forme de texte ou, plus généralement, sous forme d'une séquence de signes. Il apparaît que ce qui permet le plus aisément de décrire une telle séquence de signes, et les transformations que l'on désire y apporter, est une grammaire décrivant les constructions sémantiques possibles au moyen de ces signes.
Nous avons ici même un exemple de ce que peut être la notation d'une grammaire dans ce que nous avons appelé notation BNF, et dont nous nous servons pour décrire les caractéristiques de notre système. Nous voyons que chaque construction est décrite en énumérant les diverses formes qu'elle peut revêtir.
Nous désirons, d'une part pouvoir reconnaître chaque construction sans ambiguité quand elle se présente, d'autre part y attacher une signification, c'est à dire, en ce qui concerne notre projet, en déduire une série de valeurs ou d'actions sur des valeurs déjà existantes.
Pour permettre la reconnaissance aisée des constructions nous vérifierons que les grammaires que nous utilisons sont LL(1), et au besoin nous les modifieront en subdivisant certaines règles et/ou en introduisant des règles auxiliaires afin de remplir cette condition. En effet, partant de la règle qui définit ce que doit être le flot d'entrée (règle-cible ou « goal symbol »), pour toute grammaire LL(1), il suffit d'examiner le symbole de tête du flot d'entrée pour pouvoir choisir quelle alternative appliquer et ainsi trouver l'enchaînement des règles successives, sans ambiguité et sans retour en arrière.
Ceci s'exprimera en mentionnant parmi les symboles qui décrivent les règles de la grammaire, des actions telles que des affectations, des appels de procédures ou de fonctions ou des actions composites plus complexes (que nous allons définir plus loin). Ces actions étant implicitement exécutées au moment où, dans l'alternative où elles figurent, toutes les actions d'analyse et/ou sémantiques situées à leur gauche ont été accomplies.
Pour cela, la reconnaissance d'un symbole terminal s'effectuera au moyen d'une comparaison entre le contenu de la variable qui reçoit le symbole de tête du flot d'entrée et les valeurs possibles de ce symbole, suivie, au cas ou le symbole de tête est reconnu, de la lecture du symbole suivant. Cette décomposition de l'opération de reconnaissance d'un symbole terminal en deux primitives (comparaison et lecture) permet de généraliser la sémantique de la primitive de comparaison en la faisant porter, non seulement sur les signes du flot d'entrée, mais aussi sur toute valeur intermédiaire, et en étendant les comparaisons possibles aux opérateurs d'ordre ("<",">", "<=", ">=").
De même que la comparaison du symbole de tête du flot d'entrée aux symboles directeurs des diverses alternatives de la rêgle en cours permet de choisir quelle alternative appliquer, la comparaison de certaines valeurs intermédiaires permettra de décider quelle sera la prochaine action à exécuter parmi plusieurs actions concurrentes. Cette construction est analogue à la construction "(COND...)" du langage LlSP.
Elle se traduit par :
De plus, cette construction est, du point de vue sémantique, strictement équivalente à une règle de grammaire, en ce sens que si aucune des conditions posées n'est satisfaite, la construction elle-même a la signification d'une condition fausse. Une telle construction, délimitée par les mots-clés 'DO' et 'END', peut donc être utilisée en tant que condition composite partout où l'on peut user d'une expression conditionnelle. De même si une telle construction constitue le corps d'une procédure ou fonction non échantillonnée la référence au nom de cette procédure ou fonction a le sens d'une référence à la règle dont les alternatives correspondent chacune à l'une des conditions de 1 à n.IF <condition 1> <action 1> IF <condition 2> <action 2> ... IF <condition n> <action n>
C'est par ce moyen que s'exprime la correspondance bijective entre les règles d'une grammaire LL(1) et les procédures et paragraphes de notre langage de commande.
Appliquant la démarche inverse de celle qui nous a mené d'une grammaire à une structure de contrôle, nous pouvons également considérer que les structures de contrôle ci-dessus, présentes dans un programme, définissent une grammaire décrivant le langage constitué par les « trajectoires » que ce programme peut suivre au cours de toutes ses exécutions possibles. Ce point de vue est cohérent sous réserve qu'à chaque choix parmi plusieurs actions les conditions concurrentes soient mutuellement exclusives, ce qui correspond à la condition LL(1) pour une grammaire.
Le mot-clé 'THEN' peut apparaître entre « conjonction » et « instructions », de même qu'à la place d'une virgule car il n'est pas significatif. La conjonction de plusieurs conditions est vraie si toutes les conditions sont vraies. Soit C1, C2... Cn les conjonctions qui apparaissent dans les alternatives A1, A2, An, on nomme Cx la conjonction non (C1) & non (C2)... & non (Cn). La construction « ELSE instructions » signifie « IF Cx instructions », tandis que « ELSE DONE » ou « DONE » signifient « IF Cx <rien> ». Si l'une de ces trois constructions est utilisée pour terminer une série d'alternatives, la règle ainsi définie est toujours applicable. C'est à dire qu'en tant que condition composite elle est toujours vraie, donc il est recommandé de ne pas l'utiliser de cette façon, ce qui n'aurait aucun sens et irait à l'encontre de la règle d'exclusion mutuelle des conditions concurrentes.corpsbloc = alternative | instructions. alternatives = clause { clause } [ finalter ]. clause = 'IF' conjonction instructions. conjonction = condition { '&' condition }. finalter = 'DONE' | 'ELSE' 'DONE' | 'ELSE' instructions.
En fait, dans la mise en oeuvre actuelle, cette règle d'exclusion mutuelle n'est ni vérifiée ni même requise pour le bon fonctionnement du choix des alternatives. Les conditions sont testées dans l'ordre dans lequel elles apparaissent dans le texte du programme et acquièrent donc un sens légèrement diffèrent : Cn <= Cn & non (Cn-1) & ... & non (C1). Il appartient à l'utilisateur de vérifier lui-même la condition d'exclusion au cas où elle est significative.
Les opérateurs de comparaison '#' et '<>', ont le sens de « non égal ». Le format des appels de procédures non échantillonnées et des déclanchements de procédures échantillonnées est le même, mais seul l'appel de procédures non échantillonnées constitue une condition significative, et, de plus, sous réserve que la partie exécutable de la procédure appelée soit une série d'alternatives. Une procédure non échantillonnée contenant des instructions inconditionnelles est équivalente à une règle toujours applicable.condition = expression opcomp expression | paragraphe | appelproc. opcomp = '=' | '#' | '<>' | '>' | '<' | '<=' | '>='. paragraphe = 'DO' alternative 'END' appelproc = nomproc [ arguments ]. nomproc = ident. arguments ='(' expression {',' expression } ')'.
Les diverses interprétations d'un identificateur « ident » (nomproc, étiquette, nomfvar, nomfunc) sont différenciées d'après les déclarations antérieures. TIME est une fonction sans arguments prédéfinie, dont la valeur est le temps courant exprimé en nombre de pas écoulés depuis le début de l'exécution du programme. Les parties d'expression étiquettées peuvent être utilisées comme des fonctions sans arguments, ce qui évite d'en réécrire le détail.expression = terme { addop terme }. addop = '+' | '-'. terme = fact { mulop fact }. mulop = '*' | '/'. fact = étiquette ':' fact | '-' fact | const | appelfunc | 'TIME' | nomvar | '(' specvaleur ')'. appelfunc = nomfunc [ arguments ]. étiquette = ident. nomvar = ident. nomfunc = ident. specvaleur = valternatives | valtexte. valtexte = instructions expression instructions. valternatives = 'IF' vclause { 'IF' vclause } [ 'ELSE' valtexte ]. vclause = conjonction valtexte | valcond instructions. valcond = condition '&' valcond | expression { '&' condition }.
On voit apparaître une des incidences les plus bizarres de notre choix de structure de contrôle. Une fonction non échantillonnée est, aussi bien qu'une procédure, l'équivalent d'une règle, pourvu que sa partie exécutable soit constituée d'alternatives. Donc une telle fonction peut à la fois renvoyer une valeur et constituer la condition qui détermine le choix de l'alternative qui correspond à cette même valeur. Ceci s'exprime par la définition de « valcond ».
Dans tous les cas où une série d'alternatives n'est pas terminée par une clause 'ELSE' ou 'DONE', une des alternatives doit être obligatoirement satisfaite, sinon le processus pour lequel cette condition de non satisfaction apparaît cesse de se dérouler. C'est par ce mécanisme que s'effectue la gestion de l'entrelacement des calculs. En effet la signification intuitive de cette condition est claire, c'est seulement si l'une des alternatives spécifiées est applicable que l'on désire continuer les calculs mentionnés dans les instructions suivantes. De deux choses l'une : ou bien les conditions testées sont stables vis-à-vis du déroulement des processus concurrents et le processus actuel doit être définitivement arrêté, ou bien les processus concurrents sont susceptibles de modifier ces conditions et il faut donc leur donner la possibilité de le faire en leur laissant l'usage des ressources de calcul.
On voit donc qu'une procédure, une fois lancée au cours d'un cycle de calculs, se déroule à l'intérieur de ce même cycle jusqu'à ce qu'elle rencontre une condition qui détermine, pour elle, la fin de ce cycle. Cette fin de cycle peut être spécifiée explicitement au moyen de l'instruction 'RESUME' qui passe le contrôle au prochain processus dans la boucle d'appels.
L'appel d'une procédure échantillonnée crée le processus correspondant qui, à partir de cet instant, va se dérouler à chaque cycle de calcul jusqu'à ce que s'établisse sa condition d'arrêt. Le processus appelant ne perd pas le contrôle au moment de l'appel. L'appel d'une procédure non échantillonnée est tout à fait classique, le processus en cours continue son déroulement au sein de la procédure appelée. L'instruction EXIT permet de provoquer l'arrêt définitif du processus en cours.instructions = { instruction }. instruction = étiquette ':' instruction | paragraphe | appelproc | ident ':=' expression | 'RESUME' | 'EXIT'.
Une instruction étiquettée peut être utilisée comme une procédure non échantillonnée sans arguments, ce qui évite d'en réécrire le détail. C'est également par ce moyen que l'on peut spécifier l'itération de certaines séquences d'instructions par l'appel d'un paragraphe à l'intérieur de lui-même. Cependant, dans cette première mise en oeuvre du langage, les paragraphes récursifs ne sont pas autorisés. C'est à dire que les appels exprimant l'itération devront être strictement récursifs à droite, i.e. qu'il ne devra pas y avoir d'instructions exécutables comprises entre le point d'appel et la fin du paragraphe appelé. En effet une récursion à droite est équivalente à une itération, et, ce qui apparaît comme un appel peut être transformé en un branchement sans altérer la sémantique du texte.
____________________________
Server © IRCAM-CGP, 1996-2008 - file updated on .
____________________________
Serveur © IRCAM-CGP, 1996-2008 - document mis à jour le .