@o-lang/olang 1.0.4 → 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",
@@ -47,31 +49,41 @@ async function defaultMockResolver(action, context) {
47
49
  }
48
50
 
49
51
  /**
50
- * Built-in Math Resolver
51
- * Supports minimal math operations for workflows
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.
52
55
  */
53
56
  async function builtInMathResolver(action, context) {
54
- // Replace variables in context
55
- action = action.replace(/\{([^\}]+)\}/g, (_, key) => {
56
- const val = context[key.trim()];
57
- return val !== undefined ? val : `{${key}}`;
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}}`;
58
63
  });
59
64
 
60
- let match;
65
+ // simple function matches
66
+ let m;
67
+ m = a.match(/^add\(([^,]+),\s*([^)]+)\)$/i);
68
+ if (m) return parseFloat(m[1]) + parseFloat(m[2]);
61
69
 
62
- match = action.match(/^add\(([^,]+),\s*([^)]+)\)$/);
63
- if (match) return parseFloat(match[1]) + parseFloat(match[2]);
70
+ m = a.match(/^subtract\(([^,]+),\s*([^)]+)\)$/i);
71
+ if (m) return parseFloat(m[1]) - parseFloat(m[2]);
64
72
 
65
- match = action.match(/^subtract\(([^,]+),\s*([^)]+)\)$/);
66
- if (match) return parseFloat(match[1]) - parseFloat(match[2]);
73
+ m = a.match(/^multiply\(([^,]+),\s*([^)]+)\)$/i);
74
+ if (m) return parseFloat(m[1]) * parseFloat(m[2]);
67
75
 
68
- match = action.match(/^multiply\(([^,]+),\s*([^)]+)\)$/);
69
- if (match) return parseFloat(match[1]) * parseFloat(match[2]);
76
+ m = a.match(/^divide\(([^,]+),\s*([^)]+)\)$/i);
77
+ if (m) return parseFloat(m[1]) / parseFloat(m[2]);
70
78
 
71
- match = action.match(/^divide\(([^,]+),\s*([^)]+)\)$/);
72
- if (match) return parseFloat(match[1]) / parseFloat(match[2]);
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
+ }
73
84
 
74
- return null; // not handled
85
+ // not a math action
86
+ return null;
75
87
  }
76
88
 
77
89
  /**
@@ -119,8 +131,7 @@ function loadSingleResolver(specifier) {
119
131
  }
120
132
 
121
133
  /**
122
- * Updated resolver chain
123
- * Built-in math resolver is added first, then user resolvers, then default mock
134
+ * loadResolverChain: include built-in math resolver first, then user resolvers, then default mock resolver
124
135
  */
125
136
  function loadResolverChain(specifiers) {
126
137
  const userResolvers = specifiers?.map(loadSingleResolver) || [];
@@ -155,7 +166,9 @@ program
155
166
  'Input parameters',
156
167
  (val, acc = {}) => {
157
168
  const [k, v] = val.split('=');
158
- acc[k] = isNaN(v) ? v : parseFloat(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;
159
172
  return acc;
160
173
  },
161
174
  {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.0.4",
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
@@ -74,10 +74,12 @@ class RuntimeAPI {
74
74
  // Replace context variables in curly braces
75
75
  expr = expr.replace(/\{([^\}]+)\}/g, (_, path) => {
76
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, '\\"')}"`;
77
79
  return value !== undefined ? value : 0;
78
80
  });
79
81
 
80
- // Create a function for supported math functions only
82
+ // expose safe functions only
81
83
  const funcNames = Object.keys(this.mathFunctions);
82
84
  const safeFunc = {};
83
85
  funcNames.forEach(fn => {
@@ -100,7 +102,7 @@ class RuntimeAPI {
100
102
  async executeStep(step, agentResolver) {
101
103
  switch (step.type) {
102
104
  case 'calculate': {
103
- // e.g., "add({x}, {y})"
105
+ // step.expression or actionRaw can contain math expression strings
104
106
  const expr = step.expression || step.actionRaw;
105
107
  const result = this.evaluateMath(expr);
106
108
  if (step.saveAs) this.context[step.saveAs] = result;
@@ -112,6 +114,32 @@ class RuntimeAPI {
112
114
  const value = this.getNested(this.context, path.trim());
113
115
  return value !== undefined ? String(value) : `{${path}}`;
114
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
115
143
  const res = await agentResolver(action, this.context);
116
144
  if (step.saveAs) this.context[step.saveAs] = res;
117
145
  break;