nextlua 2.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/main.js +307 -25
package/package.json
CHANGED
package/src/main.js
CHANGED
|
@@ -141,6 +141,115 @@ function readLongBracket(input, index) {
|
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
function decodeStringToken(token) {
|
|
145
|
+
const quote = token[0];
|
|
146
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") {
|
|
147
|
+
return token;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function keepEscape(code) {
|
|
151
|
+
// Only de-uglify printable ASCII; leave control bytes and chars that
|
|
152
|
+
// would need re-escaping (quote/backslash) as their original escapes.
|
|
153
|
+
if (code < 0x20 || code > 0x7e) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const ch = String.fromCharCode(code);
|
|
157
|
+
return ch !== '"' && ch !== "'" && ch !== "`" && ch !== "\\";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Pass 1: split into units — final text, or a preserved decimal escape
|
|
161
|
+
// (kept as { dec } so padding can be decided once we know what follows).
|
|
162
|
+
const units = [];
|
|
163
|
+
function pushText(text) {
|
|
164
|
+
const last = units[units.length - 1];
|
|
165
|
+
if (last && last.text !== undefined) {
|
|
166
|
+
last.text += text;
|
|
167
|
+
} else {
|
|
168
|
+
units.push({ text });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let i = 0;
|
|
173
|
+
while (i < token.length) {
|
|
174
|
+
const ch = token[i];
|
|
175
|
+
if (ch !== "\\") {
|
|
176
|
+
pushText(ch);
|
|
177
|
+
i++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const next = token[i + 1];
|
|
182
|
+
const hexMatch = next === "x" && /^[0-9A-Fa-f]{2}/.test(token.slice(i + 2, i + 4))
|
|
183
|
+
? token.slice(i + 2, i + 4)
|
|
184
|
+
: null;
|
|
185
|
+
if (hexMatch) {
|
|
186
|
+
const code = parseInt(hexMatch, 16);
|
|
187
|
+
if (keepEscape(code)) {
|
|
188
|
+
pushText(String.fromCharCode(code));
|
|
189
|
+
} else {
|
|
190
|
+
// Hex escapes are fixed-width (\xHH), so never ambiguous.
|
|
191
|
+
pushText("\\x" + hexMatch);
|
|
192
|
+
}
|
|
193
|
+
i += 4;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const decMatch = /^[0-9]{1,3}/.exec(token.slice(i + 1, i + 4));
|
|
198
|
+
if (decMatch) {
|
|
199
|
+
const digits = decMatch[0];
|
|
200
|
+
const code = parseInt(digits, 10);
|
|
201
|
+
i += 1 + digits.length;
|
|
202
|
+
if (code <= 0xff && keepEscape(code)) {
|
|
203
|
+
pushText(String.fromCharCode(code));
|
|
204
|
+
} else {
|
|
205
|
+
units.push({ dec: Math.min(code, 0xff) });
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Preserve any other escape sequence verbatim (\n, \t, \\, \" ...).
|
|
211
|
+
pushText(ch + (next === undefined ? "" : next));
|
|
212
|
+
i += next === undefined ? 1 : 2;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Pass 2: serialize. A preserved decimal escape only needs zero-padding to
|
|
216
|
+
// 3 digits when the next output char is a digit (which would otherwise
|
|
217
|
+
// merge into a single, possibly >255, escape).
|
|
218
|
+
let result = "";
|
|
219
|
+
for (let u = 0; u < units.length; u++) {
|
|
220
|
+
const unit = units[u];
|
|
221
|
+
if (unit.text !== undefined) {
|
|
222
|
+
result += unit.text;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const nextUnit = units[u + 1];
|
|
227
|
+
const nextChar = nextUnit && nextUnit.text !== undefined ? nextUnit.text[0] : "";
|
|
228
|
+
result += "\\" + (/[0-9]/.test(nextChar)
|
|
229
|
+
? String(unit.dec).padStart(3, "0")
|
|
230
|
+
: String(unit.dec));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function normalizeNumberToken(token) {
|
|
237
|
+
// Underscore digit separators are stripped (a no-op when none are present).
|
|
238
|
+
const stripped = token.replace(/_/g, "");
|
|
239
|
+
|
|
240
|
+
const hexMatch = /^0[xX]([0-9A-Fa-f]+)$/.exec(stripped);
|
|
241
|
+
if (hexMatch) {
|
|
242
|
+
return BigInt("0x" + hexMatch[1]).toString(10);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const binMatch = /^0[bB]([01]+)$/.exec(stripped);
|
|
246
|
+
if (binMatch) {
|
|
247
|
+
return BigInt("0b" + binMatch[1]).toString(10);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return stripped;
|
|
251
|
+
}
|
|
252
|
+
|
|
144
253
|
function tokenize(input) {
|
|
145
254
|
const tokens = [];
|
|
146
255
|
let i = 0;
|
|
@@ -174,17 +283,26 @@ function tokenize(input) {
|
|
|
174
283
|
if (ch === '"' || ch === "'" || ch === "`") {
|
|
175
284
|
let value = ch;
|
|
176
285
|
i++;
|
|
286
|
+
let escaped = false;
|
|
177
287
|
|
|
178
288
|
while (i < input.length) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
289
|
+
const c = input[i];
|
|
290
|
+
value += c;
|
|
291
|
+
i++;
|
|
292
|
+
if (escaped) {
|
|
293
|
+
escaped = false;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (c === "\\") {
|
|
297
|
+
escaped = true;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (c === ch) {
|
|
182
301
|
break;
|
|
183
302
|
}
|
|
184
|
-
i++;
|
|
185
303
|
}
|
|
186
304
|
|
|
187
|
-
tokens.push(value);
|
|
305
|
+
tokens.push(decodeStringToken(value));
|
|
188
306
|
continue;
|
|
189
307
|
}
|
|
190
308
|
|
|
@@ -217,7 +335,7 @@ function tokenize(input) {
|
|
|
217
335
|
while (end < input.length && /[A-Fa-f0-9_xX.]/.test(input[end])) {
|
|
218
336
|
end++;
|
|
219
337
|
}
|
|
220
|
-
tokens.push(input.slice(i, end));
|
|
338
|
+
tokens.push(normalizeNumberToken(input.slice(i, end)));
|
|
221
339
|
i = end;
|
|
222
340
|
continue;
|
|
223
341
|
}
|
|
@@ -241,6 +359,26 @@ function isLiteral(token) {
|
|
|
241
359
|
return /^["'`]/.test(token) || /^\d/.test(token) || token === "..." || token === "true" || token === "false" || token === "nil";
|
|
242
360
|
}
|
|
243
361
|
|
|
362
|
+
const keywordsSpacedBeforeParen = new Set([
|
|
363
|
+
"if",
|
|
364
|
+
"elseif",
|
|
365
|
+
"while",
|
|
366
|
+
"until",
|
|
367
|
+
"return",
|
|
368
|
+
"and",
|
|
369
|
+
"or",
|
|
370
|
+
"not",
|
|
371
|
+
"in"
|
|
372
|
+
]);
|
|
373
|
+
|
|
374
|
+
function isValueEnd(token) {
|
|
375
|
+
return (isIdentifier(token) && !reservedKeywords.has(token)) ||
|
|
376
|
+
isLiteral(token) ||
|
|
377
|
+
token === ")" ||
|
|
378
|
+
token === "]" ||
|
|
379
|
+
token === "}";
|
|
380
|
+
}
|
|
381
|
+
|
|
244
382
|
function canEndStatement(token) {
|
|
245
383
|
return (isIdentifier(token) && !reservedKeywords.has(token)) || isLiteral(token) || token === ")" || token === "]" || token === "}" || token === "end" || token === "break" || token === "continue";
|
|
246
384
|
}
|
|
@@ -271,6 +409,16 @@ function needsSpace(prev, current) {
|
|
|
271
409
|
}
|
|
272
410
|
|
|
273
411
|
if (current === "(") {
|
|
412
|
+
// Control-flow keywords and binary operators read better with a space
|
|
413
|
+
// before the paren (`if (`, `while (`, `a * (`, `x = (`), but calls
|
|
414
|
+
// like `function(` and `foo(` stay tight. A unary sign before `(` is
|
|
415
|
+
// handled by joinTokens, which suppresses the space (`-(x)`).
|
|
416
|
+
return keywordsSpacedBeforeParen.has(prev) || binaryOperators.has(prev);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (current === "[" && isValueEnd(prev)) {
|
|
420
|
+
// Indexing: `A[x]`, `t.k[x]`, `f()[x]` — no space. Table-constructor
|
|
421
|
+
// keys (`{ [k] = v }`) keep prev as `{`/`,`, so they are unaffected.
|
|
274
422
|
return false;
|
|
275
423
|
}
|
|
276
424
|
|
|
@@ -282,7 +430,9 @@ function needsSpace(prev, current) {
|
|
|
282
430
|
return false;
|
|
283
431
|
}
|
|
284
432
|
|
|
285
|
-
if (prev === "#"
|
|
433
|
+
if (prev === "#") {
|
|
434
|
+
// The length operator binds tightly to its operand: `#h`, never `# h`.
|
|
435
|
+
// The space *before* `#` still follows the normal rules (`= #h`).
|
|
286
436
|
return false;
|
|
287
437
|
}
|
|
288
438
|
|
|
@@ -293,22 +443,75 @@ function needsSpace(prev, current) {
|
|
|
293
443
|
return true;
|
|
294
444
|
}
|
|
295
445
|
|
|
296
|
-
function
|
|
446
|
+
function isGenericOpen(tokens, index) {
|
|
447
|
+
// tokens[index] is "<". Treat it as a generic-parameter list only in a
|
|
448
|
+
// declaration context: `function<T>`, `function foo<T>`, `type X<T>`.
|
|
449
|
+
const prev = tokens[index - 1];
|
|
450
|
+
if (prev === "function" || prev === "type") {
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const prev2 = tokens[index - 2];
|
|
455
|
+
return (prev2 === "function" || prev2 === "type") &&
|
|
456
|
+
isIdentifier(prev) &&
|
|
457
|
+
!reservedKeywords.has(prev);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function isUnarySign(tokens, index) {
|
|
461
|
+
// tokens[index] is "-" or "+". It is unary (binding tightly to its operand
|
|
462
|
+
// with no following space: `-1`, `-x`, `-(y)`, `-#t`) when it does not
|
|
463
|
+
// follow a complete value. Otherwise it is the binary operator (`a - 1`).
|
|
464
|
+
const token = tokens[index];
|
|
465
|
+
if (token !== "-" && token !== "+") {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
const before = tokens[index - 1];
|
|
469
|
+
return before === undefined || !isValueEnd(before);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function joinTokens(tokens) {
|
|
297
473
|
let text = "";
|
|
298
474
|
let prev = null;
|
|
475
|
+
let genericDepth = 0;
|
|
476
|
+
|
|
477
|
+
for (let idx = 0; idx < tokens.length; idx++) {
|
|
478
|
+
const token = tokens[idx];
|
|
299
479
|
|
|
300
|
-
|
|
301
|
-
|
|
480
|
+
const inGeneric = genericDepth > 0;
|
|
481
|
+
const opensGeneric = !inGeneric && token === "<" && isGenericOpen(tokens, idx);
|
|
482
|
+
|
|
483
|
+
// A unary +/- binds to its operand, so suppress the space after it.
|
|
484
|
+
// (Skip the suppression when the next token is itself a +/- so we never
|
|
485
|
+
// fuse two signs into a `--` comment or a `+-` run.)
|
|
486
|
+
const prevIsUnarySign = prev !== null &&
|
|
487
|
+
token !== "-" && token !== "+" &&
|
|
488
|
+
isUnarySign(tokens, idx - 1);
|
|
489
|
+
|
|
490
|
+
if (prev !== null && !inGeneric && !opensGeneric && !prevIsUnarySign && needsSpace(prev, token)) {
|
|
302
491
|
text += " ";
|
|
303
492
|
}
|
|
304
493
|
text += token;
|
|
494
|
+
|
|
495
|
+
// Track generic-bracket nesting so the whole `<...>` stays tight.
|
|
496
|
+
if (opensGeneric || (inGeneric && token === "<")) {
|
|
497
|
+
genericDepth++;
|
|
498
|
+
} else if (inGeneric && token === ">") {
|
|
499
|
+
genericDepth--;
|
|
500
|
+
} else if (inGeneric && token === ">>") {
|
|
501
|
+
genericDepth = Math.max(0, genericDepth - 2);
|
|
502
|
+
}
|
|
503
|
+
|
|
305
504
|
prev = token;
|
|
306
505
|
}
|
|
307
506
|
|
|
308
|
-
return text
|
|
507
|
+
return text;
|
|
309
508
|
}
|
|
310
509
|
|
|
311
|
-
function
|
|
510
|
+
function renderLine(tokens) {
|
|
511
|
+
return joinTokens(tokens).trim();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function layout(input) {
|
|
312
515
|
const tokens = tokenize(input);
|
|
313
516
|
const lines = [];
|
|
314
517
|
let current = [];
|
|
@@ -329,7 +532,7 @@ function beautify(input) {
|
|
|
329
532
|
|
|
330
533
|
lines.push({
|
|
331
534
|
depth: Math.max(0, depth),
|
|
332
|
-
|
|
535
|
+
tokens: current
|
|
333
536
|
});
|
|
334
537
|
current = [];
|
|
335
538
|
}
|
|
@@ -381,7 +584,7 @@ function beautify(input) {
|
|
|
381
584
|
flushCurrent();
|
|
382
585
|
lines.push({
|
|
383
586
|
depth: Math.max(0, depth),
|
|
384
|
-
|
|
587
|
+
tokens: [token]
|
|
385
588
|
});
|
|
386
589
|
continue;
|
|
387
590
|
}
|
|
@@ -390,6 +593,17 @@ function beautify(input) {
|
|
|
390
593
|
flushCurrent();
|
|
391
594
|
}
|
|
392
595
|
|
|
596
|
+
if (token === "else" && inlineIfStack.length && inlineIfStack[inlineIfStack.length - 1] === "else") {
|
|
597
|
+
// The `else` of an inline `if ... then ... else ...` expression.
|
|
598
|
+
// It can appear nested inside parens/brackets (e.g. `x = (if c then
|
|
599
|
+
// a else b)`), so it must be matched regardless of bracket depth —
|
|
600
|
+
// otherwise inlineIfStack never empties and statement-splitting
|
|
601
|
+
// stays disabled for the rest of the block.
|
|
602
|
+
inlineIfStack.pop();
|
|
603
|
+
current.push(token);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
|
|
393
607
|
if (blockMiddle.has(token) && atStatementLevel()) {
|
|
394
608
|
if (token === "else" && inlineIfStack.length) {
|
|
395
609
|
inlineIfStack.pop();
|
|
@@ -494,6 +708,23 @@ function beautify(input) {
|
|
|
494
708
|
}
|
|
495
709
|
|
|
496
710
|
if (token === ";") {
|
|
711
|
+
const table = currentTable();
|
|
712
|
+
const inTable = table &&
|
|
713
|
+
braceDepth === table.brace &&
|
|
714
|
+
parenDepth === table.paren &&
|
|
715
|
+
bracketDepth === table.bracket &&
|
|
716
|
+
blockBases.length === table.blockDepth;
|
|
717
|
+
|
|
718
|
+
if (inTable) {
|
|
719
|
+
// Inside a table constructor a `;` is just a field separator;
|
|
720
|
+
// normalize it to `,`.
|
|
721
|
+
current[current.length - 1] = ",";
|
|
722
|
+
if (table.multiline) {
|
|
723
|
+
flushCurrent();
|
|
724
|
+
}
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
|
|
497
728
|
flushCurrent();
|
|
498
729
|
continue;
|
|
499
730
|
}
|
|
@@ -558,27 +789,78 @@ function beautify(input) {
|
|
|
558
789
|
|
|
559
790
|
flushCurrent();
|
|
560
791
|
|
|
561
|
-
return lines
|
|
562
|
-
|
|
792
|
+
return lines;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function beautify(input) {
|
|
796
|
+
return layout(input)
|
|
797
|
+
.map(line => `${indent.repeat(line.depth)}${renderLine(line.tokens)}`.trimEnd())
|
|
563
798
|
.join("\n");
|
|
564
799
|
}
|
|
565
800
|
|
|
801
|
+
function isIdentChar(ch) {
|
|
802
|
+
return /[A-Za-z0-9_]/.test(ch);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function minifyNeedsSpace(prev, current) {
|
|
806
|
+
if (prev === null) {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const lastA = prev[prev.length - 1];
|
|
811
|
+
const firstB = current[0];
|
|
812
|
+
|
|
813
|
+
// Two word/number tokens would fuse into one (`local`+`x`, `1`+`e`).
|
|
814
|
+
if (isIdentChar(lastA) && isIdentChar(firstB)) {
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// A numeric literal followed by `.`/`..` is ambiguous (`1`+`..` -> `1..`).
|
|
819
|
+
if (/^[0-9]/.test(prev) && firstB === ".") {
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Avoid two operator chars fusing into a longer token (`-`+`-` -> `--`
|
|
824
|
+
// comment, `.`+`.` -> `..`, `<`+`=` -> `<=`, `:`+`:` -> `::`, ...).
|
|
825
|
+
const pair = lastA + firstB;
|
|
826
|
+
if (pair === "--" || multiCharTokens.some(token => token.startsWith(pair))) {
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
|
|
566
833
|
function minify(input) {
|
|
567
|
-
const
|
|
568
|
-
|
|
834
|
+
const lines = layout(input).filter(
|
|
835
|
+
line => !(line.tokens.length === 1 && isComment(line.tokens[0]))
|
|
836
|
+
);
|
|
569
837
|
|
|
570
|
-
let
|
|
838
|
+
let result = "";
|
|
571
839
|
let prev = null;
|
|
572
840
|
|
|
573
|
-
for (const
|
|
574
|
-
|
|
575
|
-
|
|
841
|
+
for (const line of lines) {
|
|
842
|
+
const tokens = line.tokens;
|
|
843
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
844
|
+
const token = tokens[k];
|
|
845
|
+
|
|
846
|
+
if (prev !== null) {
|
|
847
|
+
const callable = prev === ")" || prev === "]" ||
|
|
848
|
+
(isIdentifier(prev) && !reservedKeywords.has(prev));
|
|
849
|
+
if (k === 0 && token === "(" && callable) {
|
|
850
|
+
// A new statement starting with `(` after a prefix-expression
|
|
851
|
+
// would be parsed as a call continuation; separate with `;`.
|
|
852
|
+
result += ";";
|
|
853
|
+
} else if (minifyNeedsSpace(prev, token)) {
|
|
854
|
+
result += " ";
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
result += token;
|
|
859
|
+
prev = token;
|
|
576
860
|
}
|
|
577
|
-
text += token;
|
|
578
|
-
prev = token;
|
|
579
861
|
}
|
|
580
862
|
|
|
581
|
-
return
|
|
863
|
+
return result;
|
|
582
864
|
}
|
|
583
865
|
|
|
584
866
|
module.exports = { beautify, minify };
|