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 01/06/2020 13:30 | #
Du coup je peux faire un truc comme :
def __init__(self):
self.l_token = TokenList()
self.token_ahead = Token()
et je déclare en global : gl_var = GlobalVar() ?
Citer : Posté le 01/06/2020 13:32 | #
Plutôt ça.
def __init__(self, l_token):
self.l_token = l_token
self.token_ahead = l_token.get_next_token()
def expect(self):
# use self.l_token and self.token_ahead
def atome(self):
return self.expect([ "NUM", "VAR" ])
Citer : Posté le 01/06/2020 13:34 | #
Ah ok… je commence à voir le truc… Je retouche mon code
Ajouté le 01/06/2020 à 13:40 :
J'ai terminé le code est poussé sur le git
Il est pas commenté, mais les différentes parties du code sont bien séparée
Ajouté le 01/06/2020 à 16:05 :
J'ai réussi à faire une somme ! Il détecte les variables et les chiffres… Ce n'est pas encore récursif, mais la génération de l'AST fonctionne le programme affiche les tokens et l'AST. Pour le dernier, les différents niveaux sont symbolisés par des alinéas.
J'ai mis à jour le git : https://gitea.planet-casio.com/Shadow/Compylateur
Citer : Posté le 01/06/2020 16:10 | #
Parfait ! T'es bien parti là. Faudra penser à créer ta classe d'AST à un moment car tu en auras besoin assez vite
Citer : Posté le 01/06/2020 16:11 | #
Elle est déjà faite
Avec une classe Branch pour ajouter des branches dans l'idée l'AST accepte des branches de type Branch qui peuvent être récursives
Edit : Le code de l'AST :
class AST():
def __init__(self):
self.branch = list()
def add_branch(self, branch):
self.branch.append(branch)
class Branch():
def __init__(self, title, value, *sub_branch):
self.title = title
self.value = value
self.sub_branch = list(sub_branch)
def add_sub_branch(self, *sub_branch):
for i in sub_branch: self.sub_branch.append(i)
def gen(self):
return self.title, self.value, self.sub_branch
def AST_gen(branch, tab = 0):
for i in branch:
print(tab * " " + "{0} : {1}".format(i.gen()[0], i.gen()[1]))
if i.gen()[2]: AST_gen(i.gen()[2], tab + 1)
Ajouté le 01/06/2020 à 16:21 :
J'ai juste un petit problème, token_ahead correspond au token regardé, mais j'ai pas de token d'avance ^^'
Citer : Posté le 01/06/2020 16:27 | #
Ton alternance AST/Branche ne, euh... ne sert à rien. Voilà comment on fait normalement :
def __init__(self, children = []):
self.children = children
def add_child(self, child):
self.children.append(child)
class PlusNode(ASTNode):
def __init__(self, children = []):
ASTNode.__init__(self, children)
def __str__(self):
return "Plus({})".format(",".join(str(c) for c in self.children)))
etc, avec une classe par type de noeud qui va bien (ou alors tu ajoutes juste à ASTNode un nom de noeud si tu préfères faire comme ça).
C'est le prochain token qui sortira quand tu appelleras expect(). Donc c'est bon.
Citer : Posté le 01/06/2020 16:33 | #
Justement, non x) ça marchais pas, au niveau de "l'armoçage" du parser j'avais un token_ahead sur VAR | NUM mais avec expect qui me renvoyais un None parce que je n'ai rien avant… Du coup j'ai du décalé pour ne plus avoir de problème, donc token_ahead est le token en cours de lecture et expect sert juste à lire le token et à vérifier que c'est le bon type…
Je comprends pas le code de l'AST… dans l'absolu, c'est grave qu'il soit comme ça le mien ? Ou est-ce que ça va gêner plus tard ?
C'est pas une alternance, dans mon code AST est le 'tronc' de l'arbre mais toutes les branches sont de type Branch, et dans les branches, je ne reviens pas à un autre AST.
Citer : Posté le 01/06/2020 17:30 | #
Ah donc en fait ton AST c'est un objet AST qui contient des Branch qui contient des Branch et ainsi de suite ? Dans ce cas tu n'as pas besoin de l'objet AST tout en haut.
Citer : Posté le 01/06/2020 17:33 | #
Yup, surtout que techniquement c'est pas des branches mais des nodes. Une branche ça connecte 2 nodes.
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 01/06/2020 17:37 | #
Ouaip c'est ça
L'objet AST contient une liste de Branch qui elles sont récursives. Plusieurs Branch peuvent partir de l'AST.
J'ai pas trop regardé la théorie… j'ai fait ça au pif… x) Le coup des nodes, ça me parle pas du tout ^^'
Citer : Posté le 01/06/2020 17:41 | #
Ben un "node" c'est une "Branch" dans ton code. On appelle ça un noeud en référence au noeuds des arbres desquels partent les branches. Donc normalement pour coder un arbre tu n'as qu'un seul type d'éléments, qui contient des enfants du même type. C'est tout.
Citer : Posté le 01/06/2020 17:41 | #
Les nodes c'est les cercles, les branches c'est les traits. Si ton objet Branch a une info autre que "node x est relié à node y" c'est pas une branche
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 01/06/2020 17:44 | #
Ouaip… en gros mon code est foutu comme ça :
AST
- Branch #1 : titre, valeur
- Branch #1 : titre, valeur
- Branch #2 : titre, valeur
- Branch #2 : titre, valeur
- Branch #1 : titre, valeur
- Branch #2 : titre, valeur
Citer : Posté le 01/06/2020 17:51 | #
Voilà, mais ça sert à rien d'avoir le AST en haut. Tu peux avoir ça :
- type = "program"
- children = [
Node:
- type = "statement"
- children = [
Node:
- type = "variable"
- name = "a"
- children = []
Node:
- type = "int"
- value = 42
- children = []
]
]
Citer : Posté le 01/06/2020 18:09 | #
Ouaip… c'est juste… là j'en ai un peu marre, mais du coup je vais voir pour l'enlever
Ajouté le 01/06/2020 à 19:17 :
Au sujet du parser, j'ai retouché le code : token_ahead est devenu : token_watch et expect est devenu read. Ça colle mieux à la réalité…
Pour supprimer l'objet AST, je ferais ça demain je vois grosso-modo comment faire : rajouter une branche type = programme qui englobe tout vu que c'est récursif ça ne posera aucun problème…
Citer : Posté le 01/06/2020 20:00 | #
Au sujet du parser, j'ai retouché le code : token_ahead est devenu : token_watch et expect est devenu read. Ça colle mieux à la réalité…
Comme tu veux, mais sache que les conventions c'est bien lookahead et expect
Exactement ! Et cette branche sera produite par la fonction associée au symbole d'entrée de ta grammaire.
Citer : Posté le 01/06/2020 20:17 | #
Rhôoo les conventions… J'en respecte aucune sur pas mal de plans, je vais pas commencer… Plus sérieusement, j'ai pas trouver comment amorcer le parser autrement doit y avoir une subtilité qui m'a échappée… ?
Citer : Posté le 01/06/2020 20:19 | #
Aucune subtilité ne t'a échappé, c'est juste des noms. Toute la méthode est juste jusque-là.
Le symbole d'entrée c'est celui qui représente un programme entier. J'en ai donné un exemple tout à l'heure (ici).
Citer : Posté le 01/06/2020 20:23 | #
Ah ok… mais comment je peux amorcer le parser pour le premier token ? Si je respecte les… conventions… je contrôle le token suivant et je renvoie le type du token précédent…mais quand je contrôle le premier token je renvoie un token qui… n'existe pas… Avec ma méthode, le token renvoyer est celui qui est analysé… du coup j'ai pas de dé-phasage et pas d'erreur…
Citer : Posté le 01/06/2020 20:29 | #
Tu dois charger le premier token dans le lookahead dès la création du parser, c'est tout (?)
Citer : Posté le 02/06/2020 08:41 | #
Aha c'est ça que j'ai pas
Pour l'AST, j'ai effectivement confondu les Nodes et les Branchs, je corrige ça en même temps que je supprime l'objet inutile !
Du coup je ré-essaye de repartir sur un truc plus académique avec token_ahead et expect
Ajouté le 02/06/2020 à 09:07 :
J'ai compris le problème de token_ahead et expect… je vais essayer d'expliquer, mais je promet rien xD
En gros mes tokens c'est ça :
+-----+ +-----+ +-----+
X | VAR | | + | | VAR |
+-----+ +-----+ +-----+
Avec X le token_ahead au tout début, avant le parser.
- Initialisation du parser, token_ahead regarde VAR et renvoie rien.
- atome_1 = parser.atome() là on a un problème token_ahead regarde + et renvoie une erreur syntaxe car il attend VAR.
- Arrêt de l'exécution
Si je n'initialise par token_ahead :
- atome_1 = parser.atome() : token_ahead passe sur VAR, tout va bien c'est ce que l'on attend, mais cela renvoie le token précédent, soit un magnifique… None.
- la valeur de atome_1 ne veut plus rien dire, et même si je n'ai pas d'erreurs par la suite, c'est complètement inexploitable…