mirror of
https://github.com/kanaka/mal.git
synced 2024-11-11 08:56:41 +03:00
20858d2916
Issue #166
399 lines
10 KiB
D
399 lines
10 KiB
D
import core.time;
|
|
import std.algorithm;
|
|
import std.array;
|
|
import std.datetime;
|
|
import std.file;
|
|
import std.stdio;
|
|
import env;
|
|
import main;
|
|
import reader;
|
|
import readline;
|
|
import types;
|
|
import printer;
|
|
|
|
static MalType mal_equal(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 2);
|
|
return bool_to_mal(a[0] == a[1]);
|
|
}
|
|
|
|
static MalType mal_throw(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
throw new MalException(a[0]);
|
|
}
|
|
|
|
static MalType mal_symbol(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto s = verify_cast!MalString(a[0]);
|
|
return new MalSymbol(s.val);
|
|
}
|
|
|
|
static MalType mal_string_q(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto s = cast(MalString) a[0];
|
|
if (s is null) return mal_false;
|
|
return bool_to_mal(!s.is_keyword());
|
|
}
|
|
|
|
static MalType mal_keyword(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto s = verify_cast!MalString(a[0]);
|
|
return new MalString("\u029e" ~ s.val);
|
|
}
|
|
|
|
static MalType mal_keyword_q(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto s = cast(MalString) a[0];
|
|
if (s is null) return mal_false;
|
|
return bool_to_mal(s.is_keyword());
|
|
}
|
|
|
|
static MalType mal_pr_str(MalType[] a ...)
|
|
{
|
|
auto items_strs = a.map!(e => pr_str(e, true));
|
|
return new MalString(array(items_strs).join(" "));
|
|
}
|
|
|
|
static MalType mal_str(MalType[] a ...)
|
|
{
|
|
auto items_strs = a.map!(e => pr_str(e, false));
|
|
return new MalString(array(items_strs).join(""));
|
|
}
|
|
|
|
static MalType mal_prn(MalType[] a ...)
|
|
{
|
|
auto items_strs = a.map!(e => pr_str(e, true));
|
|
writeln(array(items_strs).join(" "));
|
|
return mal_nil;
|
|
}
|
|
|
|
static MalType mal_println(MalType[] a ...)
|
|
{
|
|
auto items_strs = a.map!(e => pr_str(e, false));
|
|
writeln(array(items_strs).join(" "));
|
|
return mal_nil;
|
|
}
|
|
|
|
static MalType mal_read_string(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto s = verify_cast!MalString(a[0]);
|
|
return read_str(s.val);
|
|
}
|
|
|
|
static MalType mal_readline(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto s = verify_cast!MalString(a[0]);
|
|
auto line = _readline(s.val);
|
|
return line is null ? mal_nil : new MalString(line);
|
|
}
|
|
|
|
static MalType mal_slurp(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto filename = verify_cast!MalString(a[0]).val;
|
|
auto content = cast(string) std.file.read(filename);
|
|
return new MalString(content);
|
|
}
|
|
|
|
alias TwoIntFunc = MalType function(long x, long y);
|
|
|
|
MalType binary_int_op(TwoIntFunc f, MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 2);
|
|
MalInteger i0 = verify_cast!MalInteger(a[0]);
|
|
MalInteger i1 = verify_cast!MalInteger(a[1]);
|
|
return f(i0.val, i1.val);
|
|
}
|
|
|
|
static MalType mal_time_ms(MalType[] a ...)
|
|
{
|
|
immutable epoch = SysTime(unixTimeToStdTime(0));
|
|
immutable hnsecs_since_epoch = Clock.currTime(UTC()) - epoch;
|
|
immutable ms = hnsecs_since_epoch.total!"msecs"();
|
|
return new MalInteger(ms);
|
|
}
|
|
|
|
static bool is_nil(MalType v)
|
|
{
|
|
return cast(MalNil)(v) !is null;
|
|
}
|
|
|
|
static MalType mal_assoc(MalType[] a ...)
|
|
{
|
|
verify_min_args_count(a, 1);
|
|
auto hm = verify_cast!MalHashmap(a[0]);
|
|
auto new_hm = new MalHashmap(hm.data.dup);
|
|
new_hm.put_kv_list(a[1..$]);
|
|
return new_hm;
|
|
}
|
|
|
|
static MalType mal_dissoc(MalType[] a ...)
|
|
{
|
|
verify_min_args_count(a, 1);
|
|
auto hm = verify_cast!MalHashmap(a[0]);
|
|
auto new_hm = new MalHashmap(hm.data.dup);
|
|
foreach (k; a[1..$])
|
|
{
|
|
new_hm.remove(k);
|
|
}
|
|
return new_hm;
|
|
}
|
|
|
|
static MalType mal_get(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 2);
|
|
if (is_nil(a[0])) return mal_nil;
|
|
auto hm = verify_cast!MalHashmap(a[0]);
|
|
return hm.get(a[1]);
|
|
}
|
|
|
|
static MalType mal_contains_q(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 2);
|
|
if (is_nil(a[0])) return mal_false;
|
|
auto hm = verify_cast!MalHashmap(a[0]);
|
|
return bool_to_mal(hm.contains(a[1]));
|
|
}
|
|
|
|
static MalType mal_keys(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto hm = verify_cast!MalHashmap(a[0]);
|
|
auto keys = hm.data.keys.map!(s => cast(MalType)(new MalString(s)));
|
|
return new MalList(array(keys));
|
|
}
|
|
|
|
static MalType mal_vals(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto hm = verify_cast!MalHashmap(a[0]);
|
|
return new MalList(hm.data.values);
|
|
}
|
|
|
|
static MalType mal_cons(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 2);
|
|
auto lst = verify_cast!MalSequential(a[1]);
|
|
return new MalList([a[0]] ~ lst.elements);
|
|
}
|
|
|
|
static MalType mal_concat(MalType[] a ...)
|
|
{
|
|
MalType[] res;
|
|
foreach (e; a)
|
|
{
|
|
auto lst = verify_cast!MalSequential(e);
|
|
res ~= lst.elements;
|
|
}
|
|
return new MalList(res);
|
|
}
|
|
|
|
static MalType mal_nth(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 2);
|
|
if (is_nil(a[0]))
|
|
{
|
|
throw new Exception("nth: index out of range");
|
|
}
|
|
auto seq = verify_cast!MalSequential(a[0]);
|
|
auto index = verify_cast!MalInteger(a[1]).val;
|
|
if (index >= seq.elements.length)
|
|
{
|
|
throw new Exception("nth: index out of range");
|
|
}
|
|
return seq.elements[index];
|
|
}
|
|
|
|
static MalType mal_first(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
if (is_nil(a[0])) return mal_nil;
|
|
auto seq = verify_cast!MalSequential(a[0]);
|
|
if (seq.elements.length == 0) return mal_nil;
|
|
return seq.elements[0];
|
|
}
|
|
|
|
static MalType mal_rest(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
if (is_nil(a[0])) return new MalList([]);
|
|
auto seq = verify_cast!MalSequential(a[0]);
|
|
if (seq.elements.length == 0) return new MalList([]);
|
|
return new MalList(seq.elements[1..$]);
|
|
}
|
|
|
|
|
|
static MalType mal_empty_q(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
if (is_nil(a[0]))
|
|
{
|
|
return mal_true;
|
|
}
|
|
auto s = verify_cast!MalSequential(a[0]);
|
|
return bool_to_mal(s.elements.length == 0);
|
|
}
|
|
|
|
static MalType mal_count(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
if (is_nil(a[0]))
|
|
{
|
|
return new MalInteger(0);
|
|
}
|
|
auto s = verify_cast!MalSequential(a[0]);
|
|
return new MalInteger(cast(int)(s.elements.length));
|
|
}
|
|
|
|
static MalType mal_apply(MalType[] a ...)
|
|
{
|
|
verify_min_args_count(a, 2);
|
|
auto last_seq_elems = verify_cast!MalSequential(a[$-1]).elements;
|
|
auto funcargs = a.length == 2 ? last_seq_elems : (a[1..$-1] ~ last_seq_elems);
|
|
|
|
auto builtinfn = cast(MalBuiltinFunc) a[0];
|
|
if (builtinfn !is null)
|
|
{
|
|
return builtinfn.fn(funcargs);
|
|
}
|
|
|
|
auto malfunc = verify_cast!MalFunc(a[0]);
|
|
auto callenv = new Env(malfunc.def_env, malfunc.arg_names, funcargs);
|
|
|
|
return EVAL(malfunc.func_body, callenv);
|
|
}
|
|
|
|
static MalType mal_map(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 2);
|
|
auto seq = verify_cast!MalSequential(a[1]);
|
|
auto mapped_items = seq.elements.map!(e => mal_apply(a[0], new MalList([e])));
|
|
return new MalList(array(mapped_items));
|
|
}
|
|
|
|
static MalType mal_conj(MalType[] a ...)
|
|
{
|
|
verify_min_args_count(a, 1);
|
|
auto seq = verify_cast!MalSequential(a[0]);
|
|
return reduce!((s,e) => s.conj(e))(seq, a[1..$]);
|
|
}
|
|
|
|
static MalType mal_seq(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto seqobj = cast(HasSeq) a[0];
|
|
if (seqobj is null) return mal_nil;
|
|
return seqobj.seq();
|
|
}
|
|
|
|
static MalType mal_meta(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 1);
|
|
auto metaobj = cast(MalMeta) a[0];
|
|
if (metaobj is null) return mal_nil;
|
|
return metaobj.meta();
|
|
}
|
|
|
|
static MalType mal_with_meta(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 2);
|
|
auto metaobj = cast(MalMeta) a[0];
|
|
if (metaobj is null) return a[0];
|
|
return metaobj.with_meta(a[1]);
|
|
}
|
|
|
|
static MalType mal_reset_bang(MalType[] a ...)
|
|
{
|
|
verify_args_count(a, 2);
|
|
verify_cast!MalAtom(a[0]).val = a[1];
|
|
return a[1];
|
|
}
|
|
|
|
static MalType mal_swap_bang(MalType[] a ...)
|
|
{
|
|
verify_min_args_count(a, 2);
|
|
auto atom = verify_cast!MalAtom(a[0]);
|
|
auto args = [atom.val] ~ a[2..$];
|
|
auto newval = mal_apply([a[1], new MalList(args)]);
|
|
return mal_reset_bang([atom, newval]);
|
|
}
|
|
|
|
BuiltinStaticFuncType[string] core_ns;
|
|
|
|
static this()
|
|
{
|
|
core_ns = [
|
|
"=": &mal_equal,
|
|
"throw": &mal_throw,
|
|
|
|
"nil?": (a ...) => mal_type_q!MalNil(a),
|
|
"true?": (a ...) => mal_type_q!MalTrue(a),
|
|
"false?": (a ...) => mal_type_q!MalFalse(a),
|
|
"symbol": &mal_symbol,
|
|
"symbol?": (a ...) => mal_type_q!MalSymbol(a),
|
|
"string?": &mal_string_q,
|
|
"keyword": &mal_keyword,
|
|
"keyword?": &mal_keyword_q,
|
|
|
|
"pr-str": &mal_pr_str,
|
|
"str": &mal_str,
|
|
"prn": &mal_prn,
|
|
"println": &mal_println,
|
|
"read-string": &mal_read_string,
|
|
"readline": &mal_readline,
|
|
"slurp": &mal_slurp,
|
|
|
|
"<": (a ...) => binary_int_op((x,y) => bool_to_mal(x < y), a),
|
|
"<=": (a ...) => binary_int_op((x,y) => bool_to_mal(x <= y), a),
|
|
">": (a ...) => binary_int_op((x,y) => bool_to_mal(x > y), a),
|
|
">=": (a ...) => binary_int_op((x,y) => bool_to_mal(x >= y), a),
|
|
"+": (a ...) => binary_int_op((x,y) => new MalInteger(x + y), a),
|
|
"-": (a ...) => binary_int_op((x,y) => new MalInteger(x - y), a),
|
|
"*": (a ...) => binary_int_op((x,y) => new MalInteger(x * y), a),
|
|
"/": (a ...) => binary_int_op((x,y) => new MalInteger(x / y), a),
|
|
"time-ms": &mal_time_ms,
|
|
|
|
"list": (a ...) => new MalList(a),
|
|
"list?": (a ...) => mal_type_q!MalList(a),
|
|
"vector": (a ...) => new MalVector(a),
|
|
"vector?": (a ...) => mal_type_q!MalVector(a),
|
|
"hash-map": (a ...) => new MalHashmap(a),
|
|
"map?": (a ...) => mal_type_q!MalHashmap(a),
|
|
"assoc": &mal_assoc,
|
|
"dissoc": &mal_dissoc,
|
|
"get": &mal_get,
|
|
"contains?": &mal_contains_q,
|
|
"keys": &mal_keys,
|
|
"vals": &mal_vals,
|
|
|
|
"sequential?": (a ...) => mal_type_q!MalSequential(a),
|
|
"cons": &mal_cons,
|
|
"concat": &mal_concat,
|
|
"nth": &mal_nth,
|
|
"first": &mal_first,
|
|
"rest": &mal_rest,
|
|
"empty?": &mal_empty_q,
|
|
"count": &mal_count,
|
|
"apply": &mal_apply,
|
|
"map": &mal_map,
|
|
|
|
"conj": &mal_conj,
|
|
"seq": &mal_seq,
|
|
|
|
"meta": &mal_meta,
|
|
"with-meta": &mal_with_meta,
|
|
"atom": (a ...) => new MalAtom(verify_args_count(a, 1)[0]),
|
|
"atom?": (a ...) => mal_type_q!MalAtom(a),
|
|
"deref": (a ...) => verify_cast!MalAtom(verify_args_count(a, 1)[0]).val,
|
|
"reset!": &mal_reset_bang,
|
|
"swap!": &mal_swap_bang
|
|
];
|
|
}
|