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.
- package/README.md +52 -0
- package/StoneEngine.js +879 -0
- package/StoneEngineService.js +1727 -0
- package/adapters/FileSystemAdapter.js +230 -0
- package/adapters/OutputAdapter.js +208 -0
- package/adapters/index.js +6 -0
- package/cli/CLIOutputAdapter.js +196 -0
- package/cli/DaemonClient.js +349 -0
- package/cli/JSONOutputAdapter.js +135 -0
- package/cli/ReplSession.js +567 -0
- package/cli/ViewerServer.js +590 -0
- package/cli/commands/check.js +84 -0
- package/cli/commands/daemon.js +189 -0
- package/cli/commands/kill.js +66 -0
- package/cli/commands/package.js +713 -0
- package/cli/commands/ps.js +65 -0
- package/cli/commands/run.js +537 -0
- package/cli/entry.js +169 -0
- package/cli/index.js +14 -0
- package/cli/stonec.js +358 -0
- package/cli/test-compiler.js +181 -0
- package/cli/viewer/index.html +495 -0
- package/daemon/IPCServer.js +455 -0
- package/daemon/ProcessManager.js +327 -0
- package/daemon/ProcessRunner.js +307 -0
- package/daemon/daemon.js +398 -0
- package/daemon/index.js +16 -0
- package/frontend/analysis/index.js +5 -0
- package/frontend/analysis/livenessAnalyzer.js +568 -0
- package/frontend/analysis/treeShaker.js +265 -0
- package/frontend/index.js +20 -0
- package/frontend/parsing/astBuilder.js +2196 -0
- package/frontend/parsing/index.js +7 -0
- package/frontend/parsing/sonParser.js +592 -0
- package/frontend/parsing/stoneAstTypes.js +703 -0
- package/frontend/parsing/terminal-registry.js +435 -0
- package/frontend/parsing/tokenizer.js +692 -0
- package/frontend/type-checker/OverloadedFunctionType.js +43 -0
- package/frontend/type-checker/TypeEnvironment.js +165 -0
- package/frontend/type-checker/bidirectionalInference.js +149 -0
- package/frontend/type-checker/index.js +10 -0
- package/frontend/type-checker/moduleAnalysis.js +248 -0
- package/frontend/type-checker/operatorMappings.js +35 -0
- package/frontend/type-checker/overloadResolution.js +605 -0
- package/frontend/type-checker/typeChecker.js +452 -0
- package/frontend/type-checker/typeCompatibility.js +389 -0
- package/frontend/type-checker/visitors/controlFlow.js +483 -0
- package/frontend/type-checker/visitors/functions.js +604 -0
- package/frontend/type-checker/visitors/index.js +38 -0
- package/frontend/type-checker/visitors/literals.js +341 -0
- package/frontend/type-checker/visitors/modules.js +159 -0
- package/frontend/type-checker/visitors/operators.js +109 -0
- package/frontend/type-checker/visitors/statements.js +768 -0
- package/frontend/types/index.js +5 -0
- package/frontend/types/operatorMap.js +134 -0
- package/frontend/types/types.js +2046 -0
- package/frontend/utils/errorCollector.js +244 -0
- package/frontend/utils/index.js +5 -0
- package/frontend/utils/moduleResolver.js +479 -0
- package/package.json +50 -0
- package/packages/browserCache.js +359 -0
- package/packages/fetcher.js +236 -0
- package/packages/index.js +130 -0
- package/packages/lockfile.js +271 -0
- package/packages/manifest.js +291 -0
- package/packages/packageResolver.js +356 -0
- package/packages/resolver.js +310 -0
- 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;
|