import type { Type } from "./types.js"; import { ANY, BOOL, CHAR, ERR, FLOAT, INT, STRING, VOID, BYTES, fun, list, mapType, optional, result, set as setType, tuple, typeparam } from "./types.js"; export interface GenCtx { /** wrap an expression so it is evaluated at most once (emit a temp) */ temp: (expr: string, fn: (name: string) => string) => string; /** true when inside an async function */ async: boolean; } export interface NativeFun { name: string; typeParams: string[]; params: Type[]; ret: Type; gen: (args: string[], ctx: GenCtx) => string; /** const value exports (e.g. pi) */ const?: undefined; /** builtin Result constructors: ok / err */ builtin?: "ok" | "err"; } export interface NativeConst { name: string; type: Type; gen: () => string; } export type NativeExport = NativeFun | NativeConst; export interface NativeModule { kind: "native"; id: string; name: string; exports: Record; } const T = (i: number) => typeparam("T", i); const U = (i: number) => typeparam("U", i); const nfun = ( name: string, params: Type[], ret: Type, gen: NativeFun["gen"], typeParams: string[] = [], ): NativeFun => ({ name, typeParams, params, ret, gen }); // ---------- std.string ---------- const stringFuns: Record = { toUpper: nfun("toUpper", [STRING], STRING, ([s]) => `${s}.toUpperCase()`), toLower: nfun("toLower", [STRING], STRING, ([s]) => `${s}.toLowerCase()`), trim: nfun("trim", [STRING], STRING, ([s]) => `${s}.trim()`), trimStart: nfun("trimStart", [STRING], STRING, ([s]) => `${s}.trimStart()`), trimEnd: nfun("trimEnd", [STRING], STRING, ([s]) => `${s}.trimEnd()`), split: nfun("split", [STRING, STRING], list(STRING), ([s, sep]) => `${s}.split(${sep})`), splitLines: nfun("splitLines", [STRING], list(STRING), ([s]) => `${s}.split("\\n")`), contains: nfun("contains", [STRING, STRING], BOOL, ([s, sub]) => `${s}.includes(${sub})`), startsWith: nfun("startsWith", [STRING, STRING], BOOL, ([s, p]) => `${s}.startsWith(${p})`), endsWith: nfun("endsWith", [STRING, STRING], BOOL, ([s, p]) => `${s}.endsWith(${p})`), replace: nfun("replace", [STRING, STRING, STRING], STRING, ([s, a, b]) => `${s}.replace(${a}, ${b})`), replaceAll: nfun("replaceAll", [STRING, STRING, STRING], STRING, ([s, a, b]) => `${s}.split(${a}).join(${b})`), length: nfun("length", [STRING], INT, ([s]) => `${s}.length`), isEmpty: nfun("isEmpty", [STRING], BOOL, ([s]) => `${s}.length === 0`), repeat: nfun("repeat", [STRING, INT], STRING, ([s, n]) => `${s}.repeat(${n})`), padStart: nfun("padStart", [STRING, INT, CHAR], STRING, ([s, n, ch]) => `${s}.padStart(${n}, ${ch})`), padEnd: nfun("padEnd", [STRING, INT, CHAR], STRING, ([s, n, ch]) => `${s}.padEnd(${n}, ${ch})`), extract: nfun("extract", [STRING, STRING], optional(STRING), ([s, p]) => `$ex.s.extract(${s}, ${p})`), matches: nfun("matches", [STRING, STRING], BOOL, ([s, p]) => `$ex.s.matches(${s}, ${p})`), charAt: nfun("charAt", [STRING, INT], optional(CHAR), ([s, i]) => `$ex.s.charAt(${s}, ${i})`), }; // ---------- std.collections ---------- const collectionsFuns: Record = { map: nfun("map", [list(T(0)), fun([T(0)], U(1))], list(U(1)), ([xs, f]) => `$ex.c.map(${xs}, ${f})`, ["T", "U"]), filter: nfun("filter", [list(T(0)), fun([T(0)], BOOL)], list(T(0)), ([xs, f]) => `${xs}.filter(${f})`, ["T"]), flatMap: nfun("flatMap", [list(T(0)), fun([T(0)], list(U(1)))], list(U(1)), ([xs, f]) => `${xs}.flatMap(${f})`, ["T", "U"]), reduce: nfun("reduce", [list(T(0)), U(1), fun([U(1), T(0)], U(1))], U(1), ([xs, init, f]) => `${xs}.reduce(${f}, ${init})`, ["T", "U"]), sum: nfun("sum", [list(INT)], INT, ([xs]) => `$ex.c.sum(${xs})`), fsum: nfun("fsum", [list(FLOAT)], FLOAT, ([xs]) => `$ex.c.fsum(${xs})`), count: nfun("count", [list(T(0))], INT, ([xs]) => `${xs}.length`, ["T"]), first: nfun("first", [list(T(0))], optional(T(0)), ([xs]) => `$ex.c.first(${xs})`, ["T"]), last: nfun("last", [list(T(0))], optional(T(0)), ([xs]) => `$ex.c.last(${xs})`, ["T"]), contains: nfun("contains", [list(T(0)), T(0)], BOOL, ([xs, v]) => `${xs}.includes(${v})`, ["T"]), sort: nfun("sort", [list(T(0))], list(T(0)), ([xs]) => `$ex.c.sort(${xs})`, ["T"]), reverse: nfun("reverse", [list(T(0))], list(T(0)), ([xs]) => `$ex.c.reverse(${xs})`, ["T"]), indexed: nfun("indexed", [list(T(0))], list(tuple([INT, T(0)])), ([xs]) => `$ex.c.indexed(${xs})`, ["T"]), unique: nfun("unique", [list(T(0))], list(T(0)), ([xs]) => `$ex.c.unique(${xs})`, ["T"]), min: nfun("min", [list(INT)], optional(INT), ([xs]) => `$ex.c.min(${xs})`), max: nfun("max", [list(INT)], optional(INT), ([xs]) => `$ex.c.max(${xs})`), fmin: nfun("fmin", [list(FLOAT)], optional(FLOAT), ([xs]) => `$ex.c.fmin(${xs})`), fmax: nfun("fmax", [list(FLOAT)], optional(FLOAT), ([xs]) => `$ex.c.fmax(${xs})`), splitFirst: nfun("splitFirst", [list(T(0))], result(tuple([T(0), list(T(0))])), ([xs]) => `$ex.c.splitFirst(${xs})`, ["T"]), join: nfun("join", [list(STRING), STRING], STRING, ([xs, sep]) => `${xs}.join(${sep})`), }; // ---------- std.math ---------- const mathFuns: Record = { abs: nfun("abs", [INT], INT, ([x]) => `Math.abs(${x})`), fabs: nfun("fabs", [FLOAT], FLOAT, ([x]) => `Math.abs(${x})`), floor: nfun("floor", [FLOAT], FLOAT, ([x]) => `Math.floor(${x})`), ceil: nfun("ceil", [FLOAT], FLOAT, ([x]) => `Math.ceil(${x})`), round: nfun("round", [FLOAT], FLOAT, ([x]) => `Math.round(${x})`), sqrt: nfun("sqrt", [FLOAT], FLOAT, ([x]) => `Math.sqrt(${x})`), pow: nfun("pow", [FLOAT, FLOAT], FLOAT, ([a, b]) => `Math.pow(${a}, ${b})`), min: nfun("min", [INT, INT], INT, ([a, b]) => `Math.min(${a}, ${b})`), max: nfun("max", [INT, INT], INT, ([a, b]) => `Math.max(${a}, ${b})`), fmin: nfun("fmin", [FLOAT, FLOAT], FLOAT, ([a, b]) => `Math.min(${a}, ${b})`), fmax: nfun("fmax", [FLOAT, FLOAT], FLOAT, ([a, b]) => `Math.max(${a}, ${b})`), clamp: nfun("clamp", [INT, INT, INT], INT, ([v, lo, hi]) => `Math.min(${hi}, Math.max(${lo}, ${v}))`), fclamp: nfun("fclamp", [FLOAT, FLOAT, FLOAT], FLOAT, ([v, lo, hi]) => `Math.min(${hi}, Math.max(${lo}, ${v}))`), pi: { name: "pi", type: FLOAT, gen: () => "Math.PI" }, random: nfun("random", [], FLOAT, () => `Math.random()`), randomInt: nfun("randomInt", [INT, INT], INT, ([lo, hi]) => `$ex.m.randomInt(${lo}, ${hi})`), roundInt: nfun("roundInt", [FLOAT], INT, ([x]) => `Math.round(${x})`), }; // ---------- std.test ---------- const testFuns: Record = { expect: nfun("expect", [ANY], { k: "expect" }, ([v]) => `$ex.test.expect(${v})`), eq: nfun("eq", [{ k: "expect" }, ANY], VOID, ([e, v]) => `$ex.test.eq(${e}, ${v})`), ne: nfun("ne", [{ k: "expect" }, ANY], VOID, ([e, v]) => `$ex.test.ne(${e}, ${v})`), truthy: nfun("truthy", [{ k: "expect" }], VOID, ([e]) => `$ex.test.truthy(${e})`), falsy: nfun("falsy", [{ k: "expect" }], VOID, ([e]) => `$ex.test.falsy(${e})`), raises: nfun("raises", [{ k: "expect" }, fun([], ANY)], VOID, ([e, f]) => `$ex.test.raises(${e}, ${f})`), fails: nfun("fails", [{ k: "expect" }], VOID, ([e]) => `$ex.test.fails(${e})`), contains: nfun("contains", [{ k: "expect" }, ANY], VOID, ([e, v]) => `$ex.test.contains(${e}, ${v})`), matches: nfun("matches", [{ k: "expect" }, STRING], VOID, ([e, p]) => `$ex.test.matches(${e}, ${p})`), approx: nfun("approx", [{ k: "expect" }, FLOAT], VOID, ([e, v]) => `$ex.test.approx(${e}, ${v})`), }; // ---------- std.io ---------- const ioFuns: Record = { print: nfun("print", [ANY], VOID, ([v]) => `$ex.io.print(${v})`), println: nfun("println", [ANY], VOID, ([v]) => `$ex.io.println(${v})`), }; // ---------- module tables ---------- /** always-available builtin funs (not module-scoped): Result constructors */ export const BUILTIN_FUNS: Record = { ok: { ...nfun("ok", [ANY], result(ANY), ([v]) => `$ex.ok(${v})`), builtin: "ok" }, err: { ...nfun("err", [ANY], result(ERR), ([e]) => `$ex.err(${e})`), builtin: "err" }, }; export const NATIVE_MODULES: Record = { "std.string": { kind: "native", id: "std/string", name: "std.string", exports: stringFuns }, "std.collections": { kind: "native", id: "std/collections", name: "std.collections", exports: collectionsFuns }, "std.math": { kind: "native", id: "std/math", name: "std.math", exports: mathFuns }, "std.test": { kind: "native", id: "std/test", name: "std.test", exports: testFuns }, "std.io": { kind: "native", id: "std/io", name: "std.io", exports: ioFuns }, }; // Builtin method tables — receiver type → method. These resolve `x.method(...)`. const primMethods: Record> = { String: { ...stringFuns, }, Int: { abs: nfun("abs", [INT], INT, ([x]) => `Math.abs(${x})`), clamp: nfun("clamp", [INT, INT, INT], INT, ([v, lo, hi]) => `Math.min(${hi}, Math.max(${lo}, ${v}))`), min: nfun("min", [INT, INT], INT, ([a, b]) => `Math.min(${a}, ${b})`), max: nfun("max", [INT, INT], INT, ([a, b]) => `Math.max(${a}, ${b})`), toString: nfun("toString", [INT], STRING, ([x]) => `String(${x})`), }, Float: { abs: nfun("abs", [FLOAT], FLOAT, ([x]) => `Math.abs(${x})`), floor: nfun("floor", [FLOAT], FLOAT, ([x]) => `Math.floor(${x})`), ceil: nfun("ceil", [FLOAT], FLOAT, ([x]) => `Math.ceil(${x})`), round: nfun("round", [FLOAT], FLOAT, ([x]) => `Math.round(${x})`), sqrt: nfun("sqrt", [FLOAT], FLOAT, ([x]) => `Math.sqrt(${x})`), clamp: nfun("clamp", [FLOAT, FLOAT, FLOAT], FLOAT, ([v, lo, hi]) => `Math.min(${hi}, Math.max(${lo}, ${v}))`), toString: nfun("toString", [FLOAT], STRING, ([x]) => `String(${x})`), }, }; export const LIST_METHODS: Record = { map: collectionsFuns["map"] as NativeFun, filter: collectionsFuns["filter"] as NativeFun, flatMap: collectionsFuns["flatMap"] as NativeFun, reduce: collectionsFuns["reduce"] as NativeFun, sum: collectionsFuns["sum"] as NativeFun, fsum: collectionsFuns["fsum"] as NativeFun, count: collectionsFuns["count"] as NativeFun, first: collectionsFuns["first"] as NativeFun, last: collectionsFuns["last"] as NativeFun, contains: collectionsFuns["contains"] as NativeFun, sort: collectionsFuns["sort"] as NativeFun, reverse: collectionsFuns["reverse"] as NativeFun, indexed: collectionsFuns["indexed"] as NativeFun, unique: collectionsFuns["unique"] as NativeFun, min: collectionsFuns["min"] as NativeFun, max: collectionsFuns["max"] as NativeFun, fmin: collectionsFuns["fmin"] as NativeFun, fmax: collectionsFuns["fmax"] as NativeFun, splitFirst: collectionsFuns["splitFirst"] as NativeFun, join: collectionsFuns["join"] as NativeFun, }; const setMethods: Record = { contains: nfun("contains", [setType(T(0)), T(0)], BOOL, ([s, v]) => `${s}.has(${v})`, ["T"]), size: nfun("size", [setType(T(0))], INT, ([s]) => `${s}.size`, ["T"]), toList: nfun("toList", [setType(T(0))], list(T(0)), ([s]) => `[...${s}]`, ["T"]), }; const mapMethods: Record = { get: nfun("get", [mapType(T(0), U(1)), T(0)], optional(U(1)), ([m, k]) => `${m}.has(${k}) ? ${m}.get(${k}) : undefined`, ["T", "U"]), containsKey: nfun("containsKey", [mapType(T(0), U(1)), T(0)], BOOL, ([m, k]) => `${m}.has(${k})`, ["T", "U"]), keys: nfun("keys", [mapType(T(0), U(1))], list(T(0)), ([m]) => `[...${m}.keys()]`, ["T", "U"]), values: nfun("values", [mapType(T(0), U(1))], list(U(1)), ([m]) => `[...${m}.values()]`, ["T", "U"]), size: nfun("size", [mapType(T(0), U(1))], INT, ([m]) => `${m}.size`, ["T", "U"]), }; const bytesMethods: Record = { length: nfun("length", [BYTES], INT, ([b]) => `${b}.length`), at: nfun("at", [BYTES, INT], INT, ([b, i]) => `${b}[${i}]`), toList: nfun("toList", [BYTES], list(INT), ([b]) => `[...${b}]`), }; const expectMethods: Record = { eq: testFuns["eq"] as NativeFun, ne: testFuns["ne"] as NativeFun, truthy: testFuns["truthy"] as NativeFun, falsy: testFuns["falsy"] as NativeFun, raises: testFuns["raises"] as NativeFun, fails: testFuns["fails"] as NativeFun, contains: testFuns["contains"] as NativeFun, matches: testFuns["matches"] as NativeFun, approx: testFuns["approx"] as NativeFun, }; const rangeMethods: Record = { map: LIST_METHODS["map"]!, toList: nfun("toList", [{ k: "range" }], list(ANY), ([r]) => `[...${r}]`), filter: LIST_METHODS["filter"]!, count: LIST_METHODS["count"]!, first: LIST_METHODS["first"]!, last: LIST_METHODS["last"]!, sum: LIST_METHODS["sum"]!, contains: LIST_METHODS["contains"]!, splitFirst: LIST_METHODS["splitFirst"]!, }; const errMethods: Record = { message: nfun("message", [ERR], STRING, ([e]) => `${e}.message`), stack: nfun("stack", [ERR], STRING, ([e]) => `${e}.stack`), }; const anyMethods: Record = { require: nfun("require", [ANY, STRING], ANY, ([v, msg]) => `$ex.require(${v}, ${msg})`), }; export function findBuiltinMethod(type: Type, name: string): NativeFun | null { switch (type.k) { case "prim": return primMethods[type.name]?.[name] ?? null; case "list": return LIST_METHODS[name] ?? null; case "set": return setMethods[name] ?? null; case "map": return mapMethods[name] ?? null; case "bytes": return bytesMethods[name] ?? null; case "expect": return expectMethods[name] ?? null; case "range": return rangeMethods[name] ?? null; case "err": return errMethods[name] ?? null; case "any": return anyMethods[name] ?? null; case "optional": if (name === "require") { return nfun("require", [optional(T(0)), STRING], T(0), ([v, msg]) => `$ex.require(${v}, ${msg})`, ["T"]); } return null; case "result": if (name === "require") { return nfun("require", [result(T(0)), STRING], T(0), ([v, msg]) => `$ex.requireResult(${v}, ${msg})`, ["T"]); } return null; default: return null; } } export function nativeConstValue(exp: NativeExport): { type: Type; gen: () => string } | null { if (!("type" in exp && "gen" in exp)) return null; return { type: exp.type, gen: exp.gen }; } /** find a native const export by module id (e.g. "std/math") and name */ export function findNativeConst(moduleId: string, name: string): NativeConst | null { const mod = NATIVE_MODULES[moduleId.replace("/", ".")]; const exp = mod?.exports[name]; if (exp && "type" in exp) return exp as NativeConst; return null; } // Number parsing builtins used by Int.parse / Float.parse export const INT_PARSE = nfun("parse", [STRING], result(INT), ([s]) => `$ex.n.parseInt(${s})`); export const FLOAT_PARSE = nfun("parse", [STRING], result(FLOAT), ([s]) => `$ex.n.parseFloat(${s})`); // Bytes constructor export const BYTES_FROM_LIST = nfun("Bytes", [list(INT)], BYTES, ([xs]) => `Uint8Array.from(${xs})`);