execution de mon petit langage

a marqué ce sujet comme résolu.

bonjour, j’ai commence a créer un petit interpréteur en rust, j’ai fait le lexer , la construction d’arbre syntactique , et il manque que l’execution . la sortie de mon arbre syntactique de ce code par example let a = 5 : Let {name: "a", value: Number {value: "5"}} ou encore if a then b donne IfThen {cond: Identifier {value: "a"} , then: Identifier {value: "b"}} pouvez vous m’aider ou m’expliquez comment il faut faire

lexer.rs :





#[derive(Debug, PartialEq, Clone)]
pub enum Lexer {
    // The lexer is a simple state machine with the following states.
    //
    // * `Seperator`: The lexer expects to find  or one
    //   of the symbols `(`, `)`, `{`, `}`, `[`, `]`, `;`, `,`
    //
    // * `Operator`: The lexer expects to find one of the symbols `+`, `-`, `*`, `/`, `%`, `=`, `!`, `<`, `>`, `&`, `|`, `^`, `~`, `?`
    // * `Comment`: The lexer expects to find a `*/` to end the comment.
    //
    // * `String`: The lexer expects to find a `"` to end the string.
    //
    // * `Number`: The lexer expects to find a number.
    //
    // * `Identifier`: The lexer expects to find an identifier.
    //
    // * `Operator`: The lexer expects to find an operator.
    //
    // * `Keyword`: The lexer expects to find a keyword.
    //
    // * `Error`: The lexer expects to find an error.
    //
    // * `Eof`: The lexer expects to find the end of the file.
    Comment(String),
    Number(String),
    Identifier(String),
    Operator(String),
    Keyword(String),
    String(String),
    Error(String),
    Eof
    }

impl Lexer {
    pub fn check_for_number(input: &str) -> bool {
        input.to_string().parse::<f64>().is_ok()
    }
    pub fn check_for_identifier(input: &str) -> bool {
        let chars = input.chars().collect::<Vec<char>>();
        if input.len() == 0 {
                return false;
            }
            chars[0].is_alphabetic() || chars[0] == '_'

        }

    pub fn check_for_operator(input: &str) -> bool {
        if input.len() > 1 {
            return false;
        }
        match input {
            "+"| "-" | "*" | "/" | "%"  => true,
            "=" | "!" | "<" | ">" | "&" | "|" | "^" | "~" | "?" => true,
            _ => return false
        }
    }

    pub fn check_for_keyword(input : &str) -> bool {
        match input {
            // check if one of the keywords is in the input
            "if" | "else" | "for" | "while" | "let" | "then" | "do" | "in" => true,
            _ => false
        }
    }

    pub fn check_for_seperator(input: &str) -> bool {
        match input {
            "(" | ")" | "{" | "}" | "[" | "]" | ";" | "," => true,
            _ => false
        }
    }

}
                
pub fn tokenize(string: &str) -> Vec<Lexer> {
    let mut tokens = Vec::new();
    let buffer = string.to_string();
    let mut tkchar = String::new();
    let mut is_string = false;
    let lenght = buffer.len();
    let mut string_curr;
    let mut d = false;
    for (i, c) in &mut buffer.chars().enumerate() {
        string_curr = c.to_string();
        if d {
             d = false;
            continue
        }
        if is_string {
            if string_curr == "\"" {
                is_string = false;
                tokens.push(Lexer::String(tkchar));
                tkchar = String::new();
                d = true;
                continue
            } else {
                tkchar += &c.to_string();
                continue
            }

        }
        if string_curr == "\"" {
            is_string = true;
            continue
        }

        if i == lenght - 1 {
            tkchar += &string_curr;
            tkchar = tkchar.trim().to_string();
            if Lexer::check_for_number(&tkchar)  {
                tokens.push(Lexer::Number(tkchar));
                tkchar = String::new();

            } else if Lexer::check_for_operator(&tkchar) {
                tokens.push(Lexer::Operator(tkchar));
                tkchar = String::new();
            } else if Lexer::check_for_keyword(&tkchar) {
                tokens.push(Lexer::Keyword(tkchar));
                tkchar = String::new();
            } else if Lexer::check_for_identifier(&tkchar) {
                tokens.push(Lexer::Identifier(tkchar));
                tkchar = String::new();
            } else {
                tokens.push(Lexer::Error(tkchar));
                tkchar = String::new();
            }
            break;
        }
        if string_curr == " ".to_string() || string_curr == "\n".to_string() || string_curr == "\r".to_string() {
            if i == 0 {
                continue;
            }

            if Lexer::check_for_number(&tkchar)  {
                tokens.push(Lexer::Number(tkchar));
                tkchar = String::new();
            
            } else if Lexer::check_for_operator(&tkchar) {
                tokens.push(Lexer::Operator(tkchar));
                tkchar = String::new();
            } else if Lexer::check_for_keyword(&tkchar) {
                tokens.push(Lexer::Keyword(tkchar));
                tkchar = String::new();
            } else if Lexer::check_for_identifier(&tkchar) {
                tokens.push(Lexer::Identifier(tkchar));
                tkchar = String::new();
            } else {
                tokens.push(Lexer::Error(tkchar));
                tkchar = String::new();
            }

            continue
        } 
        tkchar += &string_curr;
    }
            
    return tokens;
}

tree.rs


#[derive(Debug, PartialEq, Clone)]
pub enum Expr {
    IfThen {
        cond: Box<Expr>,
        then: Box<Expr>    
    },
    IfThenElse {
        cond: Box<Expr>,
        then: Box<Expr>,
        else_: Box<Expr>,
    },
    While {
        cond: Box<Expr>,
        body: Box<Expr>
    },
    Let {
        name: String,
        value: Box<Expr>
    },
    Number {
        value: String
    },
    Identifier {
        name: String
    },
    BinOp {
        op: String,
        left: Box<Expr>,
        right: Box<Expr>
    },
    String {
        value: String
    },
    For {
        name: String,
        iter: Box<Expr>,
        body: Box<Expr>
    },
}

et parser.rs

use crate::lexer::Lexer;
use crate::tree::Expr;

