@shapeshift-labs/frontier-lang-parser 0.3.13 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -220,6 +220,25 @@ npm install @shapeshift-labs/frontier-lang-parser
220
220
 
221
221
  The parser projects text into `@shapeshift-labs/frontier-lang-kernel` documents. The syntax is intentionally small and experimental.
222
222
 
223
+ ## Authored conversion syntax
224
+
225
+ `.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.
226
+
227
+ ```frontier
228
+ conversion TodoJavascriptToRust @id("conversion_todo_js_rust") {
229
+ sourceLanguage javascript
230
+ target rust
231
+ sourceRuntime javascript node
232
+ targetRuntime rust cli
233
+ runtimeRequirement fetchRuntime @id("runtime_requirement_fetch") capability fetch sourceRuntime node targetRuntime cli evidence artifact_todo_title_probe
234
+ dialect nodeProcess @id("dialect_node_process") language javascript dialect node.runtime kind runtime target rust disposition unsupported readiness blocked loss loss_node_process_projection
235
+ extern viteRoutes @id("extern_vite_routes") language javascript dialect vite.plugin.virtual-module externKind generatorArtifact target rust disposition runtime-required evidence evidence_vite_routes_manifest bindingSymbol virtual:routes
236
+ constraint type publicApi @id("type_constraint_public_api") role source kind public-function symbol symbol:addTodo signatureHash sig_add_todo evidence artifact_todo_title_probe
237
+ }
238
+ ```
239
+
240
+ `sourceRuntime` and `targetRuntime` become runtime maps. `runtimeRequirement` rows become proof obligations for host/runtime capabilities. `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.
241
+
223
242
  ## Benchmarks
224
243
 
225
244
  Run the package-local benchmark with:
