@lokascript/framework 2.0.0 → 2.2.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.
Files changed (138) hide show
  1. package/dist/aot/index.d.ts +1 -0
  2. package/dist/aot/index.d.ts.map +1 -1
  3. package/dist/aot/registry-bridge.d.ts +27 -0
  4. package/dist/aot/registry-bridge.d.ts.map +1 -0
  5. package/dist/api/create-dsl.d.ts.map +1 -1
  6. package/dist/api/dispatcher.d.ts +22 -0
  7. package/dist/api/dispatcher.d.ts.map +1 -1
  8. package/dist/api/domain-registry.d.ts +41 -0
  9. package/dist/api/domain-registry.d.ts.map +1 -1
  10. package/dist/api/index.js +1438 -21
  11. package/dist/api/index.js.map +1 -1
  12. package/dist/core/index.js +247 -10
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/core/pattern-matching/index.js +74 -10
  15. package/dist/core/pattern-matching/index.js.map +1 -1
  16. package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
  17. package/dist/core/tokenization/index.js +133 -0
  18. package/dist/core/tokenization/index.js.map +1 -1
  19. package/dist/core/tokenization/morphology/base-normalizer.d.ts +94 -0
  20. package/dist/core/tokenization/morphology/base-normalizer.d.ts.map +1 -0
  21. package/dist/core/tokenization/morphology/index.d.ts +3 -1
  22. package/dist/core/tokenization/morphology/index.d.ts.map +1 -1
  23. package/dist/core/types.d.ts +81 -2
  24. package/dist/core/types.d.ts.map +1 -1
  25. package/dist/core/types.js +40 -0
  26. package/dist/core/types.js.map +1 -1
  27. package/dist/feedback/confidence-gate.d.ts +28 -0
  28. package/dist/feedback/confidence-gate.d.ts.map +1 -0
  29. package/dist/feedback/feedback-formatter.d.ts +21 -0
  30. package/dist/feedback/feedback-formatter.d.ts.map +1 -0
  31. package/dist/feedback/index.d.ts +11 -0
  32. package/dist/feedback/index.d.ts.map +1 -0
  33. package/dist/feedback/pattern-tracker.d.ts +47 -0
  34. package/dist/feedback/pattern-tracker.d.ts.map +1 -0
  35. package/dist/feedback/types.d.ts +113 -0
  36. package/dist/feedback/types.d.ts.map +1 -0
  37. package/dist/generation/index.js +2 -1
  38. package/dist/generation/index.js.map +1 -1
  39. package/dist/index.cjs +3264 -132
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.ts +8 -2
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +3223 -132
  44. package/dist/index.js.map +1 -1
  45. package/dist/ir/explicit-parser.d.ts +86 -0
  46. package/dist/ir/explicit-parser.d.ts.map +1 -0
  47. package/dist/ir/explicit-renderer.d.ts +34 -0
  48. package/dist/ir/explicit-renderer.d.ts.map +1 -0
  49. package/dist/ir/from-interchange.d.ts +51 -0
  50. package/dist/ir/from-interchange.d.ts.map +1 -0
  51. package/dist/ir/index.d.ts +31 -0
  52. package/dist/ir/index.d.ts.map +1 -0
  53. package/dist/ir/index.js +1934 -0
  54. package/dist/ir/index.js.map +1 -0
  55. package/dist/ir/json-schema.d.ts +40 -0
  56. package/dist/ir/json-schema.d.ts.map +1 -0
  57. package/dist/ir/protocol-json.d.ts +73 -0
  58. package/dist/ir/protocol-json.d.ts.map +1 -0
  59. package/dist/ir/references.d.ts +19 -0
  60. package/dist/ir/references.d.ts.map +1 -0
  61. package/dist/ir/to-runtime-ast.d.ts +53 -0
  62. package/dist/ir/to-runtime-ast.d.ts.map +1 -0
  63. package/dist/ir/types.d.ts +151 -0
  64. package/dist/ir/types.d.ts.map +1 -0
  65. package/dist/parsing/index.js +74 -10
  66. package/dist/parsing/index.js.map +1 -1
  67. package/dist/prompts/index.d.ts +9 -0
  68. package/dist/prompts/index.d.ts.map +1 -0
  69. package/dist/prompts/prompt-generator.d.ts +49 -0
  70. package/dist/prompts/prompt-generator.d.ts.map +1 -0
  71. package/dist/prompts/types.d.ts +64 -0
  72. package/dist/prompts/types.d.ts.map +1 -0
  73. package/dist/schema/command-schema.d.ts +17 -0
  74. package/dist/schema/command-schema.d.ts.map +1 -1
  75. package/dist/schema/index.js.map +1 -1
  76. package/dist/testing/index.js +8673 -8260
  77. package/dist/testing/index.js.map +1 -1
  78. package/dist/training/index.d.ts +11 -0
  79. package/dist/training/index.d.ts.map +1 -0
  80. package/dist/training/jsonl-writer.d.ts +42 -0
  81. package/dist/training/jsonl-writer.d.ts.map +1 -0
  82. package/dist/training/schema-synthesizer.d.ts +20 -0
  83. package/dist/training/schema-synthesizer.d.ts.map +1 -0
  84. package/dist/training/types.d.ts +73 -0
  85. package/dist/training/types.d.ts.map +1 -0
  86. package/package.json +5 -1
  87. package/src/aot/index.ts +1 -0
  88. package/src/aot/registry-bridge.test.ts +104 -0
  89. package/src/aot/registry-bridge.ts +66 -0
  90. package/src/api/create-dsl.test.ts +306 -0
  91. package/src/api/create-dsl.ts +21 -0
  92. package/src/api/dispatcher.test.ts +66 -0
  93. package/src/api/dispatcher.ts +77 -9
  94. package/src/api/domain-registry.ts +120 -4
  95. package/src/core/pattern-matching/pattern-matcher.test.ts +131 -0
  96. package/src/core/pattern-matching/pattern-matcher.ts +102 -19
  97. package/src/core/tokenization/morphology/base-normalizer.ts +249 -0
  98. package/src/core/tokenization/morphology/index.ts +3 -1
  99. package/src/core/types.ts +147 -2
  100. package/src/feedback/confidence-gate.test.ts +111 -0
  101. package/src/feedback/confidence-gate.ts +70 -0
  102. package/src/feedback/feedback-formatter.test.ts +198 -0
  103. package/src/feedback/feedback-formatter.ts +298 -0
  104. package/src/feedback/index.ts +24 -0
  105. package/src/feedback/pattern-tracker.test.ts +198 -0
  106. package/src/feedback/pattern-tracker.ts +140 -0
  107. package/src/feedback/types.ts +149 -0
  108. package/src/generation/pattern-generator.ts +2 -2
  109. package/src/index.ts +29 -0
  110. package/src/integration/llm-lse-integration.test.ts +457 -0
  111. package/src/ir/conformance.test.ts +120 -0
  112. package/src/ir/explicit-parser.test.ts +612 -0
  113. package/src/ir/explicit-parser.ts +793 -0
  114. package/src/ir/explicit-renderer.test.ts +229 -0
  115. package/src/ir/explicit-renderer.ts +198 -0
  116. package/src/ir/from-interchange.test.ts +853 -0
  117. package/src/ir/from-interchange.ts +594 -0
  118. package/src/ir/index.ts +77 -0
  119. package/src/ir/json-schema.test.ts +281 -0
  120. package/src/ir/json-schema.ts +299 -0
  121. package/src/ir/protocol-json.test.ts +1187 -0
  122. package/src/ir/protocol-json.ts +751 -0
  123. package/src/ir/references.test.ts +38 -0
  124. package/src/ir/references.ts +33 -0
  125. package/src/ir/round-trip.test.ts +557 -0
  126. package/src/ir/to-runtime-ast.ts +203 -0
  127. package/src/ir/types.ts +191 -0
  128. package/src/prompts/index.ts +15 -0
  129. package/src/prompts/prompt-generator.test.ts +386 -0
  130. package/src/prompts/prompt-generator.ts +531 -0
  131. package/src/prompts/types.ts +89 -0
  132. package/src/schema/command-schema.ts +19 -0
  133. package/src/training/index.ts +13 -0
  134. package/src/training/jsonl-writer.test.ts +138 -0
  135. package/src/training/jsonl-writer.ts +67 -0
  136. package/src/training/schema-synthesizer.test.ts +250 -0
  137. package/src/training/schema-synthesizer.ts +306 -0
  138. package/src/training/types.ts +103 -0