pub fn parse_expr(tokens: &mut Vec<Lexer>) -> Result<Expr, String> {
    if tokens.len() == 0 {
        return Err("Unexpected end of file".to_string());
    }
    let token = tokens[0].clone();
    match token {
        Lexer::Number(n) => {
            tokens.remove(0);
            return Ok(Expr::Number { value: n.clone() });
        },
        Lexer::Identifier(i) => {
            tokens.remove(0);
            Ok(Expr::Identifier { name: i.clone() })
        },
        Lexer::Keyword(k) => {
            match k.as_ref() {
                "if" => {
                    tokens.remove(0); // remove "if" of if block
                    let cond = parse_expr(tokens)?;
                    if tokens[0] != Lexer::Keyword("then".to_string()) {
                        return Err("Expected keyword 'then'".to_string());
                    }
                    tokens.remove(0); // remove "then" of if block 
                    let then = parse_expr(tokens)?;
                    if tokens.len() == 1 {
                        Ok(Expr::IfThen {
                            cond: Box::new(cond),
                            then: Box::new(then)
                        })
                    } else if tokens.len() >= 2 {
                            tokens.remove(0);
                            let else_ = parse_expr(tokens)?;
                            Ok(Expr::IfThenElse {
                                cond: Box::new(cond),
                                then: Box::new(then),
                                else_: Box::new(else_)
                            })
                        
                    } else {
                        Err("error syntatic".to_string()) // doesn't match any case         
                    
                    }
                    
                },
                "while" => {
                    tokens.remove(0); // remove "while" of while block
                    let cond = parse_expr(tokens)?;
                    if tokens[0] != Lexer::Keyword("do".to_string()) {
                        return Err("Expected keyword 'do'".to_string());
                    }
                    tokens.remove(0); // remove "do" of while block
                    let body = parse_expr(tokens)?;
                    Ok(Expr::While {
                        cond: Box::new(cond),
                        body: Box::new(body)
                    })
                },
                "let" => {
                    tokens.remove(0);
                    if let Lexer::Identifier(name) = tokens[0].clone() {
                        
                        tokens.remove(0);
                        if tokens[0] != Lexer::Operator("=".to_string()) {
                            return Err("Expected operator '='".to_string());
                        } else {
                            tokens.remove(0);
                        }
                        let value = parse_expr(tokens)?;
                        Ok(Expr::Let {
                            name: name.clone(),
                            value: Box::new(value)
                        })
                    } else {
                        Err("Expected identifier".to_string())
                    }
                },
                "for" => {
                    tokens.remove(0);
                    println!("for: {:?}", tokens.len());
                    if tokens.len() < 4 {
                        return Err("Expected identifier: invalid syntash".to_string());
                    }
                    if let Lexer::Identifier(name) = tokens[0].clone()  {
                        tokens.remove(0);
                        if tokens[0] != Lexer::Keyword("in".to_string()) {
                            return Err("Expected keyword 'in'".to_string());
                        }
                        tokens.remove(0);
                        let iter = parse_expr(tokens)?;
                        if tokens[0] != Lexer::Keyword("do".to_string()) {
                            return Err("Expected keyword 'do'".to_string());
                        }
                        tokens.remove(0);
                        let body = parse_expr(tokens)?;
                        Ok(Expr::For {
                            name: name.clone(),
                            iter: Box::new(iter),
                            body: Box::new(body)
                        })
                    } else {
                        Err("Expected identifier: no var".to_string())
                    }
                },
                
                _ => Err("Unexpected keyword: ".to_string())
            }
        },
        Lexer::Operator(op) => {
            tokens.remove(0);
            let left = parse_expr(tokens)?;
            let right = parse_expr(tokens)?;
            Ok(Expr::BinOp {
                op: op.clone(),
                left: Box::new(left),
                right: Box::new(right)
            })
        },
        Lexer::String(s) => {
            tokens.remove(0);
            Ok(Expr::String { value: s.clone() })
        },
        _ => Err("Unexpected token".to_string())
    }
}

merci pour votre temps

Salut,

Si je comprends bien, tu arrives à fabriquer l’arbre d'Expr correspondant à l’entrée utilisateur, et ce qu’il te manque est la machine virtuelle capable d’exécuter cet arbre ?

Ça va dépendre un peu de ce que ton langage est capable de représenter. Vu ce que tu as dans Expr, j’ai l’impression que tu peux te contenter de quelque chose de simple. À toi de voir ensuite à quel point tu veux raffiner les choses. Ta machine virtuelle pourrait par exemple contenir un mapping entre les identifieurs et leurs valeurs, du style:

struct Ident(String);

enum Value {
     Number(f64),
     String(String),
     Bool(bool),
}

struct Vm(std::collections::HashMap<Ident, Value>);

impl Vm {
     fn eval_expr(&mut self, expr: Expr) -> Value {
         match expr {
             todo!()
         }
     }
}

Par ailleurs, je n’ai pas regardé ton code dans le détail mais ça me surprend un peu que tu as des String partout dans ta définition de Expr, y compris pour les variantes qui contiennent par exemple des nombres. Au lieu de toutes ces variantes pour des expressions qui sont directement des valeurs, tu pourrais utiliser une variante (e.g. Literal) autour de l’enum Value que je propose (note les bool en plus de ce que tu as comme tu as des conditions par-ci par-là qui sont aussi des Expr).

Évidemment, c’est une VM très naïve, mais c’est un début honorable. Selon ce que tu comptes faire de ce projet, il y a des tonnes de directions pour améliorer ça, mais il faudrait en savoir un peu plus sur ce que tu comptes faire pour t’aiguiller.

+0 -0

Salut,

Je viens de jeter un œil à ton code, et mon principal commentaire est que tu n’encodes pas assez d’information directement dans le système de type. Un des gros points forts de Rust est son système de type fort, statique, et expressif, autant s’en servir !

Le nœud du problème se situe dans ton Lexer (un meilleur nom serait Token au passage) qui se contente de wrapper des String. Dans le reste de mon explication je vais me concentrer sur Keyword mais c’est applicable sur les autres variantes. Ça présente quelque problèmes :

  • tu fais le travail de vérification sur les String deux fois, par exemple tu vérifies dans Lexer::check_for_keyword que la chaîne est un keyword valide, et tu te retapes le test dans parser::parse_expr (qui au passage porte mal son nom puisqu’elle absorbe des Lexer (Token) plutôt que des Expr) avec une branche qui absorbe tout et sort une erreur lorsque tu matches Lexer::Keyword(k) contre un "keyword" invalide ;
  • ça veut dire que si tu agrandis la liste des keywords valides, tu n’as aucun garde-fou si tu oublies le cas correspondant dans le match ou que tu ne le prends pas en compte dans Lexer::check_for_keyword ;
  • c’est pas top de se trimbaler des String partout, ça veut dire des allocations lors des clones et des désallocations lors des drop. C’est probablement pas critique dans ton projet qui est purement éducatif, mais si on peut l’éviter facilement, autant le faire.

Au passage, un autre symptôme que ton problème est mal modélisé est que tu as une variante Lexer::Error au lieu de n’avoir que des états valides pour l’objet Lexer et remonter les erreurs via des mécanismes idiomatiques comme Result<T, E>.

