- ***C'est quoi concretement le compilateur ?
Le compilateur prend la liste de tokens produite par le lexer et genere une suite
d'instructions 'bytecode'. La machine virtuelle (fournie par le prof == vm.c) les execute ensuite une par une avec une pile et une table de variables.
** Fonctions à connaitre :
check — regarde si le token courant est du type voulu, sans avancer.
advance — passe au token suivant (et met l'ancien dans previous)
match — combine check + advance : si le token courant est du bon type, il avance et retourne 1. Sinon, ne fait rien et retourne 0. C'est le cœur de la lecture de tokens : "si tu vois X, consomme-le".
expect — comme match mais obligatoire : si le token n'est pas du bon type, affiche une erreur et active le flag had_error. (Utilisé pour les éléments syntaxiques nécessaires (ex: le ; après chaque instruction).)
compile_primary → celui qui emettra les instructions ; cas initial : un entier, un string, un identifiant (variable), ou une expression entre parenthèses. Génère les instructions
compile_factor → (appelle compile_primary), puis gère * et /
compile_term → (appelle compile_factor), puis gère + et -. Plus basse priorité que *//, donc 2 + 3 * 4 sera bien calculé dans le bon ordre.
compile_comparison → gère <, >, <=, >=.
compile_equality → gère == et !=, la priorité la plus basse.
compile_expression → tout commence par ici (point d'entrée): appelle compile_equality, qui appellera d'autre fonctions jusqua trouver la bonne
compile_let_statement — gère let x = expr;. Il attend un identifiant, puis =, compile l'expression
compile_if_statement — gère if (condition) { bloc }. Compile la condition
compile_block — gère { ... } : attend {, compile des instructions jusqu'à }.
compile_statement — aiguillage général : selon le token courant, délègue à la bonne fonction de compilation. Gère aussi l'assignation x = expr; (sans let).
compile — fonction principale : initialise le compilateur, compile toutes les instructions jusqu'à TOKEN_EOF, et retourne le bytecode ou NULL si erreur
*Fonctionnement :
tout d'abord il faut respecter l'ordre des priorités mathématiques, pour cela ; Chaque fonction gère UN niveau de priorité.
Par exemple :
compile_factor s’occupe de * et /compile_term s’occupe de + et -- Et comme
compile_term appelle avant compile_factor, les multiplications sont calculées avant les additions automatiquement.
Les fonctions imbriquées servent à respecter les priorités des opérateurs.
Le backpatching (pour fonctions compile if/while) sert à corriger les sauts une fois qu’on connaît leur destination. (on dirige le saut vers 0 et des qu'on connait son adresse finale on vient la modifier afin de jump vers la bonne adresse)
compile_statement choisit quelle partie du code compiler selon le token actuel. (exemple token while -> elle compile du while)