sci2-ts 0.0.0-stage → 0.2.2
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/CHANGELOG.md +78 -0
- package/LICENSE +21 -0
- package/README.md +113 -2
- package/apps/viewer/editor.html +43 -0
- package/apps/viewer/explore.html +27 -0
- package/apps/viewer/index.html +26 -0
- package/apps/viewer/package.json +27 -0
- package/apps/viewer/play.html +26 -0
- package/apps/viewer/plugin.d.ts +18 -0
- package/apps/viewer/plugin.js +75 -0
- package/apps/viewer/plugin.ts +84 -0
- package/apps/viewer/src/adlib.d.ts +20 -0
- package/apps/viewer/src/adlib.js +55 -0
- package/apps/viewer/src/adlib.ts +62 -0
- package/apps/viewer/src/app.d.ts +5 -0
- package/apps/viewer/src/app.js +5 -0
- package/apps/viewer/src/app.ts +5 -0
- package/apps/viewer/src/asset-index.d.ts +22 -0
- package/apps/viewer/src/asset-index.js +74 -0
- package/apps/viewer/src/asset-index.ts +90 -0
- package/apps/viewer/src/audio.d.ts +28 -0
- package/apps/viewer/src/audio.js +102 -0
- package/apps/viewer/src/audio.ts +110 -0
- package/apps/viewer/src/editor-handles.d.ts +63 -0
- package/apps/viewer/src/editor-handles.js +332 -0
- package/apps/viewer/src/editor-handles.ts +368 -0
- package/apps/viewer/src/editor-languages.d.ts +23 -0
- package/apps/viewer/src/editor-languages.js +157 -0
- package/apps/viewer/src/editor-languages.ts +135 -0
- package/apps/viewer/src/editor.css +213 -0
- package/apps/viewer/src/editor.d.ts +1 -0
- package/apps/viewer/src/editor.js +393 -0
- package/apps/viewer/src/editor.ts +412 -0
- package/apps/viewer/src/explore/classes.d.ts +7 -0
- package/apps/viewer/src/explore/classes.js +63 -0
- package/apps/viewer/src/explore/classes.ts +61 -0
- package/apps/viewer/src/explore/context.d.ts +33 -0
- package/apps/viewer/src/explore/context.js +96 -0
- package/apps/viewer/src/explore/context.ts +124 -0
- package/apps/viewer/src/explore/explore.css +93 -0
- package/apps/viewer/src/explore/explore.d.ts +1 -0
- package/apps/viewer/src/explore/explore.js +102 -0
- package/apps/viewer/src/explore/explore.ts +104 -0
- package/apps/viewer/src/explore/inspect.d.ts +5 -0
- package/apps/viewer/src/explore/inspect.js +208 -0
- package/apps/viewer/src/explore/inspect.ts +279 -0
- package/apps/viewer/src/explore/live.d.ts +22 -0
- package/apps/viewer/src/explore/live.js +318 -0
- package/apps/viewer/src/explore/live.ts +365 -0
- package/apps/viewer/src/explore/map.d.ts +11 -0
- package/apps/viewer/src/explore/map.js +182 -0
- package/apps/viewer/src/explore/map.ts +193 -0
- package/apps/viewer/src/files.d.ts +21 -0
- package/apps/viewer/src/files.js +69 -0
- package/apps/viewer/src/files.ts +72 -0
- package/apps/viewer/src/game-profile.d.ts +19 -0
- package/apps/viewer/src/game-profile.js +4 -0
- package/apps/viewer/src/game-profile.ts +26 -0
- package/apps/viewer/src/main.d.ts +1 -0
- package/apps/viewer/src/main.js +473 -0
- package/apps/viewer/src/main.ts +527 -0
- package/apps/viewer/src/music.d.ts +14 -0
- package/apps/viewer/src/music.js +32 -0
- package/apps/viewer/src/music.ts +34 -0
- package/apps/viewer/src/opl-worklet.d.ts +1 -0
- package/apps/viewer/src/opl-worklet.js +47 -0
- package/apps/viewer/src/opl-worklet.ts +55 -0
- package/apps/viewer/src/overlay.d.ts +31 -0
- package/apps/viewer/src/overlay.js +103 -0
- package/apps/viewer/src/overlay.ts +123 -0
- package/apps/viewer/src/play.css +14 -0
- package/apps/viewer/src/play.d.ts +1 -0
- package/apps/viewer/src/play.js +179 -0
- package/apps/viewer/src/play.ts +177 -0
- package/apps/viewer/src/saves.d.ts +10 -0
- package/apps/viewer/src/saves.js +50 -0
- package/apps/viewer/src/saves.ts +51 -0
- package/apps/viewer/src/session.d.ts +91 -0
- package/apps/viewer/src/session.js +301 -0
- package/apps/viewer/src/session.ts +321 -0
- package/apps/viewer/src/sound-preview.d.ts +7 -0
- package/apps/viewer/src/sound-preview.js +214 -0
- package/apps/viewer/src/sound-preview.ts +228 -0
- package/apps/viewer/src/style.css +64 -0
- package/apps/viewer/vite.config.ts +25 -0
- package/bin/sci-ts.mjs +9 -0
- package/lib/994.sc +49 -0
- package/lib/995.sc +53 -0
- package/lib/996.sc +299 -0
- package/lib/997.sc +198 -0
- package/lib/998.sc +338 -0
- package/lib/999.sc +189 -0
- package/lib/resources.d.ts +2 -0
- package/lib/resources.js +84 -0
- package/lib/resources.ts +88 -0
- package/lib/system.sh +62 -0
- package/package.json +75 -4
- package/packages/content/room.schema.json +267 -0
- package/packages/content/src/index.d.ts +3 -0
- package/packages/content/src/index.js +3 -0
- package/packages/content/src/index.ts +3 -0
- package/packages/content/src/room.d.ts +111 -0
- package/packages/content/src/room.js +886 -0
- package/packages/content/src/room.ts +1011 -0
- package/packages/content/src/target.d.ts +80 -0
- package/packages/content/src/target.js +1 -0
- package/packages/content/src/target.ts +60 -0
- package/packages/content/src/yarn.d.ts +90 -0
- package/packages/content/src/yarn.js +214 -0
- package/packages/content/src/yarn.ts +261 -0
- package/packages/sci/src/analysis/game.d.ts +90 -0
- package/packages/sci/src/analysis/game.js +221 -0
- package/packages/sci/src/analysis/game.ts +305 -0
- package/packages/sci/src/analysis/runtime.d.ts +58 -0
- package/packages/sci/src/analysis/runtime.js +133 -0
- package/packages/sci/src/analysis/runtime.ts +163 -0
- package/packages/sci/src/audio/adlib.d.ts +73 -0
- package/packages/sci/src/audio/adlib.js +280 -0
- package/packages/sci/src/audio/adlib.ts +329 -0
- package/packages/sci/src/audio/index.d.ts +43 -0
- package/packages/sci/src/audio/index.js +99 -0
- package/packages/sci/src/audio/index.ts +122 -0
- package/packages/sci/src/audio/midi.d.ts +92 -0
- package/packages/sci/src/audio/midi.js +242 -0
- package/packages/sci/src/audio/midi.ts +262 -0
- package/packages/sci/src/audio/opl2.d.ts +74 -0
- package/packages/sci/src/audio/opl2.js +283 -0
- package/packages/sci/src/audio/opl2.ts +283 -0
- package/packages/sci/src/audio/sol.d.ts +32 -0
- package/packages/sci/src/audio/sol.js +81 -0
- package/packages/sci/src/audio/sol.ts +100 -0
- package/packages/sci/src/audio/write.d.ts +35 -0
- package/packages/sci/src/audio/write.js +91 -0
- package/packages/sci/src/audio/write.ts +108 -0
- package/packages/sci/src/gfx/cel.d.ts +36 -0
- package/packages/sci/src/gfx/cel.js +82 -0
- package/packages/sci/src/gfx/cel.ts +110 -0
- package/packages/sci/src/gfx/palette.d.ts +24 -0
- package/packages/sci/src/gfx/palette.js +49 -0
- package/packages/sci/src/gfx/palette.ts +56 -0
- package/packages/sci/src/gfx/pic-file.d.ts +46 -0
- package/packages/sci/src/gfx/pic-file.js +114 -0
- package/packages/sci/src/gfx/pic-file.ts +155 -0
- package/packages/sci/src/gfx/pic.d.ts +27 -0
- package/packages/sci/src/gfx/pic.js +34 -0
- package/packages/sci/src/gfx/pic.ts +53 -0
- package/packages/sci/src/gfx/quantize.d.ts +42 -0
- package/packages/sci/src/gfx/quantize.js +199 -0
- package/packages/sci/src/gfx/quantize.ts +218 -0
- package/packages/sci/src/gfx/view-file.d.ts +62 -0
- package/packages/sci/src/gfx/view-file.js +182 -0
- package/packages/sci/src/gfx/view-file.ts +233 -0
- package/packages/sci/src/gfx/view.d.ts +28 -0
- package/packages/sci/src/gfx/view.js +45 -0
- package/packages/sci/src/gfx/view.ts +62 -0
- package/packages/sci/src/index.d.ts +38 -0
- package/packages/sci/src/index.js +38 -0
- package/packages/sci/src/index.ts +38 -0
- package/packages/sci/src/motion/pathfinding.d.ts +40 -0
- package/packages/sci/src/motion/pathfinding.js +140 -0
- package/packages/sci/src/motion/pathfinding.ts +147 -0
- package/packages/sci/src/resource/archive.d.ts +16 -0
- package/packages/sci/src/resource/archive.js +51 -0
- package/packages/sci/src/resource/archive.ts +56 -0
- package/packages/sci/src/resource/lzs.d.ts +12 -0
- package/packages/sci/src/resource/lzs.js +64 -0
- package/packages/sci/src/resource/lzs.ts +66 -0
- package/packages/sci/src/resource/manager.d.ts +45 -0
- package/packages/sci/src/resource/manager.js +158 -0
- package/packages/sci/src/resource/manager.ts +184 -0
- package/packages/sci/src/resource/map.d.ts +18 -0
- package/packages/sci/src/resource/map.js +34 -0
- package/packages/sci/src/resource/map.ts +44 -0
- package/packages/sci/src/resource/types.d.ts +37 -0
- package/packages/sci/src/resource/types.js +45 -0
- package/packages/sci/src/resource/types.ts +57 -0
- package/packages/sci/src/script/assembly.d.ts +67 -0
- package/packages/sci/src/script/assembly.js +748 -0
- package/packages/sci/src/script/assembly.ts +714 -0
- package/packages/sci/src/script/compiler.d.ts +75 -0
- package/packages/sci/src/script/compiler.js +993 -0
- package/packages/sci/src/script/compiler.ts +1040 -0
- package/packages/sci/src/script/disasm.d.ts +81 -0
- package/packages/sci/src/script/disasm.js +318 -0
- package/packages/sci/src/script/disasm.ts +323 -0
- package/packages/sci/src/script/kernel-names.d.ts +6 -0
- package/packages/sci/src/script/kernel-names.js +165 -0
- package/packages/sci/src/script/kernel-names.ts +165 -0
- package/packages/sci/src/script/opcodes.d.ts +22 -0
- package/packages/sci/src/script/opcodes.js +45 -0
- package/packages/sci/src/script/opcodes.ts +72 -0
- package/packages/sci/src/script/script.d.ts +56 -0
- package/packages/sci/src/script/script.js +63 -0
- package/packages/sci/src/script/script.ts +111 -0
- package/packages/sci/src/script/sexpr.d.ts +45 -0
- package/packages/sci/src/script/sexpr.js +151 -0
- package/packages/sci/src/script/sexpr.ts +139 -0
- package/packages/sci/src/script/vocab.d.ts +15 -0
- package/packages/sci/src/script/vocab.js +44 -0
- package/packages/sci/src/script/vocab.ts +48 -0
- package/packages/sci/src/script/world.d.ts +25 -0
- package/packages/sci/src/script/world.js +65 -0
- package/packages/sci/src/script/world.ts +70 -0
- package/packages/sci/src/text/font.d.ts +37 -0
- package/packages/sci/src/text/font.js +103 -0
- package/packages/sci/src/text/font.ts +125 -0
- package/packages/sci/src/text/message.d.ts +69 -0
- package/packages/sci/src/text/message.js +171 -0
- package/packages/sci/src/text/message.ts +205 -0
- package/packages/sci/src/vm/kernels/arrays.d.ts +19 -0
- package/packages/sci/src/vm/kernels/arrays.js +182 -0
- package/packages/sci/src/vm/kernels/arrays.ts +164 -0
- package/packages/sci/src/vm/kernels/audio.d.ts +70 -0
- package/packages/sci/src/vm/kernels/audio.js +347 -0
- package/packages/sci/src/vm/kernels/audio.ts +349 -0
- package/packages/sci/src/vm/kernels/core.d.ts +5 -0
- package/packages/sci/src/vm/kernels/core.js +87 -0
- package/packages/sci/src/vm/kernels/core.ts +91 -0
- package/packages/sci/src/vm/kernels/graphics.d.ts +141 -0
- package/packages/sci/src/vm/kernels/graphics.js +467 -0
- package/packages/sci/src/vm/kernels/graphics.ts +502 -0
- package/packages/sci/src/vm/kernels/index.d.ts +8 -0
- package/packages/sci/src/vm/kernels/index.js +29 -0
- package/packages/sci/src/vm/kernels/index.ts +32 -0
- package/packages/sci/src/vm/kernels/lists.d.ts +6 -0
- package/packages/sci/src/vm/kernels/lists.js +127 -0
- package/packages/sci/src/vm/kernels/lists.ts +111 -0
- package/packages/sci/src/vm/kernels/motion.d.ts +7 -0
- package/packages/sci/src/vm/kernels/motion.js +204 -0
- package/packages/sci/src/vm/kernels/motion.ts +194 -0
- package/packages/sci/src/vm/kernels/palette.d.ts +82 -0
- package/packages/sci/src/vm/kernels/palette.js +292 -0
- package/packages/sci/src/vm/kernels/palette.ts +303 -0
- package/packages/sci/src/vm/kernels/saves.d.ts +45 -0
- package/packages/sci/src/vm/kernels/saves.js +123 -0
- package/packages/sci/src/vm/kernels/saves.ts +149 -0
- package/packages/sci/src/vm/kernels/system.d.ts +62 -0
- package/packages/sci/src/vm/kernels/system.js +110 -0
- package/packages/sci/src/vm/kernels/system.ts +128 -0
- package/packages/sci/src/vm/kernels/text.d.ts +13 -0
- package/packages/sci/src/vm/kernels/text.js +338 -0
- package/packages/sci/src/vm/kernels/text.ts +328 -0
- package/packages/sci/src/vm/memory.d.ts +138 -0
- package/packages/sci/src/vm/memory.js +168 -0
- package/packages/sci/src/vm/memory.ts +266 -0
- package/packages/sci/src/vm/replace.d.ts +11 -0
- package/packages/sci/src/vm/replace.js +14 -0
- package/packages/sci/src/vm/replace.ts +16 -0
- package/packages/sci/src/vm/savegame.d.ts +89 -0
- package/packages/sci/src/vm/savegame.js +161 -0
- package/packages/sci/src/vm/savegame.ts +188 -0
- package/packages/sci/src/vm/value.d.ts +17 -0
- package/packages/sci/src/vm/value.js +12 -0
- package/packages/sci/src/vm/value.ts +25 -0
- package/packages/sci/src/vm/vm.d.ts +155 -0
- package/packages/sci/src/vm/vm.js +1053 -0
- package/packages/sci/src/vm/vm.ts +914 -0
- package/tools/adlib-render.d.ts +1 -0
- package/tools/adlib-render.js +49 -0
- package/tools/adlib-render.ts +42 -0
- package/tools/analyze.d.ts +1 -0
- package/tools/analyze.js +25 -0
- package/tools/analyze.ts +25 -0
- package/tools/art/build.d.ts +34 -0
- package/tools/art/build.js +175 -0
- package/tools/art/build.ts +153 -0
- package/tools/art.d.ts +1 -0
- package/tools/art.js +31 -0
- package/tools/art.ts +29 -0
- package/tools/asm.d.ts +1 -0
- package/tools/asm.js +61 -0
- package/tools/asm.ts +55 -0
- package/tools/cli.d.ts +1 -0
- package/tools/cli.js +99 -0
- package/tools/cli.ts +100 -0
- package/tools/editor-server.d.ts +29 -0
- package/tools/editor-server.js +162 -0
- package/tools/editor-server.ts +176 -0
- package/tools/explore.d.ts +1 -0
- package/tools/explore.js +305 -0
- package/tools/explore.ts +250 -0
- package/tools/game/audio.d.ts +19 -0
- package/tools/game/audio.js +143 -0
- package/tools/game/audio.ts +130 -0
- package/tools/game/build.d.ts +46 -0
- package/tools/game/build.js +259 -0
- package/tools/game/build.ts +283 -0
- package/tools/game/defaults.d.ts +26 -0
- package/tools/game/defaults.js +82 -0
- package/tools/game/defaults.ts +83 -0
- package/tools/game/font.d.ts +3 -0
- package/tools/game/font.js +124 -0
- package/tools/game/font.ts +126 -0
- package/tools/game/kit.d.ts +7 -0
- package/tools/game/kit.js +7 -0
- package/tools/game/kit.ts +10 -0
- package/tools/game/new.d.ts +8 -0
- package/tools/game/new.js +39 -0
- package/tools/game/new.ts +38 -0
- package/tools/game/target.d.ts +7 -0
- package/tools/game/target.js +41 -0
- package/tools/game/target.ts +42 -0
- package/tools/game/template/resources.ts.tmpl +43 -0
- package/tools/game/template/rooms/1.room.yaml +9 -0
- package/tools/game/template/rooms/1.yarn +17 -0
- package/tools/game/template/scripts/0.sc +24 -0
- package/tools/game.d.ts +1 -0
- package/tools/game.js +77 -0
- package/tools/game.ts +70 -0
- package/tools/mod-build.d.ts +28 -0
- package/tools/mod-build.js +173 -0
- package/tools/mod-build.ts +179 -0
- package/tools/mod-target.d.ts +16 -0
- package/tools/mod-target.js +26 -0
- package/tools/mod-target.ts +32 -0
- package/tools/mod.d.ts +1 -0
- package/tools/mod.js +47 -0
- package/tools/mod.ts +45 -0
- package/tools/msg.d.ts +1 -0
- package/tools/msg.js +40 -0
- package/tools/msg.ts +36 -0
- package/tools/node-files.d.ts +3 -0
- package/tools/node-files.js +34 -0
- package/tools/node-files.ts +33 -0
- package/tools/patches.d.ts +3 -0
- package/tools/patches.js +13 -0
- package/tools/patches.ts +13 -0
- package/tools/pic-build.d.ts +9 -0
- package/tools/pic-build.js +26 -0
- package/tools/pic-build.ts +27 -0
- package/tools/pic.d.ts +1 -0
- package/tools/pic.js +64 -0
- package/tools/pic.ts +61 -0
- package/tools/play.d.ts +1 -0
- package/tools/play.js +54 -0
- package/tools/play.ts +54 -0
- package/tools/png.d.ts +16 -0
- package/tools/png.js +124 -0
- package/tools/png.ts +123 -0
- package/tools/res.d.ts +1 -0
- package/tools/res.js +48 -0
- package/tools/res.ts +50 -0
- package/tools/scenario.d.ts +1 -0
- package/tools/scenario.js +74 -0
- package/tools/scenario.ts +80 -0
- package/tools/site/build.d.ts +8 -0
- package/tools/site/build.js +110 -0
- package/tools/site/build.ts +116 -0
- package/tools/site/markdown.d.ts +11 -0
- package/tools/site/markdown.js +88 -0
- package/tools/site/markdown.ts +84 -0
- package/tools/view-build.d.ts +34 -0
- package/tools/view-build.js +47 -0
- package/tools/view-build.ts +71 -0
- package/tools/view.d.ts +1 -0
- package/tools/view.js +54 -0
- package/tools/view.ts +52 -0
|
@@ -0,0 +1,993 @@
|
|
|
1
|
+
import { read, show, ReadError } from "./sexpr.js";
|
|
2
|
+
export class CompileError extends Error {
|
|
3
|
+
file;
|
|
4
|
+
line;
|
|
5
|
+
constructor(message, file, line) {
|
|
6
|
+
super(`${file}:${line}: ${message}`);
|
|
7
|
+
this.file = file;
|
|
8
|
+
this.line = line;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** The object header slots, in order; every class and instance begins with them. */
|
|
12
|
+
export const OBJECT_HEADER = ["-objID-", "-size-", "-propDict-", "-methDict-", "-classScript-", "-species-", "-super-", "-info-", "name"];
|
|
13
|
+
const BUILTIN_DEFINES = { TRUE: 1, FALSE: 0, NULL: 0 };
|
|
14
|
+
export function compileScripts(sources, ctx) {
|
|
15
|
+
const units = sources.map((s) => declare(s, ctx));
|
|
16
|
+
const byNumber = new Map();
|
|
17
|
+
for (const u of units) {
|
|
18
|
+
const other = byNumber.get(u.number);
|
|
19
|
+
if (other)
|
|
20
|
+
throw new CompileError(`script ${u.number} is also ${other.file}`, u.file, 1);
|
|
21
|
+
byNumber.set(u.number, u);
|
|
22
|
+
}
|
|
23
|
+
// Classes: numbered superclasses first, then in script order, skipping numbers used
|
|
24
|
+
// elsewhere (so a root class like Obj gets 0).
|
|
25
|
+
const classes = new Map();
|
|
26
|
+
for (const u of [...units].sort((a, b) => a.number - b.number)) {
|
|
27
|
+
for (const o of u.objects) {
|
|
28
|
+
if (o.kind !== "class")
|
|
29
|
+
continue;
|
|
30
|
+
if (classes.has(o.name) || ctx.externalClass?.(o.name))
|
|
31
|
+
throw new CompileError(`class ${o.name} is defined twice`, u.file, o.line);
|
|
32
|
+
classes.set(o.name, { obj: o, unit: u });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const taken = new Set(ctx.takenSpecies ?? []);
|
|
36
|
+
let nextSpecies = 0;
|
|
37
|
+
const number = (c, seen) => {
|
|
38
|
+
if (c.obj.species !== 0xffff)
|
|
39
|
+
return;
|
|
40
|
+
if (seen.has(c.obj.name))
|
|
41
|
+
throw new CompileError(`class ${c.obj.name} is its own superclass`, c.unit.file, c.obj.line);
|
|
42
|
+
seen.add(c.obj.name);
|
|
43
|
+
const sup = c.obj.superName ? classes.get(c.obj.superName) : undefined;
|
|
44
|
+
if (sup)
|
|
45
|
+
number(sup, seen);
|
|
46
|
+
while (taken.has(nextSpecies))
|
|
47
|
+
nextSpecies++;
|
|
48
|
+
c.obj.species = nextSpecies;
|
|
49
|
+
taken.add(nextSpecies);
|
|
50
|
+
};
|
|
51
|
+
for (const c of classes.values())
|
|
52
|
+
number(c, new Set());
|
|
53
|
+
// Globals: script 0's variables, then those other scripts declare with (global ...), in
|
|
54
|
+
// script order; they all live in script 0.
|
|
55
|
+
const globals = new Map();
|
|
56
|
+
const zero = byNumber.get(0);
|
|
57
|
+
const declared = [...units].sort((a, b) => a.number - b.number).flatMap((u) => u.globals.map((v) => ({ v, u })));
|
|
58
|
+
if (declared.length && !zero) {
|
|
59
|
+
throw new CompileError("(global ...) needs script 0 to be compiled with it (globals live there)", declared[0].u.file, declared[0].v.line);
|
|
60
|
+
}
|
|
61
|
+
for (const { v, u } of declared) {
|
|
62
|
+
const other = zero.locals.find((x) => x.name === v.name);
|
|
63
|
+
if (other)
|
|
64
|
+
throw new CompileError(`global ${v.name} is declared twice`, u.file, v.line);
|
|
65
|
+
zero.locals.push(v);
|
|
66
|
+
}
|
|
67
|
+
if (zero) {
|
|
68
|
+
let i = 0;
|
|
69
|
+
for (const v of zero.locals)
|
|
70
|
+
(globals.set(v.name, { index: i, size: v.size }), (i += v.size));
|
|
71
|
+
}
|
|
72
|
+
// Procedures other scripts can call.
|
|
73
|
+
const publicProcs = new Map();
|
|
74
|
+
for (const u of units) {
|
|
75
|
+
for (const p of u.publics)
|
|
76
|
+
if (u.procedures.some((f) => f.name === p.name))
|
|
77
|
+
publicProcs.set(p.name, { script: u.number, index: p.index });
|
|
78
|
+
}
|
|
79
|
+
const project = { ctx, classes, globals, publicProcs, layouts: new Map(), sent: [] };
|
|
80
|
+
const compiled = units.map((u) => ({
|
|
81
|
+
file: u.file,
|
|
82
|
+
number: u.number,
|
|
83
|
+
assembly: new ScriptCompiler(u, project).compile(),
|
|
84
|
+
classes: new Map(u.objects.filter((o) => o.kind === "class").map((o) => [o.name, o.species])),
|
|
85
|
+
variables: u.locals.flatMap((v) => (v.size === 1 ? [v.name] : Array.from({ length: v.size }, (_, i) => `${v.name}[${i}]`))),
|
|
86
|
+
}));
|
|
87
|
+
// Selectors sent that nothing here defines: probably misspelt.
|
|
88
|
+
if (ctx.onWarning) {
|
|
89
|
+
const defined = new Set(OBJECT_HEADER);
|
|
90
|
+
for (const u of units) {
|
|
91
|
+
for (const o of u.objects) {
|
|
92
|
+
for (const p of o.props)
|
|
93
|
+
defined.add(p.name);
|
|
94
|
+
for (const m of o.methods)
|
|
95
|
+
defined.add(m.name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const reported = new Set();
|
|
99
|
+
for (const s of project.sent) {
|
|
100
|
+
if (defined.has(s.name) || reported.has(s.name))
|
|
101
|
+
continue;
|
|
102
|
+
reported.add(s.name);
|
|
103
|
+
ctx.onWarning({ message: `nothing defines ${s.name} (no class or object has a property or method by that name)`, file: s.file, line: s.line });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return compiled;
|
|
107
|
+
}
|
|
108
|
+
/** Reads a source file's top-level forms into declarations. */
|
|
109
|
+
function declare(src, ctx) {
|
|
110
|
+
const unit = { file: src.file, number: -1, defines: new Map(), locals: [], globals: [], publics: [], externs: new Map(), objects: [], procedures: [] };
|
|
111
|
+
const fail = (msg, line, file = src.file) => {
|
|
112
|
+
throw new CompileError(msg, file, line);
|
|
113
|
+
};
|
|
114
|
+
const parse = (text, file) => {
|
|
115
|
+
try {
|
|
116
|
+
return read(text);
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
if (e instanceof ReadError)
|
|
120
|
+
fail(e.message.replace(/^line \d+: /, ""), e.line, file);
|
|
121
|
+
throw e;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const topLevel = (forms, file, included) => {
|
|
125
|
+
for (const form of forms) {
|
|
126
|
+
if (form.kind !== "list" || form.items[0]?.kind !== "sym")
|
|
127
|
+
fail(`expected a declaration like (script ...), got ${show(form)}`, form.line, file);
|
|
128
|
+
const [head, ...rest] = form.items;
|
|
129
|
+
const line = form.line;
|
|
130
|
+
const sym = (n, what) => (n?.kind === "sym" ? n.name : fail(`expected ${what}`, n?.line ?? line, file));
|
|
131
|
+
const num = (n, what) => (n?.kind === "num" ? n.value : fail(`expected ${what}`, n?.line ?? line, file));
|
|
132
|
+
const onlyInScript = () => included && fail(`(${head.name}) can't be in an included file`, line, file);
|
|
133
|
+
switch (head.name) {
|
|
134
|
+
case "script":
|
|
135
|
+
onlyInScript();
|
|
136
|
+
if (unit.number >= 0)
|
|
137
|
+
fail("a second (script N)", line);
|
|
138
|
+
unit.number = num(rest[0], "the script's number");
|
|
139
|
+
break;
|
|
140
|
+
case "include": {
|
|
141
|
+
const name = rest[0]?.kind === "str" ? rest[0].value : sym(rest[0], "a file name");
|
|
142
|
+
const text = ctx.include?.(name, file);
|
|
143
|
+
if (text === undefined)
|
|
144
|
+
fail(`can't find ${name} to include`, line, file);
|
|
145
|
+
topLevel(parse(text, name), name, true);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "define":
|
|
149
|
+
unit.defines.set(sym(rest[0], "a name"), rest[1] ?? fail("(define NAME value)", line, file));
|
|
150
|
+
break;
|
|
151
|
+
case "enum": {
|
|
152
|
+
// (enum [start] A B (= C 10) D): A = start (0 by default), B = A + 1, ...
|
|
153
|
+
let i = 0;
|
|
154
|
+
let names = rest;
|
|
155
|
+
if (names[0]?.kind === "num")
|
|
156
|
+
(i = names[0].value), (names = names.slice(1));
|
|
157
|
+
for (const n of names) {
|
|
158
|
+
if (n.kind === "list" && n.items[0]?.kind === "sym" && n.items[0].name === "=") {
|
|
159
|
+
i = constValue(n.items[2], unit.defines, (m) => fail(m, n.line, file));
|
|
160
|
+
unit.defines.set(sym(n.items[1], "a name"), { kind: "num", value: i++, line: n.line });
|
|
161
|
+
}
|
|
162
|
+
else
|
|
163
|
+
unit.defines.set(sym(n, "a name"), { kind: "num", value: i++, line: n.line });
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "extern":
|
|
168
|
+
for (let i = 0; i < rest.length; i += 3) {
|
|
169
|
+
unit.externs.set(sym(rest[i], "a procedure name"), { script: num(rest[i + 1], "a script number"), index: num(rest[i + 2], "an export index") });
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
case "public":
|
|
173
|
+
onlyInScript();
|
|
174
|
+
for (let i = 0; i < rest.length; i += 2) {
|
|
175
|
+
unit.publics.push({ name: sym(rest[i], "a name"), index: num(rest[i + 1], "an export index"), line: rest[i].line });
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
case "local":
|
|
179
|
+
onlyInScript();
|
|
180
|
+
unit.locals.push(...variables(rest, (m, l) => fail(m, l)));
|
|
181
|
+
break;
|
|
182
|
+
case "global":
|
|
183
|
+
onlyInScript();
|
|
184
|
+
unit.globals.push(...variables(rest, (m, l) => fail(m, l)));
|
|
185
|
+
break;
|
|
186
|
+
case "class":
|
|
187
|
+
case "instance":
|
|
188
|
+
onlyInScript();
|
|
189
|
+
unit.objects.push(object(head.name, rest, line, (m, l) => fail(m, l)));
|
|
190
|
+
break;
|
|
191
|
+
case "procedure": {
|
|
192
|
+
onlyInScript();
|
|
193
|
+
const sig = rest[0];
|
|
194
|
+
if (sig?.kind === "sym")
|
|
195
|
+
break; // a forward declaration
|
|
196
|
+
unit.procedures.push(func(sig, rest.slice(1), line, (m, l) => fail(m, l)));
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
fail(`unknown declaration (${head.name} ...)`, line, file);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
topLevel(parse(src.text, src.file), src.file, false);
|
|
205
|
+
if (unit.number < 0)
|
|
206
|
+
fail("missing (script N)", 1);
|
|
207
|
+
return unit;
|
|
208
|
+
}
|
|
209
|
+
/** `a [b 4] (= c 5) [d 2] = (1 2)`: names, array sizes, initial values. */
|
|
210
|
+
function variables(nodes, fail) {
|
|
211
|
+
const out = [];
|
|
212
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
213
|
+
const n = nodes[i];
|
|
214
|
+
if (n.kind === "sym" && n.name !== "=") {
|
|
215
|
+
const v = { name: n.name, size: 1, init: [], line: n.line };
|
|
216
|
+
if (nodes[i + 1]?.kind === "sym" && nodes[i + 1].name === "=") {
|
|
217
|
+
v.init = [nodes[i + 2] ?? fail("= needs a value", n.line)];
|
|
218
|
+
i += 2;
|
|
219
|
+
}
|
|
220
|
+
out.push(v);
|
|
221
|
+
}
|
|
222
|
+
else if (n.kind === "index") {
|
|
223
|
+
const [name, size] = n.items;
|
|
224
|
+
if (name?.kind !== "sym" || size?.kind !== "num")
|
|
225
|
+
fail("an array is [name size]", n.line);
|
|
226
|
+
const v = { name: name.name, size: size.value, init: [], line: n.line };
|
|
227
|
+
if (nodes[i + 1]?.kind === "sym" && nodes[i + 1].name === "=") {
|
|
228
|
+
const values = nodes[i + 2];
|
|
229
|
+
v.init = values?.kind === "list" || values?.kind === "index" ? values.items : [values ?? fail("= needs values", n.line)];
|
|
230
|
+
if (v.init.length > v.size)
|
|
231
|
+
fail(`${v.name} has ${v.size} elements, but ${v.init.length} values`, n.line);
|
|
232
|
+
i += 2;
|
|
233
|
+
}
|
|
234
|
+
out.push(v);
|
|
235
|
+
}
|
|
236
|
+
else if (n.kind === "list" && n.items[0]?.kind === "sym" && n.items[0].name === "=" && n.items[1]?.kind === "sym") {
|
|
237
|
+
out.push({ name: n.items[1].name, size: 1, init: [n.items[2] ?? fail("(= name value)", n.line)], line: n.line });
|
|
238
|
+
}
|
|
239
|
+
else
|
|
240
|
+
fail(`expected a variable name, got ${show(n)}`, n.line);
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
/** `(name a b &tmp t [buf 4])` and a body. */
|
|
245
|
+
function func(sig, body, line, fail) {
|
|
246
|
+
if (sig?.kind !== "list" || sig.items[0]?.kind !== "sym")
|
|
247
|
+
return fail("expected (name params...)", sig?.line ?? line);
|
|
248
|
+
const [name, ...rest] = sig.items;
|
|
249
|
+
const tmp = rest.findIndex((n) => n.kind === "sym" && n.name === "&tmp");
|
|
250
|
+
const params = (tmp < 0 ? rest : rest.slice(0, tmp)).map((n) => (n.kind === "sym" ? n.name : fail(`expected a parameter name, got ${show(n)}`, n.line)));
|
|
251
|
+
const temps = tmp < 0 ? [] : variables(rest.slice(tmp + 1), fail);
|
|
252
|
+
for (const t of temps)
|
|
253
|
+
if (t.init.length)
|
|
254
|
+
fail("temporaries can't have initial values", t.line);
|
|
255
|
+
return { name: name.name, params, temps, body, line: sig.line };
|
|
256
|
+
}
|
|
257
|
+
function object(kind, rest, line, fail) {
|
|
258
|
+
const [name, ...more] = rest;
|
|
259
|
+
if (name?.kind !== "sym")
|
|
260
|
+
return fail(`expected (${kind} Name of Class ...)`, line);
|
|
261
|
+
const o = { kind: kind, name: name.name, props: [], methods: [], line, species: 0xffff };
|
|
262
|
+
let i = 0;
|
|
263
|
+
if (more[0]?.kind === "sym" && (more[0].name === "of" || more[0].name === "kindof")) {
|
|
264
|
+
if (more[1]?.kind !== "sym")
|
|
265
|
+
fail(`${o.name}: expected a class name after ${more[0].name}`, line);
|
|
266
|
+
o.superName = more[1].name;
|
|
267
|
+
i = 2;
|
|
268
|
+
}
|
|
269
|
+
else if (kind === "instance")
|
|
270
|
+
fail(`instance ${o.name} needs "of Class"`, line);
|
|
271
|
+
for (const n of more.slice(i)) {
|
|
272
|
+
if (n.kind !== "list" || n.items[0]?.kind !== "sym")
|
|
273
|
+
fail(`${o.name}: expected (properties ...) or (method ...), got ${show(n)}`, n.line);
|
|
274
|
+
const [head, ...body] = n.items;
|
|
275
|
+
if (head.name === "properties") {
|
|
276
|
+
for (let k = 0; k < body.length; k += 2) {
|
|
277
|
+
const p = body[k];
|
|
278
|
+
if (p.kind !== "sym")
|
|
279
|
+
fail(`expected a property name, got ${show(p)}`, p.line);
|
|
280
|
+
o.props.push({ name: p.name, value: body[k + 1] ?? fail(`property ${p.name} needs a value`, p.line), line: p.line });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else if (head.name === "method") {
|
|
284
|
+
const m = func(body[0], body.slice(1), n.line, fail);
|
|
285
|
+
if (o.methods.some((x) => x.name === m.name))
|
|
286
|
+
fail(`${o.name} has two ${m.name} methods`, n.line);
|
|
287
|
+
o.methods.push(m);
|
|
288
|
+
}
|
|
289
|
+
else
|
|
290
|
+
fail(`${o.name}: expected (properties ...) or (method ...), got (${head.name} ...)`, n.line);
|
|
291
|
+
}
|
|
292
|
+
return o;
|
|
293
|
+
}
|
|
294
|
+
/** A constant expression: numbers, defines and arithmetic on them. */
|
|
295
|
+
function constValue(n, defines, fail, depth = 0) {
|
|
296
|
+
if (depth > 50)
|
|
297
|
+
fail("a define refers to itself");
|
|
298
|
+
if (n.kind === "num")
|
|
299
|
+
return n.value;
|
|
300
|
+
if (n.kind === "sym") {
|
|
301
|
+
if (n.name in BUILTIN_DEFINES)
|
|
302
|
+
return BUILTIN_DEFINES[n.name];
|
|
303
|
+
const d = defines.get(n.name);
|
|
304
|
+
if (!d)
|
|
305
|
+
return fail(`${n.name} isn't a constant`);
|
|
306
|
+
return constValue(d, defines, fail, depth + 1);
|
|
307
|
+
}
|
|
308
|
+
if (n.kind === "list" && n.items[0]?.kind === "sym") {
|
|
309
|
+
const op = n.items[0].name;
|
|
310
|
+
const args = n.items.slice(1).map((a) => constValue(a, defines, fail, depth + 1));
|
|
311
|
+
const fold = (f) => args.reduce(f);
|
|
312
|
+
switch (op) {
|
|
313
|
+
case "+": return fold((a, b) => a + b);
|
|
314
|
+
case "-": return args.length === 1 ? -args[0] : fold((a, b) => a - b);
|
|
315
|
+
case "*": return fold((a, b) => a * b);
|
|
316
|
+
case "/": return fold((a, b) => Math.trunc(a / b));
|
|
317
|
+
case "|": return fold((a, b) => a | b);
|
|
318
|
+
case "&": return fold((a, b) => a & b);
|
|
319
|
+
case "^": return fold((a, b) => a ^ b);
|
|
320
|
+
case "<<": return fold((a, b) => a << b);
|
|
321
|
+
case ">>": return fold((a, b) => a >> b);
|
|
322
|
+
case "~": return ~args[0] & 0xffff;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return fail(`${show(n)} isn't a constant`);
|
|
326
|
+
}
|
|
327
|
+
const SEND_SELECTOR = /^([A-Za-z_-][\w-]*)[:?]$/;
|
|
328
|
+
/** A message in a send starts with a selector (`x:`, `x?`) or `[expr]`, a selector computed at run time. */
|
|
329
|
+
const isMessageHead = (n) => (n?.kind === "sym" && SEND_SELECTOR.test(n.name)) || (n?.kind === "index" && n.items.length === 1);
|
|
330
|
+
const BINARY = {
|
|
331
|
+
"+": "add", "-": "sub", "*": "mul", "/": "div", mod: "mod", "<<": "shl", ">>": "shr", "&": "and", "|": "or", "^": "xor",
|
|
332
|
+
};
|
|
333
|
+
const COMPARE = {
|
|
334
|
+
"==": "eq?", "!=": "ne?", "<": "lt?", ">": "gt?", "<=": "le?", ">=": "ge?", "u<": "ult?", "u>": "ugt?", "u<=": "ule?", "u>=": "uge?",
|
|
335
|
+
};
|
|
336
|
+
const COMPOUND = { "+=": "+", "-=": "-", "*=": "*", "/=": "/", "|=": "|", "&=": "&", "^=": "^", "<<=": "<<", ">>=": ">>" };
|
|
337
|
+
class ScriptCompiler {
|
|
338
|
+
unit;
|
|
339
|
+
project;
|
|
340
|
+
out = [];
|
|
341
|
+
strings = new Map();
|
|
342
|
+
labels = 0;
|
|
343
|
+
fn;
|
|
344
|
+
locals = new Map();
|
|
345
|
+
procedures;
|
|
346
|
+
objects;
|
|
347
|
+
constructor(unit, project) {
|
|
348
|
+
this.unit = unit;
|
|
349
|
+
this.project = project;
|
|
350
|
+
let i = 0;
|
|
351
|
+
for (const v of unit.locals) {
|
|
352
|
+
if (this.locals.has(v.name))
|
|
353
|
+
this.fail(`variable ${v.name} is declared twice`, v.line);
|
|
354
|
+
this.locals.set(v.name, { index: i, size: v.size });
|
|
355
|
+
i += v.size;
|
|
356
|
+
}
|
|
357
|
+
this.procedures = new Set(unit.procedures.map((p) => p.name));
|
|
358
|
+
this.objects = new Map(unit.objects.map((o) => [o.name, o]));
|
|
359
|
+
}
|
|
360
|
+
fail(message, line) {
|
|
361
|
+
throw new CompileError(message, this.unit.file, line);
|
|
362
|
+
}
|
|
363
|
+
compile() {
|
|
364
|
+
const u = this.unit;
|
|
365
|
+
const head = [`; compiled from ${u.file}`, `script ${u.number}`];
|
|
366
|
+
// Locals: initial values (numbers, strings, objects in this script).
|
|
367
|
+
const localValues = [];
|
|
368
|
+
for (const v of u.locals) {
|
|
369
|
+
for (let k = 0; k < v.size; k++)
|
|
370
|
+
localValues.push(v.init[k] ? this.initValue(v.init[k]) : "0");
|
|
371
|
+
}
|
|
372
|
+
if (localValues.length)
|
|
373
|
+
head.push(`locals ${localValues.join(" ")}`);
|
|
374
|
+
// Exports.
|
|
375
|
+
const exports = [];
|
|
376
|
+
for (const p of u.publics) {
|
|
377
|
+
if (!this.procedures.has(p.name) && !this.objects.has(p.name))
|
|
378
|
+
this.fail(`public ${p.name} is neither a procedure nor an object here`, p.line);
|
|
379
|
+
if (exports[p.index] !== undefined)
|
|
380
|
+
this.fail(`export ${p.index} is used twice`, p.line);
|
|
381
|
+
exports[p.index] = p.name;
|
|
382
|
+
}
|
|
383
|
+
if (exports.length)
|
|
384
|
+
head.push("exports", ...Array.from(exports, (e) => ` ${e ?? 0}`));
|
|
385
|
+
// Objects.
|
|
386
|
+
const body = [];
|
|
387
|
+
for (const o of u.objects)
|
|
388
|
+
body.push(...this.objectLines(o));
|
|
389
|
+
// Code.
|
|
390
|
+
const code = [];
|
|
391
|
+
for (const o of u.objects) {
|
|
392
|
+
const owner = this.ownerOf(o);
|
|
393
|
+
for (const m of o.methods)
|
|
394
|
+
code.push(...this.function(m, `${o.name}::${m.name}`, owner));
|
|
395
|
+
}
|
|
396
|
+
for (const p of u.procedures)
|
|
397
|
+
code.push(...this.function(p, p.name, undefined));
|
|
398
|
+
const strings = [...this.strings].map(([text, label]) => ` ${label} ${quote(text)}`);
|
|
399
|
+
return [...head, ...body, ...(strings.length ? ["strings", ...strings] : []), "code", ...code, ""].join("\n");
|
|
400
|
+
}
|
|
401
|
+
/** A class's layout: inherited properties first, then its own. */
|
|
402
|
+
layout(name, line) {
|
|
403
|
+
const cached = this.project.layouts.get(name);
|
|
404
|
+
if (cached)
|
|
405
|
+
return cached;
|
|
406
|
+
const local = this.project.classes.get(name);
|
|
407
|
+
let layout;
|
|
408
|
+
if (local) {
|
|
409
|
+
const { obj } = local;
|
|
410
|
+
const props = obj.superName ? this.layout(obj.superName, obj.line).props.map((p) => ({ ...p })) : [];
|
|
411
|
+
for (const p of obj.props) {
|
|
412
|
+
if (OBJECT_HEADER.includes(p.name))
|
|
413
|
+
continue;
|
|
414
|
+
const inherited = props.find((x) => x.name === p.name);
|
|
415
|
+
if (inherited)
|
|
416
|
+
inherited.value = p.value;
|
|
417
|
+
else
|
|
418
|
+
props.push({ name: p.name, value: p.value });
|
|
419
|
+
}
|
|
420
|
+
layout = { species: obj.species, props };
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
const ext = this.project.ctx.externalClass?.(name);
|
|
424
|
+
if (!ext)
|
|
425
|
+
this.fail(`unknown class ${name}`, line);
|
|
426
|
+
layout = { species: ext.species, props: ext.properties.map((p) => ({ ...p })) };
|
|
427
|
+
}
|
|
428
|
+
this.project.layouts.set(name, layout);
|
|
429
|
+
return layout;
|
|
430
|
+
}
|
|
431
|
+
ownerOf(o) {
|
|
432
|
+
const layout = this.objectLayout(o);
|
|
433
|
+
const superSpecies = o.kind === "instance" ? this.layout(o.superName, o.line).species : o.superName ? this.layout(o.superName, o.line).species : undefined;
|
|
434
|
+
return { obj: o, layout: [...OBJECT_HEADER, ...layout.map((p) => p.name)], superSpecies };
|
|
435
|
+
}
|
|
436
|
+
/** An object's properties after the header, with the values it gives them. */
|
|
437
|
+
objectLayout(o) {
|
|
438
|
+
if (o.kind === "class")
|
|
439
|
+
return this.layout(o.name, o.line).props;
|
|
440
|
+
const props = this.layout(o.superName, o.line).props.map((p) => ({ ...p }));
|
|
441
|
+
for (const p of o.props) {
|
|
442
|
+
if (p.name === "name")
|
|
443
|
+
continue;
|
|
444
|
+
const slot = props.find((x) => x.name === p.name);
|
|
445
|
+
if (!slot)
|
|
446
|
+
this.fail(`${o.name}: ${o.superName} has no property ${p.name}`, p.line);
|
|
447
|
+
slot.value = p.value;
|
|
448
|
+
}
|
|
449
|
+
return props;
|
|
450
|
+
}
|
|
451
|
+
objectLines(o) {
|
|
452
|
+
const lines = [""];
|
|
453
|
+
const own = o.props.find((p) => p.name === "name");
|
|
454
|
+
const name = own ? this.initValue(own.value) : `@${this.string(o.name)}`;
|
|
455
|
+
if (o.kind === "class") {
|
|
456
|
+
const sup = o.superName ? `$${this.layout(o.superName, o.line).species.toString(16)}` : "-";
|
|
457
|
+
lines.push(`class ${o.name} of ${sup} species ${o.species}${o.superName ? ` ; ${o.superName}` : ""}`);
|
|
458
|
+
lines.push(` header ${OBJECT_HEADER.join(" ")}`, ` name ${name}`);
|
|
459
|
+
for (const p of this.objectLayout(o))
|
|
460
|
+
lines.push(` prop ${p.name} ${this.propValue(p.value)}`);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
const layout = this.objectLayout(o);
|
|
464
|
+
lines.push(`instance ${o.name} of $${this.layout(o.superName, o.line).species.toString(16)} ; ${o.superName}`);
|
|
465
|
+
lines.push(` layout ${[...OBJECT_HEADER, ...layout.map((p) => p.name)].join(" ")}`, ` name ${name}`);
|
|
466
|
+
for (const p of layout)
|
|
467
|
+
lines.push(` ${p.name} ${this.propValue(p.value)}`);
|
|
468
|
+
}
|
|
469
|
+
for (const m of o.methods)
|
|
470
|
+
lines.push(` method ${m.name} ${o.name}::${m.name}`);
|
|
471
|
+
return lines;
|
|
472
|
+
}
|
|
473
|
+
propValue(v) {
|
|
474
|
+
return typeof v === "number" ? String(toWord(v)) : this.initValue(v);
|
|
475
|
+
}
|
|
476
|
+
/** A value fixed when the script loads: a number, a string, a selector, or an object in this script. */
|
|
477
|
+
initValue(n) {
|
|
478
|
+
if (n.kind === "str")
|
|
479
|
+
return `@${this.string(n.value)}`;
|
|
480
|
+
if (n.kind === "sel") {
|
|
481
|
+
this.project.sent.push({ name: n.name, file: this.unit.file, line: n.line });
|
|
482
|
+
return `#${n.name}`;
|
|
483
|
+
}
|
|
484
|
+
if (n.kind === "sym" && this.objects.has(n.name) && this.objects.get(n.name).kind === "instance")
|
|
485
|
+
return `@${n.name}`;
|
|
486
|
+
return String(toWord(constValue(n, this.unit.defines, (m) => this.fail(m, n.line))));
|
|
487
|
+
}
|
|
488
|
+
string(text) {
|
|
489
|
+
let label = this.strings.get(text);
|
|
490
|
+
if (!label)
|
|
491
|
+
this.strings.set(text, (label = `s_${this.strings.size}`));
|
|
492
|
+
return label;
|
|
493
|
+
}
|
|
494
|
+
label() {
|
|
495
|
+
return `.L${++this.labels}`;
|
|
496
|
+
}
|
|
497
|
+
// --- Functions ---
|
|
498
|
+
function(f, label, owner) {
|
|
499
|
+
this.out.length = 0;
|
|
500
|
+
const params = new Map(f.params.map((p, i) => [p, i + 1]));
|
|
501
|
+
const temps = new Map();
|
|
502
|
+
let size = 0;
|
|
503
|
+
for (const t of f.temps)
|
|
504
|
+
(temps.set(t.name, { index: size, size: t.size }), (size += t.size));
|
|
505
|
+
this.fn = { func: f, owner, params, temps, loops: [], switches: 0 };
|
|
506
|
+
this.emit(`${label}:`);
|
|
507
|
+
if (size)
|
|
508
|
+
this.emit(` link ${size}`);
|
|
509
|
+
this.body(f.body);
|
|
510
|
+
this.emit(" ret");
|
|
511
|
+
this.fn = undefined;
|
|
512
|
+
return ["", ...this.out];
|
|
513
|
+
}
|
|
514
|
+
emit(line) {
|
|
515
|
+
this.out.push(line);
|
|
516
|
+
}
|
|
517
|
+
op(line) {
|
|
518
|
+
this.out.push(` ${line}`);
|
|
519
|
+
}
|
|
520
|
+
body(nodes) {
|
|
521
|
+
for (const n of nodes)
|
|
522
|
+
this.expr(n);
|
|
523
|
+
}
|
|
524
|
+
/** What a name means here: variables first, then properties, then everything else. */
|
|
525
|
+
resolve(name, line) {
|
|
526
|
+
const fn = this.fn;
|
|
527
|
+
const temp = fn.temps.get(name);
|
|
528
|
+
if (temp)
|
|
529
|
+
return { kind: "var", type: "t", index: temp.index, size: temp.size };
|
|
530
|
+
const param = fn.params.get(name);
|
|
531
|
+
if (param !== undefined)
|
|
532
|
+
return { kind: "var", type: "p", index: param, size: 1 };
|
|
533
|
+
if (fn.owner?.layout.includes(name))
|
|
534
|
+
return { kind: "prop", name };
|
|
535
|
+
const local = this.locals.get(name);
|
|
536
|
+
if (local)
|
|
537
|
+
return { kind: "var", type: this.unit.number === 0 ? "g" : "l", ...local };
|
|
538
|
+
const global = this.project.globals.get(name);
|
|
539
|
+
if (global)
|
|
540
|
+
return { kind: "var", type: "g", ...global };
|
|
541
|
+
if (name === "self")
|
|
542
|
+
return { kind: "self" };
|
|
543
|
+
if (name === "argc")
|
|
544
|
+
return { kind: "argc" };
|
|
545
|
+
const define = this.unit.defines.get(name);
|
|
546
|
+
if (define)
|
|
547
|
+
return { kind: "const", node: define };
|
|
548
|
+
if (name in BUILTIN_DEFINES)
|
|
549
|
+
return { kind: "const", node: { kind: "num", value: BUILTIN_DEFINES[name], line } };
|
|
550
|
+
const obj = this.objects.get(name);
|
|
551
|
+
if (obj?.kind === "instance")
|
|
552
|
+
return { kind: "object", label: name };
|
|
553
|
+
if (obj?.kind === "class" || this.project.classes.has(name) || this.project.ctx.externalClass?.(name)) {
|
|
554
|
+
return { kind: "class", species: this.layout(name, line).species, name };
|
|
555
|
+
}
|
|
556
|
+
return this.fail(`unknown name ${name}`, line);
|
|
557
|
+
}
|
|
558
|
+
varOp(action, stack, ref, indexed = false) {
|
|
559
|
+
// Script 0's own variables are the globals; elsewhere, its locals.
|
|
560
|
+
const type = ref.type === "g" && this.unit.number === 0 ? "l" : ref.type;
|
|
561
|
+
return `${action}${stack ? "s" : "a"}${type}${indexed ? "i" : ""} ${ref.index}`;
|
|
562
|
+
}
|
|
563
|
+
/** Evaluates into acc. */
|
|
564
|
+
expr(n) {
|
|
565
|
+
switch (n.kind) {
|
|
566
|
+
case "num": return this.op(`ldi ${toSigned(n.value)}`);
|
|
567
|
+
case "str": return this.op(`lofsa @${this.string(n.value)}`);
|
|
568
|
+
case "sel":
|
|
569
|
+
this.project.sent.push({ name: n.name, file: this.unit.file, line: n.line });
|
|
570
|
+
return this.op(`ldi #${n.name}`);
|
|
571
|
+
case "addr": return this.address(n.name, n.index, n.line);
|
|
572
|
+
case "index": {
|
|
573
|
+
const [arr, i] = n.items;
|
|
574
|
+
if (arr?.kind !== "sym" || !i || n.items.length !== 2)
|
|
575
|
+
this.fail("an array element is [name index]", n.line);
|
|
576
|
+
const ref = this.arrayRef(arr.name, n.line);
|
|
577
|
+
this.expr(i);
|
|
578
|
+
return this.op(this.varOp("l", false, ref, true));
|
|
579
|
+
}
|
|
580
|
+
case "sym": {
|
|
581
|
+
if (n.name === "&rest" || SEND_SELECTOR.test(n.name))
|
|
582
|
+
this.fail(`${n.name} only makes sense in a call or send`, n.line);
|
|
583
|
+
const ref = this.resolve(n.name, n.line);
|
|
584
|
+
switch (ref.kind) {
|
|
585
|
+
case "var": return this.op(this.varOp("l", false, ref));
|
|
586
|
+
case "prop": return this.op(`pToa ${ref.name}`);
|
|
587
|
+
case "const": return this.op(`ldi ${toSigned(constValue(ref.node, this.unit.defines, (m) => this.fail(m, n.line)))}`);
|
|
588
|
+
case "object": return this.op(`lofsa @${ref.label}`);
|
|
589
|
+
case "class": return this.op(`class $${ref.species.toString(16)}`);
|
|
590
|
+
case "self": return this.op("selfID");
|
|
591
|
+
case "argc": return this.op("lap 0");
|
|
592
|
+
}
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
case "list": return this.form(n);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/** Pushes a value, in one instruction where there is one. */
|
|
599
|
+
push(n) {
|
|
600
|
+
if (n.kind === "num" || (n.kind === "sym" && this.isConst(n.name))) {
|
|
601
|
+
const v = n.kind === "num" ? n.value : constValue({ ...n }, this.unit.defines, (m) => this.fail(m, n.line));
|
|
602
|
+
const w = toWord(v);
|
|
603
|
+
return this.op(w === 0 ? "push0" : w === 1 ? "push1" : w === 2 ? "push2" : `pushi ${toSigned(w)}`);
|
|
604
|
+
}
|
|
605
|
+
if (n.kind === "sel") {
|
|
606
|
+
this.project.sent.push({ name: n.name, file: this.unit.file, line: n.line });
|
|
607
|
+
return this.op(`pushi #${n.name}`);
|
|
608
|
+
}
|
|
609
|
+
if (n.kind === "str")
|
|
610
|
+
return this.op(`lofss @${this.string(n.value)}`);
|
|
611
|
+
if (n.kind === "sym") {
|
|
612
|
+
const ref = this.resolve(n.name, n.line);
|
|
613
|
+
if (ref.kind === "var")
|
|
614
|
+
return this.op(this.varOp("l", true, ref));
|
|
615
|
+
if (ref.kind === "prop")
|
|
616
|
+
return this.op(`pTos ${ref.name}`);
|
|
617
|
+
if (ref.kind === "object")
|
|
618
|
+
return this.op(`lofss @${ref.label}`);
|
|
619
|
+
if (ref.kind === "self")
|
|
620
|
+
return this.op("pushSelf");
|
|
621
|
+
}
|
|
622
|
+
this.expr(n);
|
|
623
|
+
this.op("push");
|
|
624
|
+
}
|
|
625
|
+
isConst(name) {
|
|
626
|
+
if (!this.fn)
|
|
627
|
+
return true;
|
|
628
|
+
const ref = (() => {
|
|
629
|
+
try {
|
|
630
|
+
return this.resolve(name, 0);
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
return undefined;
|
|
634
|
+
}
|
|
635
|
+
})();
|
|
636
|
+
return ref?.kind === "const";
|
|
637
|
+
}
|
|
638
|
+
arrayRef(name, line) {
|
|
639
|
+
const ref = this.resolve(name, line);
|
|
640
|
+
if (ref.kind !== "var")
|
|
641
|
+
this.fail(`${name} isn't a variable, so it can't be indexed`, line);
|
|
642
|
+
return ref;
|
|
643
|
+
}
|
|
644
|
+
/** @name or @[name i]: the address of a variable, for kernels that write into it. */
|
|
645
|
+
address(name, index, line) {
|
|
646
|
+
const ref = this.arrayRef(name, line);
|
|
647
|
+
const kind = { g: 0, l: 1, t: 2, p: 3 }[ref.type === "g" && this.unit.number === 0 ? "l" : ref.type];
|
|
648
|
+
if (index)
|
|
649
|
+
this.expr(index);
|
|
650
|
+
this.op(`lea ${(kind | (index ? 8 : 0)) << 1} ${ref.index}`);
|
|
651
|
+
}
|
|
652
|
+
form(n) {
|
|
653
|
+
const [head, ...args] = n.items;
|
|
654
|
+
if (!head)
|
|
655
|
+
this.fail("() is empty", n.line);
|
|
656
|
+
// A send: (target sel: ...), or (target [s] ...) with the selector in s.
|
|
657
|
+
if (isMessageHead(args[0]))
|
|
658
|
+
return this.send(head, args, n.line);
|
|
659
|
+
if (head.kind !== "sym") {
|
|
660
|
+
if (head.kind === "list" || head.kind === "index")
|
|
661
|
+
this.fail(`expected a selector after ${show(head)} (a send)`, n.line);
|
|
662
|
+
this.fail(`can't call ${show(head)}`, n.line);
|
|
663
|
+
}
|
|
664
|
+
const name = head.name;
|
|
665
|
+
const arity = (min, max = min) => {
|
|
666
|
+
if (args.length < min || args.length > max)
|
|
667
|
+
this.fail(`(${name} ...) takes ${min === max ? min : `${min} to ${max === Infinity ? "any number of" : max}`} argument(s)`, n.line);
|
|
668
|
+
};
|
|
669
|
+
switch (name) {
|
|
670
|
+
case "send": {
|
|
671
|
+
if (!args[0])
|
|
672
|
+
this.fail("(send target sel: ...)", n.line);
|
|
673
|
+
return this.send(args[0], args.slice(1), n.line);
|
|
674
|
+
}
|
|
675
|
+
case "=":
|
|
676
|
+
arity(2);
|
|
677
|
+
return this.assign(args[0], args[1], n.line);
|
|
678
|
+
case "++":
|
|
679
|
+
case "--":
|
|
680
|
+
arity(1);
|
|
681
|
+
return this.increment(args[0], name === "++" ? "+" : "-", n.line);
|
|
682
|
+
case "not":
|
|
683
|
+
arity(1);
|
|
684
|
+
this.expr(args[0]);
|
|
685
|
+
return this.op("not");
|
|
686
|
+
case "~":
|
|
687
|
+
arity(1);
|
|
688
|
+
this.expr(args[0]);
|
|
689
|
+
return this.op("bnot");
|
|
690
|
+
case "and":
|
|
691
|
+
case "or": {
|
|
692
|
+
arity(1, Infinity);
|
|
693
|
+
const end = this.label();
|
|
694
|
+
args.forEach((a, i) => {
|
|
695
|
+
this.expr(a);
|
|
696
|
+
if (i < args.length - 1)
|
|
697
|
+
this.op(`${name === "and" ? "bnt" : "bt"} ${end}`);
|
|
698
|
+
});
|
|
699
|
+
return this.emit(`${end}:`);
|
|
700
|
+
}
|
|
701
|
+
case "if": return this.ifForm(args, n.line);
|
|
702
|
+
case "cond": return this.cond(args, n.line);
|
|
703
|
+
case "switch": return this.switchForm(args, n.line);
|
|
704
|
+
case "switchto": {
|
|
705
|
+
// (switchto value (body for 0...) (body for 1...) ...): cases by position.
|
|
706
|
+
if (!args.length)
|
|
707
|
+
this.fail("(switchto value (body...)...)", n.line);
|
|
708
|
+
const cases = args.slice(1).map((c, i) => {
|
|
709
|
+
if (c.kind !== "list")
|
|
710
|
+
this.fail("a switchto case is (body...)", c.line);
|
|
711
|
+
return { kind: "list", items: [{ kind: "num", value: i, line: c.line }, ...c.items], line: c.line };
|
|
712
|
+
});
|
|
713
|
+
return this.switchForm([args[0], ...cases], n.line);
|
|
714
|
+
}
|
|
715
|
+
case "while": {
|
|
716
|
+
arity(1, Infinity);
|
|
717
|
+
const top = this.label(), end = this.label();
|
|
718
|
+
this.emit(`${top}:`);
|
|
719
|
+
this.expr(args[0]);
|
|
720
|
+
this.op(`bnt ${end}`);
|
|
721
|
+
this.loop(top, end, () => this.body(args.slice(1)));
|
|
722
|
+
this.op(`jmp ${top}`);
|
|
723
|
+
return this.emit(`${end}:`);
|
|
724
|
+
}
|
|
725
|
+
case "repeat": {
|
|
726
|
+
const top = this.label(), end = this.label();
|
|
727
|
+
this.emit(`${top}:`);
|
|
728
|
+
this.loop(top, end, () => this.body(args));
|
|
729
|
+
this.op(`jmp ${top}`);
|
|
730
|
+
return this.emit(`${end}:`);
|
|
731
|
+
}
|
|
732
|
+
case "for": {
|
|
733
|
+
arity(3, Infinity);
|
|
734
|
+
const [init, test, step] = args;
|
|
735
|
+
const list = (x) => (x.kind === "list" && (x.items.length === 0 || x.items[0].kind === "list") ? x.items : [x]);
|
|
736
|
+
this.body(list(init));
|
|
737
|
+
const top = this.label(), next = this.label(), end = this.label();
|
|
738
|
+
this.emit(`${top}:`);
|
|
739
|
+
this.expr(test);
|
|
740
|
+
this.op(`bnt ${end}`);
|
|
741
|
+
this.loop(next, end, () => this.body(args.slice(3)));
|
|
742
|
+
this.emit(`${next}:`);
|
|
743
|
+
this.body(list(step));
|
|
744
|
+
this.op(`jmp ${top}`);
|
|
745
|
+
return this.emit(`${end}:`);
|
|
746
|
+
}
|
|
747
|
+
case "break":
|
|
748
|
+
case "continue": {
|
|
749
|
+
arity(0);
|
|
750
|
+
const loop = this.fn.loops[this.fn.loops.length - 1];
|
|
751
|
+
if (!loop)
|
|
752
|
+
this.fail(`(${name}) outside a loop`, n.line);
|
|
753
|
+
// Leave the values of switches entered since the loop began.
|
|
754
|
+
for (let i = loop.switches; i < this.fn.switches; i++)
|
|
755
|
+
this.op("toss");
|
|
756
|
+
return this.op(`jmp ${name === "break" ? loop.brk : loop.cont}`);
|
|
757
|
+
}
|
|
758
|
+
case "return":
|
|
759
|
+
arity(0, 1);
|
|
760
|
+
if (args[0])
|
|
761
|
+
this.expr(args[0]);
|
|
762
|
+
return this.op("ret");
|
|
763
|
+
}
|
|
764
|
+
if (name in COMPOUND) {
|
|
765
|
+
arity(2);
|
|
766
|
+
const [target, value] = args;
|
|
767
|
+
return this.assign(target, { kind: "list", items: [{ kind: "sym", name: COMPOUND[name], line: n.line }, target, value], line: n.line }, n.line);
|
|
768
|
+
}
|
|
769
|
+
if (name in BINARY) {
|
|
770
|
+
if (name === "-" && args.length === 1) {
|
|
771
|
+
this.expr(args[0]);
|
|
772
|
+
return this.op("neg");
|
|
773
|
+
}
|
|
774
|
+
arity(2, name === "-" || name === "/" || name === "mod" || name === "<<" || name === ">>" ? 2 : Infinity);
|
|
775
|
+
this.expr(args[0]);
|
|
776
|
+
for (const a of args.slice(1)) {
|
|
777
|
+
this.op("push");
|
|
778
|
+
this.expr(a);
|
|
779
|
+
this.op(BINARY[name]);
|
|
780
|
+
}
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (name in COMPARE) {
|
|
784
|
+
arity(2);
|
|
785
|
+
this.push(args[0]);
|
|
786
|
+
this.expr(args[1]);
|
|
787
|
+
return this.op(COMPARE[name]);
|
|
788
|
+
}
|
|
789
|
+
return this.call(name, args, n.line);
|
|
790
|
+
}
|
|
791
|
+
loop(cont, brk, body) {
|
|
792
|
+
this.fn.loops.push({ brk, cont, switches: this.fn.switches });
|
|
793
|
+
body();
|
|
794
|
+
this.fn.loops.pop();
|
|
795
|
+
}
|
|
796
|
+
ifForm(args, line) {
|
|
797
|
+
if (!args.length)
|
|
798
|
+
this.fail("(if condition ...)", line);
|
|
799
|
+
const split = args.findIndex((a) => a.kind === "sym" && a.name === "else");
|
|
800
|
+
const then = split < 0 ? args.slice(1) : args.slice(1, split);
|
|
801
|
+
const otherwise = split < 0 ? undefined : args.slice(split + 1);
|
|
802
|
+
const elseLabel = this.label(), end = this.label();
|
|
803
|
+
this.expr(args[0]);
|
|
804
|
+
this.op(`bnt ${otherwise ? elseLabel : end}`);
|
|
805
|
+
this.body(then);
|
|
806
|
+
if (otherwise) {
|
|
807
|
+
this.op(`jmp ${end}`);
|
|
808
|
+
this.emit(`${elseLabel}:`);
|
|
809
|
+
this.body(otherwise);
|
|
810
|
+
}
|
|
811
|
+
this.emit(`${end}:`);
|
|
812
|
+
}
|
|
813
|
+
cond(clauses, line) {
|
|
814
|
+
const end = this.label();
|
|
815
|
+
for (const c of clauses) {
|
|
816
|
+
if (c.kind !== "list" || !c.items.length)
|
|
817
|
+
this.fail("a cond clause is (condition body...)", c.line ?? line);
|
|
818
|
+
const [test, ...body] = c.items;
|
|
819
|
+
if (test.kind === "sym" && test.name === "else") {
|
|
820
|
+
this.body(body);
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
const next = this.label();
|
|
824
|
+
this.expr(test);
|
|
825
|
+
this.op(`bnt ${next}`);
|
|
826
|
+
this.body(body);
|
|
827
|
+
this.op(`jmp ${end}`);
|
|
828
|
+
this.emit(`${next}:`);
|
|
829
|
+
}
|
|
830
|
+
this.emit(`${end}:`);
|
|
831
|
+
}
|
|
832
|
+
switchForm(args, line) {
|
|
833
|
+
if (!args.length)
|
|
834
|
+
this.fail("(switch value (case body...)...)", line);
|
|
835
|
+
const end = this.label();
|
|
836
|
+
this.expr(args[0]);
|
|
837
|
+
this.op("push");
|
|
838
|
+
this.fn.switches++;
|
|
839
|
+
for (const c of args.slice(1)) {
|
|
840
|
+
if (c.kind !== "list" || !c.items.length)
|
|
841
|
+
this.fail("a switch case is (value body...)", c.line ?? line);
|
|
842
|
+
const [value, ...body] = c.items;
|
|
843
|
+
if (value.kind === "sym" && value.name === "else") {
|
|
844
|
+
this.body(body);
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
const next = this.label();
|
|
848
|
+
this.op("dup");
|
|
849
|
+
this.expr(value);
|
|
850
|
+
this.op("eq?");
|
|
851
|
+
this.op(`bnt ${next}`);
|
|
852
|
+
this.body(body);
|
|
853
|
+
this.op(`jmp ${end}`);
|
|
854
|
+
this.emit(`${next}:`);
|
|
855
|
+
}
|
|
856
|
+
this.fn.switches--;
|
|
857
|
+
this.emit(`${end}:`);
|
|
858
|
+
this.op("toss");
|
|
859
|
+
}
|
|
860
|
+
assign(target, value, line) {
|
|
861
|
+
if (target.kind === "index") {
|
|
862
|
+
const [arr, i] = target.items;
|
|
863
|
+
if (arr?.kind !== "sym" || !i)
|
|
864
|
+
this.fail("an array element is [name index]", line);
|
|
865
|
+
const ref = this.arrayRef(arr.name, line);
|
|
866
|
+
this.push(value);
|
|
867
|
+
this.expr(i);
|
|
868
|
+
return this.op(this.varOp("s", false, ref, true));
|
|
869
|
+
}
|
|
870
|
+
if (target.kind !== "sym")
|
|
871
|
+
this.fail(`can't assign to ${show(target)}`, line);
|
|
872
|
+
const ref = this.resolve(target.name, line);
|
|
873
|
+
this.expr(value);
|
|
874
|
+
if (ref.kind === "var")
|
|
875
|
+
return this.op(this.varOp("s", false, ref));
|
|
876
|
+
if (ref.kind === "prop")
|
|
877
|
+
return this.op(`aTop ${ref.name}`);
|
|
878
|
+
this.fail(`can't assign to ${target.name}`, line);
|
|
879
|
+
}
|
|
880
|
+
increment(target, dir, line) {
|
|
881
|
+
if (target.kind === "index") {
|
|
882
|
+
const [arr, i] = target.items;
|
|
883
|
+
if (arr?.kind !== "sym" || !i)
|
|
884
|
+
this.fail("an array element is [name index]", line);
|
|
885
|
+
const ref = this.arrayRef(arr.name, line);
|
|
886
|
+
this.expr(i);
|
|
887
|
+
return this.op(this.varOp(dir, false, ref, true));
|
|
888
|
+
}
|
|
889
|
+
if (target.kind !== "sym")
|
|
890
|
+
this.fail(`can't change ${show(target)}`, line);
|
|
891
|
+
const ref = this.resolve(target.name, line);
|
|
892
|
+
if (ref.kind === "var")
|
|
893
|
+
return this.op(this.varOp(dir, false, ref));
|
|
894
|
+
if (ref.kind === "prop")
|
|
895
|
+
return this.op(`${dir === "+" ? "ipToa" : "dpToa"} ${ref.name}`);
|
|
896
|
+
this.fail(`can't change ${target.name}`, line);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Splits arguments from a trailing `&rest` (the caller's arguments after its named ones) or
|
|
900
|
+
* `&rest param` (from that parameter on).
|
|
901
|
+
*/
|
|
902
|
+
restSplit(args, line) {
|
|
903
|
+
const at = args.findIndex((a) => a.kind === "sym" && a.name === "&rest");
|
|
904
|
+
if (at < 0)
|
|
905
|
+
return { explicit: args };
|
|
906
|
+
const from = args[at + 1];
|
|
907
|
+
const fromParam = from?.kind === "sym" ? this.fn.params.get(from.name) : undefined;
|
|
908
|
+
if (args.length > at + (fromParam === undefined ? 1 : 2))
|
|
909
|
+
this.fail("&rest goes last", line);
|
|
910
|
+
return { explicit: args.slice(0, at), rest: fromParam ?? this.fn.func.params.length + 1 };
|
|
911
|
+
}
|
|
912
|
+
/** Pushes arguments; returns the frame size in bytes (what &rest adds is counted at run time). */
|
|
913
|
+
pushArgs(args) {
|
|
914
|
+
for (const a of args.explicit)
|
|
915
|
+
this.push(a);
|
|
916
|
+
if (args.rest !== undefined)
|
|
917
|
+
this.op(`&rest ${args.rest}`);
|
|
918
|
+
return args.explicit.length * 2;
|
|
919
|
+
}
|
|
920
|
+
send(target, messages, line) {
|
|
921
|
+
let frame = 0;
|
|
922
|
+
for (let i = 0; i < messages.length;) {
|
|
923
|
+
const sel = messages[i];
|
|
924
|
+
if (!isMessageHead(sel))
|
|
925
|
+
this.fail(`expected a selector like x: or init:, got ${show(sel)}`, sel.line);
|
|
926
|
+
let j = i + 1;
|
|
927
|
+
while (j < messages.length && !isMessageHead(messages[j]))
|
|
928
|
+
j++;
|
|
929
|
+
const args = this.restSplit(messages.slice(i + 1, j), sel.line);
|
|
930
|
+
if (sel.kind === "sym") {
|
|
931
|
+
const name = SEND_SELECTOR.exec(sel.name)[1];
|
|
932
|
+
this.project.sent.push({ name, file: this.unit.file, line: sel.line });
|
|
933
|
+
this.op(`pushi #${name}`);
|
|
934
|
+
}
|
|
935
|
+
else
|
|
936
|
+
this.push(sel.items[0]);
|
|
937
|
+
this.push({ kind: "num", value: args.explicit.length, line });
|
|
938
|
+
frame += 4 + this.pushArgs(args);
|
|
939
|
+
i = j;
|
|
940
|
+
}
|
|
941
|
+
if (!messages.length)
|
|
942
|
+
this.fail("a send needs at least one selector", line);
|
|
943
|
+
if (target.kind === "sym" && target.name === "self")
|
|
944
|
+
return this.op(`self ${frame}`);
|
|
945
|
+
if (target.kind === "sym" && target.name === "super") {
|
|
946
|
+
const sup = this.fn.owner?.superSpecies;
|
|
947
|
+
if (sup === undefined)
|
|
948
|
+
this.fail("super: this object has no superclass", line);
|
|
949
|
+
return this.op(`super $${sup.toString(16)} ${frame}`);
|
|
950
|
+
}
|
|
951
|
+
this.expr(target);
|
|
952
|
+
this.op(`send ${frame}`);
|
|
953
|
+
}
|
|
954
|
+
call(name, args, line) {
|
|
955
|
+
const local = this.procedures.has(name);
|
|
956
|
+
const ext = local ? undefined : this.unit.externs.get(name) ?? this.project.publicProcs.get(name);
|
|
957
|
+
const kernel = local || ext ? -1 : this.project.ctx.kernelNames.indexOf(name);
|
|
958
|
+
if (!local && !ext && kernel < 0) {
|
|
959
|
+
if (this.objects.has(name) || this.project.classes.has(name))
|
|
960
|
+
this.fail(`${name} is an object: send it a message, like (${name} init:)`, line);
|
|
961
|
+
this.fail(`unknown procedure or kernel ${name}`, line);
|
|
962
|
+
}
|
|
963
|
+
const split = this.restSplit(args, line);
|
|
964
|
+
this.push({ kind: "num", value: split.explicit.length, line });
|
|
965
|
+
const frame = this.pushArgs(split);
|
|
966
|
+
if (local)
|
|
967
|
+
this.op(`call ${name} ${frame}`);
|
|
968
|
+
else if (ext)
|
|
969
|
+
this.op(ext.script === 0 ? `callb ${ext.index} ${frame}` : `calle ${ext.script} ${ext.index} ${frame}`);
|
|
970
|
+
else
|
|
971
|
+
this.op(`callk ${name} ${frame}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const toWord = (v) => ((v % 0x10000) + 0x10000) % 0x10000;
|
|
975
|
+
const toSigned = (v) => {
|
|
976
|
+
const w = toWord(v);
|
|
977
|
+
return w >= 0x8000 ? w - 0x10000 : w;
|
|
978
|
+
};
|
|
979
|
+
function quote(text) {
|
|
980
|
+
let s = '"';
|
|
981
|
+
for (const ch of text) {
|
|
982
|
+
const c = ch.charCodeAt(0);
|
|
983
|
+
if (ch === '"' || ch === "\\")
|
|
984
|
+
s += `\\${ch}`;
|
|
985
|
+
else if (ch === "\n")
|
|
986
|
+
s += "\\n";
|
|
987
|
+
else if (c >= 0x20 && c < 0x7f)
|
|
988
|
+
s += ch;
|
|
989
|
+
else
|
|
990
|
+
s += `\\x${(c & 0xff).toString(16).padStart(2, "0")}`;
|
|
991
|
+
}
|
|
992
|
+
return `${s}"`;
|
|
993
|
+
}
|