Ces problèmes peuvent être réglés facilement en utilisant le système de type. Tu peux modéliser l’ensemble des keywords possible avec une enum. Cette enum peut implémenter FromStr (qui remplace Lexer::check_for_keyword), et cette implémentation te garantie que tu ne construis que des Keywords valides (et tu ne trimbales plus des chaines brutes mais une représentation logique à la place). Ça te permet d’avoir des match sans pattern "match-all", et donc une vérification statique que tous les cas sont pris en compte (ce qui est précieux lorsque tu changes la liste de keywords possibles).

use std::str::FromStr;

enum Keyword {
    If,
    Else,
    While,
}

impl FromStr for Keyword {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "if" => Ok(Keyword::If),
            "else" => Ok(Keyword::Else),
            "while" => Ok(Keyword::While),
            _ => Err(format!("{s} is not a valid keyword")),
        }
    }
}

enum Token {
    Keyword(Keyword),
}

impl FromStr for Token {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Ok(k) = s.parse() {
            return Ok(Token::Keyword(k))
        }
        Err(format!("{s} cannot be parsed as a token"))
    }
}

fn tokenize(s: &str) -> Result<Vec<Token>, String> {
    s.split_whitespace()
        .map(|s| s.parse())
        .collect()
}

Là évidemment j’ai implémenté tokenize de façon ultra-naïve, tu vas sûrement vouloir avoir un splitting plus subtil mais c’est pour montrer l’idée. (Et on peut remarquer que la signature de tokenize donne envie de wrapper Vec<Token> dans un newtype qui implémenterait FromStr…) Ce modèle avec seulement des états valides via le système de types te permet d’éviter complètement les branches _ => Err() dans parser::parse_expr et exploiter le pattern matching exhaustif de Rust.


Par ailleurs, concernant ton problème pour implémenter l’exécution de ton arbre d'Expr, est-ce que mon précédent message est compréhensible ? J’ai l’impression qu’il répond à ton problème.

+2 -0

merci, j’ai commence à créer un évaluateur avant d’avoir lu ton message, merci pour les conseils, je vais les suivre des que j’ai finis mon message merci beaucoup , apres avoir suivi tes conseil je posterai mon nouveau code , pour recevoir des conseils, j’espère et merci pour ton temps

+0 -0

mais c’est applicable sur les autres variantes

comment on fait pour le token number ? :euh:

créer t’on un enum de nombre(ça me semble une très mauvaise idée) ou une struct tuple ou struct vide struct Number; puis l’implémente fromstr ? merci pour ton temps

comment on fait pour le token number ? 

Tu touches du doigt le problème assez profond de la définition du système de type pour ton langage, qui va aussi affecter la façon dont tu peux implémenter la VM. Tu peux garder les choses simples dans un premier temps avec un seul type de nombre (c’est ce que faisait Lua pendant longtemps avant d’avoir un type entier séparé). Voici comment je vois avec les choses, avec quelque détails pour voir comment l’ensemble s’articule :

/// Représente l'ensemble des types que ton langage est capable de manipuler
enum Value {
    Bool(bool),
    Number(f64),
    String(String),
}

/// Représente l'ensemble des mots-clés du langage
enum Keyword {
    If,
    Then,
    Else,
    End,
    While,
    // ...
}

/// Représente un identificateur
#[derive(Hash, Eq, PartialEq)]
struct Ident(String);


enum Token {
    /// Un mot clé
    Keyword(Keyword),
    /// Un identificateur
    Ident(Ident),
    /// Une valeur littérale qu'on peut wrapper dans une variante de Value
    Literal(Value),
}

/// Arbre d'expression
enum Expr {
    /// On a besoin de récupérer une valeur brute
    Literal(Value),
    // ...
}

fn lexer(prgm: String) -> Vec<Token> {
    todo!()
}

fn parser(tokens: &[Token]) -> Expr {
    todo!()
}

struct Vm(std::collections::HashMap<Ident, Value>);

impl Vm {
    fn eval_expr(&mut self, expr: Expr) -> Value {
        match expr {
            Expr::Literal(v) => v,
            // ...
        }
    }
}

Ça a le mérite d’être relativement simple, mais c’est pas très flexible et ça ferme la porte à la définition de types par l’utilisateur puisque la liste de types possibles est statique. Les implémentations des opérations sont aussi un peu pénibles à écrire parce qu’il faut vérifier explicitement que le lhs et le rhs ont des types compatibles (et sortir une erreur au runtime en cas de problème).


Une solution beaucoup plus flexible (mais qui demande un gros effort d’implémentation) est de faire quelque chose à la Python où il y a en fait un seul type de données (PyObject en l’occurrence) avec des pseudo-types (seulement des labels au runtime) qui ne font que remplir l’interface disponible au runtime. Une version primitive de ça serait faisable j’imagine. Mais pour être efficace, ça demande une couche supplémentaire entre l’arbre Expr et la VM où tu as typiquement une compilation en bytecode (consommé par la suite par la Vm). Tu peux aussi imaginer implémenter un système de type statique compris par le compilateur vers le bytecode, mais c’est encore un niveau au-dessus en terme de complexité. Ça dépend un peu jusqu’où tu veux aller. Définir un système de types est pas un truc qui s’improvise facilement cela dit.

+0 -0

