Précédent

Pointeur

1) Introduction

La science de notre ére a cette capacité unificatrice, de réunir toutes les questions fondamentales une seule, en un seul principe qu'est la loi qui généralise..., et qui rammène toutes les explications en une seule explication. Elle est la cause de l'avènement des religions monothéistes sur celles plurithéistes, et elle constitue un argument en faveur de cette unicité dans la démarche de construction des sciences et de notre langage informatique idéal.

Les formes d'implémentation à la fois rudimentaires et fondamentales correspondent à des mécanismes de raisonnement à la fois rudimentaires et fondamentaux. On commence par exhiber le principe de l'égalité et de la transitivité qui joue un rôle fondamental en logique du premier ordre. Comment implémenter efficacement l'égalité et sa transitivité ? On le fait en utilisant les pointeurs, et en chaînant ces pointeurs pour procéder à des affectations permanentes.

Par exemple, `x"='ga'"`, `y"='bu'"` et `z"='zo'"`. Ces variables possèdent chacune un premier pointeur dont l'adresse n'est pas modifiable et qui pointe respectivement sur les valeurs `"'ga'","'bu'","'zo'"`. Un pointeur peut pointer une valeur ou un autre pointeur. La valeur de `x` s'obtient en regardant ce que pointe `x` et si c'est un pointeur, en regardant ce qu'il pointe à son tour et ainsi de suite jusqu'à arriver sur une valeur, ou si on tourne en rond alors la valeur de `x` est posée égale au pointeur nil. Ainsi chaque variable constitue une chaine de pointeur avec un premier pointeur et un dernier pointeur pointant sur une valeur, ou le pointeur nil.

On obtient une modification de `x` qui établit un lien d'égalité permanent, notée `x":="y`, en raccordant le dernier pointeur de `x` sur le premier pointeur de `y`. En procédant Ainsi, qu'avec ce type opérande `":="`, il n'y a aucune perte d'information sur l'égalité entre variable. Puis de surcroît, un apsect simplificateur est mis en oeuvre qui consiste à chaque parcourt d'un chemin de pointeurs, à faire pointer tous les pointeurs du chemin sauf le dernier sur ce dernier pointeur. Cet aspect applique la transitivité de l'égalité pour racourcir les chemins. De nombreux algorithmes vont devenir de complexité linéaire grace à cette simple astuce.

On ne rentre pas dans les détailles de l'implémentation, de ce que fait le bas niveau de protocole, on donne seulement les degrés de liberté suffisants pour que le protocole de bas niveau est au moins la possibilité de le faire, à défaut de préciser comment il le fait.

2) Les pointeurs

Un pointeur est une variable anonyme contenant une addresse désignant une case mémoire pouvant contenir une valeur, un terme ou un autre pointeur. Le concept de pointeur ouvre un nouvel horizon permettant de définir des arbres convergeant vers leurs racines. On conçoit dans notre langage terminologique monotype différents opérateurs spéciaux permettant de gérer ces arbres.

Comme nous sommes dans un langage monotype, la variable, la valeur, le terme et le pointeur doivent être de même type, seul le mécanisme de calcul présidé par les opérateurs change.

La notion de variable évolue, elle doit toujours commencer par un premier pointeur. Ainsi la variable peut toujours être perçu comme un pointeur. Et l'adresse de cette permière case mémoire contenant ce pointeur, et qui est identifié à la variable, ne change pas sauf si on détruit la variable et qu'on la recrée. Ce choix se justifie par le fait que la taille des valeurs désignés par une variable est de taille variables, et qu'il est donc plus pratique de procéder à un adressage indirecte passant d'une table des variables de taille fixe pour aller dans un empilement de données de taille variable.

L'aspect pointeur d'une variable `x` sera évaluée de trois façons différentes selon que l'on attend une valeur (ou un terme), un premier pointeur ou un dernier pointeur :

Toute variable peut être vue comme une valeur, un terme ou bien comme le premier pointeur ou bien comme le dernier pointeur, et c'est l'opérateur qui précice comment est perçu chacun de ses arguments et comment est perçu chacun de ses résultats. Les pointeurs ainsi définis vont permettre de concevoir des algorithmes de complexité linéaire qui n'auraient pas pu être facilement mis en oeuvre sans cette artifice.

