@shapeshift-labs/frontier-lang-parser 0.3.55 → 0.3.57
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-expression-semantics.js +6 -0
- package/dist/action-expression-tokenizer.js +3 -3
- package/dist/action-expression.js +58 -0
- package/dist/action-syntax-children.js +1 -1
- package/dist/index.js +9 -11
- package/dist/source-syntax-children.js +37 -0
- package/dist/type-variants.js +63 -0
- package/package.json +2 -2
|
@@ -9,6 +9,8 @@ export function hasNumericOperator(node) {
|
|
|
9
9
|
if (node.kind === 'binary') return NUMERIC_OPERATORS.has(node.op) || hasNumericOperator(node.left) || hasNumericOperator(node.right);
|
|
10
10
|
if (node.kind === 'logical') return hasNumericOperator(node.left) || hasNumericOperator(node.right);
|
|
11
11
|
if (node.kind === 'unary') return hasNumericOperator(node.argument);
|
|
12
|
+
if (node.kind === 'array') return (node.elements ?? []).some(hasNumericOperator);
|
|
13
|
+
if (node.kind === 'object') return (node.entries ?? []).some((entry) => hasNumericOperator(entry.value));
|
|
12
14
|
return false;
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -20,6 +22,8 @@ export function hasNonLiteralOrderedComparison(node) {
|
|
|
20
22
|
}
|
|
21
23
|
if (node.kind === 'logical') return hasNonLiteralOrderedComparison(node.left) || hasNonLiteralOrderedComparison(node.right);
|
|
22
24
|
if (node.kind === 'unary') return hasNonLiteralOrderedComparison(node.argument);
|
|
25
|
+
if (node.kind === 'array') return (node.elements ?? []).some(hasNonLiteralOrderedComparison);
|
|
26
|
+
if (node.kind === 'object') return (node.entries ?? []).some((entry) => hasNonLiteralOrderedComparison(entry.value));
|
|
23
27
|
return false;
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -28,6 +32,8 @@ export function hasCallExpression(node) {
|
|
|
28
32
|
if (node.kind === 'call') return true;
|
|
29
33
|
if (node.kind === 'binary' || node.kind === 'logical') return hasCallExpression(node.left) || hasCallExpression(node.right);
|
|
30
34
|
if (node.kind === 'unary') return hasCallExpression(node.argument);
|
|
35
|
+
if (node.kind === 'array') return (node.elements ?? []).some(hasCallExpression);
|
|
36
|
+
if (node.kind === 'object') return (node.entries ?? []).some((entry) => hasCallExpression(entry.value));
|
|
31
37
|
return false;
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -42,12 +42,12 @@ export function tokenizeActionExpression(text) {
|
|
|
42
42
|
index++;
|
|
43
43
|
continue;
|
|
44
44
|
}
|
|
45
|
-
if (char === '(' || char === ')' || char === '.' || char === ',') {
|
|
45
|
+
if (char === '(' || char === ')' || char === '.' || char === ',' || char === '[' || char === ']' || char === '{' || char === '}' || char === ':') {
|
|
46
46
|
tokens.push({ type: 'punctuation', text: char, start: index, end: index + 1 });
|
|
47
47
|
index++;
|
|
48
48
|
continue;
|
|
49
49
|
}
|
|
50
|
-
if ('
|
|
50
|
+
if ('=&|?'.includes(char)) return fail('unsupported-action-expression-operator');
|
|
51
51
|
return fail('malformed-action-expression');
|
|
52
52
|
}
|
|
53
53
|
tokens.push({ type: 'eof', text: '', start: text.length, end: text.length });
|
|
@@ -84,7 +84,7 @@ function canStartSignedNumber(tokens) {
|
|
|
84
84
|
const previous = tokens[tokens.length - 1];
|
|
85
85
|
if (!previous) return true;
|
|
86
86
|
if (previous.type === 'operator') return true;
|
|
87
|
-
return previous.type === 'punctuation' && (previous.text === '(' || previous.text === ',');
|
|
87
|
+
return previous.type === 'punctuation' && (previous.text === '(' || previous.text === ',' || previous.text === '[' || previous.text === ':');
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
function fail(reason) {
|
|
@@ -138,10 +138,66 @@ class ExpressionParser {
|
|
|
138
138
|
if (!this.matchPunctuation(')')) this.reject('malformed-action-expression');
|
|
139
139
|
return expression;
|
|
140
140
|
}
|
|
141
|
+
if (token.type === 'punctuation' && token.text === '[') return this.parseArray();
|
|
142
|
+
if (token.type === 'punctuation' && token.text === '{') return this.parseObject();
|
|
141
143
|
this.reject('malformed-action-expression');
|
|
142
144
|
return { kind: 'literal', value: null };
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
parseArray() {
|
|
148
|
+
this.matchPunctuation('[');
|
|
149
|
+
const elements = [];
|
|
150
|
+
if (this.matchPunctuation(']')) return { kind: 'array', elements };
|
|
151
|
+
while (this.ok) {
|
|
152
|
+
elements.push(this.parseExpression());
|
|
153
|
+
if (this.matchPunctuation(']')) break;
|
|
154
|
+
if (!this.matchPunctuation(',')) {
|
|
155
|
+
this.reject('malformed-action-expression');
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if (this.peek().type === 'punctuation' && this.peek().text === ']') {
|
|
159
|
+
this.reject('malformed-action-expression');
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { kind: 'array', elements };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
parseObject() {
|
|
167
|
+
this.matchPunctuation('{');
|
|
168
|
+
const entries = [];
|
|
169
|
+
if (this.matchPunctuation('}')) return { kind: 'object', entries };
|
|
170
|
+
while (this.ok) {
|
|
171
|
+
const key = this.readObjectKey();
|
|
172
|
+
if (key === undefined) {
|
|
173
|
+
this.reject('malformed-action-expression');
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
if (!this.matchPunctuation(':')) {
|
|
177
|
+
this.reject('malformed-action-expression');
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
entries.push({ key, value: this.parseExpression() });
|
|
181
|
+
if (this.matchPunctuation('}')) break;
|
|
182
|
+
if (!this.matchPunctuation(',')) {
|
|
183
|
+
this.reject('malformed-action-expression');
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
if (this.peek().type === 'punctuation' && this.peek().text === '}') {
|
|
187
|
+
this.reject('malformed-action-expression');
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { kind: 'object', entries };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
readObjectKey() {
|
|
195
|
+
const token = this.peek();
|
|
196
|
+
if (token.type !== 'identifier' && token.type !== 'string') return undefined;
|
|
197
|
+
this.index++;
|
|
198
|
+
return token.text === '__proto__' ? '__proto__' : String(token.value ?? token.text);
|
|
199
|
+
}
|
|
200
|
+
|
|
145
201
|
parseRef() {
|
|
146
202
|
const parts = [];
|
|
147
203
|
const first = this.consumeIdentifier();
|
|
@@ -254,6 +310,8 @@ function attachCallType(node, callType) {
|
|
|
254
310
|
if (node.kind === 'call') return compactRecord({ ...node, callType });
|
|
255
311
|
if (node.kind === 'binary' || node.kind === 'logical') return { ...node, left: attachCallType(node.left, callType), right: attachCallType(node.right, callType) };
|
|
256
312
|
if (node.kind === 'unary') return { ...node, argument: attachCallType(node.argument, callType) };
|
|
313
|
+
if (node.kind === 'array') return { ...node, elements: (node.elements ?? []).map((element) => attachCallType(element, callType)) };
|
|
314
|
+
if (node.kind === 'object') return { ...node, entries: (node.entries ?? []).map((entry) => ({ ...entry, value: attachCallType(entry.value, callType) })) };
|
|
257
315
|
return node;
|
|
258
316
|
}
|
|
259
317
|
|
|
@@ -187,7 +187,7 @@ function validateActionRow(rowKind, rawName, rest, header) {
|
|
|
187
187
|
const value = readInlineValue('value', rest);
|
|
188
188
|
const parsed = value ? parseActionValue(value, { valueType: readInlineType(rest), comparisonType: readInlineComparisonType(rest), callType: readInlineCallType(rest) }) : undefined;
|
|
189
189
|
if (parsed?.ok) return { ok: true };
|
|
190
|
-
if (parsed?.reason
|
|
190
|
+
if (isActionExpressionAdmissionReason(parsed?.reason)) {
|
|
191
191
|
return { ok: false, reason: parsed.reason };
|
|
192
192
|
}
|
|
193
193
|
return { ok: false, reason: 'unsupported-action-binding-value' };
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { parseResourceGraphBlock } from './resource-graph.js';
|
|
|
16
16
|
import { parseRuntimeCapabilityBlock } from './runtime-capability.js';
|
|
17
17
|
import { parseNativeSourceBlock } from './source-evidence.js';
|
|
18
18
|
import { parseTargetProjectionMetadata } from './target-projection.js';
|
|
19
|
+
import { readVariantPayloadFields } from './type-variants.js';
|
|
19
20
|
import { parseViewBlock } from './view.js';
|
|
20
21
|
import { FrontierSourceBlockKinds, readFrontierSourceBlocks } from './source-syntax-report.js';
|
|
21
22
|
export { FrontierSourceBlockKinds, inspectFrontierSourceSyntax } from './source-syntax-report.js';
|
|
@@ -166,15 +167,7 @@ function parseCapability(block) {
|
|
|
166
167
|
function parseType(block) {
|
|
167
168
|
const name = nameFrom(block.header);
|
|
168
169
|
const alias = /^\s*=\s*([^\n]+)/m.exec(block.body)?.[1]?.trim();
|
|
169
|
-
return typeNode({
|
|
170
|
-
id: idFrom(block.header, `type_${name}`),
|
|
171
|
-
name,
|
|
172
|
-
parameters: readTypeParameters(block.header),
|
|
173
|
-
type: alias ? parseTypeExpression(alias) : undefined,
|
|
174
|
-
fields: readTypeFields(block.body),
|
|
175
|
-
variants: readVariants(block.body),
|
|
176
|
-
invariants: readList('invariants', block.body)
|
|
177
|
-
});
|
|
170
|
+
return typeNode({ id: idFrom(block.header, `type_${name}`), name, parameters: readTypeParameters(block.header), type: alias ? parseTypeExpression(alias) : undefined, fields: readTypeFields(block.body), variants: readVariants(block.body), invariants: readList('invariants', block.body) });
|
|
178
171
|
}
|
|
179
172
|
function parseExtern(block) {
|
|
180
173
|
const name = nameFrom(block.header);
|
|
@@ -310,8 +303,13 @@ function readTypeFields(body) {
|
|
|
310
303
|
}
|
|
311
304
|
function readVariants(body) {
|
|
312
305
|
const variants = [];
|
|
313
|
-
const re = /^\s*variant\s+([A-Za-z_$][\w$]*)(
|
|
306
|
+
const re = /^\s*variant\s+([A-Za-z_$][\w$]*)(.*)$/gm;
|
|
314
307
|
let match;
|
|
315
|
-
while ((match = re.exec(body)))
|
|
308
|
+
while ((match = re.exec(body))) {
|
|
309
|
+
const fields = readVariantPayloadFields(match[2] ?? '', match[1], parseTypeExpression);
|
|
310
|
+
if (fields === null) continue;
|
|
311
|
+
const id = /@id\(\s*["']([^"']+)["']\s*\)/.exec(match[2] ?? '')?.[1];
|
|
312
|
+
variants.push({ ...(id ? { id } : {}), name: match[1], ...(fields?.length ? { fields } : {}) });
|
|
313
|
+
}
|
|
316
314
|
return variants.length ? variants : undefined;
|
|
317
315
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readActionSyntaxChildren } from './action-syntax-children.js';
|
|
2
2
|
import { ROW_SYNTAX_CONFIG } from './source-syntax-row-config.js';
|
|
3
|
+
import { inspectVariantPayload } from './type-variants.js';
|
|
3
4
|
|
|
4
5
|
const ROW_NAME_PATTERN = '([A-Za-z_$@./:*+-][\\w$./@:*+-]*)';
|
|
5
6
|
|
|
@@ -11,11 +12,47 @@ export function readSourceSyntaxChildren(source, block, options = {}) {
|
|
|
11
12
|
if (block.kind === 'conversion' || block.kind === 'universalConversionPlan') {
|
|
12
13
|
return readConversionSyntaxChildren(source, block, options);
|
|
13
14
|
}
|
|
15
|
+
if (block.kind === 'type') {
|
|
16
|
+
return readTypeSyntaxChildren(source, block, options);
|
|
17
|
+
}
|
|
14
18
|
const rowConfig = ROW_SYNTAX_CONFIG[block.kind];
|
|
15
19
|
if (rowConfig) return readGenericRowSyntaxChildren(source, block, options, rowConfig);
|
|
16
20
|
return [];
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
function readTypeSyntaxChildren(source, block, options) {
|
|
24
|
+
const children = [];
|
|
25
|
+
for (const line of readBodyLines(source, block)) {
|
|
26
|
+
if (!line.text || line.text.startsWith('#')) continue;
|
|
27
|
+
const variant = /^variant\s+([A-Za-z_$][\w$]*)(.*)$/.exec(line.text);
|
|
28
|
+
if (!variant) continue;
|
|
29
|
+
const [, name, rest] = variant;
|
|
30
|
+
const payload = inspectVariantPayload(rest ?? '');
|
|
31
|
+
children.push(cleanRecord({
|
|
32
|
+
kind: 'typeVariant',
|
|
33
|
+
rowKind: 'variant',
|
|
34
|
+
normalizedRowKind: 'variant',
|
|
35
|
+
name,
|
|
36
|
+
id: idFrom(rest, `type_variant_${safeId(name)}`),
|
|
37
|
+
header: line.text,
|
|
38
|
+
startOffset: line.startOffset,
|
|
39
|
+
endOffset: line.endOffset,
|
|
40
|
+
location: sourcePosition(source, line.startOffset),
|
|
41
|
+
parentKind: block.kind,
|
|
42
|
+
parentId: block.id,
|
|
43
|
+
parentName: block.name,
|
|
44
|
+
moduleId: block.moduleId,
|
|
45
|
+
moduleName: block.moduleName,
|
|
46
|
+
sourceSpan: sourceSpan(source, block, line.startOffset, line.endOffset, options),
|
|
47
|
+
recognized: payload.ok,
|
|
48
|
+
reason: payload.ok ? undefined : payload.reason,
|
|
49
|
+
fieldCount: payload.fieldCount,
|
|
50
|
+
fieldIds: payload.fieldIds
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
return children;
|
|
54
|
+
}
|
|
55
|
+
|
|
19
56
|
function readConversionSyntaxChildren(source, block, options) {
|
|
20
57
|
const children = [];
|
|
21
58
|
for (const line of readBodyLines(source, block)) {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function readVariantPayloadFields(rest, variantName, parseTypeExpression) {
|
|
2
|
+
const withoutId = stripLeadingVariantId(rest);
|
|
3
|
+
if (!withoutId) return undefined;
|
|
4
|
+
const payload = /^\((.*)\)$/.exec(withoutId);
|
|
5
|
+
if (!payload || !payload[1].trim()) return null;
|
|
6
|
+
const fields = [];
|
|
7
|
+
for (const part of splitTopLevelCommaList(payload[1])) {
|
|
8
|
+
const field = parseVariantPayloadField(part, variantName, parseTypeExpression);
|
|
9
|
+
if (!field) return null;
|
|
10
|
+
fields.push(field);
|
|
11
|
+
}
|
|
12
|
+
return fields;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function inspectVariantPayload(rest) {
|
|
16
|
+
const withoutId = stripLeadingVariantId(rest);
|
|
17
|
+
if (!withoutId) return { ok: true };
|
|
18
|
+
const payload = /^\((.*)\)$/.exec(withoutId);
|
|
19
|
+
if (!payload || !payload[1].trim()) return { ok: false, reason: 'malformed-type-variant-payload' };
|
|
20
|
+
const fieldIds = [];
|
|
21
|
+
const seen = new Set();
|
|
22
|
+
for (const fieldSource of splitTopLevelCommaList(payload[1])) {
|
|
23
|
+
const field = parseVariantPayloadField(fieldSource);
|
|
24
|
+
if (!field) return { ok: false, reason: 'malformed-type-variant-payload' };
|
|
25
|
+
if (seen.has(field.id)) return { ok: false, reason: 'duplicate-type-variant-field-id' };
|
|
26
|
+
seen.add(field.id);
|
|
27
|
+
fieldIds.push(field.id);
|
|
28
|
+
}
|
|
29
|
+
return { ok: true, fieldCount: fieldIds.length, fieldIds };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseVariantPayloadField(source, variantName, parseTypeExpression) {
|
|
33
|
+
const match = /^\s*([A-Za-z_$][\w$]*)(\?)?(?:\s+@id\(\s*["']([^"']+)["']\s*\))?(\?)?\s*:\s*(.+)\s*$/.exec(source);
|
|
34
|
+
if (!match || !match[5].trim()) return undefined;
|
|
35
|
+
const optional = Boolean(match[2] || match[4]);
|
|
36
|
+
return {
|
|
37
|
+
id: match[3] ?? (variantName ? `variant_field_${variantName}_${match[1]}` : match[1]),
|
|
38
|
+
name: match[1],
|
|
39
|
+
...(parseTypeExpression ? { type: parseTypeExpression(match[5].trim()) } : {}),
|
|
40
|
+
...(optional ? { optional: true } : {})
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function splitTopLevelCommaList(source) {
|
|
45
|
+
const parts = [];
|
|
46
|
+
let depth = 0;
|
|
47
|
+
let start = 0;
|
|
48
|
+
for (let index = 0; index < source.length; index++) {
|
|
49
|
+
const char = source[index];
|
|
50
|
+
if (char === '<') depth++;
|
|
51
|
+
if (char === '>') depth = Math.max(0, depth - 1);
|
|
52
|
+
if (char === ',' && depth === 0) {
|
|
53
|
+
parts.push(source.slice(start, index).trim());
|
|
54
|
+
start = index + 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
parts.push(source.slice(start).trim());
|
|
58
|
+
return parts;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stripLeadingVariantId(rest) {
|
|
62
|
+
return rest.replace(/^\s*@id\(\s*["'][^"']+["']\s*\)/, '').trim();
|
|
63
|
+
}
|
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.57",
|
|
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-return-smoke.mjs && node test/action-else-smoke.mjs && node test/action-match-smoke.mjs && node test/action-for-in-smoke.mjs && node test/action-repeat-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/type-variant-payload-smoke.mjs && node test/action-body-smoke.mjs && node test/action-structured-literals-smoke.mjs && node test/action-return-smoke.mjs && node test/action-else-smoke.mjs && node test/action-match-smoke.mjs && node test/action-for-in-smoke.mjs && node test/action-repeat-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",
|