oui, j’avais commencé à fait quelque chose en python (que j’avais abandonne je ne sais plus pourquoi :P ) du même genre mais pas de la même façon : firelang , je pense que je vais me tourner dans cette version python :( en revoir rust :'( , pouvez vous me donner des conseils sur ce code python (il y aura plus de monde vue que il y a une plus grosse communauté python que rust (que je trouve dommage vue que rust est un langage super :soleil: ) bon merci pour les conseil et j’espère avoir d’autre conseil sur mon code python !

EDIT: en faite je vais d’abord faire en rust malgre les problemes de type

+0 -0

Pour le fun j’ai implémenté ça en OCaml:

type expr =
| If of { cond: expr; then_: expr; else_: expr option }
| While of { cond: expr; body: expr }
| Let of { var: var; def: expr }
| Var of var
| Block of expr list
| Bool of bool
| Int of int
| Binop of { op: binop; left: expr; right: expr }

and binop = Plus | Equal

and var = string

module Env = Map.Make(String)

type value =
| Bool of bool
| Int of int
| String of string
| Unit

let get_bool : value -> bool = function
  | Bool b -> b
  | _ -> failwith "Expected a boolean"

let get_int : value -> int = function
  | Int n -> n
  | _ -> failwith "Expected an integer"

type env = value Env.t

let rec eval (env : env) : expr -> value * env = function
| If { cond; then_; else_ } ->
  let b = eval env cond |> fst |> get_bool in
  let else_  = Option.value ~default:(Block []) else_ in
  eval env (if b then then_ else else_)
| (While {cond; body}) as loop ->
  let continue = eval env cond |> fst |> get_bool in
  if not continue then Unit, env
  else (ignore (eval env body); eval env loop)
| Let { var; def } ->
  let v, _env' = eval env def in
  Unit, (Env.add var v env)
| Var var ->
  begin match Env.find var env with
    | exception Not_found -> failwith ("Unbound variable " ^ var)
    | v -> v, env
  end
| Block [] ->
  Unit, env
| Block [e] -> eval env e
| Block (e :: es) ->
  let (_v, env') = eval env e in
  eval env' (Block es)
| Bool b ->
  Bool b, env
| Int n ->
  Int n, env
| Binop { op; left; right } ->
  begin match op with
    | Plus ->
      let n1 = eval env left |> fst |> get_int in
      let n2 = eval env right |> fst |> get_int in
      Int (n1 + n2), env
    | Equal ->
      let n1 = eval env left |> fst |> get_int in
      let n2 = eval env right |> fst |> get_int in
      Bool (n1 = n2), env
  end

let test =
  let binop op left right = Binop { op; left; right } in
  If { cond = binop Equal (Int 0) (Int 1);
       then_ = Int 21;
       else_ =
         Some (Block [
           Let {var = "n"; def = Int 21};
           binop Plus (Var "n") (Var "n");
         ]);
     }

let () =
  assert (eval Env.empty test |> fst |> get_int
          =  42)

excellent , j’étais entrain d’apprendre ocaml , coïncidence ? non google nous espione ;) bon sympa ton code , je suis vraiment débutant en ocaml, alors la bravo je regarde ton code de même façon que les moldus regarde mon code :D . je suis entrain d’apprendre les fonction let et je suis au patterne matching :euh: . pfff j’ai créer ma première et propre fonction factoriel en ocaml :soleil: (récursive bien sur). il fallait que j’apprenne un autre langage fonctionnelle et puis c’etais aussi pour le fun d’apprendre ocaml

Heu, je pense que tu as mal compris mon message. Je ne dis pas du tout d’implémenter ça en Python, je dis qu’une piste possible est d’implémenter (en Rust ou ce que tu veux) un système de type similaire à celui de Python (en beaucoup plus simple évidemment) où les types exposent une interface (lire trait) simple et toutes les opérations sont définies à partir de ça. Quelque chose dans ce goût là, en gros :

use std::{collections::HashMap, rc::Rc, cell::RefCell};

enum BuiltinType {
    Bool,
    Number,
    String,
}

enum TypeLabel {
    Builtin(BuiltinType),
    UserDefined(String),
}

enum RuntimeError {
    UndefinedSymbol,
}

/// Représente un identificateur
#[derive(Hash, Eq, PartialEq)]
struct Ident(String);

type FaillibleExpr = Result<Value, RuntimeError>;

trait ObjectInterface {
    fn type_label(&self) -> TypeLabel;

    fn symbol_table(&mut self) -> &mut HashMap<Ident, Value>;

    fn get_symbol(&mut self, id: &Ident) -> FaillibleExpr {
        self.symbol_table()
            .get(id)
            .cloned()
            .ok_or(RuntimeError::UndefinedSymbol)
    }

    fn set_symbol(&mut self, id: Ident, val: Value) {
        self.symbol_table().insert(id, val);
    }
}

type Value = Rc<RefCell<dyn ObjectInterface>>;

C’est simple en apparence mais ça devient vite compliqué cela dit. À ta place, je resterai sur quelques built-ins simples dans une enum dans un premier temps, histoire que tu puisses te rendre compte des problèmes qui viennent avec la conception et l’implémentation d’un langage. Ce sera déjà pas mal si tu arrives à une première implémentation qui fonctionne. Tu as tout le temps de faire mûrir ton projet par la suite dans les directions qui t’intéressent.

+0 -0

je ne sais comment utiliser ton code(je ne veux pas seulement copier/coler mais le comprendre et savoir comment l’utiliser) , que doit on mettre dans UserDefined ? (c’est une String alors quoi mettre si l’utilisateur le définie )

C’est juste un placeholder que j’ai écrit rapidement pour montrer que ce système autorise facilement les types définis par l’utilisateur final du langage. La chaîne interne (peut être que Vec<Ident> serait un meilleur choix d’ailleurs) représente simplement le nom du type de façon unique (on peut imaginer plein d’autres choix pour identifier les types, j’ai juste pris un choix un peu naïf mais pas déconnant). L’idée derrière le TypeLabel est simplement de pouvoir vérifier au runtime les types des objets manipulés. Par exemple si tu essayes d’ajouter un BuiltinType::Number à un BuiltinType::String, tu peux sortir une RuntimeError::TypeError décrivant le problème.

Encore une fois cela dit, il me semblerait plus sage de se restreindre pour Value à une enum qui wrappe quelque types simples dans un premier temps.

+0 -0

bonjour, voilà enfin mon nouveau code qui marche (je suis trop content :P ) , j’espere avoir de nouveau conseil , j’ai essayé de supprimer le plus de string possible :D . lexer.rs

use std::str::FromStr;

#[derive(Debug, Clone, PartialEq)]
pub struct Identifier(pub String);

#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
    Number(f64),
    String(String),
    Bool(bool),
}



#[derive(Debug, Clone, PartialEq)]
pub enum Keyword {
	If,
	While,
	Let,
	For,
	Else,
    In
}

#[derive(Debug, Clone, PartialEq)]
pub enum Operator {
    Add,
    Sub,
    Mul,
    Div,
    Mod,
    Assign,
    Eq,
    Neq,
    Lt,
    Gt,
    Le,
    Ge,
    And,
    Or
}



#[derive(Debug, Clone, PartialEq)]
pub enum Seperator {
    SemiColon,
    Comma,
    Dot,
    Colon,
    LeftParen,
    RightParen,
    LeftBrace,
    RightBrace,
    LeftBracket,
    RightBracket
}

impl FromStr for Keyword {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "if" => Ok(Keyword::If),
            "while" => Ok(Keyword::While),
            "let" => Ok(Keyword::Let),
            "for" => Ok(Keyword::For),
            "else" => Ok(Keyword::Else),
            "in" => Ok(Keyword::In),
            _ => Err(format!("{} is not a keyword", s))
        }
    }
}

