// 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 . 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, } }