L'instruction `x"="y` modifie le premier pointeur de `x` pour le faire pointer sur ce que désigne `y`. C'est l'affectation classique des variables. L'inconvénient de cette affectation, c'est qu'il y a une perte d'information. Les instructions `x"="y; y"="z` ne vont pas nécessairement entrainer que `x"=="z`. On conçois une autre modification de `x` qui se fait sans perte d'information sur les égalités, notée `x":="y` qui raccorde le dernier pointeur de `x` s'il n'est pas le dernier pointeur de `y`, sur le premier pointeur de `y`. Puis on effectue un aspect qui modifie tous les pointeurs du chemin de `x` ainsi parcouru pour les faire pointer sur son le dernier pointeur.

3) L'unification de terme

L'unification de termes joue un rôle fondamental dans la logique du premier ordre, et également en informatique car elle est de complexité linéaire grâce à l'usage des pointeurs comme nous allons le voir. La meilleur façon d'expliquer ce qu'est l'unification de deux termes est de prendre un exemple. On définit le langage `"<"a,b,f"(.)",g"(.,.)>"`. A ce stade, `a,b,f"(.)",g"(.,.)"` sont des opérateurs constants et toute autre nom est un opérateur variable. On considère deux termes

`g(g(x,g(y,z)),y)`

`g(g(x,x),g(a,x))`

Il y a donc 3 opérateurs variables `x, y, z` qui sont initialement des variables libres. L'unification peut se programmer en une fonction récurcive. On construit un langage de programmation dans le quel cet algorithme s'écrit le plus simplement possible. Un terme se décompose en une racine et une séquence d'arguments. On note X une métavariable contenant le terme. On note A(X) la racine du terme et S(X) la séquences des arguments. On note nb(S(X)) la taille de la séquence S(X) et S(X)[i] le ième argument de la séquence S(X). L'unification pourrait s'écrire ainsi :

unifier =
   X,Y ? X==nil |-> X=Y;true ||
   X,Y ? Y==nil |-> Y=X;true ||
   X,Y |-> S(X)==S(Y) && 
           n=nb(S(X));n==nb(S(Y)) && 
           b=true;for(i=0,i<n,i++){b=b && unifier(S(X)[i],S(Y)[i])};b
 

 

 

2) Les opérateurs d'arité variable

On définie les variables séquences, notée avec une étoile en suffixe tel que `x"*"` pour désigner une séquences fini de variables libres pouvant être vide. Noter que `x"*"` ne constitue pas un canal de transmission qui pourrait être sans fin. C'est une séquence finie qui représente la liste des arguments transmis à un opérateur d'arité nécessairement fini. Cela permet d'évoquer tous les opérateurs de n'importe quelle arité.

Si on utilise les variables séquence qu'en fin de séquence et seule, alors le langage ainsi étendu, admet encore un mécanisme d'unification de complexité linéaire. C'est pouquoi nous n'utilserons de variables séquences, pour les termes susceptible de faire l'objet d'une unification, que en fin de séquence et seule.

 

 

 

 

 

 

 

 

 

-*-*-*-*-*-*-*-

6) Développement canonique hardware du langage

Puisque nous avons introduit le langage réel binaire, les opérations fondamentales peuvent être définies presque naturellement comme un assembleur mais s'appliquant à une succession quelconque de bits.

CONCAT(x,y) : Concatène la donnée x et la donnée y qui sont des suites de bits.

AND(x,y) : Effectue un "et" bits à bits. Les arguments étant complété d'autant de 0 par la gauche pour avoir la même taille.

OR(x,y) : Effectue un "ou" bits à bits. Les arguments étant complété d'autant de 0 par la gauche pour avoir la même taille.

ADD(x,y) : Effectue une addition bits à bits. Les arguments étant complété d'autant de 0 par la gauche pour avoir la même taille et pour donner place à l'éventuelle retenue.

etc..

3) Terminologie monotype avec liste d'entrées et liste de sorties reliées par des lignes

Comme nous voulons optimiser la complexité des calculs, il nous faut pouvoir programmer des calculs qui retourne plusieurs résultats à la fois. Sans cela, de nombreux calculs sont refait inutilement.

