mlogjs 0.4.8 → 0.4.10

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
@@ -146,7 +146,19 @@ const unaryOperators = [
146
146
  ];
147
147
  const updateOperators = ["++", "--"];
148
148
  const singleOperators = [...unaryOperators, ...updateOperators];
149
- const operators = [...leftRightOperators, ...singleOperators];
149
+ const operators = [...leftRightOperators, ...singleOperators];
150
+ const orderIndependentOperators = [
151
+ "!=",
152
+ "!==",
153
+ "&&",
154
+ "&",
155
+ "*",
156
+ "+",
157
+ "==",
158
+ "===",
159
+ "|",
160
+ "||",
161
+ ];
150
162
 
151
163
  class VoidValue {
152
164
  constructor() {
@@ -177,18 +189,18 @@ for (const key of operators) {
177
189
  }
178
190
 
179
191
  class InstructionBase {
180
- constructor(...args) {
181
- this.intent = EInstIntent.none;
182
- this.alwaysRuns = true;
183
- this._hidden = false;
184
- this.args = args;
185
- }
186
192
  get hidden() {
187
193
  return this._hidden;
188
194
  }
189
195
  set hidden(value) {
190
196
  this._hidden = value;
191
197
  }
198
+ constructor(...args) {
199
+ this.intent = EInstIntent.none;
200
+ this.alwaysRuns = true;
201
+ this._hidden = false;
202
+ this.args = args;
203
+ }
192
204
  resolve(_i) { }
193
205
  toString() {
194
206
  return this.args.filter(arg => arg).join(" ");
@@ -262,6 +274,11 @@ function camelToDashCase(name) {
262
274
  // eslint-disable-next-line @typescript-eslint/ban-types
263
275
  function assign(obj, props) {
264
276
  return Object.assign(obj, props);
277
+ }
278
+ /** Lazily computes and caches an expression */
279
+ function lazy(fn) {
280
+ let value;
281
+ return () => (value ??= fn());
265
282
  }
266
283
 
267
284
  function appendSourceLocations(valueInst, node) {
@@ -401,14 +418,14 @@ class EndInstruction extends InstructionBase {
401
418
  }
402
419
 
403
420
  class AddressResolver extends InstructionBase {
404
- constructor(...bonds) {
405
- super();
406
- this.bonds = bonds;
407
- }
408
421
  get hidden() {
409
422
  return true;
410
423
  }
411
424
  set hidden(value) { }
425
+ constructor(...bonds) {
426
+ super();
427
+ this.bonds = bonds;
428
+ }
412
429
  resolve(i) {
413
430
  for (const literal of this.bonds)
414
431
  literal.data = i;
@@ -524,12 +541,16 @@ class BaseValue extends VoidValue {
524
541
  ],
525
542
  ];
526
543
  }
544
+ "!=="(scope, other, out) {
545
+ const [equal, equalInst] = this["==="](scope, other);
546
+ const [result, resultInst] = equal["!"](scope, out);
547
+ return [result, [...equalInst, ...resultInst]];
548
+ }
527
549
  }
528
550
  const operatorMap$1 = {
529
551
  "==": "equal",
530
552
  "===": "strictEqual",
531
553
  "!=": "notEqual",
532
- "!==": "notEqual",
533
554
  "<": "lessThan",
534
555
  ">": "greaterThan",
535
556
  "<=": "lessThanEq",
@@ -616,6 +637,7 @@ class LiteralValue extends BaseValue {
616
637
  super();
617
638
  this.mutability = EMutability.constant;
618
639
  this.data = data;
640
+ this.name = this.toString();
619
641
  }
620
642
  eval(_scope) {
621
643
  return [this, []];
@@ -739,12 +761,40 @@ class StoreValue extends BaseValue {
739
761
  const [evalValue, evalInst] = value.eval(scope, this);
740
762
  return [this, [...evalInst, new SetInstruction(this, evalValue)]];
741
763
  }
764
+ "=="(scope, value, out) {
765
+ if (compareStores(this, value))
766
+ return [new LiteralValue(1), []];
767
+ return super["=="](scope, value, out);
768
+ }
769
+ "==="(scope, value, out) {
770
+ if (compareStores(this, value))
771
+ return [new LiteralValue(1), []];
772
+ return super["==="](scope, value, out);
773
+ }
774
+ "!="(scope, value, out) {
775
+ if (compareStores(this, value))
776
+ return [new LiteralValue(0), []];
777
+ return super["!="](scope, value, out);
778
+ }
779
+ "!=="(scope, value, out) {
780
+ if (compareStores(this, value))
781
+ return [new LiteralValue(0), []];
782
+ return super["!=="](scope, value, out);
783
+ }
742
784
  eval(_scope) {
743
785
  return [this, []];
744
786
  }
745
787
  toString() {
746
788
  return this.name;
747
789
  }
790
+ }
791
+ function compareStores(left, right) {
792
+ return (left === right ||
793
+ // prevents a store from being equal to a senseable value
794
+ (right instanceof left.constructor &&
795
+ right.mutability === EMutability.constant &&
796
+ left.mutability === EMutability.constant &&
797
+ right.name === left.name));
748
798
  }
749
799
 
750
800
  class ObjectValue extends VoidValue {
@@ -926,16 +976,18 @@ class LazyValue extends VoidValue {
926
976
  }
927
977
 
928
978
  class Scope {
929
- constructor(values, parent = null, stacked = false, ntemp = 0, name = "", inst = []) {
979
+ constructor(values, parent = null, stacked = false, ntemp = 0, name = "", inst = [], operationCache = {}, cacheDependencies = {}) {
930
980
  this.parent = parent;
931
981
  this.stacked = stacked;
932
982
  this.ntemp = ntemp;
933
983
  this.name = name;
934
984
  this.inst = inst;
985
+ this.operationCache = operationCache;
986
+ this.cacheDependencies = cacheDependencies;
935
987
  this.data = values;
936
988
  }
937
989
  copy() {
938
- const scope = new Scope({ ...this.data }, this.parent, this.stacked, this.ntemp, this.name, this.inst);
990
+ const scope = new Scope({ ...this.data }, this.parent, this.stacked, this.ntemp, this.name, this.inst, { ...this.operationCache }, { ...this.cacheDependencies });
939
991
  scope.break = this.break;
940
992
  scope.continue = this.continue;
941
993
  scope.function = this.function;
@@ -944,6 +996,8 @@ class Scope {
944
996
  createScope() {
945
997
  const scope = this.copy();
946
998
  scope.data = {};
999
+ scope.operationCache = {};
1000
+ scope.cacheDependencies = {};
947
1001
  scope.parent = this;
948
1002
  return scope;
949
1003
  }
@@ -989,6 +1043,46 @@ class Scope {
989
1043
  this.ntemp++;
990
1044
  return result;
991
1045
  }
1046
+ addCachedOperation(op, result, left, right) {
1047
+ if (!left.name || !result.name || (right && !right.name))
1048
+ return;
1049
+ const id = formatCacheId(op, left, right);
1050
+ this.operationCache[id] = result;
1051
+ this.clearDependentCache(result);
1052
+ addCacheDependency(this, left.name, id);
1053
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1054
+ if (right)
1055
+ addCacheDependency(this, right.name, id);
1056
+ addCacheDependency(this, result.name, id);
1057
+ }
1058
+ getCachedOperation(op, left, right) {
1059
+ const id = formatCacheId(op, left, right);
1060
+ const result = this.operationCache[id];
1061
+ if (result)
1062
+ return result;
1063
+ if (this.parent)
1064
+ return this.parent.getCachedOperation(op, left, right);
1065
+ }
1066
+ clearDependentCache(value) {
1067
+ if (!value.name)
1068
+ return;
1069
+ const dependents = this.cacheDependencies[value.name];
1070
+ if (dependents) {
1071
+ for (const id of dependents) {
1072
+ delete this.operationCache[id];
1073
+ }
1074
+ }
1075
+ if (this.parent)
1076
+ this.parent.clearDependentCache(value);
1077
+ }
1078
+ }
1079
+ function formatCacheId(op, left, right) {
1080
+ if (right)
1081
+ return `{${op}}{${left.name}}{${right.name}}`;
1082
+ return `{${op}}{${left.name}}`;
1083
+ }
1084
+ function addCacheDependency(scope, name, id) {
1085
+ (scope.cacheDependencies[name] ??= []).push(id);
992
1086
  }
993
1087
 
994
1088
  const Program = (c, scope, node) => {
@@ -1164,7 +1258,21 @@ const BooleanLiteral = (_c, scope, node) => [new LiteralValue(+node.value), []];
1164
1258
  const LRExpression = (c, scope, node, out) => {
1165
1259
  const [left, leftInst] = c.handleEval(scope, node.left);
1166
1260
  const [right, rightInst] = c.handleEval(scope, node.right);
1261
+ if (!(left instanceof LiteralValue) || !(right instanceof LiteralValue)) {
1262
+ const cachedResult = scope.getCachedOperation(node.operator, left, right);
1263
+ if (cachedResult)
1264
+ return [cachedResult, [...leftInst, ...rightInst]];
1265
+ // because a * b is the same as b * a
1266
+ if (orderIndependentOperators.includes(node.operator)) {
1267
+ const cachedResult = scope.getCachedOperation(node.operator, right, left);
1268
+ if (cachedResult)
1269
+ return [cachedResult, [...leftInst, ...rightInst]];
1270
+ }
1271
+ }
1167
1272
  const [op, opInst] = left[node.operator](scope, right, out);
1273
+ if (!(op instanceof LiteralValue)) {
1274
+ scope.addCachedOperation(node.operator, op, left, right);
1275
+ }
1168
1276
  return [op, [...leftInst, ...rightInst, ...opInst]];
1169
1277
  };
1170
1278
  const BinaryExpression = LRExpression;
@@ -1186,18 +1294,26 @@ const AssignmentExpression = (c, scope, node) => {
1186
1294
  ];
1187
1295
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1188
1296
  const [op, opInst] = left[node.operator](scope, right);
1297
+ if (left)
1298
+ scope.clearDependentCache(left);
1189
1299
  return [op, [...leftInst, ...rightInst, ...opInst]];
1190
1300
  };
1191
1301
  const UnaryExpression = (c, scope, { argument, operator }, out) => {
1192
1302
  const [arg, argInst] = c.handleEval(scope, argument);
1303
+ const cachedResult = scope.getCachedOperation(operator, arg);
1304
+ if (cachedResult)
1305
+ return [cachedResult, argInst];
1193
1306
  const operatorId = operator == "+" || operator == "-" ? `u${operator}` : operator;
1194
1307
  if (operatorId === "throw")
1195
1308
  throw new CompilerError("throw operator is not supported");
1196
1309
  const [op, opInst] = arg[operatorId](scope, out);
1310
+ scope.addCachedOperation(operator, op, arg);
1197
1311
  return [op, [...argInst, ...opInst]];
1198
1312
  };
1199
1313
  const UpdateExpression = (c, scope, { argument, operator, prefix }, out) => {
1200
1314
  const [arg, argInst] = c.handle(scope, argument);
1315
+ if (arg)
1316
+ scope.clearDependentCache(arg);
1201
1317
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1202
1318
  const [op, opInst] = arg[operator](scope, prefix, out);
1203
1319
  return [op, [...argInst, ...opInst]];
@@ -1228,6 +1344,17 @@ const ConditionalExpression = (c, scope, node, out) => {
1228
1344
  new AddressResolver(endExpressionAdress),
1229
1345
  ],
1230
1346
  ];
1347
+ };
1348
+ const SequenceExpression = (c, scope, node, out) => {
1349
+ const { expressions } = node;
1350
+ const inst = [];
1351
+ // compute every expression except the last one
1352
+ for (let i = 0; i < expressions.length - 1; i++) {
1353
+ const [, exprInst] = c.handleEval(scope, expressions[i], discardedName);
1354
+ inst.push(...exprInst);
1355
+ }
1356
+ const [value, valueInst] = c.handleEval(scope, expressions[expressions.length - 1], out);
1357
+ return [value, [...inst, ...valueInst]];
1231
1358
  };
1232
1359
 
1233
1360
  const Identifier = (c, scope, node) => {
@@ -1259,6 +1386,7 @@ const ReturnStatement = (c, scope, node, out) => {
1259
1386
  const [ret, retInst] = scope.function.return(scope, arg, out);
1260
1387
  return [ret, [...argInst, ...retInst]];
1261
1388
  };
1389
+ const EmptyStatement = () => [null, []];
1262
1390
  const LabeledStatement = (c, scope, node) => {
1263
1391
  const inner = scope.createScope();
1264
1392
  inner.label = node.label.name;
@@ -1288,9 +1416,12 @@ const IfStatement = (c, scope, node) => {
1288
1416
  return [null, inst];
1289
1417
  }
1290
1418
  const endIfAddr = new LiteralValue(null);
1291
- inst.push(...testInst, new JumpInstruction(endIfAddr, EJumpKind.Equal, test, new LiteralValue(0)), ...withAlwaysRuns(c.handle(scope, node.consequent), false)[1], new AddressResolver(endIfAddr));
1292
- const endElseAddr = new LiteralValue(null);
1293
- if (node.alternate) {
1419
+ inst.push(...testInst, new JumpInstruction(endIfAddr, EJumpKind.Equal, test, new LiteralValue(0)), ...withAlwaysRuns(c.handle(scope, node.consequent), false)[1]);
1420
+ if (!node.alternate) {
1421
+ inst.push(new AddressResolver(endIfAddr));
1422
+ }
1423
+ else {
1424
+ const endElseAddr = new LiteralValue(null);
1294
1425
  inst.push(new JumpInstruction(endElseAddr, EJumpKind.Always), new AddressResolver(endIfAddr), ...withAlwaysRuns(c.handle(scope, node.alternate), false)[1], new AddressResolver(endElseAddr));
1295
1426
  }
1296
1427
  return [null, inst];
@@ -1308,7 +1439,7 @@ const SwitchStatement = (c, scope, node) => {
1308
1439
  let constantCase = false;
1309
1440
  let onlyConstantTests = true;
1310
1441
  for (const scase of node.cases) {
1311
- const [, bodyInst] = c.handleMany(innerScope, scase.consequent);
1442
+ const bodyInst = lazy(() => c.handleMany(innerScope, scase.consequent)[1]);
1312
1443
  const bodyAdress = new LiteralValue(null);
1313
1444
  const bodyLine = new AddressResolver(bodyAdress);
1314
1445
  const canFallInto = !endsWithoutFalltrough(inst, endLine);
@@ -1322,7 +1453,7 @@ const SwitchStatement = (c, scope, node) => {
1322
1453
  ])[1];
1323
1454
  }
1324
1455
  if (includeBody)
1325
- inst.push(bodyLine, ...bodyInst);
1456
+ inst.push(bodyLine, ...bodyInst());
1326
1457
  continue;
1327
1458
  }
1328
1459
  const [test, testInst] = c.handleEval(innerScope, scase.test);
@@ -1346,8 +1477,8 @@ const SwitchStatement = (c, scope, node) => {
1346
1477
  // and all the other cases will never run
1347
1478
  if (noFallInto &&
1348
1479
  onlyConstantTests &&
1349
- endsWithoutFalltrough(bodyInst, endLine)) {
1350
- return [null, [...bodyInst, endLine]];
1480
+ endsWithoutFalltrough(bodyInst(), endLine)) {
1481
+ return [null, [...bodyInst(), endLine]];
1351
1482
  }
1352
1483
  jump = c.handle(innerScope, scase, () => [
1353
1484
  null,
@@ -1364,7 +1495,7 @@ const SwitchStatement = (c, scope, node) => {
1364
1495
  if (includeJump)
1365
1496
  caseJumps.push(...testInst, ...jump);
1366
1497
  if (includeBody)
1367
- inst.push(bodyLine, ...bodyInst);
1498
+ inst.push(bodyLine, ...bodyInst());
1368
1499
  }
1369
1500
  // ensures that the processor exits
1370
1501
  // the switch if no cases match
@@ -1842,7 +1973,9 @@ const ObjectPattern = (c, scope, node) => {
1842
1973
  });
1843
1974
  }
1844
1975
  return [new DestructuringValue(members), inst];
1845
- };
1976
+ };
1977
+ // the mlog runtime already does this check for us
1978
+ const OptionalMemberExpression = MemberExpression;
1846
1979
 
1847
1980
  const TemplateLiteral = () => {
1848
1981
  throw new CompilerError("Template strings are currently unavailable. To inline mlog code use the asm function.");
@@ -1919,11 +2052,13 @@ var handlers = /*#__PURE__*/Object.freeze({
1919
2052
  UnaryExpression: UnaryExpression,
1920
2053
  UpdateExpression: UpdateExpression,
1921
2054
  ConditionalExpression: ConditionalExpression,
2055
+ SequenceExpression: SequenceExpression,
1922
2056
  Identifier: Identifier,
1923
2057
  ExpressionStatement: ExpressionStatement,
1924
2058
  BreakStatement: BreakStatement,
1925
2059
  ContinueStatement: ContinueStatement,
1926
2060
  ReturnStatement: ReturnStatement,
2061
+ EmptyStatement: EmptyStatement,
1927
2062
  LabeledStatement: LabeledStatement,
1928
2063
  IfStatement: IfStatement,
1929
2064
  SwitchStatement: SwitchStatement,
@@ -1942,6 +2077,7 @@ var handlers = /*#__PURE__*/Object.freeze({
1942
2077
  MemberExpression: MemberExpression,
1943
2078
  ArrayPattern: ArrayPattern,
1944
2079
  ObjectPattern: ObjectPattern,
2080
+ OptionalMemberExpression: OptionalMemberExpression,
1945
2081
  TemplateLiteral: TemplateLiteral,
1946
2082
  BlockStatement: BlockStatement,
1947
2083
  File: File,
@@ -1989,6 +2125,7 @@ const mathOperations = {
1989
2125
  sqrt: a => Math.sqrt(a),
1990
2126
  rand: null,
1991
2127
  };
2128
+ const orderIndependentOperations = ["max", "min", "len"];
1992
2129
  function createMacroMathOperations() {
1993
2130
  const macroMathOperations = {
1994
2131
  PI: new StoreValue("@pi", EMutability.constant),
@@ -2005,7 +2142,17 @@ function createMacroMathOperations() {
2005
2142
  throw new CompilerError("Cannot do math operation with non-numerical literals.");
2006
2143
  return [new LiteralValue(fn(a.num, b.num)), []];
2007
2144
  }
2145
+ const cachedResult = scope.getCachedOperation(key, a, b);
2146
+ if (cachedResult)
2147
+ return [cachedResult, []];
2148
+ // max, min and len do not change if the arguments change in order
2149
+ if (orderIndependentOperations.includes(key)) {
2150
+ const cachedResult = scope.getCachedOperation(key, b, a);
2151
+ if (cachedResult)
2152
+ return [cachedResult, []];
2153
+ }
2008
2154
  const temp = StoreValue.from(scope, out);
2155
+ scope.addCachedOperation(key, temp, a, b);
2009
2156
  return [temp, [new OperationInstruction(key, temp, a, b)]];
2010
2157
  }
2011
2158
  if (fn && a instanceof LiteralValue) {
@@ -2013,8 +2160,14 @@ function createMacroMathOperations() {
2013
2160
  throw new CompilerError("Cannot do math operation with non-numerical literal.");
2014
2161
  return [new LiteralValue(fn(a.num)), []];
2015
2162
  }
2163
+ const cachedResult = scope.getCachedOperation(key, a);
2164
+ if (cachedResult)
2165
+ return [cachedResult, []];
2016
2166
  const temp = StoreValue.from(scope, out);
2017
- return [temp, [new OperationInstruction(key, temp, a, b)]];
2167
+ // ensures that operations like `rand` are not cached
2168
+ if (mathOperations[key])
2169
+ scope.addCachedOperation(key, temp, a);
2170
+ return [temp, [new OperationInstruction(key, temp, a, null)]];
2018
2171
  });
2019
2172
  }
2020
2173
  return macroMathOperations;