execution de mon petit langage

a marqué ce sujet comme résolu.

Personellement je suis pas vraiment excité par la proposition de PartialExpr d'@adri1, je comprends pas pourquoi ce serait nécessaire ici. Pour utiliser des gros mots, j’ai l’impression que cette approche revient à implémenter un parseur LR(1) à la main, en voyant chaque token comme une transition d’un état de parsing à un autre état de parsing. Le parser actuel est plutôt en style "recursive descent", et cette approche piétonne est connue pour marcher parfaitement bien pour parser des structures récursives comme des blocs imbriqués. Je pense qu’il devrait être possible de continuer sur cette piste sans utiliser de PartialExpr.

Mais dans le fond: pourquoi écrire un parseur à la main ? Pour apprendre ? (Pourquoi pas, c’est sans doute une bonne raison.) Il existe des générateurs de parseurs pour Rust, par exemple lalrpop, et aussi des bibliothèques de "parser combinators".

voila j’ai finnit la v1 de mon petit langage avec lalrpop comme parser: tlang , dans la v2 a venir je vais ajouter les fonctions et quelle que amelioration dans le code

apres la v2 je construirai la doc de tlang

le fichier test.txt est un example de code de tlang

merci pour vos commentaire !

+0 -0

Je viens de jeter un œil, et voici quelque remarques générales en vrac.

  • Pourquoi es tu sur l’édition 2018 ? À moins d’avoir une bonne raison, tu as autant de passer tout de suite sur 2021. Les éditions viennent principalement avec des changements de confort donc autant en profiter. Là en l’occurrence tu as notamment les captures partielles dans les closures, et l’élimination des bare trait objects qui sont pas mal.
  • Tu n’as pas besoin de extern crate depuis longtemps (l’édition 2018 justement), la doc de lalrpop n’est pas à jour (ça pourrait mériter une PR).
  • Tu devrais corriger les warnings de cargo check. Bon là t’en as deux qui viennent de lalrpop, ça pourrait aussi mériter un ticket pour qu’ils ajoutent les bons attributs si ces warnings sont pas corrigeables facilement.
  • Tu devrais jeter un coup d’œil à cargo clippy (malheureusement aussi pollué par lalrpop, ça mérite aussi un ticket pour qu’ils ajoutent les bons attributs, probablement un #![allow(clippy::all)]). Il signale plusieurs maladresses dans ton code comme des return, des ? ou des clone inutiles. De manière générale, exploite le fait que tout est une expression en Rust.
  • Tu devrais utiliser cargo fmt.
  • T’en qu’à faire, tu devrais utiliser un outil genre rust analyzer qui pointe du doigt les problèmes au fur-et-à-mesure que tu codes et offre des corrections rapides, c’est pratique. À minima, cargo fix t’aiderait pas mal à améliorer la qualité du code sans faire d’énormes efforts.
  • Le chemin du fichier source est en dur dans ton main, c’est pas très pratique. Tu devrais accepter un argument en ligne de commande (pour info, cargo run -- args passe les args à ton programme) avec le chemin du fichier source à compiler.
  • Ton exemple de fichier source est pour le moins surprenant, il ne fait rien et a même une branche while avec autre chose qu’un booléen en argument (et qui du coup n’est pas exécutée alors qu’elle a l’air infinie). C’est pas terrible. Tu pourrais pas donner un exemple qui sort quelque chose de plus intéressant, même si c’est un bête compteur ?
  • Tu pourrais avoir intérêt à faire une lib utilisée par main pour que les appels dans main soit plus simple. Il est plus usuel d’avoir un main presque vide qui appelle une lib et une lib qui essentiellement se contente de donner la structure des mod et exposer l’interface publique. Ça rend l’écriture de tests plus facile (tu devrais commencer à en écrire d’ailleurs, histoire de pas risquer de tout casser sans t’en rendre compte).
  • Dans ton match expr dans Vm::eval_expr, tu as une branche _ pour capturer ce que tu n’as pas encore implémenté. C’est une mauvaise idée parce que ça empêche le compilo de te dire que tu as oublié une branche si tu ajoutes une Expr. Si tu as des Expr dont l’évaluation n’est pas implémentée (comme Function), fais une branche avec todo!(). C’est la façon standard de marquer un truc que tu vas implémenter, et tu auras une erreur au runtime si tu va dans cette branche. Un outil comme cargo fix ou rust analyzer fera ça pour toi. Comme son type est !, ça se coerce au type de ton match si c’est une expression. D’ailleurs, un truc qui aurait du te mettre la puce à l’oreille est que le message d’erreur est absurde : tu peux pas avoir une "unknown expression" alors que le système de type te garantie que expr est l’une des variantes que tu t’es donné.
  • La fonction Vm::eval_many_expr est surprenante : si ton programme était wrappé dans un Block tu pourrais le traiter comme n’importe quelle autre Expr.
  • executer n’est pas un mot anglais.
+0 -0

ok, donc j’ai mis edition 2021 et j’ai ceci comme erreur:

error[E0432]: unresolved import `self::__lalrpop_util::lexer`
  --> /Users/antoine/Documents/tlang/target/debug/build/tlang-50b05856cac9f9d6/out/tlang.rs:28:31
   |
28 |     use self::__lalrpop_util::lexer::Token;
   |                               ^^^^^ could not find `lexer` in `__lalrpop_util`

merci pour ton aide

+0 -0

mais c’est deja fait:

[package]
name = "tlang"
version = "0.1.0"
edition = "2021"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
lalrpop-util = "0.19.7"
regex = "1"

[build-dependencies]
lalrpop = { version = "^0.19", features = ["lexer"] }

pourquoi ?

+0 -0

hmmm resolver = "1" a marche

mais bon une autre erreur(qui est cette fois lie a mon code):

error[E0412]: cannot find type `Ident` in this scope
  --> /Users/antoine/Documents/tlang/target/debug/build/tlang-c9bfb8db51dc027e/out/tlang.rs:35:22
   |
35 |         Variant3(Vec<Ident>),
   |                      ^^^^^ not found in this scope
   |

mon lalrpop :

use std::str::FromStr;
use crate::tree::Expr;
use crate::tree::Literal;
use crate::tree::Op;

grammar;

pub Exprs = MultiLine<Expr>;
Ident : Expr = <i:r"[a-zA-Z_][a-zA-Z0-9_]*"> => Expr::Ident{name: i.to_string()};
Block: Expr = "{" <e:Exprs> "}" => Expr::Block {body: e};
Num : Expr = <n:r"[0-9]+"> => Expr::Literal{value: Literal::Number(n.parse::<f64>().unwrap())} ;
Str : Expr = <s:r#"'(\\.|[^"])*'"#> => Expr::Literal{value: Literal::String(s[1..s.len()-1].to_string())};

IfExpr : Expr = "if" <e:Expr> "{" <e1:Exprs>  "}" => Expr::IfThen{cond: Box::new(e), then: Box::new(Expr::Block {body: e1} )};

IfElseExpr : Expr = <e1:IfExpr> "else" <e2:Block>  => {
    match e1 {
        Expr::IfThen{cond, then} => Expr::IfThenElse{cond: cond, then: then, else_: Box::new(e2)},
        _ => panic!("Invalid if-else expression")
    }
};

ForExpr : Expr = "for" <e1:Expr> "in" <e2:Expr> "{" <e3:Exprs> "}" => Expr::For{
    name: Box::new(e1),
    iter: Box::new(e2),
    body: Box::new(Expr::Block {body: e3})
};

WhileExpr : Expr = "while" <e1:Expr> "{" <e2:Exprs> "}" => Expr::While{
    cond: Box::new(e1),
    body: Box::new(Expr::Block {body: e2})
};

LetExpr : Expr = "let" <e1:Ident> "=" <e2:Expr>  => Expr::Assign{name: match e1 {
    Expr::Ident{name} => name,
    _ => panic!("Invalid let expression")
}, value: Box::new(e2)};


Op: Expr = {
    <e1:Term> "+" <e2:Fact> => Expr::BinOp{left: Box::new(e1), op: Op::Add, right: Box::new(e2)},
    <e1:Term> "-" <e2:Fact> => Expr::BinOp{left: Box::new(e1), op: Op::Sub, right: Box::new(e2)},
    Fact
};


Fact: Expr = {
    <e1:Fact> "*" <e2:Term> => Expr::BinOp{left: Box::new(e1), op: Op::Mul, right: Box::new(e2)},
    <e1:Fact> "/" <e2:Term> => Expr::BinOp{left: Box::new(e1), op: Op::Div, right: Box::new(e2)},
    Term
};

CmpOp: Expr = {
    <e1:Term> "==" <e2:Term> => Expr::BinOp{left: Box::new(e1), op: Op::Eq, right: Box::new(e2)},
    <e1:Term> "!=" <e2:Term> => Expr::BinOp{left: Box::new(e1), op: Op::Neq, right: Box::new(e2)},
    <e1:Term> "<" <e2:Term> => Expr::BinOp{left: Box::new(e1), op: Op::Lt, right: Box::new(e2)},
    <e1:Term> "<=" <e2:Term> => Expr::BinOp{left: Box::new(e1), op: Op::Le, right: Box::new(e2)},
    <e1:Term> ">" <e2:Term> => Expr::BinOp{left: Box::new(e1), op: Op::Gt, right: Box::new(e2)},
    <e1:Term> ">=" <e2:Term> => Expr::BinOp{left: Box::new(e1), op: Op::Ge, right: Box::new(e2)}
};

List: Vec<Expr> = "[" Comma<Expr> "]";
Args: Vec<Ident> = "(" Comma<Ident> ")";

FunDef : Expr = "def" <e1:Ident> "(" <e2:Args> ")" "{" <e3:Exprs> "}" => {
    match e1 {
        Expr::Ident{name} => Expr::FunDef{name: name, args: e2, body: Box::new(Expr::Block {body: e3}) },
        _ => panic!("Invalid function definition")
    }
};

True : Expr = "true" => Expr::Literal{value: Literal::Bool(true)};
False : Expr = "false" => Expr::Literal{value: Literal::Bool(false)};

Value = { Num, Str, Ident , True, False};
Term = {
    Value,
    "(" <e:Expr> ")" => e,

}

Expr = {
    Block,
    IfExpr,
    IfElseExpr,
    ForExpr,
    WhileExpr,
    LetExpr,
    Op,
    CmpOp
}

MultiLine<T> : Vec<T> = {
    <mut v:(<T> "\n") *> <e:T*>  => {
        v.into_iter().chain(e).collect()
    }
}

Comma<P> : Vec<P> = {
    <mut v:(<P> ",") *> <e:P*>  => {
        v.into_iter().chain(e).collect()
    }
}
+0 -0

hmmm resolver = "1" a marche

NightProg

Ça sert à rien (et c’est même une mauvaise idée…), relis bien mon message encore une fois. lalrpop et lalrpop-util sont deux crates différentes.

+0 -0

bon, voila le bilan : J’AI IMPLEMENTE LES FONCTIONS(la recursion n’est pas encore disponible):

def pow(n) {
  n*n
}

@pow(4)

et oui pour appeler une fonction il faut commencer par @

Pourquoi es tu sur l’édition 2018 ? À moins d’avoir une bonne raison, tu as autant de passer tout de suite sur 2021. Les éditions viennent principalement avec des changements de confort donc autant en profiter. Là en l’occurrence tu as notamment les captures partielles dans les closures, et l’élimination des bare trait objects qui sont pas mal.

fix :)

