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,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileSystemAdapter - Base class for Stone file system access
|
|
3
|
+
*
|
|
4
|
+
* Provides an abstraction layer between the Stone execution engine and
|
|
5
|
+
* the file system. Allows different implementations for:
|
|
6
|
+
* - Node.js (CLI) - uses actual filesystem
|
|
7
|
+
* - Web app - uses in-memory file map or API
|
|
8
|
+
* - Testing - uses mock filesystem
|
|
9
|
+
*/
|
|
10
|
+
export class FileSystemAdapter {
|
|
11
|
+
/**
|
|
12
|
+
* Read file contents
|
|
13
|
+
* @param {string} path - File path
|
|
14
|
+
* @returns {Promise<string>} File contents
|
|
15
|
+
* @throws {Error} If file not found or read fails
|
|
16
|
+
*/
|
|
17
|
+
async readFile(path) {
|
|
18
|
+
throw new Error('FileSystemAdapter.readFile() not implemented');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Write file contents
|
|
23
|
+
* @param {string} path - File path
|
|
24
|
+
* @param {string} content - Content to write
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
* @throws {Error} If write fails
|
|
27
|
+
*/
|
|
28
|
+
async writeFile(path, content) {
|
|
29
|
+
throw new Error('FileSystemAdapter.writeFile() not implemented');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if file exists
|
|
34
|
+
* @param {string} path - File path
|
|
35
|
+
* @returns {Promise<boolean>}
|
|
36
|
+
*/
|
|
37
|
+
async exists(path) {
|
|
38
|
+
throw new Error('FileSystemAdapter.exists() not implemented');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* List files in directory
|
|
43
|
+
* @param {string} path - Directory path
|
|
44
|
+
* @returns {Promise<string[]>} List of file names
|
|
45
|
+
* @throws {Error} If directory not found or read fails
|
|
46
|
+
*/
|
|
47
|
+
async readDir(path) {
|
|
48
|
+
throw new Error('FileSystemAdapter.readDir() not implemented');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve path relative to base
|
|
53
|
+
* @param {string} base - Base path
|
|
54
|
+
* @param {string} relative - Relative path
|
|
55
|
+
* @returns {string} Resolved absolute path
|
|
56
|
+
*/
|
|
57
|
+
resolvePath(base, relative) {
|
|
58
|
+
throw new Error('FileSystemAdapter.resolvePath() not implemented');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get directory name from path
|
|
63
|
+
* @param {string} path - File path
|
|
64
|
+
* @returns {string} Directory path
|
|
65
|
+
*/
|
|
66
|
+
dirname(path) {
|
|
67
|
+
throw new Error('FileSystemAdapter.dirname() not implemented');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get base name from path
|
|
72
|
+
* @param {string} path - File path
|
|
73
|
+
* @returns {string} File name
|
|
74
|
+
*/
|
|
75
|
+
basename(path) {
|
|
76
|
+
throw new Error('FileSystemAdapter.basename() not implemented');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Join path segments
|
|
81
|
+
* @param {...string} segments - Path segments
|
|
82
|
+
* @returns {string} Joined path
|
|
83
|
+
*/
|
|
84
|
+
join(...segments) {
|
|
85
|
+
throw new Error('FileSystemAdapter.join() not implemented');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if path is absolute
|
|
90
|
+
* @param {string} path - Path to check
|
|
91
|
+
* @returns {boolean}
|
|
92
|
+
*/
|
|
93
|
+
isAbsolute(path) {
|
|
94
|
+
throw new Error('FileSystemAdapter.isAbsolute() not implemented');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* MemoryFileSystemAdapter - In-memory file system for testing and web
|
|
100
|
+
*
|
|
101
|
+
* Uses a Map to store file contents. Useful for:
|
|
102
|
+
* - Unit testing without touching real filesystem
|
|
103
|
+
* - Web app where files are loaded from API/memory
|
|
104
|
+
*/
|
|
105
|
+
export class MemoryFileSystemAdapter extends FileSystemAdapter {
|
|
106
|
+
constructor(files = {}) {
|
|
107
|
+
super();
|
|
108
|
+
// files is an object { path: content }
|
|
109
|
+
this.files = new Map(Object.entries(files));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async readFile(path) {
|
|
113
|
+
const normalizedPath = this._normalizePath(path);
|
|
114
|
+
if (!this.files.has(normalizedPath)) {
|
|
115
|
+
throw new Error(`File not found: ${path}`);
|
|
116
|
+
}
|
|
117
|
+
return this.files.get(normalizedPath);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async writeFile(path, content) {
|
|
121
|
+
const normalizedPath = this._normalizePath(path);
|
|
122
|
+
this.files.set(normalizedPath, content);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async exists(path) {
|
|
126
|
+
const normalizedPath = this._normalizePath(path);
|
|
127
|
+
return this.files.has(normalizedPath);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async readDir(path) {
|
|
131
|
+
const normalizedPath = this._normalizePath(path);
|
|
132
|
+
const prefix = normalizedPath.endsWith('/') ? normalizedPath : normalizedPath + '/';
|
|
133
|
+
|
|
134
|
+
const entries = new Set();
|
|
135
|
+
for (const filePath of this.files.keys()) {
|
|
136
|
+
if (filePath.startsWith(prefix)) {
|
|
137
|
+
const remaining = filePath.slice(prefix.length);
|
|
138
|
+
const firstPart = remaining.split('/')[0];
|
|
139
|
+
if (firstPart) {
|
|
140
|
+
entries.add(firstPart);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return Array.from(entries);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
resolvePath(base, relative) {
|
|
148
|
+
if (this.isAbsolute(relative)) {
|
|
149
|
+
return this._normalizePath(relative);
|
|
150
|
+
}
|
|
151
|
+
const baseParts = base.split('/').filter(Boolean);
|
|
152
|
+
const relativeParts = relative.split('/').filter(Boolean);
|
|
153
|
+
|
|
154
|
+
// Remove filename from base if it doesn't end with /
|
|
155
|
+
if (base && !base.endsWith('/')) {
|
|
156
|
+
baseParts.pop();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const part of relativeParts) {
|
|
160
|
+
if (part === '..') {
|
|
161
|
+
baseParts.pop();
|
|
162
|
+
} else if (part !== '.') {
|
|
163
|
+
baseParts.push(part);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return '/' + baseParts.join('/');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
dirname(path) {
|
|
171
|
+
const parts = path.split('/').filter(Boolean);
|
|
172
|
+
parts.pop();
|
|
173
|
+
return '/' + parts.join('/');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
basename(path) {
|
|
177
|
+
const parts = path.split('/').filter(Boolean);
|
|
178
|
+
return parts[parts.length - 1] || '';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
join(...segments) {
|
|
182
|
+
return this._normalizePath(segments.join('/'));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
isAbsolute(path) {
|
|
186
|
+
return path.startsWith('/');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Add a file to the in-memory filesystem
|
|
191
|
+
* @param {string} path - File path
|
|
192
|
+
* @param {string} content - File content
|
|
193
|
+
*/
|
|
194
|
+
addFile(path, content) {
|
|
195
|
+
this.files.set(this._normalizePath(path), content);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Remove a file from the in-memory filesystem
|
|
200
|
+
* @param {string} path - File path
|
|
201
|
+
*/
|
|
202
|
+
removeFile(path) {
|
|
203
|
+
this.files.delete(this._normalizePath(path));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Clear all files
|
|
208
|
+
*/
|
|
209
|
+
clear() {
|
|
210
|
+
this.files.clear();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
_normalizePath(path) {
|
|
214
|
+
// Normalize path: remove duplicate slashes, resolve . and ..
|
|
215
|
+
const parts = path.split('/').filter(Boolean);
|
|
216
|
+
const result = [];
|
|
217
|
+
|
|
218
|
+
for (const part of parts) {
|
|
219
|
+
if (part === '..') {
|
|
220
|
+
result.pop();
|
|
221
|
+
} else if (part !== '.') {
|
|
222
|
+
result.push(part);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return '/' + result.join('/');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export default FileSystemAdapter;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OutputAdapter - Base class for Stone execution output handling
|
|
3
|
+
*
|
|
4
|
+
* Provides an abstraction layer between the Stone execution engine and
|
|
5
|
+
* the output destination (CLI terminal, web app React state, JSON stream, etc.)
|
|
6
|
+
*
|
|
7
|
+
* All methods are no-ops by default - subclasses override what they need.
|
|
8
|
+
*/
|
|
9
|
+
export class OutputAdapter {
|
|
10
|
+
/**
|
|
11
|
+
* Called when print() builtin is invoked
|
|
12
|
+
* @param {string} text - Text to print
|
|
13
|
+
*/
|
|
14
|
+
print(text) {
|
|
15
|
+
// Default: no-op
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Called when a new terminal is created (graph2d, graph3d, console, etc.)
|
|
20
|
+
* @param {string} id - Unique terminal ID (e.g., 'graph2d_0')
|
|
21
|
+
* @param {string} type - Terminal type ('graph2d', 'graph3d', 'console')
|
|
22
|
+
* @param {Object} config - Terminal configuration (title, x_label, etc.)
|
|
23
|
+
*/
|
|
24
|
+
createTerminal(id, type, config) {
|
|
25
|
+
// Default: no-op
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Called when data is added to a terminal (e.g., graph.add(data))
|
|
30
|
+
* @param {string} id - Terminal ID
|
|
31
|
+
* @param {any} data - Data to add (format depends on terminal type)
|
|
32
|
+
*/
|
|
33
|
+
addToTerminal(id, data) {
|
|
34
|
+
// Default: no-op
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Called when terminal data is replaced (e.g., graph.set(data))
|
|
39
|
+
* @param {string} id - Terminal ID
|
|
40
|
+
* @param {any} data - New data (replaces existing)
|
|
41
|
+
*/
|
|
42
|
+
setTerminal(id, data) {
|
|
43
|
+
// Default: no-op
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Called when a terminal is cleared
|
|
48
|
+
* @param {string} id - Terminal ID
|
|
49
|
+
*/
|
|
50
|
+
clearTerminal(id) {
|
|
51
|
+
// Default: no-op
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Called when script execution finishes
|
|
56
|
+
* @param {boolean} success - Whether execution succeeded
|
|
57
|
+
* @param {any} result - Final result value (or error)
|
|
58
|
+
*/
|
|
59
|
+
finish(success, result) {
|
|
60
|
+
// Default: no-op
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Called when a type-checking or compilation error occurs
|
|
65
|
+
* @param {string} message - Error message
|
|
66
|
+
* @param {Object} location - Source location {line, column, file}
|
|
67
|
+
*/
|
|
68
|
+
error(message, location) {
|
|
69
|
+
// Default: no-op
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Called for progress updates during execution
|
|
74
|
+
* @param {Object} info - Progress information {phase, message, percent}
|
|
75
|
+
*/
|
|
76
|
+
progress(info) {
|
|
77
|
+
// Default: no-op
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* BufferedOutputAdapter - Collects output in memory for batch return
|
|
83
|
+
*
|
|
84
|
+
* Used for backward compatibility with existing StoneEngineService API
|
|
85
|
+
* that returns all output at once after execution.
|
|
86
|
+
*/
|
|
87
|
+
export class BufferedOutputAdapter extends OutputAdapter {
|
|
88
|
+
constructor() {
|
|
89
|
+
super();
|
|
90
|
+
this.output = [];
|
|
91
|
+
this.terminalsData = {};
|
|
92
|
+
this.errors = [];
|
|
93
|
+
this.result = null;
|
|
94
|
+
this.success = true;
|
|
95
|
+
this._debug = false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
print(text) {
|
|
99
|
+
if (this._debug) console.log('[BufferedAdapter] print:', text);
|
|
100
|
+
this.output.push(text);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
createTerminal(id, type, config) {
|
|
104
|
+
if (this._debug) console.log('[BufferedAdapter] createTerminal:', id, type);
|
|
105
|
+
this.terminalsData[id] = {
|
|
106
|
+
type,
|
|
107
|
+
config,
|
|
108
|
+
plots: type === 'graph2d' ? [] : undefined,
|
|
109
|
+
objects: type === 'graph3d' ? [] : undefined,
|
|
110
|
+
lines: type === 'console' ? [] : undefined,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
addToTerminal(id, data) {
|
|
115
|
+
if (this._debug) console.log('[BufferedAdapter] addToTerminal:', id, 'items:', Array.isArray(data) ? data.length : 1);
|
|
116
|
+
const terminal = this.terminalsData[id];
|
|
117
|
+
if (!terminal) {
|
|
118
|
+
if (this._debug) console.log('[BufferedAdapter] WARNING: terminal not found:', id);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const items = Array.isArray(data) ? data : [data];
|
|
123
|
+
|
|
124
|
+
switch (terminal.type) {
|
|
125
|
+
case 'graph2d':
|
|
126
|
+
terminal.plots.push(...items);
|
|
127
|
+
break;
|
|
128
|
+
case 'graph3d':
|
|
129
|
+
terminal.objects.push(...items);
|
|
130
|
+
if (this._debug) console.log('[BufferedAdapter] graph3d objects count:', terminal.objects.length);
|
|
131
|
+
break;
|
|
132
|
+
case 'console':
|
|
133
|
+
terminal.lines.push(...items);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setTerminal(id, data) {
|
|
139
|
+
const terminal = this.terminalsData[id];
|
|
140
|
+
if (!terminal) return;
|
|
141
|
+
|
|
142
|
+
const items = Array.isArray(data) ? data : [data];
|
|
143
|
+
|
|
144
|
+
switch (terminal.type) {
|
|
145
|
+
case 'graph2d':
|
|
146
|
+
terminal.plots = items;
|
|
147
|
+
break;
|
|
148
|
+
case 'graph3d':
|
|
149
|
+
terminal.objects = items;
|
|
150
|
+
break;
|
|
151
|
+
case 'console':
|
|
152
|
+
terminal.lines = items;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
clearTerminal(id) {
|
|
158
|
+
const terminal = this.terminalsData[id];
|
|
159
|
+
if (!terminal) return;
|
|
160
|
+
|
|
161
|
+
switch (terminal.type) {
|
|
162
|
+
case 'graph2d':
|
|
163
|
+
terminal.plots = [];
|
|
164
|
+
break;
|
|
165
|
+
case 'graph3d':
|
|
166
|
+
terminal.objects = [];
|
|
167
|
+
break;
|
|
168
|
+
case 'console':
|
|
169
|
+
terminal.lines = [];
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
finish(success, result) {
|
|
175
|
+
this.success = success;
|
|
176
|
+
this.result = result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
error(message, location) {
|
|
180
|
+
this.errors.push({ message, location });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get collected output for batch return
|
|
185
|
+
*/
|
|
186
|
+
getOutput() {
|
|
187
|
+
return {
|
|
188
|
+
output: this.output,
|
|
189
|
+
terminalsData: this.terminalsData,
|
|
190
|
+
errors: this.errors,
|
|
191
|
+
result: this.result,
|
|
192
|
+
success: this.success,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Reset adapter state for reuse
|
|
198
|
+
*/
|
|
199
|
+
reset() {
|
|
200
|
+
this.output = [];
|
|
201
|
+
this.terminalsData = {};
|
|
202
|
+
this.errors = [];
|
|
203
|
+
this.result = null;
|
|
204
|
+
this.success = true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export default OutputAdapter;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLIOutputAdapter - Human-readable terminal output with colors
|
|
3
|
+
*
|
|
4
|
+
* Formats Stone execution output for the command line:
|
|
5
|
+
* - Colorized print output
|
|
6
|
+
* - Terminal updates (for console terminals)
|
|
7
|
+
* - Error messages with source locations
|
|
8
|
+
* - Progress indicators
|
|
9
|
+
*
|
|
10
|
+
* Graph terminals (graph2d, graph3d) trigger browser viewing
|
|
11
|
+
* instead of printing to console.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { OutputAdapter } from '../adapters/OutputAdapter.js';
|
|
15
|
+
|
|
16
|
+
// ANSI color codes
|
|
17
|
+
const colors = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
bold: '\x1b[1m',
|
|
20
|
+
dim: '\x1b[2m',
|
|
21
|
+
|
|
22
|
+
// Text colors
|
|
23
|
+
black: '\x1b[30m',
|
|
24
|
+
red: '\x1b[31m',
|
|
25
|
+
green: '\x1b[32m',
|
|
26
|
+
yellow: '\x1b[33m',
|
|
27
|
+
blue: '\x1b[34m',
|
|
28
|
+
magenta: '\x1b[35m',
|
|
29
|
+
cyan: '\x1b[36m',
|
|
30
|
+
white: '\x1b[37m',
|
|
31
|
+
|
|
32
|
+
// Bright colors
|
|
33
|
+
brightRed: '\x1b[91m',
|
|
34
|
+
brightGreen: '\x1b[92m',
|
|
35
|
+
brightYellow: '\x1b[93m',
|
|
36
|
+
brightBlue: '\x1b[94m',
|
|
37
|
+
brightMagenta: '\x1b[95m',
|
|
38
|
+
brightCyan: '\x1b[96m',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const c = (color, text) => `${colors[color]}${text}${colors.reset}`;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* CLIOutputAdapter - Formats output for terminal display
|
|
45
|
+
*/
|
|
46
|
+
export class CLIOutputAdapter extends OutputAdapter {
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
super();
|
|
49
|
+
this.colorize = options.colorize !== false;
|
|
50
|
+
this.verbose = options.verbose || false;
|
|
51
|
+
this.quiet = options.quiet || false;
|
|
52
|
+
this.stream = options.stream || process.stdout;
|
|
53
|
+
this.errorStream = options.errorStream || process.stderr;
|
|
54
|
+
|
|
55
|
+
// Track terminals for graph viewing
|
|
56
|
+
this.terminals = {};
|
|
57
|
+
this.hasGraphTerminals = false;
|
|
58
|
+
|
|
59
|
+
// Callback for when graphs are created
|
|
60
|
+
this.onGraphCreated = options.onGraphCreated || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_write(text) {
|
|
64
|
+
if (!this.quiet) {
|
|
65
|
+
this.stream.write(text);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_writeError(text) {
|
|
70
|
+
this.errorStream.write(text);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_color(color, text) {
|
|
74
|
+
return this.colorize ? c(color, text) : text;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
print(text) {
|
|
78
|
+
this._write(text + '\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
createTerminal(id, type, config) {
|
|
82
|
+
this.terminals[id] = {
|
|
83
|
+
type,
|
|
84
|
+
config,
|
|
85
|
+
data: [],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (type === 'graph2d' || type === 'graph3d') {
|
|
89
|
+
this.hasGraphTerminals = true;
|
|
90
|
+
|
|
91
|
+
if (this.verbose) {
|
|
92
|
+
this._write(this._color('dim', `[Created ${type} terminal: ${config.title || id}]\n`));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Notify about graph creation
|
|
96
|
+
if (this.onGraphCreated) {
|
|
97
|
+
this.onGraphCreated(id, type, config);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
addToTerminal(id, data) {
|
|
103
|
+
const terminal = this.terminals[id];
|
|
104
|
+
if (!terminal) return;
|
|
105
|
+
|
|
106
|
+
const items = Array.isArray(data) ? data : [data];
|
|
107
|
+
terminal.data.push(...items);
|
|
108
|
+
|
|
109
|
+
// For console terminals, print lines
|
|
110
|
+
if (terminal.type === 'console') {
|
|
111
|
+
for (const item of items) {
|
|
112
|
+
this.print(String(item));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setTerminal(id, data) {
|
|
118
|
+
const terminal = this.terminals[id];
|
|
119
|
+
if (!terminal) return;
|
|
120
|
+
|
|
121
|
+
const items = Array.isArray(data) ? data : [data];
|
|
122
|
+
terminal.data = items;
|
|
123
|
+
|
|
124
|
+
// For console terminals, re-print all lines
|
|
125
|
+
// (This is mainly for interactive/REPL use)
|
|
126
|
+
if (terminal.type === 'console') {
|
|
127
|
+
for (const item of items) {
|
|
128
|
+
this.print(String(item));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
clearTerminal(id) {
|
|
134
|
+
const terminal = this.terminals[id];
|
|
135
|
+
if (terminal) {
|
|
136
|
+
terminal.data = [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
error(message, location) {
|
|
141
|
+
let output = this._color('brightRed', 'Error: ');
|
|
142
|
+
|
|
143
|
+
if (location) {
|
|
144
|
+
const loc = `${location.file || '<script>'}:${location.line}:${location.column}`;
|
|
145
|
+
output += this._color('cyan', loc) + ' ';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
output += message + '\n';
|
|
149
|
+
this._writeError(output);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
finish(success, result) {
|
|
153
|
+
if (this.verbose) {
|
|
154
|
+
if (success) {
|
|
155
|
+
this._write(this._color('green', '\n✓ Execution completed successfully\n'));
|
|
156
|
+
if (result !== null && result !== undefined) {
|
|
157
|
+
this._write(this._color('dim', `Result: ${JSON.stringify(result)}\n`));
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
this._write(this._color('red', '\n✗ Execution failed\n'));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
progress(info) {
|
|
166
|
+
if (this.verbose && !this.quiet) {
|
|
167
|
+
const { phase, message, percent } = info;
|
|
168
|
+
let output = this._color('dim', '[');
|
|
169
|
+
|
|
170
|
+
if (phase) output += phase;
|
|
171
|
+
if (message) output += ': ' + message;
|
|
172
|
+
if (percent !== undefined) output += ` (${percent}%)`;
|
|
173
|
+
|
|
174
|
+
output += this._color('dim', ']\n');
|
|
175
|
+
this._write(output);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get all terminal data (for graph viewing)
|
|
181
|
+
* @returns {Object} Terminal data by ID
|
|
182
|
+
*/
|
|
183
|
+
getTerminals() {
|
|
184
|
+
return this.terminals;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check if there are any graph terminals
|
|
189
|
+
* @returns {boolean}
|
|
190
|
+
*/
|
|
191
|
+
hasGraphs() {
|
|
192
|
+
return this.hasGraphTerminals;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export default CLIOutputAdapter;
|