Les membres ayant 30 points peuvent parler sur les canaux annonces, projets et hs du chat.
La shoutbox n'est pas chargée par défaut pour des raisons de performances. Cliquez pour charger.

Forum Casio - Projets de programmation


Index du Forum » Projets de programmation » Compylateur
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

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 !


Précédente 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ··· 18, 19, 20 Suivante
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 18/05/2020 16:00 | #


Ah ok, merci !

Ajouté le 27/05/2020 à 13:42 :
J'ai bien avancé le lexer… il me reste la gestion des intervalles qui n'est pas du tout implémentée Le code est à sa première version (depuis la refonte) j'implémente les intervalles et j'essaye d'optimiser tout ça…

Je ne met pas le code ici (trop gros) par contre il est dispo sur le git : https://gitea.planet-casio.com/Shadow/Compylateur/src/branch/master/Lexer.py

Un exemple de tokens générés : sur cette ligne Si a est égal à b ou que c est supérieur ou égal à a j'obtient :
('CMND', 'si')
('UNDEF', 'a')
('COMP', 'est égal à')
('UNDEF', 'b')
('LOGI', 'ou que')
('UNDEF', 'c')
('COMP', 'est supérieur ou égal à')
('UNDEF', 'a')


(là je met sur différentes lignes pour la lisibilité, en pratique c'est une liste )
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 27/05/2020 13:56 | #


Oh, pas mal du tout. Le "que" n'a pas grand-chose à faire dans ta phrase mais c'est stylé !
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 27/05/2020 13:57 | #


C'est plus français en fait c'est une autre proposition Et ça permet de différencier le et logique du séparateur

Ajouté le 27/05/2020 à 16:31 :
Le coup du que dans le token ça permet des affectations multiples

Le code suivant : a et b sont des entiers naturels me renvoie :
('UNDEF', 'a')
('SPTR', 'et')
('UNDEF', 'b')
('ASSI', 'sont')
('TYPE', 'des entiers naturels')


Ajouté le 28/05/2020 à 08:12 :
Le parser est très loin d'être au point, mais je commence à y réfléchir

Pour l'arbre je partirai plutôt vers un truc récursif avec une branche de départ AST object qui pourrait contenir des branches filles : Branch object

Chaque branche fille a plusieurs caractéristique : Titre, Valeur et Branches filles (de type Branch)… et ainsi de suite

Ça c'était pour le modèle de l'AST retourné par le parser. Avec le nouveau lexer, il faut que je refasse une grammaire complète… du coup je vais commencer à l'attaquer !
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 28/05/2020 08:56 | #


L'AST est forcément récursif, mais tu ne peux pas avoir que 2 niveaux (AST → Branche). Normalement chaque noeud de l'AST a un rôle (expression, boucle, assignment, etc) correspondant aux différents symboles de ta grammaire, et chaque noeud a des enfants qui sont aussi des noeuds d'AST.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 28/05/2020 09:02 | #


Ouaip j'était partit sur un modèle comme ça

J'ai essayé d'afficher l'AST avec des tabulations, sur un exemple simple j'ai ça :

title : command
value : while
    title : condition
    value : <
        title : var
        value : a
        title : num
        value : 5
    title : assign
    value : expr_to_var
        title : var
        value : a
        title : expr
        value : a - 1

Pour le code : while a < 5: a -= 1

Pour l'instant c'est théorique, j'ai rempli l'AST à la main mais dans l'idée c'est ça…
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 28/05/2020 09:05 | #


Pas mal ! Il manque une seule chose : le while devrait avoir en arguments une condition et un "bloc" (ie une *liste*) d'instructions, une seule ne peut pas suffire. Ton expr_to_var devrait probablement s'appeler "assign" mais comme tu préfères. a-1 devrait être un noeud de soustraction avec a d'un côté et 1 de l'autre, ou une addition avec a et -1, selon tes goûts. Pareil, les opérandes de < devraient être des expressions, contenant pour a une variable, et pour 5 un noeud d'entier constant.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 28/05/2020 09:08 | #


Okay… donc je peux subdiviser encore l'AST

Pour l'instant c'est encore très théorique, je ne vois pas encore dans quel sens je vais le remplir quand je remplis à la main, je commence par la fin et j'ajoute les sous-branches aux branches avant de relier les branches à l'arbre…
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 28/05/2020 09:11 | #


Shadow15510 a écrit :
quand je remplis à la main, je commence par la fin et j'ajoute les sous-branches aux branches avant de relier les branches à l'arbre…

Donc tu réfléchis comme un parser LR
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 28/05/2020 09:13 | #


Ah… ? Et c'est comme ça qu'il faut faire ? Ou ma méthode est inapplicable en conditions réelles ?
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 28/05/2020 09:18 | #


Eh bien... les parsers "bottom-up" comme les parsers LR appliquent les règles de grammaire de bas en haut : ils commencent par construire des petits groupes comme des variables ou des expressions, puis des choses plus grosses comme des boucles, et ainsi de suite jusqu'à avoir tout le programme.

Les parsers "top-down" comme les parsers LL font exactement le contraire, et décident dès qu'ils aperçoivent un token spécifique comme "Pour tout" qu'une boucle for doit avoir lieu, et s'intéressent ensuite seulement à trouver les expressions et les variables dedans.

Sans rentrer dans les détails, les deux ne permettent pas de parser les mêmes grammaires, mais la grammaire d'un langage de programmation comme le tien sera facile et tu pourras utiliser celui que tu veux.

Cependant, les parsers LR sont fait pour être générés automatiquement parce que c'est imbitable à écrire à la main, c'est des automates. Si tu veux écrire ton parser à la main, il est bien mieux de partir sur un parser LL.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 28/05/2020 09:21 | #


Okay, c'est vrai que le LL à l'air plus intuitif

Pour l'instant je viens de me rendre compte que j'ai un problème sur mon lexer… quand je fini par un token spécifique (ni un chiffre ni un UNDEF) j'ai une out of range x))
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Zezombye Hors ligne Rédacteur Points: 1756 Défis: 13 Message

Citer : Posté le 28/05/2020 09:24 | #


Yup, perso mon AST a une propriété "args" (un array d'AST) et "children" (un array d'AST aussi), les "children" sont les lignes appartenant à un AST (dans le cas d'un if, while, for, etc).
Divers jeux : Puissance 4 - Chariot Wars - Sokoban
Ecrivez vos programmes basic sur PC avec BIDE
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 29/05/2020 08:41 | #


Pour le lexer (encore et toujours ) j'ai commencé à faire des tests en "conditions réelles" c'est à dire avec des algos susceptible d'être compilés une fois Compylateur terminé

Pour ce code :
Saisir A
u prend la valeur 2
n prend la valeur 0
Tant que u<A
n prend la valeur n+1
u prend la valeur 3*n^2+2
Fin Tant que
Afficher n


J'ai la liste :
('USER', 'saisir')
('UNDEF', 'a')
('SPTR', '\n')
('UNDEF', 'u')
('ASSI', 'prend la valeur')
('NUM', '2')
('SPTR', '\n')
('UNDEF', 'n')
('ASSI', 'prend la valeur')
('NUM', '0')
('SPTR', '\n')
('CMND', 'tant que')
('UNDEF', 'u')
('COMP', '<')
('UNDEF', 'a')
('SPTR', '\n')
('UNDEF', 'n')
('ASSI', 'prend la valeur')
('UNDEF', 'n')
('OPTR', '+')
('NUM', '1')
('SPTR', '\n')
('UNDEF', 'u')
('ASSI', 'prend la valeur')
('NUM', '3')
('OPTR', '*')
('UNDEF', 'n')
('OPTR', '^')
('NUM', '2')
('OPTR', '+')
('NUM', '2')
('SPTR', '\n')
('CMND', 'fin tant que')
('SPTR', '\n')
('CMND', 'afficher')
('UNDEF', 'n')



Ajouté le 29/05/2020 à 09:24 :
Pour la grammaire j'ai trouvé un système sous forme de dictionnaire :
Les clefs sont le type du token (CMND, COMP…) et les items sont les valeurs acceptées. Par exemple pour un test conditionnel, j'aurait CMND comme type de token avec 'si' en valeur : test = {"CMND":("si")}

Pour une comparaison : comparaison : {"UNDEF":(None), "COMP":("=", "<", "<=", ">", ">="), "UNDEF":(None)} c'est encore bancal, mais dans l'idéal None permettrait de tout accepter…

Après faut que j'y réfléchisse encore, mais je pense faire comme Zez a dit :

expr = {UNDEF | NUM} OPTR {UNDEF | NUM}
       {UNDEF | NUM}


Et du coup un truc dans le code un peu comme ça (là c'est incomplet )
comparaison = {"expr":(None), "COMP":("=", "<", "<=", ">", ">="), "expr":(None)}
expr = {"UNDEF":(None), "OPTR":("+", "-", "/", "*", "^"), "UNDEF":(None)}

"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 29/05/2020 17:30 | #


Wow, euh ça ne se code pas comme ça une grammaire. Tu peux improviser, mais ton parser sera mauvais à la sortie, garanti (y'a qu'à regarder celui de ZZ <3).

Tu as deux options pas trop compliquées :
1. Utiliser un générateur de parser auquel tu donnes la grammaire sous forme quasi-formelle et qui génère le code ;
2. Écrire un parser top-down avec des fonctions récursives.

Je peux te montrer des exemples des deux, mais c'est des choses très codifiées que tu peux pas laisser au hasard.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 30/05/2020 13:12 | #


Je n'utilise que mon code sur ce projet donc je vais écrire un parser

Pour le sens du haut vers le bas me semble le plus logique ça permet de 'lire' les tokens dans l'ordre sans avoir à faire des retours en arrière…

Pour le côté récursif, autant au niveau du code de l'arbre c'était évident, autant là je vois moins bien comment ça marche…
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 30/05/2020 14:17 | #


Shadow15510 a écrit :
Je n'utilise que mon code sur ce projet donc je vais écrire un parser

Pas de souci.

Pour le sens du haut vers le bas me semble le plus logique ça permet de 'lire' les tokens dans l'ordre sans avoir à faire des retours en arrière…

Les parsers courants lisent tous les tokens une seule fois du début à la fin, donc oui faut pas revenir en arrière.

Pour le côté récursif, autant au niveau du code de l'arbre c'était évident, autant là je vois moins bien comment ça marche…

En gros pour chaque symbole comme expr tu auras une fonction genre expr() qui va lire une expression dans le flux de tokens et renvoyer le résultat.

Par exemple, avec la grammaire classique pour l'arithmétique, qui ressemble à ça (on note traditionnellement les tokens en majuscules et les symboles de grammaire en minuscules) :

produit -> INT | INT FOIS produit
somme -> produit | produit PLUS somme

Tu auras deux fonctions récursives en plus de la fonction expect() qui est utilisée pour obtenir le token suivant et du lookahead dont j'ai parlé avant, qui est le token suivant (on s'autorise à regarder un peu devant pour prendre des décisions).

# Initialise le lookahead avec le premier token du script
lookahead = get_next_token()

# Renvoie le prochain token en vérifiant qu'il est du bon type (le nom "expect" est classique)
# Si valid_tokens = [], on accepte tout (pour simplifier)
# Gère aussi le lookahed
def expect(valid_tokens = []):
    # Récupère le lookahead (puisque c'est le prochain) et le remplacer
    t = lookahead
    lookahead = get_next_token()
    # Vérifier le type
    if valid_tokens != [] && t.type not in valid_tokens:
       raise SyntaxError("expected one of " + ", ".join(valid_tokens))
    # Et on renvoie
    return t

# Lit une somme
def somme():
    # D'abord on veut un INT
    i1 = expect([INT])
    # Ensuite si le lookahead est PLUS, on utilise somme -> INT PLUS somme
    # Sinon, on utilise somme -> INT
    if lookahead.type != PLUS:
        return Somme(i1)
    # On lit le plus
    expect([PLUS])
    # Et ensuite on lit une autre somme
    s = somme()
    return Somme(i1, s)

# Ici, c'est tout pareil, on gère deux règles : produit -> somme et produit -> somme FOIS somme
# On décide pas tout de suite laquelle on va appliquer
def produit():
    # On récupère une somme
    s1 = somme()
    # Ensuite si le lookahead n'est pas FOIS, on laisse tomber
    if lookahead.type != FOIS:
        return Produit(s1)
    # Si c'est FOIS, on le lit et on récupère un autre produit
    expect([FOIS])
    p = produit()
    return Produit(s1, p)

Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 30/05/2020 14:28 | #


Donc ma grammaire va se présenter sous la forme d'une suite de fonction… ça éclaircit vachement le truc déjà !

Ça met aussi en évidence que je dois faire un objet token ? avec type ?

J'ai très bien compris le coup de
produit -> INT | INT FOIS produit
somme -> produit | produit PLUS somme


Pour quoi INT | INT FOIS produit ? le premier INT correspond à quoi ? au résultat attendu… ?

Pourquoi faire des trucs récursifs… ? Pour somme à la fin tu va voir la prochaine somme… ? Mais entre les deux sommes il y a d'autres instructions… ? Ou alors c'est pour parer aux cas comme : a+2+b ?

Une subtilité en terme de connaissance Python : raise SyntaxError("expected one of " + ", ".join(valid_tokens)) le raise stop l'execution… ? (je connais pas du tout… )
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 30/05/2020 14:54 | #


Ta grammaire ça reste le truc formel où tu écris chaque règle avec des flèches, mais le parser oui c'est des fonctions récursives.

Pour quoi INT | INT FOIS produit ? le premier INT correspond à quoi ? au résultat attendu… ?

Le premier c'est le A dans A×B. Un produit c'est deux opérandes. Dans A×B×C×D tu vas obtenir Produit(A,Produit(B,Produit(C,D))) par exemple.

Pourquoi faire des trucs récursifs… ? Pour somme à la fin tu va voir la prochaine somme… ? Mais entre les deux sommes il y a d'autres instructions… ? Ou alors c'est pour parer aux cas comme : a+2+b ?

Non, le somme() à la fin de ne va pas voir la "prochaine somme". N'oublie pas, on lit les tokens une fois chacun et dans l'ordre. Si je passe là c'est que je sais que derrière INT PLUS j'ai encore des éléments dans ma somme. C'est donc bien le cas de a+2+b+etc. Tu pourrais aussi faire une boucle mais on se précipite pas, pour l'instant on fait la grammaire exactement comme elle est écrite.

Une subtilité en terme de connaissance Python : raise SyntaxError("expected one of " + ", ".join(valid_tokens)) le raise stop l'execution… ? (je connais pas du tout… )

Ça balance une exception, comme quand tu interromps le programme au clavier, que tu accèdes en-dehors des bornes d'un tableau, que tu divises par zéro, etc. Si tu rattrapes pas exprès l'exception avec un try ailleurs ça arrête le programme.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 30/05/2020 15:05 | #


Le premier c'est le A dans A×B


Je crois que j'ai toujours pas compris… xD Pourquoi on écrit pas tout simplement : produit -> INT FOIS INT ?

Je crois que j'ai pas d'autre questions pour l'instant… Merci beaucoup !! J'ai de quoi commencer à faire les règles de grammaire et le parser
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 30/05/2020 15:36 | #


Je crois que j'ai toujours pas compris… xD Pourquoi on écrit pas tout simplement : produit -> INT FOIS INT ?

Parce que 2×3×4 est un produit !
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Shadow15510 Hors ligne Administrateur Points: 5504 Défis: 18 Message

Citer : Posté le 30/05/2020 16:12 | #


Ah je crois que je viens de comprendre… c'est produit = INT la première fois est après on prend le modèle récursif produit = INT FOIS produit ?

Du coup toute ma grammaire va être définie par : nom_pattern -> SIMPLE | RECURRENCE ?
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Précédente 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ··· 18, 19, 20 Suivante

LienAjouter une imageAjouter une vidéoAjouter un lien vers un profilAjouter du codeCiterAjouter un spoiler(texte affichable/masquable par un clic)Ajouter une barre de progressionItaliqueGrasSoulignéAfficher du texte barréCentréJustifiéPlus petitPlus grandPlus de smileys !
Cliquez pour épingler Cliquez pour détacher Cliquez pour fermer
Alignement de l'image: Redimensionnement de l'image (en pixel):
Afficher la liste des membres
:bow: :cool: :good: :love: ^^
:omg: :fusil: :aie: :argh: :mdr:
:boulet2: :thx: :champ: :whistle: :bounce:
valider
 :)  ;)  :D  :p
 :lol:  8)  :(  :@
 0_0  :oops:  :grr:  :E
 :O  :sry:  :mmm:  :waza:
 :'(  :here:  ^^  >:)

Σ π θ ± α β γ δ Δ σ λ
Veuillez donner la réponse en chiffre
Vous devez activer le Javascript dans votre navigateur pour pouvoir valider ce formulaire.

Si vous n'avez pas volontairement désactivé cette fonctionnalité de votre navigateur, il s'agit probablement d'un bug : contactez l'équipe de Planète Casio.

Planète Casio v4.3 © créé par Neuronix et Muelsaco 2004 - 2024 | Il y a 108 connectés | Nous contacter | Qui sommes-nous ? | Licences et remerciements

Planète Casio est un site communautaire non affilié à Casio. Toute reproduction de Planète Casio, même partielle, est interdite.
Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou copyrights.
CASIO est une marque déposée par CASIO Computer Co., Ltd