@shapeshift-labs/frontier-lang-compiler 0.2.65 → 0.2.67
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 +37 -8
- package/bench/smoke.mjs +15 -1
- package/bench/universal-fixture-suite.mjs +183 -0
- package/dist/declarations/import-adapter-core.d.ts +3 -0
- package/dist/declarations/native-project-admission.d.ts +133 -0
- package/dist/declarations/roundtrip-audit.d.ts +177 -0
- package/dist/declarations/roundtrip.d.ts +2 -53
- package/dist/declarations/semantic-history-records.d.ts +277 -0
- package/dist/declarations/semantic-history.d.ts +45 -92
- 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/declarations/semantic-slice-admission.d.ts +111 -0
- package/dist/declarations/semantic-slice.d.ts +36 -1
- package/dist/declarations/universal-conversion-plan.d.ts +59 -0
- package/dist/declarations/universal-runtime-capabilities.d.ts +171 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal/index-impl/attachExternalOwnership.js +18 -10
- package/dist/internal/index-impl/createNativeRoundtripEvidence.js +54 -49
- package/dist/internal/index-impl/createSemanticImportSidecar.js +6 -0
- package/dist/internal/index-impl/createSemanticSlice.js +4 -3
- package/dist/internal/index-impl/createSemanticSliceAdmissionRecord.js +10 -1
- package/dist/internal/index-impl/diffNativeSourceImports.js +3 -2
- package/dist/internal/index-impl/expandSemanticSliceSelection.js +0 -1
- package/dist/internal/index-impl/externalSemanticBase.js +1 -0
- package/dist/internal/index-impl/importExternalSemanticIndex.js +4 -0
- package/dist/internal/index-impl/nativeRoundtripAudit.js +217 -0
- package/dist/internal/index-impl/projectImportAdmissionImportEvidence.js +160 -0
- package/dist/internal/index-impl/projectImportAdmissionLanguageSummaries.js +247 -0
- package/dist/internal/index-impl/projectImportAdmissionRanks.js +52 -0
- package/dist/internal/index-impl/projectImportAdmissionSummaries.js +77 -117
- package/dist/internal/index-impl/projectImportAdmissionTasks.js +239 -0
- package/dist/internal/index-impl/semanticHistoryRecordNormalizers.js +151 -0
- package/dist/internal/index-impl/semanticHistoryRecordOverlaps.js +113 -0
- package/dist/internal/index-impl/semanticHistoryRecords.js +210 -149
- package/dist/internal/index-impl/semanticMergeCandidateRecordInternals.js +314 -0
- package/dist/internal/index-impl/semanticMergeCandidateRecords.js +241 -0
- package/dist/internal/index-impl/semanticSliceAdmissionSurface.js +142 -0
- package/dist/internal/index-impl/semanticSliceExpectationAssertions.js +100 -0
- package/dist/internal/index-impl/semanticSliceExpectationRecords.js +75 -0
- package/dist/internal/index-impl/semanticSliceExpectedAssertions.js +5 -2
- package/dist/internal/index-impl/testSemanticSlice.js +4 -1
- package/dist/internal/index-impl/withExternalEmptyLoss.js +1 -0
- package/dist/language-adapter-package-contracts.js +12 -57
- package/dist/language-adapter-package-rows.js +116 -0
- 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/dist/universal-conversion-plan-summary.js +42 -0
- package/dist/universal-conversion-plan.js +46 -40
- package/dist/universal-runtime-capabilities.js +92 -0
- package/dist/universal-runtime-host-selectors.js +192 -0
- package/dist/universal-runtime-profiles.js +109 -0
- package/dist/universal-runtime-route-records.js +162 -0
- 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 +135 -59
- package/examples/js-frontier-rust-workbench-convert.mjs +161 -0
- package/examples/js-frontier-rust-workbench-html.mjs +20 -13
- package/examples/js-frontier-rust-workbench-route-styles.mjs +126 -0
- package/examples/js-frontier-rust-workbench-route.mjs +190 -0
- package/examples/js-frontier-rust-workbench-styles.mjs +12 -54
- package/examples/js-frontier-rust-workbench.mjs +54 -214
- 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,37 @@ 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),
|
|
104
|
+
semanticMergeReadiness: payload.result.routeExplanation?.semanticMerge?.readiness,
|
|
105
|
+
missingEvidence: payload.result.routeExplanation?.semanticMerge?.missingEvidence ?? [],
|
|
106
|
+
routeExplanation: (payload.result.routeExplanation?.routes ?? []).map((route) => ({
|
|
107
|
+
target: route.target,
|
|
108
|
+
mode: route.mode,
|
|
109
|
+
routeAction: route.routeAction,
|
|
110
|
+
readiness: route.readiness,
|
|
111
|
+
admissionAction: route.semanticMerge?.admissionAction,
|
|
112
|
+
missingEvidence: route.missingEvidence
|
|
113
|
+
}))
|
|
107
114
|
});
|
|
108
115
|
} catch (error) {
|
|
109
116
|
state.result = errorResult(error);
|
|
@@ -116,49 +123,63 @@ async function convertFrom(sourceLanguage) {
|
|
|
116
123
|
function render() {
|
|
117
124
|
const result = state.result || {};
|
|
118
125
|
const summary = result.summary || {};
|
|
126
|
+
const projections = projectionSet(result);
|
|
119
127
|
readinessValue.textContent = summary.readiness || 'error';
|
|
120
128
|
readinessValue.style.color = readinessColor(summary.readiness);
|
|
121
129
|
symbolValue.textContent = String(summary.symbols ?? 0);
|
|
122
130
|
mappingValue.textContent = String(summary.sourceMapMappings ?? 0);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
rustValue.textContent = projectionStatus(projections.rust);
|
|
132
|
+
pythonValue.textContent = projectionStatus(projections.python);
|
|
133
|
+
typescriptStatus.textContent = result.sourceHash || 'source';
|
|
134
|
+
rustStatus.textContent = projectionSummaryText(projections.rust);
|
|
135
|
+
pythonStatus.textContent = projectionSummaryText(projections.python);
|
|
136
|
+
typescriptInput.value = state.sources.typescript || '';
|
|
137
|
+
rustOutput.value = projections.rust?.output || '';
|
|
138
|
+
pythonOutput.value = projections.python?.output || '';
|
|
139
|
+
document.querySelector('[data-view-panel="source"]').classList.add('isSource');
|
|
140
|
+
frontierJson.textContent = JSON.stringify(result.frontier || {}, null, 2);
|
|
128
141
|
frontierJson.hidden = state.activeTab !== 'json';
|
|
129
142
|
graphView.hidden = state.activeTab !== 'graph';
|
|
130
143
|
if (!graphView.hidden) graphView.innerHTML = graphHtml(result);
|
|
131
144
|
}
|
|
132
145
|
|
|
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));
|
|
146
|
+
function projectionSet(result) {
|
|
147
|
+
if (result.projections) return result.projections;
|
|
148
|
+
const projections = {};
|
|
149
|
+
if (result.projection) {
|
|
150
|
+
projections[result.projection.targetLanguage || result.projection.target || 'rust'] = result.projection;
|
|
142
151
|
}
|
|
152
|
+
return projections;
|
|
143
153
|
}
|
|
144
154
|
|
|
145
|
-
function
|
|
146
|
-
if (
|
|
147
|
-
return
|
|
155
|
+
function projectionStatus(projection) {
|
|
156
|
+
if (!projection) return 'missing';
|
|
157
|
+
return projection.ok ? projection.readiness || 'ready' : 'blocked';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function projectionSummaryText(projection) {
|
|
161
|
+
if (!projection) return 'missing';
|
|
162
|
+
return (projection.mode || '-') + ' / ' + String(projection.sourceMapMappings ?? 0) + ' maps';
|
|
148
163
|
}
|
|
149
164
|
|
|
150
165
|
function graphHtml(result) {
|
|
151
166
|
const summary = result.summary || {};
|
|
152
167
|
const frontier = result.frontier || {};
|
|
168
|
+
const routeExplanation = result.routeExplanation || {};
|
|
169
|
+
const semanticMerge = routeExplanation.semanticMerge || {};
|
|
153
170
|
const symbols = frontier.symbols || [];
|
|
154
171
|
const relations = frontier.relations || [];
|
|
172
|
+
const missingEvidence = semanticMerge.missingEvidence || [];
|
|
155
173
|
return [
|
|
156
174
|
'<div class="chipRow">',
|
|
157
175
|
chip(summary.readiness, readinessClass(summary.readiness)),
|
|
176
|
+
chip('merge ' + (semanticMerge.readiness || 'unknown'), readinessClass(semanticMerge.readiness)),
|
|
177
|
+
chip(String(missingEvidence.length) + ' missing evidence', missingEvidence.length ? 'review' : 'ready'),
|
|
158
178
|
chip(String(summary.losses || 0) + ' losses', 'review'),
|
|
159
179
|
chip(String(summary.patchHints || 0) + ' patch hints', 'ready'),
|
|
160
|
-
chip((result
|
|
180
|
+
chip(projectionTargets(result), 'review'),
|
|
161
181
|
'</div>',
|
|
182
|
+
routeExplanationHtml(routeExplanation),
|
|
162
183
|
'<div class="sectionTitle">Symbols</div>',
|
|
163
184
|
'<div class="graphGrid">',
|
|
164
185
|
...symbols.map((symbol) => nodeCard(symbol)),
|
|
@@ -172,10 +193,56 @@ function graphHtml(result) {
|
|
|
172
193
|
].join('');
|
|
173
194
|
}
|
|
174
195
|
|
|
196
|
+
function projectionTargets(result) {
|
|
197
|
+
const targets = Object.keys(projectionSet(result));
|
|
198
|
+
return (result.sourceLanguage || '-') + ' -> ' + (targets.length ? targets.join('/') : '-');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function routeExplanationHtml(explanation) {
|
|
202
|
+
const routes = explanation.routes || [];
|
|
203
|
+
if (!routes.length) return '';
|
|
204
|
+
const semanticMerge = explanation.semanticMerge || {};
|
|
205
|
+
return [
|
|
206
|
+
'<div class="sectionTitle">Route Explanation</div>',
|
|
207
|
+
'<section class="routeSummary">',
|
|
208
|
+
'<strong>' + escapeHtml('Semantic merge: ' + (semanticMerge.readiness || 'unknown')) + '</strong>',
|
|
209
|
+
'<span>' + escapeHtml((semanticMerge.admissionAction || 'prioritize') + ' / score ' + String(semanticMerge.score ?? 0)) + '</span>',
|
|
210
|
+
'</section>',
|
|
211
|
+
'<div class="routeGrid">',
|
|
212
|
+
...routes.map((route) => routeCard(route)),
|
|
213
|
+
'</div>'
|
|
214
|
+
].join('');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function routeCard(route) {
|
|
218
|
+
const semanticMerge = route.semanticMerge || {};
|
|
219
|
+
const missing = route.missingEvidenceDetails || [];
|
|
220
|
+
return '<article class="routeCard">' +
|
|
221
|
+
'<div class="routeCardHeader">' +
|
|
222
|
+
'<strong>' + escapeHtml((route.sourceLanguage || '-') + ' -> ' + (route.target || '-')) + '</strong>' +
|
|
223
|
+
chip(semanticMerge.readiness || route.readiness, readinessClass(semanticMerge.readiness || route.readiness)) +
|
|
224
|
+
'</div>' +
|
|
225
|
+
'<p>' + escapeHtml(route.explanation || '') + '</p>' +
|
|
226
|
+
'<dl class="routeFacts">' +
|
|
227
|
+
factHtml('Mode', route.mode) +
|
|
228
|
+
factHtml('Action', route.routeAction) +
|
|
229
|
+
factHtml('Adapter', route.adapter || 'none') +
|
|
230
|
+
factHtml('Admission', semanticMerge.admissionAction || 'prioritize') +
|
|
231
|
+
'</dl>' +
|
|
232
|
+
'<div class="missingEvidenceList">' +
|
|
233
|
+
missing.map((gap) => '<span title="' + escapeHtml(gap.summary) + '">' + escapeHtml(gap.label) + '</span>').join('') +
|
|
234
|
+
'</div>' +
|
|
235
|
+
'</article>';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function factHtml(label, value) {
|
|
239
|
+
return '<div><dt>' + escapeHtml(label) + '</dt><dd>' + escapeHtml(value || '-') + '</dd></div>';
|
|
240
|
+
}
|
|
241
|
+
|
|
175
242
|
function nodeCard(symbol) {
|
|
176
243
|
return '<article class="nodeCard" data-graph-node="' + escapeHtml(symbol.id || symbol.name) + '">' +
|
|
177
244
|
'<strong>' + escapeHtml(symbol.name) + '</strong>' +
|
|
178
|
-
'<span>' + escapeHtml(symbol.kind || 'symbol') + '
|
|
245
|
+
'<span>' + escapeHtml(symbol.kind || 'symbol') + ' - ' + escapeHtml(symbol.region || 'region') + '</span>' +
|
|
179
246
|
'</article>';
|
|
180
247
|
}
|
|
181
248
|
|
|
@@ -199,13 +266,22 @@ function errorResult(error) {
|
|
|
199
266
|
return {
|
|
200
267
|
summary: { readiness: 'blocked', symbols: 0, sourceMapMappings: 0, losses: 1, patchHints: 0 },
|
|
201
268
|
frontier: { symbols: [], relations: [], error: String(error.message || error) },
|
|
202
|
-
|
|
269
|
+
projections: {
|
|
270
|
+
rust: { output: String(error.stack || error), readiness: 'blocked', mode: 'error', ok: false },
|
|
271
|
+
python: { output: String(error.stack || error), readiness: 'blocked', mode: 'error', ok: false }
|
|
272
|
+
}
|
|
203
273
|
};
|
|
204
274
|
}
|
|
205
275
|
|
|
206
276
|
function projectedSourceText() {
|
|
207
|
-
const
|
|
208
|
-
return
|
|
277
|
+
const projections = projectionSet(state.result || {});
|
|
278
|
+
return [
|
|
279
|
+
'// Rust projection',
|
|
280
|
+
projections.rust?.output || '',
|
|
281
|
+
'',
|
|
282
|
+
'# Python projection',
|
|
283
|
+
projections.python?.output || ''
|
|
284
|
+
].join('\\n');
|
|
209
285
|
}
|
|
210
286
|
|
|
211
287
|
function boundsHtml(bounds) {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compileNativeSource,
|
|
3
|
+
createSemanticImportSidecar,
|
|
4
|
+
createUniversalConversionPlan,
|
|
5
|
+
importNativeSource,
|
|
6
|
+
queryUniversalConversionPlan,
|
|
7
|
+
writeUniversalAstJson
|
|
8
|
+
} from '../dist/index.js';
|
|
9
|
+
import {
|
|
10
|
+
createTsToPythonWorkbenchAdapter,
|
|
11
|
+
createTsToRustWorkbenchAdapter
|
|
12
|
+
} from './js-frontier-rust-workbench-adapters.mjs';
|
|
13
|
+
import { conversionBounds } from './js-frontier-rust-workbench-bounds.mjs';
|
|
14
|
+
import {
|
|
15
|
+
explainRoute,
|
|
16
|
+
routeExplanationSummary
|
|
17
|
+
} from './js-frontier-rust-workbench-route.mjs';
|
|
18
|
+
|
|
19
|
+
export function convertSource(source, options = {}) {
|
|
20
|
+
const sourceLanguage = normalizeSourceLanguage(options.sourceLanguage);
|
|
21
|
+
const targets = ['rust', 'python'];
|
|
22
|
+
const imported = importNativeSource({
|
|
23
|
+
language: sourceLanguage,
|
|
24
|
+
sourcePath: `workbench/input.${sourceExtension(sourceLanguage)}`,
|
|
25
|
+
sourceText: source
|
|
26
|
+
});
|
|
27
|
+
const targetAdapters = [createTsToRustWorkbenchAdapter(), createTsToPythonWorkbenchAdapter()];
|
|
28
|
+
const projections = Object.fromEntries(targets.map((target) => {
|
|
29
|
+
const projection = compileNativeSource(imported, {
|
|
30
|
+
target,
|
|
31
|
+
targetPath: `workbench/output.${targetExtension(target)}`,
|
|
32
|
+
targetAdapters,
|
|
33
|
+
emitOnBlocked: true
|
|
34
|
+
});
|
|
35
|
+
return [target, projectionSummary(projection, { sourceLanguage, target })];
|
|
36
|
+
}));
|
|
37
|
+
const conversionPlan = createUniversalConversionPlan({
|
|
38
|
+
generatedAt: 0,
|
|
39
|
+
imports: [imported],
|
|
40
|
+
targetAdapters,
|
|
41
|
+
targets
|
|
42
|
+
});
|
|
43
|
+
const routes = targets.map((target) => explainRoute(
|
|
44
|
+
queryUniversalConversionPlan(conversionPlan, { sourceLanguage, target }).bestRoute,
|
|
45
|
+
{ projection: projections[target], sourceLanguage, target }
|
|
46
|
+
));
|
|
47
|
+
const sidecar = createSemanticImportSidecar(imported, { generatedAt: 0, targetPath: projections.rust?.targetPath });
|
|
48
|
+
return {
|
|
49
|
+
sourceHash: imported.nativeSource.sourceHash,
|
|
50
|
+
sourceLanguage,
|
|
51
|
+
summary: {
|
|
52
|
+
readiness: sidecar.summary.readiness,
|
|
53
|
+
symbols: imported.semanticIndex.symbols.length,
|
|
54
|
+
sourceMapMappings: imported.sourceMaps[0]?.mappings.length ?? 0,
|
|
55
|
+
losses: imported.losses.length,
|
|
56
|
+
patchHints: sidecar.patchHints.length
|
|
57
|
+
},
|
|
58
|
+
frontier: {
|
|
59
|
+
universalAst: universalAstSummary(imported),
|
|
60
|
+
semanticIndexId: imported.semanticIndex.id,
|
|
61
|
+
sidecarId: sidecar.id,
|
|
62
|
+
symbols: imported.semanticIndex.symbols.map(symbolSummary),
|
|
63
|
+
relations: relationSummaries(imported),
|
|
64
|
+
losses: imported.losses.map(lossSummary),
|
|
65
|
+
patchHints: sidecar.patchHints.map((hint) => ({
|
|
66
|
+
id: hint.id,
|
|
67
|
+
readiness: hint.readiness,
|
|
68
|
+
operations: hint.supportedOperations,
|
|
69
|
+
ownershipKey: hint.ownershipKey
|
|
70
|
+
}))
|
|
71
|
+
},
|
|
72
|
+
bounds: conversionBounds(sourceLanguage, targets.join('/')),
|
|
73
|
+
routeExplanation: routeExplanationSummary(conversionPlan, routes),
|
|
74
|
+
projection: projections.rust,
|
|
75
|
+
projections
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function projectionSummary(projection, { sourceLanguage, target }) {
|
|
80
|
+
return {
|
|
81
|
+
target,
|
|
82
|
+
targetLanguage: target,
|
|
83
|
+
sourceLanguage,
|
|
84
|
+
targetPath: projection.sourceMap?.targetPath,
|
|
85
|
+
mode: projection.outputMode,
|
|
86
|
+
readiness: projection.readiness.readiness,
|
|
87
|
+
ok: projection.ok,
|
|
88
|
+
output: projection.output,
|
|
89
|
+
sourceMapMappings: projection.sourceMap?.mappings.length ?? 0,
|
|
90
|
+
targetAdapter: projection.metadata?.targetProjectionAdapterId,
|
|
91
|
+
evidence: (projection.evidence ?? []).map(evidenceSummary)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function evidenceSummary(record) {
|
|
96
|
+
return {
|
|
97
|
+
id: record.id,
|
|
98
|
+
kind: record.kind,
|
|
99
|
+
status: record.status,
|
|
100
|
+
summary: record.summary,
|
|
101
|
+
adapterId: record.metadata?.adapterId
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function universalAstSummary(imported) {
|
|
106
|
+
const summary = { valid: true, validationError: undefined };
|
|
107
|
+
let parsed = imported.universalAst;
|
|
108
|
+
try {
|
|
109
|
+
parsed = JSON.parse(writeUniversalAstJson(imported.universalAst));
|
|
110
|
+
} catch (error) {
|
|
111
|
+
summary.valid = false;
|
|
112
|
+
summary.validationError = String(error.message || error);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
...summary,
|
|
116
|
+
kind: parsed.kind,
|
|
117
|
+
id: parsed.id,
|
|
118
|
+
nativeSources: parsed.nativeSources?.length ?? 0,
|
|
119
|
+
semanticSymbols: parsed.semanticIndex?.symbols?.length ?? 0,
|
|
120
|
+
sourceMaps: parsed.sourceMaps?.length ?? 0,
|
|
121
|
+
layers: Object.keys(parsed.layers ?? {})
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function symbolSummary(symbol) {
|
|
126
|
+
return {
|
|
127
|
+
id: symbol.id,
|
|
128
|
+
name: symbol.name,
|
|
129
|
+
kind: symbol.kind,
|
|
130
|
+
language: symbol.language,
|
|
131
|
+
region: symbol.metadata?.ownershipRegionKind,
|
|
132
|
+
ownershipKey: symbol.metadata?.ownershipRegionKey
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function relationSummaries(imported) {
|
|
137
|
+
const names = new Map(imported.semanticIndex.symbols.map((symbol) => [symbol.id, symbol.name]));
|
|
138
|
+
return imported.semanticIndex.relations.map((relation) => ({
|
|
139
|
+
id: relation.id,
|
|
140
|
+
predicate: relation.predicate,
|
|
141
|
+
label: `${names.get(relation.sourceId) ?? relation.sourceId} ${relation.predicate} ${names.get(relation.targetId) ?? relation.targetId}`
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function lossSummary(loss) {
|
|
146
|
+
return { id: loss.id, kind: loss.kind, severity: loss.severity, message: loss.message };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizeSourceLanguage(language) {
|
|
150
|
+
return 'typescript';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function targetExtension(target) {
|
|
154
|
+
if (target === 'rust') return 'rs';
|
|
155
|
+
if (target === 'python') return 'py';
|
|
156
|
+
return 'txt';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function sourceExtension(language) {
|
|
160
|
+
return 'ts';
|
|
161
|
+
}
|
|
@@ -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>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export function workbenchRouteStyles() {
|
|
2
|
+
return `
|
|
3
|
+
.routeSummary {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: space-between;
|
|
7
|
+
gap: 10px;
|
|
8
|
+
border: 1px solid var(--line);
|
|
9
|
+
border-radius: 8px;
|
|
10
|
+
background: #12171a;
|
|
11
|
+
padding: 10px;
|
|
12
|
+
}
|
|
13
|
+
.routeSummary strong {
|
|
14
|
+
min-width: 0;
|
|
15
|
+
overflow-wrap: anywhere;
|
|
16
|
+
font-size: 13px;
|
|
17
|
+
}
|
|
18
|
+
.routeSummary span {
|
|
19
|
+
flex: 0 0 auto;
|
|
20
|
+
color: var(--muted);
|
|
21
|
+
font-family: var(--mono);
|
|
22
|
+
font-size: 11px;
|
|
23
|
+
}
|
|
24
|
+
.routeGrid {
|
|
25
|
+
display: grid;
|
|
26
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
27
|
+
gap: 8px;
|
|
28
|
+
margin-top: 8px;
|
|
29
|
+
}
|
|
30
|
+
.routeCard {
|
|
31
|
+
border: 1px solid var(--line);
|
|
32
|
+
border-radius: 8px;
|
|
33
|
+
background: #14191d;
|
|
34
|
+
padding: 10px;
|
|
35
|
+
}
|
|
36
|
+
.routeCardHeader {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
gap: 8px;
|
|
41
|
+
}
|
|
42
|
+
.routeCardHeader strong {
|
|
43
|
+
min-width: 0;
|
|
44
|
+
overflow-wrap: anywhere;
|
|
45
|
+
font-size: 13px;
|
|
46
|
+
}
|
|
47
|
+
.routeCard p {
|
|
48
|
+
margin: 8px 0;
|
|
49
|
+
color: var(--muted);
|
|
50
|
+
font-size: 12px;
|
|
51
|
+
line-height: 1.45;
|
|
52
|
+
overflow-wrap: anywhere;
|
|
53
|
+
}
|
|
54
|
+
.routeFacts {
|
|
55
|
+
display: grid;
|
|
56
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
57
|
+
gap: 6px;
|
|
58
|
+
margin: 0 0 8px;
|
|
59
|
+
}
|
|
60
|
+
.routeFacts div {
|
|
61
|
+
min-width: 0;
|
|
62
|
+
}
|
|
63
|
+
.routeFacts dt {
|
|
64
|
+
color: var(--faint);
|
|
65
|
+
font-size: 10px;
|
|
66
|
+
text-transform: uppercase;
|
|
67
|
+
}
|
|
68
|
+
.routeFacts dd {
|
|
69
|
+
margin: 2px 0 0;
|
|
70
|
+
color: var(--text);
|
|
71
|
+
font-family: var(--mono);
|
|
72
|
+
font-size: 11px;
|
|
73
|
+
overflow-wrap: anywhere;
|
|
74
|
+
}
|
|
75
|
+
.missingEvidenceList {
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-wrap: wrap;
|
|
78
|
+
gap: 5px;
|
|
79
|
+
}
|
|
80
|
+
.missingEvidenceList span {
|
|
81
|
+
border: 1px solid rgba(231, 183, 95, .45);
|
|
82
|
+
border-radius: 999px;
|
|
83
|
+
padding: 3px 6px;
|
|
84
|
+
color: var(--amber);
|
|
85
|
+
font-size: 10px;
|
|
86
|
+
}
|
|
87
|
+
.edgeList {
|
|
88
|
+
display: grid;
|
|
89
|
+
gap: 6px;
|
|
90
|
+
margin: 0;
|
|
91
|
+
padding: 0;
|
|
92
|
+
list-style: none;
|
|
93
|
+
}
|
|
94
|
+
.edgeList li {
|
|
95
|
+
border-left: 2px solid var(--blue);
|
|
96
|
+
background: rgba(109, 180, 232, .08);
|
|
97
|
+
padding: 6px 8px;
|
|
98
|
+
color: var(--muted);
|
|
99
|
+
font-size: 12px;
|
|
100
|
+
overflow-wrap: anywhere;
|
|
101
|
+
}
|
|
102
|
+
.boundsGrid {
|
|
103
|
+
display: grid;
|
|
104
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
105
|
+
gap: 8px;
|
|
106
|
+
}
|
|
107
|
+
.boundCard {
|
|
108
|
+
border: 1px solid var(--line);
|
|
109
|
+
border-radius: 8px;
|
|
110
|
+
background: #12171a;
|
|
111
|
+
padding: 10px;
|
|
112
|
+
}
|
|
113
|
+
.boundCard strong {
|
|
114
|
+
display: block;
|
|
115
|
+
margin-bottom: 8px;
|
|
116
|
+
font-size: 12px;
|
|
117
|
+
}
|
|
118
|
+
.boundCard ul {
|
|
119
|
+
margin: 0;
|
|
120
|
+
padding-left: 16px;
|
|
121
|
+
color: var(--muted);
|
|
122
|
+
font-size: 11px;
|
|
123
|
+
line-height: 1.45;
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
}
|