//! Meta renaming // SPDX-FileCopyrightText: 2025 Nguyễn Gia Phong // SPDX-License-Identifier: GPL-3.0-or-later const StructField = std.builtin.Type.StructField; const eql = std.mem.eql; const isAlphanumeric = std.ascii.isAlphanumeric; const isUpper = std.ascii.isUpper; const startsWithIgnoreCase = std.ascii.startsWithIgnoreCase; const std = @import("std"); const toLower = std.ascii.toLower; const toUpper = std.ascii.toUpper; fn camelCase(comptime orig: [:0]const u8) [:0]const u8 { var camel: [orig.len + 1]u8 = undefined; var n = 0; var upper = false; for (orig) |char| { if (char == '_') { upper = true; continue; } camel[n] = if (n == 0) toLower(char) else if (upper) toUpper(char) else char; n += 1; upper = false; } camel[n] = 0; return camel[0..n:0]; } pub inline fn kebabCase(comptime orig: [:0]const u8) [:0]const u8 { var kebab: [orig.len * 2]u8 = undefined; var n: usize = 0; var sep = false; for (orig) |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] = ' '; }, ',' => { sep = isAlphanumeric(char); continue; }, '.' => kebab[n] = '/', 'A'...'Z' => { if (sep) { kebab[n] = '-'; n += 1; } kebab[n] = toLower(char); }, '_' => kebab[n] = '-', else => kebab[n] = char, } n += 1; sep = isAlphanumeric(char); } kebab[n] = 0; return kebab[0..n:0]; } fn snakeCase(comptime orig: [:0]const u8) [:0]const u8 { if (orig[0] == '_') return orig[1..]; var snake: [orig.len * 2]u8 = undefined; var n: usize = 0; var sep = false; for (orig) |char| { if (sep and isUpper(char)) { snake[n] = '_'; n += 1; } sep = isAlphanumeric(char); snake[n] = toLower(char); n += 1; } snake[n] = 0; return snake[0..n:0]; } fn titleCase(comptime orig: [:0]const u8) [:0]const u8 { var title: [orig.len + 1]u8 = undefined; var n = 0; var upper = true; for (orig) |char| { if (char == '_') { upper = true; continue; } title[n] = if (upper) toUpper(char) else char; n += 1; upper = false; } title[n] = 0; return title[0..n:0]; } pub fn space(container: type, comptime namespace: [:0]const u8, comptime excludes: []const []const u8) type { const src = @typeInfo(container).@"struct".decls; @setEvalBranchQuota(src.len * 1000); var dest: [src.len]StructField = undefined; var n: usize = 0; outer: inline for (src) |decl_info| { if (!startsWithIgnoreCase(decl_info.name, namespace)) continue; for (excludes) |name| if (eql(u8, name, decl_info.name)) continue :outer; if (!@hasDecl(container, decl_info.name)) continue; const decl = @field(container, decl_info.name); const T = @TypeOf(decl); const basename = decl_info.name[namespace.len..]; dest[n] = .{ .name = switch (@typeInfo(T)) { .type => titleCase, .@"fn" => camelCase, else => snakeCase, }(if (basename.len == 0) namespace else basename), .type = T, .default_value_ptr = @ptrCast(&decl), .is_comptime = true, .alignment = @alignOf(T), }; n += 1; } return @Type(.{ .@"struct" = .{ .layout = .auto, .fields = dest[0..n], .decls = &.{}, .is_tuple = false, } }); } pub const janet = space(@cImport(@cInclude("janet.h")), "janet", &.{ // Extern variables "janet_type_names", "janet_signal_names", "janet_status_names", "janet_instructions", "janet_stream_type", "janet_channel_type", "janet_parser_type", "janet_rng_type", "janet_file_type", "janet_peg_type", "janet_s64_type", "janet_u64_type", // Dependency loops "JanetEVCallback", "janet_async_start_fiber", "janet_async_start", // Nontranslatable macros "JANET_API", "JANET_CFUN", "JANET_DEF", "JANET_DEF_S", "JANET_DEF_SD", "JANET_EXPORT", "JANET_FN", "JANET_FN_", "JANET_FN_D", "JANET_FN_S", "JANET_FN_SD", "JANET_MODULE_ENTRY", "JANET_NO_RETURN", "JANET_OUT_OF_MEMORY", "JANET_REG", "JANET_REG_", "JANET_REG_D", "JANET_REG_END", "JANET_REG_S", "JANET_REG_SD", "JANET_THREAD_LOCAL", "janet_abstract_from_head", "janet_eprintf", "janet_printf", "janet_struct_from_head", "janet_tuple_from_head", }){};