@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  const { Command } = require('commander');
3
- const { parse } = require('./src/parser');
4
- const { execute } = require('./src/runtime');
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
- return defaultMockResolver;
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
- acc[k] = v;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "author": "Olalekan Ogundipe <info@workfily.com>",
5
5
  "description": "O-Lang: A governance language for user-directed, rule-enforced agent workflows",
6
6
  "main": "./src/index.js",
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]) === eq[2];
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;