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()
What you need is to convert a sequence of symbols (numbers, operation on numbers, parenthesis) into a tree structure, that represents the calculation expressed by your sequence of symbols. Such a thing is exactly the job of a ''parser'' You might want to have a look at simple parsers like this http://en.wikipedia.org/wiki/LL_parser Those are simple to code, and you can compute the parsing tables with a pencil and paper.
Looks like you're trying to write something like this Forth in python.
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
Simp
function 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
Simp
tobuiltin_op
to 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
shlex
module 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
get
because that's what you've assigned tocall
in 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
, butEval
always 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 defaultedstack
argument to theEval
function. If this argument is left at its default value,Eval
will still create a new stack, just like before. But if an existing stack is passed in,Eval
will use it instead.Here is the modified code:
In stack based languages, two very useful built in operators are
dup
andswap
.dup
takes the top stack element and duplicates it.swap
swaps the top two stack elements.If you have
dup
you can implement asquare
function like so:Here is your program with
dup
andswap
implemented: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
if
andifelse
statement. With this and function calls, it's possible to implement thefib
andfact
functions in the language. I will add in how you would define these later.Here is how you would define the
fib
function: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 thecall
operator. They can also be used for 'anonymous blocks' that are sort of a cross betweenlambda
expressions and a standard Python block. These are used in thefib
function for the two possible paths of theifelse
statement.The parser for this is ridiculously simple. And
shlex
is 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 awhile
primitive. 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.You could have a dictionary where to store variables and associate them with a function name.
For instance, let's say you're reading line by line your code:
When you spot variables ( a,b,c ) you store them in a a list and that list within a scope, this could be the global scope something along the lines:
You could have a similar data structure for functions, so when you spot a function you add it to that structure:
And you also add a new "scope" in the scope dictionary
When you find a new var and if you're inside a function definition you store that new var in that "scope"
Does it make sense?
All this should be doable in your current implementation. If you want to do something more serious I strongly recommend you to buy this book
http://pragprog.com/titles/tpdsl/language-implementation-patterns
Even though it is written using Java as example, it will give you solid foundations language apps and it is very easy to read.
Then you should have the tools to :
I hope this helps
The right answer depends on what you are worried about. If you are worried about having a scale-able solution, where the language complexity will grow, you probably should start learning/using one of the parser modules. That is potentially an answer if you are worried about performance, as some of the modules are likely to be better optimized than what you could easily generate by hand.
If, on the other hand, what you are interested in is learning, check out the shunting yard algorithm. You could probably create a dictionary of functions (which will be faster than you if statement) with the operations along the lines of:
Then in your Simp function you can call