impl FromStr for Operator {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "+" => Ok(Operator::Add),
            "-" => Ok(Operator::Sub),
            "*" => Ok(Operator::Mul),
            "/" => Ok(Operator::Div),
            "%" => Ok(Operator::Mod),
            "=" => Ok(Operator::Assign),
            "==" => Ok(Operator::Eq),
            "!=" => Ok(Operator::Neq),
            "<" => Ok(Operator::Lt),
            ">" => Ok(Operator::Gt),
            "<=" => Ok(Operator::Le),
            ">=" => Ok(Operator::Ge),
            "&&" => Ok(Operator::And),
            "||" => Ok(Operator::Or),
            _ => Err(format!("{} is not an operator", s))
        }
    }
}



impl FromStr for Seperator {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            ";" => Ok(Seperator::SemiColon),
            "," => Ok(Seperator::Comma),
            "." => Ok(Seperator::Dot),
            ":" => Ok(Seperator::Colon),
            "(" => Ok(Seperator::LeftParen),
            ")" => Ok(Seperator::RightParen),
            "{" => Ok(Seperator::LeftBrace),
            "}" => Ok(Seperator::RightBrace),
            "[" => Ok(Seperator::LeftBracket),
            "]" => Ok(Seperator::RightBracket),
            _ => Err(format!("{} is not a seperator", s))
        }
    }
}

impl FromStr for Literal {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "true" => Ok(Literal::Bool(true)),
            "false" => Ok(Literal::Bool(false)),
            _ => {
                if let Ok(num) = s.parse::<f64>() {
                    Ok(Literal::Number(num))
                } else {
                    Ok(Literal::String(s.to_string()))
                }
            }
        }
    }
}

impl FromStr for Identifier {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.len() == 0 {
            Err("Identifier cannot be empty".to_string())
        } else if ! s.chars().next().unwrap().is_alphabetic() {
            Err(format!("{} is not a valid identifier", s))
        } else if s.chars().any(|c| ! c.is_alphanumeric() && c != '_') {
            Err(format!("{} is not a valid identifier", s))
        } else {
            Ok(Identifier(s.to_string()))
        }
    }
}
#[derive(Debug,  Clone, PartialEq)]
pub enum Token {
    // The token enum is a simple state machine with the following states.
    //
    // * `Seperator`: The lexer expects to find  or one
    //   of the symbols `(`, `)`, `{`, `}`, `[`, `]`, `;`, `,`
    //
    // * `Operator`: The lexer expects to find one of the symbols `+`, `-`, `*`, `/`, `%`, `=`, `!`, `<`, `>`, `&`, `|`, `^`, `~`, `?`
    // * `Comment`: The lexer expects to find a `*/` to end the comment.
    //
    // * `String`: The lexer expects to find a `"` to end the string.
    //
    // * `Number`: The lexer expects to find a number.
    //
    // * `Identifier`: The lexer expects to find an identifier.
    //
    // * `Operator`: The lexer expects to find an operator.
    //
    // * `Keyword`: The lexer expects to find a keyword.
    //
    // * `Error`: The lexer expects to find an error.
    //
    // * `Eof`: The lexer expects to find the end of the file.
    Comment(String),
    Literal(Literal),
    Identifier(Identifier),
    Operator(Operator),
    Keyword(Keyword),
    Seperator(Seperator),
    Eof
    }


impl Token {
	pub fn from_str(string: &str) -> Result<Vec<Token>, String> {
        let mut tokens = Vec::new();
        let mut is_char = false;
        let mut chars = "".to_string();
        let mut is_num = false;
        let mut num = "".to_string();
        let mut s;
        for (_, c) in string.chars().enumerate() {
            s = c.to_string();
            if s == " " || s == "\n" || s == "\r" || s == "\t" {
                if is_char {
                    if is_char {
                        if let Ok(k) = Keyword::from_str(&chars) {
                            tokens.push(Token::Keyword(k));
                        } else if let Ok(i) = Identifier::from_str(&chars) {
                            tokens.push(Token::Identifier(i));
                        } else if let Ok(Literal::Bool(b)) = Literal::from_str(&chars) {
                            tokens.push(Token::Literal(Literal::Bool(b)));
                        } 
                        chars = "".to_string();
                        is_char = false;
                    }
                    chars = "".to_string();
                    is_char = false;
                }
                if is_num {
                    
                    if let Ok(n) = Literal::from_str(&num) {
                        tokens.push(Token::Literal(n));
                        num = "".to_string();
                        is_num = false;
                    } else {
                        return Err(format!("{} is not a valid number", num));
                    }
                }
            }
            if let Ok(sep) = Seperator::from_str(&s) {
                if is_char {
                    if is_char {
                        if let Ok(k) = Keyword::from_str(&chars) {
                            tokens.push(Token::Keyword(k));
                        } else if let Ok(i) = Identifier::from_str(&chars) {
                            tokens.push(Token::Identifier(i));
                        } else if let Ok(Literal::Bool(b)) = Literal::from_str(&chars) {
                            tokens.push(Token::Literal(Literal::Bool(b)));
                        } 
                        chars = "".to_string();
                        is_char = false;
                    }
                    chars = "".to_string();
                    is_char = false;
                }
                if is_num {
                    if let Ok(n) = Literal::from_str(&num) {
                        tokens.push(Token::Literal(n));
                        num = "".to_string();
                        is_num = false;
                    } else {
                        return Err(format!("{} is not a valid number", num));
                    }
                }
                tokens.push(Token::Seperator(sep));
            }
            if let Ok(op) = Operator::from_str(&s) {
                if is_char {
                    if let Ok(k) = Keyword::from_str(&chars) {
                        tokens.push(Token::Keyword(k));
                    } else if let Ok(i) = Identifier::from_str(&chars) {
                        tokens.push(Token::Identifier(i));
                    } else if let Ok(Literal::Bool(b)) = Literal::from_str(&chars) {
                        tokens.push(Token::Literal(Literal::Bool(b)));
                    } 
                    chars = "".to_string();
                    is_char = false;
                }
                if is_num {
                    if let Ok(n) = Literal::from_str(&num) {
                        tokens.push(Token::Literal(n));
                        num = "".to_string();
                        is_num = false;
                    } else {
                        return Err(format!("{} is not a valid number", num));
                    }
                }
                tokens.push(Token::Operator(op));
            }
    
            if let Ok(_) = Identifier::from_str(&s) {
                if is_num {
                    return Err("Identifier cannot be a number".to_string());
                    
                } else {
                    chars += &s;
                    is_char = true;
                }
                
            }
            if let Ok(Literal::Number(_)) = Literal::from_str(&s) {
                if is_char {
                    chars += &s;
                } else {
                    num += &s;
                    is_num = true;
                }
            }
        }
        if is_char {
            if let Ok(k) = Keyword::from_str(&chars) {
                tokens.push(Token::Keyword(k));
            } else if let Ok(i) = Identifier::from_str(&chars) {
                tokens.push(Token::Identifier(i));
            } else if let Ok(Literal::Bool(b)) = Literal::from_str(&chars) {
                tokens.push(Token::Literal(Literal::Bool(b)));
            } 
            chars = "".to_string();
            is_char = false;
        }
        if is_num {
            if let Ok(n) = Literal::from_str(&num) {
                tokens.push(Token::Literal(n));
                num = "".to_string();
                is_num = false;
            } else {
                return Err(format!("{} is not a valid number", num));
            }
        }
        return Ok(tokens);
    
    
	}
}

