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

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,7 @@ function parseActionIfBlock(header, body, index, state) {
52
52
  kind: 'if',
53
53
  id: details.id,
54
54
  name: details.name,
55
+ comparisonType: details.comparisonType,
55
56
  condition: details.condition,
56
57
  body: parseActionBodyRecords(body, state)
57
58
  });
@@ -63,10 +64,12 @@ function parseActionIfHeader(header, index) {
63
64
  const nameText = explicitCondition ? withoutId.slice(0, explicitCondition.index).trim() : '';
64
65
  const name = firstIdentifier(nameText) ?? `if_${index}`;
65
66
  const conditionText = explicitCondition?.[1]?.trim() || withoutId;
67
+ const comparisonType = readInlineComparisonType(header);
66
68
  return {
67
69
  id: idFrom(header, `action_body_if_${name}`),
68
70
  name,
69
- condition: conditionText ? readActionValue(conditionText) : undefined
71
+ comparisonType,
72
+ condition: conditionText ? readActionValue(conditionText, { comparisonType }) : undefined
70
73
  };
71
74
  }
72
75
 
@@ -79,9 +82,10 @@ function parseActionBodyLine(line, index) {
79
82
  if (rowKind === 'set' || rowKind === 'insert' || rowKind === 'merge') {
80
83
  const path = readInlineWord('path', rest);
81
84
  const valueType = readInlineType(rest);
82
- const value = readInlineActionValue('value', rest, { valueType });
85
+ const comparisonType = readInlineComparisonType(rest);
86
+ const value = readInlineActionValue('value', rest, { valueType, comparisonType });
83
87
  if (!path || !value) return undefined;
84
- return compactRecord({ kind: 'patch', op: rowKind, id: idFrom(rest, `action_body_${rowKind}_${name}`), name, path, valueType, value });
88
+ return compactRecord({ kind: 'patch', op: rowKind, id: idFrom(rest, `action_body_${rowKind}_${name}`), name, path, valueType, comparisonType, value });
85
89
  }
86
90
  if (rowKind === 'remove') {
87
91
  const path = readInlineWord('path', rest);
@@ -96,9 +100,10 @@ function parseActionBodyLine(line, index) {
96
100
  }
97
101
  if (rowKind === 'let') {
98
102
  const valueType = readInlineType(rest);
99
- const value = readInlineActionBindingValue('value', rest, { valueType });
103
+ const comparisonType = readInlineComparisonType(rest);
104
+ const value = readInlineActionBindingValue('value', rest, { valueType, comparisonType });
100
105
  if (!rawName || rawName.startsWith('@') || !isActionBindingName(name) || !value) return undefined;
101
- return compactRecord({ kind: 'let', id: idFrom(rest, `action_body_let_${name}`), name, valueType, value });
106
+ return compactRecord({ kind: 'let', id: idFrom(rest, `action_body_let_${name}`), name, valueType, comparisonType, value });
102
107
  }
103
108
  if (rowKind === 'return') {
104
109
  const valueText = stripIds(rawName?.startsWith('@') ? rest : `${rawName ?? ''}${rest ?? ''}`).trim();
@@ -189,6 +194,7 @@ function isActionBindingName(value) {
189
194
 
190
195
  function idFrom(header, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1] ?? fallback; }
191
196
  function readInlineType(text) { return readInlineWord('type', text) ?? readInlineWord('valueType', text); }
197
+ function readInlineComparisonType(text) { return readInlineWord('compare', text) ?? readInlineWord('comparisonType', text) ?? readInlineWord('compareType', text); }
192
198
  function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
193
199
  function readInlineValue(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+(.+?)(?=\\s+[A-Za-z_$][\\w$-]*\\s+|$)').exec(text)?.[1]?.trim(); }
194
200
  function stripIds(text) { return String(text ?? '').replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim(); }
@@ -0,0 +1,31 @@
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
+
6
+ export function hasNumericOperator(node) {
7
+ if (!node || typeof node !== 'object') return false;
8
+ if (node.kind === 'binary') return NUMERIC_OPERATORS.has(node.op) || hasNumericOperator(node.left) || hasNumericOperator(node.right);
9
+ if (node.kind === 'logical') return hasNumericOperator(node.left) || hasNumericOperator(node.right);
10
+ if (node.kind === 'unary') return hasNumericOperator(node.argument);
11
+ return false;
12
+ }
13
+
14
+ export function hasNonLiteralOrderedComparison(node) {
15
+ if (!node || typeof node !== 'object') return false;
16
+ if (node.kind === 'binary') {
17
+ const current = ORDERED_COMPARISON_OPERATORS.has(node.op) && !isLiteralNumericComparison(node);
18
+ return current || hasNonLiteralOrderedComparison(node.left) || hasNonLiteralOrderedComparison(node.right);
19
+ }
20
+ if (node.kind === 'logical') return hasNonLiteralOrderedComparison(node.left) || hasNonLiteralOrderedComparison(node.right);
21
+ if (node.kind === 'unary') return hasNonLiteralOrderedComparison(node.argument);
22
+ return false;
23
+ }
24
+
25
+ export function isNumericType(value) {
26
+ return NUMERIC_TYPES.has(String(value ?? '').trim().toLowerCase());
27
+ }
28
+
29
+ function isLiteralNumericComparison(node) {
30
+ return node.left?.kind === 'literal' && typeof node.left.value === 'number' && node.right?.kind === 'literal' && typeof node.right.value === 'number';
31
+ }
@@ -1,7 +1,7 @@
1
+ import { NUMERIC_OPERATORS, hasNonLiteralOrderedComparison, hasNumericOperator, isNumericType } from './action-expression-semantics.js';
2
+
1
3
  const IDENTIFIER = /^[A-Za-z_$][\w$-]*$/;
2
4
  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']);
5
5
 
6
6
  export function readActionValue(value, options = {}) {
7
7
  const parsed = parseActionValue(value, options);
@@ -16,9 +16,13 @@ export function parseActionValue(value, options = {}) {
16
16
  if (hasNumericOperator(expression.ast) && !isNumericType(options.valueType ?? options.type)) {
17
17
  return fail((options.valueType ?? options.type) ? 'unsupported-action-expression-type' : 'missing-action-expression-type');
18
18
  }
19
+ if (hasNonLiteralOrderedComparison(expression.ast) && !isNumericType(options.comparisonType ?? options.compareType)) {
20
+ return fail((options.comparisonType ?? options.compareType) ? 'unsupported-action-comparison-type' : 'missing-action-comparison-type');
21
+ }
19
22
  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 }) };
23
+ const comparisonType = options.comparisonType ?? options.compareType;
24
+ if (expression.literal) return { ok: true, value: compactRecord({ value: expression.ast.value, valueType, comparisonType }) };
25
+ return { ok: true, value: compactRecord({ expression: text, expressionAst: expression.ast, valueType, comparisonType }) };
22
26
  }
23
27
 
24
28
  export function parseActionExpression(value, options = {}) {
@@ -288,18 +292,6 @@ function canStartSignedNumber(tokens) {
288
292
  return previous.type === 'punctuation' && previous.text === '(';
289
293
  }
290
294
 
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
295
  function refNode(parts) {
304
296
  const [root, ...rest] = parts;
305
297
  const scope = root === 'input' || root === 'state' || root === 'patches' ? root : 'local';
@@ -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) });
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) });
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) }) : 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') {
118
118
  return { ok: false, reason: parsed.reason };
119
119
  }
120
120
  return { ok: false, reason: 'unsupported-action-binding-value' };
@@ -134,6 +134,10 @@ 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
+
137
141
  function readIfCondition(header) {
138
142
  const text = header.replace(/^if\b/, '').replace(/\{\s*$/, '').replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim();
139
143
  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.47",
4
4
  "description": "Parser for the first Frontier Lang .frontier syntax slice.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",