@shapeshift-labs/frontier-lang-parser 0.3.48 → 0.3.50
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 +52 -8
- package/dist/action-else-block.js +18 -0
- package/dist/action-syntax-children.js +54 -7
- package/package.json +2 -2
package/dist/action-body.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFrontierNestedBlocks } from './source-syntax-report.js';
|
|
2
2
|
import { readActionValue } from './action-expression.js';
|
|
3
|
+
import { readElseHeaderBlock } from './action-else-block.js';
|
|
3
4
|
|
|
4
5
|
export function readActionBodyRecords(body) {
|
|
5
6
|
const records = [];
|
|
@@ -27,10 +28,18 @@ function parseActionBodyRecords(source, state) {
|
|
|
27
28
|
const open = offset + ifHeader[0].length - 1;
|
|
28
29
|
const close = findMatchingBrace(source, open);
|
|
29
30
|
if (close < 0) break;
|
|
31
|
+
const elseBlock = readElseHeaderBlock(source, close + 1, source.length, { skipWhitespace: skipWhitespaceAndComments, findMatchingBrace });
|
|
30
32
|
const index = state.index++;
|
|
31
|
-
const record = parseActionIfBlock(ifHeader[1].trim(), source.slice(open + 1, close), index, state);
|
|
33
|
+
const record = parseActionIfBlock(ifHeader[1].trim(), source.slice(open + 1, close), index, state, elseBlock);
|
|
32
34
|
if (record) records.push(record);
|
|
33
|
-
offset = close + 1;
|
|
35
|
+
offset = elseBlock ? elseBlock.close + 1 : close + 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const elseHeader = /^else\b([^{\n]*)\{/.exec(source.slice(offset));
|
|
39
|
+
if (elseHeader) {
|
|
40
|
+
const open = offset + elseHeader[0].length - 1;
|
|
41
|
+
const close = findMatchingBrace(source, open);
|
|
42
|
+
offset = close < 0 ? source.length : close + 1;
|
|
34
43
|
continue;
|
|
35
44
|
}
|
|
36
45
|
const lineEnd = source.indexOf('\n', offset);
|
|
@@ -45,9 +54,10 @@ function parseActionBodyRecords(source, state) {
|
|
|
45
54
|
return records;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
|
-
function parseActionIfBlock(header, body, index, state) {
|
|
57
|
+
function parseActionIfBlock(header, body, index, state, elseBlock) {
|
|
49
58
|
const details = parseActionIfHeader(header, index);
|
|
50
59
|
if (!details.condition) return undefined;
|
|
60
|
+
const elseDetails = elseBlock ? parseActionElseHeader(elseBlock.header, index) : undefined;
|
|
51
61
|
return compactRecord({
|
|
52
62
|
kind: 'if',
|
|
53
63
|
id: details.id,
|
|
@@ -55,7 +65,10 @@ function parseActionIfBlock(header, body, index, state) {
|
|
|
55
65
|
comparisonType: details.comparisonType,
|
|
56
66
|
callType: details.callType,
|
|
57
67
|
condition: details.condition,
|
|
58
|
-
body: parseActionBodyRecords(body, state)
|
|
68
|
+
body: parseActionBodyRecords(body, state),
|
|
69
|
+
elseId: elseDetails?.id,
|
|
70
|
+
elseName: elseDetails?.name,
|
|
71
|
+
elseBody: elseBlock ? parseActionBodyRecords(elseBlock.body, state) : undefined
|
|
59
72
|
});
|
|
60
73
|
}
|
|
61
74
|
|
|
@@ -76,6 +89,15 @@ function parseActionIfHeader(header, index) {
|
|
|
76
89
|
};
|
|
77
90
|
}
|
|
78
91
|
|
|
92
|
+
function parseActionElseHeader(header, index) {
|
|
93
|
+
const withoutId = header.replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim();
|
|
94
|
+
const name = firstIdentifier(withoutId) ?? `else_${index}`;
|
|
95
|
+
return {
|
|
96
|
+
id: idFrom(header, `action_body_else_${name}`),
|
|
97
|
+
name
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
79
101
|
function parseActionBodyLine(line, index) {
|
|
80
102
|
const statement = /^([A-Za-z_$][\w$-]*)(?:\s+([A-Za-z_$@./:*+-][\w$./@:*+-]*))?(.*)$/.exec(line);
|
|
81
103
|
if (!statement) return undefined;
|
|
@@ -111,14 +133,36 @@ function parseActionBodyLine(line, index) {
|
|
|
111
133
|
return compactRecord({ kind: 'let', id: idFrom(rest, `action_body_let_${name}`), name, valueType, comparisonType, callType, value });
|
|
112
134
|
}
|
|
113
135
|
if (rowKind === 'return') {
|
|
114
|
-
const
|
|
115
|
-
const value = valueText ? readActionValue(valueText) : undefined;
|
|
116
|
-
if (valueText && !value) return undefined;
|
|
117
|
-
return compactRecord({
|
|
136
|
+
const details = readReturnDetails(rawName, rest);
|
|
137
|
+
const value = details.valueText ? readActionValue(details.valueText, details) : undefined;
|
|
138
|
+
if (details.valueText && !value) return undefined;
|
|
139
|
+
return compactRecord({
|
|
140
|
+
kind: 'return',
|
|
141
|
+
id: idFrom(rest, `action_body_return_${index}`),
|
|
142
|
+
valueType: details.valueType,
|
|
143
|
+
comparisonType: details.comparisonType,
|
|
144
|
+
callType: details.callType,
|
|
145
|
+
value
|
|
146
|
+
});
|
|
118
147
|
}
|
|
119
148
|
return undefined;
|
|
120
149
|
}
|
|
121
150
|
|
|
151
|
+
function readReturnDetails(rawName, rest) {
|
|
152
|
+
const text = stripIds(rawName?.startsWith('@') ? rest : `${rawName ?? ''}${rest ?? ''}`).trim();
|
|
153
|
+
const explicitValue = /\bvalue\s+/.test(text);
|
|
154
|
+
const valueType = readInlineType(text);
|
|
155
|
+
const comparisonType = readInlineComparisonType(text);
|
|
156
|
+
const callType = readInlineCallType(text);
|
|
157
|
+
const valueText = explicitValue ? readInlineValue('value', text) : text;
|
|
158
|
+
return {
|
|
159
|
+
valueText,
|
|
160
|
+
valueType,
|
|
161
|
+
comparisonType,
|
|
162
|
+
callType
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
122
166
|
function skipWhitespaceAndComments(source, offset) {
|
|
123
167
|
let index = offset;
|
|
124
168
|
while (index < source.length) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function readElseHeaderBlock(source, offset, endOffset = source.length, helpers = {}) {
|
|
2
|
+
const skipWhitespace = helpers.skipWhitespace;
|
|
3
|
+
const findMatchingBrace = helpers.findMatchingBrace;
|
|
4
|
+
if (typeof skipWhitespace !== 'function' || typeof findMatchingBrace !== 'function') return undefined;
|
|
5
|
+
const elseOffset = skipWhitespace(source, offset, endOffset);
|
|
6
|
+
const elseHeader = /^else\b([^{\n]*)\{/.exec(source.slice(elseOffset, endOffset));
|
|
7
|
+
if (!elseHeader) return undefined;
|
|
8
|
+
const header = elseHeader[1].trim();
|
|
9
|
+
const open = elseOffset + elseHeader[0].length - 1;
|
|
10
|
+
const close = findMatchingBrace(source, open);
|
|
11
|
+
if (close < 0 || close > endOffset) return undefined;
|
|
12
|
+
return { header, start: elseOffset, open, close, body: source.slice(open + 1, close), supported: isSupportedElseHeader(header) };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isSupportedElseHeader(header) {
|
|
16
|
+
const withoutId = String(header ?? '').replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim();
|
|
17
|
+
return !withoutId || /^[A-Za-z_$][\w$-]*$/.test(withoutId);
|
|
18
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseActionValue } from './action-expression.js';
|
|
2
|
+
import { readElseHeaderBlock } from './action-else-block.js';
|
|
2
3
|
|
|
3
4
|
const ACTION_BODY_ROWS = new Set(['set', 'insert', 'remove', 'merge', 'callEffect', 'return', 'if', 'let']);
|
|
4
5
|
|
|
@@ -33,9 +34,31 @@ function readActionSyntaxRows(source, block, options, state, startOffset, endOff
|
|
|
33
34
|
}, state, parentActionBodyId);
|
|
34
35
|
children.push(child);
|
|
35
36
|
children.push(...readActionSyntaxRows(source, block, options, state, open + 1, close, child.id));
|
|
37
|
+
const elseBlock = readElseHeaderBlock(source, close + 1, endOffset, { skipWhitespace: skipWhitespaceAndLineComments, findMatchingBrace });
|
|
38
|
+
if (elseBlock) {
|
|
39
|
+
const elseChild = actionSyntaxChild(source, block, options, {
|
|
40
|
+
text: source.slice(elseBlock.start, elseBlock.open + 1).trim(),
|
|
41
|
+
startOffset: elseBlock.start,
|
|
42
|
+
endOffset: elseBlock.open + 1
|
|
43
|
+
}, state, child.id, { recognized: elseBlock.supported });
|
|
44
|
+
children.push(elseChild);
|
|
45
|
+
if (elseBlock.supported) children.push(...readActionSyntaxRows(source, block, options, state, elseBlock.open + 1, elseBlock.close, elseChild.id));
|
|
46
|
+
offset = elseBlock.close + 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
36
49
|
offset = close + 1;
|
|
37
50
|
continue;
|
|
38
51
|
}
|
|
52
|
+
const elseBlock = readElseHeaderBlock(source, offset, endOffset, { skipWhitespace: skipWhitespaceAndLineComments, findMatchingBrace });
|
|
53
|
+
if (elseBlock) {
|
|
54
|
+
children.push(actionSyntaxChild(source, block, options, {
|
|
55
|
+
text: source.slice(elseBlock.start, elseBlock.open + 1).trim(),
|
|
56
|
+
startOffset: elseBlock.start,
|
|
57
|
+
endOffset: elseBlock.open + 1
|
|
58
|
+
}, state, parentActionBodyId));
|
|
59
|
+
offset = elseBlock.close + 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
39
62
|
const lineEnd = source.indexOf('\n', offset);
|
|
40
63
|
const rawEnd = lineEnd < 0 || lineEnd > endOffset ? endOffset : lineEnd;
|
|
41
64
|
const rawLine = source.slice(offset, rawEnd);
|
|
@@ -52,14 +75,14 @@ function readActionSyntaxRows(source, block, options, state, startOffset, endOff
|
|
|
52
75
|
return children;
|
|
53
76
|
}
|
|
54
77
|
|
|
55
|
-
function actionSyntaxChild(source, block, options, line, state, parentActionBodyId) {
|
|
78
|
+
function actionSyntaxChild(source, block, options, line, state, parentActionBodyId, overrides = {}) {
|
|
56
79
|
const rowIndex = state.rowIndex++;
|
|
57
80
|
const row = /^([A-Za-z_$][\w$-]*)(?:\s+([A-Za-z_$@./:*+-][\w$./@:*+-]*))?(.*)$/.exec(line.text);
|
|
58
81
|
const rowKind = row?.[1] ?? 'unknown';
|
|
59
82
|
const name = actionRowName(rowKind, row?.[2], rowIndex);
|
|
60
83
|
const rest = row?.[2]?.startsWith('@') ? ` ${row[2]}${row[3] ?? ''}` : (row?.[3] ?? '');
|
|
61
84
|
const validation = validateActionRow(rowKind, row?.[2], rest, line.text);
|
|
62
|
-
const recognized = ACTION_BODY_ROWS.has(rowKind) && validation.ok;
|
|
85
|
+
const recognized = overrides.recognized ?? (ACTION_BODY_ROWS.has(rowKind) && validation.ok);
|
|
63
86
|
return cleanRecord({
|
|
64
87
|
kind: recognized ? 'actionBodyRow' : 'actionUnknownRow',
|
|
65
88
|
rowKind,
|
|
@@ -104,8 +127,12 @@ function validateActionRow(rowKind, rawName, rest, header) {
|
|
|
104
127
|
return input ? validateActionExpressionText(input) : { ok: true };
|
|
105
128
|
}
|
|
106
129
|
if (rowKind === 'return') {
|
|
107
|
-
const
|
|
108
|
-
|
|
130
|
+
const details = readReturnDetails(rawName, rest);
|
|
131
|
+
if (!details.valueText) return { ok: true };
|
|
132
|
+
const parsed = parseActionValue(details.valueText, details);
|
|
133
|
+
if (parsed.ok) return { ok: true };
|
|
134
|
+
if (isActionExpressionAdmissionReason(parsed.reason)) return { ok: false, reason: parsed.reason };
|
|
135
|
+
return { ok: false, reason: 'unsupported-action-return-value' };
|
|
109
136
|
}
|
|
110
137
|
if (rowKind === 'let') {
|
|
111
138
|
if (!rawName || rawName.startsWith('@') || !/^[A-Za-z_$][\w$-]*$/.test(rawName)) {
|
|
@@ -148,9 +175,15 @@ function readIfCondition(header) {
|
|
|
148
175
|
return explicit ? explicit[1].trim() : text;
|
|
149
176
|
}
|
|
150
177
|
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
178
|
+
function readReturnDetails(rawName, rest) {
|
|
179
|
+
const text = stripIds(rawName?.startsWith('@') ? rest : `${rawName ?? ''}${rest ?? ''}`).trim();
|
|
180
|
+
const explicitValue = /\bvalue\s+/.test(text);
|
|
181
|
+
return {
|
|
182
|
+
valueText: explicitValue ? readInlineValue('value', text) : text,
|
|
183
|
+
valueType: readInlineType(text),
|
|
184
|
+
comparisonType: readInlineComparisonType(text),
|
|
185
|
+
callType: readInlineCallType(text)
|
|
186
|
+
};
|
|
154
187
|
}
|
|
155
188
|
|
|
156
189
|
function stripIds(text) {
|
|
@@ -162,6 +195,20 @@ function validateActionExpressionText(text, options = {}) {
|
|
|
162
195
|
return parsed.ok ? { ok: true } : { ok: false, reason: parsed.reason };
|
|
163
196
|
}
|
|
164
197
|
|
|
198
|
+
function isActionExpressionAdmissionReason(reason) {
|
|
199
|
+
return reason === 'missing-action-expression-type'
|
|
200
|
+
|| reason === 'unsupported-action-expression-type'
|
|
201
|
+
|| reason === 'missing-action-comparison-type'
|
|
202
|
+
|| reason === 'unsupported-action-comparison-type'
|
|
203
|
+
|| reason === 'missing-action-call-type'
|
|
204
|
+
|| reason === 'unsupported-action-call-type'
|
|
205
|
+
|| reason === 'unsupported-action-call-callee'
|
|
206
|
+
|| reason === 'unsupported-action-call-argument'
|
|
207
|
+
|| reason === 'unsupported-action-expression-ref'
|
|
208
|
+
|| reason === 'malformed-action-expression'
|
|
209
|
+
|| reason === 'missing-action-expression';
|
|
210
|
+
}
|
|
211
|
+
|
|
165
212
|
function readNestedBodyBlocks(kind, source) {
|
|
166
213
|
const blocks = [];
|
|
167
214
|
const header = new RegExp('\\b' + kind.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:\\s+([^{}\\n]+?))?\\s*\\{', 'g');
|
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.50",
|
|
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/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",
|
|
25
|
+
"test": "npm run build && node test/smoke.mjs && node test/action-body-smoke.mjs && node test/action-return-smoke.mjs && node test/action-else-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",
|