parser.rs

use crate::lexer::Token;
use crate::lexer::Keyword;
use crate::lexer::Seperator;
use crate::lexer::Operator;
use crate::lexer::Identifier;
use crate::tree::Expr;
use crate::tree::Op;

pub fn parse_token(tokens: &mut Vec<Token>) -> Result<Expr, String> {
    if tokens.len() == 0 {
        return Err("Unexpected end of file".to_string());
    }
    let token = tokens[0].clone();
    match token {
        Token::Literal(n) => {
            tokens.remove(0);
            return Ok(Expr::Literal { value: n });
        },
        Token::Identifier(i) => {
            tokens.remove(0);
            Ok(Expr::Identifier { name: i.0.clone() })
        },
        Token::Keyword(k) => {
            match k {
                Keyword::If => {
                    let mut is_else = false;
                    tokens.remove(0); // remove "if " of if block
                    let cond = parse_expr(tokens)?;
                    
                    if tokens[0] != Token::Seperator(Seperator::LeftBrace) {
                        return Err("Expected token '{'".to_string());
                    }
                    if tokens.last().unwrap() != &Token::Seperator(Seperator::RightBrace) {
                        return Err("Expected token '}'".to_string());
                    }
                    tokens.remove(0); // remove "{" of if block 
                    if tokens.iter().filter(|x| x == &&Token::Keyword(Keyword::Else)).count() >= 1 {
                        let index = tokens.iter().position(|x| x == &Token::Keyword(Keyword::Else)).unwrap();
                        tokens.remove(index-1); // remove "}" of if block
                        is_else = true
                    } else {
                        tokens.pop(); // remove "}" of if block
                    }
                    let then = parse_expr(tokens)?;
                    
                    if tokens.len() == 0 {
                        Ok(Expr::IfThen {
                            cond: Box::new(cond),
                            then: Box::new(then)
                        })
                    } else if is_else {
                        tokens.remove(0);
                        if tokens[0] != Token::Seperator(Seperator::LeftBrace) {
                            return Err("Expected token '{'".to_string());
                        }
                        if tokens.last() != Some(&Token::Seperator(Seperator::RightBrace)) {
                            return Err("Expected token '}'".to_string());
                        }
                        tokens.remove(0);
                        tokens.pop();
                        let else_ = parse_expr(tokens)?;
                        Ok(Expr::IfThenElse {
                            cond: Box::new(cond),
                            then: Box::new(then),
                            else_: Box::new(else_)
                        })
                        
                    } else {
                        Err("error syntatic".to_string()) // doesn't match any case         
                    
                    }
                    
                },
                Keyword::While => {
                    tokens.remove(0); // remove "while" of while block
                    let cond = parse_expr(tokens)?;
                    if tokens[0] != Token::Seperator(Seperator::LeftBrace) {
                        return Err("Expected token '{'".to_string());
                    }
                    if tokens[tokens.len()-1] != Token::Seperator(Seperator::RightBrace) {
                        return Err("Expected token '}'".to_string());
                    }
                    tokens.remove(0); // remove "{" of while block
                    tokens.pop(); // remove "}" of while block
                    let body = parse_expr(tokens)?;
                    Ok(Expr::While {
                        cond: Box::new(cond),
                        body: Box::new(body)
                    })
                },
                Keyword::Let => {
                    tokens.remove(0);
                    if let Token::Identifier(Identifier(name)) = tokens[0].clone() {
                        
                        tokens.remove(0);
                        if tokens[0] != Token::Operator(Operator::Assign) {
                            return Err("Expected operator '='".to_string());
                        } else {
                            tokens.remove(0);
                        }
                        let value = parse_expr(tokens)?;
                        Ok(Expr::Assign {
                            name: name.clone(),
                            value: Box::new(value)
                        })
                    } else {
                        Err("Expected identifier".to_string())
                    }
                },
                Keyword::For => {
                    tokens.remove(0);
                    if tokens.len() < 4 {
                        return Err("Expected identifier: invalid syntash".to_string());
                    }
                    if let Token::Identifier(Identifier(name)) = tokens[0].clone()  {
                        tokens.remove(0);
                        if tokens[0] != Token::Keyword(Keyword::In) {
                            return Err("Expected keyword 'in'".to_string());
                        }
                        tokens.remove(0);
                        let iter = parse_expr(tokens)?;
                        if tokens[0] != Token::Seperator(Seperator::LeftBrace) {
                            return Err("Expected keyword '{'".to_string());
                        }
                        if tokens[tokens.len()-1] != Token::Seperator(Seperator::RightBrace) {
                            return Err("Expected keyword '}'".to_string());
                        }
                        tokens.remove(0);
                        tokens.pop();
                        let body = parse_expr(tokens)?;
                        Ok(Expr::For {
                            name: name.clone(),
                            iter: Box::new(iter),
                            body: Box::new(body)
                        })
                    } else {
                        Err("Expected identifier: no var".to_string())
                    }
                },
                
                _ => Err("Unexpected keyword: ".to_string())
            }
        },
        Token::Operator(op) => {
            tokens.remove(0); // remove operator of binary expression
            let left = parse_expr(tokens)?;
            let right = parse_expr(tokens)?;
            let op_enum = match op {
                Operator::Add => Op::Add,
                Operator::Sub => Op::Sub,
                Operator::Mul => Op::Mul,
                Operator::Div => Op::Div,
                Operator::Mod => Op::Mod,
                Operator::Eq => Op::Eq,
                Operator::Lt => Op::Lt,
                Operator::Gt => Op::Gt,
                Operator::Le => Op::Le,
                Operator::Ge => Op::Ge,
                Operator::Neq => Op::Neq,
                Operator::And => Op::And,
                Operator::Or => Op::Or,
                Operator::Assign => Op::Assign,

                
            };
            Ok(Expr::BinOp {
                op: op_enum.clone(),
                left: Box::new(left),
                right: Box::new(right)
            })
        }
        _ => Err("Unexpected token".to_string())
    }
}

