//! High-level interface // SPDX-FileCopyrightText: 2024-2025 Nguyễn Gia Phong // SPDX-License-Identifier: GPL-3.0-or-later const SourceLocation = std.builtin.SourceLocation; const c = @cImport(@cInclude("janet.h")); const expectEqual = std.testing.expectEqual; const sliceTo = std.mem.sliceTo; const std = @import("std"); const zeroes = std.mem.zeroes; const Table = zsanett.Table; const Value = zsanett.Value; const isNil = zsanett.isNil; const unwrap = zsanett.unwrap; const wrap = zsanett.wrap; const zsanett = @import("root.zig"); const Environment = @This(); table: *Table, pub fn init(base: ?Environment) Environment { const replacements = if (base == null) null else base.?.table; return .{ .table = c.janet_core_env(replacements) }; } /// Evaluates the Janet program given in str. pub fn eval(self: Environment, comptime T: type, str: []const u8, src: [*:0]const u8) !T { var ret: Value = undefined; const errflags = c.janet_dobytes(self.table, str.ptr, @intCast(str.len), src, &ret); // Errors are already logged by Janet, // among them only one is returned and not machine-readable. return if (errflags == 0) unwrap(T, ret) else error.JanetError; } test eval { zsanett.init(); defer zsanett.deinit(); const env = Environment.init(null); try expectEqual(try env.eval(i32, "(+ 3 7)", @src().fn_name), 10); } /// Binds val to name. pub fn def(self: Environment, name: [*:0]const u8, val: anytype, documentation: [*:0]const u8) !void { c.janet_def(self.table, name, try wrap(val), documentation); } test def { const Number = struct { const Number = @This(); n: i8, pub fn nextN(self: Number) Number { return Number{ .n = self.n + 1 }; } }; zsanett.init(); defer zsanett.deinit(); const env = Environment.init(null); try env.def("seven", Number{ .n = 7 }, "number 7 in a struct"); const nine = try env.eval(Number, "(:next-n (:next-n seven))", @src().fn_name); try expectEqual(Number{ .n = 9 }, nine); } /// Gets value. pub fn resolve(self: Environment, T: type, name: []const u8) !T { const symbol = c.janet_symbol(name.ptr, @intCast(name.len)); return try unwrap(T, c.janet_resolve_ext(self.table, symbol).value); } test resolve { zsanett.init(); defer zsanett.deinit(); const env = Environment.init(null); try env.def("pi", 3, "pi for engineers"); try expectEqual(try env.resolve(u8, "pi"), 3); } /// Exposes given function in Janet. pub fn defn(self: Environment, comptime fun: anytype, reg: struct { prefix: ?[*:0]const u8 = null, name: [*:0]const u8, documentation: [*:0]const u8, source: SourceLocation, }) !void { const cfuns = [_]c.JanetRegExt{ .{ .name = reg.name, .cfun = c.janet_unwrap_cfunction(try wrap(fun)), .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(self.table, prefix, &cfuns) else c.janet_cfuns_ext(self.table, null, &cfuns); } test defn { zsanett.init(); defer zsanett.deinit(); const env = Environment.init(null); try env.defn(struct { fn add(a: i32, b: i32) i32 { return a + b; } }.add, .{ .name = "add", .documentation = "(add a b)\n\nReturn a + b.", .source = @src(), }); try expectEqual(try env.eval(i32, "(add 3 7)", @src().fn_name), 10); }