@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.
Files changed (40) hide show
  1. package/dist/compiler/dt-compiler.d.ts +3 -0
  2. package/dist/compiler/dt-compiler.d.ts.map +1 -1
  3. package/dist/compiler/dt-compiler.js +205 -1
  4. package/dist/compiler/dt-compiler.js.map +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +5 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/parser/ast-to-markdown.d.ts.map +1 -1
  9. package/dist/parser/ast-to-markdown.js +3 -1
  10. package/dist/parser/ast-to-markdown.js.map +1 -1
  11. package/dist/parser/ast.d.ts +1 -0
  12. package/dist/parser/ast.d.ts.map +1 -1
  13. package/dist/parser/markdown-parser.d.ts.map +1 -1
  14. package/dist/parser/markdown-parser.js +14 -4
  15. package/dist/parser/markdown-parser.js.map +1 -1
  16. package/dist/skills.d.ts +1 -0
  17. package/dist/skills.d.ts.map +1 -1
  18. package/dist/skills.js +287 -23
  19. package/dist/skills.js.map +1 -1
  20. package/dist/verifier/dt-verifier.d.ts +28 -1
  21. package/dist/verifier/dt-verifier.d.ts.map +1 -1
  22. package/dist/verifier/dt-verifier.js +332 -1
  23. package/dist/verifier/dt-verifier.js.map +1 -1
  24. package/dist/verifier/properties.d.ts +4 -0
  25. package/dist/verifier/properties.d.ts.map +1 -1
  26. package/dist/verifier/properties.js +56 -20
  27. package/dist/verifier/properties.js.map +1 -1
  28. package/dist/verifier/structural.d.ts.map +1 -1
  29. package/dist/verifier/structural.js +6 -1
  30. package/dist/verifier/structural.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/compiler/dt-compiler.ts +223 -1
  33. package/src/index.ts +5 -1
  34. package/src/parser/ast-to-markdown.ts +2 -1
  35. package/src/parser/ast.ts +1 -0
  36. package/src/parser/markdown-parser.ts +11 -3
  37. package/src/skills.ts +319 -23
  38. package/src/verifier/dt-verifier.ts +367 -1
  39. package/src/verifier/properties.ts +78 -23
  40. 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 properties = checkProperties(machine);
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.ignoredEvents?.length) lines.push(`- ignore: ${state.ignoredEvents.join(', ')}`);
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 names = text.slice(7).trim().split(',').map(e => e.trim()).filter(Boolean);
435
- if (!entry.ignoredEvents) entry.ignoredEvents = [];
436
- entry.ignoredEvents.push(...names);
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++;