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 ··· 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 Suivante
Lephenixnoir En ligne Administrateur Points: 24673 Défis: 170 Message

Citer : Posté le 10/06/2020 14:16 | #


Et... n'oublie pas que les fonctions peuvent avoir plusieurs arguments !
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 10/06/2020 14:33 | #


AH… Oui mais non… on va dire que y a pas le droit

J'ai un token SPTR qui rassemble tous les séparateurs : virgules, point-virgule, mots-clé de séparation…
Du coup ma grammaire devient :
function_call -> VAR LPAR (expr | SPTR expr) RPAR
?
"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 10/06/2020 14:50 | #


Hmm, presque. D'abord... comme pour les opérateurs, comme pour les parenthèses, faut que tu sépares tous ces symboles en tokens différents. Seule la virgule a le droit d'être utilisée pour séparer des fonctions.

Ici tu autorises 1 ou 2 arguments exactement, ce qui est dommage. Tu peux en autoriser un nombre variable de cette façon

function_call -> VAR LPAR expr (COMMA expr)* RPAR

* est un raccourci pour "0 ou plus", et cache donc une boucle.
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 10/06/2020 14:55 | #


Donc je dois avoir un token par symbole… Ça va être atroce… faut vraiment que le lexer soit repensé…
"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 10/06/2020 15:07 | #


Non non, c'est comme ça que les lexers marchent. Avoir un type de token ça ne coûte rien. Ça ne prend pas plus de mémoire, ça ne prend pas plus de temps. Tous les lexers des langages de programmation ont un token par opérateur, un par type de parenthèse, et un par symbole de ponctuation.

Maintenant ton lexer à dicos est un peu exotique, mais sur le principe avoir beaucoup de types de tokens est parfaitement normal.
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 10/06/2020 15:27 | #


ton lexer à dicos est un peu exotique

Hu… je suis partit sur des set mais c'est pas plus propre
j'ai un truc qui ressemble à ça :
# ==================================================
# Lexer
# ==================================================

# --- Main function --- #

def lexer(prgm_src):    
    var_type = {"des réels", "un réel", "des entiers", "un entiers", "un entier naturel", "des entiers naturels", "un entier relatif", "des entiers relatifs", "une liste", "des listes", "un flottant", "des flottants", "une chaîne de caractères", "des chaînes de caractères"}
    cmnd = {"fin", "finsi", "fin si", "fintantque", "fin tantque", "fin tant que", "finpour", "fin pour", "afficher", "si", "alors", "sinon", "tant que", "tantque", "pour"}
    sptr = {"et", "\n", "à", "entre", "de", ";", "faire"}
    comp = {"=", "<", "<=", ">", ">=", "est supérieur à", "est supérieur ou égal à", "est inférieur à", "est inférieur ou égal à", "est différent de", "est égal à"}
    user = {"saisir", "saisir la valeur de", "saisir les valeurs de", "demander la valeur de", "demander à l'utilisateur la valeur de"}
    logi = {"et que", "ou que"}
    assi = {"prend la valeur", "sont", "est"}
    rang = {"allant", "variant"}
    lpar = {"("}
    rpar = {")"}
    plus = {"+"}
    moins = {"-"}
    fois = {"*"}
    divis = {"/"}
    exp = {"^"}
    comma = {","}
    
    for i in {"=", "<", "<=", ">", ">=", "+", "-", "/", "*", "^", "(", ")", "[", "]", "{", "}", '"', "\n", ",", ";"}:
        prgm_src = prgm_src.replace(i, " " + i + " ")
    word = [i for i in prgm_src.lower().split(" ") if i != ""]

    l_token = TokenList()
    index, undef = 0, bool()

    token = (var_type, cmnd, comp, user, logi, assi, sptr, rang, lpar, rpar, plus, moins, fois, divis, exp, comma)
    name = ("TYPE", "CMND", "COMP", "USER", "LOGI", "ASSI", "SPTR", "RANG", "LPAR", "RPAR", "PLUS", "MINUS", "MULTI", "DIVI", "EXP", "COMMA")

    while True:
        undef = True
        for j in range(len(token)):
            for k in token[j]:
                
                target = k.split(" ")
                
                if index >= len(word): return l_token
                
                if word[index] in target and lexer_detect(word, index, target):
                        l_token.add(Token(name[j], k))
                        undef = False
                        index += len(target)
        

        if undef and word[index] == "\"":
            l_token, index = text_detecter(word, index, l_token)
        elif undef:
            if word[index].isdigit():
                l_token.add(Token("NUM", eval(word[index])))
            else:
                l_token.add(Token("VAR", word[index]))
            index += 1

# --- Secondary functions --- #