tree.rs

use crate::lexer::Literal;
#[derive(Debug, PartialEq, Clone)]
pub enum Expr {
    IfThen {
        cond: Box<Expr>,
        then: Box<Expr>    
    },
    IfThenElse {
        cond: Box<Expr>,
        then: Box<Expr>,
        else_: Box<Expr>,
    },
    While {
        cond: Box<Expr>,
        body: Box<Expr>
    },
    Assign {
        name: String,
        value: Box<Expr>
    },
    Literal {
        value: Literal
    },
    BinOp {
        op: Op,
        left: Box<Expr>,
        right: Box<Expr>
    },
    For {
        name: String,
        iter: Box<Expr>,
        body: Box<Expr>
    },
    Identifier {
        name: String
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum Op {
    Add,
    Sub,
    Mul,
    Div,
    Mod,
    Eq,
    Neq,
    Lt,
    Gt,
    Le,
    Ge,
    And,
    Or,
    Invalid,
    Assign
}

et executer.rs

use crate::tree::Expr; 
use crate::tree::Op;
use crate::lexer::Literal;
use crate::lexer;
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
pub struct Ident(pub String);

#[derive(Debug, PartialEq, Clone)]
pub enum Value {
    Number(f64),
    String(String),
    Bool(bool),
    None
}

impl Value {
    pub fn add(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
            _ => Err("invalid operation".to_string())
        }
        

    }
    pub fn sub(&self, other: &Value) -> Result<Value, String> {
 
        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a - b)),
            _ => Err("invalid operation".to_string())
        }

    }

    pub fn mul(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a * b)),
            _ => Err("invalid operation".to_string())
        }
        

    }

    pub fn div(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a / b)),
            _ => Err("invalid operation".to_string())
        }
        

    }

    pub fn modulo(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a % b)),
            _ => Err("invalid operation".to_string())
        }
        

    }

    pub fn eq(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a == b)),
            (Value::String(a), Value::String(b)) => Ok(Value::Bool(a == b)),
            (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a == b)),
               _ => Err("invalid operation".to_string())
        }
        

    }

    pub fn neq(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a != b)),
            (Value::String(a), Value::String(b)) => Ok(Value::Bool(a != b)),
            (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a != b)),
             _ => Err("invalid operation".to_string())
            
        }

    }

    pub fn gt(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(*a > *b)),
            _ => Err("invalid operation".to_string())
        }
        

    }

    pub fn lt(&self, other: &Value) -> Result<Value, String> {
        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(*a < *b)),
            _ => Err("invalid operation".to_string())
        }

    }

    pub fn ge(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(*a >= *b)),
            _ => Err("invalid operation".to_string())
        }
        

    }

    pub fn le(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(*a <= *b)),
            _ => Err("invalid operation".to_string())
        }
        

    }

    pub fn and(&self, other: &Value) -> Result<Value, String> {

        match (self, other) {
            (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(*a && *b)),
            _ => Err("invalid operation".to_string())
        }
        

    }

    pub fn or(&self, other: &Value) -> Result<Value, String> {
        match (self, other) {
            (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(*a || *b)),
            _ => Err("invalid operation".to_string())
        }
    }   
}

pub struct Vm(std::collections::HashMap<Ident, Value>);

impl Vm {
    pub fn new() -> Self {
        Vm(std::collections::HashMap::new())
    }
    pub fn eval_expr(&mut self, expr: Expr) -> Result<Value, String> {
        match expr {
            Expr::Literal { value } => {
                Ok(match value {
                    Literal::Number(n) => Value::Number(n),
                    Literal::String(s) => Value::String(s),
                    Literal::Bool(b) => Value::Bool(b)
                })
            },
            Expr::Identifier{ name } => Ok(self.get_ident(Ident(name))),
            Expr::BinOp { op, left, right } => {                
                let left = self.eval_expr(*left)?;
                let right = self.eval_expr(*right)?;
                return Ok(match op {
                    Op::Add => left.add(&right)?,
                    Op::Sub => left.sub(&right)?,
                    Op::Mul => left.mul(&right)?,
                    Op::Div => left.div(&right)?,
                    Op::Mod => left.modulo(&right)?,
                    Op::Eq => left.eq(&right)?,
                    Op::Neq => left.neq(&right)?,
                    Op::Gt => left.gt(&right)?,
                    Op::Lt => left.lt(&right)?,
                    Op::Ge => left.ge(&right)?,
                    Op::Le => left.le(&right)?,
                    Op::And => left.and(&right)?,
                    Op::Or => left.or(&right)?,
                    _ => return Err(String::from("Unknown operator"))

                });
            },
            Expr::IfThen { cond, then } => {
                if let Value::Bool(c) = self.eval_expr(*cond)?{
                    if c {
                        self.eval_expr(*then)
                    } else {
                        Ok(Value::None)
                    }
                }
                else {
                    return Err("condition is not bool".to_string());
                }
            },
            Expr::IfThenElse { cond, then, else_ } => {
                if let Value::Bool(n) = self.eval_expr(*cond)? {
                    if n {
                        return self.eval_expr(*then);
                    }
                    else {
                        return self.eval_expr(*else_);
                    }
                    return self.eval_expr(*then);
                }
                else {
                    return Err("condition is not bool".to_string());
                }
            },
            Expr::Assign { name, value } => {
                let value_evaluate = self.eval_expr(*value)?;
                self.set_ident(Ident(name), value_evaluate.clone());
                return Ok(value_evaluate);
            },
            Expr::While { ref cond, ref body } => {
                while self.eval_expr(*cond.clone())? == Value::Bool(true) {
                    self.eval_expr(*body.clone())?;
                }
                return Ok(Value::None);
            },
            Expr::For {ref name, ref iter, ref body} => {
                let iter = self.eval_expr(*iter.clone())?;
                let n = match iter {
                    Value::Number(n) => n,
                    _ => return Err("iter is not number".to_string())
                };
                
                for i in 0..n as i32 {
                    self.set_ident(Ident(name.clone().to_string()), Value::Number(i as f64));
                }
                return Ok(Value::None);
            },
        }
    }

    pub fn set_ident(&mut self, ident: Ident, value: Value) {
        self.0.insert(ident, value);
    }

    pub fn get_ident(&mut self, ident: Ident) -> Value {
        match self.0.get(&ident) {
            Some(v) => v.clone(),
            None => Value::None
        }
    }
}

encore merci pour les conseils , je suis trop fière de ce que j’ai fait :soleil: même si c’est loin d’être le top

