@o-lang/olang 1.0.15 → 1.0.16

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/parser.js +72 -93
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
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
@@ -1,12 +1,9 @@
1
1
  // src/parser.js
2
-
3
2
  function parse(code, fileName = null) {
4
3
  if (fileName && !fileName.endsWith(".ol")) {
5
4
  throw new Error(`Expected .ol workflow, got: ${fileName}`);
6
5
  }
7
-
8
6
  const rawLines = code.split(/\r?\n/);
9
-
10
7
  const lines = rawLines
11
8
  .map(l => l.trim())
12
9
  .filter(l => l && !l.startsWith('#') && !l.startsWith('//'));
@@ -17,37 +14,41 @@ function parse(code, fileName = null) {
17
14
  steps: [],
18
15
  returnValues: [],
19
16
  allowedResolvers: [],
20
-
21
17
  resolverPolicy: {
22
18
  declared: [],
23
19
  autoInjected: [],
24
20
  used: [],
25
21
  warnings: []
26
22
  },
27
-
28
23
  __warnings: [],
29
24
  __requiresMath: false
30
25
  };
31
26
 
32
27
  let i = 0;
33
-
34
28
  while (i < lines.length) {
35
29
  let line = lines[i];
36
30
 
37
- const allowMatch = line.match(/^Allow resolvers\s*:\s*$/i);
31
+ // FIXED: Match both "Allow resolvers" and "Allowed resolvers"
32
+ const allowMatch = line.match(/^Allow(?:ed)?\s+resolvers\s*:\s*$/i);
38
33
  if (allowMatch) {
39
34
  i++;
40
- while (i < lines.length && !/^[A-Za-z]/.test(lines[i])) {
41
- const val = lines[i].trim();
42
- if (val) {
43
- workflow.allowedResolvers.push(val);
44
- workflow.resolverPolicy.declared.push(val);
35
+ // Read all subsequent non-empty lines until a new top-level section (starts with capital letter)
36
+ while (i < lines.length) {
37
+ const nextLine = lines[i].trim();
38
+ // Stop if line is empty or appears to be a new top-level keyword (e.g., Step, Workflow, Return)
39
+ if (nextLine === '' || /^[A-Z][a-z]/.test(nextLine)) {
40
+ break;
41
+ }
42
+ if (nextLine) {
43
+ workflow.allowedResolvers.push(nextLine);
44
+ workflow.resolverPolicy.declared.push(nextLine);
45
45
  }
46
46
  i++;
47
47
  }
48
48
  continue;
49
49
  }
50
50
 
51
+ // ---- Math step patterns (standalone) ----
51
52
  let mathAdd = line.match(/^Add\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
52
53
  if (mathAdd) {
53
54
  workflow.__requiresMath = true;
@@ -65,7 +66,7 @@ function parse(code, fileName = null) {
65
66
  workflow.__requiresMath = true;
66
67
  workflow.steps.push({
67
68
  type: 'calculate',
68
- expression: `subtract({${mathSub[2]}}, {${mathSub[1]}})`,
69
+ expression: `subtract({${mathAdd[2]}}, {${mathAdd[1]}})`,
69
70
  saveAs: mathSub[3].trim()
70
71
  });
71
72
  i++;
@@ -96,6 +97,7 @@ function parse(code, fileName = null) {
96
97
  continue;
97
98
  }
98
99
 
100
+ // ---- Workflow declaration ----
99
101
  const wfMatch = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?/i);
100
102
  if (wfMatch) {
101
103
  workflow.name = wfMatch[1];
@@ -106,96 +108,84 @@ function parse(code, fileName = null) {
106
108
  continue;
107
109
  }
108
110
 
109
- // ---------------------------
110
- // Return statement (updated: auto-detect math)
111
- // ---------------------------
112
- const returnMatch = line.match(/^Return\s+(.+)$/i);
113
- if (returnMatch) {
114
- const returns = returnMatch[1].split(',').map(v => v.trim());
115
- workflow.returnValues = returns;
116
-
117
- // --- Check if any return vars come from math steps ---
118
- for (const retVar of returns) {
119
- const producedByMath = workflow.steps.some(
120
- s => s.saveAs === retVar && s.type === 'calculate'
121
- );
122
- if (producedByMath) workflow.__requiresMath = true;
123
- }
124
-
125
- i++;
126
- continue;
127
- }
128
-
129
-
130
- // ---------------------------
131
- // Steps (updated: auto-detect math + saveAs)
132
- // ---------------------------
133
- const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
134
- if (stepMatch) {
135
- const stepNum = parseInt(stepMatch[1], 10);
136
- const raw = stepMatch[2].trim();
137
-
138
- // --- Detect math inside Step ---
139
- let mathDetected = null;
140
- let expr = '';
141
- let saveVar = null;
142
-
143
- const mathOps = [
144
- { re: /^Add\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'add' },
145
- { re: /^Subtract\s+\{(.+?)\}\s+from\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'subtract' },
146
- { re: /^Multiply\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'multiply' },
147
- { re: /^Divide\s+\{(.+?)\}\s+by\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'divide' }
148
- ];
149
-
150
- for (const op of mathOps) {
151
- const m = raw.match(op.re);
152
- if (m) {
153
- mathDetected = op.fn;
154
- saveVar = m[3].trim();
155
- if (op.fn === 'subtract') expr = `subtract({${m[2]}}, {${m[1]}})`;
156
- else expr = `${op.fn}({${m[1]}}, {${m[2]}})`;
157
- break;
111
+ // ---- Return statement (updated: auto-detect math) ----
112
+ const returnMatch = line.match(/^Return\s+(.+)$/i);
113
+ if (returnMatch) {
114
+ const returns = returnMatch[1].split(',').map(v => v.trim());
115
+ workflow.returnValues = returns;
116
+ // Check if any return vars come from math steps
117
+ for (const retVar of returns) {
118
+ const producedByMath = workflow.steps.some(
119
+ s => s.saveAs === retVar && s.type === 'calculate'
120
+ );
121
+ if (producedByMath) workflow.__requiresMath = true;
122
+ }
123
+ i++;
124
+ continue;
158
125
  }
159
- }
160
-
161
- if (mathDetected) workflow.__requiresMath = true;
162
-
163
- workflow.steps.push({
164
- type: mathDetected ? 'calculate' : 'action',
165
- stepNumber: stepNum,
166
- actionRaw: mathDetected ? null : raw,
167
- expression: mathDetected ? expr : undefined,
168
- saveAs: saveVar,
169
- constraints: {}
170
- });
171
126
 
172
- i++;
173
- continue;
174
- }
127
+ // ---- Step parsing (with inline math detection) ----
128
+ const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
129
+ if (stepMatch) {
130
+ const stepNum = parseInt(stepMatch[1], 10);
131
+ const raw = stepMatch[2].trim();
132
+
133
+ let mathDetected = null;
134
+ let expr = '';
135
+ let saveVar = null;
136
+ const mathOps = [
137
+ { re: /^Add\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'add' },
138
+ { re: /^Subtract\s+\{(.+?)\}\s+from\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'subtract' },
139
+ { re: /^Multiply\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'multiply' },
140
+ { re: /^Divide\s+\{(.+?)\}\s+by\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'divide' }
141
+ ];
142
+ for (const op of mathOps) {
143
+ const m = raw.match(op.re);
144
+ if (m) {
145
+ mathDetected = op.fn;
146
+ saveVar = m[3].trim();
147
+ if (op.fn === 'subtract') {
148
+ expr = `subtract({${m[2]}}, {${m[1]}})`;
149
+ } else {
150
+ expr = `${op.fn}({${m[1]}}, {${m[2]}})`;
151
+ }
152
+ break;
153
+ }
154
+ }
155
+ if (mathDetected) workflow.__requiresMath = true;
156
+ workflow.steps.push({
157
+ type: mathDetected ? 'calculate' : 'action',
158
+ stepNumber: stepNum,
159
+ actionRaw: mathDetected ? null : raw,
160
+ expression: mathDetected ? expr : undefined,
161
+ saveAs: saveVar,
162
+ constraints: {}
163
+ });
164
+ i++;
165
+ continue;
166
+ }
175
167
 
168
+ // ---- Save as (for legacy or multi-line steps) ----
176
169
  const saveMatch = line.match(/^Save as\s+(.+)$/i);
177
170
  if (saveMatch && workflow.steps.length > 0) {
178
171
  const lastStep = workflow.steps[workflow.steps.length - 1];
179
172
  lastStep.saveAs = saveMatch[1].trim();
180
-
181
173
  if (lastStep.saveAs.match(/[A-Z][A-Za-z0-9_]*/)) {
182
174
  workflow.__requiresMath = true;
183
175
  }
184
-
185
176
  i++;
186
177
  continue;
187
178
  }
188
179
 
180
+ // ---- Constraint parsing ----
189
181
  const constraintMatch = line.match(/^Constraint:\s*(.+)$/i);
190
182
  if (constraintMatch && workflow.steps.length > 0) {
191
183
  const lastStep = workflow.steps[workflow.steps.length - 1];
192
184
  if (!lastStep.constraints) lastStep.constraints = {};
193
-
194
185
  const eq = constraintMatch[1].match(/^([^=]+)=\s*(.+)$/);
195
186
  if (eq) {
196
187
  let key = eq[1].trim();
197
188
  let value = eq[2].trim();
198
-
199
189
  if (value.startsWith('[') && value.endsWith(']')) {
200
190
  value = value.slice(1, -1).split(',').map(v =>
201
191
  v.trim().replace(/^"/, '').replace(/"$/, '')
@@ -205,7 +195,6 @@ if (stepMatch) {
205
195
  } else if (value.startsWith('"') && value.endsWith('"')) {
206
196
  value = value.slice(1, -1);
207
197
  }
208
-
209
198
  lastStep.constraints[key] = value;
210
199
  }
211
200
  i++;
@@ -220,7 +209,6 @@ if (stepMatch) {
220
209
  // ============================
221
210
  if (workflow.__requiresMath) {
222
211
  workflow.resolverPolicy.used.push('builtInMathResolver');
223
-
224
212
  if (!workflow.resolverPolicy.declared.includes('builtInMathResolver')) {
225
213
  workflow.resolverPolicy.autoInjected.push('builtInMathResolver');
226
214
  workflow.allowedResolvers.unshift('builtInMathResolver');
@@ -237,7 +225,6 @@ if (stepMatch) {
237
225
  }
238
226
 
239
227
  workflow.resolverPolicy.warnings = workflow.__warnings.slice();
240
-
241
228
  return workflow;
242
229
  }
243
230
 
@@ -247,7 +234,6 @@ if (stepMatch) {
247
234
  function parseBlock(lines) {
248
235
  const steps = [];
249
236
  let current = null;
250
-
251
237
  for (const line of lines) {
252
238
  const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
253
239
  if (stepMatch) {
@@ -261,37 +247,30 @@ function parseBlock(lines) {
261
247
  steps.push(current);
262
248
  continue;
263
249
  }
264
-
265
250
  const saveMatch = line.match(/^Save as\s+(.+)$/i);
266
251
  if (saveMatch && current) current.saveAs = saveMatch[1].trim();
267
-
268
252
  const debriefMatch = line.match(/^Debrief\s+(\w+)\s+with\s+"(.+)"$/i);
269
253
  if (debriefMatch) {
270
254
  steps.push({ type: 'debrief', agent: debriefMatch[1], message: debriefMatch[2] });
271
255
  }
272
-
273
256
  const evolveMatch = line.match(/^Evolve\s+(\w+)\s+using\s+feedback:\s+"(.+)"$/i);
274
257
  if (evolveMatch) {
275
258
  steps.push({ type: 'evolve', agent: evolveMatch[1], feedback: evolveMatch[2] });
276
259
  }
277
-
278
260
  const promptMatch = line.match(/^Prompt user to\s+"(.+)"$/i);
279
261
  if (promptMatch) {
280
262
  steps.push({ type: 'prompt', question: promptMatch[1], saveAs: null });
281
263
  }
282
-
283
264
  const useMatch = line.match(/^Use\s+(.+)$/i);
284
265
  if (useMatch) {
285
266
  steps.push({ type: 'use', tool: useMatch[1].trim(), saveAs: null, constraints: {} });
286
267
  }
287
-
288
268
  const askMatch = line.match(/^Ask\s+(.+)$/i);
289
269
  if (askMatch) {
290
270
  steps.push({ type: 'ask', target: askMatch[1].trim(), saveAs: null, constraints: {} });
291
271
  }
292
272
  }
293
-
294
273
  return steps;
295
274
  }
296
275
 
297
- module.exports = { parse };
276
+ module.exports = { parse };