mlogjs 0.4.6 → 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,146 +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
1504
  // TODO: use static analysis to determine if
1384
1505
  // we can safely skip the temp value assignment
1385
- normalCall(scope, args) {
1506
+ normalCall(scope, args, out) {
1386
1507
  if (!this.bundled)
1387
1508
  this.childScope.inst.push(...this.inst);
1388
1509
  this.bundled = true;
1389
1510
  const callAddressLiteral = new LiteralValue(null);
1390
- const temp = assign(new SenseableValue(scope), {
1391
- mutability: EMutability.mutable,
1392
- });
1393
- const inst = this.paramOwners
1394
- .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])
1395
1514
  .reduce((s, c) => s.concat(c), [])
1396
- .concat(...this.ret.value["="](scope, callAddressLiteral)[1], new JumpInstruction(this.addr, EJumpKind.Always), new AddressResolver(callAddressLiteral),
1515
+ .concat(...this.ret["="](scope, callAddressLiteral)[1], new JumpInstruction(this.addr, EJumpKind.Always), new AddressResolver(callAddressLiteral),
1397
1516
  // ensures that functions can be called multiple times inside expressions
1398
- ...temp["="](scope, this.temp.value)[1]);
1517
+ ...temp["="](scope, this.temp)[1]);
1399
1518
  return [temp, inst];
1400
1519
  }
1401
1520
  inlineReturn(scope, arg) {
1402
- const argInst = arg ? this.inlineTemp.value["="](scope, arg)[1] : [];
1403
1521
  if (!this.inlineEnd)
1404
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
+ }
1405
1530
  const jump = assign(new JumpInstruction(this.inlineEnd, EJumpKind.Always), {
1406
1531
  intent: EInstIntent.return,
1532
+ intentSource: this,
1407
1533
  });
1408
- return [null, [...argInst, jump]];
1534
+ return [null, [...inst, jump]];
1409
1535
  }
