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 11/05/2020 08:52 | #
Ah ok du coup la méthode : TOKEN(classe_du_token, valeur) est bien… ? Imaginons que le lexer me renvoie ça :
TOKEN(MOT, 'a')
TOKEN(VERBE, 'est')
TOKEN(COMP, 'supérieur')
TOKEN(PREP, 'à')
TOKEN(CHIFFRE, '12')
Ce serait bien ? Et si dans le code il se base sur le fait d'avoir un TOKEN(COM, 'if') pour savoir que a est un mot et non un verbe, c'est bon… ?
Citer : Posté le 11/05/2020 08:58 | #
Ton flux de tokens a une bonne tête oui. Par contre :
Non, surtout pas ! Le lexer doit traiter les mots indépendamment du contexte. Dans cette situation, tu devrais plutôt laisser MOT "a" (← cette notation est plus courte donc si tu permets, je vais écrire comme ça) et laisser le parser décider dans le contexte si c'est un verbe ou un nom de variable. En particulier, après un COM "if" le parser demandera un nom de variable donc tu pourras automatique faire une "promotion".
Citer : Posté le 11/05/2020 09:01 | #
Okay, du coup les tokens MOT vont être un peu fourre-tout ?
Citer : Posté le 11/05/2020 09:03 | #
Oui, dans ton cas il y a pas mal d'ambiguité donc le parser aura pas mal de boulot.
Citer : Posté le 11/05/2020 09:06 | #
Okay !
Pour le lexer je pensais faire des grandes listes : verbe = ["prend", "demander", "est"…] c'est bien ? (le système de liste est aussi valable pour les perposition, les commandes, les comparaison… )
Citer : Posté le 11/05/2020 09:26 | #
Tu peux, mais il faut utiliser des ensembles : verbe = { "prend", "demander", "est" ... } si tu veux pas faire exploser la complexité temporelle du truc.
Citer : Posté le 11/05/2020 09:31 | #
Des ensembles ? Je connais pas…
Pour l'instant j'ai ce code qui marche un peu :
def __init__ (self, classe, valeur):
self.classe = classe
self.valeur = valeur
def gen (self):
return "TOKEN({0}, '{1}')".format(self.classe, self.valeur)
class Token_list ():
def __init__ (self):
self.liste = list()
def add (self, token):
self.liste.append (token)
def gen (self):
for i in self.liste:print(i.gen())
def lexer(prgm_src):
verbe = ["est", "prend", "demander", "saisir", "allant"]
prep = ["à", "la", "le", "que", "de", "ou", "entre", "plus", "moins"]
com = ["si", "alors", "sinon", "pour", "tant"]
comp = ["supérieur", "inférieur", "égal", "différent", "grand", "petit"]
detect = [verbe, prep, com, comp]
mot = prgm_src.split(" ")
l_token = Token_list()
for i in mot:
token = str()
for j in range(len(detect)):
if i.lower() in detect[j]:token = ["VERBE", "PREP", "COM", "COMP"][j]
if i.isdigit(): token = "CHIFFRE"
elif not len(token):token = "MOT"
l_token.add(Token(token, i))
return l_token.gen()
Ajouté le 11/05/2020 à 09:50 :
C'est quoi l'intérêt des ensembles ici ?
Citer : Posté le 11/05/2020 09:51 | #
Un ensemble c'est similaire à une liste mais :
• Ce n'est pas ordonné (il n'y a pas d'élément numéro 1/2/3, il n'y a pas de positions), c'est juste un gros sac d'éléments
• Tester si une valeur est dans l'ensemble (avec in) est beaucoup, beaucoup plus rapide qu'avec une liste, surtout quand l'ensemble contient beaucoup d'éléments
Citer : Posté le 11/05/2020 09:51 | #
Ah ok Merci !
Ajouté le 11/05/2020 à 09:56 :
Pour le moment je teste juste des petits trucs basiques avec le lexer Pour l'instant le vocabulaire lui fait un peu défaut
Mais avec le code source : Si a est supérieur ou égal à 15 j'obtiens :
(MOT, 'a')
(VERBE, 'est')
(COMP, 'supérieur')
(LOGI, 'ou')
(COMP, 'égal')
(PREP, 'à')
(CHIFFRE, '15')
Citer : Posté le 11/05/2020 10:01 | #
Voilà, ça c'est une bonne sortie de lexer
Citer : Posté le 11/05/2020 10:03 | #
Okay, niquel Prochaine étape : je prend mon bouquin de math et je regarde tous les programmes en langages naturel pour noter le max de mots possible
(je gère même plus petit que au niveau du lexer )
Ajouté le 12/05/2020 à 09:34 :
J'ai simplifié le code du lexer Je pense continuer à ajouter des mots et optimiser le code encore un peu et commencer le parser du coup…
J'ai du coup quelques questions…
- Quelle sortie on attend d'un parser (le lexer renvoie une liste, mais le parser ?)
- Les tokens vont être analysés par groupe… ? si les tokens ne matchent avec aucun pattern, je fait comment ? Je renvoie une erreur en mode : ERREUR : ARRÊTEZ DE CODER AVEC LES PIEDS NOM DE DIEU !!
Citer : Posté le 12/05/2020 09:38 | #
Un parseur renvoie un AST.
Si les tokens ne matchent avec aucun pattern, tu renvoies une erreur oui
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 12/05/2020 09:44 | #
Okay, merci !!
AST, c'est quoi ? Un Arbre de Syntaxe … ? Et concrètement au niveau du code faut-il créer un objet ou est-ce qu'il y a des alternatives déjà existante… ? (sans parler d'utiliser un parser déjà fait )
Citer : Posté le 12/05/2020 09:55 | #
Doit sûrement y avoir des trucs déjà existants mais tu peux facilement créer le tien (si ton but est d'apprendre et non de sortir la feature aussi vite que possible )
Perso j'avais utilisé un objet avec les propriétés :
- texte (nom de la fonction ou constante)
- args (les arguments de la fonction, s'il y en a)
- children (pour les structures)
- parent
- expectedType (pour les optimisations sur les casts)
- type
Après tu itères sur l'AST pour faire les optimisations (si besoin), et tu convertis l'AST en python ou autre.
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 12/05/2020 10:00 | #
Le parser renverra "erreur de syntaxe", avec un peu de chance tu pourras dire à quel ligne/colonne. Inutile d'essayer de corriger l'erreur et de continuer à parser. Si le programme est syntaxiquement invalide, c'est comme {[}] : il ne veut rien dire.
Pour le parser il faut que tu utilises un générateur de parser, et en gros tu auras ce genre de situations :
• Le parser va reconnaître un motif comme A B C D et il sait que ça donne un noeud d'AST que j'appelle X
• Dans la définition de la grammaire, tu pourras mettre du code pour dire ce qu'il se passe quand ce motif est reconnu
• Et dans ce code le parser te donnera A, B, C et D, et tu devras renvoyer X, une classe de ton choix
Par exemple dans yacc ça donnerait :
Citer : Posté le 12/05/2020 10:03 | #
Le but et d'apprendre comment se passe la compilation et de la comprendre ouaip !
Je conçois bien tes arguments, mais j'ai un peu de mal avec expectedType et type…
En lisant ça : https://en.wikipedia.org/wiki/Abstract_syntax_tree je pense simplifier un peu en gardant :
- le 'type' (ex. variable, constante)
- le 'paramètre' et son nom (ex. nom = 'a', valeur = 0)
- les enfants
- les parents
@Lephe : Okay, perso j'ai pas sauvé le numéro de la ligne, mais je peux changer ça Pour la reconnaissance de motifs ça sent le hard code x) En plus il faut gérer les promotions des tokens MOT
Citer : Posté le 12/05/2020 10:08 | #
La promotion des tokens MOT c'est juste une règle en plus, cf. #175962.
Citer : Posté le 12/05/2020 10:11 | #
Okay ! Merci !
Pour le lexer j'envisage d'ajouter un token NOM est-ce pertinent ?
Gérer avec le lexer les parenthèses, crochets et accolades… c'est pas très utile, en langage naturel c'est très peu répandu de mettre des parenthèses… non ?
Citer : Posté le 12/05/2020 10:17 | #
Écris ta grammaire, tu verras si t'as besoin de NOM.
Les parenthèses c'est un exemple enfin. x)
Citer : Posté le 12/05/2020 10:20 | #
Le truc "expectedType" c'est plus un truc pour me simplifier la vie.
Si on a "A + B[0]" ça fera un AST :
{
"text": "_add",
"type": "
"args": [{
"text": "A",
"type": "int",
"expectedType": "numberOrVector",
},{
"text": "_valueInArray",
"type": "int",
"expectedType": "numberOrVector",
"parent": "_add",
"args": [{
"text": "B",
"type": "int[],
"parent": "_valueInArray",
},{
"text": "0",
"type": "numberLiteral",
"parent": "_valueInArray",
}]
}]
}
Ton AST peut être un peu différent, par exemple en pratique j'utilise une pseudo fonction "_number_".
Dans mon langage si on donne un array à une fonction qui n'accepte pas un array, le premier élément de cet array est pris. Ca fait que ça peut être optimisé en "A + B".
Du coup faut dire que quand tu optimises ton AST, si tu as la fonction "_valueInArray" et que la valeur est 0, et que le type de l'argument où est "_valueInArray" n'est pas un array, tu peux optimiser en donnant directement l'array. C'est là que "expectedType" intervient, s'il n'était pas là il faudrait faire un lookup "quel type est attendu sur la fonction _add et sur le 2ème argument". Il n'est pas obligatoire mais il simplifie la vie (et rend le truc plus rapide vu qu'il n'y a pas de lookup à faire).
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 12/05/2020 10:27 | #
Ouaip je vais commencer par écrire la grammaire c'est une bonne idée
Oui, mais je répondais pas à ton exemple c'était une réflexion plus générale sur l'utilité pour le lexer de gérer les parenthèse et co
Au niveau du langage naturel, j'ai un petit problème : les fonctions mathématiques, j'ai vu plusieurs algo où c'est juste marqué : f est une fonction mathématiques et après ils utilisent f(x) n'importe quel humain comprends que f(x) est une fonction quelconque… mais le compilo va pas aimer la blague Du coup ça fait partit des limites ou une alternative est possible ? (à la limite c'est pas non plus le plus pressé…)