@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/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
- resolve(specifier: string): string | undefined;
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, or `undefined` when a module could not be compiled. Type
228
- * errors are reported but do not stop it. */
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
- resolve(specifier: string): string | undefined;
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, or `undefined` when a module could not be compiled. Type
228
- * errors are reported but do not stop it. */
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-VANZBLCL.js";
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 scopes = analyzeScopes(program2);
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.1.0",
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": "^3.1.0"
41
+ "@tilua/parser": "^5.0.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^22.20.2",
@@ -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
- table.move(t, 1, #t, 1, out)
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
- table.move(part, 1, #part, #out + 1, out)
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
- table.move(value, 1, #value, #out + 1, out)
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 // 2 do
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
@@ -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