Skip to content

Instantly share code, notes, and snippets.

@samanthadoran
Created February 4, 2016 21:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samanthadoran/8ecaa37d357ab22f4845 to your computer and use it in GitHub Desktop.
Save samanthadoran/8ecaa37d357ab22f4845 to your computer and use it in GitHub Desktop.
import reader, printer, nre, types, tables, future, env
proc READ(input: string): malData
proc eval_ast(ast: malData, env: Env): malData
proc EVAL(ast: malData, env: Env): malData
proc PRINT(ast: malData): string
proc rep(input: string, env: Env): string
proc READ(input: string): malData =
result = read_str(input)
proc eval_ast(ast: malData, env: Env): malData =
case ast.malType
#Deviates from mal guide, but we'll have to make due
of malSymbol:
if env.find(ast.sym) != nil:
let val = env.getvar(ast.sym)(@[])
if val.malType == malNil:
result = ast
else:
result = val
else:
result = ast
of malList:
var mList = malData(malType: malList, kind: malList, list: @[])
for i in ast.list:
mList.list.add(EVAL(i, env))
result = mList
else:
result = ast
proc EVAL(ast: malData, env: Env): malData =
case ast.malType
of malList:
#Get the first key, avoid evaluating the list until we are sure the
#environment won't be changing.
#TODO: Consider a method of not having to write the calculation of that
#value three times. Preferably without causing a crash due to change in env.
let firstKey = ast.list[0]
#It's a symbol, we have to do some operations
if firstKey.malType == malSymbol:
case firstKey.sym
#Adds to the current environment
of "def!":
let mList = eval_ast(ast, env)
#Be careful to use the ast.list[1], the other one has already been replaced from env`
env.setvar(ast.list[1].sym, proc(nodes: openarray[malData]): malData = result = mList.list[2])
result = env.getvar(ast.list[1].sym)(@[])
#Creates a new environment and evalutes proceeding statements with it
of "let*":
var innerEnv = initEnv(env)
let binds = ast.list[1]
for i in countup(0, len(binds.list) - 2, 2):
let bindEval = EVAL(binds.list[i + 1], innerEnv)
innerEnv.setVar(binds.list[i].sym,
proc(nodes: openarray[malData]): malData = result = bindEval)
result = EVAL(ast.list[2], innerEnv)
#Conditional branching...
of "if":
let conditional = ast.list[1]
#Keep track of whether or not we should evaluate the if or the else
var branch: bool
#Automatically false
if conditional.malType == malNil:
branch = false
#Check the value..
elif conditional.malType == malBool:
if conditional.boolean:
branch = true
else:
branch = false
#Anything else is 'true'
else:
branch = true
if branch:
result = EVAL(ast.list[2], env)
else:
if len(ast.list) > 2:
result = EVAL(ast.list[3], env)
else:
result = malData(malType: malNil, kind: malNil)
#Evaluate all list members and return final evaluation
of "do":
for i in 1..<len(ast.list):
#We have to deviate from the mal guide here as we return the SYMBOL,
#not the function from eval_ast. This is due to a statically typed
#data structure.
result = EVAL(ast.list[i], env)
#User defined function, this could be... tricky
of "fn*":
discard """
let r = proc(nodes: openarray[malData]): malData {.closure.} =
echo("We call r?")
var s: malData = malData(malType: malList, kind: malList, list: @[])
for n in nodes:
s.list.add(n)
result = EVAL(ast.list[2], initEnv(env, ast.list[1], s))
result = malData(malType: malFunc, kind: malFunc, p: r)
echo("We make the fn result")
"""
discard
#See if we can find the right function
else:
let mList = eval_ast(ast, env)
let lenList = len(mList.list)
let fEnv = env.find(firstKey.sym)
if fEnv != nil:
result = fEnv.getvar(firstKey.sym)(mList.list[1..<lenList])
#TODO: Throw an exception here!
else:
discard
#It's just a list, return it sanely
else:
let mList = eval_ast(ast, env)
result = mList
else:
#It is something other than a list, just have eval_ast do its magic
result = eval_ast(ast, env)
proc PRINT(ast: malData): string =
result = pr_str(ast)
proc rep(input: string, env: Env): string =
result = PRINT(EVAL(READ(input), env))
proc makeInitialEnv(): Env =
#Gross and messy function to make our initial environment
result = initEnv(nil)
result.setvar("+", proc(nodes: openarray[malData]): malData =
if len(nodes) == 0:
result = malData(malType: malNil, kind: malNil)
else:
var acc: int = nodes[0].num
for i in 1..<len(nodes):
acc += nodes[i].num
result = malData(malType: malNumber, kind: malNumber, num: acc)
)
result.setvar("-", proc(nodes: openarray[malData]): malData =
if len(nodes) == 0:
result = malData(malType: malNil, kind: malNil)
else:
var acc: int = nodes[0].num
for i in 1..<len(nodes):
acc -= nodes[i].num
result = malData(malType: malNumber, kind: malNumber, num: acc)
)
result.setvar("*", proc(nodes: openarray[malData]): malData =
if len(nodes) == 0:
result = malData(malType: malNil, kind: malNil)
else:
var acc: int = nodes[0].num
for i in 1..<len(nodes):
acc *= nodes[i].num
result = malData(malType: malNumber, kind: malNumber, num: acc)
)
result.setvar("/", proc(nodes: openarray[malData]): malData =
if len(nodes) == 0:
result = malData(malType: malNil, kind: malNil)
else:
var acc: int = nodes[0].num
for i in 1..<len(nodes):
acc = acc div nodes[i].num
result = malData(malType: malNumber, kind: malNumber, num: acc)
)
proc main() =
#Keep our initial environment
var env = makeInitialEnv()
while true:
stdout.write "user> "
let input = readline(stdin)
let output = rep(input, env)
if output != nil:
echo(output)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment