Repository: source-academy/js-slang
Pull request #184 (2019-02-26 12:55:31)

[NATIVE #3] Normalise errors
Now certain errors such as assigning to a constant and undefined variable errors throw their corresponding SourceErrors.

## Note: The stack message is different accross all browsers, so currently this only works in V8 (chrome and node). Might have to consider using an external library to parse the errors in another PR.

coveralls: ## Pull Request Test Coverage Report for [Build 724](https://coveralls.io/builds/21856062)

* **149** of **152**   **(98.03%)**  changed or added relevant lines in **8** files are covered.
* **1** unchanged line in **1** file lost coverage.
* Overall coverage increased (+**0.5%**) to **91.608%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/index.ts](https://coveralls.io/builds/21856062/source?filename=src%2Findex.ts#L55) | 33 | 36 | 91.67%
<!-- | **Total:** | **149** | **152** | **98.03%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/utils/properTailCalls.ts](https://coveralls.io/builds/21856062/source?filename=src%2Futils%2FproperTailCalls.ts#L17) | 1 | 91.11% |
<!-- | **Total:** | **1** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/21856062/badge)](https://coveralls.io/builds/21856062) |
| :-- | --: |
| Change from base [Build 696](https://coveralls.io/builds/21814562): |  0.5% |
| Covered Lines: | 1479 |
| Relevant Lines: | 1611 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/src/index.ts b/src/index.ts
index 4f892245..631b7be3 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,14 +1,28 @@
 import { ExpressionStatement, Program } from 'estree'
 import { RawSourceMap, SourceMapConsumer } from 'source-map'
 import { UNKNOWN_LOCATION } from './constants'
-// import { UNKNOWN_LOCATION } from './constants'
 import createContext from './createContext'
 import { evaluate } from './interpreter'
-import { ExceptionError, InterruptedError, RuntimeSourceError } from './interpreter-errors'
+import {
+  ConstAssignment,
+  ExceptionError,
+  InterruptedError,
+  RuntimeSourceError,
+  UndefinedVariable
+} from './interpreter-errors'
 import { parse } from './parser'
 import { AsyncScheduler, PreemptiveScheduler } from './schedulers'
 import { transpile } from './transpiler'
-import { Context, Directive, Error, Finished, Result, Scheduler, SourceError } from './types'
+import {
+  Context,
+  Directive,
+  Error as ResultError,
+  Finished,
+  Result,
+  Scheduler,
+  SourceError
+} from './types'
+import { locationDummyNode } from './utils/astCreator'
 import { sandboxedEval } from './utils/evalContainer'
 
 export interface IOptions {
@@ -46,6 +60,48 @@ export function parseError(errors: SourceError[], verbose: boolean = verboseErro
   return errorMessagesArr.join('\n')
 }
 
+function convertNativeErrorToSourceError(
+  error: Error,
+  line: number | null,
+  column: number | null,
+  name: string | null
+) {
+  // brute-forced from MDN website for phrasing of errors from different browsers
+  // FWIW node and chrome uses V8 so they'll have the same error messages
+  // unable to test on other engines
+  const assignmentToConst = [
+    'invalid assignment to const',
+    'Assignment to constant variable',
+    'Assignment to const',
+    'Redeclaration of const'
+  ]
+  const undefinedVariable = ['is not defined']
+
+  const message = error.message
+  if (name === null) {
+    name = 'UNKNOWN'
+  }
+
+  function messageContains(possibleErrorMessages: string[]) {
+    return possibleErrorMessages.some(errorMessage => message.includes(errorMessage))
+  }
+
+  if (messageContains(assignmentToConst)) {
+    return new ConstAssignment(locationDummyNode(line!, column!), name)
+  } else if (messageContains(undefinedVariable)) {
+    return new UndefinedVariable(name, locationDummyNode(line!, column!))
+  } else {
+    const location =
+      line === null || column === null
+        ? UNKNOWN_LOCATION
+        : {
+            start: { line, column },
+            end: { line: -1, column: -1 }
+          }
+    return new ExceptionError(error, location)
+  }
+}
+
 export function runInContext(
   code: string,
   context: Context,
@@ -98,18 +154,17 @@ export function runInContext(
           line === 1 ? lastStatementSourceMapJson! : sourceMapJson!,
           null,
           consumer => {
-            const { line: originalLine, column: originalColumn } = consumer.originalPositionFor({
+            const {
+              line: originalLine,
+              column: originalColumn,
+              name
+            } = consumer.originalPositionFor({
               line,
               column
             })
-            const location =
-              line === null
-                ? UNKNOWN_LOCATION
-                : {
-                    start: { line: originalLine!, column: originalColumn! },
-                    end: { line: -1, column: -1 }
-                  }
-            context.errors.push(new ExceptionError(error, location))
+            context.errors.push(
+              convertNativeErrorToSourceError(error, originalLine, originalColumn, name)
+            )
             return resolvedErrorPromise
           }
         )
@@ -129,7 +184,7 @@ export function runInContext(
   }
 }
 
-export function resume(result: Result): Finished | Error | Promise<Result> {
+export function resume(result: Result): Finished | ResultError | Promise<Result> {
   if (result.status === 'finished' || result.status === 'error') {
     return result
   } else {
diff --git a/src/transformer.ts b/src/transformer.ts
deleted file mode 100644
index 38dd7f95..00000000
--- a/src/transformer.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-import * as es from 'estree'
-import * as create from './utils/astCreator'
-
-export function transform(node: es.Node) {
-  const transformer = transformers[node.type]
-  const transformed = transformer(node)
-  if (
-    transformed !== null &&
-    transformed !== undefined &&
-    typeof transformed === 'object' &&
-    transformed.tag !== undefined
-  ) {
-    transformed.loc = node.loc
-  }
-  return transformed
-}
-
-/**
- * Clone and transform all nodes
- * TODO: to prep for future use to
- * get back line nos for error messages
- */
-const transformers = Object.freeze({
-  Program(program: es.Program) {
-    return {
-      type: 'Program',
-      body: program.body.map(transform),
-      sourceType: 'module'
-    }
-  },
-  BlockStatement(block: es.BlockStatement) {
-    return create.blockStatement(block.body.map(transform))
-  },
-  ExpressionStatement(node: es.ExpressionStatement) {
-    return create.expressionStatement(transform(node.expression))
-  },
-  IfStatement(node: es.IfStatement) {
-    return {
-      type: 'IfStatement',
-      test: transform(node.test),
-      consequent: transform(node.consequent),
-      alternate: node.alternate && transform(node.alternate)
-    }
-  },
-  /**
-   * Transform function declarations
-   * function f(args...) {
-   *  statements...
-   * }
-   * into
-   * const f = (args...) {
-   *  statements...
-   * }
-   *
-   */
-  FunctionDeclaration(node: es.FunctionDeclaration) {
-    const { id, params, body } = node
-    return create.constantDeclaration(
-      (id as es.Identifier).name,
-      transform(create.blockArrowFunction(params.map(transform), transform(body)))
-    )
-  },
-  /**
-   * Assumes that declarations are exactly (const | let) name = value;
-   */
-  VariableDeclaration(node: es.VariableDeclaration) {
-    const { id, init } = node.declarations[0]
-    return {
-      type: 'VariableDeclaration',
-      declarations: [
-        {
-          type: 'VariableDeclarator',
-          id: transform(id),
-          init: transform(init!)
-        }
-      ],
-      kind: node.kind
-    }
-  },
-  ReturnStatement(node: es.ReturnStatement) {
-    return create.returnStatement(transform(node.argument!))
-  },
-  CallExpression(node: es.CallExpression) {
-    return create.callExpression(transform(node.callee), node.arguments.map(transform))
-  },
-  UnaryExpression(node: es.UnaryExpression) {
-    return {
-      type: 'UnaryExpression',
-      operator: node.operator,
-      prefix: node.prefix,
-      argument: transform(node.argument)
-    }
-  },
-  BinaryExpression(node: es.BinaryExpression) {
-    return {
-      type: 'BinaryExpression',
-      operator: node.operator,
-      left: transform(node.left),
-      right: transform(node.right)
-    }
-  },
-  LogicalExpression(node: es.LogicalExpression) {
-    return {
-      type: 'LogicalExpression',
-      left: transform(node.left),
-      operator: node.operator,
-      right: transform(node.right)
-    }
-  },
-  ConditionalExpression(node: es.ConditionalExpression) {
-    return {
-      type: 'ConditionalExpression',
-      test: transform(node.test),
-      consequent: transform(node.consequent),
-      alternate: transform(node.alternate)
-    }
-  },
-  ArrowFunctionExpression(node: es.ArrowFunctionExpression) {
-    if (node.expression) {
-      return transform(
-        create.blockArrowFunction(
-          node.params as es.Identifier[],
-          create.blockStatement([create.returnStatement(node.body as es.Expression)])
-        )
-      )
-    } else {
-      return {
-        type: 'ArrowFunctionExpression',
-        expression: false,
-        generator: false,
-        params: node.params.map(transform),
-        body: transform(node.body)
-      }
-    }
-  },
-  Identifier(node: es.Identifier) {
-    return create.identifier(node.name)
-  },
-  Literal(node: es.Literal) {
-    return {
-      type: 'Literal',
-      value: node.value
-    }
-  },
-  ArrayExpression(node: es.ArrayExpression) {
-    return {
-      type: 'ArrayExpression',
-      elements: node.elements.map(transform)
-    }
-  },
-  AssignmentExpression(node: es.AssignmentExpression) {
-    return {
-      type: 'AssignmentExpression',
-      operator: node.operator,
-      left: transform(node.left),
-      right: transform(node.right)
-    }
-  },
-  ForStatement(node: es.ForStatement) {
-    return {
-      type: 'ForStatement',
-      init: node.init && transform(node.init),
-      test: node.test && transform(node.test),
-      update: node.update && transform(node.update),
-      body: transform(node.body)
-    }
-  },
-  WhileStatement(node: es.WhileStatement) {
-    return {
-      type: 'WhileStatement',
-      test: transform(node.test),
-      body: transform(node.body)
-    }
-  },
-  BreakStatement(node: es.BreakStatement) {
-    return {
-      type: 'BreakStatement'
-    }
-  },
-  ContinueStatement(node: es.ContinueStatement) {
-    return {
-      type: 'ContinueStatement'
-    }
-  },
-  MemberExpression(node: es.MemberExpression) {
-    return {
-      type: 'MemberExpression',
-      object: transform(node.object),
-      property: transform(node.property),
-      computed: node.computed
-    }
-  },
-  ObjectExpression(node: es.ObjectExpression) {
-    return {
-      type: 'ObjectExpression',
-      properties: node.properties.map(transform)
-    }
-  },
-  Property(node: es.Property) {
-    return {
-      type: 'Property',
-      method: node.method,
-      shorthand: node.shorthand,
-      computed: node.computed,
-      key: transform(node.key),
-      value: transform(node.value)
-    }
-  }
-})
diff --git a/src/utils/properTailCalls.ts b/src/utils/properTailCalls.ts
index b1473b3f..523c896a 100644
--- a/src/utils/properTailCalls.ts
+++ b/src/utils/properTailCalls.ts
@@ -1,5 +1,5 @@
 import { InvalidNumberOfArguments } from '../interpreter-errors'
-import * as create from './astCreator'
+import { callExpression, locationDummyNode } from './astCreator'
 
 /**
  * Limitations:
@@ -22,7 +22,7 @@ export const callIteratively = (f: any, ...args: any[]) => {
       const receivedLength = args.length
       if (expectedLength !== receivedLength) {
         throw new InvalidNumberOfArguments(
-          create.callExpression(create.locationDummyNode(line, column), args, {
+          callExpression(locationDummyNode(line, column), args, {
             start: { line, column },
             end: { line, column }
           }),


Test patch:
diff --git a/src/__tests__/__snapshots__/index.ts.snap b/src/__tests__/__snapshots__/index.ts.snap
index 1cf6325a..54af8fa0 100644
--- a/src/__tests__/__snapshots__/index.ts.snap
+++ b/src/__tests__/__snapshots__/index.ts.snap
@@ -78,6 +78,17 @@ Object {
 	[implementation hidden]
 }",
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"callIfFuncAndRightArgs(stringify, 1, 0, pair);\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -179,6 +190,29 @@ test();",
   "parsedErrors": "Line 3: Cannot assign new value to constant constant.",
   "result": undefined,
   "resultStatus": "error",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const test = wrap(() => {
+      const constant = 3;
+      constant = 4;
+      return {
+        isTail: false,
+        value: constant
+      };
+    }, \\"function test() {\\\\n  const constant = 3;\\\\n  constant = 4;\\\\n  return constant;\\\\n}\\");
+    const lastStatementResult = eval(\\"callIfFuncAndRightArgs(test, 6, 0);\\");
+    native.globals.set(\\"test\\", {
+      kind: \\"const\\",
+      value: test
+    });
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -196,6 +230,25 @@ o.a.b.c;",
   "parsedErrors": "",
   "result": "string",
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const o = {};
+    o.a = {};
+    o.a.b = {};
+    o.a.b.c = \\"string\\";
+    const lastStatementResult = eval(\\"o.a.b.c;\\");
+    native.globals.set(\\"o\\", {
+      kind: \\"const\\",
+      value: o
+    });
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -223,6 +276,28 @@ fac(5);",
   "parsedErrors": "",
   "result": 120,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const fac = wrap(i => boolOrErr(i === 1, 1, 19) ? {
+      isTail: false,
+      value: 1
+    } : {
+      isTail: false,
+      value: i * callIfFuncAndRightArgs(fac, 1, 37, i - 1)
+    }, \\"i => i === 1 ? 1 : i * fac(i - 1)\\");
+    const lastStatementResult = eval(\\"callIfFuncAndRightArgs(fac, 2, 0, 5);\\");
+    native.globals.set(\\"fac\\", {
+      kind: \\"const\\",
+      value: fac
+    });
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -254,6 +329,27 @@ identity(t) === t && t(1, 2, 3) === 6;",
   "parsedErrors": "",
   "result": true,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const t = wrap((x, y, z) => {
+      return {
+        isTail: false,
+        value: x + y + z
+      };
+    }, \\"function t(x, y, z) {\\\\n  return x + y + z;\\\\n}\\");
+    const lastStatementResult = eval(\\"boolOrErr(callIfFuncAndRightArgs(identity, 4, 0, t) === t, 4, 0) && callIfFuncAndRightArgs(t, 4, 21, 1, 2, 3) === 6;\\");
+    native.globals.set(\\"t\\", {
+      kind: \\"const\\",
+      value: t
+    });
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -267,6 +363,17 @@ Object {
   "parsedErrors": "",
   "result": "[object Object]",
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"callIfFuncAndRightArgs(toString, 1, 0, {   a: 1 });\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -316,6 +423,23 @@ o.a;",
   "parsedErrors": "",
   "result": 1,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const o = {};
+    o.a = 1;
+    const lastStatementResult = eval(\\"o.a;\\");
+    native.globals.set(\\"o\\", {
+      kind: \\"const\\",
+      value: o
+    });
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -466,6 +590,17 @@ Object {
   "parsedErrors": "",
   "result": undefined,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"if (boolOrErr(false, 1, 0)) {} else {}\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -482,6 +617,17 @@ Object {
   "parsedErrors": "",
   "result": 2,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"if (boolOrErr(false, 1, 0)) {} else {   2; }\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -500,6 +646,27 @@ toString(a=>b) + toString(f);",
   return 5;
 }",
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const f = wrap(x => {
+      return {
+        isTail: false,
+        value: 5
+      };
+    }, \\"function f(x) {\\\\n  return 5;\\\\n}\\");
+    const lastStatementResult = eval(\\"callIfFuncAndRightArgs(toString, 4, 0, wrap(a => ({   isTail: false,   value: b }), \\\\\\"a => b\\\\\\")) + callIfFuncAndRightArgs(toString, 4, 17, f);\\");
+    native.globals.set(\\"f\\", {
+      kind: \\"const\\",
+      value: f
+    });
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -571,6 +738,17 @@ Object {
   "parsedErrors": "",
   "result": false,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(false, 1, 0) && callIfFuncAndRightArgs(1, 1, 9);\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -584,6 +762,17 @@ Object {
   "parsedErrors": "",
   "result": true,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(true, 1, 0) || callIfFuncAndRightArgs(1, 1, 8);\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -597,6 +786,17 @@ Object {
   "parsedErrors": "",
   "result": false,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(false, 1, 0) && false;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -610,6 +810,17 @@ Object {
   "parsedErrors": "",
   "result": false,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(false, 1, 0) && true;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -623,6 +834,17 @@ Object {
   "parsedErrors": "",
   "result": false,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(false, 1, 0) || false;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -636,6 +858,17 @@ Object {
   "parsedErrors": "",
   "result": true,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(false, 1, 0) || true;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -649,6 +882,17 @@ Object {
   "parsedErrors": "",
   "result": false,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(false, 1, 0) ? true : false;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -662,6 +906,17 @@ Object {
   "parsedErrors": "",
   "result": false,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(true, 1, 0) && false;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -675,6 +930,17 @@ Object {
   "parsedErrors": "",
   "result": true,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(true, 1, 0) && true;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -688,6 +954,17 @@ Object {
   "parsedErrors": "",
   "result": true,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(true, 1, 0) || false;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -701,6 +978,17 @@ Object {
   "parsedErrors": "",
   "result": true,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(true, 1, 0) || true;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -714,6 +1002,17 @@ Object {
   "parsedErrors": "",
   "result": true,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"boolOrErr(true, 1, 0) ? true : false;\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -729,6 +1028,17 @@ Object {
   "parsedErrors": "",
   "result": undefined,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"if (boolOrErr(true, 1, 0)) {} else {}\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
@@ -745,6 +1055,17 @@ Object {
   "parsedErrors": "",
   "result": 1,
   "resultStatus": "finished",
+  "transpiled": "const native = $$NATIVE_STORAGE;
+const boolOrErr = native.operators.get(\\"itselfIfBooleanElseError\\");
+const callIfFuncAndRightArgs = native.operators.get(\\"callIfFunctionAndRightArgumentsElseError\\");
+const wrap = native.properTailCalls.wrap;
+(( <globals redacted> ) => {
+  return (() => {
+    const lastStatementResult = eval(\\"if (boolOrErr(true, 1, 0)) {   1; } else {}\\");
+    return lastStatementResult;
+  })();
+})();
+",
   "visualiseListResult": Array [],
 }
 `;
diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts
index 6a246e3c..e218805e 100644
--- a/src/__tests__/index.ts
+++ b/src/__tests__/index.ts
@@ -36,7 +36,7 @@ test('Arrow function definition returns itself', () => {
 })
 
 test('Builtins hide their implementation when stringify', () => {
-  return expectResult('stringify(pair);', { chapter: 2 }).toMatchInlineSnapshot(`
+  return expectResult('stringify(pair);', { chapter: 2, native: true }).toMatchInlineSnapshot(`
 "function pair(left, right) {
 	[implementation hidden]
 }"
@@ -53,7 +53,11 @@ test('Builtins hide their implementation when toString', () => {
 })
 
 test('Objects toString matches up with JS', () => {
-  return expectToMatchJS('toString({a: 1});', { chapter: 100, testBuiltins: { toString } })
+  return expectToMatchJS('toString({a: 1});', {
+    chapter: 100,
+    native: true,
+    testBuiltins: { toString }
+  })
 })
 
 test('Arrays toString matches up with JS', () => {
@@ -72,7 +76,7 @@ test('functions toString (mostly) matches up with JS', () => {
   }
   toString(a=>b) + toString(f);
   `,
-    { testBuiltins: { toString } }
+    { native: true, testBuiltins: { toString } }
   )
 })
 
@@ -92,10 +96,13 @@ test('primitives toString matches up with JS', () => {
 })
 
 test('Factorial arrow function', () => {
-  return expectResult(stripIndent`
+  return expectResult(
+    stripIndent`
     const fac = (i) => i === 1 ? 1 : i * fac(i-1);
     fac(5);
-  `).toBe(120)
+  `,
+    { native: true }
+  ).toBe(120)
 })
 
 test('parseError for missing semicolon', () => {
@@ -135,7 +142,7 @@ test('Cannot overwrite consts even when assignment is allowed', () => {
     }
     test();
   `,
-    { chapter: 3 }
+    { chapter: 3, native: true }
   ).toMatchInlineSnapshot(`"Line 3: Cannot assign new value to constant constant."`)
 })
 
@@ -205,7 +212,7 @@ test('Functions passed into non-source functions remain equal', () => {
     }
     identity(t) === t && t(1, 2, 3) === 6;
   `,
-    { chapter: 1, testBuiltins: { 'identity(x)': (x: any) => x } }
+    { chapter: 1, testBuiltins: { 'identity(x)': (x: any) => x }, native: true }
   ).toBe(true)
 })
 
@@ -216,7 +223,7 @@ test('Simple object assignment and retrieval', () => {
     o.a = 1;
     o.a;
   `,
-    { chapter: 100 }
+    { chapter: 100, native: true }
   ).toBe(1)
 })
 
@@ -229,7 +236,7 @@ test('Deep object assignment and retrieval', () => {
     o.a.b.c = "string";
     o.a.b.c;
   `,
-    { chapter: 100 }
+    { chapter: 100, native: true }
   ).toBe('string')
 })
 
@@ -270,83 +277,95 @@ test('Test equal for different lists', () => {
 })
 
 test('true if with empty if works', () => {
-  return expectResult(stripIndent`
+  return expectResult(
+    stripIndent`
     if (true) {
     } else {
     }
-  `).toBe(undefined)
+  `,
+    { native: true }
+  ).toBe(undefined)
 })
 
 test('true if with nonempty if works', () => {
-  return expectResult(stripIndent`
+  return expectResult(
+    stripIndent`
     if (true) {
       1;
     } else {
     }
-  `).toBe(1)
+  `,
+    { native: true }
+  ).toBe(1)
 })
 
 test('false if with empty else works', () => {
-  return expectResult(stripIndent`
+  return expectResult(
+    stripIndent`
     if (false) {
     } else {
     }
-  `).toBe(undefined)
+  `,
+    { native: true }
+  ).toBe(undefined)
 })
 
 test('false if with nonempty if works', () => {
-  return expectResult(stripIndent`
+  return expectResult(
+    stripIndent`
     if (false) {
     } else {
       2;
     }
-  `).toBe(2)
+  `,
+    { native: true }
+  ).toBe(2)
 })
 
 test('test true conditional expression', () => {
-  return expectToMatchJS('true ? true : false;')
+  return expectToMatchJS('true ? true : false;', { native: true })
 })
 
 test('test false conditional expression', () => {
-  return expectToMatchJS('false ? true : false;')
+  return expectToMatchJS('false ? true : false;', { native: true })
 })
 
 test('test false && true', () => {
-  return expectToMatchJS('false && true;')
+  return expectToMatchJS('false && true;', { native: true })
 })
 
 test('test false && false', () => {
-  return expectToMatchJS('false && false;')
+  return expectToMatchJS('false && false;', { native: true })
 })
 
 test('test true && false', () => {
-  return expectToMatchJS('true && false;')
+  return expectToMatchJS('true && false;', { native: true })
 })
 
 test('test true && true', () => {
-  return expectToMatchJS('true && true;')
+  return expectToMatchJS('true && true;', { native: true })
 })
 
 test('test && shortcircuiting', () => {
-  return expectToMatchJS('false && 1();')
+  return expectToMatchJS('false && 1();', { native: true })
 })
 
 test('test false || true', () => {
-  return expectToMatchJS('false || true;')
+  return expectToMatchJS('false || true;', { native: true })
 })
 
 test('test false || false', () => {
-  return expectToMatchJS('false || false;')
+  return expectToMatchJS('false || false;', { native: true })
 })
 
 test('test true || false', () => {
-  return expectToMatchJS('true || false;')
+  return expectToMatchJS('true || false;', { native: true })
 })
 
 test('test true || true', () => {
-  return expectToMatchJS('true || true;')
+  return expectToMatchJS('true || true;', { native: true })
 })
 
 test('test || shortcircuiting', () => {
-  return expectToMatchJS('true || 1();')
+  return expectToMatchJS('true || 1();', { native: true })
 })