pequeño lenguaje en python

Estoy escribiendo lo que ni siquiera podría llamarse un lenguaje en python. Actualmente tengo varios operadores: + , - , * , ^ , fac , @ , !! . fac calcula un factorial, @ devuelve el valor de una variable, !! establece una variable. El código está abajo. ¿Cómo podría escribir una manera de definir funciones en este lenguaje simple?

EDITAR: he actualizado el código!

 import sys, shlex, readline, os, string List, assign, call, add, sub, div, Pow, mul, mod, fac, duf, read,\ kill, clr, STO, RET, fib, curs = {}, "set", "get", "+", "-", "/", "^", "*",\ "%", "fact", "func", "read", "kill", "clear", ">", "@", "fib", "vars" def fact(num): if num == 1: return 1 else: return num*fact(num-1) def Simp(op, num2, num1): global List try: num1, num2 = float(num1), float(num2) except: try: num1 = float(num1) except: try: num2 = float(num2) except: pass if op == mul: return num1*num2 elif op == div: return num1/num2 elif op == sub: return num1-num2 elif op == add: return num1+num2 elif op == Pow: return num1**num2 elif op == assign: List[num1] = num2; return "ok" elif op == call: return List[num1] elif op == fac: return fact(num1) elif op == duf: return "%s %s %s"%(duf, num1, num2) elif op == mod: return num1%num2 elif op == kill: del List[num1]; return "ok" elif op == clr: os.system("clear") elif op == STO: List[num2] = num1; return "ok" elif op == RET: return List[num1] elif op == curs: return List elif op == read: List[num1] = Eval(raw_input("%s "%num1)); return "ok" def Eval(expr): ops = "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s"%(mul, add, sub, div, Pow, assign, call, fac, duf, mod, read, kill, clr, STO, RET, curs) stack, expr, ops = [], shlex.split(string.lower(expr)), ops.split() for i in expr: if i[0] != ';': if i not in ops: stack.append(i) elif i in ops: stack.append(Simp(i, stack.pop(), stack.pop())) else: stack.append("ok") return stack[0] def shell(): try: x = "" while x != "quit": x = raw_input("star> ") try: l = Eval(x) except KeyError: l = "does not exist" except: l = "parse error!" if l != None: print " =>",l,"\n" except (EOFError, KeyboardInterrupt): print if len(sys.argv) > 1: x = open(sys.argv[1], 'r'); l = x.readlines(); x.close() for i in l: if i[0] != ";": i = ' '.join(i.split()) x = Eval(i) if x != None: print i,"\n =>",x,"\n" else: pass shell() else: shell() 

Su progtwig está muy confundido, y necesita ser arreglado antes de que pueda ser modificado para admitir funciones definitorias. Lo haré en varios pasos y, a medida que los complete, los agregaré a la respuesta. Esta respuesta llegará a ser bastante larga.

Además, obviamente no has decidido cuál debería ser tu definición de idioma. Decidiste hacer que la definición de tu idioma siguiera tu técnica de implementación, y esto está un poco roto y resulta en mucho dolor.

Primero, la definición de tu función Simp está realmente rota. Exige que todo quite exactamente dos valores de la stack y vuelva a poner exactamente un valor. Esto está descompuesto. La función factorial no funciona de esta manera, ni tampoco la función de Fibonacci, por lo que se ve obligado a tener un argumento ‘ficticio’ que nunca se usa. Además, cosas como la asignación a un elemento de tu lista global o diccionario no tienen ninguna razón para insertar valores en la stack, por lo que te quedas presionando “ok”. Esto está roto y necesita reparación.