L'opérateur peut posséder plusieurs entrées selon son arité d'entrée fixée. Puis par symétrie il doit pouvoir posséder plusieurs sorties selon une arité de sortie fixée aussi. Et nous devons pouvoir rediriger chaque sortie vers une entrée de notre choix. Prenons par exemple un opérateur `f` d'arité (2,3) c'est-à-dire d'arité d'entrée 2 et d'arité de sortie 3. Et considérons l'instruction d'affectation suivante :

`(x,y,z)=f(a,b)`

Cela signifie que les valeurs d'entrées de l'opérateur sont celle de `a` et `b`, et que une fois le calcul fait, le résulttat qui est une séquence de trois valeurs de sortie est affecté à la séquence des trois variables `x,y,z` respectivement.

On peut alors représenter l'opérateur `f` comme un composant possédant 2 entrées et 3 sorties, et au lieu de donner des noms à ces entrées et sorties, on trace des lignes reliants, comme dans un circuit electrique, les sorties de certain composants vers les entrées d'autres composants.

*-*-*-*-*-*-*

 

 

 

 

 

 

 

3) Shell

Sous linux, la connexion d'un fichier en lecture comprend un curseur de lecture qui pointe sur le premier caractère du fichier. Puis lors de la lecture, le curseur s'avance à chaque caractère lu. De même la connexion d'un fichier en écriture comprend un curseur d'écriture qui pointe sur la place du premier caractère du fichier à écrire. Puis lors de l'écriture, le curseur s'avance à chaque caractère écrit. Et, il y a deux façons de connecter un fichier en écriture, soit en mode "rewrite" ou en mode "append", dans ce dernier mode le curseur d'écriture se trouve à la fin du fichier.

Puis il existe un caractère dit séparateur de champ, qui sous linux est par défaut le caractère de fin de ligne "\n". Cela permet de lire une chaines de caractères correspondant ici à une ligne. Et cette opération de lecture ou d'écriture est rendu exclusive, interdisant les autres processus d'interférer dans l'opération c'est-à-dire, si je lis la ligne alors aucun autre processus ne peut écrire dans le fichier tant que je n'ai pas fini de lire la ligne, et si j'écris la ligne alors aucun autre processus ne peut ni lire ni écrire dans le fichier tant que je n'ai pas fini de l'écrire.Ces dernières prescriptions sont justifiées dans un système linux gérant des fichiers stockés sur des périphériques. Mais, dans notre situation de concepteur de langage, nous nous plaçons dans un cadre abstrait plus générale, dans le quel deux cas de figures sont à considérer :

Dans les deux cas il s'agit d'installer un mécanisme de calcul parallèle ou pseudo-parallèle si le processeur n'éxécute pas réellement en parallèle mais alloue un temps d'exécution alternativement à chaque processus. Et on voit bien comment, cette notion de processus avec ses méthodes d'accès aux fichiers partagés et à des mémoires vives partagés, vont impacter la conception de notre langage de programmation.

Les processus sont des machines abstraites, et leur conception s'accompagne de la conception des canaux de transmission entre processus, ainsi que de la conception des mute, mécanisme de verouillage d'accès à des mémoires partagées, pour pouvoir réserver l'exclusivité d'accés en cas d'écriture.


4) Première simplification du langage

La première simplification de notre langage de programmation va consister à unifier la variable et le canal. Un paramètre est un canal qui ne lit ou n'écrit qu'un seul terme.

--*-*-*-*-*-*-*--

Si l'opérateur attend comme entrée un canal, et qu'on lui connecte un paramètre, le canal en question recevra la valeur du paramètre.

Si l'opérateur attend comme entrée un paramètre, et qu'on lui connecte un canal, le premier terme émit par le canal va être capturé et affecté au paramètre en question.

Si l'opérateur offre comme sortie un canal, et qu'on lui connecte un paramètre, le premier terme émit par le canal va être capturé et affecté au paramètre en question.

Si l'opérateur offre comme sortie un paramètre, et qu'on lui connecte un canal, le canal en question recevra la valeur du paramètre.

-*-*-*-*-*-

Que se pase-t-il s'ils se branche sur les entrées deux mêmes canaux, ou si l'ntrée présente deux canaux interne. Et de même pour les sorties.

 

7) Les opérateurs d'arité variable

Il semble qu'il faille définir l'opérateur d'arité variable différement, prenant en entrée une séquence de termes (ou de valeurs) de taille variable non-bornée qui s'apparente davantage à un canal de transmission, qui par nature constitue une ligne et non un arbre. Et cela rejoint un autre concept informatique incontournable qu'est le canal de transmission entre deux processus.

