about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/root.zig131
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;
         }
     };