@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.
- package/README.md +31 -6
- package/dist/declarations/import-adapter-core.d.ts +3 -0
- package/dist/declarations/native-project-admission.d.ts +18 -0
- package/dist/declarations/semantic-merge-candidates.d.ts +200 -0
- package/dist/declarations/semantic-merge-conflicts.d.ts +12 -0
- package/dist/declarations/semantic-sidecar.d.ts +8 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/index-impl/attachExternalOwnership.js +18 -10
- package/dist/internal/index-impl/createSemanticImportSidecar.js +6 -0
- package/dist/internal/index-impl/createSemanticSlice.js +3 -2
- package/dist/internal/index-impl/diffNativeSourceImports.js +3 -2
- package/dist/internal/index-impl/externalSemanticBase.js +1 -0
- package/dist/internal/index-impl/importExternalSemanticIndex.js +4 -0
- package/dist/internal/index-impl/projectImportAdmissionSummaries.js +31 -6
- package/dist/internal/index-impl/runNativeImporterAdapter.js +1 -1
- package/dist/internal/index-impl/semanticMergeCandidateRecordInternals.js +314 -0
- package/dist/internal/index-impl/semanticMergeCandidateRecords.js +241 -0
- package/dist/internal/index-impl/withExternalEmptyLoss.js +1 -0
- package/dist/language-adapter-package-contracts.js +6 -6
- package/dist/lightweight-dependency-language.js +8 -0
- package/dist/native-region-scanner-core.js +42 -10
- package/dist/native-region-scanner-js-helpers.js +2 -0
- package/dist/native-region-scanner-js-imports.js +111 -0
- package/dist/native-region-scanner-js.js +111 -28
- package/examples/js-frontier-rust-workbench-adapters.mjs +89 -0
- package/examples/js-frontier-rust-workbench-bounds.mjs +4 -3
- package/examples/js-frontier-rust-workbench-client.mjs +78 -59
- package/examples/js-frontier-rust-workbench-html.mjs +20 -13
- package/examples/js-frontier-rust-workbench-styles.mjs +9 -16
- package/examples/js-frontier-rust-workbench.mjs +67 -121
- 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: {
|
|
6
|
-
sourceLanguage:
|
|
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:
|
|
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: ['
|
|
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
|
|
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
|
|
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('
|
|
56
|
-
|
|
47
|
+
document.getElementById('convertForm').addEventListener('submit', async (event) => {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
await convertFromTypescript();
|
|
57
50
|
});
|
|
58
51
|
|
|
59
|
-
document.getElementById('resetButton').addEventListener('click',
|
|
60
|
-
state.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
|
-
|
|
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
|
|
88
|
-
state.sourceLanguage =
|
|
89
|
-
state.sources
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
146
|
-
if (
|
|
147
|
-
return
|
|
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
|
|
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') + '
|
|
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
|
-
|
|
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
|
|
208
|
-
return
|
|
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
|
|
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
|
-
<
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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">
|
|
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="
|
|
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, .
|
|
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
|
-
.
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
39
|
-
|
|
40
|
-
assert(
|
|
41
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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,
|
|
119
|
-
projection:
|
|
120
|
-
|
|
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
|
|
152
|
+
function projectionSummary(projection, { sourceLanguage, target }) {
|
|
133
153
|
return {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
211
|
+
return 'typescript';
|
|
254
212
|
}
|
|
255
213
|
|
|
256
214
|
function targetExtension(target) {
|
|
257
|
-
|
|
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
|
|
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