mlogjs 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{index.cjs → Compiler-7GMG4MDr.js} +1746 -452
- package/dist/Compiler.d.ts +8 -12
- package/dist/CompilerError.d.ts +13 -3
- package/dist/Scope.d.ts +8 -8
- package/dist/bin/files.d.ts +18 -0
- package/dist/bin.mjs +170 -0
- package/dist/handlers/Import.d.ts +5 -0
- package/dist/handlers/Literal.d.ts +2 -2
- package/dist/handlers/Typescript.d.ts +2 -2
- package/dist/handlers/index.d.ts +1 -0
- package/dist/index.mjs +3 -4267
- package/dist/instructions/AddressResolver.d.ts +4 -4
- package/dist/instructions/ControlInstructions.d.ts +3 -3
- package/dist/instructions/InstructionBase.d.ts +1 -0
- package/dist/instructions/JumpInstruction.d.ts +5 -5
- package/dist/instructions/index.d.ts +0 -1
- package/dist/macros/DynamicArray.d.ts +4 -4
- package/dist/macros/Function.d.ts +2 -2
- package/dist/macros/GetBuildings.d.ts +4 -0
- package/dist/macros/GetColor.d.ts +4 -0
- package/dist/macros/Marker.d.ts +4 -0
- package/dist/macros/Namespace.d.ts +16 -0
- package/dist/macros/Queries.d.ts +10 -0
- package/dist/macros/StringView.d.ts +4 -0
- package/dist/macros/commands/Effect.d.ts +4 -0
- package/dist/macros/commands/Format.d.ts +4 -0
- package/dist/macros/commands/LocalePrint.d.ts +4 -0
- package/dist/macros/commands/PlayMusic.d.ts +4 -0
- package/dist/macros/commands/PlaySound.d.ts +4 -0
- package/dist/macros/commands/PrintChar.d.ts +4 -0
- package/dist/macros/commands/Query.d.ts +4 -0
- package/dist/macros/commands/SetProp.d.ts +4 -0
- package/dist/macros/commands/SpawnBullet.d.ts +4 -0
- package/dist/macros/commands/Sync.d.ts +6 -0
- package/dist/macros/commands/UnpackColor.d.ts +4 -0
- package/dist/macros/commands/WeatherSense.d.ts +4 -0
- package/dist/macros/commands/WeatherSet.d.ts +4 -0
- package/dist/macros/commands/index.d.ts +13 -0
- package/dist/macros/index.d.ts +7 -1
- package/dist/macros/util.d.ts +5 -2
- package/dist/modules.d.ts +8 -0
- package/dist/operators.d.ts +1 -1
- package/dist/types.d.ts +84 -59
- package/dist/utils/constants.d.ts +9 -4
- package/dist/utils/general.d.ts +8 -9
- package/dist/utils/instruction.d.ts +5 -1
- package/dist/utils/value.d.ts +14 -5
- package/dist/values/AssignmentValue.d.ts +1 -1
- package/dist/values/BaseValue.d.ts +4 -2
- package/dist/values/DestructuringValue.d.ts +5 -7
- package/dist/values/FunctionValue.d.ts +8 -6
- package/dist/values/JumpOutValue.d.ts +4 -4
- package/dist/values/LazyValue.d.ts +3 -3
- package/dist/values/LiteralValue.d.ts +2 -3
- package/dist/values/ObjectValue.d.ts +6 -11
- package/dist/values/StoreValue.d.ts +10 -5
- package/dist/values/VoidValue.d.ts +2 -1
- package/dist/values/index.d.ts +0 -1
- package/lib/commands.d.ts +776 -730
- package/lib/eslib/lib.es2015.collection.d.ts +50 -39
- package/lib/eslib/lib.es2015.core.d.ts +59 -0
- package/lib/eslib/lib.es2015.d.ts +0 -3
- package/lib/eslib/lib.es2015.generator.d.ts +54 -53
- package/lib/eslib/lib.es2015.iterable.d.ts +9 -6
- package/lib/eslib/lib.es5.d.ts +53 -6
- package/lib/globals.d.ts +309 -18
- package/lib/helpers.d.ts +86 -52
- package/lib/kinds.d.ts +42 -7
- package/lib/macros.d.ts +53 -18
- package/lib/traits.d.ts +308 -146
- package/lib/util.d.ts +144 -48
- package/lib/world.d.ts +1197 -0
- package/package.json +27 -35
- package/dist/bin.cjs +0 -111
- package/dist/initScope.d.ts +0 -5
- package/dist/instructions/ZeroSpaceInstruction.d.ts +0 -4
- package/dist/values/SenseableValue.d.ts +0 -24
- /package/dist/{bin.d.ts → bin/index.d.ts} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,4267 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
class CompilerError extends Error {
|
|
5
|
-
constructor(message, source) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.loc = source;
|
|
8
|
-
}
|
|
9
|
-
get loc() {
|
|
10
|
-
return this._loc;
|
|
11
|
-
}
|
|
12
|
-
set loc(value) {
|
|
13
|
-
if (!value)
|
|
14
|
-
return;
|
|
15
|
-
if ("type" in value) {
|
|
16
|
-
this._loc = value.loc;
|
|
17
|
-
}
|
|
18
|
-
else if ("start" in value) {
|
|
19
|
-
this._loc = value;
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
this._loc = {
|
|
23
|
-
start: value,
|
|
24
|
-
end: value,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
static from(error, source) {
|
|
29
|
-
let message;
|
|
30
|
-
if (error && typeof error === "object" && "loc" in error) {
|
|
31
|
-
const err = error;
|
|
32
|
-
message = err.toString();
|
|
33
|
-
source ??= err.loc;
|
|
34
|
-
}
|
|
35
|
-
else if (error instanceof Error) {
|
|
36
|
-
message = error.message;
|
|
37
|
-
}
|
|
38
|
-
else if (typeof error === "string") {
|
|
39
|
-
message = error;
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
message = `Unknown error: ${error}`;
|
|
43
|
-
}
|
|
44
|
-
const result = new CompilerError(message, source);
|
|
45
|
-
result.inner = error;
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
var EInstIntent;
|
|
51
|
-
(function (EInstIntent) {
|
|
52
|
-
EInstIntent[EInstIntent["none"] = 0] = "none";
|
|
53
|
-
EInstIntent[EInstIntent["break"] = 1] = "break";
|
|
54
|
-
EInstIntent[EInstIntent["continue"] = 2] = "continue";
|
|
55
|
-
EInstIntent[EInstIntent["return"] = 3] = "return";
|
|
56
|
-
/**
|
|
57
|
-
* This is used by the `end` and `stop` instructions.
|
|
58
|
-
* Means that the processor will restart or shutdown.
|
|
59
|
-
*/
|
|
60
|
-
EInstIntent[EInstIntent["exit"] = 4] = "exit";
|
|
61
|
-
})(EInstIntent || (EInstIntent = {}));
|
|
62
|
-
/** Defines the possible types of mutability of a value */
|
|
63
|
-
var EMutability;
|
|
64
|
-
(function (EMutability) {
|
|
65
|
-
/**
|
|
66
|
-
* The value can be changed by the user's code,
|
|
67
|
-
* by the mlog runtime, or us
|
|
68
|
-
*/
|
|
69
|
-
EMutability[EMutability["mutable"] = 0] = "mutable";
|
|
70
|
-
/**
|
|
71
|
-
* The value cannot be directly assigned,
|
|
72
|
-
* but it's still not safe from mutations coming from the runtime.
|
|
73
|
-
*/
|
|
74
|
-
EMutability[EMutability["readonly"] = 1] = "readonly";
|
|
75
|
-
/** Constant values, won't change during execution */
|
|
76
|
-
EMutability[EMutability["constant"] = 2] = "constant";
|
|
77
|
-
/** An immutable value that hasn't been initialized yet */
|
|
78
|
-
EMutability[EMutability["init"] = 3] = "init";
|
|
79
|
-
})(EMutability || (EMutability = {}));
|
|
80
|
-
|
|
81
|
-
const arithmeticBinaryOperators = [
|
|
82
|
-
"*",
|
|
83
|
-
"**",
|
|
84
|
-
"+",
|
|
85
|
-
"-",
|
|
86
|
-
"/",
|
|
87
|
-
"%",
|
|
88
|
-
];
|
|
89
|
-
const comparisonBinaryOperators = [
|
|
90
|
-
"!=",
|
|
91
|
-
"!==",
|
|
92
|
-
"<",
|
|
93
|
-
"<=",
|
|
94
|
-
"==",
|
|
95
|
-
"===",
|
|
96
|
-
">",
|
|
97
|
-
">=",
|
|
98
|
-
];
|
|
99
|
-
const bitwiseBinaryOperators = [
|
|
100
|
-
"&",
|
|
101
|
-
"<<",
|
|
102
|
-
">>",
|
|
103
|
-
">>>",
|
|
104
|
-
"^",
|
|
105
|
-
"|",
|
|
106
|
-
];
|
|
107
|
-
const binaryOperators = [
|
|
108
|
-
...arithmeticBinaryOperators,
|
|
109
|
-
...comparisonBinaryOperators,
|
|
110
|
-
...bitwiseBinaryOperators,
|
|
111
|
-
"instanceof",
|
|
112
|
-
"in",
|
|
113
|
-
];
|
|
114
|
-
const logicalOperators = ["&&", "??", "||"];
|
|
115
|
-
const arithmeticAssignmentOperators = [
|
|
116
|
-
"%=",
|
|
117
|
-
"&=",
|
|
118
|
-
"*=",
|
|
119
|
-
"**=",
|
|
120
|
-
"+=",
|
|
121
|
-
"-=",
|
|
122
|
-
"/=",
|
|
123
|
-
];
|
|
124
|
-
const logicalAssignmentOperators = ["&&=", "||=", "??="];
|
|
125
|
-
const bitwiseAssignmentOperators = [
|
|
126
|
-
"<<=",
|
|
127
|
-
">>=",
|
|
128
|
-
">>>=",
|
|
129
|
-
"^=",
|
|
130
|
-
"|=",
|
|
131
|
-
];
|
|
132
|
-
const assignmentOperators = [
|
|
133
|
-
...arithmeticAssignmentOperators,
|
|
134
|
-
...logicalAssignmentOperators,
|
|
135
|
-
...bitwiseAssignmentOperators,
|
|
136
|
-
"=",
|
|
137
|
-
];
|
|
138
|
-
const leftRightOperators = [
|
|
139
|
-
...binaryOperators,
|
|
140
|
-
...logicalOperators,
|
|
141
|
-
...assignmentOperators,
|
|
142
|
-
];
|
|
143
|
-
const unaryOperators = [
|
|
144
|
-
"!",
|
|
145
|
-
"u+",
|
|
146
|
-
"u-",
|
|
147
|
-
"delete",
|
|
148
|
-
"typeof",
|
|
149
|
-
"void",
|
|
150
|
-
"~",
|
|
151
|
-
];
|
|
152
|
-
const updateOperators = ["++", "--"];
|
|
153
|
-
const singleOperators = [...unaryOperators, ...updateOperators];
|
|
154
|
-
const operators = [...leftRightOperators, ...singleOperators];
|
|
155
|
-
const operatorMap$2 = {
|
|
156
|
-
"==": "equal",
|
|
157
|
-
"===": "strictEqual",
|
|
158
|
-
"!=": "notEqual",
|
|
159
|
-
"<": "lessThan",
|
|
160
|
-
">": "greaterThan",
|
|
161
|
-
"<=": "lessThanEq",
|
|
162
|
-
">=": "greaterThanEq",
|
|
163
|
-
"+": "add",
|
|
164
|
-
"-": "sub",
|
|
165
|
-
"*": "mul",
|
|
166
|
-
"/": "div",
|
|
167
|
-
"%": "mod",
|
|
168
|
-
"**": "pow",
|
|
169
|
-
"|": "or",
|
|
170
|
-
"&": "and",
|
|
171
|
-
"^": "xor",
|
|
172
|
-
">>": "shr",
|
|
173
|
-
">>>": "shr",
|
|
174
|
-
"<<": "shl",
|
|
175
|
-
"&&": "land",
|
|
176
|
-
"||": "or",
|
|
177
|
-
};
|
|
178
|
-
const orderIndependentOperators = [
|
|
179
|
-
"!=",
|
|
180
|
-
"!==",
|
|
181
|
-
"&&",
|
|
182
|
-
"&",
|
|
183
|
-
"*",
|
|
184
|
-
"+",
|
|
185
|
-
"==",
|
|
186
|
-
"===",
|
|
187
|
-
"|",
|
|
188
|
-
"||",
|
|
189
|
-
];
|
|
190
|
-
|
|
191
|
-
class VoidValue {
|
|
192
|
-
constructor() {
|
|
193
|
-
this.mutability = EMutability.mutable;
|
|
194
|
-
this.macro = false;
|
|
195
|
-
}
|
|
196
|
-
eval(_scope, _out) {
|
|
197
|
-
throw new CompilerError(`[${this.debugString()}] cannot be evaluated into a value.`);
|
|
198
|
-
}
|
|
199
|
-
call(_scope, _args, _out) {
|
|
200
|
-
throw new CompilerError(`[${this.debugString()}] is not callable.`);
|
|
201
|
-
}
|
|
202
|
-
get(_scope, name) {
|
|
203
|
-
throw new CompilerError(`The member [${name.debugString()}] does not exist in [${this.debugString()}]`);
|
|
204
|
-
}
|
|
205
|
-
hasProperty(_scope, _prop) {
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
preCall(_scope, _out) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
postCall(_scope) { }
|
|
212
|
-
toOut() {
|
|
213
|
-
return this;
|
|
214
|
-
}
|
|
215
|
-
debugString() {
|
|
216
|
-
return "VoidValue";
|
|
217
|
-
}
|
|
218
|
-
toString() {
|
|
219
|
-
return '"[macro VoidValue]"';
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
for (const key of operators) {
|
|
223
|
-
VoidValue.prototype[key] = function () {
|
|
224
|
-
console.log(this);
|
|
225
|
-
throw new CompilerError(`The operator '${key}' is not defined for [${this.debugString()}].`);
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
class InstructionBase {
|
|
230
|
-
get hidden() {
|
|
231
|
-
return this._hidden;
|
|
232
|
-
}
|
|
233
|
-
set hidden(value) {
|
|
234
|
-
this._hidden = value;
|
|
235
|
-
}
|
|
236
|
-
constructor(...args) {
|
|
237
|
-
this.intent = EInstIntent.none;
|
|
238
|
-
this.alwaysRuns = true;
|
|
239
|
-
this._hidden = false;
|
|
240
|
-
this.args = args;
|
|
241
|
-
}
|
|
242
|
-
resolve(_i) { }
|
|
243
|
-
toString() {
|
|
244
|
-
return this.args.filter(arg => arg).join(" ");
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
class OperationInstruction extends InstructionBase {
|
|
249
|
-
constructor(kind, temp, left, right) {
|
|
250
|
-
super("op", kind, temp, left, right);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* The prefix for internal variables inside the compiler output
|
|
256
|
-
*/
|
|
257
|
-
const internalPrefix = "&";
|
|
258
|
-
const discardedName = `${internalPrefix}_`;
|
|
259
|
-
const itemNames = [
|
|
260
|
-
"copper",
|
|
261
|
-
"lead",
|
|
262
|
-
"metaglass",
|
|
263
|
-
"graphite",
|
|
264
|
-
"sand",
|
|
265
|
-
"coal",
|
|
266
|
-
"titanium",
|
|
267
|
-
"thorium",
|
|
268
|
-
"scrap",
|
|
269
|
-
"silicon",
|
|
270
|
-
"plastanium",
|
|
271
|
-
"phaseFabric",
|
|
272
|
-
"surgeAlloy",
|
|
273
|
-
"sporePod",
|
|
274
|
-
"blastCompound",
|
|
275
|
-
"pyratite",
|
|
276
|
-
"beryllium",
|
|
277
|
-
"tungsten",
|
|
278
|
-
"oxide",
|
|
279
|
-
"carbide",
|
|
280
|
-
"fissileMatter",
|
|
281
|
-
"dormantCyst",
|
|
282
|
-
];
|
|
283
|
-
/** The name of a special processor variable, controls the instruction pointer */
|
|
284
|
-
const counterName = "@counter";
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Returns a string that has the format: [line]:[column].
|
|
288
|
-
*
|
|
289
|
-
* If `name` is provided, and it is a string, the resulting string will
|
|
290
|
-
* have the format: [name]:[line]:[column].
|
|
291
|
-
*/
|
|
292
|
-
function nodeName(node, name) {
|
|
293
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
294
|
-
const { line, column } = node.loc.start;
|
|
295
|
-
if (typeof name === "string")
|
|
296
|
-
return `${name}:${line}:${column}`;
|
|
297
|
-
return `${line}:${column}`;
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Converts a camel case string into a dash case one.
|
|
301
|
-
*
|
|
302
|
-
* As an example `camelCase` becomes `camel-case`
|
|
303
|
-
* @example
|
|
304
|
-
* ```
|
|
305
|
-
* camelToDashCase("camelCase") // returns "camel-case"
|
|
306
|
-
* ```
|
|
307
|
-
*/
|
|
308
|
-
function camelToDashCase(name) {
|
|
309
|
-
return name.replace(/[A-Z]/g, str => `-${str.toLowerCase()}`);
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* A more type safe version of `Object.assign`
|
|
313
|
-
*/
|
|
314
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
315
|
-
function assign(obj, props) {
|
|
316
|
-
return Object.assign(obj, props);
|
|
317
|
-
}
|
|
318
|
-
/** Lazily computes and caches an expression */
|
|
319
|
-
function lazy(fn) {
|
|
320
|
-
let value;
|
|
321
|
-
return () => (value ??= fn());
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function appendSourceLocations(valueInst, node) {
|
|
325
|
-
for (const inst of valueInst[1]) {
|
|
326
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
327
|
-
inst.source ??= node.loc;
|
|
328
|
-
}
|
|
329
|
-
return valueInst;
|
|
330
|
-
}
|
|
331
|
-
function withAlwaysRuns(valueInst, value) {
|
|
332
|
-
valueInst[1].forEach(inst => (inst.alwaysRuns = value));
|
|
333
|
-
return valueInst;
|
|
334
|
-
}
|
|
335
|
-
/** Removes jump instructions that point right to the next line */
|
|
336
|
-
function hideRedundantJumps(inst) {
|
|
337
|
-
for (let i = inst.length - 1; i >= 0; i--) {
|
|
338
|
-
const instruction = inst[i];
|
|
339
|
-
if (!(instruction instanceof JumpInstruction))
|
|
340
|
-
continue;
|
|
341
|
-
const { address } = instruction;
|
|
342
|
-
let searchIndex = i + 1;
|
|
343
|
-
let current = inst[searchIndex];
|
|
344
|
-
while (current?.hidden) {
|
|
345
|
-
if (current instanceof AddressResolver &&
|
|
346
|
-
current.bonds.includes(address)) {
|
|
347
|
-
// only gets assigned if `current` is an adress resolver
|
|
348
|
-
// that resolves the jump adress
|
|
349
|
-
// and that is also right next to the jump,
|
|
350
|
-
// without any actual instructions in between
|
|
351
|
-
instruction.hidden = true;
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
searchIndex++;
|
|
355
|
-
current = inst[searchIndex];
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
/** A helper that appends the instructions from `value` into `inst` and returns it's `IValue` instance */
|
|
360
|
-
function pipeInsts(value, inst) {
|
|
361
|
-
inst.push(...value[1]);
|
|
362
|
-
return value[0];
|
|
363
|
-
}
|
|
364
|
-
function usesAddressResolver(resolver, instructions) {
|
|
365
|
-
for (let i = 0; i < instructions.length; i++) {
|
|
366
|
-
const inst = instructions[i];
|
|
367
|
-
if (inst instanceof JumpInstruction &&
|
|
368
|
-
resolver.bonds.includes(inst.address))
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function isTemplateObjectArray(value) {
|
|
375
|
-
return (value instanceof ObjectValue &&
|
|
376
|
-
value.data.length instanceof LiteralValue &&
|
|
377
|
-
value.data.length.isNumber() &&
|
|
378
|
-
value.data.raw instanceof ObjectValue &&
|
|
379
|
-
value.data.raw.data.length instanceof LiteralValue &&
|
|
380
|
-
value.data.raw.data.length.isNumber());
|
|
381
|
-
}
|
|
382
|
-
function extractOutName(out) {
|
|
383
|
-
if (!out || typeof out === "string")
|
|
384
|
-
return out;
|
|
385
|
-
return out.name;
|
|
386
|
-
}
|
|
387
|
-
function extractDestrucuringOut(out, field) {
|
|
388
|
-
if (isDiscardedOut(out))
|
|
389
|
-
return discardedName;
|
|
390
|
-
if (!(out instanceof DestructuringValue))
|
|
391
|
-
return;
|
|
392
|
-
return out.fields[field] ?? discardedName;
|
|
393
|
-
}
|
|
394
|
-
/** Asserts that `value` is a `LiteralValue` that contains a string */
|
|
395
|
-
function assertStringLiteral(value, name) {
|
|
396
|
-
if (!(value instanceof LiteralValue) || !value.isString())
|
|
397
|
-
throw new CompilerError(`${name} must be a string literal`);
|
|
398
|
-
}
|
|
399
|
-
/** Asserts that `value` is a string literal that is part of a given set of `members`*/
|
|
400
|
-
function assertLiteralOneOf(value, members, name) {
|
|
401
|
-
assertStringLiteral(value, name);
|
|
402
|
-
if (!members.includes(value.data))
|
|
403
|
-
throw new CompilerError(`${name} must be one of: ${members
|
|
404
|
-
.slice(0, -1)
|
|
405
|
-
.map(member => `"${member}"`)
|
|
406
|
-
.join(", ")} or "${members[members.length - 1]}"`);
|
|
407
|
-
}
|
|
408
|
-
/** Asserts that `value` is either a store or a literal */
|
|
409
|
-
function assertIsRuntimeValue(value, name) {
|
|
410
|
-
if (!(value instanceof LiteralValue) && !(value instanceof StoreValue))
|
|
411
|
-
throw new CompilerError(`${name} must be a valid runtime value`);
|
|
412
|
-
}
|
|
413
|
-
/** Asserts that `value` is an `ObjectValue` */
|
|
414
|
-
function assertIsObjectMacro(value, name) {
|
|
415
|
-
if (!(value instanceof ObjectValue))
|
|
416
|
-
throw new CompilerError(`${name} must be an object macro`);
|
|
417
|
-
}
|
|
418
|
-
/** Asserts that `value` is an `ObjectValue` that has a length property */
|
|
419
|
-
function assertIsArrayMacro(value, name) {
|
|
420
|
-
if (value instanceof ObjectValue) {
|
|
421
|
-
const { length } = value.data;
|
|
422
|
-
if (length instanceof LiteralValue && length.isNumber())
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
throw new CompilerError(`${name} must be an array macro`);
|
|
426
|
-
}
|
|
427
|
-
/** Asserts that all of the fields described are present on `value`*/
|
|
428
|
-
function assertObjectFields(value, fields) {
|
|
429
|
-
const result = [];
|
|
430
|
-
for (const field of fields) {
|
|
431
|
-
const param = typeof field === "object" ? field : { key: field };
|
|
432
|
-
const item = value.data[param.key];
|
|
433
|
-
if (item) {
|
|
434
|
-
param.validate?.(item);
|
|
435
|
-
result.push(item);
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
if (param.default == undefined)
|
|
439
|
-
throw new CompilerError(`The field "${param.name ?? param.key}" must be present`);
|
|
440
|
-
result.push(param.default);
|
|
441
|
-
}
|
|
442
|
-
return result;
|
|
443
|
-
}
|
|
444
|
-
function isDiscardedOut(out) {
|
|
445
|
-
if (!out)
|
|
446
|
-
return false;
|
|
447
|
-
if (typeof out === "string")
|
|
448
|
-
return out === discardedName;
|
|
449
|
-
return out.name === discardedName;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
class SetInstruction extends InstructionBase {
|
|
453
|
-
constructor(store, value) {
|
|
454
|
-
super("set", store, value);
|
|
455
|
-
}
|
|
456
|
-
get store() {
|
|
457
|
-
return this.args[1];
|
|
458
|
-
}
|
|
459
|
-
get value() {
|
|
460
|
-
return this.args[2];
|
|
461
|
-
}
|
|
462
|
-
get hidden() {
|
|
463
|
-
const { store, value } = this;
|
|
464
|
-
return (this._hidden ||
|
|
465
|
-
// assignment to discarded value
|
|
466
|
-
store.name === discardedName ||
|
|
467
|
-
// self assignment of a store
|
|
468
|
-
(!!store.name &&
|
|
469
|
-
store instanceof StoreValue &&
|
|
470
|
-
value instanceof StoreValue &&
|
|
471
|
-
store.name === value.name));
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
class EndInstruction extends InstructionBase {
|
|
476
|
-
constructor() {
|
|
477
|
-
super("end");
|
|
478
|
-
this.intent = EInstIntent.exit;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
class AddressResolver extends InstructionBase {
|
|
483
|
-
get hidden() {
|
|
484
|
-
return true;
|
|
485
|
-
}
|
|
486
|
-
set hidden(value) { }
|
|
487
|
-
constructor(...bonds) {
|
|
488
|
-
super();
|
|
489
|
-
this.bonds = bonds;
|
|
490
|
-
}
|
|
491
|
-
resolve(i) {
|
|
492
|
-
for (const literal of this.bonds)
|
|
493
|
-
literal.data = i;
|
|
494
|
-
}
|
|
495
|
-
bind(bond) {
|
|
496
|
-
this.bonds.push(bond);
|
|
497
|
-
}
|
|
498
|
-
bindBreak(scope) {
|
|
499
|
-
scope.break = this;
|
|
500
|
-
return this;
|
|
501
|
-
}
|
|
502
|
-
bindContinue(scope) {
|
|
503
|
-
scope.continue = this;
|
|
504
|
-
return this;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
var EJumpKind;
|
|
509
|
-
(function (EJumpKind) {
|
|
510
|
-
EJumpKind["Equal"] = "equal";
|
|
511
|
-
EJumpKind["NotEqual"] = "notEqual";
|
|
512
|
-
EJumpKind["LessThan"] = "lessThan";
|
|
513
|
-
EJumpKind["LessThanEq"] = "lessThanEq";
|
|
514
|
-
EJumpKind["GreaterThan"] = "greaterThan";
|
|
515
|
-
EJumpKind["GreaterThanEq"] = "greaterThanEq";
|
|
516
|
-
EJumpKind["StrictEqual"] = "strictEqual";
|
|
517
|
-
EJumpKind["Always"] = "always";
|
|
518
|
-
})(EJumpKind || (EJumpKind = {}));
|
|
519
|
-
class JumpInstruction extends InstructionBase {
|
|
520
|
-
constructor(address, kind, left = null, right = null) {
|
|
521
|
-
super("jump", address, kind, left, right);
|
|
522
|
-
this.address = address;
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* Works with the `JumpOutValue` class to determine
|
|
526
|
-
* wether a jump should be added if the expression wasn't jump compressed.
|
|
527
|
-
*/
|
|
528
|
-
static or(test, out) {
|
|
529
|
-
if (test === out)
|
|
530
|
-
return [];
|
|
531
|
-
return [
|
|
532
|
-
new JumpInstruction(out.address, out.whenTrue ? EJumpKind.NotEqual : EJumpKind.Equal, test, new LiteralValue(0)),
|
|
533
|
-
];
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
class SetCounterInstruction extends InstructionBase {
|
|
538
|
-
constructor(value) {
|
|
539
|
-
super("set", counterName, value);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
class ZeroSpaceInstruction extends InstructionBase {
|
|
544
|
-
toString() {
|
|
545
|
-
return this.args.join("");
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
class BreakInstruction extends JumpInstruction {
|
|
550
|
-
constructor(address) {
|
|
551
|
-
super(address, EJumpKind.Always);
|
|
552
|
-
this.intent = EInstIntent.break;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
class ContinueInstruction extends JumpInstruction {
|
|
556
|
-
constructor(address) {
|
|
557
|
-
super(address, EJumpKind.Always);
|
|
558
|
-
this.intent = EInstIntent.continue;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
const operatorMap$1 = {
|
|
563
|
-
"!=": EJumpKind.NotEqual,
|
|
564
|
-
"<": EJumpKind.LessThan,
|
|
565
|
-
"<=": EJumpKind.LessThanEq,
|
|
566
|
-
"==": EJumpKind.Equal,
|
|
567
|
-
"===": EJumpKind.StrictEqual,
|
|
568
|
-
">": EJumpKind.GreaterThan,
|
|
569
|
-
">=": EJumpKind.GreaterThanEq,
|
|
570
|
-
};
|
|
571
|
-
const invertedOperatorMap = {
|
|
572
|
-
"==": operatorMap$1["!="],
|
|
573
|
-
"!=": operatorMap$1["=="],
|
|
574
|
-
"!==": operatorMap$1["==="],
|
|
575
|
-
">": operatorMap$1["<="],
|
|
576
|
-
"<": operatorMap$1[">="],
|
|
577
|
-
">=": operatorMap$1["<"],
|
|
578
|
-
"<=": operatorMap$1[">"],
|
|
579
|
-
};
|
|
580
|
-
class JumpOutValue extends VoidValue {
|
|
581
|
-
constructor(node, address, whenTrue) {
|
|
582
|
-
super();
|
|
583
|
-
this.node = node;
|
|
584
|
-
this.address = address;
|
|
585
|
-
this.whenTrue = whenTrue;
|
|
586
|
-
this.macro = true;
|
|
587
|
-
}
|
|
588
|
-
canHandle(operator) {
|
|
589
|
-
if (this.whenTrue)
|
|
590
|
-
return operator in operatorMap$1;
|
|
591
|
-
return operator in invertedOperatorMap;
|
|
592
|
-
}
|
|
593
|
-
handle(operator, left, right) {
|
|
594
|
-
let kind;
|
|
595
|
-
if (this.whenTrue) {
|
|
596
|
-
if (operator === "!==")
|
|
597
|
-
throw new CompilerError('Unsupported jump compression operator "!=="');
|
|
598
|
-
kind = operatorMap$1[operator];
|
|
599
|
-
}
|
|
600
|
-
else {
|
|
601
|
-
if (operator === "===")
|
|
602
|
-
throw new CompilerError('Unsupported jump compression operator "==="');
|
|
603
|
-
kind = invertedOperatorMap[operator];
|
|
604
|
-
}
|
|
605
|
-
const jump = new JumpInstruction(this.address, kind, left, right);
|
|
606
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
607
|
-
jump.source = this.node.loc;
|
|
608
|
-
return jump;
|
|
609
|
-
}
|
|
610
|
-
eval(_scope, _out) {
|
|
611
|
-
return [this, []];
|
|
612
|
-
}
|
|
613
|
-
debugString() {
|
|
614
|
-
return "JumpOutValue";
|
|
615
|
-
}
|
|
616
|
-
toString() {
|
|
617
|
-
return '"[macro JumpOutValue]"';
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
class BaseValue extends VoidValue {
|
|
622
|
-
"u-"(scope, out) {
|
|
623
|
-
const [that, inst] = this.eval(scope);
|
|
624
|
-
const temp = StoreValue.from(scope, out);
|
|
625
|
-
return [
|
|
626
|
-
temp,
|
|
627
|
-
[
|
|
628
|
-
...inst,
|
|
629
|
-
new OperationInstruction("sub", temp, new LiteralValue(0), that),
|
|
630
|
-
],
|
|
631
|
-
];
|
|
632
|
-
}
|
|
633
|
-
"u+"(scope, out) {
|
|
634
|
-
const inst = [];
|
|
635
|
-
const value = pipeInsts(this.eval(scope), inst);
|
|
636
|
-
const zero = new LiteralValue(0);
|
|
637
|
-
const result = pipeInsts(zero["+"](scope, value, out), inst);
|
|
638
|
-
return [result, inst];
|
|
639
|
-
}
|
|
640
|
-
"!"(scope, out) {
|
|
641
|
-
const inst = [];
|
|
642
|
-
const falseLiteral = new LiteralValue(0);
|
|
643
|
-
const that = pipeInsts(this.eval(scope), inst);
|
|
644
|
-
const result = pipeInsts(that["=="](scope, falseLiteral, out), inst);
|
|
645
|
-
return [result, inst];
|
|
646
|
-
}
|
|
647
|
-
"~"(scope, out) {
|
|
648
|
-
const [that, inst] = this.eval(scope);
|
|
649
|
-
const temp = StoreValue.from(scope, out);
|
|
650
|
-
return [temp, [...inst, new OperationInstruction("not", temp, that, null)]];
|
|
651
|
-
}
|
|
652
|
-
// requires special handling
|
|
653
|
-
// the handler should give an object value to allow the lazy evaluation
|
|
654
|
-
"??"(scope, other, out) {
|
|
655
|
-
const result = SenseableValue.from(scope, out, EMutability.mutable);
|
|
656
|
-
const nullLiteral = new LiteralValue(null);
|
|
657
|
-
const endAdress = new LiteralValue(null);
|
|
658
|
-
const [left, leftInst] = this.eval(scope, result);
|
|
659
|
-
const [right, rightInst] = other.eval(scope, result);
|
|
660
|
-
const [test, testInst] = left["==="](scope, nullLiteral);
|
|
661
|
-
return [
|
|
662
|
-
result,
|
|
663
|
-
[
|
|
664
|
-
...leftInst,
|
|
665
|
-
...result["="](scope, left)[1],
|
|
666
|
-
...testInst,
|
|
667
|
-
new JumpInstruction(endAdress, EJumpKind.Equal, test, new LiteralValue(0)),
|
|
668
|
-
...rightInst,
|
|
669
|
-
...result["="](scope, right)[1],
|
|
670
|
-
new AddressResolver(endAdress),
|
|
671
|
-
],
|
|
672
|
-
];
|
|
673
|
-
}
|
|
674
|
-
"!=="(scope, other, out) {
|
|
675
|
-
if (out instanceof JumpOutValue && out.canHandle("!==")) {
|
|
676
|
-
const [left, leftInst] = this.eval(scope);
|
|
677
|
-
const [right, rightInst] = other.eval(scope);
|
|
678
|
-
return [out, [...leftInst, ...rightInst, out.handle("!==", left, right)]];
|
|
679
|
-
}
|
|
680
|
-
const [equal, equalInst] = this["==="](scope, other);
|
|
681
|
-
const [result, resultInst] = equal["!"](scope, out);
|
|
682
|
-
return [result, [...equalInst, ...resultInst]];
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
for (const key in operatorMap$2) {
|
|
686
|
-
const kind = operatorMap$2[key];
|
|
687
|
-
BaseValue.prototype[key] = function (scope, value, out) {
|
|
688
|
-
const [left, leftInst] = this.eval(scope);
|
|
689
|
-
const [right, rightInst] = value.eval(scope);
|
|
690
|
-
if (out instanceof JumpOutValue && out.canHandle(key)) {
|
|
691
|
-
const jump = out.handle(key, left, right);
|
|
692
|
-
return [out, [...leftInst, ...rightInst, jump]];
|
|
693
|
-
}
|
|
694
|
-
const temp = StoreValue.from(scope, out);
|
|
695
|
-
return [
|
|
696
|
-
temp,
|
|
697
|
-
[
|
|
698
|
-
...leftInst,
|
|
699
|
-
...rightInst,
|
|
700
|
-
new OperationInstruction(kind, temp, left, right),
|
|
701
|
-
],
|
|
702
|
-
];
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
const assignmentToBinary = {
|
|
706
|
-
"+=": "+",
|
|
707
|
-
"-=": "-",
|
|
708
|
-
"*=": "*",
|
|
709
|
-
"/=": "/",
|
|
710
|
-
"%=": "%",
|
|
711
|
-
"**=": "**",
|
|
712
|
-
"|=": "|",
|
|
713
|
-
"&=": "&",
|
|
714
|
-
"^=": "^",
|
|
715
|
-
">>=": ">>",
|
|
716
|
-
">>>=": ">>>",
|
|
717
|
-
"<<=": "<<",
|
|
718
|
-
"&&=": "&&",
|
|
719
|
-
"||=": "||",
|
|
720
|
-
"??=": "??",
|
|
721
|
-
};
|
|
722
|
-
for (const k in assignmentToBinary) {
|
|
723
|
-
const op = k;
|
|
724
|
-
BaseValue.prototype[op] = function (scope, value) {
|
|
725
|
-
const [opValue, opInst] = this[assignmentToBinary[op]](scope, value, this.toOut());
|
|
726
|
-
const [retValue, retInst] = this["="](scope, opValue);
|
|
727
|
-
return [retValue, [...opInst, ...retInst]];
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
for (const key of updateOperators) {
|
|
731
|
-
BaseValue.prototype[key] = function (scope, prefix, out) {
|
|
732
|
-
let [ret, inst] = this.eval(scope);
|
|
733
|
-
if (!prefix && !isDiscardedOut(out)) {
|
|
734
|
-
const temp = StoreValue.from(scope, out);
|
|
735
|
-
pipeInsts(temp["="](scope, ret), inst);
|
|
736
|
-
ret = temp;
|
|
737
|
-
}
|
|
738
|
-
const kind = key === "++" ? "+=" : "-=";
|
|
739
|
-
pipeInsts(this[kind](scope, new LiteralValue(1)), inst);
|
|
740
|
-
return [ret, inst];
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
const literalMethods = {
|
|
745
|
-
length: function () {
|
|
746
|
-
if (!this.isString())
|
|
747
|
-
throw new CompilerError("Length method only works on string literal values.");
|
|
748
|
-
return new LiteralValue(this.data.length);
|
|
749
|
-
},
|
|
750
|
-
};
|
|
751
|
-
class LiteralValue extends BaseValue {
|
|
752
|
-
constructor(data) {
|
|
753
|
-
super();
|
|
754
|
-
this.mutability = EMutability.constant;
|
|
755
|
-
this.data = data;
|
|
756
|
-
this.name = this.toString();
|
|
757
|
-
}
|
|
758
|
-
eval(_scope) {
|
|
759
|
-
return [this, []];
|
|
760
|
-
}
|
|
761
|
-
toString() {
|
|
762
|
-
return JSON.stringify(this.data);
|
|
763
|
-
}
|
|
764
|
-
get(scope, name) {
|
|
765
|
-
if (!(name instanceof LiteralValue && name.isString()))
|
|
766
|
-
return super.get(scope, name);
|
|
767
|
-
const method = literalMethods[name.data];
|
|
768
|
-
if (!method)
|
|
769
|
-
throw new CompilerError(`The member [${name.debugString()}] does not exist on literal values.`);
|
|
770
|
-
return [method.apply(this, [scope]), []];
|
|
771
|
-
}
|
|
772
|
-
hasProperty(scope, prop) {
|
|
773
|
-
if (this.isString() && prop instanceof LiteralValue && prop.isString())
|
|
774
|
-
return Object.prototype.hasOwnProperty.call(literalMethods, prop.data);
|
|
775
|
-
return false;
|
|
776
|
-
}
|
|
777
|
-
get num() {
|
|
778
|
-
if (this.data === null)
|
|
779
|
-
return 0;
|
|
780
|
-
if (typeof this.data === "string")
|
|
781
|
-
return 1;
|
|
782
|
-
return this.data;
|
|
783
|
-
}
|
|
784
|
-
"??"(scope, other, out) {
|
|
785
|
-
if (this.data === null)
|
|
786
|
-
return other.eval(scope, out);
|
|
787
|
-
return [this, []];
|
|
788
|
-
}
|
|
789
|
-
typeof() {
|
|
790
|
-
return [new LiteralValue("literal"), []];
|
|
791
|
-
}
|
|
792
|
-
isString() {
|
|
793
|
-
return typeof this.data === "string";
|
|
794
|
-
}
|
|
795
|
-
isNumber() {
|
|
796
|
-
return typeof this.data === "number";
|
|
797
|
-
}
|
|
798
|
-
debugString() {
|
|
799
|
-
return this.toString();
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
const operatorMap = {
|
|
803
|
-
"==": (a, b) => +(a == b),
|
|
804
|
-
"===": (a, b) => +(a === b),
|
|
805
|
-
"!=": (a, b) => +(a != b),
|
|
806
|
-
"!==": (a, b) => +(a !== b),
|
|
807
|
-
"<": (a, b) => +(a < b),
|
|
808
|
-
">": (a, b) => +(a > b),
|
|
809
|
-
"<=": (a, b) => +(a <= b),
|
|
810
|
-
">=": (a, b) => +(a >= b),
|
|
811
|
-
"+": (a, b) => a + b,
|
|
812
|
-
"-": (a, b) => a - b,
|
|
813
|
-
"*": (a, b) => a * b,
|
|
814
|
-
"/": (a, b) => a / b,
|
|
815
|
-
"%": (a, b) => a % b,
|
|
816
|
-
"**": (a, b) => a ** b,
|
|
817
|
-
"|": (a, b) => a | b,
|
|
818
|
-
"&": (a, b) => a & b,
|
|
819
|
-
"^": (a, b) => a ^ b,
|
|
820
|
-
">>": (a, b) => a >> b,
|
|
821
|
-
">>>": (a, b) => a >> b,
|
|
822
|
-
"<<": (a, b) => a << b,
|
|
823
|
-
"&&": (a, b) => +(a && b),
|
|
824
|
-
"||": (a, b) => +(a || b),
|
|
825
|
-
};
|
|
826
|
-
for (const k in operatorMap) {
|
|
827
|
-
const key = k;
|
|
828
|
-
const fn = operatorMap[key];
|
|
829
|
-
LiteralValue.prototype[key] = function (scope, value, out) {
|
|
830
|
-
if (key === "&&") {
|
|
831
|
-
if (this.data)
|
|
832
|
-
return [value, []];
|
|
833
|
-
return [new LiteralValue(0), []];
|
|
834
|
-
}
|
|
835
|
-
if (key === "||") {
|
|
836
|
-
if (!this.data)
|
|
837
|
-
return [value, []];
|
|
838
|
-
return [new LiteralValue(1), []];
|
|
839
|
-
}
|
|
840
|
-
if (!(value instanceof LiteralValue)) {
|
|
841
|
-
return BaseValue.prototype[key].apply(this, [scope, value, out]);
|
|
842
|
-
}
|
|
843
|
-
return [new LiteralValue(fn(this.data, value.data)), []];
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
const unaryOperatorMap = {
|
|
847
|
-
"!": v => +!v,
|
|
848
|
-
"~": v => ~v,
|
|
849
|
-
"u-": v => -v,
|
|
850
|
-
"u+": v => +v,
|
|
851
|
-
};
|
|
852
|
-
for (const key in unaryOperatorMap) {
|
|
853
|
-
LiteralValue.prototype[key] = function () {
|
|
854
|
-
const fn = unaryOperatorMap[key];
|
|
855
|
-
return [new LiteralValue(fn(this.data)), []];
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* `StoreValue` represents values unknown at compile time,
|
|
861
|
-
* mostly used with mutable variables and temporary values
|
|
862
|
-
*/
|
|
863
|
-
class StoreValue extends BaseValue {
|
|
864
|
-
constructor(name, mutability = EMutability.mutable) {
|
|
865
|
-
super();
|
|
866
|
-
this.name = name;
|
|
867
|
-
this.mutability = mutability;
|
|
868
|
-
}
|
|
869
|
-
static from(scope, out, mutability = EMutability.mutable) {
|
|
870
|
-
if (out instanceof StoreValue)
|
|
871
|
-
return out;
|
|
872
|
-
const name = typeof out === "string" ? out : scope.makeTempName();
|
|
873
|
-
return new StoreValue(name, mutability);
|
|
874
|
-
}
|
|
875
|
-
static out(scope, out, mutability = EMutability.mutable) {
|
|
876
|
-
if (!out || typeof out === "string") {
|
|
877
|
-
return new StoreValue(out ?? scope.makeTempName(), mutability);
|
|
878
|
-
}
|
|
879
|
-
return out;
|
|
880
|
-
}
|
|
881
|
-
typeof() {
|
|
882
|
-
return [new LiteralValue("store"), []];
|
|
883
|
-
}
|
|
884
|
-
"="(scope, value) {
|
|
885
|
-
if (this.mutability !== EMutability.mutable &&
|
|
886
|
-
this.mutability !== EMutability.init)
|
|
887
|
-
throw new CompilerError(`Cannot assign to immutable value: [${this.debugString()}].`);
|
|
888
|
-
if (compareStores(this, value))
|
|
889
|
-
return [this, []];
|
|
890
|
-
const [evalValue, evalInst] = value.eval(scope, this);
|
|
891
|
-
if (evalValue.macro)
|
|
892
|
-
throw new CompilerError(`Cannot assign a macro to a store (attempted to assign [${evalValue.debugString()}] to [${this.debugString()}])`);
|
|
893
|
-
return [evalValue, [...evalInst, new SetInstruction(this, evalValue)]];
|
|
894
|
-
}
|
|
895
|
-
"=="(scope, value, out) {
|
|
896
|
-
if (compareStores(this, value))
|
|
897
|
-
return [new LiteralValue(1), []];
|
|
898
|
-
return super["=="](scope, value, out);
|
|
899
|
-
}
|
|
900
|
-
"==="(scope, value, out) {
|
|
901
|
-
if (compareStores(this, value))
|
|
902
|
-
return [new LiteralValue(1), []];
|
|
903
|
-
return super["==="](scope, value, out);
|
|
904
|
-
}
|
|
905
|
-
"!="(scope, value, out) {
|
|
906
|
-
if (compareStores(this, value))
|
|
907
|
-
return [new LiteralValue(0), []];
|
|
908
|
-
return super["!="](scope, value, out);
|
|
909
|
-
}
|
|
910
|
-
"!=="(scope, value, out) {
|
|
911
|
-
if (compareStores(this, value))
|
|
912
|
-
return [new LiteralValue(0), []];
|
|
913
|
-
return super["!=="](scope, value, out);
|
|
914
|
-
}
|
|
915
|
-
eval(_scope) {
|
|
916
|
-
return [this, []];
|
|
917
|
-
}
|
|
918
|
-
debugString() {
|
|
919
|
-
return `StoreValue("${this.name}")`;
|
|
920
|
-
}
|
|
921
|
-
toString() {
|
|
922
|
-
return this.name;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
function compareStores(left, right) {
|
|
926
|
-
return right instanceof StoreValue && right.name === left.name;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
class ObjectValue extends VoidValue {
|
|
930
|
-
constructor(data = {}) {
|
|
931
|
-
super();
|
|
932
|
-
this.mutability = EMutability.constant;
|
|
933
|
-
this.macro = true;
|
|
934
|
-
this.data = data;
|
|
935
|
-
}
|
|
936
|
-
static fromArray(items, initialData) {
|
|
937
|
-
const data = {
|
|
938
|
-
...initialData,
|
|
939
|
-
length: new LiteralValue(items.length),
|
|
940
|
-
};
|
|
941
|
-
items.forEach((item, i) => {
|
|
942
|
-
if (item)
|
|
943
|
-
data[i] = item;
|
|
944
|
-
});
|
|
945
|
-
return new ObjectValue(data);
|
|
946
|
-
}
|
|
947
|
-
typeof(scope, out) {
|
|
948
|
-
const { $typeof } = this.data;
|
|
949
|
-
if ($typeof)
|
|
950
|
-
return $typeof.call(scope, [], out);
|
|
951
|
-
return [new LiteralValue("object"), []];
|
|
952
|
-
}
|
|
953
|
-
get(scope, key, out) {
|
|
954
|
-
if (key instanceof LiteralValue && (key.isNumber() || key.isString())) {
|
|
955
|
-
// avoids naming collisions with keys like
|
|
956
|
-
// constructor or toString
|
|
957
|
-
if (Object.prototype.hasOwnProperty.call(this.data, key.data)) {
|
|
958
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
959
|
-
return [this.data[key.data], []];
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
const { $get } = this.data;
|
|
963
|
-
if (!$get)
|
|
964
|
-
throw new CompilerError(`The member [${key.debugString()}] is not present in [${this.debugString()}]`);
|
|
965
|
-
return $get.call(scope, [key], out);
|
|
966
|
-
}
|
|
967
|
-
hasProperty(scope, prop) {
|
|
968
|
-
if (prop instanceof LiteralValue && (prop.isNumber() || prop.isString())) {
|
|
969
|
-
const hasMember = Object.prototype.hasOwnProperty.call(this.data, prop.data);
|
|
970
|
-
return hasMember;
|
|
971
|
-
}
|
|
972
|
-
return false;
|
|
973
|
-
}
|
|
974
|
-
eval(scope, out) {
|
|
975
|
-
const { $eval } = this.data;
|
|
976
|
-
if (!$eval)
|
|
977
|
-
return [this, []];
|
|
978
|
-
return $eval.call(scope, [], out);
|
|
979
|
-
}
|
|
980
|
-
call(scope, args, out) {
|
|
981
|
-
const { $call } = this.data;
|
|
982
|
-
if (!$call)
|
|
983
|
-
return super.call(scope, args, out);
|
|
984
|
-
return $call.call(scope, args, out);
|
|
985
|
-
}
|
|
986
|
-
"??"(scope, other, out) {
|
|
987
|
-
const $ = this.data["$??"];
|
|
988
|
-
if ($)
|
|
989
|
-
return $.call(scope, [other], out);
|
|
990
|
-
return [this, []];
|
|
991
|
-
}
|
|
992
|
-
debugString() {
|
|
993
|
-
if (this.name)
|
|
994
|
-
return `ObjectValue("${this.name}")`;
|
|
995
|
-
return "ObjectValue";
|
|
996
|
-
}
|
|
997
|
-
toString() {
|
|
998
|
-
return '"[macro ObjectValue]"';
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
for (const op of leftRightOperators) {
|
|
1002
|
-
if (op === "??")
|
|
1003
|
-
continue;
|
|
1004
|
-
ObjectValue.prototype[op] = function (scope, value, out) {
|
|
1005
|
-
const $ = this.data[`$${op}`];
|
|
1006
|
-
if (!$)
|
|
1007
|
-
return VoidValue.prototype[op].apply(this, [scope, value, out]);
|
|
1008
|
-
return $.call(scope, [value], out);
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
for (const op of unaryOperators) {
|
|
1012
|
-
if (op === "typeof")
|
|
1013
|
-
continue;
|
|
1014
|
-
ObjectValue.prototype[op] = function (scope, out) {
|
|
1015
|
-
const $ = this.data[`$${op}`];
|
|
1016
|
-
if (!$)
|
|
1017
|
-
return VoidValue.prototype[op].apply(this, [scope, out]);
|
|
1018
|
-
return $.call(scope, [], out);
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
for (const op of updateOperators) {
|
|
1022
|
-
ObjectValue.prototype[op] = function (scope, prefix, out) {
|
|
1023
|
-
const $ = this.data[`$${op}`];
|
|
1024
|
-
if (!$)
|
|
1025
|
-
return VoidValue.prototype[op].apply(this, [scope, prefix, out]);
|
|
1026
|
-
return $.call(scope, [new LiteralValue(+prefix)], out);
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
/**
|
|
1031
|
-
* Properties that should return a new `SenseableValue`
|
|
1032
|
-
*/
|
|
1033
|
-
const senseableProps = ["controller"];
|
|
1034
|
-
/**
|
|
1035
|
-
* Senseable values are "objects" in the mlog runtime that
|
|
1036
|
-
* have special properties that can be accessed
|
|
1037
|
-
* via the `sense` instruction.
|
|
1038
|
-
*
|
|
1039
|
-
* This class extends `StoreValue` to allow dynamic behavior
|
|
1040
|
-
* that is not possible with object macros.
|
|
1041
|
-
*
|
|
1042
|
-
* Note that these values are constant by default, so make sure
|
|
1043
|
-
* to set that field to `EMutability.mutable` if you want them to be assignable. Example:
|
|
1044
|
-
* ```
|
|
1045
|
-
* const value = new SenseableValue(name, EMutability.mutable);
|
|
1046
|
-
* ```
|
|
1047
|
-
*/
|
|
1048
|
-
class SenseableValue extends StoreValue {
|
|
1049
|
-
constructor(name, mutability = EMutability.constant) {
|
|
1050
|
-
super(name, mutability);
|
|
1051
|
-
}
|
|
1052
|
-
static from(scope, out, mutability = EMutability.constant) {
|
|
1053
|
-
if (out instanceof SenseableValue)
|
|
1054
|
-
return out;
|
|
1055
|
-
const name = typeof out === "string" ? out : scope.makeTempName();
|
|
1056
|
-
return new SenseableValue(name, mutability);
|
|
1057
|
-
}
|
|
1058
|
-
static out(scope, out, mutability = EMutability.mutable) {
|
|
1059
|
-
if (!out || typeof out === "string") {
|
|
1060
|
-
return new SenseableValue(out ?? scope.makeTempName(), mutability);
|
|
1061
|
-
}
|
|
1062
|
-
return out;
|
|
1063
|
-
}
|
|
1064
|
-
get(scope, prop, out) {
|
|
1065
|
-
const mutability = EMutability.readonly;
|
|
1066
|
-
if (prop instanceof LiteralValue && prop.isString()) {
|
|
1067
|
-
const name = itemNames.includes(prop.data)
|
|
1068
|
-
? camelToDashCase(prop.data)
|
|
1069
|
-
: prop.data;
|
|
1070
|
-
const result = senseableProps.includes(prop.data)
|
|
1071
|
-
? SenseableValue.from(scope, out, mutability)
|
|
1072
|
-
: StoreValue.from(scope, out, mutability);
|
|
1073
|
-
return [
|
|
1074
|
-
result,
|
|
1075
|
-
[new InstructionBase("sensor", result, this, `@${name}`)],
|
|
1076
|
-
];
|
|
1077
|
-
}
|
|
1078
|
-
if (prop instanceof StoreValue) {
|
|
1079
|
-
const temp = SenseableValue.from(scope, out, mutability);
|
|
1080
|
-
return [temp, [new InstructionBase("sensor", temp, this, prop)]];
|
|
1081
|
-
}
|
|
1082
|
-
throw new CompilerError(`The property [${prop.debugString()}] cannot be sensed`);
|
|
1083
|
-
}
|
|
1084
|
-
debugString() {
|
|
1085
|
-
return `SenseableValue("${this.name}")`;
|
|
1086
|
-
}
|
|
1087
|
-
hasProperty(scope, prop) {
|
|
1088
|
-
return ((prop instanceof LiteralValue && prop.isString()) ||
|
|
1089
|
-
prop instanceof StoreValue);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
/**
|
|
1094
|
-
* Specific case class, required on the ArrayPattern and ObjectPattern handlers
|
|
1095
|
-
* in order to keep the separation of concerns and make the code more modular.
|
|
1096
|
-
*
|
|
1097
|
-
* When it is assign a value, it will get it's own properties and recursively assign
|
|
1098
|
-
* each of them to their counterparts on the right hand value.
|
|
1099
|
-
*/
|
|
1100
|
-
class DestructuringValue extends VoidValue {
|
|
1101
|
-
constructor(members) {
|
|
1102
|
-
super();
|
|
1103
|
-
this.members = members;
|
|
1104
|
-
this.macro = true;
|
|
1105
|
-
this.fields = {};
|
|
1106
|
-
for (const [key, { value }] of this.members) {
|
|
1107
|
-
if (key instanceof LiteralValue && (key.isString() || key.isNumber())) {
|
|
1108
|
-
this.fields[key.data] = value;
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
"="(scope, right) {
|
|
1113
|
-
const inst = [];
|
|
1114
|
-
for (const [key, { value, handler }] of this.members) {
|
|
1115
|
-
pipeInsts(handler(() => right.get(scope, key, value), () => right.hasProperty(scope, key), scope), inst);
|
|
1116
|
-
}
|
|
1117
|
-
return [right, inst];
|
|
1118
|
-
}
|
|
1119
|
-
eval(_scope) {
|
|
1120
|
-
return [this, []];
|
|
1121
|
-
}
|
|
1122
|
-
debugString() {
|
|
1123
|
-
return "DestructuringValue";
|
|
1124
|
-
}
|
|
1125
|
-
toString() {
|
|
1126
|
-
return '"[macro DestructuringValue]"';
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
/**
|
|
1131
|
-
* This class is used to handle some cases where the value
|
|
1132
|
-
* can't be directly evaluated by the node handler
|
|
1133
|
-
*/
|
|
1134
|
-
class LazyValue extends VoidValue {
|
|
1135
|
-
constructor(evalutate) {
|
|
1136
|
-
super();
|
|
1137
|
-
this.eval = evalutate;
|
|
1138
|
-
}
|
|
1139
|
-
debugString() {
|
|
1140
|
-
return "LazyValue";
|
|
1141
|
-
}
|
|
1142
|
-
toString() {
|
|
1143
|
-
return '"[macro LazyValue]"';
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
class AssignmentValue extends VoidValue {
|
|
1148
|
-
constructor(left, right) {
|
|
1149
|
-
super();
|
|
1150
|
-
this.left = left;
|
|
1151
|
-
this.right = right;
|
|
1152
|
-
this.name = left.name;
|
|
1153
|
-
}
|
|
1154
|
-
"="(scope, value, out) {
|
|
1155
|
-
const inst = [];
|
|
1156
|
-
const input = pipeInsts(value["??"](scope, this.right, this.left.toOut()), inst);
|
|
1157
|
-
const output = pipeInsts(this.left["="](scope, input, out), inst);
|
|
1158
|
-
return [output, inst];
|
|
1159
|
-
}
|
|
1160
|
-
toOut() {
|
|
1161
|
-
return this.left.toOut();
|
|
1162
|
-
}
|
|
1163
|
-
debugString() {
|
|
1164
|
-
return "AssignmentValue";
|
|
1165
|
-
}
|
|
1166
|
-
toString() {
|
|
1167
|
-
return '"[macro AssignmentValue]"';
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
class Scope {
|
|
1172
|
-
constructor(values, parent = null, stacked = false, ntemp = 0, name = "", inst = [], operationCache = {}, cacheDependencies = {}) {
|
|
1173
|
-
this.parent = parent;
|
|
1174
|
-
this.stacked = stacked;
|
|
1175
|
-
this.ntemp = ntemp;
|
|
1176
|
-
this.name = name;
|
|
1177
|
-
this.inst = inst;
|
|
1178
|
-
this.operationCache = operationCache;
|
|
1179
|
-
this.cacheDependencies = cacheDependencies;
|
|
1180
|
-
// Only `unchecked` is supposed to change this
|
|
1181
|
-
this.checkIndexes = true;
|
|
1182
|
-
this.data = values;
|
|
1183
|
-
}
|
|
1184
|
-
copy() {
|
|
1185
|
-
const scope = new Scope({ ...this.data }, this.parent, this.stacked, this.ntemp, this.name, this.inst, { ...this.operationCache }, { ...this.cacheDependencies });
|
|
1186
|
-
scope.break = this.break;
|
|
1187
|
-
scope.continue = this.continue;
|
|
1188
|
-
scope.function = this.function;
|
|
1189
|
-
return scope;
|
|
1190
|
-
}
|
|
1191
|
-
createScope() {
|
|
1192
|
-
const scope = this.copy();
|
|
1193
|
-
scope.data = {};
|
|
1194
|
-
scope.operationCache = {};
|
|
1195
|
-
scope.cacheDependencies = {};
|
|
1196
|
-
scope.parent = this;
|
|
1197
|
-
return scope;
|
|
1198
|
-
}
|
|
1199
|
-
createFunction(name, stacked = this.stacked) {
|
|
1200
|
-
return new Scope({}, this, stacked, 0, name, this.inst);
|
|
1201
|
-
}
|
|
1202
|
-
has(identifier) {
|
|
1203
|
-
if (identifier in this.data)
|
|
1204
|
-
return true;
|
|
1205
|
-
if (this.parent)
|
|
1206
|
-
return this.parent.has(identifier);
|
|
1207
|
-
return false;
|
|
1208
|
-
}
|
|
1209
|
-
get(identifier) {
|
|
1210
|
-
const value = this.data[identifier];
|
|
1211
|
-
if (value)
|
|
1212
|
-
return value;
|
|
1213
|
-
if (this.parent)
|
|
1214
|
-
return this.parent.get(identifier);
|
|
1215
|
-
throw new CompilerError(`${identifier} is not declared.`);
|
|
1216
|
-
}
|
|
1217
|
-
set(name, value) {
|
|
1218
|
-
if (name in this.data)
|
|
1219
|
-
throw new CompilerError(`${name} is already declared.`);
|
|
1220
|
-
return this.hardSet(name, value);
|
|
1221
|
-
}
|
|
1222
|
-
hardSet(name, value) {
|
|
1223
|
-
if (!name)
|
|
1224
|
-
throw new CompilerError("Values in a scope must have an identifier");
|
|
1225
|
-
this.data[name] = value;
|
|
1226
|
-
return value;
|
|
1227
|
-
}
|
|
1228
|
-
make(identifier, name) {
|
|
1229
|
-
const value = assign(new SenseableValue(name), {
|
|
1230
|
-
mutability: EMutability.mutable,
|
|
1231
|
-
});
|
|
1232
|
-
return this.set(identifier, value);
|
|
1233
|
-
}
|
|
1234
|
-
makeTempName() {
|
|
1235
|
-
let result = `${internalPrefix}t${this.ntemp}`;
|
|
1236
|
-
if (this.name)
|
|
1237
|
-
result += `:${this.name}`;
|
|
1238
|
-
this.ntemp++;
|
|
1239
|
-
return result;
|
|
1240
|
-
}
|
|
1241
|
-
addCachedOperation(op, result, left, right) {
|
|
1242
|
-
if (!left.name || !result.name || (right && !right.name))
|
|
1243
|
-
return;
|
|
1244
|
-
const id = formatCacheId(op, left, right);
|
|
1245
|
-
this.operationCache[id] = result;
|
|
1246
|
-
this.clearDependentCache(result);
|
|
1247
|
-
addCacheDependency(this, left.name, id);
|
|
1248
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1249
|
-
if (right)
|
|
1250
|
-
addCacheDependency(this, right.name, id);
|
|
1251
|
-
addCacheDependency(this, result.name, id);
|
|
1252
|
-
}
|
|
1253
|
-
getCachedOperation(op, left, right) {
|
|
1254
|
-
const id = formatCacheId(op, left, right);
|
|
1255
|
-
const result = this.operationCache[id];
|
|
1256
|
-
if (result)
|
|
1257
|
-
return result;
|
|
1258
|
-
if (this.parent)
|
|
1259
|
-
return this.parent.getCachedOperation(op, left, right);
|
|
1260
|
-
}
|
|
1261
|
-
clearDependentCache(value) {
|
|
1262
|
-
if (!value.name)
|
|
1263
|
-
return;
|
|
1264
|
-
const dependents = this.cacheDependencies[value.name];
|
|
1265
|
-
if (dependents) {
|
|
1266
|
-
for (const id of dependents) {
|
|
1267
|
-
delete this.operationCache[id];
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
if (this.parent)
|
|
1271
|
-
this.parent.clearDependentCache(value);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
function formatCacheId(op, left, right) {
|
|
1275
|
-
if (right)
|
|
1276
|
-
return `{${op}}{${left.name}}{${right.name}}`;
|
|
1277
|
-
return `{${op}}{${left.name}}`;
|
|
1278
|
-
}
|
|
1279
|
-
function addCacheDependency(scope, name, id) {
|
|
1280
|
-
(scope.cacheDependencies[name] ??= []).push(id);
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
const Program = (c, scope, node) => {
|
|
1284
|
-
return c.handleMany(scope ?? new Scope({}), node.body);
|
|
1285
|
-
};
|
|
1286
|
-
|
|
1287
|
-
const VariableDeclaration = (c, scope, node) => {
|
|
1288
|
-
return c.handleMany(scope, node.declarations, child => VariableDeclarator(c, scope, child, undefined, node.kind));
|
|
1289
|
-
};
|
|
1290
|
-
const VariableDeclarator = (c, scope, node, out, kind = "let") => {
|
|
1291
|
-
const { init } = node;
|
|
1292
|
-
const [value, inst] = Declare(c, scope, node.id, kind);
|
|
1293
|
-
if (init) {
|
|
1294
|
-
const [result, resultInst] = c.handleEval(scope, init, value.out);
|
|
1295
|
-
pipeInsts(value.handler(result, resultInst), inst);
|
|
1296
|
-
}
|
|
1297
|
-
else {
|
|
1298
|
-
pipeInsts(value.handler(null, []), inst);
|
|
1299
|
-
}
|
|
1300
|
-
return [null, inst];
|
|
1301
|
-
};
|
|
1302
|
-
const Declare = (c, scope, node, kind) => {
|
|
1303
|
-
return c.handle(scope, node, () => {
|
|
1304
|
-
switch (node.type) {
|
|
1305
|
-
case "Identifier":
|
|
1306
|
-
return DeclareIdentifier(c, scope, node, kind);
|
|
1307
|
-
case "ArrayPattern":
|
|
1308
|
-
return DeclareArrayPattern(c, scope, node, kind);
|
|
1309
|
-
case "ObjectPattern":
|
|
1310
|
-
return DeclareObjectPattern(c, scope, node, kind);
|
|
1311
|
-
case "AssignmentPattern":
|
|
1312
|
-
return DeclareAssignmentPattern(c, scope, node, kind);
|
|
1313
|
-
default:
|
|
1314
|
-
throw new CompilerError(`Unsupported declaration type: ${node.type}`, node);
|
|
1315
|
-
}
|
|
1316
|
-
});
|
|
1317
|
-
};
|
|
1318
|
-
const DeclareIdentifier = (c, scope, node, kind) => {
|
|
1319
|
-
const { name: identifier } = node;
|
|
1320
|
-
const name = nodeName(node, !c.compactNames && identifier);
|
|
1321
|
-
const out = new SenseableValue(name, kind === "const" ? EMutability.init : EMutability.mutable);
|
|
1322
|
-
const declarationValue = new DeclarationValue({
|
|
1323
|
-
c,
|
|
1324
|
-
scope,
|
|
1325
|
-
node,
|
|
1326
|
-
out,
|
|
1327
|
-
handler(init, inst) {
|
|
1328
|
-
if (kind === "const" && !init)
|
|
1329
|
-
throw new CompilerError("Constants must be initialized.", node);
|
|
1330
|
-
if (kind === "const" && init?.mutability === EMutability.constant) {
|
|
1331
|
-
scope.set(identifier, init);
|
|
1332
|
-
return [null, inst];
|
|
1333
|
-
}
|
|
1334
|
-
else {
|
|
1335
|
-
const value = scope.make(identifier, name);
|
|
1336
|
-
if (init) {
|
|
1337
|
-
if (init.macro)
|
|
1338
|
-
throw new CompilerError("Macro values must be held by constants", node);
|
|
1339
|
-
pipeInsts(value["="](scope, init), inst);
|
|
1340
|
-
}
|
|
1341
|
-
if (kind === "const")
|
|
1342
|
-
value.mutability = EMutability.constant;
|
|
1343
|
-
return [null, inst];
|
|
1344
|
-
}
|
|
1345
|
-
},
|
|
1346
|
-
});
|
|
1347
|
-
return [declarationValue, []];
|
|
1348
|
-
};
|
|
1349
|
-
const DeclareArrayPattern = (c, scope, node, kind) => {
|
|
1350
|
-
const members = new Map();
|
|
1351
|
-
const { elements } = node;
|
|
1352
|
-
const inst = [];
|
|
1353
|
-
for (let i = 0; i < elements.length; i++) {
|
|
1354
|
-
const element = elements[i];
|
|
1355
|
-
if (!element)
|
|
1356
|
-
continue;
|
|
1357
|
-
const value = pipeInsts(Declare(c, scope, element, kind), inst);
|
|
1358
|
-
members.set(new LiteralValue(i), {
|
|
1359
|
-
value: value.out,
|
|
1360
|
-
handler(get, propExists, scope) {
|
|
1361
|
-
const [init, inst] = c.handle(scope, element, () => {
|
|
1362
|
-
let [init, initInst] = propExists()
|
|
1363
|
-
? get()
|
|
1364
|
-
: [value.defaultInit ? new LiteralValue(null) : null, []];
|
|
1365
|
-
if (!init)
|
|
1366
|
-
throw new CompilerError(`The target object does not have a value at index ${i}`, element);
|
|
1367
|
-
const getDefault = value.defaultInit;
|
|
1368
|
-
if (getDefault) {
|
|
1369
|
-
init = pipeInsts(init["??"](scope, new LazyValue(() => getDefault()), value.out), initInst);
|
|
1370
|
-
}
|
|
1371
|
-
return [init, initInst];
|
|
1372
|
-
});
|
|
1373
|
-
return value.handler(init, inst);
|
|
1374
|
-
},
|
|
1375
|
-
});
|
|
1376
|
-
}
|
|
1377
|
-
const out = new DestructuringValue(members);
|
|
1378
|
-
const value = new DeclarationValue({
|
|
1379
|
-
c,
|
|
1380
|
-
scope,
|
|
1381
|
-
node,
|
|
1382
|
-
out,
|
|
1383
|
-
handler(init, initInst) {
|
|
1384
|
-
if (!(init instanceof ObjectValue))
|
|
1385
|
-
throw new CompilerError("The value being destructured must be an object value");
|
|
1386
|
-
pipeInsts(out["="](scope, init), initInst);
|
|
1387
|
-
return [null, initInst];
|
|
1388
|
-
},
|
|
1389
|
-
});
|
|
1390
|
-
return [value, inst];
|
|
1391
|
-
};
|
|
1392
|
-
const DeclareObjectPattern = (c, scope, node, kind) => {
|
|
1393
|
-
const members = new Map();
|
|
1394
|
-
const { properties } = node;
|
|
1395
|
-
const propertiesInst = c.handleMany(scope, properties, prop => {
|
|
1396
|
-
if (prop.type === "RestElement")
|
|
1397
|
-
throw new CompilerError("The rest operator is not supported", prop);
|
|
1398
|
-
const { key, value: propValue } = prop;
|
|
1399
|
-
const keyInst = key.type === "Identifier" && !prop.computed
|
|
1400
|
-
? [new LiteralValue(key.name), []]
|
|
1401
|
-
: c.handleEval(scope, prop.key);
|
|
1402
|
-
const [value, valueInst] = Declare(c, scope, propValue, kind);
|
|
1403
|
-
members.set(keyInst[0], {
|
|
1404
|
-
value: value.out,
|
|
1405
|
-
handler(get, propExists, scope) {
|
|
1406
|
-
const [init, inst] = c.handle(scope, prop, () => {
|
|
1407
|
-
let [init, initInst] = propExists() || !value.defaultInit
|
|
1408
|
-
? get()
|
|
1409
|
-
: [new LiteralValue(null), []];
|
|
1410
|
-
const getDefault = value.defaultInit;
|
|
1411
|
-
if (getDefault) {
|
|
1412
|
-
init = pipeInsts(init["??"](scope, new LazyValue(() => getDefault()), value.out), initInst);
|
|
1413
|
-
}
|
|
1414
|
-
return [init, [...keyInst[1], ...initInst]];
|
|
1415
|
-
});
|
|
1416
|
-
return value.handler(init, inst);
|
|
1417
|
-
},
|
|
1418
|
-
});
|
|
1419
|
-
return [null, valueInst];
|
|
1420
|
-
});
|
|
1421
|
-
const out = new DestructuringValue(members);
|
|
1422
|
-
const declarationValue = new DeclarationValue({
|
|
1423
|
-
c,
|
|
1424
|
-
scope,
|
|
1425
|
-
node,
|
|
1426
|
-
out,
|
|
1427
|
-
handler(init, initInst) {
|
|
1428
|
-
if (!init)
|
|
1429
|
-
throw new CompilerError("Cannot use object destructuring without an initializer", node);
|
|
1430
|
-
pipeInsts(out["="](scope, init), initInst);
|
|
1431
|
-
return [null, initInst];
|
|
1432
|
-
},
|
|
1433
|
-
});
|
|
1434
|
-
return [declarationValue, propertiesInst[1]];
|
|
1435
|
-
};
|
|
1436
|
-
const DeclareAssignmentPattern = (c, scope, node, kind) => {
|
|
1437
|
-
const [value, inst] = Declare(c, scope, node.left, kind);
|
|
1438
|
-
value.defaultInit = () => c.handleEval(scope, node.right, value.out);
|
|
1439
|
-
return [value, inst];
|
|
1440
|
-
};
|
|
1441
|
-
class DeclarationValue extends VoidValue {
|
|
1442
|
-
constructor({ c, scope, node, out, handler, }) {
|
|
1443
|
-
super();
|
|
1444
|
-
this.out = out;
|
|
1445
|
-
this.c = c;
|
|
1446
|
-
this.scope = scope;
|
|
1447
|
-
this.node = node;
|
|
1448
|
-
this.handle = handler;
|
|
1449
|
-
}
|
|
1450
|
-
handler(init, inst) {
|
|
1451
|
-
return this.c.handle(this.scope, this.node, () => this.handle(init, inst));
|
|
1452
|
-
}
|
|
1453
|
-
debugString() {
|
|
1454
|
-
return "DeclarationValue";
|
|
1455
|
-
}
|
|
1456
|
-
toString() {
|
|
1457
|
-
return '"[macro DeclarationValue]"';
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
const Literal = (_c, scope, node) => [new LiteralValue(node.value), []];
|
|
1462
|
-
const NumericLiteral = Literal;
|
|
1463
|
-
const StringLiteral = Literal;
|
|
1464
|
-
const NullLiteral = () => {
|
|
1465
|
-
throw new CompilerError("`null` is no longer supported, use `undefined` instead");
|
|
1466
|
-
};
|
|
1467
|
-
const BooleanLiteral = (_c, scope, node) => [new LiteralValue(+node.value), []];
|
|
1468
|
-
|
|
1469
|
-
const LRExpression = (c, scope, node, out) => {
|
|
1470
|
-
const [left, leftInst] = c.handleEval(scope, node.left);
|
|
1471
|
-
const [right, rightInst] = c.handleEval(scope, node.right);
|
|
1472
|
-
if (!(left instanceof LiteralValue) || !(right instanceof LiteralValue)) {
|
|
1473
|
-
const cachedResult = scope.getCachedOperation(node.operator, left, right);
|
|
1474
|
-
if (cachedResult)
|
|
1475
|
-
return [cachedResult, [...leftInst, ...rightInst]];
|
|
1476
|
-
// because a * b is the same as b * a
|
|
1477
|
-
if (orderIndependentOperators.includes(node.operator)) {
|
|
1478
|
-
const cachedResult = scope.getCachedOperation(node.operator, right, left);
|
|
1479
|
-
if (cachedResult)
|
|
1480
|
-
return [cachedResult, [...leftInst, ...rightInst]];
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
const [op, opInst] = left[node.operator](scope, right, out);
|
|
1484
|
-
if (!(op instanceof LiteralValue)) {
|
|
1485
|
-
scope.addCachedOperation(node.operator, op, left, right);
|
|
1486
|
-
}
|
|
1487
|
-
return [op, [...leftInst, ...rightInst, ...opInst]];
|
|
1488
|
-
};
|
|
1489
|
-
const BinaryExpression = LRExpression;
|
|
1490
|
-
const LogicalExpression = (c, scope, node, out) => {
|
|
1491
|
-
if (node.operator !== "??")
|
|
1492
|
-
return LRExpression(c, scope, node, out);
|
|
1493
|
-
const other = new LazyValue((scope, out) => c.handleEval(scope, node.right, out));
|
|
1494
|
-
const [left, leftInst] = c.handleValue(scope, node.left);
|
|
1495
|
-
const [result, resultInst] = left["??"](scope, other, out);
|
|
1496
|
-
return [result, [...leftInst, ...resultInst]];
|
|
1497
|
-
};
|
|
1498
|
-
const AssignmentExpression = (c, scope, node) => {
|
|
1499
|
-
const [left, leftInst] = c.handleValue(scope, node.left);
|
|
1500
|
-
const leftOutput = left.toOut();
|
|
1501
|
-
const [right, rightInst] = node.operator !== "??="
|
|
1502
|
-
? c.handleEval(scope, node.right, node.operator === "=" ? leftOutput : undefined)
|
|
1503
|
-
: [
|
|
1504
|
-
new LazyValue((scope, out) => c.handleEval(scope, node.right, out)),
|
|
1505
|
-
[],
|
|
1506
|
-
];
|
|
1507
|
-
const [op, opInst] = left[node.operator](scope, right);
|
|
1508
|
-
scope.clearDependentCache(left);
|
|
1509
|
-
scope.clearDependentCache(leftOutput);
|
|
1510
|
-
return [op, [...leftInst, ...rightInst, ...opInst]];
|
|
1511
|
-
};
|
|
1512
|
-
const UnaryExpression = (c, scope, { argument, operator }, out) => {
|
|
1513
|
-
const [arg, argInst] = c.handleEval(scope, argument);
|
|
1514
|
-
const cachedResult = scope.getCachedOperation(operator, arg);
|
|
1515
|
-
if (cachedResult)
|
|
1516
|
-
return [cachedResult, argInst];
|
|
1517
|
-
const operatorId = operator == "+" || operator == "-" ? `u${operator}` : operator;
|
|
1518
|
-
if (operatorId === "throw")
|
|
1519
|
-
throw new CompilerError("throw operator is not supported");
|
|
1520
|
-
const [op, opInst] = arg[operatorId](scope, out);
|
|
1521
|
-
scope.addCachedOperation(operator, op, arg);
|
|
1522
|
-
return [op, [...argInst, ...opInst]];
|
|
1523
|
-
};
|
|
1524
|
-
const UpdateExpression = (c, scope, { argument, operator, prefix }, out) => {
|
|
1525
|
-
const [arg, argInst] = c.handleValue(scope, argument);
|
|
1526
|
-
scope.clearDependentCache(arg);
|
|
1527
|
-
const [op, opInst] = arg[operator](scope, prefix, out);
|
|
1528
|
-
return [op, [...argInst, ...opInst]];
|
|
1529
|
-
};
|
|
1530
|
-
const ConditionalExpression = (c, scope, node, out) => {
|
|
1531
|
-
const alternateStartAdress = new LiteralValue(null);
|
|
1532
|
-
const endExpressionAdress = new LiteralValue(null);
|
|
1533
|
-
const testOut = new JumpOutValue(node, alternateStartAdress, false);
|
|
1534
|
-
const [test, testInst] = c.handleEval(scope, node.test, testOut);
|
|
1535
|
-
if (test instanceof LiteralValue) {
|
|
1536
|
-
if (test.data)
|
|
1537
|
-
return c.handleEval(scope, node.consequent, out);
|
|
1538
|
-
return c.handleEval(scope, node.alternate, out);
|
|
1539
|
-
}
|
|
1540
|
-
const result = SenseableValue.from(scope, out, EMutability.mutable);
|
|
1541
|
-
const consequent = c.handleEval(scope, node.consequent, result);
|
|
1542
|
-
const alternate = c.handleEval(scope, node.alternate, result);
|
|
1543
|
-
return [
|
|
1544
|
-
result,
|
|
1545
|
-
[
|
|
1546
|
-
...testInst,
|
|
1547
|
-
...JumpInstruction.or(test, testOut),
|
|
1548
|
-
...consequent[1],
|
|
1549
|
-
...result["="](scope, consequent[0])[1],
|
|
1550
|
-
new JumpInstruction(endExpressionAdress, EJumpKind.Always),
|
|
1551
|
-
new AddressResolver(alternateStartAdress),
|
|
1552
|
-
...alternate[1],
|
|
1553
|
-
...result["="](scope, alternate[0])[1],
|
|
1554
|
-
new AddressResolver(endExpressionAdress),
|
|
1555
|
-
],
|
|
1556
|
-
];
|
|
1557
|
-
};
|
|
1558
|
-
const SequenceExpression = (c, scope, node, out) => {
|
|
1559
|
-
const { expressions } = node;
|
|
1560
|
-
const inst = [];
|
|
1561
|
-
// compute every expression except the last one
|
|
1562
|
-
for (let i = 0; i < expressions.length - 1; i++) {
|
|
1563
|
-
pipeInsts(c.handleEval(scope, expressions[i], discardedName), inst);
|
|
1564
|
-
}
|
|
1565
|
-
const value = pipeInsts(c.handleEval(scope, expressions[expressions.length - 1], out), inst);
|
|
1566
|
-
return [value, inst];
|
|
1567
|
-
};
|
|
1568
|
-
|
|
1569
|
-
const Identifier = (c, scope, node) => {
|
|
1570
|
-
return [scope.get(node.name), []];
|
|
1571
|
-
};
|
|
1572
|
-
|
|
1573
|
-
const ExpressionStatement = (c, scope, node) => {
|
|
1574
|
-
return c.handle(scope, node.expression, undefined, discardedName);
|
|
1575
|
-
};
|
|
1576
|
-
const BreakStatement = (_, scope, node) => {
|
|
1577
|
-
const label = node.label?.name;
|
|
1578
|
-
const target = label ? findScopeLabel(scope, label) : scope;
|
|
1579
|
-
const addr = new LiteralValue(null);
|
|
1580
|
-
target.break.bind(addr);
|
|
1581
|
-
return [null, [new BreakInstruction(addr)]];
|
|
1582
|
-
};
|
|
1583
|
-
const ContinueStatement = (_, scope, node) => {
|
|
1584
|
-
const addr = new LiteralValue(null);
|
|
1585
|
-
const label = node.label?.name;
|
|
1586
|
-
const target = label ? findScopeLabel(scope, label) : scope;
|
|
1587
|
-
target.continue.bind(addr);
|
|
1588
|
-
return [null, [new ContinueInstruction(addr)]];
|
|
1589
|
-
};
|
|
1590
|
-
const ReturnStatement = (c, scope, node, out) => {
|
|
1591
|
-
const { argument } = node;
|
|
1592
|
-
const [arg, argInst] = argument
|
|
1593
|
-
? [new LazyValue((scope, out) => c.handleEval(scope, argument, out)), []]
|
|
1594
|
-
: [new LiteralValue(null), []];
|
|
1595
|
-
const [ret, retInst] = scope.function.return(scope, arg, out);
|
|
1596
|
-
return [ret, [...argInst, ...retInst]];
|
|
1597
|
-
};
|
|
1598
|
-
const EmptyStatement = () => [null, []];
|
|
1599
|
-
const LabeledStatement = (c, scope, node) => {
|
|
1600
|
-
const inner = scope.createScope();
|
|
1601
|
-
inner.label = node.label.name;
|
|
1602
|
-
const end = new LiteralValue(null);
|
|
1603
|
-
const endAdress = new AddressResolver(end).bindBreak(inner);
|
|
1604
|
-
const [, bodyInst] = c.handle(inner, node.body);
|
|
1605
|
-
if (usesAddressResolver(endAdress, bodyInst))
|
|
1606
|
-
bodyInst.push(endAdress);
|
|
1607
|
-
return [null, bodyInst];
|
|
1608
|
-
};
|
|
1609
|
-
function findScopeLabel(scope, label) {
|
|
1610
|
-
let current = scope;
|
|
1611
|
-
while (current !== null) {
|
|
1612
|
-
if (current.label === label)
|
|
1613
|
-
return current;
|
|
1614
|
-
current = current.parent;
|
|
1615
|
-
}
|
|
1616
|
-
throw new CompilerError(`Could not find label "${label}"`);
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
const IfStatement = (c, scope, node) => {
|
|
1620
|
-
const inst = [];
|
|
1621
|
-
const endIfAddr = new LiteralValue(null);
|
|
1622
|
-
const testOut = createJumpOut(c, scope, node, endIfAddr);
|
|
1623
|
-
const mergedJumps = testOut.whenTrue;
|
|
1624
|
-
const test = pipeInsts(c.handleEval(scope, node.test, testOut), inst);
|
|
1625
|
-
if (test instanceof LiteralValue) {
|
|
1626
|
-
if (test.data)
|
|
1627
|
-
return [null, c.handle(scope, node.consequent)[1]];
|
|
1628
|
-
else if (node.alternate)
|
|
1629
|
-
return [null, c.handle(scope, node.alternate)[1]];
|
|
1630
|
-
return [null, []];
|
|
1631
|
-
}
|
|
1632
|
-
const consequent = !mergedJumps
|
|
1633
|
-
? withAlwaysRuns(c.handle(scope, node.consequent), false)
|
|
1634
|
-
: [null, []];
|
|
1635
|
-
inst.push(...JumpInstruction.or(test, testOut), ...consequent[1]);
|
|
1636
|
-
if (!node.alternate) {
|
|
1637
|
-
inst.push(new AddressResolver(endIfAddr));
|
|
1638
|
-
}
|
|
1639
|
-
else {
|
|
1640
|
-
const endElseAddr = new LiteralValue(null);
|
|
1641
|
-
const needsJump = !endsWithControlFlow(consequent[1]) && !mergedJumps;
|
|
1642
|
-
if (needsJump)
|
|
1643
|
-
inst.push(new JumpInstruction(endElseAddr, EJumpKind.Always));
|
|
1644
|
-
inst.push(new AddressResolver(endIfAddr), ...withAlwaysRuns(c.handle(scope, node.alternate), false)[1]);
|
|
1645
|
-
if (needsJump)
|
|
1646
|
-
inst.push(new AddressResolver(endElseAddr));
|
|
1647
|
-
}
|
|
1648
|
-
return [null, inst];
|
|
1649
|
-
};
|
|
1650
|
-
/**
|
|
1651
|
-
* Returns `true` if the instruction array ends with an instruction
|
|
1652
|
-
* that already changes the control flow of the script (like `jump x always`, `end` , `stop`, etc)
|
|
1653
|
-
*/
|
|
1654
|
-
function endsWithControlFlow(inst) {
|
|
1655
|
-
for (let i = inst.length - 1; i >= 0; i--) {
|
|
1656
|
-
const instruction = inst[i];
|
|
1657
|
-
if (instruction instanceof AddressResolver)
|
|
1658
|
-
return false;
|
|
1659
|
-
if (instruction.hidden)
|
|
1660
|
-
continue;
|
|
1661
|
-
return instruction.intent !== EInstIntent.none;
|
|
1662
|
-
}
|
|
1663
|
-
return false;
|
|
1664
|
-
}
|
|
1665
|
-
function createJumpOut(c, scope, node, endIfAddr) {
|
|
1666
|
-
const jumpNode = asMergeableJumpNode(node.consequent);
|
|
1667
|
-
if (!jumpNode)
|
|
1668
|
-
return new JumpOutValue(node, endIfAddr, false);
|
|
1669
|
-
const jump = c.handle(scope, jumpNode)[1][0];
|
|
1670
|
-
return new JumpOutValue(node, jump.address, true);
|
|
1671
|
-
}
|
|
1672
|
-
function asMergeableJumpNode(body) {
|
|
1673
|
-
if (body.type === "ContinueStatement" || body.type === "BreakStatement")
|
|
1674
|
-
return body;
|
|
1675
|
-
if (body.type !== "BlockStatement")
|
|
1676
|
-
return;
|
|
1677
|
-
if (body.body.length == 0)
|
|
1678
|
-
return;
|
|
1679
|
-
return asMergeableJumpNode(body.body[0]);
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
const SwitchStatement = (c, scope, node) => {
|
|
1683
|
-
const innerScope = scope.createScope();
|
|
1684
|
-
const [ref, refInst] = c.handleEval(scope, node.discriminant);
|
|
1685
|
-
const inst = [];
|
|
1686
|
-
const endAdress = new LiteralValue(null);
|
|
1687
|
-
const endLine = new AddressResolver(endAdress).bindBreak(innerScope);
|
|
1688
|
-
const caseJumps = [];
|
|
1689
|
-
let defaultJump;
|
|
1690
|
-
// if there is a case that is guaranteed to run
|
|
1691
|
-
let constantCase = false;
|
|
1692
|
-
let onlyConstantTests = true;
|
|
1693
|
-
for (const scase of node.cases) {
|
|
1694
|
-
const bodyInst = lazy(() => c.handleMany(createCaseScope(innerScope), scase.consequent)[1]);
|
|
1695
|
-
const bodyAdress = new LiteralValue(null);
|
|
1696
|
-
const bodyLine = new AddressResolver(bodyAdress);
|
|
1697
|
-
const canFallInto = !endsWithoutFalltrough(inst, endLine);
|
|
1698
|
-
let includeJump = !constantCase;
|
|
1699
|
-
const includeBody = !constantCase || canFallInto;
|
|
1700
|
-
if (!scase.test) {
|
|
1701
|
-
if (includeJump) {
|
|
1702
|
-
[defaultJump] = c.handle(innerScope, scase, () => [
|
|
1703
|
-
null,
|
|
1704
|
-
[new JumpInstruction(bodyAdress, EJumpKind.Always)],
|
|
1705
|
-
])[1];
|
|
1706
|
-
}
|
|
1707
|
-
if (includeBody)
|
|
1708
|
-
inst.push(bodyLine, ...bodyInst());
|
|
1709
|
-
continue;
|
|
1710
|
-
}
|
|
1711
|
-
const [test, testInst] = c.handleEval(innerScope, scase.test);
|
|
1712
|
-
if (onlyConstantTests)
|
|
1713
|
-
onlyConstantTests = test.mutability === EMutability.constant;
|
|
1714
|
-
let jump;
|
|
1715
|
-
// check if it can be evaluated at compile time
|
|
1716
|
-
// the scope copy prevents unecessary increases in the temp counter
|
|
1717
|
-
const [comp] = ref["=="](innerScope.copy(), test);
|
|
1718
|
-
if (comp instanceof LiteralValue) {
|
|
1719
|
-
// whether other cases can falltrough and reach this one
|
|
1720
|
-
const noFallInto = !canFallInto;
|
|
1721
|
-
const isLastCase = scase === node.cases[node.cases.length - 1];
|
|
1722
|
-
if (!comp.data) {
|
|
1723
|
-
// this case is not accessible in any way
|
|
1724
|
-
if (noFallInto)
|
|
1725
|
-
continue;
|
|
1726
|
-
includeJump = false;
|
|
1727
|
-
}
|
|
1728
|
-
else {
|
|
1729
|
-
// this case is only accessible via the comparison jump
|
|
1730
|
-
// and all the other cases will never run
|
|
1731
|
-
if (noFallInto &&
|
|
1732
|
-
onlyConstantTests &&
|
|
1733
|
-
(isLastCase || endsWithoutFalltrough(bodyInst(), endLine))) {
|
|
1734
|
-
return [null, [...bodyInst(), endLine]];
|
|
1735
|
-
}
|
|
1736
|
-
jump = c.handle(innerScope, scase, () => [
|
|
1737
|
-
null,
|
|
1738
|
-
[new JumpInstruction(bodyAdress, EJumpKind.Always)],
|
|
1739
|
-
])[1];
|
|
1740
|
-
constantCase = true;
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
// makes sourcemapping for the jump more specific
|
|
1744
|
-
jump ??= c.handle(innerScope, scase, () => [
|
|
1745
|
-
null,
|
|
1746
|
-
[new JumpInstruction(bodyAdress, EJumpKind.StrictEqual, ref, test)],
|
|
1747
|
-
])[1];
|
|
1748
|
-
if (includeJump)
|
|
1749
|
-
caseJumps.push(...testInst, ...jump);
|
|
1750
|
-
if (includeBody)
|
|
1751
|
-
inst.push(bodyLine, ...bodyInst());
|
|
1752
|
-
}
|
|
1753
|
-
// ensures that the processor exits
|
|
1754
|
-
// the switch if no cases match
|
|
1755
|
-
defaultJump ??= new JumpInstruction(endAdress, EJumpKind.Always);
|
|
1756
|
-
if (caseJumps.length > 0) {
|
|
1757
|
-
withAlwaysRuns([null, [...caseJumps.slice(1), defaultJump]], false);
|
|
1758
|
-
withAlwaysRuns([null, inst], false);
|
|
1759
|
-
}
|
|
1760
|
-
return [
|
|
1761
|
-
null,
|
|
1762
|
-
[
|
|
1763
|
-
...refInst,
|
|
1764
|
-
...caseJumps,
|
|
1765
|
-
...(caseJumps.length > 0 && !constantCase ? [defaultJump] : []),
|
|
1766
|
-
...inst,
|
|
1767
|
-
...(usesEndLine(endLine, inst, defaultJump) ? [endLine] : []),
|
|
1768
|
-
],
|
|
1769
|
-
];
|
|
1770
|
-
};
|
|
1771
|
-
function endsWithoutFalltrough(inst, endLine) {
|
|
1772
|
-
if (inst.length === 0)
|
|
1773
|
-
return true;
|
|
1774
|
-
for (let i = inst.length - 1; i >= 0; i--) {
|
|
1775
|
-
const instruction = inst[i];
|
|
1776
|
-
// this means that there is an instruction trying to reference
|
|
1777
|
-
// the final function `set @counter` instruction
|
|
1778
|
-
// of course this generates an unecessary
|
|
1779
|
-
// `set @counter` in cases where a control flow
|
|
1780
|
-
// structure contains a fallback return statement
|
|
1781
|
-
// TODO: fix this with static analysis
|
|
1782
|
-
if (instruction instanceof AddressResolver)
|
|
1783
|
-
return false;
|
|
1784
|
-
if (instruction.hidden)
|
|
1785
|
-
continue;
|
|
1786
|
-
return (instruction instanceof SetCounterInstruction ||
|
|
1787
|
-
instruction.intent === EInstIntent.return ||
|
|
1788
|
-
(instruction instanceof BreakInstruction &&
|
|
1789
|
-
endLine.bonds.includes(instruction.address)));
|
|
1790
|
-
}
|
|
1791
|
-
return false;
|
|
1792
|
-
}
|
|
1793
|
-
function usesEndLine(endLine, inst, defaultJump) {
|
|
1794
|
-
if (endLine.bonds.includes(defaultJump.address))
|
|
1795
|
-
return true;
|
|
1796
|
-
return usesAddressResolver(endLine, inst);
|
|
1797
|
-
}
|
|
1798
|
-
function createCaseScope(scope) {
|
|
1799
|
-
// prevents the operation cache from propagating
|
|
1800
|
-
// but also allows the case to declare variables
|
|
1801
|
-
// that can be used by other cases
|
|
1802
|
-
const child = scope.createScope();
|
|
1803
|
-
child.data = scope.data;
|
|
1804
|
-
return child;
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
const WhileStatement = (c, scope, node) => {
|
|
1808
|
-
const startLoopAddr = new LiteralValue(null);
|
|
1809
|
-
const endLoopAddr = new LiteralValue(null);
|
|
1810
|
-
const hasEmptyBody = isEmptyBody(node.body);
|
|
1811
|
-
const testOut = hasEmptyBody
|
|
1812
|
-
? new JumpOutValue(node, startLoopAddr, true)
|
|
1813
|
-
: new JumpOutValue(node, endLoopAddr, false);
|
|
1814
|
-
const [test, testLines] = c.handleEval(scope, node.test, testOut);
|
|
1815
|
-
const childScope = scope.createScope();
|
|
1816
|
-
const startLoopLine = new AddressResolver(startLoopAddr).bindContinue(childScope);
|
|
1817
|
-
const endLoopLine = new AddressResolver(endLoopAddr).bindBreak(childScope);
|
|
1818
|
-
if (scope.label) {
|
|
1819
|
-
startLoopLine.bindContinue(scope);
|
|
1820
|
-
}
|
|
1821
|
-
if (test instanceof LiteralValue) {
|
|
1822
|
-
if (!test.data)
|
|
1823
|
-
return [null, []];
|
|
1824
|
-
const bodyInst = c.handle(childScope, node.body)[1];
|
|
1825
|
-
return [
|
|
1826
|
-
null,
|
|
1827
|
-
[
|
|
1828
|
-
startLoopLine,
|
|
1829
|
-
...bodyInst,
|
|
1830
|
-
new JumpInstruction(startLoopAddr, EJumpKind.Always),
|
|
1831
|
-
...(usesAddressResolver(endLoopLine, bodyInst) ? [endLoopLine] : []),
|
|
1832
|
-
],
|
|
1833
|
-
];
|
|
1834
|
-
}
|
|
1835
|
-
return [
|
|
1836
|
-
null,
|
|
1837
|
-
[
|
|
1838
|
-
startLoopLine,
|
|
1839
|
-
...testLines,
|
|
1840
|
-
...JumpInstruction.or(test, testOut),
|
|
1841
|
-
...withAlwaysRuns(c.handle(childScope, node.body), false)[1],
|
|
1842
|
-
...(hasEmptyBody
|
|
1843
|
-
? []
|
|
1844
|
-
: [new JumpInstruction(startLoopAddr, EJumpKind.Always), endLoopLine]),
|
|
1845
|
-
],
|
|
1846
|
-
];
|
|
1847
|
-
};
|
|
1848
|
-
const DoWhileStatement = (c, scope, node) => {
|
|
1849
|
-
const startLoopAddr = new LiteralValue(null);
|
|
1850
|
-
const endLoopAddr = new LiteralValue(null);
|
|
1851
|
-
const testOut = new JumpOutValue(node, startLoopAddr, true);
|
|
1852
|
-
const [test, testLines] = c.handleEval(scope, node.test, testOut);
|
|
1853
|
-
const childScope = scope.createScope();
|
|
1854
|
-
const startLoopLine = new AddressResolver(startLoopAddr).bindContinue(childScope);
|
|
1855
|
-
const endLoopLine = new AddressResolver(endLoopAddr).bindBreak(childScope);
|
|
1856
|
-
if (scope.label) {
|
|
1857
|
-
startLoopLine.bindContinue(scope);
|
|
1858
|
-
}
|
|
1859
|
-
const [, bodyLines] = c.handle(childScope, node.body);
|
|
1860
|
-
if (test instanceof LiteralValue) {
|
|
1861
|
-
if (!test.data)
|
|
1862
|
-
return [null, bodyLines];
|
|
1863
|
-
return [
|
|
1864
|
-
null,
|
|
1865
|
-
[
|
|
1866
|
-
startLoopLine,
|
|
1867
|
-
...bodyLines,
|
|
1868
|
-
new JumpInstruction(startLoopAddr, EJumpKind.Always),
|
|
1869
|
-
...(usesAddressResolver(endLoopLine, bodyLines) ? [endLoopLine] : []),
|
|
1870
|
-
],
|
|
1871
|
-
];
|
|
1872
|
-
}
|
|
1873
|
-
return [
|
|
1874
|
-
null,
|
|
1875
|
-
[
|
|
1876
|
-
startLoopLine,
|
|
1877
|
-
...bodyLines,
|
|
1878
|
-
...testLines,
|
|
1879
|
-
...JumpInstruction.or(test, testOut),
|
|
1880
|
-
...(usesAddressResolver(endLoopLine, bodyLines) ? [endLoopLine] : []),
|
|
1881
|
-
],
|
|
1882
|
-
];
|
|
1883
|
-
};
|
|
1884
|
-
function isEmptyBody(node) {
|
|
1885
|
-
if (node.type === "EmptyStatement")
|
|
1886
|
-
return true;
|
|
1887
|
-
if (node.type === "BlockStatement")
|
|
1888
|
-
return node.body.length === 0;
|
|
1889
|
-
return false;
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
const ForStatement = (c, parentScope, node) => {
|
|
1893
|
-
const startLoopAddr = new LiteralValue(null);
|
|
1894
|
-
// continue statements jump here to run the loop update lines
|
|
1895
|
-
const beforeEndAddr = new LiteralValue(null);
|
|
1896
|
-
const endLoopAddr = new LiteralValue(null);
|
|
1897
|
-
const scope = parentScope.createScope();
|
|
1898
|
-
const testOut = new JumpOutValue(node, endLoopAddr, false);
|
|
1899
|
-
const initInst = node.init ? c.handle(scope, node.init)[1] : [];
|
|
1900
|
-
const [test, testLines] = node.test
|
|
1901
|
-
? c.handleEval(scope, node.test, testOut)
|
|
1902
|
-
: [new LiteralValue(1), []];
|
|
1903
|
-
const updateLines = node.update
|
|
1904
|
-
? c.handle(scope, node.update, undefined, discardedName)[1]
|
|
1905
|
-
: [];
|
|
1906
|
-
const startLoopLine = new AddressResolver(startLoopAddr);
|
|
1907
|
-
const endLoopLine = new AddressResolver(endLoopAddr).bindBreak(scope);
|
|
1908
|
-
const beforeEndLine = new AddressResolver(beforeEndAddr).bindContinue(scope);
|
|
1909
|
-
if (parentScope.label) {
|
|
1910
|
-
beforeEndLine.bindContinue(parentScope);
|
|
1911
|
-
}
|
|
1912
|
-
if (test instanceof LiteralValue && !test.data) {
|
|
1913
|
-
return [null, [...initInst]];
|
|
1914
|
-
}
|
|
1915
|
-
const isInfiniteLoop = test instanceof LiteralValue;
|
|
1916
|
-
return [
|
|
1917
|
-
null,
|
|
1918
|
-
[
|
|
1919
|
-
...initInst,
|
|
1920
|
-
startLoopLine,
|
|
1921
|
-
...testLines,
|
|
1922
|
-
...(isInfiniteLoop ? [] : JumpInstruction.or(test, testOut)),
|
|
1923
|
-
...withAlwaysRuns(c.handle(scope, node.body), false)[1],
|
|
1924
|
-
beforeEndLine,
|
|
1925
|
-
...updateLines,
|
|
1926
|
-
new JumpInstruction(startLoopAddr, EJumpKind.Always),
|
|
1927
|
-
...(isInfiniteLoop ? [] : [endLoopLine]),
|
|
1928
|
-
],
|
|
1929
|
-
];
|
|
1930
|
-
};
|
|
1931
|
-
|
|
1932
|
-
class FunctionValue extends VoidValue {
|
|
1933
|
-
constructor({ scope, params, body, c, out, }) {
|
|
1934
|
-
super();
|
|
1935
|
-
this.mutability = EMutability.constant;
|
|
1936
|
-
this.macro = true;
|
|
1937
|
-
this.paramValues = [];
|
|
1938
|
-
this.paramNames = new Map();
|
|
1939
|
-
this.destructuringKeyData = new Map();
|
|
1940
|
-
this.paramOuts = [];
|
|
1941
|
-
this.minimumArgumentCount = 0;
|
|
1942
|
-
this.maximumArgumentCount = 0;
|
|
1943
|
-
this.bundled = false;
|
|
1944
|
-
this.initialized = false;
|
|
1945
|
-
this.scope = scope;
|
|
1946
|
-
this.body = body;
|
|
1947
|
-
this.c = c;
|
|
1948
|
-
this.params = params;
|
|
1949
|
-
this.name = extractOutName(out) ?? scope.makeTempName();
|
|
1950
|
-
}
|
|
1951
|
-
typeof() {
|
|
1952
|
-
return [new LiteralValue("function"), []];
|
|
1953
|
-
}
|
|
1954
|
-
initScope() {
|
|
1955
|
-
this.childScope = this.scope.createFunction(this.name);
|
|
1956
|
-
this.childScope.function = this;
|
|
1957
|
-
for (let i = 0; i < this.params.length; i++) {
|
|
1958
|
-
const value = this.unwrapParameter(this.params[i]);
|
|
1959
|
-
this.paramValues.push(value);
|
|
1960
|
-
this.paramOuts.push(value.toOut());
|
|
1961
|
-
if (!(value instanceof AssignmentValue))
|
|
1962
|
-
this.minimumArgumentCount = i + 1;
|
|
1963
|
-
}
|
|
1964
|
-
this.callSize = this.paramValues.length + 2;
|
|
1965
|
-
this.maximumArgumentCount = this.params.length;
|
|
1966
|
-
}
|
|
1967
|
-
ensureInit() {
|
|
1968
|
-
if (this.initialized)
|
|
1969
|
-
return;
|
|
1970
|
-
this.initialized = true;
|
|
1971
|
-
this.initScope();
|
|
1972
|
-
this.addr = new LiteralValue(null);
|
|
1973
|
-
this.temp = new SenseableValue(`${internalPrefix}f${this.name}`, EMutability.mutable);
|
|
1974
|
-
this.ret = new StoreValue(`${internalPrefix}r${this.name}`);
|
|
1975
|
-
this.inst = [
|
|
1976
|
-
new AddressResolver(this.addr),
|
|
1977
|
-
...this.c.handle(this.childScope, this.body)[1],
|
|
1978
|
-
];
|
|
1979
|
-
if (!endsWithReturn(this, this.inst)) {
|
|
1980
|
-
pipeInsts(this.temp["="](this.scope, new LiteralValue(null)), this.inst);
|
|
1981
|
-
this.inst.push(assign(new SetCounterInstruction(this.ret), {
|
|
1982
|
-
intent: EInstIntent.return,
|
|
1983
|
-
intentSource: this,
|
|
1984
|
-
}));
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
normalReturn(scope, arg) {
|
|
1988
|
-
const inst = [];
|
|
1989
|
-
if (arg) {
|
|
1990
|
-
const value = pipeInsts(arg.eval(scope, this.temp), inst);
|
|
1991
|
-
pipeInsts(this.temp["="](scope, value), inst);
|
|
1992
|
-
}
|
|
1993
|
-
const returnInst = assign(new SetCounterInstruction(this.ret), {
|
|
1994
|
-
intent: EInstIntent.return,
|
|
1995
|
-
intentSource: this,
|
|
1996
|
-
});
|
|
1997
|
-
return [null, [...inst, returnInst]];
|
|
1998
|
-
}
|
|
1999
|
-
// TODO: use static analysis to determine if
|
|
2000
|
-
// we can safely skip the temp value assignment
|
|
2001
|
-
normalCall(scope, args, out) {
|
|
2002
|
-
if (!this.bundled)
|
|
2003
|
-
this.childScope.inst.push(...this.inst);
|
|
2004
|
-
this.bundled = true;
|
|
2005
|
-
const callAddressLiteral = new LiteralValue(null);
|
|
2006
|
-
// the value will be stored somewhere
|
|
2007
|
-
// no need to save it in a temporary variable
|
|
2008
|
-
const temp = out
|
|
2009
|
-
? this.temp
|
|
2010
|
-
: SenseableValue.from(scope, undefined, EMutability.mutable);
|
|
2011
|
-
const inst = this.paramValues
|
|
2012
|
-
.map((param, i) => param["="](scope, args[i] ?? new LiteralValue(null))[1])
|
|
2013
|
-
.reduce((s, c) => s.concat(c), [])
|
|
2014
|
-
.concat(...this.ret["="](scope, callAddressLiteral)[1], new JumpInstruction(this.addr, EJumpKind.Always), new AddressResolver(callAddressLiteral),
|
|
2015
|
-
// ensures that functions can be called multiple times inside expressions
|
|
2016
|
-
...temp["="](scope, this.temp)[1]);
|
|
2017
|
-
return [temp, inst];
|
|
2018
|
-
}
|
|
2019
|
-
inlineReturn(scope, arg) {
|
|
2020
|
-
if (!this.inlineEnd)
|
|
2021
|
-
throw new CompilerError("Error during inline attempt: missing inline end adress");
|
|
2022
|
-
const inst = [];
|
|
2023
|
-
if (arg) {
|
|
2024
|
-
const value = pipeInsts(arg.eval(scope, this.inlineTemp), inst);
|
|
2025
|
-
pipeInsts(this.inlineTemp["="](scope, value), inst);
|
|
2026
|
-
}
|
|
2027
|
-
const jump = assign(new JumpInstruction(this.inlineEnd, EJumpKind.Always), {
|
|
2028
|
-
intent: EInstIntent.return,
|
|
2029
|
-
intentSource: this,
|
|
2030
|
-
});
|
|
2031
|
-
return [null, [...inst, jump]];
|
|
2032
|
-
}
|
|
2033
|
-
inlineCall(scope, args, out) {
|
|
2034
|
-
// create return value
|
|
2035
|
-
this.inlineTemp = SenseableValue.from(this.childScope, out, EMutability.mutable);
|
|
2036
|
-
// make a copy of the function scope
|
|
2037
|
-
const fnScope = this.childScope.copy();
|
|
2038
|
-
this.tryingInline = true;
|
|
2039
|
-
let inst = [];
|
|
2040
|
-
// hard set variables within the function scope
|
|
2041
|
-
for (let i = 0; i < this.paramValues.length; i++) {
|
|
2042
|
-
inst.push(...this.hardSetParameter(fnScope, this.paramValues[i], args[i]));
|
|
2043
|
-
}
|
|
2044
|
-
this.inlineEnd = new LiteralValue(null);
|
|
2045
|
-
inst.push(...this.c.handle(fnScope, this.body)[1]);
|
|
2046
|
-
this.tryingInline = false;
|
|
2047
|
-
const returnIndex = inst.findIndex(i => i.alwaysRuns &&
|
|
2048
|
-
i.intent === EInstIntent.return &&
|
|
2049
|
-
i.intentSource === this);
|
|
2050
|
-
if (returnIndex !== -1)
|
|
2051
|
-
inst = inst.slice(0, returnIndex + 1);
|
|
2052
|
-
inst.push(new AddressResolver(this.inlineEnd));
|
|
2053
|
-
// return the function value
|
|
2054
|
-
return [this.inlineTemp, inst];
|
|
2055
|
-
}
|
|
2056
|
-
call(scope, args, out) {
|
|
2057
|
-
this.ensureInit();
|
|
2058
|
-
const min = this.minimumArgumentCount;
|
|
2059
|
-
const max = this.maximumArgumentCount;
|
|
2060
|
-
if (args.length < min || args.length > max) {
|
|
2061
|
-
if (min !== max)
|
|
2062
|
-
throw new CompilerError(`Cannot call: expected ${min}-${max} arguments but got: ${args.length}`);
|
|
2063
|
-
throw new CompilerError(`Cannot call: expected ${min} arguments but got: ${args.length}`);
|
|
2064
|
-
}
|
|
2065
|
-
validateParameters(args, this.paramValues);
|
|
2066
|
-
const inlineCall = this.inlineCall(scope, args, out);
|
|
2067
|
-
hideRedundantJumps(inlineCall[1]);
|
|
2068
|
-
const inlineSize = inlineCall[1].filter(i => !i.hidden).length;
|
|
2069
|
-
if (this.inline || inlineSize <= this.callSize)
|
|
2070
|
-
return inlineCall;
|
|
2071
|
-
return this.normalCall(scope, args, out);
|
|
2072
|
-
}
|
|
2073
|
-
return(scope, arg) {
|
|
2074
|
-
this.ensureInit();
|
|
2075
|
-
if (arg && arg.macro) {
|
|
2076
|
-
this.inline = true;
|
|
2077
|
-
return [null, []];
|
|
2078
|
-
}
|
|
2079
|
-
if (this.inline || this.tryingInline)
|
|
2080
|
-
return this.inlineReturn(scope, arg);
|
|
2081
|
-
return this.normalReturn(scope, arg);
|
|
2082
|
-
}
|
|
2083
|
-
eval(_scope) {
|
|
2084
|
-
return [this, []];
|
|
2085
|
-
}
|
|
2086
|
-
preCall() {
|
|
2087
|
-
this.ensureInit();
|
|
2088
|
-
if (this.inline || this.tryingInline)
|
|
2089
|
-
return;
|
|
2090
|
-
return this.paramOuts;
|
|
2091
|
-
}
|
|
2092
|
-
debugString() {
|
|
2093
|
-
return `FunctionValue("${this.name}")`;
|
|
2094
|
-
}
|
|
2095
|
-
toString() {
|
|
2096
|
-
return '"[macro FunctionValue]"';
|
|
2097
|
-
}
|
|
2098
|
-
unwrapParameter(param) {
|
|
2099
|
-
switch (param.type) {
|
|
2100
|
-
case "Identifier": {
|
|
2101
|
-
const name = nodeName(param, !this.c.compactNames && param.name);
|
|
2102
|
-
const value = new SenseableValue(name, EMutability.mutable);
|
|
2103
|
-
this.childScope.set(param.name, value);
|
|
2104
|
-
this.paramNames.set(value, param.name);
|
|
2105
|
-
return value;
|
|
2106
|
-
}
|
|
2107
|
-
case "AssignmentPattern": {
|
|
2108
|
-
const left = this.unwrapParameter(param.left);
|
|
2109
|
-
const [rightValue, rightInst] = this.c.handleEval(this.childScope, param.right, left);
|
|
2110
|
-
const right = new LazyValue(scope => {
|
|
2111
|
-
if (this.inline || this.tryingInline) {
|
|
2112
|
-
return this.c.handleEval(scope, param.right, left);
|
|
2113
|
-
}
|
|
2114
|
-
return [rightValue, [...rightInst]];
|
|
2115
|
-
});
|
|
2116
|
-
return new AssignmentValue(left, right);
|
|
2117
|
-
}
|
|
2118
|
-
case "ObjectPattern": {
|
|
2119
|
-
const members = new Map();
|
|
2120
|
-
for (const prop of param.properties) {
|
|
2121
|
-
if (prop.type === "RestElement")
|
|
2122
|
-
throw new CompilerError("Rest parameters are not supported");
|
|
2123
|
-
const { key: propKey, value: propValue } = prop;
|
|
2124
|
-
const propInst = [];
|
|
2125
|
-
const key = propKey.type === "Identifier" && !prop.computed
|
|
2126
|
-
? new LiteralValue(propKey.name)
|
|
2127
|
-
: pipeInsts(this.c.handleEval(this.childScope, propKey), propInst);
|
|
2128
|
-
const value = this.unwrapParameter(propValue);
|
|
2129
|
-
const hasDefault = value instanceof AssignmentValue;
|
|
2130
|
-
this.destructuringKeyData.set(key, { inst: propInst, value });
|
|
2131
|
-
members.set(key, {
|
|
2132
|
-
value: value.toOut(),
|
|
2133
|
-
handler: (get, propExists, scope) => {
|
|
2134
|
-
return this.c.handle(scope, prop, () => {
|
|
2135
|
-
const inst = [...propInst];
|
|
2136
|
-
if (propExists() || !hasDefault) {
|
|
2137
|
-
const input = pipeInsts(get(), inst);
|
|
2138
|
-
// assigns the output to the target value
|
|
2139
|
-
const output = pipeInsts(value["="](scope, input), inst);
|
|
2140
|
-
return [output, inst];
|
|
2141
|
-
}
|
|
2142
|
-
const result = pipeInsts(value["="](scope, new LiteralValue(null)), inst);
|
|
2143
|
-
return [result, inst];
|
|
2144
|
-
});
|
|
2145
|
-
},
|
|
2146
|
-
});
|
|
2147
|
-
}
|
|
2148
|
-
return new DestructuringValue(members);
|
|
2149
|
-
}
|
|
2150
|
-
case "ArrayPattern": {
|
|
2151
|
-
const members = new Map();
|
|
2152
|
-
for (let i = 0; i < param.elements.length; i++) {
|
|
2153
|
-
const element = param.elements[i];
|
|
2154
|
-
if (!element)
|
|
2155
|
-
continue;
|
|
2156
|
-
const value = this.unwrapParameter(element);
|
|
2157
|
-
const hasDefault = value instanceof AssignmentValue;
|
|
2158
|
-
if (!value)
|
|
2159
|
-
throw new CompilerError("Destructuring element must resolve to a value", element);
|
|
2160
|
-
const key = new LiteralValue(i);
|
|
2161
|
-
this.destructuringKeyData.set(key, { inst: [], value });
|
|
2162
|
-
members.set(key, {
|
|
2163
|
-
value: value.toOut(),
|
|
2164
|
-
handler: (get, propExists, scope) => {
|
|
2165
|
-
return this.c.handle(scope, element, () => {
|
|
2166
|
-
if (propExists() || !hasDefault) {
|
|
2167
|
-
const inst = get();
|
|
2168
|
-
// assigns the output to the target value
|
|
2169
|
-
pipeInsts(value["="](scope, inst[0]), inst[1]);
|
|
2170
|
-
return inst;
|
|
2171
|
-
}
|
|
2172
|
-
return value["="](scope, new LiteralValue(null));
|
|
2173
|
-
});
|
|
2174
|
-
},
|
|
2175
|
-
});
|
|
2176
|
-
}
|
|
2177
|
-
return new DestructuringValue(members);
|
|
2178
|
-
}
|
|
2179
|
-
default:
|
|
2180
|
-
throw new CompilerError(`Unsupported function parameter type: ${param.type}`);
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
hardSetParameter(scope, param, value = new LiteralValue(null)) {
|
|
2184
|
-
if (param instanceof SenseableValue) {
|
|
2185
|
-
const name = this.paramNames.get(param);
|
|
2186
|
-
scope.hardSet(name, value);
|
|
2187
|
-
return [];
|
|
2188
|
-
}
|
|
2189
|
-
if (param instanceof AssignmentValue) {
|
|
2190
|
-
const [result, inst] = value["??"](scope, param.right, param.left);
|
|
2191
|
-
inst.push(...this.hardSetParameter(scope, param.left, result));
|
|
2192
|
-
return inst;
|
|
2193
|
-
}
|
|
2194
|
-
const inst = [];
|
|
2195
|
-
for (const [key, { value: out }] of param.members) {
|
|
2196
|
-
const data = this.destructuringKeyData.get(key);
|
|
2197
|
-
if (!data)
|
|
2198
|
-
continue;
|
|
2199
|
-
inst.push(...data.inst);
|
|
2200
|
-
const hasDefault = data.value instanceof AssignmentValue;
|
|
2201
|
-
const input = value.hasProperty(scope, key) || !hasDefault
|
|
2202
|
-
? pipeInsts(value.get(scope, key, out), inst)
|
|
2203
|
-
: new LiteralValue(null);
|
|
2204
|
-
inst.push(...this.hardSetParameter(scope, data.value, input));
|
|
2205
|
-
}
|
|
2206
|
-
return inst;
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
function endsWithReturn(fun, inst) {
|
|
2210
|
-
for (let i = inst.length - 1; i >= 0; i--) {
|
|
2211
|
-
const instruction = inst[i];
|
|
2212
|
-
// this means that there is an instruction trying to reference
|
|
2213
|
-
// the final function `set @counter` instruction
|
|
2214
|
-
// of course this generates an unecessary
|
|
2215
|
-
// `set @counter` in cases where a control flow
|
|
2216
|
-
// structure contains a fallback return statement
|
|
2217
|
-
// TODO: fix this with static analysis
|
|
2218
|
-
if (instruction instanceof AddressResolver)
|
|
2219
|
-
return false;
|
|
2220
|
-
if (instruction.hidden)
|
|
2221
|
-
continue;
|
|
2222
|
-
return (instruction.intent === EInstIntent.exit ||
|
|
2223
|
-
(instruction.intent === EInstIntent.return &&
|
|
2224
|
-
instruction.intentSource === fun));
|
|
2225
|
-
}
|
|
2226
|
-
return false;
|
|
2227
|
-
}
|
|
2228
|
-
function validateParameters(args, params) {
|
|
2229
|
-
for (let i = 0; i < args.length; i++) {
|
|
2230
|
-
const arg = args[i];
|
|
2231
|
-
const param = params[i];
|
|
2232
|
-
if (arg instanceof StoreValue &&
|
|
2233
|
-
param instanceof AssignmentValue &&
|
|
2234
|
-
param.left.macro) {
|
|
2235
|
-
throw new CompilerError("Cannot pass a store to a function parameter that has a macro as a default value.");
|
|
2236
|
-
}
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
function handleFunctionNode(c, scope, node, out) {
|
|
2241
|
-
let { params, body } = node;
|
|
2242
|
-
if (es.isExpression(body)) {
|
|
2243
|
-
const { loc } = body;
|
|
2244
|
-
const statement = es.returnStatement(body);
|
|
2245
|
-
statement.loc = loc;
|
|
2246
|
-
body = es.blockStatement([statement]);
|
|
2247
|
-
body.loc = loc;
|
|
2248
|
-
}
|
|
2249
|
-
return [
|
|
2250
|
-
new FunctionValue({
|
|
2251
|
-
scope,
|
|
2252
|
-
params: params,
|
|
2253
|
-
body,
|
|
2254
|
-
c,
|
|
2255
|
-
out,
|
|
2256
|
-
}),
|
|
2257
|
-
[],
|
|
2258
|
-
];
|
|
2259
|
-
}
|
|
2260
|
-
const ArrowFunctionExpression = (c, scope, node, out) => {
|
|
2261
|
-
return handleFunctionNode(c, scope, node, out);
|
|
2262
|
-
};
|
|
2263
|
-
const FunctionDeclaration = (c, scope, node) => {
|
|
2264
|
-
const identifier = node.id.name;
|
|
2265
|
-
const name = nodeName(node, !c.compactNames && identifier);
|
|
2266
|
-
const functionIns = handleFunctionNode(c, scope, node, name);
|
|
2267
|
-
scope.set(identifier, functionIns[0]);
|
|
2268
|
-
return functionIns;
|
|
2269
|
-
};
|
|
2270
|
-
const FunctionExpression = ArrowFunctionExpression;
|
|
2271
|
-
const ObjectMethod = ArrowFunctionExpression;
|
|
2272
|
-
|
|
2273
|
-
const CallExpression = (c, scope, node, out) => {
|
|
2274
|
-
const inst = [];
|
|
2275
|
-
const callee = pipeInsts(c.handleEval(scope, node.callee), inst);
|
|
2276
|
-
const paramOuts = callee.preCall(scope, out);
|
|
2277
|
-
const args = node.arguments.map((node, index) => {
|
|
2278
|
-
const out = paramOuts?.[index];
|
|
2279
|
-
return pipeInsts(c.handleEval(scope, node, out), inst);
|
|
2280
|
-
});
|
|
2281
|
-
const callValue = pipeInsts(callee.call(scope, args, out), inst);
|
|
2282
|
-
callee.postCall(scope);
|
|
2283
|
-
return [callValue, inst];
|
|
2284
|
-
};
|
|
2285
|
-
const NewExpression = CallExpression;
|
|
2286
|
-
const TaggedTemplateExpression = (c, scope, node, out) => {
|
|
2287
|
-
const [tag, tagInst] = c.handleEval(scope, node.tag);
|
|
2288
|
-
const template = node.quasi;
|
|
2289
|
-
const stringsObject = ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.cooked ?? "")), {
|
|
2290
|
-
raw: ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.raw))),
|
|
2291
|
-
});
|
|
2292
|
-
const expressions = [];
|
|
2293
|
-
const expressionInsts = [];
|
|
2294
|
-
template.expressions.forEach(expression => {
|
|
2295
|
-
const value = pipeInsts(c.handleEval(scope, expression), expressionInsts);
|
|
2296
|
-
expressions.push(value);
|
|
2297
|
-
});
|
|
2298
|
-
const [result, resultInst] = tag.call(scope, [stringsObject, ...expressions], out);
|
|
2299
|
-
return [result, [...expressionInsts, ...tagInst, ...resultInst]];
|
|
2300
|
-
};
|
|
2301
|
-
|
|
2302
|
-
const ObjectExpression = (c, scope, node, out) => {
|
|
2303
|
-
const data = {};
|
|
2304
|
-
const inst = [];
|
|
2305
|
-
for (const prop of node.properties) {
|
|
2306
|
-
if (prop.type === "SpreadElement")
|
|
2307
|
-
throw new CompilerError("Cannot handle spread element", prop);
|
|
2308
|
-
if (prop.computed)
|
|
2309
|
-
throw new CompilerError("Cannot handle computed property.", prop);
|
|
2310
|
-
const { key } = prop;
|
|
2311
|
-
const value = prop.type === "ObjectProperty" ? prop.value : prop;
|
|
2312
|
-
let index;
|
|
2313
|
-
if (key.type === "Identifier") {
|
|
2314
|
-
index = key.name;
|
|
2315
|
-
}
|
|
2316
|
-
else if (key.type === "StringLiteral" || key.type === "NumericLiteral") {
|
|
2317
|
-
index = `${key.value}`;
|
|
2318
|
-
}
|
|
2319
|
-
else {
|
|
2320
|
-
throw new CompilerError(`Unsupported object key type: ${key.type}`, key);
|
|
2321
|
-
}
|
|
2322
|
-
const memberOut = extractDestrucuringOut(out, index);
|
|
2323
|
-
const member = pipeInsts(c.handleEval(scope, value, memberOut), inst);
|
|
2324
|
-
data[index] = member;
|
|
2325
|
-
}
|
|
2326
|
-
return [new ObjectValue(data), inst];
|
|
2327
|
-
};
|
|
2328
|
-
const ArrayExpression = (c, scope, node, out) => {
|
|
2329
|
-
const items = [];
|
|
2330
|
-
const inst = [];
|
|
2331
|
-
node.elements.forEach((element, i) => {
|
|
2332
|
-
if (!element) {
|
|
2333
|
-
items.push(undefined);
|
|
2334
|
-
return;
|
|
2335
|
-
}
|
|
2336
|
-
const value = pipeInsts(c.handleEval(scope, element, extractDestrucuringOut(out, i)), inst);
|
|
2337
|
-
items.push(value);
|
|
2338
|
-
});
|
|
2339
|
-
return [ObjectValue.fromArray(items), inst];
|
|
2340
|
-
};
|
|
2341
|
-
const MemberExpression = (c, scope, node, out, optional = false) => {
|
|
2342
|
-
const [prop, propInst] = node.computed
|
|
2343
|
-
? c.handleEval(scope, node.property)
|
|
2344
|
-
: [new LiteralValue(node.property.name), []];
|
|
2345
|
-
if (!out) {
|
|
2346
|
-
const [obj, objInst] = c.handleEval(scope, node.object);
|
|
2347
|
-
if (optional && obj instanceof LiteralValue && obj.data === null)
|
|
2348
|
-
return [obj, [...objInst, ...propInst]];
|
|
2349
|
-
const [got, gotInst] = obj.get(scope, prop);
|
|
2350
|
-
return [got, [...objInst, ...propInst, ...gotInst]];
|
|
2351
|
-
}
|
|
2352
|
-
const temp = SenseableValue.out(scope, out);
|
|
2353
|
-
const objOut = new DestructuringValue(new Map([
|
|
2354
|
-
[
|
|
2355
|
-
prop,
|
|
2356
|
-
{
|
|
2357
|
-
value: temp,
|
|
2358
|
-
// a direct assignment should NOT happen
|
|
2359
|
-
handler: () => {
|
|
2360
|
-
throw new CompilerError("Unexpected destructuring assignment");
|
|
2361
|
-
},
|
|
2362
|
-
},
|
|
2363
|
-
],
|
|
2364
|
-
]));
|
|
2365
|
-
const [obj, objInst] = c.handleEval(scope, node.object, objOut);
|
|
2366
|
-
if (optional && obj instanceof LiteralValue && obj.data === null)
|
|
2367
|
-
return [obj, [...objInst, ...propInst]];
|
|
2368
|
-
const [got, gotInst] = obj.get(scope, prop, temp);
|
|
2369
|
-
return [got, [...objInst, ...propInst, ...gotInst]];
|
|
2370
|
-
};
|
|
2371
|
-
const ArrayPattern = (c, scope, node) => {
|
|
2372
|
-
const inst = [];
|
|
2373
|
-
const members = new Map();
|
|
2374
|
-
for (let i = 0; i < node.elements.length; i++) {
|
|
2375
|
-
const element = node.elements[i];
|
|
2376
|
-
if (!element)
|
|
2377
|
-
continue;
|
|
2378
|
-
const value = pipeInsts(c.handleValue(scope, element), inst);
|
|
2379
|
-
const hasDefault = value instanceof AssignmentValue;
|
|
2380
|
-
members.set(new LiteralValue(i), {
|
|
2381
|
-
value: value.toOut(),
|
|
2382
|
-
handler(get, propExists, scope) {
|
|
2383
|
-
return c.handle(scope, element, () => {
|
|
2384
|
-
if (propExists() || !hasDefault) {
|
|
2385
|
-
const inst = get();
|
|
2386
|
-
// assigns the output to the target value
|
|
2387
|
-
pipeInsts(value["="](scope, inst[0]), inst[1]);
|
|
2388
|
-
return inst;
|
|
2389
|
-
}
|
|
2390
|
-
return value["="](scope, new LiteralValue(null));
|
|
2391
|
-
});
|
|
2392
|
-
},
|
|
2393
|
-
});
|
|
2394
|
-
}
|
|
2395
|
-
return [new DestructuringValue(members), inst];
|
|
2396
|
-
};
|
|
2397
|
-
const ObjectPattern = (c, scope, node) => {
|
|
2398
|
-
const inst = [];
|
|
2399
|
-
const members = new Map();
|
|
2400
|
-
for (const prop of node.properties) {
|
|
2401
|
-
if (prop.type === "RestElement")
|
|
2402
|
-
throw new CompilerError("The rest operator is not supported");
|
|
2403
|
-
if (!prop)
|
|
2404
|
-
continue;
|
|
2405
|
-
const propKey = prop.key;
|
|
2406
|
-
const [key, keyInst] = propKey.type === "Identifier" && !prop.computed
|
|
2407
|
-
? [new LiteralValue(propKey.name), []]
|
|
2408
|
-
: c.handleEval(scope, propKey);
|
|
2409
|
-
const value = pipeInsts(c.handleValue(scope, prop.value), inst);
|
|
2410
|
-
const hasDefault = value instanceof AssignmentValue;
|
|
2411
|
-
members.set(key, {
|
|
2412
|
-
value: value.toOut(),
|
|
2413
|
-
handler(get, propExists, scope) {
|
|
2414
|
-
return c.handle(scope, prop, () => {
|
|
2415
|
-
const inst = keyInst;
|
|
2416
|
-
if (propExists() || !hasDefault) {
|
|
2417
|
-
const input = pipeInsts(get(), inst);
|
|
2418
|
-
// assigns the output to the target value
|
|
2419
|
-
const output = pipeInsts(value["="](scope, input), inst);
|
|
2420
|
-
return [output, inst];
|
|
2421
|
-
}
|
|
2422
|
-
const result = pipeInsts(value["="](scope, new LiteralValue(null)), inst);
|
|
2423
|
-
return [result, inst];
|
|
2424
|
-
});
|
|
2425
|
-
},
|
|
2426
|
-
});
|
|
2427
|
-
}
|
|
2428
|
-
return [new DestructuringValue(members), inst];
|
|
2429
|
-
};
|
|
2430
|
-
// the mlog runtime already does this check for us
|
|
2431
|
-
const OptionalMemberExpression = (c, scope, node, out) => MemberExpression(c, scope, node, out, true);
|
|
2432
|
-
const AssignmentPattern = (c, scope, node) => {
|
|
2433
|
-
const [left, inst] = c.handleValue(scope, node.left);
|
|
2434
|
-
const right = new LazyValue((_, out) => c.handleEval(scope, node.right, out));
|
|
2435
|
-
return [new AssignmentValue(left, right), inst];
|
|
2436
|
-
};
|
|
2437
|
-
|
|
2438
|
-
const TemplateLiteral = () => {
|
|
2439
|
-
throw new CompilerError("Template strings can only be used in tagged template literals. To inline mlog code use the asm function.");
|
|
2440
|
-
};
|
|
2441
|
-
|
|
2442
|
-
const BlockStatement = (c, scope, node) => {
|
|
2443
|
-
return c.handleMany(scope.createScope(), node.body);
|
|
2444
|
-
};
|
|
2445
|
-
|
|
2446
|
-
const File = (c, scope, node) => {
|
|
2447
|
-
return c.handle(scope ?? new Scope({}), node.program);
|
|
2448
|
-
};
|
|
2449
|
-
|
|
2450
|
-
const TypeCastExpression = (c, scope, node, out) => {
|
|
2451
|
-
return c.handle(scope, node.expression, undefined, out);
|
|
2452
|
-
};
|
|
2453
|
-
const TSAsExpression = TypeCastExpression;
|
|
2454
|
-
const TSTypeAssertion = TypeCastExpression;
|
|
2455
|
-
const IgnoredHandler = () => [null, []];
|
|
2456
|
-
const TSInterfaceDeclaration = IgnoredHandler;
|
|
2457
|
-
const TSTypeAliasDeclaration = IgnoredHandler;
|
|
2458
|
-
const TSEnumDeclaration = (c, scope, node) => {
|
|
2459
|
-
if (!node.const)
|
|
2460
|
-
throw new CompilerError("All enums must be declared with the const keyword before enum");
|
|
2461
|
-
let counter = 0;
|
|
2462
|
-
let lastType = "number";
|
|
2463
|
-
const data = {};
|
|
2464
|
-
for (const member of node.members) {
|
|
2465
|
-
if (lastType === "string" && !member.initializer)
|
|
2466
|
-
throw new CompilerError("This enum member must be initialized", member);
|
|
2467
|
-
const [value] = member.initializer
|
|
2468
|
-
? c.handleEval(scope, member.initializer)
|
|
2469
|
-
: [new LiteralValue(counter)];
|
|
2470
|
-
if (!(value instanceof LiteralValue))
|
|
2471
|
-
throw new CompilerError("Enum members must contain literal values", member);
|
|
2472
|
-
if (value.isNumber()) {
|
|
2473
|
-
lastType = "number";
|
|
2474
|
-
counter = value.data + 1;
|
|
2475
|
-
}
|
|
2476
|
-
else {
|
|
2477
|
-
lastType = "string";
|
|
2478
|
-
}
|
|
2479
|
-
const name = member.id.type === "Identifier" ? member.id.name : member.id.value;
|
|
2480
|
-
data[name] = value;
|
|
2481
|
-
}
|
|
2482
|
-
const value = new ObjectValue(data);
|
|
2483
|
-
value.name = nodeName(node, !c.compactNames && node.id.name);
|
|
2484
|
-
scope.set(node.id.name, value);
|
|
2485
|
-
return [null, []];
|
|
2486
|
-
};
|
|
2487
|
-
const TSNonNullExpression = (c, scope, node, out) => {
|
|
2488
|
-
return c.handle(scope, node.expression, undefined, out);
|
|
2489
|
-
};
|
|
2490
|
-
const TSSatisfiesExpression = (c, scope, node, out) => c.handle(scope, node.expression, undefined, out);
|
|
2491
|
-
|
|
2492
|
-
var handlers = /*#__PURE__*/Object.freeze({
|
|
2493
|
-
__proto__: null,
|
|
2494
|
-
ArrayExpression: ArrayExpression,
|
|
2495
|
-
ArrayPattern: ArrayPattern,
|
|
2496
|
-
ArrowFunctionExpression: ArrowFunctionExpression,
|
|
2497
|
-
AssignmentExpression: AssignmentExpression,
|
|
2498
|
-
AssignmentPattern: AssignmentPattern,
|
|
2499
|
-
BinaryExpression: BinaryExpression,
|
|
2500
|
-
BlockStatement: BlockStatement,
|
|
2501
|
-
BooleanLiteral: BooleanLiteral,
|
|
2502
|
-
BreakStatement: BreakStatement,
|
|
2503
|
-
CallExpression: CallExpression,
|
|
2504
|
-
ConditionalExpression: ConditionalExpression,
|
|
2505
|
-
ContinueStatement: ContinueStatement,
|
|
2506
|
-
DoWhileStatement: DoWhileStatement,
|
|
2507
|
-
EmptyStatement: EmptyStatement,
|
|
2508
|
-
ExpressionStatement: ExpressionStatement,
|
|
2509
|
-
File: File,
|
|
2510
|
-
ForStatement: ForStatement,
|
|
2511
|
-
FunctionDeclaration: FunctionDeclaration,
|
|
2512
|
-
FunctionExpression: FunctionExpression,
|
|
2513
|
-
Identifier: Identifier,
|
|
2514
|
-
IfStatement: IfStatement,
|
|
2515
|
-
LRExpression: LRExpression,
|
|
2516
|
-
LabeledStatement: LabeledStatement,
|
|
2517
|
-
LogicalExpression: LogicalExpression,
|
|
2518
|
-
MemberExpression: MemberExpression,
|
|
2519
|
-
NewExpression: NewExpression,
|
|
2520
|
-
NullLiteral: NullLiteral,
|
|
2521
|
-
NumericLiteral: NumericLiteral,
|
|
2522
|
-
ObjectExpression: ObjectExpression,
|
|
2523
|
-
ObjectMethod: ObjectMethod,
|
|
2524
|
-
ObjectPattern: ObjectPattern,
|
|
2525
|
-
OptionalMemberExpression: OptionalMemberExpression,
|
|
2526
|
-
Program: Program,
|
|
2527
|
-
ReturnStatement: ReturnStatement,
|
|
2528
|
-
SequenceExpression: SequenceExpression,
|
|
2529
|
-
StringLiteral: StringLiteral,
|
|
2530
|
-
SwitchStatement: SwitchStatement,
|
|
2531
|
-
TSAsExpression: TSAsExpression,
|
|
2532
|
-
TSEnumDeclaration: TSEnumDeclaration,
|
|
2533
|
-
TSInterfaceDeclaration: TSInterfaceDeclaration,
|
|
2534
|
-
TSNonNullExpression: TSNonNullExpression,
|
|
2535
|
-
TSSatisfiesExpression: TSSatisfiesExpression,
|
|
2536
|
-
TSTypeAliasDeclaration: TSTypeAliasDeclaration,
|
|
2537
|
-
TSTypeAssertion: TSTypeAssertion,
|
|
2538
|
-
TaggedTemplateExpression: TaggedTemplateExpression,
|
|
2539
|
-
TemplateLiteral: TemplateLiteral,
|
|
2540
|
-
UnaryExpression: UnaryExpression,
|
|
2541
|
-
UpdateExpression: UpdateExpression,
|
|
2542
|
-
VariableDeclaration: VariableDeclaration,
|
|
2543
|
-
VariableDeclarator: VariableDeclarator,
|
|
2544
|
-
WhileStatement: WhileStatement
|
|
2545
|
-
});
|
|
2546
|
-
|
|
2547
|
-
class MacroFunction extends VoidValue {
|
|
2548
|
-
constructor(fn, paramOuts) {
|
|
2549
|
-
super();
|
|
2550
|
-
this.paramOuts = paramOuts;
|
|
2551
|
-
this.macro = true;
|
|
2552
|
-
this.mutability = EMutability.constant;
|
|
2553
|
-
this.fn = fn;
|
|
2554
|
-
}
|
|
2555
|
-
call(scope, args, out) {
|
|
2556
|
-
return this.fn.apply(this, [scope, out, ...args]);
|
|
2557
|
-
}
|
|
2558
|
-
eval(_scope) {
|
|
2559
|
-
return [this, []];
|
|
2560
|
-
}
|
|
2561
|
-
preCall(_scope, _out) {
|
|
2562
|
-
return this.paramOuts;
|
|
2563
|
-
}
|
|
2564
|
-
debugString() {
|
|
2565
|
-
return "MacroFunction";
|
|
2566
|
-
}
|
|
2567
|
-
toString() {
|
|
2568
|
-
return '"[macro MacroFunction]"';
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
|
|
2572
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2573
|
-
const toRadians = (degrees) => (degrees * Math.PI) / 180;
|
|
2574
|
-
const toDegrees = (radians) => (radians * 180) / Math.PI;
|
|
2575
|
-
const mathOperations = {
|
|
2576
|
-
max: (a, b) => Math.max(a, b),
|
|
2577
|
-
min: (a, b) => Math.min(a, b),
|
|
2578
|
-
angle: (x, y) => {
|
|
2579
|
-
const angle = toDegrees(Math.atan2(y, x));
|
|
2580
|
-
return angle < 0 ? angle + 360 : angle;
|
|
2581
|
-
},
|
|
2582
|
-
len: (a, b) => Math.sqrt(a ** 2 + b ** 2),
|
|
2583
|
-
noise: null,
|
|
2584
|
-
abs: a => Math.abs(a),
|
|
2585
|
-
log: a => Math.log(a),
|
|
2586
|
-
log10: a => Math.log10(a),
|
|
2587
|
-
sin: a => Math.sin(toRadians(a)),
|
|
2588
|
-
cos: a => Math.cos(toRadians(a)),
|
|
2589
|
-
tan: a => Math.tan(toRadians(a)),
|
|
2590
|
-
floor: a => Math.floor(a),
|
|
2591
|
-
ceil: a => Math.ceil(a),
|
|
2592
|
-
sqrt: a => Math.sqrt(a),
|
|
2593
|
-
asin: a => toDegrees(Math.asin(a)),
|
|
2594
|
-
acos: a => toDegrees(Math.acos(a)),
|
|
2595
|
-
atan: a => toDegrees(Math.atan(a)),
|
|
2596
|
-
// yes, this doesn't work with negative numbers
|
|
2597
|
-
// but the game also implements it this way.
|
|
2598
|
-
idiv: (a, b) => Math.floor(a / b),
|
|
2599
|
-
pow: (a, b) => Math.pow(a, b),
|
|
2600
|
-
rand: null,
|
|
2601
|
-
};
|
|
2602
|
-
const orderIndependentOperations = ["max", "min", "len"];
|
|
2603
|
-
function createMacroMathOperations() {
|
|
2604
|
-
const macroMathOperations = {
|
|
2605
|
-
PI: new StoreValue("@pi", EMutability.constant),
|
|
2606
|
-
E: new StoreValue("@e", EMutability.constant),
|
|
2607
|
-
degToRad: new StoreValue("@degToRad", EMutability.constant),
|
|
2608
|
-
radToDeg: new StoreValue("@radToDeg", EMutability.constant),
|
|
2609
|
-
};
|
|
2610
|
-
for (const key in mathOperations) {
|
|
2611
|
-
const fn = mathOperations[key];
|
|
2612
|
-
macroMathOperations[key] = new MacroFunction((scope, out, a, b) => {
|
|
2613
|
-
const argumentCount = +!!a + +!!b;
|
|
2614
|
-
if (fn && argumentCount != fn.length) {
|
|
2615
|
-
throw new CompilerError(`Expected ${fn.length} arguments, but got ${argumentCount}`);
|
|
2616
|
-
}
|
|
2617
|
-
const cacheKey = getCacheKey(key);
|
|
2618
|
-
if (b) {
|
|
2619
|
-
if (fn && a instanceof LiteralValue && b instanceof LiteralValue) {
|
|
2620
|
-
if (!a.isNumber() || !b.isNumber())
|
|
2621
|
-
throw new CompilerError("Cannot do math operation with non-numerical literals.");
|
|
2622
|
-
return [new LiteralValue(fn(a.num, b.num)), []];
|
|
2623
|
-
}
|
|
2624
|
-
const cachedResult = scope.getCachedOperation(cacheKey, a, b);
|
|
2625
|
-
if (cachedResult)
|
|
2626
|
-
return [cachedResult, []];
|
|
2627
|
-
// max, min and len do not change if the arguments change in order
|
|
2628
|
-
if (orderIndependentOperations.includes(key)) {
|
|
2629
|
-
const cachedResult = scope.getCachedOperation(cacheKey, b, a);
|
|
2630
|
-
if (cachedResult)
|
|
2631
|
-
return [cachedResult, []];
|
|
2632
|
-
}
|
|
2633
|
-
const temp = StoreValue.from(scope, out);
|
|
2634
|
-
scope.addCachedOperation(cacheKey, temp, a, b);
|
|
2635
|
-
return [temp, [new OperationInstruction(key, temp, a, b)]];
|
|
2636
|
-
}
|
|
2637
|
-
if (fn && a instanceof LiteralValue) {
|
|
2638
|
-
if (!a.isNumber())
|
|
2639
|
-
throw new CompilerError("Cannot do math operation with non-numerical literal.");
|
|
2640
|
-
return [new LiteralValue(fn(a.num)), []];
|
|
2641
|
-
}
|
|
2642
|
-
const cachedResult = scope.getCachedOperation(cacheKey, a);
|
|
2643
|
-
if (cachedResult)
|
|
2644
|
-
return [cachedResult, []];
|
|
2645
|
-
const temp = StoreValue.from(scope, out);
|
|
2646
|
-
// ensures that operations like `rand` are not cached
|
|
2647
|
-
if (mathOperations[key])
|
|
2648
|
-
scope.addCachedOperation(cacheKey, temp, a);
|
|
2649
|
-
return [temp, [new OperationInstruction(key, temp, a, null)]];
|
|
2650
|
-
});
|
|
2651
|
-
}
|
|
2652
|
-
return macroMathOperations;
|
|
2653
|
-
}
|
|
2654
|
-
function getCacheKey(key) {
|
|
2655
|
-
if (key === "pow")
|
|
2656
|
-
return "**";
|
|
2657
|
-
return key;
|
|
2658
|
-
}
|
|
2659
|
-
class MlogMath extends ObjectValue {
|
|
2660
|
-
constructor() {
|
|
2661
|
-
super(createMacroMathOperations());
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
// op angle result a b
|
|
2665
|
-
// op len result a b
|
|
2666
|
-
|
|
2667
|
-
class Concat extends MacroFunction {
|
|
2668
|
-
constructor() {
|
|
2669
|
-
super((scope, out, ...values) => {
|
|
2670
|
-
const [first] = values;
|
|
2671
|
-
if (!isTemplateObjectArray(first)) {
|
|
2672
|
-
assertLiterals(values, "Concat arguments must be all literal values");
|
|
2673
|
-
let text = "";
|
|
2674
|
-
for (const value of values)
|
|
2675
|
-
text += value.data;
|
|
2676
|
-
return [new LiteralValue(text), []];
|
|
2677
|
-
}
|
|
2678
|
-
let text = "";
|
|
2679
|
-
const [, ...interpolated] = values;
|
|
2680
|
-
assertLiterals(interpolated, "Interpolated values in concat must be literals");
|
|
2681
|
-
for (let i = 0; i < values.length; i++) {
|
|
2682
|
-
const [string] = first.get(scope, new LiteralValue(i));
|
|
2683
|
-
text += string.data;
|
|
2684
|
-
if (i < interpolated.length)
|
|
2685
|
-
text += interpolated[i].data;
|
|
2686
|
-
}
|
|
2687
|
-
return [new LiteralValue(text), []];
|
|
2688
|
-
});
|
|
2689
|
-
}
|
|
2690
|
-
}
|
|
2691
|
-
function assertLiterals(values, message) {
|
|
2692
|
-
for (const value of values) {
|
|
2693
|
-
if (!(value instanceof LiteralValue))
|
|
2694
|
-
throw new CompilerError(message);
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
|
|
2698
|
-
/** Used to create namespaces that contain multiple methods that map into instructions */
|
|
2699
|
-
function createOverloadNamespace({ overloads, handler, }) {
|
|
2700
|
-
const result = {};
|
|
2701
|
-
for (const key in overloads) {
|
|
2702
|
-
const { named, args } = overloads[key];
|
|
2703
|
-
if (named) {
|
|
2704
|
-
result[key] = new MacroFunction((scope, out, options) => {
|
|
2705
|
-
assertIsObjectMacro(options, named);
|
|
2706
|
-
return handler(scope, key, out, ...assertObjectFields(options, args));
|
|
2707
|
-
});
|
|
2708
|
-
continue;
|
|
2709
|
-
}
|
|
2710
|
-
result[key] = new MacroFunction((scope, out, ...params) => {
|
|
2711
|
-
const handlerParams = [];
|
|
2712
|
-
// validate the paramters
|
|
2713
|
-
for (let i = 0; i < args.length; i++) {
|
|
2714
|
-
let arg = args[i];
|
|
2715
|
-
if (typeof arg === "string")
|
|
2716
|
-
arg = { key: arg };
|
|
2717
|
-
const param = params[i];
|
|
2718
|
-
if (!param) {
|
|
2719
|
-
if (arg.default == undefined)
|
|
2720
|
-
throw new CompilerError(`Missing argument: ${arg.name ?? arg.key}`);
|
|
2721
|
-
handlerParams.push(arg.default);
|
|
2722
|
-
continue;
|
|
2723
|
-
}
|
|
2724
|
-
arg.validate?.(param);
|
|
2725
|
-
handlerParams.push(param);
|
|
2726
|
-
}
|
|
2727
|
-
return handler(scope, key, out, ...handlerParams);
|
|
2728
|
-
});
|
|
2729
|
-
}
|
|
2730
|
-
return result;
|
|
2731
|
-
}
|
|
2732
|
-
|
|
2733
|
-
class Draw extends ObjectValue {
|
|
2734
|
-
constructor() {
|
|
2735
|
-
const data = createOverloadNamespace({
|
|
2736
|
-
overloads: {
|
|
2737
|
-
clear: {
|
|
2738
|
-
args: ["r", "g", "b"],
|
|
2739
|
-
},
|
|
2740
|
-
color: {
|
|
2741
|
-
args: ["r", "g", "b", { key: "a", default: "" }],
|
|
2742
|
-
},
|
|
2743
|
-
col: {
|
|
2744
|
-
args: ["rgbaData"],
|
|
2745
|
-
},
|
|
2746
|
-
stroke: {
|
|
2747
|
-
args: ["width"],
|
|
2748
|
-
},
|
|
2749
|
-
line: {
|
|
2750
|
-
named: "options",
|
|
2751
|
-
args: ["x", "y", "x2", "y2"],
|
|
2752
|
-
},
|
|
2753
|
-
rect: {
|
|
2754
|
-
named: "options",
|
|
2755
|
-
args: ["x", "y", "width", "height"],
|
|
2756
|
-
},
|
|
2757
|
-
lineRect: { named: "options", args: ["x", "y", "width", "height"] },
|
|
2758
|
-
linePoly: {
|
|
2759
|
-
named: "options",
|
|
2760
|
-
args: ["x", "y", "sides", "radius", "rotation"],
|
|
2761
|
-
},
|
|
2762
|
-
poly: {
|
|
2763
|
-
named: "options",
|
|
2764
|
-
args: ["x", "y", "sides", "radius", "rotation"],
|
|
2765
|
-
},
|
|
2766
|
-
triangle: {
|
|
2767
|
-
named: "options",
|
|
2768
|
-
args: ["x", "y", "x2", "y2", "x3", "y3"],
|
|
2769
|
-
},
|
|
2770
|
-
image: {
|
|
2771
|
-
named: "options",
|
|
2772
|
-
args: ["x", "y", "image", "size", "rotation"],
|
|
2773
|
-
},
|
|
2774
|
-
},
|
|
2775
|
-
handler(scope, overload, out, ...args) {
|
|
2776
|
-
return [null, [new InstructionBase("draw", overload, ...args)]];
|
|
2777
|
-
},
|
|
2778
|
-
});
|
|
2779
|
-
super(data);
|
|
2780
|
-
}
|
|
2781
|
-
}
|
|
2782
|
-
|
|
2783
|
-
class Print extends MacroFunction {
|
|
2784
|
-
constructor() {
|
|
2785
|
-
super((scope, out, ...values) => {
|
|
2786
|
-
const [first] = values;
|
|
2787
|
-
const inst = [];
|
|
2788
|
-
if (!isTemplateObjectArray(first)) {
|
|
2789
|
-
for (const value of values) {
|
|
2790
|
-
inst.push(new InstructionBase("print", value));
|
|
2791
|
-
}
|
|
2792
|
-
return [null, inst];
|
|
2793
|
-
}
|
|
2794
|
-
// `first` is likely a template strings array
|
|
2795
|
-
// maybe this should be checked in another way?
|
|
2796
|
-
const { length } = first.data;
|
|
2797
|
-
for (let i = 1; i < values.length; i++) {
|
|
2798
|
-
const [string] = first.get(scope, new LiteralValue(i - 1));
|
|
2799
|
-
if (string.data)
|
|
2800
|
-
inst.push(new InstructionBase("print", string));
|
|
2801
|
-
inst.push(new InstructionBase("print", values[i]));
|
|
2802
|
-
}
|
|
2803
|
-
const [tail] = first.get(scope, new LiteralValue(length.data - 1));
|
|
2804
|
-
if (tail.data)
|
|
2805
|
-
inst.push(new InstructionBase("print", tail));
|
|
2806
|
-
return [null, inst];
|
|
2807
|
-
});
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
const defaultTargetName$1 = "message1";
|
|
2812
|
-
class PrintFlush extends MacroFunction {
|
|
2813
|
-
constructor() {
|
|
2814
|
-
super((scope, out, target) => {
|
|
2815
|
-
if (!target)
|
|
2816
|
-
return [null, [new InstructionBase("printflush", defaultTargetName$1)]];
|
|
2817
|
-
if (!(target instanceof SenseableValue))
|
|
2818
|
-
throw new CompilerError("The printflush target must be a senseable value");
|
|
2819
|
-
return [null, [new InstructionBase("printflush", target)]];
|
|
2820
|
-
});
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
const defaultTargetName = "display1";
|
|
2825
|
-
class DrawFlush extends MacroFunction {
|
|
2826
|
-
constructor() {
|
|
2827
|
-
super((scope, out, target) => {
|
|
2828
|
-
if (!target)
|
|
2829
|
-
return [null, [new InstructionBase("drawflush", defaultTargetName)]];
|
|
2830
|
-
if (!(target instanceof SenseableValue))
|
|
2831
|
-
throw new CompilerError("The drawflush target must be a senseable value");
|
|
2832
|
-
return [null, [new InstructionBase("drawflush", target)]];
|
|
2833
|
-
});
|
|
2834
|
-
}
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
|
-
class GetBlock extends ObjectValue {
|
|
2838
|
-
constructor() {
|
|
2839
|
-
const data = createOverloadNamespace({
|
|
2840
|
-
overloads: {
|
|
2841
|
-
floor: { args: ["x", "y"] },
|
|
2842
|
-
ore: { args: ["x", "y"] },
|
|
2843
|
-
block: { args: ["x", "y"] },
|
|
2844
|
-
building: { args: ["x", "y"] },
|
|
2845
|
-
},
|
|
2846
|
-
handler(scope, overload, out, x, y) {
|
|
2847
|
-
const output = overload === "building"
|
|
2848
|
-
? SenseableValue.from(scope, out, EMutability.constant)
|
|
2849
|
-
: StoreValue.from(scope, out, EMutability.constant);
|
|
2850
|
-
return [
|
|
2851
|
-
output,
|
|
2852
|
-
[new InstructionBase("getblock", overload, output, x, y)],
|
|
2853
|
-
];
|
|
2854
|
-
},
|
|
2855
|
-
});
|
|
2856
|
-
super(data);
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
|
|
2860
|
-
class GetLink extends MacroFunction {
|
|
2861
|
-
constructor() {
|
|
2862
|
-
super((scope, out, index) => {
|
|
2863
|
-
if (!(index instanceof StoreValue) &&
|
|
2864
|
-
(!(index instanceof LiteralValue) || !index.isNumber()))
|
|
2865
|
-
throw new CompilerError("The getlink index must be a number literal or a store");
|
|
2866
|
-
const outBuild = SenseableValue.from(scope, out, EMutability.constant);
|
|
2867
|
-
return [outBuild, [new InstructionBase("getlink", outBuild, index)]];
|
|
2868
|
-
});
|
|
2869
|
-
}
|
|
2870
|
-
}
|
|
2871
|
-
|
|
2872
|
-
class Control extends ObjectValue {
|
|
2873
|
-
constructor() {
|
|
2874
|
-
super(createOverloadNamespace({
|
|
2875
|
-
overloads: {
|
|
2876
|
-
enabled: { args: ["building", "value"] },
|
|
2877
|
-
shoot: {
|
|
2878
|
-
named: "options",
|
|
2879
|
-
args: ["building", "x", "y", "shoot"],
|
|
2880
|
-
},
|
|
2881
|
-
shootp: {
|
|
2882
|
-
named: "options",
|
|
2883
|
-
args: ["building", "unit", "shoot"],
|
|
2884
|
-
},
|
|
2885
|
-
config: { args: ["building", "value"] },
|
|
2886
|
-
color: { args: ["building", "rgbaData"] },
|
|
2887
|
-
},
|
|
2888
|
-
handler(scope, overload, out, ...args) {
|
|
2889
|
-
return [null, [new InstructionBase("control", overload, ...args)]];
|
|
2890
|
-
},
|
|
2891
|
-
}));
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
|
|
2895
|
-
const validRadarFilters = [
|
|
2896
|
-
"any",
|
|
2897
|
-
"enemy",
|
|
2898
|
-
"ally",
|
|
2899
|
-
"player",
|
|
2900
|
-
"attacker",
|
|
2901
|
-
"flying",
|
|
2902
|
-
"boss",
|
|
2903
|
-
"ground",
|
|
2904
|
-
];
|
|
2905
|
-
const validRadarSorts = [
|
|
2906
|
-
"distance",
|
|
2907
|
-
"health",
|
|
2908
|
-
"shield",
|
|
2909
|
-
"armor",
|
|
2910
|
-
"maxHealth",
|
|
2911
|
-
];
|
|
2912
|
-
class Radar extends MacroFunction {
|
|
2913
|
-
constructor() {
|
|
2914
|
-
super((scope, out, options) => {
|
|
2915
|
-
assertIsObjectMacro(options, "The radar options");
|
|
2916
|
-
const { building, filters, order, sort } = options.data;
|
|
2917
|
-
if (!(building instanceof SenseableValue))
|
|
2918
|
-
throw new CompilerError("The building must a senseable value");
|
|
2919
|
-
assertIsArrayMacro(filters, "filters");
|
|
2920
|
-
const { length } = filters.data;
|
|
2921
|
-
if (!(length instanceof LiteralValue) || !length.isNumber())
|
|
2922
|
-
throw new CompilerError("The length of an array macro must be");
|
|
2923
|
-
if (length.data !== 3)
|
|
2924
|
-
throw new CompilerError("The filters array must have 3 items");
|
|
2925
|
-
// data is not an array
|
|
2926
|
-
const { 0: filter1, 1: filter2, 2: filter3 } = filters.data;
|
|
2927
|
-
assertLiteralOneOf(filter1, validRadarFilters, "The first filter");
|
|
2928
|
-
assertLiteralOneOf(filter2, validRadarFilters, "The second filter");
|
|
2929
|
-
assertLiteralOneOf(filter3, validRadarFilters, "The third filter");
|
|
2930
|
-
if (!(order instanceof LiteralValue || order instanceof StoreValue))
|
|
2931
|
-
throw new CompilerError("The radar order must be a literal or a store");
|
|
2932
|
-
assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
|
|
2933
|
-
const outUnit = SenseableValue.from(scope, out, EMutability.constant);
|
|
2934
|
-
return [
|
|
2935
|
-
outUnit,
|
|
2936
|
-
[
|
|
2937
|
-
new InstructionBase("radar", filter1.data, filter2.data, filter3.data, sort.data, building, order, outUnit),
|
|
2938
|
-
],
|
|
2939
|
-
];
|
|
2940
|
-
});
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
|
-
class Sensor extends MacroFunction {
|
|
2945
|
-
constructor() {
|
|
2946
|
-
super((scope, out, property, target) => {
|
|
2947
|
-
if (!(property instanceof StoreValue))
|
|
2948
|
-
throw new CompilerError("The sensor property must be a store");
|
|
2949
|
-
if (!(target instanceof SenseableValue))
|
|
2950
|
-
throw new CompilerError("The sensor target must be a senseable value");
|
|
2951
|
-
const temp = SenseableValue.from(scope, out, EMutability.readonly);
|
|
2952
|
-
return [temp, [new InstructionBase("sensor", temp, target, property)]];
|
|
2953
|
-
});
|
|
2954
|
-
}
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2957
|
-
class Wait extends MacroFunction {
|
|
2958
|
-
constructor() {
|
|
2959
|
-
super((scope, out, seconds) => {
|
|
2960
|
-
if (!(seconds instanceof StoreValue) &&
|
|
2961
|
-
!(seconds instanceof LiteralValue && seconds.isNumber()))
|
|
2962
|
-
throw new CompilerError("The wait seconds must be either a number literal or a store");
|
|
2963
|
-
return [null, [new InstructionBase("wait", seconds)]];
|
|
2964
|
-
});
|
|
2965
|
-
}
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2968
|
-
class Lookup extends ObjectValue {
|
|
2969
|
-
constructor() {
|
|
2970
|
-
const data = createOverloadNamespace({
|
|
2971
|
-
overloads: {
|
|
2972
|
-
block: { args: ["index"] },
|
|
2973
|
-
unit: { args: ["index"] },
|
|
2974
|
-
item: { args: ["index"] },
|
|
2975
|
-
liquid: { args: ["index"] },
|
|
2976
|
-
},
|
|
2977
|
-
handler(scope, overload, out, index) {
|
|
2978
|
-
const output = StoreValue.from(scope, out);
|
|
2979
|
-
return [
|
|
2980
|
-
output,
|
|
2981
|
-
[new InstructionBase("lookup", overload, output, index)],
|
|
2982
|
-
];
|
|
2983
|
-
},
|
|
2984
|
-
});
|
|
2985
|
-
super(data);
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
|
|
2989
|
-
class PackColor extends MacroFunction {
|
|
2990
|
-
constructor() {
|
|
2991
|
-
super((scope, out, ...args) => {
|
|
2992
|
-
if (args.length !== 4) {
|
|
2993
|
-
throw new CompilerError(`Expected 4 arguments, received ${args.length}`);
|
|
2994
|
-
}
|
|
2995
|
-
if (!args.every((value) => value instanceof StoreValue ||
|
|
2996
|
-
(value instanceof LiteralValue && value.isNumber()))) {
|
|
2997
|
-
throw new CompilerError("packColor arguments must be either stores or number literals");
|
|
2998
|
-
}
|
|
2999
|
-
const output = StoreValue.from(scope, out);
|
|
3000
|
-
return [output, [new InstructionBase("packcolor", output, ...args)]];
|
|
3001
|
-
});
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
|
|
3005
|
-
class End extends MacroFunction {
|
|
3006
|
-
constructor() {
|
|
3007
|
-
super(() => [null, [new EndInstruction()]]);
|
|
3008
|
-
}
|
|
3009
|
-
}
|
|
3010
|
-
|
|
3011
|
-
class SetBlock extends ObjectValue {
|
|
3012
|
-
constructor() {
|
|
3013
|
-
const data = createOverloadNamespace({
|
|
3014
|
-
overloads: {
|
|
3015
|
-
floor: { args: ["x", "y", "to"] },
|
|
3016
|
-
ore: { args: ["x", "y", "to"] },
|
|
3017
|
-
block: {
|
|
3018
|
-
named: "options",
|
|
3019
|
-
args: ["x", "y", "to", "team", { key: "rotation", default: "0" }],
|
|
3020
|
-
},
|
|
3021
|
-
},
|
|
3022
|
-
handler(scope, overload, out, x, y, to, team, rotation) {
|
|
3023
|
-
return [
|
|
3024
|
-
null,
|
|
3025
|
-
[
|
|
3026
|
-
new InstructionBase("setblock", overload, to, x, y, team ?? "@derelict", rotation ?? "0"),
|
|
3027
|
-
],
|
|
3028
|
-
];
|
|
3029
|
-
},
|
|
3030
|
-
});
|
|
3031
|
-
super(data);
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
|
|
3035
|
-
class SpawnUnit extends MacroFunction {
|
|
3036
|
-
constructor() {
|
|
3037
|
-
super((scope, out, options) => {
|
|
3038
|
-
assertIsObjectMacro(options, "The options");
|
|
3039
|
-
const { type, x, y, team, rotation } = options.data;
|
|
3040
|
-
assertIsRuntimeValue(type, "type");
|
|
3041
|
-
assertIsRuntimeValue(x, "x");
|
|
3042
|
-
assertIsRuntimeValue(y, "y");
|
|
3043
|
-
assertIsRuntimeValue(team, "team");
|
|
3044
|
-
const output = SenseableValue.from(scope, out, EMutability.constant);
|
|
3045
|
-
return [
|
|
3046
|
-
output,
|
|
3047
|
-
[
|
|
3048
|
-
new InstructionBase("spawn", type, x, y, rotation ?? "0", team, output),
|
|
3049
|
-
],
|
|
3050
|
-
];
|
|
3051
|
-
});
|
|
3052
|
-
}
|
|
3053
|
-
}
|
|
3054
|
-
|
|
3055
|
-
class Stop extends MacroFunction {
|
|
3056
|
-
constructor() {
|
|
3057
|
-
super(() => [
|
|
3058
|
-
null,
|
|
3059
|
-
[
|
|
3060
|
-
assign(new InstructionBase("stop"), {
|
|
3061
|
-
intent: EInstIntent.exit,
|
|
3062
|
-
}),
|
|
3063
|
-
],
|
|
3064
|
-
]);
|
|
3065
|
-
}
|
|
3066
|
-
}
|
|
3067
|
-
|
|
3068
|
-
class UnitBind extends MacroFunction {
|
|
3069
|
-
constructor() {
|
|
3070
|
-
super((scope, out, unit) => {
|
|
3071
|
-
if (!(unit instanceof StoreValue))
|
|
3072
|
-
throw new CompilerError("The unitBind unit must be a store");
|
|
3073
|
-
return [null, [new InstructionBase("ubind", unit)]];
|
|
3074
|
-
});
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
|
|
3078
|
-
class UnitControl extends ObjectValue {
|
|
3079
|
-
constructor() {
|
|
3080
|
-
const data = createOverloadNamespace({
|
|
3081
|
-
overloads: {
|
|
3082
|
-
idle: { args: [] },
|
|
3083
|
-
stop: { args: [] },
|
|
3084
|
-
move: { args: ["x", "y"] },
|
|
3085
|
-
approach: { named: "options", args: ["x", "y", "radius"] },
|
|
3086
|
-
boost: { args: ["enable"] },
|
|
3087
|
-
pathfind: { args: [] },
|
|
3088
|
-
target: { named: "options", args: ["x", "y", "shoot"] },
|
|
3089
|
-
targetp: { named: "options", args: ["unit", "shoot"] },
|
|
3090
|
-
itemDrop: { args: ["target", "amount"] },
|
|
3091
|
-
itemTake: { args: ["target", "item", "amount"] },
|
|
3092
|
-
payDrop: { args: [] },
|
|
3093
|
-
payTake: { named: "options", args: ["takeUnits"] },
|
|
3094
|
-
payEnter: { args: [] },
|
|
3095
|
-
mine: { args: ["x", "y"] },
|
|
3096
|
-
flag: { args: ["value"] },
|
|
3097
|
-
build: {
|
|
3098
|
-
named: "options",
|
|
3099
|
-
args: ["x", "y", "block", "rotation", { key: "config", default: "" }],
|
|
3100
|
-
},
|
|
3101
|
-
getBlock: {
|
|
3102
|
-
args: ["x", "y"],
|
|
3103
|
-
},
|
|
3104
|
-
within: {
|
|
3105
|
-
named: "options",
|
|
3106
|
-
args: ["x", "y", "radius"],
|
|
3107
|
-
},
|
|
3108
|
-
unbind: {
|
|
3109
|
-
args: [],
|
|
3110
|
-
},
|
|
3111
|
-
},
|
|
3112
|
-
handler(scope, overload, out, ...args) {
|
|
3113
|
-
let result = null;
|
|
3114
|
-
let extraArgs = [];
|
|
3115
|
-
switch (overload) {
|
|
3116
|
-
case "getBlock": {
|
|
3117
|
-
const outType = StoreValue.from(scope, extractDestrucuringOut(out, 0));
|
|
3118
|
-
const outBuilding = SenseableValue.from(scope, extractDestrucuringOut(out, 1));
|
|
3119
|
-
const outFloor = StoreValue.from(scope, extractDestrucuringOut(out, 2));
|
|
3120
|
-
result = ObjectValue.fromArray([outType, outBuilding, outFloor]);
|
|
3121
|
-
extraArgs = [outType, outBuilding, outFloor];
|
|
3122
|
-
break;
|
|
3123
|
-
}
|
|
3124
|
-
case "within": {
|
|
3125
|
-
result = StoreValue.from(scope, out);
|
|
3126
|
-
extraArgs = [result, new LiteralValue(0)];
|
|
3127
|
-
break;
|
|
3128
|
-
}
|
|
3129
|
-
}
|
|
3130
|
-
return [
|
|
3131
|
-
result,
|
|
3132
|
-
[new InstructionBase("ucontrol", overload, ...args, ...extraArgs)],
|
|
3133
|
-
];
|
|
3134
|
-
},
|
|
3135
|
-
});
|
|
3136
|
-
super(data);
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
|
|
3140
|
-
class UnitRadar extends MacroFunction {
|
|
3141
|
-
constructor() {
|
|
3142
|
-
super((scope, out, options) => {
|
|
3143
|
-
assertIsObjectMacro(options, "The radar options");
|
|
3144
|
-
const { filters, order, sort } = options.data;
|
|
3145
|
-
assertIsArrayMacro(filters, "filters");
|
|
3146
|
-
const { length } = filters.data;
|
|
3147
|
-
if (!(length instanceof LiteralValue) || !length.isNumber())
|
|
3148
|
-
throw new CompilerError("The length of an array macro must be");
|
|
3149
|
-
if (length.data !== 3)
|
|
3150
|
-
throw new CompilerError("The filters array must have 3 items");
|
|
3151
|
-
// data is not an array
|
|
3152
|
-
const { 0: filter1, 1: filter2, 2: filter3 } = filters.data;
|
|
3153
|
-
assertLiteralOneOf(filter1, validRadarFilters, "The first filter");
|
|
3154
|
-
assertLiteralOneOf(filter2, validRadarFilters, "The second filter");
|
|
3155
|
-
assertLiteralOneOf(filter3, validRadarFilters, "The third filter");
|
|
3156
|
-
if (!(order instanceof LiteralValue || order instanceof StoreValue))
|
|
3157
|
-
throw new CompilerError("The radar order must be a literal or a store");
|
|
3158
|
-
assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
|
|
3159
|
-
const outUnit = SenseableValue.from(scope, out, EMutability.constant);
|
|
3160
|
-
return [
|
|
3161
|
-
outUnit,
|
|
3162
|
-
[
|
|
3163
|
-
new InstructionBase("uradar", filter1.data, filter2.data, filter3.data, sort.data, "0", // I don't know why, but mindustry requires this extra parameter
|
|
3164
|
-
order, outUnit),
|
|
3165
|
-
],
|
|
3166
|
-
];
|
|
3167
|
-
});
|
|
3168
|
-
}
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
|
-
const validBuildingGroups = [
|
|
3172
|
-
"core",
|
|
3173
|
-
"storage",
|
|
3174
|
-
"generator",
|
|
3175
|
-
"turret",
|
|
3176
|
-
"factory",
|
|
3177
|
-
"repair",
|
|
3178
|
-
"rally",
|
|
3179
|
-
"battery",
|
|
3180
|
-
"reactor",
|
|
3181
|
-
];
|
|
3182
|
-
class UnitLocate extends ObjectValue {
|
|
3183
|
-
constructor() {
|
|
3184
|
-
const data = createOverloadNamespace({
|
|
3185
|
-
overloads: {
|
|
3186
|
-
ore: {
|
|
3187
|
-
args: ["ore"],
|
|
3188
|
-
},
|
|
3189
|
-
building: { named: "options", args: ["group", "enemy"] },
|
|
3190
|
-
spawn: {
|
|
3191
|
-
args: [],
|
|
3192
|
-
},
|
|
3193
|
-
damaged: {
|
|
3194
|
-
args: [],
|
|
3195
|
-
},
|
|
3196
|
-
},
|
|
3197
|
-
handler(scope, overload, out, ...args) {
|
|
3198
|
-
const outFound = StoreValue.from(scope, extractDestrucuringOut(out, 0));
|
|
3199
|
-
const outX = StoreValue.from(scope, extractDestrucuringOut(out, 1));
|
|
3200
|
-
const outY = StoreValue.from(scope, extractDestrucuringOut(out, 2));
|
|
3201
|
-
const outBuilding = SenseableValue.from(scope, overload === "ore" ? discardedName : extractDestrucuringOut(out, 3));
|
|
3202
|
-
const outArgs = [outX, outY, outFound, outBuilding];
|
|
3203
|
-
let inputArgs = [];
|
|
3204
|
-
switch (overload) {
|
|
3205
|
-
case "ore": {
|
|
3206
|
-
const [ore] = args;
|
|
3207
|
-
inputArgs = [overload, "core", "true", ore];
|
|
3208
|
-
break;
|
|
3209
|
-
}
|
|
3210
|
-
case "building": {
|
|
3211
|
-
const [group, enemy] = args;
|
|
3212
|
-
if (!(group instanceof LiteralValue))
|
|
3213
|
-
throw new CompilerError("The building group must be a string literal");
|
|
3214
|
-
assertLiteralOneOf(group, validBuildingGroups, "The building group");
|
|
3215
|
-
inputArgs = [overload, group.data, enemy, "@copper"];
|
|
3216
|
-
break;
|
|
3217
|
-
}
|
|
3218
|
-
case "spawn":
|
|
3219
|
-
case "damaged": {
|
|
3220
|
-
inputArgs = [overload, "core", "true", "@copper"];
|
|
3221
|
-
break;
|
|
3222
|
-
}
|
|
3223
|
-
}
|
|
3224
|
-
return [
|
|
3225
|
-
ObjectValue.fromArray([
|
|
3226
|
-
outFound,
|
|
3227
|
-
outX,
|
|
3228
|
-
outY,
|
|
3229
|
-
...(overload !== "ore" ? [outBuilding] : []),
|
|
3230
|
-
]),
|
|
3231
|
-
[new InstructionBase("ulocate", ...inputArgs, ...outArgs)],
|
|
3232
|
-
];
|
|
3233
|
-
},
|
|
3234
|
-
});
|
|
3235
|
-
super(data);
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
|
|
3239
|
-
const validEffects = [
|
|
3240
|
-
"burning",
|
|
3241
|
-
"freezing",
|
|
3242
|
-
"unmoving",
|
|
3243
|
-
"wet",
|
|
3244
|
-
"melting",
|
|
3245
|
-
"sapped",
|
|
3246
|
-
"electrified",
|
|
3247
|
-
"spore-slowed",
|
|
3248
|
-
"tarred",
|
|
3249
|
-
"overdrive",
|
|
3250
|
-
"overclock",
|
|
3251
|
-
"boss",
|
|
3252
|
-
"shocked",
|
|
3253
|
-
"blasted",
|
|
3254
|
-
];
|
|
3255
|
-
class ApplyStatus extends ObjectValue {
|
|
3256
|
-
constructor() {
|
|
3257
|
-
const data = createOverloadNamespace({
|
|
3258
|
-
overloads: {
|
|
3259
|
-
apply: {
|
|
3260
|
-
args: ["status", "unit", { key: "duration", default: "10" }],
|
|
3261
|
-
},
|
|
3262
|
-
clear: { args: ["status", "unit"] },
|
|
3263
|
-
},
|
|
3264
|
-
handler(scope, overload, out, effect, unit, duration) {
|
|
3265
|
-
if (!(effect instanceof LiteralValue))
|
|
3266
|
-
throw new CompilerError("The status effect must be a string literal");
|
|
3267
|
-
assertLiteralOneOf(effect, validEffects, "The status effect");
|
|
3268
|
-
return [
|
|
3269
|
-
null,
|
|
3270
|
-
[
|
|
3271
|
-
new InstructionBase("status", String(overload !== "apply"), effect.data, unit, duration),
|
|
3272
|
-
],
|
|
3273
|
-
];
|
|
3274
|
-
},
|
|
3275
|
-
});
|
|
3276
|
-
super(data);
|
|
3277
|
-
}
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
|
-
class SpawnWave extends MacroFunction {
|
|
3281
|
-
constructor() {
|
|
3282
|
-
super((scope, out, natural, x, y) => {
|
|
3283
|
-
if (!(natural instanceof LiteralValue) ||
|
|
3284
|
-
(natural.data !== 1 && natural.data !== 0))
|
|
3285
|
-
throw new CompilerError("The 'natural' argument must be a boolean literal");
|
|
3286
|
-
return [
|
|
3287
|
-
null,
|
|
3288
|
-
[
|
|
3289
|
-
new InstructionBase("spawnwave", x ?? "10", y ?? "10", String(!!natural.data)),
|
|
3290
|
-
],
|
|
3291
|
-
];
|
|
3292
|
-
});
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
|
-
class SetRule extends ObjectValue {
|
|
3297
|
-
constructor() {
|
|
3298
|
-
const data = createOverloadNamespace({
|
|
3299
|
-
overloads: {
|
|
3300
|
-
currentWaveTime: { args: ["seconds"] },
|
|
3301
|
-
waveTimer: { args: ["enabled"] },
|
|
3302
|
-
waves: { args: ["enabled"] },
|
|
3303
|
-
wave: { args: ["number"] },
|
|
3304
|
-
waveSpacing: { args: ["seconds"] },
|
|
3305
|
-
waveSending: { args: ["enabled"] },
|
|
3306
|
-
attackMode: { args: ["enabled"] },
|
|
3307
|
-
enemyCoreBuildRadius: { args: ["radius"] },
|
|
3308
|
-
dropZoneRadius: { args: ["radius"] },
|
|
3309
|
-
unitCap: { args: ["cap"] },
|
|
3310
|
-
mapArea: { named: "options", args: ["x", "y", "width", "height"] },
|
|
3311
|
-
lighting: { args: ["enabled"] },
|
|
3312
|
-
ambientLight: { args: ["rgbaData"] },
|
|
3313
|
-
solarMultiplier: { args: ["multiplier"] },
|
|
3314
|
-
buildSpeed: { args: ["team", "multiplier"] },
|
|
3315
|
-
unitBuildSpeed: { args: ["team", "multiplier"] },
|
|
3316
|
-
unitCost: { args: ["team", "multiplier"] },
|
|
3317
|
-
unitDamage: { args: ["team", "multiplier"] },
|
|
3318
|
-
blockHealth: { args: ["team", "multiplier"] },
|
|
3319
|
-
blockDamage: { args: ["team", "multiplier"] },
|
|
3320
|
-
rtsMinWeight: { args: ["team", "value"] },
|
|
3321
|
-
rtsMinSquad: { args: ["team", "value"] },
|
|
3322
|
-
},
|
|
3323
|
-
handler(scope, overload, out, ...args) {
|
|
3324
|
-
const params = ["10", "0", "0", "100", "100"];
|
|
3325
|
-
switch (overload) {
|
|
3326
|
-
case "mapArea": {
|
|
3327
|
-
const [x, y, width, height] = args;
|
|
3328
|
-
params[1] = x;
|
|
3329
|
-
params[2] = y;
|
|
3330
|
-
params[3] = width;
|
|
3331
|
-
params[4] = height;
|
|
3332
|
-
break;
|
|
3333
|
-
}
|
|
3334
|
-
case "buildSpeed":
|
|
3335
|
-
case "unitBuildSpeed":
|
|
3336
|
-
case "unitCost":
|
|
3337
|
-
case "unitDamage":
|
|
3338
|
-
case "blockHealth":
|
|
3339
|
-
case "blockDamage":
|
|
3340
|
-
case "rtsMinWeight":
|
|
3341
|
-
case "rtsMinSquad": {
|
|
3342
|
-
const [team, value] = args;
|
|
3343
|
-
params[1] = team;
|
|
3344
|
-
params[0] = value;
|
|
3345
|
-
break;
|
|
3346
|
-
}
|
|
3347
|
-
default:
|
|
3348
|
-
params[0] = args[0]; // the general value
|
|
3349
|
-
}
|
|
3350
|
-
return [null, [new InstructionBase("setrule", overload, ...params)]];
|
|
3351
|
-
},
|
|
3352
|
-
});
|
|
3353
|
-
super(data);
|
|
3354
|
-
}
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
class FlushMessage extends ObjectValue {
|
|
3358
|
-
constructor() {
|
|
3359
|
-
const data = createOverloadNamespace({
|
|
3360
|
-
overloads: {
|
|
3361
|
-
notify: { args: [] },
|
|
3362
|
-
mission: { args: [] },
|
|
3363
|
-
announce: { args: ["duration"] },
|
|
3364
|
-
toast: { args: ["duration"] },
|
|
3365
|
-
},
|
|
3366
|
-
handler(scope, overload, out, duration) {
|
|
3367
|
-
return [null, [new InstructionBase("message", overload, duration)]];
|
|
3368
|
-
},
|
|
3369
|
-
});
|
|
3370
|
-
super(data);
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
|
|
3374
|
-
class Cutscene extends ObjectValue {
|
|
3375
|
-
constructor() {
|
|
3376
|
-
const data = createOverloadNamespace({
|
|
3377
|
-
overloads: {
|
|
3378
|
-
pan: { named: "options", args: ["x", "y", "speed"] },
|
|
3379
|
-
zoom: { args: ["level"] },
|
|
3380
|
-
stop: { args: [] },
|
|
3381
|
-
},
|
|
3382
|
-
handler(scope, overload, out, ...args) {
|
|
3383
|
-
const params = Object.assign(["100", "100", "0.06", "0"], args);
|
|
3384
|
-
return [null, [new InstructionBase("cutscene", overload, ...params)]];
|
|
3385
|
-
},
|
|
3386
|
-
});
|
|
3387
|
-
super(data);
|
|
3388
|
-
}
|
|
3389
|
-
}
|
|
3390
|
-
|
|
3391
|
-
class Explosion extends MacroFunction {
|
|
3392
|
-
constructor() {
|
|
3393
|
-
super((scope, out, options) => {
|
|
3394
|
-
assertIsObjectMacro(options, "The options");
|
|
3395
|
-
const params = assertObjectFields(options, [
|
|
3396
|
-
"team",
|
|
3397
|
-
"x",
|
|
3398
|
-
"y",
|
|
3399
|
-
"radius",
|
|
3400
|
-
"damage",
|
|
3401
|
-
"air",
|
|
3402
|
-
"ground",
|
|
3403
|
-
"pierce",
|
|
3404
|
-
]);
|
|
3405
|
-
return [null, [new InstructionBase("explosion", ...params)]];
|
|
3406
|
-
});
|
|
3407
|
-
}
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
|
-
class SetRate extends MacroFunction {
|
|
3411
|
-
constructor() {
|
|
3412
|
-
super((scope, out, ipt) => [null, [new InstructionBase("setrate", ipt)]]);
|
|
3413
|
-
}
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
|
-
class Fetch extends ObjectValue {
|
|
3417
|
-
constructor() {
|
|
3418
|
-
const data = createOverloadNamespace({
|
|
3419
|
-
overloads: {
|
|
3420
|
-
unit: { args: ["team", "index"] },
|
|
3421
|
-
unitCount: { args: ["team"] },
|
|
3422
|
-
player: { args: ["team", "index"] },
|
|
3423
|
-
playerCount: { args: ["team"] },
|
|
3424
|
-
core: { args: ["team", "index"] },
|
|
3425
|
-
coreCount: { args: ["team"] },
|
|
3426
|
-
build: { args: ["team", "index", "block"] },
|
|
3427
|
-
buildCount: { args: ["team", "block"] },
|
|
3428
|
-
},
|
|
3429
|
-
handler(scope, overload, out, team, ...rest) {
|
|
3430
|
-
const output = SenseableValue.from(scope, out, EMutability.constant);
|
|
3431
|
-
const params = ["0", "@conveyor"];
|
|
3432
|
-
if (overload === "buildCount") {
|
|
3433
|
-
params[1] = rest[0];
|
|
3434
|
-
}
|
|
3435
|
-
else {
|
|
3436
|
-
Object.assign(params, rest);
|
|
3437
|
-
}
|
|
3438
|
-
return [
|
|
3439
|
-
output,
|
|
3440
|
-
[new InstructionBase("fetch", overload, output, team, ...params)],
|
|
3441
|
-
];
|
|
3442
|
-
},
|
|
3443
|
-
});
|
|
3444
|
-
super(data);
|
|
3445
|
-
}
|
|
3446
|
-
}
|
|
3447
|
-
|
|
3448
|
-
class GetFlag extends MacroFunction {
|
|
3449
|
-
constructor() {
|
|
3450
|
-
super((scope, out, flag) => {
|
|
3451
|
-
const result = StoreValue.from(scope, out);
|
|
3452
|
-
return [result, [new InstructionBase("getflag", result, flag)]];
|
|
3453
|
-
});
|
|
3454
|
-
}
|
|
3455
|
-
}
|
|
3456
|
-
|
|
3457
|
-
class SetFlag extends MacroFunction {
|
|
3458
|
-
constructor() {
|
|
3459
|
-
super((scope, out, flag, value) => {
|
|
3460
|
-
if (!flag)
|
|
3461
|
-
throw new CompilerError("Missing flag argument");
|
|
3462
|
-
if (!value)
|
|
3463
|
-
throw new CompilerError("Missing flag value");
|
|
3464
|
-
return [null, [new InstructionBase("setflag", flag, value)]];
|
|
3465
|
-
});
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
|
|
3469
|
-
class MemoryEntry extends BaseValue {
|
|
3470
|
-
constructor(scope, mem, prop) {
|
|
3471
|
-
super();
|
|
3472
|
-
this.scope = scope;
|
|
3473
|
-
this.mem = mem;
|
|
3474
|
-
this.prop = prop;
|
|
3475
|
-
this.macro = true;
|
|
3476
|
-
this.store = null;
|
|
3477
|
-
}
|
|
3478
|
-
eval(scope, out) {
|
|
3479
|
-
if (this.store)
|
|
3480
|
-
return [this.store, []];
|
|
3481
|
-
const temp = (this.store = StoreValue.from(scope, out));
|
|
3482
|
-
return [
|
|
3483
|
-
temp,
|
|
3484
|
-
[new InstructionBase("read", temp, this.mem.cell, this.prop)],
|
|
3485
|
-
];
|
|
3486
|
-
}
|
|
3487
|
-
"="(scope, value) {
|
|
3488
|
-
const [data, dataInst] = value.eval(scope);
|
|
3489
|
-
return [
|
|
3490
|
-
data,
|
|
3491
|
-
[
|
|
3492
|
-
...dataInst,
|
|
3493
|
-
new InstructionBase("write", data, this.mem.cell, this.prop),
|
|
3494
|
-
],
|
|
3495
|
-
];
|
|
3496
|
-
}
|
|
3497
|
-
debugString() {
|
|
3498
|
-
return "MemoryEntry";
|
|
3499
|
-
}
|
|
3500
|
-
toString() {
|
|
3501
|
-
return '"[macro MemoryEntry]"';
|
|
3502
|
-
}
|
|
3503
|
-
}
|
|
3504
|
-
class MemoryMacro extends ObjectValue {
|
|
3505
|
-
constructor(cell, size) {
|
|
3506
|
-
super({
|
|
3507
|
-
length: size,
|
|
3508
|
-
size,
|
|
3509
|
-
});
|
|
3510
|
-
this.cell = cell;
|
|
3511
|
-
}
|
|
3512
|
-
get(scope, key, out) {
|
|
3513
|
-
if (super.hasProperty(scope, key))
|
|
3514
|
-
return super.get(scope, key, out);
|
|
3515
|
-
if (key instanceof LiteralValue && !key.isNumber())
|
|
3516
|
-
throw new CompilerError(`The member [${key.debugString()}] is not present in [${this.debugString()}]`);
|
|
3517
|
-
const entry = new MemoryEntry(scope, this, key);
|
|
3518
|
-
if (out)
|
|
3519
|
-
return entry.eval(scope, out);
|
|
3520
|
-
return [entry, []];
|
|
3521
|
-
}
|
|
3522
|
-
hasProperty(scope, prop) {
|
|
3523
|
-
if ((prop instanceof LiteralValue && prop.isNumber()) ||
|
|
3524
|
-
prop instanceof StoreValue)
|
|
3525
|
-
return true;
|
|
3526
|
-
return super.hasProperty(scope, prop);
|
|
3527
|
-
}
|
|
3528
|
-
debugString() {
|
|
3529
|
-
return `Memory("${this.cell.toString()}")`;
|
|
3530
|
-
}
|
|
3531
|
-
toString() {
|
|
3532
|
-
return '"[macro Memory]"';
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
class MemoryBuilder extends MacroFunction {
|
|
3536
|
-
constructor() {
|
|
3537
|
-
super((scope, out, cell, size = new LiteralValue(64)) => {
|
|
3538
|
-
if (!(cell instanceof SenseableValue))
|
|
3539
|
-
throw new CompilerError("Memory cell must be a senseable value.");
|
|
3540
|
-
if (!(size instanceof LiteralValue && size.isNumber()) &&
|
|
3541
|
-
!(size instanceof StoreValue))
|
|
3542
|
-
throw new CompilerError("The memory size must be a number literal or a store.");
|
|
3543
|
-
if (size.mutability === EMutability.constant) {
|
|
3544
|
-
return [new MemoryMacro(cell, size), []];
|
|
3545
|
-
}
|
|
3546
|
-
const name = extractOutName(out) ?? scope.makeTempName();
|
|
3547
|
-
const store = new StoreValue(`${name}.&len`);
|
|
3548
|
-
const [, inst] = store["="](scope, size);
|
|
3549
|
-
store.mutability = EMutability.constant;
|
|
3550
|
-
return [new MemoryMacro(cell, store), inst];
|
|
3551
|
-
});
|
|
3552
|
-
}
|
|
3553
|
-
}
|
|
3554
|
-
|
|
3555
|
-
const dynamicVars = ["time", "tick"];
|
|
3556
|
-
class NamespaceMacro extends ObjectValue {
|
|
3557
|
-
constructor({ changeCasing = false } = {}) {
|
|
3558
|
-
super();
|
|
3559
|
-
this.changeCasing = changeCasing;
|
|
3560
|
-
}
|
|
3561
|
-
get(scope, key, out) {
|
|
3562
|
-
if (super.hasProperty(scope, key))
|
|
3563
|
-
return super.get(scope, key, out);
|
|
3564
|
-
if (!(key instanceof LiteralValue) || !key.isString())
|
|
3565
|
-
throw new CompilerError("Cannot use dynamic properties on namespace macros");
|
|
3566
|
-
const symbolName = this.changeCasing ? camelToDashCase(key.data) : key.data;
|
|
3567
|
-
const mutability = dynamicVars.includes(symbolName)
|
|
3568
|
-
? EMutability.readonly
|
|
3569
|
-
: EMutability.constant;
|
|
3570
|
-
const result = new StoreValue(`@${symbolName}`, mutability);
|
|
3571
|
-
return [result, []];
|
|
3572
|
-
}
|
|
3573
|
-
hasProperty(scope, prop) {
|
|
3574
|
-
return prop instanceof LiteralValue && prop.isString();
|
|
3575
|
-
}
|
|
3576
|
-
}
|
|
3577
|
-
class VarsNamespace extends NamespaceMacro {
|
|
3578
|
-
constructor() {
|
|
3579
|
-
super();
|
|
3580
|
-
Object.assign(this.data, {
|
|
3581
|
-
unit: new SenseableValue("@unit"),
|
|
3582
|
-
this: new SenseableValue("@this"),
|
|
3583
|
-
});
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
|
|
3587
|
-
/**
|
|
3588
|
-
* The size of a dynamic array iteration item.
|
|
3589
|
-
* Includes the set counter instruction
|
|
3590
|
-
*/
|
|
3591
|
-
const itemSize = 2;
|
|
3592
|
-
class DynamicArray extends ObjectValue {
|
|
3593
|
-
constructor(scope, name, values, dynamic) {
|
|
3594
|
-
const getterName = `${name}.&read`;
|
|
3595
|
-
const setterName = `${name}.&write`;
|
|
3596
|
-
const returnName = `${name}.&rt`;
|
|
3597
|
-
const lengthName = getLengthName(name);
|
|
3598
|
-
const lengthStore = new StoreValue(lengthName);
|
|
3599
|
-
const sizeValue = new LiteralValue(values.length);
|
|
3600
|
-
super({
|
|
3601
|
-
fill: new MacroFunction((scope, out, value) => {
|
|
3602
|
-
if (!value)
|
|
3603
|
-
throw new CompilerError("Missing argument: value");
|
|
3604
|
-
this.initFill();
|
|
3605
|
-
const inst = [];
|
|
3606
|
-
const returnAddress = new LiteralValue(null);
|
|
3607
|
-
pipeInsts(values[0]["="](scope, value), inst);
|
|
3608
|
-
pipeInsts(this.returnTemp["="](scope, returnAddress), inst);
|
|
3609
|
-
inst.push(new JumpInstruction(this.fillAddr, EJumpKind.Always));
|
|
3610
|
-
inst.push(new AddressResolver(returnAddress));
|
|
3611
|
-
if (this.lengthStore) {
|
|
3612
|
-
pipeInsts(this.lengthStore["="](scope, new LiteralValue(values.length)), inst);
|
|
3613
|
-
}
|
|
3614
|
-
return [null, inst];
|
|
3615
|
-
}, [values[0]]),
|
|
3616
|
-
at: new MacroFunction((scope, out, index) => {
|
|
3617
|
-
const inst = [];
|
|
3618
|
-
const isLiteral = index instanceof LiteralValue;
|
|
3619
|
-
if (isLiteral) {
|
|
3620
|
-
assertBounds(index, -values.length, values.length - 1);
|
|
3621
|
-
}
|
|
3622
|
-
const indexTemp = isLiteral ? index : StoreValue.from(scope);
|
|
3623
|
-
const address = new LiteralValue(null);
|
|
3624
|
-
if (!isLiteral) {
|
|
3625
|
-
pipeInsts(indexTemp["="](scope, index), inst);
|
|
3626
|
-
inst.push(new JumpInstruction(address, EJumpKind.GreaterThanEq, indexTemp, new LiteralValue(0)));
|
|
3627
|
-
}
|
|
3628
|
-
const finalIndex = isLiteral && index.data >= 0
|
|
3629
|
-
? index
|
|
3630
|
-
: pipeInsts(indexTemp["+"](scope, this.lengthStore ?? sizeValue, indexTemp), inst);
|
|
3631
|
-
if (!isLiteral)
|
|
3632
|
-
inst.push(new AddressResolver(address));
|
|
3633
|
-
const entry = new DynamicArrayEntry({
|
|
3634
|
-
scope,
|
|
3635
|
-
array: this,
|
|
3636
|
-
index: finalIndex,
|
|
3637
|
-
checked: scope.checkIndexes,
|
|
3638
|
-
});
|
|
3639
|
-
const result = pipeInsts(entry.eval(scope, out), inst);
|
|
3640
|
-
return [result, inst];
|
|
3641
|
-
}),
|
|
3642
|
-
size: sizeValue,
|
|
3643
|
-
...(dynamic
|
|
3644
|
-
? {
|
|
3645
|
-
length: new StoreValue(lengthName, EMutability.readonly),
|
|
3646
|
-
push: new MacroFunction((scope, out, item) => {
|
|
3647
|
-
const checked = scope.checkIndexes;
|
|
3648
|
-
const entry = new DynamicArrayEntry({
|
|
3649
|
-
scope,
|
|
3650
|
-
array: this,
|
|
3651
|
-
index: lengthStore,
|
|
3652
|
-
checked,
|
|
3653
|
-
});
|
|
3654
|
-
const [, inst] = entry["="](scope, item);
|
|
3655
|
-
return [new LiteralValue(null), inst];
|
|
3656
|
-
}, [setterName]),
|
|
3657
|
-
pop: new MacroFunction((scope, out) => {
|
|
3658
|
-
const checked = scope.checkIndexes;
|
|
3659
|
-
const inst = [];
|
|
3660
|
-
const index = pipeInsts(lengthStore["-"](scope, new LiteralValue(1)), inst);
|
|
3661
|
-
const failAddress = new LiteralValue(null);
|
|
3662
|
-
const entry = new DynamicArrayEntry({
|
|
3663
|
-
scope,
|
|
3664
|
-
array: this,
|
|
3665
|
-
index,
|
|
3666
|
-
checked,
|
|
3667
|
-
newLength: index,
|
|
3668
|
-
failAddress,
|
|
3669
|
-
});
|
|
3670
|
-
const nullLiteral = new LiteralValue(null);
|
|
3671
|
-
const hasOut = !isDiscardedOut(out);
|
|
3672
|
-
const result = hasOut
|
|
3673
|
-
? pipeInsts(entry.eval(scope, out), inst)
|
|
3674
|
-
: nullLiteral;
|
|
3675
|
-
// only add checks if the scope is checked and
|
|
3676
|
-
// aren't any previous checks
|
|
3677
|
-
entry.checked = checked && !hasOut;
|
|
3678
|
-
pipeInsts(entry["="](scope, nullLiteral), inst);
|
|
3679
|
-
inst.push(new AddressResolver(failAddress));
|
|
3680
|
-
return [result, inst];
|
|
3681
|
-
}),
|
|
3682
|
-
removeAt: new MacroFunction((scope, out, index) => {
|
|
3683
|
-
if (!index)
|
|
3684
|
-
throw new CompilerError("Missing index argument");
|
|
3685
|
-
this.initRemoveAt();
|
|
3686
|
-
if (index instanceof LiteralValue) {
|
|
3687
|
-
assertBounds(index, 0, values.length - 1);
|
|
3688
|
-
}
|
|
3689
|
-
const checked = scope.checkIndexes;
|
|
3690
|
-
const hasOut = !isDiscardedOut(out);
|
|
3691
|
-
const { returnTemp } = this;
|
|
3692
|
-
const inst = [];
|
|
3693
|
-
let result = new LiteralValue(null);
|
|
3694
|
-
const returnAdress = new LiteralValue(null);
|
|
3695
|
-
const failAddress = new LiteralValue(null);
|
|
3696
|
-
const upperBound = new JumpInstruction(failAddress, EJumpKind.GreaterThanEq, index, lengthStore);
|
|
3697
|
-
if (hasOut) {
|
|
3698
|
-
const entry = new DynamicArrayEntry({
|
|
3699
|
-
scope,
|
|
3700
|
-
array: this,
|
|
3701
|
-
checked: scope.checkIndexes,
|
|
3702
|
-
index,
|
|
3703
|
-
failAddress,
|
|
3704
|
-
upperBound,
|
|
3705
|
-
});
|
|
3706
|
-
if (index instanceof LiteralValue) {
|
|
3707
|
-
const value = pipeInsts(entry.eval(scope), inst);
|
|
3708
|
-
result = StoreValue.from(scope, out);
|
|
3709
|
-
pipeInsts(result["="](scope, value), inst);
|
|
3710
|
-
}
|
|
3711
|
-
else {
|
|
3712
|
-
result = pipeInsts(entry.eval(scope, out), inst);
|
|
3713
|
-
}
|
|
3714
|
-
}
|
|
3715
|
-
if (checked) {
|
|
3716
|
-
if (!hasOut && !(index instanceof LiteralValue)) {
|
|
3717
|
-
inst.push(new JumpInstruction(failAddress, EJumpKind.LessThan, index, new LiteralValue(0)));
|
|
3718
|
-
}
|
|
3719
|
-
if (!hasOut || index instanceof LiteralValue)
|
|
3720
|
-
inst.push(upperBound);
|
|
3721
|
-
}
|
|
3722
|
-
pipeInsts(returnTemp["="](scope, returnAdress), inst);
|
|
3723
|
-
const counter = new StoreValue(counterName);
|
|
3724
|
-
inst.push(new InstructionBase("op", "add", counter, this.removeAtAddr, index));
|
|
3725
|
-
inst.push(new AddressResolver(returnAdress));
|
|
3726
|
-
pipeInsts(lengthStore["--"](scope, true), inst);
|
|
3727
|
-
inst.push(new AddressResolver(failAddress));
|
|
3728
|
-
return [result, inst];
|
|
3729
|
-
}),
|
|
3730
|
-
}
|
|
3731
|
-
: {}),
|
|
3732
|
-
});
|
|
3733
|
-
this.scope = scope;
|
|
3734
|
-
this.name = name;
|
|
3735
|
-
this.values = values;
|
|
3736
|
-
this.dynamic = dynamic;
|
|
3737
|
-
this.bundledSetter = false;
|
|
3738
|
-
this.bundledGetter = false;
|
|
3739
|
-
this.bundledRemoveAt = false;
|
|
3740
|
-
this.bundledFill = false;
|
|
3741
|
-
this.scope = scope;
|
|
3742
|
-
this.name = name;
|
|
3743
|
-
this.values = values;
|
|
3744
|
-
this.getterTemp = new SenseableValue(getterName, EMutability.mutable);
|
|
3745
|
-
this.setterTemp = new SenseableValue(setterName, EMutability.mutable);
|
|
3746
|
-
this.returnTemp = new StoreValue(returnName);
|
|
3747
|
-
if (dynamic)
|
|
3748
|
-
this.lengthStore = lengthStore;
|
|
3749
|
-
this.getterAddr = new LiteralValue(null);
|
|
3750
|
-
this.setterAddr = new LiteralValue(null);
|
|
3751
|
-
this.removeAtAddr = new LiteralValue(null);
|
|
3752
|
-
this.fillAddr = new LiteralValue(null);
|
|
3753
|
-
}
|
|
3754
|
-
get(scope, index, out) {
|
|
3755
|
-
if (super.hasProperty(scope, index))
|
|
3756
|
-
return super.get(scope, index, out);
|
|
3757
|
-
const { values } = this;
|
|
3758
|
-
if (index instanceof LiteralValue) {
|
|
3759
|
-
assertBounds(index, 0, values.length - 1);
|
|
3760
|
-
}
|
|
3761
|
-
const entry = new DynamicArrayEntry({
|
|
3762
|
-
scope,
|
|
3763
|
-
array: this,
|
|
3764
|
-
index,
|
|
3765
|
-
checked: scope.checkIndexes,
|
|
3766
|
-
});
|
|
3767
|
-
if (out)
|
|
3768
|
-
return entry.eval(scope, out);
|
|
3769
|
-
return [entry, []];
|
|
3770
|
-
}
|
|
3771
|
-
hasProperty(scope, prop) {
|
|
3772
|
-
if (prop instanceof LiteralValue && prop.isNumber()) {
|
|
3773
|
-
return prop.data >= 0 && prop.data < this.values.length;
|
|
3774
|
-
}
|
|
3775
|
-
if (prop instanceof StoreValue)
|
|
3776
|
-
return true;
|
|
3777
|
-
return super.hasProperty(scope, prop);
|
|
3778
|
-
}
|
|
3779
|
-
initGetter() {
|
|
3780
|
-
if (this.bundledGetter)
|
|
3781
|
-
return;
|
|
3782
|
-
this.bundledGetter = true;
|
|
3783
|
-
this.scope.inst.push(new AddressResolver(this.getterAddr));
|
|
3784
|
-
for (const value of this.values) {
|
|
3785
|
-
this.scope.inst.push(new SetInstruction(this.getterTemp, value), new SetCounterInstruction(this.returnTemp));
|
|
3786
|
-
}
|
|
3787
|
-
}
|
|
3788
|
-
initSetter() {
|
|
3789
|
-
if (this.bundledSetter)
|
|
3790
|
-
return;
|
|
3791
|
-
this.bundledSetter = true;
|
|
3792
|
-
this.scope.inst.push(new AddressResolver(this.setterAddr));
|
|
3793
|
-
for (const value of this.values) {
|
|
3794
|
-
this.scope.inst.push(new SetInstruction(value, this.setterTemp), new SetCounterInstruction(this.returnTemp));
|
|
3795
|
-
}
|
|
3796
|
-
}
|
|
3797
|
-
initRemoveAt() {
|
|
3798
|
-
if (this.bundledRemoveAt)
|
|
3799
|
-
return;
|
|
3800
|
-
this.bundledRemoveAt = true;
|
|
3801
|
-
const { inst } = this.scope;
|
|
3802
|
-
inst.push(new AddressResolver(this.removeAtAddr));
|
|
3803
|
-
for (let i = 0; i < this.values.length - 1; i++) {
|
|
3804
|
-
const value = this.values[i];
|
|
3805
|
-
const next = this.values[i + 1];
|
|
3806
|
-
pipeInsts(value["="](this.scope, next), inst);
|
|
3807
|
-
}
|
|
3808
|
-
const last = this.values[this.values.length - 1];
|
|
3809
|
-
pipeInsts(last["="](this.scope, new LiteralValue(null)), inst);
|
|
3810
|
-
inst.push(new SetCounterInstruction(this.returnTemp));
|
|
3811
|
-
}
|
|
3812
|
-
initFill() {
|
|
3813
|
-
if (this.bundledFill)
|
|
3814
|
-
return;
|
|
3815
|
-
this.bundledFill = true;
|
|
3816
|
-
const { inst } = this.scope;
|
|
3817
|
-
inst.push(new AddressResolver(this.fillAddr));
|
|
3818
|
-
const firstValue = this.values[0];
|
|
3819
|
-
for (let i = 1; i < this.values.length; i++) {
|
|
3820
|
-
const value = this.values[i];
|
|
3821
|
-
pipeInsts(value["="](this.scope, firstValue), inst);
|
|
3822
|
-
}
|
|
3823
|
-
inst.push(new SetCounterInstruction(this.returnTemp));
|
|
3824
|
-
}
|
|
3825
|
-
debugString() {
|
|
3826
|
-
const id = this.dynamic ? "DynamicArray" : "MutableArray";
|
|
3827
|
-
return `${id}("${this.name}")`;
|
|
3828
|
-
}
|
|
3829
|
-
toString() {
|
|
3830
|
-
const id = this.dynamic ? "DynamicArray" : "MutableArray";
|
|
3831
|
-
return `"[macro ${id}]"`;
|
|
3832
|
-
}
|
|
3833
|
-
}
|
|
3834
|
-
class DynamicArrayEntry extends BaseValue {
|
|
3835
|
-
constructor({ array, checked, index, scope, newLength, failAddress, upperBound, }) {
|
|
3836
|
-
super();
|
|
3837
|
-
this.macro = true;
|
|
3838
|
-
this.array = array;
|
|
3839
|
-
this.scope = scope;
|
|
3840
|
-
this.checked = checked;
|
|
3841
|
-
this.index = index;
|
|
3842
|
-
this.newLength = newLength;
|
|
3843
|
-
this.failAddress = failAddress;
|
|
3844
|
-
this.upperBound = upperBound;
|
|
3845
|
-
}
|
|
3846
|
-
eval(scope, out) {
|
|
3847
|
-
const { checked, index } = this;
|
|
3848
|
-
if (index instanceof LiteralValue) {
|
|
3849
|
-
return [this.array.values[index.data], []];
|
|
3850
|
-
}
|
|
3851
|
-
this.array.initGetter();
|
|
3852
|
-
const inst = [];
|
|
3853
|
-
const { getterTemp, values, getterAddr, returnTemp } = this.array;
|
|
3854
|
-
// the value will be stored somewhere
|
|
3855
|
-
// no need to save it in a temporary variable
|
|
3856
|
-
const temp = out instanceof StoreValue
|
|
3857
|
-
? out
|
|
3858
|
-
: out
|
|
3859
|
-
? getterTemp
|
|
3860
|
-
: SenseableValue.from(scope, undefined, EMutability.mutable);
|
|
3861
|
-
// used in checked mode, jumps to this address
|
|
3862
|
-
// if the index is out of bounds
|
|
3863
|
-
const failAddr = this.failAddress ?? new LiteralValue(null);
|
|
3864
|
-
if (checked) {
|
|
3865
|
-
inst.push(...temp["="](scope, new LiteralValue(null))[1], new JumpInstruction(failAddr, EJumpKind.LessThan, index, new LiteralValue(0)), this.upperBound ??
|
|
3866
|
-
new JumpInstruction(failAddr, EJumpKind.GreaterThan, index, new LiteralValue(values.length - 1)));
|
|
3867
|
-
}
|
|
3868
|
-
const returnAdress = new LiteralValue(null);
|
|
3869
|
-
pipeInsts(returnTemp["="](scope, returnAdress), inst);
|
|
3870
|
-
const counter = new StoreValue(counterName);
|
|
3871
|
-
const doubleIndex = pipeInsts(index["*"](scope, new LiteralValue(itemSize)), inst);
|
|
3872
|
-
const line = pipeInsts(getterAddr["+"](scope, doubleIndex, counter), inst);
|
|
3873
|
-
pipeInsts(counter["="](scope, line), inst);
|
|
3874
|
-
inst.push(new AddressResolver(returnAdress));
|
|
3875
|
-
// without this you can't access the array twice inside the same expression
|
|
3876
|
-
pipeInsts(temp["="](scope, getterTemp), inst);
|
|
3877
|
-
if (checked && !this.failAddress) {
|
|
3878
|
-
inst.push(new AddressResolver(failAddr));
|
|
3879
|
-
}
|
|
3880
|
-
return [temp, inst];
|
|
3881
|
-
}
|
|
3882
|
-
"="(scope, value) {
|
|
3883
|
-
const { checked, index } = this;
|
|
3884
|
-
const { setterTemp, values, setterAddr, returnTemp, lengthStore } = this.array;
|
|
3885
|
-
const inst = [];
|
|
3886
|
-
// where to jump in checked mode if the index is out of range
|
|
3887
|
-
const failAddress = this.failAddress ?? new LiteralValue(null);
|
|
3888
|
-
if (index instanceof LiteralValue) {
|
|
3889
|
-
const member = values[index.data];
|
|
3890
|
-
pipeInsts(member["="](scope, value), inst);
|
|
3891
|
-
}
|
|
3892
|
-
else {
|
|
3893
|
-
this.array.initSetter();
|
|
3894
|
-
// used in both checked and unchecked modes
|
|
3895
|
-
// indicates where to jump after success
|
|
3896
|
-
const returnAdress = new LiteralValue(null);
|
|
3897
|
-
if (checked) {
|
|
3898
|
-
inst.push(new JumpInstruction(failAddress, EJumpKind.LessThan, index, new LiteralValue(0)), this.upperBound ??
|
|
3899
|
-
new JumpInstruction(failAddress, EJumpKind.GreaterThan, index, new LiteralValue(values.length - 1)));
|
|
3900
|
-
}
|
|
3901
|
-
pipeInsts(setterTemp["="](scope, value), inst);
|
|
3902
|
-
pipeInsts(returnTemp["="](scope, returnAdress), inst);
|
|
3903
|
-
const counter = new StoreValue(counterName);
|
|
3904
|
-
const doubleIndex = pipeInsts(index["*"](scope, new LiteralValue(itemSize)), inst);
|
|
3905
|
-
const line = pipeInsts(setterAddr["+"](scope, doubleIndex, counter), inst);
|
|
3906
|
-
pipeInsts(counter["="](scope, line), inst);
|
|
3907
|
-
inst.push(new AddressResolver(returnAdress));
|
|
3908
|
-
}
|
|
3909
|
-
if (lengthStore) {
|
|
3910
|
-
if (this.newLength) {
|
|
3911
|
-
pipeInsts(lengthStore["="](scope, this.newLength), inst);
|
|
3912
|
-
}
|
|
3913
|
-
else {
|
|
3914
|
-
const indexIsLength = index.name === lengthStore.name;
|
|
3915
|
-
const len = pipeInsts(index["+"](scope, new LiteralValue(1), indexIsLength ? lengthStore : undefined), inst);
|
|
3916
|
-
if (!indexIsLength)
|
|
3917
|
-
inst.push(new OperationInstruction("max", lengthStore, len, lengthStore));
|
|
3918
|
-
}
|
|
3919
|
-
}
|
|
3920
|
-
if (!this.failAddress)
|
|
3921
|
-
inst.push(new AddressResolver(failAddress));
|
|
3922
|
-
return [value, inst];
|
|
3923
|
-
}
|
|
3924
|
-
toOut() {
|
|
3925
|
-
return this.array.setterTemp;
|
|
3926
|
-
}
|
|
3927
|
-
debugString() {
|
|
3928
|
-
const name = this.array.dynamic ? "DynamicArray" : "MutableArray";
|
|
3929
|
-
return `${name}Entry`;
|
|
3930
|
-
}
|
|
3931
|
-
toString() {
|
|
3932
|
-
const name = this.array.dynamic ? "DynamicArray" : "MutableArray";
|
|
3933
|
-
return `"[macro ${name}Entry]"`;
|
|
3934
|
-
}
|
|
3935
|
-
}
|
|
3936
|
-
class DynamicArrayConstructor extends MacroFunction {
|
|
3937
|
-
constructor(dynamic) {
|
|
3938
|
-
super((scope, out, init) => {
|
|
3939
|
-
const name = extractOutName(out) ?? scope.makeTempName();
|
|
3940
|
-
const inst = [];
|
|
3941
|
-
const values = [];
|
|
3942
|
-
let length;
|
|
3943
|
-
if (init instanceof LiteralValue && typeof init.data === "number") {
|
|
3944
|
-
length = init.data;
|
|
3945
|
-
}
|
|
3946
|
-
else if (init instanceof ObjectValue) {
|
|
3947
|
-
assertIsArrayMacro(init, "The array initializer");
|
|
3948
|
-
length = init.data.length.data;
|
|
3949
|
-
}
|
|
3950
|
-
else {
|
|
3951
|
-
throw new CompilerError("The dynamic array initializer must be an array macro or a number literal");
|
|
3952
|
-
}
|
|
3953
|
-
for (let i = 0; i < length; i++) {
|
|
3954
|
-
values.push(new SenseableValue(`${name}->${i}`, EMutability.mutable));
|
|
3955
|
-
}
|
|
3956
|
-
if (init instanceof ObjectValue) {
|
|
3957
|
-
const length = init.data.length.data;
|
|
3958
|
-
for (let i = 0; i < length; i++) {
|
|
3959
|
-
const value = pipeInsts(init.get(scope, new LiteralValue(i), values[i]), inst);
|
|
3960
|
-
pipeInsts(values[i]["="](scope, value), inst);
|
|
3961
|
-
}
|
|
3962
|
-
}
|
|
3963
|
-
if (dynamic) {
|
|
3964
|
-
const lengthStore = new StoreValue(getLengthName(name));
|
|
3965
|
-
pipeInsts(lengthStore["="](scope, new LiteralValue(init instanceof ObjectValue ? length : 0)), inst);
|
|
3966
|
-
}
|
|
3967
|
-
return [new DynamicArray(scope, name, values, dynamic), inst];
|
|
3968
|
-
});
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
const getLengthName = (name) => `${name}.&len`;
|
|
3972
|
-
function assertBounds(
|
|
3973
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3974
|
-
index, min, max) {
|
|
3975
|
-
if (!index.isNumber())
|
|
3976
|
-
throw new CompilerError("The index must be a store or a number literal");
|
|
3977
|
-
if (index.data < min || index.data > max)
|
|
3978
|
-
throw new CompilerError(`The index "${index.data}" is out of bounds: [${min}, ${max}]`);
|
|
3979
|
-
}
|
|
3980
|
-
|
|
3981
|
-
class Unchecked extends MacroFunction {
|
|
3982
|
-
constructor() {
|
|
3983
|
-
super((scope, out, expression) => [expression, []]);
|
|
3984
|
-
}
|
|
3985
|
-
preCall(scope, out) {
|
|
3986
|
-
scope.checkIndexes = false;
|
|
3987
|
-
if (out)
|
|
3988
|
-
return [out];
|
|
3989
|
-
}
|
|
3990
|
-
postCall(scope) {
|
|
3991
|
-
scope.checkIndexes = true;
|
|
3992
|
-
}
|
|
3993
|
-
}
|
|
3994
|
-
|
|
3995
|
-
class GetGlobal extends MacroFunction {
|
|
3996
|
-
constructor(mutability) {
|
|
3997
|
-
super((scope, out, name) => {
|
|
3998
|
-
if (!(name instanceof LiteralValue) || !name.isString())
|
|
3999
|
-
throw new CompilerError("The name parameter must be a string literal.");
|
|
4000
|
-
const result = new SenseableValue(name.data, mutability);
|
|
4001
|
-
return [result, []];
|
|
4002
|
-
});
|
|
4003
|
-
}
|
|
4004
|
-
}
|
|
4005
|
-
|
|
4006
|
-
class Asm extends MacroFunction {
|
|
4007
|
-
constructor() {
|
|
4008
|
-
super((scope, out, stringsArray, ...values) => {
|
|
4009
|
-
if (!isTemplateObjectArray(stringsArray))
|
|
4010
|
-
throw new CompilerError("Expected to receive a template strings array");
|
|
4011
|
-
const args = [];
|
|
4012
|
-
for (let i = 0; i < values.length; i++) {
|
|
4013
|
-
const [item] = stringsArray.get(scope, new LiteralValue(i));
|
|
4014
|
-
args.push(item.data);
|
|
4015
|
-
args.push(values[i]);
|
|
4016
|
-
}
|
|
4017
|
-
const { length } = stringsArray.data;
|
|
4018
|
-
const [tail] = stringsArray.get(scope, new LiteralValue(length.data - 1));
|
|
4019
|
-
args.push(tail.data);
|
|
4020
|
-
return [null, formatInstructions(args)];
|
|
4021
|
-
});
|
|
4022
|
-
}
|
|
4023
|
-
}
|
|
4024
|
-
/** Splits multiline asm calls and formats each line. */
|
|
4025
|
-
function formatInstructions(args) {
|
|
4026
|
-
const instructions = [];
|
|
4027
|
-
let buffer = [];
|
|
4028
|
-
for (let i = 0; i < args.length; i++) {
|
|
4029
|
-
const arg = args[i];
|
|
4030
|
-
if (typeof arg !== "string" || !arg.includes("\n")) {
|
|
4031
|
-
buffer.push(arg);
|
|
4032
|
-
continue;
|
|
4033
|
-
}
|
|
4034
|
-
const segments = arg.split("\n");
|
|
4035
|
-
const params = [...buffer, segments[0]];
|
|
4036
|
-
if (validateInstructionArgs(params)) {
|
|
4037
|
-
instructions.push(new ZeroSpaceInstruction(...params));
|
|
4038
|
-
buffer = [];
|
|
4039
|
-
}
|
|
4040
|
-
// iterates over all segments except the first and last
|
|
4041
|
-
// the first segment was already processed above
|
|
4042
|
-
// the last segment will be added to the buffer because there might
|
|
4043
|
-
// be an interpolation after it
|
|
4044
|
-
// and putting it in the buffer makes sure it has a proper output format
|
|
4045
|
-
for (let i = 1; i < segments.length - 1; i++) {
|
|
4046
|
-
const trimmed = segments[i].trim();
|
|
4047
|
-
if (trimmed.length == 0)
|
|
4048
|
-
continue;
|
|
4049
|
-
instructions.push(new ZeroSpaceInstruction(trimmed));
|
|
4050
|
-
}
|
|
4051
|
-
if (segments.length > 1) {
|
|
4052
|
-
buffer.push(segments[segments.length - 1]);
|
|
4053
|
-
}
|
|
4054
|
-
}
|
|
4055
|
-
if (buffer.length > 0 && validateInstructionArgs(buffer)) {
|
|
4056
|
-
instructions.push(new ZeroSpaceInstruction(...buffer));
|
|
4057
|
-
}
|
|
4058
|
-
return instructions;
|
|
4059
|
-
}
|
|
4060
|
-
/** Determines if an asm line should be generated and trims it at the start and end */
|
|
4061
|
-
function validateInstructionArgs(args) {
|
|
4062
|
-
if (args.length === 0)
|
|
4063
|
-
return false;
|
|
4064
|
-
if (args.length === 1) {
|
|
4065
|
-
const item = args[0];
|
|
4066
|
-
if (typeof item !== "string")
|
|
4067
|
-
return true;
|
|
4068
|
-
args[0] = item.trim();
|
|
4069
|
-
return args[0].length !== 0;
|
|
4070
|
-
}
|
|
4071
|
-
const first = args[0];
|
|
4072
|
-
const last = args[args.length - 1];
|
|
4073
|
-
if (typeof first === "string")
|
|
4074
|
-
args[0] = first.trimStart();
|
|
4075
|
-
if (typeof last === "string")
|
|
4076
|
-
args[args.length - 1] = last.trimEnd();
|
|
4077
|
-
return true;
|
|
4078
|
-
}
|
|
4079
|
-
|
|
4080
|
-
/**
|
|
4081
|
-
* Adds all the compiler globals to `scope`
|
|
4082
|
-
*/
|
|
4083
|
-
function initScope(scope) {
|
|
4084
|
-
scope.hardSet("undefined", new LiteralValue(null));
|
|
4085
|
-
// namespaces
|
|
4086
|
-
scope.hardSet("ControlKind", new NamespaceMacro());
|
|
4087
|
-
scope.hardSet("Vars", new VarsNamespace());
|
|
4088
|
-
scope.hardSet("Teams", new NamespaceMacro());
|
|
4089
|
-
scope.hardSet("Items", new NamespaceMacro({ changeCasing: true }));
|
|
4090
|
-
scope.hardSet("Liquids", new NamespaceMacro());
|
|
4091
|
-
scope.hardSet("Units", new NamespaceMacro({ changeCasing: true }));
|
|
4092
|
-
scope.hardSet("LAccess", new NamespaceMacro());
|
|
4093
|
-
scope.hardSet("Blocks", new NamespaceMacro({ changeCasing: true }));
|
|
4094
|
-
// helper methods
|
|
4095
|
-
scope.hardSet("getBuilding", new GetGlobal(EMutability.constant));
|
|
4096
|
-
scope.hardSet("getVar", new GetGlobal(EMutability.mutable));
|
|
4097
|
-
scope.hardSet("concat", new Concat());
|
|
4098
|
-
scope.hardSet("asm", new Asm());
|
|
4099
|
-
scope.hardSet("Math", new MlogMath());
|
|
4100
|
-
scope.hardSet("Memory", new MemoryBuilder());
|
|
4101
|
-
scope.hardSet("MutableArray", new DynamicArrayConstructor(false));
|
|
4102
|
-
scope.hardSet("DynamicArray", new DynamicArrayConstructor(true));
|
|
4103
|
-
scope.hardSet("unchecked", new Unchecked());
|
|
4104
|
-
// commands
|
|
4105
|
-
scope.hardSet("draw", new Draw());
|
|
4106
|
-
scope.hardSet("print", new Print());
|
|
4107
|
-
scope.hardSet("printFlush", new PrintFlush());
|
|
4108
|
-
scope.hardSet("drawFlush", new DrawFlush());
|
|
4109
|
-
scope.hardSet("getLink", new GetLink());
|
|
4110
|
-
scope.hardSet("control", new Control());
|
|
4111
|
-
scope.hardSet("radar", new Radar());
|
|
4112
|
-
scope.hardSet("sensor", new Sensor());
|
|
4113
|
-
scope.hardSet("wait", new Wait());
|
|
4114
|
-
scope.hardSet("lookup", new Lookup());
|
|
4115
|
-
scope.hardSet("packColor", new PackColor());
|
|
4116
|
-
scope.hardSet("endScript", new End());
|
|
4117
|
-
scope.hardSet("stopScript", new Stop());
|
|
4118
|
-
scope.hardSet("unitBind", new UnitBind());
|
|
4119
|
-
scope.hardSet("unitControl", new UnitControl());
|
|
4120
|
-
scope.hardSet("unitRadar", new UnitRadar());
|
|
4121
|
-
scope.hardSet("unitLocate", new UnitLocate());
|
|
4122
|
-
// world processor commands
|
|
4123
|
-
scope.hardSet("getBlock", new GetBlock());
|
|
4124
|
-
scope.hardSet("setBlock", new SetBlock());
|
|
4125
|
-
scope.hardSet("spawnUnit", new SpawnUnit());
|
|
4126
|
-
scope.hardSet("applyStatus", new ApplyStatus());
|
|
4127
|
-
scope.hardSet("spawnWave", new SpawnWave());
|
|
4128
|
-
scope.hardSet("setRule", new SetRule());
|
|
4129
|
-
scope.hardSet("flushMessage", new FlushMessage());
|
|
4130
|
-
scope.hardSet("cutscene", new Cutscene());
|
|
4131
|
-
scope.hardSet("explosion", new Explosion());
|
|
4132
|
-
scope.hardSet("setRate", new SetRate());
|
|
4133
|
-
scope.hardSet("fetch", new Fetch());
|
|
4134
|
-
scope.hardSet("getFlag", new GetFlag());
|
|
4135
|
-
scope.hardSet("setFlag", new SetFlag());
|
|
4136
|
-
}
|
|
4137
|
-
|
|
4138
|
-
class Compiler {
|
|
4139
|
-
constructor({ stackName, compactNames = false, sourcemap = false, } = {}) {
|
|
4140
|
-
this.handlers = handlers;
|
|
4141
|
-
this.usingStack = !!stackName;
|
|
4142
|
-
this.stackName = stackName;
|
|
4143
|
-
this.compactNames = compactNames;
|
|
4144
|
-
this.sourcemap = sourcemap;
|
|
4145
|
-
}
|
|
4146
|
-
compile(script) {
|
|
4147
|
-
let output;
|
|
4148
|
-
let sourcemaps;
|
|
4149
|
-
try {
|
|
4150
|
-
const program = this.parse(script);
|
|
4151
|
-
const scope = new Scope({});
|
|
4152
|
-
initScope(scope);
|
|
4153
|
-
const valueInst = this.handle(scope, program);
|
|
4154
|
-
if (needsEndInstruction(valueInst[1], scope)) {
|
|
4155
|
-
valueInst[1].push(new EndInstruction());
|
|
4156
|
-
}
|
|
4157
|
-
valueInst[1].push(...scope.inst);
|
|
4158
|
-
hideRedundantJumps(valueInst[1]);
|
|
4159
|
-
this.resolve(valueInst);
|
|
4160
|
-
output = this.serialize(valueInst) + "\n";
|
|
4161
|
-
if (this.sourcemap) {
|
|
4162
|
-
sourcemaps = this.mapSources(valueInst);
|
|
4163
|
-
}
|
|
4164
|
-
}
|
|
4165
|
-
catch (error) {
|
|
4166
|
-
const err = error instanceof CompilerError ? error : CompilerError.from(error);
|
|
4167
|
-
return [null, err];
|
|
4168
|
-
}
|
|
4169
|
-
return [output, null, sourcemaps];
|
|
4170
|
-
}
|
|
4171
|
-
resolve(valueInst) {
|
|
4172
|
-
let i = 0;
|
|
4173
|
-
for (const inst of valueInst[1]) {
|
|
4174
|
-
inst.resolve(i);
|
|
4175
|
-
if (!inst.hidden)
|
|
4176
|
-
i++;
|
|
4177
|
-
}
|
|
4178
|
-
}
|
|
4179
|
-
serialize(resLines) {
|
|
4180
|
-
const [, inst] = resLines;
|
|
4181
|
-
return inst.filter(l => !l.hidden).join("\n");
|
|
4182
|
-
}
|
|
4183
|
-
parse(script) {
|
|
4184
|
-
return parse(script, {
|
|
4185
|
-
ranges: true,
|
|
4186
|
-
plugins: ["typescript"],
|
|
4187
|
-
});
|
|
4188
|
-
}
|
|
4189
|
-
mapSources(valueInst) {
|
|
4190
|
-
const result = [];
|
|
4191
|
-
for (const inst of valueInst[1]) {
|
|
4192
|
-
if (!inst.hidden)
|
|
4193
|
-
result.push(inst.source);
|
|
4194
|
-
}
|
|
4195
|
-
return result;
|
|
4196
|
-
}
|
|
4197
|
-
handle(scope, node, handler = this.handlers[node.type], out) {
|
|
4198
|
-
try {
|
|
4199
|
-
if (!handler)
|
|
4200
|
-
throw new CompilerError("Missing handler for " + node.type);
|
|
4201
|
-
const result = handler(this, scope, node, out, null);
|
|
4202
|
-
if (this.sourcemap)
|
|
4203
|
-
return appendSourceLocations(result, node);
|
|
4204
|
-
return result;
|
|
4205
|
-
}
|
|
4206
|
-
catch (error) {
|
|
4207
|
-
if (error instanceof CompilerError) {
|
|
4208
|
-
error.loc ??= node.loc;
|
|
4209
|
-
}
|
|
4210
|
-
throw error;
|
|
4211
|
-
}
|
|
4212
|
-
}
|
|
4213
|
-
/** Handles the node and asserts that it resolves to a value. */
|
|
4214
|
-
handleValue(scope, node, handler = this.handlers[node.type], out) {
|
|
4215
|
-
const result = this.handle(scope, node, handler, out);
|
|
4216
|
-
if (!result[0])
|
|
4217
|
-
throw new CompilerError(`This node (${node.type}) did not return a value`, node);
|
|
4218
|
-
return result;
|
|
4219
|
-
}
|
|
4220
|
-
handleEval(scope, node, out) {
|
|
4221
|
-
const [res, inst] = this.handleValue(scope, node, undefined, out);
|
|
4222
|
-
const [evaluated, evaluatedInst] = res.eval(scope, out);
|
|
4223
|
-
const result = [evaluated, [...inst, ...evaluatedInst]];
|
|
4224
|
-
if (this.sourcemap)
|
|
4225
|
-
return appendSourceLocations(result, node);
|
|
4226
|
-
return result;
|
|
4227
|
-
}
|
|
4228
|
-
/**
|
|
4229
|
-
* Handles many nodes in order.
|
|
4230
|
-
*
|
|
4231
|
-
* The usage of this method over a regular loop over an array of nodes
|
|
4232
|
-
* is only required if the code inside the loop generates
|
|
4233
|
-
* instructions that are not tracked by the compiler
|
|
4234
|
-
* handler methods ({@link handle}, {@link handleEval}
|
|
4235
|
-
* and {@link handleMany})
|
|
4236
|
-
*
|
|
4237
|
-
*/
|
|
4238
|
-
handleMany(scope, nodes, handler) {
|
|
4239
|
-
const lines = [];
|
|
4240
|
-
for (const node of hoistedFunctionNodes(nodes)) {
|
|
4241
|
-
pipeInsts(this.handle(scope, node, handler && (() => handler(node))), lines);
|
|
4242
|
-
}
|
|
4243
|
-
return [null, lines];
|
|
4244
|
-
}
|
|
4245
|
-
}
|
|
4246
|
-
function* hoistedFunctionNodes(nodes) {
|
|
4247
|
-
// sorting is O(n long n) while this is just O(n)
|
|
4248
|
-
// besides, it's better not to modify the node array
|
|
4249
|
-
for (const node of nodes) {
|
|
4250
|
-
if (node.type === "FunctionDeclaration") {
|
|
4251
|
-
yield node;
|
|
4252
|
-
}
|
|
4253
|
-
}
|
|
4254
|
-
for (const node of nodes) {
|
|
4255
|
-
if (node.type !== "FunctionDeclaration") {
|
|
4256
|
-
yield node;
|
|
4257
|
-
}
|
|
4258
|
-
}
|
|
4259
|
-
}
|
|
4260
|
-
function needsEndInstruction(inst, scope) {
|
|
4261
|
-
const last = inst[inst.length - 1];
|
|
4262
|
-
if (last instanceof AddressResolver)
|
|
4263
|
-
return true;
|
|
4264
|
-
return scope.inst.length > 0;
|
|
4265
|
-
}
|
|
4266
|
-
|
|
4267
|
-
export { Compiler, CompilerError };
|
|
1
|
+
export { C as Compiler, a as CompilerError } from './Compiler-7GMG4MDr.js';
|
|
2
|
+
import '@babel/parser';
|
|
3
|
+
import '@babel/types';
|