diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.zig | 169 |
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, + } +} |