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
package/daemon/daemon.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stone Daemon - Central process manager for Stone scripts
|
|
3
|
+
*
|
|
4
|
+
* The daemon provides:
|
|
5
|
+
* - Process management (start, kill, list scripts)
|
|
6
|
+
* - Streaming output to connected clients
|
|
7
|
+
* - Automatic cleanup when clients disconnect
|
|
8
|
+
* - Auto-shutdown when idle (no clients, no running processes)
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node daemon.js [--foreground] [--no-auto-shutdown]
|
|
12
|
+
*
|
|
13
|
+
* CLI clients connect via IPC to run scripts:
|
|
14
|
+
* stone run script.stn -> connects to daemon, streams output
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { IPCServer } from './IPCServer.js';
|
|
18
|
+
import { ProcessManager } from './ProcessManager.js';
|
|
19
|
+
import { ProcessRunner, runProcess } from './ProcessRunner.js';
|
|
20
|
+
|
|
21
|
+
const DEFAULT_IDLE_TIMEOUT = 60000; // 1 minute with no activity = shutdown
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* StoneDaemon - Main daemon class
|
|
25
|
+
*/
|
|
26
|
+
export class StoneDaemon {
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.ipc = new IPCServer(options);
|
|
29
|
+
this.processManager = new ProcessManager();
|
|
30
|
+
|
|
31
|
+
this.autoShutdown = options.autoShutdown !== false;
|
|
32
|
+
this.idleTimeout = options.idleTimeout || DEFAULT_IDLE_TIMEOUT;
|
|
33
|
+
this._shutdownTimer = null;
|
|
34
|
+
this._isShuttingDown = false;
|
|
35
|
+
|
|
36
|
+
this._setupMessageHandlers();
|
|
37
|
+
this._setupEventForwarding();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Start the daemon
|
|
42
|
+
*/
|
|
43
|
+
async start() {
|
|
44
|
+
console.log('Starting Stone daemon...');
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await this.ipc.start();
|
|
48
|
+
console.log(`Stone daemon listening on: ${this.ipc.getSocketPath()}`);
|
|
49
|
+
|
|
50
|
+
// Start idle timer
|
|
51
|
+
this._resetIdleTimer();
|
|
52
|
+
|
|
53
|
+
// Handle process signals
|
|
54
|
+
process.on('SIGINT', () => this.shutdown());
|
|
55
|
+
process.on('SIGTERM', () => this.shutdown());
|
|
56
|
+
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('Failed to start daemon:', err.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Shutdown the daemon
|
|
65
|
+
*/
|
|
66
|
+
async shutdown() {
|
|
67
|
+
if (this._isShuttingDown) return;
|
|
68
|
+
this._isShuttingDown = true;
|
|
69
|
+
|
|
70
|
+
console.log('Shutting down Stone daemon...');
|
|
71
|
+
|
|
72
|
+
// Clear idle timer
|
|
73
|
+
if (this._shutdownTimer) {
|
|
74
|
+
clearTimeout(this._shutdownTimer);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Kill all running processes
|
|
78
|
+
const stats = this.processManager.getProcessStats();
|
|
79
|
+
if (stats.running > 0) {
|
|
80
|
+
console.log(`Killing ${stats.running} running processes...`);
|
|
81
|
+
for (const proc of this.processManager.listProcesses({ status: 'running' })) {
|
|
82
|
+
this.processManager.killProcess(proc.id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Stop IPC server
|
|
87
|
+
await this.ipc.stop();
|
|
88
|
+
|
|
89
|
+
console.log('Stone daemon stopped.');
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_setupMessageHandlers() {
|
|
94
|
+
// Run a script
|
|
95
|
+
this.ipc.onMessage('run', async (client, message) => {
|
|
96
|
+
return this._handleRun(client, message);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Kill a process
|
|
100
|
+
this.ipc.onMessage('kill', async (client, message) => {
|
|
101
|
+
return this._handleKill(client, message);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// List processes
|
|
105
|
+
this.ipc.onMessage('list', async (client, message) => {
|
|
106
|
+
return this._handleList(client, message);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Get process info
|
|
110
|
+
this.ipc.onMessage('info', async (client, message) => {
|
|
111
|
+
return this._handleInfo(client, message);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Subscribe to process events (for processes not owned by this client)
|
|
115
|
+
this.ipc.onMessage('subscribe', async (client, message) => {
|
|
116
|
+
return this._handleSubscribe(client, message);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Ping (keepalive)
|
|
120
|
+
this.ipc.onMessage('ping', async () => {
|
|
121
|
+
return { pong: true, time: Date.now() };
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Get daemon status
|
|
125
|
+
this.ipc.onMessage('status', async () => {
|
|
126
|
+
return {
|
|
127
|
+
status: 'running',
|
|
128
|
+
clients: this.ipc.getClientCount(),
|
|
129
|
+
processes: this.processManager.getProcessStats(),
|
|
130
|
+
socketPath: this.ipc.getSocketPath(),
|
|
131
|
+
uptime: process.uptime(),
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Shutdown daemon
|
|
136
|
+
this.ipc.onMessage('shutdown', async (client, message) => {
|
|
137
|
+
// Only allow shutdown if no other clients are connected
|
|
138
|
+
// or if force flag is set
|
|
139
|
+
if (this.ipc.getClientCount() > 1 && !message.force) {
|
|
140
|
+
return { error: 'Other clients are connected. Use --force to override.' };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Schedule shutdown (don't await - respond first)
|
|
144
|
+
setImmediate(() => this.shutdown());
|
|
145
|
+
return { shutting_down: true };
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_setupEventForwarding() {
|
|
150
|
+
// Client connected
|
|
151
|
+
this.ipc.on('connection', (client) => {
|
|
152
|
+
console.log(`Client connected: ${client.clientId}`);
|
|
153
|
+
this._resetIdleTimer();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Client disconnected
|
|
157
|
+
this.ipc.on('disconnect', (client) => {
|
|
158
|
+
console.log(`Client disconnected: ${client.clientId}`);
|
|
159
|
+
|
|
160
|
+
// Kill processes owned by this client
|
|
161
|
+
const killed = this.processManager.killProcessesByOwner(client.clientId);
|
|
162
|
+
if (killed.length > 0) {
|
|
163
|
+
console.log(`Killed ${killed.length} processes owned by ${client.clientId}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check for auto-shutdown
|
|
167
|
+
this._checkIdleShutdown();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Process events - broadcast process:created to ALL clients so they can subscribe
|
|
171
|
+
this.processManager.on('process:created', (proc) => {
|
|
172
|
+
// Broadcast to ALL clients (not just subscribers) so desktop can auto-subscribe
|
|
173
|
+
this.ipc.broadcast({
|
|
174
|
+
event: 'process:created',
|
|
175
|
+
processId: proc.id,
|
|
176
|
+
process: proc.toJSON(),
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
this.processManager.on('process:started', (proc) => {
|
|
181
|
+
this.ipc.sendToSubscribers(proc.id, {
|
|
182
|
+
event: 'process:started',
|
|
183
|
+
processId: proc.id,
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
this.processManager.on('process:completed', (proc) => {
|
|
188
|
+
this.ipc.sendToSubscribers(proc.id, {
|
|
189
|
+
event: 'process:completed',
|
|
190
|
+
processId: proc.id,
|
|
191
|
+
result: proc.result,
|
|
192
|
+
duration: proc.getDuration(),
|
|
193
|
+
});
|
|
194
|
+
this._resetIdleTimer();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
this.processManager.on('process:failed', (proc) => {
|
|
198
|
+
this.ipc.sendToSubscribers(proc.id, {
|
|
199
|
+
event: 'process:failed',
|
|
200
|
+
processId: proc.id,
|
|
201
|
+
error: proc.error,
|
|
202
|
+
});
|
|
203
|
+
this._resetIdleTimer();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
this.processManager.on('process:killed', (proc) => {
|
|
207
|
+
this.ipc.sendToSubscribers(proc.id, {
|
|
208
|
+
event: 'process:killed',
|
|
209
|
+
processId: proc.id,
|
|
210
|
+
});
|
|
211
|
+
this._resetIdleTimer();
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async _handleRun(client, message) {
|
|
216
|
+
const { script, source, cwd, variables } = message;
|
|
217
|
+
|
|
218
|
+
if (!script && !source) {
|
|
219
|
+
throw new Error('Either script path or source is required');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Create process entry
|
|
223
|
+
const proc = this.processManager.createProcess(
|
|
224
|
+
script || '<inline>',
|
|
225
|
+
client.clientId,
|
|
226
|
+
{ cwd, variables }
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Mark as running
|
|
230
|
+
this.processManager.startProcess(proc.id);
|
|
231
|
+
client.addOwnedProcess(proc.id);
|
|
232
|
+
|
|
233
|
+
// Create runner and set up event forwarding to subscribers
|
|
234
|
+
const runner = new ProcessRunner(proc, {
|
|
235
|
+
scriptPath: script,
|
|
236
|
+
source: source,
|
|
237
|
+
cwd: cwd || process.cwd(),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Forward runner events to all subscribed clients (owner + subscribers)
|
|
241
|
+
runner.on('event', (event) => {
|
|
242
|
+
this.ipc.sendToSubscribers(proc.id, event);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Link runner to process for kill support
|
|
246
|
+
proc.runner = runner;
|
|
247
|
+
|
|
248
|
+
// Run async
|
|
249
|
+
runner.run().then((result) => {
|
|
250
|
+
if (result.success) {
|
|
251
|
+
this.processManager.completeProcess(proc.id, result.result, result.terminalsData);
|
|
252
|
+
} else {
|
|
253
|
+
this.processManager.failProcess(proc.id, result.error);
|
|
254
|
+
}
|
|
255
|
+
}).catch((err) => {
|
|
256
|
+
this.processManager.failProcess(proc.id, err.message);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Return immediately with process info
|
|
260
|
+
return { processId: proc.id, status: 'started' };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async _handleKill(client, message) {
|
|
264
|
+
const { processId } = message;
|
|
265
|
+
|
|
266
|
+
if (!processId) {
|
|
267
|
+
throw new Error('Process ID is required');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const success = this.processManager.killProcess(processId, client.clientId);
|
|
271
|
+
|
|
272
|
+
if (success) {
|
|
273
|
+
client.removeOwnedProcess(processId);
|
|
274
|
+
return { killed: true, processId };
|
|
275
|
+
} else {
|
|
276
|
+
return { killed: false, processId, reason: 'Process not running or not found' };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async _handleList(client, message) {
|
|
281
|
+
const { status, mine } = message;
|
|
282
|
+
|
|
283
|
+
const filter = {};
|
|
284
|
+
if (status) filter.status = status;
|
|
285
|
+
if (mine) filter.owner = client.clientId;
|
|
286
|
+
|
|
287
|
+
const processes = this.processManager.listProcesses(filter);
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
processes: processes.map(p => p.toJSON()),
|
|
291
|
+
count: processes.length,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async _handleInfo(client, message) {
|
|
296
|
+
const { processId } = message;
|
|
297
|
+
|
|
298
|
+
if (!processId) {
|
|
299
|
+
throw new Error('Process ID is required');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const proc = this.processManager.getProcess(processId);
|
|
303
|
+
if (!proc) {
|
|
304
|
+
return { found: false, processId };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return { found: true, process: proc.toJSON() };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async _handleSubscribe(client, message) {
|
|
311
|
+
const { processId } = message;
|
|
312
|
+
|
|
313
|
+
if (!processId) {
|
|
314
|
+
throw new Error('Process ID is required');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const proc = this.processManager.getProcess(processId);
|
|
318
|
+
if (!proc) {
|
|
319
|
+
return { subscribed: false, reason: 'Process not found' };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Add subscription for this client to receive events for this process
|
|
323
|
+
client.addSubscription(processId);
|
|
324
|
+
|
|
325
|
+
// If process is already complete, send current state including terminalsData
|
|
326
|
+
if (proc.status === 'completed' || proc.status === 'failed' || proc.status === 'killed') {
|
|
327
|
+
return {
|
|
328
|
+
subscribed: true,
|
|
329
|
+
process: proc.toJSON(),
|
|
330
|
+
terminalsData: proc.terminalsData,
|
|
331
|
+
status: proc.status,
|
|
332
|
+
result: proc.result,
|
|
333
|
+
error: proc.error,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Process is still running, client will receive future events
|
|
338
|
+
return {
|
|
339
|
+
subscribed: true,
|
|
340
|
+
process: proc.toJSON(),
|
|
341
|
+
status: proc.status,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
_resetIdleTimer() {
|
|
346
|
+
if (!this.autoShutdown) return;
|
|
347
|
+
|
|
348
|
+
if (this._shutdownTimer) {
|
|
349
|
+
clearTimeout(this._shutdownTimer);
|
|
350
|
+
this._shutdownTimer = null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
_checkIdleShutdown() {
|
|
355
|
+
if (!this.autoShutdown) return;
|
|
356
|
+
|
|
357
|
+
// Don't shutdown if there are connected clients
|
|
358
|
+
if (this.ipc.getClientCount() > 0) return;
|
|
359
|
+
|
|
360
|
+
// Don't shutdown if there are running processes
|
|
361
|
+
if (this.processManager.hasRunningProcesses()) return;
|
|
362
|
+
|
|
363
|
+
// Schedule shutdown
|
|
364
|
+
console.log(`No clients or running processes. Shutting down in ${this.idleTimeout / 1000}s...`);
|
|
365
|
+
this._shutdownTimer = setTimeout(() => {
|
|
366
|
+
// Double-check before shutdown
|
|
367
|
+
if (this.ipc.getClientCount() === 0 && !this.processManager.hasRunningProcesses()) {
|
|
368
|
+
console.log('Idle timeout reached. Shutting down.');
|
|
369
|
+
this.shutdown();
|
|
370
|
+
}
|
|
371
|
+
}, this.idleTimeout);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// CLI entry point
|
|
376
|
+
async function main() {
|
|
377
|
+
const args = process.argv.slice(2);
|
|
378
|
+
|
|
379
|
+
const options = {
|
|
380
|
+
autoShutdown: !args.includes('--no-auto-shutdown'),
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const daemon = new StoneDaemon(options);
|
|
384
|
+
await daemon.start();
|
|
385
|
+
|
|
386
|
+
// Keep process running
|
|
387
|
+
console.log('Stone daemon is running. Press Ctrl+C to stop.');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Run if executed directly
|
|
391
|
+
if (process.argv[1] && process.argv[1].includes('daemon.js')) {
|
|
392
|
+
main().catch((err) => {
|
|
393
|
+
console.error('Fatal error:', err);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export default StoneDaemon;
|
package/daemon/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stone Daemon - Process management for Stone scripts
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* - StoneDaemon - Main daemon class
|
|
6
|
+
* - IPCServer - IPC server for client connections
|
|
7
|
+
* - IPCClientConnection - Client for connecting to daemon
|
|
8
|
+
* - ProcessManager - Process table and lifecycle
|
|
9
|
+
* - ProcessRunner - Script execution with streaming
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export { StoneDaemon } from './daemon.js';
|
|
13
|
+
export { default as StoneDaemon_default } from './daemon.js';
|
|
14
|
+
export { IPCServer, IPCClientConnection, getSocketPath } from './IPCServer.js';
|
|
15
|
+
export { ProcessManager, StoneProcess } from './ProcessManager.js';
|
|
16
|
+
export { ProcessRunner, runProcess, StreamingOutputAdapter } from './ProcessRunner.js';
|