@shapeshift-labs/frontier-lang-parser 0.3.26 → 0.3.28

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
@@ -260,6 +260,55 @@ and events stay attached to the child render node instead of leaking onto the
260
260
  parent. `kind component` records `component` instead of a literal HTML tag, so
261
261
  target adapters can decide how to project it.
262
262
 
263
+ ## Authored package and canvas syntax
264
+
265
+ `.frontier` files can describe package-management and canvas semantic surfaces
266
+ directly, without requiring the source of truth to be a generated JSON tree.
267
+ These blocks make dependency, script, export, draw-command, command-trace, and
268
+ proof-gap records queryable as source-level evidence. They intentionally do not
269
+ claim package install equivalence, canvas runtime equivalence, or visual
270
+ equivalence.
271
+
272
+ ```frontier
273
+ packageManifest AppPackage @id("pkg_manifest_app") {
274
+ sourcePath package.json
275
+ sourceHash sha256:package
276
+ packageManager npm@11.0.0
277
+ evidence packageProbe @id("evidence_package_probe") kind test status passed path reports/package.json
278
+ metadata name @id("pkg_meta_name") value "@example/app" evidence evidence_package_probe
279
+ dependency react @id("pkg_dep_react") section dependencies range ^19.0.0 evidence evidence_package_probe
280
+ dependency typescript @id("pkg_dep_typescript") section peerDependencies range ^5.9.0 proofGap package-peer-compatibility-boundary evidence evidence_package_probe
281
+ script test @id("pkg_script_test") command "vitest --run" proofGap package-script-runtime-boundary evidence evidence_package_probe
282
+ export root @id("pkg_export_root") section exports name . target ./dist/index.js proofGap package-conditional-resolution-boundary evidence evidence_package_probe
283
+ gap workspace @id("pkg_gap_workspace") code package-workspace-graph-boundary summary "Workspace expansion requires repository graph evidence."
284
+ }
285
+
286
+ canvasSurface PreviewCanvas @id("canvas_surface_preview") {
287
+ sourcePath src/draw.js
288
+ sourceHash sha256:draw
289
+ evidence canvasProbe @id("evidence_canvas_probe") kind browser-probe status passed path reports/canvas.json
290
+ element preview @id("canvas_element_preview") name canvas category html-canvas order 1 identity canvas:preview attributes data-frontier-key=preview|width=100 evidence evidence_canvas_probe
291
+ command context @id("canvas_command_context") name getContext category context context 2d order 2 proofGap canvas-context-runtime-boundary evidence evidence_canvas_probe
292
+ state fillStyle @id("canvas_state_fill_style") name fillStyle category state order 3 proofGap canvas-stateful-render-order-boundary evidence evidence_canvas_probe
293
+ command fill @id("canvas_command_fill") name fillRect category draw context 2d order 4 proofGap canvas-stateful-render-order-boundary evidence evidence_canvas_probe
294
+ command offscreen @id("canvas_command_offscreen") name transferControlToOffscreen category offscreen order 5 proofGap canvas-offscreen-worker-boundary evidence evidence_canvas_probe
295
+ trace drawFrame @id("canvas_trace_draw_frame") commands getContext|fillStyle|fillRect|transferControlToOffscreen evidence evidence_canvas_probe
296
+ gap image @id("canvas_gap_image") code canvas-image-resource-boundary summary "Image drawing needs bitmap/resource evidence."
297
+ }
298
+ ```
299
+
300
+ The parser stores package blocks in `metadata.packageManifests` and canvas
301
+ blocks in `metadata.canvasSurfaces`, and mirrors both under
302
+ `metadata.universalAst` so compiler and merge tooling can see them next to
303
+ native-source source maps and merge candidates. Package records keep
304
+ `packageInstallEquivalenceClaim`, `installEquivalenceClaim`, and
305
+ `runtimeEquivalenceClaim` false. Canvas records keep
306
+ `browserRuntimeEquivalenceClaim`, `canvasRuntimeEquivalenceClaim`, and
307
+ `canvasVisualEquivalenceClaim` false.
308
+ Runtime probes, package-manager solver output, lockfile proof, browser pixels,
309
+ worker transfer evidence, and GPU validation must still be supplied by higher
310
+ layers before admission.
311
+
263
312
  ## Authored target projection syntax
264
313
 
265
314
  `.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.
@@ -368,12 +417,20 @@ conversion TodoJavascriptToRust @id("conversion_todo_js_rust") {
368
417
  constraint memory-model todoMemory @id("memory_model_todo") role source kind stable-reference resource TodoDb.todos memoryKind shared-memory memoryOrder acquire lockId lock:todo shared evidence artifact_todo_title_probe
369
418
  constraint effect-constraint todoWrite @id("effect_constraint_todo_write") role source kind storage-write capability storage.write resource TodoDb.todos adapterRequired evidence artifact_todo_title_probe
370
419
  constraint host-environment browserFetch @id("host_environment_fetch") role source kind browser-api capability fetch apiName fetch globalName window permission network adapterRequired evidence artifact_todo_title_probe
420
+ constraint callable-boundary saveUser @id("callable_boundary_save_user") role source kind method-call callableKind function functionName saveUser parameterCount 2 parameterOrder user|options returnKind promise asyncKind async evidence artifact_todo_title_probe
421
+ constraint adt-pattern resultShape @id("adt_pattern_result") role source kind tagged-union adtKind union typeName UserResult variantNames Ok|Err payloadFieldNames value|error exhaustivenessKinds total evidence artifact_todo_title_probe
422
+ constraint numeric-semantics amountNumber @id("numeric_semantics_amount") role source kind integer numericKind int width 53 overflowMode safe-integer specialValues nan|infinity evidence artifact_todo_title_probe
423
+ constraint text-semantics titleText @id("text_semantics_title") role source kind string encoding utf-16 normalizationForm nfc boundaryKinds grapheme|word evidence artifact_todo_title_probe
424
+ constraint collection-semantics todoList @id("collection_semantics_todos") role source kind array collectionKind array elementKind Todo iterationOrder insertion duplicatePolicy allow evidence artifact_todo_title_probe
425
+ constraint serialization-semantics todoJson @id("serialization_semantics_todo_json") role source kind json format json codec JSON.stringify schemaName TodoPayload deterministic evidence artifact_todo_title_probe
426
+ constraint dependency-semantics npmReact @id("dependency_semantics_react") role source kind package packageManager npm packageName react versionRange ^19.0.0 lockfile package-lock.json integrity sha512-demo evidence artifact_todo_title_probe
427
+ constraint protocol serializable @id("protocol_serializable") role target kind trait-bound protocolKind trait traitName Serializable requirementNames serialize|deserialize evidence artifact_todo_title_probe
371
428
  }
372
429
  ```
373
430
 
