@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.
- package/dist/aot/index.d.ts +1 -0
- package/dist/aot/index.d.ts.map +1 -1
- package/dist/aot/registry-bridge.d.ts +27 -0
- package/dist/aot/registry-bridge.d.ts.map +1 -0
- package/dist/api/create-dsl.d.ts.map +1 -1
- package/dist/api/dispatcher.d.ts +22 -0
- package/dist/api/dispatcher.d.ts.map +1 -1
- package/dist/api/domain-registry.d.ts +41 -0
- package/dist/api/domain-registry.d.ts.map +1 -1
- package/dist/api/index.js +1438 -21
- package/dist/api/index.js.map +1 -1
- package/dist/core/index.js +247 -10
- package/dist/core/index.js.map +1 -1
- package/dist/core/pattern-matching/index.js +74 -10
- package/dist/core/pattern-matching/index.js.map +1 -1
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
- package/dist/core/tokenization/index.js +133 -0
- package/dist/core/tokenization/index.js.map +1 -1
- package/dist/core/tokenization/morphology/base-normalizer.d.ts +94 -0
- package/dist/core/tokenization/morphology/base-normalizer.d.ts.map +1 -0
- package/dist/core/tokenization/morphology/index.d.ts +3 -1
- package/dist/core/tokenization/morphology/index.d.ts.map +1 -1
- package/dist/core/types.d.ts +81 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +40 -0
- package/dist/core/types.js.map +1 -1
- package/dist/feedback/confidence-gate.d.ts +28 -0
- package/dist/feedback/confidence-gate.d.ts.map +1 -0
- package/dist/feedback/feedback-formatter.d.ts +21 -0
- package/dist/feedback/feedback-formatter.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +11 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/pattern-tracker.d.ts +47 -0
- package/dist/feedback/pattern-tracker.d.ts.map +1 -0
- package/dist/feedback/types.d.ts +113 -0
- package/dist/feedback/types.d.ts.map +1 -0
- package/dist/generation/index.js +2 -1
- package/dist/generation/index.js.map +1 -1
- package/dist/index.cjs +3264 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3223 -132
- package/dist/index.js.map +1 -1
- package/dist/ir/explicit-parser.d.ts +86 -0
- package/dist/ir/explicit-parser.d.ts.map +1 -0
- package/dist/ir/explicit-renderer.d.ts +34 -0
- package/dist/ir/explicit-renderer.d.ts.map +1 -0
- package/dist/ir/from-interchange.d.ts +51 -0
- package/dist/ir/from-interchange.d.ts.map +1 -0
- package/dist/ir/index.d.ts +31 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +1934 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/json-schema.d.ts +40 -0
- package/dist/ir/json-schema.d.ts.map +1 -0
- package/dist/ir/protocol-json.d.ts +73 -0
- package/dist/ir/protocol-json.d.ts.map +1 -0
- package/dist/ir/references.d.ts +19 -0
- package/dist/ir/references.d.ts.map +1 -0
- package/dist/ir/to-runtime-ast.d.ts +53 -0
- package/dist/ir/to-runtime-ast.d.ts.map +1 -0
- package/dist/ir/types.d.ts +151 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/parsing/index.js +74 -10
- package/dist/parsing/index.js.map +1 -1
- package/dist/prompts/index.d.ts +9 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/prompt-generator.d.ts +49 -0
- package/dist/prompts/prompt-generator.d.ts.map +1 -0
- package/dist/prompts/types.d.ts +64 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/schema/command-schema.d.ts +17 -0
- package/dist/schema/command-schema.d.ts.map +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/index.js +8673 -8260
- package/dist/testing/index.js.map +1 -1
- package/dist/training/index.d.ts +11 -0
- package/dist/training/index.d.ts.map +1 -0
- package/dist/training/jsonl-writer.d.ts +42 -0
- package/dist/training/jsonl-writer.d.ts.map +1 -0
- package/dist/training/schema-synthesizer.d.ts +20 -0
- package/dist/training/schema-synthesizer.d.ts.map +1 -0
- package/dist/training/types.d.ts +73 -0
- package/dist/training/types.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/aot/index.ts +1 -0
- package/src/aot/registry-bridge.test.ts +104 -0
- package/src/aot/registry-bridge.ts +66 -0
- package/src/api/create-dsl.test.ts +306 -0
- package/src/api/create-dsl.ts +21 -0
- package/src/api/dispatcher.test.ts +66 -0
- package/src/api/dispatcher.ts +77 -9
- package/src/api/domain-registry.ts +120 -4
- package/src/core/pattern-matching/pattern-matcher.test.ts +131 -0
- package/src/core/pattern-matching/pattern-matcher.ts +102 -19
- package/src/core/tokenization/morphology/base-normalizer.ts +249 -0
- package/src/core/tokenization/morphology/index.ts +3 -1
- package/src/core/types.ts +147 -2
- package/src/feedback/confidence-gate.test.ts +111 -0
- package/src/feedback/confidence-gate.ts +70 -0
- package/src/feedback/feedback-formatter.test.ts +198 -0
- package/src/feedback/feedback-formatter.ts +298 -0
- package/src/feedback/index.ts +24 -0
- package/src/feedback/pattern-tracker.test.ts +198 -0
- package/src/feedback/pattern-tracker.ts +140 -0
- package/src/feedback/types.ts +149 -0
- package/src/generation/pattern-generator.ts +2 -2
- package/src/index.ts +29 -0
- package/src/integration/llm-lse-integration.test.ts +457 -0
- package/src/ir/conformance.test.ts +120 -0
- package/src/ir/explicit-parser.test.ts +612 -0
- package/src/ir/explicit-parser.ts +793 -0
- package/src/ir/explicit-renderer.test.ts +229 -0
- package/src/ir/explicit-renderer.ts +198 -0
- package/src/ir/from-interchange.test.ts +853 -0
- package/src/ir/from-interchange.ts +594 -0
- package/src/ir/index.ts +77 -0
- package/src/ir/json-schema.test.ts +281 -0
- package/src/ir/json-schema.ts +299 -0
- package/src/ir/protocol-json.test.ts +1187 -0
- package/src/ir/protocol-json.ts +751 -0
- package/src/ir/references.test.ts +38 -0
- package/src/ir/references.ts +33 -0
- package/src/ir/round-trip.test.ts +557 -0
- package/src/ir/to-runtime-ast.ts +203 -0
- package/src/ir/types.ts +191 -0
- package/src/prompts/index.ts +15 -0
- package/src/prompts/prompt-generator.test.ts +386 -0
- package/src/prompts/prompt-generator.ts +531 -0
- package/src/prompts/types.ts +89 -0
- package/src/schema/command-schema.ts +19 -0
- package/src/training/index.ts +13 -0
- package/src/training/jsonl-writer.test.ts +138 -0
- package/src/training/jsonl-writer.ts +67 -0
- package/src/training/schema-synthesizer.test.ts +250 -0
- package/src/training/schema-synthesizer.ts +306 -0
- 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
|
+
}
|
package/src/ir/index.ts
ADDED
|
@@ -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';
|