storm-lua-minify 0.2.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +157 -37
- package/dist/aggregateSpecialization.js +406 -0
- package/dist/ast2lua.js +223 -82
- package/dist/astWalk.js +162 -0
- package/dist/callGraph.js +372 -0
- package/dist/cli.js +53 -32
- package/dist/cliOptions.js +36 -0
- package/dist/cliProgress.js +87 -0
- package/dist/config.js +73 -0
- package/dist/constantFold.js +798 -0
- package/dist/controlFlow.js +266 -0
- package/dist/functionRewrites.js +580 -0
- package/dist/generatedAst.js +108 -0
- package/dist/generatedNode.js +23 -0
- package/dist/globalRename.js +35 -0
- package/dist/interproceduralAnalysis.js +842 -0
- package/dist/interproceduralConstants.js +120 -0
- package/dist/keywordLocator.js +68 -0
- package/dist/linker.js +13 -0
- package/dist/luaString.js +157 -0
- package/dist/minifier.js +1243 -35
- package/dist/optimizerAnalysis.js +43 -0
- package/dist/optimizerDiagnostics.js +65 -0
- package/dist/optimizerFacts.js +529 -0
- package/dist/optimizerPass.js +96 -0
- package/dist/optimizerTransaction.js +56 -0
- package/dist/optimizerValueDomain.js +180 -0
- package/dist/options.js +233 -0
- package/dist/progress.js +2 -0
- package/dist/removeUnused.js +145 -0
- package/dist/renamer.js +236 -53
- package/dist/resolver.js +37 -14
- package/dist/runtimeEnvironment.js +105 -0
- package/dist/sourceMetadata.js +314 -0
- package/dist/statementDataflow.js +259 -0
- package/dist/statementScheduler.js +595 -0
- package/dist/symbolLiveness.js +92 -0
- package/dist/tableEffects.js +356 -0
- package/dist/transform.js +40 -0
- package/dist/valueFlow.js +409 -0
- package/dist/wholeProgramExports.js +646 -0
- package/dist/wholeProgramFieldRenames.js +583 -0
- package/dist/wholeProgramFields.js +672 -0
- package/dist/wholeProgramObjects.js +783 -0
- package/package.json +50 -33
- package/.github/workflows/ci.yml +0 -39
- package/.github/workflows/publish.yml +0 -39
- package/.prettierignore +0 -3
- package/.prettierrc.json +0 -1
- package/dist/index.js +0 -27
- package/eslint.config.js +0 -29
- package/src/ast2lua.ts +0 -940
- package/src/cli.ts +0 -86
- package/src/linker.ts +0 -108
- package/src/minifier.ts +0 -284
- package/src/output.ts +0 -71
- package/src/renamer.ts +0 -134
- package/src/resolver.ts +0 -378
- package/test/circular-require.test.ts +0 -19
- package/test/fixtures/bare-require/main.lua +0 -2
- package/test/fixtures/bare-require/mod.lua +0 -1
- package/test/fixtures/bitwise-precedence/main.lua +0 -10
- package/test/fixtures/circular-require/a.lua +0 -2
- package/test/fixtures/circular-require/b.lua +0 -2
- package/test/fixtures/circular-require/main.lua +0 -2
- package/test/fixtures/dofile/greet.lua +0 -1
- package/test/fixtures/dofile/main.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_alpha.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_bravo.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_charlie.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_delta.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_echo.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_foxtrot.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_golf.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_hotel.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_india.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_juliet.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_kilo.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/main.lua +0 -25
- package/test/fixtures/multi-require/common.lua +0 -1
- package/test/fixtures/multi-require/main.lua +0 -3
- package/test/fixtures/nested-module/main.lua +0 -2
- package/test/fixtures/nested-module/sub/deep.lua +0 -1
- package/test/fixtures/require-call/main.lua +0 -2
- package/test/fixtures/require-call/mod.lua +0 -5
- package/test/fixtures/require-in-expression/main.lua +0 -1
- package/test/fixtures/require-in-expression/mod.lua +0 -1
- package/test/fixtures/require-string-call/main.lua +0 -2
- package/test/fixtures/require-string-call/mod.lua +0 -5
- package/test/fixtures/single-file/main.lua +0 -15
- package/test/identifier-collision.test.ts +0 -28
- package/test/lib/collision.ts +0 -131
- package/test/lib/helpers.ts +0 -124
- package/test/no-rename.test.ts +0 -19
- package/test/output.test.ts +0 -95
- package/test/precedence.test.ts +0 -28
- package/test/renamer.test.ts +0 -130
- package/test/resolver.test.ts +0 -206
- package/test/roundtrip.test.ts +0 -26
- package/test/snapshot.test.ts +0 -27
- package/test/snapshots/bare-require.sl.lua +0 -1
- package/test/snapshots/bitwise-precedence.sl.lua +0 -2
- package/test/snapshots/dofile.sl.lua +0 -1
- package/test/snapshots/entry-scope-many-requires.m.lua +0 -14
- package/test/snapshots/multi-require.m.lua +0 -4
- package/test/snapshots/multi-require.sl.lua +0 -1
- package/test/snapshots/nested-module.m.lua +0 -4
- package/test/snapshots/require-call.m.lua +0 -5
- package/test/snapshots/require-call.sl.lua +0 -1
- package/test/snapshots/require-in-expression.sl.lua +0 -1
- package/test/snapshots/require-string-call.m.lua +0 -5
- package/test/snapshots/single-file.sl.lua +0 -6
- package/test/sourcemap.test.ts +0 -105
- package/tsconfig.eslint.json +0 -8
- package/tsconfig.json +0 -111
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.propagateInterproceduralConstants = propagateInterproceduralConstants;
|
|
4
|
+
/**
|
|
5
|
+
* Replace a direct, pure, single-return call with its proven scalar result.
|
|
6
|
+
*
|
|
7
|
+
* This is intentionally narrower than #76 inlining. It accepts only a one-statement return body,
|
|
8
|
+
* no actual arguments, a non-recursive target, and a literal no longer than the shortest possible
|
|
9
|
+
* renamed `a()` call. Thus evaluation can be removed without alpha conversion and without relying
|
|
10
|
+
* on later function DCE to make the local rewrite profitable.
|
|
11
|
+
*/
|
|
12
|
+
function propagateInterproceduralConstants(chunk, analysis) {
|
|
13
|
+
let changed = false;
|
|
14
|
+
const replacementOf = (expression) => {
|
|
15
|
+
if (expression.type !== "CallExpression" ||
|
|
16
|
+
expression.arguments.length !== 0 ||
|
|
17
|
+
expression.base.type !== "Identifier")
|
|
18
|
+
return undefined;
|
|
19
|
+
const call = analysis.callGraph.callSiteOf(expression);
|
|
20
|
+
if (!call || call.hasUnknownTarget || call.targets.size !== 1)
|
|
21
|
+
return undefined;
|
|
22
|
+
const target = [...call.targets][0];
|
|
23
|
+
if (analysis.callGraph.sccs.some((scc) => scc.recursive && scc.functions.includes(target)) ||
|
|
24
|
+
target.declaration.body.length !== 1 ||
|
|
25
|
+
target.declaration.body[0].type !== "ReturnStatement")
|
|
26
|
+
return undefined;
|
|
27
|
+
const summary = analysis.summaryOf(target);
|
|
28
|
+
if (summary.effects.length > 0 ||
|
|
29
|
+
summary.escapes.length > 0 ||
|
|
30
|
+
summary.externalEffects.length > 0 ||
|
|
31
|
+
summary.mayError ||
|
|
32
|
+
summary.mayInvokeMetamethod ||
|
|
33
|
+
summary.returns.prefix.length !== 1 ||
|
|
34
|
+
summary.returns.tail.kind !== "none")
|
|
35
|
+
return undefined;
|
|
36
|
+
const value = summary.returns.prefix[0];
|
|
37
|
+
if (value.unknownReasons.length > 0 || value.atoms.length !== 1)
|
|
38
|
+
return undefined;
|
|
39
|
+
const replacement = literalOf(value.atoms[0], expression);
|
|
40
|
+
if (!replacement || printedLength(replacement) > 3)
|
|
41
|
+
return undefined;
|
|
42
|
+
return replacement;
|
|
43
|
+
};
|
|
44
|
+
const rewriteBlock = (body) => {
|
|
45
|
+
body.forEach((statement) => {
|
|
46
|
+
if ((statement.type === "LocalStatement" ||
|
|
47
|
+
statement.type === "AssignmentStatement") &&
|
|
48
|
+
statement.variables.length === 1 &&
|
|
49
|
+
statement.init.length === 1) {
|
|
50
|
+
const replacement = replacementOf(statement.init[0]);
|
|
51
|
+
if (replacement) {
|
|
52
|
+
statement.init[0] = replacement;
|
|
53
|
+
changed = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
switch (statement.type) {
|
|
57
|
+
case "DoStatement":
|
|
58
|
+
case "WhileStatement":
|
|
59
|
+
case "RepeatStatement":
|
|
60
|
+
case "FunctionDeclaration":
|
|
61
|
+
case "ForNumericStatement":
|
|
62
|
+
case "ForGenericStatement":
|
|
63
|
+
rewriteBlock(statement.body);
|
|
64
|
+
return;
|
|
65
|
+
case "IfStatement":
|
|
66
|
+
statement.clauses.forEach((clause) => {
|
|
67
|
+
rewriteBlock(clause.body);
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
default:
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
rewriteBlock(chunk.body);
|
|
76
|
+
return changed;
|
|
77
|
+
}
|
|
78
|
+
function literalOf(atom, source) {
|
|
79
|
+
const metadata = {
|
|
80
|
+
...(source.loc ? { loc: source.loc } : {}),
|
|
81
|
+
...("range" in source
|
|
82
|
+
? { range: source.range }
|
|
83
|
+
: {}),
|
|
84
|
+
};
|
|
85
|
+
switch (atom.kind) {
|
|
86
|
+
case "nil":
|
|
87
|
+
return { type: "NilLiteral", value: null, raw: "nil", ...metadata };
|
|
88
|
+
case "boolean":
|
|
89
|
+
return {
|
|
90
|
+
type: "BooleanLiteral",
|
|
91
|
+
value: atom.value,
|
|
92
|
+
raw: atom.value ? "true" : "false",
|
|
93
|
+
...metadata,
|
|
94
|
+
};
|
|
95
|
+
case "number":
|
|
96
|
+
return {
|
|
97
|
+
type: "NumericLiteral",
|
|
98
|
+
value: Number(atom.raw),
|
|
99
|
+
raw: atom.raw,
|
|
100
|
+
...metadata,
|
|
101
|
+
};
|
|
102
|
+
case "string": {
|
|
103
|
+
const raw = JSON.stringify(atom.value);
|
|
104
|
+
return { type: "StringLiteral", value: atom.value, raw, ...metadata };
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function printedLength(expression) {
|
|
111
|
+
switch (expression.type) {
|
|
112
|
+
case "NilLiteral":
|
|
113
|
+
case "BooleanLiteral":
|
|
114
|
+
case "NumericLiteral":
|
|
115
|
+
case "StringLiteral":
|
|
116
|
+
return expression.raw.length;
|
|
117
|
+
default:
|
|
118
|
+
return Infinity;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.KeywordLocator = void 0;
|
|
7
|
+
const luaparse_1 = __importDefault(require("luaparse"));
|
|
8
|
+
// luaparseの型定義(@types/luaparse)には`tokenTypes`が含まれないため、
|
|
9
|
+
// 実行時の値を直接参照する。luaparse 0.3.1のソース上の定義は
|
|
10
|
+
// `var EOF = 1, StringLiteral = 2, Keyword = 4, ...`。
|
|
11
|
+
const tokenTypes = luaparse_1.default.tokenTypes;
|
|
12
|
+
function comparePosition(a, b) {
|
|
13
|
+
return a.line !== b.line ? a.line - b.line : a.column - b.column;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* luaparseのASTは文・式の境界にしか`loc`を持たず、`then`/`do`/`until`のような
|
|
17
|
+
* キーワード単体の出現位置は保持しない。このクラスはluaparseの低レベル
|
|
18
|
+
* 再トークン化API(`parse(code, {wait: true, ...})` + `lex()`)でモジュールの
|
|
19
|
+
* ソースを再走査し、キーワードトークンの正確な位置を検索できるようにする(#14)。
|
|
20
|
+
*/
|
|
21
|
+
class KeywordLocator {
|
|
22
|
+
tokens;
|
|
23
|
+
constructor(sourceText, luaParseSettings) {
|
|
24
|
+
const parser = luaparse_1.default.parse(sourceText, {
|
|
25
|
+
...luaParseSettings,
|
|
26
|
+
wait: true,
|
|
27
|
+
});
|
|
28
|
+
const tokens = [];
|
|
29
|
+
for (;;) {
|
|
30
|
+
const token = parser.lex();
|
|
31
|
+
if (token.type === tokenTypes.EOF) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
if (token.type === tokenTypes.Keyword) {
|
|
35
|
+
tokens.push({
|
|
36
|
+
value: token.value,
|
|
37
|
+
line: token.line,
|
|
38
|
+
column: token.range[0] - token.lineStart,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.tokens = tokens;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* `from`(含む)以降で最初に現れる、値が`value`と一致するキーワードトークンの
|
|
46
|
+
* 開始位置を返す。見つからない場合はundefined。
|
|
47
|
+
*/
|
|
48
|
+
findFrom(from, value) {
|
|
49
|
+
let lo = 0;
|
|
50
|
+
let hi = this.tokens.length;
|
|
51
|
+
while (lo < hi) {
|
|
52
|
+
const mid = (lo + hi) >>> 1;
|
|
53
|
+
if (comparePosition(this.tokens[mid], from) < 0) {
|
|
54
|
+
lo = mid + 1;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
hi = mid;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (let i = lo; i < this.tokens.length; i++) {
|
|
61
|
+
if (this.tokens[i].value === value) {
|
|
62
|
+
return { line: this.tokens[i].line, column: this.tokens[i].column };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.KeywordLocator = KeywordLocator;
|
package/dist/linker.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RESERVED_MODULE_FUNCTION_NAMES = void 0;
|
|
3
4
|
exports.staticStringArgument = staticStringArgument;
|
|
4
5
|
exports.findModuleReferences = findModuleReferences;
|
|
6
|
+
// require/dofileは、Linkパス(このファイルのfindModuleReferences)だけでなく
|
|
7
|
+
// Printパス(ast2lua.tsのmatchModuleCallExpression、minifier.tsの
|
|
8
|
+
// buildRequireWrapper/collectRequireTargets)でも、この2つの名前文字列を
|
|
9
|
+
// 手がかりに呼び出しを再検出している。そのため、識別子リネーム系のパス
|
|
10
|
+
// (#8a: globalRename.ts、#8b: transform.tsのinsertGlobalAliases)は、
|
|
11
|
+
// これらの名前を書き換え候補から常に除外しなければならない。書き換えると
|
|
12
|
+
// Printパスがrequire/dofile呼び出しを検出できなくなり、モジュール解決が
|
|
13
|
+
// 静かに壊れる(ディスパッチテーブルの生成漏れ等)。
|
|
14
|
+
exports.RESERVED_MODULE_FUNCTION_NAMES = new Set([
|
|
15
|
+
"require",
|
|
16
|
+
"dofile",
|
|
17
|
+
]);
|
|
5
18
|
/**
|
|
6
19
|
* Chunk配下を型を問わず再帰的に走査するジェネリックウォーカー。
|
|
7
20
|
* printerとは独立に、AST全体からrequire/dofile呼び出しを見つけ出すために使う(#18)。
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.luaByteStringOfText = luaByteStringOfText;
|
|
4
|
+
exports.decodeLuaStringLiteral = decodeLuaStringLiteral;
|
|
5
|
+
exports.luaByteStringKey = luaByteStringKey;
|
|
6
|
+
exports.compareLuaByteStrings = compareLuaByteStrings;
|
|
7
|
+
exports.concatLuaByteStrings = concatLuaByteStrings;
|
|
8
|
+
exports.encodeLuaByteString = encodeLuaByteString;
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
function luaByteStringOfText(text) {
|
|
11
|
+
return { bytes: encoder.encode(text) };
|
|
12
|
+
}
|
|
13
|
+
function decodeLuaStringLiteral(node) {
|
|
14
|
+
const raw = node.raw;
|
|
15
|
+
const long = /^\[(=*)\[/.exec(raw);
|
|
16
|
+
if (long) {
|
|
17
|
+
const close = `]${long[1]}]`;
|
|
18
|
+
if (!raw.endsWith(close)) {
|
|
19
|
+
return { ok: false, reason: "invalid-delimiter" };
|
|
20
|
+
}
|
|
21
|
+
let value = raw.slice(long[0].length, -close.length);
|
|
22
|
+
value = value.replace(/\r\n|\r/g, "\n");
|
|
23
|
+
if (value.startsWith("\n"))
|
|
24
|
+
value = value.slice(1);
|
|
25
|
+
return { ok: true, value: luaByteStringOfText(value) };
|
|
26
|
+
}
|
|
27
|
+
const quote = raw[0];
|
|
28
|
+
if (raw.length < 2 ||
|
|
29
|
+
(quote !== '"' && quote !== "'") ||
|
|
30
|
+
raw[raw.length - 1] !== quote) {
|
|
31
|
+
return { ok: false, reason: "invalid-delimiter" };
|
|
32
|
+
}
|
|
33
|
+
const bytes = [];
|
|
34
|
+
const appendText = (text) => bytes.push(...encoder.encode(text));
|
|
35
|
+
for (let index = 1; index < raw.length - 1; index++) {
|
|
36
|
+
const current = raw[index];
|
|
37
|
+
if (current !== "\\") {
|
|
38
|
+
const codePoint = raw.codePointAt(index);
|
|
39
|
+
if (codePoint === undefined) {
|
|
40
|
+
return { ok: false, reason: "invalid-code-point" };
|
|
41
|
+
}
|
|
42
|
+
appendText(String.fromCodePoint(codePoint));
|
|
43
|
+
if (codePoint > 0xffff)
|
|
44
|
+
index++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
index++;
|
|
48
|
+
if (index >= raw.length - 1) {
|
|
49
|
+
return { ok: false, reason: "invalid-escape" };
|
|
50
|
+
}
|
|
51
|
+
const escaped = raw[index];
|
|
52
|
+
const simple = SIMPLE_ESCAPES[escaped];
|
|
53
|
+
if (simple !== undefined) {
|
|
54
|
+
bytes.push(simple);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (escaped === "\n" || escaped === "\r") {
|
|
58
|
+
if (escaped === "\r" && raw[index + 1] === "\n")
|
|
59
|
+
index++;
|
|
60
|
+
bytes.push(0x0a);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (escaped === "z") {
|
|
64
|
+
while (/\s/.test(raw[index + 1] ?? ""))
|
|
65
|
+
index++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (escaped === "x") {
|
|
69
|
+
const hex = raw.slice(index + 1, index + 3);
|
|
70
|
+
if (!/^[0-9a-fA-F]{2}$/.test(hex)) {
|
|
71
|
+
return { ok: false, reason: "invalid-escape" };
|
|
72
|
+
}
|
|
73
|
+
bytes.push(Number.parseInt(hex, 16));
|
|
74
|
+
index += 2;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (escaped === "u") {
|
|
78
|
+
const match = /^\{([0-9a-fA-F]+)\}/.exec(raw.slice(index + 1));
|
|
79
|
+
if (!match)
|
|
80
|
+
return { ok: false, reason: "invalid-escape" };
|
|
81
|
+
const codePoint = Number.parseInt(match[1], 16);
|
|
82
|
+
if (codePoint > 0x10ffff ||
|
|
83
|
+
(codePoint >= 0xd800 && codePoint <= 0xdfff)) {
|
|
84
|
+
return { ok: false, reason: "invalid-code-point" };
|
|
85
|
+
}
|
|
86
|
+
appendText(String.fromCodePoint(codePoint));
|
|
87
|
+
index += match[0].length;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (/\d/.test(escaped)) {
|
|
91
|
+
let digits = escaped;
|
|
92
|
+
while (digits.length < 3 && /\d/.test(raw[index + 1] ?? "")) {
|
|
93
|
+
digits += raw[++index];
|
|
94
|
+
}
|
|
95
|
+
const byte = Number.parseInt(digits, 10);
|
|
96
|
+
if (byte > 255)
|
|
97
|
+
return { ok: false, reason: "out-of-range-byte" };
|
|
98
|
+
bytes.push(byte);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
return { ok: false, reason: "invalid-escape" };
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, value: { bytes: Uint8Array.from(bytes) } };
|
|
104
|
+
}
|
|
105
|
+
function luaByteStringKey(value) {
|
|
106
|
+
return Array.from(value.bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
107
|
+
}
|
|
108
|
+
function compareLuaByteStrings(left, right) {
|
|
109
|
+
const length = Math.min(left.bytes.length, right.bytes.length);
|
|
110
|
+
for (let index = 0; index < length; index++) {
|
|
111
|
+
if (left.bytes[index] !== right.bytes[index]) {
|
|
112
|
+
return left.bytes[index] - right.bytes[index];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return left.bytes.length - right.bytes.length;
|
|
116
|
+
}
|
|
117
|
+
function concatLuaByteStrings(left, right) {
|
|
118
|
+
const bytes = new Uint8Array(left.bytes.length + right.bytes.length);
|
|
119
|
+
bytes.set(left.bytes);
|
|
120
|
+
bytes.set(right.bytes, left.bytes.length);
|
|
121
|
+
return { bytes };
|
|
122
|
+
}
|
|
123
|
+
/** 任意のLua byte列を、再decode可能な決定論的quoted literalへ戻す。 */
|
|
124
|
+
function encodeLuaByteString(value) {
|
|
125
|
+
const encodeWith = (quote) => {
|
|
126
|
+
let raw = String.fromCharCode(quote);
|
|
127
|
+
value.bytes.forEach((byte) => {
|
|
128
|
+
if (byte === quote || byte === 0x5c) {
|
|
129
|
+
raw += `\\${String.fromCharCode(byte)}`;
|
|
130
|
+
}
|
|
131
|
+
else if (byte >= 0x20 && byte <= 0x7e) {
|
|
132
|
+
raw += String.fromCharCode(byte);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
raw += `\\x${byte.toString(16).padStart(2, "0")}`;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
return raw + String.fromCharCode(quote);
|
|
139
|
+
};
|
|
140
|
+
const doubleQuoted = encodeWith(0x22);
|
|
141
|
+
const singleQuoted = encodeWith(0x27);
|
|
142
|
+
return singleQuoted.length < doubleQuoted.length
|
|
143
|
+
? singleQuoted
|
|
144
|
+
: doubleQuoted;
|
|
145
|
+
}
|
|
146
|
+
const SIMPLE_ESCAPES = {
|
|
147
|
+
a: 0x07,
|
|
148
|
+
b: 0x08,
|
|
149
|
+
f: 0x0c,
|
|
150
|
+
n: 0x0a,
|
|
151
|
+
r: 0x0d,
|
|
152
|
+
t: 0x09,
|
|
153
|
+
v: 0x0b,
|
|
154
|
+
"\\": 0x5c,
|
|
155
|
+
'"': 0x22,
|
|
156
|
+
"'": 0x27,
|
|
157
|
+
};
|