@tilua/compiler 4.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -10
- package/dist/{chunk-VZ7XC4BG.js → chunk-Y7QGXLCV.js} +643 -127
- package/dist/cli.cjs +709 -171
- package/dist/cli.js +32 -9
- package/dist/index.cjs +665 -148
- package/dist/index.d.cts +41 -6
- package/dist/index.d.ts +41 -6
- package/dist/index.js +6 -4
- package/package.json +2 -2
- package/runtime/array.luau +36 -4
- package/runtime/console.luau +227 -0
- package/runtime/result.luau +122 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as T from '@tilua/parser';
|
|
2
|
-
import { LoweringPlugin, BuildTarget, TypeAnalysis, ScopeAnalysis, TiluaConfig, ConfigProblem } from '@tilua/parser';
|
|
2
|
+
import { LoweringPlugin, BuildTarget, TypeAnalysis, ScopeAnalysis, OutputMode, TiluaConfig, ConfigProblem } from '@tilua/parser';
|
|
3
3
|
export { LoweringPlugin, MethodCall, MethodLowering } from '@tilua/parser';
|
|
4
4
|
import * as L from 'luau-parser';
|
|
5
5
|
|
|
@@ -88,11 +88,15 @@ interface ModuleContext {
|
|
|
88
88
|
/** The bundle's name for this module. */
|
|
89
89
|
readonly name: string;
|
|
90
90
|
/** The bundle's name for the module `specifier` imports, or `undefined`
|
|
91
|
-
* when there is no such module (reported).
|
|
92
|
-
|
|
91
|
+
* when there is no such module (reported). `dynamic` says an `import()`
|
|
92
|
+
* is asking: the module is needed when that runs, not before this one. */
|
|
93
|
+
resolve(specifier: string, dynamic?: boolean): string | undefined;
|
|
93
94
|
/** The bundle's `require`: an expression that, called with a module's
|
|
94
95
|
* name, returns its exports table. */
|
|
95
96
|
readonly require: L.Expression;
|
|
97
|
+
/** What `import("./x")` calls, with the module's name: the bundle's
|
|
98
|
+
* wrapper around its `require`. Default: `require` itself. */
|
|
99
|
+
readonly import?: L.Expression;
|
|
96
100
|
/** Luau reaching the bundle's line map — what a library's runtime reads,
|
|
97
101
|
* as `__LINES__`, to name the file a frame was written in. */
|
|
98
102
|
readonly lines?: string;
|
|
@@ -129,6 +133,9 @@ interface LowerOptions {
|
|
|
129
133
|
* lowers what Luau adds to Lua and 5.1 has no syntax for — `a += b`,
|
|
130
134
|
* `continue`, `if c then a else b` as an expression, `//`. */
|
|
131
135
|
readonly target?: Target;
|
|
136
|
+
/** The project leaves Lua's standard library out (`"noLib": true`), so
|
|
137
|
+
* nothing of it is lowered either. */
|
|
138
|
+
readonly noLib?: boolean;
|
|
132
139
|
}
|
|
133
140
|
/** `"luau"` is Roblox's Lua. `"lua51"` is stock Lua 5.1, which has none of
|
|
134
141
|
* Luau's additions — and no `goto` either, so `continue` has to be built out
|
|
@@ -189,6 +196,13 @@ interface TiluaConfigJson {
|
|
|
189
196
|
* `a += b`, `continue`, `if c then a else b` as an expression, `//`,
|
|
190
197
|
* digit separators and binary literals. */
|
|
191
198
|
target?: Target;
|
|
199
|
+
/** How many files the output is. `"single"` (the default) is one file;
|
|
200
|
+
* `"chunks"` also writes a file for what only an `import()` reaches,
|
|
201
|
+
* loaded when it is first asked for; `"modules"` writes one per module. */
|
|
202
|
+
output?: OutputMode;
|
|
203
|
+
/** Leave out Lua's standard library — `print`, `string`, `pcall` — which
|
|
204
|
+
* is otherwise there for every file, as JavaScript's is in TypeScript. */
|
|
205
|
+
noLib?: boolean;
|
|
192
206
|
}
|
|
193
207
|
/** A config file's path, or the config itself. Relative paths — to a config
|
|
194
208
|
* file, and inside an inline config — resolve from `cwd`. */
|
|
@@ -214,6 +228,23 @@ interface BundleOptions {
|
|
|
214
228
|
readonly typeCheck?: boolean;
|
|
215
229
|
/** Which Lua the output must run on. Overrides the config's `target`. */
|
|
216
230
|
readonly target?: Target;
|
|
231
|
+
/** How many files to make. Overrides the config's `output`; `"single"`
|
|
232
|
+
* without either. */
|
|
233
|
+
readonly output?: OutputMode;
|
|
234
|
+
/** What the entry's file is called: `main.luau`. The other files of
|
|
235
|
+
* `"chunks"` are named after it (`main_shop.luau`), and every file takes
|
|
236
|
+
* its extension. Default: the entry's own name, with `.luau`. */
|
|
237
|
+
readonly outFile?: string;
|
|
238
|
+
}
|
|
239
|
+
/** One file of the output. */
|
|
240
|
+
interface BundleFile {
|
|
241
|
+
/** Where it goes, from the folder the output is written to: `main.luau`,
|
|
242
|
+
* `src/shop/buy.luau`. Files find each other by these, so they have to be
|
|
243
|
+
* written as they are named. */
|
|
244
|
+
readonly path: string;
|
|
245
|
+
readonly code: string;
|
|
246
|
+
/** The modules in it, by name. */
|
|
247
|
+
readonly modules: string[];
|
|
217
248
|
}
|
|
218
249
|
interface BundleDiagnostic {
|
|
219
250
|
/** Absolute path of the file it is about. */
|
|
@@ -227,9 +258,13 @@ interface BundleDiagnostic {
|
|
|
227
258
|
readonly category: "syntax" | "scope" | "module" | "type" | "config";
|
|
228
259
|
}
|
|
229
260
|
interface BundleResult {
|
|
230
|
-
/** The bundle
|
|
231
|
-
* errors are reported but do
|
|
261
|
+
/** The bundle — the entry's file, when there are several — or `undefined`
|
|
262
|
+
* when a module could not be compiled. Type errors are reported but do
|
|
263
|
+
* not stop it. */
|
|
232
264
|
readonly code?: string;
|
|
265
|
+
/** Every file of the output, the entry's first. Empty when there is no
|
|
266
|
+
* bundle. */
|
|
267
|
+
readonly files: BundleFile[];
|
|
233
268
|
/** The bundled modules' names, entry first. */
|
|
234
269
|
readonly modules: string[];
|
|
235
270
|
readonly diagnostics: BundleDiagnostic[];
|
|
@@ -248,4 +283,4 @@ interface CompileResult {
|
|
|
248
283
|
}
|
|
249
284
|
declare function compile(source: string, config?: ConfigInput): Promise<CompileResult>;
|
|
250
285
|
|
|
251
|
-
export { type BundleDiagnostic, type BundleOptions, type BundleResult, type CompileResult, type ConfigInput, type Diagnostic, type LoadedLowering, type LowerDiagnostic, type LowerOptions, type LowerResult, type LoweringProblem, type ModuleContext, type ResolvedConfig, type TiluaConfigJson, bundle, compile, loadLowerings, lower, resolveConfig };
|
|
286
|
+
export { type BundleDiagnostic, type BundleFile, type BundleOptions, type BundleResult, type CompileResult, type ConfigInput, type Diagnostic, type LoadedLowering, type LowerDiagnostic, type LowerOptions, type LowerResult, type LoweringProblem, type ModuleContext, type ResolvedConfig, type TiluaConfigJson, bundle, compile, loadLowerings, lower, resolveConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as T from '@tilua/parser';
|
|
2
|
-
import { LoweringPlugin, BuildTarget, TypeAnalysis, ScopeAnalysis, TiluaConfig, ConfigProblem } from '@tilua/parser';
|
|
2
|
+
import { LoweringPlugin, BuildTarget, TypeAnalysis, ScopeAnalysis, OutputMode, TiluaConfig, ConfigProblem } from '@tilua/parser';
|
|
3
3
|
export { LoweringPlugin, MethodCall, MethodLowering } from '@tilua/parser';
|
|
4
4
|
import * as L from 'luau-parser';
|
|
5
5
|
|
|
@@ -88,11 +88,15 @@ interface ModuleContext {
|
|
|
88
88
|
/** The bundle's name for this module. */
|
|
89
89
|
readonly name: string;
|
|
90
90
|
/** The bundle's name for the module `specifier` imports, or `undefined`
|
|
91
|
-
* when there is no such module (reported).
|
|
92
|
-
|
|
91
|
+
* when there is no such module (reported). `dynamic` says an `import()`
|
|
92
|
+
* is asking: the module is needed when that runs, not before this one. */
|
|
93
|
+
resolve(specifier: string, dynamic?: boolean): string | undefined;
|
|
93
94
|
/** The bundle's `require`: an expression that, called with a module's
|
|
94
95
|
* name, returns its exports table. */
|
|
95
96
|
readonly require: L.Expression;
|
|
97
|
+
/** What `import("./x")` calls, with the module's name: the bundle's
|
|
98
|
+
* wrapper around its `require`. Default: `require` itself. */
|
|
99
|
+
readonly import?: L.Expression;
|
|
96
100
|
/** Luau reaching the bundle's line map — what a library's runtime reads,
|
|
97
101
|
* as `__LINES__`, to name the file a frame was written in. */
|
|
98
102
|
readonly lines?: string;
|
|
@@ -129,6 +133,9 @@ interface LowerOptions {
|
|
|
129
133
|
* lowers what Luau adds to Lua and 5.1 has no syntax for — `a += b`,
|
|
130
134
|
* `continue`, `if c then a else b` as an expression, `//`. */
|
|
131
135
|
readonly target?: Target;
|
|
136
|
+
/** The project leaves Lua's standard library out (`"noLib": true`), so
|
|
137
|
+
* nothing of it is lowered either. */
|
|
138
|
+
readonly noLib?: boolean;
|
|
132
139
|
}
|
|
133
140
|
/** `"luau"` is Roblox's Lua. `"lua51"` is stock Lua 5.1, which has none of
|
|
134
141
|
* Luau's additions — and no `goto` either, so `continue` has to be built out
|
|
@@ -189,6 +196,13 @@ interface TiluaConfigJson {
|
|
|
189
196
|
* `a += b`, `continue`, `if c then a else b` as an expression, `//`,
|
|
190
197
|
* digit separators and binary literals. */
|
|
191
198
|
target?: Target;
|
|
199
|
+
/** How many files the output is. `"single"` (the default) is one file;
|
|
200
|
+
* `"chunks"` also writes a file for what only an `import()` reaches,
|
|
201
|
+
* loaded when it is first asked for; `"modules"` writes one per module. */
|
|
202
|
+
output?: OutputMode;
|
|
203
|
+
/** Leave out Lua's standard library — `print`, `string`, `pcall` — which
|
|
204
|
+
* is otherwise there for every file, as JavaScript's is in TypeScript. */
|
|
205
|
+
noLib?: boolean;
|
|
192
206
|
}
|
|
193
207
|
/** A config file's path, or the config itself. Relative paths — to a config
|
|
194
208
|
* file, and inside an inline config — resolve from `cwd`. */
|
|
@@ -214,6 +228,23 @@ interface BundleOptions {
|
|
|
214
228
|
readonly typeCheck?: boolean;
|
|
215
229
|
/** Which Lua the output must run on. Overrides the config's `target`. */
|
|
216
230
|
readonly target?: Target;
|
|
231
|
+
/** How many files to make. Overrides the config's `output`; `"single"`
|
|
232
|
+
* without either. */
|
|
233
|
+
readonly output?: OutputMode;
|
|
234
|
+
/** What the entry's file is called: `main.luau`. The other files of
|
|
235
|
+
* `"chunks"` are named after it (`main_shop.luau`), and every file takes
|
|
236
|
+
* its extension. Default: the entry's own name, with `.luau`. */
|
|
237
|
+
readonly outFile?: string;
|
|
238
|
+
}
|
|
239
|
+
/** One file of the output. */
|
|
240
|
+
interface BundleFile {
|
|
241
|
+
/** Where it goes, from the folder the output is written to: `main.luau`,
|
|
242
|
+
* `src/shop/buy.luau`. Files find each other by these, so they have to be
|
|
243
|
+
* written as they are named. */
|
|
244
|
+
readonly path: string;
|
|
245
|
+
readonly code: string;
|
|
246
|
+
/** The modules in it, by name. */
|
|
247
|
+
readonly modules: string[];
|
|
217
248
|
}
|
|
218
249
|
interface BundleDiagnostic {
|
|
219
250
|
/** Absolute path of the file it is about. */
|
|
@@ -227,9 +258,13 @@ interface BundleDiagnostic {
|
|
|
227
258
|
readonly category: "syntax" | "scope" | "module" | "type" | "config";
|
|
228
259
|
}
|
|
229
260
|
interface BundleResult {
|
|
230
|
-
/** The bundle
|
|
231
|
-
* errors are reported but do
|
|
261
|
+
/** The bundle — the entry's file, when there are several — or `undefined`
|
|
262
|
+
* when a module could not be compiled. Type errors are reported but do
|
|
263
|
+
* not stop it. */
|
|
232
264
|
readonly code?: string;
|
|
265
|
+
/** Every file of the output, the entry's first. Empty when there is no
|
|
266
|
+
* bundle. */
|
|
267
|
+
readonly files: BundleFile[];
|
|
233
268
|
/** The bundled modules' names, entry first. */
|
|
234
269
|
readonly modules: string[];
|
|
235
270
|
readonly diagnostics: BundleDiagnostic[];
|
|
@@ -248,4 +283,4 @@ interface CompileResult {
|
|
|
248
283
|
}
|
|
249
284
|
declare function compile(source: string, config?: ConfigInput): Promise<CompileResult>;
|
|
250
285
|
|
|
251
|
-
export { type BundleDiagnostic, type BundleOptions, type BundleResult, type CompileResult, type ConfigInput, type Diagnostic, type LoadedLowering, type LowerDiagnostic, type LowerOptions, type LowerResult, type LoweringProblem, type ModuleContext, type ResolvedConfig, type TiluaConfigJson, bundle, compile, loadLowerings, lower, resolveConfig };
|
|
286
|
+
export { type BundleDiagnostic, type BundleFile, type BundleOptions, type BundleResult, type CompileResult, type ConfigInput, type Diagnostic, type LoadedLowering, type LowerDiagnostic, type LowerOptions, type LowerResult, type LoweringProblem, type ModuleContext, type ResolvedConfig, type TiluaConfigJson, bundle, compile, loadLowerings, lower, resolveConfig };
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
lower,
|
|
5
5
|
program,
|
|
6
6
|
resolveConfig
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Y7QGXLCV.js";
|
|
8
8
|
|
|
9
9
|
// src/compile.ts
|
|
10
10
|
import { readFileSync } from "fs";
|
|
@@ -22,13 +22,14 @@ async function compile(source, config) {
|
|
|
22
22
|
}
|
|
23
23
|
throw error;
|
|
24
24
|
}
|
|
25
|
-
const
|
|
25
|
+
const { config: resolved, problems } = resolveConfig(config, resolve("main.tilua"));
|
|
26
|
+
const noLib = resolved?.noLib ?? false;
|
|
27
|
+
const scopes = analyzeScopes(program2, { noLib });
|
|
26
28
|
const diagnostics = scopes.diagnostics.map((d) => ({
|
|
27
29
|
message: d.message,
|
|
28
30
|
line: d.node.line.start,
|
|
29
31
|
column: d.node.column.start
|
|
30
32
|
}));
|
|
31
|
-
const { config: resolved, problems } = resolveConfig(config, resolve("main.tilua"));
|
|
32
33
|
const libraries = resolved ? resolveTypeLibraries(resolved) : { files: [], lowerings: [], problems: [] };
|
|
33
34
|
const { lowerings, problems: loweringProblems } = await loadLowerings(libraries.lowerings);
|
|
34
35
|
for (const p of [...problems, ...libraries.problems]) {
|
|
@@ -37,7 +38,8 @@ async function compile(source, config) {
|
|
|
37
38
|
for (const p of loweringProblems) diagnostics.push({ message: p.message, line: 1, column: 1 });
|
|
38
39
|
const libs = libraries.files.map((file) => parse(readFileSync(file, "utf8")));
|
|
39
40
|
const lowered = lower(program2, scopes, {
|
|
40
|
-
types: analyzeTypes(program2, scopes, { libs, diagnostics: false }),
|
|
41
|
+
types: analyzeTypes(program2, scopes, { libs, noLib, diagnostics: false }),
|
|
42
|
+
noLib,
|
|
41
43
|
lowerings,
|
|
42
44
|
target: resolved?.target,
|
|
43
45
|
source
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tilua/compiler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Compiles tilua to Luau: lowers the @tilua/parser AST to a luau-parser AST and prints it",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"type": "module",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"luau-parser": "^1.0.3",
|
|
41
|
-
"@tilua/parser": "^
|
|
41
|
+
"@tilua/parser": "^5.0.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^22.20.2",
|
package/runtime/array.luau
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
local __NAME__ = {}
|
|
2
2
|
|
|
3
|
+
-- `table.move`, which Lua 5.1 does not have.
|
|
4
|
+
local __NAME___move = table.move or function(from, first, last, at, to)
|
|
5
|
+
for i = first, last do
|
|
6
|
+
to[at + i - first] = from[i]
|
|
7
|
+
end
|
|
8
|
+
return to
|
|
9
|
+
end
|
|
10
|
+
|
|
3
11
|
function __NAME__.find(t, test)
|
|
4
12
|
for i = 1, #t do
|
|
5
13
|
if test(t[i], i) then
|
|
@@ -96,10 +104,10 @@ end
|
|
|
96
104
|
|
|
97
105
|
function __NAME__.concat(t, ...)
|
|
98
106
|
local out = __ARRAY__({})
|
|
99
|
-
|
|
107
|
+
__NAME___move(t, 1, #t, 1, out)
|
|
100
108
|
for i = 1, select("#", ...) do
|
|
101
109
|
local part = select(i, ...)
|
|
102
|
-
|
|
110
|
+
__NAME___move(part, 1, #part, #out + 1, out)
|
|
103
111
|
end
|
|
104
112
|
return out
|
|
105
113
|
end
|
|
@@ -122,12 +130,36 @@ function __NAME__.slice(t, start, stop)
|
|
|
122
130
|
end
|
|
123
131
|
|
|
124
132
|
-- One level, as JavaScript's `flat()` does by default.
|
|
133
|
+
function __NAME__.keys(t)
|
|
134
|
+
local out = __ARRAY__({})
|
|
135
|
+
for i = 1, #t do
|
|
136
|
+
out[i] = i
|
|
137
|
+
end
|
|
138
|
+
return out
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
function __NAME__.values(t)
|
|
142
|
+
local out = __ARRAY__({})
|
|
143
|
+
for i = 1, #t do
|
|
144
|
+
out[i] = t[i]
|
|
145
|
+
end
|
|
146
|
+
return out
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
function __NAME__.entries(t)
|
|
150
|
+
local out = __ARRAY__({})
|
|
151
|
+
for i = 1, #t do
|
|
152
|
+
out[i] = __ARRAY__({ i, t[i] })
|
|
153
|
+
end
|
|
154
|
+
return out
|
|
155
|
+
end
|
|
156
|
+
|
|
125
157
|
function __NAME__.flat(t)
|
|
126
158
|
local out = __ARRAY__({})
|
|
127
159
|
for i = 1, #t do
|
|
128
160
|
local value = t[i]
|
|
129
161
|
if type(value) == "table" then
|
|
130
|
-
|
|
162
|
+
__NAME___move(value, 1, #value, #out + 1, out)
|
|
131
163
|
else
|
|
132
164
|
out[#out + 1] = value
|
|
133
165
|
end
|
|
@@ -137,7 +169,7 @@ end
|
|
|
137
169
|
|
|
138
170
|
function __NAME__.reverse(t)
|
|
139
171
|
local n = #t
|
|
140
|
-
for i = 1, n
|
|
172
|
+
for i = 1, math.floor(n / 2) do
|
|
141
173
|
t[i], t[n - i + 1] = t[n - i + 1], t[i]
|
|
142
174
|
end
|
|
143
175
|
return t
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
-- The runtime behind `console:log`, `console:warn`, `console:error`, and the
|
|
2
|
+
-- `error` a project calls.
|
|
3
|
+
--
|
|
4
|
+
-- Every entry takes where it was called from as its first argument, written
|
|
5
|
+
-- by the compiler at the call, and puts it in front of what it prints, the way
|
|
6
|
+
-- a browser console names the line that logged: `[src/shop/buy:12]: ...`. A
|
|
7
|
+
-- bundle is one Luau file, so a line Luau reports names a file nobody wrote;
|
|
8
|
+
-- this is the place the author can open. `error` puts it inside the message
|
|
9
|
+
-- instead, as Lua does: `src/shop/buy.tilua:12: ...`.
|
|
10
|
+
--
|
|
11
|
+
-- A call to `print` or `warn` needs none of this: the compiler passes the
|
|
12
|
+
-- place to the real function as an extra argument. Read as values
|
|
13
|
+
-- (`local p = print`) there is no call site to be given, so the `*Value`
|
|
14
|
+
-- forms work it out from the stack and the bundle's line map instead.
|
|
15
|
+
|
|
16
|
+
local __NAME__ = {}
|
|
17
|
+
|
|
18
|
+
local MAX_DEPTH = 4
|
|
19
|
+
local ESCAPES = { ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ['"'] = '\\"', ["\\"] = "\\\\" }
|
|
20
|
+
|
|
21
|
+
local function quote(text)
|
|
22
|
+
return '"' .. text:gsub('[%c"\\]', function(c)
|
|
23
|
+
return ESCAPES[c] or ("\\%d"):format(c:byte())
|
|
24
|
+
end) .. '"'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
-- A table with 1..n and nothing else prints as a list, as it was written.
|
|
28
|
+
local function isArray(value)
|
|
29
|
+
local count = 0
|
|
30
|
+
for key in pairs(value) do
|
|
31
|
+
if type(key) ~= "number" then
|
|
32
|
+
return false
|
|
33
|
+
end
|
|
34
|
+
count = count + 1
|
|
35
|
+
end
|
|
36
|
+
return count == #value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
local function isIdentifier(key)
|
|
40
|
+
return type(key) == "string" and key:match("^[A-Za-z_][A-Za-z0-9_]*$") ~= nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
local render
|
|
44
|
+
|
|
45
|
+
local function renderTable(value, seen, depth, indent)
|
|
46
|
+
if seen[value] then
|
|
47
|
+
return "<cycle>"
|
|
48
|
+
end
|
|
49
|
+
if depth > MAX_DEPTH then
|
|
50
|
+
return "..."
|
|
51
|
+
end
|
|
52
|
+
seen[value] = true
|
|
53
|
+
local inner = indent .. " "
|
|
54
|
+
local parts = {}
|
|
55
|
+
local array = isArray(value)
|
|
56
|
+
if array then
|
|
57
|
+
for i = 1, #value do
|
|
58
|
+
parts[#parts + 1] = inner .. render(value[i], nil, seen, depth + 1, inner)
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
local keys = {}
|
|
62
|
+
for key in pairs(value) do
|
|
63
|
+
keys[#keys + 1] = key
|
|
64
|
+
end
|
|
65
|
+
table.sort(keys, function(a, b)
|
|
66
|
+
return tostring(a) < tostring(b)
|
|
67
|
+
end)
|
|
68
|
+
for _, key in ipairs(keys) do
|
|
69
|
+
local name = isIdentifier(key) and key or ("[" .. render(key, nil, seen, depth + 1, inner) .. "]")
|
|
70
|
+
parts[#parts + 1] = inner .. name .. " = " .. render(value[key], nil, seen, depth + 1, inner)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
seen[value] = nil
|
|
74
|
+
if #parts == 0 then
|
|
75
|
+
return "{}"
|
|
76
|
+
end
|
|
77
|
+
local open = array and "[" or "{"
|
|
78
|
+
local close = array and "]" or "}"
|
|
79
|
+
return open .. "\n" .. table.concat(parts, ",\n") .. "\n" .. indent .. close
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
-- `meta` is what the compiler knew about this value and the runtime cannot:
|
|
83
|
+
-- a function's type, and the code it was written as.
|
|
84
|
+
render = function(value, meta, seen, depth, indent)
|
|
85
|
+
local kind = type(value)
|
|
86
|
+
if kind == "string" then
|
|
87
|
+
return quote(value)
|
|
88
|
+
end
|
|
89
|
+
if kind == "number" or kind == "boolean" or kind == "nil" then
|
|
90
|
+
return tostring(value)
|
|
91
|
+
end
|
|
92
|
+
if kind == "function" then
|
|
93
|
+
if meta == nil then
|
|
94
|
+
return "function"
|
|
95
|
+
end
|
|
96
|
+
if meta[2] == nil or meta[2] == "" then
|
|
97
|
+
return meta[1]
|
|
98
|
+
end
|
|
99
|
+
return meta[1] .. " " .. meta[2]
|
|
100
|
+
end
|
|
101
|
+
if kind == "table" then
|
|
102
|
+
-- A value with its own `__tostring` (a Roblox Vector3, a class
|
|
103
|
+
-- instance that says how it reads) is trusted to describe itself.
|
|
104
|
+
local metatable = getmetatable(value)
|
|
105
|
+
if type(metatable) == "table" and rawget(metatable, "__tostring") ~= nil then
|
|
106
|
+
return tostring(value)
|
|
107
|
+
end
|
|
108
|
+
return renderTable(value, seen, depth, indent)
|
|
109
|
+
end
|
|
110
|
+
return tostring(value)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
local function join(meta, count, ...)
|
|
114
|
+
local parts = {}
|
|
115
|
+
for i = 1, count do
|
|
116
|
+
parts[#parts + 1] = render((select(i, ...)), meta ~= nil and meta[i] or nil, {}, 1, "")
|
|
117
|
+
end
|
|
118
|
+
return table.concat(parts, " ")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
local function prefixed(at, text)
|
|
122
|
+
if at == nil then
|
|
123
|
+
return text
|
|
124
|
+
end
|
|
125
|
+
return at .. ": " .. text
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
-- A line of the bundle, as the place in the project that wrote it.
|
|
129
|
+
local lines = __LINES__
|
|
130
|
+
local function place(line)
|
|
131
|
+
local origin = lines ~= nil and lines[tonumber(line)]
|
|
132
|
+
if not origin then
|
|
133
|
+
return nil
|
|
134
|
+
end
|
|
135
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
-- The place of the function `level` frames above the caller of this, or nil.
|
|
139
|
+
local function caller(level)
|
|
140
|
+
local line
|
|
141
|
+
if debug.info ~= nil then
|
|
142
|
+
line = debug.info(level + 1, "l")
|
|
143
|
+
elseif debug.getinfo ~= nil then
|
|
144
|
+
local info = debug.getinfo(level + 1, "l")
|
|
145
|
+
line = info and info.currentline
|
|
146
|
+
end
|
|
147
|
+
if line == nil or line < 0 then
|
|
148
|
+
return nil
|
|
149
|
+
end
|
|
150
|
+
return place(line)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
-- Luau names the bundle in every frame; the project's files are what the
|
|
154
|
+
-- reader knows. Frames the map has nothing for are dropped: they are this
|
|
155
|
+
-- runtime's own, and the bundle's.
|
|
156
|
+
local function trace(level)
|
|
157
|
+
local out = {}
|
|
158
|
+
for frame in debug.traceback("", level + 1):gmatch("[^\n]+") do
|
|
159
|
+
local line = frame:match(":(%d+)")
|
|
160
|
+
local at = line ~= nil and place(line) or nil
|
|
161
|
+
if at ~= nil then
|
|
162
|
+
local name = frame:match("function ([%w_.:]+)")
|
|
163
|
+
out[#out + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
return table.concat(out, "\n")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
-- Never on the caller's thread: reporting a problem must not become one.
|
|
170
|
+
local detach = (task ~= nil and task.spawn) or function(f)
|
|
171
|
+
local ok, problem = pcall(f)
|
|
172
|
+
if not ok then
|
|
173
|
+
print(problem)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
-- `tag` is how the line starts, `[src/a:12]:`, passed to `print` and `warn`
|
|
178
|
+
-- as an argument of its own so the host joins it the way it joins the rest.
|
|
179
|
+
function __NAME__.log(tag, meta, ...)
|
|
180
|
+
print(tag, join(meta, select("#", ...), ...))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
function __NAME__.warn(tag, meta, ...)
|
|
184
|
+
local text = join(meta, select("#", ...), ...)
|
|
185
|
+
if warn ~= nil then
|
|
186
|
+
warn(tag, text)
|
|
187
|
+
else
|
|
188
|
+
print(tag, "[warn]", text)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
function __NAME__.error(tag, meta, ...)
|
|
193
|
+
local message = tag .. " " .. join(meta, select("#", ...), ...)
|
|
194
|
+
local where = trace(2)
|
|
195
|
+
detach(function()
|
|
196
|
+
error(where ~= "" and (message .. "\n" .. where) or message, 0)
|
|
197
|
+
end)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
-- `error(message, level)`, raised for real — `pcall` still catches it — with
|
|
201
|
+
-- the position already in the project's files. Level 1, the default, is the
|
|
202
|
+
-- call itself, which the compiler wrote down; a higher level is a caller the
|
|
203
|
+
-- compiler could not see, so it is read off the stack. A message that is not
|
|
204
|
+
-- a string, or level 0, carries no position in Lua either.
|
|
205
|
+
function __NAME__.raise(at, message, level)
|
|
206
|
+
if level == nil then
|
|
207
|
+
level = 1
|
|
208
|
+
end
|
|
209
|
+
if type(message) ~= "string" or level == 0 then
|
|
210
|
+
error(message, 0)
|
|
211
|
+
end
|
|
212
|
+
local where = at
|
|
213
|
+
if level > 1 then
|
|
214
|
+
where = caller(level + 1)
|
|
215
|
+
end
|
|
216
|
+
error(prefixed(where, message), 0)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
function __NAME__.raiseValue(message, level)
|
|
220
|
+
if level == nil then
|
|
221
|
+
level = 1
|
|
222
|
+
end
|
|
223
|
+
if type(message) ~= "string" or level == 0 then
|
|
224
|
+
error(message, 0)
|
|
225
|
+
end
|
|
226
|
+
error(prefixed(caller(level + 1), message), 0)
|
|
227
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
local __NAME__ = {}
|
|
2
|
+
|
|
3
|
+
-- The globals whose Luau answer is several values, as tilua has them: one
|
|
4
|
+
-- value — an array, or an object. The compiler calls these instead of the
|
|
5
|
+
-- globals they stand for, and reading one of those globals as a value
|
|
6
|
+
-- (`local p = pcall`) gives the function here, so the two never disagree.
|
|
7
|
+
|
|
8
|
+
-- `pcall`'s `ok, ...` as `{ success, data, error }`. `data` is what the
|
|
9
|
+
-- function returned: one value, since a tilua function returns one.
|
|
10
|
+
local function result(ok, ...)
|
|
11
|
+
if ok then
|
|
12
|
+
return { success = true, data = (...) }
|
|
13
|
+
end
|
|
14
|
+
return { success = false, error = (...) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
function __NAME__.pcall(f, ...)
|
|
18
|
+
return result(pcall(f, ...))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
function __NAME__.xpcall(f, handler, ...)
|
|
22
|
+
return result(xpcall(f, handler, ...))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
function __NAME__.resume(co, ...)
|
|
26
|
+
return result(coroutine.resume(co, ...))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
-- Several values as an array — or nil, when all there was is nil.
|
|
30
|
+
local function values(...)
|
|
31
|
+
if select("#", ...) <= 1 and (...) == nil then
|
|
32
|
+
return nil
|
|
33
|
+
end
|
|
34
|
+
return { ... }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
function __NAME__.find(s, pattern, init, plain)
|
|
38
|
+
return values(string.find(s, pattern, init, plain))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
function __NAME__.match(s, pattern, init)
|
|
42
|
+
return values(string.match(s, pattern, init))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
function __NAME__.gsub(s, pattern, replacement, n)
|
|
46
|
+
return (string.gsub(s, pattern, replacement, n))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
function __NAME__.byte(s, i, j)
|
|
50
|
+
if j == nil then
|
|
51
|
+
return (string.byte(s, i))
|
|
52
|
+
end
|
|
53
|
+
return { string.byte(s, i, j) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
function __NAME__.modf(x)
|
|
57
|
+
return { math.modf(x) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
function __NAME__.frexp(x)
|
|
61
|
+
return { math.frexp(x) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
function __NAME__.next(t, key)
|
|
65
|
+
local k, v = next(t, key)
|
|
66
|
+
if k == nil then
|
|
67
|
+
return nil
|
|
68
|
+
end
|
|
69
|
+
return { k, v }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
function __NAME__.select(n, ...)
|
|
73
|
+
if n == "#" then
|
|
74
|
+
return select("#", ...)
|
|
75
|
+
end
|
|
76
|
+
return { select(n, ...) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
function __NAME__.unpack(list, i, j)
|
|
80
|
+
return { (table.unpack or unpack)(list, i or 1, j or #list) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
-- `pairs(t)` and `ipairs(t)` as an iteration — `{ step, state, first }`,
|
|
84
|
+
-- which a loop walks with. Each step answers one `{ key, value }`, and is
|
|
85
|
+
-- handed the one before it.
|
|
86
|
+
local function pairsStep(t, previous)
|
|
87
|
+
local k, v = next(t, previous ~= nil and previous[1] or nil)
|
|
88
|
+
if k == nil then
|
|
89
|
+
return nil
|
|
90
|
+
end
|
|
91
|
+
return { k, v }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
function __NAME__.pairs(t)
|
|
95
|
+
return { pairsStep, t, nil }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
local function ipairsStep(t, previous)
|
|
99
|
+
local i = (previous ~= nil and previous[1] or 0) + 1
|
|
100
|
+
local v = t[i]
|
|
101
|
+
if v == nil then
|
|
102
|
+
return nil
|
|
103
|
+
end
|
|
104
|
+
return { i, v }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
function __NAME__.ipairs(t)
|
|
108
|
+
return { ipairsStep, t, nil }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
-- An iterator function: each call is one match's captures, as an array.
|
|
112
|
+
function __NAME__.gmatch(s, pattern)
|
|
113
|
+
local step = string.gmatch(s, pattern)
|
|
114
|
+
return function()
|
|
115
|
+
return values(step())
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
function __NAME__.loadstring(source, chunkname)
|
|
120
|
+
local f, message = loadstring(source, chunkname)
|
|
121
|
+
return { f, message }
|
|
122
|
+
end
|