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 }