Little language in python

I am writing something that cannot even be called a language in python. I currently have several operators: + , - , * , ^ , fac , @ , !! . fac evaluates factorial, @ returns the value of the variable, !! sets a variable. The code is below. How do I write a way to define functions in this simple language?

EDIT: I updated the code!

 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() 
+6
source share
5 answers

Your program is very confused and needs to be fixed before it can be modified to support function definitions. I will do this in a few steps, and when I complete them, I will add them to the answer. This answer will be quite long.

In addition, you obviously have not decided what should be in your language. You have decided to make your definition of language follow your implementation technique, and this is partly broken and leads to pain.

First, the definition of your Simp function is really broken. It requires that everything take exactly two values ​​from the stack and return exactly one value. Broken The factorial function does not work like the Fibonacci function, so you are forced to have a 'dummy' argument that has never been used. In addition, things like assigning an item to your global list or dictionary have no reason to push values ​​onto the stack, so you leave the ok button pressed. It is broken and needs fixing.

Here is the version with this problem fixed. Note that I renamed Simp to builtin_op in order to more accurately reflect its purpose:

 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() 

There are still a number of problems that are not fixed, and I will not fix in any future version. For example, it is possible that a value on the stack cannot be interpreted as a floating point number. This will throw an exception, and this exception can be thrown before another value is read from the stack. This means that if the wrong “types” are on the stack, the stack may be in an ambiguous state after a “parsing error”. As a rule, you want to avoid such situations in the language.

Defining functions are an interesting problem. In your language, an assessment is carried out immediately. You do not have a mechanism to defer evaluation until the end. But you are using the shlex module for parsing. And he has a way of saying that a whole group of characters (including spaces, etc.) are part of one object. This gives us a quick and easy way to implement functions. You can do something like this:

 star> "3 +" add3 func 

to create your function and:

 star> 2 add3 get 

to call him. I used get because it is what you assigned call in your program.

The only problem is that to work you will need the function of accessing the current state of the stack. You can easily pass the string for the function back to Eval , but Eval always creates a new stack every time it is called. To implement the functions, this must be eliminated. So I added the default stack argument to the Eval function. If this argument remains the default, Eval will still create a new stack, as before. But if an existing stack is passed, Eval will use it instead.

Here is the modified code:

 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() 

In stack-based languages, there are two very useful built-in operators: dup and swap . dup takes the top element of the stack and duplicates it. swap swaps the top two elements of the stack.

If you have dup , you can implement the square function as follows:

 star> "dup *" square func 

Here is your dup and swap program implemented:

 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() 

Finally, here is my version of Python that is much clearer (in my opinion, one way or another) than the written Python:

 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 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) 

This new version supports the if and ifelse operator. Using these and functional calls, you can implement the fib and fact functions in a language. I will add how you define them later.

Here is how you would define the fib function:

 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] 

The sequence 0 + 2 0 + before < consists in forcing comparison to digital comparison.

Also note how single characters [ and ] are quotation operators. They cause everything in between to fail and are instead stored on the stack as a single list of items. This is the key to defining functions. A function is a sequence of tokens that you can execute using the call statement. They can also be used for "anonymous blocks", which are a kind of expression between lambda expressions and the Python standard block. They are used in the fib function for two possible paths of the ifelse statement.

The parser is ridiculously simple for this. And shlex is powerful enough as a lexer for this simple language. Other projects will extract individual items from the list. Create a new list consisting only of part of the previous list. "Transfer" of one token on the stack. Implementation of the while primitive. Numeric operators that work with integers (in fact, Forth, numerical operators work with integers by default, and you need to specify something like +. To get the floating point version). And some operations with character tokens that allow you to manipulate the string. Perhaps a sufficient operation of "split" and "join", which turns the token into a list of separate tokens for characters or combines the list into one token.

+7
source

The correct answer depends on what bothers you. If you are concerned about the availability of a scalable solution in which the complexity of the language will grow, you should probably start learning / using one of the analyzer modules. This is potentially the answer if you are concerned about performance, as some of the modules are likely to be better optimized than what you could easily create manually.

If, on the other hand, you are interested in training, check out the shunting yard algorithm . Perhaps you could create a function dictionary (which will be faster than you if the instruction) with line-by-line operations:

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

Then in your Simp function you can call

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

You need to convert a sequence of characters (numbers, operations on numbers, brackets) into a tree structure, which is a calculation expressed by your sequence of characters. Such a thing is precisely the work of the parser. You might want to take a look at simple parsers like http://en.wikipedia.org/wiki/LL_parser They are easy to code, and you can calculate parsing tables with pencil and paper.

+1
source

It looks like you are trying to write something like Forth in python.

+1
source

You may have a dictionary where to store variables and bind them to the function name.

For example, let's say you read line by line your code:

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

When you define the variables (a, b, c), you save them in a list and in this list within the scope, it can be a global scope, something along the lines:

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

You may have a similar data structure for functions, so when you discover a function, you add it to this structure:

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

And you will also add a new "scope" to the scope dictionary

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

When you discover a new var, and if you are inside a function definition, you store this new var in this scope

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

Does it make sense?

All of this should be doable in your current implementation. If you want to do something more serious, I highly recommend you buy this book.

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

Although written using Java as an example, it will provide you with reliable, basic language applications, and it is very easy to read.

Then you should have tools for:

  • Create Lexer (split input into tokens)
  • Analysis (check if your tokens match your language rules)
  • AST (to walk along your entrance)
  • Identify program symbols (e.g., vars and functions)
  • Create an interpreter.

I hope this helps

+1
source

Source: https://habr.com/ru/post/890458/


All Articles