S01E14 — 11-08-2024.
Inspecteur la Bavure.
jour 14.
Palala ! Sayé, on y est ! Ça va boucler, ça va tourner, ça va looper, maintenant je vais pouvoir itérer des blocs d’instructions selon, les règles de l’art de l’informatique. J’étais tellement content de voir ma boucle while avoir le comportement attendu. Je criais : “mon enfant sait compter !!!”. C’est vraiment les montagnes russes la programmation informatique. Laisse-moi te compter une histoire.
Je venais de finir d’implémenter le support des fonctions via le mot clé fun. Puis comme souvent, je fais de la revue de mon code. Cela me permet de voir le reste à faire, mais aussi de détecter les erreurs d’implémentation. Je suis sur la revue de l’interpréteur zo. Je scrolle et m’arrête sur chaque fonction et commence mon analyse en compilant ce que le code fait dans ma tête. Me voici maintenant sur la fonction interpret_expr_assign_op.
Et là qu’est-ce que je vois ?!
1 /// Interprets an assignment operator expression.
2 fn interpret_expr_assign_op(
3 &mut self,
4 binop: &ast::BinOp,
5 assignee: &ast::Expr,
6 value: &ast::Expr,
7 span: Span,
8 ) -> Result<Value> {
9 let name = assignee.as_symbol();
10
11 let lhs = match self.scope_map.var(name) {
12 Some(value) => value.to_owned(),
13 None => return Err(error::eval::not_found_var(span, *name)),
14 };
15
16 self.scope_map.update_var(*name, lhs.clone())?;
17
18 let rhs = self.interpret_expr(value)?;
19
20 Ok(match binop.kind {
21 ast::BinOpKind::Add => lhs + rhs,
20 ast::BinOpKind::Sub => lhs - rhs,
21 ast::BinOpKind::Mul => lhs * rhs,
22 ast::BinOpKind::Div => lhs / rhs,
23 ast::BinOpKind::Rem => lhs % rhs,
24 // todo — should be `unknown assignop` error instead.
25 _ => return Err(error::eval::unknown_binop(span, *binop)),
26 })
27 }
Au-dessus, tu as exactement ce que j’avais écrit. Quelle infamie ! Comment ai-je pu écrire ça ! Pardonnez-moi. Bon, je t’ai mis le numéro des lignes, mais elles ne correspondent pas à celles dans mon code. Elles sont là pour faciliter la compréhension de mon récit. Avant, je dois introduire un concept : scope_map. C’est une structure de données utilisée pour gérer la portée des variables. Avoir une carte des portées va me permettre de délimiter la portée d’une ou plusieurs variables localement. C’est-à-dire que je restreins l’étendue des variables. Dans le cas, d’un opérateur d’affectation, la variable doit déjà avoir été déclarée en amont dans le code pour que l’on puisse la mettre à jour avec la nouvelle valeur. Et rien qu’en regardant mon code, je rigole de ma connerie. Donc, ce que je fais c’est :
- A la ligne
9, je récupère le nom de la variable. - A la ligne
11, j’utilise le nom de la variable pour récupérer sa valeur dans la carte des portées. lhsreprésente l’ancienne valeur.- A la ligne
16, je mets à jour la variable avec l’ancienne valeur. - A la ligne
18, je récupère la nouvelle valeur. - Enfin, à la ligne
20, j’effectue l’opération en fonction du type de l’opérateur binaire et retourne le résultat.
Je suppose qu’à la lecture, tu as capté l’incohérence. Pourquoi, je mets à jour la variable avec la même valeur lhs — c’est-à-dire l’ancienne. En gros, je récupère une ancienne valeur et je la mets à jour avec une ancienne valeur qui plus est la même ?! Quel Bachi-bouzouk, je suis quand même ! Tu m’étonnes que ça ne marchait pas, Tonnerre de Brest ! Du coup, j’ai revu et corrigé mon code comme ceci :
1 /// Interprets an assignment operator expression.
2 fn interpret_expr_assign_op(
3 &mut self,
4 binop: &ast::BinOp,
5 assignee: &ast::Expr,
6 value: &ast::Expr,
7 span: Span,
8 ) -> Result<Value> {
9 let name = assignee.as_symbol();
10
11 let lhs = match self.scope_map.var(name) {
12 Some(value) => value.to_owned(),
13 None => return Err(error::eval::not_found_var(span, *name)),
14 };
15
16 let rhs = self.interpret_expr(value)?;
17
18 let value = match binop.kind {
19 ast::BinOpKind::Add => lhs + rhs,
20 ast::BinOpKind::Sub => lhs - rhs,
21 ast::BinOpKind::Mul => lhs * rhs,
22 ast::BinOpKind::Div => lhs / rhs,
23 ast::BinOpKind::Rem => lhs % rhs,
24 // todo — should be `unknown assignop` error instead.
25 _ => return Err(error::eval::unknown_binop(span, *binop)),
26 };
27
28 self.scope_map.update_var(*name, value.clone())?;
29
30 Ok(value)
31 }
Comme tu le vois ligne 18, je récupère la nouvelle valeur de l’opération binaire. Et c’est cette nouvelle valeur que je mets à jour avec la variable correspondante L28. Sans oublier de retourner la bonne valeur. C’est bien mieux comme ça. Et surtout, plus logique.
Voilà, je laisse ça là pour ne plus jamais oublier cette bavure.