zahl

Log | Files | Refs | README

value.zig (7144B)


      1 const std = @import("std");
      2 const c = @import("chunk.zig");
      3 pub const Int = std.math.big.int.Managed;
      4 
      5 pub const Special = enum {
      6     // Builtin math operations
      7     @"+",
      8     @"/",
      9     @"*",
     10     @"^",
     11     @"-",
     12     @"%",
     13 
     14     @"let*",
     15     @"if",
     16     @"defn!",
     17     @"print!",
     18 
     19 
     20     // Comparisons
     21     @"=",
     22     @"<",
     23 
     24     // List manipulation
     25     cons,
     26     fst,
     27     rest,
     28     list,
     29 
     30     // somewhat imperative programming ?
     31     do,
     32 };
     33 
     34 pub const Function = struct {
     35     const Self = @This();
     36 
     37     code: c.Chunk,
     38     all: std.mem.Allocator,
     39     arity: u16,
     40     name: []const u8 = "_main",
     41 
     42     pub fn init(all: std.mem.Allocator) Self {
     43         return Self {
     44             .code = c.Chunk.init(all),
     45             .all = all,
     46             .arity = 0,
     47         };
     48     }
     49 
     50     pub fn deinit(self: *Self) void {
     51         self.code.deinit();
     52     }
     53 };
     54 
     55 pub const NativeError = error {
     56     InvalidType,
     57     OutOfMemory
     58 };
     59 
     60 pub const NativePayload = *const fn(args: []*Value, all: std.mem.Allocator) anyerror!*Value;
     61 pub const NativeFunction = struct {
     62     arity: u16,
     63     name: []const u8,
     64     code: NativePayload,
     65 };
     66 
     67 const vallog = std.log.scoped(.val);
     68 pub const Value = struct {
     69     const Self = @This();
     70 
     71     const Payload = union(enum) {
     72         number: Int,
     73         list: []*Value,
     74         symbol: []const u8,
     75         special: Special,
     76         function: Function,
     77         native: NativeFunction,
     78         true,
     79         false,
     80         nil,
     81     };
     82 
     83     // XXX: It is very important that payload is not modified !
     84     val: Payload,
     85     all: std.mem.Allocator,
     86     refcount: usize,
     87 
     88     pub const Tag: type = @typeInfo(Payload).Union.tag_type.?;
     89 
     90     fn init(payload: Payload, all: std.mem.Allocator) !*Self {
     91         var new = try all.create(Self);
     92         new.val = payload;
     93         new.all = all;
     94         new.refcount = 1;
     95 
     96         return new;
     97     }
     98 
     99     pub fn newInt(all: std.mem.Allocator, val: anytype) !*Self {
    100         var payload = if (@TypeOf(val) == Int) val else (try Int.initSet(all, val));
    101 
    102         if (intcache.get(payload)) |cached| {
    103             payload.deinit();
    104             return cached.ref();
    105         } else {
    106             var new = try Self.init(.{ .number = payload }, all);
    107             try intcache.put(payload, new);
    108             return new.ref();
    109         }
    110     }
    111 
    112     // orig has to be allocated with all
    113     pub fn newSymbol(all: std.mem.Allocator, orig: []const u8) !*Self {
    114         return try Self.init(.{ .symbol = orig }, all);
    115     }
    116 
    117     // lst has to be allocated with all
    118     pub fn newList(all: std.mem.Allocator, lst: []*Value) !*Self {
    119         return try Self.init(.{ .list = lst }, all);
    120     }
    121 
    122     pub fn newNil(all: std.mem.Allocator) !*Self {
    123         return try Self.init(.nil, all);
    124     }
    125 
    126     pub fn newSpecial(all: std.mem.Allocator, val: Special) !*Self {
    127         return try Self.init(.{ .special = val }, all);
    128     }
    129 
    130     pub fn newBool(all: std.mem.Allocator, val: bool) !*Self {
    131         _ = all;
    132         return if (val) trueval.ref() else falseval.ref();
    133     }
    134 
    135     pub fn newFunction(all: std.mem.Allocator, val: Function) !*Self {
    136         return try Self.init(.{ .function = val }, all);
    137     }
    138 
    139     pub fn newNative(all: std.mem.Allocator, val: NativeFunction) !*Self {
    140         return try Self.init(.{.native = val}, all);
    141     }
    142 
    143     pub fn ref(self: *Self) *Self {
    144         self.refcount += 1;
    145         return self;
    146     }
    147 
    148     pub fn weakref(self: *Self) *Self {
    149         // TODO: implement something to avoid invalid accesses later ?
    150         return self;
    151     }
    152 
    153     pub fn unref(self: *Self) void {
    154         self.refcount -= 1;
    155 
    156         if (self.refcount == 0) {
    157             self.deinit();
    158         }
    159     }
    160 
    161     pub fn deinit(self: *Self) void {
    162         vallog.debug("Destroy {}", .{self.formatter()});
    163         switch (self.val) {
    164             .number => |_| {
    165                 // XXX: numbers are freed when the cache is cleared
    166             },
    167             .list => |lst| {
    168                 for (lst) |val| {
    169                     val.unref();
    170                 }
    171                 self.all.free(lst);
    172             },
    173             .symbol => |_| {
    174                 // Symbols are interned
    175                 // self.all.free(sym);
    176             },
    177             .function => |*fun| {
    178                 fun.deinit();
    179             },
    180             .native => |_| {
    181 
    182             },
    183             .nil, .special, .true, .false => {},
    184         }
    185         self.all.destroy(self);
    186     }
    187 
    188     pub fn isA(self: Self, tag: Tag) bool {
    189         return @as(Tag, self.val) == tag;
    190     }
    191 
    192     pub fn isFalsy(self: Self) bool {
    193         return switch (self.val) {
    194             .false, .nil => true,
    195             else => false,
    196         };
    197     }
    198 
    199     pub fn formatter(self: *const Self) std.fmt.Formatter(Self.format) {
    200         return .{ .data = self };
    201     }
    202 
    203     fn format(self: *const Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
    204         switch (self.val) {
    205             .number => |num| {
    206                 if (fmt.len > 0 and fmt[0] == 'f') {
    207                     const digits = try num.toString(self.all, 10, std.fmt.Case.upper);
    208                     defer self.all.free(digits);
    209                     try writer.writeAll(digits);
    210                 } else {
    211                     try std.fmt.format(writer, "{}", .{num});
    212                 }
    213             },
    214             .list => |lst| {
    215                 try writer.writeByte('(');
    216                 for (lst, 0..) |sub, idx| {
    217                     if (idx > 0) {
    218                         try writer.writeByte(' ');
    219                     }
    220                     try sub.format(fmt, options, writer);
    221                 }
    222                 try writer.writeByte(')');
    223             },
    224             .symbol => |sym| try writer.writeAll(sym),
    225             .special => |s| try writer.writeAll(@tagName(s)),
    226             .function => |f| try writer.print("<function {s}>", .{f.name}),
    227             .native => |n| try writer.print("<native {s}>", .{n.name}),
    228             inline .nil, .true, .false => |_, tag| try writer.writeAll(@tagName(tag)),
    229         }
    230     }
    231 };
    232 
    233 var trueval: *Value = undefined;
    234 var falseval: *Value = undefined;
    235 
    236 const IntHashContext = struct {
    237     pub fn hash(_: @This(), v: Int) u64 {
    238         var wh = std.hash.Wyhash.init(0xDEADBEEF);
    239         for (v.limbs[0..v.len()]) |limb| {
    240             wh.update(std.mem.asBytes(&limb));
    241         }
    242         return wh.final();
    243     }
    244 
    245     pub fn eql(_: @This(), left: Int, right: Int) bool {
    246         return left.eql(right);
    247     }
    248 };
    249 
    250 const IntHashMap = std.HashMap(Int, *Value, IntHashContext, 80);
    251 
    252 // FIXME: this seems to be a good idea (20% perf gain) but consumes too much memory.
    253 // maybe having some garbage collection is good for ints ?
    254 var intcache: IntHashMap = undefined;
    255 
    256 pub fn init(all: std.mem.Allocator) !void {
    257     trueval = try Value.init(.true, all);
    258     falseval = try Value.init(.false, all);
    259     intcache = IntHashMap.init(all);
    260 }
    261 
    262 pub fn deinit() void {
    263     trueval.unref();
    264     falseval.unref();
    265 
    266     var iter = intcache.iterator();
    267 
    268     while (iter.next()) |entry| {
    269         // First unref to avoid invalid memory accesses
    270         entry.value_ptr.*.unref();
    271         entry.key_ptr.deinit();
    272     }
    273 
    274     intcache.deinit();
    275 }