@shapeshift-labs/frontier-lang-parser 0.3.46 → 0.3.48

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.
@@ -52,6 +52,8 @@ function parseActionIfBlock(header, body, index, state) {
52
52
  kind: 'if',
53
53
  id: details.id,
54
54
  name: details.name,
55
+ comparisonType: details.comparisonType,
56
+ callType: details.callType,
55
57
  condition: details.condition,
56
58
  body: parseActionBodyRecords(body, state)
57
59
  });
@@ -63,10 +65,14 @@ function parseActionIfHeader(header, index) {
63
65
  const nameText = explicitCondition ? withoutId.slice(0, explicitCondition.index).trim() : '';
64
66
  const name = firstIdentifier(nameText) ?? `if_${index}`;
65
67
  const conditionText = explicitCondition?.[1]?.trim() || withoutId;
68
+ const comparisonType = readInlineComparisonType(header);
69
+ const callType = readInlineCallType(header);
66
70
  return {
67
71
  id: idFrom(header, `action_body_if_${name}`),
68
72
  name,
69
- condition: conditionText ? readActionValue(conditionText) : undefined
73
+ comparisonType,
74
+ callType,
75
+ condition: conditionText ? readActionValue(conditionText, { comparisonType, callType }) : undefined
70
76
  };
71
77
  }
72
78
 