Aquí está la versión con este problema arreglado. Tenga en cuenta que he cambiado el nombre de Simp a builtin_op para reflejar con mayor precisión su propósito:

 import sys, shlex, readline, os, string List, assign, call, add, sub, div, Pow, mul, mod, fac, duf, read,\ kill, clr, STO, RET, fib, curs = {}, "set", "get", "+", "-", "/", "^", "*",\ "%", "fact", "func", "read", "kill", "clear", ">", "@", "fib", "vars" def fact(num): if num == 1: return 1 else: return num*fact(num-1) def builtin_op(op, stack): global List if op == mul: stack.append(float(stack.pop())*float(stack.pop())) elif op == div: stack.append(float(stack.pop())/float(stack.pop())) elif op == sub: stack.append(float(stack.pop())-float(stack.pop())) elif op == add: stack.append(float(stack.pop())+float(stack.pop())) elif op == Pow: stack.append(float(stack.pop())**float(stack.pop())) elif op == assign: val = List[stack.pop()] = stack.pop(); stack.append(val) elif op == call: stack.append(List[stack.pop()]) elif op == fac: stack.append(fact(stack.pop())) elif op == duf: stack.append("%s %s %s" % (duf, stack.pop(), stack.pop())) elif op == mod: stack.append(float(stack.pop())%float(stack.pop())) elif op == kill: del List[stack.pop()] elif op == clr: os.system("clear") elif op == STO: val = List[stack.pop()] = stack.pop(); stack.append(val) elif op == RET: stack.append(List[stack.pop()]) elif op == curs: stack.append(List) elif op == read: prompt = stack.pop(); List[prompt] = Eval(raw_input("%s "%prompt)); stack.append(List[prompt]) def Eval(expr): ops = "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s"%(mul, add, sub, div, Pow, assign, call, fac, duf, mod, read, kill, clr, STO, RET, curs) stack, expr, ops = [], shlex.split(string.lower(expr)), ops.split() for i in expr: if i[0] != ';': if i not in ops: stack.append(i) elif i in ops: builtin_op(i, stack) else: stack.append("ok") return stack[0] def shell(): try: x = "" while x != "quit": x = raw_input("star> ") try: l = Eval(x) except KeyError: l = "does not exist" except: l = "parse error!" if l != None: print " =>",l,"\n" except (EOFError, KeyboardInterrupt): print if len(sys.argv) > 1: x = open(sys.argv[1], 'r'); l = x.readlines(); x.close() for i in l: if i[0] != ";": i = ' '.join(i.split()) x = Eval(i) if x != None: print i,"\n =>",x,"\n" else: pass shell() else: shell() 

Todavía hay una serie de problemas que no están solucionados, y no los solucionaré en ninguna versión futura. Por ejemplo, es posible que un valor en la stack no se pueda interpretar como un número de punto flotante. Esto causará una excepción, y esta excepción puede ser lanzada antes de que el otro valor se lea de la stack. Esto significa que si los ‘tipos’ incorrectos están en la stack, la stack podría estar en un estado ambiguo después de un ‘error de análisis’. Generalmente quieres evitar situaciones como esta en un idioma.

Definir funciones es un problema interesante. En tu idioma, la evaluación es inmediata. No tienes un mecanismo para retrasar la evaluación hasta más tarde. Pero, estás usando el módulo shlex para analizar. Y tiene una manera de decir que todo un grupo de caracteres (incluidos los espacios y demás) son parte de una entidad. Esto nos da una manera rápida y fácil de implementar funciones. Puedes hacer algo como esto:

 star> "3 +" add3 func 

para crear su función, y:

 star> 2 add3 get 

llamarlo Usé get porque eso es lo que has asignado para call en tu progtwig.

El único problema es que la función necesitará acceso al estado actual de la stack para funcionar. Puede devolver fácilmente la cadena para la función a Eval , pero Eval siempre crea una stack nueva cada vez que se le llama. Para implementar funciones, esto necesita ser arreglado. Así que he agregado un argumento de stack predeterminado a la función Eval . Si este argumento se deja en su valor predeterminado, Eval seguirá creando una nueva stack, como antes. Pero si se pasa una stack existente, Eval usará en su lugar.

Aquí está el código modificado:

 import sys, shlex, readline, os, string List, assign, call, add, sub, div, Pow, mul, mod, fac, duf, read,\ kill, clr, STO, RET, fib, curs = {}, "set", "get", "+", "-", "/", "^", "*",\ "%", "fact", "func", "read", "kill", "clear", ">", "@", "fib", "vars" funcdict = {} def fact(num): if num == 1: return 1 else: return num*fact(num-1) def builtin_op(op, stack): global List global funcdict if op == mul: stack.append(float(stack.pop())*float(stack.pop())) elif op == div: stack.append(float(stack.pop())/float(stack.pop())) elif op == sub: stack.append(float(stack.pop())-float(stack.pop())) elif op == add: stack.append(float(stack.pop())+float(stack.pop())) elif op == Pow: stack.append(float(stack.pop())**float(stack.pop())) elif op == assign: val = List[stack.pop()] = stack.pop(); stack.append(val) elif op == call: Eval(funcdict[stack.pop()], stack) elif op == fac: stack.append(fact(stack.pop())) elif op == duf: name = stack.pop(); funcdict[name] = stack.pop(); stack.append(name) elif op == mod: stack.append(float(stack.pop())%float(stack.pop())) elif op == kill: del List[stack.pop()] elif op == clr: os.system("clear") elif op == STO: val = List[stack.pop()] = stack.pop(); stack.append(val) elif op == RET: stack.append(List[stack.pop()]) elif op == curs: stack.append(List) elif op == read: prompt = stack.pop(); List[prompt] = Eval(raw_input("%s "%prompt)); stack.append(List[prompt]) def Eval(expr, stack=None): ops = "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s"%(mul, add, sub, div, Pow, assign, call, fac, duf, mod, read, kill, clr, STO, RET, curs) if stack is None: stack = [] expr, ops = shlex.split(string.lower(expr)), ops.split() for i in expr: if i[0] != ';': if i not in ops: stack.append(i) elif i in ops: builtin_op(i, stack) else: stack.append("ok") return stack[0] def shell(): try: x = "" while x != "quit": x = raw_input("star> ") try: l = Eval(x) except KeyError: l = "does not exist" except: l = "parse error!" if l != None: print " =>",l,"\n" except (EOFError, KeyboardInterrupt): print if len(sys.argv) > 1: x = open(sys.argv[1], 'r'); l = x.readlines(); x.close() for i in l: if i[0] != ";": i = ' '.join(i.split()) x = Eval(i) if x != None: print i,"\n =>",x,"\n" else: pass shell() else: shell() 