374
431
  `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.
375
432
 
376
- `constraint` rows accept every universal conversion constraint family used by route admission, including hyphenated spellings such as `module-constraint`, `scope-binding`, `memory-model`, `effect-constraint`, `control-flow`, `borrow-scope`, `borrow-checker`, `host-environment`, `data-layout`, and `object-model`. The parser preserves family-specific fields such as module specifiers, package conditions, binding/reference ids, memory ordering, locks, capabilities, host permissions, ABI/layout hints, and effect adapters as authored evidence inputs. Record-level targets use explicit labels such as `effectTarget` so `role target` rows cannot be mistaken for an authored target field. These rows do not prove translation equivalence; they make the required proof surface explicit for downstream gates and admission records.
433
+ `constraint` rows accept every universal conversion constraint family used by route admission, including hyphenated spellings such as `module-constraint`, `scope-binding`, `memory-model`, `effect-constraint`, `control-flow`, `borrow-scope`, `borrow-checker`, `host-environment`, `callable-boundary`, `adt-pattern`, `data-layout`, `numeric-semantics`, `text-semantics`, `collection-semantics`, `serialization-semantics`, `dependency-semantics`, `object-model`, and `protocol`. The parser preserves family-specific fields such as module specifiers, package conditions, binding/reference ids, memory ordering, locks, capabilities, host permissions, callable signatures, pattern exhaustiveness, ABI/layout hints, numeric/text/collection behavior, wire formats, dependency lockfile evidence, and effect adapters as authored evidence inputs. Record-level targets use explicit labels such as `effectTarget` so `role target` rows cannot be mistaken for an authored target field. These rows do not prove translation equivalence; they make the required proof surface explicit for downstream gates and admission records.
377
434
 
378
435
  ## Authored dialect registry syntax
379
436
 
@@ -0,0 +1,206 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+
3
+ export function parseCanvasSurfaceBlock(block) {
4
+ const name = nameFrom(block.header);
5
+ const sourcePath = readLine('sourcePath', block.body) ?? readLine('path', block.body);
6
+ const sourceHash = readLine('sourceHash', block.body);
7
+ const evidence = parseEvidenceRows(block.body);
8
+ const records = [];
9
+ const commandTraces = [];
10
+ const proofGaps = [];
11
+ for (const rawLine of block.body.split('\n')) {
12
+ const line = rawLine.trim();
13
+ if (!line || line.startsWith('#') || isPropertyLine(line)) continue;
14
+ const match = /^(element|command|state|stateWrite|trace|gap|proofGap)\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
15
+ if (!match) continue;
16
+ const [, rowKind, rowName, rest] = match;
17
+ if (rowKind === 'gap' || rowKind === 'proofGap') {
18
+ proofGaps.push(canvasProofGap(rowName, rest));
19
+ continue;
20
+ }
21
+ if (rowKind === 'trace') {
22
+ commandTraces.push(canvasTrace(rowName, rest, { sourcePath }));
23
+ continue;
24
+ }
25
+ records.push(canvasRecord(rowKind === 'stateWrite' ? 'state-write' : rowKind, rowName, rest, { sourcePath, sourceHash }));
26
+ }
27
+ const allGaps = [...records.flatMap((record) => record.proofGaps ?? []), ...proofGaps];
28
+ const tree = {
29
+ kind: 'frontier.lang.canvasSemanticTree',
30
+ version: 1,
31
+ id: idFrom(block.header, `canvas_surface_${name}`),
32
+ name,
33
+ sourcePath,
34
+ sourceHash,
35
+ records,
36
+ commandTraces,
37
+ proofGaps: allGaps,
38
+ evidence,
39
+ parser: { status: 'authored', errors: [] },
40
+ claims: {
41
+ autoMergeClaim: false,
42
+ semanticEquivalenceClaim: false,
43
+ browserRuntimeEquivalenceClaim: false,
44
+ canvasRuntimeEquivalenceClaim: false,
45
+ canvasVisualEquivalenceClaim: false
46
+ },
47
+ metadata: { authoredName: name }
48
+ };
49
+ tree.summary = summarizeCanvasSurface(tree);
50
+ tree.treeHash = hashSemanticValue({ kind: 'frontier.lang.canvas.authoredTree.v1', records: records.map(hashableRecord), proofGaps: allGaps.map((gap) => gap.code), commandTraces: commandTraces.map((trace) => trace.traceHash) });
51
+ return tree;
52
+ }
53
+
54
+ function canvasRecord(kind, name, text, context) {
55
+ const category = readInlineWord('category', text) ?? defaultCategory(kind);
56
+ const recordName = readInlineWord('name', text) ?? name;
57
+ const order = readInlineNumber('order', text) ?? readInlineNumber('renderOrder', text) ?? 0;
58
+ const proofGaps = readInlineList(text, 'proofGap', 'proofGaps', 'gap', 'gaps')?.map((code) => canvasProofGap(code, '')) ?? [];
59
+ const textValue = readInlineQuoted('text', text) ?? readInlineWord('text', text) ?? `${kind}:${category}:${recordName}:${order}`;
60
+ return cleanRecord({
61
+ kind,
62
+ id: idFrom(text, `canvas_${kind.replace(/-/g, '_')}_${safeId(recordName)}`),
63
+ name: recordName,
64
+ category,
65
+ contextKind: readInlineWord('context', text) ?? readInlineWord('contextKind', text),
66
+ renderOrder: order,
67
+ identityKey: readInlineWord('identity', text) ?? readInlineWord('identityKey', text) ?? `canvas:${kind}:${category}:${recordName}:${order || safeId(recordName)}`,
68
+ sourcePath: readInlineWord('sourcePath', text) ?? readInlineWord('path', text) ?? context.sourcePath,
69
+ sourceHash: readInlineWord('sourceHash', text) ?? context.sourceHash,
70
+ sourceSpan: parseSpan(readInlineWord('sourceSpan', text)),
71
+ textHash: hashSemanticValue({ kind: 'frontier.lang.canvas.authoredRecordText.v1', text: textValue }),
72
+ attributes: parsePairs(readInlineWord('attributes', text) ?? readInlineWord('attrs', text) ?? readInlineWord('attr', text)),
73
+ evidenceIds: readInlineList(text, 'evidence', 'evidenceIds'),
74
+ proofGaps
75
+ });
76
+ }
77
+
78
+ function canvasTrace(name, text, context) {
79
+ const commands = readInlineList(text, 'command', 'commands') ?? [];
80
+ const records = commands.map((command, index) => ({
81
+ kind: 'trace-command',
82
+ name: command,
83
+ category: readInlineWord('category', text) ?? 'custom',
84
+ ordinal: index + 1,
85
+ commandHash: hashSemanticValue({ kind: 'frontier.lang.canvas.authoredTraceCommand.v1', command, index }),
86
+ argsHash: hashSemanticValue({ kind: 'frontier.lang.canvas.authoredTraceArgs.v1', command, index }),
87
+ proofGaps: []
88
+ }));
89
+ return cleanRecord({
90
+ kind: 'frontier.lang.canvasCommandTrace',
91
+ version: 1,
92
+ id: idFrom(text, `canvas_trace_${name}`),
93
+ name,
94
+ sourcePath: readInlineWord('sourcePath', text) ?? readInlineWord('path', text) ?? context.sourcePath,
95
+ traceHash: hashSemanticValue({ kind: 'frontier.lang.canvas.authoredTrace.v1', commands }),
96
+ records,
97
+ evidenceIds: readInlineList(text, 'evidence', 'evidenceIds'),
98
+ runtimeEquivalenceClaim: false,
99
+ visualEquivalenceClaim: false,
100
+ summary: {
101
+ commands: records.length,
102
+ drawCommands: records.filter((record) => ['draw', 'image', 'text', 'path'].includes(record.category)).length,
103
+ proofGaps: 0
104
+ }
105
+ });
106
+ }
107
+
108
+ function canvasProofGap(name, text) {
109
+ const code = readInlineWord('code', text) ?? name;
110
+ return cleanRecord({
111
+ id: idFrom(text, `canvas_gap_${safeId(code)}`),
112
+ code,
113
+ status: readInlineWord('status', text) ?? 'not-claimed',
114
+ summary: readInlineQuoted('summary', text) ?? readInlineQuoted('message', text),
115
+ failClosed: true,
116
+ semanticEquivalenceClaim: false,
117
+ browserRuntimeEquivalenceClaim: false,
118
+ canvasRuntimeEquivalenceClaim: false,
119
+ canvasVisualEquivalenceClaim: false,
120
+ sourceSpan: parseSpan(readInlineWord('sourceSpan', text))
121
+ });
122
+ }
123
+
124
+ function parseEvidenceRows(body) {
125
+ const records = [];
126
+ for (const rawLine of body.split('\n')) {
127
+ const line = rawLine.trim();
128
+ const match = /^(?:evidence|proofEvidence)\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
129
+ if (!match) continue;
130
+ records.push(cleanRecord({
131
+ id: idFrom(match[2], `evidence_${match[1]}`),
132
+ kind: readInlineWord('kind', match[2]) ?? 'note',
133
+ status: readInlineWord('status', match[2]) ?? 'unknown',
134
+ path: readInlineWord('path', match[2]),
135
+ summary: readInlineQuoted('summary', match[2]),
136
+ metadata: { name: match[1] }
137
+ }));
138
+ }
139
+ return records;
140
+ }
141
+
142
+ function summarizeCanvasSurface(tree) {
143
+ return {
144
+ elements: tree.records.filter((record) => record.kind === 'element').length,
145
+ commands: tree.records.filter((record) => record.kind === 'command').length,
146
+ stateWrites: tree.records.filter((record) => record.kind === 'state-write').length,
147
+ drawCommands: tree.records.filter((record) => ['draw', 'image', 'text', 'path'].includes(record.category)).length,
148
+ offscreenCommands: tree.records.filter((record) => record.category === 'offscreen').length,
149
+ gpuCommands: tree.records.filter((record) => record.contextKind === 'webgl' || record.contextKind === 'webgl2' || record.contextKind === 'webgpu').length,
150
+ commandTraces: tree.commandTraces.length,
151
+ proofGaps: tree.proofGaps.length,
152
+ parseErrors: tree.parser.errors.length
153
+ };
154
+ }
155
+
156
+ function defaultCategory(kind) {
157
+ if (kind === 'element') return 'html-canvas';
158
+ if (kind === 'state-write') return 'state';
159
+ return 'draw';
160
+ }
161
+
162
+ function parsePairs(value) {
163
+ if (!value) return undefined;
164
+ const pairs = Object.fromEntries(value.split(/[|,]/).map((item) => {
165
+ const [key, rawValue] = item.split('=');
166
+ return [key, rawValue ?? true];
167
+ }).filter(([key]) => key));
168
+ return Object.keys(pairs).length ? pairs : undefined;
169
+ }
170
+
171
+ function hashableRecord(record) {
172
+ return { kind: record.kind, name: record.name, category: record.category, contextKind: record.contextKind, renderOrder: record.renderOrder, proofGaps: record.proofGaps?.map((gap) => gap.code) };
173
+ }
174
+
175
+ function isPropertyLine(line) {
176
+ return /^(sourcePath|path|sourceHash|evidence|proofEvidence)\s+/.test(line);
177
+ }
178
+
179
+ function idFrom(text, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(text)?.[1] ?? fallback; }
180
+ function nameFrom(header) { return /^([A-Za-z_$][\w$-]*)/.exec(header)?.[1] ?? 'CanvasSurface'; }
181
+ function readLine(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\n]+)', 'm').exec(body)?.[1]?.trim(); }
182
+ function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
183
+ function readInlineQuoted(label, text) { return new RegExp("(?:^|\\s)" + label + "\\s+[\"']([^\"']+)[\"']").exec(text)?.[1]?.trim(); }
184
+ function readInlineNumber(label, text) {
185
+ const value = readInlineWord(label, text);
186
+ return value === undefined ? undefined : Number(value);
187
+ }
188
+ function readInlineList(text, ...labels) {
189
+ for (const label of labels) {
190
+ const value = new RegExp('(?:^|\\s)' + label + '\\s+([^\\s]+)').exec(text)?.[1]?.trim();
191
+ if (value) return value.split(/[|,]/).map((item) => item.trim()).filter(Boolean);
192
+ }
193
+ return undefined;
194
+ }
195
+ function parseSpan(value) {
196
+ if (!value) return undefined;
197
+ const match = /^(.+?):(\d+):(\d+)-(\d+):(\d+)$/.exec(value);
198
+ if (!match) return { path: value };
199
+ return { path: match[1], startLine: Number(match[2]), startColumn: Number(match[3]), endLine: Number(match[4]), endColumn: Number(match[5]) };
200
+ }
201
+ function cleanRecord(record) {
202
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0) && (!value || typeof value !== 'object' || Object.keys(value).length > 0)));
203
+ }
204
+ function safeId(value) {
205
+ return String(value ?? 'unknown').replace(/[^A-Za-z0-9_$-]+/g, '_');
206
+ }
@@ -47,14 +47,75 @@ const WORD_FIELDS = [
47
47
  ['dropKind'], ['resourceKind'], ['scopeKind'], ['typeKind'], ['signatureHash'], ['contractHash'], ['typeHash'], ['flowKind'],
48
48
  ['controlFlowKind'], ['sourceControlFlowId'], ['sourceId', 'from', 'sourceId'], ['targetId', 'to', 'targetId'], ['label'],
49
49
  ['conditionHash'], ['orderingKey', 'orderingKey', 'orderKey'], ['lifetimeKind'], ['lifetimeRegionId', 'lifetimeRegion', 'lifetimeRegionId'],
50
- ['regionKind'], ['resourceId', 'resource', 'resourceId'], ['sourcePath', 'sourcePath', 'path'], ['sourceHash'], ['target', 'effectTarget', 'targetResource']
50
+ ['regionKind'], ['resourceId', 'resource', 'resourceId'],
51
+ ['functionName'], ['methodName'], ['callableName'], ['callableKind'], ['callSignatureHash'],
52
+ ['receiverKind', 'receiverKind', 'receiver'], ['thisBinding'], ['selfBinding'], ['returnKind', 'returnKind', 'returnType'],
53
+ ['asyncKind', 'asyncKind', 'asyncMode'], ['generatorKind', 'generatorKind', 'yieldKind'], ['callbackKind'],
54
+ ['closureCapture', 'closureCapture', 'captureKind'], ['overloadSet', 'overloadSet', 'overloads'],
55
+ ['dispatchKind', 'dispatchKind', 'dispatchMode'], ['constructorKind'], ['abiKind'], ['ffiBoundary', 'ffiBoundary', 'foreignFunction'],
56
+ ['exceptionModel'], ['adtKind'], ['patternKind', 'patternKind', 'matchKind', 'caseKind'], ['typeName'], ['enumName'], ['unionName'],
57
+ ['symbolKind'], ['relationKind'], ['representationKind'], ['typeId', 'typeId', 'nodeId'], ['nodeId'], ['structId'], ['unionId'],
58
+ ['enumId'], ['fieldId'], ['bitfieldId'], ['endianness', 'endianness', 'endian'], ['repr', 'repr', 'reprAttribute'],
59
+ ['numericKind', 'numericKind', 'numberKind'], ['numberKind'], ['numericTypeName'], ['signedness'], ['overflowMode', 'overflowMode', 'overflowBehavior'],
60
+ ['divisionMode', 'divisionMode', 'integerDivisionMode'], ['moduloMode', 'moduloMode', 'remainderMode'], ['floatPrecision'], ['roundingMode'],
61
+ ['separatorPolicy'], ['coercionKind', 'coercionKind', 'conversionKind'], ['conversionKind'], ['literalKind'],
62
+ ['textKind', 'textKind', 'stringKind'], ['stringKind'], ['stringTypeName'], ['encoding', 'encoding', 'charset', 'codepage'],
63
+ ['charset'], ['codepage'], ['codeUnit', 'codeUnit', 'codeUnitWidth'], ['indexingUnit', 'indexingUnit', 'indexUnit'],
64
+ ['normalizationForm', 'normalizationForm', 'normalization'], ['locale', 'locale', 'localePolicy'], ['collation', 'collation', 'collationPolicy'],
65
+ ['caseMapping', 'caseMapping', 'caseFolding'], ['regexEngine', 'regexEngine', 'regexFlavor'], ['escapeMode', 'escapeMode', 'escaping'],
66
+ ['interpolationMode', 'interpolationMode', 'interpolation'], ['termination', 'termination', 'nullTermination'], ['boundaryKind', 'boundaryKind', 'byteBoundary'],
67
+ ['collectionKind', 'collectionKind', 'containerKind'], ['containerKind'], ['collectionTypeName'], ['elementKind', 'elementKind', 'elementType'],
68
+ ['keyKind', 'keyKind', 'keyType'], ['valueKind', 'valueKind', 'valueType'], ['orderKind'], ['iterationOrder', 'iterationOrder', 'traversalOrder'],
69
+ ['duplicatePolicy', 'duplicatePolicy', 'duplicates'], ['equality', 'equality', 'equalitySemantics'], ['hash', 'hash', 'hashSemantics'],
70
+ ['comparator', 'comparator', 'comparison'], ['boundsBehavior', 'boundsBehavior', 'bounds'], ['lengthSemantics', 'lengthSemantics', 'sizeSemantics'],
71
+ ['sparseSemantics', 'sparseSemantics', 'holes'], ['mutability', 'mutability', 'collectionMutability', 'stringMutability'],
72
+ ['persistence', 'persistence', 'persistent'], ['iteratorInvalidation', 'iteratorInvalidation', 'invalidation'],
73
+ ['traversal', 'traversal', 'laziness'], ['capacityGrowth', 'capacityGrowth', 'growth'], ['concurrency', 'concurrency', 'threadSafety'],
74
+ ['format', 'format', 'wireFormat', 'serializationFormat'], ['wireFormat'], ['serializationFormat'], ['codec', 'codec', 'runtimeCodec'],
75
+ ['schemaName', 'schemaName', 'schemaId', 'schema'], ['schemaId'], ['fieldNaming', 'fieldNaming', 'naming'], ['fieldOrder', 'fieldOrder', 'order'],
76
+ ['omissionPolicy'], ['defaultValueSemantics'], ['nullSemantics', 'nullSemantics', 'nullability'], ['unknownFieldPolicy'],
77
+ ['enumEncoding', 'enumEncoding', 'tagEncoding'], ['varint', 'varint', 'varintEncoding'], ['schemaVersion', 'schemaVersion', 'version'],
78
+ ['compatibility'], ['canonicalization'], ['precision', 'precision', 'precisionLoss'], ['roundtrip', 'roundtrip', 'roundtripStability'],
79
+ ['validation'], ['securityEscaping'], ['packageManager', 'packageManager', 'manager'], ['manager'], ['manifestSchema', 'manifestSchema', 'manifestKind'],
80
+ ['manifestKind'], ['versionRange', 'versionRange', 'range'], ['resolvedVersion', 'resolvedVersion', 'version'], ['lockfile', 'lockfile', 'lockfilePath'],
81
+ ['integrity', 'integrity', 'lockfileIntegrity'], ['dependencyClass', 'dependencyClass', 'dependencyType'], ['dependencyType'],
82
+ ['workspace', 'workspace', 'workspaceBoundary'], ['registry', 'registry', 'registrySource'], ['sourceUrl'], ['nativeAbi', 'nativeAbi', 'abi'],
83
+ ['buildTool', 'buildTool', 'builder'], ['packageManagerVersion', 'packageManagerVersion', 'managerVersion'],
84
+ ['offlineCache', 'offlineCache', 'cachePolicy'], ['dedupeHoist', 'dedupeHoist', 'hoistPolicy'], ['provenance', 'provenance', 'sourceProvenance'],
85
+ ['trust', 'trust', 'supplyChainTrust'], ['concurrencyKind'], ['constructId', 'constructId', 'taskId', 'threadId', 'actorId', 'channelId'],
86
+ ['taskId'], ['threadId'], ['executor', 'executor', 'queue', 'runtime'], ['executorId'], ['isolationKey'], ['cancellationKey', 'cancellationKey', 'signalId', 'contextId'],
87
+ ['signalId'], ['contextId'], ['errorKind'], ['errorType', 'errorType', 'exceptionType', 'resultType'], ['exceptionType'], ['resultType'],
88
+ ['boundaryId', 'boundaryId', 'catchId', 'handlerId'], ['catchId'], ['handlerId'], ['evaluationKind', 'evaluationKind', 'expressionKind'],
89
+ ['expressionKind'], ['expressionId', 'expressionId', 'nodeId'], ['operator'], ['evaluationOrder', 'evaluationOrder', 'order'],
90
+ ['expansionKind', 'expansionKind', 'macroKind', 'templateKind', 'decoratorKind', 'generatorKind'], ['macroKind'], ['templateKind'],
91
+ ['decoratorKind'], ['expansionId'], ['generatorId'], ['generatedSourcePath'], ['expandedHash', 'expandedHash', 'generatedHash'],
92
+ ['generatedHash'], ['classId'], ['classKind'], ['prototypeId'], ['mixinId'], ['constructorId'], ['inheritanceKind'], ['referenceSemantics'],
93
+ ['valueSemantics'], ['protocolKind'], ['protocolName'], ['traitName'], ['interfaceName'], ['subjectName', 'subjectName', 'receiverName', 'implementedFor'],
94
+ ['implementedFor'], ['sourcePath', 'sourcePath', 'path'], ['sourceHash'], ['target', 'effectTarget', 'targetResource']
51
95
  ];
52
96
  const LIST_FIELDS = [
53
97
  ['factKinds', 'fact', 'facts', 'factKind', 'factKinds'], ['importAttributes', 'importAttribute', 'importAttributes', 'assertion', 'assertions'],
54
- ['reads', 'read', 'reads'], ['writes', 'write', 'writes'], ['evidenceIds', 'evidence', 'evidenceIds']
98
+ ['reads', 'read', 'reads'], ['writes', 'write', 'writes'],
99
+ ['requiredParameters'], ['optionalParameters'], ['parameterOrder', 'parameterOrder', 'orderedParameters'], ['defaultParameters', 'defaultParameters', 'defaults'],
100
+ ['namedArguments', 'namedArguments', 'keywordArguments'], ['effects'], ['variantNames', 'variantNames', 'variants', 'caseNames', 'cases', 'members'],
101
+ ['constructorNames', 'constructorNames', 'constructors', 'caseConstructors'], ['payloadFieldNames', 'payloadFieldNames', 'payloadFields', 'fields', 'tupleFields', 'recordFields'],
102
+ ['tagFieldNames', 'tagFieldNames', 'tagFields', 'discriminatorFields', 'discriminants'], ['matchArmNames', 'matchArmNames', 'matchArms', 'arms', 'switchCases'],
103
+ ['guardKinds', 'guardKinds', 'guards', 'whereClauses', 'conditions'], ['destructuringKinds', 'destructuringKinds', 'destructuring', 'bindingPatterns', 'deconstruction'],
104
+ ['exhaustivenessKinds', 'exhaustivenessKinds', 'exhaustiveness', 'coverageKinds'], ['fallbackKinds', 'fallbackKinds', 'fallbacks', 'defaultCases', 'wildcards'],
105
+ ['genericParameterNames', 'genericParameterNames', 'typeParameters', 'genericParameters'], ['specialValues', 'specialValues', 'floatSpecialValues'],
106
+ ['coercionKinds', 'coercionKinds', 'coercions'], ['literalKinds'], ['boundaryKinds', 'boundaryKinds', 'boundaries'],
107
+ ['omittedFields', 'omittedFields', 'unknownFields'], ['defaultValues'], ['peerDependencies', 'peerDependencies', 'peers'], ['optionalDependencies'],
108
+ ['devDependencies'], ['features', 'features', 'extras', 'flags'], ['lifecycleScripts', 'lifecycleScripts', 'scripts'],
109
+ ['requirementNames', 'requirementNames', 'requirements', 'methods', 'members'], ['associatedTypeNames', 'associatedTypeNames', 'associatedTypes'],
110
+ ['boundNames', 'boundNames', 'bounds', 'traitBounds', 'protocolBounds', 'whereBounds'], ['implementationKinds', 'implementationKinds', 'implKinds', 'implementations'],
111
+ ['dispatchKinds', 'dispatchKinds', 'dispatchModes'], ['coherenceKinds', 'coherenceKinds', 'coherenceRules'], ['evidenceIds', 'evidence', 'evidenceIds']
55
112
  ];
56
- const NUMBER_FIELDS = [['arity'], ['size'], ['alignment']];
57
- const FLAG_FIELDS = ['nullable', 'optional', 'publicContract', 'closure', 'captured', 'writeExpr', 'mutable', 'shadowed', 'hoisted', 'typeOnly', 'isTypeReference', 'isValueReference', 'shared', 'volatile', 'atomic', 'adapterRequired', 'async', 'generator', 'exceptional', 'cancellable'];
113
+ const NUMBER_FIELDS = [
114
+ ['arity'], ['size'], ['alignment'], ['parameterCount'], ['requiredParameterCount'], ['optionalParameterCount'],
115
+ ['sizeBytes'], ['alignmentBytes'], ['offsetBytes'], ['pointerWidth'], ['integerWidth'], ['bitWidth'], ['width'],
116
+ ['codeUnitWidth'], ['indexBase']
117
+ ];
118
+ const FLAG_FIELDS = ['nullable', 'optional', 'publicContract', 'closure', 'captured', 'writeExpr', 'mutable', 'shadowed', 'hoisted', 'typeOnly', 'isTypeReference', 'isValueReference', 'shared', 'volatile', 'atomic', 'adapterRequired', 'async', 'generator', 'exceptional', 'cancellable', 'variadic', 'signed', 'nan', 'infinity', 'deterministic', 'streaming', 'framing', 'copyOnWrite', 'multipleInheritance', 'reflection', 'staticDispatch', 'virtual', 'spawn', 'await', 'structured', 'reentrant', 'cancelable'];
58
119
 
59
120
  export const FAMILIES = Object.freeze(Object.fromEntries(FAMILY_ROWS.flatMap(([name, field, sourceKey, targetKey, aliases = [], extra = {}]) => {
60
121
  const config = { field, sourceKey, targetKey, ...extra };
package/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import { actionNode, capabilityNode, createDocument, effectNode, entityNode, externNode, latticeNode, migrationNode, stateNode, targetNode, typeNode } from '@shapeshift-labs/frontier-lang-kernel';
2
2
  import { parseConstraintSpaceBlock } from './constraint-space.js';
3
3
  import { parseConversionBlock } from './conversion.js';
4
+ import { parseCanvasSurfaceBlock } from './canvas-surface.js';
4
5
  import { parseDecisionGraphBlock } from './decision-graph.js';
5
6
  import { parseDialectRegistryBlock } from './dialect-registry.js';
6
7
  import { parseInterlinguaBlock } from './interlingua.js';
7
8
  import { createParsedMetadata } from './metadata.js';
8
9
  import { parseSemanticOperationsBlock } from './operations.js';
10
+ import { parsePackageManifestBlock } from './package-manifest.js';
9
11
  import { parseParadigmBlock } from './paradigm.js';
10
12
  import { parseProofBlock } from './proof.js';
11
13
  import { parseResourceGraphBlock } from './resource-graph.js';
@@ -25,6 +27,8 @@ export function parseFrontierSource(source, options = {}) {
25
27
  const interlinguaBlocks = [];
26
28
  const resourceGraphBlocks = [];
27
29
  const nativeSourceBlocks = [];
30
+ const packageManifestBlocks = [];
31
+ const canvasSurfaceBlocks = [];
28
32
  const documentId = options.id ?? readId(source) ?? 'mod_frontier';
29
33
  const documentName = options.name ?? readName(source) ?? 'FrontierModule';
30
34
  for (const block of readBlocks(source)) {
@@ -53,8 +57,10 @@ export function parseFrontierSource(source, options = {}) {
53
57
  if (block.kind === 'dialectRegistry' || block.kind === 'universalDialectRegistry') dialectRegistryBlocks.push(parseDialectRegistryBlock(block));
54
58
  if (block.kind === 'interlingua' || block.kind === 'universalInterlingua') interlinguaBlocks.push(parseInterlinguaBlock(block));
55
59
  if (block.kind === 'resourceGraph' || block.kind === 'semanticResourceGraph') resourceGraphBlocks.push(parseResourceGraphBlock(block));
60
+ if (block.kind === 'packageManifest' || block.kind === 'packageGraph' || block.kind === 'packageSurface') packageManifestBlocks.push(parsePackageManifestBlock(block));
61
+ if (block.kind === 'canvasSurface' || block.kind === 'canvasGraph') canvasSurfaceBlocks.push(parseCanvasSurfaceBlock(block));
56
62
  }
57
- const metadata = createParsedMetadata({ proofBlocks, paradigmBlocks, operationBlocks, conversionBlocks, constraintSpaceBlocks, decisionGraphBlocks, dialectRegistryBlocks, interlinguaBlocks, resourceGraphBlocks, nativeSourceBlocks });
63
+ const metadata = createParsedMetadata({ proofBlocks, paradigmBlocks, operationBlocks, conversionBlocks, constraintSpaceBlocks, decisionGraphBlocks, dialectRegistryBlocks, interlinguaBlocks, resourceGraphBlocks, nativeSourceBlocks, packageManifestBlocks, canvasSurfaceBlocks });
58
64
  return createDocument({ id: documentId, name: documentName, nodes, ...(metadata ? { metadata } : {}) });
59
65
  }
60
66
 
@@ -64,7 +70,7 @@ function readName(source) { return /module\s+([A-Za-z_$][\w$]*)/.exec(source)?.[
64
70
  function readId(source) { return /module\s+[A-Za-z_$][\w$]*\s+@id\(\s*["']([^"']+)["']\s*\)/.exec(source)?.[1]; }
65
71
  function readBlocks(source) {
66
72
  const blocks = [];
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;
73
+ 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|packageManifest|packageGraph|packageSurface|canvasSurface|canvasGraph)\s+([^{}]+)\{/g;
68
74
  let match;
69
75
  while ((match = header.exec(source))) {
70
76
  let depth = 1; let index = header.lastIndex;
package/dist/metadata.js CHANGED
@@ -23,7 +23,7 @@ const PARADIGM_GROUPS = [
23
23
  'loweringRecords'
24
24
  ];
25
25
 
26
- export function createParsedMetadata({ proofBlocks = [], paradigmBlocks = [], operationBlocks = [], conversionBlocks = [], constraintSpaceBlocks = [], decisionGraphBlocks = [], dialectRegistryBlocks = [], interlinguaBlocks = [], resourceGraphBlocks = [], nativeSourceBlocks = [] } = {}) {
26
+ export function createParsedMetadata({ proofBlocks = [], paradigmBlocks = [], operationBlocks = [], conversionBlocks = [], constraintSpaceBlocks = [], decisionGraphBlocks = [], dialectRegistryBlocks = [], interlinguaBlocks = [], resourceGraphBlocks = [], nativeSourceBlocks = [], packageManifestBlocks = [], canvasSurfaceBlocks = [] } = {}) {
27
27
  const metadata = {};
28
28
  if (proofBlocks.length) metadata.proof = mergeProofBlocks(proofBlocks);
29
29
  if (paradigmBlocks.length) metadata.paradigmSemantics = mergeParadigmBlocks(paradigmBlocks);
@@ -34,8 +34,10 @@ export function createParsedMetadata({ proofBlocks = [], paradigmBlocks = [], op
34
34
  if (dialectRegistryBlocks.length) metadata.dialects = mergeDialectRegistryBlocks(dialectRegistryBlocks);
35
35
  if (interlinguaBlocks.length) metadata.universalInterlingua = mergeInterlinguaBlocks(interlinguaBlocks);
36
36
  if (resourceGraphBlocks.length) metadata.semanticResourceGraphs = mergeResourceGraphBlocks(resourceGraphBlocks);
37
- if (nativeSourceBlocks.some((block) => block.sourceMaps.length || block.mergeCandidates.length || block.evidence.length)) {
38
- metadata.universalAst = mergeNativeSourceBlocks(nativeSourceBlocks);
37
+ if (packageManifestBlocks.length) metadata.packageManifests = mergePackageManifestBlocks(packageManifestBlocks);
38
+ if (canvasSurfaceBlocks.length) metadata.canvasSurfaces = mergeCanvasSurfaceBlocks(canvasSurfaceBlocks);
39
+ if (nativeSourceBlocks.some((block) => block.sourceMaps.length || block.mergeCandidates.length || block.evidence.length) || packageManifestBlocks.length || canvasSurfaceBlocks.length) {
40
+ metadata.universalAst = mergeUniversalAstBlocks(nativeSourceBlocks, packageManifestBlocks, canvasSurfaceBlocks);
39
41
  }
40
42
  return Object.keys(metadata).length ? metadata : undefined;
41
43
  }
@@ -106,7 +108,7 @@ function mergeConstraintSpaceBlocks(blocks) {
106
108
  };
107
109
  }
108
110
 
109
- function mergeNativeSourceBlocks(blocks) {
111
+ function mergeUniversalAstBlocks(blocks, packageManifestBlocks = [], canvasSurfaceBlocks = []) {
110
112
  return {
111
113
  id: blocks.length === 1 ? `universalAst:${blocks[0].node.id}` : 'universalAst:source',
112
114
  nativeSourceIds: blocks.map((block) => block.node.id),
@@ -114,7 +116,71 @@ function mergeNativeSourceBlocks(blocks) {
114
116
  mergeCandidates: blocks.flatMap((block) => block.mergeCandidates ?? []),
115
117
  evidence: blocks.flatMap((block) => block.evidence ?? []),
116
118
  losses: blocks.flatMap((block) => block.losses ?? []),
117
- metadata: { authoredNativeSourceIds: blocks.map((block) => block.node.id) }
119
+ packageManifests: packageManifestBlocks,
120
+ canvasSurfaces: canvasSurfaceBlocks,
121
+ packageManifestIds: ids(packageManifestBlocks),
122
+ canvasSurfaceIds: ids(canvasSurfaceBlocks),
123
+ metadata: {
124
+ authoredNativeSourceIds: blocks.map((block) => block.node.id),
125
+ authoredPackageManifestIds: ids(packageManifestBlocks),
126
+ authoredCanvasSurfaceIds: ids(canvasSurfaceBlocks)
127
+ }
128
+ };
129
+ }
130
+
131
+ function mergePackageManifestBlocks(blocks) {
132
+ return {
133
+ id: blocks.length === 1 ? blocks[0].id : 'packageManifests:source',
134
+ manifests: blocks,
135
+ manifestIds: ids(blocks),
136
+ recordIds: blocks.flatMap((block) => ids(block.records)),
137
+ evidenceIds: blocks.flatMap((block) => ids(block.evidence)),
138
+ proofGapCodes: [...new Set(blocks.flatMap((block) => (block.proofGaps ?? []).map((gap) => gap.code).filter(Boolean)))],
139
+ summary: {
140
+ manifestCount: blocks.length,
141
+ recordCount: blocks.reduce((sum, block) => sum + (block.records?.length ?? 0), 0),
142
+ dependencyCount: blocks.reduce((sum, block) => sum + (block.summary?.dependencies ?? 0), 0),
143
+ scriptCount: blocks.reduce((sum, block) => sum + (block.summary?.scripts ?? 0), 0),
144
+ exportCount: blocks.reduce((sum, block) => sum + (block.summary?.exports ?? 0), 0),
145
+ proofGapCount: blocks.reduce((sum, block) => sum + (block.proofGaps?.length ?? 0), 0)
146
+ },
147
+ claims: {
148
+ autoMergeClaim: false,
149
+ semanticEquivalenceClaim: false,
150
+ packageInstallEquivalenceClaim: false,
151
+ installEquivalenceClaim: false,
152
+ runtimeEquivalenceClaim: false
153
+ },
154
+ metadata: { authoredPackageManifestIds: ids(blocks) }
155
+ };
156
+ }
157
+
158
+ function mergeCanvasSurfaceBlocks(blocks) {
159
+ return {
160
+ id: blocks.length === 1 ? blocks[0].id : 'canvasSurfaces:source',
161
+ surfaces: blocks,
162
+ surfaceIds: ids(blocks),
163
+ recordIds: blocks.flatMap((block) => ids(block.records)),
164
+ commandTraceIds: blocks.flatMap((block) => ids(block.commandTraces)),
165
+ evidenceIds: blocks.flatMap((block) => ids(block.evidence)),
166
+ proofGapCodes: [...new Set(blocks.flatMap((block) => (block.proofGaps ?? []).map((gap) => gap.code).filter(Boolean)))],
167
+ summary: {
168
+ surfaceCount: blocks.length,
169
+ recordCount: blocks.reduce((sum, block) => sum + (block.records?.length ?? 0), 0),
170
+ commandTraceCount: blocks.reduce((sum, block) => sum + (block.commandTraces?.length ?? 0), 0),
171
+ drawCommandCount: blocks.reduce((sum, block) => sum + (block.summary?.drawCommands ?? 0), 0),
172
+ offscreenCommandCount: blocks.reduce((sum, block) => sum + (block.summary?.offscreenCommands ?? 0), 0),
173
+ gpuCommandCount: blocks.reduce((sum, block) => sum + (block.summary?.gpuCommands ?? 0), 0),
174
+ proofGapCount: blocks.reduce((sum, block) => sum + (block.proofGaps?.length ?? 0), 0)
175
+ },
176
+ claims: {
177
+ autoMergeClaim: false,
178
+ semanticEquivalenceClaim: false,
179
+ browserRuntimeEquivalenceClaim: false,
180
+ canvasRuntimeEquivalenceClaim: false,
181
+ canvasVisualEquivalenceClaim: false
182
+ },
183
+ metadata: { authoredCanvasSurfaceIds: ids(blocks) }
118
184
  };
119
185
  }
120
186
 
@@ -0,0 +1,162 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+
3
+ export function parsePackageManifestBlock(block) {
4
+ const name = nameFrom(block.header);
5
+ const sourcePath = readLine('sourcePath', block.body) ?? readLine('path', block.body) ?? 'package.json';
6
+ const sourceHash = readLine('sourceHash', block.body);
7
+ const evidence = parseEvidenceRows(block.body);
8
+ const records = [];
9
+ const proofGaps = [];
10
+ for (const rawLine of block.body.split('\n')) {
11
+ const line = rawLine.trim();
12
+ if (!line || line.startsWith('#') || isPropertyLine(line)) continue;
13
+ const match = /^(metadata|dependency|script|export|gap|proofGap)\s+([A-Za-z_$.*@/][\w$./@*-]*)(.*)$/.exec(line);
14
+ if (!match) continue;
15
+ const [, rowKind, rowName, rest] = match;
16
+ if (rowKind === 'gap' || rowKind === 'proofGap') {
17
+ proofGaps.push(packageProofGap(rowName, rest));
18
+ continue;
19
+ }
20
+ const record = packageRecord(rowKind, rowName, rest, { sourcePath, sourceHash, evidence });
21
+ if (record) records.push(record);
22
+ }
23
+ const recordGaps = records.flatMap((record) => record.proofGaps ?? []);
24
+ const allGaps = [...recordGaps, ...proofGaps];
25
+ const tree = {
26
+ kind: 'frontier.lang.packageManifestSemanticTree',
27
+ version: 1,
28
+ id: idFrom(block.header, `package_manifest_${name}`),
29
+ name,
30
+ sourcePath,
31
+ sourceHash,
32
+ packageManager: readLine('packageManager', block.body),
33
+ records,
34
+ proofGaps: allGaps,
35
+ evidence,
36
+ parser: { status: 'authored', errors: [] },
37
+ claims: {
38
+ autoMergeClaim: false,
39
+ semanticEquivalenceClaim: false,
40
+ packageInstallEquivalenceClaim: false,
41
+ installEquivalenceClaim: false,
42
+ runtimeEquivalenceClaim: false
43
+ },
44
+ metadata: { authoredName: name }
45
+ };
46
+ tree.summary = summarizePackageManifest(tree);
47
+ tree.treeHash = hashSemanticValue({ kind: 'frontier.lang.package.authoredTree.v1', records: records.map(hashableRecord), proofGaps: allGaps.map((gap) => gap.code) });
48
+ return tree;
49
+ }
50
+
51
+ function packageRecord(kind, name, text, context) {
52
+ const section = readInlineWord('section', text) ?? defaultSection(kind);
53
+ const recordName = readInlineWord('name', text) ?? name;
54
+ const value = packageValue(kind, text);
55
+ const proofGaps = readInlineList(text, 'proofGap', 'proofGaps', 'gap', 'gaps')?.map((code) => packageProofGap(code, '')) ?? [];
56
+ return cleanRecord({
57
+ kind,
58
+ id: idFrom(text, `package_${kind}_${safeId(section)}_${safeId(recordName)}`),
59
+ section,
60
+ name: recordName,
61
+ value,
62
+ valueHash: hashSemanticValue({ kind: 'frontier.lang.package.authoredRecordValue.v1', value }),
63
+ identityKey: readInlineWord('identity', text) ?? readInlineWord('identityKey', text) ?? `${kind}:${section}:${recordName}`,
64
+ sourcePath: readInlineWord('sourcePath', text) ?? readInlineWord('path', text) ?? context.sourcePath,
65
+ sourceHash: readInlineWord('sourceHash', text) ?? context.sourceHash,
66
+ sourceSpan: parseSpan(readInlineWord('sourceSpan', text)),
67
+ evidenceIds: readInlineList(text, 'evidence', 'evidenceIds'),
68
+ proofGaps
69
+ });
70
+ }
71
+
72
+ function packageValue(kind, text) {
73
+ if (kind === 'dependency') return readInlineQuoted('value', text) ?? readInlineWord('range', text) ?? readInlineWord('value', text) ?? '*';
74
+ if (kind === 'script') return readInlineQuoted('command', text) ?? readInlineQuoted('value', text) ?? readInlineWord('command', text) ?? readInlineWord('value', text);
75
+ if (kind === 'export') return readInlineQuoted('target', text) ?? readInlineQuoted('value', text) ?? readInlineWord('target', text) ?? readInlineWord('value', text);
76
+ return readInlineQuoted('value', text) ?? readInlineWord('value', text);
77
+ }
78
+
79
+ function defaultSection(kind) {
80
+ if (kind === 'dependency') return 'dependencies';
81
+ if (kind === 'script') return 'scripts';
82
+ if (kind === 'export') return 'exports';
83
+ return 'metadata';
84
+ }
85
+
86
+ function packageProofGap(name, text) {
87
+ const code = readInlineWord('code', text) ?? name;
88
+ return cleanRecord({
89
+ id: idFrom(text, `package_gap_${safeId(code)}`),
90
+ code,
91
+ status: readInlineWord('status', text) ?? 'not-claimed',
92
+ summary: readInlineQuoted('summary', text) ?? readInlineQuoted('message', text),
93
+ failClosed: true,
94
+ semanticEquivalenceClaim: false,
95
+ packageInstallEquivalenceClaim: false,
96
+ installEquivalenceClaim: false,
97
+ runtimeEquivalenceClaim: false,
98
+ sourceSpan: parseSpan(readInlineWord('sourceSpan', text))
99
+ });
100
+ }
101
+
102
+ function parseEvidenceRows(body) {
103
+ const records = [];
104
+ for (const rawLine of body.split('\n')) {
105
+ const line = rawLine.trim();
106
+ const match = /^(?:evidence|proofEvidence)\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
107
+ if (!match) continue;
108
+ records.push(cleanRecord({
109
+ id: idFrom(match[2], `evidence_${match[1]}`),
110
+ kind: readInlineWord('kind', match[2]) ?? 'note',
111
+ status: readInlineWord('status', match[2]) ?? 'unknown',
112
+ path: readInlineWord('path', match[2]),
113
+ summary: readInlineQuoted('summary', match[2]),
114
+ metadata: { name: match[1] }
115
+ }));
116
+ }
117
+ return records;
118
+ }
119
+
120
+ function summarizePackageManifest(tree) {
121
+ return {
122
+ metadata: tree.records.filter((record) => record.kind === 'metadata').length,
123
+ dependencies: tree.records.filter((record) => record.kind === 'dependency').length,
124
+ scripts: tree.records.filter((record) => record.kind === 'script').length,
125
+ exports: tree.records.filter((record) => record.kind === 'export').length,
126
+ proofGaps: tree.proofGaps.length,
127
+ parseErrors: tree.parser.errors.length
128
+ };
129
+ }
130
+
131
+ function hashableRecord(record) {
132
+ return { kind: record.kind, section: record.section, name: record.name, value: record.value, proofGaps: record.proofGaps?.map((gap) => gap.code) };
133
+ }
134
+
135
+ function isPropertyLine(line) {
136
+ return /^(sourcePath|path|sourceHash|packageManager|evidence|proofEvidence)\s+/.test(line);
137
+ }
138
+
139
+ function idFrom(text, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(text)?.[1] ?? fallback; }
140
+ function nameFrom(header) { return /^([A-Za-z_$][\w$-]*)/.exec(header)?.[1] ?? 'PackageManifest'; }
141
+ function readLine(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\n]+)', 'm').exec(body)?.[1]?.trim(); }
142
+ function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
143
+ function readInlineQuoted(label, text) { return new RegExp("(?:^|\\s)" + label + "\\s+[\"']([^\"']+)[\"']").exec(text)?.[1]?.trim(); }
144
+ function readInlineList(text, ...labels) {
145
+ for (const label of labels) {
146
+ const value = new RegExp('(?:^|\\s)' + label + '\\s+([^\\s]+)').exec(text)?.[1]?.trim();
147
+ if (value) return value.split(/[|,]/).map((item) => item.trim()).filter(Boolean);
148
+ }
149
+ return undefined;
150
+ }
151
+ function parseSpan(value) {
152
+ if (!value) return undefined;
153
+ const match = /^(.+?):(\d+):(\d+)-(\d+):(\d+)$/.exec(value);
154
+ if (!match) return { path: value };
155
+ return { path: match[1], startLine: Number(match[2]), startColumn: Number(match[3]), endLine: Number(match[4]), endColumn: Number(match[5]) };
156
+ }
157
+ function cleanRecord(record) {
158
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0) && (!value || typeof value !== 'object' || Object.keys(value).length > 0)));
159
+ }
160
+ function safeId(value) {
161
+ return String(value ?? 'unknown').replace(/[^A-Za-z0-9_$-]+/g, '_');
162
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.26",
3
+ "version": "0.3.28",
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/conversion-constraint-fields-smoke.mjs && node test/view-render-graph-smoke.mjs && node test/resource-graph-smoke.mjs && node test/interlingua-smoke.mjs && node test/dialect-registry-smoke.mjs",
25
+ "test": "npm run build && node test/smoke.mjs && node test/conversion-constraint-fields-smoke.mjs && node test/package-canvas-surface-smoke.mjs && node test/view-render-graph-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",