Tu n’as pas besoin de extern crate depuis longtemps (l’édition 2018 justement), la doc de lalrpop n’est pas à jour (ça pourrait mériter une PR).

si, j’en ai besoin parce que sans il y a cette erreur: error: cannot find macro 'lalrpop_mod' in this scope

Tu devrais corriger les warnings de cargo check. Bon là t’en as deux qui viennent de lalrpop, ça pourrait aussi mériter un ticket pour qu’ils ajoutent les bons attributs si ces warnings sont pas corrigeables facilement.

presque tout fix(a part deux ou trois que je verrai plus tard)

Tu devrais jeter un coup d’œil à cargo clippy (malheureusement aussi pollué par lalrpop, ça mérite aussi un ticket pour qu’ils ajoutent les bons attributs, probablement un #![allow(clippy::all)]). Il signale plusieurs maladresses dans ton code comme des return, des ? ou des clone inutiles. De manière générale, exploite le fait que tout est une expression en Rust

a tiens non dans le mien mais plutot celui de lalrpop

Tu devrais utiliser cargo fmt.

ok je l’utilise

T’en qu’à faire, tu devrais utiliser un outil genre rust analyzer qui pointe du doigt les problèmes au fur-et-à-mesure que tu codes et offre des corrections rapides, c’est pratique. À minima, cargo fix t’aiderait pas mal à améliorer la qualité du code sans faire d’énormes efforts. Le chemin du fichier source est en dur dans ton main, c’est pas très pratique. Tu devrais accepter un argument en ligne de commande (pour info, cargo run — args passe les args à ton programme) avec le chemin du fichier source à compiler.

pas encore fait mais bientot ;)