L'opérateur d'arité d'entrée variable possède une seule entrée de canal, de même l'opérateur d'arité de sortie variable possède une seule sortie de canal.

 

 

4) Polymorphisme

Le contexte contient la définition du langage. Et pour que cela ne soit pas trop encombrant, il s'agira d'un contexte journalisée, la dernière mise-à-jour du contexte, le contexte héritant de ses parents.

5) Sémantique

La première sémantique à laquel on pense est celle obtenue sans effort : L'opérateur joue le rôle d'un alias. Il y a deux mondes : le monde idéal constitué par notre langage formel, et le monde réel constitué par la couche logiciel de base.

L'opérateur possède une liste de `n` entrées et il possède une liste de `m` sorties. L'opérateur d'arité `(n,m)` qui fait partie du monde idéal est associé à un sous-programme qui est une procédure de même arité faisant partie du monde réel. L'interpréteur va simplement traduire chaque opérateur par la procédure qui lui est associé et procéder à son appel en connectant les `n` entrées, et en connectant les `m` sorties. On applique la logique polonaise c'est-à-dire que si un opérateur posséde des arguments non-spécifiés, ils sont simplement reportés et ajoutés dans l'ordre de leur apparision comme des entrées du programme global, et de même si un opérateur posséde des sortie qui ne sont pas utilisées, elles sont simplement reportées et ajoutés dans l'ordre de leur apparition comme des sorties du programme global.

Le premier langage `L_1` est une terminologie monotype. Tous les arguments sont d'un même type `E`. Ainsi tous les opérateurs de notre langage ont un type de la forme, soit `E`, ou soit `E^n"→"E^m` `n` et `m` sont des entiers strictement positif. On ajoute à cela l'opérateur poubelle qui prend un argument et ne retourne rien.

L'opérateur nullaire est associé à une donnée réelle.

6) Conversion en Bash

On est sous l'ère de Linux. La première forme de compilateur à laquelle on pense convertie notre langage en Bash. Chaque opérateur du langage idéal est associé à une commande dite réel qui est un script shell Bash et qui doit avoir le même nombre d'arguments que l'arité de l'opérateur, et dont les arguments et le résultat sont des données d'un même type unique dit réel. L'avantage de passer par le shell Bash est de pouvoir utiliser les tubes nommées comme canaux.

Une première difficulté apparaît dans la transmission des arguments réel à la commande réel. En effet, le shell prévoit des arguments sous forme de chaines de caractères, et il existe de nombreux caractères spéciaux du Bash qui correspondent à des commandes. Pour que ses caractères spéciaux n'agissent pas et se comportent comme n'importe quel autre caractère, il faut que la chaine soit encadrée par des simples quotes '...'.

Le shell prévoit aussi des arguments pouvant être de trés grande taille et pouvant être transmis par l'entrée standart et/ou par d'autres tubes nommés ou indirectement par l'intermédiaire de noms de fichier. À notez qu'il existe la possibilité d'utiliser un fichier comme une liste de `n` blocs. L'accès aux données passe alors par un nom de fichier et un indice entier. À notez qu'il n'existe pas de notion de pointeur et que l'addressage indirecte ne se fait que sous forme de liste de valeur de taille variable et donc nécessairement par un mécanisme de double adressage donc moins performant.

Ces contraintes techniques nous oblige à concevoir une première version de notre interpréteur n'utilisant que des arguments en ligne de commande entouré de simples quotes '...', et donc on ne peut pas utiliser le caractère quote ' dans notre langage. Si on tient absolument à pouvoir l'utiliser, la solution la plus simple consiste alors à convertir notre langage idéal en un langage précodé ramplaçant le caractère quote ' par un autre, mais dés lors cet autre caractère ne doit pas être utilisé dans notre langage idéal.

Le Bash n'est pas un langage de programmation complet du point de vue de la rapidité, en particulier il ne permet pas de manipuler les pointeurs et de faire des adressages indirects performant, et il n'existe pas de réel compilateur du Bash, C'est pourquoi cette solution noté L0 reste d'un intérêt limité.

6) Conversion en Golang

La seconde forme de compilateur à laquelle on pense convertie notre langage en langage Golang.

----- 29 juin 2023 ----

 

 

 

 

 

