Skip to content

Introduction à la théorie des compilateurs

Préface

Que se passe-t-il quand vous appuyez sur le bouton « Exécuter » ? Comment le code devient-il un résultat à l'écran ? Chaque ligne de code que vous écrivez est en réalité « illisible » pour l'ordinateur — il ne connaît que les 0 et les 1. Le compilateur est le « traducteur » qui transforme le langage humain en langage machine. Comprendre la théorie des compilateurs vous permet de comprendre d'où viennent les messages d'erreur, pourquoi certains langages sont rapides et d'autres lents, et les mécanismes fondamentaux de l'optimisation du code.

Que allez-vous apprendre dans cet article ?

À la fin de ce chapitre, vous aurez acquis :

  • Vue d'ensemble : maîtriser le pipeline complet de compilation, du code source au programme exécutable
  • Analyse lexicale : comprendre comment le compilateur découpe le code en tokens
  • Analyse syntaxique : comprendre la construction de l'AST (arbre syntaxique abstrait)
  • Visualisation de l'AST : voir directement la structure arborescente du code
  • Analyse sémantique et optimisation : comprendre les principes de la vérification de types et de l'optimisation du code
  • Techniques d'optimisation en pratique : maîtriser le constant folding, l'élimination du code mort et d'autres optimisations clés
  • Modèles d'exécution : distinguer les trois modes d'exécution : compilé, interprété et JIT
ChapitreContenuConcepts clés
Chapitre 1Qu'est-ce qu'un compilateurAnalogie du traducteur, pipeline de compilation
Chapitre 2Analyse lexicaleToken, règles lexicales
Chapitre 3Analyse syntaxiqueAST, arbre syntaxique, priorités
Chapitre 4Visualisation de l'ASTArbre syntaxique interactif, types de nœuds
Chapitre 5Analyse sémantique et optimisationVérification de types, constant folding, élimination du code mort
Chapitre 6Techniques d'optimisation en pratiqueInlining de fonctions, loop hoisting, propagation de constantes
Chapitre 7Compilé vs Interprété vs JITComparaison des trois modèles d'exécution

0. Vue d'ensemble : le « voyage de traduction » du code

Imaginez que vous êtes un traducteur chargé de traduire un roman chinois en anglais. Vous ne traduisez pas mot à mot, mais vous :

  1. Identifiez les mots — découpez la phrase en unités (analyse lexicale)
  2. Comprenez la syntaxe — jugez si la structure de la phrase est correcte (analyse syntaxique)
  3. Comprenez le sens — assurez-vous que le sens est fluide et cohérent (analyse sémantique)
  4. Affinez — rendez la traduction plus naturelle (optimisation du code)
  5. Produisez la traduction — rédigez la version anglaise finale (génération de code)

Le compilateur fait exactement la même chose, sauf qu'il traduit des langages de programmation.

Compiler Principles: The Art of TranslationHow code becomes machine instructions
A compiler is like a translator, turning human-readable code into machine-readable instructions
The Complete Code Translation Pipeline
1
Lexical analysis
Break code into individual words called tokens
int age = 25 → [int, age, =, 25]
2
Syntax analysis
Check grammar rules and build a syntax tree
Validate whether statement structure is correct
3
Semantic analysis
Check whether the meaning of the code is valid
Check variable definitions and type compatibility
4
Intermediate code generation
Generate a machine-independent intermediate representation
Generate bytecode or intermediate representation
5
Optimization
Improve code so it runs more efficiently
Constant folding and dead-code elimination
6
Target code generation
Generate machine code or target code
Generate x86 or ARM machine instructions
Lexical analysis: tokenization
int age = 25;
Keywordint
Identifierage
Operator=
Number25
Separator;
Syntax analysis: build a tree
Assignment statement
Variableage
Operator=
Number25
Compilation vs Interpretation
Compiled languages
Source code → Compiler → Machine code
C, Go, Rust
✓ Fast execution
✓ Compile once, run many times
✗ Slow compile step
Interpreted languages
Source code → Interpreter → Line-by-line execution
Python, JavaScript, PHP
✓ Fast development
✓ Cross-platform
✗ Slower execution
Compiler Optimization
Before:
x = 5 + 3 + 2
⬇️
After:
x = 10
The compiler can optimize code automatically and improve runtime efficiency

1. Le pipeline en six étapes du compilateur

Le travail du compilateur peut être divisé en six phases, comme une chaîne de montage d'usine, chaque phase transmettant son résultat à la suivante.