EDIT: dis le moi si tu vois un Value::Error quelque part pour que je l’enlève

EDIT: je sais pas comment implementer une fonction dans mon langage

+0 -0

Deux remarques:

  • Il n’y a pas de façon de séquencer plusieurs expressions ensemble dans ton langage, c’est un problème non ? Par exemple, comment représenter une branche "then" d’une conditionnelle qui d’abord définit une variable (Assign) et ensuite l’utilise pour calculer un résultat, je fais comment ? (En C par exemple: if (foo) { int x = 21; return (x + x); }). Dans ma version OCaml j’avais rajouté Block pour gérer ça.

  • Utiliser un HashMap pour représenter l’environnement en utilisant directement le nom des variables n’est pas forcément une bonne idée en présence de fonctions locales. Par exemple si tu imagines le programme suivant:

let x = 1
let f () = x
let x = 2
let () = assert (f () = 1)

Est-ce que ton langage se rend bien compte que f () renvoie 1, ou est-ce que la définition suivante let x = 2 change "la valeur de x" et donc f () se met à renvoyer 2, ce qui est incorrect ?

dans mon langage , la fonction(que je n’ai pas encore implémenté ) returnera toujours la meme valeur (c’est a dire dans ton example copiera la valeur de la variable x ) dans cette example de fonction dans mon langage :

let f (n) = n*2
let x = 9
f(x) // on copie la valeur de la variable x , puis remplace n par la valeur de x copié c'est à dire 9*2 

let d() = x // on copie la valeur de x  et donc x n'est pas une reference  
           // l'interpreteure va transformer ce programe en let d() = 9 
x = 7 // on modifie x 
d() // donne 9 (la valeur de x dans la ligne  5  est de 9 )   

pour la sequence , je pense (PENSE ! ) faire comme rust c’est a dire que tout est une expression (je ne sais si c’est une bonne idee ) EDIT: non en faite tu as raison je vais implementer les block

+0 -0

Utiliser un HashMap pour représenter l’environnement en utilisant directement le nom des variables n’est pas forcément une bonne idée en présence de fonctions locales.

Mmm je ne pense pas que ça soit un problème du tout, il suffit de créer une Vm (qui est en fait plutôt un Scope en l’état) pour le contexte de la fonction initialisée avec les variables du scope globale qu’elle capture. De cette façon, la capture se fait bien par valeur (plutôt que par pseudo-référence comme ce que fait Python… :-° ). Pour faire les choses proprement, il y a sûrement intérêt à séparer ce qui serait un Scope de la Vm mais c’est un refactoring assez trivial à ce stade.

pour la sequence , je pense (PENSE ! ) faire comme rust c’est a dire que tout est une expression (je ne sais si c’est une bonne idee ) EDIT: non en faite tu as raison je vais implementer les block

Ces deux choses sont orthogonales. Tu as besoin de la notion de bloc (peu importe sous quelle forme) pour grouper des expressions, mais tu peux parfaitement dire qu’évaluer n’importe quelle Expr renvoie une valeur (comme ce que fait Rust), et c’est d’ailleurs ce que tu as avec fn eval_expr(&mut self, expr: Expr) -> Result<Value, String> (et ton Value::None est l’équivalent de () en Rust).


Sinon t’aurais pas un Github ou autre avec le code ? Ce serait plus facile pour l’explorer localement.

+0 -0

oui :soleil: , le voila (peut-etre qui ne se ressemble pas quelque petite modif) : tlang(T-lang) , je suis entrain d’ajouter la notion de block dans mon code(je n’ai pas finis et donc je ne le push pas ) desole de ne pas avoir donne mon git plus tot j’ai deux branch main et V1 je croie que c’est identique

EDIT : en faite le nouveau code est sur la branch V1

EDIT : je vais faire les block comme en rust (let f = { ... } ) suis-je entrain de faire un interpréteur rust ? :lol:

hs: pourquoi plusieurs personne utilise ocaml pour créer un mini langage parce qu’a part d’être fonctionnelle(même si c’est déjà un grand avantage) il a pas de tres grand avantage (sans vouloir blesser les fan d’ocaml)

+0 -0

@adri1 en fait je me suis un peu mélangé les pinceaux entre ma question et mon exemple, il y a des cas où on voit le problème aussi sans avoir de fonctions locales, mais juste des blocs imbriqués.

let x = 1
{
  let x = 2
}
assert (x == 2)

Ici on a un problème si let x = 2 écrase le binding pour x dans l’environnment, au lieu de juste le "shadower".

@adri1 en fait je me suis un peu mélangé les pinceaux entre ma question et mon exemple, il y a des cas où on voit le problème aussi sans avoir de fonctions locales, mais juste des blocs imbriqués.

let x = 1
{
  let x = 2
}
assert (x == 2)

Ici on a un problème si let x = 2 écrase le binding pour x dans l’environnment, au lieu de juste le "shadower".

gasche

On est d’accord que le cas raisonnable est plutôt d’avoir assert x == 1 qui passe ? Autrement dit, x=2 shadow le x du scope parent dans le bloc, mais ne change pas ce que le scope global voit. J’ai l’impression que le cas du shadowing est couvert proprement en ne faisant jamais remonter le HashMap<Ident, Value> (et en faisant descendre les variables capturées). Autrement dit, le code suivant doit passer :

let x = 1
let y = 42
{
    let x = y + 1
    assert x == 43
}
assert x == 1

hs: pourquoi plusieurs personne utilise ocaml pour créer un mini langage parce qu’a part d’être fonctionnelle(même si c’est déjà un grand avantage) il a pas de tres grand avantage (sans vouloir blesser les fan d’OCaml)

C’est sûrement un mélange de plusieurs choses :

  • écrire un lexer, un parser, et un interpréteur marche plutôt bien en paradigme fonctionnel ;
  • il n’y a pas besoin d’un écosystème très riche pour écrire un compilateur ou un interpréteur, donc le fait que OCaml soit relativement "niche" n’est pas un problème ;
  • les gens qui écrivent des petits langages pour étudier des aspects précis du design d’un langage ont souvent une culture informatique relativement large, et tendent à graviter autour de langages qui sont eux-même plutôt bien conçus d’un point de vue académique (certains de ces projets qui démarrent comme une expérience donnent naissance à des langages industriels puisque les premiers compilateurs Rust étaient en OCaml, jusqu’à ce que Rust en tant que langage ait été suffisamment mûr pour écrire le compilo en Rust) ;
  • le phénomène s’auto-entretient probablement puisqu’il y a beaucoup de ressources sur le sujet qui utilisent des exemples en OCaml.
+0 -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