@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
package/src/skills.ts
CHANGED
|
@@ -7,8 +7,15 @@ import { checkProperties } from './verifier/properties.js';
|
|
|
7
7
|
import { compileToXState } from './compiler/xstate.js';
|
|
8
8
|
import { compileToMermaid } from './compiler/mermaid.js';
|
|
9
9
|
import { MachineDef, StateDef, GuardExpression, Type } from './parser/ast.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { DecisionTableDef } from './parser/dt-ast.js';
|
|
11
|
+
import { verifyDecisionTable, verifyDecisionTables, checkFileContextAlignment, checkDTMachineIntegration, computeAlignedDTOutputDomain } from './verifier/dt-verifier.js';
|
|
12
|
+
import {
|
|
13
|
+
compileDecisionTableToTypeScript,
|
|
14
|
+
compileDecisionTableToPython,
|
|
15
|
+
compileDecisionTableToGo,
|
|
16
|
+
compileDecisionTableToJSON,
|
|
17
|
+
toSnakeCase,
|
|
18
|
+
} from './compiler/dt-compiler.js';
|
|
12
19
|
import { loadConfig, resolveConfigOverrides } from './config/index.js';
|
|
13
20
|
import { createProvider } from './llm/index.js';
|
|
14
21
|
import type { LLMProvider } from './llm/index.js';
|
|
@@ -88,6 +95,7 @@ export interface GenerateActionsResult {
|
|
|
88
95
|
machine: string;
|
|
89
96
|
actions: ActionScaffold[];
|
|
90
97
|
scaffolds: Record<string, string>;
|
|
98
|
+
decisionTableCode?: Record<string, string>; // DT name → compiled evaluator code
|
|
91
99
|
tests?: Record<string, string>;
|
|
92
100
|
}
|
|
93
101
|
|
|
@@ -247,8 +255,13 @@ export async function verifySkill(input: SkillInput): Promise<VerifySkillResult>
|
|
|
247
255
|
const label = resolveLabel(input);
|
|
248
256
|
|
|
249
257
|
let machine: MachineDef;
|
|
258
|
+
let fileDecisionTables: import('./parser/dt-ast.js').DecisionTableDef[] = [];
|
|
250
259
|
try {
|
|
251
|
-
|
|
260
|
+
const { file } = parseMarkdown(source);
|
|
261
|
+
if (file.machines.length === 0) throw new Error(`${label} contains no machine definition.`);
|
|
262
|
+
if (file.machines.length > 1) throw new Error(`${label} contains multiple machines.`);
|
|
263
|
+
machine = file.machines[0];
|
|
264
|
+
fileDecisionTables = file.decisionTables;
|
|
252
265
|
} catch (err) {
|
|
253
266
|
// Parse error - return as verification error
|
|
254
267
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -270,15 +283,28 @@ export async function verifySkill(input: SkillInput): Promise<VerifySkillResult>
|
|
|
270
283
|
const structural = checkStructural(machine);
|
|
271
284
|
const completeness = checkCompleteness(machine);
|
|
272
285
|
const determinism = checkDeterminism(machine);
|
|
273
|
-
const properties = checkProperties(machine);
|
|
274
286
|
|
|
275
|
-
|
|
287
|
+
// Check co-located decision table alignment and machine integration (single-machine files only)
|
|
288
|
+
const orcaFile = { machines: [machine], decisionTables: fileDecisionTables };
|
|
289
|
+
const dtOutputDomain = fileDecisionTables.length > 0 ? computeAlignedDTOutputDomain(orcaFile) : undefined;
|
|
290
|
+
const properties = checkProperties(machine, { dtOutputDomain });
|
|
291
|
+
const dtAlignment = fileDecisionTables.length > 0
|
|
292
|
+
? checkFileContextAlignment(orcaFile)
|
|
293
|
+
: [];
|
|
294
|
+
const dtIntegration = fileDecisionTables.length > 0
|
|
295
|
+
? checkDTMachineIntegration(orcaFile)
|
|
296
|
+
: [];
|
|
297
|
+
|
|
298
|
+
const mapError = (e: { code: string; message: string; severity: 'error' | 'warning'; location?: { state?: string; event?: string; decisionTable?: string; condition?: string; action?: string }; suggestion?: string }): SkillError => ({
|
|
276
299
|
code: e.code,
|
|
277
300
|
message: e.message,
|
|
278
301
|
severity: e.severity,
|
|
279
302
|
location: e.location ? {
|
|
280
303
|
state: e.location.state,
|
|
281
304
|
event: e.location.event,
|
|
305
|
+
decisionTable: e.location.decisionTable,
|
|
306
|
+
condition: e.location.condition,
|
|
307
|
+
action: e.location.action,
|
|
282
308
|
} : undefined,
|
|
283
309
|
suggestion: e.suggestion,
|
|
284
310
|
});
|
|
@@ -288,6 +314,8 @@ export async function verifySkill(input: SkillInput): Promise<VerifySkillResult>
|
|
|
288
314
|
...completeness.errors.map(mapError),
|
|
289
315
|
...determinism.errors.map(mapError),
|
|
290
316
|
...properties.errors.map(mapError),
|
|
317
|
+
...dtAlignment.map(mapError),
|
|
318
|
+
...dtIntegration.map(mapError),
|
|
291
319
|
];
|
|
292
320
|
|
|
293
321
|
return {
|
|
@@ -347,7 +375,18 @@ export async function generateActionsSkill(
|
|
|
347
375
|
generateTests: boolean = false
|
|
348
376
|
): Promise<GenerateActionsResult> {
|
|
349
377
|
const source = resolveSource(input);
|
|
350
|
-
|
|
378
|
+
|
|
379
|
+
// Parse the full file to get both the machine and any co-located decision tables
|
|
380
|
+
const { file } = parseMarkdown(source);
|
|
381
|
+
const label = resolveLabel(input);
|
|
382
|
+
if (file.machines.length === 0) {
|
|
383
|
+
throw new Error(`${label} contains no machine definition.`);
|
|
384
|
+
}
|
|
385
|
+
if (file.machines.length > 1) {
|
|
386
|
+
throw new Error(`${label} contains multiple machines. Use a single-machine file for action generation.`);
|
|
387
|
+
}
|
|
388
|
+
const machine = file.machines[0];
|
|
389
|
+
const decisionTables = file.decisionTables;
|
|
351
390
|
|
|
352
391
|
const actions: ActionScaffold[] = machine.actions.map(action => ({
|
|
353
392
|
name: action.name,
|
|
@@ -359,6 +398,18 @@ export async function generateActionsSkill(
|
|
|
359
398
|
context_used: extractContextFields(machine, action.name),
|
|
360
399
|
}));
|
|
361
400
|
|
|
401
|
+
// Compile decision table evaluator code for each DT in the file
|
|
402
|
+
const decisionTableCode: Record<string, string> = {};
|
|
403
|
+
for (const dt of decisionTables) {
|
|
404
|
+
if (language === 'python') {
|
|
405
|
+
decisionTableCode[dt.name] = compileDecisionTableToPython(dt);
|
|
406
|
+
} else if (language === 'go') {
|
|
407
|
+
decisionTableCode[dt.name] = compileDecisionTableToGo(dt);
|
|
408
|
+
} else {
|
|
409
|
+
decisionTableCode[dt.name] = compileDecisionTableToTypeScript(dt);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
362
413
|
let scaffolds: Record<string, string> = {};
|
|
363
414
|
let tests: Record<string, string> = {};
|
|
364
415
|
|
|
@@ -373,7 +424,7 @@ export async function generateActionsSkill(
|
|
|
373
424
|
temperature: config.temperature,
|
|
374
425
|
});
|
|
375
426
|
|
|
376
|
-
scaffolds = await generateWithLLM(provider, actions, machine, language as CodeGeneratorType);
|
|
427
|
+
scaffolds = await generateWithLLM(provider, actions, machine, language as CodeGeneratorType, decisionTables);
|
|
377
428
|
|
|
378
429
|
if (generateTests) {
|
|
379
430
|
tests = await generateUnitTests(provider, actions, machine, language as CodeGeneratorType);
|
|
@@ -381,7 +432,13 @@ export async function generateActionsSkill(
|
|
|
381
432
|
} else {
|
|
382
433
|
// Use template-based scaffold generation
|
|
383
434
|
for (const action of machine.actions) {
|
|
384
|
-
|
|
435
|
+
const matchedDT = findMatchingDT(action.name, decisionTables);
|
|
436
|
+
if (matchedDT && !action.hasEffect && isDTFullyAligned(matchedDT, machine)) {
|
|
437
|
+
// All DT conditions and outputs exist in context — generate fully wired code
|
|
438
|
+
scaffolds[action.name] = generateFullyWiredActionScaffold(action, machine, language, matchedDT);
|
|
439
|
+
} else {
|
|
440
|
+
scaffolds[action.name] = generateActionScaffold(action, machine, language, matchedDT ?? undefined);
|
|
441
|
+
}
|
|
385
442
|
}
|
|
386
443
|
|
|
387
444
|
if (generateTests) {
|
|
@@ -394,6 +451,7 @@ export async function generateActionsSkill(
|
|
|
394
451
|
machine: machine.name,
|
|
395
452
|
actions,
|
|
396
453
|
scaffolds,
|
|
454
|
+
decisionTableCode: Object.keys(decisionTableCode).length > 0 ? decisionTableCode : undefined,
|
|
397
455
|
tests: Object.keys(tests).length > 0 ? tests : undefined,
|
|
398
456
|
};
|
|
399
457
|
}
|
|
@@ -402,22 +460,35 @@ async function generateWithLLM(
|
|
|
402
460
|
provider: LLMProvider,
|
|
403
461
|
actions: ActionScaffold[],
|
|
404
462
|
machine: MachineDef,
|
|
405
|
-
language: CodeGeneratorType
|
|
463
|
+
language: CodeGeneratorType,
|
|
464
|
+
decisionTables: DecisionTableDef[] = []
|
|
406
465
|
): Promise<Record<string, string>> {
|
|
407
466
|
const generator = getCodeGenerator(language);
|
|
408
467
|
const scaffolds: Record<string, string> = {};
|
|
409
468
|
|
|
469
|
+
const dtContext = decisionTables.length > 0
|
|
470
|
+
? `\nDecision tables available:\n${decisionTables.map(dt =>
|
|
471
|
+
`- ${dt.name}: conditions=[${dt.conditions.map(c => c.name).join(', ')}] outputs=[${dt.actions.map(a => a.name).join(', ')}]`
|
|
472
|
+
).join('\n')}`
|
|
473
|
+
: '';
|
|
474
|
+
|
|
410
475
|
const systemPrompt = `You are an expert ${language} developer specializing in state machine action implementations.
|
|
411
476
|
Given a machine definition and action signatures, generate complete action implementations.
|
|
412
477
|
Follow the type signatures exactly. Use the provided context fields.
|
|
413
|
-
If an action has an effect, return [newContext, effect] tuple
|
|
478
|
+
If an action has an effect, return [newContext, effect] tuple.
|
|
479
|
+
If decision tables are listed, use their evaluator functions (e.g. evaluate${language === 'go' ? 'DtName' : 'DtName'}) when appropriate.`;
|
|
414
480
|
|
|
415
481
|
for (const action of actions) {
|
|
482
|
+
const matchedDT = findMatchingDT(action.name, decisionTables);
|
|
483
|
+
const dtHint = matchedDT
|
|
484
|
+
? `\nThis action should use the ${matchedDT.name} decision table evaluator.`
|
|
485
|
+
: '';
|
|
486
|
+
|
|
416
487
|
const userPrompt = `Machine: ${machine.name}
|
|
417
|
-
Context fields: ${machine.context.map(f => `${f.name}: ${f.type || 'unknown'}`).join(', ')}
|
|
488
|
+
Context fields: ${machine.context.map(f => `${f.name}: ${f.type || 'unknown'}`).join(', ')}${dtContext}
|
|
418
489
|
|
|
419
490
|
Action: ${action.signature}
|
|
420
|
-
Description: ${action.name}${action.hasEffect ? ` (effect type: ${action.effectType})` : ''}
|
|
491
|
+
Description: ${action.name}${action.hasEffect ? ` (effect type: ${action.effectType})` : ''}${dtHint}
|
|
421
492
|
|
|
422
493
|
Generate the implementation:`;
|
|
423
494
|
|
|
@@ -436,7 +507,7 @@ Generate the implementation:`;
|
|
|
436
507
|
} catch (err) {
|
|
437
508
|
console.error(`LLM error for action ${action.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
438
509
|
// Fall back to scaffold on error
|
|
439
|
-
scaffolds[action.name] = generateActionScaffold(action, machine, language);
|
|
510
|
+
scaffolds[action.name] = generateActionScaffold(action, machine, language, matchedDT ?? undefined);
|
|
440
511
|
}
|
|
441
512
|
}
|
|
442
513
|
|
|
@@ -779,10 +850,222 @@ function toPascalCase(snake: string): string {
|
|
|
779
850
|
return snake.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
780
851
|
}
|
|
781
852
|
|
|
853
|
+
/**
|
|
854
|
+
* Find a decision table whose name tokens overlap with the action name tokens.
|
|
855
|
+
* E.g. action "apply_routing_decision" matches DT "PaymentRouting" via "routing".
|
|
856
|
+
*/
|
|
857
|
+
function findMatchingDT(actionName: string, dts: DecisionTableDef[]): DecisionTableDef | null {
|
|
858
|
+
if (dts.length === 0) return null;
|
|
859
|
+
const actionTokens = new Set(
|
|
860
|
+
actionName.toLowerCase().split('_').filter(t => t.length > 2)
|
|
861
|
+
);
|
|
862
|
+
for (const dt of dts) {
|
|
863
|
+
const dtTokens = dt.name
|
|
864
|
+
.replace(/([A-Z])/g, ' $1')
|
|
865
|
+
.trim()
|
|
866
|
+
.toLowerCase()
|
|
867
|
+
.split(/\s+/)
|
|
868
|
+
.filter(t => t.length > 2);
|
|
869
|
+
if (dtTokens.some(t => actionTokens.has(t))) return dt;
|
|
870
|
+
}
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Generate a commented example DT call block to include in an action stub.
|
|
876
|
+
*/
|
|
877
|
+
function generateDTCallComment(dt: DecisionTableDef, language: string): string {
|
|
878
|
+
const dtName = dt.name;
|
|
879
|
+
|
|
880
|
+
if (language === 'python') {
|
|
881
|
+
const fnName = `evaluate_${toSnakeCase(dtName)}`;
|
|
882
|
+
const inputClass = `${toPascalCase(dtName)}Input`;
|
|
883
|
+
const inputArgs = dt.conditions.map(c => {
|
|
884
|
+
const hint = c.type === 'enum' && c.values.length > 0
|
|
885
|
+
? `str (${c.values.join(', ')})`
|
|
886
|
+
: c.type === 'bool' ? 'bool' : 'str';
|
|
887
|
+
return ` # ${c.name}=..., # ${hint} — map from ctx`;
|
|
888
|
+
}).join('\n');
|
|
889
|
+
const outputFields = dt.actions.map(a =>
|
|
890
|
+
` # # dt_result.${a.name} → ctx['${a.name}']`
|
|
891
|
+
).join('\n');
|
|
892
|
+
return [
|
|
893
|
+
` # Call ${fnName} to evaluate ${dtName} rules:`,
|
|
894
|
+
` # dt_result = ${fnName}(${inputClass}(`,
|
|
895
|
+
inputArgs,
|
|
896
|
+
` # ))`,
|
|
897
|
+
` # if dt_result is not None:`,
|
|
898
|
+
outputFields,
|
|
899
|
+
].join('\n');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (language === 'go') {
|
|
903
|
+
const fnName = `Evaluate${toPascalCase(dtName)}`;
|
|
904
|
+
const inputStruct = `${toPascalCase(dtName)}Input`;
|
|
905
|
+
const inputArgs = dt.conditions.map(c => {
|
|
906
|
+
const goField = toPascalCase(c.name);
|
|
907
|
+
const hint = c.type === 'enum' && c.values.length > 0
|
|
908
|
+
? `string (${c.values.join(', ')})`
|
|
909
|
+
: c.type === 'bool' ? 'bool' : 'string';
|
|
910
|
+
return `\t// \t${goField}: ..., // ${hint} — map from ctx`;
|
|
911
|
+
}).join('\n');
|
|
912
|
+
const outputFields = dt.actions.map(a =>
|
|
913
|
+
`\t// \tresult["${a.name}"] = dtResult.${toPascalCase(a.name)}`
|
|
914
|
+
).join('\n');
|
|
915
|
+
return [
|
|
916
|
+
`\t// Call ${fnName} to evaluate ${dtName} rules:`,
|
|
917
|
+
`\t// dtResult := ${fnName}(${inputStruct}{`,
|
|
918
|
+
inputArgs,
|
|
919
|
+
`\t// })`,
|
|
920
|
+
`\t// if dtResult != nil {`,
|
|
921
|
+
outputFields,
|
|
922
|
+
`\t// }`,
|
|
923
|
+
].join('\n');
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// TypeScript (default)
|
|
927
|
+
const fnName = `evaluate${toPascalCase(dtName)}`;
|
|
928
|
+
const inputType = `${toPascalCase(dtName)}Input`;
|
|
929
|
+
const inputArgs = dt.conditions.map(c => {
|
|
930
|
+
const hint = c.type === 'enum' && c.values.length > 0
|
|
931
|
+
? `enum: ${c.values.join(', ')}`
|
|
932
|
+
: c.type;
|
|
933
|
+
return ` // ${c.name}: /* ctx.? */ as ${inputType}['${c.name}'], // ${hint} — map from ctx`;
|
|
934
|
+
}).join('\n');
|
|
935
|
+
const outputFields = dt.actions.map(a =>
|
|
936
|
+
` // ${a.name}: dtResult.${a.name},`
|
|
937
|
+
).join('\n');
|
|
938
|
+
return [
|
|
939
|
+
` // Call ${fnName} to evaluate ${dtName} rules:`,
|
|
940
|
+
` // const dtResult = ${fnName}({`,
|
|
941
|
+
inputArgs,
|
|
942
|
+
` // });`,
|
|
943
|
+
` // if (dtResult !== null) {`,
|
|
944
|
+
` // return { ...ctx,`,
|
|
945
|
+
outputFields,
|
|
946
|
+
` // };`,
|
|
947
|
+
` // }`,
|
|
948
|
+
].join('\n');
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Returns true when every DT condition name and output name exists as a
|
|
953
|
+
* context field in the machine — the full co-location contract is satisfied.
|
|
954
|
+
*/
|
|
955
|
+
function isDTFullyAligned(dt: DecisionTableDef, machine: MachineDef): boolean {
|
|
956
|
+
const contextNames = new Set(machine.context.map(f => f.name));
|
|
957
|
+
return dt.conditions.every(c => contextNames.has(c.name)) &&
|
|
958
|
+
dt.actions.every(a => contextNames.has(a.name));
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/** Generate a Go type assertion for reading a context value. */
|
|
962
|
+
function goCtxRead(fieldName: string, condType: string): string {
|
|
963
|
+
if (condType === 'bool') return `ctx["${fieldName}"].(bool)`;
|
|
964
|
+
if (condType === 'int_range') return `ctx["${fieldName}"].(int)`;
|
|
965
|
+
return `ctx["${fieldName}"].(string)`;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function generateFullyWiredActionScaffold(
|
|
969
|
+
action: { name: string; parameters: string[]; returnType: string; hasEffect: boolean; effectType?: string },
|
|
970
|
+
machine: MachineDef,
|
|
971
|
+
language: string,
|
|
972
|
+
dt: DecisionTableDef
|
|
973
|
+
): string {
|
|
974
|
+
if (language === 'python') {
|
|
975
|
+
const dtFnName = `evaluate_${toSnakeCase(dt.name)}`;
|
|
976
|
+
const inputClass = `${toPascalCase(dt.name)}Input`;
|
|
977
|
+
const inputArgs = dt.conditions
|
|
978
|
+
.map(c => ` ${c.name}=ctx['${c.name}'],`)
|
|
979
|
+
.join('\n');
|
|
980
|
+
const outputAssigns = dt.actions
|
|
981
|
+
.map(a => ` '${a.name}': dt_result.${a.name},`)
|
|
982
|
+
.join('\n');
|
|
983
|
+
return `# Action: ${action.name}
|
|
984
|
+
# Decision table: ${dt.name}
|
|
985
|
+
# Register via: machine.register_action("${action.name}", ${action.name})
|
|
986
|
+
|
|
987
|
+
from typing import Any
|
|
988
|
+
|
|
989
|
+
async def ${action.name}(ctx: dict[str, Any], event: Any = None) -> dict[str, Any]:
|
|
990
|
+
dt_result = ${dtFnName}(${inputClass}(
|
|
991
|
+
${inputArgs}
|
|
992
|
+
))
|
|
993
|
+
if dt_result is not None:
|
|
994
|
+
return {**ctx,
|
|
995
|
+
${outputAssigns}
|
|
996
|
+
}
|
|
997
|
+
return dict(ctx)
|
|
998
|
+
`;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (language === 'go') {
|
|
1002
|
+
const fnName = toPascalCase(action.name);
|
|
1003
|
+
const dtFnName = `Evaluate${toPascalCase(dt.name)}`;
|
|
1004
|
+
const inputStruct = `${toPascalCase(dt.name)}Input`;
|
|
1005
|
+
const ctxReads = dt.conditions.map(c => {
|
|
1006
|
+
const varName = toPascalCase(c.name).charAt(0).toLowerCase() + toPascalCase(c.name).slice(1);
|
|
1007
|
+
return `\t${varName}, _ := ${goCtxRead(c.name, c.type)}`;
|
|
1008
|
+
}).join('\n');
|
|
1009
|
+
const inputFields = dt.conditions.map(c => {
|
|
1010
|
+
const goField = toPascalCase(c.name);
|
|
1011
|
+
const varName = goField.charAt(0).toLowerCase() + goField.slice(1);
|
|
1012
|
+
return `\t\t${goField}: ${varName},`;
|
|
1013
|
+
}).join('\n');
|
|
1014
|
+
const outputAssigns = dt.actions
|
|
1015
|
+
.map(a => `\t\tresult["${a.name}"] = dtResult.${toPascalCase(a.name)}`)
|
|
1016
|
+
.join('\n');
|
|
1017
|
+
return `// Action: ${action.name}
|
|
1018
|
+
// Decision table: ${dt.name}
|
|
1019
|
+
// Register via: machine.RegisterAction("${action.name}", ${fnName})
|
|
1020
|
+
|
|
1021
|
+
func ${fnName}(ctx orca.Context, event map[string]any) map[string]any {
|
|
1022
|
+
${ctxReads}
|
|
1023
|
+
\tdtResult := ${dtFnName}(${inputStruct}{
|
|
1024
|
+
${inputFields}
|
|
1025
|
+
\t})
|
|
1026
|
+
\tresult := make(orca.Context)
|
|
1027
|
+
\tfor k, v := range ctx {
|
|
1028
|
+
\t\tresult[k] = v
|
|
1029
|
+
\t}
|
|
1030
|
+
\tif dtResult != nil {
|
|
1031
|
+
${outputAssigns}
|
|
1032
|
+
\t}
|
|
1033
|
+
\treturn result
|
|
1034
|
+
}
|
|
1035
|
+
`;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// TypeScript (default)
|
|
1039
|
+
const dtFnName = `evaluate${toPascalCase(dt.name)}`;
|
|
1040
|
+
const inputArgs = dt.conditions
|
|
1041
|
+
.map(c => ` ${c.name}: ctx.${c.name},`)
|
|
1042
|
+
.join('\n');
|
|
1043
|
+
const outputAssigns = dt.actions
|
|
1044
|
+
.map(a => ` ${a.name}: dtResult.${a.name},`)
|
|
1045
|
+
.join('\n');
|
|
1046
|
+
return `// Action: ${action.name}
|
|
1047
|
+
// Decision table: ${dt.name}
|
|
1048
|
+
|
|
1049
|
+
export function ${action.name}(ctx: Context): Context {
|
|
1050
|
+
const dtResult = ${dtFnName}({
|
|
1051
|
+
${inputArgs}
|
|
1052
|
+
});
|
|
1053
|
+
if (dtResult !== null) {
|
|
1054
|
+
return {
|
|
1055
|
+
...ctx,
|
|
1056
|
+
${outputAssigns}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
return { ...ctx };
|
|
1060
|
+
}
|
|
1061
|
+
`;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
782
1064
|
function generateActionScaffold(
|
|
783
1065
|
action: { name: string; parameters: string[]; returnType: string; hasEffect: boolean; effectType?: string },
|
|
784
1066
|
machine: MachineDef,
|
|
785
|
-
language: string
|
|
1067
|
+
language: string,
|
|
1068
|
+
matchedDT?: DecisionTableDef
|
|
786
1069
|
): string {
|
|
787
1070
|
if (language === 'python') {
|
|
788
1071
|
if (action.hasEffect) {
|
|
@@ -800,13 +1083,17 @@ async def ${action.name}(effect: Effect) -> EffectResult:
|
|
|
800
1083
|
return EffectResult(status=EffectStatus.SUCCESS, data={})
|
|
801
1084
|
`;
|
|
802
1085
|
}
|
|
803
|
-
|
|
1086
|
+
const pyDTComment = matchedDT ? '\n' + generateDTCallComment(matchedDT, 'python') + '\n' : '';
|
|
1087
|
+
const pyTodo = matchedDT
|
|
1088
|
+
? ` # TODO: Implement action using ${matchedDT.name} decision table`
|
|
1089
|
+
: ' # TODO: Implement action';
|
|
1090
|
+
return `# Action: ${action.name}${matchedDT ? `\n# Decision table: ${matchedDT.name}` : ''}
|
|
804
1091
|
# Register via: machine.register_action("${action.name}", ${action.name})
|
|
805
1092
|
|
|
806
1093
|
from typing import Any
|
|
807
1094
|
|
|
808
|
-
async def ${action.name}(ctx: dict[str, Any], event: Any = None) -> dict[str, Any]
|
|
809
|
-
|
|
1095
|
+
async def ${action.name}(ctx: dict[str, Any], event: Any = None) -> dict[str, Any]:${pyDTComment}
|
|
1096
|
+
${pyTodo}
|
|
810
1097
|
return dict(ctx)
|
|
811
1098
|
`;
|
|
812
1099
|
}
|
|
@@ -827,11 +1114,15 @@ func ${fnName}(effect orca.Effect) orca.EffectResult {
|
|
|
827
1114
|
}
|
|
828
1115
|
`;
|
|
829
1116
|
}
|
|
830
|
-
|
|
1117
|
+
const goDTComment = matchedDT ? '\n' + generateDTCallComment(matchedDT, 'go') + '\n' : '';
|
|
1118
|
+
const goTodo = matchedDT
|
|
1119
|
+
? `\t// TODO: Implement action using ${matchedDT.name} decision table`
|
|
1120
|
+
: '\t// TODO: Implement action';
|
|
1121
|
+
return `// Action: ${action.name}${matchedDT ? `\n// Decision table: ${matchedDT.name}` : ''}
|
|
831
1122
|
// Register via: machine.RegisterAction("${action.name}", ${fnName})
|
|
832
1123
|
|
|
833
|
-
func ${fnName}(ctx orca.Context, event map[string]any) map[string]any {
|
|
834
|
-
|
|
1124
|
+
func ${fnName}(ctx orca.Context, event map[string]any) map[string]any {${goDTComment}
|
|
1125
|
+
${goTodo}
|
|
835
1126
|
\tresult := make(orca.Context)
|
|
836
1127
|
\tfor k, v := range ctx {
|
|
837
1128
|
\t\tresult[k] = v
|
|
@@ -863,10 +1154,15 @@ export function ${action.name}(${params}): [Context, Effect<${action.effectType}
|
|
|
863
1154
|
// }
|
|
864
1155
|
`;
|
|
865
1156
|
}
|
|
866
|
-
return `// Action: ${action.name}
|
|
867
1157
|
|
|
868
|
-
|
|
869
|
-
|
|
1158
|
+
const tsDTComment = matchedDT ? '\n' + generateDTCallComment(matchedDT, 'typescript') + '\n' : '';
|
|
1159
|
+
const tsTodo = matchedDT
|
|
1160
|
+
? ` // TODO: Implement action using ${matchedDT.name} decision table`
|
|
1161
|
+
: ' // TODO: Implement action';
|
|
1162
|
+
return `// Action: ${action.name}${matchedDT ? `\n// Decision table: ${matchedDT.name}` : ''}
|
|
1163
|
+
|
|
1164
|
+
export function ${action.name}(ctx: Context): Context {${tsDTComment}
|
|
1165
|
+
${tsTodo}
|
|
870
1166
|
return { ...ctx };
|
|
871
1167
|
}
|
|
872
1168
|
`;
|