@shapeshift-labs/frontier-lang-parser 0.3.56 → 0.3.58

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
@@ -16,6 +16,8 @@ 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 { parseOptionalTypeExpression, parseTypeExpression } from './type-expressions.js';
20
+ import { readVariantPayloadFields } from './type-variants.js';
19
21
  import { parseViewBlock } from './view.js';
20
22
  import { FrontierSourceBlockKinds, readFrontierSourceBlocks } from './source-syntax-report.js';
21
23
  export { FrontierSourceBlockKinds, inspectFrontierSourceSyntax } from './source-syntax-report.js';
@@ -166,15 +168,7 @@ function parseCapability(block) {
166
168
  function parseType(block) {
167
169
  const name = nameFrom(block.header);
168
170
  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
- });
171
+ 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
172
  }
179
173
  function parseExtern(block) {
180
174
  const name = nameFrom(block.header);
@@ -287,15 +281,6 @@ function parseSemantic(text) {
287
281
  if (latticeId) return { kind: 'lattice', latticeId };
288
282
  return undefined;
289
283
  }
290
- function parseOptionalTypeExpression(value) { return value ? parseTypeExpression(value.trim()) : undefined; }
291
- function parseTypeExpression(value) {
292
- const text = value.trim();
293
- if (/^Set<.+>$/.test(text)) return { kind: 'set', item: parseTypeExpression(text.slice(4, -1)) };
294
- if (/^List<.+>$/.test(text)) return { kind: 'list', item: parseTypeExpression(text.slice(5, -1)) };
295
- const map = /^Map<(.+),\s*(.+)>$/.exec(text);
296
- if (map) return { kind: 'map', key: parseTypeExpression(map[1]), value: parseTypeExpression(map[2]) };
297
- return text;
298
- }
299
284
  function readTypeParameters(header) {
300
285
  return /<([^>]+)>/.exec(header)?.[1]?.split(',').map((item) => item.trim()).filter(Boolean);
301
286
  }
@@ -310,8 +295,13 @@ function readTypeFields(body) {
310
295
  }
311
296
  function readVariants(body) {
312
297
  const variants = [];
313
- const re = /^\s*variant\s+([A-Za-z_$][\w$]*)(?:\s+@id\(\s*["']([^"']+)["']\s*\))?/gm;
298
+ const re = /^\s*variant\s+([A-Za-z_$][\w$]*)(.*)$/gm;
314
299
  let match;
315
- while ((match = re.exec(body))) variants.push({ id: match[2], name: match[1] });
300
+ while ((match = re.exec(body))) {
301
+ const fields = readVariantPayloadFields(match[2] ?? '', match[1], parseTypeExpression);
302
+ if (fields === null) continue;
303
+ const id = /@id\(\s*["']([^"']+)["']\s*\)/.exec(match[2] ?? '')?.[1];
304
+ variants.push({ ...(id ? { id } : {}), name: match[1], ...(fields?.length ? { fields } : {}) });
305
+ }
316
306
  return variants.length ? variants : undefined;
317
307
  }
@@ -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,33 @@
1
+ import { splitTopLevelCommaList } from './type-variants.js';
2
+
3
+ export function parseOptionalTypeExpression(value) {
4
+ return value ? parseTypeExpression(value.trim()) : undefined;
5
+ }
6
+
7
+ export function parseTypeExpression(value) {
8
+ const text = value.trim();
9
+ const application = readTypeApplication(text);
10
+ if (!application) return text;
11
+ const args = splitTopLevelCommaList(application.body);
12
+ if (args.some((arg) => !arg)) return text;
13
+ const parsedArgs = args.map(parseTypeExpression);
14
+ if (application.name === 'Set' && parsedArgs.length === 1) return { kind: 'set', item: parsedArgs[0] };
15
+ if (application.name === 'List' && parsedArgs.length === 1) return { kind: 'list', item: parsedArgs[0] };
16
+ if (application.name === 'Map' && parsedArgs.length === 2) return { kind: 'map', key: parsedArgs[0], value: parsedArgs[1] };
17
+ return { kind: 'ref', name: application.name, args: parsedArgs };
18
+ }
19
+
20
+ function readTypeApplication(text) {
21
+ const match = /^([A-Za-z_$][\w$]*)</.exec(text);
22
+ if (!match || !text.endsWith('>')) return undefined;
23
+ let depth = 0;
24
+ for (let index = match[1].length; index < text.length; index++) {
25
+ const char = text[index];
26
+ if (char === '<') depth++;
27
+ if (char === '>') depth--;
28
+ if (depth < 0) return undefined;
29
+ if (depth === 0 && index !== text.length - 1) return undefined;
30
+ }
31
+ if (depth !== 0) return undefined;
32
+ return { name: match[1], body: text.slice(match[1].length + 1, -1).trim() };
33
+ }
@@ -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
+ export 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/dist/view.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { viewNode } from '@shapeshift-labs/frontier-lang-kernel';
2
2
  import { readFrontierNestedBlocks } from './source-syntax-report.js';
3
+ import { parseOptionalTypeExpression, parseTypeExpression } from './type-expressions.js';
3
4
 
4
5
  export function parseViewBlock(block) {
5
6
  const name = nameFrom(block.header);
@@ -30,7 +31,7 @@ function readViewEvents(body) {
30
31
  const events = [];
31
32
  const re = /^\s*event\s+([A-Za-z_$][\w$.-]*)(?:\s+@id\(\s*["']([^"']+)["']\s*\))?([^\n]*)$/gm;
32
33
  let match;
33
- while ((match = re.exec(body))) events.push({ id: match[2] ?? `view_event_${match[1]}`, name: match[1], action: readInlineWord('action', match[3]), input: parseOptionalTypeExpression(readInlineWord('input', match[3])) });
34
+ while ((match = re.exec(body))) events.push({ id: match[2] ?? `view_event_${match[1]}`, name: match[1], action: readInlineWord('action', match[3]), input: parseOptionalTypeExpression(readInlineType('input', match[3])) });
34
35
  return events;
35
36
  }
36
37
 
@@ -95,15 +96,7 @@ function readList(label, body) { const line = new RegExp('^\\s*' + label + '\\s+
95
96
  function readLine(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\n]+)', 'm').exec(body)?.[1]?.trim(); }
96
97
  function readQuotedLine(label, body) { return new RegExp(`^\\s*${label}\\s+["']([^"']+)["']`, 'm').exec(body)?.[1]; }
97
98
  function readInlineWord(label, text = '') { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
99
+ function readInlineType(label, text = '') { return new RegExp('(?:^|\\s)' + label + '\\s+(.+)$').exec(text)?.[1]?.trim(); }
98
100
  function readRenderValue(value) { const quoted = /^["']([^"']+)["']$/.exec(value.trim()); return quoted ? { value: quoted[1] } : { expression: value.trim() }; }
99
101
  function compactRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)); }
100
102
  function uniqueStrings(values) { const result = []; for (const value of values) if (value && !result.includes(value)) result.push(value); return result.length ? result : undefined; }
101
- function parseOptionalTypeExpression(value) { return value ? parseTypeExpression(value.trim()) : undefined; }
102
- function parseTypeExpression(value) {
103
- const text = value.trim();
104
- if (/^Set<.+>$/.test(text)) return { kind: 'set', item: parseTypeExpression(text.slice(4, -1)) };
105
- if (/^List<.+>$/.test(text)) return { kind: 'list', item: parseTypeExpression(text.slice(5, -1)) };
106
- const map = /^Map<(.+),\s*(.+)>$/.exec(text);
107
- if (map) return { kind: 'map', key: parseTypeExpression(map[1]), value: parseTypeExpression(map[2]) };
108
- return text;
109
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.56",
3
+ "version": "0.3.58",
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-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/type-variant-payload-smoke.mjs && node test/type-generic-ref-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",