git @ Cat's Eye Technologies concoctor / master src / concoctor.js
master

Tree @master (Download .tar.gz)

concoctor.js @masterraw · history · blame

// 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 };