@@ -79,9 +85,11 @@ function parseActionBodyLine(line, index) {
79
85
  if (rowKind === 'set' || rowKind === 'insert' || rowKind === 'merge') {
80
86
  const path = readInlineWord('path', rest);
81
87
  const valueType = readInlineType(rest);
82
- const value = readInlineActionValue('value', rest, { valueType });
88
+ const comparisonType = readInlineComparisonType(rest);
89
+ const callType = readInlineCallType(rest);
90
+ const value = readInlineActionValue('value', rest, { valueType, comparisonType, callType });
83
91
  if (!path || !value) return undefined;
84
- return compactRecord({ kind: 'patch', op: rowKind, id: idFrom(rest, `action_body_${rowKind}_${name}`), name, path, valueType, value });
92
+ return compactRecord({ kind: 'patch', op: rowKind, id: idFrom(rest, `action_body_${rowKind}_${name}`), name, path, valueType, comparisonType, callType, value });
85
93
  }
86
94
  if (rowKind === 'remove') {
87
95
  const path = readInlineWord('path', rest);
@@ -96,9 +104,11 @@ function parseActionBodyLine(line, index) {
96
104
  }
97
105
  if (rowKind === 'let') {
98
106
  const valueType = readInlineType(rest);
99
- const value = readInlineActionBindingValue('value', rest, { valueType });
107
+ const comparisonType = readInlineComparisonType(rest);
108
+ const callType = readInlineCallType(rest);
109
+ const value = readInlineActionBindingValue('value', rest, { valueType, comparisonType, callType });
100
110
  if (!rawName || rawName.startsWith('@') || !isActionBindingName(name) || !value) return undefined;
101
- return compactRecord({ kind: 'let', id: idFrom(rest, `action_body_let_${name}`), name, valueType, value });
111
+ return compactRecord({ kind: 'let', id: idFrom(rest, `action_body_let_${name}`), name, valueType, comparisonType, callType, value });
102
112
  }
103
113
  if (rowKind === 'return') {
104
114
  const valueText = stripIds(rawName?.startsWith('@') ? rest : `${rawName ?? ''}${rest ?? ''}`).trim();
@@ -189,6 +199,8 @@ function isActionBindingName(value) {
189
199
 
190
200
  function idFrom(header, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1] ?? fallback; }
191
201
  function readInlineType(text) { return readInlineWord('type', text) ?? readInlineWord('valueType', text); }
202
+ function readInlineComparisonType(text) { return readInlineWord('compare', text) ?? readInlineWord('comparisonType', text) ?? readInlineWord('compareType', text); }
203
+ function readInlineCallType(text) { return readInlineWord('call', text) ?? readInlineWord('callType', text); }
192
204
  function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
193
205
  function readInlineValue(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+(.+?)(?=\\s+[A-Za-z_$][\\w$-]*\\s+|$)').exec(text)?.[1]?.trim(); }
194
206
  function stripIds(text) { return String(text ?? '').replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim(); }
@@ -0,0 +1,44 @@
1
+ export const NUMERIC_OPERATORS = new Set(['+', '-', '*', '/', '%']);
2
+
3
+ const ORDERED_COMPARISON_OPERATORS = new Set(['>', '>=', '<', '<=']);
4
+ const NUMERIC_TYPES = new Set(['number', 'numeric', 'int', 'integer', 'float', 'double', 'decimal']);
5
+ const CALL_TYPES = new Set(['text', 'string', 'bool', 'boolean', 'number', 'numeric', 'int', 'integer', 'float', 'double', 'decimal', 'json']);
6
+
7
+ export function hasNumericOperator(node) {
8
+ if (!node || typeof node !== 'object') return false;
9
+ if (node.kind === 'binary') return NUMERIC_OPERATORS.has(node.op) || hasNumericOperator(node.left) || hasNumericOperator(node.right);
10
+ if (node.kind === 'logical') return hasNumericOperator(node.left) || hasNumericOperator(node.right);
11
+ if (node.kind === 'unary') return hasNumericOperator(node.argument);
12
+ return false;
13
+ }
14
+
15
+ export function hasNonLiteralOrderedComparison(node) {
16
+ if (!node || typeof node !== 'object') return false;
17
+ if (node.kind === 'binary') {
18
+ const current = ORDERED_COMPARISON_OPERATORS.has(node.op) && !isLiteralNumericComparison(node);
19
+ return current || hasNonLiteralOrderedComparison(node.left) || hasNonLiteralOrderedComparison(node.right);
20
+ }
21
+ if (node.kind === 'logical') return hasNonLiteralOrderedComparison(node.left) || hasNonLiteralOrderedComparison(node.right);
22
+ if (node.kind === 'unary') return hasNonLiteralOrderedComparison(node.argument);
23
+ return false;
24
+ }
25
+
26
+ export function hasCallExpression(node) {
27
+ if (!node || typeof node !== 'object') return false;
28
+ if (node.kind === 'call') return true;
29
+ if (node.kind === 'binary' || node.kind === 'logical') return hasCallExpression(node.left) || hasCallExpression(node.right);
30
+ if (node.kind === 'unary') return hasCallExpression(node.argument);
31
+ return false;
32
+ }
33
+
34
+ export function isNumericType(value) {
35
+ return NUMERIC_TYPES.has(String(value ?? '').trim().toLowerCase());
36
+ }
37
+
38
+ export function isSupportedCallType(value) {
39
+ return CALL_TYPES.has(String(value ?? '').trim().toLowerCase());
40
+ }
41
+
42
+ function isLiteralNumericComparison(node) {
43
+ return node.left?.kind === 'literal' && typeof node.left.value === 'number' && node.right?.kind === 'literal' && typeof node.right.value === 'number';
44
+ }
@@ -0,0 +1,92 @@
1
+ import { NUMERIC_OPERATORS } from './action-expression-semantics.js';
2
+
3
+ export function tokenizeActionExpression(text) {
4
+ const tokens = [];
5
+ let index = 0;
6
+ while (index < text.length) {
7
+ const char = text[index];
8
+ if (/\s/.test(char)) {
9
+ index++;
10
+ continue;
11
+ }
12
+ if (char === '"' || char === "'") {
13
+ const read = readStringToken(text, index);
14
+ if (!read.ok) return read;
15
+ tokens.push(read.token);
16
+ index = read.next;
17
+ continue;
18
+ }
19
+ const canReadSignedNumber = char !== '-' || canStartSignedNumber(tokens);
20
+ const number = canReadSignedNumber ? /^-?\d+(?:\.\d+)?/.exec(text.slice(index)) : undefined;
21
+ if (number) {
22
+ tokens.push({ type: 'number', value: Number(number[0]), text: number[0], start: index, end: index + number[0].length });
23
+ index += number[0].length;
24
+ continue;
25
+ }
26
+ const identifier = /^[A-Za-z_$][\w$-]*/.exec(text.slice(index));
27
+ if (identifier) {
28
+ tokens.push({ type: 'identifier', text: identifier[0], start: index, end: index + identifier[0].length });
29
+ index += identifier[0].length;
30
+ continue;
31
+ }
32
+ const triple = text.slice(index, index + 3);
33
+ if (triple === '===' || triple === '!==') return fail('unsupported-action-expression-operator');
34
+ const double = text.slice(index, index + 2);
35
+ if (double === '&&' || double === '||' || double === '==' || double === '!=' || double === '>=' || double === '<=') {
36
+ tokens.push({ type: 'operator', text: double, start: index, end: index + 2 });
37
+ index += 2;
38
+ continue;
39
+ }
40
+ if (char === '!' || char === '>' || char === '<' || NUMERIC_OPERATORS.has(char)) {
41
+ tokens.push({ type: 'operator', text: char, start: index, end: index + 1 });
42
+ index++;
43
+ continue;
44
+ }
45
+ if (char === '(' || char === ')' || char === '.' || char === ',') {
46
+ tokens.push({ type: 'punctuation', text: char, start: index, end: index + 1 });
47
+ index++;
48
+ continue;
49
+ }
50
+ if ('=&|?:[]{}'.includes(char)) return fail('unsupported-action-expression-operator');
51
+ return fail('malformed-action-expression');
52
+ }
53
+ tokens.push({ type: 'eof', text: '', start: text.length, end: text.length });
54
+ return { ok: true, tokens };
55
+ }
56
+
57
+ function readStringToken(text, start) {
58
+ const quote = text[start];
59
+ let value = '';
60
+ let index = start + 1;
61
+ while (index < text.length) {
62
+ const char = text[index];
63
+ if (char === quote) {
64
+ return { ok: true, token: { type: 'string', value, text: text.slice(start, index + 1), start, end: index + 1 }, next: index + 1 };
65
+ }
66
+ if (char === '\\') {
67
+ const next = text[index + 1];
68
+ if (next === undefined) return fail('malformed-action-expression');
69
+ if (next === 'n') value += '\n';
70
+ else if (next === 'r') value += '\r';
71
+ else if (next === 't') value += '\t';
72
+ else if (next === '\\' || next === '"' || next === "'") value += next;
73
+ else return fail('malformed-action-expression');
74
+ index += 2;
75
+ continue;
76
+ }
77
+ value += char;
78
+ index++;
79
+ }
80
+ return fail('malformed-action-expression');
81
+ }
82
+
83
+ function canStartSignedNumber(tokens) {
84
+ const previous = tokens[tokens.length - 1];
85
+ if (!previous) return true;
86
+ if (previous.type === 'operator') return true;
87
+ return previous.type === 'punctuation' && (previous.text === '(' || previous.text === ',');
88
+ }
89
+
90
+ function fail(reason) {
91
+ return { ok: false, reason };
92
+ }
@@ -1,7 +1,9 @@
1
+ import { hasCallExpression, hasNonLiteralOrderedComparison, hasNumericOperator, isNumericType, isSupportedCallType } from './action-expression-semantics.js';
2
+ import { tokenizeActionExpression } from './action-expression-tokenizer.js';
3
+
1
4
  const IDENTIFIER = /^[A-Za-z_$][\w$-]*$/;
2
5
  const BLOCKED_REF_ROOTS = new Set(['globalThis', 'process', 'window', 'document', 'constructor', 'prototype', '__proto__', 'env']);
3
- const NUMERIC_OPERATORS = new Set(['+', '-', '*', '/', '%']);
4
- const NUMERIC_TYPES = new Set(['number', 'numeric', 'int', 'integer', 'float', 'double', 'decimal']);
6
+ const BLOCKED_CALL_ROOTS = new Set([...BLOCKED_REF_ROOTS, 'input', 'state', 'patches']);
5
7
 
6
8
  export function readActionValue(value, options = {}) {
7
9
  const parsed = parseActionValue(value, options);
@@ -16,9 +18,18 @@ export function parseActionValue(value, options = {}) {
16
18
  if (hasNumericOperator(expression.ast) && !isNumericType(options.valueType ?? options.type)) {
17
19
  return fail((options.valueType ?? options.type) ? 'unsupported-action-expression-type' : 'missing-action-expression-type');
18
20
  }
21
+ if (hasNonLiteralOrderedComparison(expression.ast) && !isNumericType(options.comparisonType ?? options.compareType)) {
22
+ return fail((options.comparisonType ?? options.compareType) ? 'unsupported-action-comparison-type' : 'missing-action-comparison-type');
23
+ }
24
+ if (hasCallExpression(expression.ast) && !isSupportedCallType(options.callType ?? options.call ?? options.returns)) {
25
+ return fail((options.callType ?? options.call ?? options.returns) ? 'unsupported-action-call-type' : 'missing-action-call-type');
26
+ }
19
27
  const valueType = options.valueType ?? options.type;
20
- if (expression.literal) return { ok: true, value: compactRecord({ value: expression.ast.value, valueType }) };
21
- return { ok: true, value: compactRecord({ expression: text, expressionAst: expression.ast, valueType }) };
28
+ const comparisonType = options.comparisonType ?? options.compareType;
29
+ const callType = options.callType ?? options.call ?? options.returns;
30
+ const ast = callType ? attachCallType(expression.ast, callType) : expression.ast;
31
+ if (expression.literal) return { ok: true, value: compactRecord({ value: ast.value, valueType, comparisonType, callType }) };
32
+ return { ok: true, value: compactRecord({ expression: text, expressionAst: ast, valueType, comparisonType, callType }) };
22
33
  }
23
34
 
24
35
  export function parseActionExpression(value, options = {}) {
@@ -37,86 +48,6 @@ export function isSupportedActionValue(value, options = {}) {
37
48
  return parseActionValue(value, options).ok;
38
49
  }
39
50
 
40
- function tokenizeActionExpression(text) {
41
- const tokens = [];
42
- let index = 0;
43
- while (index < text.length) {
44
- const char = text[index];
45
- if (/\s/.test(char)) {
46
- index++;
47
- continue;
48
- }
49
- if (char === '"' || char === "'") {
50
- const read = readStringToken(text, index);
51
- if (!read.ok) return read;
52
- tokens.push(read.token);
53
- index = read.next;
54
- continue;
55
- }
56
- const canReadSignedNumber = char !== '-' || canStartSignedNumber(tokens);
57
- const number = canReadSignedNumber ? /^-?\d+(?:\.\d+)?/.exec(text.slice(index)) : undefined;
58
- if (number) {
59
- tokens.push({ type: 'number', value: Number(number[0]), text: number[0], start: index, end: index + number[0].length });
60
- index += number[0].length;
61
- continue;
62
- }
63
- const identifier = /^[A-Za-z_$][\w$-]*/.exec(text.slice(index));
64
- if (identifier) {
65
- tokens.push({ type: 'identifier', text: identifier[0], start: index, end: index + identifier[0].length });
66
- index += identifier[0].length;
67
- continue;
68
- }
69
- const triple = text.slice(index, index + 3);
70
- if (triple === '===' || triple === '!==') return fail('unsupported-action-expression-operator');
71
- const double = text.slice(index, index + 2);
72
- if (double === '&&' || double === '||' || double === '==' || double === '!=' || double === '>=' || double === '<=') {
73
- tokens.push({ type: 'operator', text: double, start: index, end: index + 2 });
74
- index += 2;
75
- continue;
76
- }
77
- if (char === '!' || char === '>' || char === '<' || NUMERIC_OPERATORS.has(char)) {
78
- tokens.push({ type: 'operator', text: char, start: index, end: index + 1 });
79
- index++;
80
- continue;
81
- }
82
- if (char === '(' || char === ')' || char === '.') {
83
- tokens.push({ type: 'punctuation', text: char, start: index, end: index + 1 });
84
- index++;
85
- continue;
86
- }
87
- if ('=&|?:[]{}'.includes(char)) return fail('unsupported-action-expression-operator');
88
- return fail('malformed-action-expression');
89
- }
90
- tokens.push({ type: 'eof', text: '', start: text.length, end: text.length });
91
- return { ok: true, tokens };
92
- }
93
-
94
- function readStringToken(text, start) {
95
- const quote = text[start];
96
- let value = '';
97
- let index = start + 1;
98
- while (index < text.length) {
99
- const char = text[index];
100
- if (char === quote) {
101
- return { ok: true, token: { type: 'string', value, text: text.slice(start, index + 1), start, end: index + 1 }, next: index + 1 };
102
- }
103
- if (char === '\\') {
104
- const next = text[index + 1];
105
- if (next === undefined) return fail('malformed-action-expression');
106
- if (next === 'n') value += '\n';
107
- else if (next === 'r') value += '\r';
108
- else if (next === 't') value += '\t';
109
- else if (next === '\\' || next === '"' || next === "'") value += next;
110
- else return fail('malformed-action-expression');
111
- index += 2;
112
- continue;
113
- }
114
- value += char;
115
- index++;
116
- }
117
- return fail('malformed-action-expression');
118
- }
119
-
120
51
  class ExpressionParser {
121
52
  constructor(tokens, options) {
122
53
  this.tokens = tokens;
@@ -216,6 +147,7 @@ class ExpressionParser {
216
147
  const first = this.consumeIdentifier();
217
148
  if (!first) return { kind: 'literal', value: null };
218
149
  parts.push(first);
150
+ if (this.peek().type === 'punctuation' && this.peek().text === '(') return this.parseCall(first);
219
151
  while (this.matchPunctuation('.')) {
220
152
  const part = this.consumeIdentifier();
221
153
  if (!part) {
@@ -225,7 +157,7 @@ class ExpressionParser {
225
157
  parts.push(part);
226
158
  }
227
159
  if (this.peek().type === 'punctuation' && this.peek().text === '(') {
228
- this.reject('unsupported-action-expression-ref');
160
+ this.reject('unsupported-action-call-callee');
229
161
  return refNode(parts);
230
162
  }
231
163
  if (BLOCKED_REF_ROOTS.has(parts[0])) {
@@ -235,6 +167,31 @@ class ExpressionParser {
235
167
  return refNode(parts);
236
168
  }
237
169
 
170
+ parseCall(callee) {
171
+ if (!IDENTIFIER.test(callee) || BLOCKED_CALL_ROOTS.has(callee)) {
172
+ this.reject('unsupported-action-call-callee');
173
+ return { kind: 'call', callee, args: [] };
174
+ }
175
+ this.matchPunctuation('(');
176
+ const args = [];
177
+ if (this.matchPunctuation(')')) return compactRecord({ kind: 'call', callee, args, callType: this.options.callType ?? this.options.call ?? this.options.returns });
178
+ while (this.ok) {
179
+ const argument = this.parseExpression();
180
+ if (hasCallExpression(argument)) this.reject('unsupported-action-call-argument');
181
+ args.push(argument);
182
+ if (this.matchPunctuation(')')) break;
183
+ if (!this.matchPunctuation(',')) {
184
+ this.reject('malformed-action-expression');
185
+ break;
186
+ }
187
+ if (this.peek().type === 'punctuation' && this.peek().text === ')') {
188
+ this.reject('malformed-action-expression');
189
+ break;
190
+ }
191
+ }
192
+ return compactRecord({ kind: 'call', callee, args, callType: this.options.callType ?? this.options.call ?? this.options.returns });
193
+ }
194
+
238
195
  consumeIdentifier() {
239
196
  const token = this.peek();
240
197
  if (token.type !== 'identifier' || !IDENTIFIER.test(token.text)) {
@@ -281,25 +238,6 @@ class ExpressionParser {
281
238
  }
282
239
  }
283
240
 
284
- function canStartSignedNumber(tokens) {
285
- const previous = tokens[tokens.length - 1];
286
- if (!previous) return true;
287
- if (previous.type === 'operator') return true;
288
- return previous.type === 'punctuation' && previous.text === '(';
289
- }
290
-
291
- function hasNumericOperator(node) {
292
- if (!node || typeof node !== 'object') return false;
293
- if (node.kind === 'binary') return NUMERIC_OPERATORS.has(node.op) || hasNumericOperator(node.left) || hasNumericOperator(node.right);
294
- if (node.kind === 'logical') return hasNumericOperator(node.left) || hasNumericOperator(node.right);
295
- if (node.kind === 'unary') return hasNumericOperator(node.argument);
296
- return false;
297
- }
298
-
299
- function isNumericType(value) {
300
- return NUMERIC_TYPES.has(String(value ?? '').trim().toLowerCase());
301
- }
302
-
303
241
  function refNode(parts) {
304
242
  const [root, ...rest] = parts;
305
243
  const scope = root === 'input' || root === 'state' || root === 'patches' ? root : 'local';
@@ -311,6 +249,14 @@ function fail(reason) {
311
249
  return { ok: false, reason };
312
250
  }
313
251
 
252
+ function attachCallType(node, callType) {
253
+ if (!node || typeof node !== 'object') return node;
254
+ if (node.kind === 'call') return compactRecord({ ...node, callType });
255
+ if (node.kind === 'binary' || node.kind === 'logical') return { ...node, left: attachCallType(node.left, callType), right: attachCallType(node.right, callType) };
256
+ if (node.kind === 'unary') return { ...node, argument: attachCallType(node.argument, callType) };
257
+ return node;
258
+ }
259
+
314
260
  function compactRecord(record) {
315
261
  return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0)));
316
262
  }
@@ -91,10 +91,10 @@ function actionRowName(rowKind, rawName, rowIndex) {
91
91
 
92
92
  function validateActionRow(rowKind, rawName, rest, header) {
93
93
  if (!ACTION_BODY_ROWS.has(rowKind)) return { ok: false, reason: 'unsupported-action-body-row' };
94
- if (rowKind === 'if') return validateActionExpressionText(readIfCondition(header));
94
+ if (rowKind === 'if') return validateActionExpressionText(readIfCondition(header), { comparisonType: readInlineComparisonType(header), callType: readInlineCallType(header) });
95
95
  if (rowKind === 'set' || rowKind === 'insert' || rowKind === 'merge') {
96
96
  if (!readInlineWord('path', rest)) return { ok: false, reason: 'missing-action-path' };
97
- return validateActionExpressionText(readInlineValue('value', rest), { valueType: readInlineType(rest) });
97
+ return validateActionExpressionText(readInlineValue('value', rest), { valueType: readInlineType(rest), comparisonType: readInlineComparisonType(rest), callType: readInlineCallType(rest) });
98
98
  }
99
99
  if (rowKind === 'remove') {
100
100
  return readInlineWord('path', rest) ? { ok: true } : { ok: false, reason: 'missing-action-path' };
@@ -112,9 +112,9 @@ function validateActionRow(rowKind, rawName, rest, header) {
112
112
  return { ok: false, reason: 'unsupported-action-binding-name' };
113
113
  }
114
114
  const value = readInlineValue('value', rest);
115
- const parsed = value ? parseActionValue(value, { valueType: readInlineType(rest) }) : undefined;
115
+ const parsed = value ? parseActionValue(value, { valueType: readInlineType(rest), comparisonType: readInlineComparisonType(rest), callType: readInlineCallType(rest) }) : undefined;
116
116
  if (parsed?.ok) return { ok: true };
117
- if (parsed?.reason === 'missing-action-expression-type' || parsed?.reason === 'unsupported-action-expression-type') {
117
+ if (parsed?.reason === 'missing-action-expression-type' || parsed?.reason === 'unsupported-action-expression-type' || parsed?.reason === 'missing-action-comparison-type' || parsed?.reason === 'unsupported-action-comparison-type' || parsed?.reason === 'missing-action-call-type' || parsed?.reason === 'unsupported-action-call-type' || parsed?.reason === 'unsupported-action-call-callee' || parsed?.reason === 'unsupported-action-call-argument') {
118
118
  return { ok: false, reason: parsed.reason };
119
119
  }
120
120
  return { ok: false, reason: 'unsupported-action-binding-value' };
@@ -134,6 +134,14 @@ function readInlineType(text) {
134
134
  return readInlineWord('type', text) ?? readInlineWord('valueType', text);
135
135
  }
136
136
 
137
+ function readInlineComparisonType(text) {
138
+ return readInlineWord('compare', text) ?? readInlineWord('comparisonType', text) ?? readInlineWord('compareType', text);
139
+ }
140
+
141
+ function readInlineCallType(text) {
142
+ return readInlineWord('call', text) ?? readInlineWord('callType', text);
143
+ }
144
+
137
145
  function readIfCondition(header) {
138
146
  const text = header.replace(/^if\b/, '').replace(/\{\s*$/, '').replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim();
139
147
  const explicit = /\bcondition\s+(.+)$/.exec(text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.46",
3
+ "version": "0.3.48",
4
4
  "description": "Parser for the first Frontier Lang .frontier syntax slice.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "scripts": {
24
24
  "build": "node scripts/build.mjs",
25
- "test": "npm run build && node test/smoke.mjs && node test/action-body-smoke.mjs && node test/target-projection-aggregate-smoke.mjs && node test/conversion-constraint-fields-smoke.mjs && node test/conversion-evidence-smoke.mjs && node test/package-canvas-surface-smoke.mjs && node test/view-render-graph-smoke.mjs && node test/resource-graph-smoke.mjs && node test/interlingua-smoke.mjs && node test/dialect-registry-smoke.mjs",
25
+ "test": "npm run build && node test/smoke.mjs && node test/action-body-smoke.mjs && node test/action-call-smoke.mjs && node test/target-projection-aggregate-smoke.mjs && node test/conversion-constraint-fields-smoke.mjs && node test/conversion-evidence-smoke.mjs && node test/package-canvas-surface-smoke.mjs && node test/view-render-graph-smoke.mjs && node test/resource-graph-smoke.mjs && node test/interlingua-smoke.mjs && node test/dialect-registry-smoke.mjs",
26
26
  "typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p test/tsconfig.json",
27
27
  "fuzz": "npm run build && node fuzz/smoke.mjs",
28
28
  "bench": "npm run build && node bench/smoke.mjs",