I’m writing what might not even be called a language in python. I currently have several operators: +, -, *, ^, fac, @, !!. fac computes a factorial, @ returns the value of a variable, !! sets a variable. The code is below. How would I go about writing 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()
Your program is very confused, and it needs to be fixed before it can be modified to support defining functions. I will do this in several steps and as I complete them, I will add them into the answer. This answer will get to be quite long.
Also, you obviously haven’t decided what your language definition should be. You’ve decided to make your language definition sort of follow your implementation technique, and this is kind of broken, and results in a lot of pain.
First, the definition for your
Simpfunction is really broken. It demands that everything take exactly two values off the stack, and put exactly one value back on. This is broken. The factorial function doesn’t work this way, nor does the Fibonacci function, so you are forced to have a ‘dummy’ argument that’s never used. Also, things like assigning to an element of your global list or dictionary have no reason to push values onto the stack, so you’re left pushing ‘ok’. This is broken and needs fixing.Here is the version with this problem fixed. Notice that I’ve renamed
Simptobuiltin_opto more accurately reflect its purpose:There are still a number of problems here that aren’t fixed, and I won’t fix in any future version. For example, it’s possible a value on the stack cannot be interpreted as a floating point number. This will cause an exception, and this exception may be thrown before the other value is read off the stack. This means that if the wrong ‘types’ are on the stack the stack could be in an ambiguous state after a ‘parse error’. Generally you want to avoid situations like this in a language.
Defining functions is an interesting problem. In your language, evaluation is immediate. You don’t have a mechanism for delaying evaluation until later. But, you’re using the
shlexmodule for parsing. And it has a way of saying that a whole group of characters (including spaces and such) are part of one entity. This gives us a quick and easy way to implement functions. You can do something like this:to create your function, and:
to call it. I used
getbecause that’s what you’ve assigned tocallin your program.The only problem is that the function is going to need access to the current state of the stack in order to work. You can easily feed the string for the function back into
Eval, butEvalalways creates a brand new stack each time it is called. In order to implement functions, this needs to be fixed. So I’ve added a defaultedstackargument to theEvalfunction. If this argument is left at its default value,Evalwill still create a new stack, just like before. But if an existing stack is passed in,Evalwill use it instead.Here is the modified code:
In stack based languages, two very useful built in operators are
dupandswap.duptakes the top stack element and duplicates it.swapswaps the top two stack elements.If you have
dupyou can implement asquarefunction like so:Here is your program with
dupandswapimplemented:Lastly, here is my version in Python that’s much clearer (in my opinion anyway) than the Python you’ve written:
This new version supports an
ifandifelsestatement. With this and function calls, it’s possible to implement thefibandfactfunctions in the language. I will add in how you would define these later.Here is how you would define the
fibfunction:The
0 + 2 0 +sequence before the<is to force the comparison to be a numeric comparison.Also notice how the
[and]single characters are quoting operators. They cause everything between them to be not executed and instead stored on the stack as a single list of items. This is the key to defining functions. Functions are a sequence of tokens that you can execute with thecalloperator. They can also be used for ‘anonymous blocks’ that are sort of a cross betweenlambdaexpressions and a standard Python block. These are used in thefibfunction for the two possible paths of theifelsestatement.The parser for this is ridiculously simple. And
shlexis plenty powerful enough as a lexer for this simple language. Other projects would be fetching individual items out of a list. Creating a new list that consists of only a portion of a previous list. ‘Listifying’ a single token on the stack. Implementation of awhileprimitive. Numeric operators that operate on integers (in actual Forth, the numeric operations by default operate on integers and you need to specify something like+.to get a floating point version). And some operations on symbol tokens that allow string manipulation. Perhaps a ‘split’ and ‘join’ operation that turns a token into a list of individual tokens for the characters or joins a list together into a single token would be sufficient.