about summary refs log tree commit diff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig169
1 files changed, 169 insertions, 0 deletions
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..f147a7a
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,169 @@
+// Top-level module
+// Copyright (C) 2024  Nguyễn Gia Phong
+//
+// This file is part of jz.
+//
+// jz is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// jz is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with jz.  If not, see <https://www.gnu.org/licenses/>.
+
+const SourceLocation = std.builtin.SourceLocation;
+const Struct = std.builtin.Type.Struct;
+const StructField = std.builtin.Type.StructField;
+const assert = std.debug.assert;
+const c = @cImport(@cInclude("janet.h"));
+const expectEqual = std.testing.expectEqual;
+const formatIntBuf = std.fmt.formatIntBuf;
+const log10 = std.math.log10;
+const std = @import("std");
+const zeroes = std.mem.zeroes;
+
+/// Initialize global Janet state, that is thread local.
+pub fn init() void {
+    _ = c.janet_init();
+}
+
+/// Free resources managed by Janet.
+pub const deinit = c.janet_deinit;
+
+/// Janet environment Table.
+pub const Table = c.JanetTable;
+
+/// Return a core environment Table based on the given optional one.
+pub const coreEnv = c.janet_core_env;
+
+/// Janet value structure.
+pub const Value = c.Janet;
+
+pub fn eval(env: *Table, str: []const u8, src: [*:0]const u8) union(enum) {
+    out: Value,
+    err: Value,
+} {
+    var ret: Value = undefined;
+    const errflags = c.janet_dobytes(env, str.ptr, @intCast(str.len), src, &ret);
+    return if (errflags == 0) .{ .out = ret } else .{ .err = ret };
+}
+
+/// Wrap native type in Janet value.
+fn wrap(x: anytype) Value {
+    return switch (@TypeOf(x)) {
+        f64 => c.janet_wrap_number(x),
+        bool => c.janet_wrap_boolean(@intFromBool(x)),
+        [*:0]const u8 => c.janet_wrap_string(x),
+        i32 => c.janet_wrap_integer(x),
+        else => unreachable,
+    };
+}
+
+/// Unwrap to native type.
+fn unwrap(comptime T: type, x: Value) T {
+    return switch (T) {
+        [*:0]const u8 => c.janet_unwrap_string(x),
+        bool => c.janet_unwrap_boolean(x) != 0,
+        f64 => c.janet_unwrap_number(x),
+        i32 => c.janet_unwrap_integer(x),
+        else => unreachable,
+    };
+}
+
+test "isomorphism" {
+    inline for (.{
+        true,
+        @as(i32, 42069),
+        @as(f64, 420.69),
+        @as([*:0]const u8, "foobar"),
+    }) |x| try expectEqual(x, unwrap(@TypeOf(x), wrap(x)));
+}
+
+fn get(comptime T: type, argv: [*c]Value, n: i32) T {
+    return switch (T) {
+        f64 => c.janet_getnumber(argv, n),
+        [*:0]const u8 => c.janet_getcstring(argv, n),
+        bool => c.janet_getboolean(argv, n) != 0,
+        i32 => c.janet_getinteger(argv, n),
+        i64 => c.janet_getinteger64(argv, n),
+        usize => c.janet_size(argv, n),
+        else => unreachable,
+    };
+}
+
+pub fn def(env: *Table, comptime fun: anytype, reg: struct {
+    prefix: ?[*:0]const u8 = null,
+    name: [*:0]const u8,
+    documentation: [*:0]const u8,
+    source: SourceLocation,
+}) void {
+    const function = @typeInfo(@TypeOf(fun)).Fn;
+    if (function.is_generic)
+        @compileError("fun is generic");
+    if (function.is_var_args)
+        @compileError("fun is variadic");
+    const n = function.params.len;
+    comptime var fields: [n]StructField = undefined;
+    comptime for (&fields, 0.., function.params) |*field, i, param| {
+        const digits = if (i == 0) 1 else log10(i) + 1;
+        var name: [digits]u8 = undefined;
+        assert(formatIntBuf(&name, i, 10, .lower, .{}) == digits);
+        field.name = &name;
+        field.type = param.type.?;
+        field.default_value = null;
+        field.is_comptime = false;
+        field.alignment = 0;
+    };
+    const Tuple = Struct{
+        .layout = .Auto,
+        .fields = &fields,
+        .decls = &.{},
+        .is_tuple = true,
+    };
+    const cfun = struct {
+        fn cfun(argc: i32, argv: [*c]Value) callconv(.C) Value {
+            c.janet_fixarity(argc, n);
+            var args: @Type(.{ .Struct = Tuple }) = undefined;
+            inline for (&args, function.params, 0..) |*arg, param, i|
+                arg.* = get(param.type.?, argv, i);
+            const result = @call(.auto, fun, args);
+            return wrap(result);
+        }
+    }.cfun;
+    const cfuns = [_]c.JanetRegExt{
+        .{
+            .name = reg.name,
+            .cfun = cfun,
+            .documentation = reg.documentation,
+            .source_file = reg.source.file,
+            .source_line = @bitCast(reg.source.line),
+        },
+        zeroes(c.JanetRegExt),
+    };
+    if (reg.prefix) |prefix|
+        c.janet_cfuns_ext_prefix(env, prefix, &cfuns)
+    else
+        c.janet_cfuns_ext(env, null, &cfuns);
+}
+
+test def {
+    init();
+    defer deinit();
+    var env = coreEnv(null);
+    def(env, struct {
+        fn add(a: i32, b: i32) i32 { return a + b; }
+    }.add, .{
+        .name = "add",
+        .documentation = "(add a b)\n\nReturn a + b.",
+        .source = @src(),
+    });
+    switch (eval(env, "(add 3 7)", "add")) {
+        .out => |out| try expectEqual(unwrap(i32, out), 10),
+        .err => return error.EvaluationFailure,
+    }
+}