@orcalang/orca-lang 0.1.21 → 0.1.24
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 +1 -0
- package/dist/compiler/dt-compiler.d.ts.map +1 -1
- package/dist/compiler/dt-compiler.js +149 -3
- package/dist/compiler/dt-compiler.js.map +1 -1
- package/dist/health-check.js +75 -0
- package/dist/health-check.js.map +1 -1
- package/dist/parser/dt-ast.d.ts +11 -1
- package/dist/parser/dt-ast.d.ts.map +1 -1
- package/dist/parser/dt-parser.d.ts.map +1 -1
- package/dist/parser/dt-parser.js +40 -8
- package/dist/parser/dt-parser.js.map +1 -1
- package/dist/skills.d.ts +2 -2
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +200 -6
- package/dist/skills.js.map +1 -1
- package/dist/tools.js +4 -4
- package/dist/tools.js.map +1 -1
- package/dist/verifier/dt-verifier.d.ts.map +1 -1
- package/dist/verifier/dt-verifier.js +259 -31
- package/dist/verifier/dt-verifier.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler/dt-compiler.ts +151 -3
- package/src/health-check.ts +79 -0
- package/src/parser/dt-ast.ts +4 -2
- package/src/parser/dt-parser.ts +46 -8
- package/src/skills.ts +201 -7
- package/src/tools.ts +4 -4
- package/src/verifier/dt-verifier.ts +272 -29
package/src/skills.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
compileDecisionTableToTypeScript,
|
|
14
14
|
compileDecisionTableToPython,
|
|
15
15
|
compileDecisionTableToGo,
|
|
16
|
+
compileDecisionTableToRust,
|
|
16
17
|
compileDecisionTableToJSON,
|
|
17
18
|
toSnakeCase,
|
|
18
19
|
} from './compiler/dt-compiler.js';
|
|
@@ -405,6 +406,8 @@ export async function generateActionsSkill(
|
|
|
405
406
|
decisionTableCode[dt.name] = compileDecisionTableToPython(dt);
|
|
406
407
|
} else if (language === 'go') {
|
|
407
408
|
decisionTableCode[dt.name] = compileDecisionTableToGo(dt);
|
|
409
|
+
} else if (language === 'rust') {
|
|
410
|
+
decisionTableCode[dt.name] = compileDecisionTableToRust(dt);
|
|
408
411
|
} else {
|
|
409
412
|
decisionTableCode[dt.name] = compileDecisionTableToTypeScript(dt);
|
|
410
413
|
}
|
|
@@ -578,6 +581,9 @@ function generateTemplateTestsForAction(action: ActionScaffold, machine: Machine
|
|
|
578
581
|
if (language === 'go') {
|
|
579
582
|
return generateGoTestScaffold(action, machine);
|
|
580
583
|
}
|
|
584
|
+
if (language === 'rust') {
|
|
585
|
+
return generateRustTestScaffold(action, machine);
|
|
586
|
+
}
|
|
581
587
|
return generateTypeScriptTestScaffold(action, machine);
|
|
582
588
|
}
|
|
583
589
|
|
|
@@ -762,6 +768,63 @@ ${preserved.map(f => `\tif result["${f}"] != ctx["${f}"] {\n\t\tt.Errorf("${f}:
|
|
|
762
768
|
`;
|
|
763
769
|
}
|
|
764
770
|
|
|
771
|
+
function generateRustTestScaffold(action: ActionScaffold, machine: MachineDef): string {
|
|
772
|
+
const ctxFields = machine.context.map(f => {
|
|
773
|
+
return ` "${f.name}": ${getDefaultValueForType(f.type, 'rust')}`;
|
|
774
|
+
}).join(',\n');
|
|
775
|
+
const contextUsed = action.context_used.length > 0 ? action.context_used : machine.context.map(f => f.name);
|
|
776
|
+
const preserved = contextUsed.filter(f => !actionModifiesField(action, f));
|
|
777
|
+
|
|
778
|
+
if (action.hasEffect) {
|
|
779
|
+
return `// Tests for ${action.name}
|
|
780
|
+
#[cfg(test)]
|
|
781
|
+
mod tests {
|
|
782
|
+
use super::*;
|
|
783
|
+
use serde_json::json;
|
|
784
|
+
|
|
785
|
+
#[test]
|
|
786
|
+
fn test_${action.name}_executes_effect() {
|
|
787
|
+
let ctx = json!({
|
|
788
|
+
${ctxFields}
|
|
789
|
+
});
|
|
790
|
+
let event = json!({"type": "test"});
|
|
791
|
+
let (result, effect) = ${action.name}(&ctx, &event);
|
|
792
|
+
assert!(result.is_object());
|
|
793
|
+
assert_eq!(effect.effect_type, "${action.effectType}");
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
`;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return `// Tests for ${action.name}
|
|
800
|
+
#[cfg(test)]
|
|
801
|
+
mod tests {
|
|
802
|
+
use super::*;
|
|
803
|
+
use serde_json::json;
|
|
804
|
+
|
|
805
|
+
#[test]
|
|
806
|
+
fn test_${action.name}_transforms_context() {
|
|
807
|
+
let ctx = json!({
|
|
808
|
+
${ctxFields}
|
|
809
|
+
});
|
|
810
|
+
let event = json!({"type": "test"});
|
|
811
|
+
let result = ${action.name}(&ctx, &event);
|
|
812
|
+
assert!(result.is_object());
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
#[test]
|
|
816
|
+
fn test_${action.name}_preserves_fields() {
|
|
817
|
+
let ctx = json!({
|
|
818
|
+
${ctxFields}
|
|
819
|
+
});
|
|
820
|
+
let event = json!({"type": "test"});
|
|
821
|
+
let result = ${action.name}(&ctx, &event);
|
|
822
|
+
${preserved.map(f => ` assert_eq!(result["${f}"], ctx["${f}"]);`).join('\n')}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
`;
|
|
826
|
+
}
|
|
827
|
+
|
|
765
828
|
function actionModifiesField(action: ActionScaffold, fieldName: string): boolean {
|
|
766
829
|
// Heuristic: if the action name suggests modification of a field, it likely modifies it
|
|
767
830
|
const modifiers = ['increment', 'decrement', 'set', 'update', 'add', 'remove', 'clear', 'reset', 'toggle'];
|
|
@@ -783,7 +846,7 @@ function actionModifiesField(action: ActionScaffold, fieldName: string): boolean
|
|
|
783
846
|
|
|
784
847
|
function getDefaultValueForType(type: any, language: string = 'typescript'): string {
|
|
785
848
|
if (!type) {
|
|
786
|
-
return language === 'python' ? '""' : language === 'go' ? '""' : "''";
|
|
849
|
+
return language === 'python' ? '""' : language === 'go' ? '""' : language === 'rust' ? '"".to_string()' : "''";
|
|
787
850
|
}
|
|
788
851
|
if (typeof type === 'object' && 'kind' in type) {
|
|
789
852
|
if (language === 'python') {
|
|
@@ -808,6 +871,22 @@ function getDefaultValueForType(type: any, language: string = 'typescript'): str
|
|
|
808
871
|
case 'map': return 'nil';
|
|
809
872
|
case 'custom': return 'nil';
|
|
810
873
|
}
|
|
874
|
+
} else if (language === 'rust') {
|
|
875
|
+
switch (type.kind) {
|
|
876
|
+
case 'string': return '"".to_string()';
|
|
877
|
+
case 'int': return '0';
|
|
878
|
+
case 'decimal': return '0.0';
|
|
879
|
+
case 'bool': return 'false';
|
|
880
|
+
case 'optional': return 'null';
|
|
881
|
+
case 'array': return 'vec![]';
|
|
882
|
+
case 'map': return 'HashMap::new()';
|
|
883
|
+
case 'custom': {
|
|
884
|
+
// Handle common type aliases
|
|
885
|
+
if (type.name === 'float' || type.name === 'double') return '0.0';
|
|
886
|
+
if (type.name === 'integer' || type.name === 'long') return '0';
|
|
887
|
+
return 'null';
|
|
888
|
+
}
|
|
889
|
+
}
|
|
811
890
|
} else {
|
|
812
891
|
switch (type.kind) {
|
|
813
892
|
case 'string': return "''";
|
|
@@ -821,7 +900,7 @@ function getDefaultValueForType(type: any, language: string = 'typescript'): str
|
|
|
821
900
|
}
|
|
822
901
|
}
|
|
823
902
|
}
|
|
824
|
-
return language === 'python' ? 'None' : language === 'go' ? 'nil' : 'null';
|
|
903
|
+
return language === 'python' ? 'None' : language === 'go' ? 'nil' : language === 'rust' ? 'null' : 'null';
|
|
825
904
|
}
|
|
826
905
|
|
|
827
906
|
function extractContextFields(machine: MachineDef, actionName: string): string[] {
|
|
@@ -923,6 +1002,29 @@ function generateDTCallComment(dt: DecisionTableDef, language: string): string {
|
|
|
923
1002
|
].join('\n');
|
|
924
1003
|
}
|
|
925
1004
|
|
|
1005
|
+
if (language === 'rust') {
|
|
1006
|
+
const fnName = `evaluate_${toSnakeCase(dtName)}`;
|
|
1007
|
+
const inputStruct = `${toPascalCase(dtName)}Input`;
|
|
1008
|
+
const inputArgs = dt.conditions.map(c => {
|
|
1009
|
+
const hint = c.type === 'enum' && c.values.length > 0
|
|
1010
|
+
? `String (${c.values.join(', ')})`
|
|
1011
|
+
: c.type === 'bool' ? 'bool' : c.type === 'int_range' ? 'i64' : 'String';
|
|
1012
|
+
return ` // ${c.name}: ..., // ${hint} — map from ctx`;
|
|
1013
|
+
}).join('\n');
|
|
1014
|
+
const outputFields = dt.actions.map(a =>
|
|
1015
|
+
` // result["${a.name}"] = Value::String(dt_result.${a.name}.clone());`
|
|
1016
|
+
).join('\n');
|
|
1017
|
+
return [
|
|
1018
|
+
` // Call ${fnName} to evaluate ${dtName} rules:`,
|
|
1019
|
+
` // let dt_input = ${inputStruct} {`,
|
|
1020
|
+
inputArgs,
|
|
1021
|
+
` // };`,
|
|
1022
|
+
` // if let Some(dt_result) = ${fnName}(&dt_input) {`,
|
|
1023
|
+
outputFields,
|
|
1024
|
+
` // }`,
|
|
1025
|
+
].join('\n');
|
|
1026
|
+
}
|
|
1027
|
+
|
|
926
1028
|
// TypeScript (default)
|
|
927
1029
|
const fnName = `evaluate${toPascalCase(dtName)}`;
|
|
928
1030
|
const inputType = `${toPascalCase(dtName)}Input`;
|
|
@@ -965,6 +1067,14 @@ function goCtxRead(fieldName: string, condType: string): string {
|
|
|
965
1067
|
return `ctx["${fieldName}"].(string)`;
|
|
966
1068
|
}
|
|
967
1069
|
|
|
1070
|
+
/** Generate a Rust serde_json value extraction for reading a context value. */
|
|
1071
|
+
function rustCtxRead(fieldName: string, condType: string): string {
|
|
1072
|
+
if (condType === 'bool') return `ctx["${fieldName}"].as_bool().unwrap_or_default()`;
|
|
1073
|
+
if (condType === 'int_range') return `ctx["${fieldName}"].as_i64().unwrap_or_default()`;
|
|
1074
|
+
if (condType === 'decimal_range') return `ctx["${fieldName}"].as_f64().unwrap_or_default()`;
|
|
1075
|
+
return `ctx["${fieldName}"].as_str().unwrap_or_default().to_string()`;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
968
1078
|
function generateFullyWiredActionScaffold(
|
|
969
1079
|
action: { name: string; parameters: string[]; returnType: string; hasEffect: boolean; effectType?: string },
|
|
970
1080
|
machine: MachineDef,
|
|
@@ -1035,6 +1145,45 @@ ${outputAssigns}
|
|
|
1035
1145
|
`;
|
|
1036
1146
|
}
|
|
1037
1147
|
|
|
1148
|
+
if (language === 'rust') {
|
|
1149
|
+
const dtFnName = `evaluate_${toSnakeCase(dt.name)}`;
|
|
1150
|
+
const inputStruct = `${toPascalCase(dt.name)}Input`;
|
|
1151
|
+
const ctxReads = dt.conditions
|
|
1152
|
+
.map(c => ` let ${c.name} = ${rustCtxRead(c.name, c.type)};`)
|
|
1153
|
+
.join('\n');
|
|
1154
|
+
const inputFields = dt.conditions
|
|
1155
|
+
.map(c => ` ${c.name},`)
|
|
1156
|
+
.join('\n');
|
|
1157
|
+
const outputAssigns = dt.actions
|
|
1158
|
+
.map(a => {
|
|
1159
|
+
const aType = a.type as string;
|
|
1160
|
+
if (aType === 'bool') return ` result["${a.name}"] = Value::Bool(dt_result.${a.name});`;
|
|
1161
|
+
if (aType === 'int_range') return ` result["${a.name}"] = json!(dt_result.${a.name});`;
|
|
1162
|
+
if (aType === 'decimal_range') return ` result["${a.name}"] = json!(dt_result.${a.name});`;
|
|
1163
|
+
return ` result["${a.name}"] = Value::String(dt_result.${a.name}.clone());`;
|
|
1164
|
+
})
|
|
1165
|
+
.join('\n');
|
|
1166
|
+
return `// Action: ${action.name}
|
|
1167
|
+
// Decision table: ${dt.name}
|
|
1168
|
+
// Register via: machine.register_action_rust("${action.name}", Box::new(${action.name}))
|
|
1169
|
+
|
|
1170
|
+
use serde_json::{Value, json};
|
|
1171
|
+
|
|
1172
|
+
fn ${action.name}(ctx: &Value, event: &Value) -> Value {
|
|
1173
|
+
${ctxReads}
|
|
1174
|
+
let dt_input = ${inputStruct} {
|
|
1175
|
+
${inputFields}
|
|
1176
|
+
};
|
|
1177
|
+
if let Some(dt_result) = ${dtFnName}(&dt_input) {
|
|
1178
|
+
let mut result = ctx.clone();
|
|
1179
|
+
${outputAssigns}
|
|
1180
|
+
return result;
|
|
1181
|
+
}
|
|
1182
|
+
ctx.clone()
|
|
1183
|
+
}
|
|
1184
|
+
`;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1038
1187
|
// TypeScript (default)
|
|
1039
1188
|
const dtFnName = `evaluate${toPascalCase(dt.name)}`;
|
|
1040
1189
|
const inputArgs = dt.conditions
|
|
@@ -1132,6 +1281,36 @@ ${goTodo}
|
|
|
1132
1281
|
`;
|
|
1133
1282
|
}
|
|
1134
1283
|
|
|
1284
|
+
if (language === 'rust') {
|
|
1285
|
+
if (action.hasEffect) {
|
|
1286
|
+
return `// Action: ${action.name}
|
|
1287
|
+
// Effect: ${action.effectType}
|
|
1288
|
+
// Register via: machine.register_action_rust("${action.name}", Box::new(${action.name}))
|
|
1289
|
+
|
|
1290
|
+
use serde_json::Value;
|
|
1291
|
+
|
|
1292
|
+
fn ${action.name}(ctx: &Value, event: &Value) -> (Value, Effect) {
|
|
1293
|
+
// TODO: Implement effect
|
|
1294
|
+
(ctx.clone(), Effect { effect_type: "${action.effectType}".to_string(), payload: Value::Null })
|
|
1295
|
+
}
|
|
1296
|
+
`;
|
|
1297
|
+
}
|
|
1298
|
+
const rustDTComment = matchedDT ? '\n' + generateDTCallComment(matchedDT, 'rust') + '\n' : '';
|
|
1299
|
+
const rustTodo = matchedDT
|
|
1300
|
+
? ` // TODO: Implement action using ${matchedDT.name} decision table`
|
|
1301
|
+
: ' // TODO: Implement action';
|
|
1302
|
+
return `// Action: ${action.name}${matchedDT ? `\n// Decision table: ${matchedDT.name}` : ''}
|
|
1303
|
+
// Register via: machine.register_action_rust("${action.name}", Box::new(${action.name}))
|
|
1304
|
+
|
|
1305
|
+
use serde_json::Value;
|
|
1306
|
+
|
|
1307
|
+
fn ${action.name}(ctx: &Value, event: &Value) -> Value {${rustDTComment}
|
|
1308
|
+
${rustTodo}
|
|
1309
|
+
ctx.clone()
|
|
1310
|
+
}
|
|
1311
|
+
`;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1135
1314
|
// TypeScript (default)
|
|
1136
1315
|
const params = action.parameters.map(p => {
|
|
1137
1316
|
if (p === 'ctx' || p === 'Context') return 'ctx: Context';
|
|
@@ -1996,7 +2175,7 @@ export interface VerifyDTSkillResult {
|
|
|
1996
2175
|
|
|
1997
2176
|
export interface CompileDTSkillResult {
|
|
1998
2177
|
status: 'success' | 'error';
|
|
1999
|
-
target:
|
|
2178
|
+
target: string;
|
|
2000
2179
|
output: string;
|
|
2001
2180
|
warnings: SkillError[];
|
|
2002
2181
|
}
|
|
@@ -2109,7 +2288,7 @@ export function verifyDTSkill(input: SkillInput): VerifyDTSkillResult {
|
|
|
2109
2288
|
/**
|
|
2110
2289
|
* Compile a decision table to TypeScript or JSON.
|
|
2111
2290
|
*/
|
|
2112
|
-
export function compileDTSkill(input: SkillInput, target:
|
|
2291
|
+
export function compileDTSkill(input: SkillInput, target: string = 'typescript'): CompileDTSkillResult {
|
|
2113
2292
|
try {
|
|
2114
2293
|
const source = resolveSource(input);
|
|
2115
2294
|
const { file } = parseMarkdown(source);
|
|
@@ -2124,9 +2303,24 @@ export function compileDTSkill(input: SkillInput, target: 'typescript' | 'json'
|
|
|
2124
2303
|
}
|
|
2125
2304
|
|
|
2126
2305
|
const dt = file.decisionTables[0];
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
:
|
|
2306
|
+
let output: string;
|
|
2307
|
+
switch (target) {
|
|
2308
|
+
case 'json':
|
|
2309
|
+
output = compileDecisionTableToJSON(dt);
|
|
2310
|
+
break;
|
|
2311
|
+
case 'python':
|
|
2312
|
+
output = compileDecisionTableToPython(dt);
|
|
2313
|
+
break;
|
|
2314
|
+
case 'go':
|
|
2315
|
+
output = compileDecisionTableToGo(dt);
|
|
2316
|
+
break;
|
|
2317
|
+
case 'rust':
|
|
2318
|
+
output = compileDecisionTableToRust(dt);
|
|
2319
|
+
break;
|
|
2320
|
+
default:
|
|
2321
|
+
output = compileDecisionTableToTypeScript(dt);
|
|
2322
|
+
break;
|
|
2323
|
+
}
|
|
2130
2324
|
|
|
2131
2325
|
return { status: 'success', target, output, warnings: [] };
|
|
2132
2326
|
} catch (err) {
|
package/src/tools.ts
CHANGED
|
@@ -72,14 +72,14 @@ export const ORCA_TOOLS: ToolDef[] = [
|
|
|
72
72
|
{
|
|
73
73
|
name: 'generate_actions',
|
|
74
74
|
description:
|
|
75
|
-
'Generate action scaffold code from verified machine. lang: typescript (default), python, or
|
|
75
|
+
'Generate action scaffold code from verified machine. lang: typescript (default), python, go, or rust. Pass verified .orca.md source. use_llm: true for implementations vs templates. generate_tests: true for test scaffolds.',
|
|
76
76
|
inputSchema: {
|
|
77
77
|
type: 'object',
|
|
78
78
|
properties: {
|
|
79
79
|
source: { type: 'string', description: 'Raw .orca.md content of a valid machine. Actions in the "## actions" table (| Name | Signature |) will have stubs generated.' },
|
|
80
80
|
lang: {
|
|
81
81
|
type: 'string',
|
|
82
|
-
enum: ['typescript', 'python', 'go'],
|
|
82
|
+
enum: ['typescript', 'python', 'go', 'rust'],
|
|
83
83
|
description: 'Target language (default: typescript)',
|
|
84
84
|
},
|
|
85
85
|
use_llm: {
|
|
@@ -170,14 +170,14 @@ export const ORCA_TOOLS: ToolDef[] = [
|
|
|
170
170
|
{
|
|
171
171
|
name: 'compile_decision_table',
|
|
172
172
|
description:
|
|
173
|
-
'Compile verified decision table to TypeScript evaluator function or portable JSON. Run verify_decision_table first.
|
|
173
|
+
'Compile verified decision table to TypeScript, Python, Go, or Rust evaluator function, or portable JSON. Run verify_decision_table first.',
|
|
174
174
|
inputSchema: {
|
|
175
175
|
type: 'object',
|
|
176
176
|
properties: {
|
|
177
177
|
source: { type: 'string', description: 'Raw .orca.md content containing a # decision_table heading.' },
|
|
178
178
|
target: {
|
|
179
179
|
type: 'string',
|
|
180
|
-
enum: ['typescript', 'json'],
|
|
180
|
+
enum: ['typescript', 'python', 'go', 'rust', 'json'],
|
|
181
181
|
description: 'Compilation target (default: typescript)',
|
|
182
182
|
},
|
|
183
183
|
},
|