mlogjs 0.4.6 → 0.4.8

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.cjs CHANGED
@@ -90,6 +90,8 @@ var EMutability;
90
90
  EMutability[EMutability["readonly"] = 1] = "readonly";
91
91
  /** Constant values, won't change during execution */
92
92
  EMutability[EMutability["constant"] = 2] = "constant";
93
+ /** An immutable value that hasn't been initialized yet */
94
+ EMutability[EMutability["init"] = 3] = "init";
93
95
  })(EMutability || (EMutability = {}));
94
96
 
95
97
  const arithmeticBinaryOperators = [
@@ -169,24 +171,20 @@ const operators = [...leftRightOperators, ...singleOperators];
169
171
 
170
172
  class VoidValue {
171
173
  constructor() {
172
- this.owner = null;
173
174
  this.mutability = EMutability.mutable;
174
175
  this.macro = false;
175
176
  }
176
- eval(_scope) {
177
+ eval(_scope, _out) {
177
178
  throw new CompilerError(`${this} cannot eval.`);
178
179
  }
179
- consume(_scope) {
180
- throw new CompilerError(`${this} cannot be consumed.`);
181
- }
182
- call(_scope, _args) {
180
+ call(_scope, _args, _out) {
183
181
  throw new CompilerError(`${this} cannot call.`);
184
182
  }
185
183
  get(_scope, _name) {
186
184
  throw new CompilerError(`${this} cannot get.`);
187
185
  }
188
- ensureOwned() {
189
- throw new CompilerError(`${this} cannot be owned`);
186
+ paramOuts() {
187
+ return;
190
188
  }
191
189
  toString() {
192
190
  return "void";
@@ -224,6 +222,176 @@ class OperationInstruction extends InstructionBase {
224
222
  }
225
223
  }
226
224
 
225
+ /**
226
+ * The prefix for internal variables inside the compiler output
227
+ */
228
+ const internalPrefix = "&";
229
+ const discardedName = `${internalPrefix}_`;
230
+ const itemNames = [
231
+ "copper",
232
+ "lead",
233
+ "metaglass",
234
+ "graphite",
235
+ "sand",
236
+ "coal",
237
+ "titanium",
238
+ "thorium",
239
+ "scrap",
240
+ "silicon",
241
+ "plastanium",
242
+ "phaseFabric",
243
+ "surgeAlloy",
244
+ "sporePod",
245
+ "blastCompound",
246
+ "pyratite",
247
+ "beryllium",
248
+ "tungsten",
249
+ "oxide",
250
+ "carbide",
251
+ "fissileMatter",
252
+ "dormantCyst",
253
+ ];
254
+
255
+ /**
256
+ * Returns a string that has the format: [line]:[column].
257
+ *
258
+ * If `name` is provided, and it is a string, the resulting string will
259
+ * have the format: [name]:[line]:[column].
260
+ */
261
+ function nodeName(node, name) {
262
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
263
+ const { line, column } = node.loc.start;
264
+ if (typeof name === "string")
265
+ return `${name}:${line}:${column}`;
266
+ return `${line}:${column}`;
267
+ }
268
+ /**
269
+ * Converts a camel case string into a dash case one.
270
+ *
271
+ * As an example `camelCase` becomes `camel-case`
272
+ * @example
273
+ * ```
274
+ * camelToDashCase("camelCase") // returns "camel-case"
275
+ * ```
276
+ */
277
+ function camelToDashCase(name) {
278
+ return name.replace(/[A-Z]/g, str => `-${str.toLowerCase()}`);
279
+ }
280
+ /**
281
+ * A more type safe version of `Object.assign`
282
+ */
283
+ // eslint-disable-next-line @typescript-eslint/ban-types
284
+ function assign(obj, props) {
285
+ return Object.assign(obj, props);
286
+ }
287
+
288
+ function appendSourceLocations(valueInst, node) {
289
+ for (const inst of valueInst[1]) {
290
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
291
+ inst.source ??= node.loc;
292
+ }
293
+ return valueInst;
294
+ }
295
+ function withAlwaysRuns(valueInst, value) {
296
+ valueInst[1].forEach(inst => (inst.alwaysRuns = value));
297
+ return valueInst;
298
+ }
299
+ /** Removes jump instructions that point right to the next line */
300
+ function hideRedundantJumps(inst) {
301
+ for (let i = 0; i < inst.length; i++) {
302
+ const instruction = inst[i];
303
+ if (!(instruction instanceof JumpInstruction))
304
+ continue;
305
+ const { address } = instruction;
306
+ let searchIndex = i + 1;
307
+ let current = inst[searchIndex];
308
+ while (current?.hidden) {
309
+ if (current instanceof AddressResolver &&
310
+ current.bonds.includes(address)) {
311
+ // only gets assigned if `current` is an adress resolver
312
+ // that resolves the jump adress
313
+ // and that is also right next to the jump,
314
+ // without any actual instructions in between
315
+ instruction.hidden = true;
316
+ break;
317
+ }
318
+ searchIndex++;
319
+ current = inst[searchIndex];
320
+ }
321
+ }
322
+ }
323
+
324
+ function isTemplateObjectArray(value) {
325
+ return (value instanceof ObjectValue &&
326
+ value.data.length instanceof LiteralValue &&
327
+ value.data.length.isNumber() &&
328
+ value.data.raw instanceof ObjectValue &&
329
+ value.data.raw.data.length instanceof LiteralValue &&
330
+ value.data.raw.data.length.isNumber());
331
+ }
332
+ function extractOutName(out) {
333
+ if (!out || typeof out === "string")
334
+ return out;
335
+ return out.name;
336
+ }
337
+ function extractDestrucuringOut(out, field) {
338
+ if (out === discardedName)
339
+ return out;
340
+ if (!(out instanceof DestructuringValue))
341
+ return;
342
+ return out.fields[field] ?? discardedName;
343
+ }
344
+ /** Asserts that `value` is a `LiteralValue` that contains a string */
345
+ function assertStringLiteral(value, name) {
346
+ if (!(value instanceof LiteralValue) || !value.isString())
347
+ throw new CompilerError(`${name} must be a string literal`);
348
+ }
349
+ /** Asserts that `value` is a string literal that is part of a given set of `members`*/
350
+ function assertLiteralOneOf(value, members, name) {
351
+ assertStringLiteral(value, name);
352
+ if (!members.includes(value.data))
353
+ throw new CompilerError(`${name} must be one of: ${members
354
+ .slice(0, -1)
355
+ .map(member => `"${member}"`)
356
+ .join(", ")} or "${members[members.length - 1]}"`);
357
+ }
358
+ /** Asserts that `value` is either a store or a literal */
359
+ function assertIsRuntimeValue(value, name) {
360
+ if (!(value instanceof LiteralValue) && !(value instanceof StoreValue))
361
+ throw new CompilerError(`${name} must be a valid runtime value`);
362
+ }
363
+ /** Asserts that `value` is an `ObjectValue` */
364
+ function assertIsObjectMacro(value, name) {
365
+ if (!(value instanceof ObjectValue))
366
+ throw new CompilerError(`${name} must be an object macro`);
367
+ }
368
+ /** Asserts that `value` is an `ObjectValue` that has a length property */
369
+ function assertIsArrayMacro(value, name) {
370
+ if (value instanceof ObjectValue) {
371
+ const { length } = value.data;
372
+ if (length instanceof LiteralValue && length.isNumber())
373
+ return;
374
+ }
375
+ throw new CompilerError(`${name} must be an array macro`);
376
+ }
377
+ /** Asserts that all of the fields described are present on `value`*/
378
+ function assertObjectFields(value, fields) {
379
+ const result = [];
380
+ for (const field of fields) {
381
+ const param = typeof field === "object" ? field : { key: field };
382
+ const item = value.data[param.key];
383
+ if (item) {
384
+ param.validate?.(item);
385
+ result.push(item);
386
+ continue;
387
+ }
388
+ if (param.default == undefined)
389
+ throw new CompilerError(`The field "${param.name ?? param.key}" must be present`);
390
+ result.push(param.default);
391
+ }
392
+ return result;
393
+ }
394
+
227
395
  class SetInstruction extends InstructionBase {
228
396
  constructor(store, value) {
229
397
  super("set", store, value);
@@ -238,12 +406,12 @@ class SetInstruction extends InstructionBase {
238
406
  const { store, value } = this;
239
407
  return (this._hidden ||
240
408
  // assignment to discarded value
241
- !store.owner ||
409
+ store.name === discardedName ||
242
410
  // self assignment of a store
243
- (!!store.owner &&
411
+ (!!store.name &&
244
412
  store instanceof StoreValue &&
245
413
  value instanceof StoreValue &&
246
- store.owner === value.owner));
414
+ store.name === value.name));
247
415
  }
248
416
  }
249
417
 
@@ -323,9 +491,9 @@ class ContinueInstruction extends JumpInstruction {
323
491
  }
324
492
 
325
493
  class BaseValue extends VoidValue {
326
- "u-"(scope) {
327
- const [that, inst] = this.consume(scope);
328
- const temp = new StoreValue(scope);
494
+ "u-"(scope, out) {
495
+ const [that, inst] = this.eval(scope);
496
+ const temp = StoreValue.from(scope, out);
329
497
  return [
330
498
  temp,
331
499
  [
@@ -334,40 +502,37 @@ class BaseValue extends VoidValue {
334
502
  ],
335
503
  ];
336
504
  }
337
- "u+"(scope) {
505
+ "u+"(scope, out) {
338
506
  // TODO: should it return 0 + this ?
339
- return this.eval(scope);
507
+ return this.eval(scope, out);
340
508
  }
341
- "!"(scope) {
342
- const [that, inst] = this.consume(scope);
343
- const temp = new StoreValue(scope);
509
+ "!"(scope, out) {
510
+ const [that, inst] = this.eval(scope);
511
+ const temp = StoreValue.from(scope, out);
344
512
  const falseLiteral = new LiteralValue(0);
345
513
  return [
346
514
  temp,
347
515
  [...inst, new OperationInstruction("equal", temp, that, falseLiteral)],
348
516
  ];
349
517
  }
350
- "~"(scope) {
351
- const [that, inst] = this.consume(scope);
352
- const temp = new StoreValue(scope);
518
+ "~"(scope, out) {
519
+ const [that, inst] = this.eval(scope);
520
+ const temp = StoreValue.from(scope, out);
353
521
  return [temp, [...inst, new OperationInstruction("not", temp, that, null)]];
354
522
  }
355
523
  // requires special handling
356
524
  // the handler should give an object value to allow the lazy evaluation
357
- "??"(scope, other) {
358
- const [left, leftInst] = this.eval(scope);
525
+ "??"(scope, other, out) {
526
+ if (this instanceof LiteralValue) {
527
+ if (this.data === null)
528
+ return other.eval(scope, out);
529
+ return [this, []];
530
+ }
531
+ const result = SenseableValue.out(scope, out);
532
+ const [left, leftInst] = this.eval(scope, result);
533
+ const [right, rightInst] = other.eval(scope, result);
359
534
  const nullLiteral = new LiteralValue(null);
360
535
  const endAdress = new LiteralValue(null);
361
- const [nullTest] = left["=="](scope, nullLiteral);
362
- if (nullTest instanceof LiteralValue) {
363
- if (nullTest.data)
364
- return other.eval(scope);
365
- else
366
- return [left, leftInst];
367
- }
368
- const [right, rightInst] = other.eval(scope);
369
- const result = new StoreValue(scope);
370
- result.ensureOwned();
371
536
  return [
372
537
  result,
373
538
  [
@@ -407,10 +572,10 @@ const operatorMap$1 = {
407
572
  };
408
573
  for (const key in operatorMap$1) {
409
574
  const kind = operatorMap$1[key];
410
- BaseValue.prototype[key] = function (scope, value) {
411
- const [left, leftInst] = this.consume(scope);
412
- const [right, rightInst] = value.consume(scope);
413
- const temp = new StoreValue(scope);
575
+ BaseValue.prototype[key] = function (scope, value, out) {
576
+ const [left, leftInst] = this.eval(scope);
577
+ const [right, rightInst] = value.eval(scope);
578
+ const temp = StoreValue.from(scope, out);
414
579
  return [
415
580
  temp,
416
581
  [
@@ -438,19 +603,19 @@ const assignmentToBinary = {
438
603
  "||=": "||",
439
604
  "??=": "??",
440
605
  };
441
- for (const op in assignmentToBinary) {
606
+ for (const k in assignmentToBinary) {
607
+ const op = k;
442
608
  BaseValue.prototype[op] = function (scope, value) {
443
- this.ensureOwned();
444
- const [opValue, opInst] = this[assignmentToBinary[op]](scope, value);
609
+ const [opValue, opInst] = this[assignmentToBinary[op]](scope, value, this);
445
610
  const [retValue, retInst] = this["="](scope, opValue);
446
611
  return [retValue, [...opInst, ...retInst]];
447
612
  };
448
613
  }
449
614
  for (const key of updateOperators) {
450
- BaseValue.prototype[key] = function (scope, prefix) {
451
- let [ret, inst] = this.consume(scope);
615
+ BaseValue.prototype[key] = function (scope, prefix, out) {
616
+ let [ret, inst] = this.eval(scope);
452
617
  if (!prefix) {
453
- const temp = new StoreValue(scope);
618
+ const temp = StoreValue.out(scope, out);
454
619
  const [tempValue, tempInst] = temp["="](scope, ret);
455
620
  ret = tempValue;
456
621
  inst.push(...tempInst);
@@ -462,7 +627,7 @@ for (const key of updateOperators) {
462
627
 
463
628
  const literalMethods = {
464
629
  length: function () {
465
- if (typeof this.data !== "string")
630
+ if (!this.isString())
466
631
  throw new CompilerError("Length method only works on string literal values.");
467
632
  return new LiteralValue(this.data.length);
468
633
  },
@@ -476,14 +641,11 @@ class LiteralValue extends BaseValue {
476
641
  eval(_scope) {
477
642
  return [this, []];
478
643
  }
479
- consume(_scope) {
480
- return [this, []];
481
- }
482
644
  toString() {
483
645
  return JSON.stringify(this.data);
484
646
  }
485
647
  get(scope, name) {
486
- if (!(name instanceof LiteralValue && typeof name.data === "string"))
648
+ if (!(name instanceof LiteralValue && name.isString()))
487
649
  return super.get(scope, name);
488
650
  const method = literalMethods[name.data];
489
651
  if (!method)
@@ -500,7 +662,12 @@ class LiteralValue extends BaseValue {
500
662
  typeof() {
501
663
  return [new LiteralValue("literal"), []];
502
664
  }
503
- ensureOwned() { }
665
+ isString() {
666
+ return typeof this.data === "string";
667
+ }
668
+ isNumber() {
669
+ return typeof this.data === "number";
670
+ }
504
671
  }
505
672
  const operatorMap = {
506
673
  "==": (a, b) => +(a == b),
@@ -526,11 +693,22 @@ const operatorMap = {
526
693
  "&&": (a, b) => +(a && b),
527
694
  "||": (a, b) => +(a || b),
528
695
  };
529
- for (const key in operatorMap) {
696
+ for (const k in operatorMap) {
697
+ const key = k;
530
698
  const fn = operatorMap[key];
531
- LiteralValue.prototype[key] = function (scope, value) {
699
+ LiteralValue.prototype[key] = function (scope, value, out) {
700
+ if (key === "&&") {
701
+ if (this.data)
702
+ return [value, []];
703
+ return [new LiteralValue(0), []];
704
+ }
705
+ if (key === "||") {
706
+ if (!this.data)
707
+ return [value, []];
708
+ return [new LiteralValue(1), []];
709
+ }
532
710
  if (!(value instanceof LiteralValue)) {
533
- return BaseValue.prototype[key].apply(this, [scope, value]);
711
+ return BaseValue.prototype[key].apply(this, [scope, value, out]);
534
712
  }
535
713
  return [new LiteralValue(fn(this.data, value.data)), []];
536
714
  };
@@ -548,162 +726,45 @@ for (const key in unaryOperatorMap) {
548
726
  };
549
727
  }
550
728
 
551
- /**
552
- * The prefix for internal variables inside the compiler output
553
- */
554
- const internalPrefix = "&";
555
- const discardedName = `${internalPrefix}_`;
556
- /**
557
- * Returns a string that has the format: [line]:[column].
558
- *
559
- * If `name` is provided, and it is a string, the resulting string will
560
- * have the format: [name]:[line]:[column].
561
- */
562
- function nodeName(node, name) {
563
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
564
- const { line, column } = node.loc.start;
565
- if (typeof name === "string")
566
- return `${name}:${line}:${column}`;
567
- return `${line}:${column}`;
568
- }
569
- /**
570
- * Converts a camel case string into a dash case one.
571
- *
572
- * As an example `camelCase` becomes `camel-case`
573
- * @example
574
- * ```
575
- * camelToDashCase("camelCase") // returns "camel-case"
576
- * ```
577
- */
578
- function camelToDashCase(name) {
579
- return name.replace(/[A-Z]/g, str => `-${str.toLowerCase()}`);
580
- }
581
- const itemNames = [
582
- "copper",
583
- "lead",
584
- "metaglass",
585
- "graphite",
586
- "sand",
587
- "coal",
588
- "titanium",
589
- "thorium",
590
- "scrap",
591
- "silicon",
592
- "plastanium",
593
- "phaseFabric",
594
- "surgeAlloy",
595
- "sporePod",
596
- "blastCompound",
597
- "pyratite",
598
- "beryllium",
599
- "tungsten",
600
- "oxide",
601
- "carbide",
602
- "fissileMatter",
603
- "dormantCyst",
604
- ];
605
- /**
606
- * A more type safe version of `Object.assign`
607
- */
608
- // eslint-disable-next-line @typescript-eslint/ban-types
609
- function assign(obj, props) {
610
- return Object.assign(obj, props);
611
- }
612
- function appendSourceLocations(valueInst, node) {
613
- for (const inst of valueInst[1]) {
614
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
615
- inst.source ??= node.loc;
616
- }
617
- return valueInst;
618
- }
619
- function withAlwaysRuns(valueInst, value) {
620
- valueInst[1].forEach(inst => (inst.alwaysRuns = value));
621
- return valueInst;
622
- }
623
- function isTemplateObjectArray(value) {
624
- return (value instanceof ObjectValue &&
625
- value.data.length instanceof LiteralValue &&
626
- typeof value.data.length.data === "number" &&
627
- value.data.raw instanceof ObjectValue &&
628
- value.data.raw.data.length instanceof LiteralValue &&
629
- typeof value.data.raw.data.length.data === "number");
630
- }
631
-
632
- class ValueOwner {
633
- /**
634
- * Creates a new `ValueOwner`, if `value` is already owned,
635
- * `this` will be aliased to `value`, else `this` will own `value`
636
- */
637
- constructor({ scope, constant = false, value, identifier, name, }) {
638
- this.scope = scope;
639
- this.constant = constant;
640
- this.value = value;
641
- this.identifier = identifier;
642
- this.name = name ?? scope.makeTempName();
643
- this.persistent = !!name;
644
- this.owned = new Set();
645
- if (!value.owner?.persistent)
646
- this.own(value);
647
- }
648
- own(target) {
649
- target.owner = this;
650
- this.owned.add(target);
651
- }
652
- replace(target) {
653
- this.value.owner = null;
654
- this.owned.delete(this.value);
655
- this.value = target;
656
- this.own(target);
657
- }
658
- moveInto(owner) {
659
- this.owned.forEach(value => owner.own(value));
660
- this.owned.clear();
661
- }
662
- }
663
-
664
729
  /**
665
730
  * `StoreValue` represents values unknown at compile time,
666
731
  * mostly used with mutable variables and temporary values
667
732
  */
668
733
  class StoreValue extends BaseValue {
669
- constructor(scope) {
734
+ constructor(name, mutability = EMutability.mutable) {
670
735
  super();
671
- this.scope = scope;
672
- this.mutability = EMutability.mutable;
736
+ this.name = name;
737
+ this.mutability = mutability;
738
+ }
739
+ static from(scope, out, mutability = EMutability.mutable) {
740
+ if (out instanceof StoreValue)
741
+ return out;
742
+ const name = typeof out === "string" ? out : scope.makeTempName();
743
+ return new StoreValue(name, mutability);
744
+ }
745
+ static out(scope, out, mutability = EMutability.mutable) {
746
+ if (!out || typeof out === "string") {
747
+ return new StoreValue(out ?? scope.makeTempName(), mutability);
748
+ }
749
+ return out;
673
750
  }
674
751
  typeof() {
675
752
  return [new LiteralValue("store"), []];
676
753
  }
677
754
  "="(scope, value) {
678
- if (this.mutability !== EMutability.mutable)
755
+ if (this.mutability !== EMutability.mutable &&
756
+ this.mutability !== EMutability.init)
679
757
  throw new CompilerError(`Cannot assign to immutable store '${this}'.`);
680
- if (this.owner) {
681
- if (this.owner === value.owner && value instanceof StoreValue)
682
- return [this, []];
683
- if (!value.owner?.persistent) {
684
- if (!value.owner)
685
- this.owner.own(value);
686
- else
687
- value.owner.moveInto(this.owner);
688
- if (value instanceof StoreValue)
689
- return [this, []];
690
- }
691
- }
692
- const [evalValue, evalInst] = value.consume(scope);
758
+ if (this.name === value.name && value instanceof StoreValue)
759
+ return [this, []];
760
+ const [evalValue, evalInst] = value.eval(scope, this);
693
761
  return [this, [...evalInst, new SetInstruction(this, evalValue)]];
694
762
  }
695
763
  eval(_scope) {
696
764
  return [this, []];
697
765
  }
698
- consume(_scope) {
699
- this.ensureOwned();
700
- return [this, []];
701
- }
702
- ensureOwned() {
703
- this.owner ??= new ValueOwner({ scope: this.scope, value: this });
704
- }
705
766
  toString() {
706
- return this.owner?.name ?? discardedName;
767
+ return this.name;
707
768
  }
708
769
  }
709
770
 
@@ -725,10 +786,13 @@ class ObjectValue extends VoidValue {
725
786
  });
726
787
  return new ObjectValue(data);
727
788
  }
728
- typeof() {
789
+ typeof(scope, out) {
790
+ const { $typeof } = this.data;
791
+ if ($typeof)
792
+ return $typeof.call(scope, [], out);
729
793
  return [new LiteralValue("object"), []];
730
794
  }
731
- get(scope, key) {
795
+ get(scope, key, out) {
732
796
  // avoids naming collisions with keys like
733
797
  // constructor or toString
734
798
  if (Object.prototype.hasOwnProperty.call(this.data, key.data)) {
@@ -739,40 +803,45 @@ class ObjectValue extends VoidValue {
739
803
  const { $get } = this.data;
740
804
  if (!$get)
741
805
  throw new CompilerError("Cannot get undefined member.");
742
- return $get.call(scope, [key]);
806
+ return $get.call(scope, [key], out);
743
807
  }
744
- eval(scope) {
808
+ eval(scope, out) {
745
809
  const { $eval } = this.data;
746
810
  if (!$eval)
747
811
  return [this, []];
748
- return $eval.call(scope, []);
749
- }
750
- consume(scope) {
751
- const { $consume } = this.data;
752
- if ($consume)
753
- return $consume.call(scope, []);
754
- const result = this.eval(scope);
755
- // required by typescript
756
- const res = result[0];
757
- res.ensureOwned();
758
- return result;
812
+ return $eval.call(scope, [], out);
759
813
  }
760
- call(scope, args) {
814
+ call(scope, args, out) {
761
815
  const { $call } = this.data;
762
816
  if (!$call)
763
- return super.call(scope, args);
764
- return $call.call(scope, args);
817
+ return super.call(scope, args, out);
818
+ return $call.call(scope, args, out);
765
819
  }
766
- ensureOwned() { }
767
820
  }
768
- for (const op of operators) {
769
- ObjectValue.prototype[op] = function (...args) {
821
+ for (const op of leftRightOperators) {
822
+ ObjectValue.prototype[op] = function (scope, value, out) {
823
+ const $ = this.data[`$${op}`];
824
+ if (!$)
825
+ return VoidValue.prototype[op].apply(this, [scope, value, out]);
826
+ return $.call(scope, [value], out);
827
+ };
828
+ }
829
+ for (const op of unaryOperators) {
830
+ if (op === "typeof")
831
+ continue;
832
+ ObjectValue.prototype[op] = function (scope, out) {
833
+ const $ = this.data[`$${op}`];
834
+ if (!$)
835
+ return VoidValue.prototype[op].apply(this, [scope, out]);
836
+ return $.call(scope, [], out);
837
+ };
838
+ }
839
+ for (const op of updateOperators) {
840
+ ObjectValue.prototype[op] = function (scope, prefix, out) {
770
841
  const $ = this.data[`$${op}`];
771
- // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-unsafe-return
772
842
  if (!$)
773
- return VoidValue.prototype[op].apply(this, args);
774
- const [scope, ...fnArgs] = args;
775
- return $.call(scope, fnArgs);
843
+ return VoidValue.prototype[op].apply(this, [scope, prefix, out]);
844
+ return $.call(scope, [new LiteralValue(+prefix)], out);
776
845
  };
777
846
  }
778
847
 
@@ -797,27 +866,37 @@ const senseableProps = ["controller"];
797
866
  * ```
798
867
  */
799
868
  class SenseableValue extends StoreValue {
800
- constructor(scope) {
801
- super(scope);
802
- this.mutability = EMutability.constant;
869
+ constructor(name, mutability = EMutability.constant) {
870
+ super(name, mutability);
871
+ }
872
+ static from(scope, out, mutability = EMutability.constant) {
873
+ if (out instanceof SenseableValue)
874
+ return out;
875
+ const name = typeof out === "string" ? out : scope.makeTempName();
876
+ return new SenseableValue(name, mutability);
877
+ }
878
+ static out(scope, out, mutability = EMutability.mutable) {
879
+ if (!out || typeof out === "string") {
880
+ return new SenseableValue(out ?? scope.makeTempName(), mutability);
881
+ }
882
+ return out;
803
883
  }
804
- get(scope, prop) {
805
- if (prop instanceof LiteralValue && typeof prop.data === "string") {
884
+ get(scope, prop, out) {
885
+ const mutability = EMutability.readonly;
886
+ if (prop instanceof LiteralValue && prop.isString()) {
806
887
  const name = itemNames.includes(prop.data)
807
888
  ? camelToDashCase(prop.data)
808
889
  : prop.data;
809
890
  const result = senseableProps.includes(prop.data)
810
- ? new SenseableValue(scope)
811
- : new StoreValue(scope);
891
+ ? SenseableValue.from(scope, out, mutability)
892
+ : StoreValue.from(scope, out, mutability);
812
893
  return [
813
894
  result,
814
895
  [new InstructionBase("sensor", result, this, `@${name}`)],
815
896
  ];
816
897
  }
817
898
  if (prop instanceof StoreValue) {
818
- const temp = assign(new SenseableValue(scope), {
819
- mutability: EMutability.readonly,
820
- });
899
+ const temp = SenseableValue.from(scope, out, mutability);
821
900
  return [temp, [new InstructionBase("sensor", temp, this, prop)]];
822
901
  }
823
902
  throw new CompilerError(`Cannot sense "${prop}" property`);
@@ -836,21 +915,34 @@ class DestructuringValue extends VoidValue {
836
915
  super();
837
916
  this.members = members;
838
917
  this.macro = true;
918
+ this.fields = {};
919
+ for (const [key, { value }] of this.members) {
920
+ if (key instanceof LiteralValue && (key.isString() || key.isNumber())) {
921
+ this.fields[key.data] = value;
922
+ }
923
+ }
839
924
  }
840
925
  "="(scope, right) {
841
926
  const inst = [];
842
- for (const [key, value] of this.members) {
843
- const [item, itemInst] = right.get(scope, key);
844
- inst.push(...itemInst);
845
- inst.push(...value["="](scope, item)[1]);
927
+ for (const [key, { value, handler }] of this.members) {
928
+ const [, handlerInst] = handler(() => right.get(scope, key, value));
929
+ inst.push(...handlerInst);
846
930
  }
847
931
  return [right, inst];
848
932
  }
849
933
  eval(_scope) {
850
934
  return [this, []];
851
935
  }
852
- consume(_scope) {
853
- return [this, []];
936
+ }
937
+
938
+ /**
939
+ * This class is used to handle some cases where the value
940
+ * can't be directly evaluated by the node handler
941
+ */
942
+ class LazyValue extends VoidValue {
943
+ constructor(evalutate) {
944
+ super();
945
+ this.eval = evalutate;
854
946
  }
855
947
  }
856
948
 
@@ -887,46 +979,29 @@ class Scope {
887
979
  return false;
888
980
  }
889
981
  get(identifier) {
890
- const owner = this.data[identifier];
891
- if (owner)
892
- return owner.value;
982
+ const value = this.data[identifier];
983
+ if (value)
984
+ return value;
893
985
  if (this.parent)
894
986
  return this.parent.get(identifier);
895
987
  throw new CompilerError(`${identifier} is not declared.`);
896
988
  }
897
- set(...args) {
898
- const name = args.length === 1 ? args[0].identifier : args[0];
899
- if (!name)
900
- throw new CompilerError("Values in a scope must have an identifier");
989
+ set(name, value) {
901
990
  if (name in this.data)
902
991
  throw new CompilerError(`${name} is already declared.`);
903
- return this.hardSet(...args);
904
- }
905
- hardSet(...args) {
906
- const owner = args.length === 1
907
- ? args[0]
908
- : new ValueOwner({
909
- scope: this,
910
- identifier: args[0],
911
- name: args[0],
912
- value: args[1],
913
- });
914
- const { identifier } = owner;
915
- if (!identifier)
992
+ return this.hardSet(name, value);
993
+ }
994
+ hardSet(name, value) {
995
+ if (!name)
916
996
  throw new CompilerError("Values in a scope must have an identifier");
917
- this.data[identifier] = owner;
918
- return owner.value;
997
+ this.data[name] = value;
998
+ return value;
919
999
  }
920
1000
  make(identifier, name) {
921
- const owner = new ValueOwner({
922
- scope: this,
923
- value: assign(new SenseableValue(this), {
924
- mutability: EMutability.mutable,
925
- }),
926
- identifier,
927
- name,
1001
+ const value = assign(new SenseableValue(name), {
1002
+ mutability: EMutability.mutable,
928
1003
  });
929
- return this.set(owner);
1004
+ return this.set(identifier, value);
930
1005
  }
931
1006
  makeTempName() {
932
1007
  let result = `${internalPrefix}t${this.ntemp}`;
@@ -942,163 +1017,222 @@ const Program = (c, scope, node) => {
942
1017
  };
943
1018
 
944
1019
  const VariableDeclaration = (c, scope, node) => {
945
- return c.handleMany(scope, node.declarations, child => VariableDeclarator(c, scope, child, node.kind));
1020
+ return c.handleMany(scope, node.declarations, child => VariableDeclarator(c, scope, child, undefined, node.kind));
946
1021
  };
947
- const VariableDeclarator = (c, scope, node, kind = "let") => {
948
- const [init, inst] = node.init
949
- ? c.handleEval(scope, node.init)
950
- : [null, []];
951
- inst.push(...Declare(c, scope, node.id, kind, init)[1]);
1022
+ const VariableDeclarator = (c, scope, node, out, kind = "let") => {
1023
+ const { init } = node;
1024
+ const [value, inst] = Declare(c, scope, node.id, kind);
1025
+ if (init) {
1026
+ const [result, resultInst] = c.handleEval(scope, init, value.out);
1027
+ inst.push(...value.handler(result, resultInst)[1]);
1028
+ }
1029
+ else {
1030
+ inst.push(...value.handler(null, [])[1]);
1031
+ }
952
1032
  return [null, inst];
953
1033
  };
954
- const Declare = (c, scope, node, kind, init) => {
1034
+ const Declare = (c, scope, node, kind) => {
955
1035
  return c.handle(scope, node, () => {
956
1036
  switch (node.type) {
957
1037
  case "Identifier":
958
- return DeclareIdentifier(c, scope, node, kind, init);
1038
+ return DeclareIdentifier(c, scope, node, kind);
959
1039
  case "ArrayPattern":
960
- return DeclareArrayPattern(c, scope, node, kind, init);
1040
+ return DeclareArrayPattern(c, scope, node, kind);
961
1041
  case "ObjectPattern":
962
- return DeclareObjectPattern(c, scope, node, kind, init);
1042
+ return DeclareObjectPattern(c, scope, node, kind);
963
1043
  default:
964
1044
  throw new CompilerError(`Unsupported declaration type: ${node.type}`, node);
965
1045
  }
966
1046
  });
967
1047
  };
968
- const DeclareIdentifier = (c, scope, node, kind, init) => {
1048
+ const DeclareIdentifier = (c, scope, node, kind) => {
969
1049
  const { name: identifier } = node;
970
1050
  const name = nodeName(node, !c.compactNames && identifier);
971
- if (kind === "const" && !init)
972
- throw new CompilerError("Constants must be initialized.", node);
973
- if (kind === "const" && init?.mutability === EMutability.constant) {
974
- const owner = new ValueOwner({
975
- scope,
976
- identifier,
977
- name,
978
- value: init,
979
- constant: true,
980
- });
981
- scope.set(owner);
982
- return [null, []];
983
- }
984
- else {
985
- const value = scope.make(identifier, name);
986
- const inst = [];
987
- if (init) {
988
- if (init.macro)
989
- throw new CompilerError("Macro values must be held by constants", node);
990
- inst.push(...value["="](scope, init)[1]);
991
- }
992
- if (kind === "const")
993
- value.mutability = EMutability.constant;
994
- return [null, inst];
995
- }
1051
+ const out = SenseableValue.from(scope, name, kind === "const" ? EMutability.init : EMutability.mutable);
1052
+ const declarationValue = new DeclarationValue({
1053
+ c,
1054
+ scope,
1055
+ node,
1056
+ out,
1057
+ handler(init, inst) {
1058
+ if (kind === "const" && !init)
1059
+ throw new CompilerError("Constants must be initialized.", node);
1060
+ if (kind === "const" && init?.mutability === EMutability.constant) {
1061
+ scope.set(identifier, init);
1062
+ return [null, inst];
1063
+ }
1064
+ else {
1065
+ const value = scope.make(identifier, name);
1066
+ if (init) {
1067
+ if (init.macro)
1068
+ throw new CompilerError("Macro values must be held by constants", node);
1069
+ inst.push(...value["="](scope, init)[1]);
1070
+ }
1071
+ if (kind === "const")
1072
+ value.mutability = EMutability.constant;
1073
+ return [null, inst];
1074
+ }
1075
+ },
1076
+ });
1077
+ return [declarationValue, []];
996
1078
  };
997
- const DeclareArrayPattern = (c, scope, node, kind, init) => {
1079
+ const DeclareArrayPattern = (c, scope, node, kind) => {
1080
+ const members = new Map();
998
1081
  const { elements } = node;
999
- if (!(init instanceof ObjectValue))
1000
- throw new CompilerError("The value being destructured must be an object value");
1001
1082
  const inst = [];
1002
1083
  for (let i = 0; i < elements.length; i++) {
1003
1084
  const element = elements[i];
1004
1085
  if (!element)
1005
1086
  continue;
1006
- const val = init.data[i];
1007
- if (!val)
1008
- throw new CompilerError(`The target object does not have a value at index ${i}`, element);
1009
- inst.push(...Declare(c, scope, element, kind, val)[1]);
1087
+ const [value, valueInst] = Declare(c, scope, element, kind);
1088
+ inst.push(...valueInst);
1089
+ members.set(new LiteralValue(i), {
1090
+ value: value.out,
1091
+ handler(get) {
1092
+ return c.handle(scope, element, () => {
1093
+ let [init, initInst] = [null, []];
1094
+ try {
1095
+ [init, initInst] = get();
1096
+ }
1097
+ catch (e) {
1098
+ // catches the "cannot get undefined member" error
1099
+ // TODO: refactor and add error codes
1100
+ // so we don't have to do string checks
1101
+ if (!(e instanceof CompilerError) ||
1102
+ !e.message.includes("undefined member"))
1103
+ throw e;
1104
+ }
1105
+ if (!init)
1106
+ throw new CompilerError(`The target object does not have a value at index ${i}`, element);
1107
+ return value.handler(init, initInst);
1108
+ });
1109
+ },
1110
+ });
1010
1111
  }
1011
- return [null, inst];
1112
+ const out = new DestructuringValue(members);
1113
+ const value = new DeclarationValue({
1114
+ c,
1115
+ scope,
1116
+ node,
1117
+ out,
1118
+ handler(init, initInst) {
1119
+ if (!(init instanceof ObjectValue))
1120
+ throw new CompilerError("The value being destructured must be an object value");
1121
+ initInst.push(...out["="](scope, init)[1]);
1122
+ return [null, initInst];
1123
+ },
1124
+ });
1125
+ return [value, inst];
1012
1126
  };
1013
- const DeclareObjectPattern = (c, scope, node, kind, base) => {
1014
- if (!base)
1015
- throw new CompilerError("Cannot use object destructuring without an initializer", node);
1127
+ const DeclareObjectPattern = (c, scope, node, kind) => {
1128
+ const inst = [];
1129
+ const members = new Map();
1016
1130
  const { properties } = node;
1017
- const [init, inst] = base.consume(scope);
1018
1131
  const propertiesInst = c.handleMany(scope, properties, prop => {
1019
1132
  if (prop.type === "RestElement")
1020
1133
  throw new CompilerError("The rest operator is not supported", prop);
1021
- const { key, value } = prop;
1134
+ const { key, value: propValue } = prop;
1022
1135
  const keyInst = key.type === "Identifier" && !prop.computed
1023
1136
  ? [new LiteralValue(key.name), []]
1024
- : c.handleConsume(scope, prop.key);
1025
- const propInit = init.get(scope, keyInst[0]);
1026
- const declarationInst = Declare(c, scope, value, kind, propInit[0]);
1027
- return [null, [...keyInst[1], ...propInit[1], ...declarationInst[1]]];
1137
+ : c.handleEval(scope, prop.key);
1138
+ const [value, valueInst] = Declare(c, scope, propValue, kind);
1139
+ members.set(keyInst[0], {
1140
+ value: value.out,
1141
+ handler(get) {
1142
+ return c.handle(scope, propValue, () => {
1143
+ const [init, initInst] = get();
1144
+ return value.handler(init, initInst);
1145
+ });
1146
+ },
1147
+ });
1148
+ return [null, [...keyInst[1], ...valueInst]];
1028
1149
  });
1029
- return [null, [...inst, ...propertiesInst[1]]];
1030
- };
1150
+ const out = new DestructuringValue(members);
1151
+ const declarationValue = new DeclarationValue({
1152
+ c,
1153
+ scope,
1154
+ node,
1155
+ out,
1156
+ handler(init, initInst) {
1157
+ if (!init)
1158
+ throw new CompilerError("Cannot use object destructuring without an initializer", node);
1159
+ initInst.push(...out["="](scope, init)[1]);
1160
+ return [null, initInst];
1161
+ },
1162
+ });
1163
+ return [declarationValue, [...inst, ...propertiesInst[1]]];
1164
+ };
1165
+ class DeclarationValue extends VoidValue {
1166
+ constructor({ c, scope, node, out, handler, }) {
1167
+ super();
1168
+ this.out = out;
1169
+ this.c = c;
1170
+ this.scope = scope;
1171
+ this.node = node;
1172
+ this.handle = handler;
1173
+ }
1174
+ handler(init, inst) {
1175
+ return this.c.handle(this.scope, this.node, () => this.handle(init, inst));
1176
+ }
1177
+ }
1031
1178
 
1032
1179
  const Literal = (_c, scope, node) => [new LiteralValue(node.value), []];
1033
1180
  const NumericLiteral = Literal;
1034
1181
  const StringLiteral = Literal;
1035
- const NullLiteral = () => [
1036
- new LiteralValue(null),
1037
- [],
1038
- ];
1182
+ const NullLiteral = () => [new LiteralValue(null), []];
1039
1183
  const BooleanLiteral = (_c, scope, node) => [new LiteralValue(+node.value), []];
1040
1184
 
1041
- const LRExpression = (c, scope, node) => {
1042
- const [left, leftInst] = c.handleConsume(scope, node.left);
1043
- const [right, rightInst] = c.handleConsume(scope, node.right);
1044
- const [op, opInst] = left[node.operator](scope, right);
1185
+ const LRExpression = (c, scope, node, out) => {
1186
+ const [left, leftInst] = c.handleEval(scope, node.left);
1187
+ const [right, rightInst] = c.handleEval(scope, node.right);
1188
+ const [op, opInst] = left[node.operator](scope, right, out);
1045
1189
  return [op, [...leftInst, ...rightInst, ...opInst]];
1046
1190
  };
1047
1191
  const BinaryExpression = LRExpression;
1048
- const LogicalExpression = (c, scope, node) => {
1192
+ const LogicalExpression = (c, scope, node, out) => {
1049
1193
  if (node.operator !== "??")
1050
- return LRExpression(c, scope, node);
1051
- const other = lazyValue({
1052
- eval: scope => c.handleEval(scope, node.right),
1053
- consume: scope => c.handleConsume(scope, node.right),
1054
- });
1194
+ return LRExpression(c, scope, node, undefined);
1195
+ const other = new LazyValue((scope, out) => c.handleEval(scope, node.right, out));
1055
1196
  const [left, leftInst] = c.handleEval(scope, node.left);
1056
- const [result, resultInst] = left["??"](scope, other);
1197
+ const [result, resultInst] = left["??"](scope, other, out);
1057
1198
  return [result, [...leftInst, ...resultInst]];
1058
1199
  };
1059
1200
  const AssignmentExpression = (c, scope, node) => {
1060
1201
  const [left, leftInst] = c.handle(scope, node.left);
1061
- // doesn't need to consume because the operators already do that
1062
1202
  const [right, rightInst] = node.operator !== "??="
1063
- ? c.handleEval(scope, node.right)
1203
+ ? c.handleEval(scope, node.right, node.operator === "=" && left ? left : undefined)
1064
1204
  : [
1065
- lazyValue({
1066
- eval: scope => c.handleEval(scope, node.right),
1067
- consume: scope => c.handleConsume(scope, node.right),
1068
- }),
1205
+ new LazyValue((scope, out) => c.handleEval(scope, node.right, out)),
1069
1206
  [],
1070
1207
  ];
1071
1208
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1072
1209
  const [op, opInst] = left[node.operator](scope, right);
1073
1210
  return [op, [...leftInst, ...rightInst, ...opInst]];
1074
1211
  };
1075
- const UnaryExpression = (c, scope, { argument, operator }) => {
1076
- const [arg, argInst] = c.handleConsume(scope, argument);
1212
+ const UnaryExpression = (c, scope, { argument, operator }, out) => {
1213
+ const [arg, argInst] = c.handleEval(scope, argument);
1077
1214
  const operatorId = operator == "+" || operator == "-" ? `u${operator}` : operator;
1078
1215
  if (operatorId === "throw")
1079
1216
  throw new CompilerError("throw operator is not supported");
1080
- const [op, opInst] = arg[operatorId](scope);
1217
+ const [op, opInst] = arg[operatorId](scope, out);
1081
1218
  return [op, [...argInst, ...opInst]];
1082
1219
  };
1083
- const UpdateExpression = (c, scope, { argument, operator, prefix }) => {
1220
+ const UpdateExpression = (c, scope, { argument, operator, prefix }, out) => {
1084
1221
  const [arg, argInst] = c.handle(scope, argument);
1085
1222
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1086
- const [op, opInst] = arg[operator](scope, prefix);
1223
+ const [op, opInst] = arg[operator](scope, prefix, out);
1087
1224
  return [op, [...argInst, ...opInst]];
1088
1225
  };
1089
- const ConditionalExpression = (c, scope, node) => {
1090
- const [test, testInst] = c.handleConsume(scope, node.test);
1226
+ const ConditionalExpression = (c, scope, node, out) => {
1227
+ const [test, testInst] = c.handleEval(scope, node.test);
1091
1228
  if (test instanceof LiteralValue) {
1092
1229
  if (test.data)
1093
- return c.handleEval(scope, node.consequent);
1094
- return c.handleEval(scope, node.alternate);
1095
- }
1096
- // TODO: this creates those annoying jumps on the
1097
- // temp counter, specially if you nest ternary operators
1098
- const result = new StoreValue(scope);
1099
- result.ensureOwned();
1100
- const consequent = c.handleEval(scope, node.consequent);
1101
- const alternate = c.handleEval(scope, node.alternate);
1230
+ return c.handleEval(scope, node.consequent, out);
1231
+ return c.handleEval(scope, node.alternate, out);
1232
+ }
1233
+ const result = SenseableValue.out(scope, out);
1234
+ const consequent = c.handleEval(scope, node.consequent, result);
1235
+ const alternate = c.handleEval(scope, node.alternate, result);
1102
1236
  const alternateStartAdress = new LiteralValue(null);
1103
1237
  const endExpressionAdress = new LiteralValue(null);
1104
1238
  return [
@@ -1115,20 +1249,14 @@ const ConditionalExpression = (c, scope, node) => {
1115
1249
  new AddressResolver(endExpressionAdress),
1116
1250
  ],
1117
1251
  ];
1118
- };
1119
- function lazyValue(options) {
1120
- const value = new VoidValue();
1121
- value.eval = options.eval;
1122
- value.consume = options.consume;
1123
- return value;
1124
- }
1252
+ };
1125
1253
 
1126
1254
  const Identifier = (c, scope, node) => {
1127
1255
  return [scope.get(node.name), []];
1128
1256
  };
1129
1257
 
1130
1258
  const ExpressionStatement = (c, scope, node) => {
1131
- return c.handle(scope, node.expression);
1259
+ return c.handle(scope, node.expression, undefined, discardedName);
1132
1260
  };
1133
1261
  const BreakStatement = (_, scope, node) => {
1134
1262
  const label = node.label?.name;
@@ -1144,11 +1272,12 @@ const ContinueStatement = (_, scope, node) => {
1144
1272
  target.continue.bind(addr);
1145
1273
  return [null, [new ContinueInstruction(addr)]];
1146
1274
  };
1147
- const ReturnStatement = (c, scope, node) => {
1148
- const [arg, argInst] = node.argument
1149
- ? c.handle(scope, node.argument)
1275
+ const ReturnStatement = (c, scope, node, out) => {
1276
+ const { argument } = node;
1277
+ const [arg, argInst] = argument
1278
+ ? [new LazyValue((scope, out) => c.handleEval(scope, argument, out)), []]
1150
1279
  : [null, []];
1151
- const [ret, retInst] = scope.function.return(scope, arg);
1280
+ const [ret, retInst] = scope.function.return(scope, arg, out);
1152
1281
  return [ret, [...argInst, ...retInst]];
1153
1282
  };
1154
1283
  const LabeledStatement = (c, scope, node) => {
@@ -1171,7 +1300,7 @@ function findScopeLabel(scope, label) {
1171
1300
 
1172
1301
  const IfStatement = (c, scope, node) => {
1173
1302
  const inst = [];
1174
- const [test, testInst] = c.handleConsume(scope, node.test);
1303
+ const [test, testInst] = c.handleEval(scope, node.test);
1175
1304
  if (test instanceof LiteralValue) {
1176
1305
  if (test.data)
1177
1306
  inst.push(...c.handle(scope, node.consequent)[1]);
@@ -1190,44 +1319,73 @@ const IfStatement = (c, scope, node) => {
1190
1319
 
1191
1320
  const SwitchStatement = (c, scope, node) => {
1192
1321
  const innerScope = scope.createScope();
1193
- const [ref, refInst] = c.handleConsume(scope, node.discriminant);
1322
+ const [ref, refInst] = c.handleEval(scope, node.discriminant);
1194
1323
  const inst = [];
1195
1324
  const endAdress = new LiteralValue(null);
1196
1325
  const endLine = new AddressResolver(endAdress).bindBreak(innerScope);
1197
1326
  const caseJumps = [];
1198
1327
  let defaultJump;
1328
+ // if there is a case that is guaranteed to run
1329
+ let constantCase = false;
1330
+ let onlyConstantTests = true;
1199
1331
  for (const scase of node.cases) {
1200
1332
  const [, bodyInst] = c.handleMany(innerScope, scase.consequent);
1201
1333
  const bodyAdress = new LiteralValue(null);
1202
1334
  const bodyLine = new AddressResolver(bodyAdress);
1335
+ const canFallInto = !endsWithoutFalltrough(inst, endLine);
1336
+ let includeJump = !constantCase;
1337
+ const includeBody = !constantCase || canFallInto;
1203
1338
  if (!scase.test) {
1204
- [defaultJump] = c.handle(innerScope, scase, () => [
1205
- null,
1206
- [new JumpInstruction(bodyAdress, EJumpKind.Always)],
1207
- ])[1];
1208
- inst.push(bodyLine, ...bodyInst);
1339
+ if (includeJump) {
1340
+ [defaultJump] = c.handle(innerScope, scase, () => [
1341
+ null,
1342
+ [new JumpInstruction(bodyAdress, EJumpKind.Always)],
1343
+ ])[1];
1344
+ }
1345
+ if (includeBody)
1346
+ inst.push(bodyLine, ...bodyInst);
1209
1347
  continue;
1210
1348
  }
1211
- const [test, testInst] = c.handleConsume(innerScope, scase.test);
1349
+ const [test, testInst] = c.handleEval(innerScope, scase.test);
1350
+ if (onlyConstantTests)
1351
+ onlyConstantTests = test.mutability === EMutability.constant;
1352
+ let jump;
1212
1353
  // check if it can be evaluated at compile time
1213
- const [comp] = ref["=="](innerScope, test);
1214
- // if the constant expression resolves to false
1215
- // the whole case gets omitted
1216
- // otherwise it returns the instructions for
1217
- // the body of this case
1354
+ // the scope copy prevents unecessary increases in the temp counter
1355
+ const [comp] = ref["=="](innerScope.copy(), test);
1218
1356
  if (comp instanceof LiteralValue) {
1219
- if (comp.data)
1220
- return [null, bodyInst];
1221
- else
1222
- continue;
1357
+ // whether other cases can falltrough and reach this one
1358
+ const noFallInto = !canFallInto;
1359
+ if (!comp.data) {
1360
+ // this case is not accessible in any way
1361
+ if (noFallInto)
1362
+ continue;
1363
+ includeJump = false;
1364
+ }
1365
+ else {
1366
+ // this case is only accessible via the comparison jump
1367
+ // and all the other cases will never run
1368
+ if (noFallInto &&
1369
+ onlyConstantTests &&
1370
+ endsWithoutFalltrough(bodyInst, endLine)) {
1371
+ return [null, [...bodyInst, endLine]];
1372
+ }
1373
+ jump = c.handle(innerScope, scase, () => [
1374
+ null,
1375
+ [new JumpInstruction(bodyAdress, EJumpKind.Always)],
1376
+ ])[1];
1377
+ constantCase = true;
1378
+ }
1223
1379
  }
1224
1380
  // makes sourcemapping for the jump more specific
1225
- const [, jump] = c.handle(innerScope, scase, () => [
1381
+ jump ??= c.handle(innerScope, scase, () => [
1226
1382
  null,
1227
1383
  [new JumpInstruction(bodyAdress, EJumpKind.StrictEqual, ref, test)],
1228
- ]);
1229
- caseJumps.push(...testInst, ...jump);
1230
- inst.push(bodyLine, ...bodyInst);
1384
+ ])[1];
1385
+ if (includeJump)
1386
+ caseJumps.push(...testInst, ...jump);
1387
+ if (includeBody)
1388
+ inst.push(bodyLine, ...bodyInst);
1231
1389
  }
1232
1390
  // ensures that the processor exits
1233
1391
  // the switch if no cases match
@@ -1242,16 +1400,38 @@ const SwitchStatement = (c, scope, node) => {
1242
1400
  [
1243
1401
  ...refInst,
1244
1402
  ...caseJumps,
1245
- ...(caseJumps.length > 0 ? [defaultJump] : []),
1403
+ ...(caseJumps.length > 0 && !constantCase ? [defaultJump] : []),
1246
1404
  ...inst,
1247
1405
  endLine,
1248
1406
  ],
1249
1407
  ];
1250
- };
1408
+ };
1409
+ function endsWithoutFalltrough(inst, endLine) {
1410
+ if (inst.length === 0)
1411
+ return true;
1412
+ for (let i = inst.length - 1; i >= 0; i--) {
1413
+ const instruction = inst[i];
1414
+ // this means that there is an instruction trying to reference
1415
+ // the final function `set @counter` instruction
1416
+ // of course this generates an unecessary
1417
+ // `set @counter` in cases where a control flow
1418
+ // structure contains a fallback return statement
1419
+ // TODO: fix this with static analysis
1420
+ if (instruction instanceof AddressResolver)
1421
+ return false;
1422
+ if (instruction.hidden)
1423
+ continue;
1424
+ return (instruction instanceof SetCounterInstruction ||
1425
+ instruction.intent === EInstIntent.return ||
1426
+ (instruction instanceof BreakInstruction &&
1427
+ endLine.bonds.includes(instruction.address)));
1428
+ }
1429
+ return false;
1430
+ }
1251
1431
 
1252
1432
  const WhileStatement = (c, scope, node) => {
1253
1433
  const lines = [];
1254
- const [test, testLines] = c.handleConsume(scope, node.test);
1434
+ const [test, testLines] = c.handleEval(scope, node.test);
1255
1435
  const childScope = scope.createScope();
1256
1436
  const startLoopAddr = new LiteralValue(null);
1257
1437
  const endLoopAddr = new LiteralValue(null);
@@ -1271,7 +1451,7 @@ const WhileStatement = (c, scope, node) => {
1271
1451
  };
1272
1452
  const DoWhileStatement = (c, scope, node) => {
1273
1453
  const lines = [];
1274
- const [test, testLines] = c.handleConsume(scope, node.test);
1454
+ const [test, testLines] = c.handleEval(scope, node.test);
1275
1455
  const childScope = scope.createScope();
1276
1456
  const startLoopAddr = new LiteralValue(null);
1277
1457
  const startLoopLine = new AddressResolver(startLoopAddr).bindContinue(childScope);
@@ -1300,9 +1480,11 @@ const ForStatement = (c, parentScope, node) => {
1300
1480
  const scope = parentScope.createScope();
1301
1481
  const initInst = node.init ? c.handle(scope, node.init)[1] : [];
1302
1482
  const [test, testLines] = node.test
1303
- ? c.handleConsume(scope, node.test)
1483
+ ? c.handleEval(scope, node.test)
1304
1484
  : [new LiteralValue(1), []];
1305
- const updateLines = node.update ? c.handle(scope, node.update)[1] : [];
1485
+ const updateLines = node.update
1486
+ ? c.handle(scope, node.update, undefined, discardedName)[1]
1487
+ : [];
1306
1488
  const startLoopAddr = new LiteralValue(null);
1307
1489
  // continue statements jump here to run the loop update lines
1308
1490
  const beforeEndAddr = new LiteralValue(null);
@@ -1330,146 +1512,131 @@ const ForStatement = (c, parentScope, node) => {
1330
1512
  };
1331
1513
 
1332
1514
  class FunctionValue extends VoidValue {
1333
- constructor({ scope, params, body, c, }) {
1515
+ constructor({ scope, params, body, c, out, }) {
1334
1516
  super();
1335
1517
  this.mutability = EMutability.constant;
1336
1518
  this.macro = true;
1337
- this.paramOwners = [];
1519
+ this.paramValues = [];
1520
+ this.paramNames = [];
1338
1521
  this.bundled = false;
1339
1522
  this.initialized = false;
1340
1523
  this.scope = scope;
1341
1524
  this.body = body;
1342
1525
  this.c = c;
1343
1526
  this.params = params;
1527
+ this.out = out;
1528
+ this.name = extractOutName(out) ?? scope.makeTempName();
1344
1529
  }
1345
1530
  typeof() {
1346
1531
  return [new LiteralValue("function"), []];
1347
1532
  }
1348
- initScope(name) {
1349
- this.childScope = this.scope.createFunction(name);
1533
+ initScope() {
1534
+ this.childScope = this.scope.createFunction(this.name);
1350
1535
  this.childScope.function = this;
1351
1536
  for (const id of this.params) {
1352
1537
  const name = nodeName(id, !this.c.compactNames && id.name);
1353
- const owner = new ValueOwner({
1354
- scope: this.childScope,
1355
- value: assign(new SenseableValue(this.childScope), {
1356
- mutability: EMutability.mutable,
1357
- }),
1358
- identifier: id.name,
1359
- name,
1360
- });
1361
- this.childScope.set(owner);
1362
- this.paramOwners.push(owner);
1538
+ const value = new SenseableValue(name, EMutability.mutable);
1539
+ this.childScope.set(id.name, value);
1540
+ this.paramValues.push(value);
1541
+ this.paramNames.push(id.name);
1363
1542
  }
1364
- this.callSize = this.paramOwners.length + 2;
1543
+ this.callSize = this.paramValues.length + 2;
1365
1544
  }
1366
1545
  ensureInit() {
1367
- if (!this.owner)
1368
- throw new CompilerError(`Functions must be owned by a variable`);
1369
1546
  if (this.initialized)
1370
1547
  return;
1371
1548
  this.initialized = true;
1372
- const { name } = this.owner;
1373
- this.initScope(name);
1549
+ this.initScope();
1374
1550
  this.addr = new LiteralValue(null);
1375
- this.temp = new ValueOwner({
1376
- scope: this.childScope,
1377
- name: `${internalPrefix}f${name}`,
1378
- value: assign(new SenseableValue(this.childScope), {
1379
- mutability: EMutability.mutable,
1380
- }),
1381
- });
1382
- this.ret = new ValueOwner({
1383
- scope: this.childScope,
1384
- name: `${internalPrefix}r${name}`,
1385
- value: new StoreValue(this.childScope),
1386
- });
1551
+ this.temp = new SenseableValue(`${internalPrefix}f${this.name}`, EMutability.mutable);
1552
+ this.ret = new StoreValue(`${internalPrefix}r${this.name}`);
1387
1553
  this.inst = [
1388
1554
  new AddressResolver(this.addr),
1389
1555
  ...this.c.handle(this.childScope, this.body)[1],
1390
1556
  ];
1391
1557
  if (!endsWithReturn(this.inst)) {
1392
- this.inst.push(assign(new SetCounterInstruction(this.ret.value), {
1558
+ this.inst.push(assign(new SetCounterInstruction(this.ret), {
1393
1559
  intent: EInstIntent.return,
1394
1560
  }));
1395
1561
  }
1396
1562
  }
1397
1563
  normalReturn(scope, arg) {
1398
- const argInst = arg ? this.temp.value["="](scope, arg)[1] : [];
1399
- const returnInst = assign(new SetCounterInstruction(this.ret.value), {
1564
+ const inst = [];
1565
+ if (arg) {
1566
+ const [value, valueInst] = arg.eval(scope, this.temp);
1567
+ const argInst = this.temp["="](scope, value)[1];
1568
+ inst.push(...valueInst);
1569
+ inst.push(...argInst);
1570
+ }
1571
+ const returnInst = assign(new SetCounterInstruction(this.ret), {
1400
1572
  intent: EInstIntent.return,
1401
1573
  });
1402
- return [null, [...argInst, returnInst]];
1574
+ return [null, [...inst, returnInst]];
1403
1575
  }
1404
1576
  // TODO: use static analysis to determine if
1405
1577
  // we can safely skip the temp value assignment
1406
- normalCall(scope, args) {
1578
+ normalCall(scope, args, out) {
1407
1579
  if (!this.bundled)
1408
1580
  this.childScope.inst.push(...this.inst);
1409
1581
  this.bundled = true;
1410
1582
  const callAddressLiteral = new LiteralValue(null);
1411
- const temp = assign(new SenseableValue(scope), {
1412
- mutability: EMutability.mutable,
1413
- });
1414
- const inst = this.paramOwners
1415
- .map(({ value: param }, i) => param["="](scope, args[i])[1])
1583
+ const temp = SenseableValue.from(scope, out, EMutability.mutable);
1584
+ const inst = this.paramValues
1585
+ .map((param, i) => param["="](scope, args[i])[1])
1416
1586
  .reduce((s, c) => s.concat(c), [])
1417
- .concat(...this.ret.value["="](scope, callAddressLiteral)[1], new JumpInstruction(this.addr, EJumpKind.Always), new AddressResolver(callAddressLiteral),
1587
+ .concat(...this.ret["="](scope, callAddressLiteral)[1], new JumpInstruction(this.addr, EJumpKind.Always), new AddressResolver(callAddressLiteral),
1418
1588
  // ensures that functions can be called multiple times inside expressions
1419
- ...temp["="](scope, this.temp.value)[1]);
1589
+ ...temp["="](scope, this.temp)[1]);
1420
1590
  return [temp, inst];
1421
1591
  }
1422
1592
  inlineReturn(scope, arg) {
1423
- const argInst = arg ? this.inlineTemp.value["="](scope, arg)[1] : [];
1424
1593
  if (!this.inlineEnd)
1425
1594
  throw new CompilerError("Error during inline attempt: missing inline end adress");
1595
+ const inst = [];
1596
+ if (arg) {
1597
+ const [value, valueInst] = arg.eval(scope, this.inlineTemp);
1598
+ const argInst = this.inlineTemp["="](scope, value)[1];
1599
+ inst.push(...valueInst);
1600
+ inst.push(...argInst);
1601
+ }
1426
1602
  const jump = assign(new JumpInstruction(this.inlineEnd, EJumpKind.Always), {
1427
1603
  intent: EInstIntent.return,
1604
+ intentSource: this,
1428
1605
  });
1429
- return [null, [...argInst, jump]];
1606
+ return [null, [...inst, jump]];
1430
1607
  }
1431
- inlineCall(scope, args) {
1608
+ inlineCall(scope, args, out) {
1432
1609
  // create return value
1433
- this.inlineTemp = new ValueOwner({
1434
- scope: this.childScope,
1435
- value: assign(new SenseableValue(scope), {
1436
- mutability: EMutability.mutable,
1437
- }),
1438
- });
1610
+ this.inlineTemp = SenseableValue.from(this.childScope, out, EMutability.mutable);
1439
1611
  // make a copy of the function scope
1440
1612
  const fnScope = this.childScope.copy();
1441
1613
  // hard set variables within the function scope
1442
- this.paramOwners.forEach((owner, i) => {
1443
- const value = args[i];
1444
- if (!value.owner?.persistent)
1445
- owner.own(value);
1446
- fnScope.hardSet(new ValueOwner({
1447
- scope: fnScope,
1448
- value,
1449
- identifier: owner.identifier,
1450
- name: owner.name,
1451
- }));
1614
+ this.paramNames.forEach((name, i) => {
1615
+ fnScope.hardSet(name, args[i]);
1452
1616
  });
1453
1617
  this.inlineEnd = new LiteralValue(null);
1454
1618
  this.tryingInline = true;
1455
1619
  let inst = this.c.handle(fnScope, this.body)[1];
1456
1620
  this.tryingInline = false;
1457
- const returnIndex = inst.findIndex(i => i.alwaysRuns && i.intent === EInstIntent.return);
1621
+ const returnIndex = inst.findIndex(i => i.alwaysRuns &&
1622
+ i.intent === EInstIntent.return &&
1623
+ i.intentSource === this);
1458
1624
  if (returnIndex !== -1)
1459
1625
  inst = inst.slice(0, returnIndex + 1);
1460
1626
  inst.push(new AddressResolver(this.inlineEnd));
1461
1627
  // return the function value
1462
- return [this.inlineTemp.value, inst];
1628
+ return [this.inlineTemp, inst];
1463
1629
  }
1464
- call(scope, args) {
1630
+ call(scope, args, out) {
1465
1631
  this.ensureInit();
1466
- if (args.length !== this.paramOwners.length)
1632
+ if (args.length !== this.paramNames.length)
1467
1633
  throw new CompilerError("Cannot call: argument count not matching.");
1468
- const inlineCall = this.inlineCall(scope, args);
1634
+ const inlineCall = this.inlineCall(scope, args, out);
1635
+ hideRedundantJumps(inlineCall[1]);
1469
1636
  const inlineSize = inlineCall[1].filter(i => !i.hidden).length;
1470
1637
  if (this.inline || inlineSize <= this.callSize)
1471
1638
  return inlineCall;
1472
- return this.normalCall(scope, args);
1639
+ return this.normalCall(scope, args, out);
1473
1640
  }
1474
1641
  return(scope, arg) {
1475
1642
  this.ensureInit();
@@ -1484,8 +1651,11 @@ class FunctionValue extends VoidValue {
1484
1651
  eval(_scope) {
1485
1652
  return [this, []];
1486
1653
  }
1487
- consume(_scope) {
1488
- return [this, []];
1654
+ paramOuts() {
1655
+ this.ensureInit();
1656
+ if (this.inline || this.tryingInline)
1657
+ return;
1658
+ return this.paramValues;
1489
1659
  }
1490
1660
  }
1491
1661
  function endsWithReturn(inst) {
@@ -1506,7 +1676,7 @@ function endsWithReturn(inst) {
1506
1676
  return false;
1507
1677
  }
1508
1678
 
1509
- function handleFunctionNode(c, scope, node) {
1679
+ function handleFunctionNode(c, scope, node, out) {
1510
1680
  let { params, body } = node;
1511
1681
  if (es__namespace.isExpression(body)) {
1512
1682
  const { loc } = body;
@@ -1521,46 +1691,44 @@ function handleFunctionNode(c, scope, node) {
1521
1691
  params: params,
1522
1692
  body,
1523
1693
  c,
1694
+ out,
1524
1695
  }),
1525
1696
  [],
1526
1697
  ];
1527
1698
  }
1528
- const ArrowFunctionExpression = (c, scope, node) => {
1529
- return handleFunctionNode(c, scope, node);
1699
+ const ArrowFunctionExpression = (c, scope, node, out) => {
1700
+ return handleFunctionNode(c, scope, node, out);
1530
1701
  };
1531
1702
  const FunctionDeclaration = (c, scope, node) => {
1532
1703
  const identifier = node.id.name;
1533
1704
  const name = nodeName(node, !c.compactNames && identifier);
1534
- const functionIns = handleFunctionNode(c, scope, node);
1535
- const owner = new ValueOwner({
1536
- scope,
1537
- value: functionIns[0],
1538
- identifier,
1539
- name,
1540
- });
1541
- return [scope.set(owner), []];
1705
+ const functionIns = handleFunctionNode(c, scope, node, name);
1706
+ scope.set(identifier, functionIns[0]);
1707
+ return functionIns;
1542
1708
  };
1543
1709
  const FunctionExpression = ArrowFunctionExpression;
1544
1710
  const ObjectMethod = ArrowFunctionExpression;
1545
1711
 
1546
- const CallExpression = (c, scope, node) => {
1712
+ const CallExpression = (c, scope, node, out) => {
1547
1713
  const inst = [];
1548
- const args = node.arguments.map(node => {
1714
+ const [callee, calleeInst] = c.handleEval(scope, node.callee);
1715
+ inst.push(...calleeInst);
1716
+ const paramOuts = callee.paramOuts();
1717
+ const args = node.arguments.map((node, index) => {
1549
1718
  // TODO: this might be why t0 did not increment
1550
1719
  // TODO: figure out if the previous todo is still relevant
1551
- const [v, i] = c.handleConsume(scope, node);
1720
+ const out = paramOuts?.[index];
1721
+ const [v, i] = c.handleEval(scope, node, out);
1552
1722
  inst.push(...i);
1553
1723
  return v;
1554
1724
  });
1555
- const [callee, calleeInst] = c.handleConsume(scope, node.callee);
1556
- inst.push(...calleeInst);
1557
- const [callValue, callInst] = callee.call(scope, args);
1725
+ const [callValue, callInst] = callee.call(scope, args, out);
1558
1726
  inst.push(...callInst);
1559
1727
  return [callValue, inst];
1560
1728
  };
1561
1729
  const NewExpression = CallExpression;
1562
- const TaggedTemplateExpression = (c, scope, node) => {
1563
- const [tag, tagInst] = c.handleConsume(scope, node.tag);
1730
+ const TaggedTemplateExpression = (c, scope, node, out) => {
1731
+ const [tag, tagInst] = c.handleEval(scope, node.tag);
1564
1732
  const template = node.quasi;
1565
1733
  const stringsObject = ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.cooked ?? "")), {
1566
1734
  raw: ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.raw))),
@@ -1568,15 +1736,15 @@ const TaggedTemplateExpression = (c, scope, node) => {
1568
1736
  const expressions = [];
1569
1737
  const expressionInsts = [];
1570
1738
  template.expressions.forEach(expression => {
1571
- const [value, inst] = c.handleConsume(scope, expression);
1739
+ const [value, inst] = c.handleEval(scope, expression);
1572
1740
  expressions.push(value);
1573
1741
  expressionInsts.push(...inst);
1574
1742
  });
1575
- const [result, resultInst] = tag.call(scope, [stringsObject, ...expressions]);
1743
+ const [result, resultInst] = tag.call(scope, [stringsObject, ...expressions], out);
1576
1744
  return [result, [...expressionInsts, ...tagInst, ...resultInst]];
1577
1745
  };
1578
1746
 
1579
- const ObjectExpression = (c, scope, node) => {
1747
+ const ObjectExpression = (c, scope, node, out) => {
1580
1748
  const data = {};
1581
1749
  const inst = [];
1582
1750
  for (const prop of node.properties) {
@@ -1596,38 +1764,48 @@ const ObjectExpression = (c, scope, node) => {
1596
1764
  else {
1597
1765
  throw new CompilerError(`Unsupported object key type: ${key.type}`, key);
1598
1766
  }
1599
- const [member, memberInst] = c.handleEval(scope, value);
1600
- // TODO: use an approach that can be defined by each type
1601
- if (!member.owner && !(member instanceof LiteralValue)) {
1602
- new ValueOwner({
1603
- scope,
1604
- constant: true,
1605
- value: member,
1606
- });
1607
- }
1767
+ const [member, memberInst] = c.handleEval(scope, value, extractDestrucuringOut(out, index));
1608
1768
  data[index] = member;
1609
1769
  inst.push(...memberInst);
1610
1770
  }
1611
1771
  return [new ObjectValue(data), inst];
1612
1772
  };
1613
- const ArrayExpression = (c, scope, node) => {
1773
+ const ArrayExpression = (c, scope, node, out) => {
1614
1774
  const items = [];
1615
1775
  const inst = [];
1616
- node.elements.forEach(element => {
1776
+ node.elements.forEach((element, i) => {
1617
1777
  const [value, valueInst] = element
1618
- ? c.handleEval(scope, element)
1778
+ ? c.handleEval(scope, element, extractDestrucuringOut(out, i))
1619
1779
  : [undefined, []];
1620
1780
  items.push(value);
1621
1781
  inst.push(...valueInst);
1622
1782
  });
1623
1783
  return [ObjectValue.fromArray(items), inst];
1624
1784
  };
1625
- const MemberExpression = (c, scope, node) => {
1626
- const [obj, objInst] = c.handleConsume(scope, node.object);
1785
+ const MemberExpression = (c, scope, node, out) => {
1627
1786
  const [prop, propInst] = node.computed
1628
- ? c.handleConsume(scope, node.property)
1787
+ ? c.handleEval(scope, node.property)
1629
1788
  : [new LiteralValue(node.property.name), []];
1630
- const [got, gotInst] = obj.get(scope, prop);
1789
+ if (!out) {
1790
+ const [obj, objInst] = c.handleEval(scope, node.object);
1791
+ const [got, gotInst] = obj.get(scope, prop);
1792
+ return [got, [...objInst, ...propInst, ...gotInst]];
1793
+ }
1794
+ const temp = SenseableValue.out(scope, out);
1795
+ const objOut = new DestructuringValue(new Map([
1796
+ [
1797
+ prop,
1798
+ {
1799
+ value: temp,
1800
+ // a direct assignment should NOT happen
1801
+ handler: () => {
1802
+ throw new CompilerError("Unexpected destructuring assignment");
1803
+ },
1804
+ },
1805
+ ],
1806
+ ]));
1807
+ const [obj, objInst] = c.handleEval(scope, node.object, objOut);
1808
+ const [got, gotInst] = obj.get(scope, prop, temp);
1631
1809
  return [got, [...objInst, ...propInst, ...gotInst]];
1632
1810
  };
1633
1811
  const ArrayPattern = (c, scope, node) => {
@@ -1637,11 +1815,21 @@ const ArrayPattern = (c, scope, node) => {
1637
1815
  const element = node.elements[i];
1638
1816
  if (!element)
1639
1817
  continue;
1640
- const valueInst = c.handle(scope, element);
1641
- if (!valueInst[0])
1818
+ const [value, valueInst] = c.handle(scope, element);
1819
+ if (!value)
1642
1820
  throw new CompilerError("Destructuring element must resolve to a value", element);
1643
- inst.push(...valueInst[1]);
1644
- members.set(new LiteralValue(i), valueInst[0]);
1821
+ inst.push(...valueInst);
1822
+ members.set(new LiteralValue(i), {
1823
+ value,
1824
+ handler(get) {
1825
+ return c.handle(scope, element, () => {
1826
+ const inst = get();
1827
+ // assigns the output to the target value
1828
+ inst[1].push(...value["="](scope, inst[0])[1]);
1829
+ return inst;
1830
+ });
1831
+ },
1832
+ });
1645
1833
  }
1646
1834
  return [new DestructuringValue(members), inst];
1647
1835
  };
@@ -1656,13 +1844,23 @@ const ObjectPattern = (c, scope, node) => {
1656
1844
  const { key } = prop;
1657
1845
  const keyInst = !prop.computed && key.type === "Identifier"
1658
1846
  ? [new LiteralValue(key.name), []]
1659
- : c.handleConsume(scope, prop.key);
1847
+ : c.handleEval(scope, prop.key);
1660
1848
  inst.push(...keyInst[1]);
1661
- const valueInst = c.handle(scope, prop.value);
1662
- if (!valueInst[0])
1849
+ const [value, valueInst] = c.handle(scope, prop.value);
1850
+ if (!value)
1663
1851
  throw new CompilerError("Destructuring member must resolve to a value", prop.value);
1664
- inst.push(...valueInst[1]);
1665
- members.set(keyInst[0], valueInst[0]);
1852
+ inst.push(...valueInst);
1853
+ members.set(keyInst[0], {
1854
+ value,
1855
+ handler(get) {
1856
+ return c.handle(scope, prop, () => {
1857
+ const inst = get();
1858
+ // assigns the output to the target value
1859
+ inst[1].push(...value["="](scope, inst[0])[1]);
1860
+ return inst;
1861
+ });
1862
+ },
1863
+ });
1666
1864
  }
1667
1865
  return [new DestructuringValue(members), inst];
1668
1866
  };
@@ -1707,7 +1905,7 @@ const TSEnumDeclaration = (c, scope, node) => {
1707
1905
  : [new LiteralValue(counter)];
1708
1906
  if (!(value instanceof LiteralValue))
1709
1907
  throw new CompilerError("Enum members must contain literal values", member);
1710
- if (typeof value.data === "number") {
1908
+ if (value.isNumber()) {
1711
1909
  lastType = "number";
1712
1910
  counter = value.data + 1;
1713
1911
  }
@@ -1717,13 +1915,9 @@ const TSEnumDeclaration = (c, scope, node) => {
1717
1915
  const name = member.id.type === "Identifier" ? member.id.name : member.id.value;
1718
1916
  data[name] = value;
1719
1917
  }
1720
- scope.set(new ValueOwner({
1721
- scope,
1722
- value: new ObjectValue(data),
1723
- constant: true,
1724
- identifier: node.id.name,
1725
- name: nodeName(node, !c.compactNames && node.id.name),
1726
- }));
1918
+ const value = new ObjectValue(data);
1919
+ value.name = nodeName(node, !c.compactNames && node.id.name);
1920
+ scope.set(node.id.name, value);
1727
1921
  return [null, []];
1728
1922
  };
1729
1923
  const TSNonNullExpression = (c, scope, node) => {
@@ -1787,15 +1981,12 @@ class MacroFunction extends VoidValue {
1787
1981
  this.mutability = EMutability.constant;
1788
1982
  this.fn = fn;
1789
1983
  }
1790
- call(scope, args) {
1791
- return this.fn.apply(this, [scope, ...args]);
1984
+ call(scope, args, out) {
1985
+ return this.fn.apply(this, [scope, out, ...args]);
1792
1986
  }
1793
1987
  eval(_scope) {
1794
1988
  return [this, []];
1795
1989
  }
1796
- consume(_scope) {
1797
- return [this, []];
1798
- }
1799
1990
  }
1800
1991
 
1801
1992
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
@@ -1819,49 +2010,39 @@ const mathOperations = {
1819
2010
  sqrt: a => Math.sqrt(a),
1820
2011
  rand: null,
1821
2012
  };
1822
- function mathConstant(scope, name) {
1823
- return new ValueOwner({
1824
- scope,
1825
- constant: true,
1826
- name,
1827
- value: assign(new StoreValue(scope), {
1828
- mutability: EMutability.constant,
1829
- }),
1830
- }).value;
1831
- }
1832
- function createMacroMathOperations(scope) {
2013
+ function createMacroMathOperations() {
1833
2014
  const macroMathOperations = {
1834
- PI: mathConstant(scope, "@pi"),
1835
- E: mathConstant(scope, "@e"),
1836
- degToRad: mathConstant(scope, "@degToRad"),
1837
- radToDeg: mathConstant(scope, "@radToDeg"),
2015
+ PI: new StoreValue("@pi", EMutability.constant),
2016
+ E: new StoreValue("@e", EMutability.constant),
2017
+ degToRad: new StoreValue("@degToRad", EMutability.constant),
2018
+ radToDeg: new StoreValue("@radToDeg", EMutability.constant),
1838
2019
  };
1839
2020
  for (const key in mathOperations) {
1840
2021
  const fn = mathOperations[key];
1841
- macroMathOperations[key] = new MacroFunction((scope, a, b) => {
2022
+ macroMathOperations[key] = new MacroFunction((scope, out, a, b) => {
1842
2023
  if (b) {
1843
2024
  if (fn && a instanceof LiteralValue && b instanceof LiteralValue) {
1844
- if (typeof a.data !== "number" || typeof b.data !== "number")
2025
+ if (!a.isNumber() || !b.isNumber())
1845
2026
  throw new CompilerError("Cannot do math operation with non-numerical literals.");
1846
2027
  return [new LiteralValue(fn(a.num, b.num)), []];
1847
2028
  }
1848
- const temp = new StoreValue(scope);
2029
+ const temp = StoreValue.from(scope, out);
1849
2030
  return [temp, [new OperationInstruction(key, temp, a, b)]];
1850
2031
  }
1851
2032
  if (fn && a instanceof LiteralValue) {
1852
- if (typeof a.data !== "number")
2033
+ if (!a.isNumber())
1853
2034
  throw new CompilerError("Cannot do math operation with non-numerical literal.");
1854
2035
  return [new LiteralValue(fn(a.num)), []];
1855
2036
  }
1856
- const temp = new StoreValue(scope);
2037
+ const temp = StoreValue.from(scope, out);
1857
2038
  return [temp, [new OperationInstruction(key, temp, a, b)]];
1858
2039
  });
1859
2040
  }
1860
2041
  return macroMathOperations;
1861
2042
  }
1862
2043
  class MlogMath extends ObjectValue {
1863
- constructor(scope) {
1864
- super(createMacroMathOperations(scope));
2044
+ constructor() {
2045
+ super(createMacroMathOperations());
1865
2046
  }
1866
2047
  }
1867
2048
  // op angle result a b
@@ -1869,7 +2050,7 @@ class MlogMath extends ObjectValue {
1869
2050
 
1870
2051
  class Concat extends MacroFunction {
1871
2052
  constructor() {
1872
- super((scope, ...values) => {
2053
+ super((scope, out, ...values) => {
1873
2054
  for (const value of values)
1874
2055
  if (!(value instanceof LiteralValue))
1875
2056
  throw new CompilerError("Concat arguments must all be literal values.");
@@ -1881,66 +2062,19 @@ class Concat extends MacroFunction {
1881
2062
  }
1882
2063
  }
1883
2064
 
1884
- /** Asserts that `value` is a `LiteralValue` that contains a string */
1885
- function assertStringLiteral(value, name) {
1886
- if (!(value instanceof LiteralValue) || typeof value.data !== "string")
1887
- throw new CompilerError(`${name} must be a string literal`);
1888
- }
1889
- /** Asserts that `value` is a string literal that is part of a given set of `members`*/
1890
- function assertLiteralOneOf(value, members, name) {
1891
- assertStringLiteral(value, name);
1892
- if (!members.includes(value.data))
1893
- throw new CompilerError(`${name} must be one of: ${members
1894
- .slice(0, -1)
1895
- .map(member => `"${member}"`)
1896
- .join(", ")} or "${members[members.length - 1]}"`);
1897
- }
1898
- /** Asserts that `value` is either a store or a literal */
1899
- function assertIsRuntimeValue(value, name) {
1900
- if (!(value instanceof LiteralValue) && !(value instanceof StoreValue))
1901
- throw new CompilerError(`${name} must be a valid runtime value`);
1902
- }
1903
- /** Asserts that `value` is an `ObjectValue` */
1904
- function assertIsObjectMacro(value, name) {
1905
- if (!(value instanceof ObjectValue))
1906
- throw new CompilerError(`${name} must be an object macro`);
1907
- }
1908
- /** Asserts that `value` is an `ObjectValue` that has a length property */
1909
- function assertIsArrayMacro(value, name) {
1910
- if (!(value instanceof ObjectValue) || !value.data.length)
1911
- throw new CompilerError(`${name} must be an array macro`);
1912
- }
1913
- /** Asserts that all of the fields described are present on `value`*/
1914
- function assertObjectFields(value, fields) {
1915
- const result = [];
1916
- for (const field of fields) {
1917
- const param = typeof field === "object" ? field : { key: field };
1918
- const item = value.data[param.key];
1919
- if (item) {
1920
- param.validate?.(item);
1921
- result.push(item);
1922
- continue;
1923
- }
1924
- if (param.default == undefined)
1925
- throw new CompilerError(`The field "${param.name ?? param.key}" must be present`);
1926
- result.push(param.default);
1927
- }
1928
- return result;
1929
- }
1930
-
1931
2065
  /** Used to create namespaces that contain multiple methods that map into instructions */
1932
2066
  function createOverloadNamespace({ overloads, handler, }) {
1933
2067
  const result = {};
1934
2068
  for (const key in overloads) {
1935
2069
  const { named, args } = overloads[key];
1936
2070
  if (named) {
1937
- result[key] = new MacroFunction((scope, options) => {
2071
+ result[key] = new MacroFunction((scope, out, options) => {
1938
2072
  assertIsObjectMacro(options, named);
1939
- return handler(scope, key, ...assertObjectFields(options, args));
2073
+ return handler(scope, key, out, ...assertObjectFields(options, args));
1940
2074
  });
1941
2075
  continue;
1942
2076
  }
1943
- result[key] = new MacroFunction((scope, ...params) => {
2077
+ result[key] = new MacroFunction((scope, out, ...params) => {
1944
2078
  const handlerParams = [];
1945
2079
  // validate the paramters
1946
2080
  for (let i = 0; i < args.length; i++) {
@@ -1957,7 +2091,7 @@ function createOverloadNamespace({ overloads, handler, }) {
1957
2091
  arg.validate?.(param);
1958
2092
  handlerParams.push(param);
1959
2093
  }
1960
- return handler(scope, key, ...handlerParams);
2094
+ return handler(scope, key, out, ...handlerParams);
1961
2095
  });
1962
2096
  }
1963
2097
  return result;
@@ -2002,7 +2136,7 @@ class Draw extends ObjectValue {
2002
2136
  args: ["x", "y", "image", "size", "rotation"],
2003
2137
  },
2004
2138
  },
2005
- handler(scope, overload, ...args) {
2139
+ handler(scope, overload, out, ...args) {
2006
2140
  return [null, [new InstructionBase("draw", overload, ...args)]];
2007
2141
  },
2008
2142
  });
@@ -2012,7 +2146,7 @@ class Draw extends ObjectValue {
2012
2146
 
2013
2147
  class Print extends MacroFunction {
2014
2148
  constructor() {
2015
- super((scope, ...values) => {
2149
+ super((scope, out, ...values) => {
2016
2150
  const [first] = values;
2017
2151
  const inst = [];
2018
2152
  if (!isTemplateObjectArray(first)) {
@@ -2041,7 +2175,7 @@ class Print extends MacroFunction {
2041
2175
  const defaultTargetName$1 = "message1";
2042
2176
  class PrintFlush extends MacroFunction {
2043
2177
  constructor() {
2044
- super((scope, target) => {
2178
+ super((scope, out, target) => {
2045
2179
  if (!target)
2046
2180
  return [null, [new InstructionBase("printflush", defaultTargetName$1)]];
2047
2181
  if (!(target instanceof SenseableValue))
@@ -2054,7 +2188,7 @@ class PrintFlush extends MacroFunction {
2054
2188
  const defaultTargetName = "display1";
2055
2189
  class DrawFlush extends MacroFunction {
2056
2190
  constructor() {
2057
- super((scope, target) => {
2191
+ super((scope, out, target) => {
2058
2192
  if (!target)
2059
2193
  return [null, [new InstructionBase("drawflush", defaultTargetName)]];
2060
2194
  if (!(target instanceof SenseableValue))
@@ -2073,10 +2207,10 @@ class GetBlock extends ObjectValue {
2073
2207
  block: { args: ["x", "y"] },
2074
2208
  building: { args: ["x", "y"] },
2075
2209
  },
2076
- handler(scope, overload, x, y) {
2210
+ handler(scope, overload, out, x, y) {
2077
2211
  const output = overload === "building"
2078
- ? new SenseableValue(scope)
2079
- : new StoreValue(scope);
2212
+ ? SenseableValue.from(scope, out, EMutability.constant)
2213
+ : StoreValue.from(scope, out, EMutability.constant);
2080
2214
  return [
2081
2215
  output,
2082
2216
  [new InstructionBase("getblock", overload, output, x, y)],
@@ -2089,11 +2223,11 @@ class GetBlock extends ObjectValue {
2089
2223
 
2090
2224
  class GetLink extends MacroFunction {
2091
2225
  constructor() {
2092
- super((scope, index) => {
2226
+ super((scope, out, index) => {
2093
2227
  if (!(index instanceof StoreValue) &&
2094
- (!(index instanceof LiteralValue) || typeof index.data !== "number"))
2228
+ (!(index instanceof LiteralValue) || !index.isNumber()))
2095
2229
  throw new CompilerError("The getlink index must be a number literal or a store");
2096
- const outBuild = new SenseableValue(scope);
2230
+ const outBuild = SenseableValue.from(scope, out, EMutability.constant);
2097
2231
  return [outBuild, [new InstructionBase("getlink", outBuild, index)]];
2098
2232
  });
2099
2233
  }
@@ -2115,11 +2249,10 @@ class Control extends ObjectValue {
2115
2249
  config: { args: ["building", "value"] },
2116
2250
  color: { args: ["building", "r", "g", "b"] },
2117
2251
  },
2118
- handler(scope, overload, ...args) {
2252
+ handler(scope, overload, out, ...args) {
2119
2253
  if (overload !== "color")
2120
2254
  return [null, [new InstructionBase("control", overload, ...args)]];
2121
- const temp = new StoreValue(scope);
2122
- temp.ensureOwned();
2255
+ const temp = StoreValue.from(scope);
2123
2256
  // this "patch" is here for compatibility reasons
2124
2257
  // I don't want to add another breaking change
2125
2258
  // TODO: figure out if this patch should be removed later
@@ -2155,14 +2288,14 @@ const validRadarSorts = [
2155
2288
  ];
2156
2289
  class Radar extends MacroFunction {
2157
2290
  constructor() {
2158
- super((scope, options) => {
2291
+ super((scope, out, options) => {
2159
2292
  assertIsObjectMacro(options, "The radar options");
2160
2293
  const { building, filters, order, sort } = options.data;
2161
2294
  if (!(building instanceof SenseableValue))
2162
2295
  throw new CompilerError("The building must a senseable value");
2163
2296
  assertIsArrayMacro(filters, "filters");
2164
2297
  const { length } = filters.data;
2165
- if (!(length instanceof LiteralValue) || typeof length.data !== "number")
2298
+ if (!(length instanceof LiteralValue) || !length.isNumber())
2166
2299
  throw new CompilerError("The length of an array macro must be");
2167
2300
  if (length.data !== 3)
2168
2301
  throw new CompilerError("The filters array must have 3 items");
@@ -2174,7 +2307,7 @@ class Radar extends MacroFunction {
2174
2307
  if (!(order instanceof LiteralValue || order instanceof StoreValue))
2175
2308
  throw new CompilerError("The radar order must be a literal or a store");
2176
2309
  assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
2177
- const outUnit = new SenseableValue(scope);
2310
+ const outUnit = SenseableValue.from(scope, out, EMutability.constant);
2178
2311
  return [
2179
2312
  outUnit,
2180
2313
  [
@@ -2187,14 +2320,12 @@ class Radar extends MacroFunction {
2187
2320
 
2188
2321
  class Sensor extends MacroFunction {
2189
2322
  constructor() {
2190
- super((scope, property, target) => {
2323
+ super((scope, out, property, target) => {
2191
2324
  if (!(property instanceof StoreValue))
2192
2325
  throw new CompilerError("The sensor property must be a store");
2193
2326
  if (!(target instanceof SenseableValue))
2194
2327
  throw new CompilerError("The sensor target must be a senseable value");
2195
- const temp = assign(new SenseableValue(scope), {
2196
- mutability: EMutability.readonly,
2197
- });
2328
+ const temp = SenseableValue.from(scope, out, EMutability.readonly);
2198
2329
  return [temp, [new InstructionBase("sensor", temp, target, property)]];
2199
2330
  });
2200
2331
  }
@@ -2202,9 +2333,9 @@ class Sensor extends MacroFunction {
2202
2333
 
2203
2334
  class Wait extends MacroFunction {
2204
2335
  constructor() {
2205
- super((scope, seconds) => {
2336
+ super((scope, out, seconds) => {
2206
2337
  if (!(seconds instanceof StoreValue) &&
2207
- !(seconds instanceof LiteralValue && typeof seconds.data === "number"))
2338
+ !(seconds instanceof LiteralValue && seconds.isNumber()))
2208
2339
  throw new CompilerError("The wait seconds must be either a number literal or a store");
2209
2340
  return [null, [new InstructionBase("wait", seconds)]];
2210
2341
  });
@@ -2220,8 +2351,8 @@ class Lookup extends ObjectValue {
2220
2351
  item: { args: ["index"] },
2221
2352
  liquid: { args: ["index"] },
2222
2353
  },
2223
- handler(scope, overload, index) {
2224
- const output = new StoreValue(scope);
2354
+ handler(scope, overload, out, index) {
2355
+ const output = StoreValue.from(scope, out);
2225
2356
  return [
2226
2357
  output,
2227
2358
  [new InstructionBase("lookup", overload, output, index)],
@@ -2234,15 +2365,15 @@ class Lookup extends ObjectValue {
2234
2365
 
2235
2366
  class PackColor extends MacroFunction {
2236
2367
  constructor() {
2237
- super((scope, ...args) => {
2368
+ super((scope, out, ...args) => {
2238
2369
  if (args.length !== 4) {
2239
2370
  throw new CompilerError(`Expected 4 arguments, received ${args.length}`);
2240
2371
  }
2241
2372
  if (!args.every((value) => value instanceof StoreValue ||
2242
- (value instanceof LiteralValue && typeof value.data === "number"))) {
2373
+ (value instanceof LiteralValue && value.isNumber()))) {
2243
2374
  throw new CompilerError("packColor arguments must be either stores or number literals");
2244
2375
  }
2245
- const output = new StoreValue(scope);
2376
+ const output = StoreValue.from(scope, out);
2246
2377
  return [output, [new InstructionBase("packcolor", output, ...args)]];
2247
2378
  });
2248
2379
  }
@@ -2265,7 +2396,7 @@ class SetBlock extends ObjectValue {
2265
2396
  args: ["x", "y", "to", "team", { key: "rotation", default: "0" }],
2266
2397
  },
2267
2398
  },
2268
- handler(scope, overload, x, y, to, team, rotation) {
2399
+ handler(scope, overload, out, x, y, to, team, rotation) {
2269
2400
  return [
2270
2401
  null,
2271
2402
  [
@@ -2280,14 +2411,14 @@ class SetBlock extends ObjectValue {
2280
2411
 
2281
2412
  class SpawnUnit extends MacroFunction {
2282
2413
  constructor() {
2283
- super((scope, options) => {
2414
+ super((scope, out, options) => {
2284
2415
  assertIsObjectMacro(options, "The options");
2285
2416
  const { type, x, y, team, rotation } = options.data;
2286
2417
  assertIsRuntimeValue(type, "type");
2287
2418
  assertIsRuntimeValue(x, "x");
2288
2419
  assertIsRuntimeValue(y, "y");
2289
2420
  assertIsRuntimeValue(team, "team");
2290
- const output = new SenseableValue(scope);
2421
+ const output = SenseableValue.from(scope, out, EMutability.constant);
2291
2422
  return [
2292
2423
  output,
2293
2424
  [
@@ -2306,7 +2437,7 @@ class Stop extends MacroFunction {
2306
2437
 
2307
2438
  class UnitBind extends MacroFunction {
2308
2439
  constructor() {
2309
- super((scope, type) => {
2440
+ super((scope, out, type) => {
2310
2441
  if (!(type instanceof StoreValue))
2311
2442
  throw new CompilerError("The unitBind type must be a store");
2312
2443
  return [null, [new InstructionBase("ubind", type)]];
@@ -2345,21 +2476,20 @@ class UnitControl extends ObjectValue {
2345
2476
  args: ["x", "y", "radius"],
2346
2477
  },
2347
2478
  },
2348
- handler(scope, overload, ...args) {
2479
+ handler(scope, overload, out, ...args) {
2349
2480
  let result = null;
2350
2481
  let extraArgs = [];
2351
2482
  switch (overload) {
2352
2483
  case "getBlock": {
2353
- const outType = new StoreValue(scope);
2354
- const outBuilding = new SenseableValue(scope);
2484
+ const outType = StoreValue.from(scope, extractDestrucuringOut(out, 0));
2485
+ const outBuilding = SenseableValue.from(scope, extractDestrucuringOut(out, 1));
2355
2486
  result = ObjectValue.fromArray([outType, outBuilding]);
2356
2487
  extraArgs = [outType, outBuilding, new LiteralValue(0)];
2357
2488
  break;
2358
2489
  }
2359
2490
  case "within": {
2360
- const temp = new StoreValue(scope);
2361
- result = temp;
2362
- extraArgs = [temp, new LiteralValue(0)];
2491
+ result = StoreValue.from(scope, out);
2492
+ extraArgs = [result, new LiteralValue(0)];
2363
2493
  break;
2364
2494
  }
2365
2495
  }
@@ -2375,12 +2505,12 @@ class UnitControl extends ObjectValue {
2375
2505
 
2376
2506
  class UnitRadar extends MacroFunction {
2377
2507
  constructor() {
2378
- super((scope, options) => {
2508
+ super((scope, out, options) => {
2379
2509
  assertIsObjectMacro(options, "The radar options");
2380
2510
  const { filters, order, sort } = options.data;
2381
2511
  assertIsArrayMacro(filters, "filters");
2382
2512
  const { length } = filters.data;
2383
- if (!(length instanceof LiteralValue) || typeof length.data !== "number")
2513
+ if (!(length instanceof LiteralValue) || !length.isNumber())
2384
2514
  throw new CompilerError("The length of an array macro must be");
2385
2515
  if (length.data !== 3)
2386
2516
  throw new CompilerError("The filters array must have 3 items");
@@ -2392,7 +2522,7 @@ class UnitRadar extends MacroFunction {
2392
2522
  if (!(order instanceof LiteralValue || order instanceof StoreValue))
2393
2523
  throw new CompilerError("The radar order must be a literal or a store");
2394
2524
  assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
2395
- const outUnit = new SenseableValue(scope);
2525
+ const outUnit = SenseableValue.from(scope, out, EMutability.constant);
2396
2526
  return [
2397
2527
  outUnit,
2398
2528
  [
@@ -2430,11 +2560,11 @@ class UnitLocate extends ObjectValue {
2430
2560
  args: [],
2431
2561
  },
2432
2562
  },
2433
- handler(scope, overload, ...args) {
2434
- const outFound = new StoreValue(scope);
2435
- const outX = new StoreValue(scope);
2436
- const outY = new StoreValue(scope);
2437
- const outBuilding = new SenseableValue(scope);
2563
+ handler(scope, overload, out, ...args) {
2564
+ const outFound = StoreValue.from(scope, extractDestrucuringOut(out, 0));
2565
+ const outX = StoreValue.from(scope, extractDestrucuringOut(out, 1));
2566
+ const outY = StoreValue.from(scope, extractDestrucuringOut(out, 2));
2567
+ const outBuilding = SenseableValue.from(scope, overload === "ore" ? discardedName : extractDestrucuringOut(out, 3));
2438
2568
  const outArgs = [outX, outY, outFound, outBuilding];
2439
2569
  let inputArgs = [];
2440
2570
  switch (overload) {
@@ -2497,7 +2627,7 @@ class ApplyStatus extends ObjectValue {
2497
2627
  },
2498
2628
  clear: { args: ["status", "unit"] },
2499
2629
  },
2500
- handler(scope, overload, effect, unit, duration) {
2630
+ handler(scope, overload, out, effect, unit, duration) {
2501
2631
  if (!(effect instanceof LiteralValue))
2502
2632
  throw new CompilerError("The status effect must be a string literal");
2503
2633
  assertLiteralOneOf(effect, validEffects, "The status effect");
@@ -2515,7 +2645,7 @@ class ApplyStatus extends ObjectValue {
2515
2645
 
2516
2646
  class SpawnWave extends MacroFunction {
2517
2647
  constructor() {
2518
- super((scope, natural, x, y) => {
2648
+ super((scope, out, natural, x, y) => {
2519
2649
  if (!(natural instanceof LiteralValue) ||
2520
2650
  (natural.data !== 1 && natural.data !== 0))
2521
2651
  throw new CompilerError("The 'natural' argument must be a boolean literal");
@@ -2556,7 +2686,7 @@ class SetRule extends ObjectValue {
2556
2686
  rtsMinWeight: { args: ["team", "value"] },
2557
2687
  rtsMinSquad: { args: ["team", "value"] },
2558
2688
  },
2559
- handler(scope, overload, ...args) {
2689
+ handler(scope, overload, out, ...args) {
2560
2690
  const params = ["10", "0", "0", "100", "100"];
2561
2691
  switch (overload) {
2562
2692
  case "mapArea": {
@@ -2599,7 +2729,7 @@ class FlushMessage extends ObjectValue {
2599
2729
  announce: { args: ["duration"] },
2600
2730
  toast: { args: ["duration"] },
2601
2731
  },
2602
- handler(scope, overload, duration) {
2732
+ handler(scope, overload, out, duration) {
2603
2733
  return [null, [new InstructionBase("message", overload, duration)]];
2604
2734
  },
2605
2735
  });
@@ -2615,7 +2745,7 @@ class Cutscene extends ObjectValue {
2615
2745
  zoom: { args: ["level"] },
2616
2746
  stop: { args: [] },
2617
2747
  },
2618
- handler(scope, overload, ...args) {
2748
+ handler(scope, overload, out, ...args) {
2619
2749
  const params = Object.assign(["100", "100", "0.06", "0"], args);
2620
2750
  return [null, [new InstructionBase("cutscene", overload, ...params)]];
2621
2751
  },
@@ -2626,7 +2756,7 @@ class Cutscene extends ObjectValue {
2626
2756
 
2627
2757
  class Explosion extends MacroFunction {
2628
2758
  constructor() {
2629
- super((scope, options) => {
2759
+ super((scope, out, options) => {
2630
2760
  assertIsObjectMacro(options, "The options");
2631
2761
  const params = assertObjectFields(options, [
2632
2762
  "team",
@@ -2645,7 +2775,7 @@ class Explosion extends MacroFunction {
2645
2775
 
2646
2776
  class SetRate extends MacroFunction {
2647
2777
  constructor() {
2648
- super((scope, ipt) => [null, [new InstructionBase("setrate", ipt)]]);
2778
+ super((scope, out, ipt) => [null, [new InstructionBase("setrate", ipt)]]);
2649
2779
  }
2650
2780
  }
2651
2781
 
@@ -2662,8 +2792,8 @@ class Fetch extends ObjectValue {
2662
2792
  build: { args: ["team", "index", "block"] },
2663
2793
  buildCount: { args: ["team", "block"] },
2664
2794
  },
2665
- handler(scope, overload, team, ...rest) {
2666
- const output = new SenseableValue(scope);
2795
+ handler(scope, overload, out, team, ...rest) {
2796
+ const output = SenseableValue.from(scope, out, EMutability.constant);
2667
2797
  const params = ["0", "@conveyor"];
2668
2798
  if (overload === "buildCount") {
2669
2799
  params[1] = rest[0];
@@ -2683,8 +2813,8 @@ class Fetch extends ObjectValue {
2683
2813
 
2684
2814
  class GetFlag extends MacroFunction {
2685
2815
  constructor() {
2686
- super((scope, flag) => {
2687
- const result = new StoreValue(scope);
2816
+ super((scope, out, flag) => {
2817
+ const result = StoreValue.from(scope, out);
2688
2818
  return [result, [new InstructionBase("getflag", result, flag)]];
2689
2819
  });
2690
2820
  }
@@ -2692,7 +2822,7 @@ class GetFlag extends MacroFunction {
2692
2822
 
2693
2823
  class SetFlag extends MacroFunction {
2694
2824
  constructor() {
2695
- super((scope, flag, value) => {
2825
+ super((scope, out, flag, value) => {
2696
2826
  if (!flag)
2697
2827
  throw new CompilerError("Missing flag argument");
2698
2828
  if (!value)
@@ -2711,28 +2841,17 @@ class MemoryEntry extends BaseValue {
2711
2841
  this.macro = true;
2712
2842
  this.store = null;
2713
2843
  }
2714
- eval(scope) {
2844
+ eval(scope, out) {
2715
2845
  if (this.store)
2716
2846
  return [this.store, []];
2717
- const temp = new StoreValue(scope);
2847
+ const temp = (this.store = StoreValue.from(scope, out));
2718
2848
  return [
2719
2849
  temp,
2720
2850
  [new InstructionBase("read", temp, this.mem.cell, this.prop)],
2721
2851
  ];
2722
2852
  }
2723
- consume(scope) {
2724
- if (this.store)
2725
- return [this.store, []];
2726
- this.ensureOwned();
2727
- this.store = new StoreValue(scope);
2728
- this.owner.own(this.store);
2729
- return [
2730
- this.store,
2731
- [new InstructionBase("read", this.store, this.mem.cell, this.prop)],
2732
- ];
2733
- }
2734
2853
  "="(scope, value) {
2735
- const [data, dataInst] = value.consume(scope);
2854
+ const [data, dataInst] = value.eval(scope);
2736
2855
  return [
2737
2856
  data,
2738
2857
  [
@@ -2741,26 +2860,17 @@ class MemoryEntry extends BaseValue {
2741
2860
  ],
2742
2861
  ];
2743
2862
  }
2744
- ensureOwned() {
2745
- this.owner ??= new ValueOwner({ scope: this.scope, value: this });
2746
- }
2747
- }
2748
- for (const operator of operators) {
2749
- if (operator === "=")
2750
- continue;
2751
- MemoryEntry.prototype[operator] = function (...args) {
2752
- // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-unsafe-return
2753
- return BaseValue.prototype[operator].apply(this, args);
2754
- };
2755
2863
  }
2756
2864
  class MemoryMacro extends ObjectValue {
2757
2865
  constructor(cell, size) {
2758
2866
  super({
2759
- $get: new MacroFunction((scope, prop) => {
2760
- if (prop instanceof LiteralValue && typeof prop.data !== "number")
2867
+ $get: new MacroFunction((scope, out, prop) => {
2868
+ if (prop instanceof LiteralValue && !prop.isNumber())
2761
2869
  throw new CompilerError(`Invalid memory object property: "${prop.data}"`);
2762
- const obj = new MemoryEntry(scope, this, prop);
2763
- return [obj, []];
2870
+ const entry = new MemoryEntry(scope, this, prop);
2871
+ if (out)
2872
+ return entry.eval(scope, out);
2873
+ return [entry, []];
2764
2874
  }),
2765
2875
  length: size,
2766
2876
  size,
@@ -2774,10 +2884,10 @@ class MemoryMacro extends ObjectValue {
2774
2884
  class MemoryBuilder extends ObjectValue {
2775
2885
  constructor() {
2776
2886
  super({
2777
- $call: new MacroFunction((scope, cell, size = new LiteralValue(64)) => {
2887
+ $call: new MacroFunction((scope, out, cell, size = new LiteralValue(64)) => {
2778
2888
  if (!(cell instanceof SenseableValue))
2779
2889
  throw new CompilerError("Memory cell must be a senseable value.");
2780
- if (!(size instanceof LiteralValue && typeof size.data === "number"))
2890
+ if (!(size instanceof LiteralValue && size.isNumber()))
2781
2891
  throw new CompilerError("The memory size must be a number literal.");
2782
2892
  return [new MemoryMacro(cell, size), []];
2783
2893
  }),
@@ -2789,8 +2899,8 @@ const dynamicVars = ["time", "tick"];
2789
2899
  class NamespaceMacro extends ObjectValue {
2790
2900
  constructor({ changeCasing = false } = {}) {
2791
2901
  super({
2792
- $get: new MacroFunction((scope, prop) => {
2793
- if (!(prop instanceof LiteralValue) || typeof prop.data !== "string")
2902
+ $get: new MacroFunction((scope, out, prop) => {
2903
+ if (!(prop instanceof LiteralValue) || !prop.isString())
2794
2904
  throw new CompilerError("Cannot use dynamic properties on namespace macros");
2795
2905
  const symbolName = this.changeCasing
2796
2906
  ? camelToDashCase(prop.data)
@@ -2798,57 +2908,37 @@ class NamespaceMacro extends ObjectValue {
2798
2908
  const mutability = dynamicVars.includes(symbolName)
2799
2909
  ? EMutability.readonly
2800
2910
  : EMutability.constant;
2801
- const owner = new ValueOwner({
2802
- scope,
2803
- value: assign(new StoreValue(scope), {
2804
- mutability,
2805
- }),
2806
- name: `@${symbolName}`,
2807
- });
2808
- return [owner.value, []];
2911
+ const result = new StoreValue(`@${symbolName}`, mutability);
2912
+ return [result, []];
2809
2913
  }),
2810
2914
  });
2811
2915
  this.changeCasing = changeCasing;
2812
2916
  }
2813
2917
  }
2814
2918
  class VarsNamespace extends NamespaceMacro {
2815
- constructor(scope) {
2919
+ constructor() {
2816
2920
  super();
2817
2921
  Object.assign(this.data, {
2818
- unit: new ValueOwner({
2819
- scope,
2820
- name: "@unit",
2821
- value: new SenseableValue(scope),
2822
- }).value,
2823
- this: new ValueOwner({
2824
- scope,
2825
- name: "@this",
2826
- value: new SenseableValue(scope),
2827
- }).value,
2922
+ unit: new SenseableValue("@unit"),
2923
+ this: new SenseableValue("@this"),
2828
2924
  });
2829
2925
  }
2830
2926
  }
2831
2927
 
2832
2928
  class GetGlobal extends MacroFunction {
2833
2929
  constructor(mutability) {
2834
- super((scope, name) => {
2835
- if (!(name instanceof LiteralValue) || typeof name.data !== "string")
2930
+ super((scope, out, name) => {
2931
+ if (!(name instanceof LiteralValue) || !name.isString())
2836
2932
  throw new CompilerError("The name parameter must be a string literal.");
2837
- const owner = new ValueOwner({
2838
- scope,
2839
- name: name.data,
2840
- value: assign(new SenseableValue(scope), {
2841
- mutability,
2842
- }),
2843
- });
2844
- return [owner.value, []];
2933
+ const result = new SenseableValue(name.data, mutability);
2934
+ return [result, []];
2845
2935
  });
2846
2936
  }
2847
2937
  }
2848
2938
 
2849
2939
  class Asm extends MacroFunction {
2850
2940
  constructor() {
2851
- super((scope, stringsArray, ...values) => {
2941
+ super((scope, out, stringsArray, ...values) => {
2852
2942
  if (!isTemplateObjectArray(stringsArray))
2853
2943
  throw new CompilerError("Expected to receive a template strings array");
2854
2944
  const args = [];
@@ -2926,7 +3016,7 @@ function validateInstructionArgs(args) {
2926
3016
  function initScope(scope) {
2927
3017
  // namespaces
2928
3018
  scope.hardSet("ControlKind", new NamespaceMacro());
2929
- scope.hardSet("Vars", new VarsNamespace(scope));
3019
+ scope.hardSet("Vars", new VarsNamespace());
2930
3020
  scope.hardSet("Teams", new NamespaceMacro());
2931
3021
  scope.hardSet("Items", new NamespaceMacro({ changeCasing: true }));
2932
3022
  scope.hardSet("Liquids", new NamespaceMacro());
@@ -2938,7 +3028,7 @@ function initScope(scope) {
2938
3028
  scope.hardSet("getVar", new GetGlobal(EMutability.mutable));
2939
3029
  scope.hardSet("concat", new Concat());
2940
3030
  scope.hardSet("asm", new Asm());
2941
- scope.hardSet("Math", new MlogMath(scope));
3031
+ scope.hardSet("Math", new MlogMath());
2942
3032
  scope.hardSet("Memory", new MemoryBuilder());
2943
3033
  // commands
2944
3034
  scope.hardSet("draw", new Draw());
@@ -3030,11 +3120,11 @@ class Compiler {
3030
3120
  }
3031
3121
  return result;
3032
3122
  }
3033
- handle(scope, node, handler = this.handlers[node.type]) {
3123
+ handle(scope, node, handler = this.handlers[node.type], out) {
3034
3124
  try {
3035
3125
  if (!handler)
3036
3126
  throw new CompilerError("Missing handler for " + node.type);
3037
- const result = handler(this, scope, node, null);
3127
+ const result = handler(this, scope, node, out, null);
3038
3128
  if (this.sourcemap)
3039
3129
  return appendSourceLocations(result, node);
3040
3130
  return result;
@@ -3046,21 +3136,11 @@ class Compiler {
3046
3136
  throw error;
3047
3137
  }
3048
3138
  }
3049
- handleEval(scope, node) {
3050
- const [res, inst] = this.handle(scope, node);
3139
+ handleEval(scope, node, out) {
3140
+ const [res, inst] = this.handle(scope, node, undefined, out);
3051
3141
  if (!res)
3052
3142
  throw new CompilerError("Cannot eval null", node);
3053
- const [evaluated, evaluatedInst] = res.eval(scope);
3054
- const result = [evaluated, [...inst, ...evaluatedInst]];
3055
- if (this.sourcemap)
3056
- return appendSourceLocations(result, node);
3057
- return result;
3058
- }
3059
- handleConsume(scope, node) {
3060
- const [res, inst] = this.handle(scope, node);
3061
- if (!res)
3062
- throw new CompilerError("Cannot consume null", node);
3063
- const [evaluated, evaluatedInst] = res.consume(scope);
3143
+ const [evaluated, evaluatedInst] = res.eval(scope, out);
3064
3144
  const result = [evaluated, [...inst, ...evaluatedInst]];
3065
3145
  if (this.sourcemap)
3066
3146
  return appendSourceLocations(result, node);
@@ -3072,7 +3152,7 @@ class Compiler {
3072
3152
  * The usage of this method over a regular loop over an array of nodes
3073
3153
  * is only required if the code inside the loop generates
3074
3154
  * instructions that are not tracked by the compiler
3075
- * handler methods ({@link handle}, {@link handleEval}, {@link handleConsume}
3155
+ * handler methods ({@link handle}, {@link handleEval}
3076
3156
  * and {@link handleMany})
3077
3157
  *
3078
3158
  */
@@ -3085,30 +3165,6 @@ class Compiler {
3085
3165
  return [null, lines];
3086
3166
  }
3087
3167
  }
3088
- /** Removes jump instructions that point right to the next line */
3089
- function hideRedundantJumps(inst) {
3090
- for (let i = 0; i < inst.length; i++) {
3091
- const instruction = inst[i];
3092
- if (!(instruction instanceof JumpInstruction))
3093
- continue;
3094
- const { address } = instruction;
3095
- let searchIndex = i + 1;
3096
- let current = inst[searchIndex];
3097
- while (current.hidden) {
3098
- if (current instanceof AddressResolver &&
3099
- current.bonds.includes(address)) {
3100
- // only gets assigned if `current` is an adress resolver
3101
- // that resolves the jump adress
3102
- // and that is also right next to the jump,
3103
- // without any actual instructions in between
3104
- instruction.hidden = true;
3105
- break;
3106
- }
3107
- searchIndex++;
3108
- current = inst[searchIndex];
3109
- }
3110
- }
3111
- }
3112
3168
  function* hoistedFunctionNodes(nodes) {
3113
3169
  // sorting is O(n long n) while this is just O(n)
3114
3170
  // besides, it's better not to modify the node array