diff options
-rw-r--r-- | src/root.zig | 131 |
1 files changed, 86 insertions, 45 deletions
diff --git a/src/root.zig b/src/root.zig index b944b1b..c5b0633 100644 --- a/src/root.zig +++ b/src/root.zig @@ -8,15 +8,25 @@ const Type = std.builtin.Type; 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 expectEqualDeep = std.testing.expectEqualDeep; -const toLower = std.ascii.toLower; +const isAlphanumeric = std.ascii.isAlphanumeric; const std = @import("std"); +const toLower = std.ascii.toLower; pub const Environment = @import("Environment.zig"); test { _ = Environment; } +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; @@ -37,15 +47,33 @@ pub const Table = c.JanetTable; /// Janet value structure. pub const Value = c.Janet; +pub const KV = c.JanetKV; /// Creates a Janet keyword in kebab-case. fn keyword(comptime name: [:0]const u8) Value { var n: usize = 0; + var prev: u8 = ':'; var kebab: [name.len * 2]u8 = undefined; for (name) |char| { switch (char) { + '(' => { + var i = n; + while (i > 0) : (i -= 1) + kebab[i] = switch (kebab[i - 1]) { + ' ' => break, + else => kebab[i - 1], + }; + kebab[i] = '('; + n += 1; + kebab[n] = ' '; + }, + ',' => { + prev = char; + continue; + }, + '.' => kebab[n] = '/', 'A'...'Z' => { - if (n > 0) { + if (isAlphanumeric(prev)) { kebab[n] = '-'; n += 1; } @@ -55,13 +83,41 @@ fn keyword(comptime name: [:0]const u8) Value { else => kebab[n] = char, } n += 1; + prev = char; } kebab[n] = 0; return c.janet_keywordv(&kebab, @as(i32, @intCast(n))); } +fn structWithMethods(T: type, fields_len: usize) !*KV { + if (!comptime isIntern(T)) + return c.janet_struct_begin(@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; + }, + else => {}, + } + } + const kv = c.janet_struct_begin(@intCast(fields_len + count)); + for (cfuns[0..count]) |cfun| { + const k, const v = cfun; + c.janet_struct_put(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, .int => |int| @intCast(switch (int.signedness) { @@ -101,10 +157,8 @@ pub fn wrap(x: anytype) !Value { .@"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), + .error_set => keyword("zsanett/error"), // FIXME: use janet error + .error_union => if (x) |p| try wrap(p) else |err| try wrap(err), .@"fn" => |info| y: { assert(!info.is_generic); const entry = try fn_map.getOrPut(c_allocator, &x); @@ -124,12 +178,18 @@ pub fn wrap(x: anytype) !Value { (wrap(err) catch unreachable); } }.cfun; - entry.value_ptr.* = y; + entry.value_ptr.* = &y; break :y c.janet_wrap_cfunction(y); }, - .optional => try wrap(x orelse {}), + .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 = c.janet_wrap_pointer(@constCast(@ptrCast(x))); + c.janet_struct_put(kv, keyword(@typeName(info.child)), ptr); + break :y c.janet_wrap_struct(c.janet_struct_end(kv)); + } else c.janet_wrap_pointer(@constCast(@ptrCast(x))), + .many, .c => c.janet_wrap_pointer(@constCast(@ptrCast(x))), .slice => y: { const len: i32 = @intCast(x.len * @sizeOf(info.child)); if (info.is_const) { @@ -144,30 +204,7 @@ pub fn wrap(x: anytype) !Value { }, }, .@"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); @@ -210,7 +247,7 @@ fn isNil(x: Value) bool { } /// 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, @@ -227,16 +264,20 @@ 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.* == c.janet_unwrap_cfunction(x)) + break :y @ptrCast(entry.key_ptr.*); + break :y error.UnknownFunction; + } else if (isIntern(info.child)) y: { + const ptr = c.janet_struct_get(c.janet_unwrap_struct(x), + keyword(@typeName(info.child))); + break :y @alignCast(@ptrCast(c.janet_unwrap_pointer(ptr))); + } else @alignCast(@ptrCast(c.janet_unwrap_pointer(x))), + .many, .c => @alignCast(@ptrCast(c.janet_unwrap_pointer(x))), .slice => slice: { const ptr = if (info.is_const) c.janet_unwrap_string(x) @@ -302,7 +343,7 @@ 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; } }; |