@tilua/compiler 3.1.0 → 5.0.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/README.md +122 -12
- package/dist/{chunk-VANZBLCL.js → chunk-3J2QMA4W.js} +699 -135
- package/dist/cli.cjs +764 -178
- package/dist/cli.js +32 -9
- package/dist/index.cjs +721 -156
- package/dist/index.d.cts +44 -6
- package/dist/index.d.ts +44 -6
- package/dist/index.js +6 -4
- package/package.json +2 -2
- package/runtime/array.luau +17 -9
- package/runtime/console.luau +227 -0
- package/runtime/object.luau +4 -4
- package/runtime/result.luau +122 -0
- package/runtime/string.luau +2 -2
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;
|
|
@@ -100,6 +104,9 @@ interface ModuleContext {
|
|
|
100
104
|
* reads, as `__FAIL__`, to report an error from a callback the way an
|
|
101
105
|
* error that reached the entry is reported. */
|
|
102
106
|
readonly fail?: string;
|
|
107
|
+
/** Luau reaching the bundle's record of which tables are arrays, so that
|
|
108
|
+
* every module marks and reads the same one (`typeof`). */
|
|
109
|
+
readonly arrays?: string;
|
|
103
110
|
}
|
|
104
111
|
interface LowerOptions {
|
|
105
112
|
/** Lower the file as a module of a bundle. Without it, `import` and
|
|
@@ -126,6 +133,9 @@ interface LowerOptions {
|
|
|
126
133
|
* lowers what Luau adds to Lua and 5.1 has no syntax for — `a += b`,
|
|
127
134
|
* `continue`, `if c then a else b` as an expression, `//`. */
|
|
128
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;
|
|
129
139
|
}
|
|
130
140
|
/** `"luau"` is Roblox's Lua. `"lua51"` is stock Lua 5.1, which has none of
|
|
131
141
|
* Luau's additions — and no `goto` either, so `continue` has to be built out
|
|
@@ -186,6 +196,13 @@ interface TiluaConfigJson {
|
|
|
186
196
|
* `a += b`, `continue`, `if c then a else b` as an expression, `//`,
|
|
187
197
|
* digit separators and binary literals. */
|
|
188
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;
|
|
189
206
|
}
|
|
190
207
|
/** A config file's path, or the config itself. Relative paths — to a config
|
|
191
208
|
* file, and inside an inline config — resolve from `cwd`. */
|
|
@@ -211,6 +228,23 @@ interface BundleOptions {
|
|
|
211
228
|
readonly typeCheck?: boolean;
|
|
212
229
|
/** Which Lua the output must run on. Overrides the config's `target`. */
|
|
213
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[];
|
|
214
248
|
}
|
|
215
249
|
interface BundleDiagnostic {
|
|
216
250
|
/** Absolute path of the file it is about. */
|
|
@@ -224,9 +258,13 @@ interface BundleDiagnostic {
|
|
|
224
258
|
readonly category: "syntax" | "scope" | "module" | "type" | "config";
|
|
225
259
|
}
|
|
226
260
|
interface BundleResult {
|
|
227
|
-
/** The bundle
|
|
228
|
-
* 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. */
|
|
229
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[];
|
|
230
268
|
/** The bundled modules' names, entry first. */
|
|
231
269
|
readonly modules: string[];
|
|
232
270
|
readonly diagnostics: BundleDiagnostic[];
|
|
@@ -245,4 +283,4 @@ interface CompileResult {
|
|
|
245
283
|
}
|
|
246
284
|
declare function compile(source: string, config?: ConfigInput): Promise<CompileResult>;
|
|
247
285
|
|
|
248
|
-
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;
|
|
@@ -100,6 +104,9 @@ interface ModuleContext {
|
|
|
100
104
|
* reads, as `__FAIL__`, to report an error from a callback the way an
|
|
101
105
|
* error that reached the entry is reported. */
|
|
102
106
|
readonly fail?: string;
|
|
107
|
+
/** Luau reaching the bundle's record of which tables are arrays, so that
|
|
108
|
+
* every module marks and reads the same one (`typeof`). */
|
|
109
|
+
readonly arrays?: string;
|
|
103
110
|
}
|
|
104
111
|
interface LowerOptions {
|
|
105
112
|
/** Lower the file as a module of a bundle. Without it, `import` and
|
|
@@ -126,6 +133,9 @@ interface LowerOptions {
|
|
|
126
133
|
* lowers what Luau adds to Lua and 5.1 has no syntax for — `a += b`,
|
|
127
134
|
* `continue`, `if c then a else b` as an expression, `//`. */
|
|
128
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;
|
|
129
139
|
}
|
|
130
140
|
/** `"luau"` is Roblox's Lua. `"lua51"` is stock Lua 5.1, which has none of
|
|
131
141
|
* Luau's additions — and no `goto` either, so `continue` has to be built out
|
|
@@ -186,6 +196,13 @@ interface TiluaConfigJson {
|
|
|
186
196
|
* `a += b`, `continue`, `if c then a else b` as an expression, `//`,
|
|
187
197
|
* digit separators and binary literals. */
|
|
188
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;
|
|
189
206
|
}
|
|
190
207
|
/** A config file's path, or the config itself. Relative paths — to a config
|
|
191
208
|
* file, and inside an inline config — resolve from `cwd`. */
|
|
@@ -211,6 +228,23 @@ interface BundleOptions {
|
|
|
211
228
|
readonly typeCheck?: boolean;
|
|
212
229
|
/** Which Lua the output must run on. Overrides the config's `target`. */
|
|
213
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[];
|
|
214
248
|
}
|
|
215
249
|
interface BundleDiagnostic {
|
|
216
250
|
/** Absolute path of the file it is about. */
|
|
@@ -224,9 +258,13 @@ interface BundleDiagnostic {
|
|
|
224
258
|
readonly category: "syntax" | "scope" | "module" | "type" | "config";
|
|
225
259
|
}
|
|
226
260
|
interface BundleResult {
|
|
227
|
-
/** The bundle
|
|
228
|
-
* 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. */
|
|
229
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[];
|
|
230
268
|
/** The bundled modules' names, entry first. */
|
|
231
269
|
readonly modules: string[];
|
|
232
270
|
readonly diagnostics: BundleDiagnostic[];
|
|
@@ -245,4 +283,4 @@ interface CompileResult {
|
|
|
245
283
|
}
|
|
246
284
|
declare function compile(source: string, config?: ConfigInput): Promise<CompileResult>;
|
|
247
285
|
|
|
248
|
-
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-3J2QMA4W.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.0",
|
|
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.0"
|
|
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
|
|
@@ -19,7 +27,7 @@ function __NAME__.findIndex(t, test)
|
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
function __NAME__.filter(t, test)
|
|
22
|
-
local out = {}
|
|
30
|
+
local out = __ARRAY__({})
|
|
23
31
|
for i = 1, #t do
|
|
24
32
|
if test(t[i], i) then
|
|
25
33
|
out[#out + 1] = t[i]
|
|
@@ -29,7 +37,7 @@ function __NAME__.filter(t, test)
|
|
|
29
37
|
end
|
|
30
38
|
|
|
31
39
|
function __NAME__.map(t, transform)
|
|
32
|
-
local out = {}
|
|
40
|
+
local out = __ARRAY__({})
|
|
33
41
|
for i = 1, #t do
|
|
34
42
|
out[i] = transform(t[i], i)
|
|
35
43
|
end
|
|
@@ -95,11 +103,11 @@ function __NAME__.join(t, separator)
|
|
|
95
103
|
end
|
|
96
104
|
|
|
97
105
|
function __NAME__.concat(t, ...)
|
|
98
|
-
local out = {}
|
|
99
|
-
|
|
106
|
+
local out = __ARRAY__({})
|
|
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
|
|
@@ -114,7 +122,7 @@ function __NAME__.slice(t, start, stop)
|
|
|
114
122
|
if to < 0 then to = n + to + 1 end
|
|
115
123
|
if from < 1 then from = 1 end
|
|
116
124
|
if to > n then to = n end
|
|
117
|
-
local out = {}
|
|
125
|
+
local out = __ARRAY__({})
|
|
118
126
|
for i = from, to do
|
|
119
127
|
out[#out + 1] = t[i]
|
|
120
128
|
end
|
|
@@ -123,11 +131,11 @@ end
|
|
|
123
131
|
|
|
124
132
|
-- One level, as JavaScript's `flat()` does by default.
|
|
125
133
|
function __NAME__.flat(t)
|
|
126
|
-
local out = {}
|
|
134
|
+
local out = __ARRAY__({})
|
|
127
135
|
for i = 1, #t do
|
|
128
136
|
local value = t[i]
|
|
129
137
|
if type(value) == "table" then
|
|
130
|
-
|
|
138
|
+
__NAME___move(value, 1, #value, #out + 1, out)
|
|
131
139
|
else
|
|
132
140
|
out[#out + 1] = value
|
|
133
141
|
end
|
|
@@ -137,7 +145,7 @@ end
|
|
|
137
145
|
|
|
138
146
|
function __NAME__.reverse(t)
|
|
139
147
|
local n = #t
|
|
140
|
-
for i = 1, n
|
|
148
|
+
for i = 1, math.floor(n / 2) do
|
|
141
149
|
t[i], t[n - i + 1] = t[n - i + 1], t[i]
|
|
142
150
|
end
|
|
143
151
|
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
|
package/runtime/object.luau
CHANGED
|
@@ -5,7 +5,7 @@ local __NAME__ = {}
|
|
|
5
5
|
-- follow the named ones. With no one order to give (`known` is nil), every
|
|
6
6
|
-- key is listed, in the order `pairs` walks them.
|
|
7
7
|
local function keysOf(known, open, t)
|
|
8
|
-
local out = {}
|
|
8
|
+
local out = __ARRAY__({})
|
|
9
9
|
if known == nil then
|
|
10
10
|
for key in pairs(t) do
|
|
11
11
|
out[#out + 1] = key
|
|
@@ -37,7 +37,7 @@ end
|
|
|
37
37
|
-- rather than moving everything after it up one.
|
|
38
38
|
function __NAME__.values(known, open, t)
|
|
39
39
|
local keys = keysOf(known, open, t)
|
|
40
|
-
local out = {}
|
|
40
|
+
local out = __ARRAY__({})
|
|
41
41
|
for i = 1, #keys do
|
|
42
42
|
out[i] = t[keys[i]]
|
|
43
43
|
end
|
|
@@ -46,9 +46,9 @@ end
|
|
|
46
46
|
|
|
47
47
|
function __NAME__.entries(known, open, t)
|
|
48
48
|
local keys = keysOf(known, open, t)
|
|
49
|
-
local out = {}
|
|
49
|
+
local out = __ARRAY__({})
|
|
50
50
|
for i = 1, #keys do
|
|
51
|
-
out[i] = { keys[i], t[keys[i]] }
|
|
51
|
+
out[i] = __ARRAY__({ keys[i], t[keys[i]] })
|
|
52
52
|
end
|
|
53
53
|
return out
|
|
54
54
|
end
|