@shapeshift-labs/frontier-lang-parser 0.3.47 → 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.
package/dist/action-body.js
CHANGED
|
@@ -53,6 +53,7 @@ function parseActionIfBlock(header, body, index, state) {
|
|
|
53
53
|
id: details.id,
|
|
54
54
|
name: details.name,
|
|
55
55
|
comparisonType: details.comparisonType,
|
|
56
|
+
callType: details.callType,
|
|
56
57
|
condition: details.condition,
|
|
57
58
|
body: parseActionBodyRecords(body, state)
|
|
58
59
|
});
|
|
@@ -65,11 +66,13 @@ function parseActionIfHeader(header, index) {
|
|
|
65
66
|
const name = firstIdentifier(nameText) ?? `if_${index}`;
|
|
66
67
|
const conditionText = explicitCondition?.[1]?.trim() || withoutId;
|
|
67
68
|
const comparisonType = readInlineComparisonType(header);
|
|
69
|
+
const callType = readInlineCallType(header);
|
|
68
70
|
return {
|
|
69
71
|
id: idFrom(header, `action_body_if_${name}`),
|
|
70
72
|
name,
|
|
71
73
|
comparisonType,
|
|
72
|
-
|
|
74
|
+
callType,
|
|
75
|
+
condition: conditionText ? readActionValue(conditionText, { comparisonType, callType }) : undefined
|
|
73
76
|
};
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -83,9 +86,10 @@ function parseActionBodyLine(line, index) {
|
|
|
83
86
|
const path = readInlineWord('path', rest);
|
|
84
87
|
const valueType = readInlineType(rest);
|
|
85
88
|
const comparisonType = readInlineComparisonType(rest);
|
|
86
|
-
const
|
|
89
|
+
const callType = readInlineCallType(rest);
|
|
90
|
+
const value = readInlineActionValue('value', rest, { valueType, comparisonType, callType });
|
|
87
91
|
if (!path || !value) return undefined;
|
|
88
|
-
return compactRecord({ kind: 'patch', op: rowKind, id: idFrom(rest, `action_body_${rowKind}_${name}`), name, path, valueType, comparisonType, value });
|
|
92
|
+
return compactRecord({ kind: 'patch', op: rowKind, id: idFrom(rest, `action_body_${rowKind}_${name}`), name, path, valueType, comparisonType, callType, value });
|
|
89
93
|
}
|
|
90
94
|
if (rowKind === 'remove') {
|
|
91
95
|
const path = readInlineWord('path', rest);
|
|
@@ -101,9 +105,10 @@ function parseActionBodyLine(line, index) {
|
|
|
101
105
|
if (rowKind === 'let') {
|
|
102
106
|
const valueType = readInlineType(rest);
|
|
103
107
|
const comparisonType = readInlineComparisonType(rest);
|
|
104
|
-
const
|
|
108
|
+
const callType = readInlineCallType(rest);
|
|
109
|
+
const value = readInlineActionBindingValue('value', rest, { valueType, comparisonType, callType });
|
|
105
110
|
if (!rawName || rawName.startsWith('@') || !isActionBindingName(name) || !value) return undefined;
|
|
106
|
-
return compactRecord({ kind: 'let', id: idFrom(rest, `action_body_let_${name}`), name, valueType, comparisonType, value });
|
|
111
|
+
return compactRecord({ kind: 'let', id: idFrom(rest, `action_body_let_${name}`), name, valueType, comparisonType, callType, value });
|
|
107
112
|
}
|
|
108
113
|
if (rowKind === 'return') {
|
|
109
114
|
const valueText = stripIds(rawName?.startsWith('@') ? rest : `${rawName ?? ''}${rest ?? ''}`).trim();
|
|
@@ -195,6 +200,7 @@ function isActionBindingName(value) {
|
|
|
195
200
|
function idFrom(header, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1] ?? fallback; }
|
|
196
201
|
function readInlineType(text) { return readInlineWord('type', text) ?? readInlineWord('valueType', text); }
|
|
197
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); }
|
|
198
204
|
function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
|
|
199
205
|
function readInlineValue(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+(.+?)(?=\\s+[A-Za-z_$][\\w$-]*\\s+|$)').exec(text)?.[1]?.trim(); }
|
|
200
206
|
function stripIds(text) { return String(text ?? '').replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim(); }
|
|
@@ -2,6 +2,7 @@ export const NUMERIC_OPERATORS = new Set(['+', '-', '*', '/', '%']);
|
|
|
2
2
|
|
|
3
3
|
const ORDERED_COMPARISON_OPERATORS = new Set(['>', '>=', '<', '<=']);
|
|
4
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']);
|
|
5
6
|
|
|
6
7
|
export function hasNumericOperator(node) {
|
|
7
8
|
if (!node || typeof node !== 'object') return false;
|
|
@@ -22,10 +23,22 @@ export function hasNonLiteralOrderedComparison(node) {
|
|
|
22
23
|
return false;
|
|
23
24
|
}
|
|
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
|
+
|
|
25
34
|
export function isNumericType(value) {
|
|
26
35
|
return NUMERIC_TYPES.has(String(value ?? '').trim().toLowerCase());
|
|
27
36
|
}
|
|
28
37
|
|
|
38
|
+
export function isSupportedCallType(value) {
|
|
39
|
+
return CALL_TYPES.has(String(value ?? '').trim().toLowerCase());
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
function isLiteralNumericComparison(node) {
|
|
30
43
|
return node.left?.kind === 'literal' && typeof node.left.value === 'number' && node.right?.kind === 'literal' && typeof node.right.value === 'number';
|
|
31
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 {
|
|
1
|
+
import { hasCallExpression, hasNonLiteralOrderedComparison, hasNumericOperator, isNumericType, isSupportedCallType } from './action-expression-semantics.js';
|
|
2
|
+
import { tokenizeActionExpression } from './action-expression-tokenizer.js';
|
|
2
3
|
|
|
3
4
|
const IDENTIFIER = /^[A-Za-z_$][\w$-]*$/;
|
|
4
5
|
const BLOCKED_REF_ROOTS = new Set(['globalThis', 'process', 'window', 'document', 'constructor', 'prototype', '__proto__', 'env']);
|
|
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);
|
|
@@ -19,10 +21,15 @@ export function parseActionValue(value, options = {}) {
|
|
|
19
21
|
if (hasNonLiteralOrderedComparison(expression.ast) && !isNumericType(options.comparisonType ?? options.compareType)) {
|
|
20
22
|
return fail((options.comparisonType ?? options.compareType) ? 'unsupported-action-comparison-type' : 'missing-action-comparison-type');
|
|
21
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
|
+
}
|
|
22
27
|
const valueType = options.valueType ?? options.type;
|
|
23
28
|
const comparisonType = options.comparisonType ?? options.compareType;
|
|
24
|
-
|
|
25
|
-
|
|
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 }) };
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
export function parseActionExpression(value, options = {}) {
|
|
@@ -41,86 +48,6 @@ export function isSupportedActionValue(value, options = {}) {
|
|
|
41
48
|
return parseActionValue(value, options).ok;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
|
-
function tokenizeActionExpression(text) {
|
|
45
|
-
const tokens = [];
|
|
46
|
-
let index = 0;
|
|
47
|
-
while (index < text.length) {
|
|
48
|
-
const char = text[index];
|
|
49
|
-
if (/\s/.test(char)) {
|
|
50
|
-
index++;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (char === '"' || char === "'") {
|
|
54
|
-
const read = readStringToken(text, index);
|
|
55
|
-
if (!read.ok) return read;
|
|
56
|
-
tokens.push(read.token);
|
|
57
|
-
index = read.next;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
const canReadSignedNumber = char !== '-' || canStartSignedNumber(tokens);
|
|
61
|
-
const number = canReadSignedNumber ? /^-?\d+(?:\.\d+)?/.exec(text.slice(index)) : undefined;
|
|
62
|
-
if (number) {
|
|
63
|
-
tokens.push({ type: 'number', value: Number(number[0]), text: number[0], start: index, end: index + number[0].length });
|
|
64
|
-
index += number[0].length;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const identifier = /^[A-Za-z_$][\w$-]*/.exec(text.slice(index));
|
|
68
|
-
if (identifier) {
|
|
69
|
-
tokens.push({ type: 'identifier', text: identifier[0], start: index, end: index + identifier[0].length });
|
|
70
|
-
index += identifier[0].length;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const triple = text.slice(index, index + 3);
|
|
74
|
-
if (triple === '===' || triple === '!==') return fail('unsupported-action-expression-operator');
|
|
75
|
-
const double = text.slice(index, index + 2);
|
|
76
|
-
if (double === '&&' || double === '||' || double === '==' || double === '!=' || double === '>=' || double === '<=') {
|
|
77
|
-
tokens.push({ type: 'operator', text: double, start: index, end: index + 2 });
|
|
78
|
-
index += 2;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (char === '!' || char === '>' || char === '<' || NUMERIC_OPERATORS.has(char)) {
|
|
82
|
-
tokens.push({ type: 'operator', text: char, start: index, end: index + 1 });
|
|
83
|
-
index++;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (char === '(' || char === ')' || char === '.') {
|
|
87
|
-
tokens.push({ type: 'punctuation', text: char, start: index, end: index + 1 });
|
|
88
|
-
index++;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if ('=&|?:[]{}'.includes(char)) return fail('unsupported-action-expression-operator');
|
|
92
|
-
return fail('malformed-action-expression');
|
|
93
|
-
}
|
|
94
|
-
tokens.push({ type: 'eof', text: '', start: text.length, end: text.length });
|
|
95
|
-
return { ok: true, tokens };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function readStringToken(text, start) {
|
|
99
|
-
const quote = text[start];
|
|
100
|
-
let value = '';
|
|
101
|
-
let index = start + 1;
|
|
102
|
-
while (index < text.length) {
|
|
103
|
-
const char = text[index];
|
|
104
|
-
if (char === quote) {
|
|
105
|
-
return { ok: true, token: { type: 'string', value, text: text.slice(start, index + 1), start, end: index + 1 }, next: index + 1 };
|
|
106
|
-
}
|
|
107
|
-
if (char === '\\') {
|
|
108
|
-
const next = text[index + 1];
|
|
109
|
-
if (next === undefined) return fail('malformed-action-expression');
|
|
110
|
-
if (next === 'n') value += '\n';
|
|
111
|
-
else if (next === 'r') value += '\r';
|
|
112
|
-
else if (next === 't') value += '\t';
|
|
113
|
-
else if (next === '\\' || next === '"' || next === "'") value += next;
|
|
114
|
-
else return fail('malformed-action-expression');
|
|
115
|
-
index += 2;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
value += char;
|
|
119
|
-
index++;
|
|
120
|
-
}
|
|
121
|
-
return fail('malformed-action-expression');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
51
|
class ExpressionParser {
|
|
125
52
|
constructor(tokens, options) {
|
|
126
53
|
this.tokens = tokens;
|
|
@@ -220,6 +147,7 @@ class ExpressionParser {
|
|
|
220
147
|
const first = this.consumeIdentifier();
|
|
221
148
|
if (!first) return { kind: 'literal', value: null };
|
|
222
149
|
parts.push(first);
|
|
150
|
+
if (this.peek().type === 'punctuation' && this.peek().text === '(') return this.parseCall(first);
|
|
223
151
|
while (this.matchPunctuation('.')) {
|
|
224
152
|
const part = this.consumeIdentifier();
|
|
225
153
|
if (!part) {
|
|
@@ -229,7 +157,7 @@ class ExpressionParser {
|
|
|
229
157
|
parts.push(part);
|
|
230
158
|
}
|
|
231
159
|
if (this.peek().type === 'punctuation' && this.peek().text === '(') {
|
|
232
|
-
this.reject('unsupported-action-
|
|
160
|
+
this.reject('unsupported-action-call-callee');
|
|
233
161
|
return refNode(parts);
|
|
234
162
|
}
|
|
235
163
|
if (BLOCKED_REF_ROOTS.has(parts[0])) {
|
|
@@ -239,6 +167,31 @@ class ExpressionParser {
|
|
|
239
167
|
return refNode(parts);
|
|
240
168
|
}
|
|
241
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
|
+
|
|
242
195
|
consumeIdentifier() {
|
|
243
196
|
const token = this.peek();
|
|
244
197
|
if (token.type !== 'identifier' || !IDENTIFIER.test(token.text)) {
|
|
@@ -285,13 +238,6 @@ class ExpressionParser {
|
|
|
285
238
|
}
|
|
286
239
|
}
|
|
287
240
|
|
|
288
|
-
function canStartSignedNumber(tokens) {
|
|
289
|
-
const previous = tokens[tokens.length - 1];
|
|
290
|
-
if (!previous) return true;
|
|
291
|
-
if (previous.type === 'operator') return true;
|
|
292
|
-
return previous.type === 'punctuation' && previous.text === '(';
|
|
293
|
-
}
|
|
294
|
-
|
|
295
241
|
function refNode(parts) {
|
|
296
242
|
const [root, ...rest] = parts;
|
|
297
243
|
const scope = root === 'input' || root === 'state' || root === 'patches' ? root : 'local';
|
|
@@ -303,6 +249,14 @@ function fail(reason) {
|
|
|
303
249
|
return { ok: false, reason };
|
|
304
250
|
}
|
|
305
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
|
+
|
|
306
260
|
function compactRecord(record) {
|
|
307
261
|
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0)));
|
|
308
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), { comparisonType: readInlineComparisonType(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), comparisonType: readInlineComparisonType(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), comparisonType: readInlineComparisonType(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' || parsed?.reason === 'missing-action-comparison-type' || parsed?.reason === 'unsupported-action-comparison-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' };
|
|
@@ -138,6 +138,10 @@ function readInlineComparisonType(text) {
|
|
|
138
138
|
return readInlineWord('compare', text) ?? readInlineWord('comparisonType', text) ?? readInlineWord('compareType', text);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
function readInlineCallType(text) {
|
|
142
|
+
return readInlineWord('call', text) ?? readInlineWord('callType', text);
|
|
143
|
+
}
|
|
144
|
+
|
|
141
145
|
function readIfCondition(header) {
|
|
142
146
|
const text = header.replace(/^if\b/, '').replace(/\{\s*$/, '').replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim();
|
|
143
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.
|
|
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",
|