Compylateur
Posté le 08/05/2020 14:00
Bonjour à tous !
Il y a quelques temps j'ai fait un 'compilateur' qui permet d'exécuter un algorithme en langage naturel en le 'traduisant' en Python. Le code est atroce et repose sur un remplacement entre les commandes en langage naturel et les commandes en Python (à coup de dictionnaires et de tests conditionnels
)… J'aimerais faire de ce projet un 'vrai' compilateur
(on reste sur du Python
). Et j'ai quelques questions :
- La phase d'analyse lexicale repose pour l'instant sur une recherche et un replacement, avec un dictionnaire qui contient en clés les commandes en langage naturel, et en items, les commandes correspondantes en Python… Je me doute que ce n'est pas pertinent…
En fait l'analyse lexicale est mélangée à la phase d'analyse syntaxique.
- Comment faire pour basculer du langage naturel au Python ? Faut-il forcément passer par un hard code, ou est-ce que d'autre technique plus esthétiques existent ?
- L'analyse syntaxique est un bête
replace basé sur un dico… Du coup ça revient à la question précédente : comment éviter le hard code ?
- La phase sémantique… Je ne suis pas sûr d'avoir bien compris toutes les subtilités…
Dans mon cas, après le remplacement bête et méchant, la syntaxe Python n'est pas bonne, du coup je passe à travers différents tests conditionnels pour avoir un 'vrai' script fonctionnel… Encore une fois le hard code à coup de
if me plaît moyen…
- En derniers je refait un passage sur mon code généré et j'ajoute les alinéas. Est-ce que je devrais les gérer plus tôt (je pense à la phase d'analyse syntaxique… mais le placement des alinéas dépend du contexte du code, et sur une ligne donnée je vois pas trop comment faire…
Merci d'avance !
Citer : Posté le 22/06/2020 16:08 | #
C'pas bête !
Ajouté le 24/06/2020 à 13:48 :
Du coup tu me conseille de commencer par quel statement ?
Citer : Posté le 24/06/2020 13:49 | #
Il se ressemblent pas mal mais en gros tu dois définir de quoi assigner des variables et afficher des résultats. Après tu peux jouer à faire des blocs (if/else/etc) qui fonctionnent tous pareil.
Citer : Posté le 24/06/2020 13:56 | #
Ok… bon ben go pour l'assignement de variable
Dans ma tête je vois ça comme ça : on a deux types d'assignement :
- assignement 'direct' : a = 2
- assignement par l'utilisateur : a = input()
Et autre difficulté : la multitude de manière d'assigner… Du coup je pense commencer par rajouter les tokens…
Ajouté le 24/06/2020 à 14:16 :
Je suis en train de lister les différentes manières d'assigner :
… prend la valeur …
affecter à … la valeur…
… est initialisé à …
demander la valeur de … à l'utilisateur
on demande la valeur de … à l'utilisateur
saisir …
… = …
Il y en a d'autre ?
Edit :
J'ai rajouté ces tokens :
"prend la valeur":"TAKE"
"est initialisé à":"TAKE"
"demander la valeur de":"REQUEST"
"on demande la valeur de":"REQUEST"
"saisir":"REQUEST"
"à l'utilisateur":"USER"
"la valeur":"VALUE"
Ajouté le 24/06/2020 à 14:33 :
Du coup j'aurais une règle pour les assignement :
assignement -> AFFECT VAR VALUE expr | VAR TAKE expr | REQUEST VAR (USER)
USER est facultatif
Citer : Posté le 24/06/2020 14:34 | #
Ça a l'air pas mal. Le facultatif est souvent noté avec un ?, par exemple "USER?" (pour ton information).
Citer : Posté le 24/06/2020 14:37 | #
Ah ok, je savais pas bon ben du coup je vais tenter un premier jet de code
Pour l'instant le symbole = est associé au token EQUAL qui n'est utilisé nulle part, je ne sais pas comment l'utiliser ^^' l'idéal serait d'avoir l'assignement et la comparaison dessus, mais je ne sais pas comment éviter les expressions flottantes… ?
Ajouté le 24/06/2020 à 14:48 :
J'ai ça :
def assignement(self):
value = None
if self.token_ahead.type == "AFFECT"
self.expect()
var = self.expect("VAR")
self.expect("VALUE")
value = self.expr()
elif self.token_ahead.type == "REQUEST":
self.expect()
var = self.expect("VAR")
if self.token_ahead.type == "USER": self.expect()
elif self.token_ahead.type("VAR"):
var = self.expect()
self.expect("TAKE")
value = self.expr()
if value: return Node("Assignement", "direct", var.value, expr)
else: return Node("Assignement", "user", var.value)
Citer : Posté le 24/06/2020 14:58 | #
Tu n'as que ce que tu autorises explicitement.
assignment -> ... | VAR EQUAL expr # comme tout à l'heure
Tant que tu as pas statement -> expr, il ne sera pas possible d'avoir d'expression flottante.
Ton code a l'air pas mal, ça implémente bien la grammaire que tu as décrite. Bravo ! Tu commences à avoir les bons réflexes des parsers à descente récursive.
Tu devrais sans doute faire deux noeuds différents, assigner et demander une valeur à l'utilisateur sont des choses assez éloignées.
Citer : Posté le 24/06/2020 15:02 | #
Merci !
Okay je commence à comprendre un peu les mécaniques aussi… Mon code est buggé pour l'instant… donc faut que je le revoie xD
Le problème est comment différencier a = 2 où j'assigne 2 à a et a = 2 ou je teste si a est égal à 2… ?
Ajouté le 24/06/2020 à 15:08 :
C'est bon !
J'ai ce code :
def assignement(self):
value = None
if self.token_ahead.type == "AFFECT":
self.expect()
var = self.expect("VAR")
self.expect("VALUE")
value = self.expr()
elif self.token_ahead.type == "REQUEST":
self.expect()
var = self.expect("VAR")
if self.token_ahead.type == "USER": self.expect()
elif self.token_ahead.type == "VAR":
var = self.expect()
self.expect("TAKE")
value = self.expr()
if value: return Node("Assignement","", Node("Variable",var.value), value)
else: return Node("User's request", "", Node("Variable", var.value))
Et ce résultat :
--- Tokens ---
('AFFECT', 'affecter à')
('VAR', 'minimum')
('VALUE', 'la valeur')
('VAR', 'min')
('LPAR', '(')
('VAR', 'f')
('LPAR', '(')
('VAR', 'a')
('PLUS', '+')
('NUM', 2)
('RPAR', ')')
('RPAR', ')')
--- AST ---
Assignement :
Variable : minimum
Function : min
Function : f
Operation : +
Variable : a
Number : 2
Ajouté le 27/06/2020 à 16:34 :
Du coup je vais commencer à implémenter les if et dérivées… (si, alors, sinon) Juste un petit problème : cibler les actions qui sont exécutées dans le bloc conditionnel… ?
Pour la condition je pense à une grammaire : si -> IF condition
Edit : Finalement une syntaxe plus comme ça : si -> IF condition *(THEN ?) me parait mieux plusieurs THEN parce que alors et faire sont tous les deux géré comme des THEN et que la construction si … alors faire … est à peu près correcte… Par contre on peut aussi dire : si … alors, … du coup il faut gérer les virgules… ?
Citer : Posté le 27/06/2020 16:43 | #
Tu dois utiliser la structure récursive des blocs ici. Essaie de voir si tu comprends la grammaire ci-dessous :
block -> statement*
statement -> assignment | statement_if | statement_for | statement_etc
assignment -> # comme avant
statement_if -> IF condition THEN block (ELIF condition THEN block)* (ELSE block)? END
Citer : Posté le 27/06/2020 16:47 | #
Alors… je pense voir dans l'ensemble…
block est un bloc d'instructions.
statement est une instruction : assignement, condition, boucle itérative, boucle conditionnelle, affichage.
assigment on a déjà vu.
statement_if test conditionnel qui permet de contenir d'autre tests conditionnels grâce à la chaîne block -> statement -> statement_if
Citer : Posté le 27/06/2020 16:53 | #
C'est à peu près ça. statement_if ne permet pas spécifquement de "contenir d'autre tests conditionnels". Il peut contenir n'importe quel code, y compris des mélanges de boucles, conditions, calculs, I/O, etc.
Citer : Posté le 27/06/2020 17:11 | #
Yep okay…
Du coup j'implémente cette grammaire (enfin… j'essaye ) et je reviens
Ajouté le 27/06/2020 à 17:37 :
J'ai déjà quelques problèmes… je ne gère pas les conditions correctement… par exemple a est égal à b n'est pas détecté comme une condition… xD
Ajouté le 27/06/2020 à 17:48 :
Alors !
J'ai ce résultat (qui m'a l'air bon ) :
--- Tokens ---
('IF', 'si')
('VAR', 'a')
('EGA', 'est égal à')
('VAR', 'b')
('THEN', 'alors')
('VAR', 'a')
('TAKE', 'prend la valeur')
('VAR', 'b')
('MINUS', '-')
('NUM', 1)
--- AST ---
Statement : if
Comparison : EGA
Variable : a
Variable : b
Assignement :
Variable : a
Operation : +
Variable : b
Operation : -
Number : 1
Avec ce code :
def condition(self): return self.condition_or()
def condition_or(self):
elmnt_1 = self.condition_and()
if self.token_ahead.type != "OR": return elmnt_1
self.expect()
elmnt_2 = self.condition_and()
return Node("Condition", "OR", elmnt_1, elmnt_2)
def condition_and(self):
elmnt_1 = self.comparison_1()
if self.token_ahead.type != "AND": return elmnt_1
self.expect()
elmnt_2 = self.comparison_1()
return Node("Condition", "AND", elmnt_1, elmnt_2)
def comparison_1(self):
elmnt_1 = self.comparison_2()
if self.token_ahead.type not in ("EGA", "DIF"): return elmnt_1
comp = self.expect()
elmnt_2 = self.comparison_2()
return Node("Comparison", comp.type, elmnt_1, elmnt_2)
def comparison_2(self):
elmnt_1 = self.expr()
if self.token_ahead.type not in ("SUP", "SUP_EGA", "INF", "INF_EGA"): return elmnt_1
comp = self.expect()
elmnt_2 = self.expr()
return Node("Comparison", comp.type, elmnt_1, elmnt_2)
# --- Statements's rules --- #
def block(self): return self.statement()
def statement(self):
return self.assignement()
def assignement(self):
value = None
if self.token_ahead.type == "AFFECT":
self.expect()
var = self.expect("VAR")
self.expect("VALUE")
value = self.expr()
elif self.token_ahead.type == "REQUEST":
self.expect()
var = self.expect("VAR")
if self.token_ahead.type == "USER": self.expect()
elif self.token_ahead.type == "VAR":
var = self.expect()
self.expect("TAKE")
value = self.expr()
if value: return Node("Assignement","", Node("Variable",var.value), value)
else: return Node("User's request", "", Node("Variable", var.value))
def statement_if(self):
self.expect("IF")
cond_1 = self.condition()
self.expect("THEN")
block_1 = self.block()
ast = Node("Statement", "if", cond_1, block_1)
while self.token_ahead.type in ("ELIF", "ELSE"):
type_if = self.expect()
if type_if.type == "ELIF":
cond_2 = self.condition()
self.expect("THEN")
block_2 = self.block()
ast.add_node(Node("Statement", "elif", cond_2, block_2))
else:
block_2 = self.block()
ast.add_node(Node("Statement", "else", block_2))
return ast
J'aurais deux-trois questions :
- Comment détecter la présence d'un statement ? (pour block)
- Comment différencier les statements ? (pour statement)
- Est-ce que si, alors, sinon, si, sinon (pour les tokens respectifs : IF THEN ELIF ELSE c'est bon où est-ce qu'il faut prévoir plus de cas ?)
- Comment on fait pour connaitre la portée des tests conditionnels ? (et par la même occasion, on peut élargir aux autres boucles, voire même aux déclarations de fonctions )
Citer : Posté le 27/06/2020 17:58 | #
- Comment différencier les statements ? (pour statement)
Avec le lookahead.
Ça c'est toi qui vois ! N'oublie pas que tu as aussi besoin d'une forme de END ou ENDIF sinon tu ne pourras pas parser (la grammaire sera ambigue).
Quelle portée ?
Citer : Posté le 27/06/2020 18:03 | #
Ah ben oui… x)
C'est possible de rajouter des règles par la suite… je pense faire une version light avec moins de cas gérés. Justement la portée c'est l'absence de END qui m'a coincé xDD mais d'un autre côté je dois le mettre comment ? Tout à la fin de statement_if je rajoute une ligne : self.expect("END") ?
Citer : Posté le 27/06/2020 18:05 | #
Voilà c'est ça ! Facile
Citer : Posté le 27/06/2020 18:11 | #
Okay okay… du coup j'ai ce code
def block(self):
ast = Node("Block", "")
while self.oken_ahead.type in ("AFFECT," "REQUEST", "VAR", "IF"): ast.add_node(self.statement())
return ast
def statement(self):
if self.token_ahead.type in ("AFFECT", "REQUEST", "VAR"): return self.assignement()
elif self.token_ahead.type == "IF": return self.statement_if()
def assignement(self):
value = None
if self.token_ahead.type == "AFFECT":
self.expect()
var = self.expect("VAR")
self.expect("VALUE")
value = self.expr()
elif self.token_ahead.type == "REQUEST":
self.expect()
var = self.expect("VAR")
if self.token_ahead.type == "USER": self.expect()
elif self.token_ahead.type == "VAR":
var = self.expect()
self.expect("TAKE")
value = self.expr()
if value: return Node("Assignement","", Node("Variable",var.value), value)
else: return Node("User's request", "", Node("Variable", var.value))
def statement_if(self):
self.expect("IF")
cond_1 = self.condition()
self.expect("THEN")
block_1 = self.block()
ast = Node("Statement", "if", cond_1, block_1)
while self.token_ahead.type in ("ELIF", "ELSE"):
type_if = self.expect()
if type_if.type == "ELIF":
cond_2 = self.condition()
self.expect("THEN")
block_2 = self.block()
ast.add_node(Node("Statement", "elif", cond_2, block_2))
else:
block_2 = self.block()
ast.add_node(Node("Statement", "else", block_2))
self.expect("END")
return ast
Ajouté le 27/06/2020 à 18:19 :
Du coup je vais passer aux boucles itératives
J'ai pensé à cette grammaire : statement_for -> FOR VAR INTER_ST (VAR | NUM) INTER_ED (VAR | NUM) block END
Citer : Posté le 27/06/2020 18:29 | #
Ça a l'air pas mal, tu n'auras pas d'ambiguités.
Citer : Posté le 27/06/2020 18:30 | #
Ok, merci
Ajouté le 28/06/2020 à 14:10 :
J'ai codé les boucles itératives… j'ai ce résultat :
--- Tokens ---
('FOR', 'pour')
('VAR', 'i')
('INTER_ST', 'allant de')
('NUM', 0)
('INTER_ED', 'à')
('NUM', 3)
('COMMA', ',')
('VAR', 'a')
('TAKE', 'prend la valeur')
('VAR', 'i')
('EXP', '^')
('NUM', 2)
('END', 'fin')
('FOR', 'pour')
--- AST ---
Statement : for
Incremented variable : i
Start value : 0
End value : 3
Block :
Assignement :
Variable : a
Operation : ^
Variable : i
Number : 2
Avec ce code :
def block(self):
ast = Node("Block", "")
while self.token_ahead.type in ("AFFECT," "REQUEST", "VAR", "IF", "FOR"): ast.add_node(self.statement())
return ast
def statement(self):
if self.token_ahead.type in ("AFFECT", "REQUEST", "VAR"): return self.assignement()
elif self.token_ahead.type == "IF": return self.statement_if()
elif self.token_ahead.type == "FOR": return statement_for()
def assignement(self):
value = None
if self.token_ahead.type == "AFFECT":
self.expect()
var = self.expect("VAR")
self.expect("VALUE")
value = self.expr()
elif self.token_ahead.type == "REQUEST":
self.expect()
var = self.expect("VAR")
if self.token_ahead.type == "USER": self.expect()
elif self.token_ahead.type == "VAR":
var = self.expect()
self.expect("TAKE")
value = self.expr()
if value: return Node("Assignement","", Node("Variable",var.value), value)
else: return Node("User's request", "", Node("Variable", var.value))
def statement_if(self):
self.expect("IF")
cond_1 = self.condition()
self.expect("THEN", "COMMA", "DO")
block_1 = self.block()
ast = Node("Statement", "if", cond_1, block_1)
while self.token_ahead.type in ("ELIF", "ELSE"):
type_if = self.expect()
if type_if.type == "ELIF":
cond_2 = self.condition()
self.expect("THEN", "COMMA", "DO")
block_2 = self.block()
ast.add_node(Node("Statement", "elif", cond_2, block_2))
else:
block_2 = self.block()
ast.add_node(Node("Statement", "else", block_2))
self.expect("END")
return ast
def statement_for(self):
self.expect("FOR")
it_var = self.expect("VAR")
self.expect("INTER_ST")
start_value = self.expr()
self.expect("INTER_ED")
end_value = self.expr()
self.expect("COMMA", "DO")
ast = Node("Statement", "for", Node("Incremented variable", it_var.value), Node("Start value", start_value.value), Node("End value", end_value.value))
ast.add_node(self.block())
self.expect("END")
return ast
Je sais pas trop comment faire l'AST pour avoir les infos sur la variable incrémentée… ? Ça me parait bancal de faire des nodes pour ça…
Edit : Autre problème que j'ai pas encore soulevé : les lignes ? Pour l'instant quand je suis sur une seule ligne c'est bon, mais à aucun moment les lignes ne sont gérées… x)
Citer : Posté le 28/06/2020 14:39 | #
Quelles infos tu veux ? Tu as le nom, ça suffit non ?
Il suffit d'ignorer les \n dans le lexer. Si tu veux les compter il faut s'investir un peu plus.
Citer : Posté le 28/06/2020 14:45 | #
Le nom ne suffit pas, les bornes sont utiles aussi
J'ai rien dit pour les lignes ça marche effectivement… xD
Du coup j'ai une question avant de faire les while
- Les blocks sont des blocs de statement, en sortie j'ai ça comme AST :
Assignement :
Variable : a
Operation : +
Variable : a
Number : 1
Assignement :
Variable : i
Operation : *
Variable : a
Operation : 1/
Number : 2
Est-ce que c'est bon ?
Merci d'avance
Ajouté le 28/06/2020 à 15:11 :
Bon… j'ai commencé à faire des trucs de plus en plus sérieux, j'arrive maintenant à faire passer des codes avec plusieurs instructions :
--- Tokens ---
('WHILE', 'tant que')
('VAR', 'a')
('INF', 'est inférieur à')
('NUM', 5)
('COMMA', ',')
('VAR', 'a')
('TAKE', 'prend la valeur')
('VAR', 'a')
('PLUS', '+')
('NUM', 1)
('DISPLAY', 'afficher')
('VAR', 'a')
('END_WHILE', 'fin tantque')
--- AST ---
Statement : while
Comparison : INF
Variable : a
Number : 5
Block :
Assignement :
Variable : a
Operation : +
Variable : a
Number : 1
Display : a
Ajouté le 28/06/2020 à 18:45 :
J'ai quelques problème :
- Comment amorcer le parser ? (j'appelle quelle règle ? J'ai pensé à block, mais la création d'un node qui ne sert à rien est dérangeant…)
- Comment gérer l'affichage proprement ? (Est-ce qu'il faut faire une règle de grammaire en plus ?)
- Où en est au niveau de l'analyse syntaxique ? (Et si on approche de la fin, à quoi sert l'analyse sémantique ? )
Citer : Posté le 28/06/2020 18:52 | #
Oui pour l'instant c'est block. Il n'y aucun noeud qui ne serve à rien dans l'histoire !
L'affichage de quoi ?
On arrive à la fin pour avoir une base sympa. L'analyse sémantique sert principalement à typer, histoire de rejeter les constructions stupides comme 2+f quand f est une fonction.
Citer : Posté le 28/06/2020 18:54 | #
Ah ok, donc l'AST :
Statement : while
Comparison : INF
Variable : a
Number : 5
Block :
Assignement :
Variable : a
Operation : +
Variable : a
Number : 1
Display : a
le nœud Block :… ? Fin je pensais qu'on pouvais en enlever un
Je pense à l'affichage du texte, Afficher "A = ", a
Okay ! Ça avance !