2015-02-28 00:36:27 +01:00
|
|
|
require_relative "mal_readline"
|
|
|
|
|
require_relative "types"
|
|
|
|
|
require_relative "reader"
|
|
|
|
|
require_relative "printer"
|
|
|
|
|
require_relative "env"
|
|
|
|
|
require_relative "core"
|
2014-04-13 14:37:56 -05:00
|
|
|
|
|
|
|
|
# read
|
|
|
|
|
def READ(str)
|
|
|
|
|
return read_str(str)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# eval
|
2020-07-21 18:01:48 +02:00
|
|
|
def qq_loop(ast)
|
|
|
|
|
acc = List.new []
|
|
|
|
|
ast.reverse_each do |elt|
|
2025-07-09 18:21:28 +00:00
|
|
|
if elt in List[:"splice-unquote", quoted]
|
2023-04-21 15:13:23 +02:00
|
|
|
acc = List.new [:concat, quoted, acc]
|
2020-07-21 18:01:48 +02:00
|
|
|
else
|
|
|
|
|
acc = List.new [:cons, quasiquote(elt), acc]
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return acc
|
2014-04-13 14:37:56 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def quasiquote(ast)
|
2025-07-09 18:21:28 +00:00
|
|
|
case ast
|
2020-07-21 18:01:48 +02:00
|
|
|
when List
|
2025-07-09 18:21:28 +00:00
|
|
|
if ast in List[:unquote, quoted] # ← fixed pattern
|
2023-04-21 15:13:23 +02:00
|
|
|
quoted
|
2014-04-13 14:37:56 -05:00
|
|
|
else
|
2020-07-21 18:01:48 +02:00
|
|
|
qq_loop(ast)
|
2014-04-13 14:37:56 -05:00
|
|
|
end
|
2020-07-21 18:01:48 +02:00
|
|
|
when Vector
|
|
|
|
|
List.new [:vec, qq_loop(ast)]
|
2023-04-21 15:13:23 +02:00
|
|
|
when Hash, Symbol
|
2020-07-21 18:01:48 +02:00
|
|
|
List.new [:quote, ast]
|
|
|
|
|
else
|
|
|
|
|
ast
|
|
|
|
|
end
|
2014-04-13 14:37:56 -05:00
|
|
|
end
|
|
|
|
|
|
2022-01-10 00:15:40 +01:00
|
|
|
def EVAL(ast, env)
|
|
|
|
|
while true
|
2014-04-13 14:37:56 -05:00
|
|
|
|
2022-01-10 00:15:40 +01:00
|
|
|
if env.get_or_nil(:"DEBUG-EVAL")
|
|
|
|
|
puts "EVAL: #{_pr_str(ast, true)}"
|
2014-04-13 14:37:56 -05:00
|
|
|
end
|
|
|
|
|
|
2022-01-10 00:15:40 +01:00
|
|
|
case ast
|
2023-04-21 15:13:23 +02:00
|
|
|
in Symbol
|
2022-01-10 00:15:40 +01:00
|
|
|
return env.get(ast)
|
2023-04-21 15:13:23 +02:00
|
|
|
in Vector
|
2022-01-10 00:15:40 +01:00
|
|
|
return Vector.new ast.map{|a| EVAL(a, env)}
|
2023-04-21 15:13:23 +02:00
|
|
|
in Hash
|
2014-04-13 15:27:34 -05:00
|
|
|
new_hm = {}
|
2021-08-21 19:08:17 +02:00
|
|
|
ast.each{|k,v| new_hm[k] = EVAL(v, env)}
|
2022-01-10 00:15:40 +01:00
|
|
|
return new_hm
|
2014-04-13 14:37:56 -05:00
|
|
|
|
|
|
|
|
# apply list
|
|
|
|
|
|
2023-04-21 15:13:23 +02:00
|
|
|
in :def!, a1, a2
|
2014-04-13 14:37:56 -05:00
|
|
|
return env.set(a1, EVAL(a2, env))
|
2023-04-21 15:13:23 +02:00
|
|
|
in :"let*", a1, a2
|
2014-04-13 14:37:56 -05:00
|
|
|
let_env = Env.new(env)
|
|
|
|
|
a1.each_slice(2) do |a,e|
|
|
|
|
|
let_env.set(a, EVAL(e, let_env))
|
|
|
|
|
end
|
2014-04-23 21:59:50 -05:00
|
|
|
env = let_env
|
|
|
|
|
ast = a2 # Continue loop (TCO)
|
2023-04-21 15:13:23 +02:00
|
|
|
in :quote, a1
|
2014-04-13 14:37:56 -05:00
|
|
|
return a1
|
2023-04-21 15:13:23 +02:00
|
|
|
in :quasiquote, a1
|
2014-04-23 21:59:50 -05:00
|
|
|
ast = quasiquote(a1); # Continue loop (TCO)
|
2023-04-21 15:13:23 +02:00
|
|
|
in :defmacro!, a1, a2
|
2019-12-17 13:16:28 +02:00
|
|
|
func = EVAL(a2, env).clone
|
2014-04-13 14:37:56 -05:00
|
|
|
func.is_macro = true
|
|
|
|
|
return env.set(a1, func)
|
2023-04-21 15:13:23 +02:00
|
|
|
in [:"try*", a1, [:"catch*", key, handler]]
|
2014-04-13 14:37:56 -05:00
|
|
|
begin
|
|
|
|
|
return EVAL(a1, env)
|
|
|
|
|
rescue Exception => exc
|
|
|
|
|
if exc.is_a? MalException
|
|
|
|
|
exc = exc.data
|
|
|
|
|
else
|
|
|
|
|
exc = exc.message
|
|
|
|
|
end
|
2023-04-21 15:13:23 +02:00
|
|
|
ast = handler
|
|
|
|
|
env = Env.new(env, [key], [exc]) # Continue loop (TCO)
|
2014-04-13 14:37:56 -05:00
|
|
|
end
|
2023-04-21 15:13:23 +02:00
|
|
|
in [:"try*", a1]
|
|
|
|
|
ast = a1 # Continue loop (TCO)
|
|
|
|
|
in [:do, *]
|
2022-01-10 00:15:40 +01:00
|
|
|
ast[1..-2].map{|a| EVAL(a, env)}
|
2014-04-23 21:59:50 -05:00
|
|
|
ast = ast.last # Continue loop (TCO)
|
2023-04-21 15:13:23 +02:00
|
|
|
in [:if, a1, a2, *]
|
2014-04-13 14:37:56 -05:00
|
|
|
cond = EVAL(a1, env)
|
2023-04-21 15:13:23 +02:00
|
|
|
if cond
|
2014-04-23 21:59:50 -05:00
|
|
|
ast = a2 # Continue loop (TCO)
|
2023-04-21 15:13:23 +02:00
|
|
|
else
|
|
|
|
|
ast = ast[3] # Continue loop (TCO)
|
2014-04-13 14:37:56 -05:00
|
|
|
end
|
2023-04-21 15:13:23 +02:00
|
|
|
in :"fn*", a1, a2
|
2014-04-13 14:37:56 -05:00
|
|
|
return Function.new(a2, env, a1) {|*args|
|
2016-01-28 16:16:33 -05:00
|
|
|
EVAL(a2, Env.new(env, a1, List.new(args)))
|
2014-04-13 14:37:56 -05:00
|
|
|
}
|
2023-04-21 15:13:23 +02:00
|
|
|
in [a0, *]
|
2022-01-10 00:15:40 +01:00
|
|
|
f = EVAL(a0, env)
|
|
|
|
|
args = ast.drop(1)
|
2014-04-13 14:37:56 -05:00
|
|
|
if f.class == Function
|
2022-01-10 00:15:40 +01:00
|
|
|
if f.is_macro
|
|
|
|
|
ast = f[*args]
|
|
|
|
|
next # Continue loop (TCO)
|
|
|
|
|
end
|
2014-04-13 14:37:56 -05:00
|
|
|
ast = f.ast
|
2022-01-10 00:15:40 +01:00
|
|
|
env = f.gen_env(List.new args.map{|a| EVAL(a, env)})
|
|
|
|
|
# Continue loop (TCO)
|
2014-04-13 14:37:56 -05:00
|
|
|
else
|
2022-01-10 00:15:40 +01:00
|
|
|
return f[*args.map{|a| EVAL(a, env)}]
|
2014-04-13 14:37:56 -05:00
|
|
|
end
|
2023-04-21 15:13:23 +02:00
|
|
|
|
|
|
|
|
else # Empty list or scalar
|
|
|
|
|
return ast
|
2014-04-13 14:37:56 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# print
|
|
|
|
|
def PRINT(exp)
|
|
|
|
|
return _pr_str(exp, true)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# repl
|
|
|
|
|
repl_env = Env.new
|
|
|
|
|
RE = lambda {|str| EVAL(READ(str), repl_env) }
|
|
|
|
|
REP = lambda {|str| PRINT(EVAL(READ(str), repl_env)) }
|
|
|
|
|
|
2014-04-16 23:57:50 -05:00
|
|
|
# core.rb: defined using ruby
|
|
|
|
|
$core_ns.each do |k,v| repl_env.set(k,v) end
|
|
|
|
|
repl_env.set(:eval, lambda {|ast| EVAL(ast, repl_env)})
|
2014-04-19 13:04:09 -05:00
|
|
|
repl_env.set(:"*ARGV*", List.new(ARGV.slice(1,ARGV.length) || []))
|
2014-04-13 14:37:56 -05:00
|
|
|
|
2014-04-16 23:57:50 -05:00
|
|
|
# core.mal: defined using the language itself
|
2014-04-13 14:37:56 -05:00
|
|
|
RE["(def! not (fn* (a) (if a false true)))"]
|
2019-07-15 23:57:02 +02:00
|
|
|
RE["(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))"]
|
2014-04-13 14:37:56 -05:00
|
|
|
RE["(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 ARGV.size > 0
|
2014-04-19 13:04:09 -05:00
|
|
|
RE["(load-file \"" + ARGV[0] + "\")"]
|
2014-04-13 14:37:56 -05:00
|
|
|
exit 0
|
|
|
|
|
end
|
2014-04-19 13:04:09 -05:00
|
|
|
|
|
|
|
|
# repl loop
|
2014-04-17 22:23:07 -05:00
|
|
|
while line = _readline("user> ")
|
2014-04-13 14:37:56 -05:00
|
|
|
begin
|
|
|
|
|
puts REP[line]
|
|
|
|
|
rescue Exception => e
|
Test uncaught throw, catchless try* . Fix 46 impls.
Fixes made to: ada, c, chuck, clojure, coffee, common-lisp, cpp,
crystal, d, dart, elm, erlang, es6, factor, fsharp, gnu-smalltalk,
groovy, guile, haxe, hy, js, livescript, matlab, miniMAL, nasm, nim,
objc, objpascal, ocaml, perl, perl6, php, plsql, ps, python, r,
rpython, ruby, scheme, swift3, tcl, ts, vb, vimscript, wasm, yorick.
Catchless try* test is an optional test. Not all implementations
support catchless try* but a number were fixed so they at least don't
crash on catchless try*.
2018-12-03 13:20:44 -06:00
|
|
|
if e.is_a? MalException
|
|
|
|
|
puts "Error: #{_pr_str(e.data, true)}"
|
|
|
|
|
else
|
|
|
|
|
puts "Error: #{e}"
|
|
|
|
|
end
|
2024-08-01 18:07:35 -05:00
|
|
|
puts "\t#{e.backtrace[0..100].join("\n\t")}"
|
2014-04-13 14:37:56 -05:00
|
|
|
end
|
|
|
|
|
end
|