2019-05-10 11:34:20 +03:00
|
|
|
class Mal.BuiltinFunctionEval : Mal.BuiltinFunction {
|
|
|
|
public Mal.Env env;
|
|
|
|
public BuiltinFunctionEval(Mal.Env env_) { env = env_; }
|
|
|
|
public override Mal.ValWithMetadata copy() {
|
|
|
|
return new Mal.BuiltinFunctionEval(env);
|
|
|
|
}
|
|
|
|
public override string name() { return "eval"; }
|
|
|
|
public override Mal.Val call(Mal.List args) throws Mal.Error {
|
|
|
|
if (args.vs.length() != 1)
|
|
|
|
throw new Mal.Error.BAD_PARAMS("%s: expected one argument", name());
|
|
|
|
return Mal.Main.EVAL(args.vs.data, env);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Mal.Main : GLib.Object {
|
|
|
|
static bool eof;
|
|
|
|
|
|
|
|
static construct {
|
|
|
|
eof = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Mal.Val? READ() {
|
|
|
|
string? line = Readline.readline("user> ");
|
|
|
|
if (line != null) {
|
|
|
|
if (line.length > 0)
|
|
|
|
Readline.History.add(line);
|
|
|
|
|
|
|
|
try {
|
|
|
|
return Reader.read_str(line);
|
|
|
|
} catch (Mal.Error err) {
|
|
|
|
GLib.stderr.printf("%s\n", err.message);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
stdout.printf("\n");
|
|
|
|
eof = true;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Mal.Val eval_ast(Mal.Val ast, Mal.Env env)
|
|
|
|
throws Mal.Error {
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
var roota = new GC.Root(ast); (void)roota;
|
|
|
|
var roote = new GC.Root(env); (void)roote;
|
2019-05-10 11:34:20 +03:00
|
|
|
if (ast is Mal.Sym)
|
|
|
|
return env.get(ast as Mal.Sym);
|
|
|
|
if (ast is Mal.List) {
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
var result = new Mal.List.empty();
|
|
|
|
var root = new GC.Root(result); (void)root;
|
2019-05-10 11:34:20 +03:00
|
|
|
foreach (var elt in (ast as Mal.List).vs)
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
result.vs.append(EVAL(elt, env));
|
|
|
|
return result;
|
2019-05-10 11:34:20 +03:00
|
|
|
}
|
|
|
|
if (ast is Mal.Vector) {
|
2019-07-16 23:10:02 +03:00
|
|
|
var vec = ast as Mal.Vector;
|
|
|
|
var result = new Mal.Vector.with_size(vec.length);
|
|
|
|
var root = new GC.Root(result); (void)root;
|
|
|
|
for (var i = 0; i < vec.length; i++)
|
|
|
|
result[i] = EVAL(vec[i], env);
|
|
|
|
return result;
|
2019-05-10 11:34:20 +03:00
|
|
|
}
|
|
|
|
if (ast is Mal.Hashmap) {
|
|
|
|
var result = new Mal.Hashmap();
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
var root = new GC.Root(result); (void)root;
|
2019-05-10 11:34:20 +03:00
|
|
|
var map = (ast as Mal.Hashmap).vs;
|
|
|
|
foreach (var key in map.get_keys())
|
|
|
|
result.insert(key, EVAL(map[key], env));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
return ast;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Mal.Val define_eval(Mal.Val key, Mal.Val value,
|
2019-07-15 23:46:55 +03:00
|
|
|
Mal.Env env,
|
2019-05-10 11:34:20 +03:00
|
|
|
bool is_macro = false)
|
|
|
|
throws Mal.Error {
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
var rootk = new GC.Root(key); (void)rootk;
|
2019-07-15 23:46:55 +03:00
|
|
|
var roote = new GC.Root(env); (void)roote;
|
2019-05-10 11:34:20 +03:00
|
|
|
var symkey = key as Mal.Sym;
|
|
|
|
if (symkey == null)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"let*: expected a symbol to define");
|
2019-07-15 23:46:55 +03:00
|
|
|
var val = EVAL(value, env);
|
2019-05-10 11:34:20 +03:00
|
|
|
if (val is Mal.Function)
|
|
|
|
(val as Mal.Function).is_macro = is_macro;
|
2019-07-15 23:46:55 +03:00
|
|
|
env.set(symkey, val);
|
2019-05-10 11:34:20 +03:00
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2020-07-21 19:01:48 +03:00
|
|
|
// If ast is (sym x), return x, else return null.
|
|
|
|
public static Mal.Val? unquoted (Mal.Val ast,
|
|
|
|
string sym)
|
|
|
|
throws Mal.Error {
|
|
|
|
var list = ast as Mal.List;
|
|
|
|
if (list == null || list.vs == null) return null;
|
|
|
|
var a0 = list.vs.data as Mal.Sym;
|
|
|
|
if (a0 == null || a0.v != sym) return null;
|
|
|
|
if (list.vs.next == null || list.vs.next.next != null)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(sym + ": wrong arg count");
|
|
|
|
return list.vs.next.data;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Mal.Val qq_loop(Mal.Val elt,
|
|
|
|
Mal.Val acc)
|
2019-05-10 11:34:20 +03:00
|
|
|
throws Mal.Error {
|
2020-07-21 19:01:48 +03:00
|
|
|
var list = new Mal.List.empty();
|
|
|
|
var unq = unquoted(elt, "splice-unquote");
|
|
|
|
if (unq != null) {
|
|
|
|
list.vs.append(new Mal.Sym("concat"));
|
|
|
|
list.vs.append(unq);
|
|
|
|
} else {
|
|
|
|
list.vs.append(new Mal.Sym("cons"));
|
|
|
|
list.vs.append(quasiquote (elt));
|
2019-05-10 11:34:20 +03:00
|
|
|
}
|
2020-07-21 19:01:48 +03:00
|
|
|
list.vs.append(acc);
|
|
|
|
return list;
|
|
|
|
}
|
2019-05-10 11:34:20 +03:00
|
|
|
|
2020-07-21 19:01:48 +03:00
|
|
|
public static Mal.Val qq_foldr(Mal.Iterator xs)
|
|
|
|
throws Mal.Error {
|
|
|
|
if (xs.empty()) {
|
|
|
|
return new Mal.List.empty();
|
|
|
|
} else {
|
|
|
|
var elt = xs.deref();
|
|
|
|
xs.step();
|
|
|
|
return qq_loop(elt, qq_foldr(xs));
|
2019-05-10 11:34:20 +03:00
|
|
|
}
|
2020-07-21 19:01:48 +03:00
|
|
|
}
|
2019-05-10 11:34:20 +03:00
|
|
|
|
2020-07-21 19:01:48 +03:00
|
|
|
public static Mal.Val quasiquote(Mal.Val ast)
|
|
|
|
throws Mal.Error {
|
|
|
|
if (ast is Mal.List) {
|
|
|
|
var unq = unquoted(ast, "unquote");
|
|
|
|
if (unq != null) {
|
|
|
|
return unq;
|
|
|
|
} else {
|
|
|
|
return qq_foldr((ast as Mal.List).iter());
|
2019-05-10 11:34:20 +03:00
|
|
|
}
|
2020-07-21 19:01:48 +03:00
|
|
|
} else if (ast is Mal.Vector) {
|
|
|
|
var list = new Mal.List.empty();
|
|
|
|
list.vs.append(new Mal.Sym("vec"));
|
|
|
|
list.vs.append(qq_foldr((ast as Mal.Vector).iter()));
|
|
|
|
return list;
|
|
|
|
} else if (ast is Mal.Sym || ast is Mal.Hashmap) {
|
|
|
|
var list = new Mal.List.empty();
|
|
|
|
list.vs.append(new Mal.Sym("quote"));
|
|
|
|
list.vs.append(ast);
|
|
|
|
return list;
|
|
|
|
} else {
|
|
|
|
return ast;
|
2019-05-10 11:34:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static bool is_macro_call(Mal.Val v, Mal.Env env) {
|
|
|
|
var list = v as Mal.List;
|
|
|
|
if (list == null || list.vs == null || !(list.vs.data is Mal.Sym))
|
|
|
|
return false;
|
|
|
|
try {
|
|
|
|
var fn = env.get(list.vs.data as Mal.Sym) as Mal.Function;
|
|
|
|
return (fn != null && fn.is_macro);
|
|
|
|
} catch (Mal.Error err) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Mal.Val macroexpand(Mal.Val ast_, Mal.Env env)
|
|
|
|
throws Mal.Error {
|
|
|
|
Mal.Val ast = ast_;
|
|
|
|
while (is_macro_call(ast, env)) {
|
|
|
|
var call = ast as Mal.List;
|
|
|
|
var macro = (env.get(call.vs.data as Mal.Sym) as Mal.Function);
|
|
|
|
var macroargs = new Mal.List(call.vs.copy());
|
|
|
|
macroargs.vs.remove_link(macroargs.vs.first());
|
|
|
|
var fnenv = new Mal.Env.funcall(
|
|
|
|
macro.env, macro.parameters, macroargs);
|
|
|
|
ast = Mal.Main.EVAL(macro.body, fnenv);
|
|
|
|
}
|
|
|
|
return ast;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Mal.Val EVAL(Mal.Val ast_, Mal.Env env_)
|
|
|
|
throws Mal.Error {
|
|
|
|
// Copy the implicitly 'unowned' function arguments into
|
|
|
|
// ordinary owned variables which increment the objects'
|
|
|
|
// reference counts. This is so that when we overwrite these
|
|
|
|
// variables within the loop (for TCO) the objects we assign
|
|
|
|
// into them don't immediately get garbage-collected.
|
|
|
|
Mal.Val ast = ast_;
|
|
|
|
Mal.Env env = env_;
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
var ast_root = new GC.Root(ast); (void)ast_root;
|
|
|
|
var env_root = new GC.Root(env); (void)env_root;
|
2019-05-10 11:34:20 +03:00
|
|
|
while (true) {
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
ast_root.obj = ast;
|
|
|
|
env_root.obj = env;
|
|
|
|
GC.Core.maybe_collect();
|
2019-05-10 11:34:20 +03:00
|
|
|
ast = macroexpand(ast, env);
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
ast_root.obj = ast;
|
2019-05-10 11:34:20 +03:00
|
|
|
if (ast is Mal.List) {
|
|
|
|
unowned GLib.List<Mal.Val> list = (ast as Mal.List).vs;
|
|
|
|
if (list.first() == null)
|
|
|
|
return ast;
|
|
|
|
|
|
|
|
var first = list.first().data;
|
|
|
|
if (first is Mal.Sym) {
|
|
|
|
var sym = first as Mal.Sym;
|
|
|
|
switch (sym.v) {
|
|
|
|
case "def!":
|
|
|
|
case "defmacro!":
|
|
|
|
if (list.length() != 3)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"def!: expected two values");
|
|
|
|
return define_eval(list.next.data, list.next.next.data,
|
2019-07-15 23:46:55 +03:00
|
|
|
env, sym.v == "defmacro!");
|
2019-05-10 11:34:20 +03:00
|
|
|
case "let*":
|
|
|
|
if (list.length() != 3)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"let*: expected two values");
|
|
|
|
var defns = list.nth(1).data;
|
|
|
|
env = new Mal.Env.within(env);
|
|
|
|
|
|
|
|
if (defns is Mal.List) {
|
|
|
|
for (unowned GLib.List<Mal.Val> iter =
|
|
|
|
(defns as Mal.List).vs;
|
|
|
|
iter != null; iter = iter.next.next) {
|
|
|
|
if (iter.next == null)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"let*: expected an even-length list" +
|
|
|
|
" of definitions");
|
2019-07-15 23:46:55 +03:00
|
|
|
define_eval(iter.data, iter.next.data, env);
|
2019-05-10 11:34:20 +03:00
|
|
|
}
|
|
|
|
} else if (defns is Mal.Vector) {
|
2019-05-12 12:27:30 +03:00
|
|
|
var vec = defns as Mal.Vector;
|
2019-05-10 11:34:20 +03:00
|
|
|
if (vec.length % 2 != 0)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"let*: expected an even-length vector" +
|
|
|
|
" of definitions");
|
|
|
|
for (var i = 0; i < vec.length; i += 2)
|
2019-07-15 23:46:55 +03:00
|
|
|
define_eval(vec[i], vec[i+1], env);
|
2019-05-10 11:34:20 +03:00
|
|
|
} else {
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"let*: expected a list or vector of definitions");
|
|
|
|
}
|
|
|
|
ast = list.nth(2).data;
|
|
|
|
continue; // tail-call optimisation
|
|
|
|
case "do":
|
|
|
|
Mal.Val result = null;
|
|
|
|
for (list = list.next; list != null; list = list.next)
|
|
|
|
result = EVAL(list.data, env);
|
|
|
|
if (result == null)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"do: expected at least one argument");
|
|
|
|
return result;
|
|
|
|
case "if":
|
|
|
|
if (list.length() != 3 && list.length() != 4)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"if: expected two or three arguments");
|
|
|
|
list = list.next;
|
|
|
|
var cond = EVAL(list.data, env);
|
|
|
|
list = list.next;
|
|
|
|
if (!cond.truth_value()) {
|
|
|
|
// Skip to the else clause, which defaults to nil.
|
|
|
|
list = list.next;
|
|
|
|
if (list == null)
|
|
|
|
return new Mal.Nil();
|
|
|
|
}
|
|
|
|
ast = list.data;
|
|
|
|
continue; // tail-call optimisation
|
|
|
|
case "fn*":
|
|
|
|
if (list.length() != 3)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"fn*: expected two arguments");
|
|
|
|
var binds = list.next.data as Mal.Listlike;
|
|
|
|
var body = list.next.next.data;
|
|
|
|
if (binds == null)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"fn*: expected a list of parameter names");
|
|
|
|
for (var iter = binds.iter(); iter.nonempty();
|
|
|
|
iter.step())
|
|
|
|
if (!(iter.deref() is Mal.Sym))
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"fn*: expected parameter name to be "+
|
|
|
|
"symbol");
|
|
|
|
return new Mal.Function(binds, body, env);
|
|
|
|
case "quote":
|
|
|
|
if (list.length() != 2)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"quote: expected one argument");
|
|
|
|
return list.next.data;
|
2020-07-21 19:01:48 +03:00
|
|
|
case "quasiquoteexpand":
|
|
|
|
if (list.length() != 2)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"quasiquoteexpand: expected one argument");
|
|
|
|
return quasiquote(list.next.data);
|
2019-05-10 11:34:20 +03:00
|
|
|
case "quasiquote":
|
|
|
|
if (list.length() != 2)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"quasiquote: expected one argument");
|
|
|
|
ast = quasiquote(list.next.data);
|
|
|
|
continue; // tail-call optimisation
|
|
|
|
case "macroexpand":
|
|
|
|
if (list.length() != 2)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"macroexpand: expected one argument");
|
|
|
|
return macroexpand(list.next.data, env);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var newlist = eval_ast(ast, env) as Mal.List;
|
|
|
|
unowned GLib.List<Mal.Val> firstlink = newlist.vs.first();
|
|
|
|
Mal.Val firstdata = firstlink.data;
|
|
|
|
newlist.vs.remove_link(firstlink);
|
|
|
|
|
|
|
|
if (firstdata is Mal.BuiltinFunction) {
|
|
|
|
return (firstdata as Mal.BuiltinFunction).call(newlist);
|
|
|
|
} else if (firstdata is Mal.Function) {
|
|
|
|
var fn = firstdata as Mal.Function;
|
|
|
|
env = new Mal.Env.funcall(fn.env, fn.parameters, newlist);
|
|
|
|
ast = fn.body;
|
|
|
|
continue; // tail-call optimisation
|
|
|
|
} else {
|
|
|
|
throw new Mal.Error.CANNOT_APPLY(
|
|
|
|
"bad value at start of list");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return eval_ast(ast, env);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void PRINT(Mal.Val value) {
|
|
|
|
stdout.printf("%s\n", pr_str(value));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void rep(Mal.Env env) throws Mal.Error {
|
|
|
|
Mal.Val? val = READ();
|
|
|
|
if (val != null) {
|
|
|
|
val = EVAL(val, env);
|
|
|
|
PRINT(val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void setup(string line, Mal.Env env) {
|
|
|
|
try {
|
|
|
|
EVAL(Reader.read_str(line), env);
|
|
|
|
} catch (Mal.Error err) {
|
|
|
|
assert(false); // shouldn't happen
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static int main(string[] args) {
|
|
|
|
var env = new Mal.Env();
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
var root = new GC.Root(env); (void)root;
|
2019-05-10 11:34:20 +03:00
|
|
|
|
|
|
|
Mal.Core.make_ns();
|
|
|
|
foreach (var key in Mal.Core.ns.get_keys())
|
|
|
|
env.set(new Mal.Sym(key), Mal.Core.ns[key]);
|
|
|
|
env.set(new Mal.Sym("eval"), new Mal.BuiltinFunctionEval(env));
|
|
|
|
|
|
|
|
setup("(def! not (fn* (a) (if a false true)))", env);
|
2019-07-16 00:57:02 +03:00
|
|
|
setup("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", env);
|
2019-05-10 11:34:20 +03:00
|
|
|
setup("(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)))))))", env);
|
|
|
|
|
|
|
|
var ARGV = new GLib.List<Mal.Val>();
|
|
|
|
if (args.length > 1) {
|
|
|
|
for (int i = args.length - 1; i >= 2; i--)
|
|
|
|
ARGV.prepend(new Mal.String(args[i]));
|
|
|
|
}
|
|
|
|
env.set(new Mal.Sym("*ARGV*"), new Mal.List(ARGV));
|
|
|
|
|
|
|
|
if (args.length > 1) {
|
|
|
|
var contents = new GLib.List<Mal.Val>();
|
|
|
|
contents.prepend(new Mal.String(args[1]));
|
|
|
|
contents.prepend(new Mal.Sym("load-file"));
|
|
|
|
try {
|
|
|
|
EVAL(new Mal.List(contents), env);
|
|
|
|
} catch (Mal.Error err) {
|
|
|
|
GLib.stderr.printf("%s\n", err.message);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
while (!eof) {
|
|
|
|
try {
|
|
|
|
rep(env);
|
|
|
|
} catch (Mal.Error err) {
|
|
|
|
GLib.stderr.printf("%s\n", err.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|