Le langage est alors défini dans un dictionnaire qui est une liste de triplets. Chaque triplet contient un nom d'opérateur idéal, un entier désignant l'arité de l'opérateur, et le nom d'une procédure réel ayant le même nombre d'arguments. Mais attention, les arguments et les résultats doivent tous être des données d'un même type réel unique.

Une autre contrainte technique concerne la forme des données réels qui doivent être des chaines de caractères ne comprenant pas de guillemet car dans sa forme la plus pratique, le guillemet est utilisé pour délimité la chaine de caractère servant d'argument dans une ligne de commande.

On met en oeuvre cet interpréteur L1 sous forme d'une ligne de commande en shell Bash s'executant dans un terminal possédant un environnement. L'interpréteur agit pour le compte de ce terminal. Il utilise le langage `L_1` qui doit être mémorisé dans l'environement du terminal. Il interprète une formule et l'exécute dans le terminal. Si la formule est de petite taille, elle peut être transmise comme argument en ligne, sinon elle doit être transmise par l'entrée standard. Puis l'exécution de la formule sera susceptible de modifier l'environnement du terminal et le langage lui-même.

-*-*-*-*-*-*-

 

4) Implémentation

On est sous l'ère de Linux. La première forme d'implémentation s'appuit sur le système d'exploitation et le terminal de commande en shell Bash. Chaque opérateur du langage idéal constitue un alias d'une commande réel présente dans le shell (qui est un nom de programme accessible via le PATH) et qui doit avoir le même nombre d'arguments que l'arité de l'opérateur, et dont les arguments et le résultat sont des données d'un même type réel unique.

Une première difficulté apparaît dans la transmission des arguments réel à la procédure réel. En effet, le shell ne prévoit que des arguments de petite taille et en petit nombre transmis dans la ligne de commande, et un argument de grande taille transmis par l'entrées standart.

Cette contrainte technique nous oblige à concevoir une première version de notre interpréteur n'utilisant que des arguments réels de petite tailles, mais pouvant quant-même interprété une formule de taille gigantesque.

Le langage est alors défini dans un dictionnaire qui est une liste de triplets. Chaque triplet contient un nom d'opérateur idéal, un entier désignant l'arité de l'opérateur, et le nom d'une procédure réel ayant le même nombre d'arguments. Mais attention, les arguments et les résultats doivent tous être des données d'un même type réel unique.

Une autre contrainte technique concerne la forme des données réels qui doivent être des chaines de caractères ne comprenant pas de guillemet car dans sa forme la plus pratique, le guillemet est utilisé pour délimité la chaine de caractère servant d'argument dans une ligne de commande.

On met en oeuvre cet interpréteur L1 sous forme d'une ligne de commande en shell Bash s'executant dans un terminal possédant un environnement. L'interpréteur agit pour le compte de ce terminal. Il utilise le langage `L_1` qui doit être mémorisé dans l'environement du terminal. Il interprète une formule et l'exécute dans le terminal. Si la formule est de petite taille, elle peut être transmise comme argument en ligne, sinon elle doit être transmise par l'entrée standard. Puis l'exécution de la formule sera susceptible de modifier l'environnement du terminal et le langage lui-même.

Une troisième difficulté alors apparaît pour modifier l'environnement du terminal. Car dans la conception même du système Linux, un processus ne peut pas changer l'environnement de son processus père. L'interpréteur est un progamme à part entière qui s'exécute dans un processus fils du terminal.

On contourne la difficulté en utilisant un environnement partagé qui est un fichier temporaire, et donc, qui est accessible en lecture-écriture par tout processus mais de façon non-simultanée. Pour assurer la rapidité d'accès, on crée ce fichier temporaire en mémoire vive comme suit :

    mkdir /home/martin/ram
    sudo mount -t tmpfs -o size=1000M none /home/martin/ram

Et pour le démonter :

    sudo umount /home/martin/ram  

Notez que la taille de ce fichier temporaire est limitée arbitrairement.

La première opération consiste à mémoriser le dictionnaire du langage dans l'environnement partagé, ce qui se fait par exemple par la commande suivante :

echo "f 2 F g 3 G h 0 H" > /home/martin/ram/L1

Le shell Bash utilise les redirections ce qui permet de mettre les données arguments dans des fichiers textes. On peut donc pré-enregistrer le langage dans un fichier local L1 puis après, le recopier :

