1
1
mirror of https://github.com/kanaka/mal.git synced 2024-08-17 09:40:21 +03:00
mal/impls/zig/types.zig
Joel Martin 8a19f60386 Move implementations into impls/ dir
- Reorder README to have implementation list after "learning tool"
  bullet.

- This also moves tests/ and libs/ into impls. It would be preferrable
  to have these directories at the top level.  However, this causes
  difficulties with the wasm implementations which need pre-open
  directories and have trouble with paths starting with "../../". So
  in lieu of that, symlink those directories to the top-level.

- Move the run_argv_test.sh script into the tests directory for
  general hygiene.
2020-02-10 23:50:16 -06:00

472 lines
15 KiB
Zig

const string_copy = @import("utils.zig").string_copy;
const string_concat = @import("utils.zig").string_concat;
const Allocator = @import("std").mem.Allocator;
const warn = @import("std").debug.warn;
const Env = @import("env.zig").Env;
const MalError = @import("error.zig").MalError;
const MalHashMap = @import("hmap.zig").MalHashMap;
const MalLinkedList = @import("linked_list.zig").MalLinkedList;
const linked_list = @import("linked_list.zig");
const hash_map = @import("hmap.zig");
pub const MalTypeValue = enum {
List,
Vector,
Generic,
Int,
String,
Keyword,
Nil,
True,
False,
Fn0,
Fn1,
Fn2,
Fn3,
Fn4,
FVar,
Func,
Atom,
HashMap,
};
pub const MalFuncData = struct {
arg_list: *MalType,
body: *MalType,
environment: *Env,
eval_func: ?(*const fn(o_mal: *MalType, env: *Env) MalError!*MalType),
is_macro: bool,
};
pub const MalData = union(MalTypeValue) {
List: MalLinkedList,
Vector: MalLinkedList,
Generic: []const u8,
Int: i64,
String: []const u8,
Keyword: []const u8,
Nil: void,
True: void,
False: void,
Fn0: *const fn () MalError!*MalType,
Fn1: *const fn (a1: *MalType) MalError!*MalType,
Fn2: *const fn (a1: *MalType, a2: *MalType) MalError!*MalType,
Fn3: *const fn (a1: *MalType, a2: *MalType, a3: *MalType) MalError!*MalType,
Fn4: *const fn (a1: *MalType, a2: *MalType, a3: *MalType, a4: *MalType) MalError!*MalType,
FVar: *const fn (args: MalLinkedList) MalError!*MalType,
Func: MalFuncData,
Atom: **MalType,
HashMap: MalHashMap,
};
pub const MalType = struct {
reference_count: *i32,
data: MalData,
meta: ?*MalType,
pub fn new_nil(allocator: *Allocator) MalError!*MalType {
const mal: *MalType = allocator.create(MalType)
catch return MalError.SystemError;
errdefer allocator.destroy(mal);
mal.reference_count = allocator.create(i32)
catch return MalError.SystemError;
mal.reference_count.* = 1;
mal.data = MalData { .Nil = undefined };
mal.meta = null;
return mal;
}
pub fn new_generic(allocator: *Allocator, value: [] const u8) MalError!*MalType {
// TODO: should we free on errors?
const mal: *MalType = try MalType.new_nil(allocator);
errdefer mal.delete(allocator);
const value_copy = string_copy(allocator, value)
catch return MalError.SystemError;
errdefer allocator.destroy(value_copy);
mal.data = MalData { .Generic = value_copy };
return mal;
}
pub fn new_string(allocator: *Allocator, value: [] const u8) MalError!*MalType {
const mal = try MalType.new_nil(allocator);
const string_cpy = string_copy(allocator, value) catch return MalError.SystemError;
mal.data = MalData { .String = string_cpy };
return mal;
}
pub fn new_keyword(allocator: *Allocator, value: [] const u8) MalError!*MalType {
const mal = try MalType.new_nil(allocator);
const kwd_prefix: [] const u8 = [_]u8 {255};
const kwd_cpy = string_concat(allocator, kwd_prefix, value)
catch return MalError.SystemError;
mal.data = MalData { .Keyword = kwd_cpy };
return mal;
}
pub fn new_int(allocator: *Allocator, value: i64) MalError!*MalType {
const mal = try MalType.new_nil(allocator);
mal.data = MalData { .Int = value };
return mal;
}
pub fn new_bool(allocator: *Allocator, b: bool) MalError!*MalType {
const mal = try MalType.new_nil(allocator);
if(b) {
mal.data = MalData { .True = undefined };
}
else {
mal.data = MalData { .False = undefined };
}
return mal;
}
pub fn new_list_empty(allocator: *Allocator) MalError!*MalType {
const mal = try MalType.new_nil(allocator);
mal.data = MalData {.List = MalLinkedList.init(allocator)};
return mal;
}
pub fn new_vector_empty(allocator: *Allocator) MalError!*MalType {
const mal = try MalType.new_nil(allocator);
mal.data = MalData {.Vector = MalLinkedList.init(allocator)};
return mal;
}
pub fn new_list(allocator: *Allocator, ll: MalLinkedList) MalError!*MalType {
const mal = try MalType.new_nil(allocator);
mal.data = MalData {.List = ll};
return mal;
}
pub fn new_vector(allocator: *Allocator, ll: MalLinkedList) MalError!*MalType {
const mal = try MalType.new_nil(allocator);
mal.data = MalData {.Vector = ll};
return mal;
}
pub fn new_atom(allocator: *Allocator, mal: *MalType) MalError!*MalType {
const new_mal = try MalType.new_nil(allocator);
errdefer new_mal.delete(allocator);
const atom_value = allocator.create(*MalType) catch return MalError.SystemError;
atom_value.* = try mal.copy(allocator);
new_mal.data = MalData { .Atom = atom_value };
return new_mal;
}
pub fn new_hashmap(allocator: *Allocator) MalError!*MalType {
const new_mal = try MalType.new_nil(allocator);
errdefer new_mal.delete(allocator);
const hmap = MalHashMap.init(allocator);
new_mal.data = MalData {.HashMap = hmap};
return new_mal;
}
pub fn hashmap_insert(mal: *MalType, key: []const u8, value: *MalType) MalError!void {
switch(mal.data) {
.HashMap => |*hmap| {
_ = hmap.*.put(key, value) catch return MalError.SystemError;
},
else => return MalError.TypeError,
}
}
pub fn hashmap_remove(mal: *MalType, key: []const u8) MalError!void {
switch(mal.data) {
.HashMap => |*hmap| {
_ = hmap.*.remove(key);
},
else => return MalError.TypeError,
}
}
pub fn hashmap_get(mal: *MalType, key: []const u8) MalError!?*MalType {
// TODO: should we copy the data here, or downstream?
switch(mal.data) {
.HashMap => |hmap| {
return hmap.getValue(key);
},
.Nil => {
return null;
},
else => return MalError.TypeError,
}
}
pub fn hashmap_contains(mal: *MalType, key: []const u8) MalError!bool {
// TODO: should we copy the data here, or downstream?
return switch(mal.data) {
.HashMap => |hmap| (hmap.getValue(key) != null),
else => MalError.TypeError,
};
}
pub fn sequence_linked_list(mal: *MalType) MalError!*MalLinkedList {
return switch(mal.data) {
.List => |*l| l,
.Vector => |*v| v,
else => MalError.TypeError,
};
}
pub fn const_sequence_linked_list(mal: *const MalType) MalError!MalLinkedList {
return switch(mal.data) {
.List => |l| l,
.Vector => |v| v,
else => MalError.TypeError,
};
}
pub fn sequence_append(mal: *MalType, allocator: *Allocator, new_el: *MalType) MalError!void {
var ll = try mal.sequence_linked_list();
try linked_list.append_mal(allocator, ll, new_el);
}
pub fn sequence_prepend(mal: *MalType, allocator: *Allocator, new_el: *MalType) MalError!void {
var ll = try mal.sequence_linked_list();
try linked_list.prepend_mal(allocator, ll, new_el);
}
pub fn sequence_pop_first(mal: *MalType, allocator: *Allocator) MalError!*MalType {
var ll = try mal.sequence_linked_list();
return linked_list.pop_first(allocator, ll);
}
pub fn sequence_pop_last(mal: *MalType, allocator: *Allocator) MalError!*MalType {
var ll = try mal.sequence_linked_list();
if(ll.count() == 0) {
return MalError.OutOfBounds;
}
return ll.pop();
}
pub fn sequence_length(mal: *MalType) MalError!i64 {
return switch(mal.data) {
.List => |l| @intCast(i64, l.count()),
.Vector => |v| @intCast(i64, v.count()),
else => MalError.TypeError,
};
}
pub fn sequence_nth(mal: *MalType, pos: u32) MalError!*MalType {
var ll = try mal.sequence_linked_list();
if(ll.count() <= pos) {
return MalError.OutOfBounds;
}
return ll.at(pos);
}
pub fn as_int(mal: *const MalType) MalError!i64 {
return switch(mal.data) {
.Int => |val| val,
else => MalError.TypeError,
};
}
pub fn as_symbol(mal: *const MalType) MalError![]const u8 {
return switch(mal.data) {
.Generic => |val| val,
else => MalError.TypeError,
};
}
pub fn as_string(mal: *const MalType) MalError![]const u8 {
return switch(mal.data) {
.String => |s| s,
else => MalError.TypeError,
};
}
pub fn shallow_destroy(mal: *MalType, allocator: *Allocator) void {
mal.reference_count.* -= 1;
if(mal.meta) |mal_meta| {
mal_meta.delete(allocator);
}
if(mal.reference_count.* <= 0) {
allocator.destroy(mal.reference_count);
}
allocator.destroy(mal);
}
pub fn delete(mal: *MalType, allocator: *Allocator) void {
const ref_count = mal.reference_count.*;
switch(mal.data) {
.List => |*l| {
linked_list.destroy(allocator, l, false);
},
.Vector => |*v| {
linked_list.destroy(allocator, v, false);
},
.String => |string| {
allocator.free(string);
},
.Generic => |string| {
allocator.free(string);
},
.Keyword => |string| {
allocator.free(string);
},
.Atom => |atom| {
if(ref_count <= 1)
atom.*.delete(allocator);
},
.HashMap => |hm| {
hash_map.destroy(allocator, hm, false);
},
.Func => |func_data| {
func_data.arg_list.delete(allocator);
func_data.body.delete(allocator);
func_data.environment.delete();
},
else => {},
}
mal.shallow_destroy(allocator);
}
pub fn get_num_args(mal: *const MalType) i8 {
return switch(mal.data) {
.Fn0 => 0,
.Fn1 => 1,
.Fn2 => 2,
.Fn3 => 3,
.Fn4 => 4,
.FVar => -1,
else => -2,
};
}
pub fn copy(mal: *const MalType, allocator: *Allocator) MalError!*MalType {
var new_mal = allocator.create(MalType)
catch |err| return MalError.SystemError;
new_mal.reference_count = mal.reference_count;
mal.reference_count.* += 1;
new_mal.data = MalData {.Nil=undefined};
if(mal.meta) |mal_meta| {
new_mal.meta = try mal_meta.copy(allocator);
} else {
new_mal.meta = null;
}
switch(mal.data) {
.Generic => |val| {
const cpy_val = string_copy(allocator, val)
catch return MalError.SystemError;
new_mal.data = MalData { .Generic = cpy_val };
},
.Int => |val| {
new_mal.data = MalData { .Int = val };
},
.Fn0 => |f0| {
new_mal.data = MalData { .Fn0 = f0 };
},
.Fn1 => |f1| {
new_mal.data = MalData { .Fn1 = f1 };
},
.Fn2 => |f2| {
new_mal.data = MalData { .Fn2 = f2 };
},
.Fn3 => |f3| {
new_mal.data = MalData { .Fn3 = f3 };
},
.Fn4 => |f4| {
new_mal.data = MalData { .Fn4 = f4 };
},
.FVar => |f| {
new_mal.data = MalData { .FVar = f };
},
.String => |string| {
const string_cpy = string_copy(allocator, string)
catch return MalError.SystemError;
new_mal.data = MalData { .String = string_cpy };
},
.Keyword => |kwd| {
const kwd_cpy = string_copy(allocator, kwd)
catch return MalError.SystemError;
new_mal.data = MalData { .Keyword = kwd_cpy };
},
.List => |l| {
new_mal.data = MalData { .List = try linked_list.deepcopy(allocator, l) };
},
.Vector => |v| {
new_mal.data = MalData { .Vector = try linked_list.deepcopy(allocator, v) };
},
.Func => |func_data| {
const al = try func_data.arg_list.copy(allocator);
const b = try func_data.body.copy(allocator);
const new_func_data = MalFuncData {
.arg_list = al,
.body = b,
.environment = try func_data.environment.copy(allocator),
.eval_func = func_data.eval_func,
.is_macro = func_data.is_macro,
};
new_mal.data = MalData { .Func = new_func_data };
},
.Atom => |atom| {
new_mal.data = MalData { .Atom = atom };
},
.HashMap => |h| {
new_mal.data = MalData {.HashMap = try hash_map.deepcopy(allocator, h)};
},
else => {
new_mal.data = mal.data;
},
}
return new_mal;
}
};
pub fn apply_function(allocator: *Allocator, args: MalLinkedList) MalError!*MalType {
// TODO: this should take a MLL pointer
var args_copy = try linked_list.deepcopy(allocator, args); //TODO: could be more efficient
var args_arr = args_copy.toSlice();
const mal_func = args_arr[0];
// First check if it is a user-defined Mal function
if(MalTypeValue(mal_func.data) == MalTypeValue.Func) {
const func_data = mal_func.data.Func;
const args_ll = try func_data.arg_list.sequence_linked_list();
const func_env = func_data.environment;
const eval_func = func_data.eval_func orelse return MalError.TypeError;
var new_env = try Env.new(allocator, func_env);
// TODO: make sure that set_list checks that first_arg and first_arg_value have same len
try new_env.set_slice(args_ll.toSlice(), args_arr[1..args_arr.len]);
linked_list.destroy(allocator, &args_copy, true);
const new_body = try func_data.body.copy(allocator);
mal_func.delete(allocator);
return eval_func.*(new_body, new_env);
}
// Otherwise, it is a built-in Zig function
// TODO: safety?
const n = mal_func.get_num_args();
if(n <= -2) {
return MalError.ArgError;
}
if(n == -1) {
// Variable arg function
(try linked_list.pop_first(allocator, &args_copy)).delete(allocator);
defer linked_list.destroy(allocator, &args_copy, false);
return (mal_func.data.FVar.*)(args_copy);
}
var arg = args_arr[1..args_arr.len];
// TODO: replace this
const ret = switch(n) {
0 => (mal_func.data.Fn0.*)(),
1 => (mal_func.data.Fn1.*)(arg[0]),
2 => (mal_func.data.Fn2.*)(arg[0], arg[1]),
3 => (mal_func.data.Fn3.*)(arg[0], arg[1], arg[2]),
4 => (mal_func.data.Fn4.*)(arg[0], arg[1], arg[2], arg[3]),
else => MalError.ArgError,
};
linked_list.destroy(allocator, &args_copy, false);
return ret;
}