En los lenguajes basados ​​en stack, dos operadores integrados muy útiles son dup y swap . dup toma el elemento de la stack superior y lo duplica. swap intercambia los dos elementos de la stack superior.

Si tienes dup puedes implementar una función square así:

 star> "dup *" square func 

Aquí está su progtwig con dup y swap implementado:

 import sys, shlex, readline, os, string List, assign, call, add, sub, div, Pow, mul, mod, fac, duf, read,\ kill, clr, STO, RET, fib, curs, dup, swap = {}, "set", "get", "+", "-", "/", "^", "*",\ "%", "fact", "func", "read", "kill", "clear", ">", "@", "fib", "vars", "dup", "swap" funcdict = {} def fact(num): if num == 1: return 1 else: return num*fact(num-1) def builtin_op(op, stack): global List global funcdict if op == mul: stack.append(float(stack.pop())*float(stack.pop())) elif op == div: stack.append(float(stack.pop())/float(stack.pop())) elif op == sub: stack.append(float(stack.pop())-float(stack.pop())) elif op == add: stack.append(float(stack.pop())+float(stack.pop())) elif op == Pow: stack.append(float(stack.pop())**float(stack.pop())) elif op == assign: val = List[stack.pop()] = stack.pop(); stack.append(val) elif op == call: Eval(funcdict[stack.pop()], stack) elif op == fac: stack.append(fact(stack.pop())) elif op == duf: name = stack.pop(); funcdict[name] = stack.pop(); stack.append(name) elif op == mod: stack.append(float(stack.pop())%float(stack.pop())) elif op == kill: del List[stack.pop()] elif op == clr: os.system("clear") elif op == STO: val = List[stack.pop()] = stack.pop(); stack.append(val) elif op == RET: stack.append(List[stack.pop()]) elif op == curs: stack.append(List) elif op == dup: val = stack.pop(); stack.append(val); stack.append(val) elif op == swap: val1 = stack.pop(); val2 = stack.pop(); stack.append(val1); stack.append(val2) elif op == read: prompt = stack.pop(); List[prompt] = Eval(raw_input("%s "%prompt)); stack.append(List[prompt]) def Eval(expr, stack=None): ops = "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s"%(mul, add, sub, div, Pow, assign, call, fac, duf, mod, read, kill, clr, STO, RET, curs, dup, swap) if stack is None: stack = [] expr, ops = shlex.split(string.lower(expr)), ops.split() for i in expr: if i[0] != ';': if i not in ops: stack.append(i) elif i in ops: builtin_op(i, stack) else: stack.append("ok") return stack[0] def shell(): try: x = "" while x != "quit": x = raw_input("star> ") try: l = Eval(x) except KeyError: l = "does not exist" except: l = "parse error!" if l != None: print " =>",l,"\n" except (EOFError, KeyboardInterrupt): print if len(sys.argv) > 1: x = open(sys.argv[1], 'r'); l = x.readlines(); x.close() for i in l: if i[0] != ";": i = ' '.join(i.split()) x = Eval(i) if x != None: print i,"\n =>",x,"\n" else: pass shell() else: shell() 

