@shapeshift-labs/frontier-lang-parser 0.3.35 → 0.3.37
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/conversion-constraint-record.js +3 -1
- package/dist/conversion.js +64 -5
- package/dist/index.d.ts +40 -3
- package/dist/index.js +3 -12
- package/dist/source-block-kinds.js +44 -0
- package/dist/source-syntax-children.js +77 -0
- package/dist/source-syntax-report.js +65 -46
- package/dist/view.js +2 -10
- package/package.json +1 -1
|
@@ -133,13 +133,15 @@ export const FAMILIES = Object.freeze(Object.fromEntries(FAMILY_ROWS.flatMap(([n
|
|
|
133
133
|
return [name, ...aliases].map((alias) => [alias, config]);
|
|
134
134
|
})));
|
|
135
135
|
|
|
136
|
-
export function parseConstraintRecord(name, text, role) {
|
|
136
|
+
export function parseConstraintRecord(name, text, role, context = {}) {
|
|
137
137
|
const kind = wordAny(text, 'kind', 'constraintKind');
|
|
138
138
|
return cleanRecord({
|
|
139
139
|
id: wordAny(text, 'recordId') ?? idFrom(text, `constraint_record_${name}`),
|
|
140
140
|
role,
|
|
141
141
|
kind,
|
|
142
142
|
name: wordAny(text, 'name') ?? name,
|
|
143
|
+
sourceSpan: context.sourceSpan,
|
|
144
|
+
authoredSourceSpan: context.authoredSourceSpan,
|
|
143
145
|
constraintKind: wordAny(text, 'constraintKind'),
|
|
144
146
|
constraintKinds: listAny(text, 'constraint', 'constraints', 'constraintKind', 'constraintKinds') ?? (kind ? [kind] : undefined),
|
|
145
147
|
...readMapped(text, WORD_FIELDS, wordAny),
|
package/dist/conversion.js
CHANGED
|
@@ -3,8 +3,8 @@ import { FAMILIES, parseConstraintRecord } from './conversion-constraint-record.
|
|
|
3
3
|
export function parseConversionBlock(block) {
|
|
4
4
|
const name = nameFrom(block.header);
|
|
5
5
|
const plan = { id: idFrom(block.header, `conversion_${name}`), targets: [], metadata: { name } };
|
|
6
|
-
for (const
|
|
7
|
-
const line =
|
|
6
|
+
for (const authoredLine of readAuthoredLines(block)) {
|
|
7
|
+
const line = authoredLine.text;
|
|
8
8
|
if (!line || line.startsWith('#')) continue;
|
|
9
9
|
const target = /^target\s+([^\s,]+)/.exec(line)?.[1];
|
|
10
10
|
const sourceLanguage = /^sourceLanguage\s+([^\s,]+)/.exec(line)?.[1] ?? /^source\s+([^\s,]+)/.exec(line)?.[1];
|
|
@@ -21,7 +21,7 @@ export function parseConversionBlock(block) {
|
|
|
21
21
|
else if (runtimeRequirement) addRuntimeRequirement(plan, runtimeRequirement[1], runtimeRequirement[2]);
|
|
22
22
|
else if (dialect) addDialectRecord(plan, dialect[1], dialect[2], false);
|
|
23
23
|
else if (extern) addDialectRecord(plan, extern[1], extern[2], true);
|
|
24
|
-
else if (constraint) addConstraint(plan, constraint[1], constraint[2], constraint[3]);
|
|
24
|
+
else if (constraint) addConstraint(plan, constraint[1], constraint[2], constraint[3], authoredLine);
|
|
25
25
|
}
|
|
26
26
|
return cleanRecord({ ...plan, targets: unique(plan.targets) });
|
|
27
27
|
}
|
|
@@ -79,15 +79,19 @@ function addDialectRecord(plan, name, text, isExtern) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function addConstraint(plan, family, name, text) {
|
|
82
|
+
function addConstraint(plan, family, name, text, authoredLine = {}) {
|
|
83
83
|
const config = FAMILIES[family] ?? { field: family.endsWith('s') ? family : `${family}Constraints`, sourceKey: 'sourceRecords', targetKey: 'targetRecords' };
|
|
84
84
|
const role = readInlineWord('role', text) ?? 'source';
|
|
85
|
-
const
|
|
85
|
+
const explicitSourceSpan = parseSpan(readInlineWord('sourceSpan', text), readInlineWord('sourcePath', text) ?? readInlineWord('path', text));
|
|
86
|
+
const sourceSpan = explicitSourceSpan ?? authoredLine.sourceSpan;
|
|
87
|
+
const record = parseConstraintRecord(name, text, role, { sourceSpan, authoredSourceSpan: authoredLine.sourceSpan });
|
|
86
88
|
const entry = cleanRecord({
|
|
87
89
|
id: idFrom(text, `${config.field}_${name}`),
|
|
88
90
|
sourceLanguage: readInlineWord('sourceLanguage', text) ?? plan.sourceLanguage,
|
|
89
91
|
target: readInlineWord('targetLanguage', text) ?? plan.targets[0],
|
|
90
92
|
mode: readInlineWord('mode', text),
|
|
93
|
+
sourceSpan,
|
|
94
|
+
authoredSourceSpan: authoredLine.sourceSpan,
|
|
91
95
|
sourceMapIds: readInlineList(text, 'sourceMap', 'sourceMaps', 'sourceMapId', 'sourceMapIds'),
|
|
92
96
|
sourceMapMappingIds: readInlineList(text, 'sourceMapMapping', 'sourceMapMappings', 'sourceMapMappingId', 'sourceMapMappingIds'),
|
|
93
97
|
proofObligationIds: readInlineList(text, 'proofObligation', 'proofObligations', 'proofObligationId', 'proofObligationIds', 'obligation', 'obligations'),
|
|
@@ -111,12 +115,16 @@ function resourceGraphFromRecord(record, entry) {
|
|
|
111
115
|
const tokens = lowerTokens(record);
|
|
112
116
|
const resourceId = record.resourceId ?? record.symbolId ?? `${record.id}_resource`;
|
|
113
117
|
const evidenceIds = record.evidenceIds ?? entry.evidenceIds;
|
|
118
|
+
const sourceSpan = record.sourceSpan ?? entry.sourceSpan;
|
|
119
|
+
const authoredSourceSpan = record.authoredSourceSpan ?? entry.authoredSourceSpan;
|
|
114
120
|
return cleanRecord({
|
|
115
121
|
id: `${record.id}_resource_graph`,
|
|
116
122
|
sourceLanguage: entry.sourceLanguage,
|
|
117
123
|
target: entry.target,
|
|
118
124
|
sourcePath: record.sourcePath,
|
|
119
125
|
sourceHash: record.sourceHash,
|
|
126
|
+
sourceSpan,
|
|
127
|
+
authoredSourceSpan,
|
|
120
128
|
evidenceIds,
|
|
121
129
|
sourceMapIds: record.sourceMapIds ?? entry.sourceMapIds,
|
|
122
130
|
sourceMapMappingIds: record.sourceMapMappingIds ?? entry.sourceMapMappingIds,
|
|
@@ -129,6 +137,8 @@ function resourceGraphFromRecord(record, entry) {
|
|
|
129
137
|
resourceKind: record.resourceKind ?? record.kind ?? record.constraintKind,
|
|
130
138
|
sourcePath: record.sourcePath,
|
|
131
139
|
sourceHash: record.sourceHash,
|
|
140
|
+
sourceSpan,
|
|
141
|
+
authoredSourceSpan,
|
|
132
142
|
evidenceIds,
|
|
133
143
|
metadata: { factKinds: record.factKinds, constraintKinds: record.constraintKinds }
|
|
134
144
|
}],
|
|
@@ -137,6 +147,8 @@ function resourceGraphFromRecord(record, entry) {
|
|
|
137
147
|
resourceId,
|
|
138
148
|
ownerId: record.ownerId ?? record.symbolId ?? `${resourceId}:owner`,
|
|
139
149
|
ownerKind: record.ownerKind ?? tokenKind(tokens, /owner|single-owner/) ?? 'owner',
|
|
150
|
+
sourceSpan,
|
|
151
|
+
authoredSourceSpan,
|
|
140
152
|
evidenceIds
|
|
141
153
|
}] : undefined,
|
|
142
154
|
loans: needsLoan(tokens, record) ? [{
|
|
@@ -144,30 +156,40 @@ function resourceGraphFromRecord(record, entry) {
|
|
|
144
156
|
resourceId,
|
|
145
157
|
mode: record.mode ?? loanMode(tokens),
|
|
146
158
|
lifetimeRegionId: record.lifetimeRegionId,
|
|
159
|
+
sourceSpan,
|
|
160
|
+
authoredSourceSpan,
|
|
147
161
|
evidenceIds
|
|
148
162
|
}] : undefined,
|
|
149
163
|
aliases: tokenMatches(tokens, /alias/) ? [{
|
|
150
164
|
id: `${record.id}_alias`,
|
|
151
165
|
resourceId,
|
|
152
166
|
aliasKind: record.aliasKind ?? tokenKind(tokens, /alias/) ?? 'alias',
|
|
167
|
+
sourceSpan,
|
|
168
|
+
authoredSourceSpan,
|
|
153
169
|
evidenceIds
|
|
154
170
|
}] : undefined,
|
|
155
171
|
moves: tokenMatches(tokens, /move|transfer/) ? [{
|
|
156
172
|
id: `${record.id}_move`,
|
|
157
173
|
resourceId,
|
|
158
174
|
moveKind: record.moveKind ?? tokenKind(tokens, /move|transfer/) ?? 'move',
|
|
175
|
+
sourceSpan,
|
|
176
|
+
authoredSourceSpan,
|
|
159
177
|
evidenceIds
|
|
160
178
|
}] : undefined,
|
|
161
179
|
drops: tokenMatches(tokens, /drop/) ? [{
|
|
162
180
|
id: `${record.id}_drop`,
|
|
163
181
|
resourceId,
|
|
164
182
|
dropKind: record.dropKind ?? tokenKind(tokens, /drop/) ?? 'drop',
|
|
183
|
+
sourceSpan,
|
|
184
|
+
authoredSourceSpan,
|
|
165
185
|
evidenceIds
|
|
166
186
|
}] : undefined,
|
|
167
187
|
lifetimeRegions: record.lifetimeKind || record.lifetimeRegionId || tokenMatches(tokens, /lifetime/) ? [{
|
|
168
188
|
id: record.lifetimeRegionId ?? `${record.id}_lifetime`,
|
|
169
189
|
resourceId,
|
|
170
190
|
lifetimeKind: record.lifetimeKind ?? tokenKind(tokens, /lifetime/) ?? record.regionKind,
|
|
191
|
+
sourceSpan,
|
|
192
|
+
authoredSourceSpan,
|
|
171
193
|
evidenceIds
|
|
172
194
|
}] : undefined,
|
|
173
195
|
borrowScopes: needsLoan(tokens, record) || record.scopeKind ? [{
|
|
@@ -176,17 +198,42 @@ function resourceGraphFromRecord(record, entry) {
|
|
|
176
198
|
scopeKind: record.scopeKind ?? record.kind,
|
|
177
199
|
lifetimeRegionId: record.lifetimeRegionId,
|
|
178
200
|
constraintKinds: record.constraintKinds,
|
|
201
|
+
sourceSpan,
|
|
202
|
+
authoredSourceSpan,
|
|
179
203
|
evidenceIds
|
|
180
204
|
}] : undefined,
|
|
181
205
|
unsafeBoundaries: tokenMatches(tokens, /unsafe|raw/) ? [{
|
|
182
206
|
id: `${record.id}_unsafe`,
|
|
183
207
|
resourceId,
|
|
184
208
|
kind: tokenKind(tokens, /unsafe|raw/) ?? 'unsafe-boundary',
|
|
209
|
+
sourceSpan,
|
|
210
|
+
authoredSourceSpan,
|
|
185
211
|
evidenceIds
|
|
186
212
|
}] : undefined
|
|
187
213
|
});
|
|
188
214
|
}
|
|
189
215
|
|
|
216
|
+
function readAuthoredLines(block) {
|
|
217
|
+
const lines = block.body.split('\n');
|
|
218
|
+
const records = [];
|
|
219
|
+
let lineStart = block.syntax?.bodyStartOffset ?? 0;
|
|
220
|
+
for (const rawLine of lines) {
|
|
221
|
+
const rawEnd = lineStart + rawLine.length;
|
|
222
|
+
const leading = /^\s*/.exec(rawLine)?.[0].length ?? 0;
|
|
223
|
+
const trailing = /\s*$/.exec(rawLine)?.[0].length ?? 0;
|
|
224
|
+
const startOffset = lineStart + leading;
|
|
225
|
+
const endOffset = Math.max(startOffset, rawEnd - trailing);
|
|
226
|
+
records.push({
|
|
227
|
+
text: rawLine.trim(),
|
|
228
|
+
startOffset,
|
|
229
|
+
endOffset,
|
|
230
|
+
sourceSpan: typeof block.sourceSpan === 'function' ? block.sourceSpan(startOffset, endOffset) : undefined
|
|
231
|
+
});
|
|
232
|
+
lineStart = rawEnd + 1;
|
|
233
|
+
}
|
|
234
|
+
return records;
|
|
235
|
+
}
|
|
236
|
+
|
|
190
237
|
function lowerTokens(record) {
|
|
191
238
|
return unique([record.kind, record.constraintKind, record.scopeKind, record.resourceKind, record.ownerKind, record.mode, record.aliasKind, record.moveKind, record.dropKind, record.lifetimeKind, record.regionKind, record.flowKind, record.controlFlowKind, ...(record.constraintKinds ?? []), ...(record.factKinds ?? [])].filter(Boolean).map((value) => String(value).toLowerCase()));
|
|
192
239
|
}
|
|
@@ -213,6 +260,18 @@ function readInlineList(text, ...labels) {
|
|
|
213
260
|
}
|
|
214
261
|
return undefined;
|
|
215
262
|
}
|
|
263
|
+
function parseSpan(value, fallbackPath) {
|
|
264
|
+
if (!value) return undefined;
|
|
265
|
+
const match = /^(.+):(\d+):(\d+)-(\d+):(\d+)$/.exec(value);
|
|
266
|
+
if (!match) return undefined;
|
|
267
|
+
return cleanRecord({
|
|
268
|
+
path: match[1] || fallbackPath,
|
|
269
|
+
startLine: Number(match[2]),
|
|
270
|
+
startColumn: Number(match[3]),
|
|
271
|
+
endLine: Number(match[4]),
|
|
272
|
+
endColumn: Number(match[5])
|
|
273
|
+
});
|
|
274
|
+
}
|
|
216
275
|
function cleanRecord(record) {
|
|
217
276
|
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0)));
|
|
218
277
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
import type { FrontierLangDocument } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
-
export interface ParseFrontierOptions { readonly id?: string; readonly name?: string; }
|
|
2
|
+
export interface ParseFrontierOptions { readonly id?: string; readonly name?: string; readonly sourcePath?: string; }
|
|
3
3
|
export declare const FrontierSourceBlockKinds: readonly string[];
|
|
4
|
+
export interface FrontierSourcePosition {
|
|
5
|
+
readonly line: number;
|
|
6
|
+
readonly column: number;
|
|
7
|
+
readonly offset: number;
|
|
8
|
+
}
|
|
9
|
+
export interface FrontierSourceSpan {
|
|
10
|
+
readonly sourceId?: string;
|
|
11
|
+
readonly path?: string;
|
|
12
|
+
readonly blockId?: string;
|
|
13
|
+
readonly blockKind?: string;
|
|
14
|
+
readonly startOffset: number;
|
|
15
|
+
readonly endOffset: number;
|
|
16
|
+
readonly start: FrontierSourcePosition;
|
|
17
|
+
readonly end: FrontierSourcePosition;
|
|
18
|
+
}
|
|
4
19
|
export interface FrontierSourceSyntaxDiagnostic {
|
|
5
20
|
readonly reason: 'unterminated-block' | 'unmatched-close-brace';
|
|
6
21
|
readonly message: string;
|
|
7
|
-
readonly location:
|
|
22
|
+
readonly location: FrontierSourcePosition;
|
|
23
|
+
}
|
|
24
|
+
export interface FrontierSourceChildSyntaxRecord {
|
|
25
|
+
readonly kind: 'conversionConstraint';
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly id?: string;
|
|
28
|
+
readonly family?: string;
|
|
29
|
+
readonly role?: string;
|
|
30
|
+
readonly header: string;
|
|
31
|
+
readonly startOffset: number;
|
|
32
|
+
readonly endOffset: number;
|
|
33
|
+
readonly location: FrontierSourcePosition;
|
|
34
|
+
readonly parentKind?: string;
|
|
35
|
+
readonly parentId?: string;
|
|
36
|
+
readonly parentName?: string;
|
|
37
|
+
readonly moduleId?: string;
|
|
38
|
+
readonly moduleName?: string;
|
|
39
|
+
readonly sourceSpan?: FrontierSourceSpan;
|
|
40
|
+
readonly recognized: boolean;
|
|
8
41
|
}
|
|
9
42
|
export interface FrontierSourceBlockSyntaxRecord {
|
|
10
43
|
readonly kind: string;
|
|
@@ -15,12 +48,13 @@ export interface FrontierSourceBlockSyntaxRecord {
|
|
|
15
48
|
readonly endOffset: number;
|
|
16
49
|
readonly bodyStartOffset: number;
|
|
17
50
|
readonly bodyEndOffset: number;
|
|
18
|
-
readonly location:
|
|
51
|
+
readonly location: FrontierSourcePosition;
|
|
19
52
|
readonly moduleId?: string;
|
|
20
53
|
readonly moduleName?: string;
|
|
21
54
|
readonly recognized: boolean;
|
|
22
55
|
readonly malformed?: boolean;
|
|
23
56
|
readonly diagnostics?: readonly FrontierSourceSyntaxDiagnostic[];
|
|
57
|
+
readonly children?: readonly FrontierSourceChildSyntaxRecord[];
|
|
24
58
|
}
|
|
25
59
|
export interface FrontierUnknownSourceBlockSyntaxRecord extends FrontierSourceBlockSyntaxRecord {
|
|
26
60
|
readonly recognized: false;
|
|
@@ -39,8 +73,11 @@ export interface FrontierSourceSyntaxReport {
|
|
|
39
73
|
readonly recognizedBlockCount: number;
|
|
40
74
|
readonly unknownBlockCount: number;
|
|
41
75
|
readonly malformedBlockCount: number;
|
|
76
|
+
readonly childCount: number;
|
|
77
|
+
readonly recognizedChildCount: number;
|
|
42
78
|
readonly diagnosticCount: number;
|
|
43
79
|
readonly recognizedKinds: readonly string[];
|
|
80
|
+
readonly recognizedChildKinds: readonly string[];
|
|
44
81
|
readonly unknownKinds: readonly string[];
|
|
45
82
|
readonly failClosed: boolean;
|
|
46
83
|
readonly unsupportedSyntax: boolean;
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { parseRuntimeCapabilityBlock } from './runtime-capability.js';
|
|
|
16
16
|
import { parseNativeSourceBlock } from './source-evidence.js';
|
|
17
17
|
import { parseTargetProjectionMetadata } from './target-projection.js';
|
|
18
18
|
import { parseViewBlock } from './view.js';
|
|
19
|
-
import { FrontierSourceBlockKinds } from './source-syntax-report.js';
|
|
19
|
+
import { FrontierSourceBlockKinds, readFrontierSourceBlocks } from './source-syntax-report.js';
|
|
20
20
|
export { FrontierSourceBlockKinds, inspectFrontierSourceSyntax } from './source-syntax-report.js';
|
|
21
21
|
|
|
22
22
|
export function parseFrontierSource(source, options = {}) {
|
|
@@ -72,21 +72,12 @@ export function parseFrontierSource(source, options = {}) {
|
|
|
72
72
|
return createDocument({ id: documentId, name: documentName, nodes, ...(metadata ? { metadata } : {}) });
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
export function parseFrontierFile(name, source) { return parseFrontierSource(source, { name: name.replace(/\.frontier$/, '') }); }
|
|
75
|
+
export function parseFrontierFile(name, source) { return parseFrontierSource(source, { name: name.replace(/\.frontier$/, ''), sourcePath: name }); }
|
|
76
76
|
|
|
77
77
|
function readName(source) { return /module\s+([A-Za-z_$][\w$]*)/.exec(source)?.[1]; }
|
|
78
78
|
function readId(source) { return /module\s+[A-Za-z_$][\w$]*\s+@id\(\s*["']([^"']+)["']\s*\)/.exec(source)?.[1]; }
|
|
79
79
|
function readBlocks(source) {
|
|
80
|
-
|
|
81
|
-
const header = new RegExp('\\b(' + FrontierSourceBlockKinds.join('|') + ')\\s+([^{}]+)\\{', 'g');
|
|
82
|
-
let match;
|
|
83
|
-
while ((match = header.exec(source))) {
|
|
84
|
-
let depth = 1; let index = header.lastIndex;
|
|
85
|
-
while (index < source.length && depth > 0) { const ch = source[index++]; if (ch === '{') depth++; if (ch === '}') depth--; }
|
|
86
|
-
blocks.push({ kind: match[1], header: match[2].trim(), body: source.slice(header.lastIndex, index - 1) });
|
|
87
|
-
header.lastIndex = index;
|
|
88
|
-
}
|
|
89
|
-
return blocks;
|
|
80
|
+
return readFrontierSourceBlocks(source);
|
|
90
81
|
}
|
|
91
82
|
function idFrom(header, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1] ?? fallback; }
|
|
92
83
|
function nameFrom(header) { return /^([A-Za-z_$][\w$]*)/.exec(header)?.[1] ?? 'Unnamed'; }
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const FrontierSourceBlockKinds = Object.freeze([
|
|
2
|
+
'entity',
|
|
3
|
+
'state',
|
|
4
|
+
'action',
|
|
5
|
+
'view',
|
|
6
|
+
'migration',
|
|
7
|
+
'capability',
|
|
8
|
+
'effect',
|
|
9
|
+
'type',
|
|
10
|
+
'extern',
|
|
11
|
+
'lattice',
|
|
12
|
+
'nativeSource',
|
|
13
|
+
'target',
|
|
14
|
+
'proof',
|
|
15
|
+
'paradigm',
|
|
16
|
+
'paradigmSemantics',
|
|
17
|
+
'operations',
|
|
18
|
+
'semanticOperations',
|
|
19
|
+
'conversion',
|
|
20
|
+
'universalConversionPlan',
|
|
21
|
+
'constraintSpace',
|
|
22
|
+
'possibilitySpace',
|
|
23
|
+
'decisionGraph',
|
|
24
|
+
'admissionGraph',
|
|
25
|
+
'dialectRegistry',
|
|
26
|
+
'universalDialectRegistry',
|
|
27
|
+
'interlingua',
|
|
28
|
+
'universalInterlingua',
|
|
29
|
+
'resourceGraph',
|
|
30
|
+
'semanticResourceGraph',
|
|
31
|
+
'packageManifest',
|
|
32
|
+
'packageGraph',
|
|
33
|
+
'packageSurface',
|
|
34
|
+
'canvasSurface',
|
|
35
|
+
'canvasGraph',
|
|
36
|
+
'applicationSurface',
|
|
37
|
+
'appHost',
|
|
38
|
+
'plugin',
|
|
39
|
+
'pluginSurface',
|
|
40
|
+
'pluginContract',
|
|
41
|
+
'runtimeCapabilities',
|
|
42
|
+
'runtimeCapabilityMatrix',
|
|
43
|
+
'runtimeHosts'
|
|
44
|
+
]);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export function readSourceSyntaxChildren(source, block, options = {}) {
|
|
2
|
+
if (block.malformed) return [];
|
|
3
|
+
if (block.kind === 'conversion' || block.kind === 'universalConversionPlan') {
|
|
4
|
+
return readConversionSyntaxChildren(source, block, options);
|
|
5
|
+
}
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function readConversionSyntaxChildren(source, block, options) {
|
|
10
|
+
const children = [];
|
|
11
|
+
for (const line of readBodyLines(source, block)) {
|
|
12
|
+
if (!line.text || line.text.startsWith('#')) continue;
|
|
13
|
+
const constraint = /^constraint\s+([A-Za-z_$][\w$-]*)\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line.text);
|
|
14
|
+
if (!constraint) continue;
|
|
15
|
+
const [, family, name, rest] = constraint;
|
|
16
|
+
children.push(cleanRecord({
|
|
17
|
+
kind: 'conversionConstraint',
|
|
18
|
+
name,
|
|
19
|
+
id: idFrom(rest, `conversion_constraint_${family}_${name}`),
|
|
20
|
+
family,
|
|
21
|
+
role: readInlineWord('role', rest) ?? 'source',
|
|
22
|
+
header: line.text,
|
|
23
|
+
startOffset: line.startOffset,
|
|
24
|
+
endOffset: line.endOffset,
|
|
25
|
+
location: sourcePosition(source, line.startOffset),
|
|
26
|
+
parentKind: block.kind,
|
|
27
|
+
parentId: block.id,
|
|
28
|
+
parentName: block.name,
|
|
29
|
+
moduleId: block.moduleId,
|
|
30
|
+
moduleName: block.moduleName,
|
|
31
|
+
sourceSpan: sourceSpan(source, block, line.startOffset, line.endOffset, options),
|
|
32
|
+
recognized: true
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
return children;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readBodyLines(source, block) {
|
|
39
|
+
const body = source.slice(block.bodyStartOffset, block.bodyEndOffset);
|
|
40
|
+
const lines = body.split('\n');
|
|
41
|
+
const records = [];
|
|
42
|
+
let lineStart = block.bodyStartOffset;
|
|
43
|
+
for (const rawLine of lines) {
|
|
44
|
+
const rawEnd = lineStart + rawLine.length;
|
|
45
|
+
const leading = /^\s*/.exec(rawLine)?.[0].length ?? 0;
|
|
46
|
+
const trailing = /\s*$/.exec(rawLine)?.[0].length ?? 0;
|
|
47
|
+
const startOffset = lineStart + leading;
|
|
48
|
+
const endOffset = Math.max(startOffset, rawEnd - trailing);
|
|
49
|
+
records.push({ text: rawLine.trim(), startOffset, endOffset });
|
|
50
|
+
lineStart = rawEnd + 1;
|
|
51
|
+
}
|
|
52
|
+
return records;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function sourcePosition(source, offset) {
|
|
56
|
+
const lines = source.slice(0, offset).split('\n');
|
|
57
|
+
return { line: lines.length, column: lines[lines.length - 1].length + 1, offset };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function sourceSpan(source, block, startOffset, endOffset, options = {}) {
|
|
61
|
+
return cleanRecord({
|
|
62
|
+
sourceId: options.documentId,
|
|
63
|
+
path: options.sourcePath,
|
|
64
|
+
blockId: block.id,
|
|
65
|
+
blockKind: block.kind,
|
|
66
|
+
startOffset,
|
|
67
|
+
endOffset,
|
|
68
|
+
start: sourcePosition(source, startOffset),
|
|
69
|
+
end: sourcePosition(source, endOffset)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function idFrom(header, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1] ?? fallback; }
|
|
74
|
+
function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
|
|
75
|
+
function cleanRecord(record) {
|
|
76
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0)));
|
|
77
|
+
}
|
|
@@ -1,47 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
'view',
|
|
6
|
-
'migration',
|
|
7
|
-
'capability',
|
|
8
|
-
'effect',
|
|
9
|
-
'type',
|
|
10
|
-
'extern',
|
|
11
|
-
'lattice',
|
|
12
|
-
'nativeSource',
|
|
13
|
-
'target',
|
|
14
|
-
'proof',
|
|
15
|
-
'paradigm',
|
|
16
|
-
'paradigmSemantics',
|
|
17
|
-
'operations',
|
|
18
|
-
'semanticOperations',
|
|
19
|
-
'conversion',
|
|
20
|
-
'universalConversionPlan',
|
|
21
|
-
'constraintSpace',
|
|
22
|
-
'possibilitySpace',
|
|
23
|
-
'decisionGraph',
|
|
24
|
-
'admissionGraph',
|
|
25
|
-
'dialectRegistry',
|
|
26
|
-
'universalDialectRegistry',
|
|
27
|
-
'interlingua',
|
|
28
|
-
'universalInterlingua',
|
|
29
|
-
'resourceGraph',
|
|
30
|
-
'semanticResourceGraph',
|
|
31
|
-
'packageManifest',
|
|
32
|
-
'packageGraph',
|
|
33
|
-
'packageSurface',
|
|
34
|
-
'canvasSurface',
|
|
35
|
-
'canvasGraph',
|
|
36
|
-
'applicationSurface',
|
|
37
|
-
'appHost',
|
|
38
|
-
'plugin',
|
|
39
|
-
'pluginSurface',
|
|
40
|
-
'pluginContract',
|
|
41
|
-
'runtimeCapabilities',
|
|
42
|
-
'runtimeCapabilityMatrix',
|
|
43
|
-
'runtimeHosts'
|
|
44
|
-
]);
|
|
1
|
+
import { readSourceSyntaxChildren } from './source-syntax-children.js';
|
|
2
|
+
import { FrontierSourceBlockKinds } from './source-block-kinds.js';
|
|
3
|
+
|
|
4
|
+
export { FrontierSourceBlockKinds };
|
|
45
5
|
|
|
46
6
|
const FrontierSourceBlockKindSet = new Set(FrontierSourceBlockKinds);
|
|
47
7
|
|
|
@@ -51,13 +11,18 @@ export function inspectFrontierSourceSyntax(source, options = {}) {
|
|
|
51
11
|
const structure = scanFrontierStructure(source);
|
|
52
12
|
const blocks = readCandidateDeclarationBlocks(source, structure).map((block) => ({
|
|
53
13
|
...block,
|
|
54
|
-
recognized: FrontierSourceBlockKindSet.has(block.kind)
|
|
14
|
+
recognized: FrontierSourceBlockKindSet.has(block.kind),
|
|
15
|
+
children: readSourceSyntaxChildren(source, block, { ...options, documentId })
|
|
55
16
|
}));
|
|
17
|
+
for (const block of blocks) {
|
|
18
|
+
if (!block.children.length) delete block.children;
|
|
19
|
+
}
|
|
56
20
|
const recognizedBlocks = blocks.filter((block) => block.recognized);
|
|
57
21
|
const unknownBlocks = blocks.filter((block) => !block.recognized).map((block) => ({
|
|
58
22
|
...block,
|
|
59
23
|
reason: 'unsupported-top-level-block'
|
|
60
24
|
}));
|
|
25
|
+
const childRecords = blocks.flatMap((block) => block.children ?? []);
|
|
61
26
|
const malformedBlockOpenOffsets = new Set(blocks.filter((block) => block.malformed).map((block) => block.bodyStartOffset - 1));
|
|
62
27
|
const diagnostics = [
|
|
63
28
|
...blocks.flatMap((block) => block.diagnostics ?? []),
|
|
@@ -87,8 +52,11 @@ export function inspectFrontierSourceSyntax(source, options = {}) {
|
|
|
87
52
|
recognizedBlockCount: recognizedBlocks.length,
|
|
88
53
|
unknownBlockCount: unknownBlocks.length,
|
|
89
54
|
malformedBlockCount: malformedBlocks.length,
|
|
55
|
+
childCount: childRecords.length,
|
|
56
|
+
recognizedChildCount: childRecords.filter((child) => child.recognized).length,
|
|
90
57
|
diagnosticCount: diagnostics.length,
|
|
91
58
|
recognizedKinds: unique(recognizedBlocks.map((block) => block.kind)),
|
|
59
|
+
recognizedChildKinds: unique(childRecords.filter((child) => child.recognized).map((child) => child.kind)),
|
|
92
60
|
unknownKinds: unique(unknownBlocks.map((block) => block.kind)),
|
|
93
61
|
failClosed,
|
|
94
62
|
unsupportedSyntax: unknownBlocks.length > 0
|
|
@@ -102,6 +70,40 @@ export function inspectFrontierSourceSyntax(source, options = {}) {
|
|
|
102
70
|
};
|
|
103
71
|
}
|
|
104
72
|
|
|
73
|
+
export function readFrontierSourceBlocks(source, options = {}) {
|
|
74
|
+
const report = inspectFrontierSourceSyntax(source, options);
|
|
75
|
+
return report.recognizedBlocks.filter((block) => !block.malformed).map((block) => ({
|
|
76
|
+
kind: block.kind,
|
|
77
|
+
header: block.header,
|
|
78
|
+
body: source.slice(block.bodyStartOffset, block.bodyEndOffset),
|
|
79
|
+
syntax: block,
|
|
80
|
+
sourceSpan: (startOffset, endOffset) => sourceSpan(source, block, startOffset, endOffset, { ...options, documentId: report.documentId })
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function readFrontierNestedBlocks(kind, source) {
|
|
85
|
+
const structure = scanFrontierStructure(source);
|
|
86
|
+
const blocks = [];
|
|
87
|
+
const header = new RegExp('\\b' + escapeRegExp(kind) + '\\s+([^{}]+?)\\{', 'g');
|
|
88
|
+
let match;
|
|
89
|
+
while ((match = header.exec(source))) {
|
|
90
|
+
const start = match.index;
|
|
91
|
+
const open = header.lastIndex - 1;
|
|
92
|
+
if (!structure.codeOffsets[start] || !structure.codeOffsets[open]) continue;
|
|
93
|
+
const close = findMatchingBrace(structure, open);
|
|
94
|
+
if (close < 0) continue;
|
|
95
|
+
blocks.push({
|
|
96
|
+
kind,
|
|
97
|
+
header: match[1].trim(),
|
|
98
|
+
body: source.slice(header.lastIndex, close),
|
|
99
|
+
start,
|
|
100
|
+
end: close + 1
|
|
101
|
+
});
|
|
102
|
+
header.lastIndex = close + 1;
|
|
103
|
+
}
|
|
104
|
+
return blocks;
|
|
105
|
+
}
|
|
106
|
+
|
|
105
107
|
function readCandidateDeclarationBlocks(source, structure) {
|
|
106
108
|
const moduleRanges = readModuleRanges(source, structure);
|
|
107
109
|
const blocks = [];
|
|
@@ -277,8 +279,25 @@ function sourcePosition(source, offset) {
|
|
|
277
279
|
return { line: lines.length, column: lines[lines.length - 1].length + 1, offset };
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
function sourceSpan(source, block, startOffset, endOffset, options = {}) {
|
|
283
|
+
return cleanRecord({
|
|
284
|
+
sourceId: options.documentId,
|
|
285
|
+
path: options.sourcePath,
|
|
286
|
+
blockId: block.id,
|
|
287
|
+
blockKind: block.kind,
|
|
288
|
+
startOffset,
|
|
289
|
+
endOffset,
|
|
290
|
+
start: sourcePosition(source, startOffset),
|
|
291
|
+
end: sourcePosition(source, endOffset)
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
280
295
|
function readName(source) { return /module\s+([A-Za-z_$][\w$]*)/.exec(source)?.[1]; }
|
|
281
296
|
function readId(source) { return /module\s+[A-Za-z_$][\w$]*\s+@id\(\s*["']([^"']+)["']\s*\)/.exec(source)?.[1]; }
|
|
282
|
-
function idFrom(header) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1]; }
|
|
297
|
+
function idFrom(header, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1] ?? fallback; }
|
|
283
298
|
function nameFrom(header) { return /^([A-Za-z_$][\w$]*)/.exec(header)?.[1] ?? 'Unnamed'; }
|
|
284
299
|
function unique(values) { return [...new Set(values.filter(Boolean))]; }
|
|
300
|
+
function escapeRegExp(value) { return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
301
|
+
function cleanRecord(record) {
|
|
302
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0)));
|
|
303
|
+
}
|
package/dist/view.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { viewNode } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { readFrontierNestedBlocks } from './source-syntax-report.js';
|
|
2
3
|
|
|
3
4
|
export function parseViewBlock(block) {
|
|
4
5
|
const name = nameFrom(block.header);
|
|
@@ -79,16 +80,7 @@ function readRenderEvents(body) {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
function readNestedBlocks(kind, source) {
|
|
82
|
-
|
|
83
|
-
const header = new RegExp('\\b' + kind + '\\s+([^{}]+)\\{', 'g');
|
|
84
|
-
let match;
|
|
85
|
-
while ((match = header.exec(source))) {
|
|
86
|
-
let depth = 1; let index = header.lastIndex;
|
|
87
|
-
while (index < source.length && depth > 0) { const ch = source[index++]; if (ch === '{') depth++; if (ch === '}') depth--; }
|
|
88
|
-
blocks.push({ kind, header: match[1].trim(), body: source.slice(header.lastIndex, index - 1), start: match.index, end: index });
|
|
89
|
-
header.lastIndex = index;
|
|
90
|
-
}
|
|
91
|
-
return blocks;
|
|
83
|
+
return readFrontierNestedBlocks(kind, source);
|
|
92
84
|
}
|
|
93
85
|
|
|
94
86
|
function stripNestedBlocks(kind, source) {
|