zahl

Log | Files | Refs | README

commit bd9e2497b188e79cc89ca8719a52f7b63d3008d3
parent 2f340def621ac5a0f8e9fa78df26250ca0793e4b
Author: Thomas Vigouroux <thomas.vigouroux@univ-grenoble-alpes.fr>
Date:   Fri, 14 Jun 2024 08:34:19 +0200

feat: native functions

Diffstat:
Msrc/compile.zig | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Asrc/core.zig | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.zig | 3+++
Msrc/value.zig | 23+++++++++++++++++++++++
Msrc/vm.zig | 40++++++++++++++++++++++++++++++----------
5 files changed, 212 insertions(+), 12 deletions(-)

diff --git a/src/compile.zig b/src/compile.zig @@ -1,6 +1,7 @@ const std = @import("std"); const v = @import("value.zig"); const c = @import("chunk.zig"); +const core = @import("core.zig"); const complog = std.log.scoped(.compile); @@ -61,10 +62,69 @@ pub const Compiler = struct { return null; } + fn compileIntrinsic(self: *Self, index: core.mapping) !void { + const val = core.getIntrinsic(index); + const cst = try self.getChunk().addConstant(try v.Value.newNative(self.all, val)); + try self.getChunk().write(.{ .constant = cst }); + try self.getChunk().write(.{ .call = val.arity }); + } + + fn checkMatch(comptime desc: anytype, val: *v.Value) bool { + const rootT = @TypeOf(desc); + switch (rootT) { + v.Special => { + return val.isA(.special) and val.val.special == desc; + }, + v.Value.Tag => { + return val.isA(desc); + }, + else => {} + } + + switch (@typeInfo(rootT)) { + .Struct => |s| { + if (!val.isA(.list)) { + return false; + } + + const lst = val.val.list; + + inline for (s.fields, 0..) |field, idx| { + if (!checkMatch(@field(desc, field.name), lst[idx])) { + return false; + } + } + + return true; + }, + .ComptimeInt => { + if (!val.isA(.number)) { + return false; + } + + // FIXME: create a const here and check that ? That'll avoid a conversion + + const tocheck = val.val.number.to(isize) catch return false; + + return tocheck == desc; + }, + .EnumLiteral => { + if (desc == .any) { + return true; + } + }, + else => @compileError("Uninmplemented"), + } + } + fn compileInner(self: *Self, val: *v.Value, astate: CompileState) CompileError!void { var state = astate; - switch (val.val) { - .function => |_| @panic("Unreachable"), + + if (Compiler.checkMatch(.{v.Special.@"=", .{ v.Special.@"%", .any, 2}, 0}, val)) { + try self.compileInner(val.val.list[1].val.list[1], .{.return_this = false}); + try self.compileIntrinsic(.isEven); + } else switch (val.val) { + inline .function, .native => |_| @panic("Unreachable"), .symbol => |sym| { if (self.sym_map.get(sym)) |store| { switch (store) { @@ -356,3 +416,37 @@ pub const Compiler = struct { return self.func; } }; + +test "Simple match" { + const scan = @import("scan.zig"); + + try v.init(std.testing.allocator); + defer v.deinit(); + + var offset: ?usize = null; + var scanner = try scan.Reader.init("(% 2 3)", std.testing.allocator, &offset); + + var val = try scanner.readValue(&offset); + defer val.unref(); + + try std.testing.expect(Compiler.checkMatch(.{ v.Special.@"%", 2, 3, }, val)); +} + +test "Nested match" { + const scan = @import("scan.zig"); + const s = @import("symintern.zig"); + + try v.init(std.testing.allocator); + defer v.deinit(); + + s.init(std.testing.allocator); + defer s.deinit(); + + var offset: ?usize = null; + var scanner = try scan.Reader.init("(= (% val 2) 0)", std.testing.allocator, &offset); + + var val = try scanner.readValue(&offset); + defer val.unref(); + + try std.testing.expect(Compiler.checkMatch(.{ v.Special.@"=", .{ v.Special.@"%", .any, 2}, 0, }, val)); +} diff --git a/src/core.zig b/src/core.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const v = @import("value.zig"); + +fn sqr(args: []*v.Value, all: std.mem.Allocator) !*v.Value { + var newval = try v.Int.init(all); + + if (!args[0].isA(.number)) { + return v.NativeError.InvalidType; + } + + try newval.sqr(&args[0].val.number); + + return try v.Value.newInt(all, newval); +} + +fn sqrt(args: []*v.Value, all: std.mem.Allocator) !*v.Value { + if (!args[0].isA(.number) or !args[0].val.number.isPositive()) { + return v.NativeError.InvalidType; + } + + var newval = try v.Int.init(all); + + try newval.sqrt(&args[0].val.number); + + return try v.Value.newInt(all, newval); +} + +fn isOdd(args: []*v.Value, all: std.mem.Allocator) !*v.Value { + if (!args[0].isA(.number)) { + return v.NativeError.InvalidType; + } + + return try v.Value.newBool(all, args[0].val.number.isOdd()); +} + +fn isEven(args: []*v.Value, all: std.mem.Allocator) !*v.Value { + if (!args[0].isA(.number)) { + return v.NativeError.InvalidType; + } + + return try v.Value.newBool(all, args[0].val.number.isEven()); +} + +pub const mapping = enum(usize) { + sqr = 0, + sqrt = 1, + isOdd = 2, + isEven = 3, +}; + +pub const funcs: []const v.NativeFunction = &.{ + .{ .arity = 1, .name = "sqr", .code = sqr }, + .{ .arity = 1, .name = "sqrt", .code = sqrt }, + .{ .arity = 1, .name = "odd?", .code = isOdd }, + .{ .arity = 1, .name = "even?", .code = isEven }, +}; + +pub inline fn getIntrinsic(index: mapping) v.NativeFunction { + return funcs[@intFromEnum(index)]; +} diff --git a/src/main.zig b/src/main.zig @@ -8,6 +8,7 @@ const s = @import("scan.zig"); const comp = @import("compile.zig"); const symintern = @import("symintern.zig"); const builtin = @import("builtin"); +const core = @import("core.zig"); const GPAll = std.heap.GeneralPurposeAllocator(.{}); @@ -97,6 +98,8 @@ pub fn main() !void { var v = try vm.VM.init(all); defer v.deinit(); + try v.setupCore(core.funcs); + var args = std.process.args(); _ = args.next(); diff --git a/src/value.zig b/src/value.zig @@ -49,6 +49,18 @@ pub const Function = struct { } }; +pub const NativeError = error { + InvalidType, + OutOfMemory +}; + +pub const NativePayload = *const fn(args: []*Value, all: std.mem.Allocator) anyerror!*Value; +pub const NativeFunction = struct { + arity: u16, + name: []const u8, + code: NativePayload, +}; + const vallog = std.log.scoped(.val); pub const Value = struct { const Self = @This(); @@ -59,6 +71,7 @@ pub const Value = struct { symbol: []const u8, special: Special, function: Function, + native: NativeFunction, true, false, nil, @@ -120,6 +133,10 @@ pub const Value = struct { return try Self.init(.{ .function = val }, all); } + pub fn newNative(all: std.mem.Allocator, val: NativeFunction) !*Self { + return try Self.init(.{.native = val}, all); + } + pub fn ref(self: *Self) *Self { self.refcount += 1; return self; @@ -157,6 +174,9 @@ pub const Value = struct { .function => |*fun| { fun.deinit(); }, + .native => |_| { + + }, .nil, .special, .true, .false => {}, } self.all.destroy(self); @@ -201,6 +221,7 @@ pub const Value = struct { .symbol => |sym| try writer.writeAll(sym), .special => |s| try writer.writeAll(@tagName(s)), .function => |f| try writer.print("<function {s}>", .{f.name}), + .native => |n| try writer.print("<native {s}>", .{n.name}), inline .nil, .true, .false => |_, tag| try writer.writeAll(@tagName(tag)), } } @@ -225,6 +246,8 @@ const IntHashContext = struct { const IntHashMap = std.HashMap(Int, *Value, IntHashContext, 80); +// FIXME: this seems to be a good idea (20% perf gain) but consumes too much memory. +// maybe having some garbage collection is good for ints ? var intcache: IntHashMap = undefined; pub fn init(all: std.mem.Allocator) !void { diff --git a/src/vm.zig b/src/vm.zig @@ -12,7 +12,8 @@ const VMError = error { InvalidType, OutOfMemory, NegativeIntoUnsigned, - TargetTooSmall + TargetTooSmall, + NativeError, } || std.fs.File.WriteError; const CallFrame = struct { @@ -42,6 +43,12 @@ pub const VM = struct { }; } + pub fn setupCore(self: *Self, comptime funcs: []const v.NativeFunction) !void { + inline for (funcs) |func| { + try self.globals.put(func.name, try v.Value.newNative(self.all, func)); + } + } + fn push(self: *Self, val: *v.Value) !void { try self.stack.append(val); } @@ -179,20 +186,33 @@ pub const VM = struct { .call => |arity| { const funcval = self.pop(); defer funcval.unref(); - if (!funcval.isA(.function)) { - return VMError.InvalidType; - } - const func = funcval.val.function; + switch (funcval.val) { + .function => |func| { + std.debug.assert(arity == func.arity); + + const offset = self.stack.items.len - arity; - std.debug.assert(arity == func.arity); + const frame: CallFrame = .{.ip = func.code.instrs.items, .offset = offset, .func = &funcval.val.function}; - const offset = self.stack.items.len - arity; + self.advanceIp(); + try self.call_stack.append(frame); + }, + .native => |n| { + std.debug.assert(arity == n.arity); - const frame: CallFrame = .{.ip = func.code.instrs.items, .offset = offset, .func = &funcval.val.function}; + const offset = self.stack.items.len - arity; + var new = n.code(self.stack.items[offset..], self.all) catch return VMError.NativeError; + errdefer new.unref(); - self.advanceIp(); - try self.call_stack.append(frame); + self.advanceIp(); + + try self.doPopScope(0, n.arity); + + try self.push(new); + }, + else => return VMError.InvalidType, + } }, .@"return" => {