echo "f 2 F g 3 G h 0 H" > data
  cat data > /home/martin/ram/L1

La seconde opération est l'interpréteur lui-même qui porte comme nom L1 et qui prend en argument une formule. Par exemple :

L1 "f(g(h,h,h),f(h,f(h,h)))"

et si la formule est de grande taille, elle est transmise par l'entrée standard :

echo "f(g(h,h,h),f(h,f(h,h)))" > F

cat F > L1

ou avec un pipe :

 echo "f(g(h,h,h),f(h,f(h,h)))" | L1

5) Programmation en Go

Nous programmons la commande L1 qui constitue notre interpréteur, en utilisant le langage de programmation Go. Le dictionnaire du langage idéal `L_1` est mémorisé dans le fichier /home/martin/ram/L1. On le récupère sous forme d'une string s par l'instruction

s, e := ioutil.ReadFile("/home/martin/ram/L1")

La première contrainte technique sur le shell nous oblige à deux formes d'appel de notre interpréteur. La formule à executer est transmise soit comme paramètre en ligne de commande, ou soit par l'entrée standart si sa taille est trop grande pour tenir en ligne de commande.

5.1) Introduction

Le programme L1 commence par ces lignes. Le dictionnaire du langage est mémorisé dans la variable m de type dictionnaire :

package main

import (
    "errors"
    "fmt"
    "io/ioutil"
    "os"
    "strconv"
    "strings"
)

type Proc struct {
    n int
    p string
}

func main() {

 

 
// Pour la foncion panic et errors.New
// Pour afficher, fmt.Println
// Pour la fonction ioutil.ReadAll
// Pour l'entrrée standart, os.Stdin
// Pour la fonction strconv.Atoi
// Pour la fonction strings.Fields et le tpe string
 


// n = arité de l'opérateur
// p = nom de la procédure


//----- Charge le dictionnaire du langage ---------------------------------------------------------------------------------------------
 
    s, e := ioutil.ReadFile("/home/dmabboux/ram/L1")
    if e != nil {
        panic(e)
    }
    m := map[string]Proc{}
    k := strings.Fields(string(s))
    n := len(k)
    if n%3 != 0 {
        panic(errors.New("Langage incomplet"))
    }
    for i := 0; i < n; i += 3 {
        j, e := strconv.Atoi(k[i+1])
        if e != nil {
            fmt.Println(e)
        }
        m[k[i]] = Proc{j, k[i+2]}
    }

// Récupère dans s le langage L1

// Émet l'éventuel erreur d'entrée/sortie

// Crée un dictionnaire
// Transforme " f 2 F g 3 G " en [f 2 F g 3 G]
// Nombre de mots dans la définition du langage
// Si n n'est pas un multiple de 3 alors on émet une erreur.


// Pour i = 0. 3. 6. 9, ... , n
// j = l'entier correpondant à la chaine k[i+1]

// Émet l'éventuel erreur de reconnaissance d'un entier

// Ajouter au dictionaire le mot k[i] associé au couple {j,k[i+2]}
//------ Lit la formule ----------------------------------------------------------------------------------------------------------------------
    f := ""
    if len(os.Args) == 1 {
        v, e := ioutil.ReadAll(os.Stdin)
        if e != nil {
            panic(e)
       }
        f = string(v)
    } else {
        u := os.Args[1]
        f = string(u)
    }

// S'il n'y a pas d'argument en ligne
// Récupère dans v la formule arrivant par l'entrée standart

// Émet l'éventuel erreur d'entrée/sortie



// Récupère la formule arrivant par la ligne de commande

//------ Test ----------------------------------------------------------------------------------------------------------------------

    fmt.Println(m)
    fmt.Println(f)

    for f != "" && f[0] == byte(' ') {
        f = f[1:]
    }
    fmt.Println(f)

    r := []rune(f)

    fmt.Println(r)
}    




// Retire les premier caractères blancs




// Convertit la chaine en un tableau de mots de 32 bits


On utilise la norme internationale Unicode qui codifie le plus vaste jeux de caractères provenant du monde entier, comprenant toutes les langues et les jeux de symboles. Et on utilise la norme UTF-8 qui permet pour les caractères courant de tenir que sur un seul octet, puis sur 2 ou 3 ou 4 octets pour des caractères qui s'avèrent moins utilisés.