@@ -1,10 +1,22 @@
1
1
  const FAMILIES = {
2
2
  type: { field: 'typeConstraints', sourceKey: 'sourceTypes', targetKey: 'targetTypes' },
3
3
  typeConstraint: { field: 'typeConstraints', sourceKey: 'sourceTypes', targetKey: 'targetTypes' },
4
+ resourceTransfer: { field: 'resourceTransfers', sourceKey: 'sourceGraphs', targetKey: 'targetGraphs', graph: true },
5
+ resourceTransferConstraint: { field: 'resourceTransfers', sourceKey: 'sourceGraphs', targetKey: 'targetGraphs', graph: true },
6
+ 'resource-transfer': { field: 'resourceTransfers', sourceKey: 'sourceGraphs', targetKey: 'targetGraphs', graph: true },
7
+ ownership: { field: 'resourceTransfers', sourceKey: 'sourceGraphs', targetKey: 'targetGraphs', graph: true },
4
8
  controlFlow: { field: 'controlFlowConstraints', sourceKey: 'sourceControlFlows', targetKey: 'targetControlFlows' },
5
9
  controlFlowConstraint: { field: 'controlFlowConstraints', sourceKey: 'sourceControlFlows', targetKey: 'targetControlFlows' },
6
10
  lifetime: { field: 'lifetimeConstraints', sourceKey: 'sourceLifetimeConstraints', targetKey: 'targetLifetimeConstraints' },
7
11
  lifetimeConstraint: { field: 'lifetimeConstraints', sourceKey: 'sourceLifetimeConstraints', targetKey: 'targetLifetimeConstraints' },
12
+ borrowScope: { field: 'borrowScopeConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
13
+ borrowScopeConstraint: { field: 'borrowScopeConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
14
+ 'borrow-scope': { field: 'borrowScopeConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
15
+ 'borrow-scope-constraint': { field: 'borrowScopeConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
16
+ borrowChecker: { field: 'borrowCheckerConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
17
+ borrowCheckerConstraint: { field: 'borrowCheckerConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
18
+ 'borrow-checker': { field: 'borrowCheckerConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
19
+ 'borrow-checker-constraint': { field: 'borrowCheckerConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
8
20
  callableBoundary: { field: 'callableBoundaryConstraints', sourceKey: 'sourceCallables', targetKey: 'targetCallables' },
9
21
  adtPattern: { field: 'adtPatternConstraints', sourceKey: 'sourcePatterns', targetKey: 'targetPatterns' },
10
22
  dataLayout: { field: 'dataLayoutConstraints', sourceKey: 'sourceLayouts', targetKey: 'targetLayouts' },
@@ -34,14 +46,75 @@ export function parseConversionBlock(block) {
34
46
  if (!line || line.startsWith('#')) continue;
35
47
  const target = /^target\s+([^\s,]+)/.exec(line)?.[1];
36
48
  const sourceLanguage = /^sourceLanguage\s+([^\s,]+)/.exec(line)?.[1] ?? /^source\s+([^\s,]+)/.exec(line)?.[1];
49
+ const sourceRuntime = /^sourceRuntime\s+([^\s,]+)\s+([^\s,]+)/.exec(line);
50
+ const targetRuntime = /^targetRuntime\s+([^\s,]+)\s+([^\s,]+)/.exec(line);
51
+ const runtimeRequirement = /^(?:runtimeRequirement|requiredRuntime|requiresRuntime)\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
52
+ const dialect = /^dialect\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
53
+ const extern = /^extern\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
37
54
  const constraint = /^constraint\s+([A-Za-z_$][\w$-]*)\s+([A-Za-z_$][\w$-]*)(.*)$/.exec(line);
38
55
  if (target) plan.targets.push(target);
39
56
  else if (sourceLanguage) plan.sourceLanguage = sourceLanguage;
57
+ else if (sourceRuntime) plan.sourceRuntimes = { ...(plan.sourceRuntimes ?? {}), [sourceRuntime[1]]: sourceRuntime[2] };
58
+ else if (targetRuntime) plan.targetRuntimes = { ...(plan.targetRuntimes ?? {}), [targetRuntime[1]]: targetRuntime[2] };
59
+ else if (runtimeRequirement) addRuntimeRequirement(plan, runtimeRequirement[1], runtimeRequirement[2]);
60
+ else if (dialect) addDialectRecord(plan, dialect[1], dialect[2], false);
61
+ else if (extern) addDialectRecord(plan, extern[1], extern[2], true);
40
62
  else if (constraint) addConstraint(plan, constraint[1], constraint[2], constraint[3]);
41
63
  }
42
64
  return cleanRecord({ ...plan, targets: unique(plan.targets) });
43
65
  }
44
66
 
67
+ function addRuntimeRequirement(plan, name, text) {
68
+ plan.runtimeRequirements = [...(plan.runtimeRequirements ?? []), cleanRecord({
69
+ id: idFrom(text, `runtime_requirement_${name}`),
70
+ name,
71
+ sourceLanguage: readInlineWord('sourceLanguage', text) ?? plan.sourceLanguage,
72
+ target: readInlineWord('target', text) ?? readInlineWord('targetLanguage', text) ?? plan.targets[0],
73
+ capability: readInlineWord('capability', text) ?? readInlineWord('kind', text) ?? name,
74
+ kind: readInlineWord('kind', text),
75
+ sourceRuntime: readInlineWord('sourceRuntime', text) ?? readInlineWord('runtime', text),
76
+ targetRuntime: readInlineWord('targetRuntime', text),
77
+ sourceHost: readInlineWord('sourceHost', text),
78
+ targetHost: readInlineWord('targetHost', text),
79
+ reason: readInlineQuoted('reason', text) ?? readInlineWord('reason', text),
80
+ evidenceIds: readInlineList(text, 'evidence', 'evidenceIds')
81
+ })];
82
+ }
83
+
84
+ function addDialectRecord(plan, name, text, isExtern) {
85
+ const projection = cleanRecord({
86
+ disposition: readInlineWord('disposition', text),
87
+ readiness: readInlineWord('readiness', text),
88
+ targets: readInlineList(text, 'target', 'targets', 'targetLanguage') ?? plan.targets,
89
+ evidenceIds: readInlineList(text, 'projectionEvidence', 'projectionEvidenceIds', 'evidence', 'evidenceIds'),
90
+ lossIds: readInlineList(text, 'projectionLoss', 'projectionLossIds', 'loss', 'lossId', 'lossIds')
91
+ });
92
+ const record = cleanRecord({
93
+ id: idFrom(text, `${isExtern ? 'extern' : 'dialect'}_${name}`),
94
+ language: readInlineWord('language', text) ?? plan.sourceLanguage,
95
+ dialect: readInlineWord('dialect', text) ?? name,
96
+ name: readInlineWord('name', text) ?? name,
97
+ nativeKind: readInlineWord('nativeKind', text),
98
+ sourcePath: readInlineWord('sourcePath', text) ?? readInlineWord('path', text),
99
+ nativeSourceId: readInlineWord('nativeSource', text) ?? readInlineWord('nativeSourceId', text),
100
+ nativeAstId: readInlineWord('nativeAst', text) ?? readInlineWord('nativeAstId', text),
101
+ nativeAstNodeId: readInlineWord('nativeAstNode', text) ?? readInlineWord('nativeAstNodeId', text),
102
+ semanticNodeId: readInlineWord('semanticNode', text) ?? readInlineWord('semanticNodeId', text),
103
+ semanticSymbolId: readInlineWord('symbol', text) ?? readInlineWord('semanticSymbolId', text),
104
+ sourceMapId: readInlineWord('sourceMap', text) ?? readInlineWord('sourceMapId', text),
105
+ sourceMapMappingId: readInlineWord('sourceMapMapping', text) ?? readInlineWord('sourceMapMappingId', text),
106
+ lossIds: readInlineList(text, 'loss', 'lossId', 'lossIds'),
107
+ evidenceIds: readInlineList(text, 'evidence', 'evidenceIds'),
108
+ projection
109
+ });
110
+ if (isExtern) {
111
+ const binding = cleanRecord({ module: readInlineWord('module', text), path: readInlineWord('bindingPath', text), symbol: readInlineWord('bindingSymbol', text) ?? readInlineWord('symbol', text), abi: readInlineWord('abi', text), version: readInlineWord('version', text) });
112
+ plan.externs = [...(plan.externs ?? []), cleanRecord({ ...record, externKind: readInlineWord('externKind', text) ?? readInlineWord('kind', text) ?? 'extern', ...(Object.keys(binding).length ? { binding } : {}) })];
113
+ } else {
114
+ plan.dialects = [...(plan.dialects ?? []), cleanRecord({ ...record, constructKind: readInlineWord('constructKind', text) ?? readInlineWord('kind', text) ?? 'runtime', externIds: readInlineList(text, 'extern', 'externIds') })];
115
+ }
116
+ }
117
+
45
118
  function addConstraint(plan, family, name, text) {
46
119
  const config = FAMILIES[family] ?? { field: family.endsWith('s') ? family : `${family}Constraints`, sourceKey: 'sourceRecords', targetKey: 'targetRecords' };
47
120
  const role = readInlineWord('role', text) ?? 'source';
@@ -58,7 +131,7 @@ function addConstraint(plan, family, name, text) {
58
131
  metadata: { name, family, authoredConversionBlockId: plan.id }
59
132
  });
60
133
  const recordKey = role === 'target' ? config.targetKey : config.sourceKey;
61
- entry[recordKey] = [record];
134
+ entry[recordKey] = [config.graph ? resourceGraphFromRecord(record, entry) : record];
62
135
  plan[config.field] = [...(plan[config.field] ?? []), entry];
63
136
  }
64
137
 
@@ -75,17 +148,28 @@ function parseConstraintRecord(name, text, role) {
75
148
  symbolId: readInlineWord('symbol', text) ?? readInlineWord('symbolId', text),
76
149
  symbolName: readInlineWord('symbolName', text),
77
150
  localName: readInlineWord('localName', text),
151
+ ownerId: readInlineWord('owner', text) ?? readInlineWord('ownerId', text),
152
+ ownerKind: readInlineWord('ownerKind', text),
153
+ mode: readInlineWord('mode', text) ?? readInlineWord('loanMode', text),
154
+ aliasKind: readInlineWord('aliasKind', text),
155
+ moveKind: readInlineWord('moveKind', text),
156
+ dropKind: readInlineWord('dropKind', text),
157
+ resourceKind: readInlineWord('resourceKind', text),
158
+ scopeKind: readInlineWord('scopeKind', text),
78
159
  typeKind: readInlineWord('typeKind', text),
79
160
  signatureHash: readInlineWord('signatureHash', text),
80
161
  contractHash: readInlineWord('contractHash', text),
81
162
  typeHash: readInlineWord('typeHash', text),
82
163
  flowKind: readInlineWord('flowKind', text),
164
+ controlFlowKind: readInlineWord('controlFlowKind', text),
165
+ sourceControlFlowId: readInlineWord('sourceControlFlowId', text),
83
166
  sourceId: readInlineWord('from', text) ?? readInlineWord('sourceId', text),
84
167
  targetId: readInlineWord('to', text) ?? readInlineWord('targetId', text),
85
168
  label: readInlineWord('label', text),
86
169
  conditionHash: readInlineWord('conditionHash', text),
87
170
  orderingKey: readInlineWord('orderingKey', text) ?? readInlineWord('orderKey', text),
88
171
  lifetimeKind: readInlineWord('lifetimeKind', text),
172
+ lifetimeRegionId: readInlineWord('lifetimeRegion', text) ?? readInlineWord('lifetimeRegionId', text),
89
173
  regionKind: readInlineWord('regionKind', text),
90
174
  predicate: readInlineQuoted('predicate', text) ?? readInlineWord('predicate', text),
91
175
  resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text),
@@ -103,6 +187,94 @@ function parseConstraintRecord(name, text, role) {
103
187
  });
104
188
  }
105
189
 
190
+ function resourceGraphFromRecord(record, entry) {
191
+ const tokens = lowerTokens(record);
192
+ const resourceId = record.resourceId ?? record.symbolId ?? `${record.id}_resource`;
193
+ const evidenceIds = record.evidenceIds ?? entry.evidenceIds;
194
+ return cleanRecord({
195
+ id: `${record.id}_resource_graph`,
196
+ sourceLanguage: entry.sourceLanguage,
197
+ target: entry.target,
198
+ sourcePath: record.sourcePath,
199
+ sourceHash: record.sourceHash,
200
+ evidenceIds,
201
+ resources: [{
202
+ id: resourceId,
203
+ resourceKind: record.resourceKind ?? record.kind ?? record.constraintKind,
204
+ sourcePath: record.sourcePath,
205
+ sourceHash: record.sourceHash,
206
+ evidenceIds,
207
+ metadata: { factKinds: record.factKinds, constraintKinds: record.constraintKinds }
208
+ }],
209
+ owners: needsOwner(tokens, record) ? [{
210
+ id: `${record.id}_owner`,
211
+ resourceId,
212
+ ownerId: record.ownerId ?? record.symbolId ?? `${resourceId}:owner`,
213
+ ownerKind: record.ownerKind ?? tokenKind(tokens, /owner|single-owner/) ?? 'owner',
214
+ evidenceIds
215
+ }] : undefined,
216
+ loans: needsLoan(tokens, record) ? [{
217
+ id: `${record.id}_loan`,
218
+ resourceId,
219
+ mode: record.mode ?? loanMode(tokens),
220
+ lifetimeRegionId: record.lifetimeRegionId,
221
+ evidenceIds
222
+ }] : undefined,
223
+ aliases: tokenMatches(tokens, /alias/) ? [{
224
+ id: `${record.id}_alias`,
225
+ resourceId,
226
+ aliasKind: record.aliasKind ?? tokenKind(tokens, /alias/) ?? 'alias',
227
+ evidenceIds
228
+ }] : undefined,
229
+ moves: tokenMatches(tokens, /move|transfer/) ? [{
230
+ id: `${record.id}_move`,
231
+ resourceId,
232
+ moveKind: record.moveKind ?? tokenKind(tokens, /move|transfer/) ?? 'move',
233
+ evidenceIds
234
+ }] : undefined,
235
+ drops: tokenMatches(tokens, /drop/) ? [{
236
+ id: `${record.id}_drop`,
237
+ resourceId,
238
+ dropKind: record.dropKind ?? tokenKind(tokens, /drop/) ?? 'drop',
239
+ evidenceIds
240
+ }] : undefined,
241
+ lifetimeRegions: record.lifetimeKind || record.lifetimeRegionId || tokenMatches(tokens, /lifetime/) ? [{
242
+ id: record.lifetimeRegionId ?? `${record.id}_lifetime`,
243
+ resourceId,
244
+ lifetimeKind: record.lifetimeKind ?? tokenKind(tokens, /lifetime/) ?? record.regionKind,
245
+ evidenceIds
246
+ }] : undefined,
247
+ borrowScopes: needsLoan(tokens, record) || record.scopeKind ? [{
248
+ id: `${record.id}_borrow_scope`,
249
+ resourceId,
250
+ scopeKind: record.scopeKind ?? record.kind,
251
+ lifetimeRegionId: record.lifetimeRegionId,
252
+ constraintKinds: record.constraintKinds,
253
+ evidenceIds
254
+ }] : undefined,
255
+ unsafeBoundaries: tokenMatches(tokens, /unsafe|raw/) ? [{
256
+ id: `${record.id}_unsafe`,
257
+ resourceId,
258
+ kind: tokenKind(tokens, /unsafe|raw/) ?? 'unsafe-boundary',
259
+ evidenceIds
260
+ }] : undefined
261
+ });
262
+ }
263
+
264
+ function lowerTokens(record) {
265
+ return unique([record.kind, record.constraintKind, record.scopeKind, record.resourceKind, record.ownerKind, record.mode, record.aliasKind, record.moveKind, record.dropKind, record.lifetimeKind, record.regionKind, record.flowKind, record.controlFlowKind, ...(record.constraintKinds ?? []), ...(record.factKinds ?? [])].filter(Boolean).map((value) => String(value).toLowerCase()));
266
+ }
267
+ function needsOwner(tokens, record) { return record.ownerId || record.ownerKind || tokenMatches(tokens, /owner|single-owner/); }
268
+ function needsLoan(tokens, record) { return record.mode || tokenMatches(tokens, /borrow|loan|raw/); }
269
+ function loanMode(tokens) {
270
+ if (tokens.includes('shared-borrow') || tokens.includes('shared')) return 'shared';
271
+ if (tokens.includes('exclusive-borrow') || tokens.includes('mutable') || tokens.includes('exclusive')) return 'exclusive';
272
+ if (tokens.includes('raw-access-boundary') || tokens.includes('raw')) return 'raw';
273
+ if (tokens.includes('move')) return 'move';
274
+ return 'unknown';
275
+ }
276
+ function tokenMatches(tokens, pattern) { return tokens.some((token) => pattern.test(token)); }
277
+ function tokenKind(tokens, pattern) { return tokens.find((token) => pattern.test(token)); }
106
278
  function idFrom(text, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(text)?.[1] ?? fallback; }
107
279
  function nameFrom(header) { return /^([A-Za-z_$][\w$]*)/.exec(header)?.[1] ?? 'Conversion'; }
108
280
  function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
package/dist/metadata.js CHANGED
@@ -66,6 +66,9 @@ function mergeConversionBlocks(blocks) {
66
66
  if (block.sourceLanguage && !plan.sourceLanguage) plan.sourceLanguage = block.sourceLanguage;
67
67
  for (const [key, value] of Object.entries(block)) {
68
68
  if (Array.isArray(value) && key !== 'targets') plan[key] = [...(plan[key] ?? []), ...value];
69
+ else if ((key === 'sourceRuntimes' || key === 'targetRuntimes') && value && typeof value === 'object') {
70
+ plan[key] = { ...(plan[key] ?? {}), ...value };
71
+ }
69
72
  }
70
73
  }
71
74
  return plan;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "Parser for the first Frontier Lang .frontier syntax slice.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",