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.
@@ -1,6 +1,6 @@
1
1
  import { CompilerError } from "./CompilerError";
2
2
  import { es, IScope, IValue, TEOutput, THandler, TValueInstructions } from "./types";
3
- declare type THandlerMap = {
3
+ type THandlerMap = {
4
4
  [k in es.Node["type"]]?: THandler<IValue | null>;
5
5
  };
6
6
  export interface CompilerOptions {
@@ -12,7 +12,7 @@ export interface ParsingError extends Error {
12
12
  column: number;
13
13
  };
14
14
  }
15
- declare type CompilerErrorSource = es.Node | es.SourceLocation | {
15
+ type CompilerErrorSource = es.Node | es.SourceLocation | {
16
16
  line: number;
17
17
  column: number;
18
18
  };
package/dist/Scope.d.ts CHANGED
@@ -7,12 +7,14 @@ export declare class Scope implements IScope {
7
7
  ntemp: number;
8
8
  name: string;
9
9
  inst: IInstruction[];
10
+ operationCache: Record<string, IValue>;
11
+ cacheDependencies: Record<string, string[]>;
10
12
  data: Record<string, IValue | null>;
11
13
  break: AddressResolver;
12
14
  continue: AddressResolver;
13
15
  function: IFunctionValue;
14
16
  label?: string;
15
- constructor(values: Record<string, IValue | null>, parent?: IScope | null, stacked?: boolean, ntemp?: number, name?: string, inst?: IInstruction[]);
17
+ constructor(values: Record<string, IValue | null>, parent?: IScope | null, stacked?: boolean, ntemp?: number, name?: string, inst?: IInstruction[], operationCache?: Record<string, IValue>, cacheDependencies?: Record<string, string[]>);
16
18
  copy(): IScope;
17
19
  createScope(): IScope;
18
20
  createFunction(name: string, stacked?: boolean): IScope;
@@ -22,4 +24,7 @@ export declare class Scope implements IScope {
22
24
  hardSet<T extends IValue>(name: string, value: T): T;
23
25
  make(identifier: string, name: string): SenseableValue;
24
26
  makeTempName(): string;
27
+ addCachedOperation(op: string, result: IValue, left: IValue, right?: IValue | undefined): void;
28
+ getCachedOperation(op: string, left: IValue, right?: IValue | undefined): IValue | undefined;
29
+ clearDependentCache(value: IValue): void;
25
30
  }
@@ -1,6 +1,7 @@
1
- import { THandler } from "../types";
1
+ import { IValue, THandler } from "../types";
2
2
  export declare const ObjectExpression: THandler;
3
3
  export declare const ArrayExpression: THandler;
4
4
  export declare const MemberExpression: THandler;
5
5
  export declare const ArrayPattern: THandler;
6
6
  export declare const ObjectPattern: THandler;
7
+ export declare const OptionalMemberExpression: THandler<IValue>;
@@ -6,3 +6,4 @@ export declare const AssignmentExpression: THandler;
6
6
  export declare const UnaryExpression: THandler;
7
7
  export declare const UpdateExpression: THandler;
8
8
  export declare const ConditionalExpression: THandler;
9
+ export declare const SequenceExpression: THandler;
@@ -3,4 +3,5 @@ export declare const ExpressionStatement: THandler<IValue | null>;
3
3
  export declare const BreakStatement: THandler<null>;
4
4
  export declare const ContinueStatement: THandler<null>;
5
5
  export declare const ReturnStatement: THandler<IValue | null>;
6
+ export declare const EmptyStatement: THandler<null>;
6
7
  export declare const LabeledStatement: THandler<null>;
package/dist/index.cjs CHANGED
@@ -167,7 +167,19 @@ const unaryOperators = [
167
167
  ];
168
168
  const updateOperators = ["++", "--"];
169
169
  const singleOperators = [...unaryOperators, ...updateOperators];
170
- const operators = [...leftRightOperators, ...singleOperators];
170
+ const operators = [...leftRightOperators, ...singleOperators];
171
+ const orderIndependentOperators = [
172
+ "!=",
173
+ "!==",
174
+ "&&",
175
+ "&",
176
+ "*",
177
+ "+",
178
+ "==",
179
+ "===",
180
+ "|",
181
+ "||",
182
+ ];
171
183
 
172
184
  class VoidValue {
173
185
  constructor() {
@@ -198,18 +210,18 @@ for (const key of operators) {
198
210
  }
199
211
 
200
212
  class InstructionBase {
201
- constructor(...args) {
202
- this.intent = EInstIntent.none;
203
- this.alwaysRuns = true;
204
- this._hidden = false;
205
- this.args = args;
206
- }
207
213
  get hidden() {
208
214
  return this._hidden;
209
215
  }
210
216
  set hidden(value) {
211
217
  this._hidden = value;
212
218
  }
219
+ constructor(...args) {
220
+ this.intent = EInstIntent.none;
221
+ this.alwaysRuns = true;
222
+ this._hidden = false;
223
+ this.args = args;
224
+ }
213
225
  resolve(_i) { }
214
226
  toString() {
215
227
  return this.args.filter(arg => arg).join(" ");
@@ -283,6 +295,11 @@ function camelToDashCase(name) {
283
295
  // eslint-disable-next-line @typescript-eslint/ban-types
284
296
  function assign(obj, props) {
285
297
  return Object.assign(obj, props);
298
+ }
299
+ /** Lazily computes and caches an expression */
300
+ function lazy(fn) {
301
+ let value;
302
+ return () => (value ??= fn());
286
303
  }
287
304
 
288
305
  function appendSourceLocations(valueInst, node) {
@@ -422,14 +439,14 @@ class EndInstruction extends InstructionBase {
422
439
  }
423
440
 
424
441
  class AddressResolver extends InstructionBase {
425
- constructor(...bonds) {
426
- super();
427
- this.bonds = bonds;
428
- }
429
442
  get hidden() {
430
443
  return true;
431
444
  }
432
445
  set hidden(value) { }
446
+ constructor(...bonds) {
447
+ super();
448
+ this.bonds = bonds;
449
+ }
433
450
  resolve(i) {
434
451
  for (const literal of this.bonds)
435
452
  literal.data = i;
@@ -545,12 +562,16 @@ class BaseValue extends VoidValue {
545
562
  ],
546
563
  ];
547
564
  }
565
+ "!=="(scope, other, out) {
566
+ const [equal, equalInst] = this["==="](scope, other);
567
+ const [result, resultInst] = equal["!"](scope, out);
568
+ return [result, [...equalInst, ...resultInst]];
569
+ }
548
570
  }
549
571
  const operatorMap$1 = {
550
572
  "==": "equal",
551
573
  "===": "strictEqual",
552
574
  "!=": "notEqual",
553
- "!==": "notEqual",
554
575
  "<": "lessThan",
555
576
  ">": "greaterThan",
556
577
  "<=": "lessThanEq",
@@ -637,6 +658,7 @@ class LiteralValue extends BaseValue {
637
658
  super();
638
659
  this.mutability = EMutability.constant;
639
660
  this.data = data;
661
+ this.name = this.toString();
640
662
  }
641
663
  eval(_scope) {
642
664
  return [this, []];
@@ -760,12 +782,40 @@ class StoreValue extends BaseValue {
760
782
  const [evalValue, evalInst] = value.eval(scope, this);
761
783
  return [this, [...evalInst, new SetInstruction(this, evalValue)]];
762
784
  }
785
+ "=="(scope, value, out) {
786
+ if (compareStores(this, value))
787
+ return [new LiteralValue(1), []];
788
+ return super["=="](scope, value, out);
789
+ }
790
+ "==="(scope, value, out) {
791
+ if (compareStores(this, value))
792
+ return [new LiteralValue(1), []];
793
+ return super["==="](scope, value, out);
794
+ }
795
+ "!="(scope, value, out) {
796
+ if (compareStores(this, value))
797
+ return [new LiteralValue(0), []];
798
+ return super["!="](scope, value, out);
799
+ }
800
+ "!=="(scope, value, out) {
801
+ if (compareStores(this, value))
802
+ return [new LiteralValue(0), []];
803
+ return super["!=="](scope, value, out);
804
+ }
763
805
  eval(_scope) {
764
806
  return [this, []];
765
807
  }
766
808
  toString() {
767
809
  return this.name;
768
810
  }
811
+ }
812
+ function compareStores(left, right) {
813
+ return (left === right ||
814
+ // prevents a store from being equal to a senseable value
815
+ (right instanceof left.constructor &&
816
+ right.mutability === EMutability.constant &&
817
+ left.mutability === EMutability.constant &&
818
+ right.name === left.name));
769
819
  }
770
820
 
771
821
  class ObjectValue extends VoidValue {
@@ -947,16 +997,18 @@ class LazyValue extends VoidValue {
947
997
  }
948
998
 
949
999
  class Scope {
950
- constructor(values, parent = null, stacked = false, ntemp = 0, name = "", inst = []) {
1000
+ constructor(values, parent = null, stacked = false, ntemp = 0, name = "", inst = [], operationCache = {}, cacheDependencies = {}) {
951
1001
  this.parent = parent;
952
1002
  this.stacked = stacked;
953
1003
  this.ntemp = ntemp;
954
1004
  this.name = name;
955
1005
  this.inst = inst;
1006
+ this.operationCache = operationCache;
1007
+ this.cacheDependencies = cacheDependencies;
956
1008
  this.data = values;
957
1009
  }
958
1010
  copy() {
959
- const scope = new Scope({ ...this.data }, this.parent, this.stacked, this.ntemp, this.name, this.inst);
1011
+ const scope = new Scope({ ...this.data }, this.parent, this.stacked, this.ntemp, this.name, this.inst, { ...this.operationCache }, { ...this.cacheDependencies });
960
1012
  scope.break = this.break;
961
1013
  scope.continue = this.continue;
962
1014
  scope.function = this.function;
@@ -965,6 +1017,8 @@ class Scope {
965
1017
  createScope() {
966
1018
  const scope = this.copy();
967
1019
  scope.data = {};
1020
+ scope.operationCache = {};
1021
+ scope.cacheDependencies = {};
968
1022
  scope.parent = this;
969
1023
  return scope;
970
1024
  }
@@ -1010,6 +1064,46 @@ class Scope {
1010
1064
  this.ntemp++;
1011
1065
  return result;
1012
1066
  }
1067
+ addCachedOperation(op, result, left, right) {
1068
+ if (!left.name || !result.name || (right && !right.name))
1069
+ return;
1070
+ const id = formatCacheId(op, left, right);
1071
+ this.operationCache[id] = result;
1072
+ this.clearDependentCache(result);
1073
+ addCacheDependency(this, left.name, id);
1074
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1075
+ if (right)
1076
+ addCacheDependency(this, right.name, id);
1077
+ addCacheDependency(this, result.name, id);
1078
+ }
1079
+ getCachedOperation(op, left, right) {
1080
+ const id = formatCacheId(op, left, right);
1081
+ const result = this.operationCache[id];
1082
+ if (result)
1083
+ return result;
1084
+ if (this.parent)
1085
+ return this.parent.getCachedOperation(op, left, right);
1086
+ }
1087
+ clearDependentCache(value) {
1088
+ if (!value.name)
1089
+ return;
1090
+ const dependents = this.cacheDependencies[value.name];
1091
+ if (dependents) {
1092
+ for (const id of dependents) {
1093
+ delete this.operationCache[id];
1094
+ }
1095
+ }
1096
+ if (this.parent)
1097
+ this.parent.clearDependentCache(value);
1098
+ }
1099
+ }
1100
+ function formatCacheId(op, left, right) {
1101
+ if (right)
1102
+ return `{${op}}{${left.name}}{${right.name}}`;
1103
+ return `{${op}}{${left.name}}`;
1104
+ }
1105
+ function addCacheDependency(scope, name, id) {
1106
+ (scope.cacheDependencies[name] ??= []).push(id);
1013
1107
  }
1014
1108
 
1015
1109
  const Program = (c, scope, node) => {
@@ -1185,7 +1279,21 @@ const BooleanLiteral = (_c, scope, node) => [new LiteralValue(+node.value), []];
1185
1279
  const LRExpression = (c, scope, node, out) => {
1186
1280
  const [left, leftInst] = c.handleEval(scope, node.left);
1187
1281
  const [right, rightInst] = c.handleEval(scope, node.right);
1282
+ if (!(left instanceof LiteralValue) || !(right instanceof LiteralValue)) {
1283
+ const cachedResult = scope.getCachedOperation(node.operator, left, right);
1284
+ if (cachedResult)
1285
+ return [cachedResult, [...leftInst, ...rightInst]];
1286
+ // because a * b is the same as b * a
1287
+ if (orderIndependentOperators.includes(node.operator)) {
1288
+ const cachedResult = scope.getCachedOperation(node.operator, right, left);
1289
+ if (cachedResult)
1290
+ return [cachedResult, [...leftInst, ...rightInst]];
1291
+ }
1292
+ }
1188
1293
  const [op, opInst] = left[node.operator](scope, right, out);
1294
+ if (!(op instanceof LiteralValue)) {
1295
+ scope.addCachedOperation(node.operator, op, left, right);
1296
+ }
1189
1297
  return [op, [...leftInst, ...rightInst, ...opInst]];
1190
1298
  };
1191
1299
  const BinaryExpression = LRExpression;
@@ -1207,18 +1315,26 @@ const AssignmentExpression = (c, scope, node) => {
1207
1315
  ];
1208
1316
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1209
1317
  const [op, opInst] = left[node.operator](scope, right);
1318
+ if (left)
1319
+ scope.clearDependentCache(left);
1210
1320
  return [op, [...leftInst, ...rightInst, ...opInst]];
1211
1321
  };
1212
1322
  const UnaryExpression = (c, scope, { argument, operator }, out) => {
1213
1323
  const [arg, argInst] = c.handleEval(scope, argument);
1324
+ const cachedResult = scope.getCachedOperation(operator, arg);
1325
+ if (cachedResult)
1326
+ return [cachedResult, argInst];
1214
1327
  const operatorId = operator == "+" || operator == "-" ? `u${operator}` : operator;
1215
1328
  if (operatorId === "throw")
1216
1329
  throw new CompilerError("throw operator is not supported");
1217
1330
  const [op, opInst] = arg[operatorId](scope, out);
1331
+ scope.addCachedOperation(operator, op, arg);
1218
1332
  return [op, [...argInst, ...opInst]];
1219
1333
  };
1220
1334
  const UpdateExpression = (c, scope, { argument, operator, prefix }, out) => {
1221
1335
  const [arg, argInst] = c.handle(scope, argument);
1336
+ if (arg)
1337
+ scope.clearDependentCache(arg);
1222
1338
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1223
1339
  const [op, opInst] = arg[operator](scope, prefix, out);
1224
1340
  return [op, [...argInst, ...opInst]];
@@ -1249,6 +1365,17 @@ const ConditionalExpression = (c, scope, node, out) => {
1249
1365
  new AddressResolver(endExpressionAdress),
1250
1366
  ],
1251
1367
  ];
1368
+ };
1369
+ const SequenceExpression = (c, scope, node, out) => {
1370
+ const { expressions } = node;
1371
+ const inst = [];
1372
+ // compute every expression except the last one
1373
+ for (let i = 0; i < expressions.length - 1; i++) {
1374
+ const [, exprInst] = c.handleEval(scope, expressions[i], discardedName);
1375
+ inst.push(...exprInst);
1376
+ }
1377
+ const [value, valueInst] = c.handleEval(scope, expressions[expressions.length - 1], out);
1378
+ return [value, [...inst, ...valueInst]];
1252
1379
  };
1253
1380
 
1254
1381
  const Identifier = (c, scope, node) => {
@@ -1280,6 +1407,7 @@ const ReturnStatement = (c, scope, node, out) => {
1280
1407
  const [ret, retInst] = scope.function.return(scope, arg, out);
1281
1408
  return [ret, [...argInst, ...retInst]];
1282
1409
  };
1410
+ const EmptyStatement = () => [null, []];
1283
1411
  const LabeledStatement = (c, scope, node) => {
1284
1412
  const inner = scope.createScope();
1285
1413
  inner.label = node.label.name;
@@ -1309,9 +1437,12 @@ const IfStatement = (c, scope, node) => {
1309
1437
  return [null, inst];
1310
1438
  }
1311
1439
  const endIfAddr = new LiteralValue(null);
1312
- inst.push(...testInst, new JumpInstruction(endIfAddr, EJumpKind.Equal, test, new LiteralValue(0)), ...withAlwaysRuns(c.handle(scope, node.consequent), false)[1], new AddressResolver(endIfAddr));
1313
- const endElseAddr = new LiteralValue(null);
1314
- if (node.alternate) {
1440
+ inst.push(...testInst, new JumpInstruction(endIfAddr, EJumpKind.Equal, test, new LiteralValue(0)), ...withAlwaysRuns(c.handle(scope, node.consequent), false)[1]);
1441
+ if (!node.alternate) {
1442
+ inst.push(new AddressResolver(endIfAddr));
1443
+ }
1444
+ else {
1445
+ const endElseAddr = new LiteralValue(null);
1315
1446
  inst.push(new JumpInstruction(endElseAddr, EJumpKind.Always), new AddressResolver(endIfAddr), ...withAlwaysRuns(c.handle(scope, node.alternate), false)[1], new AddressResolver(endElseAddr));
1316
1447
  }
1317
1448
  return [null, inst];
@@ -1329,7 +1460,7 @@ const SwitchStatement = (c, scope, node) => {
1329
1460
  let constantCase = false;
1330
1461
  let onlyConstantTests = true;
1331
1462
  for (const scase of node.cases) {
1332
- const [, bodyInst] = c.handleMany(innerScope, scase.consequent);
1463
+ const bodyInst = lazy(() => c.handleMany(innerScope, scase.consequent)[1]);
1333
1464
  const bodyAdress = new LiteralValue(null);
1334
1465
  const bodyLine = new AddressResolver(bodyAdress);
1335
1466
  const canFallInto = !endsWithoutFalltrough(inst, endLine);
@@ -1343,7 +1474,7 @@ const SwitchStatement = (c, scope, node) => {
1343
1474
  ])[1];
1344
1475
  }
1345
1476
  if (includeBody)
1346
- inst.push(bodyLine, ...bodyInst);
1477
+ inst.push(bodyLine, ...bodyInst());
1347
1478
  continue;
1348
1479
  }
1349
1480
  const [test, testInst] = c.handleEval(innerScope, scase.test);
@@ -1367,8 +1498,8 @@ const SwitchStatement = (c, scope, node) => {
1367
1498
  // and all the other cases will never run
1368
1499
  if (noFallInto &&
1369
1500
  onlyConstantTests &&
1370
- endsWithoutFalltrough(bodyInst, endLine)) {
1371
- return [null, [...bodyInst, endLine]];
1501
+ endsWithoutFalltrough(bodyInst(), endLine)) {
1502
+ return [null, [...bodyInst(), endLine]];
1372
1503
  }
1373
1504
  jump = c.handle(innerScope, scase, () => [
1374
1505
  null,
@@ -1385,7 +1516,7 @@ const SwitchStatement = (c, scope, node) => {
1385
1516
  if (includeJump)
1386
1517
  caseJumps.push(...testInst, ...jump);
1387
1518
  if (includeBody)
1388
- inst.push(bodyLine, ...bodyInst);
1519
+ inst.push(bodyLine, ...bodyInst());
1389
1520
  }
1390
1521
  // ensures that the processor exits
1391
1522
  // the switch if no cases match
@@ -1863,7 +1994,9 @@ const ObjectPattern = (c, scope, node) => {
1863
1994
  });
1864
1995
  }
1865
1996
  return [new DestructuringValue(members), inst];
1866
- };
1997
+ };
1998
+ // the mlog runtime already does this check for us
1999
+ const OptionalMemberExpression = MemberExpression;
1867
2000
 
1868
2001
  const TemplateLiteral = () => {
1869
2002
  throw new CompilerError("Template strings are currently unavailable. To inline mlog code use the asm function.");
@@ -1940,11 +2073,13 @@ var handlers = /*#__PURE__*/Object.freeze({
1940
2073
  UnaryExpression: UnaryExpression,
1941
2074
  UpdateExpression: UpdateExpression,
1942
2075
  ConditionalExpression: ConditionalExpression,
2076
+ SequenceExpression: SequenceExpression,
1943
2077
  Identifier: Identifier,
1944
2078
  ExpressionStatement: ExpressionStatement,
1945
2079
  BreakStatement: BreakStatement,
1946
2080
  ContinueStatement: ContinueStatement,
1947
2081
  ReturnStatement: ReturnStatement,
2082
+ EmptyStatement: EmptyStatement,
1948
2083
  LabeledStatement: LabeledStatement,
1949
2084
  IfStatement: IfStatement,
1950
2085
  SwitchStatement: SwitchStatement,
@@ -1963,6 +2098,7 @@ var handlers = /*#__PURE__*/Object.freeze({
1963
2098
  MemberExpression: MemberExpression,
1964
2099
  ArrayPattern: ArrayPattern,
1965
2100
  ObjectPattern: ObjectPattern,
2101
+ OptionalMemberExpression: OptionalMemberExpression,
1966
2102
  TemplateLiteral: TemplateLiteral,
1967
2103
  BlockStatement: BlockStatement,
1968
2104
  File: File,
@@ -2010,6 +2146,7 @@ const mathOperations = {
2010
2146
  sqrt: a => Math.sqrt(a),
2011
2147
  rand: null,
2012
2148
  };
2149
+ const orderIndependentOperations = ["max", "min", "len"];
2013
2150
  function createMacroMathOperations() {
2014
2151
  const macroMathOperations = {
2015
2152
  PI: new StoreValue("@pi", EMutability.constant),
@@ -2026,7 +2163,17 @@ function createMacroMathOperations() {
2026
2163
  throw new CompilerError("Cannot do math operation with non-numerical literals.");
2027
2164
  return [new LiteralValue(fn(a.num, b.num)), []];
2028
2165
  }
2166
+ const cachedResult = scope.getCachedOperation(key, a, b);
2167
+ if (cachedResult)
2168
+ return [cachedResult, []];
2169
+ // max, min and len do not change if the arguments change in order
2170
+ if (orderIndependentOperations.includes(key)) {
2171
+ const cachedResult = scope.getCachedOperation(key, b, a);
2172
+ if (cachedResult)
2173
+ return [cachedResult, []];
2174
+ }
2029
2175
  const temp = StoreValue.from(scope, out);
2176
+ scope.addCachedOperation(key, temp, a, b);
2030
2177
  return [temp, [new OperationInstruction(key, temp, a, b)]];
2031
2178
  }
2032
2179
  if (fn && a instanceof LiteralValue) {
@@ -2034,8 +2181,14 @@ function createMacroMathOperations() {
2034
2181
  throw new CompilerError("Cannot do math operation with non-numerical literal.");
2035
2182
  return [new LiteralValue(fn(a.num)), []];
2036
2183
  }
2184
+ const cachedResult = scope.getCachedOperation(key, a);
2185
+ if (cachedResult)
2186
+ return [cachedResult, []];
2037
2187
  const temp = StoreValue.from(scope, out);
2038
- return [temp, [new OperationInstruction(key, temp, a, b)]];
2188
+ // ensures that operations like `rand` are not cached
2189
+ if (mathOperations[key])
2190
+ scope.addCachedOperation(key, temp, a);
2191
+ return [temp, [new OperationInstruction(key, temp, a, null)]];
2039
2192
  });
2040
2193
  }
2041
2194
  return macroMathOperations;