Ton exemple de fichier source est pour le moins surprenant, il ne fait rien et a même une branche while avec autre chose qu’un booléen en argument (et qui du coup n’est pas exécutée alors qu’elle a l’air infinie). C’est pas terrible. Tu pourrais pas donner un exemple qui sort quelque chose de plus intéressant, même si c’est un bête compteur ?

je l’ai fait vraiment vite mais j’ai des choses plus importants dans tlang a faire

T’en qu’à faire, tu devrais utiliser un outil genre rust analyzer qui pointe du doigt les problèmes au fur-et-à-mesure que tu codes et offre des corrections rapides, c’est pratique. À minima, cargo fix t’aiderait pas mal à améliorer la qualité du code sans faire d’énormes efforts.

fix :D

Le chemin du fichier source est en dur dans ton main, c’est pas très pratique. Tu devrais accepter un argument en ligne de commande (pour info, cargo run — args passe les args à ton programme) avec le chemin du fichier source à compiler.

en vrais je voulais juste voir si ca marche je ferai une command ou un repl apres

Tu pourrais avoir intérêt à faire une lib utilisée par main pour que les appels dans main soit plus simple. Il est plus usuel d’avoir un main presque vide qui appelle une lib et une lib qui essentiellement se contente de donner la structure des mod et exposer l’interface publique. Ça rend l’écriture de tests plus facile (tu devrais commencer à en écrire d’ailleurs, histoire de pas risquer de tout casser sans t’en rendre compte).

