26 mai 2026
Le fichier prog2.logo utilise une construction Logo
classique :
pour hex
repete 6 [avance 40 droite 60]
fin
L’objectif est donc d’exécuter plusieurs fois un bloc
d’instructions délimité par [ et
].
Le lexer reconnaît déjà :
char const kwRepeat[] = "repete";et les caractères :
[]sont déjà transformés en tokens :
TokenSignLe travail restant concerne donc essentiellement :
Program)RepeatPour :
repete 3 [avance 10 droite 90]
on veut obtenir :
avance 10
droite 90
avance 10
droite 90
avance 10
droite 90
Le bloc entre [ et ] doit être exécuté
plusieurs fois.
Comme les autres instructions :
Instruction
│
├── Move
├── Turn
├── SetPenPos
├── FunctionCall
└── Repeat
Par exemple :
class Repeat : public Instruction
{
public:
Repeat(...);
private:
virtual void doExecute(Interpreter&) const override;
virtual void doDisplay(std::ostream&) const override;
};Le plus simple est que Repeat contienne un
sous-programme :
class Repeat : public Instruction
{
int myCount;
std::unique_ptr<Program> myBody;
};Ainsi :
repete 6 [avance 40 droite 60]
produit :
Repeat
├─ count = 6
└─ Program
├─ Move(40)
└─ Turn(60)
Actuellement :
Program::parseKeyword(...)gère :
avance
recule
droite
gauche
leve
baisse
pour
finIl faut ajouter :
else if (token->second == kwRepeat)
{
myInstructions.push_back(
std::make_unique<Repeat>(tokens, ++token, mainProgram)
);
}Dans le constructeur de Repeat :
repete 6 [avance 40 droite 60]
on doit lire :
6puis vérifier :
[ensuite construire le sous-programme.
Par exemple :
if(token->first != TokenNumber)
throw ParseError(...);
myCount = std::stoi(token->second);
++token;Puis :
if(token->second != "[")
throw ParseError(...);
++token;Le plus gros travail est ici.
Actuellement un Program se termine sur :
finmais un bloc REPETE doit se terminer sur :
]Il faut donc permettre à Program d’être construit avec
un délimiteur de fin.
Par exemple :
enum class ProgramEnd
{
EndKeyword,
ClosingBracket
};Puis :
Program(tokens,currentToken,mainProgram,
ProgramEnd::ClosingBracket);Ainsi :
repete 6 [
avance 40
droite 60
]
sera analysé exactement comme un programme normal.
C’est la partie la plus délicate.
Aujourd’hui la pile d’appels contient :
Main
└─ Fonction
Pour une boucle il faut quelque chose comme :
Main
└─ Repeat
itération 1
itération 2
...
Ne pas modifier l’interpréteur.
Au moment du parsing :
repete 6 [avance 40 droite 60]
on génère directement :
Move(40)
Turn(60)
Move(40)
Turn(60)
Move(40)
Turn(60)
Move(40)
Turn(60)
Move(40)
Turn(60)
Move(40)
Turn(60)
Autrement dit :
C’est ce qu’on appelle du loop unrolling.
Avantages :
InterpreterExecutionContextPour un projet de deuxième année, c’est probablement la meilleure stratégie.
Un vrai interpréteur ferait :
Repeat
{
int count;
Program body;
}et l’exécution pousserait un contexte spécial :
RepeatContext
{
compteurCourant;
compteurMax;
}Mais cela oblige à :
ExecutionContextInterpreter::nextStep()Instruction::getNext()C’est beaucoup plus invasif.
Ordre des travaux :
Repeat.h/.cppkwRepeat dans
Program::parseKeyword()repete N [ ... ]
Exemple :
repete 3 [avance 10]
devient en mémoire :
Move(10)
Move(10)
Move(10)
Cette version permet déjà d’exécuter correctement votre
prog2.logo sans toucher à l’interpréteur.
RepeatC’est la solution qu’on attendrait plutôt dans un interpréteur industriel.