Lexeur¶
Le gros challenge de cette partie est de délimiter les bornes des mots. Encore une fois, la version simple mais incomplète est beaucoup plus accessible et rentable en temps.
C’est probablement la partie la plus dure à faire parfaitement.
Faites des tests
Beaucoup de tests
Testez ce que vous ne savez pas
Méthode simple¶
Voici un algorithme de lexing minimal :
On saute les espaces
Si le caractère actuel commence un opérateur, on lexe et retourne un nouveau token
Sinon, on fait partie d’un mot. On continue à lexer tant que le caractère n’est pas une opérateur / un espace.
Cet algorithme ne gère pas les quotes. Il est fortement conseillé d’utiliser une approche récursive pour gérer celles-ci.
Méthode propre¶
Le lexeur devrait être réutilisable pour la partie expansion. Sinon, vous pourriez interprêter différement deux chaines, et échouer avec perte et fracas.
Au premier abord, la tache est difficile: la tache du lexeur est de délimiter les strings, et celle de l’expansion de les interprêter. Il s’agit donc de leur construire une base commune
Vous avez envie de le construire récursivement, de telle sorte que les différents contextes (', ", $(), \`
)
soient gérés sans cas particulier. Idéalement, le code doit être réutilisable pour la partie expansion (quand
vous allez réutiliser les strings. De plus, (si vous voulez les gérer, c’est à dire certainement pas), les
HEREDOC ne devraient pas être un type de token différent du point de vue du parseur, mais plutôt une sorte de
promesse future de mot.
Une solution intéressante peut être de construire deux lexeurs, le premier servant de base commune au second et à l’expansion.
Les alias¶
Les alias sont gérés lors du lexing. L’environnement d’exécution contient un tableau associant le nom de l’alias à la liste de tokens qui devront le remplacer.
Considérez l’exemple suivant:
alias oops='mafonction ('
oops) {
echo hm
}
Le shell fait comme suit:
il parse la première commande comme le mot alias suivit d’un unique argument:
oops=mafonction (
.il exécute la première commande, qui rajoute enregistre
oops
dans la table des alias. La valeur de l’alias est lexée pour pouvoir être plus facilement substituée par la suite.il commence à parser la seconde ligne. Comme le token oops commence une commande, le shell regarde si il existe un alias à ce nom. comme c’est le cas, il remplace le token par la liste de tokens contenue dans l’alias.
mafonction
commence une commande. Il n’y a pas d’alias à ce nom, le parsing continue. Une fonction est reconnue.
Quelques exemples de comportements importants:
# les alias travaillent au niveau des tokens
alias atroce='func('
# la ligne suivante donne une erreur de syntaxe,
# car func(\n n'est pas du shell valide.
atroce # ERROR: syntax error near unexpected token `newline'
# ceci est une déclaration de fonction valide.
# les tokens sont substitués car en début de ligne
atroce ) {
echo vraiment atroce
}
if true; then
alias machin='echo hm ok'
# la commande suivante n'est pas trouvée car
# l'alias n'était pas encore là quand le mot
# a été lexé.
machin # ERROR: machin: command not found
fi
# l'alias déclaré au dessus est maintenant actif
machin # "hm ok"
# ATTENTION: cet alias s'appelle lui-même.
# Cela doit être détecté lors de l'exécution.
alias echo='echo bidule'
alias machin='echo truc'
machin # "bidule truc"
# les alias ne sont substitués qu'en début de ligne
printf '%s\n' echo # "echo"
# la recherche d'alias se fait forcément avant l'expansion
# (on est au lexing). "\echo" n'est pas littéralement un nom
# d'alias, et ne le deviendra qu'après expansion.
\echo vrai echo # "vrai echo"