Por último, aquí está mi versión en Python que es mucho más clara (en mi opinión, de todos modos) que el Python que has escrito:

 import shlex, functools, sys, StringIO def bin_numeric_op(func): @functools.wraps(func) def execute(self): n2, n1 = self._stack.pop(), self._stack.pop() n1 = float(n1) n2 = float(n2) self._stack.append(func(n1, n2)) return execute def relational_op(func): @functools.wraps(func) def execute(self): n2, n1 = self._stack.pop(), self._stack.pop() self._stack.append(bool(func(n1, n2))) return execute def bin_bool_op(func): @functools.wraps(func) def execute(self): n2, n1 = self._stack.pop(), self._stack.pop() n1 = bool(n1) n2 = bool(n2) self._stack.append(bool(func(n1, n2))) return execute class Interpreter(object): def __init__(self): self._stack = [] self._vars = {} self._squarestack = [] def processToken(self, token): if token == '[': self._squarestack.append(len(self._stack)) # Currently inside square brackets, don't execute elif len(self._squarestack) > 0: if token == ']': startlist = self._squarestack.pop() lst = self._stack[startlist:] self._stack[startlist:] = [tuple(lst)] else: self._stack.append(token) # Not current inside list and close square token, something's wrong. elif token == ']': raise ValueError("Unmatched ']'") elif token in self.builtin_ops: self.builtin_ops[token](self) else: self._stack.append(token) def get_stack(self): return self._stack def get_vars(self): return self._vars @bin_numeric_op def add(n1, n2): return n1 + n2 @bin_numeric_op def mul(n1, n2): return n1 * n2 @bin_numeric_op def div(n1, n2): return n1 / n2 @bin_numeric_op def sub(n1, n2): return n1 - n2 @bin_numeric_op def mod(n1, n2): return n1 % n2 @bin_numeric_op def Pow(n1, n2): return n1**n2 @relational_op def less(v1, v2): return v1 < v2 @relational_op def lesseq(v1, v2): return v1 <= v2 @relational_op def greater(v1, v2): return v1 > v2 @relational_op def greatereq(v1, v2): return v1 > v2 @relational_op def isequal(v1, v2): return v1 == v2 @relational_op def isnotequal(v1, v2): return v1 != v2 @bin_bool_op def bool_and(v1, v2): return v1 and v2 @bin_bool_op def bool_or(v1, v2): return v1 or v2 def bool_not(self): stack = self._stack v1 = stack.pop() stack.append(not v1) def if_func(self): stack = self._stack pred = stack.pop() code = stack.pop() if pred: self.run(code) def ifelse_func(self): stack = self._stack pred = stack.pop() nocode = stack.pop() yescode = stack.pop() code = yescode if pred else nocode self.run(code) def store(self): stack = self._stack value = stack.pop() varname = stack.pop() self._vars[varname] = value def fetch(self): stack = self._stack varname = stack.pop() stack.append(self._vars[varname]) def remove(self): varname = self._stack.pop() del self._vars[varname] # The default argument is because this is used internally as well. def run(self, code=None): if code is None: code = self._stack.pop() for tok in code: self.processToken(tok) def dup(self): self._stack.append(self._stack[-1]) def swap(self): self._stack[-2:] = self._stack[-1:-3:-1] def pop(self): self._stack.pop() def showstack(self): print"%r" % (self._stack,) def showvars(self): print "%r" % (self._vars,) builtin_ops = { '+': add, '*': mul, '/': div, '-': sub, '%': mod, '^': Pow, '<': less, '<=': lesseq, '>': greater, '>=': greatereq, '==': isequal, '!=': isnotequal, '&&': bool_and, '||': bool_or, 'not': bool_not, 'if': if_func, 'ifelse': ifelse_func, '!': store, '@': fetch, 'del': remove, 'call': run, 'dup': dup, 'swap': swap, 'pop': pop, 'stack': showstack, 'vars': showvars } def shell(interp): try: while True: x = raw_input("star> ") msg = None try: interp.run(shlex.split(x)) except KeyError: msg = "does not exist" except: sys.excepthook(*sys.exc_info()) msg = "parse error!" if msg != None: print " =>",msg,"\n" else: print " => %r\n" % (interp.get_stack(),) except (EOFError, KeyboardInterrupt): print interp = Interpreter() if len(sys.argv) > 1: lex = shlex.shlex(open(sys.argv[1], 'r'), sys.argv[1]) tok = shlex.get_token() while tok is not None: interp.processToken(tok) tok = lex.get_token() shell(interp) 

Esta nueva versión soporta una sentencia if y ifelse . Con esto y las llamadas a funciones, es posible implementar las funciones fib y fact en el lenguaje. Agregaré cómo definirías esto más adelante.

