26 mai 2026
Ce code implémente un mini-interpréteur Logo (le langage de la tortue graphique) en C++ avec affichage graphique via SFML. Son architecture est assez propre pour un projet pédagogique : on retrouve les grandes étapes classiques d’un interpréteur :
Le programme lit un fichier Logo comme :
avance 100
droite 90
avance 50
pour carre
avance 100
droite 90
avance 100
droite 90
avance 100
droite 90
avance 100
fin
carre
et produit un dessin en faisant avancer une tortue.
L’architecture générale est :
Fichier Logo
│
▼
Lexer.cpp
│
▼
Liste de tokens
│
▼
Program.cpp
│
▼
Arbre / liste d'instructions
│
▼
Interpreter.cpp
│
▼
Exécution instruction par instruction
│
▼
Turtle.cpp (SFML)
Fichier :
Lexer.cppSon rôle est de transformer :
avance 100
droite 90
en :
(TokenKeyword,"avance")
(TokenNumber,"100")
(TokenKeyword,"droite")
(TokenNumber,"90")
La fonction principale est :
TokenList getTokens(string const &s)Le type :
typedef std::pair<TokenType,std::string> Token;représente un mot du programme.
Fichier :
Program.cppCette classe transforme les tokens en objets C++ représentant les instructions.
Par exemple :
avance 100
devient :
Move(100,true)et
droite 90
devient :
Turn(90,true)InstructionList myInstructions;contient toutes les instructions du programme.
Chaque élément est un :
std::unique_ptr<Instruction>Donc on utilise le polymorphisme.
Classe de base :
class InstructionToutes les commandes héritent de cette classe.
Instruction
│
├── UnaryInstruction
│ ├── Move
│ └── Turn
│
├── SetPenPos
│
└── FunctionCall
avance 100
recule 100sont représentés par :
MoveLe booléen :
isForwardindique s’il s’agit d’un avance ou d’un recule.
droite 90
gauche 90sont représentés par :
TurnLe booléen :
isClockwiseindique le sens de rotation.
leve
baisse
active ou désactive le stylo.
Lorsque le parseur rencontre :
carre
il crée :
FunctionCall("carre")qui appellera la fonction Logo correspondante.
Classe intermédiaire :
class UnaryInstructionElle factorise toutes les instructions ayant un seul paramètre numérique.
Exemple :
avance 100
droite 90
Les deux possèdent :
int myParameter;Le constructeur vérifie :
if(currentPos->first != TokenNumber)et convertit le texte en entier.
Classe :
InterpreterC’est le cœur du système.
Dans le constructeur :
parse(code);on fait :
tokens
→
Programet on obtient :
myProgramstartProgram();initialise la pile d’exécution :
myCallStackavec le programme principal.
nextStep();fait exactement une instruction.
Pseudo-code :
instruction courante
execute()
calcul instruction suivante
mettre à jour le pointeur
C’est la partie la plus intéressante.
Le programme utilise :
std::vector<ExecutionContext> myCallStack;comme une pile d’appels.
Contient :
InstructionPointer myCurrentPos;
InstructionPointer myEndOfBlock;Autrement dit :
où suis-je ?
où se termine mon bloc ?
Programme :
pour carre
avance 100
fin
carre
Pile :
Main
Lorsque :
carre
est exécuté :
FunctionCall::doExecute()fait :
pushNewContext(...)La pile devient :
Main
└── carre
Quand la fonction se termine :
myCallStack.pop_back();et on revient au programme principal.
Chaque instruction sait calculer :
InstructionPointer getNext(...)Par défaut :
return ++interpreter.currentPos();Donc :
instruction suivante
Lorsqu’une fonction est appelée :
return interpreter.currentPos();On ne passe pas à l’instruction suivante immédiatement.
Pourquoi ?
Parce qu’on vient d’empiler un nouveau contexte d’exécution.
L’instruction suivante doit être la première de la fonction appelée.
Namespace :
namespace turtleFichier :
Turtle.cppVariables globales :
double turtleX;
double turtleY;
double turtleDir;représentent :
position X
position Y
orientation
Dans :
Move::doExecute()on utilise :
sin()
cos()pour calculer la nouvelle position.
turtleX += ...
turtleY -= ...Si :
turtlePenDownalors :
drawLine(...)ajoute un segment dans :
sf::VertexArray linesTurn::doExecute()modifie :
turtleDirLe déroulement est :
ouvrir fichier
↓
compiler
↓
afficher programme
↓
initialiser tortue
↓
startProgram()
↓
tant que programme actif
nextStep()
redraw()
sleep()
↓
attendre fermeture fenêtre
Dans :
ExecutionContexton trouve :
std::map<std::string,int> myVariables;et :
int &variable(...)mais aucune instruction ne les utilise.
Cela ressemble à une fonctionnalité prévue mais non terminée.
Dans :
FunctionCall::doExecute()on trouve :
Program *prog = myMainProgram.findSubProgram(myName);puis immédiatement :
prog->begin()Si la fonction n’existe pas :
prog == nullptr⇒ crash.
Il faudrait ajouter :
if(!prog)
throw std::runtime_error("Unknown function");Le namespace :
turtlecontient de nombreuses variables globales :
turtleX
turtleY
turtleDir
...C’est simple mais peu robuste.
Une classe :
class Turtleserait plus maintenable.
repeteLe mot-clé :
repeteest reconnu par le lexer :
kwRepeatmais n’est jamais implémenté dans :
Program::parseKeyword()Donc le langage est incomplet.
Pour maintenir ce code, les 4 classes les plus importantes sont :
Instruction.Si vous comprenez le chemin :
Texte Logo
↓
Lexer
↓
Program
↓
Instruction
↓
Interpreter
↓
Turtle
alors vous maîtrisez déjà environ 80 % du fonctionnement de ce projet.