@silverbulletmd/silverbullet 2.4.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/LICENSE.md +18 -0
- package/README.md +98 -0
- package/client/asset_bundle/bundle.ts +95 -0
- package/client/data/datastore.ts +85 -0
- package/client/data/kv_primitives.ts +25 -0
- package/client/markdown_parser/constants.ts +13 -0
- package/client/plugos/event.ts +36 -0
- package/client/plugos/eventhook.ts +8 -0
- package/client/plugos/hooks/code_widget.ts +59 -0
- package/client/plugos/hooks/command.ts +104 -0
- package/client/plugos/hooks/document_editor.ts +77 -0
- package/client/plugos/hooks/event.ts +187 -0
- package/client/plugos/hooks/mq.ts +154 -0
- package/client/plugos/hooks/plug_namespace.ts +85 -0
- package/client/plugos/hooks/slash_command.ts +192 -0
- package/client/plugos/hooks/syscall.ts +66 -0
- package/client/plugos/manifest_cache.ts +67 -0
- package/client/plugos/plug.ts +99 -0
- package/client/plugos/plug_compile.ts +202 -0
- package/client/plugos/protocol.ts +40 -0
- package/client/plugos/proxy_fetch.ts +53 -0
- package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
- package/client/plugos/sandboxes/sandbox.ts +14 -0
- package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
- package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
- package/client/plugos/syscalls/asset.ts +35 -0
- package/client/plugos/syscalls/clientStore.ts +21 -0
- package/client/plugos/syscalls/client_code_widget.ts +12 -0
- package/client/plugos/syscalls/code_widget.ts +24 -0
- package/client/plugos/syscalls/config.ts +46 -0
- package/client/plugos/syscalls/datastore.ts +89 -0
- package/client/plugos/syscalls/editor.ts +673 -0
- package/client/plugos/syscalls/event.ts +36 -0
- package/client/plugos/syscalls/fetch.ts +128 -0
- package/client/plugos/syscalls/index.ts +102 -0
- package/client/plugos/syscalls/jsonschema.ts +69 -0
- package/client/plugos/syscalls/language.ts +23 -0
- package/client/plugos/syscalls/lua.ts +58 -0
- package/client/plugos/syscalls/markdown.ts +84 -0
- package/client/plugos/syscalls/mq.ts +52 -0
- package/client/plugos/syscalls/service_registry.ts +43 -0
- package/client/plugos/syscalls/shell.ts +39 -0
- package/client/plugos/syscalls/space.ts +139 -0
- package/client/plugos/syscalls/sync.ts +77 -0
- package/client/plugos/syscalls/system.ts +150 -0
- package/client/plugos/system.ts +201 -0
- package/client/plugos/types.ts +60 -0
- package/client/plugos/util.ts +14 -0
- package/client/plugos/worker_runtime.ts +195 -0
- package/client/space_lua/ast.ts +328 -0
- package/client/space_lua/ast_narrow.ts +81 -0
- package/client/space_lua/eval.ts +2478 -0
- package/client/space_lua/labels.ts +416 -0
- package/client/space_lua/numeric.ts +240 -0
- package/client/space_lua/parse.ts +1522 -0
- package/client/space_lua/query_collection.ts +232 -0
- package/client/space_lua/rp.ts +27 -0
- package/client/space_lua/runtime.ts +1702 -0
- package/client/space_lua/stdlib/crypto.ts +10 -0
- package/client/space_lua/stdlib/encoding.ts +19 -0
- package/client/space_lua/stdlib/format.ts +770 -0
- package/client/space_lua/stdlib/js.ts +73 -0
- package/client/space_lua/stdlib/load.ts +52 -0
- package/client/space_lua/stdlib/math.ts +193 -0
- package/client/space_lua/stdlib/net.ts +113 -0
- package/client/space_lua/stdlib/os.ts +368 -0
- package/client/space_lua/stdlib/space_lua.ts +153 -0
- package/client/space_lua/stdlib/string.ts +286 -0
- package/client/space_lua/stdlib/table.ts +401 -0
- package/client/space_lua/stdlib.ts +489 -0
- package/client/space_lua/tonumber.ts +501 -0
- package/client/space_lua/util.ts +96 -0
- package/dist/plug-compile.js +1513 -0
- package/package.json +120 -0
- package/plug-api/constants.ts +42 -0
- package/plug-api/lib/async.ts +162 -0
- package/plug-api/lib/crypto.ts +202 -0
- package/plug-api/lib/dates.ts +13 -0
- package/plug-api/lib/json.ts +136 -0
- package/plug-api/lib/limited_map.ts +72 -0
- package/plug-api/lib/memory_cache.ts +21 -0
- package/plug-api/lib/native_fetch.ts +6 -0
- package/plug-api/lib/ref.ts +275 -0
- package/plug-api/lib/resolve.ts +90 -0
- package/plug-api/lib/tags.ts +15 -0
- package/plug-api/lib/transclusion.ts +122 -0
- package/plug-api/lib/tree.ts +232 -0
- package/plug-api/lib/yaml.ts +284 -0
- package/plug-api/syscall.ts +15 -0
- package/plug-api/syscalls/asset.ts +36 -0
- package/plug-api/syscalls/client_store.ts +33 -0
- package/plug-api/syscalls/code_widget.ts +8 -0
- package/plug-api/syscalls/config.ts +58 -0
- package/plug-api/syscalls/datastore.ts +96 -0
- package/plug-api/syscalls/editor.ts +517 -0
- package/plug-api/syscalls/event.ts +47 -0
- package/plug-api/syscalls/index.ts +77 -0
- package/plug-api/syscalls/jsonschema.ts +25 -0
- package/plug-api/syscalls/language.ts +23 -0
- package/plug-api/syscalls/lua.ts +20 -0
- package/plug-api/syscalls/markdown.ts +38 -0
- package/plug-api/syscalls/mq.ts +79 -0
- package/plug-api/syscalls/shell.ts +14 -0
- package/plug-api/syscalls/space.ts +212 -0
- package/plug-api/syscalls/sync.ts +28 -0
- package/plug-api/syscalls/system.ts +102 -0
- package/plug-api/syscalls/yaml.ts +28 -0
- package/plug-api/syscalls.ts +21 -0
- package/plug-api/system_mock.ts +89 -0
- package/plug-api/types/client.ts +116 -0
- package/plug-api/types/config.ts +22 -0
- package/plug-api/types/datastore.ts +28 -0
- package/plug-api/types/event.ts +27 -0
- package/plug-api/types/index.ts +56 -0
- package/plug-api/types/manifest.ts +98 -0
- package/plug-api/types/namespace.ts +6 -0
- package/plugs/builtin_plugs.ts +14 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
// Goto/label resolution and validation for function bodies
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ASTCtx,
|
|
5
|
+
LuaBlock,
|
|
6
|
+
LuaForInStatement,
|
|
7
|
+
LuaForStatement,
|
|
8
|
+
LuaGotoStatement,
|
|
9
|
+
LuaIfStatement,
|
|
10
|
+
LuaLabelStatement,
|
|
11
|
+
LuaLocalStatement,
|
|
12
|
+
LuaRepeatStatement,
|
|
13
|
+
LuaWhileStatement,
|
|
14
|
+
} from "./ast.ts";
|
|
15
|
+
import { LuaAttribute } from "./ast.ts";
|
|
16
|
+
import { asBlock } from "./ast_narrow.ts";
|
|
17
|
+
|
|
18
|
+
type BlockGotoMeta = {
|
|
19
|
+
labels: Map<string, number>; // in this block
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type FunctionMeta = {
|
|
23
|
+
// Stores per-block data and drops entries automatically when a block
|
|
24
|
+
// is no longer used.
|
|
25
|
+
blockMeta: WeakMap<LuaBlock, BlockGotoMeta>;
|
|
26
|
+
funcHasGotos: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
class LabelResolveError extends Error {
|
|
30
|
+
constructor(msg: string, public astCtx: ASTCtx) {
|
|
31
|
+
super(msg);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Cache
|
|
36
|
+
const functionMetaByRoot = new WeakMap<LuaBlock, FunctionMeta>();
|
|
37
|
+
const functionMetaByAnyBlock = new WeakMap<LuaBlock, FunctionMeta>();
|
|
38
|
+
|
|
39
|
+
export function getBlockGotoMeta(
|
|
40
|
+
block: LuaBlock,
|
|
41
|
+
): (BlockGotoMeta & { funcHasGotos: boolean }) | undefined {
|
|
42
|
+
let fm = functionMetaByAnyBlock.get(block);
|
|
43
|
+
if (!fm) {
|
|
44
|
+
fm = resolveFunction(block);
|
|
45
|
+
}
|
|
46
|
+
const bm = fm.blockMeta.get(block);
|
|
47
|
+
if (!bm) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return { ...bm, funcHasGotos: fm.funcHasGotos };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type LocalID = number;
|
|
54
|
+
|
|
55
|
+
type GotoInfo = {
|
|
56
|
+
node: LuaGotoStatement;
|
|
57
|
+
active: Set<LocalID>;
|
|
58
|
+
block: LuaBlock;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type ValidationCtx = {
|
|
62
|
+
labelActiveByBlock: WeakMap<LuaBlock, Map<string, Set<LocalID>>>;
|
|
63
|
+
labelLocByBlock: WeakMap<
|
|
64
|
+
LuaBlock,
|
|
65
|
+
Map<string, { index: number; ctx: ASTCtx }>
|
|
66
|
+
>;
|
|
67
|
+
gotos: GotoInfo[];
|
|
68
|
+
hasGoto: boolean;
|
|
69
|
+
nextLocalId: number;
|
|
70
|
+
closeLocals: Set<LocalID>;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type BlockRole =
|
|
74
|
+
| "Root"
|
|
75
|
+
| "Do"
|
|
76
|
+
| "If"
|
|
77
|
+
| "While"
|
|
78
|
+
| "Repeat"
|
|
79
|
+
| "For"
|
|
80
|
+
| "ForIn";
|
|
81
|
+
|
|
82
|
+
function resolveFunction(root: LuaBlock): FunctionMeta {
|
|
83
|
+
const existing = functionMetaByRoot.get(root);
|
|
84
|
+
if (existing) {
|
|
85
|
+
return existing;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const blockMeta = new WeakMap<LuaBlock, BlockGotoMeta>();
|
|
89
|
+
const vctx: ValidationCtx = {
|
|
90
|
+
labelActiveByBlock: new WeakMap(),
|
|
91
|
+
labelLocByBlock: new WeakMap(),
|
|
92
|
+
gotos: [],
|
|
93
|
+
hasGoto: false,
|
|
94
|
+
nextLocalId: 1,
|
|
95
|
+
closeLocals: new Set<LocalID>(),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const seenBlocks = new Set<LuaBlock>();
|
|
99
|
+
const parentByBlock = new WeakMap<LuaBlock, LuaBlock | undefined>();
|
|
100
|
+
const roleByBlock = new WeakMap<LuaBlock, BlockRole>();
|
|
101
|
+
|
|
102
|
+
processBlock(
|
|
103
|
+
root,
|
|
104
|
+
undefined,
|
|
105
|
+
"Root",
|
|
106
|
+
new Set<LocalID>(),
|
|
107
|
+
blockMeta,
|
|
108
|
+
vctx,
|
|
109
|
+
seenBlocks,
|
|
110
|
+
parentByBlock,
|
|
111
|
+
roleByBlock,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Validate gotos
|
|
115
|
+
for (const g of vctx.gotos) {
|
|
116
|
+
const target = g.node.name;
|
|
117
|
+
|
|
118
|
+
// Search current block for the label, then ancestors
|
|
119
|
+
let searchBlock: LuaBlock | undefined = g.block;
|
|
120
|
+
let labelIndex: number | undefined;
|
|
121
|
+
let labelDefBlock: LuaBlock | undefined;
|
|
122
|
+
|
|
123
|
+
while (searchBlock) {
|
|
124
|
+
const meta = blockMeta.get(searchBlock);
|
|
125
|
+
if (meta && meta.labels.has(target)) {
|
|
126
|
+
labelIndex = meta.labels.get(target);
|
|
127
|
+
labelDefBlock = searchBlock;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
searchBlock = parentByBlock.get(searchBlock);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (labelIndex === undefined || !labelDefBlock) {
|
|
134
|
+
throw new LabelResolveError(
|
|
135
|
+
`no visible label '${target}' for goto`,
|
|
136
|
+
g.node.ctx,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const activeMap = vctx.labelActiveByBlock.get(labelDefBlock);
|
|
141
|
+
const locMap = vctx.labelLocByBlock.get(labelDefBlock);
|
|
142
|
+
if (!activeMap || !locMap) {
|
|
143
|
+
throw new LabelResolveError(
|
|
144
|
+
`no visible label '${target}' for goto`,
|
|
145
|
+
g.node.ctx,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const lset = activeMap.get(target);
|
|
150
|
+
const lloc = locMap.get(target);
|
|
151
|
+
if (!lset || !lloc) {
|
|
152
|
+
throw new LabelResolveError(
|
|
153
|
+
`no visible label '${target}' for goto`,
|
|
154
|
+
g.node.ctx,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Local scope forward jump check
|
|
159
|
+
let entersLocalScope = false;
|
|
160
|
+
let entersCloseScope = false;
|
|
161
|
+
|
|
162
|
+
for (const id of lset) {
|
|
163
|
+
if (!g.active.has(id)) {
|
|
164
|
+
entersLocalScope = true;
|
|
165
|
+
if (vctx.closeLocals.has(id)) {
|
|
166
|
+
entersCloseScope = true;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (entersCloseScope) {
|
|
173
|
+
throw new LabelResolveError(
|
|
174
|
+
`goto '${target}' jumps into the scope of a local variable`,
|
|
175
|
+
g.node.ctx,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (entersLocalScope) {
|
|
180
|
+
const safeEnd = isSafeEndLabel(labelDefBlock, lloc.index, roleByBlock);
|
|
181
|
+
if (!safeEnd) {
|
|
182
|
+
throw new LabelResolveError(
|
|
183
|
+
`goto '${target}' jumps into the scope of a local variable`,
|
|
184
|
+
g.node.ctx,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const fm: FunctionMeta = {
|
|
191
|
+
blockMeta,
|
|
192
|
+
funcHasGotos: vctx.hasGoto,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
functionMetaByRoot.set(root, fm);
|
|
196
|
+
for (const block of seenBlocks) {
|
|
197
|
+
functionMetaByAnyBlock.set(block, fm);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return fm;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isSafeEndLabel(
|
|
204
|
+
block: LuaBlock,
|
|
205
|
+
labelIndex: number,
|
|
206
|
+
roleByBlock: WeakMap<LuaBlock, BlockRole>,
|
|
207
|
+
): boolean {
|
|
208
|
+
const role = roleByBlock.get(block);
|
|
209
|
+
if (role === "Repeat") {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
for (let i = labelIndex + 1; i < block.statements.length; i++) {
|
|
213
|
+
const t = block.statements[i].type;
|
|
214
|
+
if (t !== "Label" && t !== "Semicolon") {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function cloneSet<T>(s: Set<T>): Set<T> {
|
|
222
|
+
const c = new Set<T>();
|
|
223
|
+
for (const v of s) {
|
|
224
|
+
c.add(v);
|
|
225
|
+
}
|
|
226
|
+
return c;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function processBlock(
|
|
230
|
+
block: LuaBlock,
|
|
231
|
+
parent: LuaBlock | undefined,
|
|
232
|
+
role: BlockRole,
|
|
233
|
+
active: Set<LocalID>,
|
|
234
|
+
blockMeta: WeakMap<LuaBlock, BlockGotoMeta>,
|
|
235
|
+
vctx: ValidationCtx,
|
|
236
|
+
seen: Set<LuaBlock>,
|
|
237
|
+
parentByBlock: WeakMap<LuaBlock, LuaBlock | undefined>,
|
|
238
|
+
roleByBlock: WeakMap<LuaBlock, BlockRole>,
|
|
239
|
+
): void {
|
|
240
|
+
const labels = new Map<string, number>();
|
|
241
|
+
blockMeta.set(block, { labels });
|
|
242
|
+
|
|
243
|
+
seen.add(block);
|
|
244
|
+
parentByBlock.set(block, parent);
|
|
245
|
+
roleByBlock.set(block, role);
|
|
246
|
+
|
|
247
|
+
const labelActiveMap = new Map<string, Set<LocalID>>();
|
|
248
|
+
const labelLocMap = new Map<string, { index: number; ctx: ASTCtx }>();
|
|
249
|
+
vctx.labelActiveByBlock.set(block, labelActiveMap);
|
|
250
|
+
vctx.labelLocByBlock.set(block, labelLocMap);
|
|
251
|
+
|
|
252
|
+
const curActive = cloneSet(active);
|
|
253
|
+
|
|
254
|
+
const stmts = block.statements;
|
|
255
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
256
|
+
const s = stmts[i];
|
|
257
|
+
switch (s.type) {
|
|
258
|
+
case "Label": {
|
|
259
|
+
const lab = s as LuaLabelStatement;
|
|
260
|
+
if (labels.has(lab.name)) {
|
|
261
|
+
throw new LabelResolveError(
|
|
262
|
+
`label '${lab.name}' already defined`,
|
|
263
|
+
lab.ctx,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
labels.set(lab.name, i);
|
|
267
|
+
const actSet = cloneSet(curActive);
|
|
268
|
+
labelActiveMap.set(lab.name, actSet);
|
|
269
|
+
labelLocMap.set(lab.name, { index: i, ctx: lab.ctx });
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case "Goto": {
|
|
273
|
+
const g = s as LuaGotoStatement;
|
|
274
|
+
vctx.hasGoto = true;
|
|
275
|
+
vctx.gotos.push({ node: g, active: cloneSet(curActive), block });
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case "Local": {
|
|
279
|
+
const l = s as LuaLocalStatement;
|
|
280
|
+
for (let j = 0; j < l.names.length; j++) {
|
|
281
|
+
const id = vctx.nextLocalId++;
|
|
282
|
+
curActive.add(id);
|
|
283
|
+
|
|
284
|
+
const isClose =
|
|
285
|
+
l.names[j].attributes?.includes(LuaAttribute.Close) ===
|
|
286
|
+
true;
|
|
287
|
+
if (isClose) {
|
|
288
|
+
vctx.closeLocals.add(id);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case "LocalFunction": {
|
|
294
|
+
curActive.add(vctx.nextLocalId++);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "Function": {
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case "For": {
|
|
301
|
+
const fr = s as LuaForStatement;
|
|
302
|
+
const childActive = cloneSet(curActive);
|
|
303
|
+
childActive.add(vctx.nextLocalId++);
|
|
304
|
+
processBlock(
|
|
305
|
+
fr.block,
|
|
306
|
+
block,
|
|
307
|
+
"For",
|
|
308
|
+
childActive,
|
|
309
|
+
blockMeta,
|
|
310
|
+
vctx,
|
|
311
|
+
seen,
|
|
312
|
+
parentByBlock,
|
|
313
|
+
roleByBlock,
|
|
314
|
+
);
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case "ForIn": {
|
|
318
|
+
const fi = s as LuaForInStatement;
|
|
319
|
+
const childActive = cloneSet(curActive);
|
|
320
|
+
for (let j = 0; j < fi.names.length; j++) {
|
|
321
|
+
childActive.add(vctx.nextLocalId++);
|
|
322
|
+
}
|
|
323
|
+
processBlock(
|
|
324
|
+
fi.block,
|
|
325
|
+
block,
|
|
326
|
+
"ForIn",
|
|
327
|
+
childActive,
|
|
328
|
+
blockMeta,
|
|
329
|
+
vctx,
|
|
330
|
+
seen,
|
|
331
|
+
parentByBlock,
|
|
332
|
+
roleByBlock,
|
|
333
|
+
);
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
case "While": {
|
|
337
|
+
const w = s as LuaWhileStatement;
|
|
338
|
+
processBlock(
|
|
339
|
+
w.block,
|
|
340
|
+
block,
|
|
341
|
+
"While",
|
|
342
|
+
cloneSet(curActive),
|
|
343
|
+
blockMeta,
|
|
344
|
+
vctx,
|
|
345
|
+
seen,
|
|
346
|
+
parentByBlock,
|
|
347
|
+
roleByBlock,
|
|
348
|
+
);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
case "Repeat": {
|
|
352
|
+
const r = s as LuaRepeatStatement;
|
|
353
|
+
processBlock(
|
|
354
|
+
r.block,
|
|
355
|
+
block,
|
|
356
|
+
"Repeat",
|
|
357
|
+
cloneSet(curActive),
|
|
358
|
+
blockMeta,
|
|
359
|
+
vctx,
|
|
360
|
+
seen,
|
|
361
|
+
parentByBlock,
|
|
362
|
+
roleByBlock,
|
|
363
|
+
);
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
case "If": {
|
|
367
|
+
const iff = s as LuaIfStatement;
|
|
368
|
+
for (let k = 0; k < iff.conditions.length; k++) {
|
|
369
|
+
processBlock(
|
|
370
|
+
iff.conditions[k].block,
|
|
371
|
+
block,
|
|
372
|
+
"If",
|
|
373
|
+
cloneSet(curActive),
|
|
374
|
+
blockMeta,
|
|
375
|
+
vctx,
|
|
376
|
+
seen,
|
|
377
|
+
parentByBlock,
|
|
378
|
+
roleByBlock,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
if (iff.elseBlock) {
|
|
382
|
+
processBlock(
|
|
383
|
+
iff.elseBlock,
|
|
384
|
+
block,
|
|
385
|
+
"If",
|
|
386
|
+
cloneSet(curActive),
|
|
387
|
+
blockMeta,
|
|
388
|
+
vctx,
|
|
389
|
+
seen,
|
|
390
|
+
parentByBlock,
|
|
391
|
+
roleByBlock,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case "Block": {
|
|
397
|
+
const child = asBlock(s);
|
|
398
|
+
processBlock(
|
|
399
|
+
child,
|
|
400
|
+
block,
|
|
401
|
+
"Do",
|
|
402
|
+
cloneSet(curActive),
|
|
403
|
+
blockMeta,
|
|
404
|
+
vctx,
|
|
405
|
+
seen,
|
|
406
|
+
parentByBlock,
|
|
407
|
+
roleByBlock,
|
|
408
|
+
);
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
default: {
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import type { NumericType } from "./ast.ts";
|
|
2
|
+
import { luaToNumberDetailed } from "./tonumber.ts";
|
|
3
|
+
import { luaTypeName } from "./runtime.ts";
|
|
4
|
+
|
|
5
|
+
export interface LuaTaggedFloat {
|
|
6
|
+
readonly value: number;
|
|
7
|
+
readonly isFloat: true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Pre-allocated singletons for float zeros
|
|
11
|
+
const FLOAT_POS_ZERO: LuaTaggedFloat = { value: 0, isFloat: true };
|
|
12
|
+
const FLOAT_NEG_ZERO: LuaTaggedFloat = { value: -0, isFloat: true };
|
|
13
|
+
|
|
14
|
+
export const luaStringCoercionError: Error = new Error(
|
|
15
|
+
"LuaStringCoercionError",
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export function isNegativeZero(n: number): boolean {
|
|
19
|
+
return n === 0 && 1 / n === -Infinity;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isTaggedFloat(v: unknown): v is LuaTaggedFloat {
|
|
23
|
+
return v !== null && typeof v === "object" && (v as any).isFloat === true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function makeFloat(n: number): LuaTaggedFloat {
|
|
27
|
+
if (n === 0) {
|
|
28
|
+
return isNegativeZero(n) ? FLOAT_NEG_ZERO : FLOAT_POS_ZERO;
|
|
29
|
+
}
|
|
30
|
+
return { value: n, isFloat: true };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Box a zero with a given kind tag.
|
|
34
|
+
export function makeLuaZero(
|
|
35
|
+
n: number,
|
|
36
|
+
numericType: NumericType,
|
|
37
|
+
): any {
|
|
38
|
+
if (n !== 0) {
|
|
39
|
+
return n;
|
|
40
|
+
}
|
|
41
|
+
if (numericType !== "float") {
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
return isNegativeZero(n) ? FLOAT_NEG_ZERO : FLOAT_POS_ZERO;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Tag an integer-valued number as float.
|
|
48
|
+
// Only allocates for integer-valued results; non-integer floats
|
|
49
|
+
// (1.5, NaN, Inf) are already unambiguously float as plain `number`.
|
|
50
|
+
export function makeLuaFloat(n: number): any {
|
|
51
|
+
if (!Number.isInteger(n)) {
|
|
52
|
+
return n;
|
|
53
|
+
}
|
|
54
|
+
return makeFloat(n);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getZeroBoxKind(x: any): NumericType | undefined {
|
|
58
|
+
if (isTaggedFloat(x)) {
|
|
59
|
+
return "float";
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Unwrap a potentially tagged or boxed Number to a plain number.
|
|
65
|
+
export function untagNumber(n: any): number {
|
|
66
|
+
if (typeof n === "number") return n;
|
|
67
|
+
if (isTaggedFloat(n)) {
|
|
68
|
+
return n.value;
|
|
69
|
+
}
|
|
70
|
+
return n;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function coerceToNumber(v: unknown): number | null {
|
|
74
|
+
if (typeof v === "number") {
|
|
75
|
+
return v;
|
|
76
|
+
}
|
|
77
|
+
if (isTaggedFloat(v)) {
|
|
78
|
+
return v.value;
|
|
79
|
+
}
|
|
80
|
+
if (typeof v === "string") {
|
|
81
|
+
const det = luaToNumberDetailed(v);
|
|
82
|
+
if (!det) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return det.value;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function inferNumericType(n: number): NumericType {
|
|
91
|
+
if (!Number.isFinite(n)) {
|
|
92
|
+
return "float";
|
|
93
|
+
}
|
|
94
|
+
if (isNegativeZero(n)) {
|
|
95
|
+
return "float";
|
|
96
|
+
}
|
|
97
|
+
return Number.isInteger(n) ? "int" : "float";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function combineNumericTypes(
|
|
101
|
+
a: NumericType | undefined,
|
|
102
|
+
b: NumericType | undefined,
|
|
103
|
+
): NumericType {
|
|
104
|
+
if (a === "float" || b === "float") {
|
|
105
|
+
return "float";
|
|
106
|
+
}
|
|
107
|
+
return "int";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getNumericKind(
|
|
111
|
+
n: unknown,
|
|
112
|
+
): NumericType | undefined {
|
|
113
|
+
if (typeof n === "number") {
|
|
114
|
+
return inferNumericType(n);
|
|
115
|
+
}
|
|
116
|
+
if (isTaggedFloat(n)) {
|
|
117
|
+
return "float";
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export type CoerceNumericResult = {
|
|
123
|
+
n: number;
|
|
124
|
+
type: NumericType;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export function coerceNumeric(
|
|
128
|
+
val: unknown,
|
|
129
|
+
hint?: NumericType,
|
|
130
|
+
): CoerceNumericResult {
|
|
131
|
+
if (typeof val === "number") {
|
|
132
|
+
return { n: val, type: hint ?? inferNumericType(val) };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (isTaggedFloat(val)) {
|
|
136
|
+
return { n: val.value, type: hint ?? "float" };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (typeof val === "string") {
|
|
140
|
+
const det = luaToNumberDetailed(val);
|
|
141
|
+
if (!det) {
|
|
142
|
+
throw luaStringCoercionError;
|
|
143
|
+
}
|
|
144
|
+
return { n: det.value, type: hint ?? det.numericType };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new Error(
|
|
148
|
+
`attempt to perform arithmetic on a ${luaTypeName(val)} value`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type CoerceNumericPairResult = {
|
|
153
|
+
left: number;
|
|
154
|
+
right: number;
|
|
155
|
+
resultType: NumericType;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export function coerceNumericPair(
|
|
159
|
+
a: unknown,
|
|
160
|
+
b: unknown,
|
|
161
|
+
leftType?: NumericType,
|
|
162
|
+
rightType?: NumericType,
|
|
163
|
+
op?: string,
|
|
164
|
+
): CoerceNumericPairResult {
|
|
165
|
+
const forceFloat = op === "/" || op === "^";
|
|
166
|
+
|
|
167
|
+
// Both plain numbers
|
|
168
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
169
|
+
const lt = leftType ?? inferNumericType(a);
|
|
170
|
+
const rt = rightType ?? inferNumericType(b);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
left: a,
|
|
174
|
+
right: b,
|
|
175
|
+
resultType: forceFloat
|
|
176
|
+
? "float"
|
|
177
|
+
: ((lt === "float" || rt === "float") ? "float" : "int"),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// One tagged float, one plain number
|
|
182
|
+
if (typeof a === "number" && isTaggedFloat(b)) {
|
|
183
|
+
return {
|
|
184
|
+
left: a,
|
|
185
|
+
right: b.value,
|
|
186
|
+
resultType: "float",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (isTaggedFloat(a) && typeof b === "number") {
|
|
191
|
+
return {
|
|
192
|
+
left: a.value,
|
|
193
|
+
right: b,
|
|
194
|
+
resultType: "float",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Both tagged floats
|
|
199
|
+
if (isTaggedFloat(a) && isTaggedFloat(b)) {
|
|
200
|
+
return {
|
|
201
|
+
left: a.value,
|
|
202
|
+
right: b.value,
|
|
203
|
+
resultType: "float",
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// General fallback
|
|
208
|
+
const A = coerceNumeric(a, leftType);
|
|
209
|
+
const B = coerceNumeric(b, rightType);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
left: A.n,
|
|
213
|
+
right: B.n,
|
|
214
|
+
resultType: forceFloat ? "float" : combineNumericTypes(A.type, B.type),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function normalizeArithmeticResult(
|
|
219
|
+
n: number,
|
|
220
|
+
resultType: NumericType,
|
|
221
|
+
): number {
|
|
222
|
+
if (n === 0) {
|
|
223
|
+
if (resultType === "int") {
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
return isNegativeZero(n) ? -0 : 0;
|
|
227
|
+
}
|
|
228
|
+
return n;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function toInteger(v: unknown): number | null {
|
|
232
|
+
if (typeof v === "number") {
|
|
233
|
+
return Number.isInteger(v) ? v : null;
|
|
234
|
+
}
|
|
235
|
+
if (isTaggedFloat(v)) {
|
|
236
|
+
const n = v.value;
|
|
237
|
+
return Number.isInteger(n) ? n : null;
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|