mlogjs 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -69,6 +69,8 @@ var EMutability;
69
69
  EMutability[EMutability["readonly"] = 1] = "readonly";
70
70
  /** Constant values, won't change during execution */
71
71
  EMutability[EMutability["constant"] = 2] = "constant";
72
+ /** An immutable value that hasn't been initialized yet */
73
+ EMutability[EMutability["init"] = 3] = "init";
72
74
  })(EMutability || (EMutability = {}));
73
75
 
74
76
  const arithmeticBinaryOperators = [
@@ -148,24 +150,20 @@ const operators = [...leftRightOperators, ...singleOperators];
148
150
 
149
151
  class VoidValue {
150
152
  constructor() {
151
- this.owner = null;
152
153
  this.mutability = EMutability.mutable;
153
154
  this.macro = false;
154
155
  }
155
- eval(_scope) {
156
+ eval(_scope, _out) {
156
157
  throw new CompilerError(`${this} cannot eval.`);
157
158
  }
158
- consume(_scope) {
159
- throw new CompilerError(`${this} cannot be consumed.`);
160
- }
161
- call(_scope, _args) {
159
+ call(_scope, _args, _out) {
162
160
  throw new CompilerError(`${this} cannot call.`);
163
161
  }
164
162
  get(_scope, _name) {
165
163
  throw new CompilerError(`${this} cannot get.`);
166
164
  }
167
- ensureOwned() {
168
- throw new CompilerError(`${this} cannot be owned`);
165
+ paramOuts() {
166
+ return;
169
167
  }
170
168
  toString() {
171
169
  return "void";
@@ -203,6 +201,176 @@ class OperationInstruction extends InstructionBase {
203
201
  }
204
202
  }
205
203
 
204
+ /**
205
+ * The prefix for internal variables inside the compiler output
206
+ */
207
+ const internalPrefix = "&";
208
+ const discardedName = `${internalPrefix}_`;
209
+ const itemNames = [
210
+ "copper",
211
+ "lead",
212
+ "metaglass",
213
+ "graphite",
214
+ "sand",
215
+ "coal",
216
+ "titanium",
217
+ "thorium",
218
+ "scrap",
219
+ "silicon",
220
+ "plastanium",
221
+ "phaseFabric",
222
+ "surgeAlloy",
223
+ "sporePod",
224
+ "blastCompound",
225
+ "pyratite",
226
+ "beryllium",
227
+ "tungsten",
228
+ "oxide",
229
+ "carbide",
230
+ "fissileMatter",
231
+ "dormantCyst",
232
+ ];
233
+
234
+ /**
235
+ * Returns a string that has the format: [line]:[column].
236
+ *
237
+ * If `name` is provided, and it is a string, the resulting string will
238
+ * have the format: [name]:[line]:[column].
239
+ */
240
+ function nodeName(node, name) {
241
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
242
+ const { line, column } = node.loc.start;
243
+ if (typeof name === "string")
244
+ return `${name}:${line}:${column}`;
245
+ return `${line}:${column}`;
246
+ }
247
+ /**
248
+ * Converts a camel case string into a dash case one.
249
+ *
250
+ * As an example `camelCase` becomes `camel-case`
251
+ * @example
252
+ * ```
253
+ * camelToDashCase("camelCase") // returns "camel-case"
254
+ * ```
255
+ */
256
+ function camelToDashCase(name) {
257
+ return name.replace(/[A-Z]/g, str => `-${str.toLowerCase()}`);
258
+ }
259
+ /**
260
+ * A more type safe version of `Object.assign`
261
+ */
262
+ // eslint-disable-next-line @typescript-eslint/ban-types
263
+ function assign(obj, props) {
264
+ return Object.assign(obj, props);
265
+ }
266
+
267
+ function appendSourceLocations(valueInst, node) {
268
+ for (const inst of valueInst[1]) {
269
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
270
+ inst.source ??= node.loc;
271
+ }
272
+ return valueInst;
273
+ }
274
+ function withAlwaysRuns(valueInst, value) {
275
+ valueInst[1].forEach(inst => (inst.alwaysRuns = value));
276
+ return valueInst;
277
+ }
278
+ /** Removes jump instructions that point right to the next line */
279
+ function hideRedundantJumps(inst) {
280
+ for (let i = 0; i < inst.length; i++) {
281
+ const instruction = inst[i];
282
+ if (!(instruction instanceof JumpInstruction))
283
+ continue;
284
+ const { address } = instruction;
285
+ let searchIndex = i + 1;
286
+ let current = inst[searchIndex];
287
+ while (current?.hidden) {
288
+ if (current instanceof AddressResolver &&
289
+ current.bonds.includes(address)) {
290
+ // only gets assigned if `current` is an adress resolver
291
+ // that resolves the jump adress
292
+ // and that is also right next to the jump,
293
+ // without any actual instructions in between
294
+ instruction.hidden = true;
295
+ break;
296
+ }
297
+ searchIndex++;
298
+ current = inst[searchIndex];
299
+ }
300
+ }
301
+ }
302
+
303
+ function isTemplateObjectArray(value) {
304
+ return (value instanceof ObjectValue &&
305
+ value.data.length instanceof LiteralValue &&
306
+ value.data.length.isNumber() &&
307
+ value.data.raw instanceof ObjectValue &&
308
+ value.data.raw.data.length instanceof LiteralValue &&
309
+ value.data.raw.data.length.isNumber());
310
+ }
311
+ function extractOutName(out) {
312
+ if (!out || typeof out === "string")
313
+ return out;
314
+ return out.name;
315
+ }
316
+ function extractDestrucuringOut(out, field) {
317
+ if (out === discardedName)
318
+ return out;
319
+ if (!(out instanceof DestructuringValue))
320
+ return;
321
+ return out.fields[field] ?? discardedName;
322
+ }
323
+ /** Asserts that `value` is a `LiteralValue` that contains a string */
324
+ function assertStringLiteral(value, name) {
325
+ if (!(value instanceof LiteralValue) || !value.isString())
326
+ throw new CompilerError(`${name} must be a string literal`);
327
+ }
328
+ /** Asserts that `value` is a string literal that is part of a given set of `members`*/
329
+ function assertLiteralOneOf(value, members, name) {
330
+ assertStringLiteral(value, name);
331
+ if (!members.includes(value.data))
332
+ throw new CompilerError(`${name} must be one of: ${members
333
+ .slice(0, -1)
334
+ .map(member => `"${member}"`)
335
+ .join(", ")} or "${members[members.length - 1]}"`);
336
+ }
337
+ /** Asserts that `value` is either a store or a literal */
338
+ function assertIsRuntimeValue(value, name) {
339
+ if (!(value instanceof LiteralValue) && !(value instanceof StoreValue))
340
+ throw new CompilerError(`${name} must be a valid runtime value`);
341
+ }
342
+ /** Asserts that `value` is an `ObjectValue` */
343
+ function assertIsObjectMacro(value, name) {
344
+ if (!(value instanceof ObjectValue))
345
+ throw new CompilerError(`${name} must be an object macro`);
346
+ }
347
+ /** Asserts that `value` is an `ObjectValue` that has a length property */
348
+ function assertIsArrayMacro(value, name) {
349
+ if (value instanceof ObjectValue) {
350
+ const { length } = value.data;
351
+ if (length instanceof LiteralValue && length.isNumber())
352
+ return;
353
+ }
354
+ throw new CompilerError(`${name} must be an array macro`);
355
+ }
356
+ /** Asserts that all of the fields described are present on `value`*/
357
+ function assertObjectFields(value, fields) {
358
+ const result = [];
359
+ for (const field of fields) {
360
+ const param = typeof field === "object" ? field : { key: field };
361
+ const item = value.data[param.key];
362
+ if (item) {
363
+ param.validate?.(item);
364
+ result.push(item);
365
+ continue;
366
+ }
367
+ if (param.default == undefined)
368
+ throw new CompilerError(`The field "${param.name ?? param.key}" must be present`);
369
+ result.push(param.default);
370
+ }
371
+ return result;
372
+ }
373
+
206
374
  class SetInstruction extends InstructionBase {
207
375
  constructor(store, value) {
208
376
  super("set", store, value);
@@ -217,12 +385,12 @@ class SetInstruction extends InstructionBase {
217
385
  const { store, value } = this;
218
386
  return (this._hidden ||
219
387
  // assignment to discarded value
220
- !store.owner ||
388
+ store.name === discardedName ||
221
389
  // self assignment of a store
222
- (!!store.owner &&
390
+ (!!store.name &&
223
391
  store instanceof StoreValue &&
224
392
  value instanceof StoreValue &&
225
- store.owner === value.owner));
393
+ store.name === value.name));
226
394
  }
227
395
  }
228
396
 
@@ -302,9 +470,9 @@ class ContinueInstruction extends JumpInstruction {
302
470
  }
303
471
 
304
472
  class BaseValue extends VoidValue {
305
- "u-"(scope) {
306
- const [that, inst] = this.consume(scope);
307
- const temp = new StoreValue(scope);
473
+ "u-"(scope, out) {
474
+ const [that, inst] = this.eval(scope);
475
+ const temp = StoreValue.from(scope, out);
308
476
  return [
309
477
  temp,
310
478
  [
@@ -313,40 +481,37 @@ class BaseValue extends VoidValue {
313
481
  ],
314
482
  ];
315
483
  }
316
- "u+"(scope) {
484
+ "u+"(scope, out) {
317
485
  // TODO: should it return 0 + this ?
318
- return this.eval(scope);
486
+ return this.eval(scope, out);
319
487
  }
320
- "!"(scope) {
321
- const [that, inst] = this.consume(scope);
322
- const temp = new StoreValue(scope);
488
+ "!"(scope, out) {
489
+ const [that, inst] = this.eval(scope);
490
+ const temp = StoreValue.from(scope, out);
323
491
  const falseLiteral = new LiteralValue(0);
324
492
  return [
325
493
  temp,
326
494
  [...inst, new OperationInstruction("equal", temp, that, falseLiteral)],
327
495
  ];
328
496
  }
329
- "~"(scope) {
330
- const [that, inst] = this.consume(scope);
331
- const temp = new StoreValue(scope);
497
+ "~"(scope, out) {
498
+ const [that, inst] = this.eval(scope);
499
+ const temp = StoreValue.from(scope, out);
332
500
  return [temp, [...inst, new OperationInstruction("not", temp, that, null)]];
333
501
  }
334
502
  // requires special handling
335
503
  // the handler should give an object value to allow the lazy evaluation
336
- "??"(scope, other) {
337
- const [left, leftInst] = this.eval(scope);
504
+ "??"(scope, other, out) {
505
+ if (this instanceof LiteralValue) {
506
+ if (this.data === null)
507
+ return other.eval(scope, out);
508
+ return [this, []];
509
+ }
510
+ const result = SenseableValue.out(scope, out);
511
+ const [left, leftInst] = this.eval(scope, result);
512
+ const [right, rightInst] = other.eval(scope, result);
338
513
  const nullLiteral = new LiteralValue(null);
339
514
  const endAdress = new LiteralValue(null);
340
- const [nullTest] = left["=="](scope, nullLiteral);
341
- if (nullTest instanceof LiteralValue) {
342
- if (nullTest.data)
343
- return other.eval(scope);
344
- else
345
- return [left, leftInst];
346
- }
347
- const [right, rightInst] = other.eval(scope);
348
- const result = new StoreValue(scope);
349
- result.ensureOwned();
350
515
  return [
351
516
  result,
352
517
  [
@@ -386,10 +551,10 @@ const operatorMap$1 = {
386
551
  };
387
552
  for (const key in operatorMap$1) {
388
553
  const kind = operatorMap$1[key];
389
- BaseValue.prototype[key] = function (scope, value) {
390
- const [left, leftInst] = this.consume(scope);
391
- const [right, rightInst] = value.consume(scope);
392
- const temp = new StoreValue(scope);
554
+ BaseValue.prototype[key] = function (scope, value, out) {
555
+ const [left, leftInst] = this.eval(scope);
556
+ const [right, rightInst] = value.eval(scope);
557
+ const temp = StoreValue.from(scope, out);
393
558
  return [
394
559
  temp,
395
560
  [
@@ -417,19 +582,19 @@ const assignmentToBinary = {
417
582
  "||=": "||",
418
583
  "??=": "??",
419
584
  };
420
- for (const op in assignmentToBinary) {
585
+ for (const k in assignmentToBinary) {
586
+ const op = k;
421
587
  BaseValue.prototype[op] = function (scope, value) {
422
- this.ensureOwned();
423
- const [opValue, opInst] = this[assignmentToBinary[op]](scope, value);
588
+ const [opValue, opInst] = this[assignmentToBinary[op]](scope, value, this);
424
589
  const [retValue, retInst] = this["="](scope, opValue);
425
590
  return [retValue, [...opInst, ...retInst]];
426
591
  };
427
592
  }
428
593
  for (const key of updateOperators) {
429
- BaseValue.prototype[key] = function (scope, prefix) {
430
- let [ret, inst] = this.consume(scope);
594
+ BaseValue.prototype[key] = function (scope, prefix, out) {
595
+ let [ret, inst] = this.eval(scope);
431
596
  if (!prefix) {
432
- const temp = new StoreValue(scope);
597
+ const temp = StoreValue.out(scope, out);
433
598
  const [tempValue, tempInst] = temp["="](scope, ret);
434
599
  ret = tempValue;
435
600
  inst.push(...tempInst);
@@ -441,7 +606,7 @@ for (const key of updateOperators) {
441
606
 
442
607
  const literalMethods = {
443
608
  length: function () {
444
- if (typeof this.data !== "string")
609
+ if (!this.isString())
445
610
  throw new CompilerError("Length method only works on string literal values.");
446
611
  return new LiteralValue(this.data.length);
447
612
  },
@@ -455,14 +620,11 @@ class LiteralValue extends BaseValue {
455
620
  eval(_scope) {
456
621
  return [this, []];
457
622
  }
458
- consume(_scope) {
459
- return [this, []];
460
- }
461
623
  toString() {
462
624
  return JSON.stringify(this.data);
463
625
  }
464
626
  get(scope, name) {
465
- if (!(name instanceof LiteralValue && typeof name.data === "string"))
627
+ if (!(name instanceof LiteralValue && name.isString()))
466
628
  return super.get(scope, name);
467
629
  const method = literalMethods[name.data];
468
630
  if (!method)
@@ -479,7 +641,12 @@ class LiteralValue extends BaseValue {
479
641
  typeof() {
480
642
  return [new LiteralValue("literal"), []];
481
643
  }
482
- ensureOwned() { }
644
+ isString() {
645
+ return typeof this.data === "string";
646
+ }
647
+ isNumber() {
648
+ return typeof this.data === "number";
649
+ }
483
650
  }
484
651
  const operatorMap = {
485
652
  "==": (a, b) => +(a == b),
@@ -505,11 +672,22 @@ const operatorMap = {
505
672
  "&&": (a, b) => +(a && b),
506
673
  "||": (a, b) => +(a || b),
507
674
  };
508
- for (const key in operatorMap) {
675
+ for (const k in operatorMap) {
676
+ const key = k;
509
677
  const fn = operatorMap[key];
510
- LiteralValue.prototype[key] = function (scope, value) {
678
+ LiteralValue.prototype[key] = function (scope, value, out) {
679
+ if (key === "&&") {
680
+ if (this.data)
681
+ return [value, []];
682
+ return [new LiteralValue(0), []];
683
+ }
684
+ if (key === "||") {
685
+ if (!this.data)
686
+ return [value, []];
687
+ return [new LiteralValue(1), []];
688
+ }
511
689
  if (!(value instanceof LiteralValue)) {
512
- return BaseValue.prototype[key].apply(this, [scope, value]);
690
+ return BaseValue.prototype[key].apply(this, [scope, value, out]);
513
691
  }
514
692
  return [new LiteralValue(fn(this.data, value.data)), []];
515
693
  };
@@ -527,162 +705,45 @@ for (const key in unaryOperatorMap) {
527
705
  };
528
706
  }
529
707
 
530
- /**
531
- * The prefix for internal variables inside the compiler output
532
- */
533
- const internalPrefix = "&";
534
- const discardedName = `${internalPrefix}_`;
535
- /**
536
- * Returns a string that has the format: [line]:[column].
537
- *
538
- * If `name` is provided, and it is a string, the resulting string will
539
- * have the format: [name]:[line]:[column].
540
- */
541
- function nodeName(node, name) {
542
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
543
- const { line, column } = node.loc.start;
544
- if (typeof name === "string")
545
- return `${name}:${line}:${column}`;
546
- return `${line}:${column}`;
547
- }
548
- /**
549
- * Converts a camel case string into a dash case one.
550
- *
551
- * As an example `camelCase` becomes `camel-case`
552
- * @example
553
- * ```
554
- * camelToDashCase("camelCase") // returns "camel-case"
555
- * ```
556
- */
557
- function camelToDashCase(name) {
558
- return name.replace(/[A-Z]/g, str => `-${str.toLowerCase()}`);
559
- }
560
- const itemNames = [
561
- "copper",
562
- "lead",
563
- "metaglass",
564
- "graphite",
565
- "sand",
566
- "coal",
567
- "titanium",
568
- "thorium",
569
- "scrap",
570
- "silicon",
571
- "plastanium",
572
- "phaseFabric",
573
- "surgeAlloy",
574
- "sporePod",
575
- "blastCompound",
576
- "pyratite",
577
- "beryllium",
578
- "tungsten",
579
- "oxide",
580
- "carbide",
581
- "fissileMatter",
582
- "dormantCyst",
583
- ];
584
- /**
585
- * A more type safe version of `Object.assign`
586
- */
587
- // eslint-disable-next-line @typescript-eslint/ban-types
588
- function assign(obj, props) {
589
- return Object.assign(obj, props);
590
- }
591
- function appendSourceLocations(valueInst, node) {
592
- for (const inst of valueInst[1]) {
593
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
594
- inst.source ??= node.loc;
595
- }
596
- return valueInst;
597
- }
598
- function withAlwaysRuns(valueInst, value) {
599
- valueInst[1].forEach(inst => (inst.alwaysRuns = value));
600
- return valueInst;
601
- }
602
- function isTemplateObjectArray(value) {
603
- return (value instanceof ObjectValue &&
604
- value.data.length instanceof LiteralValue &&
605
- typeof value.data.length.data === "number" &&
606
- value.data.raw instanceof ObjectValue &&
607
- value.data.raw.data.length instanceof LiteralValue &&
608
- typeof value.data.raw.data.length.data === "number");
609
- }
610
-
611
- class ValueOwner {
612
- /**
613
- * Creates a new `ValueOwner`, if `value` is already owned,
614
- * `this` will be aliased to `value`, else `this` will own `value`
615
- */
616
- constructor({ scope, constant = false, value, identifier, name, }) {
617
- this.scope = scope;
618
- this.constant = constant;
619
- this.value = value;
620
- this.identifier = identifier;
621
- this.name = name ?? scope.makeTempName();
622
- this.persistent = !!name;
623
- this.owned = new Set();
624
- if (!value.owner?.persistent)
625
- this.own(value);
626
- }
627
- own(target) {
628
- target.owner = this;
629
- this.owned.add(target);
630
- }
631
- replace(target) {
632
- this.value.owner = null;
633
- this.owned.delete(this.value);
634
- this.value = target;
635
- this.own(target);
636
- }
637
- moveInto(owner) {
638
- this.owned.forEach(value => owner.own(value));
639
- this.owned.clear();
640
- }
641
- }
642
-
643
708
  /**
644
709
  * `StoreValue` represents values unknown at compile time,
645
710
  * mostly used with mutable variables and temporary values
646
711
  */
647
712
  class StoreValue extends BaseValue {
648
- constructor(scope) {
713
+ constructor(name, mutability = EMutability.mutable) {
649
714
  super();
650
- this.scope = scope;
651
- this.mutability = EMutability.mutable;
715
+ this.name = name;
716
+ this.mutability = mutability;
717
+ }
718
+ static from(scope, out, mutability = EMutability.mutable) {
719
+ if (out instanceof StoreValue)
720
+ return out;
721
+ const name = typeof out === "string" ? out : scope.makeTempName();
722
+ return new StoreValue(name, mutability);
723
+ }
724
+ static out(scope, out, mutability = EMutability.mutable) {
725
+ if (!out || typeof out === "string") {
726
+ return new StoreValue(out ?? scope.makeTempName(), mutability);
727
+ }
728
+ return out;
652
729
  }
653
730
  typeof() {
654
731
  return [new LiteralValue("store"), []];
655
732
  }
656
733
  "="(scope, value) {
657
- if (this.mutability !== EMutability.mutable)
734
+ if (this.mutability !== EMutability.mutable &&
735
+ this.mutability !== EMutability.init)
658
736
  throw new CompilerError(`Cannot assign to immutable store '${this}'.`);
659
- if (this.owner) {
660
- if (this.owner === value.owner && value instanceof StoreValue)
661
- return [this, []];
662
- if (!value.owner?.persistent) {
663
- if (!value.owner)
664
- this.owner.own(value);
665
- else
666
- value.owner.moveInto(this.owner);
667
- if (value instanceof StoreValue)
668
- return [this, []];
669
- }
670
- }
671
- const [evalValue, evalInst] = value.consume(scope);
737
+ if (this.name === value.name && value instanceof StoreValue)
738
+ return [this, []];
739
+ const [evalValue, evalInst] = value.eval(scope, this);
672
740
  return [this, [...evalInst, new SetInstruction(this, evalValue)]];
673
741
  }
674
742
  eval(_scope) {
675
743
  return [this, []];
676
744
  }
677
- consume(_scope) {
678
- this.ensureOwned();
679
- return [this, []];
680
- }
681
- ensureOwned() {
682
- this.owner ??= new ValueOwner({ scope: this.scope, value: this });
683
- }
684
745
  toString() {
685
- return this.owner?.name ?? discardedName;
746
+ return this.name;
686
747
  }
687
748
  }
688
749
 
@@ -704,10 +765,13 @@ class ObjectValue extends VoidValue {
704
765
  });
705
766
  return new ObjectValue(data);
706
767
  }
707
- typeof() {
768
+ typeof(scope, out) {
769
+ const { $typeof } = this.data;
770
+ if ($typeof)
771
+ return $typeof.call(scope, [], out);
708
772
  return [new LiteralValue("object"), []];
709
773
  }
710
- get(scope, key) {
774
+ get(scope, key, out) {
711
775
  // avoids naming collisions with keys like
712
776
  // constructor or toString
713
777
  if (Object.prototype.hasOwnProperty.call(this.data, key.data)) {
@@ -718,40 +782,45 @@ class ObjectValue extends VoidValue {
718
782
  const { $get } = this.data;
719
783
  if (!$get)
720
784
  throw new CompilerError("Cannot get undefined member.");
721
- return $get.call(scope, [key]);
785
+ return $get.call(scope, [key], out);
722
786
  }
723
- eval(scope) {
787
+ eval(scope, out) {
724
788
  const { $eval } = this.data;
725
789
  if (!$eval)
726
790
  return [this, []];
727
- return $eval.call(scope, []);
728
- }
729
- consume(scope) {
730
- const { $consume } = this.data;
731
- if ($consume)
732
- return $consume.call(scope, []);
733
- const result = this.eval(scope);
734
- // required by typescript
735
- const res = result[0];
736
- res.ensureOwned();
737
- return result;
791
+ return $eval.call(scope, [], out);
738
792
  }
739
- call(scope, args) {
793
+ call(scope, args, out) {
740
794
  const { $call } = this.data;
741
795
  if (!$call)
742
- return super.call(scope, args);
743
- return $call.call(scope, args);
796
+ return super.call(scope, args, out);
797
+ return $call.call(scope, args, out);
744
798
  }
745
- ensureOwned() { }
746
799
  }
747
- for (const op of operators) {
748
- ObjectValue.prototype[op] = function (...args) {
800
+ for (const op of leftRightOperators) {
801
+ ObjectValue.prototype[op] = function (scope, value, out) {
802
+ const $ = this.data[`$${op}`];
803
+ if (!$)
804
+ return VoidValue.prototype[op].apply(this, [scope, value, out]);
805
+ return $.call(scope, [value], out);
806
+ };
807
+ }
808
+ for (const op of unaryOperators) {
809
+ if (op === "typeof")
810
+ continue;
811
+ ObjectValue.prototype[op] = function (scope, out) {
812
+ const $ = this.data[`$${op}`];
813
+ if (!$)
814
+ return VoidValue.prototype[op].apply(this, [scope, out]);
815
+ return $.call(scope, [], out);
816
+ };
817
+ }
818
+ for (const op of updateOperators) {
819
+ ObjectValue.prototype[op] = function (scope, prefix, out) {
749
820
  const $ = this.data[`$${op}`];
750
- // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-unsafe-return
751
821
  if (!$)
752
- return VoidValue.prototype[op].apply(this, args);
753
- const [scope, ...fnArgs] = args;
754
- return $.call(scope, fnArgs);
822
+ return VoidValue.prototype[op].apply(this, [scope, prefix, out]);
823
+ return $.call(scope, [new LiteralValue(+prefix)], out);
755
824
  };
756
825
  }
757
826
 
@@ -776,27 +845,37 @@ const senseableProps = ["controller"];
776
845
  * ```
777
846
  */
778
847
  class SenseableValue extends StoreValue {
779
- constructor(scope) {
780
- super(scope);
781
- this.mutability = EMutability.constant;
848
+ constructor(name, mutability = EMutability.constant) {
849
+ super(name, mutability);
850
+ }
851
+ static from(scope, out, mutability = EMutability.constant) {
852
+ if (out instanceof SenseableValue)
853
+ return out;
854
+ const name = typeof out === "string" ? out : scope.makeTempName();
855
+ return new SenseableValue(name, mutability);
856
+ }
857
+ static out(scope, out, mutability = EMutability.mutable) {
858
+ if (!out || typeof out === "string") {
859
+ return new SenseableValue(out ?? scope.makeTempName(), mutability);
860
+ }
861
+ return out;
782
862
  }
783
- get(scope, prop) {
784
- if (prop instanceof LiteralValue && typeof prop.data === "string") {
863
+ get(scope, prop, out) {
864
+ const mutability = EMutability.readonly;
865
+ if (prop instanceof LiteralValue && prop.isString()) {
785
866
  const name = itemNames.includes(prop.data)
786
867
  ? camelToDashCase(prop.data)
787
868
  : prop.data;
788
869
  const result = senseableProps.includes(prop.data)
789
- ? new SenseableValue(scope)
790
- : new StoreValue(scope);
870
+ ? SenseableValue.from(scope, out, mutability)
871
+ : StoreValue.from(scope, out, mutability);
791
872
  return [
792
873
  result,
793
874
  [new InstructionBase("sensor", result, this, `@${name}`)],
794
875
  ];
795
876
  }
796
877
  if (prop instanceof StoreValue) {
797
- const temp = assign(new SenseableValue(scope), {
798
- mutability: EMutability.readonly,
799
- });
878
+ const temp = SenseableValue.from(scope, out, mutability);
800
879
  return [temp, [new InstructionBase("sensor", temp, this, prop)]];
801
880
  }
802
881
  throw new CompilerError(`Cannot sense "${prop}" property`);
@@ -815,21 +894,34 @@ class DestructuringValue extends VoidValue {
815
894
  super();
816
895
  this.members = members;
817
896
  this.macro = true;
897
+ this.fields = {};
898
+ for (const [key, { value }] of this.members) {
899
+ if (key instanceof LiteralValue && (key.isString() || key.isNumber())) {
900
+ this.fields[key.data] = value;
901
+ }
902
+ }
818
903
  }
819
904
  "="(scope, right) {
820
905
  const inst = [];
821
- for (const [key, value] of this.members) {
822
- const [item, itemInst] = right.get(scope, key);
823
- inst.push(...itemInst);
824
- inst.push(...value["="](scope, item)[1]);
906
+ for (const [key, { value, handler }] of this.members) {
907
+ const [, handlerInst] = handler(() => right.get(scope, key, value));
908
+ inst.push(...handlerInst);
825
909
  }
826
910
  return [right, inst];
827
911
  }
828
912
  eval(_scope) {
829
913
  return [this, []];
830
914
  }
831
- consume(_scope) {
832
- return [this, []];
915
+ }
916
+
917
+ /**
918
+ * This class is used to handle some cases where the value
919
+ * can't be directly evaluated by the node handler
920
+ */
921
+ class LazyValue extends VoidValue {
922
+ constructor(evalutate) {
923
+ super();
924
+ this.eval = evalutate;
833
925
  }
834
926
  }
835
927
 
@@ -866,46 +958,29 @@ class Scope {
866
958
  return false;
867
959
  }
868
960
  get(identifier) {
869
- const owner = this.data[identifier];
870
- if (owner)
871
- return owner.value;
961
+ const value = this.data[identifier];
962
+ if (value)
963
+ return value;
872
964
  if (this.parent)
873
965
  return this.parent.get(identifier);
874
966
  throw new CompilerError(`${identifier} is not declared.`);
875
967
  }
876
- set(...args) {
877
- const name = args.length === 1 ? args[0].identifier : args[0];
878
- if (!name)
879
- throw new CompilerError("Values in a scope must have an identifier");
968
+ set(name, value) {
880
969
  if (name in this.data)
881
970
  throw new CompilerError(`${name} is already declared.`);
882
- return this.hardSet(...args);
883
- }
884
- hardSet(...args) {
885
- const owner = args.length === 1
886
- ? args[0]
887
- : new ValueOwner({
888
- scope: this,
889
- identifier: args[0],
890
- name: args[0],
891
- value: args[1],
892
- });
893
- const { identifier } = owner;
894
- if (!identifier)
971
+ return this.hardSet(name, value);
972
+ }
973
+ hardSet(name, value) {
974
+ if (!name)
895
975
  throw new CompilerError("Values in a scope must have an identifier");
896
- this.data[identifier] = owner;
897
- return owner.value;
976
+ this.data[name] = value;
977
+ return value;
898
978
  }
899
979
  make(identifier, name) {
900
- const owner = new ValueOwner({
901
- scope: this,
902
- value: assign(new SenseableValue(this), {
903
- mutability: EMutability.mutable,
904
- }),
905
- identifier,
906
- name,
980
+ const value = assign(new SenseableValue(name), {
981
+ mutability: EMutability.mutable,
907
982
  });
908
- return this.set(owner);
983
+ return this.set(identifier, value);
909
984
  }
910
985
  makeTempName() {
911
986
  let result = `${internalPrefix}t${this.ntemp}`;
@@ -921,163 +996,222 @@ const Program = (c, scope, node) => {
921
996
  };
922
997
 
923
998
  const VariableDeclaration = (c, scope, node) => {
924
- return c.handleMany(scope, node.declarations, child => VariableDeclarator(c, scope, child, node.kind));
999
+ return c.handleMany(scope, node.declarations, child => VariableDeclarator(c, scope, child, undefined, node.kind));
925
1000
  };
926
- const VariableDeclarator = (c, scope, node, kind = "let") => {
927
- const [init, inst] = node.init
928
- ? c.handleEval(scope, node.init)
929
- : [null, []];
930
- inst.push(...Declare(c, scope, node.id, kind, init)[1]);
1001
+ const VariableDeclarator = (c, scope, node, out, kind = "let") => {
1002
+ const { init } = node;
1003
+ const [value, inst] = Declare(c, scope, node.id, kind);
1004
+ if (init) {
1005
+ const [result, resultInst] = c.handleEval(scope, init, value.out);
1006
+ inst.push(...value.handler(result, resultInst)[1]);
1007
+ }
1008
+ else {
1009
+ inst.push(...value.handler(null, [])[1]);
1010
+ }
931
1011
  return [null, inst];
932
1012
  };
933
- const Declare = (c, scope, node, kind, init) => {
1013
+ const Declare = (c, scope, node, kind) => {
934
1014
  return c.handle(scope, node, () => {
935
1015
  switch (node.type) {
936
1016
  case "Identifier":
937
- return DeclareIdentifier(c, scope, node, kind, init);
1017
+ return DeclareIdentifier(c, scope, node, kind);
938
1018
  case "ArrayPattern":
939
- return DeclareArrayPattern(c, scope, node, kind, init);
1019
+ return DeclareArrayPattern(c, scope, node, kind);
940
1020
  case "ObjectPattern":
941
- return DeclareObjectPattern(c, scope, node, kind, init);
1021
+ return DeclareObjectPattern(c, scope, node, kind);
942
1022
  default:
943
1023
  throw new CompilerError(`Unsupported declaration type: ${node.type}`, node);
944
1024
  }
945
1025
  });
946
1026
  };
947
- const DeclareIdentifier = (c, scope, node, kind, init) => {
1027
+ const DeclareIdentifier = (c, scope, node, kind) => {
948
1028
  const { name: identifier } = node;
949
1029
  const name = nodeName(node, !c.compactNames && identifier);
950
- if (kind === "const" && !init)
951
- throw new CompilerError("Constants must be initialized.", node);
952
- if (kind === "const" && init?.mutability === EMutability.constant) {
953
- const owner = new ValueOwner({
954
- scope,
955
- identifier,
956
- name,
957
- value: init,
958
- constant: true,
959
- });
960
- scope.set(owner);
961
- return [null, []];
962
- }
963
- else {
964
- const value = scope.make(identifier, name);
965
- const inst = [];
966
- if (init) {
967
- if (init.macro)
968
- throw new CompilerError("Macro values must be held by constants", node);
969
- inst.push(...value["="](scope, init)[1]);
970
- }
971
- if (kind === "const")
972
- value.mutability = EMutability.constant;
973
- return [null, inst];
974
- }
1030
+ const out = SenseableValue.from(scope, name, kind === "const" ? EMutability.init : EMutability.mutable);
1031
+ const declarationValue = new DeclarationValue({
1032
+ c,
1033
+ scope,
1034
+ node,
1035
+ out,
1036
+ handler(init, inst) {
1037
+ if (kind === "const" && !init)
1038
+ throw new CompilerError("Constants must be initialized.", node);
1039
+ if (kind === "const" && init?.mutability === EMutability.constant) {
1040
+ scope.set(identifier, init);
1041
+ return [null, inst];
1042
+ }
1043
+ else {
1044
+ const value = scope.make(identifier, name);
1045
+ if (init) {
1046
+ if (init.macro)
1047
+ throw new CompilerError("Macro values must be held by constants", node);
1048
+ inst.push(...value["="](scope, init)[1]);
1049
+ }
1050
+ if (kind === "const")
1051
+ value.mutability = EMutability.constant;
1052
+ return [null, inst];
1053
+ }
1054
+ },
1055
+ });
1056
+ return [declarationValue, []];
975
1057
  };
976
- const DeclareArrayPattern = (c, scope, node, kind, init) => {
1058
+ const DeclareArrayPattern = (c, scope, node, kind) => {
1059
+ const members = new Map();
977
1060
  const { elements } = node;
978
- if (!(init instanceof ObjectValue))
979
- throw new CompilerError("The value being destructured must be an object value");
980
1061
  const inst = [];
981
1062
  for (let i = 0; i < elements.length; i++) {
982
1063
  const element = elements[i];
983
1064
  if (!element)
984
1065
  continue;
985
- const val = init.data[i];
986
- if (!val)
987
- throw new CompilerError(`The target object does not have a value at index ${i}`, element);
988
- inst.push(...Declare(c, scope, element, kind, val)[1]);
1066
+ const [value, valueInst] = Declare(c, scope, element, kind);
1067
+ inst.push(...valueInst);
1068
+ members.set(new LiteralValue(i), {
1069
+ value: value.out,
1070
+ handler(get) {
1071
+ return c.handle(scope, element, () => {
1072
+ let [init, initInst] = [null, []];
1073
+ try {
1074
+ [init, initInst] = get();
1075
+ }
1076
+ catch (e) {
1077
+ // catches the "cannot get undefined member" error
1078
+ // TODO: refactor and add error codes
1079
+ // so we don't have to do string checks
1080
+ if (!(e instanceof CompilerError) ||
1081
+ !e.message.includes("undefined member"))
1082
+ throw e;
1083
+ }
1084
+ if (!init)
1085
+ throw new CompilerError(`The target object does not have a value at index ${i}`, element);
1086
+ return value.handler(init, initInst);
1087
+ });
1088
+ },
1089
+ });
989
1090
  }
990
- return [null, inst];
1091
+ const out = new DestructuringValue(members);
1092
+ const value = new DeclarationValue({
1093
+ c,
1094
+ scope,
1095
+ node,
1096
+ out,
1097
+ handler(init, initInst) {
1098
+ if (!(init instanceof ObjectValue))
1099
+ throw new CompilerError("The value being destructured must be an object value");
1100
+ initInst.push(...out["="](scope, init)[1]);
1101
+ return [null, initInst];
1102
+ },
1103
+ });
1104
+ return [value, inst];
991
1105
  };
992
- const DeclareObjectPattern = (c, scope, node, kind, base) => {
993
- if (!base)
994
- throw new CompilerError("Cannot use object destructuring without an initializer", node);
1106
+ const DeclareObjectPattern = (c, scope, node, kind) => {
1107
+ const inst = [];
1108
+ const members = new Map();
995
1109
  const { properties } = node;
996
- const [init, inst] = base.consume(scope);
997
1110
  const propertiesInst = c.handleMany(scope, properties, prop => {
998
1111
  if (prop.type === "RestElement")
999
1112
  throw new CompilerError("The rest operator is not supported", prop);
1000
- const { key, value } = prop;
1113
+ const { key, value: propValue } = prop;
1001
1114
  const keyInst = key.type === "Identifier" && !prop.computed
1002
1115
  ? [new LiteralValue(key.name), []]
1003
- : c.handleConsume(scope, prop.key);
1004
- const propInit = init.get(scope, keyInst[0]);
1005
- const declarationInst = Declare(c, scope, value, kind, propInit[0]);
1006
- return [null, [...keyInst[1], ...propInit[1], ...declarationInst[1]]];
1116
+ : c.handleEval(scope, prop.key);
1117
+ const [value, valueInst] = Declare(c, scope, propValue, kind);
1118
+ members.set(keyInst[0], {
1119
+ value: value.out,
1120
+ handler(get) {
1121
+ return c.handle(scope, propValue, () => {
1122
+ const [init, initInst] = get();
1123
+ return value.handler(init, initInst);
1124
+ });
1125
+ },
1126
+ });
1127
+ return [null, [...keyInst[1], ...valueInst]];
1007
1128
  });
1008
- return [null, [...inst, ...propertiesInst[1]]];
1009
- };
1129
+ const out = new DestructuringValue(members);
1130
+ const declarationValue = new DeclarationValue({
1131
+ c,
1132
+ scope,
1133
+ node,
1134
+ out,
1135
+ handler(init, initInst) {
1136
+ if (!init)
1137
+ throw new CompilerError("Cannot use object destructuring without an initializer", node);
1138
+ initInst.push(...out["="](scope, init)[1]);
1139
+ return [null, initInst];
1140
+ },
1141
+ });
1142
+ return [declarationValue, [...inst, ...propertiesInst[1]]];
1143
+ };
1144
+ class DeclarationValue extends VoidValue {
1145
+ constructor({ c, scope, node, out, handler, }) {
1146
+ super();
1147
+ this.out = out;
1148
+ this.c = c;
1149
+ this.scope = scope;
1150
+ this.node = node;
1151
+ this.handle = handler;
1152
+ }
1153
+ handler(init, inst) {
1154
+ return this.c.handle(this.scope, this.node, () => this.handle(init, inst));
1155
+ }
1156
+ }
1010
1157
 
1011
1158
  const Literal = (_c, scope, node) => [new LiteralValue(node.value), []];
1012
1159
  const NumericLiteral = Literal;
1013
1160
  const StringLiteral = Literal;
1014
- const NullLiteral = () => [
1015
- new LiteralValue(null),
1016
- [],
1017
- ];
1161
+ const NullLiteral = () => [new LiteralValue(null), []];
1018
1162
  const BooleanLiteral = (_c, scope, node) => [new LiteralValue(+node.value), []];
1019
1163
 
1020
- const LRExpression = (c, scope, node) => {
1021
- const [left, leftInst] = c.handleConsume(scope, node.left);
1022
- const [right, rightInst] = c.handleConsume(scope, node.right);
1023
- const [op, opInst] = left[node.operator](scope, right);
1164
+ const LRExpression = (c, scope, node, out) => {
1165
+ const [left, leftInst] = c.handleEval(scope, node.left);
1166
+ const [right, rightInst] = c.handleEval(scope, node.right);
1167
+ const [op, opInst] = left[node.operator](scope, right, out);
1024
1168
  return [op, [...leftInst, ...rightInst, ...opInst]];
1025
1169
  };
1026
1170
  const BinaryExpression = LRExpression;
1027
- const LogicalExpression = (c, scope, node) => {
1171
+ const LogicalExpression = (c, scope, node, out) => {
1028
1172
  if (node.operator !== "??")
1029
- return LRExpression(c, scope, node);
1030
- const other = lazyValue({
1031
- eval: scope => c.handleEval(scope, node.right),
1032
- consume: scope => c.handleConsume(scope, node.right),
1033
- });
1173
+ return LRExpression(c, scope, node, undefined);
1174
+ const other = new LazyValue((scope, out) => c.handleEval(scope, node.right, out));
1034
1175
  const [left, leftInst] = c.handleEval(scope, node.left);
1035
- const [result, resultInst] = left["??"](scope, other);
1176
+ const [result, resultInst] = left["??"](scope, other, out);
1036
1177
  return [result, [...leftInst, ...resultInst]];
1037
1178
  };
1038
1179
  const AssignmentExpression = (c, scope, node) => {
1039
1180
  const [left, leftInst] = c.handle(scope, node.left);
1040
- // doesn't need to consume because the operators already do that
1041
1181
  const [right, rightInst] = node.operator !== "??="
1042
- ? c.handleEval(scope, node.right)
1182
+ ? c.handleEval(scope, node.right, node.operator === "=" && left ? left : undefined)
1043
1183
  : [
1044
- lazyValue({
1045
- eval: scope => c.handleEval(scope, node.right),
1046
- consume: scope => c.handleConsume(scope, node.right),
1047
- }),
1184
+ new LazyValue((scope, out) => c.handleEval(scope, node.right, out)),
1048
1185
  [],
1049
1186
  ];
1050
1187
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1051
1188
  const [op, opInst] = left[node.operator](scope, right);
1052
1189
  return [op, [...leftInst, ...rightInst, ...opInst]];
1053
1190
  };
1054
- const UnaryExpression = (c, scope, { argument, operator }) => {
1055
- const [arg, argInst] = c.handleConsume(scope, argument);
1191
+ const UnaryExpression = (c, scope, { argument, operator }, out) => {
1192
+ const [arg, argInst] = c.handleEval(scope, argument);
1056
1193
  const operatorId = operator == "+" || operator == "-" ? `u${operator}` : operator;
1057
1194
  if (operatorId === "throw")
1058
1195
  throw new CompilerError("throw operator is not supported");
1059
- const [op, opInst] = arg[operatorId](scope);
1196
+ const [op, opInst] = arg[operatorId](scope, out);
1060
1197
  return [op, [...argInst, ...opInst]];
1061
1198
  };
1062
- const UpdateExpression = (c, scope, { argument, operator, prefix }) => {
1199
+ const UpdateExpression = (c, scope, { argument, operator, prefix }, out) => {
1063
1200
  const [arg, argInst] = c.handle(scope, argument);
1064
1201
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1065
- const [op, opInst] = arg[operator](scope, prefix);
1202
+ const [op, opInst] = arg[operator](scope, prefix, out);
1066
1203
  return [op, [...argInst, ...opInst]];
1067
1204
  };
1068
- const ConditionalExpression = (c, scope, node) => {
1069
- const [test, testInst] = c.handleConsume(scope, node.test);
1205
+ const ConditionalExpression = (c, scope, node, out) => {
1206
+ const [test, testInst] = c.handleEval(scope, node.test);
1070
1207
  if (test instanceof LiteralValue) {
1071
1208
  if (test.data)
1072
- return c.handleEval(scope, node.consequent);
1073
- return c.handleEval(scope, node.alternate);
1074
- }
1075
- // TODO: this creates those annoying jumps on the
1076
- // temp counter, specially if you nest ternary operators
1077
- const result = new StoreValue(scope);
1078
- result.ensureOwned();
1079
- const consequent = c.handleEval(scope, node.consequent);
1080
- const alternate = c.handleEval(scope, node.alternate);
1209
+ return c.handleEval(scope, node.consequent, out);
1210
+ return c.handleEval(scope, node.alternate, out);
1211
+ }
1212
+ const result = SenseableValue.out(scope, out);
1213
+ const consequent = c.handleEval(scope, node.consequent, result);
1214
+ const alternate = c.handleEval(scope, node.alternate, result);
1081
1215
  const alternateStartAdress = new LiteralValue(null);
1082
1216
  const endExpressionAdress = new LiteralValue(null);
1083
1217
  return [
@@ -1094,20 +1228,14 @@ const ConditionalExpression = (c, scope, node) => {
1094
1228
  new AddressResolver(endExpressionAdress),
1095
1229
  ],
1096
1230
  ];
1097
- };
1098
- function lazyValue(options) {
1099
- const value = new VoidValue();
1100
- value.eval = options.eval;
1101
- value.consume = options.consume;
1102
- return value;
1103
- }
1231
+ };
1104
1232
 
1105
1233
  const Identifier = (c, scope, node) => {
1106
1234
  return [scope.get(node.name), []];
1107
1235
  };
1108
1236
 
1109
1237
  const ExpressionStatement = (c, scope, node) => {
1110
- return c.handle(scope, node.expression);
1238
+ return c.handle(scope, node.expression, undefined, discardedName);
1111
1239
  };
1112
1240
  const BreakStatement = (_, scope, node) => {
1113
1241
  const label = node.label?.name;
@@ -1123,11 +1251,12 @@ const ContinueStatement = (_, scope, node) => {
1123
1251
  target.continue.bind(addr);
1124
1252
  return [null, [new ContinueInstruction(addr)]];
1125
1253
  };
1126
- const ReturnStatement = (c, scope, node) => {
1127
- const [arg, argInst] = node.argument
1128
- ? c.handle(scope, node.argument)
1254
+ const ReturnStatement = (c, scope, node, out) => {
1255
+ const { argument } = node;
1256
+ const [arg, argInst] = argument
1257
+ ? [new LazyValue((scope, out) => c.handleEval(scope, argument, out)), []]
1129
1258
  : [null, []];
1130
- const [ret, retInst] = scope.function.return(scope, arg);
1259
+ const [ret, retInst] = scope.function.return(scope, arg, out);
1131
1260
  return [ret, [...argInst, ...retInst]];
1132
1261
  };
1133
1262
  const LabeledStatement = (c, scope, node) => {
@@ -1150,7 +1279,7 @@ function findScopeLabel(scope, label) {
1150
1279
 
1151
1280
  const IfStatement = (c, scope, node) => {
1152
1281
  const inst = [];
1153
- const [test, testInst] = c.handleConsume(scope, node.test);
1282
+ const [test, testInst] = c.handleEval(scope, node.test);
1154
1283
  if (test instanceof LiteralValue) {
1155
1284
  if (test.data)
1156
1285
  inst.push(...c.handle(scope, node.consequent)[1]);
@@ -1169,7 +1298,7 @@ const IfStatement = (c, scope, node) => {
1169
1298
 
1170
1299
  const SwitchStatement = (c, scope, node) => {
1171
1300
  const innerScope = scope.createScope();
1172
- const [ref, refInst] = c.handleConsume(scope, node.discriminant);
1301
+ const [ref, refInst] = c.handleEval(scope, node.discriminant);
1173
1302
  const inst = [];
1174
1303
  const endAdress = new LiteralValue(null);
1175
1304
  const endLine = new AddressResolver(endAdress).bindBreak(innerScope);
@@ -1187,7 +1316,7 @@ const SwitchStatement = (c, scope, node) => {
1187
1316
  inst.push(bodyLine, ...bodyInst);
1188
1317
  continue;
1189
1318
  }
1190
- const [test, testInst] = c.handleConsume(innerScope, scase.test);
1319
+ const [test, testInst] = c.handleEval(innerScope, scase.test);
1191
1320
  // check if it can be evaluated at compile time
1192
1321
  const [comp] = ref["=="](innerScope, test);
1193
1322
  // if the constant expression resolves to false
@@ -1230,7 +1359,7 @@ const SwitchStatement = (c, scope, node) => {
1230
1359
 
1231
1360
  const WhileStatement = (c, scope, node) => {
1232
1361
  const lines = [];
1233
- const [test, testLines] = c.handleConsume(scope, node.test);
1362
+ const [test, testLines] = c.handleEval(scope, node.test);
1234
1363
  const childScope = scope.createScope();
1235
1364
  const startLoopAddr = new LiteralValue(null);
1236
1365
  const endLoopAddr = new LiteralValue(null);
@@ -1250,7 +1379,7 @@ const WhileStatement = (c, scope, node) => {
1250
1379
  };
1251
1380
  const DoWhileStatement = (c, scope, node) => {
1252
1381
  const lines = [];
1253
- const [test, testLines] = c.handleConsume(scope, node.test);
1382
+ const [test, testLines] = c.handleEval(scope, node.test);
1254
1383
  const childScope = scope.createScope();
1255
1384
  const startLoopAddr = new LiteralValue(null);
1256
1385
  const startLoopLine = new AddressResolver(startLoopAddr).bindContinue(childScope);
@@ -1279,9 +1408,11 @@ const ForStatement = (c, parentScope, node) => {
1279
1408
  const scope = parentScope.createScope();
1280
1409
  const initInst = node.init ? c.handle(scope, node.init)[1] : [];
1281
1410
  const [test, testLines] = node.test
1282
- ? c.handleConsume(scope, node.test)
1411
+ ? c.handleEval(scope, node.test)
1283
1412
  : [new LiteralValue(1), []];
1284
- const updateLines = node.update ? c.handle(scope, node.update)[1] : [];
1413
+ const updateLines = node.update
1414
+ ? c.handle(scope, node.update, undefined, discardedName)[1]
1415
+ : [];
1285
1416
  const startLoopAddr = new LiteralValue(null);
1286
1417
  // continue statements jump here to run the loop update lines
1287
1418
  const beforeEndAddr = new LiteralValue(null);
@@ -1309,139 +1440,131 @@ const ForStatement = (c, parentScope, node) => {
1309
1440
  };
1310
1441
 
1311
1442
  class FunctionValue extends VoidValue {
1312
- constructor({ scope, params, body, c, }) {
1443
+ constructor({ scope, params, body, c, out, }) {
1313
1444
  super();
1314
1445
  this.mutability = EMutability.constant;
1315
1446
  this.macro = true;
1316
- this.paramOwners = [];
1447
+ this.paramValues = [];
1448
+ this.paramNames = [];
1317
1449
  this.bundled = false;
1318
1450
  this.initialized = false;
1319
1451
  this.scope = scope;
1320
1452
  this.body = body;
1321
1453
  this.c = c;
1322
1454
  this.params = params;
1455
+ this.out = out;
1456
+ this.name = extractOutName(out) ?? scope.makeTempName();
1323
1457
  }
1324
1458
  typeof() {
1325
1459
  return [new LiteralValue("function"), []];
1326
1460
  }
1327
- initScope(name) {
1328
- this.childScope = this.scope.createFunction(name);
1461
+ initScope() {
1462
+ this.childScope = this.scope.createFunction(this.name);
1329
1463
  this.childScope.function = this;
1330
1464
  for (const id of this.params) {
1331
1465
  const name = nodeName(id, !this.c.compactNames && id.name);
1332
- const owner = new ValueOwner({
1333
- scope: this.childScope,
1334
- value: assign(new SenseableValue(this.childScope), {
1335
- mutability: EMutability.mutable,
1336
- }),
1337
- identifier: id.name,
1338
- name,
1339
- });
1340
- this.childScope.set(owner);
1341
- this.paramOwners.push(owner);
1466
+ const value = new SenseableValue(name, EMutability.mutable);
1467
+ this.childScope.set(id.name, value);
1468
+ this.paramValues.push(value);
1469
+ this.paramNames.push(id.name);
1342
1470
  }
1343
- this.callSize = this.paramOwners.length + 2;
1471
+ this.callSize = this.paramValues.length + 2;
1344
1472
  }
1345
1473
  ensureInit() {
1346
- if (!this.owner)
1347
- throw new CompilerError(`Functions must be owned by a variable`);
1348
1474
  if (this.initialized)
1349
1475
  return;
1350
1476
  this.initialized = true;
1351
- const { name } = this.owner;
1352
- this.initScope(name);
1477
+ this.initScope();
1353
1478
  this.addr = new LiteralValue(null);
1354
- this.temp = new ValueOwner({
1355
- scope: this.childScope,
1356
- name: `${internalPrefix}f${name}`,
1357
- value: assign(new SenseableValue(this.childScope), {
1358
- mutability: EMutability.mutable,
1359
- }),
1360
- });
1361
- this.ret = new ValueOwner({
1362
- scope: this.childScope,
1363
- name: `${internalPrefix}r${name}`,
1364
- value: new StoreValue(this.childScope),
1365
- });
1479
+ this.temp = new SenseableValue(`${internalPrefix}f${this.name}`, EMutability.mutable);
1480
+ this.ret = new StoreValue(`${internalPrefix}r${this.name}`);
1366
1481
  this.inst = [
1367
1482
  new AddressResolver(this.addr),
1368
1483
  ...this.c.handle(this.childScope, this.body)[1],
1369
1484
  ];
1370
1485
  if (!endsWithReturn(this.inst)) {
1371
- this.inst.push(assign(new SetCounterInstruction(this.ret.value), {
1486
+ this.inst.push(assign(new SetCounterInstruction(this.ret), {
1372
1487
  intent: EInstIntent.return,
1373
1488
  }));
1374
1489
  }
1375
1490
  }
1376
1491
  normalReturn(scope, arg) {
1377
- const argInst = arg ? this.temp.value["="](scope, arg)[1] : [];
1378
- const returnInst = assign(new SetCounterInstruction(this.ret.value), {
1492
+ const inst = [];
1493
+ if (arg) {
1494
+ const [value, valueInst] = arg.eval(scope, this.temp);
1495
+ const argInst = this.temp["="](scope, value)[1];
1496
+ inst.push(...valueInst);
1497
+ inst.push(...argInst);
1498
+ }
1499
+ const returnInst = assign(new SetCounterInstruction(this.ret), {
1379
1500
  intent: EInstIntent.return,
1380
1501
  });
1381
- return [null, [...argInst, returnInst]];
1502
+ return [null, [...inst, returnInst]];
1382
1503
  }
1383
- normalCall(scope, args) {
1504
+ // TODO: use static analysis to determine if
1505
+ // we can safely skip the temp value assignment
1506
+ normalCall(scope, args, out) {
1384
1507
  if (!this.bundled)
1385
1508
  this.childScope.inst.push(...this.inst);
1386
1509
  this.bundled = true;
1387
1510
  const callAddressLiteral = new LiteralValue(null);
1388
- const inst = this.paramOwners
1389
- .map(({ value: param }, i) => param["="](scope, args[i])[1])
1511
+ const temp = SenseableValue.from(scope, out, EMutability.mutable);
1512
+ const inst = this.paramValues
1513
+ .map((param, i) => param["="](scope, args[i])[1])
1390
1514
  .reduce((s, c) => s.concat(c), [])
1391
- .concat(...this.ret.value["="](scope, callAddressLiteral)[1], new JumpInstruction(this.addr, EJumpKind.Always), new AddressResolver(callAddressLiteral));
1392
- return [this.temp.value, inst];
1515
+ .concat(...this.ret["="](scope, callAddressLiteral)[1], new JumpInstruction(this.addr, EJumpKind.Always), new AddressResolver(callAddressLiteral),
1516
+ // ensures that functions can be called multiple times inside expressions
1517
+ ...temp["="](scope, this.temp)[1]);
1518
+ return [temp, inst];
1393
1519
  }
1394
1520
  inlineReturn(scope, arg) {
1395
- const argInst = arg ? this.inlineTemp.value["="](scope, arg)[1] : [];
1396
1521
  if (!this.inlineEnd)
1397
1522
  throw new CompilerError("Error during inline attempt: missing inline end adress");
1523
+ const inst = [];
1524
+ if (arg) {
1525
+ const [value, valueInst] = arg.eval(scope, this.inlineTemp);
1526
+ const argInst = this.inlineTemp["="](scope, value)[1];
1527
+ inst.push(...valueInst);
1528
+ inst.push(...argInst);
1529
+ }
1398
1530
  const jump = assign(new JumpInstruction(this.inlineEnd, EJumpKind.Always), {
1399
1531
  intent: EInstIntent.return,
1532
+ intentSource: this,
1400
1533
  });
1401
- return [null, [...argInst, jump]];
1534
+ return [null, [...inst, jump]];
1402
1535
  }
1403
- inlineCall(scope, args) {
1536
+ inlineCall(scope, args, out) {
1404
1537
  // create return value
1405
- this.inlineTemp = new ValueOwner({
1406
- scope: this.childScope,
1407
- value: assign(new SenseableValue(scope), {
1408
- mutability: EMutability.mutable,
1409
- }),
1410
- });
1538
+ this.inlineTemp = SenseableValue.from(this.childScope, out, EMutability.mutable);
1411
1539
  // make a copy of the function scope
1412
1540
  const fnScope = this.childScope.copy();
1413
1541
  // hard set variables within the function scope
1414
- this.paramOwners.forEach((owner, i) => {
1415
- const value = args[i];
1416
- if (!value.owner?.persistent)
1417
- owner.own(value);
1418
- fnScope.hardSet(new ValueOwner({
1419
- scope: fnScope,
1420
- value,
1421
- identifier: owner.identifier,
1422
- name: owner.name,
1423
- }));
1542
+ this.paramNames.forEach((name, i) => {
1543
+ fnScope.hardSet(name, args[i]);
1424
1544
  });
1425
1545
  this.inlineEnd = new LiteralValue(null);
1426
1546
  this.tryingInline = true;
1427
1547
  let inst = this.c.handle(fnScope, this.body)[1];
1428
1548
  this.tryingInline = false;
1429
- const returnIndex = inst.findIndex(i => i.alwaysRuns && i.intent === EInstIntent.return);
1549
+ const returnIndex = inst.findIndex(i => i.alwaysRuns &&
1550
+ i.intent === EInstIntent.return &&
1551
+ i.intentSource === this);
1430
1552
  if (returnIndex !== -1)
1431
1553
  inst = inst.slice(0, returnIndex + 1);
1432
1554
  inst.push(new AddressResolver(this.inlineEnd));
1433
1555
  // return the function value
1434
- return [this.inlineTemp.value, inst];
1556
+ return [this.inlineTemp, inst];
1435
1557
  }
1436
- call(scope, args) {
1558
+ call(scope, args, out) {
1437
1559
  this.ensureInit();
1438
- if (args.length !== this.paramOwners.length)
1560
+ if (args.length !== this.paramNames.length)
1439
1561
  throw new CompilerError("Cannot call: argument count not matching.");
1440
- const inlineCall = this.inlineCall(scope, args);
1562
+ const inlineCall = this.inlineCall(scope, args, out);
1563
+ hideRedundantJumps(inlineCall[1]);
1441
1564
  const inlineSize = inlineCall[1].filter(i => !i.hidden).length;
1442
1565
  if (this.inline || inlineSize <= this.callSize)
1443
1566
  return inlineCall;
1444
- return this.normalCall(scope, args);
1567
+ return this.normalCall(scope, args, out);
1445
1568
  }
1446
1569
  return(scope, arg) {
1447
1570
  this.ensureInit();
@@ -1456,8 +1579,11 @@ class FunctionValue extends VoidValue {
1456
1579
  eval(_scope) {
1457
1580
  return [this, []];
1458
1581
  }
1459
- consume(_scope) {
1460
- return [this, []];
1582
+ paramOuts() {
1583
+ this.ensureInit();
1584
+ if (this.inline || this.tryingInline)
1585
+ return;
1586
+ return this.paramValues;
1461
1587
  }
1462
1588
  }
1463
1589
  function endsWithReturn(inst) {
@@ -1478,7 +1604,7 @@ function endsWithReturn(inst) {
1478
1604
  return false;
1479
1605
  }
1480
1606
 
1481
- function handleFunctionNode(c, scope, node) {
1607
+ function handleFunctionNode(c, scope, node, out) {
1482
1608
  let { params, body } = node;
1483
1609
  if (es.isExpression(body)) {
1484
1610
  const { loc } = body;
@@ -1493,46 +1619,44 @@ function handleFunctionNode(c, scope, node) {
1493
1619
  params: params,
1494
1620
  body,
1495
1621
  c,
1622
+ out,
1496
1623
  }),
1497
1624
  [],
1498
1625
  ];
1499
1626
  }
1500
- const ArrowFunctionExpression = (c, scope, node) => {
1501
- return handleFunctionNode(c, scope, node);
1627
+ const ArrowFunctionExpression = (c, scope, node, out) => {
1628
+ return handleFunctionNode(c, scope, node, out);
1502
1629
  };
1503
1630
  const FunctionDeclaration = (c, scope, node) => {
1504
1631
  const identifier = node.id.name;
1505
1632
  const name = nodeName(node, !c.compactNames && identifier);
1506
- const functionIns = handleFunctionNode(c, scope, node);
1507
- const owner = new ValueOwner({
1508
- scope,
1509
- value: functionIns[0],
1510
- identifier,
1511
- name,
1512
- });
1513
- return [scope.set(owner), []];
1633
+ const functionIns = handleFunctionNode(c, scope, node, name);
1634
+ scope.set(identifier, functionIns[0]);
1635
+ return functionIns;
1514
1636
  };
1515
1637
  const FunctionExpression = ArrowFunctionExpression;
1516
1638
  const ObjectMethod = ArrowFunctionExpression;
1517
1639
 
1518
- const CallExpression = (c, scope, node) => {
1640
+ const CallExpression = (c, scope, node, out) => {
1519
1641
  const inst = [];
1520
- const args = node.arguments.map(node => {
1642
+ const [callee, calleeInst] = c.handleEval(scope, node.callee);
1643
+ inst.push(...calleeInst);
1644
+ const paramOuts = callee.paramOuts();
1645
+ const args = node.arguments.map((node, index) => {
1521
1646
  // TODO: this might be why t0 did not increment
1522
1647
  // TODO: figure out if the previous todo is still relevant
1523
- const [v, i] = c.handleConsume(scope, node);
1648
+ const out = paramOuts?.[index];
1649
+ const [v, i] = c.handleEval(scope, node, out);
1524
1650
  inst.push(...i);
1525
1651
  return v;
1526
1652
  });
1527
- const [callee, calleeInst] = c.handleConsume(scope, node.callee);
1528
- inst.push(...calleeInst);
1529
- const [callValue, callInst] = callee.call(scope, args);
1653
+ const [callValue, callInst] = callee.call(scope, args, out);
1530
1654
  inst.push(...callInst);
1531
1655
  return [callValue, inst];
1532
1656
  };
1533
1657
  const NewExpression = CallExpression;
1534
- const TaggedTemplateExpression = (c, scope, node) => {
1535
- const [tag, tagInst] = c.handleConsume(scope, node.tag);
1658
+ const TaggedTemplateExpression = (c, scope, node, out) => {
1659
+ const [tag, tagInst] = c.handleEval(scope, node.tag);
1536
1660
  const template = node.quasi;
1537
1661
  const stringsObject = ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.cooked ?? "")), {
1538
1662
  raw: ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.raw))),
@@ -1540,15 +1664,15 @@ const TaggedTemplateExpression = (c, scope, node) => {
1540
1664
  const expressions = [];
1541
1665
  const expressionInsts = [];
1542
1666
  template.expressions.forEach(expression => {
1543
- const [value, inst] = c.handleConsume(scope, expression);
1667
+ const [value, inst] = c.handleEval(scope, expression);
1544
1668
  expressions.push(value);
1545
1669
  expressionInsts.push(...inst);
1546
1670
  });
1547
- const [result, resultInst] = tag.call(scope, [stringsObject, ...expressions]);
1671
+ const [result, resultInst] = tag.call(scope, [stringsObject, ...expressions], out);
1548
1672
  return [result, [...expressionInsts, ...tagInst, ...resultInst]];
1549
1673
  };
1550
1674
 
1551
- const ObjectExpression = (c, scope, node) => {
1675
+ const ObjectExpression = (c, scope, node, out) => {
1552
1676
  const data = {};
1553
1677
  const inst = [];
1554
1678
  for (const prop of node.properties) {
@@ -1568,38 +1692,48 @@ const ObjectExpression = (c, scope, node) => {
1568
1692
  else {
1569
1693
  throw new CompilerError(`Unsupported object key type: ${key.type}`, key);
1570
1694
  }
1571
- const [member, memberInst] = c.handleEval(scope, value);
1572
- // TODO: use an approach that can be defined by each type
1573
- if (!member.owner && !(member instanceof LiteralValue)) {
1574
- new ValueOwner({
1575
- scope,
1576
- constant: true,
1577
- value: member,
1578
- });
1579
- }
1695
+ const [member, memberInst] = c.handleEval(scope, value, extractDestrucuringOut(out, index));
1580
1696
  data[index] = member;
1581
1697
  inst.push(...memberInst);
1582
1698
  }
1583
1699
  return [new ObjectValue(data), inst];
1584
1700
  };
1585
- const ArrayExpression = (c, scope, node) => {
1701
+ const ArrayExpression = (c, scope, node, out) => {
1586
1702
  const items = [];
1587
1703
  const inst = [];
1588
- node.elements.forEach(element => {
1704
+ node.elements.forEach((element, i) => {
1589
1705
  const [value, valueInst] = element
1590
- ? c.handleEval(scope, element)
1706
+ ? c.handleEval(scope, element, extractDestrucuringOut(out, i))
1591
1707
  : [undefined, []];
1592
1708
  items.push(value);
1593
1709
  inst.push(...valueInst);
1594
1710
  });
1595
1711
  return [ObjectValue.fromArray(items), inst];
1596
1712
  };
1597
- const MemberExpression = (c, scope, node) => {
1598
- const [obj, objInst] = c.handleConsume(scope, node.object);
1713
+ const MemberExpression = (c, scope, node, out) => {
1599
1714
  const [prop, propInst] = node.computed
1600
- ? c.handleConsume(scope, node.property)
1715
+ ? c.handleEval(scope, node.property)
1601
1716
  : [new LiteralValue(node.property.name), []];
1602
- const [got, gotInst] = obj.get(scope, prop);
1717
+ if (!out) {
1718
+ const [obj, objInst] = c.handleEval(scope, node.object);
1719
+ const [got, gotInst] = obj.get(scope, prop);
1720
+ return [got, [...objInst, ...propInst, ...gotInst]];
1721
+ }
1722
+ const temp = SenseableValue.out(scope, out);
1723
+ const objOut = new DestructuringValue(new Map([
1724
+ [
1725
+ prop,
1726
+ {
1727
+ value: temp,
1728
+ // a direct assignment should NOT happen
1729
+ handler: () => {
1730
+ throw new CompilerError("Unexpected destructuring assignment");
1731
+ },
1732
+ },
1733
+ ],
1734
+ ]));
1735
+ const [obj, objInst] = c.handleEval(scope, node.object, objOut);
1736
+ const [got, gotInst] = obj.get(scope, prop, temp);
1603
1737
  return [got, [...objInst, ...propInst, ...gotInst]];
1604
1738
  };
1605
1739
  const ArrayPattern = (c, scope, node) => {
@@ -1609,11 +1743,21 @@ const ArrayPattern = (c, scope, node) => {
1609
1743
  const element = node.elements[i];
1610
1744
  if (!element)
1611
1745
  continue;
1612
- const valueInst = c.handle(scope, element);
1613
- if (!valueInst[0])
1746
+ const [value, valueInst] = c.handle(scope, element);
1747
+ if (!value)
1614
1748
  throw new CompilerError("Destructuring element must resolve to a value", element);
1615
- inst.push(...valueInst[1]);
1616
- members.set(new LiteralValue(i), valueInst[0]);
1749
+ inst.push(...valueInst);
1750
+ members.set(new LiteralValue(i), {
1751
+ value,
1752
+ handler(get) {
1753
+ return c.handle(scope, element, () => {
1754
+ const inst = get();
1755
+ // assigns the output to the target value
1756
+ inst[1].push(...value["="](scope, inst[0])[1]);
1757
+ return inst;
1758
+ });
1759
+ },
1760
+ });
1617
1761
  }
1618
1762
  return [new DestructuringValue(members), inst];
1619
1763
  };
@@ -1628,13 +1772,23 @@ const ObjectPattern = (c, scope, node) => {
1628
1772
  const { key } = prop;
1629
1773
  const keyInst = !prop.computed && key.type === "Identifier"
1630
1774
  ? [new LiteralValue(key.name), []]
1631
- : c.handleConsume(scope, prop.key);
1775
+ : c.handleEval(scope, prop.key);
1632
1776
  inst.push(...keyInst[1]);
1633
- const valueInst = c.handle(scope, prop.value);
1634
- if (!valueInst[0])
1777
+ const [value, valueInst] = c.handle(scope, prop.value);
1778
+ if (!value)
1635
1779
  throw new CompilerError("Destructuring member must resolve to a value", prop.value);
1636
- inst.push(...valueInst[1]);
1637
- members.set(keyInst[0], valueInst[0]);
1780
+ inst.push(...valueInst);
1781
+ members.set(keyInst[0], {
1782
+ value,
1783
+ handler(get) {
1784
+ return c.handle(scope, prop, () => {
1785
+ const inst = get();
1786
+ // assigns the output to the target value
1787
+ inst[1].push(...value["="](scope, inst[0])[1]);
1788
+ return inst;
1789
+ });
1790
+ },
1791
+ });
1638
1792
  }
1639
1793
  return [new DestructuringValue(members), inst];
1640
1794
  };
@@ -1679,7 +1833,7 @@ const TSEnumDeclaration = (c, scope, node) => {
1679
1833
  : [new LiteralValue(counter)];
1680
1834
  if (!(value instanceof LiteralValue))
1681
1835
  throw new CompilerError("Enum members must contain literal values", member);
1682
- if (typeof value.data === "number") {
1836
+ if (value.isNumber()) {
1683
1837
  lastType = "number";
1684
1838
  counter = value.data + 1;
1685
1839
  }
@@ -1689,13 +1843,9 @@ const TSEnumDeclaration = (c, scope, node) => {
1689
1843
  const name = member.id.type === "Identifier" ? member.id.name : member.id.value;
1690
1844
  data[name] = value;
1691
1845
  }
1692
- scope.set(new ValueOwner({
1693
- scope,
1694
- value: new ObjectValue(data),
1695
- constant: true,
1696
- identifier: node.id.name,
1697
- name: nodeName(node, !c.compactNames && node.id.name),
1698
- }));
1846
+ const value = new ObjectValue(data);
1847
+ value.name = nodeName(node, !c.compactNames && node.id.name);
1848
+ scope.set(node.id.name, value);
1699
1849
  return [null, []];
1700
1850
  };
1701
1851
  const TSNonNullExpression = (c, scope, node) => {
@@ -1759,15 +1909,12 @@ class MacroFunction extends VoidValue {
1759
1909
  this.mutability = EMutability.constant;
1760
1910
  this.fn = fn;
1761
1911
  }
1762
- call(scope, args) {
1763
- return this.fn.apply(this, [scope, ...args]);
1912
+ call(scope, args, out) {
1913
+ return this.fn.apply(this, [scope, out, ...args]);
1764
1914
  }
1765
1915
  eval(_scope) {
1766
1916
  return [this, []];
1767
1917
  }
1768
- consume(_scope) {
1769
- return [this, []];
1770
- }
1771
1918
  }
1772
1919
 
1773
1920
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
@@ -1791,49 +1938,39 @@ const mathOperations = {
1791
1938
  sqrt: a => Math.sqrt(a),
1792
1939
  rand: null,
1793
1940
  };
1794
- function mathConstant(scope, name) {
1795
- return new ValueOwner({
1796
- scope,
1797
- constant: true,
1798
- name,
1799
- value: assign(new StoreValue(scope), {
1800
- mutability: EMutability.constant,
1801
- }),
1802
- }).value;
1803
- }
1804
- function createMacroMathOperations(scope) {
1941
+ function createMacroMathOperations() {
1805
1942
  const macroMathOperations = {
1806
- PI: mathConstant(scope, "@pi"),
1807
- E: mathConstant(scope, "@e"),
1808
- degToRad: mathConstant(scope, "@degToRad"),
1809
- radToDeg: mathConstant(scope, "@radToDeg"),
1943
+ PI: new StoreValue("@pi", EMutability.constant),
1944
+ E: new StoreValue("@e", EMutability.constant),
1945
+ degToRad: new StoreValue("@degToRad", EMutability.constant),
1946
+ radToDeg: new StoreValue("@radToDeg", EMutability.constant),
1810
1947
  };
1811
1948
  for (const key in mathOperations) {
1812
1949
  const fn = mathOperations[key];
1813
- macroMathOperations[key] = new MacroFunction((scope, a, b) => {
1950
+ macroMathOperations[key] = new MacroFunction((scope, out, a, b) => {
1814
1951
  if (b) {
1815
1952
  if (fn && a instanceof LiteralValue && b instanceof LiteralValue) {
1816
- if (typeof a.data !== "number" || typeof b.data !== "number")
1953
+ if (!a.isNumber() || !b.isNumber())
1817
1954
  throw new CompilerError("Cannot do math operation with non-numerical literals.");
1818
1955
  return [new LiteralValue(fn(a.num, b.num)), []];
1819
1956
  }
1820
- const temp = new StoreValue(scope);
1957
+ const temp = StoreValue.from(scope, out);
1821
1958
  return [temp, [new OperationInstruction(key, temp, a, b)]];
1822
1959
  }
1823
1960
  if (fn && a instanceof LiteralValue) {
1824
- if (typeof a.data !== "number")
1961
+ if (!a.isNumber())
1825
1962
  throw new CompilerError("Cannot do math operation with non-numerical literal.");
1826
1963
  return [new LiteralValue(fn(a.num)), []];
1827
1964
  }
1828
- const temp = new StoreValue(scope);
1965
+ const temp = StoreValue.from(scope, out);
1829
1966
  return [temp, [new OperationInstruction(key, temp, a, b)]];
1830
1967
  });
1831
1968
  }
1832
1969
  return macroMathOperations;
1833
1970
  }
1834
1971
  class MlogMath extends ObjectValue {
1835
- constructor(scope) {
1836
- super(createMacroMathOperations(scope));
1972
+ constructor() {
1973
+ super(createMacroMathOperations());
1837
1974
  }
1838
1975
  }
1839
1976
  // op angle result a b
@@ -1841,7 +1978,7 @@ class MlogMath extends ObjectValue {
1841
1978
 
1842
1979
  class Concat extends MacroFunction {
1843
1980
  constructor() {
1844
- super((scope, ...values) => {
1981
+ super((scope, out, ...values) => {
1845
1982
  for (const value of values)
1846
1983
  if (!(value instanceof LiteralValue))
1847
1984
  throw new CompilerError("Concat arguments must all be literal values.");
@@ -1853,66 +1990,19 @@ class Concat extends MacroFunction {
1853
1990
  }
1854
1991
  }
1855
1992
 
1856
- /** Asserts that `value` is a `LiteralValue` that contains a string */
1857
- function assertStringLiteral(value, name) {
1858
- if (!(value instanceof LiteralValue) || typeof value.data !== "string")
1859
- throw new CompilerError(`${name} must be a string literal`);
1860
- }
1861
- /** Asserts that `value` is a string literal that is part of a given set of `members`*/
1862
- function assertLiteralOneOf(value, members, name) {
1863
- assertStringLiteral(value, name);
1864
- if (!members.includes(value.data))
1865
- throw new CompilerError(`${name} must be one of: ${members
1866
- .slice(0, -1)
1867
- .map(member => `"${member}"`)
1868
- .join(", ")} or "${members[members.length - 1]}"`);
1869
- }
1870
- /** Asserts that `value` is either a store or a literal */
1871
- function assertIsRuntimeValue(value, name) {
1872
- if (!(value instanceof LiteralValue) && !(value instanceof StoreValue))
1873
- throw new CompilerError(`${name} must be a valid runtime value`);
1874
- }
1875
- /** Asserts that `value` is an `ObjectValue` */
1876
- function assertIsObjectMacro(value, name) {
1877
- if (!(value instanceof ObjectValue))
1878
- throw new CompilerError(`${name} must be an object macro`);
1879
- }
1880
- /** Asserts that `value` is an `ObjectValue` that has a length property */
1881
- function assertIsArrayMacro(value, name) {
1882
- if (!(value instanceof ObjectValue) || !value.data.length)
1883
- throw new CompilerError(`${name} must be an array macro`);
1884
- }
1885
- /** Asserts that all of the fields described are present on `value`*/
1886
- function assertObjectFields(value, fields) {
1887
- const result = [];
1888
- for (const field of fields) {
1889
- const param = typeof field === "object" ? field : { key: field };
1890
- const item = value.data[param.key];
1891
- if (item) {
1892
- param.validate?.(item);
1893
- result.push(item);
1894
- continue;
1895
- }
1896
- if (param.default == undefined)
1897
- throw new CompilerError(`The field "${param.name ?? param.key}" must be present`);
1898
- result.push(param.default);
1899
- }
1900
- return result;
1901
- }
1902
-
1903
1993
  /** Used to create namespaces that contain multiple methods that map into instructions */
1904
1994
  function createOverloadNamespace({ overloads, handler, }) {
1905
1995
  const result = {};
1906
1996
  for (const key in overloads) {
1907
1997
  const { named, args } = overloads[key];
1908
1998
  if (named) {
1909
- result[key] = new MacroFunction((scope, options) => {
1999
+ result[key] = new MacroFunction((scope, out, options) => {
1910
2000
  assertIsObjectMacro(options, named);
1911
- return handler(scope, key, ...assertObjectFields(options, args));
2001
+ return handler(scope, key, out, ...assertObjectFields(options, args));
1912
2002
  });
1913
2003
  continue;
1914
2004
  }
1915
- result[key] = new MacroFunction((scope, ...params) => {
2005
+ result[key] = new MacroFunction((scope, out, ...params) => {
1916
2006
  const handlerParams = [];
1917
2007
  // validate the paramters
1918
2008
  for (let i = 0; i < args.length; i++) {
@@ -1929,7 +2019,7 @@ function createOverloadNamespace({ overloads, handler, }) {
1929
2019
  arg.validate?.(param);
1930
2020
  handlerParams.push(param);
1931
2021
  }
1932
- return handler(scope, key, ...handlerParams);
2022
+ return handler(scope, key, out, ...handlerParams);
1933
2023
  });
1934
2024
  }
1935
2025
  return result;
@@ -1974,7 +2064,7 @@ class Draw extends ObjectValue {
1974
2064
  args: ["x", "y", "image", "size", "rotation"],
1975
2065
  },
1976
2066
  },
1977
- handler(scope, overload, ...args) {
2067
+ handler(scope, overload, out, ...args) {
1978
2068
  return [null, [new InstructionBase("draw", overload, ...args)]];
1979
2069
  },
1980
2070
  });
@@ -1984,7 +2074,7 @@ class Draw extends ObjectValue {
1984
2074
 
1985
2075
  class Print extends MacroFunction {
1986
2076
  constructor() {
1987
- super((scope, ...values) => {
2077
+ super((scope, out, ...values) => {
1988
2078
  const [first] = values;
1989
2079
  const inst = [];
1990
2080
  if (!isTemplateObjectArray(first)) {
@@ -2013,7 +2103,7 @@ class Print extends MacroFunction {
2013
2103
  const defaultTargetName$1 = "message1";
2014
2104
  class PrintFlush extends MacroFunction {
2015
2105
  constructor() {
2016
- super((scope, target) => {
2106
+ super((scope, out, target) => {
2017
2107
  if (!target)
2018
2108
  return [null, [new InstructionBase("printflush", defaultTargetName$1)]];
2019
2109
  if (!(target instanceof SenseableValue))
@@ -2026,7 +2116,7 @@ class PrintFlush extends MacroFunction {
2026
2116
  const defaultTargetName = "display1";
2027
2117
  class DrawFlush extends MacroFunction {
2028
2118
  constructor() {
2029
- super((scope, target) => {
2119
+ super((scope, out, target) => {
2030
2120
  if (!target)
2031
2121
  return [null, [new InstructionBase("drawflush", defaultTargetName)]];
2032
2122
  if (!(target instanceof SenseableValue))
@@ -2045,10 +2135,10 @@ class GetBlock extends ObjectValue {
2045
2135
  block: { args: ["x", "y"] },
2046
2136
  building: { args: ["x", "y"] },
2047
2137
  },
2048
- handler(scope, overload, x, y) {
2138
+ handler(scope, overload, out, x, y) {
2049
2139
  const output = overload === "building"
2050
- ? new SenseableValue(scope)
2051
- : new StoreValue(scope);
2140
+ ? SenseableValue.from(scope, out, EMutability.constant)
2141
+ : StoreValue.from(scope, out, EMutability.constant);
2052
2142
  return [
2053
2143
  output,
2054
2144
  [new InstructionBase("getblock", overload, output, x, y)],
@@ -2061,11 +2151,11 @@ class GetBlock extends ObjectValue {
2061
2151
 
2062
2152
  class GetLink extends MacroFunction {
2063
2153
  constructor() {
2064
- super((scope, index) => {
2154
+ super((scope, out, index) => {
2065
2155
  if (!(index instanceof StoreValue) &&
2066
- (!(index instanceof LiteralValue) || typeof index.data !== "number"))
2156
+ (!(index instanceof LiteralValue) || !index.isNumber()))
2067
2157
  throw new CompilerError("The getlink index must be a number literal or a store");
2068
- const outBuild = new SenseableValue(scope);
2158
+ const outBuild = SenseableValue.from(scope, out, EMutability.constant);
2069
2159
  return [outBuild, [new InstructionBase("getlink", outBuild, index)]];
2070
2160
  });
2071
2161
  }
@@ -2087,11 +2177,10 @@ class Control extends ObjectValue {
2087
2177
  config: { args: ["building", "value"] },
2088
2178
  color: { args: ["building", "r", "g", "b"] },
2089
2179
  },
2090
- handler(scope, overload, ...args) {
2180
+ handler(scope, overload, out, ...args) {
2091
2181
  if (overload !== "color")
2092
2182
  return [null, [new InstructionBase("control", overload, ...args)]];
2093
- const temp = new StoreValue(scope);
2094
- temp.ensureOwned();
2183
+ const temp = StoreValue.from(scope);
2095
2184
  // this "patch" is here for compatibility reasons
2096
2185
  // I don't want to add another breaking change
2097
2186
  // TODO: figure out if this patch should be removed later
@@ -2127,14 +2216,14 @@ const validRadarSorts = [
2127
2216
  ];
2128
2217
  class Radar extends MacroFunction {
2129
2218
  constructor() {
2130
- super((scope, options) => {
2219
+ super((scope, out, options) => {
2131
2220
  assertIsObjectMacro(options, "The radar options");
2132
2221
  const { building, filters, order, sort } = options.data;
2133
2222
  if (!(building instanceof SenseableValue))
2134
2223
  throw new CompilerError("The building must a senseable value");
2135
2224
  assertIsArrayMacro(filters, "filters");
2136
2225
  const { length } = filters.data;
2137
- if (!(length instanceof LiteralValue) || typeof length.data !== "number")
2226
+ if (!(length instanceof LiteralValue) || !length.isNumber())
2138
2227
  throw new CompilerError("The length of an array macro must be");
2139
2228
  if (length.data !== 3)
2140
2229
  throw new CompilerError("The filters array must have 3 items");
@@ -2146,7 +2235,7 @@ class Radar extends MacroFunction {
2146
2235
  if (!(order instanceof LiteralValue || order instanceof StoreValue))
2147
2236
  throw new CompilerError("The radar order must be a literal or a store");
2148
2237
  assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
2149
- const outUnit = new SenseableValue(scope);
2238
+ const outUnit = SenseableValue.from(scope, out, EMutability.constant);
2150
2239
  return [
2151
2240
  outUnit,
2152
2241
  [
@@ -2159,14 +2248,12 @@ class Radar extends MacroFunction {
2159
2248
 
2160
2249
  class Sensor extends MacroFunction {
2161
2250
  constructor() {
2162
- super((scope, property, target) => {
2251
+ super((scope, out, property, target) => {
2163
2252
  if (!(property instanceof StoreValue))
2164
2253
  throw new CompilerError("The sensor property must be a store");
2165
2254
  if (!(target instanceof SenseableValue))
2166
2255
  throw new CompilerError("The sensor target must be a senseable value");
2167
- const temp = assign(new SenseableValue(scope), {
2168
- mutability: EMutability.readonly,
2169
- });
2256
+ const temp = SenseableValue.from(scope, out, EMutability.readonly);
2170
2257
  return [temp, [new InstructionBase("sensor", temp, target, property)]];
2171
2258
  });
2172
2259
  }
@@ -2174,9 +2261,9 @@ class Sensor extends MacroFunction {
2174
2261
 
2175
2262
  class Wait extends MacroFunction {
2176
2263
  constructor() {
2177
- super((scope, seconds) => {
2264
+ super((scope, out, seconds) => {
2178
2265
  if (!(seconds instanceof StoreValue) &&
2179
- !(seconds instanceof LiteralValue && typeof seconds.data === "number"))
2266
+ !(seconds instanceof LiteralValue && seconds.isNumber()))
2180
2267
  throw new CompilerError("The wait seconds must be either a number literal or a store");
2181
2268
  return [null, [new InstructionBase("wait", seconds)]];
2182
2269
  });
@@ -2192,8 +2279,8 @@ class Lookup extends ObjectValue {
2192
2279
  item: { args: ["index"] },
2193
2280
  liquid: { args: ["index"] },
2194
2281
  },
2195
- handler(scope, overload, index) {
2196
- const output = new StoreValue(scope);
2282
+ handler(scope, overload, out, index) {
2283
+ const output = StoreValue.from(scope, out);
2197
2284
  return [
2198
2285
  output,
2199
2286
  [new InstructionBase("lookup", overload, output, index)],
@@ -2206,15 +2293,15 @@ class Lookup extends ObjectValue {
2206
2293
 
2207
2294
  class PackColor extends MacroFunction {
2208
2295
  constructor() {
2209
- super((scope, ...args) => {
2296
+ super((scope, out, ...args) => {
2210
2297
  if (args.length !== 4) {
2211
2298
  throw new CompilerError(`Expected 4 arguments, received ${args.length}`);
2212
2299
  }
2213
2300
  if (!args.every((value) => value instanceof StoreValue ||
2214
- (value instanceof LiteralValue && typeof value.data === "number"))) {
2301
+ (value instanceof LiteralValue && value.isNumber()))) {
2215
2302
  throw new CompilerError("packColor arguments must be either stores or number literals");
2216
2303
  }
2217
- const output = new StoreValue(scope);
2304
+ const output = StoreValue.from(scope, out);
2218
2305
  return [output, [new InstructionBase("packcolor", output, ...args)]];
2219
2306
  });
2220
2307
  }
@@ -2237,7 +2324,7 @@ class SetBlock extends ObjectValue {
2237
2324
  args: ["x", "y", "to", "team", { key: "rotation", default: "0" }],
2238
2325
  },
2239
2326
  },
2240
- handler(scope, overload, x, y, to, team, rotation) {
2327
+ handler(scope, overload, out, x, y, to, team, rotation) {
2241
2328
  return [
2242
2329
  null,
2243
2330
  [
@@ -2252,14 +2339,14 @@ class SetBlock extends ObjectValue {
2252
2339
 
2253
2340
  class SpawnUnit extends MacroFunction {
2254
2341
  constructor() {
2255
- super((scope, options) => {
2342
+ super((scope, out, options) => {
2256
2343
  assertIsObjectMacro(options, "The options");
2257
2344
  const { type, x, y, team, rotation } = options.data;
2258
2345
  assertIsRuntimeValue(type, "type");
2259
2346
  assertIsRuntimeValue(x, "x");
2260
2347
  assertIsRuntimeValue(y, "y");
2261
2348
  assertIsRuntimeValue(team, "team");
2262
- const output = new SenseableValue(scope);
2349
+ const output = SenseableValue.from(scope, out, EMutability.constant);
2263
2350
  return [
2264
2351
  output,
2265
2352
  [
@@ -2278,7 +2365,7 @@ class Stop extends MacroFunction {
2278
2365
 
2279
2366
  class UnitBind extends MacroFunction {
2280
2367
  constructor() {
2281
- super((scope, type) => {
2368
+ super((scope, out, type) => {
2282
2369
  if (!(type instanceof StoreValue))
2283
2370
  throw new CompilerError("The unitBind type must be a store");
2284
2371
  return [null, [new InstructionBase("ubind", type)]];
@@ -2317,21 +2404,20 @@ class UnitControl extends ObjectValue {
2317
2404
  args: ["x", "y", "radius"],
2318
2405
  },
2319
2406
  },
2320
- handler(scope, overload, ...args) {
2407
+ handler(scope, overload, out, ...args) {
2321
2408
  let result = null;
2322
2409
  let extraArgs = [];
2323
2410
  switch (overload) {
2324
2411
  case "getBlock": {
2325
- const outType = new StoreValue(scope);
2326
- const outBuilding = new SenseableValue(scope);
2412
+ const outType = StoreValue.from(scope, extractDestrucuringOut(out, 0));
2413
+ const outBuilding = SenseableValue.from(scope, extractDestrucuringOut(out, 1));
2327
2414
  result = ObjectValue.fromArray([outType, outBuilding]);
2328
2415
  extraArgs = [outType, outBuilding, new LiteralValue(0)];
2329
2416
  break;
2330
2417
  }
2331
2418
  case "within": {
2332
- const temp = new StoreValue(scope);
2333
- result = temp;
2334
- extraArgs = [temp, new LiteralValue(0)];
2419
+ result = StoreValue.from(scope, out);
2420
+ extraArgs = [result, new LiteralValue(0)];
2335
2421
  break;
2336
2422
  }
2337
2423
  }
@@ -2347,12 +2433,12 @@ class UnitControl extends ObjectValue {
2347
2433
 
2348
2434
  class UnitRadar extends MacroFunction {
2349
2435
  constructor() {
2350
- super((scope, options) => {
2436
+ super((scope, out, options) => {
2351
2437
  assertIsObjectMacro(options, "The radar options");
2352
2438
  const { filters, order, sort } = options.data;
2353
2439
  assertIsArrayMacro(filters, "filters");
2354
2440
  const { length } = filters.data;
2355
- if (!(length instanceof LiteralValue) || typeof length.data !== "number")
2441
+ if (!(length instanceof LiteralValue) || !length.isNumber())
2356
2442
  throw new CompilerError("The length of an array macro must be");
2357
2443
  if (length.data !== 3)
2358
2444
  throw new CompilerError("The filters array must have 3 items");
@@ -2364,7 +2450,7 @@ class UnitRadar extends MacroFunction {
2364
2450
  if (!(order instanceof LiteralValue || order instanceof StoreValue))
2365
2451
  throw new CompilerError("The radar order must be a literal or a store");
2366
2452
  assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
2367
- const outUnit = new SenseableValue(scope);
2453
+ const outUnit = SenseableValue.from(scope, out, EMutability.constant);
2368
2454
  return [
2369
2455
  outUnit,
2370
2456
  [
@@ -2402,11 +2488,11 @@ class UnitLocate extends ObjectValue {
2402
2488
  args: [],
2403
2489
  },
2404
2490
  },
2405
- handler(scope, overload, ...args) {
2406
- const outFound = new StoreValue(scope);
2407
- const outX = new StoreValue(scope);
2408
- const outY = new StoreValue(scope);
2409
- const outBuilding = new SenseableValue(scope);
2491
+ handler(scope, overload, out, ...args) {
2492
+ const outFound = StoreValue.from(scope, extractDestrucuringOut(out, 0));
2493
+ const outX = StoreValue.from(scope, extractDestrucuringOut(out, 1));
2494
+ const outY = StoreValue.from(scope, extractDestrucuringOut(out, 2));
2495
+ const outBuilding = SenseableValue.from(scope, overload === "ore" ? discardedName : extractDestrucuringOut(out, 3));
2410
2496
  const outArgs = [outX, outY, outFound, outBuilding];
2411
2497
  let inputArgs = [];
2412
2498
  switch (overload) {
@@ -2469,7 +2555,7 @@ class ApplyStatus extends ObjectValue {
2469
2555
  },
2470
2556
  clear: { args: ["status", "unit"] },
2471
2557
  },
2472
- handler(scope, overload, effect, unit, duration) {
2558
+ handler(scope, overload, out, effect, unit, duration) {
2473
2559
  if (!(effect instanceof LiteralValue))
2474
2560
  throw new CompilerError("The status effect must be a string literal");
2475
2561
  assertLiteralOneOf(effect, validEffects, "The status effect");
@@ -2487,7 +2573,7 @@ class ApplyStatus extends ObjectValue {
2487
2573
 
2488
2574
  class SpawnWave extends MacroFunction {
2489
2575
  constructor() {
2490
- super((scope, natural, x, y) => {
2576
+ super((scope, out, natural, x, y) => {
2491
2577
  if (!(natural instanceof LiteralValue) ||
2492
2578
  (natural.data !== 1 && natural.data !== 0))
2493
2579
  throw new CompilerError("The 'natural' argument must be a boolean literal");
@@ -2528,7 +2614,7 @@ class SetRule extends ObjectValue {
2528
2614
  rtsMinWeight: { args: ["team", "value"] },
2529
2615
  rtsMinSquad: { args: ["team", "value"] },
2530
2616
  },
2531
- handler(scope, overload, ...args) {
2617
+ handler(scope, overload, out, ...args) {
2532
2618
  const params = ["10", "0", "0", "100", "100"];
2533
2619
  switch (overload) {
2534
2620
  case "mapArea": {
@@ -2571,7 +2657,7 @@ class FlushMessage extends ObjectValue {
2571
2657
  announce: { args: ["duration"] },
2572
2658
  toast: { args: ["duration"] },
2573
2659
  },
2574
- handler(scope, overload, duration) {
2660
+ handler(scope, overload, out, duration) {
2575
2661
  return [null, [new InstructionBase("message", overload, duration)]];
2576
2662
  },
2577
2663
  });
@@ -2587,7 +2673,7 @@ class Cutscene extends ObjectValue {
2587
2673
  zoom: { args: ["level"] },
2588
2674
  stop: { args: [] },
2589
2675
  },
2590
- handler(scope, overload, ...args) {
2676
+ handler(scope, overload, out, ...args) {
2591
2677
  const params = Object.assign(["100", "100", "0.06", "0"], args);
2592
2678
  return [null, [new InstructionBase("cutscene", overload, ...params)]];
2593
2679
  },
@@ -2598,7 +2684,7 @@ class Cutscene extends ObjectValue {
2598
2684
 
2599
2685
  class Explosion extends MacroFunction {
2600
2686
  constructor() {
2601
- super((scope, options) => {
2687
+ super((scope, out, options) => {
2602
2688
  assertIsObjectMacro(options, "The options");
2603
2689
  const params = assertObjectFields(options, [
2604
2690
  "team",
@@ -2617,7 +2703,7 @@ class Explosion extends MacroFunction {
2617
2703
 
2618
2704
  class SetRate extends MacroFunction {
2619
2705
  constructor() {
2620
- super((scope, ipt) => [null, [new InstructionBase("setrate", ipt)]]);
2706
+ super((scope, out, ipt) => [null, [new InstructionBase("setrate", ipt)]]);
2621
2707
  }
2622
2708
  }
2623
2709
 
@@ -2634,8 +2720,8 @@ class Fetch extends ObjectValue {
2634
2720
  build: { args: ["team", "index", "block"] },
2635
2721
  buildCount: { args: ["team", "block"] },
2636
2722
  },
2637
- handler(scope, overload, team, ...rest) {
2638
- const output = new SenseableValue(scope);
2723
+ handler(scope, overload, out, team, ...rest) {
2724
+ const output = SenseableValue.from(scope, out, EMutability.constant);
2639
2725
  const params = ["0", "@conveyor"];
2640
2726
  if (overload === "buildCount") {
2641
2727
  params[1] = rest[0];
@@ -2655,8 +2741,8 @@ class Fetch extends ObjectValue {
2655
2741
 
2656
2742
  class GetFlag extends MacroFunction {
2657
2743
  constructor() {
2658
- super((scope, flag) => {
2659
- const result = new StoreValue(scope);
2744
+ super((scope, out, flag) => {
2745
+ const result = StoreValue.from(scope, out);
2660
2746
  return [result, [new InstructionBase("getflag", result, flag)]];
2661
2747
  });
2662
2748
  }
@@ -2664,7 +2750,7 @@ class GetFlag extends MacroFunction {
2664
2750
 
2665
2751
  class SetFlag extends MacroFunction {
2666
2752
  constructor() {
2667
- super((scope, flag, value) => {
2753
+ super((scope, out, flag, value) => {
2668
2754
  if (!flag)
2669
2755
  throw new CompilerError("Missing flag argument");
2670
2756
  if (!value)
@@ -2683,28 +2769,17 @@ class MemoryEntry extends BaseValue {
2683
2769
  this.macro = true;
2684
2770
  this.store = null;
2685
2771
  }
2686
- eval(scope) {
2772
+ eval(scope, out) {
2687
2773
  if (this.store)
2688
2774
  return [this.store, []];
2689
- const temp = new StoreValue(scope);
2775
+ const temp = (this.store = StoreValue.from(scope, out));
2690
2776
  return [
2691
2777
  temp,
2692
2778
  [new InstructionBase("read", temp, this.mem.cell, this.prop)],
2693
2779
  ];
2694
2780
  }
2695
- consume(scope) {
2696
- if (this.store)
2697
- return [this.store, []];
2698
- this.ensureOwned();
2699
- this.store = new StoreValue(scope);
2700
- this.owner.own(this.store);
2701
- return [
2702
- this.store,
2703
- [new InstructionBase("read", this.store, this.mem.cell, this.prop)],
2704
- ];
2705
- }
2706
2781
  "="(scope, value) {
2707
- const [data, dataInst] = value.consume(scope);
2782
+ const [data, dataInst] = value.eval(scope);
2708
2783
  return [
2709
2784
  data,
2710
2785
  [
@@ -2713,26 +2788,17 @@ class MemoryEntry extends BaseValue {
2713
2788
  ],
2714
2789
  ];
2715
2790
  }
2716
- ensureOwned() {
2717
- this.owner ??= new ValueOwner({ scope: this.scope, value: this });
2718
- }
2719
- }
2720
- for (const operator of operators) {
2721
- if (operator === "=")
2722
- continue;
2723
- MemoryEntry.prototype[operator] = function (...args) {
2724
- // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-unsafe-return
2725
- return BaseValue.prototype[operator].apply(this, args);
2726
- };
2727
2791
  }
2728
2792
  class MemoryMacro extends ObjectValue {
2729
2793
  constructor(cell, size) {
2730
2794
  super({
2731
- $get: new MacroFunction((scope, prop) => {
2732
- if (prop instanceof LiteralValue && typeof prop.data !== "number")
2795
+ $get: new MacroFunction((scope, out, prop) => {
2796
+ if (prop instanceof LiteralValue && !prop.isNumber())
2733
2797
  throw new CompilerError(`Invalid memory object property: "${prop.data}"`);
2734
- const obj = new MemoryEntry(scope, this, prop);
2735
- return [obj, []];
2798
+ const entry = new MemoryEntry(scope, this, prop);
2799
+ if (out)
2800
+ return entry.eval(scope, out);
2801
+ return [entry, []];
2736
2802
  }),
2737
2803
  length: size,
2738
2804
  size,
@@ -2746,10 +2812,10 @@ class MemoryMacro extends ObjectValue {
2746
2812
  class MemoryBuilder extends ObjectValue {
2747
2813
  constructor() {
2748
2814
  super({
2749
- $call: new MacroFunction((scope, cell, size = new LiteralValue(64)) => {
2815
+ $call: new MacroFunction((scope, out, cell, size = new LiteralValue(64)) => {
2750
2816
  if (!(cell instanceof SenseableValue))
2751
2817
  throw new CompilerError("Memory cell must be a senseable value.");
2752
- if (!(size instanceof LiteralValue && typeof size.data === "number"))
2818
+ if (!(size instanceof LiteralValue && size.isNumber()))
2753
2819
  throw new CompilerError("The memory size must be a number literal.");
2754
2820
  return [new MemoryMacro(cell, size), []];
2755
2821
  }),
@@ -2761,8 +2827,8 @@ const dynamicVars = ["time", "tick"];
2761
2827
  class NamespaceMacro extends ObjectValue {
2762
2828
  constructor({ changeCasing = false } = {}) {
2763
2829
  super({
2764
- $get: new MacroFunction((scope, prop) => {
2765
- if (!(prop instanceof LiteralValue) || typeof prop.data !== "string")
2830
+ $get: new MacroFunction((scope, out, prop) => {
2831
+ if (!(prop instanceof LiteralValue) || !prop.isString())
2766
2832
  throw new CompilerError("Cannot use dynamic properties on namespace macros");
2767
2833
  const symbolName = this.changeCasing
2768
2834
  ? camelToDashCase(prop.data)
@@ -2770,57 +2836,37 @@ class NamespaceMacro extends ObjectValue {
2770
2836
  const mutability = dynamicVars.includes(symbolName)
2771
2837
  ? EMutability.readonly
2772
2838
  : EMutability.constant;
2773
- const owner = new ValueOwner({
2774
- scope,
2775
- value: assign(new StoreValue(scope), {
2776
- mutability,
2777
- }),
2778
- name: `@${symbolName}`,
2779
- });
2780
- return [owner.value, []];
2839
+ const result = new StoreValue(`@${symbolName}`, mutability);
2840
+ return [result, []];
2781
2841
  }),
2782
2842
  });
2783
2843
  this.changeCasing = changeCasing;
2784
2844
  }
2785
2845
  }
2786
2846
  class VarsNamespace extends NamespaceMacro {
2787
- constructor(scope) {
2847
+ constructor() {
2788
2848
  super();
2789
2849
  Object.assign(this.data, {
2790
- unit: new ValueOwner({
2791
- scope,
2792
- name: "@unit",
2793
- value: new SenseableValue(scope),
2794
- }).value,
2795
- this: new ValueOwner({
2796
- scope,
2797
- name: "@this",
2798
- value: new SenseableValue(scope),
2799
- }).value,
2850
+ unit: new SenseableValue("@unit"),
2851
+ this: new SenseableValue("@this"),
2800
2852
  });
2801
2853
  }
2802
2854
  }
2803
2855
 
2804
2856
  class GetGlobal extends MacroFunction {
2805
2857
  constructor(mutability) {
2806
- super((scope, name) => {
2807
- if (!(name instanceof LiteralValue) || typeof name.data !== "string")
2858
+ super((scope, out, name) => {
2859
+ if (!(name instanceof LiteralValue) || !name.isString())
2808
2860
  throw new CompilerError("The name parameter must be a string literal.");
2809
- const owner = new ValueOwner({
2810
- scope,
2811
- name: name.data,
2812
- value: assign(new SenseableValue(scope), {
2813
- mutability,
2814
- }),
2815
- });
2816
- return [owner.value, []];
2861
+ const result = new SenseableValue(name.data, mutability);
2862
+ return [result, []];
2817
2863
  });
2818
2864
  }
2819
2865
  }
2820
2866
 
2821
2867
  class Asm extends MacroFunction {
2822
2868
  constructor() {
2823
- super((scope, stringsArray, ...values) => {
2869
+ super((scope, out, stringsArray, ...values) => {
2824
2870
  if (!isTemplateObjectArray(stringsArray))
2825
2871
  throw new CompilerError("Expected to receive a template strings array");
2826
2872
  const args = [];
@@ -2898,7 +2944,7 @@ function validateInstructionArgs(args) {
2898
2944
  function initScope(scope) {
2899
2945
  // namespaces
2900
2946
  scope.hardSet("ControlKind", new NamespaceMacro());
2901
- scope.hardSet("Vars", new VarsNamespace(scope));
2947
+ scope.hardSet("Vars", new VarsNamespace());
2902
2948
  scope.hardSet("Teams", new NamespaceMacro());
2903
2949
  scope.hardSet("Items", new NamespaceMacro({ changeCasing: true }));
2904
2950
  scope.hardSet("Liquids", new NamespaceMacro());
@@ -2910,7 +2956,7 @@ function initScope(scope) {
2910
2956
  scope.hardSet("getVar", new GetGlobal(EMutability.mutable));
2911
2957
  scope.hardSet("concat", new Concat());
2912
2958
  scope.hardSet("asm", new Asm());
2913
- scope.hardSet("Math", new MlogMath(scope));
2959
+ scope.hardSet("Math", new MlogMath());
2914
2960
  scope.hardSet("Memory", new MemoryBuilder());
2915
2961
  // commands
2916
2962
  scope.hardSet("draw", new Draw());
@@ -3002,11 +3048,11 @@ class Compiler {
3002
3048
  }
3003
3049
  return result;
3004
3050
  }
3005
- handle(scope, node, handler = this.handlers[node.type]) {
3051
+ handle(scope, node, handler = this.handlers[node.type], out) {
3006
3052
  try {
3007
3053
  if (!handler)
3008
3054
  throw new CompilerError("Missing handler for " + node.type);
3009
- const result = handler(this, scope, node, null);
3055
+ const result = handler(this, scope, node, out, null);
3010
3056
  if (this.sourcemap)
3011
3057
  return appendSourceLocations(result, node);
3012
3058
  return result;
@@ -3018,21 +3064,11 @@ class Compiler {
3018
3064
  throw error;
3019
3065
  }
3020
3066
  }
3021
- handleEval(scope, node) {
3022
- const [res, inst] = this.handle(scope, node);
3067
+ handleEval(scope, node, out) {
3068
+ const [res, inst] = this.handle(scope, node, undefined, out);
3023
3069
  if (!res)
3024
3070
  throw new CompilerError("Cannot eval null", node);
3025
- const [evaluated, evaluatedInst] = res.eval(scope);
3026
- const result = [evaluated, [...inst, ...evaluatedInst]];
3027
- if (this.sourcemap)
3028
- return appendSourceLocations(result, node);
3029
- return result;
3030
- }
3031
- handleConsume(scope, node) {
3032
- const [res, inst] = this.handle(scope, node);
3033
- if (!res)
3034
- throw new CompilerError("Cannot consume null", node);
3035
- const [evaluated, evaluatedInst] = res.consume(scope);
3071
+ const [evaluated, evaluatedInst] = res.eval(scope, out);
3036
3072
  const result = [evaluated, [...inst, ...evaluatedInst]];
3037
3073
  if (this.sourcemap)
3038
3074
  return appendSourceLocations(result, node);
@@ -3044,7 +3080,7 @@ class Compiler {
3044
3080
  * The usage of this method over a regular loop over an array of nodes
3045
3081
  * is only required if the code inside the loop generates
3046
3082
  * instructions that are not tracked by the compiler
3047
- * handler methods ({@link handle}, {@link handleEval}, {@link handleConsume}
3083
+ * handler methods ({@link handle}, {@link handleEval}
3048
3084
  * and {@link handleMany})
3049
3085
  *
3050
3086
  */
@@ -3057,30 +3093,6 @@ class Compiler {
3057
3093
  return [null, lines];
3058
3094
  }
3059
3095
  }
3060
- /** Removes jump instructions that point right to the next line */
3061
- function hideRedundantJumps(inst) {
3062
- for (let i = 0; i < inst.length; i++) {
3063
- const instruction = inst[i];
3064
- if (!(instruction instanceof JumpInstruction))
3065
- continue;
3066
- const { address } = instruction;
3067
- let searchIndex = i + 1;
3068
- let current = inst[searchIndex];
3069
- while (current.hidden) {
3070
- if (current instanceof AddressResolver &&
3071
- current.bonds.includes(address)) {
3072
- // only gets assigned if `current` is an adress resolver
3073
- // that resolves the jump adress
3074
- // and that is also right next to the jump,
3075
- // without any actual instructions in between
3076
- instruction.hidden = true;
3077
- break;
3078
- }
3079
- searchIndex++;
3080
- current = inst[searchIndex];
3081
- }
3082
- }
3083
- }
3084
3096
  function* hoistedFunctionNodes(nodes) {
3085
3097
  // sorting is O(n long n) while this is just O(n)
3086
3098
  // besides, it's better not to modify the node array