// Copyright (c) 2026 Chris Pressey, Cat's Eye Technologies.
//
// SPDX-License-Identifier: LicenseRef-MIT-X-concoctor
function bless(fn, attributes) {
fn.concoctAttributes = {
name: attributes?.name,
signature: attributes?.signature,
};
return fn;
}
function concoct(ctx, body, attributes) {
const ctxMap = ctx instanceof Map ? ctx : new Map(Object.entries(ctx));
ctxMap.forEach(function (fn, name) {
if (!fn.concoctAttributes) {
throw new Error("Unvetted function: " + name);
}
});
// FIXME this interface needs serious streamlining
let scanner = new attributes.scanner(body);
const parser = new attributes.parser(scanner);
const ast = parser.parseFunDefn();
const params = obtainParamList(ast);
const signature = attributes?.signature || null;
// Validate that signature length matches parameter count if provided
if (signature && signature.length !== params.length) {
throw new Error(
`Signature length (${signature.length}) does not match parameter count (${params.length})`,
);
}
const javaScriptText = attributes.compile(ctxMap, ast, signature, params);
const fullCode = `return ${javaScriptText};`;
// Create the function with context functions as additional parameters
const ctxFunctionNames = Array.from(ctxMap.keys());
let fun;
try {
fun = new Function(...ctxFunctionNames, ...params, fullCode);
} catch (e) {
console.log(e);
console.log("ctxFunctionNames:", JSON.stringify(ctxFunctionNames));
console.log("params:", JSON.stringify(params));
console.log("fullCode:", fullCode);
}
const ctxFunctionValues = ctxFunctionNames.map((name) => ctxMap.get(name));
const wrapper = function (...args) {
return fun(...ctxFunctionValues, ...args);
};
return bless(wrapper, {
name: attributes?.name,
signature: attributes?.signature,
});
}
function obtainParamList(ast) {
if (ast.type === "fun-defn") {
return ast.params;
}
return [];
}
function makeCtx(dict) {
let ctx = new Map();
Object.keys(dict).forEach(function (key) {
const fn = dict[key];
if (!fn.concoctAttributes) {
throw new Error("Unvetted function: " + key);
}
ctx.set(key, fn);
});
return ctx;
}
function merge(ctx1, ctx2) {
const result = new Map(ctx1);
for (const [key, item] of ctx2) {
if (result.has(key)) {
if (result.get(key) !== item) {
throw new Error(`Conflicting definitions for function: ${key}`);
}
} else {
result.set(key, item);
}
}
return result;
}
export { bless, concoct, makeCtx, merge };