@o-lang/olang 1.0.3 → 1.0.5
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/cli.js +54 -6
- package/package.json +1 -1
- package/src/parser.js +56 -0
- package/src/runtime.js +89 -3
package/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const { Command } = require('commander');
|
|
3
|
-
const { parse } = require('
|
|
4
|
-
const { execute } = require('
|
|
3
|
+
const { parse } = require('../src/parser'); // adjusted path if bin vs root
|
|
4
|
+
const { execute } = require('../src/runtime');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
|
|
@@ -21,6 +21,8 @@ function ensureOlExtension(filename) {
|
|
|
21
21
|
* Default mock resolver (for demo use)
|
|
22
22
|
*/
|
|
23
23
|
async function defaultMockResolver(action, context) {
|
|
24
|
+
if (!action || typeof action !== 'string') return `[Unhandled: ${String(action)}]`;
|
|
25
|
+
|
|
24
26
|
if (action.startsWith('Search for ')) {
|
|
25
27
|
return {
|
|
26
28
|
title: "HR Policy 2025",
|
|
@@ -46,6 +48,44 @@ async function defaultMockResolver(action, context) {
|
|
|
46
48
|
return `[Unhandled: ${action}]`;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Built-in Math Resolver (so action style math strings are handled too)
|
|
53
|
+
* Supports action strings like: add(1,2), subtract(5,2), multiply(2,3), divide(6,3), sum([1,2,3])
|
|
54
|
+
* Note: runtime handles calculate steps; this resolver helps when parser emits actions with math strings.
|
|
55
|
+
*/
|
|
56
|
+
async function builtInMathResolver(action, context) {
|
|
57
|
+
if (!action || typeof action !== 'string') return null;
|
|
58
|
+
|
|
59
|
+
// Replace contextual placeholders {var}
|
|
60
|
+
const a = action.replace(/\{([^\}]+)\}/g, (_, k) => {
|
|
61
|
+
const v = context[k.trim()];
|
|
62
|
+
return v !== undefined ? v : `{${k}}`;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// simple function matches
|
|
66
|
+
let m;
|
|
67
|
+
m = a.match(/^add\(([^,]+),\s*([^)]+)\)$/i);
|
|
68
|
+
if (m) return parseFloat(m[1]) + parseFloat(m[2]);
|
|
69
|
+
|
|
70
|
+
m = a.match(/^subtract\(([^,]+),\s*([^)]+)\)$/i);
|
|
71
|
+
if (m) return parseFloat(m[1]) - parseFloat(m[2]);
|
|
72
|
+
|
|
73
|
+
m = a.match(/^multiply\(([^,]+),\s*([^)]+)\)$/i);
|
|
74
|
+
if (m) return parseFloat(m[1]) * parseFloat(m[2]);
|
|
75
|
+
|
|
76
|
+
m = a.match(/^divide\(([^,]+),\s*([^)]+)\)$/i);
|
|
77
|
+
if (m) return parseFloat(m[1]) / parseFloat(m[2]);
|
|
78
|
+
|
|
79
|
+
m = a.match(/^sum\(\s*\[([^\]]+)\]\s*\)$/i);
|
|
80
|
+
if (m) {
|
|
81
|
+
const arr = m[1].split(',').map(s => parseFloat(s.trim()));
|
|
82
|
+
return arr.reduce((s, v) => s + v, 0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// not a math action
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
49
89
|
/**
|
|
50
90
|
* Resolver chaining mechanism
|
|
51
91
|
*/
|
|
@@ -90,13 +130,19 @@ function loadSingleResolver(specifier) {
|
|
|
90
130
|
}
|
|
91
131
|
}
|
|
92
132
|
|
|
133
|
+
/**
|
|
134
|
+
* loadResolverChain: include built-in math resolver first, then user resolvers, then default mock resolver
|
|
135
|
+
*/
|
|
93
136
|
function loadResolverChain(specifiers) {
|
|
137
|
+
const userResolvers = specifiers?.map(loadSingleResolver) || [];
|
|
138
|
+
const resolvers = [builtInMathResolver, ...userResolvers, defaultMockResolver];
|
|
139
|
+
|
|
94
140
|
if (!specifiers || specifiers.length === 0) {
|
|
95
|
-
console.log('ℹ️ No resolver provided. Using default mock resolver.');
|
|
96
|
-
|
|
141
|
+
console.log('ℹ️ No resolver provided. Using built-in math + default mock resolver.');
|
|
142
|
+
} else {
|
|
143
|
+
console.log(`📦 Loaded user resolvers: ${specifiers.join(', ')}`);
|
|
97
144
|
}
|
|
98
145
|
|
|
99
|
-
const resolvers = specifiers.map(loadSingleResolver);
|
|
100
146
|
return createResolverChain(resolvers);
|
|
101
147
|
}
|
|
102
148
|
|
|
@@ -120,7 +166,9 @@ program
|
|
|
120
166
|
'Input parameters',
|
|
121
167
|
(val, acc = {}) => {
|
|
122
168
|
const [k, v] = val.split('=');
|
|
123
|
-
|
|
169
|
+
// try to parse numbers, preserve strings otherwise
|
|
170
|
+
const parsed = v === undefined ? '' : (v === 'true' ? true : (v === 'false' ? false : (isNaN(v) ? v : parseFloat(v))));
|
|
171
|
+
acc[k] = parsed;
|
|
124
172
|
return acc;
|
|
125
173
|
},
|
|
126
174
|
{}
|
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -22,6 +22,62 @@ function parse(code, fileName = null) {
|
|
|
22
22
|
while (i < lines.length) {
|
|
23
23
|
let line = lines[i];
|
|
24
24
|
|
|
25
|
+
// ============================
|
|
26
|
+
// NEW: Detect math operations
|
|
27
|
+
// ============================
|
|
28
|
+
|
|
29
|
+
// Add X and Y
|
|
30
|
+
let mathAdd = line.match(/^Add\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
31
|
+
if (mathAdd) {
|
|
32
|
+
workflow.steps.push({
|
|
33
|
+
type: 'calculate',
|
|
34
|
+
expression: `add({${mathAdd[1]}}, {${mathAdd[2]}})`,
|
|
35
|
+
saveAs: mathAdd[3].trim()
|
|
36
|
+
});
|
|
37
|
+
i++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Subtract A from B => B - A
|
|
42
|
+
let mathSub = line.match(/^Subtract\s+\{(.+?)\}\s+from\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
43
|
+
if (mathSub) {
|
|
44
|
+
workflow.steps.push({
|
|
45
|
+
type: 'calculate',
|
|
46
|
+
expression: `subtract({${mathSub[2]}}, {${mathSub[1]}})`,
|
|
47
|
+
saveAs: mathSub[3].trim()
|
|
48
|
+
});
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Multiply X and Y
|
|
54
|
+
let mathMul = line.match(/^Multiply\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
55
|
+
if (mathMul) {
|
|
56
|
+
workflow.steps.push({
|
|
57
|
+
type: 'calculate',
|
|
58
|
+
expression: `multiply({${mathMul[1]}}, {${mathMul[2]}})`,
|
|
59
|
+
saveAs: mathMul[3].trim()
|
|
60
|
+
});
|
|
61
|
+
i++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Divide A by B
|
|
66
|
+
let mathDiv = line.match(/^Divide\s+\{(.+?)\}\s+by\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
67
|
+
if (mathDiv) {
|
|
68
|
+
workflow.steps.push({
|
|
69
|
+
type: 'calculate',
|
|
70
|
+
expression: `divide({${mathDiv[1]}}, {${mathDiv[2]}})`,
|
|
71
|
+
saveAs: mathDiv[3].trim()
|
|
72
|
+
});
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ====================== END NEW MATH RULES ======================
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
25
81
|
// Workflow
|
|
26
82
|
const wfMatch = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?/i);
|
|
27
83
|
if (wfMatch) {
|
package/src/runtime.js
CHANGED
|
@@ -24,9 +24,11 @@ class RuntimeAPI {
|
|
|
24
24
|
evaluateCondition(cond, ctx) {
|
|
25
25
|
cond = cond.trim();
|
|
26
26
|
const eq = cond.match(/^\{(.+)\}\s+equals\s+"(.*)"$/);
|
|
27
|
-
if (eq) return this.getNested(ctx, eq[1])
|
|
27
|
+
if (eq) return this.getNested(ctx, eq[1]) == eq[2];
|
|
28
28
|
const gt = cond.match(/^\{(.+)\}\s+greater than\s+(\d+\.?\d*)$/);
|
|
29
29
|
if (gt) return parseFloat(this.getNested(ctx, gt[1])) > parseFloat(gt[2]);
|
|
30
|
+
const lt = cond.match(/^\{(.+)\}\s+less than\s+(\d+\.?\d*)$/);
|
|
31
|
+
if (lt) return parseFloat(this.getNested(ctx, lt[1])) < parseFloat(lt[2]);
|
|
30
32
|
return Boolean(this.getNested(ctx, cond.replace(/\{|\}/g, '')));
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -45,27 +47,111 @@ class RuntimeAPI {
|
|
|
45
47
|
return null;
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
// --------------------------
|
|
51
|
+
// Math helper functions
|
|
52
|
+
// --------------------------
|
|
53
|
+
mathFunctions = {
|
|
54
|
+
add: (a, b) => a + b,
|
|
55
|
+
subtract: (a, b) => a - b,
|
|
56
|
+
multiply: (a, b) => a * b,
|
|
57
|
+
divide: (a, b) => a / b,
|
|
58
|
+
equals: (a, b) => a === b,
|
|
59
|
+
greater: (a, b) => a > b,
|
|
60
|
+
less: (a, b) => a < b,
|
|
61
|
+
sum: arr => arr.reduce((acc, val) => acc + val, 0),
|
|
62
|
+
avg: arr => arr.reduce((acc, val) => acc + val, 0) / arr.length,
|
|
63
|
+
min: arr => Math.min(...arr),
|
|
64
|
+
max: arr => Math.max(...arr),
|
|
65
|
+
increment: a => a + 1,
|
|
66
|
+
decrement: a => a - 1,
|
|
67
|
+
round: a => Math.round(a),
|
|
68
|
+
floor: a => Math.floor(a),
|
|
69
|
+
ceil: a => Math.ceil(a),
|
|
70
|
+
abs: a => Math.abs(a)
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
evaluateMath(expr) {
|
|
74
|
+
// Replace context variables in curly braces
|
|
75
|
+
expr = expr.replace(/\{([^\}]+)\}/g, (_, path) => {
|
|
76
|
+
const value = this.getNested(this.context, path.trim());
|
|
77
|
+
// if value is string, wrap in quotes for functions that accept strings
|
|
78
|
+
if (typeof value === 'string') return `"${value.replace(/"/g, '\\"')}"`;
|
|
79
|
+
return value !== undefined ? value : 0;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// expose safe functions only
|
|
83
|
+
const funcNames = Object.keys(this.mathFunctions);
|
|
84
|
+
const safeFunc = {};
|
|
85
|
+
funcNames.forEach(fn => {
|
|
86
|
+
safeFunc[fn] = this.mathFunctions[fn];
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// eslint-disable-next-line no-new-func
|
|
91
|
+
const f = new Function(...funcNames, `return ${expr};`);
|
|
92
|
+
return f(...funcNames.map(fn => safeFunc[fn]));
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.warn(`[O-Lang] Failed to evaluate math expression "${expr}": ${e.message}`);
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// --------------------------
|
|
100
|
+
// Execute workflow step
|
|
101
|
+
// --------------------------
|
|
48
102
|
async executeStep(step, agentResolver) {
|
|
49
103
|
switch (step.type) {
|
|
104
|
+
case 'calculate': {
|
|
105
|
+
// step.expression or actionRaw can contain math expression strings
|
|
106
|
+
const expr = step.expression || step.actionRaw;
|
|
107
|
+
const result = this.evaluateMath(expr);
|
|
108
|
+
if (step.saveAs) this.context[step.saveAs] = result;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
|
|
50
112
|
case 'action': {
|
|
51
113
|
const action = step.actionRaw.replace(/\{([^\}]+)\}/g, (_, path) => {
|
|
52
114
|
const value = this.getNested(this.context, path.trim());
|
|
53
115
|
return value !== undefined ? String(value) : `{${path}}`;
|
|
54
116
|
});
|
|
117
|
+
|
|
118
|
+
// Provide fallback math recognition for action lines too (e.g., add(1,2))
|
|
119
|
+
// Try simple math function calls in action form
|
|
120
|
+
const mathCall = action.match(/^(add|subtract|multiply|divide|sum|avg|min|max|round|floor|ceil|abs)\((.*)\)$/i);
|
|
121
|
+
if (mathCall) {
|
|
122
|
+
const fn = mathCall[1].toLowerCase();
|
|
123
|
+
const argsRaw = mathCall[2];
|
|
124
|
+
// simple split by comma (doesn't handle nested arrays/funcs) — fine for workflow math
|
|
125
|
+
const args = argsRaw.split(',').map(s => s.trim()).map(s => {
|
|
126
|
+
// if it's a quoted string
|
|
127
|
+
if (/^".*"$/.test(s) || /^'.*'$/.test(s)) return s.slice(1, -1);
|
|
128
|
+
// try number
|
|
129
|
+
if (!isNaN(s)) return parseFloat(s);
|
|
130
|
+
// variable lookup
|
|
131
|
+
const lookup = s.replace(/^\{|\}$/g, '').trim();
|
|
132
|
+
return this.getNested(this.context, lookup);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (this.mathFunctions[fn]) {
|
|
136
|
+
const value = this.mathFunctions[fn](...args);
|
|
137
|
+
if (step.saveAs) this.context[step.saveAs] = value;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// fallback to agent resolver
|
|
55
143
|
const res = await agentResolver(action, this.context);
|
|
56
144
|
if (step.saveAs) this.context[step.saveAs] = res;
|
|
57
145
|
break;
|
|
58
146
|
}
|
|
59
147
|
|
|
60
148
|
case 'use': {
|
|
61
|
-
// "Use <Tool>" step
|
|
62
149
|
const res = await agentResolver(`Use ${step.tool}`, this.context);
|
|
63
150
|
if (step.saveAs) this.context[step.saveAs] = res;
|
|
64
151
|
break;
|
|
65
152
|
}
|
|
66
153
|
|
|
67
154
|
case 'ask': {
|
|
68
|
-
// "Ask <Target>" step
|
|
69
155
|
const res = await agentResolver(`Ask ${step.target}`, this.context);
|
|
70
156
|
if (step.saveAs) this.context[step.saveAs] = res;
|
|
71
157
|
break;
|