@shapeshift-labs/frontier-lang-compiler 0.2.64 → 0.2.66

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.
Files changed (32) hide show
  1. package/README.md +31 -6
  2. package/dist/declarations/import-adapter-core.d.ts +3 -0
  3. package/dist/declarations/native-project-admission.d.ts +18 -0
  4. package/dist/declarations/semantic-merge-candidates.d.ts +200 -0
  5. package/dist/declarations/semantic-merge-conflicts.d.ts +12 -0
  6. package/dist/declarations/semantic-sidecar.d.ts +8 -3
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +1 -0
  9. package/dist/internal/index-impl/attachExternalOwnership.js +18 -10
  10. package/dist/internal/index-impl/createSemanticImportSidecar.js +6 -0
  11. package/dist/internal/index-impl/createSemanticSlice.js +3 -2
  12. package/dist/internal/index-impl/diffNativeSourceImports.js +3 -2
  13. package/dist/internal/index-impl/externalSemanticBase.js +1 -0
  14. package/dist/internal/index-impl/importExternalSemanticIndex.js +4 -0
  15. package/dist/internal/index-impl/projectImportAdmissionSummaries.js +31 -6
  16. package/dist/internal/index-impl/runNativeImporterAdapter.js +1 -1
  17. package/dist/internal/index-impl/semanticMergeCandidateRecordInternals.js +314 -0
  18. package/dist/internal/index-impl/semanticMergeCandidateRecords.js +241 -0
  19. package/dist/internal/index-impl/withExternalEmptyLoss.js +1 -0
  20. package/dist/language-adapter-package-contracts.js +6 -6
  21. package/dist/lightweight-dependency-language.js +8 -0
  22. package/dist/native-region-scanner-core.js +42 -10
  23. package/dist/native-region-scanner-js-helpers.js +2 -0
  24. package/dist/native-region-scanner-js-imports.js +111 -0
  25. package/dist/native-region-scanner-js.js +111 -28
  26. package/examples/js-frontier-rust-workbench-adapters.mjs +89 -0
  27. package/examples/js-frontier-rust-workbench-bounds.mjs +4 -3
  28. package/examples/js-frontier-rust-workbench-client.mjs +78 -59
  29. package/examples/js-frontier-rust-workbench-html.mjs +20 -13
  30. package/examples/js-frontier-rust-workbench-styles.mjs +9 -16
  31. package/examples/js-frontier-rust-workbench.mjs +67 -121
  32. package/package.json +1 -1
