From 2d6c1880ff0c3ed4315dab81ffaae496239ca146 Mon Sep 17 00:00:00 2001 From: Nathan Braswell Date: Tue, 10 Mar 2020 12:57:28 -0400 Subject: [PATCH] MaL implementation in Kraken --- mal.krak | 1578 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1578 insertions(+) create mode 100644 mal.krak diff --git a/mal.krak b/mal.krak new file mode 100644 index 0000000..8c4c70e --- /dev/null +++ b/mal.krak @@ -0,0 +1,1578 @@ +import io:* +import str:* +import vec:* +import vec_literals:* +import util:* +import map:* + +fun tokenize(s: str): vec { + var to_ret = vec() + for (var i = 0; i < s.length(); i++;) { + if (s[i] == ' ' || s[i] == '\t' || s[i] == ',' || s[i] == '\n') { + i++ + while (i < s.length() && (s[i] == ' ' || s[i] == '\t' || s[i] == ',' || s[i] == '\n')) { + i++; + } + i-- + } else if (i+1 < s.length() && s[i] == '~' && s[i+1] == '@') { + to_ret.add(str("~@")) + i++ + } else if ( s[i] == '[' || s[i] == ']' || s[i] == '{' || s[i] == '}' || s[i] == '(' || s[i] == ')' || s[i] == '\'' || s[i] == '`' || s[i] == '~' || s[i] == '^' || s[i] == '@') { // ' + to_ret.add(str(s[i])) + } else if (s[i] == ';') { + var next_tok = str(s[i]) + i++ + while (i < s.length() && (s[i] != '\n')) { + next_tok += s[i] + i++ + } + i-- + } else if (s[i] == '"') { + var str_start = i + var next_tok = str(s[i]) + i++ + while (i < s.length()) { + next_tok += s[i] + if s[i] == '"' { + var backslash_count = 0 + while ((i-backslash_count)-1 > str_start && s[(i-backslash_count)-1] == '\\') { + backslash_count++ + } + // even number of backslashes + if (backslash_count & 1) == 0 { + break + } + } + i++ + } + to_ret.add(next_tok) + } else { + var next_tok = str(s[i]) + i++ + while (i < s.length() && !( s[i] == ' ' || s[i] == '\t' || s[i] == ',' || s[i] == '\n' || s[i] == '[' || s[i] == ']' || s[i] == '{' || s[i] == '}' || s[i] == '(' || s[i] == ')' || s[i] == '\'' || s[i] == '`' || s[i] == '~' || s[i] == '^' || s[i] == '@' || s[i] == ';')) { // ' + next_tok += s[i] + i++ + } + to_ret.add(next_tok) + i-- + } + } + return to_ret +} + + +fun read_form(tokens: *vec, i: *int): MalResult { + if ((*tokens)[*i] == "(" || (*tokens)[*i] == "[") { + return read_list(tokens, i, (*tokens)[*i] == "[") + } else if ((*tokens)[*i] == "{") { + return read_dict(tokens, i) + } else if ((*tokens)[*i] == "@") { + (*i)++; + var inner = read_form(tokens, i) + if (is_err(inner)) { + return inner + } + return MalResult::Ok(MalValue::List(vec(MalValue::Symbol(str("deref")), get_value(inner)))) + } else if ((*tokens)[*i] == "'") { + (*i)++; + var inner = read_form(tokens, i) + if (is_err(inner)) { + return inner + } + return MalResult::Ok(MalValue::List(vec(MalValue::Symbol(str("quote")), get_value(inner)))) + } else if ((*tokens)[*i] == "`") { + (*i)++; + var inner = read_form(tokens, i) + if (is_err(inner)) { + return inner + } + return MalResult::Ok(MalValue::List(vec(MalValue::Symbol(str("quasiquote")), get_value(inner)))) + } else if ((*tokens)[*i] == "~") { + (*i)++; + var inner = read_form(tokens, i) + if (is_err(inner)) { + return inner + } + return MalResult::Ok(MalValue::List(vec(MalValue::Symbol(str("unquote")), get_value(inner)))) + } else if ((*tokens)[*i] == "~@") { + (*i)++; + var inner = read_form(tokens, i) + if (is_err(inner)) { + return inner + } + return MalResult::Ok(MalValue::List(vec(MalValue::Symbol(str("splice-unquote")), get_value(inner)))) + } else { + return read_atom(tokens, i) + } +} +fun read_dict(tokens: *vec, i: *int): MalResult { + (*i)++; + var values = vec() + while (*i < tokens->size && (*tokens)[*i] != "}") { + var result = read_form(tokens, i) + if (is_err(result)) { + return result + } + values.add(get_value(result)) + } + if (*i == tokens->size) { + return MalResult::Err(MalValue::String(str("unbalanced {"))) + } + (*i)++; + return create_map(values) +} +fun create_map(values: vec): MalResult { + var to_ret = map() + if values.size & 1 == 1 { + return MalResult::Err(MalValue::String(str("odd number of keys/values"))) + } + for (var i = 0; i < values.size; i+=2;) { + to_ret.set(values[i], values[i+1]) + } + return MalResult::Ok(MalValue::Map(to_ret)) +} +fun is_map(m: MalValue): bool { + match (m) { + MalValue::Map(m) { + return true + } + MalValue::Nil() { + return true + } + } + return false +} +fun get_map(m: MalValue): map { + match (m) { + MalValue::Map(m) { + return m + } + MalValue::Nil() { + return map() + } + } + error("can't get_map not a map") +} +fun read_list(tokens: *vec, i: *int, is_vec: bool): MalResult { + var to_ret = vec() + (*i)++; + while (*i < tokens->size && ((!is_vec &&(*tokens)[*i] != ")") || (is_vec &&(*tokens)[*i] != "]"))) { + var result = read_form(tokens, i) + if (is_err(result)) { + return result + } + to_ret.add(get_value(result)) + } + if (*i == tokens->size) { + if is_vec { + return MalResult::Err(MalValue::String(str("unbalanced ["))) + } else { + return MalResult::Err(MalValue::String(str("unbalanced ("))) + } + } + (*i)++; + if is_vec { + return MalResult::Ok(MalValue::Vector(to_ret)) + } else { + return MalResult::Ok(MalValue::List(to_ret)) + } +} +fun read_atom(tokens: *vec, i: *int): MalResult { + var all_num = true + var token = (*tokens)[*i] + (*i)++ + for (var j = 0; j < token.length(); j++;) { + all_num = all_num && ((j == 0 && token[j] == '-' && token.length() > 1) || token[j] >= '0' && token[j] <= '9') + } + if (all_num) { + return MalResult::Ok(MalValue::Int(string_to_num(token))) + } else if (token == "true") { + return MalResult::Ok(MalValue::True()) + } else if (token == "false") { + return MalResult::Ok(MalValue::False()) + } else if (token == "nil") { + return MalResult::Ok(MalValue::Nil()) + } else if (token[0] == '"') { + var to_ret = str() + if token.length() == 1 || token[token.length()-1] != '"' { + return MalResult::Err(MalValue::String(str("unbalanced \""))) //" + } + for (var i = 1; i < token.length()-1; i++;) { + if token[i] == '\\' { + if i == token.length()-2 { + return MalResult::Err(MalValue::String(str("unbalanced \""))) //" + } + if token[i+1] == 'n' { + to_ret += '\n' + } else if token[i+1] == '\\' || token[i+1] == '"' { + to_ret += token[i+1] + } else { + return MalResult::Err(MalValue::String(str("bad string escape"))) + } + // skip + i++ + } else { + to_ret += token[i] + } + } + return MalResult::Ok(MalValue::String(to_ret)) + } else if (token[0] == ':') { + return MalResult::Ok(MalValue::Keyword(token.slice(1,-1))) + } else { + return MalResult::Ok(MalValue::Symbol(token)) + } +} +fun read_str(s: str): MalResult { + var tokens = tokenize(s) + // comment, print nothing + if tokens.size == 0 { + return MalResult::Err(MalValue::String(str(""))) + } + var i = 0 + return read_form(&tokens, &i) +} +adt MalValue { + Nil, + True, + False, + Int: int, + String: str, + Symbol: str, + Keyword: str, + List: vec, + Vector: vec, + Map: map, + Function: MalFunction, + BuiltinFunction: MalBuiltinFunction, + Atom: *MalValue +} +fun equals_MalValue(a: MalValue, b: MalValue): bool { + match (a) { + MalValue::List(d) { match (b) { + MalValue::List(db) { + if d.size != db.size { + return false + } + for (var i = 0; i < d.size; i++;) { + if !equals_MalValue(d[i], db[i]) { + return false + } + } + return true + } + MalValue::Vector(db) { + if d.size != db.size { + return false + } + for (var i = 0; i < d.size; i++;) { + if !equals_MalValue(d[i], db[i]) { + return false + } + } + return true + } + } } + MalValue::Vector(d) { match (b) { + MalValue::List(db) { + if d.size != db.size { + return false + } + for (var i = 0; i < d.size; i++;) { + if !equals_MalValue(d[i], db[i]) { + return false + } + } + return true + } + MalValue::Vector(db) { + if d.size != db.size { + return false + } + for (var i = 0; i < d.size; i++;) { + if !equals_MalValue(d[i], db[i]) { + return false + } + } + return true + } + } } + MalValue::Map(d) { match (b) { MalValue::Map(db) { + if d.size() != db.size() { + return false + } + for (var i = 0; i < d.keys.size; i++;) { + if !db.contains_key(d.keys[i]) || !equals_MalValue(d.values[i], db[d.keys[i]]) { + return false + } + } + return true + } } } + MalValue::String(d) { match (b) { MalValue::String(db) { return d == db; } } } + MalValue::Int(d) { match (b) { MalValue::Int(db) { return d == db; } } } + MalValue::Symbol(d) { match (b) { MalValue::Symbol(db) { return d == db; } } } + MalValue::Keyword(d) { match (b) { MalValue::Keyword(db) { return d == db; } } } + MalValue::Function(d) { match (b) { MalValue::Function(db) { return d == db; } } } + MalValue::BuiltinFunction(d) { match (b) { MalValue::BuiltinFunction(db) { return d == db; } } } + MalValue::Atom(d) { match (b) { MalValue::Atom(db) { return d == db; } } } + MalValue::True() { match (b) { MalValue::True() { return true; } } } + MalValue::False() { match (b) { MalValue::False() { return true; } } } + MalValue::Nil() { match (b) { MalValue::Nil() { return true; } } } + } + return false +} +fun is_keyword(v: MalValue): bool { + match (v) { + MalValue::Keyword(k) { + return true + } + } + return false +} +fun is_keyword_or_string(v: MalValue): bool { + match (v) { + MalValue::Keyword(k) { + return true + } + MalValue::String(s) { + return true + } + } + return false +} +fun get_keyword_or_string_text(v: MalValue): str { + match (v) { + MalValue::Keyword(k) { + return k + } + MalValue::String(s) { + return s + } + } + error("Tried to get_keyword_or_string_text on not a keyword or string!") +} +fun is_list(v: MalValue): bool { + match (v) { + MalValue::List(l) { + return true + } + } + return false +} +fun get_list(v: MalValue): vec { + match (v) { + MalValue::List(l) { + return l + } + } + error("Tried to get list on not a list") +} +fun is_vector(v: MalValue): bool { + match (v) { + MalValue::Vector(v) { + return true + } + } + return false +} +fun is_list_or_vec(v: MalValue): bool { + match (v) { + MalValue::List(l) { + return true + } + MalValue::Vector(v) { + return true + } + MalValue::Nil() { + return true + } + } + return false +} +fun get_list_or_vec(v: MalValue): vec { + match (v) { + MalValue::List(l) { + return l + } + MalValue::Vector(v) { + return v + } + MalValue::Nil() { + return vec() + } + } + error("Tried to get list or vec on not a list or vec") +} +fun is_symbol(v: MalValue): bool { + match (v) { + MalValue::Symbol(s) { + return true + } + } + return false +} +fun is_symbol(v: MalValue, text: *char): bool { + match (v) { + MalValue::Symbol(s) { + return s == text + } + } + return false +} +fun get_symbol_text(v: MalValue): str { + match (v) { + MalValue::Symbol(s) { + return s + } + } + error("get_symbol_text on not symbol") +} +fun is_string(v: MalValue): bool { + match (v) { + MalValue::String(s) { + return true + } + } + return false +} +fun get_string(v: MalValue): str { + match (v) { + MalValue::String(s) { + return s + } + } + error("get_string on not a string") +} +fun is_int(v: MalValue): bool { + match (v) { + MalValue::Int(i) { + return true + } + } + return false +} +fun get_int(v: MalValue): int { + match (v) { + MalValue::Int(i) { + return i + } + } + error("get_int on not an int") +} +fun is_nil(v: MalValue): bool { + match (v) { + MalValue::Nil() { + return true + } + } + return false +} +fun is_atom(v: MalValue): bool { + match (v) { + MalValue::Atom(a) { + return true + } + } + return false +} +fun get_atom(v: MalValue): *MalValue { + match (v) { + MalValue::Atom(a) { + return a + } + } + error("Called get_atom on not an atom") +} +fun is_truthy(v: MalValue): bool { + match (v) { + MalValue::False() { + return false + } + MalValue::Nil() { + return false + } + } + return true +} +fun bool_to_MalValue(b: bool): MalValue { + if b { + return MalValue::True() + } else { + return MalValue::False() + } +} + +fun is_pair(v: MalValue): bool { + return is_list_or_vec(v) && get_list_or_vec(v).size > 0 +} + +fun is_macro_call(ast: MalValue, env: *Env): bool { + if !is_list(ast) { + return false + } + var l = get_list(ast) + if l.size == 0 || !is_symbol(l[0]) { + return false + } + var res = env->get(get_symbol_text(l[0])) + if is_err(res) { + return false + } + var v = get_value(res) + match (v) { + MalValue::Function(f) { + return f.is_macro + } + } + return false +} + +fun macroexpand(ast: MalValue, env: *Env): MalResult { + while is_macro_call(ast, env) { + var l = get_list(ast) + var v = get_value(env->get(get_symbol_text(l[0]))) + match (v) { + MalValue::Function(f) { + var params = l.slice(1,-1) + if (!f.is_variadic && f.parameters.size != params.size) || (f.is_variadic && f.parameters.size > params.size + 1) { + return MalResult::Err(MalValue::String(str("called with the wrong number of parameters"))) + } + env = new()->construct(f.env) + for (var i = 0; i < f.parameters.size; i++;) { + if f.is_variadic && i == f.parameters.size - 1 { + env->set(f.parameters[i], MalValue::List(params.slice(i, -1))) + } else { + env->set(f.parameters[i], params[i]) + } + } + var tmp = *f.body + var tmp2 = EVAL(env, tmp) + if is_err(tmp2) { + return tmp2 + } + ast = get_value(tmp2) + } + } + } + return MalResult::Ok(ast) +} + +fun quasiquote(ast: MalValue): MalValue { + if !is_pair(ast) { + return MalValue::List(vec(MalValue::Symbol(str("quote")), ast)) + } else { + var ast_list = get_list_or_vec(ast) + if is_symbol(ast_list[0], "unquote") { + return ast_list[1] + } else { + if is_pair(ast_list[0]) && is_symbol(get_list_or_vec(ast_list[0])[0], "splice-unquote") { + return MalValue::List(vec(MalValue::Symbol(str("concat")), get_list_or_vec(ast_list[0])[1], quasiquote(MalValue::List(ast_list.slice(1,-1))))) + } else { + return MalValue::List(vec(MalValue::Symbol(str("cons")), quasiquote(ast_list[0]), quasiquote(MalValue::List(ast_list.slice(1,-1))))) + } + } + } +} + +obj Env (Object) { + var data: map + var outer: *Env + fun construct(): *Env { + return construct(null()) + } + fun construct(outer: *Env): *Env { + data.construct() + this->outer = outer + return this + } + fun copy_construct(old: *Env): void { + data.copy_construct(&old->data) + outer = old->outer + } + fun destruct(): void { + data.destruct() + outer = null() + } + fun operator=(other:ref Env):void { + destruct() + copy_construct(&other) + } + fun set(key: str, val: MalValue) { + data.set(key, val) + } + fun find(key: str): *Env { + if (data.contains_key(key)) { + return this + } else if (outer != null()) { + return outer->find(key) + } else { + return null() + } + } + fun get(key: str): MalResult { + var env = find(key) + if (env != null()) { + return MalResult::Ok(env->data.get(key)) + } else { + return MalResult::Err(MalValue::String(str("'") + key + "' not found")) + } + } +} +obj MalBuiltinFunction (Object) { + var fp: fun(vec): MalResult + fun construct(fp: fun(vec): MalResult): *MalBuiltinFunction { + this->fp = fp + return this + } + fun copy_construct(old: *MalBuiltinFunction): void { + this->fp = old->fp + } + fun destruct(): void { + } + fun operator=(other:ref MalBuiltinFunction):void { + destruct() + copy_construct(&other) + } + fun operator==(other: ref MalBuiltinFunction):bool { + return false + } + fun call(params: vec): MalResult { + return fp(params) + } +} +fun make_builtin_function(f: fun(vec): MalResult): MalValue { + var to_ret.construct(f): MalBuiltinFunction + return MalValue::BuiltinFunction(to_ret) +} +obj MalFunction (Object) { + var env: *Env + var parameters: vec + var is_variadic: bool + var is_macro: bool + var body: *MalValue + fun construct(env: *Env, parameters: vec, is_variadic: bool, is_macro: bool, body: MalValue): *MalFunction { + this->env = env + this->parameters.copy_construct(¶meters) + this->is_variadic = is_variadic + this->is_macro = is_macro + this->body = new() + this->body->copy_construct(&body) + return this + } + fun copy_construct(old: *MalFunction): void { + this->env = old->env + this->parameters.copy_construct(&old->parameters) + this->is_variadic = old->is_variadic + this->is_macro = old->is_macro + this->body = new() + this->body->copy_construct(old->body) + } + fun destruct(): void { + this->env = null() + parameters.destruct() + delete(body) + body = null() + } + fun operator=(other:ref MalFunction):void { + destruct() + copy_construct(&other) + } + fun operator==(other: ref MalFunction):bool { + // not sure about env + return env == other.env && parameters == other.parameters && is_variadic == other.is_variadic && is_macro == other.is_macro && equals_MalValue(*body, *other.body) + } + // no call b/c need to do in EVAL for TCO + fun prep_call(params: vec): pair<*Env, MalResult> { + // tco + if (!is_variadic && parameters.size != params.size) || (is_variadic && parameters.size > params.size + 1) { + return make_pair(null(), MalResult::Err(MalValue::String(str("called with the wrong number of parameters")))) + } + var new_env = new()->construct(env) + for (var i = 0; i < parameters.size; i++;) { + if is_variadic && i == parameters.size - 1 { + new_env->set(parameters[i], MalValue::List(params.slice(i, -1))) + } else { + new_env->set(parameters[i], params[i]) + } + } + return make_pair(new_env, MalResult::Ok(*body)) + } +} + +fun function_call(f: MalValue, params: vec): MalResult { + match (f) { + MalValue::BuiltinFunction(f) { + return f.call(params) + } + MalValue::Function(f) { + var call_pair = f.prep_call(params) + if is_err(call_pair.second) { + return call_pair.second + } + return EVAL(call_pair.first, get_value(call_pair.second)) + } + } + return MalResult::Err(MalValue::String(str("trying to apply not a function"))) +} + +adt MalResult { + Ok: MalValue, + Err: MalValue +} +fun is_err(r: MalResult): bool { + match (r) { + MalResult::Err(e) { + return true + } + } + return false +} +fun get_err(r: MalResult): MalValue { + match (r) { + MalResult::Err(e) { + return e + } + } + return MalValue::String(str("not error")) +} +fun get_value(r: MalResult): MalValue { + match (r) { + MalResult::Ok(v) { + return v + } + } + return MalValue::Symbol(str("error")) +} +fun pr_str(v: MalValue, print_readably: bool): str { + match (v) { + MalValue::List(l) { + var to_ret = str("(") + for (var i = 0; i < l.size; i++;) { + if (i != 0) { + to_ret += " " + } + to_ret += pr_str(l[i], print_readably) + } + return to_ret + ")" + } + MalValue::Vector(l) { + var to_ret = str("[") + for (var i = 0; i < l.size; i++;) { + if (i != 0) { + to_ret += " " + } + to_ret += pr_str(l[i], print_readably) + } + return to_ret + "]" + } + MalValue::Map(m) { + var to_ret = str("{") + for (var i = 0; i < m.keys.size; i++;) { + if (i != 0) { + to_ret += " " + } + to_ret += pr_str(m.keys[i], print_readably) + to_ret += str(" ") + to_ret += pr_str(m.values[i], print_readably) + } + return to_ret + "}" + } + MalValue::Int(i) { + return to_string(i) + } + MalValue::String(s) { + if print_readably { + var to_ret = str("\"") //" + for (var i = 0; i < s.length(); i++;) { + if s[i] == '\n' { + to_ret += '\\' + to_ret += 'n' + } else if s[i] == '\\' || s[i] == '"' { + to_ret += '\\' + to_ret += s[i] + } else { + to_ret += s[i] + } + } + return to_ret + "\"" //" + } else { + return s + } + } + MalValue::Symbol(s) { + return s + } + MalValue::Keyword(k) { + return str(":") + k + } + MalValue::BuiltinFunction(f) { + return str("builtin function") + } + MalValue::Function(f) { + return str("function") + } + MalValue::Atom(a) { + return str("(atom ") + pr_str(*a, print_readably) + ")" + } + MalValue::True() { + return str("true") + } + MalValue::False() { + return str("false") + } + MalValue::Nil() { + return str("nil") + } + } + error("can't print") +} + +fun READ(s: str): MalResult { + return read_str(s) +} + +fun eval_ast(env: *Env, ast: MalValue): MalResult { + match (ast) { + MalValue::List(l) { + var to_ret = vec() + for (var i = 0; i < l.size; i++;) { + var mid = EVAL(env, l[i]) + if is_err(mid) { + return mid + } + to_ret.add(get_value(mid)) + } + return MalResult::Ok(MalValue::List(to_ret)) + } + MalValue::Vector(l) { + var to_ret = vec() + for (var i = 0; i < l.size; i++;) { + var mid = EVAL(env, l[i]) + if is_err(mid) { + return mid + } + to_ret.add(get_value(mid)) + } + return MalResult::Ok(MalValue::Vector(to_ret)) + } + MalValue::Map(l) { + var to_ret = map() + for (var i = 0; i < l.keys.size; i++;) { + var mid = EVAL(env, l.values[i]) + if is_err(mid) { + return mid + } + to_ret.set(l.keys[i], get_value(mid)) + } + return MalResult::Ok(MalValue::Map(to_ret)) + } + MalValue::Symbol(s) { + return env->get(s) + } + } + return MalResult::Ok(ast) +} + +fun EVAL(env: *Env, ast: MalValue): MalResult { + // for tco + while (true) { + var expanded = macroexpand(ast, env) + if (is_err(expanded)) { + return expanded + } + ast = get_value(expanded) + if !is_list(ast) { + return eval_ast(env, ast) + } + match (ast) { + MalValue::List(l) { + if (l.size == 0) { + return MalResult::Ok(ast) + } else if (is_symbol(l[0], "def!")) { + if (l.size != 3) { + return MalResult::Err(MalValue::String(str("def! without exaclty key and value"))) + } + if (!is_symbol(l[1])) { + return MalResult::Err(MalValue::String(str("def! not on symbol"))) + } + var value = EVAL(env, l[2]) + if (is_err(value)) { + return value + } + env->set(get_symbol_text(l[1]), get_value(value)) + return value + } else if (is_symbol(l[0], "defmacro!")) { + if (l.size != 3) { + return MalResult::Err(MalValue::String(str("defmacro! without exaclty key and value"))) + } + if (!is_symbol(l[1])) { + return MalResult::Err(MalValue::String(str("defmacro! not on symbol"))) + } + var value = EVAL(env, l[2]) + if (is_err(value)) { + return value + } + var v = get_value(value) + match (v) { + MalValue::Function(f) { + f.is_macro = true + env->set(get_symbol_text(l[1]), MalValue::Function(f)) + return value + } + } + return MalResult::Err(MalValue::String(str("defmacro! on not a function"))) + } else if (is_symbol(l[0], "let*")) { + if (l.size != 3) { + return MalResult::Err(MalValue::String(str("let* without list of bindings & end value"))) + } + if (!is_list_or_vec(l[1])) { + return MalResult::Err(MalValue::String(str("let* without list of bindings"))) + } + var bindings = get_list_or_vec(l[1]) + if (bindings.size & 1 != 0) { + return MalResult::Err(MalValue::String(str("let* list of bindings has odd number of entries"))) + } + var new_env = new()->construct(env) + for (var i = 0; i < bindings.size; i+=2;) { + if (!is_symbol(bindings[i])) { + return MalResult::Err(MalValue::String(str("let* var name not symbol"))) + } + var to_set_value = EVAL(new_env, bindings[i+1]) + if (is_err(to_set_value)) { + return to_set_value + } + new_env->set(get_symbol_text(bindings[i]), get_value(to_set_value)) + } + // tco + env = new_env + var tmp = l[2] + ast = tmp + continue + } else if (is_symbol(l[0], "do")) { + for (var i = 1; i < l.size-1; i++;) { + var mid = EVAL(env, l[i]) + if is_err(mid) { + return mid + } + } + // tco + var tmp = l[l.size-1] + ast = tmp + continue + } else if (is_symbol(l[0], "if")) { + if l.size != 3 && l.size != 4 { + return MalResult::Err(MalValue::String(str("if needs 2 or 3 children"))) + } + var cond = EVAL(env, l[1]) + if is_err(cond) { + return cond + } + // tco + if is_truthy(get_value(cond)) { + var tmp = l[2] + ast = tmp + } else if l.size == 4 { + var tmp = l[3] + ast = tmp + } else { + return MalResult::Ok(MalValue::Nil()) + } + continue + } else if (is_symbol(l[0], "fn*")) { + if l.size != 3 { + return MalResult::Err(MalValue::String(str("fn* needs 2 children"))) + } + if (!is_list_or_vec(l[1])) { + return MalResult::Err(MalValue::String(str("fn* without list of parameters"))) + } + var parameters = get_list_or_vec(l[1]) + var parameters_str = vec() + var is_variadic = false + for (var i = 0; i < parameters.size; i++;) { + if (!is_symbol(parameters[i])) { + return MalResult::Err(MalValue::String(str("fn* parameter name not symbol"))) + } + var symbol_text = get_symbol_text(parameters[i]) + if symbol_text == "&" { + if i != parameters.size - 2 { + return MalResult::Err(MalValue::String(str("fn* has wrong number of arguments after &"))) + } + if (!is_symbol(parameters[i+1])) { + return MalResult::Err(MalValue::String(str("fn* parameter name not symbol"))) + } + is_variadic = true + i++ + symbol_text = get_symbol_text(parameters[i]) + } + parameters_str.add(symbol_text) + } + var to_ret.construct(env, parameters_str, is_variadic, false, l[2]): MalFunction + return MalResult::Ok(MalValue::Function(to_ret)) + } else if (is_symbol(l[0], "quote")) { + if l.size == 1 { + return MalResult::Err(MalValue::String(str("quote with no arguments"))) + } + return MalResult::Ok(l[1]) + } else if (is_symbol(l[0], "quasiquote")) { + if l.size == 1 { + return MalResult::Err(MalValue::String(str("quasiquote with no arguments"))) + } + var tmp = quasiquote(l[1]) + ast = tmp + continue + } else if (is_symbol(l[0], "macroexpand")) { + if l.size == 1 { + return MalResult::Err(MalValue::String(str("macroexpand with no arguments"))) + } + return macroexpand(l[1], env) + } else if (is_symbol(l[0], "try*")) { + if l.size != 2 && (l.size != 3 || !is_list(l[2])) { + return MalResult::Err(MalValue::String(str("try* wrong arguments"))) + } + var A = EVAL(env, l[1]) + if l.size == 3 && is_err(A) { + var catch = get_list(l[2]) + if catch.size != 3 || !is_symbol(catch[0], "catch*") || !is_symbol(catch[1]) { + return MalResult::Err(MalValue::String(str("catch* block malformed"))) + } + var new_env = new()->construct(env) + env->set(get_symbol_text(catch[1]), get_err(A)) + return EVAL(new_env, catch[2]) + } else { + return A + } + } else { + var mid = eval_ast(env, ast) + if is_err(mid) { + return mid + } + var to_call = get_list(get_value(mid)) + match (to_call[0]) { + MalValue::BuiltinFunction(f) { + return f.call(to_call.slice(1,-1)) + } + MalValue::Function(f) { + var params = to_call.slice(1,-1) + var call_pair = f.prep_call(to_call.slice(1, -1)) + if is_err(call_pair.second) { + return call_pair.second + } + env = call_pair.first + ast = get_value(call_pair.second) + continue + } + } + return MalResult::Err(MalValue::String(str("trying to call not a function"))) + } + } + } + return eval_ast(env, ast) + } +} + +fun PRINT(v: MalValue): str { + return pr_str(v, true) +} + +fun rep(env: *Env, a: str): str { + var read = READ(a) + if is_err(read) { + return PRINT(get_err(read)) + } else { + var evaled = EVAL(env, get_value(read)) + if is_err(evaled) { + return str("Exception: ") + PRINT(get_err(evaled)) + } else { + return PRINT(get_value(evaled)) + } + } +} +fun print_wrapper(params: vec, sep: *char, print_readably: bool): str { + var to_ret = str() + for (var i = 0; i < params.size; i++;) { + to_ret += pr_str(params[i], print_readably) + if i != params.size-1 { + to_ret += sep + } + } + return to_ret +} + +fun main(argc: int, argv: **char): int { + var env = new()->construct() + env->set(str("+"), make_builtin_function(fun(params: vec): MalResult { + var to_ret = 0 + for (var i = 0; i < params.size; i++;) { + match (params[i]) { + MalValue::Int(v) { + to_ret += v + continue + } + } + return MalResult::Err(MalValue::String(str("called + with not an int: ") + pr_str(params[i], false))) + } + return MalResult::Ok(MalValue::Int(to_ret)) + })); + env->set(str("-"), make_builtin_function(fun(params: vec): MalResult { + var to_ret = 0 + for (var i = 0; i < params.size; i++;) { + match (params[i]) { + MalValue::Int(v) { + if (i == 0) { + to_ret += v + } else { + to_ret -= v + } + continue + } + } + return MalResult::Err(MalValue::String(str("called - with not an int: ") + pr_str(params[i], false))) + } + return MalResult::Ok(MalValue::Int(to_ret)) + })); + env->set(str("*"), make_builtin_function(fun(params: vec): MalResult { + var to_ret = 1 + for (var i = 0; i < params.size; i++;) { + match (params[i]) { + MalValue::Int(v) { + to_ret *= v + continue + } + } + return MalResult::Err(MalValue::String(str("called * with not an int: ") + pr_str(params[i], false))) + } + return MalResult::Ok(MalValue::Int(to_ret)) + })); + env->set(str("/"), make_builtin_function(fun(params: vec): MalResult { + var to_ret = 1 + for (var i = 0; i < params.size; i++;) { + match (params[i]) { + MalValue::Int(v) { + if (i == 0) { + to_ret *= v + } else { + to_ret /= v + } + continue + } + } + return MalResult::Err(MalValue::String(str("called / with not an int: ") + pr_str(params[i], false))) + } + return MalResult::Ok(MalValue::Int(to_ret)) + })); + env->set(str("prn"), make_builtin_function(fun(params: vec): MalResult { + if params.size == 0 { + return MalResult::Err(MalValue::String(str("Called prn with 0 parameters"))) + } + println(pr_str(params[0], true)) + return MalResult::Ok(MalValue::Nil()) + })); + env->set(str("list"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Ok(MalValue::List(params)) + })); + env->set(str("list?"), make_builtin_function(fun(params: vec): MalResult { + if params.size > 0 && (is_list(params[0]) || is_nil(params[0])) { + return MalResult::Ok(MalValue::True()) + } else { + return MalResult::Ok(MalValue::False()) + } + })); + env->set(str("empty?"), make_builtin_function(fun(params: vec): MalResult { + if params.size == 0 || !is_list_or_vec(params[0]) { + return MalResult::Err(MalValue::String(str("first parameter of empty? is not a list"))) + } else { + return MalResult::Ok(bool_to_MalValue(get_list_or_vec(params[0]).size == 0)) + } + })); + env->set(str("count"), make_builtin_function(fun(params: vec): MalResult { + if params.size == 0 || !is_list_or_vec(params[0]) { + return MalResult::Err(MalValue::String(str("first parameter of count is not a list"))) + } else { + return MalResult::Ok(MalValue::Int(get_list_or_vec(params[0]).size)) + } + })); + env->set(str("="), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 { + return MalResult::Err(MalValue::String(str("= with not two parameters"))) + } else { + return MalResult::Ok(bool_to_MalValue(equals_MalValue(params[0], params[1]))) + } + })); + env->set(str("<"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_int(params[0]) || !is_int(params[1]) { + return MalResult::Err(MalValue::String(str("< with not two numbers"))) + } else { + return MalResult::Ok(bool_to_MalValue(get_int(params[0]) < get_int(params[1]))) + } + })); + env->set(str("<="), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_int(params[0]) || !is_int(params[1]) { + return MalResult::Err(MalValue::String(str("<= with not two numbers"))) + } else { + return MalResult::Ok(bool_to_MalValue(get_int(params[0]) <= get_int(params[1]))) + } + })); + env->set(str(">"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_int(params[0]) || !is_int(params[1]) { + return MalResult::Err(MalValue::String(str("> with not two numbers"))) + } else { + return MalResult::Ok(bool_to_MalValue(get_int(params[0]) > get_int(params[1]))) + } + })); + env->set(str(">="), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_int(params[0]) || !is_int(params[1]) { + return MalResult::Err(MalValue::String(str(">= with not two numbers"))) + } else { + return MalResult::Ok(bool_to_MalValue(get_int(params[0]) >= get_int(params[1]))) + } + })); + env->set(str("pr-str"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Ok(MalValue::String(print_wrapper(params, " ", true))) + })); + env->set(str("str"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Ok(MalValue::String(print_wrapper(params, "", false))) + })); + env->set(str("prn"), make_builtin_function(fun(params: vec): MalResult { + println(print_wrapper(params, " ", true)) + return MalResult::Ok(MalValue::Nil()) + })); + env->set(str("println"), make_builtin_function(fun(params: vec): MalResult { + println(print_wrapper(params, " ", false)) + return MalResult::Ok(MalValue::Nil()) + })); + env->set(str("read-string"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_string(params[0]) { + return MalResult::Err(MalValue::String(str("read-string with not a single string"))) + } else { + return read_str(get_string(params[0])) + } + })); + env->set(str("slurp"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_string(params[0]) { + return MalResult::Err(MalValue::String(str("slurp with not a single string"))) + } else { + return MalResult::Ok(MalValue::String(read_file(get_string(params[0])))) + } + })); + env->set(str("eval"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 { + return MalResult::Err(MalValue::String(str("eval with wrong number of params"))) + } else { + return EVAL(env, params[0]) + } + })); + env->set(str("atom"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 { + return MalResult::Err(MalValue::String(str("atom with wrong number of params"))) + } else { + var atom = new() + atom->copy_construct(¶ms[0]) + return MalResult::Ok(MalValue::Atom(atom)) + } + })); + env->set(str("atom?"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 { + return MalResult::Err(MalValue::String(str("atom? with wrong number of params"))) + } else { + return MalResult::Ok(bool_to_MalValue(is_atom(params[0]))) + } + })); + env->set(str("deref"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_atom(params[0]) { + return MalResult::Err(MalValue::String(str("deref called with wrong number of params or not an atom"))) + } else { + return MalResult::Ok(*get_atom(params[0])) + } + })); + env->set(str("reset!"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_atom(params[0]) { + return MalResult::Err(MalValue::String(str("reset! called with wrong number of params or first not an atom"))) + } else { + var tmp = params[1] + *get_atom(params[0]) = tmp + return MalResult::Ok(params[1]) + } + })); + env->set(str("swap!"), make_builtin_function(fun(params: vec): MalResult { + if params.size < 2 || !is_atom(params[0]) { + return MalResult::Err(MalValue::String(str("swap! called with wrong number of params or first not an atom"))) + } else { + var call = vec(params[1], *get_atom(params[0])) + params.slice(2,-1) + var res = function_call(params[1], vec(*get_atom(params[0])) + params.slice(2, -1)) + if !is_err(res) { + *get_atom(params[0]) = get_value(res) + } + return res + } + })); + env->set(str("cons"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_list_or_vec(params[1]) { + return MalResult::Err(MalValue::String(str("cons called with wrong number of params or second not an list/vec"))) + } else { + return MalResult::Ok(MalValue::List(vec(params[0]) + get_list_or_vec(params[1]))) + } + })); + env->set(str("concat"), make_builtin_function(fun(params: vec): MalResult { + var to_ret = vec() + for (var i = 0; i < params.size; i++;) { + if !is_list_or_vec(params[i]) { + return MalResult::Err(MalValue::String(str("concat called with not an list"))) + } + to_ret += get_list_or_vec(params[i]) + } + return MalResult::Ok(MalValue::List(to_ret)) + })); + env->set(str("nth"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_list_or_vec(params[0]) || !is_int(params[1]) { + return MalResult::Err(MalValue::String(str("nth called with wrong number or type of params"))) + } else { + var list = get_list_or_vec(params[0]) + var idx = get_int(params[1]) + if idx >= list.size { + return MalResult::Err(MalValue::String(str("nth idx out of range"))) + } + return MalResult::Ok(list[idx]) + } + })); + env->set(str("first"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_list_or_vec(params[0]) { + return MalResult::Err(MalValue::String(str("first called with wrong number or type of params"))) + } else { + var list = get_list_or_vec(params[0]) + if list.size == 0 { + return MalResult::Ok(MalValue::Nil()) + } + return MalResult::Ok(list[0]) + } + })); + env->set(str("rest"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_list_or_vec(params[0]) { + return MalResult::Err(MalValue::String(str("rest called with wrong number or type of params"))) + } else { + var list = get_list_or_vec(params[0]) + if list.size == 0 { + return MalResult::Ok(MalValue::List(vec())) + } + return MalResult::Ok(MalValue::List(list.slice(1,-1))) + } + })); + env->set(str("throw"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 { + return MalResult::Err(MalValue::String(str("throw called with wrong number or type of params"))) + } else { + return MalResult::Err(params[0]) + } + })); + env->set(str("apply"), make_builtin_function(fun(params: vec): MalResult { + if params.size < 2 || !is_list_or_vec(params[params.size-1]) { + return MalResult::Err(MalValue::String(str("apply called with wrong number or type of params"))) + } else { + var inner_params = params.slice(1,-2) + get_list_or_vec(params[params.size-1]) + return function_call(params[0], inner_params) + } + })); + env->set(str("map"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_list_or_vec(params[1]) { + return MalResult::Err(MalValue::String(str("map called with wrong number or type of params"))) + } else { + var l = get_list_or_vec(params[1]) + var to_ret = vec() + for (var i = 0; i < l.size; i++;) { + var mid = function_call(params[0], vec(l[i])) + if is_err(mid) { + return mid + } + to_ret.add(get_value(mid)) + } + return MalResult::Ok(MalValue::List(to_ret)) + } + })); + env->set(str("symbol?"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 { + return MalResult::Err(MalValue::String(str("symbol? called with wrong number of params"))) + } else { + return MalResult::Ok(bool_to_MalValue(is_symbol(params[0]))) + } + })); + env->set(str("symbol"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_string(params[0]) { + return MalResult::Err(MalValue::String(str("symbol called with wrong number or type of params"))) + } else { + return MalResult::Ok(MalValue::Symbol(get_string(params[0]))) + } + })); + env->set(str("keyword?"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 { + return MalResult::Err(MalValue::String(str("keyword? called with wrong number of params"))) + } else { + return MalResult::Ok(bool_to_MalValue(is_keyword(params[0]))) + } + })); + env->set(str("keyword"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_keyword_or_string(params[0]) { + return MalResult::Err(MalValue::String(str("keyword called with wrong number or type of params"))) + } else { + return MalResult::Ok(MalValue::Keyword(get_keyword_or_string_text(params[0]))) + } + })); + env->set(str("vector"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Ok(MalValue::Vector(params)) + })); + env->set(str("vector?"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 { + return MalResult::Err(MalValue::String(str("vector? called with wrong number of params"))) + } else { + return MalResult::Ok(bool_to_MalValue(is_vector(params[0]))) + } + })); + env->set(str("sequential?"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 { + return MalResult::Err(MalValue::String(str("sequential? called with wrong number of params"))) + } else if is_nil(params[0]) { + return MalResult::Ok(MalValue::False()) + } else { + return MalResult::Ok(bool_to_MalValue(is_list_or_vec(params[0]))) + } + })); + env->set(str("hash-map"), make_builtin_function(fun(params: vec): MalResult { + return create_map(params) + })); + env->set(str("map?"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 { + return MalResult::Err(MalValue::String(str("map? called with wrong number of params"))) + } else { + return MalResult::Ok(bool_to_MalValue(is_map(params[0]))) + } + })); + env->set(str("assoc"), make_builtin_function(fun(params: vec): MalResult { + if params.size & 1 != 1 || !is_map(params[0]) { + return MalResult::Err(MalValue::String(str("assoc? called with wrong number or type of params"))) + } else { + var base = get_map(params[0]) + var new = create_map(params.slice(1,-1)) + if is_err(new) { + return new + } + var new_map = get_map(get_value(new)) + for (var i = 0; i < new_map.keys.size; i++;) { + base.set(new_map.keys[i], new_map.values[i]) + } + return MalResult::Ok(MalValue::Map(base)) + } + })); + env->set(str("dissoc"), make_builtin_function(fun(params: vec): MalResult { + if params.size < 2 || !is_map(params[0]) { + return MalResult::Err(MalValue::String(str("dissoc? called with wrong number or type of params"))) + } else { + var base = get_map(params[0]) + var remove = vec() + if params.size != 2 || !is_list(params[1]) { + remove = params.slice(1,-1) + } else { + remove = get_list(params[1]) + } + for (var i = 0; i < remove.size; i++;) { + if base.contains_key(remove[i]) { + base.remove(remove[i]) + } + } + return MalResult::Ok(MalValue::Map(base)) + } + })); + env->set(str("get"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_map(params[0]) || !is_keyword_or_string(params[1]) { + return MalResult::Err(MalValue::String(str("get called with wrong number or type of params"))) + } else { + var base = get_map(params[0]) + if base.contains_key(params[1]) { + return MalResult::Ok(base.get(params[1])) + } else { + return MalResult::Ok(MalValue::Nil()) + } + } + })); + env->set(str("contains?"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 2 || !is_map(params[0]) || !is_keyword_or_string(params[1]) { + return MalResult::Err(MalValue::String(str("contains? called with wrong number or type of params"))) + } else { + var base = get_map(params[0]) + if base.contains_key(params[1]) { + return MalResult::Ok(MalValue::True()) + } else { + return MalResult::Ok(MalValue::False()) + } + } + })); + env->set(str("keys"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_map(params[0]) { + return MalResult::Err(MalValue::String(str("keys called with wrong number or type of params"))) + } else { + return MalResult::Ok(MalValue::List(get_map(params[0]).keys)) + } + })); + env->set(str("vals"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_map(params[0]) { + return MalResult::Err(MalValue::String(str("vals called with wrong number or type of params"))) + } else { + return MalResult::Ok(MalValue::List(get_map(params[0]).values)) + } + })); + env->set(str("readline"), make_builtin_function(fun(params: vec): MalResult { + if params.size != 1 || !is_string(params[0]) { + return MalResult::Err(MalValue::String(str("readline called with wrong number or type of params"))) + } else { + var entered = get_line(get_string(params[0]), 1024) + if entered == "***EOF***" { + return MalResult::Ok(MalValue::Nil()) + } + return MalResult::Ok(MalValue::String(entered)) + } + })); + env->set(str("time-ms"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Err(MalValue::String(str("not implemented"))) + })); + env->set(str("meta"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Err(MalValue::String(str("not implemented"))) + })); + env->set(str("with-meta"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Err(MalValue::String(str("not implemented"))) + })); + env->set(str("fn?"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Err(MalValue::String(str("not implemented"))) + })); + env->set(str("string?"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Err(MalValue::String(str("not implemented"))) + })); + env->set(str("number?"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Err(MalValue::String(str("not implemented"))) + })); + env->set(str("seq"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Err(MalValue::String(str("not implemented"))) + })); + env->set(str("conj"), make_builtin_function(fun(params: vec): MalResult { + return MalResult::Err(MalValue::String(str("not implemented"))) + })); + rep(env, str("(def! not (fn* (a) (if a false true)))")) + rep(env, str("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \" \\nnil)\")))))")) + rep(env, str("(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(env, str("(def! nil? (fn* (a) (= nil a)))")) + rep(env, str("(def! true? (fn* (a) (= true a)))")) + rep(env, str("(def! false? (fn* (a) (= false a)))")) + rep(env, str("(def! *host-language* \"kraken\")")) + var params = vec() + if argc > 1 { + for (var i = 2; i < argc; i++;) { + params.add(MalValue::String(str(argv[i]))) + } + env->set(str("*ARGV*"), MalValue::List(params)) + rep(env, str("(load-file \"") + argv[1] + "\")") + } else { + env->set(str("*ARGV*"), MalValue::List(params)) + rep(env, str("(println (str \"Mal [\" *host-language* \"]\"))")) + while (true) { + var line = get_line(str("user> "), 1024) + if (line == "***EOF***") + break + println(rep(env, line)) + } + } +}