starlight-cli 1.0.2 → 1.0.4
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/dist/index.js +2556 -0
- package/dist/read.cs.js +123 -0
- package/dist/read.ps1 +128 -0
- package/dist/read.sh +137 -0
- package/index.js +3 -5
- package/install.js +3 -6
- package/package.json +10 -3
- package/src/evaluator.js +400 -0
- package/src/lexer.js +157 -0
- package/src/parser.js +441 -0
- package/src/starlight.js +117 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2556 @@
|
|
|
1
|
+
/******/ (() => { // webpackBootstrap
|
|
2
|
+
/******/ var __webpack_modules__ = ({
|
|
3
|
+
|
|
4
|
+
/***/ 552:
|
|
5
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
/*
|
|
9
|
+
* readlineSync
|
|
10
|
+
* https://github.com/anseki/readline-sync
|
|
11
|
+
*
|
|
12
|
+
* Copyright (c) 2019 anseki
|
|
13
|
+
* Licensed under the MIT license.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
var
|
|
19
|
+
IS_WIN = process.platform === 'win32',
|
|
20
|
+
|
|
21
|
+
ALGORITHM_CIPHER = 'aes-256-cbc',
|
|
22
|
+
ALGORITHM_HASH = 'sha256',
|
|
23
|
+
DEFAULT_ERR_MSG = 'The current environment doesn\'t support interactive reading from TTY.',
|
|
24
|
+
|
|
25
|
+
fs = __nccwpck_require__(896),
|
|
26
|
+
TTY = process.binding('tty_wrap').TTY,
|
|
27
|
+
childProc = __nccwpck_require__(317),
|
|
28
|
+
pathUtil = __nccwpck_require__(928),
|
|
29
|
+
|
|
30
|
+
defaultOptions = {
|
|
31
|
+
/* eslint-disable key-spacing */
|
|
32
|
+
prompt: '> ',
|
|
33
|
+
hideEchoBack: false,
|
|
34
|
+
mask: '*',
|
|
35
|
+
limit: [],
|
|
36
|
+
limitMessage: 'Input another, please.$<( [)limit(])>',
|
|
37
|
+
defaultInput: '',
|
|
38
|
+
trueValue: [],
|
|
39
|
+
falseValue: [],
|
|
40
|
+
caseSensitive: false,
|
|
41
|
+
keepWhitespace: false,
|
|
42
|
+
encoding: 'utf8',
|
|
43
|
+
bufferSize: 1024,
|
|
44
|
+
print: void 0,
|
|
45
|
+
history: true,
|
|
46
|
+
cd: false,
|
|
47
|
+
phContent: void 0,
|
|
48
|
+
preCheck: void 0
|
|
49
|
+
/* eslint-enable key-spacing */
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
fdR = 'none',
|
|
53
|
+
isRawMode = false,
|
|
54
|
+
salt = 0,
|
|
55
|
+
lastInput = '',
|
|
56
|
+
inputHistory = [],
|
|
57
|
+
_DBG_useExt = false,
|
|
58
|
+
_DBG_checkOptions = false,
|
|
59
|
+
_DBG_checkMethod = false,
|
|
60
|
+
fdW, ttyR, extHostPath, extHostArgs, tempdir, rawInput;
|
|
61
|
+
|
|
62
|
+
function getHostArgs(options) {
|
|
63
|
+
// Send any text to crazy Windows shell safely.
|
|
64
|
+
function encodeArg(arg) {
|
|
65
|
+
return arg.replace(/[^\w\u0080-\uFFFF]/g, function(chr) {
|
|
66
|
+
return '#' + chr.charCodeAt(0) + ';';
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return extHostArgs.concat((function(conf) {
|
|
71
|
+
var args = [];
|
|
72
|
+
Object.keys(conf).forEach(function(optionName) {
|
|
73
|
+
if (conf[optionName] === 'boolean') {
|
|
74
|
+
if (options[optionName]) { args.push('--' + optionName); }
|
|
75
|
+
} else if (conf[optionName] === 'string') {
|
|
76
|
+
if (options[optionName]) {
|
|
77
|
+
args.push('--' + optionName, encodeArg(options[optionName]));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return args;
|
|
82
|
+
})({
|
|
83
|
+
/* eslint-disable key-spacing */
|
|
84
|
+
display: 'string',
|
|
85
|
+
displayOnly: 'boolean',
|
|
86
|
+
keyIn: 'boolean',
|
|
87
|
+
hideEchoBack: 'boolean',
|
|
88
|
+
mask: 'string',
|
|
89
|
+
limit: 'string',
|
|
90
|
+
caseSensitive: 'boolean'
|
|
91
|
+
/* eslint-enable key-spacing */
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// piping via files (for Node.js v0.10-)
|
|
96
|
+
function _execFileSync(options, execOptions) {
|
|
97
|
+
|
|
98
|
+
function getTempfile(name) {
|
|
99
|
+
var suffix = '',
|
|
100
|
+
filepath, fd;
|
|
101
|
+
tempdir = tempdir || (__nccwpck_require__(857).tmpdir)();
|
|
102
|
+
|
|
103
|
+
while (true) {
|
|
104
|
+
filepath = pathUtil.join(tempdir, name + suffix);
|
|
105
|
+
try {
|
|
106
|
+
fd = fs.openSync(filepath, 'wx');
|
|
107
|
+
} catch (e) {
|
|
108
|
+
if (e.code === 'EEXIST') {
|
|
109
|
+
suffix++;
|
|
110
|
+
continue;
|
|
111
|
+
} else {
|
|
112
|
+
throw e;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
fs.closeSync(fd);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
return filepath;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
var res = {},
|
|
122
|
+
pathStdout = getTempfile('readline-sync.stdout'),
|
|
123
|
+
pathStderr = getTempfile('readline-sync.stderr'),
|
|
124
|
+
pathExit = getTempfile('readline-sync.exit'),
|
|
125
|
+
pathDone = getTempfile('readline-sync.done'),
|
|
126
|
+
crypto = __nccwpck_require__(982),
|
|
127
|
+
hostArgs, shellPath, shellArgs, exitCode, extMessage, shasum, decipher, password;
|
|
128
|
+
|
|
129
|
+
shasum = crypto.createHash(ALGORITHM_HASH);
|
|
130
|
+
shasum.update('' + process.pid + (salt++) + Math.random());
|
|
131
|
+
password = shasum.digest('hex');
|
|
132
|
+
decipher = crypto.createDecipher(ALGORITHM_CIPHER, password);
|
|
133
|
+
|
|
134
|
+
hostArgs = getHostArgs(options);
|
|
135
|
+
if (IS_WIN) {
|
|
136
|
+
shellPath = process.env.ComSpec || 'cmd.exe';
|
|
137
|
+
process.env.Q = '"'; // The quote (") that isn't escaped.
|
|
138
|
+
// `()` for ignore space by echo
|
|
139
|
+
shellArgs = ['/V:ON', '/S', '/C',
|
|
140
|
+
'(%Q%' + shellPath + '%Q% /V:ON /S /C %Q%' + /* ESLint bug? */ // eslint-disable-line no-path-concat
|
|
141
|
+
'%Q%' + extHostPath + '%Q%' +
|
|
142
|
+
hostArgs.map(function(arg) { return ' %Q%' + arg + '%Q%'; }).join('') +
|
|
143
|
+
' & (echo !ERRORLEVEL!)>%Q%' + pathExit + '%Q%%Q%) 2>%Q%' + pathStderr + '%Q%' +
|
|
144
|
+
' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' +
|
|
145
|
+
' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' +
|
|
146
|
+
' >%Q%' + pathStdout + '%Q%' +
|
|
147
|
+
' & (echo 1)>%Q%' + pathDone + '%Q%'];
|
|
148
|
+
} else {
|
|
149
|
+
shellPath = '/bin/sh';
|
|
150
|
+
shellArgs = ['-c',
|
|
151
|
+
// Use `()`, not `{}` for `-c` (text param)
|
|
152
|
+
'("' + extHostPath + '"' + /* ESLint bug? */ // eslint-disable-line no-path-concat
|
|
153
|
+
hostArgs.map(function(arg) { return " '" + arg.replace(/'/g, "'\\''") + "'"; }).join('') +
|
|
154
|
+
'; echo $?>"' + pathExit + '") 2>"' + pathStderr + '"' +
|
|
155
|
+
' |"' + process.execPath + '" "' + __dirname + '/encrypt.js"' +
|
|
156
|
+
' "' + ALGORITHM_CIPHER + '" "' + password + '"' +
|
|
157
|
+
' >"' + pathStdout + '"' +
|
|
158
|
+
'; echo 1 >"' + pathDone + '"'];
|
|
159
|
+
}
|
|
160
|
+
if (_DBG_checkMethod) { _DBG_checkMethod('_execFileSync', hostArgs); }
|
|
161
|
+
try {
|
|
162
|
+
childProc.spawn(shellPath, shellArgs, execOptions);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
res.error = new Error(e.message);
|
|
165
|
+
res.error.method = '_execFileSync - spawn';
|
|
166
|
+
res.error.program = shellPath;
|
|
167
|
+
res.error.args = shellArgs;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
while (fs.readFileSync(pathDone, {encoding: options.encoding}).trim() !== '1') {} // eslint-disable-line no-empty
|
|
171
|
+
if ((exitCode =
|
|
172
|
+
fs.readFileSync(pathExit, {encoding: options.encoding}).trim()) === '0') {
|
|
173
|
+
res.input =
|
|
174
|
+
decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}),
|
|
175
|
+
'hex', options.encoding) +
|
|
176
|
+
decipher.final(options.encoding);
|
|
177
|
+
} else {
|
|
178
|
+
extMessage = fs.readFileSync(pathStderr, {encoding: options.encoding}).trim();
|
|
179
|
+
res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : ''));
|
|
180
|
+
res.error.method = '_execFileSync';
|
|
181
|
+
res.error.program = shellPath;
|
|
182
|
+
res.error.args = shellArgs;
|
|
183
|
+
res.error.extMessage = extMessage;
|
|
184
|
+
res.error.exitCode = +exitCode;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fs.unlinkSync(pathStdout);
|
|
188
|
+
fs.unlinkSync(pathStderr);
|
|
189
|
+
fs.unlinkSync(pathExit);
|
|
190
|
+
fs.unlinkSync(pathDone);
|
|
191
|
+
|
|
192
|
+
return res;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function readlineExt(options) {
|
|
196
|
+
var res = {},
|
|
197
|
+
execOptions = {env: process.env, encoding: options.encoding},
|
|
198
|
+
hostArgs, extMessage;
|
|
199
|
+
|
|
200
|
+
if (!extHostPath) {
|
|
201
|
+
if (IS_WIN) {
|
|
202
|
+
if (process.env.PSModulePath) { // Windows PowerShell
|
|
203
|
+
extHostPath = 'powershell.exe';
|
|
204
|
+
extHostArgs = ['-ExecutionPolicy', 'Bypass',
|
|
205
|
+
'-File', __nccwpck_require__.ab + "read.ps1"]; // eslint-disable-line no-path-concat
|
|
206
|
+
} else { // Windows Script Host
|
|
207
|
+
extHostPath = 'cscript.exe';
|
|
208
|
+
extHostArgs = ['//nologo', __nccwpck_require__.ab + "read.cs.js"]; // eslint-disable-line no-path-concat
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
extHostPath = '/bin/sh';
|
|
212
|
+
extHostArgs = [__nccwpck_require__.ab + "read.sh"]; // eslint-disable-line no-path-concat
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (IS_WIN && !process.env.PSModulePath) { // Windows Script Host
|
|
216
|
+
// ScriptPW (Win XP and Server2003) needs TTY stream as STDIN.
|
|
217
|
+
// In this case, If STDIN isn't TTY, an error is thrown.
|
|
218
|
+
execOptions.stdio = [process.stdin];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (childProc.execFileSync) {
|
|
222
|
+
hostArgs = getHostArgs(options);
|
|
223
|
+
if (_DBG_checkMethod) { _DBG_checkMethod('execFileSync', hostArgs); }
|
|
224
|
+
try {
|
|
225
|
+
res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions);
|
|
226
|
+
} catch (e) { // non-zero exit code
|
|
227
|
+
extMessage = e.stderr ? (e.stderr + '').trim() : '';
|
|
228
|
+
res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : ''));
|
|
229
|
+
res.error.method = 'execFileSync';
|
|
230
|
+
res.error.program = extHostPath;
|
|
231
|
+
res.error.args = hostArgs;
|
|
232
|
+
res.error.extMessage = extMessage;
|
|
233
|
+
res.error.exitCode = e.status;
|
|
234
|
+
res.error.code = e.code;
|
|
235
|
+
res.error.signal = e.signal;
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
res = _execFileSync(options, execOptions);
|
|
239
|
+
}
|
|
240
|
+
if (!res.error) {
|
|
241
|
+
res.input = res.input.replace(/^\s*'|'\s*$/g, '');
|
|
242
|
+
options.display = '';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return res;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
display: string
|
|
250
|
+
displayOnly: boolean
|
|
251
|
+
keyIn: boolean
|
|
252
|
+
hideEchoBack: boolean
|
|
253
|
+
mask: string
|
|
254
|
+
limit: string (pattern)
|
|
255
|
+
caseSensitive: boolean
|
|
256
|
+
keepWhitespace: boolean
|
|
257
|
+
encoding, bufferSize, print
|
|
258
|
+
*/
|
|
259
|
+
function _readlineSync(options) {
|
|
260
|
+
var input = '',
|
|
261
|
+
displaySave = options.display,
|
|
262
|
+
silent = !options.display && options.keyIn && options.hideEchoBack && !options.mask;
|
|
263
|
+
|
|
264
|
+
function tryExt() {
|
|
265
|
+
var res = readlineExt(options);
|
|
266
|
+
if (res.error) { throw res.error; }
|
|
267
|
+
return res.input;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (_DBG_checkOptions) { _DBG_checkOptions(options); }
|
|
271
|
+
|
|
272
|
+
(function() { // open TTY
|
|
273
|
+
var fsB, constants, verNum;
|
|
274
|
+
|
|
275
|
+
function getFsB() {
|
|
276
|
+
if (!fsB) {
|
|
277
|
+
fsB = process.binding('fs'); // For raw device path
|
|
278
|
+
constants = process.binding('constants');
|
|
279
|
+
// for v6.3.0+
|
|
280
|
+
constants = constants && constants.fs && typeof constants.fs.O_RDWR === 'number'
|
|
281
|
+
? constants.fs : constants;
|
|
282
|
+
}
|
|
283
|
+
return fsB;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (typeof fdR !== 'string') { return; }
|
|
287
|
+
fdR = null;
|
|
288
|
+
|
|
289
|
+
if (IS_WIN) {
|
|
290
|
+
// iojs-v2.3.2+ input stream can't read first line. (#18)
|
|
291
|
+
// ** Don't get process.stdin before check! **
|
|
292
|
+
// Fixed v5.1.0
|
|
293
|
+
// Fixed v4.2.4
|
|
294
|
+
// It regressed again in v5.6.0, it is fixed in v6.2.0.
|
|
295
|
+
verNum = (function(ver) { // getVerNum
|
|
296
|
+
var nums = ver.replace(/^\D+/, '').split('.');
|
|
297
|
+
var verNum = 0;
|
|
298
|
+
if ((nums[0] = +nums[0])) { verNum += nums[0] * 10000; }
|
|
299
|
+
if ((nums[1] = +nums[1])) { verNum += nums[1] * 100; }
|
|
300
|
+
if ((nums[2] = +nums[2])) { verNum += nums[2]; }
|
|
301
|
+
return verNum;
|
|
302
|
+
})(process.version);
|
|
303
|
+
if (!(verNum >= 20302 && verNum < 40204 || verNum >= 50000 && verNum < 50100 || verNum >= 50600 && verNum < 60200) &&
|
|
304
|
+
process.stdin.isTTY) {
|
|
305
|
+
process.stdin.pause();
|
|
306
|
+
fdR = process.stdin.fd;
|
|
307
|
+
ttyR = process.stdin._handle;
|
|
308
|
+
} else {
|
|
309
|
+
try {
|
|
310
|
+
// The stream by fs.openSync('\\\\.\\CON', 'r') can't switch to raw mode.
|
|
311
|
+
// 'CONIN$' might fail on XP, 2000, 7 (x86).
|
|
312
|
+
fdR = getFsB().open('CONIN$', constants.O_RDWR, parseInt('0666', 8));
|
|
313
|
+
ttyR = new TTY(fdR, true);
|
|
314
|
+
} catch (e) { /* ignore */ }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (process.stdout.isTTY) {
|
|
318
|
+
fdW = process.stdout.fd;
|
|
319
|
+
} else {
|
|
320
|
+
try {
|
|
321
|
+
fdW = fs.openSync('\\\\.\\CON', 'w');
|
|
322
|
+
} catch (e) { /* ignore */ }
|
|
323
|
+
if (typeof fdW !== 'number') { // Retry
|
|
324
|
+
try {
|
|
325
|
+
fdW = getFsB().open('CONOUT$', constants.O_RDWR, parseInt('0666', 8));
|
|
326
|
+
} catch (e) { /* ignore */ }
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
} else {
|
|
331
|
+
if (process.stdin.isTTY) {
|
|
332
|
+
process.stdin.pause();
|
|
333
|
+
try {
|
|
334
|
+
fdR = fs.openSync('/dev/tty', 'r'); // device file, not process.stdin
|
|
335
|
+
ttyR = process.stdin._handle;
|
|
336
|
+
} catch (e) { /* ignore */ }
|
|
337
|
+
} else {
|
|
338
|
+
// Node.js v0.12 read() fails.
|
|
339
|
+
try {
|
|
340
|
+
fdR = fs.openSync('/dev/tty', 'r');
|
|
341
|
+
ttyR = new TTY(fdR, false);
|
|
342
|
+
} catch (e) { /* ignore */ }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (process.stdout.isTTY) {
|
|
346
|
+
fdW = process.stdout.fd;
|
|
347
|
+
} else {
|
|
348
|
+
try {
|
|
349
|
+
fdW = fs.openSync('/dev/tty', 'w');
|
|
350
|
+
} catch (e) { /* ignore */ }
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
})();
|
|
354
|
+
|
|
355
|
+
(function() { // try read
|
|
356
|
+
var isCooked = !options.hideEchoBack && !options.keyIn,
|
|
357
|
+
atEol, limit, buffer, reqSize, readSize, chunk, line;
|
|
358
|
+
rawInput = '';
|
|
359
|
+
|
|
360
|
+
// Node.js v0.10- returns an error if same mode is set.
|
|
361
|
+
function setRawMode(mode) {
|
|
362
|
+
if (mode === isRawMode) { return true; }
|
|
363
|
+
if (ttyR.setRawMode(mode) !== 0) { return false; }
|
|
364
|
+
isRawMode = mode;
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (_DBG_useExt || !ttyR ||
|
|
369
|
+
typeof fdW !== 'number' && (options.display || !isCooked)) {
|
|
370
|
+
input = tryExt();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (options.display) {
|
|
375
|
+
fs.writeSync(fdW, options.display);
|
|
376
|
+
options.display = '';
|
|
377
|
+
}
|
|
378
|
+
if (options.displayOnly) { return; }
|
|
379
|
+
|
|
380
|
+
if (!setRawMode(!isCooked)) {
|
|
381
|
+
input = tryExt();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
reqSize = options.keyIn ? 1 : options.bufferSize;
|
|
386
|
+
// Check `allocUnsafe` to make sure of the new API.
|
|
387
|
+
buffer = Buffer.allocUnsafe && Buffer.alloc ? Buffer.alloc(reqSize) : new Buffer(reqSize);
|
|
388
|
+
|
|
389
|
+
if (options.keyIn && options.limit) {
|
|
390
|
+
limit = new RegExp('[^' + options.limit + ']',
|
|
391
|
+
'g' + (options.caseSensitive ? '' : 'i'));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
while (true) {
|
|
395
|
+
readSize = 0;
|
|
396
|
+
try {
|
|
397
|
+
readSize = fs.readSync(fdR, buffer, 0, reqSize);
|
|
398
|
+
} catch (e) {
|
|
399
|
+
if (e.code !== 'EOF') {
|
|
400
|
+
setRawMode(false);
|
|
401
|
+
input += tryExt();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (readSize > 0) {
|
|
406
|
+
chunk = buffer.toString(options.encoding, 0, readSize);
|
|
407
|
+
rawInput += chunk;
|
|
408
|
+
} else {
|
|
409
|
+
chunk = '\n';
|
|
410
|
+
rawInput += String.fromCharCode(0);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (chunk && typeof (line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') {
|
|
414
|
+
chunk = line;
|
|
415
|
+
atEol = true;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// other ctrl-chars
|
|
419
|
+
// eslint-disable-next-line no-control-regex
|
|
420
|
+
if (chunk) { chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); }
|
|
421
|
+
if (chunk && limit) { chunk = chunk.replace(limit, ''); }
|
|
422
|
+
|
|
423
|
+
if (chunk) {
|
|
424
|
+
if (!isCooked) {
|
|
425
|
+
if (!options.hideEchoBack) {
|
|
426
|
+
fs.writeSync(fdW, chunk);
|
|
427
|
+
} else if (options.mask) {
|
|
428
|
+
fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
input += chunk;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (!options.keyIn && atEol ||
|
|
435
|
+
options.keyIn && input.length >= reqSize) { break; }
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (!isCooked && !silent) { fs.writeSync(fdW, '\n'); }
|
|
439
|
+
setRawMode(false);
|
|
440
|
+
})();
|
|
441
|
+
|
|
442
|
+
if (options.print && !silent) {
|
|
443
|
+
options.print(
|
|
444
|
+
displaySave + (
|
|
445
|
+
options.displayOnly ? '' : (
|
|
446
|
+
options.hideEchoBack ? (new Array(input.length + 1)).join(options.mask) : input
|
|
447
|
+
) + '\n' // must at least write '\n'
|
|
448
|
+
),
|
|
449
|
+
options.encoding);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return options.displayOnly ? '' :
|
|
453
|
+
(lastInput = options.keepWhitespace || options.keyIn ? input : input.trim());
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function flattenArray(array, validator) {
|
|
457
|
+
var flatArray = [];
|
|
458
|
+
function _flattenArray(array) {
|
|
459
|
+
if (array == null) { return; }
|
|
460
|
+
if (Array.isArray(array)) {
|
|
461
|
+
array.forEach(_flattenArray);
|
|
462
|
+
} else if (!validator || validator(array)) {
|
|
463
|
+
flatArray.push(array);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
_flattenArray(array);
|
|
467
|
+
return flatArray;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function escapePattern(pattern) {
|
|
471
|
+
return pattern.replace(/[\x00-\x7f]/g, // eslint-disable-line no-control-regex
|
|
472
|
+
function(s) { return '\\x' + ('00' + s.charCodeAt().toString(16)).substr(-2); });
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// margeOptions(options1, options2 ... )
|
|
476
|
+
// margeOptions(true, options1, options2 ... )
|
|
477
|
+
// arg1=true : Start from defaultOptions and pick elements of that.
|
|
478
|
+
function margeOptions() {
|
|
479
|
+
var optionsList = Array.prototype.slice.call(arguments),
|
|
480
|
+
optionNames, fromDefault;
|
|
481
|
+
|
|
482
|
+
if (optionsList.length && typeof optionsList[0] === 'boolean') {
|
|
483
|
+
fromDefault = optionsList.shift();
|
|
484
|
+
if (fromDefault) {
|
|
485
|
+
optionNames = Object.keys(defaultOptions);
|
|
486
|
+
optionsList.unshift(defaultOptions);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return optionsList.reduce(function(options, optionsPart) {
|
|
491
|
+
if (optionsPart == null) { return options; }
|
|
492
|
+
|
|
493
|
+
// ======== DEPRECATED ========
|
|
494
|
+
if (optionsPart.hasOwnProperty('noEchoBack') &&
|
|
495
|
+
!optionsPart.hasOwnProperty('hideEchoBack')) {
|
|
496
|
+
optionsPart.hideEchoBack = optionsPart.noEchoBack;
|
|
497
|
+
delete optionsPart.noEchoBack;
|
|
498
|
+
}
|
|
499
|
+
if (optionsPart.hasOwnProperty('noTrim') &&
|
|
500
|
+
!optionsPart.hasOwnProperty('keepWhitespace')) {
|
|
501
|
+
optionsPart.keepWhitespace = optionsPart.noTrim;
|
|
502
|
+
delete optionsPart.noTrim;
|
|
503
|
+
}
|
|
504
|
+
// ======== /DEPRECATED ========
|
|
505
|
+
|
|
506
|
+
if (!fromDefault) { optionNames = Object.keys(optionsPart); }
|
|
507
|
+
optionNames.forEach(function(optionName) {
|
|
508
|
+
var value;
|
|
509
|
+
if (!optionsPart.hasOwnProperty(optionName)) { return; }
|
|
510
|
+
value = optionsPart[optionName];
|
|
511
|
+
/* eslint-disable no-multi-spaces */
|
|
512
|
+
switch (optionName) {
|
|
513
|
+
// _readlineSync <- * * -> defaultOptions
|
|
514
|
+
// ================ string
|
|
515
|
+
case 'mask': // * *
|
|
516
|
+
case 'limitMessage': // *
|
|
517
|
+
case 'defaultInput': // *
|
|
518
|
+
case 'encoding': // * *
|
|
519
|
+
value = value != null ? value + '' : '';
|
|
520
|
+
if (value && optionName !== 'limitMessage') { value = value.replace(/[\r\n]/g, ''); }
|
|
521
|
+
options[optionName] = value;
|
|
522
|
+
break;
|
|
523
|
+
// ================ number(int)
|
|
524
|
+
case 'bufferSize': // * *
|
|
525
|
+
if (!isNaN(value = parseInt(value, 10)) && typeof value === 'number') {
|
|
526
|
+
options[optionName] = value; // limited updating (number is needed)
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
// ================ boolean
|
|
530
|
+
case 'displayOnly': // *
|
|
531
|
+
case 'keyIn': // *
|
|
532
|
+
case 'hideEchoBack': // * *
|
|
533
|
+
case 'caseSensitive': // * *
|
|
534
|
+
case 'keepWhitespace': // * *
|
|
535
|
+
case 'history': // *
|
|
536
|
+
case 'cd': // *
|
|
537
|
+
options[optionName] = !!value;
|
|
538
|
+
break;
|
|
539
|
+
// ================ array
|
|
540
|
+
case 'limit': // * * to string for readlineExt
|
|
541
|
+
case 'trueValue': // *
|
|
542
|
+
case 'falseValue': // *
|
|
543
|
+
options[optionName] = flattenArray(value, function(value) {
|
|
544
|
+
var type = typeof value;
|
|
545
|
+
return type === 'string' || type === 'number' ||
|
|
546
|
+
type === 'function' || value instanceof RegExp;
|
|
547
|
+
}).map(function(value) {
|
|
548
|
+
return typeof value === 'string' ? value.replace(/[\r\n]/g, '') : value;
|
|
549
|
+
});
|
|
550
|
+
break;
|
|
551
|
+
// ================ function
|
|
552
|
+
case 'print': // * *
|
|
553
|
+
case 'phContent': // *
|
|
554
|
+
case 'preCheck': // *
|
|
555
|
+
options[optionName] = typeof value === 'function' ? value : void 0;
|
|
556
|
+
break;
|
|
557
|
+
// ================ other
|
|
558
|
+
case 'prompt': // *
|
|
559
|
+
case 'display': // *
|
|
560
|
+
options[optionName] = value != null ? value : '';
|
|
561
|
+
break;
|
|
562
|
+
// no default
|
|
563
|
+
}
|
|
564
|
+
/* eslint-enable no-multi-spaces */
|
|
565
|
+
});
|
|
566
|
+
return options;
|
|
567
|
+
}, {});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function isMatched(res, comps, caseSensitive) {
|
|
571
|
+
return comps.some(function(comp) {
|
|
572
|
+
var type = typeof comp;
|
|
573
|
+
return type === 'string'
|
|
574
|
+
? (caseSensitive ? res === comp : res.toLowerCase() === comp.toLowerCase()) :
|
|
575
|
+
type === 'number' ? parseFloat(res) === comp :
|
|
576
|
+
type === 'function' ? comp(res) :
|
|
577
|
+
comp instanceof RegExp ? comp.test(res) : false;
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function replaceHomePath(path, expand) {
|
|
582
|
+
var homePath = pathUtil.normalize(
|
|
583
|
+
IS_WIN ? (process.env.HOMEDRIVE || '') + (process.env.HOMEPATH || '') :
|
|
584
|
+
process.env.HOME || '').replace(/[/\\]+$/, '');
|
|
585
|
+
path = pathUtil.normalize(path);
|
|
586
|
+
return expand ? path.replace(/^~(?=\/|\\|$)/, homePath) :
|
|
587
|
+
path.replace(new RegExp('^' + escapePattern(homePath) +
|
|
588
|
+
'(?=\\/|\\\\|$)', IS_WIN ? 'i' : ''), '~');
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function replacePlaceholder(text, generator) {
|
|
592
|
+
var PTN_INNER = '(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?',
|
|
593
|
+
rePlaceholder = new RegExp('(\\$)?(\\$<' + PTN_INNER + '>)', 'g'),
|
|
594
|
+
rePlaceholderCompat = new RegExp('(\\$)?(\\$\\{' + PTN_INNER + '\\})', 'g');
|
|
595
|
+
|
|
596
|
+
function getPlaceholderText(s, escape, placeholder, pre, param, post) {
|
|
597
|
+
var text;
|
|
598
|
+
return escape || typeof (text = generator(param)) !== 'string' ? placeholder :
|
|
599
|
+
text ? (pre || '') + text + (post || '') : '';
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return text.replace(rePlaceholder, getPlaceholderText)
|
|
603
|
+
.replace(rePlaceholderCompat, getPlaceholderText);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function array2charlist(array, caseSensitive, collectSymbols) {
|
|
607
|
+
var group = [],
|
|
608
|
+
groupClass = -1,
|
|
609
|
+
charCode = 0,
|
|
610
|
+
symbols = '',
|
|
611
|
+
values, suppressed;
|
|
612
|
+
function addGroup(groups, group) {
|
|
613
|
+
if (group.length > 3) { // ellipsis
|
|
614
|
+
groups.push(group[0] + '...' + group[group.length - 1]);
|
|
615
|
+
suppressed = true;
|
|
616
|
+
} else if (group.length) {
|
|
617
|
+
groups = groups.concat(group);
|
|
618
|
+
}
|
|
619
|
+
return groups;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
values = array.reduce(function(chars, value) {
|
|
623
|
+
return chars.concat((value + '').split(''));
|
|
624
|
+
}, []).reduce(function(groups, curChar) {
|
|
625
|
+
var curGroupClass, curCharCode;
|
|
626
|
+
if (!caseSensitive) { curChar = curChar.toLowerCase(); }
|
|
627
|
+
curGroupClass = /^\d$/.test(curChar) ? 1 :
|
|
628
|
+
/^[A-Z]$/.test(curChar) ? 2 : /^[a-z]$/.test(curChar) ? 3 : 0;
|
|
629
|
+
if (collectSymbols && curGroupClass === 0) {
|
|
630
|
+
symbols += curChar;
|
|
631
|
+
} else {
|
|
632
|
+
curCharCode = curChar.charCodeAt(0);
|
|
633
|
+
if (curGroupClass && curGroupClass === groupClass &&
|
|
634
|
+
curCharCode === charCode + 1) {
|
|
635
|
+
group.push(curChar);
|
|
636
|
+
} else {
|
|
637
|
+
groups = addGroup(groups, group);
|
|
638
|
+
group = [curChar];
|
|
639
|
+
groupClass = curGroupClass;
|
|
640
|
+
}
|
|
641
|
+
charCode = curCharCode;
|
|
642
|
+
}
|
|
643
|
+
return groups;
|
|
644
|
+
}, []);
|
|
645
|
+
values = addGroup(values, group); // last group
|
|
646
|
+
if (symbols) { values.push(symbols); suppressed = true; }
|
|
647
|
+
return {values: values, suppressed: suppressed};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function joinChunks(chunks, suppressed) {
|
|
651
|
+
return chunks.join(chunks.length > 2 ? ', ' : suppressed ? ' / ' : '/');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function getPhContent(param, options) {
|
|
655
|
+
var resCharlist = {},
|
|
656
|
+
text, values, arg;
|
|
657
|
+
if (options.phContent) {
|
|
658
|
+
text = options.phContent(param, options);
|
|
659
|
+
}
|
|
660
|
+
if (typeof text !== 'string') {
|
|
661
|
+
switch (param) {
|
|
662
|
+
case 'hideEchoBack':
|
|
663
|
+
case 'mask':
|
|
664
|
+
case 'defaultInput':
|
|
665
|
+
case 'caseSensitive':
|
|
666
|
+
case 'keepWhitespace':
|
|
667
|
+
case 'encoding':
|
|
668
|
+
case 'bufferSize':
|
|
669
|
+
case 'history':
|
|
670
|
+
case 'cd':
|
|
671
|
+
text = !options.hasOwnProperty(param) ? '' :
|
|
672
|
+
typeof options[param] === 'boolean' ? (options[param] ? 'on' : 'off') :
|
|
673
|
+
options[param] + '';
|
|
674
|
+
break;
|
|
675
|
+
// case 'prompt':
|
|
676
|
+
// case 'query':
|
|
677
|
+
// case 'display':
|
|
678
|
+
// text = options.hasOwnProperty('displaySrc') ? options.displaySrc + '' : '';
|
|
679
|
+
// break;
|
|
680
|
+
case 'limit':
|
|
681
|
+
case 'trueValue':
|
|
682
|
+
case 'falseValue':
|
|
683
|
+
values = options[options.hasOwnProperty(param + 'Src') ? param + 'Src' : param];
|
|
684
|
+
if (options.keyIn) { // suppress
|
|
685
|
+
resCharlist = array2charlist(values, options.caseSensitive);
|
|
686
|
+
values = resCharlist.values;
|
|
687
|
+
} else {
|
|
688
|
+
values = values.filter(function(value) {
|
|
689
|
+
var type = typeof value;
|
|
690
|
+
return type === 'string' || type === 'number';
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
text = joinChunks(values, resCharlist.suppressed);
|
|
694
|
+
break;
|
|
695
|
+
case 'limitCount':
|
|
696
|
+
case 'limitCountNotZero':
|
|
697
|
+
text = options[options.hasOwnProperty('limitSrc') ? 'limitSrc' : 'limit'].length;
|
|
698
|
+
text = text || param !== 'limitCountNotZero' ? text + '' : '';
|
|
699
|
+
break;
|
|
700
|
+
case 'lastInput':
|
|
701
|
+
text = lastInput;
|
|
702
|
+
break;
|
|
703
|
+
case 'cwd':
|
|
704
|
+
case 'CWD':
|
|
705
|
+
case 'cwdHome':
|
|
706
|
+
text = process.cwd();
|
|
707
|
+
if (param === 'CWD') {
|
|
708
|
+
text = pathUtil.basename(text);
|
|
709
|
+
} else if (param === 'cwdHome') {
|
|
710
|
+
text = replaceHomePath(text);
|
|
711
|
+
}
|
|
712
|
+
break;
|
|
713
|
+
case 'date':
|
|
714
|
+
case 'time':
|
|
715
|
+
case 'localeDate':
|
|
716
|
+
case 'localeTime':
|
|
717
|
+
text = (new Date())['to' +
|
|
718
|
+
param.replace(/^./, function(str) { return str.toUpperCase(); }) +
|
|
719
|
+
'String']();
|
|
720
|
+
break;
|
|
721
|
+
default: // with arg
|
|
722
|
+
if (typeof (arg = (param.match(/^history_m(\d+)$/) || [])[1]) === 'string') {
|
|
723
|
+
text = inputHistory[inputHistory.length - arg] || '';
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return text;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function getPhCharlist(param) {
|
|
731
|
+
var matches = /^(.)-(.)$/.exec(param),
|
|
732
|
+
text = '',
|
|
733
|
+
from, to, code, step;
|
|
734
|
+
if (!matches) { return null; }
|
|
735
|
+
from = matches[1].charCodeAt(0);
|
|
736
|
+
to = matches[2].charCodeAt(0);
|
|
737
|
+
step = from < to ? 1 : -1;
|
|
738
|
+
for (code = from; code !== to + step; code += step) { text += String.fromCharCode(code); }
|
|
739
|
+
return text;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// cmd "arg" " a r g " "" 'a"r"g' "a""rg" "arg
|
|
743
|
+
function parseCl(cl) {
|
|
744
|
+
var reToken = new RegExp(/(\s*)(?:("|')(.*?)(?:\2|$)|(\S+))/g),
|
|
745
|
+
taken = '',
|
|
746
|
+
args = [],
|
|
747
|
+
matches, part;
|
|
748
|
+
cl = cl.trim();
|
|
749
|
+
while ((matches = reToken.exec(cl))) {
|
|
750
|
+
part = matches[3] || matches[4] || '';
|
|
751
|
+
if (matches[1]) {
|
|
752
|
+
args.push(taken);
|
|
753
|
+
taken = '';
|
|
754
|
+
}
|
|
755
|
+
taken += part;
|
|
756
|
+
}
|
|
757
|
+
if (taken) { args.push(taken); }
|
|
758
|
+
return args;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function toBool(res, options) {
|
|
762
|
+
return (
|
|
763
|
+
(options.trueValue.length &&
|
|
764
|
+
isMatched(res, options.trueValue, options.caseSensitive)) ? true :
|
|
765
|
+
(options.falseValue.length &&
|
|
766
|
+
isMatched(res, options.falseValue, options.caseSensitive)) ? false : res);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function getValidLine(options) {
|
|
770
|
+
var res, forceNext, limitMessage,
|
|
771
|
+
matches, histInput, args, resCheck;
|
|
772
|
+
|
|
773
|
+
function _getPhContent(param) { return getPhContent(param, options); }
|
|
774
|
+
function addDisplay(text) { options.display += (/[^\r\n]$/.test(options.display) ? '\n' : '') + text; }
|
|
775
|
+
|
|
776
|
+
options.limitSrc = options.limit;
|
|
777
|
+
options.displaySrc = options.display;
|
|
778
|
+
options.limit = ''; // for readlineExt
|
|
779
|
+
options.display = replacePlaceholder(options.display + '', _getPhContent);
|
|
780
|
+
|
|
781
|
+
while (true) {
|
|
782
|
+
res = _readlineSync(options);
|
|
783
|
+
forceNext = false;
|
|
784
|
+
limitMessage = '';
|
|
785
|
+
|
|
786
|
+
if (options.defaultInput && !res) { res = options.defaultInput; }
|
|
787
|
+
|
|
788
|
+
if (options.history) {
|
|
789
|
+
if ((matches = /^\s*!(?:!|-1)(:p)?\s*$/.exec(res))) { // `!!` `!-1` +`:p`
|
|
790
|
+
histInput = inputHistory[0] || '';
|
|
791
|
+
if (matches[1]) { // only display
|
|
792
|
+
forceNext = true;
|
|
793
|
+
} else { // replace input
|
|
794
|
+
res = histInput;
|
|
795
|
+
}
|
|
796
|
+
// Show it even if it is empty (NL only).
|
|
797
|
+
addDisplay(histInput + '\n');
|
|
798
|
+
if (!forceNext) { // Loop may break
|
|
799
|
+
options.displayOnly = true;
|
|
800
|
+
_readlineSync(options);
|
|
801
|
+
options.displayOnly = false;
|
|
802
|
+
}
|
|
803
|
+
} else if (res && res !== inputHistory[inputHistory.length - 1]) {
|
|
804
|
+
inputHistory = [res];
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (!forceNext && options.cd && res) {
|
|
809
|
+
args = parseCl(res);
|
|
810
|
+
switch (args[0].toLowerCase()) {
|
|
811
|
+
case 'cd':
|
|
812
|
+
if (args[1]) {
|
|
813
|
+
try {
|
|
814
|
+
process.chdir(replaceHomePath(args[1], true));
|
|
815
|
+
} catch (e) {
|
|
816
|
+
addDisplay(e + '');
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
forceNext = true;
|
|
820
|
+
break;
|
|
821
|
+
case 'pwd':
|
|
822
|
+
addDisplay(process.cwd());
|
|
823
|
+
forceNext = true;
|
|
824
|
+
break;
|
|
825
|
+
// no default
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (!forceNext && options.preCheck) {
|
|
830
|
+
resCheck = options.preCheck(res, options);
|
|
831
|
+
res = resCheck.res;
|
|
832
|
+
if (resCheck.forceNext) { forceNext = true; } // Don't switch to false.
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (!forceNext) {
|
|
836
|
+
if (!options.limitSrc.length ||
|
|
837
|
+
isMatched(res, options.limitSrc, options.caseSensitive)) { break; }
|
|
838
|
+
if (options.limitMessage) {
|
|
839
|
+
limitMessage = replacePlaceholder(options.limitMessage, _getPhContent);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
addDisplay((limitMessage ? limitMessage + '\n' : '') +
|
|
844
|
+
replacePlaceholder(options.displaySrc + '', _getPhContent));
|
|
845
|
+
}
|
|
846
|
+
return toBool(res, options);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// for dev
|
|
850
|
+
exports._DBG_set_useExt = function(val) { _DBG_useExt = val; };
|
|
851
|
+
exports._DBG_set_checkOptions = function(val) { _DBG_checkOptions = val; };
|
|
852
|
+
exports._DBG_set_checkMethod = function(val) { _DBG_checkMethod = val; };
|
|
853
|
+
exports._DBG_clearHistory = function() { lastInput = ''; inputHistory = []; };
|
|
854
|
+
|
|
855
|
+
// ------------------------------------
|
|
856
|
+
|
|
857
|
+
exports.setDefaultOptions = function(options) {
|
|
858
|
+
defaultOptions = margeOptions(true, options);
|
|
859
|
+
return margeOptions(true); // copy
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
exports.question = function(query, options) {
|
|
863
|
+
/* eslint-disable key-spacing */
|
|
864
|
+
return getValidLine(margeOptions(margeOptions(true, options), {
|
|
865
|
+
display: query
|
|
866
|
+
}));
|
|
867
|
+
/* eslint-enable key-spacing */
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
exports.prompt = function(options) {
|
|
871
|
+
var readOptions = margeOptions(true, options);
|
|
872
|
+
readOptions.display = readOptions.prompt;
|
|
873
|
+
return getValidLine(readOptions);
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
exports.keyIn = function(query, options) {
|
|
877
|
+
/* eslint-disable key-spacing */
|
|
878
|
+
var readOptions = margeOptions(margeOptions(true, options), {
|
|
879
|
+
display: query,
|
|
880
|
+
keyIn: true,
|
|
881
|
+
keepWhitespace: true
|
|
882
|
+
});
|
|
883
|
+
/* eslint-enable key-spacing */
|
|
884
|
+
|
|
885
|
+
// char list
|
|
886
|
+
readOptions.limitSrc = readOptions.limit.filter(function(value) {
|
|
887
|
+
var type = typeof value;
|
|
888
|
+
return type === 'string' || type === 'number';
|
|
889
|
+
}).map(function(text) {
|
|
890
|
+
return replacePlaceholder(text + '', getPhCharlist);
|
|
891
|
+
});
|
|
892
|
+
// pattern
|
|
893
|
+
readOptions.limit = escapePattern(readOptions.limitSrc.join(''));
|
|
894
|
+
|
|
895
|
+
['trueValue', 'falseValue'].forEach(function(optionName) {
|
|
896
|
+
readOptions[optionName] = readOptions[optionName].reduce(function(comps, comp) {
|
|
897
|
+
var type = typeof comp;
|
|
898
|
+
if (type === 'string' || type === 'number') {
|
|
899
|
+
comps = comps.concat((comp + '').split(''));
|
|
900
|
+
} else { comps.push(comp); }
|
|
901
|
+
return comps;
|
|
902
|
+
}, []);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
readOptions.display = replacePlaceholder(readOptions.display + '',
|
|
906
|
+
function(param) { return getPhContent(param, readOptions); });
|
|
907
|
+
|
|
908
|
+
return toBool(_readlineSync(readOptions), readOptions);
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
// ------------------------------------
|
|
912
|
+
|
|
913
|
+
exports.questionEMail = function(query, options) {
|
|
914
|
+
if (query == null) { query = 'Input e-mail address: '; }
|
|
915
|
+
/* eslint-disable key-spacing */
|
|
916
|
+
return exports.question(query, margeOptions({
|
|
917
|
+
// -------- default
|
|
918
|
+
hideEchoBack: false,
|
|
919
|
+
// http://www.w3.org/TR/html5/forms.html#valid-e-mail-address
|
|
920
|
+
limit: /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
|
|
921
|
+
limitMessage: 'Input valid e-mail address, please.',
|
|
922
|
+
trueValue: null,
|
|
923
|
+
falseValue: null
|
|
924
|
+
}, options, {
|
|
925
|
+
// -------- forced
|
|
926
|
+
keepWhitespace: false,
|
|
927
|
+
cd: false
|
|
928
|
+
}));
|
|
929
|
+
/* eslint-enable key-spacing */
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
exports.questionNewPassword = function(query, options) {
|
|
933
|
+
/* eslint-disable key-spacing */
|
|
934
|
+
var resCharlist, min, max,
|
|
935
|
+
readOptions = margeOptions({
|
|
936
|
+
// -------- default
|
|
937
|
+
hideEchoBack: true,
|
|
938
|
+
mask: '*',
|
|
939
|
+
limitMessage: 'It can include: $<charlist>\n' +
|
|
940
|
+
'And the length must be: $<length>',
|
|
941
|
+
trueValue: null,
|
|
942
|
+
falseValue: null,
|
|
943
|
+
caseSensitive: true
|
|
944
|
+
}, options, {
|
|
945
|
+
// -------- forced
|
|
946
|
+
history: false,
|
|
947
|
+
cd: false,
|
|
948
|
+
// limit (by charlist etc.),
|
|
949
|
+
phContent: function(param) {
|
|
950
|
+
return param === 'charlist' ? resCharlist.text :
|
|
951
|
+
param === 'length' ? min + '...' + max : null;
|
|
952
|
+
}
|
|
953
|
+
}),
|
|
954
|
+
// added: charlist, min, max, confirmMessage, unmatchMessage
|
|
955
|
+
charlist, confirmMessage, unmatchMessage,
|
|
956
|
+
limit, limitMessage, res1, res2;
|
|
957
|
+
/* eslint-enable key-spacing */
|
|
958
|
+
options = options || {};
|
|
959
|
+
|
|
960
|
+
charlist = replacePlaceholder(
|
|
961
|
+
options.charlist ? options.charlist + '' : '$<!-~>', getPhCharlist);
|
|
962
|
+
if (isNaN(min = parseInt(options.min, 10)) || typeof min !== 'number') { min = 12; }
|
|
963
|
+
if (isNaN(max = parseInt(options.max, 10)) || typeof max !== 'number') { max = 24; }
|
|
964
|
+
limit = new RegExp('^[' + escapePattern(charlist) +
|
|
965
|
+
']{' + min + ',' + max + '}$');
|
|
966
|
+
resCharlist = array2charlist([charlist], readOptions.caseSensitive, true);
|
|
967
|
+
resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed);
|
|
968
|
+
|
|
969
|
+
confirmMessage = options.confirmMessage != null ? options.confirmMessage :
|
|
970
|
+
'Reinput a same one to confirm it: ';
|
|
971
|
+
unmatchMessage = options.unmatchMessage != null ? options.unmatchMessage :
|
|
972
|
+
'It differs from first one.' +
|
|
973
|
+
' Hit only the Enter key if you want to retry from first one.';
|
|
974
|
+
|
|
975
|
+
if (query == null) { query = 'Input new password: '; }
|
|
976
|
+
|
|
977
|
+
limitMessage = readOptions.limitMessage;
|
|
978
|
+
while (!res2) {
|
|
979
|
+
readOptions.limit = limit;
|
|
980
|
+
readOptions.limitMessage = limitMessage;
|
|
981
|
+
res1 = exports.question(query, readOptions);
|
|
982
|
+
|
|
983
|
+
readOptions.limit = [res1, ''];
|
|
984
|
+
readOptions.limitMessage = unmatchMessage;
|
|
985
|
+
res2 = exports.question(confirmMessage, readOptions);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return res1;
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
function _questionNum(query, options, parser) {
|
|
992
|
+
var validValue;
|
|
993
|
+
function getValidValue(value) {
|
|
994
|
+
validValue = parser(value);
|
|
995
|
+
return !isNaN(validValue) && typeof validValue === 'number';
|
|
996
|
+
}
|
|
997
|
+
/* eslint-disable key-spacing */
|
|
998
|
+
exports.question(query, margeOptions({
|
|
999
|
+
// -------- default
|
|
1000
|
+
limitMessage: 'Input valid number, please.'
|
|
1001
|
+
}, options, {
|
|
1002
|
+
// -------- forced
|
|
1003
|
+
limit: getValidValue,
|
|
1004
|
+
cd: false
|
|
1005
|
+
// trueValue, falseValue, caseSensitive, keepWhitespace don't work.
|
|
1006
|
+
}));
|
|
1007
|
+
/* eslint-enable key-spacing */
|
|
1008
|
+
return validValue;
|
|
1009
|
+
}
|
|
1010
|
+
exports.questionInt = function(query, options) {
|
|
1011
|
+
return _questionNum(query, options, function(value) { return parseInt(value, 10); });
|
|
1012
|
+
};
|
|
1013
|
+
exports.questionFloat = function(query, options) {
|
|
1014
|
+
return _questionNum(query, options, parseFloat);
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
exports.questionPath = function(query, options) {
|
|
1018
|
+
/* eslint-disable key-spacing */
|
|
1019
|
+
var error = '',
|
|
1020
|
+
validPath, // before readOptions
|
|
1021
|
+
readOptions = margeOptions({
|
|
1022
|
+
// -------- default
|
|
1023
|
+
hideEchoBack: false,
|
|
1024
|
+
limitMessage: '$<error(\n)>Input valid path, please.' +
|
|
1025
|
+
'$<( Min:)min>$<( Max:)max>',
|
|
1026
|
+
history: true,
|
|
1027
|
+
cd: true
|
|
1028
|
+
}, options, {
|
|
1029
|
+
// -------- forced
|
|
1030
|
+
keepWhitespace: false,
|
|
1031
|
+
limit: function(value) {
|
|
1032
|
+
var exists, stat, res;
|
|
1033
|
+
value = replaceHomePath(value, true);
|
|
1034
|
+
error = ''; // for validate
|
|
1035
|
+
// mkdir -p
|
|
1036
|
+
function mkdirParents(dirPath) {
|
|
1037
|
+
dirPath.split(/\/|\\/).reduce(function(parents, dir) {
|
|
1038
|
+
var path = pathUtil.resolve((parents += dir + pathUtil.sep));
|
|
1039
|
+
if (!fs.existsSync(path)) {
|
|
1040
|
+
fs.mkdirSync(path);
|
|
1041
|
+
} else if (!fs.statSync(path).isDirectory()) {
|
|
1042
|
+
throw new Error('Non directory already exists: ' + path);
|
|
1043
|
+
}
|
|
1044
|
+
return parents;
|
|
1045
|
+
}, '');
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
try {
|
|
1049
|
+
exists = fs.existsSync(value);
|
|
1050
|
+
validPath = exists ? fs.realpathSync(value) : pathUtil.resolve(value);
|
|
1051
|
+
// options.exists default: true, not-bool: no-check
|
|
1052
|
+
if (!options.hasOwnProperty('exists') && !exists ||
|
|
1053
|
+
typeof options.exists === 'boolean' && options.exists !== exists) {
|
|
1054
|
+
error = (exists ? 'Already exists' : 'No such file or directory') +
|
|
1055
|
+
': ' + validPath;
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
if (!exists && options.create) {
|
|
1059
|
+
if (options.isDirectory) {
|
|
1060
|
+
mkdirParents(validPath);
|
|
1061
|
+
} else {
|
|
1062
|
+
mkdirParents(pathUtil.dirname(validPath));
|
|
1063
|
+
fs.closeSync(fs.openSync(validPath, 'w')); // touch
|
|
1064
|
+
}
|
|
1065
|
+
validPath = fs.realpathSync(validPath);
|
|
1066
|
+
}
|
|
1067
|
+
if (exists && (options.min || options.max ||
|
|
1068
|
+
options.isFile || options.isDirectory)) {
|
|
1069
|
+
stat = fs.statSync(validPath);
|
|
1070
|
+
// type check first (directory has zero size)
|
|
1071
|
+
if (options.isFile && !stat.isFile()) {
|
|
1072
|
+
error = 'Not file: ' + validPath;
|
|
1073
|
+
return false;
|
|
1074
|
+
} else if (options.isDirectory && !stat.isDirectory()) {
|
|
1075
|
+
error = 'Not directory: ' + validPath;
|
|
1076
|
+
return false;
|
|
1077
|
+
} else if (options.min && stat.size < +options.min ||
|
|
1078
|
+
options.max && stat.size > +options.max) {
|
|
1079
|
+
error = 'Size ' + stat.size + ' is out of range: ' + validPath;
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (typeof options.validate === 'function' &&
|
|
1084
|
+
(res = options.validate(validPath)) !== true) {
|
|
1085
|
+
if (typeof res === 'string') { error = res; }
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
} catch (e) {
|
|
1089
|
+
error = e + '';
|
|
1090
|
+
return false;
|
|
1091
|
+
}
|
|
1092
|
+
return true;
|
|
1093
|
+
},
|
|
1094
|
+
// trueValue, falseValue, caseSensitive don't work.
|
|
1095
|
+
phContent: function(param) {
|
|
1096
|
+
return param === 'error' ? error :
|
|
1097
|
+
param !== 'min' && param !== 'max' ? null :
|
|
1098
|
+
options.hasOwnProperty(param) ? options[param] + '' : '';
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
// added: exists, create, min, max, isFile, isDirectory, validate
|
|
1102
|
+
/* eslint-enable key-spacing */
|
|
1103
|
+
options = options || {};
|
|
1104
|
+
|
|
1105
|
+
if (query == null) { query = 'Input path (you can "cd" and "pwd"): '; }
|
|
1106
|
+
|
|
1107
|
+
exports.question(query, readOptions);
|
|
1108
|
+
return validPath;
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// props: preCheck, args, hRes, limit
|
|
1112
|
+
function getClHandler(commandHandler, options) {
|
|
1113
|
+
var clHandler = {},
|
|
1114
|
+
hIndex = {};
|
|
1115
|
+
if (typeof commandHandler === 'object') {
|
|
1116
|
+
Object.keys(commandHandler).forEach(function(cmd) {
|
|
1117
|
+
if (typeof commandHandler[cmd] === 'function') {
|
|
1118
|
+
hIndex[options.caseSensitive ? cmd : cmd.toLowerCase()] = commandHandler[cmd];
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
clHandler.preCheck = function(res) {
|
|
1122
|
+
var cmdKey;
|
|
1123
|
+
clHandler.args = parseCl(res);
|
|
1124
|
+
cmdKey = clHandler.args[0] || '';
|
|
1125
|
+
if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); }
|
|
1126
|
+
clHandler.hRes =
|
|
1127
|
+
cmdKey !== '_' && hIndex.hasOwnProperty(cmdKey)
|
|
1128
|
+
? hIndex[cmdKey].apply(res, clHandler.args.slice(1)) :
|
|
1129
|
+
hIndex.hasOwnProperty('_') ? hIndex._.apply(res, clHandler.args) : null;
|
|
1130
|
+
return {res: res, forceNext: false};
|
|
1131
|
+
};
|
|
1132
|
+
if (!hIndex.hasOwnProperty('_')) {
|
|
1133
|
+
clHandler.limit = function() { // It's called after preCheck.
|
|
1134
|
+
var cmdKey = clHandler.args[0] || '';
|
|
1135
|
+
if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); }
|
|
1136
|
+
return hIndex.hasOwnProperty(cmdKey);
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
} else {
|
|
1140
|
+
clHandler.preCheck = function(res) {
|
|
1141
|
+
clHandler.args = parseCl(res);
|
|
1142
|
+
clHandler.hRes = typeof commandHandler === 'function'
|
|
1143
|
+
? commandHandler.apply(res, clHandler.args) : true; // true for break loop
|
|
1144
|
+
return {res: res, forceNext: false};
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
return clHandler;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
exports.promptCL = function(commandHandler, options) {
|
|
1151
|
+
/* eslint-disable key-spacing */
|
|
1152
|
+
var readOptions = margeOptions({
|
|
1153
|
+
// -------- default
|
|
1154
|
+
hideEchoBack: false,
|
|
1155
|
+
limitMessage: 'Requested command is not available.',
|
|
1156
|
+
caseSensitive: false,
|
|
1157
|
+
history: true
|
|
1158
|
+
}, options),
|
|
1159
|
+
// -------- forced
|
|
1160
|
+
// trueValue, falseValue, keepWhitespace don't work.
|
|
1161
|
+
// preCheck, limit (by clHandler)
|
|
1162
|
+
clHandler = getClHandler(commandHandler, readOptions);
|
|
1163
|
+
/* eslint-enable key-spacing */
|
|
1164
|
+
readOptions.limit = clHandler.limit;
|
|
1165
|
+
readOptions.preCheck = clHandler.preCheck;
|
|
1166
|
+
exports.prompt(readOptions);
|
|
1167
|
+
return clHandler.args;
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
exports.promptLoop = function(inputHandler, options) {
|
|
1171
|
+
/* eslint-disable key-spacing */
|
|
1172
|
+
var readOptions = margeOptions({
|
|
1173
|
+
// -------- default
|
|
1174
|
+
hideEchoBack: false,
|
|
1175
|
+
trueValue: null,
|
|
1176
|
+
falseValue: null,
|
|
1177
|
+
caseSensitive: false,
|
|
1178
|
+
history: true
|
|
1179
|
+
}, options);
|
|
1180
|
+
/* eslint-enable key-spacing */
|
|
1181
|
+
while (true) { if (inputHandler(exports.prompt(readOptions))) { break; } }
|
|
1182
|
+
// return; // nothing is returned
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
exports.promptCLLoop = function(commandHandler, options) {
|
|
1186
|
+
/* eslint-disable key-spacing */
|
|
1187
|
+
var readOptions = margeOptions({
|
|
1188
|
+
// -------- default
|
|
1189
|
+
hideEchoBack: false,
|
|
1190
|
+
limitMessage: 'Requested command is not available.',
|
|
1191
|
+
caseSensitive: false,
|
|
1192
|
+
history: true
|
|
1193
|
+
}, options),
|
|
1194
|
+
// -------- forced
|
|
1195
|
+
// trueValue, falseValue, keepWhitespace don't work.
|
|
1196
|
+
// preCheck, limit (by clHandler)
|
|
1197
|
+
clHandler = getClHandler(commandHandler, readOptions);
|
|
1198
|
+
/* eslint-enable key-spacing */
|
|
1199
|
+
readOptions.limit = clHandler.limit;
|
|
1200
|
+
readOptions.preCheck = clHandler.preCheck;
|
|
1201
|
+
while (true) {
|
|
1202
|
+
exports.prompt(readOptions);
|
|
1203
|
+
if (clHandler.hRes) { break; }
|
|
1204
|
+
}
|
|
1205
|
+
// return; // nothing is returned
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
exports.promptSimShell = function(options) {
|
|
1209
|
+
/* eslint-disable key-spacing */
|
|
1210
|
+
return exports.prompt(margeOptions({
|
|
1211
|
+
// -------- default
|
|
1212
|
+
hideEchoBack: false,
|
|
1213
|
+
history: true
|
|
1214
|
+
}, options, {
|
|
1215
|
+
// -------- forced
|
|
1216
|
+
prompt: (function() {
|
|
1217
|
+
return IS_WIN ? '$<cwd>>' :
|
|
1218
|
+
// 'user@host:cwd$ '
|
|
1219
|
+
(process.env.USER || '') +
|
|
1220
|
+
(process.env.HOSTNAME ? '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') +
|
|
1221
|
+
':$<cwdHome>$ ';
|
|
1222
|
+
})()
|
|
1223
|
+
}));
|
|
1224
|
+
/* eslint-enable key-spacing */
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
function _keyInYN(query, options, limit) {
|
|
1228
|
+
var res;
|
|
1229
|
+
if (query == null) { query = 'Are you sure? '; }
|
|
1230
|
+
if ((!options || options.guide !== false) && (query += '')) {
|
|
1231
|
+
query = query.replace(/\s*:?\s*$/, '') + ' [y/n]: ';
|
|
1232
|
+
}
|
|
1233
|
+
/* eslint-disable key-spacing */
|
|
1234
|
+
res = exports.keyIn(query, margeOptions(options, {
|
|
1235
|
+
// -------- forced
|
|
1236
|
+
hideEchoBack: false,
|
|
1237
|
+
limit: limit,
|
|
1238
|
+
trueValue: 'y',
|
|
1239
|
+
falseValue: 'n',
|
|
1240
|
+
caseSensitive: false
|
|
1241
|
+
// mask doesn't work.
|
|
1242
|
+
}));
|
|
1243
|
+
// added: guide
|
|
1244
|
+
/* eslint-enable key-spacing */
|
|
1245
|
+
return typeof res === 'boolean' ? res : '';
|
|
1246
|
+
}
|
|
1247
|
+
exports.keyInYN = function(query, options) { return _keyInYN(query, options); };
|
|
1248
|
+
exports.keyInYNStrict = function(query, options) { return _keyInYN(query, options, 'yn'); };
|
|
1249
|
+
|
|
1250
|
+
exports.keyInPause = function(query, options) {
|
|
1251
|
+
if (query == null) { query = 'Continue...'; }
|
|
1252
|
+
if ((!options || options.guide !== false) && (query += '')) {
|
|
1253
|
+
query = query.replace(/\s+$/, '') + ' (Hit any key)';
|
|
1254
|
+
}
|
|
1255
|
+
/* eslint-disable key-spacing */
|
|
1256
|
+
exports.keyIn(query, margeOptions({
|
|
1257
|
+
// -------- default
|
|
1258
|
+
limit: null
|
|
1259
|
+
}, options, {
|
|
1260
|
+
// -------- forced
|
|
1261
|
+
hideEchoBack: true,
|
|
1262
|
+
mask: ''
|
|
1263
|
+
}));
|
|
1264
|
+
// added: guide
|
|
1265
|
+
/* eslint-enable key-spacing */
|
|
1266
|
+
// return; // nothing is returned
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
exports.keyInSelect = function(items, query, options) {
|
|
1270
|
+
/* eslint-disable key-spacing */
|
|
1271
|
+
var readOptions = margeOptions({
|
|
1272
|
+
// -------- default
|
|
1273
|
+
hideEchoBack: false
|
|
1274
|
+
}, options, {
|
|
1275
|
+
// -------- forced
|
|
1276
|
+
trueValue: null,
|
|
1277
|
+
falseValue: null,
|
|
1278
|
+
caseSensitive: false,
|
|
1279
|
+
// limit (by items),
|
|
1280
|
+
phContent: function(param) {
|
|
1281
|
+
return param === 'itemsCount' ? items.length + '' :
|
|
1282
|
+
param === 'firstItem' ? (items[0] + '').trim() :
|
|
1283
|
+
param === 'lastItem' ? (items[items.length - 1] + '').trim() : null;
|
|
1284
|
+
}
|
|
1285
|
+
}),
|
|
1286
|
+
// added: guide, cancel
|
|
1287
|
+
keylist = '',
|
|
1288
|
+
key2i = {},
|
|
1289
|
+
charCode = 49 /* '1' */,
|
|
1290
|
+
display = '\n';
|
|
1291
|
+
/* eslint-enable key-spacing */
|
|
1292
|
+
if (!Array.isArray(items) || !items.length || items.length > 35) {
|
|
1293
|
+
throw '`items` must be Array (max length: 35).';
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
items.forEach(function(item, i) {
|
|
1297
|
+
var key = String.fromCharCode(charCode);
|
|
1298
|
+
keylist += key;
|
|
1299
|
+
key2i[key] = i;
|
|
1300
|
+
display += '[' + key + '] ' + (item + '').trim() + '\n';
|
|
1301
|
+
charCode = charCode === 57 /* '9' */ ? 97 /* 'a' */ : charCode + 1;
|
|
1302
|
+
});
|
|
1303
|
+
if (!options || options.cancel !== false) {
|
|
1304
|
+
keylist += '0';
|
|
1305
|
+
key2i['0'] = -1;
|
|
1306
|
+
display += '[0] ' +
|
|
1307
|
+
(options && options.cancel != null && typeof options.cancel !== 'boolean'
|
|
1308
|
+
? (options.cancel + '').trim() : 'CANCEL') + '\n';
|
|
1309
|
+
}
|
|
1310
|
+
readOptions.limit = keylist;
|
|
1311
|
+
display += '\n';
|
|
1312
|
+
|
|
1313
|
+
if (query == null) { query = 'Choose one from list: '; }
|
|
1314
|
+
if ((query += '')) {
|
|
1315
|
+
if (!options || options.guide !== false) {
|
|
1316
|
+
query = query.replace(/\s*:?\s*$/, '') + ' [$<limit>]: ';
|
|
1317
|
+
}
|
|
1318
|
+
display += query;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
return key2i[exports.keyIn(display, readOptions).toLowerCase()];
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
exports.getRawInput = function() { return rawInput; };
|
|
1325
|
+
|
|
1326
|
+
// ======== DEPRECATED ========
|
|
1327
|
+
function _setOption(optionName, args) {
|
|
1328
|
+
var options;
|
|
1329
|
+
if (args.length) { options = {}; options[optionName] = args[0]; }
|
|
1330
|
+
return exports.setDefaultOptions(options)[optionName];
|
|
1331
|
+
}
|
|
1332
|
+
exports.setPrint = function() { return _setOption('print', arguments); };
|
|
1333
|
+
exports.setPrompt = function() { return _setOption('prompt', arguments); };
|
|
1334
|
+
exports.setEncoding = function() { return _setOption('encoding', arguments); };
|
|
1335
|
+
exports.setMask = function() { return _setOption('mask', arguments); };
|
|
1336
|
+
exports.setBufferSize = function() { return _setOption('bufferSize', arguments); };
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
/***/ }),
|
|
1340
|
+
|
|
1341
|
+
/***/ 112:
|
|
1342
|
+
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
|
|
1343
|
+
|
|
1344
|
+
const readlineSync = __nccwpck_require__(552);
|
|
1345
|
+
const fs = __nccwpck_require__(896);
|
|
1346
|
+
const Lexer = __nccwpck_require__(211);
|
|
1347
|
+
const Parser = __nccwpck_require__(222);
|
|
1348
|
+
|
|
1349
|
+
class ReturnValue {
|
|
1350
|
+
constructor(value) { this.value = value; }
|
|
1351
|
+
}
|
|
1352
|
+
class BreakSignal {}
|
|
1353
|
+
class ContinueSignal {}
|
|
1354
|
+
|
|
1355
|
+
class Environment {
|
|
1356
|
+
constructor(parent = null) {
|
|
1357
|
+
this.store = Object.create(null);
|
|
1358
|
+
this.parent = parent;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
has(name) {
|
|
1362
|
+
if (name in this.store) return true;
|
|
1363
|
+
if (this.parent) return this.parent.has(name);
|
|
1364
|
+
return false;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
get(name) {
|
|
1368
|
+
if (name in this.store) return this.store[name];
|
|
1369
|
+
if (this.parent) return this.parent.get(name);
|
|
1370
|
+
throw new Error(`Undefined variable: ${name}`);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
set(name, value) {
|
|
1374
|
+
if (name in this.store) { this.store[name] = value; return value; }
|
|
1375
|
+
if (this.parent && this.parent.has(name)) { return this.parent.set(name, value); }
|
|
1376
|
+
this.store[name] = value;
|
|
1377
|
+
return value;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
define(name, value) {
|
|
1381
|
+
this.store[name] = value;
|
|
1382
|
+
return value;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
class Evaluator {
|
|
1387
|
+
constructor() {
|
|
1388
|
+
this.global = new Environment();
|
|
1389
|
+
this.setupBuiltins();
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
setupBuiltins() {
|
|
1393
|
+
this.global.define('len', arg => {
|
|
1394
|
+
if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
|
|
1395
|
+
if (arg && typeof arg === 'object') return Object.keys(arg).length;
|
|
1396
|
+
return 0;
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
this.global.define('print', arg => { console.log(arg); return null; });
|
|
1400
|
+
this.global.define('type', arg => {
|
|
1401
|
+
if (Array.isArray(arg)) return 'array';
|
|
1402
|
+
return typeof arg;
|
|
1403
|
+
});
|
|
1404
|
+
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
1405
|
+
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
1406
|
+
|
|
1407
|
+
this.global.define('ask', prompt => {
|
|
1408
|
+
const readlineSync = __nccwpck_require__(552);
|
|
1409
|
+
return readlineSync.question(prompt + ' ');
|
|
1410
|
+
});
|
|
1411
|
+
this.global.define('num', arg => {
|
|
1412
|
+
const n = Number(arg);
|
|
1413
|
+
if (Number.isNaN(n)) {
|
|
1414
|
+
throw new Error('Cannot convert value to number');
|
|
1415
|
+
}
|
|
1416
|
+
return n;
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
this.global.define('str', arg => {
|
|
1420
|
+
return String(arg);
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+
evaluate(node, env = this.global) {
|
|
1427
|
+
switch (node.type) {
|
|
1428
|
+
case 'Program': return this.evalProgram(node, env);
|
|
1429
|
+
case 'BlockStatement': return this.evalBlock(node, new Environment(env));
|
|
1430
|
+
case 'VarDeclaration': return this.evalVarDeclaration(node, env);
|
|
1431
|
+
case 'AssignmentExpression': return this.evalAssignment(node, env);
|
|
1432
|
+
case 'CompoundAssignment': return this.evalCompoundAssignment(node, env);
|
|
1433
|
+
case 'SldeployStatement': return this.evalSldeploy(node, env);
|
|
1434
|
+
case 'AskStatement': return this.evalAsk(node, env);
|
|
1435
|
+
case 'DefineStatement': return this.evalDefine(node, env);
|
|
1436
|
+
case 'ExpressionStatement': return this.evaluate(node.expression, env);
|
|
1437
|
+
case 'BinaryExpression': return this.evalBinary(node, env);
|
|
1438
|
+
case 'LogicalExpression': return this.evalLogical(node, env);
|
|
1439
|
+
case 'UnaryExpression': return this.evalUnary(node, env);
|
|
1440
|
+
case 'Literal': return node.value;
|
|
1441
|
+
case 'Identifier': return env.get(node.name);
|
|
1442
|
+
case 'IfStatement': return this.evalIf(node, env);
|
|
1443
|
+
case 'WhileStatement': return this.evalWhile(node, env);
|
|
1444
|
+
case 'ForStatement': return this.evalFor(node, env);
|
|
1445
|
+
case 'BreakStatement': throw new BreakSignal();
|
|
1446
|
+
case 'ContinueStatement': throw new ContinueSignal();
|
|
1447
|
+
case 'ImportStatement': return this.evalImport(node, env);
|
|
1448
|
+
case 'FunctionDeclaration': return this.evalFunctionDeclaration(node, env);
|
|
1449
|
+
case 'CallExpression': return this.evalCall(node, env);
|
|
1450
|
+
case 'ReturnStatement': {
|
|
1451
|
+
const val = node.argument ? this.evaluate(node.argument, env) : null;
|
|
1452
|
+
throw new ReturnValue(val);
|
|
1453
|
+
}
|
|
1454
|
+
case 'ArrayExpression': return node.elements.map(el => this.evaluate(el, env));
|
|
1455
|
+
case 'IndexExpression': return this.evalIndex(node, env);
|
|
1456
|
+
case 'ObjectExpression': return this.evalObject(node, env);
|
|
1457
|
+
case 'MemberExpression': return this.evalMember(node, env);
|
|
1458
|
+
case 'UpdateExpression': return this.evalUpdate(node, env);
|
|
1459
|
+
default:
|
|
1460
|
+
throw new Error(`Unknown node type in evaluator: ${node.type}`);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
evalProgram(node, env) {
|
|
1465
|
+
let result = null;
|
|
1466
|
+
for (const stmt of node.body) {
|
|
1467
|
+
result = this.evaluate(stmt, env);
|
|
1468
|
+
}
|
|
1469
|
+
return result;
|
|
1470
|
+
}
|
|
1471
|
+
evalImport(node, env) {
|
|
1472
|
+
const path = node.path;
|
|
1473
|
+
let lib;
|
|
1474
|
+
|
|
1475
|
+
try {
|
|
1476
|
+
lib = require(path);
|
|
1477
|
+
} catch (e) {
|
|
1478
|
+
const fullPath = path.endsWith('.sl') ? path : path + '.sl';
|
|
1479
|
+
|
|
1480
|
+
if (!fs.existsSync(fullPath)) {
|
|
1481
|
+
throw new Error(`Import not found: ${path}`);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
1485
|
+
const tokens = new Lexer(code).getTokens();
|
|
1486
|
+
const ast = new Parser(tokens).parse();
|
|
1487
|
+
|
|
1488
|
+
const moduleEnv = new Environment(env);
|
|
1489
|
+
this.evaluate(ast, moduleEnv);
|
|
1490
|
+
|
|
1491
|
+
lib = {};
|
|
1492
|
+
for (const key of Object.keys(moduleEnv.store)) {
|
|
1493
|
+
lib[key] = moduleEnv.store[key];
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// JS-style default export fallback
|
|
1497
|
+
lib.default = lib;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
for (const spec of node.specifiers) {
|
|
1501
|
+
if (spec.type === 'DefaultImport') {
|
|
1502
|
+
env.define(spec.local, lib.default ?? lib);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
if (spec.type === 'NamespaceImport') {
|
|
1506
|
+
env.define(spec.local, lib);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
if (spec.type === 'NamedImport') {
|
|
1510
|
+
if (!(spec.imported in lib)) {
|
|
1511
|
+
throw new Error(`Module '${path}' has no export '${spec.imported}'`);
|
|
1512
|
+
}
|
|
1513
|
+
env.define(spec.local, lib[spec.imported]);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
evalBlock(node, env) {
|
|
1521
|
+
let result = null;
|
|
1522
|
+
for (const stmt of node.body) {
|
|
1523
|
+
try {
|
|
1524
|
+
result = this.evaluate(stmt, env);
|
|
1525
|
+
} catch (e) {
|
|
1526
|
+
if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
|
|
1527
|
+
throw e;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
return result;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
evalVarDeclaration(node, env) {
|
|
1534
|
+
const val = this.evaluate(node.expr, env);
|
|
1535
|
+
return env.define(node.id, val);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
evalAssignment(node, env) {
|
|
1539
|
+
const rightVal = this.evaluate(node.right, env);
|
|
1540
|
+
const left = node.left;
|
|
1541
|
+
|
|
1542
|
+
if (left.type === 'Identifier') return env.set(left.name, rightVal);
|
|
1543
|
+
if (left.type === 'MemberExpression') {
|
|
1544
|
+
const obj = this.evaluate(left.object, env);
|
|
1545
|
+
obj[left.property] = rightVal;
|
|
1546
|
+
return rightVal;
|
|
1547
|
+
}
|
|
1548
|
+
if (left.type === 'IndexExpression') {
|
|
1549
|
+
const obj = this.evaluate(left.object, env);
|
|
1550
|
+
const idx = this.evaluate(left.indexer, env);
|
|
1551
|
+
obj[idx] = rightVal;
|
|
1552
|
+
return rightVal;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
throw new Error('Invalid assignment target');
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
evalCompoundAssignment(node, env) {
|
|
1559
|
+
const left = node.left;
|
|
1560
|
+
let current;
|
|
1561
|
+
|
|
1562
|
+
if (left.type === 'Identifier') current = env.get(left.name);
|
|
1563
|
+
else if (left.type === 'MemberExpression') current = this.evalMember(left, env);
|
|
1564
|
+
else if (left.type === 'IndexExpression') current = this.evalIndex(left, env);
|
|
1565
|
+
else throw new Error('Invalid compound assignment target');
|
|
1566
|
+
|
|
1567
|
+
const rhs = this.evaluate(node.right, env);
|
|
1568
|
+
let computed;
|
|
1569
|
+
switch (node.operator) {
|
|
1570
|
+
case 'PLUSEQ': computed = current + rhs; break;
|
|
1571
|
+
case 'MINUSEQ': computed = current - rhs; break;
|
|
1572
|
+
case 'STAREQ': computed = current * rhs; break;
|
|
1573
|
+
case 'SLASHEQ': computed = current / rhs; break;
|
|
1574
|
+
case 'MODEQ': computed = current % rhs; break;
|
|
1575
|
+
default: throw new Error('Unknown compound operator');
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
if (left.type === 'Identifier') env.set(left.name, computed);
|
|
1579
|
+
else if (left.type === 'MemberExpression') this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
1580
|
+
else this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
1581
|
+
|
|
1582
|
+
return computed;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
evalSldeploy(node, env) {
|
|
1586
|
+
const val = this.evaluate(node.expr, env);
|
|
1587
|
+
console.log(val);
|
|
1588
|
+
return val;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
evalAsk(node, env) {
|
|
1592
|
+
const prompt = this.evaluate(node.prompt, env);
|
|
1593
|
+
const input = readlineSync.question(prompt + ' ');
|
|
1594
|
+
return input;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
evalDefine(node, env) {
|
|
1598
|
+
const val = node.expr ? this.evaluate(node.expr, env) : null;
|
|
1599
|
+
return this.global.define(node.id, val);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
evalBinary(node, env) {
|
|
1603
|
+
const l = this.evaluate(node.left, env);
|
|
1604
|
+
const r = this.evaluate(node.right, env);
|
|
1605
|
+
switch (node.operator) {
|
|
1606
|
+
case 'PLUS': return l + r;
|
|
1607
|
+
case 'MINUS': return l - r;
|
|
1608
|
+
case 'STAR': return l * r;
|
|
1609
|
+
case 'SLASH': return l / r;
|
|
1610
|
+
case 'MOD': return l % r;
|
|
1611
|
+
case 'EQEQ': return l === r;
|
|
1612
|
+
case 'NOTEQ': return l !== r;
|
|
1613
|
+
case 'LT': return l < r;
|
|
1614
|
+
case 'LTE': return l <= r;
|
|
1615
|
+
case 'GT': return l > r;
|
|
1616
|
+
case 'GTE': return l >= r;
|
|
1617
|
+
default: throw new Error(`Unknown binary operator ${node.operator}`);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
evalLogical(node, env) {
|
|
1622
|
+
const l = this.evaluate(node.left, env);
|
|
1623
|
+
if (node.operator === 'AND') return l && this.evaluate(node.right, env);
|
|
1624
|
+
if (node.operator === 'OR') return l || this.evaluate(node.right, env);
|
|
1625
|
+
throw new Error(`Unknown logical operator ${node.operator}`);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
evalUnary(node, env) {
|
|
1629
|
+
const val = this.evaluate(node.argument, env);
|
|
1630
|
+
switch (node.operator) {
|
|
1631
|
+
case 'NOT': return !val;
|
|
1632
|
+
case 'MINUS': return -val;
|
|
1633
|
+
case 'PLUS': return +val;
|
|
1634
|
+
default: throw new Error(`Unknown unary operator ${node.operator}`);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
evalIf(node, env) {
|
|
1639
|
+
const test = this.evaluate(node.test, env);
|
|
1640
|
+
if (test) return this.evaluate(node.consequent, env);
|
|
1641
|
+
if (node.alternate) return this.evaluate(node.alternate, env);
|
|
1642
|
+
return null;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
evalWhile(node, env) {
|
|
1646
|
+
while (this.evaluate(node.test, env)) {
|
|
1647
|
+
try { this.evaluate(node.body, env); }
|
|
1648
|
+
catch (e) {
|
|
1649
|
+
if (e instanceof BreakSignal) break;
|
|
1650
|
+
if (e instanceof ContinueSignal) continue;
|
|
1651
|
+
throw e;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return null;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
evalFor(node, env) {
|
|
1658
|
+
const local = new Environment(env);
|
|
1659
|
+
if (node.init) this.evaluate(node.init, local);
|
|
1660
|
+
while (!node.test || this.evaluate(node.test, local)) {
|
|
1661
|
+
try { this.evaluate(node.body, local); }
|
|
1662
|
+
catch (e) {
|
|
1663
|
+
if (e instanceof BreakSignal) break;
|
|
1664
|
+
if (e instanceof ContinueSignal) { if (node.update) this.evaluate(node.update, local); continue; }
|
|
1665
|
+
throw e;
|
|
1666
|
+
}
|
|
1667
|
+
if (node.update) this.evaluate(node.update, local);
|
|
1668
|
+
}
|
|
1669
|
+
return null;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
evalFunctionDeclaration(node, env) {
|
|
1673
|
+
const fn = { params: node.params, body: node.body, env };
|
|
1674
|
+
env.define(node.name, fn);
|
|
1675
|
+
return null;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
evalCall(node, env) {
|
|
1679
|
+
const calleeEvaluated = this.evaluate(node.callee, env);
|
|
1680
|
+
if (typeof calleeEvaluated === 'function') {
|
|
1681
|
+
const args = node.arguments.map(a => this.evaluate(a, env));
|
|
1682
|
+
return calleeEvaluated(...args);
|
|
1683
|
+
}
|
|
1684
|
+
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
|
|
1685
|
+
throw new Error('Call to non-function');
|
|
1686
|
+
}
|
|
1687
|
+
const fn = calleeEvaluated;
|
|
1688
|
+
const callEnv = new Environment(fn.env);
|
|
1689
|
+
fn.params.forEach((p, i) => {
|
|
1690
|
+
const argVal = node.arguments[i] ? this.evaluate(node.arguments[i], env) : null;
|
|
1691
|
+
callEnv.define(p, argVal);
|
|
1692
|
+
});
|
|
1693
|
+
try { return this.evaluate(fn.body, callEnv); }
|
|
1694
|
+
catch (e) { if (e instanceof ReturnValue) return e.value; throw e; }
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
evalIndex(node, env) {
|
|
1698
|
+
const obj = this.evaluate(node.object, env);
|
|
1699
|
+
const idx = this.evaluate(node.indexer, env);
|
|
1700
|
+
if (obj == null) throw new Error('Indexing null/undefined');
|
|
1701
|
+
return obj[idx];
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
evalObject(node, env) {
|
|
1705
|
+
const out = {};
|
|
1706
|
+
for (const p of node.props) out[p.key] = this.evaluate(p.value, env);
|
|
1707
|
+
return out;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
evalMember(node, env) {
|
|
1711
|
+
const obj = this.evaluate(node.object, env);
|
|
1712
|
+
if (obj == null) throw new Error('Member access of null/undefined');
|
|
1713
|
+
return obj[node.property];
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
evalUpdate(node, env) {
|
|
1717
|
+
const arg = node.argument;
|
|
1718
|
+
const getCurrent = () => {
|
|
1719
|
+
if (arg.type === 'Identifier') return env.get(arg.name);
|
|
1720
|
+
if (arg.type === 'MemberExpression') return this.evalMember(arg, env);
|
|
1721
|
+
if (arg.type === 'IndexExpression') return this.evalIndex(arg, env);
|
|
1722
|
+
throw new Error('Invalid update target');
|
|
1723
|
+
};
|
|
1724
|
+
const setValue = (v) => {
|
|
1725
|
+
if (arg.type === 'Identifier') env.set(arg.name, v);
|
|
1726
|
+
else if (arg.type === 'MemberExpression') {
|
|
1727
|
+
const obj = this.evaluate(arg.object, env);
|
|
1728
|
+
obj[arg.property] = v;
|
|
1729
|
+
}
|
|
1730
|
+
else if (arg.type === 'IndexExpression') {
|
|
1731
|
+
const obj = this.evaluate(arg.object, env);
|
|
1732
|
+
const idx = this.evaluate(arg.indexer, env);
|
|
1733
|
+
obj[idx] = v;
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
const current = getCurrent();
|
|
1737
|
+
const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
|
|
1738
|
+
if (node.prefix) { setValue(newVal); return newVal; }
|
|
1739
|
+
else { setValue(newVal); return current; }
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
module.exports = Evaluator;
|
|
1744
|
+
|
|
1745
|
+
/***/ }),
|
|
1746
|
+
|
|
1747
|
+
/***/ 211:
|
|
1748
|
+
/***/ ((module) => {
|
|
1749
|
+
|
|
1750
|
+
class Lexer {
|
|
1751
|
+
constructor(input) {
|
|
1752
|
+
this.input = input;
|
|
1753
|
+
this.pos = 0;
|
|
1754
|
+
this.currentChar = input[0] || null;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
advance() {
|
|
1758
|
+
this.pos++;
|
|
1759
|
+
this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
peek() {
|
|
1763
|
+
return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
error(msg) {
|
|
1767
|
+
throw new Error(`LEXER ERROR: ${msg} at position ${this.pos}`);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
skipWhitespace() {
|
|
1771
|
+
while (this.currentChar && /\s/.test(this.currentChar)) this.advance();
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
skipComment() {
|
|
1775
|
+
if (this.currentChar === '#') {
|
|
1776
|
+
if (this.peek() === '*') {
|
|
1777
|
+
this.advance(); this.advance(); // skip #*
|
|
1778
|
+
while (this.currentChar !== null) {
|
|
1779
|
+
if (this.currentChar === '*' && this.peek() === '#') {
|
|
1780
|
+
this.advance(); this.advance();
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
this.advance();
|
|
1784
|
+
}
|
|
1785
|
+
this.error("Unterminated multi-line comment (#* ... *#)");
|
|
1786
|
+
} else {
|
|
1787
|
+
while (this.currentChar && this.currentChar !== '\n') this.advance();
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
number() {
|
|
1793
|
+
let result = '';
|
|
1794
|
+
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1795
|
+
result += this.currentChar;
|
|
1796
|
+
this.advance();
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
|
|
1800
|
+
result += '.'; this.advance();
|
|
1801
|
+
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1802
|
+
result += this.currentChar;
|
|
1803
|
+
this.advance();
|
|
1804
|
+
}
|
|
1805
|
+
return { type: 'NUMBER', value: parseFloat(result) };
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
return { type: 'NUMBER', value: parseInt(result) };
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
identifier() {
|
|
1812
|
+
let result = '';
|
|
1813
|
+
while (this.currentChar && /[A-Za-z0-9_]/.test(this.currentChar)) {
|
|
1814
|
+
result += this.currentChar;
|
|
1815
|
+
this.advance();
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
const keywords = [
|
|
1819
|
+
'let', 'sldeploy', 'if', 'else', 'while', 'for',
|
|
1820
|
+
'break', 'continue', 'func', 'return', 'true', 'false', 'null',
|
|
1821
|
+
'ask', 'define', 'import', 'from', 'as'
|
|
1822
|
+
];
|
|
1823
|
+
|
|
1824
|
+
if (keywords.includes(result)) {
|
|
1825
|
+
return { type: result.toUpperCase(), value: result };
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
return { type: 'IDENTIFIER', value: result };
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
string() {
|
|
1833
|
+
const quote = this.currentChar;
|
|
1834
|
+
this.advance();
|
|
1835
|
+
let result = '';
|
|
1836
|
+
|
|
1837
|
+
while (this.currentChar && this.currentChar !== quote) {
|
|
1838
|
+
if (this.currentChar === '\\') {
|
|
1839
|
+
this.advance();
|
|
1840
|
+
switch (this.currentChar) {
|
|
1841
|
+
case 'n': result += '\n'; break;
|
|
1842
|
+
case 't': result += '\t'; break;
|
|
1843
|
+
case '"': result += '"'; break;
|
|
1844
|
+
case "'": result += "'"; break;
|
|
1845
|
+
case '\\': result += '\\'; break;
|
|
1846
|
+
default: result += this.currentChar;
|
|
1847
|
+
}
|
|
1848
|
+
} else {
|
|
1849
|
+
result += this.currentChar;
|
|
1850
|
+
}
|
|
1851
|
+
this.advance();
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
if (this.currentChar !== quote) {
|
|
1855
|
+
this.error('Unterminated string literal');
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
this.advance();
|
|
1859
|
+
return { type: 'STRING', value: result };
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
getTokens() {
|
|
1863
|
+
const tokens = [];
|
|
1864
|
+
|
|
1865
|
+
while (this.currentChar !== null) {
|
|
1866
|
+
if (/\s/.test(this.currentChar)) { this.skipWhitespace(); continue; }
|
|
1867
|
+
if (this.currentChar === '#') { this.skipComment(); continue; }
|
|
1868
|
+
if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
|
|
1869
|
+
if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
|
|
1870
|
+
if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
|
|
1871
|
+
|
|
1872
|
+
const char = this.currentChar;
|
|
1873
|
+
const next = this.peek();
|
|
1874
|
+
|
|
1875
|
+
if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ' }); this.advance(); this.advance(); continue; }
|
|
1876
|
+
if (char === '=' && next === '>') { tokens.push({ type: 'ARROW' }); this.advance(); this.advance(); continue; }
|
|
1877
|
+
if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ' }); this.advance(); this.advance(); continue; }
|
|
1878
|
+
if (char === '<' && next === '=') { tokens.push({ type: 'LTE' }); this.advance(); this.advance(); continue; }
|
|
1879
|
+
if (char === '>' && next === '=') { tokens.push({ type: 'GTE' }); this.advance(); this.advance(); continue; }
|
|
1880
|
+
if (char === '&' && next === '&') { tokens.push({ type: 'AND' }); this.advance(); this.advance(); continue; }
|
|
1881
|
+
if (char === '|' && next === '|') { tokens.push({ type: 'OR' }); this.advance(); this.advance(); continue; }
|
|
1882
|
+
if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
|
|
1883
|
+
if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS' }); this.advance(); this.advance(); continue; }
|
|
1884
|
+
|
|
1885
|
+
const map = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
|
|
1886
|
+
if (next === '=' && map[char]) { tokens.push({ type: map[char] }); this.advance(); this.advance(); continue; }
|
|
1887
|
+
|
|
1888
|
+
const singles = {
|
|
1889
|
+
'+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
|
|
1890
|
+
'=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
|
|
1891
|
+
'(': 'LPAREN', ')': 'RPAREN', '{': 'LBRACE', '}': 'RBRACE',
|
|
1892
|
+
';': 'SEMICOLON', ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET',
|
|
1893
|
+
':': 'COLON', '.': 'DOT'
|
|
1894
|
+
};
|
|
1895
|
+
|
|
1896
|
+
if (singles[char]) { tokens.push({ type: singles[char] }); this.advance(); continue; }
|
|
1897
|
+
|
|
1898
|
+
this.error("Unexpected character: " + char);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
tokens.push({ type: 'EOF' });
|
|
1902
|
+
return tokens;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
module.exports = Lexer;
|
|
1907
|
+
|
|
1908
|
+
/***/ }),
|
|
1909
|
+
|
|
1910
|
+
/***/ 222:
|
|
1911
|
+
/***/ ((module) => {
|
|
1912
|
+
|
|
1913
|
+
class Parser {
|
|
1914
|
+
constructor(tokens) {
|
|
1915
|
+
this.tokens = tokens;
|
|
1916
|
+
this.pos = 0;
|
|
1917
|
+
this.current = this.tokens[this.pos];
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
advance() {
|
|
1921
|
+
this.pos++;
|
|
1922
|
+
this.current = this.pos < this.tokens.length ? this.tokens[this.pos] : { type: 'EOF' };
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
eat(type) {
|
|
1926
|
+
if (this.current.type === type) this.advance();
|
|
1927
|
+
else throw new Error(`Expected ${type}, got ${this.current.type}`);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
peekType(offset = 1) {
|
|
1931
|
+
return (this.tokens[this.pos + offset] || { type: 'EOF' }).type;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
parse() {
|
|
1935
|
+
const body = [];
|
|
1936
|
+
while (this.current.type !== 'EOF') {
|
|
1937
|
+
body.push(this.statement());
|
|
1938
|
+
}
|
|
1939
|
+
return { type: 'Program', body };
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
statement() {
|
|
1943
|
+
switch (this.current.type) {
|
|
1944
|
+
case 'LET': return this.varDeclaration();
|
|
1945
|
+
case 'SLDEPLOY': return this.sldeployStatement();
|
|
1946
|
+
case 'DEFINE': return this.defineStatement();
|
|
1947
|
+
case 'IF': return this.ifStatement();
|
|
1948
|
+
case 'WHILE': return this.whileStatement();
|
|
1949
|
+
case 'FOR': return this.forStatement();
|
|
1950
|
+
case 'BREAK': return this.breakStatement();
|
|
1951
|
+
case 'CONTINUE': return this.continueStatement();
|
|
1952
|
+
case 'FUNC': return this.funcDeclaration();
|
|
1953
|
+
case 'RETURN': return this.returnStatement();
|
|
1954
|
+
case 'IMPORT': return this.importStatement();
|
|
1955
|
+
case 'LBRACE': return this.block();
|
|
1956
|
+
default:
|
|
1957
|
+
return this.expressionStatement();
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
varDeclaration() {
|
|
1962
|
+
this.eat('LET');
|
|
1963
|
+
const id = this.current.value;
|
|
1964
|
+
this.eat('IDENTIFIER');
|
|
1965
|
+
this.eat('EQUAL');
|
|
1966
|
+
const expr = this.expression();
|
|
1967
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
1968
|
+
return { type: 'VarDeclaration', id, expr };
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
sldeployStatement() {
|
|
1972
|
+
this.eat('SLDEPLOY');
|
|
1973
|
+
const expr = this.expression();
|
|
1974
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
1975
|
+
return { type: 'SldeployStatement', expr };
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
defineStatement() {
|
|
1979
|
+
this.eat('DEFINE');
|
|
1980
|
+
const id = this.current.value;
|
|
1981
|
+
this.eat('IDENTIFIER');
|
|
1982
|
+
let expr = null;
|
|
1983
|
+
if (this.current.type === 'EQUAL') {
|
|
1984
|
+
this.eat('EQUAL');
|
|
1985
|
+
expr = this.expression();
|
|
1986
|
+
}
|
|
1987
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
1988
|
+
return { type: 'DefineStatement', id, expr };
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
ifStatement() {
|
|
1992
|
+
this.eat('IF');
|
|
1993
|
+
this.eat('LPAREN');
|
|
1994
|
+
const test = this.expression();
|
|
1995
|
+
this.eat('RPAREN');
|
|
1996
|
+
const consequent = this.block();
|
|
1997
|
+
let alternate = null;
|
|
1998
|
+
if (this.current.type === 'ELSE') {
|
|
1999
|
+
this.eat('ELSE');
|
|
2000
|
+
if (this.current.type === 'IF') alternate = this.ifStatement();
|
|
2001
|
+
else alternate = this.block();
|
|
2002
|
+
}
|
|
2003
|
+
return { type: 'IfStatement', test, consequent, alternate };
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
whileStatement() {
|
|
2007
|
+
this.eat('WHILE');
|
|
2008
|
+
this.eat('LPAREN');
|
|
2009
|
+
const test = this.expression();
|
|
2010
|
+
this.eat('RPAREN');
|
|
2011
|
+
const body = this.block();
|
|
2012
|
+
return { type: 'WhileStatement', test, body };
|
|
2013
|
+
}
|
|
2014
|
+
importStatement() {
|
|
2015
|
+
this.eat('IMPORT');
|
|
2016
|
+
|
|
2017
|
+
let specifiers = [];
|
|
2018
|
+
|
|
2019
|
+
if (this.current.type === 'STAR') {
|
|
2020
|
+
this.eat('STAR');
|
|
2021
|
+
this.eat('AS');
|
|
2022
|
+
const name = this.current.value;
|
|
2023
|
+
this.eat('IDENTIFIER');
|
|
2024
|
+
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
2025
|
+
}
|
|
2026
|
+
else if (this.current.type === 'LBRACE') {
|
|
2027
|
+
this.eat('LBRACE');
|
|
2028
|
+
while (this.current.type !== 'RBRACE') {
|
|
2029
|
+
const importedName = this.current.value;
|
|
2030
|
+
this.eat('IDENTIFIER');
|
|
2031
|
+
let localName = importedName;
|
|
2032
|
+
if (this.current.type === 'AS') {
|
|
2033
|
+
this.eat('AS');
|
|
2034
|
+
localName = this.current.value;
|
|
2035
|
+
this.eat('IDENTIFIER');
|
|
2036
|
+
}
|
|
2037
|
+
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2038
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2039
|
+
}
|
|
2040
|
+
this.eat('RBRACE');
|
|
2041
|
+
}
|
|
2042
|
+
else if (this.current.type === 'IDENTIFIER') {
|
|
2043
|
+
const localName = this.current.value;
|
|
2044
|
+
this.eat('IDENTIFIER');
|
|
2045
|
+
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
2046
|
+
} else {
|
|
2047
|
+
throw new Error('Unexpected token in import statement');
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
this.eat('FROM');
|
|
2051
|
+
const pathToken = this.current;
|
|
2052
|
+
if (pathToken.type !== 'STRING') throw new Error('Expected string literal after from in import');
|
|
2053
|
+
this.eat('STRING');
|
|
2054
|
+
|
|
2055
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2056
|
+
|
|
2057
|
+
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
|
|
2061
|
+
forStatement() {
|
|
2062
|
+
this.eat('FOR');
|
|
2063
|
+
this.eat('LPAREN');
|
|
2064
|
+
|
|
2065
|
+
let init = null;
|
|
2066
|
+
if (this.current.type !== 'SEMICOLON') {
|
|
2067
|
+
init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
|
|
2068
|
+
} else {
|
|
2069
|
+
this.eat('SEMICOLON');
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
let test = null;
|
|
2073
|
+
if (this.current.type !== 'SEMICOLON') test = this.expression();
|
|
2074
|
+
this.eat('SEMICOLON');
|
|
2075
|
+
|
|
2076
|
+
let update = null;
|
|
2077
|
+
if (this.current.type !== 'RPAREN') update = this.expression();
|
|
2078
|
+
this.eat('RPAREN');
|
|
2079
|
+
|
|
2080
|
+
const body = this.block();
|
|
2081
|
+
return { type: 'ForStatement', init, test, update, body };
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
breakStatement() {
|
|
2085
|
+
this.eat('BREAK');
|
|
2086
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2087
|
+
return { type: 'BreakStatement' };
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
continueStatement() {
|
|
2091
|
+
this.eat('CONTINUE');
|
|
2092
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2093
|
+
return { type: 'ContinueStatement' };
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
funcDeclaration() {
|
|
2097
|
+
this.eat('FUNC');
|
|
2098
|
+
const name = this.current.value;
|
|
2099
|
+
this.eat('IDENTIFIER');
|
|
2100
|
+
this.eat('LPAREN');
|
|
2101
|
+
const params = [];
|
|
2102
|
+
if (this.current.type !== 'RPAREN') {
|
|
2103
|
+
params.push(this.current.value);
|
|
2104
|
+
this.eat('IDENTIFIER');
|
|
2105
|
+
while (this.current.type === 'COMMA') {
|
|
2106
|
+
this.eat('COMMA');
|
|
2107
|
+
params.push(this.current.value);
|
|
2108
|
+
this.eat('IDENTIFIER');
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
this.eat('RPAREN');
|
|
2112
|
+
const body = this.block();
|
|
2113
|
+
return { type: 'FunctionDeclaration', name, params, body };
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
returnStatement() {
|
|
2117
|
+
this.eat('RETURN');
|
|
2118
|
+
let argument = null;
|
|
2119
|
+
if (this.current.type !== 'SEMICOLON') argument = this.expression();
|
|
2120
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2121
|
+
return { type: 'ReturnStatement', argument };
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
block() {
|
|
2125
|
+
this.eat('LBRACE');
|
|
2126
|
+
const body = [];
|
|
2127
|
+
while (this.current.type !== 'RBRACE') {
|
|
2128
|
+
body.push(this.statement());
|
|
2129
|
+
}
|
|
2130
|
+
this.eat('RBRACE');
|
|
2131
|
+
return { type: 'BlockStatement', body };
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
expressionStatement() {
|
|
2135
|
+
const expr = this.expression();
|
|
2136
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2137
|
+
return { type: 'ExpressionStatement', expression: expr };
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
expression() {
|
|
2141
|
+
return this.assignment();
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
assignment() {
|
|
2145
|
+
const node = this.logicalOr();
|
|
2146
|
+
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
2147
|
+
if (compoundOps.includes(this.current.type)) {
|
|
2148
|
+
const op = this.current.type;
|
|
2149
|
+
this.eat(op);
|
|
2150
|
+
const right = this.assignment();
|
|
2151
|
+
return { type: 'CompoundAssignment', operator: op, left: node, right };
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
if (this.current.type === 'EQUAL') {
|
|
2155
|
+
this.eat('EQUAL');
|
|
2156
|
+
const right = this.assignment();
|
|
2157
|
+
return { type: 'AssignmentExpression', left: node, right };
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
return node;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
logicalOr() {
|
|
2164
|
+
let node = this.logicalAnd();
|
|
2165
|
+
while (this.current.type === 'OR') {
|
|
2166
|
+
const op = this.current.type;
|
|
2167
|
+
this.eat(op);
|
|
2168
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
|
|
2169
|
+
}
|
|
2170
|
+
return node;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
logicalAnd() {
|
|
2174
|
+
let node = this.equality();
|
|
2175
|
+
while (this.current.type === 'AND') {
|
|
2176
|
+
const op = this.current.type;
|
|
2177
|
+
this.eat(op);
|
|
2178
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
|
|
2179
|
+
}
|
|
2180
|
+
return node;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
equality() {
|
|
2184
|
+
let node = this.comparison();
|
|
2185
|
+
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
2186
|
+
const op = this.current.type;
|
|
2187
|
+
this.eat(op);
|
|
2188
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
2189
|
+
}
|
|
2190
|
+
return node;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
comparison() {
|
|
2194
|
+
let node = this.term();
|
|
2195
|
+
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
2196
|
+
const op = this.current.type;
|
|
2197
|
+
this.eat(op);
|
|
2198
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
2199
|
+
}
|
|
2200
|
+
return node;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
term() {
|
|
2204
|
+
let node = this.factor();
|
|
2205
|
+
while (['PLUS', 'MINUS'].includes(this.current.type)) {
|
|
2206
|
+
const op = this.current.type;
|
|
2207
|
+
this.eat(op);
|
|
2208
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
|
|
2209
|
+
}
|
|
2210
|
+
return node;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
factor() {
|
|
2214
|
+
let node = this.unary();
|
|
2215
|
+
while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
|
|
2216
|
+
const op = this.current.type;
|
|
2217
|
+
this.eat(op);
|
|
2218
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
|
|
2219
|
+
}
|
|
2220
|
+
return node;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
unary() {
|
|
2224
|
+
if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
|
|
2225
|
+
const op = this.current.type;
|
|
2226
|
+
this.eat(op);
|
|
2227
|
+
return { type: 'UnaryExpression', operator: op, argument: this.unary() };
|
|
2228
|
+
}
|
|
2229
|
+
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
2230
|
+
const op = this.current.type;
|
|
2231
|
+
this.eat(op);
|
|
2232
|
+
const argument = this.unary();
|
|
2233
|
+
return { type: 'UpdateExpression', operator: op, argument, prefix: true };
|
|
2234
|
+
}
|
|
2235
|
+
return this.postfix();
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
postfix() {
|
|
2239
|
+
let node = this.primary();
|
|
2240
|
+
while (true) {
|
|
2241
|
+
if (this.current.type === 'LBRACKET') {
|
|
2242
|
+
this.eat('LBRACKET');
|
|
2243
|
+
const index = this.expression();
|
|
2244
|
+
this.eat('RBRACKET');
|
|
2245
|
+
node = { type: 'IndexExpression', object: node, indexer: index };
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
2248
|
+
if (this.current.type === 'LPAREN') {
|
|
2249
|
+
this.eat('LPAREN');
|
|
2250
|
+
const args = [];
|
|
2251
|
+
if (this.current.type !== 'RPAREN') {
|
|
2252
|
+
args.push(this.expression());
|
|
2253
|
+
while (this.current.type === 'COMMA') {
|
|
2254
|
+
this.eat('COMMA');
|
|
2255
|
+
args.push(this.expression());
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
this.eat('RPAREN');
|
|
2259
|
+
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
if (this.current.type === 'DOT') {
|
|
2263
|
+
this.eat('DOT');
|
|
2264
|
+
if (this.current.type !== 'IDENTIFIER') throw new Error('Expected property name after dot');
|
|
2265
|
+
const property = this.current.value;
|
|
2266
|
+
this.eat('IDENTIFIER');
|
|
2267
|
+
node = { type: 'MemberExpression', object: node, property };
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
2271
|
+
const op = this.current.type;
|
|
2272
|
+
this.eat(op);
|
|
2273
|
+
node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
|
|
2274
|
+
continue;
|
|
2275
|
+
}
|
|
2276
|
+
break;
|
|
2277
|
+
}
|
|
2278
|
+
return node;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
primary() {
|
|
2282
|
+
const t = this.current;
|
|
2283
|
+
|
|
2284
|
+
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
2285
|
+
if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
|
|
2286
|
+
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
2287
|
+
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
2288
|
+
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|
|
2289
|
+
|
|
2290
|
+
if (t.type === 'ASK') {
|
|
2291
|
+
this.eat('ASK');
|
|
2292
|
+
this.eat('LPAREN');
|
|
2293
|
+
const args = [];
|
|
2294
|
+
if (this.current.type !== 'RPAREN') {
|
|
2295
|
+
args.push(this.expression());
|
|
2296
|
+
while (this.current.type === 'COMMA') {
|
|
2297
|
+
this.eat('COMMA');
|
|
2298
|
+
args.push(this.expression());
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
this.eat('RPAREN');
|
|
2302
|
+
return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
if (t.type === 'IDENTIFIER') {
|
|
2306
|
+
const name = t.value;
|
|
2307
|
+
this.eat('IDENTIFIER');
|
|
2308
|
+
return { type: 'Identifier', name };
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
if (t.type === 'LPAREN') {
|
|
2312
|
+
this.eat('LPAREN');
|
|
2313
|
+
const expr = this.expression();
|
|
2314
|
+
this.eat('RPAREN');
|
|
2315
|
+
return expr;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
if (t.type === 'LBRACKET') {
|
|
2319
|
+
this.eat('LBRACKET');
|
|
2320
|
+
const elements = [];
|
|
2321
|
+
if (this.current.type !== 'RBRACKET') {
|
|
2322
|
+
elements.push(this.expression());
|
|
2323
|
+
while (this.current.type === 'COMMA') {
|
|
2324
|
+
this.eat('COMMA');
|
|
2325
|
+
elements.push(this.expression());
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
this.eat('RBRACKET');
|
|
2329
|
+
return { type: 'ArrayExpression', elements };
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
if (t.type === 'LBRACE') {
|
|
2333
|
+
this.eat('LBRACE');
|
|
2334
|
+
const props = [];
|
|
2335
|
+
while (this.current.type !== 'RBRACE') {
|
|
2336
|
+
let key;
|
|
2337
|
+
if (this.current.type === 'STRING') { key = this.current.value; this.eat('STRING'); }
|
|
2338
|
+
else if (this.current.type === 'IDENTIFIER') { key = this.current.value; this.eat('IDENTIFIER'); }
|
|
2339
|
+
else throw new Error('Invalid object key');
|
|
2340
|
+
this.eat('COLON');
|
|
2341
|
+
const value = this.expression();
|
|
2342
|
+
props.push({ key, value });
|
|
2343
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2344
|
+
}
|
|
2345
|
+
this.eat('RBRACE');
|
|
2346
|
+
return { type: 'ObjectExpression', props };
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
throw new Error(`Unexpected token in primary: ${t.type}`);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
module.exports = Parser;
|
|
2354
|
+
|
|
2355
|
+
/***/ }),
|
|
2356
|
+
|
|
2357
|
+
/***/ 317:
|
|
2358
|
+
/***/ ((module) => {
|
|
2359
|
+
|
|
2360
|
+
"use strict";
|
|
2361
|
+
module.exports = require("child_process");
|
|
2362
|
+
|
|
2363
|
+
/***/ }),
|
|
2364
|
+
|
|
2365
|
+
/***/ 982:
|
|
2366
|
+
/***/ ((module) => {
|
|
2367
|
+
|
|
2368
|
+
"use strict";
|
|
2369
|
+
module.exports = require("crypto");
|
|
2370
|
+
|
|
2371
|
+
/***/ }),
|
|
2372
|
+
|
|
2373
|
+
/***/ 896:
|
|
2374
|
+
/***/ ((module) => {
|
|
2375
|
+
|
|
2376
|
+
"use strict";
|
|
2377
|
+
module.exports = require("fs");
|
|
2378
|
+
|
|
2379
|
+
/***/ }),
|
|
2380
|
+
|
|
2381
|
+
/***/ 857:
|
|
2382
|
+
/***/ ((module) => {
|
|
2383
|
+
|
|
2384
|
+
"use strict";
|
|
2385
|
+
module.exports = require("os");
|
|
2386
|
+
|
|
2387
|
+
/***/ }),
|
|
2388
|
+
|
|
2389
|
+
/***/ 928:
|
|
2390
|
+
/***/ ((module) => {
|
|
2391
|
+
|
|
2392
|
+
"use strict";
|
|
2393
|
+
module.exports = require("path");
|
|
2394
|
+
|
|
2395
|
+
/***/ })
|
|
2396
|
+
|
|
2397
|
+
/******/ });
|
|
2398
|
+
/************************************************************************/
|
|
2399
|
+
/******/ // The module cache
|
|
2400
|
+
/******/ var __webpack_module_cache__ = {};
|
|
2401
|
+
/******/
|
|
2402
|
+
/******/ // The require function
|
|
2403
|
+
/******/ function __nccwpck_require__(moduleId) {
|
|
2404
|
+
/******/ // Check if module is in cache
|
|
2405
|
+
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
|
2406
|
+
/******/ if (cachedModule !== undefined) {
|
|
2407
|
+
/******/ return cachedModule.exports;
|
|
2408
|
+
/******/ }
|
|
2409
|
+
/******/ // Create a new module (and put it into the cache)
|
|
2410
|
+
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
2411
|
+
/******/ // no module.id needed
|
|
2412
|
+
/******/ // no module.loaded needed
|
|
2413
|
+
/******/ exports: {}
|
|
2414
|
+
/******/ };
|
|
2415
|
+
/******/
|
|
2416
|
+
/******/ // Execute the module function
|
|
2417
|
+
/******/ var threw = true;
|
|
2418
|
+
/******/ try {
|
|
2419
|
+
/******/ __webpack_modules__[moduleId](module, module.exports, __nccwpck_require__);
|
|
2420
|
+
/******/ threw = false;
|
|
2421
|
+
/******/ } finally {
|
|
2422
|
+
/******/ if(threw) delete __webpack_module_cache__[moduleId];
|
|
2423
|
+
/******/ }
|
|
2424
|
+
/******/
|
|
2425
|
+
/******/ // Return the exports of the module
|
|
2426
|
+
/******/ return module.exports;
|
|
2427
|
+
/******/ }
|
|
2428
|
+
/******/
|
|
2429
|
+
/************************************************************************/
|
|
2430
|
+
/******/ /* webpack/runtime/compat */
|
|
2431
|
+
/******/
|
|
2432
|
+
/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";
|
|
2433
|
+
/******/
|
|
2434
|
+
/************************************************************************/
|
|
2435
|
+
var __webpack_exports__ = {};
|
|
2436
|
+
const fs = __nccwpck_require__(896);
|
|
2437
|
+
const path = __nccwpck_require__(928);
|
|
2438
|
+
|
|
2439
|
+
const Lexer = __nccwpck_require__(211);
|
|
2440
|
+
const Parser = __nccwpck_require__(222);
|
|
2441
|
+
const Evaluator = __nccwpck_require__(112);
|
|
2442
|
+
|
|
2443
|
+
const COLOR = {
|
|
2444
|
+
reset: '\x1b[0m',
|
|
2445
|
+
bold: '\x1b[1m',
|
|
2446
|
+
red: '\x1b[31m',
|
|
2447
|
+
yellow: '\x1b[33m',
|
|
2448
|
+
blue: '\x1b[34m',
|
|
2449
|
+
cyan: '\x1b[36m',
|
|
2450
|
+
gray: '\x1b[90m',
|
|
2451
|
+
green: '\x1b[32m'
|
|
2452
|
+
};
|
|
2453
|
+
|
|
2454
|
+
function waitAndExit(code = 1) {
|
|
2455
|
+
console.error(COLOR.gray + '\nPress any key to exit...' + COLOR.reset);
|
|
2456
|
+
process.stdin.setRawMode(true);
|
|
2457
|
+
process.stdin.resume();
|
|
2458
|
+
process.stdin.once('data', () => process.exit(code));
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
function fatal(msg) {
|
|
2462
|
+
console.error(COLOR.red + msg + COLOR.reset);
|
|
2463
|
+
return waitAndExit(1);
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
function printSourceContext(code, line, column) {
|
|
2467
|
+
const lines = code.split('\n');
|
|
2468
|
+
const srcLine = lines[line - 1];
|
|
2469
|
+
if (!srcLine) return;
|
|
2470
|
+
|
|
2471
|
+
console.error(
|
|
2472
|
+
COLOR.gray + `\n ${line} | ` + COLOR.reset + srcLine
|
|
2473
|
+
);
|
|
2474
|
+
console.error(
|
|
2475
|
+
COLOR.gray + ' | ' +
|
|
2476
|
+
COLOR.red + ' '.repeat(Math.max(0, column - 1)) + '^' +
|
|
2477
|
+
COLOR.reset
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
const args = process.argv.slice(2);
|
|
2482
|
+
|
|
2483
|
+
if (args.length === 0) {
|
|
2484
|
+
return fatal('Usage: starlight <file.sl>');
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
const filePath = path.resolve(args[0]);
|
|
2488
|
+
|
|
2489
|
+
if (!fs.existsSync(filePath)) {
|
|
2490
|
+
return fatal(`File not found: ${filePath}`);
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
let code;
|
|
2494
|
+
try {
|
|
2495
|
+
code = fs.readFileSync(filePath, 'utf-8');
|
|
2496
|
+
} catch (err) {
|
|
2497
|
+
return fatal(`Failed to read file: ${err.message}`);
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
let tokens;
|
|
2501
|
+
try {
|
|
2502
|
+
const lexer = new Lexer(code);
|
|
2503
|
+
tokens = lexer.getTokens();
|
|
2504
|
+
} catch (err) {
|
|
2505
|
+
console.error(COLOR.red + COLOR.bold + 'Starlight Lexer Error' + COLOR.reset);
|
|
2506
|
+
console.error(COLOR.cyan + `File: ${filePath}` + COLOR.reset);
|
|
2507
|
+
console.error(COLOR.yellow + `Message: ${err.message}` + COLOR.reset);
|
|
2508
|
+
|
|
2509
|
+
if (err.line && err.column) {
|
|
2510
|
+
printSourceContext(code, err.line, err.column);
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
return waitAndExit(1);
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
let ast;
|
|
2517
|
+
try {
|
|
2518
|
+
const parser = new Parser(tokens);
|
|
2519
|
+
ast = parser.parse();
|
|
2520
|
+
} catch (err) {
|
|
2521
|
+
console.error(COLOR.red + COLOR.bold + 'Starlight Parser Error' + COLOR.reset);
|
|
2522
|
+
console.error(COLOR.cyan + `File: ${filePath}` + COLOR.reset);
|
|
2523
|
+
console.error(COLOR.yellow + `Message: ${err.message}` + COLOR.reset);
|
|
2524
|
+
|
|
2525
|
+
if (err.token && err.token.line && err.token.column) {
|
|
2526
|
+
printSourceContext(code, err.token.line, err.token.column);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
return waitAndExit(1);
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
try {
|
|
2533
|
+
const evaluator = new Evaluator();
|
|
2534
|
+
evaluator.evaluate(ast);
|
|
2535
|
+
} catch (err) {
|
|
2536
|
+
console.error(COLOR.red + COLOR.bold + 'Starlight Runtime Error' + COLOR.reset);
|
|
2537
|
+
console.error(COLOR.cyan + `File: ${filePath}` + COLOR.reset);
|
|
2538
|
+
console.error(COLOR.yellow + `Message: ${err.message}` + COLOR.reset);
|
|
2539
|
+
|
|
2540
|
+
if (err.line && err.column) {
|
|
2541
|
+
printSourceContext(code, err.line, err.column);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
return waitAndExit(1);
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
console.log(
|
|
2548
|
+
COLOR.green +
|
|
2549
|
+
'\nProgram finished successfully.' +
|
|
2550
|
+
COLOR.reset
|
|
2551
|
+
);
|
|
2552
|
+
waitAndExit(0);
|
|
2553
|
+
|
|
2554
|
+
module.exports = __webpack_exports__;
|
|
2555
|
+
/******/ })()
|
|
2556
|
+
;
|