mirror of
https://github.com/kanaka/mal.git
synced 2024-11-09 18:06:35 +03:00
480 lines
12 KiB
Plaintext
480 lines
12 KiB
Plaintext
// @import types/boxed/*.ck
|
|
// @import types/MalObject.ck
|
|
// @import types/mal/MalAtom.ck
|
|
// @import types/mal/MalError.ck
|
|
// @import types/mal/MalNil.ck
|
|
// @import types/mal/MalFalse.ck
|
|
// @import types/mal/MalTrue.ck
|
|
// @import types/mal/MalInt.ck
|
|
// @import types/mal/MalString.ck
|
|
// @import types/mal/MalSymbol.ck
|
|
// @import types/mal/MalKeyword.ck
|
|
// @import types/mal/MalList.ck
|
|
// @import types/mal/MalVector.ck
|
|
// @import types/mal/MalHashMap.ck
|
|
// @import util/*.ck
|
|
// @import reader.ck
|
|
// @import printer.ck
|
|
// @import env.ck
|
|
// @import types/MalSubr.ck
|
|
// @import types/subr/*.ck
|
|
// @import core.ck
|
|
// @import func.ck
|
|
|
|
fun MalObject READ(string input)
|
|
{
|
|
return Reader.read_str(input);
|
|
}
|
|
|
|
fun int isPair(MalObject m)
|
|
{
|
|
if( (m.type == "list" || m.type == "vector") &&
|
|
Util.sequenceToMalObjectArray(m).size() > 0 )
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
fun MalObject quasiquote(MalObject ast)
|
|
{
|
|
if( !isPair(ast) )
|
|
{
|
|
return MalList.create([MalSymbol.create("quote"), ast]);
|
|
}
|
|
|
|
Util.sequenceToMalObjectArray(ast) @=> MalObject a[];
|
|
a[0] @=> MalObject a0;
|
|
|
|
if( a0.type == "symbol" && (a0$MalSymbol).value() == "unquote" )
|
|
{
|
|
return a[1];
|
|
}
|
|
|
|
if( isPair(a0) )
|
|
{
|
|
Util.sequenceToMalObjectArray(a0) @=> MalObject a0_[];
|
|
a0_[0] @=> MalObject a0_0;
|
|
|
|
if( a0_0.type == "symbol" && (a0_0$MalSymbol).value() == "splice-unquote" )
|
|
{
|
|
return MalList.create(
|
|
[MalSymbol.create("concat"), a0_[1],
|
|
quasiquote(MalList.create(MalObject.slice(a, 1)))]);
|
|
}
|
|
}
|
|
|
|
return MalList.create(
|
|
[MalSymbol.create("cons"), quasiquote(a[0]),
|
|
quasiquote(MalList.create(MalObject.slice(a, 1)))]);
|
|
}
|
|
|
|
fun int isMacroCall(MalObject ast, Env env)
|
|
{
|
|
if( ast.type == "list" )
|
|
{
|
|
(ast$MalList).value() @=> MalObject a[];
|
|
|
|
if( a[0].type == "symbol" )
|
|
{
|
|
(a[0]$MalSymbol).value() => string name;
|
|
env.find(name) @=> MalObject value;
|
|
|
|
if( value != null && value.type == "func" && (value$Func).isMacro )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
fun MalObject macroexpand(MalObject ast, Env env)
|
|
{
|
|
while( isMacroCall(ast, env) )
|
|
{
|
|
Util.sequenceToMalObjectArray(ast) @=> MalObject list[];
|
|
(list[0]$MalSymbol).value() => string name;
|
|
env.get(name) @=> MalObject macro;
|
|
MalObject.slice(list, 1) @=> MalObject args[];
|
|
|
|
if( macro.type == "subr" )
|
|
{
|
|
(macro$MalSubr).call(args) @=> ast;
|
|
}
|
|
else // macro.type == "func"
|
|
{
|
|
macro$Func @=> Func func;
|
|
Env.create(func.env, func.args, args) @=> Env eval_env;
|
|
EVAL(func.ast, eval_env) @=> ast;;
|
|
}
|
|
}
|
|
|
|
return ast;
|
|
}
|
|
|
|
fun MalObject EVAL(MalObject m, Env env)
|
|
{
|
|
while( true )
|
|
{
|
|
if( m.type != "list" )
|
|
{
|
|
return eval_ast(m, env);
|
|
}
|
|
|
|
if( (m$MalList).value().size() == 0 )
|
|
{
|
|
return m;
|
|
}
|
|
|
|
macroexpand(m, env) @=> m;
|
|
|
|
if( m.type != "list" )
|
|
{
|
|
return eval_ast(m, env);
|
|
}
|
|
|
|
(m$MalList).value() @=> MalObject ast[];
|
|
|
|
if( ast[0].type == "symbol" )
|
|
{
|
|
(ast[0]$MalSymbol).value() => string a0;
|
|
|
|
if( a0 == "def!" )
|
|
{
|
|
(ast[1]$MalSymbol).value() => string a1;
|
|
|
|
EVAL(ast[2], env) @=> MalObject value;
|
|
if( value.type == "error" )
|
|
{
|
|
return value;
|
|
}
|
|
|
|
env.set(a1, value);
|
|
return value;
|
|
}
|
|
else if( a0 == "let*" )
|
|
{
|
|
Env.create(env) @=> Env let_env;
|
|
Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[];
|
|
|
|
for( 0 => int i; i < bindings.size(); 2 +=> i)
|
|
{
|
|
(bindings[i]$MalSymbol).value() => string symbol;
|
|
EVAL(bindings[i+1], let_env) @=> MalObject value;
|
|
|
|
if( value.type == "error" )
|
|
{
|
|
return value;
|
|
}
|
|
|
|
let_env.set(symbol, value);
|
|
}
|
|
|
|
let_env @=> env;
|
|
ast[2] @=> m;
|
|
continue; // TCO
|
|
}
|
|
else if( a0 == "quote" )
|
|
{
|
|
return ast[1];
|
|
}
|
|
else if( a0 == "quasiquote" )
|
|
{
|
|
quasiquote(ast[1]) @=> m;
|
|
continue; // TCO
|
|
}
|
|
else if( a0 == "defmacro!" )
|
|
{
|
|
(ast[1]$MalSymbol).value() => string a1;
|
|
|
|
EVAL(ast[2], env) @=> MalObject value;
|
|
if( value.type == "error" )
|
|
{
|
|
return value;
|
|
}
|
|
|
|
true => (value$Func).isMacro;
|
|
|
|
env.set(a1, value);
|
|
return value;
|
|
}
|
|
else if( a0 == "macroexpand" )
|
|
{
|
|
return macroexpand(ast[1], env);
|
|
}
|
|
else if( a0 == "do" )
|
|
{
|
|
MalObject.slice(ast, 1, ast.size()-1) @=> MalObject forms[];
|
|
eval_ast(MalList.create(forms), env) @=> MalObject value;
|
|
|
|
if( value.type == "error" )
|
|
{
|
|
return value;
|
|
}
|
|
|
|
// HACK: this assumes do gets at least one argument...
|
|
ast[ast.size()-1] @=> m;
|
|
continue; // TCO
|
|
}
|
|
else if( a0 == "if" )
|
|
{
|
|
EVAL(ast[1], env) @=> MalObject condition;
|
|
|
|
if( condition.type == "error" )
|
|
{
|
|
return condition;
|
|
}
|
|
|
|
if( !(condition.type == "nil") && !(condition.type == "false") )
|
|
{
|
|
ast[2] @=> m;
|
|
continue; // TCO
|
|
}
|
|
else
|
|
{
|
|
if( ast.size() < 4 )
|
|
{
|
|
return Constants.NIL;
|
|
}
|
|
else
|
|
{
|
|
ast[3] @=> m;
|
|
continue; // TCO
|
|
}
|
|
}
|
|
}
|
|
else if( a0 == "fn*" )
|
|
{
|
|
(ast[1]$MalList).value() @=> MalObject arg_values[];
|
|
string args[arg_values.size()];
|
|
|
|
for( 0 => int i; i < arg_values.size(); i++ )
|
|
{
|
|
(arg_values[i]$MalSymbol).value() => args[i];
|
|
}
|
|
|
|
ast[2] @=> MalObject _ast;
|
|
|
|
return Func.create(env, args, _ast);
|
|
}
|
|
}
|
|
|
|
eval_ast(m, env) @=> MalObject result;
|
|
if( result.type == "error" )
|
|
{
|
|
return result;
|
|
}
|
|
|
|
(result$MalList).value() @=> MalObject values[];
|
|
values[0].type => string type;
|
|
MalObject.slice(values, 1) @=> MalObject args[];
|
|
|
|
if( type == "subr" )
|
|
{
|
|
values[0]$MalSubr @=> MalSubr subr;
|
|
return subr.call(args);
|
|
}
|
|
else // type == "func"
|
|
{
|
|
values[0]$Func @=> Func func;
|
|
Env.create(func.env, func.args, args) @=> Env eval_env;
|
|
eval_env @=> env;
|
|
func.ast @=> m;
|
|
continue; // TCO
|
|
}
|
|
}
|
|
}
|
|
|
|
fun MalObject eval_ast(MalObject m, Env env)
|
|
{
|
|
m.type => string type;
|
|
|
|
if( type == "symbol" )
|
|
{
|
|
(m$MalSymbol).value() => string symbol;
|
|
return env.get(symbol);
|
|
}
|
|
else if( type == "list" || type == "vector" || type == "hashmap" )
|
|
{
|
|
(m$MalList).value() @=> MalObject values[];
|
|
MalObject results[values.size()];
|
|
|
|
if( type != "hashmap" )
|
|
{
|
|
for( 0 => int i; i < values.size(); i++ )
|
|
{
|
|
EVAL(values[i], env) @=> MalObject result;
|
|
|
|
if( result.type == "error" )
|
|
{
|
|
return result;
|
|
}
|
|
|
|
result @=> results[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( 0 => int i; i < values.size(); i++ )
|
|
{
|
|
if( i % 2 == 0 )
|
|
{
|
|
values[i] @=> results[i];
|
|
}
|
|
else
|
|
{
|
|
EVAL(values[i], env) @=> results[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
if( type == "list" )
|
|
{
|
|
return MalList.create(results);
|
|
}
|
|
else if( type == "vector" )
|
|
{
|
|
return MalVector.create(results);
|
|
}
|
|
else if( type == "hashmap" )
|
|
{
|
|
return MalHashMap.create(results);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return m;
|
|
}
|
|
}
|
|
|
|
fun string PRINT(MalObject m)
|
|
{
|
|
return Printer.pr_str(m, true);
|
|
}
|
|
|
|
Env.create(null) @=> Env repl_env;
|
|
for( 0 => int i; i < Core.names.size(); i++ )
|
|
{
|
|
Core.names[i] => string name;
|
|
repl_env.set(name, Core.ns[name]);
|
|
}
|
|
|
|
// HACK, HACK, HACK
|
|
class MalEval extends MalSubr
|
|
{
|
|
fun MalObject call(MalObject args[])
|
|
{
|
|
args[0] @=> MalObject m;
|
|
return EVAL(args[0], repl_env);
|
|
}
|
|
|
|
fun MalObject apply(MalObject f, MalObject args[])
|
|
{
|
|
if( f.type == "subr" )
|
|
{
|
|
return (f$MalSubr).call(args);
|
|
}
|
|
else // f.type == "func"
|
|
{
|
|
f$Func @=> Func func;
|
|
Env.create(func.env, func.args, args) @=> Env eval_env;
|
|
return EVAL(func.ast, eval_env);
|
|
}
|
|
}
|
|
}
|
|
|
|
new MalEval @=> MalEval eval;
|
|
repl_env.set("eval", new MalEval);
|
|
eval @=> (repl_env.get("swap!")$MalSubr).eval;
|
|
|
|
fun MalObject[] MalArgv(string args[])
|
|
{
|
|
MalObject values[args.size()-1];
|
|
|
|
for( 1 => int i; i < args.size(); i++ )
|
|
{
|
|
MalString.create(args[i]) @=> values[i-1];
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
// NOTE: normally I'd use \0, but strings are null-terminated...
|
|
String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[];
|
|
repl_env.set("*ARGV*", MalList.create(MalArgv(args)));
|
|
|
|
fun string errorMessage(MalObject m)
|
|
{
|
|
(m$MalError).value() @=> MalObject value;
|
|
|
|
if( value.type == "string" )
|
|
{
|
|
return Printer.pr_str(value, false);
|
|
}
|
|
else
|
|
{
|
|
return "exception: " + Printer.pr_str(value, true);
|
|
}
|
|
}
|
|
|
|
fun string rep(string input)
|
|
{
|
|
READ(input) @=> MalObject m;
|
|
|
|
if( m.type == "error" )
|
|
{
|
|
return errorMessage(m);
|
|
}
|
|
|
|
EVAL(m, repl_env) @=> MalObject result;
|
|
if( result.type == "error" )
|
|
{
|
|
return errorMessage(result);
|
|
}
|
|
|
|
return PRINT(result);
|
|
}
|
|
|
|
rep("(def! not (fn* (a) (if a false true)))");
|
|
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
|
|
|
|
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)))))))");
|
|
rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))");
|
|
|
|
fun void main()
|
|
{
|
|
ConsoleInput stdin;
|
|
string input;
|
|
|
|
while( true )
|
|
{
|
|
stdin.prompt("user>") => now;
|
|
stdin.getLine() => input;
|
|
rep(input) => string output;
|
|
|
|
if( output == "empty input" )
|
|
{
|
|
// proceed immediately with prompt
|
|
}
|
|
else
|
|
{
|
|
Util.println(output);
|
|
}
|
|
}
|
|
}
|
|
|
|
if( args.size() > 1 )
|
|
{
|
|
args[1] => string filename;
|
|
rep("(load-file \"" + filename + "\")");
|
|
}
|
|
else
|
|
{
|
|
main();
|
|
}
|