ok, je vais le faire prochainement

Dans ton match expr dans Vm::evalexpr, tu as une branche pour capturer ce que tu n’as pas encore implémenté. C’est une mauvaise idée parce que ça empêche le compilo de te dire que tu as oublié une branche si tu ajoutes une Expr. Si tu as des Expr dont l’évaluation n’est pas implémentée (comme Function), fais une branche avec todo!(). C’est la façon standard de marquer un truc que tu vas implémenter, et tu auras une erreur au runtime si tu va dans cette branche. Un outil comme cargo fix ou rust analyzer fera ça pour toi. Comme son type est !, ça se coerce au type de ton match si c’est une expression. D’ailleurs, un truc qui aurait du te mettre la puce à l’oreille est que le message d’erreur est absurde : tu peux pas avoir une "unknown expression" alors que le système de type te garantie que expr est l’une des variantes que tu t’es donné.

cargo fix n’a rien modifie mais ok, je le ferai plus tard (dans pas longtemps mais pour l’instant j’ai impl tout les cas )

La fonction Vm::eval_many_expr est surprenante : si ton programme était wrappé dans un Block tu pourrais le traiter comme n’importe quelle autre Expr.

fix :)

executer n’est pas un mot anglais.

zut ! bon pour l’instant je le garde telle qu’elle

+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