@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 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.22",
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",