@kumikijs/compiler 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +15 -1
- package/dist/index.js +137 -26
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -469,6 +469,12 @@ declare function parseCapabilityManifest(raw: unknown): ManifestResult;
|
|
|
469
469
|
type CodegenOptions = {
|
|
470
470
|
runtimeSpecifier: string; /** Emit the in-language `test` definitions (`__kumikiTests`). Off for production builds. */
|
|
471
471
|
includeTests?: boolean;
|
|
472
|
+
/**
|
|
473
|
+
* Emit `export default App;` instead of auto-mounting to `#root`. Use when the
|
|
474
|
+
* module is imported (e.g. the Vite plugin / Web Component embedding) rather
|
|
475
|
+
* than run as a standalone page bundle.
|
|
476
|
+
*/
|
|
477
|
+
exportApp?: boolean;
|
|
472
478
|
};
|
|
473
479
|
declare function codegen(program: Program, opts: CodegenOptions): string;
|
|
474
480
|
/**
|
|
@@ -539,6 +545,14 @@ type ExtendedCodegenOptions = CodegenOptions & {
|
|
|
539
545
|
declare function inlineRuntime(generatedJs: string, runtimeBundleJs: string): string;
|
|
540
546
|
declare function compile(source: string, opts: ExtendedCodegenOptions): CompileResult;
|
|
541
547
|
//#endregion
|
|
548
|
+
//#region src/dts.d.ts
|
|
549
|
+
/**
|
|
550
|
+
* Generate a TypeScript declaration for a Kumiki program's slot/effect/type
|
|
551
|
+
* surface. Returns module source (importing nothing) suitable for writing next
|
|
552
|
+
* to the `.kumiki` file and importing for typed provider authoring.
|
|
553
|
+
*/
|
|
554
|
+
declare function generateDts(program: Program): string;
|
|
555
|
+
//#endregion
|
|
542
556
|
//#region src/lexer.d.ts
|
|
543
557
|
declare function lex(source: string): Token[];
|
|
544
558
|
//#endregion
|
|
@@ -549,4 +563,4 @@ declare class ParseError extends Error {
|
|
|
549
563
|
}
|
|
550
564
|
declare function parse(tokens: Token[]): Program;
|
|
551
565
|
//#endregion
|
|
552
|
-
export { type AppDef, type BinOp, type CapabilityManifest, type CodegenOptions, type CompileFail, type CompileOk, type CompileResult, type Def, type EffectDef, type EventPattern, type Expr, type ExtendedCodegenOptions, FIELD_ACCESS_SHORTCUTS, type FnDef, KNOWN_MEMBERS, KNOWN_METHODS, type KumikiError, type Lvalue, type ManifestResult, type MatchArm, type MotionDef, ParseError, type Pattern, type PolicyExpr, type Pos, type Program, RUNTIME_HELPERS, type ReducerDef, type Refinement, type RetryExpr, STANDARD_CAPABILITIES, type SlotDef, type Statement, type TestDef, type ThemeDef, type ThemeValue, type TileArg, type TileDef, type TileExpr, type TileMatchArm, type TileProp, type Token, type TypeDef, type TypeExpr, type UiEventKind, check, codegen, compile, inlineRuntime, lex, parse, parseCapabilityManifest };
|
|
566
|
+
export { type AppDef, type BinOp, type CapabilityManifest, type CodegenOptions, type CompileFail, type CompileOk, type CompileResult, type Def, type EffectDef, type EventPattern, type Expr, type ExtendedCodegenOptions, FIELD_ACCESS_SHORTCUTS, type FnDef, KNOWN_MEMBERS, KNOWN_METHODS, type KumikiError, type Lvalue, type ManifestResult, type MatchArm, type MotionDef, ParseError, type Pattern, type PolicyExpr, type Pos, type Program, RUNTIME_HELPERS, type ReducerDef, type Refinement, type RetryExpr, STANDARD_CAPABILITIES, type SlotDef, type Statement, type TestDef, type ThemeDef, type ThemeValue, type TileArg, type TileDef, type TileExpr, type TileMatchArm, type TileProp, type Token, type TypeDef, type TypeExpr, type UiEventKind, check, codegen, compile, generateDts, inlineRuntime, lex, parse, parseCapabilityManifest };
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ function codegen(program, opts) {
|
|
|
26
26
|
lines.push("");
|
|
27
27
|
lines.push("const _s = _stdlib;");
|
|
28
28
|
lines.push("");
|
|
29
|
+
lines.push("function createApp() {");
|
|
29
30
|
for (const fn of fns) lines.push(genFn(fn, ctx));
|
|
30
31
|
lines.push("const _effects = {");
|
|
31
32
|
for (const eff of effects) lines.push(` ${JSON.stringify(eff.name)}: ${genEffect(eff, ctx)},`);
|
|
@@ -78,21 +79,29 @@ function codegen(program, opts) {
|
|
|
78
79
|
lines.push(` themeName: ${themeRef},`);
|
|
79
80
|
lines.push(" motions: _motions,");
|
|
80
81
|
lines.push("};");
|
|
81
|
-
lines.push("");
|
|
82
|
-
lines.push("globalThis.__kumikiApp = App;");
|
|
83
82
|
if (opts.includeTests && tests.length > 0) {
|
|
84
|
-
lines.push("");
|
|
85
83
|
lines.push("const _tilesById = {");
|
|
86
84
|
for (const tile of tiles) lines.push(` ${JSON.stringify(tile.name)}: (${jsName("$1")}) => ${genTile(tile, ctx)},`);
|
|
87
85
|
lines.push("};");
|
|
88
|
-
lines.push("
|
|
86
|
+
lines.push("App._tilesById = _tilesById;");
|
|
87
|
+
}
|
|
88
|
+
lines.push(" return App;");
|
|
89
|
+
lines.push("}");
|
|
90
|
+
lines.push("");
|
|
91
|
+
lines.push("const App = createApp();");
|
|
92
|
+
lines.push("globalThis.__kumikiApp = App;");
|
|
93
|
+
if (opts.includeTests && tests.length > 0) {
|
|
94
|
+
lines.push("");
|
|
89
95
|
lines.push("const __kumikiTests = [");
|
|
90
96
|
for (const t of tests) lines.push(genTest(t, ctx));
|
|
91
97
|
lines.push("];");
|
|
92
98
|
lines.push("globalThis.__kumikiTests = __kumikiTests;");
|
|
93
99
|
}
|
|
94
100
|
lines.push("");
|
|
95
|
-
|
|
101
|
+
if (opts.exportApp) {
|
|
102
|
+
lines.push("export default App;");
|
|
103
|
+
lines.push("export { createApp };");
|
|
104
|
+
} else lines.push(`mount(App, document.getElementById("root"), { providers: globalThis.__kumikiProviders, ...globalThis.__kumikiMount });`);
|
|
96
105
|
return lines.join("\n");
|
|
97
106
|
}
|
|
98
107
|
function recordField(e, name) {
|
|
@@ -119,14 +128,14 @@ function genTest(t, gen) {
|
|
|
119
128
|
name: ${nameJs},
|
|
120
129
|
kind: "reducer-test",
|
|
121
130
|
run: () => {
|
|
122
|
-
_s.resetLive(
|
|
131
|
+
_s.resetLive(App.live, App.slots, ${slotsJs});
|
|
123
132
|
const _el = ${elJs};
|
|
124
|
-
const _r =
|
|
133
|
+
const _r = App.reducers.find((r) => r.name === ${JSON.stringify(t.target)});
|
|
125
134
|
if (!_r) throw new Error("reducer ${t.target} not found");
|
|
126
135
|
let _res = null, _panic = null;
|
|
127
|
-
try { _res = _r.apply(
|
|
136
|
+
try { _res = _r.apply(App.live, { $el: _el, $event: _el }); }
|
|
128
137
|
catch (e) { _panic = (e && e.message) ? e.message : String(e); }
|
|
129
|
-
return _s.runReducerTest({ name: ${nameJs}, givenSlots: { ...
|
|
138
|
+
return _s.runReducerTest({ name: ${nameJs}, givenSlots: { ...App.live }, result: _res, panic: _panic, expect: ${expectJs} });
|
|
130
139
|
},
|
|
131
140
|
},`;
|
|
132
141
|
}
|
|
@@ -139,8 +148,8 @@ function genTest(t, gen) {
|
|
|
139
148
|
name: ${nameJs},
|
|
140
149
|
kind: "tile-test",
|
|
141
150
|
run: () => {
|
|
142
|
-
_s.resetLive(
|
|
143
|
-
const _actual = _tilesById[${JSON.stringify(t.target)}](${inJs});
|
|
151
|
+
_s.resetLive(App.live, App.slots, ${slotsJs});
|
|
152
|
+
const _actual = App._tilesById[${JSON.stringify(t.target)}](${inJs});
|
|
144
153
|
const _expected = ${expectedJs};
|
|
145
154
|
return _s.runTileTest({ name: ${nameJs}, actual: _actual, expected: _expected });
|
|
146
155
|
},
|
|
@@ -196,23 +205,29 @@ function genFn(fn, gen) {
|
|
|
196
205
|
]));
|
|
197
206
|
return `function ${jsName(fn.name)}(${params}) { return ${jsOfExpr(fn.body, ctx)}; }`;
|
|
198
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* The built-in implementation call for a standard capability, given the request
|
|
210
|
+
* variable name. Returns null for custom capabilities (no built-in — a host
|
|
211
|
+
* provider is required).
|
|
212
|
+
*/
|
|
213
|
+
function builtinEffectCall(eff, reqVar) {
|
|
214
|
+
if (eff.cap === "storage.read") return `builtinEffects.storageRead(${eff.mapRequest ? `{ key: ${reqVar}.key }` : reqVar})`;
|
|
215
|
+
if (eff.cap === "storage.write") return `builtinEffects.storageWrite(${eff.mapRequest ? `{ key: ${reqVar}.key, value: ${reqVar}.value }` : reqVar})`;
|
|
216
|
+
if (eff.cap.startsWith("http.")) {
|
|
217
|
+
const method = eff.cap.slice(5).toUpperCase();
|
|
218
|
+
return `builtinEffects.httpFetch(${JSON.stringify(method)}, ${reqVar}, "")`;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
199
222
|
function genEffect(eff, gen) {
|
|
223
|
+
const capJs = JSON.stringify(eff.cap);
|
|
224
|
+
const reqVar = eff.mapRequest ? "req" : "input";
|
|
225
|
+
const tail = `const p = caps.provider(${capJs}); if (p) return p(${reqVar}, caps); return ${builtinEffectCall(eff, reqVar) ?? `{ kind: "err", value: { message: ${JSON.stringify(`Capability ${eff.cap} has no provider`)} } }`};`;
|
|
200
226
|
let invokeBody;
|
|
201
|
-
if (eff.
|
|
202
|
-
const mapJs = jsOfExpr(eff.mapRequest, makeEvalCtx(gen, new Set(["$1"])));
|
|
203
|
-
invokeBody = `async (${jsName("$1")}, _caps) => { const req = ${mapJs}; return builtinEffects.storageRead({ key: req.key }); }`;
|
|
204
|
-
} else invokeBody = `async (input) => builtinEffects.storageRead(input)`;
|
|
205
|
-
else if (eff.cap === "storage.write") if (eff.mapRequest) {
|
|
227
|
+
if (eff.mapRequest) {
|
|
206
228
|
const mapJs = jsOfExpr(eff.mapRequest, makeEvalCtx(gen, new Set(["$1"])));
|
|
207
|
-
invokeBody = `async (${jsName("$1")},
|
|
208
|
-
} else invokeBody = `async (input) =>
|
|
209
|
-
else if (eff.cap.startsWith("http.")) {
|
|
210
|
-
const method = eff.cap.slice(5).toUpperCase();
|
|
211
|
-
if (eff.mapRequest) {
|
|
212
|
-
const mapJs = jsOfExpr(eff.mapRequest, makeEvalCtx(gen, new Set(["$1"])));
|
|
213
|
-
invokeBody = `async (${jsName("$1")}, _caps) => { const req = ${mapJs}; return builtinEffects.httpFetch(${JSON.stringify(method)}, req, ""); }`;
|
|
214
|
-
} else invokeBody = `async (input) => builtinEffects.httpFetch(${JSON.stringify(method)}, input, "")`;
|
|
215
|
-
} else invokeBody = `async () => ({ kind: "err", value: { message: "Capability ${eff.cap} not implemented" } })`;
|
|
229
|
+
invokeBody = `async (${jsName("$1")}, caps) => { const req = ${mapJs}; ${tail} }`;
|
|
230
|
+
} else invokeBody = `async (input, caps) => { ${tail} }`;
|
|
216
231
|
return `{
|
|
217
232
|
name: ${JSON.stringify(eff.name)},
|
|
218
233
|
cap: ${JSON.stringify(eff.cap)},
|
|
@@ -4038,4 +4053,100 @@ function compile(source, opts) {
|
|
|
4038
4053
|
};
|
|
4039
4054
|
}
|
|
4040
4055
|
//#endregion
|
|
4041
|
-
|
|
4056
|
+
//#region src/dts.ts
|
|
4057
|
+
const PRIM_TS = {
|
|
4058
|
+
Int: "number",
|
|
4059
|
+
Float: "number",
|
|
4060
|
+
Time: "number",
|
|
4061
|
+
Text: "string",
|
|
4062
|
+
Bool: "boolean",
|
|
4063
|
+
Unit: "null",
|
|
4064
|
+
Bytes: "Uint8Array"
|
|
4065
|
+
};
|
|
4066
|
+
const KNOWN_SCALAR = {
|
|
4067
|
+
Url: "string",
|
|
4068
|
+
Email: "string",
|
|
4069
|
+
Uuid: "string",
|
|
4070
|
+
HttpStatus: "number",
|
|
4071
|
+
Duration: "number"
|
|
4072
|
+
};
|
|
4073
|
+
function tsOfType(t, ctx) {
|
|
4074
|
+
switch (t.kind) {
|
|
4075
|
+
case "TypePrim": return PRIM_TS[t.name] ?? "unknown";
|
|
4076
|
+
case "TypeRef":
|
|
4077
|
+
if (ctx.userTypes.has(t.name)) return t.name;
|
|
4078
|
+
return KNOWN_SCALAR[t.name] ?? "unknown";
|
|
4079
|
+
case "TypeApp": {
|
|
4080
|
+
const arg = (i) => t.args[i] ? tsOfType(t.args[i], ctx) : "unknown";
|
|
4081
|
+
switch (t.name) {
|
|
4082
|
+
case "List": return `${wrap(arg(0))}[]`;
|
|
4083
|
+
case "Map": return `Record<string, ${arg(1)}>`;
|
|
4084
|
+
case "Set": return "Record<string, true>";
|
|
4085
|
+
case "Option": return `{ _tag: "Some"; _0: ${arg(0)} } | { _tag: "None" }`;
|
|
4086
|
+
case "Result": return `{ _tag: "Ok"; _0: ${arg(0)} } | { _tag: "Err"; _0: ${arg(1)} }`;
|
|
4087
|
+
case "Tuple": return `[${t.args.map((a) => tsOfType(a, ctx)).join(", ")}]`;
|
|
4088
|
+
default: return "unknown";
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
case "TypeRecord": return `{ ${t.fields.map((f) => `${f.name}: ${tsOfType(f.type, ctx)}`).join("; ")} }`;
|
|
4092
|
+
case "TypeUnion": return t.variants.map((v) => variantTs(v.name, v.payloads, ctx)).join(" | ");
|
|
4093
|
+
case "TypeNominal":
|
|
4094
|
+
case "TypeRefinement": return tsOfType(t.inner, ctx);
|
|
4095
|
+
default: return "unknown";
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
/** One tagged variant: `{ _tag: "Name" }` or `{ _tag: "Name"; _0: P0; … }`. */
|
|
4099
|
+
function variantTs(name, payloads, ctx) {
|
|
4100
|
+
if (payloads.length === 0) return `{ _tag: ${JSON.stringify(name)} }`;
|
|
4101
|
+
const fields = payloads.map((p, i) => `_${i}: ${tsOfType(p, ctx)}`).join("; ");
|
|
4102
|
+
return `{ _tag: ${JSON.stringify(name)}; ${fields} }`;
|
|
4103
|
+
}
|
|
4104
|
+
/** Parenthesize a union so `T[]` binds correctly (`(A | null)[]`). */
|
|
4105
|
+
function wrap(ts) {
|
|
4106
|
+
return ts.includes("|") ? `(${ts})` : ts;
|
|
4107
|
+
}
|
|
4108
|
+
/**
|
|
4109
|
+
* Generate a TypeScript declaration for a Kumiki program's slot/effect/type
|
|
4110
|
+
* surface. Returns module source (importing nothing) suitable for writing next
|
|
4111
|
+
* to the `.kumiki` file and importing for typed provider authoring.
|
|
4112
|
+
*/
|
|
4113
|
+
function generateDts(program) {
|
|
4114
|
+
const types = program.defs.filter((d) => d.kind === "TypeDef");
|
|
4115
|
+
const slots = program.defs.filter((d) => d.kind === "SlotDef");
|
|
4116
|
+
const effects = program.defs.filter((d) => d.kind === "EffectDef");
|
|
4117
|
+
const ctx = { userTypes: new Set(types.map((t) => t.name)) };
|
|
4118
|
+
const out = [];
|
|
4119
|
+
out.push("// Auto-generated by @kumikijs/compiler from the .kumiki source. Do not edit.");
|
|
4120
|
+
out.push("");
|
|
4121
|
+
out.push("/** A typed host implementation for one custom capability. */");
|
|
4122
|
+
out.push("export type Provider<In, Out> = (");
|
|
4123
|
+
out.push(" input: In,");
|
|
4124
|
+
out.push(" caps: { has(c: string): boolean; provider(c: string): unknown },");
|
|
4125
|
+
out.push(") =>");
|
|
4126
|
+
out.push(" | { kind: 'ok'; value: Out }");
|
|
4127
|
+
out.push(" | { kind: 'err'; value: unknown }");
|
|
4128
|
+
out.push(" | Promise<{ kind: 'ok'; value: Out } | { kind: 'err'; value: unknown }>;");
|
|
4129
|
+
out.push("");
|
|
4130
|
+
for (const t of types) {
|
|
4131
|
+
const params = t.params.length > 0 ? `<${t.params.join(", ")}>` : "";
|
|
4132
|
+
out.push(`export type ${t.name}${params} = ${tsOfType(t.body, ctx)};`);
|
|
4133
|
+
}
|
|
4134
|
+
if (types.length > 0) out.push("");
|
|
4135
|
+
out.push("export interface Slots {");
|
|
4136
|
+
for (const s of slots) out.push(` ${s.name}: ${tsOfType(s.type, ctx)};`);
|
|
4137
|
+
out.push("}");
|
|
4138
|
+
out.push("");
|
|
4139
|
+
const custom = effects.filter((e) => !STANDARD_CAPABILITIES.has(e.cap));
|
|
4140
|
+
out.push("/** Host implementations for this app's custom capabilities. */");
|
|
4141
|
+
out.push("export interface Providers {");
|
|
4142
|
+
for (const e of custom) {
|
|
4143
|
+
const input = tsOfType(e.inType, ctx);
|
|
4144
|
+
const output = tsOfType(e.outType, ctx);
|
|
4145
|
+
out.push(` ${JSON.stringify(e.cap)}?: Provider<${input}, ${output}>;`);
|
|
4146
|
+
}
|
|
4147
|
+
out.push("}");
|
|
4148
|
+
out.push("");
|
|
4149
|
+
return out.join("\n");
|
|
4150
|
+
}
|
|
4151
|
+
//#endregion
|
|
4152
|
+
export { FIELD_ACCESS_SHORTCUTS, KNOWN_MEMBERS, KNOWN_METHODS, ParseError, RUNTIME_HELPERS, STANDARD_CAPABILITIES, check, codegen, compile, generateDts, inlineRuntime, lex, parse, parseCapabilityManifest };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kumikijs/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kumiki compiler — lexer, parser, typechecker, and code generator.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"provenance": true
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@kumikijs/runtime": "0.
|
|
44
|
+
"@kumikijs/runtime": "0.5.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^25.9.1",
|