Pour faciliter la programmation, on a besoin d'une liste de caractères de même taille c'est pourquoi on procède à une convertion de la chaine de catactères en un tableau de runes, où une rune est un mot de 32 bits (4 octects), comme si nous utilisions la norme UTF-32.

5.2) Recurcivité ou progressivité

Il y a deux solutions, l'une dite récurcive qui s'applique sur des formules de taille suffisament raisonnable pour que la pile d'appels ne déborde pas, l'autre dite progressive qui s'applique à des formules de taille gigantesques. La solution récurcive lit la formule en lançant un processus récurcif. Par exemple, considérons la formule `f(g(h,h,h),f(f(h,h),h))`. L'interpéteur `varphi` lit le premier opérateur `f` et lance la commande F appliqué aux arguments qui sont le résultat d'une lecture par l'interpréteur des sous-formules correpondantes. Nous obtenons :

`varphi( f(g(h,h,h),f(f(h,h),h)) ) = sfF( varphi( g(h,h,h) ), varphi( f(f(h,h),h)) ) )`

`varphi(h) = H`

La solution progressive lit la formule et convertit les feuilles, c'est à dire les opérateurs nullaires, et converti les opérateurs dont tous les arguments sont des données réels. Puis on recommence l'opération jusqu'à n'avoir qu'un seul résultat réel. Considérons par exemple la formule `f(g(h,h,h),f(f(h,h),h))`. L'interpéteur `varphi` fait une première lecture et convertit toutes les feuilles.

`varphi( f(g(h,h,h),f(f(h,h),h)) ) = f(g(H,H,H),f(f(H,H),H))`

`varphi( f(g(H,H,H),f(f(H,H),H)) ) = f(A,f(B,H))`
`A=G(H,H,H)`
`B= F(H,H)`

`varphi( f(A,f(B,H)) ) = f(A,C)`
`C= F(B,H)`

`varphi( f(A,C)  ) = D`
`D = F(A,C)`

On remarque que les deux solutions passent par un langage élargie incorporant les données réel avec la même syntaxe idéale.

5.3) Solution récurcive

A ce stade, l'interpréteur reconnait le premier opérateur comme la chaine sans caractère blanc se situant collée au caractère "(". Puis il reconnait les arguments séparés par des virgules au premier niveau de parenthèse et qui peuvent être espacés par des caractères blancs. Plusieur erreurs de syntaxes peuvent se produirent :

  1. L'opérateur fonctionnel doit être collé à la parenthèse ouvrante.
  2. Les parenthèses doitvent définir des niveaux emboités.
  3. La virgule sépare les arguments.
  4. L'arité de l'opérateur doit être respectée.

Deslors, l'appel `g(h,,)` désigne l'appel de l'opérateur `g` avec trois arguments dont le premier est `h` et le second ainsi que le troisième est l'argument vide. Il existe donc un argument vide, et qui n'est pas référencé dans le dictionnaire, et qui s'écrit comme donnée réel par la chaine vide "".

L'interpréteur, après avoir repéré l'opérateur acollé à la parenthèse ouvrante, va énumérer les sous-formules correspondant aux arguments qui sont séparées par des virgules au premier niveau de parenthèse et par la parenthèse fermant ce premier niveau.

 

 

 

 

 

 

 

6) Des données textes ou binaires

Apparait la notion de données textes ou binaires. La donnée se présente sous forme d'un bloc contiguë d'octets formant une chaine de caractère.

Une donnée binaire possède une taille fixe exprimés en nombre d'octets et admet toutes les valeurs possibles. Un octet admet 256 valeurs possibles.

Une données texte est une chaine de caractères au format unicode/utf8 et qui est délimité. Il existe plusieurs sortes de délimitation. Celle utilisée par les arguments d'une fonction dans une commande ligne du shell, consiste le plus simplement en l'entourage de l'argument par des guillemets, faisant que le guillemet ne doit pas être utilisé dans la chaine de caractères. L'usage du caractère guillemet est ainsi réservé par le protocole de bas niveau.

7) Langage étendu

La résolution passe par une extension du langage idéal en introduisant les données réels

 

 

 

 

---- 9 novembre 2022 ----

 

 

 

 

 

 

 

 

 

 


Dominique. Mabboux-Stromberg