Lisek: évaluation des Tokens

a marqué ce sujet comme résolu.

Bonjour, Dans mon langage Lisek, je suis parvenu a produire ce qui suit:

  • output: Contient les tokens,
  • values: Contient les valeurs des variables, accessible avec le token Ident(id) qui contient l’ID dans values de sa valeur La syntaxe de mon langage se présente comme suit:
let a (2)
let b (+ 1 (- 2 1)) // 2

cond (boolean_statment) { /* if */ } { /* else */ }
until (boolean_statment) { /* do */ } // loop

conc "hello," " world" // "hello, world"
format (a) // 2
format ("hello") // "hello"

let est géré totalement pendant le parsing et il y a donc aucun Token associé a la création de variable.

Ce que je voudrais, et c’est ce que j’avais essayé, c’est de faire en sorte qu’un builtin (+, -, *, until, cond, conc, …) soit toujours le point d’entrée de toute choses, de sorte que rien ne puisse commencer avec une chaine de caractère ou un nombre par exemple car ça n’a pas de sens. J’avais tenté de faire une fonction eval qui vérifie que le premier token soit un Builtin et si c’est le cas, appelait la fonction que contenait le builtin. Une fois la fonction appelé, dans cette fonction, j’appelle la fonction advance le même nombre de fois qu’elle n’avait d’argument. Donc, par exemple, si je tombe sur cond, j’appelle trois fois advance pour récupérer le booléen, le scope du if et du else. La fonction advance est la suivante:

pub fn advance(&mut self, tok: Token) {
    match tok.ttype {
        TType::String(s) => self.stack.push(Value::String(s)),
        TType::Integer(i) => self.stack.push(Value::Integer(i)),
        TType::Float(f) => self.stack.push(Value::Float(f)),
        TType::Builtin(func) => func(self, self.pos, tok.line, tok.column),
        TType::Ident(id) => self.advance(self.values.get(id).unwrap().clone()),
        TType::SParen(v) => for t in v {self.advance(t)}
        TType::SBrac(v) => self.stack.push(Value::SBrac(v))
    }
}

Ma démarche nécessite beaucoup de choses étranges dans mon code, et elle m’a l’air assez triviale. J’aimerai que vous me proposiez une autre façon de faire pour évaluer de façon cohérente mes tokens.

Cordialement.

Salut,

J’ai du mal à comprendre le lien entre le problème que tu exposes (vérifier que chaque instruction commence par un mot bulitin) et la solution que tu proposes (fonction eval et récupération de tous les tokens).

La solution ne consisterait-elle pas juste à chaque détection d’une nouvelle instruction lors de l’analyse de vérifier que le premier token appartient à un ensemble précis ?

On pourrait aussi se poser la question de pourquoi interdire une telle construction. Peut être que ça n’a pas de sens, mais + 5 3 sans rien faire du résultat n’en a pas plus. Ça semble pourtant valide de laisser le programme exécuter des instructions inutiles.

On pourrait aussi se poser la question de pourquoi interdire une telle construction. Peut être que ça n’a pas de sens, mais + 5 3 sans rien faire du résultat n’en a pas plus. Ça semble pourtant valide de laisser le programme exécuter des instructions inutiles.

Mon langage est fait de tel sorte que s’il croise une String(s) ou tout autre type primaire, il l’ajoute a la stack, et je veux pas que ce soit possible pour des resons de coherence dans le code.

Mon idee, ce serait d’ajouter un processus avant l’evaluation qui serait de produire en sorti une chaine de token contenant chaque builtin avec ses argument dans le meme Token, de tel sorte qu’on aurait List(Builtin(Self::cond), TType::SParen(..), TType::SBrac(..), TType::SBrac(..)) pour le builtin cond. Avec cette nouvelle liste de token, je pourrai proceder a une evaluation en sens inverse pour chaqu’un de ces tokens, ce qui me permettrait d’ajouter les elements a la stack avant d’appeller la fonction. Donc, pour mon exemple precedant, j’appliquerai la fonction reverse a mon token (List(Builtin(Self::cond), TType::SParen(..), TType::SBrac(..), TType::SBrac(..))) pour obtenir List(TType::SBrac(..), TType::SBrac(..), TType::SParen(..), Builtin(Self::cond)).

Salut,

Ta question n’est pas très claire, pour être honnête. Tu parles de tokens et de les manipuler individuellement pour l’évaluation dans ce qui ressemble à un interpréteur, mais ta question concerne plutôt la grammaire voire la sémantique de ton langage, j’ai l’impression. Tu parles d’interdire et/ou d’autoriser certains tokens à certaines positions, c’est le boulot de la grammaire, ça.

Un truc qui me surprend beaucoup, c’est ça :

let est géré totalement pendant le parsing et il y a donc aucun Token associé a la création de variable.

À quoi ressemble let a (2) par exemple ?

À quoi ressemble let a (2) par exemple ?

A rien comme je l’ai dis, ca ne figure nul part dans ma suite de Token. C’est a dire que lors de mon parsing qui fait aussi le boulot d’un lexer en quelque sorte (je cree l’AST sur la chaine de caractere qu’est le programme d’entree), il y a deux choses majeures possibles: si je tombe sur des parentheses, une accolade, une guillemet, un nombre ou un signe -, j’appelle respectivement des fonctions qui viennent creer un token comme Integer(i), Float(f), String(s), SParen(Vec<Token>) ou SBrac(Vec<Token>). Le deuxieme cas, c’est pour les identifiants qui peuvent etre trois choses possibles: le premier cas est let, qui n’est pas considere comme un builtin, et qui est gere independament des autres identifiants. Pour se faire, j’ai une fonction qui recupere le nom de la variable (a dans ton exemple), qui l’ajoute a un vecteur symbols, et qui recupere ce qu’il y a dans les () dans un Token SParen(..). Ce Token, je l’ajoute a un autre vecteur appele values, et si vous avez bien suivi, cette valeur a la meme coordonne que son nom dans symbols, et donc je peux acceder facilement a la valeur d’une variable en regardant au meme index dans values. Si je croise une identifiant qui n’est pas un builtin et dont le nom se trouve dans symbols, j’ajoute un token Ident(id) qui contient l’index pour acceder a sa valeur.

+0 -0

La complexité du paragraphe que tu es obligé d’écrire pour tenter d’expliquer ce qui se passe lorsque tu rencontres une simple affectation est un très bon indice que mélanger l’analyse lexicale et l’analyse syntaxique est une fausse bonne idée. C’est pas pour rien si tous les compilateurs modernes un peu sérieux les séparent. Ta question sur comment autoriser ou non certains tokens à certaines positions est réglée par construction si tu sépares les deux et que ton analyse syntaxique s’appuie sur une grammaire.

Ton analyse lexicale doit être bête et méchante, tu prends un code et tu sors une suite de tokens qui représentent tous les lexèmes significatifs du programme d’entrée. Seulement ensuite tu peux commencer à les manipuler et aller vers des représentations internes différentes pour les analyses grammaticales et sémantiques.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte