Compilateur TPC - Analyse et Génération de Code

Compilateur pour le langage TPC avec génération d’assembleur x86-64

CFlexBisonNASMx86-64 AssemblyMakeGCC
Voir le code
Projet académique réalisé dans le cours de Compilation (Université Gustave Eiffel). L’objectif était de développer un compilateur complet pour TPC (Tiny Pascal C), un petit langage inspiré du C et de Pascal. Le compilateur transforme un code source en assembleur NASM x86-64 exécutable. Le projet couvre toutes les étapes d’un compilateur : analyse lexicale (Flex), analyse syntaxique (Bison), construction d’un AST, gestion des symboles et des portées, analyse sémantique avec vérification des types, puis génération du code assembleur selon la convention System V AMD64. L’implémentation est découpée en modules clairs et un Makefile automatise toute la chaîne.

Analyse Lexicale avec Flex

Le fichier tpc.lex définit les tokens du langage via des expressions régulières : mots-clés, identifiants, opérateurs, délimiteurs et littéraux. Le lexer ignore les commentaires et les espaces. Chaque token conserve sa position (ligne/colonne) pour afficher des erreurs claires. Les erreurs lexicales (caractère invalide, chaîne non terminée) sont détectées et signalées proprement.

Analyse Syntaxique avec Bison

Le fichier tpc.y décrit la grammaire LALR(1) : déclarations, instructions (affectations, if, while, return) et expressions avec priorités d’opérateurs. Bison génère le parseur et construit l’AST via des actions sémantiques. Les erreurs de syntaxe sont signalées avec la ligne et un message précis (parenthèse manquante, instruction incomplète, etc.).

Arbre Syntaxique Abstrait

Le module tree.c/h définit les structures de l’AST pour les déclarations, expressions et instructions. Chaque nœud contient son type, ses enfants et ses attributs (valeur, identifiant, opérateur). L’AST est utilisé pour l’analyse sémantique et la génération de code. Une option d’affichage permet de visualiser l’arbre pour le debug.

Tables de Symboles et Portées

Les tables de symboles gèrent les portées imbriquées (globale, fonctions, blocs). Chaque symbole stocke son nom, son type, sa catégorie (variable, paramètre, fonction) et un offset mémoire. Le système détecte les erreurs courantes : redéclarations, identifiants non déclarés, incohérences de types. Une option affiche les tables pour faciliter la vérification.

Analyse Sémantique

Le module sémantique parcourt l’AST pour vérifier la compatibilité des types, l’utilisation correcte des variables et la cohérence des signatures de fonctions. Les règles sont strictes (pas de conversion implicite, sauf char → int). Chaque nœud reçoit un type résultant, ce qui prépare la génération de code. Les erreurs sont listées avec leur position.

Génération de Code x86-64

La génération produit de l’assembleur NASM x86-64 en respectant System V AMD64 (passage de paramètres, alignement, retour). Le code est séparé en sections .data, .bss et .text. Chaque fonction a un prologue/épilogue propre. Les expressions sont traduites avec une pile d’opérandes et des registres. Les structures if/while utilisent labels et sauts conditionnels. Les tableaux sont gérés avec calcul d’offset.

Architecture et Tests

Le projet est découpé en modules clairs : lexer, parser, AST, symboles, sémantique, génération, et un main qui orchestre le tout. Le Makefile automatise flex → bison → gcc → ld. Des tests couvrent les déclarations, fonctions, boucles, expressions, et les erreurs attendues. Les scripts automatisent l’assemblage et l’exécution pour vérifier les résultats.