#!/usr/bin/env node /* Writes packages/runtime/src/scr_dyn_methods.h: the method-name ids the * checked-dynamic dispatch in scr_dyn_invoke.c compares (DYN_M_), the * names, and an open-addressed hash table mapping a name to its id. The ids * are the DYN_M_ names scr_dyn_invoke.c uses, so the header follows the * dispatch code. * * node scripts/gen-dyn-methods.mjs write the header * node scripts/gen-dyn-methods.mjs --check fail when it is out of date */ import { readFileSync, writeFileSync } from "node:fs"; const source = new URL("../packages/runtime/src/scr_dyn_invoke.c", import.meta.url); const target = new URL("../packages/runtime/src/scr_dyn_methods.h", import.meta.url); const SLOTS = 256; const used = new Set(); for (const match of readFileSync(source, "utf8").matchAll(/\bDYN_M_([A-Za-z][A-Za-z0-9]*)\b/g)) if (match[1] !== "NONE" && match[1] !== "COUNT") used.add(match[1]); const names = [...used].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); if (names.length >= SLOTS / 2) throw new Error("too many method names for the hash table"); // The slot dyn_method_of in the header computes. const slotOf = (name) => (name.length * 7 + name.charCodeAt(0) * 31 + name.charCodeAt(name.length - 1)) & (SLOTS - 1); const slots = new Array(SLOTS).fill(0); names.forEach((name, i) => { let slot = slotOf(name); while (slots[slot] !== 0) slot = (slot + 1) & (SLOTS - 1); slots[slot] = i + 1; }); const rows = (values, width) => { const out = []; for (let i = 0; i < values.length; i += width) out.push(` ${values.slice(i, i + width).join(", ")},`); return out; }; const text = [ "/* Generated by scripts/gen-dyn-methods.mjs from the DYN_M_ ids scr_dyn_invoke.c", " * uses. Do not edit by hand. */", "#ifndef SCR_DYN_METHODS_H", "#define SCR_DYN_METHODS_H", "", "#include ", "#include ", "", "enum {", " DYN_M_NONE,", ...names.map((name) => ` DYN_M_${name},`), " DYN_M_COUNT", "};", "", "static const char *const dyn_method_names[DYN_M_COUNT] = {", " NULL,", ...names.map((name) => ` ${JSON.stringify(name)},`), "};", "", "static const unsigned char dyn_method_lengths[DYN_M_COUNT] = {", ...rows([0, ...names.map((name) => name.length)], 16), "};", "", `/* Each name's id at its slot (or the next free one); 0 ends a probe. */`, `static const unsigned char dyn_method_slots[${SLOTS}] = {`, ...rows(slots, 16), "};", "", `#define DYN_METHOD_MAX_LENGTH ${Math.max(...names.map((name) => name.length))}`, "", "/* The id of a method name, compared by content; DYN_M_NONE for any other", " * name. */", "static inline int dyn_method_of(const char *m) {", " size_t length = strlen(m);", " if (length == 0 || length > DYN_METHOD_MAX_LENGTH) return DYN_M_NONE;", " unsigned slot = (unsigned)(length * 7 + (unsigned char)m[0] * 31 +", ` (unsigned char)m[length - 1]) & ${SLOTS - 1};`, " for (;;) {", " unsigned id = dyn_method_slots[slot];", " if (id == 0) return DYN_M_NONE;", " if (dyn_method_lengths[id] == length && memcmp(m, dyn_method_names[id], length) == 0) return (int)id;", ` slot = (slot + 1) & ${SLOTS - 1};`, " }", "}", "", "#endif", "", ].join("\n"); if (process.argv.includes("--check")) { let current = ""; try { current = readFileSync(target, "utf8"); } catch {} if (current !== text) { console.error( "packages/runtime/src/scr_dyn_methods.h is out of date: run node scripts/gen-dyn-methods.mjs", ); process.exit(1); } } else { writeFileSync(target, text); }