stone-lang 0.1.0

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 (68) hide show
  1. package/README.md +52 -0
  2. package/StoneEngine.js +879 -0
  3. package/StoneEngineService.js +1727 -0
  4. package/adapters/FileSystemAdapter.js +230 -0
  5. package/adapters/OutputAdapter.js +208 -0
  6. package/adapters/index.js +6 -0
  7. package/cli/CLIOutputAdapter.js +196 -0
  8. package/cli/DaemonClient.js +349 -0
  9. package/cli/JSONOutputAdapter.js +135 -0
  10. package/cli/ReplSession.js +567 -0
  11. package/cli/ViewerServer.js +590 -0
  12. package/cli/commands/check.js +84 -0
  13. package/cli/commands/daemon.js +189 -0
  14. package/cli/commands/kill.js +66 -0
  15. package/cli/commands/package.js +713 -0
  16. package/cli/commands/ps.js +65 -0
  17. package/cli/commands/run.js +537 -0
  18. package/cli/entry.js +169 -0
  19. package/cli/index.js +14 -0
  20. package/cli/stonec.js +358 -0
  21. package/cli/test-compiler.js +181 -0
  22. package/cli/viewer/index.html +495 -0
  23. package/daemon/IPCServer.js +455 -0
  24. package/daemon/ProcessManager.js +327 -0
  25. package/daemon/ProcessRunner.js +307 -0
  26. package/daemon/daemon.js +398 -0
  27. package/daemon/index.js +16 -0
  28. package/frontend/analysis/index.js +5 -0
  29. package/frontend/analysis/livenessAnalyzer.js +568 -0
  30. package/frontend/analysis/treeShaker.js +265 -0
  31. package/frontend/index.js +20 -0
  32. package/frontend/parsing/astBuilder.js +2196 -0
  33. package/frontend/parsing/index.js +7 -0
  34. package/frontend/parsing/sonParser.js +592 -0
  35. package/frontend/parsing/stoneAstTypes.js +703 -0
  36. package/frontend/parsing/terminal-registry.js +435 -0
  37. package/frontend/parsing/tokenizer.js +692 -0
  38. package/frontend/type-checker/OverloadedFunctionType.js +43 -0
  39. package/frontend/type-checker/TypeEnvironment.js +165 -0
  40. package/frontend/type-checker/bidirectionalInference.js +149 -0
  41. package/frontend/type-checker/index.js +10 -0
  42. package/frontend/type-checker/moduleAnalysis.js +248 -0
  43. package/frontend/type-checker/operatorMappings.js +35 -0
  44. package/frontend/type-checker/overloadResolution.js +605 -0
  45. package/frontend/type-checker/typeChecker.js +452 -0
  46. package/frontend/type-checker/typeCompatibility.js +389 -0
  47. package/frontend/type-checker/visitors/controlFlow.js +483 -0
  48. package/frontend/type-checker/visitors/functions.js +604 -0
  49. package/frontend/type-checker/visitors/index.js +38 -0
  50. package/frontend/type-checker/visitors/literals.js +341 -0
  51. package/frontend/type-checker/visitors/modules.js +159 -0
  52. package/frontend/type-checker/visitors/operators.js +109 -0
  53. package/frontend/type-checker/visitors/statements.js +768 -0
  54. package/frontend/types/index.js +5 -0
  55. package/frontend/types/operatorMap.js +134 -0
  56. package/frontend/types/types.js +2046 -0
  57. package/frontend/utils/errorCollector.js +244 -0
  58. package/frontend/utils/index.js +5 -0
  59. package/frontend/utils/moduleResolver.js +479 -0
  60. package/package.json +50 -0
  61. package/packages/browserCache.js +359 -0
  62. package/packages/fetcher.js +236 -0
  63. package/packages/index.js +130 -0
  64. package/packages/lockfile.js +271 -0
  65. package/packages/manifest.js +291 -0
  66. package/packages/packageResolver.js +356 -0
  67. package/packages/resolver.js +310 -0
  68. package/packages/semver.js +635 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Compiler Integration Tests
