novac 2.0.1 → 2.2.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/LICENSE +1 -1
- package/README.md +1574 -597
- package/bin/novac +468 -171
- package/bin/nvc +522 -0
- package/bin/nvml +78 -17
- package/demo.nv +0 -0
- package/demo_builtins.nv +0 -0
- package/demo_http.nv +0 -0
- package/examples/bf.nv +69 -0
- package/examples/math.nv +21 -0
- package/kits/birdAPI/kitdef.js +954 -0
- package/kits/kitRNG/kitdef.js +740 -0
- package/kits/kitSSH/kitdef.js +1272 -0
- package/kits/kitadb/kitdef.js +606 -0
- package/kits/kitai/kitdef.js +2185 -0
- package/kits/kitansi/kitdef.js +1402 -0
- package/kits/kitcanvas/kitdef.js +914 -0
- package/kits/kitclippy/kitdef.js +925 -0
- package/kits/kitformat/kitdef.js +1485 -0
- package/kits/kitgps/kitdef.js +1862 -0
- package/kits/kitlibproc/kitdef.js +3 -2
- package/kits/kitmatrix/ex.js +19 -0
- package/kits/kitmatrix/kitdef.js +960 -0
- package/kits/kitmorse/kitdef.js +229 -0
- package/kits/kitmpatch/kitdef.js +906 -0
- package/kits/kitnet/kitdef.js +1401 -0
- package/kits/kitnovacweb/README.md +1416 -143
- package/kits/kitnovacweb/kitdef.js +92 -2
- package/kits/kitnovacweb/nvml/executor.js +578 -176
- package/kits/kitnovacweb/nvml/index.js +2 -2
- package/kits/kitnovacweb/nvml/lexer.js +72 -69
- package/kits/kitnovacweb/nvml/parser.js +328 -159
- package/kits/kitnovacweb/nvml/renderer.js +770 -270
- package/kits/kitparse/kitdef.js +1688 -0
- package/kits/kitproto/kitdef.js +613 -0
- package/kits/kitqr/kitdef.js +637 -0
- package/kits/kitregex++/kitdef.js +1353 -0
- package/kits/kitrequire/kitdef.js +1599 -0
- package/kits/kitx11/kitdef.js +1 -0
- package/kits/kitx11/kitx11.js +2472 -0
- package/kits/kitx11/kitx11_conn.js +948 -0
- package/kits/kitx11/kitx11_worker.js +121 -0
- package/kits/libtea/kitdef.js +2691 -0
- package/kits/libterm/ex.js +285 -0
- package/kits/libterm/kitdef.js +1927 -0
- package/novac/LICENSE +21 -0
- package/novac/README.md +1823 -0
- package/novac/bin/novac +950 -0
- package/novac/bin/nvc +522 -0
- package/novac/bin/nvml +542 -0
- package/novac/demo.nv +245 -0
- package/novac/demo_builtins.nv +209 -0
- package/novac/demo_http.nv +62 -0
- package/novac/examples/bf.nv +69 -0
- package/novac/examples/math.nv +21 -0
- package/novac/kits/kitai/kitdef.js +2185 -0
- package/novac/kits/kitansi/kitdef.js +1402 -0
- package/novac/kits/kitformat/kitdef.js +1485 -0
- package/novac/kits/kitgps/kitdef.js +1862 -0
- package/novac/kits/kitlibfs/kitdef.js +231 -0
- package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
- package/novac/kits/kitmatrix/ex.js +19 -0
- package/novac/kits/kitmatrix/kitdef.js +960 -0
- package/novac/kits/kitmpatch/kitdef.js +906 -0
- package/novac/kits/kitnovacweb/README.md +1572 -0
- package/novac/kits/kitnovacweb/demo.nv +12 -0
- package/novac/kits/kitnovacweb/demo.nvml +71 -0
- package/novac/kits/kitnovacweb/index.nova +12 -0
- package/novac/kits/kitnovacweb/kitdef.js +692 -0
- package/novac/kits/kitnovacweb/nova.kit.json +8 -0
- package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
- package/novac/kits/kitnovacweb/nvml/index.js +67 -0
- package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
- package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
- package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
- package/novac/kits/kitparse/kitdef.js +1688 -0
- package/novac/kits/kitregex++/kitdef.js +1353 -0
- package/novac/kits/kitrequire/kitdef.js +1599 -0
- package/novac/kits/kitx11/kitdef.js +1 -0
- package/novac/kits/kitx11/kitx11.js +2472 -0
- package/novac/kits/kitx11/kitx11_conn.js +948 -0
- package/novac/kits/kitx11/kitx11_worker.js +121 -0
- package/novac/kits/libtea/tf.js +2691 -0
- package/novac/kits/libterm/ex.js +285 -0
- package/novac/kits/libterm/kitdef.js +1927 -0
- package/novac/node_modules/chalk/license +9 -0
- package/novac/node_modules/chalk/package.json +83 -0
- package/novac/node_modules/chalk/readme.md +297 -0
- package/novac/node_modules/chalk/source/index.d.ts +325 -0
- package/novac/node_modules/chalk/source/index.js +225 -0
- package/novac/node_modules/chalk/source/utilities.js +33 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/novac/node_modules/commander/LICENSE +22 -0
- package/novac/node_modules/commander/Readme.md +1176 -0
- package/novac/node_modules/commander/esm.mjs +16 -0
- package/novac/node_modules/commander/index.js +24 -0
- package/novac/node_modules/commander/lib/argument.js +150 -0
- package/novac/node_modules/commander/lib/command.js +2777 -0
- package/novac/node_modules/commander/lib/error.js +39 -0
- package/novac/node_modules/commander/lib/help.js +747 -0
- package/novac/node_modules/commander/lib/option.js +380 -0
- package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/novac/node_modules/commander/package-support.json +19 -0
- package/novac/node_modules/commander/package.json +82 -0
- package/novac/node_modules/commander/typings/esm.d.mts +3 -0
- package/novac/node_modules/commander/typings/index.d.ts +1113 -0
- package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
- package/novac/node_modules/node-addon-api/README.md +95 -0
- package/novac/node_modules/node-addon-api/common.gypi +21 -0
- package/novac/node_modules/node-addon-api/except.gypi +25 -0
- package/novac/node_modules/node-addon-api/index.js +14 -0
- package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
- package/novac/node_modules/node-addon-api/napi.h +3364 -0
- package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
- package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
- package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/novac/node_modules/node-addon-api/package-support.json +21 -0
- package/novac/node_modules/node-addon-api/package.json +480 -0
- package/novac/node_modules/node-addon-api/tools/README.md +73 -0
- package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/novac/node_modules/serialize-javascript/LICENSE +27 -0
- package/novac/node_modules/serialize-javascript/README.md +149 -0
- package/novac/node_modules/serialize-javascript/index.js +297 -0
- package/novac/node_modules/serialize-javascript/package.json +33 -0
- package/novac/package.json +27 -0
- package/novac/scripts/update-bin.js +24 -0
- package/novac/src/core/bstd.js +1035 -0
- package/novac/src/core/config.js +155 -0
- package/novac/src/core/describe.js +187 -0
- package/novac/src/core/emitter.js +499 -0
- package/novac/src/core/error.js +86 -0
- package/novac/src/core/executor.js +5606 -0
- package/novac/src/core/formatter.js +686 -0
- package/novac/src/core/lexer.js +1026 -0
- package/novac/src/core/nova_builtins.js +717 -0
- package/novac/src/core/nova_thread_worker.js +166 -0
- package/novac/src/core/parser.js +2181 -0
- package/novac/src/core/types.js +112 -0
- package/novac/src/index.js +28 -0
- package/novac/src/runtime/stdlib.js +244 -0
- package/package.json +6 -3
- package/scripts/update-bin.js +0 -0
- package/src/core/bstd.js +838 -362
- package/src/core/executor.js +2578 -170
- package/src/core/lexer.js +502 -54
- package/src/core/nova_builtins.js +21 -3
- package/src/core/parser.js +413 -72
- package/src/core/types.js +30 -2
- package/src/index.js +0 -0
- package/examples/example-project/README.md +0 -3
- package/examples/example-project/src/main.nova +0 -3
- package/src/core/environment.js +0 -0
- /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
|
@@ -0,0 +1,1035 @@
|
|
|
1
|
+
const { CustomError, formatError, NovaException } = require('./error.js');
|
|
2
|
+
const { hash } = require('crypto');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
const { execFileSync, execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
let Air = {};
|
|
10
|
+
|
|
11
|
+
// read all .js files in the current directory
|
|
12
|
+
fs.readdirSync(__dirname)
|
|
13
|
+
.filter(file => file !== 'bstd.js' && file.endsWith('.js'))
|
|
14
|
+
.forEach(file => {
|
|
15
|
+
const name = path.basename(file, '.js');
|
|
16
|
+
Air[name] = require(path.join(__dirname, file));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
let Chalk = require('chalk').default;
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// History (unchanged from original)
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
let History = {
|
|
25
|
+
getKey(name) { return '.184.' + name; },
|
|
26
|
+
exists(key) { return fs.existsSync(key); },
|
|
27
|
+
createHistory(key) {
|
|
28
|
+
fs.writeFileSync(key, JSON.stringify({ history: [] }));
|
|
29
|
+
return key;
|
|
30
|
+
},
|
|
31
|
+
appendHistory(key, addition, pushOrIndex, index) {
|
|
32
|
+
let content = JSON.parse(fs.readFileSync(key));
|
|
33
|
+
if (pushOrIndex === 'index') content.history[index] = addition;
|
|
34
|
+
else content.history.push(addition);
|
|
35
|
+
fs.writeFileSync(key, JSON.stringify(content));
|
|
36
|
+
},
|
|
37
|
+
readHistory(key) {
|
|
38
|
+
let content = JSON.parse(fs.readFileSync(key));
|
|
39
|
+
return content.history;
|
|
40
|
+
},
|
|
41
|
+
rawSetHistory(key, historyArray) {
|
|
42
|
+
let content = JSON.parse(fs.readFileSync(key));
|
|
43
|
+
content.history = historyArray;
|
|
44
|
+
fs.writeFileSync(key, JSON.stringify(content));
|
|
45
|
+
},
|
|
46
|
+
rawGetHistory(key) {
|
|
47
|
+
let content = JSON.parse(fs.readFileSync(key));
|
|
48
|
+
return content.history;
|
|
49
|
+
},
|
|
50
|
+
clearHistory(key) {
|
|
51
|
+
fs.writeFileSync(key, JSON.stringify({ history: [] }));
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
// Shema (unchanged from original)
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
const ShemaTypes = {};
|
|
59
|
+
class ShemaType {
|
|
60
|
+
constructor(typename, validatorFunction, defaultObj) {
|
|
61
|
+
this.typename = typename;
|
|
62
|
+
this.validate = validatorFunction;
|
|
63
|
+
this.defaultObj
|
|
64
|
+
ShemaTypes[typename] = this;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
class Shema {
|
|
68
|
+
constructor(obj) { this.shema = obj; }
|
|
69
|
+
validate(obj) {
|
|
70
|
+
for (const key in this.shema) {
|
|
71
|
+
const type = this.shema[key];
|
|
72
|
+
const value = obj[key];
|
|
73
|
+
if (type instanceof ShemaType) {
|
|
74
|
+
if (!type.validate(value))
|
|
75
|
+
throw new Error(`Validation failed for key "${key}": expected ${type.typename}, got ${typeof value}`);
|
|
76
|
+
} else if (type instanceof Shema) {
|
|
77
|
+
type.validate(value);
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error(`Invalid shema definition for key "${key}"`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
defaultObj() {
|
|
84
|
+
const obj = {};
|
|
85
|
+
for (const key in this.shema) {
|
|
86
|
+
const type = this.shema[key];
|
|
87
|
+
if (type instanceof ShemaType) {
|
|
88
|
+
obj[key] = type.typename === 'Int' || type.typename === 'Float' ? 0 :
|
|
89
|
+
type.typename === 'String' ? '' :
|
|
90
|
+
type.typename === 'Boolean' ? false :
|
|
91
|
+
type.typename === 'Array' ? [] :
|
|
92
|
+
type.typename === 'Object' ? {} :
|
|
93
|
+
type.typename === 'Function' ? () => { } :
|
|
94
|
+
type.typename === 'Null' ? null :
|
|
95
|
+
type.typename === 'Undefined' ? undefined :
|
|
96
|
+
type?.defaultObj ? type.defaultObj() : null;
|
|
97
|
+
} else if (type instanceof Shema) {
|
|
98
|
+
obj[key] = type.defaultObj();
|
|
99
|
+
} else {
|
|
100
|
+
throw new Error(`Invalid shema definition for key "${key}"`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return obj;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
new ShemaType('Int', v => Number.isInteger(v));
|
|
107
|
+
new ShemaType('Float', v => typeof v === 'number' && !Number.isNaN(v));
|
|
108
|
+
new ShemaType('String', v => typeof v === 'string');
|
|
109
|
+
new ShemaType('Boolean', v => typeof v === 'boolean');
|
|
110
|
+
new ShemaType('Array', v => Array.isArray(v));
|
|
111
|
+
new ShemaType('Object', v => v !== null && typeof v === 'object' && !Array.isArray(v));
|
|
112
|
+
new ShemaType('Function', v => typeof v === 'function');
|
|
113
|
+
new ShemaType('Null', v => v === null);
|
|
114
|
+
new ShemaType('Undefined', v => v === undefined);
|
|
115
|
+
new ShemaType('Symbol', v => typeof v === 'symbol');
|
|
116
|
+
new ShemaType('BigInt', v => typeof v === 'bigint');
|
|
117
|
+
new ShemaType('Date', v => v instanceof Date && !isNaN(v));
|
|
118
|
+
new ShemaType('RegExp', v => v instanceof RegExp);
|
|
119
|
+
new ShemaType('Shema', v => v instanceof Shema);
|
|
120
|
+
new ShemaType('Any', () => true);
|
|
121
|
+
|
|
122
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
// ConfigFile (unchanged from original)
|
|
124
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
+
class ConfigFile {
|
|
126
|
+
constructor(name, type, hiddenFile = false, shema) {
|
|
127
|
+
this.path = hiddenFile ? '.134.' + name + '.' + type : name + '.135.' + type;
|
|
128
|
+
this.type = type;
|
|
129
|
+
this.shema = shema instanceof Shema ? shema : null;
|
|
130
|
+
if (!fs.existsSync(this.path))
|
|
131
|
+
fs.writeFileSync(this.path, this.type === 'json' ? JSON.parse(shema.defaultObj) : '');
|
|
132
|
+
}
|
|
133
|
+
writeFull(configObj) {
|
|
134
|
+
if (this.shema && this.shema instanceof Shema && this.shema.validate(configObj) === false)
|
|
135
|
+
throw new Error('Config validation failed');
|
|
136
|
+
fs.writeFileSync(this.path, JSON.stringify(configObj));
|
|
137
|
+
}
|
|
138
|
+
write(key, value) {
|
|
139
|
+
const config = this.readFull();
|
|
140
|
+
let current = config;
|
|
141
|
+
for (let i = 0; i < key.length - 1; i++) {
|
|
142
|
+
if (typeof current[key[i]] !== 'object' || current[key[i]] === null) current[key[i]] = {};
|
|
143
|
+
current = current[key[i]];
|
|
144
|
+
}
|
|
145
|
+
current[key[key.length - 1]] = value;
|
|
146
|
+
this.writeFull(config);
|
|
147
|
+
}
|
|
148
|
+
readFull() {
|
|
149
|
+
const content = fs.readFileSync(this.path, 'utf-8');
|
|
150
|
+
if (this.type === 'json') return JSON.parse(content);
|
|
151
|
+
throw new Error('Unsupported config type: ' + this.type);
|
|
152
|
+
}
|
|
153
|
+
read(key) {
|
|
154
|
+
const config = this.readFull();
|
|
155
|
+
let current = config;
|
|
156
|
+
for (let i = 0; i < key.length; i++) {
|
|
157
|
+
if (typeof current[key[i]] !== 'object' || current[key[i]] === null) return undefined;
|
|
158
|
+
current = current[key[i]];
|
|
159
|
+
}
|
|
160
|
+
return current;
|
|
161
|
+
}
|
|
162
|
+
interactiveSetup(defaultConfig = {}) {
|
|
163
|
+
const config = this.readFull();
|
|
164
|
+
const mergedConfig = { ...defaultConfig, ...config };
|
|
165
|
+
const questions = [];
|
|
166
|
+
const buildQuestions = (obj, path = []) => {
|
|
167
|
+
for (const key in obj) {
|
|
168
|
+
const value = obj[key];
|
|
169
|
+
if (typeof value === 'object' && value !== null) buildQuestions(value, path.concat(key));
|
|
170
|
+
else questions.push({ path: path.concat(key), default: value });
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
buildQuestions(mergedConfig);
|
|
174
|
+
const askQuestion = (index) => {
|
|
175
|
+
if (index >= questions.length) { this.writeFull(mergedConfig); console.log('Config saved successfully!'); return; }
|
|
176
|
+
const { path, default: defaultValue } = questions[index];
|
|
177
|
+
InputMgr.prompt(`Enter value for ${path.join('.')} (default: ${defaultValue}): `).then(answer => {
|
|
178
|
+
if (answer.trim() !== '') {
|
|
179
|
+
let current = mergedConfig;
|
|
180
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
181
|
+
if (typeof current[path[i]] !== 'object' || current[path[i]] === null) current[path[i]] = {};
|
|
182
|
+
current = current[path[i]];
|
|
183
|
+
}
|
|
184
|
+
current[path[path.length - 1]] = answer;
|
|
185
|
+
}
|
|
186
|
+
askQuestion(index + 1);
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
askQuestion(0);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
194
|
+
// JsDB / Storage / Zip / Crypto / PathUtils / Base64 / OsUtils (unchanged)
|
|
195
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
196
|
+
let JsDB = {
|
|
197
|
+
class(JsClass) { return (...args) => new JsClass(...args); },
|
|
198
|
+
run(code) { return eval(code); },
|
|
199
|
+
require: (p) => require(path.join(process.cwd(), p)),
|
|
200
|
+
reqRaw: require,
|
|
201
|
+
gt: globalThis,
|
|
202
|
+
fetch(url, options) {
|
|
203
|
+
const { execSync } = require('child_process');
|
|
204
|
+
let command;
|
|
205
|
+
if (process.platform === 'win32') {
|
|
206
|
+
command = `powershell -Command "Invoke-WebRequest -Uri '${url}' -Method ${options?.method || 'GET'} -Headers @(${Object.entries(options?.headers || {}).map(([k, v]) => `@{${k}='${v}'}`).join(',')}) -Body '${options?.body || ''}' -UseBasicParsing"`;
|
|
207
|
+
} else {
|
|
208
|
+
command = `curl -X ${options?.method || 'GET'} '${url}' ${Object.entries(options?.headers || {}).map(([k, v]) => `-H "${k}: ${v}"`).join(' ')} -d '${options?.body || ''}'`;
|
|
209
|
+
}
|
|
210
|
+
try { return execSync(command, { encoding: 'utf-8' }); }
|
|
211
|
+
catch (error) { throw new Error(`Fetch failed: ${error.message}`); }
|
|
212
|
+
},
|
|
213
|
+
process,
|
|
214
|
+
version: process.version,
|
|
215
|
+
platform: process.platform,
|
|
216
|
+
env: process.env
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const Storage = {
|
|
220
|
+
setItem(key, value) {
|
|
221
|
+
const filePath = path.join(__dirname, '.storage', key);
|
|
222
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
223
|
+
fs.writeFileSync(filePath, JSON.stringify(value));
|
|
224
|
+
},
|
|
225
|
+
getItem(key) {
|
|
226
|
+
const filePath = path.join(__dirname, '.storage', key);
|
|
227
|
+
if (fs.existsSync(filePath)) return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
228
|
+
return null;
|
|
229
|
+
},
|
|
230
|
+
removeItem(key) {
|
|
231
|
+
const filePath = path.join(__dirname, '.storage', key);
|
|
232
|
+
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
233
|
+
},
|
|
234
|
+
clear() {
|
|
235
|
+
const storageDir = path.join(__dirname, '.storage');
|
|
236
|
+
if (fs.existsSync(storageDir)) fs.rmSync(storageDir, { recursive: true, force: true });
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const Zip = {
|
|
241
|
+
zip(files, output) {
|
|
242
|
+
const archiver = require('archiver');
|
|
243
|
+
const outputStream = fs.createWriteStream(output);
|
|
244
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
245
|
+
return new Promise((resolve, reject) => {
|
|
246
|
+
outputStream.on('close', () => resolve());
|
|
247
|
+
archive.on('error', err => reject(err));
|
|
248
|
+
archive.pipe(outputStream);
|
|
249
|
+
files.forEach(file => { if (fs.existsSync(file)) archive.file(file, { name: path.basename(file) }); });
|
|
250
|
+
archive.finalize();
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
unzip(zipFile, outputDir) {
|
|
254
|
+
const unzipper = require('unzipper');
|
|
255
|
+
return new Promise((resolve, reject) => {
|
|
256
|
+
fs.createReadStream(zipFile)
|
|
257
|
+
.pipe(unzipper.Extract({ path: outputDir }))
|
|
258
|
+
.on('close', () => resolve())
|
|
259
|
+
.on('error', err => reject(err));
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const Crypto = {
|
|
265
|
+
hash(data, algorithm = 'sha256') { return hash(algorithm).update(data).digest('hex'); }
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const PathUtils = {
|
|
269
|
+
join(...segments) { return path.join(...segments); },
|
|
270
|
+
resolve(...segments) { return path.resolve(...segments); },
|
|
271
|
+
basename(p, ext) { return path.basename(p, ext); },
|
|
272
|
+
dirname(p) { return path.dirname(p); },
|
|
273
|
+
extname(p) { return path.extname(p); }
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const Base64 = {
|
|
277
|
+
encode(str) { return Buffer.from(str, 'utf-8').toString('base64'); },
|
|
278
|
+
decode(encoded) { return Buffer.from(encoded, 'base64').toString('utf-8'); }
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const OsUtils = {
|
|
282
|
+
platform: process.platform,
|
|
283
|
+
homedir: require('os').homedir,
|
|
284
|
+
tmpdir: require('os').tmpdir,
|
|
285
|
+
cpus: require('os').cpus,
|
|
286
|
+
totalmem: require('os').totalmem,
|
|
287
|
+
freemem: require('os').freemem,
|
|
288
|
+
uptime: require('os').uptime
|
|
289
|
+
};
|
|
290
|
+
switch (process.platform) {
|
|
291
|
+
case 'win32':
|
|
292
|
+
OsUtils.requestAdminPrivileges = function () {
|
|
293
|
+
try { execSync('powershell -Command "Start-Process cmd -Verb runAs"'); }
|
|
294
|
+
catch (error) { throw new Error('Failed to request admin privileges: ' + error.message); }
|
|
295
|
+
};
|
|
296
|
+
OsUtils.openFile = function (filePath) {
|
|
297
|
+
try { execSync(`start "" "${filePath}"`); }
|
|
298
|
+
catch (error) { throw new Error('Failed to open file: ' + error.message); }
|
|
299
|
+
};
|
|
300
|
+
break;
|
|
301
|
+
case 'darwin':
|
|
302
|
+
OsUtils.openFile = function (filePath) {
|
|
303
|
+
try { execSync(`open "${filePath}"`); }
|
|
304
|
+
catch (error) { throw new Error('Failed to open file: ' + error.message); }
|
|
305
|
+
};
|
|
306
|
+
break;
|
|
307
|
+
case 'linux':
|
|
308
|
+
OsUtils.openFile = function (filePath) {
|
|
309
|
+
try { execSync(`xdg-open "${filePath}"`); }
|
|
310
|
+
catch (error) { throw new Error('Failed to open file: ' + error.message); }
|
|
311
|
+
};
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
316
|
+
// MathExprDebugger (unchanged from original)
|
|
317
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
318
|
+
class MathExprDebugger {
|
|
319
|
+
constructor(value) { this.value = value.toString(); }
|
|
320
|
+
__operators = {
|
|
321
|
+
unary: {
|
|
322
|
+
'+': () => new MathExprDebugger(this.value),
|
|
323
|
+
'-': () => new MathExprDebugger('( -' + this.value + ' )')
|
|
324
|
+
},
|
|
325
|
+
binary: {
|
|
326
|
+
'+': (a) => new MathExprDebugger(`(${this.value} + ${a.value})`),
|
|
327
|
+
'-': (a) => new MathExprDebugger(`(${this.value} - ${a.value})`),
|
|
328
|
+
'*': (a) => new MathExprDebugger(`(${this.value} * ${a.value})`),
|
|
329
|
+
'/': (a) => new MathExprDebugger(`(${this.value} / ${a.value})`)
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
toString() { return this.value; }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
336
|
+
//
|
|
337
|
+
// I N P U T M G R — Full terminal input manager for novac / bstd
|
|
338
|
+
//
|
|
339
|
+
// Features:
|
|
340
|
+
// · prompt(msg, opts) — sync line read (main API)
|
|
341
|
+
// · promptAsync(msg, opts) — async line read
|
|
342
|
+
// · promptPassword(msg) — sync masked/hashed password read
|
|
343
|
+
// · promptPasswordAsync(msg) — async masked/hashed password read
|
|
344
|
+
// · repl(opts) — sync REPL loop
|
|
345
|
+
// · replAsync(opts) — async REPL loop
|
|
346
|
+
// · mouse(cb, opts) — terminal mouse/click event listener (async)
|
|
347
|
+
// · SyntaxHighlight — pluggable syntax highlight API
|
|
348
|
+
// · history management — per-name persistent JSON history files
|
|
349
|
+
//
|
|
350
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
351
|
+
|
|
352
|
+
// ── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
353
|
+
const ESC = '\x1b';
|
|
354
|
+
const _ansi = {
|
|
355
|
+
reset: `${ESC}[0m`,
|
|
356
|
+
bold: `${ESC}[1m`,
|
|
357
|
+
dim: `${ESC}[2m`,
|
|
358
|
+
hide: `${ESC}[?25l`,
|
|
359
|
+
show: `${ESC}[?25h`,
|
|
360
|
+
clearLine: `${ESC}[2K`,
|
|
361
|
+
bol: `${ESC}[0G`,
|
|
362
|
+
up: (n = 1) => `${ESC}[${n}A`,
|
|
363
|
+
down: (n = 1) => `${ESC}[${n}B`,
|
|
364
|
+
right: (n = 1) => `${ESC}[${n}C`,
|
|
365
|
+
left: (n = 1) => `${ESC}[${n}D`,
|
|
366
|
+
col: (n) => `${ESC}[${n}G`,
|
|
367
|
+
mouseOn: `${ESC}[?1000h${ESC}[?1002h${ESC}[?1006h`,
|
|
368
|
+
mouseOff: `${ESC}[?1006l${ESC}[?1002l${ESC}[?1000l`,
|
|
369
|
+
fg: (c) => `${ESC}[${c}m`,
|
|
370
|
+
saveCursor: `${ESC}[s`,
|
|
371
|
+
restCursor: `${ESC}[u`,
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// ── syncReadLine — the core synchronous stdin line reader ─────────────────────
|
|
375
|
+
// Reads one line from stdin in raw mode, honouring:
|
|
376
|
+
// mask — character to echo instead of real chars (password mode)
|
|
377
|
+
// history — array of previous entries for ↑/↓ navigation
|
|
378
|
+
// highlight — function(line) → coloured string for display
|
|
379
|
+
// placeholder — greyed-out text shown when buffer is empty
|
|
380
|
+
function _syncReadLine(promptStr, { mask = null, history = [], highlight = null, placeholder = '' } = {}) {
|
|
381
|
+
const stdin = process.stdin;
|
|
382
|
+
const stdout = process.stdout;
|
|
383
|
+
|
|
384
|
+
// Save / restore terminal settings
|
|
385
|
+
const wasTTY = stdin.isTTY;
|
|
386
|
+
const wasRaw = wasTTY && stdin.isRaw;
|
|
387
|
+
|
|
388
|
+
if (wasTTY) stdin.setRawMode(true);
|
|
389
|
+
stdin.resume();
|
|
390
|
+
|
|
391
|
+
let buf = '';
|
|
392
|
+
let cursor = 0; // insertion point in buf
|
|
393
|
+
let histIdx = history.length; // pointer: history.length = "current" (fresh) line
|
|
394
|
+
let saved = ''; // preserves current input when navigating history
|
|
395
|
+
|
|
396
|
+
// Write prompt + current buffer, clear to end of line
|
|
397
|
+
const redraw = () => {
|
|
398
|
+
const display = mask
|
|
399
|
+
? mask.repeat(buf.length)
|
|
400
|
+
: (highlight ? highlight(buf) : buf);
|
|
401
|
+
const shown = buf.length === 0 && placeholder
|
|
402
|
+
? `${_ansi.dim}${placeholder}${_ansi.reset}`
|
|
403
|
+
: display;
|
|
404
|
+
// Go to BOL, clear line, rewrite
|
|
405
|
+
stdout.write(`\r${_ansi.clearLine}${promptStr}${shown}`);
|
|
406
|
+
// Reposition cursor
|
|
407
|
+
const visLen = (mask ? mask.length * cursor : promptStr.length + cursor);
|
|
408
|
+
// We know promptStr printable length is promptStr.replace(/\x1b\[[^m]*m/g,'').length
|
|
409
|
+
const promptVis = promptStr.replace(/\x1b\[[^m]*m/g, '').length;
|
|
410
|
+
stdout.write(_ansi.col(promptVis + cursor + 1));
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
redraw();
|
|
414
|
+
|
|
415
|
+
let line = null;
|
|
416
|
+
// Read bytes synchronously in a tight loop
|
|
417
|
+
const buf8 = Buffer.alloc(8);
|
|
418
|
+
|
|
419
|
+
while (line === null) {
|
|
420
|
+
const n = fs.readSync(stdin.fd, buf8, 0, buf8.length, null);
|
|
421
|
+
if (n <= 0) { line = buf; break; }
|
|
422
|
+
let seq = buf8.slice(0, n).toString('utf8');
|
|
423
|
+
|
|
424
|
+
// ── Escape sequences ───────────────────────────────────────────────────
|
|
425
|
+
if (seq === '\x1b[A' || seq === '\x1bOA') {
|
|
426
|
+
// Arrow UP — history prev
|
|
427
|
+
if (histIdx > 0) {
|
|
428
|
+
if (histIdx === history.length) saved = buf;
|
|
429
|
+
histIdx--;
|
|
430
|
+
buf = history[histIdx];
|
|
431
|
+
cursor = buf.length;
|
|
432
|
+
redraw();
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (seq === '\x1b[B' || seq === '\x1bOB') {
|
|
437
|
+
// Arrow DOWN — history next
|
|
438
|
+
if (histIdx < history.length) {
|
|
439
|
+
histIdx++;
|
|
440
|
+
buf = histIdx === history.length ? saved : history[histIdx];
|
|
441
|
+
cursor = buf.length;
|
|
442
|
+
redraw();
|
|
443
|
+
}
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (seq === '\x1b[C' || seq === '\x1bOC') {
|
|
447
|
+
// Arrow RIGHT
|
|
448
|
+
if (cursor < buf.length) { cursor++; redraw(); }
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (seq === '\x1b[D' || seq === '\x1bOD') {
|
|
452
|
+
// Arrow LEFT
|
|
453
|
+
if (cursor > 0) { cursor--; redraw(); }
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (seq === '\x1b[H' || seq === '\x1bOH' || seq === '\x01') {
|
|
457
|
+
// Home / Ctrl-A
|
|
458
|
+
cursor = 0; redraw(); continue;
|
|
459
|
+
}
|
|
460
|
+
if (seq === '\x1b[F' || seq === '\x1bOF' || seq === '\x05') {
|
|
461
|
+
// End / Ctrl-E
|
|
462
|
+
cursor = buf.length; redraw(); continue;
|
|
463
|
+
}
|
|
464
|
+
if (seq === '\x1b[3~') {
|
|
465
|
+
// Delete key
|
|
466
|
+
if (cursor < buf.length) { buf = buf.slice(0, cursor) + buf.slice(cursor + 1); redraw(); }
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
// Ctrl-U — clear line
|
|
470
|
+
if (seq === '\x15') { buf = ''; cursor = 0; redraw(); continue; }
|
|
471
|
+
// Ctrl-K — clear from cursor to end
|
|
472
|
+
if (seq === '\x0b') { buf = buf.slice(0, cursor); redraw(); continue; }
|
|
473
|
+
// Ctrl-W — delete word before cursor
|
|
474
|
+
if (seq === '\x17') {
|
|
475
|
+
const left = buf.slice(0, cursor).replace(/\S+\s*$/, '');
|
|
476
|
+
buf = left + buf.slice(cursor);
|
|
477
|
+
cursor = left.length;
|
|
478
|
+
redraw(); continue;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ── Single characters ──────────────────────────────────────────────────
|
|
482
|
+
for (let i = 0; i < seq.length; i++) {
|
|
483
|
+
const ch = seq[i];
|
|
484
|
+
const code = ch.charCodeAt(0);
|
|
485
|
+
|
|
486
|
+
if (ch === '\r' || ch === '\n') {
|
|
487
|
+
// Enter
|
|
488
|
+
stdout.write('\n');
|
|
489
|
+
line = buf;
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
if (code === 127 || code === 8) {
|
|
493
|
+
// Backspace
|
|
494
|
+
if (cursor > 0) { buf = buf.slice(0, cursor - 1) + buf.slice(cursor); cursor--; redraw(); }
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (ch === '\x03') {
|
|
498
|
+
// Ctrl-C
|
|
499
|
+
stdout.write('^C\n');
|
|
500
|
+
if (wasTTY) stdin.setRawMode(wasRaw);
|
|
501
|
+
stdin.pause();
|
|
502
|
+
process.exit(130);
|
|
503
|
+
}
|
|
504
|
+
if (ch === '\x04') {
|
|
505
|
+
// Ctrl-D (EOF)
|
|
506
|
+
stdout.write('\n');
|
|
507
|
+
line = buf;
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
if (code >= 32) {
|
|
511
|
+
// Printable
|
|
512
|
+
buf = buf.slice(0, cursor) + ch + buf.slice(cursor);
|
|
513
|
+
cursor++;
|
|
514
|
+
redraw();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (wasTTY) stdin.setRawMode(wasRaw);
|
|
520
|
+
stdin.pause();
|
|
521
|
+
|
|
522
|
+
return line;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ── SyntaxHighlight — pluggable highlighter registry ─────────────────────────
|
|
526
|
+
//
|
|
527
|
+
// Usage:
|
|
528
|
+
// SyntaxHighlight.register('myLang', (line) => coloredLine);
|
|
529
|
+
// const hl = SyntaxHighlight.get('myLang');
|
|
530
|
+
// SyntaxHighlight.highlight('myLang', 'some code');
|
|
531
|
+
//
|
|
532
|
+
// Built-in themes: 'novac', 'json', 'shell', 'basic', 'bf'
|
|
533
|
+
const SyntaxHighlight = (() => {
|
|
534
|
+
const _reg = new Map();
|
|
535
|
+
|
|
536
|
+
// ── colour helpers (no deps) ────────────────────────────────────────────
|
|
537
|
+
const C = {
|
|
538
|
+
reset: '\x1b[0m',
|
|
539
|
+
keyword: '\x1b[35m', // magenta
|
|
540
|
+
string: '\x1b[32m', // green
|
|
541
|
+
number: '\x1b[33m', // yellow
|
|
542
|
+
comment: '\x1b[90m', // dark grey
|
|
543
|
+
ident: '\x1b[36m', // cyan
|
|
544
|
+
op: '\x1b[37m', // white
|
|
545
|
+
error: '\x1b[31m', // red
|
|
546
|
+
prompt: '\x1b[94m', // bright blue
|
|
547
|
+
dim: '\x1b[2m',
|
|
548
|
+
bold: '\x1b[1m',
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// paint helper: wrap text in a colour and reset
|
|
552
|
+
const paint = (colour, text) => colour + text + C.reset;
|
|
553
|
+
|
|
554
|
+
// ── Nova/novac highlighter ──────────────────────────────────────────────
|
|
555
|
+
const _novacKeywords = new Set([
|
|
556
|
+
'var', 'let', 'const', 'class', 'if', 'else', 'for', 'while', 'do', 'return', 'give',
|
|
557
|
+
'func', 'function', 'async', 'await', 'new', 'import', 'export', 'from', 'switch',
|
|
558
|
+
'case', 'default', 'break', 'continue', 'try', 'catch', 'finally', 'throw', 'emit',
|
|
559
|
+
'on', 'of', 'each', 'match', 'when', 'where', 'as', 'type', 'struct', 'interface',
|
|
560
|
+
'enum', 'trait', 'impl', 'yield', 'loop', 'repeat', 'unless', 'until', 'block',
|
|
561
|
+
'assert', 'guard', 'with', 'run', 'true', 'false', 'null',
|
|
562
|
+
]);
|
|
563
|
+
|
|
564
|
+
const _hlNovac = (line) => {
|
|
565
|
+
// Tokenise very simply: strings, numbers, keywords, idents, operators
|
|
566
|
+
let out = '';
|
|
567
|
+
let i = 0;
|
|
568
|
+
while (i < line.length) {
|
|
569
|
+
// Comment
|
|
570
|
+
if (line[i] === '/' && (line[i + 1] === '/' || line[i + 1] === '!')) {
|
|
571
|
+
out += paint(C.comment, line.slice(i)); break;
|
|
572
|
+
}
|
|
573
|
+
// String (single or double)
|
|
574
|
+
if (line[i] === '"' || line[i] === "'") {
|
|
575
|
+
const q = line[i]; let j = i + 1;
|
|
576
|
+
while (j < line.length && line[j] !== q) { if (line[j] === '\\') j++; j++; }
|
|
577
|
+
out += paint(C.string, line.slice(i, j + 1)); i = j + 1; continue;
|
|
578
|
+
}
|
|
579
|
+
// f-string prefix
|
|
580
|
+
if (line[i] === 'f' && (line[i + 1] === '"' || line[i + 1] === "'")) {
|
|
581
|
+
const q = line[i + 1]; let j = i + 2;
|
|
582
|
+
while (j < line.length && line[j] !== q) { if (line[j] === '\\') j++; j++; }
|
|
583
|
+
out += paint(C.string, line.slice(i, j + 1)); i = j + 1; continue;
|
|
584
|
+
}
|
|
585
|
+
// Number
|
|
586
|
+
if (/[0-9]/.test(line[i])) {
|
|
587
|
+
let j = i;
|
|
588
|
+
while (j < line.length && /[0-9._xXbBoOeE+\-]/.test(line[j])) j++;
|
|
589
|
+
out += paint(C.number, line.slice(i, j)); i = j; continue;
|
|
590
|
+
}
|
|
591
|
+
// Identifier / keyword
|
|
592
|
+
if (/[A-Za-z_$]/.test(line[i])) {
|
|
593
|
+
let j = i;
|
|
594
|
+
while (j < line.length && /[A-Za-z0-9_$]/.test(line[j])) j++;
|
|
595
|
+
const word = line.slice(i, j);
|
|
596
|
+
out += _novacKeywords.has(word) ? paint(C.keyword, word) : paint(C.ident, word);
|
|
597
|
+
i = j; continue;
|
|
598
|
+
}
|
|
599
|
+
// Operators & punctuation — keep plain
|
|
600
|
+
out += paint(C.op, line[i]); i++;
|
|
601
|
+
}
|
|
602
|
+
return out;
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// ── JSON highlighter ────────────────────────────────────────────────────
|
|
606
|
+
const _hlJSON = (line) => {
|
|
607
|
+
return line
|
|
608
|
+
.replace(/"(?:[^"\\]|\\.)*"/g, s => paint(C.string, s))
|
|
609
|
+
.replace(/\b(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/g, s => paint(C.number, s))
|
|
610
|
+
.replace(/\b(true|false|null)\b/g, s => paint(C.keyword, s));
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// ── Shell highlighter ───────────────────────────────────────────────────
|
|
614
|
+
const _shellKw = new Set(['if', 'then', 'else', 'fi', 'for', 'do', 'done', 'while', 'case', 'esac', 'function', 'return', 'exit', 'echo', 'cd', 'ls', 'mkdir', 'rm', 'cp', 'mv', 'grep', 'sed', 'awk', 'cat', 'source', 'export', 'alias']);
|
|
615
|
+
const _hlShell = (line) => {
|
|
616
|
+
if (line.trimStart().startsWith('#')) return paint(C.comment, line);
|
|
617
|
+
let out = '';
|
|
618
|
+
const tokens = line.split(/(\s+|[|>&;(){}[\]]|"(?:[^"\\]|\\.)*"|'[^']*')/);
|
|
619
|
+
let first = true;
|
|
620
|
+
for (const tok of tokens) {
|
|
621
|
+
if (!tok) continue;
|
|
622
|
+
if (first && /[A-Za-z_]/.test(tok[0])) {
|
|
623
|
+
out += _shellKw.has(tok) ? paint(C.keyword, tok) : paint(C.prompt, tok);
|
|
624
|
+
first = false;
|
|
625
|
+
} else if (tok.startsWith('"') || tok.startsWith("'")) {
|
|
626
|
+
out += paint(C.string, tok);
|
|
627
|
+
} else if (/^-?[0-9]+$/.test(tok)) {
|
|
628
|
+
out += paint(C.number, tok);
|
|
629
|
+
} else if (/^[|>&;(){}[\]]$/.test(tok.trim())) {
|
|
630
|
+
out += paint(C.op, tok);
|
|
631
|
+
} else {
|
|
632
|
+
out += tok;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return out;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// ── BASIC highlighter ───────────────────────────────────────────────────
|
|
639
|
+
const _basicKw = new Set([
|
|
640
|
+
'PRINT', 'INPUT', 'LET', 'IF', 'THEN', 'ELSE', 'GOTO', 'GOSUB', 'RETURN', 'END',
|
|
641
|
+
'FOR', 'TO', 'STEP', 'NEXT', 'DIM', 'DATA', 'READ', 'RESTORE', 'REM', 'STOP',
|
|
642
|
+
'LIST', 'RUN', 'NEW', 'LOAD', 'SAVE', 'CLS', 'CLR', 'AND', 'OR', 'NOT',
|
|
643
|
+
'INT', 'SQR', 'ABS', 'RND', 'SIN', 'COS', 'TAN', 'LOG', 'EXP', 'SGN',
|
|
644
|
+
'MID$', 'LEFT$', 'RIGHT$', 'LEN', 'STR$', 'VAL', 'CHR$', 'ASC', 'INSTR',
|
|
645
|
+
'TAB', 'SPC', 'AT', 'POKE', 'PEEK', 'CALL', 'ON',
|
|
646
|
+
]);
|
|
647
|
+
const _hlBasic = (line) => {
|
|
648
|
+
// line-number prefix
|
|
649
|
+
const lineNo = line.match(/^(\s*\d+\s)/);
|
|
650
|
+
let rest = lineNo ? line.slice(lineNo[0].length) : line;
|
|
651
|
+
let prefix = lineNo ? paint(C.number, lineNo[0]) : '';
|
|
652
|
+
// REM comment
|
|
653
|
+
if (/^\s*REM\b/i.test(rest)) return prefix + paint(C.comment, rest);
|
|
654
|
+
let out = '';
|
|
655
|
+
const tokens = rest.split(/(\b[A-Z$]+\b|\d+(?:\.\d+)?|"[^"]*"|[^A-Za-z0-9"$\s]+|\s+)/gi);
|
|
656
|
+
for (const tok of tokens) {
|
|
657
|
+
if (!tok) continue;
|
|
658
|
+
if (_basicKw.has(tok.toUpperCase())) out += paint(C.keyword, tok);
|
|
659
|
+
else if (tok.startsWith('"')) out += paint(C.string, tok);
|
|
660
|
+
else if (/^-?\d/.test(tok)) out += paint(C.number, tok);
|
|
661
|
+
else out += tok;
|
|
662
|
+
}
|
|
663
|
+
return prefix + out;
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// ── Brainfuck highlighter ───────────────────────────────────────────────
|
|
667
|
+
const _hlBF = (line) => {
|
|
668
|
+
return [...line].map(c => {
|
|
669
|
+
if ('><'.includes(c)) return paint(C.ident, c);
|
|
670
|
+
if ('+-'.includes(c)) return paint(C.keyword, c);
|
|
671
|
+
if ('.,'.includes(c)) return paint(C.string, c);
|
|
672
|
+
if ('[]'.includes(c)) return paint(C.op, c);
|
|
673
|
+
return paint(C.comment, c);
|
|
674
|
+
}).join('');
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// Register built-ins
|
|
678
|
+
_reg.set('novac', _hlNovac);
|
|
679
|
+
_reg.set('nova', _hlNovac);
|
|
680
|
+
_reg.set('json', _hlJSON);
|
|
681
|
+
_reg.set('shell', _hlShell);
|
|
682
|
+
_reg.set('bash', _hlShell);
|
|
683
|
+
_reg.set('basic', _hlBasic);
|
|
684
|
+
_reg.set('bf', _hlBF);
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
/** Register a custom highlighter: fn(line: string) → string */
|
|
688
|
+
register(name, fn) { _reg.set(name, fn); },
|
|
689
|
+
/** Get a highlighter by name (or null) */
|
|
690
|
+
get(name) { return _reg.get(name) || null; },
|
|
691
|
+
/** Highlight a line with a named theme */
|
|
692
|
+
highlight(name, line) {
|
|
693
|
+
const fn = _reg.get(name);
|
|
694
|
+
return fn ? fn(line) : line;
|
|
695
|
+
},
|
|
696
|
+
/** List registered theme names */
|
|
697
|
+
list() { return [..._reg.keys()]; },
|
|
698
|
+
/** Raw colour constants for custom highlighters */
|
|
699
|
+
colors: C,
|
|
700
|
+
paint,
|
|
701
|
+
};
|
|
702
|
+
})();
|
|
703
|
+
|
|
704
|
+
// ── InputMgr — main export ────────────────────────────────────────────────────
|
|
705
|
+
const InputMgr = (() => {
|
|
706
|
+
|
|
707
|
+
// ── internal history manager ──────────────────────────────────────────────
|
|
708
|
+
//
|
|
709
|
+
// Histories are stored in hidden JSON files: `.184.<name>`
|
|
710
|
+
// The same format used by the existing `History` object in bstd, but
|
|
711
|
+
// InputMgr manages them automatically per prompt name.
|
|
712
|
+
//
|
|
713
|
+
const _histCache = new Map(); // name → array of strings (in-memory)
|
|
714
|
+
|
|
715
|
+
function _histKey(name) { return '.184.inputmgr.' + name; }
|
|
716
|
+
|
|
717
|
+
function _histLoad(name) {
|
|
718
|
+
if (_histCache.has(name)) return _histCache.get(name);
|
|
719
|
+
const key = _histKey(name);
|
|
720
|
+
let arr = [];
|
|
721
|
+
if (fs.existsSync(key)) {
|
|
722
|
+
try { arr = JSON.parse(fs.readFileSync(key, 'utf8')).history || []; }
|
|
723
|
+
catch (_) { arr = []; }
|
|
724
|
+
}
|
|
725
|
+
_histCache.set(name, arr);
|
|
726
|
+
return arr;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function _histSave(name, arr) {
|
|
730
|
+
_histCache.set(name, arr);
|
|
731
|
+
try { fs.writeFileSync(_histKey(name), JSON.stringify({ history: arr })); }
|
|
732
|
+
catch (_) { /* best-effort */ }
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function _histPush(name, line) {
|
|
736
|
+
if (!line || !line.trim()) return;
|
|
737
|
+
const arr = _histLoad(name);
|
|
738
|
+
// Deduplicate: remove previous identical entry, push to end
|
|
739
|
+
const idx = arr.lastIndexOf(line);
|
|
740
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
741
|
+
arr.push(line);
|
|
742
|
+
// Cap at 500 entries
|
|
743
|
+
if (arr.length > 500) arr.splice(0, arr.length - 500);
|
|
744
|
+
_histSave(name, arr);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// ── password hashing ────────────────────────────────────────────────────
|
|
748
|
+
//
|
|
749
|
+
// Returns: { raw, hash, algo, verify(input) }
|
|
750
|
+
//
|
|
751
|
+
function _hashPassword(raw, algo = 'sha256') {
|
|
752
|
+
const hashed = crypto.createHash(algo).update(raw).digest('hex');
|
|
753
|
+
return {
|
|
754
|
+
raw,
|
|
755
|
+
hash: hashed,
|
|
756
|
+
algo,
|
|
757
|
+
verify: (input) => crypto.createHash(algo).update(input).digest('hex') === hashed,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ── core prompt (sync) ──────────────────────────────────────────────────
|
|
762
|
+
//
|
|
763
|
+
// opts:
|
|
764
|
+
// password {boolean} — mask input with '•'
|
|
765
|
+
// mask {string} — custom mask char (default '•')
|
|
766
|
+
// historyKey {string|false} — key for history persistence (default 'default')
|
|
767
|
+
// highlight {string|fn|false} — theme name or custom fn
|
|
768
|
+
// placeholder{string} — grey hint when empty
|
|
769
|
+
// hash {string|false} — hash algorithm for password result (sha256)
|
|
770
|
+
//
|
|
771
|
+
function prompt(message = '> ', opts = {}) {
|
|
772
|
+
const {
|
|
773
|
+
password = false,
|
|
774
|
+
mask = '•',
|
|
775
|
+
historyKey = 'default',
|
|
776
|
+
highlight = false,
|
|
777
|
+
placeholder = '',
|
|
778
|
+
hash = false,
|
|
779
|
+
} = opts;
|
|
780
|
+
|
|
781
|
+
const hist = historyKey !== false ? _histLoad(String(historyKey)) : [];
|
|
782
|
+
const hlFn = highlight === false ? null
|
|
783
|
+
: typeof highlight === 'function' ? highlight
|
|
784
|
+
: SyntaxHighlight.get(String(highlight));
|
|
785
|
+
|
|
786
|
+
const maskChar = password ? String(mask) : null;
|
|
787
|
+
const result = _syncReadLine(message, { mask: maskChar, history: hist, highlight: hlFn, placeholder });
|
|
788
|
+
|
|
789
|
+
if (historyKey !== false && !password) _histPush(String(historyKey), result);
|
|
790
|
+
|
|
791
|
+
if (password && hash) return _hashPassword(result, typeof hash === 'string' ? hash : 'sha256');
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// ── promptAsync (async version of prompt) ──────────────────────────────
|
|
796
|
+
async function promptAsync(message = '> ', opts = {}) {
|
|
797
|
+
// Runs prompt in a microtask so it doesn't block the event-loop scheduler
|
|
798
|
+
// (on Node the synchronous stdin.read inside _syncReadLine is still
|
|
799
|
+
// blocking, but wrapping in a Promise lets callers await it naturally)
|
|
800
|
+
return new Promise((resolve) => {
|
|
801
|
+
setImmediate(() => resolve(prompt(message, opts)));
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// ── promptPassword (sync convenience) ──────────────────────────────────
|
|
806
|
+
function promptPassword(message = 'Password: ', algo = 'sha256') {
|
|
807
|
+
return prompt(message, { password: true, hash: algo });
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// ── promptPasswordAsync ─────────────────────────────────────────────────
|
|
811
|
+
async function promptPasswordAsync(message = 'Password: ', algo = 'sha256') {
|
|
812
|
+
return promptAsync(message, { password: true, hash: algo });
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// ── repl (sync) ─────────────────────────────────────────────────────────
|
|
816
|
+
//
|
|
817
|
+
// opts:
|
|
818
|
+
// prompt {string} — prompt string (default '> ')
|
|
819
|
+
// banner {string} — printed before loop starts
|
|
820
|
+
// historyKey {string} — history key (default 'repl')
|
|
821
|
+
// highlight {string|fn|false}
|
|
822
|
+
// eval {fn} — handler(line, ctx) → string|undefined
|
|
823
|
+
// return '__exit__' to quit
|
|
824
|
+
// exit {string[]} — words that quit the repl (default ['exit','quit','.q'])
|
|
825
|
+
// context {object} — arbitrary context passed to eval
|
|
826
|
+
// maxHistory {number} — max history size (default 500)
|
|
827
|
+
//
|
|
828
|
+
function repl(opts = {}) {
|
|
829
|
+
const {
|
|
830
|
+
prompt: promptStr = '> ',
|
|
831
|
+
banner: bannerStr = '',
|
|
832
|
+
historyKey: hk = 'repl',
|
|
833
|
+
highlight: hl = false,
|
|
834
|
+
eval: evalFn = null,
|
|
835
|
+
exit: exitWords = ['exit', 'quit', '.q'],
|
|
836
|
+
context: ctx = {},
|
|
837
|
+
maxHistory: _maxHist = 500,
|
|
838
|
+
} = opts;
|
|
839
|
+
|
|
840
|
+
if (bannerStr) process.stdout.write(bannerStr + '\n');
|
|
841
|
+
|
|
842
|
+
const hlFn = hl === false ? null
|
|
843
|
+
: typeof hl === 'function' ? hl
|
|
844
|
+
: SyntaxHighlight.get(String(hl));
|
|
845
|
+
|
|
846
|
+
while (true) {
|
|
847
|
+
const line = prompt(promptStr, { historyKey: hk, highlight: hlFn });
|
|
848
|
+
|
|
849
|
+
if (line === null) break; // EOF / Ctrl-D
|
|
850
|
+
const trimmed = line.trim();
|
|
851
|
+
if (!trimmed) continue;
|
|
852
|
+
if (exitWords.includes(trimmed.toLowerCase())) break;
|
|
853
|
+
|
|
854
|
+
let out;
|
|
855
|
+
if (evalFn) {
|
|
856
|
+
try { out = evalFn(trimmed, ctx); }
|
|
857
|
+
catch (e) { out = `\x1b[31mError: ${e && e.message ? e.message : e}\x1b[0m`; }
|
|
858
|
+
}
|
|
859
|
+
if (out === '__exit__') break;
|
|
860
|
+
if (out !== undefined && out !== null) process.stdout.write(String(out) + '\n');
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// ── replAsync ───────────────────────────────────────────────────────────
|
|
865
|
+
async function replAsync(opts = {}) {
|
|
866
|
+
const {
|
|
867
|
+
prompt: promptStr = '> ',
|
|
868
|
+
banner: bannerStr = '',
|
|
869
|
+
historyKey: hk = 'repl',
|
|
870
|
+
highlight: hl = false,
|
|
871
|
+
eval: evalFn = null,
|
|
872
|
+
exit: exitWords = ['exit', 'quit', '.q'],
|
|
873
|
+
context: ctx = {},
|
|
874
|
+
} = opts;
|
|
875
|
+
|
|
876
|
+
if (bannerStr) process.stdout.write(bannerStr + '\n');
|
|
877
|
+
|
|
878
|
+
const hlFn = hl === false ? null
|
|
879
|
+
: typeof hl === 'function' ? hl
|
|
880
|
+
: SyntaxHighlight.get(String(hl));
|
|
881
|
+
|
|
882
|
+
while (true) {
|
|
883
|
+
const line = await promptAsync(promptStr, { historyKey: hk, highlight: hlFn });
|
|
884
|
+
|
|
885
|
+
if (line === null) break;
|
|
886
|
+
const trimmed = line.trim();
|
|
887
|
+
if (!trimmed) continue;
|
|
888
|
+
if (exitWords.includes(trimmed.toLowerCase())) break;
|
|
889
|
+
|
|
890
|
+
let out;
|
|
891
|
+
if (evalFn) {
|
|
892
|
+
try {
|
|
893
|
+
const raw = evalFn(trimmed, ctx);
|
|
894
|
+
out = raw instanceof Promise ? await raw : raw;
|
|
895
|
+
} catch (e) {
|
|
896
|
+
out = `\x1b[31mError: ${e && e.message ? e.message : e}\x1b[0m`;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
if (out === '__exit__') break;
|
|
900
|
+
if (out !== undefined && out !== null) process.stdout.write(String(out) + '\n');
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// ── mouse — terminal mouse / click events ───────────────────────────────
|
|
905
|
+
//
|
|
906
|
+
// Enables SGR mouse reporting and calls cb({ type, button, x, y, ... })
|
|
907
|
+
// for every mouse event. Returns a stop() function.
|
|
908
|
+
//
|
|
909
|
+
// opts:
|
|
910
|
+
// buttons {boolean} — report button press/release (default true)
|
|
911
|
+
// motion {boolean} — report mouse motion (default false)
|
|
912
|
+
// scroll {boolean} — report scroll events (default true)
|
|
913
|
+
//
|
|
914
|
+
function mouse(cb, opts = {}) {
|
|
915
|
+
const { buttons = true, motion = false, scroll = true } = opts;
|
|
916
|
+
|
|
917
|
+
const stdin = process.stdin;
|
|
918
|
+
const stdout = process.stdout;
|
|
919
|
+
|
|
920
|
+
// Enable mouse
|
|
921
|
+
stdout.write(_ansi.mouseOn);
|
|
922
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
923
|
+
stdin.resume();
|
|
924
|
+
|
|
925
|
+
function onData(raw) {
|
|
926
|
+
// SGR mouse: ESC [ < Pb ; Px ; Py M/m
|
|
927
|
+
const sgr = raw.toString().match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
928
|
+
if (sgr) {
|
|
929
|
+
const btn = parseInt(sgr[1], 10);
|
|
930
|
+
const x = parseInt(sgr[2], 10) - 1;
|
|
931
|
+
const y = parseInt(sgr[3], 10) - 1;
|
|
932
|
+
const release = sgr[4] === 'm';
|
|
933
|
+
const button = btn & 3;
|
|
934
|
+
const isScroll = btn >= 64;
|
|
935
|
+
const isMotion = !!(btn & 32);
|
|
936
|
+
|
|
937
|
+
if (!scroll && isScroll) return;
|
|
938
|
+
if (!motion && isMotion && !release) return;
|
|
939
|
+
if (!buttons && !isScroll && !isMotion) return;
|
|
940
|
+
|
|
941
|
+
cb({
|
|
942
|
+
type: release ? 'mouseup' : (isScroll ? 'scroll' : (isMotion ? 'mousemove' : 'mousedown')),
|
|
943
|
+
button,
|
|
944
|
+
x, y,
|
|
945
|
+
shift: !!(btn & 4),
|
|
946
|
+
alt: !!(btn & 8),
|
|
947
|
+
ctrl: !!(btn & 16),
|
|
948
|
+
direction: isScroll ? ((btn & 1) ? 'down' : 'up') : null,
|
|
949
|
+
raw: { btn, x, y, release },
|
|
950
|
+
});
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Ctrl-C passthrough so user can still quit
|
|
955
|
+
if (raw.includes('\x03')) {
|
|
956
|
+
stop();
|
|
957
|
+
stdout.write('\n');
|
|
958
|
+
process.exit(130);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
stdin.on('data', onData);
|
|
963
|
+
|
|
964
|
+
function stop() {
|
|
965
|
+
stdout.write(_ansi.mouseOff);
|
|
966
|
+
stdin.removeListener('data', onData);
|
|
967
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
968
|
+
stdin.pause();
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
return stop;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// ── history helpers exposed to Nova scripts ─────────────────────────────
|
|
975
|
+
const historyAPI = {
|
|
976
|
+
/** Load history array for a given key */
|
|
977
|
+
load: (name) => _histLoad(String(name)),
|
|
978
|
+
/** Save a history array for a given key */
|
|
979
|
+
save: (name, arr) => _histSave(String(name), arr),
|
|
980
|
+
/** Push a single entry to a history key */
|
|
981
|
+
push: (name, entry) => _histPush(String(name), String(entry)),
|
|
982
|
+
/** Clear history for a key */
|
|
983
|
+
clear: (name) => _histSave(String(name), []),
|
|
984
|
+
/** List all history keys managed by InputMgr */
|
|
985
|
+
keys: () => {
|
|
986
|
+
try {
|
|
987
|
+
return fs.readdirSync('.').filter(f => f.startsWith('.184.inputmgr.'))
|
|
988
|
+
.map(f => f.slice('.184.inputmgr.'.length));
|
|
989
|
+
} catch (_) { return []; }
|
|
990
|
+
},
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
return {
|
|
994
|
+
// ── Sync API (primary) ────────────────────────────────────────────────
|
|
995
|
+
prompt,
|
|
996
|
+
promptPassword,
|
|
997
|
+
repl,
|
|
998
|
+
|
|
999
|
+
// ── Async API ─────────────────────────────────────────────────────────
|
|
1000
|
+
promptAsync,
|
|
1001
|
+
promptPasswordAsync,
|
|
1002
|
+
replAsync,
|
|
1003
|
+
|
|
1004
|
+
// ── Mouse / terminal events ────────────────────────────────────────────
|
|
1005
|
+
mouse,
|
|
1006
|
+
|
|
1007
|
+
// ── Syntax highlighting ────────────────────────────────────────────────
|
|
1008
|
+
SyntaxHighlight,
|
|
1009
|
+
highlight: SyntaxHighlight.highlight.bind(SyntaxHighlight),
|
|
1010
|
+
|
|
1011
|
+
// ── Password hashing utility (standalone) ─────────────────────────────
|
|
1012
|
+
hashPassword: _hashPassword,
|
|
1013
|
+
|
|
1014
|
+
// ── History management ────────────────────────────────────────────────
|
|
1015
|
+
history: historyAPI,
|
|
1016
|
+
};
|
|
1017
|
+
})();
|
|
1018
|
+
|
|
1019
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
1020
|
+
// Module exports
|
|
1021
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
1022
|
+
module.exports = {
|
|
1023
|
+
// ── Original exports (all preserved) ──────────────────────────────────
|
|
1024
|
+
Air, Chalk, History,
|
|
1025
|
+
Shema, ShemaType,
|
|
1026
|
+
CustomError, formatError, NovaException,
|
|
1027
|
+
ConfigFile,
|
|
1028
|
+
JsDB, Storage, Zip, Crypto,
|
|
1029
|
+
PathUtils, Base64,
|
|
1030
|
+
MED: MathExprDebugger,
|
|
1031
|
+
|
|
1032
|
+
// ── New: InputMgr + SyntaxHighlight ───────────────────────────────────
|
|
1033
|
+
InputMgr,
|
|
1034
|
+
SyntaxHighlight,
|
|
1035
|
+
};
|