Partage
  • Partager sur Facebook
  • Partager sur Twitter

[WIP]Langage

    28 novembre 2010 à 18:55:40

    Bonjour tout le monde

    Je m'attaque actuellement à la création d'un langage de programmation fonctionnel.
    Pour me faciliter le travail, j'ai choisi de prendre le python comme support pour ainsi me concentrer surtout sur l'algo et pas sur le code.

    De plus, pour rendre la tache encore plus facile, le langage utilise totalement la notation postfixé.
    Ainsi je n'ai plus à m'occuper des blocs, et des priorités.
    Cependant, ce dernier point oblige le langage a être extrêmement moche.
    En effet, je n'utilise pas de blocs délimités donc je dois définir la taille d'un bloc à l'avance si celui ci n'est pas connu directement.
    Pour déclarer une fonction il faut alors écrire cela :
    //let f nbrArgs arg1 arg2 arg3 expr
    let f 1 x + 2 3


    Enfin, je pars avec l'hypothèse que le code donné est très bien écris avec chaque token séparé.

    Pour l'instant je m'en sors plutôt bien. En effet, le parseur à l'air robuste et le langage arrive à évaluer des fonctions récursives.

    Voici un exemple de code :
    //Une fonction
    >>> let f 1 x + ^ e x cos x
    
    //Condition d'arret de la récursion
    >>> let a 3 x0 x1 n if | = f / + x0 x1 2 0 = n 0 1 0
    
    //Une fonction qui cherche une approximation de la solution de f(x) = 0 par la méthode de dichotomie
    >>> let zero 3 x0 x1 n if a x0 x1 n / + x0 x1 2 if > f / + x0 x1 2 0 zero x0 / + x0 x1 2 - n 1 zero / + x0 x1 2 x1 - n 1
    
    //On cherche la solution avec 42 (j'ai perdu) tours de boucle max
    >>> eval zero -2 -1 42
    -1.74613953041


    Ce bout de code soulève 3 problèmes :
    Le code est aussi clair que de la roche. Autrement dit, le post fixé, c'est bien quand on a affaire
    à une somme de deux constantes. Mais quand on a un calcul assez long, cela devient completement illisible.

    Deuxième problème qui va avec le premier : le calcul est trop long et il est largement possible de le raccourcir en déclarant des variables/fonctions dans une variable/fonction.

    Enfin, dernier problème qui saute au yeux : la fonction zero ne sert que avec la fonction f. Or une fonction de ce style devrait pouvoir être adapté à n'importe quel fonction. Il manque donc le fait de pouvoir passer des fonctions en paramètre.


    Pour le premier problème, j'ai choisi de faire un parser lisant les notations post-fixés pour plus de facilité, cependant, je me demandais si il était possible de créer un traducteur qui ne parserait pas le code mais qui ferait une bete traduction sans oublier des erreurs et sans en rajouter.

    Pour le second, je pense que c'est seulement une histoire d'organisation : la table contenant la liste des déclarations est alors propre à chaque fonction au lieu d'être commune à toutes.

    Même si les premiers problèmes paraissent plus prioritaires (surtout la lisibilité), j'aimerais vraiment inclure le fait de pouvoir donner des fonctions en paramètre. Mais je bloque sur ce problème et je ne vois pas comment le résoudre.

    Est-ce que vous auriez des pistes pour résoudre ces problèmes ?

    Voici mon code (j'ai essayé d'implémenter les fonctions en paramètre donc il y a une parti qui ressemble à pas grand chose) si cela peut vous aider à mieux comprendre les problèmes.

    # -*- coding: utf-8 -*-
    
    import math
    
    import Token
    
    #Reconnaissance des éléments de l'expression
    def Lexer(expr):
    	lsInstr = []
    	for instr in expr.lower().split('\n'):
    		if not instr:	continue
    		
    		lsToken = []
    		for token in instr.split(' '):
    			if not token:	continue
    			if token[0:2] == '//':	break
    			
    			letter = 'abcdefghijklmnopqrstuvwxyz'
    			charAccepted = letter + letter.upper() + '0123456789_'
    			
    			#Reconnaissance des opérateurs de bases
    			if token == '+':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: a[0] + a[1])]
    			elif token == '-':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: a[0] - a[1])]
    			elif token == '*':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: a[0] * a[1])]
    			elif token == '/':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: a[0] / a[1])]
    			elif token == '^':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: a[0] ** a[1])]
    			elif token == '%':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: fmod(a[0], a[1]))]
    			elif token == '!':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.factorial(a[0]))]
    			
    			#Reconnaissance des fonctions de base
    			elif token == 'cos':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.cos(a[0]))]
    			elif token == 'sin':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.sin(a[0]))]
    			elif token == 'tan':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.tan(a[0]))]
    			elif token == 'acos':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.acos(a[0]))]
    			elif token == 'asin':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.asin(a[0]))]
    			elif token == 'atan':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.atan(a[0]))]
    			elif token == 'cosh':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.cosh(a[0]))]
    			elif token == 'sinh':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.sinh(a[0]))]
    			elif token == 'tanh':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.tanh(a[0]))]
    			elif token == 'acosh':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.acosh(a[0]))]
    			elif token == 'asinh':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.asinh(a[0]))]
    			elif token == 'atanh':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.atanh(a[0]))]
    			elif token == 'floor':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.floor(a[0]))]
    			elif token == 'ceil':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.ceil(a[0]))]
    			elif token == 'abs':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: math.fabs(a[0]))]
    			elif token == 'neg':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: -a[0])]
    			elif token == 'if':		lsToken += [Token.Condition()]
    			
    			#Reconnaissance des opérateurs binaires de base
    			elif token == '&':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: int(a[0]) & int(a[1]))] 
    			elif token == '|':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: int(a[0]) | int(a[1]))]
    			elif token == '~':	lsToken += [Token.Operator(nbrArgs = 1, function = lambda a: ~int(a[0]))]
    			
    			#Reconnaissance des opérateurs booléens de base
    			elif token == '=':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: 1 if a[0] == a[1] else 0)]
    			elif token == '!=':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: 1 if a[0] != a[1] else 0)]
    			elif token == '<=':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: 1 if a[0] <= a[1] else 0)]
    			elif token == '>=':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: 1 if a[0] >= a[1] else 0)]
    			elif token == '<':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: 1 if a[0] < a[1] else 0)]
    			elif token == '>':	lsToken += [Token.Operator(nbrArgs = 2, function = lambda a: 1 if a[0] > a[1] else 0)]
    			
    			#Reconnaissance des constantes de bases
    			elif token == 'pi':	lsToken += [Token.Number(value = math.pi)]
    			elif token == 'e':	lsToken += [Token.Number(value = math.e)]
    			
    			#Reconnaissance des mots clés
    			elif token == 'let':	lsToken += [Token.Assign()]
    			elif token == 'eval':	lsToken += [Token.Eval()]
    			
    			#Reconnaissance des variables
    			elif (token[0] in letter or token[0] == '$') and not False in [c in charAccepted for c in token[1:]]:
    				lsToken += [Token.Variable(token)]
    			
    			#Sinon on essaye de récuperer un nombre
    			else:
    				try:
    					lsToken += [Token.Number(float(token))]
    				except: # Sinon : erreur
    					raise Exception('Erreur de syntaxe : le token `' + token + '` n\'est pas valide.')
    		
    		if lsToken:	lsInstr += [lsToken]
    	return lsInstr
    
    #Effectue une première passe et retourne la liste des fonctions définies ainsi que la liste des calculs à effectuer
    def Parse(lsInstr):
    	lsVarFunct = []
    	lsEval = []
    	
    	#Première passe pour lister les déclarations
    	for token in lsInstr:
    		#Si c'est une déclaration valide
    		assign = isinstance(token[0], Token.Assign)
    		if assign:	assign &= len(token) >= 4
    		if assign:	assign &= isinstance(token[1], Token.Variable)
    		if assign:	assign &= isinstance(token[2], Token.Number)
    		if assign:	assign &= len(token) >= 4 + token[2].value
    		if assign:	assign &= not False in [isinstance(token[n + 3], Token.Variable) for n in xrange(int(token[2].value))]
    		if assign:
    			n = 0
    			args = []
    			d = 0
    			while n < int(token[2].value) + d:
    				t = token[3 + n]
    				
    				if isinstance(t, Token.Variable):
    					if t.id[0] == '$':
    						if len(token) >= 4 + token[2].value + d + 1 and isinstance(token[3 + n + 1], Token.Number):
    							args += [Token.ArgFunction(id = t.id[1:], nbrArgs = int(token[3 + n + 1].value))]
    							n += 1
    							d += 1
    						else:
    							raise Exception('Erreur de syntaxe : Le nombre de paramètres est attendu à la place du token `' + str(token[3 + int(token[2].value) - n + 1]) + '`.')
    					else:
    						args += [t]
    				else:
    					raise Exception('Erreur de syntaxe : Le token `' + str(t) + '` n\'est pas attendu.')
    				
    				n += 1
    			
    			vf = Token.VarFunction(token[1].id, args, token[3 + int(token[2].value) + d:])
    			if vf in lsVarFunct:
    				raise Exception('La fonction `' + str(vf) + '` a déjà été déclarée')
    			else:
    				lsVarFunct += [vf]
    		
    		#Si c'est une évaluation valide
    		elif isinstance(token[0], Token.Eval) and len(token) >= 1:
    			pass
    		
    		else:
    			raise Exception('Erreur de syntaxe : Le token `' + str(token[0]) + '` n\'est pas attendu.')
    	
    	#Deuxième passe pour parser les expressions des fonctions déclarés
    	for vf in lsVarFunct:
    		for token in vf.expr:
    			if isinstance(token, Token.Variable):
    				if token.id != vf.id and not token in vf.args and not token in lsVarFunct:
    					raise Exception('Erreur : la variable `' + str(token) + '` n\'est pas déclaré.')
    		
    		try:
    			expr = ParseInstr(vf.expr, lsVarFunct, [af for af in vf.args if isinstance(af, Token.ArgFunction)])
    			if vf.expr:
    				raise Exception('Erreur de syntaxe : L\'expression `' + str(vf.expr) + '` n\'est pas attendu.')
    			vf.expr = expr
    		except:
    			raise
    	
    	#Troisième passe pour parser les évaluations:
    	for token in lsInstr:
    		#Plus besoin de toutes les conditions puisque si il y a eu une erreur, elle a déjà été vu
    		if isinstance(token[0], Token.Eval):
    			try:
    				t = token[1:]
    				
    				lsEval += [ParseInstr(t, lsVarFunct)]
    				if t:
    					raise Exception('Erreur de syntaxe : L\'expression `' + str(t) + '` n\'est pas attendu.')
    			except:
    				raise
    	return (lsVarFunct, lsEval)
    
    #Troisième passe pour parser les expressions à évaluer
    def ParseInstr(expr, lsVarFunct, lsArgFunct = []):
    	t0 = expr[0]
    	
    	#Si le token est un nombre
    	if isinstance(t0, Token.Number):
    		del expr[0]
    		return [t0]
    	
    	#Si le token est une variable
    	elif isinstance(t0, Token.Variable):
    		del expr[0]
    		args = []
    		if t0 in lsArgFunct:
    			af = [a for a in lsArgFunct if t0 == a][0]
    			if len(expr) < af.nbrArgs:
    				raise Exception('Erreur : il n\'y a pas assez d\'arguments')
    			
    			for _ in xrange(af.nbrArgs):
    				args += [ParseInstr(expr, lsVarFunct, lsArgFunct)]
    		else:
    			ls = [vf for vf in lsVarFunct if vf.id == t0.id]
    			if ls:
    				if len(expr) < len(ls[0].args):
    					pass
    					#raise Exception('Erreur : il n\'y a pas assez d\'arguments')
    				else:
    					for _ in xrange(len(ls[0].args)):
    						args += [ParseInstr(expr, lsVarFunct, lsArgFunct)]
    		return t0, args
    	
    	# Si le token est un opérateur
    	elif isinstance(t0, Token.Operator):
    		args = []
    		del expr[0]
    		if len(expr) < t0.nbrArgs:
    			raise Exception('Erreur : il n\'y a pas assez d\'arguments.')
    		
    		for _ in xrange(t0.nbrArgs):
    			args += [ParseInstr(expr, lsVarFunct, lsArgFunct)]
    		return t0, args
    	
    	#Sinon : erreur
    	else:
    		raise Exception('Erreur de syntaxe : Le token `' + str(t0) + '` n\'est pas attendu.')
    
    #Evalue une expression sous forme d'arbre
    def Eval(expr, lsVar, varValue = [[], []]):
    	#Si le token est un nombre
    	if isinstance(expr[0], Token.Number):
    		return expr[0].value
    	
    	#Si le token est un opérateur
    	elif isinstance(expr[0], Token.Operator):
    		try:
    			#Si c'est une condition, on évalue uniquement la branche vérifiant la condition
    			if isinstance(expr[0], Token.Condition):
    				if Eval(expr[1][0], lsVar, varValue):
    					return Eval(expr[1][1], lsVar, varValue)
    				else:
    					return Eval(expr[1][2], lsVar, varValue)
    			
    			#Sinon, on évalue l'expression comme d'habitude
    			else:
    					return expr[0].function([Eval(t, lsVar, varValue) for t in expr[1]])
    		except:
    			raise Exception('Erreur de calcul : Impossible de calculer le résultat.')
    	
    	#Si le token est une variable
    	elif isinstance(expr[0], Token.Variable):
    		if expr[0] in varValue[0]:
    			return varValue[1][varValue[0].index(expr[0])]
    		
    		var = [v for v in lsVar if v.id == expr[0].id][0]
    		args = [Eval(t, lsVar, varValue) for t in expr[1]]
    		
    		varValue[0] = var.args + varValue[0]
    		varValue[1] = args + varValue[1]
    		return Eval(var.expr, lsVar, varValue)
    
    if __name__ == '__main__':
    	code, oldEval = '', 0
    	
    	while True:
    		print '>>>',
    		expr = raw_input()
    		if expr.lower() == 'quit':
    			break
    		
    		code += '\n' + expr
    		tree = Parse(Lexer(code))
    		
    		if len(tree[1]) > oldEval:
    			oldEval = len(tree[1])
    			print Eval(tree[1][-1], tree[0])
    


    Token.py :
    # -*- coding: utf-8 -*-
    
    class Operator:
    	def __init__(self, nbrArgs, function):
    		self.nbrArgs = nbrArgs
    		self.function = function
    	
    	def __repr__(self):
    		return '<operator nbrArgs = ' + str(self.nbrArgs) + '>'
    
    class Condition(Operator):
    	def __init__(self):
    		self.nbrArgs = 3
    		self.function = None
    	
    	def __repr__(self):
    		return '<condition>'
    
    class Number:
    	def __init__(self, value):
    		self.value = value
    	
    	def __repr__(self):
    		return '<number value = ' + str(self.value) + '>'
    	
    	def __eq__(self, nbr):
    		return self.value == nbr.value
    
    class Variable:
    	def __init__(self, id):
    		self.id = id
    	
    	def __repr__(self):
    		return '<variable id = ' + self.id + '>'
    	
    	def __eq__(self, var):
    		return var.id == self.id
    
    class ArgFunction(Variable):
    	def __init__(self, id, nbrArgs):
    		self.id = id
    		self.nbrArgs = nbrArgs
    	
    	def __repr__(self):
    		return '<argument id = ' + self.id + ' nbrArgs = ' + str(self.nbrArgs) + '>'
    
    class VarFunction(Variable):
    	def __init__(self, id, args, expr):
    		self.id = id
    		self.args = args
    		self.expr = expr
    	
    	def __repr__(self):
    		return '<function id = ' + self.id + ' args = ' + str(self.args) + ' expr = ' + str(self.expr) + '>'
    
    class Assign:
    	def __repr__(self):
    		return '<assign>'
    
    class Eval:
    	def __repr__(self):
    		return '<eval>'
    


    Merci d'avance pour vos réponses.

    Si vous avez besoin de plus d'info, n'hésitez pas à demander.
    • Partager sur Facebook
    • Partager sur Twitter
      28 novembre 2010 à 20:07:48

      Hé bien... c'est ambitieux. La création d'un langage n'est surement pas une chose facile. Bonne chance :)

      Une question : c'est un "véritable" langage de programmation ou juste un langage "exotique", pour le fun ?
      • Partager sur Facebook
      • Partager sur Twitter
        28 novembre 2010 à 21:33:36

        J'ai déjà fait il y a plusieurs mois il traceur de courbe avec un parseur très instable.
        Je l'ai retrouvé il n'y a pas longtemps et j'ai voulu le refaire avec un parseur beaucoup plus robuste.

        Donc mon parseur ne lisait que des expressions mathématiques de bases au début. Puis comme je faisais un traceur de graphe, c'était mieux de pouvoir définir des graphes à partir d'autres.
        Puis lorsque j'ai vu que tout fonctionnai bien, j'ai continuer dans le but d'en faire un langage assez complet :).

        Donc c'est un véritable langage de prog fait pour le fun >< .

        Mais j'aimerais le refaire pour en faire un langage tourné vers les maths (et donc avec le domaine de définition à définir par exemple).
        • Partager sur Facebook
        • Partager sur Twitter
          28 novembre 2010 à 22:32:27

          Je suggère que tu postes plutôt ça dans Autres Langages : c'est un sujet très intéressant, mais qui touchera plus de monde là-bas (puisqu'il n'est pas spécifique au Python).
          Ton lexer est très peu factorisé : en déclarant un dictionnaire avec les correspondances opérateur/fonction associée, je pense que tu y gagnerais beaucoup.

          Pour le premier problème, si tu fais un traducteur, cela revient à faire un parser selon une autre syntaxe. Si tu veux, tu peux utiliser des outils qui te permettent de faire ça tous seuls, comme lex et yacc.
          Pour le deuxième problème, tu peux effectivement avoir une table par fonction, ou alors une table de variables dont les noms soient préfixés par leur portée par exemple (comme dichotomie_minimal).
          Pour le troisième problème, tu peux, une fois tes fonctions définies, les enregistrer dans un dictionnaire (en tant que fonctions python), avec comme clef leur nom dans ton code. Ensuite, quand tu tombes sur un nom que tu ne connais pas, tu peux aller vérifier ce dictionnaire. Tu peux d'ailleurs utiliser un grand dictionnaire qui contienne à la fois les builtin et les fonctions que tu rajoutes.

          Enfin, tu dis : "Pour me faciliter le travail, j'ai choisi de prendre le python comme support pour ainsi me concentrer surtout sur l'algo et pas sur le code.". Pourquoi pas, mais python n'est pas le plus adapté. Un langage comme Ocaml est bien plus puissant pour traiter ce genre de problème - note que ce n'est pas de la propagande, juste une information. Ah, et moi aussi j'ai perdu. Enflure.
          • Partager sur Facebook
          • Partager sur Twitter

          [WIP]Langage

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown