@shapeshift-labs/frontier-lang-parser 0.3.61 → 0.3.63

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,38 +246,19 @@ 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 = [];
251
+ const seenNames = new Set();
252
+ const seenIds = new Set();
306
253
  const re = /^\s*variant\s+([A-Za-z_$][\w$]*)(.*)$/gm;
307
254
  let match;
308
255
  while ((match = re.exec(body))) {
309
256
  const fields = readVariantPayloadFields(match[2] ?? '', match[1], parseTypeExpression);
310
257
  if (fields === null) continue;
311
258
  const id = /@id\(\s*["']([^"']+)["']\s*\)/.exec(match[2] ?? '')?.[1];
259
+ const variantId = id ?? `type_variant_${safeId(match[1])}`;
260
+ if (seenNames.has(match[1]) || seenIds.has(variantId)) continue;
261
+ seenNames.add(match[1]); seenIds.add(variantId);
312
262
  variants.push({ ...(id ? { id } : {}), name: match[1], ...(fields?.length ? { fields } : {}) });
313
263
  }
314
264
  return variants.length ? variants : undefined;
@@ -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,19 +39,42 @@ 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
+ });
49
+ const seenVariantNames = new Set();
50
+ const seenVariantIds = new Set();
27
51
  for (const line of readBodyLines(source, block)) {
28
52
  if (!line.text || line.text.startsWith('#')) continue;
29
53
  const variant = /^variant\s+([A-Za-z_$][\w$]*)(.*)$/.exec(line.text);
30
54
  if (!variant) continue;
31
55
  const [, name, rest] = variant;
32
56
  const payload = inspectVariantPayload(rest ?? '', inspectTypeExpressionSyntax);
57
+ const id = idFrom(rest, `type_variant_${safeId(name)}`);
58
+ let recognized = payload.ok;
59
+ let reason = payload.ok ? undefined : payload.reason;
60
+ if (recognized && seenVariantNames.has(name)) {
61
+ recognized = false;
62
+ reason = 'duplicate-type-variant-name';
63
+ }
64
+ if (recognized && seenVariantIds.has(id)) {
65
+ recognized = false;
66
+ reason = 'duplicate-type-variant-id';
67
+ }
68
+ if (recognized) {
69
+ seenVariantNames.add(name);
70
+ seenVariantIds.add(id);
71
+ }
33
72
  children.push(cleanRecord({
34
73
  kind: 'typeVariant',
35
74
  rowKind: 'variant',
36
75
  normalizedRowKind: 'variant',
37
76
  name,
38
- id: idFrom(rest, `type_variant_${safeId(name)}`),
77
+ id,
39
78
  header: line.text,
40
79
  startOffset: line.startOffset,
41
80
  endOffset: line.endOffset,
@@ -46,8 +85,8 @@ function readTypeSyntaxChildren(source, block, options) {
46
85
  moduleId: block.moduleId,
47
86
  moduleName: block.moduleName,
48
87
  sourceSpan: sourceSpan(source, block, line.startOffset, line.endOffset, options),
49
- recognized: payload.ok,
50
- reason: payload.ok ? undefined : payload.reason,
88
+ recognized,
89
+ reason,
51
90
  fieldCount: payload.fieldCount,
52
91
  fieldIds: payload.fieldIds
53
92
  }));
@@ -55,6 +94,46 @@ function readTypeSyntaxChildren(source, block, options) {
55
94
  return children;
56
95
  }
57
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
+
58
137
  function readConversionSyntaxChildren(source, block, options) {
59
138
  const children = [];
60
139
  for (const line of readBodyLines(source, block)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.61",
3
+ "version": "0.3.63",
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",