diff --git a/impls/zig/Dockerfile b/impls/zig/Dockerfile index 2e70fa9bd9..8b1bbd7580 100644 --- a/impls/zig/Dockerfile +++ b/impls/zig/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:bionic +FROM ubuntu:24.04 MAINTAINER Joel Martin ########################################################## @@ -9,10 +9,8 @@ MAINTAINER Joel Martin RUN apt-get -y update # Required for running tests -RUN apt-get -y install make python - -# Some typical implementation and test requirements -RUN apt-get -y install curl libreadline-dev libedit-dev libpcre3-dev +RUN apt-get -y install make python3 +RUN ln -fs /usr/bin/python3 /usr/local/bin/python RUN mkdir -p /mal WORKDIR /mal @@ -20,14 +18,9 @@ WORKDIR /mal ########################################################## # Specific implementation requirements ########################################################## +RUN apt-get -y install ca-certificates curl gcc libc6-dev libpcre3-dev libreadline-dev xz-utils -RUN apt-get -y install gcc gdc ldc gpg wget - -RUN wget https://ziglang.org/download/0.5.0/zig-linux-x86_64-0.5.0.tar.xz && \ - echo `pwd` && \ - tar -xf zig-linux-x86_64-0.5.0.tar.xz && \ - cp -r zig-linux-x86_64-0.5.0 /usr/local/bin && \ - ln -sf /usr/local/bin/zig-linux-x86_64-0.5.0/zig /usr/local/bin/zig && \ - chmod +x /usr/local/bin/zig +RUN curl https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJC/mal +RUN ln -fst/usr/local/bin /mal/zig-linux-x86_64-0.13.0/zig ENV HOME /mal diff --git a/impls/zig/Makefile b/impls/zig/Makefile index acf99b11d8..1eda252d6a 100644 --- a/impls/zig/Makefile +++ b/impls/zig/Makefile @@ -3,14 +3,13 @@ STEPS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tc all: $(STEPS) -dist: mal +zig_opts += --release=safe +zig_opts += -Doptimize=Debug +$(STEPS): + zig build $(zig_opts) -Dname=$@ -Droot_source_file=$@.zig - -%: %.zig - zig build -Drelease-fast=true - ln -sf zig-cache/bin/$* . - -.PHONY: clean +.PHONY: all $(STEPS) clean clean: - rm -f $(STEPS) + rm -fr .zig-cache/ zig-out/ + rm -f *~ diff --git a/impls/zig/README b/impls/zig/README new file mode 100644 index 0000000000..94ad7da67b --- /dev/null +++ b/impls/zig/README @@ -0,0 +1,24 @@ +debug_alloc in types.zig may help with reference counting. + + +TODO Simplify the printer with the new reader functions in the zig +library. + + +NOTE Before implementing any optimization or optional fix that would +increase the complexity, please take into account that someone has to +maintain the code, and the zig language evolves quickly. + +Some memory leaks are probably already present, especially when an +error interrupts the normal execution flow. + +Examples of things that are deliberately not implemented... + * TCO for try* + * preallocate integers between 0 and 100 at startup + * use ArrayList.ensureTotalCapacityPrecise/HashMap.ensureTotalCapacity + after most calls to new_list/vector/map. + * store symbols in a global hash map, + * implement lists/vectors as slices/cons cells/whatever + * deallocate cyclic structures not detected by reference counting like + (let* (f (fn* () nil))) + (def! a (atom 2)) (def! v [a]) (reset! a v) diff --git a/impls/zig/build.zig b/impls/zig/build.zig index 4154e1c471..8e9232f7eb 100644 --- a/impls/zig/build.zig +++ b/impls/zig/build.zig @@ -1,35 +1,26 @@ -const LibExeObjStep = @import("std").build.LibExeObjStep; -const Builder = @import("std").build.Builder; -const builtin = @import("builtin"); - -const warn = @import("std").debug.warn; +const Builder = @import("std").Build; pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - const exes = [_] *LibExeObjStep { - b.addExecutable("step0_repl", "step0_repl.zig"), - b.addExecutable("step1_read_print", "step1_read_print.zig"), - b.addExecutable("step2_eval", "step2_eval.zig"), - b.addExecutable("step3_env", "step3_env.zig"), - b.addExecutable("step4_if_fn_do", "step4_if_fn_do.zig"), - b.addExecutable("step5_tco", "step5_tco.zig"), - b.addExecutable("step6_file", "step6_file.zig"), - b.addExecutable("step7_quote", "step7_quote.zig"), - b.addExecutable("step8_macros", "step8_macros.zig"), - b.addExecutable("step9_try", "step9_try.zig"), - b.addExecutable("stepA_mal", "stepA_mal.zig"), - }; + // Two options select the built step. + + const name = b.option([]const u8, "name", "step name (without .zig)") + orelse "stepA_mal"; + + const root_source_file = b.path( + b.option([]const u8, "root_source_file", "step name (with .zig)") + orelse "stepA_mal.zig"); + + const exe = b.addExecutable(.{ + .name = name, + .root_source_file = root_source_file, + .target = b.standardTargetOptions(.{}), + .optimize = b.standardOptimizeOption(.{}), + }); - for(exes) |exe| { - exe.setBuildMode(mode); - exe.linkSystemLibrary("c"); - exe.linkSystemLibrary("pcre"); - exe.linkSystemLibrary("readline"); - const run_cmd = exe.run(); - const step = b.step(exe.name, exe.name); - step.dependOn(&run_cmd.step); - b.default_step.dependOn(&exe.step); - b.installArtifact(exe); - } + exe.linkSystemLibrary("c"); + exe.linkSystemLibrary("pcre"); + exe.linkSystemLibrary("readline"); + b.default_step.dependOn(&exe.step); + b.installArtifact(exe); } diff --git a/impls/zig/core.zig b/impls/zig/core.zig index 6e8ccd521c..45cadece3f 100644 --- a/impls/zig/core.zig +++ b/impls/zig/core.zig @@ -1,22 +1,12 @@ const std = @import("std"); -const warn = @import("std").debug.warn; -const AllocatorType = @import("std").mem.Allocator; -var Allocator: *AllocatorType = undefined; +const Allocator = std.heap.c_allocator; -pub fn set_allocator(alloc: *AllocatorType) void { - Allocator = alloc; -} - -const Env = @import("env.zig").Env; -const MalData = @import("types.zig").MalData; const MalType = @import("types.zig").MalType; -const MalTypeValue = @import("types.zig").MalTypeValue; const printer = @import("printer.zig"); const reader = @import("reader.zig"); -const getline_prompt = @import("readline.zig").getline_prompt; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; +const getline_prompt = @import("readline.zig").getline; +const string_eql = std.hash_map.eqlString; const MalError = @import("error.zig").MalError; @@ -24,831 +14,945 @@ const hmap = @import("hmap.zig"); const MalLinkedList = @import("linked_list.zig").MalLinkedList; const MalHashMap = @import("hmap.zig").MalHashMap; -const linked_list = @import("linked_list.zig"); -const apply_function = @import("types.zig").apply_function; + +// Set by the step file at startup. +pub var apply_function: *const fn(f: MalType, args: []*MalType) MalError!*MalType = undefined; const safeAdd = @import("std").math.add; const safeSub = @import("std").math.sub; const safeMul = @import("std").math.mul; const safeDivFloor = @import("std").math.divFloor; -fn int_plus(a1: *MalType, a2: *MalType) MalError!*MalType { +const stdout_file = std.io.getStdOut(); +const throw = @import("error.zig").throw; + +fn int_plus(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeAdd(i64, x, y) catch return MalError.Overflow; - return MalType.new_int(Allocator, res); + const res = try safeAdd(i64, x, y); + return MalType.new_int(res); } -fn int_minus(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_minus(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeSub(i64, x, y) catch return MalError.Overflow; - return MalType.new_int(Allocator, res); + const res = try safeSub(i64, x, y); + return MalType.new_int(res); } -fn int_mult(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_mult(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeMul(i64, x, y) catch return MalError.Overflow; - return MalType.new_int(Allocator, res); + const res = try safeMul(i64, x, y); + return MalType.new_int(res); } -fn int_div(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_div(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeDivFloor(i64, x, y) catch |err| switch(err) { - error.DivisionByZero => return MalError.DivisionByZero, - else => return MalError.Overflow, - }; - return MalType.new_int(Allocator, res); + const res = try safeDivFloor(i64, x, y); + return MalType.new_int(res); } -fn int_lt(a1: *MalType, a2: *MalType) MalError!*MalType { - return MalType.new_bool(Allocator, (try a1.as_int()) < (try a2.as_int())); +fn int_lt(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + return MalType.new_bool((try a1.as_int()) < (try a2.as_int())); } -fn int_leq(a1: *MalType, a2: *MalType) MalError!*MalType { - return MalType.new_bool(Allocator, (try a1.as_int()) <= (try a2.as_int())); +fn int_leq(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + return MalType.new_bool((try a1.as_int()) <= (try a2.as_int())); } -fn int_gt(a1: *MalType, a2: *MalType) MalError!*MalType { - return MalType.new_bool(Allocator, (try a1.as_int()) > (try a2.as_int())); +fn int_gt(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + return MalType.new_bool((try a1.as_int()) > (try a2.as_int())); } -fn int_geq(a1: *MalType, a2: *MalType) MalError!*MalType { - return MalType.new_bool(Allocator, (try a1.as_int()) >= (try a2.as_int())); +fn int_geq(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + return MalType.new_bool((try a1.as_int()) >= (try a2.as_int())); } -fn _linked_list_equality(l1: MalLinkedList, l2: MalLinkedList) MalError!bool { - if(l1.count() != l2.count()) { - return false; - } - var it1 = l1.iterator(); - var it2 = l2.iterator(); - while(true) { - const m1 = it1.next() orelse return (it2.next() == null); - const m2 = it2.next() orelse return false; - const el_cmp = try equality(m1, m2); - if(MalTypeValue(el_cmp.data) == MalTypeValue.False) { - el_cmp.delete(Allocator); +fn _linked_list_equality(l1: []const *MalType, l2:[]const *MalType) bool { + if(l1.len != l2.len) return false; + for(l1, l2) |m1, m2| { + if(! _equality(m1.*, m2.*)) { return false; } - el_cmp.delete(Allocator); } return true; } -fn _hashmap_equality(h1: MalHashMap, h2: MalHashMap) MalError!bool { +fn _hashmap_equality(h1: MalHashMap, h2: MalHashMap) bool { if(h1.count() != h2.count()) { return false; } var iterator = h1.iterator(); - var optional_pair = iterator.next(); - while(optional_pair) |pair| { - const optional_val = h2.getValue(pair.key); + while(iterator.next()) |pair| { + const optional_val = h2.get(pair.key_ptr.*); if(optional_val) |val| { - const el_cmp = try equality(pair.value, val); - if(MalTypeValue(el_cmp.data) == MalTypeValue.False) { - el_cmp.delete(Allocator); + const el_cmp = _equality(pair.value_ptr.*.*, val.*); + if(! el_cmp) { return false; } - el_cmp.delete(Allocator); } else { return false; } - optional_pair = iterator.next(); } return true; } -// TODO: make _equality -> bool -fn equality(a1: *MalType, a2: *MalType) MalError!*MalType { - const a1_is_sequential = (MalTypeValue(a1.data) == MalTypeValue.List) or - (MalTypeValue(a1.data) == MalTypeValue.Vector); - const a2_is_sequential = (MalTypeValue(a2.data) == MalTypeValue.List) or - (MalTypeValue(a2.data) == MalTypeValue.Vector); +fn equality(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + return MalType.new_bool(_equality(a1.*, a2.*)); +} - if(a1_is_sequential and a2_is_sequential) { - const l1 = (try a1.sequence_linked_list()).*; - const l2 = (try a2.sequence_linked_list()).*; - return MalType.new_bool(Allocator, try _linked_list_equality(l1, l2)); - } - - if(MalTypeValue(a1.data) != MalTypeValue(a2.data)) { - return MalType.new_bool(Allocator, false); - } - - switch(a1.data) { - .True, .False, .Nil => { - return MalType.new_bool(Allocator, true); +fn _equality(a1: MalType, a2: MalType) bool { + switch(a1) { + .Nil => { + switch(a2) { + .Nil => return true, + else => return false, + } }, - .Int => |v1| { - return MalType.new_bool(Allocator, v1 == a2.data.Int); + .False => { + switch(a2) { + .False => return true, + else => return false, + } }, - .List => |l1| { - const l2 = a2.data.List; - return MalType.new_bool(Allocator, try _linked_list_equality(l1, l2)); + .True => { + switch(a2) { + .True => return true, + else => return false, + } }, - .Vector => |v1| { - const v2 = a2.data.Vector; - return MalType.new_bool(Allocator, try _linked_list_equality(v1, v2)); + .Int => |l1| { + switch(a2) { + .Int => |l2| return l1.data == l2.data, + else => return false, + } }, .String => |s1| { - const s2 = a2.data.String; - return MalType.new_bool(Allocator, string_eql(s1, s2)); + switch(a2) { + .String => |s2| return string_eql(s1.data, s2.data), + else => return false, + } + }, + .Symbol => |s1| { + switch(a2) { + .Symbol => |s2| return string_eql(s1.data, s2.data), + else => return false, + } }, - .Generic => |v1| { - const v2 = a2.data.Generic; - return MalType.new_bool(Allocator, string_eql(v1, v2)); + .Keyword => |s1| { + switch(a2) { + .Keyword => |s2| return string_eql(s1.data, s2.data), + else => return false, + } }, - .Keyword => |k1| { - const k2 = a2.data.Keyword; - return MalType.new_bool(Allocator, string_eql(k1, k2)); + .List, .Vector => |l1| { + switch(a2) { + .List, .Vector => |l2| return _linked_list_equality( + l1.data.items, l2.data.items), + else => return false, + } }, .HashMap => |h1| { - const h2 = a2.data.HashMap; - return MalType.new_bool(Allocator, try _hashmap_equality(h1,h2)); + switch(a2) { + .HashMap => |h2| return _hashmap_equality(h1.data, h2.data), + else => return false, + } + }, + else => { + return false; }, - // TODO: implement more types - else => return MalType.new_bool(Allocator, false), } } -fn list(args: MalLinkedList) MalError!*MalType { - var new_mal = try MalType.new_list_empty(Allocator); - new_mal.data = MalData{.List = try linked_list.deepcopy(Allocator, args)}; +fn list(args: []*MalType) !*MalType { + const new_mal = try MalType.new_list(); + errdefer new_mal.decref(); + for(args) |x| { + try new_mal.List.data.append(Allocator, x); + x.incref(); + } return new_mal; } -fn vector(args: MalLinkedList) MalError!*MalType { - var new_mal = try MalType.new_list_empty(Allocator); - new_mal.data = MalData{.Vector = try linked_list.deepcopy(Allocator, args)}; +fn vector(args: []*MalType) !*MalType { + const new_mal = try MalType.new_vector(); + errdefer new_mal.decref(); + for(args) |x| { + try new_mal.Vector.data.append(Allocator, x); + x.incref(); + } return new_mal; } -fn map(args: MalLinkedList) MalError!*MalType { - if(args.count() < 2) return MalError.ArgError; - const func_mal = args.at(0); - var args_mal = args.at(1); - var new_ll = MalLinkedList.init(Allocator); - var to_map_ll = try args_mal.sequence_linked_list(); - - var iterator = to_map_ll.iterator(); - while(iterator.next()) |mal| { - var args_ll = MalLinkedList.init(Allocator); - // TODO: can be more efficient than this - try linked_list.append_mal(Allocator, &args_ll, try func_mal.copy(Allocator)); - try linked_list.append_mal(Allocator, &args_ll, try mal.copy(Allocator)); - const new_mal = try apply_function(Allocator, args_ll); - linked_list.destroy(Allocator, &args_ll, false); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn map(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const func_mal = args[0]; + const args_mal = args[1]; + var to_map_ll = try args_mal.as_slice(); + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + for(0..to_map_ll.len) |i| { + const new_mal = try apply_function(func_mal.*, to_map_ll[i..i+1]); + try new_list.List.data.append(Allocator, new_mal); } - const new_list = try MalType.new_nil(Allocator); - new_list.data = MalData{.List = new_ll}; return new_list; } -fn is_list(a1: *MalType) MalError!*MalType { - return MalType.new_bool(Allocator, MalTypeValue(a1.data) == MalTypeValue.List); +fn is_list(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .List => &MalType.TRUE, + else => &MalType.FALSE, + }; } -fn is_vector(a1: *MalType) MalError!*MalType { - return MalType.new_bool(Allocator, MalTypeValue(a1.data) == MalTypeValue.Vector); +fn is_vector(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .Vector => &MalType.TRUE, + else => &MalType.FALSE, + }; } -pub fn is_string(a1: *MalType) MalError!*MalType { - return MalType.new_bool(Allocator, MalTypeValue(a1.data) == MalTypeValue.String); +fn is_string(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .String => &MalType.TRUE, + else => &MalType.FALSE, + }; } -pub fn is_number(a1: *MalType) MalError!*MalType { - return MalType.new_bool(Allocator, MalTypeValue(a1.data) == MalTypeValue.Int); +fn is_number(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .Int => &MalType.TRUE, + else => &MalType.FALSE, + }; } -pub fn is_fn(a1: *MalType) MalError!*MalType { - const is_function = switch(a1.data) { - .Fn0 => true, - .Fn1 => true, - .Fn2 => true, - .Fn3 => true, - .Fn4 => true, - .FVar => true, - .Func => |func_data| !func_data.is_macro, +fn is_fn(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const is_function = switch(a1.*) { + .FnCore => true, + .Func => |func_data| ! func_data.is_macro, else => false, }; - return MalType.new_bool(Allocator, is_function); + return MalType.new_bool(is_function); } -pub fn is_macro(a1: *MalType) MalError!*MalType { - const is_func_and_macro = switch(a1.data) { +fn is_macro(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const is_func_and_macro = switch(a1.*) { .Func => |data| data.is_macro, else => false, }; - return MalType.new_bool(Allocator, is_func_and_macro); + return MalType.new_bool(is_func_and_macro); } -fn empty(a1: *MalType) MalError!*MalType { - return switch(a1.data) { - .List => |l| MalType.new_bool(Allocator, l.len == 0), - .Vector => |v| MalType.new_bool(Allocator, v.len == 0), - else => MalType.new_bool(Allocator, false), - }; +fn empty(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const slice = try a1.as_slice(); + return MalType.new_bool(slice.len == 0); } -fn prn(args: MalLinkedList) MalError!*MalType { +fn prn(args: []*MalType) MalError!*MalType { const s = try printer.print_mal_to_string(args, true, true); - const stdout_file = std.io.getStdOut() catch return MalError.SystemError; - stdout_file.write(s) catch return MalError.SystemError; - stdout_file.write("\n") catch return MalError.SystemError; - Allocator.free(s); - const mal = try MalType.new_nil(Allocator); + defer Allocator.free(s); + try stdout_file.writeAll(s); + try stdout_file.writeAll("\n"); + const mal = &MalType.NIL; return mal; } -fn println(args: MalLinkedList) MalError!*MalType { +fn println(args: []*MalType) !*MalType { const s = try printer.print_mal_to_string(args, false, true); - const stdout_file = std.io.getStdOut() catch return MalError.SystemError; - stdout_file.write(s) catch return MalError.SystemError; - stdout_file.write("\n") catch return MalError.SystemError; - Allocator.free(s); - const mal = try MalType.new_nil(Allocator); + defer Allocator.free(s); + try stdout_file.writeAll(s); + try stdout_file.writeAll("\n"); + const mal = &MalType.NIL; return mal; } -fn str(args: MalLinkedList) MalError!*MalType { - if(args.count() == 0) { - const s: []u8 = ""; - return MalType.new_string(Allocator, s); - } - const s = try printer.print_mal_to_string(args, false, false); - return MalType.new_string(Allocator, s); +fn str(args: []*MalType) !*MalType { + const items = try printer.print_mal_to_string(args, false, false); + return MalType.new_string(items, false); } -fn pr_str(args: MalLinkedList) MalError!*MalType { - if(args.count() == 0) { - const s: []u8 = ""; - return MalType.new_string(Allocator, s); - } +fn pr_str(args: []*MalType) !*MalType { const s = try printer.print_mal_to_string(args, true, true); - return MalType.new_string(Allocator, s); + return MalType.new_string(s, false); } -fn slurp(a1: *MalType) MalError!*MalType { - switch(a1.data) { - .String => |path| { - const file_contents = std.io.readFileAlloc(Allocator, path) - catch |err| return MalError.SystemError; // TODO: change this error - defer Allocator.free(file_contents); - return MalType.new_string(Allocator, file_contents); - }, - else => { - return MalError.TypeError; - }, - } - return unreachable; +fn slurp(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const path = try a1.as_string(); + const dir = std.fs.cwd(); + const items = try dir.readFileAlloc(Allocator, path, 10000); + return MalType.new_string(items, false); } -fn atom(a1: *MalType) MalError!*MalType { - return MalType.new_atom(Allocator, a1); +fn atom(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const result = try MalType.new_atom(a1); + a1.incref(); + return result; } -fn is_atom(a1: *MalType) MalError!*MalType { - return MalType.new_bool(Allocator, MalTypeValue(a1.data) == MalTypeValue.Atom); +fn is_atom(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + return switch(args[0].*) { + .Atom => &MalType.TRUE, + else => &MalType.FALSE, + }; } -fn deref(a1: *MalType) MalError!*MalType { - return switch(a1.data) { - .Atom => |atom_val| atom_val.*.copy(Allocator), - else => MalError.TypeError, - }; +fn deref(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + switch(a1.*) { + .Atom => |atom_val| { + atom_val.data.incref(); + return atom_val.data; + }, + else => return MalError.TypeError, + } } -fn atom_reset(a1: *MalType, a2: *MalType) MalError!*MalType { - switch(a1.data) { +fn atom_reset(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + switch(a1.*) { .Atom => |*atom_val| { - var new_target = try a2.copy(Allocator); - atom_val.*.*.delete(Allocator); - atom_val.*.* = new_target; - return new_target.copy(Allocator); + atom_val.data.decref(); + atom_val.data = a2; + // incref for the atom and for the result + a2.incref(); + a2.incref(); + return a2; }, else => return MalError.TypeError, } } -fn atom_swap(args: MalLinkedList) MalError!*MalType { - const args_arr = args.toSlice(); +fn atom_swap(args: []*MalType) !*MalType { const n = args.len; if(n < 2) return MalError.ArgError; - var new_args = MalLinkedList.init(Allocator); - defer linked_list.destroy(Allocator, &new_args, false); - try linked_list.append_mal(Allocator, &new_args, try args_arr[1].copy(Allocator)); - try linked_list.append_mal(Allocator, &new_args, try deref(args_arr[0])); - var i: usize = 2; - while(i < n) { - try linked_list.append_mal(Allocator, &new_args, try args_arr[i].copy(Allocator)); + + const atom_val = switch(args[0].*) { + .Atom => |*a| a, + else => return MalError.TypeError, + }; + + var new_args = try Allocator.alloc(*MalType, args.len - 1); + defer Allocator.free(new_args); + var i:usize = 0; + new_args[i] = atom_val.data; i+=1; + for(args[2..args.len]) |x| { + new_args[i] = x; i += 1; } - const return_mal = try apply_function(Allocator, new_args); - const new_mal = atom_reset(args_arr[0], return_mal); - return_mal.delete(Allocator); + std.debug.assert(i == new_args.len); + + const new_mal = try apply_function(args[1].*, new_args); + atom_val.data.decref(); // after the computation + atom_val.data = new_mal; + new_mal.incref(); return new_mal; } -pub fn vec(a1: *const MalType) MalError!*MalType { - const ll = switch(a1.data) { - .List => |l| l, - .Vector => |v| v, - else => return MalError.TypeError, - }; - const copy = try linked_list.deepcopy(Allocator, ll); - return MalType.new_vector(Allocator, copy); -} - -pub fn cons(a1: *const MalType, a2: *const MalType) MalError!*MalType { - // TODO: do we need this for vectors? - const old_ll = try a2.const_sequence_linked_list(); - var new_ll = try linked_list.deepcopy(Allocator, old_ll); - var new_list = try MalType.new_nil(Allocator); - new_list.data = MalData{.List = new_ll}; - errdefer new_list.delete(Allocator); - var new_mal = try a1.copy(Allocator); - errdefer new_mal.delete(Allocator); - try linked_list.prepend_mal(Allocator, &new_list.data.List, new_mal); - return new_list; +fn vec(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + switch(a1.*) { + .List => |l| { + const result = try MalType.new_vector(); + errdefer result.decref(); + for(l.data.items) |x| { + try result.Vector.data.append(Allocator, x); + x.incref(); + } + return result; + }, + .Vector => { + a1.incref(); + return a1; + }, + else => return MalError.TypeError, + } } -pub fn concat(args: MalLinkedList) MalError!*MalType { - // First we make a new array with shallow copies - var new_ll = MalLinkedList.init(Allocator); - errdefer linked_list.destroy(Allocator, &new_ll, false); - var iterator = args.iterator(); - while(iterator.next()) |mal| { - const mal_seq = try mal.sequence_linked_list(); - new_ll.appendSlice(mal_seq.toSlice()) catch return MalError.SystemError; +fn cons(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + const old_ll = try a2.as_slice(); + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + try new_list.List.data.append(Allocator, a1); + a1.incref(); + for(old_ll) |x| { + try new_list.List.data.append(Allocator, x); + x.incref(); } + return new_list; +} - // Now we turn the shallow copies into deep copies - const new_arr = new_ll.toSlice(); - var i: usize = 0; - while(i < new_arr.len) { - new_arr[i] = try new_arr[i].copy(Allocator); - i += 1; +pub fn concat(args: []*MalType) !*MalType { + const new_mal = try MalType.new_list(); + errdefer new_mal.decref(); + for(args) |x| { + for(try x.as_slice()) |y| { + try new_mal.List.data.append(Allocator, y); + y.incref(); + } } - - // Wrap the list in a MalType, return - var new_mal = try MalType.new_nil(Allocator); - new_mal.data = MalData{.List = new_ll}; return new_mal; } -pub fn rest(a1: *const MalType) MalError!*MalType { - var old_list = switch(a1.data) { - .List => |l| l, - .Vector => |v| v, - .Nil => return MalType.new_list_empty(Allocator), +fn rest(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const new_mal = try MalType.new_list(); + errdefer new_mal.decref(); + switch(a1.*) { + .List, .Vector => |l| { + const old_list = l.data.items; + if(old_list.len != 0) { + for(l.data.items[1..]) |x| { + try new_mal.List.data.append(Allocator, x); + x.incref(); + } + } + }, + .Nil => { }, else => return MalError.TypeError, - }; - var new_list = try linked_list.deepcopy(Allocator, old_list); - errdefer linked_list.destroy(Allocator, &new_list, false); - - if(new_list.count() > 0) { - const mal = try linked_list.pop_first(Allocator, &new_list); - mal.delete(Allocator); } - var new_mal = try MalType.new_nil(Allocator); - new_mal.data = MalData{.List = new_list}; return new_mal; } -pub fn _nth(mal_list: *const MalType, pos: i64) MalError!*MalType { - // TODO: vectors? - const l = try mal_list.const_sequence_linked_list(); - if(pos < 0 or pos >= @intCast(i64,l.count())) { +fn nth(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + const l = try a1.as_slice(); + const i = try a2.as_int(); + const pos: usize = @intCast(i); + if(pos < 0 or l.len <= pos) { return MalError.OutOfBounds; } - return l.at(@intCast(usize,pos)); + const result = l[pos]; + result.incref(); + return result; +} + +fn first(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + switch(a1.*) { + .List, .Vector => |l| { + if(l.data.items.len == 0) return &MalType.NIL; + const result = l.data.items[0]; + result.incref(); + return result; + }, + .Nil => return &MalType.NIL, + else => return MalError.TypeError, + } } -pub fn nth(a1: *const MalType, a2: *const MalType) MalError!*MalType { - return switch(a2.data) { - .Int => |pos| (try _nth(a1, pos)).copy(Allocator), - else => MalError.TypeError, +fn is_nil(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .Nil => &MalType.TRUE, + else => &MalType.FALSE, }; } -pub fn first(a1: *const MalType) MalError!*MalType { - var l = switch(a1.data) { - .List => |l| l, - .Vector => |v| v, - .Nil => return MalType.new_nil(Allocator), - else => return MalError.TypeError, +fn is_true(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .True => &MalType.TRUE, + else => &MalType.FALSE, }; - if(l.count() == 0) return MalType.new_nil(Allocator); - return l.at(0).copy(Allocator); } -fn check_type(mal: *const MalType, value_type: MalTypeValue) MalError!*MalType { - // TODO: use this everywhere - // TODO: do this more generically - return MalType.new_bool(Allocator, MalTypeValue(mal.data) == value_type); -} - -pub fn is_nil(a1: *const MalType) MalError!*MalType { - return check_type(a1, MalTypeValue.Nil); -} - -pub fn is_true(a1: *const MalType) MalError!*MalType { - return check_type(a1, MalTypeValue.True); +fn is_false(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .False => &MalType.TRUE, + else => &MalType.FALSE, + }; } -pub fn is_false(a1: *const MalType) MalError!*MalType { - return check_type(a1, MalTypeValue.False); +fn is_symbol(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .Symbol => &MalType.TRUE, + else => &MalType.FALSE, + }; } -pub fn is_symbol(a1: *const MalType) MalError!*MalType { - return check_type(a1, MalTypeValue.Generic); +fn is_keyword(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .Keyword => &MalType.TRUE, + else => &MalType.FALSE, + }; } -pub fn is_keyword(a1: *const MalType) MalError!*MalType { - return check_type(a1, MalTypeValue.Keyword); +fn is_map(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .HashMap => &MalType.TRUE, + else => &MalType.FALSE, + }; } -pub fn is_map(a1: *const MalType) MalError!*MalType { - return check_type(a1, MalTypeValue.HashMap); +fn is_sequential(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return switch(a1.*) { + .List, .Vector => &MalType.TRUE, + else => &MalType.FALSE, + }; } -pub fn is_sequential(a1: *const MalType) MalError!*MalType { - const res = (MalTypeValue(a1.data) == MalTypeValue.Vector) or - (MalTypeValue(a1.data) == MalTypeValue.List); - return MalType.new_bool(Allocator, res); +fn symbol(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const string = try a1.as_string(); + return MalType.new_symbol(string, true); } -pub fn symbol(a1: *const MalType) MalError!*MalType { - const string = switch(a1.data) { - .String => |s| s, - else => return MalError.TypeError, - }; - return MalType.new_generic(Allocator, string); -} - -pub fn hash_map(args: MalLinkedList) MalError!*MalType { - const new_mal = try MalType.new_hashmap(Allocator); - const args_arr = args.toSlice(); - const n = args_arr.len; - if((n%2) != 0) return MalError.ArgError; - var i: usize = 0; - - while(2*i+1 < n) { - const this_key = switch(args_arr[2*i].data) { - .String => |s| s, - .Keyword => |kwd| kwd, - else => return MalError.ArgError, - }; - const this_key_cpy = string_copy(Allocator, this_key) catch return MalError.SystemError; - const this_val_cpy = try args_arr[2*i+1].copy(Allocator); - try new_mal.hashmap_insert(this_key_cpy, this_val_cpy); - i += 1; - } +pub fn hash_map(args: []*MalType) !*MalType { + const new_mal = try MalType.new_hashmap(); + errdefer new_mal.decref(); + try hmap.map_insert_from_kvs(&new_mal.HashMap.data, args); return new_mal; } -pub fn hash_map_assoc(args: MalLinkedList) MalError!*MalType { - const args_arr = args.toSlice(); - if(args_arr.len < 1) return MalError.ArgError; - const new_mal = try MalType.new_nil(Allocator); - errdefer new_mal.delete(Allocator); - const base_hmap = switch(args_arr[0].data) { - .HashMap => |hm| hm, - else => return MalError.TypeError, - }; - const hmap_cpy = hmap.deepcopy(Allocator, base_hmap) catch return MalError.SystemError; - new_mal.data = MalData {.HashMap = hmap_cpy}; - - const assoc_arr = args_arr[1..args_arr.len]; - if((assoc_arr.len % 2) != 0) return MalError.ArgError; - var i: usize = 0; - while(2*i+1 < assoc_arr.len) { - const this_key = switch(assoc_arr[2*i].data) { - .String => |s| s, - .Keyword => |kwd| kwd, - else => return MalError.ArgError, - }; - const this_key_cpy = string_copy(Allocator, this_key) catch return MalError.SystemError; - const this_val_cpy = try assoc_arr[2*i+1].copy(Allocator); - try new_mal.hashmap_insert(this_key_cpy, this_val_cpy); - i += 1; - } +pub fn hash_map_assoc(args: []*MalType) !*MalType { + if(args.len < 1) return MalError.ArgError; + const a1 = args[0]; + const new_mal = try MalType.new_hashmap(); + errdefer new_mal.decref(); + const base_hmap = try a1.as_map(); + try hmap.map_insert_from_map(&new_mal.HashMap.data, base_hmap); + try hmap.map_insert_from_kvs(&new_mal.HashMap.data, args[1..]); return new_mal; } -pub fn hash_map_dissoc(args: MalLinkedList) MalError!*MalType { - const args_arr = args.toSlice(); - if(args_arr.len < 1) return MalError.ArgError; - const new_mal = try MalType.new_nil(Allocator); - errdefer new_mal.delete(Allocator); - const base_hmap = switch(args_arr[0].data) { - .HashMap => |hm| hm, - else => return MalError.TypeError, - }; - const hmap_cpy = hmap.deepcopy(Allocator, base_hmap) catch return MalError.SystemError; - new_mal.data = MalData {.HashMap = hmap_cpy}; - - var i: usize = 1; - while(i < args_arr.len) { - const this_key = switch(args_arr[i].data) { - .String => |s| s, - .Keyword => |kwd| kwd, - else => return MalError.ArgError, - }; - try new_mal.hashmap_remove(this_key); - i += 1; +pub fn hash_map_dissoc(args: []*MalType) !*MalType { + if(args.len < 1) return MalError.ArgError; + const a1 = args[0]; + const new_mal = try MalType.new_hashmap(); + errdefer new_mal.decref(); + const base_hmap = try a1.as_map(); + try hmap.map_insert_from_map(&new_mal.HashMap.data, base_hmap); + for(args[1..]) |k| { + switch(k.*) { + .Keyword, .String => { + if(new_mal.HashMap.data.fetchRemove(k)) |old| { + old.key.decref(); + old.value.decref(); + } + }, + else => return MalError.TypeError, + } } return new_mal; } -pub fn hash_map_get(a1: *MalType, a2: *MalType) MalError!*MalType { - const key = switch(a2.data) { - .String => |s| s, - .Keyword => |kwd| kwd, +fn hash_map_get(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + const hm = switch(a1.*) { + .HashMap => |m| m.data, + .Nil => return &MalType.NIL, else => return MalError.TypeError, }; - const optional_val = try a1.hashmap_get(key); - if(optional_val) |val| { - return val.copy(Allocator); + switch(a2.*) { + .Keyword, .String => {}, + else => return MalError.TypeError, + } + if(hm.get(a2)) |value| { + value.incref(); + return value; } - else return MalType.new_nil(Allocator); + return &MalType.NIL; } -pub fn hash_map_contains(a1: *MalType, a2: *MalType) MalError!*MalType { - const key = switch(a2.data) { - .String => |s| s, - .Keyword => |kwd| kwd, +fn hash_map_contains(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + switch(a2.*) { + .Keyword, .String => { + const hm = try a1.as_map(); + return MalType.new_bool(hm.contains(a2)); + }, else => return MalError.TypeError, - }; - const contains_bool = try a1.hashmap_contains(key); - return MalType.new_bool(Allocator, contains_bool); + } } -pub fn hash_map_keys(a1: *MalType) MalError!*MalType { - const hm = switch(a1.data) { - .HashMap => |h| h, - else => return MalError.TypeError, - }; - var new_ll = MalLinkedList.init(Allocator); - errdefer linked_list.destroy(Allocator, &new_ll, false); - var iterator = hm.iterator(); - var optional_pair = iterator.next(); - - while(true) { - const pair = optional_pair orelse break; - const key = string_copy(Allocator, pair.key) catch return MalError.SystemError; - - var key_mal: *MalType = undefined; - if(key.len > 1 and key[0] == 255) { - key_mal = try MalType.new_keyword(Allocator, key[1..key.len]); - } else { - key_mal = try MalType.new_string(Allocator, key); - } - try linked_list.append_mal(Allocator, &new_ll, key_mal); - optional_pair = iterator.next(); +fn hash_map_keys(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const hm = try a1.as_map(); + const new_mal = try MalType.new_list(); + errdefer new_mal.decref(); + var iterator = hm.keyIterator(); + while(iterator.next()) |key_mal| { + try new_mal.List.data.append(Allocator, key_mal.*); + key_mal.*.incref(); } - var new_mal = try MalType.new_nil(Allocator); - new_mal.data = MalData{.List = new_ll}; return new_mal; } -pub fn hash_map_vals(a1: *MalType) MalError!*MalType { - const hm = switch(a1.data) { - .HashMap => |h| h, - else => return MalError.TypeError, - }; - var new_ll = MalLinkedList.init(Allocator); - errdefer linked_list.destroy(Allocator, &new_ll, false); - var iterator = hm.iterator(); - var optional_pair = iterator.next(); - - while(true) { - const pair = optional_pair orelse break; - const val = try pair.value.copy(Allocator); - try linked_list.append_mal(Allocator, &new_ll, val); - optional_pair = iterator.next(); +fn hash_map_vals(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const hm = try a1.as_map(); + const new_mal = try MalType.new_list(); + errdefer new_mal.decref(); + var iterator = hm.valueIterator(); + while(iterator.next()) |val| { + try new_mal.List.data.append(Allocator, val.*); + val.*.incref(); } - var new_mal = try MalType.new_nil(Allocator); - new_mal.data = MalData{.List = new_ll}; return new_mal; } -pub fn sequence_length(a1: *MalType) MalError!*MalType { - const len = switch(a1.data) { - .List => |l| l.count(), - .Vector => |v| v.count(), - .String => |s| s.len, +fn sequence_length(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const len = switch(a1.*) { + .List, .Vector => |l| l.data.items.len, + .String => |s| s.data.len, .Nil => 0, else => return MalError.TypeError, }; - return MalType.new_int(Allocator, @intCast(i64,len)); + return MalType.new_int(@intCast(len)); } -pub fn keyword(a1: *MalType) MalError!*MalType { - const kwd = switch(a1.data) { - .String => |s| s, - .Keyword => |k| return a1.copy(Allocator), +fn keyword(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + switch(a1.*) { + .String => |s| { + return MalType.new_keyword(s.data, true); + }, + .Keyword => { + a1.incref(); + return a1; + }, else => return MalError.TypeError, - }; - return MalType.new_keyword(Allocator, kwd); + } } -pub fn readline(a1: *MalType) MalError!*MalType { +fn core_readline(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; const prompt = try a1.as_string(); - const optional_read_line = getline_prompt(Allocator, prompt) - catch return MalError.SystemError; + const optional_read_line = try getline_prompt(prompt); if(optional_read_line) |read_line| { - return MalType.new_string(Allocator, read_line); + return MalType.new_string(read_line, false); } - const mal = try MalType.new_nil(Allocator); - return MalType.new_nil(Allocator); + return &MalType.NIL; } -pub fn time_ms() MalError!*MalType { - const itime: i64 = @intCast(i64, std.time.milliTimestamp()); - return MalType.new_int(Allocator, itime); +fn time_ms(args: []*MalType) !*MalType { + if(args.len != 0) return MalError.ArgError; + const itime = std.time.milliTimestamp(); + return try MalType.new_int(@intCast(itime)); } -pub fn meta(a1: *MalType) MalError!*MalType { - if(a1.meta) |mal_meta| { - return mal_meta.copy(Allocator); - } - return MalType.new_nil(Allocator); +fn meta(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + const result = switch(a1.*) { + .List, .Vector => |l| l.metadata, + .FnCore => |l| l.metadata, + .Func => |l| l.metadata, + .HashMap => |l| l.metadata, + else => return MalError.TypeError, + }; + result.incref(); + return result; } -pub fn with_meta(a1: *MalType, a2: *MalType) MalError!*MalType { - var new_mal = try a1.copy(Allocator); - if(new_mal.meta) |mal_meta| { - mal_meta.delete(Allocator); +fn with_meta(args: []*MalType) !*MalType { + if(args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; + switch(a1.*) { + .List => |l| { + const new_mal = try MalType.new_list(); + errdefer new_mal.decref(); + for(l.data.items) |x| { + try new_mal.List.data.append(Allocator, x); + x.incref(); + } + new_mal.List.metadata = a2; + a2.incref(); + return new_mal; + }, + .Vector => |l| { + const new_mal = try MalType.new_vector(); + errdefer new_mal.decref(); + for(l.data.items) |x| { + try new_mal.Vector.data.append(Allocator, x); + x.incref(); + } + new_mal.Vector.metadata = a2; + a2.incref(); + return new_mal; + }, + .FnCore => |l| { + const new_mal = try MalType.newFnCore(l.data); + new_mal.FnCore.metadata = a2; + a2.incref(); + return new_mal; + }, + .Func => |l| { + const new_mal = try MalType.newFunc(l.arg_list, l.body, + l.environment); + l.arg_list.incref(); + l.body.incref(); + l.environment.incref(); + new_mal.Func.metadata = a2; + a2.incref(); + return new_mal; + }, + .HashMap => |l| { + const new_mal = try MalType.new_hashmap(); + errdefer new_mal.decref(); + try hmap.map_insert_from_map(&new_mal.HashMap.data, l.data); + new_mal.HashMap.metadata = a2; + a2.incref(); + return new_mal; + }, + else => return MalError.TypeError, } - new_mal.meta = try a2.copy(Allocator); - return new_mal; } -pub fn seq(a1: *MalType) MalError!*MalType { - switch(a1.data) { +fn seq(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + switch(a1.*) { .List => |l| { - if(l.count() == 0) return MalType.new_nil(Allocator); - return a1.copy(Allocator); + if(l.data.items.len == 0) return &MalType.NIL; + a1.incref(); + return a1; }, - .Vector => |v| { - if(v.count() == 0) return MalType.new_nil(Allocator); - const mal_copy = try a1.copy(Allocator); - const ll = mal_copy.data.Vector; - mal_copy.data = MalData{.List = ll}; + .Vector => |l| { + if(l.data.items.len == 0) return &MalType.NIL; + const mal_copy = try MalType.new_list(); + errdefer mal_copy.decref(); + for(l.data.items) |x| { + try mal_copy.List.data.append(Allocator, x); + x.incref(); + } return mal_copy; }, .String => |s| { - if(s.len == 0) return MalType.new_nil(Allocator); - const new_list = try MalType.new_list_empty(Allocator); - for(s) |letter| { - const new_char = try MalType.new_string(Allocator, [_]u8 {letter}); - try new_list.sequence_append(Allocator, new_char); + if(s.data.len == 0) return &MalType.NIL; + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + for(s.data) |x| { + const one_char = try Allocator.alloc(u8, 1); + one_char[0] = x; + const new_char = try MalType.new_string(one_char, false); + errdefer new_char.decref(); + try new_list.List.data.append(Allocator, new_char); } return new_list; }, .Nil => { - return MalType.new_nil(Allocator); + return &MalType.NIL; }, else => { return MalError.TypeError; } } - return MalType.new_nil(Allocator); } -pub fn conj(args: MalLinkedList) MalError!*MalType { - var iterator = args.iterator(); - const container = iterator.next() orelse return MalError.ArgError; - const append = switch(container.data) { - .List => false, - .Vector => true, +pub fn conj(args: []*MalType) !*MalType { + if(args.len == 0) return MalError.ArgError; + const container = args[0]; + switch(container.*) { + .List => |l| { + const return_mal = try MalType.new_list(); + errdefer return_mal.decref(); + for(1..args.len) |j| { + const new_item = args[args.len-j]; + try return_mal.List.data.append(Allocator, new_item); + new_item.incref(); + } + for(l.data.items) |x| { + try return_mal.List.data.append(Allocator, x); + x.incref(); + } + return return_mal; + }, + .Vector => |l|{ + const return_mal = try MalType.new_vector(); + errdefer return_mal.decref(); + for(l.data.items) |x| { + try return_mal.Vector.data.append(Allocator, x); + x.incref(); + } + for(args[1..]) |x| { + try return_mal.Vector.data.append(Allocator, x); + x.incref(); + } + return return_mal; + }, else => return MalError.ArgError, - }; - - var return_mal = try container.copy(Allocator); - while(iterator.next()) |mal| { - const mal_copy = try mal.copy(Allocator); - if(append) { - try return_mal.sequence_append(Allocator, mal_copy); - } else { - try return_mal.sequence_prepend(Allocator, mal_copy); - } } - return return_mal; } -fn read_string(a1: *MalType) MalError!*MalType { +fn read_string(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; const str_to_eval = try a1.as_string(); var read = try reader.read_str(str_to_eval); - return (try reader.read_form(&read)) orelse return MalType.new_nil(Allocator); -} - -pub fn do_apply(args: MalLinkedList) MalError!*MalType { - // TODO: not always safe to delete new_ll here - if(args.count() == 0) return MalError.ArgError; - var args_copy = args; - const list_node = args_copy.pop(); - const list_ll = try list_node.sequence_linked_list(); - var new_ll = try linked_list.deepcopy(Allocator, list_ll.*); - defer linked_list.destroy(Allocator, &new_ll, false); - var optional_node = args_copy.popOrNull(); - while(optional_node) |node| { - try linked_list.prepend_mal(Allocator, &new_ll, try node.copy(Allocator)); - optional_node = args_copy.popOrNull(); - } - var return_mal = apply_function(Allocator, new_ll); - return return_mal; + return reader.read_form(&read); } -pub const CorePairType = enum { - Fn0, - Fn1, - Fn2, - Fn3, - Fn4, - FVar, -}; +pub fn do_apply(args: []*MalType) !*MalType { + if(args.len < 2) return MalError.ArgError; + const a1 = args[0]; + const last = args[args.len - 1]; + const more_args = try last.as_slice(); + var fargs = try Allocator.alloc(*MalType, args.len + more_args.len - 2); + defer Allocator.free(fargs); + var i:usize = 0; + for(args[1..args.len-1]) |x| { fargs[i] = x; i+=1; } + for(more_args) |x| { fargs[i] = x; i+=1; } + std.debug.assert(i == fargs.len); + return apply_function(a1.*, fargs); +} -pub const CorePairData = union(CorePairType) { - 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, -}; +pub fn core_throw(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return throw(a1); +} pub const CorePair = struct { name: []const u8, - func: CorePairData, + func: *const fn(args: []*MalType) MalError!*MalType, }; -pub const core_namespace = [_] CorePair { - CorePair { .name = "+", .func = CorePairData {.Fn2 = &int_plus} }, - CorePair { .name = "-", .func = CorePairData {.Fn2 = &int_minus} }, - CorePair { .name = "*", .func = CorePairData {.Fn2 = &int_mult} }, - CorePair { .name = "/", .func = CorePairData {.Fn2 = &int_div} }, - CorePair { .name = "<", .func = CorePairData {.Fn2 = &int_lt} }, - CorePair { .name = "<=", .func = CorePairData {.Fn2 = &int_leq} }, - CorePair { .name = ">", .func = CorePairData {.Fn2 = &int_gt} }, - CorePair { .name = ">=", .func = CorePairData {.Fn2 = &int_geq} }, - CorePair { .name = "=", .func = CorePairData {.Fn2 = &equality} }, - CorePair { .name = "list?", .func = CorePairData {.Fn1 = &is_list} }, - CorePair { .name = "vector?", .func = CorePairData {.Fn1 = &is_vector} }, - CorePair { .name = "count", .func = CorePairData {.Fn1 = &sequence_length} }, - CorePair { .name = "list", .func = CorePairData {.FVar = &list} }, - CorePair { .name = "vector", .func = CorePairData {.FVar = &vector} }, - CorePair { .name = "map", .func = CorePairData {.FVar = &map} }, - CorePair { .name = "empty?", .func = CorePairData {.Fn1 = &empty} }, - CorePair { .name = "prn", .func = CorePairData {.FVar = &prn} }, - CorePair { .name = "println", .func = CorePairData {.FVar = &println} }, - CorePair { .name = "pr-str", .func = CorePairData {.FVar = &pr_str} }, - CorePair { .name = "str", .func = CorePairData {.FVar = &str} }, - CorePair { .name = "slurp", .func = CorePairData {.Fn1 = &slurp} }, - CorePair { .name = "atom", .func = CorePairData {.Fn1 = &atom} }, - CorePair { .name = "atom?", .func = CorePairData {.Fn1 = &is_atom} }, - CorePair { .name = "deref", .func = CorePairData {.Fn1 = &deref} }, - CorePair { .name = "reset!", .func = CorePairData {.Fn2 = &atom_reset} }, - CorePair { .name = "swap!", .func = CorePairData {.FVar = &atom_swap} }, - CorePair { .name = "vec", .func = CorePairData {.Fn1 = &vec} }, - CorePair { .name = "cons", .func = CorePairData {.Fn2 = &cons} }, - CorePair { .name = "concat", .func = CorePairData {.FVar = &concat} }, - CorePair { .name = "rest", .func = CorePairData {.Fn1 = &rest } }, - CorePair { .name = "nth", .func = CorePairData {.Fn2 = &nth } }, - CorePair { .name = "first", .func = CorePairData {.Fn1 = &first } }, - CorePair { .name = "nil?", .func = CorePairData {.Fn1 = &is_nil } }, - CorePair { .name = "true?", .func = CorePairData {.Fn1 = &is_true } }, - CorePair { .name = "false?", .func = CorePairData {.Fn1 = &is_false } }, - CorePair { .name = "symbol", .func = CorePairData {.Fn1 = &symbol } }, - CorePair { .name = "symbol?", .func = CorePairData {.Fn1 = &is_symbol } }, - CorePair { .name = "keyword?", .func = CorePairData {.Fn1 = &is_keyword } }, - CorePair { .name = "map?", .func = CorePairData {.Fn1 = &is_map } }, - CorePair { .name = "sequential?", .func = CorePairData {.Fn1 = &is_sequential } }, - CorePair { .name = "apply", .func = CorePairData {.FVar = &do_apply } }, - CorePair { .name = "hash-map", .func = CorePairData {.FVar = &hash_map } }, - CorePair { .name = "assoc", .func = CorePairData {.FVar = &hash_map_assoc } }, - CorePair { .name = "dissoc", .func = CorePairData {.FVar = &hash_map_dissoc } }, - CorePair { .name = "get", .func = CorePairData {.Fn2 = &hash_map_get } }, - CorePair { .name = "contains?", .func = CorePairData {.Fn2 = &hash_map_contains } }, - CorePair { .name = "keys", .func = CorePairData {.Fn1 = &hash_map_keys } }, - CorePair { .name = "vals", .func = CorePairData {.Fn1 = &hash_map_vals } }, - CorePair { .name = "keyword", .func = CorePairData {.Fn1 = &keyword } }, - CorePair { .name = "read-string", .func = CorePairData {.Fn1 = &read_string } }, - CorePair { .name = "readline", .func = CorePairData {.Fn1 = &readline } }, - CorePair { .name = "time-ms", .func = CorePairData {.Fn0 = &time_ms } }, - CorePair { .name = "meta", .func = CorePairData {.Fn1 = &meta } }, - CorePair { .name = "with-meta", .func = CorePairData {.Fn2 = &with_meta } }, - CorePair { .name = "fn?", .func = CorePairData {.Fn1 = &is_fn } }, - CorePair { .name = "string?", .func = CorePairData {.Fn1 = &is_string } }, - CorePair { .name = "number?", .func = CorePairData {.Fn1 = &is_number } }, - CorePair { .name = "macro?", .func = CorePairData {.Fn1 = &is_macro } }, - CorePair { .name = "seq", .func = CorePairData {.Fn1 = &seq } }, - CorePair { .name = "conj", .func = CorePairData {.FVar = &conj } }, +pub const core_namespace = [_]CorePair { + .{ .name = "+", .func = &int_plus }, + .{ .name = "-", .func = &int_minus }, + .{ .name = "*", .func = &int_mult }, + .{ .name = "/", .func = &int_div }, + .{ .name = "<", .func = &int_lt }, + .{ .name = "<=", .func = &int_leq }, + .{ .name = ">", .func = &int_gt }, + .{ .name = ">=", .func = &int_geq }, + .{ .name = "=", .func = &equality }, + .{ .name = "list?", .func = &is_list }, + .{ .name = "vector?", .func = &is_vector }, + .{ .name = "count", .func = &sequence_length }, + .{ .name = "list", .func = &list, }, + .{ .name = "vector", .func = &vector, }, + .{ .name = "map", .func = &map }, + .{ .name = "empty?", .func = &empty }, + .{ .name = "prn", .func = &prn }, + .{ .name = "println", .func = &println }, + .{ .name = "pr-str", .func = &pr_str }, + .{ .name = "str", .func = &str }, + .{ .name = "slurp", .func = &slurp }, + .{ .name = "atom", .func = &atom }, + .{ .name = "atom?", .func = &is_atom }, + .{ .name = "deref", .func = &deref }, + .{ .name = "reset!", .func = &atom_reset }, + .{ .name = "swap!", .func = &atom_swap }, + .{ .name = "vec", .func = &vec }, + .{ .name = "cons", .func = &cons }, + .{ .name = "concat", .func = &concat }, + .{ .name = "rest", .func = &rest }, + .{ .name = "nth", .func = &nth }, + .{ .name = "first", .func = &first }, + .{ .name = "nil?", .func = &is_nil }, + .{ .name = "true?", .func = &is_true }, + .{ .name = "false?", .func = &is_false }, + .{ .name = "symbol", .func = &symbol }, + .{ .name = "symbol?", .func = &is_symbol }, + .{ .name = "keyword?", .func = &is_keyword }, + .{ .name = "map?", .func = &is_map }, + .{ .name = "sequential?", .func = &is_sequential }, + .{ .name = "apply", .func = &do_apply }, + .{ .name = "hash-map", .func = &hash_map }, + .{ .name = "assoc", .func = &hash_map_assoc }, + .{ .name = "dissoc", .func = &hash_map_dissoc }, + .{ .name = "get", .func = &hash_map_get }, + .{ .name = "contains?", .func = &hash_map_contains }, + .{ .name = "keys", .func = &hash_map_keys }, + .{ .name = "vals", .func = &hash_map_vals }, + .{ .name = "keyword", .func = &keyword }, + .{ .name = "read-string", .func = &read_string }, + .{ .name = "readline", .func = &core_readline }, + .{ .name = "time-ms", .func = &time_ms }, + .{ .name = "meta", .func = &meta }, + .{ .name = "with-meta", .func = &with_meta }, + .{ .name = "fn?", .func = &is_fn }, + .{ .name = "string?", .func = &is_string }, + .{ .name = "number?", .func = &is_number }, + .{ .name = "macro?", .func = &is_macro }, + .{ .name = "seq", .func = &seq }, + .{ .name = "conj", .func = &conj }, + .{ .name = "throw", .func = &core_throw }, }; diff --git a/impls/zig/env.zig b/impls/zig/env.zig index 18d1f15724..8abafc1d97 100644 --- a/impls/zig/env.zig +++ b/impls/zig/env.zig @@ -1,160 +1,110 @@ const std = @import("std"); -const warn = @import("std").debug.warn; -const Allocator = @import("std").mem.Allocator; +const warn = std.log.warn; +const allocator = std.heap.c_allocator; -const string_copy = @import("utils.zig").string_copy; -const string_eql = @import("utils.zig").string_eql; const MalType = @import("types.zig").MalType; -const MalTypeValue = @import("types.zig").MalTypeValue; const MalHashMap = @import("hmap.zig").MalHashMap; -const MalLinkedList = @import("linked_list.zig").MalLinkedList; const MalError = @import("error.zig").MalError; -const linked_list = @import("linked_list.zig"); const hash_map = @import("hmap.zig"); +const debug_alloc = @import("types.zig").debug_alloc; pub const Env = struct { - outer: ?**Env, - data: *MalHashMap, - allocator: *Allocator, - refcount: *i32, + outer: ?*Env, + data: MalHashMap, + refcount: i32 = 1, - pub fn new(allocator: *Allocator, optional_outer: ?*Env) MalError!*Env { - const env = allocator.create(Env) catch return MalError.SystemError; - env.refcount = allocator.create(i32) catch return MalError.SystemError; - env.refcount.* = 1; - if(optional_outer) |outer| { - const env_ptr = allocator.create(*Env) catch return MalError.SystemError; - env_ptr.* = try outer.copy(allocator); - env.outer = env_ptr; - } else { - env.outer = null; - } - env.data = allocator.create(MalHashMap) catch return MalError.SystemError; - env.data.* = MalHashMap.init(allocator); - env.allocator = allocator; - return env; - } - - pub fn copy(env: *Env, allocator: *Allocator) MalError!*Env { - const new_env = allocator.create(Env) catch return MalError.SystemError; - new_env.refcount = env.refcount; - env.refcount.* += 1; - new_env.outer = env.outer; - new_env.data = env.data; - new_env.allocator = allocator; - return new_env; - } - - pub fn delete(env: *Env) void { - env.refcount.* -= 1; - if(env.refcount.* <= 0) { - if(env.outer) |*outer| { - outer.*.*.delete(); - env.allocator.destroy(env.outer.?); - } - //env.print_keys(); - hash_map.destroy(env.allocator, env.data.*, false); - env.allocator.destroy(env.refcount); - env.allocator.destroy(env.data); - } - env.allocator.destroy(env); - } - - pub fn set(env: *Env, key: []const u8, value: *MalType) MalError!void { - const optional_prev_mal = env.data.getValue(key); - if(optional_prev_mal) |prev_mal| { - prev_mal.delete(env.allocator); - } - //warn("Setting {}\n", key); - const key_copy = string_copy(env.allocator, key) catch return MalError.SystemError; - _ = env.data.put(key_copy, value) catch return MalError.SystemError; + pub fn new_root() Env { + return .{.outer = null, .data = .{}}; } - pub fn root_set(env: *Env, key: []const u8, value: *MalType) MalError!void { - var root_env = env; - while(true) { - const outer_ptr = root_env.outer orelse break; - root_env = outer_ptr.*; - } - try root_env.set(key, value); + pub fn new(outer: *Env) !*Env { + // The caller is in charge of incremeting the reference count + // for outer if necessary. + const env = try allocator.create(Env); + env.* = .{ .outer = outer, .data = .{} }; + if(debug_alloc) warn("Env: new {any}", .{env}); + return env; } - pub fn find(env: *const Env, key: []const u8) bool { - const optional_mal = env.data.getValue(key); - if(optional_mal) |mal| { - return true; - } - if(env.outer) |outer| { - return outer.*.find(key); + pub fn incref(env: *Env) void { + if(debug_alloc) { + warn("Env: incref {any}", .{env}); } - return false; + env.refcount += 1; + // std.debug.assert(env.refcount < 100); } - pub fn get(env: *const Env, key: []const u8) MalError!*MalType { - const optional_mal = env.data.getValue(key); - if(optional_mal) |mal| { - //warn("Got for key '{}': {} (me: {})\n", key, mal, @ptrToInt(env)); - return mal; - } - if(env.outer) |outer| { - return outer.*.get(key); + pub fn decref(env: *Env) void { + var e = env; + while (true) { + if(debug_alloc) { + warn("Env: decref {any}", .{e}); + e.print_keys(); + } + std.debug.assert (0 < e.refcount); + e.refcount -= 1; + if(0 < e.refcount) { + break; + } + if(debug_alloc) { + warn("Env: FREE {any}", .{e}); + } + const old = e; + if(e.outer) |outer| { + e = outer; + } else { + warn("INTERNAL ERROR: repl-env should never reach a 0 refcount.", .{}); + break; + } + hash_map.map_destroy(&old.data); + allocator.destroy(old); } - return MalError.EnvLookupError; } - pub fn set_list(env: *Env, names: MalLinkedList, vals: MalLinkedList) MalError!void { - var name_arr = names.toSlice(); - var vals_arr = vals.toSlice(); - var i: usize = 0; - - while(i < name_arr.len) { - const key = try name_arr[i].as_symbol(); - if(!string_eql(key, "&")) { - try env.set(key, vals_arr[i]); - i += 1; - continue; - } - - // Here we deal with variadic binding - if(i+1 >= name_arr.len) return MalError.OutOfBounds; - const var_key = try name_arr[i+1].as_symbol(); - var new_ll = MalLinkedList.init(env.allocator); - new_ll.appendSlice(vals_arr[i..vals_arr.len]) catch return MalError.SystemError; - const new_mal = try MalType.new_list(env.allocator, new_ll); - try env.set(var_key, new_mal); - return; + // Incref both the key and value. + pub fn set(env: *Env, key: *MalType, value: *MalType) !void { + // The caller is in charge of incremeting the reference count + // for the value if necessary. + switch (key.*) { + .Symbol => { + if(debug_alloc) { + warn("Env: set {s} {any}", .{key.Symbol.data, key}); + } + try hash_map.map_insert_incref_key(&env.data, key, value); + }, + else => return MalError.ArgError, } } - pub fn set_slice(env: *Env, name_arr: []*MalType, vals_arr: []*MalType) MalError!void { - var i: usize = 0; - - while(i < name_arr.len) { - const key = try name_arr[i].as_symbol(); - if(!string_eql(key, "&")) { - try env.set(key, vals_arr[i]); - i += 1; - continue; - } - - // Here we deal with variadic binding - if(i+1 >= name_arr.len) return MalError.OutOfBounds; - const var_key = try name_arr[i+1].as_symbol(); - var new_ll = MalLinkedList.init(env.allocator); - new_ll.appendSlice(vals_arr[i..vals_arr.len]) catch return MalError.SystemError; - const new_mal = try MalType.new_list(env.allocator, new_ll); - try env.set(var_key, new_mal); - return; + pub fn get(env: Env, key: *MalType) !?*MalType { + // The result is not increfed(). + switch (key.*) { + .Symbol => { + if(debug_alloc) { + warn("Env: get {s} {any}", .{key.Symbol.data, key}); + } + var e: * const Env = &env; + while(true) { + if(e.data.get(key)) |value| { + return value; + } + e = e.outer orelse return null; + } + }, + else => return MalError.KeyError, } } - pub fn print_keys(env: *Env) void { - var it = env.data.iterator(); - var optional_pair = it.next(); - while(optional_pair) |pair| { - warn("{},",pair.key); - optional_pair = it.next(); + pub fn print_keys(env: Env) void { + var it = env.data.keyIterator(); + var count: i32 = 5; + while (it.next()) |key| { + warn(" key={s},", .{key.*.Symbol.data}); + count -= 1; + if(count <= 0) { + warn(" ...", .{}); + break; + } } - warn("\n"); } }; diff --git a/impls/zig/error.zig b/impls/zig/error.zig index f300ef86fa..b5ca016bca 100644 --- a/impls/zig/error.zig +++ b/impls/zig/error.zig @@ -1,20 +1,71 @@ +const assert = @import("std").debug.assert; +const MalType = @import("types.zig").MalType; + pub const MalError = error { SystemError, - EnvLookupError, ApplyError, - EvalError, KeyError, ThrownError, TypeError, ArgError, - ReaderUnmatchedParen, - ReaderUnmatchedString, - ReaderBadHashmap, - OutOfBounds, Overflow, DivisionByZero, + OutOfBounds, + + OutOfMemory, + + InvalidCharacter, + + DiskQuota, + FileTooBig, + InputOutput, + NoSpaceLeft, + DeviceBusy, + InvalidArgument, + AccessDenied, + BrokenPipe, + SystemResources, + OperationAborted, + NotOpenForWriting, + LockViolation, + WouldBlock, + ConnectionResetByPeer, + Unexpected, + + InvalidUtf8, + SharingViolation, + PathAlreadyExists, + FileNotFound, + PipeBusy, + NameTooLong, + InvalidWtf8, + BadPathName, + NetworkNotFound, + AntivirusInterference, + SymLinkLoop, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + IsDir, + NotDir, + FileLocksNotSupported, + FileBusy, + Unseekable, + ConnectionTimedOut, + NotOpenForReading, + SocketNotConnected, }; -pub fn error_string_repr(mal_error: MalError) []const u8 { - return @errorName(mal_error); +var error_data: ?*MalType = null; + +pub fn throw(mal: *MalType) MalError { + assert(error_data == null); + error_data = mal; + mal.incref(); + return MalError.ThrownError; +} + +pub fn get_error_data() ?*MalType { + defer error_data = null; + return error_data; } diff --git a/impls/zig/hmap.zig b/impls/zig/hmap.zig index 0765526c98..7611f6fa4b 100644 --- a/impls/zig/hmap.zig +++ b/impls/zig/hmap.zig @@ -1,58 +1,92 @@ -const warn = @import("std").debug.warn; -const Allocator = @import("std").mem.Allocator; +const warn = @import("std").log.warn; +const allocator = @import("std").heap.c_allocator; const hash_map = @import("std").hash_map; const MalType = @import("types.zig").MalType; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; +const string_eql = @import("std").hash_map.eqlString; const MalError = @import("error.zig").MalError; +const debug_alloc = @import("types.zig").debug_alloc; + +const Context = struct { + + pub fn hash(_: @This(), key: *MalType) u64 { + return switch(key.*) { + .Symbol, .String, .Keyword => |s| hash_map.hashString(s.data), + else => unreachable, + }; + } + + pub fn eql(_: @This(), ma: *MalType, mb: *MalType) bool { + return switch(ma.*) { + .Keyword => |a| switch(mb.*) { + .Keyword => |b| string_eql(a.data, b.data), + else => false, + }, + .String => |a| switch(mb.*) { + .String => |b| string_eql(a.data, b.data), + else => false, + }, + .Symbol => |a| switch(mb.*) { + .Symbol => |b| string_eql(a.data, b.data), + else => false, + }, + else => unreachable, + }; + } +}; -fn bad_hash(str: []const u8) u32 { - var hash: u64 = 1; - const m: u32 = (1<<31); - const a: u32 = 1103515245; - const c: u32 = 12345; - - var i: usize = 0; - const n = str.len; - while(i < n) { - hash = (hash + str[i]) % m; - hash = (a * hash) % m; - hash = (c + hash) % m; - i += 1; - } - const res: u32 = @intCast(u32, hash % m); - return res; -} -pub const MalHashMap = hash_map.HashMap([]const u8, *MalType, bad_hash, string_eql); +pub const MalHashMap = hash_map.HashMapUnmanaged(*MalType, *MalType, + Context, 80); -pub fn deepcopy(allocator: *Allocator, hashmap: MalHashMap) MalError!MalHashMap { - var hmap_cpy = MalHashMap.init(allocator); +pub fn map_destroy(hashmap: *MalHashMap) void { + if (debug_alloc) { + warn("destroy_map_elements", .{}); + } var iterator = hashmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = string_copy(allocator, pair.key) catch return MalError.SystemError; - const val = try pair.value.copy(allocator); - _ = hmap_cpy.put(key, val) catch return MalError.SystemError; - optional_pair = iterator.next(); - } - return hmap_cpy; + while(iterator.next()) |pair| { + pair.key_ptr.*.decref(); + pair.value_ptr.*.decref(); + } + hashmap.deinit(allocator); } -pub fn destroy(allocator: *Allocator, hashmap: MalHashMap, shallow: bool) void { - var iterator = hashmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - //warn(" deleting {} {}\n", pair.key, pair.value); - if(!shallow) { - allocator.free(pair.key); - pair.value.delete(allocator); - } - optional_pair = iterator.next(); - } - hashmap.deinit(); +// If the key was present in the map, the implementation reuses it, +// instead of the new one. So we need to increment the reference +// counting for the key here. +// The ref count of the value is not incremented here. +pub fn map_insert_incref_key(hashmap: *MalHashMap, key: *MalType, value: *MalType) !void { + switch(key.*) { + .String, .Keyword, .Symbol => { + if (try hashmap.fetchPut(allocator, key, value)) |old| { + // No change in the key reference count. + old.value.decref(); + } else { + key.incref(); + } + }, + else => return MalError.TypeError, + } +} + +pub fn map_insert_from_map(hashmap: *MalHashMap, from: MalHashMap) !void { + var iterator = from.iterator(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + try map_insert_incref_key(hashmap, key, value); + value.incref(); + } } +pub fn map_insert_from_kvs(hashmap: *MalHashMap, kvs: []const *MalType) !void { + if (kvs.len % 2 == 1) { + return MalError.TypeError; + } + for (0..kvs.len/2) |i| { + const key = kvs[2*i]; + const value = kvs[2*i+1]; + try map_insert_incref_key(hashmap, key, value); + value.incref(); + } +} diff --git a/impls/zig/linked_list.zig b/impls/zig/linked_list.zig index e50c0a72a3..ff21bb32a8 100644 --- a/impls/zig/linked_list.zig +++ b/impls/zig/linked_list.zig @@ -1,58 +1,13 @@ -const Allocator = @import("std").mem.Allocator; - -const TailQueue = @import("std").TailQueue; -const ArrayList = @import("std").ArrayList; +const allocator = @import("std").heap.c_allocator; +const ArrayListUnmanaged = @import("std").ArrayListUnmanaged; const MalType = @import("types.zig").MalType; -const MalError = @import("error.zig").MalError; - -pub const MalLinkedList = ArrayList(*MalType); - -pub fn deepcopy(allocator: *Allocator, ll: MalLinkedList) MalError!MalLinkedList { - var new_ll = MalLinkedList.init(allocator); - const ll_slice = ll.toSlice(); - var i: usize = 0; - while(i < ll_slice.len) { - const new_mal = try ll_slice[i].copy(allocator); - new_ll.append(new_mal) catch return MalError.SystemError; - i += 1; - } - - return new_ll; -} - -pub fn destroy(allocator: *Allocator, ll: *MalLinkedList, shallow: bool) void { - if(!shallow) { - const ll_slice = ll.toSlice(); - var i: usize = 0; - while(i < ll_slice.len) { - ll_slice[i].delete(allocator); - i += 1; - } - } - ll.deinit(); -} - -// TODO: deprecate -pub fn append_mal(allocator: *Allocator, ll: *MalLinkedList, mal: *MalType) MalError!void { - ll.append(mal) catch return MalError.SystemError; -} -// TODO: deprecate -pub fn prepend_mal(allocator: *Allocator, ll: *MalLinkedList, mal: *MalType) MalError!void { - ll.insert(0, mal) catch return MalError.SystemError; -} +// The name is poorly choosen but historical. -pub fn pop_first(allocator: *Allocator, ll: *MalLinkedList) MalError!*MalType { - if(ll.count() == 0) { - return MalError.OutOfBounds; - } - return ll.orderedRemove(0); -} +pub const MalLinkedList = ArrayListUnmanaged(*MalType); -pub fn first(ll: *const MalLinkedList) ?*MalType { - if(ll.count() == 0) { - return null; - } - return ll.at(0); +pub fn list_destroy(ll: *MalLinkedList) void { + for(ll.items) |x| + x.decref(); + ll.deinit(allocator); } - diff --git a/impls/zig/logging_alloc.zig b/impls/zig/logging_alloc.zig deleted file mode 100644 index 7917421087..0000000000 --- a/impls/zig/logging_alloc.zig +++ /dev/null @@ -1,35 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const warn = @import("std").debug.warn; - -pub const LoggingAllocator = struct { - allocator: Allocator, - parent_allocator: *Allocator, - - const Self = @This(); - - pub fn init(parent_allocator: *Allocator) Self { - return Self { - .allocator = Allocator{ - .reallocFn = realloc, - .shrinkFn = shrink, - }, - .parent_allocator = parent_allocator, - }; - } - - fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { - warn("mem new {} {} {}\n", old_mem.len, new_size, @intCast(i64,new_size) - @intCast(i64, old_mem.len)); - const self = @fieldParentPtr(Self, "allocator", allocator); - const result = self.parent_allocator.reallocFn(self.parent_allocator, old_mem, old_align, new_size, new_align); - return result; - } - - fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - warn("mem del {} {} {}\n", old_mem.len, new_size, @intCast(i64,new_size) - @intCast(i64,old_mem.len)); - warn("deleted: {}\n", old_mem); - const self = @fieldParentPtr(Self, "allocator", allocator); - const result = self.parent_allocator.shrinkFn(self.parent_allocator, old_mem, old_align, new_size, new_align); - return result; - } -}; diff --git a/impls/zig/printer.zig b/impls/zig/printer.zig index 633aac8e4e..ebdb14dfe1 100644 --- a/impls/zig/printer.zig +++ b/impls/zig/printer.zig @@ -1,13 +1,9 @@ const io = @import("std").io; const fmt = @import("std").fmt; -const warn = @import("std").debug.warn; -const mem = @import("std").mem; -const math = @import("std").math; const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalTypeValue = @import("types.zig").MalTypeValue; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const MalError = @import("error.zig").MalError; @@ -22,66 +18,60 @@ const backslash = \\\ ; -fn appendToBuffer(resize_buffer: *ResizeBuffer, buffer: []const u8) MalError!void { +fn appendToBuffer(resize_buffer: *ResizeBuffer, buffer: []const u8) !void { const n: usize = buffer.len; if(n + resize_buffer.pos > resize_buffer.len or resize_buffer.buffer == null) { - const new_len = math.max(math.max(2*resize_buffer.len, 10), n+resize_buffer.pos); - var bigger_buffer: [] u8 = Allocator.alloc(u8, new_len) catch return MalError.SystemError; + var new_len: usize = 10; + const new_len2 = 2*resize_buffer.len; + if(new_len < new_len2) + new_len = new_len2; + const new_len3 = n+resize_buffer.pos; + if(new_len < new_len3) + new_len = new_len3; + var bigger_buffer: [] u8 = try Allocator.alloc(u8, new_len); if(resize_buffer.buffer) |old_buffer| { - var i: usize = 0; - while(i < resize_buffer.len) { + for(0..resize_buffer.len) |i| bigger_buffer[i] = old_buffer[i]; - i += 1; - } Allocator.free(old_buffer); } resize_buffer.buffer = bigger_buffer; resize_buffer.len = new_len; } - if(resize_buffer.buffer) |n_buffer| { - var i: usize = 0; - while(i < n) { + if(resize_buffer.buffer) |n_buffer| + for(0..n) |i| { n_buffer[resize_buffer.pos] = buffer[i]; - i += 1; resize_buffer.pos += 1; - } - } + }; +} + +// TODO: Writer and ResizeBuffer should probably me merged. +fn writeFn(context: *ResizeBuffer, bytes: []const u8) !usize { + try appendToBuffer(context, bytes); + return bytes.len; } +pub const Writer = io.Writer(*ResizeBuffer, MalError, writeFn); +pub fn writer(rb: *ResizeBuffer) Writer { + return .{ .context = rb }; +} + +pub fn print_str(mal: MalType) ![]const u8 { + // const stdout_file = io.getStdOut(); -fn print_mal_to_buffer(mal: *const MalType, readable: bool) MalError!ResizeBuffer { var rb = ResizeBuffer{ .buffer = null, .pos = 0, .len = 0, }; - - try print_to_buffer(mal, &rb, readable); - return rb; -} - -pub fn print_str(optional_mal: ?*const MalType) MalError![] const u8 { - const stdout_file = io.getStdOut() catch return MalError.SystemError; - if(optional_mal == null) { - var return_string: [] u8 = Allocator.alloc(u8, 3) catch return MalError.SystemError; - return_string[0] = 'E'; //TODO: memcpy - return_string[1] = 'O'; - return_string[2] = 'F'; - return return_string; // TODO: is this right? - //stdout_file.write("EOF\n") catch return MalError.SystemError; - } - const mal = optional_mal orelse return ""; - var rb = try print_mal_to_buffer(mal, true); - + try print_to_buffer(mal, &rb, true); if(rb.buffer) |buffer| { - //stdout_file.write(buffer[0..rb.pos]) catch return MalError.SystemError; - //stdout_file.write("\n") catch return MalError.SystemError; - var return_string: [] u8 = Allocator.alloc(u8, rb.pos) catch return MalError.SystemError; - var i: usize = 0; // TODO: replace with memcpy (and elsewhere) - while(i < rb.pos) { + //stdout_file.write(buffer[0..rb.pos]); + //stdout_file.write("\n"); + var return_string: [] u8 = try Allocator.alloc(u8, rb.pos); + // TODO: replace with memcpy (and elsewhere) + for (0..rb.pos) |i| { return_string[i] = buffer[i]; - i += 1; } Allocator.free(buffer); return return_string; @@ -89,7 +79,7 @@ pub fn print_str(optional_mal: ?*const MalType) MalError![] const u8 { return MalError.SystemError; } -pub fn print_mal_to_string(args: MalLinkedList, readable: bool, sep: bool) MalError![] u8 { +pub fn print_mal_to_string(args: []const *MalType, readable: bool, sep: bool) ![] u8 { // TODO: handle empty string var rb = ResizeBuffer{ .buffer = null, @@ -97,24 +87,19 @@ pub fn print_mal_to_string(args: MalLinkedList, readable: bool, sep: bool) MalEr .len = 0, }; - var iterator = args.iterator(); - var first: bool = true; - while(iterator.next()) |node| { - if(!first and sep) { + for (args, 0..) |node, idx| { + if(0 < idx and sep) { try appendToBuffer(&rb, " "); } - try print_to_buffer(node, &rb, readable); - first = false; + try print_to_buffer(node.*, &rb, readable); } // TODO: is this the right exception? if(rb.buffer) |buffer| { const len = rb.pos; - var return_string: [] u8 = Allocator.alloc(u8, len) catch return MalError.SystemError; - var i: usize = 0; - while(i < len) { + var return_string: [] u8 = try Allocator.alloc(u8, len); + for (0..len) |i| { return_string[i] = buffer[i]; - i += 1; } Allocator.free(buffer); return return_string; @@ -123,38 +108,35 @@ pub fn print_mal_to_string(args: MalLinkedList, readable: bool, sep: bool) MalEr return s; } -fn print_to_buffer(mal: *const MalType, rb: *ResizeBuffer, readable: bool) MalError!void { - switch(mal.data) { +fn print_to_buffer(mal: MalType, rb: *ResizeBuffer, readable: bool) !void { + switch(mal) { .String => |string| { if(readable) { try appendToBuffer(rb, "\""); - } - // TODO: optimize this - var i: usize = 0; - var n: usize = string.len; - while(i < n){ - const this_char = string[i]; - if(readable and (this_char == '"' or this_char==92)) { + // TODO: optimize this + for(string.data, 0..) |this_char, i| { + if(this_char == '"' or this_char==92) { try appendToBuffer(rb, backslash); - } - if(readable and (this_char == '\n')) { + } + if(this_char == '\n') { try appendToBuffer(rb, "\\n"); + } + else { + try appendToBuffer(rb, string.data[i..i+1]); + } } - else { - try appendToBuffer(rb, string[i..i+1]); - } - i += 1; - } - if(readable) { try appendToBuffer(rb, "\""); } + else { + try appendToBuffer(rb, string.data); + } }, .Keyword => |kwd| { try appendToBuffer(rb, ":"); - try appendToBuffer(rb, kwd[1..kwd.len]); + try appendToBuffer(rb, kwd.data); }, .Int => |val| { - try fmt.format(rb, MalError, appendToBuffer, "{0}", val); + try fmt.format(writer(rb), "{0}", .{val.data}); }, .Nil => { try appendToBuffer(rb, "nil"); @@ -167,62 +149,57 @@ fn print_to_buffer(mal: *const MalType, rb: *ResizeBuffer, readable: bool) MalEr }, .List => |l| { try appendToBuffer(rb, "("); - var iterator = l.iterator(); - var first_iteration = true; - while(iterator.next()) |next_mal| { - if(!first_iteration) { + for (l.data.items, 0..) |next_mal, i| { + if(0 |v| { try appendToBuffer(rb, "["); - var iterator = v.iterator(); - var first_iteration = true; - while(iterator.next()) |next_mal| { - if(!first_iteration) { + for (v.data.items, 0..) |next_mal, i| { + if(0 |atom_value| { try appendToBuffer(rb, "(atom "); - try print_to_buffer(atom_value.*, rb, readable); + try print_to_buffer(atom_value.data.*, rb, readable); try appendToBuffer(rb, ")"); }, - .Func, .Fn0, .Fn1, .Fn2, .Fn3, .Fn4, .FVar => { + .Func, .FnCore => { try appendToBuffer(rb, "#"); }, - .Generic => |value| { - try appendToBuffer(rb, value); + .Symbol => |value| { + try appendToBuffer(rb, value.data); }, .HashMap => |h| { try appendToBuffer(rb, "{"); - var iterator = h.iterator(); + var iterator = h.data.iterator(); var first = true; - while(true) { - const optional_pair = iterator.next(); - const pair = optional_pair orelse break; + while(iterator.next()) |pair| { if(!first) { try appendToBuffer(rb, " "); } - if(pair.key.len > 1 and pair.key[0] == 255) { + switch (pair.key_ptr.*.*) { + .Keyword => |k| { try appendToBuffer(rb, ":"); - try appendToBuffer(rb, pair.key[1..pair.key.len]); - } - else { + try appendToBuffer(rb, k.data); + }, + .String => |s| { try appendToBuffer(rb, "\""); - try appendToBuffer(rb, pair.key); + try appendToBuffer(rb, s.data); try appendToBuffer(rb, "\""); + }, + else => unreachable, } try appendToBuffer(rb, " "); - try print_to_buffer(pair.value, rb, readable); + try print_to_buffer(pair.value_ptr.*.*, rb, readable); first = false; } try appendToBuffer(rb, "}"); diff --git a/impls/zig/reader.zig b/impls/zig/reader.zig index c9c7d779dd..2d778cdb3d 100644 --- a/impls/zig/reader.zig +++ b/impls/zig/reader.zig @@ -1,23 +1,24 @@ const fmt = @import("std").fmt; -const warn = @import("std").debug.warn; -pub const pcre = @cImport({ +const pcre = @cImport({ @cInclude("pcre.h"); }); const MalType = @import("types.zig").MalType; -const MalData = @import("types.zig").MalData; -const MalTypeValue = @import("types.zig").MalTypeValue; const MalError = @import("error.zig").MalError; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const printer = @import("printer.zig"); const Allocator = @import("std").heap.c_allocator; -const string_eql = @import("utils.zig").string_eql; +const string_eql = @import("std").hash_map.eqlString; const linked_list = @import("linked_list.zig"); +const assert = @import("std").debug.assert; +const throw = @import("error.zig").throw; +const MalHashMap = @import("hmap.zig").MalHashMap; +const map_insert_incref_key = @import("hmap.zig").map_insert_incref_key; const match: [*]const u8 = - c\\[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*) + \\[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*) ; var error_msg: [*c]const u8 = undefined; var erroroffset: c_int = 0; @@ -36,13 +37,11 @@ const Reader = struct { }; } - pub fn next(self: *Reader) []const u8 { - const this_token = self.peek(); + pub fn next(self: *Reader) void { self.position += 1; - return this_token; } - pub fn peek(self: *Reader) []const u8 { + pub fn peek(self: *Reader) ?[]const u8 { while(!self.eol()) { const start = self.tokens[2*self.position]; const end = self.tokens[2*self.position+1]; @@ -52,7 +51,7 @@ const Reader = struct { } return self.string[start..end]; } - return ""; + return null; } pub fn eol(self: *Reader) bool { @@ -75,14 +74,9 @@ const alias_pairs = [_] AliasPair { AliasPair {.name="^", .value="with-meta", .count=2}, }; -pub fn read_form(reader: *Reader) MalError!?*MalType { - if(reader.eol()) { - return null; - } - const token = reader.peek(); - if(token.len == 0) { - return MalType.new_nil(Allocator); - } +pub fn read_form(reader: *Reader) MalError!*MalType { + const token = reader.peek() orelse return MalError.ArgError; + reader.next(); if(token[0] == '(') { return try read_list(reader); } @@ -90,109 +84,92 @@ pub fn read_form(reader: *Reader) MalError!?*MalType { return try read_vector(reader); } else if(token[0] == ':') { - const keyword = reader.next(); - return MalType.new_keyword(Allocator, keyword[1..keyword.len]); + return MalType.new_keyword(token[1..], true); } else if(token[0] == '{') { return try read_hashmap(reader); } - for(alias_pairs) |pair| { const name = pair.name; const value = pair.value; const count = pair.count; - if(!string_eql(token, name)) { - continue; - } - var new_ll = MalLinkedList.init(Allocator); - const new_generic = try MalType.new_generic(Allocator, value); - const tmp = reader.next(); - var num_read: u8 = 0; - while(num_read < count) { - const next_read = (try read_form(reader)) orelse return MalError.ArgError; - try linked_list.prepend_mal(Allocator, &new_ll, next_read); - num_read += 1; + if(string_eql(token, name)) { + assert (count == 1 or count == 2); + const result = try MalType.new_list(); + errdefer result.decref(); + const first = try MalType.new_symbol(value, true); + try result.List.data.append(Allocator, first); + for(0..count) |_| { + const second = try read_form(reader); + errdefer second.decref(); + try result.List.data.insert(Allocator, 1, second); + } + return result; } - try linked_list.prepend_mal(Allocator, &new_ll, new_generic); - const new_list = try MalType.new_nil(Allocator); - new_list.data = MalData {.List = new_ll}; - return new_list; } - - return try read_atom(reader); + if(token_is_int(token)) { + const value = try fmt.parseInt(i32, token, 10); + return try MalType.new_int(value); + } + else if(string_eql(token, "nil")) { + return &MalType.NIL; + } + else if(string_eql(token, "true")) { + return &MalType.TRUE; + } + else if(string_eql(token, "false")) { + return &MalType.FALSE; + } + else if(token[0] == '"') { + return try read_atom_string(token); + } + else { + return try MalType.new_symbol(token, true); + } } -pub fn read_list(reader: *Reader) MalError!*MalType { - const first_token = reader.next(); - var new_ll = MalLinkedList.init(Allocator); - const mal_list: *MalType = try MalType.new_nil(Allocator); - - while(!reader.eol()) { - var next_token = reader.peek(); - - if(next_token.len == 0) { - return MalError.ReaderUnmatchedParen; - } - if(next_token[0] == ')') { - const right_paren = reader.next(); - mal_list.data = MalData{.List = new_ll}; - return mal_list; - } - const mal = (try read_form(reader)) orelse return MalError.ArgError; - try linked_list.append_mal(Allocator, &new_ll, mal); +fn read_list(reader: *Reader) !*MalType { + const result = try MalType.new_list(); + errdefer result.decref(); + while(try read_list_element(reader, ')', "unbalanced '('")) |mal| { + try result.List.data.append(Allocator, mal); } - return MalError.ReaderUnmatchedParen; + return result; } -pub fn read_vector(reader: *Reader) MalError!*MalType { - const first_token = reader.next(); - var new_ll = MalLinkedList.init(Allocator); - const mal_list: *MalType = try MalType.new_nil(Allocator); - - while(!reader.eol()) { - var next_token = reader.peek(); - - if(next_token.len == 0) { - return MalError.ReaderUnmatchedParen; - } - if(next_token[0] == ']') { - const right_paren = reader.next(); - mal_list.data = MalData{.Vector = new_ll}; - return mal_list; - } - const mal = (try read_form(reader)) orelse return MalError.ArgError; - try linked_list.append_mal(Allocator, &new_ll, mal); +fn read_vector(reader: *Reader) !*MalType { + const result = try MalType.new_vector(); + errdefer result.decref(); + while(try read_list_element(reader, ']', "unbalanced '['")) |mal| { + try result.Vector.data.append(Allocator, mal); } - return MalError.ReaderUnmatchedParen; + return result; } +fn read_hashmap(reader: *Reader) !*MalType { + const result = try MalType.new_hashmap(); + errdefer result.decref(); + while(try read_list_element(reader, '}', "unbalanced '{'")) |key| { + const value = try read_form(reader); + errdefer value.decref(); + try map_insert_incref_key(&result.HashMap.data, key, value); + key.decref(); + } + return result; +} -pub fn read_hashmap(reader: *Reader) MalError!*MalType { - const first_token = reader.next(); - const new_hashmap = try MalType.new_hashmap(Allocator); - while(!reader.eol()) { - var next_token = reader.peek(); - - if(next_token.len == 0) { - return MalError.ReaderUnmatchedParen; - } - if(next_token[0] == '}') { - const right_paren = reader.next(); - return new_hashmap; +fn read_list_element(reader: *Reader, + comptime closer: u8, + comptime unbalanced: []const u8, + ) !?*MalType { + if(reader.peek()) |next_token| { + if(next_token[0] == closer) { + reader.next(); + return null; } - const mal = (try read_form(reader)) orelse return MalError.ArgError; - const key = switch(mal.data) { - .String => |s| s, - .Keyword => |kwd| kwd, - else => return MalError.TypeError, - }; - if(next_token.len == 0 or next_token[0] == '}') { - return MalError.ReaderBadHashmap; - } - const val = (try read_form(reader)) orelse return MalError.ArgError; - try new_hashmap.hashmap_insert(key, val); + return try read_form(reader); } - return MalError.ReaderUnmatchedParen; + return throw(try MalType.new_string(unbalanced, true)); } fn char_is_int(c: u8) bool { @@ -207,56 +184,14 @@ fn token_is_int(token: []const u8) bool { return false; } -pub fn read_atom(reader: *Reader) MalError!*MalType { - const token = reader.next(); - - if(token_is_int(token)) { - var mal_atom = try MalType.new_nil(Allocator); - try read_atom_int(mal_atom, token); - return mal_atom; - } - else if(string_eql(token, "nil")) { - return MalType.new_nil(Allocator); - } - else if(string_eql(token, "true")) { - return MalType.new_bool(Allocator, true); - } - else if(string_eql(token, "false")) { - return MalType.new_bool(Allocator, false); - } - else if(token[0] == '"') { - var mal_atom = try MalType.new_nil(Allocator); - try read_atom_string(mal_atom, token); - return mal_atom; - } - else { - var mal_atom = try MalType.new_generic(Allocator, token); - return mal_atom; - } -} - -fn read_atom_int(mal_atom: *MalType, token: []const u8) MalError!void { - // TODO: extract int type from union - mal_atom.data = MalData {.Int = fmt.parseInt(i32, token, 10) - catch |err| return MalError.SystemError }; -} - -fn read_atom_string(mal_atom: *MalType, token: []const u8) MalError!void { +fn read_atom_string(token: []const u8) MalError!*MalType { const n = token.len; if(token[0] != '"' or token[n-1] != '"' or n <= 1) { - return MalError.ReaderUnmatchedString; + return throw(try MalType.new_string("unbalanced '\"'", true)); } - if(n <= 2) { - // We get here when the token is an empty string. - // We encode this as MalTypeValue.String, with null .string_value - var string = Allocator.alloc(u8, 0) catch return MalError.SystemError; - mal_atom.data = MalData {.String = string}; - return; - } - - var tmp_buffer = Allocator.alloc(u8, n-2) catch return MalError.SystemError; - defer Allocator.free(tmp_buffer); + var tmp_buffer = try Allocator.alloc(u8, n-2); + errdefer Allocator.free(tmp_buffer); var i: usize = 1; var j: usize = 0; const escape_char: u8 = '\\'; //TODO: remove this comment required by bad emacs config ' @@ -268,7 +203,7 @@ fn read_atom_string(mal_atom: *MalType, token: []const u8) MalError!void { } else { if(i==n-2) { - return MalError.ReaderUnmatchedString; + return throw(try MalType.new_string("unbalanced '\"'", true)); } if(token[i+1] == 'n') { tmp_buffer[j] = '\n'; @@ -280,14 +215,7 @@ fn read_atom_string(mal_atom: *MalType, token: []const u8) MalError!void { } } - var string = Allocator.alloc(u8, j) catch return MalError.SystemError; - i = 0; - while(i < j) { - string[i] = tmp_buffer[i]; - i += 1; - } - - mal_atom.data = MalData {.String = string}; + return try MalType.new_string(tmp_buffer[0..j], false); } pub fn read_str(string: [] const u8) MalError!Reader { @@ -299,14 +227,12 @@ pub fn read_str(string: [] const u8) MalError!Reader { } // Allocates an array of matches. Caller is becomes owner of memory. -pub fn tokenize(regex: ?*pcre.pcre, string: [] const u8) MalError![] usize { +fn tokenize(regex: ?*pcre.pcre, string: [] const u8) MalError![] usize { // TODO: pass in allocator const buffer_size: usize = 3 * string.len + 10; - var indices: [] c_int = Allocator.alloc(c_int, buffer_size) - catch return MalError.SystemError; + var indices: [] c_int = try Allocator.alloc(c_int, buffer_size); defer Allocator.free(indices); - var match_buffer: [] usize = Allocator.alloc(usize, buffer_size) - catch return MalError.SystemError; + var match_buffer: [] usize = try Allocator.alloc(usize, buffer_size); defer Allocator.free(match_buffer); var current_match: usize = 0; var start_pos: c_int = 0; @@ -314,27 +240,24 @@ pub fn tokenize(regex: ?*pcre.pcre, string: [] const u8) MalError![] usize { var rc: c_int = 0; var start_match: usize = 0; var end_match: usize = 0; - const subject_size: c_int = @intCast(c_int, string.len); + const subject_size: c_int = @intCast(string.len); while(start_pos < subject_size) { rc = pcre.pcre_exec(regex, 0, &string[0], subject_size, start_pos, 0, - &indices[0], @intCast(c_int,buffer_size)); + &indices[0], @intCast(buffer_size)); if(rc <= 0) break; start_pos = indices[1]; - start_match = @intCast(usize, indices[2]); - end_match = @intCast(usize, indices[3]); + start_match = @intCast(indices[2]); + end_match = @intCast(indices[3]); match_buffer[current_match] = start_match; match_buffer[current_match+1] = end_match; current_match += 2; } - var matches: [] usize = Allocator.alloc(usize, current_match) - catch return MalError.SystemError; - var i: usize = 0; - while(i < current_match) { + var matches: [] usize = try Allocator.alloc(usize, current_match); + for(0..current_match) |i| { matches[i] = match_buffer[i]; - i += 1; } return matches; diff --git a/impls/zig/readline.zig b/impls/zig/readline.zig index ed15d83f83..e31dfc62d4 100644 --- a/impls/zig/readline.zig +++ b/impls/zig/readline.zig @@ -1,49 +1,39 @@ -const Allocator = @import("std").mem.Allocator; +const allocator = @import("std").heap.c_allocator; const readline = @cImport( @cInclude("readline/readline.h")); const rl_hist = @cImport( @cInclude("readline/history.h")); const free = @import("std").c.free; -const addNullByte = @import("std").cstr.addNullByte; -const warn = @import("std").debug.warn; +fn addNullByte(prompt: []const u8) ![]u8 { + const result = try allocator.alloc(u8, prompt.len + 1); + for (0.., prompt) |i, source| + result[i] = source; + result[prompt.len] = 0; + return result; +} -pub fn slice_from_cstr(allocator: *Allocator, str: [*]const u8) ![]u8{ +fn slice_from_cstr(str: [*]const u8) ![]const u8 { var length: usize = 0; - while(true) { - if(str[length] == 0) - break; - length += 1; + while(str[length] != 0) { + length += 1; } // TODO: check for 0-length const slice = try allocator.alloc(u8, length); - var i: usize = 0; - while(i < length) { - slice[i] = str[i]; - i += 1; + for (str, 0..length) |source, i| { + slice[i] = source; } return slice; } -pub fn getline(allocator: *Allocator) !?[] u8 { - var input: ?[*] u8 = readline.readline(c"user> "); - if(input) |actual| { - const aslice = try slice_from_cstr(allocator, actual); - rl_hist.add_history(actual); - free(actual); - return aslice; - } - return null; -} - -pub fn getline_prompt(allocator: *Allocator, prompt: []const u8) !?[] u8 { - const null_terminated_prompt = try addNullByte(allocator, prompt); - var input: ?[*] u8 = readline.readline(&null_terminated_prompt[0]); - allocator.free(null_terminated_prompt); +pub fn getline(prompt: []const u8) !?[]const u8 { + const null_terminated_prompt = try addNullByte(prompt); + defer allocator.free(null_terminated_prompt); + const input = readline.readline(&null_terminated_prompt[0]); if(input) |actual| { - const aslice = try slice_from_cstr(allocator, actual); + defer free(actual); + const aslice = try slice_from_cstr(actual); rl_hist.add_history(actual); - free(actual); return aslice; } return null; diff --git a/impls/zig/run b/impls/zig/run index 8ba68a5484..35613af46a 100755 --- a/impls/zig/run +++ b/impls/zig/run @@ -1,2 +1,2 @@ -#!/bin/bash -exec $(dirname $0)/${STEP:-stepA_mal} "${@}" +#!/bin/sh +exec $(dirname $0)/zig-out/bin/${STEP:-stepA_mal} "${@}" diff --git a/impls/zig/step0_repl.zig b/impls/zig/step0_repl.zig index 265db0ff67..302cc01eba 100644 --- a/impls/zig/step0_repl.zig +++ b/impls/zig/step0_repl.zig @@ -1,36 +1,30 @@ -const std = @import("std"); -const warn = @import("std").debug.warn; - const getline = @import("readline.zig").getline; const Allocator = @import("std").heap.c_allocator; +const stdout_file = @import("std").io.getStdOut(); -fn READ(a: [] u8) [] u8 { +fn READ(a: []const u8) []const u8 { return a; } -fn EVAL(a: [] u8) [] u8 { +fn EVAL(a: []const u8) []const u8 { return a; } -fn PRINT(a: [] u8) [] u8 { - return a; +fn PRINT(a: []const u8) !void { + try stdout_file.writeAll(a); + try stdout_file.writeAll("\n"); } -fn rep(input: [] u8) [] u8 { - var read_input = READ(input); - var eval_input = EVAL(read_input); - var print_input = PRINT(eval_input); - return print_input; +fn rep(input: []const u8) !void { + const read_input = READ(input); + const eval_input = EVAL(read_input); + try PRINT(eval_input); } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - while(true) { - var line = (try getline(Allocator)) orelse break; - var output = rep(line); - try stdout_file.write(output); - Allocator.free(output); - try stdout_file.write("\n"); + while(try getline("user> ")) |line| { + defer Allocator.free(line); + try rep(line); } } diff --git a/impls/zig/step1_read_print.zig b/impls/zig/step1_read_print.zig index 0b0793d073..be4bc92ce6 100644 --- a/impls/zig/step1_read_print.zig +++ b/impls/zig/step1_read_print.zig @@ -1,46 +1,50 @@ -const std = @import("std"); -const warn = @import("std").debug.warn; - const reader = @import("reader.zig"); -const pcre = reader.pcre; const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; +const get_error_data = @import("error.zig").get_error_data; +const stdout_file = @import("std").io.getStdOut(); -fn READ(a: [] u8) !?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } -fn EVAL(a: ?*MalType) ?*MalType { +fn EVAL(a: *MalType) *MalType { + a.incref(); return a; } -fn PRINT(optional_mal: ?*MalType) ![] u8 { - return printer.print_str(optional_mal); +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep(input: [] u8) ![] u8 { - var read_input = READ(input) catch null; - var eval_input = EVAL(read_input); - var print_input = PRINT(eval_input); - if(eval_input) |mal| { - mal.delete(Allocator); - } - return print_input; +fn rep(input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = EVAL(read_input); + defer eval_input.decref(); + try PRINT(eval_input.*); } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - while(true) { - var line = (try getline(Allocator)) orelse break; - var output = try rep(line); - try stdout_file.write(output); - Allocator.free(output); - try stdout_file.write("\n"); + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); + } + }; } } diff --git a/impls/zig/step2_eval.zig b/impls/zig/step2_eval.zig index e71d3d37ba..d2084525d5 100644 --- a/impls/zig/step2_eval.zig +++ b/impls/zig/step2_eval.zig @@ -1,124 +1,116 @@ const std = @import("std"); -const warn = @import("std").debug.warn; const reader = @import("reader.zig"); -const pcre = reader.pcre; const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; -const string_copy = @import("utils.zig").string_copy; -const string_concat = @import("utils.zig").string_concat; -const apply_function = @import("types.zig").apply_function; -const linked_list = @import("linked_list.zig"); const hash_map = @import("hmap.zig"); const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalData = @import("types.zig").MalData; const MalError = @import("error.zig").MalError; const MalLinkedList = @import("linked_list.zig").MalLinkedList; -const MalHashMap = hash_map.MalHashMap; +const get_error_data = @import("error.zig").get_error_data; +const throw = @import("error.zig").throw; +const stdout_file = std.io.getStdOut(); -var repl_environment: *MalHashMap = undefined; +var repl_environment = hash_map.MalHashMap { }; -fn READ(a: [] u8) MalError!?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } -fn EVAL(mal: *MalType) MalError!*MalType { - switch(mal.data) { +fn EVAL(mal: *MalType, env: hash_map.MalHashMap) MalError!*MalType { + + // try stdout_file.writeAll("EVAL: "); + // try PRINT(mal.*); + + switch(mal.*) { .List => |ll| { - if(ll.len == 0) { + const items = ll.data.items; + if(items.len == 0) { + mal.incref(); return mal; } - var new_list = try eval_ast(mal); - return apply_function(Allocator, (try new_list.sequence_linked_list()).*); + else { + const first_mal = items[0]; + const evaluated_first = try EVAL(first_mal, env); + defer evaluated_first.decref(); + // A slice would be sufficient, but a List is convenient + // for partial deallocation in case of error. + const args = try MalType.new_list(); + defer args.decref(); + for(items[1..]) |x| { + const new_item = try EVAL(x, env); + try args.List.data.append(Allocator, new_item); + } + return apply_function(evaluated_first.*, args.List.data.items); + } + }, + .Symbol => { + return EVAL_symbol(mal, env); + }, + .Vector => |ll| { + return EVAL_vector(ll.data.items, env); + }, + .HashMap => |hmap| { + return EVAL_map(hmap.data, env); }, else => { - return eval_ast(mal); + mal.incref(); + return mal; }, } } -fn PRINT(optional_mal: ?*MalType) MalError![] u8 { - return printer.print_str(optional_mal); +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep(input: [] u8) MalError!?[] u8 { - var read_input = (try READ(input)) orelse return null; - var eval_input = try EVAL(read_input); - var print_input = try PRINT(eval_input); - eval_input.delete(Allocator); - return print_input; +fn rep(input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = try EVAL(read_input, repl_environment); + defer eval_input.decref(); + try PRINT(eval_input.*); } -fn lookup(symbol: []const u8, do_warn: bool) MalError!*MalType { - var optional_mal = repl_environment.getValue(symbol); - if(optional_mal) |mal| { - return mal.copy(Allocator); +fn EVAL_symbol(mal: *MalType, env: hash_map.MalHashMap) !*MalType { + if(env.get(mal)) |value| { + value.incref(); + return value; } - if(do_warn) { - const s1 = string_concat(Allocator, "'", symbol) catch return MalError.SystemError; - const s2 = string_concat(Allocator, s1, "' not found") catch return MalError.SystemError; - defer Allocator.free(s1); - defer Allocator.free(s2); - warn("'{}' not found.\n", symbol); - } - return MalError.KeyError; + const err = try std.fmt.allocPrint(Allocator, "'{s}' not found", + .{mal.Symbol.data}); + return throw(try MalType.new_string(err, false)); } -fn eval_ast(mal: *MalType) MalError!*MalType { - switch(mal.data) { - .Generic => |symbol| { - defer mal.delete(Allocator); - return lookup(symbol, true); - }, - .List => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn EVAL_vector(ll: []*MalType, env: hash_map.MalHashMap) !*MalType { + const ret_mal = try MalType.new_vector(); + errdefer ret_mal.decref(); + for(ll) |x| { + const new_mal = try EVAL(x, env); + try ret_mal.Vector.data.append(Allocator, new_mal); } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_list(Allocator, new_ll); return ret_mal; - }, - .Vector => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal); - try linked_list.append_mal(Allocator, &new_ll, new_mal); - } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_vector(Allocator, new_ll); - return ret_mal; - }, - .HashMap => |hmap| { - var new_hashmap = try MalType.new_hashmap(Allocator); +} + +fn EVAL_map(hmap: hash_map.MalHashMap, env: hash_map.MalHashMap) !*MalType { + const new_hashmap = try MalType.new_hashmap(); + errdefer new_hashmap.decref(); var iterator = hmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = pair.key; - const value = pair.value; - const evaled_value = try EVAL(value); - try new_hashmap.hashmap_insert(key, evaled_value); - optional_pair = iterator.next(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + const evaled_value = try EVAL(value, env); + // key *is* new in this map. + try hash_map.map_insert_incref_key(&new_hashmap.HashMap.data, key, evaled_value); } - hash_map.destroy(Allocator, hmap, true); - mal.shallow_destroy(Allocator); return new_hashmap; - }, - else => { - return mal; - } - } } const safeAdd = @import("std").math.add; @@ -126,71 +118,88 @@ const safeSub = @import("std").math.sub; const safeMul = @import("std").math.mul; const safeDivFloor = @import("std").math.divFloor; -fn int_plus(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_plus(args: []*MalType) MalError!*MalType { + if (args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeAdd(i64, x, y) catch return MalError.Overflow; - return MalType.new_int(Allocator, res); + const res = try safeAdd(i64, x, y); + return MalType.new_int(res); } -fn int_minus(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_minus(args: []*MalType) MalError!*MalType { + if (args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeSub(i64, x, y) catch return MalError.Overflow; - return MalType.new_int(Allocator, res); + const res = try safeSub(i64, x, y); + return MalType.new_int(res); } -fn int_mult(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_mult(args: []*MalType) MalError!*MalType { + if (args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeMul(i64, x, y) catch return MalError.Overflow; - return MalType.new_int(Allocator, res); + const res = try safeMul(i64, x, y); + return MalType.new_int(res); } -fn int_div(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_div(args: []*MalType) MalError!*MalType { + if (args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeDivFloor(i64, x, y) catch |err| switch(err) { - error.DivisionByZero => return MalError.DivisionByZero, - else => return MalError.Overflow, - }; - return MalType.new_int(Allocator, res); + const res = try safeDivFloor(i64, x, y); + return MalType.new_int(res); +} + +fn make_environment() !void { + + const plus_sym = try MalType.new_symbol("+", true); + const plus_mal = try MalType.newFnCore(&int_plus); + try repl_environment.put(Allocator, plus_sym, plus_mal); + const minus_sym = try MalType.new_symbol("-", true); + const minus_mal = try MalType.newFnCore(&int_minus); + try repl_environment.put(Allocator, minus_sym, minus_mal); + const mult_sym = try MalType.new_symbol("*", true); + const mult_mal = try MalType.newFnCore(&int_mult); + try repl_environment.put(Allocator, mult_sym, mult_mal); + const div_sym = try MalType.new_symbol("/", true); + const div_mal = try MalType.newFnCore(&int_div); + try repl_environment.put(Allocator, div_sym, div_mal); } -fn make_environment() MalError!void { - repl_environment = Allocator.create(MalHashMap) catch return MalError.SystemError; - repl_environment.* = MalHashMap.init(Allocator); - - const plus_mal = try MalType.new_nil(Allocator); - plus_mal.data = MalData{.Fn2 = &int_plus}; - _ = repl_environment.put("+", plus_mal) catch return MalError.SystemError; - const minus_mal = try MalType.new_nil(Allocator); - minus_mal.data = MalData{.Fn2 = &int_minus}; - _ = repl_environment.put("-", minus_mal) catch return MalError.SystemError; - const mult_mal = try MalType.new_nil(Allocator); - mult_mal.data = MalData{.Fn2 = &int_mult}; - _ = repl_environment.put("*", mult_mal) catch return MalError.SystemError; - const div_mal = try MalType.new_nil(Allocator); - div_mal.data = MalData{.Fn2 = &int_div}; - _ = repl_environment.put("/", div_mal) catch return MalError.SystemError; +pub fn apply_function(f: MalType, args: []*MalType) MalError!*MalType { + + switch(f) { + .FnCore => |fncoredata| { + return fncoredata.data(args); + }, + else => { + return MalError.ApplyError; + }, + } } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); try make_environment(); - while(true) { - var line = (try getline(Allocator)) orelse break; - var optional_output = rep(line) catch |err| { - if(err == MalError.KeyError) { - continue; - } else { - return err; + + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); } }; - if(optional_output) |output| { - try stdout_file.write(output); - Allocator.free(output); - try stdout_file.write("\n"); - } } } diff --git a/impls/zig/step3_env.zig b/impls/zig/step3_env.zig index 53b00c2370..2ceb0f15aa 100644 --- a/impls/zig/step3_env.zig +++ b/impls/zig/step3_env.zig @@ -1,180 +1,169 @@ const std = @import("std"); -const warn = @import("std").debug.warn; const reader = @import("reader.zig"); -const pcre = reader.pcre; const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; -const string_concat = @import("utils.zig").string_concat; -const apply_function = @import("types.zig").apply_function; -const linked_list = @import("linked_list.zig"); +const string_eql = std.hash_map.eqlString; const hash_map = @import("hmap.zig"); const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalData = @import("types.zig").MalData; const MalError = @import("error.zig").MalError; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const Env = @import("env.zig").Env; +const get_error_data = @import("error.zig").get_error_data; +const throw = @import("error.zig").throw; +const stdout_file = std.io.getStdOut(); -var repl_environment: *Env = undefined; +var repl_environment = Env.new_root(); -fn READ(a: [] u8) MalError!?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } +// Do not allocate this one on each EVAL run. +// The string is static, but will never be deallocated. +var DEBUG_EVAL = MalType { .Symbol = .{ .data = "DEBUG-EVAL" } }; + fn EVAL(mal: *MalType, env: *Env) MalError!*MalType { - switch(mal.data) { + + if(try env.get(&DEBUG_EVAL)) |dbgeval| { + switch (dbgeval.*) { + .Nil, .False => {}, + else => { + try stdout_file.writeAll("EVAL: "); + try PRINT(mal.*); + } + } + } + + switch(mal.*) { .List => |ll| { - if(ll.len == 0) { + const items = ll.data.items; + if(items.len == 0) { + mal.incref(); return mal; } - var first_mal = linked_list.first(&ll) orelse return MalError.ArgError; - var symbol = switch(first_mal.data) { - .Generic => |symbol| symbol, + const first_mal = items[0]; + const symbol = switch(first_mal.*) { + .Symbol => |symbol| symbol.data, else => "", }; if(string_eql(symbol, "def!")) { - return EVAL_def(mal, env); + return EVAL_def(items[1..], env); } else if(string_eql(symbol, "let*")) { - return EVAL_let(mal, env); + return EVAL_let(items[1..], env); } else { - var new_list = try eval_ast(mal, env); - return apply_function(Allocator, (try new_list.sequence_linked_list()).*); + const evaluated_first = try EVAL(first_mal, env); + defer evaluated_first.decref(); + // A slice would be sufficient, but a List is convenient + // for partial deallocation in case of error. + const args = try MalType.new_list(); + defer args.decref(); + for(items[1..]) |x| { + const new_item = try EVAL(x, env); + try args.List.data.append(Allocator, new_item); + } + return apply_function(evaluated_first.*, args.List.data.items); } }, + .Symbol => { + return EVAL_symbol(mal, env); + }, + .Vector => |ll| { + return EVAL_vector(ll.data.items, env); + }, + .HashMap => |hmap| { + return EVAL_map(hmap.data, env); + }, else => { - return eval_ast(mal, env); + mal.incref(); + return mal; }, } } -fn EVAL_def(mal: *MalType, env: *Env) MalError!*MalType { - const first_arg = try mal.sequence_nth(1); - const second_arg = try mal.sequence_nth(2); - const second_arg_copy = try second_arg.copy(Allocator); - const symbol_name = try first_arg.as_symbol(); - const new_value = try EVAL(second_arg_copy, env); +fn EVAL_def(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env); try env.set(symbol_name, new_value); - mal.delete(Allocator); - return new_value.copy(Allocator); -} - -fn EVAL_let(mal: *MalType, env: *Env) MalError!*MalType { - const binding_arg = try mal.sequence_nth(1); - const eval_arg = try mal.sequence_nth(2); - const eval_arg_copy = try eval_arg.copy(Allocator); - const new_env = try Env.new(Allocator, env); - defer new_env.delete(); - var binding_ll = switch(binding_arg.data) { - .List => |l| l, - .Vector => |v| v, - else => return MalError.TypeError, - }; - var iterator = binding_ll.iterator(); - var optional_node = iterator.next(); - while(optional_node) |node| { - const key_mal = node; - const key = try key_mal.as_symbol(); - const val_mal = iterator.next() orelse return MalError.ArgError; + new_value.incref(); + return new_value; +} + +fn EVAL_let(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const binding_arg = args[0]; + const eval_arg = args[1]; + const binds = try binding_arg.as_slice(); + if(binds.len % 2 != 0) return MalError.ArgError; + const new_env = try Env.new(env); + env.incref(); + defer new_env.decref(); + for(0..binds.len / 2) |i| { + const key = binds[2*i]; + const val_mal = binds[2*i + 1]; const evaled_mal = try EVAL(val_mal, new_env); + errdefer evaled_mal.decref(); try new_env.set(key, evaled_mal); - optional_node = iterator.next(); - key_mal.delete(Allocator); + // Do not increment the refcount for the value. } - - linked_list.destroy(Allocator, &binding_ll, true); - binding_arg.data = MalData{.Nil=undefined}; - mal.delete(Allocator); - - // We use eval_arg_copy, since we just deleted eval_arg above - return EVAL(eval_arg_copy, new_env); + return EVAL(eval_arg, new_env); } -fn PRINT(optional_mal: ?*MalType) MalError![] u8 { - return printer.print_str(optional_mal); +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep(environment: *Env, input: [] u8) MalError!?[] u8 { - var read_input = (try READ(input)) orelse return null; - var eval_input = try EVAL(read_input, environment); - var print_input = try PRINT(eval_input); - eval_input.delete(Allocator); - return print_input; +fn rep(input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = try EVAL(read_input, &repl_environment); + defer eval_input.decref(); + try PRINT(eval_input.*); } -fn lookup(environment: *Env, symbol: []const u8, do_warn: bool) MalError!*MalType { - var mal = environment.get(symbol) catch |err| { - if(do_warn) { - const s1 = string_concat(Allocator, "'", symbol) catch return MalError.SystemError; - const s2 = string_concat(Allocator, s1, "' not found") catch return MalError.SystemError; - defer Allocator.free(s1); - defer Allocator.free(s2); - warn("'{}' not found.\n", symbol); - } - return MalError.KeyError; - }; - var new_mal = try mal.copy(Allocator); - return new_mal; +fn EVAL_symbol(mal: *MalType, env: *Env) !*MalType { + if(try env.get(mal)) |value| { + value.incref(); + return value; + } + const err = try std.fmt.allocPrint(Allocator, "'{s}' not found", + .{mal.Symbol.data}); + return throw(try MalType.new_string(err, false)); } -fn eval_ast(mal: *MalType, env: *Env) MalError!*MalType { - switch(mal.data) { - .Generic => |symbol| { - defer mal.delete(Allocator); - return lookup(env, symbol, true); - }, - .List => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); - } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_list(Allocator, new_ll); - return ret_mal; - }, - .Vector => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn EVAL_vector(ll: []*MalType, env: *Env) !*MalType { + const ret_mal = try MalType.new_vector(); + errdefer ret_mal.decref(); + for(ll) |x| { + const new_mal = try EVAL(x, env); + try ret_mal.Vector.data.append(Allocator, new_mal); } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_vector(Allocator, new_ll); return ret_mal; - }, - .HashMap => |hmap| { - var new_hashmap = try MalType.new_hashmap(Allocator); +} + +fn EVAL_map(hmap: hash_map.MalHashMap, env: *Env) !*MalType { + const new_hashmap = try MalType.new_hashmap(); + errdefer new_hashmap.decref(); var iterator = hmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = pair.key; - const value = pair.value; - const evaled_value = try EVAL(value, try env.copy(Allocator)); - try new_hashmap.hashmap_insert(key, evaled_value); - optional_pair = iterator.next(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + const evaled_value = try EVAL(value, env); + // key *is* new in this map. + try hash_map.map_insert_incref_key(&new_hashmap.HashMap.data, key, evaled_value); } - hash_map.destroy(Allocator, hmap, true); - mal.shallow_destroy(Allocator); return new_hashmap; - }, - else => { - return mal; - } - } } const safeAdd = @import("std").math.add; @@ -182,73 +171,88 @@ const safeSub = @import("std").math.sub; const safeMul = @import("std").math.mul; const safeDivFloor = @import("std").math.divFloor; -fn int_plus(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_plus(args: []*MalType) MalError!*MalType { + if (args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeAdd(i64, x, y) catch return MalError.Overflow; - return MalType.new_int(Allocator, res); + const res = try safeAdd(i64, x, y); + return MalType.new_int(res); } -fn int_minus(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_minus(args: []*MalType) MalError!*MalType { + if (args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeSub(i64, x, y) catch return MalError.Overflow; - return MalType.new_int(Allocator, res); + const res = try safeSub(i64, x, y); + return MalType.new_int(res); } -fn int_mult(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_mult(args: []*MalType) MalError!*MalType { + if (args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeMul(i64, x, y) catch return MalError.Overflow; - return MalType.new_int(Allocator, res); + const res = try safeMul(i64, x, y); + return MalType.new_int(res); } -fn int_div(a1: *MalType, a2: *MalType) MalError!*MalType { +fn int_div(args: []*MalType) MalError!*MalType { + if (args.len != 2) return MalError.ArgError; + const a1 = args[0]; + const a2 = args[1]; const x = try a1.as_int(); const y = try a2.as_int(); - const res = safeDivFloor(i64, x, y) catch |err| switch(err) { - error.DivisionByZero => return MalError.DivisionByZero, - else => return MalError.Overflow, - }; - return MalType.new_int(Allocator, res); + const res = try safeDivFloor(i64, x, y); + return MalType.new_int(res); } -fn make_environment() MalError!*Env { - repl_environment = try Env.new(Allocator, null); - var environment = repl_environment; +fn make_environment() !void { + + const plus_sym = try MalType.new_symbol("+", true); + const plus_mal = try MalType.newFnCore(&int_plus); + try repl_environment.set(plus_sym, plus_mal); + const minus_sym = try MalType.new_symbol("-", true); + const minus_mal = try MalType.newFnCore(&int_minus); + try repl_environment.set(minus_sym, minus_mal); + const mult_sym = try MalType.new_symbol("*", true); + const mult_mal = try MalType.newFnCore(&int_mult); + try repl_environment.set(mult_sym, mult_mal); + const div_sym = try MalType.new_symbol("/", true); + const div_mal = try MalType.newFnCore(&int_div); + try repl_environment.set(div_sym, div_mal); +} - const plus_mal = try MalType.new_nil(Allocator); - plus_mal.data = MalData{.Fn2 = &int_plus}; - try environment.set("+", plus_mal); - const minus_mal = try MalType.new_nil(Allocator); - minus_mal.data = MalData{.Fn2 = &int_minus}; - try environment.set("-", minus_mal); - const mult_mal = try MalType.new_nil(Allocator); - mult_mal.data = MalData{.Fn2 = &int_mult}; - try environment.set("*", mult_mal); - const div_mal = try MalType.new_nil(Allocator); - div_mal.data = MalData{.Fn2 = &int_div}; - try environment.set("/", div_mal); +pub fn apply_function(f: MalType, args: []*MalType) MalError!*MalType { - return environment; + switch(f) { + .FnCore => |fncoredata| { + return fncoredata.data(args); + }, + else => { + return MalError.ApplyError; + }, + } } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - var environment = try make_environment(); - while(true) { - var line = (try getline(Allocator)) orelse break; - var optional_output = rep(environment, line) catch |err| { - if(err == MalError.KeyError) { - continue; - } else { - return err; + try make_environment(); + + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); } }; - if(optional_output) |output| { - try stdout_file.write(output); - Allocator.free(output); - try stdout_file.write("\n"); - } } } diff --git a/impls/zig/step4_if_fn_do.zig b/impls/zig/step4_if_fn_do.zig index aeb8942e59..e6b092250f 100644 --- a/impls/zig/step4_if_fn_do.zig +++ b/impls/zig/step4_if_fn_do.zig @@ -1,287 +1,276 @@ const std = @import("std"); -const warn = @import("std").debug.warn; const reader = @import("reader.zig"); -const pcre = reader.pcre; const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; -const string_concat = @import("utils.zig").string_concat; -const apply_function = @import("types.zig").apply_function; -const linked_list = @import("linked_list.zig"); +const string_eql = std.hash_map.eqlString; const hash_map = @import("hmap.zig"); const core = @import("core.zig"); const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalData = @import("types.zig").MalData; const MalError = @import("error.zig").MalError; -const MalFuncData = @import("types.zig").MalFuncData; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const Env = @import("env.zig").Env; +const get_error_data = @import("error.zig").get_error_data; +const throw = @import("error.zig").throw; +const stdout_file = std.io.getStdOut(); -var repl_environment: *Env = undefined; +var repl_environment = Env.new_root(); -fn READ(a: []const u8) MalError!?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } +// Do not allocate this one on each EVAL run. +// The string is static, but will never be deallocated. +var DEBUG_EVAL = MalType { .Symbol = .{ .data = "DEBUG-EVAL" } }; + fn EVAL(mal: *MalType, env: *Env) MalError!*MalType { - switch(mal.data) { + + if(try env.get(&DEBUG_EVAL)) |dbgeval| { + switch (dbgeval.*) { + .Nil, .False => {}, + else => { + try stdout_file.writeAll("EVAL: "); + try PRINT(mal.*); + } + } + } + + switch(mal.*) { .List => |ll| { - if(ll.len == 0) { + const items = ll.data.items; + if(items.len == 0) { + mal.incref(); return mal; } - var first_mal = linked_list.first(&ll) orelse return MalError.ArgError; - var symbol = switch(first_mal.data) { - .Generic => |symbol| symbol, + const first_mal = items[0]; + const symbol = switch(first_mal.*) { + .Symbol => |symbol| symbol.data, else => "", }; if(string_eql(symbol, "def!")) { - return EVAL_def(mal, env); + return EVAL_def(items[1..], env); } else if(string_eql(symbol, "let*")) { - return EVAL_let(mal, env); + return EVAL_let(items[1..], env); } else if(string_eql(symbol, "do")) { - return EVAL_do(mal, env); + return EVAL_do(items[1..], env); } else if(string_eql(symbol, "if")) { - return EVAL_if(mal, env); + return EVAL_if(items[1..], env); } else if(string_eql(symbol, "fn*")) { - return EVAL_fn(mal, env); + return EVAL_fn(items[1..], env); } else { - var new_list = try eval_ast(mal, env); - return apply_function(Allocator, (try new_list.sequence_linked_list()).*); + const evaluated_first = try EVAL(first_mal, env); + defer evaluated_first.decref(); + // A slice would be sufficient, but a List is convenient + // for partial deallocation in case of error. + const args = try MalType.new_list(); + defer args.decref(); + for(items[1..]) |x| { + const new_item = try EVAL(x, env); + try args.List.data.append(Allocator, new_item); + } + return apply_function(evaluated_first.*, args.List.data.items); } }, + .Symbol => { + return EVAL_symbol(mal, env); + }, + .Vector => |ll| { + return EVAL_vector(ll.data.items, env); + }, + .HashMap => |hmap| { + return EVAL_map(hmap.data, env); + }, else => { - return eval_ast(mal, env); + mal.incref(); + return mal; }, } } -fn EVAL_def(mal: *MalType, env: *Env) MalError!*MalType { - const first_arg = try mal.sequence_nth(1); - const second_arg = try mal.sequence_nth(2); - const second_arg_copy = try second_arg.copy(Allocator); - const symbol_name = try first_arg.as_symbol(); - const new_value = try EVAL(second_arg_copy, env); +fn EVAL_def(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env); try env.set(symbol_name, new_value); - mal.delete(Allocator); - return new_value.copy(Allocator); + new_value.incref(); + return new_value; } -fn EVAL_let(mal: *MalType, env: *Env) MalError!*MalType { - const binding_arg = try mal.sequence_nth(1); - const eval_arg = try mal.sequence_nth(2); - const eval_arg_copy = try eval_arg.copy(Allocator); - const new_env = try Env.new(Allocator, env); - defer new_env.delete(); - var binding_ll = switch(binding_arg.data) { - .List => |l| l, - .Vector => |v| v, - else => return MalError.TypeError, - }; - var iterator = binding_ll.iterator(); - var optional_node = iterator.next(); - while(optional_node) |node| { - const key_mal = node; - const key = try key_mal.as_symbol(); - const val_mal = iterator.next() orelse return MalError.ArgError; +fn EVAL_let(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const binding_arg = args[0]; + const eval_arg = args[1]; + const binds = try binding_arg.as_slice(); + if(binds.len % 2 != 0) return MalError.ArgError; + const new_env = try Env.new(env); + env.incref(); + defer new_env.decref(); + for(0..binds.len / 2) |i| { + const key = binds[2*i]; + const val_mal = binds[2*i + 1]; const evaled_mal = try EVAL(val_mal, new_env); + errdefer evaled_mal.decref(); try new_env.set(key, evaled_mal); - optional_node = iterator.next(); - key_mal.delete(Allocator); + // Do not increment the refcount for the value. } - - linked_list.destroy(Allocator, &binding_ll, true); - binding_arg.data = MalData{.Nil=undefined}; - mal.delete(Allocator); - - // We use eval_arg_copy, since we just deleted eval_arg above - return EVAL(eval_arg_copy, new_env); + return EVAL(eval_arg, new_env); } -fn EVAL_do(mal: *MalType, env: *Env) MalError!*MalType { - var ll = &mal.data.List; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var evaled_mal = try eval_ast(mal, env); - var last_mal = try evaled_mal.sequence_pop_last(Allocator); - evaled_mal.delete(Allocator); - return last_mal; +fn EVAL_do(args: []*MalType, env: *Env) !*MalType { + if(args.len == 0) return MalError.ArgError; + const last_mal = args[args.len - 1]; + for (args[0..args.len - 1]) |form| { + const item = try EVAL(form, env); + item.decref(); + } + return EVAL(last_mal, env); } -fn EVAL_if(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - const first_arg = try mal.sequence_nth(1); - const first_arg_copy = try first_arg.copy(Allocator); - const evaled = try EVAL(first_arg_copy, env); - const is_true = switch(evaled.data) { +fn EVAL_if(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2 and args.len != 3) return MalError.ArgError; + const first_arg = args[0]; + const evaled = try EVAL(first_arg, env); + const is_true = switch(evaled.*) { .False => false, .Nil => false, else => true, }; + evaled.decref(); if(is_true) { - const second_arg = try mal.sequence_nth(2); - const second_arg_copy = try second_arg.copy(Allocator); - return EVAL(second_arg_copy, env); + const second_arg = args[1]; + return EVAL(second_arg, env); } - if((try mal.sequence_length()) < 4) { - return MalType.new_nil(Allocator); + if(args.len == 2) { + return &MalType.NIL; } - const third_arg = try mal.sequence_nth(3); - const third_arg_copy = try third_arg.copy(Allocator); - return EVAL(third_arg_copy, env); + const third_arg = args[2]; + return EVAL(third_arg, env); } -fn EVAL_fn(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - const arg_mal = try (try mal.sequence_nth(1)).copy(Allocator); - const body_mal = try (try mal.sequence_nth(2)).copy(Allocator); - const func_data = MalFuncData { - .arg_list = arg_mal, - .body = body_mal, - .environment = env, - .is_macro = false, - .eval_func = &EVAL, - }; - const new_func = try MalType.new_nil(Allocator); - new_func.data = MalData{.Func = func_data}; +fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const arg_mal = args[0]; + const body_mal = args[1]; + for (try arg_mal.as_slice()) |x| { + switch (x.*) { + .Symbol => {}, + else => return MalError.TypeError, + } + } + const new_func = try MalType.newFunc(arg_mal, body_mal, env); + arg_mal.incref(); + body_mal.incref(); + env.incref(); return new_func; } -fn PRINT(optional_mal: ?*MalType) MalError![] u8 { - return printer.print_str(optional_mal); +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep(environment: *Env, input: [] const u8) MalError!?[] u8 { - var read_input = (try READ(input)) orelse return null; - var eval_input = try EVAL(read_input, try environment.copy(Allocator)); - var print_input = try PRINT(eval_input); - eval_input.delete(Allocator); - return print_input; +fn rep(print: bool, input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = try EVAL(read_input, &repl_environment); + defer eval_input.decref(); + if(print) { + try PRINT(eval_input.*); + } } -fn lookup(environment: *Env, symbol: []const u8, do_warn: bool) MalError!*MalType { - var mal = environment.get(symbol) catch |err| { - if(do_warn) { - const s1 = string_concat(Allocator, "'", symbol) catch return MalError.SystemError; - const s2 = string_concat(Allocator, s1, "' not found") catch return MalError.SystemError; - defer Allocator.free(s1); - defer Allocator.free(s2); - warn("'{}' not found.\n", symbol); - } - return MalError.KeyError; - }; - var new_mal = try mal.copy(Allocator); - return new_mal; +fn EVAL_symbol(mal: *MalType, env: *Env) !*MalType { + if(try env.get(mal)) |value| { + value.incref(); + return value; + } + const err = try std.fmt.allocPrint(Allocator, "'{s}' not found", + .{mal.Symbol.data}); + return throw(try MalType.new_string(err, false)); } -fn eval_ast(mal: *MalType, env: *Env) MalError!*MalType { - switch(mal.data) { - .Generic => |symbol| { - defer mal.delete(Allocator); - return lookup(env, symbol, true); - }, - .List => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn EVAL_vector(ll: []*MalType, env: *Env) !*MalType { + const ret_mal = try MalType.new_vector(); + errdefer ret_mal.decref(); + for(ll) |x| { + const new_mal = try EVAL(x, env); + try ret_mal.Vector.data.append(Allocator, new_mal); } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_list(Allocator, new_ll); return ret_mal; - }, - .Vector => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); - } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_vector(Allocator, new_ll); - return ret_mal; - }, - .HashMap => |hmap| { - var new_hashmap = try MalType.new_hashmap(Allocator); +} + +fn EVAL_map(hmap: hash_map.MalHashMap, env: *Env) !*MalType { + const new_hashmap = try MalType.new_hashmap(); + errdefer new_hashmap.decref(); var iterator = hmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = pair.key; - const value = pair.value; - const evaled_value = try EVAL(value, try env.copy(Allocator)); - try new_hashmap.hashmap_insert(key, evaled_value); - optional_pair = iterator.next(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + const evaled_value = try EVAL(value, env); + try hash_map.map_insert_incref_key(&new_hashmap.HashMap.data, key, evaled_value); } - hash_map.destroy(Allocator, hmap, true); - mal.shallow_destroy(Allocator); return new_hashmap; - }, - else => { - return mal; - } - } } -fn make_environment() MalError!*Env { - repl_environment = try Env.new(Allocator, null); - var environment = try repl_environment.copy(Allocator); +fn make_environment() !void { for(core.core_namespace) |pair| { - const name = pair.name; - const func_mal: *MalType = try MalType.new_nil(Allocator); - func_mal.data = switch(pair.func) { - core.CorePairType.Fn0 => |func| MalData{.Fn0 = func}, - core.CorePairType.Fn1 => |func| MalData{.Fn1 = func}, - core.CorePairType.Fn2 => |func| MalData{.Fn2 = func}, - core.CorePairType.FVar => |func| MalData{.FVar = func}, - else => return MalError.TypeError, - }; - try environment.set(name, func_mal); + const name = try MalType.new_symbol(pair.name, true); + const func_mal = try MalType.newFnCore(pair.func); + try repl_environment.set(name, func_mal); + name.decref(); } const def_not_string: [] const u8 = \\(def! not (fn* (a) (if a false true))) ; - var optional_output = try rep(environment, def_not_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_not_string); +} + +pub fn apply_function(f: MalType, args: []*MalType) MalError!*MalType { - return environment; + switch(f) { + .FnCore => |fncoredata| { + return fncoredata.data(args); + }, + .Func => |funcdata| { + const apply_env = try funcdata.gen_env(args); + defer apply_env.decref(); + return EVAL(funcdata.body, apply_env); + }, + else => { + return MalError.ApplyError; + }, + } } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - core.set_allocator(Allocator); - var environment = try make_environment(); - while(true) { - var line = (try getline(Allocator)) orelse break; - var optional_output = rep(environment, line) catch |err| { - if(err == MalError.KeyError) { - continue; - } else { - return err; + try make_environment(); + + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(true, line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); } }; - if(optional_output) |output| { - try stdout_file.write(output); - Allocator.free(output); - Allocator.free(line); - try stdout_file.write("\n"); - } } } diff --git a/impls/zig/step5_tco.zig b/impls/zig/step5_tco.zig index c4a3e258d8..4fbc4345b7 100644 --- a/impls/zig/step5_tco.zig +++ b/impls/zig/step5_tco.zig @@ -1,302 +1,307 @@ const std = @import("std"); -const warn = @import("std").debug.warn; const reader = @import("reader.zig"); -const pcre = reader.pcre; const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; -const string_concat = @import("utils.zig").string_concat; -const apply_function = @import("types.zig").apply_function; -const linked_list = @import("linked_list.zig"); +const string_eql = std.hash_map.eqlString; const hash_map = @import("hmap.zig"); const core = @import("core.zig"); const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalData = @import("types.zig").MalData; const MalError = @import("error.zig").MalError; -const MalFuncData = @import("types.zig").MalFuncData; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const Env = @import("env.zig").Env; +const get_error_data = @import("error.zig").get_error_data; +const throw = @import("error.zig").throw; +const stdout_file = std.io.getStdOut(); -var repl_environment: *Env = undefined; +var repl_environment = Env.new_root(); -fn READ(a: []const u8) MalError!?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } -fn EVAL(mal_arg: *MalType, env_arg: *Env) MalError!*MalType { +// Do not allocate this one on each EVAL run. +// The string is static, but will never be deallocated. +var DEBUG_EVAL = MalType { .Symbol = .{ .data = "DEBUG-EVAL" } }; + +fn EVAL(mal_arg: *MalType, env_arg: *Env, finally_destroy_env: bool) MalError!*MalType { var mal = mal_arg; var env = env_arg; + var fde = finally_destroy_env; + defer if(fde) env.decref(); while(true) { - switch(mal.data) { + + if(try env.get(&DEBUG_EVAL)) |dbgeval| { + switch (dbgeval.*) { + .Nil, .False => {}, + else => { + try stdout_file.writeAll("EVAL: "); + try PRINT(mal.*); + } + } + } + + switch(mal.*) { .List => |ll| { - if(ll.len == 0) { + const items = ll.data.items; + if(items.len == 0) { + mal.incref(); return mal; } - var first_mal = linked_list.first(&ll) orelse return MalError.ArgError; - var symbol = switch(first_mal.data) { - .Generic => |symbol| symbol, + const first_mal = items[0]; + const symbol = switch(first_mal.*) { + .Symbol => |symbol| symbol.data, else => "", }; if(string_eql(symbol, "def!")) { - return EVAL_def(mal, env); + return EVAL_def(items[1..], env); } else if(string_eql(symbol, "let*")) { - try EVAL_let(&mal, &env); + try EVAL_let(items[1..], &mal, &env, &fde); continue; } else if(string_eql(symbol, "do")) { - try EVAL_do(&mal, &env); + try EVAL_do(items[1..], &mal, env); continue; } else if(string_eql(symbol, "if")) { - try EVAL_if(&mal, &env); + try EVAL_if(items[1..], &mal, env); continue; } else if(string_eql(symbol, "fn*")) { - return EVAL_fn(mal, env); + return EVAL_fn(items[1..], env); } else { - var new_list = try eval_ast(mal, env); - return apply_function(Allocator, (try new_list.sequence_linked_list()).*); + const evaluated_first = try EVAL(first_mal, env, false); + defer evaluated_first.decref(); + // A slice would be sufficient, but a List is convenient + // for partial deallocation in case of error. + const args = try MalType.new_list(); + defer args.decref(); + for(items[1..]) |x| { + const new_item = try EVAL(x, env, false); + try args.List.data.append(Allocator, new_item); + } + switch(evaluated_first.*) { + .Func => |func_data| { + if(fde) { + env.decref(); + } + else { + fde = true; + } + env = try func_data.gen_env(args.List.data.items); + mal = func_data.body; + continue; + }, + else => {}, + } + return apply_function(evaluated_first.*, args.List.data.items); } }, + .Symbol => { + return EVAL_symbol(mal, env); + }, + .Vector => |ll| { + return EVAL_vector(ll.data.items, env); + }, + .HashMap => |hmap| { + return EVAL_map(hmap.data, env); + }, else => { - return eval_ast(mal, env); + mal.incref(); + return mal; }, } } } -fn EVAL_def(mal: *MalType, env: *Env) MalError!*MalType { - const first_arg = try mal.sequence_nth(1); - const second_arg = try mal.sequence_nth(2); - const second_arg_copy = try second_arg.copy(Allocator); - const symbol_name = try first_arg.as_symbol(); - const new_value = try EVAL(second_arg_copy, env); +fn EVAL_def(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env, false); try env.set(symbol_name, new_value); - mal.delete(Allocator); - return new_value.copy(Allocator); + new_value.incref(); + return new_value; } -fn EVAL_let(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - const mal = mal_ptr.*; +fn EVAL_let(args: []*MalType, mal_ptr: **MalType, env_ptr: **Env, fde: *bool) !void { + if(args.len != 2) return MalError.ArgError; const env = env_ptr.*; - const binding_arg = try mal.sequence_nth(1); - const eval_arg = try mal.sequence_nth(2); - const eval_arg_copy = try eval_arg.copy(Allocator); - const new_env = try Env.new(Allocator, env); - var binding_ll = switch(binding_arg.data) { - .List => |l| l, - .Vector => |v| v, - else => return MalError.TypeError, - }; - var iterator = binding_ll.iterator(); - var optional_node = iterator.next(); - while(optional_node) |node| { - const key_mal = node; - const key = try key_mal.as_symbol(); - const val_mal = iterator.next() orelse return MalError.ArgError; - const evaled_mal = try EVAL(val_mal, new_env); - try new_env.set(key, evaled_mal); - optional_node = iterator.next(); - key_mal.delete(Allocator); + const binding_arg = args[0]; + const eval_arg = args[1]; + const binds = try binding_arg.as_slice(); + if(binds.len % 2 != 0) return MalError.ArgError; + const new_env = try Env.new(env); + // Change env and fde in case an error occurs later in this procedure + // and fde triggers an env.decref() at the exit of EVAL. + if(!fde.*) { + env.incref(); + fde.* = true; } - - linked_list.destroy(Allocator, &binding_ll, true); - binding_arg.data = MalData{.Nil=undefined}; - mal.delete(Allocator); - - // We use eval_arg_copy, since we just deleted eval_arg above - mal_ptr.* = eval_arg_copy; env_ptr.* = new_env; + for(0..binds.len / 2) |i| { + const key = binds[2*i]; + const val_mal = binds[2*i + 1]; + const evaled_mal = try EVAL(val_mal, new_env, false); + errdefer evaled_mal.decref(); + try new_env.set(key, evaled_mal); + // Do not increment the refcount for the value. + } + mal_ptr.* = eval_arg; } -fn EVAL_do(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - var ll = &mal.data.List; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var last_mal = try mal.sequence_pop_last(Allocator); - var evaled_mal = try eval_ast(mal, env); - evaled_mal.delete(Allocator); - +fn EVAL_do(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len == 0) return MalError.ArgError; + const last_mal = args[args.len - 1]; + for (args[0..args.len - 1]) |form| { + const item = try EVAL(form, env, false); + item.decref(); + } mal_ptr.* = last_mal; } -fn EVAL_if(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - defer mal.delete(Allocator); - const first_arg = try mal.sequence_nth(1); - const first_arg_copy = try first_arg.copy(Allocator); - const evaled = try EVAL(first_arg_copy, env); - const is_true = switch(evaled.data) { +fn EVAL_if(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len != 2 and args.len != 3) return MalError.ArgError; + const first_arg = args[0]; + const evaled = try EVAL(first_arg, env, false); + const is_true = switch(evaled.*) { .False => false, .Nil => false, else => true, }; + evaled.decref(); if(is_true) { - const second_arg = try mal.sequence_nth(2); - mal_ptr.* = try second_arg.copy(Allocator); + const second_arg = args[1]; + mal_ptr.* = second_arg; return; } - if((try mal.sequence_length()) < 4) { - mal_ptr.* = try MalType.new_nil(Allocator); + if(args.len == 2) { + mal_ptr.* = &MalType.NIL; return; } - const third_arg = try mal.sequence_nth(3); - const third_arg_copy = try third_arg.copy(Allocator); - mal_ptr.* = third_arg_copy; + const third_arg = args[2]; + mal_ptr.* = third_arg; } -fn EVAL_fn(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - const arg_mal = try (try mal.sequence_nth(1)).copy(Allocator); - const body_mal = try (try mal.sequence_nth(2)).copy(Allocator); - const func_data = MalFuncData { - .arg_list = arg_mal, - .body = body_mal, - .environment = env, - .is_macro = false, - .eval_func = &EVAL, - }; - const new_func = try MalType.new_nil(Allocator); - new_func.data = MalData{.Func = func_data}; +fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const arg_mal = args[0]; + const body_mal = args[1]; + for (try arg_mal.as_slice()) |x| { + switch (x.*) { + .Symbol => {}, + else => return MalError.TypeError, + } + } + const new_func = try MalType.newFunc(arg_mal, body_mal, env); + arg_mal.incref(); + body_mal.incref(); + env.incref(); return new_func; } -fn PRINT(optional_mal: ?*MalType) MalError![] u8 { - return printer.print_str(optional_mal); +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep(environment: *Env, input: [] const u8) MalError!?[] u8 { - var read_input = (try READ(input)) orelse return null; - var eval_input = try EVAL(read_input, environment); - var print_input = try PRINT(eval_input); - eval_input.delete(Allocator); - return print_input; +fn rep(print: bool, input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = try EVAL(read_input, &repl_environment, false); + defer eval_input.decref(); + if(print) { + try PRINT(eval_input.*); + } } -fn lookup(environment: *Env, symbol: []const u8, do_warn: bool) MalError!*MalType { - var mal = environment.get(symbol) catch |err| { - if(do_warn) { - const s1 = string_concat(Allocator, "'", symbol) catch return MalError.SystemError; - const s2 = string_concat(Allocator, s1, "' not found") catch return MalError.SystemError; - defer Allocator.free(s1); - defer Allocator.free(s2); - warn("'{}' not found.\n", symbol); - } - return MalError.KeyError; - }; - var new_mal = try mal.copy(Allocator); - return new_mal; +fn EVAL_symbol(mal: *MalType, env: *Env) !*MalType { + if(try env.get(mal)) |value| { + value.incref(); + return value; + } + const err = try std.fmt.allocPrint(Allocator, "'{s}' not found", + .{mal.Symbol.data}); + return throw(try MalType.new_string(err, false)); } -fn eval_ast(mal: *MalType, env: *Env) MalError!*MalType { - switch(mal.data) { - .Generic => |symbol| { - defer mal.delete(Allocator); - return lookup(env, symbol, true); - }, - .List => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); - } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_list(Allocator, new_ll); - return ret_mal; - }, - .Vector => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn EVAL_vector(ll: []*MalType, env: *Env) !*MalType { + const ret_mal = try MalType.new_vector(); + errdefer ret_mal.decref(); + for(ll) |x| { + const new_mal = try EVAL(x, env, false); + try ret_mal.Vector.data.append(Allocator, new_mal); } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_vector(Allocator, new_ll); return ret_mal; - }, - .HashMap => |hmap| { - var new_hashmap = try MalType.new_hashmap(Allocator); +} + +fn EVAL_map(hmap: hash_map.MalHashMap, env: *Env) !*MalType { + const new_hashmap = try MalType.new_hashmap(); + errdefer new_hashmap.decref(); var iterator = hmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = pair.key; - const value = pair.value; - const evaled_value = try EVAL(value, try env.copy(Allocator)); - try new_hashmap.hashmap_insert(key, evaled_value); - optional_pair = iterator.next(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + const evaled_value = try EVAL(value, env, false); + try hash_map.map_insert_incref_key(&new_hashmap.HashMap.data, key, evaled_value); } - hash_map.destroy(Allocator, hmap, true); - mal.shallow_destroy(Allocator); return new_hashmap; - }, - else => { - return mal; - } - } } -fn make_environment() MalError!*Env { - repl_environment = try Env.new(Allocator, null); - var environment = repl_environment; +fn make_environment() !void { for(core.core_namespace) |pair| { - const name = pair.name; - const func_mal: *MalType = try MalType.new_nil(Allocator); - func_mal.data = switch(pair.func) { - core.CorePairType.Fn0 => |func| MalData{.Fn0 = func}, - core.CorePairType.Fn1 => |func| MalData{.Fn1 = func}, - core.CorePairType.Fn2 => |func| MalData{.Fn2 = func}, - core.CorePairType.FVar => |func| MalData{.FVar = func}, - else => return MalError.TypeError, - }; - try environment.set(name, func_mal); + const name = try MalType.new_symbol(pair.name, true); + const func_mal = try MalType.newFnCore(pair.func); + try repl_environment.set(name, func_mal); + name.decref(); } const def_not_string: [] const u8 = \\(def! not (fn* (a) (if a false true))) ; - var optional_output = try rep(environment, def_not_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_not_string); +} - return environment; +pub fn apply_function(f: MalType, args: []*MalType) MalError!*MalType { + + switch(f) { + .FnCore => |fncoredata| { + return fncoredata.data(args); + }, + .Func => |funcdata| { + const apply_env = try funcdata.gen_env(args); + defer apply_env.decref(); + return EVAL(funcdata.body, apply_env, false); + }, + else => { + return MalError.ApplyError; + }, + } } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - core.set_allocator(Allocator); - var environment = try make_environment(); - while(true) { - var line = (try getline(Allocator)) orelse break; - var optional_output = rep(environment, line) catch |err| { - if(err == MalError.KeyError) { - continue; - } else { - return err; + try make_environment(); + + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(true, line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); } }; - if(optional_output) |output| { - try stdout_file.write(output); - Allocator.free(output); - Allocator.free(line); - try stdout_file.write("\n"); - } } } diff --git a/impls/zig/step6_file.zig b/impls/zig/step6_file.zig index 6b99215029..2de7a02f91 100644 --- a/impls/zig/step6_file.zig +++ b/impls/zig/step6_file.zig @@ -1,382 +1,345 @@ const std = @import("std"); -const warn = @import("std").debug.warn; const reader = @import("reader.zig"); -const pcre = reader.pcre; const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; -const string_concat = @import("utils.zig").string_concat; -const apply_function = @import("types.zig").apply_function; -const linked_list = @import("linked_list.zig"); +const string_eql = std.hash_map.eqlString; const hash_map = @import("hmap.zig"); const core = @import("core.zig"); const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalTypeValue = @import("types.zig").MalTypeValue; -const MalData = @import("types.zig").MalData; const MalError = @import("error.zig").MalError; -const MalFuncData = @import("types.zig").MalFuncData; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const Env = @import("env.zig").Env; +const get_error_data = @import("error.zig").get_error_data; +const throw = @import("error.zig").throw; +const stdout_file = std.io.getStdOut(); -var repl_environment: *Env = undefined; +var repl_environment = Env.new_root(); -fn READ(a: []const u8) MalError!?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } -fn EVAL(mal_arg: *MalType, env_arg: *Env) MalError!*MalType { +// Do not allocate this one on each EVAL run. +// The string is static, but will never be deallocated. +var DEBUG_EVAL = MalType { .Symbol = .{ .data = "DEBUG-EVAL" } }; + +fn EVAL(mal_arg: *MalType, env_arg: *Env, finally_destroy_env: bool) MalError!*MalType { var mal = mal_arg; var env = env_arg; + var fde = finally_destroy_env; + defer if(fde) env.decref(); while(true) { - switch(mal.data) { + + if(try env.get(&DEBUG_EVAL)) |dbgeval| { + switch (dbgeval.*) { + .Nil, .False => {}, + else => { + try stdout_file.writeAll("EVAL: "); + try PRINT(mal.*); + } + } + } + + switch(mal.*) { .List => |ll| { - if(ll.len == 0) { + const items = ll.data.items; + if(items.len == 0) { + mal.incref(); return mal; } - var first_mal = linked_list.first(&ll) orelse return MalError.ArgError; - var symbol = switch(first_mal.data) { - .Generic => |symbol| symbol, + const first_mal = items[0]; + const symbol = switch(first_mal.*) { + .Symbol => |symbol| symbol.data, else => "", }; if(string_eql(symbol, "def!")) { - return EVAL_def(mal, env); + return EVAL_def(items[1..], env); } else if(string_eql(symbol, "let*")) { - try EVAL_let(&mal, &env); + try EVAL_let(items[1..], &mal, &env, &fde); continue; } else if(string_eql(symbol, "do")) { - try EVAL_do(&mal, &env); + try EVAL_do(items[1..], &mal, env); continue; } else if(string_eql(symbol, "if")) { - try EVAL_if(&mal, &env); + try EVAL_if(items[1..], &mal, env); continue; } else if(string_eql(symbol, "fn*")) { - return EVAL_fn(mal, env); + return EVAL_fn(items[1..], env); } else { - var new_list = try eval_ast(mal, try env.copy(Allocator)); - - if(MalTypeValue((try new_list.sequence_nth(0)).data) == MalTypeValue.Func) { - try do_user_func(try new_list.sequence_linked_list(), &mal, &env); - new_list.shallow_destroy(Allocator); - continue; + const evaluated_first = try EVAL(first_mal, env, false); + defer evaluated_first.decref(); + // A slice would be sufficient, but a List is convenient + // for partial deallocation in case of error. + const args = try MalType.new_list(); + defer args.decref(); + for(items[1..]) |x| { + const new_item = try EVAL(x, env, false); + try args.List.data.append(Allocator, new_item); + } + switch(evaluated_first.*) { + .Func => |func_data| { + if(fde) { + env.decref(); + } + else { + fde = true; + } + env = try func_data.gen_env(args.List.data.items); + mal = func_data.body; + continue; + }, + else => {}, } - const res = try apply_function(Allocator, (try new_list.sequence_linked_list()).*); - new_list.delete(Allocator); - env.delete(); - return res; + return apply_function(evaluated_first.*, args.List.data.items); } }, + .Symbol => { + return EVAL_symbol(mal, env); + }, + .Vector => |ll| { + return EVAL_vector(ll.data.items, env); + }, + .HashMap => |hmap| { + return EVAL_map(hmap.data, env); + }, else => { - return eval_ast(mal, env); + mal.incref(); + return mal; }, } } } -fn eval(a1: *MalType) MalError!*MalType { - return EVAL(try a1.copy(Allocator), try repl_environment.copy(Allocator)); +fn eval(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return EVAL(a1, &repl_environment, false); } -fn EVAL_def(mal: *MalType, env: *Env) MalError!*MalType { - const first_arg = try mal.sequence_nth(1); - const second_arg = try mal.sequence_nth(2); - const second_arg_copy = try second_arg.copy(Allocator); - const symbol_name = try first_arg.as_symbol(); - const new_value = try EVAL(second_arg_copy, try env.copy(Allocator)); +fn EVAL_def(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env, false); try env.set(symbol_name, new_value); - mal.delete(Allocator); - env.delete(); - return new_value.copy(Allocator); + new_value.incref(); + return new_value; } -fn EVAL_let(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - //TODO: make faster - const mal = mal_ptr.*; +fn EVAL_let(args: []*MalType, mal_ptr: **MalType, env_ptr: **Env, fde: *bool) !void { + if(args.len != 2) return MalError.ArgError; const env = env_ptr.*; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - const binding_arg = try mal.sequence_pop_first(Allocator); - const eval_arg = try mal.sequence_pop_first(Allocator); - const new_env = try Env.new(Allocator, env); - var binding_ll = switch(binding_arg.data) { - .List => |l| l, - .Vector => |v| v, - else => return MalError.TypeError, - }; - var iterator = binding_ll.iterator(); - var optional_node = iterator.next(); - while(optional_node) |node| { - const key_mal = node; - const key = try key_mal.as_symbol(); - const val_mal = iterator.next() orelse return MalError.ArgError; - const evaled_mal = try EVAL(val_mal, try new_env.copy(Allocator)); + const binding_arg = args[0]; + const eval_arg = args[1]; + const binds = try binding_arg.as_slice(); + if(binds.len % 2 != 0) return MalError.ArgError; + const new_env = try Env.new(env); + // Change env and fde in case an error occurs later in this procedure + // and fde triggers an env.decref() at the exit of EVAL. + if(!fde.*) { + env.incref(); + fde.* = true; + } + env_ptr.* = new_env; + for(0..binds.len / 2) |i| { + const key = binds[2*i]; + const val_mal = binds[2*i + 1]; + const evaled_mal = try EVAL(val_mal, new_env, false); + errdefer evaled_mal.decref(); try new_env.set(key, evaled_mal); - optional_node = iterator.next(); - key_mal.delete(Allocator); + // Do not increment the refcount for the value. } - - linked_list.destroy(Allocator, &binding_ll, true); - binding_arg.data = MalData{.Nil=undefined}; - binding_arg.delete(Allocator); - mal.delete(Allocator); - - // We use eval_arg_copy, since we just deleted eval_arg above mal_ptr.* = eval_arg; - env.delete(); - env_ptr.* = new_env; } -fn EVAL_do(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - var ll = &mal.data.List; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var last_mal = try mal.sequence_pop_last(Allocator); - var evaled_mal = try eval_ast(mal, try env.copy(Allocator)); - evaled_mal.delete(Allocator); +fn EVAL_do(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len == 0) return MalError.ArgError; + const last_mal = args[args.len - 1]; + for (args[0..args.len - 1]) |form| { + const item = try EVAL(form, env, false); + item.decref(); + } mal_ptr.* = last_mal; } -fn EVAL_if(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - defer mal.delete(Allocator); - const first_arg = try mal.sequence_nth(1); - const first_arg_copy = try first_arg.copy(Allocator); - const evaled = try EVAL(first_arg_copy, try env.copy(Allocator)); - const is_true = switch(evaled.data) { +fn EVAL_if(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len != 2 and args.len != 3) return MalError.ArgError; + const first_arg = args[0]; + const evaled = try EVAL(first_arg, env, false); + const is_true = switch(evaled.*) { .False => false, .Nil => false, else => true, }; - evaled.delete(Allocator); + evaled.decref(); if(is_true) { - const second_arg = try mal.sequence_nth(2); - mal_ptr.* = try second_arg.copy(Allocator); + const second_arg = args[1]; + mal_ptr.* = second_arg; return; } - if((try mal.sequence_length()) < 4) { - mal_ptr.* = try MalType.new_nil(Allocator); + if(args.len == 2) { + mal_ptr.* = &MalType.NIL; return; } - const third_arg = try mal.sequence_nth(3); - const third_arg_copy = try third_arg.copy(Allocator); - mal_ptr.* = third_arg_copy; + const third_arg = args[2]; + mal_ptr.* = third_arg; } -fn EVAL_fn(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - const arg_mal = try (try mal.sequence_nth(1)).copy(Allocator); - const body_mal = try (try mal.sequence_nth(2)).copy(Allocator); - const func_data = MalFuncData { - .arg_list = arg_mal, - .body = body_mal, - .environment = env, - .is_macro = false, - .eval_func = &EVAL, - }; - const new_func = try MalType.new_nil(Allocator); - new_func.data = MalData{.Func = func_data}; +fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const arg_mal = args[0]; + const body_mal = args[1]; + for (try arg_mal.as_slice()) |x| { + switch (x.*) { + .Symbol => {}, + else => return MalError.TypeError, + } + } + const new_func = try MalType.newFunc(arg_mal, body_mal, env); + arg_mal.incref(); + body_mal.incref(); + env.incref(); return new_func; } -fn PRINT(optional_mal: ?*MalType) MalError![] u8 { - return printer.print_str(optional_mal); -} - -fn rep(environment: *Env, input: [] const u8) MalError!?[] u8 { - var read_input = (try READ(input)) orelse return null; - var eval_input = try EVAL(read_input, try environment.copy(Allocator)); - var print_input = try PRINT(eval_input); - eval_input.delete(Allocator); - return print_input; +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep_and_print_errors(environment: *Env, input: [] const u8) ?[]u8 { - return rep(environment, input) catch |err| { - switch(err) { - MalError.KeyError => { }, - MalError.OutOfBounds => { - warn("Error: out of bounds\n"); - }, - MalError.ReaderUnmatchedParen => { - warn("Error: expected closing paren, got EOF\n"); - }, - else => { - warn("Unhandled error\n"); - }, - } - return null; - }; +fn rep(print: bool, input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = try EVAL(read_input, &repl_environment, false); + defer eval_input.decref(); + if(print) { + try PRINT(eval_input.*); + } } - -fn lookup(environment: *Env, symbol: []const u8, do_warn: bool) MalError!*MalType { - var mal = environment.get(symbol) catch |err| { - if(do_warn) { - const s1 = string_concat(Allocator, "'", symbol) catch return MalError.SystemError; - const s2 = string_concat(Allocator, s1, "' not found") catch return MalError.SystemError; - defer Allocator.free(s1); - defer Allocator.free(s2); - warn("'{}' not found.\n", symbol); - } - return MalError.KeyError; - }; - var new_mal = try mal.copy(Allocator); - return new_mal; +fn EVAL_symbol(mal: *MalType, env: *Env) !*MalType { + if(try env.get(mal)) |value| { + value.incref(); + return value; + } + const err = try std.fmt.allocPrint(Allocator, "'{s}' not found", + .{mal.Symbol.data}); + return throw(try MalType.new_string(err, false)); } -fn eval_ast(mal: *MalType, env: *Env) MalError!*MalType { - defer env.delete(); - switch(mal.data) { - .Generic => |symbol| { - defer mal.delete(Allocator); - return lookup(env, symbol, true); - }, - .List => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn EVAL_vector(ll: []*MalType, env: *Env) !*MalType { + const ret_mal = try MalType.new_vector(); + errdefer ret_mal.decref(); + for(ll) |x| { + const new_mal = try EVAL(x, env, false); + try ret_mal.Vector.data.append(Allocator, new_mal); } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_list(Allocator, new_ll); return ret_mal; - }, - .Vector => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); - } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_vector(Allocator, new_ll); - return ret_mal; - }, - .HashMap => |hmap| { - var new_hashmap = try MalType.new_hashmap(Allocator); +} + +fn EVAL_map(hmap: hash_map.MalHashMap, env: *Env) !*MalType { + const new_hashmap = try MalType.new_hashmap(); + errdefer new_hashmap.decref(); var iterator = hmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = pair.key; - const value = pair.value; - const evaled_value = try EVAL(value, try env.copy(Allocator)); - try new_hashmap.hashmap_insert(key, evaled_value); - optional_pair = iterator.next(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + const evaled_value = try EVAL(value, env, false); + try hash_map.map_insert_incref_key(&new_hashmap.HashMap.data, key, evaled_value); } - hash_map.destroy(Allocator, hmap, true); - mal.shallow_destroy(Allocator); return new_hashmap; - }, - else => { - return mal; - } - } } -fn make_environment() MalError!*Env { - repl_environment = try Env.new(Allocator, null); - var environment = try repl_environment.copy(Allocator); +fn make_environment() !void { for(core.core_namespace) |pair| { - const name = pair.name; - const func_mal: *MalType = try MalType.new_nil(Allocator); - func_mal.data = switch(pair.func) { - core.CorePairType.Fn0 => |func| MalData{.Fn0 = func}, - core.CorePairType.Fn1 => |func| MalData{.Fn1 = func}, - core.CorePairType.Fn2 => |func| MalData{.Fn2 = func}, - core.CorePairType.FVar => |func| MalData{.FVar = func}, - else => return MalError.TypeError, - }; - try environment.set(name, func_mal); + const name = try MalType.new_symbol(pair.name, true); + const func_mal = try MalType.newFnCore(pair.func); + try repl_environment.set(name, func_mal); + name.decref(); } - const eval_mal = try MalType.new_nil(Allocator); - eval_mal.data = MalData{.Fn1 = &eval}; - try environment.set("eval", eval_mal); + const eval_sym = try MalType.new_symbol("eval", true); + const eval_mal = try MalType.newFnCore(eval); + try repl_environment.set(eval_sym, eval_mal); + eval_sym.decref(); const def_not_string: [] const u8 = \\(def! not (fn* (a) (if a false true))) ; - var optional_output = try rep(environment, def_not_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_not_string); const load_file_string: [] const u8 = \\(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) ; - optional_output = try rep(environment, load_file_string); - if(optional_output) |output| { - Allocator.free(output); - } - - return environment; + try rep(false, load_file_string); } -fn do_user_func(args: *MalLinkedList, mal_ptr: **MalType, env_ptr: **Env) MalError!void { - const mal_func = try linked_list.pop_first(Allocator, args); - const env = env_ptr.*; - // 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; - var new_env = try Env.new(Allocator, func_env); - func_env.delete(); - try new_env.set_list(args_ll.*, args.*); - linked_list.destroy(Allocator, args, true); - func_data.arg_list.delete(Allocator); - mal_func.shallow_destroy(Allocator); - mal_ptr.* = func_data.body; - env.delete(); - env_ptr.* = new_env; - return; +pub fn apply_function(f: MalType, args: []*MalType) MalError!*MalType { + + switch(f) { + .FnCore => |fncoredata| { + return fncoredata.data(args); + }, + .Func => |funcdata| { + const apply_env = try funcdata.gen_env(args); + defer apply_env.decref(); + return EVAL(funcdata.body, apply_env, false); + }, + else => { + return MalError.ApplyError; + }, } - return MalError.TypeError; } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - core.set_allocator(Allocator); - var environment = try make_environment(); + + // Break a circular dependency between modules. + core.apply_function = &apply_function; + + try make_environment(); const args = try std.process.argsAlloc(Allocator); - var arg_list = try MalType.new_list_empty(Allocator); - for(args) |arg,i| { - if(i < 2) continue; - const new_mal = try MalType.new_string(Allocator, arg); - try arg_list.sequence_append(Allocator, new_mal); + const arg_list = try MalType.new_list(); + if(1 < args.len) { + for (args[2..]) |arg| { + const new_mal = try MalType.new_string(arg, false); + try arg_list.List.data.append(Allocator, new_mal); + } } - try environment.set("*ARGV*", arg_list); + const argv_sym = try MalType.new_symbol("*ARGV*", true); + try repl_environment.set(argv_sym, arg_list); + argv_sym.decref(); if(args.len > 1) { - const run_cmd = try string_concat(Allocator, try string_concat(Allocator, "(load-file \"", args[1]), "\")"); - var output = try rep(environment, run_cmd); + const run_cmd = try std.fmt.allocPrint(Allocator, "(load-file \"{s}\")", .{args[1]}); + try rep(false, run_cmd); return; } - while(true) { - var line = (try getline(Allocator)) orelse break; - var output = rep_and_print_errors(environment, line) orelse continue; - try stdout_file.write(output); - Allocator.free(output); - Allocator.free(line); - try stdout_file.write("\n"); + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(true, line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); + } + }; } } diff --git a/impls/zig/step7_quote.zig b/impls/zig/step7_quote.zig index 57fc0047f5..4a9b07be51 100644 --- a/impls/zig/step7_quote.zig +++ b/impls/zig/step7_quote.zig @@ -1,467 +1,433 @@ const std = @import("std"); -const warn = @import("std").debug.warn; const reader = @import("reader.zig"); -const pcre = reader.pcre; const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; -const string_concat = @import("utils.zig").string_concat; -const apply_function = @import("types.zig").apply_function; -const linked_list = @import("linked_list.zig"); +const string_eql = std.hash_map.eqlString; const hash_map = @import("hmap.zig"); const core = @import("core.zig"); const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalTypeValue = @import("types.zig").MalTypeValue; -const MalData = @import("types.zig").MalData; const MalError = @import("error.zig").MalError; -const MalFuncData = @import("types.zig").MalFuncData; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const Env = @import("env.zig").Env; +const get_error_data = @import("error.zig").get_error_data; +const throw = @import("error.zig").throw; +const stdout_file = std.io.getStdOut(); -var repl_environment: *Env = undefined; +var repl_environment = Env.new_root(); -fn READ(a: []const u8) MalError!?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } -fn EVAL(mal_arg: *MalType, env_arg: *Env) MalError!*MalType { +// Do not allocate this one on each EVAL run. +// The string is static, but will never be deallocated. +var DEBUG_EVAL = MalType { .Symbol = .{ .data = "DEBUG-EVAL" } }; + +fn EVAL(mal_arg: *MalType, env_arg: *Env, finally_destroy_env: bool) MalError!*MalType { var mal = mal_arg; var env = env_arg; + var fde = finally_destroy_env; + defer if(fde) env.decref(); while(true) { - switch(mal.data) { + + if(try env.get(&DEBUG_EVAL)) |dbgeval| { + switch (dbgeval.*) { + .Nil, .False => {}, + else => { + try stdout_file.writeAll("EVAL: "); + try PRINT(mal.*); + } + } + } + + switch(mal.*) { .List => |ll| { - if(ll.len == 0) { + const items = ll.data.items; + if(items.len == 0) { + mal.incref(); return mal; } - var first_mal = linked_list.first(&ll) orelse return MalError.ArgError; - var symbol = switch(first_mal.data) { - .Generic => |symbol| symbol, + const first_mal = items[0]; + const symbol = switch(first_mal.*) { + .Symbol => |symbol| symbol.data, else => "", }; if(string_eql(symbol, "def!")) { - return EVAL_def(mal, env); + return EVAL_def(items[1..], env); } else if(string_eql(symbol, "let*")) { - try EVAL_let(&mal, &env); + try EVAL_let(items[1..], &mal, &env, &fde); continue; } else if(string_eql(symbol, "do")) { - try EVAL_do(&mal, &env); + try EVAL_do(items[1..], &mal, env); continue; } else if(string_eql(symbol, "if")) { - try EVAL_if(&mal, &env); + try EVAL_if(items[1..], &mal, env); continue; } else if(string_eql(symbol, "fn*")) { - return EVAL_fn(mal, env); + return EVAL_fn(items[1..], env); } else if(string_eql(symbol, "quote")) { - return EVAL_quote(mal, env); - } - else if(string_eql(symbol, "quasiquoteexpand")) { - env.delete(); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - mal.delete(Allocator); - return try quasiquote(second); + return EVAL_quote(items[1..]); } else if(string_eql(symbol, "quasiquote")) { - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - mal.delete(Allocator); + if(items.len != 2) return MalError.ArgError; + const second = items[1]; mal = try quasiquote(second); continue; } else { - var new_list = try eval_ast(mal, try env.copy(Allocator)); - - if(MalTypeValue((try new_list.sequence_nth(0)).data) == MalTypeValue.Func) { - try do_user_func(try new_list.sequence_linked_list(), &mal, &env); - new_list.shallow_destroy(Allocator); - continue; + const evaluated_first = try EVAL(first_mal, env, false); + defer evaluated_first.decref(); + // A slice would be sufficient, but a List is convenient + // for partial deallocation in case of error. + const args = try MalType.new_list(); + defer args.decref(); + for(items[1..]) |x| { + const new_item = try EVAL(x, env, false); + try args.List.data.append(Allocator, new_item); + } + switch(evaluated_first.*) { + .Func => |func_data| { + if(fde) { + env.decref(); + } + else { + fde = true; + } + env = try func_data.gen_env(args.List.data.items); + mal = func_data.body; + continue; + }, + else => {}, } - const res = try apply_function(Allocator, (try new_list.sequence_linked_list()).*); - new_list.delete(Allocator); - env.delete(); - return res; + return apply_function(evaluated_first.*, args.List.data.items); } }, + .Symbol => { + return EVAL_symbol(mal, env); + }, + .Vector => |ll| { + return EVAL_vector(ll.data.items, env); + }, + .HashMap => |hmap| { + return EVAL_map(hmap.data, env); + }, else => { - return eval_ast(mal, env); + mal.incref(); + return mal; }, } } } -fn eval(a1: *MalType) MalError!*MalType { - return EVAL(try a1.copy(Allocator), try repl_environment.copy(Allocator)); +fn eval(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return EVAL(a1, &repl_environment, false); } -fn starts_with(ast: *MalType, sym: []const u8) bool { - const ll = switch(ast.data) { +fn starts_with(mal: MalType, sym: []const u8) ?*MalType { + const ll = switch(mal) { .List => |l| l, - else => return false, + else => return null, }; - if(ll.count() < 2) { - return false; + const items = ll.data.items; + if(items.len != 2) { + return null; } - const ss = switch(ll.at(0).data) { - .Generic => |s| s, - else => return false, + const ss = switch(items[0].*) { + .Symbol => |s| s, + else => return null, }; - return string_eql(ss, sym); + if(string_eql(ss.data, sym)) { + return items[1]; + } + return null; } -fn EVAL_def(mal: *MalType, env: *Env) MalError!*MalType { - const first_arg = try mal.sequence_nth(1); - const second_arg = try mal.sequence_nth(2); - const second_arg_copy = try second_arg.copy(Allocator); - const symbol_name = try first_arg.as_symbol(); - const new_value = try EVAL(second_arg_copy, try env.copy(Allocator)); +fn EVAL_def(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env, false); try env.set(symbol_name, new_value); - mal.delete(Allocator); - env.delete(); - return new_value.copy(Allocator); + new_value.incref(); + return new_value; } -fn EVAL_let(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - //TODO: make faster - const mal = mal_ptr.*; +fn EVAL_let(args: []*MalType, mal_ptr: **MalType, env_ptr: **Env, fde: *bool) !void { + if(args.len != 2) return MalError.ArgError; const env = env_ptr.*; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - const binding_arg = try mal.sequence_pop_first(Allocator); - const eval_arg = try mal.sequence_pop_first(Allocator); - const new_env = try Env.new(Allocator, env); - var binding_ll = switch(binding_arg.data) { - .List => |l| l, - .Vector => |v| v, - else => return MalError.TypeError, - }; - var iterator = binding_ll.iterator(); - var optional_node = iterator.next(); - while(optional_node) |node| { - const key_mal = node; - const key = try key_mal.as_symbol(); - const val_mal = iterator.next() orelse return MalError.ArgError; - const evaled_mal = try EVAL(val_mal, try new_env.copy(Allocator)); + const binding_arg = args[0]; + const eval_arg = args[1]; + const binds = try binding_arg.as_slice(); + if(binds.len % 2 != 0) return MalError.ArgError; + const new_env = try Env.new(env); + // Change env and fde in case an error occurs later in this procedure + // and fde triggers an env.decref() at the exit of EVAL. + if(!fde.*) { + env.incref(); + fde.* = true; + } + env_ptr.* = new_env; + for(0..binds.len / 2) |i| { + const key = binds[2*i]; + const val_mal = binds[2*i + 1]; + const evaled_mal = try EVAL(val_mal, new_env, false); + errdefer evaled_mal.decref(); try new_env.set(key, evaled_mal); - optional_node = iterator.next(); - key_mal.delete(Allocator); + // Do not increment the refcount for the value. } - - linked_list.destroy(Allocator, &binding_ll, true); - binding_arg.data = MalData{.Nil=undefined}; - binding_arg.delete(Allocator); - mal.delete(Allocator); - - // We use eval_arg_copy, since we just deleted eval_arg above mal_ptr.* = eval_arg; - env.delete(); - env_ptr.* = new_env; } -fn EVAL_do(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - var ll = &mal.data.List; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var last_mal = try mal.sequence_pop_last(Allocator); - var evaled_mal = try eval_ast(mal, try env.copy(Allocator)); - evaled_mal.delete(Allocator); +fn EVAL_do(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len == 0) return MalError.ArgError; + const last_mal = args[args.len - 1]; + for (args[0..args.len - 1]) |form| { + const item = try EVAL(form, env, false); + item.decref(); + } mal_ptr.* = last_mal; } -fn EVAL_if(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - defer mal.delete(Allocator); - const first_arg = try mal.sequence_nth(1); - const first_arg_copy = try first_arg.copy(Allocator); - const evaled = try EVAL(first_arg_copy, try env.copy(Allocator)); - const is_true = switch(evaled.data) { +fn EVAL_if(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len != 2 and args.len != 3) return MalError.ArgError; + const first_arg = args[0]; + const evaled = try EVAL(first_arg, env, false); + const is_true = switch(evaled.*) { .False => false, .Nil => false, else => true, }; - evaled.delete(Allocator); + evaled.decref(); if(is_true) { - const second_arg = try mal.sequence_nth(2); - mal_ptr.* = try second_arg.copy(Allocator); + const second_arg = args[1]; + mal_ptr.* = second_arg; return; } - if((try mal.sequence_length()) < 4) { - mal_ptr.* = try MalType.new_nil(Allocator); + if(args.len == 2) { + mal_ptr.* = &MalType.NIL; return; } - const third_arg = try mal.sequence_nth(3); - const third_arg_copy = try third_arg.copy(Allocator); - mal_ptr.* = third_arg_copy; + const third_arg = args[2]; + mal_ptr.* = third_arg; } -fn EVAL_fn(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - const arg_mal = try (try mal.sequence_nth(1)).copy(Allocator); - const body_mal = try (try mal.sequence_nth(2)).copy(Allocator); - const func_data = MalFuncData { - .arg_list = arg_mal, - .body = body_mal, - .environment = env, - .is_macro = false, - .eval_func = &EVAL, - }; - const new_func = try MalType.new_nil(Allocator); - new_func.data = MalData{.Func = func_data}; +fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const arg_mal = args[0]; + const body_mal = args[1]; + for (try arg_mal.as_slice()) |x| { + switch (x.*) { + .Symbol => {}, + else => return MalError.TypeError, + } + } + const new_func = try MalType.newFunc(arg_mal, body_mal, env); + arg_mal.incref(); + body_mal.incref(); + env.incref(); return new_func; } -fn EVAL_quote(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - defer env.delete(); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - return try mal.sequence_pop_first(Allocator); +fn EVAL_quote(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const quoted = args[0]; + quoted.incref(); + return quoted; } fn quasiquote(ast: *MalType) MalError!*MalType { - const kind = MalTypeValue(ast.data); - if(kind == MalTypeValue.Generic or kind == MalTypeValue.HashMap) { - const new_list = try MalType.new_list_empty(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "quote")); - try new_list.sequence_append(Allocator, ast); + switch (ast.*) { + .Symbol, .HashMap => { + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + try new_list.List.data.append(Allocator, try MalType.new_symbol("quote", true)); + try new_list.List.data.append(Allocator, ast); + ast.incref(); return new_list; - } - - if(kind != MalTypeValue.List and kind != MalTypeValue.Vector) { + }, + .List => |l| { + if(starts_with(ast.*, "unquote")) |unquoted| { + unquoted.incref(); + return unquoted; + } + return try qq_loop(l.data.items); + }, + .Vector => |l| { + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + try new_list.List.data.append(Allocator, try MalType.new_symbol("vec", true)); + try new_list.List.data.append(Allocator, try qq_loop(l.data.items)); + return new_list; + }, + else => { + ast.incref(); return ast; - } - - defer ast.delete(Allocator); - - if(starts_with(ast, "unquote")) { - (try ast.sequence_pop_first(Allocator)).delete(Allocator); - return ast.sequence_pop_first(Allocator); - } + }, + } +} - var result = try MalType.new_list_empty(Allocator); - while(0 < (try ast.sequence_length())) { - var elt = try ast.sequence_pop_last(Allocator); - const new_list = try MalType.new_list_empty(Allocator); - if(starts_with(elt, "splice-unquote")) { - (try elt.sequence_pop_first(Allocator)).delete(Allocator); - defer elt.delete(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "concat")); - try new_list.sequence_append(Allocator, try elt.sequence_pop_first(Allocator)); - } else { - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "cons")); - try new_list.sequence_append(Allocator, try quasiquote(elt)); +fn qq_loop(items: []*MalType) !*MalType { + var result = try MalType.new_list(); + errdefer result.decref(); + for (0..items.len) |i| { + const elt = items[items.len - 1 - i]; + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + if(starts_with(elt.*, "splice-unquote")) |unquoted| { + try new_list.List.data.append(Allocator, try MalType.new_symbol("concat", true)); + try new_list.List.data.append(Allocator, unquoted); + unquoted.incref(); } - try new_list.sequence_append(Allocator, result); - result = new_list; - } - - if(kind == MalTypeValue.Vector) { - const new_list = try MalType.new_list_empty(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "vec")); - try new_list.sequence_append(Allocator, result); + else { + try new_list.List.data.append(Allocator, try MalType.new_symbol("cons", true)); + try new_list.List.data.append(Allocator, try quasiquote(elt)); + } + try new_list.List.data.append(Allocator, result); result = new_list; } return result; } -fn PRINT(optional_mal: ?*MalType) MalError![] u8 { - return printer.print_str(optional_mal); -} - -fn rep(environment: *Env, input: [] const u8) MalError!?[] u8 { - var read_input = (try READ(input)) orelse return null; - var eval_input = try EVAL(read_input, try environment.copy(Allocator)); - var print_input = try PRINT(eval_input); - eval_input.delete(Allocator); - return print_input; +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep_and_print_errors(environment: *Env, input: [] const u8) ?[]u8 { - return rep(environment, input) catch |err| { - switch(err) { - MalError.KeyError => { }, - MalError.OutOfBounds => { - warn("Error: out of bounds\n"); - }, - MalError.ReaderUnmatchedParen => { - warn("Error: expected closing paren, got EOF\n"); - }, - else => { - warn("Unhandled error\n"); - }, - } - return null; - }; +fn rep(print: bool, input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = try EVAL(read_input, &repl_environment, false); + defer eval_input.decref(); + if(print) { + try PRINT(eval_input.*); + } } - -fn lookup(environment: *Env, symbol: []const u8, do_warn: bool) MalError!*MalType { - var mal = environment.get(symbol) catch |err| { - if(do_warn) { - const s1 = string_concat(Allocator, "'", symbol) catch return MalError.SystemError; - const s2 = string_concat(Allocator, s1, "' not found") catch return MalError.SystemError; - defer Allocator.free(s1); - defer Allocator.free(s2); - warn("'{}' not found.\n", symbol); - } - return MalError.KeyError; - }; - var new_mal = try mal.copy(Allocator); - return new_mal; +fn EVAL_symbol(mal: *MalType, env: *Env) !*MalType { + if(try env.get(mal)) |value| { + value.incref(); + return value; + } + const err = try std.fmt.allocPrint(Allocator, "'{s}' not found", + .{mal.Symbol.data}); + return throw(try MalType.new_string(err, false)); } -fn eval_ast(mal: *MalType, env: *Env) MalError!*MalType { - defer env.delete(); - switch(mal.data) { - .Generic => |symbol| { - defer mal.delete(Allocator); - return lookup(env, symbol, true); - }, - .List => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); - } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_list(Allocator, new_ll); - return ret_mal; - }, - .Vector => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn EVAL_vector(ll: []*MalType, env: *Env) !*MalType { + const ret_mal = try MalType.new_vector(); + errdefer ret_mal.decref(); + for(ll) |x| { + const new_mal = try EVAL(x, env, false); + try ret_mal.Vector.data.append(Allocator, new_mal); } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_vector(Allocator, new_ll); return ret_mal; - }, - .HashMap => |hmap| { - var new_hashmap = try MalType.new_hashmap(Allocator); +} + +fn EVAL_map(hmap: hash_map.MalHashMap, env: *Env) !*MalType { + const new_hashmap = try MalType.new_hashmap(); + errdefer new_hashmap.decref(); var iterator = hmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = pair.key; - const value = pair.value; - const evaled_value = try EVAL(value, try env.copy(Allocator)); - try new_hashmap.hashmap_insert(key, evaled_value); - optional_pair = iterator.next(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + const evaled_value = try EVAL(value, env, false); + try hash_map.map_insert_incref_key(&new_hashmap.HashMap.data, key, evaled_value); } - hash_map.destroy(Allocator, hmap, true); - mal.shallow_destroy(Allocator); return new_hashmap; - }, - else => { - return mal; - } - } } -fn make_environment() MalError!*Env { - repl_environment = try Env.new(Allocator, null); - var environment = try repl_environment.copy(Allocator); +fn make_environment() !void { for(core.core_namespace) |pair| { - const name = pair.name; - const func_mal: *MalType = try MalType.new_nil(Allocator); - func_mal.data = switch(pair.func) { - core.CorePairType.Fn0 => |func| MalData{.Fn0 = func}, - core.CorePairType.Fn1 => |func| MalData{.Fn1 = func}, - core.CorePairType.Fn2 => |func| MalData{.Fn2 = func}, - core.CorePairType.FVar => |func| MalData{.FVar = func}, - else => return MalError.TypeError, - }; - try environment.set(name, func_mal); + const name = try MalType.new_symbol(pair.name, true); + const func_mal = try MalType.newFnCore(pair.func); + try repl_environment.set(name, func_mal); + name.decref(); } - const eval_mal = try MalType.new_nil(Allocator); - eval_mal.data = MalData{.Fn1 = &eval}; - try environment.set("eval", eval_mal); + const eval_sym = try MalType.new_symbol("eval", true); + const eval_mal = try MalType.newFnCore(eval); + try repl_environment.set(eval_sym, eval_mal); + eval_sym.decref(); const def_not_string: [] const u8 = \\(def! not (fn* (a) (if a false true))) ; - var optional_output = try rep(environment, def_not_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_not_string); const load_file_string: [] const u8 = \\(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) ; - optional_output = try rep(environment, load_file_string); - if(optional_output) |output| { - Allocator.free(output); - } - - return environment; + try rep(false, load_file_string); } -fn do_user_func(args: *MalLinkedList, mal_ptr: **MalType, env_ptr: **Env) MalError!void { - const mal_func = try linked_list.pop_first(Allocator, args); - const env = env_ptr.*; - // 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; - var new_env = try Env.new(Allocator, func_env); - func_env.delete(); - try new_env.set_list(args_ll.*, args.*); - linked_list.destroy(Allocator, args, true); - func_data.arg_list.delete(Allocator); - mal_func.shallow_destroy(Allocator); - mal_ptr.* = func_data.body; - env.delete(); - env_ptr.* = new_env; - return; +pub fn apply_function(f: MalType, args: []*MalType) MalError!*MalType { + + switch(f) { + .FnCore => |fncoredata| { + return fncoredata.data(args); + }, + .Func => |funcdata| { + const apply_env = try funcdata.gen_env(args); + defer apply_env.decref(); + return EVAL(funcdata.body, apply_env, false); + }, + else => { + return MalError.ApplyError; + }, } - return MalError.TypeError; } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - core.set_allocator(Allocator); - var environment = try make_environment(); + + // Break a circular dependency between modules. + core.apply_function = &apply_function; + + try make_environment(); const args = try std.process.argsAlloc(Allocator); - var arg_list = try MalType.new_list_empty(Allocator); - for(args) |arg,i| { - if(i < 2) continue; - const new_mal = try MalType.new_string(Allocator, arg); - try arg_list.sequence_append(Allocator, new_mal); + const arg_list = try MalType.new_list(); + if(1 < args.len) { + for (args[2..]) |arg| { + const new_mal = try MalType.new_string(arg, false); + try arg_list.List.data.append(Allocator, new_mal); + } } - try environment.set("*ARGV*", arg_list); + const argv_sym = try MalType.new_symbol("*ARGV*", true); + try repl_environment.set(argv_sym, arg_list); + argv_sym.decref(); if(args.len > 1) { - const run_cmd = try string_concat(Allocator, try string_concat(Allocator, "(load-file \"", args[1]), "\")"); - var output = try rep(environment, run_cmd); + const run_cmd = try std.fmt.allocPrint(Allocator, "(load-file \"{s}\")", .{args[1]}); + try rep(false, run_cmd); return; } - while(true) { - var line = (try getline(Allocator)) orelse break; - var output = rep_and_print_errors(environment, line) orelse continue; - try stdout_file.write(output); - Allocator.free(output); - Allocator.free(line); - try stdout_file.write("\n"); + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(true, line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); + } + }; } } diff --git a/impls/zig/step8_macros.zig b/impls/zig/step8_macros.zig index 6a17f169dd..66c4891538 100644 --- a/impls/zig/step8_macros.zig +++ b/impls/zig/step8_macros.zig @@ -1,535 +1,471 @@ const std = @import("std"); -const warn = @import("std").debug.warn; const reader = @import("reader.zig"); -const pcre = reader.pcre; const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; -const string_concat = @import("utils.zig").string_concat; -const apply_function = @import("types.zig").apply_function; -const linked_list = @import("linked_list.zig"); +const string_eql = std.hash_map.eqlString; const hash_map = @import("hmap.zig"); const core = @import("core.zig"); const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalTypeValue = @import("types.zig").MalTypeValue; -const MalData = @import("types.zig").MalData; const MalError = @import("error.zig").MalError; -const MalFuncData = @import("types.zig").MalFuncData; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const Env = @import("env.zig").Env; +const get_error_data = @import("error.zig").get_error_data; +const throw = @import("error.zig").throw; +const stdout_file = std.io.getStdOut(); -var repl_environment: *Env = undefined; +var repl_environment = Env.new_root(); -fn READ(a: []const u8) MalError!?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } -fn EVAL(mal_arg: *MalType, env_arg: *Env) MalError!*MalType { +// Do not allocate this one on each EVAL run. +// The string is static, but will never be deallocated. +var DEBUG_EVAL = MalType { .Symbol = .{ .data = "DEBUG-EVAL" } }; + +fn EVAL(mal_arg: *MalType, env_arg: *Env, finally_destroy_env: bool) MalError!*MalType { var mal = mal_arg; var env = env_arg; + var fde = finally_destroy_env; + defer if(fde) env.decref(); while(true) { - mal = try macroexpand(mal, env); - switch(mal.data) { + + if(try env.get(&DEBUG_EVAL)) |dbgeval| { + switch (dbgeval.*) { + .Nil, .False => {}, + else => { + try stdout_file.writeAll("EVAL: "); + try PRINT(mal.*); + } + } + } + + switch(mal.*) { .List => |ll| { - if(ll.len == 0) { + const items = ll.data.items; + if(items.len == 0) { + mal.incref(); return mal; } - var first_mal = linked_list.first(&ll) orelse return MalError.ArgError; - var symbol = switch(first_mal.data) { - .Generic => |symbol| symbol, + const first_mal = items[0]; + const symbol = switch(first_mal.*) { + .Symbol => |symbol| symbol.data, else => "", }; if(string_eql(symbol, "def!")) { - return EVAL_def(mal, env, false); + return EVAL_def(items[1..], env); } else if(string_eql(symbol, "defmacro!")) { - return EVAL_def(mal, env, true); + return EVAL_defmacro(items[1..], env); } else if(string_eql(symbol, "let*")) { - try EVAL_let(&mal, &env); + try EVAL_let(items[1..], &mal, &env, &fde); continue; } else if(string_eql(symbol, "do")) { - try EVAL_do(&mal, &env); + try EVAL_do(items[1..], &mal, env); continue; } else if(string_eql(symbol, "if")) { - try EVAL_if(&mal, &env); + try EVAL_if(items[1..], &mal, env); continue; } else if(string_eql(symbol, "fn*")) { - return EVAL_fn(mal, env); + return EVAL_fn(items[1..], env); } else if(string_eql(symbol, "quote")) { - return EVAL_quote(mal, env); - } - else if(string_eql(symbol, "quasiquoteexpand")) { - env.delete(); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - mal.delete(Allocator); - return try quasiquote(second); + return EVAL_quote(items[1..]); } else if(string_eql(symbol, "quasiquote")) { - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - mal.delete(Allocator); + if(items.len != 2) return MalError.ArgError; + const second = items[1]; mal = try quasiquote(second); continue; } - else if(string_eql(symbol, "macroexpand")) { - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - const expanded = macroexpand(second, env); - env.delete(); - return expanded; - } else { - var new_list = try eval_ast(mal, try env.copy(Allocator)); - - if(MalTypeValue((try new_list.sequence_nth(0)).data) == MalTypeValue.Func) { - try do_user_func(try new_list.sequence_linked_list(), &mal, &env); - new_list.shallow_destroy(Allocator); - continue; + const evaluated_first = try EVAL(first_mal, env, false); + defer evaluated_first.decref(); + switch (evaluated_first.*) { + .Func => |func_data| { + if(func_data.is_macro) { + mal = try apply_function(evaluated_first.*, items[1..]); + continue; + } + }, + else => {} + } + // A slice would be sufficient, but a List is convenient + // for partial deallocation in case of error. + const args = try MalType.new_list(); + defer args.decref(); + for(items[1..]) |x| { + const new_item = try EVAL(x, env, false); + try args.List.data.append(Allocator, new_item); } - const res = try apply_function(Allocator, (try new_list.sequence_linked_list()).*); - new_list.delete(Allocator); - env.delete(); - return res; + switch(evaluated_first.*) { + .Func => |func_data| { + if(fde) { + env.decref(); + } + else { + fde = true; + } + env = try func_data.gen_env(args.List.data.items); + mal = func_data.body; + continue; + }, + else => {}, + } + return apply_function(evaluated_first.*, args.List.data.items); } }, + .Symbol => { + return EVAL_symbol(mal, env); + }, + .Vector => |ll| { + return EVAL_vector(ll.data.items, env); + }, + .HashMap => |hmap| { + return EVAL_map(hmap.data, env); + }, else => { - return eval_ast(mal, env); + mal.incref(); + return mal; }, } } } -fn eval(a1: *MalType) MalError!*MalType { - return EVAL(try a1.copy(Allocator), try repl_environment.copy(Allocator)); -} - -fn starts_with(ast: *MalType, sym: []const u8) bool { - const ll = switch(ast.data) { - .List => |l| l, - else => return false, - }; - if(ll.count() < 2) { - return false; - } - const ss = switch(ll.at(0).data) { - .Generic => |s| s, - else => return false, - }; - return string_eql(ss, sym); +fn eval(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return EVAL(a1, &repl_environment, false); } -fn is_macro_call(mal: *MalType, env: *Env) ?*MalType { - const ll = switch(mal.data) { +fn starts_with(mal: MalType, sym: []const u8) ?*MalType { + const ll = switch(mal) { .List => |l| l, else => return null, }; - const first_node = linked_list.first(&ll) orelse return null; - const symbol = switch(first_node.data) { - .Generic => |s| s, + const items = ll.data.items; + if(items.len != 2) { + return null; + } + const ss = switch(items[0].*) { + .Symbol => |s| s, else => return null, }; - const val = lookup(env, symbol, false) catch return null; - const is_macro = switch(val.data) { - .Func => |f| f.is_macro, - else => false, - }; - if(is_macro) { - return val; + if(string_eql(ss.data, sym)) { + return items[1]; } - val.delete(Allocator); return null; } -fn macroexpand(mal: *MalType, env: *Env) MalError!*MalType { - var cur_mal = mal; - var optional_macro = is_macro_call(cur_mal, env); - while(optional_macro) |macro| { - var new_list = (try cur_mal.sequence_linked_list()).*; - - if(new_list.count() > 0) { - const first = try linked_list.pop_first(Allocator, &new_list); - first.delete(Allocator); - } - try linked_list.prepend_mal(Allocator, &new_list, macro); - var new_mal = try apply_function(Allocator, new_list); - linked_list.destroy(Allocator, &new_list, false); - cur_mal.shallow_destroy(Allocator); - cur_mal = new_mal; - optional_macro = is_macro_call(cur_mal, env); - } - return cur_mal; -} - -fn EVAL_def(mal: *MalType, env: *Env, macro: bool) MalError!*MalType { - const first_arg = try mal.sequence_nth(1); - const second_arg = try mal.sequence_nth(2); - const second_arg_copy = try second_arg.copy(Allocator); - const symbol_name = try first_arg.as_symbol(); - const new_value = try EVAL(second_arg_copy, try env.copy(Allocator)); - if(macro) { - var func_data = switch(new_value.data) { - .Func => |*f| f, - else => return MalError.TypeError, - }; - func_data.*.is_macro = true; - } +fn EVAL_def(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env, false); try env.set(symbol_name, new_value); - mal.delete(Allocator); - env.delete(); - return new_value.copy(Allocator); + new_value.incref(); + return new_value; } -fn EVAL_let(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - //TODO: make faster - const mal = mal_ptr.*; - const env = env_ptr.*; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - const binding_arg = try mal.sequence_pop_first(Allocator); - const eval_arg = try mal.sequence_pop_first(Allocator); - const new_env = try Env.new(Allocator, env); - var binding_ll = switch(binding_arg.data) { - .List => |l| l, - .Vector => |v| v, +fn EVAL_defmacro(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env, false); + errdefer new_value.decref(); + const f = switch (new_value.*) { + .Func => |func_data| func_data, else => return MalError.TypeError, }; - var iterator = binding_ll.iterator(); - var optional_node = iterator.next(); - while(optional_node) |node| { - const key_mal = node; - const key = try key_mal.as_symbol(); - const val_mal = iterator.next() orelse return MalError.ArgError; - const evaled_mal = try EVAL(val_mal, try new_env.copy(Allocator)); + const macro = try MalType.newFunc(f.arg_list, f.body, f.environment); + f.arg_list.incref(); + f.body.incref(); + f.environment.incref(); + macro.Func.is_macro = true; + try env.set(symbol_name, macro); + macro.incref(); + return macro; +} + +fn EVAL_let(args: []*MalType, mal_ptr: **MalType, env_ptr: **Env, fde: *bool) !void { + if(args.len != 2) return MalError.ArgError; + const env = env_ptr.*; + const binding_arg = args[0]; + const eval_arg = args[1]; + const binds = try binding_arg.as_slice(); + if(binds.len % 2 != 0) return MalError.ArgError; + const new_env = try Env.new(env); + // Change env and fde in case an error occurs later in this procedure + // and fde triggers an env.decref() at the exit of EVAL. + if(!fde.*) { + env.incref(); + fde.* = true; + } + env_ptr.* = new_env; + for(0..binds.len / 2) |i| { + const key = binds[2*i]; + const val_mal = binds[2*i + 1]; + const evaled_mal = try EVAL(val_mal, new_env, false); + errdefer evaled_mal.decref(); try new_env.set(key, evaled_mal); - optional_node = iterator.next(); - key_mal.delete(Allocator); + // Do not increment the refcount for the value. } - - linked_list.destroy(Allocator, &binding_ll, true); - binding_arg.data = MalData{.Nil=undefined}; - binding_arg.delete(Allocator); - mal.delete(Allocator); - - // We use eval_arg_copy, since we just deleted eval_arg above mal_ptr.* = eval_arg; - env.delete(); - env_ptr.* = new_env; } -fn EVAL_do(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - var ll = &mal.data.List; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var last_mal = try mal.sequence_pop_last(Allocator); - var evaled_mal = try eval_ast(mal, try env.copy(Allocator)); - evaled_mal.delete(Allocator); +fn EVAL_do(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len == 0) return MalError.ArgError; + const last_mal = args[args.len - 1]; + for (args[0..args.len - 1]) |form| { + const item = try EVAL(form, env, false); + item.decref(); + } mal_ptr.* = last_mal; } -fn EVAL_if(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - defer mal.delete(Allocator); - const first_arg = try mal.sequence_nth(1); - const first_arg_copy = try first_arg.copy(Allocator); - const evaled = try EVAL(first_arg_copy, try env.copy(Allocator)); - const is_true = switch(evaled.data) { +fn EVAL_if(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len != 2 and args.len != 3) return MalError.ArgError; + const first_arg = args[0]; + const evaled = try EVAL(first_arg, env, false); + const is_true = switch(evaled.*) { .False => false, .Nil => false, else => true, }; - evaled.delete(Allocator); + evaled.decref(); if(is_true) { - const second_arg = try mal.sequence_nth(2); - mal_ptr.* = try second_arg.copy(Allocator); + const second_arg = args[1]; + mal_ptr.* = second_arg; return; } - if((try mal.sequence_length()) < 4) { - mal_ptr.* = try MalType.new_nil(Allocator); + if(args.len == 2) { + mal_ptr.* = &MalType.NIL; return; } - const third_arg = try mal.sequence_nth(3); - const third_arg_copy = try third_arg.copy(Allocator); - mal_ptr.* = third_arg_copy; + const third_arg = args[2]; + mal_ptr.* = third_arg; } -fn EVAL_fn(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - const arg_mal = try (try mal.sequence_nth(1)).copy(Allocator); - const body_mal = try (try mal.sequence_nth(2)).copy(Allocator); - const func_data = MalFuncData { - .arg_list = arg_mal, - .body = body_mal, - .environment = env, - .is_macro = false, - .eval_func = &EVAL, - }; - const new_func = try MalType.new_nil(Allocator); - new_func.data = MalData{.Func = func_data}; +fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const arg_mal = args[0]; + const body_mal = args[1]; + for (try arg_mal.as_slice()) |x| { + switch (x.*) { + .Symbol => {}, + else => return MalError.TypeError, + } + } + const new_func = try MalType.newFunc(arg_mal, body_mal, env); + arg_mal.incref(); + body_mal.incref(); + env.incref(); return new_func; } -fn EVAL_quote(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - defer env.delete(); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - return try mal.sequence_pop_first(Allocator); +fn EVAL_quote(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const quoted = args[0]; + quoted.incref(); + return quoted; } fn quasiquote(ast: *MalType) MalError!*MalType { - const kind = MalTypeValue(ast.data); - if(kind == MalTypeValue.Generic or kind == MalTypeValue.HashMap) { - const new_list = try MalType.new_list_empty(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "quote")); - try new_list.sequence_append(Allocator, ast); + switch (ast.*) { + .Symbol, .HashMap => { + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + try new_list.List.data.append(Allocator, try MalType.new_symbol("quote", true)); + try new_list.List.data.append(Allocator, ast); + ast.incref(); return new_list; - } - - if(kind != MalTypeValue.List and kind != MalTypeValue.Vector) { + }, + .List => |l| { + if(starts_with(ast.*, "unquote")) |unquoted| { + unquoted.incref(); + return unquoted; + } + return try qq_loop(l.data.items); + }, + .Vector => |l| { + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + try new_list.List.data.append(Allocator, try MalType.new_symbol("vec", true)); + try new_list.List.data.append(Allocator, try qq_loop(l.data.items)); + return new_list; + }, + else => { + ast.incref(); return ast; - } - - defer ast.delete(Allocator); - - if(starts_with(ast, "unquote")) { - (try ast.sequence_pop_first(Allocator)).delete(Allocator); - return ast.sequence_pop_first(Allocator); - } + }, + } +} - var result = try MalType.new_list_empty(Allocator); - while(0 < (try ast.sequence_length())) { - var elt = try ast.sequence_pop_last(Allocator); - const new_list = try MalType.new_list_empty(Allocator); - if(starts_with(elt, "splice-unquote")) { - (try elt.sequence_pop_first(Allocator)).delete(Allocator); - defer elt.delete(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "concat")); - try new_list.sequence_append(Allocator, try elt.sequence_pop_first(Allocator)); - } else { - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "cons")); - try new_list.sequence_append(Allocator, try quasiquote(elt)); +fn qq_loop(items: []*MalType) !*MalType { + var result = try MalType.new_list(); + errdefer result.decref(); + for (0..items.len) |i| { + const elt = items[items.len - 1 - i]; + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + if(starts_with(elt.*, "splice-unquote")) |unquoted| { + try new_list.List.data.append(Allocator, try MalType.new_symbol("concat", true)); + try new_list.List.data.append(Allocator, unquoted); + unquoted.incref(); } - try new_list.sequence_append(Allocator, result); - result = new_list; - } - - if(kind == MalTypeValue.Vector) { - const new_list = try MalType.new_list_empty(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "vec")); - try new_list.sequence_append(Allocator, result); + else { + try new_list.List.data.append(Allocator, try MalType.new_symbol("cons", true)); + try new_list.List.data.append(Allocator, try quasiquote(elt)); + } + try new_list.List.data.append(Allocator, result); result = new_list; } return result; } -fn PRINT(optional_mal: ?*MalType) MalError![] u8 { - return printer.print_str(optional_mal); +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep(environment: *Env, input: [] const u8) MalError!?[] u8 { - var read_input = (try READ(input)) orelse return null; - var eval_input = try EVAL(read_input, try environment.copy(Allocator)); - var print_input = try PRINT(eval_input); - eval_input.delete(Allocator); - return print_input; -} - -fn rep_and_print_errors(environment: *Env, input: [] const u8) ?[]u8 { - return rep(environment, input) catch |err| { - switch(err) { - MalError.KeyError => { }, - MalError.OutOfBounds => { - warn("Error: out of bounds\n"); - }, - MalError.ReaderUnmatchedParen => { - warn("Error: expected closing paren, got EOF\n"); - }, - else => { - warn("Unhandled error\n"); - }, - } - return null; - }; +fn rep(print: bool, input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = try EVAL(read_input, &repl_environment, false); + defer eval_input.decref(); + if(print) { + try PRINT(eval_input.*); + } } - -fn lookup(environment: *Env, symbol: []const u8, do_warn: bool) MalError!*MalType { - var mal = environment.get(symbol) catch |err| { - if(do_warn) { - const s1 = string_concat(Allocator, "'", symbol) catch return MalError.SystemError; - const s2 = string_concat(Allocator, s1, "' not found") catch return MalError.SystemError; - defer Allocator.free(s1); - defer Allocator.free(s2); - warn("'{}' not found.\n", symbol); - } - return MalError.KeyError; - }; - var new_mal = try mal.copy(Allocator); - return new_mal; +fn EVAL_symbol(mal: *MalType, env: *Env) !*MalType { + if(try env.get(mal)) |value| { + value.incref(); + return value; + } + const err = try std.fmt.allocPrint(Allocator, "'{s}' not found", + .{mal.Symbol.data}); + return throw(try MalType.new_string(err, false)); } -fn eval_ast(mal: *MalType, env: *Env) MalError!*MalType { - defer env.delete(); - switch(mal.data) { - .Generic => |symbol| { - defer mal.delete(Allocator); - return lookup(env, symbol, true); - }, - .List => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); - } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_list(Allocator, new_ll); - return ret_mal; - }, - .Vector => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn EVAL_vector(ll: []*MalType, env: *Env) !*MalType { + const ret_mal = try MalType.new_vector(); + errdefer ret_mal.decref(); + for(ll) |x| { + const new_mal = try EVAL(x, env, false); + try ret_mal.Vector.data.append(Allocator, new_mal); } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_vector(Allocator, new_ll); return ret_mal; - }, - .HashMap => |hmap| { - var new_hashmap = try MalType.new_hashmap(Allocator); +} + +fn EVAL_map(hmap: hash_map.MalHashMap, env: *Env) !*MalType { + const new_hashmap = try MalType.new_hashmap(); + errdefer new_hashmap.decref(); var iterator = hmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = pair.key; - const value = pair.value; - const evaled_value = try EVAL(value, try env.copy(Allocator)); - try new_hashmap.hashmap_insert(key, evaled_value); - optional_pair = iterator.next(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + const evaled_value = try EVAL(value, env, false); + try hash_map.map_insert_incref_key(&new_hashmap.HashMap.data, key, evaled_value); } - hash_map.destroy(Allocator, hmap, true); - mal.shallow_destroy(Allocator); return new_hashmap; - }, - else => { - return mal; - } - } } -fn make_environment() MalError!*Env { - repl_environment = try Env.new(Allocator, null); - var environment = try repl_environment.copy(Allocator); +fn make_environment() !void { for(core.core_namespace) |pair| { - const name = pair.name; - const func_mal: *MalType = try MalType.new_nil(Allocator); - func_mal.data = switch(pair.func) { - core.CorePairType.Fn0 => |func| MalData{.Fn0 = func}, - core.CorePairType.Fn1 => |func| MalData{.Fn1 = func}, - core.CorePairType.Fn2 => |func| MalData{.Fn2 = func}, - core.CorePairType.FVar => |func| MalData{.FVar = func}, - else => return MalError.TypeError, - }; - try environment.set(name, func_mal); + const name = try MalType.new_symbol(pair.name, true); + const func_mal = try MalType.newFnCore(pair.func); + try repl_environment.set(name, func_mal); + name.decref(); } - const eval_mal = try MalType.new_nil(Allocator); - eval_mal.data = MalData{.Fn1 = &eval}; - try environment.set("eval", eval_mal); - + const eval_sym = try MalType.new_symbol("eval", true); + const eval_mal = try MalType.newFnCore(eval); + try repl_environment.set(eval_sym, eval_mal); + eval_sym.decref(); + const def_not_string: [] const u8 = \\(def! not (fn* (a) (if a false true))) ; - var optional_output = try rep(environment, def_not_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_not_string); const load_file_string: [] const u8 = \\(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) ; - optional_output = try rep(environment, load_file_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, load_file_string); const def_cond_macro_string: [] const u8 = \\(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))))))) ; - optional_output = try rep(environment, def_cond_macro_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_cond_macro_string); - return environment; } -fn do_user_func(args: *MalLinkedList, mal_ptr: **MalType, env_ptr: **Env) MalError!void { - const mal_func = try linked_list.pop_first(Allocator, args); - const env = env_ptr.*; - // 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; - var new_env = try Env.new(Allocator, func_env); - func_env.delete(); - try new_env.set_list(args_ll.*, args.*); - linked_list.destroy(Allocator, args, true); - func_data.arg_list.delete(Allocator); - mal_func.shallow_destroy(Allocator); - mal_ptr.* = func_data.body; - env.delete(); - env_ptr.* = new_env; - return; +pub fn apply_function(f: MalType, args: []*MalType) MalError!*MalType { + + switch(f) { + .FnCore => |fncoredata| { + return fncoredata.data(args); + }, + .Func => |funcdata| { + const apply_env = try funcdata.gen_env(args); + defer apply_env.decref(); + return EVAL(funcdata.body, apply_env, false); + }, + else => { + return MalError.ApplyError; + }, } - return MalError.TypeError; } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - core.set_allocator(Allocator); - var environment = try make_environment(); + + // Break a circular dependency between modules. + core.apply_function = &apply_function; + + try make_environment(); const args = try std.process.argsAlloc(Allocator); - var arg_list = try MalType.new_list_empty(Allocator); - for(args) |arg,i| { - if(i < 2) continue; - const new_mal = try MalType.new_string(Allocator, arg); - try arg_list.sequence_append(Allocator, new_mal); + const arg_list = try MalType.new_list(); + if(1 < args.len) { + for (args[2..]) |arg| { + const new_mal = try MalType.new_string(arg, false); + try arg_list.List.data.append(Allocator, new_mal); + } } - try environment.set("*ARGV*", arg_list); + const argv_sym = try MalType.new_symbol("*ARGV*", true); + try repl_environment.set(argv_sym, arg_list); + argv_sym.decref(); if(args.len > 1) { - const run_cmd = try string_concat(Allocator, try string_concat(Allocator, "(load-file \"", args[1]), "\")"); - var output = rep_and_print_errors(environment, run_cmd); + const run_cmd = try std.fmt.allocPrint(Allocator, "(load-file \"{s}\")", .{args[1]}); + try rep(false, run_cmd); return; } - - while(true) { - var line = (try getline(Allocator)) orelse break; - var output = rep_and_print_errors(environment, line) orelse continue; - try stdout_file.write(output); - Allocator.free(output); - Allocator.free(line); - try stdout_file.write("\n"); + + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(true, line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); + } + }; } } diff --git a/impls/zig/step9_try.zig b/impls/zig/step9_try.zig index 8b65bae47e..d4fd546855 100644 --- a/impls/zig/step9_try.zig +++ b/impls/zig/step9_try.zig @@ -1,612 +1,508 @@ const std = @import("std"); -const warn = @import("std").debug.warn; const reader = @import("reader.zig"); const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; -const string_concat = @import("utils.zig").string_concat; -const apply_function_unsafe = @import("types.zig").apply_function; //hack -const linked_list = @import("linked_list.zig"); +const string_eql = std.hash_map.eqlString; const hash_map = @import("hmap.zig"); const core = @import("core.zig"); -const error_string_repr = @import("error.zig").error_string_repr; -const CAllocator = @import("std").heap.c_allocator; -const AllocatorType = @import("std").mem.Allocator; -pub var Allocator: *AllocatorType = undefined; +const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalTypeValue = @import("types.zig").MalTypeValue; -const MalData = @import("types.zig").MalData; const MalError = @import("error.zig").MalError; -const MalFuncData = @import("types.zig").MalFuncData; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const Env = @import("env.zig").Env; +const get_error_data = @import("error.zig").get_error_data; +const throw = @import("error.zig").throw; +const stdout_file = std.io.getStdOut(); -var repl_environment: *Env = undefined; +var repl_environment = Env.new_root(); -fn READ(a: []const u8) MalError!?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } -fn EVAL(mal_arg: *MalType, env_arg: *Env) MalError!*MalType { +// Do not allocate this one on each EVAL run. +// The string is static, but will never be deallocated. +var DEBUG_EVAL = MalType { .Symbol = .{ .data = "DEBUG-EVAL" } }; + +fn EVAL(mal_arg: *MalType, env_arg: *Env, finally_destroy_env: bool) MalError!*MalType { var mal = mal_arg; var env = env_arg; + var fde = finally_destroy_env; + defer if(fde) env.decref(); while(true) { - mal = try macroexpand(mal, env); - switch(mal.data) { + + if(try env.get(&DEBUG_EVAL)) |dbgeval| { + switch (dbgeval.*) { + .Nil, .False => {}, + else => { + try stdout_file.writeAll("EVAL: "); + try PRINT(mal.*); + } + } + } + + switch(mal.*) { .List => |ll| { - if(ll.len == 0) { - env.delete(); + const items = ll.data.items; + if(items.len == 0) { + mal.incref(); return mal; } - var first_mal = linked_list.first(&ll) orelse return MalError.ArgError; - var symbol = switch(first_mal.data) { - .Generic => |symbol| symbol, + const first_mal = items[0]; + const symbol = switch(first_mal.*) { + .Symbol => |symbol| symbol.data, else => "", }; if(string_eql(symbol, "def!")) { - return EVAL_def(mal, env, false); + return EVAL_def(items[1..], env); } else if(string_eql(symbol, "defmacro!")) { - return EVAL_def(mal, env, true); + return EVAL_defmacro(items[1..], env); } else if(string_eql(symbol, "let*")) { - try EVAL_let(&mal, &env); + try EVAL_let(items[1..], &mal, &env, &fde); continue; } else if(string_eql(symbol, "do")) { - try EVAL_do(&mal, &env); + try EVAL_do(items[1..], &mal, env); continue; } else if(string_eql(symbol, "if")) { - try EVAL_if(&mal, &env); + try EVAL_if(items[1..], &mal, env); continue; } else if(string_eql(symbol, "fn*")) { - return EVAL_fn(mal, env); + return EVAL_fn(items[1..], env); } else if(string_eql(symbol, "quote")) { - return EVAL_quote(mal, env); - } - else if(string_eql(symbol, "quasiquoteexpand")) { - env.delete(); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - mal.delete(Allocator); - return try quasiquote(second); + return EVAL_quote(items[1..]); } else if(string_eql(symbol, "quasiquote")) { - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - mal.delete(Allocator); + if(items.len != 2) return MalError.ArgError; + const second = items[1]; mal = try quasiquote(second); continue; } - else if(string_eql(symbol, "macroexpand")) { - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - const expanded = macroexpand(second, env); - env.delete(); - return expanded; - } else if(string_eql(symbol, "try*")) { - return EVAL_try(mal, env); + return EVAL_try(items[1..], env); } else { - var new_list = try eval_ast(mal, try env.copy(Allocator)); - - if(MalTypeValue((try new_list.sequence_nth(0)).data) == MalTypeValue.Func) { - try do_user_func(try new_list.sequence_linked_list(), &mal, &env); - new_list.shallow_destroy(Allocator); - continue; + const evaluated_first = try EVAL(first_mal, env, false); + defer evaluated_first.decref(); + switch (evaluated_first.*) { + .Func => |func_data| { + if(func_data.is_macro) { + mal = try apply_function(evaluated_first.*, items[1..]); + continue; + } + }, + else => {} + } + // A slice would be sufficient, but a List is convenient + // for partial deallocation in case of error. + const args = try MalType.new_list(); + defer args.decref(); + for(items[1..]) |x| { + const new_item = try EVAL(x, env, false); + try args.List.data.append(Allocator, new_item); } - const res = try apply_function((try new_list.sequence_linked_list()).*); - new_list.delete(Allocator); - env.delete(); - return res; + switch(evaluated_first.*) { + .Func => |func_data| { + if(fde) { + env.decref(); + } + else { + fde = true; + } + env = try func_data.gen_env(args.List.data.items); + mal = func_data.body; + continue; + }, + else => {}, + } + return apply_function(evaluated_first.*, args.List.data.items); } }, + .Symbol => { + return EVAL_symbol(mal, env); + }, + .Vector => |ll| { + return EVAL_vector(ll.data.items, env); + }, + .HashMap => |hmap| { + return EVAL_map(hmap.data, env); + }, else => { - return eval_ast(mal, env); + mal.incref(); + return mal; }, } } } -fn eval(a1: *MalType) MalError!*MalType { - return EVAL(try a1.copy(Allocator), try repl_environment.copy(Allocator)); -} - -fn starts_with(ast: *MalType, sym: []const u8) bool { - const ll = switch(ast.data) { - .List => |l| l, - else => return false, - }; - if(ll.count() < 2) { - return false; - } - const ss = switch(ll.at(0).data) { - .Generic => |s| s, - else => return false, - }; - return string_eql(ss, sym); +fn eval(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return EVAL(a1, &repl_environment, false); } -fn is_macro_call(mal: *MalType, env: *Env) ?*MalType { - const ll = switch(mal.data) { +fn starts_with(mal: MalType, sym: []const u8) ?*MalType { + const ll = switch(mal) { .List => |l| l, else => return null, }; - const first_node = linked_list.first(&ll) orelse return null; - const symbol = switch(first_node.data) { - .Generic => |s| s, + const items = ll.data.items; + if(items.len != 2) { + return null; + } + const ss = switch(items[0].*) { + .Symbol => |s| s, else => return null, }; - const val = lookup(env, symbol, false) catch return null; - const is_macro = switch(val.data) { - .Func => |f| f.is_macro, - else => false, - }; - if(is_macro) { - return val; + if(string_eql(ss.data, sym)) { + return items[1]; } - val.delete(Allocator); return null; } -fn macroexpand(mal: *MalType, env: *Env) MalError!*MalType { - var cur_mal = mal; - var optional_macro = is_macro_call(cur_mal, env); - while(optional_macro) |macro| { - var new_list = (try cur_mal.sequence_linked_list()).*; - - if(new_list.count() > 0) { - const first = try linked_list.pop_first(Allocator, &new_list); - first.delete(Allocator); - } - try linked_list.prepend_mal(Allocator, &new_list, macro); - var new_mal = try apply_function_unsafe(Allocator, new_list); - linked_list.destroy(Allocator, &new_list, false); - cur_mal.shallow_destroy(Allocator); - cur_mal = new_mal; - optional_macro = is_macro_call(cur_mal, env); - } - return cur_mal; -} - -fn EVAL_def(mal: *MalType, env: *Env, macro: bool) MalError!*MalType { - const first_arg = try mal.sequence_nth(1); - const second_arg = try mal.sequence_nth(2); - const second_arg_copy = try second_arg.copy(Allocator); - const symbol_name = try first_arg.as_symbol(); - const new_value = try EVAL(second_arg_copy, try env.copy(Allocator)); - if(macro) { - var func_data = switch(new_value.data) { - .Func => |*f| f, - else => return MalError.TypeError, - }; - func_data.*.is_macro = true; - } +fn EVAL_def(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env, false); try env.set(symbol_name, new_value); - mal.delete(Allocator); - env.delete(); - return new_value.copy(Allocator); + new_value.incref(); + return new_value; } -fn EVAL_let(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - //TODO: make faster - const mal = mal_ptr.*; - const env = env_ptr.*; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - const binding_arg = try mal.sequence_pop_first(Allocator); - const eval_arg = try mal.sequence_pop_first(Allocator); - const new_env = try Env.new(Allocator, env); - var binding_ll = switch(binding_arg.data) { - .List => |l| l, - .Vector => |v| v, +fn EVAL_defmacro(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env, false); + errdefer new_value.decref(); + const f = switch (new_value.*) { + .Func => |func_data| func_data, else => return MalError.TypeError, }; - var iterator = binding_ll.iterator(); - var optional_node = iterator.next(); - while(optional_node) |node| { - const key_mal = node; - const key = try key_mal.as_symbol(); - const val_mal = iterator.next() orelse return MalError.ArgError; - const evaled_mal = try EVAL(val_mal, try new_env.copy(Allocator)); + const macro = try MalType.newFunc(f.arg_list, f.body, f.environment); + f.arg_list.incref(); + f.body.incref(); + f.environment.incref(); + macro.Func.is_macro = true; + try env.set(symbol_name, macro); + macro.incref(); + return macro; +} + +fn EVAL_let(args: []*MalType, mal_ptr: **MalType, env_ptr: **Env, fde: *bool) !void { + if(args.len != 2) return MalError.ArgError; + const env = env_ptr.*; + const binding_arg = args[0]; + const eval_arg = args[1]; + const binds = try binding_arg.as_slice(); + if(binds.len % 2 != 0) return MalError.ArgError; + const new_env = try Env.new(env); + // Change env and fde in case an error occurs later in this procedure + // and fde triggers an env.decref() at the exit of EVAL. + if(!fde.*) { + env.incref(); + fde.* = true; + } + env_ptr.* = new_env; + for(0..binds.len / 2) |i| { + const key = binds[2*i]; + const val_mal = binds[2*i + 1]; + const evaled_mal = try EVAL(val_mal, new_env, false); + errdefer evaled_mal.decref(); try new_env.set(key, evaled_mal); - optional_node = iterator.next(); - key_mal.delete(Allocator); + // Do not increment the refcount for the value. } - - linked_list.destroy(Allocator, &binding_ll, true); - binding_arg.data = MalData{.Nil=undefined}; - binding_arg.delete(Allocator); - mal.delete(Allocator); - - // We use eval_arg_copy, since we just deleted eval_arg above mal_ptr.* = eval_arg; - env.delete(); - env_ptr.* = new_env; } -fn EVAL_do(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - var ll = &mal.data.List; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var last_mal = try mal.sequence_pop_last(Allocator); - var evaled_mal = try eval_ast(mal, try env.copy(Allocator)); - evaled_mal.delete(Allocator); +fn EVAL_do(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len == 0) return MalError.ArgError; + const last_mal = args[args.len - 1]; + for (args[0..args.len - 1]) |form| { + const item = try EVAL(form, env, false); + item.decref(); + } mal_ptr.* = last_mal; } -fn EVAL_if(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - defer mal.delete(Allocator); - const first_arg = try mal.sequence_nth(1); - const first_arg_copy = try first_arg.copy(Allocator); - const evaled = try EVAL(first_arg_copy, try env.copy(Allocator)); - const is_true = switch(evaled.data) { +fn EVAL_if(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len != 2 and args.len != 3) return MalError.ArgError; + const first_arg = args[0]; + const evaled = try EVAL(first_arg, env, false); + const is_true = switch(evaled.*) { .False => false, .Nil => false, else => true, }; - evaled.delete(Allocator); + evaled.decref(); if(is_true) { - const second_arg = try mal.sequence_nth(2); - mal_ptr.* = try second_arg.copy(Allocator); + const second_arg = args[1]; + mal_ptr.* = second_arg; return; } - if((try mal.sequence_length()) < 4) { - mal_ptr.* = try MalType.new_nil(Allocator); + if(args.len == 2) { + mal_ptr.* = &MalType.NIL; return; } - const third_arg = try mal.sequence_nth(3); - const third_arg_copy = try third_arg.copy(Allocator); - mal_ptr.* = third_arg_copy; + const third_arg = args[2]; + mal_ptr.* = third_arg; } -fn EVAL_fn(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - const arg_mal = try (try mal.sequence_nth(1)).copy(Allocator); - const body_mal = try (try mal.sequence_nth(2)).copy(Allocator); - const func_data = MalFuncData { - .arg_list = arg_mal, - .body = body_mal, - .environment = env, - .is_macro = false, - .eval_func = &EVAL, - }; - const new_func = try MalType.new_nil(Allocator); - new_func.data = MalData{.Func = func_data}; +fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const arg_mal = args[0]; + const body_mal = args[1]; + for (try arg_mal.as_slice()) |x| { + switch (x.*) { + .Symbol => {}, + else => return MalError.TypeError, + } + } + const new_func = try MalType.newFunc(arg_mal, body_mal, env); + arg_mal.incref(); + body_mal.incref(); + env.incref(); return new_func; } -fn EVAL_quote(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - defer env.delete(); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - return try mal.sequence_pop_first(Allocator); +fn EVAL_quote(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const quoted = args[0]; + quoted.incref(); + return quoted; } -fn EVAL_try(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var mal_to_try = try mal.sequence_pop_first(Allocator); - if(0 == try mal.sequence_length()) { - return EVAL(mal_to_try, env); +fn EVAL_try(args: []*MalType, env: *Env) !*MalType { + if(args.len != 1 and args.len != 2) return MalError.ArgError; + const mal_to_try = args[0]; + if(args.len == 1) { + return EVAL(mal_to_try, env, false); + } + const catch_mal = args[1]; + const catch_list = switch (catch_mal.*) { + .List => |l| l.data.items, + else => return MalError.TypeError, + }; + if(catch_list.len != 3) return MalError.ArgError; + switch (catch_list[0].*) { + .Symbol => |s| { + if(!string_eql(s.data, "catch*")) return MalError.ArgError; + }, + else => return MalError.ArgError, } - var catch_mal = try mal.sequence_pop_first(Allocator); - const evaled_mal = EVAL(mal_to_try, try env.copy(Allocator)) catch |err| { - switch(err) { - MalError.ThrownError => { - }, - else => { - const error_mal = try MalType.new_string(Allocator, error_string_repr(err)); - try env.set("__error", error_mal); - } - } - // TODO: check that first element of catch is "catch*" - (try catch_mal.sequence_pop_first(Allocator)).delete(Allocator); - const err_symbol = try catch_mal.sequence_pop_first(Allocator); - const err_body =try catch_mal.sequence_pop_first(Allocator); - catch_mal.delete(Allocator); - - const err_val = try lookup(env, "__error", false); - var new_env = try Env.new(Allocator, env); - try new_env.set(try err_symbol.as_symbol(), err_val); - err_symbol.delete(Allocator); - const result = EVAL(err_body, try new_env.copy(Allocator)); - new_env.delete(); - env.delete(); + const evaled_mal = EVAL(mal_to_try, env, false) catch |err| { + const err_symbol = catch_list[1]; + const err_body = catch_list[2]; + const err_val = get_error_data() + orelse try MalType.new_string(@errorName(err), true); + const new_env = try Env.new(env); + env.incref(); + defer new_env.decref(); + try new_env.set(err_symbol, err_val); // no incref for err_val. + const result = EVAL(err_body, new_env, false); return result; }; - env.delete(); return evaled_mal; } fn quasiquote(ast: *MalType) MalError!*MalType { - const kind = MalTypeValue(ast.data); - if(kind == MalTypeValue.Generic or kind == MalTypeValue.HashMap) { - const new_list = try MalType.new_list_empty(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "quote")); - try new_list.sequence_append(Allocator, ast); + switch (ast.*) { + .Symbol, .HashMap => { + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + try new_list.List.data.append(Allocator, try MalType.new_symbol("quote", true)); + try new_list.List.data.append(Allocator, ast); + ast.incref(); return new_list; - } - - if(kind != MalTypeValue.List and kind != MalTypeValue.Vector) { + }, + .List => |l| { + if(starts_with(ast.*, "unquote")) |unquoted| { + unquoted.incref(); + return unquoted; + } + return try qq_loop(l.data.items); + }, + .Vector => |l| { + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + try new_list.List.data.append(Allocator, try MalType.new_symbol("vec", true)); + try new_list.List.data.append(Allocator, try qq_loop(l.data.items)); + return new_list; + }, + else => { + ast.incref(); return ast; - } - - defer ast.delete(Allocator); - - if(starts_with(ast, "unquote")) { - (try ast.sequence_pop_first(Allocator)).delete(Allocator); - return ast.sequence_pop_first(Allocator); - } + }, + } +} - var result = try MalType.new_list_empty(Allocator); - while(0 < (try ast.sequence_length())) { - var elt = try ast.sequence_pop_last(Allocator); - const new_list = try MalType.new_list_empty(Allocator); - if(starts_with(elt, "splice-unquote")) { - (try elt.sequence_pop_first(Allocator)).delete(Allocator); - defer elt.delete(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "concat")); - try new_list.sequence_append(Allocator, try elt.sequence_pop_first(Allocator)); - } else { - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "cons")); - try new_list.sequence_append(Allocator, try quasiquote(elt)); +fn qq_loop(items: []*MalType) !*MalType { + var result = try MalType.new_list(); + errdefer result.decref(); + for (0..items.len) |i| { + const elt = items[items.len - 1 - i]; + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + if(starts_with(elt.*, "splice-unquote")) |unquoted| { + try new_list.List.data.append(Allocator, try MalType.new_symbol("concat", true)); + try new_list.List.data.append(Allocator, unquoted); + unquoted.incref(); } - try new_list.sequence_append(Allocator, result); - result = new_list; - } - - if(kind == MalTypeValue.Vector) { - const new_list = try MalType.new_list_empty(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "vec")); - try new_list.sequence_append(Allocator, result); + else { + try new_list.List.data.append(Allocator, try MalType.new_symbol("cons", true)); + try new_list.List.data.append(Allocator, try quasiquote(elt)); + } + try new_list.List.data.append(Allocator, result); result = new_list; } return result; } -fn PRINT(optional_mal: ?*MalType) MalError![] u8 { - return printer.print_str(optional_mal); +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep(environment: *Env, input: [] const u8) MalError!?[] u8 { - var read_input = (try READ(input)) orelse return null; - var eval_input = try EVAL(read_input, try environment.copy(Allocator)); - var print_input = try PRINT(eval_input); - eval_input.delete(Allocator); - return print_input; -} - -fn rep_and_print_errors(environment: *Env, input: [] const u8) ?[]u8 { - return rep(environment, input) catch |err| { - switch(err) { - MalError.KeyError => { }, - MalError.OutOfBounds => { - warn("Error: out of bounds\n"); - }, - MalError.ThrownError => { - warn("Thrown error: "); - const error_mal = lookup(environment, "__error", false) - catch {warn("\n"); return null;}; - const warning = PRINT(error_mal) - catch {warn("\n"); return null;}; - warn("{}\n", warning); - error_mal.delete(Allocator); - Allocator.free(warning); - }, - MalError.ReaderUnmatchedParen => { - warn("Error: expected closing paren, got EOF\n"); - }, - else => { - warn("Error: {}\n", error_string_repr(err)); - }, - } - return null; - }; +fn rep(print: bool, input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = try EVAL(read_input, &repl_environment, false); + defer eval_input.decref(); + if(print) { + try PRINT(eval_input.*); + } } -fn lookup(environment: *Env, symbol: []const u8, do_warn: bool) MalError!*MalType { - var mal = environment.get(symbol) catch |err| { - if(do_warn) { - const s1 = string_concat(Allocator, "'", symbol) catch return MalError.SystemError; - const s2 = string_concat(Allocator, s1, "' not found") catch return MalError.SystemError; - defer Allocator.free(s1); - defer Allocator.free(s2); - _ = try throw(try MalType.new_string(Allocator, s2)); - } - return MalError.KeyError; - }; - var new_mal = try mal.copy(Allocator); - return new_mal; +fn EVAL_symbol(mal: *MalType, env: *Env) !*MalType { + if(try env.get(mal)) |value| { + value.incref(); + return value; + } + const err = try std.fmt.allocPrint(Allocator, "'{s}' not found", + .{mal.Symbol.data}); + return throw(try MalType.new_string(err, false)); } -fn eval_ast(mal: *MalType, env: *Env) MalError!*MalType { - defer env.delete(); - switch(mal.data) { - .Generic => |symbol| { - defer mal.delete(Allocator); - return lookup(env, symbol, true); - }, - .List => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); - } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_list(Allocator, new_ll); - return ret_mal; - }, - .Vector => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn EVAL_vector(ll: []*MalType, env: *Env) !*MalType { + const ret_mal = try MalType.new_vector(); + errdefer ret_mal.decref(); + for(ll) |x| { + const new_mal = try EVAL(x, env, false); + try ret_mal.Vector.data.append(Allocator, new_mal); } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_vector(Allocator, new_ll); return ret_mal; - }, - .HashMap => |hmap| { - var new_hashmap = try MalType.new_hashmap(Allocator); +} + +fn EVAL_map(hmap: hash_map.MalHashMap, env: *Env) !*MalType { + const new_hashmap = try MalType.new_hashmap(); + errdefer new_hashmap.decref(); var iterator = hmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = pair.key; - const value = pair.value; - const evaled_value = try EVAL(value, try env.copy(Allocator)); - try new_hashmap.hashmap_insert(key, evaled_value); - optional_pair = iterator.next(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + const evaled_value = try EVAL(value, env, false); + try hash_map.map_insert_incref_key(&new_hashmap.HashMap.data, key, evaled_value); } - hash_map.destroy(Allocator, hmap, true); - mal.shallow_destroy(Allocator); return new_hashmap; - }, - else => { - return mal; - } - } } -fn throw(a1: *MalType) MalError!*MalType { - const error_mal = try a1.copy(Allocator); - try repl_environment.set("__error", error_mal); - return MalError.ThrownError; -} - -fn make_environment() MalError!*Env { - repl_environment = try Env.new(Allocator, null); - var environment = try repl_environment.copy(Allocator); +fn make_environment() !void { for(core.core_namespace) |pair| { - const name = pair.name; - const func_mal: *MalType = try MalType.new_nil(Allocator); - func_mal.data = switch(pair.func) { - core.CorePairType.Fn0 => |func| MalData{.Fn0 = func}, - core.CorePairType.Fn1 => |func| MalData{.Fn1 = func}, - core.CorePairType.Fn2 => |func| MalData{.Fn2 = func}, - core.CorePairType.FVar => |func| MalData{.FVar = func}, - else => return MalError.TypeError, - }; - try environment.set(name, func_mal); + const name = try MalType.new_symbol(pair.name, true); + const func_mal = try MalType.newFnCore(pair.func); + try repl_environment.set(name, func_mal); + name.decref(); } - const eval_mal = try MalType.new_nil(Allocator); - eval_mal.data = MalData{.Fn1 = &eval}; - try environment.set("eval", eval_mal); - - const throw_mal = try MalType.new_nil(Allocator); - throw_mal.data = MalData{.Fn1 = &throw}; - try environment.set("throw", throw_mal); + const eval_sym = try MalType.new_symbol("eval", true); + const eval_mal = try MalType.newFnCore(eval); + try repl_environment.set(eval_sym, eval_mal); + eval_sym.decref(); const def_not_string: [] const u8 = \\(def! not (fn* (a) (if a false true))) ; - var optional_output = try rep(environment, def_not_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_not_string); const load_file_string: [] const u8 = \\(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) ; - optional_output = try rep(environment, load_file_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, load_file_string); const def_cond_macro_string: [] const u8 = \\(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))))))) ; - optional_output = try rep(environment, def_cond_macro_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_cond_macro_string); - return environment; } -fn do_user_func(args: *MalLinkedList, mal_ptr: **MalType, env_ptr: **Env) MalError!void { - const mal_func = try linked_list.pop_first(Allocator, args); - const env = env_ptr.*; - // 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; - var new_env = try Env.new(Allocator, func_env); - func_env.delete(); - try new_env.set_list(args_ll.*, args.*); - linked_list.destroy(Allocator, args, true); - func_data.arg_list.delete(Allocator); - mal_func.shallow_destroy(Allocator); - mal_ptr.* = func_data.body; - env.delete(); - env_ptr.* = new_env; - return; - } - return MalError.TypeError; -} - -fn apply_function(args: MalLinkedList) MalError!*MalType { +pub fn apply_function(f: MalType, args: []*MalType) MalError!*MalType { - const return_mal = apply_function_unsafe(Allocator, args) catch |err| { - if(err == MalError.ReaderUnmatchedParen) { - warn("Error: expected closing paren, got EOF\n"); - } else if(err == MalError.ReaderUnmatchedString) { - warn("Error: expected closing string, got EOF\n"); - } - return err; - }; - return return_mal; + switch(f) { + .FnCore => |fncoredata| { + return fncoredata.data(args); + }, + .Func => |funcdata| { + const apply_env = try funcdata.gen_env(args); + defer apply_env.decref(); + return EVAL(funcdata.body, apply_env, false); + }, + else => { + return MalError.ApplyError; + }, + } } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - Allocator = CAllocator; - core.set_allocator(Allocator); - var environment = try make_environment(); + // Break a circular dependency between modules. + core.apply_function = &apply_function; + + try make_environment(); const args = try std.process.argsAlloc(Allocator); - var arg_list = try MalType.new_list_empty(Allocator); - for(args) |arg,i| { - if(i < 2) continue; - const new_mal = try MalType.new_string(Allocator, arg); - try arg_list.sequence_append(Allocator, new_mal); + const arg_list = try MalType.new_list(); + if(1 < args.len) { + for (args[2..]) |arg| { + const new_mal = try MalType.new_string(arg, false); + try arg_list.List.data.append(Allocator, new_mal); + } } - try environment.set("*ARGV*", arg_list); + const argv_sym = try MalType.new_symbol("*ARGV*", true); + try repl_environment.set(argv_sym, arg_list); + argv_sym.decref(); if(args.len > 1) { - const run_cmd = try string_concat(Allocator, try string_concat(Allocator, "(load-file \"", args[1]), "\")"); - var output = rep_and_print_errors(environment, run_cmd); + const run_cmd = try std.fmt.allocPrint(Allocator, "(load-file \"{s}\")", .{args[1]}); + try rep(false, run_cmd); return; } - while(true) { - var line = (try getline(Allocator)) orelse break; - var output = rep_and_print_errors(environment, line) orelse continue; - try stdout_file.write(output); - Allocator.free(output); - Allocator.free(line); - try stdout_file.write("\n"); + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(true, line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); + } + }; } } diff --git a/impls/zig/stepA_mal.zig b/impls/zig/stepA_mal.zig index 06a9ece26f..02cfcc1781 100644 --- a/impls/zig/stepA_mal.zig +++ b/impls/zig/stepA_mal.zig @@ -1,628 +1,520 @@ const std = @import("std"); -const warn = @import("std").debug.warn; const reader = @import("reader.zig"); const printer = @import("printer.zig"); const getline = @import("readline.zig").getline; -const string_eql = @import("utils.zig").string_eql; -const string_copy = @import("utils.zig").string_copy; -const string_concat = @import("utils.zig").string_concat; -const apply_function_unsafe = @import("types.zig").apply_function; -const linked_list = @import("linked_list.zig"); +const string_eql = std.hash_map.eqlString; const hash_map = @import("hmap.zig"); const core = @import("core.zig"); -const error_string_repr = @import("error.zig").error_string_repr; -const CAllocator = @import("std").heap.c_allocator; -const AllocatorType = @import("std").mem.Allocator; -pub var Allocator: *AllocatorType = undefined; +const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalTypeValue = @import("types.zig").MalTypeValue; -const MalData = @import("types.zig").MalData; const MalError = @import("error.zig").MalError; -const MalFuncData = @import("types.zig").MalFuncData; const MalLinkedList = @import("linked_list.zig").MalLinkedList; const Env = @import("env.zig").Env; +const get_error_data = @import("error.zig").get_error_data; +const throw = @import("error.zig").throw; +const stdout_file = std.io.getStdOut(); -var repl_environment: *Env = undefined; +var repl_environment = Env.new_root(); -fn READ(a: []const u8) MalError!?*MalType { +fn READ(a: []const u8) !*MalType { var read = try reader.read_str(a); - var optional_mal = reader.read_form(&read); - return optional_mal; + return reader.read_form(&read); } -fn EVAL(mal_arg: *MalType, env_arg: *Env) MalError!*MalType { +// Do not allocate this one on each EVAL run. +// The string is static, but will never be deallocated. +var DEBUG_EVAL = MalType { .Symbol = .{ .data = "DEBUG-EVAL" } }; + +fn EVAL(mal_arg: *MalType, env_arg: *Env, finally_destroy_env: bool) MalError!*MalType { var mal = mal_arg; var env = env_arg; + var fde = finally_destroy_env; + defer if(fde) env.decref(); while(true) { - mal = try macroexpand(mal, env); - switch(mal.data) { + + if(try env.get(&DEBUG_EVAL)) |dbgeval| { + switch (dbgeval.*) { + .Nil, .False => {}, + else => { + try stdout_file.writeAll("EVAL: "); + try PRINT(mal.*); + } + } + } + + switch(mal.*) { .List => |ll| { - if(ll.len == 0) { - env.delete(); + const items = ll.data.items; + if(items.len == 0) { + mal.incref(); return mal; } - var first_mal = linked_list.first(&ll) orelse return MalError.ArgError; - var symbol = switch(first_mal.data) { - .Generic => |symbol| symbol, + const first_mal = items[0]; + const symbol = switch(first_mal.*) { + .Symbol => |symbol| symbol.data, else => "", }; if(string_eql(symbol, "def!")) { - return EVAL_def(mal, env, false); + return EVAL_def(items[1..], env); } else if(string_eql(symbol, "defmacro!")) { - return EVAL_def(mal, env, true); + return EVAL_defmacro(items[1..], env); } else if(string_eql(symbol, "let*")) { - try EVAL_let(&mal, &env); + try EVAL_let(items[1..], &mal, &env, &fde); continue; } else if(string_eql(symbol, "do")) { - try EVAL_do(&mal, &env); + try EVAL_do(items[1..], &mal, env); continue; } else if(string_eql(symbol, "if")) { - try EVAL_if(&mal, &env); + try EVAL_if(items[1..], &mal, env); continue; } else if(string_eql(symbol, "fn*")) { - return EVAL_fn(mal, env); + return EVAL_fn(items[1..], env); } else if(string_eql(symbol, "quote")) { - return EVAL_quote(mal, env); - } - else if(string_eql(symbol, "quasiquoteexpand")) { - env.delete(); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - mal.delete(Allocator); - return try quasiquote(second); + return EVAL_quote(items[1..]); } else if(string_eql(symbol, "quasiquote")) { - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - mal.delete(Allocator); + if(items.len != 2) return MalError.ArgError; + const second = items[1]; mal = try quasiquote(second); continue; } - else if(string_eql(symbol, "macroexpand")) { - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var second = try mal.sequence_pop_first(Allocator); - const expanded = macroexpand(second, env); - env.delete(); - return expanded; - } else if(string_eql(symbol, "try*")) { - return EVAL_try(mal, env); + return EVAL_try(items[1..], env); } else { - var new_list = try eval_ast(mal, try env.copy(Allocator)); - - if(MalTypeValue((try new_list.sequence_nth(0)).data) == MalTypeValue.Func) { - try do_user_func(try new_list.sequence_linked_list(), &mal, &env); - new_list.shallow_destroy(Allocator); - continue; + const evaluated_first = try EVAL(first_mal, env, false); + defer evaluated_first.decref(); + switch (evaluated_first.*) { + .Func => |func_data| { + if(func_data.is_macro) { + mal = try apply_function(evaluated_first.*, items[1..]); + continue; + } + }, + else => {} + } + // A slice would be sufficient, but a List is convenient + // for partial deallocation in case of error. + const args = try MalType.new_list(); + defer args.decref(); + for(items[1..]) |x| { + const new_item = try EVAL(x, env, false); + try args.List.data.append(Allocator, new_item); } - const res = try apply_function((try new_list.sequence_linked_list()).*); - new_list.delete(Allocator); - env.delete(); - return res; + switch(evaluated_first.*) { + .Func => |func_data| { + if(fde) { + env.decref(); + } + else { + fde = true; + } + env = try func_data.gen_env(args.List.data.items); + mal = func_data.body; + continue; + }, + else => {}, + } + return apply_function(evaluated_first.*, args.List.data.items); } }, + .Symbol => { + return EVAL_symbol(mal, env); + }, + .Vector => |ll| { + return EVAL_vector(ll.data.items, env); + }, + .HashMap => |hmap| { + return EVAL_map(hmap.data, env); + }, else => { - return eval_ast(mal, env); + mal.incref(); + return mal; }, } } } -fn eval(a1: *MalType) MalError!*MalType { - return EVAL(try a1.copy(Allocator), try repl_environment.copy(Allocator)); -} - -fn starts_with(ast: *MalType, sym: []const u8) bool { - const ll = switch(ast.data) { - .List => |l| l, - else => return false, - }; - if(ll.count() < 2) { - return false; - } - const ss = switch(ll.at(0).data) { - .Generic => |s| s, - else => return false, - }; - return string_eql(ss, sym); +fn eval(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const a1 = args[0]; + return EVAL(a1, &repl_environment, false); } -fn is_macro_call(mal: *MalType, env: *Env) ?*MalType { - const ll = switch(mal.data) { +fn starts_with(mal: MalType, sym: []const u8) ?*MalType { + const ll = switch(mal) { .List => |l| l, else => return null, }; - const first_node = linked_list.first(&ll) orelse return null; - const symbol = switch(first_node.data) { - .Generic => |s| s, + const items = ll.data.items; + if(items.len != 2) { + return null; + } + const ss = switch(items[0].*) { + .Symbol => |s| s, else => return null, }; - const val = lookup(env, symbol, false) catch return null; - const is_macro = switch(val.data) { - .Func => |f| f.is_macro, - else => false, - }; - if(is_macro) { - return val; + if(string_eql(ss.data, sym)) { + return items[1]; } - val.delete(Allocator); return null; } -fn macroexpand(mal: *MalType, env: *Env) MalError!*MalType { - var cur_mal = mal; - var optional_macro = is_macro_call(cur_mal, env); - while(optional_macro) |macro| { - var new_list = (try cur_mal.sequence_linked_list()).*; - - if(new_list.count() > 0) { - const first = try linked_list.pop_first(Allocator, &new_list); - first.delete(Allocator); - } - try linked_list.prepend_mal(Allocator, &new_list, macro); - var new_mal = try apply_function_unsafe(Allocator, new_list); - linked_list.destroy(Allocator, &new_list, false); - cur_mal.shallow_destroy(Allocator); - cur_mal = new_mal; - optional_macro = is_macro_call(cur_mal, env); - } - return cur_mal; -} - -fn EVAL_def(mal: *MalType, env: *Env, macro: bool) MalError!*MalType { - const first_arg = try mal.sequence_nth(1); - const second_arg = try mal.sequence_nth(2); - const second_arg_copy = try second_arg.copy(Allocator); - const symbol_name = try first_arg.as_symbol(); - const new_value = try EVAL(second_arg_copy, try env.copy(Allocator)); - if(macro) { - var func_data = switch(new_value.data) { - .Func => |*f| f, - else => return MalError.TypeError, - }; - func_data.*.is_macro = true; - } +fn EVAL_def(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env, false); try env.set(symbol_name, new_value); - mal.delete(Allocator); - env.delete(); - return new_value.copy(Allocator); + new_value.incref(); + return new_value; } -fn EVAL_let(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - //TODO: make faster - const mal = mal_ptr.*; - const env = env_ptr.*; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - const binding_arg = try mal.sequence_pop_first(Allocator); - const eval_arg = try mal.sequence_pop_first(Allocator); - const new_env = try Env.new(Allocator, env); - var binding_ll = switch(binding_arg.data) { - .List => |l| l, - .Vector => |v| v, +fn EVAL_defmacro(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const symbol_name = args[0]; + const second_arg = args[1]; + const new_value = try EVAL(second_arg, env, false); + errdefer new_value.decref(); + const f = switch (new_value.*) { + .Func => |func_data| func_data, else => return MalError.TypeError, }; - var iterator = binding_ll.iterator(); - var optional_node = iterator.next(); - while(optional_node) |node| { - const key_mal = node; - const key = try key_mal.as_symbol(); - const val_mal = iterator.next() orelse return MalError.ArgError; - const evaled_mal = try EVAL(val_mal, try new_env.copy(Allocator)); + const macro = try MalType.newFunc(f.arg_list, f.body, f.environment); + f.arg_list.incref(); + f.body.incref(); + f.environment.incref(); + macro.Func.is_macro = true; + try env.set(symbol_name, macro); + macro.incref(); + return macro; +} + +fn EVAL_let(args: []*MalType, mal_ptr: **MalType, env_ptr: **Env, fde: *bool) !void { + if(args.len != 2) return MalError.ArgError; + const env = env_ptr.*; + const binding_arg = args[0]; + const eval_arg = args[1]; + const binds = try binding_arg.as_slice(); + if(binds.len % 2 != 0) return MalError.ArgError; + const new_env = try Env.new(env); + // Change env and fde in case an error occurs later in this procedure + // and fde triggers an env.decref() at the exit of EVAL. + if(!fde.*) { + env.incref(); + fde.* = true; + } + env_ptr.* = new_env; + for(0..binds.len / 2) |i| { + const key = binds[2*i]; + const val_mal = binds[2*i + 1]; + const evaled_mal = try EVAL(val_mal, new_env, false); + errdefer evaled_mal.decref(); try new_env.set(key, evaled_mal); - optional_node = iterator.next(); - key_mal.delete(Allocator); + // Do not increment the refcount for the value. } - - linked_list.destroy(Allocator, &binding_ll, true); - binding_arg.data = MalData{.Nil=undefined}; - binding_arg.delete(Allocator); - mal.delete(Allocator); - - // We use eval_arg_copy, since we just deleted eval_arg above mal_ptr.* = eval_arg; - env.delete(); - env_ptr.* = new_env; } -fn EVAL_do(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - var ll = &mal.data.List; - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var last_mal = try mal.sequence_pop_last(Allocator); - var evaled_mal = try eval_ast(mal, try env.copy(Allocator)); - evaled_mal.delete(Allocator); +fn EVAL_do(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len == 0) return MalError.ArgError; + const last_mal = args[args.len - 1]; + for (args[0..args.len - 1]) |form| { + const item = try EVAL(form, env, false); + item.decref(); + } mal_ptr.* = last_mal; } -fn EVAL_if(mal_ptr: **MalType, env_ptr: **Env) MalError!void { - var mal = mal_ptr.*; - var env = env_ptr.*; - defer mal.delete(Allocator); - const first_arg = try mal.sequence_nth(1); - const first_arg_copy = try first_arg.copy(Allocator); - const evaled = try EVAL(first_arg_copy, try env.copy(Allocator)); - const is_true = switch(evaled.data) { +fn EVAL_if(args: []*MalType, mal_ptr: **MalType, env: *Env) !void { + if(args.len != 2 and args.len != 3) return MalError.ArgError; + const first_arg = args[0]; + const evaled = try EVAL(first_arg, env, false); + const is_true = switch(evaled.*) { .False => false, .Nil => false, else => true, }; - evaled.delete(Allocator); + evaled.decref(); if(is_true) { - const second_arg = try mal.sequence_nth(2); - mal_ptr.* = try second_arg.copy(Allocator); + const second_arg = args[1]; + mal_ptr.* = second_arg; return; } - if((try mal.sequence_length()) < 4) { - mal_ptr.* = try MalType.new_nil(Allocator); + if(args.len == 2) { + mal_ptr.* = &MalType.NIL; return; } - const third_arg = try mal.sequence_nth(3); - const third_arg_copy = try third_arg.copy(Allocator); - mal_ptr.* = third_arg_copy; + const third_arg = args[2]; + mal_ptr.* = third_arg; } -fn EVAL_fn(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - const arg_mal = try (try mal.sequence_nth(1)).copy(Allocator); - const body_mal = try (try mal.sequence_nth(2)).copy(Allocator); - const func_data = MalFuncData { - .arg_list = arg_mal, - .body = body_mal, - .environment = env, - .is_macro = false, - .eval_func = &EVAL, - }; - const new_func = try MalType.new_nil(Allocator); - new_func.data = MalData{.Func = func_data}; +fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { + if(args.len != 2) return MalError.ArgError; + const arg_mal = args[0]; + const body_mal = args[1]; + for (try arg_mal.as_slice()) |x| { + switch (x.*) { + .Symbol => {}, + else => return MalError.TypeError, + } + } + const new_func = try MalType.newFunc(arg_mal, body_mal, env); + arg_mal.incref(); + body_mal.incref(); + env.incref(); return new_func; } -fn EVAL_quote(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - defer env.delete(); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - return try mal.sequence_pop_first(Allocator); +fn EVAL_quote(args: []*MalType) !*MalType { + if(args.len != 1) return MalError.ArgError; + const quoted = args[0]; + quoted.incref(); + return quoted; } -fn EVAL_try(mal: *MalType, env: *Env) MalError!*MalType { - defer mal.delete(Allocator); - (try mal.sequence_pop_first(Allocator)).delete(Allocator); - var mal_to_try = try mal.sequence_pop_first(Allocator); - if(0 == try mal.sequence_length()) { - return EVAL(mal_to_try, env); +fn EVAL_try(args: []*MalType, env: *Env) !*MalType { + if(args.len != 1 and args.len != 2) return MalError.ArgError; + const mal_to_try = args[0]; + if(args.len == 1) { + return EVAL(mal_to_try, env, false); + } + const catch_mal = args[1]; + const catch_list = switch (catch_mal.*) { + .List => |l| l.data.items, + else => return MalError.TypeError, + }; + if(catch_list.len != 3) return MalError.ArgError; + switch (catch_list[0].*) { + .Symbol => |s| { + if(!string_eql(s.data, "catch*")) return MalError.ArgError; + }, + else => return MalError.ArgError, } - var catch_mal = try mal.sequence_pop_first(Allocator); - const evaled_mal = EVAL(mal_to_try, try env.copy(Allocator)) catch |err| { - switch(err) { - MalError.ThrownError => { - }, - else => { - const error_mal = try MalType.new_string(Allocator, error_string_repr(err)); - try env.set("__error", error_mal); - } - } - // TODO: check that first element of catch is "catch*" - (try catch_mal.sequence_pop_first(Allocator)).delete(Allocator); - const err_symbol = try catch_mal.sequence_pop_first(Allocator); - const err_body =try catch_mal.sequence_pop_first(Allocator); - catch_mal.delete(Allocator); - - const err_val = try lookup(env, "__error", false); - var new_env = try Env.new(Allocator, env); - try new_env.set(try err_symbol.as_symbol(), err_val); - err_symbol.delete(Allocator); - const result = EVAL(err_body, try new_env.copy(Allocator)); - new_env.delete(); - env.delete(); + const evaled_mal = EVAL(mal_to_try, env, false) catch |err| { + const err_symbol = catch_list[1]; + const err_body = catch_list[2]; + const err_val = get_error_data() + orelse try MalType.new_string(@errorName(err), true); + const new_env = try Env.new(env); + env.incref(); + defer new_env.decref(); + try new_env.set(err_symbol, err_val); // no incref for err_val. + const result = EVAL(err_body, new_env, false); return result; }; - env.delete(); return evaled_mal; } fn quasiquote(ast: *MalType) MalError!*MalType { - const kind = MalTypeValue(ast.data); - if(kind == MalTypeValue.Generic or kind == MalTypeValue.HashMap) { - const new_list = try MalType.new_list_empty(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "quote")); - try new_list.sequence_append(Allocator, ast); + switch (ast.*) { + .Symbol, .HashMap => { + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + try new_list.List.data.append(Allocator, try MalType.new_symbol("quote", true)); + try new_list.List.data.append(Allocator, ast); + ast.incref(); return new_list; - } - - if(kind != MalTypeValue.List and kind != MalTypeValue.Vector) { + }, + .List => |l| { + if(starts_with(ast.*, "unquote")) |unquoted| { + unquoted.incref(); + return unquoted; + } + return try qq_loop(l.data.items); + }, + .Vector => |l| { + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + try new_list.List.data.append(Allocator, try MalType.new_symbol("vec", true)); + try new_list.List.data.append(Allocator, try qq_loop(l.data.items)); + return new_list; + }, + else => { + ast.incref(); return ast; - } - - defer ast.delete(Allocator); - - if(starts_with(ast, "unquote")) { - (try ast.sequence_pop_first(Allocator)).delete(Allocator); - return ast.sequence_pop_first(Allocator); - } + }, + } +} - var result = try MalType.new_list_empty(Allocator); - while(0 < (try ast.sequence_length())) { - var elt = try ast.sequence_pop_last(Allocator); - const new_list = try MalType.new_list_empty(Allocator); - if(starts_with(elt, "splice-unquote")) { - (try elt.sequence_pop_first(Allocator)).delete(Allocator); - defer elt.delete(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "concat")); - try new_list.sequence_append(Allocator, try elt.sequence_pop_first(Allocator)); - } else { - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "cons")); - try new_list.sequence_append(Allocator, try quasiquote(elt)); +fn qq_loop(items: []*MalType) !*MalType { + var result = try MalType.new_list(); + errdefer result.decref(); + for (0..items.len) |i| { + const elt = items[items.len - 1 - i]; + const new_list = try MalType.new_list(); + errdefer new_list.decref(); + if(starts_with(elt.*, "splice-unquote")) |unquoted| { + try new_list.List.data.append(Allocator, try MalType.new_symbol("concat", true)); + try new_list.List.data.append(Allocator, unquoted); + unquoted.incref(); } - try new_list.sequence_append(Allocator, result); - result = new_list; - } - - if(kind == MalTypeValue.Vector) { - const new_list = try MalType.new_list_empty(Allocator); - try new_list.sequence_append(Allocator, try MalType.new_generic(Allocator, "vec")); - try new_list.sequence_append(Allocator, result); + else { + try new_list.List.data.append(Allocator, try MalType.new_symbol("cons", true)); + try new_list.List.data.append(Allocator, try quasiquote(elt)); + } + try new_list.List.data.append(Allocator, result); result = new_list; } return result; } -fn PRINT(optional_mal: ?*MalType) MalError![] u8 { - return printer.print_str(optional_mal); +fn PRINT(mal: MalType) !void { + const output = try printer.print_str(mal); + defer Allocator.free(output); + try stdout_file.writeAll(output); + try stdout_file.writeAll("\n"); } -fn rep(environment: *Env, input: [] const u8) MalError!?[] u8 { - var read_input = (try READ(input)) orelse return null; - var eval_input = try EVAL(read_input, try environment.copy(Allocator)); - var print_input = try PRINT(eval_input); - eval_input.delete(Allocator); - return print_input; -} - -fn rep_and_print_errors(environment: *Env, input: [] const u8) ?[]u8 { - return rep(environment, input) catch |err| { - switch(err) { - MalError.KeyError => { }, - MalError.OutOfBounds => { - warn("Error: out of bounds\n"); - }, - MalError.ThrownError => { - warn("Thrown error: "); - const error_mal = lookup(environment, "__error", false) - catch {warn("\n"); return null;}; - const warning = PRINT(error_mal) - catch {warn("\n"); return null;}; - warn("{}\n", warning); - error_mal.delete(Allocator); - Allocator.free(warning); - }, - MalError.ReaderUnmatchedParen => { - warn("Error: expected closing paren, got EOF\n"); - }, - else => { - warn("Error: {}\n", error_string_repr(err)); - }, - } - return null; - }; +fn rep(print: bool, input: []const u8) !void { + const read_input = try READ(input); + defer read_input.decref(); + const eval_input = try EVAL(read_input, &repl_environment, false); + defer eval_input.decref(); + if(print) { + try PRINT(eval_input.*); + } } -fn lookup(environment: *Env, symbol: []const u8, do_warn: bool) MalError!*MalType { - var mal = environment.get(symbol) catch |err| { - if(do_warn) { - const s1 = string_concat(Allocator, "'", symbol) catch return MalError.SystemError; - const s2 = string_concat(Allocator, s1, "' not found") catch return MalError.SystemError; - defer Allocator.free(s1); - defer Allocator.free(s2); - _ = try throw(try MalType.new_string(Allocator, s2)); - } - return MalError.KeyError; - }; - var new_mal = try mal.copy(Allocator); - return new_mal; +fn EVAL_symbol(mal: *MalType, env: *Env) !*MalType { + if(try env.get(mal)) |value| { + value.incref(); + return value; + } + const err = try std.fmt.allocPrint(Allocator, "'{s}' not found", + .{mal.Symbol.data}); + return throw(try MalType.new_string(err, false)); } -fn eval_ast(mal: *MalType, env: *Env) MalError!*MalType { - defer env.delete(); - switch(mal.data) { - .Generic => |symbol| { - defer mal.delete(Allocator); - return lookup(env, symbol, true); - }, - .List => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); +fn EVAL_vector(ll: []*MalType, env: *Env) !*MalType { + const ret_mal = try MalType.new_vector(); + errdefer ret_mal.decref(); + for(ll) |x| { + const new_mal = try EVAL(x, env, false); + try ret_mal.Vector.data.append(Allocator, new_mal); } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_list(Allocator, new_ll); return ret_mal; - }, - .Vector => |*ll| { - var new_ll = MalLinkedList.init(Allocator); - var iterator = ll.iterator(); - while(iterator.next()) |next_mal| { - const new_mal = try EVAL(next_mal, try env.copy(Allocator)); - try linked_list.append_mal(Allocator, &new_ll, new_mal); - } - linked_list.destroy(Allocator, ll, true); - mal.shallow_destroy(Allocator); - const ret_mal = MalType.new_vector(Allocator, new_ll); - return ret_mal; - }, - .HashMap => |hmap| { - var new_hashmap = try MalType.new_hashmap(Allocator); +} + +fn EVAL_map(hmap: hash_map.MalHashMap, env: *Env) !*MalType { + const new_hashmap = try MalType.new_hashmap(); + errdefer new_hashmap.decref(); var iterator = hmap.iterator(); - var optional_pair = iterator.next(); - while(true) { - const pair = optional_pair orelse break; - const key = pair.key; - const value = pair.value; - const evaled_value = try EVAL(value, try env.copy(Allocator)); - try new_hashmap.hashmap_insert(key, evaled_value); - optional_pair = iterator.next(); + while(iterator.next()) |pair| { + const key = pair.key_ptr.*; + const value = pair.value_ptr.*; + const evaled_value = try EVAL(value, env, false); + try hash_map.map_insert_incref_key(&new_hashmap.HashMap.data, key, evaled_value); } - hash_map.destroy(Allocator, hmap, true); - mal.shallow_destroy(Allocator); return new_hashmap; - }, - else => { - return mal; - } - } -} - -fn throw(a1: *MalType) MalError!*MalType { - const error_mal = try a1.copy(Allocator); - try repl_environment.set("__error", error_mal); - return MalError.ThrownError; } -fn make_environment() MalError!*Env { - repl_environment = try Env.new(Allocator, null); - var environment = try repl_environment.copy(Allocator); +fn make_environment() !void { for(core.core_namespace) |pair| { - const name = pair.name; - const func_mal: *MalType = try MalType.new_nil(Allocator); - func_mal.data = switch(pair.func) { - core.CorePairType.Fn0 => |func| MalData{.Fn0 = func}, - core.CorePairType.Fn1 => |func| MalData{.Fn1 = func}, - core.CorePairType.Fn2 => |func| MalData{.Fn2 = func}, - core.CorePairType.Fn3 => |func| MalData{.Fn3 = func}, - core.CorePairType.Fn4 => |func| MalData{.Fn4 = func}, - core.CorePairType.FVar => |func| MalData{.FVar = func}, - else => return MalError.TypeError, - }; - try environment.set(name, func_mal); + const name = try MalType.new_symbol(pair.name, true); + const func_mal = try MalType.newFnCore(pair.func); + try repl_environment.set(name, func_mal); + name.decref(); } - const eval_mal = try MalType.new_nil(Allocator); - eval_mal.data = MalData{.Fn1 = &eval}; - try environment.set("eval", eval_mal); - - const throw_mal = try MalType.new_nil(Allocator); - throw_mal.data = MalData{.Fn1 = &throw}; - try environment.set("throw", throw_mal); + const eval_sym = try MalType.new_symbol("eval", true); + const eval_mal = try MalType.newFnCore(eval); + try repl_environment.set(eval_sym, eval_mal); + eval_sym.decref(); const def_not_string: [] const u8 = \\(def! not (fn* (a) (if a false true))) ; - var optional_output = try rep(environment, def_not_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_not_string); const load_file_string: [] const u8 = \\(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) ; - optional_output = try rep(environment, load_file_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, load_file_string); const def_cond_macro_string: [] const u8 = \\(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))))))) ; - optional_output = try rep(environment, def_cond_macro_string); - if(optional_output) |output| { - Allocator.free(output); - } + try rep(false, def_cond_macro_string); - try environment.set("*host-language*", try MalType.new_string(Allocator, "Zig")); - - return environment; + const host_language_sym = try MalType.new_symbol("*host-language*", true); + const host_language_mal = try MalType.new_string("Zig", true); + try repl_environment.set(host_language_sym, host_language_mal); } -fn do_print_header(environment: *Env) MalError!void { +fn do_print_header() !void { const welcome_msg_cmd: [] const u8 = \\(println (str "Mal [" *host-language* "]")) ; - var optional_output = try rep(environment, welcome_msg_cmd); - if(optional_output) |output| { - Allocator.free(output); - } -} - -fn do_user_func(args: *MalLinkedList, mal_ptr: **MalType, env_ptr: **Env) MalError!void { - const mal_func = try linked_list.pop_first(Allocator, args); - const env = env_ptr.*; - // 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; - var new_env = try Env.new(Allocator, func_env); - func_env.delete(); - try new_env.set_list(args_ll.*, args.*); - linked_list.destroy(Allocator, args, true); - func_data.arg_list.delete(Allocator); - mal_func.shallow_destroy(Allocator); - mal_ptr.* = func_data.body; - env.delete(); - env_ptr.* = new_env; - return; - } - return MalError.TypeError; + try rep(false, welcome_msg_cmd); } -fn apply_function(args: MalLinkedList) MalError!*MalType { +pub fn apply_function(f: MalType, args: []*MalType) MalError!*MalType { - const return_mal = apply_function_unsafe(Allocator, args) catch |err| { - if(err == MalError.ReaderUnmatchedParen) { - warn("Error: expected closing paren, got EOF\n"); - } else if(err == MalError.ReaderUnmatchedString) { - warn("Error: expected closing string, got EOF\n"); - } - return err; - }; - return return_mal; + switch(f) { + .FnCore => |fncoredata| { + return fncoredata.data(args); + }, + .Func => |funcdata| { + const apply_env = try funcdata.gen_env(args); + defer apply_env.decref(); + return EVAL(funcdata.body, apply_env, false); + }, + else => { + return MalError.ApplyError; + }, + } } pub fn main() !void { - const stdout_file = try std.io.getStdOut(); - Allocator = CAllocator; - core.set_allocator(Allocator); - var environment = try make_environment(); + // Break a circular dependency between modules. + core.apply_function = &apply_function; + + try make_environment(); const args = try std.process.argsAlloc(Allocator); - var arg_list = try MalType.new_list_empty(Allocator); - for(args) |arg,i| { - if(i < 2) continue; - const new_mal = try MalType.new_string(Allocator, arg); - try arg_list.sequence_append(Allocator, new_mal); + const arg_list = try MalType.new_list(); + if(1 < args.len) { + for (args[2..]) |arg| { + const new_mal = try MalType.new_string(arg, false); + try arg_list.List.data.append(Allocator, new_mal); + } } - try environment.set("*ARGV*", arg_list); + const argv_sym = try MalType.new_symbol("*ARGV*", true); + try repl_environment.set(argv_sym, arg_list); + argv_sym.decref(); if(args.len > 1) { - const run_cmd = try string_concat(Allocator, try string_concat(Allocator, "(load-file \"", args[1]), "\")"); - var output = rep_and_print_errors(environment, run_cmd); + const run_cmd = try std.fmt.allocPrint(Allocator, "(load-file \"{s}\")", .{args[1]}); + try rep(false, run_cmd); return; } - try do_print_header(repl_environment); - - while(true) { - var line = (try getline(Allocator)) orelse break; - var output = rep_and_print_errors(environment, line) orelse continue; - try stdout_file.write(output); - Allocator.free(output); - Allocator.free(line); - try stdout_file.write("\n"); + try do_print_header(); + + while(try getline("user> ")) |line| { + defer Allocator.free(line); + rep(true, line) catch |err| { + try stdout_file.writeAll("Error: "); + try stdout_file.writeAll(@errorName(err)); + try stdout_file.writeAll("\n"); + if(get_error_data()) |mal| { + defer mal.decref(); + try stdout_file.writeAll("MAL error object is: "); + try PRINT(mal.*); + } + }; } } diff --git a/impls/zig/types.zig b/impls/zig/types.zig index 468b4c2251..e061f25592 100644 --- a/impls/zig/types.zig +++ b/impls/zig/types.zig @@ -1,7 +1,7 @@ -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 std = @import("std"); + +const allocator = std.heap.c_allocator; +const warn = std.log.warn; const Env = @import("env.zig").Env; const MalError = @import("error.zig").MalError; const MalHashMap = @import("hmap.zig").MalHashMap; @@ -9,463 +9,317 @@ const MalLinkedList = @import("linked_list.zig").MalLinkedList; const linked_list = @import("linked_list.zig"); const hash_map = @import("hmap.zig"); +const map_destroy = @import("hmap.zig").map_destroy; + +pub const debug_alloc = false; + +pub const ListData = struct { + data: MalLinkedList, + reference_count: i32 = 1, + metadata: *MalType = &MalType.NIL, +}; -pub const MalTypeValue = enum { - List, - Vector, - Generic, - Int, - String, - Keyword, - Nil, - True, - False, - Fn0, - Fn1, - Fn2, - Fn3, - Fn4, - FVar, - Func, - Atom, - HashMap, +pub const FnCoreData = struct { + data: *const fn (args: []*MalType) MalError!*MalType, + reference_count: i32 = 1, // May reach 0 when metadata. + metadata: *MalType = &MalType.NIL, }; 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, + is_macro: bool = false, + reference_count: i32 = 1, + metadata: *MalType = &MalType.NIL, + + pub fn gen_env(self: MalFuncData, args: []*MalType) !*Env { + const binds = try self.arg_list.as_slice(); + var res = try Env.new(self.environment); + self.environment.incref(); + errdefer res.decref(); + if (2 <= binds.len + and std.hash_map.eqlString(binds[binds.len - 2].Symbol.data, "&")) + { + if (args.len < binds.len - 2) + return MalError.TypeError; + for (binds[0..binds.len-2], args[0..binds.len-2]) |k, v| { + try res.set(k, v); + v.incref(); + } + const more = try MalType.new_list(); + errdefer more.decref(); + for (args[binds.len-2..args.len]) |x| { + try more.List.data.append(allocator, x); + x.incref(); + } + try res.set(binds[binds.len - 1], more); + // Do not increment the reference count for this value. + } + else { + if (args.len != binds.len) { + return MalError.TypeError; + } + for(binds, args) |k, v| { + try res.set(k, v); + v.incref(); + } + } + return res; + } +}; + +pub const StringData = struct { + data: [] const u8, + reference_count: i32 = 1, +}; + +pub const HashMapData = struct { + data: MalHashMap, + reference_count: i32 = 1, + metadata: *MalType = &MalType.NIL, }; -pub const MalData = union(MalTypeValue) { - List: MalLinkedList, - Vector: MalLinkedList, - Generic: []const u8, - Int: i64, - String: []const u8, - Keyword: []const u8, +pub const MalType = union(enum) { + List: ListData, + Vector: ListData, + Int: struct { + data: i64, + reference_count: i32 = 1, + }, + Symbol: StringData, + String: StringData, + Keyword: StringData, 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, + FnCore: FnCoreData, 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; + Atom: struct { + data: *MalType, + reference_count: i32 = 1, + }, + HashMap: HashMapData, + + // Define some frequent values in advance. They are not allocated + // on the heap, but should never be deallocated anyway. + pub var NIL = MalType { .Nil = undefined }; + pub var FALSE = MalType { .False = undefined }; + pub var TRUE = MalType { .True = undefined }; + + pub fn new_symbol(value: []const u8, copy: bool) !*MalType { + const mal = try allocator.create(MalType); 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 }; + const data = if (copy) try allocator.dupe(u8, value) else value; + mal.* = .{.Symbol=.{.data = data}}; + if (debug_alloc) warn("Init {any}", .{mal}); 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 }; + pub fn new_string(value: []const u8, copy: bool) !*MalType { + const mal = try allocator.create(MalType); + errdefer allocator.destroy(mal); + const data = if (copy) try allocator.dupe(u8, value) else value; + mal.* = .{.String=.{.data = data}}; + if (debug_alloc) warn("Init {any}", .{mal}); 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 }; + pub fn new_keyword(value: []const u8, copy: bool) !*MalType { + const mal = try allocator.create(MalType); + errdefer allocator.destroy(mal); + const data = if (copy) try allocator.dupe(u8, value) else value; + mal.* = .{.Keyword=.{.data = data}}; + if (debug_alloc) warn("Init {any}", .{mal}); return mal; } - - pub fn new_int(allocator: *Allocator, value: i64) MalError!*MalType { - const mal = try MalType.new_nil(allocator); - mal.data = MalData { .Int = value }; + + pub fn new_int(value: i64) !*MalType { + const mal = try allocator.create(MalType); + mal.* = .{.Int=.{.data = value}}; + if (debug_alloc) warn("Init {any}", .{mal}); return mal; } - pub fn new_bool(allocator: *Allocator, b: bool) MalError!*MalType { - const mal = try MalType.new_nil(allocator); + pub fn new_bool(b: bool) *MalType { if(b) { - mal.data = MalData { .True = undefined }; + return &TRUE; } else { - mal.data = MalData { .False = undefined }; + return &FALSE; } - 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)}; + pub fn newFnCore(f: *const fn (args: []*MalType) MalError!*MalType) !*MalType { + const mal = try allocator.create(MalType); + mal.* = .{.FnCore=.{.data = f}}; + if (debug_alloc) warn("Init core function", .{}); 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)}; + pub fn newFunc(arg_list: *MalType, + body: *MalType, + environment: *Env, + ) !*MalType + { + const mal = try allocator.create(MalType); + mal.* = .{.Func=.{ + .arg_list = arg_list, + .body = body, + .environment = environment, + }}; + if (debug_alloc) warn("Init {any}", .{mal}); return mal; } - pub fn new_list(allocator: *Allocator, ll: MalLinkedList) MalError!*MalType { - const mal = try MalType.new_nil(allocator); - mal.data = MalData {.List = ll}; + pub fn new_list() !*MalType { + const mal = try allocator.create(MalType); + mal.* = .{.List=.{.data = MalLinkedList { }}}; + if (debug_alloc) warn("Init {any}", .{mal}); return mal; } - - pub fn new_vector(allocator: *Allocator, ll: MalLinkedList) MalError!*MalType { - const mal = try MalType.new_nil(allocator); - mal.data = MalData {.Vector = ll}; + + pub fn new_vector() !*MalType { + const mal = try allocator.create(MalType); + errdefer allocator.destroy(mal); + mal.* = .{.Vector=.{.data = MalLinkedList { }}}; + if (debug_alloc) warn("Init {any}", .{mal}); 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 }; + pub fn new_atom(mal: *MalType) !*MalType { + const new_mal = try allocator.create(MalType); + errdefer allocator.destroy(new_mal); + new_mal.* = .{.Atom=.{.data = mal}}; + if (debug_alloc) warn("Init {any}", .{new_mal}); 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}; + pub fn new_hashmap() !*MalType { + const new_mal = try allocator.create(MalType); + errdefer allocator.destroy(new_mal); + new_mal.* = .{.HashMap=.{.data = .{}}}; + if (debug_alloc) warn("Init {any}", .{new_mal}); 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, - } - } + // Trivial but convenient checkers/getters. - 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), + pub fn as_slice(self: MalType) ![]*MalType { + return switch (self) { + .List, .Vector => |x| x.data.items, 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()), + pub fn as_int(mal: MalType) !i64 { + return switch (mal) { + .Int => |val| val.data, 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, + pub fn as_string(self: MalType) ![]const u8 { + return switch (self) { + .String => |s| s.data, 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); + pub fn as_map(self: MalType) !MalHashMap { + switch (self) { + .HashMap => |x| return x.data, + else => return MalError.TypeError, } - 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); + pub fn decref(mal: *MalType) void { + switch(mal.*) { + .List, .Vector => |*l| { + std.debug.assert (0 < l.reference_count); + l.reference_count -= 1; + if (l.reference_count == 0) { + if (debug_alloc) warn("Free {any}", .{mal}); + linked_list.list_destroy(&l.data); + l.metadata.decref(); + allocator.destroy(mal); + } }, - .String => |string| { - allocator.free(string); + .Keyword, .String, .Symbol => |*l| { + std.debug.assert (0 < l.reference_count); + l.reference_count -= 1; + if (l.reference_count == 0) { + if (debug_alloc) warn("Free {s} {any}", .{l.data, mal}); + allocator.free(l.data); + allocator.destroy(mal); + } }, - .Generic => |string| { - allocator.free(string); + .Atom => |*l| { + std.debug.assert (0 < l.reference_count); + l.reference_count -= 1; + if (l.reference_count == 0) { + if (debug_alloc) warn("Free {any}", .{mal}); + l.data.decref(); + allocator.destroy(mal); + } }, - .Keyword => |string| { - allocator.free(string); + .HashMap => |*l| { + std.debug.assert (0 <= l.reference_count); + l.reference_count -= 1; + if (l.reference_count == 0) { + if (debug_alloc) warn("Free {any}", .{mal}); + map_destroy(&l.data); + l.metadata.decref(); + allocator.destroy(mal); + } }, - .Atom => |atom| { - if(ref_count <= 1) - atom.*.delete(allocator); + .Func => |*l| { + std.debug.assert (0 < l.reference_count); + l.reference_count -= 1; + if (l.reference_count == 0) { + if (debug_alloc) warn("Free {any}", .{mal}); + l.arg_list.decref(); + l.body.decref(); + l.environment.decref(); + l.metadata.decref(); + allocator.destroy(mal); + } }, - .HashMap => |hm| { - hash_map.destroy(allocator, hm, false); + .Int => |*l| { + std.debug.assert (0 < l.reference_count); + l.reference_count -= 1; + if (l.reference_count == 0) { + if (debug_alloc) warn("Free {any}", .{mal}); + allocator.destroy(mal); + } }, - .Func => |func_data| { - func_data.arg_list.delete(allocator); - func_data.body.delete(allocator); - func_data.environment.delete(); + .FnCore => |*l| { + std.debug.assert (0 < l.reference_count); + l.reference_count -= 1; + if (l.reference_count == 0) { + if (debug_alloc) warn("Free {any}", .{mal}); + l.metadata.decref(); + allocator.destroy(mal); + } }, - else => {}, + .Nil, .False, .True => {}, } - 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; + pub fn incref(mal: *MalType) void { + // A procedure instead of a function returning its argument + // because it must most of the time be applied *after* a + // successful assignment. + switch(mal.*) { + .List, .Vector => |*l| l.reference_count += 1, + .Int => |*l| l.reference_count += 1, + .Keyword, .String, .Symbol => |*l| l.reference_count += 1, + .FnCore => |*l| l.reference_count += 1, + .Func => |*l| l.reference_count += 1, + .Atom => |*l| l.reference_count += 1, + .HashMap => |*l| l.reference_count += 1, + .Nil, .False, .True => {}, } - - 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; -} +}; diff --git a/impls/zig/utils.zig b/impls/zig/utils.zig deleted file mode 100644 index d9d0890768..0000000000 --- a/impls/zig/utils.zig +++ /dev/null @@ -1,48 +0,0 @@ -const warn = @import("std").debug.warn; - -const Allocator = @import("std").mem.Allocator; - -pub fn string_eql(a: []const u8, b: []const u8) bool { - if(a.len != b.len) { - return false; - } - const n = a.len; - var i: usize = 0; - while(i < n) { - if(a[i] != b[i]) { - return false; - } - i += 1; - } - return true; -} - -pub fn string_copy(allocator: *Allocator, str: []const u8) ![]const u8 { - const copy = try allocator.alloc(u8, str.len); - var i: usize = 0; - while(i < str.len) { - copy[i] = str[i]; - i += 1; - } - return copy; -} - -pub fn string_concat(allocator: *Allocator, s1: []const u8, s2: []const u8) ![] const u8 { - const n: usize = s1.len + s2.len; - var i: usize = 0; - var pos: usize = 0; - const copy = try allocator.alloc(u8, n); - while(i < s1.len) { - copy[pos] = s1[i]; - pos += 1; - i += 1; - } - i = 0; - while(i < s2.len) { - copy[pos] = s2[i]; - pos += 1; - i += 1; - } - return copy; -} -