How a Compiler WorksA six-step journey from source code to machine code
1
Lexical analysis→ Token stream
2
Syntax analysis→ AST syntax tree
3
Semantic analysis→ Typed AST
4
Intermediate code generation→ IR (intermediate representation)
5
Code optimization→ Optimized IR
6
Target code generation→ Machine code
1Lexical analysisOutput: Token stream
Split source code into individual words called tokens, like recognizing each word in a sentence.
Recognize keywordsRecognize identifiersRecognize numbersRecognize operatorsFilter whitespace
int x = 10 + 5;
→ [int] [x] [=] [10] [+] [5] [;]
    keyword identifier operator number operator number separator
Live lexical analysis
intKeyword
xIdentifier
=Operator
10Number
+Operator
5Number
;Separator
Three Execution Models Compared
Compiled
Source Compiler Machine code CPU execution
Fast executionMust wait for compilation
C, C++, Rust, Go
Interpreted
Source Interpreter Line-by-line execution
Run immediately while writingSlower execution
Python, Ruby, PHP
JIT
Source Bytecode JIT hot path compilation Execution
Balances performance and flexibilitySlower startup
Java, JavaScript (V8)
Core idea:A compiler is like a translator: it gradually turns human-readable code into instructions the machine can run. The six stages each do one job: identify words → understand syntax → check meaning → generate IR → optimize → generate machine code.

Pipeline de compilation

  1. Analyse lexicale (Lexical Analysis) : découper le code source en tokens (mots)
  2. Analyse syntaxique (Syntax Analysis) : organiser les tokens en arbre syntaxique (AST)
  3. Analyse sémantique (Semantic Analysis) : vérifier que les types sont corrects et les variables déclarées
  4. Génération de code intermédiaire (IR Generation) : produire une représentation intermédiaire indépendante de la plateforme
  5. Optimisation du code (Optimization) : rendre le code intermédiaire plus efficace
  6. Génération de code (Code Generation) : produire le code machine de la plateforme cible
PhaseEntréeSortieAnalogie
Analyse lexicaleFlux de caractères du code sourceFlux de tokensDécouper une phrase en mots
Analyse syntaxiqueFlux de tokensAST (arbre syntaxique)Analyser la structure de la phrase
Analyse sémantiqueASTAST typéVérifier que le sens est cohérent
Code intermédiaireAST typéIRRédiger un premier brouillon
Optimisation du codeIRIR optimiséAffiner et supprimer
Génération de codeIR optimiséCode machineProduire la version finale

2. Analyse lexicale : découper le code en « mots »

L'analyse lexicale est la première étape de la compilation. Le compilateur scanne chaque caractère du code source de gauche à droite et les regroupe en tokens (unités lexicales) significatifs.

🔤 Lexer: Split Code into Tokens

Enter a line of code and see lexical analysis results in real time

Comme votre cerveau assemble automatiquement les lettres en mots lorsque vous lisez une phrase en anglais, l'analyseur lexical assemble les caractères en tokens :

Code source : let x = 10 + 5;

