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,455 @@
1
+ /**
2
+ * IPCServer - Cross-platform IPC server for Stone Daemon
3
+ *
4
+ * Provides inter-process communication between:
5
+ * - CLI clients running `stone run script.stn`
6
+ * - Riva desktop app
7
+ * - Other Stone tools
8
+ *
9
+ * Uses:
10
+ * - Unix domain sockets on Linux/macOS
11
+ * - Named pipes on Windows
12
+ *
13
+ * Protocol: JSON-RPC style messages over newline-delimited JSON
14
+ */
15
+
16
+ import net from 'net';
17
+ import fs from 'fs';
18
+ import path from 'path';
19
+ import os from 'os';
20
+ import { EventEmitter } from 'events';
21
+
22
+ // Socket/pipe path based on platform
23
+ const getSocketPath = () => {
24
+ if (process.platform === 'win32') {
25
+ return '\\\\.\\pipe\\stone-daemon';
26
+ }
27
+ // Unix socket in user's runtime directory or tmp
28
+ const runtimeDir = process.env.XDG_RUNTIME_DIR || os.tmpdir();
29
+ return path.join(runtimeDir, `stone-daemon-${process.getuid ? process.getuid() : 'user'}.sock`);
30
+ };
31
+
32
+ /**
33
+ * Client connection wrapper
34
+ */
35
+ class IPCClient extends EventEmitter {
36
+ constructor(socket, clientId) {
37
+ super();
38
+ this.socket = socket;
39
+ this.clientId = clientId;
40
+ this.buffer = '';
41
+ this.ownedProcesses = new Set(); // Process IDs owned by this client
42
+ this.subscriptions = new Set(); // Process IDs this client is subscribed to (may not own)
43
+
44
+ this.socket.setEncoding('utf8');
45
+
46
+ this.socket.on('data', (data) => {
47
+ this.buffer += data;
48
+ this._processBuffer();
49
+ });
50
+
51
+ this.socket.on('close', () => {
52
+ this.emit('close');
53
+ });
54
+
55
+ this.socket.on('error', (err) => {
56
+ this.emit('error', err);
57
+ });
58
+ }
59
+
60
+ _processBuffer() {
61
+ const lines = this.buffer.split('\n');
62
+ this.buffer = lines.pop() || ''; // Keep incomplete line in buffer
63
+
64
+ for (const line of lines) {
65
+ if (!line.trim()) continue;
66
+ try {
67
+ const message = JSON.parse(line);
68
+ this.emit('message', message);
69
+ } catch (err) {
70
+ this.emit('error', new Error(`Invalid JSON: ${line}`));
71
+ }
72
+ }
73
+ }
74
+
75
+ send(message) {
76
+ if (this.socket.writable) {
77
+ this.socket.write(JSON.stringify(message) + '\n');
78
+ }
79
+ }
80
+
81
+ close() {
82
+ this.socket.end();
83
+ }
84
+
85
+ addOwnedProcess(processId) {
86
+ this.ownedProcesses.add(processId);
87
+ }
88
+
89
+ removeOwnedProcess(processId) {
90
+ this.ownedProcesses.delete(processId);
91
+ }
92
+
93
+ addSubscription(processId) {
94
+ this.subscriptions.add(processId);
95
+ }
96
+
97
+ removeSubscription(processId) {
98
+ this.subscriptions.delete(processId);
99
+ }
100
+
101
+ isSubscribedTo(processId) {
102
+ return this.subscriptions.has(processId) || this.ownedProcesses.has(processId);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * IPC Server
108
+ */
109
+ export class IPCServer extends EventEmitter {
110
+ constructor(options = {}) {
111
+ super();
112
+ this.socketPath = options.socketPath || getSocketPath();
113
+ this.server = null;
114
+ this.clients = new Map(); // clientId -> IPCClient
115
+ this.clientCounter = 0;
116
+ this.messageHandlers = new Map(); // command -> handler function
117
+ }
118
+
119
+ /**
120
+ * Register a message handler
121
+ * @param {string} command - Command name (e.g., 'run', 'kill', 'list')
122
+ * @param {Function} handler - Handler function (client, message) => response
123
+ */
124
+ onMessage(command, handler) {
125
+ this.messageHandlers.set(command, handler);
126
+ }
127
+
128
+ /**
129
+ * Start the IPC server
130
+ * @returns {Promise<void>}
131
+ */
132
+ async start() {
133
+ // Clean up stale socket file on Unix
134
+ if (process.platform !== 'win32') {
135
+ try {
136
+ await fs.promises.unlink(this.socketPath);
137
+ } catch (err) {
138
+ if (err.code !== 'ENOENT') {
139
+ // Check if another daemon is running
140
+ try {
141
+ const testSocket = net.connect(this.socketPath);
142
+ await new Promise((resolve, reject) => {
143
+ testSocket.on('connect', () => {
144
+ testSocket.end();
145
+ reject(new Error('Another Stone daemon is already running'));
146
+ });
147
+ testSocket.on('error', () => {
148
+ resolve(); // Socket exists but no one is listening
149
+ });
150
+ });
151
+ } catch (e) {
152
+ if (e.message.includes('Another Stone daemon')) {
153
+ throw e;
154
+ }
155
+ }
156
+ // Try to remove stale socket
157
+ await fs.promises.unlink(this.socketPath);
158
+ }
159
+ }
160
+ }
161
+
162
+ return new Promise((resolve, reject) => {
163
+ this.server = net.createServer((socket) => {
164
+ this._handleConnection(socket);
165
+ });
166
+
167
+ this.server.on('error', (err) => {
168
+ if (err.code === 'EADDRINUSE') {
169
+ reject(new Error('Another Stone daemon is already running'));
170
+ } else {
171
+ reject(err);
172
+ }
173
+ });
174
+
175
+ this.server.listen(this.socketPath, () => {
176
+ // Set socket permissions on Unix
177
+ if (process.platform !== 'win32') {
178
+ fs.chmod(this.socketPath, 0o600, () => {});
179
+ }
180
+ this.emit('listening', this.socketPath);
181
+ resolve();
182
+ });
183
+ });
184
+ }
185
+
186
+ /**
187
+ * Stop the IPC server
188
+ */
189
+ async stop() {
190
+ // Close all client connections
191
+ for (const client of this.clients.values()) {
192
+ client.close();
193
+ }
194
+ this.clients.clear();
195
+
196
+ // Close server
197
+ if (this.server) {
198
+ return new Promise((resolve) => {
199
+ this.server.close(() => {
200
+ // Clean up socket file on Unix
201
+ if (process.platform !== 'win32') {
202
+ fs.unlink(this.socketPath, () => {});
203
+ }
204
+ resolve();
205
+ });
206
+ });
207
+ }
208
+ }
209
+
210
+ _handleConnection(socket) {
211
+ const clientId = `client_${++this.clientCounter}`;
212
+ const client = new IPCClient(socket, clientId);
213
+ this.clients.set(clientId, client);
214
+
215
+ this.emit('connection', client);
216
+
217
+ client.on('message', async (message) => {
218
+ await this._handleMessage(client, message);
219
+ });
220
+
221
+ client.on('close', () => {
222
+ this.emit('disconnect', client);
223
+ this.clients.delete(clientId);
224
+ });
225
+
226
+ client.on('error', (err) => {
227
+ this.emit('clientError', client, err);
228
+ });
229
+ }
230
+
231
+ async _handleMessage(client, message) {
232
+ const { cmd, id } = message;
233
+
234
+ if (!cmd) {
235
+ client.send({ id, error: 'Missing command' });
236
+ return;
237
+ }
238
+
239
+ const handler = this.messageHandlers.get(cmd);
240
+ if (!handler) {
241
+ client.send({ id, error: `Unknown command: ${cmd}` });
242
+ return;
243
+ }
244
+
245
+ try {
246
+ const response = await handler(client, message);
247
+ client.send({ id, ...response });
248
+ } catch (err) {
249
+ client.send({ id, error: err.message });
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Broadcast a message to all connected clients
255
+ * @param {Object} message - Message to broadcast
256
+ */
257
+ broadcast(message) {
258
+ for (const client of this.clients.values()) {
259
+ client.send(message);
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Send a message to a specific client
265
+ * @param {string} clientId - Client ID
266
+ * @param {Object} message - Message to send
267
+ */
268
+ sendTo(clientId, message) {
269
+ const client = this.clients.get(clientId);
270
+ if (client) {
271
+ client.send(message);
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Send a message to all clients subscribed to a process
277
+ * @param {string} processId - Process ID
278
+ * @param {Object} message - Message to send
279
+ */
280
+ sendToSubscribers(processId, message) {
281
+ for (const client of this.clients.values()) {
282
+ if (client.isSubscribedTo(processId)) {
283
+ client.send(message);
284
+ }
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Get connected client count
290
+ * @returns {number}
291
+ */
292
+ getClientCount() {
293
+ return this.clients.size;
294
+ }
295
+
296
+ /**
297
+ * Get the socket path
298
+ * @returns {string}
299
+ */
300
+ getSocketPath() {
301
+ return this.socketPath;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * IPC Client for connecting to the daemon
307
+ */
308
+ export class IPCClientConnection extends EventEmitter {
309
+ constructor(options = {}) {
310
+ super();
311
+ this.socketPath = options.socketPath || getSocketPath();
312
+ this.socket = null;
313
+ this.buffer = '';
314
+ this.pendingRequests = new Map(); // id -> { resolve, reject, timeout }
315
+ this.requestCounter = 0;
316
+ this.connected = false;
317
+ }
318
+
319
+ /**
320
+ * Connect to the daemon
321
+ * @returns {Promise<void>}
322
+ */
323
+ async connect() {
324
+ return new Promise((resolve, reject) => {
325
+ this.socket = net.connect(this.socketPath);
326
+
327
+ this.socket.setEncoding('utf8');
328
+
329
+ this.socket.on('connect', () => {
330
+ this.connected = true;
331
+ this.emit('connect');
332
+ resolve();
333
+ });
334
+
335
+ this.socket.on('data', (data) => {
336
+ this.buffer += data;
337
+ this._processBuffer();
338
+ });
339
+
340
+ this.socket.on('close', () => {
341
+ this.connected = false;
342
+ this.emit('close');
343
+ // Reject any pending requests
344
+ for (const [id, request] of this.pendingRequests.entries()) {
345
+ clearTimeout(request.timeout);
346
+ request.reject(new Error('Connection closed'));
347
+ this.pendingRequests.delete(id);
348
+ }
349
+ });
350
+
351
+ this.socket.on('error', (err) => {
352
+ if (!this.connected) {
353
+ reject(err);
354
+ } else {
355
+ this.emit('error', err);
356
+ }
357
+ });
358
+ });
359
+ }
360
+
361
+ _processBuffer() {
362
+ const lines = this.buffer.split('\n');
363
+ this.buffer = lines.pop() || '';
364
+
365
+ for (const line of lines) {
366
+ if (!line.trim()) continue;
367
+ try {
368
+ const message = JSON.parse(line);
369
+ this._handleMessage(message);
370
+ } catch (err) {
371
+ this.emit('error', new Error(`Invalid JSON from server: ${line}`));
372
+ }
373
+ }
374
+ }
375
+
376
+ _handleMessage(message) {
377
+ const { id } = message;
378
+
379
+ // Check if this is a response to a pending request
380
+ if (id !== undefined && this.pendingRequests.has(id)) {
381
+ const request = this.pendingRequests.get(id);
382
+ clearTimeout(request.timeout);
383
+ this.pendingRequests.delete(id);
384
+
385
+ if (message.error) {
386
+ request.reject(new Error(message.error));
387
+ } else {
388
+ request.resolve(message);
389
+ }
390
+ return;
391
+ }
392
+
393
+ // Otherwise, it's an event from the server
394
+ if (message.event) {
395
+ this.emit('event', message);
396
+ this.emit(message.event, message);
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Send a request and wait for response
402
+ * @param {string} cmd - Command name
403
+ * @param {Object} params - Command parameters
404
+ * @param {number} timeout - Timeout in ms (default 30000)
405
+ * @returns {Promise<Object>} Response
406
+ */
407
+ async request(cmd, params = {}, timeout = 30000) {
408
+ if (!this.connected) {
409
+ throw new Error('Not connected to daemon');
410
+ }
411
+
412
+ const id = ++this.requestCounter;
413
+ const message = { id, cmd, ...params };
414
+
415
+ return new Promise((resolve, reject) => {
416
+ const timeoutId = setTimeout(() => {
417
+ this.pendingRequests.delete(id);
418
+ reject(new Error(`Request timeout: ${cmd}`));
419
+ }, timeout);
420
+
421
+ this.pendingRequests.set(id, { resolve, reject, timeout: timeoutId });
422
+ this.socket.write(JSON.stringify(message) + '\n');
423
+ });
424
+ }
425
+
426
+ /**
427
+ * Send a message without waiting for response
428
+ * @param {Object} message - Message to send
429
+ */
430
+ send(message) {
431
+ if (this.connected) {
432
+ this.socket.write(JSON.stringify(message) + '\n');
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Disconnect from the daemon
438
+ */
439
+ disconnect() {
440
+ if (this.socket) {
441
+ this.socket.end();
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Check if connected
447
+ * @returns {boolean}
448
+ */
449
+ isConnected() {
450
+ return this.connected;
451
+ }
452
+ }
453
+
454
+ export { getSocketPath };
455
+ export default IPCServer;