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.