@shapeshift-labs/frontier-lang-parser 0.3.19 → 0.3.21

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
@@ -243,6 +243,55 @@ decisionGraph TodoAdmission @id("decision_graph_todo") {
243
243
 
244
244
  The parser projects these rows into `metadata.decisionGraph`. Decision graph records are normalized by the kernel helpers, so authored files can describe why a merge is admissible without embedding raw JSON. This is intentionally evidence-first: a safe merge can be represented as a graph of changes, gates, evidence, and decisions, while missing proof can remain explicit as blocked or review-required records.
245
245
 
246
+ ## Authored resource graph syntax
247
+
248
+ `.frontier` files can carry semantic resource graph evidence directly in `resourceGraph` or `semanticResourceGraph` blocks. These blocks make ownership, aliases, loans, moves, drops, lifetimes, unsafe boundaries, conflicts, and proof obligations explicit for translation and semantic merge admission.
249
+
250
+ ```frontier
251
+ resourceGraph TodoResources @id("resource_graph_todo") {
252
+ sourceLanguage javascript
253
+ sourcePath src/todo.ts
254
+ sourceHash sha256:example
255
+ evidence artifact_todo_title_probe
256
+ resource todos @id("resource_todos") kind collection owner owner_todo_store
257
+ owner todoStore @id("owner_todo_store") kind store
258
+ lifetime request @id("life_request") kind lexical startLine 1 endLine 80
259
+ loan readTodos @id("loan_read_todos") resource resource_todos owner owner_todo_store lifetime life_request mode shared access read
260
+ alias todosAlias @id("alias_todos") resource resource_todos owner owner_todo_store alias alias:todos kind local
261
+ move todoMove @id("move_todos") resource resource_todos fromOwner owner_todo_store toOwner owner_worker kind transfer
262
+ drop todoDrop @id("drop_todos") resource resource_todos owner owner_worker lifetime life_request kind lexical-drop order 1
263
+ escape todoEscape @id("escape_todos") resource resource_todos loan loan_read_todos lifetime life_request kind returned-borrow status needs-proof
264
+ outlives requestModule @id("life_request_outlives_module") from life_module to life_request kind contains
265
+ borrow readScope @id("borrow_scope_todos") resource resource_todos lifetime life_request kind shared-borrow constraint shared|read-only
266
+ unsafe ffiBoundary @id("unsafe_todos_ffi") resource resource_todos kind ffi proofStatus missing
267
+ conflict aliasConflict @id("conflict_todos_alias") resource resource_todos loan loan_read_todos alias alias_todos reasonCode exclusive-resource-alias-overlap-requires-proof status open severity error
268
+ proof aliasProof @id("proof_obligation_alias") resource resource_todos conflict conflict_todos_alias kind alias-safety status open statement "Prove the alias cannot mutate during the shared loan."
269
+ }
270
+ ```
271
+
272
+ The parser projects these rows into `metadata.semanticResourceGraphs`. Resource graphs are evidence, not proof: generated claims for borrow-checker soundness, alias safety, lifetime soundness, semantic equivalence, and auto-merge stay false. Compiler conversion routes can use these authored graphs as source-side resource, ownership, lifetime, and borrow-checker evidence while still requiring target proof before admission.
273
+
274
+ ## Authored interlingua syntax
275
+
276
+ `.frontier` files can carry universal interlingua route evidence directly in `interlingua` or `universalInterlingua` blocks. These blocks describe the source lift, represented and missing semantic layers, constraint edges, proof obligations, and target lowering disposition for a route without claiming semantic equivalence.
277
+
278
+ ```frontier
279
+ interlingua JsToRust @id("interlingua_js_rust") {
280
+ route conversion_route_javascript_to_rust
281
+ sourceLanguage javascript
282
+ target rust
283
+ mode target-adapter
284
+ lift source @id("lift_js") sourceImport native_import_js sourcePath src/public-api.js sourceHash sha256:source sourceMap source_map_js ownership symbol:displayName conflict symbol:displayName evidence evidence_translation proof proof_translation
285
+ layer symbols @id("layer_symbols") kind semantic-symbol status represented evidence evidence_translation
286
+ layer ownership @id("layer_ownership") kind semantic-ownership status missing missingEvidence translation-borrow-scope:borrow-across-await
287
+ constraint borrowAwait @id("constraint_borrow_await") family borrow-scope layer semantic-ownership status needs-evidence action collect-borrow-scope required shared-borrow-compatible|borrow-across-await represented shared-borrow-compatible missing borrow-across-await missingEvidence translation-borrow-scope:borrow-across-await evidence evidence_borrow_scope obligation obligation_borrow_await
288
+ obligation borrowAwait @id("obligation_borrow_await") edge constraint_borrow_await family borrow-scope kind borrow-across-await status missing missingEvidence translation-borrow-scope:borrow-across-await evidence evidence_borrow_scope
289
+ lowering rustAdapter @id("lowering_rust_adapter") disposition target-adapter adapter fixture-js-rust adapterKind targetProjection readiness needs-review lossClass targetAdapterProjection proofEvidence proof_translation missingEvidence host-target-adapter-review review adapter-review
290
+ }
291
+ ```
292
+
293
+ The parser projects these rows into `metadata.universalInterlingua`. Interlingua records are route evidence: they expose queryable layer kinds, constraint families, obligation kinds/statuses, lowering disposition, proof ids, and missing evidence while keeping `autoMergeClaim` and `semanticEquivalenceClaim` false. The compiler can merge matching authored records into generated conversion routes by route id or source/target.
294
+
246
295
  ## Authored conversion syntax
247
296
 
248
297
  `.frontier` files can carry universal conversion evidence directly in `conversion` or `universalConversionPlan` blocks. The parser projects these records into `metadata.universalConversionPlan` for the compiler facade and downstream semantic merge tooling.
package/dist/index.js CHANGED
@@ -2,10 +2,12 @@ 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 { parseInterlinguaBlock } from './interlingua.js';
5
6
  import { createParsedMetadata } from './metadata.js';
6
7
  import { parseSemanticOperationsBlock } from './operations.js';
7
8
  import { parseParadigmBlock } from './paradigm.js';
8
9
  import { parseProofBlock } from './proof.js';
10
+ import { parseResourceGraphBlock } from './resource-graph.js';
9
11
  import { parseNativeSourceBlock } from './source-evidence.js';
10
12
  import { parseViewBlock } from './view.js';
11
13
 
@@ -17,6 +19,8 @@ export function parseFrontierSource(source, options = {}) {
17
19
  const conversionBlocks = [];
18
20
  const constraintSpaceBlocks = [];
19
21
  const decisionGraphBlocks = [];
22
+ const interlinguaBlocks = [];
23
+ const resourceGraphBlocks = [];
20
24
  const nativeSourceBlocks = [];
21
25
  const documentId = options.id ?? readId(source) ?? 'mod_frontier';
22
26
  const documentName = options.name ?? readName(source) ?? 'FrontierModule';
@@ -43,8 +47,10 @@ export function parseFrontierSource(source, options = {}) {
43
47
  if (block.kind === 'conversion' || block.kind === 'universalConversionPlan') conversionBlocks.push(parseConversionBlock(block));
44
48
  if (block.kind === 'constraintSpace' || block.kind === 'possibilitySpace') constraintSpaceBlocks.push(parseConstraintSpaceBlock(block));
45
49
  if (block.kind === 'decisionGraph' || block.kind === 'admissionGraph') decisionGraphBlocks.push(parseDecisionGraphBlock(block));
50
+ if (block.kind === 'interlingua' || block.kind === 'universalInterlingua') interlinguaBlocks.push(parseInterlinguaBlock(block));
51
+ if (block.kind === 'resourceGraph' || block.kind === 'semanticResourceGraph') resourceGraphBlocks.push(parseResourceGraphBlock(block));
46
52
  }
47
- const metadata = createParsedMetadata({ proofBlocks, paradigmBlocks, operationBlocks, conversionBlocks, constraintSpaceBlocks, decisionGraphBlocks, nativeSourceBlocks });
53
+ const metadata = createParsedMetadata({ proofBlocks, paradigmBlocks, operationBlocks, conversionBlocks, constraintSpaceBlocks, decisionGraphBlocks, interlinguaBlocks, resourceGraphBlocks, nativeSourceBlocks });
48
54
  return createDocument({ id: documentId, name: documentName, nodes, ...(metadata ? { metadata } : {}) });
49
55
  }
50
56
 
@@ -54,7 +60,7 @@ function readName(source) { return /module\s+([A-Za-z_$][\w$]*)/.exec(source)?.[
54
60
  function readId(source) { return /module\s+[A-Za-z_$][\w$]*\s+@id\(\s*["']([^"']+)["']\s*\)/.exec(source)?.[1]; }
55
61
  function readBlocks(source) {
56
62
  const blocks = [];
57
- 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)\s+([^{}]+)\{/g;
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;
58
64
  let match;
59
65
  while ((match = header.exec(source))) {
60
66
  let depth = 1; let index = header.lastIndex;
@@ -0,0 +1,240 @@
1
+ const GROUPS = {
2
+ layer: 'layerRecords',
3
+ constraint: 'constraintEdges',
4
+ edge: 'constraintEdges',
5
+ obligation: 'obligations',
6
+ proofObligation: 'obligations',
7
+ lowering: 'loweringRecords',
8
+ lift: 'liftRecords',
9
+ evidence: 'evidenceRecords'
10
+ };
11
+
12
+ export function parseInterlinguaBlock(block) {
13
+ const name = nameFrom(block.header);
14
+ const record = {
15
+ kind: 'frontier.lang.universalInterlinguaRecord',
16
+ version: 1,
17
+ id: idFrom(block.header, `interlingua_${name}`),
18
+ routeId: readLine('route', block.body) ?? readLine('routeId', block.body),
19
+ sourceLanguage: readLine('sourceLanguage', block.body) ?? readLine('source', block.body),
20
+ target: readLine('target', block.body),
21
+ mode: readLine('mode', block.body),
22
+ layerRecords: [],
23
+ constraintEdges: [],
24
+ obligations: [],
25
+ loweringRecords: [],
26
+ liftRecords: [],
27
+ evidenceRecords: [],
28
+ claims: {
29
+ autoMergeClaim: false,
30
+ semanticEquivalenceClaim: false
31
+ },
32
+ metadata: { name, authoredFrontierInterlingua: true }
33
+ };
34
+
35
+ for (const rawLine of block.body.split('\n')) {
36
+ const line = rawLine.trim();
37
+ if (!line || line.startsWith('#') || isPropertyLine(line)) continue;
38
+ const match = /^([A-Za-z_$][\w$-]*)\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
39
+ if (!match) continue;
40
+ const [, rowKind, rowName, rest] = match;
41
+ const normalized = normalizeRowKind(rowKind);
42
+ const parsed = parseInterlinguaRow(normalized, rowName, rest, record);
43
+ const group = GROUPS[normalized];
44
+ if (parsed && group) record[group].push(parsed);
45
+ }
46
+
47
+ record.lift = summarizeLift(record);
48
+ record.layers = summarizeLayers(record.layerRecords, record.constraintEdges);
49
+ record.constraints = summarizeConstraints(record.constraintEdges, record.obligations);
50
+ record.lowering = summarizeLowering(record);
51
+ record.query = summarizeQuery(record);
52
+ record.summary = summarize(record);
53
+
54
+ return {
55
+ id: record.id,
56
+ record,
57
+ records: [record, ...record.constraintEdges, ...record.obligations, ...record.layerRecords, ...record.loweringRecords, ...record.liftRecords, ...record.evidenceRecords],
58
+ summary: {
59
+ recordCount: 1,
60
+ ...record.summary
61
+ },
62
+ metadata: { name }
63
+ };
64
+ }
65
+
66
+ function parseInterlinguaRow(kind, name, text, parent) {
67
+ const common = commonRecord(kind, name, text, parent);
68
+ if (kind === 'layer') return cleanRecord({ ...common, layerKind: readInlineWord('layerKind', text) ?? readInlineWord('kind', text) ?? name, status: readInlineWord('status', text) ?? 'represented', sourceIds: readInlineList(text, 'source', 'sourceId', 'sourceIds'), targetIds: readInlineList(text, 'target', 'targetId', 'targetIds'), missingEvidence: readInlineList(text, 'missingEvidence'), blockers: readInlineList(text, 'blocker', 'blockers'), review: readInlineList(text, 'review') });
69
+ if (kind === 'constraint') return cleanRecord({ ...common, family: readInlineWord('family', text) ?? readInlineWord('kind', text) ?? name, layerKind: readInlineWord('layer', text) ?? readInlineWord('layerKind', text), status: readInlineWord('status', text) ?? 'required', action: readInlineWord('action', text), sourceId: readInlineWord('source', text) ?? readInlineWord('sourceId', text), requiredKinds: readInlineList(text, 'required', 'requiredKind', 'requiredKinds'), representedKinds: readInlineList(text, 'represented', 'representedKind', 'representedKinds'), missingKinds: readInlineList(text, 'missing', 'missingKind', 'missingKinds'), missingEvidence: readInlineList(text, 'missingEvidence'), blockers: readInlineList(text, 'blocker', 'blockers'), review: readInlineList(text, 'review'), obligationIds: readInlineList(text, 'obligation', 'obligationId', 'obligationIds') });
70
+ if (kind === 'obligation') return cleanRecord({ ...common, edgeId: readInlineWord('edge', text) ?? readInlineWord('edgeId', text), family: readInlineWord('family', text), obligationKind: readInlineWord('obligationKind', text) ?? readInlineWord('kind', text) ?? name, status: readInlineWord('status', text) ?? 'required', sourceId: readInlineWord('source', text) ?? readInlineWord('sourceId', text), sourceNodeIds: readInlineList(text, 'sourceNode', 'sourceNodeIds'), targetNodeIds: readInlineList(text, 'targetNode', 'targetNodeIds'), missingEvidence: readInlineList(text, 'missingEvidence'), severity: readInlineWord('severity', text) });
71
+ if (kind === 'lowering') return cleanRecord({ ...common, disposition: readInlineWord('disposition', text) ?? readInlineWord('kind', text), adapterId: readInlineWord('adapter', text) ?? readInlineWord('adapterId', text), adapterKind: readInlineWord('adapterKind', text), readiness: readInlineWord('readiness', text), routeAction: readInlineWord('routeAction', text), lossClass: readInlineWord('lossClass', text), lossIds: readInlineList(text, 'loss', 'lossId', 'lossIds'), proofEvidenceIds: readInlineList(text, 'proofEvidence', 'proofEvidenceIds'), missingEvidence: readInlineList(text, 'missingEvidence'), blockers: readInlineList(text, 'blocker', 'blockers'), review: readInlineList(text, 'review') });
72
+ if (kind === 'lift') return cleanRecord({ ...common, sourceImportIds: readInlineList(text, 'sourceImport', 'sourceImportId', 'sourceImportIds'), sourcePaths: readInlineList(text, 'sourcePath', 'sourcePaths', 'path', 'paths'), sourceHashes: readInlineList(text, 'sourceHash', 'sourceHashes'), sourceMapIds: readInlineList(text, 'sourceMap', 'sourceMapId', 'sourceMapIds'), sourceMapMappingIds: readInlineList(text, 'sourceMapMapping', 'sourceMapMappingId', 'sourceMapMappingIds'), ownershipKeys: readInlineList(text, 'ownership', 'ownershipKey', 'ownershipKeys'), conflictKeys: readInlineList(text, 'conflict', 'conflictKey', 'conflictKeys'), proofIds: readInlineList(text, 'proof', 'proofId', 'proofIds') });
73
+ if (kind === 'evidence') return cleanRecord({ ...common, evidenceKind: readInlineWord('evidenceKind', text) ?? readInlineWord('kind', text), status: readInlineWord('status', text), path: readInlineWord('path', text), command: readInlineQuoted('command', text) ?? readInlineWord('command', text) });
74
+ return undefined;
75
+ }
76
+
77
+ function commonRecord(kind, name, text, parent) {
78
+ return cleanRecord({
79
+ id: idFrom(text, `${recordPrefix(kind)}_${name}`),
80
+ recordKind: `frontier.lang.universalInterlingua.${recordKind(kind)}`,
81
+ name,
82
+ routeId: readInlineWord('route', text) ?? readInlineWord('routeId', text) ?? parent.routeId,
83
+ sourceLanguage: readInlineWord('sourceLanguage', text) ?? parent.sourceLanguage,
84
+ target: readInlineWord('target', text) ?? parent.target,
85
+ evidenceIds: readInlineList(text, 'evidence', 'evidenceId', 'evidenceIds'),
86
+ metadata: { authoredName: name }
87
+ });
88
+ }
89
+
90
+ function summarizeLift(record) {
91
+ return cleanRecord({
92
+ sourceLanguage: record.sourceLanguage,
93
+ sourceImportIds: unique(record.liftRecords.flatMap((entry) => entry.sourceImportIds ?? [])),
94
+ sourcePaths: unique(record.liftRecords.flatMap((entry) => entry.sourcePaths ?? [])),
95
+ sourceHashes: unique(record.liftRecords.flatMap((entry) => entry.sourceHashes ?? [])),
96
+ sourceMapIds: unique(record.liftRecords.flatMap((entry) => entry.sourceMapIds ?? [])),
97
+ sourceMapMappingIds: unique(record.liftRecords.flatMap((entry) => entry.sourceMapMappingIds ?? [])),
98
+ ownershipKeys: unique(record.liftRecords.flatMap((entry) => entry.ownershipKeys ?? [])),
99
+ conflictKeys: unique(record.liftRecords.flatMap((entry) => entry.conflictKeys ?? [])),
100
+ evidenceIds: unique(allEvidence(record)),
101
+ proofIds: unique(record.liftRecords.flatMap((entry) => entry.proofIds ?? []))
102
+ });
103
+ }
104
+
105
+ function summarizeLayers(layers, edges) {
106
+ const byStatus = (status) => unique(layers.filter((layer) => layer.status === status).map((layer) => layer.layerKind));
107
+ return {
108
+ kinds: unique([...layers.map((layer) => layer.layerKind), ...edges.map((edge) => edge.layerKind)]),
109
+ representedKinds: byStatus('represented'),
110
+ missingKinds: unique([...byStatus('missing'), ...edges.filter((edge) => (edge.missingKinds ?? []).length || (edge.missingEvidence ?? []).length).map((edge) => edge.layerKind)]),
111
+ reviewKinds: byStatus('review'),
112
+ blockedKinds: byStatus('blocked'),
113
+ constructCount: layers.length,
114
+ representedCount: byStatus('represented').length,
115
+ missingCount: byStatus('missing').length,
116
+ reviewCount: byStatus('review').length,
117
+ blockedCount: byStatus('blocked').length
118
+ };
119
+ }
120
+
121
+ function summarizeConstraints(edges, obligations) {
122
+ const normalizedObligations = obligations.map((obligation) => cleanRecord({ ...obligation, kind: obligation.obligationKind ?? obligation.kind }));
123
+ return {
124
+ edges,
125
+ edgeCount: edges.length,
126
+ obligations: normalizedObligations,
127
+ obligationCount: normalizedObligations.length,
128
+ families: unique(edges.map((edge) => edge.family)),
129
+ statuses: unique(edges.map((edge) => edge.status)),
130
+ actions: unique(edges.map((edge) => edge.action)),
131
+ sourceIds: unique(edges.map((edge) => edge.sourceId)),
132
+ evidenceIds: unique(edges.flatMap((edge) => edge.evidenceIds ?? [])),
133
+ requiredKinds: unique(edges.flatMap((edge) => edge.requiredKinds ?? [])),
134
+ representedKinds: unique(edges.flatMap((edge) => edge.representedKinds ?? [])),
135
+ missingKinds: unique(edges.flatMap((edge) => edge.missingKinds ?? [])),
136
+ missingEvidence: unique(edges.flatMap((edge) => edge.missingEvidence ?? [])),
137
+ obligationKinds: unique(normalizedObligations.map((obligation) => obligation.kind)),
138
+ obligationStatuses: unique(normalizedObligations.map((obligation) => obligation.status)),
139
+ obligationEvidenceIds: unique(normalizedObligations.flatMap((obligation) => obligation.evidenceIds ?? [])),
140
+ obligationMissingEvidence: unique(normalizedObligations.flatMap((obligation) => obligation.missingEvidence ?? [])),
141
+ blockers: unique(edges.flatMap((edge) => edge.blockers ?? [])),
142
+ review: unique(edges.flatMap((edge) => edge.review ?? []))
143
+ };
144
+ }
145
+
146
+ function summarizeLowering(record) {
147
+ const latest = record.loweringRecords.at(-1) ?? {};
148
+ return cleanRecord({
149
+ disposition: latest.disposition,
150
+ mode: record.mode,
151
+ routeAction: latest.routeAction,
152
+ lossClass: latest.lossClass,
153
+ adapterId: latest.adapterId,
154
+ adapterKind: latest.adapterKind,
155
+ readiness: latest.readiness,
156
+ proofEvidenceIds: unique(record.loweringRecords.flatMap((entry) => entry.proofEvidenceIds ?? [])),
157
+ evidenceIds: unique(allEvidence(record)),
158
+ missingEvidence: unique(record.loweringRecords.flatMap((entry) => entry.missingEvidence ?? [])),
159
+ lossIds: unique(record.loweringRecords.flatMap((entry) => entry.lossIds ?? [])),
160
+ blockers: unique(record.loweringRecords.flatMap((entry) => entry.blockers ?? [])),
161
+ review: unique(record.loweringRecords.flatMap((entry) => entry.review ?? []))
162
+ });
163
+ }
164
+
165
+ function summarizeQuery(record) {
166
+ return {
167
+ layerKinds: record.layers.kinds,
168
+ representedLayerKinds: record.layers.representedKinds,
169
+ missingLayerKinds: record.layers.missingKinds,
170
+ reviewLayerKinds: record.layers.reviewKinds,
171
+ blockedLayerKinds: record.layers.blockedKinds,
172
+ constraintFamilies: record.constraints.families,
173
+ constraintStatuses: record.constraints.statuses,
174
+ constraintActions: record.constraints.actions,
175
+ constraintSourceIds: record.constraints.sourceIds,
176
+ constraintEvidenceIds: record.constraints.evidenceIds,
177
+ constraintRequiredKinds: record.constraints.requiredKinds,
178
+ constraintRepresentedKinds: record.constraints.representedKinds,
179
+ constraintMissingKinds: record.constraints.missingKinds,
180
+ constraintMissingEvidence: record.constraints.missingEvidence,
181
+ constraintObligationKinds: record.constraints.obligationKinds,
182
+ constraintObligationStatuses: record.constraints.obligationStatuses,
183
+ constraintObligationEvidenceIds: record.constraints.obligationEvidenceIds,
184
+ constraintObligationMissingEvidence: record.constraints.obligationMissingEvidence,
185
+ loweringDisposition: record.lowering.disposition,
186
+ missingEvidence: unique([...(record.lowering.missingEvidence ?? []), ...(record.constraints.missingEvidence ?? [])]),
187
+ proofEvidenceIds: record.lowering.proofEvidenceIds ?? [],
188
+ targetAdapterId: record.lowering.adapterId
189
+ };
190
+ }
191
+
192
+ function summarize(record) {
193
+ return {
194
+ layerCount: record.layers.constructCount,
195
+ constraintCount: record.constraints.edgeCount,
196
+ obligationCount: record.constraints.obligationCount,
197
+ loweringCount: record.loweringRecords.length,
198
+ liftCount: record.liftRecords.length,
199
+ evidenceCount: record.evidenceRecords.length,
200
+ missingEvidenceCount: record.query.missingEvidence.length,
201
+ blockerCount: (record.lowering.blockers ?? []).length + (record.constraints.blockers ?? []).length
202
+ };
203
+ }
204
+
205
+ function allEvidence(record) {
206
+ return [...record.evidenceRecords.map((entry) => entry.id), ...record.layerRecords.flatMap((entry) => entry.evidenceIds ?? []), ...record.constraintEdges.flatMap((entry) => entry.evidenceIds ?? []), ...record.obligations.flatMap((entry) => entry.evidenceIds ?? []), ...record.liftRecords.flatMap((entry) => entry.evidenceIds ?? [])];
207
+ }
208
+
209
+ function normalizeRowKind(kind) {
210
+ if (kind === 'constraintEdge') return 'constraint';
211
+ if (kind === 'proof' || kind === 'proofObligation') return 'obligation';
212
+ if (kind === 'lower' || kind === 'lowering') return 'lowering';
213
+ if (kind === 'source' || kind === 'sourceLift') return 'lift';
214
+ return kind;
215
+ }
216
+
217
+ function recordKind(kind) {
218
+ if (kind === 'constraint') return 'constraintEdge';
219
+ if (kind === 'obligation') return 'constraintObligation';
220
+ return kind;
221
+ }
222
+
223
+ function recordPrefix(kind) { return recordKind(kind).replace(/[A-Z]/g, (value) => `_${value.toLowerCase()}`); }
224
+ function isPropertyLine(line) { return /^(route|routeId|sourceLanguage|source|target|mode)\s+/.test(line); }
225
+ function idFrom(text, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(text)?.[1] ?? fallback; }
226
+ function nameFrom(header) { return /^([A-Za-z_$][\w$]*)/.exec(header)?.[1] ?? 'Interlingua'; }
227
+ function readLine(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\n]+)', 'm').exec(body)?.[1]?.trim(); }
228
+ function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
229
+ function readInlineQuoted(label, text) { return new RegExp("(?:^|\\s)" + label + "\\s+[\"']([^\"']+)[\"']").exec(text)?.[1]?.trim(); }
230
+ function readInlineList(text, ...labels) {
231
+ for (const label of labels) {
232
+ const value = new RegExp('(?:^|\\s)' + label + '\\s+([^\\s]+)').exec(text)?.[1]?.trim();
233
+ if (value) return value.split(/[|,]/).map((item) => item.trim()).filter(Boolean);
234
+ }
235
+ return undefined;
236
+ }
237
+ function unique(values = []) { return [...new Set(values.filter(Boolean))]; }
238
+ function cleanRecord(record) {
239
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0)));
240
+ }
package/dist/metadata.js CHANGED
@@ -21,7 +21,7 @@ const PARADIGM_GROUPS = [
21
21
  'loweringRecords'
22
22
  ];
23
23
 
24
- export function createParsedMetadata({ proofBlocks = [], paradigmBlocks = [], operationBlocks = [], conversionBlocks = [], constraintSpaceBlocks = [], decisionGraphBlocks = [], nativeSourceBlocks = [] } = {}) {
24
+ export function createParsedMetadata({ proofBlocks = [], paradigmBlocks = [], operationBlocks = [], conversionBlocks = [], constraintSpaceBlocks = [], decisionGraphBlocks = [], interlinguaBlocks = [], resourceGraphBlocks = [], nativeSourceBlocks = [] } = {}) {
25
25
  const metadata = {};
26
26
  if (proofBlocks.length) metadata.proof = mergeProofBlocks(proofBlocks);
27
27
  if (paradigmBlocks.length) metadata.paradigmSemantics = mergeParadigmBlocks(paradigmBlocks);
@@ -29,6 +29,8 @@ export function createParsedMetadata({ proofBlocks = [], paradigmBlocks = [], op
29
29
  if (conversionBlocks.length) metadata.universalConversionPlan = mergeConversionBlocks(conversionBlocks);
30
30
  if (constraintSpaceBlocks.length) metadata.constraintSpaces = mergeConstraintSpaceBlocks(constraintSpaceBlocks);
31
31
  if (decisionGraphBlocks.length) metadata.decisionGraph = mergeDecisionGraphBlocks(decisionGraphBlocks);
32
+ if (interlinguaBlocks.length) metadata.universalInterlingua = mergeInterlinguaBlocks(interlinguaBlocks);
33
+ if (resourceGraphBlocks.length) metadata.semanticResourceGraphs = mergeResourceGraphBlocks(resourceGraphBlocks);
32
34
  if (nativeSourceBlocks.some((block) => block.sourceMaps.length || block.mergeCandidates.length || block.evidence.length)) {
33
35
  metadata.universalAst = mergeNativeSourceBlocks(nativeSourceBlocks);
34
36
  }
@@ -152,6 +154,83 @@ function mergeDecisionGraphBlocks(blocks) {
152
154
  };
153
155
  }
154
156
 
157
+ function mergeInterlinguaBlocks(blocks) {
158
+ const interlinguaRecords = blocks.map((block) => block.record).filter(Boolean);
159
+ const records = blocks.flatMap((block) => block.records ?? []);
160
+ return {
161
+ id: blocks.length === 1 ? blocks[0].id : 'universalInterlingua:source',
162
+ interlinguaRecords,
163
+ records,
164
+ recordIds: ids(records),
165
+ interlinguaRecordIds: ids(interlinguaRecords),
166
+ layerIds: blocks.flatMap((block) => ids(block.record?.layerRecords)),
167
+ constraintIds: blocks.flatMap((block) => ids(block.record?.constraintEdges)),
168
+ obligationIds: blocks.flatMap((block) => ids(block.record?.obligations)),
169
+ loweringIds: blocks.flatMap((block) => ids(block.record?.loweringRecords)),
170
+ liftIds: blocks.flatMap((block) => ids(block.record?.liftRecords)),
171
+ evidenceIds: [...new Set(blocks.flatMap((block) => block.record?.query?.proofEvidenceIds ?? []).concat(blocks.flatMap((block) => block.record?.query?.constraintEvidenceIds ?? [])))],
172
+ routeIds: [...new Set(interlinguaRecords.map((record) => record.routeId).filter(Boolean))],
173
+ summary: {
174
+ interlinguaCount: interlinguaRecords.length,
175
+ recordCount: records.length,
176
+ layerCount: sum(blocks, 'layerCount'),
177
+ constraintCount: sum(blocks, 'constraintCount'),
178
+ obligationCount: sum(blocks, 'obligationCount'),
179
+ loweringCount: sum(blocks, 'loweringCount'),
180
+ liftCount: sum(blocks, 'liftCount'),
181
+ evidenceCount: sum(blocks, 'evidenceCount'),
182
+ missingEvidenceCount: sum(blocks, 'missingEvidenceCount'),
183
+ blockerCount: sum(blocks, 'blockerCount')
184
+ },
185
+ metadata: { authoredInterlinguaBlockIds: blocks.map((block) => block.id) }
186
+ };
187
+ }
188
+
189
+ function mergeResourceGraphBlocks(blocks) {
190
+ const graphs = blocks.map((block) => block.graph).filter(Boolean);
191
+ const records = blocks.flatMap((block) => block.records ?? []);
192
+ return {
193
+ id: blocks.length === 1 ? blocks[0].id : 'semanticResourceGraphs:source',
194
+ graphs,
195
+ resourceGraphs: graphs,
196
+ records,
197
+ graphIds: ids(graphs),
198
+ recordIds: ids(records),
199
+ resourceIds: blocks.flatMap((block) => ids(block.graph?.resources)),
200
+ ownerIds: blocks.flatMap((block) => ids(block.graph?.owners)),
201
+ loanIds: blocks.flatMap((block) => ids(block.graph?.loans)),
202
+ aliasIds: blocks.flatMap((block) => ids(block.graph?.aliases)),
203
+ moveIds: blocks.flatMap((block) => ids(block.graph?.moves)),
204
+ dropIds: blocks.flatMap((block) => ids(block.graph?.drops)),
205
+ escapeIds: blocks.flatMap((block) => ids(block.graph?.escapes)),
206
+ lifetimeRegionIds: blocks.flatMap((block) => ids(block.graph?.lifetimeRegions)),
207
+ lifetimeRelationIds: blocks.flatMap((block) => ids(block.graph?.lifetimeRelations)),
208
+ borrowScopeIds: blocks.flatMap((block) => ids(block.graph?.borrowScopes)),
209
+ unsafeBoundaryIds: blocks.flatMap((block) => ids(block.graph?.unsafeBoundaries)),
210
+ conflictIds: blocks.flatMap((block) => ids(block.graph?.conflicts)),
211
+ proofObligationIds: blocks.flatMap((block) => ids(block.graph?.proofObligations)),
212
+ summary: {
213
+ graphCount: blocks.length,
214
+ recordCount: records.length,
215
+ resourceCount: sum(blocks, 'resources'),
216
+ ownerCount: sum(blocks, 'owners'),
217
+ loanCount: sum(blocks, 'loans'),
218
+ aliasCount: sum(blocks, 'aliases'),
219
+ moveCount: sum(blocks, 'moves'),
220
+ dropCount: sum(blocks, 'drops'),
221
+ escapeCount: sum(blocks, 'escapes'),
222
+ lifetimeRegionCount: sum(blocks, 'lifetimeRegions'),
223
+ lifetimeRelationCount: sum(blocks, 'lifetimeRelations'),
224
+ borrowScopeCount: sum(blocks, 'borrowScopes'),
225
+ unsafeBoundaryCount: sum(blocks, 'unsafeBoundaries'),
226
+ conflictCount: sum(blocks, 'conflicts'),
227
+ proofObligationCount: sum(blocks, 'proofObligations'),
228
+ unsafeBoundariesWithoutProof: sum(blocks, 'unsafeBoundariesWithoutProof')
229
+ },
230
+ metadata: { authoredResourceGraphBlockIds: blocks.map((block) => block.id) }
231
+ };
232
+ }
233
+
155
234
  function ids(records = []) {
156
235
  return records.map((record) => record?.id).filter(Boolean);
157
236
  }
@@ -0,0 +1,208 @@
1
+ const GROUPS = {
2
+ resource: 'resources',
3
+ owner: 'owners',
4
+ loan: 'loans',
5
+ alias: 'aliases',
6
+ move: 'moves',
7
+ drop: 'drops',
8
+ escape: 'escapes',
9
+ lifetimeRegion: 'lifetimeRegions',
10
+ lifetimeRelation: 'lifetimeRelations',
11
+ borrowScope: 'borrowScopes',
12
+ unsafeBoundary: 'unsafeBoundaries',
13
+ conflict: 'conflicts',
14
+ proofObligation: 'proofObligations'
15
+ };
16
+
17
+ export function parseResourceGraphBlock(block) {
18
+ const name = nameFrom(block.header);
19
+ const graph = {
20
+ kind: 'frontier.lang.semanticResourceGraph',
21
+ version: 1,
22
+ id: idFrom(block.header, `resource_graph_${name}`),
23
+ sourceLanguage: readLine('sourceLanguage', block.body) ?? readLine('language', block.body),
24
+ sourcePath: readLine('sourcePath', block.body) ?? readLine('path', block.body),
25
+ sourceHash: readLine('sourceHash', block.body),
26
+ status: readLine('status', block.body) ?? 'partial',
27
+ evidenceIds: readListLine('evidence', block.body) ?? readListLine('evidenceIds', block.body) ?? [],
28
+ resources: [],
29
+ owners: [],
30
+ loans: [],
31
+ aliases: [],
32
+ moves: [],
33
+ drops: [],
34
+ escapes: [],
35
+ lifetimeRegions: [],
36
+ lifetimeRelations: [],
37
+ outlives: [],
38
+ borrowScopes: [],
39
+ borrowScopeRegions: [],
40
+ unsafeBoundaries: [],
41
+ conflicts: [],
42
+ proofObligations: [],
43
+ claims: {
44
+ borrowCheckerClaim: false,
45
+ aliasSafetyClaim: false,
46
+ lifetimeSoundnessClaim: false,
47
+ semanticEquivalenceClaim: false,
48
+ autoMergeClaim: false
49
+ },
50
+ metadata: { name }
51
+ };
52
+
53
+ for (const rawLine of block.body.split('\n')) {
54
+ const line = rawLine.trim();
55
+ if (!line || line.startsWith('#') || isGraphPropertyLine(line)) continue;
56
+ const match = /^([A-Za-z_$][\w$-]*)\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
57
+ if (!match) continue;
58
+ const [, rowKind, rowName, rest] = match;
59
+ const normalized = normalizeRowKind(rowKind);
60
+ const record = parseResourceRecord(normalized, rowName, rest, graph);
61
+ const group = GROUPS[normalized];
62
+ if (record && group) graph[group].push(record);
63
+ }
64
+
65
+ graph.outlives = graph.lifetimeRelations;
66
+ graph.borrowScopeRegions = graph.borrowScopes;
67
+ graph.summary = summarize(graph);
68
+ graph.query = {
69
+ resourceIds: ids(graph.resources),
70
+ ownerIds: ids(graph.owners),
71
+ lifetimeRegionIds: ids(graph.lifetimeRegions),
72
+ sourcePaths: unique(allRecords(graph).map((record) => record.sourcePath)),
73
+ evidenceIds: unique([...graph.evidenceIds, ...allRecords(graph).flatMap((record) => record.evidenceIds ?? [])]),
74
+ blockerReasonCodes: unique(graph.conflicts.map((record) => record.reasonCode))
75
+ };
76
+
77
+ return {
78
+ id: graph.id,
79
+ graph,
80
+ records: allRecords(graph),
81
+ summary: {
82
+ graphCount: 1,
83
+ ...graph.summary
84
+ },
85
+ metadata: { name }
86
+ };
87
+ }
88
+
89
+ function parseResourceRecord(kind, name, text, graph) {
90
+ const common = commonRecord(kind, name, text, graph);
91
+ if (kind === 'resource') return cleanRecord({ ...common, resourceKind: readInlineWord('resourceKind', text) ?? readInlineWord('kind', text) ?? name, ownerId: readInlineWord('owner', text) ?? readInlineWord('ownerId', text), ownerName: readInlineWord('ownerName', text) });
92
+ if (kind === 'owner') return cleanRecord({ ...common, ownerKind: readInlineWord('ownerKind', text) ?? readInlineWord('kind', text) ?? 'owner' });
93
+ if (kind === 'loan') return cleanRecord({ ...common, resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text), ownerId: readInlineWord('owner', text) ?? readInlineWord('ownerId', text), lifetimeRegionId: readInlineWord('lifetime', text) ?? readInlineWord('lifetimeRegion', text) ?? readInlineWord('lifetimeRegionId', text), mode: readInlineWord('mode', text) ?? 'unknown', access: readInlineWord('access', text) });
94
+ if (kind === 'alias') return cleanRecord({ ...common, resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text), ownerId: readInlineWord('owner', text) ?? readInlineWord('ownerId', text), aliasId: readInlineWord('alias', text) ?? readInlineWord('aliasId', text), aliasKind: readInlineWord('aliasKind', text) ?? readInlineWord('kind', text) ?? 'alias' });
95
+ if (kind === 'move') return cleanRecord({ ...common, resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text), fromOwnerId: readInlineWord('fromOwner', text) ?? readInlineWord('fromOwnerId', text), toOwnerId: readInlineWord('toOwner', text) ?? readInlineWord('toOwnerId', text), moveKind: readInlineWord('moveKind', text) ?? readInlineWord('kind', text) });
96
+ if (kind === 'drop') return cleanRecord({ ...common, resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text), ownerId: readInlineWord('owner', text) ?? readInlineWord('ownerId', text), lifetimeRegionId: readInlineWord('lifetime', text) ?? readInlineWord('lifetimeRegion', text) ?? readInlineWord('lifetimeRegionId', text), dropKind: readInlineWord('dropKind', text) ?? readInlineWord('kind', text) ?? 'drop', line: readInlineNumber('line', text), order: readInlineNumber('order', text) });
97
+ if (kind === 'escape') return cleanRecord({ ...common, resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text), ownerId: readInlineWord('owner', text) ?? readInlineWord('ownerId', text), lifetimeRegionId: readInlineWord('lifetime', text) ?? readInlineWord('lifetimeRegion', text) ?? readInlineWord('lifetimeRegionId', text), loanId: readInlineWord('loan', text) ?? readInlineWord('loanId', text), escapeKind: readInlineWord('escapeKind', text) ?? readInlineWord('kind', text) ?? 'escape', status: readInlineWord('status', text) ?? 'needs-proof' });
98
+ if (kind === 'lifetimeRegion') return cleanRecord({ ...common, lifetimeKind: readInlineWord('lifetimeKind', text) ?? readInlineWord('kind', text) ?? 'lexical', startLine: readInlineNumber('startLine', text), endLine: readInlineNumber('endLine', text) });
99
+ if (kind === 'lifetimeRelation') return cleanRecord({ ...common, relationKind: readInlineWord('relationKind', text) ?? readInlineWord('kind', text) ?? 'outlives', fromLifetimeId: readInlineWord('fromLifetime', text) ?? readInlineWord('fromLifetimeId', text) ?? readInlineWord('from', text), toLifetimeId: readInlineWord('toLifetime', text) ?? readInlineWord('toLifetimeId', text) ?? readInlineWord('to', text), from: readInlineWord('from', text), to: readInlineWord('to', text) });
100
+ if (kind === 'borrowScope') return cleanRecord({ ...common, scopeKind: readInlineWord('scopeKind', text) ?? readInlineWord('kind', text) ?? 'borrow-scope', constraintKind: readInlineWord('constraintKind', text), constraintKinds: readInlineList(text, 'constraint', 'constraints', 'constraintKind', 'constraintKinds') ?? [], ownershipKind: readInlineWord('ownershipKind', text), lifetimeKind: readInlineWord('lifetimeKind', text), controlFlowKind: readInlineWord('controlFlowKind', text), sourceControlFlowId: readInlineWord('sourceControlFlow', text) ?? readInlineWord('sourceControlFlowId', text), lifetimeRegionId: readInlineWord('lifetime', text) ?? readInlineWord('lifetimeRegion', text) ?? readInlineWord('lifetimeRegionId', text), resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text) });
101
+ if (kind === 'unsafeBoundary') return cleanRecord({ ...common, resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text), unsafeBoundary: true, proofStatus: readInlineWord('proofStatus', text) ?? readInlineWord('status', text) ?? 'missing', kind: readInlineWord('kind', text) });
102
+ if (kind === 'conflict') return cleanRecord({ ...common, resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text), ownerId: readInlineWord('owner', text) ?? readInlineWord('ownerId', text), loanId: readInlineWord('loan', text) ?? readInlineWord('loanId', text), aliasId: readInlineWord('alias', text) ?? readInlineWord('aliasId', text), unsafeBoundaryId: readInlineWord('unsafeBoundary', text) ?? readInlineWord('unsafeBoundaryId', text), reasonCode: readInlineWord('reasonCode', text), message: readInlineQuoted('message', text), status: readInlineWord('status', text) ?? 'open', severity: readInlineWord('severity', text) ?? 'error' });
103
+ if (kind === 'proofObligation') return cleanRecord({ ...common, resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text), conflictId: readInlineWord('conflict', text) ?? readInlineWord('conflictId', text), kind: readInlineWord('kind', text), status: readInlineWord('status', text) ?? 'open', statement: readInlineQuoted('statement', text) });
104
+ return undefined;
105
+ }
106
+
107
+ function commonRecord(kind, name, text, graph) {
108
+ return cleanRecord({
109
+ recordKind: recordKind(kind),
110
+ id: idFrom(text, `${recordPrefix(kind)}_${name}`),
111
+ name,
112
+ sourcePath: readInlineWord('sourcePath', text) ?? readInlineWord('path', text) ?? graph.sourcePath,
113
+ sourceHash: readInlineWord('sourceHash', text) ?? graph.sourceHash,
114
+ evidenceIds: readInlineList(text, 'evidence', 'evidenceIds') ?? graph.evidenceIds,
115
+ metadata: { authoredName: name }
116
+ });
117
+ }
118
+
119
+ function summarize(graph) {
120
+ return {
121
+ records: allRecords(graph).length,
122
+ resources: graph.resources.length,
123
+ owners: graph.owners.length,
124
+ loans: graph.loans.length,
125
+ aliases: graph.aliases.length,
126
+ moves: graph.moves.length,
127
+ drops: graph.drops.length,
128
+ escapes: graph.escapes.length,
129
+ lifetimeRegions: graph.lifetimeRegions.length,
130
+ lifetimeRelations: graph.lifetimeRelations.length,
131
+ borrowScopes: graph.borrowScopes.length,
132
+ unsafeBoundaries: graph.unsafeBoundaries.length,
133
+ conflicts: graph.conflicts.length,
134
+ proofObligations: graph.proofObligations.length,
135
+ unsafeBoundariesWithoutProof: graph.unsafeBoundaries.filter((record) => record.proofStatus !== 'passed').length,
136
+ reasonCodes: unique(graph.conflicts.map((record) => record.reasonCode))
137
+ };
138
+ }
139
+
140
+ function allRecords(graph) {
141
+ return [
142
+ ...graph.resources,
143
+ ...graph.owners,
144
+ ...graph.loans,
145
+ ...graph.aliases,
146
+ ...graph.moves,
147
+ ...graph.drops,
148
+ ...graph.escapes,
149
+ ...graph.lifetimeRegions,
150
+ ...graph.lifetimeRelations,
151
+ ...graph.borrowScopes,
152
+ ...graph.unsafeBoundaries,
153
+ ...graph.conflicts,
154
+ ...graph.proofObligations
155
+ ];
156
+ }
157
+
158
+ function normalizeRowKind(kind) {
159
+ if (kind === 'lifetime' || kind === 'life') return 'lifetimeRegion';
160
+ if (kind === 'outlives' || kind === 'lifetimeRelation' || kind === 'lifeRelation') return 'lifetimeRelation';
161
+ if (kind === 'borrow' || kind === 'borrowScope' || kind === 'borrowRegion') return 'borrowScope';
162
+ if (kind === 'unsafe' || kind === 'unsafeBoundary') return 'unsafeBoundary';
163
+ if (kind === 'proof' || kind === 'obligation' || kind === 'proofObligation') return 'proofObligation';
164
+ return kind;
165
+ }
166
+
167
+ function recordKind(kind) {
168
+ if (kind === 'lifetimeRegion') return 'lifetime-region';
169
+ if (kind === 'lifetimeRelation') return 'lifetime-relation';
170
+ if (kind === 'borrowScope') return 'borrow-scope';
171
+ if (kind === 'unsafeBoundary') return 'unsafe-boundary';
172
+ if (kind === 'proofObligation') return 'proof-obligation';
173
+ return kind;
174
+ }
175
+
176
+ function recordPrefix(kind) {
177
+ return recordKind(kind).replace(/-/g, '_');
178
+ }
179
+
180
+ function isGraphPropertyLine(line) {
181
+ return /^(sourceLanguage|language|sourcePath|path|sourceHash|status|evidence|evidenceIds)\s+/.test(line);
182
+ }
183
+
184
+ function idFrom(text, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(text)?.[1] ?? fallback; }
185
+ function nameFrom(header) { return /^([A-Za-z_$][\w$]*)/.exec(header)?.[1] ?? 'ResourceGraph'; }
186
+ function readLine(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\n]+)', 'm').exec(body)?.[1]?.trim(); }
187
+ function readListLine(label, body) {
188
+ const line = readLine(label, body);
189
+ return line ? line.split(/[|,]/).map((item) => item.trim()).filter(Boolean) : undefined;
190
+ }
191
+ function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
192
+ function readInlineQuoted(label, text) { return new RegExp("(?:^|\\s)" + label + "\\s+[\"']([^\"']+)[\"']").exec(text)?.[1]?.trim(); }
193
+ function readInlineNumber(label, text) {
194
+ const value = readInlineWord(label, text);
195
+ return value === undefined ? undefined : Number(value);
196
+ }
197
+ function readInlineList(text, ...labels) {
198
+ for (const label of labels) {
199
+ const value = new RegExp('(?:^|\\s)' + label + '\\s+([^\\s]+)').exec(text)?.[1]?.trim();
200
+ if (value) return value.split(/[|,]/).map((item) => item.trim()).filter(Boolean);
201
+ }
202
+ return undefined;
203
+ }
204
+ function ids(records = []) { return records.map((record) => record?.id).filter(Boolean); }
205
+ function unique(values = []) { return [...new Set(values.filter(Boolean))]; }
206
+ function cleanRecord(record) {
207
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0)));
208
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
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",
25
+ "test": "npm run build && node test/smoke.mjs && node test/resource-graph-smoke.mjs && node test/interlingua-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",