mlogjs 0.4.6 → 0.4.8

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