@orcalang/orca-lang 0.1.19 → 0.1.21
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/compiler/dt-compiler.d.ts +3 -0
- package/dist/compiler/dt-compiler.d.ts.map +1 -1
- package/dist/compiler/dt-compiler.js +205 -1
- package/dist/compiler/dt-compiler.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/parser/ast-to-markdown.d.ts.map +1 -1
- package/dist/parser/ast-to-markdown.js +3 -1
- package/dist/parser/ast-to-markdown.js.map +1 -1
- package/dist/parser/ast.d.ts +1 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/markdown-parser.d.ts.map +1 -1
- package/dist/parser/markdown-parser.js +14 -4
- package/dist/parser/markdown-parser.js.map +1 -1
- package/dist/skills.d.ts +1 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +287 -23
- package/dist/skills.js.map +1 -1
- package/dist/verifier/dt-verifier.d.ts +28 -1
- package/dist/verifier/dt-verifier.d.ts.map +1 -1
- package/dist/verifier/dt-verifier.js +332 -1
- package/dist/verifier/dt-verifier.js.map +1 -1
- package/dist/verifier/properties.d.ts +4 -0
- package/dist/verifier/properties.d.ts.map +1 -1
- package/dist/verifier/properties.js +56 -20
- package/dist/verifier/properties.js.map +1 -1
- package/dist/verifier/structural.d.ts.map +1 -1
- package/dist/verifier/structural.js +6 -1
- package/dist/verifier/structural.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler/dt-compiler.ts +223 -1
- package/src/index.ts +5 -1
- package/src/parser/ast-to-markdown.ts +2 -1
- package/src/parser/ast.ts +1 -0
- package/src/parser/markdown-parser.ts +11 -3
- package/src/skills.ts +319 -23
- package/src/verifier/dt-verifier.ts +367 -1
- package/src/verifier/properties.ts +78 -23
- package/src/verifier/structural.ts +5 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Decision Table Compiler
|
|
2
|
-
// Compiles verified decision tables to TypeScript evaluator functions or JSON
|
|
2
|
+
// Compiles verified decision tables to TypeScript, Python, Go evaluator functions or JSON
|
|
3
3
|
|
|
4
4
|
import { DecisionTableDef, CellValue } from '../parser/dt-ast.js';
|
|
5
5
|
|
|
@@ -11,6 +11,14 @@ function toPascalCase(name: string): string {
|
|
|
11
11
|
.join('');
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// Convert PascalCase or camelCase to snake_case
|
|
15
|
+
export function toSnakeCase(name: string): string {
|
|
16
|
+
return name
|
|
17
|
+
.replace(/([A-Z])/g, '_$1')
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/^_/, '');
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
// Generate TypeScript type for input interface
|
|
15
23
|
function generateInputType(dt: DecisionTableDef): string {
|
|
16
24
|
const lines: string[] = [];
|
|
@@ -230,3 +238,217 @@ export function compileDecisionTableToJSON(dt: DecisionTableDef): string {
|
|
|
230
238
|
|
|
231
239
|
return JSON.stringify(json, null, 2);
|
|
232
240
|
}
|
|
241
|
+
|
|
242
|
+
// ============================================================
|
|
243
|
+
// Python Compiler
|
|
244
|
+
// ============================================================
|
|
245
|
+
|
|
246
|
+
function generatePythonType(type: string): string {
|
|
247
|
+
if (type === 'bool') return 'bool';
|
|
248
|
+
if (type === 'int_range') return 'int';
|
|
249
|
+
return 'str';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function generatePythonConditionCheck(condName: string, condType: string, cell: CellValue): string {
|
|
253
|
+
switch (cell.kind) {
|
|
254
|
+
case 'any':
|
|
255
|
+
return '';
|
|
256
|
+
case 'exact':
|
|
257
|
+
if (condType === 'bool') return `input.${condName} == ${cell.value}`;
|
|
258
|
+
return `input.${condName} == '${cell.value}'`;
|
|
259
|
+
case 'negated':
|
|
260
|
+
if (condType === 'bool') return `input.${condName} != ${cell.value}`;
|
|
261
|
+
return `input.${condName} != '${cell.value}'`;
|
|
262
|
+
case 'set': {
|
|
263
|
+
const checks = cell.values.map(v =>
|
|
264
|
+
condType === 'bool' ? `input.${condName} == ${v}` : `input.${condName} == '${v}'`
|
|
265
|
+
).join(' or ');
|
|
266
|
+
return `(${checks})`;
|
|
267
|
+
}
|
|
268
|
+
default:
|
|
269
|
+
return '';
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function compileDecisionTableToPython(dt: DecisionTableDef): string {
|
|
274
|
+
const inputClassName = `${toPascalCase(dt.name)}Input`;
|
|
275
|
+
const outputClassName = `${toPascalCase(dt.name)}Output`;
|
|
276
|
+
const fnName = `evaluate_${toSnakeCase(dt.name)}`;
|
|
277
|
+
|
|
278
|
+
const lines: string[] = [
|
|
279
|
+
'from typing import Optional',
|
|
280
|
+
'from dataclasses import dataclass',
|
|
281
|
+
'',
|
|
282
|
+
'',
|
|
283
|
+
'@dataclass',
|
|
284
|
+
`class ${inputClassName}:`,
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
for (const cond of dt.conditions) {
|
|
288
|
+
const typeStr = generatePythonType(cond.type);
|
|
289
|
+
const comment = cond.type === 'enum' && cond.values.length > 0
|
|
290
|
+
? ` # ${cond.values.join(', ')}`
|
|
291
|
+
: '';
|
|
292
|
+
lines.push(` ${cond.name}: ${typeStr}${comment}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
lines.push('');
|
|
296
|
+
lines.push('');
|
|
297
|
+
lines.push('@dataclass');
|
|
298
|
+
lines.push(`class ${outputClassName}:`);
|
|
299
|
+
|
|
300
|
+
for (const action of dt.actions) {
|
|
301
|
+
const typeStr = generatePythonType(action.type);
|
|
302
|
+
const comment = action.type === 'enum' && action.values && action.values.length > 0
|
|
303
|
+
? ` # ${action.values.join(', ')}`
|
|
304
|
+
: '';
|
|
305
|
+
lines.push(` ${action.name}: ${typeStr}${comment}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
lines.push('');
|
|
309
|
+
lines.push('');
|
|
310
|
+
lines.push(`def ${fnName}(input: ${inputClassName}) -> Optional[${outputClassName}]:`);
|
|
311
|
+
|
|
312
|
+
for (let ruleIdx = 0; ruleIdx < dt.rules.length; ruleIdx++) {
|
|
313
|
+
const rule = dt.rules[ruleIdx];
|
|
314
|
+
const ruleNum = rule.number ?? ruleIdx + 1;
|
|
315
|
+
|
|
316
|
+
const checks: string[] = [];
|
|
317
|
+
for (const [condName, cell] of rule.conditions) {
|
|
318
|
+
const condDef = dt.conditions.find(c => c.name === condName);
|
|
319
|
+
const condType = condDef?.type ?? 'string';
|
|
320
|
+
const check = generatePythonConditionCheck(condName, condType, cell);
|
|
321
|
+
if (check) checks.push(check);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const actionArgs = dt.actions
|
|
325
|
+
.map(a => {
|
|
326
|
+
const value = rule.actions.get(a.name);
|
|
327
|
+
if (value === undefined) return null;
|
|
328
|
+
if (a.type === 'bool') return `${a.name}=${value}`;
|
|
329
|
+
return `${a.name}='${value}'`;
|
|
330
|
+
})
|
|
331
|
+
.filter((v): v is string => v !== null)
|
|
332
|
+
.join(', ');
|
|
333
|
+
|
|
334
|
+
lines.push(` # Rule ${ruleNum}`);
|
|
335
|
+
if (checks.length === 0) {
|
|
336
|
+
lines.push(` return ${outputClassName}(${actionArgs})`);
|
|
337
|
+
} else {
|
|
338
|
+
lines.push(` if ${checks.join(' and ')}:`);
|
|
339
|
+
lines.push(` return ${outputClassName}(${actionArgs})`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
lines.push(' return None # no rule matched');
|
|
344
|
+
|
|
345
|
+
return lines.join('\n');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ============================================================
|
|
349
|
+
// Go Compiler
|
|
350
|
+
// ============================================================
|
|
351
|
+
|
|
352
|
+
function generateGoType(type: string): string {
|
|
353
|
+
if (type === 'bool') return 'bool';
|
|
354
|
+
if (type === 'int_range') return 'int';
|
|
355
|
+
return 'string';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function generateGoConditionCheck(condName: string, condType: string, cell: CellValue): string {
|
|
359
|
+
const goField = toPascalCase(condName);
|
|
360
|
+
switch (cell.kind) {
|
|
361
|
+
case 'any':
|
|
362
|
+
return '';
|
|
363
|
+
case 'exact':
|
|
364
|
+
if (condType === 'bool') return `input.${goField} == ${cell.value}`;
|
|
365
|
+
return `input.${goField} == "${cell.value}"`;
|
|
366
|
+
case 'negated':
|
|
367
|
+
if (condType === 'bool') return `input.${goField} != ${cell.value}`;
|
|
368
|
+
return `input.${goField} != "${cell.value}"`;
|
|
369
|
+
case 'set': {
|
|
370
|
+
const checks = cell.values.map(v =>
|
|
371
|
+
condType === 'bool' ? `input.${goField} == ${v}` : `input.${goField} == "${v}"`
|
|
372
|
+
).join(' || ');
|
|
373
|
+
return `(${checks})`;
|
|
374
|
+
}
|
|
375
|
+
default:
|
|
376
|
+
return '';
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function compileDecisionTableToGo(dt: DecisionTableDef): string {
|
|
381
|
+
const inputTypeName = `${toPascalCase(dt.name)}Input`;
|
|
382
|
+
const outputTypeName = `${toPascalCase(dt.name)}Output`;
|
|
383
|
+
const fnName = `Evaluate${toPascalCase(dt.name)}`;
|
|
384
|
+
|
|
385
|
+
const lines: string[] = [];
|
|
386
|
+
|
|
387
|
+
lines.push(`// ${inputTypeName} defines the input conditions for the ${dt.name} decision table`);
|
|
388
|
+
lines.push(`type ${inputTypeName} struct {`);
|
|
389
|
+
for (const cond of dt.conditions) {
|
|
390
|
+
const goType = generateGoType(cond.type);
|
|
391
|
+
const fieldName = toPascalCase(cond.name);
|
|
392
|
+
const comment = cond.type === 'enum' && cond.values.length > 0
|
|
393
|
+
? ` // ${cond.values.join(', ')}`
|
|
394
|
+
: '';
|
|
395
|
+
lines.push(`\t${fieldName} ${goType}${comment}`);
|
|
396
|
+
}
|
|
397
|
+
lines.push('}');
|
|
398
|
+
lines.push('');
|
|
399
|
+
|
|
400
|
+
lines.push(`// ${outputTypeName} defines the decision outputs for the ${dt.name} decision table`);
|
|
401
|
+
lines.push(`type ${outputTypeName} struct {`);
|
|
402
|
+
for (const action of dt.actions) {
|
|
403
|
+
const goType = generateGoType(action.type);
|
|
404
|
+
const fieldName = toPascalCase(action.name);
|
|
405
|
+
const comment = action.type === 'enum' && action.values && action.values.length > 0
|
|
406
|
+
? ` // ${action.values.join(', ')}`
|
|
407
|
+
: '';
|
|
408
|
+
lines.push(`\t${fieldName} ${goType}${comment}`);
|
|
409
|
+
}
|
|
410
|
+
lines.push('}');
|
|
411
|
+
lines.push('');
|
|
412
|
+
|
|
413
|
+
const policy = dt.policy ?? 'first-match';
|
|
414
|
+
lines.push(`// ${fnName} evaluates the ${dt.name} decision table (${policy} policy)`);
|
|
415
|
+
lines.push(`func ${fnName}(input ${inputTypeName}) *${outputTypeName} {`);
|
|
416
|
+
|
|
417
|
+
for (let ruleIdx = 0; ruleIdx < dt.rules.length; ruleIdx++) {
|
|
418
|
+
const rule = dt.rules[ruleIdx];
|
|
419
|
+
const ruleNum = rule.number ?? ruleIdx + 1;
|
|
420
|
+
|
|
421
|
+
const checks: string[] = [];
|
|
422
|
+
for (const [condName, cell] of rule.conditions) {
|
|
423
|
+
const condDef = dt.conditions.find(c => c.name === condName);
|
|
424
|
+
const condType = condDef?.type ?? 'string';
|
|
425
|
+
const check = generateGoConditionCheck(condName, condType, cell);
|
|
426
|
+
if (check) checks.push(check);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const actionFields = dt.actions
|
|
430
|
+
.map(a => {
|
|
431
|
+
const value = rule.actions.get(a.name);
|
|
432
|
+
if (value === undefined) return null;
|
|
433
|
+
const fieldName = toPascalCase(a.name);
|
|
434
|
+
if (a.type === 'bool') return `${fieldName}: ${value}`;
|
|
435
|
+
return `${fieldName}: "${value}"`;
|
|
436
|
+
})
|
|
437
|
+
.filter((v): v is string => v !== null)
|
|
438
|
+
.join(', ');
|
|
439
|
+
|
|
440
|
+
lines.push(`\t// Rule ${ruleNum}`);
|
|
441
|
+
if (checks.length === 0) {
|
|
442
|
+
lines.push(`\treturn &${outputTypeName}{${actionFields}}`);
|
|
443
|
+
} else {
|
|
444
|
+
lines.push(`\tif ${checks.join(' && ')} {`);
|
|
445
|
+
lines.push(`\t\treturn &${outputTypeName}{${actionFields}}`);
|
|
446
|
+
lines.push(`\t}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
lines.push('\treturn nil // no rule matched');
|
|
451
|
+
lines.push('}');
|
|
452
|
+
|
|
453
|
+
return lines.join('\n');
|
|
454
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { checkStructural, analyzeFile } from './verifier/structural.js';
|
|
|
22
22
|
import { checkCompleteness } from './verifier/completeness.js';
|
|
23
23
|
import { checkDeterminism } from './verifier/determinism.js';
|
|
24
24
|
import { checkProperties } from './verifier/properties.js';
|
|
25
|
+
import { computeAlignedDTOutputDomain } from './verifier/dt-verifier.js';
|
|
25
26
|
import { compileToXState, compileToXStateMachine } from './compiler/xstate.js';
|
|
26
27
|
import { compileToMermaid } from './compiler/mermaid.js';
|
|
27
28
|
import { verifySkill, compileSkill, generateActionsSkill, refineSkill, generateOrcaSkill, generateOrcaMultiSkill, parseSkill, type SkillInput } from './skills.js';
|
|
@@ -232,7 +233,10 @@ async function verify(input: SkillInput, json: boolean = false): Promise<void> {
|
|
|
232
233
|
const structural = checkStructural(machine);
|
|
233
234
|
const completeness = checkCompleteness(machine);
|
|
234
235
|
const determinism = checkDeterminism(machine);
|
|
235
|
-
const
|
|
236
|
+
const dtOutputDomain = file.decisionTables.length > 0
|
|
237
|
+
? computeAlignedDTOutputDomain({ machines: [machine], decisionTables: file.decisionTables })
|
|
238
|
+
: undefined;
|
|
239
|
+
const properties = checkProperties(machine, { dtOutputDomain });
|
|
236
240
|
|
|
237
241
|
const allErrors = [
|
|
238
242
|
...structural.errors,
|
|
@@ -101,7 +101,8 @@ function emitStates(states: StateDef[], level: number): string[] {
|
|
|
101
101
|
if (state.onEntry) lines.push(`- on_entry: ${state.onEntry}`);
|
|
102
102
|
if (state.onExit) lines.push(`- on_exit: ${state.onExit}`);
|
|
103
103
|
if (state.timeout) lines.push(`- timeout: ${state.timeout.duration} -> ${state.timeout.target}`);
|
|
104
|
-
if (state.
|
|
104
|
+
if (state.ignoredAll) lines.push(`- ignore: *`);
|
|
105
|
+
else if (state.ignoredEvents?.length) lines.push(`- ignore: ${state.ignoredEvents.join(', ')}`);
|
|
105
106
|
if (state.onDone) lines.push(`- on_done: -> ${state.onDone}`);
|
|
106
107
|
|
|
107
108
|
if (state.parallel) {
|
package/src/parser/ast.ts
CHANGED
|
@@ -120,6 +120,7 @@ export interface StateDef {
|
|
|
120
120
|
parent?: string; // Parent state name for hierarchical states
|
|
121
121
|
transitions?: Transition[];
|
|
122
122
|
ignoredEvents?: string[];
|
|
123
|
+
ignoredAll?: boolean; // true when "- ignore: *" — all unhandled events discarded
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
export interface Transition {
|
|
@@ -391,6 +391,7 @@ interface StateEntry {
|
|
|
391
391
|
invoke?: InvokeDef;
|
|
392
392
|
_pendingOnError?: string; // temp: on_error parsed before invoke
|
|
393
393
|
ignoredEvents?: string[];
|
|
394
|
+
ignoredAll?: boolean;
|
|
394
395
|
line: number;
|
|
395
396
|
}
|
|
396
397
|
|
|
@@ -431,9 +432,14 @@ function parseStateBullet(entry: StateEntry, text: string): void {
|
|
|
431
432
|
entry.timeout = { duration: rest.slice(0, arrowIdx).trim(), target: rest.slice(arrowIdx + 2).trim() };
|
|
432
433
|
}
|
|
433
434
|
} else if (text.startsWith('ignore:')) {
|
|
434
|
-
const
|
|
435
|
-
if (
|
|
436
|
-
|
|
435
|
+
const val = text.slice(7).trim();
|
|
436
|
+
if (val === '*') {
|
|
437
|
+
entry.ignoredAll = true;
|
|
438
|
+
} else {
|
|
439
|
+
const names = val.split(',').map(e => e.trim()).filter(Boolean);
|
|
440
|
+
if (!entry.ignoredEvents) entry.ignoredEvents = [];
|
|
441
|
+
entry.ignoredEvents.push(...names);
|
|
442
|
+
}
|
|
437
443
|
} else if (text.startsWith('on_done:')) {
|
|
438
444
|
let val = text.slice(8).trim();
|
|
439
445
|
if (val.startsWith('->')) val = val.slice(2).trim();
|
|
@@ -630,6 +636,7 @@ function buildStatesAtLevel(
|
|
|
630
636
|
if (entry.onDone) state.onDone = entry.onDone;
|
|
631
637
|
if (entry.timeout) state.timeout = entry.timeout;
|
|
632
638
|
if (entry.ignoredEvents?.length) state.ignoredEvents = entry.ignoredEvents;
|
|
639
|
+
if (entry.ignoredAll) state.ignoredAll = true;
|
|
633
640
|
if (entry.invoke) state.invoke = entry.invoke;
|
|
634
641
|
|
|
635
642
|
i++;
|
|
@@ -678,6 +685,7 @@ function buildParallelRegions(
|
|
|
678
685
|
if (e.onDone) s.onDone = e.onDone;
|
|
679
686
|
if (e.timeout) s.timeout = e.timeout;
|
|
680
687
|
if (e.ignoredEvents?.length) s.ignoredEvents = e.ignoredEvents;
|
|
688
|
+
if (e.ignoredAll) s.ignoredAll = true;
|
|
681
689
|
if (e.invoke) s.invoke = e.invoke;
|
|
682
690
|
regionStates.push(s);
|
|
683
691
|
i++;
|