2015-03-30 01:55:11 +03:00
|
|
|
#!/usr/bin/env julia
|
|
|
|
|
2015-10-24 23:39:13 +03:00
|
|
|
push!(LOAD_PATH, pwd(), "/usr/share/julia/base")
|
2015-04-01 06:17:47 +03:00
|
|
|
import readline_mod
|
2015-03-30 01:55:11 +03:00
|
|
|
import reader
|
|
|
|
import printer
|
|
|
|
using env
|
|
|
|
import core
|
|
|
|
using types
|
|
|
|
|
|
|
|
# READ
|
|
|
|
function READ(str)
|
|
|
|
reader.read_str(str)
|
|
|
|
end
|
|
|
|
|
|
|
|
# EVAL
|
2020-07-21 19:01:48 +03:00
|
|
|
function quasiquote_loop(elts)
|
|
|
|
acc = Any[]
|
|
|
|
for i in length(elts):-1:1
|
|
|
|
elt = elts[i]
|
|
|
|
if isa(elt, Array) && length(elt) == 2 && elt[1] == symbol("splice-unquote")
|
|
|
|
acc = Any[:concat, elt[2], acc]
|
|
|
|
else
|
|
|
|
acc = Any[:cons, quasiquote(elt), acc]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return acc
|
2015-03-30 01:55:11 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
function quasiquote(ast)
|
2020-07-21 19:01:48 +03:00
|
|
|
if isa(ast, Array)
|
|
|
|
if length(ast) == 2 && ast[1] == symbol("unquote")
|
|
|
|
ast[2]
|
|
|
|
else
|
|
|
|
quasiquote_loop(ast)
|
|
|
|
end
|
|
|
|
elseif isa(ast, Tuple)
|
|
|
|
Any[:vec, quasiquote_loop(ast)]
|
|
|
|
elseif typeof(ast) == Symbol || isa(ast, Dict)
|
|
|
|
Any[:quote, ast]
|
2015-03-30 01:55:11 +03:00
|
|
|
else
|
2020-07-21 19:01:48 +03:00
|
|
|
ast
|
2015-03-30 01:55:11 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-01-10 02:15:40 +03:00
|
|
|
function EVAL(ast, env)
|
|
|
|
while true
|
2015-03-30 01:55:11 +03:00
|
|
|
|
2022-01-10 02:15:40 +03:00
|
|
|
dbgenv = env_find(env, Symbol("DEBUG-EVAL"))
|
|
|
|
if dbgenv != nothing
|
|
|
|
dbgeval = env_get(dbgenv, Symbol("DEBUG-EVAL"))
|
|
|
|
if dbgeval !== nothing && dbgeval !== false
|
|
|
|
println("EVAL: $(printer.pr_str(ast,true))")
|
|
|
|
end
|
2015-03-30 01:55:11 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
if typeof(ast) == Symbol
|
2022-01-10 02:15:40 +03:00
|
|
|
return env_get(env,ast)
|
|
|
|
elseif isa(ast, Tuple)
|
|
|
|
return map((x) -> EVAL(x,env), ast)
|
2015-04-01 06:42:42 +03:00
|
|
|
elseif isa(ast, Dict)
|
2022-01-10 02:15:40 +03:00
|
|
|
return [x[1] => EVAL(x[2], env) for x=ast]
|
|
|
|
elseif !isa(ast, Array)
|
|
|
|
return ast
|
2015-03-30 01:55:11 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
# apply
|
2016-03-30 16:18:07 +03:00
|
|
|
if isempty(ast) return ast end
|
2015-03-30 01:55:11 +03:00
|
|
|
|
|
|
|
if :def! == ast[1]
|
2015-10-24 23:39:13 +03:00
|
|
|
return env_set(env, ast[2], EVAL(ast[3], env))
|
2015-03-30 01:55:11 +03:00
|
|
|
elseif symbol("let*") == ast[1]
|
|
|
|
let_env = Env(env)
|
|
|
|
for i = 1:2:length(ast[2])
|
2015-10-24 23:39:13 +03:00
|
|
|
env_set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
|
2015-03-30 01:55:11 +03:00
|
|
|
end
|
|
|
|
env = let_env
|
|
|
|
ast = ast[3]
|
|
|
|
# TCO loop
|
|
|
|
elseif :quote == ast[1]
|
|
|
|
return ast[2]
|
|
|
|
elseif :quasiquote == ast[1]
|
|
|
|
ast = quasiquote(ast[2])
|
|
|
|
# TCO loop
|
|
|
|
elseif :defmacro! == ast[1]
|
|
|
|
func = EVAL(ast[3], env)
|
|
|
|
func.ismacro = true
|
2015-10-24 23:39:13 +03:00
|
|
|
return env_set(env, ast[2], func)
|
2015-03-30 01:55:11 +03:00
|
|
|
elseif :do == ast[1]
|
2022-01-10 02:15:40 +03:00
|
|
|
map((x) -> EVAL(x,env), ast[2:end-1])
|
2015-03-30 01:55:11 +03:00
|
|
|
ast = ast[end]
|
|
|
|
# TCO loop
|
|
|
|
elseif :if == ast[1]
|
|
|
|
cond = EVAL(ast[2], env)
|
|
|
|
if cond === nothing || cond === false
|
|
|
|
if length(ast) >= 4
|
|
|
|
ast = ast[4]
|
|
|
|
# TCO loop
|
|
|
|
else
|
|
|
|
return nothing
|
|
|
|
end
|
|
|
|
else
|
|
|
|
ast = ast[3]
|
|
|
|
# TCO loop
|
|
|
|
end
|
|
|
|
elseif symbol("fn*") == ast[1]
|
|
|
|
return MalFunc(
|
2016-01-30 05:36:32 +03:00
|
|
|
(args...) -> EVAL(ast[3], Env(env, ast[2], Any[args...])),
|
2015-03-30 01:55:11 +03:00
|
|
|
ast[3], env, ast[2])
|
|
|
|
else
|
2022-01-10 02:15:40 +03:00
|
|
|
f = EVAL(ast[1], env)
|
|
|
|
args = ast[2:end]
|
|
|
|
if isa(f, MalFunc) && f.ismacro
|
|
|
|
ast = f.fn(args...)
|
|
|
|
continue # TCO loop
|
|
|
|
end
|
|
|
|
args = map((x) -> EVAL(x,env), args)
|
2015-03-30 01:55:11 +03:00
|
|
|
if isa(f, MalFunc)
|
|
|
|
ast = f.ast
|
|
|
|
env = Env(f.env, f.params, args)
|
|
|
|
# TCO loop
|
|
|
|
else
|
|
|
|
return f(args...)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# PRINT
|
|
|
|
function PRINT(exp)
|
|
|
|
printer.pr_str(exp)
|
|
|
|
end
|
|
|
|
|
|
|
|
# REPL
|
|
|
|
repl_env = nothing
|
|
|
|
function REP(str)
|
|
|
|
return PRINT(EVAL(READ(str), repl_env))
|
|
|
|
end
|
|
|
|
|
|
|
|
# core.jl: defined using Julia
|
|
|
|
repl_env = Env(nothing, core.ns)
|
2015-10-24 23:39:13 +03:00
|
|
|
env_set(repl_env, :eval, (ast) -> EVAL(ast, repl_env))
|
|
|
|
env_set(repl_env, symbol("*ARGV*"), ARGS[2:end])
|
2015-03-30 01:55:11 +03:00
|
|
|
|
|
|
|
# core.mal: defined using the language itself
|
|
|
|
REP("(def! not (fn* (a) (if a false true)))")
|
2019-07-16 00:57:02 +03:00
|
|
|
REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
|
2015-03-30 01:55:11 +03:00
|
|
|
REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
|
|
|
|
|
|
|
|
|
|
|
|
if length(ARGS) > 0
|
|
|
|
REP("(load-file \"$(ARGS[1])\")")
|
|
|
|
exit(0)
|
|
|
|
end
|
|
|
|
|
|
|
|
while true
|
2015-04-01 06:17:47 +03:00
|
|
|
line = readline_mod.do_readline("user> ")
|
|
|
|
if line === nothing break end
|
2015-03-30 01:55:11 +03:00
|
|
|
try
|
|
|
|
println(REP(line))
|
|
|
|
catch e
|
|
|
|
if isa(e, ErrorException)
|
|
|
|
println("Error: $(e.msg)")
|
|
|
|
else
|
|
|
|
println("Error: $(string(e))")
|
|
|
|
end
|
2015-10-24 23:39:13 +03:00
|
|
|
# TODO: show at least part of stack
|
|
|
|
if !isa(e, StackOverflowError)
|
|
|
|
bt = catch_backtrace()
|
|
|
|
Base.show_backtrace(STDERR, bt)
|
|
|
|
end
|
2015-03-30 01:55:11 +03:00
|
|
|
println()
|
|
|
|
end
|
|
|
|
end
|