Aquí es cómo definirías la función fib :

 star> fib [ dup [ pop 1 0 + ] swap [ dup 1 - fib @ call swap 2 - fib @ call + ] swap 0 + 2 0 + < ifelse ] ! => [] star> 15 fib @ call => [987.0] 

La secuencia 0 + 2 0 + antes de < es forzar la comparación para que sea una comparación numérica.

Observe también cómo los caracteres [ y ] solos están citando a los operadores. Hacen que todo lo que hay entre ellos no se ejecute y, en cambio, se almacene en la stack como una sola lista de elementos. Esta es la clave para definir funciones. Las funciones son una secuencia de tokens que puede ejecutar con el operador de call . También se pueden usar para 'bloques anónimos' que son una especie de cruce entre las expresiones lambda y un bloque de Python estándar. Estos se utilizan en la función fib para las dos rutas posibles de la sentencia ifelse .

El analizador para esto es ridículamente simple. Y shlex es lo suficientemente poderoso como un lexer para este lenguaje simple. Otros proyectos serían obtener elementos individuales de una lista. Crear una nueva lista que consiste solo en una parte de una lista anterior. 'Listifying' un único token en la stack. Implementación de un while primitivo. Operadores numéricos que operan con enteros (en Forth real, las operaciones numéricas por defecto operan con enteros y es necesario especificar algo como +. Para obtener una versión de punto flotante). Y algunas operaciones en tokens de símbolos que permiten la manipulación de cadenas. Tal vez una operación de 'dividir' y 'unir' que convierta un token en una lista de tokens individuales para los caracteres o que se una a una lista sea suficiente.

La respuesta correcta depende de lo que le preocupa. Si le preocupa tener una solución escalable, donde la complejidad del lenguaje boostá, probablemente debería comenzar a aprender / usar uno de los módulos del analizador. Esa es una respuesta potencial si le preocupa el rendimiento, ya que es probable que algunos de los módulos estén mejor optimizados de lo que podría generar fácilmente a mano.

Si, por otro lado, lo que te interesa es aprender, revisa el algoritmo de derivación . Probablemente podría crear un diccionario de funciones (que será más rápido que usted si se enunciara) con las operaciones en la línea de:

 funcs = {} funcs["+"] = lambda x, y: x + y funcs["*"] = lambda x, y: y * y 

Entonces en tu función Simp puedes llamar

 func = funcs.get[Op] if func is not None: func[Op](num1,num2) else: #whatever you want to do here 

Lo que necesita es convertir una secuencia de símbolos (números, operaciones con números, paréntesis) en una estructura de árbol, que represente el cálculo expresado por su secuencia de símbolos. Tal cosa es exactamente el trabajo de un ” analizador ” Es posible que desee echar un vistazo a analizadores simples como este http://en.wikipedia.org/wiki/LL_parser Esos son fáciles de codificar, y puede calcular el Analizando mesas con lápiz y papel.

Parece que estás tratando de escribir algo como esto Forth en python.

Podría tener un diccionario donde almacenar variables y asociarlas con un nombre de función.

Por ejemplo, digamos que estás leyendo línea por línea tu código:

 a = 1 b = 2 c = a + b function x() d = 4 e = 5 f = d + e end 

Cuando localiza las variables (a, b, c) las almacena en una lista y esa lista dentro de un scope, este podría ser el scope global en las siguientes líneas:

 variables = scopes["global"] variables.append( "a" ) 

Podría tener una estructura de datos similar para las funciones, de modo que cuando encuentre una función, la agregará a esa estructura:

 fs = functions["global"] fs.append("x") 

Y también agrega un nuevo “scope” en el diccionario de scope

 scopes["x"] = [] // the function x doesn't have any var 

Cuando encuentra una nueva var y si está dentro de una definición de función, almacena esa nueva var en ese “scope”

 variables = scopes["x"] variables.append("d") 

¿Tiene sentido?

Todo esto debería ser factible en su implementación actual. Si quieres hacer algo más serio, te recomiendo que compres este libro.

http://pragprog.com/titles/tpdsl/language-implementation-patterns

A pesar de que está escrito usando Java como ejemplo, le dará una base sólida de aplicaciones de lenguaje y es muy fácil de leer.

Entonces deberías tener las herramientas para:

  1. Crear un Lexer (dividir la entrada en tokens)
  2. A Parse (valide si los tokens siguen las reglas de su idioma)
  3. Un AST (para caminar su entrada)
  4. Identificar los símbolos del progtwig (como vars y funciones)
  5. Construir un intérprete.

espero que esto ayude