mlogjs 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,7 +1319,7 @@ 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);
@@ -1208,7 +1337,7 @@ const SwitchStatement = (c, scope, node) => {
1208
1337
  inst.push(bodyLine, ...bodyInst);
1209
1338
  continue;
1210
1339
  }
1211
- const [test, testInst] = c.handleConsume(innerScope, scase.test);
1340
+ const [test, testInst] = c.handleEval(innerScope, scase.test);
1212
1341
  // check if it can be evaluated at compile time
1213
1342
  const [comp] = ref["=="](innerScope, test);
1214
1343
  // if the constant expression resolves to false
@@ -1251,7 +1380,7 @@ const SwitchStatement = (c, scope, node) => {
1251
1380
 
1252
1381
  const WhileStatement = (c, scope, node) => {
1253
1382
  const lines = [];
1254
- const [test, testLines] = c.handleConsume(scope, node.test);
1383
+ const [test, testLines] = c.handleEval(scope, node.test);
1255
1384
  const childScope = scope.createScope();
1256
1385
  const startLoopAddr = new LiteralValue(null);
1257
1386
  const endLoopAddr = new LiteralValue(null);
@@ -1271,7 +1400,7 @@ const WhileStatement = (c, scope, node) => {
1271
1400
  };
1272
1401
  const DoWhileStatement = (c, scope, node) => {
1273
1402
  const lines = [];
1274
- const [test, testLines] = c.handleConsume(scope, node.test);
1403
+ const [test, testLines] = c.handleEval(scope, node.test);
1275
1404
  const childScope = scope.createScope();
1276
1405
  const startLoopAddr = new LiteralValue(null);
1277
1406
  const startLoopLine = new AddressResolver(startLoopAddr).bindContinue(childScope);
@@ -1300,9 +1429,11 @@ const ForStatement = (c, parentScope, node) => {
1300
1429
  const scope = parentScope.createScope();
1301
1430
  const initInst = node.init ? c.handle(scope, node.init)[1] : [];
1302
1431
  const [test, testLines] = node.test
1303
- ? c.handleConsume(scope, node.test)
1432
+ ? c.handleEval(scope, node.test)
1304
1433
  : [new LiteralValue(1), []];
1305
- const updateLines = node.update ? c.handle(scope, node.update)[1] : [];
1434
+ const updateLines = node.update
1435
+ ? c.handle(scope, node.update, undefined, discardedName)[1]
1436
+ : [];
1306
1437
  const startLoopAddr = new LiteralValue(null);
1307
1438
  // continue statements jump here to run the loop update lines
1308
1439
  const beforeEndAddr = new LiteralValue(null);
@@ -1330,139 +1461,131 @@ const ForStatement = (c, parentScope, node) => {
1330
1461
  };
1331
1462
 
1332
1463
  class FunctionValue extends VoidValue {
1333
- constructor({ scope, params, body, c, }) {
1464
+ constructor({ scope, params, body, c, out, }) {
1334
1465
  super();
1335
1466
  this.mutability = EMutability.constant;
1336
1467
  this.macro = true;
1337
- this.paramOwners = [];
1468
+ this.paramValues = [];
1469
+ this.paramNames = [];
1338
1470
  this.bundled = false;
1339
1471
  this.initialized = false;
1340
1472
  this.scope = scope;
1341
1473
  this.body = body;
1342
1474
  this.c = c;
1343
1475
  this.params = params;
1476
+ this.out = out;
1477
+ this.name = extractOutName(out) ?? scope.makeTempName();
1344
1478
  }
1345
1479
  typeof() {
1346
1480
  return [new LiteralValue("function"), []];
1347
1481
  }
1348
- initScope(name) {
1349
- this.childScope = this.scope.createFunction(name);
1482
+ initScope() {
1483
+ this.childScope = this.scope.createFunction(this.name);
1350
1484
  this.childScope.function = this;
1351
1485
  for (const id of this.params) {
1352
1486
  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);
1487
+ const value = new SenseableValue(name, EMutability.mutable);
1488
+ this.childScope.set(id.name, value);
1489
+ this.paramValues.push(value);
1490
+ this.paramNames.push(id.name);
1363
1491
  }
1364
- this.callSize = this.paramOwners.length + 2;
1492
+ this.callSize = this.paramValues.length + 2;
1365
1493
  }
1366
1494
  ensureInit() {
1367
- if (!this.owner)
1368
- throw new CompilerError(`Functions must be owned by a variable`);
1369
1495
  if (this.initialized)
1370
1496
  return;
1371
1497
  this.initialized = true;
1372
- const { name } = this.owner;
1373
- this.initScope(name);
1498
+ this.initScope();
1374
1499
  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
- });
1500
+ this.temp = new SenseableValue(`${internalPrefix}f${this.name}`, EMutability.mutable);
1501
+ this.ret = new StoreValue(`${internalPrefix}r${this.name}`);
1387
1502
  this.inst = [
1388
1503
  new AddressResolver(this.addr),
1389
1504
  ...this.c.handle(this.childScope, this.body)[1],
1390
1505
  ];
1391
1506
  if (!endsWithReturn(this.inst)) {
1392
- this.inst.push(assign(new SetCounterInstruction(this.ret.value), {
1507
+ this.inst.push(assign(new SetCounterInstruction(this.ret), {
1393
1508
  intent: EInstIntent.return,
1394
1509
  }));
1395
1510
  }
1396
1511
  }
1397
1512
  normalReturn(scope, arg) {
1398
- const argInst = arg ? this.temp.value["="](scope, arg)[1] : [];
1399
- const returnInst = assign(new SetCounterInstruction(this.ret.value), {
1513
+ const inst = [];
1514
+ if (arg) {
1515
+ const [value, valueInst] = arg.eval(scope, this.temp);
1516
+ const argInst = this.temp["="](scope, value)[1];
1517
+ inst.push(...valueInst);
1518
+ inst.push(...argInst);
1519
+ }
1520
+ const returnInst = assign(new SetCounterInstruction(this.ret), {
1400
1521
  intent: EInstIntent.return,
1401
1522
  });
1402
- return [null, [...argInst, returnInst]];
1523
+ return [null, [...inst, returnInst]];
1403
1524
  }
1404
- normalCall(scope, args) {
1525
+ // TODO: use static analysis to determine if
1526
+ // we can safely skip the temp value assignment
1527
+ normalCall(scope, args, out) {
1405
1528
  if (!this.bundled)
1406
1529
  this.childScope.inst.push(...this.inst);
1407
1530
  this.bundled = true;
1408
1531
  const callAddressLiteral = new LiteralValue(null);
1409
- const inst = this.paramOwners
1410
- .map(({ value: param }, i) => param["="](scope, args[i])[1])
1532
+ const temp = SenseableValue.from(scope, out, EMutability.mutable);
1533
+ const inst = this.paramValues
1534
+ .map((param, i) => param["="](scope, args[i])[1])
1411
1535
  .reduce((s, c) => s.concat(c), [])
1412
- .concat(...this.ret.value["="](scope, callAddressLiteral)[1], new JumpInstruction(this.addr, EJumpKind.Always), new AddressResolver(callAddressLiteral));
1413
- return [this.temp.value, inst];
1536
+ .concat(...this.ret["="](scope, callAddressLiteral)[1], new JumpInstruction(this.addr, EJumpKind.Always), new AddressResolver(callAddressLiteral),
1537
+ // ensures that functions can be called multiple times inside expressions
1538
+ ...temp["="](scope, this.temp)[1]);
1539
+ return [temp, inst];
1414
1540
  }
1415
1541
  inlineReturn(scope, arg) {
1416
- const argInst = arg ? this.inlineTemp.value["="](scope, arg)[1] : [];
1417
1542
  if (!this.inlineEnd)
1418
1543
  throw new CompilerError("Error during inline attempt: missing inline end adress");
1544
+ const inst = [];
1545
+ if (arg) {
1546
+ const [value, valueInst] = arg.eval(scope, this.inlineTemp);
1547
+ const argInst = this.inlineTemp["="](scope, value)[1];
1548
+ inst.push(...valueInst);
1549
+ inst.push(...argInst);
1550
+ }
1419
1551
  const jump = assign(new JumpInstruction(this.inlineEnd, EJumpKind.Always), {
1420
1552
  intent: EInstIntent.return,
1553
+ intentSource: this,
1421
1554
  });
1422
- return [null, [...argInst, jump]];
1555
+ return [null, [...inst, jump]];
1423
1556
  }
1424
- inlineCall(scope, args) {
1557
+ inlineCall(scope, args, out) {
1425
1558
  // create return value
1426
- this.inlineTemp = new ValueOwner({
1427
- scope: this.childScope,
1428
- value: assign(new SenseableValue(scope), {
1429
- mutability: EMutability.mutable,
1430
- }),
1431
- });
1559
+ this.inlineTemp = SenseableValue.from(this.childScope, out, EMutability.mutable);
1432
1560
  // make a copy of the function scope
1433
1561
  const fnScope = this.childScope.copy();
1434
1562
  // hard set variables within the function scope
1435
- this.paramOwners.forEach((owner, i) => {
1436
- const value = args[i];
1437
- if (!value.owner?.persistent)
1438
- owner.own(value);
1439
- fnScope.hardSet(new ValueOwner({
1440
- scope: fnScope,
1441
- value,
1442
- identifier: owner.identifier,
1443
- name: owner.name,
1444
- }));
1563
+ this.paramNames.forEach((name, i) => {
1564
+ fnScope.hardSet(name, args[i]);
1445
1565
  });
1446
1566
  this.inlineEnd = new LiteralValue(null);
1447
1567
  this.tryingInline = true;
1448
1568
  let inst = this.c.handle(fnScope, this.body)[1];
1449
1569
  this.tryingInline = false;
1450
- const returnIndex = inst.findIndex(i => i.alwaysRuns && i.intent === EInstIntent.return);
1570
+ const returnIndex = inst.findIndex(i => i.alwaysRuns &&
1571
+ i.intent === EInstIntent.return &&
1572
+ i.intentSource === this);
1451
1573
  if (returnIndex !== -1)
1452
1574
  inst = inst.slice(0, returnIndex + 1);
1453
1575
  inst.push(new AddressResolver(this.inlineEnd));
1454
1576
  // return the function value
1455
- return [this.inlineTemp.value, inst];
1577
+ return [this.inlineTemp, inst];
1456
1578
  }
1457
- call(scope, args) {
1579
+ call(scope, args, out) {
1458
1580
  this.ensureInit();
1459
- if (args.length !== this.paramOwners.length)
1581
+ if (args.length !== this.paramNames.length)
1460
1582
  throw new CompilerError("Cannot call: argument count not matching.");
1461
- const inlineCall = this.inlineCall(scope, args);
1583
+ const inlineCall = this.inlineCall(scope, args, out);
1584
+ hideRedundantJumps(inlineCall[1]);
1462
1585
  const inlineSize = inlineCall[1].filter(i => !i.hidden).length;
1463
1586
  if (this.inline || inlineSize <= this.callSize)
1464
1587
  return inlineCall;
1465
- return this.normalCall(scope, args);
1588
+ return this.normalCall(scope, args, out);
1466
1589
  }
1467
1590
  return(scope, arg) {
1468
1591
  this.ensureInit();
@@ -1477,8 +1600,11 @@ class FunctionValue extends VoidValue {
1477
1600
  eval(_scope) {
1478
1601
  return [this, []];
1479
1602
  }
1480
- consume(_scope) {
1481
- return [this, []];
1603
+ paramOuts() {
1604
+ this.ensureInit();
1605
+ if (this.inline || this.tryingInline)
1606
+ return;
1607
+ return this.paramValues;
1482
1608
  }
1483
1609
  }
1484
1610
  function endsWithReturn(inst) {
@@ -1499,7 +1625,7 @@ function endsWithReturn(inst) {
1499
1625
  return false;
1500
1626
  }
1501
1627
 
1502
- function handleFunctionNode(c, scope, node) {
1628
+ function handleFunctionNode(c, scope, node, out) {
1503
1629
  let { params, body } = node;
1504
1630
  if (es__namespace.isExpression(body)) {
1505
1631
  const { loc } = body;
@@ -1514,46 +1640,44 @@ function handleFunctionNode(c, scope, node) {
1514
1640
  params: params,
1515
1641
  body,
1516
1642
  c,
1643
+ out,
1517
1644
  }),
1518
1645
  [],
1519
1646
  ];
1520
1647
  }
1521
- const ArrowFunctionExpression = (c, scope, node) => {
1522
- return handleFunctionNode(c, scope, node);
1648
+ const ArrowFunctionExpression = (c, scope, node, out) => {
1649
+ return handleFunctionNode(c, scope, node, out);
1523
1650
  };
1524
1651
  const FunctionDeclaration = (c, scope, node) => {
1525
1652
  const identifier = node.id.name;
1526
1653
  const name = nodeName(node, !c.compactNames && identifier);
1527
- const functionIns = handleFunctionNode(c, scope, node);
1528
- const owner = new ValueOwner({
1529
- scope,
1530
- value: functionIns[0],
1531
- identifier,
1532
- name,
1533
- });
1534
- return [scope.set(owner), []];
1654
+ const functionIns = handleFunctionNode(c, scope, node, name);
1655
+ scope.set(identifier, functionIns[0]);
1656
+ return functionIns;
1535
1657
  };
1536
1658
  const FunctionExpression = ArrowFunctionExpression;
1537
1659
  const ObjectMethod = ArrowFunctionExpression;
1538
1660
 
1539
- const CallExpression = (c, scope, node) => {
1661
+ const CallExpression = (c, scope, node, out) => {
1540
1662
  const inst = [];
1541
- const args = node.arguments.map(node => {
1663
+ const [callee, calleeInst] = c.handleEval(scope, node.callee);
1664
+ inst.push(...calleeInst);
1665
+ const paramOuts = callee.paramOuts();
1666
+ const args = node.arguments.map((node, index) => {
1542
1667
  // TODO: this might be why t0 did not increment
1543
1668
  // TODO: figure out if the previous todo is still relevant
1544
- const [v, i] = c.handleConsume(scope, node);
1669
+ const out = paramOuts?.[index];
1670
+ const [v, i] = c.handleEval(scope, node, out);
1545
1671
  inst.push(...i);
1546
1672
  return v;
1547
1673
  });
1548
- const [callee, calleeInst] = c.handleConsume(scope, node.callee);
1549
- inst.push(...calleeInst);
1550
- const [callValue, callInst] = callee.call(scope, args);
1674
+ const [callValue, callInst] = callee.call(scope, args, out);
1551
1675
  inst.push(...callInst);
1552
1676
  return [callValue, inst];
1553
1677
  };
1554
1678
  const NewExpression = CallExpression;
1555
- const TaggedTemplateExpression = (c, scope, node) => {
1556
- const [tag, tagInst] = c.handleConsume(scope, node.tag);
1679
+ const TaggedTemplateExpression = (c, scope, node, out) => {
1680
+ const [tag, tagInst] = c.handleEval(scope, node.tag);
1557
1681
  const template = node.quasi;
1558
1682
  const stringsObject = ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.cooked ?? "")), {
1559
1683
  raw: ObjectValue.fromArray(template.quasis.map(quasi => new LiteralValue(quasi.value.raw))),
@@ -1561,15 +1685,15 @@ const TaggedTemplateExpression = (c, scope, node) => {
1561
1685
  const expressions = [];
1562
1686
  const expressionInsts = [];
1563
1687
  template.expressions.forEach(expression => {
1564
- const [value, inst] = c.handleConsume(scope, expression);
1688
+ const [value, inst] = c.handleEval(scope, expression);
1565
1689
  expressions.push(value);
1566
1690
  expressionInsts.push(...inst);
1567
1691
  });
1568
- const [result, resultInst] = tag.call(scope, [stringsObject, ...expressions]);
1692
+ const [result, resultInst] = tag.call(scope, [stringsObject, ...expressions], out);
1569
1693
  return [result, [...expressionInsts, ...tagInst, ...resultInst]];
1570
1694
  };
1571
1695
 
1572
- const ObjectExpression = (c, scope, node) => {
1696
+ const ObjectExpression = (c, scope, node, out) => {
1573
1697
  const data = {};
1574
1698
  const inst = [];
1575
1699
  for (const prop of node.properties) {
@@ -1589,38 +1713,48 @@ const ObjectExpression = (c, scope, node) => {
1589
1713
  else {
1590
1714
  throw new CompilerError(`Unsupported object key type: ${key.type}`, key);
1591
1715
  }
1592
- const [member, memberInst] = c.handleEval(scope, value);
1593
- // TODO: use an approach that can be defined by each type
1594
- if (!member.owner && !(member instanceof LiteralValue)) {
1595
- new ValueOwner({
1596
- scope,
1597
- constant: true,
1598
- value: member,
1599
- });
1600
- }
1716
+ const [member, memberInst] = c.handleEval(scope, value, extractDestrucuringOut(out, index));
1601
1717
  data[index] = member;
1602
1718
  inst.push(...memberInst);
1603
1719
  }
1604
1720
  return [new ObjectValue(data), inst];
1605
1721
  };
1606
- const ArrayExpression = (c, scope, node) => {
1722
+ const ArrayExpression = (c, scope, node, out) => {
1607
1723
  const items = [];
1608
1724
  const inst = [];
1609
- node.elements.forEach(element => {
1725
+ node.elements.forEach((element, i) => {
1610
1726
  const [value, valueInst] = element
1611
- ? c.handleEval(scope, element)
1727
+ ? c.handleEval(scope, element, extractDestrucuringOut(out, i))
1612
1728
  : [undefined, []];
1613
1729
  items.push(value);
1614
1730
  inst.push(...valueInst);
1615
1731
  });
1616
1732
  return [ObjectValue.fromArray(items), inst];
1617
1733
  };
1618
- const MemberExpression = (c, scope, node) => {
1619
- const [obj, objInst] = c.handleConsume(scope, node.object);
1734
+ const MemberExpression = (c, scope, node, out) => {
1620
1735
  const [prop, propInst] = node.computed
1621
- ? c.handleConsume(scope, node.property)
1736
+ ? c.handleEval(scope, node.property)
1622
1737
  : [new LiteralValue(node.property.name), []];
1623
- const [got, gotInst] = obj.get(scope, prop);
1738
+ if (!out) {
1739
+ const [obj, objInst] = c.handleEval(scope, node.object);
1740
+ const [got, gotInst] = obj.get(scope, prop);
1741
+ return [got, [...objInst, ...propInst, ...gotInst]];
1742
+ }
1743
+ const temp = SenseableValue.out(scope, out);
1744
+ const objOut = new DestructuringValue(new Map([
1745
+ [
1746
+ prop,
1747
+ {
1748
+ value: temp,
1749
+ // a direct assignment should NOT happen
1750
+ handler: () => {
1751
+ throw new CompilerError("Unexpected destructuring assignment");
1752
+ },
1753
+ },
1754
+ ],
1755
+ ]));
1756
+ const [obj, objInst] = c.handleEval(scope, node.object, objOut);
1757
+ const [got, gotInst] = obj.get(scope, prop, temp);
1624
1758
  return [got, [...objInst, ...propInst, ...gotInst]];
1625
1759
  };
1626
1760
  const ArrayPattern = (c, scope, node) => {
@@ -1630,11 +1764,21 @@ const ArrayPattern = (c, scope, node) => {
1630
1764
  const element = node.elements[i];
1631
1765
  if (!element)
1632
1766
  continue;
1633
- const valueInst = c.handle(scope, element);
1634
- if (!valueInst[0])
1767
+ const [value, valueInst] = c.handle(scope, element);
1768
+ if (!value)
1635
1769
  throw new CompilerError("Destructuring element must resolve to a value", element);
1636
- inst.push(...valueInst[1]);
1637
- members.set(new LiteralValue(i), valueInst[0]);
1770
+ inst.push(...valueInst);
1771
+ members.set(new LiteralValue(i), {
1772
+ value,
1773
+ handler(get) {
1774
+ return c.handle(scope, element, () => {
1775
+ const inst = get();
1776
+ // assigns the output to the target value
1777
+ inst[1].push(...value["="](scope, inst[0])[1]);
1778
+ return inst;
1779
+ });
1780
+ },
1781
+ });
1638
1782
  }
1639
1783
  return [new DestructuringValue(members), inst];
1640
1784
  };
@@ -1649,13 +1793,23 @@ const ObjectPattern = (c, scope, node) => {
1649
1793
  const { key } = prop;
1650
1794
  const keyInst = !prop.computed && key.type === "Identifier"
1651
1795
  ? [new LiteralValue(key.name), []]
1652
- : c.handleConsume(scope, prop.key);
1796
+ : c.handleEval(scope, prop.key);
1653
1797
  inst.push(...keyInst[1]);
1654
- const valueInst = c.handle(scope, prop.value);
1655
- if (!valueInst[0])
1798
+ const [value, valueInst] = c.handle(scope, prop.value);
1799
+ if (!value)
1656
1800
  throw new CompilerError("Destructuring member must resolve to a value", prop.value);
1657
- inst.push(...valueInst[1]);
1658
- members.set(keyInst[0], valueInst[0]);
1801
+ inst.push(...valueInst);
1802
+ members.set(keyInst[0], {
1803
+ value,
1804
+ handler(get) {
1805
+ return c.handle(scope, prop, () => {
1806
+ const inst = get();
1807
+ // assigns the output to the target value
1808
+ inst[1].push(...value["="](scope, inst[0])[1]);
1809
+ return inst;
1810
+ });
1811
+ },
1812
+ });
1659
1813
  }
1660
1814
  return [new DestructuringValue(members), inst];
1661
1815
  };
@@ -1700,7 +1854,7 @@ const TSEnumDeclaration = (c, scope, node) => {
1700
1854
  : [new LiteralValue(counter)];
1701
1855
  if (!(value instanceof LiteralValue))
1702
1856
  throw new CompilerError("Enum members must contain literal values", member);
1703
- if (typeof value.data === "number") {
1857
+ if (value.isNumber()) {
1704
1858
  lastType = "number";
1705
1859
  counter = value.data + 1;
1706
1860
  }
@@ -1710,13 +1864,9 @@ const TSEnumDeclaration = (c, scope, node) => {
1710
1864
  const name = member.id.type === "Identifier" ? member.id.name : member.id.value;
1711
1865
  data[name] = value;
1712
1866
  }
1713
- scope.set(new ValueOwner({
1714
- scope,
1715
- value: new ObjectValue(data),
1716
- constant: true,
1717
- identifier: node.id.name,
1718
- name: nodeName(node, !c.compactNames && node.id.name),
1719
- }));
1867
+ const value = new ObjectValue(data);
1868
+ value.name = nodeName(node, !c.compactNames && node.id.name);
1869
+ scope.set(node.id.name, value);
1720
1870
  return [null, []];
1721
1871
  };
1722
1872
  const TSNonNullExpression = (c, scope, node) => {
@@ -1780,15 +1930,12 @@ class MacroFunction extends VoidValue {
1780
1930
  this.mutability = EMutability.constant;
1781
1931
  this.fn = fn;
1782
1932
  }
1783
- call(scope, args) {
1784
- return this.fn.apply(this, [scope, ...args]);
1933
+ call(scope, args, out) {
1934
+ return this.fn.apply(this, [scope, out, ...args]);
1785
1935
  }
1786
1936
  eval(_scope) {
1787
1937
  return [this, []];
1788
1938
  }
1789
- consume(_scope) {
1790
- return [this, []];
1791
- }
1792
1939
  }
1793
1940
 
1794
1941
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
@@ -1812,49 +1959,39 @@ const mathOperations = {
1812
1959
  sqrt: a => Math.sqrt(a),
1813
1960
  rand: null,
1814
1961
  };
1815
- function mathConstant(scope, name) {
1816
- return new ValueOwner({
1817
- scope,
1818
- constant: true,
1819
- name,
1820
- value: assign(new StoreValue(scope), {
1821
- mutability: EMutability.constant,
1822
- }),
1823
- }).value;
1824
- }
1825
- function createMacroMathOperations(scope) {
1962
+ function createMacroMathOperations() {
1826
1963
  const macroMathOperations = {
1827
- PI: mathConstant(scope, "@pi"),
1828
- E: mathConstant(scope, "@e"),
1829
- degToRad: mathConstant(scope, "@degToRad"),
1830
- radToDeg: mathConstant(scope, "@radToDeg"),
1964
+ PI: new StoreValue("@pi", EMutability.constant),
1965
+ E: new StoreValue("@e", EMutability.constant),
1966
+ degToRad: new StoreValue("@degToRad", EMutability.constant),
1967
+ radToDeg: new StoreValue("@radToDeg", EMutability.constant),
1831
1968
  };
1832
1969
  for (const key in mathOperations) {
1833
1970
  const fn = mathOperations[key];
1834
- macroMathOperations[key] = new MacroFunction((scope, a, b) => {
1971
+ macroMathOperations[key] = new MacroFunction((scope, out, a, b) => {
1835
1972
  if (b) {
1836
1973
  if (fn && a instanceof LiteralValue && b instanceof LiteralValue) {
1837
- if (typeof a.data !== "number" || typeof b.data !== "number")
1974
+ if (!a.isNumber() || !b.isNumber())
1838
1975
  throw new CompilerError("Cannot do math operation with non-numerical literals.");
1839
1976
  return [new LiteralValue(fn(a.num, b.num)), []];
1840
1977
  }
1841
- const temp = new StoreValue(scope);
1978
+ const temp = StoreValue.from(scope, out);
1842
1979
  return [temp, [new OperationInstruction(key, temp, a, b)]];
1843
1980
  }
1844
1981
  if (fn && a instanceof LiteralValue) {
1845
- if (typeof a.data !== "number")
1982
+ if (!a.isNumber())
1846
1983
  throw new CompilerError("Cannot do math operation with non-numerical literal.");
1847
1984
  return [new LiteralValue(fn(a.num)), []];
1848
1985
  }
1849
- const temp = new StoreValue(scope);
1986
+ const temp = StoreValue.from(scope, out);
1850
1987
  return [temp, [new OperationInstruction(key, temp, a, b)]];
1851
1988
  });
1852
1989
  }
1853
1990
  return macroMathOperations;
1854
1991
  }
1855
1992
  class MlogMath extends ObjectValue {
1856
- constructor(scope) {
1857
- super(createMacroMathOperations(scope));
1993
+ constructor() {
1994
+ super(createMacroMathOperations());
1858
1995
  }
1859
1996
  }
1860
1997
  // op angle result a b
@@ -1862,7 +1999,7 @@ class MlogMath extends ObjectValue {
1862
1999
 
1863
2000
  class Concat extends MacroFunction {
1864
2001
  constructor() {
1865
- super((scope, ...values) => {
2002
+ super((scope, out, ...values) => {
1866
2003
  for (const value of values)
1867
2004
  if (!(value instanceof LiteralValue))
1868
2005
  throw new CompilerError("Concat arguments must all be literal values.");
@@ -1874,66 +2011,19 @@ class Concat extends MacroFunction {
1874
2011
  }
1875
2012
  }
1876
2013
 
1877
- /** Asserts that `value` is a `LiteralValue` that contains a string */
1878
- function assertStringLiteral(value, name) {
1879
- if (!(value instanceof LiteralValue) || typeof value.data !== "string")
1880
- throw new CompilerError(`${name} must be a string literal`);
1881
- }
1882
- /** Asserts that `value` is a string literal that is part of a given set of `members`*/
1883
- function assertLiteralOneOf(value, members, name) {
1884
- assertStringLiteral(value, name);
1885
- if (!members.includes(value.data))
1886
- throw new CompilerError(`${name} must be one of: ${members
1887
- .slice(0, -1)
1888
- .map(member => `"${member}"`)
1889
- .join(", ")} or "${members[members.length - 1]}"`);
1890
- }
1891
- /** Asserts that `value` is either a store or a literal */
1892
- function assertIsRuntimeValue(value, name) {
1893
- if (!(value instanceof LiteralValue) && !(value instanceof StoreValue))
1894
- throw new CompilerError(`${name} must be a valid runtime value`);
1895
- }
1896
- /** Asserts that `value` is an `ObjectValue` */
1897
- function assertIsObjectMacro(value, name) {
1898
- if (!(value instanceof ObjectValue))
1899
- throw new CompilerError(`${name} must be an object macro`);
1900
- }
1901
- /** Asserts that `value` is an `ObjectValue` that has a length property */
1902
- function assertIsArrayMacro(value, name) {
1903
- if (!(value instanceof ObjectValue) || !value.data.length)
1904
- throw new CompilerError(`${name} must be an array macro`);
1905
- }
1906
- /** Asserts that all of the fields described are present on `value`*/
1907
- function assertObjectFields(value, fields) {
1908
- const result = [];
1909
- for (const field of fields) {
1910
- const param = typeof field === "object" ? field : { key: field };
1911
- const item = value.data[param.key];
1912
- if (item) {
1913
- param.validate?.(item);
1914
- result.push(item);
1915
- continue;
1916
- }
1917
- if (param.default == undefined)
1918
- throw new CompilerError(`The field "${param.name ?? param.key}" must be present`);
1919
- result.push(param.default);
1920
- }
1921
- return result;
1922
- }
1923
-
1924
2014
  /** Used to create namespaces that contain multiple methods that map into instructions */
1925
2015
  function createOverloadNamespace({ overloads, handler, }) {
1926
2016
  const result = {};
1927
2017
  for (const key in overloads) {
1928
2018
  const { named, args } = overloads[key];
1929
2019
  if (named) {
1930
- result[key] = new MacroFunction((scope, options) => {
2020
+ result[key] = new MacroFunction((scope, out, options) => {
1931
2021
  assertIsObjectMacro(options, named);
1932
- return handler(scope, key, ...assertObjectFields(options, args));
2022
+ return handler(scope, key, out, ...assertObjectFields(options, args));
1933
2023
  });
1934
2024
  continue;
1935
2025
  }
1936
- result[key] = new MacroFunction((scope, ...params) => {
2026
+ result[key] = new MacroFunction((scope, out, ...params) => {
1937
2027
  const handlerParams = [];
1938
2028
  // validate the paramters
1939
2029
  for (let i = 0; i < args.length; i++) {
@@ -1950,7 +2040,7 @@ function createOverloadNamespace({ overloads, handler, }) {
1950
2040
  arg.validate?.(param);
1951
2041
  handlerParams.push(param);
1952
2042
  }
1953
- return handler(scope, key, ...handlerParams);
2043
+ return handler(scope, key, out, ...handlerParams);
1954
2044
  });
1955
2045
  }
1956
2046
  return result;
@@ -1995,7 +2085,7 @@ class Draw extends ObjectValue {
1995
2085
  args: ["x", "y", "image", "size", "rotation"],
1996
2086
  },
1997
2087
  },
1998
- handler(scope, overload, ...args) {
2088
+ handler(scope, overload, out, ...args) {
1999
2089
  return [null, [new InstructionBase("draw", overload, ...args)]];
2000
2090
  },
2001
2091
  });
@@ -2005,7 +2095,7 @@ class Draw extends ObjectValue {
2005
2095
 
2006
2096
  class Print extends MacroFunction {
2007
2097
  constructor() {
2008
- super((scope, ...values) => {
2098
+ super((scope, out, ...values) => {
2009
2099
  const [first] = values;
2010
2100
  const inst = [];
2011
2101
  if (!isTemplateObjectArray(first)) {
@@ -2034,7 +2124,7 @@ class Print extends MacroFunction {
2034
2124
  const defaultTargetName$1 = "message1";
2035
2125
  class PrintFlush extends MacroFunction {
2036
2126
  constructor() {
2037
- super((scope, target) => {
2127
+ super((scope, out, target) => {
2038
2128
  if (!target)
2039
2129
  return [null, [new InstructionBase("printflush", defaultTargetName$1)]];
2040
2130
  if (!(target instanceof SenseableValue))
@@ -2047,7 +2137,7 @@ class PrintFlush extends MacroFunction {
2047
2137
  const defaultTargetName = "display1";
2048
2138
  class DrawFlush extends MacroFunction {
2049
2139
  constructor() {
2050
- super((scope, target) => {
2140
+ super((scope, out, target) => {
2051
2141
  if (!target)
2052
2142
  return [null, [new InstructionBase("drawflush", defaultTargetName)]];
2053
2143
  if (!(target instanceof SenseableValue))
@@ -2066,10 +2156,10 @@ class GetBlock extends ObjectValue {
2066
2156
  block: { args: ["x", "y"] },
2067
2157
  building: { args: ["x", "y"] },
2068
2158
  },
2069
- handler(scope, overload, x, y) {
2159
+ handler(scope, overload, out, x, y) {
2070
2160
  const output = overload === "building"
2071
- ? new SenseableValue(scope)
2072
- : new StoreValue(scope);
2161
+ ? SenseableValue.from(scope, out, EMutability.constant)
2162
+ : StoreValue.from(scope, out, EMutability.constant);
2073
2163
  return [
2074
2164
  output,
2075
2165
  [new InstructionBase("getblock", overload, output, x, y)],
@@ -2082,11 +2172,11 @@ class GetBlock extends ObjectValue {
2082
2172
 
2083
2173
  class GetLink extends MacroFunction {
2084
2174
  constructor() {
2085
- super((scope, index) => {
2175
+ super((scope, out, index) => {
2086
2176
  if (!(index instanceof StoreValue) &&
2087
- (!(index instanceof LiteralValue) || typeof index.data !== "number"))
2177
+ (!(index instanceof LiteralValue) || !index.isNumber()))
2088
2178
  throw new CompilerError("The getlink index must be a number literal or a store");
2089
- const outBuild = new SenseableValue(scope);
2179
+ const outBuild = SenseableValue.from(scope, out, EMutability.constant);
2090
2180
  return [outBuild, [new InstructionBase("getlink", outBuild, index)]];
2091
2181
  });
2092
2182
  }
@@ -2108,11 +2198,10 @@ class Control extends ObjectValue {
2108
2198
  config: { args: ["building", "value"] },
2109
2199
  color: { args: ["building", "r", "g", "b"] },
2110
2200
  },
2111
- handler(scope, overload, ...args) {
2201
+ handler(scope, overload, out, ...args) {
2112
2202
  if (overload !== "color")
2113
2203
  return [null, [new InstructionBase("control", overload, ...args)]];
2114
- const temp = new StoreValue(scope);
2115
- temp.ensureOwned();
2204
+ const temp = StoreValue.from(scope);
2116
2205
  // this "patch" is here for compatibility reasons
2117
2206
  // I don't want to add another breaking change
2118
2207
  // TODO: figure out if this patch should be removed later
@@ -2148,14 +2237,14 @@ const validRadarSorts = [
2148
2237
  ];
2149
2238
  class Radar extends MacroFunction {
2150
2239
  constructor() {
2151
- super((scope, options) => {
2240
+ super((scope, out, options) => {
2152
2241
  assertIsObjectMacro(options, "The radar options");
2153
2242
  const { building, filters, order, sort } = options.data;
2154
2243
  if (!(building instanceof SenseableValue))
2155
2244
  throw new CompilerError("The building must a senseable value");
2156
2245
  assertIsArrayMacro(filters, "filters");
2157
2246
  const { length } = filters.data;
2158
- if (!(length instanceof LiteralValue) || typeof length.data !== "number")
2247
+ if (!(length instanceof LiteralValue) || !length.isNumber())
2159
2248
  throw new CompilerError("The length of an array macro must be");
2160
2249
  if (length.data !== 3)
2161
2250
  throw new CompilerError("The filters array must have 3 items");
@@ -2167,7 +2256,7 @@ class Radar extends MacroFunction {
2167
2256
  if (!(order instanceof LiteralValue || order instanceof StoreValue))
2168
2257
  throw new CompilerError("The radar order must be a literal or a store");
2169
2258
  assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
2170
- const outUnit = new SenseableValue(scope);
2259
+ const outUnit = SenseableValue.from(scope, out, EMutability.constant);
2171
2260
  return [
2172
2261
  outUnit,
2173
2262
  [
@@ -2180,14 +2269,12 @@ class Radar extends MacroFunction {
2180
2269
 
2181
2270
  class Sensor extends MacroFunction {
2182
2271
  constructor() {
2183
- super((scope, property, target) => {
2272
+ super((scope, out, property, target) => {
2184
2273
  if (!(property instanceof StoreValue))
2185
2274
  throw new CompilerError("The sensor property must be a store");
2186
2275
  if (!(target instanceof SenseableValue))
2187
2276
  throw new CompilerError("The sensor target must be a senseable value");
2188
- const temp = assign(new SenseableValue(scope), {
2189
- mutability: EMutability.readonly,
2190
- });
2277
+ const temp = SenseableValue.from(scope, out, EMutability.readonly);
2191
2278
  return [temp, [new InstructionBase("sensor", temp, target, property)]];
2192
2279
  });
2193
2280
  }
@@ -2195,9 +2282,9 @@ class Sensor extends MacroFunction {
2195
2282
 
2196
2283
  class Wait extends MacroFunction {
2197
2284
  constructor() {
2198
- super((scope, seconds) => {
2285
+ super((scope, out, seconds) => {
2199
2286
  if (!(seconds instanceof StoreValue) &&
2200
- !(seconds instanceof LiteralValue && typeof seconds.data === "number"))
2287
+ !(seconds instanceof LiteralValue && seconds.isNumber()))
2201
2288
  throw new CompilerError("The wait seconds must be either a number literal or a store");
2202
2289
  return [null, [new InstructionBase("wait", seconds)]];
2203
2290
  });
@@ -2213,8 +2300,8 @@ class Lookup extends ObjectValue {
2213
2300
  item: { args: ["index"] },
2214
2301
  liquid: { args: ["index"] },
2215
2302
  },
2216
- handler(scope, overload, index) {
2217
- const output = new StoreValue(scope);
2303
+ handler(scope, overload, out, index) {
2304
+ const output = StoreValue.from(scope, out);
2218
2305
  return [
2219
2306
  output,
2220
2307
  [new InstructionBase("lookup", overload, output, index)],
@@ -2227,15 +2314,15 @@ class Lookup extends ObjectValue {
2227
2314
 
2228
2315
  class PackColor extends MacroFunction {
2229
2316
  constructor() {
2230
- super((scope, ...args) => {
2317
+ super((scope, out, ...args) => {
2231
2318
  if (args.length !== 4) {
2232
2319
  throw new CompilerError(`Expected 4 arguments, received ${args.length}`);
2233
2320
  }
2234
2321
  if (!args.every((value) => value instanceof StoreValue ||
2235
- (value instanceof LiteralValue && typeof value.data === "number"))) {
2322
+ (value instanceof LiteralValue && value.isNumber()))) {
2236
2323
  throw new CompilerError("packColor arguments must be either stores or number literals");
2237
2324
  }
2238
- const output = new StoreValue(scope);
2325
+ const output = StoreValue.from(scope, out);
2239
2326
  return [output, [new InstructionBase("packcolor", output, ...args)]];
2240
2327
  });
2241
2328
  }
@@ -2258,7 +2345,7 @@ class SetBlock extends ObjectValue {
2258
2345
  args: ["x", "y", "to", "team", { key: "rotation", default: "0" }],
2259
2346
  },
2260
2347
  },
2261
- handler(scope, overload, x, y, to, team, rotation) {
2348
+ handler(scope, overload, out, x, y, to, team, rotation) {
2262
2349
  return [
2263
2350
  null,
2264
2351
  [
@@ -2273,14 +2360,14 @@ class SetBlock extends ObjectValue {
2273
2360
 
2274
2361
  class SpawnUnit extends MacroFunction {
2275
2362
  constructor() {
2276
- super((scope, options) => {
2363
+ super((scope, out, options) => {
2277
2364
  assertIsObjectMacro(options, "The options");
2278
2365
  const { type, x, y, team, rotation } = options.data;
2279
2366
  assertIsRuntimeValue(type, "type");
2280
2367
  assertIsRuntimeValue(x, "x");
2281
2368
  assertIsRuntimeValue(y, "y");
2282
2369
  assertIsRuntimeValue(team, "team");
2283
- const output = new SenseableValue(scope);
2370
+ const output = SenseableValue.from(scope, out, EMutability.constant);
2284
2371
  return [
2285
2372
  output,
2286
2373
  [
@@ -2299,7 +2386,7 @@ class Stop extends MacroFunction {
2299
2386
 
2300
2387
  class UnitBind extends MacroFunction {
2301
2388
  constructor() {
2302
- super((scope, type) => {
2389
+ super((scope, out, type) => {
2303
2390
  if (!(type instanceof StoreValue))
2304
2391
  throw new CompilerError("The unitBind type must be a store");
2305
2392
  return [null, [new InstructionBase("ubind", type)]];
@@ -2338,21 +2425,20 @@ class UnitControl extends ObjectValue {
2338
2425
  args: ["x", "y", "radius"],
2339
2426
  },
2340
2427
  },
2341
- handler(scope, overload, ...args) {
2428
+ handler(scope, overload, out, ...args) {
2342
2429
  let result = null;
2343
2430
  let extraArgs = [];
2344
2431
  switch (overload) {
2345
2432
  case "getBlock": {
2346
- const outType = new StoreValue(scope);
2347
- const outBuilding = new SenseableValue(scope);
2433
+ const outType = StoreValue.from(scope, extractDestrucuringOut(out, 0));
2434
+ const outBuilding = SenseableValue.from(scope, extractDestrucuringOut(out, 1));
2348
2435
  result = ObjectValue.fromArray([outType, outBuilding]);
2349
2436
  extraArgs = [outType, outBuilding, new LiteralValue(0)];
2350
2437
  break;
2351
2438
  }
2352
2439
  case "within": {
2353
- const temp = new StoreValue(scope);
2354
- result = temp;
2355
- extraArgs = [temp, new LiteralValue(0)];
2440
+ result = StoreValue.from(scope, out);
2441
+ extraArgs = [result, new LiteralValue(0)];
2356
2442
  break;
2357
2443
  }
2358
2444
  }
@@ -2368,12 +2454,12 @@ class UnitControl extends ObjectValue {
2368
2454
 
2369
2455
  class UnitRadar extends MacroFunction {
2370
2456
  constructor() {
2371
- super((scope, options) => {
2457
+ super((scope, out, options) => {
2372
2458
  assertIsObjectMacro(options, "The radar options");
2373
2459
  const { filters, order, sort } = options.data;
2374
2460
  assertIsArrayMacro(filters, "filters");
2375
2461
  const { length } = filters.data;
2376
- if (!(length instanceof LiteralValue) || typeof length.data !== "number")
2462
+ if (!(length instanceof LiteralValue) || !length.isNumber())
2377
2463
  throw new CompilerError("The length of an array macro must be");
2378
2464
  if (length.data !== 3)
2379
2465
  throw new CompilerError("The filters array must have 3 items");
@@ -2385,7 +2471,7 @@ class UnitRadar extends MacroFunction {
2385
2471
  if (!(order instanceof LiteralValue || order instanceof StoreValue))
2386
2472
  throw new CompilerError("The radar order must be a literal or a store");
2387
2473
  assertLiteralOneOf(sort, validRadarSorts, "The radar sort");
2388
- const outUnit = new SenseableValue(scope);
2474
+ const outUnit = SenseableValue.from(scope, out, EMutability.constant);
2389
2475
  return [
2390
2476
  outUnit,
2391
2477
  [
@@ -2423,11 +2509,11 @@ class UnitLocate extends ObjectValue {
2423
2509
  args: [],
2424
2510
  },
2425
2511
  },
2426
- handler(scope, overload, ...args) {
2427
- const outFound = new StoreValue(scope);
2428
- const outX = new StoreValue(scope);
2429
- const outY = new StoreValue(scope);
2430
- const outBuilding = new SenseableValue(scope);
2512
+ handler(scope, overload, out, ...args) {
2513
+ const outFound = StoreValue.from(scope, extractDestrucuringOut(out, 0));
2514
+ const outX = StoreValue.from(scope, extractDestrucuringOut(out, 1));
2515
+ const outY = StoreValue.from(scope, extractDestrucuringOut(out, 2));
2516
+ const outBuilding = SenseableValue.from(scope, overload === "ore" ? discardedName : extractDestrucuringOut(out, 3));
2431
2517
  const outArgs = [outX, outY, outFound, outBuilding];
2432
2518
  let inputArgs = [];
2433
2519
  switch (overload) {
@@ -2490,7 +2576,7 @@ class ApplyStatus extends ObjectValue {
2490
2576
  },
2491
2577
  clear: { args: ["status", "unit"] },
2492
2578
  },
2493
- handler(scope, overload, effect, unit, duration) {
2579
+ handler(scope, overload, out, effect, unit, duration) {
2494
2580
  if (!(effect instanceof LiteralValue))
2495
2581
  throw new CompilerError("The status effect must be a string literal");
2496
2582
  assertLiteralOneOf(effect, validEffects, "The status effect");
@@ -2508,7 +2594,7 @@ class ApplyStatus extends ObjectValue {
2508
2594
 
2509
2595
  class SpawnWave extends MacroFunction {
2510
2596
  constructor() {
2511
- super((scope, natural, x, y) => {
2597
+ super((scope, out, natural, x, y) => {
2512
2598
  if (!(natural instanceof LiteralValue) ||
2513
2599
  (natural.data !== 1 && natural.data !== 0))
2514
2600
  throw new CompilerError("The 'natural' argument must be a boolean literal");
@@ -2549,7 +2635,7 @@ class SetRule extends ObjectValue {
2549
2635
  rtsMinWeight: { args: ["team", "value"] },
2550
2636
  rtsMinSquad: { args: ["team", "value"] },
2551
2637
  },
2552
- handler(scope, overload, ...args) {
2638
+ handler(scope, overload, out, ...args) {
2553
2639
  const params = ["10", "0", "0", "100", "100"];
2554
2640
  switch (overload) {
2555
2641
  case "mapArea": {
@@ -2592,7 +2678,7 @@ class FlushMessage extends ObjectValue {
2592
2678
  announce: { args: ["duration"] },
2593
2679
  toast: { args: ["duration"] },
2594
2680
  },
2595
- handler(scope, overload, duration) {
2681
+ handler(scope, overload, out, duration) {
2596
2682
  return [null, [new InstructionBase("message", overload, duration)]];
2597
2683
  },
2598
2684
  });
@@ -2608,7 +2694,7 @@ class Cutscene extends ObjectValue {
2608
2694
  zoom: { args: ["level"] },
2609
2695
  stop: { args: [] },
2610
2696
  },
2611
- handler(scope, overload, ...args) {
2697
+ handler(scope, overload, out, ...args) {
2612
2698
  const params = Object.assign(["100", "100", "0.06", "0"], args);
2613
2699
  return [null, [new InstructionBase("cutscene", overload, ...params)]];
2614
2700
  },
@@ -2619,7 +2705,7 @@ class Cutscene extends ObjectValue {
2619
2705
 
2620
2706
  class Explosion extends MacroFunction {
2621
2707
  constructor() {
2622
- super((scope, options) => {
2708
+ super((scope, out, options) => {
2623
2709
  assertIsObjectMacro(options, "The options");
2624
2710
  const params = assertObjectFields(options, [
2625
2711
  "team",
@@ -2638,7 +2724,7 @@ class Explosion extends MacroFunction {
2638
2724
 
2639
2725
  class SetRate extends MacroFunction {
2640
2726
  constructor() {
2641
- super((scope, ipt) => [null, [new InstructionBase("setrate", ipt)]]);
2727
+ super((scope, out, ipt) => [null, [new InstructionBase("setrate", ipt)]]);
2642
2728
  }
2643
2729
  }
2644
2730
 
@@ -2655,8 +2741,8 @@ class Fetch extends ObjectValue {
2655
2741
  build: { args: ["team", "index", "block"] },
2656
2742
  buildCount: { args: ["team", "block"] },
2657
2743
  },
2658
- handler(scope, overload, team, ...rest) {
2659
- const output = new SenseableValue(scope);
2744
+ handler(scope, overload, out, team, ...rest) {
2745
+ const output = SenseableValue.from(scope, out, EMutability.constant);
2660
2746
  const params = ["0", "@conveyor"];
2661
2747
  if (overload === "buildCount") {
2662
2748
  params[1] = rest[0];
@@ -2676,8 +2762,8 @@ class Fetch extends ObjectValue {
2676
2762
 
2677
2763
  class GetFlag extends MacroFunction {
2678
2764
  constructor() {
2679
- super((scope, flag) => {
2680
- const result = new StoreValue(scope);
2765
+ super((scope, out, flag) => {
2766
+ const result = StoreValue.from(scope, out);
2681
2767
  return [result, [new InstructionBase("getflag", result, flag)]];
2682
2768
  });
2683
2769
  }
@@ -2685,7 +2771,7 @@ class GetFlag extends MacroFunction {
2685
2771
 
2686
2772
  class SetFlag extends MacroFunction {
2687
2773
  constructor() {
2688
- super((scope, flag, value) => {
2774
+ super((scope, out, flag, value) => {
2689
2775
  if (!flag)
2690
2776
  throw new CompilerError("Missing flag argument");
2691
2777
  if (!value)
@@ -2704,28 +2790,17 @@ class MemoryEntry extends BaseValue {
2704
2790
  this.macro = true;
2705
2791
  this.store = null;
2706
2792
  }
2707
- eval(scope) {
2793
+ eval(scope, out) {
2708
2794
  if (this.store)
2709
2795
  return [this.store, []];
2710
- const temp = new StoreValue(scope);
2796
+ const temp = (this.store = StoreValue.from(scope, out));
2711
2797
  return [
2712
2798
  temp,
2713
2799
  [new InstructionBase("read", temp, this.mem.cell, this.prop)],
2714
2800
  ];
2715
2801
  }
2716
- consume(scope) {
2717
- if (this.store)
2718
- return [this.store, []];
2719
- this.ensureOwned();
2720
- this.store = new StoreValue(scope);
2721
- this.owner.own(this.store);
2722
- return [
2723
- this.store,
2724
- [new InstructionBase("read", this.store, this.mem.cell, this.prop)],
2725
- ];
2726
- }
2727
2802
  "="(scope, value) {
2728
- const [data, dataInst] = value.consume(scope);
2803
+ const [data, dataInst] = value.eval(scope);
2729
2804
  return [
2730
2805
  data,
2731
2806
  [
@@ -2734,26 +2809,17 @@ class MemoryEntry extends BaseValue {
2734
2809
  ],
2735
2810
  ];
2736
2811
  }
2737
- ensureOwned() {
2738
- this.owner ??= new ValueOwner({ scope: this.scope, value: this });
2739
- }
2740
- }
2741
- for (const operator of operators) {
2742
- if (operator === "=")
2743
- continue;
2744
- MemoryEntry.prototype[operator] = function (...args) {
2745
- // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-unsafe-return
2746
- return BaseValue.prototype[operator].apply(this, args);
2747
- };
2748
2812
  }
2749
2813
  class MemoryMacro extends ObjectValue {
2750
2814
  constructor(cell, size) {
2751
2815
  super({
2752
- $get: new MacroFunction((scope, prop) => {
2753
- if (prop instanceof LiteralValue && typeof prop.data !== "number")
2816
+ $get: new MacroFunction((scope, out, prop) => {
2817
+ if (prop instanceof LiteralValue && !prop.isNumber())
2754
2818
  throw new CompilerError(`Invalid memory object property: "${prop.data}"`);
2755
- const obj = new MemoryEntry(scope, this, prop);
2756
- return [obj, []];
2819
+ const entry = new MemoryEntry(scope, this, prop);
2820
+ if (out)
2821
+ return entry.eval(scope, out);
2822
+ return [entry, []];
2757
2823
  }),
2758
2824
  length: size,
2759
2825
  size,
@@ -2767,10 +2833,10 @@ class MemoryMacro extends ObjectValue {
2767
2833
  class MemoryBuilder extends ObjectValue {
2768
2834
  constructor() {
2769
2835
  super({
2770
- $call: new MacroFunction((scope, cell, size = new LiteralValue(64)) => {
2836
+ $call: new MacroFunction((scope, out, cell, size = new LiteralValue(64)) => {
2771
2837
  if (!(cell instanceof SenseableValue))
2772
2838
  throw new CompilerError("Memory cell must be a senseable value.");
2773
- if (!(size instanceof LiteralValue && typeof size.data === "number"))
2839
+ if (!(size instanceof LiteralValue && size.isNumber()))
2774
2840
  throw new CompilerError("The memory size must be a number literal.");
2775
2841
  return [new MemoryMacro(cell, size), []];
2776
2842
  }),
@@ -2782,8 +2848,8 @@ const dynamicVars = ["time", "tick"];
2782
2848
  class NamespaceMacro extends ObjectValue {
2783
2849
  constructor({ changeCasing = false } = {}) {
2784
2850
  super({
2785
- $get: new MacroFunction((scope, prop) => {
2786
- if (!(prop instanceof LiteralValue) || typeof prop.data !== "string")
2851
+ $get: new MacroFunction((scope, out, prop) => {
2852
+ if (!(prop instanceof LiteralValue) || !prop.isString())
2787
2853
  throw new CompilerError("Cannot use dynamic properties on namespace macros");
2788
2854
  const symbolName = this.changeCasing
2789
2855
  ? camelToDashCase(prop.data)
@@ -2791,57 +2857,37 @@ class NamespaceMacro extends ObjectValue {
2791
2857
  const mutability = dynamicVars.includes(symbolName)
2792
2858
  ? EMutability.readonly
2793
2859
  : EMutability.constant;
2794
- const owner = new ValueOwner({
2795
- scope,
2796
- value: assign(new StoreValue(scope), {
2797
- mutability,
2798
- }),
2799
- name: `@${symbolName}`,
2800
- });
2801
- return [owner.value, []];
2860
+ const result = new StoreValue(`@${symbolName}`, mutability);
2861
+ return [result, []];
2802
2862
  }),
2803
2863
  });
2804
2864
  this.changeCasing = changeCasing;
2805
2865
  }
2806
2866
  }
2807
2867
  class VarsNamespace extends NamespaceMacro {
2808
- constructor(scope) {
2868
+ constructor() {
2809
2869
  super();
2810
2870
  Object.assign(this.data, {
2811
- unit: new ValueOwner({
2812
- scope,
2813
- name: "@unit",
2814
- value: new SenseableValue(scope),
2815
- }).value,
2816
- this: new ValueOwner({
2817
- scope,
2818
- name: "@this",
2819
- value: new SenseableValue(scope),
2820
- }).value,
2871
+ unit: new SenseableValue("@unit"),
2872
+ this: new SenseableValue("@this"),
2821
2873
  });
2822
2874
  }
2823
2875
  }
2824
2876
 
2825
2877
  class GetGlobal extends MacroFunction {
2826
2878
  constructor(mutability) {
2827
- super((scope, name) => {
2828
- if (!(name instanceof LiteralValue) || typeof name.data !== "string")
2879
+ super((scope, out, name) => {
2880
+ if (!(name instanceof LiteralValue) || !name.isString())
2829
2881
  throw new CompilerError("The name parameter must be a string literal.");
2830
- const owner = new ValueOwner({
2831
- scope,
2832
- name: name.data,
2833
- value: assign(new SenseableValue(scope), {
2834
- mutability,
2835
- }),
2836
- });
2837
- return [owner.value, []];
2882
+ const result = new SenseableValue(name.data, mutability);
2883
+ return [result, []];
2838
2884
  });
2839
2885
  }
2840
2886
  }
2841
2887
 
2842
2888
  class Asm extends MacroFunction {
2843
2889
  constructor() {
2844
- super((scope, stringsArray, ...values) => {
2890
+ super((scope, out, stringsArray, ...values) => {
2845
2891
  if (!isTemplateObjectArray(stringsArray))
2846
2892
  throw new CompilerError("Expected to receive a template strings array");
2847
2893
  const args = [];
@@ -2919,7 +2965,7 @@ function validateInstructionArgs(args) {
2919
2965
  function initScope(scope) {
2920
2966
  // namespaces
2921
2967
  scope.hardSet("ControlKind", new NamespaceMacro());
2922
- scope.hardSet("Vars", new VarsNamespace(scope));
2968
+ scope.hardSet("Vars", new VarsNamespace());
2923
2969
  scope.hardSet("Teams", new NamespaceMacro());
2924
2970
  scope.hardSet("Items", new NamespaceMacro({ changeCasing: true }));
2925
2971
  scope.hardSet("Liquids", new NamespaceMacro());
@@ -2931,7 +2977,7 @@ function initScope(scope) {
2931
2977
  scope.hardSet("getVar", new GetGlobal(EMutability.mutable));
2932
2978
  scope.hardSet("concat", new Concat());
2933
2979
  scope.hardSet("asm", new Asm());
2934
- scope.hardSet("Math", new MlogMath(scope));
2980
+ scope.hardSet("Math", new MlogMath());
2935
2981
  scope.hardSet("Memory", new MemoryBuilder());
2936
2982
  // commands
2937
2983
  scope.hardSet("draw", new Draw());
@@ -3023,11 +3069,11 @@ class Compiler {
3023
3069
  }
3024
3070
  return result;
3025
3071
  }
3026
- handle(scope, node, handler = this.handlers[node.type]) {
3072
+ handle(scope, node, handler = this.handlers[node.type], out) {
3027
3073
  try {
3028
3074
  if (!handler)
3029
3075
  throw new CompilerError("Missing handler for " + node.type);
3030
- const result = handler(this, scope, node, null);
3076
+ const result = handler(this, scope, node, out, null);
3031
3077
  if (this.sourcemap)
3032
3078
  return appendSourceLocations(result, node);
3033
3079
  return result;
@@ -3039,21 +3085,11 @@ class Compiler {
3039
3085
  throw error;
3040
3086
  }
3041
3087
  }
3042
- handleEval(scope, node) {
3043
- const [res, inst] = this.handle(scope, node);
3088
+ handleEval(scope, node, out) {
3089
+ const [res, inst] = this.handle(scope, node, undefined, out);
3044
3090
  if (!res)
3045
3091
  throw new CompilerError("Cannot eval null", node);
3046
- const [evaluated, evaluatedInst] = res.eval(scope);
3047
- const result = [evaluated, [...inst, ...evaluatedInst]];
3048
- if (this.sourcemap)
3049
- return appendSourceLocations(result, node);
3050
- return result;
3051
- }
3052
- handleConsume(scope, node) {
3053
- const [res, inst] = this.handle(scope, node);
3054
- if (!res)
3055
- throw new CompilerError("Cannot consume null", node);
3056
- const [evaluated, evaluatedInst] = res.consume(scope);
3092
+ const [evaluated, evaluatedInst] = res.eval(scope, out);
3057
3093
  const result = [evaluated, [...inst, ...evaluatedInst]];
3058
3094
  if (this.sourcemap)
3059
3095
  return appendSourceLocations(result, node);
@@ -3065,7 +3101,7 @@ class Compiler {
3065
3101
  * The usage of this method over a regular loop over an array of nodes
3066
3102
  * is only required if the code inside the loop generates
3067
3103
  * instructions that are not tracked by the compiler
3068
- * handler methods ({@link handle}, {@link handleEval}, {@link handleConsume}
3104
+ * handler methods ({@link handle}, {@link handleEval}
3069
3105
  * and {@link handleMany})
3070
3106
  *
3071
3107
  */
@@ -3078,30 +3114,6 @@ class Compiler {
3078
3114
  return [null, lines];
3079
3115
  }
3080
3116
  }
3081
- /** Removes jump instructions that point right to the next line */
3082
- function hideRedundantJumps(inst) {
3083
- for (let i = 0; i < inst.length; i++) {
3084
- const instruction = inst[i];
3085
- if (!(instruction instanceof JumpInstruction))
3086
- continue;
3087
- const { address } = instruction;
3088
- let searchIndex = i + 1;
3089
- let current = inst[searchIndex];
3090
- while (current.hidden) {
3091
- if (current instanceof AddressResolver &&
3092
- current.bonds.includes(address)) {
3093
- // only gets assigned if `current` is an adress resolver
3094
- // that resolves the jump adress
3095
- // and that is also right next to the jump,
3096
- // without any actual instructions in between
3097
- instruction.hidden = true;
3098
- break;
3099
- }
3100
- searchIndex++;
3101
- current = inst[searchIndex];
3102
- }
3103
- }
3104
- }
3105
3117
  function* hoistedFunctionNodes(nodes) {
3106
3118
  // sorting is O(n long n) while this is just O(n)
3107
3119
  // besides, it's better not to modify the node array