Flux de tokens :
[let]   → Mot-clé (mot réservé du langage)
[x]     → Identifiant (nom de variable)
[=]     → Opérateur (affectation)
[10]    → Littéral numérique
[+]     → Opérateur (addition)
[5]     → Littéral numérique
[;]     → Séparateur (fin d'instruction)

Les cinq types de tokens

  • Mots-clés : mots spéciaux réservés par le langage, comme let, if, return, function
  • Identifiants : noms définis par le programmeur, comme les noms de variables et de fonctions
  • Littéraux : valeurs écrites directement dans le code, comme le nombre 42 ou la chaîne "hello"
  • Opérateurs : symboles effectuant des calculs, comme +, -, =, ===
  • Séparateurs : symboles délimitant la structure du code, comme ;, ,, (, )

3. Analyse syntaxique : construire l'arbre syntaxique (AST)

L'analyse lexicale a découpé le code en tokens, mais les tokens ne sont que des « mots » isolés. L'analyse syntaxique a pour mission d'organiser ces tokens en un arbre syntaxique abstrait (Abstract Syntax Tree, AST) selon les règles grammaticales — il reflète la structure du code et la priorité des opérateurs.

Expression : 1 + 2 * 3

Arbre syntaxique :     Pourquoi cette forme ?
       +            Parce que * a une
      / \           priorité supérieure
     1   *          à +, donc 2 * 3
        / \         est d'abord regroupé
       2   3        en sous-arbre

L'importance de l'AST

L'AST est la « structure de données centrale » du compilateur ; l'analyse sémantique, l'optimisation et la génération de code ultérieures s'appuient toutes dessus. Les outils de développement modernes utilisent également massivement l'AST :

  • ESLint : parse le code en AST pour vérifier les violations de règles
  • Prettier : parse en AST puis reformate la sortie
  • Babel : parse l'AST → transforme → génère du code compatible
  • Refactoring dans l'IDE : renommage sûr de variables et extraction de fonctions basés sur l'AST
Structure syntaxiqueSéquence de tokensNœud AST
Déclaration de variablelet x = 10VariableDeclaration → Identifier + Literal
Appel de fonctionadd ( 1 , 2 )CallExpression → Identifier + Arguments
Instruction conditionnelleif ( a > b )IfStatement → BinaryExpression + Block

4. Visualisation de l'AST : voir le « squelette » du code

Ci-dessus, nous avons décrit la structure de l'AST textuellement, mais « voir » est plus intuitif que « lire ». Le composant interactif ci-dessous vous permet de choisir différentes expressions et d'observer leur arbre syntaxique en temps réel.

🌳 AST Visualizer: See the Skeleton of Code

Choose an expression and inspect its abstract syntax tree

Syntax tree
BinaryExpression+
NumericLiteral1
BinaryExpression*
NumericLiteral2
NumericLiteral3
Parse notes
1* has higher precedence than +, so 2 * 3 groups first
22 * 3 forms a BinaryExpression subtree
31 and that subtree become the left and right operands of +
4The final + node is the root, showing the evaluation order
💡 Try AST Explorer — inspect ASTs for arbitrary code online

Grâce à la visualisation, vous découvrirez que les règles fondamentales de l'AST sont en réalité très simples :

Structure du codeNœud racine de l'ASTNœuds enfants
1 + 2 * 3BinaryExpression (+)Gauche : NumericLiteral(1), Droite : BinaryExpression(*)
let x = 10VariableDeclarationVariableDeclarator → Identifier(x) + NumericLiteral(10)
add(a, b)CallExpressionIdentifier(add) + Arguments(a, b)

L'AST dans le développement quotidien

Vous n'avez peut-être jamais écrit de compilateur, mais vous utilisez chaque jour des outils basés sur l'AST :

  • ESLint / Prettier : parsent le code en AST, vérifient les règles ou reformatent
  • Babel / SWC : parsent l'AST → transforment la syntaxe → génèrent du code compatible
  • Refactoring dans l'IDE : renommage sûr, extraction de fonctions basés sur l'AST
  • Tree-shaking : analysent les import/export de l'AST, suppriment le code inutilisé

5. Analyse sémantique et optimisation du code

L'analyse syntaxique garantit que le code est « structurellement correct », mais une structure correcte ne signifie pas que le « sens est correct ». L'analyse sémantique vérifie la validité du sens du code, tandis que l'optimisation du code permet au programme de s'exécuter plus rapidement.

Compilation PracticeFrom code to executable file
Input code
Compilation steps
1
Preprocess
gcc -E hello.c -o hello.i
Process #include and expand macros
2
Compile
gcc -S hello.i -o hello.s
Generate assembly code
3
Assemble
gcc -c hello.s -o hello.o
Generate object file
4
Link
gcc hello.o -o hello
Generate executable file
Generated files
📄
hello.c
Source code file
📝
hello.i
Preprocessed file
⚙️
hello.s
Assembly code file
📦
hello.o
Object file
🚀
hello
Executable file
Common compiler tools
GCC
GNU Compiler Collection
Clang
LLVM C/C++ compiler
MSVC
Microsoft Visual C++

4.1 Analyse sémantique : vérifier que le « sens » est correct

VérificationExempleRésultat
Vérification de typesint x = "hello"❌ Incompatibilité de types
Vérification de portéeUtilisation d'une variable non déclarée y❌ La variable n'existe pas
Inférence de types1 + 2.0✅ Résultat inféré comme float
Vérification des paramètresadd(1, 2, 3) mais la fonction n'accepte que 2 paramètres❌ Nombre de paramètres incorrect

La plupart des erreurs que vous voyez proviennent de l'analyse sémantique

  • TypeError: Cannot read properties of undefined — vérification de types
  • ReferenceError: x is not defined — vérification de portée
  • Expected 2 arguments, but got 3 — vérification des paramètres

4.2 Optimisation du code : rendre le programme plus rapide

Avant de générer le code final, le compilateur applique diverses optimisations au code intermédiaire. Ces optimisations sont transparentes pour le programmeur mais peuvent améliorer significativement les performances.

Technique d'optimisationAvantAprèsPrincipe
Constant foldingx = 10 + 5x = 15Calcul du résultat à la compilation
Élimination du code mortif (false) { ... }Suppression directeCode qui ne sera jamais exécuté
Propagation de constantesx = 15; y = x * 2y = 30Remplacement direct par les valeurs connues
Déplacement des invariants de bouclelen = arr.length calculé en boucleDéplacé hors de la boucleÉviter les calculs répétés

6. Techniques d'optimisation en pratique : comment le compilateur accélère le code

Nous avons mentionné plusieurs techniques d'optimisation par leur nom. Voyons maintenant en détail comment le compilateur procède concrètement. Le composant interactif ci-dessous illustre 5 des optimisations les plus courantes ; vous pouvez comparer visuellement le code avant et après optimisation.

⚡ Compiler Optimization: Make Code Faster Automatically

Choose an optimization technique and see how the compiler improves code

📝 Before optimization
const width = 10
const height = 20
const area = width * height  // computed at runtime
console.log(area)
Compiler optimization
🚀 After optimization
const area = 200  // computed during compilation
console.log(200)
How Constant folding works
The compiler sees that width and height are constants, so it computes 10 * 20 = 200 during compilation. Runtime no longer needs a multiplication.
Performance gain:
30%

Les compilateurs modernes et les moteurs JIT (comme V8, GCC, LLVM) appliquent automatiquement des dizaines d'optimisations. En tant que développeur, vous n'avez pas besoin de les faire manuellement, mais les comprendre vous aide à :

  • Écrire du code plus facilement optimisable : par exemple, utiliser const plutôt que let facilite le constant folding par le compilateur
  • Comprendre les différences de performance : pourquoi les petites fonctions sont-elles plus rapides que les grandes ? Parce que le compilateur peut les inliner
  • Éviter la « désoptimisation » : certains patterns comme eval() et with empêchent les optimisations du compilateur
Technique d'optimisationCondition de déclenchementImpact sur les performancesCe que le développeur peut faire
Constant foldingL'expression ne contient que des constantesÉlimine le calcul à l'exécutionUtiliser davantage les déclarations const
Élimination du code mortCode inaccessible ou résultat inutiliséRéduit la taille du codeNettoyer le code inutilisé
Déplacement des invariants de boucleCalcul invariant dans la boucleRéduit les calculs répétésL'extraction manuelle est aussi une bonne pratique
Inlining de fonctionsPetite fonction appelée fréquemmentÉlimine le surcoût d'appelGarder les fonctions petites et ciblées
Propagation de constantesValeur de variable déterminable à la compilationToute la chaîne de calcul est éliminéeUtiliser des constantes plutôt que des nombres magiques

7. Compilé vs Interprété vs JIT

Une fois le code écrit, il existe trois « modes de traduction » pour l'exécuter. Chacun a ses avantages et inconvénients, déterminant directement les caractéristiques de performance et les cas d'usage d'un langage.

🔄 Compiled vs Interpreted vs JIT

Click an execution mode to see how code moves from source to running program

📝
Source code
main.c
⚙️
Compiler
Full compilation
📦
Machine code
Binary executable
🚀
Run directly
CPU runs it directly
Run speed
Very fast
Startup
Slow; compile first
Portability
Recompile required
Representative languages:CC++RustGo
DimensionCompiléInterprétéJIT (Just-In-Time)
ProcessusCompiler entièrement en code machine, puis exécuterLire et exécuter ligne par ligneD'abord interpréter, puis compiler les points chauds
Vitesse d'exécutionLa plus rapideLa plus lenteIntermédiaire (points chauds proches du compilé)
Vitesse de démarrageLent (compilation nécessaire)Rapide (exécution directe)Intermédiaire (préchauffage nécessaire)
MultiplateformeRecompilation nécessaireNaturellement multiplateformeMultiplateforme
Langages représentatifsC, Rust, GoPython, RubyJavaScript (V8), Java

Pourquoi JavaScript est-il si rapide ?

Le compilateur JIT du moteur V8 surveille quelles portions de code sont fréquemment exécutées (points chauds), puis les compile en code machine hautement optimisé. Ainsi, bien que JavaScript soit un « langage interprété », sa performance dans V8 peut approcher celle des langages compilés. C'est aussi ce qui permet à Node.js d'être utilisé côté serveur.


Résumé

La théorie des compilateurs n'est pas un savoir réservé aux seuls développeurs de compilateurs. Comprendre le processus de compilation vous aide à mieux saisir les messages d'erreur, à choisir le bon langage et à écrire du code plus efficace.

Revenons sur les points clés de ce chapitre :

  1. Le compilateur est un traducteur : il traduit le code lisible par l'humain en instructions exécutables par la machine
  2. Pipeline en six étapes : analyse lexicale → analyse syntaxique → analyse sémantique → code intermédiaire → optimisation → génération de code
  3. L'analyse lexicale découpe en tokens : découpe le flux de caractères en mots-clés, identifiants, opérateurs et autres unités significatives
  4. L'analyse syntaxique construit l'AST : organise les tokens en structure arborescente selon les règles grammaticales, reflétant la priorité des opérateurs
  5. L'analyse sémantique garantit la correction : vérification de types, vérification de portée — la plupart des erreurs que vous voyez proviennent d'ici
  6. Le compilateur optimise automatiquement : le constant folding, l'élimination du code mort, l'inlining de fonctions accélèrent le code automatiquement
  7. Trois modèles d'exécution : compilé le plus rapide, interprété le plus flexible, JIT combine les deux

Lectures complémentaires