diff options
Diffstat (limited to 'src/root.zig')
-rw-r--r-- | src/root.zig | 416 |
1 files changed, 270 insertions, 146 deletions
diff --git a/src/root.zig b/src/root.zig index b944b1b..939549f 100644 --- a/src/root.zig +++ b/src/root.zig @@ -2,184 +2,214 @@ // SPDX-FileCopyrightText: 2024-2025 Nguyễn Gia Phong // SPDX-License-Identifier: GPL-3.0-or-later +const Allocator = std.mem.Allocator; const ArgsTuple = std.meta.ArgsTuple; const HashMap = std.AutoHashMapUnmanaged; -const Type = std.builtin.Type; +const SourceLocation = std.builtin.SourceLocation; const assert = std.debug.assert; -const c = @cImport(@cInclude("janet.h")); const c_allocator = std.heap.c_allocator; +const declarations = std.meta.declarations; +const eql = std.mem.eql; +const expectEqual = std.testing.expectEqual; const expectEqualDeep = std.testing.expectEqualDeep; -const toLower = std.ascii.toLower; +const expectError = std.testing.expectError; const std = @import("std"); +const toLower = std.ascii.toLower; +const zeroes = std.mem.zeroes; + +pub const janet = rename.janet; +pub const kebabCase = rename.kebabCase; +const rename = @import("rename.zig"); +pub const renamespace = rename.space; -pub const Environment = @import("Environment.zig"); test { - _ = Environment; + _ = rename; +} + +fn isIntern(T: type) bool { + return switch (@typeInfo(T)) { + .@"struct", .@"enum", .@"union", .@"opaque" => true, + else => false, + } and (@import("builtin").is_test or @import("root").zsanettIntern(T)); } /// Associative list of Zig and wrapped Janet functions. -var fn_map: HashMap(*const anyopaque, c.JanetCFunction) = undefined; +var fn_map: HashMap(*const anyopaque, janet.CFunction) = undefined; /// Initializes global Janet state, that is thread local. pub fn init() void { - _ = c.janet_init(); // always 0 + _ = janet.init(); // always 0 fn_map = .empty; } /// Frees resources managed by Janet. pub fn deinit() void { fn_map.deinit(c_allocator); - c.janet_deinit(); + janet.deinit(); } -/// Janet environment Table. -pub const Table = c.JanetTable; - /// Janet value structure. -pub const Value = c.Janet; +pub const Value = janet.Janet; /// Creates a Janet keyword in kebab-case. fn keyword(comptime name: [:0]const u8) Value { - var n: usize = 0; - var kebab: [name.len * 2]u8 = undefined; - for (name) |char| { - switch (char) { - 'A'...'Z' => { - if (n > 0) { - kebab[n] = '-'; - n += 1; - } - kebab[n] = toLower(char); + const kebab = kebabCase(name); + return janet.keywordv(kebab.ptr, @as(i32, @intCast(kebab.len))); +} + +fn structWithMethods(T: type, fields_len: usize) !*janet.KV { + if (!comptime isIntern(T)) + return janet.structBegin(@intCast(fields_len)); + const decls = comptime declarations(T); + var cfuns: [decls.len]struct { Value, Value } = undefined; + var count: usize = 0; + inline for (decls) |decl_info| { + const decl = @field(T, decl_info.name); + switch (@typeInfo(@TypeOf(decl))) { + .@"fn" => |fn_info| if (!fn_info.is_generic) { + cfuns[count] = .{ + keyword(decl_info.name), + try wrap(decl), + }; + count += 1; }, - '_' => kebab[n] = '-', - else => kebab[n] = char, + else => {}, } - n += 1; } - kebab[n] = 0; - return c.janet_keywordv(&kebab, @as(i32, @intCast(n))); + const kv = janet.structBegin(@intCast(fields_len + count)); + for (cfuns[0..count]) |cfun| { + const k, const v = cfun; + janet.structPut(kv, k, v); + } + return kv; } /// Gets function argument. -fn getArg(comptime T: type, argv: [*c]Value, n: i32) !T { +fn getArg(T: type, argv: [*c]Value, n: i32) !T { return switch (@typeInfo(T)) { - .bool => c.janet_getboolean(argv, n) != 0, + .bool => janet.getboolean(argv, n) != 0, .int => |int| @intCast(switch (int.signedness) { .signed => switch (int.bits) { - 0...16 => c.janet_getinteger16, - 17...32 => c.janet_getinteger, - else => c.janet_getinteger64, + 0...16 => janet.getinteger16, + 17...32 => janet.getinteger, + else => janet.getinteger64, }, .unsigned => if (T != usize) switch (int.bits) { - 0...16 => c.janet_getuinteger16, - 17...32 => c.janet_getuinteger, - else => c.janet_getuinteger64, - } else c.janet_getsize, + 0...16 => janet.getuinteger16, + 17...32 => janet.getuinteger, + else => janet.getuinteger64, + } else janet.getsize, }(argv, n)), - .float => @floatCast(c.janet_getnumber(argv, n)), + .float => @floatCast(janet.getnumber(argv, n)), else => unwrap(T, argv[@intCast(n)]), }; } +fn raise(err: anyerror) Value { + const symb = janet.symbol("error", 5); + const fun = janet.resolveExt(janet.coreEnv(null), symb).value; + const name = @errorName(err); + var msg: [*]u8 = @ptrCast(janet.smalloc(name.len * 2)); + var n: u16 = 0; + for (name) |char| { + switch (char) { + 'A'...'Z' => if (n > 0) { + msg[n] = ' '; + n += 1; + }, + else => {}, + } + msg[n] = toLower(char); + n += 1; + } + msg = @ptrCast(janet.srealloc(msg, n)); + var argv = [_]Value{ janet.wrapString(janet.string(msg, n)) }; + return janet.call(janet.unwrapFunction(fun), argv.len, &argv); +} + +pub fn wrapFn(f: anytype) Allocator.Error!janet.CFunction { + const F = @TypeOf(f); + const info = @typeInfo(F).@"fn"; + assert(!info.is_generic); + const entry = try fn_map.getOrPut(c_allocator, &f); + if (!entry.found_existing) + entry.value_ptr.* = struct { + fn cfun(argc: i32, argv: [*c]Value) callconv(.C) Value { + janet.fixarity(argc, info.params.len); + var args: ArgsTuple(F) = undefined; + inline for (&args, info.params, 0..) |*arg, param, i| { + const P = param.type.?; + arg.* = getArg(P, argv, i) catch |err| + return raise(err); + } + const result = @call(.auto, f, args); + return if (@typeInfo(@TypeOf(result)) != .error_union) + wrap(result) catch unreachable + else if (result) |res| + wrap(res) catch unreachable + else |err| + raise(err); + } + }.cfun; + return entry.value_ptr.*; +} + /// Wraps native type in Janet value. pub fn wrap(x: anytype) !Value { const T = @TypeOf(x); return switch (@typeInfo(T)) { - .void => c.janet_wrap_nil(), - .bool => c.janet_wrap_boolean(@intFromBool(x)), - .int => c.janet_wrap_integer(@intCast(x)), - .comptime_int => c.janet_wrap_integer(x), - .float => c.janet_wrap_number(@floatCast(x)), - .comptime_float => c.janet_wrap_number(x), + .void => janet.wrapNil(), + .bool => janet.wrapBoolean(@intFromBool(x)), + .int => janet.wrapInteger(@intCast(x)), + .comptime_int => janet.wrapInteger(x), + .float => janet.wrapNumber(@floatCast(x)), + .comptime_float => janet.wrapNumber(x), .enum_literal => keyword(@tagName(x)), .array => |info| y: { var tuple: [info.len]Value = undefined; for (x, &tuple) |src, *dest| dest.* = try wrap(src); - break :y c.janet_wrap_tuple(c.janet_tuple_n(&tuple, tuple.len)); + break :y janet.wrapTuple(janet.tupleN(&tuple, tuple.len)); }, .@"enum" => switch (x) { inline else => |tag| keyword(@tagName(tag)), }, - .error_set => switch (x) { - inline else => |err| keyword("error/" ++ @errorName(err)), - }, - .error_union => try wrap(x catch |err| err), - .@"fn" => |info| y: { - assert(!info.is_generic); - const entry = try fn_map.getOrPut(c_allocator, &x); - if (entry.found_existing) - break :y c.janet_wrap_cfunction(entry.value_ptr.*); - const n = info.params.len; - const y = struct { - fn cfun(argc: i32, argv: [*c]Value) callconv(.C) Value { - c.janet_fixarity(argc, n); - var args: ArgsTuple(@TypeOf(x)) = undefined; - inline for (&args, info.params, 0..) |*arg, param, i| { - const P = param.type.?; - arg.* = getArg(P, argv, i) catch |err| - return wrap(err) catch unreachable; - } - return wrap(@call(.auto, x, args)) catch |err| - (wrap(err) catch unreachable); - } - }.cfun; - entry.value_ptr.* = y; - break :y c.janet_wrap_cfunction(y); - }, - .optional => try wrap(x orelse {}), + .@"fn" => janet.wrapCfunction(try wrapFn(x)), + .optional => if (x) |child| try wrap(child) else try wrap({}), .pointer => |info| switch (info.size) { - .one, .many, .c => c.janet_wrap_pointer(@constCast(@ptrCast(x))), + .one => if (isIntern(info.child)) y: { + const kv = try structWithMethods(info.child, 1); + const ptr = janet.wrapPointer(@constCast(@ptrCast(x))); + janet.structPut(kv, keyword(@typeName(info.child)), ptr); + break :y janet.wrapStruct(janet.structEnd(kv)); + } else janet.wrapPointer(@constCast(@ptrCast(x))), + .many, .c => janet.wrapPointer(@constCast(@ptrCast(x))), .slice => y: { const len: i32 = @intCast(x.len * @sizeOf(info.child)); if (info.is_const) { - const string = c.janet_string(@ptrCast(x.ptr), len); - break :y c.janet_wrap_string(string); + const string = janet.string(@ptrCast(x.ptr), len); + break :y janet.wrapString(string); } else { const memory: *anyopaque = @ptrCast(x.ptr); - const buffer = c.janet_pointer_buffer_unsafe(memory, - len, len); - break :y c.janet_wrap_buffer(buffer); + const buffer = janet.pointerBufferUnsafe(memory, len, len); + break :y janet.wrapBuffer(buffer); } }, }, .@"struct" => |info| y: { - const cfuns = fns: { - var count: usize = 0; - var cfuns: [info.decls.len]struct { Value, Value } = undefined; - inline for (info.decls) |decl_info| { - const decl = @field(T, decl_info.name); - switch (@typeInfo(@TypeOf(decl))) { - .@"fn" => |fn_info| if (!fn_info.is_generic) { - cfuns[count] = .{ - keyword(decl_info.name), - try wrap(decl), - }; - count += 1; - }, - else => {}, - } - } - break :fns cfuns[0..count]; - }; - const count = cfuns.len + info.fields.len; - const kv = c.janet_struct_begin(@intCast(count)); - for (cfuns) |cfun| { - const k, const v = cfun; - c.janet_struct_put(kv, k, v); - } + const kv = try structWithMethods(T, info.fields.len); inline for (info.fields) |field| { const k = keyword(field.name); const v = @field(x, field.name); - c.janet_struct_put(kv, k, try wrap(v)); + janet.structPut(kv, k, try wrap(v)); } - break :y c.janet_wrap_struct(c.janet_struct_end(kv)); + break :y janet.wrapStruct(janet.structEnd(kv)); }, .@"union" => |info| if (info.tag_type != null) switch (x) { - inline else => |v, tag| c.janet_wrap_tuple(y: { + inline else => |v, tag| janet.wrapTuple(y: { const k = keyword(@tagName(tag)); const tuple = [_]Value{ k, try wrap(v) }; - break :y c.janet_tuple_n(&tuple, tuple.len); + break :y janet.tupleN(&tuple, tuple.len); }), } else @compileError("can't wrap untagged union"), .vector => |info| try wrap(@as([info.len]info.child, x)), @@ -187,37 +217,32 @@ pub fn wrap(x: anytype) !Value { }; } -/// Unwraps Janet value to a tuple head. -fn tupleHead(x: Value) *const c.JanetTupleHead { - return c.janet_tuple_head(c.janet_unwrap_tuple(x)); -} - /// Accesses Janet tuple data type-safely. fn tupleData(len: comptime_int, - head: *const c.JanetTupleHead) *const [len]Value { + head: *const janet.TupleHead) *const [len]Value { assert(head.length == len); return @ptrCast(head.data()); } /// Compares given Janet values for equality. fn equal(x: Value, y: Value) bool { - return c.janet_equals(x, y) != 0; + return janet.equals(x, y) != 0; } /// Checks for Janet nil. fn isNil(x: Value) bool { - return c.janet_checktype(x, c.JANET_NIL) != 0; + return janet.checktype(x, janet.NIL) != 0; } /// Unwraps to native type. -pub fn unwrap(comptime T: type, x: Value) !T { +pub fn unwrap(T: type, x: Value) !T { return switch (@typeInfo(T)) { .void => {}, - .bool => c.janet_unwrap_boolean(x) != 0, - .int => @intCast(c.janet_unwrap_integer(x)), - .float => @floatCast(c.janet_unwrap_number(x)), + .bool => janet.unwrapBoolean(x) != 0, + .int => @intCast(janet.unwrapInteger(x)), + .float => @floatCast(janet.unwrapNumber(x)), inline .array, .vector => |info| y: { - const head = tupleHead(x); + const head = janet.tupleHead(janet.unwrapTuple(x)); var array: [info.len]info.child = undefined; for (tupleData(info.len, head).*, &array) |src, *dest| dest.* = try unwrap(info.child, src); @@ -227,29 +252,33 @@ pub fn unwrap(comptime T: type, x: Value) !T { if (equal(keyword(field.name), x)) break @field(T, field.name); } else error.NoCorrespodingEnum, - .@"fn" => y: { - var iterator = fn_map.iterator(); - while (iterator.next()) |entry| - if (entry.value_ptr.* == c.janet_unwrap_cfunction(x)) - break :y entry.key_ptr.*; - break :y error.UnknownFunction; - }, - .optional => |info| if (isNil(x)) null else unwrap(info.child, x), + .optional => |info| if (isNil(x)) null else try unwrap(info.child, x), .pointer => |info| switch (info.size) { - .one, .many, .c => @alignCast(@ptrCast(c.janet_unwrap_pointer(x))), + .one => if (@typeInfo(info.child) == .@"fn") y: { + var iterator = fn_map.iterator(); + while (iterator.next()) |entry| + if (entry.value_ptr.* == janet.unwrapCfunction(x)) + break :y @ptrCast(entry.key_ptr.*); + break :y error.UnknownFunction; + } else if (isIntern(info.child)) y: { + const ptr = janet.structGet(janet.unwrapStruct(x), + keyword(@typeName(info.child))); + break :y @alignCast(@ptrCast(janet.unwrapPointer(ptr))); + } else @alignCast(@ptrCast(janet.unwrapPointer(x))), + .many, .c => @alignCast(@ptrCast(janet.unwrapPointer(x))), .slice => slice: { const ptr = if (info.is_const) - c.janet_unwrap_string(x) + janet.unwrapString(x) else - c.janet_unwrap_buffer(x).*.data; + janet.unwrapBuffer(x).*.data; comptime var many_info = info; many_info.size = .many; - const Many = @Type(Type{ .pointer = many_info }); + const Many = @Type(.{ .pointer = many_info }); const many: Many = @alignCast(@ptrCast(ptr)); const size = if (info.is_const) - c.janet_string_length(ptr) + janet.stringLength(ptr) else - c.janet_unwrap_buffer(x).*.count; + janet.unwrapBuffer(x).*.count; const len = @divExact(size, @sizeOf(info.child)); break :slice if (info.sentinel()) |sentinel| many[0..@intCast(len):sentinel] @@ -258,26 +287,24 @@ pub fn unwrap(comptime T: type, x: Value) !T { }, }, .@"struct" => |info| y: { - const src = c.janet_unwrap_struct(x); + const src = janet.unwrapStruct(x); var dest: T = undefined; - inline for (info.fields) |field| { + break :y inline for (info.fields) |field| { const k = keyword(field.name); - const v = c.janet_struct_get(src, k); + const v = janet.structGet(src, k); @field(dest, field.name) = if (isNil(v)) - field.defaultValue() orelse return error.MissingStructField + field.defaultValue() orelse break error.MissingStructField else try unwrap(field.type, v); - } - break :y dest; + } else dest; }, .@"union" => |info| if (info.tag_type != null) y: { - const head = tupleHead(x); + const head = janet.tupleHead(janet.unwrapTuple(x)); const k, const v = tupleData(2, head).*; - inline for (info.fields) |field| { + break :y inline for (info.fields) |field| { if (equal(keyword(field.name), k)) - break :y @unionInit(T, field.name, - try unwrap(field.type, v)); - } else break :y error.UnionTagNotFound; + break @unionInit(T, field.name, try unwrap(field.type, v)); + } else error.UnionTagNotFound; } else @compileError("can't wrap untagged union"), else => @compileError(@typeName(T)), }; @@ -302,18 +329,115 @@ test "isomorphism" { } = .{ .integer = 123456789 }, vector: @Vector(3, u4) = .{ 5, 6, 7 }, - pub fn sum(i: @This()) f64 { + fn sum(i: @This()) f64 { return @as(f64, @floatFromInt(i.integer)) + i.float; } }; const i = Immutable{}; try expectEqualDeep(i, try unwrap(Immutable, try wrap(i))); const sum = try unwrap(@TypeOf(&Immutable.sum), try wrap(Immutable.sum)); - try expectEqualDeep(i.sum(), sum(i)); + try expectEqual(i.sum(), sum(i)); var array: [2]bool = undefined; const buffer: []bool = &array; buffer[0] = false; buffer[1] = true; - try expectEqualDeep(buffer, try unwrap(@TypeOf(buffer), try wrap(buffer))); + try expectEqual(buffer, try unwrap(@TypeOf(buffer), try wrap(buffer))); +} + +/// Evaluates the Janet program given in str. +pub fn eval(comptime T: type, str: []const u8, src: [*:0]const u8) !T { + var ret: Value = undefined; + const errflags = janet.dobytes(janet.coreEnv(null), + str.ptr, @intCast(str.len), src, &ret); + // Errors are already logged by Janet, + // among them only one is returned and not machine-readable. + return if (errflags == 0) unwrap(T, ret) else error.JanetError; +} + +test eval { + init(); + defer deinit(); + try expectEqual(try eval(i32, "(+ 3 7)", @src().fn_name), 10); +} + +/// Binds val to name. +pub fn def(name: [*:0]const u8, val: anytype, + documentation: [*:0]const u8) !void { + janet.def(janet.coreEnv(null), name, try wrap(val), documentation); +} + +test def { + const Number = struct { + const Number = @This(); + n: i8, + pub fn nextN(self: Number) Number { + return Number{ .n = self.n + 1 }; + } + }; + init(); + defer deinit(); + try def("seven", Number{ .n = 7 }, "number 7 in a struct"); + const nine = try eval(Number, "(:next-n (:next-n seven))", + @src().fn_name); + try expectEqual(Number{ .n = 9 }, nine); +} + +/// Gets value. +pub fn resolve(T: type, name: []const u8) !T { + const symbol = janet.symbol(name.ptr, @intCast(name.len)); + return try unwrap(T, janet.resolveExt(janet.coreEnv(null), symbol).value); +} + +test resolve { + init(); + defer deinit(); + try def("pi", 3, "pi for engineers"); + try expectEqual(try resolve(u8, "pi"), 3); +} + +/// Exposes given function in Janet. +pub fn defn(comptime fun: anytype, reg: struct { + prefix: ?[*:0]const u8 = null, + name: [*:0]const u8, + documentation: [*:0]const u8, + source: SourceLocation, +}) !void { + const cfuns = [_]janet.RegExt{ + .{ + .name = reg.name, + .cfun = try wrapFn(fun), + .documentation = reg.documentation, + .source_file = reg.source.file, + .source_line = @bitCast(reg.source.line), + }, + zeroes(janet.RegExt), + }; + const env = janet.coreEnv(null); + if (reg.prefix) |prefix| + janet.cfunsExtPrefix(env, prefix, &cfuns) + else + janet.cfunsExt(env, null, &cfuns); +} + +test defn { + init(); + defer deinit(); + try defn(struct { + fn add(a: i32, b: i32) i32 { return a + b; } + }.add, .{ + .name = "add", + .documentation = "(add a b)\n\nReturn a + b.", + .source = @src(), + }); + try expectEqual(try eval(i32, "(add 3 7)", @src().fn_name), 10); + + try defn(struct { + fn raise() !void { return error.ThisIsFine; } + }.raise, .{ + .name = "raise", + .documentation = "(raise)\n\nRaise an error.", + .source = @src(), + }); + try expectError(error.JanetError, eval(void, "(raise)", @src().fn_name)); } |