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,65 @@
1
+ /**
2
+ * ps command - List running Stone processes
3
+ *
4
+ * Usage:
5
+ * stone ps
6
+ * stone ps --mine
7
+ * stone ps --status running
8
+ */
9
+
10
+ import { DaemonClient } from '../DaemonClient.js';
11
+
12
+ /**
13
+ * List running processes
14
+ * @param {Object} options - Command options
15
+ */
16
+ export async function psCommand(options = {}) {
17
+ const { mine = false, status = null, json = false } = options;
18
+
19
+ try {
20
+ const client = new DaemonClient({ autoStartDaemon: false });
21
+
22
+ // Check if daemon is running
23
+ const isRunning = await DaemonClient.isDaemonRunning();
24
+ if (!isRunning) {
25
+ if (json) {
26
+ console.log(JSON.stringify({ processes: [], error: 'Daemon not running' }));
27
+ } else {
28
+ console.log('No Stone daemon running.');
29
+ }
30
+ return;
31
+ }
32
+
33
+ await client.connect();
34
+
35
+ const processes = await client.list({ mine, status });
36
+
37
+ if (json) {
38
+ console.log(JSON.stringify({ processes }));
39
+ } else {
40
+ if (processes.length === 0) {
41
+ console.log('No processes.');
42
+ } else {
43
+ // Print table header
44
+ console.log('ID\t\tSTATUS\t\tSCRIPT\t\tDURATION');
45
+ console.log('-'.repeat(60));
46
+
47
+ for (const proc of processes) {
48
+ const duration = proc.duration ? `${proc.duration}ms` : '-';
49
+ console.log(`${proc.id}\t\t${proc.status}\t\t${proc.script}\t\t${duration}`);
50
+ }
51
+ }
52
+ }
53
+
54
+ client.disconnect();
55
+ } catch (err) {
56
+ if (json) {
57
+ console.log(JSON.stringify({ error: err.message }));
58
+ } else {
59
+ console.error(`Error: ${err.message}`);
60
+ }
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ export default psCommand;
@@ -0,0 +1,537 @@
1
+ /**
2
+ * run command - Execute a Stone script
3
+ *
4
+ * Usage:
5
+ * stone run <file.stn>
6
+ * stone run <file.stn> --json
7
+ * stone run <file.stn> --headless
8
+ *
9
+ * Modes:
10
+ * - Default: Human-readable output, opens browser for graphs
11
+ * - --json: JSON Lines output for IDE/desktop integration
12
+ * - --headless: No browser, exports graphs to files
13
+ *
14
+ * Execution:
15
+ * - Tries to use daemon first (shared with desktop app)
16
+ * - Falls back to direct StoneEngine if daemon unavailable
17
+ */
18
+
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+ import { StoneEngine } from '../../StoneEngine.js';
22
+ import { CLIOutputAdapter } from '../CLIOutputAdapter.js';
23
+ import { JSONOutputAdapter } from '../JSONOutputAdapter.js';
24
+ import { ViewerServer } from '../ViewerServer.js';
25
+ import { IPCClientConnection } from '../../daemon/IPCServer.js';
26
+ import { createNodePackageContext } from '../../packages/packageResolver.js';
27
+
28
+ /**
29
+ * Execute a Stone script
30
+ * @param {string} scriptPath - Path to script file
31
+ * @param {Object} options - Command options
32
+ */
33
+ export async function runCommand(scriptPath, options = {}) {
34
+ const {
35
+ json = false,
36
+ headless = false,
37
+ verbose = false,
38
+ quiet = false,
39
+ ast = false,
40
+ bytecode = false,
41
+ noServe = false,
42
+ daemon = true, // --no-daemon sets this to false
43
+ } = options;
44
+ const noDaemon = !daemon;
45
+
46
+ // Resolve script path
47
+ const fullPath = path.isAbsolute(scriptPath)
48
+ ? scriptPath
49
+ : path.resolve(process.cwd(), scriptPath);
50
+
51
+ // Check file exists
52
+ if (!fs.existsSync(fullPath)) {
53
+ console.error(`Error: Script not found: ${scriptPath}`);
54
+ process.exit(1);
55
+ }
56
+
57
+ // Read source code
58
+ const source = fs.readFileSync(fullPath, 'utf8');
59
+
60
+ // Handle special output modes (these don't need daemon)
61
+ if (ast) {
62
+ const engine = new StoneEngine({ debug: verbose });
63
+ try {
64
+ const parsed = engine.parse(source, { filename: scriptPath });
65
+ console.log(JSON.stringify(parsed, null, 2));
66
+ process.exit(0);
67
+ } catch (err) {
68
+ console.error(`Parse error: ${err.message}`);
69
+ process.exit(1);
70
+ }
71
+ }
72
+
73
+ // Try daemon execution first (unless --no-daemon)
74
+ if (!noDaemon) {
75
+ try {
76
+ const result = await runViaDaemon(fullPath, source, options);
77
+ if (result !== null) {
78
+ // Daemon handled it
79
+ return;
80
+ }
81
+ // result === null means daemon unavailable, fall through to direct
82
+ } catch (err) {
83
+ if (verbose) {
84
+ console.log(`[Daemon unavailable: ${err.message}, using direct execution]`);
85
+ }
86
+ }
87
+ }
88
+
89
+ // Fall back to direct execution
90
+ await runDirect(fullPath, source, options);
91
+ }
92
+
93
+ /**
94
+ * Run script via daemon
95
+ * @returns {Promise<boolean|null>} true if success, false if failed, null if daemon unavailable
96
+ */
97
+ async function runViaDaemon(fullPath, source, options) {
98
+ const { json, verbose, quiet } = options;
99
+
100
+ const client = new IPCClientConnection();
101
+
102
+ try {
103
+ await client.connect();
104
+ } catch (err) {
105
+ // Daemon not running - try to start it
106
+ if (err.code === 'ENOENT' || err.code === 'ECONNREFUSED') {
107
+ const started = await startDaemon(verbose);
108
+ if (!started) {
109
+ return null; // Can't start daemon, fall back to direct
110
+ }
111
+
112
+ // Wait a bit and retry
113
+ await new Promise(r => setTimeout(r, 500));
114
+ try {
115
+ await client.connect();
116
+ } catch (retryErr) {
117
+ return null; // Still can't connect
118
+ }
119
+ } else {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ // When running via daemon, the desktop app handles graph visualization
125
+ // No ViewerServer needed - just print output and terminal creation messages
126
+
127
+ // Handle events from daemon - set up BEFORE run command to not miss early events
128
+ let exitCode = 0;
129
+ let finished = false;
130
+ let processId = null;
131
+ let terminalsCreated = [];
132
+
133
+ client.on('event', (event) => {
134
+ // Ignore events for other processes
135
+ if (processId && event.processId && event.processId !== processId) {
136
+ return;
137
+ }
138
+
139
+ // Track terminal creation
140
+ if (event.event === 'terminal:create') {
141
+ const termType = event.type || event.terminalType;
142
+ terminalsCreated.push({ id: event.id, type: termType });
143
+
144
+ // For graph terminals, show creation message (console terminals just print their content)
145
+ if (!json && !quiet && (termType === 'graph2d' || termType === 'graph3d')) {
146
+ console.log(`\x1b[36m[Terminal created: ${termType}]\x1b[0m`);
147
+ }
148
+ }
149
+
150
+ // Handle console terminal output - print the actual lines
151
+ if (event.event === 'terminal:add') {
152
+ const terminal = terminalsCreated.find(t => t.id === event.id);
153
+ if (terminal && terminal.type === 'console') {
154
+ const lines = Array.isArray(event.data) ? event.data : [event.data];
155
+ for (const line of lines) {
156
+ if (json) {
157
+ console.log(JSON.stringify({ type: 'output', value: line }));
158
+ } else if (!quiet) {
159
+ console.log(line);
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ // Handle print events (for direct print() calls)
166
+ if (event.event === 'print') {
167
+ if (json) {
168
+ console.log(JSON.stringify({ type: 'output', value: event.value }));
169
+ } else if (!quiet) {
170
+ console.log(event.value);
171
+ }
172
+ }
173
+
174
+ // Handle error events
175
+ if (event.event === 'error') {
176
+ if (json) {
177
+ console.log(JSON.stringify({ type: 'error', message: event.message, location: event.location }));
178
+ } else if (!quiet) {
179
+ console.error(`Error: ${event.message}`);
180
+ if (event.location) {
181
+ console.error(` at ${event.location.file}:${event.location.line}:${event.location.column}`);
182
+ }
183
+ }
184
+ }
185
+
186
+ // Check for completion
187
+ if (event.event === 'process:end') {
188
+ finished = true;
189
+ exitCode = event.success ? 0 : 1;
190
+
191
+ if (verbose && !json) {
192
+ console.log(`\nCompleted in ${event.duration}ms`);
193
+ }
194
+
195
+ if (!event.success && !quiet) {
196
+ if (json) {
197
+ console.log(JSON.stringify({ type: 'failed', error: event.error }));
198
+ } else {
199
+ console.error(`\nExecution failed: ${event.error}`);
200
+ }
201
+ }
202
+
203
+ // Show message about viewing graph terminals in desktop app
204
+ if (!json && !quiet && terminalsCreated.length > 0) {
205
+ const graphTerminals = terminalsCreated.filter(t => t.type === 'graph2d' || t.type === 'graph3d');
206
+ if (graphTerminals.length > 0) {
207
+ console.log(`\n\x1b[33m📊 ${graphTerminals.length} graph terminal(s) created. View in Riva Viewport.\x1b[0m`);
208
+ }
209
+ }
210
+ }
211
+ });
212
+
213
+ // Send run command to daemon
214
+ const response = await client.request('run', {
215
+ script: fullPath,
216
+ source: source,
217
+ cwd: path.dirname(fullPath),
218
+ });
219
+
220
+ if (!response.processId) {
221
+ console.error('Daemon failed to start process');
222
+ client.disconnect();
223
+ return false;
224
+ }
225
+
226
+ processId = response.processId;
227
+ if (verbose) {
228
+ console.log(`[Running via daemon, processId: ${processId}]`);
229
+ }
230
+
231
+ // Wait for process to complete
232
+ await new Promise((resolve) => {
233
+ const checkInterval = setInterval(() => {
234
+ if (finished) {
235
+ clearInterval(checkInterval);
236
+ resolve();
237
+ }
238
+ }, 50);
239
+ });
240
+
241
+ client.disconnect();
242
+ process.exit(exitCode);
243
+ }
244
+
245
+ /**
246
+ * Handle a daemon event
247
+ */
248
+ function handleDaemonEvent(event, processId, ctx) {
249
+ const { json, quiet, verbose, viewerServer, sessionId } = ctx;
250
+
251
+ // Ignore events for other processes (only filter if we have a processId to compare)
252
+ if (processId && event.processId && event.processId !== processId) {
253
+ return;
254
+ }
255
+
256
+ switch (event.event) {
257
+ case 'print':
258
+ if (json) {
259
+ console.log(JSON.stringify({ type: 'output', value: event.value }));
260
+ } else if (!quiet) {
261
+ console.log(event.value);
262
+ }
263
+ break;
264
+
265
+ case 'terminal:create':
266
+ if (viewerServer && sessionId) {
267
+ const session = viewerServer.getSession(sessionId);
268
+ if (session) {
269
+ session.addTerminal(event.id, event.type, event.config);
270
+ session.broadcast({ type: 'terminal:create', id: event.id, terminalType: event.type, config: event.config });
271
+ }
272
+
273
+ // Open browser on first graph
274
+ if (!ctx.browserOpened && (event.type === 'graph2d' || event.type === 'graph3d')) {
275
+ ctx.onBrowserOpened();
276
+ const viewUrl = viewerServer.getViewUrl(sessionId);
277
+ console.log(`\n View graphs at: \x1b[36m${viewUrl}\x1b[0m\n`);
278
+
279
+ import('child_process').then(({ exec }) => {
280
+ const cmd = process.platform === 'win32' ? 'start' :
281
+ process.platform === 'darwin' ? 'open' : 'xdg-open';
282
+ exec(`${cmd} ${viewUrl}`);
283
+ }).catch(() => {});
284
+ }
285
+ }
286
+ break;
287
+
288
+ case 'terminal:add':
289
+ if (viewerServer && sessionId) {
290
+ const session = viewerServer.getSession(sessionId);
291
+ if (session) {
292
+ session.addToTerminal(event.id, event.data);
293
+ session.broadcast({ type: 'terminal:add', id: event.id, data: event.data });
294
+ }
295
+ }
296
+ break;
297
+
298
+ case 'terminal:set':
299
+ if (viewerServer && sessionId) {
300
+ const session = viewerServer.getSession(sessionId);
301
+ if (session) {
302
+ session.setTerminal(event.id, event.data);
303
+ session.broadcast({ type: 'terminal:set', id: event.id, data: event.data });
304
+ }
305
+ }
306
+ break;
307
+
308
+ case 'terminal:clear':
309
+ if (viewerServer && sessionId) {
310
+ const session = viewerServer.getSession(sessionId);
311
+ if (session) {
312
+ session.clearTerminal(event.id);
313
+ session.broadcast({ type: 'terminal:clear', id: event.id });
314
+ }
315
+ }
316
+ break;
317
+
318
+ case 'error':
319
+ if (json) {
320
+ console.log(JSON.stringify({ type: 'error', message: event.message, location: event.location }));
321
+ } else if (!quiet) {
322
+ console.error(`Error: ${event.message}`);
323
+ if (event.location) {
324
+ console.error(` at ${event.location.file}:${event.location.line}:${event.location.column}`);
325
+ }
326
+ }
327
+ break;
328
+
329
+ case 'process:end':
330
+ if (verbose && !json) {
331
+ console.log(`\nCompleted in ${event.duration}ms`);
332
+ }
333
+ if (!event.success && !quiet) {
334
+ if (json) {
335
+ console.log(JSON.stringify({ type: 'failed', error: event.error }));
336
+ } else {
337
+ console.error(`\nExecution failed: ${event.error}`);
338
+ }
339
+ }
340
+ break;
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Start the daemon process
346
+ */
347
+ async function startDaemon(verbose) {
348
+ const { spawn } = await import('child_process');
349
+
350
+ // Find daemon path
351
+ const daemonPath = path.join(
352
+ path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')),
353
+ '..', '..', 'daemon', 'daemon.js'
354
+ );
355
+
356
+ if (!fs.existsSync(daemonPath)) {
357
+ if (verbose) {
358
+ console.log(`[Daemon not found at ${daemonPath}]`);
359
+ }
360
+ return false;
361
+ }
362
+
363
+ if (verbose) {
364
+ console.log(`[Starting daemon at ${daemonPath}]`);
365
+ }
366
+
367
+ const daemon = spawn('node', [daemonPath], {
368
+ detached: true,
369
+ stdio: 'ignore',
370
+ });
371
+
372
+ daemon.unref();
373
+ return true;
374
+ }
375
+
376
+ /**
377
+ * Direct execution (fallback when daemon unavailable)
378
+ */
379
+ async function runDirect(fullPath, source, options) {
380
+ const { json, headless, verbose, quiet, noServe } = options;
381
+ const scriptPath = path.basename(fullPath);
382
+
383
+ // ViewerServer for graphs
384
+ let viewerServer = null;
385
+ let sessionId = null;
386
+ let browserOpened = false;
387
+
388
+ const usesGraphs = source.includes('graph2d') || source.includes('graph3d');
389
+ if (usesGraphs && !headless && !json) {
390
+ viewerServer = new ViewerServer();
391
+ try {
392
+ await viewerServer.start();
393
+ sessionId = viewerServer.createSession();
394
+ if (verbose) {
395
+ console.log(`[Viewer server started on port ${viewerServer.port}]`);
396
+ }
397
+ } catch (err) {
398
+ console.error(`Failed to start viewer server: ${err.message}`);
399
+ }
400
+ }
401
+
402
+ // Create appropriate adapter
403
+ let adapter;
404
+ if (json) {
405
+ adapter = new JSONOutputAdapter();
406
+ } else {
407
+ adapter = new CLIOutputAdapter({
408
+ verbose,
409
+ quiet,
410
+ onGraphCreated: (id, type, config) => {
411
+ if (viewerServer && sessionId) {
412
+ const session = viewerServer.getSession(sessionId);
413
+ if (session) {
414
+ session.addTerminal(id, type, config);
415
+ session.broadcast({ type: 'terminal:create', id, terminalType: type, config });
416
+ }
417
+
418
+ if (!browserOpened) {
419
+ browserOpened = true;
420
+ const viewUrl = viewerServer.getViewUrl(sessionId);
421
+ console.log(`\n View graphs at: \x1b[36m${viewUrl}\x1b[0m\n`);
422
+
423
+ import('child_process').then(({ exec }) => {
424
+ const cmd = process.platform === 'win32' ? 'start' :
425
+ process.platform === 'darwin' ? 'open' : 'xdg-open';
426
+ exec(`${cmd} ${viewUrl}`);
427
+ }).catch(() => {});
428
+ }
429
+ }
430
+ },
431
+ });
432
+ }
433
+
434
+ // Wrap adapter to forward graph updates to viewer
435
+ const wrappedAdapter = {
436
+ print: (text) => adapter.print(text),
437
+ createTerminal: (id, type, config) => {
438
+ adapter.createTerminal(id, type, config);
439
+ },
440
+ addToTerminal: (id, data) => {
441
+ adapter.addToTerminal(id, data);
442
+ if (viewerServer && sessionId) {
443
+ const session = viewerServer.getSession(sessionId);
444
+ if (session) {
445
+ session.addToTerminal(id, data);
446
+ session.broadcast({ type: 'terminal:add', id, data });
447
+ }
448
+ }
449
+ },
450
+ setTerminal: (id, data) => {
451
+ adapter.setTerminal(id, data);
452
+ if (viewerServer && sessionId) {
453
+ const session = viewerServer.getSession(sessionId);
454
+ if (session) {
455
+ session.setTerminal(id, data);
456
+ session.broadcast({ type: 'terminal:set', id, data });
457
+ }
458
+ }
459
+ },
460
+ clearTerminal: (id) => {
461
+ adapter.clearTerminal(id);
462
+ if (viewerServer && sessionId) {
463
+ const session = viewerServer.getSession(sessionId);
464
+ if (session) {
465
+ session.clearTerminal(id);
466
+ session.broadcast({ type: 'terminal:clear', id });
467
+ }
468
+ }
469
+ },
470
+ error: (message, location) => adapter.error(message, location),
471
+ finish: (success, result) => adapter.finish(success, result),
472
+ progress: (info) => adapter.progress?.(info),
473
+ };
474
+
475
+ // Create engine
476
+ const engine = new StoneEngine({
477
+ outputAdapter: wrappedAdapter,
478
+ debug: verbose,
479
+ });
480
+
481
+ // Set up package context for stone_modules resolution
482
+ try {
483
+ const packageContext = await createNodePackageContext();
484
+ engine.setPackageContext(packageContext);
485
+ } catch (e) {
486
+ // Package context not available, continue without it
487
+ }
488
+
489
+ // Execute
490
+ try {
491
+ const startTime = Date.now();
492
+
493
+ const result = await engine.execute(source, {
494
+ filename: scriptPath,
495
+ currentPath: path.dirname(fullPath),
496
+ });
497
+
498
+ const duration = Date.now() - startTime;
499
+
500
+ if (result.success) {
501
+ if (verbose && !json) {
502
+ console.log(`\nCompleted in ${duration}ms`);
503
+ }
504
+
505
+ if (viewerServer && !noServe) {
506
+ console.log('Press Ctrl+C to exit...');
507
+ await new Promise(() => {});
508
+ }
509
+
510
+ process.exit(0);
511
+ } else {
512
+ if (!json && !quiet) {
513
+ console.error(`\nExecution failed: ${result.error}`);
514
+ }
515
+
516
+ if (viewerServer) {
517
+ await viewerServer.stop();
518
+ }
519
+
520
+ process.exit(1);
521
+ }
522
+ } catch (err) {
523
+ if (!json) {
524
+ console.error(`\nFatal error: ${err.message}`);
525
+ } else {
526
+ console.log(JSON.stringify({ type: 'fatal', error: err.message }));
527
+ }
528
+
529
+ if (viewerServer) {
530
+ await viewerServer.stop();
531
+ }
532
+
533
+ process.exit(1);
534
+ }
535
+ }
536
+
537
+ export default runCommand;