@shapeshift-labs/frontier-lang-parser 0.3.62 → 0.3.64
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/index.js +4 -59
- package/dist/member-records.js +74 -0
- package/dist/source-syntax-children.js +77 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { actionNode, capabilityNode, createDocument, effectNode,
|
|
1
|
+
import { actionNode, capabilityNode, createDocument, effectNode, externNode, latticeNode, migrationNode, targetNode, typeNode } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
2
|
import { readActionBodyRecords, stripNestedBlocks } from './action-body.js';
|
|
3
3
|
import { parseConstraintSpaceBlock } from './constraint-space.js';
|
|
4
4
|
import { parseConversionBlock } from './conversion.js';
|
|
@@ -8,6 +8,7 @@ import { parseDecisionGraphBlock } from './decision-graph.js';
|
|
|
8
8
|
import { parseDialectRegistryBlock } from './dialect-registry.js';
|
|
9
9
|
import { parseInterlinguaBlock } from './interlingua.js';
|
|
10
10
|
import { createParsedMetadata } from './metadata.js';
|
|
11
|
+
import { parseEntityBlock, parseStateBlock, readTypeFields } from './member-records.js';
|
|
11
12
|
import { parseSemanticOperationsBlock } from './operations.js';
|
|
12
13
|
import { parsePackageManifestBlock } from './package-manifest.js';
|
|
13
14
|
import { parseParadigmBlock } from './paradigm.js';
|
|
@@ -85,40 +86,8 @@ function readBlocks(source, options) {
|
|
|
85
86
|
}
|
|
86
87
|
function idFrom(header, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1] ?? fallback; }
|
|
87
88
|
function nameFrom(header) { return /^([A-Za-z_$][\w$]*)/.exec(header)?.[1] ?? 'Unnamed'; }
|
|
88
|
-
function parseEntity(block) {
|
|
89
|
-
|
|
90
|
-
const fields = [];
|
|
91
|
-
const fieldRe = /^\s*([A-Za-z_$][\w$]*)(?:\s+@id\(\s*["']([^"']+)["']\s*\))?\s*:\s*([^@{\n]+)([^\n{]*)(?:\{([^}]*)\})?/gm;
|
|
92
|
-
let m;
|
|
93
|
-
while ((m = fieldRe.exec(block.body))) {
|
|
94
|
-
const mergeText = (m[4] ?? '') + ' ' + (m[5] ?? '');
|
|
95
|
-
fields.push({
|
|
96
|
-
id: m[2] ?? `field_${name}_${m[1]}`,
|
|
97
|
-
name: m[1],
|
|
98
|
-
type: parseTypeExpression(m[3].trim()),
|
|
99
|
-
key: /@key/.test(m[4] ?? ''),
|
|
100
|
-
merge: parseMerge(mergeText),
|
|
101
|
-
semantic: parseSemantic(mergeText)
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
return entityNode({ id: idFrom(block.header, `ent_${name}`), name, fields });
|
|
105
|
-
}
|
|
106
|
-
function parseState(block) {
|
|
107
|
-
const name = nameFrom(block.header);
|
|
108
|
-
const collections = [];
|
|
109
|
-
const re = /^\s*([A-Za-z_$][\w$]*)(?:\s+@id\(\s*["']([^"']+)["']\s*\))?\s*:\s*([^@{\n]+)(?:\{([^}]*)\})?/gm;
|
|
110
|
-
let m;
|
|
111
|
-
while ((m = re.exec(block.body))) {
|
|
112
|
-
collections.push({
|
|
113
|
-
id: m[2] ?? `collection_${name}_${m[1]}`,
|
|
114
|
-
name: m[1],
|
|
115
|
-
type: parseTypeExpression(m[3].trim()),
|
|
116
|
-
merge: parseMerge(m[4] ?? ''),
|
|
117
|
-
semantic: parseSemantic(m[4] ?? '')
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
return stateNode({ id: idFrom(block.header, `state_${name}`), name, collections });
|
|
121
|
-
}
|
|
89
|
+
function parseEntity(block) { return parseEntityBlock(block, idFrom, nameFrom); }
|
|
90
|
+
function parseState(block) { return parseStateBlock(block, idFrom, nameFrom); }
|
|
122
91
|
function parseAction(block) {
|
|
123
92
|
const name = nameFrom(block.header);
|
|
124
93
|
const topLevelBody = stripNestedBlocks('body', block.body);
|
|
@@ -277,30 +246,6 @@ function readChangeRecords(body) {
|
|
|
277
246
|
}
|
|
278
247
|
return changes;
|
|
279
248
|
}
|
|
280
|
-
function parseMerge(text) {
|
|
281
|
-
const kind = /merge\s+([A-Za-z][\w-]*)/.exec(text)?.[1];
|
|
282
|
-
if (!kind) return undefined;
|
|
283
|
-
const law = /law\s+([A-Za-z][\w-]*)/.exec(text)?.[1];
|
|
284
|
-
const laws = /laws\s+([A-Za-z][\w-]*(?:\s*,\s*[A-Za-z][\w-]*)*)/.exec(text)?.[1]?.split(',').map((item) => item.trim()).filter(Boolean);
|
|
285
|
-
const latticeId = /lattice\s+([A-Za-z_$][\w$-]*)/.exec(text)?.[1];
|
|
286
|
-
return { kind, law, laws, latticeId };
|
|
287
|
-
}
|
|
288
|
-
function parseSemantic(text) {
|
|
289
|
-
const crdtType = /crdt\s+([A-Za-z][\w-]*)/.exec(text)?.[1];
|
|
290
|
-
const latticeId = /lattice\s+([A-Za-z_$][\w$-]*)/.exec(text)?.[1];
|
|
291
|
-
if (crdtType) return { kind: 'crdt', latticeId, crdt: { type: crdtType } };
|
|
292
|
-
if (latticeId) return { kind: 'lattice', latticeId };
|
|
293
|
-
return undefined;
|
|
294
|
-
}
|
|
295
|
-
function readTypeFields(body) {
|
|
296
|
-
const fields = [];
|
|
297
|
-
const re = /^\s*([A-Za-z_$][\w$]*)(?:\s+@id\(\s*["']([^"']+)["']\s*\))?\s*:\s*([^\n]+)/gm;
|
|
298
|
-
let match;
|
|
299
|
-
while ((match = re.exec(body))) {
|
|
300
|
-
fields.push({ id: match[2] ?? `type_field_${match[1]}`, name: match[1], type: parseTypeExpression(match[3].trim()) });
|
|
301
|
-
}
|
|
302
|
-
return fields.length ? fields : undefined;
|
|
303
|
-
}
|
|
304
249
|
function readVariants(body) {
|
|
305
250
|
const variants = [];
|
|
306
251
|
const seenNames = new Set();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { entityNode, stateNode } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { parseTypeExpression } from './type-expressions.js';
|
|
3
|
+
|
|
4
|
+
export function parseEntityBlock(block, idFrom, nameFrom) {
|
|
5
|
+
const name = nameFrom(block.header);
|
|
6
|
+
const fields = [], seen = identitySets();
|
|
7
|
+
const fieldRe = /^\s*([A-Za-z_$][\w$]*)(?:\s+@id\(\s*["']([^"']+)["']\s*\))?\s*:\s*([^@{\n]+)([^\n{]*)(?:\{([^}]*)\})?/gm;
|
|
8
|
+
let m;
|
|
9
|
+
while ((m = fieldRe.exec(block.body))) {
|
|
10
|
+
const mergeText = (m[4] ?? '') + ' ' + (m[5] ?? '');
|
|
11
|
+
appendUnique(fields, seen, {
|
|
12
|
+
id: m[2] ?? `field_${name}_${m[1]}`,
|
|
13
|
+
name: m[1],
|
|
14
|
+
type: parseTypeExpression(m[3].trim()),
|
|
15
|
+
key: /@key/.test(m[4] ?? ''),
|
|
16
|
+
merge: parseMerge(mergeText),
|
|
17
|
+
semantic: parseSemantic(mergeText)
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return entityNode({ id: idFrom(block.header, `ent_${name}`), name, fields });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseStateBlock(block, idFrom, nameFrom) {
|
|
24
|
+
const name = nameFrom(block.header);
|
|
25
|
+
const collections = [], seen = identitySets();
|
|
26
|
+
const re = /^\s*([A-Za-z_$][\w$]*)(?:\s+@id\(\s*["']([^"']+)["']\s*\))?\s*:\s*([^@{\n]+)(?:\{([^}]*)\})?/gm;
|
|
27
|
+
let m;
|
|
28
|
+
while ((m = re.exec(block.body))) {
|
|
29
|
+
appendUnique(collections, seen, {
|
|
30
|
+
id: m[2] ?? `collection_${name}_${m[1]}`,
|
|
31
|
+
name: m[1],
|
|
32
|
+
type: parseTypeExpression(m[3].trim()),
|
|
33
|
+
merge: parseMerge(m[4] ?? ''),
|
|
34
|
+
semantic: parseSemantic(m[4] ?? '')
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return stateNode({ id: idFrom(block.header, `state_${name}`), name, collections });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function readTypeFields(body) {
|
|
41
|
+
const fields = [], seen = identitySets();
|
|
42
|
+
const re = /^\s*([A-Za-z_$][\w$]*)(?:\s+@id\(\s*["']([^"']+)["']\s*\))?\s*:\s*([^\n]+)/gm;
|
|
43
|
+
let match;
|
|
44
|
+
while ((match = re.exec(body))) {
|
|
45
|
+
appendUnique(fields, seen, { id: match[2] ?? `type_field_${match[1]}`, name: match[1], type: parseTypeExpression(match[3].trim()) });
|
|
46
|
+
}
|
|
47
|
+
return fields.length ? fields : undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function identitySets() {
|
|
51
|
+
return { names: new Set(), ids: new Set() };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function appendUnique(records, seen, record) {
|
|
55
|
+
if (seen.names.has(record.name) || seen.ids.has(record.id)) return;
|
|
56
|
+
seen.names.add(record.name); seen.ids.add(record.id); records.push(record);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseMerge(text) {
|
|
60
|
+
const kind = /merge\s+([A-Za-z][\w-]*)/.exec(text)?.[1];
|
|
61
|
+
if (!kind) return undefined;
|
|
62
|
+
const law = /law\s+([A-Za-z][\w-]*)/.exec(text)?.[1];
|
|
63
|
+
const laws = /laws\s+([A-Za-z][\w-]*(?:\s*,\s*[A-Za-z][\w-]*)*)/.exec(text)?.[1]?.split(',').map((item) => item.trim()).filter(Boolean);
|
|
64
|
+
const latticeId = /lattice\s+([A-Za-z_$][\w$-]*)/.exec(text)?.[1];
|
|
65
|
+
return { kind, law, laws, latticeId };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function parseSemantic(text) {
|
|
69
|
+
const crdtType = /crdt\s+([A-Za-z][\w-]*)/.exec(text)?.[1];
|
|
70
|
+
const latticeId = /lattice\s+([A-Za-z_$][\w$-]*)/.exec(text)?.[1];
|
|
71
|
+
if (crdtType) return { kind: 'crdt', latticeId, crdt: { type: crdtType } };
|
|
72
|
+
if (latticeId) return { kind: 'lattice', latticeId };
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
@@ -15,6 +15,22 @@ export function readSourceSyntaxChildren(source, block, options = {}) {
|
|
|
15
15
|
children = readConversionSyntaxChildren(source, block, options);
|
|
16
16
|
} else if (block.kind === 'type') {
|
|
17
17
|
children = readTypeSyntaxChildren(source, block, options);
|
|
18
|
+
} else if (block.kind === 'entity') {
|
|
19
|
+
children = readMemberSyntaxChildren(source, block, options, {
|
|
20
|
+
kind: 'entityField',
|
|
21
|
+
rowKind: 'field',
|
|
22
|
+
idPrefix: `field_${safeId(block.name)}_`,
|
|
23
|
+
duplicateNameReason: 'duplicate-entity-field-name',
|
|
24
|
+
duplicateIdReason: 'duplicate-entity-field-id'
|
|
25
|
+
});
|
|
26
|
+
} else if (block.kind === 'state') {
|
|
27
|
+
children = readMemberSyntaxChildren(source, block, options, {
|
|
28
|
+
kind: 'stateCollection',
|
|
29
|
+
rowKind: 'collection',
|
|
30
|
+
idPrefix: `collection_${safeId(block.name)}_`,
|
|
31
|
+
duplicateNameReason: 'duplicate-state-collection-name',
|
|
32
|
+
duplicateIdReason: 'duplicate-state-collection-id'
|
|
33
|
+
});
|
|
18
34
|
} else {
|
|
19
35
|
const rowConfig = ROW_SYNTAX_CONFIG[block.kind];
|
|
20
36
|
if (rowConfig) children = readGenericRowSyntaxChildren(source, block, options, rowConfig);
|
|
@@ -23,7 +39,13 @@ export function readSourceSyntaxChildren(source, block, options = {}) {
|
|
|
23
39
|
}
|
|
24
40
|
|
|
25
41
|
function readTypeSyntaxChildren(source, block, options) {
|
|
26
|
-
const children =
|
|
42
|
+
const children = readMemberSyntaxChildren(source, block, options, {
|
|
43
|
+
kind: 'typeField',
|
|
44
|
+
rowKind: 'typeField',
|
|
45
|
+
idPrefix: 'type_field_',
|
|
46
|
+
duplicateNameReason: 'duplicate-type-field-name',
|
|
47
|
+
duplicateIdReason: 'duplicate-type-field-id'
|
|
48
|
+
});
|
|
27
49
|
const seenVariantNames = new Set();
|
|
28
50
|
const seenVariantIds = new Set();
|
|
29
51
|
for (const line of readBodyLines(source, block)) {
|
|
@@ -72,6 +94,46 @@ function readTypeSyntaxChildren(source, block, options) {
|
|
|
72
94
|
return children;
|
|
73
95
|
}
|
|
74
96
|
|
|
97
|
+
function readMemberSyntaxChildren(source, block, options, config) {
|
|
98
|
+
const children = [];
|
|
99
|
+
const seenNames = new Set(), seenIds = new Set();
|
|
100
|
+
for (const line of readBodyLines(source, block)) {
|
|
101
|
+
if (!line.text || line.text.startsWith('#')) continue;
|
|
102
|
+
const member = /^([A-Za-z_$][\w$]*)(?:\s+@id\(\s*["']([^"']+)["']\s*\))?\s*:\s*(.+)$/.exec(line.text);
|
|
103
|
+
if (!member) continue;
|
|
104
|
+
const [, name, explicitId] = member;
|
|
105
|
+
const id = explicitId ?? `${config.idPrefix}${safeId(name)}`;
|
|
106
|
+
let recognized = true, reason;
|
|
107
|
+
if (seenNames.has(name)) { recognized = false; reason = config.duplicateNameReason; }
|
|
108
|
+
if (recognized && seenIds.has(id)) { recognized = false; reason = config.duplicateIdReason; }
|
|
109
|
+
if (recognized) { seenNames.add(name); seenIds.add(id); }
|
|
110
|
+
children.push(memberChild(source, block, options, line, { ...config, name, id, recognized, reason }));
|
|
111
|
+
}
|
|
112
|
+
return children;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function memberChild(source, block, options, line, input) {
|
|
116
|
+
return cleanRecord({
|
|
117
|
+
kind: input.kind,
|
|
118
|
+
rowKind: input.rowKind,
|
|
119
|
+
normalizedRowKind: input.rowKind,
|
|
120
|
+
name: input.name,
|
|
121
|
+
id: input.id,
|
|
122
|
+
header: line.text,
|
|
123
|
+
startOffset: line.startOffset,
|
|
124
|
+
endOffset: line.endOffset,
|
|
125
|
+
location: sourcePosition(source, line.startOffset),
|
|
126
|
+
parentKind: block.kind,
|
|
127
|
+
parentId: block.id,
|
|
128
|
+
parentName: block.name,
|
|
129
|
+
moduleId: block.moduleId,
|
|
130
|
+
moduleName: block.moduleName,
|
|
131
|
+
sourceSpan: sourceSpan(source, block, line.startOffset, line.endOffset, options),
|
|
132
|
+
recognized: input.recognized,
|
|
133
|
+
reason: input.reason
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
75
137
|
function readConversionSyntaxChildren(source, block, options) {
|
|
76
138
|
const children = [];
|
|
77
139
|
for (const line of readBodyLines(source, block)) {
|
|
@@ -160,6 +222,7 @@ function conversionChild(source, block, options, line, child) {
|
|
|
160
222
|
|
|
161
223
|
function readGenericRowSyntaxChildren(source, block, options, config) {
|
|
162
224
|
const children = [];
|
|
225
|
+
const seenIds = new Set();
|
|
163
226
|
const rowPattern = new RegExp('^([A-Za-z_$][\\w$-]*)\\s+' + ROW_NAME_PATTERN + '(.*)$');
|
|
164
227
|
for (const line of readBodyLines(source, block)) {
|
|
165
228
|
if (!line.text || line.text.startsWith('#')) continue;
|
|
@@ -168,12 +231,22 @@ function readGenericRowSyntaxChildren(source, block, options, config) {
|
|
|
168
231
|
const [, rowKind, name, rest] = row;
|
|
169
232
|
if (!config.rowKinds.has(rowKind)) continue;
|
|
170
233
|
const normalizedRowKind = config.normalize?.(rowKind) ?? rowKind;
|
|
234
|
+
const id = idFrom(rest, `${config.idPrefix}_${safeId(normalizedRowKind)}_${safeId(name)}`);
|
|
235
|
+
let recognized = true;
|
|
236
|
+
let reason;
|
|
237
|
+
if (seenIds.has(id)) {
|
|
238
|
+
recognized = false;
|
|
239
|
+
reason = 'duplicate-generic-row-id';
|
|
240
|
+
}
|
|
241
|
+
if (recognized) {
|
|
242
|
+
seenIds.add(id);
|
|
243
|
+
}
|
|
171
244
|
children.push(cleanRecord({
|
|
172
245
|
kind: config.childKind,
|
|
173
246
|
rowKind,
|
|
174
247
|
normalizedRowKind,
|
|
175
248
|
name,
|
|
176
|
-
id
|
|
249
|
+
id,
|
|
177
250
|
header: line.text,
|
|
178
251
|
startOffset: line.startOffset,
|
|
179
252
|
endOffset: line.endOffset,
|
|
@@ -184,7 +257,8 @@ function readGenericRowSyntaxChildren(source, block, options, config) {
|
|
|
184
257
|
moduleId: block.moduleId,
|
|
185
258
|
moduleName: block.moduleName,
|
|
186
259
|
sourceSpan: sourceSpan(source, block, line.startOffset, line.endOffset, options),
|
|
187
|
-
recognized
|
|
260
|
+
recognized,
|
|
261
|
+
reason
|
|
188
262
|
}));
|
|
189
263
|
}
|
|
190
264
|
return children;
|
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.64",
|
|
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/type-variant-payload-smoke.mjs && node test/type-generic-ref-smoke.mjs && node test/type-parameter-constraints-smoke.mjs && node test/type-structural-expression-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",
|
|
25
|
+
"test": "npm run build && node test/smoke.mjs && node test/member-identity-smoke.mjs && node test/type-variant-payload-smoke.mjs && node test/type-generic-ref-smoke.mjs && node test/type-parameter-constraints-smoke.mjs && node test/type-structural-expression-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",
|