From 335430dcb0df4687322f4165c9fd7232f37d6107 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Wed, 18 Sep 2024 23:52:55 +0200 Subject: [PATCH 1/3] zig: build with 0.13.0, merge eval_ast, fix remaining issues Update build system, syntax and library calls for zig 0.13.0. Rewrite the build system so that the steps can build separately. Drop intermediate symbolic links (unneeded complexity, confusing timestamps). Build with debugging options, this is a toy project full of memory leaks. Declare the allocators as global variables instead of passing and/or storing always the same reference everywhere for no benefit. Make apply_function a global variable instead of adding a reference to EVAL in each function. Pass arguments as a slice instead of using a different type for each argument count. There is no point in renaming default errors. Remove a lot of reference counting and some indirection levels. This fixes the current segmentation faults. Create each object as soon as possible and use errdefer so that it is deallocated if an exception occurs when computing its elemements. Use a global variable to convey a MAL object alongside a thrown error. Remove the unused logging_alloc module (but add a debug_alloc boolean in types.zig). --- impls/zig/Dockerfile | 19 +- impls/zig/Makefile | 15 +- impls/zig/README | 24 + impls/zig/build.zig | 51 +- impls/zig/core.zig | 1304 +++++++++++++++++--------------- impls/zig/env.zig | 214 ++---- impls/zig/error.zig | 67 +- impls/zig/hmap.zig | 126 +-- impls/zig/linked_list.zig | 61 +- impls/zig/logging_alloc.zig | 35 - impls/zig/printer.zig | 173 ++--- impls/zig/reader.zig | 263 +++---- impls/zig/readline.zig | 48 +- impls/zig/run | 4 +- impls/zig/step0_repl.zig | 32 +- impls/zig/step1_read_print.zig | 54 +- impls/zig/step2_eval.zig | 263 +++---- impls/zig/step3_env.zig | 342 ++++----- impls/zig/step4_if_fn_do.zig | 377 +++++---- impls/zig/step5_tco.zig | 405 +++++----- impls/zig/step6_file.zig | 489 ++++++------ impls/zig/step7_quote.zig | 614 +++++++-------- impls/zig/step8_macros.zig | 702 ++++++++--------- impls/zig/step9_try.zig | 794 +++++++++---------- impls/zig/stepA_mal.zig | 812 +++++++++----------- impls/zig/types.zig | 628 ++++++--------- impls/zig/utils.zig | 48 -- 27 files changed, 3692 insertions(+), 4272 deletions(-) create mode 100644 impls/zig/README delete mode 100644 impls/zig/logging_alloc.zig delete mode 100644 impls/zig/utils.zig 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; -} - From e4eec2cf9baea5ebef27cefb638e362c2fff7e3c Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Thu, 19 Sep 2024 14:00:11 +0200 Subject: [PATCH 2/3] zig: simplify the reader, fix a memory leak --- impls/zig/core.zig | 10 +- impls/zig/printer.zig | 182 ++++++++------------------------- impls/zig/reader.zig | 1 - impls/zig/step1_read_print.zig | 4 +- impls/zig/step2_eval.zig | 4 +- impls/zig/step3_env.zig | 4 +- impls/zig/step4_if_fn_do.zig | 4 +- impls/zig/step5_tco.zig | 4 +- impls/zig/step6_file.zig | 4 +- impls/zig/step7_quote.zig | 4 +- impls/zig/step8_macros.zig | 4 +- impls/zig/step9_try.zig | 4 +- impls/zig/stepA_mal.zig | 4 +- 13 files changed, 58 insertions(+), 175 deletions(-) diff --git a/impls/zig/core.zig b/impls/zig/core.zig index 45cadece3f..a9df6e6878 100644 --- a/impls/zig/core.zig +++ b/impls/zig/core.zig @@ -294,18 +294,14 @@ fn empty(args: []*MalType) !*MalType { } fn prn(args: []*MalType) MalError!*MalType { - const s = try printer.print_mal_to_string(args, true, true); - defer Allocator.free(s); - try stdout_file.writeAll(s); + try printer.n_stdout(args, true, true); try stdout_file.writeAll("\n"); const mal = &MalType.NIL; return mal; } fn println(args: []*MalType) !*MalType { - const s = try printer.print_mal_to_string(args, false, true); - defer Allocator.free(s); - try stdout_file.writeAll(s); + try printer.n_stdout(args, false, true); try stdout_file.writeAll("\n"); const mal = &MalType.NIL; return mal; @@ -313,11 +309,13 @@ fn println(args: []*MalType) !*MalType { fn str(args: []*MalType) !*MalType { const items = try printer.print_mal_to_string(args, false, false); + errdefer Allocator.free(items); return MalType.new_string(items, false); } fn pr_str(args: []*MalType) !*MalType { const s = try printer.print_mal_to_string(args, true, true); + errdefer Allocator.free(s); return MalType.new_string(s, false); } diff --git a/impls/zig/printer.zig b/impls/zig/printer.zig index ebdb14dfe1..30d6361a9b 100644 --- a/impls/zig/printer.zig +++ b/impls/zig/printer.zig @@ -1,208 +1,114 @@ -const io = @import("std").io; -const fmt = @import("std").fmt; - +const std = @import("std"); +const stdout_writer = std.io.getStdOut().writer(); const Allocator = @import("std").heap.c_allocator; const MalType = @import("types.zig").MalType; -const MalLinkedList = @import("linked_list.zig").MalLinkedList; const MalError = @import("error.zig").MalError; -const ResizeBuffer = struct { - buffer: ?[]u8, - pos: usize, - len: usize, -}; - // TODO fix emacs highlighting, remove this const backslash = \\\ ; -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) { - 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| { - for(0..resize_buffer.len) |i| - bigger_buffer[i] = old_buffer[i]; - Allocator.free(old_buffer); - } - resize_buffer.buffer = bigger_buffer; - resize_buffer.len = new_len; - } - - if(resize_buffer.buffer) |n_buffer| - for(0..n) |i| { - n_buffer[resize_buffer.pos] = buffer[i]; - resize_buffer.pos += 1; - }; +pub fn one_stdout(mal: MalType) !void { + try print_to_buffer(mal, stdout_writer, true); } -// TODO: Writer and ResizeBuffer should probably me merged. -fn writeFn(context: *ResizeBuffer, bytes: []const u8) !usize { - try appendToBuffer(context, bytes); - return bytes.len; +pub fn n_stdout(args: []const *MalType, readably: bool, sep: bool) !void { + try n_writer(stdout_writer, args, readably, sep); } -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(); - - var rb = ResizeBuffer{ - .buffer = null, - .pos = 0, - .len = 0, - }; - try print_to_buffer(mal, &rb, true); - if(rb.buffer) |buffer| { - //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]; - } - Allocator.free(buffer); - return return_string; - } - return MalError.SystemError; -} - -pub fn print_mal_to_string(args: []const *MalType, readable: bool, sep: bool) ![] u8 { - // TODO: handle empty string - var rb = ResizeBuffer{ - .buffer = null, - .pos = 0, - .len = 0, - }; +fn n_writer(rb: anytype, args: []const *MalType, readable: bool, sep: bool) !void { for (args, 0..) |node, idx| { if(0 < idx and sep) { - try appendToBuffer(&rb, " "); + try rb.writeAll(" "); } - try print_to_buffer(node.*, &rb, readable); + 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 = try Allocator.alloc(u8, len); - for (0..len) |i| { - return_string[i] = buffer[i]; - } - Allocator.free(buffer); - return return_string; - } - const s: []u8 = ""; - return s; +pub fn print_mal_to_string(args: []const *MalType, readable: bool, sep: bool) ![]u8 { + var rb = std.ArrayListUnmanaged(u8) { }; + errdefer rb.deinit(Allocator); + const writer = rb.writer(Allocator); + try n_writer(writer, args, readable, sep); + return rb.toOwnedSlice(Allocator); } -fn print_to_buffer(mal: MalType, rb: *ResizeBuffer, readable: bool) !void { +fn print_to_buffer(mal: MalType, rb: anytype, readable: bool) MalError!void { switch(mal) { .String => |string| { if(readable) { - try appendToBuffer(rb, "\""); + try rb.writeAll("\""); // TODO: optimize this for(string.data, 0..) |this_char, i| { if(this_char == '"' or this_char==92) { - try appendToBuffer(rb, backslash); + try rb.writeAll(backslash); } if(this_char == '\n') { - try appendToBuffer(rb, "\\n"); + try rb.writeAll("\\n"); } else { - try appendToBuffer(rb, string.data[i..i+1]); + try rb.writeAll(string.data[i..i+1]); } } - try appendToBuffer(rb, "\""); + try rb.writeAll("\""); } else { - try appendToBuffer(rb, string.data); + try rb.writeAll(string.data); } }, .Keyword => |kwd| { - try appendToBuffer(rb, ":"); - try appendToBuffer(rb, kwd.data); + try rb.writeAll(":"); + try rb.writeAll(kwd.data); }, .Int => |val| { - try fmt.format(writer(rb), "{0}", .{val.data}); + try rb.print("{0}", .{val.data}); }, .Nil => { - try appendToBuffer(rb, "nil"); + try rb.writeAll("nil"); }, .True => { - try appendToBuffer(rb, "true"); + try rb.writeAll("true"); }, .False => { - try appendToBuffer(rb, "false"); + try rb.writeAll("false"); }, .List => |l| { - try appendToBuffer(rb, "("); - for (l.data.items, 0..) |next_mal, i| { - if(0 |v| { - try appendToBuffer(rb, "["); - for (v.data.items, 0..) |next_mal, i| { - if(0 |atom_value| { - try appendToBuffer(rb, "(atom "); + try rb.writeAll("(atom "); try print_to_buffer(atom_value.data.*, rb, readable); - try appendToBuffer(rb, ")"); + try rb.writeAll(")"); }, .Func, .FnCore => { - try appendToBuffer(rb, "#"); + try rb.writeAll("#"); }, .Symbol => |value| { - try appendToBuffer(rb, value.data); + try rb.writeAll(value.data); }, .HashMap => |h| { - try appendToBuffer(rb, "{"); + try rb.writeAll("{"); var iterator = h.data.iterator(); var first = true; while(iterator.next()) |pair| { if(!first) { - try appendToBuffer(rb, " "); - } - switch (pair.key_ptr.*.*) { - .Keyword => |k| { - try appendToBuffer(rb, ":"); - try appendToBuffer(rb, k.data); - }, - .String => |s| { - try appendToBuffer(rb, "\""); - try appendToBuffer(rb, s.data); - try appendToBuffer(rb, "\""); - }, - else => unreachable, + try rb.writeAll(" "); } - try appendToBuffer(rb, " "); + try print_to_buffer(pair.key_ptr.*.*, rb, true); + try rb.writeAll(" "); try print_to_buffer(pair.value_ptr.*.*, rb, readable); first = false; } - try appendToBuffer(rb, "}"); + try rb.writeAll("}"); }, } } diff --git a/impls/zig/reader.zig b/impls/zig/reader.zig index 2d778cdb3d..d7d6e51d95 100644 --- a/impls/zig/reader.zig +++ b/impls/zig/reader.zig @@ -7,7 +7,6 @@ const pcre = @cImport({ const MalType = @import("types.zig").MalType; 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("std").hash_map.eqlString; diff --git a/impls/zig/step1_read_print.zig b/impls/zig/step1_read_print.zig index be4bc92ce6..d781d5e24f 100644 --- a/impls/zig/step1_read_print.zig +++ b/impls/zig/step1_read_print.zig @@ -19,9 +19,7 @@ fn EVAL(a: *MalType) *MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } diff --git a/impls/zig/step2_eval.zig b/impls/zig/step2_eval.zig index d2084525d5..2b02a0748f 100644 --- a/impls/zig/step2_eval.zig +++ b/impls/zig/step2_eval.zig @@ -65,9 +65,7 @@ fn EVAL(mal: *MalType, env: hash_map.MalHashMap) MalError!*MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } diff --git a/impls/zig/step3_env.zig b/impls/zig/step3_env.zig index 2ceb0f15aa..a5de51db8a 100644 --- a/impls/zig/step3_env.zig +++ b/impls/zig/step3_env.zig @@ -118,9 +118,7 @@ fn EVAL_let(args: []*MalType, env: *Env) !*MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } diff --git a/impls/zig/step4_if_fn_do.zig b/impls/zig/step4_if_fn_do.zig index e6b092250f..121ae0944b 100644 --- a/impls/zig/step4_if_fn_do.zig +++ b/impls/zig/step4_if_fn_do.zig @@ -176,9 +176,7 @@ fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } diff --git a/impls/zig/step5_tco.zig b/impls/zig/step5_tco.zig index 4fbc4345b7..5ae23e406f 100644 --- a/impls/zig/step5_tco.zig +++ b/impls/zig/step5_tco.zig @@ -207,9 +207,7 @@ fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } diff --git a/impls/zig/step6_file.zig b/impls/zig/step6_file.zig index 2de7a02f91..ae915c7737 100644 --- a/impls/zig/step6_file.zig +++ b/impls/zig/step6_file.zig @@ -213,9 +213,7 @@ fn EVAL_fn(args: []*MalType, env: *Env) !*MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } diff --git a/impls/zig/step7_quote.zig b/impls/zig/step7_quote.zig index 4a9b07be51..ab62ca6ff9 100644 --- a/impls/zig/step7_quote.zig +++ b/impls/zig/step7_quote.zig @@ -301,9 +301,7 @@ fn qq_loop(items: []*MalType) !*MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } diff --git a/impls/zig/step8_macros.zig b/impls/zig/step8_macros.zig index 66c4891538..cc59670fb4 100644 --- a/impls/zig/step8_macros.zig +++ b/impls/zig/step8_macros.zig @@ -333,9 +333,7 @@ fn qq_loop(items: []*MalType) !*MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } diff --git a/impls/zig/step9_try.zig b/impls/zig/step9_try.zig index d4fd546855..279609b515 100644 --- a/impls/zig/step9_try.zig +++ b/impls/zig/step9_try.zig @@ -370,9 +370,7 @@ fn qq_loop(items: []*MalType) !*MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } diff --git a/impls/zig/stepA_mal.zig b/impls/zig/stepA_mal.zig index 02cfcc1781..f1c61cc343 100644 --- a/impls/zig/stepA_mal.zig +++ b/impls/zig/stepA_mal.zig @@ -370,9 +370,7 @@ fn qq_loop(items: []*MalType) !*MalType { } fn PRINT(mal: MalType) !void { - const output = try printer.print_str(mal); - defer Allocator.free(output); - try stdout_file.writeAll(output); + try printer.one_stdout(mal); try stdout_file.writeAll("\n"); } From 8b0ca399869b12c17d8407e28ac86370e7ec026d Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 19 Sep 2024 14:45:48 -0400 Subject: [PATCH 3/3] zig: move zig unpack path from /mal to /opt --- impls/zig/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impls/zig/Dockerfile b/impls/zig/Dockerfile index 8b1bbd7580..da927e6f30 100644 --- a/impls/zig/Dockerfile +++ b/impls/zig/Dockerfile @@ -20,7 +20,7 @@ WORKDIR /mal ########################################################## RUN apt-get -y install ca-certificates curl gcc libc6-dev libpcre3-dev libreadline-dev xz-utils -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 +RUN curl https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJC/opt +RUN ln -fst/usr/local/bin /opt/zig-linux-x86_64-0.13.0/zig ENV HOME /mal