@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 CHANGED
@@ -1,4 +1,4 @@
1
- import { actionNode, capabilityNode, createDocument, effectNode, entityNode, externNode, latticeNode, migrationNode, stateNode, targetNode, typeNode } from '@shapeshift-labs/frontier-lang-kernel';
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
- const name = nameFrom(block.header);
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: idFrom(rest, `${config.idPrefix}_${safeId(normalizedRowKind)}_${safeId(name)}`),
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: true
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.62",
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",