@@ -0,0 +1,594 @@
1
+ /**
2
+ * InterchangeNode → SemanticNode Converter
3
+ *
4
+ * Converts the expression-level InterchangeNode AST (from core/semantic parsers)
5
+ * into the command-level SemanticNode IR used by LSE, MCP tools, and domain DSLs.
6
+ *
7
+ * This bridges the two IR pipelines in the monorepo:
8
+ * - Pipeline A (InterchangeNode): full expression trees, 16 node types
9
+ * - Pipeline B (SemanticNode): command-level semantics, opaque expression values
10
+ *
11
+ * Expression subtrees are intentionally collapsed to ExpressionValue { raw: string }
12
+ * — LSE deliberately abstracts away expression-level detail.
13
+ *
14
+ * Uses structural typing for the input to avoid a dependency on the core package.
15
+ */
16
+
17
+ import type {
18
+ SemanticNode,
19
+ SemanticValue,
20
+ SemanticRole,
21
+ LoopVariant,
22
+ EventModifiers as SemanticEventModifiers,
23
+ } from '../core/types';
24
+ import {
25
+ createCommandNode,
26
+ createEventHandlerNode,
27
+ createConditionalNode,
28
+ createLoopNode,
29
+ createLiteral,
30
+ createSelector,
31
+ createReference,
32
+ createExpression,
33
+ createPropertyPath,
34
+ } from '../core/types';
35
+ import { DEFAULT_REFERENCES } from './references';
36
+
37
+ // =============================================================================
38
+ // Structural Input Types (no core package dependency)
39
+ // =============================================================================
40
+
41
+ /**
42
+ * Minimal interface matching InterchangeNode's structural shape.
43
+ * Uses the `type` discriminant for dispatch. Additional fields are
44
+ * accessed dynamically — this keeps the framework free of core imports.
45
+ */
46
+ interface INode {
47
+ readonly type: string;
48
+ readonly [key: string]: unknown;
49
+ }
50
+
51
+ // =============================================================================
52
+ // Main Converter
53
+ // =============================================================================
54
+
55
+ /**
56
+ * Convert an InterchangeNode (expression-level AST) to a SemanticNode (command-level IR).
57
+ *
58
+ * Handles all 16 InterchangeNode types:
59
+ * - Command/event nodes map to corresponding SemanticNode kinds
60
+ * - Control flow nodes (if, repeat, foreach, while) map to conditional/loop kinds
61
+ * - Expression nodes are wrapped as `get` commands with the value as patient role
62
+ *
63
+ * @param node - An InterchangeNode (or structurally compatible object)
64
+ * @returns A SemanticNode suitable for LSE rendering, protocol JSON, or MCP tools
65
+ */
66
+ export function fromInterchangeNode(node: INode): SemanticNode {
67
+ if (!node || typeof node !== 'object') {
68
+ return createCommandNode('unknown', {});
69
+ }
70
+
71
+ switch (node.type) {
72
+ case 'event':
73
+ return convertEvent(node);
74
+
75
+ case 'command':
76
+ return convertCommand(node);
77
+
78
+ case 'if':
79
+ return convertIf(node);
80
+
81
+ case 'repeat':
82
+ return convertRepeat(node);
83
+
84
+ case 'foreach':
85
+ return convertForEach(node);
86
+
87
+ case 'while':
88
+ return convertWhile(node);
89
+
90
+ // Expression nodes — wrap as `get` command with patient role
91
+ default:
92
+ return createCommandNode('get', { patient: convertValue(node) });
93
+ }
94
+ }
95
+
96
+ // =============================================================================
97
+ // Node Converters
98
+ // =============================================================================
99
+
100
+ function convertEvent(node: INode): SemanticNode {
101
+ const event = String(node.event ?? 'click');
102
+ const bodyNodes = Array.isArray(node.body) ? (node.body as INode[]) : [];
103
+ const body = bodyNodes.map(fromInterchangeNode);
104
+
105
+ const roles: Record<SemanticRole, SemanticValue> = {
106
+ event: createLiteral(event),
107
+ };
108
+
109
+ // Preserve event target if present
110
+ if (node.target && typeof node.target === 'object') {
111
+ roles.source = convertValue(node.target as INode);
112
+ }
113
+
114
+ // Bridge event modifiers
115
+ const modifiers = node.modifiers as Record<string, unknown> | undefined;
116
+ let eventModifiers: SemanticEventModifiers | undefined;
117
+
118
+ if (modifiers) {
119
+ eventModifiers = bridgeEventModifiers(modifiers);
120
+
121
+ // InterchangeNode-only modifiers → encode as roles
122
+ if (modifiers.prevent) roles.prevent = createLiteral(true);
123
+ if (modifiers.stop) roles.stop = createLiteral(true);
124
+ if (modifiers.passive) roles.passive = createLiteral(true);
125
+ if (modifiers.capture) roles.capture = createLiteral(true);
126
+ }
127
+
128
+ return createEventHandlerNode('on', roles, body, undefined, eventModifiers);
129
+ }
130
+
131
+ function convertCommand(node: INode): SemanticNode {
132
+ const name = String(node.name ?? 'unknown');
133
+ const args = Array.isArray(node.args) ? (node.args as INode[]) : [];
134
+ const target =
135
+ node.target && typeof node.target === 'object' ? (node.target as INode) : undefined;
136
+ const existingRoles = node.roles as Record<string, INode> | undefined;
137
+ const modifiers = node.modifiers as Record<string, unknown> | undefined;
138
+
139
+ let roles: Record<SemanticRole, SemanticValue>;
140
+
141
+ if (existingRoles && typeof existingRoles === 'object' && Object.keys(existingRoles).length > 0) {
142
+ // Semantic parser path: roles already present
143
+ roles = {};
144
+ for (const [role, value] of Object.entries(existingRoles)) {
145
+ if (value && typeof value === 'object' && 'type' in value) {
146
+ roles[role] = convertValue(value as INode);
147
+ }
148
+ }
149
+ } else {
150
+ // Core parser path: infer roles from command name
151
+ roles = inferRoles(name, args, modifiers, target);
152
+ }
153
+
154
+ return createCommandNode(name, roles);
155
+ }
156
+
157
+ function convertIf(node: INode): SemanticNode {
158
+ const condition =
159
+ node.condition && typeof node.condition === 'object'
160
+ ? convertValue(node.condition as INode)
161
+ : createExpression('true');
162
+
163
+ const thenBody = Array.isArray(node.thenBranch) ? (node.thenBranch as INode[]) : [];
164
+ const thenBranch = thenBody.map(fromInterchangeNode);
165
+
166
+ // Handle else branch — may include chained else-if
167
+ let elseBranch: SemanticNode[] | undefined;
168
+
169
+ const elseIfBranches = node.elseIfBranches as
170
+ | ReadonlyArray<{ condition: INode; body: INode[] }>
171
+ | undefined;
172
+
173
+ if (elseIfBranches && elseIfBranches.length > 0) {
174
+ // Chain else-if branches as nested conditionals
175
+ elseBranch = [chainElseIfs(elseIfBranches, node.elseBranch as INode[] | undefined)];
176
+ } else if (Array.isArray(node.elseBranch) && (node.elseBranch as INode[]).length > 0) {
177
+ elseBranch = (node.elseBranch as INode[]).map(fromInterchangeNode);
178
+ }
179
+
180
+ const roles: Record<SemanticRole, SemanticValue> = { condition };
181
+ return createConditionalNode('if', roles, thenBranch, elseBranch);
182
+ }
183
+
184
+ function chainElseIfs(
185
+ branches: ReadonlyArray<{ condition: INode; body: INode[] }>,
186
+ finalElse: INode[] | undefined
187
+ ): SemanticNode {
188
+ const [first, ...rest] = branches;
189
+ const condition = convertValue(first.condition as INode);
190
+ const thenBranch = first.body.map(fromInterchangeNode);
191
+
192
+ let elseBranch: SemanticNode[] | undefined;
193
+ if (rest.length > 0) {
194
+ elseBranch = [chainElseIfs(rest, finalElse)];
195
+ } else if (finalElse && finalElse.length > 0) {
196
+ elseBranch = finalElse.map(fromInterchangeNode);
197
+ }
198
+
199
+ return createConditionalNode('if', { condition }, thenBranch, elseBranch);
200
+ }
201
+
202
+ function convertRepeat(node: INode): SemanticNode {
203
+ const bodyNodes = Array.isArray(node.body) ? (node.body as INode[]) : [];
204
+ const body = bodyNodes.map(fromInterchangeNode);
205
+ const roles: Record<SemanticRole, SemanticValue> = {};
206
+
207
+ let variant: LoopVariant;
208
+
209
+ if (node.count !== undefined && node.count !== null) {
210
+ variant = 'times';
211
+ if (typeof node.count === 'number') {
212
+ roles.quantity = createLiteral(node.count, 'number');
213
+ } else if (typeof node.count === 'object') {
214
+ roles.quantity = convertValue(node.count as INode);
215
+ }
216
+ } else if (node.whileCondition && typeof node.whileCondition === 'object') {
217
+ variant = 'while';
218
+ roles.condition = convertValue(node.whileCondition as INode);
219
+ } else if (typeof node.untilEvent === 'string') {
220
+ variant = 'until';
221
+ roles.condition = createLiteral(node.untilEvent);
222
+ } else {
223
+ variant = 'forever';
224
+ }
225
+
226
+ return createLoopNode('repeat', roles, variant, body);
227
+ }
228
+
229
+ function convertForEach(node: INode): SemanticNode {
230
+ const bodyNodes = Array.isArray(node.body) ? (node.body as INode[]) : [];
231
+ const body = bodyNodes.map(fromInterchangeNode);
232
+ const roles: Record<SemanticRole, SemanticValue> = {};
233
+
234
+ if (node.collection && typeof node.collection === 'object') {
235
+ roles.source = convertValue(node.collection as INode);
236
+ }
237
+
238
+ const itemName = typeof node.itemName === 'string' ? node.itemName : undefined;
239
+ const indexName = typeof node.indexName === 'string' ? node.indexName : undefined;
240
+
241
+ return createLoopNode('repeat', roles, 'for', body, itemName, indexName);
242
+ }
243
+
244
+ function convertWhile(node: INode): SemanticNode {
245
+ const bodyNodes = Array.isArray(node.body) ? (node.body as INode[]) : [];
246
+ const body = bodyNodes.map(fromInterchangeNode);
247
+ const roles: Record<SemanticRole, SemanticValue> = {};
248
+
249
+ if (node.condition && typeof node.condition === 'object') {
250
+ roles.condition = convertValue(node.condition as INode);
251
+ }
252
+
253
+ return createLoopNode('repeat', roles, 'while', body);
254
+ }
255
+
256
+ // =============================================================================
257
+ // Value Conversion
258
+ // =============================================================================
259
+
260
+ /**
261
+ * Convert an InterchangeNode to a SemanticValue.
262
+ *
263
+ * Simple nodes map directly to typed SemanticValue variants.
264
+ * Complex expression nodes are collapsed to ExpressionValue { raw: string }.
265
+ */
266
+ export function convertValue(node: INode): SemanticValue {
267
+ switch (node.type) {
268
+ case 'literal':
269
+ return convertLiteral(node);
270
+
271
+ case 'selector':
272
+ return createSelector(String(node.value ?? ''), inferSelectorKind(String(node.value ?? '')));
273
+
274
+ case 'identifier':
275
+ return convertIdentifier(node);
276
+
277
+ case 'variable':
278
+ return convertVariable(node);
279
+
280
+ case 'possessive':
281
+ return createPropertyPath(
282
+ node.object && typeof node.object === 'object'
283
+ ? convertValue(node.object as INode)
284
+ : createReference('me'),
285
+ String(node.property ?? '')
286
+ );
287
+
288
+ // Complex expressions → collapse to raw string
289
+ case 'binary':
290
+ case 'unary':
291
+ case 'member':
292
+ case 'call':
293
+ case 'positional':
294
+ return createExpression(renderExpr(node));
295
+
296
+ default:
297
+ // Unknown type — render as expression
298
+ if ('value' in node) return createLiteral(String(node.value ?? ''));
299
+ return createExpression(renderExpr(node));
300
+ }
301
+ }
302
+
303
+ function convertLiteral(node: INode): SemanticValue {
304
+ const value = node.value;
305
+ if (value === null || value === undefined) return createLiteral('null');
306
+
307
+ const dataType =
308
+ typeof value === 'number'
309
+ ? ('number' as const)
310
+ : typeof value === 'boolean'
311
+ ? ('boolean' as const)
312
+ : undefined;
313
+
314
+ return createLiteral(value as string | number | boolean, dataType);
315
+ }
316
+
317
+ function convertIdentifier(node: INode): SemanticValue {
318
+ const value = String(node.value ?? '');
319
+ if (DEFAULT_REFERENCES.has(value)) {
320
+ return createReference(value);
321
+ }
322
+ return createLiteral(value);
323
+ }
324
+
325
+ function convertVariable(node: INode): SemanticValue {
326
+ const name = String(node.name ?? '');
327
+ const scope = String(node.scope ?? 'local');
328
+
329
+ switch (scope) {
330
+ case 'element':
331
+ return createExpression(`:${name}`);
332
+ case 'global':
333
+ return createExpression(`$${name}`);
334
+ default:
335
+ return createExpression(name);
336
+ }
337
+ }
338
+
339
+ // =============================================================================
340
+ // Expression Renderer
341
+ // =============================================================================
342
+
343
+ /**
344
+ * Recursively render an InterchangeNode expression tree to a string.
345
+ * Used to collapse complex expressions into ExpressionValue.raw.
346
+ */
347
+ export function renderExpr(node: INode): string {
348
+ if (!node || typeof node !== 'object') return '';
349
+
350
+ switch (node.type) {
351
+ case 'literal': {
352
+ const v = node.value;
353
+ if (v === null || v === undefined) return 'null';
354
+ if (typeof v === 'string') return /\s/.test(v) ? `"${v}"` : v;
355
+ return String(v);
356
+ }
357
+
358
+ case 'identifier':
359
+ return String(node.value ?? '');
360
+
361
+ case 'selector':
362
+ return String(node.value ?? '');
363
+
364
+ case 'variable': {
365
+ const name = String(node.name ?? '');
366
+ const scope = String(node.scope ?? 'local');
367
+ if (scope === 'element') return `:${name}`;
368
+ if (scope === 'global') return `$${name}`;
369
+ return name;
370
+ }
371
+
372
+ case 'binary': {
373
+ const left = node.left && typeof node.left === 'object' ? renderExpr(node.left as INode) : '';
374
+ const right =
375
+ node.right && typeof node.right === 'object' ? renderExpr(node.right as INode) : '';
376
+ return `${left} ${node.operator ?? ''} ${right}`;
377
+ }
378
+
379
+ case 'unary': {
380
+ const operand =
381
+ node.operand && typeof node.operand === 'object' ? renderExpr(node.operand as INode) : '';
382
+ return `${node.operator ?? ''} ${operand}`;
383
+ }
384
+
385
+ case 'member': {
386
+ const obj =
387
+ node.object && typeof node.object === 'object' ? renderExpr(node.object as INode) : '';
388
+ if (node.computed && node.property && typeof node.property === 'object') {
389
+ return `${obj}[${renderExpr(node.property as INode)}]`;
390
+ }
391
+ const prop =
392
+ typeof node.property === 'string'
393
+ ? node.property
394
+ : node.property && typeof node.property === 'object'
395
+ ? renderExpr(node.property as INode)
396
+ : '';
397
+ return `${obj}.${prop}`;
398
+ }
399
+
400
+ case 'possessive': {
401
+ const obj =
402
+ node.object && typeof node.object === 'object' ? renderExpr(node.object as INode) : '';
403
+ return `${obj}'s ${node.property ?? ''}`;
404
+ }
405
+
406
+ case 'call': {
407
+ const callee =
408
+ node.callee && typeof node.callee === 'object' ? renderExpr(node.callee as INode) : '';
409
+ const args = Array.isArray(node.args)
410
+ ? (node.args as INode[]).map(a => renderExpr(a)).join(', ')
411
+ : '';
412
+ return `${callee}(${args})`;
413
+ }
414
+
415
+ case 'positional': {
416
+ const target =
417
+ node.target && typeof node.target === 'object' ? renderExpr(node.target as INode) : '';
418
+ return target ? `${node.position ?? ''} ${target}` : String(node.position ?? '');
419
+ }
420
+
421
+ default:
422
+ if ('value' in node) return String(node.value ?? '');
423
+ return '';
424
+ }
425
+ }
426
+
427
+ // =============================================================================
428
+ // Role Inference
429
+ // =============================================================================
430
+
431
+ /**
432
+ * Infer semantic roles from command name, positional args, modifiers, and target.
433
+ * Mirrors the heuristics in core's from-core.ts inferRoles() function.
434
+ */
435
+ function inferRoles(
436
+ name: string,
437
+ args: INode[],
438
+ modifiers: Record<string, unknown> | undefined,
439
+ target: INode | undefined
440
+ ): Record<SemanticRole, SemanticValue> {
441
+ const roles: Record<SemanticRole, SemanticValue> = {};
442
+
443
+ switch (name) {
444
+ case 'set': {
445
+ if (args[0]) roles.destination = convertValue(args[0]);
446
+ const toVal = modifiers?.to;
447
+ if (toVal && typeof toVal === 'object' && 'type' in (toVal as object)) {
448
+ roles.patient = convertValue(toVal as INode);
449
+ } else if (args[1]) {
450
+ roles.patient = convertValue(args[1]);
451
+ }
452
+ break;
453
+ }
454
+
455
+ case 'put': {
456
+ if (args[0]) roles.patient = convertValue(args[0]);
457
+ if (modifiers) {
458
+ for (const key of ['into', 'before', 'after'] as const) {
459
+ const val = modifiers[key];
460
+ if (val && typeof val === 'object' && 'type' in (val as object)) {
461
+ roles.destination = convertValue(val as INode);
462
+ roles.method = createLiteral(key);
463
+ break;
464
+ }
465
+ }
466
+ }
467
+ if (!roles.destination && target) {
468
+ roles.destination = convertValue(target);
469
+ }
470
+ break;
471
+ }
472
+
473
+ case 'increment':
474
+ case 'decrement': {
475
+ if (args[0]) roles.destination = convertValue(args[0]);
476
+ const byVal = modifiers?.by;
477
+ if (byVal && typeof byVal === 'object' && 'type' in (byVal as object)) {
478
+ roles.quantity = convertValue(byVal as INode);
479
+ } else if (args[1]) {
480
+ roles.quantity = convertValue(args[1]);
481
+ }
482
+ break;
483
+ }
484
+
485
+ case 'fetch': {
486
+ if (args[0]) roles.source = convertValue(args[0]);
487
+ const asVal = modifiers?.as;
488
+ if (asVal && typeof asVal === 'object' && 'type' in (asVal as object)) {
489
+ roles.responseType = convertValue(asVal as INode);
490
+ } else if (typeof asVal === 'string') {
491
+ roles.responseType = createLiteral(asVal);
492
+ }
493
+ break;
494
+ }
495
+
496
+ case 'wait':
497
+ case 'settle': {
498
+ if (args[0]) roles.duration = convertValue(args[0]);
499
+ break;
500
+ }
501
+
502
+ case 'toggle':
503
+ case 'add':
504
+ case 'show':
505
+ case 'hide': {
506
+ if (args[0]) roles.patient = convertValue(args[0]);
507
+ if (target) roles.destination = convertValue(target);
508
+ break;
509
+ }
510
+
511
+ case 'remove': {
512
+ if (args[0]) roles.patient = convertValue(args[0]);
513
+ if (target) roles.source = convertValue(target);
514
+ break;
515
+ }
516
+
517
+ case 'send':
518
+ case 'trigger': {
519
+ if (args[0]) roles.patient = convertValue(args[0]);
520
+ if (target) roles.destination = convertValue(target);
521
+ break;
522
+ }
523
+
524
+ case 'log': {
525
+ if (args[0]) roles.patient = convertValue(args[0]);
526
+ break;
527
+ }
528
+
529
+ default: {
530
+ // Generic fallback: first arg as patient, target as destination
531
+ if (args[0]) roles.patient = convertValue(args[0]);
532
+ if (target) roles.destination = convertValue(target);
533
+ break;
534
+ }
535
+ }
536
+
537
+ return roles;
538
+ }
539
+
540
+ // =============================================================================
541
+ // Helpers
542
+ // =============================================================================
543
+
544
+ /**
545
+ * Infer selectorKind from a CSS selector string.
546
+ */
547
+ function inferSelectorKind(
548
+ value: string
549
+ ): 'id' | 'class' | 'attribute' | 'element' | 'complex' | undefined {
550
+ if (!value) return undefined;
551
+ if (value.startsWith('#')) return 'id';
552
+ if (value.startsWith('.')) return 'class';
553
+ if (value.startsWith('[')) return 'attribute';
554
+ if (value.startsWith('<') || value.startsWith('*')) return 'element';
555
+ // Check for combinators indicating complex selectors
556
+ if (/[>\+~ ]/.test(value) && value.length > 1) return 'complex';
557
+ return undefined;
558
+ }
559
+
560
+ /**
561
+ * Infer a SemanticValue from a string (for event modifier `from` bridging).
562
+ */
563
+ function inferValueFromString(value: string): SemanticValue {
564
+ if (DEFAULT_REFERENCES.has(value)) return createReference(value);
565
+ const kind = inferSelectorKind(value);
566
+ if (kind) return createSelector(value, kind);
567
+ return createLiteral(value);
568
+ }
569
+
570
+ /**
571
+ * Bridge InterchangeNode EventModifiers to SemanticNode EventModifiers.
572
+ */
573
+ function bridgeEventModifiers(mods: Record<string, unknown>): SemanticEventModifiers | undefined {
574
+ const once = mods.once === true ? true : undefined;
575
+ const debounce = typeof mods.debounce === 'number' ? mods.debounce : undefined;
576
+ const throttle = typeof mods.throttle === 'number' ? mods.throttle : undefined;
577
+ const from = typeof mods.from === 'string' ? inferValueFromString(mods.from) : undefined;
578
+
579
+ if (
580
+ once === undefined &&
581
+ debounce === undefined &&
582
+ throttle === undefined &&
583
+ from === undefined
584
+ ) {
585
+ return undefined;
586
+ }
587
+
588
+ return {
589
+ ...(once !== undefined ? { once } : {}),
590
+ ...(debounce !== undefined ? { debounce } : {}),
591
+ ...(throttle !== undefined ? { throttle } : {}),
592
+ ...(from !== undefined ? { from } : {}),
593
+ };
594
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * IR (Intermediate Representation)
3
+ *
4
+ * Universal explicit bracket syntax for semantic DSL interchange.
5
+ * Domain-agnostic — works for any DSL built on the framework.
6
+ *
7
+ * Three input formats converge on SemanticNode:
8
+ * 1. Bracket syntax: [toggle patient:.active destination:#button]
9
+ * 2. LLM JSON: { action: "toggle", roles: { patient: { type: "selector", value: ".active" } } }
10
+ * 3. Natural language: (handled by language-specific parsers, not this module)
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { parseExplicit, renderExplicit, jsonToSemanticNode } from '@lokascript/framework/ir';
15
+ *
16
+ * const node = parseExplicit('[toggle patient:.active]');
17
+ * const bracket = renderExplicit(node); // "[toggle patient:.active]"
18
+ * const json = semanticNodeToJSON(node); // { action: "toggle", roles: { ... } }
19
+ * ```
20
+ */
21
+
22
+ // Types
23
+ export type {
24
+ SemanticJSON,
25
+ SemanticJSONValue,
26
+ SchemaLookup,
27
+ ParseExplicitOptions,
28
+ IRDiagnostic,
29
+ } from './types';
30
+
31
+ // References
32
+ export { DEFAULT_REFERENCES, isValidReference } from './references';
33
+
34
+ // Explicit syntax parser
35
+ export {
36
+ parseExplicit,
37
+ parseCompound,
38
+ parseDocument,
39
+ isExplicitSyntax,
40
+ isCompoundSyntax,
41
+ isDocumentSyntax,
42
+ } from './explicit-parser';
43
+
44
+ // Explicit syntax renderer
45
+ export { renderExplicit, renderDocument } from './explicit-renderer';
46
+
47
+ // JSON schema conversion (deprecated — use fromProtocolJSON/validateProtocolJSON/toProtocolJSON)
48
+ export { jsonToSemanticNode, validateSemanticJSON, semanticNodeToJSON } from './json-schema';
49
+
50
+ // Protocol full-fidelity wire format
51
+ export type {
52
+ ProtocolNodeJSON,
53
+ ProtocolValueJSON,
54
+ ProtocolNodeKind,
55
+ ProtocolChainType,
56
+ // v1.2 sub-types
57
+ ProtocolDiagnosticJSON,
58
+ AnnotationJSON,
59
+ MatchArmJSON,
60
+ LSEEnvelopeJSON,
61
+ } from './types';
62
+ export {
63
+ toProtocolJSON,
64
+ fromProtocolJSON,
65
+ validateProtocolJSON,
66
+ // v1.2 envelope
67
+ toEnvelopeJSON,
68
+ fromEnvelopeJSON,
69
+ isEnvelope,
70
+ } from './protocol-json';
71
+
72
+ // Interchange AST → SemanticNode converter
73
+ export { fromInterchangeNode, convertValue, renderExpr } from './from-interchange';
74
+
75
+ // SemanticNode → Runtime AST converter (for core runtime execution)
76
+ export { semanticNodeToRuntimeAST, semanticValueToAST } from './to-runtime-ast';
77
+ export type { RuntimeASTNode, RuntimeCommandNode, RuntimeEventNode } from './to-runtime-ast';