@shapeshift-labs/frontier-lang-parser 0.3.22 → 0.3.24
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 +32 -0
- package/dist/dialect-registry.js +184 -0
- package/dist/index.js +9 -3
- package/dist/metadata.js +4 -1
- package/dist/target-projection.js +84 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -220,6 +220,23 @@ npm install @shapeshift-labs/frontier-lang-parser
|
|
|
220
220
|
|
|
221
221
|
The parser projects text into `@shapeshift-labs/frontier-lang-kernel` documents. The syntax is intentionally small and experimental.
|
|
222
222
|
|
|
223
|
+
## Authored target projection syntax
|
|
224
|
+
|
|
225
|
+
`.frontier` target blocks can carry projection contracts next to their emit settings. These rows describe what a target lowering claims to represent, what it still needs proof for, and which losses or missing evidence must stay visible to merge and translation tooling.
|
|
226
|
+
|
|
227
|
+
```frontier
|
|
228
|
+
target rust @id("target_rust") {
|
|
229
|
+
language rust
|
|
230
|
+
package example_todo
|
|
231
|
+
emitPath src/generated/todo.rs
|
|
232
|
+
moduleFormat crate
|
|
233
|
+
projection rustAdapter @id("target_projection_rust") disposition target-adapter readiness needs-review represented semantic-symbol|source-map missing semantic-ownership evidence artifact_todo_title_probe proof artifact_todo_title_probe loss loss_borrow_scope missingEvidence translation-borrow-scope:borrow-across-await
|
|
234
|
+
layer ownership @id("target_layer_rust_ownership") kind semantic-ownership status missing missingEvidence translation-borrow-scope:borrow-across-await
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The parser stores these rows on the target node metadata as `projectionContracts` and `projectionLayers`. They are target-lowering evidence, not proof of equivalence: `autoMergeClaim` and `semanticEquivalenceClaim` remain false.
|
|
239
|
+
|
|
223
240
|
## Authored decision graph syntax
|
|
224
241
|
|
|
225
242
|
`.frontier` files can carry semantic merge admission evidence directly in `decisionGraph` or `admissionGraph` blocks. These blocks preserve the causal review shape around a candidate edit: semantic changes, gates, evidence records, patch events, admission decisions, merge decisions, graph nodes, and graph edges.
|
|
@@ -311,6 +328,21 @@ conversion TodoJavascriptToRust @id("conversion_todo_js_rust") {
|
|
|
311
328
|
|
|
312
329
|
`sourceRuntime` and `targetRuntime` become runtime maps. `runtimeRequirement` rows become proof obligations for host/runtime capabilities, including authored `requiredSignals` denominators such as source hashes, target hashes, probe ids, runtime commands, telemetry hashes, and capability-specific trace hashes. `proofEvidence` and `evidence` attach evidence ids, but the compiler still requires bound evidence records before a proof obligation is satisfied. `dialect` and `extern` rows preserve dialect-specific constructs, projection readiness, loss/evidence ids, and binding metadata without requiring the authored Frontier file to drop down to raw JSON.
|
|
313
330
|
|
|
331
|
+
## Authored dialect registry syntax
|
|
332
|
+
|
|
333
|
+
`.frontier` files can carry reusable dialect registries with `dialectRegistry` or `universalDialectRegistry` blocks. These blocks describe language-specific constructs that should stay visible during translation instead of being silently collapsed into generic stubs.
|
|
334
|
+
|
|
335
|
+
```frontier
|
|
336
|
+
dialectRegistry RuntimeDialects @id("dialect_registry_runtime") {
|
|
337
|
+
language javascript
|
|
338
|
+
sourcePath src/runtime.ts
|
|
339
|
+
dialect nodeProcess @id("dialect_registry_node_process") dialect node.runtime kind runtime name process.env target rust disposition unsupported readiness blocked loss loss_node_process_projection evidence evidence_node_runtime sourceMap sourcemap_todo_ts
|
|
340
|
+
extern viteRoutes @id("dialect_registry_vite_routes") dialect vite.plugin.virtual-module externKind generatorArtifact target rust disposition runtime-required readiness needs-review evidence evidence_vite_routes_manifest bindingSymbol virtual:routes module vite
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The parser projects these rows into `metadata.dialects` as a `frontier.lang.universalDialectRegistry`-shaped object. `dialect` rows become universal dialect records, `extern` rows become universal extern records, and projection fields preserve target disposition, readiness, evidence ids, loss ids, source-map ids, and binding metadata. Registry records are evidence for route scoring, not proof of equivalent behavior: authored metadata keeps `autoMergeClaim` and `semanticEquivalenceClaim` false.
|
|
345
|
+
|
|
314
346
|
## Authored possibility spaces
|
|
315
347
|
|
|
316
348
|
`.frontier` files can describe a governed space of valid implementations with `constraintSpace` or `possibilitySpace` blocks. These blocks are metadata-only: they do not add kernel nodes, but they preserve the variables, hard constraints, preferences, collapse strategies, and admission rules that let tools reason about semantic merge, translation, refactoring, and runtime projection as constraint satisfaction.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
export function parseDialectRegistryBlock(block) {
|
|
2
|
+
const name = nameFrom(block.header);
|
|
3
|
+
const registry = {
|
|
4
|
+
kind: 'frontier.lang.universalDialectRegistry',
|
|
5
|
+
version: 1,
|
|
6
|
+
id: idFrom(block.header, `dialect_registry_${name}`),
|
|
7
|
+
language: readWord('language', block.body),
|
|
8
|
+
dialect: readWord('dialect', block.body),
|
|
9
|
+
sourcePath: readWord('sourcePath', block.body) ?? readWord('path', block.body),
|
|
10
|
+
dialects: [],
|
|
11
|
+
externs: [],
|
|
12
|
+
metadata: {
|
|
13
|
+
authoredDialectRegistry: true,
|
|
14
|
+
authoredDialectRegistryBlockName: name,
|
|
15
|
+
semanticEquivalenceClaim: false,
|
|
16
|
+
autoMergeClaim: false
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
for (const rawLine of block.body.split('\n')) {
|
|
20
|
+
const line = rawLine.trim();
|
|
21
|
+
if (!line || line.startsWith('#')) continue;
|
|
22
|
+
const dialect = /^(?:dialect|record)\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
|
|
23
|
+
const extern = /^extern\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
|
|
24
|
+
if (dialect) registry.dialects.push(dialectRecord(registry, dialect[1], dialect[2]));
|
|
25
|
+
if (extern) registry.externs.push(externRecord(registry, extern[1], extern[2]));
|
|
26
|
+
}
|
|
27
|
+
return summarizeRegistry(registry);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function mergeDialectRegistryBlocks(blocks = []) {
|
|
31
|
+
if (!blocks.length) return undefined;
|
|
32
|
+
return summarizeRegistry({
|
|
33
|
+
kind: 'frontier.lang.universalDialectRegistry',
|
|
34
|
+
version: 1,
|
|
35
|
+
id: blocks.length === 1 ? blocks[0].id : 'dialect_registry:source',
|
|
36
|
+
language: first(blocks.map((block) => block.language)),
|
|
37
|
+
dialects: uniqueById(blocks.flatMap((block) => block.dialects ?? [])),
|
|
38
|
+
externs: uniqueById(blocks.flatMap((block) => block.externs ?? [])),
|
|
39
|
+
metadata: {
|
|
40
|
+
authoredDialectRegistryBlockIds: blocks.map((block) => block.id),
|
|
41
|
+
semanticEquivalenceClaim: false,
|
|
42
|
+
autoMergeClaim: false
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function dialectRecord(registry, name, text) {
|
|
48
|
+
return cleanRecord({
|
|
49
|
+
kind: 'frontier.lang.universalDialectRecord',
|
|
50
|
+
version: 1,
|
|
51
|
+
id: idFrom(text, `dialect_${name}`),
|
|
52
|
+
language: readInlineWord('language', text) ?? registry.language,
|
|
53
|
+
dialect: readInlineWord('dialect', text) ?? registry.dialect ?? name,
|
|
54
|
+
constructKind: readInlineWord('constructKind', text) ?? readInlineWord('kind', text) ?? 'runtime',
|
|
55
|
+
name: readInlineWord('name', text) ?? name,
|
|
56
|
+
nativeKind: readInlineWord('nativeKind', text),
|
|
57
|
+
sourcePath: readInlineWord('sourcePath', text) ?? readInlineWord('path', text) ?? registry.sourcePath,
|
|
58
|
+
nativeSourceId: readInlineWord('nativeSource', text) ?? readInlineWord('nativeSourceId', text),
|
|
59
|
+
nativeAstId: readInlineWord('nativeAst', text) ?? readInlineWord('nativeAstId', text),
|
|
60
|
+
nativeAstNodeId: readInlineWord('nativeAstNode', text) ?? readInlineWord('nativeAstNodeId', text),
|
|
61
|
+
semanticNodeId: readInlineWord('semanticNode', text) ?? readInlineWord('semanticNodeId', text),
|
|
62
|
+
semanticSymbolId: readInlineWord('symbol', text) ?? readInlineWord('semanticSymbol', text) ?? readInlineWord('semanticSymbolId', text),
|
|
63
|
+
semanticOccurrenceId: readInlineWord('semanticOccurrence', text) ?? readInlineWord('semanticOccurrenceId', text),
|
|
64
|
+
sourceMapId: readInlineWord('sourceMap', text) ?? readInlineWord('sourceMapId', text),
|
|
65
|
+
sourceMapMappingId: readInlineWord('sourceMapMapping', text) ?? readInlineWord('sourceMapMappingId', text),
|
|
66
|
+
externIds: readInlineList(text, 'extern', 'externId', 'externIds') ?? [],
|
|
67
|
+
lossIds: readInlineList(text, 'loss', 'lossId', 'lossIds') ?? [],
|
|
68
|
+
evidenceIds: readInlineList(text, 'evidence', 'evidenceId', 'evidenceIds') ?? [],
|
|
69
|
+
projection: projectionRecord(text),
|
|
70
|
+
metadata: { semanticEquivalenceClaim: false, autoMergeClaim: false }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function externRecord(registry, name, text) {
|
|
75
|
+
const binding = cleanRecord({
|
|
76
|
+
module: readInlineWord('module', text),
|
|
77
|
+
path: readInlineWord('bindingPath', text) ?? readInlineWord('path', text),
|
|
78
|
+
symbol: readInlineWord('bindingSymbol', text) ?? readInlineWord('symbol', text),
|
|
79
|
+
abi: readInlineWord('abi', text),
|
|
80
|
+
version: readInlineWord('version', text)
|
|
81
|
+
});
|
|
82
|
+
return cleanRecord({
|
|
83
|
+
kind: 'frontier.lang.universalExternRecord',
|
|
84
|
+
version: 1,
|
|
85
|
+
id: idFrom(text, `extern_${name}`),
|
|
86
|
+
language: readInlineWord('language', text) ?? registry.language,
|
|
87
|
+
dialect: readInlineWord('dialect', text) ?? registry.dialect ?? name,
|
|
88
|
+
externKind: readInlineWord('externKind', text) ?? readInlineWord('kind', text) ?? 'foreignSymbol',
|
|
89
|
+
name: readInlineWord('name', text) ?? name,
|
|
90
|
+
...(Object.keys(binding).length ? { binding } : {}),
|
|
91
|
+
sourcePath: readInlineWord('sourcePath', text) ?? registry.sourcePath,
|
|
92
|
+
nativeSourceId: readInlineWord('nativeSource', text) ?? readInlineWord('nativeSourceId', text),
|
|
93
|
+
nativeAstId: readInlineWord('nativeAst', text) ?? readInlineWord('nativeAstId', text),
|
|
94
|
+
nativeAstNodeId: readInlineWord('nativeAstNode', text) ?? readInlineWord('nativeAstNodeId', text),
|
|
95
|
+
semanticNodeId: readInlineWord('semanticNode', text) ?? readInlineWord('semanticNodeId', text),
|
|
96
|
+
semanticSymbolId: readInlineWord('semanticSymbol', text) ?? readInlineWord('semanticSymbolId', text),
|
|
97
|
+
sourceMapId: readInlineWord('sourceMap', text) ?? readInlineWord('sourceMapId', text),
|
|
98
|
+
sourceMapMappingId: readInlineWord('sourceMapMapping', text) ?? readInlineWord('sourceMapMappingId', text),
|
|
99
|
+
lossIds: readInlineList(text, 'loss', 'lossId', 'lossIds') ?? [],
|
|
100
|
+
evidenceIds: readInlineList(text, 'evidence', 'evidenceId', 'evidenceIds') ?? [],
|
|
101
|
+
projection: projectionRecord(text),
|
|
102
|
+
metadata: { semanticEquivalenceClaim: false, autoMergeClaim: false }
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function projectionRecord(text) {
|
|
107
|
+
const lossIds = readInlineList(text, 'projectionLoss', 'projectionLossIds', 'loss', 'lossId', 'lossIds') ?? [];
|
|
108
|
+
const disposition = readInlineWord('disposition', text) ?? (lossIds.length ? 'lossy' : 'review-required');
|
|
109
|
+
return cleanRecord({
|
|
110
|
+
disposition,
|
|
111
|
+
readiness: readInlineWord('readiness', text) ?? readinessForDisposition(disposition),
|
|
112
|
+
targets: readInlineList(text, 'target', 'targets', 'targetLanguage') ?? [],
|
|
113
|
+
lossIds,
|
|
114
|
+
evidenceIds: readInlineList(text, 'projectionEvidence', 'projectionEvidenceIds', 'evidence', 'evidenceId', 'evidenceIds') ?? [],
|
|
115
|
+
sourceMapIds: readInlineList(text, 'sourceMap', 'sourceMapId', 'sourceMapIds'),
|
|
116
|
+
sourceMapMappingIds: readInlineList(text, 'sourceMapMapping', 'sourceMapMappingId', 'sourceMapMappingIds'),
|
|
117
|
+
notes: readInlineList(text, 'note', 'notes')
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function summarizeRegistry(registry) {
|
|
122
|
+
const dialects = uniqueById(registry.dialects ?? []);
|
|
123
|
+
const externs = uniqueById(registry.externs ?? []);
|
|
124
|
+
const records = [...dialects, ...externs];
|
|
125
|
+
return cleanRecord({
|
|
126
|
+
...registry,
|
|
127
|
+
dialects,
|
|
128
|
+
externs,
|
|
129
|
+
summary: {
|
|
130
|
+
dialects: dialects.length,
|
|
131
|
+
externs: externs.length,
|
|
132
|
+
records: records.length,
|
|
133
|
+
languages: unique(records.map((record) => record.language)),
|
|
134
|
+
dialectNames: unique(records.map((record) => record.dialect)),
|
|
135
|
+
constructKinds: countBy(dialects.map((record) => record.constructKind)),
|
|
136
|
+
externKinds: countBy(externs.map((record) => record.externKind)),
|
|
137
|
+
lossIds: unique(records.flatMap((record) => record.lossIds ?? [])),
|
|
138
|
+
evidenceIds: unique(records.flatMap((record) => record.evidenceIds ?? [])),
|
|
139
|
+
sourceMapIds: unique(records.flatMap((record) => [record.sourceMapId, ...(record.projection?.sourceMapIds ?? [])])),
|
|
140
|
+
projectionDispositions: countBy(records.map((record) => record.projection?.disposition)),
|
|
141
|
+
projectionReadiness: records.reduce((readiness, record) => maxReadiness(readiness, record.projection?.readiness ?? 'ready'), 'ready'),
|
|
142
|
+
recordsWithLosses: records.filter((record) => (record.lossIds ?? []).length).length,
|
|
143
|
+
recordsWithProjectionEvidence: records.filter((record) => (record.projection?.evidenceIds ?? []).length).length
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readinessForDisposition(disposition) {
|
|
149
|
+
if (disposition === 'preserved' || disposition === 'lossless') return 'ready';
|
|
150
|
+
if (disposition === 'lossy' || disposition === 'declaration-only') return 'ready-with-losses';
|
|
151
|
+
if (disposition === 'stub-only' || disposition === 'unsupported') return 'blocked';
|
|
152
|
+
return 'needs-review';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function maxReadiness(left, right) {
|
|
156
|
+
const order = ['ready', 'ready-with-losses', 'needs-review', 'blocked'];
|
|
157
|
+
return order.indexOf(right) > order.indexOf(left) ? right : left;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function idFrom(text, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(text)?.[1] ?? fallback; }
|
|
161
|
+
function nameFrom(header) { return /^([A-Za-z_$][\w$]*)/.exec(header)?.[1] ?? 'Unnamed'; }
|
|
162
|
+
function readWord(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\s,]+)', 'm').exec(body)?.[1]?.trim(); }
|
|
163
|
+
function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
|
|
164
|
+
function readInlineList(text, ...labels) {
|
|
165
|
+
for (const label of labels) {
|
|
166
|
+
const value = readInlineWord(label, text);
|
|
167
|
+
if (value) return value.split(/[|,]/).map((item) => item.trim()).filter(Boolean);
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
function cleanRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)); }
|
|
172
|
+
function unique(values) { return [...new Set(values.filter(Boolean))]; }
|
|
173
|
+
function first(values) { return values.find(Boolean); }
|
|
174
|
+
function uniqueById(records) {
|
|
175
|
+
const seen = new Set();
|
|
176
|
+
return records.filter((record) => {
|
|
177
|
+
if (!record?.id || seen.has(record.id)) return false;
|
|
178
|
+
seen.add(record.id);
|
|
179
|
+
return true;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function countBy(values) {
|
|
183
|
+
return values.filter(Boolean).reduce((counts, value) => ({ ...counts, [value]: (counts[value] ?? 0) + 1 }), {});
|
|
184
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { actionNode, capabilityNode, createDocument, effectNode, entityNode, ext
|
|
|
2
2
|
import { parseConstraintSpaceBlock } from './constraint-space.js';
|
|
3
3
|
import { parseConversionBlock } from './conversion.js';
|
|
4
4
|
import { parseDecisionGraphBlock } from './decision-graph.js';
|
|
5
|
+
import { parseDialectRegistryBlock } from './dialect-registry.js';
|
|
5
6
|
import { parseInterlinguaBlock } from './interlingua.js';
|
|
6
7
|
import { createParsedMetadata } from './metadata.js';
|
|
7
8
|
import { parseSemanticOperationsBlock } from './operations.js';
|
|
@@ -9,6 +10,7 @@ import { parseParadigmBlock } from './paradigm.js';
|
|
|
9
10
|
import { parseProofBlock } from './proof.js';
|
|
10
11
|
import { parseResourceGraphBlock } from './resource-graph.js';
|
|
11
12
|
import { parseNativeSourceBlock } from './source-evidence.js';
|
|
13
|
+
import { parseTargetProjectionMetadata } from './target-projection.js';
|
|
12
14
|
import { parseViewBlock } from './view.js';
|
|
13
15
|
|
|
14
16
|
export function parseFrontierSource(source, options = {}) {
|
|
@@ -19,6 +21,7 @@ export function parseFrontierSource(source, options = {}) {
|
|
|
19
21
|
const conversionBlocks = [];
|
|
20
22
|
const constraintSpaceBlocks = [];
|
|
21
23
|
const decisionGraphBlocks = [];
|
|
24
|
+
const dialectRegistryBlocks = [];
|
|
22
25
|
const interlinguaBlocks = [];
|
|
23
26
|
const resourceGraphBlocks = [];
|
|
24
27
|
const nativeSourceBlocks = [];
|
|
@@ -47,10 +50,11 @@ export function parseFrontierSource(source, options = {}) {
|
|
|
47
50
|
if (block.kind === 'conversion' || block.kind === 'universalConversionPlan') conversionBlocks.push(parseConversionBlock(block));
|
|
48
51
|
if (block.kind === 'constraintSpace' || block.kind === 'possibilitySpace') constraintSpaceBlocks.push(parseConstraintSpaceBlock(block));
|
|
49
52
|
if (block.kind === 'decisionGraph' || block.kind === 'admissionGraph') decisionGraphBlocks.push(parseDecisionGraphBlock(block));
|
|
53
|
+
if (block.kind === 'dialectRegistry' || block.kind === 'universalDialectRegistry') dialectRegistryBlocks.push(parseDialectRegistryBlock(block));
|
|
50
54
|
if (block.kind === 'interlingua' || block.kind === 'universalInterlingua') interlinguaBlocks.push(parseInterlinguaBlock(block));
|
|
51
55
|
if (block.kind === 'resourceGraph' || block.kind === 'semanticResourceGraph') resourceGraphBlocks.push(parseResourceGraphBlock(block));
|
|
52
56
|
}
|
|
53
|
-
const metadata = createParsedMetadata({ proofBlocks, paradigmBlocks, operationBlocks, conversionBlocks, constraintSpaceBlocks, decisionGraphBlocks, interlinguaBlocks, resourceGraphBlocks, nativeSourceBlocks });
|
|
57
|
+
const metadata = createParsedMetadata({ proofBlocks, paradigmBlocks, operationBlocks, conversionBlocks, constraintSpaceBlocks, decisionGraphBlocks, dialectRegistryBlocks, interlinguaBlocks, resourceGraphBlocks, nativeSourceBlocks });
|
|
54
58
|
return createDocument({ id: documentId, name: documentName, nodes, ...(metadata ? { metadata } : {}) });
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -60,7 +64,7 @@ function readName(source) { return /module\s+([A-Za-z_$][\w$]*)/.exec(source)?.[
|
|
|
60
64
|
function readId(source) { return /module\s+[A-Za-z_$][\w$]*\s+@id\(\s*["']([^"']+)["']\s*\)/.exec(source)?.[1]; }
|
|
61
65
|
function readBlocks(source) {
|
|
62
66
|
const blocks = [];
|
|
63
|
-
const header = /\b(entity|state|action|view|migration|capability|effect|type|extern|lattice|nativeSource|target|proof|paradigm|paradigmSemantics|operations|semanticOperations|conversion|universalConversionPlan|constraintSpace|possibilitySpace|decisionGraph|admissionGraph|interlingua|universalInterlingua|resourceGraph|semanticResourceGraph)\s+([^{}]+)\{/g;
|
|
67
|
+
const header = /\b(entity|state|action|view|migration|capability|effect|type|extern|lattice|nativeSource|target|proof|paradigm|paradigmSemantics|operations|semanticOperations|conversion|universalConversionPlan|constraintSpace|possibilitySpace|decisionGraph|admissionGraph|dialectRegistry|universalDialectRegistry|interlingua|universalInterlingua|resourceGraph|semanticResourceGraph)\s+([^{}]+)\{/g;
|
|
64
68
|
let match;
|
|
65
69
|
while ((match = header.exec(source))) {
|
|
66
70
|
let depth = 1; let index = header.lastIndex;
|
|
@@ -193,6 +197,7 @@ function parseLattice(block) {
|
|
|
193
197
|
}
|
|
194
198
|
function parseTarget(block) {
|
|
195
199
|
const name = nameFrom(block.header);
|
|
200
|
+
const metadata = parseTargetProjectionMetadata(block.body, name);
|
|
196
201
|
return targetNode({
|
|
197
202
|
id: idFrom(block.header, `target_${name}`),
|
|
198
203
|
name,
|
|
@@ -201,7 +206,8 @@ function parseTarget(block) {
|
|
|
201
206
|
packageName: readWord('package', block.body),
|
|
202
207
|
emitPath: readWord('emitPath', block.body),
|
|
203
208
|
moduleFormat: readWord('moduleFormat', block.body)
|
|
204
|
-
}
|
|
209
|
+
},
|
|
210
|
+
...(metadata ? { metadata } : {})
|
|
205
211
|
});
|
|
206
212
|
}
|
|
207
213
|
function readList(label, body) { const line = new RegExp('^\\s*' + label + '\\s+([^\\n]+)', 'm').exec(body)?.[1]; return line ? line.split(',').map((item) => item.trim()).filter(Boolean) : undefined; }
|
package/dist/metadata.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mergeDialectRegistryBlocks } from './dialect-registry.js';
|
|
2
|
+
|
|
1
3
|
const PROOF_GROUPS = ['contracts', 'refinements', 'invariants', 'termination', 'temporal', 'obligations', 'artifacts', 'assumptions'];
|
|
2
4
|
const PARADIGM_GROUPS = [
|
|
3
5
|
'bindingScopes',
|
|
@@ -21,7 +23,7 @@ const PARADIGM_GROUPS = [
|
|
|
21
23
|
'loweringRecords'
|
|
22
24
|
];
|
|
23
25
|
|
|
24
|
-
export function createParsedMetadata({ proofBlocks = [], paradigmBlocks = [], operationBlocks = [], conversionBlocks = [], constraintSpaceBlocks = [], decisionGraphBlocks = [], interlinguaBlocks = [], resourceGraphBlocks = [], nativeSourceBlocks = [] } = {}) {
|
|
26
|
+
export function createParsedMetadata({ proofBlocks = [], paradigmBlocks = [], operationBlocks = [], conversionBlocks = [], constraintSpaceBlocks = [], decisionGraphBlocks = [], dialectRegistryBlocks = [], interlinguaBlocks = [], resourceGraphBlocks = [], nativeSourceBlocks = [] } = {}) {
|
|
25
27
|
const metadata = {};
|
|
26
28
|
if (proofBlocks.length) metadata.proof = mergeProofBlocks(proofBlocks);
|
|
27
29
|
if (paradigmBlocks.length) metadata.paradigmSemantics = mergeParadigmBlocks(paradigmBlocks);
|
|
@@ -29,6 +31,7 @@ export function createParsedMetadata({ proofBlocks = [], paradigmBlocks = [], op
|
|
|
29
31
|
if (conversionBlocks.length) metadata.universalConversionPlan = mergeConversionBlocks(conversionBlocks);
|
|
30
32
|
if (constraintSpaceBlocks.length) metadata.constraintSpaces = mergeConstraintSpaceBlocks(constraintSpaceBlocks);
|
|
31
33
|
if (decisionGraphBlocks.length) metadata.decisionGraph = mergeDecisionGraphBlocks(decisionGraphBlocks);
|
|
34
|
+
if (dialectRegistryBlocks.length) metadata.dialects = mergeDialectRegistryBlocks(dialectRegistryBlocks);
|
|
32
35
|
if (interlinguaBlocks.length) metadata.universalInterlingua = mergeInterlinguaBlocks(interlinguaBlocks);
|
|
33
36
|
if (resourceGraphBlocks.length) metadata.semanticResourceGraphs = mergeResourceGraphBlocks(resourceGraphBlocks);
|
|
34
37
|
if (nativeSourceBlocks.some((block) => block.sourceMaps.length || block.mergeCandidates.length || block.evidence.length)) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export function parseTargetProjectionMetadata(body, targetName) {
|
|
2
|
+
const projectionContracts = readTargetProjectionContracts(body, targetName);
|
|
3
|
+
const projectionLayers = readTargetProjectionLayers(body, targetName);
|
|
4
|
+
if (!projectionContracts.length && !projectionLayers.length) return undefined;
|
|
5
|
+
return cleanObject({
|
|
6
|
+
authoredTargetProjection: true,
|
|
7
|
+
projectionContracts,
|
|
8
|
+
projectionLayers,
|
|
9
|
+
projectionContractIds: projectionContracts.map((entry) => entry.id),
|
|
10
|
+
projectionLayerIds: projectionLayers.map((entry) => entry.id),
|
|
11
|
+
evidenceIds: uniqueStrings([...projectionContracts.flatMap((entry) => entry.evidenceIds ?? []), ...projectionLayers.flatMap((entry) => entry.evidenceIds ?? [])]),
|
|
12
|
+
proofEvidenceIds: uniqueStrings(projectionContracts.flatMap((entry) => entry.proofEvidenceIds ?? [])),
|
|
13
|
+
lossIds: uniqueStrings(projectionContracts.flatMap((entry) => entry.lossIds ?? [])),
|
|
14
|
+
missingEvidence: uniqueStrings([...projectionContracts.flatMap((entry) => entry.missingEvidence ?? []), ...projectionLayers.flatMap((entry) => entry.missingEvidence ?? [])]),
|
|
15
|
+
semanticEquivalenceClaim: false,
|
|
16
|
+
autoMergeClaim: false
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readTargetProjectionContracts(body, targetName) {
|
|
21
|
+
const rows = [];
|
|
22
|
+
const re = /^\s*(projection|lowering)\s+([A-Za-z_$][\w$-]*)([^\n]*)$/gm;
|
|
23
|
+
let match;
|
|
24
|
+
while ((match = re.exec(body))) {
|
|
25
|
+
const [, rowKind, name, text] = match;
|
|
26
|
+
rows.push(cleanObject({
|
|
27
|
+
id: idFrom(text, `target_projection_${targetName}_${name}`),
|
|
28
|
+
kind: 'frontier.lang.targetProjectionContract',
|
|
29
|
+
name,
|
|
30
|
+
rowKind,
|
|
31
|
+
disposition: readInlineWord('disposition', text) ?? readInlineWord('mode', text),
|
|
32
|
+
readiness: readInlineWord('readiness', text),
|
|
33
|
+
adapterId: readInlineWord('adapter', text) ?? readInlineWord('adapterId', text),
|
|
34
|
+
sourceLanguage: readInlineWord('sourceLanguage', text) ?? readInlineWord('source', text),
|
|
35
|
+
representedLayerKinds: readInlineList(text, 'represented', 'representedLayerKinds'),
|
|
36
|
+
missingLayerKinds: readInlineList(text, 'missing', 'missingLayerKinds'),
|
|
37
|
+
requiredLayerKinds: readInlineList(text, 'requires', 'required', 'requiredLayerKinds'),
|
|
38
|
+
evidenceIds: readInlineList(text, 'evidence', 'evidenceId', 'evidenceIds'),
|
|
39
|
+
proofEvidenceIds: readInlineList(text, 'proof', 'proofEvidence', 'proofEvidenceIds'),
|
|
40
|
+
lossIds: readInlineList(text, 'loss', 'lossId', 'lossIds'),
|
|
41
|
+
missingEvidence: readInlineList(text, 'missingEvidence'),
|
|
42
|
+
review: readInlineList(text, 'review'),
|
|
43
|
+
blockers: readInlineList(text, 'blocker', 'blockers'),
|
|
44
|
+
semanticEquivalenceClaim: false,
|
|
45
|
+
autoMergeClaim: false
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
return rows;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readTargetProjectionLayers(body, targetName) {
|
|
52
|
+
const rows = [];
|
|
53
|
+
const re = /^\s*layer\s+([A-Za-z_$][\w$-]*)([^\n]*)$/gm;
|
|
54
|
+
let match;
|
|
55
|
+
while ((match = re.exec(body))) {
|
|
56
|
+
const [, name, text] = match;
|
|
57
|
+
rows.push(cleanObject({
|
|
58
|
+
id: idFrom(text, `target_layer_${targetName}_${name}`),
|
|
59
|
+
kind: 'frontier.lang.targetProjectionLayer',
|
|
60
|
+
name,
|
|
61
|
+
layerKind: readInlineWord('kind', text) ?? readInlineWord('layerKind', text) ?? name,
|
|
62
|
+
status: readInlineWord('status', text) ?? 'represented',
|
|
63
|
+
evidenceIds: readInlineList(text, 'evidence', 'evidenceId', 'evidenceIds'),
|
|
64
|
+
missingEvidence: readInlineList(text, 'missingEvidence'),
|
|
65
|
+
review: readInlineList(text, 'review'),
|
|
66
|
+
blockers: readInlineList(text, 'blocker', 'blockers'),
|
|
67
|
+
semanticEquivalenceClaim: false,
|
|
68
|
+
autoMergeClaim: false
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
return rows;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function idFrom(text, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(text)?.[1] ?? fallback; }
|
|
75
|
+
function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
|
|
76
|
+
function readInlineList(text, ...labels) {
|
|
77
|
+
for (const label of labels) {
|
|
78
|
+
const value = new RegExp('(?:^|\\s)' + label + '\\s+([^\\s]+)').exec(text)?.[1]?.trim();
|
|
79
|
+
if (value) return value.split(/[|,]/).map((item) => item.trim()).filter(Boolean);
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
function uniqueStrings(values) { return [...new Set(values.filter(Boolean))]; }
|
|
84
|
+
function cleanObject(object) { return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0))); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shapeshift-labs/frontier-lang-parser",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.24",
|
|
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/resource-graph-smoke.mjs && node test/interlingua-smoke.mjs",
|
|
25
|
+
"test": "npm run build && node test/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",
|