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 18/06/2020 11:38 | #
Eh ben désolé de te décevoir mais c'est la représentation correcte et non-redondante.
Et f((a+b)*2) ça donne quoi ?
Citer : Posté le 18/06/2020 11:49 | #
C'pas bien d'essayer de me piéger
--- Tokens ---
('VAR', 'f')
('LPAR', '(')
('LPAR', '(')
('VAR', 'a')
('PLUS', '+')
('VAR', 'b')
('RPAR', ')')
('MULTI', '*')
('NUM', 2)
('RPAR', ')')
--- AST ---
Function : f
Parameter : #1
Operation : *
Operation : +
Variable : a
Variable : b
Number : 2
Citer : Posté le 18/06/2020 12:01 | #
Avec le code que t'as posté tout à l'heure ? o_o
Citer : Posté le 18/06/2020 12:02 | #
J'ai inversé le if et le else entre temps
Edit :
J'ai ce code actuellement
param = list()
while self.token_ahead.type != "RPAR":
if self.token_ahead in ("COMMA", "RPAR"): self.expect(["COMMA", "RPAR"])
else:
param.append(Node("Parameter", "#{}".format(len(param)+1), self.expr()))
return param
Citer : Posté le 18/06/2020 12:53 | #
Ok, avec le code de tout à l'heure ça serait pas passé.
Tu as remarqué que tu acceptes f(x,,y) et que tu ne lis pas la parenthèse fermante ?
Citer : Posté le 18/06/2020 13:15 | #
J'ai pas compris pourquoi je ne lit pas la parenthèse fermante… ?
Et oui j'accepte f(x,,y) mais je ne suis pas sûr… Est-ce que je suis obligé de faire remonter une erreur de syntaxe ou est-ce que l'on peut considérer que c'est une faute de frappe ? C'est pas très propre ?
Citer : Posté le 18/06/2020 13:36 | #
personnellement, je préfère toujours qu'un compilo me retourne une erreur plutot qu'il essaye de comprendre ce que j'ai voulu faire
si tu prend exemple sur js, le fait qu'il autorise "5"+ 3 par exemple pose plein de pb...
notament
"5" - 3 = 2 mais "5" + 3 = "53"
Sell-me
Pixel
Html Intrepreter
Venez me rejoindre sur mon nouveau serveur Discord dédié a la programmation sur toutes les plateformes
https://discord.gg/bzfymHQ
Venez vous instruire, dans ce magnifique cours sur les Intelligences Artificielles que j'ai créé:
http://reseaux-neurones-a--z.ml/
Faites apprendre des choses à une machine, faites reconnaître à un ordi des images...
Citer : Posté le 18/06/2020 13:39 | #
J'ai pas compris pourquoi je ne lit pas la parenthèse fermante… ?
Car tu n'appelles pas self.expect() pour récupérer le token de parenthèse fermante. Tu comprends bien que ton if n'attrape que les virgules car si le prochain token est une parenthèse fermante, tu auras quitté la boucle avant même d'atteindre le if !
Ça devrait être une erreur de syntaxe. Ta boucle devrait, en principe, garantir que les virgules et les expressions sont alternées. On peut par exemple procéder comme ceci :
param = list()
while self.token_ahead.type != "RPAR":
param.append(self.expr())
if self.token_ahead.type == "RPAR":
break
self.expect(["COMMA"])
self.expect(["RPAR"])
return param
Ici chaque tour de boucle lit une expression et une virgule. On autorise uniquement une virgule avant la tout dernière parenthèse fermante, ce qui est classique et permet d'écrire des appels sur plusieurs lignes dans les langages impératifs, comme ça :
paramètre_1,
gros_appel(x, y, z, t) * produit_tensoriel(espace_bizarre),
inversion_paranormale_métrique(U),
)
Tu vois l'idée je pense.
"5" - 3 = 2 mais "5" + 3 = "53"
Ça ça relève plutôt du typage et de l'analyse sémantique... mais c'est un bon exemple ! Effectivement, un compilateur doit le plus strict possible pour éviter les malentendus. Si le langage n'est pas rigoureux les programmes écrits avec ne le seront pas non plus !
Citer : Posté le 18/06/2020 18:13 | #
Okay… Je retouche ça ! Merci
Ajouté le 19/06/2020 à 09:44 :
Du coup j'ai ce code pour les appels de fonctions :
param = list()
while self.token_ahead.type != "RPAR":
param.append(Node("Parameter", "#{}".format(len(param)+1), self.expr()))
if self.token_ahead.type == "RPAR":
break
self.expect(["COMMA"])
self.expect(["RPAR"])
return param
Ce qui me donne des AST (ou CST… ?) comme ça :
--- Tokens ---
('VAR', 'f')
('LPAR', '(')
('LPAR', '(')
('VAR', 'a')
('PLUS', '+')
('NUM', 2)
('RPAR', ')')
('COMMA', ',')
('NUM', 12)
('RPAR', ')')
--- AST ---
Function : f
Parameter : #1
Operation : +
Variable : a
Number : 2
Parameter : #2
Number : 12
Du coup là on en a terminé avec les expressions mathématiques ? Tu avais parlé du plus unaire, mais que c'était optionnel… C'est important ? Et quels avantages / inconvénient pour la suite ?
Citer : Posté le 19/06/2020 10:30 | #
C'est pas mal ! J'en ai pas parlé tout à l'heure, mais ton noeud "Parameter" ne... joue aucun rôle. De la même façon que les arguments de + sont juste mis ensemble dans une liste, les arguments de la fonction peuvent être mis ensemble dans une liste. Il n'y a pas besoin d'avoir un noeud spécifique. D'ailleurs ça se voit : ton noeud "Parameter" ne "calcule" rien, contrairement à tous les autres.
Citer : Posté le 19/06/2020 10:31 | #
C'est juste je l'enlève
Citer : Posté le 19/06/2020 10:56 | #
Le plus unaire on s'en fout, c'est pour la décoration et l'exhaustivité. Je pense qu'il y a d'autres priorités maintenant !
Citer : Posté le 19/06/2020 10:59 | #
Les conditions et comparaisons ?
Citer : Posté le 19/06/2020 11:16 | #
Par exemple ouais. Mais n'oublie pas d'écrire la grammaire au propre avant de coder ! Je sais que ça peut être chiant, mais c'est là que tu vois vraiment ce que tu fais !
Citer : Posté le 19/06/2020 11:24 | #
Ouaip
Je pensais à un truc comme ça :
condition -> comparaison | (comparaison LOGI comparaison)*
Je me dit qu'il faut que je sépare les tokens COMP en type de comparaison, du coup j'aurais une flotte de méthode pour les comparaisons comme pour somme -> produit -> puissance et la règle comparaison serait l'équivalent de la méthode expr pour les opérations ^^. Du coup ça ferais une grammaire plus comme ça :
comp_sup_ega -> comp_inf >= comp_inf
comp_inf -> comp inf_ega < comp_inf_ega
comp_inf_ega -> comp_ega <= comp_ega
comp_ega -> comp_dif == comp_dif
comp_dif -> atome != atome
comparaison -> comp_sup
condition -> comparaison | (comparaison LOGI comparaison)*
Déjà voir si je suis pas partit n'importe où et ensuite voir pour détailler les opérations logiques avant de coder des comparaisons
Citer : Posté le 19/06/2020 11:33 | #
Tu as recommencé ! Tu as été coller des atomes là où tu attends des expressions arithmétiques ! On a pourtant défini expr spécifiquement pour éviter ce problème !
Oui, mais tu peux utiliser la même méthode pour >, ≥, < et ≤ qui ont la même priorité (traditionnellement), par contre il t'en faudra une autre pour = et ≠ qui sont moins prioritaires (traditionnellement). Tu peux voir ces deux groupes comme addition/soustraction et multiplication/division.
Et n'oublie pas que tes opérateurs logiques ont aussi une priorité, c'est le NOT, puis le AND, puis le OR, donc il te faut trois règles pour gérer ces choses-là
Citer : Posté le 19/06/2020 11:40 | #
Oui, j'avais oublié que expr est le plus général xD du coup ma grammaire pour les comparaisons c'est juste :
comp_dif -> comp_ega DIF comp_ega
comp_ega -> expr EGA expr
?
Citer : Posté le 19/06/2020 12:09 | #
Plusieurs choses... d'abord là tu ne tiens pas compte de l'associativité (mais c'est pas un problème). Ensuite la priorité de comp_dif et comp_ega devrait être dans l'autre sens (on s'autorise à comparer des booléens pour l'égalité donc on veut pouvoir écrire Si x≤2 == y>3). Enfin on peut aussi comparer des expressions pour l'égalité donc tu devrais avoir un moyen de créer un comp_ega avec juste une expression.
Citer : Posté le 19/06/2020 12:13 | #
Là j'ai plus de mal à suivre… si j'ai bien compris, ma grammaire devient ça ?
comp_dif -> general_comp DIF general_comp
general_comp -> expr (SUP | SUP_EGA | INF | INF_EGA) expr
Citer : Posté le 19/06/2020 12:19 | #
Plutôt quelque chose comme ça.
comp_ega -> general_comp | expr (EGA | DIF) expr | general_comp (EGA | DIF) general_comp
# Les opérateurs logiques utilisent ensuite comp_ega comme un atome
Citer : Posté le 19/06/2020 13:00 | #
Ah ok !
Donc pour les opérateurs logiques j'ai :
and -> not AND not
not -> comp_ega | NOT comp_ega
c'est ça ?
Pour la méthode general_comp j'ai ce code :
elmnt_1 = self.expr()
comp = self.expect("SUP", "SUP_EGA", "INF", "INF_EGA")
elmnt_2 = self.expr()
return Node("Comparison", comp.type, elmnt_1, elmnt_2)
Edit : Finalement je me demande si c'est pas plus pratique d'avoir comme règle :
general_comp -> expr | expr (SUP | SUP_EGA | INF | INF_EGA) expr ? Comme ça, je peux simplifier la règle comp_ega en
comp_ega -> general_comp | general_comp (EGA | DIF) general_comp ?