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:
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" => {