3
+ *
4
+ * Tests the Stone compiler with all backends.
5
+ */
6
+
7
+ import {
8
+ compile,
9
+ compileToWasm,
10
+ compileToC,
11
+ compileToHIR,
12
+ CompilationTarget,
13
+ StoneCompilerOptions,
14
+ } from '../compiler.js';
15
+ import { OptimizationLevel } from '../optimization/optimization-manager.js';
16
+
17
+ // ============================================================================
18
+ // TEST UTILITIES
19
+ // ============================================================================
20
+
21
+ function test(name, fn) {
22
+ try {
23
+ fn();
24
+ console.log(`✓ ${name}`);
25
+ return true;
26
+ } catch (error) {
27
+ console.error(`✗ ${name}`);
28
+ console.error(` ${error.message}`);
29
+ return false;
30
+ }
31
+ }
32
+
33
+ function assert(condition, message) {
34
+ if (!condition) {
35
+ throw new Error(message || 'Assertion failed');
36
+ }
37
+ }
38
+
39
+ // ============================================================================
40
+ // TESTS
41
+ // ============================================================================
42
+
43
+ const testSource = `
44
+ fn add(a, b) = a + b
45
+ fn mul(x, y) = x * y
46
+ result = add(3, 4) + mul(2, 5)
47
+ `;
48
+
49
+ const tests = [
50
+ // Basic compilation tests
51
+ () => test('compile() returns success', () => {
52
+ const result = compile(testSource);
53
+ assert(result.success, 'Compilation should succeed');
54
+ assert(result.output, 'Should have output');
55
+ assert(result.metadata, 'Should have metadata');
56
+ }),
57
+
58
+ () => test('compile() returns timings', () => {
59
+ const result = compile(testSource);
60
+ assert(result.metadata.timings.parse !== undefined, 'Should have parse timing');
61
+ assert(result.metadata.timings.hir !== undefined, 'Should have hir timing');
62
+ assert(result.metadata.totalTime >= 0, 'Should have total time');
63
+ }),
64
+
65
+ // Target-specific tests
66
+ () => test('compileToWasm() produces WASM binary', () => {
67
+ const result = compileToWasm(testSource);
68
+ assert(result.success, 'WASM compilation should succeed');
69
+ assert(result.output.binary instanceof Uint8Array, 'Should produce binary');
70
+ // Check WASM magic number
71
+ assert(result.output.binary[0] === 0x00, 'Should have WASM magic');
72
+ assert(result.output.binary[1] === 0x61, 'Should have WASM magic');
73
+ }),
74
+
75
+ () => test('compileToC() produces C source', () => {
76
+ const result = compileToC(testSource);
77
+ assert(result.success, 'C compilation should succeed');
78
+ assert(typeof result.output.source === 'string', 'Should produce source');
79
+ assert(result.output.source.includes('#include'), 'Should have includes');
80
+ assert(result.output.source.includes('int main'), 'Should have main');
81
+ assert(result.output.makefile, 'Should have Makefile');
82
+ }),
83
+
84
+ () => test('compileToHIR() produces HIR text', () => {
85
+ const result = compileToHIR(testSource);
86
+ assert(result.success, 'HIR compilation should succeed');
87
+ assert(typeof result.output.text === 'string', 'Should produce text');
88
+ assert(result.output.hir, 'Should have HIR module');
89
+ }),
90
+
91
+ // Optimization level tests
92
+ () => test('O0 optimization level works', () => {
93
+ const result = compile(testSource, {
94
+ optimizationLevel: OptimizationLevel.NONE,
95
+ });
96
+ assert(result.success, 'Should compile with O0');
97
+ }),
98
+
99
+ () => test('O3 optimization level works', () => {
100
+ const result = compile(testSource, {
101
+ optimizationLevel: OptimizationLevel.AGGRESSIVE,
102
+ });
103
+ assert(result.success, 'Should compile with O3');
104
+ }),
105
+
106
+ // Error handling tests
107
+ () => test('compile() handles syntax errors', () => {
108
+ const badSource = 'fn broken( = )';
109
+ const result = compile(badSource);
110
+ assert(!result.success, 'Should fail on syntax error');
111
+ assert(result.error, 'Should have error info');
112
+ assert(result.error.stage === 'parse', 'Should fail at parse stage');
113
+ }),
114
+
115
+ // Complex source tests
116
+ () => test('compiles functions with blocks', () => {
117
+ const source = `
118
+ fn compute(x) {
119
+ y = x * 2
120
+ z = y + 1
121
+ return z
122
+ }
123
+ `;
124
+ const result = compile(source);
125
+ assert(result.success, 'Should compile block functions');
126
+ }),
127
+
128
+ () => test('compiles conditionals', () => {
129
+ const source = `
130
+ fn max(a, b) = if (a > b) { a } else { b }
131
+ `;
132
+ const result = compile(source);
133
+ assert(result.success, 'Should compile conditionals');
134
+ }),
135
+
136
+ // Options tests
137
+ () => test('printHIR option works', () => {
138
+ const result = compile(testSource, { printHIR: true });
139
+ assert(result.success, 'Should compile with printHIR');
140
+ }),
141
+
142
+ () => test('different targets produce different outputs', () => {
143
+ const wasmResult = compileToWasm(testSource);
144
+ const cResult = compileToC(testSource);
145
+ const hirResult = compileToHIR(testSource);
146
+
147
+ assert(wasmResult.success, 'WASM should succeed');
148
+ assert(cResult.success, 'C should succeed');
149
+ assert(hirResult.success, 'HIR should succeed');
150
+
151
+ // Verify they're different
152
+ assert(wasmResult.output.binary, 'WASM has binary');
153
+ assert(cResult.output.source, 'C has source');
154
+ assert(hirResult.output.text, 'HIR has text');
155
+ }),
156
+ ];
157
+
158
+ // ============================================================================
159
+ // RUN TESTS
160
+ // ============================================================================
161
+
162
+ console.log('Stone Compiler Integration Tests');
163
+ console.log('='.repeat(50));
164
+ console.log('');
165
+
166
+ let passed = 0;
167
+ let failed = 0;
168
+
169
+ for (const testFn of tests) {
170
+ if (testFn()) {
171
+ passed++;
172
+ } else {
173
+ failed++;
174
+ }
175
+ }
176
+
177
+ console.log('');
178
+ console.log('='.repeat(50));
179
+ console.log(`Results: ${passed} passed, ${failed} failed`);
180
+
181
+ process.exit(failed > 0 ? 1 : 0);
@@ -0,0 +1,495 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Stone Viewer</title>
7
+ <script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
8
+ <style>
9
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10
+ body {
11
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
12
+ background: #0d1117;
13
+ color: #e6edf3;
14
+ min-height: 100vh;
15
+ }
16
+ header {
17
+ background: #161b22;
18
+ border-bottom: 1px solid #30363d;
19
+ padding: 12px 20px;
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 12px;
23
+ }
24
+ header h1 {
25
+ font-size: 16px;
26
+ font-weight: 500;
27
+ color: #e6edf3;
28
+ }
29
+ header .session-id {
30
+ font-family: monospace;
31
+ font-size: 12px;
32
+ color: #7d8590;
33
+ background: #21262d;
34
+ padding: 4px 8px;
35
+ border-radius: 4px;
36
+ }
37
+ .container {
38
+ display: grid;
39
+ grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
40
+ gap: 20px;
41
+ padding: 20px;
42
+ }
43
+ .terminal {
44
+ background: #161b22;
45
+ border: 1px solid #30363d;
46
+ border-radius: 8px;
47
+ overflow: hidden;
48
+ }
49
+ .terminal-header {
50
+ background: #21262d;
51
+ padding: 10px 14px;
52
+ border-bottom: 1px solid #30363d;
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 8px;
56
+ }
57
+ .terminal-type {
58
+ font-size: 10px;
59
+ text-transform: uppercase;
60
+ letter-spacing: 0.5px;
61
+ color: #7d8590;
62
+ background: #30363d;
63
+ padding: 2px 6px;
64
+ border-radius: 3px;
65
+ }
66
+ .terminal-title {
67
+ font-size: 13px;
68
+ font-weight: 500;
69
+ color: #e6edf3;
70
+ }
71
+ .terminal-body {
72
+ padding: 14px;
73
+ }
74
+ .console-lines {
75
+ font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
76
+ font-size: 13px;
77
+ max-height: 400px;
78
+ overflow-y: auto;
79
+ line-height: 1.5;
80
+ }
81
+ .console-line {
82
+ padding: 1px 0;
83
+ color: #e6edf3;
84
+ }
85
+ .graph {
86
+ min-height: 350px;
87
+ }
88
+ .status {
89
+ position: fixed;
90
+ bottom: 16px;
91
+ right: 16px;
92
+ padding: 8px 14px;
93
+ background: #21262d;
94
+ border: 1px solid #30363d;
95
+ border-radius: 6px;
96
+ font-size: 12px;
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 8px;
100
+ }
101
+ .status-dot {
102
+ width: 8px;
103
+ height: 8px;
104
+ border-radius: 50%;
105
+ background: #7d8590;
106
+ }
107
+ .status.connected .status-dot { background: #3fb950; }
108
+ .status.disconnected .status-dot { background: #f85149; }
109
+ .status.connecting .status-dot { background: #d29922; animation: pulse 1s infinite; }
110
+ @keyframes pulse {
111
+ 0%, 100% { opacity: 1; }
112
+ 50% { opacity: 0.5; }
113
+ }
114
+ .empty-state {
115
+ text-align: center;
116
+ padding: 60px 20px;
117
+ color: #7d8590;
118
+ }
119
+ .empty-state h2 {
120
+ font-size: 18px;
121
+ font-weight: 500;
122
+ margin-bottom: 8px;
123
+ color: #e6edf3;
124
+ }
125
+ .empty-state p {
126
+ font-size: 14px;
127
+ }
128
+ </style>
129
+ </head>
130
+ <body>
131
+ <header>
132
+ <h1>Stone Viewer</h1>
133
+ <span class="session-id" id="session-id"></span>
134
+ </header>
135
+
136
+ <div class="container" id="terminals">
137
+ <div class="empty-state" id="empty-state">
138
+ <h2>Waiting for output...</h2>
139
+ <p>Terminals will appear here as they are created by your script.</p>
140
+ </div>
141
+ </div>
142
+
143
+ <div class="status connecting" id="status">
144
+ <div class="status-dot"></div>
145
+ <span id="status-text">Connecting...</span>
146
+ </div>
147
+
148
+ <script>
149
+ // Extract session ID from URL
150
+ const sessionId = window.location.pathname.split('/')[2];
151
+ document.getElementById('session-id').textContent = sessionId;
152
+
153
+ const container = document.getElementById('terminals');
154
+ const emptyState = document.getElementById('empty-state');
155
+ const statusEl = document.getElementById('status');
156
+ const statusText = document.getElementById('status-text');
157
+ let terminals = {};
158
+ let hasTerminals = false;
159
+
160
+ // WebSocket connection
161
+ const wsUrl = `ws://${location.host}/ws/${sessionId}`;
162
+ let ws;
163
+ let reconnectAttempts = 0;
164
+ const maxReconnectAttempts = 10;
165
+
166
+ function connect() {
167
+ ws = new WebSocket(wsUrl);
168
+
169
+ ws.onopen = () => {
170
+ reconnectAttempts = 0;
171
+ statusEl.className = 'status connected';
172
+ statusText.textContent = 'Connected';
173
+ };
174
+
175
+ ws.onclose = () => {
176
+ statusEl.className = 'status disconnected';
177
+ statusText.textContent = 'Disconnected';
178
+
179
+ if (reconnectAttempts < maxReconnectAttempts) {
180
+ reconnectAttempts++;
181
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts - 1), 10000);
182
+ statusText.textContent = `Reconnecting in ${delay/1000}s...`;
183
+ setTimeout(connect, delay);
184
+ }
185
+ };
186
+
187
+ ws.onerror = () => {
188
+ statusEl.className = 'status disconnected';
189
+ statusText.textContent = 'Connection error';
190
+ };
191
+
192
+ ws.onmessage = (e) => {
193
+ const msg = JSON.parse(e.data);
194
+ handleMessage(msg);
195
+ };
196
+ }
197
+
198
+ function handleMessage(msg) {
199
+ switch (msg.type) {
200
+ case 'init':
201
+ for (const [id, data] of Object.entries(msg.terminals || {})) {
202
+ createTerminal(id, data.type, data.config);
203
+ if (data.data && data.data.length > 0) {
204
+ updateTerminal(id, data.type, data.data, false);
205
+ }
206
+ }
207
+ break;
208
+
209
+ case 'terminal:create':
210
+ createTerminal(msg.id, msg.terminalType, msg.config);
211
+ break;
212
+
213
+ case 'terminal:add':
214
+ updateTerminal(msg.id, terminals[msg.id]?.type, msg.data, true);
215
+ break;
216
+
217
+ case 'terminal:set':
218
+ updateTerminal(msg.id, terminals[msg.id]?.type, msg.data, false);
219
+ break;
220
+
221
+ case 'terminal:clear':
222
+ clearTerminal(msg.id);
223
+ break;
224
+ }
225
+ }
226
+
227
+ function createTerminal(id, type, config) {
228
+ if (terminals[id]) return;
229
+
230
+ // Hide empty state
231
+ if (!hasTerminals) {
232
+ hasTerminals = true;
233
+ emptyState.style.display = 'none';
234
+ }
235
+
236
+ const div = document.createElement('div');
237
+ div.className = 'terminal';
238
+ div.id = `terminal-${id}`;
239
+
240
+ const header = document.createElement('div');
241
+ header.className = 'terminal-header';
242
+
243
+ const typeLabel = document.createElement('span');
244
+ typeLabel.className = 'terminal-type';
245
+ typeLabel.textContent = type;
246
+ header.appendChild(typeLabel);
247
+
248
+ const title = document.createElement('span');
249
+ title.className = 'terminal-title';
250
+ title.textContent = config.title || id;
251
+ header.appendChild(title);
252
+
253
+ div.appendChild(header);
254
+
255
+ const body = document.createElement('div');
256
+ body.className = 'terminal-body';
257
+
258
+ if (type === 'console') {
259
+ const lines = document.createElement('div');
260
+ lines.className = 'console-lines';
261
+ lines.id = `lines-${id}`;
262
+ body.appendChild(lines);
263
+ } else {
264
+ const graph = document.createElement('div');
265
+ graph.className = 'graph';
266
+ graph.id = `graph-${id}`;
267
+ body.appendChild(graph);
268
+ }
269
+
270
+ div.appendChild(body);
271
+ container.appendChild(div);
272
+ terminals[id] = { type, config, element: div, data: [] };
273
+ }
274
+
275
+ function updateTerminal(id, type, data, append) {
276
+ const terminal = terminals[id];
277
+ if (!terminal) return;
278
+
279
+ const items = Array.isArray(data) ? data : [data];
280
+
281
+ if (append) {
282
+ terminal.data.push(...items);
283
+ } else {
284
+ terminal.data = items;
285
+ }
286
+
287
+ if (type === 'console') {
288
+ renderConsole(id, terminal.data, append);
289
+ } else if (type === 'graph2d') {
290
+ renderGraph2D(id, terminal.data, terminal.config);
291
+ } else if (type === 'graph3d') {
292
+ renderGraph3D(id, terminal.data, terminal.config);
293
+ }
294
+ }
295
+
296
+ function clearTerminal(id) {
297
+ const terminal = terminals[id];
298
+ if (!terminal) return;
299
+
300
+ terminal.data = [];
301
+
302
+ if (terminal.type === 'console') {
303
+ const linesEl = document.getElementById(`lines-${id}`);
304
+ if (linesEl) linesEl.innerHTML = '';
305
+ } else {
306
+ const graphEl = document.getElementById(`graph-${id}`);
307
+ if (graphEl) Plotly.purge(graphEl);
308
+ }
309
+ }
310
+
311
+ function renderConsole(id, lines, append) {
312
+ const linesEl = document.getElementById(`lines-${id}`);
313
+ if (!linesEl) return;
314
+
315
+ if (!append) linesEl.innerHTML = '';
316
+
317
+ const itemsToRender = append ? lines.slice(-lines.length) : lines;
318
+ for (const item of itemsToRender) {
319
+ const line = document.createElement('div');
320
+ line.className = 'console-line';
321
+ line.textContent = String(item);
322
+ linesEl.appendChild(line);
323
+ }
324
+ linesEl.scrollTop = linesEl.scrollHeight;
325
+ }
326
+
327
+ // Convert StoneArray to regular JS array
328
+ function stoneArrayToJS(arr) {
329
+ if (!arr || arr._type !== 'StoneArray') return arr;
330
+ const data = arr.data;
331
+ // Handle both typed array (ArrayBuffer) and serialized object format
332
+ if (ArrayBuffer.isView(data)) {
333
+ return Array.from(data);
334
+ } else if (Array.isArray(data)) {
335
+ return data;
336
+ } else if (typeof data === 'object') {
337
+ // Serialized JSON format: {"0": val, "1": val, ...}
338
+ const size = arr.size || Object.keys(data).length;
339
+ const result = [];
340
+ for (let i = 0; i < size; i++) {
341
+ result.push(data[i]);
342
+ }
343
+ return result;
344
+ }
345
+ return [];
346
+ }
347
+
348
+ // Process Stone plot data to Plotly format
349
+ function processPlotData(plot) {
350
+ const type = plot.type || 'line';
351
+
352
+ // Extract x/y data - handle y-only case by auto-generating x
353
+ let x = plot.x;
354
+ let y = plot.y;
355
+
356
+ // Handle StoneArray format first
357
+ if (x && x._type === 'StoneArray') {
358
+ x = stoneArrayToJS(x);
359
+ }
360
+ if (y && y._type === 'StoneArray') {
361
+ y = stoneArrayToJS(y);
362
+ }
363
+
364
+ // If only y is provided, generate x as indices
365
+ if (y && !x) {
366
+ const yArr = Array.isArray(y) ? y : [];
367
+ x = yArr.map((_, i) => i);
368
+ }
369
+
370
+ // Convert Stone types to Plotly types
371
+ const color = plot.style?.color || plot.color || null;
372
+
373
+ switch (type) {
374
+ case 'line':
375
+ return {
376
+ x: x || [],
377
+ y: y || [],
378
+ type: 'scatter',
379
+ mode: 'lines+markers',
380
+ name: plot.title || plot.name || plot.label || 'Line',
381
+ line: { color: color, width: plot.style?.lineWidth || 2 },
382
+ marker: { color: color, size: plot.style?.markerSize || 5 }
383
+ };
384
+ case 'scatter':
385
+ return {
386
+ x: x || [],
387
+ y: y || [],
388
+ type: 'scatter',
389
+ mode: 'markers',
390
+ name: plot.title || plot.name || plot.label || 'Scatter',
391
+ marker: { color: color, size: plot.style?.markerSize || 8 }
392
+ };
393
+ case 'bar':
394
+ return {
395
+ x: x || plot.labels || [],
396
+ y: y || plot.values || [],
397
+ type: 'bar',
398
+ name: plot.title || plot.name || plot.label || 'Bar',
399
+ marker: { color: color }
400
+ };
401
+ case 'area':
402
+ return {
403
+ x: x || [],
404
+ y: y || [],
405
+ type: 'scatter',
406
+ mode: 'lines',
407
+ fill: 'tozeroy',
408
+ name: plot.title || plot.name || plot.label || 'Area',
409
+ line: { color: color }
410
+ };
411
+ default:
412
+ // Pass through for already-Plotly-formatted data
413
+ return {
414
+ x: x || [],
415
+ y: y || [],
416
+ type: plot.type || 'scatter',
417
+ mode: plot.mode || 'lines+markers',
418
+ name: plot.title || plot.name || plot.label || 'Plot',
419
+ line: plot.line || { color: color },
420
+ marker: plot.marker || { color: color }
421
+ };
422
+ }
423
+ }
424
+
425
+ function renderGraph2D(id, plots, config) {
426
+ const graphEl = document.getElementById(`graph-${id}`);
427
+ if (!graphEl) return;
428
+
429
+ const traces = plots.map((plot, i) => {
430
+ const processed = processPlotData(plot);
431
+ if (!processed.name || processed.name === 'Line' || processed.name === 'Plot') {
432
+ processed.name = `Series ${i + 1}`;
433
+ }
434
+ return processed;
435
+ });
436
+
437
+ const layout = {
438
+ title: { text: config.title || '', font: { color: '#e6edf3', size: 14 } },
439
+ xaxis: {
440
+ title: config.x_label || 'x',
441
+ color: '#7d8590',
442
+ gridcolor: '#30363d',
443
+ zerolinecolor: '#30363d',
444
+ },
445
+ yaxis: {
446
+ title: config.y_label || 'y',
447
+ color: '#7d8590',
448
+ gridcolor: '#30363d',
449
+ zerolinecolor: '#30363d',
450
+ },
451
+ paper_bgcolor: '#161b22',
452
+ plot_bgcolor: '#161b22',
453
+ font: { color: '#e6edf3' },
454
+ margin: { t: 50, r: 30, b: 50, l: 60 },
455
+ legend: { font: { color: '#e6edf3' } },
456
+ };
457
+
458
+ Plotly.react(graphEl, traces, layout, { responsive: true });
459
+ }
460
+
461
+ function renderGraph3D(id, objects, config) {
462
+ const graphEl = document.getElementById(`graph-${id}`);
463
+ if (!graphEl) return;
464
+
465
+ const traces = objects.map((obj, i) => ({
466
+ x: obj.x || [],
467
+ y: obj.y || [],
468
+ z: obj.z || [],
469
+ name: obj.label || `Object ${i + 1}`,
470
+ type: 'scatter3d',
471
+ mode: obj.mode || 'markers',
472
+ marker: { size: obj.size || 3, color: obj.color },
473
+ }));
474
+
475
+ const layout = {
476
+ title: { text: config.title || '', font: { color: '#e6edf3', size: 14 } },
477
+ paper_bgcolor: '#161b22',
478
+ scene: {
479
+ bgcolor: '#161b22',
480
+ xaxis: { title: 'x', gridcolor: '#30363d', color: '#7d8590' },
481
+ yaxis: { title: 'y', gridcolor: '#30363d', color: '#7d8590' },
482
+ zaxis: { title: 'z', gridcolor: '#30363d', color: '#7d8590' },
483
+ },
484
+ font: { color: '#e6edf3' },
485
+ margin: { t: 50, r: 30, b: 30, l: 30 },
486
+ };
487
+
488
+ Plotly.react(graphEl, traces, layout, { responsive: true });
489
+ }
490
+
491
+ // Start connection
492
+ connect();
493
+ </script>
494
+ </body>
495
+ </html>