@shapeshift-labs/frontier-lang-parser 0.3.40 → 0.3.42

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/README.md CHANGED
@@ -81,6 +81,7 @@ The published Frontier package family is generated from one shared package catal
81
81
  - [`@shapeshift-labs/frontier-trace`](https://www.npmjs.com/package/@shapeshift-labs/frontier-trace): Serializable traces, spans, events, causal links, W3C trace context helpers, timeline/resource/path queries, critical-path analysis, registry graph output, JSONL/proof helpers, Chrome trace export, and redaction for app-wide feature observability.
82
82
  - [`@shapeshift-labs/frontier-manifest`](https://www.npmjs.com/package/@shapeshift-labs/frontier-manifest): Build/static feature manifests for owners, routes, actions, states, migrations, tests, source files, assets, resources, tasks, dependency metadata, registry graph output, feature maps, JSONL export, and impact queries.
83
83
  - [`@shapeshift-labs/frontier-view`](https://www.npmjs.com/package/@shapeshift-labs/frontier-view): Renderer-neutral view manifests, type defaults, validation frames, action bindings, visual channels, virtual/LOD hints, and data-to-representation mapping for Frontier apps.
84
+ - [`@shapeshift-labs/frontier-collapse`](https://www.npmjs.com/package/@shapeshift-labs/frontier-collapse): Constraint-collapse and projection admission records for one Frontier program becoming many valid shapes, including identity, behavior, runtime, proof, explicit projection losses, admitted projections, rejected shapes, and target contracts for React, SwiftUI, CLI, worker routes, plugin mounts, native adapters, and local-first views.
84
85
  - [`@shapeshift-labs/frontier-icons`](https://www.npmjs.com/package/@shapeshift-labs/frontier-icons): Renderer-neutral icon records, icon sets, lookup aliases, SVG frames, string rendering, and registry evidence for Frontier apps.
85
86
  - [`@shapeshift-labs/frontier-design`](https://www.npmjs.com/package/@shapeshift-labs/frontier-design): Renderer-neutral design-system tokens, semantic roles, recipes, target style frames, CSS variable output, and registry graph evidence for Frontier apps.
86
87
  - [`@shapeshift-labs/frontier-canvas`](https://www.npmjs.com/package/@shapeshift-labs/frontier-canvas): Renderer-neutral infinite canvas surfaces for Frontier apps, including camera and viewport math, pan/zoom plans, grid materialization, snapping, hit testing, selection handles, extensible tool dispatch, frame records, registry graph output, and impact/proof helpers.
@@ -88,7 +89,8 @@ The published Frontier package family is generated from one shared package catal
88
89
  - [`@shapeshift-labs/frontier-dnd`](https://www.npmjs.com/package/@shapeshift-labs/frontier-dnd): Renderer-neutral drag-and-drop sessions, sensor descriptors, collision ranking, drop planning, reorder patches, state partitioning, and registry evidence for Frontier apps.
89
90
  - [`@shapeshift-labs/frontier-dom`](https://www.npmjs.com/package/@shapeshift-labs/frontier-dom): Patch-native DOM and host renderer bindings, manifest hydration, JSX runtime/compiler helpers, SSR, devtools, and logging bridges.
90
91
  - [`@shapeshift-labs/frontier-playwright`](https://www.npmjs.com/package/@shapeshift-labs/frontier-playwright): Playwright/headless automation probes for Frontier state, DOM, devtools, marks, and timeline queries.
91
- - [`@shapeshift-labs/frontier-annotations`](https://www.npmjs.com/package/@shapeshift-labs/frontier-annotations): Browser harness DOM annotation overlay, selected-node targeting records, CSS/source-hint capture, AST/source graph context ranking, and Codex/frontier-swarm task planning for AI-operable UI fixes.
92
+ - [`@shapeshift-labs/frontier-annotations`](https://www.npmjs.com/package/@shapeshift-labs/frontier-annotations): Browser harness DOM annotation overlay, selected-node targeting records, click geometry, optional image media refs, CSS/source-hint capture, AST/source graph context ranking, and Codex/frontier-swarm task planning for AI-operable UI fixes.
93
+ - [`@shapeshift-labs/frontier-annotations-harness`](https://www.npmjs.com/package/@shapeshift-labs/frontier-annotations-harness): Node browser annotation harness server and CLI for injecting Frontier annotation threads, capturing selected-element screenshots and canvas click crops, collecting source context, writing Loom/frontier-swarm Codex task artifacts, and optionally starting swarm runs.
92
94
  - [`@shapeshift-labs/frontier-test`](https://www.npmjs.com/package/@shapeshift-labs/frontier-test): Serializable test/spec evidence manifests for Frontier apps, including fixtures, commands, expected patches/effects/routes/policies, coverage declarations, run plans, run records, report adapters, replay proofs, fuzzers, benchmarks, registry graph output, and impact queries.
93
95
  - [`@shapeshift-labs/frontier-fixtures`](https://www.npmjs.com/package/@shapeshift-labs/frontier-fixtures): Deterministic fixture and scenario generation for Frontier apps, including schema-valid sample state, related entity collections, actor personas, route states, replay-verified patch streams, event records, JSONL bundles, and evidence summaries.
94
96
  - [`@shapeshift-labs/frontier-component-preview`](https://www.npmjs.com/package/@shapeshift-labs/frontier-component-preview): Frontier-native component preview books, generated preview manifests, stateful variants, Vite virtual modules, standalone browser preview shells, inspector bridges, and preview harness evidence for Frontier apps.
@@ -186,6 +188,7 @@ Package source repositories:
186
188
  - [`siliconjungle/-shapeshift-labs-frontier-trace`](https://github.com/siliconjungle/-shapeshift-labs-frontier-trace)
187
189
  - [`siliconjungle/-shapeshift-labs-frontier-manifest`](https://github.com/siliconjungle/-shapeshift-labs-frontier-manifest)
188
190
  - [`siliconjungle/-shapeshift-labs-frontier-view`](https://github.com/siliconjungle/-shapeshift-labs-frontier-view)
191
+ - [`siliconjungle/-shapeshift-labs-frontier-collapse`](https://github.com/siliconjungle/-shapeshift-labs-frontier-collapse)
189
192
  - [`siliconjungle/-shapeshift-labs-frontier-icons`](https://github.com/siliconjungle/-shapeshift-labs-frontier-icons)
190
193
  - [`siliconjungle/-shapeshift-labs-frontier-design`](https://github.com/siliconjungle/-shapeshift-labs-frontier-design)
191
194
  - [`siliconjungle/-shapeshift-labs-frontier-canvas`](https://github.com/siliconjungle/-shapeshift-labs-frontier-canvas)
@@ -194,6 +197,7 @@ Package source repositories:
194
197
  - [`siliconjungle/-shapeshift-labs-frontier-dom`](https://github.com/siliconjungle/-shapeshift-labs-frontier-dom)
195
198
  - [`siliconjungle/-shapeshift-labs-frontier-playwright`](https://github.com/siliconjungle/-shapeshift-labs-frontier-playwright)
196
199
  - [`siliconjungle/-shapeshift-labs-frontier-annotations`](https://github.com/siliconjungle/-shapeshift-labs-frontier-annotations)
200
+ - [`siliconjungle/-shapeshift-labs-frontier-annotations-harness`](https://github.com/siliconjungle/-shapeshift-labs-frontier-annotations-harness)
197
201
  - [`siliconjungle/-shapeshift-labs-frontier-test`](https://github.com/siliconjungle/-shapeshift-labs-frontier-test)
198
202
  - [`siliconjungle/-shapeshift-labs-frontier-fixtures`](https://github.com/siliconjungle/-shapeshift-labs-frontier-fixtures)
199
203
  - [`siliconjungle/-shapeshift-labs-frontier-component-preview`](https://github.com/siliconjungle/-shapeshift-labs-frontier-component-preview)
@@ -0,0 +1,175 @@
1
+ import { readFrontierNestedBlocks } from './source-syntax-report.js';
2
+
3
+ export function readActionBodyRecords(body) {
4
+ const records = [];
5
+ const state = { index: 0 };
6
+ for (const bodyBlock of readFrontierNestedBlocks('body', body)) {
7
+ records.push(...parseActionBodyRecords(bodyBlock.body, state));
8
+ }
9
+ return records;
10
+ }
11
+
12
+ export function stripNestedBlocks(kind, source) {
13
+ let text = source;
14
+ for (const block of readFrontierNestedBlocks(kind, source).reverse()) text = text.slice(0, block.start) + text.slice(block.end);
15
+ return text;
16
+ }
17
+
18
+ function parseActionBodyRecords(source, state) {
19
+ const records = [];
20
+ let offset = 0;
21
+ while (offset < source.length) {
22
+ offset = skipWhitespaceAndComments(source, offset);
23
+ if (offset >= source.length) break;
24
+ const ifHeader = /^if\b([^{\n]*)\{/.exec(source.slice(offset));
25
+ if (ifHeader) {
26
+ const open = offset + ifHeader[0].length - 1;
27
+ const close = findMatchingBrace(source, open);
28
+ if (close < 0) break;
29
+ const index = state.index++;
30
+ records.push(parseActionIfBlock(ifHeader[1].trim(), source.slice(open + 1, close), index, state));
31
+ offset = close + 1;
32
+ continue;
33
+ }
34
+ const lineEnd = source.indexOf('\n', offset);
35
+ const end = lineEnd < 0 ? source.length : lineEnd;
36
+ const line = source.slice(offset, end).trim();
37
+ if (line && line !== '}') {
38
+ const record = parseActionBodyLine(line, state.index++);
39
+ if (record) records.push(record);
40
+ }
41
+ offset = end + 1;
42
+ }
43
+ return records;
44
+ }
45
+
46
+ function parseActionIfBlock(header, body, index, state) {
47
+ const details = parseActionIfHeader(header, index);
48
+ return compactRecord({
49
+ kind: 'if',
50
+ id: details.id,
51
+ name: details.name,
52
+ condition: details.condition,
53
+ body: parseActionBodyRecords(body, state)
54
+ });
55
+ }
56
+
57
+ function parseActionIfHeader(header, index) {
58
+ const withoutId = header.replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim();
59
+ const explicitCondition = /\bcondition\s+(.+)$/.exec(withoutId);
60
+ const nameText = explicitCondition ? withoutId.slice(0, explicitCondition.index).trim() : '';
61
+ const name = firstIdentifier(nameText) ?? `if_${index}`;
62
+ const conditionText = explicitCondition?.[1]?.trim() || withoutId;
63
+ return {
64
+ id: idFrom(header, `action_body_if_${name}`),
65
+ name,
66
+ condition: conditionText ? readActionValue(conditionText) : undefined
67
+ };
68
+ }
69
+
70
+ function parseActionBodyLine(line, index) {
71
+ const statement = /^([A-Za-z_$][\w$-]*)(?:\s+([A-Za-z_$@./:*+-][\w$./@:*+-]*))?(.*)$/.exec(line);
72
+ if (!statement) return undefined;
73
+ const [, rowKind, rawName, rawRest] = statement;
74
+ const name = rawName && !rawName.startsWith('@') ? rawName : `${rowKind}_${index}`;
75
+ const rest = rawName?.startsWith('@') ? ` ${rawName}${rawRest ?? ''}` : (rawRest ?? '');
76
+ if (rowKind === 'set' || rowKind === 'insert' || rowKind === 'merge') {
77
+ return compactRecord({ kind: 'patch', op: rowKind, id: idFrom(rest, `action_body_${rowKind}_${name}`), name, path: readInlineWord('path', rest), value: readInlineActionValue('value', rest) });
78
+ }
79
+ if (rowKind === 'remove') {
80
+ return compactRecord({ kind: 'patch', op: 'remove', id: idFrom(rest, `action_body_remove_${name}`), name, path: readInlineWord('path', rest) });
81
+ }
82
+ if (rowKind === 'callEffect') {
83
+ return compactRecord({ kind: 'callEffect', id: idFrom(rest, `action_body_callEffect_${name}`), name, capability: readInlineWord('capability', rest) ?? readInlineWord('effect', rest) ?? name, input: readInlineActionValue('input', rest) });
84
+ }
85
+ if (rowKind === 'return') {
86
+ const valueText = rawName?.startsWith('@') ? rest.trim() : `${rawName ?? ''}${rest ?? ''}`.trim();
87
+ return compactRecord({ kind: 'return', id: idFrom(rest, `action_body_return_${index}`), value: valueText ? readActionValue(valueText) : undefined });
88
+ }
89
+ return undefined;
90
+ }
91
+
92
+ function skipWhitespaceAndComments(source, offset) {
93
+ let index = offset;
94
+ while (index < source.length) {
95
+ while (index < source.length && /\s/.test(source[index])) index++;
96
+ if (source[index] !== '#') return index;
97
+ const lineEnd = source.indexOf('\n', index);
98
+ index = lineEnd < 0 ? source.length : lineEnd + 1;
99
+ }
100
+ return index;
101
+ }
102
+
103
+ function findMatchingBrace(source, open) {
104
+ let depth = 0;
105
+ let state = 'code';
106
+ let quote = '';
107
+ for (let index = open; index < source.length; index++) {
108
+ const char = source[index];
109
+ const next = source[index + 1];
110
+ if (state === 'line-comment') {
111
+ if (char === '\n') state = 'code';
112
+ continue;
113
+ }
114
+ if (state === 'block-comment') {
115
+ if (char === '*' && next === '/') {
116
+ index++;
117
+ state = 'code';
118
+ }
119
+ continue;
120
+ }
121
+ if (state === 'string') {
122
+ if (char === '\\') {
123
+ index++;
124
+ continue;
125
+ }
126
+ if (char === quote) {
127
+ state = 'code';
128
+ quote = '';
129
+ }
130
+ continue;
131
+ }
132
+ if (char === '/' && next === '/') {
133
+ index++;
134
+ state = 'line-comment';
135
+ continue;
136
+ }
137
+ if (char === '/' && next === '*') {
138
+ index++;
139
+ state = 'block-comment';
140
+ continue;
141
+ }
142
+ if (char === '"' || char === "'" || char === '`') {
143
+ state = 'string';
144
+ quote = char;
145
+ continue;
146
+ }
147
+ if (char === '{') depth++;
148
+ if (char === '}') {
149
+ depth--;
150
+ if (depth === 0) return index;
151
+ }
152
+ }
153
+ return -1;
154
+ }
155
+
156
+ function readInlineActionValue(label, text) {
157
+ const value = new RegExp('(?:^|\\s)' + label + '\\s+(.+?)(?=\\s+[A-Za-z_$][\\w$-]*\\s+|$)').exec(text)?.[1]?.trim();
158
+ return value ? readActionValue(value) : undefined;
159
+ }
160
+
161
+ function readActionValue(value) {
162
+ const text = value.trim();
163
+ const quoted = /^["']([^"']*)["']$/.exec(text);
164
+ if (quoted) return { value: quoted[1] };
165
+ if (text === 'true') return { value: true };
166
+ if (text === 'false') return { value: false };
167
+ if (text === 'null') return { value: null };
168
+ if (/^-?\d+(?:\.\d+)?$/.test(text)) return { value: Number(text) };
169
+ return { expression: text };
170
+ }
171
+
172
+ function idFrom(header, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1] ?? fallback; }
173
+ function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
174
+ function firstIdentifier(text) { return /^([A-Za-z_$][\w$-]*)\b/.exec(text)?.[1]; }
175
+ function compactRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0))); }
@@ -0,0 +1,195 @@
1
+ const ACTION_BODY_ROWS = new Set(['set', 'insert', 'remove', 'merge', 'callEffect', 'return', 'if']);
2
+
3
+ export function readActionSyntaxChildren(source, block, options) {
4
+ const body = source.slice(block.bodyStartOffset, block.bodyEndOffset);
5
+ const bodyBlocks = readNestedBodyBlocks('body', body);
6
+ const state = { rowIndex: 0 };
7
+ const children = [];
8
+ for (const bodyBlock of bodyBlocks) {
9
+ const bodyStartOffset = block.bodyStartOffset + bodyBlock.bodyStart;
10
+ const bodyEndOffset = block.bodyStartOffset + bodyBlock.bodyEnd;
11
+ children.push(...readActionSyntaxRows(source, block, options, state, bodyStartOffset, bodyEndOffset));
12
+ }
13
+ return children;
14
+ }
15
+
16
+ function readActionSyntaxRows(source, block, options, state, startOffset, endOffset, parentActionBodyId) {
17
+ const children = [];
18
+ let offset = startOffset;
19
+ while (offset < endOffset) {
20
+ offset = skipWhitespaceAndLineComments(source, offset, endOffset);
21
+ if (offset >= endOffset) break;
22
+ const ifHeader = /^if\b([^{\n]*)\{/.exec(source.slice(offset, endOffset));
23
+ if (ifHeader) {
24
+ const open = offset + ifHeader[0].length - 1;
25
+ const close = findMatchingBrace(source, open);
26
+ if (close < 0 || close > endOffset) break;
27
+ const child = actionSyntaxChild(source, block, options, {
28
+ text: source.slice(offset, open + 1).trim(),
29
+ startOffset: offset,
30
+ endOffset: open + 1
31
+ }, state, parentActionBodyId);
32
+ children.push(child);
33
+ children.push(...readActionSyntaxRows(source, block, options, state, open + 1, close, child.id));
34
+ offset = close + 1;
35
+ continue;
36
+ }
37
+ const lineEnd = source.indexOf('\n', offset);
38
+ const rawEnd = lineEnd < 0 || lineEnd > endOffset ? endOffset : lineEnd;
39
+ const rawLine = source.slice(offset, rawEnd);
40
+ const leading = /^\s*/.exec(rawLine)?.[0].length ?? 0;
41
+ const trailing = /\s*$/.exec(rawLine)?.[0].length ?? 0;
42
+ const line = {
43
+ text: rawLine.trim(),
44
+ startOffset: offset + leading,
45
+ endOffset: Math.max(offset + leading, rawEnd - trailing)
46
+ };
47
+ if (line.text && line.text !== '}') children.push(actionSyntaxChild(source, block, options, line, state, parentActionBodyId));
48
+ offset = rawEnd + 1;
49
+ }
50
+ return children;
51
+ }
52
+
53
+ function actionSyntaxChild(source, block, options, line, state, parentActionBodyId) {
54
+ const rowIndex = state.rowIndex++;
55
+ const row = /^([A-Za-z_$][\w$-]*)(?:\s+([A-Za-z_$@./:*+-][\w$./@:*+-]*))?(.*)$/.exec(line.text);
56
+ const rowKind = row?.[1] ?? 'unknown';
57
+ const name = actionRowName(rowKind, row?.[2], rowIndex);
58
+ const rest = row?.[2]?.startsWith('@') ? ` ${row[2]}${row[3] ?? ''}` : (row?.[3] ?? '');
59
+ const recognized = ACTION_BODY_ROWS.has(rowKind);
60
+ return cleanRecord({
61
+ kind: recognized ? 'actionBodyRow' : 'actionUnknownRow',
62
+ rowKind,
63
+ normalizedRowKind: recognized ? rowKind : 'unknown',
64
+ name,
65
+ id: idFrom(rest, `action_body_${safeId(rowKind)}_${safeId(name)}_${rowIndex}`),
66
+ header: line.text,
67
+ startOffset: line.startOffset,
68
+ endOffset: line.endOffset,
69
+ location: sourcePosition(source, line.startOffset),
70
+ parentKind: block.kind,
71
+ parentId: block.id,
72
+ parentName: block.name,
73
+ moduleId: block.moduleId,
74
+ moduleName: block.moduleName,
75
+ parentActionBodyId,
76
+ sourceSpan: sourceSpan(source, block, line.startOffset, line.endOffset, options),
77
+ recognized,
78
+ reason: recognized ? undefined : 'unsupported-action-body-row'
79
+ });
80
+ }
81
+
82
+ function actionRowName(rowKind, rawName, rowIndex) {
83
+ if (rowKind === 'if') {
84
+ return rawName && !rawName.startsWith('@') && /^[A-Za-z_$][\w$-]*$/.test(rawName) ? rawName : `${rowKind}_${rowIndex}`;
85
+ }
86
+ return rawName && !rawName.startsWith('@') ? rawName : `${rowKind}_${rowIndex}`;
87
+ }
88
+
89
+ function readNestedBodyBlocks(kind, source) {
90
+ const blocks = [];
91
+ const header = new RegExp('\\b' + kind.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:\\s+([^{}\\n]+?))?\\s*\\{', 'g');
92
+ let match;
93
+ while ((match = header.exec(source))) {
94
+ const open = header.lastIndex - 1;
95
+ const close = findMatchingBrace(source, open);
96
+ if (close < 0) continue;
97
+ blocks.push({
98
+ header: (match[1] ?? '').trim(),
99
+ start: match.index,
100
+ bodyStart: open + 1,
101
+ bodyEnd: close,
102
+ end: close + 1
103
+ });
104
+ header.lastIndex = close + 1;
105
+ }
106
+ return blocks;
107
+ }
108
+
109
+ function skipWhitespaceAndLineComments(source, offset, endOffset) {
110
+ let index = offset;
111
+ while (index < endOffset) {
112
+ while (index < endOffset && /\s/.test(source[index])) index++;
113
+ if (source[index] !== '#') return index;
114
+ const lineEnd = source.indexOf('\n', index);
115
+ index = lineEnd < 0 || lineEnd > endOffset ? endOffset : lineEnd + 1;
116
+ }
117
+ return index;
118
+ }
119
+
120
+ function findMatchingBrace(source, open) {
121
+ let depth = 0;
122
+ let state = 'code';
123
+ let quote = '';
124
+ for (let index = open; index < source.length; index++) {
125
+ const char = source[index];
126
+ const next = source[index + 1];
127
+ if (state === 'line-comment') {
128
+ if (char === '\n') state = 'code';
129
+ continue;
130
+ }
131
+ if (state === 'block-comment') {
132
+ if (char === '*' && next === '/') {
133
+ index++;
134
+ state = 'code';
135
+ }
136
+ continue;
137
+ }
138
+ if (state === 'string') {
139
+ if (char === '\\') {
140
+ index++;
141
+ continue;
142
+ }
143
+ if (char === quote) {
144
+ state = 'code';
145
+ quote = '';
146
+ }
147
+ continue;
148
+ }
149
+ if (char === '/' && next === '/') {
150
+ index++;
151
+ state = 'line-comment';
152
+ continue;
153
+ }
154
+ if (char === '/' && next === '*') {
155
+ index++;
156
+ state = 'block-comment';
157
+ continue;
158
+ }
159
+ if (char === '"' || char === "'" || char === '`') {
160
+ state = 'string';
161
+ quote = char;
162
+ continue;
163
+ }
164
+ if (char === '{') depth++;
165
+ if (char === '}') {
166
+ depth--;
167
+ if (depth === 0) return index;
168
+ }
169
+ }
170
+ return -1;
171
+ }
172
+
173
+ function sourcePosition(source, offset) {
174
+ const lines = source.slice(0, offset).split('\n');
175
+ return { line: lines.length, column: lines[lines.length - 1].length + 1, offset };
176
+ }
177
+
178
+ function sourceSpan(source, block, startOffset, endOffset, options = {}) {
179
+ return cleanRecord({
180
+ sourceId: options.documentId,
181
+ path: options.sourcePath,
182
+ blockId: block.id,
183
+ blockKind: block.kind,
184
+ startOffset,
185
+ endOffset,
186
+ start: sourcePosition(source, startOffset),
187
+ end: sourcePosition(source, endOffset)
188
+ });
189
+ }
190
+
191
+ function idFrom(header, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1] ?? fallback; }
192
+ function safeId(value) { return String(value).replace(/[^A-Za-z0-9_$-]+/g, '_').replace(/^_+|_+$/g, '') || 'row'; }
193
+ function cleanRecord(record) {
194
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0)));
195
+ }
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { actionNode, capabilityNode, createDocument, effectNode, entityNode, externNode, latticeNode, migrationNode, stateNode, targetNode, typeNode } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { readActionBodyRecords, stripNestedBlocks } from './action-body.js';
2
3
  import { parseConstraintSpaceBlock } from './constraint-space.js';
3
4
  import { parseConversionBlock } from './conversion.js';
4
5
  import { parseApplicationSurfaceBlock } from './application-surface.js';
@@ -117,16 +118,21 @@ function parseState(block) {
117
118
  }
118
119
  function parseAction(block) {
119
120
  const name = nameFrom(block.header);
121
+ const topLevelBody = stripNestedBlocks('body', block.body);
122
+ const body = readActionBodyRecords(block.body);
120
123
  return actionNode({
121
124
  id: idFrom(block.header, `action_${name}`),
122
125
  name,
123
- input: parseOptionalTypeExpression(/input\s*:?\s*([^\n]+)/.exec(block.body)?.[1]),
124
- returns: parseOptionalTypeExpression(/returns\s+([^\n]+)/.exec(block.body)?.[1]),
125
- reads: readList('reads', block.body),
126
- writes: readList('writes', block.body),
127
- uses: readList('uses', block.body)
126
+ input: parseOptionalTypeExpression(/input\s*:?\s*([^\n]+)/.exec(topLevelBody)?.[1]),
127
+ returns: parseOptionalTypeExpression(/returns\s+([^\n]+)/.exec(topLevelBody)?.[1]),
128
+ reads: readList('reads', topLevelBody),
129
+ writes: readList('writes', topLevelBody),
130
+ uses: readList('uses', topLevelBody),
131
+ throws: readList('throws', topLevelBody),
132
+ body: body.length ? body : undefined
128
133
  });
129
134
  }
135
+
130
136
  function parseMigration(block) {
131
137
  const name = nameFrom(block.header);
132
138
  return migrationNode({ id: idFrom(block.header, `migration_${name}`), name, fromVersion: readWord('fromVersion', block.body) ?? readWord('from', block.body) ?? 'unknown', toVersion: readWord('toVersion', block.body) ?? readWord('to', block.body) ?? 'unknown', changes: readChangeRecords(block.body), invariants: readList('invariants', block.body) });
@@ -1,9 +1,13 @@
1
+ import { readActionSyntaxChildren } from './action-syntax-children.js';
1
2
  import { ROW_SYNTAX_CONFIG } from './source-syntax-row-config.js';
2
3
 
3
4
  const ROW_NAME_PATTERN = '([A-Za-z_$@./:*+-][\\w$./@:*+-]*)';
4
5
 
5
6
  export function readSourceSyntaxChildren(source, block, options = {}) {
6
7
  if (block.malformed) return [];
8
+ if (block.kind === 'action') {
9
+ return readActionSyntaxChildren(source, block, options);
10
+ }
7
11
  if (block.kind === 'conversion' || block.kind === 'universalConversionPlan') {
8
12
  return readConversionSyntaxChildren(source, block, options);
9
13
  }
@@ -131,10 +135,14 @@ function readGenericRowSyntaxChildren(source, block, options, config) {
131
135
  }
132
136
 
133
137
  function readBodyLines(source, block) {
134
- const body = source.slice(block.bodyStartOffset, block.bodyEndOffset);
138
+ return readTextLines(source, block.bodyStartOffset, block.bodyEndOffset);
139
+ }
140
+
141
+ function readTextLines(source, startOffset, endOffset) {
142
+ const body = source.slice(startOffset, endOffset);
135
143
  const lines = body.split('\n');
136
144
  const records = [];
137
- let lineStart = block.bodyStartOffset;
145
+ let lineStart = startOffset;
138
146
  for (const rawLine of lines) {
139
147
  const rawEnd = lineStart + rawLine.length;
140
148
  const leading = /^\s*/.exec(rawLine)?.[0].length ?? 0;
@@ -91,7 +91,7 @@ export function readFrontierSourceBlocks(source, options = {}) {
91
91
  export function readFrontierNestedBlocks(kind, source) {
92
92
  const structure = scanFrontierStructure(source);
93
93
  const blocks = [];
94
- const header = new RegExp('\\b' + escapeRegExp(kind) + '\\s+([^{}]+?)\\{', 'g');
94
+ const header = new RegExp('\\b' + escapeRegExp(kind) + '(?:\\s+([^{}\\n]+?))?\\s*\\{', 'g');
95
95
  let match;
96
96
  while ((match = header.exec(source))) {
97
97
  const start = match.index;
@@ -101,7 +101,7 @@ export function readFrontierNestedBlocks(kind, source) {
101
101
  if (close < 0) continue;
102
102
  blocks.push({
103
103
  kind,
104
- header: match[1].trim(),
104
+ header: (match[1] ?? '').trim(),
105
105
  body: source.slice(header.lastIndex, close),
106
106
  start,
107
107
  end: close + 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.40",
3
+ "version": "0.3.42",
4
4
  "description": "Parser for the first Frontier Lang .frontier syntax slice.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",