def lexer_detect(word, index, target):
    try:
        return not 0 in [target[i] == word[i + index] for i in range(len(target))]
    except:
        return 0

def text_detecter(word, index, l_token):
    txt = word[index]
    index += 1
    while word[index] != '"':
        txt = txt + " " + word[index]
        index += 1
    l_token.add(Token("TEXT", txt + ' "'))
    return l_token, index + 1

"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 10/06/2020 17:21 | #


Pour ce que c'est ça se tient, si tu veux l'écrire à la main je vois pas de gros changement structurel possible. Sinon faudrait utiliser un générateur de lexer avec les regex qui vont bien.
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 10/06/2020 17:23 | #


je pensais virer les grand ensemble et faire deux tuples : symboles détecté / nom du symbole avec une concordances des index ? Du coup le code serait un peu plus simple ?
"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 10/06/2020 17:25 | #


Tu ferais mieux d'organiser un tel tuple comme un dictionnaire. Un truc du genre

constants = {
  ",": "COMMA",
  "+": "PLUS",
  ...
}

voire mieux, pour ces cas-là, juste utiliser la chaîne de caractère exacte. Le nom du token pour "," peut être ",".
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 10/06/2020 17:26 | #


Ouaip, je change tout ç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 10/06/2020 17:26 | #


Un exemple à la con de ça se trouve d'ailleurs dans mon propre code.
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 10/06/2020 17:51 | #


J'ai ça :
# ==================================================
# Lexer
# ==================================================

# --- Main function --- #

def lexer(prgm_src):    
    token = {
        "(":"LPAR",
        ")":"RPAR",
        "+":"PLUS",
        "-":"MINUS",
        "*":"MULTI",
        "/":"DIVI",
        "^":"EXP",
        ",":"COMMA"}
    
    for i in {"=", "<", "<=", ">", ">=", "+", "-", "/", "*", "^", "(", ")", "[", "]", "{", "}", '"', "\n", ",", ";"}:
        prgm_src = prgm_src.replace(i, " " + i + " ")
    word = [i for i in prgm_src.lower().split(" ") if i != ""]

    l_token = TokenList()
    index, undef = 0, bool()

    while index < len(word):
        undef = True

        for target in token.keys():                
            name = token[target]
              
            if word[index] in target and lexer_detect(word, index, target):
                    l_token.add(Token(name, target))
                    undef = False
                    index += len(target)
                    break
        

        if undef and word[index] == "\"":
            l_token, index = text_detecter(word, index, l_token)
        elif undef:
            if word[index].isdigit():
                l_token.add(Token("NUM", eval(word[index])))
            else:
                l_token.add(Token("VAR", word[index]))
            index += 1
            
    return l_token

# --- Secondary functions --- #

def lexer_detect(word, index, target):
    try:
        return not 0 in [target[i] == word[i + index] for i in range(len(target))]
    except:
        return 0

def text_detecter(word, index, l_token):
    txt = word[index]
    index += 1
    while word[index] != '"':
        txt = txt + " " + word[index]
        index += 1
    l_token.add(Token("TEXT", txt + ' "'))
    return l_token, index + 1

À défaut d'être vraiment plus optimisé, rajouter des tokens ne m'oblige plus à modifier 5 trucs x)

Ajouté le 18/06/2020 à 08:14 :
Après quelques jours de pause, j'ai fini de mettre les fonctions ! o/ Je pense que c'est terminé : je gère les opérations mathématiques passées en argument, les différents paramètres… en sortie j'ai ça :

>>> compylateur("f(a)")
--- Tokens ---
('VAR', 'f')
('LPAR', '(')
('VAR', 'a')
('RPAR', ')')


--- AST ---
Function : f
  Parameter : #1
    Variable : a
>>> compylateur("3+a / dew(temp, humi)")
--- Tokens ---
('NUM', 3)
('PLUS', '+')
('VAR', 'a')
('DIVI', '/')
('VAR', 'dew')
('LPAR', '(')
('VAR', 'temp')
('COMMA', ',')
('VAR', 'humi')
('RPAR', ')')


--- AST ---
Operation : +
  Number : 3
  Operation : *
    Variable : a
    Operation : 1/
      Function : dew
        Parameter : #1
          Variable : temp
        Parameter : #2
          Variable : humi