1410
- inlineCall(scope, args) {
1536
+ inlineCall(scope, args, out) {
1411
1537
  // create return value
1412
- this.inlineTemp = new ValueOwner({
1413
- scope: this.childScope,
1414
- value: assign(new SenseableValue(scope), {
1415
- mutability: EMutability.mutable,
1416
- }),
1417
- });
1538
+ this.inlineTemp = SenseableValue.from(this.childScope, out, EMutability.mutable);
1418
1539
  // make a copy of the function scope
1419
1540
  const fnScope = this.childScope.copy();
1420
1541
  // hard set variables within the function scope
1421
- this.paramOwners.forEach((owner, i) => {
1422
- const value = args[i];
1423
- if (!value.owner?.persistent)
1424
- owner.own(value);
1425
- fnScope.hardSet(new ValueOwner({
1426
- scope: fnScope,
1427
- value,
1428
- identifier: owner.identifier,
1429
- name: owner.name,
1430
- }));
1542
+ this.paramNames.forEach((name, i) => {
1543
+ fnScope.hardSet(name, args[i]);
1431
1544
  });
1432
1545
  this.inlineEnd = new LiteralValue(null);
1433
1546
  this.tryingInline = true;
1434
1547
  let inst = this.c.handle(fnScope, this.body)[1];
1435
1548
  this.tryingInline = false;
1436
- 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);
1437
1552
  if (returnIndex !== -1)
1438
1553
  inst = inst.slice(0, returnIndex + 1);
1439
1554
  inst.push(new AddressResolver(this.inlineEnd));
1440
1555
  // return the function value
1441
- return [this.inlineTemp.value, inst];
1556
+ return [this.inlineTemp, inst];
1442
1557
  }
1443
- call(scope, args) {
1558
+ call(scope, args, out) {
1444
1559
  this.ensureInit();
1445
- if (args.length !== this.paramOwners.length)
1560
+ if (args.length !== this.paramNames.length)
1446
1561
  throw new CompilerError("Cannot call: argument count not matching.");
1447
- const inlineCall = this.inlineCall(scope, args);
1562
+ const inlineCall = this.inlineCall(scope, args, out);
1563
+ hideRedundantJumps(inlineCall[1]);
1448
1564
  const inlineSize = inlineCall[1].filter(i => !i.hidden).length;
1449
1565
  if (this.inline || inlineSize <= this.callSize)
1450
1566
  return inlineCall;
1451
- return this.normalCall(scope, args);
1567
+ return this.normalCall(scope, args, out);
1452
1568
  }
1453
1569
  return(scope, arg) {
1454
1570
  this.ensureInit();
@@ -1463,8 +1579,11 @@ class FunctionValue extends VoidValue {
1463
1579
  eval(_scope) {
1464
1580
  return [this, []];
1465
1581
  }
1466
- consume(_scope) {
1467
- return [this, []];
1582
+ paramOuts() {
1583
+ this.ensureInit();
1584
+ if (this.inline || this.tryingInline)
1585
+ return;
1586
+ return this.paramValues;
1468
1587
  }
1469
1588
  }
1470
1589
  function endsWithReturn(inst) {
@@ -1485,7 +1604,7 @@ function endsWithReturn(inst) {
1485
1604
  return false;
1486
1605
  }
1487
1606
 
1488
- function handleFunctionNode(c, scope, node) {
1607
+ function handleFunctionNode(c, scope, node, out) {
1489
1608
  let { params, body } = node;
1490
1609
  if (es.isExpression(body)) {
1491
1610
  const { loc } = body;
@@ -1500,46 +1619,44 @@ function handleFunctionNode(c, scope, node) {
1500
1619
  params: params,
1501
1620
  body,
1502
1621
  c,
1622
+ out,
1503
1623
  }),
1504
1624
  [],
1505
1625
  ];
1506
1626
  }
1507
- const ArrowFunctionExpression = (c, scope, node) => {
1508
- return handleFunctionNode(c, scope, node);
1627
+ const ArrowFunctionExpression = (c, scope, node, out) => {
1628
+ return handleFunctionNode(c, scope, node, out);
1509
1629
  };
1510
1630
  const FunctionDeclaration = (c, scope, node) => {
1511
1631
  const identifier = node.id.name;
1512
1632
  const name = nodeName(node, !c.compactNames && identifier);
1513
- const functionIns = handleFunctionNode(c, scope, node);
1514
- const owner = new ValueOwner({
1515
- scope,
1516
- value: functionIns[0],
1517
- identifier,
1518
- name,
1519
- });
1520
- return [scope.set(owner), []];
1633
+ const functionIns = handleFunctionNode(c, scope, node, name);
1634
+ scope.set(identifier, functionIns[0]);
1635
+ return functionIns;
1521
1636
  };
1522
1637
  const FunctionExpression = ArrowFunctionExpression;
1523
1638
  const ObjectMethod = ArrowFunctionExpression;
1524
1639
 
1525
- const CallExpression = (c, scope, node) => {
1640
+ const CallExpression = (c, scope, node, out) => {
1526
1641
  const inst = [];
1527
- 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) => {
1528
1646
  // TODO: this might be why t0 did not increment
1529
1647
  // TODO: figure out if the previous todo is still relevant
1530
- const [v, i] = c.handleConsume(scope, node);
1648
+ const out = paramOuts?.[index];
1649
+ const [v, i] = c.handleEval(scope, node, out);
1531
1650
  inst.push(...i);
1532
1651
  return v;
1533
1652
  });
1534
- const [callee, calleeInst] = c.handleConsume(scope, node.callee);
1535
- inst.push(...calleeInst);
1536
- const [callValue, callInst] = callee.call(scope, args);
1653
+ const [callValue, callInst] = callee.call(scope, args, out);
1537
1654
  inst.push(...callInst);
1538
1655
  return [callValue, inst];
1539
1656
  };
1540
1657
  const NewExpression = CallExpression;
1541
- const TaggedTemplateExpression = (c, scope, node) => {
1542
- const [tag, tagInst] = c.handleConsume(scope, node.tag);
1658
+ const TaggedTemplateExpression = (c, scope, node, out) => {
1659
+ const [tag, tagInst] = c.handleEval(scope, node.tag);
1543
1660
  const template = node.quasi;
1544
1661
  const stringsObject = ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.cooked ?? "")), {
1545
1662
  raw: ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.raw))),
@@ -1547,15 +1664,15 @@ const TaggedTemplateExpression = (c, scope, node) => {
1547
1664
  const expressions = [];
1548
1665
  const expressionInsts = [];
1549
1666
  template.expressions.forEach(expression => {
1550
- const [value, inst] = c.handleConsume(scope, expression);
1667
+ const [value, inst] = c.handleEval(scope, expression);
1551
1668
  expressions.push(value);
1552
1669
  expressionInsts.push(...inst);
1553
1670
  });
1554
- const [result, resultInst] = tag.call(scope, [stringsObject, ...expressions]);
1671
+ const [result, resultInst] = tag.call(scope, [stringsObject, ...expressions], out);
1555
1672
  return [result, [...expressionInsts, ...tagInst, ...resultInst]];
1556
1673
  };
1557
1674
 
1558
- const ObjectExpression = (c, scope, node) => {
1675
+ const ObjectExpression = (c, scope, node, out) => {
1559
1676
  const data = {};
1560
1677
  const inst = [];
1561
1678
  for (const prop of node.properties) {
@@ -1575,38 +1692,48 @@ const ObjectExpression = (c, scope, node) => {
1575
1692
  else {
1576
1693
  throw new CompilerError(`Unsupported object key type: ${key.type}`, key);
1577
1694
  }
1578
- const [member, memberInst] = c.handleEval(scope, value);
1579
- // TODO: use an approach that can be defined by each type
1580
- if (!member.owner && !(member instanceof LiteralValue)) {
1581
- new ValueOwner({
1582
- scope,
1583
- constant: true,
1584
- value: member,
1585
- });
1586
- }
1695
+ const [member, memberInst] = c.handleEval(scope, value, extractDestrucuringOut(out, index));
1587
1696
  data[index] = member;
1588
1697
  inst.push(...memberInst);
1589
1698
  }
1590
1699
  return [new ObjectValue(data), inst];
1591
1700
  };
1592
- const ArrayExpression = (c, scope, node) => {
1701
+ const ArrayExpression = (c, scope, node, out) => {
1593
1702
  const items = [];
1594
1703
  const inst = [];
1595
- node.elements.forEach(element => {
1704
+ node.elements.forEach((element, i) => {
1596
1705
  const [value, valueInst] = element
1597
- ? c.handleEval(scope, element)
1706
+ ? c.handleEval(scope, element, extractDestrucuringOut(out, i))
1598
1707
  : [undefined, []];
1599
1708
  items.push(value);
1600
1709
  inst.push(...valueInst);
1601
1710
  });
1602
1711
  return [ObjectValue.fromArray(items), inst];
1603
1712
  };
1604
- const MemberExpression = (c, scope, node) => {
1605
- const [obj, objInst] = c.handleConsume(scope, node.object);
1713
+ const MemberExpression = (c, scope, node, out) => {
1606
1714
  const [prop, propInst] = node.computed
1607
- ? c.handleConsume(scope, node.property)
1715
+ ? c.handleEval(scope, node.property)
1608
1716
  : [new LiteralValue(node.property.name), []];
1609
- 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);
1610
1737
  return [got, [...objInst, ...propInst, ...gotInst]];
1611
1738
  };
1612
1739
  const ArrayPattern = (c, scope, node) => {
@@ -1616,11 +1743,21 @@ const ArrayPattern = (c, scope, node) => {
1616
1743
  const element = node.elements[i];
1617
1744
  if (!element)
1618
1745
  continue;
1619
- const valueInst = c.handle(scope, element);
1620
- if (!valueInst[0])
1746
+ const [value, valueInst] = c.handle(scope, element);
1747
+ if (!value)
1621
1748
  throw new CompilerError("Destructuring element must resolve to a value", element);
1622
- inst.push(...valueInst[1]);
1623
- 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
+ });
1624
1761
  }
1625
1762
  return [new DestructuringValue(members), inst];
1626
1763
  };
@@ -1635,13 +1772,23 @@ const ObjectPattern = (c, scope, node) => {
1635
1772
  const { key } = prop;
1636
1773
  const keyInst = !prop.computed && key.type === "Identifier"
1637
1774
  ? [new LiteralValue(key.name), []]
1638
- : c.handleConsume(scope, prop.key);
1775
+ : c.handleEval(scope, prop.key);
1639
1776
  inst.push(...keyInst[1]);
1640
- const valueInst = c.handle(scope, prop.value);
1641
- if (!valueInst[0])
1777
+ const [value, valueInst] = c.handle(scope, prop.value);
1778
+ if (!value)
1642
1779
  throw new CompilerError("Destructuring member must resolve to a value", prop.value);
1643
- inst.push(...valueInst[1]);
1644
- 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
+ });
1645
1792
  }
1646
1793
  return [new DestructuringValue(members), inst];
1647
1794
  };
@@ -1686,7 +1833,7 @@ const TSEnumDeclaration = (c, scope, node) => {
1686
1833
  : [new LiteralValue(counter)];
1687
1834
  if (!(value instanceof LiteralValue))
1688
1835
  throw new CompilerError("Enum members must contain literal values", member);
1689
- if (typeof value.data === "number") {
1836
+ if (value.isNumber()) {
1690
1837
  lastType = "number";
1691
1838
  counter = value.data + 1;
1692
1839
  }
@@ -1696,13 +1843,9 @@ const TSEnumDeclaration = (c, scope, node) => {
1696
1843
  const name = member.id.type === "Identifier" ? member.id.name : member.id.value;
1697
1844
  data[name] = value;
1698
1845
  }
1699
- scope.set(new ValueOwner({
1700
- scope,
1701
- value: new ObjectValue(data),
1702
- constant: true,
1703
- identifier: node.id.name,
1704
- name: nodeName(node, !c.compactNames && node.id.name),
1705
- }));
1846
+ const value = new ObjectValue(data);
1847
+ value.name = nodeName(node, !c.compactNames && node.id.name);
1848
+ scope.set(node.id.name, value);
1706
1849
  return [null, []];
1707
1850
  };
1708
1851
  const TSNonNullExpression = (c, scope, node) => {
@@ -1766,15 +1909,12 @@ class MacroFunction extends VoidValue {
1766
1909
  this.mutability = EMutability.constant;
1767
1910
  this.fn = fn;
1768
1911
  }
1769
- call(scope, args) {
1770
- return this.fn.apply(this, [scope, ...args]);
1912
+ call(scope, args, out) {
1913
+ return this.fn.apply(this, [scope, out, ...args]);
1771
1914
  }
1772
1915
  eval(_scope) {
1773
1916
  return [this, []];
1774
1917
  }
1775
- consume(_scope) {
1776
- return [this, []];
1777
- }
1778
1918
  }
1779
1919
 
1780
1920
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
@@ -1798,49 +1938,39 @@ const mathOperations = {
1798
1938
  sqrt: a => Math.sqrt(a),
1799
1939
  rand: null,
1800
1940
  };
1801
- function mathConstant(scope, name) {
1802
- return new ValueOwner({
1803
- scope,
1804
- constant: true,
1805
- name,
1806
- value: assign(new StoreValue(scope), {
1807
- mutability: EMutability.constant,
1808
- }),
1809
- }).value;
1810
- }
1811
- function createMacroMathOperations(scope) {
1941
+ function createMacroMathOperations() {
1812
1942
  const macroMathOperations = {
1813
- PI: mathConstant(scope, "@pi"),
1814
- E: mathConstant(scope, "@e"),
1815
- degToRad: mathConstant(scope, "@degToRad"),
1816
- 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),
1817
1947
  };
1818
1948
  for (const key in mathOperations) {
1819
1949
  const fn = mathOperations[key];
1820
- macroMathOperations[key] = new MacroFunction((scope, a, b) => {
1950
+ macroMathOperations[key] = new MacroFunction((scope, out, a, b) => {
1821
1951
  if (b) {
1822
1952
  if (fn && a instanceof LiteralValue && b instanceof LiteralValue) {
1823
- if (typeof a.data !== "number" || typeof b.data !== "number")
1953
+ if (!a.isNumber() || !b.isNumber())
1824
1954
  throw new CompilerError("Cannot do math operation with non-numerical literals.");
1825
1955
  return [new LiteralValue(fn(a.num, b.num)), []];
1826
1956
  }
1827
- const temp = new StoreValue(scope);
1957
+ const temp = StoreValue.from(scope, out);
1828
1958
  return [temp, [new OperationInstruction(key, temp, a, b)]];
1829
1959
  }
1830
1960
  if (fn && a instanceof LiteralValue) {
1831
- if (typeof a.data !== "number")
1961
+ if (!a.isNumber())
1832
1962
  throw new CompilerError("Cannot do math operation with non-numerical literal.");
1833
1963
  return [new LiteralValue(fn(a.num)), []];
1834
1964
  }
1835
- const temp = new StoreValue(scope);
1965
+ const temp = StoreValue.from(scope, out);
1836
1966
  return [temp, [new OperationInstruction(key, temp, a, b)]];
1837
1967
  });
1838
1968
  }
1839
1969
  return macroMathOperations;
1840
1970
  }
1841
1971
  class MlogMath extends ObjectValue {
1842
- constructor(scope) {
1843
- super(createMacroMathOperations(scope));
1972
+ constructor() {
1973
+ super(createMacroMathOperations());
1844
1974
  }
1845
1975
  }
1846
1976
  // op angle result a b
@@ -1848,7 +1978,7 @@ class MlogMath extends ObjectValue {
1848
1978
 
1849
1979
  class Concat extends MacroFunction {
1850
1980
  constructor() {
1851
- super((scope, ...values) => {
1981
+ super((scope, out, ...values) => {
1852
1982
  for (const value of values)
1853
1983
  if (!(value instanceof LiteralValue))
1854
1984
  throw new CompilerError("Concat arguments must all be literal values.");
@@ -1860,66 +1990,19 @@ class Concat extends MacroFunction {
1860
1990
  }
1861
1991
  }
1862
1992
 
1863
- /** Asserts that `value` is a `LiteralValue` that contains a string */
1864
- function assertStringLiteral(value, name) {
1865
- if (!(value instanceof LiteralValue) || typeof value.data !== "string")
1866
- throw new CompilerError(`${name} must be a string literal`);
1867
- }
1868
- /** Asserts that `value` is a string literal that is part of a given set of `members`*/
1869
- function assertLiteralOneOf(value, members, name) {
1870
- assertStringLiteral(value, name);
1871
- if (!members.includes(value.data))
1872
- throw new CompilerError(`${name} must be one of: ${members
1873
- .slice(0, -1)
1874
- .map(member => `"${member}"`)
1875
- .join(", ")} or "${members[members.length - 1]}"`);
1876
- }
1877
- /** Asserts that `value` is either a store or a literal */
1878
- function assertIsRuntimeValue(value, name) {
1879
- if (!(value instanceof LiteralValue) && !(value instanceof StoreValue))
1880
- throw new CompilerError(`${name} must be a valid runtime value`);
1881
- }
1882
- /** Asserts that `value` is an `ObjectValue` */
1883
- function assertIsObjectMacro(value, name) {
1884
- if (!(value instanceof ObjectValue))
1885
- throw new CompilerError(`${name} must be an object macro`);
1886
- }
1887
- /** Asserts that `value` is an `ObjectValue` that has a length property */
1888
- function assertIsArrayMacro(value, name) {
1889
- if (!(value instanceof ObjectValue) || !value.data.length)
1890
- throw new CompilerError(`${name} must be an array macro`);
1891
- }
1892
- /** Asserts that all of the fields described are present on `value`*/
1893
- function assertObjectFields(value, fields) {
1894
- const result = [];
1895
- for (const field of fields) {
1896
- const param = typeof field === "object" ? field : { key: field };
1897
- const item = value.data[param.key];
1898
- if (item) {
1899
- param.validate?.(item);
1900
- result.push(item);
1901
- continue;
1902
- }
1903
- if (param.default == undefined)
1904
- throw new CompilerError(`The field "${param.name ?? param.key}" must be present`);
1905
- result.push(param.default);
1906
- }
1907
- return result;
1908
- }
1909
-
1910
1993
  /** Used to create namespaces that contain multiple methods that map into instructions */
1911
1994
  function createOverloadNamespace({ overloads, handler, }) {
1912
1995
  const result = {};
1913
1996
  for (const key in overloads) {
1914
1997
  const { named, args } = overloads[key];
1915
1998
  if (named) {
1916
- result[key] = new MacroFunction((scope, options) => {
1999
+ result[key] = new MacroFunction((scope, out, options) => {
1917
2000
  assertIsObjectMacro(options, named);
1918
- return handler(scope, key, ...assertObjectFields(options, args));
2001
+ return handler(scope, key, out, ...assertObjectFields(options, args));
1919
2002
  });
1920
2003
  continue;
1921
2004
  }
1922
- result[key] = new MacroFunction((scope, ...params) => {
2005
+ result[key] = new MacroFunction((scope, out, ...params) => {
1923
2006
  const handlerParams = [];
1924
2007
  // validate the paramters
1925
2008
  for (let i = 0; i < args.length; i++) {
@@ -1936,7 +2019,7 @@ function createOverloadNamespace({ overloads, handler, }) {
1936
2019
  arg.validate?.(param);
1937
2020
  handlerParams.push(param);
1938
2021
  }
1939
- return handler(scope, key, ...handlerParams);
2022
+ return handler(scope, key, out, ...handlerParams);
1940
2023
  });
1941
2024
  }
1942
2025
  return result;
@@ -1981,7 +2064,7 @@ class Draw extends ObjectValue {
1981
2064
  args: ["x", "y", "image", "size", "rotation"],
1982
2065
  },
1983
2066
  },
1984
- handler(scope, overload, ...args) {
2067
+ handler(scope, overload, out, ...args) {
1985
2068
  return [null, [new InstructionBase("draw", overload, ...args)]];
1986
2069
  },
1987
2070
  });
@@ -1991,7 +2074,7 @@ class Draw extends ObjectValue {
1991
2074
 
1992
2075
  class Print extends MacroFunction {
1993
2076
  constructor() {
1994
- super((scope, ...values) => {
2077
+ super((scope, out, ...values) => {
1995
2078
  const [first] = values;
1996
2079
  const inst = [];
1997
2080
  if (!isTemplateObjectArray(first)) {
@@ -2020,7 +2103,7 @@ class Print extends MacroFunction {
2020
2103
  const defaultTargetName$1 = "message1";
2021
2104
  class PrintFlush extends MacroFunction {
2022
2105
  constructor() {
2023
- super((scope, target) => {
2106
+ super((scope, out, target) => {
2024
2107
  if (!target)
2025
2108
  return [null, [new InstructionBase("printflush", defaultTargetName$1)]];
2026
2109
  if (!(target instanceof SenseableValue))
@@ -2033,7 +2116,7 @@ class PrintFlush extends MacroFunction {
2033
2116
  const defaultTargetName = "display1";
2034
2117
  class DrawFlush extends MacroFunction {
2035
2118
  constructor() {
2036
- super((scope, target) => {
2119
+ super((scope, out, target) => {
2037
2120
  if (!target)
2038
2121
  return [null, [new InstructionBase("drawflush", defaultTargetName)]];
2039
2122
  if (!(target instanceof SenseableValue))
@@ -2052,10 +2135,10 @@ class GetBlock extends ObjectValue {
2052
2135
  block: { args: ["x", "y"] },
2053
2136
  building: { args: ["x", "y"] },
2054
2137
  },
2055
- handler(scope, overload, x, y) {
2138
+ handler(scope, overload, out, x, y) {
2056
2139
  const output = overload === "building"
2057
- ? new SenseableValue(scope)
2058
- : new StoreValue(scope);
2140
+ ? SenseableValue.from(scope, out, EMutability.constant)
2141
+ : StoreValue.from(scope, out, EMutability.constant);
2059
2142
  return [
2060
2143
  output,
2061
2144
  [new InstructionBase("getblock", overload, output, x, y)],
@@ -2068,11 +2151,11 @@ class GetBlock extends ObjectValue {
2068
2151
 
2069
2152
  class GetLink extends MacroFunction {
2070
2153
  constructor() {
2071
- super((scope, index) => {
2154
+ super((scope, out, index) => {
2072
2155
  if (!(index instanceof StoreValue) &&
2073
- (!(index instanceof LiteralValue) || typeof index.data !== "number"))
2156
+ (!(index instanceof LiteralValue) || !index.isNumber()))
2074
2157
  throw new CompilerError("The getlink index must be a number literal or a store");
2075
- const outBuild = new SenseableValue(scope);
2158
+ const outBuild = SenseableValue.from(scope, out, EMutability.constant);
2076
2159
  return [outBuild, [new InstructionBase("getlink", outBuild, index)]];
2077
2160
  });
2078
2161
  }
@@ -2094,11 +2177,10 @@ class Control extends ObjectValue {
2094
2177
  config: { args: ["building", "value"] },
2095
2178
  color: { args: ["building", "r", "g", "b"] },
2096
2179
  },
2097
- handler(scope, overload, ...args) {
2180
+ handler(scope, overload, out, ...args) {
2098
2181
  if (overload !== "color")
2099
2182
  return [null, [new InstructionBase("control", overload, ...args)]];
2100
- const temp = new StoreValue(scope);
2101
- temp.ensureOwned();
2183
+ const temp = StoreValue.from(scope);
2102
2184
  // this "patch" is here for compatibility reasons
2103
2185
  // I don't want to add another breaking change
2104
2186
  // TODO: figure out if this patch should be removed later
@@ -2134,14 +2216,14 @@ const validRadarSorts = [
2134
2216
  ];
2135
2217
  class Radar extends MacroFunction {
2136
2218
  constructor() {
2137
- super((scope, options) => {
2219
+ super((scope, out, options) => {
2138
2220
  assertIsObjectMacro(options, "The radar options");
2139
2221
  const { building, filters, order, sort } = options.data;
2140
2222
  if (!(building instanceof SenseableValue))
2141
2223
  throw new CompilerError("The building must a senseable value");
2142
2224
  assertIsArrayMacro(filters, "filters");
2143
2225
  const { length } = filters.data;
2144
- if (!(length instanceof LiteralValue) || typeof length.data !== "number")
2226
+ if (!(length instanceof LiteralValue) || !length.isNumber())
2145
2227
  throw new CompilerError("The length of an array macro must be");
2146
2228
  if (length.data !== 3)
2147
2229
  throw new CompilerError("The filters array must have 3 items");
@@ -2153,7 +2235,7 @@ class Radar extends MacroFunction {
2153
2235
  if (!(order instanceof LiteralValue || order instanceof StoreValue))
2154
2236
  throw new CompilerError("The radar order must be a literal or a store");
2155
2237
  assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
2156
- const outUnit = new SenseableValue(scope);
2238
+ const outUnit = SenseableValue.from(scope, out, EMutability.constant);
2157
2239
  return [
2158
2240
  outUnit,
2159
2241
  [
@@ -2166,14 +2248,12 @@ class Radar extends MacroFunction {
2166
2248
 
2167
2249
  class Sensor extends MacroFunction {
2168
2250
  constructor() {
2169
- super((scope, property, target) => {
2251
+ super((scope, out, property, target) => {
2170
2252
  if (!(property instanceof StoreValue))
2171
2253
  throw new CompilerError("The sensor property must be a store");
2172
2254
  if (!(target instanceof SenseableValue))
2173
2255
  throw new CompilerError("The sensor target must be a senseable value");
2174
- const temp = assign(new SenseableValue(scope), {
2175
- mutability: EMutability.readonly,
2176
- });
2256
+ const temp = SenseableValue.from(scope, out, EMutability.readonly);
2177
2257
  return [temp, [new InstructionBase("sensor", temp, target, property)]];
2178
2258
  });
2179
2259
  }
@@ -2181,9 +2261,9 @@ class Sensor extends MacroFunction {
2181
2261
 
2182
2262
  class Wait extends MacroFunction {
2183
2263
  constructor() {
2184
- super((scope, seconds) => {
2264
+ super((scope, out, seconds) => {
2185
2265
  if (!(seconds instanceof StoreValue) &&
2186
- !(seconds instanceof LiteralValue && typeof seconds.data === "number"))
2266
+ !(seconds instanceof LiteralValue && seconds.isNumber()))
2187
2267
  throw new CompilerError("The wait seconds must be either a number literal or a store");
2188
2268
  return [null, [new InstructionBase("wait", seconds)]];
2189
2269
  });
@@ -2199,8 +2279,8 @@ class Lookup extends ObjectValue {
2199
2279
  item: { args: ["index"] },
2200
2280
  liquid: { args: ["index"] },
2201
2281
  },
2202
- handler(scope, overload, index) {
2203
- const output = new StoreValue(scope);
2282
+ handler(scope, overload, out, index) {
2283
+ const output = StoreValue.from(scope, out);
2204
2284
  return [
2205
2285
  output,
2206
2286
  [new InstructionBase("lookup", overload, output, index)],
@@ -2213,15 +2293,15 @@ class Lookup extends ObjectValue {
2213
2293
 
2214
2294
  class PackColor extends MacroFunction {
2215
2295
  constructor() {
2216
- super((scope, ...args) => {
2296
+ super((scope, out, ...args) => {
2217
2297
  if (args.length !== 4) {
2218
2298
  throw new CompilerError(`Expected 4 arguments, received ${args.length}`);
2219
2299
  }
2220
2300
  if (!args.every((value) => value instanceof StoreValue ||
2221
- (value instanceof LiteralValue && typeof value.data === "number"))) {
2301
+ (value instanceof LiteralValue && value.isNumber()))) {
2222
2302
  throw new CompilerError("packColor arguments must be either stores or number literals");
2223
2303
  }
2224
- const output = new StoreValue(scope);
2304
+ const output = StoreValue.from(scope, out);
2225
2305
  return [output, [new InstructionBase("packcolor", output, ...args)]];
2226
2306
  });
2227
2307
  }
@@ -2244,7 +2324,7 @@ class SetBlock extends ObjectValue {
2244
2324
  args: ["x", "y", "to", "team", { key: "rotation", default: "0" }],
2245
2325
  },
2246
2326
  },
2247
- handler(scope, overload, x, y, to, team, rotation) {
2327
+ handler(scope, overload, out, x, y, to, team, rotation) {
2248
2328
  return [
2249
2329
  null,
2250
2330
  [
@@ -2259,14 +2339,14 @@ class SetBlock extends ObjectValue {
2259
2339
 
2260
2340
  class SpawnUnit extends MacroFunction {
2261
2341
  constructor() {
2262
- super((scope, options) => {
2342
+ super((scope, out, options) => {
2263
2343
  assertIsObjectMacro(options, "The options");
2264
2344
  const { type, x, y, team, rotation } = options.data;
2265
2345
  assertIsRuntimeValue(type, "type");
2266
2346
  assertIsRuntimeValue(x, "x");
2267
2347
  assertIsRuntimeValue(y, "y");
2268
2348
  assertIsRuntimeValue(team, "team");
2269
- const output = new SenseableValue(scope);
2349
+ const output = SenseableValue.from(scope, out, EMutability.constant);
2270
2350
  return [
2271
2351
  output,
2272
2352
  [
@@ -2285,7 +2365,7 @@ class Stop extends MacroFunction {
2285
2365
 
2286
2366
  class UnitBind extends MacroFunction {
2287
2367
  constructor() {
2288
- super((scope, type) => {
2368
+ super((scope, out, type) => {
2289
2369
  if (!(type instanceof StoreValue))
2290
2370
  throw new CompilerError("The unitBind type must be a store");
2291
2371
  return [null, [new InstructionBase("ubind", type)]];
@@ -2324,21 +2404,20 @@ class UnitControl extends ObjectValue {
2324
2404
  args: ["x", "y", "radius"],
2325
2405
  },
2326
2406
  },
2327
- handler(scope, overload, ...args) {
2407
+ handler(scope, overload, out, ...args) {
2328
2408
  let result = null;
2329
2409
  let extraArgs = [];
2330
2410
  switch (overload) {
2331
2411
  case "getBlock": {
2332
- const outType = new StoreValue(scope);
2333
- const outBuilding = new SenseableValue(scope);
2412
+ const outType = StoreValue.from(scope, extractDestrucuringOut(out, 0));
2413
+ const outBuilding = SenseableValue.from(scope, extractDestrucuringOut(out, 1));
2334
2414
  result = ObjectValue.fromArray([outType, outBuilding]);
2335
2415
  extraArgs = [outType, outBuilding, new LiteralValue(0)];
2336
2416
  break;
2337
2417
  }
2338
2418
  case "within": {
2339
- const temp = new StoreValue(scope);
2340
- result = temp;
2341
- extraArgs = [temp, new LiteralValue(0)];
2419
+ result = StoreValue.from(scope, out);
2420
+ extraArgs = [result, new LiteralValue(0)];
2342
2421
  break;
2343
2422
  }
2344
2423
  }
@@ -2354,12 +2433,12 @@ class UnitControl extends ObjectValue {
2354
2433
 
2355
2434
  class UnitRadar extends MacroFunction {
2356
2435
  constructor() {
2357
- super((scope, options) => {
2436
+ super((scope, out, options) => {
2358
2437
  assertIsObjectMacro(options, "The radar options");
2359
2438
  const { filters, order, sort } = options.data;
2360
2439
  assertIsArrayMacro(filters, "filters");
2361
2440
  const { length } = filters.data;
2362
- if (!(length instanceof LiteralValue) || typeof length.data !== "number")
2441
+ if (!(length instanceof LiteralValue) || !length.isNumber())
2363
2442
  throw new CompilerError("The length of an array macro must be");
2364
2443
  if (length.data !== 3)
2365
2444
  throw new CompilerError("The filters array must have 3 items");
@@ -2371,7 +2450,7 @@ class UnitRadar extends MacroFunction {
2371
2450
  if (!(order instanceof LiteralValue || order instanceof StoreValue))
2372
2451
  throw new CompilerError("The radar order must be a literal or a store");
2373
2452
  assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
2374
- const outUnit = new SenseableValue(scope);
2453
+ const outUnit = SenseableValue.from(scope, out, EMutability.constant);
2375
2454
  return [
2376
2455
  outUnit,
2377
2456
  [
@@ -2409,11 +2488,11 @@ class UnitLocate extends ObjectValue {
2409
2488
  args: [],
2410
2489
  },
2411
2490
  },
2412
- handler(scope, overload, ...args) {
2413
- const outFound = new StoreValue(scope);
2414
- const outX = new StoreValue(scope);
2415
- const outY = new StoreValue(scope);
2416
- 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));
2417
2496
  const outArgs = [outX, outY, outFound, outBuilding];
2418
2497
  let inputArgs = [];
2419
2498
  switch (overload) {
@@ -2476,7 +2555,7 @@ class ApplyStatus extends ObjectValue {
2476
2555
  },
2477
2556
  clear: { args: ["status", "unit"] },
2478
2557
  },
2479
- handler(scope, overload, effect, unit, duration) {
2558
+ handler(scope, overload, out, effect, unit, duration) {
2480
2559
  if (!(effect instanceof LiteralValue))
2481
2560
  throw new CompilerError("The status effect must be a string literal");
2482
2561
  assertLiteralOneOf(effect, validEffects, "The status effect");
@@ -2494,7 +2573,7 @@ class ApplyStatus extends ObjectValue {
2494
2573
 
2495
2574
  class SpawnWave extends MacroFunction {
2496
2575
  constructor() {
2497
- super((scope, natural, x, y) => {
2576
+ super((scope, out, natural, x, y) => {
2498
2577
  if (!(natural instanceof LiteralValue) ||
2499
2578
  (natural.data !== 1 && natural.data !== 0))
2500
2579
  throw new CompilerError("The 'natural' argument must be a boolean literal");
@@ -2535,7 +2614,7 @@ class SetRule extends ObjectValue {
2535
2614
  rtsMinWeight: { args: ["team", "value"] },
2536
2615
  rtsMinSquad: { args: ["team", "value"] },
2537
2616
  },
2538
- handler(scope, overload, ...args) {
2617
+ handler(scope, overload, out, ...args) {
2539
2618
  const params = ["10", "0", "0", "100", "100"];
2540
2619
  switch (overload) {
2541
2620
  case "mapArea": {
@@ -2578,7 +2657,7 @@ class FlushMessage extends ObjectValue {
2578
2657
  announce: { args: ["duration"] },
2579
2658
  toast: { args: ["duration"] },
2580
2659
  },
2581
- handler(scope, overload, duration) {
2660
+ handler(scope, overload, out, duration) {
2582
2661
  return [null, [new InstructionBase("message", overload, duration)]];
2583
2662
  },
2584
2663
  });
@@ -2594,7 +2673,7 @@ class Cutscene extends ObjectValue {
2594
2673
  zoom: { args: ["level"] },
2595
2674
  stop: { args: [] },
2596
2675
  },
2597
- handler(scope, overload, ...args) {
2676
+ handler(scope, overload, out, ...args) {
2598
2677
  const params = Object.assign(["100", "100", "0.06", "0"], args);
2599
2678
  return [null, [new InstructionBase("cutscene", overload, ...params)]];
2600
2679
  },
@@ -2605,7 +2684,7 @@ class Cutscene extends ObjectValue {
2605
2684
 
2606
2685
  class Explosion extends MacroFunction {
2607
2686
  constructor() {
2608
- super((scope, options) => {
2687
+ super((scope, out, options) => {
2609
2688
  assertIsObjectMacro(options, "The options");
2610
2689
  const params = assertObjectFields(options, [
2611
2690
  "team",
@@ -2624,7 +2703,7 @@ class Explosion extends MacroFunction {
2624
2703
 
2625
2704
  class SetRate extends MacroFunction {
2626
2705
  constructor() {
2627
- super((scope, ipt) => [null, [new InstructionBase("setrate", ipt)]]);
2706
+ super((scope, out, ipt) => [null, [new InstructionBase("setrate", ipt)]]);
2628
2707
  }
2629
2708
  }
2630
2709
 
@@ -2641,8 +2720,8 @@ class Fetch extends ObjectValue {
2641
2720
  build: { args: ["team", "index", "block"] },
2642
2721
  buildCount: { args: ["team", "block"] },
2643
2722
  },
2644
- handler(scope, overload, team, ...rest) {
2645
- const output = new SenseableValue(scope);
2723
+ handler(scope, overload, out, team, ...rest) {
2724
+ const output = SenseableValue.from(scope, out, EMutability.constant);
2646
2725
  const params = ["0", "@conveyor"];
2647
2726
  if (overload === "buildCount") {
2648
2727
  params[1] = rest[0];
@@ -2662,8 +2741,8 @@ class Fetch extends ObjectValue {
2662
2741
 
2663
2742
  class GetFlag extends MacroFunction {
2664
2743
  constructor() {
2665
- super((scope, flag) => {
2666
- const result = new StoreValue(scope);
2744
+ super((scope, out, flag) => {
2745
+ const result = StoreValue.from(scope, out);
2667
2746
  return [result, [new InstructionBase("getflag", result, flag)]];
2668
2747
  });
2669
2748
  }
@@ -2671,7 +2750,7 @@ class GetFlag extends MacroFunction {
2671
2750
 
2672
2751
  class SetFlag extends MacroFunction {
2673
2752
  constructor() {
2674
- super((scope, flag, value) => {
2753
+ super((scope, out, flag, value) => {
2675
2754
  if (!flag)
2676
2755
  throw new CompilerError("Missing flag argument");
2677
2756
  if (!value)
@@ -2690,28 +2769,17 @@ class MemoryEntry extends BaseValue {
2690
2769
  this.macro = true;
2691
2770
  this.store = null;
2692
2771
  }
2693
- eval(scope) {
2772
+ eval(scope, out) {
2694
2773
  if (this.store)
2695
2774
  return [this.store, []];
2696
- const temp = new StoreValue(scope);
2775
+ const temp = (this.store = StoreValue.from(scope, out));
2697
2776
  return [
2698
2777
  temp,
2699
2778
  [new InstructionBase("read", temp, this.mem.cell, this.prop)],
2700
2779
  ];
2701
2780
  }
2702
- consume(scope) {
2703
- if (this.store)
2704
- return [this.store, []];
2705
- this.ensureOwned();
2706
- this.store = new StoreValue(scope);
2707
- this.owner.own(this.store);
2708
- return [
2709
- this.store,
2710
- [new InstructionBase("read", this.store, this.mem.cell, this.prop)],
2711
- ];
2712
- }
2713
2781
  "="(scope, value) {
2714
- const [data, dataInst] = value.consume(scope);
2782
+ const [data, dataInst] = value.eval(scope);
2715
2783
  return [
2716
2784
  data,
2717
2785
  [
@@ -2720,26 +2788,17 @@ class MemoryEntry extends BaseValue {
2720
2788
  ],
2721
2789
  ];
2722
2790
  }
2723
- ensureOwned() {
2724
- this.owner ??= new ValueOwner({ scope: this.scope, value: this });
2725
- }
2726
- }
2727
- for (const operator of operators) {
2728
- if (operator === "=")
2729
- continue;
2730
- MemoryEntry.prototype[operator] = function (...args) {
2731
- // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-unsafe-return
2732
- return BaseValue.prototype[operator].apply(this, args);
2733
- };
2734
2791
  }
2735
2792
  class MemoryMacro extends ObjectValue {
2736
2793
  constructor(cell, size) {
2737
2794
  super({
2738
- $get: new MacroFunction((scope, prop) => {
2739
- if (prop instanceof LiteralValue && typeof prop.data !== "number")
2795
+ $get: new MacroFunction((scope, out, prop) => {
2796
+ if (prop instanceof LiteralValue && !prop.isNumber())
2740
2797
  throw new CompilerError(`Invalid memory object property: "${prop.data}"`);
2741
- const obj = new MemoryEntry(scope, this, prop);
2742
- return [obj, []];
2798
+ const entry = new MemoryEntry(scope, this, prop);
2799
+ if (out)
2800
+ return entry.eval(scope, out);
2801
+ return [entry, []];
2743
2802
  }),
2744
2803
  length: size,
2745
2804
  size,
@@ -2753,10 +2812,10 @@ class MemoryMacro extends ObjectValue {
2753
2812
  class MemoryBuilder extends ObjectValue {
2754
2813
  constructor() {
2755
2814
  super({
2756
- $call: new MacroFunction((scope, cell, size = new LiteralValue(64)) => {
2815
+ $call: new MacroFunction((scope, out, cell, size = new LiteralValue(64)) => {
2757
2816
  if (!(cell instanceof SenseableValue))
2758
2817
  throw new CompilerError("Memory cell must be a senseable value.");
2759
- if (!(size instanceof LiteralValue && typeof size.data === "number"))
2818
+ if (!(size instanceof LiteralValue && size.isNumber()))
2760
2819
  throw new CompilerError("The memory size must be a number literal.");
2761
2820
  return [new MemoryMacro(cell, size), []];
2762
2821
  }),
@@ -2768,8 +2827,8 @@ const dynamicVars = ["time", "tick"];
2768
2827
  class NamespaceMacro extends ObjectValue {
2769
2828
  constructor({ changeCasing = false } = {}) {
2770
2829
  super({
2771
- $get: new MacroFunction((scope, prop) => {
2772
- if (!(prop instanceof LiteralValue) || typeof prop.data !== "string")
2830
+ $get: new MacroFunction((scope, out, prop) => {
2831
+ if (!(prop instanceof LiteralValue) || !prop.isString())
2773
2832
  throw new CompilerError("Cannot use dynamic properties on namespace macros");
2774
2833
  const symbolName = this.changeCasing
2775
2834
  ? camelToDashCase(prop.data)
@@ -2777,57 +2836,37 @@ class NamespaceMacro extends ObjectValue {
2777
2836
  const mutability = dynamicVars.includes(symbolName)
2778
2837
  ? EMutability.readonly
2779
2838
  : EMutability.constant;
2780
- const owner = new ValueOwner({
2781
- scope,
2782
- value: assign(new StoreValue(scope), {
2783
- mutability,
2784
- }),
2785
- name: `@${symbolName}`,
2786
- });
2787
- return [owner.value, []];
2839
+ const result = new StoreValue(`@${symbolName}`, mutability);
2840
+ return [result, []];
2788
2841
  }),
2789
2842
  });
2790
2843
  this.changeCasing = changeCasing;
2791
2844
  }
2792
2845
  }
2793
2846
  class VarsNamespace extends NamespaceMacro {
2794
- constructor(scope) {
2847
+ constructor() {
2795
2848
  super();
2796
2849
  Object.assign(this.data, {
2797
- unit: new ValueOwner({
2798
- scope,
2799
- name: "@unit",
2800
- value: new SenseableValue(scope),
2801
- }).value,
2802
- this: new ValueOwner({
2803
- scope,
2804
- name: "@this",
2805
- value: new SenseableValue(scope),
2806
- }).value,
2850
+ unit: new SenseableValue("@unit"),
2851
+ this: new SenseableValue("@this"),
2807
2852
  });
2808
2853
  }
2809
2854
  }
2810
2855
 
2811
2856
  class GetGlobal extends MacroFunction {
2812
2857
  constructor(mutability) {
2813
- super((scope, name) => {
2814
- if (!(name instanceof LiteralValue) || typeof name.data !== "string")
2858
+ super((scope, out, name) => {
2859
+ if (!(name instanceof LiteralValue) || !name.isString())
2815
2860
  throw new CompilerError("The name parameter must be a string literal.");
2816
- const owner = new ValueOwner({
2817
- scope,
2818
- name: name.data,
2819
- value: assign(new SenseableValue(scope), {
2820
- mutability,
2821
- }),
2822
- });
2823
- return [owner.value, []];
2861
+ const result = new SenseableValue(name.data, mutability);
2862
+ return [result, []];
2824
2863
  });
2825
2864
  }
2826
2865
  }
2827
2866
 
2828
2867
  class Asm extends MacroFunction {
2829
2868
  constructor() {
2830
- super((scope, stringsArray, ...values) => {
2869
+ super((scope, out, stringsArray, ...values) => {
2831
2870
  if (!isTemplateObjectArray(stringsArray))
2832
2871
  throw new CompilerError("Expected to receive a template strings array");
2833
2872
  const args = [];
@@ -2905,7 +2944,7 @@ function validateInstructionArgs(args) {
2905
2944
  function initScope(scope) {
2906
2945
  // namespaces
2907
2946
  scope.hardSet("ControlKind", new NamespaceMacro());
2908
- scope.hardSet("Vars", new VarsNamespace(scope));
2947
+ scope.hardSet("Vars", new VarsNamespace());
2909
2948
  scope.hardSet("Teams", new NamespaceMacro());
2910
2949
  scope.hardSet("Items", new NamespaceMacro({ changeCasing: true }));
2911
2950
  scope.hardSet("Liquids", new NamespaceMacro());
@@ -2917,7 +2956,7 @@ function initScope(scope) {
2917
2956
  scope.hardSet("getVar", new GetGlobal(EMutability.mutable));
2918
2957
  scope.hardSet("concat", new Concat());
2919
2958
  scope.hardSet("asm", new Asm());
2920
- scope.hardSet("Math", new MlogMath(scope));
2959
+ scope.hardSet("Math", new MlogMath());
2921
2960
  scope.hardSet("Memory", new MemoryBuilder());
2922
2961
  // commands
2923
2962
  scope.hardSet("draw", new Draw());
@@ -3009,11 +3048,11 @@ class Compiler {
3009
3048
  }
3010
3049
  return result;
3011
3050
  }
3012
- handle(scope, node, handler = this.handlers[node.type]) {
3051
+ handle(scope, node, handler = this.handlers[node.type], out) {
3013
3052
  try {
3014
3053
  if (!handler)
3015
3054
  throw new CompilerError("Missing handler for " + node.type);
3016
- const result = handler(this, scope, node, null);
3055
+ const result = handler(this, scope, node, out, null);
3017
3056
  if (this.sourcemap)
3018
3057
  return appendSourceLocations(result, node);
3019
3058
  return result;
@@ -3025,21 +3064,11 @@ class Compiler {
3025
3064
  throw error;
3026
3065
  }
3027
3066
  }
3028
- handleEval(scope, node) {
3029
- const [res, inst] = this.handle(scope, node);
3067
+ handleEval(scope, node, out) {
3068
+ const [res, inst] = this.handle(scope, node, undefined, out);
3030
3069
  if (!res)
3031
3070
  throw new CompilerError("Cannot eval null", node);
3032
- const [evaluated, evaluatedInst] = res.eval(scope);
3033
- const result = [evaluated, [...inst, ...evaluatedInst]];
3034
- if (this.sourcemap)
3035
- return appendSourceLocations(result, node);
3036
- return result;
3037
- }
3038
- handleConsume(scope, node) {
3039
- const [res, inst] = this.handle(scope, node);
3040
- if (!res)
3041
- throw new CompilerError("Cannot consume null", node);
3042
- const [evaluated, evaluatedInst] = res.consume(scope);
3071
+ const [evaluated, evaluatedInst] = res.eval(scope, out);
3043
3072
  const result = [evaluated, [...inst, ...evaluatedInst]];
3044
3073
  if (this.sourcemap)
3045
3074
  return appendSourceLocations(result, node);
@@ -3051,7 +3080,7 @@ class Compiler {
3051
3080
  * The usage of this method over a regular loop over an array of nodes
3052
3081
  * is only required if the code inside the loop generates
3053
3082
  * instructions that are not tracked by the compiler
3054
- * handler methods ({@link handle}, {@link handleEval}, {@link handleConsume}
3083
+ * handler methods ({@link handle}, {@link handleEval}
3055
3084
  * and {@link handleMany})
3056
3085
  *
3057
3086
  */
@@ -3064,30 +3093,6 @@ class Compiler {
3064
3093
  return [null, lines];
3065
3094
  }
3066
3095
  }
3067
- /** Removes jump instructions that point right to the next line */
3068
- function hideRedundantJumps(inst) {
3069
- for (let i = 0; i < inst.length; i++) {
3070
- const instruction = inst[i];
3071
- if (!(instruction instanceof JumpInstruction))
3072
- continue;
3073
- const { address } = instruction;
3074
- let searchIndex = i + 1;
3075
- let current = inst[searchIndex];
3076
- while (current.hidden) {
3077
- if (current instanceof AddressResolver &&
3078
- current.bonds.includes(address)) {
3079
- // only gets assigned if `current` is an adress resolver
3080
- // that resolves the jump adress
3081
- // and that is also right next to the jump,
3082
- // without any actual instructions in between
3083
- instruction.hidden = true;
3084
- break;
3085
- }
3086
- searchIndex++;
3087
- current = inst[searchIndex];
3088
- }
3089
- }
3090
- }
3091
3096
  function* hoistedFunctionNodes(nodes) {
3092
3097
  // sorting is O(n long n) while this is just O(n)
3093
3098
  // besides, it's better not to modify the node array