@@ -2,8 +2,8 @@ export function workbenchClientScript() {
2
2
  return `
3
3
  const initial = JSON.parse(document.getElementById('initial-state').textContent);
4
4
  const state = {
5
- sources: { ...initial.sources },
6
- sourceLanguage: initial.sourceLanguage || 'typescript',
5
+ sources: { typescript: initial.sources?.typescript || '' },
6
+ sourceLanguage: 'typescript',
7
7
  result: initial.result,
8
8
  activeTab: 'graph',
9
9
  pending: false
@@ -11,69 +11,65 @@ const state = {
11
11
 
12
12
  window.frontierLangWorkbench = {
13
13
  kind: 'frontier.framework.demo.langWorkbench',
14
- version: 1,
14
+ version: 2,
15
15
  state,
16
16
  feature: {
17
- id: 'frontier-lang-ts-rust-workbench',
17
+ id: 'frontier-lang-ts-rust-python-workbench',
18
18
  package: '@shapeshift-labs/frontier-lang-compiler',
19
19
  route: '/examples/js-frontier-rust-workbench',
20
- panels: ['source', 'frontier', 'rust']
20
+ panels: ['typescript', 'frontier', 'rust', 'python'],
21
+ conversions: ['typescript->rust', 'typescript->python']
21
22
  },
22
23
  evidence: []
23
24
  };
24
25
 
25
26
  const typescriptInput = document.getElementById('typescriptInput');
26
- const rustInput = document.getElementById('rustInput');
27
+ const rustOutput = document.getElementById('rustOutput');
28
+ const pythonOutput = document.getElementById('pythonOutput');
27
29
  const frontierJson = document.getElementById('frontierJson');
28
30
  const graphView = document.getElementById('graphView');
29
31
  const readinessValue = document.getElementById('readinessValue');
30
32
  const symbolValue = document.getElementById('symbolValue');
31
33
  const mappingValue = document.getElementById('mappingValue');
32
- const modeValue = document.getElementById('modeValue');
34
+ const rustValue = document.getElementById('rustValue');
35
+ const pythonValue = document.getElementById('pythonValue');
33
36
  const typescriptStatus = document.getElementById('typescriptStatus');
34
37
  const rustStatus = document.getElementById('rustStatus');
38
+ const pythonStatus = document.getElementById('pythonStatus');
35
39
 
36
- let updatingEditors = false;
37
40
  typescriptInput.value = state.sources.typescript;
38
- rustInput.value = state.sources.rust;
39
41
  render();
40
42
 
41
43
  typescriptInput.addEventListener('input', () => {
42
- if (updatingEditors) return;
43
44
  state.sources.typescript = typescriptInput.value;
44
- state.sourceLanguage = 'typescript';
45
- renderEditorModes();
46
- });
47
-
48
- rustInput.addEventListener('input', () => {
49
- if (updatingEditors) return;
50
- state.sources.rust = rustInput.value;
51
- state.sourceLanguage = 'rust';
52
- renderEditorModes();
53
45
  });
54
46
 
55
- document.getElementById('runButton').addEventListener('click', async () => {
56
- await convertFrom(state.sourceLanguage);
47
+ document.getElementById('convertForm').addEventListener('submit', async (event) => {
48
+ event.preventDefault();
49
+ await convertFromTypescript();
57
50
  });
58
51
 
59
- document.getElementById('resetButton').addEventListener('click', async () => {
60
- state.sources = { ...initial.sources };
52
+ document.getElementById('resetButton').addEventListener('click', () => {
53
+ state.sources = { typescript: initial.sources?.typescript || '' };
61
54
  state.sourceLanguage = 'typescript';
62
55
  state.result = initial.result;
63
56
  render();
64
57
  });
65
58
 
66
59
  document.getElementById('copyButton').addEventListener('click', async () => {
67
- await navigator.clipboard.writeText(projectedSourceText());
60
+ try {
61
+ if (!navigator.clipboard) throw new Error('clipboard unavailable');
62
+ await navigator.clipboard.writeText(projectedSourceText());
63
+ } catch (error) {
64
+ window.frontierLangWorkbench.evidence.push({
65
+ at: Date.now(),
66
+ kind: 'clipboard',
67
+ status: 'failed',
68
+ message: String(error.message || error)
69
+ });
70
+ }
68
71
  });
69
72
 
70
- for (const button of document.querySelectorAll('[data-source-mode]')) {
71
- button.addEventListener('click', () => {
72
- state.sourceLanguage = button.dataset.sourceMode;
73
- renderEditorModes();
74
- });
75
- }
76
-
77
73
  for (const button of document.querySelectorAll('[data-frontier-tab]')) {
78
74
  button.addEventListener('click', () => {
79
75
  state.activeTab = button.dataset.frontierTab;
@@ -84,26 +80,27 @@ for (const button of document.querySelectorAll('[data-frontier-tab]')) {
84
80
  });
85
81
  }
86
82
 
87
- async function convertFrom(sourceLanguage) {
88
- state.sourceLanguage = sourceLanguage;
89
- state.sources[sourceLanguage] = sourceLanguage === 'rust' ? rustInput.value : typescriptInput.value;
83
+ async function convertFromTypescript() {
84
+ state.sourceLanguage = 'typescript';
85
+ state.sources.typescript = typescriptInput.value;
90
86
  setPending(true);
91
87
  try {
92
88
  const response = await fetch('/api/convert', {
93
89
  method: 'POST',
94
90
  headers: { 'content-type': 'application/json' },
95
- body: JSON.stringify({ source: state.sources[sourceLanguage], sourceLanguage })
91
+ body: JSON.stringify({ source: state.sources.typescript, sourceLanguage: 'typescript' })
96
92
  });
97
93
  const payload = await response.json();
98
94
  if (!response.ok || !payload.ok) throw new Error(payload.error || 'conversion failed');
99
95
  state.result = payload.result;
100
- state.sources[payload.result.projection.targetLanguage] = payload.result.projection.output;
96
+ const projections = projectionSet(payload.result);
101
97
  window.frontierLangWorkbench.evidence.push({
102
98
  at: Date.now(),
103
99
  kind: 'conversion',
104
100
  sourceHash: payload.result.sourceHash,
105
101
  symbolCount: payload.result.summary.symbols,
106
- readiness: payload.result.summary.readiness
102
+ readiness: payload.result.summary.readiness,
103
+ projections: Object.keys(projections)
107
104
  });
108
105
  } catch (error) {
109
106
  state.result = errorResult(error);
@@ -116,35 +113,43 @@ async function convertFrom(sourceLanguage) {
116
113
  function render() {
117
114
  const result = state.result || {};
118
115
  const summary = result.summary || {};
116
+ const projections = projectionSet(result);
119
117
  readinessValue.textContent = summary.readiness || 'error';
120
118
  readinessValue.style.color = readinessColor(summary.readiness);
121
119
  symbolValue.textContent = String(summary.symbols ?? 0);
122
120
  mappingValue.textContent = String(summary.sourceMapMappings ?? 0);
123
- modeValue.textContent = result.projection?.mode || '-';
124
- typescriptStatus.textContent = languageStatus('typescript', result);
125
- rustStatus.textContent = languageStatus('rust', result);
126
- renderEditorModes();
127
- frontierJson.textContent = JSON.stringify(result.frontier, null, 2);
121
+ rustValue.textContent = projectionStatus(projections.rust);
122
+ pythonValue.textContent = projectionStatus(projections.python);
123
+ typescriptStatus.textContent = result.sourceHash || 'source';
124
+ rustStatus.textContent = projectionSummaryText(projections.rust);
125
+ pythonStatus.textContent = projectionSummaryText(projections.python);
126
+ typescriptInput.value = state.sources.typescript || '';
127
+ rustOutput.value = projections.rust?.output || '';
128
+ pythonOutput.value = projections.python?.output || '';
129
+ document.querySelector('[data-view-panel="source"]').classList.add('isSource');
130
+ frontierJson.textContent = JSON.stringify(result.frontier || {}, null, 2);
128
131
  frontierJson.hidden = state.activeTab !== 'json';
129
132
  graphView.hidden = state.activeTab !== 'graph';
130
133
  if (!graphView.hidden) graphView.innerHTML = graphHtml(result);
131
134
  }
132
135
 
133
- function renderEditorModes() {
134
- updatingEditors = true;
135
- typescriptInput.value = state.sources.typescript || '';
136
- rustInput.value = state.sources.rust || '';
137
- updatingEditors = false;
138
- document.querySelector('[data-view-panel="source"]').classList.toggle('isSource', state.sourceLanguage === 'typescript');
139
- document.querySelector('[data-view-panel="rust"]').classList.toggle('isSource', state.sourceLanguage === 'rust');
140
- for (const button of document.querySelectorAll('[data-source-mode]')) {
141
- button.setAttribute('aria-pressed', String(button.dataset.sourceMode === state.sourceLanguage));
136
+ function projectionSet(result) {
137
+ if (result.projections) return result.projections;
138
+ const projections = {};
139
+ if (result.projection) {
140
+ projections[result.projection.targetLanguage || result.projection.target || 'rust'] = result.projection;
142
141
  }
142
+ return projections;
143
143
  }
144
144
 
145
- function languageStatus(language, result) {
146
- if (state.sourceLanguage === language) return result.sourceHash || 'source';
147
- return (result.projection?.targetLanguage === language ? result.projection.readiness : 'projection') || 'projection';
145
+ function projectionStatus(projection) {
146
+ if (!projection) return 'missing';
147
+ return projection.ok ? projection.readiness || 'ready' : 'blocked';
148
+ }
149
+
150
+ function projectionSummaryText(projection) {
151
+ if (!projection) return 'missing';
152
+ return (projection.mode || '-') + ' / ' + String(projection.sourceMapMappings ?? 0) + ' maps';
148
153
  }
149
154
 
150
155
  function graphHtml(result) {
@@ -157,7 +162,7 @@ function graphHtml(result) {
157
162
  chip(summary.readiness, readinessClass(summary.readiness)),
158
163
  chip(String(summary.losses || 0) + ' losses', 'review'),
159
164
  chip(String(summary.patchHints || 0) + ' patch hints', 'ready'),
160
- chip((result.sourceLanguage || '-') + ' -> ' + (result.projection?.targetLanguage || '-'), 'review'),
165
+ chip(projectionTargets(result), 'review'),
161
166
  '</div>',
162
167
  '<div class="sectionTitle">Symbols</div>',
163
168
  '<div class="graphGrid">',
@@ -172,10 +177,15 @@ function graphHtml(result) {
172
177
  ].join('');
173
178
  }
174
179
 
180
+ function projectionTargets(result) {
181
+ const targets = Object.keys(projectionSet(result));
182
+ return (result.sourceLanguage || '-') + ' -> ' + (targets.length ? targets.join('/') : '-');
183
+ }
184
+
175
185
  function nodeCard(symbol) {
176
186
  return '<article class="nodeCard" data-graph-node="' + escapeHtml(symbol.id || symbol.name) + '">' +
177
187
  '<strong>' + escapeHtml(symbol.name) + '</strong>' +
178
- '<span>' + escapeHtml(symbol.kind || 'symbol') + ' · ' + escapeHtml(symbol.region || 'region') + '</span>' +
188
+ '<span>' + escapeHtml(symbol.kind || 'symbol') + ' - ' + escapeHtml(symbol.region || 'region') + '</span>' +
179
189
  '</article>';
180
190
  }
181
191
 
@@ -199,13 +209,22 @@ function errorResult(error) {
199
209
  return {
200
210
  summary: { readiness: 'blocked', symbols: 0, sourceMapMappings: 0, losses: 1, patchHints: 0 },
201
211
  frontier: { symbols: [], relations: [], error: String(error.message || error) },
202
- projection: { output: String(error.stack || error), readiness: 'blocked', mode: 'error' }
212
+ projections: {
213
+ rust: { output: String(error.stack || error), readiness: 'blocked', mode: 'error', ok: false },
214
+ python: { output: String(error.stack || error), readiness: 'blocked', mode: 'error', ok: false }
215
+ }
203
216
  };
204
217
  }
205
218
 
206
219
  function projectedSourceText() {
207
- const target = state.result?.projection?.targetLanguage;
208
- return target ? state.sources[target] || '' : '';
220
+ const projections = projectionSet(state.result || {});
221
+ return [
222
+ '// Rust projection',
223
+ projections.rust?.output || '',
224
+ '',
225
+ '# Python projection',
226
+ projections.python?.output || ''
227
+ ].join('\\n');
209
228
  }
210
229
 
211
230
  function boundsHtml(bounds) {
@@ -4,7 +4,7 @@ export function renderWorkbenchHtml(initialState) {
4
4
  <head>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <title>Frontier Lang TypeScript to Rust Workbench</title>
7
+ <title>Frontier Lang TypeScript Projection Workbench</title>
8
8
  <link rel="stylesheet" href="/styles.css">
9
9
  </head>
10
10
  <body>
@@ -14,25 +14,22 @@ export function renderWorkbenchHtml(initialState) {
14
14
  <span class="brandMark">F</span>
15
15
  <div>
16
16
  <h1>Frontier Lang Workbench</h1>
17
- <p>TypeScript -> Frontier graph -> Rust</p>
17
+ <p>TypeScript -> Frontier graph -> Rust + Python</p>
18
18
  </div>
19
19
  </div>
20
- <div class="topActions">
21
- <div class="modeGroup" role="group" aria-label="Source language">
22
- <button data-source-mode="typescript" aria-pressed="true">TS source</button>
23
- <button data-source-mode="rust" aria-pressed="false">Rust source</button>
24
- </div>
25
- <button class="runButton" id="runButton">Run</button>
26
- <button class="iconButton" id="resetButton" title="Reset source" aria-label="Reset source">↺</button>
27
- <button class="iconButton" id="copyButton" title="Copy projection" aria-label="Copy projection">⧉</button>
28
- </div>
20
+ <form class="topActions" id="convertForm">
21
+ <button class="runButton" id="runButton" type="submit">Run</button>
22
+ <button class="iconButton" id="resetButton" type="button" title="Reset source" aria-label="Reset source">↺</button>
23
+ <button class="iconButton" id="copyButton" type="button" title="Copy projections" aria-label="Copy projections">⧉</button>
24
+ </form>
29
25
  </header>
30
26
 
31
27
  <section class="statusStrip" data-view-panel="status">
32
28
  <div><span class="label">Readiness</span><strong id="readinessValue">-</strong></div>
33
29
  <div><span class="label">Symbols</span><strong id="symbolValue">-</strong></div>
34
30
  <div><span class="label">Mappings</span><strong id="mappingValue">-</strong></div>
35
- <div><span class="label">Mode</span><strong id="modeValue">-</strong></div>
31
+ <div><span class="label">Rust</span><strong id="rustValue">-</strong></div>
32
+ <div><span class="label">Python</span><strong id="pythonValue">-</strong></div>
36
33
  </section>
37
34
 
38
35
  <section class="workspace">
@@ -64,7 +61,17 @@ export function renderWorkbenchHtml(initialState) {
64
61
  <span id="rustStatus">target</span>
65
62
  </div>
66
63
  <div class="paneBody editorBody">
67
- <textarea id="rustInput" spellcheck="false" aria-label="Rust source"></textarea>
64
+ <textarea id="rustOutput" spellcheck="false" aria-label="Rust projection" readonly></textarea>
65
+ </div>
66
+ </section>
67
+
68
+ <section class="pane outputPane" data-view-panel="python">
69
+ <div class="paneHeader">
70
+ <h2>Python</h2>
71
+ <span id="pythonStatus">target</span>
72
+ </div>
73
+ <div class="paneBody editorBody">
74
+ <textarea id="pythonOutput" spellcheck="false" aria-label="Python projection" readonly></textarea>
68
75
  </div>
69
76
  </section>
70
77
  </section>
@@ -69,8 +69,8 @@ body {
69
69
  .brand h1 { margin: 0; font-size: 15px; line-height: 1.1; letter-spacing: 0; }
70
70
  .brand p { margin: 4px 0 0; color: var(--muted); font-size: 12px; }
71
71
 
72
- .topActions { display: flex; align-items: center; gap: 8px; }
73
- .iconButton, .segmented button, .modeGroup button, .runButton {
72
+ .topActions { display: flex; align-items: center; gap: 8px; margin: 0; }
73
+ .iconButton, .segmented button, .runButton {
74
74
  height: 32px;
75
75
  border: 1px solid var(--line);
76
76
  background: var(--panel-2);
@@ -79,17 +79,7 @@ body {
79
79
  }
80
80
  .iconButton { width: 34px; font-size: 16px; cursor: pointer; }
81
81
  .runButton { min-width: 64px; padding: 0 14px; cursor: pointer; color: var(--green); }
82
- .modeGroup { display: flex; }
83
- .modeGroup button {
84
- width: 84px;
85
- border-radius: 0;
86
- font-size: 12px;
87
- cursor: pointer;
88
- }
89
- .modeGroup button:first-child { border-radius: 7px 0 0 7px; }
90
- .modeGroup button:last-child { border-radius: 0 7px 7px 0; border-left: 0; }
91
- .modeGroup button[aria-pressed="true"] { background: #213127; color: var(--green); }
92
- .iconButton:hover, .segmented button:hover, .modeGroup button:hover, .runButton:hover { background: var(--panel-3); }
82
+ .iconButton:hover, .segmented button:hover, .runButton:hover { background: var(--panel-3); }
93
83
 
94
84
  .statusStrip {
95
85
  position: fixed;
@@ -97,7 +87,7 @@ body {
97
87
  z-index: 19;
98
88
  height: var(--status-height);
99
89
  display: grid;
100
- grid-template-columns: repeat(4, minmax(0, 1fr));
90
+ grid-template-columns: repeat(5, minmax(0, 1fr));
101
91
  background: #0f1214;
102
92
  overflow: hidden;
103
93
  }
@@ -132,7 +122,7 @@ body {
132
122
  height: calc(100dvh - var(--chrome-height));
133
123
  min-height: 0;
134
124
  display: grid;
135
- grid-template-columns: minmax(270px, 0.95fr) minmax(330px, 1.1fr) minmax(300px, 1fr);
125
+ grid-template-columns: minmax(260px, 0.9fr) minmax(320px, 1.1fr) minmax(280px, 1fr) minmax(280px, 1fr);
136
126
  overflow: hidden;
137
127
  }
138
128
  .pane {
@@ -189,6 +179,9 @@ textarea, .codeBlock {
189
179
  overscroll-behavior: contain;
190
180
  white-space: pre;
191
181
  }
182
+ textarea[readonly] {
183
+ cursor: default;
184
+ }
192
185
 
193
186
  .graphView {
194
187
  padding: 12px;
@@ -291,7 +284,7 @@ textarea, .codeBlock {
291
284
  }
292
285
  .workspace {
293
286
  grid-template-columns: 1fr;
294
- grid-template-rows: repeat(3, minmax(0, 1fr));
287
+ grid-template-rows: repeat(4, minmax(0, 1fr));
295
288
  overflow: hidden;
296
289
  }
297
290
  .pane {
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import http from 'node:http';
3
3
  import { compileNativeSource, createSemanticImportSidecar, importNativeSource, writeUniversalAstJson } from '../dist/index.js';
4
+ import {
5
+ createTsToPythonWorkbenchAdapter,
6
+ createTsToRustWorkbenchAdapter
7
+ } from './js-frontier-rust-workbench-adapters.mjs';
4
8
  import { conversionBounds } from './js-frontier-rust-workbench-bounds.mjs';
5
9
  import { renderWorkbenchHtml } from './js-frontier-rust-workbench-html.mjs';
6
10
  import { workbenchClientScript } from './js-frontier-rust-workbench-client.mjs';
@@ -24,21 +28,41 @@ export class TodoStore {
24
28
  }
25
29
  }
26
30
  `;
27
- const sampleRustSource = `// Generated from Frontier semantic graph evidence.
28
- pub fn add_todo() {
29
- // port body from preserved TypeScript source
30
- }
31
-
32
- pub struct TodoStore;
33
- `;
34
31
 
35
32
  if (args.smoke) {
36
33
  const result = convertSource(sampleSource, { sourceLanguage: 'typescript' });
37
34
  assert(result.summary.symbols >= 2, 'expected semantic symbols');
38
- assert(result.projection.output.includes('pub'), 'expected Rust projection');
39
- const reverse = convertSource(result.projection.output, { sourceLanguage: 'rust' });
40
- assert(reverse.projection.output.includes('export'), 'expected TypeScript projection');
41
- console.log(JSON.stringify({ ok: true, summary: result.summary }, null, 2));
35
+ assert(result.frontier.universalAst.valid, 'expected valid universal AST summary');
36
+ assert(result.projections.rust.output.includes('pub'), 'expected Rust projection');
37
+ assert(result.projections.python.output.includes('def add_todo'), 'expected Python projection');
38
+ const html = renderWorkbenchHtml({
39
+ sourceLanguage: 'typescript',
40
+ sources: { typescript: sampleSource },
41
+ result
42
+ });
43
+ const client = workbenchClientScript();
44
+ const styles = workbenchStyles();
45
+ assert(html.includes('id="convertForm"'), 'expected submit form');
46
+ assert(html.includes('id="typescriptInput"'), 'expected TypeScript input pane');
47
+ assert(html.includes('id="frontierJson"'), 'expected Frontier graph JSON pane');
48
+ assert(html.includes('id="rustOutput"'), 'expected Rust output pane');
49
+ assert(html.includes('id="pythonOutput"'), 'expected Python output pane');
50
+ assert(client.includes("addEventListener('submit'"), 'expected submit-based conversion listener');
51
+ assert(styles.includes('grid-template-columns: minmax(260px, 0.9fr) minmax(320px, 1.1fr) minmax(280px, 1fr) minmax(280px, 1fr);'), 'expected four-pane desktop scaffold');
52
+ assert(styles.includes('overflow: auto'), 'expected scrollable pane bodies');
53
+ console.log(JSON.stringify({
54
+ ok: true,
55
+ summary: result.summary,
56
+ projections: {
57
+ rust: { ok: result.projections.rust.ok, readiness: result.projections.rust.readiness },
58
+ python: { ok: result.projections.python.ok, readiness: result.projections.python.readiness }
59
+ },
60
+ layout: {
61
+ submitBased: true,
62
+ panes: ['typescript', 'frontier', 'rust', 'python'],
63
+ independentScrollRegions: 4
64
+ }
65
+ }, null, 2));
42
66
  } else {
43
67
  const server = http.createServer(routeRequest);
44
68
  server.listen(port, '127.0.0.1', () => {
@@ -51,7 +75,7 @@ async function routeRequest(request, response) {
51
75
  if (request.method === 'GET' && request.url === '/') {
52
76
  return send(response, 200, renderWorkbenchHtml({
53
77
  sourceLanguage: 'typescript',
54
- sources: { typescript: sampleSource, rust: sampleRustSource },
78
+ sources: { typescript: sampleSource },
55
79
  result: convertSource(sampleSource, { sourceLanguage: 'typescript' })
56
80
  }), 'text/html; charset=utf-8');
57
81
  }
@@ -66,7 +90,7 @@ async function routeRequest(request, response) {
66
90
  return sendJson(response, 200, {
67
91
  ok: true,
68
92
  result: convertSource(String(body.source ?? ''), {
69
- sourceLanguage: body.sourceLanguage ?? 'typescript'
93
+ sourceLanguage: 'typescript'
70
94
  })
71
95
  });
72
96
  }
@@ -78,19 +102,23 @@ async function routeRequest(request, response) {
78
102
 
79
103
  function convertSource(source, options = {}) {
80
104
  const sourceLanguage = normalizeSourceLanguage(options.sourceLanguage);
81
- const target = sourceLanguage === 'rust' ? 'typescript' : 'rust';
105
+ const targets = ['rust', 'python'];
82
106
  const imported = importNativeSource({
83
107
  language: sourceLanguage,
84
108
  sourcePath: `workbench/input.${sourceExtension(sourceLanguage)}`,
85
109
  sourceText: source
86
110
  });
87
- const projection = compileNativeSource(imported, {
88
- target,
89
- targetPath: `workbench/output.${targetExtension(target)}`,
90
- targetAdapters: [createTsToRustWorkbenchAdapter(), createRustToTsWorkbenchAdapter()],
91
- emitOnBlocked: true
92
- });
93
- const sidecar = createSemanticImportSidecar(imported, { generatedAt: 0, targetPath: projection.sourceMap?.targetPath });
111
+ const targetAdapters = [createTsToRustWorkbenchAdapter(), createTsToPythonWorkbenchAdapter()];
112
+ const projections = Object.fromEntries(targets.map((target) => {
113
+ const projection = compileNativeSource(imported, {
114
+ target,
115
+ targetPath: `workbench/output.${targetExtension(target)}`,
116
+ targetAdapters,
117
+ emitOnBlocked: true
118
+ });
119
+ return [target, projectionSummary(projection, { sourceLanguage, target })];
120
+ }));
121
+ const sidecar = createSemanticImportSidecar(imported, { generatedAt: 0, targetPath: projections.rust?.targetPath });
94
122
  return {
95
123
  sourceHash: imported.nativeSource.sourceHash,
96
124
  sourceLanguage,
@@ -115,96 +143,26 @@ function convertSource(source, options = {}) {
115
143
  ownershipKey: hint.ownershipKey
116
144
  }))
117
145
  },
118
- bounds: conversionBounds(sourceLanguage, target),
119
- projection: {
120
- target,
121
- targetLanguage: target,
122
- sourceLanguage,
123
- mode: projection.outputMode,
124
- readiness: projection.readiness.readiness,
125
- ok: projection.ok,
126
- output: projection.output,
127
- sourceMapMappings: projection.sourceMap?.mappings.length ?? 0
128
- }
146
+ bounds: conversionBounds(sourceLanguage, targets.join('/')),
147
+ projection: projections.rust,
148
+ projections
129
149
  };
130
150
  }
131
151
 
132
- function createTsToRustWorkbenchAdapter() {
152
+ function projectionSummary(projection, { sourceLanguage, target }) {
133
153
  return {
134
- id: 'frontier-lang-workbench-ts-to-rust',
135
- sourceLanguage: 'typescript',
136
- target: 'rust',
137
- version: '0.0.0-demo',
138
- capabilities: ['semantic-symbol-scaffold'],
139
- coverage: {
140
- readiness: 'ready',
141
- handledLossKinds: ['opaqueNative', 'declarationOnlyCoverage', 'partialSemanticIndex', 'sourceMapApproximation', 'sourcePreservation'],
142
- notes: ['Workbench adapter lowers TypeScript semantic symbols to Rust scaffolding and leaves bodies explicit.']
143
- },
144
- project(input) {
145
- return {
146
- output: rustFromSymbols(input.importResult.semanticIndex?.symbols ?? []),
147
- readiness: 'ready',
148
- evidence: [{
149
- id: 'evidence_workbench_js_to_rust',
150
- kind: 'projection',
151
- status: 'passed',
152
- summary: 'Workbench adapter projected TypeScript semantic symbols to Rust scaffolding.'
153
- }]
154
- };
155
- }
156
- };
157
- }
158
-
159
- function createRustToTsWorkbenchAdapter() {
160
- return {
161
- id: 'frontier-lang-workbench-rust-to-ts',
162
- sourceLanguage: 'rust',
163
- target: 'typescript',
164
- version: '0.0.0-demo',
165
- capabilities: ['semantic-symbol-scaffold'],
166
- coverage: {
167
- readiness: 'ready',
168
- handledLossKinds: ['opaqueNative', 'macroExpansion', 'declarationOnlyCoverage', 'partialSemanticIndex', 'sourceMapApproximation', 'sourcePreservation'],
169
- notes: ['Workbench adapter lowers Rust semantic symbols to TypeScript scaffolding and leaves bodies explicit.']
170
- },
171
- project(input) {
172
- return {
173
- output: tsFromSymbols(input.importResult.semanticIndex?.symbols ?? []),
174
- readiness: 'ready',
175
- evidence: [{
176
- id: 'evidence_workbench_rust_to_ts',
177
- kind: 'projection',
178
- status: 'passed',
179
- summary: 'Workbench adapter projected Rust semantic symbols to TypeScript scaffolding.'
180
- }]
181
- };
182
- }
154
+ target,
155
+ targetLanguage: target,
156
+ sourceLanguage,
157
+ targetPath: projection.sourceMap?.targetPath,
158
+ mode: projection.outputMode,
159
+ readiness: projection.readiness.readiness,
160
+ ok: projection.ok,
161
+ output: projection.output,
162
+ sourceMapMappings: projection.sourceMap?.mappings.length ?? 0
183
163
  };
184
164
  }
185
165
 
186
- function rustFromSymbols(symbols) {
187
- const lines = ['// Generated from Frontier semantic graph evidence.'];
188
- for (const symbol of symbols) {
189
- if (symbol.kind === 'class') lines.push('', `pub struct ${safeTypeName(symbol.name)};`);
190
- if (symbol.kind === 'function' || symbol.kind === 'method') {
191
- lines.push('', `pub fn ${safeRustName(symbol.name)}() {`, ' // port body from preserved TypeScript source', '}');
192
- }
193
- }
194
- return `${lines.join('\n').trim()}\n`;
195
- }
196
-
197
- function tsFromSymbols(symbols) {
198
- const lines = ['// Generated from Frontier semantic graph evidence.'];
199
- for (const symbol of symbols) {
200
- if (symbol.kind === 'type' || symbol.kind === 'class') lines.push('', `export class ${safeTypeName(symbol.name)} {}`);
201
- if (symbol.kind === 'function' || symbol.kind === 'method') {
202
- lines.push('', `export function ${safeJsName(symbol.name)}(...args: unknown[]) {`, " throw new Error('port body from preserved Rust source');", '}');
203
- }
204
- }
205
- return `${lines.join('\n').trim()}\n`;
206
- }
207
-
208
166
  function universalAstSummary(imported) {
209
167
  const summary = { valid: true, validationError: undefined };
210
168
  let parsed = imported.universalAst;
@@ -250,29 +208,17 @@ function lossSummary(loss) {
250
208
  }
251
209
 
252
210
  function normalizeSourceLanguage(language) {
253
- return String(language || 'typescript').toLowerCase() === 'rust' ? 'rust' : 'typescript';
211
+ return 'typescript';
254
212
  }
255
213
 
256
214
  function targetExtension(target) {
257
- return target === 'rust' ? 'rs' : 'ts';
215
+ if (target === 'rust') return 'rs';
216
+ if (target === 'python') return 'py';
217
+ return 'txt';
258
218
  }
259
219
 
260
220
  function sourceExtension(language) {
261
- return language === 'rust' ? 'rs' : 'ts';
262
- }
263
-
264
- function safeTypeName(name) {
265
- const clean = String(name).replace(/[^A-Za-z0-9_]/g, '');
266
- return clean && /^[A-Z]/.test(clean) ? clean : `Frontier${clean || 'Type'}`;
267
- }
268
-
269
- function safeRustName(name) {
270
- return String(name).replace(/\./g, '_').replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[^A-Za-z0-9_]/g, '_').toLowerCase();
271
- }
272
-
273
- function safeJsName(name) {
274
- const clean = String(name).replace(/[^A-Za-z0-9_$]/g, '_');
275
- return /^[A-Za-z_$]/.test(clean) ? clean : `frontier_${clean}`;
221
+ return 'ts';
276
222
  }
277
223
 
278
224
  function sendJson(response, status, value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.64",
3
+ "version": "0.2.66",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",