Et niveau code :
def atome(self, minus = False):
        atm = self.expect(["VAR", "NUM", "LPAR", "MINUS"])
        
        if atm.type == "MINUS": return self.atome(not minus)
        elif atm.type == "VAR":
            if self.token_ahead.type == "LPAR":
                self.expect()
                return Node("Function", atm.value, *self.fct())
            
            if minus: return Node("Operation", "--", Node("Variable", atm.value))
            else: return Node("Variable", atm.value)

        elif atm.type == "NUM":
            return Node("Number", (atm.value, -atm.value)[minus])
        else:
            e = self.expr()
            self.expect("RPAR")
            if minus: return Node("Operation", "--", e)
            else: return e

    def fct(self):
        param = list()
        while self.token_ahead.type != "RPAR":
            if self.token_ahead.type in ("VAR", "NUM", "MINUS"):
                param.append(Node("Parameter", "#{}".format(len(param)+1), self.expr()))
            else: self.expect(["COMMA", "RPAR"])
        return param

"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 18/06/2020 09:15 | #


C'est pas mal ! Je modifierais juste un truc sur les fonctions si j'étais toi, c'est dans cette boucle :

         while self.token_ahead.type != "RPAR":
            if self.token_ahead.type in ("VAR", "NUM", "MINUS"):
                param.append(Node("Parameter", "#{}".format(len(param)+1), self.expr()))
            else: self.expect(["COMMA", "RPAR"])

D'abord je vois pas trop comment ça termine dans le sens où à la fin tu attrapes la parenthèse fermante dans le else et donc elle n'est plus dans token_ahead au tour de boucle suivant vu que tu l'as lue. Ça c'est louche.

Et sinon j'inverserais le if/else car ici tu as été obligé de lister tous les tokens par lesquels une expression peut commencer (VAR, NUM et MINUS). Cette liste est susceptible de grandir avec le temps (le non logique, d'autres constantes comme π, que sais-je encore), et c'est facile d'oublier des tokens (en fait tu en as déjà oublié, notamment la parenthèse ouvrante !). C'est beaucoup plus facile de dire que COMMA et RPAR finissent les arguments et de laisser tout le reste à self.expr() dans le else.
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 18/06/2020 09:22 | #


La parenthèse ouvrante n'est pas oubliée. En fait atome fonctionne comme ça :
=> Token de type "VAR".
=> Si le Token suivant est "LPAR" on envoie self.expect() ce qui permet d'avoir token_ahead sur le premier paramètre de la fonction.
=> On appelle fct pour récupérer les paramètres de la fonction.

Pour le RPAR dans le expect, c'est un peu le même raisonnement que pour LPAR ça permet d'avoir le token_ahead directement sur la prochaine opération

Au niveau du if / else, je change ç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

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

Citer : Posté le 18/06/2020 09:22 | #


Pourquoi parser a/b par a*1/b :o

Il sort quoi ton parseur pour "a-b-c" ?
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 18/06/2020 09:26 | #


Pourquoi parser a/b par a*1/b

L'opération 1/b est gérée comme un nœud avec un seul enfant ça permet de gérer les priorités de calculs en rendant prioritaire l'opération 1/b c'est surtout utile quand on des divisions en chaîne : a/b/c qui devient : a * (1/b) * (1/c).
Il sort quoi ton parseur pour "a-b-c" ?


>>> compylateur("a-b-c")
--- Tokens ---
('VAR', 'a')
('MINUS', '-')
('VAR', 'b')
('MINUS', '-')
('VAR', 'c')


--- AST ---
Operation : +
  Variable : a
  Operation : -
    Variable : b
  Operation : -
    Variable : c

"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 18/06/2020 09:34 | #


J'imagine que ton parseur te sort un "+" pour les mêmes raisons ?

Donc on a : a+(-b)+(-c), et ton AST peut avoir plus de 2 opérandes pour le "+" ?

Pour moi le 1/b c'est du hack, surtout que tu peux pas le faire avec le modulo (techniquement dans ton contexte le modulo sera une fonction et non un opérateur du coup t'es bon). Faudrait proprement gérer les priorités avec un expect(["*", "/"]) qui te retourne le 1er token parmi une liste de tokens (à voir ce que Lephé pense de ça).
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 18/06/2020 09:37 | #


Modulo sera en effet une fonction, en pseudo-code c'est rarement un opérateur… Je ne sais pas trop comment ça va se goupiller, mais ça devrait ressembler à mod(a, b) ou modulo(a, b) ou même rem(a, b)… ?
"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 18/06/2020 09:38 | #


ou même "A mod B" comme en basic casio
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 18/06/2020 09:41 | #


Aussi… je pense même que reste de la division euclidienne de a par b doit être valable… (dans les livres de maths y a des algo en pseudo-code avec ce genre de notation dégueulasse )
"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 18/06/2020 11:38 | #


Pour moi le 1/b c'est du hack

Eh ben désolé de te décevoir mais c'est la représentation correcte et non-redondante.

La parenthèse ouvrante n'est pas oubliée.

Et f((a+b)*2) ça donne quoi ?
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Précédente 1, 2, 3 ··· 10, 11, 12, 13, 14, 15, 16, 17, 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 135 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