From ee9e71da330f7a7a1b6f0e1be04cda32e88a861a Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:29:55 +0800 Subject: [PATCH 01/65] Remove some dependents of interpreter Replaces the depended functions with their equivalents. --- src/__tests__/environmentTree.ts | 2 +- src/mocks/context.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__tests__/environmentTree.ts b/src/__tests__/environmentTree.ts index a06582eb5..b48c6083b 100644 --- a/src/__tests__/environmentTree.ts +++ b/src/__tests__/environmentTree.ts @@ -1,5 +1,5 @@ import { createGlobalEnvironment, EnvTree, EnvTreeNode } from '../createContext' -import { pushEnvironment } from '../interpreter/interpreter' +import { pushEnvironment } from '../ec-evaluator/utils' import { mockContext, mockEnvironment } from '../mocks/context' import { Chapter } from '../types' diff --git a/src/mocks/context.ts b/src/mocks/context.ts index 8f34f86ee..66bd17f75 100644 --- a/src/mocks/context.ts +++ b/src/mocks/context.ts @@ -1,8 +1,8 @@ import * as es from 'estree' import createContext, { EnvTree } from '../createContext' +import { createBlockEnvironment } from '../ec-evaluator/utils' import Closure from '../interpreter/closure' -import { createBlockEnvironment } from '../interpreter/interpreter' import { Chapter, Context, Environment, Frame, Variant } from '../types' export function mockContext( From 8cba6f1b18386bf18969ce7097c2a48c644a36a9 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:28:58 +0800 Subject: [PATCH 02/65] Remove most stuff from interpreter * Remove interpreter (mostly) * Redirect testing to use ec-evaluator * Update snapshots * Update dependents of interpreter --- src/__tests__/__snapshots__/display.ts.snap | 21 - src/__tests__/__snapshots__/index.ts.snap | 135 +-- src/__tests__/__snapshots__/lazy.ts.snap | 39 - src/__tests__/__snapshots__/scmlib.ts.snap | 3 +- src/__tests__/__snapshots__/stdlib.ts.snap | 52 -- src/__tests__/__snapshots__/stringify.ts.snap | 87 +- .../__snapshots__/ec-evaluator.ts.snap | 24 - .../__snapshots__/interpreter-errors.ts.snap | 101 +-- src/interpreter/closure.ts | 2 +- src/interpreter/interpreter.ts | 781 +----------------- .../__snapshots__/allowed-syntax.ts.snap | 178 ---- .../__snapshots__/disallowed-syntax.ts.snap | 35 - src/repl/repl.ts | 8 +- src/runner/sourceRunner.ts | 11 +- .../__tests__/__snapshots__/lazyLists.ts.snap | 492 +---------- .../__tests__/__snapshots__/list.ts.snap | 197 +---- .../__tests__/__snapshots__/stream.ts.snap | 163 +--- src/utils/testing.ts | 2 +- 18 files changed, 66 insertions(+), 2265 deletions(-) diff --git a/src/__tests__/__snapshots__/display.ts.snap b/src/__tests__/__snapshots__/display.ts.snap index 17d11e49e..6b6ccfc32 100644 --- a/src/__tests__/__snapshots__/display.ts.snap +++ b/src/__tests__/__snapshots__/display.ts.snap @@ -106,27 +106,6 @@ Object { } `; -exports[`display can be used to display objects: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display({a: 1, b: 2, c: {d: 3}});", - "displayResult": Array [ - "{\\"a\\": 1, \\"b\\": 2, \\"c\\": {\\"d\\": 3}}", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Object { - "a": 1, - "b": 2, - "c": Object { - "d": 3, - }, - }, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`display second argument can be a string: expectDisplayResult 1`] = ` Object { "alertResult": Array [], diff --git a/src/__tests__/__snapshots__/index.ts.snap b/src/__tests__/__snapshots__/index.ts.snap index 2339d62aa..40d7d2f44 100644 --- a/src/__tests__/__snapshots__/index.ts.snap +++ b/src/__tests__/__snapshots__/index.ts.snap @@ -14,20 +14,6 @@ a[1];", } `; -exports[`Accessing object with nonexistent property returns undefined: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = {}; -o.nonexistent;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Allow display to return value it is displaying: expectResult 1`] = ` Object { "alertResult": Array [], @@ -181,23 +167,6 @@ test();", } `; -exports[`Deep object assignment and retrieval: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = {}; -o.a = {}; -o.a.b = {}; -o.a.b.c = \\"string\\"; -o.a.b.c;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "string", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Empty code returns undefined: expectResult 1`] = ` Object { "alertResult": Array [], @@ -579,55 +548,6 @@ identity(t) === t && t(1, 2, 3) === 6;", } `; -exports[`Multiline string self-evaluates to itself: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "\`1 -1\`;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "1 -1", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Objects toString matches up with JS: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "toString({a: 1});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[object Object]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Rest parameters work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function rest(a, b, ...c) { - let sum = a + b; - for (let i = 0; i < array_length(c); i = i + 1) { - sum = sum + c[i]; - } - return sum; -} -rest(1, 2); // no error -rest(1, 2, ...[3, 4, 5], ...[6, 7], ...[]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 28, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Simple arrow function infinite recursion represents CallExpression well: expectParsedErrorNoErrorSnapshot 1`] = ` Object { "alertResult": Array [], @@ -649,11 +569,11 @@ Object { "displayResult": Array [], "numErrors": 1, "parsedErrors": "Line 1: Maximum call stack size exceeded - x(function f(x) { + x(x => { return x(x)(x); -}).. x(function f(x) { +}).. x(x => { return x(x)(x); -}).. x(function f(x) { +}).. x(x => { return x(x)(x); })..", "result": undefined, @@ -662,21 +582,6 @@ Object { } `; -exports[`Simple object assignment and retrieval: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = {}; -o.a = 1; -o.a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Single boolean self-evaluates to itself: expectResult 1`] = ` Object { "alertResult": Array [], @@ -723,7 +628,8 @@ Object { "displayResult": Array [], "numErrors": 0, "parsedErrors": "", - "result": 60, + "result": "native:60 +interpreted:undefined", "resultStatus": "finished", "visualiseListResult": Array [], } @@ -786,32 +692,6 @@ Object { } `; -exports[`Test equal for different lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "!equal(list(1, 2), pair(1, 2)) && !equal(list(1, 2, 3), list(1, list(2, 3)));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test equal for lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(list(1, 2), pair(1, pair(2, null))) && equal(list(1, 2, 3, 4), list(1, 2, 3, 4));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Test equal for primitives: expectResult 1`] = ` Object { "alertResult": Array [], @@ -866,9 +746,8 @@ toString(a=>a) + toString(f);", "displayResult": Array [], "numErrors": 0, "parsedErrors": "", - "result": "a => afunction f(x) { - return 5; -}", + "result": "native:\\"a => afunction f(x) {\\\\n return 5;\\\\n}\\" +interpreted:\\"a => ax => {\\\\n return 5;\\\\n}\\"", "resultStatus": "finished", "visualiseListResult": Array [], } diff --git a/src/__tests__/__snapshots__/lazy.ts.snap b/src/__tests__/__snapshots__/lazy.ts.snap index 58034a994..6afc6ad90 100644 --- a/src/__tests__/__snapshots__/lazy.ts.snap +++ b/src/__tests__/__snapshots__/lazy.ts.snap @@ -35,28 +35,6 @@ res;", } `; -exports[`Tail calls work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(a, b) { - return a === 1 ? a : b; -} - -function test2(a) { - return test(a, head(null)); -} - -const res = test2(1); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Thunks are memoized: expectResult 1`] = ` Object { "alertResult": Array [], @@ -119,20 +97,3 @@ res;", "visualiseListResult": Array [], } `; - -exports[`Unused arguments are not evaluated: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(a, b, c, d, e, f) { - return a; -} -const res = test(1, head(null), 1 + '', !1, '' - 1, head(head(null))); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/scmlib.ts.snap b/src/__tests__/__snapshots__/scmlib.ts.snap index 6b4087707..59bcac4bd 100644 --- a/src/__tests__/__snapshots__/scmlib.ts.snap +++ b/src/__tests__/__snapshots__/scmlib.ts.snap @@ -1502,7 +1502,8 @@ Object { "displayResult": Array [], "numErrors": 0, "parsedErrors": "", - "result": 1, + "result": "native:1 +interpreted:undefined", "resultStatus": "finished", "visualiseListResult": Array [], } diff --git a/src/__tests__/__snapshots__/stdlib.ts.snap b/src/__tests__/__snapshots__/stdlib.ts.snap index 6bfca218b..d9d0699d6 100644 --- a/src/__tests__/__snapshots__/stdlib.ts.snap +++ b/src/__tests__/__snapshots__/stdlib.ts.snap @@ -408,32 +408,6 @@ Object { } `; -exports[`Builtins work as expected 31: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object({});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 32: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object({a: 1});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Builtins work as expected 33: expectResult 1`] = ` Object { "alertResult": Array [], @@ -551,32 +525,6 @@ Object { } `; -exports[`Builtins work as expected 42: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "has_own_property({a: 1, b: 2}, 'a');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 43: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "has_own_property({a: 1, b: 2}, 'c');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Builtins work as expected 44: expectResult 1`] = ` Object { "alertResult": Array [], diff --git a/src/__tests__/__snapshots__/stringify.ts.snap b/src/__tests__/__snapshots__/stringify.ts.snap index 609e54290..494c56d15 100644 --- a/src/__tests__/__snapshots__/stringify.ts.snap +++ b/src/__tests__/__snapshots__/stringify.ts.snap @@ -44,31 +44,6 @@ stringify(f);", } `; -exports[`String representation of big objects are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = { a: 1, b: true, c: () => 1, d: { e: 5, f: 6 }, g: 0, h: 0, i: 0, j: 0, k: 0, l: 0, m: 0, n: 0}; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "{ \\"a\\": 1, - \\"b\\": true, - \\"c\\": () => 1, - \\"d\\": {\\"e\\": 5, \\"f\\": 6}, - \\"g\\": 0, - \\"h\\": 0, - \\"i\\": 0, - \\"j\\": 0, - \\"k\\": 0, - \\"l\\": 0, - \\"m\\": 0, - \\"n\\": 0}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`String representation of booleans are nice: expectResult 1`] = ` Object { "alertResult": Array [], @@ -121,9 +96,8 @@ stringify(f);", "displayResult": Array [], "numErrors": 0, "parsedErrors": "", - "result": "function f(x, y) { - return x; -}", + "result": "native:\\"function f(x, y) {\\\\n return x;\\\\n}\\" +interpreted:\\"(x, y) => {\\\\n return x;\\\\n}\\"", "resultStatus": "finished", "visualiseListResult": Array [], } @@ -373,35 +347,6 @@ stringify(xs);", } `; -exports[`String representation of nested objects are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = { a: 1, b: true, c: () => 1, d: { e: 5, f: 6 } }; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "{\\"a\\": 1, \\"b\\": true, \\"c\\": () => 1, \\"d\\": {\\"e\\": 5, \\"f\\": 6}}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of nested objects are nice: expectResult 2`] = ` -Object { - "alertResult": Array [], - "code": "let o = {}; -o.o = o; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "{\\"o\\": ...}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`String representation of null is nice: expectResult 1`] = ` Object { "alertResult": Array [], @@ -428,34 +373,6 @@ Object { } `; -exports[`String representation of objects are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = { a: 1, b: true, c: () => 1 }; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "{\\"a\\": 1, \\"b\\": true, \\"c\\": () => 1}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of objects with toReplString member calls toReplString: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = { toReplString: () => '' }; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`String representation of strings are nice: expectResult 1`] = ` Object { "alertResult": Array [], diff --git a/src/ec-evaluator/__tests__/__snapshots__/ec-evaluator.ts.snap b/src/ec-evaluator/__tests__/__snapshots__/ec-evaluator.ts.snap index de50c488d..d59ffc7c0 100644 --- a/src/ec-evaluator/__tests__/__snapshots__/ec-evaluator.ts.snap +++ b/src/ec-evaluator/__tests__/__snapshots__/ec-evaluator.ts.snap @@ -381,30 +381,6 @@ test();", } `; -exports[`streams and its pre-defined/pre-built functions are working as intended: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function make_alternating_stream(stream) { - return pair(head(stream), () => make_alternating_stream( - negate_whole_stream( - stream_tail(stream)))); -} - -function negate_whole_stream(stream) { - return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); -} - -const ones = pair(1, () => ones); -list_ref(eval_stream(make_alternating_stream(enum_stream(1, 9)), 9), 8);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 9, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`streams can be created and functions with no return statements are still evaluated properly: expectResult 1`] = ` Object { "alertResult": Array [], diff --git a/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap b/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap index 4f232772a..2997d1fe9 100644 --- a/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap +++ b/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap @@ -1,18 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Access local property: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 0})[\\"a\\"];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Builtins don't create additional errors when it's not their fault: expectParsedError 1`] = ` Object { "alertResult": Array [], @@ -45,8 +32,9 @@ function negate_whole_stream(stream) { const ones = pair(1, () => ones); eval_stream(make_alternating_stream(enum_stream(1, 9)), 10);", "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 8: Error: head(xs) expects a pair as argument xs, but encountered null", + "numErrors": 2, + "parsedErrors": "native:\\"Line 8: Error: head(xs) expects a pair as argument xs, but encountered null\\" +interpreted:\\"Line 8: Error: head(xs) expects a pair as argument xs, but encountered undefined\\\\nLine 463: Name n not declared.\\"", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], @@ -70,19 +58,6 @@ h(null);", } `; -exports[`Error when accessing inherited property of object: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "({}).valueOf;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Cannot read inherited property valueOf of {}.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - exports[`Error when assigning to builtin - verbose: expectParsedError 1`] = ` Object { "alertResult": Array [], @@ -479,51 +454,6 @@ Object { } `; -exports[`Error when calling non function value object - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -({a: 1})();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Calling non-function value {\\"a\\": 1}. -Because {\\"a\\": 1} is not a function, you cannot run {\\"a\\": 1}(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value object - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -({a: 1})();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Calling non-function value {\\"a\\": 1}. -Because {\\"a\\": 1} is not a function, you cannot run {\\"a\\": 1}(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value object: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1})();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value {\\"a\\": 1}.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - exports[`Error when calling non function value true - verbose: expectParsedError 1`] = ` Object { "alertResult": Array [], @@ -982,7 +912,8 @@ Object { f.prototype;", "displayResult": Array [], "numErrors": 1, - "parsedErrors": "Line 4: Expected object or array, got function.", + "parsedErrors": "native:\\"Line 4: Expected object or array, got function.\\" +interpreted:\\"Line 4: Name prototype not declared.\\"", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], @@ -995,20 +926,8 @@ Object { "code": "null.prop;", "displayResult": Array [], "numErrors": 1, - "parsedErrors": "Line 1: Expected object or array, got null.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when accessing property of string: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "'hi'.length;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected object or array, got string.", + "parsedErrors": "native:\\"Line 1: Expected object or array, got null.\\" +interpreted:\\"Line 1: Name prop not declared.\\"", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], @@ -1024,7 +943,8 @@ Object { f.prop = 5;", "displayResult": Array [], "numErrors": 1, - "parsedErrors": "Line 4: Expected object or array, got function.", + "parsedErrors": "native:\\"Line 4: Expected object or array, got function.\\" +interpreted:\\"Line 4: Name prop not declared.\\"", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], @@ -1037,7 +957,8 @@ Object { "code": "'hi'.prop = 5;", "displayResult": Array [], "numErrors": 1, - "parsedErrors": "Line 1: Expected object or array, got string.", + "parsedErrors": "native:\\"Line 1: Expected object or array, got string.\\" +interpreted:\\"Line 1: Name prop not declared.\\"", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], diff --git a/src/interpreter/closure.ts b/src/interpreter/closure.ts index de9a3fbaa..52553cbf2 100644 --- a/src/interpreter/closure.ts +++ b/src/interpreter/closure.ts @@ -12,7 +12,7 @@ import { identifier, returnStatement } from '../utils/astCreator' -import { apply } from './interpreter' +import { apply } from './interpreter-non-det' const closureToJS = (value: Closure, context: Context, klass: string) => { function DummyClass(this: Closure) { diff --git a/src/interpreter/interpreter.ts b/src/interpreter/interpreter.ts index f883d3a95..ee29b55a1 100644 --- a/src/interpreter/interpreter.ts +++ b/src/interpreter/interpreter.ts @@ -1,104 +1,15 @@ /* tslint:disable:max-classes-per-file */ import * as es from 'estree' -import { isEmpty, uniqueId } from 'lodash' -import { UNKNOWN_LOCATION } from '../constants' -import { LazyBuiltIn } from '../createContext' +import { createBlockEnvironment, pushEnvironment } from '../ec-evaluator/utils' import * as errors from '../errors/errors' import { RuntimeSourceError } from '../errors/runtimeSourceError' import { UndefinedImportError } from '../modules/errors' import { initModuleContext, loadModuleBundle } from '../modules/moduleLoader' import { ModuleFunctions } from '../modules/moduleTypes' import { checkEditorBreakpoints } from '../stdlib/inspector' -import { Context, ContiguousArrayElements, Environment, Frame, Value, Variant } from '../types' +import { Context, Value } from '../types' import assert from '../utils/assert' -import * as create from '../utils/astCreator' -import { conditionalExpression, literal, primitive } from '../utils/astCreator' -import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators' -import * as rttc from '../utils/rttc' -import Closure from './closure' - -class BreakValue {} - -class ContinueValue {} - -class ReturnValue { - constructor(public value: Value) {} -} - -class TailCallReturnValue { - constructor(public callee: Closure, public args: Value[], public node: es.CallExpression) {} -} - -class Thunk { - public value: Value - public isMemoized: boolean - constructor(public exp: es.Node, public env: Environment) { - this.isMemoized = false - this.value = null - } -} - -const delayIt = (exp: es.Node, env: Environment): Thunk => new Thunk(exp, env) - -function* forceIt(val: any, context: Context): Value { - if (val instanceof Thunk) { - if (val.isMemoized) return val.value - - pushEnvironment(context, val.env) - const evalRes = yield* actualValue(val.exp, context) - popEnvironment(context) - val.value = evalRes - val.isMemoized = true - return evalRes - } else return val -} - -export function* actualValue(exp: es.Node, context: Context): Value { - const evalResult = yield* evaluate(exp, context) - const forced = yield* forceIt(evalResult, context) - return forced -} - -const createEnvironment = ( - closure: Closure, - args: Value[], - callExpression?: es.CallExpression -): Environment => { - const environment: Environment = { - name: closure.functionName, // TODO: Change this - tail: closure.environment, - head: {}, - id: uniqueId() - } - if (callExpression) { - environment.callExpression = { - ...callExpression, - arguments: args.map(primitive) - } - } - closure.node.params.forEach((param, index) => { - if (param.type === 'RestElement') { - environment.head[(param.argument as es.Identifier).name] = args.slice(index) - } else { - environment.head[(param as es.Identifier).name] = args[index] - } - }) - return environment -} - -export const createBlockEnvironment = ( - context: Context, - name = 'blockEnvironment', - head: Frame = {} -): Environment => { - return { - name, - tail: currentEnvironment(context), - head, - id: uniqueId() - } -} const handleRuntimeError = (context: Context, error: RuntimeSourceError): never => { context.errors.push(error) @@ -124,30 +35,6 @@ function declareIdentifier(context: Context, name: string, node: es.Node) { return environment } -function declareVariables(context: Context, node: es.VariableDeclaration) { - for (const declaration of node.declarations) { - declareIdentifier(context, (declaration.id as es.Identifier).name, node) - } -} - -function declareFunctionsAndVariables(context: Context, node: es.BlockStatement) { - for (const statement of node.body) { - switch (statement.type) { - case 'VariableDeclaration': - declareVariables(context, statement) - break - case 'FunctionDeclaration': - if (statement.id === null) { - throw new Error( - 'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.' - ) - } - declareIdentifier(context, statement.id.name, statement) - break - } - } -} - function defineVariable(context: Context, name: string, value: Value, constant = false) { const environment = currentEnvironment(context) @@ -180,535 +67,6 @@ function* leave(context: Context) { } const currentEnvironment = (context: Context) => context.runtime.environments[0] -const replaceEnvironment = (context: Context, environment: Environment) => { - context.runtime.environments[0] = environment - context.runtime.environmentTree.insert(environment) -} -const popEnvironment = (context: Context) => context.runtime.environments.shift() -export const pushEnvironment = (context: Context, environment: Environment) => { - context.runtime.environments.unshift(environment) - context.runtime.environmentTree.insert(environment) -} - -const getVariable = (context: Context, name: string) => { - let environment: Environment | null = currentEnvironment(context) - while (environment) { - if (environment.head.hasOwnProperty(name)) { - if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { - return handleRuntimeError( - context, - new errors.UnassignedVariable(name, context.runtime.nodes[0]) - ) - } else { - return environment.head[name] - } - } else { - environment = environment.tail - } - } - return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) -} - -const setVariable = (context: Context, name: string, value: any) => { - let environment: Environment | null = currentEnvironment(context) - while (environment) { - if (environment.head.hasOwnProperty(name)) { - if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { - break - } - const descriptors = Object.getOwnPropertyDescriptors(environment.head) - if (descriptors[name].writable) { - environment.head[name] = value - return undefined - } - return handleRuntimeError( - context, - new errors.ConstAssignment(context.runtime.nodes[0]!, name) - ) - } else { - environment = environment.tail - } - } - return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) -} - -const checkNumberOfArguments = ( - context: Context, - callee: Closure | Value, - args: Value[], - exp: es.CallExpression -) => { - if (callee instanceof Closure) { - const params = callee.node.params - const hasVarArgs = params[params.length - 1]?.type === 'RestElement' - if (hasVarArgs ? params.length - 1 > args.length : params.length !== args.length) { - return handleRuntimeError( - context, - new errors.InvalidNumberOfArguments( - exp, - hasVarArgs ? params.length - 1 : params.length, - args.length, - hasVarArgs - ) - ) - } - } else { - const hasVarArgs = callee.minArgsNeeded != undefined - if (hasVarArgs ? callee.minArgsNeeded > args.length : callee.length !== args.length) { - return handleRuntimeError( - context, - new errors.InvalidNumberOfArguments( - exp, - hasVarArgs ? callee.minArgsNeeded : callee.length, - args.length, - hasVarArgs - ) - ) - } - } - return undefined -} - -function* getArgs(context: Context, call: es.CallExpression) { - const args = [] - for (const arg of call.arguments) { - if (context.variant === Variant.LAZY) { - args.push(delayIt(arg, currentEnvironment(context))) - } else if (arg.type === 'SpreadElement') { - args.push(...(yield* actualValue(arg.argument, context))) - } else { - args.push(yield* actualValue(arg, context)) - } - } - return args -} - -function transformLogicalExpression(node: es.LogicalExpression): es.ConditionalExpression { - if (node.operator === '&&') { - return conditionalExpression(node.left, node.right, literal(false), node.loc) - } else { - return conditionalExpression(node.left, literal(true), node.right, node.loc) - } -} - -function* reduceIf( - node: es.IfStatement | es.ConditionalExpression, - context: Context -): IterableIterator { - const test = yield* actualValue(node.test, context) - - const error = rttc.checkIfStatement(node, test, context.chapter) - if (error) { - return handleRuntimeError(context, error) - } - - return test ? node.consequent : node.alternate -} - -export type Evaluator = (node: T, context: Context) => IterableIterator - -function* evaluateBlockStatement(context: Context, node: es.BlockStatement) { - declareFunctionsAndVariables(context, node) - let result - for (const statement of node.body) { - result = yield* evaluate(statement, context) - if ( - result instanceof ReturnValue || - result instanceof TailCallReturnValue || - result instanceof BreakValue || - result instanceof ContinueValue - ) { - break - } - } - return result -} - -/** - * WARNING: Do not use object literal shorthands, e.g. - * { - * *Literal(node: es.Literal, ...) {...}, - * *ThisExpression(node: es.ThisExpression, ..._ {...}, - * ... - * } - * They do not minify well, raising uncaught syntax errors in production. - * See: https://github.com/webpack/webpack/issues/7566 - */ -// tslint:disable:object-literal-shorthand -// prettier-ignore -export const evaluators: { [nodeType: string]: Evaluator } = { - /** Simple Values */ - Literal: function*(node: es.Literal, _context: Context) { - return node.value - }, - - TemplateLiteral: function*(node: es.TemplateLiteral) { - // Expressions like `${1}` are not allowed, so no processing needed - return node.quasis[0].value.cooked - }, - - ThisExpression: function*(node: es.ThisExpression, context: Context) { - return currentEnvironment(context).thisContext - }, - - ArrayExpression: function*(node: es.ArrayExpression, context: Context) { - const res = [] - for (const n of node.elements as ContiguousArrayElements) { - res.push(yield* evaluate(n, context)) - } - return res - }, - - DebuggerStatement: function*(node: es.DebuggerStatement, context: Context) { - context.runtime.break = true - yield - }, - - FunctionExpression: function*(node: es.FunctionExpression, context: Context) { - return new Closure(node, currentEnvironment(context), context) - }, - - ArrowFunctionExpression: function*(node: es.ArrowFunctionExpression, context: Context) { - return Closure.makeFromArrowFunction(node, currentEnvironment(context), context) - }, - - Identifier: function*(node: es.Identifier, context: Context) { - return getVariable(context, node.name) - }, - - CallExpression: function*(node: es.CallExpression, context: Context) { - const callee = yield* actualValue(node.callee, context) - const args = yield* getArgs(context, node) - let thisContext - if (node.callee.type === 'MemberExpression') { - thisContext = yield* actualValue(node.callee.object, context) - } - const result = yield* apply(context, callee, args, node, thisContext) - return result - }, - - NewExpression: function*(node: es.NewExpression, context: Context) { - const callee = yield* evaluate(node.callee, context) - const args = [] - for (const arg of node.arguments) { - args.push(yield* evaluate(arg, context)) - } - const obj: Value = {} - if (callee instanceof Closure) { - obj.__proto__ = callee.fun.prototype - callee.fun.apply(obj, args) - } else { - obj.__proto__ = callee.prototype - callee.apply(obj, args) - } - return obj - }, - - UnaryExpression: function*(node: es.UnaryExpression, context: Context) { - const value = yield* actualValue(node.argument, context) - - const error = rttc.checkUnaryExpression(node, node.operator, value, context.chapter) - if (error) { - return handleRuntimeError(context, error) - } - return evaluateUnaryExpression(node.operator, value) - }, - - BinaryExpression: function*(node: es.BinaryExpression, context: Context) { - const left = yield* actualValue(node.left, context) - const right = yield* actualValue(node.right, context) - const error = rttc.checkBinaryExpression(node, node.operator, context.chapter, left, right) - if (error) { - return handleRuntimeError(context, error) - } - return evaluateBinaryExpression(node.operator, left, right) - }, - - ConditionalExpression: function*(node: es.ConditionalExpression, context: Context) { - return yield* this.IfStatement(node, context) - }, - - LogicalExpression: function*(node: es.LogicalExpression, context: Context) { - return yield* this.ConditionalExpression(transformLogicalExpression(node), context) - }, - - VariableDeclaration: function*(node: es.VariableDeclaration, context: Context) { - const declaration = node.declarations[0] - const constant = node.kind === 'const' - const id = declaration.id as es.Identifier - const value = yield* evaluate(declaration.init!, context) - defineVariable(context, id.name, value, constant) - return undefined - }, - - ContinueStatement: function*(_node: es.ContinueStatement, _context: Context) { - return new ContinueValue() - }, - - BreakStatement: function*(_node: es.BreakStatement, _context: Context) { - return new BreakValue() - }, - - ForStatement: function*(node: es.ForStatement, context: Context) { - // Create a new block scope for the loop variables - const loopEnvironment = createBlockEnvironment(context, 'forLoopEnvironment') - pushEnvironment(context, loopEnvironment) - - const initNode = node.init! - const testNode = node.test! - const updateNode = node.update! - if (initNode.type === 'VariableDeclaration') { - declareVariables(context, initNode) - } - yield* actualValue(initNode, context) - - let value - while (yield* actualValue(testNode, context)) { - // create block context and shallow copy loop environment head - // see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation - // and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ - // We copy this as a const to avoid ES6 funkiness when mutating loop vars - // https://github.com/source-academy/js-slang/issues/65#issuecomment-425618227 - const environment = createBlockEnvironment(context, 'forBlockEnvironment') - pushEnvironment(context, environment) - for (const name in loopEnvironment.head) { - if (loopEnvironment.head.hasOwnProperty(name)) { - declareIdentifier(context, name, node) - defineVariable(context, name, loopEnvironment.head[name], true) - } - } - - value = yield* actualValue(node.body, context) - - // Remove block context - popEnvironment(context) - if (value instanceof ContinueValue) { - value = undefined - } - if (value instanceof BreakValue) { - value = undefined - break - } - if (value instanceof ReturnValue || value instanceof TailCallReturnValue) { - break - } - - yield* actualValue(updateNode, context) - } - - popEnvironment(context) - - return value - }, - - MemberExpression: function*(node: es.MemberExpression, context: Context) { - let obj = yield* actualValue(node.object, context) - if (obj instanceof Closure) { - obj = obj.fun - } - let prop - if (node.computed) { - prop = yield* actualValue(node.property, context) - } else { - prop = (node.property as es.Identifier).name - } - - const error = rttc.checkMemberAccess(node, obj, prop) - if (error) { - return handleRuntimeError(context, error) - } - - if ( - obj !== null && - obj !== undefined && - typeof obj[prop] !== 'undefined' && - !obj.hasOwnProperty(prop) - ) { - return handleRuntimeError(context, new errors.GetInheritedPropertyError(node, obj, prop)) - } - try { - return obj[prop] - } catch { - return handleRuntimeError(context, new errors.GetPropertyError(node, obj, prop)) - } - }, - - AssignmentExpression: function*(node: es.AssignmentExpression, context: Context) { - if (node.left.type === 'MemberExpression') { - const left = node.left - const obj = yield* actualValue(left.object, context) - let prop - if (left.computed) { - prop = yield* actualValue(left.property, context) - } else { - prop = (left.property as es.Identifier).name - } - - const error = rttc.checkMemberAccess(node, obj, prop) - if (error) { - return handleRuntimeError(context, error) - } - - const val = yield* evaluate(node.right, context) - try { - obj[prop] = val - } catch { - return handleRuntimeError(context, new errors.SetPropertyError(node, obj, prop)) - } - return val - } - const id = node.left as es.Identifier - // Make sure it exist - const value = yield* evaluate(node.right, context) - setVariable(context, id.name, value) - return value - }, - - FunctionDeclaration: function*(node: es.FunctionDeclaration, context: Context) { - const id = node.id - if (id === null) { - throw new Error("Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.") - } - // tslint:disable-next-line:no-any - const closure = new Closure(node, currentEnvironment(context), context) - defineVariable(context, id.name, closure, true) - return undefined - }, - - IfStatement: function*(node: es.IfStatement | es.ConditionalExpression, context: Context) { - const result = yield* reduceIf(node, context) - if (result === null) { - return undefined; - } - return yield* evaluate(result, context) - }, - - ExpressionStatement: function*(node: es.ExpressionStatement, context: Context) { - return yield* evaluate(node.expression, context) - }, - - ReturnStatement: function*(node: es.ReturnStatement, context: Context) { - let returnExpression = node.argument! - - // If we have a conditional expression, reduce it until we get something else - while ( - returnExpression.type === 'LogicalExpression' || - returnExpression.type === 'ConditionalExpression' - ) { - if (returnExpression.type === 'LogicalExpression') { - returnExpression = transformLogicalExpression(returnExpression) - } - returnExpression = yield* reduceIf(returnExpression, context) - } - - // If we are now left with a CallExpression, then we use TCO - if (returnExpression.type === 'CallExpression' && context.variant !== Variant.LAZY) { - const callee = yield* actualValue(returnExpression.callee, context) - const args = yield* getArgs(context, returnExpression) - return new TailCallReturnValue(callee, args, returnExpression) - } else { - return new ReturnValue(yield* evaluate(returnExpression, context)) - } - }, - - WhileStatement: function*(node: es.WhileStatement, context: Context) { - let value: any // tslint:disable-line - while ( - // tslint:disable-next-line - (yield* actualValue(node.test, context)) && - !(value instanceof ReturnValue) && - !(value instanceof BreakValue) && - !(value instanceof TailCallReturnValue) - ) { - value = yield* actualValue(node.body, context) - } - if (value instanceof BreakValue) { - return undefined - } - return value - }, - - ObjectExpression: function*(node: es.ObjectExpression, context: Context) { - const obj = {} - for (const propUntyped of node.properties) { - // node.properties: es.Property | es.SpreadExpression, but - // our Acorn is set to ES6 which cannot have a es.SpreadExpression - // at this point. Force the type. - const prop = propUntyped as es.Property - let key - if (prop.key.type === 'Identifier') { - key = prop.key.name - } else { - key = yield* evaluate(prop.key, context) - } - obj[key] = yield* evaluate(prop.value, context) - } - return obj - }, - - BlockStatement: function*(node: es.BlockStatement, context: Context) { - // Create a new environment (block scoping) - const environment = createBlockEnvironment(context, 'blockEnvironment') - pushEnvironment(context, environment) - const result: Value = yield* evaluateBlockStatement(context, node) - popEnvironment(context) - return result - }, - - ImportDeclaration: function*(node: es.ImportDeclaration, context: Context) { - throw new Error('ImportDeclarations should already have been removed') - }, - - ExportNamedDeclaration: function*(_node: es.ExportNamedDeclaration, _context: Context) { - // Exports are handled as a separate pre-processing step in 'transformImportedFile'. - // Subsequently, they are removed from the AST by 'removeExports' before the AST is evaluated. - // As such, there should be no ExportNamedDeclaration nodes in the AST. - throw new Error('Encountered an ExportNamedDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.') - }, - - ExportDefaultDeclaration: function*(_node: es.ExportDefaultDeclaration, _context: Context) { - // Exports are handled as a separate pre-processing step in 'transformImportedFile'. - // Subsequently, they are removed from the AST by 'removeExports' before the AST is evaluated. - // As such, there should be no ExportDefaultDeclaration nodes in the AST. - throw new Error('Encountered an ExportDefaultDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.') - }, - - ExportAllDeclaration: function*(_node: es.ExportAllDeclaration, _context: Context) { - // Exports are handled as a separate pre-processing step in 'transformImportedFile'. - // Subsequently, they are removed from the AST by 'removeExports' before the AST is evaluated. - // As such, there should be no ExportAllDeclaration nodes in the AST. - throw new Error('Encountered an ExportAllDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.') - }, - - Program: function*(node: es.BlockStatement, context: Context) { - throw new Error('A program should not contain another program within itself') - } -} -// tslint:enable:object-literal-shorthand - -// TODO: move to util -/** - * Checks if `env` is empty (that is, head of env is an empty object) - */ -function isEmptyEnvironment(env: Environment) { - return isEmpty(env.head) -} - -/** - * Extracts the non-empty tail environment from the given environment and - * returns current environment if tail environment is a null. - */ -function getNonEmptyEnv(environment: Environment): Environment { - if (isEmptyEnvironment(environment)) { - const tailEnvironment = environment.tail - if (tailEnvironment === null) { - return environment - } - return getNonEmptyEnv(tailEnvironment) - } else { - return environment - } -} export function* evaluateProgram( program: es.Program, @@ -727,6 +85,7 @@ export function* evaluateProgram( try { for (const node of program.body) { + console.log(node) if (node.type !== 'ImportDeclaration') { otherNodes.push(node as es.Statement) continue @@ -746,6 +105,7 @@ export function* evaluateProgram( } const functions = moduleFunctions[moduleName] + console.log('tests') for (const spec of node.specifiers) { assert( @@ -758,140 +118,15 @@ export function* evaluateProgram( } declareIdentifier(context, spec.local.name, node) - defineVariable(context, spec.local.name, functions[spec.imported.name], true) + const importedObj = functions[spec.imported.name] + console.log('hi2') + Object.defineProperty(importedObj, 'name', { value: spec.local.name }) + defineVariable(context, spec.local.name, importedObj, true) } yield* leave(context) } } catch (error) { handleRuntimeError(context, error) } - - const newProgram = create.blockStatement(otherNodes) - const result = yield* forceIt(yield* evaluateBlockStatement(context, newProgram), context) - yield* leave(context) // Done visiting program - - if (result instanceof Closure) { - Object.defineProperty(getNonEmptyEnv(currentEnvironment(context)).head, uniqueId(), { - value: result, - writable: false, - enumerable: true - }) - } - return result -} - -function* evaluate(node: es.Node, context: Context) { - yield* visit(context, node) - const result = yield* evaluators[node.type](node, context) - yield* leave(context) - if (result instanceof Closure) { - Object.defineProperty(getNonEmptyEnv(currentEnvironment(context)).head, uniqueId(), { - value: result, - writable: false, - enumerable: true - }) - } - return result -} - -export function* apply( - context: Context, - fun: Closure | Value, - args: (Thunk | Value)[], - node: es.CallExpression, - thisContext?: Value -) { - let result: Value - let total = 0 - - while (!(result instanceof ReturnValue)) { - if (fun instanceof Closure) { - checkNumberOfArguments(context, fun, args, node!) - const environment = createEnvironment(fun, args, node) - if (result instanceof TailCallReturnValue) { - replaceEnvironment(context, environment) - } else { - pushEnvironment(context, environment) - total++ - } - const bodyEnvironment = createBlockEnvironment(context, 'functionBodyEnvironment') - bodyEnvironment.thisContext = thisContext - pushEnvironment(context, bodyEnvironment) - result = yield* evaluateBlockStatement(context, fun.node.body as es.BlockStatement) - popEnvironment(context) - if (result instanceof TailCallReturnValue) { - fun = result.callee - node = result.node - args = result.args - } else if (!(result instanceof ReturnValue)) { - // No Return Value, set it as undefined - result = new ReturnValue(undefined) - } - } else if (fun instanceof LazyBuiltIn) { - try { - let finalArgs = args - if (fun.evaluateArgs) { - finalArgs = [] - for (const arg of args) { - finalArgs.push(yield* forceIt(arg, context)) - } - } - result = fun.func.apply(thisContext, finalArgs) - break - } catch (e) { - // Recover from exception - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - - const loc = node.loc ?? UNKNOWN_LOCATION - if (!(e instanceof RuntimeSourceError || e instanceof errors.ExceptionError)) { - // The error could've arisen when the builtin called a source function which errored. - // If the cause was a source error, we don't want to include the error. - // However if the error came from the builtin itself, we need to handle it. - return handleRuntimeError(context, new errors.ExceptionError(e, loc)) - } - result = undefined - throw e - } - } else if (typeof fun === 'function') { - checkNumberOfArguments(context, fun, args, node!) - try { - const forcedArgs = [] - - for (const arg of args) { - forcedArgs.push(yield* forceIt(arg, context)) - } - - result = fun.apply(thisContext, forcedArgs) - break - } catch (e) { - // Recover from exception - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - - const loc = node.loc ?? UNKNOWN_LOCATION - if (!(e instanceof RuntimeSourceError || e instanceof errors.ExceptionError)) { - // The error could've arisen when the builtin called a source function which errored. - // If the cause was a source error, we don't want to include the error. - // However if the error came from the builtin itself, we need to handle it. - return handleRuntimeError(context, new errors.ExceptionError(e, loc)) - } - result = undefined - throw e - } - } else { - return handleRuntimeError(context, new errors.CallingNonFunctionValue(fun, node)) - } - } - // Unwraps return value and release stack environment - if (result instanceof ReturnValue) { - result = result.value - } - for (let i = 1; i <= total; i++) { - popEnvironment(context) - } - return result } diff --git a/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap b/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap index bfc5a3d5e..5b6531f86 100644 --- a/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap +++ b/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap @@ -2190,30 +2190,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 17: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -for (let j = 0; j < 5; j = j + 1) { - if (j < 1) { - continue; - } else { - i = i + 1; - if (j > 2) { - break; - } - } -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 18: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -3052,22 +3028,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 24: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y, ...z) { - return x + y; -} -f(...[1, 2]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 25: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -3100,19 +3060,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 25: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Object {}, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 26: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -3193,22 +3140,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 26: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Object { - "a": 1, - "b": 2, - }, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 27: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -3304,19 +3235,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 27: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2})['a'];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 28: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -3413,19 +3331,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 28: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2}).a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 29: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -3522,19 +3427,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 29: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({'a': 1, 'b': 2}).a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 30: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -3630,19 +3522,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 30: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({1: 1, 2: 2})['1'];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 31: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -3772,20 +3651,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 31: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "const key = 'a'; -({a: 1, b: 2})[key];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 32: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -3931,20 +3796,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 32: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -x.a = 3;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 33: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -4089,20 +3940,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 33: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -x['a'] = 3;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 34: fails a chapter below 1`] = ` Object { "alertResult": Array [], @@ -4272,21 +4109,6 @@ Object { } `; -exports[`Syntaxes are allowed in the chapter they are introduced 34: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -const key = 'a'; -x[key] = 3;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Syntaxes are allowed in the chapter they are introduced 35: fails a chapter below 1`] = ` Object { "alertResult": Array [], diff --git a/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap b/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap index 79ab4aa80..fd28ad2e7 100644 --- a/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap +++ b/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap @@ -1260,41 +1260,6 @@ Every statement must be terminated by a semicolon. } `; -exports[`no this, no new - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function Box() { - this[0] = 5; -} -const box = new Box();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected string as prop, got number. -Expected string as prop, got number. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no this, no new: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function Box() { - this[0] = 5; -} -const box = new Box();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected string as prop, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - exports[`no try statements - verbose: expectParsedError 1`] = ` Object { "alertResult": Array [], diff --git a/src/repl/repl.ts b/src/repl/repl.ts index b3b4f7f47..1f2921025 100644 --- a/src/repl/repl.ts +++ b/src/repl/repl.ts @@ -9,7 +9,7 @@ import { ExecutionMethod, Variant } from '../types' function startRepl( chapter = 1, - executionMethod: ExecutionMethod = 'interpreter', + executionMethod: ExecutionMethod = 'ec-evaluator', variant: Variant = Variant.DEFAULT, useSubst: boolean = false, useRepl: boolean, @@ -113,10 +113,10 @@ function main() { } const executionMethod = - opt.options.variant === 'interpreter' || - opt.options.variant === 'non-det' || - opt.options.variant === 'explicit-control' + opt.options.variant === 'non-det' ? 'interpreter' + : opt.options.variant === 'explicit-control' + ? 'ec-evaluator' : 'native' const useSubst = opt.options.variant === 'substituter' const useRepl = !opt.options.e diff --git a/src/runner/sourceRunner.ts b/src/runner/sourceRunner.ts index 4a5310475..4abb63a79 100644 --- a/src/runner/sourceRunner.ts +++ b/src/runner/sourceRunner.ts @@ -4,7 +4,7 @@ import type { RawSourceMap } from 'source-map' import type { IOptions, Result } from '..' import { JSSLANG_PROPERTIES, UNKNOWN_LOCATION } from '../constants' -import { ECEResultPromise, evaluate as ECEvaluate } from '../ec-evaluator/interpreter' +import { ECEResultPromise, evaluate } from '../ec-evaluator/interpreter' import { ExceptionError } from '../errors/errors' import { CannotFindModuleError } from '../errors/localImportErrors' import { RuntimeSourceError } from '../errors/runtimeSourceError' @@ -12,7 +12,6 @@ import { TimeoutError } from '../errors/timeoutErrors' import { transpileToGPU } from '../gpu/gpu' import { isPotentialInfiniteLoop } from '../infiniteLoops/errors' import { testForInfiniteLoop } from '../infiniteLoops/runtime' -import { evaluateProgram as evaluate } from '../interpreter/interpreter' import { nonDetEvaluate } from '../interpreter/interpreter-non-det' import { transpileToLazy } from '../lazy/lazy' import preprocessFileImports from '../localImports/preprocessor' @@ -113,7 +112,7 @@ async function runSubstitution( } function runInterpreter(program: es.Program, context: Context, options: IOptions): Promise { - let it = evaluate(program, context, true, true) + let it = evaluate(program, context, options) let scheduler: Scheduler if (context.variant === Variant.NON_DET) { it = nonDetEvaluate(program, context) @@ -219,7 +218,7 @@ async function runNative( } function runECEvaluator(program: es.Program, context: Context, options: IOptions): Promise { - const value = ECEvaluate(program, context, options) + const value = evaluate(program, context, options) return ECEResultPromise(context, value) } @@ -277,7 +276,9 @@ export async function sourceRunner( theOptions ) } - return runECEvaluator(program, context, theOptions) + const res = await runECEvaluator(program, context, theOptions) + console.log(res) + return Promise.resolve(res) } if (context.executionMethod === 'native') { diff --git a/src/stdlib/__tests__/__snapshots__/lazyLists.ts.snap b/src/stdlib/__tests__/__snapshots__/lazyLists.ts.snap index dba6ef575..dd2af90f7 100644 --- a/src/stdlib/__tests__/__snapshots__/lazyLists.ts.snap +++ b/src/stdlib/__tests__/__snapshots__/lazyLists.ts.snap @@ -1,94 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`accumulate: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`append left list is infinite: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair(1,a); -const b = append(a, list(3,4)); -list_ref(b,200);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`append right list is infinite: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair(1,a); -const b = append(list(3,4),a); -list_ref(b,200);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`append: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(append(list(123, 123), list(456, 456, 456)), list(123, 123, 456, 456, 456));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`arguments are not evaluated for list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "head(list(1,head(null)));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`arguments are not evaluated for pair: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "head(pair(1,head(null)));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`bad index error list_ref: expectParsedError 1`] = ` Object { "alertResult": Array [], "code": "list_ref(list(1, 2, 3), 3);", "displayResult": Array [], "numErrors": 1, - "parsedErrors": "Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null", + "parsedErrors": "native:\\"Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null\\" +interpreted:\\"Line 1: Calling non-function value [object Object].\\"", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], @@ -101,7 +20,8 @@ Object { "code": "list_ref(list(1, 2, 3), -1);", "displayResult": Array [], "numErrors": 1, - "parsedErrors": "Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null", + "parsedErrors": "native:\\"Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null\\" +interpreted:\\"Line 1: Calling non-function value [object Object].\\"", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], @@ -114,7 +34,8 @@ Object { "code": "list_ref(list(1, 2, 3), 1.5);", "displayResult": Array [], "numErrors": 1, - "parsedErrors": "Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null", + "parsedErrors": "native:\\"Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null\\" +interpreted:\\"Line 1: Calling non-function value [object Object].\\"", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], @@ -127,7 +48,8 @@ Object { "code": "list_ref(list(1, 2, 3), '1');", "displayResult": Array [], "numErrors": 1, - "parsedErrors": "Line 149: Expected string on right hand side of operation, got number.", + "parsedErrors": "native:\\"Line 149: Expected string on right hand side of operation, got number.\\" +interpreted:\\"Line 1: Calling non-function value [object Object].\\"", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], @@ -185,401 +107,3 @@ Object { "visualiseListResult": Array [], } `; - -exports[`build_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(build_list(x => x * x, 5), list(0, 1, 4, 9, 16));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`empty list is null: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`enum_list with floats: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(enum_list(1.5, 5), list(1.5, 2.5, 3.5, 4.5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`enum_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(enum_list(1, 5), list(1, 2, 3, 4, 5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`filter on infinite lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair(1,pair(2,a)); -const b = filter(x => x % 2 === 0,a); -list_ref(b,1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`filter: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(filter(x => x <= 4, list(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)), list(2, 1, 3, 4, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for_each: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let sum = 0; -for_each(x => { - sum = sum + x; -}, list(1, 2, 3)); -sum;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`head works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "head(pair(1, 'a string \\"\\"'));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`is_list on infinite lists works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = list(1,a); -is_list(a);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`list_ref on infinite lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair(1,a); -list_ref(a,200);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`list_ref: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3, \\"4\\", 4), 4);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`list_to_string: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list_to_string(list(1, 2, 3));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[1,[2,[3,null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`map on infinite lists works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair(1,a); -const b = map(x => 2 * x, a); -list_ref(b,200);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`map on infinite lists works: expectResult 2`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair(1,a); -const b = map(x => 2 * x, a); -list_ref(b,200);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`map: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(map(x => 2 * x, list(12, 11, 3)), list(24, 22, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`member: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal( - member(4, list(1, 2, 3, 4, 123, 456, 789)), - list(4, 123, 456, 789));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`pair creates pair: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_pair (pair(1, 'a string \\"\\"'));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`recursive list definitions are possible (head): expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = list (1,a); -head(a) + head(head(tail(a)));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`recursive pair definitions are possible (head): expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair (a,1); -tail(a) + tail(head(a));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`recursive pair definitions are possible (tail): expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair (1,a); -head(a) + head(tail(a));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove all ones on infinite list of ones and twos: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair(1,pair(2,a)); -const b = remove_all(1,a); -list_ref(b,200);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove not found: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal (remove(2, list(1)),list(1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove on infinite list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = pair(1,a); -const b = remove(1,a); -list_ref(b,200);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "remove(1, list(1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove_all not found: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(remove_all(1, list(2, 3, 4)), list(2, 3, 4));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove_all: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(remove_all(1, list(1, 2, 3, 4, 1, 1, 1, 5, 1, 1, 6)), list(2, 3, 4, 5, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`reverse: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(reverse(list(\\"string\\", \\"null\\", \\"undefined\\", \\"null\\", 123)), list(123, \\"null\\", \\"undefined\\", \\"null\\", \\"string\\"));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`tail of a 1 element list is null: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(list(1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`tail works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(pair(1, 'a string \\"\\"'));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "a string \\"\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/stdlib/__tests__/__snapshots__/list.ts.snap b/src/stdlib/__tests__/__snapshots__/list.ts.snap index e0d1de59a..b545f8a4f 100644 --- a/src/stdlib/__tests__/__snapshots__/list.ts.snap +++ b/src/stdlib/__tests__/__snapshots__/list.ts.snap @@ -325,32 +325,6 @@ Object { } `; -exports[`append: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(append(list(123, 123), list(456, 456, 456)), list(123, 123, 456, 456, 456));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`build_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(build_list(x => x * x, 5), list(0, 1, 4, 9, 16));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`display_list MCE fuzz test: expectDisplayResult 1`] = ` Object { "alertResult": Array [], @@ -733,32 +707,6 @@ Object { } `; -exports[`enum_list with floats: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(enum_list(1.5, 5), list(1.5, 2.5, 3.5, 4.5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`enum_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(enum_list(1, 5), list(1, 2, 3, 4, 5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`equal: expectResult 1`] = ` Object { "alertResult": Array [], @@ -772,19 +720,6 @@ Object { } `; -exports[`filter: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(filter(x => x <= 4, list(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)), list(2, 1, 3, 4, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`for_each: expectResult 1`] = ` Object { "alertResult": Array [], @@ -851,25 +786,20 @@ list(1, 'a string \\"\\"', () => f, f, true, 3.14);", "displayResult": Array [], "numErrors": 0, "parsedErrors": "", - "result": Array [ - 1, - Array [ - "a string \\"\\"", - Array [ - [Function], - Array [ - [Function], - Array [ - true, - Array [ - 3.14, - null, - ], - ], - ], - ], - ], - ], + "result": "native:[ 1, +[ \\"a string \\\\\\"\\\\\\"\\", +[ () => f, +[ function f() { + return 1; + }, +[true, [3.14, null]]]]]] +interpreted:[ 1, +[ \\"a string \\\\\\"\\\\\\"\\", +[ () => f, +[ () => { + return 1; + }, +[true, [3.14, null]]]]]]", "resultStatus": "finished", "visualiseListResult": Array [], } @@ -901,34 +831,6 @@ Object { } `; -exports[`map: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(map(x => 2 * x, list(12, 11, 3)), list(24, 22, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`member: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal( - member(4, list(1, 2, 3, 4, 123, 456, 789)), - list(4, 123, 456, 789));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`non-list error head: expectParsedError 1`] = ` Object { "alertResult": Array [], @@ -1000,77 +902,6 @@ Object { } `; -exports[`remove_all not found: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(remove_all(1, list(2, 3, 4)), list(2, 3, 4));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove_all: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(remove_all(1, list(1, 2, 3, 4, 1, 1, 1, 5, 1, 1, 6)), list(2, 3, 4, 5, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`reverse: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(reverse(list(\\"string\\", \\"null\\", \\"undefined\\", \\"null\\", 123)), list(123, \\"null\\", \\"undefined\\", \\"null\\", \\"string\\"));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`set_head: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let p = pair(1, 2); -const q = p; -set_head(p, 3); -p === q && equal(p, pair(3, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`set_tail: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let p = pair(1, 2); -const q = p; -set_tail(p, 3); -p === q && equal(p, pair(1, 3));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`tail of a 1 element list is null: expectResult 1`] = ` Object { "alertResult": Array [], diff --git a/src/stdlib/__tests__/__snapshots__/stream.ts.snap b/src/stdlib/__tests__/__snapshots__/stream.ts.snap index 4ab231de7..37c28046f 100644 --- a/src/stdlib/__tests__/__snapshots__/stream.ts.snap +++ b/src/stdlib/__tests__/__snapshots__/stream.ts.snap @@ -1,75 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`append: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(stream_append(stream(\\"string\\", 123), stream(456, null, undefined))) - , list(\\"string\\", 123, 456, null, undefined));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`build_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(build_stream(x => x * x, 5)), list(0, 1, 4, 9, 16));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`enum_list with floats: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(enum_stream(1.5, 5)), list(1.5, 2.5, 3.5, 4.5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`enum_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(enum_stream(1, 5)), list(1, 2, 3, 4, 5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`filter: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal( - stream_to_list( - stream_filter(x => x <= 4, stream(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)) - ) -, list(2, 1, 3, 4, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`for_each: expectResult 1`] = ` Object { "alertResult": Array [], @@ -100,34 +30,6 @@ Object { } `; -exports[`map: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(stream_map(x => 2 * x, stream(12, 11, 3))), list(24, 22, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`member: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal( - stream_to_list(stream_member(\\"string\\", stream(1, 2, 3, \\"string\\", 123, 456, null, undefined))), - list(\\"string\\", 123, 456, null, undefined));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`primitive stream functions empty stream is null: expectResult 1`] = ` Object { "alertResult": Array [], @@ -164,10 +66,8 @@ Object { "displayResult": Array [], "numErrors": 0, "parsedErrors": "", - "result": Array [ - 1, - [Function], - ], + "result": "native:[1, () => integers_from(n + 1)] +interpreted:undefined", "resultStatus": "finished", "visualiseListResult": Array [], } @@ -230,22 +130,6 @@ Object { } `; -exports[`remove not found: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stream_to_list(stream_remove(2, stream(1)));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - null, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`remove: expectResult 1`] = ` Object { "alertResult": Array [], @@ -258,46 +142,3 @@ Object { "visualiseListResult": Array [], } `; - -exports[`remove_all not found: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(stream_remove_all(1, stream(2, 3, \\"1\\"))), list(2, 3, \\"1\\"));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove_all: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(stream_remove_all(1, stream(1, 2, 3, 4, 1, 1, \\"1\\", 5, 1, 1, 6))), - list(2, 3, 4, \\"1\\", 5, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`reverse: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list( - stream_reverse( - stream(\\"string\\", null, undefined, null, 123))), -list(123, null, undefined, null, \\"string\\"));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/utils/testing.ts b/src/utils/testing.ts index 8b1d0be1a..4327a2942 100644 --- a/src/utils/testing.ts +++ b/src/utils/testing.ts @@ -117,7 +117,7 @@ async function testInContext(code: string, options: TestOptions): Promise Date: Sun, 18 Feb 2024 11:40:24 +0800 Subject: [PATCH 03/65] Fix errors post-merge --- src/__tests__/environmentTree.ts | 2 +- src/interpreter/interpreter.ts | 2 +- src/mocks/context.ts | 2 +- src/repl/repl.ts | 4 ++-- src/utils/testing.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/__tests__/environmentTree.ts b/src/__tests__/environmentTree.ts index b48c6083b..9b24043c5 100644 --- a/src/__tests__/environmentTree.ts +++ b/src/__tests__/environmentTree.ts @@ -1,5 +1,5 @@ import { createGlobalEnvironment, EnvTree, EnvTreeNode } from '../createContext' -import { pushEnvironment } from '../ec-evaluator/utils' +import { pushEnvironment } from '../cse-machine/utils' import { mockContext, mockEnvironment } from '../mocks/context' import { Chapter } from '../types' diff --git a/src/interpreter/interpreter.ts b/src/interpreter/interpreter.ts index ee29b55a1..508ec3f4e 100644 --- a/src/interpreter/interpreter.ts +++ b/src/interpreter/interpreter.ts @@ -1,7 +1,7 @@ /* tslint:disable:max-classes-per-file */ import * as es from 'estree' -import { createBlockEnvironment, pushEnvironment } from '../ec-evaluator/utils' +import { createBlockEnvironment, pushEnvironment } from '../cse-machine/utils' import * as errors from '../errors/errors' import { RuntimeSourceError } from '../errors/runtimeSourceError' import { UndefinedImportError } from '../modules/errors' diff --git a/src/mocks/context.ts b/src/mocks/context.ts index 0a361f014..645aa0e97 100644 --- a/src/mocks/context.ts +++ b/src/mocks/context.ts @@ -1,7 +1,7 @@ import * as es from 'estree' import createContext, { EnvTree } from '../createContext' -import { createBlockEnvironment } from '../ec-evaluator/utils' +import { createBlockEnvironment } from '../cse-machine/utils' import Closure from '../interpreter/closure' import { Chapter, Context, Environment, Frame, Variant } from '../types' diff --git a/src/repl/repl.ts b/src/repl/repl.ts index 1f2921025..9695296d4 100644 --- a/src/repl/repl.ts +++ b/src/repl/repl.ts @@ -9,7 +9,7 @@ import { ExecutionMethod, Variant } from '../types' function startRepl( chapter = 1, - executionMethod: ExecutionMethod = 'ec-evaluator', + executionMethod: ExecutionMethod = 'cse-machine', variant: Variant = Variant.DEFAULT, useSubst: boolean = false, useRepl: boolean, @@ -116,7 +116,7 @@ function main() { opt.options.variant === 'non-det' ? 'interpreter' : opt.options.variant === 'explicit-control' - ? 'ec-evaluator' + ? 'cse-machine' : 'native' const useSubst = opt.options.variant === 'substituter' const useRepl = !opt.options.e diff --git a/src/utils/testing.ts b/src/utils/testing.ts index 4327a2942..2380e5acf 100644 --- a/src/utils/testing.ts +++ b/src/utils/testing.ts @@ -117,7 +117,7 @@ async function testInContext(code: string, options: TestOptions): Promise Date: Thu, 2 May 2024 17:55:04 -0400 Subject: [PATCH 04/65] Fix bug in analyzer where entrypoint was being visited twice --- .../preprocessor/__tests__/analyzer.ts | 227 ++++++++++-------- src/modules/preprocessor/analyzer.ts | 2 + 2 files changed, 130 insertions(+), 99 deletions(-) diff --git a/src/modules/preprocessor/__tests__/analyzer.ts b/src/modules/preprocessor/__tests__/analyzer.ts index 40078d6cc..9fb837f8e 100644 --- a/src/modules/preprocessor/__tests__/analyzer.ts +++ b/src/modules/preprocessor/__tests__/analyzer.ts @@ -9,9 +9,6 @@ import { Chapter } from '../../../types' import { stripIndent } from '../../../utils/formatters' import parseProgramsAndConstructImportGraph from '../linker' import analyzeImportsAndExports from '../analyzer' -import { parse } from '../../../parser/parser' -import { mockContext } from '../../../mocks/context' -import type { Program } from 'estree' import loadSourceModules from '../../loader' import type { SourceFiles as Files } from '../../moduleTypes' import { objectKeys } from '../../../utils/misc' @@ -22,6 +19,44 @@ beforeEach(() => { jest.clearAllMocks() }) + async function testCode( + files: T, + entrypointFilePath: keyof T, + allowUndefinedImports: boolean, + throwOnDuplicateNames: boolean + ) { + const context = createContext(Chapter.FULL_JS) + const importGraphResult = await parseProgramsAndConstructImportGraph( + p => Promise.resolve(files[p]), + entrypointFilePath as string, + context, + {}, + true + ) + + // Return 'undefined' if there are errors while parsing. + if (context.errors.length !== 0 || !importGraphResult.ok) { + throw context.errors[0] + } + + const { programs, topoOrder, sourceModulesToImport } = importGraphResult + await loadSourceModules(sourceModulesToImport, context, false) + + try { + analyzeImportsAndExports(programs, entrypointFilePath as string, topoOrder, context, { + allowUndefinedImports, + throwOnDuplicateNames + }) + } catch (error) { + if (!(error instanceof DuplicateImportNameError) && !(error instanceof UndefinedNamespaceImportError)) { + throw error + } + + return error + } + return true + } + describe('Test throwing import validation errors', () => { type ErrorInfo = { line: number @@ -47,34 +82,8 @@ describe('Test throwing import validation errors', () => { type ImportTestCaseWithError = [...ImportTestCaseWithNoError, ErrorInfo] type ImportTestCase = ImportTestCaseWithError | ImportTestCaseWithNoError - async function testCode( - files: T, - entrypointFilePath: keyof T, - allowUndefinedImports: boolean, - throwOnDuplicateNames: boolean - ) { - const context = createContext(Chapter.FULL_JS) - const importGraphResult = await parseProgramsAndConstructImportGraph( - p => Promise.resolve(files[p]), - entrypointFilePath as string, - context, - {}, - true - ) - - // Return 'undefined' if there are errors while parsing. - if (context.errors.length !== 0 || !importGraphResult.ok) { - throw context.errors[0] - } - - const { programs, topoOrder, sourceModulesToImport } = importGraphResult - await loadSourceModules(sourceModulesToImport, context, false) - - analyzeImportsAndExports(programs, entrypointFilePath as string, topoOrder, context, { - allowUndefinedImports, - throwOnDuplicateNames - }) - return true + function expectValidationError(obj: any): asserts obj is UndefinedNamespaceImportError { + expect(obj).toBeInstanceOf(UndefinedNamespaceImportError) } async function testFailure( @@ -83,15 +92,11 @@ describe('Test throwing import validation errors', () => { allowUndefinedImports: boolean, errInfo: ErrorInfo ) { - let err: any = null - try { - await testCode(files, entrypointFilePath, allowUndefinedImports, false) - } catch (error) { - err = error - } + const err = await testCode(files, entrypointFilePath, allowUndefinedImports, false) - expect(err).not.toEqual(null) + expectValidationError(err) expect(err.moduleName).toEqual(errInfo.moduleName) + switch (errInfo.type) { case 'namespace': { // Check namespace import @@ -104,7 +109,7 @@ describe('Test throwing import validation errors', () => { } default: { expect(err).toBeInstanceOf(UndefinedImportError) - expect(err.symbol).toEqual(errInfo.symbol) + expect((err as UndefinedImportError).symbol).toEqual(errInfo.symbol) } } @@ -644,25 +649,16 @@ describe('Test throwing DuplicateImportNameErrors', () => { c.length === 2 type FullTestCase = - | [string, Record, true, string | undefined] - | [string, Record, false, undefined] + | [string, Files, true, string | undefined] + | [string, Files, false, undefined] + + function expectDuplicateError(obj: any): asserts obj is DuplicateImportNameError { + expect(obj).toBeInstanceOf(DuplicateImportNameError) + } function testCases(desc: string, cases: TestCase[]) { const [allNoCases, allYesCases] = cases.reduce( ([noThrow, yesThrow], c, i) => { - const context = mockContext(Chapter.LIBRARY_PARSER) - const programs = Object.entries(c[1]).reduce((res, [name, file]) => { - const parsed = parse(file!, context, { sourceFile: name }) - if (!parsed) { - console.error(context.errors[0]) - throw new Error('Failed to parse code!') - } - return { - ...res, - [name]: parsed - } - }, {} as Record) - // For each test case, split it into the case where throwOnDuplicateImports is true // and when it is false. No errors should ever be thrown when throwOnDuplicateImports is false if (isTestCaseWithNoError(c)) { @@ -671,13 +667,13 @@ describe('Test throwing DuplicateImportNameErrors', () => { const [desc] = c const noThrowCase: FullTestCase = [ `${i + 1}. ${desc}: no error `, - programs, + c[1], false, undefined ] const yesThrowCase: FullTestCase = [ `${i + 1}. ${desc}: no error`, - programs, + c[1], true, undefined ] @@ -690,11 +686,11 @@ describe('Test throwing DuplicateImportNameErrors', () => { const [desc, , errMsg] = c const noThrowCase: FullTestCase = [ `${i + 1}. ${desc}: no error`, - programs, + c[1], false, undefined ] - const yesThrowCase: FullTestCase = [`${i + 1}. ${desc}: error`, programs, true, errMsg] + const yesThrowCase: FullTestCase = [`${i + 1}. ${desc}: error`, c[1], true, errMsg] return [ [...noThrow, noThrowCase], [...yesThrow, yesThrowCase] @@ -705,36 +701,26 @@ describe('Test throwing DuplicateImportNameErrors', () => { const caseTester: (...args: FullTestCase) => Promise = async ( _, - programs, + files, shouldThrow, errMsg ) => { - const context = createContext(Chapter.FULL_JS) - const [entrypointFilePath, ...topoOrder] = objectKeys(programs) - - await loadSourceModules(new Set(['one_module', 'another_module']), context, false) - - const runTest = () => - analyzeImportsAndExports(programs, entrypointFilePath, topoOrder, context, { - allowUndefinedImports: true, - throwOnDuplicateNames: shouldThrow - }) + const [entrypointFilePath] = objectKeys(files) + const promise = testCode(files, entrypointFilePath, true, shouldThrow) if (!shouldThrow || errMsg === undefined) { - expect(runTest).not.toThrow() + return expect(promise).resolves.toEqual(true) } - try { - runTest() - } catch (err) { - expect(err).toBeInstanceOf(DuplicateImportNameError) - // Make sure the locations are always displayed in order - // for consistency across tests (ok since locString should be order agnostic) - const segments = (err.locString as string).split(',').map(each => each.trim()) - segments.sort() + const err = await promise + expectDuplicateError(err) - expect(segments.join(', ')).toEqual(errMsg) - } + // Make sure the locations are always displayed in order + // for consistency across tests (ok since locString should be order agnostic) + const segments = err.locString.split(',').map(each => each.trim()) + segments.sort() + + expect(segments.join(', ')).toEqual(errMsg) } describe(`${desc} with throwOnDuplicateImports false`, () => @@ -747,7 +733,9 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different imports from different Source modules across multiple files', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { bar as a } from 'another_module';` }, '(/a.js:1:9), (/b.js:1:9)' @@ -763,7 +751,9 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different imports including default imports across multiple files', { - '/a.js': `import a from 'one_module';`, + '/a.js': `import a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'another_module';` }, '(/a.js:1:7), (/b.js:1:7)' @@ -771,7 +761,9 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different imports of different types from Source modules', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'another_module';` }, '(/a.js:1:9), (/b.js:1:7)' @@ -779,14 +771,19 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different imports from both Source and local modules', { - '/a.js': `import { foo as a } from 'one_module';`, - '/b.js': `import { a } from './c.js';` + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, + '/b.js': `import { c } from './c.js';`, + '/c.js': 'export function c() {}' } ], [ 'Namespace imports from Source modules', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import * as a from 'another_module';` }, '(/a.js:1:7), (/b.js:1:7)' @@ -794,7 +791,10 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Three conflicting imports', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + import { c } from './c.js'; + `, '/b.js': `import a from 'another_module';`, '/c.js': `import { foo as a } from 'one_module';` }, @@ -806,14 +806,21 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Same import across multiple files 1', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { foo as a } from 'one_module';` } ], [ 'Same import across multiple files 2', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + + import { b } from './b.js'; + import { c } from './c.js'; + import { d } from './d.js'; + `, '/b.js': `import { foo as a } from 'one_module';`, '/c.js': `import { foo as b } from 'one_module';`, '/d.js': `import { foo as b } from 'one_module';` @@ -822,7 +829,9 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different import across multiple files', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { bar as a } from 'one_module';` }, '(/a.js:1:9), (/b.js:1:9)' @@ -830,21 +839,27 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different namespace imports across multiple files', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import * as a from 'one_module';` } ], [ 'Same default import across multiple files', { - '/a.js': `import a from 'one_module';`, + '/a.js': `import a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'one_module';` } ], [ 'Different types of imports across multiple files 1', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'one_module';` }, '(/a.js:1:9), (/b.js:1:7)' @@ -852,7 +867,9 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different types of imports across multiple files 2', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'one_module';` }, '(/a.js:1:7), (/b.js:1:7)' @@ -860,7 +877,10 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different types of imports across multiple files 3', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + import { c } from './c.js'; + `, '/b.js': `import a from 'one_module';`, '/c.js': `import * as a from 'one_module';` }, @@ -869,7 +889,10 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different types of imports across multiple files 4', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + import { c } from './c.js'; + `, '/b.js': `import a from 'one_module';`, '/c.js': `import { foo as a } from 'one_module';` }, @@ -878,21 +901,27 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Handles aliasing correctly 1', { - '/a.js': `import a from 'one_module';`, + '/a.js': `import a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { default as a } from 'one_module';` } ], [ 'Handles aliasing correctly 2', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { foo } from 'one_module';` } ], [ 'Handles aliasing correctly 3', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { a as foo } from 'one_module';` } ] diff --git a/src/modules/preprocessor/analyzer.ts b/src/modules/preprocessor/analyzer.ts index d066a50dc..4841865be 100644 --- a/src/modules/preprocessor/analyzer.ts +++ b/src/modules/preprocessor/analyzer.ts @@ -61,6 +61,8 @@ export default function analyzeImportsAndExports( Object.entries(loadedModules).map(([name, obj]) => [name, new Set(Object.keys(obj))]) ) + topoOrder = topoOrder.filter(p => p !== entrypointFilePath) + for (const sourceModule of [...topoOrder, entrypointFilePath]) { const program = programs[sourceModule] moduleDocs[sourceModule] = new Set() From 93819d80bbe72d4adda958a212e7d99b77325a3d Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Thu, 2 May 2024 18:12:14 -0400 Subject: [PATCH 05/65] straight up remove interpreter --- src/__tests__/__snapshots__/index.ts.snap | 26 + src/__tests__/environment.ts | 8 +- .../__snapshots__/cse-machine.ts.snap | 24 + src/cse-machine/__tests__/cse-machine-heap.ts | 4 +- src/cse-machine/interpreter.ts | 15 +- .../__snapshots__/interpreter-errors.ts.snap | 1027 --------------- .../__tests__/interpreter-errors.ts | 1127 ----------------- src/interpreter/closure.ts | 133 -- src/interpreter/interpreter-non-det.ts | 698 ---------- src/interpreter/interpreter.ts | 119 -- src/mocks/context.ts | 10 +- .../__snapshots__/allowed-syntax.ts.snap | 24 + src/repl/main.ts | 2 - src/repl/repl-non-det.ts | 129 -- src/runner/sourceRunner.ts | 20 +- .../__tests__/__snapshots__/list.ts.snap | 164 +++ .../__tests__/__snapshots__/stream.ts.snap | 157 +++ src/stdlib/misc.ts | 4 +- src/types.ts | 2 +- src/utils/statementSeqTransform.ts | 2 +- src/utils/stringify.ts | 2 +- 21 files changed, 425 insertions(+), 3272 deletions(-) delete mode 100644 src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap delete mode 100644 src/interpreter/__tests__/interpreter-errors.ts delete mode 100644 src/interpreter/closure.ts delete mode 100644 src/interpreter/interpreter-non-det.ts delete mode 100644 src/interpreter/interpreter.ts delete mode 100644 src/repl/repl-non-det.ts diff --git a/src/__tests__/__snapshots__/index.ts.snap b/src/__tests__/__snapshots__/index.ts.snap index 40d7d2f44..01db19b3c 100644 --- a/src/__tests__/__snapshots__/index.ts.snap +++ b/src/__tests__/__snapshots__/index.ts.snap @@ -692,6 +692,32 @@ Object { } `; +exports[`Test equal for different lists: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "!equal(list(1, 2), pair(1, 2)) && !equal(list(1, 2, 3), list(1, list(2, 3)));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`Test equal for lists: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(list(1, 2), pair(1, pair(2, null))) && equal(list(1, 2, 3, 4), list(1, 2, 3, 4));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`Test equal for primitives: expectResult 1`] = ` Object { "alertResult": Array [], diff --git a/src/__tests__/environment.ts b/src/__tests__/environment.ts index e6b8b7b7a..b979f0665 100644 --- a/src/__tests__/environment.ts +++ b/src/__tests__/environment.ts @@ -1,10 +1,10 @@ import { Program } from 'estree' -import { evaluateProgram as evaluate } from '../interpreter/interpreter' import { mockContext } from '../mocks/context' import { parse } from '../parser/parser' import { Chapter } from '../types' import { stripIndent } from '../utils/formatters' +import { evaluate } from '../cse-machine/interpreter' test('Function params and body identifiers are in different environment', () => { const code = stripIndent` @@ -18,7 +18,11 @@ test('Function params and body identifiers are in different environment', () => const context = mockContext(Chapter.SOURCE_4) context.prelude = null // hide the unneeded prelude const parsed = parse(code, context) - const it = evaluate(parsed as any as Program, context) + const it = evaluate(parsed as any as Program, context, { + envSteps: -1, + stepLimit: 1000, + isPrelude: false + }) const stepsToComment = 13 // manually counted magic number for (let i = 0; i < stepsToComment; i += 1) { it.next() diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap index 6e96d6149..68b85f4c7 100644 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap +++ b/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap @@ -506,6 +506,30 @@ test();", } `; +exports[`streams and its pre-defined/pre-built functions are working as intended: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "function make_alternating_stream(stream) { + return pair(head(stream), () => make_alternating_stream( + negate_whole_stream( + stream_tail(stream)))); +} + +function negate_whole_stream(stream) { + return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); +} + +const ones = pair(1, () => ones); +list_ref(eval_stream(make_alternating_stream(enum_stream(1, 9)), 9), 8);", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": 9, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`streams can be created and functions with no return statements are still evaluated properly: expectResult 1`] = ` Object { "alertResult": Array [], diff --git a/src/cse-machine/__tests__/cse-machine-heap.ts b/src/cse-machine/__tests__/cse-machine-heap.ts index 3565ce6d7..0027d5480 100644 --- a/src/cse-machine/__tests__/cse-machine-heap.ts +++ b/src/cse-machine/__tests__/cse-machine-heap.ts @@ -11,7 +11,7 @@ test('Heap works correctly', () => { expect(heap1.getHeap()).toMatchInlineSnapshot(`Set {}`) const arr = [0] as EnvArray - const closure = mockClosure(true) + const closure = mockClosure() heap1.add(arr, closure) heap1.add(arr) expect(heap1.contains([0] as EnvArray)).toMatchInlineSnapshot(`false`) @@ -28,7 +28,7 @@ test('Heap works correctly', () => { `) const heap2 = new Heap() - expect(heap1.move(mockClosure(true), heap2)).toMatchInlineSnapshot(`false`) + expect(heap1.move(mockClosure(), heap2)).toMatchInlineSnapshot(`false`) expect(heap1.move(arr, heap2)).toMatchInlineSnapshot(`true`) expect(heap1.contains(arr)).toMatchInlineSnapshot(`false`) expect(heap1.getHeap()).toMatchInlineSnapshot(` diff --git a/src/cse-machine/interpreter.ts b/src/cse-machine/interpreter.ts index 40057b6d0..273430774 100644 --- a/src/cse-machine/interpreter.ts +++ b/src/cse-machine/interpreter.ts @@ -6,15 +6,14 @@ */ /* tslint:disable:max-classes-per-file */ -import * as es from 'estree' +import type es from 'estree' import { isArray, reverse } from 'lodash' -import { IOptions } from '..' import { UNKNOWN_LOCATION } from '../constants' import * as errors from '../errors/errors' import { RuntimeSourceError } from '../errors/runtimeSourceError' import { checkEditorBreakpoints } from '../stdlib/inspector' -import { Context, ContiguousArrayElements, Result, Value, type StatementSequence } from '../types' +import type { Context, ContiguousArrayElements, Result, Value, StatementSequence } from '../types' import * as ast from '../utils/ast/astCreator' import { filterImportDeclarations } from '../utils/ast/helpers' import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators' @@ -23,7 +22,7 @@ import * as seq from '../utils/statementSeqTransform' import { checkProgramForUndefinedVariables } from '../validator/validator' import Closure from './closure' import { - Continuation, + type Continuation, getContinuationControl, getContinuationEnv, getContinuationStash, @@ -172,6 +171,12 @@ export class Stash extends Stack { return newStash } } +export interface CSEMachineOptions { + envSteps: number, + stepLimit: number, + isPrelude: boolean +} + /** * Function to be called when a program is to be interpreted using @@ -181,7 +186,7 @@ export class Stash extends Stack { * @param context The context to evaluate the program in. * @returns The result of running the CSE machine. */ -export function evaluate(program: es.Program, context: Context, options: IOptions): Value { +export function evaluate(program: es.Program, context: Context, options: CSEMachineOptions): Value { try { checkProgramForUndefinedVariables(program, context) } catch (error) { diff --git a/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap b/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap deleted file mode 100644 index 2997d1fe9..000000000 --- a/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap +++ /dev/null @@ -1,1027 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Builtins don't create additional errors when it's not their fault: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return a; -} -map(f, list(1, 2));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Name a not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cascading js errors work properly 1: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function make_alternating_stream(stream) { - return pair(head(stream), () => make_alternating_stream( - negate_whole_stream( - stream_tail(stream)))); -} - -function negate_whole_stream(stream) { - return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); -} - -const ones = pair(1, () => ones); -eval_stream(make_alternating_stream(enum_stream(1, 9)), 10);", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "native:\\"Line 8: Error: head(xs) expects a pair as argument xs, but encountered null\\" -interpreted:\\"Line 8: Error: head(xs) expects a pair as argument xs, but encountered undefined\\\\nLine 463: Name n not declared.\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cascading js errors work properly: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function h(p) { - return head(p); -} - -h(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Error: head(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -map = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Cannot assign new value to constant map. -As map was declared as a constant, its value cannot be changed. You will have to declare a new variable. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -undefined = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Cannot assign new value to constant undefined. -As undefined was declared as a constant, its value cannot be changed. You will have to declare a new variable. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "map = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Cannot assign new value to constant map.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "undefined = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Cannot assign new value to constant undefined.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function in tail call with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -const g = () => 1; -const f = x => g(x); -f(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 15: Expected 0 arguments, but got 1. -Try calling function g again, but with 0 arguments instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function in tail call with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const g = () => 1; -const f = x => g(x); -f(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 0 arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too few arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - const f = x => x; - f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected 1 arguments, but got 0. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too few arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - const f = x => x; - f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected 1 arguments, but got 2. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling builtin function in with too few arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int(\\"\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 2 arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling builtin function in with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(1, 2, 3);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 arguments, but got 3.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function from member expression with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - const f = [x => x]; - f[0](1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected 1 arguments, but got 2. -Try calling function f[0] again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function from member expression with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = [x => x]; -f[0](1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too few arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - function f(x) { - return x; - } - f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 5, Column 2: Expected 1 arguments, but got 0. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too few arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Expected 1 arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - function f(x) { - return x; - } - f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 5, Column 2: Expected 1 arguments, but got 2. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value "string" - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - 'string'();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value \\"string\\". -Because \\"string\\" is not a function, you cannot run \\"string\\"(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value "string": expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "'string'();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value \\"string\\".", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value 0 - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - 0();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value 0. -Because 0 is not a function, you cannot run 0(). If you were planning to perform multiplication by 0, you need to use the * operator. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value 0: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "0();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value array - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -[1]();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Calling non-function value [1]. -Because [1] is not a function, you cannot run [1](). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value array: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[1]();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value [1].", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value null - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - null();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: null literals are not allowed. -They're not part of the Source §1 specs. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value null: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "null();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: null literals are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value true - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - true();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value true. -Because true is not a function, you cannot run true(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value true: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "true();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value true.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - undefined();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value undefined. -Because undefined is not a function, you cannot run undefined(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined with arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - undefined(1, true);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value undefined. -Because undefined is not a function, you cannot run undefined(1, true). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined with arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "undefined(1, true);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value undefined.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "undefined();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value undefined.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring const after function --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() {} -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 6: SyntaxError: Identifier 'f' has already been declared (3:6) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring const after function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() {} -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring constant as variable: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring constant: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after const --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -const f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after const: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after function --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() {} -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() {} -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after let --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after let: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring let after function --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() {} -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 4: SyntaxError: Identifier 'f' has already been declared (3:4) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring let after function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() {} -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring variable as constant: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let f = x => x; -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring variable: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let f = x => x; -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error with too few arguments passed to rest parameters: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function rest(a, b, ...c) {} -rest(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 2 or more arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error with too many arguments passed to math_sin: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "math_sin(7,8);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Nice errors when errors occur inside builtins: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int(\\"10\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 2 arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Nice errors when errors occur inside builtins: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"'\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling display function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(1); -display(1, \\"test\\");", - "displayResult": Array [ - "1", - "test 1", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling list function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list(); -list(1); -list(1, 2, 3); -list(1, 2, 3, 4, 5, 6, 6);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - 2, - Array [ - 3, - Array [ - 4, - Array [ - 5, - Array [ - 6, - Array [ - 6, - null, - ], - ], - ], - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling math_max function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "math_max(); -math_max(1, 2); -math_max(1, 2, 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling math_min function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "math_min(); -math_min(1, 2); -math_min(1, 2, 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling stringify function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(1, 2); -stringify(1, 2, 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "1", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when accessing property of function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - return 1; -} -f.prototype;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "native:\\"Line 4: Expected object or array, got function.\\" -interpreted:\\"Line 4: Name prototype not declared.\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when accessing property of null: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "null.prop;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "native:\\"Line 1: Expected object or array, got null.\\" -interpreted:\\"Line 1: Name prop not declared.\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when assigning property of function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - return 1; -} -f.prop = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "native:\\"Line 4: Expected object or array, got function.\\" -interpreted:\\"Line 4: Name prop not declared.\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when assigning property of string: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "'hi'.prop = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "native:\\"Line 1: Expected object or array, got string.\\" -interpreted:\\"Line 1: Name prop not declared.\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error with * , error line at , not : expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "12 -* -'string';", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected number on right hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error with non boolean in if statement, error line at if statement, not at 1: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "if ( -1 -) { - 2; -} else {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected boolean as condition, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Undefined variable error is thrown - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -im_undefined;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Name im_undefined not declared. -Before you can read the value of im_undefined, you need to declare it as a variable or a constant. You can do this using the let or const keywords. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Undefined variable error is thrown: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "im_undefined;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Name im_undefined not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/interpreter/__tests__/interpreter-errors.ts b/src/interpreter/__tests__/interpreter-errors.ts deleted file mode 100644 index c12848a00..000000000 --- a/src/interpreter/__tests__/interpreter-errors.ts +++ /dev/null @@ -1,1127 +0,0 @@ -// import type { FunctionLike, MockedFunction } from 'jest-mock' - -/* tslint:disable:max-line-length */ -// import { memoizedGetModuleManifest } from '../../modules/moduleLoader' -import { Chapter } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { - expectDifferentParsedErrors, - expectParsedError, - expectParsedErrorNoSnapshot, - expectResult -} from '../../utils/testing' - -jest.mock('../../modules/loader/loaders') - -// const asMock = (func: T) => func as MockedFunction -// const mockedModuleFile = asMock(memoizedGetModuleFile) - -const undefinedVariable = stripIndent` -im_undefined; -` -const undefinedVariableVerbose = stripIndent` -"enable verbose"; -im_undefined; -` - -test('Undefined variable error is thrown', () => { - return expectParsedError(undefinedVariable).toMatchInlineSnapshot( - `"Line 1: Name im_undefined not declared."` - ) -}) - -test('Undefined variable error is thrown - verbose', () => { - return expectParsedError(undefinedVariableVerbose).toMatchInlineSnapshot(` - "Line 2, Column 0: Name im_undefined not declared. - Before you can read the value of im_undefined, you need to declare it as a variable or a constant. You can do this using the let or const keywords. - " - `) -}) - -test('Undefined variable error message differs from verbose version', () => { - return expectDifferentParsedErrors(undefinedVariable, undefinedVariableVerbose).toBe(undefined) -}) - -const assignToBuiltin = stripIndent` -map = 5; -` - -const assignToBuiltinVerbose = stripIndent` - "enable verbose"; - map = 5; -` - -test('Error when assigning to builtin', () => { - return expectParsedError(assignToBuiltin, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot( - `"Line 1: Cannot assign new value to constant map."` - ) -}) - -test('Error when assigning to builtin - verbose', () => { - return expectParsedError(assignToBuiltinVerbose, { chapter: Chapter.SOURCE_3 }) - .toMatchInlineSnapshot(` - "Line 2, Column 0: Cannot assign new value to constant map. - As map was declared as a constant, its value cannot be changed. You will have to declare a new variable. - " - `) -}) - -test('Assigning to builtin error message differs from verbose version', () => { - return expectDifferentParsedErrors(assignToBuiltin, assignToBuiltinVerbose).toBe(undefined) -}) - -const assignToBuiltin1 = stripIndent` -undefined = 5; -` - -const assignToBuiltinVerbose1 = stripIndent` - "enable verbose"; - undefined = 5; -` - -test('Error when assigning to builtin', () => { - return expectParsedError(assignToBuiltin1, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot( - `"Line 1: Cannot assign new value to constant undefined."` - ) -}) - -test('Error when assigning to builtin - verbose', () => { - return expectParsedError(assignToBuiltinVerbose1, { chapter: Chapter.SOURCE_3 }) - .toMatchInlineSnapshot(` - "Line 2, Column 0: Cannot assign new value to constant undefined. - As undefined was declared as a constant, its value cannot be changed. You will have to declare a new variable. - " - `) -}) - -test('Assigning to builtin error message differs from verbose version', () => { - return expectDifferentParsedErrors(assignToBuiltin1, assignToBuiltinVerbose1).toBe(undefined) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when assigning to property on undefined', () => { - return expectParsedError( - stripIndent` - undefined.prop = 123; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Cannot assign property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when assigning to property on variable with value undefined', () => { - return expectParsedError( - stripIndent` - const u = undefined; - u.prop = 123; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Cannot assign property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when deeply assigning to property on variable with value undefined', () => { - return expectParsedError( - stripIndent` - const u = undefined; - u.prop.prop = 123; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Cannot read property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing property on undefined', () => { - return expectParsedError( - stripIndent` - undefined.prop; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Cannot read property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when deeply accessing property on undefined', () => { - return expectParsedError( - stripIndent` - undefined.prop.prop; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Cannot read property prop of undefined"`) -}) - -test('Nice errors when errors occur inside builtins', () => { - return expectParsedError( - stripIndent` - parse_int("10"); - `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 1: Expected 2 arguments, but got 1."`) -}) - -test('Nice errors when errors occur inside builtins', () => { - return expectParsedError( - stripIndent` - parse("'"); - `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)"`) -}) - -test("Builtins don't create additional errors when it's not their fault", () => { - return expectParsedError( - stripIndent` - function f(x) { - return a; - } - map(f, list(1, 2)); - `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 2: Name a not declared."`) -}) - -test('Infinite recursion with a block bodied function', () => { - return expectParsedErrorNoSnapshot( - stripIndent` - function i(n) { - return n === 0 ? 0 : 1 + i(n-1); - } - i(1000); - `, - { chapter: Chapter.SOURCE_4 } - ).toEqual(expect.stringMatching(/Maximum call stack size exceeded\n *(i\(\d*\)[^i]{2,4}){3}/)) -}, 15000) - -test('Infinite recursion with function calls in argument', () => { - return expectParsedErrorNoSnapshot( - stripIndent` - function i(n, redundant) { - return n === 0 ? 0 : 1 + i(n-1, r()); - } - function r() { - return 1; - } - i(1000, 1); - `, - { chapter: Chapter.SOURCE_4 } - ).toEqual( - expect.stringMatching(/Maximum call stack size exceeded\n *(i\(\d*, 1\)[^i]{2,4}){2}[ir]/) - ) -}, 10000) - -test('Infinite recursion of mutually recursive functions', () => { - return expectParsedErrorNoSnapshot( - stripIndent` - function f(n) { - return n === 0 ? 0 : 1 + g(n - 1); - } - function g(n) { - return 1 + f(n); - } - f(1000); - `, - { chapter: Chapter.SOURCE_4 } - ).toEqual( - expect.stringMatching( - /Maximum call stack size exceeded\n([^f]*f[^g]*g[^f]*f|[^g]*g[^f]*f[^g]*g)/ - ) - ) -}) - -const callingNonFunctionValueUndefined = stripIndent` -undefined(); -` - -const callingNonFunctionValueUndefinedVerbose = stripIndent` -"enable verbose"; - undefined(); -` -// should not be different when error passing is fixed -test('Error when calling non function value undefined', () => { - return expectParsedError(callingNonFunctionValueUndefined, { - native: true - }).toMatchInlineSnapshot(`"Line 1: Calling non-function value undefined."`) -}) - -test('Error when calling non function value undefined - verbose', () => { - return expectParsedError(callingNonFunctionValueUndefinedVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value undefined. - Because undefined is not a function, you cannot run undefined(). - " - `) -}) - -test('Calling non function value undefined error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueUndefined, - callingNonFunctionValueUndefinedVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueUndefinedArgs = stripIndent` -undefined(1, true); -` - -const callingNonFunctionValueUndefinedArgsVerbose = stripIndent` -"enable verbose"; - undefined(1, true); -` -// should not be different when error passing is fixed -test('Error when calling non function value undefined with arguments', () => { - return expectParsedError(callingNonFunctionValueUndefinedArgs, { - native: false - }).toMatchInlineSnapshot(`"Line 1: Calling non-function value undefined."`) -}) - -test('Error when calling non function value undefined with arguments - verbose', () => { - return expectParsedError(callingNonFunctionValueUndefinedArgsVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value undefined. - Because undefined is not a function, you cannot run undefined(1, true). - " - `) -}) - -test('Calling non function value undefined with arguments error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueUndefinedArgs, - callingNonFunctionValueUndefinedArgsVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueNull = stripIndent` -null(); -` - -const callingNonFunctionValueNullVerbose = stripIndent` -"enable verbose"; - null(); -` - -test('Error when calling non function value null', () => { - return expectParsedError(callingNonFunctionValueNull).toMatchInlineSnapshot( - `"Line 1: null literals are not allowed."` - ) -}) - -test('Error when calling non function value null - verbose', () => { - return expectParsedError(callingNonFunctionValueNullVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: null literals are not allowed. - They're not part of the Source §1 specs. - " - `) -}) - -test('Calling non function value null error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueNull, - callingNonFunctionValueNullVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueTrue = stripIndent` -true(); -` -const callingNonFunctionValueTrueVerbose = stripIndent` -"enable verbose"; - true(); -` - -test('Error when calling non function value true', () => { - return expectParsedError(callingNonFunctionValueTrue, { native: true }).toMatchInlineSnapshot( - `"Line 1: Calling non-function value true."` - ) -}) - -test('Error when calling non function value true - verbose', () => { - return expectParsedError(callingNonFunctionValueTrueVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value true. - Because true is not a function, you cannot run true(). - " - `) -}) - -test('Calling non function value true error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueTrue, - callingNonFunctionValueTrueVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValue0 = stripIndent` -0(); -` - -const callingNonFunctionValue0Verbose = stripIndent` -"enable verbose"; - 0(); -` - -test('Error when calling non function value 0', () => { - return expectParsedError(callingNonFunctionValue0, { native: true }).toMatchInlineSnapshot( - `"Line 1: Calling non-function value 0."` - ) -}) - -test('Error when calling non function value 0 - verbose', () => { - return expectParsedError(callingNonFunctionValue0Verbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value 0. - Because 0 is not a function, you cannot run 0(). If you were planning to perform multiplication by 0, you need to use the * operator. - " - `) -}) - -test('Calling non function value 0 error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValue0, - callingNonFunctionValue0Verbose - ).toBe(undefined) -}) - -const callingNonFunctionValueString = stripIndent` -'string'(); -` - -const callingNonFunctionValueStringVerbose = stripIndent` -"enable verbose"; - 'string'(); -` - -test('Error when calling non function value "string"', () => { - return expectParsedError(callingNonFunctionValueString, { native: true }).toMatchInlineSnapshot( - `"Line 1: Calling non-function value \\"string\\"."` - ) -}) - -test('Error when calling non function value "string" - verbose', () => { - return expectParsedError(callingNonFunctionValueStringVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value \\"string\\". - Because \\"string\\" is not a function, you cannot run \\"string\\"(). - " - `) -}) - -test('Calling non function value string error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueString, - callingNonFunctionValueStringVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueArray = stripIndent` -[1](); -` - -const callingNonFunctionValueArrayVerbose = stripIndent` -"enable verbose"; -[1](); -` - -test('Error when calling non function value array', () => { - return expectParsedError(callingNonFunctionValueArray, { - chapter: Chapter.SOURCE_3, - native: true - }).toMatchInlineSnapshot(`"Line 1: Calling non-function value [1]."`) -}) - -test('Error when calling non function value array - verbose', () => { - return expectParsedError(callingNonFunctionValueArrayVerbose, { chapter: Chapter.SOURCE_3 }) - .toMatchInlineSnapshot(` - "Line 2, Column 0: Calling non-function value [1]. - Because [1] is not a function, you cannot run [1](). - " - `) -}) - -test('Calling non function value array error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueArray, - callingNonFunctionValueArrayVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueObject = stripIndent` -({a: 1})(); -` - -const callingNonFunctionValueObjectVerbose = stripIndent` -"enable verbose"; -({a: 1})(); -` - -test('Error when calling non function value object', () => { - return expectParsedError(callingNonFunctionValueObject, { - chapter: Chapter.LIBRARY_PARSER - }).toMatchInlineSnapshot(`"Line 1: Calling non-function value {\\"a\\": 1}."`) -}) - -test('Error when calling non function value object - verbose', () => { - return expectParsedError(callingNonFunctionValueObjectVerbose, { - chapter: Chapter.LIBRARY_PARSER - }).toMatchInlineSnapshot(` - "Line 2, Column 0: Calling non-function value {\\"a\\": 1}. - Because {\\"a\\": 1} is not a function, you cannot run {\\"a\\": 1}(). - " - `) -}) - -test('Calling non function value object error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueObject, - callingNonFunctionValueObjectVerbose - ).toBe(undefined) -}) - -test('Error when calling non function value object - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - ({a: 1})(); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Calling non-function value {\\"a\\": 1}. - Because {\\"a\\": 1} is not a function, you cannot run {\\"a\\": 1}(). - " - `) -}) - -test('Error when calling function with too few arguments', () => { - return expectParsedError( - stripIndent` - function f(x) { - return x; - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 4: Expected 1 arguments, but got 0."`) -}) - -test('Error when calling function with too few arguments - verbose', () => { - return expectParsedError(stripIndent` - "enable verbose"; - function f(x) { - return x; - } - f(); - `).toMatchInlineSnapshot(` - "Line 5, Column 2: Expected 1 arguments, but got 0. - Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling function with too many arguments', () => { - return expectParsedError( - stripIndent` - function f(x) { - return x; - } - f(1, 2); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 4: Expected 1 arguments, but got 2."`) -}) - -test('Error when calling function with too many arguments - verbose', () => { - return expectParsedError(stripIndent` - "enable verbose"; - function f(x) { - return x; - } - f(1, 2); - `).toMatchInlineSnapshot(` - "Line 5, Column 2: Expected 1 arguments, but got 2. - Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling arrow function with too few arguments', () => { - return expectParsedError( - stripIndent` - const f = x => x; - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 1 arguments, but got 0."`) -}) - -test('Error when calling arrow function with too few arguments - verbose', () => { - return expectParsedError(stripIndent` - "enable verbose"; - const f = x => x; - f(); - `).toMatchInlineSnapshot(` - "Line 3, Column 2: Expected 1 arguments, but got 0. - Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling arrow function with too many arguments', () => { - return expectParsedError( - stripIndent` - const f = x => x; - f(1, 2); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 1 arguments, but got 2."`) -}) - -test('Error when calling arrow function with too many arguments - verbose', () => { - return expectParsedError(stripIndent` - "enable verbose"; - const f = x => x; - f(1, 2); - `).toMatchInlineSnapshot(` - "Line 3, Column 2: Expected 1 arguments, but got 2. - Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling function from member expression with too many arguments', () => { - return expectParsedError( - stripIndent` - const f = [x => x]; - f[0](1, 2); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 1 arguments, but got 2."`) -}) - -test('Error when calling function from member expression with too many arguments - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - const f = [x => x]; - f[0](1, 2); - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(` - "Line 3, Column 2: Expected 1 arguments, but got 2. - Try calling function f[0] again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling arrow function in tail call with too many arguments - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - const g = () => 1; - const f = x => g(x); - f(1); - ` - ).toMatchInlineSnapshot(` - "Line 3, Column 15: Expected 0 arguments, but got 1. - Try calling function g again, but with 0 arguments instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling arrow function in tail call with too many arguments', () => { - return expectParsedError( - stripIndent` - const g = () => 1; - const f = x => g(x); - f(1); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 0 arguments, but got 1."`) -}) - -test('Error when calling builtin function in with too many arguments', () => { - return expectParsedError( - stripIndent` - is_number(1, 2, 3); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected 1 arguments, but got 3."`) -}) - -test('Error when calling builtin function in with too few arguments', () => { - return expectParsedError( - stripIndent` - parse_int(""); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected 2 arguments, but got 1."`) -}) - -test('No error when calling list function in with variable arguments', () => { - return expectResult( - stripIndent` - list(); - list(1); - list(1, 2, 3); - list(1, 2, 3, 4, 5, 6, 6); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - Array [ - 1, - Array [ - 2, - Array [ - 3, - Array [ - 4, - Array [ - 5, - Array [ - 6, - Array [ - 6, - null, - ], - ], - ], - ], - ], - ], - ] - `) -}) - -test('No error when calling display function in with variable arguments', () => { - return expectResult( - stripIndent` - display(1); - display(1, "test"); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`1`) -}) - -test('No error when calling stringify function in with variable arguments', () => { - return expectResult( - stripIndent` - stringify(1, 2); - stringify(1, 2, 3); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`"1"`) -}) - -test('No error when calling math_max function in with variable arguments', () => { - return expectResult( - stripIndent` - math_max(); - math_max(1, 2); - math_max(1, 2, 3); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`3`) -}) - -test('No error when calling math_min function in with variable arguments', () => { - return expectResult( - stripIndent` - math_min(); - math_min(1, 2); - math_min(1, 2, 3); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`1`) -}) - -test('Error with too many arguments passed to math_sin', () => { - return expectParsedError( - stripIndent` - math_sin(7,8); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected 1 arguments, but got 2."`) -}) - -test('Error with too few arguments passed to rest parameters', () => { - return expectParsedError( - stripIndent` - function rest(a, b, ...c) {} - rest(1); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 2 or more arguments, but got 1."`) -}) - -test('Error when redeclaring constant', () => { - return expectParsedError( - stripIndent` - const f = x => x; - const f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)"`) -}) - -test('Error when redeclaring constant as variable', () => { - return expectParsedError( - stripIndent` - const f = x => x; - let f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)"`) -}) - -test('Error when redeclaring variable as constant', () => { - return expectParsedError( - stripIndent` - let f = x => x; - const f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)"`) -}) - -test('Error when redeclaring variable', () => { - return expectParsedError( - stripIndent` - let f = x => x; - let f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)"`) -}) - -test('Error when redeclaring function after let', () => { - return expectParsedError( - stripIndent` - let f = x => x; - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)"`) -}) - -test('Error when redeclaring function after let --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let f = x => x; - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) - There is a syntax error in your program - " - `) -}) - -test('Error when redeclaring function after function', () => { - return expectParsedError( - stripIndent` - function f() {} - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)"`) -}) - -test('Error when redeclaring function after function --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f() {} - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) - There is a syntax error in your program - " - `) -}) - -test('Error when redeclaring function after const', () => { - return expectParsedError( - stripIndent` - const f = x => x; - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)"`) -}) - -test('Error when redeclaring function after const --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - const f = x => x; - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) - There is a syntax error in your program - " - `) -}) - -test('Error when redeclaring const after function', () => { - return expectParsedError( - stripIndent` - function f() {} - const f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)"`) -}) - -test('Error when redeclaring const after function --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f() {} - const f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 6: SyntaxError: Identifier 'f' has already been declared (3:6) - There is a syntax error in your program - " - `) -}) - -test('Error when redeclaring let after function', () => { - return expectParsedError( - stripIndent` - function f() {} - let f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)"`) -}) - -test('Error when redeclaring let after function --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f() {} - let f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 4: SyntaxError: Identifier 'f' has already been declared (3:4) - There is a syntax error in your program - " - `) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing property of null', () => { - return expectParsedError( - stripIndent` - null["prop"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read property prop of null"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing property of undefined', () => { - return expectParsedError( - stripIndent` - undefined["prop"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of builtin', () => { - return expectParsedError( - stripIndent` - pair["constructor"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(` - "Line 1: Cannot read inherited property constructor of function pair(left, right) { - [implementation hidden] - }" - `) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of function', () => { - return expectParsedError( - stripIndent` - function f() {} - f["constructor"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 2: Cannot read inherited property constructor of function f() {}"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of arrow function', () => { - return expectParsedError( - stripIndent` - (() => 1)["constructor"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property constructor of () => 1"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of array', () => { - return expectParsedError( - stripIndent` - [].push; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property push of []"`) -}) - -test('Error when accessing inherited property of object', () => { - return expectParsedError( - stripIndent` - ({}).valueOf; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property valueOf of {}."`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of string', () => { - return expectParsedError( - stripIndent` - 'hi'.includes; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property includes of \\"hi\\""`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of number', () => { - return expectParsedError( - stripIndent` - (1).toPrecision; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property toPrecision of 1"`) -}) - -test('Access local property', () => { - return expectResult( - stripIndent` - ({a: 0})["a"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`0`) -}) - -test('Type error when accessing property of null', () => { - return expectParsedError( - stripIndent` - null.prop; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected object or array, got null."`) -}) - -test('Type error when accessing property of string', () => { - return expectParsedError( - stripIndent` - 'hi'.length; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected object or array, got string."`) -}) - -test('Type error when accessing property of function', () => { - return expectParsedError( - stripIndent` - function f() { - return 1; - } - f.prototype; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 4: Expected object or array, got function."`) -}) - -test('Type error when assigning property of string', () => { - return expectParsedError( - stripIndent` - 'hi'.prop = 5; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected object or array, got string."`) -}) - -test('Type error when assigning property of function', () => { - return expectParsedError( - stripIndent` - function f() { - return 1; - } - f.prop = 5; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 4: Expected object or array, got function."`) -}) - -test('Type error with non boolean in if statement, error line at if statement, not at 1', () => { - return expectParsedError( - stripIndent` - if ( - 1 - ) { - 2; - } else {} - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected boolean as condition, got number."`) -}) - -test('Type error with * , error line at , not ', () => { - return expectParsedError( - stripIndent` - 12 - * - 'string'; - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected number on right hand side of operation, got string."`) -}) - -test('Cascading js errors work properly 1', () => { - return expectParsedError( - stripIndent` - function make_alternating_stream(stream) { - return pair(head(stream), () => make_alternating_stream( - negate_whole_stream( - stream_tail(stream)))); - } - - function negate_whole_stream(stream) { - return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); - } - - const ones = pair(1, () => ones); - eval_stream(make_alternating_stream(enum_stream(1, 9)), 10); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 8: Error: head(xs) expects a pair as argument xs, but encountered null"` - ) -}) - -test('Cascading js errors work properly', () => { - return expectParsedError( - stripIndent` - function h(p) { - return head(p); - } - - h(null); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 2: Error: head(xs) expects a pair as argument xs, but encountered null"` - ) -}) diff --git a/src/interpreter/closure.ts b/src/interpreter/closure.ts deleted file mode 100644 index c5d3c95b8..000000000 --- a/src/interpreter/closure.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* tslint:disable:max-classes-per-file */ -import { generate } from 'astring' -import type es from 'estree' -import { uniqueId } from 'lodash' - -import { hasReturnStatement, isBlockStatement } from '../cse-machine/utils' -import type { Context, Environment, Value } from '../types' -import { - blockArrowFunction, - blockStatement, - callExpression, - identifier, - returnStatement -} from '../utils/ast/astCreator' -import { apply } from './interpreter-non-det' - -const closureToJS = (value: Closure, context: Context, klass: string) => { - function DummyClass(this: Closure) { - const args: Value[] = Array.prototype.slice.call(arguments) - const gen = apply(context, value, args, callExpression(identifier(klass), args), this) - let it = gen.next() - while (!it.done) { - it = gen.next() - } - return it.value - } - Object.defineProperty(DummyClass, 'name', { - value: klass - }) - Object.setPrototypeOf(DummyClass, () => undefined) - Object.defineProperty(DummyClass, 'Inherits', { - value: (Parent: Value) => { - DummyClass.prototype = Object.create(Parent.prototype) - DummyClass.prototype.constructor = DummyClass - } - }) - DummyClass.toString = () => generate(value.originalNode) - DummyClass.call = (thisArg: Value, ...args: Value[]): any => { - return DummyClass.apply(thisArg, args) - } - return DummyClass -} - -class Callable extends Function { - constructor(f: any) { - super() - return Object.setPrototypeOf(f, new.target.prototype) - } -} - -/** - * Models function value in the interpreter environment. - */ -export default class Closure extends Callable { - public static makeFromArrowFunction( - node: es.ArrowFunctionExpression, - environment: Environment, - context: Context, - dummyReturn?: boolean, - predefined?: boolean - ) { - const functionBody: es.BlockStatement = !isBlockStatement(node.body) - ? blockStatement([returnStatement(node.body, node.body.loc)], node.body.loc) - : dummyReturn && !hasReturnStatement(node.body) - ? blockStatement( - [ - ...node.body.body, - returnStatement(identifier('undefined', node.body.loc), node.body.loc) - ], - node.body.loc - ) - : node.body - - const closure = new Closure( - blockArrowFunction(node.params as es.Identifier[], functionBody, node.loc), - environment, - context, - predefined - ) - - // Set the closure's node to point back at the original one - closure.originalNode = node - - return closure - } - - /** Unique ID defined for closure */ - public id: string - - /** String representation of the closure */ - public functionName: string - - /** Fake closure function */ - // tslint:disable-next-line:ban-types - public fun: Function - - /** Keeps track of whether the closure is a pre-defined function */ - public preDefined?: boolean - - /** The original node that created this Closure */ - public originalNode: es.Function - - constructor( - public node: es.Function, - public environment: Environment, - context: Context, - isPredefined?: boolean - ) { - super(function (this: any, ...args: any[]) { - return funJS.apply(this, args) - }) - this.originalNode = node - this.id = uniqueId() - if (this.node.type === 'FunctionDeclaration' && this.node.id !== null) { - this.functionName = this.node.id.name - } else { - this.functionName = - (this.node.params.length === 1 ? '' : '(') + - this.node.params.map((o: es.Identifier) => o.name).join(', ') + - (this.node.params.length === 1 ? '' : ')') + - ' => ...' - } - // TODO: Investigate how relevant this really is. - // .fun seems to only be used in interpreter's NewExpression handler, which uses .fun.prototype. - const funJS = closureToJS(this, context, this.functionName) - this.fun = funJS - this.preDefined = isPredefined == undefined ? undefined : isPredefined - } - - public toString(): string { - return generate(this.originalNode) - } -} diff --git a/src/interpreter/interpreter-non-det.ts b/src/interpreter/interpreter-non-det.ts deleted file mode 100644 index c4a47b7a6..000000000 --- a/src/interpreter/interpreter-non-det.ts +++ /dev/null @@ -1,698 +0,0 @@ -/* tslint:disable:max-classes-per-file */ -import * as es from 'estree' -import { cloneDeep } from 'lodash' - -import { CUT, UNKNOWN_LOCATION } from '../constants' -import Heap from '../cse-machine/heap' -import { uniqueId } from '../cse-machine/utils' -import * as errors from '../errors/errors' -import { RuntimeSourceError } from '../errors/runtimeSourceError' -import { Context, Environment, Node, Value } from '../types' -import { conditionalExpression, literal, primitive } from '../utils/ast/astCreator' -import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators' -import * as rttc from '../utils/rttc' -import Closure from './closure' - -class BreakValue {} - -class ContinueValue {} - -class ReturnValue { - constructor(public value: Value) {} -} - -const createEnvironment = ( - context: Context, - closure: Closure, - args: Value[], - callExpression?: es.CallExpression -): Environment => { - const environment: Environment = { - name: closure.functionName, // TODO: Change this - tail: closure.environment, - head: {}, - heap: new Heap(), - id: uniqueId(context) - } - if (callExpression) { - environment.callExpression = { - ...callExpression, - arguments: args.map(primitive) - } - } - closure.node.params.forEach((param, index) => { - const ident = param as es.Identifier - environment.head[ident.name] = args[index] - }) - return environment -} - -const createBlockEnvironment = (context: Context, name = 'blockEnvironment'): Environment => { - return { - name, - tail: currentEnvironment(context), - head: {}, - heap: new Heap(), - thisContext: context, - id: uniqueId(context) - } -} - -const handleRuntimeError = (context: Context, error: RuntimeSourceError): never => { - context.errors.push(error) - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - throw error -} - -const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol('Used to implement declaration') - -function declareIdentifier(context: Context, name: string, node: Node) { - const environment = currentEnvironment(context) - if (environment.head.hasOwnProperty(name)) { - const descriptors = Object.getOwnPropertyDescriptors(environment.head) - - return handleRuntimeError( - context, - new errors.VariableRedeclaration(node, name, descriptors[name].writable) - ) - } - environment.head[name] = DECLARED_BUT_NOT_YET_ASSIGNED - return environment -} - -function declareVariables(context: Context, node: es.VariableDeclaration) { - for (const declaration of node.declarations) { - declareIdentifier(context, (declaration.id as es.Identifier).name, node) - } -} - -function declareFunctionAndVariableIdentifiers(context: Context, node: es.BlockStatement) { - for (const statement of node.body) { - switch (statement.type) { - case 'VariableDeclaration': - declareVariables(context, statement) - break - case 'FunctionDeclaration': - if (statement.id === null) { - throw new Error( - 'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.' - ) - } - declareIdentifier(context, statement.id.name, statement) - break - } - } -} - -function defineVariable(context: Context, name: string, value: Value, constant = false) { - const environment = context.runtime.environments[0] - - if (environment.head[name] !== DECLARED_BUT_NOT_YET_ASSIGNED) { - return handleRuntimeError( - context, - new errors.VariableRedeclaration(context.runtime.nodes[0]!, name, !constant) - ) - } - - Object.defineProperty(environment.head, name, { - value, - writable: !constant, - enumerable: true - }) - - return environment -} - -function undefineVariable(context: Context, name: string) { - const environment = context.runtime.environments[0] - - Object.defineProperty(environment.head, name, { - value: DECLARED_BUT_NOT_YET_ASSIGNED, - writable: true, - enumerable: true - }) -} - -const currentEnvironment = (context: Context) => context.runtime.environments[0] -const popEnvironment = (context: Context) => context.runtime.environments.shift() -const pushEnvironment = (context: Context, environment: Environment) => - context.runtime.environments.unshift(environment) - -const getVariable = (context: Context, name: string, ensureVariableAssigned: boolean) => { - let environment: Environment | null = context.runtime.environments[0] - while (environment) { - if (environment.head.hasOwnProperty(name)) { - if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { - if (ensureVariableAssigned) { - return handleRuntimeError( - context, - new errors.UnassignedVariable(name, context.runtime.nodes[0]) - ) - } else { - return DECLARED_BUT_NOT_YET_ASSIGNED - } - } else { - return environment.head[name] - } - } else { - environment = environment.tail - } - } - return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) -} - -const setVariable = (context: Context, name: string, value: any) => { - let environment: Environment | null = context.runtime.environments[0] - while (environment) { - if (environment.head.hasOwnProperty(name)) { - if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { - break - } - const descriptors = Object.getOwnPropertyDescriptors(environment.head) - if (descriptors[name].writable) { - environment.head[name] = value - return undefined - } - return handleRuntimeError( - context, - new errors.ConstAssignment(context.runtime.nodes[0]!, name) - ) - } else { - environment = environment.tail - } - } - return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) -} - -const checkNumberOfArguments = ( - context: Context, - callee: Closure, - args: Value[], - exp: es.CallExpression -) => { - if (callee.node.params.length !== args.length) { - return handleRuntimeError( - context, - new errors.InvalidNumberOfArguments(exp, callee.node.params.length, args.length) - ) - } - return undefined -} - -/** - * Returns a random integer for a given interval (inclusive). - */ -function randomInt(min: number, max: number): number { - return Math.floor(Math.random() * (max - min + 1) + min) -} - -function* getAmbRArgs(context: Context, call: es.CallExpression) { - const args: Node[] = cloneDeep(call.arguments) - while (args.length > 0) { - const r = randomInt(0, args.length - 1) - const arg: Node = args.splice(r, 1)[0] - - yield* evaluate(arg, context) - } -} - -function* getArgs(context: Context, call: es.CallExpression) { - const args = cloneDeep(call.arguments) - return yield* cartesianProduct(context, args as es.Expression[], []) -} - -/* Given a list of non deterministic nodes, this generator returns every - * combination of values of these nodes */ -function* cartesianProduct( - context: Context, - nodes: es.Expression[], - nodeValues: Value[] -): IterableIterator { - if (nodes.length === 0) { - yield nodeValues - } else { - const currentNode = nodes.shift()! // we need the postfix ! to tell compiler that nodes array is nonempty - const nodeValueGenerator = evaluate(currentNode, context) - for (const nodeValue of nodeValueGenerator) { - nodeValues.push(nodeValue) - yield* cartesianProduct(context, nodes, nodeValues) - nodeValues.pop() - } - nodes.unshift(currentNode) - } -} - -function* getAmbArgs(context: Context, call: es.CallExpression) { - for (const arg of call.arguments) { - yield* evaluate(arg, context) - } -} - -function transformLogicalExpression(node: es.LogicalExpression): es.ConditionalExpression { - if (node.operator === '&&') { - return conditionalExpression(node.left, node.right, literal(false), node.loc) - } else { - return conditionalExpression(node.left, literal(true), node.right, node.loc) - } -} - -function* reduceIf( - node: es.IfStatement | es.ConditionalExpression, - context: Context -): IterableIterator { - const testGenerator = evaluate(node.test, context) - for (const test of testGenerator) { - const error = rttc.checkIfStatement(node, test, context.chapter) - if (error) { - return handleRuntimeError(context, error) - } - yield test ? node.consequent : node.alternate! - } -} - -export type Evaluator = (node: T, context: Context) => IterableIterator - -function* evaluateBlockSatement(context: Context, node: es.BlockStatement) { - declareFunctionAndVariableIdentifiers(context, node) - yield* evaluateSequence(context, node.body) -} - -function* evaluateSequence(context: Context, sequence: es.Statement[]): IterableIterator { - if (sequence.length === 0) { - return yield undefined - } - const firstStatement = sequence[0] - const sequenceValGenerator = evaluate(firstStatement, context) - if (sequence.length === 1) { - yield* sequenceValGenerator - } else { - sequence.shift() - let shouldUnshift = true - for (const sequenceValue of sequenceValGenerator) { - // prevent unshifting of cut operator - shouldUnshift = sequenceValue !== CUT - - if ( - sequenceValue instanceof ReturnValue || - sequenceValue instanceof BreakValue || - sequenceValue instanceof ContinueValue - ) { - yield sequenceValue - continue - } - - const res = yield* evaluateSequence(context, sequence) - if (res === CUT) { - // prevent unshifting of statements before cut - shouldUnshift = false - break - } - } - - if (shouldUnshift) sequence.unshift(firstStatement) - else return CUT - } -} - -function* evaluateConditional(node: es.IfStatement | es.ConditionalExpression, context: Context) { - const branchGenerator = reduceIf(node, context) - for (const branch of branchGenerator) { - yield* evaluate(branch, context) - } -} - -/** - * WARNING: Do not use object literal shorthands, e.g. - * { - * *Literal(node: es.Literal, ...) {...}, - * *ThisExpression(node: es.ThisExpression, ..._ {...}, - * ... - * } - * They do not minify well, raising uncaught syntax errors in production. - * See: https://github.com/webpack/webpack/issues/7566 - */ -// tslint:disable:object-literal-shorthand -// prettier-ignore -export const evaluators: { [nodeType: string]: Evaluator } = { - /** Simple Values */ - Literal: function*(node: es.Literal, _context: Context) { - yield node.value - }, - - ArrowFunctionExpression: function*(node: es.ArrowFunctionExpression, context: Context) { - yield Closure.makeFromArrowFunction(node, currentEnvironment(context), context) - }, - - ArrayExpression: function*(node: es.ArrayExpression, context: Context) { - const arrayGenerator = cartesianProduct(context, node.elements as es.Expression[], []) - for (const array of arrayGenerator) { - yield array.slice() // yield a new array to avoid modifying previous ones - } - }, - - Identifier: function*(node: es.Identifier, context: Context) { - return yield getVariable(context, node.name, true) - }, - - CallExpression: function*(node: es.CallExpression, context: Context) { - const callee = node.callee; - if (rttc.isIdentifier(callee)) { - switch (callee.name) { - case 'amb': - return yield* getAmbArgs(context, node) - case 'ambR': - return yield* getAmbRArgs(context, node) - case 'cut': - return yield CUT - } - } - - const calleeGenerator = evaluate(node.callee, context) - for (const calleeValue of calleeGenerator) { - const argsGenerator = getArgs(context, node) - for (const args of argsGenerator) { - yield* apply(context, calleeValue, args, node, undefined) - } - } - }, - - UnaryExpression: function*(node: es.UnaryExpression, context: Context) { - const argGenerator = evaluate(node.argument, context) - for (const argValue of argGenerator) { - const error = rttc.checkUnaryExpression(node, node.operator, argValue, context.chapter) - if (error) { - return handleRuntimeError(context, error) - } - yield evaluateUnaryExpression(node.operator, argValue) - } - return - }, - - BinaryExpression: function*(node: es.BinaryExpression, context: Context) { - const pairGenerator = cartesianProduct(context, [node.left, node.right], []) - for (const pair of pairGenerator) { - const leftValue = pair[0] - const rightValue = pair[1] - const error = rttc.checkBinaryExpression(node, node.operator, context.chapter, leftValue, rightValue) - if (error) { - return handleRuntimeError(context, error) - } - yield evaluateBinaryExpression(node.operator, leftValue, rightValue) - } - return - }, - - ConditionalExpression: function*(node: es.ConditionalExpression, context: Context) { - yield* evaluateConditional(node, context) - }, - - LogicalExpression: function*(node: es.LogicalExpression, context: Context) { - const conditional: es.ConditionalExpression = transformLogicalExpression(node) - yield* evaluateConditional(conditional, context) - }, - - VariableDeclaration: function*(node: es.VariableDeclaration, context: Context) { - const declaration = node.declarations[0] - const constant = node.kind === 'const' - const id = declaration.id as es.Identifier - const valueGenerator = evaluate(declaration.init!, context) - for (const value of valueGenerator) { - defineVariable(context, id.name, value, constant) - yield value - undefineVariable(context, id.name) - } - return undefined - }, - - MemberExpression: function*(node: es.MemberExpression, context: Context) { - // es.PrivateIdentifier is a ES2022 feature - const pairGenerator = cartesianProduct(context, [node.property as es.Expression, node.object as es.Expression], []) - for (const pair of pairGenerator) { - const prop = pair[0] - const obj = pair[1] - - const error = rttc.checkMemberAccess(node, obj, prop) - if (error) { - return yield handleRuntimeError(context, error) - } - - yield obj[prop] - } - - return - }, - - AssignmentExpression: function*(node: es.AssignmentExpression, context: Context) { - if (node.left.type === 'MemberExpression') { - // es.PrivateIdentifier is a ES2022 feature - const tripleGenerator = cartesianProduct(context, [node.right, node.left.property as es.Expression, node.left.object as es.Expression], []) - for (const triple of tripleGenerator) { - const val = triple[0] - const prop = triple[1] - const obj = triple[2] - - const error = rttc.checkMemberAccess(node, obj, prop) - if (error) { - return yield handleRuntimeError(context, error) - } - const originalElementValue = obj[prop] - obj[prop] = val - yield val - obj[prop] = originalElementValue - } - - return - } - - const id = node.left as es.Identifier - const originalValue = getVariable(context, id.name, false) - const valueGenerator = evaluate(node.right, context) - for (const value of valueGenerator) { - setVariable(context, id.name, value) - yield value - setVariable(context, id.name, originalValue) - } - return - }, - - FunctionDeclaration: function*(node: es.FunctionDeclaration, context: Context) { - const id = node.id - if (id === null) { - throw new Error("Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.") - } - const closure = new Closure(node, currentEnvironment(context), context) - defineVariable(context, id.name, closure, true) - yield undefined - undefineVariable(context, id.name) - }, - - IfStatement: function*(node: es.IfStatement, context: Context) { - yield* evaluateConditional(node, context) - }, - - ExpressionStatement: function*(node: es.ExpressionStatement, context: Context) { - return yield* evaluate(node.expression, context) - }, - - ContinueStatement: function*(_node: es.ContinueStatement, _context: Context) { - yield new ContinueValue() - }, - - BreakStatement: function*(_node: es.BreakStatement, _context: Context) { - yield new BreakValue() - }, - - WhileStatement: function*(node: es.WhileStatement, context: Context) { - let value: Value // tslint:disable-line - function* loop(): Value { - const testGenerator = evaluate(node.test, context) - for (const test of testGenerator) { - const error = rttc.checkIfStatement(node.test, test, context.chapter) - if (error) return handleRuntimeError(context, error) - - if (test && - !(value instanceof ReturnValue) && - !(value instanceof BreakValue) - ) { - const iterationValueGenerator = evaluate(cloneDeep(node.body), context) - for (const iterationValue of iterationValueGenerator) { - value = iterationValue - yield* loop(); - } - } else { - if (value instanceof BreakValue || value instanceof ContinueValue) { - yield undefined - } else { - yield value - } - } - } - } - - yield* loop(); - }, - - ForStatement: function*(node: es.ForStatement, context: Context) { - let value: Value - function* loop(): Value { - const testGenerator = evaluate(node.test!, context) - for (const test of testGenerator) { - const error = rttc.checkIfStatement(node.test!, test, context.chapter) - if (error) return handleRuntimeError(context, error) - - if (test && - !(value instanceof ReturnValue) && - !(value instanceof BreakValue) - ) { - const iterationEnvironment = createBlockEnvironment(context, 'forBlockEnvironment') - pushEnvironment(context, iterationEnvironment) - for (const name in loopEnvironment.head) { - if (loopEnvironment.head.hasOwnProperty(name)) { - declareIdentifier(context, name, node) - defineVariable(context, name, loopEnvironment.head[name], true) - } - } - - const iterationValueGenerator = evaluate(cloneDeep(node.body), context) - for (const iterationValue of iterationValueGenerator) { - value = iterationValue - popEnvironment(context) - const updateNode = evaluate(node.update!, context) - for (const _update of updateNode) { - yield* loop(); - } - - pushEnvironment(context, iterationEnvironment) - } - popEnvironment(context) - } else { - if (value instanceof BreakValue || value instanceof ContinueValue) { - yield undefined - } else { - yield value - } - } - } - } - - // Create a new block scope for the loop variables - const loopEnvironment = createBlockEnvironment(context, 'forLoopEnvironment') - pushEnvironment(context, loopEnvironment) - - const initNode = node.init! - if (initNode.type === 'VariableDeclaration') { - declareVariables(context, initNode) - } - - const initNodeGenerator = evaluate(node.init!, context) - for (const _init of initNodeGenerator) { - const loopGenerator = loop() - for (const loopValue of loopGenerator) { - popEnvironment(context) - yield loopValue - pushEnvironment(context, loopEnvironment) - } - } - - popEnvironment(context) - }, - - ReturnStatement: function*(node: es.ReturnStatement, context: Context) { - const returnExpression = node.argument! - const returnValueGenerator = evaluate(returnExpression, context) - for (const returnValue of returnValueGenerator) { - yield new ReturnValue(returnValue) - } - }, - - BlockStatement: function*(node: es.BlockStatement, context: Context) { - // Create a new environment (block scoping) - const environment = createBlockEnvironment(context, 'blockEnvironment') - pushEnvironment(context, environment) - - const resultGenerator = evaluateBlockSatement(context, node) - for (const result of resultGenerator) { - popEnvironment(context) - yield result - pushEnvironment(context, environment) - } - popEnvironment(context) - }, - - Program: function*(node: es.BlockStatement, context: Context) { - context.numberOfOuterEnvironments += 1 - const environment = createBlockEnvironment(context, 'programEnvironment') - pushEnvironment(context, environment) - return yield* evaluateBlockSatement(context, node) - } -} -// tslint:enable:object-literal-shorthand - -export function* evaluate(node: Node, context: Context) { - const result = yield* evaluators[node.type](node, context) - return result -} - -export function* apply( - context: Context, - fun: Closure | Value, - args: Value[], - node: es.CallExpression, - thisContext?: Value -) { - if (fun instanceof Closure) { - checkNumberOfArguments(context, fun, args, node!) - const environment = createEnvironment(context, fun, args, node) - environment.thisContext = thisContext - pushEnvironment(context, environment) - const applicationValueGenerator = evaluateBlockSatement( - context, - cloneDeep(fun.node.body) as es.BlockStatement - ) - - // This function takes a value that may be a ReturnValue. - // If so, it returns the value wrapped in the ReturnValue. - // If not, it returns the default value. - function unwrapReturnValue(result: any, defaultValue: any) { - if (result instanceof ReturnValue) { - return result.value - } else { - return defaultValue - } - } - - for (const applicationValue of applicationValueGenerator) { - popEnvironment(context) - yield unwrapReturnValue(applicationValue, undefined) - pushEnvironment(context, environment) - } - - popEnvironment(context) - } else if (typeof fun === 'function') { - try { - yield fun.apply(thisContext, args) - } catch (e) { - // Recover from exception - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - - const loc = node.loc ?? UNKNOWN_LOCATION - if (!(e instanceof RuntimeSourceError || e instanceof errors.ExceptionError)) { - // The error could've arisen when the builtin called a source function which errored. - // If the cause was a source error, we don't want to include the error. - // However if the error came from the builtin itself, we need to handle it. - return handleRuntimeError(context, new errors.ExceptionError(e, loc)) - } - throw e - } - } else { - return handleRuntimeError(context, new errors.CallingNonFunctionValue(fun, node)) - } - - return -} - -export { evaluate as nonDetEvaluate } diff --git a/src/interpreter/interpreter.ts b/src/interpreter/interpreter.ts deleted file mode 100644 index 6377e74df..000000000 --- a/src/interpreter/interpreter.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* tslint:disable:max-classes-per-file */ -import type es from 'estree' - -import { createBlockEnvironment, pushEnvironment } from '../cse-machine/utils' -import * as errors from '../errors/errors' -import { RuntimeSourceError } from '../errors/runtimeSourceError' -import { checkEditorBreakpoints } from '../stdlib/inspector' -import type { Context, Value } from '../types' -import { getModuleDeclarationSource } from '../utils/ast/helpers' - -const handleRuntimeError = (context: Context, error: RuntimeSourceError): never => { - context.errors.push(error) - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - throw error -} - -const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol('Used to implement block scope') - -function declareIdentifier(context: Context, name: string, node: es.Node) { - const environment = currentEnvironment(context) - if (environment.head.hasOwnProperty(name)) { - const descriptors = Object.getOwnPropertyDescriptors(environment.head) - - return handleRuntimeError( - context, - new errors.VariableRedeclaration(node, name, descriptors[name].writable) - ) - } - environment.head[name] = DECLARED_BUT_NOT_YET_ASSIGNED - return environment -} - -function defineVariable(context: Context, name: string, value: Value, constant = false) { - const environment = currentEnvironment(context) - - if (environment.head[name] !== DECLARED_BUT_NOT_YET_ASSIGNED) { - return handleRuntimeError( - context, - new errors.VariableRedeclaration(context.runtime.nodes[0]!, name, !constant) - ) - } - - Object.defineProperty(environment.head, name, { - value, - writable: !constant, - enumerable: true - }) - - return environment -} - -function* visit(context: Context, node: es.Node) { - checkEditorBreakpoints(context, node) - context.runtime.nodes.unshift(node) - yield context -} - -function* leave(context: Context) { - context.runtime.break = false - context.runtime.nodes.shift() - yield context -} - -const currentEnvironment = (context: Context) => context.runtime.environments[0] - -export function* evaluateProgram( - program: es.Program, - context: Context, -) { - yield* visit(context, program) - - context.numberOfOuterEnvironments += 1 - const environment = createBlockEnvironment(context, 'programEnvironment') - pushEnvironment(context, environment) - - const otherNodes: es.Statement[] = [] - - try { - for (const node of program.body) { - if (node.type !== 'ImportDeclaration') { - otherNodes.push(node as es.Statement) - continue - } - - yield* visit(context, node) - - const moduleName = getModuleDeclarationSource(node) - const functions = context.nativeStorage.loadedModules[moduleName] - - for (const spec of node.specifiers) { - declareIdentifier(context, spec.local.name, node) - let obj: any - - switch (spec.type) { - case 'ImportSpecifier': { - obj = functions[spec.imported.name] - break - } - case 'ImportDefaultSpecifier': { - obj = functions.default - break - } - case 'ImportNamespaceSpecifier': { - obj = functions - break - } - } - - defineVariable(context, spec.local.name, obj, true) - } - yield* leave(context) - } - } catch (error) { - handleRuntimeError(context, error) - } - yield* leave(context) // Done visiting program -} diff --git a/src/mocks/context.ts b/src/mocks/context.ts index 4770fc3b6..2c2ec38b2 100644 --- a/src/mocks/context.ts +++ b/src/mocks/context.ts @@ -2,7 +2,7 @@ import type es from 'estree' import createContext, { EnvTree } from '../createContext' import { createBlockEnvironment } from '../cse-machine/utils' -import Closure from '../interpreter/closure' +import Closure from '../cse-machine/closure' import { Chapter, type Context, type Environment, Variant } from '../types' export function mockContext( @@ -66,15 +66,15 @@ export function mockRuntimeContext(): Context { export function mockClosure(): Closure { return new Closure( { - type: 'FunctionExpression', + type: 'ArrowFunctionExpression', loc: null, - id: null, params: [], + expression: false, body: { type: 'BlockStatement', body: [] - } - } as es.FunctionExpression, + }, + }, {} as Environment, {} as Context ) diff --git a/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap b/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap index 2bf27c2a6..ae1003131 100644 --- a/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap +++ b/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap @@ -2190,6 +2190,30 @@ Object { } `; +exports[`Syntaxes are allowed in the chapter they are introduced 17: passes 1`] = ` +Object { + "alertResult": Array [], + "code": "let i = 1; +for (let j = 0; j < 5; j = j + 1) { + if (j < 1) { + continue; + } else { + i = i + 1; + if (j > 2) { + break; + } + } +} +i;", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": 4, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`Syntaxes are allowed in the chapter they are introduced 18: fails a chapter below 1`] = ` Object { "alertResult": Array [], diff --git a/src/repl/main.ts b/src/repl/main.ts index e478267f2..f4675f5ab 100644 --- a/src/repl/main.ts +++ b/src/repl/main.ts @@ -3,10 +3,8 @@ import { Command } from '@commander-js/extra-typings' import { getReplCommand } from './repl' -import { nonDetCommand } from './repl-non-det' import { transpilerCommand } from './transpiler' export const getMainCommand = () => new Command() .addCommand(transpilerCommand) .addCommand(getReplCommand(), { isDefault: true }) - .addCommand(nonDetCommand) diff --git a/src/repl/repl-non-det.ts b/src/repl/repl-non-det.ts deleted file mode 100644 index 0f5d09a73..000000000 --- a/src/repl/repl-non-det.ts +++ /dev/null @@ -1,129 +0,0 @@ -import type fslib from 'fs/promises' -import * as repl from 'repl' // 'repl' here refers to the module named 'repl' in index.d.ts -import { inspect } from 'util' -import { Command } from '@commander-js/extra-typings' - -import { createContext, type IOptions, parseError, type Result, resume, runInContext } from '..' -import { CUT, TRY_AGAIN } from '../constants' -import Closure from '../interpreter/closure' -import { Chapter, Context, SuspendedNonDet, Variant } from '../types' - -const NO_MORE_VALUES_MESSAGE: string = 'There are no more values of: ' -let previousInput: string | undefined // stores the input which is then shown when there are no more values for the program -let previousResult: Result // stores the result obtained when execution is suspended - -function _handleResult( - result: Result, - context: Context, - callback: (err: Error | null, result: any) => void -) { - if (result.status === 'finished' || result.status === 'suspended-non-det') { - previousResult = result - - if (result.value === CUT) result.value = undefined - callback(null, result.value) - } else { - const error = new Error(parseError(context.errors)) - // we do not display the stack trace, because the stack trace points to code within this REPL - // program, rather than the erroneous line in the user's program. Such a trace is too low level - // to be helpful. - error.stack = undefined - callback(error, undefined) - return - } -} - -function _try_again_message(): string | undefined { - if (previousInput) { - const message: string = NO_MORE_VALUES_MESSAGE + previousInput - previousInput = undefined - - return message - } else { - return undefined - } -} - -function _resume( - toResume: SuspendedNonDet, - context: Context, - callback: (err: Error | null, result: any) => void -) { - Promise.resolve(resume(toResume)).then((result: Result) => { - if (result.status === 'finished') result.value = _try_again_message() - _handleResult(result, context, callback) - }) -} - -function _try_again(context: Context, callback: (err: Error | null, result: any) => void) { - if (previousResult && previousResult.status === 'suspended-non-det') { - _resume(previousResult, context, callback) - } else { - callback(null, _try_again_message()) - } -} - -function _run( - cmd: string, - context: Context, - options: Partial, - callback: (err: Error | null, result: any) => void -) { - if (cmd.trim() === TRY_AGAIN) { - _try_again(context, callback) - } else { - previousInput = cmd.trim() - runInContext(cmd, context, options).then(result => { - _handleResult(result, context, callback) - }) - } -} - -function _startRepl(chapter: Chapter = Chapter.SOURCE_1, useSubst: boolean, prelude = '') { - // use defaults for everything - const context = createContext(chapter, Variant.NON_DET) - const options: Partial = { - executionMethod: 'interpreter', - useSubst - } - runInContext(prelude, context, options).then(preludeResult => { - if (preludeResult.status === 'finished' || preludeResult.status === 'suspended-non-det') { - console.dir(preludeResult.value, { depth: null }) - - repl.start( - // the object being passed as argument fits the interface ReplOptions in the repl module. - { - eval: (cmd, unusedContext, unusedFilename, callback) => { - _run(cmd, context, options, callback) - }, - // set depth to a large number so that `parse()` output will not be folded, - // setting to null also solves the problem, however a reference loop might crash - writer: output => { - return output instanceof Closure || typeof output === 'function' - ? output.toString() - : inspect(output, { - depth: 1000, - colors: true - }) - } - } - ) - } else { - throw new Error(parseError(context.errors)) - } - }) -} - -export const nonDetCommand = new Command('non-det') - .option('--useSubst') - .argument('') - .action(async (fileName, { useSubst }) => { - if (fileName !== undefined) { - const fs: typeof fslib = require('fs/promises') - const data = await fs.readFile(fileName, 'utf-8') - - _startRepl(Chapter.SOURCE_3, false, data) - } else { - _startRepl(Chapter.SOURCE_3, !!useSubst) - } - }) diff --git a/src/runner/sourceRunner.ts b/src/runner/sourceRunner.ts index 7c1fd7d17..33c844b2a 100644 --- a/src/runner/sourceRunner.ts +++ b/src/runner/sourceRunner.ts @@ -11,13 +11,11 @@ import { TimeoutError } from '../errors/timeoutErrors' import { transpileToGPU } from '../gpu/gpu' import { isPotentialInfiniteLoop } from '../infiniteLoops/errors' import { testForInfiniteLoop } from '../infiniteLoops/runtime' -import { nonDetEvaluate } from '../interpreter/interpreter-non-det' import { transpileToLazy } from '../lazy/lazy' import preprocessFileImports from '../modules/preprocessor' import { defaultAnalysisOptions } from '../modules/preprocessor/analyzer' import { defaultLinkerOptions } from '../modules/preprocessor/linker' import { parse } from '../parser/parser' -import { AsyncScheduler, NonDetScheduler, PreemptiveScheduler } from '../schedulers' import { callee, getEvaluationSteps, @@ -27,7 +25,7 @@ import { } from '../stepper/stepper' import { sandboxedEval } from '../transpiler/evalContainer' import { transpile } from '../transpiler/transpiler' -import { Chapter, type Context, type RecursivePartial, type Scheduler, Variant } from '../types' +import { Chapter, type Context, type RecursivePartial, Variant } from '../types' import { forceIt } from '../utils/operators' import { validateAndAnnotate } from '../validator/validator' import { compileForConcurrent } from '../vm/svml-compiler' @@ -113,20 +111,6 @@ function runSubstitution( }) } -function runInterpreter(program: es.Program, context: Context, options: IOptions): Promise { - let it = evaluate(program, context) - let scheduler: Scheduler - if (context.variant === Variant.NON_DET) { - it = nonDetEvaluate(program, context) - scheduler = new NonDetScheduler() - } else if (options.scheduler === 'async') { - scheduler = new AsyncScheduler() - } else { - scheduler = new PreemptiveScheduler(options.steps) - } - return scheduler.run(it, context) -} - async function runNative( program: es.Program, context: Context, @@ -287,7 +271,7 @@ async function sourceRunner( return runNative(program, context, theOptions) } - return runInterpreter(program, context, theOptions) + throw new Error(`Unknown execution method: ${context.executionMethod}`) } /** diff --git a/src/stdlib/__tests__/__snapshots__/list.ts.snap b/src/stdlib/__tests__/__snapshots__/list.ts.snap index b545f8a4f..a48fd9d60 100644 --- a/src/stdlib/__tests__/__snapshots__/list.ts.snap +++ b/src/stdlib/__tests__/__snapshots__/list.ts.snap @@ -325,6 +325,32 @@ Object { } `; +exports[`append: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(append(list(123, 123), list(456, 456, 456)), list(123, 123, 456, 456, 456));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`build_list: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(build_list(x => x * x, 5), list(0, 1, 4, 9, 16));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`display_list MCE fuzz test: expectDisplayResult 1`] = ` Object { "alertResult": Array [], @@ -707,6 +733,32 @@ Object { } `; +exports[`enum_list with floats: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(enum_list(1.5, 5), list(1.5, 2.5, 3.5, 4.5));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`enum_list: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(enum_list(1, 5), list(1, 2, 3, 4, 5));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`equal: expectResult 1`] = ` Object { "alertResult": Array [], @@ -720,6 +772,19 @@ Object { } `; +exports[`filter: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(filter(x => x <= 4, list(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)), list(2, 1, 3, 4, 2));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`for_each: expectResult 1`] = ` Object { "alertResult": Array [], @@ -831,6 +896,34 @@ Object { } `; +exports[`map: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(map(x => 2 * x, list(12, 11, 3)), list(24, 22, 6));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`member: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal( + member(4, list(1, 2, 3, 4, 123, 456, 789)), + list(4, 123, 456, 789));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`non-list error head: expectParsedError 1`] = ` Object { "alertResult": Array [], @@ -902,6 +995,77 @@ Object { } `; +exports[`remove_all not found: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(remove_all(1, list(2, 3, 4)), list(2, 3, 4));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`remove_all: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(remove_all(1, list(1, 2, 3, 4, 1, 1, 1, 5, 1, 1, 6)), list(2, 3, 4, 5, 6));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`reverse: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(reverse(list(\\"string\\", \\"null\\", \\"undefined\\", \\"null\\", 123)), list(123, \\"null\\", \\"undefined\\", \\"null\\", \\"string\\"));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`set_head: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "let p = pair(1, 2); +const q = p; +set_head(p, 3); +p === q && equal(p, pair(3, 2));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`set_tail: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "let p = pair(1, 2); +const q = p; +set_tail(p, 3); +p === q && equal(p, pair(1, 3));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`tail of a 1 element list is null: expectResult 1`] = ` Object { "alertResult": Array [], diff --git a/src/stdlib/__tests__/__snapshots__/stream.ts.snap b/src/stdlib/__tests__/__snapshots__/stream.ts.snap index 37c28046f..6ff17ffc3 100644 --- a/src/stdlib/__tests__/__snapshots__/stream.ts.snap +++ b/src/stdlib/__tests__/__snapshots__/stream.ts.snap @@ -1,5 +1,75 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`append: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(stream_to_list(stream_append(stream(\\"string\\", 123), stream(456, null, undefined))) + , list(\\"string\\", 123, 456, null, undefined));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`build_list: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(stream_to_list(build_stream(x => x * x, 5)), list(0, 1, 4, 9, 16));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`enum_list with floats: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(stream_to_list(enum_stream(1.5, 5)), list(1.5, 2.5, 3.5, 4.5));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`enum_list: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(stream_to_list(enum_stream(1, 5)), list(1, 2, 3, 4, 5));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`filter: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal( + stream_to_list( + stream_filter(x => x <= 4, stream(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)) + ) +, list(2, 1, 3, 4, 2));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`for_each: expectResult 1`] = ` Object { "alertResult": Array [], @@ -30,6 +100,34 @@ Object { } `; +exports[`map: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(stream_to_list(stream_map(x => 2 * x, stream(12, 11, 3))), list(24, 22, 6));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`member: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal( + stream_to_list(stream_member(\\"string\\", stream(1, 2, 3, \\"string\\", 123, 456, null, undefined))), + list(\\"string\\", 123, 456, null, undefined));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`primitive stream functions empty stream is null: expectResult 1`] = ` Object { "alertResult": Array [], @@ -130,6 +228,22 @@ Object { } `; +exports[`remove not found: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "stream_to_list(stream_remove(2, stream(1)));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": Array [ + 1, + null, + ], + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + exports[`remove: expectResult 1`] = ` Object { "alertResult": Array [], @@ -142,3 +256,46 @@ Object { "visualiseListResult": Array [], } `; + +exports[`remove_all not found: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(stream_to_list(stream_remove_all(1, stream(2, 3, \\"1\\"))), list(2, 3, \\"1\\"));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`remove_all: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(stream_to_list(stream_remove_all(1, stream(1, 2, 3, 4, 1, 1, \\"1\\", 5, 1, 1, 6))), + list(2, 3, 4, \\"1\\", 5, 6));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`reverse: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "equal(stream_to_list( + stream_reverse( + stream(\\"string\\", null, undefined, null, 123))), +list(123, null, undefined, null, \\"string\\"));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": true, + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; diff --git a/src/stdlib/misc.ts b/src/stdlib/misc.ts index bff859425..327aa6194 100644 --- a/src/stdlib/misc.ts +++ b/src/stdlib/misc.ts @@ -1,5 +1,5 @@ -import Closure from '../interpreter/closure' -import { Context, Value } from '../types' +import Closure from '../cse-machine/closure' +import type { Context, Value } from '../types' import { stringify } from '../utils/stringify' /** diff --git a/src/types.ts b/src/types.ts index e4911a649..63dd4ad70 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,7 +6,7 @@ /* tslint:disable:max-classes-per-file */ import { SourceLocation } from 'acorn' -import * as es from 'estree' +import type es from 'estree' import { EnvTree } from './createContext' import Heap from './cse-machine/heap' diff --git a/src/utils/statementSeqTransform.ts b/src/utils/statementSeqTransform.ts index 9fb9f61e2..c2bdf78ac 100644 --- a/src/utils/statementSeqTransform.ts +++ b/src/utils/statementSeqTransform.ts @@ -1,6 +1,6 @@ import * as es from 'estree' -import { Node, StatementSequence } from '../types' +import type { Node, StatementSequence } from '../types' import * as ast from './ast/astCreator' function hasDeclarations(node: es.BlockStatement | es.Program): boolean { for (const statement of node.body) { diff --git a/src/utils/stringify.ts b/src/utils/stringify.ts index 6b83b55ab..d88076003 100644 --- a/src/utils/stringify.ts +++ b/src/utils/stringify.ts @@ -1,5 +1,5 @@ import { MAX_LIST_DISPLAY_LENGTH } from '../constants' -import Closure from '../interpreter/closure' +import Closure from '../cse-machine/closure' import { Type, Value } from '../types' import { forceIt } from './operators' From a152132ff50c1c93cc9432571d474c33c09e7737 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Fri, 3 May 2024 20:55:26 -0400 Subject: [PATCH 06/65] Relocate testing functions that are only used in one place --- src/__tests__/index.ts | 41 +++++++++++--- src/cse-machine/closure.ts | 4 +- src/mocks/context.ts | 6 ++- src/transpiler/__tests__/native.ts | 21 ++++++-- src/utils/stringify.ts | 2 +- src/utils/testing.ts | 87 ++++-------------------------- 6 files changed, 70 insertions(+), 91 deletions(-) diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts index bb6123e99..6b29bf1e6 100644 --- a/src/__tests__/index.ts +++ b/src/__tests__/index.ts @@ -1,21 +1,49 @@ -import { Position } from 'acorn/dist/acorn' -import { SourceLocation } from 'estree' +import type { Position } from 'acorn/dist/acorn' +import type { SourceLocation } from 'estree' import { findDeclaration, getScope } from '../index' -import { Chapter, Value } from '../types' +import { Chapter, type Value } from '../types' import { stripIndent } from '../utils/formatters' import { createTestContext, expectParsedError, expectParsedErrorNoErrorSnapshot, expectParsedErrorNoSnapshot, - expectResult, - expectToLooselyMatchJS, - expectToMatchJS + expectResult } from '../utils/testing' +import { type TestOptions, testSuccess, snapshot } from '../utils/testing' +import type { TestBuiltins } from '../utils/testing' const toString = (x: Value) => '' + x +function expectToMatchJS(code: string, options: TestOptions = {}) { + return testSuccess(code, options) + .then(snapshot('expect to match JS')) + .then(testResult => expect(testResult.result).toEqual(evalWithBuiltins(code, options.testBuiltins)) + ) +} + +function expectToLooselyMatchJS(code: string, options: TestOptions = {}) { + return testSuccess(code, options) + .then(snapshot('expect to loosely match JS')) + .then(testResult => expect(testResult.result.replace(/ /g, '')).toEqual( + evalWithBuiltins(code, options.testBuiltins).replace(/ /g, '') + ) + ) +} + +function evalWithBuiltins(code: string, testBuiltins: TestBuiltins = {}) { + // Ugly, but if you know how to `eval` code with some builtins attached, please change this. + let evalstring = '' + for (const key in testBuiltins) { + if (testBuiltins.hasOwnProperty(key)) { + evalstring = evalstring + 'const ' + key + ' = testBuiltins.' + key + '; ' + } + } + // tslint:disable-next-line:no-eval + return eval(evalstring + code) +} + test('Empty code returns undefined', () => { return expectResult('').toBe(undefined) }) @@ -923,3 +951,4 @@ test('Find scope of a variable declaration with multiple blocks', () => { }) expect(actual).toMatchSnapshot() }) + diff --git a/src/cse-machine/closure.ts b/src/cse-machine/closure.ts index 42dc64784..09661569e 100644 --- a/src/cse-machine/closure.ts +++ b/src/cse-machine/closure.ts @@ -1,5 +1,5 @@ import { generate } from 'astring' -import * as es from 'estree' +import type es from 'estree' import { currentEnvironment, @@ -8,7 +8,7 @@ import { isStatementSequence, uniqueId } from '../cse-machine/utils' -import { Context, Environment, StatementSequence, Value } from '../types' +import type { Context, Environment, StatementSequence, Value } from '../types' import * as ast from '../utils/ast/astCreator' import { Control, Stash, generateCSEMachineStateStream } from './interpreter' import { envInstr } from './instrCreator' diff --git a/src/mocks/context.ts b/src/mocks/context.ts index 2c2ec38b2..fbbb57875 100644 --- a/src/mocks/context.ts +++ b/src/mocks/context.ts @@ -64,6 +64,8 @@ export function mockRuntimeContext(): Context { } export function mockClosure(): Closure { + const context = mockContext() + return new Closure( { type: 'ArrowFunctionExpression', @@ -75,8 +77,8 @@ export function mockClosure(): Closure { body: [] }, }, - {} as Environment, - {} as Context + mockEnvironment(context), + context ) } diff --git a/src/transpiler/__tests__/native.ts b/src/transpiler/__tests__/native.ts index 9a4e23427..c7c1ea325 100644 --- a/src/transpiler/__tests__/native.ts +++ b/src/transpiler/__tests__/native.ts @@ -1,8 +1,22 @@ -import { runInContext } from '../../index' +import { parseError, runInContext } from '../../index' import { mockContext } from '../../mocks/context' -import { Chapter, Finished } from '../../types' +import { Chapter, type Finished } from '../../types' import { stripIndent } from '../../utils/formatters' -import { expectNativeToTimeoutAndError } from '../../utils/testing' + +async function expectNativeToTimeoutAndError(code: string, timeout: number) { + const start = Date.now() + const context = mockContext(Chapter.SOURCE_4) + const promise = runInContext(code, context, { + scheduler: 'preemptive', + executionMethod: 'native', + throwInfiniteLoops: false + }) + await promise + const timeTaken = Date.now() - start + expect(timeTaken).toBeLessThan(timeout * 5) + expect(timeTaken).toBeGreaterThanOrEqual(timeout) + return parseError(context.errors) +} test('Proper stringify-ing of arguments during potentially infinite iterative function calls', async () => { const code = stripIndent` @@ -115,3 +129,4 @@ test('assigning a = b where b was from a previous program call works', async () expect(result.status).toBe('finished') expect((result as Finished).value).toBe(1) }) + diff --git a/src/utils/stringify.ts b/src/utils/stringify.ts index d88076003..0a85887b5 100644 --- a/src/utils/stringify.ts +++ b/src/utils/stringify.ts @@ -651,4 +651,4 @@ export function lineTreeToString(tree: LineTree): string { print(tree, '\n') return total -} +} \ No newline at end of file diff --git a/src/utils/testing.ts b/src/utils/testing.ts index 361634c24..daf93b82e 100644 --- a/src/utils/testing.ts +++ b/src/utils/testing.ts @@ -3,20 +3,20 @@ import type { MockedFunction } from 'jest-mock' import createContext, { defineBuiltin } from '../createContext' import { transpileToGPU } from '../gpu/gpu' -import { parseError, Result, runInContext } from '../index' +import { parseError, runInContext } from '..' import { transpileToLazy } from '../lazy/lazy' -import { mockContext } from '../mocks/context' -import { ImportOptions } from '../modules/moduleTypes' +import type { ImportOptions } from '../modules/moduleTypes' import { parse } from '../parser/parser' import { transpile } from '../transpiler/transpiler' import { Chapter, - Context, - CustomBuiltIns, - SourceError, - Value, + type Context, + type CustomBuiltIns, + type SourceError, + type Value, Variant, - type Finished + type Finished, + type Result } from '../types' import { stringify } from './stringify' @@ -34,7 +34,7 @@ export interface TestContext extends Context { visualiseListResult: Value[] } -interface TestBuiltins { +export interface TestBuiltins { [builtinName: string]: any } @@ -50,7 +50,7 @@ interface TestResult { result: Value } -interface TestOptions { +export interface TestOptions { context?: TestContext chapter?: Chapter variant?: Variant @@ -200,16 +200,6 @@ export async function testSuccess(code: string, options: TestOptions = { native: return testResult } -export async function testSuccessWithErrors( - code: string, - options: TestOptions = { native: false } -) { - const testResult = await testInContext(code, options) - expect(testResult.numErrors).not.toEqual(0) - expect(testResult.resultStatus).toBe('finished') - return testResult -} - export async function testFailure(code: string, options: TestOptions = { native: false }) { const testResult = await testInContext(code, options) expect(testResult.numErrors).not.toEqual(0) @@ -247,10 +237,6 @@ export function snapshotSuccess(code: string, options: TestOptions, snapshotName return testSuccess(code, options).then(snapshot(snapshotName)) } -export function snapshotWarning(code: string, options: TestOptions, snapshotName: string) { - return testSuccessWithErrors(code, options).then(snapshot(snapshotName)) -} - export function snapshotFailure(code: string, options: TestOptions, snapshotName: string) { return testFailure(code, options).then(snapshot(snapshotName)) } @@ -319,63 +305,10 @@ export function expectDifferentParsedErrors( ).resolves } -export function expectWarning(code: string, options: TestOptions = {}) { - return expect( - testSuccessWithErrors(code, options) - .then(snapshot('expectWarning')) - .then(testResult => testResult.parsedErrors) - ).resolves -} - export function expectParsedErrorNoSnapshot(code: string, options: TestOptions = {}) { return expect(testFailure(code, options).then(testResult => testResult.parsedErrors)).resolves } -function evalWithBuiltins(code: string, testBuiltins: TestBuiltins = {}) { - // Ugly, but if you know how to `eval` code with some builtins attached, please change this. - let evalstring = '' - for (const key in testBuiltins) { - if (testBuiltins.hasOwnProperty(key)) { - evalstring = evalstring + 'const ' + key + ' = testBuiltins.' + key + '; ' - } - } - // tslint:disable-next-line:no-eval - return eval(evalstring + code) -} - -export function expectToMatchJS(code: string, options: TestOptions = {}) { - return testSuccess(code, options) - .then(snapshot('expect to match JS')) - .then(testResult => - expect(testResult.result).toEqual(evalWithBuiltins(code, options.testBuiltins)) - ) -} - -export function expectToLooselyMatchJS(code: string, options: TestOptions = {}) { - return testSuccess(code, options) - .then(snapshot('expect to loosely match JS')) - .then(testResult => - expect(testResult.result.replace(/ /g, '')).toEqual( - evalWithBuiltins(code, options.testBuiltins).replace(/ /g, '') - ) - ) -} - -export async function expectNativeToTimeoutAndError(code: string, timeout: number) { - const start = Date.now() - const context = mockContext(Chapter.SOURCE_4) - const promise = runInContext(code, context, { - scheduler: 'preemptive', - executionMethod: 'native', - throwInfiniteLoops: false - }) - await promise - const timeTaken = Date.now() - start - expect(timeTaken).toBeLessThan(timeout * 5) - expect(timeTaken).toBeGreaterThanOrEqual(timeout) - return parseError(context.errors) -} - export function asMockedFunc any>(func: T) { return func as MockedFunction } From 233c462faf8649b9495f9c2410e614724e6318aa Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Fri, 3 May 2024 20:56:05 -0400 Subject: [PATCH 07/65] Update module tests and remove unnecessary code --- src/modules/__tests__/moduleUtils.ts | 12 +--- src/modules/loader/loaders.ts | 2 +- src/modules/preprocessor/__tests__/linker.ts | 62 ++++++++++++++++++-- src/modules/utils.ts | 24 -------- 4 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/modules/__tests__/moduleUtils.ts b/src/modules/__tests__/moduleUtils.ts index aece3d93e..d2e83f559 100644 --- a/src/modules/__tests__/moduleUtils.ts +++ b/src/modules/__tests__/moduleUtils.ts @@ -1,14 +1,4 @@ -import { isSourceModule, removeExportDefault } from '../utils' - -describe('removeExportDefault', () => { - test('Ignores normal strings', () => { - expect(removeExportDefault('normal string')).toEqual('normal string') - }) - - test('Removes export default keywords and trailing spaces', () => { - expect(removeExportDefault('export default normal string')).toEqual('normal string') - }) -}) +import { isSourceModule } from '../utils' describe('isSourceModule', () => { test.each([ diff --git a/src/modules/loader/loaders.ts b/src/modules/loader/loaders.ts index e9c6d508b..5f02d4c22 100644 --- a/src/modules/loader/loaders.ts +++ b/src/modules/loader/loaders.ts @@ -56,7 +56,7 @@ function wrapImporter(func: (p: string) => Promise) { // Exported for testing export const docsImporter = wrapImporter<{ default: any }>(async p => { - // TODO: USe import attributes when they become supported + // TODO: Use import attributes when they become supported // Import Assertions and Attributes are not widely supported by all // browsers yet, so we use fetch in the meantime const resp = await fetch(p) diff --git a/src/modules/preprocessor/__tests__/linker.ts b/src/modules/preprocessor/__tests__/linker.ts index 4de464c41..062ca3d7f 100644 --- a/src/modules/preprocessor/__tests__/linker.ts +++ b/src/modules/preprocessor/__tests__/linker.ts @@ -1,17 +1,27 @@ +import type { ImportDeclaration } from 'estree' import { mockContext } from '../../../mocks/context' import { MissingSemicolonError } from '../../../parser/errors' import { Chapter, type Context } from '../../../types' import { CircularImportError, ModuleNotFoundError } from '../../errors' import type { SourceFiles } from '../../moduleTypes' import parseProgramsAndConstructImportGraph from '../linker' +import { asMockedFunc } from '../../../utils/testing' import * as resolver from '../resolver' jest.spyOn(resolver, 'default') +import * as parser from '../../../parser/parser' +jest.spyOn(parser, 'parse') + beforeEach(() => { jest.clearAllMocks() }) +// Wrap to appease typescript +function expectTruthy(cond: boolean): asserts cond { + expect(cond).toEqual(true) +} + async function testCode(files: T, entrypointFilePath: keyof T) { const context = mockContext(Chapter.SOURCE_4) const result = await parseProgramsAndConstructImportGraph( @@ -46,6 +56,7 @@ test('Adds CircularImportError and returns undefined when imports are circular', expect(error).toBeInstanceOf(CircularImportError) }) +// TODO: https://github.com/source-academy/js-slang/issues/1535 test.skip('Longer cycle causes also causes CircularImportError', async () => { const [error] = await expectError( { @@ -130,13 +141,52 @@ test('Linker does tree-shaking', async () => { '/a.js' ) - // Wrap to appease typescript - function expectWrapper(cond: boolean): asserts cond { - expect(cond).toEqual(true) - } - expect(errors.length).toEqual(0) - expectWrapper(result.ok) + expectTruthy(result.ok) expect(resolver.default).not.toHaveBeenCalledWith('./b.js') expect(Object.keys(result.programs)).not.toContain('/b.js') }) + +test('Linker parses each file once and only once', async () => { + const files: SourceFiles = { + '/a.js': ` + import { b } from './b.js'; + import { c } from './c.js'; + `, + '/b.js': ` + import { d } from './d.js'; + export function b() { return d; } + `, + '/c.js': ` + import { e } from './d.js'; + export function c() { return e; } + `, + '/d.js': ` + export const d = "d"; + export const e = "e"; + ` + } + + const [, result] = await testCode(files, '/a.js') + expectTruthy(result.ok) + const mockedParse = asMockedFunc(parser.parse) + + for (const fileName of Object.keys(files)) { + // Assert that parse was only called once and only once for each file + const calls = mockedParse.mock.calls.filter(([,, options]) => options?.sourceFile === fileName) + expect(calls.length).toEqual(1) + } +}) + +test('Linker updates AST\'s import source values', async () => { + const [, result] = await testCode({ + '/dir/a.js': `import { b } from '../b.js';`, + '/b.js': 'export function b() {}' + }, '/dir/a.js') + + expectTruthy(result.ok) + + const aNode = result.programs['/dir/a.js'].body[0] + expect(aNode.type).toEqual('ImportDeclaration') + expect((aNode as ImportDeclaration).source.value).toEqual('/b.js') +}) \ No newline at end of file diff --git a/src/modules/utils.ts b/src/modules/utils.ts index ef658b32a..ddf72c736 100644 --- a/src/modules/utils.ts +++ b/src/modules/utils.ts @@ -1,27 +1,3 @@ -import * as _ from 'lodash' - -import type { RecursivePartial } from '../types' -import type { ImportOptions } from './moduleTypes' -import { defaultAnalysisOptions } from './preprocessor/analyzer' - -const exportDefaultStr = 'export default' -export function removeExportDefault(text: string) { - if (text.startsWith(exportDefaultStr)) { - text = text.substring(exportDefaultStr.length).trim() - } - - if (text.endsWith(';')) { - text = text.slice(0, -1) - } - - return text -} - -export function mergeImportOptions(src?: RecursivePartial): ImportOptions { - const baseOptions = _.cloneDeep(defaultAnalysisOptions) - return _.merge(baseOptions, src as any) -} - /** * Checks if the given string refers to a Source module instead of a local module */ From 4a06e53ea783c009c9f9bec252aa7c8734bb0721 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Sat, 4 May 2024 01:30:50 -0400 Subject: [PATCH 08/65] Add tests to validator --- .../__tests__/__snapshots__/validator.ts.snap | 450 ++++++++++++++++++ src/validator/__tests__/validator.ts | 251 +++++++--- src/validator/validator.ts | 37 +- 3 files changed, 649 insertions(+), 89 deletions(-) diff --git a/src/validator/__tests__/__snapshots__/validator.ts.snap b/src/validator/__tests__/__snapshots__/validator.ts.snap index 8c007e639..59278dd99 100644 --- a/src/validator/__tests__/__snapshots__/validator.ts.snap +++ b/src/validator/__tests__/__snapshots__/validator.ts.snap @@ -1,5 +1,455 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Test validateAndAnnotate for loop variable cannot be reassigned in closure: expectParsedError 1`] = ` +Object { + "alertResult": Array [], + "code": "for (let i = 0; i < 10; i = i + 1) { + function f() { + i = 10; + } +}", + "displayResult": Array [], + "numErrors": 1, + "parsedErrors": "Line 3: Assignment to a for loop variable in the for loop is not allowed.", + "result": undefined, + "resultStatus": "error", + "visualiseListResult": Array [], +} +`; + +exports[`Test validateAndAnnotate for loop variable cannot be reassigned: expectParsedError 1`] = ` +Object { + "alertResult": Array [], + "code": "for (let i = 0; i < 10; i = i + 1) { + i = 10; +}", + "displayResult": Array [], + "numErrors": 1, + "parsedErrors": "Line 2: Assignment to a for loop variable in the for loop is not allowed.", + "result": undefined, + "resultStatus": "error", + "visualiseListResult": Array [], +} +`; + +exports[`Test validateAndAnnotate testing typability 1`] = ` +Node { + "body": Array [ + Node { + "declarations": Array [ + Node { + "end": 11, + "id": Node { + "end": 7, + "loc": SourceLocation { + "end": Position { + "column": 7, + "line": 1, + }, + "start": Position { + "column": 6, + "line": 1, + }, + }, + "name": "a", + "start": 6, + "type": "Identifier", + }, + "init": Node { + "end": 11, + "loc": SourceLocation { + "end": Position { + "column": 11, + "line": 1, + }, + "start": Position { + "column": 10, + "line": 1, + }, + }, + "raw": "1", + "start": 10, + "type": "Literal", + "value": 1, + }, + "loc": SourceLocation { + "end": Position { + "column": 11, + "line": 1, + }, + "start": Position { + "column": 6, + "line": 1, + }, + }, + "start": 6, + "type": "VariableDeclarator", + }, + ], + "end": 12, + "kind": "const", + "loc": SourceLocation { + "end": Position { + "column": 12, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "start": 0, + "typability": "NotYetTyped", + "type": "VariableDeclaration", + }, + Node { + "body": Node { + "body": Array [ + Node { + "end": 54, + "expression": Node { + "end": 53, + "loc": SourceLocation { + "end": Position { + "column": 3, + "line": 3, + }, + "start": Position { + "column": 2, + "line": 3, + }, + }, + "name": "c", + "start": 52, + "type": "Identifier", + }, + "loc": SourceLocation { + "end": Position { + "column": 4, + "line": 3, + }, + "start": Position { + "column": 2, + "line": 3, + }, + }, + "start": 52, + "type": "ExpressionStatement", + }, + Node { + "argument": Node { + "arguments": Array [], + "callee": Node { + "end": 65, + "loc": SourceLocation { + "end": Position { + "column": 10, + "line": 4, + }, + "start": Position { + "column": 9, + "line": 4, + }, + }, + "name": "f", + "start": 64, + "type": "Identifier", + }, + "end": 67, + "loc": SourceLocation { + "end": Position { + "column": 12, + "line": 4, + }, + "start": Position { + "column": 9, + "line": 4, + }, + }, + "start": 64, + "type": "CallExpression", + }, + "end": 68, + "loc": SourceLocation { + "end": Position { + "column": 13, + "line": 4, + }, + "start": Position { + "column": 2, + "line": 4, + }, + }, + "start": 57, + "type": "ReturnStatement", + }, + ], + "end": 70, + "loc": SourceLocation { + "end": Position { + "column": 1, + "line": 5, + }, + "start": Position { + "column": 13, + "line": 2, + }, + }, + "start": 37, + "type": "BlockStatement", + }, + "end": 70, + "expression": false, + "generator": false, + "id": Node { + "end": 34, + "loc": SourceLocation { + "end": Position { + "column": 10, + "line": 2, + }, + "start": Position { + "column": 9, + "line": 2, + }, + }, + "name": "f", + "start": 33, + "type": "Identifier", + }, + "loc": SourceLocation { + "end": Position { + "column": 1, + "line": 5, + }, + "start": Position { + "column": 0, + "line": 2, + }, + }, + "params": Array [], + "start": 24, + "typability": "NotYetTyped", + "type": "FunctionDeclaration", + }, + Node { + "declarations": Array [ + Node { + "end": 84, + "id": Node { + "end": 78, + "loc": SourceLocation { + "end": Position { + "column": 7, + "line": 6, + }, + "start": Position { + "column": 6, + "line": 6, + }, + }, + "name": "b", + "start": 77, + "type": "Identifier", + }, + "init": Node { + "arguments": Array [], + "callee": Node { + "end": 82, + "loc": SourceLocation { + "end": Position { + "column": 11, + "line": 6, + }, + "start": Position { + "column": 10, + "line": 6, + }, + }, + "name": "f", + "start": 81, + "type": "Identifier", + }, + "end": 84, + "loc": SourceLocation { + "end": Position { + "column": 13, + "line": 6, + }, + "start": Position { + "column": 10, + "line": 6, + }, + }, + "start": 81, + "type": "CallExpression", + }, + "loc": SourceLocation { + "end": Position { + "column": 13, + "line": 6, + }, + "start": Position { + "column": 6, + "line": 6, + }, + }, + "start": 77, + "type": "VariableDeclarator", + }, + ], + "end": 85, + "kind": "const", + "loc": SourceLocation { + "end": Position { + "column": 14, + "line": 6, + }, + "start": Position { + "column": 0, + "line": 6, + }, + }, + "start": 71, + "typability": "NotYetTyped", + "type": "VariableDeclaration", + }, + Node { + "body": Node { + "body": Array [], + "end": 128, + "loc": SourceLocation { + "end": Position { + "column": 1, + "line": 8, + }, + "start": Position { + "column": 13, + "line": 7, + }, + }, + "start": 110, + "type": "BlockStatement", + }, + "end": 128, + "expression": false, + "generator": false, + "id": Node { + "end": 107, + "loc": SourceLocation { + "end": Position { + "column": 10, + "line": 7, + }, + "start": Position { + "column": 9, + "line": 7, + }, + }, + "name": "g", + "start": 106, + "type": "Identifier", + }, + "loc": SourceLocation { + "end": Position { + "column": 1, + "line": 8, + }, + "start": Position { + "column": 0, + "line": 7, + }, + }, + "params": Array [], + "start": 97, + "typability": "Untypable", + "type": "FunctionDeclaration", + }, + Node { + "declarations": Array [ + Node { + "end": 140, + "id": Node { + "end": 136, + "loc": SourceLocation { + "end": Position { + "column": 7, + "line": 9, + }, + "start": Position { + "column": 6, + "line": 9, + }, + }, + "name": "c", + "start": 135, + "type": "Identifier", + }, + "init": Node { + "end": 140, + "loc": SourceLocation { + "end": Position { + "column": 11, + "line": 9, + }, + "start": Position { + "column": 10, + "line": 9, + }, + }, + "raw": "1", + "start": 139, + "type": "Literal", + "value": 1, + }, + "loc": SourceLocation { + "end": Position { + "column": 11, + "line": 9, + }, + "start": Position { + "column": 6, + "line": 9, + }, + }, + "start": 135, + "type": "VariableDeclarator", + }, + ], + "end": 141, + "kind": "const", + "loc": SourceLocation { + "end": Position { + "column": 12, + "line": 9, + }, + "start": Position { + "column": 0, + "line": 9, + }, + }, + "start": 129, + "typability": "Untypable", + "type": "VariableDeclaration", + }, + ], + "end": 156, + "loc": SourceLocation { + "end": Position { + "column": 27, + "line": 9, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "sourceType": "module", + "start": 0, + "type": "Program", +} +`; + exports[`for loop variable cannot be reassigned in closure: expectParsedError 1`] = ` Object { "alertResult": Array [], diff --git a/src/validator/__tests__/validator.ts b/src/validator/__tests__/validator.ts index b7bee3154..8d1355923 100644 --- a/src/validator/__tests__/validator.ts +++ b/src/validator/__tests__/validator.ts @@ -1,82 +1,201 @@ -import * as es from 'estree' +import type es from 'estree' import { mockContext } from '../../mocks/context' import { parse } from '../../parser/parser' -import { Chapter, NodeWithInferredType } from '../../types' +import { Chapter, type NodeWithInferredType } from '../../types' import { getVariableDeclarationName } from '../../utils/ast/astCreator' import { stripIndent } from '../../utils/formatters' -import { expectParsedError } from '../../utils/testing' +import { astTester } from '../../utils/testing' import { simple } from '../../utils/walkers' -import { validateAndAnnotate } from '../validator' +import { checkForUndefinedVariables, validateAndAnnotate } from '../validator' +import { UndefinedVariable } from '../../errors/errors' +import { parseError } from '../..' -export function toValidatedAst(code: string) { - const context = mockContext(Chapter.SOURCE_1) - const ast = parse(code, context) - expect(ast).not.toBeUndefined() - return validateAndAnnotate(ast as es.Program, context) -} +describe(`Test ${validateAndAnnotate.name}`, () => { + astTester( + (program, context, expected) => { + validateAndAnnotate(program, context) + if (expected === undefined) { + expect(context.errors.length).toEqual(0) + } else { + expect(parseError(context.errors)).toEqual(expected) + } + }, + [ + [ + 'for loop variable cannot be reassigned', + `for (let i = 0; i < 10; i = i + 1) { + i = 10; + } + `, + 'Line 2: Assignment to a for loop variable in the for loop is not allowed.' + ], + [ + 'for loop variable cannot be reassigned in closure', + `for (let i = 0; i < 10; i = i + 1) { + function f() { + i = 10; + } + } + `, + 'Line 3: Assignment to a for loop variable in the for loop is not allowed.' + ], + [ + 'function name cannot be reassigned', + 'function a() { a = 0; }', + 'Line 1: Cannot assign new value to constant a.' + ], + [ + 'function name can be redeclared (and then reassigned)', + 'function a() { let a = 0; a = 1; return a; }' + ] + ] + ) -test('for loop variable cannot be reassigned', async () => { - const code = stripIndent` - for (let i = 0; i < 10; i = i + 1) { - i = 10; + test('testing typability', () => { + function toValidatedAst(code: string) { + const context = mockContext(Chapter.SOURCE_1) + const ast = parse(code, context) + expect(ast).not.toBeUndefined() + return validateAndAnnotate(ast as es.Program, context) } - ` - return expectParsedError(code, { chapter: Chapter.SOURCE_4 }).toMatchInlineSnapshot( - `"Line 2: Assignment to a for loop variable in the for loop is not allowed."` - ) -}) -test('for loop variable cannot be reassigned in closure', async () => { - const code = stripIndent` - for (let i = 0; i < 10; i = i + 1) { - function f() { - i = 10; + const code = stripIndent` + const a = 1; // typable + function f() { // typable + c; + return f(); } - } - ` - return expectParsedError(code, { chapter: Chapter.SOURCE_4 }).toMatchInlineSnapshot( - `"Line 3: Assignment to a for loop variable in the for loop is not allowed."` - ) + const b = f(); // typable + function g() { // not typable + } + const c = 1; // not typable + ` + const ast = toValidatedAst(code) + expect(ast).toMatchSnapshot() + simple(ast, { + VariableDeclaration(node: NodeWithInferredType) { + let expectedTypability = '' + switch (getVariableDeclarationName(node)) { + case 'a': + case 'b': + expectedTypability = 'NotYetTyped' + break + case 'c': + expectedTypability = 'Untypable' + } + expect(node.typability).toBe(expectedTypability) + }, + FunctionDeclaration(node: NodeWithInferredType) { + let expectedTypability = '' + switch (node.id!.name) { + case 'f': + expectedTypability = 'NotYetTyped' + break + case 'g': + expectedTypability = 'Untypable' + } + expect(node.typability).toBe(expectedTypability) + } + }) + }) }) -test('testing typability', () => { - const code = stripIndent` - const a = 1; // typable - function f() { // typable - c; - return f(); - } - const b = f(); // typable - function g() { // not typable - } - const c = 1; // not typable - ` - const ast = toValidatedAst(code) - expect(ast).toMatchSnapshot() - simple(ast, { - VariableDeclaration(node: NodeWithInferredType) { - let expectedTypability = '' - switch (getVariableDeclarationName(node)) { - case 'a': - case 'b': - expectedTypability = 'NotYetTyped' - break - case 'c': - expectedTypability = 'Untypable' +describe(`Test ${checkForUndefinedVariables.name}`, () => { + astTester( + (program, context, expected) => { + let err = null + try { + checkForUndefinedVariables(program, context, {} as any, false) + } catch (error) { + err = error } - expect(node.typability).toBe(expectedTypability) - }, - FunctionDeclaration(node: NodeWithInferredType) { - let expectedTypability = '' - switch (node.id!.name) { - case 'f': - expectedTypability = 'NotYetTyped' - break - case 'g': - expectedTypability = 'Untypable' + + if (expected === undefined) { + expect(err).toBeNull() + expect(context.errors.length).toEqual(0) + } else { + expect(err).toBeInstanceOf(UndefinedVariable) + expect(parseError([err])).toEqual(expected) } - expect(node.typability).toBe(expectedTypability) - } - }) + }, + [ + // ArrowFunctionExpressions + [ + 'ArrowFunctionExpression bodies are checked', + 'const a = () => b;', + 'Line 1: Name b not declared.' + ], + [ + 'ArrowFunctionExpression bodies are checked', + 'const a = () => { b; };', + 'Line 1: Name b not declared.' + ], + ['ArrowFunctionExpression parameters are valid declarations', 'const a = b => { b; };'], + ['ArrowFunctionExpressions can call themselves recursively', 'const a = () => a();'], + + // Function declarations + ['function bodies are checked', 'function a() { b; }', 'Line 1: Name b not declared.'], + ['function names can be called recursively', 'function a() { a; }'], + ['function parameters are considered valid declarations', 'function a(b) { b; return a(); }'], + + // For loops + ['for loop init variable counts', 'for (let i = 0; i < 0; i = i + 1) { i; }'], + [ + 'for loop init is checked', + 'for (i = 0; i < 0; i = i + 1) { i; }', + 'Line 1: Name i not declared.' + ], + [ + 'for loop test is checked', + 'for (let i = 0; a < 0; i = i + 1) { i; }', + 'Line 1: Name a not declared.' + ], + [ + 'for loop update is checked', + 'for (let i = 0; i < 0; a = i + 1) { i; }', + 'Line 1: Name a not declared.' + ], + [ + 'for loops bodies are checked', + 'for (let i = 0; i < 0; i = i + 1) { a; }', + 'Line 1: Name a not declared.' + ], + + // BlockStatements and Programs + [ + 'Names in upper scopes are valid declarations', + ` + const a = "a"; + { + { + a; + } + } + ` + ], + [ + 'Names in lower scopes are not accessible by upper scopes', + `a; + { + const a = "a"; + } + `, + 'Line 1: Name a not declared.' + ], + [ + 'Names in sibling scopes are inaccessible', + `{ + { const a = "a"; } + { a; } + } + `, + 'Line 3: Name a not declared.' + ], + + // Builtins and Preludes + ['identifiers in builtins are valid', 'pair();'], + ['identifiers in preludes are valid', 'stream();'] + ] + ) }) diff --git a/src/validator/validator.ts b/src/validator/validator.ts index 14fd14962..8dc10a2d0 100644 --- a/src/validator/validator.ts +++ b/src/validator/validator.ts @@ -6,13 +6,18 @@ import { parse } from '../parser/parser' import { Context, Node, NodeWithInferredType } from '../types' import { getVariableDeclarationName } from '../utils/ast/astCreator' import { - getFunctionDeclarationNamesInProgram, getIdentifiersInNativeStorage, getIdentifiersInProgram, getNativeIds, NativeIds } from '../utils/uniqueIds' import { ancestor, base, FullWalkerCallback } from '../utils/walkers' +import { + getDeclaredIdentifiers, + getIdentifiersFromVariableDeclaration, + mapIdentifiersToNames +} from '../utils/ast/helpers' +import { isVariableDeclaration } from '../utils/ast/typeGuards' class Declaration { public accessedBeforeDeclaration: boolean = false @@ -168,32 +173,18 @@ export function checkForUndefinedVariables( globalIds: NativeIds, skipUndefined: boolean ) { - const preludes = context.prelude - ? getFunctionDeclarationNamesInProgram(parse(context.prelude, context)!) - : new Set() + const preludes = new Set( + context.prelude + ? mapIdentifiersToNames(getDeclaredIdentifiers(parse(context.prelude, context)!)) + : [] + ) const env = context.runtime.environments[0].head || {} const builtins = context.nativeStorage.builtins const identifiersIntroducedByNode = new Map>() function processBlock(node: es.Program | es.BlockStatement) { - const identifiers = new Set() - for (const statement of node.body) { - if (statement.type === 'VariableDeclaration') { - identifiers.add((statement.declarations[0].id as es.Identifier).name) - } else if (statement.type === 'FunctionDeclaration') { - if (statement.id === null) { - throw new Error( - 'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.' - ) - } - identifiers.add(statement.id.name) - } else if (statement.type === 'ImportDeclaration') { - for (const specifier of statement.specifiers) { - identifiers.add(specifier.local.name) - } - } - } + const identifiers = new Set(mapIdentifiersToNames(getDeclaredIdentifiers(node))) identifiersIntroducedByNode.set(node, identifiers) } function processFunction( @@ -219,10 +210,10 @@ export function checkForUndefinedVariables( ArrowFunctionExpression: processFunction, ForStatement(forStatement: es.ForStatement, ancestors: es.Node[]) { const init = forStatement.init! - if (init.type === 'VariableDeclaration') { + if (isVariableDeclaration(init)) { identifiersIntroducedByNode.set( forStatement, - new Set([(init.declarations[0].id as es.Identifier).name]) + new Set(mapIdentifiersToNames(getIdentifiersFromVariableDeclaration(init))) ) } }, From 5ed18c5c600358cee682adee2a19c27b0e2426ca Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Sat, 4 May 2024 01:31:11 -0400 Subject: [PATCH 09/65] Refactor testing code --- src/__tests__/index.ts | 11 +- src/cse-machine/interpreter.ts | 5 +- src/cse-machine/types.ts | 6 +- src/infiniteLoops/instrument.ts | 4 +- src/mocks/context.ts | 7 +- .../preprocessor/__tests__/analyzer.ts | 92 +++++++-------- src/modules/preprocessor/__tests__/linker.ts | 37 +++--- .../preprocessor/__tests__/preprocessor.ts | 2 +- .../transformers/hoistAndMergeImports.ts | 2 +- .../__tests__/transformers/removeExports.ts | 2 +- .../transformProgramToFunctionDeclaration.ts | 2 +- src/modules/preprocessor/analyzer.ts | 4 +- src/repl/__tests__/main.ts | 21 ++-- src/repl/index.ts | 4 +- src/repl/main.ts | 5 +- src/runner/fullJSRunner.ts | 6 +- src/stdlib/__tests__/list.ts | 28 ++--- src/transpiler/__tests__/native.ts | 1 - src/transpiler/__tests__/transpiled-code.ts | 3 +- src/transpiler/transpiler.ts | 11 +- src/utils/ast/__tests__/helpers.ts | 105 ++++++++++++++++++ src/utils/ast/helpers.ts | 92 +++++++++++---- src/utils/stringify.ts | 4 +- src/utils/{testing.ts => testing/index.ts} | 49 ++++++-- src/utils/{ast => testing}/sanitizer.ts | 0 src/utils/uniqueIds.ts | 14 --- 26 files changed, 330 insertions(+), 187 deletions(-) create mode 100644 src/utils/ast/__tests__/helpers.ts rename src/utils/{testing.ts => testing/index.ts} (86%) rename src/utils/{ast => testing}/sanitizer.ts (100%) diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts index 6b29bf1e6..98a5d2135 100644 --- a/src/__tests__/index.ts +++ b/src/__tests__/index.ts @@ -19,16 +19,18 @@ const toString = (x: Value) => '' + x function expectToMatchJS(code: string, options: TestOptions = {}) { return testSuccess(code, options) .then(snapshot('expect to match JS')) - .then(testResult => expect(testResult.result).toEqual(evalWithBuiltins(code, options.testBuiltins)) + .then(testResult => + expect(testResult.result).toEqual(evalWithBuiltins(code, options.testBuiltins)) ) } function expectToLooselyMatchJS(code: string, options: TestOptions = {}) { return testSuccess(code, options) .then(snapshot('expect to loosely match JS')) - .then(testResult => expect(testResult.result.replace(/ /g, '')).toEqual( - evalWithBuiltins(code, options.testBuiltins).replace(/ /g, '') - ) + .then(testResult => + expect(testResult.result.replace(/ /g, '')).toEqual( + evalWithBuiltins(code, options.testBuiltins).replace(/ /g, '') + ) ) } @@ -951,4 +953,3 @@ test('Find scope of a variable declaration with multiple blocks', () => { }) expect(actual).toMatchSnapshot() }) - diff --git a/src/cse-machine/interpreter.ts b/src/cse-machine/interpreter.ts index 273430774..82c36b538 100644 --- a/src/cse-machine/interpreter.ts +++ b/src/cse-machine/interpreter.ts @@ -172,12 +172,11 @@ export class Stash extends Stack { } } export interface CSEMachineOptions { - envSteps: number, - stepLimit: number, + envSteps: number + stepLimit: number isPrelude: boolean } - /** * Function to be called when a program is to be interpreted using * the explicit control evaluator. diff --git a/src/cse-machine/types.ts b/src/cse-machine/types.ts index 2726ef6e7..c0022d428 100644 --- a/src/cse-machine/types.ts +++ b/src/cse-machine/types.ts @@ -1,7 +1,7 @@ -import * as es from 'estree' +import type es from 'estree' -import { Environment, Node } from '../types' -import Closure from './closure' +import type { Environment, Node } from '../types' +import type Closure from './closure' export enum InstrType { RESET = 'Reset', diff --git a/src/infiniteLoops/instrument.ts b/src/infiniteLoops/instrument.ts index acd6c7354..a32d3dcf8 100644 --- a/src/infiniteLoops/instrument.ts +++ b/src/infiniteLoops/instrument.ts @@ -5,7 +5,7 @@ import { transformImportDeclarations } from '../transpiler/transpiler' import type { Node } from '../types' import * as create from '../utils/ast/astCreator' import { recursive, simple, WalkerCallback } from '../utils/walkers' -import { getIdsFromDeclaration } from '../utils/ast/helpers' +import { getDeclaredIdentifiers } from '../utils/ast/helpers' // transforms AST of program const globalIds = { @@ -585,7 +585,7 @@ function handleImports(programs: es.Program[]): string[] { ) program.body = [...importsToAdd, ...otherNodes] return importsToAdd.flatMap(decl => { - const ids = getIdsFromDeclaration(decl) + const ids = getDeclaredIdentifiers(decl) return ids.map(id => id.name) }) }) diff --git a/src/mocks/context.ts b/src/mocks/context.ts index fbbb57875..ffe28ed46 100644 --- a/src/mocks/context.ts +++ b/src/mocks/context.ts @@ -75,16 +75,13 @@ export function mockClosure(): Closure { body: { type: 'BlockStatement', body: [] - }, + } }, mockEnvironment(context), context ) } -export function mockEnvironment( - context: Context, - name = 'blockEnvironment', -): Environment { +export function mockEnvironment(context: Context, name = 'blockEnvironment'): Environment { return createBlockEnvironment(context, name) } diff --git a/src/modules/preprocessor/__tests__/analyzer.ts b/src/modules/preprocessor/__tests__/analyzer.ts index 9fb837f8e..d7e292691 100644 --- a/src/modules/preprocessor/__tests__/analyzer.ts +++ b/src/modules/preprocessor/__tests__/analyzer.ts @@ -19,43 +19,46 @@ beforeEach(() => { jest.clearAllMocks() }) - async function testCode( - files: T, - entrypointFilePath: keyof T, - allowUndefinedImports: boolean, - throwOnDuplicateNames: boolean - ) { - const context = createContext(Chapter.FULL_JS) - const importGraphResult = await parseProgramsAndConstructImportGraph( - p => Promise.resolve(files[p]), - entrypointFilePath as string, - context, - {}, - true - ) +async function testCode( + files: T, + entrypointFilePath: keyof T, + allowUndefinedImports: boolean, + throwOnDuplicateNames: boolean +) { + const context = createContext(Chapter.FULL_JS) + const importGraphResult = await parseProgramsAndConstructImportGraph( + p => Promise.resolve(files[p]), + entrypointFilePath as string, + context, + {}, + true + ) - // Return 'undefined' if there are errors while parsing. - if (context.errors.length !== 0 || !importGraphResult.ok) { - throw context.errors[0] - } + // Return 'undefined' if there are errors while parsing. + if (context.errors.length !== 0 || !importGraphResult.ok) { + throw context.errors[0] + } - const { programs, topoOrder, sourceModulesToImport } = importGraphResult - await loadSourceModules(sourceModulesToImport, context, false) - - try { - analyzeImportsAndExports(programs, entrypointFilePath as string, topoOrder, context, { - allowUndefinedImports, - throwOnDuplicateNames - }) - } catch (error) { - if (!(error instanceof DuplicateImportNameError) && !(error instanceof UndefinedNamespaceImportError)) { - throw error - } + const { programs, topoOrder, sourceModulesToImport } = importGraphResult + await loadSourceModules(sourceModulesToImport, context, false) - return error + try { + analyzeImportsAndExports(programs, entrypointFilePath as string, topoOrder, context, { + allowUndefinedImports, + throwOnDuplicateNames + }) + } catch (error) { + if ( + !(error instanceof DuplicateImportNameError) && + !(error instanceof UndefinedNamespaceImportError) + ) { + throw error } - return true + + return error } + return true +} describe('Test throwing import validation errors', () => { type ErrorInfo = { @@ -648,9 +651,7 @@ describe('Test throwing DuplicateImportNameErrors', () => { const isTestCaseWithNoError = (c: TestCase): c is TestCaseWithNoError => c.length === 2 - type FullTestCase = - | [string, Files, true, string | undefined] - | [string, Files, false, undefined] + type FullTestCase = [string, Files, true, string | undefined] | [string, Files, false, undefined] function expectDuplicateError(obj: any): asserts obj is DuplicateImportNameError { expect(obj).toBeInstanceOf(DuplicateImportNameError) @@ -665,18 +666,8 @@ describe('Test throwing DuplicateImportNameErrors', () => { // No error message was given, so no error is expected to be thrown, // regardless of the value of throwOnDuplicateImports const [desc] = c - const noThrowCase: FullTestCase = [ - `${i + 1}. ${desc}: no error `, - c[1], - false, - undefined - ] - const yesThrowCase: FullTestCase = [ - `${i + 1}. ${desc}: no error`, - c[1], - true, - undefined - ] + const noThrowCase: FullTestCase = [`${i + 1}. ${desc}: no error `, c[1], false, undefined] + const yesThrowCase: FullTestCase = [`${i + 1}. ${desc}: no error`, c[1], true, undefined] return [ [...noThrow, noThrowCase], [...yesThrow, yesThrowCase] @@ -684,12 +675,7 @@ describe('Test throwing DuplicateImportNameErrors', () => { } const [desc, , errMsg] = c - const noThrowCase: FullTestCase = [ - `${i + 1}. ${desc}: no error`, - c[1], - false, - undefined - ] + const noThrowCase: FullTestCase = [`${i + 1}. ${desc}: no error`, c[1], false, undefined] const yesThrowCase: FullTestCase = [`${i + 1}. ${desc}: error`, c[1], true, errMsg] return [ [...noThrow, noThrowCase], diff --git a/src/modules/preprocessor/__tests__/linker.ts b/src/modules/preprocessor/__tests__/linker.ts index 062ca3d7f..bf6109a0c 100644 --- a/src/modules/preprocessor/__tests__/linker.ts +++ b/src/modules/preprocessor/__tests__/linker.ts @@ -5,7 +5,7 @@ import { Chapter, type Context } from '../../../types' import { CircularImportError, ModuleNotFoundError } from '../../errors' import type { SourceFiles } from '../../moduleTypes' import parseProgramsAndConstructImportGraph from '../linker' -import { asMockedFunc } from '../../../utils/testing' +import { asMockedFunc, expectTrue } from '../../../utils/testing' import * as resolver from '../resolver' jest.spyOn(resolver, 'default') @@ -17,11 +17,6 @@ beforeEach(() => { jest.clearAllMocks() }) -// Wrap to appease typescript -function expectTruthy(cond: boolean): asserts cond { - expect(cond).toEqual(true) -} - async function testCode(files: T, entrypointFilePath: keyof T) { const context = mockContext(Chapter.SOURCE_4) const result = await parseProgramsAndConstructImportGraph( @@ -44,6 +39,12 @@ async function expectError(files: T, entrypointFilePath: return context.errors } +async function expectSuccess(files: T, entrypointFilePath: keyof T) { + const [, result] = await testCode(files, entrypointFilePath) + expectTrue(result.ok) + return result +} + test('Adds CircularImportError and returns undefined when imports are circular', async () => { const [error] = await expectError( { @@ -142,7 +143,7 @@ test('Linker does tree-shaking', async () => { ) expect(errors.length).toEqual(0) - expectTruthy(result.ok) + expectTrue(result.ok) expect(resolver.default).not.toHaveBeenCalledWith('./b.js') expect(Object.keys(result.programs)).not.toContain('/b.js') }) @@ -167,26 +168,26 @@ test('Linker parses each file once and only once', async () => { ` } - const [, result] = await testCode(files, '/a.js') - expectTruthy(result.ok) + await expectSuccess(files, '/a.js') const mockedParse = asMockedFunc(parser.parse) for (const fileName of Object.keys(files)) { // Assert that parse was only called once and only once for each file - const calls = mockedParse.mock.calls.filter(([,, options]) => options?.sourceFile === fileName) + const calls = mockedParse.mock.calls.filter(([, , options]) => options?.sourceFile === fileName) expect(calls.length).toEqual(1) } }) -test('Linker updates AST\'s import source values', async () => { - const [, result] = await testCode({ - '/dir/a.js': `import { b } from '../b.js';`, - '/b.js': 'export function b() {}' - }, '/dir/a.js') - - expectTruthy(result.ok) +test("Linker updates AST's import source values", async () => { + const result = await expectSuccess( + { + '/dir/a.js': `import { b } from '../b.js';`, + '/b.js': 'export function b() {}' + }, + '/dir/a.js' + ) const aNode = result.programs['/dir/a.js'].body[0] expect(aNode.type).toEqual('ImportDeclaration') expect((aNode as ImportDeclaration).source.value).toEqual('/b.js') -}) \ No newline at end of file +}) diff --git a/src/modules/preprocessor/__tests__/preprocessor.ts b/src/modules/preprocessor/__tests__/preprocessor.ts index 5f0baa0af..aa6d5d307 100644 --- a/src/modules/preprocessor/__tests__/preprocessor.ts +++ b/src/modules/preprocessor/__tests__/preprocessor.ts @@ -6,7 +6,7 @@ import { mockContext } from '../../../mocks/context' import { Chapter, type RecursivePartial } from '../../../types' import { memoizedGetModuleDocsAsync } from '../../loader/loaders' import preprocessFileImports from '..' -import { sanitizeAST } from '../../../utils/ast/sanitizer' +import { sanitizeAST } from '../../../utils/testing/sanitizer' import { parse } from '../../../parser/parser' import { accessExportFunctionName, diff --git a/src/modules/preprocessor/__tests__/transformers/hoistAndMergeImports.ts b/src/modules/preprocessor/__tests__/transformers/hoistAndMergeImports.ts index 8bcc29aa8..b86cc64c1 100644 --- a/src/modules/preprocessor/__tests__/transformers/hoistAndMergeImports.ts +++ b/src/modules/preprocessor/__tests__/transformers/hoistAndMergeImports.ts @@ -2,7 +2,7 @@ import { mockContext } from '../../../../mocks/context' import { parse } from '../../../../parser/parser' import { Chapter } from '../../../../types' import hoistAndMergeImports from '../../transformers/hoistAndMergeImports' -import { sanitizeAST } from '../../../../utils/ast/sanitizer' +import { sanitizeAST } from '../../../../utils/testing/sanitizer' describe('hoistAndMergeImports', () => { const assertASTsAreEqual = (actualCode: string, expectedCode: string) => { diff --git a/src/modules/preprocessor/__tests__/transformers/removeExports.ts b/src/modules/preprocessor/__tests__/transformers/removeExports.ts index 8e45a5e0e..c53f9fb44 100644 --- a/src/modules/preprocessor/__tests__/transformers/removeExports.ts +++ b/src/modules/preprocessor/__tests__/transformers/removeExports.ts @@ -2,7 +2,7 @@ import { mockContext } from '../../../../mocks/context' import { parse } from '../../../../parser/parser' import { Chapter, type Context } from '../../../../types' import removeExports from '../../transformers/removeExports' -import { sanitizeAST } from '../../../../utils/ast/sanitizer' +import { sanitizeAST } from '../../../../utils/testing/sanitizer' type TestCase = [description: string, inputCode: string, expectedCode: string] diff --git a/src/modules/preprocessor/__tests__/transformers/transformProgramToFunctionDeclaration.ts b/src/modules/preprocessor/__tests__/transformers/transformProgramToFunctionDeclaration.ts index 5cfb4625a..201b0f26d 100644 --- a/src/modules/preprocessor/__tests__/transformers/transformProgramToFunctionDeclaration.ts +++ b/src/modules/preprocessor/__tests__/transformers/transformProgramToFunctionDeclaration.ts @@ -3,7 +3,7 @@ import { parse } from '../../../../parser/parser' import { defaultExportLookupName } from '../../../../stdlib/localImport.prelude' import { Chapter } from '../../../../types' import { transformProgramToFunctionDeclaration } from '../../transformers/transformProgramToFunctionDeclaration' -import { sanitizeAST } from '../../../../utils/ast/sanitizer' +import { sanitizeAST } from '../../../../utils/testing/sanitizer' describe('transformImportedFile', () => { const currentFileName = '/dir/a.js' diff --git a/src/modules/preprocessor/analyzer.ts b/src/modules/preprocessor/analyzer.ts index 4841865be..95b167e1a 100644 --- a/src/modules/preprocessor/analyzer.ts +++ b/src/modules/preprocessor/analyzer.ts @@ -3,7 +3,7 @@ import { partition } from 'lodash' import assert from '../../utils/assert' import { - getIdsFromDeclaration, + getDeclaredIdentifiers, getImportedName, getModuleDeclarationSource } from '../../utils/ast/helpers' @@ -82,7 +82,7 @@ export default function analyzeImportsAndExports( if (node.type === 'ExportNamedDeclaration') { if (node.declaration) { if (!options.allowUndefinedImports) { - const ids = getIdsFromDeclaration(node.declaration) + const ids = getDeclaredIdentifiers(node.declaration) ids.forEach(id => { moduleDocs[sourceModule].add(id.name) }) diff --git a/src/repl/__tests__/main.ts b/src/repl/__tests__/main.ts index 4f7040695..ca661f8f0 100644 --- a/src/repl/__tests__/main.ts +++ b/src/repl/__tests__/main.ts @@ -1,5 +1,5 @@ -import type { Command } from "commander" -import { getMainCommand } from "../main" +import type { Command } from 'commander' +import { getMainCommand } from '../main' jest.spyOn(process, 'exit').mockImplementation(code => { throw new Error(`process.exit called with ${code}`) @@ -7,11 +7,12 @@ jest.spyOn(process, 'exit').mockImplementation(code => { describe('Make sure each subcommand can be run', () => { const mainCommand = getMainCommand() - test.each( - mainCommand.commands.map(cmd => [cmd.name(), cmd] as [string, Command]) - )('Testing %s command', (_, cmd) => { - return expect(cmd.parseAsync(['-h'], { from: 'user' })).rejects.toMatchInlineSnapshot( - '[Error: process.exit called with 0]' - ) - }) -}) \ No newline at end of file + test.each(mainCommand.commands.map(cmd => [cmd.name(), cmd] as [string, Command]))( + 'Testing %s command', + (_, cmd) => { + return expect(cmd.parseAsync(['-h'], { from: 'user' })).rejects.toMatchInlineSnapshot( + '[Error: process.exit called with 0]' + ) + } + ) +}) diff --git a/src/repl/index.ts b/src/repl/index.ts index 2c714a370..6e0daceae 100644 --- a/src/repl/index.ts +++ b/src/repl/index.ts @@ -1,3 +1,3 @@ -import { getMainCommand } from "./main"; +import { getMainCommand } from './main' -getMainCommand().parseAsync() \ No newline at end of file +getMainCommand().parseAsync() diff --git a/src/repl/main.ts b/src/repl/main.ts index f4675f5ab..0177a3873 100644 --- a/src/repl/main.ts +++ b/src/repl/main.ts @@ -5,6 +5,5 @@ import { Command } from '@commander-js/extra-typings' import { getReplCommand } from './repl' import { transpilerCommand } from './transpiler' -export const getMainCommand = () => new Command() - .addCommand(transpilerCommand) - .addCommand(getReplCommand(), { isDefault: true }) +export const getMainCommand = () => + new Command().addCommand(transpilerCommand).addCommand(getReplCommand(), { isDefault: true }) diff --git a/src/runner/fullJSRunner.ts b/src/runner/fullJSRunner.ts index ceedf567e..5cf1259b3 100644 --- a/src/runner/fullJSRunner.ts +++ b/src/runner/fullJSRunner.ts @@ -16,7 +16,7 @@ import { } from '../transpiler/transpiler' import type { Context, NativeStorage } from '../types' import * as create from '../utils/ast/astCreator' -import { getFunctionDeclarationNamesInProgram } from '../utils/uniqueIds' +import { getDeclaredIdentifiers } from '../utils/ast/helpers' import { toSourceError } from './errors' import { resolvedErrorPromise } from './utils' @@ -66,8 +66,8 @@ export async function fullJSRunner( ...preludeAndBuiltins, evallerReplacer(create.identifier(NATIVE_STORAGE_ID), new Set()) ]) - getFunctionDeclarationNamesInProgram(preEvalProgram).forEach(id => - context.nativeStorage.previousProgramsIdentifiers.add(id) + getDeclaredIdentifiers(preEvalProgram).forEach(id => + context.nativeStorage.previousProgramsIdentifiers.add(id.name) ) getGloballyDeclaredIdentifiers(preEvalProgram).forEach(id => context.nativeStorage.previousProgramsIdentifiers.add(id) diff --git a/src/stdlib/__tests__/list.ts b/src/stdlib/__tests__/list.ts index e915b3678..3da4eeef8 100644 --- a/src/stdlib/__tests__/list.ts +++ b/src/stdlib/__tests__/list.ts @@ -9,27 +9,13 @@ test('list creates list', () => { list(1, 'a string ""', () => f, f, true, 3.14); `, { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(` - Array [ - 1, - Array [ - "a string \\"\\"", - Array [ - [Function], - Array [ - [Function], - Array [ - true, - Array [ - 3.14, - null, - ], - ], - ], - ], - ], - ] - `) + ).toMatchInlineSnapshot(`[ 1, +[ "a string \"\"", +[ () => f, +[ function f() { + return 1; + }, +[true, [3.14, null]]]]]]`) }) test('pair creates pair', () => { diff --git a/src/transpiler/__tests__/native.ts b/src/transpiler/__tests__/native.ts index c7c1ea325..b9c9a6069 100644 --- a/src/transpiler/__tests__/native.ts +++ b/src/transpiler/__tests__/native.ts @@ -129,4 +129,3 @@ test('assigning a = b where b was from a previous program call works', async () expect(result.status).toBe('finished') expect((result as Finished).value).toBe(1) }) - diff --git a/src/transpiler/__tests__/transpiled-code.ts b/src/transpiler/__tests__/transpiled-code.ts index 5b54c21cb..c76e1f728 100644 --- a/src/transpiler/__tests__/transpiled-code.ts +++ b/src/transpiler/__tests__/transpiled-code.ts @@ -2,8 +2,9 @@ import { mockContext } from '../../mocks/context' import { parse } from '../../parser/parser' import { Chapter } from '../../types' import * as ast from '../../utils/ast/astCreator' -import { sanitizeAST } from '../../utils/ast/sanitizer' +import { sanitizeAST } from '../../utils/testing/sanitizer' import { stripIndent } from '../../utils/formatters' +import { astTester } from '../../utils/testing' import { transformImportDeclarations, transpile } from '../transpiler' /* DO NOT HAVE 'native[]' AS A SUBSTRING IN CODE STRINGS ANYWHERE IN THIS FILE! diff --git a/src/transpiler/transpiler.ts b/src/transpiler/transpiler.ts index 9fc545d7b..843e313f6 100644 --- a/src/transpiler/transpiler.ts +++ b/src/transpiler/transpiler.ts @@ -6,9 +6,12 @@ import { type RawSourceMap, SourceMapGenerator } from 'source-map' import { NATIVE_STORAGE_ID, UNKNOWN_LOCATION } from '../constants' import { Chapter, type Context, type NativeStorage, type Node, Variant } from '../types' import * as create from '../utils/ast/astCreator' -import { filterImportDeclarations, getImportedName } from '../utils/ast/helpers' import { - getFunctionDeclarationNamesInProgram, + filterImportDeclarations, + getDeclaredIdentifiers, + getImportedName +} from '../utils/ast/helpers' +import { getIdentifiersInNativeStorage, getIdentifiersInProgram, getNativeIds, @@ -495,8 +498,8 @@ function transpileToFullJS( ) program.body = (importNodes as es.Program['body']).concat(otherNodes) - getFunctionDeclarationNamesInProgram(program).forEach(id => - context.nativeStorage.previousProgramsIdentifiers.add(id) + getDeclaredIdentifiers(program).forEach(id => + context.nativeStorage.previousProgramsIdentifiers.add(id.name) ) getGloballyDeclaredIdentifiers(program).forEach(id => context.nativeStorage.previousProgramsIdentifiers.add(id) diff --git a/src/utils/ast/__tests__/helpers.ts b/src/utils/ast/__tests__/helpers.ts new file mode 100644 index 000000000..edf5069ff --- /dev/null +++ b/src/utils/ast/__tests__/helpers.ts @@ -0,0 +1,105 @@ +import type { VariableDeclaration } from 'estree' +import { astTester } from '../../testing' +import { + getDeclaredIdentifiers, + getIdentifiersFromVariableDeclaration, + mapIdentifiersToNames +} from '../helpers' +import { Chapter } from '../../../types' + +describe(`Test ${getDeclaredIdentifiers.name}`, () => { + astTester( + (program, _, expected) => { + const ids = getDeclaredIdentifiers(program, true) + const sorted = mapIdentifiersToNames(ids).sort() + expect(sorted).toEqual(expected) + }, + [ + ['single const var declarations', 'const a = "a";', ['a']], + ['single let var declarations', 'let a = "a";', ['a']], + ['var declaration with array pattern', 'const [a, b] = [0,1];', ['a', 'b']], + ['var declaration with object pattern', 'const {a, b} = {};', ['a', 'b']], + ['function declarations', 'function a(param) {}', ['a']], + ['import specifiers', 'import { a, c as b } from "./b.js";', ['a', 'b']], + ['import default specifiers', 'import d, { a, c as b } from "./b.js";', ['a', 'b', 'd']], + ['import namespace specifier', 'import * as a from "./b.js";', ['a']], + ['export default function', 'export default function a() {}', ['a']], + ['export default function without name', 'export default function () {}', []], + ['export named var declaration', 'export const a = "a";', ['a']], + ['export named declaration reexport', 'export { a } from "./a.js";', []], + + [ + 'program', + ` + import { d } from './d.js'; + const a = "a"; + function b() {} + export const c = "c"; + `, + ['a', 'b', 'c', 'd'] + ], + [ + 'does not check programs recursively', + ` + import { d } from './d.js'; + const a = "a"; + function b() {} + export const c = "c"; + { + const e = "e"; + } + + `, + ['a', 'b', 'c', 'd'] + ], + [ + 'checks programs for nested var declarations', + ` + import { d } from './d.js'; + const a = "a"; + function b() {} + export const c = "c"; + { + const e = "e"; + { + { + var f = "f"; + } + } + } + + `, + ['a', 'b', 'c', 'd', 'f'] + ] + ], + Chapter.FULL_JS + ) +}) + +describe(`Test ${getIdentifiersFromVariableDeclaration.name}`, () => { + astTester( + (program, _, expected) => { + expect(program.body[0].type).toEqual('VariableDeclaration') + const ids = getIdentifiersFromVariableDeclaration(program.body[0] as VariableDeclaration) + const sorted = mapIdentifiersToNames(ids).sort() + expect(sorted).toEqual(expected) + }, + [ + ['single const var declarations', 'const a = "a";', ['a']], + ['single let var declarations', 'let a = "a";', ['a']], + ['var declaration with array pattern', 'const [a, b] = [0,1];', ['a', 'b']], + ['var declaration with object pattern', 'const {a, b: { c } } = {};', ['a', 'c']], + [ + 'var declaration with object pattern with rest element', + 'const {a, b: { ...c } } = {};', + ['a', 'c'] + ], + [ + 'var declaration with complex pattern', + 'const {a: [{ d }, e], b: { c: { f, g } } } = {};', + ['d', 'e', 'f', 'g'] + ] + ], + Chapter.FULL_JS + ) +}) diff --git a/src/utils/ast/helpers.ts b/src/utils/ast/helpers.ts index 59b085bc0..fa39fb96d 100644 --- a/src/utils/ast/helpers.ts +++ b/src/utils/ast/helpers.ts @@ -3,7 +3,8 @@ import type es from 'estree' import assert from '../assert' import { simple } from '../walkers' import { ArrayMap } from '../dict' -import { isImportDeclaration, isVariableDeclaration } from './typeGuards' +import type { Node } from '../../types' +import { isImportDeclaration } from './typeGuards' export function getModuleDeclarationSource( node: Exclude @@ -40,35 +41,82 @@ export function filterImportDeclarations({ ) } -export function extractIdsFromPattern(pattern: es.Pattern) { - const identifiers: es.Identifier[] = [] +export function mapIdentifiersToNames(ids: es.Identifier[]): string[] { + return ids.map(({ name }) => name) +} - simple(pattern, { - Identifier: (node: es.Identifier) => { - identifiers.push(node) +export function getIdentifiersFromVariableDeclaration(decl: es.VariableDeclaration) { + function internal(node: es.Pattern): es.Identifier | es.Identifier[] { + switch (node.type) { + case 'ArrayPattern': + return node.elements.flatMap(internal) + case 'AssignmentPattern': + return internal(node.left) + case 'Identifier': + return node + case 'MemberExpression': + throw new Error( + 'Should not get MemberExpressions as part of the id for a VariableDeclarator' + ) + case 'ObjectPattern': + return node.properties.flatMap(prop => + prop.type === 'RestElement' ? internal(prop) : internal(prop.value) + ) + case 'RestElement': + return internal(node.argument) } - }) + } - return identifiers + return decl.declarations.flatMap(({ id }) => internal(id)) } -export function getIdsFromDeclaration( - decl: es.Declaration, - allowNull: true -): (es.Identifier | null)[] -export function getIdsFromDeclaration(decl: es.Declaration, allowNull?: false): es.Identifier[] -export function getIdsFromDeclaration(decl: es.Declaration, allowNull?: boolean) { - const rawIds = isVariableDeclaration(decl) - ? decl.declarations.flatMap(({ id }) => extractIdsFromPattern(id)) - : [decl.id] +export function getDeclaredIdentifiers( + decl: + | es.Declaration + | Exclude + | es.Program + | es.BlockStatement, + checkForVarDeclarations?: boolean +): es.Identifier[] { + if (decl.type === 'Program' || decl.type === 'BlockStatement') { + const varDecls: es.Identifier[] = [] + if (checkForVarDeclarations) { + simple(decl, { + VariableDeclaration(node: es.VariableDeclaration) { + // just to account for any 'var' declarations + // which technically are always globally scoped + if (node.kind !== 'var') return + getIdentifiersFromVariableDeclaration(node).forEach(identifier => + varDecls.push(identifier) + ) + } + }) + } + + // Don't recursively find declared identifiers + return [...varDecls, ...decl.body.flatMap(internal)] + } - if (!allowNull) { - rawIds.forEach(each => { - assert(each !== null, 'Encountered a null identifier!') - }) + function internal(decl: Node): es.Identifier[] { + switch (decl.type) { + case 'ClassDeclaration': + case 'FunctionDeclaration': + // Identifier is only null when part of a export default declaration + // in which case that node introduces no new identifiers + return decl.id ? [decl.id] : [] + case 'ExportDefaultDeclaration': + case 'ExportNamedDeclaration': + return decl.declaration ? internal(decl.declaration) : [] + case 'ImportDeclaration': + return decl.specifiers.map(({ local }) => local) + case 'VariableDeclaration': + return getIdentifiersFromVariableDeclaration(decl) + default: + return [] + } } - return rawIds + return internal(decl) } export const getImportedName = ( diff --git a/src/utils/stringify.ts b/src/utils/stringify.ts index 0a85887b5..2c5762ddf 100644 --- a/src/utils/stringify.ts +++ b/src/utils/stringify.ts @@ -1,6 +1,6 @@ import { MAX_LIST_DISPLAY_LENGTH } from '../constants' import Closure from '../cse-machine/closure' -import { Type, Value } from '../types' +import type { Type, Value } from '../types' import { forceIt } from './operators' export interface ArrayLike { @@ -651,4 +651,4 @@ export function lineTreeToString(tree: LineTree): string { print(tree, '\n') return total -} \ No newline at end of file +} diff --git a/src/utils/testing.ts b/src/utils/testing/index.ts similarity index 86% rename from src/utils/testing.ts rename to src/utils/testing/index.ts index daf93b82e..b68911126 100644 --- a/src/utils/testing.ts +++ b/src/utils/testing/index.ts @@ -1,13 +1,14 @@ import { generate } from 'astring' import type { MockedFunction } from 'jest-mock' -import createContext, { defineBuiltin } from '../createContext' -import { transpileToGPU } from '../gpu/gpu' -import { parseError, runInContext } from '..' -import { transpileToLazy } from '../lazy/lazy' -import type { ImportOptions } from '../modules/moduleTypes' -import { parse } from '../parser/parser' -import { transpile } from '../transpiler/transpiler' +import type { Program } from 'estree' +import createContext, { defineBuiltin } from '../../createContext' +import { transpileToGPU } from '../../gpu/gpu' +import { parseError, runInContext } from '../..' +import { transpileToLazy } from '../../lazy/lazy' +import type { ImportOptions } from '../../modules/moduleTypes' +import { parse } from '../../parser/parser' +import { transpile } from '../../transpiler/transpiler' import { Chapter, type Context, @@ -17,8 +18,9 @@ import { Variant, type Finished, type Result -} from '../types' -import { stringify } from './stringify' +} from '../../types' +import { mockContext } from '../../mocks/context' +import { stringify } from '../stringify' export interface CodeSnippetTestCase { name: string @@ -316,3 +318,32 @@ export function asMockedFunc any>(func: T) { export function expectFinishedResult(result: Result): asserts result is Finished { expect(result.status).toEqual('finished') } + +export function expectTrue(cond: boolean): asserts cond { + expect(cond).toEqual(true) +} + +/** + * Convenience function for testing the expected output of parsing + * a single line of code + */ +export function astTester( + func: (prog: Program, context: Context, expectedError: ExpectedError | undefined) => void, + testCases: ( + | [desc: string, code: string] + | [desc: string, code: string, expectedError: ExpectedError] + )[], + chapter: Chapter = Chapter.SOURCE_4 +) { + const fullCases = testCases.map(([desc, code, err]) => { + const context = mockContext(chapter) + const program = parse(code, context) + if (!program) { + throw context.errors[0] + } + + return [desc, program, context, err] as [string, Program, Context, ExpectedError | undefined] + }) + + test.each(fullCases)('%s', (_, ...args) => func(...args)) +} diff --git a/src/utils/ast/sanitizer.ts b/src/utils/testing/sanitizer.ts similarity index 100% rename from src/utils/ast/sanitizer.ts rename to src/utils/testing/sanitizer.ts diff --git a/src/utils/uniqueIds.ts b/src/utils/uniqueIds.ts index 348dfa149..51f10404f 100644 --- a/src/utils/uniqueIds.ts +++ b/src/utils/uniqueIds.ts @@ -66,17 +66,3 @@ export function getIdentifiersInProgram(program: es.Program) { }) return identifiers } - -export function getFunctionDeclarationNamesInProgram(program: es.Program): Set { - const functionNames = new Set() - - simple(program, { - FunctionDeclaration(node: es.FunctionDeclaration) { - if (node.id && node.id.type === 'Identifier') { - functionNames.add(node.id.name) - } - } - }) - - return functionNames -} From 9e996197f8e30c79a6f6a0dc94ac7c1a8b643439 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 7 May 2024 23:30:47 -0400 Subject: [PATCH 10/65] Miscellanous changes to validator tests --- src/validator/__tests__/validator.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/validator/__tests__/validator.ts b/src/validator/__tests__/validator.ts index 8d1355923..5cd1cc3ac 100644 --- a/src/validator/__tests__/validator.ts +++ b/src/validator/__tests__/validator.ts @@ -5,11 +5,11 @@ import { parse } from '../../parser/parser' import { Chapter, type NodeWithInferredType } from '../../types' import { getVariableDeclarationName } from '../../utils/ast/astCreator' import { stripIndent } from '../../utils/formatters' -import { astTester } from '../../utils/testing' +import { astTester, expectTrue } from '../../utils/testing' import { simple } from '../../utils/walkers' import { checkForUndefinedVariables, validateAndAnnotate } from '../validator' import { UndefinedVariable } from '../../errors/errors' -import { parseError } from '../..' +import { parseError, runInContext } from '../..' describe(`Test ${validateAndAnnotate.name}`, () => { astTester( @@ -164,7 +164,7 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { // BlockStatements and Programs [ - 'Names in upper scopes are valid declarations', + 'names in upper scopes are valid declarations', ` const a = "a"; { @@ -175,7 +175,7 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { ` ], [ - 'Names in lower scopes are not accessible by upper scopes', + 'names in lower scopes are not accessible by upper scopes', `a; { const a = "a"; @@ -184,7 +184,7 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { 'Line 1: Name a not declared.' ], [ - 'Names in sibling scopes are inaccessible', + 'names in sibling scopes are inaccessible', `{ { const a = "a"; } { a; } @@ -198,4 +198,15 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { ['identifiers in preludes are valid', 'stream();'] ] ) + + test('previous program identifiers are also valid', async () => { + const context = mockContext(Chapter.SOURCE_4) + await runInContext('const x = 0;', context) + + const newProgram = parse('x+1;', context) + expectTrue(!!newProgram) + + expect(() => checkForUndefinedVariables(newProgram, context, {} as any, false)) + .not.toThrow() + }) }) From 32b6dec02ee527662e2164f7d554717a6d7d20d9 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 7 May 2024 23:31:40 -0400 Subject: [PATCH 11/65] Add stepper as execution method --- src/runner/sourceRunner.ts | 6 +++--- src/types.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runner/sourceRunner.ts b/src/runner/sourceRunner.ts index 33c844b2a..de4f2b1b7 100644 --- a/src/runner/sourceRunner.ts +++ b/src/runner/sourceRunner.ts @@ -233,12 +233,12 @@ async function sourceRunner( return runConcurrent(program, context, theOptions) } - if (theOptions.useSubst) { + determineExecutionMethod(theOptions, context, program, isVerboseErrorsEnabled) + + if (context.executionMethod === 'stepper' || theOptions.useSubst) { return runSubstitution(program, context, theOptions) } - determineExecutionMethod(theOptions, context, program, isVerboseErrorsEnabled) - // native, don't evaluate prelude if (context.executionMethod === 'native' && context.variant === Variant.NATIVE) { return await fullJSRunner(program, context, theOptions.importOptions) diff --git a/src/types.ts b/src/types.ts index 63dd4ad70..71a5dfea4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,7 +64,7 @@ export interface Comment { loc: SourceLocation | undefined } -export type ExecutionMethod = 'native' | 'interpreter' | 'auto' | 'cse-machine' +export type ExecutionMethod = 'native' | 'auto' | 'cse-machine' | 'stepper' export enum Chapter { SOURCE_1 = 1, From 3cbddba5983eaa352d0a4c2f7a94a507c3aab53a Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 7 May 2024 23:32:27 -0400 Subject: [PATCH 12/65] Arranged tsconfig excludes alphabetically --- tsconfig.prod.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tsconfig.prod.json b/tsconfig.prod.json index 0cc257b56..3d554a636 100644 --- a/tsconfig.prod.json +++ b/tsconfig.prod.json @@ -7,12 +7,13 @@ // Excludes are overwritten when using extends, so we // list all the excludes again "exclude": [ - "src/stdlib/metacircular-interpreter", - "src/stdlib/**/*.js", - "node_modules", "dist", + "node_modules", "sicp_publish", - "src/**/__tests__/**", "src/**/__mocks__/**", + "src/**/__tests__/**", + "src/stdlib/**/*.js", + "src/stdlib/metacircular-interpreter", + "src/testing" ] } From 7c02e8370335ca96f1742de33e60c5349d0ce5fb Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Sat, 11 May 2024 13:53:19 -0400 Subject: [PATCH 13/65] Refactor stringify tests --- src/__tests__/stringify.ts | 706 ------------------------------- src/utils/__tests__/stringify.ts | 595 +++++++++++++++++++++++++- 2 files changed, 577 insertions(+), 724 deletions(-) delete mode 100644 src/__tests__/stringify.ts diff --git a/src/__tests__/stringify.ts b/src/__tests__/stringify.ts deleted file mode 100644 index 60b10301f..000000000 --- a/src/__tests__/stringify.ts +++ /dev/null @@ -1,706 +0,0 @@ -import { Chapter } from '../types' -import { stripIndent } from '../utils/formatters' -import { - lineTreeToString, - stringDagToLineTree, - stringify, - valueToStringDag -} from '../utils/stringify' -import { expectResult } from '../utils/testing' - -test('String representation of numbers are nice', () => { - return expectResult( - stripIndent` - stringify(0); - `, - { native: true } - ).toMatchInlineSnapshot(`"0"`) -}) - -test('String representation of strings are nice', () => { - return expectResult( - stripIndent` - stringify('a string'); - `, - { native: true } - ).toMatchInlineSnapshot(`"\\"a string\\""`) -}) - -test('String representation of booleans are nice', () => { - return expectResult( - stripIndent` - stringify('true'); - `, - { native: true } - ).toMatchInlineSnapshot(`"\\"true\\""`) -}) - -test('String representation of functions are nice', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x; - } - stringify(f); - `, - { native: true } - ).toMatchInlineSnapshot(` - "function f(x, y) { - return x; - }" - `) -}) - -test('String representation of arrow functions are nice', () => { - return expectResult( - stripIndent` - const f = (x, y) => x; - stringify(f); - `, - { native: true } - ).toMatchInlineSnapshot(`"(x, y) => x"`) -}) - -test('String representation of arrays are nice', () => { - return expectResult( - stripIndent` - const xs = [1, 'true', true, () => 1]; - stringify(xs); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"[1, \\"true\\", true, () => 1]"`) -}) - -test('String representation of multidimensional arrays are nice', () => { - return expectResult( - stripIndent` - const xs = [1, 'true', [true, () => 1, [[]]]]; - stringify(xs); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"[1, \\"true\\", [true, () => 1, [[]]]]"`) -}) - -test('String representation of empty arrays are nice', () => { - return expectResult( - stripIndent` - const xs = []; - stringify(xs); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"[]"`) -}) - -test('String representation of lists are nice', () => { - return expectResult( - stripIndent` - stringify(enum_list(1, 10)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`"[1, [2, [3, [4, [5, [6, [7, [8, [9, [10, null]]]]]]]]]]"`) -}) - -test('Correctly handles circular structures with multiple entry points', () => { - return expectResult( - stripIndent` - const x = enum_list(1, 3); - set_tail(tail(tail(x)), x); - stringify(list(x, tail(x), tail(tail(x)))); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "[ [1, [2, [3, ...]]], - [[2, [3, [1, ...]]], [[3, [1, [2, ...]]], null]]]" - `) -}) - -// The interpreter runs into a MaximumStackLimitExceeded error on 1000, so reduced it to 100. -// tslint:disable:max-line-length -test('String representation of huge lists are nice', () => { - return expectResult( - stripIndent` - stringify(enum_list(1, 100)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(` - "[ 1, - [ 2, - [ 3, - [ 4, - [ 5, - [ 6, - [ 7, - [ 8, - [ 9, - [ 10, - [ 11, - [ 12, - [ 13, - [ 14, - [ 15, - [ 16, - [ 17, - [ 18, - [ 19, - [ 20, - [ 21, - [ 22, - [ 23, - [ 24, - [ 25, - [ 26, - [ 27, - [ 28, - [ 29, - [ 30, - [ 31, - [ 32, - [ 33, - [ 34, - [ 35, - [ 36, - [ 37, - [ 38, - [ 39, - [ 40, - [ 41, - [ 42, - [ 43, - [ 44, - [ 45, - [ 46, - [ 47, - [ 48, - [ 49, - [ 50, - [ 51, - [ 52, - [ 53, - [ 54, - [ 55, - [ 56, - [ 57, - [ 58, - [ 59, - [ 60, - [ 61, - [ 62, - [ 63, - [ 64, - [ 65, - [ 66, - [ 67, - [ 68, - [ 69, - [ 70, - [ 71, - [ 72, - [ 73, - [ 74, - [ 75, - [ 76, - [ 77, - [ 78, - [ 79, - [ 80, - [ 81, - [ 82, - [ 83, - [ 84, - [ 85, - [ 86, - [ 87, - [ 88, - [89, [90, [91, [92, [93, [94, [95, [96, [97, [98, [99, [100, null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" - `) -}) -// tslint:enable:max-line-length - -test('String representation of huge arrays are nice', () => { - return expectResult( - stripIndent` - const arr = []; - for (let i = 0; i < 100; i = i + 1) { - arr[i] = i; - } - stringify(arr); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "[ 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99]" - `) -}) - -test('String representation of objects are nice', () => { - return expectResult( - stripIndent` - const o = { a: 1, b: true, c: () => 1 }; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"{\\"a\\": 1, \\"b\\": true, \\"c\\": () => 1}"`) -}) - -test('String representation of objects with toReplString member calls toReplString', () => { - return expectResult( - stripIndent` - const o = { toReplString: () => '' }; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`""`) -}) - -test('String representation of nested objects are nice', () => { - return expectResult( - stripIndent` - const o = { a: 1, b: true, c: () => 1, d: { e: 5, f: 6 } }; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot( - `"{\\"a\\": 1, \\"b\\": true, \\"c\\": () => 1, \\"d\\": {\\"e\\": 5, \\"f\\": 6}}"` - ) -}) - -test('String representation of big objects are nice', () => { - return expectResult( - stripIndent` - const o = { a: 1, b: true, c: () => 1, d: { e: 5, f: 6 }, g: 0, h: 0, i: 0, j: 0, k: 0, l: 0, m: 0, n: 0}; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(` - "{ \\"a\\": 1, - \\"b\\": true, - \\"c\\": () => 1, - \\"d\\": {\\"e\\": 5, \\"f\\": 6}, - \\"g\\": 0, - \\"h\\": 0, - \\"i\\": 0, - \\"j\\": 0, - \\"k\\": 0, - \\"l\\": 0, - \\"m\\": 0, - \\"n\\": 0}" - `) -}) - -test('String representation of nested objects are nice', () => { - return expectResult( - stripIndent` - let o = {}; - o.o = o; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"{\\"o\\": ...}"`) -}) - -test('String representation of non literal objects is nice', () => { - const errorMsg: string = 'This is an error' - const errorObj: Error = new Error(errorMsg) - return expect(stringify(errorObj)).toMatchInlineSnapshot(`"${errorObj.toString()}"`) -}) - -test('String representation of non literal objects in nested object is nice', () => { - const errorMsg: string = 'This is an error' - const errorObj: Error = new Error(errorMsg) - const nestedObj: Object = { - data: [1, [2, errorObj], 3] - } - return expect(stringify(nestedObj)).toMatchInlineSnapshot( - `"{\\"data\\": [1, [2, ${errorObj.toString()}], 3]}"` - ) -}) - -test('String representation of instances is nice', () => { - class TestClass { - data: string - constructor(data: string) { - this.data = data - } - toString() { - return `testClass instance: ${this.data}` - } - } - const testClassInst = new TestClass('test1') - return expect(stringify(testClassInst)).toMatchInlineSnapshot(`"${testClassInst.toString()}"`) -}) - -test('String representation of builtins are nice', () => { - return expectResult( - stripIndent` - stringify(pair); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(` - "function pair(left, right) { - [implementation hidden] - }" - `) -}) - -test('String representation of null is nice', () => { - return expectResult( - stripIndent` - stringify(null); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`"null"`) -}) - -test('String representation of undefined is nice', () => { - return expectResult( - stripIndent` - stringify(undefined); - `, - { native: true } - ).toMatchInlineSnapshot(`"undefined"`) -}) - -// tslint:disable:max-line-length -test('String representation with no indent', () => { - return expectResult( - stripIndent` - stringify(parse('x=>x;'), 0); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(` - "[\\"lambda_expression\\", - [[[\\"name\\", [\\"x\\", null]], null], - [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" - `) -}) - -test('String representation with 1 space indent', () => { - return expectResult( - stripIndent` - stringify(parse('x=>x;'), 1); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(` - "[\\"lambda_expression\\", - [[[\\"name\\", [\\"x\\", null]], null], - [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" - `) -}) - -test('String representation with default (2 space) indent', () => { - return expectResult( - stripIndent` - stringify(parse('x=>x;')); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(` - "[ \\"lambda_expression\\", - [ [[\\"name\\", [\\"x\\", null]], null], - [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" - `) -}) - -test('String representation with more than 10 space indent should trim to 10 space indent', () => { - return expectResult( - stripIndent` - stringify(parse('x=>x;'), 100); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(` - "[ \\"lambda_expression\\", - [ [[\\"name\\", [\\"x\\", null]], null], - [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" - `) -}) -// tslint:enable:max-line-length - -test('lineTreeToString', () => { - return expect( - lineTreeToString({ - type: 'block', - prefixFirst: '[ ', - prefixRest: ' ', - block: [ - { - type: 'block', - prefixFirst: '[ ', - prefixRest: ' ', - block: [ - { type: 'line', line: { type: 'terminal', str: 'why', length: 3 } }, - { type: 'line', line: { type: 'terminal', str: 'hello', length: 5 } } - ], - suffixRest: ',', - suffixLast: ' ]' - }, - { type: 'line', line: { type: 'terminal', str: 'there', length: 5 } }, - { type: 'line', line: { type: 'terminal', str: 'sethbling here', length: 42 } } - ], - suffixRest: ',', - suffixLast: ' ]' - }) - ).toMatchInlineSnapshot(` - "[ [ why, - hello ], - there, - sethbling here ]" - `) -}) - -test('stringDagToLineTree', () => { - return expect( - lineTreeToString( - stringDagToLineTree( - { - type: 'multiline', - lines: ['hello world', 'why hello there', "it's a", ' multiline', 'string!'], - length: 42 - }, - 2, - 80 - ) - ) - ).toMatchInlineSnapshot(` - "hello world - why hello there - it's a - multiline - string!" - `) -}) - -test('stringDagToLineTree part 2', () => { - return expect( - stringDagToLineTree( - { - type: 'pair', - head: { type: 'terminal', str: '42', length: 2 }, - tail: { - type: 'pair', - head: { type: 'terminal', str: '69', length: 2 }, - tail: { type: 'terminal', str: 'null', length: 4 }, - length: 42 - }, - length: 42 - }, - 2, - 80 - ) - ).toMatchInlineSnapshot(` - Object { - "line": Object { - "head": Object { - "length": 2, - "str": "42", - "type": "terminal", - }, - "length": 42, - "tail": Object { - "head": Object { - "length": 2, - "str": "69", - "type": "terminal", - }, - "length": 42, - "tail": Object { - "length": 4, - "str": "null", - "type": "terminal", - }, - "type": "pair", - }, - "type": "pair", - }, - "type": "line", - } - `) -}) - -test('stringDagToLineTree part 3', () => { - return expect( - lineTreeToString( - stringDagToLineTree( - { - type: 'pair', - head: { type: 'terminal', str: '42', length: 2 }, - tail: { - type: 'pair', - head: { type: 'terminal', str: '69', length: 2 }, - tail: { type: 'terminal', str: 'null', length: 4 }, - length: 42 - }, - length: 42 - }, - 2, - 80 - ) - ) - ).toMatchInlineSnapshot(`"[42, [69, null]]"`) -}) - -test('stringDagToLineTree part 4', () => { - return expect( - lineTreeToString( - stringDagToLineTree( - { - type: 'pair', - head: { type: 'terminal', str: '42', length: 2 }, - tail: { - type: 'pair', - head: { type: 'terminal', str: '69', length: 2 }, - tail: { type: 'terminal', str: 'null', length: 4 }, - length: 42 - }, - length: 99 - }, - 2, - 80 - ) - ) - ).toMatchInlineSnapshot(` - "[ 42, - [69, null]]" - `) -}) - -test('value to StringDag', () => { - return expect( - lineTreeToString( - stringDagToLineTree( - valueToStringDag([ - 1, - [ - 2, - [ - 3, - [ - 4, - [ - 5, - [ - 6, - [ - 7, - [ - 8, - [9, [10, [11, [12, [13, [14, [15, [16, [17, [18, [19, [20, null]]]]]]]]]]]] - ] - ] - ] - ] - ] - ] - ] - ]), - 2, - 80 - ) - ) - ).toMatchInlineSnapshot(` - "[ 1, - [ 2, - [ 3, - [ 4, - [ 5, - [ 6, - [ 7, - [8, [9, [10, [11, [12, [13, [14, [15, [16, [17, [18, [19, [20, null]]]]]]]]]]]]]]]]]]]]" - `) -}) diff --git a/src/utils/__tests__/stringify.ts b/src/utils/__tests__/stringify.ts index 8b10d7199..b7c670053 100644 --- a/src/utils/__tests__/stringify.ts +++ b/src/utils/__tests__/stringify.ts @@ -1,23 +1,582 @@ -import { stringify } from '../stringify' +import { list, set_tail, tail } from '../../stdlib/list' +import { Chapter, type Value } from '../../types' +import { stripIndent } from '../formatters' +import { lineTreeToString, stringDagToLineTree, stringify, valueToStringDag } from '../stringify' +import { testCases } from '../testing/caseTesters' -describe('stringify', () => { - test('works with arrays with holes', () => { - { - const a = [] - a[1] = [] - expect(stringify(a)).toMatchInlineSnapshot(`"[undefined, []]"`) - } +import '../testing' - { - const a = [] - a[2] = [] - expect(stringify(a)).toMatchInlineSnapshot(`"[undefined, undefined, []]"`) - } +type TestCase = [string, Value, string] +const cases: TestCase[] = [ + // Primitives + ['String representation of numbers are nice', 0, '0'], + ['String representation of string are nice', 'a string', '"a string"'], + ['String representation of booleans are nice', true, 'true'], + [ + 'String representation of functions are nice', + // @ts-ignore + function f(x, y) { + return x + }, + stripIndent` + function f(x, y) { + return x; + } + ` + ], + [ + 'String representation of arrow functions are nice', + // @ts-ignore + (x, y) => x, + '(x, y) => x' + ], + ['String representation of null is nice', null, 'null'], + ['String representation of undefined is nice', undefined, 'undefined'], + + // Arrays + [ + 'String representations of arrays are nice', + [1, 'true', true, () => 1], + '[1, "true", true, () => 1]' + ], + [ + 'String representation of multidimensional arrays are nice', + [1, 'true', [true, () => 1, [[]]]], + '[1, "true", [true, () => 1, [[]]]]' + ], + ['String representation of empty arrays are nice', [], '[]'], + ['String representation works with arrays with holes 1', [, []], '[undefined, []]'], + ['String representation works with arrays with holes 2', [, , []], '[undefined, undefined, []]'], + [ + 'String representation of huge arrays is nice', + Array.from(Array(100)).map((_, i) => i), + stripIndent` + [ 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99] + ` + ], + // Lists + [ + 'String representtion of lists are nice', + list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), + '[1, [2, [3, [4, [5, [6, [7, [8, [9, [10, null]]]]]]]]]]' + ], + [ + 'String representation of huge lists are nice', + list(...Array.from(Array(100)).map((_, i) => i + 1)), + stripIndent` + [ 1, + [ 2, + [ 3, + [ 4, + [ 5, + [ 6, + [ 7, + [ 8, + [ 9, + [ 10, + [ 11, + [ 12, + [ 13, + [ 14, + [ 15, + [ 16, + [ 17, + [ 18, + [ 19, + [ 20, + [ 21, + [ 22, + [ 23, + [ 24, + [ 25, + [ 26, + [ 27, + [ 28, + [ 29, + [ 30, + [ 31, + [ 32, + [ 33, + [ 34, + [ 35, + [ 36, + [ 37, + [ 38, + [ 39, + [ 40, + [ 41, + [ 42, + [ 43, + [ 44, + [ 45, + [ 46, + [ 47, + [ 48, + [ 49, + [ 50, + [ 51, + [ 52, + [ 53, + [ 54, + [ 55, + [ 56, + [ 57, + [ 58, + [ 59, + [ 60, + [ 61, + [ 62, + [ 63, + [ 64, + [ 65, + [ 66, + [ 67, + [ 68, + [ 69, + [ 70, + [ 71, + [ 72, + [ 73, + [ 74, + [ 75, + [ 76, + [ 77, + [ 78, + [ 79, + [ 80, + [ 81, + [ 82, + [ 83, + [ 84, + [ 85, + [ 86, + [ 87, + [ 88, + [89, [90, [91, [92, [93, [94, [95, [96, [97, [98, [99, [100, null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] + ` + ], + + // Objects + [ + 'String representation of objects is nice', + { a: 1, b: true, c: () => 1 }, + '{"a": 1, "b": true, "c": () => 1}' + ], + [ + 'String representation of nested objects is nice', + { a: 1, b: true, c: () => 1, d: { e: 5, f: 6 } }, + '{"a": 1, "b": true, "c": () => 1, "d": {"e": 5, "f": 6}}' + ], + [ + 'String representation of big objects are nice', { - const a = [] - a[3] = [] - expect(stringify(a)).toMatchInlineSnapshot(`"[undefined, undefined, undefined, []]"`) - } - }) + a: 1, + b: true, + c: () => 1, + d: { e: 5, f: 6 }, + g: 0, + h: 0, + i: 0, + j: 0, + k: 0, + l: 0, + m: 0, + n: 0 + }, + stripIndent` + { "a": 1, + "b": true, + "c": () => 1, + "d": {"e": 5, "f": 6}, + "g": 0, + "h": 0, + "i": 0, + "j": 0, + "k": 0, + "l": 0, + "m": 0, + "n": 0} + ` + ] +] + +testCases(cases, (value, expected) => { + return expect(stringify(value)).toEqual(expected) +}) + +// Test cases that are a little bit more complicated +test('Correctly handles circular lists with multiple entry points', () => { + const x = list(1, 2, 3) + set_tail(tail(tail(x)), x) + const value = stringify(list(x, tail(x), tail(tail(x)))) + expect(value).toEqual( + stripIndent`[ [1, [2, [3, ...]]],\n[[2, [3, [1, ...]]], [[3, [1, [2, ...]]], null]]]` + ) +}) + +test('String representation of non literal objects is nice', () => { + const errorMsg: string = 'This is an error' + const errorObj: Error = new Error(errorMsg) + return expect(stringify(errorObj)).toMatchInlineSnapshot(`"${errorObj.toString()}"`) +}) + +test('String representation of non literal objects in nested object is nice', () => { + const errorMsg: string = 'This is an error' + const errorObj: Error = new Error(errorMsg) + const nestedObj: Object = { + data: [1, [2, errorObj], 3] + } + return expect(stringify(nestedObj)).toMatchInlineSnapshot( + `"{\\"data\\": [1, [2, ${errorObj.toString()}], 3]}"` + ) +}) + +test('String representation of objects with circular references is nice', () => { + let o: any = {} + o.o = o + expect(stringify(o)).toEqual('{"o": ...}') +}) + +test('String representation of objects with toReplString member calls toReplString', () => { + const toReplString = jest.fn(() => '') + const o = { toReplString } + expect(stringify(o)).toEqual('') + expect(toReplString).toHaveBeenCalledTimes(1) +}) + +test('String representation of builtins are nice', () => { + return expect({ + code: `stringify(pair);`, + chapter: Chapter.SOURCE_2 + }).toEvaluateToValue( + stripIndent` + function pair(left, right) { + [implementation hidden] + }` + ) +}) + +test('String representation with 1 space indent', () => { + return expect({ + code: "stringify(parse('x=>x;'), 1);", + chapter: Chapter.SOURCE_4 + }).toEvaluateToValue(` + ["lambda_expression", + [[["name", ["x", null]], null], + [["return_statement", [["name", ["x", null]], null]], null]]] + `) +}) + +test('String representation with default (2 space) indent', () => { + return expect({ + code: 'stringify(parse("x=>x;"));', + chapter: Chapter.SOURCE_4 + }).toEvaluateToValue(` + [ "lambda_expression", + [ [["name", ["x", null]], null], + [["return_statement", [["name", ["x", null]], null]], null]]]`) +}) + +test('String representation with more than 10 space indent should trim to 10 space indent', () => { + return expect({ + code: 'stringify(parse("x=>x;"), 100);', + chapter: Chapter.SOURCE_4 + }).toEvaluateToValue(` + [ "lambda_expression", + [ [["name", ["x", null]], null], + [["return_statement", [["name", ["x", null]], null]], null]]] + `) +}) + +test('lineTreeToString', () => { + return expect( + lineTreeToString({ + type: 'block', + prefixFirst: '[ ', + prefixRest: ' ', + block: [ + { + type: 'block', + prefixFirst: '[ ', + prefixRest: ' ', + block: [ + { type: 'line', line: { type: 'terminal', str: 'why', length: 3 } }, + { type: 'line', line: { type: 'terminal', str: 'hello', length: 5 } } + ], + suffixRest: ',', + suffixLast: ' ]' + }, + { type: 'line', line: { type: 'terminal', str: 'there', length: 5 } }, + { type: 'line', line: { type: 'terminal', str: 'sethbling here', length: 42 } } + ], + suffixRest: ',', + suffixLast: ' ]' + }) + ).toMatchInlineSnapshot(` + "[ [ why, + hello ], + there, + sethbling here ]" + `) +}) + +test('stringDagToLineTree', () => { + return expect( + lineTreeToString( + stringDagToLineTree( + { + type: 'multiline', + lines: ['hello world', 'why hello there', "it's a", ' multiline', 'string!'], + length: 42 + }, + 2, + 80 + ) + ) + ).toMatchInlineSnapshot(` + "hello world + why hello there + it's a + multiline + string!" + `) +}) + +test('stringDagToLineTree part 2', () => { + return expect( + stringDagToLineTree( + { + type: 'pair', + head: { type: 'terminal', str: '42', length: 2 }, + tail: { + type: 'pair', + head: { type: 'terminal', str: '69', length: 2 }, + tail: { type: 'terminal', str: 'null', length: 4 }, + length: 42 + }, + length: 42 + }, + 2, + 80 + ) + ).toMatchInlineSnapshot(` + Object { + "line": Object { + "head": Object { + "length": 2, + "str": "42", + "type": "terminal", + }, + "length": 42, + "tail": Object { + "head": Object { + "length": 2, + "str": "69", + "type": "terminal", + }, + "length": 42, + "tail": Object { + "length": 4, + "str": "null", + "type": "terminal", + }, + "type": "pair", + }, + "type": "pair", + }, + "type": "line", + } + `) +}) + +test('stringDagToLineTree part 3', () => { + return expect( + lineTreeToString( + stringDagToLineTree( + { + type: 'pair', + head: { type: 'terminal', str: '42', length: 2 }, + tail: { + type: 'pair', + head: { type: 'terminal', str: '69', length: 2 }, + tail: { type: 'terminal', str: 'null', length: 4 }, + length: 42 + }, + length: 42 + }, + 2, + 80 + ) + ) + ).toMatchInlineSnapshot(`"[42, [69, null]]"`) +}) + +test('stringDagToLineTree part 4', () => { + return expect( + lineTreeToString( + stringDagToLineTree( + { + type: 'pair', + head: { type: 'terminal', str: '42', length: 2 }, + tail: { + type: 'pair', + head: { type: 'terminal', str: '69', length: 2 }, + tail: { type: 'terminal', str: 'null', length: 4 }, + length: 42 + }, + length: 99 + }, + 2, + 80 + ) + ) + ).toMatchInlineSnapshot(` + "[ 42, + [69, null]]" + `) +}) + +test('value to StringDag', () => { + return expect( + lineTreeToString( + stringDagToLineTree( + valueToStringDag([ + 1, + [ + 2, + [ + 3, + [ + 4, + [ + 5, + [ + 6, + [ + 7, + [ + 8, + [9, [10, [11, [12, [13, [14, [15, [16, [17, [18, [19, [20, null]]]]]]]]]]]] + ] + ] + ] + ] + ] + ] + ] + ]), + 2, + 80 + ) + ) + ).toMatchInlineSnapshot(` + "[ 1, + [ 2, + [ 3, + [ 4, + [ 5, + [ 6, + [ 7, + [8, [9, [10, [11, [12, [13, [14, [15, [16, [17, [18, [19, [20, null]]]]]]]]]]]]]]]]]]]]" + `) }) From 54732e77d7b9b88ade4f60a29dbd5e809eb786e8 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 20 May 2024 15:42:07 +0800 Subject: [PATCH 14/65] Refactored tests for runners into the runners folder --- src/__tests__/__snapshots__/display.ts.snap | 136 -- src/__tests__/__snapshots__/index.ts.snap | 983 ----------- src/__tests__/__snapshots__/lazy.ts.snap | 99 -- src/__tests__/__snapshots__/scmlib.ts.snap | 1536 ----------------- .../__snapshots__/scope-finding.ts.snap | 324 ++++ src/__tests__/__snapshots__/stringify.ts.snap | 460 ----- .../__snapshots__/tailcall-return.ts.snap | 169 -- src/__tests__/code-snippets.ts | 364 ++++ src/__tests__/display.ts | 159 +- src/__tests__/environment.ts | 12 +- src/__tests__/index.ts | 937 +--------- src/__tests__/lazy.ts | 119 -- src/__tests__/non-det-interpreter.ts | 1044 ----------- src/__tests__/non-det-sicp-examples.ts | 209 --- src/__tests__/scope-finding.ts | 463 +++++ src/__tests__/scope-refactoring.ts | 4 +- src/__tests__/stringify-benchmark.ts | 153 -- src/__tests__/tailcall-return.ts | 147 -- src/runner/__tests__/files.ts | 2 +- src/runner/__tests__/runners.ts | 195 ++- src/runner/fullJSRunner.ts | 11 +- .../__tests__/__snapshots__/validator.ts.snap | 482 ------ src/validator/__tests__/validator.ts | 3 +- 23 files changed, 1430 insertions(+), 6581 deletions(-) delete mode 100644 src/__tests__/__snapshots__/lazy.ts.snap delete mode 100644 src/__tests__/__snapshots__/scmlib.ts.snap create mode 100644 src/__tests__/__snapshots__/scope-finding.ts.snap delete mode 100644 src/__tests__/__snapshots__/stringify.ts.snap delete mode 100644 src/__tests__/__snapshots__/tailcall-return.ts.snap create mode 100644 src/__tests__/code-snippets.ts delete mode 100644 src/__tests__/lazy.ts delete mode 100644 src/__tests__/non-det-interpreter.ts delete mode 100644 src/__tests__/non-det-sicp-examples.ts create mode 100644 src/__tests__/scope-finding.ts delete mode 100644 src/__tests__/stringify-benchmark.ts delete mode 100644 src/__tests__/tailcall-return.ts diff --git a/src/__tests__/__snapshots__/display.ts.snap b/src/__tests__/__snapshots__/display.ts.snap index 6b6ccfc32..7e1d32e14 100644 --- a/src/__tests__/__snapshots__/display.ts.snap +++ b/src/__tests__/__snapshots__/display.ts.snap @@ -1,126 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`display can be used to display (escaped) strings: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(\\"Tom's assisstant said: \\\\\\"tuna.\\\\\\"\\");", - "displayResult": Array [ - "\\"Tom's assisstant said: \\\\\\"tuna.\\\\\\"\\"", - ], - "numErrors": 0, - "parsedErrors": "", - "result": "Tom's assisstant said: \\"tuna.\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display arrays: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display([1, 2, [4, 5]]);", - "displayResult": Array [ - "[1, 2, [4, 5]]", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - Array [ - 4, - 5, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display functions: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(x => x); display((x, y) => x + y);", - "displayResult": Array [ - "x => x", - "(x, y) => x + y", - ], - "numErrors": 0, - "parsedErrors": "", - "result": [Function], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display funny numbers: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(1e38); display(NaN); display(Infinity);", - "displayResult": Array [ - "1e+38", - "NaN", - "Infinity", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Infinity, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display lists: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(list(1, 2));", - "displayResult": Array [ - "[1, [2, null]]", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - 2, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display numbers: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(0);", - "displayResult": Array [ - "0", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display second argument can be a string: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(31072020, \\"my_first_String\\");", - "displayResult": Array [ - "my_first_String 31072020", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 31072020, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`display throw error if second argument is non-string when used: expectParsedError 1`] = ` Object { "alertResult": Array [], @@ -146,18 +25,3 @@ Object { "visualiseListResult": Array [], } `; - -exports[`raw_display can be used to display (unescaped) strings directly: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "raw_display(\\"Tom's assisstant said: \\\\\\"tuna.\\\\\\"\\");", - "displayResult": Array [ - "Tom's assisstant said: \\"tuna.\\"", - ], - "numErrors": 0, - "parsedErrors": "", - "result": "Tom's assisstant said: \\"tuna.\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/index.ts.snap b/src/__tests__/__snapshots__/index.ts.snap index 01db19b3c..6cd0defcc 100644 --- a/src/__tests__/__snapshots__/index.ts.snap +++ b/src/__tests__/__snapshots__/index.ts.snap @@ -1,63 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Accessing array with nonexistent index returns undefined: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = []; -a[1];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Allow display to return value it is displaying: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "25*(display(1+1));", - "displayResult": Array [ - "2", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 50, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Array assignment has value: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let arr = []; -const a = arr[0] = 1; -const b = arr[1] = arr[2] = 4; -arr[0] === 1 && arr[1] === 4 && arr[2] === 4;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Arrays toString matches up with JS: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "toString([1, 2]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "1,2", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Arrow function definition returns itself: expectResult 1`] = ` Object { "alertResult": Array [], @@ -71,51 +13,6 @@ Object { } `; -exports[`Arrow function infinite recursion with list args represents CallExpression well: expectParsedErrorNoErrorSnapshot 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = xs => append(f(xs), list()); -f(list(1, 2));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Maximum call stack size exceeded - f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]])..", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Assignment has value: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let a = 1; -let b = a = 4; -b === 4 && a === 4;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins hide their implementation when stringify: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(pair);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "function pair(left, right) { - [implementation hidden] -}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - exports[`Builtins hide their implementation when toString: expectResult 1`] = ` Object { "alertResult": Array [], @@ -130,883 +27,3 @@ Object { "visualiseListResult": Array [], } `; - -exports[`Can overwrite lets when assignment is allowed: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test() { - let variable = false; - variable = true; - return variable; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot overwrite consts even when assignment is allowed: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const constant = 3; - constant = 4; - return constant; -} -test();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Cannot assign new value to constant constant.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Empty code returns undefined: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Factorial arrow function: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const fac = (i) => i === 1 ? 1 : i * fac(i-1); -fac(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 120, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Find arrow function declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 9, - "line": 2, - }, - "start": Position { - "column": 6, - "line": 2, - }, -} -`; - -exports[`Find arrow function param declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 21, - "line": 1, - }, - "start": Position { - "column": 18, - "line": 1, - }, -} -`; - -exports[`Find declaration in init of for loop 1`] = ` -SourceLocation { - "end": Position { - "column": 10, - "line": 2, - }, - "start": Position { - "column": 9, - "line": 2, - }, -} -`; - -exports[`Find declaration of of variable in update statement of a for loop 1`] = ` -SourceLocation { - "end": Position { - "column": 10, - "line": 1, - }, - "start": Position { - "column": 9, - "line": 1, - }, -} -`; - -exports[`Find function declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 12, - "line": 2, - }, - "start": Position { - "column": 9, - "line": 2, - }, -} -`; - -exports[`Find function param declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 21, - "line": 1, - }, - "start": Position { - "column": 18, - "line": 1, - }, -} -`; - -exports[`Find no declaration from occurrence when there is no declaration (syntax error) 1`] = `null`; - -exports[`Find no declaration from selection that does not refer to a declaration 1`] = `null`; - -exports[`Find scope of a function declaration 1`] = ` -Array [ - SourceLocation { - "end": Position { - "column": 5, - "line": 8, - }, - "start": Position { - "column": 4, - "line": 3, - }, - }, -] -`; - -exports[`Find scope of a function parameter 1`] = ` -Array [ - SourceLocation { - "end": Position { - "column": 9, - "line": 7, - }, - "start": Position { - "column": 22, - "line": 5, - }, - }, -] -`; - -exports[`Find scope of a nested variable declaration 1`] = ` -Array [ - SourceLocation { - "end": Position { - "column": 5, - "line": 8, - }, - "start": Position { - "column": 4, - "line": 3, - }, - }, -] -`; - -exports[`Find scope of a variable declaration 1`] = ` -Array [ - Object { - "end": Position { - "column": 4, - "line": 3, - }, - "start": Position { - "column": 0, - "line": 1, - }, - }, - Object { - "end": Position { - "column": 3, - "line": 10, - }, - "start": Position { - "column": 5, - "line": 8, - }, - }, -] -`; - -exports[`Find scope of a variable declaration with more nesting 1`] = ` -Array [ - Object { - "end": Position { - "column": 12, - "line": 6, - }, - "start": Position { - "column": 4, - "line": 3, - }, - }, - Object { - "end": Position { - "column": 8, - "line": 11, - }, - "start": Position { - "column": 13, - "line": 8, - }, - }, - Object { - "end": Position { - "column": 5, - "line": 14, - }, - "start": Position { - "column": 9, - "line": 13, - }, - }, -] -`; - -exports[`Find scope of a variable declaration with multiple blocks 1`] = ` -Array [ - Object { - "end": Position { - "column": 8, - "line": 4, - }, - "start": Position { - "column": 4, - "line": 2, - }, - }, - Object { - "end": Position { - "column": 8, - "line": 8, - }, - "start": Position { - "column": 9, - "line": 6, - }, - }, - Object { - "end": Position { - "column": 8, - "line": 12, - }, - "start": Position { - "column": 9, - "line": 10, - }, - }, - Object { - "end": Position { - "column": 5, - "line": 15, - }, - "start": Position { - "column": 9, - "line": 14, - }, - }, -] -`; - -exports[`Find variable declaration in block statement 1`] = ` -SourceLocation { - "end": Position { - "column": 7, - "line": 2, - }, - "start": Position { - "column": 6, - "line": 2, - }, -} -`; - -exports[`Find variable declaration in function scope from occurrence in function scope 1`] = ` -SourceLocation { - "end": Position { - "column": 7, - "line": 3, - }, - "start": Position { - "column": 6, - "line": 3, - }, -} -`; - -exports[`Find variable declaration in global scope 1`] = ` -SourceLocation { - "end": Position { - "column": 5, - "line": 1, - }, - "start": Position { - "column": 4, - "line": 1, - }, -} -`; - -exports[`Find variable declaration in global scope from occurrence in function scope 1`] = ` -SourceLocation { - "end": Position { - "column": 5, - "line": 1, - }, - "start": Position { - "column": 4, - "line": 1, - }, -} -`; - -exports[`Find variable declaration of same name as variable declaration in block statement 1`] = ` -SourceLocation { - "end": Position { - "column": 5, - "line": 5, - }, - "start": Position { - "column": 4, - "line": 5, - }, -} -`; - -exports[`Find variable declaration with same name as arrow function param declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 9, - "line": 4, - }, - "start": Position { - "column": 6, - "line": 4, - }, -} -`; - -exports[`Find variable declaration with same name as init of for loop 1`] = ` -SourceLocation { - "end": Position { - "column": 7, - "line": 5, - }, - "start": Position { - "column": 6, - "line": 5, - }, -} -`; - -exports[`Function infinite recursion with list args represents CallExpression well: expectParsedErrorNoErrorSnapshot 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(xs) { return append(f(xs), list()); } -f(list(1, 2));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Maximum call stack size exceeded - f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]])..", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Functions passed into non-source functions remain equal: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function t(x, y, z) { - return x + y + z; -} -identity(t) === t && t(1, 2, 3) === 6;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Simple arrow function infinite recursion represents CallExpression well: expectParsedErrorNoErrorSnapshot 1`] = ` -Object { - "alertResult": Array [], - "code": "(x => x(x)(x))(x => x(x)(x));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Maximum call stack size exceeded - x(x => x(x)(x)).. x(x => x(x)(x)).. x(x => x(x)(x))..", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Simple function infinite recursion represents CallExpression well: expectParsedErrorNoErrorSnapshot 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) {return x(x)(x);} f(f);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Maximum call stack size exceeded - x(x => { - return x(x)(x); -}).. x(x => { - return x(x)(x); -}).. x(x => { - return x(x)(x); -})..", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Single boolean self-evaluates to itself: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Single number self-evaluates to itself: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "42;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 42, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Single string self-evaluates to itself: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "'42';", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "42", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test apply_in_underlying_javascript: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "apply_in_underlying_javascript((a, b, c) => a * b * c, list(2, 5, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "native:60 -interpreted:undefined", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test context reuse: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 0; -function f() { - i = i + 1; - return i; -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test context reuse: expectResult 2`] = ` -Object { - "alertResult": Array [], - "code": "i = 100; f();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 101, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test context reuse: expectResult 3`] = ` -Object { - "alertResult": Array [], - "code": "f(); i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 102, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test context reuse: expectResult 4`] = ` -Object { - "alertResult": Array [], - "code": "i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 102, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test equal for different lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "!equal(list(1, 2), pair(1, 2)) && !equal(list(1, 2, 3), list(1, list(2, 3)));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test equal for lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(list(1, 2), pair(1, pair(2, null))) && equal(list(1, 2, 3, 4), list(1, 2, 3, 4));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test equal for primitives: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(1, 1) && equal(\\"str\\", \\"str\\") && equal(null, null) && !equal(1, 2) && !equal(\\"str\\", \\"\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`false if with empty else works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "if (false) { -} else { -}", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`false if with nonempty if works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "if (false) { -} else { - 2; -}", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`functions toString (mostly) matches up with JS: expect to loosely match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return 5; -} -toString(a=>a) + toString(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "native:\\"a => afunction f(x) {\\\\n return 5;\\\\n}\\" -interpreted:\\"a => ax => {\\\\n return 5;\\\\n}\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`parseError for missing semicolon: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "42", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing semicolon at the end of statement", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`parseError for template literals with expressions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\`\${1}\`;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expressions are not allowed in template literals (\`multiline strings\`)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`primitives toString matches up with JS: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "toString(true) + -toString(false) + -toString(1) + -toString(1.5) + -toString(null) + -toString(undefined) + -toString(NaN);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "truefalse11.5nullundefinedNaN", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test && shortcircuiting: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false && 1();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test || shortcircuiting: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true || 1();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false && false: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false && false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false && true: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false && true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false || false: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false || false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false || true: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false || true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false conditional expression: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false ? true : false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true && false: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true && false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true && true: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true && true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true || false: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true || false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true || true: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true || true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true conditional expression: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true ? true : false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`true if with empty if works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "if (true) { -} else { -}", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`true if with nonempty if works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "if (true) { - 1; -} else { -}", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/lazy.ts.snap b/src/__tests__/__snapshots__/lazy.ts.snap deleted file mode 100644 index 6afc6ad90..000000000 --- a/src/__tests__/__snapshots__/lazy.ts.snap +++ /dev/null @@ -1,99 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Binary operations force arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function add(x, y) { - return x + y; -} -const res = add(((x) => x)(5), ((x) => x + 1)(9)); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 15, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Conditionals force test: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(a, b) { - return (a ? true : head(null)) && (!b ? true : head(null)); -} - -const res = f(((b) => b)(true), ((b) => !b)(true)); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Thunks are memoized: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 1; - -function incX() { - x = x + 1; - return x; -} - -function square(n) { - return n * n; -} - -const res = square(incX()); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Thunks capture local environment: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function addSome(x) { - const y = x + 1; - return z => y + z; -} - -const addSome2 = addSome(2); - -const res = addSome2(3); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Unary operations force argument: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function neg(b) { - return !b; -} -const res = neg(((x) => x)(false)); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/scmlib.ts.snap b/src/__tests__/__snapshots__/scmlib.ts.snap deleted file mode 100644 index 59bcac4bd..000000000 --- a/src/__tests__/__snapshots__/scmlib.ts.snap +++ /dev/null @@ -1,1536 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Builtins work as expected 0: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(eq? \\"a\\" \\"a\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 1: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(eq? \\"a\\" \\"b\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(eq? '(1 2 3) '(1 2 3))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 3: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(eqv? '(1 2 3) '(1 2 3))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 4: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(equal? 1 \\"1\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 5: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(equal? '(1 2 3) '(1 2 3))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 6: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(+)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 7: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(+ 1 2 3)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 8: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(- 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": -1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 9: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(- 1 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": -1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 10: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(*)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 11: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(* 1 2 3)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 12: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(/ 1 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0.5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 13: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(= 1 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 14: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(= 1 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 15: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(< 1 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 16: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(< 1 2 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 17: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(> 1 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 18: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(> 3 2 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 19: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(<= 1 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 20: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(<= 1 2 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 21: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(>= 1 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 22: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(>= 3 2 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 23: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(>= 3 2 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 24: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(number? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 25: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(number? \\"1\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 26: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(real? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 27: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(integer? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 28: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(integer? 1.1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 29: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(exact? 1.1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 30: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(exact-integer? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 31: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(exact-integer? 1.1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 32: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(zero? 0)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 33: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(zero? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 34: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(positive? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 35: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(positive? 0)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 36: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(negative? -1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 37: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(negative? 0)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 38: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(odd? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 39: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(odd? 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 40: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(even? 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 41: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(even? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 42: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(max 1 2 3)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 43: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(max)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": -Infinity, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 44: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(min)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Infinity, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 45: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(min 1 2 3)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 46: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(abs -1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 47: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(abs 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 48: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(quotient 5 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 49: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(quotient 5 2.5)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 50: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(quotient 5 -2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": -2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 51: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(modulo 5 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 52: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(modulo 5 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 53: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(remainder 5 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 54: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(remainder 5 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 55: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(gcd 32 4110 22)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 56: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(lcm 32 4110 22)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 723360, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 57: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(floor 1.1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 58: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(ceiling 1.1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 59: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(truncate 1.6)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 60: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(truncate 1.1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 61: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(round 1.1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 62: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(round 1.6)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 63: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(square 4)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 16, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 64: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(exact-integer-sqrt 16)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 65: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(expt 2 3)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 8, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 66: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(number->string 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "1", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 67: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(boolean? (= 0 0))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 68: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(boolean? 0)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 69: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(and #t #t #t)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 70: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(and #t #f #t)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 71: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(or #f #f #t)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 72: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(or #f #f #f)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 73: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(not #t)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 74: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(not #f)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 75: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string? \\"abc\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 76: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 77: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(make-string 3)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": " ", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 78: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(make-string 3 \\"a\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "aaa", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 79: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string \\"hi\\" \\" \\" \\"mum\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "hi mum", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 80: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string-length \\"abc\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 81: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string-ref \\"abc\\" 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "b", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 82: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string=? \\"abc\\" \\"abc\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 83: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string=? \\"abc\\" \\"def\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 84: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string? \\"abc\\" \\"def\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 87: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string>? \\"def\\" \\"abc\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 88: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string<=? \\"abc\\" \\"abc\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 89: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string<=? \\"abc\\" \\"def\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 90: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string<=? \\"def\\" \\"abc\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 91: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string>=? \\"abc\\" \\"abc\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 92: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string>=? \\"def\\" \\"abc\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 93: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string>=? \\"abc\\" \\"def\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 94: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(substring \\"abc\\" 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "bc", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 95: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(substring \\"abc\\" 1 2)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "b", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 96: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string-append \\"a\\" \\"b\\" \\"c\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "abc", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 97: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(string->number \\"123\\")", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 123, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 98: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(procedure? (lambda (a) a))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 99: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(procedure? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 100: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(pair? (cons 1 2))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 101: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(pair? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 102: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(car (cons 1 2))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 103: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(cdr (cons 1 2))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 104: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(list? (list 1 2 3))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 105: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(list? (cons 1 2))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 106: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(null? (list))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 107: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(null? (cons 1 2))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 108: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(length (list 1 2 3))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 109: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(length (list))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 110: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(list-ref (list 1 2 3) 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 111: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(list->string '(\\"a\\" \\"b\\" \\"c\\"))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "abc", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 112: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(list->string (string->list \\"abc\\"))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "abc", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 113: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(promise? (delay 1))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 114: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(promise? 1)", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 115: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(force (delay 1))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "native:1 -interpreted:undefined", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 116: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(apply + 1 2 3 4 '(5 6 7 8))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 36, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 117: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "(apply + '(5 6 7 8))", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 26, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/scope-finding.ts.snap b/src/__tests__/__snapshots__/scope-finding.ts.snap new file mode 100644 index 000000000..6c7570d78 --- /dev/null +++ b/src/__tests__/__snapshots__/scope-finding.ts.snap @@ -0,0 +1,324 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Find arrow function declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 9, + "line": 2, + }, + "start": Position { + "column": 6, + "line": 2, + }, +} +`; + +exports[`Find arrow function param declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 21, + "line": 1, + }, + "start": Position { + "column": 18, + "line": 1, + }, +} +`; + +exports[`Find declaration in init of for loop 1`] = ` +SourceLocation { + "end": Position { + "column": 10, + "line": 2, + }, + "start": Position { + "column": 9, + "line": 2, + }, +} +`; + +exports[`Find declaration of of variable in update statement of a for loop 1`] = ` +SourceLocation { + "end": Position { + "column": 10, + "line": 1, + }, + "start": Position { + "column": 9, + "line": 1, + }, +} +`; + +exports[`Find function declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 12, + "line": 2, + }, + "start": Position { + "column": 9, + "line": 2, + }, +} +`; + +exports[`Find function param declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 21, + "line": 1, + }, + "start": Position { + "column": 18, + "line": 1, + }, +} +`; + +exports[`Find no declaration from occurrence when there is no declaration (syntax error) 1`] = `null`; + +exports[`Find no declaration from selection that does not refer to a declaration 1`] = `null`; + +exports[`Find scope of a function declaration 1`] = ` +Array [ + SourceLocation { + "end": Position { + "column": 5, + "line": 8, + }, + "start": Position { + "column": 4, + "line": 3, + }, + }, +] +`; + +exports[`Find scope of a function parameter 1`] = ` +Array [ + SourceLocation { + "end": Position { + "column": 9, + "line": 7, + }, + "start": Position { + "column": 22, + "line": 5, + }, + }, +] +`; + +exports[`Find scope of a nested variable declaration 1`] = ` +Array [ + SourceLocation { + "end": Position { + "column": 5, + "line": 8, + }, + "start": Position { + "column": 4, + "line": 3, + }, + }, +] +`; + +exports[`Find scope of a variable declaration 1`] = ` +Array [ + Object { + "end": Position { + "column": 4, + "line": 3, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + Object { + "end": Position { + "column": 3, + "line": 10, + }, + "start": Position { + "column": 5, + "line": 8, + }, + }, +] +`; + +exports[`Find scope of a variable declaration with more nesting 1`] = ` +Array [ + Object { + "end": Position { + "column": 12, + "line": 6, + }, + "start": Position { + "column": 4, + "line": 3, + }, + }, + Object { + "end": Position { + "column": 8, + "line": 11, + }, + "start": Position { + "column": 13, + "line": 8, + }, + }, + Object { + "end": Position { + "column": 5, + "line": 14, + }, + "start": Position { + "column": 9, + "line": 13, + }, + }, +] +`; + +exports[`Find scope of a variable declaration with multiple blocks 1`] = ` +Array [ + Object { + "end": Position { + "column": 8, + "line": 4, + }, + "start": Position { + "column": 4, + "line": 2, + }, + }, + Object { + "end": Position { + "column": 8, + "line": 8, + }, + "start": Position { + "column": 9, + "line": 6, + }, + }, + Object { + "end": Position { + "column": 8, + "line": 12, + }, + "start": Position { + "column": 9, + "line": 10, + }, + }, + Object { + "end": Position { + "column": 5, + "line": 15, + }, + "start": Position { + "column": 9, + "line": 14, + }, + }, +] +`; + +exports[`Find variable declaration in block statement 1`] = ` +SourceLocation { + "end": Position { + "column": 7, + "line": 2, + }, + "start": Position { + "column": 6, + "line": 2, + }, +} +`; + +exports[`Find variable declaration in function scope from occurrence in function scope 1`] = ` +SourceLocation { + "end": Position { + "column": 7, + "line": 3, + }, + "start": Position { + "column": 6, + "line": 3, + }, +} +`; + +exports[`Find variable declaration in global scope 1`] = ` +SourceLocation { + "end": Position { + "column": 5, + "line": 1, + }, + "start": Position { + "column": 4, + "line": 1, + }, +} +`; + +exports[`Find variable declaration in global scope from occurrence in function scope 1`] = ` +SourceLocation { + "end": Position { + "column": 5, + "line": 1, + }, + "start": Position { + "column": 4, + "line": 1, + }, +} +`; + +exports[`Find variable declaration of same name as variable declaration in block statement 1`] = ` +SourceLocation { + "end": Position { + "column": 5, + "line": 5, + }, + "start": Position { + "column": 4, + "line": 5, + }, +} +`; + +exports[`Find variable declaration with same name as arrow function param declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 9, + "line": 4, + }, + "start": Position { + "column": 6, + "line": 4, + }, +} +`; + +exports[`Find variable declaration with same name as init of for loop 1`] = ` +SourceLocation { + "end": Position { + "column": 7, + "line": 5, + }, + "start": Position { + "column": 6, + "line": 5, + }, +} +`; diff --git a/src/__tests__/__snapshots__/stringify.ts.snap b/src/__tests__/__snapshots__/stringify.ts.snap deleted file mode 100644 index 494c56d15..000000000 --- a/src/__tests__/__snapshots__/stringify.ts.snap +++ /dev/null @@ -1,460 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Correctly handles circular structures with multiple entry points: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const x = enum_list(1, 3); -set_tail(tail(tail(x)), x); -stringify(list(x, tail(x), tail(tail(x))));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ [1, [2, [3, ...]]], -[[2, [3, [1, ...]]], [[3, [1, [2, ...]]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of arrays are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const xs = [1, 'true', true, () => 1]; -stringify(xs);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[1, \\"true\\", true, () => 1]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of arrow functions are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x; -stringify(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "(x, y) => x", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of booleans are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "\\"true\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of builtins are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(pair);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "function pair(left, right) { - [implementation hidden] -}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of empty arrays are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const xs = []; -stringify(xs);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of functions are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x; -} -stringify(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "native:\\"function f(x, y) {\\\\n return x;\\\\n}\\" -interpreted:\\"(x, y) => {\\\\n return x;\\\\n}\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of huge arrays are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const arr = []; -for (let i = 0; i < 100; i = i + 1) { - arr[i] = i; -} -stringify(arr);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of huge lists are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(enum_list(1, 100));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ 1, -[ 2, -[ 3, -[ 4, -[ 5, -[ 6, -[ 7, -[ 8, -[ 9, -[ 10, -[ 11, -[ 12, -[ 13, -[ 14, -[ 15, -[ 16, -[ 17, -[ 18, -[ 19, -[ 20, -[ 21, -[ 22, -[ 23, -[ 24, -[ 25, -[ 26, -[ 27, -[ 28, -[ 29, -[ 30, -[ 31, -[ 32, -[ 33, -[ 34, -[ 35, -[ 36, -[ 37, -[ 38, -[ 39, -[ 40, -[ 41, -[ 42, -[ 43, -[ 44, -[ 45, -[ 46, -[ 47, -[ 48, -[ 49, -[ 50, -[ 51, -[ 52, -[ 53, -[ 54, -[ 55, -[ 56, -[ 57, -[ 58, -[ 59, -[ 60, -[ 61, -[ 62, -[ 63, -[ 64, -[ 65, -[ 66, -[ 67, -[ 68, -[ 69, -[ 70, -[ 71, -[ 72, -[ 73, -[ 74, -[ 75, -[ 76, -[ 77, -[ 78, -[ 79, -[ 80, -[ 81, -[ 82, -[ 83, -[ 84, -[ 85, -[ 86, -[ 87, -[ 88, -[89, [90, [91, [92, [93, [94, [95, [96, [97, [98, [99, [100, null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of lists are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(enum_list(1, 10));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[1, [2, [3, [4, [5, [6, [7, [8, [9, [10, null]]]]]]]]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of multidimensional arrays are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const xs = [1, 'true', [true, () => 1, [[]]]]; -stringify(xs);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[1, \\"true\\", [true, () => 1, [[]]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of null is nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "null", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of numbers are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(0);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "0", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of strings are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify('a string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "\\"a string\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of undefined is nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "undefined", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with 1 space indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse('x=>x;'), 1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[\\"lambda_expression\\", -[[[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with default (2 space) indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse('x=>x;'));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ \\"lambda_expression\\", -[ [[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with more than 10 space indent should trim to 10 space indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse('x=>x;'), 100);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ \\"lambda_expression\\", -[ [[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with no indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse('x=>x;'), 0);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[\\"lambda_expression\\", -[[[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/tailcall-return.ts.snap b/src/__tests__/__snapshots__/tailcall-return.ts.snap deleted file mode 100644 index 2ebb65935..000000000 --- a/src/__tests__/__snapshots__/tailcall-return.ts.snap +++ /dev/null @@ -1,169 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Simple tail call returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in boolean operators work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return false || f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in conditional expressions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x <= 0 ? y : f(x-1, y+1); -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in nested mix of conditional expressions boolean operators work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in arrow block functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -}; -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in arrow functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x <= 0 ? y : f(x-1, y+1); -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mixed tail-call/non-tail-call recursion work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y, z) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+f(0, z, 0), z); - } -} -f(5000, 5000, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 15000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mutual recursion with arrow functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x <= 0 ? y : g(x-1, y+1); -const g = (x, y) => x <= 0 ? y : f(x-1, y+1); -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mutual recursion work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return g(x-1, y+1); - } -} -function g(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/code-snippets.ts b/src/__tests__/code-snippets.ts new file mode 100644 index 000000000..9fbbeb4fd --- /dev/null +++ b/src/__tests__/code-snippets.ts @@ -0,0 +1,364 @@ +import { parseError, runInContext } from '..' +import { defineBuiltin } from '../createContext' + +import { mockContext } from '../mocks/context' +import { Chapter, type Value } from '../types' +import { stripIndent } from '../utils/formatters' +import { expectFinishedResult, type TestBuiltins } from '../utils/testing' +import { testMultipleCases } from '../utils/testing/testers' + +async function testCodeSnippet( + code: string, + expected: Value, + chapter: Chapter = Chapter.SOURCE_1, + builtins: TestBuiltins = {} +) { + const context = mockContext(chapter) + Object.entries(builtins).forEach(([key, value]) => defineBuiltin(context, key, value)) + + const result = await runInContext(code, context) + if (result.status !== 'finished') { + console.log(context.errors) + } + + expectFinishedResult(result) + expect(result.value).toEqual(expected) +} + +describe('Test basic code snippets', () => { + type TestCase = + | [code: string, expected: Value] + | [code: string, expected: Value, chapter: Chapter] + testMultipleCases( + [ + ['Empty code returns undefined', '', undefined], + ['Single string evaluates to itself', '"42";', '42'], + ['Multiline string evaluates to itself', '"1\\n1";', '1\n1'], + ['Single number evaluates to itself', '42;', 42], + ['Single boolean evaluates to itself', 'true;', true], + + [ + 'Assignment has value', + ` + let a = 1; + let b = a = 4; + b === 4 && a === 4; + `, + true, + Chapter.SOURCE_3 + ], + + // Arrays + [ + 'Array assignment has value', + ` + let arr = []; + const a = arr[0] = 1; + const b = arr[1] = arr[2] = 4; + arr[0] === 1 && arr[1] === 4 && arr[2] === 4; + `, + true, + Chapter.SOURCE_3 + ], + [ + 'Accessing array at non-existent index returns undefined', + ` + const a = [1,2]; + a[3]; + `, + undefined, + Chapter.SOURCE_4 + ], + + // Objects + [ + 'Simple object assignment and retrieval', + ` + const o = {}; + o.a = 1; + o.a; + `, + 1, + Chapter.LIBRARY_PARSER + ], + [ + 'Deep object assignment and retrieval', + ` + const o = {}; + o.a = {}; + o.a.b = {}; + o.a.b.c = "string"; + o.a.b.c; + `, + 'string', + Chapter.LIBRARY_PARSER + ], + [ + 'Accessing non-existent property on object returns undefined', + ` + const o = {}; + o.nonexistent; + `, + undefined, + Chapter.LIBRARY_PARSER + ], + + // Control structures + [ + 'true if with empty block works', + ` + if (true) { + } else {} + `, + undefined + ], + [ + 'true if with non-empty block works', + ` + if (true) { 1; } + else {} + `, + 1 + ], + [ + 'false if with empty else works', + ` + if (false) {} + else {} + `, + undefined + ], + [ + 'false if with non empty else works', + ` + if (false) {} + else { 2; } + `, + 2 + ], + + // Builtins, + ['Display returns the value it is displaying', '25*display(1+1);', 50], + [ + 'apply_in_underlying_javascript', + 'apply_in_underlying_javascript((a, b, c) => a * b * c, list(2, 5, 6));', + 60, + Chapter.SOURCE_4 + ], + + // General snippets + [ + 'Factorial arrow function', + ` + const fac = (i) => i === 1 ? 1 : i * fac(i-1); + fac(5); + `, + 120 + ], + [ + 'Rest parameters work', + ` + function rest(a, b, ...c) { + let sum = a + b; + for (let i = 0; i < array_length(c); i = i + 1) { + sum = sum + c[i]; + } + return sum; + } + rest(1, 2); // no error + rest(1, 2, ...[3, 4, 5], ...[6, 7], ...[]); + `, + 28, + Chapter.SOURCE_3 + ], + [ + 'Can overwrite lets when assignment is allowed', + ` + function test() { + let variable = false; + variable = true; + return variable; + } + test(); + `, + true, + Chapter.SOURCE_3 + ] + ], + async c => { + const chapter = c.length === 3 ? c[2] : Chapter.SOURCE_1 + const [code, expected] = c + + return testCodeSnippet(code, expected, chapter) + } + ) +}) + +describe('Test equal', () => { + testMultipleCases<[string, string, boolean]>( + [ + // Primitives + ['Equality between numbers', '1', '1', true], + ['Inequality between numbers', '1', '2', false], + ['Equality for null', 'null', 'null', true], + ['Equality for strings', "'str'", "'str'", true], + ['Inequality for strings', "''", "'str'", false], + + // Lists + ['Equality for lists created using list()', 'list(1, 2)', 'list(1, 2)', true], + ['Equality for lists created using pair()', `list(1, 2)`, 'pair(1, pair(2, null))', true], + [ + 'Equality for nested lists', + 'list(1, list(2, 3))', + 'pair(1, pair(pair(2, pair(3, null)), null))', + true + ], + ['Inequality for different lists 1', `list(1, 2)`, 'pair(1, 2)', false], + ['Inequality for different lists 2', 'list(1, 2, 3)', 'list(1, list(2, 3))', false] + ], + async ([value0, value1, expected]) => { + return testCodeSnippet(`equal(${value0}, ${value1});`, expected, Chapter.SOURCE_2) + } + ) +}) + +describe('Test matching with JS', () => { + function evalWithBuiltins(code: string, testBuiltins: TestBuiltins) { + // Ugly, but if you know how to `eval` code with some builtins attached, please change this. + let evalstring = '' + for (const key in testBuiltins) { + if (testBuiltins.hasOwnProperty(key)) { + evalstring = evalstring + 'const ' + key + ' = testBuiltins.' + key + '; ' + } + } + // tslint:disable-next-line:no-eval + return eval(evalstring + code) + } + + const toString = (x: Value) => '' + x + + testMultipleCases<[string] | [string, TestBuiltins]>( + [ + [ + 'Primitives toString matches JS', + ` + toString(true) + + toString(false) + + toString(1) + + toString(1.5) + + toString(null) + + toString(undefined) + + toString(NaN); + `, + { toString } + ], + ['Objects toString matches their JS', 'toString({a: 1});', { toString }], + ['Arrays toString matches their JS', 'toString([1, 2]);', { toString }], + + ['Test true conditional expression', 'true ? true : false;'], + ['Test false conditional expression', 'false ? true : false;'], + ['Test && shortcircuiting', 'false && 1();'], + ['Test || shortcircuiting', 'true || 1();'] + ], + async ([code, builtins]) => { + const expected = evalWithBuiltins(code, builtins ?? {}) + return testCodeSnippet(code, expected, Chapter.LIBRARY_PARSER) + } + ) + + const expressionCases: string[] = [ + 'false && true', + 'false && false', + 'true && false', + 'true && true', + 'false || true', + 'false || false', + 'true || false', + 'true || true' + ] + + testMultipleCases( + expressionCases.map(code => [`Test ${code}`, `${code};`] as [string, string]), + ([code]) => { + const expected = evalWithBuiltins(code, {}) + return testCodeSnippet(code, expected, Chapter.LIBRARY_PARSER) + } + ) +}) + +describe('Test recursion snippets', () => { + testMultipleCases( + [ + [ + 'Simple arrow function infinite recursion represents CallExpression well', + '(x => x(x)(x))(x => x(x)(x));', + stripIndent` + Line 1: Maximum call stack size exceeded + x(x => x(x)(x)).. x(x => x(x)(x)).. x(x => x(x)(x))..` + ], + [ + 'Arrow function infinite recursion with list args represents CallExpression well', + ` + const f = xs => append(f(xs), list()); + f(list(1, 2)); + `, + stripIndent` + Line 2: Maximum call stack size exceeded + f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]])..` + ], + [ + 'Arrow function infinite recursion with different args represents CallExpression well', + ` + const f = i => f(i+1) - 1; + f(0); + `, + expect.stringMatching( + /^Line 2: Maximum call stack size exceeded\n\ *(f\(\d*\)[^f]{2,4}){3}/ + ) + ], + [ + 'Simple function infinite recursion represents CallExpression well', + 'function f(x) { return x(x)(x); } f(f);', + stripIndent` + Line 1: Maximum call stack size exceeded + x(function f(x) { + return x(x)(x); + }).. x(function f(x) { + return x(x)(x); + }).. x(function f(x) { + return x(x)(x); + })..` + ], + [ + 'Function infinite recursion with list args represents CallExpression well', + ` + function f(xs) { return append(f(xs), list()); } + f(list(1, 2)); + `, + stripIndent` + Line 2: Maximum call stack size exceeded + f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]])..` + ], + [ + 'Function infinite recursion with different args represents CallExpression well', + ` + function f(i) { return f(i+1) - 1; } + f(0); + `, + expect.stringMatching( + /^Line 2: Maximum call stack size exceeded\n\ *(f\(\d*\)[^f]{2,4}){3}/ + ) + ] + ], + async ([code, expected]) => { + const context = mockContext(Chapter.SOURCE_2) + const result = await runInContext(code, context, { executionMethod: 'cse-machine' }) + expect(result.status).toEqual('error') + + const parsed = parseError(context.errors) + expect(parsed).toEqual(expected) + }, + false, + 30000 + ) +}) diff --git a/src/__tests__/display.ts b/src/__tests__/display.ts index a3d053abf..9c54ffd6d 100644 --- a/src/__tests__/display.ts +++ b/src/__tests__/display.ts @@ -1,5 +1,77 @@ +import { runInContext } from '..' import { Chapter } from '../types' -import { expectDisplayResult, expectParsedError } from '../utils/testing' +import { stripIndent } from '../utils/formatters' +import { createTestContext, expectFinishedResult, expectParsedError } from '../utils/testing' +import { testMultipleCases } from '../utils/testing/testers' + +testMultipleCases<[string, ...string[]]>( + [ + [ + 'display second argument can be a string', + `display(31072020, "my_first_String");`, + 'my_first_String 31072020' + ], + ['display can be used to display numbers', 'display(0);', '0'], + [ + 'display can be used to display funny numbers', + 'display(1e38); display(NaN); display(Infinity);', + '1e+38', + 'NaN', + 'Infinity' + ], + [ + 'display can be used to display (escaped) strings', + `display("Tom's assistant said: \\"tuna.\\"");`, + '"Tom\'s assistant said: \\"tuna.\\""' + ], + [ + 'raw_display can be used to displayed (unescaped) strings directly', + `raw_display("Tom's assisstant said: \\"tuna.\\"");`, + 'Tom\'s assisstant said: "tuna."' + ], + [ + 'display can be used to display arrow functions', + `display(x => x); display((x, y) => x + y);`, + 'x => x', + '(x, y) => x + y' + ], + [ + 'display can be used to display functions', + ` + function f() { return 0; } + display(f); + `, + stripIndent` + function f() { + return 0; + } + ` + ], + [ + 'displaying builtins hides their implementation', + 'display(pair);', + stripIndent` + function pair(left, right) { + [implementation hidden] + } + ` + ], + ['display can be used with lists', 'display(list(1, 2));', '[1, [2, null]]'], + ['display can be used with arrays', 'display([1, 2, [4, 5]]);', '[1, 2, [4, 5]]'], + [ + 'display can be used with objects', + `display({a: 1, b: 2, c: {d: 3}});`, + '{"a": 1, "b": 2, "c": {"d": 3}}' + ] + ], + async ([code, ...expected]) => { + const context = createTestContext({ chapter: Chapter.LIBRARY_PARSER }) + const result = await runInContext(code, context) + + expectFinishedResult(result) + expect(context.displayResult).toEqual(expected) + } +) test('display throw error if second argument is non-string when used', () => { return expectParsedError(`display(31072020, 0xDEADC0DE);`).toMatchInlineSnapshot( @@ -7,91 +79,6 @@ test('display throw error if second argument is non-string when used', () => { ) }) -test('display second argument can be a string', () => { - return expectDisplayResult(`display(31072020, "my_first_String");`, { native: true }) - .toMatchInlineSnapshot(` - Array [ - "my_first_String 31072020", - ] - `) -}) - -test('display can be used to display numbers', () => { - return expectDisplayResult(`display(0);`, { native: true }).toMatchInlineSnapshot(` -Array [ - "0", -] -`) -}) - -test('display can be used to display funny numbers', () => { - return expectDisplayResult(`display(1e38); display(NaN); display(Infinity);`, { native: true }) - .toMatchInlineSnapshot(` -Array [ - "1e+38", - "NaN", - "Infinity", -] -`) -}) - -test('display can be used to display (escaped) strings', () => { - return expectDisplayResult(`display("Tom's assisstant said: \\"tuna.\\"");`, { native: true }) - .toMatchInlineSnapshot(` -Array [ - "\\"Tom's assisstant said: \\\\\\"tuna.\\\\\\"\\"", -] -`) -}) - -test('raw_display can be used to display (unescaped) strings directly', () => { - return expectDisplayResult(`raw_display("Tom's assisstant said: \\"tuna.\\"");`, { native: true }) - .toMatchInlineSnapshot(` -Array [ - "Tom's assisstant said: \\"tuna.\\"", -] -`) -}) - -test('display can be used to display functions', () => { - return expectDisplayResult(`display(x => x); display((x, y) => x + y);`).toMatchInlineSnapshot(` -Array [ - "x => x", - "(x, y) => x + y", -] -`) -}) - -test('display can be used to display lists', () => { - return expectDisplayResult(`display(list(1, 2));`, { chapter: Chapter.SOURCE_2, native: true }) - .toMatchInlineSnapshot(` -Array [ - "[1, [2, null]]", -] -`) -}) - -test('display can be used to display arrays', () => { - return expectDisplayResult(`display([1, 2, [4, 5]]);`, { - chapter: Chapter.SOURCE_3, - native: true - }).toMatchInlineSnapshot(` -Array [ - "[1, 2, [4, 5]]", -] -`) -}) - -test('display can be used to display objects', () => { - return expectDisplayResult(`display({a: 1, b: 2, c: {d: 3}});`, { - chapter: Chapter.LIBRARY_PARSER - }).toMatchInlineSnapshot(` -Array [ - "{\\"a\\": 1, \\"b\\": 2, \\"c\\": {\\"d\\": 3}}", -] -`) -}) - test('display with no arguments throws an error', () => { return expectParsedError(`display();`, { chapter: Chapter.LIBRARY_PARSER }).toMatchInlineSnapshot( `"Line 1: Expected 1 or more arguments, but got 0."` diff --git a/src/__tests__/environment.ts b/src/__tests__/environment.ts index b979f0665..bb4f3ab83 100644 --- a/src/__tests__/environment.ts +++ b/src/__tests__/environment.ts @@ -18,15 +18,15 @@ test('Function params and body identifiers are in different environment', () => const context = mockContext(Chapter.SOURCE_4) context.prelude = null // hide the unneeded prelude const parsed = parse(code, context) - const it = evaluate(parsed as any as Program, context, { - envSteps: -1, + evaluate(parsed as any as Program, context, { + envSteps: 13, stepLimit: 1000, isPrelude: false }) - const stepsToComment = 13 // manually counted magic number - for (let i = 0; i < stepsToComment; i += 1) { - it.next() - } + // const stepsToComment = 13 // manually counted magic number + // for (let i = 0; i < stepsToComment; i += 1) { + // it.next() + // } context.runtime.environments.forEach(environment => { expect(environment).toMatchSnapshot() }) diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts index 98a5d2135..f683bb05d 100644 --- a/src/__tests__/index.ts +++ b/src/__tests__/index.ts @@ -1,89 +1,12 @@ -import type { Position } from 'acorn/dist/acorn' -import type { SourceLocation } from 'estree' - -import { findDeclaration, getScope } from '../index' import { Chapter, type Value } from '../types' -import { stripIndent } from '../utils/formatters' -import { - createTestContext, - expectParsedError, - expectParsedErrorNoErrorSnapshot, - expectParsedErrorNoSnapshot, - expectResult -} from '../utils/testing' -import { type TestOptions, testSuccess, snapshot } from '../utils/testing' -import type { TestBuiltins } from '../utils/testing' +import { expectResult } from '../utils/testing' const toString = (x: Value) => '' + x -function expectToMatchJS(code: string, options: TestOptions = {}) { - return testSuccess(code, options) - .then(snapshot('expect to match JS')) - .then(testResult => - expect(testResult.result).toEqual(evalWithBuiltins(code, options.testBuiltins)) - ) -} - -function expectToLooselyMatchJS(code: string, options: TestOptions = {}) { - return testSuccess(code, options) - .then(snapshot('expect to loosely match JS')) - .then(testResult => - expect(testResult.result.replace(/ /g, '')).toEqual( - evalWithBuiltins(code, options.testBuiltins).replace(/ /g, '') - ) - ) -} - -function evalWithBuiltins(code: string, testBuiltins: TestBuiltins = {}) { - // Ugly, but if you know how to `eval` code with some builtins attached, please change this. - let evalstring = '' - for (const key in testBuiltins) { - if (testBuiltins.hasOwnProperty(key)) { - evalstring = evalstring + 'const ' + key + ' = testBuiltins.' + key + '; ' - } - } - // tslint:disable-next-line:no-eval - return eval(evalstring + code) -} - -test('Empty code returns undefined', () => { - return expectResult('').toBe(undefined) -}) - -test('Single string self-evaluates to itself', () => { - return expectResult("'42';").toBe('42') -}) - -test('Multiline string self-evaluates to itself', () => { - return expectResult('`1\n1`;').toBe(`1 -1`) -}) - -test('Allow display to return value it is displaying', () => { - return expectResult('25*(display(1+1));').toBe(50) -}) - -test('Single number self-evaluates to itself', () => { - return expectResult('42;').toBe(42) -}) - -test('Single boolean self-evaluates to itself', () => { - return expectResult('true;').toBe(true) -}) - test('Arrow function definition returns itself', () => { return expectResult('() => 42;').toMatchInlineSnapshot(`[Function]`) }) -test('Builtins hide their implementation when stringify', () => { - return expectResult('stringify(pair);', { chapter: Chapter.SOURCE_2, native: true }) - .toMatchInlineSnapshot(` - "function pair(left, right) { - [implementation hidden] - }" - `) -}) - test('Builtins hide their implementation when toString', () => { return expectResult('toString(pair);', { chapter: Chapter.SOURCE_2, @@ -95,861 +18,3 @@ test('Builtins hide their implementation when toString', () => { }" `) }) - -test('Objects toString matches up with JS', () => { - return expectToMatchJS('toString({a: 1});', { - chapter: Chapter.LIBRARY_PARSER, - native: true, - testBuiltins: { toString } - }) -}) - -test('Arrays toString matches up with JS', () => { - return expectToMatchJS('toString([1, 2]);', { - chapter: Chapter.SOURCE_3, - native: true, - testBuiltins: { toString } - }) -}) - -test('functions toString (mostly) matches up with JS', () => { - return expectToLooselyMatchJS( - stripIndent` - function f(x) { - return 5; - } - toString(a=>a) + toString(f); - `, - { native: true, testBuiltins: { toString } } - ) -}) - -test('primitives toString matches up with JS', () => { - return expectToMatchJS( - stripIndent` - toString(true) + - toString(false) + - toString(1) + - toString(1.5) + - toString(null) + - toString(undefined) + - toString(NaN); - `, - { chapter: Chapter.SOURCE_2, native: true, testBuiltins: { toString } } - ) -}) - -test('Factorial arrow function', () => { - return expectResult( - stripIndent` - const fac = (i) => i === 1 ? 1 : i * fac(i-1); - fac(5); - `, - { native: true } - ).toBe(120) -}) - -test('parseError for missing semicolon', () => { - return expectParsedError('42').toMatchInlineSnapshot( - `"Line 1: Missing semicolon at the end of statement"` - ) -}) - -test('parseError for template literals with expressions', () => { - return expectParsedError('`${1}`;').toMatchInlineSnapshot( - `"Line 1: Expressions are not allowed in template literals (\`multiline strings\`)"` - ) -}) - -test('Simple arrow function infinite recursion represents CallExpression well', () => { - return expectParsedErrorNoErrorSnapshot('(x => x(x)(x))(x => x(x)(x));').toMatchInlineSnapshot(` - "Line 1: Maximum call stack size exceeded - x(x => x(x)(x)).. x(x => x(x)(x)).. x(x => x(x)(x)).." - `) -}, 30000) - -test('Simple function infinite recursion represents CallExpression well', () => { - return expectParsedErrorNoErrorSnapshot('function f(x) {return x(x)(x);} f(f);') - .toMatchInlineSnapshot(` - "Line 1: Maximum call stack size exceeded - x(function f(x) { - return x(x)(x); - }).. x(function f(x) { - return x(x)(x); - }).. x(function f(x) { - return x(x)(x); - }).." - `) -}, 30000) - -test('Cannot overwrite consts even when assignment is allowed', () => { - return expectParsedError( - stripIndent` - function test(){ - const constant = 3; - constant = 4; - return constant; - } - test(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 3: Cannot assign new value to constant constant."`) -}) - -test('Assignment has value', () => { - return expectResult( - stripIndent` - let a = 1; - let b = a = 4; - b === 4 && a === 4; - `, - - { chapter: Chapter.SOURCE_3, native: true } - ).toBe(true) -}) - -test('Array assignment has value', () => { - return expectResult( - stripIndent` - let arr = []; - const a = arr[0] = 1; - const b = arr[1] = arr[2] = 4; - arr[0] === 1 && arr[1] === 4 && arr[2] === 4; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toBe(true) -}) - -test('Can overwrite lets when assignment is allowed', () => { - return expectResult( - stripIndent` - function test() { - let variable = false; - variable = true; - return variable; - } - test(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toBe(true) -}) - -test('Arrow function infinite recursion with list args represents CallExpression well', () => { - return expectParsedErrorNoErrorSnapshot( - stripIndent` - const f = xs => append(f(xs), list()); - f(list(1, 2)); - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - "Line 1: Maximum call stack size exceeded - f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]]).." - `) -}, 30000) - -test('Function infinite recursion with list args represents CallExpression well', () => { - return expectParsedErrorNoErrorSnapshot( - stripIndent` - function f(xs) { return append(f(xs), list()); } - f(list(1, 2)); - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - "Line 1: Maximum call stack size exceeded - f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]]).." - `) -}, 30000) - -test('Arrow function infinite recursion with different args represents CallExpression well', () => { - return expectParsedErrorNoSnapshot(stripIndent` - const f = i => f(i+1) - 1; - f(0); - `).toEqual( - expect.stringMatching(/^Line 1: Maximum call stack size exceeded\n\ *(f\(\d*\)[^f]{2,4}){3}/) - ) -}, 30000) - -test('Function infinite recursion with different args represents CallExpression well', () => { - return expectParsedErrorNoSnapshot(stripIndent` - function f(i) { return f(i+1) - 1; } - f(0); - `).toEqual( - expect.stringMatching(/^Line 1: Maximum call stack size exceeded\n\ *(f\(\d*\)[^f]{2,4}){3}/) - ) -}, 30000) - -test('Functions passed into non-source functions remain equal', () => { - return expectResult( - stripIndent` - function t(x, y, z) { - return x + y + z; - } - identity(t) === t && t(1, 2, 3) === 6; - `, - { chapter: Chapter.SOURCE_3, testBuiltins: { 'identity(x)': (x: any) => x }, native: true } - ).toBe(true) -}) - -test('Accessing array with nonexistent index returns undefined', () => { - return expectResult( - stripIndent` - const a = []; - a[1]; - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toBe(undefined) -}) - -test('Accessing object with nonexistent property returns undefined', () => { - return expectResult( - stripIndent` - const o = {}; - o.nonexistent; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toBe(undefined) -}) - -test('Simple object assignment and retrieval', () => { - return expectResult( - stripIndent` - const o = {}; - o.a = 1; - o.a; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toBe(1) -}) - -test('Deep object assignment and retrieval', () => { - return expectResult( - stripIndent` - const o = {}; - o.a = {}; - o.a.b = {}; - o.a.b.c = "string"; - o.a.b.c; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toBe('string') -}) - -test('Test apply_in_underlying_javascript', () => { - return expectResult( - stripIndent` - apply_in_underlying_javascript((a, b, c) => a * b * c, list(2, 5, 6)); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toBe(60) -}) - -test('Test equal for primitives', () => { - return expectResult( - stripIndent` - equal(1, 1) && equal("str", "str") && equal(null, null) && !equal(1, 2) && !equal("str", ""); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toBe(true) -}) - -test('Test equal for lists', () => { - return expectResult( - stripIndent` - equal(list(1, 2), pair(1, pair(2, null))) && equal(list(1, 2, 3, 4), list(1, 2, 3, 4)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toBe(true) -}) - -test('Test equal for different lists', () => { - return expectResult( - stripIndent` - !equal(list(1, 2), pair(1, 2)) && !equal(list(1, 2, 3), list(1, list(2, 3))); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toBe(true) -}) - -test('true if with empty if works', () => { - return expectResult( - stripIndent` - if (true) { - } else { - } - `, - { native: true } - ).toBe(undefined) -}) - -test('true if with nonempty if works', () => { - return expectResult( - stripIndent` - if (true) { - 1; - } else { - } - `, - { native: true } - ).toBe(1) -}) - -test('false if with empty else works', () => { - return expectResult( - stripIndent` - if (false) { - } else { - } - `, - { native: true } - ).toBe(undefined) -}) - -test('false if with nonempty if works', () => { - return expectResult( - stripIndent` - if (false) { - } else { - 2; - } - `, - { native: true } - ).toBe(2) -}) - -test('test true conditional expression', () => { - return expectToMatchJS('true ? true : false;', { native: true }) -}) - -test('test false conditional expression', () => { - return expectToMatchJS('false ? true : false;', { native: true }) -}) - -test('test false && true', () => { - return expectToMatchJS('false && true;', { native: true }) -}) - -test('test false && false', () => { - return expectToMatchJS('false && false;', { native: true }) -}) - -test('test true && false', () => { - return expectToMatchJS('true && false;', { native: true }) -}) - -test('test true && true', () => { - return expectToMatchJS('true && true;', { native: true }) -}) - -test('test && shortcircuiting', () => { - return expectToMatchJS('false && 1();', { native: true }) -}) - -test('test false || true', () => { - return expectToMatchJS('false || true;', { native: true }) -}) - -test('test false || false', () => { - return expectToMatchJS('false || false;', { native: true }) -}) - -test('test true || false', () => { - return expectToMatchJS('true || false;', { native: true }) -}) - -test('test true || true', () => { - return expectToMatchJS('true || true;', { native: true }) -}) - -test('test || shortcircuiting', () => { - return expectToMatchJS('true || 1();', { native: true }) -}) - -test('Rest parameters work', () => { - return expectResult( - stripIndent` - function rest(a, b, ...c) { - let sum = a + b; - for (let i = 0; i < array_length(c); i = i + 1) { - sum = sum + c[i]; - } - return sum; - } - rest(1, 2); // no error - rest(1, 2, ...[3, 4, 5], ...[6, 7], ...[]); - `, - { native: true, chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(`28`) -}) - -test('Test context reuse', async () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const init = stripIndent` - let i = 0; - function f() { - i = i + 1; - return i; - } - i; - ` - await expectResult(init, { context, native: true }).toBe(0) - await expectResult('i = 100; f();', { context, native: true }).toBe(101) - await expectResult('f(); i;', { context, native: true }).toBe(102) - return expectResult('i;', { context, native: true }).toBe(102) -}) - -class SourceLocationTestResult { - start: Position - end: Position - constructor(startLine: number, startCol: number, endLine: number, endCol: number) { - this.start = { line: startLine, column: startCol } - this.end = { line: endLine, column: endCol } - } -} - -function expectResultsToMatch( - actualResult: SourceLocation | null | undefined, - expectedResult: SourceLocationTestResult | null | undefined -) { - if (expectedResult === null) { - expect(actualResult).toBeNull() - return - } - if (expectedResult === undefined) { - expect(actualResult).toBeUndefined() - return - } - expect(actualResult).not.toBeNull() - expect(actualResult).not.toBeUndefined() - if (actualResult === null || actualResult === undefined) { - return - } - expect(actualResult.start.line).toEqual(expectedResult.start.line) - expect(actualResult.start.column).toEqual(expectedResult.start.column) - expect(actualResult.end.line).toEqual(expectedResult.end.line) - expect(actualResult.end.column).toEqual(expectedResult.end.column) -} - -test('Find variable declaration in global scope', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - let i = 0; - function f() { - i = i + 1; - return i; - } - i; - ` - const expected = new SourceLocationTestResult(1, 4, 1, 5) - const actual = findDeclaration(code, context, { line: 6, column: 0 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find variable declaration in global scope from occurrence in function scope', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - let i = 0; - function f() { - i = i + 1; - return i; - } - i; - ` - const expected = new SourceLocationTestResult(1, 4, 1, 5) - const actual = findDeclaration(code, context, { line: 4, column: 9 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find variable declaration in function scope from occurrence in function scope', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - let i = 0; - function f() { - let i = 2; - return i; - } - i; - ` - const expected = new SourceLocationTestResult(3, 6, 3, 7) - const actual = findDeclaration(code, context, { line: 4, column: 9 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find no declaration from occurrence when there is no declaration (syntax error)', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - function f() { - let i = 2; - return i; - } - x; - ` - const expected = null - const actual = findDeclaration(code, context, { line: 5, column: 0 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find no declaration from selection that does not refer to a declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - let i = 0; - function f() { - let i = 2; - return i; - } - i; - ` - const expected = null - const actual = findDeclaration(code, context, { line: 4, column: 3 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find function declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - let i = 0; - function foo() { - let i = 2; - return i; - } - foo(); - ` - const expected = new SourceLocationTestResult(2, 9, 2, 12) - const actual = findDeclaration(code, context, { line: 6, column: 0 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find function param declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - function timesTwo(num) { - return num * 2; - } - timesTwo(2); - ` - const expected = new SourceLocationTestResult(1, 18, 1, 21) - const actual = findDeclaration(code, context, { line: 2, column: 9 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find variable declaration with same name as function param declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - function timesTwo(num) { - return num * 2; - } - const num = 2; - timesTwo(num); - ` - const expected = new SourceLocationTestResult(4, 6, 4, 9) - const actual = findDeclaration(code, context, { line: 5, column: 9 }) - expectResultsToMatch(actual, expected) - // expect(actual).toMatchSnapshot() -}) - -test('Find arrow function declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - let i = 0; - const foo = () => { - let i = 2; - return i; - } - foo(); - ` - const expected = new SourceLocationTestResult(2, 6, 2, 9) - const actual = findDeclaration(code, context, { line: 6, column: 0 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find arrow function param declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - const timesTwo = (num) => { - return num * 2; - } - timesTwo(2); - ` - const expected = new SourceLocationTestResult(1, 18, 1, 21) - const actual = findDeclaration(code, context, { line: 2, column: 9 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find variable declaration with same name as arrow function param declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - const timesTwo = (num) => { - return num * 2; - } - const num = 2; - timesTwo(num); - ` - const expected = new SourceLocationTestResult(4, 6, 4, 9) - const actual = findDeclaration(code, context, { line: 5, column: 9 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find declaration in init of for loop', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - let x = 1; - for (let i = 1; i <= 2; i++) { - x = x * i; - } - x; - ` - const expected = new SourceLocationTestResult(2, 9, 2, 10) - const actual = findDeclaration(code, context, { line: 3, column: 10 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find variable declaration with same name as init of for loop', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - let x = 1; - for (let i = 1; i <= 2; i++) { - x = x * i; - } - const i = 2; - i; - ` - const expected = new SourceLocationTestResult(5, 6, 5, 7) - const actual = findDeclaration(code, context, { line: 6, column: 0 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find variable declaration in block statement', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - { - let x = 1; - x = x + 2; - } - let x = 2; - x = x + 2; - ` - const expected = new SourceLocationTestResult(2, 6, 2, 7) - const actual = findDeclaration(code, context, { line: 3, column: 2 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) -test('Find variable declaration of same name as variable declaration in block statement', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - { - let x = 1; - x = x + 2; - } - let x = 2; - x = x + 2; - ` - const expected = new SourceLocationTestResult(5, 4, 5, 5) - const actual = findDeclaration(code, context, { line: 6, column: 0 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find declaration of of variable in update statement of a for loop', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = stripIndent` - for (let x = 10; x < 12; ++x) { - display(x); - } - let x = 5; - ` - const expected = new SourceLocationTestResult(1, 9, 1, 10) - const actual = findDeclaration(code, context, { line: 1, column: 17 }) - expectResultsToMatch(actual, expected) - expect(actual).toMatchSnapshot() -}) - -test('Find scope of a variable declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = `{ - const x = 1; - { - const x = 2; - function f(y) { - return x + y; - } - } - display(x); - }` - const expected = [ - new SourceLocationTestResult(1, 0, 3, 4), - new SourceLocationTestResult(8, 5, 10, 3) - ] - const actual = getScope(code, context, { line: 2, column: 10 }) - expected.forEach((expectedRange, index) => { - const actualRange = new SourceLocationTestResult( - actual[index].start.line, - actual[index].start.column, - actual[index].end.line, - actual[index].end.column - ) - expectResultsToMatch(actualRange, expectedRange) - }) - expect(actual).toMatchSnapshot() -}) - -test('Find scope of a nested variable declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = `{ - const x = 1; - { - const x = 2; - function f(y) { - return x + y; - } - } - display(x); - }` - const expected = [new SourceLocationTestResult(3, 4, 8, 5)] - const actual = getScope(code, context, { line: 4, column: 15 }) - expected.forEach((expectedRange, index) => { - const actualRange = new SourceLocationTestResult( - actual[index].start.line, - actual[index].start.column, - actual[index].end.line, - actual[index].end.column - ) - expectResultsToMatch(actualRange, expectedRange) - }) - expect(actual).toMatchSnapshot() -}) - -test('Find scope of a function parameter', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = `{ - const x = 1; - { - const x = 2; - function f(y) { - return x + y; - } - } - display(x); - }` - const expected = [new SourceLocationTestResult(5, 22, 7, 9)] - const actual = getScope(code, context, { line: 5, column: 19 }) - expected.forEach((expectedRange, index) => { - const actualRange = new SourceLocationTestResult( - actual[index].start.line, - actual[index].start.column, - actual[index].end.line, - actual[index].end.column - ) - expectResultsToMatch(actualRange, expectedRange) - }) - expect(actual).toMatchSnapshot() -}) - -test('Find scope of a function declaration', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = `{ - const x = 1; - { - const x = 2; - function f(y) { - return x + y; - } - } - display(x); - }` - const expected = [new SourceLocationTestResult(3, 4, 8, 5)] - const actual = getScope(code, context, { line: 5, column: 17 }) - expected.forEach((expectedRange, index) => { - const actualRange = new SourceLocationTestResult( - actual[index].start.line, - actual[index].start.column, - actual[index].end.line, - actual[index].end.column - ) - expectResultsToMatch(actualRange, expectedRange) - }) - expect(actual).toMatchSnapshot() -}) - -test('Find scope of a variable declaration with more nesting', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = `{ - const x = 1; - { - const x = 2; - function f(y) { - for (let x = 10; x < 20; x = x + 1) { - display(x); - } - return x + y; - } - for (let x = 10; x < 20; x = x + 1) { - display(x); - } - } - display(x); - }` - const expected = [ - new SourceLocationTestResult(3, 4, 6, 12), - new SourceLocationTestResult(8, 13, 11, 8), - new SourceLocationTestResult(13, 9, 14, 5) - ] - const actual = getScope(code, context, { line: 4, column: 15 }) - expected.forEach((expectedRange, index) => { - const actualRange = new SourceLocationTestResult( - actual[index].start.line, - actual[index].start.column, - actual[index].end.line, - actual[index].end.column - ) - expectResultsToMatch(actualRange, expectedRange) - }) - expect(actual).toMatchSnapshot() -}) - -test('Find scope of a variable declaration with multiple blocks', () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const code = `const x = 1; - { - const x = 2; - { - const x = 3; - } - x ; - { - const x = 4; - } - x; - { - const x = 5; - } - } - x;` - const expected = [ - new SourceLocationTestResult(2, 4, 4, 8), - new SourceLocationTestResult(6, 9, 8, 8), - new SourceLocationTestResult(10, 9, 12, 8), - new SourceLocationTestResult(14, 9, 15, 5) - ] - const actual = getScope(code, context, { line: 3, column: 15 }) - expected.forEach((expectedRange, index) => { - const actualRange = new SourceLocationTestResult( - actual[index].start.line, - actual[index].start.column, - actual[index].end.line, - actual[index].end.column - ) - expectResultsToMatch(actualRange, expectedRange) - }) - expect(actual).toMatchSnapshot() -}) diff --git a/src/__tests__/lazy.ts b/src/__tests__/lazy.ts deleted file mode 100644 index 2b34840f4..000000000 --- a/src/__tests__/lazy.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Chapter, Variant } from '../types' -import { stripIndent } from '../utils/formatters' -import { expectResult } from '../utils/testing' - -test('Unused arguments are not evaluated', () => { - return expectResult( - stripIndent` - function test(a, b, c, d, e, f) { - return a; - } - const res = test(1, head(null), 1 + '', !1, '' - 1, head(head(null))); - res; - `, - { - variant: Variant.LAZY, - chapter: Chapter.SOURCE_2, - native: true - } - ).toBe(1) -}) - -test('Unary operations force argument', () => { - return expectResult( - stripIndent` - function neg(b) { - return !b; - } - const res = neg(((x) => x)(false)); - res; - `, - { - variant: Variant.LAZY, - native: true - } - ).toBe(true) -}) - -test('Binary operations force arguments', () => { - return expectResult( - stripIndent` - function add(x, y) { - return x + y; - } - const res = add(((x) => x)(5), ((x) => x + 1)(9)); - res; - `, - { variant: Variant.LAZY, native: true } - ).toBe(15) -}) - -test('Conditionals force test', () => { - return expectResult( - stripIndent` - function f(a, b) { - return (a ? true : head(null)) && (!b ? true : head(null)); - } - - const res = f(((b) => b)(true), ((b) => !b)(true)); - res; - `, - { variant: Variant.LAZY, chapter: Chapter.SOURCE_2, native: true } - ).toBe(true) -}) - -test('Thunks are memoized', () => { - return expectResult( - stripIndent` - let x = 1; - - function incX() { - x = x + 1; - return x; - } - - function square(n) { - return n * n; - } - - const res = square(incX()); - res; - `, - { variant: Variant.LAZY, chapter: Chapter.SOURCE_3, native: true } - ).toBe(4) -}) - -test('Thunks capture local environment', () => { - return expectResult( - stripIndent` - function addSome(x) { - const y = x + 1; - return z => y + z; - } - - const addSome2 = addSome(2); - - const res = addSome2(3); - res; - `, - { variant: Variant.LAZY, native: true } - ).toBe(6) -}) - -test('Tail calls work', () => { - return expectResult( - stripIndent` - function test(a, b) { - return a === 1 ? a : b; - } - - function test2(a) { - return test(a, head(null)); - } - - const res = test2(1); - res; - `, - { variant: Variant.LAZY, chapter: Chapter.SOURCE_2, native: true } - ).toBe(1) -}) diff --git a/src/__tests__/non-det-interpreter.ts b/src/__tests__/non-det-interpreter.ts deleted file mode 100644 index 8f35874cd..000000000 --- a/src/__tests__/non-det-interpreter.ts +++ /dev/null @@ -1,1044 +0,0 @@ -/* tslint:disable:max-line-length */ -import { IOptions, parseError, Result, resume, runInContext } from '../index' -import { mockContext } from '../mocks/context' -import { Context, Finished, SuspendedNonDet, Variant } from '../types' - -test('Empty code returns undefined', async () => { - await testDeterministicCode('', undefined) -}) - -test('Unary operations', async () => { - await testDeterministicCode('-12 - 8;', -20) - await testDeterministicCode('!true;', false) - await testDeterministicCode('!(false);', true) -}) - -test('Unary operations with non deterministic terms', async () => { - await testNonDeterministicCode('-amb(12, 24) - 8;', [-20, -32]) - await testNonDeterministicCode('!amb(true, false);', [false, true]) -}) - -test('Unary operations on the wrong type should cause error', async () => { - await testDeterministicCode('!100;', 'Line 1: Expected boolean, got number.', true) -}) - -test('Binary operations', async () => { - await testDeterministicCode('1 + 4 - 10 * 5;', -45) - await testDeterministicCode('"hello" + " world" + "!";', 'hello world!') - await testDeterministicCode('(23 % 3) * (10 / 2);', 10) -}) - -test('Binary operations with non deterministic terms', async () => { - await testNonDeterministicCode('1 + amb(4) - 10 * 5;', [-45]) - await testNonDeterministicCode('amb("hello", "bye") + " world" + "!";', [ - 'hello world!', - 'bye world!' - ]) - await testNonDeterministicCode('amb((23 % 3), 7) * amb((10 / 2), 19 - 5);', [10, 28, 35, 98]) -}) - -test('Binary operations on the wrong types should cause error', async () => { - await testDeterministicCode( - 'false + 4;', - 'Line 1: Expected string or number on left hand side of operation, got boolean.', - true - ) -}) - -test('Assignment', async () => { - await testDeterministicCode('let a = 5; a = 10; a;', 10) -}) - -test('Assignment with non deterministic terms', async () => { - await testNonDeterministicCode('let a = amb(1, 2); a = amb(4, 5); a;', [4, 5, 4, 5]) - - await testNonDeterministicCode( - `let num = 5; - function reassign_num() { num = 10; return num; } - amb(reassign_num(), num);`, - [10, 5] - ) -}) - -test('Re-assignment to constant should cause error', async () => { - await testDeterministicCode( - `const f = 10; { f = 20; }`, - 'Line 1: Cannot assign new value to constant f.', - true - ) -}) - -test('Accessing un-declared variable should cause error', async () => { - await testDeterministicCode(`let g = 100; f;`, 'Name f not declared.', true) -}) - -test('If-else and conditional expressions with non deterministic terms', async () => { - await testNonDeterministicCode('amb(false, true) ? 4 - 10 : 6;', [6, -6]) - await testNonDeterministicCode( - `if (amb(true, false)) { - -100; - } else { - 200 / 2; - 210; - }`, - [-100, 210] - ) - await testNonDeterministicCode( - `if (amb(100 * 2 === 2, 40 % 2 === 0)) { - amb(false, 'test' === 'test') ? amb(false === false, false) ? "hello" : false : amb(5, "world"); - } else { - 9 * 10 / 5; - }`, - [18, 5, 'world', 'hello', false] - ) -}) - -test('Conditional expression with non boolean predicate should cause error', async () => { - await testDeterministicCode( - '100 ? 5 : 5;', - 'Line 1: Expected boolean as condition, got number.', - true - ) -}) - -test('Logical expressions', async () => { - await testDeterministicCode(`true && (false || true) && (true && false);`, false) - - await testDeterministicCode( - `function foo() { return foo(); }\ - true || foo();`, - true - ) - - await testDeterministicCode( - `function foo() { return foo(); }\ - false && foo();`, - false - ) -}) - -test('Logical expressions with non deterministic terms', async () => { - await testNonDeterministicCode( - `amb(true, false) && amb(false, true) || amb(false); - `, - [false, true, false] - ) -}) - -test('Function applications', async () => { - await testDeterministicCode( - `function f() {} f(); - `, - undefined - ) - - await testDeterministicCode( - `function factorial(n) { - return n === 0 ? 1 : n * factorial(n - 1); - } - factorial(5); - `, - 120 - ) - - await testDeterministicCode( - 'function f(x) { function subfunction(y) { return y * 2; } return x * subfunction(10); } f(6);', - 120 - ) - - await testDeterministicCode( - `function noReturnStatement_returnsUndefined() { - 20 + 40 - 6; - 5 - 5; - list(); - reverse(list(1)); - }`, - undefined - ) - - await testDeterministicCode(`const a = 2; a();`, 'Line 1: Calling non-function value 2.', true) - - await testDeterministicCode( - `(function() {})();`, - 'Line 1: Function expressions are not allowed', - true - ) - - await testDeterministicCode( - `function ignoreStatementsAfterReturn(n) { - return n; return n * 2; - } - ignoreStatementsAfterReturn(5); - `, - 5 - ) -}) - -test('Applying functions with wrong number of arguments should cause error', async () => { - await testDeterministicCode( - `function foo(a, b) { - return a + b; - } - foo(1); - `, - `Line 4: Expected 2 arguments, but got 1.`, - true - ) -}) - -test('Builtin list functions', async () => { - await testDeterministicCode('pair(false, 10);', [false, 10]) - await testDeterministicCode('list();', null) - await testDeterministicCode('list(1);', [1, null]) - await testDeterministicCode('head(list(1));', 1) - await testDeterministicCode('tail(list(1));', null) -}) - -test('Builtin list functions with non deterministic terms', async () => { - await testNonDeterministicCode('pair(amb(false, true), 10);', [ - [false, 10], - [true, 10] - ]) - await testNonDeterministicCode('list(amb());', []) - await testNonDeterministicCode('list(amb(1,2));', [ - [1, null], - [2, null] - ]) - await testNonDeterministicCode('head(amb(list(100), list(20, 30)));', [100, 20]) -}) - -test('Prelude list functions', async () => { - await testDeterministicCode('is_null(null);', true) - await testDeterministicCode('is_null(list(null));', false) - await testDeterministicCode( - `function increment(n) { return n + 1; } - map(increment, list(100, 101, 200)); - `, - [101, [102, [201, null]]] - ) - await testDeterministicCode('append(list(5), list(6,20));', [5, [6, [20, null]]]) - await testDeterministicCode('append(list(4,5), list());', [4, [5, null]]) - await testDeterministicCode('reverse(list("hello", true, 0));', [0, [true, ['hello', null]]]) -}) - -test('Empty amb application', async () => { - await testNonDeterministicCode('amb();', []) -}) - -test('Simple amb application', async () => { - await testNonDeterministicCode('amb(1, 4 + 5, 3 - 10);', [1, 9, -7]) -}) - -test('Functions with non deterministic terms', async () => { - await testNonDeterministicCode( - `function foo() { - return amb(true, false) ? 'a string' : amb(10, 20); - } - foo();`, - ['a string', 10, 20] - ) -}) - -test('Functions as amb arguments', async () => { - await testNonDeterministicCode( - ' const is_even = num => (num % 2) === 0;\ - const add_five = num => num + 5;\ - const nondet_func = amb(is_even, add_five, num => !is_even(num));\ - nondet_func(5);\ - ', - [false, 10, true] - ) -}) - -test('Combinations of amb', async () => { - await testNonDeterministicCode('list(amb(1, 2, 3), amb("a", "b"));', [ - [1, ['a', null]], - [1, ['b', null]], - [2, ['a', null]], - [2, ['b', null]], - [3, ['a', null]], - [3, ['b', null]] - ]) -}) - -test('Require operator', async () => { - await testNonDeterministicCode( - ' \ - function int_between(low, high) { \ - return low > high ? amb() : amb(low, int_between(low + 1, high)); \ - } \ - let integer = int_between(5, 10);\ - require(integer % 3 === 0); \ - integer;\ - ', - [6, 9] - ) - - await testNonDeterministicCode( - `const f = an_integer_between(1, 10); require(f > 3, true); f;`, - ['Line 1: Expected 1 arguments, but got 2.'], - true - ) -}) - -test('Cut operator', async () => { - await testNonDeterministicCode( - `const f = amb(1, 2, 3); cut(); f + amb(4, 5, 6); - `, - [5, 6, 7] - ) - - await testNonDeterministicCode( - `const f = amb(1, 2, 3); const g = amb(4, 5, 6); cut(); f + g; - `, - [5] - ) -}) - -/* Deterministic block scoping tests taken from block-scoping.ts */ - -test('Block statements', async () => { - await testDeterministicCode( - ` - function test(){ - const x = true; - { - const x = false; - } - return x; - } - test(); - `, - true - ) - - await testDeterministicCode( - ` - function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; - } - return x; - } - test(); - `, - true - ) - - await testDeterministicCode( - ` - const v = f(); - function f() { - return 1; - } - v; - `, - 'Name f declared later in current scope but not yet assigned', - true - ) - - await testDeterministicCode( - ` - const a = 1; - function f() { - display(a); - const a = 5; - } - f(); - `, - 'Name a declared later in current scope but not yet assigned', - true - ) - - await testDeterministicCode( - ` - const a = 1; - { - a + a; - const a = 10; - } - `, - 'Name a declared later in current scope but not yet assigned', - true - ) - - await testDeterministicCode( - ` - let variable = 1; - function test(){ - variable = 100; - let variable = true; - return variable; - } - test(); - `, - 'Name variable not declared.', - true - ) -}) - -test('ambR application', async () => { - await testNonDeterministicCode('ambR();', [], false, true) - - await testNonDeterministicCode('ambR(1, 2, 3, 4, 5);', [1, 2, 3, 4, 5], false, true) - - await testNonDeterministicCode( - 'ambR(ambR(4, 5, 6, 7), ambR(3, 8));', - [4, 5, 6, 7, 3, 8], - false, - true - ) -}) - -test('Deterministic arrays', async () => { - await testDeterministicCode(`[];`, []) - - await testDeterministicCode(`const a = [[1, 2], [3, [4]]]; a;`, [ - [1, 2], - [3, [4]] - ]) - - await testDeterministicCode(`const a = [[[[6]]]]; a[0][0][0][0];`, 6) - - await testDeterministicCode(`const f = () => 2; const a = [1, f(), 3]; a;`, [1, 2, 3]) - - await testDeterministicCode( - `[1, 1, 1][4.4];`, - 'Line 1: Expected array index as prop, got other number.', - true - ) - - await testDeterministicCode( - `[1, 1, 1]["str"] = 2;`, - 'Line 1: Expected array index as prop, got string.', - true - ) - - await testDeterministicCode(`4[0];`, 'Line 1: Expected object or array, got number.', true) -}) - -test('Non-deterministic array values', async () => { - await testNonDeterministicCode(`const a = [amb(1, 2), amb(3, 4)]; a;`, [ - [1, 3], - [1, 4], - [2, 3], - [2, 4] - ]) - - await testNonDeterministicCode(`const a = [1, 2, 3, 4]; a[2] = amb(10, 11, 12); a;`, [ - [1, 2, 10, 4], - [1, 2, 11, 4], - [1, 2, 12, 4] - ]) -}) - -test('Non-deterministic array objects', async () => { - await testNonDeterministicCode( - `const a = [1, 2]; const b = [3, 4]; - amb(a, b)[1] = 99; a; - `, - [ - [1, 99], - [1, 2] - ] - ) - - await testNonDeterministicCode( - `const a = [1, 2]; const b = [3, 4]; - amb(a, b)[1] = 99; b; - `, - [ - [3, 4], - [3, 99] - ] - ) -}) - -test('Non-deterministic array properties', async () => { - await testNonDeterministicCode( - ` - const a = [100, 101, 102, 103]; - a[amb(0, 1, 2, 3)] = 999; a; - `, - [ - [999, 101, 102, 103], - [100, 999, 102, 103], - [100, 101, 999, 103], - [100, 101, 102, 999] - ] - ) -}) - -test('Material Conditional', async () => { - await testDeterministicCode(`implication(true, true);`, true) - await testDeterministicCode(`implication(true, false);`, false) - await testDeterministicCode(`implication(false, true);`, true) - await testDeterministicCode(`implication(false, false);`, true) -}) - -test('Material Biconditional', async () => { - await testDeterministicCode(`bi_implication(true, true);`, true) - await testDeterministicCode(`bi_implication(true, false);`, false) - await testDeterministicCode(`bi_implication(false, true);`, false) - await testDeterministicCode(`bi_implication(false, false);`, true) -}) - -test('While loops', async () => { - await testDeterministicCode( - ` - let i = 2; - while (false) { - i = i - 1; - } - i;`, - 2 - ) - - await testDeterministicCode( - ` - let i = 5; - while (i > 0) { - i = i - 1; - }`, - 0 - ) - - await testDeterministicCode( - ` - let i = 5; - let j = 0; - while (i > 0 && j < 5) { - i = i - 1; - j = j + 2; - }`, - 6 - ) - - await testDeterministicCode( - ` - let i = 2; - while (i) { - i = i - 1; - } - i;`, - 'Line 3: Expected boolean as condition, got number.', - true - ) -}) - -test('Let statement should be block scoped in body of while loop', async () => { - await testDeterministicCode( - ` - let i = 2; - let x = 5; - while (i > 0) { - i = i - 1; - let x = 10; - } - x;`, - 5 - ) - - await testDeterministicCode( - ` - let i = 2; - while (i > 0) { - i = i - 1; - let x = 5; - } - x;`, - 'Name x not declared.', - true - ) -}) - -test('Nested while loops', async () => { - await testDeterministicCode( - ` - let count = 0; - let i = 1; - while (i > 0) { - let j = 2; - while (j > 0) { - let k = 4; - while (k > 0) { - count = count + 1; - k = k - 1; - } - j = j - 1; - } - i = i - 1; - } - count;`, - 8 - ) -}) - -test('Break statement in while loop body', async () => { - await testDeterministicCode( - ` - let i = 5; - while (i > 0) { - i = i - 1; - break; - } - i;`, - 4 - ) -}) - -test('Continue statement in while loop body', async () => { - await testDeterministicCode( - ` - let i = 5; - let j = 0; - while (i > 0 && j < 5) { - i = i - 1; - continue; - j = j + 2; - } - j;`, - 0 - ) -}) - -test('Return statement in while loop body', async () => { - await testDeterministicCode( - ` - function loopTest(i, j) { - while (i > 0 && j > i) { - return i * j; - i = i - 1; - j = j + i; - } - } - loopTest(5, 10);`, - 50 - ) -}) - -test('Non-deterministic while loop condition', async () => { - await testNonDeterministicCode( - ` - let i = amb(3, 4); - let j = 0; - while (i > 0) { - i = i - 1; - j = j + 1; - } - j;`, - [3, 4] - ) - - await testNonDeterministicCode( - ` - let i = 1; - let j = 2; - let count = 0; - while (amb(i, j) > 0) { - i = i - 1; - j = j - 1; - count = count + 1; - } - count;`, - [1, 2, 2, 1, 2, 2] - ) // chosen variables: (i,i), (i,j,i), (i,j,j), (j,i), (j,j,i), (j,j,j) -}) - -test('Non-deterministic while loop body', async () => { - /* number of total program values = - (number of values from cartesian product of the statements in loop body)^ - (number of loop iterations) - */ - - await testNonDeterministicCode( - ` - let i = 3; - let count = 0; - while (i > 0) { - count = count + amb(0, 1); - i = i - 1; - } - count;`, - [0, 1, 1, 2, 1, 2, 2, 3] - ) - - await testNonDeterministicCode( - ` - let i = 2; - let count = 0; - while (i > 0) { - count = count + amb(0, 1); - count = count + amb(0, 1); - i = i - 1; - } - count;`, - [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4] - ) -}) - -test('For loops', async () => { - await testDeterministicCode( - ` - let i = 0; - for (i; i < 0; i = i + 1) { - } - i; - `, - 0 - ) - - await testDeterministicCode( - ` - for (let i = 0; i < 5; i = i + 1) { - i; - } - `, - 4 - ) - - await testDeterministicCode( - ` - let count = 0; - for (let i = 5; i > 0; i = i - 2) { - count = count + 1; - } - count; - `, - 3 - ) - - await testDeterministicCode( - ` - for (let i = 0; 2; i = i + 1) { - } - `, - 'Line 2: Expected boolean as condition, got number.', - true - ) -}) - -test('Let statement should be block scoped in head of for loop', async () => { - await testDeterministicCode( - ` - for (let i = 2; i > 0; i = i - 1) { - } - i; - `, - 'Name i not declared.', - true - ) -}) - -test('Let statement should be block scoped in body of for loop', async () => { - await testDeterministicCode( - ` - let x = 0; - for (x; x < 10; x = x + 1) { - let x = 1; - } - x;`, - 10 - ) - - await testDeterministicCode( - ` - for (let i = 2; i > 0; i = i - 1) { - let x = 5; - } - x; - `, - 'Name x not declared.', - true - ) -}) - -test('Loop control variable should be copied into for loop body', async () => { - await testDeterministicCode( - ` - let arr = []; - for (let i = 0; i < 5; i = i + 1) { - arr[i] = () => i; - } - arr[3]();`, - 3 - ) -}) - -test('Assignment to loop control variable', async () => { - await testDeterministicCode( - ` - for (let i = 0; i < 2; i = i + 1){ - i = i + 1; - } - `, - 'Line 3: Assignment to a for loop variable in the for loop is not allowed.', - true - ) - - await testDeterministicCode( - ` - let i = 0; - for (i; i < 2; i = i + 1){ - i = i + 1; - } - i;`, - 2 - ) -}) - -test('Nested for loops', async () => { - await testDeterministicCode( - ` - let count = 0; - for (let i = 0; i < 1; i = i + 1) { - for (let j = 0; j < 2; j = j + 1) { - for (let k = 0; k < 4; k = k + 1) { - count = count + 1; - } - } - } - count; - `, - 8 - ) -}) - -test('Break statement in for loop body', async () => { - await testDeterministicCode( - ` - let count = 0; - for (let i = 0; i < 5; i = i + 1) { - break; - count = count + 1; - } - count;`, - 0 - ) -}) - -test('Continue statement in for loop body', async () => { - await testDeterministicCode( - ` - let count = 0; - for (let i = 0; i < 5; i = i + 1) { - continue; - count = count + 1; - } - count;`, - 0 - ) -}) - -test('Return statement in for loop body', async () => { - await testDeterministicCode( - ` - let count = 0; - function loopTest(x) { - for (let i = 0; i < 5; i = i + 1) { - return x; - count = count + 1; - } - } - loopTest(10);`, - 10 - ) -}) - -test('Non-deterministic for loop initializer', async () => { - await testNonDeterministicCode( - ` - let j = 0; - for (let i = amb(3, 4); i > 0; i = i - 1) { - j = j + 1; - } - j;`, - [3, 4] - ) -}) - -test('Non-deterministic for loop condition', async () => { - await testNonDeterministicCode( - ` - let count = 0; - for (let i = 2; i > amb(0, 1); i = i - 1) { - count = count + 1; - } - count;`, - [2, 2, 1, 2, 2, 1] - ) // chosen conditions: (0, 0, 0), (0, 0, 1), (0, 1), (1, 0, 0), (1, 0, 1), (1, 1) -}) - -test('Non-deterministic for loop update', async () => { - await testNonDeterministicCode( - ` - let count = 0; - for (let i = 2; i > 0; i = i - amb(1, 2)) { - count = count + 1; - } - count;`, - [2, 2, 1] - ) // chosen updates: (1, 1), (1, 2), (2) -}) - -test('Non-deterministic for loop body', async () => { - /* number of total program values = - (number of values from cartesian product of the statements in loop body)^ - (number of loop iterations) - */ - - await testNonDeterministicCode( - ` - let count = 0; - for (let i = 3; i > 0; i = i - 1) { - count = count + amb(0, 1); - } - count;`, - [0, 1, 1, 2, 1, 2, 2, 3] - ) - - await testNonDeterministicCode( - ` - let count = 0; - for (let i = 2; i > 0; i = i - 1) { - count = count + amb(0, 1); - count = count + amb(0, 1); - } - count;`, - [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4] - ) - - await testNonDeterministicCode( - ` - const N = 2; - let arr = []; - for (let r = 0; r < N; r = r + 1) { - arr[r] = []; - for (let c = 0; c < N; c = c + 1) { - arr[r][c] = an_integer_between(1, N); - } - } - require(arr[0][0] === 2); - arr;`, - [ - [ - [2, 1], - [1, 1] - ], - [ - [2, 1], - [1, 2] - ], - [ - [2, 1], - [2, 1] - ], - [ - [2, 1], - [2, 2] - ], - [ - [2, 2], - [1, 1] - ], - [ - [2, 2], - [1, 2] - ], - [ - [2, 2], - [2, 1] - ], - [ - [2, 2], - [2, 2] - ] - ] - ) -}) - -// ---------------------------------- Helper functions ------------------------------------------- - -const nonDetTestOptions = { - executionMethod: 'interpreter' -} as Partial - -export async function testDeterministicCode( - code: string, - expectedValue: any, - hasError: boolean = false -) { - /* a deterministic program is equivalent to a non deterministic program - that returns a single value */ - await testNonDeterministicCode(code, [expectedValue], hasError) -} - -/* Assumes the error message (if any) is at the last index of expectedValues */ -export async function testNonDeterministicCode( - code: string, - expectedValues: any[], - hasError: boolean = false, - random: boolean = false -) { - const context: Context = makeNonDetContext() - let result: Result = await runInContext(code, context, nonDetTestOptions) - - const results: any[] = [] - const numOfRuns = hasError ? expectedValues.length - 1 : expectedValues.length - for (let i = 0; i < numOfRuns; i++) { - if (random) { - results.push((result as SuspendedNonDet).value) - } else { - expect((result as SuspendedNonDet).value).toEqual(expectedValues[i]) - } - - expect(result.status).toEqual('suspended-non-det') - result = await resume(result) - } - - if (random) { - verifyRandomizedTest(results, expectedValues) - } - - if (hasError) { - verifyError(result, expectedValues, context) - } else { - verifyFinalResult(result) - } -} - -/* Checks the final result obtained for a test - * Assumes the test is not erroneous - */ -function verifyFinalResult(result: Result) { - // all non deterministic programs have a final result whose value is undefined - expect(result.status).toEqual('finished') - expect((result as Finished).value).toEqual(undefined) -} - -/* Checks the error obtained for an erroneous test - * The error message is captured as the test's final result - */ -function verifyError(result: Result, expectedValues: any[], context: Context) { - expect(result.status).toEqual('error') - const message: string = parseError(context.errors) - expect(message).toEqual(expectedValues[expectedValues.length - 1]) -} - -/* Compares expected and obtained results after a test is run - * Assumes the test involves randomization - */ -function verifyRandomizedTest(results: any[], expectedValues: any[]) { - results.sort() - expectedValues.sort() - expect(results).toEqual(expectedValues) -} - -function makeNonDetContext() { - const context = mockContext(3, Variant.NON_DET) - context.executionMethod = 'interpreter' - return context -} diff --git a/src/__tests__/non-det-sicp-examples.ts b/src/__tests__/non-det-sicp-examples.ts deleted file mode 100644 index 5c8ee8896..000000000 --- a/src/__tests__/non-det-sicp-examples.ts +++ /dev/null @@ -1,209 +0,0 @@ -/* This file uses programs from SICP JS 4.3 as tests for the non deterministic interpreter */ -import { testNonDeterministicCode } from './non-det-interpreter' - -test('An element of', async () => { - await testNonDeterministicCode(`an_element_of(list(1, 2, list(3, 4)));`, [1, 2, [3, [4, null]]]) -}) - -test('An integer between', async () => { - await testNonDeterministicCode('an_integer_between(5, 10);', [5, 6, 7, 8, 9, 10]) -}) - -test('Pythagorean triple', async () => { - await testNonDeterministicCode( - `function a_pythagorean_triple_between(low, high) { - const i = an_integer_between(low, high); - const j = an_integer_between(i, high); - const k = an_integer_between(j, high); - require(i * i + j * j === k * k); - return list(i, j, k); - } - a_pythagorean_triple_between(3, 5);`, - [[3, [4, [5, null]]]] - ) -}) - -test('Multiple dwelling problem', async () => { - await testNonDeterministicCode( - ` - function distinct(items) { - return is_null(items) - ? true - : is_null(tail(items)) - ? true - : is_null(member(head(items), tail(items))) - ? distinct(tail(items)) - : false; - } - function multiple_dwelling() { - const baker = amb(1, 2, 3, 4, 5); - const cooper = amb(1, 2, 3, 4, 5); - const fletcher = amb(1, 2, 3, 4, 5); - const miller = amb(1, 2, 3, 4, 5); - const smith = amb(1, 2, 3, 4, 5); - require(distinct(list(baker, cooper, fletcher, miller, smith))); - require(!(baker === 5)); - require(!(cooper === 1)); - require(!(fletcher === 5)); - require(!(fletcher === 1)); - require((miller > cooper)); - require(!(math_abs(smith - fletcher) === 1)); - require(!(math_abs(fletcher - cooper) === 1)); - return list(list("baker", baker), - list("cooper", cooper), - list("fletcher", fletcher), - list("miller", miller), - list("smith", smith)); - } - multiple_dwelling();`, - [ - [ - ['baker', [3, null]], - [ - ['cooper', [2, null]], - [ - ['fletcher', [4, null]], - [ - ['miller', [5, null]], - [['smith', [1, null]], null] - ] - ] - ] - ] - ] - ) -}) - -test('Language parsing', async () => { - await testNonDeterministicCode( - ` - let unparsed = null; - - const nouns = list("noun", "student", "professor", "cat", "class"); - const verbs = list("verb", "studies", "lectures", "eats", "sleeps"); - const articles = list("article", "the", "a"); - const prepositions = list("prep", "for", "to", "in", "by", "with"); - - function parse_word(word_list) { - require(! is_null(unparsed)); - require(member(head(unparsed), tail(word_list)) !== null); - const found_word = head(unparsed); - unparsed = tail(unparsed); - return list(head(word_list), found_word); - } - - function parse_prepositional_phrase() { - return list("prep-phrase", - parse_word(prepositions), - parse_noun_phrase()); - } - - function parse_verb_phrase() { - function maybe_extend(verb_phrase) { - return amb(verb_phrase, - maybe_extend(list("verb-phrase", - verb_phrase, - parse_prepositional_phrase()))); - } - return maybe_extend(parse_word(verbs)); - } - - function parse_simple_noun_phrase() { - return list("simple-noun-phrase", - parse_word(articles), - parse_word(nouns)); - } - - function parse_noun_phrase() { - function maybe_extend(noun_phrase) { - return amb(noun_phrase, - maybe_extend(list("noun-phrase", - noun_phrase, - parse_prepositional_phrase()))); - } - return maybe_extend(parse_simple_noun_phrase()); - } - - function parse_sentence() { - return list("sentence", - parse_noun_phrase(), - parse_verb_phrase()); - } - - function parse_input(input) { - unparsed = input; - const sent = parse_sentence(); - require(is_null(unparsed)); - return sent; - } - - parse_input(list("the", "student", "with", "the", "cat", "sleeps", "in", "the", "class")); - `, - [ - [ - 'sentence', - [ - [ - 'noun-phrase', - [ - [ - 'simple-noun-phrase', - [ - ['article', ['the', null]], - [['noun', ['student', null]], null] - ] - ], - [ - [ - 'prep-phrase', - [ - ['prep', ['with', null]], - [ - [ - 'simple-noun-phrase', - [ - ['article', ['the', null]], - [['noun', ['cat', null]], null] - ] - ], - null - ] - ] - ], - null - ] - ] - ], - [ - [ - 'verb-phrase', - [ - ['verb', ['sleeps', null]], - [ - [ - 'prep-phrase', - [ - ['prep', ['in', null]], - [ - [ - 'simple-noun-phrase', - [ - ['article', ['the', null]], - [['noun', ['class', null]], null] - ] - ], - null - ] - ] - ], - null - ] - ] - ], - null - ] - ] - ] - ] - ) -}) diff --git a/src/__tests__/scope-finding.ts b/src/__tests__/scope-finding.ts new file mode 100644 index 000000000..df917a806 --- /dev/null +++ b/src/__tests__/scope-finding.ts @@ -0,0 +1,463 @@ +import type { Position } from 'acorn/dist/acorn' +import type { SourceLocation } from 'estree' +import { stripIndent } from '../utils/formatters' +import { createTestContext } from '../utils/testing' +import { Chapter } from '../types' +import { findDeclaration, getScope } from '..' + +class SourceLocationTestResult { + start: Position + end: Position + constructor(startLine: number, startCol: number, endLine: number, endCol: number) { + this.start = { line: startLine, column: startCol } + this.end = { line: endLine, column: endCol } + } +} + +function expectResultsToMatch( + actualResult: SourceLocation | null | undefined, + expectedResult: SourceLocationTestResult | null | undefined +) { + if (expectedResult === null) { + expect(actualResult).toBeNull() + return + } + if (expectedResult === undefined) { + expect(actualResult).toBeUndefined() + return + } + expect(actualResult).not.toBeNull() + expect(actualResult).not.toBeUndefined() + if (actualResult === null || actualResult === undefined) { + return + } + expect(actualResult.start.line).toEqual(expectedResult.start.line) + expect(actualResult.start.column).toEqual(expectedResult.start.column) + expect(actualResult.end.line).toEqual(expectedResult.end.line) + expect(actualResult.end.column).toEqual(expectedResult.end.column) +} + +test('Find variable declaration in global scope', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + let i = 0; + function f() { + i = i + 1; + return i; + } + i; + ` + const expected = new SourceLocationTestResult(1, 4, 1, 5) + const actual = findDeclaration(code, context, { line: 6, column: 0 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find variable declaration in global scope from occurrence in function scope', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + let i = 0; + function f() { + i = i + 1; + return i; + } + i; + ` + const expected = new SourceLocationTestResult(1, 4, 1, 5) + const actual = findDeclaration(code, context, { line: 4, column: 9 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find variable declaration in function scope from occurrence in function scope', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + let i = 0; + function f() { + let i = 2; + return i; + } + i; + ` + const expected = new SourceLocationTestResult(3, 6, 3, 7) + const actual = findDeclaration(code, context, { line: 4, column: 9 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find no declaration from occurrence when there is no declaration (syntax error)', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + function f() { + let i = 2; + return i; + } + x; + ` + const expected = null + const actual = findDeclaration(code, context, { line: 5, column: 0 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find no declaration from selection that does not refer to a declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + let i = 0; + function f() { + let i = 2; + return i; + } + i; + ` + const expected = null + const actual = findDeclaration(code, context, { line: 4, column: 3 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find function declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + let i = 0; + function foo() { + let i = 2; + return i; + } + foo(); + ` + const expected = new SourceLocationTestResult(2, 9, 2, 12) + const actual = findDeclaration(code, context, { line: 6, column: 0 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find function param declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + function timesTwo(num) { + return num * 2; + } + timesTwo(2); + ` + const expected = new SourceLocationTestResult(1, 18, 1, 21) + const actual = findDeclaration(code, context, { line: 2, column: 9 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find variable declaration with same name as function param declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + function timesTwo(num) { + return num * 2; + } + const num = 2; + timesTwo(num); + ` + const expected = new SourceLocationTestResult(4, 6, 4, 9) + const actual = findDeclaration(code, context, { line: 5, column: 9 }) + expectResultsToMatch(actual, expected) + // expect(actual).toMatchSnapshot() +}) + +test('Find arrow function declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + let i = 0; + const foo = () => { + let i = 2; + return i; + } + foo(); + ` + const expected = new SourceLocationTestResult(2, 6, 2, 9) + const actual = findDeclaration(code, context, { line: 6, column: 0 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find arrow function param declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + const timesTwo = (num) => { + return num * 2; + } + timesTwo(2); + ` + const expected = new SourceLocationTestResult(1, 18, 1, 21) + const actual = findDeclaration(code, context, { line: 2, column: 9 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find variable declaration with same name as arrow function param declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + const timesTwo = (num) => { + return num * 2; + } + const num = 2; + timesTwo(num); + ` + const expected = new SourceLocationTestResult(4, 6, 4, 9) + const actual = findDeclaration(code, context, { line: 5, column: 9 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find declaration in init of for loop', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + let x = 1; + for (let i = 1; i <= 2; i++) { + x = x * i; + } + x; + ` + const expected = new SourceLocationTestResult(2, 9, 2, 10) + const actual = findDeclaration(code, context, { line: 3, column: 10 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find variable declaration with same name as init of for loop', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + let x = 1; + for (let i = 1; i <= 2; i++) { + x = x * i; + } + const i = 2; + i; + ` + const expected = new SourceLocationTestResult(5, 6, 5, 7) + const actual = findDeclaration(code, context, { line: 6, column: 0 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find variable declaration in block statement', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + { + let x = 1; + x = x + 2; + } + let x = 2; + x = x + 2; + ` + const expected = new SourceLocationTestResult(2, 6, 2, 7) + const actual = findDeclaration(code, context, { line: 3, column: 2 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) +test('Find variable declaration of same name as variable declaration in block statement', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + { + let x = 1; + x = x + 2; + } + let x = 2; + x = x + 2; + ` + const expected = new SourceLocationTestResult(5, 4, 5, 5) + const actual = findDeclaration(code, context, { line: 6, column: 0 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find declaration of of variable in update statement of a for loop', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = stripIndent` + for (let x = 10; x < 12; ++x) { + display(x); + } + let x = 5; + ` + const expected = new SourceLocationTestResult(1, 9, 1, 10) + const actual = findDeclaration(code, context, { line: 1, column: 17 }) + expectResultsToMatch(actual, expected) + expect(actual).toMatchSnapshot() +}) + +test('Find scope of a variable declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = `{ + const x = 1; + { + const x = 2; + function f(y) { + return x + y; + } + } + display(x); + }` + const expected = [ + new SourceLocationTestResult(1, 0, 3, 4), + new SourceLocationTestResult(8, 5, 10, 3) + ] + const actual = getScope(code, context, { line: 2, column: 10 }) + expected.forEach((expectedRange, index) => { + const actualRange = new SourceLocationTestResult( + actual[index].start.line, + actual[index].start.column, + actual[index].end.line, + actual[index].end.column + ) + expectResultsToMatch(actualRange, expectedRange) + }) + expect(actual).toMatchSnapshot() +}) + +test('Find scope of a nested variable declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = `{ + const x = 1; + { + const x = 2; + function f(y) { + return x + y; + } + } + display(x); + }` + const expected = [new SourceLocationTestResult(3, 4, 8, 5)] + const actual = getScope(code, context, { line: 4, column: 15 }) + expected.forEach((expectedRange, index) => { + const actualRange = new SourceLocationTestResult( + actual[index].start.line, + actual[index].start.column, + actual[index].end.line, + actual[index].end.column + ) + expectResultsToMatch(actualRange, expectedRange) + }) + expect(actual).toMatchSnapshot() +}) + +test('Find scope of a function parameter', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = `{ + const x = 1; + { + const x = 2; + function f(y) { + return x + y; + } + } + display(x); + }` + const expected = [new SourceLocationTestResult(5, 22, 7, 9)] + const actual = getScope(code, context, { line: 5, column: 19 }) + expected.forEach((expectedRange, index) => { + const actualRange = new SourceLocationTestResult( + actual[index].start.line, + actual[index].start.column, + actual[index].end.line, + actual[index].end.column + ) + expectResultsToMatch(actualRange, expectedRange) + }) + expect(actual).toMatchSnapshot() +}) + +test('Find scope of a function declaration', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = `{ + const x = 1; + { + const x = 2; + function f(y) { + return x + y; + } + } + display(x); + }` + const expected = [new SourceLocationTestResult(3, 4, 8, 5)] + const actual = getScope(code, context, { line: 5, column: 17 }) + expected.forEach((expectedRange, index) => { + const actualRange = new SourceLocationTestResult( + actual[index].start.line, + actual[index].start.column, + actual[index].end.line, + actual[index].end.column + ) + expectResultsToMatch(actualRange, expectedRange) + }) + expect(actual).toMatchSnapshot() +}) + +test('Find scope of a variable declaration with more nesting', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = `{ + const x = 1; + { + const x = 2; + function f(y) { + for (let x = 10; x < 20; x = x + 1) { + display(x); + } + return x + y; + } + for (let x = 10; x < 20; x = x + 1) { + display(x); + } + } + display(x); + }` + const expected = [ + new SourceLocationTestResult(3, 4, 6, 12), + new SourceLocationTestResult(8, 13, 11, 8), + new SourceLocationTestResult(13, 9, 14, 5) + ] + const actual = getScope(code, context, { line: 4, column: 15 }) + expected.forEach((expectedRange, index) => { + const actualRange = new SourceLocationTestResult( + actual[index].start.line, + actual[index].start.column, + actual[index].end.line, + actual[index].end.column + ) + expectResultsToMatch(actualRange, expectedRange) + }) + expect(actual).toMatchSnapshot() +}) + +test('Find scope of a variable declaration with multiple blocks', () => { + const context = createTestContext({ chapter: Chapter.SOURCE_4 }) + const code = `const x = 1; + { + const x = 2; + { + const x = 3; + } + x ; + { + const x = 4; + } + x; + { + const x = 5; + } + } + x;` + const expected = [ + new SourceLocationTestResult(2, 4, 4, 8), + new SourceLocationTestResult(6, 9, 8, 8), + new SourceLocationTestResult(10, 9, 12, 8), + new SourceLocationTestResult(14, 9, 15, 5) + ] + const actual = getScope(code, context, { line: 3, column: 15 }) + expected.forEach((expectedRange, index) => { + const actualRange = new SourceLocationTestResult( + actual[index].start.line, + actual[index].start.column, + actual[index].end.line, + actual[index].end.column + ) + expectResultsToMatch(actualRange, expectedRange) + }) + expect(actual).toMatchSnapshot() +}) diff --git a/src/__tests__/scope-refactoring.ts b/src/__tests__/scope-refactoring.ts index 89f65c15c..25e33047d 100644 --- a/src/__tests__/scope-refactoring.ts +++ b/src/__tests__/scope-refactoring.ts @@ -1,6 +1,6 @@ -import { Program } from 'estree' +import type { Program } from 'estree' -import { default as createContext } from '../createContext' +import createContext from '../createContext' import { getAllOccurrencesInScope } from '../index' import { looseParse } from '../parser/utils' import { diff --git a/src/__tests__/stringify-benchmark.ts b/src/__tests__/stringify-benchmark.ts deleted file mode 100644 index 485382f3e..000000000 --- a/src/__tests__/stringify-benchmark.ts +++ /dev/null @@ -1,153 +0,0 @@ -import * as list from '../stdlib/list' -import { Chapter } from '../types' -import { stripIndent } from '../utils/formatters' -import { stringify } from '../utils/stringify' -import { testSuccess } from '../utils/testing' - -test('stringify is fast', () => { - return expect( - testSuccess( - stripIndent` - function make_k_list(k, d) { - const degree = k; - const depth = d; - let repeat = 0; - function helper(k, d, to_repeat) { - if (d === 0 && k === 0) { - return null; - } else if (k === 0) { - return helper(degree, d - 1, repeat); - } else { - repeat = pair(to_repeat, helper(k - 1, d, to_repeat)); - return pair(to_repeat, helper(k - 1, d, to_repeat)); - } - } - return helper(k, d, 0); - } - - const bigstructure = make_k_list(2,2); - const start = get_time(); - stringify(bigstructure); - const end = get_time(); - end - start; - `, - { chapter: Chapter.SOURCE_3, native: false } - ).then(testResult => testResult.result) - ).resolves.toBeLessThan(2000) - // This benchmark takes 100ms on my machine, - // but less than 2 seconds should be good enough on the test servers. -}) - -test('display_list with stringify is linear runtime', () => { - const placeholder = Symbol('placeholder') - const noDisplayList = (v: any, s: any = placeholder) => { - if (s !== placeholder && typeof s !== 'string') { - throw new TypeError('display_list expects the second argument to be a string') - } - return stringify(list.rawDisplayList((x: any) => x, v, s === placeholder ? undefined : s)) - } - - return expect( - testSuccess( - stripIndent` - const build_inf = (i, f) => { - const t = list(f(i)); - let p = t; - for (let n = i - 1; n >= 0; n = n - 1) { - p = pair(f(n), p); - } - set_tail(t, p); - return p; - }; - const make_complex_list = n => { - // makes a complex list structure with O(n) pairs - const cuberootn = math_floor(math_pow(n, 0.33)); - return build_list(cuberootn, _ => build_inf(cuberootn, _ => build_list(cuberootn, i =>i))); - }; - const time_display_list = xs => { - const starttime = get_time(); - no_display_list(xs); - return get_time() - starttime; - }; - - // Warm up - time_display_list(make_complex_list(5000)); - - // measure - const ns = [ - // 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000 - // ^-- times 3 - 14000, 10000, 20000, 15000, 17000, 11000, 13000, 12000, 15000, 12000, 19000, 10000, 13000, 14000, 12000, 18000, 17000, 13000, 19000, 16000, 18000, 18000, 20000, 20000, 16000, 11000, 16000, 10000, 17000, 15000, 19000, 11000, 14000 - ]; - const xvalues = []; - const yvalues = []; - for (let i = 0; i < array_length(ns); i = i + 1) { - const xs = make_complex_list(ns[i]); - const t = time_display_list(xs); - xvalues[i] = math_log(ns[i]); - yvalues[i] = math_log(t); - } - - // linear regression adapted from https://dracoblue.net/dev/linear-least-squares-in-javascript/ - function findLineByLeastSquares(values_x, values_y) { - let sum_x = 0; - let sum_y = 0; - let sum_xy = 0; - let sum_xx = 0; - let count = 0; - - /* - * We'll use those variables for faster read/write access. - */ - let x = 0; - let y = 0; - let values_length = array_length(values_x); - - /* - * Nothing to do. - */ - if (values_length === 0) { - return [ [], [] ]; - } else {} - - /* - * Calculate the sum for each of the parts necessary. - */ - for (let v = 0; v < values_length; v = v + 1) { - x = values_x[v]; - y = values_y[v]; - sum_x = sum_x + x; - sum_y = sum_y + y; - sum_xx = sum_xx + x*x; - sum_xy = sum_xy + x*y; - count = count + 1; - } - - /* - * Calculate m and b for the formular: - * y = x * m + b - */ - let m = (count*sum_xy - sum_x*sum_y) / (count*sum_xx - sum_x*sum_x); - let b = (sum_y/count) - (m*sum_x)/count; - - return pair(m, b); - } - - // best fit - const line = findLineByLeastSquares(xvalues, yvalues); - const slope = head(line); - slope; - `, - { - chapter: Chapter.SOURCE_3, - native: false, // we're measuring a builtin, no need for native - testBuiltins: { - no_display_list: noDisplayList - } - } - ).then(testResult => testResult.result) - ).resolves.toBeLessThan(1.2) - // estimated power is less than 1.2 - // means it's probably near 1 - // => probably linear? -}, 1000000) diff --git a/src/__tests__/tailcall-return.ts b/src/__tests__/tailcall-return.ts deleted file mode 100644 index 9a3a0ce27..000000000 --- a/src/__tests__/tailcall-return.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { stripIndent } from '../utils/formatters' -import { expectParsedErrorNoSnapshot, expectResult } from '../utils/testing' - -test('Check that stack is at most 10k in size', () => { - return expectParsedErrorNoSnapshot(stripIndent` - function f(x) { - if (x <= 0) { - return 0; - } else { - return 1 + f(x-1); - } - } - f(10000); - `).toEqual(expect.stringMatching(/Maximum call stack size exceeded\n([^f]*f){3}/)) -}, 10000) - -test('Simple tail call returns work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in conditional expressions work', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x <= 0 ? y : f(x-1, y+1); - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in boolean operators work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return false || f(x-1, y+1); - } - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in nested mix of conditional expressions boolean operators work', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in arrow functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => x <= 0 ? y : f(x-1, y+1); - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in arrow block functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - }; - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mutual recursion work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return g(x-1, y+1); - } - } - function g(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mutual recursion with arrow functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => x <= 0 ? y : g(x-1, y+1); - const g = (x, y) => x <= 0 ? y : f(x-1, y+1); - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mixed tail-call/non-tail-call recursion work', () => { - return expectResult( - stripIndent` - function f(x, y, z) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+f(0, z, 0), z); - } - } - f(5000, 5000, 2); - `, - { native: true } - ).toMatchInlineSnapshot(`15000`) -}) diff --git a/src/runner/__tests__/files.ts b/src/runner/__tests__/files.ts index a7ec831e6..9a821bd71 100644 --- a/src/runner/__tests__/files.ts +++ b/src/runner/__tests__/files.ts @@ -59,7 +59,7 @@ describe('runFilesInContext', () => { it('returns ModuleNotFoundError if entrypoint file does not exist', async () => { const files: Record = {} - await await runFilesInContext(files, '/a.js', context) + await runFilesInContext(files, '/a.js', context) expect(parseError(context.errors)).toMatchInlineSnapshot(`"Module '/a.js' not found."`) }) diff --git a/src/runner/__tests__/runners.ts b/src/runner/__tests__/runners.ts index 05c2a6deb..231959164 100644 --- a/src/runner/__tests__/runners.ts +++ b/src/runner/__tests__/runners.ts @@ -1,10 +1,11 @@ -import { Context, Result, runInContext } from '../..' +import { Context, Result, parseError, runInContext } from '../..' import { UndefinedVariable } from '../../errors/errors' import { mockContext } from '../../mocks/context' import { FatalSyntaxError } from '../../parser/errors' -import { Chapter, Finished, Variant } from '../../types' +import { Chapter, Finished, Variant, type ExecutionMethod } from '../../types' import { locationDummyNode } from '../../utils/ast/astCreator' -import { CodeSnippetTestCase } from '../../utils/testing' +import { CodeSnippetTestCase, expectFinishedResult } from '../../utils/testing' +import { testMultipleCases } from '../../utils/testing/testers' import { htmlErrorHandlingScript } from '../htmlRunner' const JAVASCRIPT_CODE_SNIPPETS_NO_ERRORS: CodeSnippetTestCase[] = [ @@ -210,6 +211,194 @@ describe('Functions in Source libraries (e.g. list, streams) are available in So }) }) +test('Test context reuse', async () => { + const context = mockContext(Chapter.SOURCE_4) + const init = ` + let i = 0; + function f() { + i = i + 1; + return i; + } + i; + ` + + const snippet: [string, number][] = [ + ['i = 100; f();', 101], + ['f(); i;', 102], + ['i;', 102] + ] + + await runInContext(init, context) + for (const [code, expected] of snippet) { + const result = await runInContext(code, context) + expectFinishedResult(result) + expect(result.value).toEqual(expected) + } +}) + +describe('Test tail call return for native runner', () => { + // TODO: Check if this test is still relevant + // test.skip('Check that stack is at most 10k in size', () => { + // return expectParsedErrorNoSnapshot(stripIndent` + // function f(x) { + // if (x <= 0) { + // return 0; + // } else { + // return 1 + f(x-1); + // } + // } + // f(10000); + // `).toEqual(expect.stringMatching(/Maximum call stack size exceeded\n([^f]*f){3}/)) + // }, 10000) + + testMultipleCases([ + [ + 'Simple taill call returns work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail call in conditional expressions work', + ` + function f(x, y) { + return x <= 0 ? y : f(x-1, y+1); + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail call in boolean operators work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return false || f(x-1, y+1); + } + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail call in nested mix of conditional expressions and boolean operators work', + ` + function f(x, y) { + return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in arrow block functions work', + ` + const f = (x, y) => { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } + }; + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in expression arrow functions work', + ` + const f = (x, y) => x <= 0 ? y : f(x-1, y+1); + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in mutual recursion work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return g(x-1, y+1); + } + } + function g(x, y) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in mutual recursion with arrow functions work', + ` + const f = (x, y) => x <= 0 ? y : g(x-1, y+1); + const g = (x, y) => x <= 0 ? y : f(x-1, y+1); + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in mixed tail-call/non-tail-call recursion work', + ` + function f(x, y, z) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+f(0, z, 0), z); + } + } + f(5000, 5000, 2); + `, + 15000 + ] + ], async ([code, expected]) => { + const context = mockContext(Chapter.SOURCE_1) + const result = await runInContext(code, context) + expectFinishedResult(result) + expect(result.value).toEqual(expected) + }, false, 10000) +}) + +describe('Tests for all runners', () => { + const methodsToTest: ExecutionMethod[] = ['cse-machine', 'stepper', 'native'] + + test("Runners won't allow assignments to consts even when assignment is allowed", () => { + const snippet = ` + function test(){ + const constant = 3; + constant = 4; + return constant; + } + test(); + ` + + return Promise.all( + methodsToTest.map(async method => { + const context = mockContext(Chapter.SOURCE_3) + const result = await runInContext(snippet, context, { executionMethod: method }) + expect(result.status).toEqual('error') + expect(parseError(context.errors)).toEqual( + 'Line 4: Cannot assign new value to constant constant.' + ) + }) + ) + }) +}) // HTML Unit Tests test('Error handling script is injected in HTML code', async () => { diff --git a/src/runner/fullJSRunner.ts b/src/runner/fullJSRunner.ts index 5cf1259b3..5012323c6 100644 --- a/src/runner/fullJSRunner.ts +++ b/src/runner/fullJSRunner.ts @@ -8,12 +8,7 @@ import { NATIVE_STORAGE_ID } from '../constants' import { RuntimeSourceError } from '../errors/runtimeSourceError' import type { ImportOptions } from '../modules/moduleTypes' import { parse } from '../parser/parser' -import { - evallerReplacer, - getBuiltins, - getGloballyDeclaredIdentifiers, - transpile -} from '../transpiler/transpiler' +import { evallerReplacer, getBuiltins, transpile } from '../transpiler/transpiler' import type { Context, NativeStorage } from '../types' import * as create from '../utils/ast/astCreator' import { getDeclaredIdentifiers } from '../utils/ast/helpers' @@ -69,9 +64,7 @@ export async function fullJSRunner( getDeclaredIdentifiers(preEvalProgram).forEach(id => context.nativeStorage.previousProgramsIdentifiers.add(id.name) ) - getGloballyDeclaredIdentifiers(preEvalProgram).forEach(id => - context.nativeStorage.previousProgramsIdentifiers.add(id) - ) + const preEvalCode: string = generate(preEvalProgram) await fullJSEval(preEvalCode, context.nativeStorage) diff --git a/src/validator/__tests__/__snapshots__/validator.ts.snap b/src/validator/__tests__/__snapshots__/validator.ts.snap index 59278dd99..5efda57f7 100644 --- a/src/validator/__tests__/__snapshots__/validator.ts.snap +++ b/src/validator/__tests__/__snapshots__/validator.ts.snap @@ -1,37 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Test validateAndAnnotate for loop variable cannot be reassigned in closure: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; i < 10; i = i + 1) { - function f() { - i = 10; - } -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Assignment to a for loop variable in the for loop is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Test validateAndAnnotate for loop variable cannot be reassigned: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; i < 10; i = i + 1) { - i = 10; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Assignment to a for loop variable in the for loop is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - exports[`Test validateAndAnnotate testing typability 1`] = ` Node { "body": Array [ @@ -449,453 +417,3 @@ Node { "type": "Program", } `; - -exports[`for loop variable cannot be reassigned in closure: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; i < 10; i = i + 1) { - function f() { - i = 10; - } -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Assignment to a for loop variable in the for loop is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`for loop variable cannot be reassigned: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; i < 10; i = i + 1) { - i = 10; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Assignment to a for loop variable in the for loop is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`testing typability 1`] = ` -Node { - "body": Array [ - Node { - "declarations": Array [ - Node { - "end": 11, - "id": Node { - "end": 7, - "loc": SourceLocation { - "end": Position { - "column": 7, - "line": 1, - }, - "start": Position { - "column": 6, - "line": 1, - }, - }, - "name": "a", - "start": 6, - "type": "Identifier", - }, - "init": Node { - "end": 11, - "loc": SourceLocation { - "end": Position { - "column": 11, - "line": 1, - }, - "start": Position { - "column": 10, - "line": 1, - }, - }, - "raw": "1", - "start": 10, - "type": "Literal", - "value": 1, - }, - "loc": SourceLocation { - "end": Position { - "column": 11, - "line": 1, - }, - "start": Position { - "column": 6, - "line": 1, - }, - }, - "start": 6, - "type": "VariableDeclarator", - }, - ], - "end": 12, - "kind": "const", - "loc": SourceLocation { - "end": Position { - "column": 12, - "line": 1, - }, - "start": Position { - "column": 0, - "line": 1, - }, - }, - "start": 0, - "typability": "NotYetTyped", - "type": "VariableDeclaration", - }, - Node { - "body": Node { - "body": Array [ - Node { - "end": 54, - "expression": Node { - "end": 53, - "loc": SourceLocation { - "end": Position { - "column": 3, - "line": 3, - }, - "start": Position { - "column": 2, - "line": 3, - }, - }, - "name": "c", - "start": 52, - "type": "Identifier", - }, - "loc": SourceLocation { - "end": Position { - "column": 4, - "line": 3, - }, - "start": Position { - "column": 2, - "line": 3, - }, - }, - "start": 52, - "type": "ExpressionStatement", - }, - Node { - "argument": Node { - "arguments": Array [], - "callee": Node { - "end": 65, - "loc": SourceLocation { - "end": Position { - "column": 10, - "line": 4, - }, - "start": Position { - "column": 9, - "line": 4, - }, - }, - "name": "f", - "start": 64, - "type": "Identifier", - }, - "end": 67, - "loc": SourceLocation { - "end": Position { - "column": 12, - "line": 4, - }, - "start": Position { - "column": 9, - "line": 4, - }, - }, - "start": 64, - "type": "CallExpression", - }, - "end": 68, - "loc": SourceLocation { - "end": Position { - "column": 13, - "line": 4, - }, - "start": Position { - "column": 2, - "line": 4, - }, - }, - "start": 57, - "type": "ReturnStatement", - }, - ], - "end": 70, - "loc": SourceLocation { - "end": Position { - "column": 1, - "line": 5, - }, - "start": Position { - "column": 13, - "line": 2, - }, - }, - "start": 37, - "type": "BlockStatement", - }, - "end": 70, - "expression": false, - "generator": false, - "id": Node { - "end": 34, - "loc": SourceLocation { - "end": Position { - "column": 10, - "line": 2, - }, - "start": Position { - "column": 9, - "line": 2, - }, - }, - "name": "f", - "start": 33, - "type": "Identifier", - }, - "loc": SourceLocation { - "end": Position { - "column": 1, - "line": 5, - }, - "start": Position { - "column": 0, - "line": 2, - }, - }, - "params": Array [], - "start": 24, - "typability": "NotYetTyped", - "type": "FunctionDeclaration", - }, - Node { - "declarations": Array [ - Node { - "end": 84, - "id": Node { - "end": 78, - "loc": SourceLocation { - "end": Position { - "column": 7, - "line": 6, - }, - "start": Position { - "column": 6, - "line": 6, - }, - }, - "name": "b", - "start": 77, - "type": "Identifier", - }, - "init": Node { - "arguments": Array [], - "callee": Node { - "end": 82, - "loc": SourceLocation { - "end": Position { - "column": 11, - "line": 6, - }, - "start": Position { - "column": 10, - "line": 6, - }, - }, - "name": "f", - "start": 81, - "type": "Identifier", - }, - "end": 84, - "loc": SourceLocation { - "end": Position { - "column": 13, - "line": 6, - }, - "start": Position { - "column": 10, - "line": 6, - }, - }, - "start": 81, - "type": "CallExpression", - }, - "loc": SourceLocation { - "end": Position { - "column": 13, - "line": 6, - }, - "start": Position { - "column": 6, - "line": 6, - }, - }, - "start": 77, - "type": "VariableDeclarator", - }, - ], - "end": 85, - "kind": "const", - "loc": SourceLocation { - "end": Position { - "column": 14, - "line": 6, - }, - "start": Position { - "column": 0, - "line": 6, - }, - }, - "start": 71, - "typability": "NotYetTyped", - "type": "VariableDeclaration", - }, - Node { - "body": Node { - "body": Array [], - "end": 128, - "loc": SourceLocation { - "end": Position { - "column": 1, - "line": 8, - }, - "start": Position { - "column": 13, - "line": 7, - }, - }, - "start": 110, - "type": "BlockStatement", - }, - "end": 128, - "expression": false, - "generator": false, - "id": Node { - "end": 107, - "loc": SourceLocation { - "end": Position { - "column": 10, - "line": 7, - }, - "start": Position { - "column": 9, - "line": 7, - }, - }, - "name": "g", - "start": 106, - "type": "Identifier", - }, - "loc": SourceLocation { - "end": Position { - "column": 1, - "line": 8, - }, - "start": Position { - "column": 0, - "line": 7, - }, - }, - "params": Array [], - "start": 97, - "typability": "Untypable", - "type": "FunctionDeclaration", - }, - Node { - "declarations": Array [ - Node { - "end": 140, - "id": Node { - "end": 136, - "loc": SourceLocation { - "end": Position { - "column": 7, - "line": 9, - }, - "start": Position { - "column": 6, - "line": 9, - }, - }, - "name": "c", - "start": 135, - "type": "Identifier", - }, - "init": Node { - "end": 140, - "loc": SourceLocation { - "end": Position { - "column": 11, - "line": 9, - }, - "start": Position { - "column": 10, - "line": 9, - }, - }, - "raw": "1", - "start": 139, - "type": "Literal", - "value": 1, - }, - "loc": SourceLocation { - "end": Position { - "column": 11, - "line": 9, - }, - "start": Position { - "column": 6, - "line": 9, - }, - }, - "start": 135, - "type": "VariableDeclarator", - }, - ], - "end": 141, - "kind": "const", - "loc": SourceLocation { - "end": Position { - "column": 12, - "line": 9, - }, - "start": Position { - "column": 0, - "line": 9, - }, - }, - "start": 129, - "typability": "Untypable", - "type": "VariableDeclaration", - }, - ], - "end": 156, - "loc": SourceLocation { - "end": Position { - "column": 27, - "line": 9, - }, - "start": Position { - "column": 0, - "line": 1, - }, - }, - "sourceType": "module", - "start": 0, - "type": "Program", -} -`; diff --git a/src/validator/__tests__/validator.ts b/src/validator/__tests__/validator.ts index 71366d8e4..39376a05d 100644 --- a/src/validator/__tests__/validator.ts +++ b/src/validator/__tests__/validator.ts @@ -5,7 +5,8 @@ import { parse } from '../../parser/parser' import { Chapter, type NodeWithInferredType } from '../../types' import { getVariableDeclarationName } from '../../utils/ast/astCreator' import { stripIndent } from '../../utils/formatters' -import { astTester, expectTrue } from '../../utils/testing' +import { expectTrue } from '../../utils/testing' +import { astTester } from '../../utils/testing/testers' import { simple } from '../../utils/walkers' import { checkForUndefinedVariables, validateAndAnnotate } from '../validator' import { UndefinedVariable } from '../../errors/errors' From 0d6d545abed5b20aeb52296be5ced152f57cd326 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 20 May 2024 15:42:43 +0800 Subject: [PATCH 15/65] Misc test changes --- .../preprocessor/__tests__/analyzer.ts | 155 +++---- src/parser/__tests__/general.ts | 31 ++ .../__tests__/__snapshots__/lazyLists.ts.snap | 109 ----- .../__tests__/__snapshots__/stream.ts.snap | 6 +- src/stdlib/__tests__/lazyLists.ts | 426 ------------------ src/stdlib/__tests__/list.ts | 23 +- src/transpiler/__tests__/transpiled-code.ts | 1 - src/transpiler/transpiler.ts | 21 +- 8 files changed, 127 insertions(+), 645 deletions(-) create mode 100644 src/parser/__tests__/general.ts delete mode 100644 src/stdlib/__tests__/__snapshots__/lazyLists.ts.snap delete mode 100644 src/stdlib/__tests__/lazyLists.ts diff --git a/src/modules/preprocessor/__tests__/analyzer.ts b/src/modules/preprocessor/__tests__/analyzer.ts index d7e292691..2e258c974 100644 --- a/src/modules/preprocessor/__tests__/analyzer.ts +++ b/src/modules/preprocessor/__tests__/analyzer.ts @@ -12,6 +12,7 @@ import analyzeImportsAndExports from '../analyzer' import loadSourceModules from '../../loader' import type { SourceFiles as Files } from '../../moduleTypes' import { objectKeys } from '../../../utils/misc' +import { testTrueAndFalseCases } from '../../../utils/testing/testers' jest.mock('../../loader/loaders') @@ -81,9 +82,9 @@ describe('Test throwing import validation errors', () => { // Providing an ErrorInfo object indicates that the test case should throw // the corresponding error - type ImportTestCaseWithNoError = [string, T, keyof T] - type ImportTestCaseWithError = [...ImportTestCaseWithNoError, ErrorInfo] - type ImportTestCase = ImportTestCaseWithError | ImportTestCaseWithNoError + type ImportTestCaseWithNoError = [string, Files, string] + type ImportTestCaseWithError = [...ImportTestCaseWithNoError, ErrorInfo] + type ImportTestCase = ImportTestCaseWithError | ImportTestCaseWithNoError function expectValidationError(obj: any): asserts obj is UndefinedNamespaceImportError { expect(obj).toBeInstanceOf(UndefinedNamespaceImportError) @@ -133,50 +134,35 @@ describe('Test throwing import validation errors', () => { } type FullTestCase = [string, Files, `/${string}`, ErrorInfo | boolean] - function testCases(desc: string, cases: ImportTestCase[]) { - const [allNoCases, allYesCases] = cases.reduce( - ([noThrow, yesThrow], [desc, files, entry, errorInfo], i) => { + function testCases(desc: string, cases: ImportTestCase[]) { + testTrueAndFalseCases( + desc, + 'allowUndefinedImports', + cases, + ([desc, files, entry, errorInfo], i) => { return [ + [`${i + 1}: ${desc} should not throw an error`, files, entry, true] as FullTestCase, [ - ...noThrow, - [`${i + 1}: ${desc} should not throw an error`, files, entry, true] as FullTestCase - ], - [ - ...yesThrow, - [ - `${i + 1}: ${desc} should${errorInfo ? '' : ' not'} throw an error`, - files, - entry, - errorInfo - ] as FullTestCase - ] + `${i + 1}: ${desc} should${errorInfo ? '' : ' not'} throw an error`, + files, + entry, + errorInfo + ] as FullTestCase ] }, - [[], []] as [FullTestCase[], FullTestCase[]] - ) - - const caseTester: (...args: FullTestCase) => Promise = async ( - _, - files, - entrypointFilePath, - errorInfo - ) => { - if (errorInfo === true) { - // If allowUndefinedImports is true, the analyzer should never throw an error - await testSuccess(files, entrypointFilePath, true) - } else if (!errorInfo) { - // Otherwise it should not throw when no errors are expected - await testSuccess(files, entrypointFilePath, false) - } else { - // Or throw the expected error - await testFailure(files, entrypointFilePath, false, errorInfo) + async ([files, entrypointFilePath, errorInfo]) => { + if (errorInfo === true) { + // If allowUndefinedImports is true, the analyzer should never throw an error + await testSuccess(files, entrypointFilePath, true) + } else if (!errorInfo) { + // Otherwise it should not throw when no errors are expected + await testSuccess(files, entrypointFilePath, false) + } else { + // Or throw the expected error + await testFailure(files, entrypointFilePath, false, errorInfo) + } } - } - - describe(`${desc} with allowUndefinedimports true`, () => - test.each(allNoCases)('%s', caseTester)) - describe(`${desc} with allowUndefinedimports false`, () => - test.each(allYesCases)('%s', caseTester)) + ) } describe('Test regular imports', () => { @@ -651,68 +637,73 @@ describe('Test throwing DuplicateImportNameErrors', () => { const isTestCaseWithNoError = (c: TestCase): c is TestCaseWithNoError => c.length === 2 - type FullTestCase = [string, Files, true, string | undefined] | [string, Files, false, undefined] + type FullTestCase = [Files, true, string | undefined] | [Files, false, undefined] function expectDuplicateError(obj: any): asserts obj is DuplicateImportNameError { expect(obj).toBeInstanceOf(DuplicateImportNameError) } function testCases(desc: string, cases: TestCase[]) { - const [allNoCases, allYesCases] = cases.reduce( - ([noThrow, yesThrow], c, i) => { + testTrueAndFalseCases, FullTestCase>( + desc, + 'throwOnDuplicateImports', + cases, + (c, i) => { // For each test case, split it into the case where throwOnDuplicateImports is true // and when it is false. No errors should ever be thrown when throwOnDuplicateImports is false if (isTestCaseWithNoError(c)) { // No error message was given, so no error is expected to be thrown, // regardless of the value of throwOnDuplicateImports const [desc] = c - const noThrowCase: FullTestCase = [`${i + 1}. ${desc}: no error `, c[1], false, undefined] - const yesThrowCase: FullTestCase = [`${i + 1}. ${desc}: no error`, c[1], true, undefined] - return [ - [...noThrow, noThrowCase], - [...yesThrow, yesThrowCase] + const noThrowCase: [string, ...FullTestCase] = [ + `${i + 1}. ${desc}: no error `, + c[1], + false, + undefined + ] + const yesThrowCase: [string, ...FullTestCase] = [ + `${i + 1}. ${desc}: no error`, + c[1], + true, + undefined ] + return [noThrowCase, yesThrowCase] } const [desc, , errMsg] = c - const noThrowCase: FullTestCase = [`${i + 1}. ${desc}: no error`, c[1], false, undefined] - const yesThrowCase: FullTestCase = [`${i + 1}. ${desc}: error`, c[1], true, errMsg] - return [ - [...noThrow, noThrowCase], - [...yesThrow, yesThrowCase] + const noThrowCase: [string, ...FullTestCase] = [ + `${i + 1}. ${desc}: no error`, + c[1], + false, + undefined + ] + const yesThrowCase: [string, ...FullTestCase] = [ + `${i + 1}. ${desc}: error`, + c[1], + true, + errMsg ] + return [noThrowCase, yesThrowCase] }, - [[], []] as [FullTestCase[], FullTestCase[]] - ) - - const caseTester: (...args: FullTestCase) => Promise = async ( - _, - files, - shouldThrow, - errMsg - ) => { - const [entrypointFilePath] = objectKeys(files) - - const promise = testCode(files, entrypointFilePath, true, shouldThrow) - if (!shouldThrow || errMsg === undefined) { - return expect(promise).resolves.toEqual(true) - } + async ([files, shouldThrow, errMsg]) => { + const [entrypointFilePath] = objectKeys(files) - const err = await promise - expectDuplicateError(err) + const promise = testCode(files, entrypointFilePath, true, shouldThrow) + if (!shouldThrow || errMsg === undefined) { + return expect(promise).resolves.toEqual(true) + } - // Make sure the locations are always displayed in order - // for consistency across tests (ok since locString should be order agnostic) - const segments = err.locString.split(',').map(each => each.trim()) - segments.sort() + const err = await promise + expectDuplicateError(err) - expect(segments.join(', ')).toEqual(errMsg) - } + // Make sure the locations are always displayed in order + // for consistency across tests (ok since locString should be order agnostic) + const segments = err.locString.split(',').map(each => each.trim()) + segments.sort() - describe(`${desc} with throwOnDuplicateImports false`, () => - test.each(allNoCases)('%s', caseTester)) - describe(`${desc} with throwOnDuplicateImports true`, () => - test.each(allYesCases)('%s', caseTester)) + expect(segments.join(', ')).toEqual(errMsg) + } + ) } testCases('Imports from different modules', [ diff --git a/src/parser/__tests__/general.ts b/src/parser/__tests__/general.ts new file mode 100644 index 000000000..2058584f8 --- /dev/null +++ b/src/parser/__tests__/general.ts @@ -0,0 +1,31 @@ +import { parseError } from '../..' +import { mockContext } from '../../mocks/context' +import { Chapter } from '../../types' +import { MissingSemicolonError } from '../errors' +import { parse } from '../parser' + +describe('Make sure all JavaScript chapters throw errors for missing semicolons', () => { + test.each([Chapter.SOURCE_1, Chapter.SOURCE_2, Chapter.SOURCE_3, Chapter.SOURCE_4])( + '%s', + chapter => { + const context = mockContext(chapter) + const result = parse('42', context) + + expect(result).toBeNull() + expect(context.errors[0]).toBeInstanceOf(MissingSemicolonError) + expect(parseError(context.errors)).toEqual( + 'Line 1: Missing semicolon at the end of statement' + ) + } + ) +}) + +test('parseError for template literals with expressions', () => { + const context = mockContext(Chapter.SOURCE_1) + const result = parse('`${1}`;', context) + + expect(result).toBeNull() + expect(parseError(context.errors)).toEqual( + 'Line 1: Expressions are not allowed in template literals (`multiline strings`)' + ) +}) diff --git a/src/stdlib/__tests__/__snapshots__/lazyLists.ts.snap b/src/stdlib/__tests__/__snapshots__/lazyLists.ts.snap deleted file mode 100644 index dd2af90f7..000000000 --- a/src/stdlib/__tests__/__snapshots__/lazyLists.ts.snap +++ /dev/null @@ -1,109 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`bad index error list_ref: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3), 3);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "native:\\"Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null\\" -interpreted:\\"Line 1: Calling non-function value [object Object].\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`bad index error list_ref: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3), -1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "native:\\"Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null\\" -interpreted:\\"Line 1: Calling non-function value [object Object].\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`bad index error list_ref: expectParsedError 3`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3), 1.5);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "native:\\"Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null\\" -interpreted:\\"Line 1: Calling non-function value [object Object].\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`bad index error list_ref: expectParsedError 4`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3), '1');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "native:\\"Line 149: Expected string on right hand side of operation, got number.\\" -interpreted:\\"Line 1: Calling non-function value [object Object].\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`bad number error build_list: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "build_list(x => x, '1');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 45: Expected number on left hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`bad number error enum_list: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "enum_list('1', '5');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 139: Expected string on right hand side of operation, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`bad number error enum_list: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "enum_list('1', 5);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 139: Expected string on right hand side of operation, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`bad number error enum_list: expectParsedError 3`] = ` -Object { - "alertResult": Array [], - "code": "enum_list(1, '5');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 140: Expected number on right hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/stdlib/__tests__/__snapshots__/stream.ts.snap b/src/stdlib/__tests__/__snapshots__/stream.ts.snap index 6ff17ffc3..4ab231de7 100644 --- a/src/stdlib/__tests__/__snapshots__/stream.ts.snap +++ b/src/stdlib/__tests__/__snapshots__/stream.ts.snap @@ -164,8 +164,10 @@ Object { "displayResult": Array [], "numErrors": 0, "parsedErrors": "", - "result": "native:[1, () => integers_from(n + 1)] -interpreted:undefined", + "result": Array [ + 1, + [Function], + ], "resultStatus": "finished", "visualiseListResult": Array [], } diff --git a/src/stdlib/__tests__/lazyLists.ts b/src/stdlib/__tests__/lazyLists.ts deleted file mode 100644 index ea65b5131..000000000 --- a/src/stdlib/__tests__/lazyLists.ts +++ /dev/null @@ -1,426 +0,0 @@ -import { Chapter, Variant } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { expectParsedError, expectResult } from '../../utils/testing' - -test('pair creates pair', () => { - return expectResult( - stripIndent` - is_pair (pair(1, 'a string ""')); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('head works', () => { - return expectResult( - stripIndent` - head(pair(1, 'a string ""')); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`1`) -}) - -test('tail works', () => { - return expectResult( - stripIndent` - tail(pair(1, 'a string ""')); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`"a string \\"\\""`) -}) - -test('tail of a 1 element list is null', () => { - return expectResult( - stripIndent` - tail(list(1)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`null`) -}) - -test('empty list is null', () => { - return expectResult( - stripIndent` - list(); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot('null') -}) - -test('for_each', () => { - return expectResult( - stripIndent` - let sum = 0; - for_each(x => { - sum = sum + x; - }, list(1, 2, 3)); - sum; - `, - { chapter: Chapter.SOURCE_3, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`6`) -}) - -test('map', () => { - return expectResult( - stripIndent` - equal(map(x => 2 * x, list(12, 11, 3)), list(24, 22, 6)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('filter', () => { - return expectResult( - stripIndent` - equal(filter(x => x <= 4, list(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)), list(2, 1, 3, 4, 2)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('build_list', () => { - return expectResult( - stripIndent` - equal(build_list(x => x * x, 5), list(0, 1, 4, 9, 16)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('reverse', () => { - return expectResult( - stripIndent` - equal(reverse(list("string", "null", "undefined", "null", 123)), list(123, "null", "undefined", "null", "string")); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('append', () => { - return expectResult( - stripIndent` - equal(append(list(123, 123), list(456, 456, 456)), list(123, 123, 456, 456, 456)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('member', () => { - return expectResult( - stripIndent` - equal( - member(4, list(1, 2, 3, 4, 123, 456, 789)), - list(4, 123, 456, 789)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('remove', () => { - return expectResult( - stripIndent` - remove(1, list(1)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`null`) -}) - -test('remove not found', () => { - return expectResult( - stripIndent` - equal (remove(2, list(1)),list(1)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('remove_all', () => { - return expectResult( - stripIndent` - equal(remove_all(1, list(1, 2, 3, 4, 1, 1, 1, 5, 1, 1, 6)), list(2, 3, 4, 5, 6)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('remove_all not found', () => { - return expectResult( - stripIndent` - equal(remove_all(1, list(2, 3, 4)), list(2, 3, 4)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('enum_list', () => { - return expectResult( - stripIndent` - equal(enum_list(1, 5), list(1, 2, 3, 4, 5)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('enum_list with floats', () => { - return expectResult( - stripIndent` - equal(enum_list(1.5, 5), list(1.5, 2.5, 3.5, 4.5)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) - -test('list_ref', () => { - return expectResult( - stripIndent` - list_ref(list(1, 2, 3, "4", 4), 4); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`4`) -}) - -test('accumulate', () => { - return expectResult( - stripIndent` - accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`10`) -}) - -test('list_to_string', () => { - return expectResult( - stripIndent` - list_to_string(list(1, 2, 3)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`"[1,[2,[3,null]]]"`) -}) -test('bad number error build_list', () => { - return expectParsedError( - stripIndent` - build_list(x => x, '1'); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`"Line 45: Expected number on left hand side of operation, got string."`) -}) - -test('bad number error enum_list', () => { - return expectParsedError( - stripIndent` - enum_list('1', '5'); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot( - `"Line 139: Expected string on right hand side of operation, got number."` - ) -}) - -test('bad number error enum_list', () => { - return expectParsedError( - stripIndent` - enum_list('1', 5); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot( - `"Line 139: Expected string on right hand side of operation, got number."` - ) -}) - -test('bad number error enum_list', () => { - return expectParsedError( - stripIndent` - enum_list(1, '5'); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot( - `"Line 140: Expected number on right hand side of operation, got string."` - ) -}) - -test('bad index error list_ref', () => { - return expectParsedError( - stripIndent` - list_ref(list(1, 2, 3), 3); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot( - `"Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null"` - ) -}) - -test('bad index error list_ref', () => { - return expectParsedError( - stripIndent` - list_ref(list(1, 2, 3), -1); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot( - `"Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null"` - ) -}) - -test('bad index error list_ref', () => { - return expectParsedError( - stripIndent` - list_ref(list(1, 2, 3), 1.5); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot( - `"Line 148: Error: tail(xs) expects a pair as argument xs, but encountered null"` - ) -}) - -test('bad index error list_ref', () => { - return expectParsedError( - stripIndent` - list_ref(list(1, 2, 3), '1'); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot( - `"Line 149: Expected string on right hand side of operation, got number."` - ) -}) - -test('arguments are not evaluated for pair', () => { - return expectResult( - stripIndent` - head(pair(1,head(null))); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`1`) -}) -test('arguments are not evaluated for list', () => { - return expectResult( - stripIndent` - head(list(1,head(null))); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`1`) -}) - -test('recursive pair definitions are possible (tail)', () => { - return expectResult( - stripIndent` - const a = pair (1,a); - head(a) + head(tail(a)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`2`) -}) - -test('recursive pair definitions are possible (head)', () => { - return expectResult( - stripIndent` - const a = pair (a,1); - tail(a) + tail(head(a)); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`2`) -}) - -test('recursive list definitions are possible (head)', () => { - return expectResult( - stripIndent` - const a = list (1,a); - head(a) + head(head(tail(a))); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`2`) -}) - -test('is_list on infinite lists works', () => { - return expectResult( - stripIndent` - const a = list(1,a); - is_list(a); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`true`) -}) -test('list_ref on infinite lists', () => { - return expectResult( - stripIndent` - const a = pair(1,a); - list_ref(a,200); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`1`) -}) - -test('map on infinite lists works', () => { - return expectResult( - stripIndent` - const a = pair(1,a); - const b = map(x => 2 * x, a); - list_ref(b,200); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`2`) -}) - -test('map on infinite lists works', () => { - return expectResult( - stripIndent` - const a = pair(1,a); - const b = map(x => 2 * x, a); - list_ref(b,200); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`2`) -}) - -test('append left list is infinite', () => { - return expectResult( - stripIndent` - const a = pair(1,a); - const b = append(a, list(3,4)); - list_ref(b,200); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`1`) -}) - -test('append right list is infinite', () => { - return expectResult( - stripIndent` - const a = pair(1,a); - const b = append(list(3,4),a); - list_ref(b,200); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`1`) -}) - -test('remove on infinite list', () => { - return expectResult( - stripIndent` - const a = pair(1,a); - const b = remove(1,a); - list_ref(b,200); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`1`) -}) - -test('remove all ones on infinite list of ones and twos', () => { - return expectResult( - stripIndent` - const a = pair(1,pair(2,a)); - const b = remove_all(1,a); - list_ref(b,200); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`2`) -}) - -test('filter on infinite lists', () => { - return expectResult( - stripIndent` - const a = pair(1,pair(2,a)); - const b = filter(x => x % 2 === 0,a); - list_ref(b,1); - `, - { chapter: Chapter.SOURCE_2, native: true, variant: Variant.LAZY } - ).toMatchInlineSnapshot(`2`) -}) diff --git a/src/stdlib/__tests__/list.ts b/src/stdlib/__tests__/list.ts index 3da4eeef8..b60dafeb9 100644 --- a/src/stdlib/__tests__/list.ts +++ b/src/stdlib/__tests__/list.ts @@ -9,13 +9,22 @@ test('list creates list', () => { list(1, 'a string ""', () => f, f, true, 3.14); `, { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`[ 1, -[ "a string \"\"", -[ () => f, -[ function f() { - return 1; - }, -[true, [3.14, null]]]]]]`) + ).toMatchInlineSnapshot(` + "native:[ 1, + [ \\"a string \\\\\\"\\\\\\"\\", + [ () => f, + [ function f() { + return 1; + }, + [true, [3.14, null]]]]]] + interpreted:[ 1, + [ \\"a string \\\\\\"\\\\\\"\\", + [ () => f, + [ () => { + return 1; + }, + [true, [3.14, null]]]]]]" + `) }) test('pair creates pair', () => { diff --git a/src/transpiler/__tests__/transpiled-code.ts b/src/transpiler/__tests__/transpiled-code.ts index c76e1f728..52688a80b 100644 --- a/src/transpiler/__tests__/transpiled-code.ts +++ b/src/transpiler/__tests__/transpiled-code.ts @@ -4,7 +4,6 @@ import { Chapter } from '../../types' import * as ast from '../../utils/ast/astCreator' import { sanitizeAST } from '../../utils/testing/sanitizer' import { stripIndent } from '../../utils/formatters' -import { astTester } from '../../utils/testing' import { transformImportDeclarations, transpile } from '../transpiler' /* DO NOT HAVE 'native[]' AS A SUBSTRING IN CODE STRINGS ANYWHERE IN THIS FILE! diff --git a/src/transpiler/transpiler.ts b/src/transpiler/transpiler.ts index 843e313f6..642df9d19 100644 --- a/src/transpiler/transpiler.ts +++ b/src/transpiler/transpiler.ts @@ -49,19 +49,6 @@ export function transformImportDeclarations( return [declNodes, otherNodes] } -export function getGloballyDeclaredIdentifiers(program: es.Program): string[] { - return program.body - .filter(statement => statement.type === 'VariableDeclaration') - .map( - ({ - declarations: { - 0: { id } - }, - kind - }: es.VariableDeclaration) => (id as es.Identifier).name - ) -} - export function getBuiltins(nativeStorage: NativeStorage): es.Statement[] { const builtinsStatements: es.Statement[] = [] nativeStorage.builtins.forEach((_unused, name: string) => { @@ -458,8 +445,8 @@ function transpileToSource( program.body = (importNodes as es.Program['body']).concat(otherNodes) - getGloballyDeclaredIdentifiers(program).forEach(id => - context.nativeStorage.previousProgramsIdentifiers.add(id) + getDeclaredIdentifiers(program).forEach(id => + context.nativeStorage.previousProgramsIdentifiers.add(id.name) ) const newStatements = [ ...getDeclarationsToAccessTranspilerInternals(globalIds), @@ -501,9 +488,7 @@ function transpileToFullJS( getDeclaredIdentifiers(program).forEach(id => context.nativeStorage.previousProgramsIdentifiers.add(id.name) ) - getGloballyDeclaredIdentifiers(program).forEach(id => - context.nativeStorage.previousProgramsIdentifiers.add(id) - ) + const transpiledProgram: es.Program = create.program([ evallerReplacer(create.identifier(NATIVE_STORAGE_ID), new Set()), create.expressionStatement(create.identifier('undefined')), From e71f7647ab610ce592e65ed4a783d92cacbf274f Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 20 May 2024 15:43:03 +0800 Subject: [PATCH 16/65] Update testing utils and move stringify --- .../__tests__/__snapshots__/stringify.ts.snap | 61 +++++++ src/utils/__tests__/stringify-benchmark.ts | 153 ++++++++++++++++++ src/utils/__tests__/stringify.ts | 43 ++--- src/utils/ast/__tests__/helpers.ts | 2 +- src/utils/testing/index.ts | 27 ---- src/utils/testing/testers.ts | 80 +++++++++ 6 files changed, 310 insertions(+), 56 deletions(-) create mode 100644 src/utils/__tests__/__snapshots__/stringify.ts.snap create mode 100644 src/utils/__tests__/stringify-benchmark.ts create mode 100644 src/utils/testing/testers.ts diff --git a/src/utils/__tests__/__snapshots__/stringify.ts.snap b/src/utils/__tests__/__snapshots__/stringify.ts.snap new file mode 100644 index 000000000..fa6a7d840 --- /dev/null +++ b/src/utils/__tests__/__snapshots__/stringify.ts.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`String representation of builtins are nice: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "stringify(pair);", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": "function pair(left, right) { + [implementation hidden] +}", + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`String representation with 1 space indent: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "stringify(parse('x=>x;'), 1);", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": "[\\"lambda_expression\\", +[[[\\"name\\", [\\"x\\", null]], null], +[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`String representation with default (2 space) indent: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "stringify(parse(\\"x=>x;\\"));", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": "[ \\"lambda_expression\\", +[ [[\\"name\\", [\\"x\\", null]], null], +[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; + +exports[`String representation with more than 10 space indent should trim to 10 space indent: expectResult 1`] = ` +Object { + "alertResult": Array [], + "code": "stringify(parse(\\"x=>x;\\"), 100);", + "displayResult": Array [], + "numErrors": 0, + "parsedErrors": "", + "result": "[ \\"lambda_expression\\", +[ [[\\"name\\", [\\"x\\", null]], null], +[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", + "resultStatus": "finished", + "visualiseListResult": Array [], +} +`; diff --git a/src/utils/__tests__/stringify-benchmark.ts b/src/utils/__tests__/stringify-benchmark.ts new file mode 100644 index 000000000..08b5b0447 --- /dev/null +++ b/src/utils/__tests__/stringify-benchmark.ts @@ -0,0 +1,153 @@ +import * as list from '../../stdlib/list' +import { Chapter } from '../../types' +import { stripIndent } from '../formatters' +import { stringify } from '../stringify' +import { testSuccess } from '../testing' + +test('stringify is fast', () => { + return expect( + testSuccess( + stripIndent` + function make_k_list(k, d) { + const degree = k; + const depth = d; + let repeat = 0; + function helper(k, d, to_repeat) { + if (d === 0 && k === 0) { + return null; + } else if (k === 0) { + return helper(degree, d - 1, repeat); + } else { + repeat = pair(to_repeat, helper(k - 1, d, to_repeat)); + return pair(to_repeat, helper(k - 1, d, to_repeat)); + } + } + return helper(k, d, 0); + } + + const bigstructure = make_k_list(2,2); + const start = get_time(); + stringify(bigstructure); + const end = get_time(); + end - start; + `, + { chapter: Chapter.SOURCE_3, native: false } + ).then(testResult => testResult.result) + ).resolves.toBeLessThan(2000) + // This benchmark takes 100ms on my machine, + // but less than 2 seconds should be good enough on the test servers. +}) + +test('display_list with stringify is linear runtime', () => { + const placeholder = Symbol('placeholder') + const noDisplayList = (v: any, s: any = placeholder) => { + if (s !== placeholder && typeof s !== 'string') { + throw new TypeError('display_list expects the second argument to be a string') + } + return stringify(list.rawDisplayList((x: any) => x, v, s === placeholder ? undefined : s)) + } + + return expect( + testSuccess( + stripIndent` + const build_inf = (i, f) => { + const t = list(f(i)); + let p = t; + for (let n = i - 1; n >= 0; n = n - 1) { + p = pair(f(n), p); + } + set_tail(t, p); + return p; + }; + const make_complex_list = n => { + // makes a complex list structure with O(n) pairs + const cuberootn = math_floor(math_pow(n, 0.33)); + return build_list(cuberootn, _ => build_inf(cuberootn, _ => build_list(cuberootn, i =>i))); + }; + const time_display_list = xs => { + const starttime = get_time(); + no_display_list(xs); + return get_time() - starttime; + }; + + // Warm up + time_display_list(make_complex_list(5000)); + + // measure + const ns = [ + // 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000 + // ^-- times 3 + 14000, 10000, 20000, 15000, 17000, 11000, 13000, 12000, 15000, 12000, 19000, 10000, 13000, 14000, 12000, 18000, 17000, 13000, 19000, 16000, 18000, 18000, 20000, 20000, 16000, 11000, 16000, 10000, 17000, 15000, 19000, 11000, 14000 + ]; + const xvalues = []; + const yvalues = []; + for (let i = 0; i < array_length(ns); i = i + 1) { + const xs = make_complex_list(ns[i]); + const t = time_display_list(xs); + xvalues[i] = math_log(ns[i]); + yvalues[i] = math_log(t); + } + + // linear regression adapted from https://dracoblue.net/dev/linear-least-squares-in-javascript/ + function findLineByLeastSquares(values_x, values_y) { + let sum_x = 0; + let sum_y = 0; + let sum_xy = 0; + let sum_xx = 0; + let count = 0; + + /* + * We'll use those variables for faster read/write access. + */ + let x = 0; + let y = 0; + let values_length = array_length(values_x); + + /* + * Nothing to do. + */ + if (values_length === 0) { + return [ [], [] ]; + } else {} + + /* + * Calculate the sum for each of the parts necessary. + */ + for (let v = 0; v < values_length; v = v + 1) { + x = values_x[v]; + y = values_y[v]; + sum_x = sum_x + x; + sum_y = sum_y + y; + sum_xx = sum_xx + x*x; + sum_xy = sum_xy + x*y; + count = count + 1; + } + + /* + * Calculate m and b for the formular: + * y = x * m + b + */ + let m = (count*sum_xy - sum_x*sum_y) / (count*sum_xx - sum_x*sum_x); + let b = (sum_y/count) - (m*sum_x)/count; + + return pair(m, b); + } + + // best fit + const line = findLineByLeastSquares(xvalues, yvalues); + const slope = head(line); + slope; + `, + { + chapter: Chapter.SOURCE_3, + native: false, // we're measuring a builtin, no need for native + testBuiltins: { + no_display_list: noDisplayList + } + } + ).then(testResult => testResult.result) + ).resolves.toBeLessThan(1.2) + // estimated power is less than 1.2 + // means it's probably near 1 + // => probably linear? +}, 1000000) diff --git a/src/utils/__tests__/stringify.ts b/src/utils/__tests__/stringify.ts index b7c670053..66f11c9e3 100644 --- a/src/utils/__tests__/stringify.ts +++ b/src/utils/__tests__/stringify.ts @@ -2,11 +2,10 @@ import { list, set_tail, tail } from '../../stdlib/list' import { Chapter, type Value } from '../../types' import { stripIndent } from '../formatters' import { lineTreeToString, stringDagToLineTree, stringify, valueToStringDag } from '../stringify' -import { testCases } from '../testing/caseTesters' +import { expectResult } from '../testing' +import { testMultipleCases } from '../testing/testers' -import '../testing' - -type TestCase = [string, Value, string] +type TestCase = [desc: string, valueToStringify: Value, expected: string] const cases: TestCase[] = [ // Primitives ['String representation of numbers are nice', 0, '0'], @@ -300,8 +299,8 @@ const cases: TestCase[] = [ ] ] -testCases(cases, (value, expected) => { - return expect(stringify(value)).toEqual(expected) +testMultipleCases(cases, ([value, expected]) => { + expect(stringify(value)).toEqual(expected) }) // Test cases that are a little bit more complicated @@ -345,10 +344,7 @@ test('String representation of objects with toReplString member calls toReplStri }) test('String representation of builtins are nice', () => { - return expect({ - code: `stringify(pair);`, - chapter: Chapter.SOURCE_2 - }).toEvaluateToValue( + return expectResult(`stringify(pair);`, { chapter: Chapter.SOURCE_2 }).toEqual( stripIndent` function pair(left, right) { [implementation hidden] @@ -357,10 +353,7 @@ test('String representation of builtins are nice', () => { }) test('String representation with 1 space indent', () => { - return expect({ - code: "stringify(parse('x=>x;'), 1);", - chapter: Chapter.SOURCE_4 - }).toEvaluateToValue(` + return expectResult("stringify(parse('x=>x;'), 1);", { chapter: Chapter.SOURCE_4 }).toEqual(` ["lambda_expression", [[["name", ["x", null]], null], [["return_statement", [["name", ["x", null]], null]], null]]] @@ -368,23 +361,17 @@ test('String representation with 1 space indent', () => { }) test('String representation with default (2 space) indent', () => { - return expect({ - code: 'stringify(parse("x=>x;"));', - chapter: Chapter.SOURCE_4 - }).toEvaluateToValue(` - [ "lambda_expression", - [ [["name", ["x", null]], null], - [["return_statement", [["name", ["x", null]], null]], null]]]`) + return expectResult('stringify(parse("x=>x;"));', { chapter: Chapter.SOURCE_4 }).toEqual(` + [ "lambda_expression", + [ [["name", ["x", null]], null], + [["return_statement", [["name", ["x", null]], null]], null]]]`) }) test('String representation with more than 10 space indent should trim to 10 space indent', () => { - return expect({ - code: 'stringify(parse("x=>x;"), 100);', - chapter: Chapter.SOURCE_4 - }).toEvaluateToValue(` - [ "lambda_expression", - [ [["name", ["x", null]], null], - [["return_statement", [["name", ["x", null]], null]], null]]] + return expectResult('stringify(parse("x=>x;"), 100);', { chapter: Chapter.SOURCE_4 }).toEqual(` +[ "lambda_expression", + [ [["name", ["x", null]], null], + [["return_statement", [["name", ["x", null]], null]], null]]] `) }) diff --git a/src/utils/ast/__tests__/helpers.ts b/src/utils/ast/__tests__/helpers.ts index edf5069ff..056c52928 100644 --- a/src/utils/ast/__tests__/helpers.ts +++ b/src/utils/ast/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type { VariableDeclaration } from 'estree' -import { astTester } from '../../testing' +import { astTester } from '../../testing/testers' import { getDeclaredIdentifiers, getIdentifiersFromVariableDeclaration, diff --git a/src/utils/testing/index.ts b/src/utils/testing/index.ts index b68911126..04760da3a 100644 --- a/src/utils/testing/index.ts +++ b/src/utils/testing/index.ts @@ -1,7 +1,6 @@ import { generate } from 'astring' import type { MockedFunction } from 'jest-mock' -import type { Program } from 'estree' import createContext, { defineBuiltin } from '../../createContext' import { transpileToGPU } from '../../gpu/gpu' import { parseError, runInContext } from '../..' @@ -19,7 +18,6 @@ import { type Finished, type Result } from '../../types' -import { mockContext } from '../../mocks/context' import { stringify } from '../stringify' export interface CodeSnippetTestCase { @@ -322,28 +320,3 @@ export function expectFinishedResult(result: Result): asserts result is Finished export function expectTrue(cond: boolean): asserts cond { expect(cond).toEqual(true) } - -/** - * Convenience function for testing the expected output of parsing - * a single line of code - */ -export function astTester( - func: (prog: Program, context: Context, expectedError: ExpectedError | undefined) => void, - testCases: ( - | [desc: string, code: string] - | [desc: string, code: string, expectedError: ExpectedError] - )[], - chapter: Chapter = Chapter.SOURCE_4 -) { - const fullCases = testCases.map(([desc, code, err]) => { - const context = mockContext(chapter) - const program = parse(code, context) - if (!program) { - throw context.errors[0] - } - - return [desc, program, context, err] as [string, Program, Context, ExpectedError | undefined] - }) - - test.each(fullCases)('%s', (_, ...args) => func(...args)) -} diff --git a/src/utils/testing/testers.ts b/src/utils/testing/testers.ts new file mode 100644 index 000000000..86a6cfefd --- /dev/null +++ b/src/utils/testing/testers.ts @@ -0,0 +1,80 @@ +import type { Program } from 'estree' +import { Chapter, type Context } from '../../types' +import { mockContext } from '../../mocks/context' +import { parse } from '../../parser/parser' + +export type TestCase> = [string, ...T] + +/** + * Convenience wrapper for testing multiple cases with the same + * test function + */ +export function testMultipleCases>( + cases: [string, ...T][], + tester: (args: T, i: number) => void | Promise, + includeIndex?: boolean, + timeout?: number +) { + const withIndex = cases.map(([desc, ...c], i) => { + const newDesc = includeIndex ? `${i + 1}. ${desc}` : desc + return [newDesc, i, ...c] as [string, number, ...T] + }) + test.each(withIndex)('%s', (_, i, ...args) => tester(args, i), timeout) +} + +/** + * Used when something has to be tested with two different values + */ +export function testTrueAndFalseCases( + desc: string, + varName: string, + cases: T[], + mapper: (c: T, i: number) => [[string, ...U], [string, ...U]], + tester: (args: U, i: number) => void | Promise, + includeIndex?: boolean, + timeout?: number +) { + const [trueCases, falseCases] = cases.reduce( + ([trueRes, falseRes], c, i) => { + const [trueCase, falseCase] = mapper(c, i) + return [ + [...trueRes, trueCase], + [...falseRes, falseCase] + ] + }, + [[], []] + ) + + describe(`${desc} with ${varName}: true`, () => + testMultipleCases(trueCases, tester, includeIndex, timeout)) + describe(`${desc} with ${varName}: false`, () => + testMultipleCases(falseCases, tester, includeIndex, timeout)) +} + +/** + * Convenience function for testing the expected output of running + * a single line of code + */ + +export function astTester( + func: (prog: Program, context: Context, expectedError: ExpectedError | undefined) => void, + testCases: ( + | [desc: string, code: string] + | [desc: string, code: string, expectedError: ExpectedError] + )[], + chapter: Chapter = Chapter.SOURCE_4, + includeIndex?: boolean, + timeout?: number +) { + const fullCases = testCases.map(([desc, code, err]) => { + const context = mockContext(chapter) + const program = parse(code, context) + if (!program) { + throw context.errors[0] + } + + return [desc, program, context, err] as [string, Program, Context, ExpectedError | undefined] + }) + + testMultipleCases(fullCases, args => func(...args), includeIndex, timeout) +} From d9a01a844357037bdfbd6fcac453823eafb5843e Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 20 May 2024 15:46:55 +0800 Subject: [PATCH 17/65] Remove lazy --- src/constants.ts | 2 - src/createContext.ts | 60 ++---------- src/lazy/lazy.ts | 125 ------------------------- src/repl/transpiler.ts | 6 +- src/runner/sourceRunner.ts | 9 -- src/stdlib/lazyList.prelude.ts | 164 --------------------------------- src/types.ts | 1 - src/utils/operators.ts | 47 ---------- src/utils/testing/index.ts | 5 - 9 files changed, 11 insertions(+), 408 deletions(-) delete mode 100644 src/lazy/lazy.ts delete mode 100644 src/stdlib/lazyList.prelude.ts diff --git a/src/constants.ts b/src/constants.ts index e33dbcc44..43cda0011 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -31,10 +31,8 @@ export const sourceLanguages: Language[] = [ { chapter: Chapter.SOURCE_1, variant: Variant.DEFAULT }, { chapter: Chapter.SOURCE_1, variant: Variant.TYPED }, { chapter: Chapter.SOURCE_1, variant: Variant.WASM }, - { chapter: Chapter.SOURCE_1, variant: Variant.LAZY }, { chapter: Chapter.SOURCE_2, variant: Variant.DEFAULT }, { chapter: Chapter.SOURCE_2, variant: Variant.TYPED }, - { chapter: Chapter.SOURCE_2, variant: Variant.LAZY }, { chapter: Chapter.SOURCE_3, variant: Variant.DEFAULT }, { chapter: Chapter.SOURCE_3, variant: Variant.TYPED }, { chapter: Chapter.SOURCE_3, variant: Variant.CONCURRENT }, diff --git a/src/createContext.ts b/src/createContext.ts index 2f5ab7dca..d04bf0836 100644 --- a/src/createContext.ts +++ b/src/createContext.ts @@ -13,7 +13,6 @@ import { call_with_current_continuation } from './cse-machine/continuations' import Heap from './cse-machine/heap' import * as gpu_lib from './gpu/lib' import { AsyncScheduler } from './schedulers' -import { lazyListPrelude } from './stdlib/lazyList.prelude' import * as list from './stdlib/list' import { list_to_vector } from './stdlib/list' import { listPrelude } from './stdlib/list.prelude' @@ -34,20 +33,10 @@ import { Value, Variant } from './types' -import { makeWrapper } from './utils/makeWrapper' import * as operators from './utils/operators' import { stringify } from './utils/stringify' import { schemeVisualise } from './alt-langs/scheme/scheme-mapper' -export class LazyBuiltIn { - func: (...arg0: any) => any - evaluateArgs: boolean - constructor(func: (...arg0: any) => any, evaluateArgs: boolean) { - this.func = func - this.evaluateArgs = evaluateArgs - } -} - export class EnvTree { private _root: EnvTreeNode | null = null private map = new Map() @@ -259,16 +248,6 @@ export function defineBuiltin( value.funParameters = funParameters defineSymbol(context, funName, value) - } else if (value instanceof LazyBuiltIn) { - const wrapped = (...args: any) => value.func(...args) - const funName = extractName(name) - const funParameters = extractParameters(name) - const repr = `function ${name} {\n\t[implementation hidden]\n}` - wrapped.toString = () => repr - wrapped.funName = funName - wrapped.funParameters = funParameters - makeWrapper(value.func, wrapped) - defineSymbol(context, funName, new LazyBuiltIn(wrapped, value.evaluateArgs)) } else { defineSymbol(context, name, value) } @@ -359,27 +338,15 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn if (context.chapter >= 2) { // List library - - if (context.variant === Variant.LAZY) { - defineBuiltin(context, 'pair(left, right)', new LazyBuiltIn(list.pair, false)) - defineBuiltin(context, 'list(...values)', new LazyBuiltIn(list.list, false), 0) - defineBuiltin(context, 'is_pair(val)', new LazyBuiltIn(list.is_pair, true)) - defineBuiltin(context, 'head(xs)', new LazyBuiltIn(list.head, true)) - defineBuiltin(context, 'tail(xs)', new LazyBuiltIn(list.tail, true)) - defineBuiltin(context, 'is_null(val)', new LazyBuiltIn(list.is_null, true)) - defineBuiltin(context, 'draw_data(...xs)', new LazyBuiltIn(visualiseList, true), 1) - defineBuiltin(context, 'is_list(val)', new LazyBuiltIn(list.is_list, true)) - } else { - defineBuiltin(context, 'pair(left, right)', list.pair) - defineBuiltin(context, 'is_pair(val)', list.is_pair) - defineBuiltin(context, 'head(xs)', list.head) - defineBuiltin(context, 'tail(xs)', list.tail) - defineBuiltin(context, 'is_null(val)', list.is_null) - defineBuiltin(context, 'list(...values)', list.list, 0) - defineBuiltin(context, 'draw_data(...xs)', visualiseList, 1) - defineBuiltin(context, 'display_list(val, prepend = undefined)', displayList, 0) - defineBuiltin(context, 'is_list(val)', list.is_list) - } + defineBuiltin(context, 'pair(left, right)', list.pair) + defineBuiltin(context, 'is_pair(val)', list.is_pair) + defineBuiltin(context, 'head(xs)', list.head) + defineBuiltin(context, 'tail(xs)', list.tail) + defineBuiltin(context, 'is_null(val)', list.is_null) + defineBuiltin(context, 'list(...values)', list.list, 0) + defineBuiltin(context, 'draw_data(...xs)', visualiseList, 1) + defineBuiltin(context, 'display_list(val, prepend = undefined)', displayList, 0) + defineBuiltin(context, 'is_list(val)', list.is_list) } if (context.chapter >= 3) { @@ -440,13 +407,6 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn ) } - if (context.variant === Variant.LAZY) { - defineBuiltin(context, 'wrapLazyCallee(f)', new LazyBuiltIn(operators.wrapLazyCallee, true)) - defineBuiltin(context, 'makeLazyFunction(f)', new LazyBuiltIn(operators.makeLazyFunction, true)) - defineBuiltin(context, 'forceIt(val)', new LazyBuiltIn(operators.forceIt, true)) - defineBuiltin(context, 'delayIt(xs)', new LazyBuiltIn(operators.delayIt, true)) - } - if (context.chapter <= +Chapter.SCHEME_1 && context.chapter >= +Chapter.FULL_SCHEME) { switch (context.chapter) { case Chapter.FULL_SCHEME: @@ -842,7 +802,7 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn function importPrelude(context: Context) { let prelude = '' if (context.chapter >= 2) { - prelude += context.variant === Variant.LAZY ? lazyListPrelude : listPrelude + prelude += listPrelude prelude += localImportPrelude } if (context.chapter >= 3) { diff --git a/src/lazy/lazy.ts b/src/lazy/lazy.ts deleted file mode 100644 index 8d02437ef..000000000 --- a/src/lazy/lazy.ts +++ /dev/null @@ -1,125 +0,0 @@ -import * as es from 'estree' - -import * as create from '../utils/ast/astCreator' -import { getIdentifiersInProgram } from '../utils/uniqueIds' -import { simple } from '../utils/walkers' - -const lazyPrimitives = new Set(['makeLazyFunction', 'wrapLazyCallee', 'forceIt', 'delayIt']) - -const forcingNodes = new Set(['BinaryExpression', 'UnaryExpression']) - -function transformFunctionDeclarationsToArrowFunctions(program: es.Program) { - simple(program, { - FunctionDeclaration(node) { - const { id, params, body } = node as es.FunctionDeclaration - if (id === null) { - throw new Error( - 'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.' - ) - } - node.type = 'VariableDeclaration' - node = node as es.VariableDeclaration - const asArrowFunction = create.callExpression( - create.identifier('makeLazyFunction', node.loc), - [create.blockArrowFunction(params as es.Identifier[], body, node.loc)], - node.loc - ) - node.declarations = [ - { - type: 'VariableDeclarator', - id, - init: asArrowFunction - } - ] - node.kind = 'const' - } - }) -} - -function insertDelayAndForce(program: es.Program) { - function transformConditionals( - node: - | es.IfStatement - | es.ConditionalExpression - | es.LogicalExpression - | es.ForStatement - | es.WhileStatement - ) { - const test = node.type === 'LogicalExpression' ? 'left' : 'test' - if (forcingNodes.has(node[test].type)) { - return - } - node[test] = create.callExpression(create.identifier('forceIt'), [node[test]], node.loc) - } - - simple(program, { - BinaryExpression(node: es.BinaryExpression) { - node.left = create.callExpression( - create.identifier('forceIt'), - [node.left as es.Expression], - node.left.loc - ) - node.right = create.callExpression( - create.identifier('forceIt'), - [node.right as es.Expression], - node.right.loc - ) - }, - UnaryExpression(node: es.UnaryExpression) { - node.argument = create.callExpression( - create.identifier('forceIt'), - [node.argument as es.Expression], - node.argument.loc - ) - }, - IfStatement: transformConditionals, - ConditionalExpression: transformConditionals, - LogicalExpression: transformConditionals, - ForStatement: transformConditionals, - WhileStatement: transformConditionals, - CallExpression(node: es.CallExpression) { - if (node.callee.type === 'Identifier' && lazyPrimitives.has(node.callee.name)) { - return - } - node.callee = create.callExpression( - create.identifier('wrapLazyCallee', node.callee.loc), - [node.callee as es.Expression], - node.callee.loc - ) - node.arguments = node.arguments.map(arg => - create.callExpression( - create.identifier('delayIt'), - [create.arrowFunctionExpression([], arg as es.Expression, arg.loc)], - arg.loc - ) - ) - } - }) -} - -// transpiles if possible and modifies program to a Source program that makes use of lazy primitives -export function transpileToLazy(program: es.Program) { - const identifiers = getIdentifiersInProgram(program) - if (identifiers.has('forceIt') || identifiers.has('delayIt')) { - program.body.unshift( - create.expressionStatement( - create.callExpression( - create.identifier('display'), - [ - create.literal( - 'Manual use of lazy library detected, turning off automatic lazy evaluation transformation.' - ) - ], - { - start: { line: 0, column: 0 }, - end: { line: 0, column: 0 } - } - ) - ) - ) - return - } - - transformFunctionDeclarationsToArrowFunctions(program) - insertDelayAndForce(program) -} diff --git a/src/repl/transpiler.ts b/src/repl/transpiler.ts index efa4d654e..b59484958 100644 --- a/src/repl/transpiler.ts +++ b/src/repl/transpiler.ts @@ -6,7 +6,6 @@ import { generate } from 'astring' import { transpileToGPU } from '../gpu/gpu' import { createContext, parseError } from '../index' -import { transpileToLazy } from '../lazy/lazy' import defaultBundler from '../modules/preprocessor/bundler' import parseProgramsAndConstructImportGraph from '../modules/preprocessor/linker' import { transpile } from '../transpiler/transpiler' @@ -20,7 +19,7 @@ import { export const transpilerCommand = new Command('transpiler') .addOption( - getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.GPU, Variant.LAZY, Variant.NATIVE]) + getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.GPU, Variant.NATIVE]) ) .addOption(getChapterOption(Chapter.SOURCE_4, chapterParser)) .option( @@ -67,9 +66,6 @@ export const transpilerCommand = new Command('transpiler') case Variant.GPU: transpileToGPU(bundledProgram) break - case Variant.LAZY: - transpileToLazy(bundledProgram) - break } const transpiled = opts.pretranspile diff --git a/src/runner/sourceRunner.ts b/src/runner/sourceRunner.ts index de4f2b1b7..fea46f22d 100644 --- a/src/runner/sourceRunner.ts +++ b/src/runner/sourceRunner.ts @@ -11,7 +11,6 @@ import { TimeoutError } from '../errors/timeoutErrors' import { transpileToGPU } from '../gpu/gpu' import { isPotentialInfiniteLoop } from '../infiniteLoops/errors' import { testForInfiniteLoop } from '../infiniteLoops/runtime' -import { transpileToLazy } from '../lazy/lazy' import preprocessFileImports from '../modules/preprocessor' import { defaultAnalysisOptions } from '../modules/preprocessor/analyzer' import { defaultLinkerOptions } from '../modules/preprocessor/linker' @@ -26,7 +25,6 @@ import { import { sandboxedEval } from '../transpiler/evalContainer' import { transpile } from '../transpiler/transpiler' import { Chapter, type Context, type RecursivePartial, Variant } from '../types' -import { forceIt } from '../utils/operators' import { validateAndAnnotate } from '../validator/validator' import { compileForConcurrent } from '../vm/svml-compiler' import { runWithProgram } from '../vm/svml-machine' @@ -136,18 +134,11 @@ async function runNative( case Variant.GPU: transpileToGPU(transpiledProgram) break - case Variant.LAZY: - transpileToLazy(transpiledProgram) - break } ;({ transpiled, sourceMapJson } = transpile(transpiledProgram, context)) let value = sandboxedEval(transpiled, context.nativeStorage) - if (context.variant === Variant.LAZY) { - value = forceIt(value) - } - if (!options.isPrelude) { isPreviousCodeTimeoutError = false } diff --git a/src/stdlib/lazyList.prelude.ts b/src/stdlib/lazyList.prelude.ts deleted file mode 100644 index 301c4d024..000000000 --- a/src/stdlib/lazyList.prelude.ts +++ /dev/null @@ -1,164 +0,0 @@ -export const lazyListPrelude = ` - -// equal computes the structural equality -// over its arguments - -function equal(xs, ys) { - return is_pair(xs) - ? (is_pair(ys) && - equal(head(xs), head(ys)) && - equal(tail(xs), tail(ys))) - : is_null(xs) - ? is_null(ys) - : is_number(xs) - ? (is_number(ys) && xs === ys) - : is_string(xs) - ? (is_string(ys) && xs === ys) - : xs === ys; -} - -// returns the length of a given argument list -// assumes that the argument is a list - -function length(xs) { - return is_null(xs) ? 0 : 1 + length(tail(xs)); -} - -// map applies first arg f, assumed to be a unary function, -// to the elements of the second argument, assumed to be a list. -// f is applied element-by-element: -// map(f, list(1, 2)) results in list(f(1), f(2)) - -function map(f, xs) { - return is_null(xs) ? null : pair(f(head(xs)), map(f, tail(xs))); -} - -// build_list takes a nonnegative integer n as first argument, -// and a function fun as second argument. -// build_list returns a list of n elements, that results from -// applying fun to the numbers from 0 to n-1. - -function build_list(fun, n) { - function build(i, fun, already_built) { - return i < 0 ? already_built : build(i - 1, fun, pair(fun(i), already_built)); - } - return build(n - 1, fun, null); -} - -// for_each applies first arg fun, assumed to be a unary function, -// to the elements of the second argument, assumed to be a list. -// fun is applied element-by-element: -// for_each(fun, list(1, 2)) results in the calls fun(1) and fun(2). -// for_each returns true. - -function for_each(fun, xs) { - if (is_null(xs)) { - return true; - } else { - fun(head(xs)); - return for_each(fun, tail(xs)); - } -} - -// list_to_string returns a string that represents the argument list. -// It applies itself recursively on the elements of the given list. -// When it encounters a non-list, it applies to_string to it. - -function list_to_string(xs) { - return is_null(xs) - ? "null" - : is_pair(xs) - ? "[" + list_to_string(head(xs)) + "," + - list_to_string(tail(xs)) + "]" - : stringify(xs); -} - -// reverse reverses the argument, assumed to be a list - -function reverse(xs) { - function rev(original, reversed) { - return is_null(original) ? reversed : rev(tail(original), pair(head(original), reversed)); - } - return rev(xs, null); -} - -// append first argument, assumed to be a list, to the second argument. -// In the result null at the end of the first argument list -// is replaced by the second argument, regardless what the second -// argument consists of. - -function append(xs, ys) { - return is_null(xs) ? ys : pair(head(xs), append(tail(xs), ys)); -} - -// member looks for a given first-argument element in the -// second argument, assumed to be a list. It returns the first -// postfix sublist that starts with the given element. It returns null if the -// element does not occur in the list - -function member(v, xs) { - return is_null(xs) ? null : v === head(xs) ? xs : member(v, tail(xs)); -} - -// removes the first occurrence of a given first-argument element -// in second-argument, assmed to be a list. Returns the original -// list if there is no occurrence. - -function remove(v, xs) { - return is_null(xs) ? null : v === head(xs) ? tail(xs) : pair(head(xs), remove(v, tail(xs))); -} - -// Similar to remove, but removes all instances of v -// instead of just the first - -function remove_all(v, xs) { - return is_null(xs) - ? null - : v === head(xs) - ? remove_all(v, tail(xs)) - : pair(head(xs), remove_all(v, tail(xs))); -} - -// filter returns the sublist of elements of the second argument -// (assumed to be a list), for which the given predicate function -// returns true. - -function filter(pred, xs) { - return is_null(xs) - ? xs - : pred(head(xs)) - ? pair(head(xs), filter(pred, tail(xs))) - : filter(pred, tail(xs)); -} - -// enumerates numbers starting from start, assumed to be a number, -// using a step size of 1, until the number exceeds end, assumed -// to be a number - -function enum_list(start, end) { - const newStart = start + 1; - return start > end ? null : pair(start, enum_list(newStart, end)); -} - -// Returns the item in xs (assumed to be a list) at index n, -// assumed to be a nonnegative integer. -// Note: the first item is at position 0 - -function list_ref(xs, n) { - const rest = tail(xs); - return n === 0 ? head(xs) : list_ref(rest, n - 1); -} - -// accumulate applies an operation op (assumed to be a binary function) -// to elements of sequence (assumed to be a list) in a right-to-left order. -// first apply op to the last element and initial, resulting in r1, then to -// the second-last element and r1, resulting in r2, etc, and finally -// to the first element and r_n-1, where n is the length of the -// list. -// accumulate(op, zero, list(1, 2, 3)) results in -// op(1, op(2, op(3, zero))) - -function accumulate(f, initial, xs) { - return is_null(xs) ? initial : f(head(xs), accumulate(f, initial, tail(xs))); -} -` diff --git a/src/types.ts b/src/types.ts index 71a5dfea4..179650388 100644 --- a/src/types.ts +++ b/src/types.ts @@ -94,7 +94,6 @@ export enum Variant { TYPED = 'typed', NATIVE = 'native', WASM = 'wasm', - LAZY = 'lazy', NON_DET = 'non-det', CONCURRENT = 'concurrent', GPU = 'gpu', diff --git a/src/utils/operators.ts b/src/utils/operators.ts index 2d9e1d270..65b8465db 100644 --- a/src/utils/operators.ts +++ b/src/utils/operators.ts @@ -1,6 +1,5 @@ import type { BinaryOperator, UnaryOperator } from 'estree' -import { LazyBuiltIn } from '../createContext' import { CallingNonFunctionValue, ExceptionError, @@ -59,33 +58,6 @@ export function delayIt(f: () => any): Thunk { } } -export function wrapLazyCallee(candidate: any) { - candidate = forceIt(candidate) - if (typeof candidate === 'function') { - const wrapped: any = (...args: any[]) => candidate(...args.map(forceIt)) - makeWrapper(candidate, wrapped) - wrapped[Symbol.toStringTag] = () => candidate.toString() - wrapped.toString = () => candidate.toString() - return wrapped - } else if (candidate instanceof LazyBuiltIn) { - if (candidate.evaluateArgs) { - const wrapped: any = (...args: any[]) => candidate.func(...args.map(forceIt)) - makeWrapper(candidate.func, wrapped) - wrapped[Symbol.toStringTag] = () => candidate.toString() - wrapped.toString = () => candidate.toString() - return wrapped - } else { - return candidate - } - } - // doesn't look like a function, not our business to error now - return candidate -} - -export function makeLazyFunction(candidate: any) { - return new LazyBuiltIn(candidate, false) -} - export function callIfFuncAndRightArgs( candidate: any, line: number, @@ -125,20 +97,6 @@ export function callIfFuncAndRightArgs( throw error } } - } else if (candidate instanceof LazyBuiltIn) { - try { - if (candidate.evaluateArgs) { - args = args.map(forceIt) - } - return candidate.func(...args) - } catch (error) { - // if we already handled the error, simply pass it on - if (!(error instanceof RuntimeSourceError || error instanceof ExceptionError)) { - throw new ExceptionError(error, dummy.loc) - } else { - throw error - } - } } else { throw new CallingNonFunctionValue(candidate, dummy) } @@ -275,11 +233,6 @@ export const callIteratively = (f: any, nativeStorage: NativeStorage, ...args: a hasVarArgs ) } - } else if (f instanceof LazyBuiltIn) { - if (f.evaluateArgs) { - args = args.map(forceIt) - } - f = f.func } else { throw new CallingNonFunctionValue(f, dummy) } diff --git a/src/utils/testing/index.ts b/src/utils/testing/index.ts index 04760da3a..05bcce197 100644 --- a/src/utils/testing/index.ts +++ b/src/utils/testing/index.ts @@ -4,7 +4,6 @@ import type { MockedFunction } from 'jest-mock' import createContext, { defineBuiltin } from '../../createContext' import { transpileToGPU } from '../../gpu/gpu' import { parseError, runInContext } from '../..' -import { transpileToLazy } from '../../lazy/lazy' import type { ImportOptions } from '../../modules/moduleTypes' import { parse } from '../../parser/parser' import { transpile } from '../../transpiler/transpiler' @@ -146,10 +145,6 @@ async function testInContext(code: string, options: TestOptions): Promise Date: Tue, 21 May 2024 12:57:14 +0800 Subject: [PATCH 18/65] Separated disallowed syntax tests from rule tests --- .../__snapshots__/disallowed-syntax.ts.snap | 1436 ----------------- src/parser/__tests__/disallowed-syntax.ts | 1353 ++-------------- src/parser/errors.ts | 2 +- src/parser/source/index.ts | 6 +- src/parser/source/rules/__tests__/rules.ts | 57 + src/parser/source/rules/bracesAroundFor.ts | 15 +- src/parser/source/rules/bracesAroundIfElse.ts | 15 +- src/parser/source/rules/bracesAroundWhile.ts | 13 +- .../rules/forStatementMustHaveAllParts.ts | 12 +- src/parser/source/rules/index.ts | 3 +- src/parser/source/rules/noDeclareMutable.ts | 10 +- src/parser/source/rules/noDotAbbreviation.ts | 16 +- src/parser/source/rules/noEval.ts | 6 +- .../noExportNamedDeclarationWithDefault.ts | 12 +- .../noFunctionDeclarationWithoutIdentifier.ts | 10 +- src/parser/source/rules/noHolesInArrays.ts | 6 +- src/parser/source/rules/noIfWithoutElse.ts | 16 +- .../rules/noImplicitDeclareUndefined.ts | 13 +- .../source/rules/noImplicitReturnUndefined.ts | 9 +- .../rules/noImportSpecifierWithDefault.ts | 13 +- src/parser/source/rules/noNull.ts | 6 +- src/parser/source/rules/noSpreadInArray.ts | 3 +- .../source/rules/noTemplateExpression.ts | 3 +- src/parser/source/rules/noTypeofOperator.ts | 7 +- .../source/rules/noUnspecifiedLiteral.ts | 6 +- .../source/rules/noUnspecifiedOperator.ts | 3 +- src/parser/source/rules/noUpdateAssignment.ts | 3 +- src/parser/source/rules/noVar.ts | 3 +- .../source/rules/singleVariableDeclaration.ts | 8 +- src/parser/source/rules/strictEquality.ts | 14 +- src/parser/source/syntax.ts | 4 +- src/parser/types.ts | 20 +- 32 files changed, 391 insertions(+), 2712 deletions(-) delete mode 100644 src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap create mode 100644 src/parser/source/rules/__tests__/rules.ts diff --git a/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap b/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap deleted file mode 100644 index efa6ca0d2..000000000 --- a/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap +++ /dev/null @@ -1,1436 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Cannot have if without else in chapter <= 2 - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -if (true) { 5; }", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing \\"else\\" in \\"if-else\\" statement. -This \\"if\\" block requires corresponding \\"else\\" block which will be -evaluated when true expression evaluates to false. - -Later in the course we will lift this restriction and allow \\"if\\" without -else. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot have if without else in chapter <= 2: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "if (true) { 5; }", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing \\"else\\" in \\"if-else\\" statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot have incomplete statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -5", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 1: Missing semicolon at the end of statement -Every statement must be terminated by a semicolon. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot have incomplete statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "5", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing semicolon at the end of statement", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank expressions in for loop - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (;;) { -break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing init, test, update expressions in for statement. -This for statement requires all three parts (initialiser, test, update) to be present. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank expressions in for loop: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (;;) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing init, test, update expressions in for statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank init in for loop - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (; i < 3; i = i + 1) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing init expression in for statement. -This for statement requires all three parts (initialiser, test, update) to be present. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank init in for loop: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (; i < 3; i = i + 1) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing init expression in for statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank test in for loop - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i = 0; ; i = i + 1) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing test expression in for statement. -This for statement requires all three parts (initialiser, test, update) to be present. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank test in for loop: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; ; i = i + 1) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing test expression in for statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank update in for loop - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i = 0; i < 3;) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing update expression in for statement. -This for statement requires all three parts (initialiser, test, update) to be present. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank update in for loop: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; i < 3;) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing update expression in for statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave while loop predicate blank - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -while () { - x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 7: SyntaxError: Unexpected token (2:7) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave while loop predicate blank: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "while () { - x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: Unexpected token (1:7)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use destructuring declarations - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = [1, 2]; -let [a, b] = x; -a;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 4: Array patterns are not allowed -You are trying to use Array patterns, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use destructuring declarations: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2]; -let [a, b] = x; -a;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Array patterns are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use function expressions - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -(function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 1: Function expressions are not allowed -You are trying to use Function expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use function expressions - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -(function(x) { return x + 1; })(4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 1: Function expressions are not allowed -You are trying to use Function expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use function expressions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "(function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Function expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use function expressions: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "(function(x) { return x + 1; })(4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Function expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use multiple declarations - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 3, y = 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Multiple declaration in a single statement. -Split the variable declaration into multiple lines as follows - - let x = 3; - let y = 5; - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use multiple declarations: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3, y = 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Multiple declaration in a single statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update expressions - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 3; -x++; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: Update expressions are not allowed -You are trying to use Update expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update expressions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -x++; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Update expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 3; -x += 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: The assignment operator += is not allowed. Use = instead. - - x = x + 5; -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update statements - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 3; -x <<= 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: The assignment operator <<= is not allowed. Use = instead. - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -x += 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: The assignment operator += is not allowed. Use = instead.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update statements: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -x <<= 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: The assignment operator <<= is not allowed. Use = instead.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No array expressions in chapter 2 - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -[];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Array expressions are not allowed -You are trying to use Array expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No array expressions in chapter 2: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Array expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No empty statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Empty statements are not allowed -You are trying to use Empty statements, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No empty statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": ";", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Empty statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No spread in array expressions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[...[]];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Spread syntax is not allowed in arrays.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No trailing commas in arrays - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -[1,];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Trailing comma -Please remove the trailing comma -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No trailing commas in arrays: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[1,];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Trailing comma", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No trailing commas in objects: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "({ - a: 1, - b: 2, -});", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Trailing comma", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`for needs braces - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i = 0; i < 1; i = i + 1) - i;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing curly braces around \\"for\\" block. -Remember to enclose your \\"for\\" block with braces: - - for (let i = 0; i < 1; i = i + 1) { - //code goes here - } -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`for needs braces: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; i < 1; i = i + 1) - i;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing curly braces around \\"for\\" block.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`if needs braces - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -if (true) - true; -else - false;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 0: Missing curly braces around \\"if\\" block. -if block need to be enclosed with a pair of curly braces. - -if (true) { - true; -} - -An exception is when you have an \\"if\\" followed by \\"else if\\", in this case -\\"else if\\" block does not need to be surrounded by curly braces. - -if (someCondition) { - // ... -} else /* notice missing { here */ if (someCondition) { - // ... -} else { - // ... -} - -Rationale: Readability in dense packed code. - -In the snippet below, for instance, with poor indentation it is easy to -mistaken hello() and world() to belong to the same branch of logic. - -if (someCondition) { - 2; -} else - hello(); -world(); - -Line 2, Column 0: Missing curly braces around \\"else\\" block. -else block need to be enclosed with a pair of curly braces. - -else { - false; -} - -An exception is when you have an \\"if\\" followed by \\"else if\\", in this case -\\"else if\\" block does not need to be surrounded by curly braces. - -if (someCondition) { - // ... -} else /* notice missing { here */ if (someCondition) { - // ... -} else { - // ... -} - -Rationale: Readability in dense packed code. - -In the snippet below, for instance, with poor indentation it is easy to -mistaken hello() and world() to belong to the same branch of logic. - -if (someCondition) { - 2; -} else - hello(); -world(); -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`if needs braces: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "if (true) - true; -else - false;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Missing curly braces around \\"if\\" block. -Line 1: Missing curly braces around \\"else\\" block.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no anonymous function declarations - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -export default function (x) { - return x * x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 15: The 'function' keyword needs to be followed by a name. -Function declarations without a name are similar to function expressions, which are banned. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no anonymous function declarations: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "export default function (x) { - return x * x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: The 'function' keyword needs to be followed by a name.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no assigning to reserved keywords - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -package = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: SyntaxError: The keyword 'package' is reserved (2:0) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no assigning to reserved keywords - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -[1, , 3];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: No holes are allowed in array literals. -No holes (empty slots with no content inside) are allowed in array literals. -You probably have an extra comma, which creates a hole. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no assigning to reserved keywords: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "package = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: The keyword 'package' is reserved (1:0)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no classes - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -class Box { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 10: Class bodys are not allowed -You are trying to use Class bodys, which is not allowed (yet). - -Line 2, Column 0: Class declarations are not allowed -You are trying to use Class declarations, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no classes: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "class Box { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Class bodys are not allowed -Line 1: Class declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no declaration without assignment - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 4: Missing value in variable declaration. -A variable declaration assigns a value to a name. -For instance, to assign 20 to x, you can write: - - let x = 20; - - x + x; // 40 -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no declaration without assignment: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing value in variable declaration.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no declaring reserved keywords - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let yield = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 4: SyntaxError: The keyword 'yield' is reserved (2:4) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no declaring reserved keywords: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let yield = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: The keyword 'yield' is reserved (1:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no for in loops - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i in { a: 1, b: 2 }) { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 9: Missing value in variable declaration. -A variable declaration assigns a value to a name. -For instance, to assign 20 to i, you can write: - - let i = 20; - - i + i; // 40 - -Line 2, Column 0: For in statements are not allowed -You are trying to use For in statements, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no for in loops: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i in { a: 1, b: 2 }) { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Missing value in variable declaration. -Line 1: For in statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no for of loops - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i of list()) { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 9: Missing value in variable declaration. -A variable declaration assigns a value to a name. -For instance, to assign 20 to i, you can write: - - let i = 20; - - i + i; // 40 - -Line 2, Column 0: For of statements are not allowed -You are trying to use For of statements, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no for of loops: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i of list()) { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Missing value in variable declaration. -Line 1: For of statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no generator functions - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function* gen() { - yield 2; - return 1; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Yield expressions are not allowed -You are trying to use Yield expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no generator functions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function* gen() { - yield 2; - return 1; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Yield expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no holes in arrays: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, , 3];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: No holes are allowed in array literals.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no implicit undefined return - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() { - return; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Missing value in return statement. -This return statement is missing a value. -For instance, to return the value 42, you can write - - return 42; -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no implicit undefined return: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - return; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Missing value in return statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no interface - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -interface Box { -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: SyntaxError: The keyword 'interface' is reserved (2:0) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no interface: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "interface Box { -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: The keyword 'interface' is reserved (1:0)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no namespace imports - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -import * as x from \\"one_module\\";", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 7: Namespace imports are not allowed -You are trying to use Namespace imports, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no namespace imports: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "import * as x from \\"one_module\\";", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Namespace imports are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no regexp - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -/pattern/", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 9: Missing semicolon at the end of statement -Every statement must be terminated by a semicolon. - -Line 2, Column 0: 'RegExp' literals are not allowed. - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no regexp: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "/pattern/", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Missing semicolon at the end of statement -Line 1: 'RegExp' literals are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no repeated params - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f(x, x) { - return x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 14: SyntaxError: Argument name clash (2:14) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no repeated params: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, x) { - return x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: Argument name clash (1:14)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no sequence expression - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 1: Sequence expressions are not allowed -You are trying to use Sequence expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no sequence expression: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Sequence expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no super - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -class BoxError extends Error { - constructor() { - super(1); - } -}", - "displayResult": Array [], - "numErrors": 5, - "parsedErrors": "Line 4, Column 4: Supers are not allowed -You are trying to use Supers, which is not allowed (yet). - -Line 3, Column 13: Function expressions are not allowed -You are trying to use Function expressions, which is not allowed (yet). - -Line 3, Column 2: Method definitions are not allowed -You are trying to use Method definitions, which is not allowed (yet). - -Line 2, Column 29: Class bodys are not allowed -You are trying to use Class bodys, which is not allowed (yet). - -Line 2, Column 0: Class declarations are not allowed -You are trying to use Class declarations, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no super: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "class BoxError extends Error { - constructor() { - super(1); - } -}", - "displayResult": Array [], - "numErrors": 5, - "parsedErrors": "Line 3: Supers are not allowed -Line 2: Function expressions are not allowed -Line 2: Method definitions are not allowed -Line 1: Class bodys are not allowed -Line 1: Class declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no template literals - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -'hi'", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 4: Missing semicolon at the end of statement -Every statement must be terminated by a semicolon. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no try statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f(x, y) { - return x + y; -} -try { - f([1, 2]); -} catch (e) { - display(e); -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 7, Column 2: Catch clauses are not allowed -You are trying to use Catch clauses, which is not allowed (yet). - -Line 5, Column 0: Try statements are not allowed -You are trying to use Try statements, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no try statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x + y; -} -try { - f([1, 2]); -} catch (e) { - display(e); -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 6: Catch clauses are not allowed -Line 4: Try statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no unspecified operators - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -1 << 10;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Operator '<<' is not allowed. - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no unspecified operators: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "1 << 10;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Operator '<<' is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no unspecified unary operators - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 5; -typeof x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: Operator 'typeof' is not allowed. - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no unspecified unary operators: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 5; -typeof x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Operator 'typeof' is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no var statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -var x = 1;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Variable declaration using \\"var\\" is not allowed. -Use keyword \\"let\\" instead, to declare a variable: - - let x = 1; -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no var statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "var x = 1;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Variable declaration using \\"var\\" is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`while needs braces - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let i = 0; -while (i < 1) - i = i + 1;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: Missing curly braces around \\"while\\" block. -Remember to enclose your \\"while\\" block with braces: - - while (i < 1) { - //code goes here - } -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`while needs braces: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 0; -while (i < 1) - i = i + 1;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Missing curly braces around \\"while\\" block.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/parser/__tests__/disallowed-syntax.ts b/src/parser/__tests__/disallowed-syntax.ts index 5f6487027..8c615c430 100644 --- a/src/parser/__tests__/disallowed-syntax.ts +++ b/src/parser/__tests__/disallowed-syntax.ts @@ -1,1232 +1,127 @@ -import { Chapter } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { expectParsedError } from '../../utils/testing' - -jest.mock('../../modules/loader/loaders') - -test('Cannot leave blank init in for loop', () => { - return expectParsedError( - stripIndent` - for (; i < 3; i = i + 1) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing init expression in for statement."`) -}) - -test('Cannot leave blank init in for loop - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (; i < 3; i = i + 1) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing init expression in for statement. - This for statement requires all three parts (initialiser, test, update) to be present. - " - `) -}) - -test('Cannot leave blank test in for loop', () => { - return expectParsedError( - stripIndent` - for (let i = 0; ; i = i + 1) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing test expression in for statement."`) -}) - -test('Cannot leave blank test in for loop - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i = 0; ; i = i + 1) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing test expression in for statement. - This for statement requires all three parts (initialiser, test, update) to be present. - " - `) -}) - -test('Cannot leave blank update in for loop', () => { - return expectParsedError( - stripIndent` - for (let i = 0; i < 3;) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing update expression in for statement."`) -}) - -test('Cannot leave blank update in for loop - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i = 0; i < 3;) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing update expression in for statement. - This for statement requires all three parts (initialiser, test, update) to be present. - " - `) -}) - -test('Cannot leave blank expressions in for loop', () => { - return expectParsedError( - stripIndent` - for (;;) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing init, test, update expressions in for statement."`) -}) - -test('Cannot leave blank expressions in for loop - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (;;) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing init, test, update expressions in for statement. - This for statement requires all three parts (initialiser, test, update) to be present. - " - `) -}) - -test('Cannot leave while loop predicate blank', () => { - return expectParsedError( - stripIndent` - while () { - x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: Unexpected token (1:7)"`) -}) - -test('Cannot leave while loop predicate blank - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - while () { - x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 7: SyntaxError: Unexpected token (2:7) - There is a syntax error in your program - " - `) -}) - -test('Cannot use update expressions', () => { - return expectParsedError( - stripIndent` - let x = 3; - x++; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Update expressions are not allowed"`) -}) - -test('Cannot use update expressions - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 3; - x++; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: Update expressions are not allowed - You are trying to use Update expressions, which is not allowed (yet). - " - `) -}) - -test('Cannot have incomplete statements', () => { - return expectParsedError( - stripIndent` - 5 - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing semicolon at the end of statement"`) -}) - -test('Cannot have incomplete statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - 5 - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 1: Missing semicolon at the end of statement - Every statement must be terminated by a semicolon. - " - `) -}) - -test('no anonymous function declarations', () => { - return expectParsedError( - stripIndent` - export default function (x) { - return x * x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: The 'function' keyword needs to be followed by a name."`) -}) - -test('no anonymous function declarations - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - export default function (x) { - return x * x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 15: The 'function' keyword needs to be followed by a name. - Function declarations without a name are similar to function expressions, which are banned. - " - `) -}) - -test('Cannot have if without else in chapter <= 2', () => { - return expectParsedError( - stripIndent` - if (true) { 5; } - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`"Line 1: Missing \\"else\\" in \\"if-else\\" statement."`) -}) - -test('Cannot have if without else in chapter <= 2 - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - if (true) { 5; } - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing \\"else\\" in \\"if-else\\" statement. - This \\"if\\" block requires corresponding \\"else\\" block which will be - evaluated when true expression evaluates to false. - - Later in the course we will lift this restriction and allow \\"if\\" without - else. - " - `) -}) - -test('Cannot use multiple declarations', () => { - return expectParsedError( - stripIndent` - let x = 3, y = 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Multiple declaration in a single statement."`) -}) - -test('Cannot use multiple declarations - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 3, y = 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Multiple declaration in a single statement. - Split the variable declaration into multiple lines as follows - - let x = 3; - let y = 5; - - " - `) -}) - -test('Cannot use destructuring declarations', () => { - return expectParsedError( - stripIndent` - let x = [1, 2]; - let [a, b] = x; - a; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Array patterns are not allowed"`) -}) - -test('Cannot use destructuring declarations - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = [1, 2]; - let [a, b] = x; - a; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 4: Array patterns are not allowed - You are trying to use Array patterns, which is not allowed (yet). - " - `) -}) - -test('no declaration without assignment', () => { - return expectParsedError( - stripIndent` - let x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing value in variable declaration."`) -}) - -test('no declaration without assignment - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 4: Missing value in variable declaration. - A variable declaration assigns a value to a name. - For instance, to assign 20 to x, you can write: - - let x = 20; - - x + x; // 40 - " - `) -}) - -test('no var statements', () => { - return expectParsedError( - stripIndent` - var x = 1; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Variable declaration using \\"var\\" is not allowed."`) -}) - -test('no var statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - var x = 1; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Variable declaration using \\"var\\" is not allowed. - Use keyword \\"let\\" instead, to declare a variable: - - let x = 1; - " - `) -}) - -test('Cannot use update statements', () => { - return expectParsedError( - stripIndent` - let x = 3; - x += 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: The assignment operator += is not allowed. Use = instead."`) -}) - -test('Cannot use update statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 3; - x += 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: The assignment operator += is not allowed. Use = instead. - - x = x + 5; - " - `) -}) - -test('Cannot use update statements', () => { - return expectParsedError( - stripIndent` - let x = 3; - x <<= 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: The assignment operator <<= is not allowed. Use = instead."`) -}) - -test('Cannot use update statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 3; - x <<= 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: The assignment operator <<= is not allowed. Use = instead. - - " - `) -}) - -test('Cannot use function expressions', () => { - return expectParsedError( - stripIndent` - (function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4); - `, - { chapter: 5 } - ).toMatchInlineSnapshot(`"Line 1: Function expressions are not allowed"`) -}) - -test('Cannot use function expressions - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - (function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4); - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 2, Column 1: Function expressions are not allowed - You are trying to use Function expressions, which is not allowed (yet). - " - `) -}) - -test('Cannot use function expressions', () => { - return expectParsedError( - stripIndent` - (function(x) { return x + 1; })(4); - `, - { chapter: 5 } - ).toMatchInlineSnapshot(`"Line 1: Function expressions are not allowed"`) -}) - -test('Cannot use function expressions - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - (function(x) { return x + 1; })(4); - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 2, Column 1: Function expressions are not allowed - You are trying to use Function expressions, which is not allowed (yet). - " - `) -}) - -test('if needs braces', () => { - return expectParsedError( - stripIndent` - if (true) - true; - else - false; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 1: Missing curly braces around \\"if\\" block. - Line 1: Missing curly braces around \\"else\\" block." - `) -}) - -test('if needs braces - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - if (true) - true; - else - false; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing curly braces around \\"if\\" block. - if block need to be enclosed with a pair of curly braces. - - if (true) { - true; - } - - An exception is when you have an \\"if\\" followed by \\"else if\\", in this case - \\"else if\\" block does not need to be surrounded by curly braces. - - if (someCondition) { - // ... - } else /* notice missing { here */ if (someCondition) { - // ... - } else { - // ... - } - - Rationale: Readability in dense packed code. - - In the snippet below, for instance, with poor indentation it is easy to - mistaken hello() and world() to belong to the same branch of logic. - - if (someCondition) { - 2; - } else - hello(); - world(); - - Line 2, Column 0: Missing curly braces around \\"else\\" block. - else block need to be enclosed with a pair of curly braces. - - else { - false; - } - - An exception is when you have an \\"if\\" followed by \\"else if\\", in this case - \\"else if\\" block does not need to be surrounded by curly braces. - - if (someCondition) { - // ... - } else /* notice missing { here */ if (someCondition) { - // ... - } else { - // ... - } - - Rationale: Readability in dense packed code. - - In the snippet below, for instance, with poor indentation it is easy to - mistaken hello() and world() to belong to the same branch of logic. - - if (someCondition) { - 2; - } else - hello(); - world(); - " - `) -}) - -test('for needs braces', () => { - return expectParsedError( - stripIndent` - for (let i = 0; i < 1; i = i + 1) - i; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing curly braces around \\"for\\" block."`) -}) - -test('for needs braces - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i = 0; i < 1; i = i + 1) - i; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing curly braces around \\"for\\" block. - Remember to enclose your \\"for\\" block with braces: - - for (let i = 0; i < 1; i = i + 1) { - //code goes here - } - " - `) -}) - -test('while needs braces', () => { - return expectParsedError( - stripIndent` - let i = 0; - while (i < 1) - i = i + 1; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Missing curly braces around \\"while\\" block."`) -}) - -test('while needs braces - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let i = 0; - while (i < 1) - i = i + 1; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: Missing curly braces around \\"while\\" block. - Remember to enclose your \\"while\\" block with braces: - - while (i < 1) { - //code goes here - } - " - `) -}) - -test('No empty statements', () => { - return expectParsedError( - stripIndent` - ; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Empty statements are not allowed"`) -}) - -test('No empty statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - ; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Empty statements are not allowed - You are trying to use Empty statements, which is not allowed (yet). - " - `) -}) - -test('No array expressions in chapter 2', () => { - return expectParsedError( - stripIndent` - []; - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`"Line 1: Array expressions are not allowed"`) -}) - -test('No array expressions in chapter 2 - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - []; - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Array expressions are not allowed - You are trying to use Array expressions, which is not allowed (yet). - " - `) -}) - -test('No spread in array expressions', () => { - return expectParsedError( - stripIndent` - [...[]]; - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(`"Line 1: Spread syntax is not allowed in arrays."`) -}) - -test('No trailing commas in arrays', () => { - return expectParsedError( - stripIndent` - [1,]; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Trailing comma"`) -}) - -test('No trailing commas in arrays - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - [1,]; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 2: Trailing comma - Please remove the trailing comma - " - `) -}) - -test('No trailing commas in objects', () => { - return expectParsedError( - stripIndent` - ({ - a: 1, - b: 2, - }); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 3: Trailing comma"`) -}) - -test('no try statements', () => { - return expectParsedError( - stripIndent` - function f(x, y) { - return x + y; - } - try { - f([1, 2]); - } catch (e) { - display(e); - } - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(` - "Line 6: Catch clauses are not allowed - Line 4: Try statements are not allowed" - `) -}) - -test('no try statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f(x, y) { - return x + y; - } - try { - f([1, 2]); - } catch (e) { - display(e); - } - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(` - "Line 7, Column 2: Catch clauses are not allowed - You are trying to use Catch clauses, which is not allowed (yet). - - Line 5, Column 0: Try statements are not allowed - You are trying to use Try statements, which is not allowed (yet). - " - `) -}) - -test('no for of loops', () => { - return expectParsedError( - stripIndent` - for (let i of list()) { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 1: Missing value in variable declaration. - Line 1: For of statements are not allowed" - `) -}) - -test('no for of loops - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i of list()) { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 9: Missing value in variable declaration. - A variable declaration assigns a value to a name. - For instance, to assign 20 to i, you can write: - - let i = 20; - - i + i; // 40 - - Line 2, Column 0: For of statements are not allowed - You are trying to use For of statements, which is not allowed (yet). - " - `) -}) - -test('no for in loops', () => { - return expectParsedError( - stripIndent` - for (let i in { a: 1, b: 2 }) { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 1: Missing value in variable declaration. - Line 1: For in statements are not allowed" - `) -}) - -test('no for in loops - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i in { a: 1, b: 2 }) { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 9: Missing value in variable declaration. - A variable declaration assigns a value to a name. - For instance, to assign 20 to i, you can write: - - let i = 20; - - i + i; // 40 - - Line 2, Column 0: For in statements are not allowed - You are trying to use For in statements, which is not allowed (yet). - " - `) -}) - -test('no generator functions', () => { - return expectParsedError( - stripIndent` - function* gen() { - yield 2; - return 1; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Yield expressions are not allowed"`) -}) - -test('no generator functions - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function* gen() { - yield 2; - return 1; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 2: Yield expressions are not allowed - You are trying to use Yield expressions, which is not allowed (yet). - " - `) -}) - -test('no classes', () => { - return expectParsedError( - stripIndent` - class Box { - } - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 1: Class bodys are not allowed - Line 1: Class declarations are not allowed" - `) -}) - -test('no classes - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - class Box { - } - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 2, Column 10: Class bodys are not allowed - You are trying to use Class bodys, which is not allowed (yet). - - Line 2, Column 0: Class declarations are not allowed - You are trying to use Class declarations, which is not allowed (yet). - " - `) -}) - -test('no super', () => { - return expectParsedError( - stripIndent` - class BoxError extends Error { - constructor() { - super(1); - } - } - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 3: Supers are not allowed - Line 2: Function expressions are not allowed - Line 2: Method definitions are not allowed - Line 1: Class bodys are not allowed - Line 1: Class declarations are not allowed" - `) -}) - -test('no super - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - class BoxError extends Error { - constructor() { - super(1); +import { Chapter } from "../../types"; +import { expectParsedError, expectParsedErrorsToEqual, expectResult } from "../../utils/testing/testers"; + +expectParsedErrorsToEqual([ + [ + 'Cannot leave while loop predicate blank', + 'while() { x; }', + "Line 1: SyntaxError: Unexpected token (1:6)" + ], + [ + 'Cannot have incomplete statements', + '5', + 'Line 1: Missing semicolon at the end of statement' + ], + [ + 'No try statements', + 'try {} catch(e) {}', + 'Line 1: Catch clauses are not allowed.\nLine 1: Try statements are not allowed.' + ], + [ + 'No for of loops', + ` + let x = []; + for (const each of x) {} + `, + 'Line 3: For of statements are not allowed.' + ], + [ + 'No for in loops', + ` + let x = []; + for (const each in x) {} + `, + 'Line 3: For in statements are not allowed.' + ], + [ + 'No classes', + 'class C {}', + 'Line 1: Class declarations are not allowed.' + ], + [ + 'No sequence expressions', + '(1, 2);', + 'Line 1: Sequence expressions are not allowed.' + ], + [ + 'No this', + ` + function box() { + this[0] = 0; } - } - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 4, Column 4: Supers are not allowed - You are trying to use Supers, which is not allowed (yet). - - Line 3, Column 13: Function expressions are not allowed - You are trying to use Function expressions, which is not allowed (yet). - - Line 3, Column 2: Method definitions are not allowed - You are trying to use Method definitions, which is not allowed (yet). - - Line 2, Column 29: Class bodys are not allowed - You are trying to use Class bodys, which is not allowed (yet). - - Line 2, Column 0: Class declarations are not allowed - You are trying to use Class declarations, which is not allowed (yet). - " - `) -}) - -test('no sequence expression', () => { - return expectParsedError( - stripIndent` - (1, 2); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Sequence expressions are not allowed"`) -}) - -test('no sequence expression - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - (1, 2); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 1: Sequence expressions are not allowed - You are trying to use Sequence expressions, which is not allowed (yet). - " - `) -}) - -test('no interface', () => { - return expectParsedError( - stripIndent` - interface Box { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: The keyword 'interface' is reserved (1:0)"`) -}) - -test('no interface - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - interface Box { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: SyntaxError: The keyword 'interface' is reserved (2:0) - There is a syntax error in your program - " - `) -}) - -test('no template literals - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - 'hi' - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 4: Missing semicolon at the end of statement - Every statement must be terminated by a semicolon. - " - `) -}) - -test('no regexp', () => { - return expectParsedError( - stripIndent` - /pattern/ - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 1: Missing semicolon at the end of statement - Line 1: 'RegExp' literals are not allowed." - `) -}) - -test('no regexp - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - /pattern/ - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 9: Missing semicolon at the end of statement - Every statement must be terminated by a semicolon. - - Line 2, Column 0: 'RegExp' literals are not allowed. - - " - `) -}) - -test('no this, no new', () => { - return expectParsedError( - stripIndent` - function Box() { - this[0] = 5; - } - const box = new Box(); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Expected string as prop, got number."`) -}) - -test('no this, no new - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function Box() { - this[0] = 5; - } - const box = new Box(); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 2: Expected string as prop, got number. - Expected string as prop, got number. - " - `) -}) - -test('no unspecified operators', () => { - return expectParsedError( - stripIndent` - 1 << 10; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Operator '<<' is not allowed."`) -}) - -test('no unspecified operators - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - 1 << 10; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Operator '<<' is not allowed. - - " - `) -}) - -test('no unspecified unary operators', () => { - return expectParsedError( - stripIndent` - let x = 5; - typeof x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Operator 'typeof' is not allowed."`) -}) - -test('no unspecified unary operators - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 5; - typeof x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: Operator 'typeof' is not allowed. - - " - `) -}) - -test('no implicit undefined return', () => { - return expectParsedError( - stripIndent` - function f() { - return; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Missing value in return statement."`) -}) -test('no implicit undefined return - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f() { - return; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 2: Missing value in return statement. - This return statement is missing a value. - For instance, to return the value 42, you can write - - return 42; - " - `) -}) - -test('no repeated params', () => { - return expectParsedError( - stripIndent` - function f(x, x) { - return x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: Argument name clash (1:14)"`) -}) - -test('no repeated params - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f(x, x) { - return x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 14: SyntaxError: Argument name clash (2:14) - There is a syntax error in your program - " - `) -}) - -test('no declaring reserved keywords', () => { - return expectParsedError( - stripIndent` - let yield = 5; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: The keyword 'yield' is reserved (1:4)"`) -}) - -test('no declaring reserved keywords - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let yield = 5; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 4: SyntaxError: The keyword 'yield' is reserved (2:4) - There is a syntax error in your program - " - `) -}) - -test('no assigning to reserved keywords', () => { - return expectParsedError( - stripIndent` - package = 5; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: The keyword 'package' is reserved (1:0)"`) -}) - -test('no assigning to reserved keywords - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - package = 5; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: SyntaxError: The keyword 'package' is reserved (2:0) - There is a syntax error in your program - " - `) -}) - -test('no holes in arrays', () => { - return expectParsedError( - stripIndent` - [1, , 3]; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: No holes are allowed in array literals."`) -}) - -test('no assigning to reserved keywords - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - [1, , 3]; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: No holes are allowed in array literals. - No holes (empty slots with no content inside) are allowed in array literals. - You probably have an extra comma, which creates a hole. - " - `) -}) - -test('no namespace imports', () => { - return expectParsedError( - stripIndent` - import * as x from "one_module"; - `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 1: Namespace imports are not allowed"`) -}) - -test('no namespace imports - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - import * as x from "one_module"; `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(` - "Line 2, Column 7: Namespace imports are not allowed - You are trying to use Namespace imports, which is not allowed (yet). - " - `) + 'Line 3: \'this\' expressions are not allowed.' + ], + [ + 'No new', + 'const b = new box();', + 'Line 1: New expressions are not allowed.' + ], + [ + 'No assigning to reserved keywords', + 'package = 5;', + "Line 1: SyntaxError: The keyword 'package' is reserved (1:0)" + ], + [ + 'No declaring reserved keywords', + 'let yield = 5;', + "Line 1: SyntaxError: The keyword 'yield' is reserved (1:4)" + ], + [ + 'No interfaces', + 'interface Box {}', + "Line 1: SyntaxError: The keyword 'interface' is reserved (1:0)" + ], + [ + 'No spread in array expressions', + '[...[]];', + 'Line 1: Spread syntax is not allowed in arrays.' + ], + [ + 'No destructuring declarations', + ` + let x = [1, 2]; + let [a, b] = x; + a; + `, + 'Line 3: Array patterns are not allowed.' + ], + [ + 'No function expressions', + 'const x = function() {};', + 'Line 1: Function expressions are not allowed.' + ], + [ + 'No repeated function parameters', + 'function any(x, x) {}', + 'Line 1: SyntaxError: Argument name clash (1:16)' + ] +], Chapter.SOURCE_4) + +// test('No empty statements', () => { +// return expectParsedError( +// stripIndent` +// ; +// `, +// { chapter: Chapter.LIBRARY_PARSER } +// ).toMatchInlineSnapshot(`"Line 1: Empty statements are not allowed"`) +// }) + +// test('No empty statements - verbose', () => { +// return expectParsedError( +// stripIndent` +// "enable verbose"; +// ; +// `, +// { chapter: Chapter.LIBRARY_PARSER } +// ).toMatchInlineSnapshot(` +// "Line 2, Column 0: Empty statements are not allowed +// You are trying to use Empty statements, which is not allowed (yet). +// " +// `) +// }) + +test('No array expressions in chapter 2', async () => { + await expectParsedError('[];', Chapter.SOURCE_2).toEqual('Line 1: Array expressions are not allowed.') + await expectResult('[];', Chapter.SOURCE_3).toEqual([]) }) diff --git a/src/parser/errors.ts b/src/parser/errors.ts index a9d27b772..68ab14661 100644 --- a/src/parser/errors.ts +++ b/src/parser/errors.ts @@ -60,7 +60,7 @@ export class DisallowedConstructError implements SourceError { } public explain() { - return `${this.nodeType} are not allowed` + return `${this.nodeType} are not allowed.` } public elaborate() { diff --git a/src/parser/source/index.ts b/src/parser/source/index.ts index 733bea392..be5210989 100644 --- a/src/parser/source/index.ts +++ b/src/parser/source/index.ts @@ -1,11 +1,11 @@ import { parse as acornParse, Token, tokenizer } from 'acorn' -import * as es from 'estree' +import type es from 'estree' import { DEFAULT_ECMA_VERSION } from '../../constants' -import { Chapter, Context, Node, Rule, SourceError, Variant } from '../../types' +import type { Chapter, Context, Node, SourceError, Variant } from '../../types' import { ancestor, AncestorWalkerFn } from '../../utils/walkers' import { DisallowedConstructError, FatalSyntaxError } from '../errors' -import { AcornOptions, Parser } from '../types' +import type { AcornOptions, Parser, Rule } from '../types' import { createAcornParserOptions, positionToSourceLocation } from '../utils' import defaultRules from './rules' import syntaxBlacklist from './syntax' diff --git a/src/parser/source/rules/__tests__/rules.ts b/src/parser/source/rules/__tests__/rules.ts new file mode 100644 index 000000000..9c90a4b81 --- /dev/null +++ b/src/parser/source/rules/__tests__/rules.ts @@ -0,0 +1,57 @@ +import { parseError } from "../../../.." +import { mockContext } from "../../../../mocks/context" +import { Chapter } from "../../../../types" +import { parse } from "../../../parser" +import type { Rule } from "../../../types" +import rules from '..' + +const chaptersToTest = [ + Chapter.SOURCE_1, + Chapter.SOURCE_2, + Chapter.SOURCE_3, + Chapter.SOURCE_4, + Chapter.LIBRARY_PARSER, +] + +function testSnippet(code: string, expected: string, rule: Rule) { + const chapterCases = chaptersToTest.map(chapter => { + const isExpectedToError = rule.disableFromChapter === undefined || chapter < rule.disableFromChapter + const errStr = isExpectedToError ? 'error' : 'no error' + + return [ + `Chapter ${chapter}: ${errStr}`, + code, + isExpectedToError ? expected : undefined, + chapter + ] as [string, string, string, Chapter] + }) + + test.each(chapterCases)("%s", (_, code, expected, chapter) => { + const context = mockContext(chapter) + parse(code, context) + + if (expected === undefined) { + expect(context.errors.length).toEqual(0) + } else { + expect(context.errors.length).toBeGreaterThanOrEqual(1) + const parsedErrors = parseError(context.errors) + expect(parsedErrors).toEqual(expect.stringContaining(expected)) + } + }) +} + +describe('Test rules', () => { + rules.forEach(rule => { + if (!rule.testSnippets) return + + const disableStr = rule.disableFromChapter ? `(Disabled for Chapter ${rule.disableFromChapter} and above)` : '(Always enabled)' + describe(`Testing ${rule.name} ${disableStr}`, () => { + if (rule.testSnippets!.length === 1) { + const [[code, expected]] = rule.testSnippets! + testSnippet(code, expected, rule) + } else { + rule.testSnippets!.forEach((snippet, i) => describe(`Testing Snippet ${i+1}`, () => testSnippet(...snippet, rule))) + } + }) + }) +}) \ No newline at end of file diff --git a/src/parser/source/rules/bracesAroundFor.ts b/src/parser/source/rules/bracesAroundFor.ts index 51cb34816..4884d261d 100644 --- a/src/parser/source/rules/bracesAroundFor.ts +++ b/src/parser/source/rules/bracesAroundFor.ts @@ -2,7 +2,10 @@ import { generate } from 'astring' import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' + +const errorMsg = 'Missing curly braces around "for" block.' export class BracesAroundForError implements SourceError { public type = ErrorType.SYNTAX @@ -31,7 +34,15 @@ export class BracesAroundForError implements SourceError { const bracesAroundFor: Rule = { name: 'braces-around-for', - + testSnippets: [ + [ + ` + let j = 0; + for (let i = 0; i < 1; i = i + 1) j = j + 1; + `, + errorMsg, + ], + ], checkers: { ForStatement(node: es.ForStatement, _ancestors: [Node]) { if (node.body.type !== 'BlockStatement') { diff --git a/src/parser/source/rules/bracesAroundIfElse.ts b/src/parser/source/rules/bracesAroundIfElse.ts index a9eef232f..0100a4174 100644 --- a/src/parser/source/rules/bracesAroundIfElse.ts +++ b/src/parser/source/rules/bracesAroundIfElse.ts @@ -2,7 +2,8 @@ import { generate } from 'astring' import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' export class BracesAroundIfElseError implements SourceError { @@ -72,6 +73,18 @@ export class BracesAroundIfElseError implements SourceError { const bracesAroundIfElse: Rule = { name: 'braces-around-if-else', + testSnippets: [ + [ + ` + function f() { + if (true) return false; + else return true; + } + `, + 'Line 3: Missing curly braces around "if" block.' + ] + ], + checkers: { IfStatement(node: es.IfStatement, _ancestors: [Node]) { const errors: SourceError[] = [] diff --git a/src/parser/source/rules/bracesAroundWhile.ts b/src/parser/source/rules/bracesAroundWhile.ts index 62bda7356..56d39852d 100644 --- a/src/parser/source/rules/bracesAroundWhile.ts +++ b/src/parser/source/rules/bracesAroundWhile.ts @@ -2,7 +2,8 @@ import { generate } from 'astring' import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class BracesAroundWhileError implements SourceError { public type = ErrorType.SYNTAX @@ -29,6 +30,16 @@ export class BracesAroundWhileError implements SourceError { const bracesAroundWhile: Rule = { name: 'braces-around-while', + testSnippets: [ + [ + ` + let i = 0; + while (true) i = i + 1; + `, + 'Line 3: Missing curly braces around "while" block.' + ] + ], + checkers: { WhileStatement(node: es.WhileStatement, _ancestors: [Node]) { if (node.body.type !== 'BlockStatement') { diff --git a/src/parser/source/rules/forStatementMustHaveAllParts.ts b/src/parser/source/rules/forStatementMustHaveAllParts.ts index d2213adb3..6a5e08c37 100644 --- a/src/parser/source/rules/forStatementMustHaveAllParts.ts +++ b/src/parser/source/rules/forStatementMustHaveAllParts.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, SourceError } from '../../../types' +import { Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' export class ForStatmentMustHaveAllParts implements SourceError { @@ -30,8 +31,15 @@ export class ForStatmentMustHaveAllParts implements SourceError { const forStatementMustHaveAllParts: Rule = { name: 'for-statement-must-have-all-parts', + testSnippets: [ + ['let i = 0; for (; i < 0; i = i + 1) {}', 'Line 1: Missing init expression in for statement.'], + ['for (let i = 0; ; i = i + 1) {}', 'Line 1: Missing test expression in for statement.'], + ['for (let i = 0; i < 0;) {}', 'Line 1: Missing update expression in for statement'], + ], + + checkers: { - ForStatement(node: es.ForStatement) { + ForStatement(node) { const missingParts = ['init', 'test', 'update'].filter(part => node[part] === null) if (missingParts.length > 0) { return [new ForStatmentMustHaveAllParts(node, missingParts)] diff --git a/src/parser/source/rules/index.ts b/src/parser/source/rules/index.ts index 241307b9f..3b476c248 100644 --- a/src/parser/source/rules/index.ts +++ b/src/parser/source/rules/index.ts @@ -1,4 +1,5 @@ -import { Node, Rule } from '../../../types' +import { Node } from '../../../types' +import { Rule } from '../../types' import bracesAroundFor from './bracesAroundFor' import bracesAroundIfElse from './bracesAroundIfElse' import bracesAroundWhile from './bracesAroundWhile' diff --git a/src/parser/source/rules/noDeclareMutable.ts b/src/parser/source/rules/noDeclareMutable.ts index af0d45b12..899f29db8 100644 --- a/src/parser/source/rules/noDeclareMutable.ts +++ b/src/parser/source/rules/noDeclareMutable.ts @@ -2,7 +2,8 @@ import { generate } from 'astring' import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { Chapter, ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { Chapter, ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' const mutableDeclarators = ['let', 'var'] @@ -34,6 +35,13 @@ const noDeclareMutable: Rule = { name: 'no-declare-mutable', disableFromChapter: Chapter.SOURCE_3, + testSnippets: [ + [ + 'let i = 0;', + 'Line 1: Mutable variable declaration using keyword \'let\' is not allowed.', + ], + ], + checkers: { VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) { if (mutableDeclarators.includes(node.kind)) { diff --git a/src/parser/source/rules/noDotAbbreviation.ts b/src/parser/source/rules/noDotAbbreviation.ts index 52a536871..66955483e 100644 --- a/src/parser/source/rules/noDotAbbreviation.ts +++ b/src/parser/source/rules/noDotAbbreviation.ts @@ -1,7 +1,10 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { Chapter, ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { Chapter, ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' + +const errorMessage = 'Dot abbreviations are not allowed.' export class NoDotAbbreviationError implements SourceError { public type = ErrorType.SYNTAX @@ -14,7 +17,7 @@ export class NoDotAbbreviationError implements SourceError { } public explain() { - return 'Dot abbreviations are not allowed.' + return errorMessage } public elaborate() { @@ -27,6 +30,15 @@ const noDotAbbreviation: Rule = { name: 'no-dot-abbreviation', disableFromChapter: Chapter.LIBRARY_PARSER, + testSnippets: [ + [ + ` + const obj = {}; + obj.o; + `, + `Line 3: ${errorMessage}` + ] + ], checkers: { MemberExpression(node: es.MemberExpression, _ancestors: [Node]) { diff --git a/src/parser/source/rules/noEval.ts b/src/parser/source/rules/noEval.ts index 848748486..1eb4e1414 100644 --- a/src/parser/source/rules/noEval.ts +++ b/src/parser/source/rules/noEval.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class NoEval implements SourceError { public type = ErrorType.SYNTAX @@ -24,6 +25,9 @@ export class NoEval implements SourceError { const noEval: Rule = { name: 'no-eval', + testSnippets: [ + ['eval("0;");', 'Line 1: eval is not allowed.'] + ], checkers: { Identifier(node: es.Identifier, _ancestors: [Node]) { diff --git a/src/parser/source/rules/noExportNamedDeclarationWithDefault.ts b/src/parser/source/rules/noExportNamedDeclarationWithDefault.ts index f38be0681..5318834ca 100644 --- a/src/parser/source/rules/noExportNamedDeclarationWithDefault.ts +++ b/src/parser/source/rules/noExportNamedDeclarationWithDefault.ts @@ -2,7 +2,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' import { defaultExportLookupName } from '../../../stdlib/localImport.prelude' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' import syntaxBlacklist from '../syntax' export class NoExportNamedDeclarationWithDefaultError implements SourceError { @@ -27,6 +28,15 @@ export class NoExportNamedDeclarationWithDefaultError implements SourceError { const noExportNamedDeclarationWithDefault: Rule = { name: 'no-declare-mutable', disableFromChapter: syntaxBlacklist['ExportDefaultDeclaration'], + testSnippets: [ + [ + ` + const a = 0; + export { a as default }; + `, + 'Line 3: Export default declarations are not allowed' + ] + ], checkers: { ExportNamedDeclaration(node: es.ExportNamedDeclaration, _ancestors: [Node]) { diff --git a/src/parser/source/rules/noFunctionDeclarationWithoutIdentifier.ts b/src/parser/source/rules/noFunctionDeclarationWithoutIdentifier.ts index 4278140b7..1864d98b6 100644 --- a/src/parser/source/rules/noFunctionDeclarationWithoutIdentifier.ts +++ b/src/parser/source/rules/noFunctionDeclarationWithoutIdentifier.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class NoFunctionDeclarationWithoutIdentifierError implements SourceError { public type = ErrorType.SYNTAX @@ -24,7 +25,12 @@ export class NoFunctionDeclarationWithoutIdentifierError implements SourceError const noFunctionDeclarationWithoutIdentifier: Rule = { name: 'no-function-declaration-without-identifier', - + // testSnippets: [ + // [ + // 'const x = function() {};', + // 'Line 1: The \'function\' keyword needs to be followed by a name.' + // ] + // ], checkers: { FunctionDeclaration(node: es.FunctionDeclaration, _ancestors: Node[]): SourceError[] { if (node.id === null) { diff --git a/src/parser/source/rules/noHolesInArrays.ts b/src/parser/source/rules/noHolesInArrays.ts index c9f3649a7..26a678a35 100644 --- a/src/parser/source/rules/noHolesInArrays.ts +++ b/src/parser/source/rules/noHolesInArrays.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, SourceError } from '../../../types' +import { Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' export class NoHolesInArrays implements SourceError { @@ -28,6 +29,9 @@ export class NoHolesInArrays implements SourceError { const noHolesInArrays: Rule = { name: 'no-holes-in-arrays', + testSnippets: [ + ['[0,,0];', 'Line 1: No holes are allowed in array literals.'] + ], checkers: { ArrayExpression(node: es.ArrayExpression) { diff --git a/src/parser/source/rules/noIfWithoutElse.ts b/src/parser/source/rules/noIfWithoutElse.ts index f233f94dc..713b70b9f 100644 --- a/src/parser/source/rules/noIfWithoutElse.ts +++ b/src/parser/source/rules/noIfWithoutElse.ts @@ -2,7 +2,8 @@ import { generate } from 'astring' import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { Chapter, ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { Chapter, ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' export class NoIfWithoutElseError implements SourceError { @@ -33,6 +34,19 @@ export class NoIfWithoutElseError implements SourceError { const noIfWithoutElse: Rule = { name: 'no-if-without-else', disableFromChapter: Chapter.SOURCE_3, + testSnippets: [ + [ + ` + function f() { + if (true) { + return true; + } + return false; + } + `, + 'Line 3: Missing "else" in "if-else" statement.' + ] + ], checkers: { IfStatement(node: es.IfStatement, _ancestors: [Node]) { if (!node.alternate) { diff --git a/src/parser/source/rules/noImplicitDeclareUndefined.ts b/src/parser/source/rules/noImplicitDeclareUndefined.ts index e6404ae47..84ff927ed 100644 --- a/src/parser/source/rules/noImplicitDeclareUndefined.ts +++ b/src/parser/source/rules/noImplicitDeclareUndefined.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, SourceError } from '../../../types' +import { Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' export class NoImplicitDeclareUndefinedError implements SourceError { @@ -32,9 +33,17 @@ export class NoImplicitDeclareUndefinedError implements SourceError { const noImplicitDeclareUndefined: Rule = { name: 'no-implicit-declare-undefined', + testSnippets: [ + ['let i;', 'Line 1: Missing value in variable declaration.'] + ], checkers: { - VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) { + VariableDeclaration(node: es.VariableDeclaration, ancestors: es.Node[]) { + const ancestor = ancestors[ancestors.length - 2] + if (ancestor.type === 'ForOfStatement' || ancestor.type === 'ForInStatement') { + return [] + } + const errors: SourceError[] = [] for (const decl of node.declarations) { if (!decl.init) { diff --git a/src/parser/source/rules/noImplicitReturnUndefined.ts b/src/parser/source/rules/noImplicitReturnUndefined.ts index bddc2f1ab..419942c40 100644 --- a/src/parser/source/rules/noImplicitReturnUndefined.ts +++ b/src/parser/source/rules/noImplicitReturnUndefined.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' export class NoImplicitReturnUndefinedError implements SourceError { @@ -30,6 +31,12 @@ export class NoImplicitReturnUndefinedError implements SourceError { const noImplicitReturnUndefined: Rule = { name: 'no-implicit-return-undefined', + testSnippets: [ + [ + 'function f() { return; }', + 'Line 1: Missing value in return statement.' + ] + ], checkers: { ReturnStatement(node: es.ReturnStatement, _ancestors: [Node]) { diff --git a/src/parser/source/rules/noImportSpecifierWithDefault.ts b/src/parser/source/rules/noImportSpecifierWithDefault.ts index 2afe088b8..849b7a553 100644 --- a/src/parser/source/rules/noImportSpecifierWithDefault.ts +++ b/src/parser/source/rules/noImportSpecifierWithDefault.ts @@ -2,7 +2,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' import { defaultExportLookupName } from '../../../stdlib/localImport.prelude' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' import syntaxBlacklist from '../syntax' export class NoImportSpecifierWithDefaultError implements SourceError { @@ -16,7 +17,7 @@ export class NoImportSpecifierWithDefaultError implements SourceError { } public explain() { - return 'Import default specifiers are not allowed' + return 'Import default specifiers are not allowed.' } public elaborate() { @@ -25,8 +26,14 @@ export class NoImportSpecifierWithDefaultError implements SourceError { } const noImportSpecifierWithDefault: Rule = { - name: 'no-declare-mutable', + name: 'no-import-default-specifier', disableFromChapter: syntaxBlacklist['ImportDefaultSpecifier'], + testSnippets: [ + [ + 'import { default as a } from "./a.js";', + 'Line 1: Import default specifiers are not allowed.' + ], + ], checkers: { ImportSpecifier(node: es.ImportSpecifier, _ancestors: [Node]) { diff --git a/src/parser/source/rules/noNull.ts b/src/parser/source/rules/noNull.ts index e2cf63c04..f3c91b3b7 100644 --- a/src/parser/source/rules/noNull.ts +++ b/src/parser/source/rules/noNull.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { Chapter, ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { Chapter, ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class NoNullError implements SourceError { public type = ErrorType.SYNTAX @@ -25,6 +26,9 @@ export class NoNullError implements SourceError { const noNull: Rule = { name: 'no-null', disableFromChapter: Chapter.SOURCE_2, + testSnippets: [ + [ 'null;', 'Line 1: null literals are not allowed.'] + ], checkers: { Literal(node: es.Literal, _ancestors: [Node]) { if (node.value === null) { diff --git a/src/parser/source/rules/noSpreadInArray.ts b/src/parser/source/rules/noSpreadInArray.ts index a7883ff35..1e9d14b07 100644 --- a/src/parser/source/rules/noSpreadInArray.ts +++ b/src/parser/source/rules/noSpreadInArray.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class NoSpreadInArray implements SourceError { public type = ErrorType.SYNTAX diff --git a/src/parser/source/rules/noTemplateExpression.ts b/src/parser/source/rules/noTemplateExpression.ts index dd870fe6c..d9b0a76a9 100644 --- a/src/parser/source/rules/noTemplateExpression.ts +++ b/src/parser/source/rules/noTemplateExpression.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class NoTemplateExpressionError implements SourceError { public type = ErrorType.SYNTAX diff --git a/src/parser/source/rules/noTypeofOperator.ts b/src/parser/source/rules/noTypeofOperator.ts index 7a9a11bd7..4c4677283 100644 --- a/src/parser/source/rules/noTypeofOperator.ts +++ b/src/parser/source/rules/noTypeofOperator.ts @@ -1,12 +1,17 @@ import * as es from 'estree' -import { Rule, Variant } from '../../../types' +import { Variant } from '../../../types' +import { Rule } from '../../types' import { NoUnspecifiedOperatorError } from './noUnspecifiedOperator' const noTypeofOperator: Rule = { name: 'no-typeof-operator', disableForVariants: [Variant.TYPED], + testSnippets: [ + ['typeof "string";', "Line 1: Operator 'typeof' is not allowed."] + ], + checkers: { UnaryExpression(node: es.UnaryExpression) { if (node.operator === 'typeof') { diff --git a/src/parser/source/rules/noUnspecifiedLiteral.ts b/src/parser/source/rules/noUnspecifiedLiteral.ts index 29dc16c4c..864244c0d 100644 --- a/src/parser/source/rules/noUnspecifiedLiteral.ts +++ b/src/parser/source/rules/noUnspecifiedLiteral.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' const specifiedLiterals = ['boolean', 'string', 'number'] @@ -31,6 +32,9 @@ export class NoUnspecifiedLiteral implements SourceError { const noUnspecifiedLiteral: Rule = { name: 'no-unspecified-literal', + testSnippets: [ + ['const x = /hi/;', "Line 1: 'RegExp' literals are not allowed."] + ], checkers: { Literal(node: es.Literal, _ancestors: [Node]) { if (node.value !== null && !specifiedLiterals.includes(typeof node.value)) { diff --git a/src/parser/source/rules/noUnspecifiedOperator.ts b/src/parser/source/rules/noUnspecifiedOperator.ts index 2768936df..9d67a1af5 100644 --- a/src/parser/source/rules/noUnspecifiedOperator.ts +++ b/src/parser/source/rules/noUnspecifiedOperator.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class NoUnspecifiedOperatorError implements SourceError { public type = ErrorType.SYNTAX diff --git a/src/parser/source/rules/noUpdateAssignment.ts b/src/parser/source/rules/noUpdateAssignment.ts index 5429fc82d..539a657ec 100644 --- a/src/parser/source/rules/noUpdateAssignment.ts +++ b/src/parser/source/rules/noUpdateAssignment.ts @@ -2,7 +2,8 @@ import { generate } from 'astring' import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class NoUpdateAssignment implements SourceError { public type = ErrorType.SYNTAX diff --git a/src/parser/source/rules/noVar.ts b/src/parser/source/rules/noVar.ts index e7c591e5f..92291bbc6 100644 --- a/src/parser/source/rules/noVar.ts +++ b/src/parser/source/rules/noVar.ts @@ -2,7 +2,8 @@ import { generate } from 'astring' import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class NoVarError implements SourceError { public type = ErrorType.SYNTAX diff --git a/src/parser/source/rules/singleVariableDeclaration.ts b/src/parser/source/rules/singleVariableDeclaration.ts index 0f59c3fa4..884d49f5f 100644 --- a/src/parser/source/rules/singleVariableDeclaration.ts +++ b/src/parser/source/rules/singleVariableDeclaration.ts @@ -2,7 +2,8 @@ import { generate } from 'astring' import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class MultipleDeclarationsError implements SourceError { public type = ErrorType.SYNTAX @@ -23,7 +24,7 @@ export class MultipleDeclarationsError implements SourceError { } public explain() { - return 'Multiple declaration in a single statement.' + return 'Multiple declarations in a single statement.' } public elaborate() { @@ -34,6 +35,9 @@ export class MultipleDeclarationsError implements SourceError { const singleVariableDeclaration: Rule = { name: 'single-variable-declaration', + testSnippets: [ + ['let i = 0, j = 0;', 'Line 1: Multiple declarations in a single statement.'] + ], checkers: { VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) { diff --git a/src/parser/source/rules/strictEquality.ts b/src/parser/source/rules/strictEquality.ts index 61e3b29c0..1f9e56664 100644 --- a/src/parser/source/rules/strictEquality.ts +++ b/src/parser/source/rules/strictEquality.ts @@ -1,7 +1,8 @@ import * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' +import { Rule } from '../../types' export class StrictEqualityError implements SourceError { public type = ErrorType.SYNTAX @@ -15,20 +16,25 @@ export class StrictEqualityError implements SourceError { public explain() { if (this.node.operator === '==') { - return 'Use === instead of ==' + return 'Use === instead of ==.' } else { - return 'Use !== instead of !=' + return 'Use !== instead of !=.' } } public elaborate() { - return '== and != is not a valid operator' + return '== and != are not valid operators.' } } const strictEquality: Rule = { name: 'strict-equality', + testSnippets: [ + ['const x = 1 == 2;', 'Line 1: Use === instead of ==.'], + ['const x = 1 != 2;', 'Line 1: Use !== instead of !=.'], + ], + checkers: { BinaryExpression(node: es.BinaryExpression, _ancestors: [Node]) { if (node.operator === '==' || node.operator === '!=') { diff --git a/src/parser/source/syntax.ts b/src/parser/source/syntax.ts index 91669a8cc..469270a0f 100644 --- a/src/parser/source/syntax.ts +++ b/src/parser/source/syntax.ts @@ -1,6 +1,8 @@ +import type { Node } from 'acorn' + export const libraryParserLanguage = 100 -const syntaxBlacklist: { [nodeName: string]: number } = { +const syntaxBlacklist: { [K in Node['type']]: number } = { // List of all node types taken from // https://github.com/acornjs/acorn/blob/master/acorn-walk/src/index.js diff --git a/src/parser/types.ts b/src/parser/types.ts index 33a1eac35..e5e691313 100644 --- a/src/parser/types.ts +++ b/src/parser/types.ts @@ -1,11 +1,9 @@ -import { ParserOptions } from '@babel/parser' -import { Options } from 'acorn' -import { Program } from 'estree' +import type { Program } from 'estree' -import { Context } from '../types' +import type { Context, Node, Chapter, Variant, SourceError } from '../types' -export type AcornOptions = Options -export type BabelOptions = ParserOptions +export type { ParserOptions as BabelOptions } from '@babel/parser' +export type { Options as AcornOptions } from 'acorn' export interface Parser { parse( @@ -16,3 +14,13 @@ export interface Parser { ): Program | null validate(ast: Program, context: Context, throwOnError?: boolean): boolean } +export interface Rule { + name: string + testSnippets?: [string, string][] + + disableFromChapter?: Chapter + disableForVariants?: Variant[] + checkers: { + [name: string]: (node: T, ancestors: Node[]) => SourceError[] + }, +} From 4011aa77f61867e0b1bfaeea54d6de2d9230a903 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 21 May 2024 12:57:33 +0800 Subject: [PATCH 19/65] Update validator to include try statements --- src/validator/__tests__/validator.ts | 63 +++++++++++++++++++++++++++- src/validator/validator.ts | 45 ++++++++++++++------ 2 files changed, 94 insertions(+), 14 deletions(-) diff --git a/src/validator/__tests__/validator.ts b/src/validator/__tests__/validator.ts index 39376a05d..7a8ac7efa 100644 --- a/src/validator/__tests__/validator.ts +++ b/src/validator/__tests__/validator.ts @@ -140,6 +140,15 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { ['function names can be called recursively', 'function a() { a; }'], ['function parameters are considered valid declarations', 'function a(b) { b; return a(); }'], + // Function expressions + [ + 'FunctionExpression bodies are checked', + 'const a = function() { return b; }', + 'Line 1: Name b not declared.' + ], + ['FunctionExpression parameters are valid declarations', 'const a = function(b) { return b; }'], + ['FunctionExpressions can call themselves recursively', 'const a = function() { a(); }'], + // For loops ['for loop init variable counts', 'for (let i = 0; i < 0; i = i + 1) { i; }'], [ @@ -162,6 +171,57 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { 'for (let i = 0; i < 0; i = i + 1) { a; }', 'Line 1: Name a not declared.' ], + [ + 'for in statement declaration is valid', + 'for (const [x, y] in []) { x; y; }', + ], + [ + 'for in statement is checked', + 'for (const x in a) { x; }', + 'Line 1: Name a not declared.' + ], + [ + 'for of statement declaration is valid', + 'for (const {x, y} of []) { x; y; }', + ], + [ + 'destructuring expression in for of is checked 1', + ` + let x = 0, y = 0; + for ({x, y} of []) {} + ` + ], + [ + 'destructuring expression in for of is checked 2', + 'for ({x, y} of []) {}', + 'Line 1: Name x not declared.' + ], + [ + 'for of statement is checked', + 'for (const x in a) { x; }', + 'Line 1: Name a not declared.' + ], + + // Try statement + [ + 'try statement body is checked', + 'try { a; } catch(e) {}', + 'Line 1: Name a not declared.' + ], + [ + 'catch clauses are checked', + 'try {} catch(e) { a; }', + 'Line 1: Name a not declared.' + ], + [ + 'catch clause parameter is a valid identifier', + 'try {} catch(e) { e; }', + ], + [ + 'finally blocks are checked', + 'try {} finally { a; }', + 'Line 1: Name a not declared.' + ], // BlockStatements and Programs [ @@ -197,7 +257,8 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { // Builtins and Preludes ['identifiers in builtins are valid', 'pair();'], ['identifiers in preludes are valid', 'stream();'] - ] + ], + Chapter.FULL_JS ) test('previous program identifiers are also valid', async () => { diff --git a/src/validator/validator.ts b/src/validator/validator.ts index 8dc10a2d0..720e819e0 100644 --- a/src/validator/validator.ts +++ b/src/validator/validator.ts @@ -188,35 +188,53 @@ export function checkForUndefinedVariables( identifiersIntroducedByNode.set(node, identifiers) } function processFunction( - node: es.FunctionDeclaration | es.ArrowFunctionExpression, + node: es.FunctionDeclaration | es.ArrowFunctionExpression | es.FunctionExpression, _ancestors: es.Node[] ) { identifiersIntroducedByNode.set( node, new Set( - node.params.map(id => - id.type === 'Identifier' - ? id.name - : ((id as es.RestElement).argument as es.Identifier).name - ) + mapIdentifiersToNames(node.params.flatMap(getIdentifiersFromVariableDeclaration)) ) ) } + + function processFor( + node: es.ForOfStatement | es.ForInStatement + ) { + if (isVariableDeclaration(node.left)) { + identifiersIntroducedByNode.set( + node, + new Set(mapIdentifiersToNames(getIdentifiersFromVariableDeclaration(node.left))) + ) + } + } + const identifiersToAncestors = new Map() ancestor(program, { - Program: processBlock, - BlockStatement: processBlock, - FunctionDeclaration: processFunction, ArrowFunctionExpression: processFunction, - ForStatement(forStatement: es.ForStatement, ancestors: es.Node[]) { - const init = forStatement.init! - if (isVariableDeclaration(init)) { + BlockStatement: processBlock, + CatchClause(node: es.CatchClause) { + if (node.param) { + identifiersIntroducedByNode.set( + node, + new Set(mapIdentifiersToNames(getIdentifiersFromVariableDeclaration(node.param))) + ) + } + }, + ForStatement(forStatement: es.ForStatement) { + const init = forStatement.init + if (init && isVariableDeclaration(init)) { identifiersIntroducedByNode.set( forStatement, new Set(mapIdentifiersToNames(getIdentifiersFromVariableDeclaration(init))) ) } }, + ForInStatement: processFor, + ForOfStatement: processFor, + FunctionDeclaration: processFunction, + FunctionExpression: processFunction, Identifier(identifier: es.Identifier, ancestors: es.Node[]) { identifiersToAncestors.set(identifier, [...ancestors]) }, @@ -228,7 +246,8 @@ export function checkForUndefinedVariables( identifiersToAncestors.set(node.object, [...ancestors]) } } - } + }, + Program: processBlock, }) const nativeInternalNames = new Set(Object.values(globalIds).map(({ name }) => name)) From fd5b3e39b8f7c0bf6b8c9b6fc7de2d0599cae222 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 21 May 2024 12:58:12 +0800 Subject: [PATCH 20/65] Allow treating a pattern node like it is a variable declaration --- src/utils/ast/helpers.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/utils/ast/helpers.ts b/src/utils/ast/helpers.ts index fa39fb96d..97584a4ee 100644 --- a/src/utils/ast/helpers.ts +++ b/src/utils/ast/helpers.ts @@ -4,7 +4,7 @@ import assert from '../assert' import { simple } from '../walkers' import { ArrayMap } from '../dict' import type { Node } from '../../types' -import { isImportDeclaration } from './typeGuards' +import { isImportDeclaration, isVariableDeclaration } from './typeGuards' export function getModuleDeclarationSource( node: Exclude @@ -45,15 +45,15 @@ export function mapIdentifiersToNames(ids: es.Identifier[]): string[] { return ids.map(({ name }) => name) } -export function getIdentifiersFromVariableDeclaration(decl: es.VariableDeclaration) { - function internal(node: es.Pattern): es.Identifier | es.Identifier[] { +export function getIdentifiersFromVariableDeclaration(decl: es.VariableDeclaration | es.Pattern) { + function internal(node: es.Pattern): es.Identifier[] { switch (node.type) { case 'ArrayPattern': return node.elements.flatMap(internal) case 'AssignmentPattern': return internal(node.left) case 'Identifier': - return node + return [node] case 'MemberExpression': throw new Error( 'Should not get MemberExpressions as part of the id for a VariableDeclarator' @@ -67,7 +67,10 @@ export function getIdentifiersFromVariableDeclaration(decl: es.VariableDeclarati } } - return decl.declarations.flatMap(({ id }) => internal(id)) + if (isVariableDeclaration(decl)) { + return decl.declarations.flatMap(({ id }) => internal(id)) + } + return internal(decl) } export function getDeclaredIdentifiers( From d3a7aec87004a0c265658a03205e522bf37b248d Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 21 May 2024 12:58:32 +0800 Subject: [PATCH 21/65] Remove operators related to lazy --- src/utils/operators.ts | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/src/utils/operators.ts b/src/utils/operators.ts index 65b8465db..2fc9d773c 100644 --- a/src/utils/operators.ts +++ b/src/utils/operators.ts @@ -11,7 +11,7 @@ import { PotentialInfiniteLoopError, PotentialInfiniteRecursionError } from '../errors/timeoutErrors' -import { Chapter, type NativeStorage, Thunk } from '../types' +import { Chapter, type NativeStorage } from '../types' import { callExpression, locationDummyNode } from './ast/astCreator' import * as create from './ast/astCreator' import { makeWrapper } from './makeWrapper' @@ -33,31 +33,6 @@ export function throwIfTimeout( } } -export function forceIt(val: Thunk | any): any { - if (val !== undefined && val !== null && val.isMemoized !== undefined) { - if (val.isMemoized) { - return val.memoizedValue - } - - const evaluatedValue = forceIt(val.f()) - - val.isMemoized = true - val.memoizedValue = evaluatedValue - - return evaluatedValue - } else { - return val - } -} - -export function delayIt(f: () => any): Thunk { - return { - isMemoized: false, - value: undefined, - f - } -} - export function callIfFuncAndRightArgs( candidate: any, line: number, @@ -87,8 +62,7 @@ export function callIfFuncAndRightArgs( ) } try { - const forcedArgs = args.map(forceIt) - return originalCandidate(...forcedArgs) + return originalCandidate(...args) } catch (error) { // if we already handled the error, simply pass it on if (!(error instanceof RuntimeSourceError || error instanceof ExceptionError)) { @@ -103,7 +77,6 @@ export function callIfFuncAndRightArgs( } export function boolOrErr(candidate: any, line: number, column: number, source: string | null) { - candidate = forceIt(candidate) const error = rttc.checkIfStatement(create.locationDummyNode(line, column, source), candidate) if (error === undefined) { return candidate @@ -119,7 +92,6 @@ export function unaryOp( column: number, source: string | null ) { - argument = forceIt(argument) const error = rttc.checkUnaryExpression( create.locationDummyNode(line, column, source), operator, @@ -153,8 +125,6 @@ export function binaryOp( column: number, source: string | null ) { - left = forceIt(left) - right = forceIt(right) const error = rttc.checkBinaryExpression( create.locationDummyNode(line, column, source), operator, @@ -213,7 +183,6 @@ export const callIteratively = (f: any, nativeStorage: NativeStorage, ...args: a const pastCalls: [string, any[]][] = [] while (true) { const dummy = locationDummyNode(line, column, source) - f = forceIt(f) if (typeof f === 'function') { if (f.transformedFunction !== undefined) { f = f.transformedFunction From 633ab99591bb47fe21d8e989d9e8a77d3ebd6782 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 21 May 2024 12:58:58 +0800 Subject: [PATCH 22/65] Remove Thunk and relocate Rule type --- src/types.ts | 15 --------------- src/utils/stringify.ts | 2 -- 2 files changed, 17 deletions(-) diff --git a/src/types.ts b/src/types.ts index 179650388..824b0b48b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -47,15 +47,6 @@ export interface SourceError { elaborate(): string } -export interface Rule { - name: string - disableFromChapter?: Chapter - disableForVariants?: Variant[] - checkers: { - [name: string]: (node: T, ancestors: Node[]) => SourceError[] - } -} - export interface Comment { type: 'Line' | 'Block' value: string @@ -261,12 +252,6 @@ export interface Environment { thisContext?: Value } -export interface Thunk { - value: any - isMemoized: boolean - f: () => any -} - export interface Error { status: 'error' } diff --git a/src/utils/stringify.ts b/src/utils/stringify.ts index 2c5762ddf..259ee05eb 100644 --- a/src/utils/stringify.ts +++ b/src/utils/stringify.ts @@ -1,7 +1,6 @@ import { MAX_LIST_DISPLAY_LENGTH } from '../constants' import Closure from '../cse-machine/closure' import type { Type, Value } from '../types' -import { forceIt } from './operators' export interface ArrayLike { replPrefix: string @@ -22,7 +21,6 @@ export const stringify = ( indent: number | string = 2, splitlineThreshold = 80 ): string => { - value = forceIt(value) if (typeof indent === 'string') { throw 'stringify with arbitrary indent string not supported' } From 6f49e70b7f0135779d3354371653948a053b7ce3 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 21 May 2024 14:26:38 +0800 Subject: [PATCH 23/65] Run format --- src/parser/source/rules/__tests__/rules.ts | 43 +++++++++------- src/parser/source/rules/bracesAroundFor.ts | 10 ++-- src/parser/source/rules/bracesAroundIfElse.ts | 6 +-- .../rules/forStatementMustHaveAllParts.ts | 9 ++-- src/parser/source/rules/noDeclareMutable.ts | 5 +- src/parser/source/rules/noEval.ts | 4 +- src/parser/source/rules/noHolesInArrays.ts | 4 +- src/parser/source/rules/noIfWithoutElse.ts | 8 +-- .../rules/noImplicitDeclareUndefined.ts | 4 +- .../source/rules/noImplicitReturnUndefined.ts | 7 +-- .../rules/noImportSpecifierWithDefault.ts | 5 +- src/parser/source/rules/noNull.ts | 4 +- src/parser/source/rules/noTypeofOperator.ts | 4 +- .../source/rules/noUnspecifiedLiteral.ts | 4 +- .../source/rules/singleVariableDeclaration.ts | 4 +- src/parser/source/rules/strictEquality.ts | 2 +- src/validator/__tests__/validator.ts | 50 +++++-------------- src/validator/validator.ts | 10 ++-- 18 files changed, 66 insertions(+), 117 deletions(-) diff --git a/src/parser/source/rules/__tests__/rules.ts b/src/parser/source/rules/__tests__/rules.ts index 9c90a4b81..d5822d2e2 100644 --- a/src/parser/source/rules/__tests__/rules.ts +++ b/src/parser/source/rules/__tests__/rules.ts @@ -1,8 +1,8 @@ -import { parseError } from "../../../.." -import { mockContext } from "../../../../mocks/context" -import { Chapter } from "../../../../types" -import { parse } from "../../../parser" -import type { Rule } from "../../../types" +import { parseError } from '../../../..' +import { mockContext } from '../../../../mocks/context' +import { Chapter } from '../../../../types' +import { parse } from '../../../parser' +import type { Rule } from '../../../types' import rules from '..' const chaptersToTest = [ @@ -10,12 +10,13 @@ const chaptersToTest = [ Chapter.SOURCE_2, Chapter.SOURCE_3, Chapter.SOURCE_4, - Chapter.LIBRARY_PARSER, + Chapter.LIBRARY_PARSER ] function testSnippet(code: string, expected: string, rule: Rule) { const chapterCases = chaptersToTest.map(chapter => { - const isExpectedToError = rule.disableFromChapter === undefined || chapter < rule.disableFromChapter + const isExpectedToError = + rule.disableFromChapter === undefined || chapter < rule.disableFromChapter const errStr = isExpectedToError ? 'error' : 'no error' return [ @@ -26,7 +27,7 @@ function testSnippet(code: string, expected: string, rule: Rule) { ] as [string, string, string, Chapter] }) - test.each(chapterCases)("%s", (_, code, expected, chapter) => { + test.each(chapterCases)('%s', (_, code, expected, chapter) => { const context = mockContext(chapter) parse(code, context) @@ -42,16 +43,20 @@ function testSnippet(code: string, expected: string, rule: Rule) { describe('Test rules', () => { rules.forEach(rule => { - if (!rule.testSnippets) return + if (!rule.testSnippets) return - const disableStr = rule.disableFromChapter ? `(Disabled for Chapter ${rule.disableFromChapter} and above)` : '(Always enabled)' - describe(`Testing ${rule.name} ${disableStr}`, () => { - if (rule.testSnippets!.length === 1) { - const [[code, expected]] = rule.testSnippets! - testSnippet(code, expected, rule) - } else { - rule.testSnippets!.forEach((snippet, i) => describe(`Testing Snippet ${i+1}`, () => testSnippet(...snippet, rule))) - } - }) + const disableStr = rule.disableFromChapter + ? `(Disabled for Chapter ${rule.disableFromChapter} and above)` + : '(Always enabled)' + describe(`Testing ${rule.name} ${disableStr}`, () => { + if (rule.testSnippets!.length === 1) { + const [[code, expected]] = rule.testSnippets! + testSnippet(code, expected, rule) + } else { + rule.testSnippets!.forEach((snippet, i) => + describe(`Testing Snippet ${i + 1}`, () => testSnippet(...snippet, rule)) + ) + } }) -}) \ No newline at end of file + }) +}) diff --git a/src/parser/source/rules/bracesAroundFor.ts b/src/parser/source/rules/bracesAroundFor.ts index 4884d261d..6fd0eed3c 100644 --- a/src/parser/source/rules/bracesAroundFor.ts +++ b/src/parser/source/rules/bracesAroundFor.ts @@ -1,9 +1,9 @@ import { generate } from 'astring' -import * as es from 'estree' +import type es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' -import { Rule } from '../../types' +import { ErrorSeverity, ErrorType, type Node, type SourceError } from '../../../types' +import type { Rule } from '../../types' const errorMsg = 'Missing curly braces around "for" block.' @@ -40,8 +40,8 @@ const bracesAroundFor: Rule = { let j = 0; for (let i = 0; i < 1; i = i + 1) j = j + 1; `, - errorMsg, - ], + errorMsg + ] ], checkers: { ForStatement(node: es.ForStatement, _ancestors: [Node]) { diff --git a/src/parser/source/rules/bracesAroundIfElse.ts b/src/parser/source/rules/bracesAroundIfElse.ts index 0100a4174..75312140e 100644 --- a/src/parser/source/rules/bracesAroundIfElse.ts +++ b/src/parser/source/rules/bracesAroundIfElse.ts @@ -1,9 +1,9 @@ import { generate } from 'astring' -import * as es from 'estree' +import type es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' -import { Rule } from '../../types' +import { ErrorSeverity, ErrorType, type Node, type SourceError } from '../../../types' +import type { Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' export class BracesAroundIfElseError implements SourceError { diff --git a/src/parser/source/rules/forStatementMustHaveAllParts.ts b/src/parser/source/rules/forStatementMustHaveAllParts.ts index 6a5e08c37..57eb92541 100644 --- a/src/parser/source/rules/forStatementMustHaveAllParts.ts +++ b/src/parser/source/rules/forStatementMustHaveAllParts.ts @@ -1,8 +1,8 @@ -import * as es from 'estree' +import type * as es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, SourceError } from '../../../types' -import { Rule } from '../../types' +import { ErrorSeverity, ErrorType, type SourceError } from '../../../types' +import type { Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' export class ForStatmentMustHaveAllParts implements SourceError { @@ -34,10 +34,9 @@ const forStatementMustHaveAllParts: Rule = { testSnippets: [ ['let i = 0; for (; i < 0; i = i + 1) {}', 'Line 1: Missing init expression in for statement.'], ['for (let i = 0; ; i = i + 1) {}', 'Line 1: Missing test expression in for statement.'], - ['for (let i = 0; i < 0;) {}', 'Line 1: Missing update expression in for statement'], + ['for (let i = 0; i < 0;) {}', 'Line 1: Missing update expression in for statement'] ], - checkers: { ForStatement(node) { const missingParts = ['init', 'test', 'update'].filter(part => node[part] === null) diff --git a/src/parser/source/rules/noDeclareMutable.ts b/src/parser/source/rules/noDeclareMutable.ts index 899f29db8..0acd6042e 100644 --- a/src/parser/source/rules/noDeclareMutable.ts +++ b/src/parser/source/rules/noDeclareMutable.ts @@ -36,10 +36,7 @@ const noDeclareMutable: Rule = { disableFromChapter: Chapter.SOURCE_3, testSnippets: [ - [ - 'let i = 0;', - 'Line 1: Mutable variable declaration using keyword \'let\' is not allowed.', - ], + ['let i = 0;', "Line 1: Mutable variable declaration using keyword 'let' is not allowed."] ], checkers: { diff --git a/src/parser/source/rules/noEval.ts b/src/parser/source/rules/noEval.ts index 1eb4e1414..21ad8eb50 100644 --- a/src/parser/source/rules/noEval.ts +++ b/src/parser/source/rules/noEval.ts @@ -25,9 +25,7 @@ export class NoEval implements SourceError { const noEval: Rule = { name: 'no-eval', - testSnippets: [ - ['eval("0;");', 'Line 1: eval is not allowed.'] - ], + testSnippets: [['eval("0;");', 'Line 1: eval is not allowed.']], checkers: { Identifier(node: es.Identifier, _ancestors: [Node]) { diff --git a/src/parser/source/rules/noHolesInArrays.ts b/src/parser/source/rules/noHolesInArrays.ts index 26a678a35..6f01449b9 100644 --- a/src/parser/source/rules/noHolesInArrays.ts +++ b/src/parser/source/rules/noHolesInArrays.ts @@ -29,9 +29,7 @@ export class NoHolesInArrays implements SourceError { const noHolesInArrays: Rule = { name: 'no-holes-in-arrays', - testSnippets: [ - ['[0,,0];', 'Line 1: No holes are allowed in array literals.'] - ], + testSnippets: [['[0,,0];', 'Line 1: No holes are allowed in array literals.']], checkers: { ArrayExpression(node: es.ArrayExpression) { diff --git a/src/parser/source/rules/noIfWithoutElse.ts b/src/parser/source/rules/noIfWithoutElse.ts index 713b70b9f..814c4a849 100644 --- a/src/parser/source/rules/noIfWithoutElse.ts +++ b/src/parser/source/rules/noIfWithoutElse.ts @@ -1,9 +1,9 @@ import { generate } from 'astring' -import * as es from 'estree' +import type es from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { Chapter, ErrorSeverity, ErrorType, Node, SourceError } from '../../../types' -import { Rule } from '../../types' +import { Chapter, ErrorSeverity, ErrorType, type Node, type SourceError } from '../../../types' +import type { Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' export class NoIfWithoutElseError implements SourceError { @@ -45,7 +45,7 @@ const noIfWithoutElse: Rule = { } `, 'Line 3: Missing "else" in "if-else" statement.' - ] + ] ], checkers: { IfStatement(node: es.IfStatement, _ancestors: [Node]) { diff --git a/src/parser/source/rules/noImplicitDeclareUndefined.ts b/src/parser/source/rules/noImplicitDeclareUndefined.ts index 84ff927ed..5271a9ee8 100644 --- a/src/parser/source/rules/noImplicitDeclareUndefined.ts +++ b/src/parser/source/rules/noImplicitDeclareUndefined.ts @@ -33,9 +33,7 @@ export class NoImplicitDeclareUndefinedError implements SourceError { const noImplicitDeclareUndefined: Rule = { name: 'no-implicit-declare-undefined', - testSnippets: [ - ['let i;', 'Line 1: Missing value in variable declaration.'] - ], + testSnippets: [['let i;', 'Line 1: Missing value in variable declaration.']], checkers: { VariableDeclaration(node: es.VariableDeclaration, ancestors: es.Node[]) { diff --git a/src/parser/source/rules/noImplicitReturnUndefined.ts b/src/parser/source/rules/noImplicitReturnUndefined.ts index 419942c40..5d0abdfa1 100644 --- a/src/parser/source/rules/noImplicitReturnUndefined.ts +++ b/src/parser/source/rules/noImplicitReturnUndefined.ts @@ -31,12 +31,7 @@ export class NoImplicitReturnUndefinedError implements SourceError { const noImplicitReturnUndefined: Rule = { name: 'no-implicit-return-undefined', - testSnippets: [ - [ - 'function f() { return; }', - 'Line 1: Missing value in return statement.' - ] - ], + testSnippets: [['function f() { return; }', 'Line 1: Missing value in return statement.']], checkers: { ReturnStatement(node: es.ReturnStatement, _ancestors: [Node]) { diff --git a/src/parser/source/rules/noImportSpecifierWithDefault.ts b/src/parser/source/rules/noImportSpecifierWithDefault.ts index 849b7a553..826496b81 100644 --- a/src/parser/source/rules/noImportSpecifierWithDefault.ts +++ b/src/parser/source/rules/noImportSpecifierWithDefault.ts @@ -29,10 +29,7 @@ const noImportSpecifierWithDefault: Rule = { name: 'no-import-default-specifier', disableFromChapter: syntaxBlacklist['ImportDefaultSpecifier'], testSnippets: [ - [ - 'import { default as a } from "./a.js";', - 'Line 1: Import default specifiers are not allowed.' - ], + ['import { default as a } from "./a.js";', 'Line 1: Import default specifiers are not allowed.'] ], checkers: { diff --git a/src/parser/source/rules/noNull.ts b/src/parser/source/rules/noNull.ts index f3c91b3b7..3ea929330 100644 --- a/src/parser/source/rules/noNull.ts +++ b/src/parser/source/rules/noNull.ts @@ -26,9 +26,7 @@ export class NoNullError implements SourceError { const noNull: Rule = { name: 'no-null', disableFromChapter: Chapter.SOURCE_2, - testSnippets: [ - [ 'null;', 'Line 1: null literals are not allowed.'] - ], + testSnippets: [['null;', 'Line 1: null literals are not allowed.']], checkers: { Literal(node: es.Literal, _ancestors: [Node]) { if (node.value === null) { diff --git a/src/parser/source/rules/noTypeofOperator.ts b/src/parser/source/rules/noTypeofOperator.ts index 4c4677283..0b23453f0 100644 --- a/src/parser/source/rules/noTypeofOperator.ts +++ b/src/parser/source/rules/noTypeofOperator.ts @@ -8,9 +8,7 @@ const noTypeofOperator: Rule = { name: 'no-typeof-operator', disableForVariants: [Variant.TYPED], - testSnippets: [ - ['typeof "string";', "Line 1: Operator 'typeof' is not allowed."] - ], + testSnippets: [['typeof "string";', "Line 1: Operator 'typeof' is not allowed."]], checkers: { UnaryExpression(node: es.UnaryExpression) { diff --git a/src/parser/source/rules/noUnspecifiedLiteral.ts b/src/parser/source/rules/noUnspecifiedLiteral.ts index 864244c0d..1455b9fcd 100644 --- a/src/parser/source/rules/noUnspecifiedLiteral.ts +++ b/src/parser/source/rules/noUnspecifiedLiteral.ts @@ -32,9 +32,7 @@ export class NoUnspecifiedLiteral implements SourceError { const noUnspecifiedLiteral: Rule = { name: 'no-unspecified-literal', - testSnippets: [ - ['const x = /hi/;', "Line 1: 'RegExp' literals are not allowed."] - ], + testSnippets: [['const x = /hi/;', "Line 1: 'RegExp' literals are not allowed."]], checkers: { Literal(node: es.Literal, _ancestors: [Node]) { if (node.value !== null && !specifiedLiterals.includes(typeof node.value)) { diff --git a/src/parser/source/rules/singleVariableDeclaration.ts b/src/parser/source/rules/singleVariableDeclaration.ts index 884d49f5f..76f317c54 100644 --- a/src/parser/source/rules/singleVariableDeclaration.ts +++ b/src/parser/source/rules/singleVariableDeclaration.ts @@ -35,9 +35,7 @@ export class MultipleDeclarationsError implements SourceError { const singleVariableDeclaration: Rule = { name: 'single-variable-declaration', - testSnippets: [ - ['let i = 0, j = 0;', 'Line 1: Multiple declarations in a single statement.'] - ], + testSnippets: [['let i = 0, j = 0;', 'Line 1: Multiple declarations in a single statement.']], checkers: { VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) { diff --git a/src/parser/source/rules/strictEquality.ts b/src/parser/source/rules/strictEquality.ts index 1f9e56664..5e3ae9120 100644 --- a/src/parser/source/rules/strictEquality.ts +++ b/src/parser/source/rules/strictEquality.ts @@ -32,7 +32,7 @@ const strictEquality: Rule = { testSnippets: [ ['const x = 1 == 2;', 'Line 1: Use === instead of ==.'], - ['const x = 1 != 2;', 'Line 1: Use !== instead of !=.'], + ['const x = 1 != 2;', 'Line 1: Use !== instead of !=.'] ], checkers: { diff --git a/src/validator/__tests__/validator.ts b/src/validator/__tests__/validator.ts index 7a8ac7efa..181af29b4 100644 --- a/src/validator/__tests__/validator.ts +++ b/src/validator/__tests__/validator.ts @@ -146,7 +146,10 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { 'const a = function() { return b; }', 'Line 1: Name b not declared.' ], - ['FunctionExpression parameters are valid declarations', 'const a = function(b) { return b; }'], + [ + 'FunctionExpression parameters are valid declarations', + 'const a = function(b) { return b; }' + ], ['FunctionExpressions can call themselves recursively', 'const a = function() { a(); }'], // For loops @@ -171,19 +174,9 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { 'for (let i = 0; i < 0; i = i + 1) { a; }', 'Line 1: Name a not declared.' ], - [ - 'for in statement declaration is valid', - 'for (const [x, y] in []) { x; y; }', - ], - [ - 'for in statement is checked', - 'for (const x in a) { x; }', - 'Line 1: Name a not declared.' - ], - [ - 'for of statement declaration is valid', - 'for (const {x, y} of []) { x; y; }', - ], + ['for in statement declaration is valid', 'for (const [x, y] in []) { x; y; }'], + ['for in statement is checked', 'for (const x in a) { x; }', 'Line 1: Name a not declared.'], + ['for of statement declaration is valid', 'for (const {x, y} of []) { x; y; }'], [ 'destructuring expression in for of is checked 1', ` @@ -196,32 +189,13 @@ describe(`Test ${checkForUndefinedVariables.name}`, () => { 'for ({x, y} of []) {}', 'Line 1: Name x not declared.' ], - [ - 'for of statement is checked', - 'for (const x in a) { x; }', - 'Line 1: Name a not declared.' - ], + ['for of statement is checked', 'for (const x in a) { x; }', 'Line 1: Name a not declared.'], // Try statement - [ - 'try statement body is checked', - 'try { a; } catch(e) {}', - 'Line 1: Name a not declared.' - ], - [ - 'catch clauses are checked', - 'try {} catch(e) { a; }', - 'Line 1: Name a not declared.' - ], - [ - 'catch clause parameter is a valid identifier', - 'try {} catch(e) { e; }', - ], - [ - 'finally blocks are checked', - 'try {} finally { a; }', - 'Line 1: Name a not declared.' - ], + ['try statement body is checked', 'try { a; } catch(e) {}', 'Line 1: Name a not declared.'], + ['catch clauses are checked', 'try {} catch(e) { a; }', 'Line 1: Name a not declared.'], + ['catch clause parameter is a valid identifier', 'try {} catch(e) { e; }'], + ['finally blocks are checked', 'try {} finally { a; }', 'Line 1: Name a not declared.'], // BlockStatements and Programs [ diff --git a/src/validator/validator.ts b/src/validator/validator.ts index 720e819e0..5491903d9 100644 --- a/src/validator/validator.ts +++ b/src/validator/validator.ts @@ -193,15 +193,11 @@ export function checkForUndefinedVariables( ) { identifiersIntroducedByNode.set( node, - new Set( - mapIdentifiersToNames(node.params.flatMap(getIdentifiersFromVariableDeclaration)) - ) + new Set(mapIdentifiersToNames(node.params.flatMap(getIdentifiersFromVariableDeclaration))) ) } - function processFor( - node: es.ForOfStatement | es.ForInStatement - ) { + function processFor(node: es.ForOfStatement | es.ForInStatement) { if (isVariableDeclaration(node.left)) { identifiersIntroducedByNode.set( node, @@ -247,7 +243,7 @@ export function checkForUndefinedVariables( } } }, - Program: processBlock, + Program: processBlock }) const nativeInternalNames = new Set(Object.values(globalIds).map(({ name }) => name)) From cddbe503ef0e5c66cbfcbd5d9ecb075ea32b1fe7 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 21 May 2024 14:26:57 +0800 Subject: [PATCH 24/65] Update tests to use new testing utils --- src/__tests__/code-snippets.ts | 197 +- .../__snapshots__/allowed-syntax.ts.snap | 4565 ----------------- src/parser/__tests__/allowed-syntax.ts | 72 +- src/parser/__tests__/disallowed-syntax.ts | 189 +- src/parser/__tests__/fullTS.ts | 66 +- src/parser/types.ts | 15 +- src/repl/transpiler.ts | 4 +- src/runner/__tests__/runners.ts | 106 +- .../__snapshots__/parser-errors.ts.snap | 27 - src/stdlib/__tests__/parser-errors.ts | 10 +- src/utils/__tests__/stringify.ts | 11 +- src/utils/testing/testers.ts | 72 + 12 files changed, 383 insertions(+), 4951 deletions(-) delete mode 100644 src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap delete mode 100644 src/stdlib/__tests__/__snapshots__/parser-errors.ts.snap diff --git a/src/__tests__/code-snippets.ts b/src/__tests__/code-snippets.ts index 9fbbeb4fd..ed8cbe1ab 100644 --- a/src/__tests__/code-snippets.ts +++ b/src/__tests__/code-snippets.ts @@ -5,7 +5,7 @@ import { mockContext } from '../mocks/context' import { Chapter, type Value } from '../types' import { stripIndent } from '../utils/formatters' import { expectFinishedResult, type TestBuiltins } from '../utils/testing' -import { testMultipleCases } from '../utils/testing/testers' +import { expectResultsToEqual, testMultipleCases } from '../utils/testing/testers' async function testCodeSnippet( code: string, @@ -26,138 +26,134 @@ async function testCodeSnippet( } describe('Test basic code snippets', () => { - type TestCase = - | [code: string, expected: Value] - | [code: string, expected: Value, chapter: Chapter] - testMultipleCases( - [ - ['Empty code returns undefined', '', undefined], - ['Single string evaluates to itself', '"42";', '42'], - ['Multiline string evaluates to itself', '"1\\n1";', '1\n1'], - ['Single number evaluates to itself', '42;', 42], - ['Single boolean evaluates to itself', 'true;', true], + expectResultsToEqual([ + ['Empty code returns undefined', '', undefined], + ['Single string evaluates to itself', '"42";', '42'], + ['Multiline string evaluates to itself', '"1\\n1";', '1\n1'], + ['Single number evaluates to itself', '42;', 42], + ['Single boolean evaluates to itself', 'true;', true], - [ - 'Assignment has value', - ` + [ + 'Assignment has value', + ` let a = 1; let b = a = 4; b === 4 && a === 4; `, - true, - Chapter.SOURCE_3 - ], + true, + Chapter.SOURCE_3 + ], - // Arrays - [ - 'Array assignment has value', - ` + // Arrays + [ + 'Array assignment has value', + ` let arr = []; const a = arr[0] = 1; const b = arr[1] = arr[2] = 4; arr[0] === 1 && arr[1] === 4 && arr[2] === 4; `, - true, - Chapter.SOURCE_3 - ], - [ - 'Accessing array at non-existent index returns undefined', - ` + true, + Chapter.SOURCE_3 + ], + [ + 'Accessing array at non-existent index returns undefined', + ` const a = [1,2]; a[3]; `, - undefined, - Chapter.SOURCE_4 - ], + undefined, + Chapter.SOURCE_4 + ], - // Objects - [ - 'Simple object assignment and retrieval', - ` + // Objects + [ + 'Simple object assignment and retrieval', + ` const o = {}; o.a = 1; o.a; `, - 1, - Chapter.LIBRARY_PARSER - ], - [ - 'Deep object assignment and retrieval', - ` + 1, + Chapter.LIBRARY_PARSER + ], + [ + 'Deep object assignment and retrieval', + ` const o = {}; o.a = {}; o.a.b = {}; o.a.b.c = "string"; o.a.b.c; `, - 'string', - Chapter.LIBRARY_PARSER - ], - [ - 'Accessing non-existent property on object returns undefined', - ` + 'string', + Chapter.LIBRARY_PARSER + ], + [ + 'Accessing non-existent property on object returns undefined', + ` const o = {}; o.nonexistent; `, - undefined, - Chapter.LIBRARY_PARSER - ], + undefined, + Chapter.LIBRARY_PARSER + ], - // Control structures - [ - 'true if with empty block works', - ` + // Control structures + [ + 'true if with empty block works', + ` if (true) { } else {} `, - undefined - ], - [ - 'true if with non-empty block works', - ` + undefined + ], + [ + 'true if with non-empty block works', + ` if (true) { 1; } else {} `, - 1 - ], - [ - 'false if with empty else works', - ` + 1 + ], + [ + 'false if with empty else works', + ` if (false) {} else {} `, - undefined - ], - [ - 'false if with non empty else works', - ` + undefined + ], + [ + 'false if with non empty else works', + ` if (false) {} else { 2; } `, - 2 - ], + 2 + ], - // Builtins, - ['Display returns the value it is displaying', '25*display(1+1);', 50], - [ - 'apply_in_underlying_javascript', - 'apply_in_underlying_javascript((a, b, c) => a * b * c, list(2, 5, 6));', - 60, - Chapter.SOURCE_4 - ], + // Builtins, + ['Display returns the value it is displaying', '25*display(1+1);', 50], + [ + 'apply_in_underlying_javascript', + 'apply_in_underlying_javascript((a, b, c) => a * b * c, list(2, 5, 6));', + 60, + Chapter.SOURCE_4 + ], - // General snippets - [ - 'Factorial arrow function', - ` + // General snippets + [ + 'Factorial arrow function', + ` const fac = (i) => i === 1 ? 1 : i * fac(i-1); fac(5); `, - 120 - ], - [ - 'Rest parameters work', - ` + 120 + ], + [ + 'Rest parameters work', + ` function rest(a, b, ...c) { let sum = a + b; for (let i = 0; i < array_length(c); i = i + 1) { @@ -168,12 +164,12 @@ describe('Test basic code snippets', () => { rest(1, 2); // no error rest(1, 2, ...[3, 4, 5], ...[6, 7], ...[]); `, - 28, - Chapter.SOURCE_3 - ], - [ - 'Can overwrite lets when assignment is allowed', - ` + 28, + Chapter.SOURCE_3 + ], + [ + 'Can overwrite lets when assignment is allowed', + ` function test() { let variable = false; variable = true; @@ -181,17 +177,10 @@ describe('Test basic code snippets', () => { } test(); `, - true, - Chapter.SOURCE_3 - ] - ], - async c => { - const chapter = c.length === 3 ? c[2] : Chapter.SOURCE_1 - const [code, expected] = c - - return testCodeSnippet(code, expected, chapter) - } - ) + true, + Chapter.SOURCE_3 + ] + ]) }) describe('Test equal', () => { diff --git a/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap b/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap deleted file mode 100644 index ae1003131..000000000 --- a/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap +++ /dev/null @@ -1,4565 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Syntaxes are allowed in the chapter they are introduced 0: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - null, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 0: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 1: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"function name(a, b) {\\\\n const sum = a + b;\\\\n if (sum > 1) {\\\\n return sum;\\\\n } else {\\\\n if (a % 2 === 0) {\\\\n return -1;\\\\n } else if (b % 2 === 0) {\\\\n return 1;\\\\n } else {\\\\n return a > b ? 0 : -2;\\\\n }\\\\n }\\\\n}\\\\nname(1, 2);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "name", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "b", - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "block", - Array [ - Array [ - "sequence", - Array [ - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "sum", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "b", - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - ">", - Array [ - Array [ - "name", - Array [ - "sum", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "name", - Array [ - "sum", - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "===", - Array [ - Array [ - "binary_operator_combination", - Array [ - "%", - Array [ - Array [ - "name", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 0, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "unary_operator_combination", - Array [ - "-unary", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "===", - Array [ - Array [ - "binary_operator_combination", - Array [ - "%", - Array [ - Array [ - "name", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 0, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "conditional_expression", - Array [ - Array [ - "binary_operator_combination", - Array [ - ">", - Array [ - Array [ - "name", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "b", - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 0, - null, - ], - ], - Array [ - Array [ - "unary_operator_combination", - Array [ - "-unary", - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "application", - Array [ - Array [ - "name", - Array [ - "name", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 1: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "function name(a, b) { - const sum = a + b; - if (sum > 1) { - return sum; - } else { - if (a % 2 === 0) { - return -1; - } else if (b % 2 === 0) { - return 1; - } else { - return a > b ? 0 : -2; - } - } -} -name(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 2: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"(() => true)();\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "application", - Array [ - Array [ - "lambda_expression", - Array [ - null, - Array [ - Array [ - "return_statement", - Array [ - Array [ - "literal", - Array [ - true, - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - null, - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 2: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "(() => true)();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 3: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"((x, y) => { return x + y; })(1, 2);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "application", - Array [ - Array [ - "lambda_expression", - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 3: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "((x, y) => { return x + y; })(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 4: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"true;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "literal", - Array [ - true, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 4: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 5: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"false;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "literal", - Array [ - false, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 5: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 6: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"'a string \\\\\\"\\\\\\" \\\\\\\\'\\\\\\\\'';\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "literal", - Array [ - "a string \\"\\" ''", - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 6: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "'a string \\"\\" \\\\'\\\\'';", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "a string \\"\\" ''", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 7: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"31.4 + (-3.14e10) * -1 % 2 / 1.5;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "literal", - Array [ - 31.4, - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "/", - Array [ - Array [ - "binary_operator_combination", - Array [ - "%", - Array [ - Array [ - "binary_operator_combination", - Array [ - "*", - Array [ - Array [ - "unary_operator_combination", - Array [ - "-unary", - Array [ - Array [ - "literal", - Array [ - 31400000000, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "unary_operator_combination", - Array [ - "-unary", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 1.5, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 7: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "31.4 + (-3.14e10) * -1 % 2 / 1.5;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 31.4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 8: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"1 === 1 && 1 < 2 && 1 <= 2 && 2 >= 1 && 2 > 1 || false;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "logical_composition", - Array [ - "||", - Array [ - Array [ - "logical_composition", - Array [ - "&&", - Array [ - Array [ - "logical_composition", - Array [ - "&&", - Array [ - Array [ - "logical_composition", - Array [ - "&&", - Array [ - Array [ - "logical_composition", - Array [ - "&&", - Array [ - Array [ - "binary_operator_combination", - Array [ - "===", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "<=", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - ">=", - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - ">", - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - false, - null, - ], - ], - null, - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 8: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "1 === 1 && 1 < 2 && 1 <= 2 && 2 >= 1 && 2 > 1 || false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 9: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"true ? 1 : 2;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "conditional_expression", - Array [ - Array [ - "literal", - Array [ - true, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 9: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "true ? 1 : 2;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 10: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "null;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: null literals are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 10: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"null;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "literal", - Array [ - null, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 10: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "null;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 11: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "pair(1, null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: null literals are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 11: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"pair(1, null);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "application", - Array [ - Array [ - "name", - Array [ - "pair", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - null, - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 11: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "pair(1, null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - null, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 12: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "list(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Name list not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 12: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"list(1);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "application", - Array [ - Array [ - "name", - Array [ - "list", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 12: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "list(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - null, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 13: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "export function f(x) { - return x; -} -f(5);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Export named declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 13: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"export function f(x) {\\\\n return x;\\\\n}\\\\nf(5);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "export_named_declaration", - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "application", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 13: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "export function f(x) { - return x; -} -f(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 14: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "export const x = 1; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Export named declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 14: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"export const x = 1;\\\\nx;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "export_named_declaration", - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 14: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "export const x = 1; -x;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 15: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -while (i < 5) { - i = i + 1; -} -i;", - "displayResult": Array [], - "numErrors": 3, - "parsedErrors": "Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 3: Assignment expressions are not allowed -Line 2: While statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 15: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let i = 1;\\\\nwhile (i < 5) {\\\\n i = i + 1;\\\\n}\\\\ni;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "while_loop", - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 15: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -while (i < 5) { - i = i + 1; -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 16: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -for (i = 1; i < 5; i = i + 1) { -} -i;", - "displayResult": Array [], - "numErrors": 4, - "parsedErrors": "Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Assignment expressions are not allowed -Line 2: Assignment expressions are not allowed -Line 2: For statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 16: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let i = 1;\\\\nfor (i = 1; i < 5; i = i + 1) {\\\\n}\\\\ni;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "for_loop", - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "sequence", - Array [ - null, - null, - ], - ], - null, - ], - ], - ], - ], - ], - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 16: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -for (i = 1; i < 5; i = i + 1) { -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 17: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -for (let j = 0; j < 5; j = j + 1) { - if (j < 1) { - continue; - } else { - i = i + 1; - if (j > 2) { - break; - } - } -} -i;", - "displayResult": Array [], - "numErrors": 8, - "parsedErrors": "Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Assignment expressions are not allowed -Line 4: Continue statements are not allowed -Line 6: Assignment expressions are not allowed -Line 8: Break statements are not allowed -Line 7: Missing \\"else\\" in \\"if-else\\" statement. -Line 2: For statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 17: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let i = 1;\\\\nfor (let j = 0; j < 5; j = j + 1) {\\\\n if (j < 1) {\\\\n continue;\\\\n } else {\\\\n i = i + 1;\\\\n if (j > 2) {\\\\n break;\\\\n }\\\\n }\\\\n}\\\\ni;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "for_loop", - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 0, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "continue_statement", - null, - ], - Array [ - Array [ - "sequence", - Array [ - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - ">", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "break_statement", - null, - ], - Array [ - Array [ - "sequence", - Array [ - null, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - ], - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 17: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -for (let j = 0; j < 5; j = j + 1) { - if (j < 1) { - continue; - } else { - i = i + 1; - if (j > 2) { - break; - } - } -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 18: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "[];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Array expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 18: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"[];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "array_expression", - Array [ - null, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 18: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "[];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 19: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, 2, 3];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Array expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 19: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"[1, 2, 3];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 19: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, 2, 3];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - 3, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 20: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, 2, 3][1];", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Array expressions are not allowed -Line 1: Member expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 20: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"[1, 2, 3][1];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 20: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, 2, 3][1];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 21: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2, 3]; -x[1];", - "displayResult": Array [], - "numErrors": 3, - "parsedErrors": "Line 1: Array expressions are not allowed -Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Member expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 21: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = [1, 2, 3];\\\\nx[1];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 21: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2, 3]; -x[1];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 22: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2, 3]; -x[1] = 4;", - "displayResult": Array [], - "numErrors": 4, - "parsedErrors": "Line 1: Array expressions are not allowed -Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Member expressions are not allowed -Line 2: Assignment expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 22: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = [1, 2, 3];\\\\nx[1] = 4;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_assignment", - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 4, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 22: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2, 3]; -x[1] = 4;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 23: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -let y = 4; -let z = 5; -x = y = z = 6; -x;", - "displayResult": Array [], - "numErrors": 6, - "parsedErrors": "Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Mutable variable declaration using keyword 'let' is not allowed. -Line 3: Mutable variable declaration using keyword 'let' is not allowed. -Line 4: Assignment expressions are not allowed -Line 4: Assignment expressions are not allowed -Line 4: Assignment expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 23: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = 3;\\\\nlet y = 4;\\\\nlet z = 5;\\\\nx = y = z = 6;\\\\nx;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 4, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "z", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "z", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 6, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 23: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -let y = 4; -let z = 5; -x = y = z = 6; -x;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 24: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y, ...z) { - return x + y; -} -f(...[1, 2]);", - "displayResult": Array [], - "numErrors": 3, - "parsedErrors": "Line 1: Rest elements are not allowed -Line 4: Array expressions are not allowed -Line 4: Spread elements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 24: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"function f(x, y, ...z) {\\\\n return x + y;\\\\n}\\\\nf(...[1, 2]);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - Array [ - Array [ - "rest_element", - Array [ - Array [ - "name", - Array [ - "z", - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "application", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "spread_element", - Array [ - Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 25: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({});", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 25: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({});\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_expression", - Array [ - null, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 26: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2});", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 26: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({a: 1, b: 2});\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 27: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2})['a'];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 27: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({a: 1, b: 2})['a'];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 28: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2}).a;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Object expressions are not allowed -Line 1: Dot abbreviations are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 28: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({a: 1, b: 2}).a;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 29: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({'a': 1, 'b': 2}).a;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Object expressions are not allowed -Line 1: Dot abbreviations are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 29: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({'a': 1, 'b': 2}).a;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "literal", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 30: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({1: 1, 2: 2})['1'];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 30: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({1: 1, 2: 2})['1'];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "1", - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 31: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "const key = 'a'; -({a: 1, b: 2})[key];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 31: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"const key = 'a';\\\\n({a: 1, b: 2})[key];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "key", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "key", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 32: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -x.a = 3;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Object expressions are not allowed -Line 2: Dot abbreviations are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 32: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = {a: 1, b: 2};\\\\nx.a = 3;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_assignment", - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 33: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -x['a'] = 3;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 33: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = {a: 1, b: 2};\\\\nx['a'] = 3;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_assignment", - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 34: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -const key = 'a'; -x[key] = 3;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 34: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = {a: 1, b: 2};\\\\nconst key = 'a';\\\\nx[key] = 3;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "key", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_assignment", - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "key", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 35: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "import defaultExport from \\"one_module\\";", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Import default specifiers are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 35: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"import defaultExport from \\\\\\"one_module\\\\\\";\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "import_declaration", - Array [ - Array [ - Array [ - "default", - null, - ], - null, - ], - Array [ - "one_module", - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 35: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "import defaultExport from \\"one_module\\";", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 36: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "export default function f(x) { - return x; -} -f(5);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Export default declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 36: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"export default function f(x) {\\\\n return x;\\\\n}\\\\nf(5);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "export_default_declaration", - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "application", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 36: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "export default function f(x) { - return x; -} -f(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 37: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "const x = 1; -export default x; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Export default declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 37: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"const x = 1;\\\\nexport default x;\\\\nx;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "export_default_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 37: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "const x = 1; -export default x; -x;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 38: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "function square(x) { - return x * x; -} -export { square as default };", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Export default declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 38: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"function square(x) {\\\\n return x * x;\\\\n}\\\\nexport { square as default };\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "square", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "*", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "export_named_declaration", - Array [ - Array [ - Array [ - "name", - Array [ - "default", - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 38: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "function square(x) { - return x * x; -} -export { square as default };", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 39: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "import { default as x } from './a.js';", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Import default specifiers are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 40: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "import * as a from 'one_module';", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Namespace imports are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/parser/__tests__/allowed-syntax.ts b/src/parser/__tests__/allowed-syntax.ts index 07d913834..95426862e 100644 --- a/src/parser/__tests__/allowed-syntax.ts +++ b/src/parser/__tests__/allowed-syntax.ts @@ -1,10 +1,15 @@ +import { mockContext } from '../../mocks/context' import { Chapter } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { snapshotFailure, snapshotSuccess } from '../../utils/testing' +import { parse } from '../parser' -jest.mock('../../modules/loader/loaders') +function testParse(chapter: Chapter, code: string) { + const context = mockContext(chapter) + parse(code, context) -test.each([ + return context.errors.length === 0 +} + +describe.each([ [Chapter.SOURCE_1, ''], [ @@ -91,20 +96,6 @@ test.each([ ` ], - [ - Chapter.SOURCE_2, - ` - pair(1, null); - ` - ], - - [ - Chapter.SOURCE_2, - ` - list(1); - ` - ], - [ Chapter.SOURCE_2, ` @@ -331,34 +322,19 @@ test.each([ [ Chapter.LIBRARY_PARSER, - ` - import { default as x } from './a.js'; - `, - // Skip the success tests because the imported file does not exist. - true - ], - [Chapter.LIBRARY_PARSER, `import * as a from 'one_module';`, true] -] as [Chapter, string, boolean | undefined][])( - 'Syntaxes are allowed in the chapter they are introduced %#', - (chapter: Chapter, snippet: string, skipSuccessTests: boolean = false) => { - snippet = stripIndent(snippet) - const parseSnippet = `parse(${JSON.stringify(snippet)});` - const tests: ReturnType[] = [] - if (!skipSuccessTests) { - tests.push( - snapshotSuccess(snippet, { chapter, native: chapter !== Chapter.LIBRARY_PARSER }, 'passes') - ) - tests.push( - snapshotSuccess( - parseSnippet, - { chapter: Math.max(4, chapter), native: true }, - 'parse passes' - ) - ) - } - if (chapter > 1) { - tests.push(snapshotFailure(snippet, { chapter: chapter - 1 }, 'fails a chapter below')) - } - return Promise.all(tests) + `import { default as x } from './a.js';`, + ], + [Chapter.LIBRARY_PARSER, `import * as a from 'one_module';`] +])("%#", (chapter, code) => { + test(`Should pass for Chapter ${chapter}`, () => { + expect(testParse(chapter, code)).toEqual(true) + }) + + if (chapter > 1) { + // Parsing should fail for the chapter below the one they + // are introduced in + test(`Should fail for Chapter ${chapter - 1}`, () => { + expect(testParse(chapter - 1, code)).toEqual(false) + }) } -) +}) diff --git a/src/parser/__tests__/disallowed-syntax.ts b/src/parser/__tests__/disallowed-syntax.ts index 8c615c430..cca034fa9 100644 --- a/src/parser/__tests__/disallowed-syntax.ts +++ b/src/parser/__tests__/disallowed-syntax.ts @@ -1,127 +1,102 @@ -import { Chapter } from "../../types"; -import { expectParsedError, expectParsedErrorsToEqual, expectResult } from "../../utils/testing/testers"; +import { Chapter } from '../../types' +import { + expectParsedError, + expectParsedErrorsToEqual, + expectResult +} from '../../utils/testing/testers' -expectParsedErrorsToEqual([ - [ - 'Cannot leave while loop predicate blank', - 'while() { x; }', - "Line 1: SyntaxError: Unexpected token (1:6)" - ], - [ - 'Cannot have incomplete statements', - '5', - 'Line 1: Missing semicolon at the end of statement' - ], - [ - 'No try statements', - 'try {} catch(e) {}', - 'Line 1: Catch clauses are not allowed.\nLine 1: Try statements are not allowed.' - ], - [ - 'No for of loops', - ` +expectParsedErrorsToEqual( + [ + [ + 'Cannot leave while loop predicate blank', + 'while() { x; }', + 'Line 1: SyntaxError: Unexpected token (1:6)' + ], + ['Cannot have incomplete statements', '5', 'Line 1: Missing semicolon at the end of statement'], + [ + 'No try statements', + 'try {} catch(e) {}', + 'Line 1: Catch clauses are not allowed.\nLine 1: Try statements are not allowed.' + ], + [ + 'No for of loops', + ` let x = []; for (const each of x) {} `, - 'Line 3: For of statements are not allowed.' - ], - [ - 'No for in loops', - ` + 'Line 3: For of statements are not allowed.' + ], + [ + 'No for in loops', + ` let x = []; for (const each in x) {} `, - 'Line 3: For in statements are not allowed.' - ], - [ - 'No classes', - 'class C {}', - 'Line 1: Class declarations are not allowed.' - ], - [ - 'No sequence expressions', - '(1, 2);', - 'Line 1: Sequence expressions are not allowed.' - ], - [ - 'No this', - ` + 'Line 3: For in statements are not allowed.' + ], + ['No classes', 'class C {}', 'Line 1: Class declarations are not allowed.'], + ['No sequence expressions', '(1, 2);', 'Line 1: Sequence expressions are not allowed.'], + [ + 'No this', + ` function box() { this[0] = 0; } `, - 'Line 3: \'this\' expressions are not allowed.' - ], - [ - 'No new', - 'const b = new box();', - 'Line 1: New expressions are not allowed.' - ], - [ - 'No assigning to reserved keywords', - 'package = 5;', - "Line 1: SyntaxError: The keyword 'package' is reserved (1:0)" - ], - [ - 'No declaring reserved keywords', - 'let yield = 5;', - "Line 1: SyntaxError: The keyword 'yield' is reserved (1:4)" - ], - [ - 'No interfaces', - 'interface Box {}', - "Line 1: SyntaxError: The keyword 'interface' is reserved (1:0)" - ], - [ - 'No spread in array expressions', - '[...[]];', - 'Line 1: Spread syntax is not allowed in arrays.' - ], - [ - 'No destructuring declarations', - ` + "Line 3: 'this' expressions are not allowed." + ], + ['No new', 'const b = new box();', 'Line 1: New expressions are not allowed.'], + [ + 'No assigning to reserved keywords', + 'package = 5;', + "Line 1: SyntaxError: The keyword 'package' is reserved (1:0)" + ], + [ + 'No declaring reserved keywords', + 'let yield = 5;', + "Line 1: SyntaxError: The keyword 'yield' is reserved (1:4)" + ], + [ + 'No interfaces', + 'interface Box {}', + "Line 1: SyntaxError: The keyword 'interface' is reserved (1:0)" + ], + [ + 'No spread in array expressions', + '[...[]];', + 'Line 1: Spread syntax is not allowed in arrays.' + ], + [ + 'No destructuring declarations', + ` let x = [1, 2]; let [a, b] = x; a; `, - 'Line 3: Array patterns are not allowed.' - ], - [ - 'No function expressions', - 'const x = function() {};', - 'Line 1: Function expressions are not allowed.' + 'Line 3: Array patterns are not allowed.' + ], + [ + 'No function expressions', + 'const x = function() {};', + 'Line 1: Function expressions are not allowed.' + ], + [ + 'No repeated function parameters', + 'function any(x, x) {}', + 'Line 1: SyntaxError: Argument name clash (1:16)' + ], + [ + 'No empty statements', + ';', + 'Line 1: Empty statements are not allowed.' + ] ], - [ - 'No repeated function parameters', - 'function any(x, x) {}', - 'Line 1: SyntaxError: Argument name clash (1:16)' - ] -], Chapter.SOURCE_4) - -// test('No empty statements', () => { -// return expectParsedError( -// stripIndent` -// ; -// `, -// { chapter: Chapter.LIBRARY_PARSER } -// ).toMatchInlineSnapshot(`"Line 1: Empty statements are not allowed"`) -// }) - -// test('No empty statements - verbose', () => { -// return expectParsedError( -// stripIndent` -// "enable verbose"; -// ; -// `, -// { chapter: Chapter.LIBRARY_PARSER } -// ).toMatchInlineSnapshot(` -// "Line 2, Column 0: Empty statements are not allowed -// You are trying to use Empty statements, which is not allowed (yet). -// " -// `) -// }) + Chapter.SOURCE_4 +) test('No array expressions in chapter 2', async () => { - await expectParsedError('[];', Chapter.SOURCE_2).toEqual('Line 1: Array expressions are not allowed.') + await expectParsedError('[];', Chapter.SOURCE_2).toEqual( + 'Line 1: Array expressions are not allowed.' + ) await expectResult('[];', Chapter.SOURCE_3).toEqual([]) }) diff --git a/src/parser/__tests__/fullTS.ts b/src/parser/__tests__/fullTS.ts index 776a06cd7..d308a38f3 100644 --- a/src/parser/__tests__/fullTS.ts +++ b/src/parser/__tests__/fullTS.ts @@ -1,50 +1,54 @@ import { parseError } from '../..' import { mockContext } from '../../mocks/context' import { Chapter } from '../../types' +import { testMultipleCases } from '../../utils/testing/testers' import { FullTSParser } from '../fullTS' const parser = new FullTSParser() -let context = mockContext(Chapter.FULL_TS) - -beforeEach(() => { - context = mockContext(Chapter.FULL_TS) -}) describe('fullTS parser', () => { - it('formats errors correctly', () => { - const code = `type StringOrNumber = string | number; - const x: StringOrNumber = true; - ` - - parser.parse(code, context) - expect(parseError(context.errors)).toMatchInlineSnapshot( - `"Line 2: Type \'boolean\' is not assignable to type \'StringOrNumber\'."` - ) - }) - - it('allows usage of builtins/preludes', () => { - const code = `const xs = list(1); - const ys = list(1); - equal(xs, ys); - ` - + testMultipleCases<[string, string | undefined]>([ + [ + 'formats errors correctly', + `type StringOrNumber = string | number; + const x: StringOrNumber = true; + `, + "Line 2: Type 'boolean' is not assignable to type 'StringOrNumber'." + ], + [ + 'allows usage of builtins/preludes', + ` + const xs = list(1); + const ys = list(1); + equal(xs, ys); + `, + undefined + ], + [ + 'allows usage of imports from modules', + ` + import { show, heart } from 'rune'; + show(heart); + `, + undefined + ] + ], ([code, expected]) => { + const context = mockContext(Chapter.FULL_TS) parser.parse(code, context) - expect(parseError(context.errors)).toMatchInlineSnapshot(`""`) - }) - it('allows usage of imports/modules', () => { - const code = `import { show, heart } from "rune"; - show(heart); - ` - - parser.parse(code, context) - expect(parseError(context.errors)).toMatchInlineSnapshot(`""`) + if (expected === undefined) { + expect(context.errors.length).toEqual(0) + } else { + expect(context.errors.length).toBeGreaterThanOrEqual(1) + expect(parseError(context.errors)).toEqual(expected) + } }) it('returns ESTree compliant program', () => { const code = `type StringOrNumber = string | number; const x: StringOrNumber = 1; ` + const context = mockContext(Chapter.FULL_TS) // Resulting program should not have node for type alias declaration const parsedProgram = parser.parse(code, context) diff --git a/src/parser/types.ts b/src/parser/types.ts index e5e691313..ffd2e5a42 100644 --- a/src/parser/types.ts +++ b/src/parser/types.ts @@ -15,12 +15,23 @@ export interface Parser { validate(ast: Program, context: Context, throwOnError?: boolean): boolean } export interface Rule { + /** + * Name of the rule + */ name: string - testSnippets?: [string, string][] + /** + * Test snippets to test the behaviour of the rule. Providing no test snippets + * means that this rule will not be tested when running unit tests. + */ + testSnippets?: [code: string, expected: string][] + + /** + * Disable this rule for this chapter (inclusive) and above + */ disableFromChapter?: Chapter disableForVariants?: Variant[] checkers: { [name: string]: (node: T, ancestors: Node[]) => SourceError[] - }, + } } diff --git a/src/repl/transpiler.ts b/src/repl/transpiler.ts index b59484958..18c5cf733 100644 --- a/src/repl/transpiler.ts +++ b/src/repl/transpiler.ts @@ -18,9 +18,7 @@ import { } from './utils' export const transpilerCommand = new Command('transpiler') - .addOption( - getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.GPU, Variant.NATIVE]) - ) + .addOption(getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.GPU, Variant.NATIVE])) .addOption(getChapterOption(Chapter.SOURCE_4, chapterParser)) .option( '-p, --pretranspile', diff --git a/src/runner/__tests__/runners.ts b/src/runner/__tests__/runners.ts index 231959164..382fd582c 100644 --- a/src/runner/__tests__/runners.ts +++ b/src/runner/__tests__/runners.ts @@ -5,7 +5,7 @@ import { FatalSyntaxError } from '../../parser/errors' import { Chapter, Finished, Variant, type ExecutionMethod } from '../../types' import { locationDummyNode } from '../../utils/ast/astCreator' import { CodeSnippetTestCase, expectFinishedResult } from '../../utils/testing' -import { testMultipleCases } from '../../utils/testing/testers' +import { expectResultsToEqual } from '../../utils/testing/testers' import { htmlErrorHandlingScript } from '../htmlRunner' const JAVASCRIPT_CODE_SNIPPETS_NO_ERRORS: CodeSnippetTestCase[] = [ @@ -249,12 +249,13 @@ describe('Test tail call return for native runner', () => { // } // f(10000); // `).toEqual(expect.stringMatching(/Maximum call stack size exceeded\n([^f]*f){3}/)) - // }, 10000) + // }, 10000) - testMultipleCases([ + expectResultsToEqual( [ - 'Simple taill call returns work', - ` + [ + 'Simple tail call returns work', + ` function f(x, y) { if (x <= 0) { return y; @@ -264,21 +265,21 @@ describe('Test tail call return for native runner', () => { } f(5000, 5000); `, - 10000 - ], - [ - 'Tail call in conditional expressions work', - ` + 10000 + ], + [ + 'Tail call in conditional expressions work', + ` function f(x, y) { return x <= 0 ? y : f(x-1, y+1); } f(5000, 5000); `, - 10000 - ], - [ - 'Tail call in boolean operators work', - ` + 10000 + ], + [ + 'Tail call in boolean operators work', + ` function f(x, y) { if (x <= 0) { return y; @@ -288,21 +289,21 @@ describe('Test tail call return for native runner', () => { } f(5000, 5000); `, - 10000 - ], - [ - 'Tail call in nested mix of conditional expressions and boolean operators work', - ` + 10000 + ], + [ + 'Tail call in nested mix of conditional expressions and boolean operators work', + ` function f(x, y) { return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; } f(5000, 5000); `, - 10000 - ], - [ - 'Tail calls in arrow block functions work', - ` + 10000 + ], + [ + 'Tail calls in arrow block functions work', + ` const f = (x, y) => { if (x <= 0) { return y; @@ -312,19 +313,19 @@ describe('Test tail call return for native runner', () => { }; f(5000, 5000); `, - 10000 - ], - [ - 'Tail calls in expression arrow functions work', - ` + 10000 + ], + [ + 'Tail calls in expression arrow functions work', + ` const f = (x, y) => x <= 0 ? y : f(x-1, y+1); f(5000, 5000); `, - 10000 - ], - [ - 'Tail calls in mutual recursion work', - ` + 10000 + ], + [ + 'Tail calls in mutual recursion work', + ` function f(x, y) { if (x <= 0) { return y; @@ -341,20 +342,20 @@ describe('Test tail call return for native runner', () => { } f(5000, 5000); `, - 10000 - ], - [ - 'Tail calls in mutual recursion with arrow functions work', - ` + 10000 + ], + [ + 'Tail calls in mutual recursion with arrow functions work', + ` const f = (x, y) => x <= 0 ? y : g(x-1, y+1); const g = (x, y) => x <= 0 ? y : f(x-1, y+1); f(5000, 5000); `, - 10000 - ], - [ - 'Tail calls in mixed tail-call/non-tail-call recursion work', - ` + 10000 + ], + [ + 'Tail calls in mixed tail-call/non-tail-call recursion work', + ` function f(x, y, z) { if (x <= 0) { return y; @@ -364,14 +365,13 @@ describe('Test tail call return for native runner', () => { } f(5000, 5000, 2); `, - 15000 - ] - ], async ([code, expected]) => { - const context = mockContext(Chapter.SOURCE_1) - const result = await runInContext(code, context) - expectFinishedResult(result) - expect(result.value).toEqual(expected) - }, false, 10000) + 15000 + ] + ], + Chapter.SOURCE_1, + false, + 10000 + ) }) describe('Tests for all runners', () => { diff --git a/src/stdlib/__tests__/__snapshots__/parser-errors.ts.snap b/src/stdlib/__tests__/__snapshots__/parser-errors.ts.snap deleted file mode 100644 index 4e0dc6769..000000000 --- a/src/stdlib/__tests__/__snapshots__/parser-errors.ts.snap +++ /dev/null @@ -1,27 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Blacklisted syntax: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse(\\"function* f() { yield 1; } f();\\"), undefined, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: ParseError: Yield expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Blatant syntax error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse(\\"'\\"), undefined, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/stdlib/__tests__/parser-errors.ts b/src/stdlib/__tests__/parser-errors.ts index 70f4eb4a7..7196eb21f 100644 --- a/src/stdlib/__tests__/parser-errors.ts +++ b/src/stdlib/__tests__/parser-errors.ts @@ -1,14 +1,14 @@ import { Chapter } from '../../types' import { stripIndent } from '../../utils/formatters' -import { expectParsedError } from '../../utils/testing' +import { expectParsedError } from '../../utils/testing/testers' test('Blatant syntax error', () => { return expectParsedError( stripIndent` stringify(parse("'"), undefined, 2); `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)"`) + Chapter.SOURCE_4 + ).toEqual("Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)") }) test('Blacklisted syntax', () => { @@ -16,6 +16,6 @@ test('Blacklisted syntax', () => { stripIndent` stringify(parse("function* f() { yield 1; } f();"), undefined, 2); `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 1: ParseError: Yield expressions are not allowed"`) + Chapter.SOURCE_4 + ).toEqual("Line 1: ParseError: Yield expressions are not allowed.") }) diff --git a/src/utils/__tests__/stringify.ts b/src/utils/__tests__/stringify.ts index 66f11c9e3..11e3f2ee3 100644 --- a/src/utils/__tests__/stringify.ts +++ b/src/utils/__tests__/stringify.ts @@ -2,8 +2,7 @@ import { list, set_tail, tail } from '../../stdlib/list' import { Chapter, type Value } from '../../types' import { stripIndent } from '../formatters' import { lineTreeToString, stringDagToLineTree, stringify, valueToStringDag } from '../stringify' -import { expectResult } from '../testing' -import { testMultipleCases } from '../testing/testers' +import { expectResult, testMultipleCases } from '../testing/testers' type TestCase = [desc: string, valueToStringify: Value, expected: string] const cases: TestCase[] = [ @@ -344,7 +343,7 @@ test('String representation of objects with toReplString member calls toReplStri }) test('String representation of builtins are nice', () => { - return expectResult(`stringify(pair);`, { chapter: Chapter.SOURCE_2 }).toEqual( + return expectResult(`stringify(pair);`, Chapter.SOURCE_2).toEqual( stripIndent` function pair(left, right) { [implementation hidden] @@ -353,7 +352,7 @@ test('String representation of builtins are nice', () => { }) test('String representation with 1 space indent', () => { - return expectResult("stringify(parse('x=>x;'), 1);", { chapter: Chapter.SOURCE_4 }).toEqual(` + return expectResult("stringify(parse('x=>x;'), 1);", Chapter.SOURCE_4).toEqual(` ["lambda_expression", [[["name", ["x", null]], null], [["return_statement", [["name", ["x", null]], null]], null]]] @@ -361,14 +360,14 @@ test('String representation with 1 space indent', () => { }) test('String representation with default (2 space) indent', () => { - return expectResult('stringify(parse("x=>x;"));', { chapter: Chapter.SOURCE_4 }).toEqual(` + return expectResult('stringify(parse("x=>x;"));', Chapter.SOURCE_4).toEqual(` [ "lambda_expression", [ [["name", ["x", null]], null], [["return_statement", [["name", ["x", null]], null]], null]]]`) }) test('String representation with more than 10 space indent should trim to 10 space indent', () => { - return expectResult('stringify(parse("x=>x;"), 100);', { chapter: Chapter.SOURCE_4 }).toEqual(` + return expectResult('stringify(parse("x=>x;"), 100);', Chapter.SOURCE_4).toEqual(` [ "lambda_expression", [ [["name", ["x", null]], null], [["return_statement", [["name", ["x", null]], null]], null]]] diff --git a/src/utils/testing/testers.ts b/src/utils/testing/testers.ts index 86a6cfefd..40acac7a4 100644 --- a/src/utils/testing/testers.ts +++ b/src/utils/testing/testers.ts @@ -2,6 +2,8 @@ import type { Program } from 'estree' import { Chapter, type Context } from '../../types' import { mockContext } from '../../mocks/context' import { parse } from '../../parser/parser' +import { parseError, runInContext } from '../..' +import { expectFinishedResult } from '.' export type TestCase> = [string, ...T] @@ -78,3 +80,73 @@ export function astTester( testMultipleCases(fullCases, args => func(...args), includeIndex, timeout) } + +export function expectResult(code: string, chapter: Chapter) { + const context = mockContext(chapter) + return expect( + runInContext(code, context).then(result => { + expectFinishedResult(result) + return result.value + }) + ).resolves +} + +type ExpectResultsTestCase = [string, string, any] | [string, string, any, Chapter] +export function expectResultsToEqual( + snippets: ExpectResultsTestCase[], + defaultChapter: Chapter = Chapter.SOURCE_1, + includeIndex?: boolean, + timeout?: number +) { + const fullSnippets = snippets.map(snippet => { + const chapter = snippet.length === 4 ? snippet[3] : defaultChapter + return [snippet[0], snippet[1], snippet[2], chapter] as [string, string, any, Chapter] + }) + + testMultipleCases( + fullSnippets, + ([code, expected, chapter]) => { + return expectResult(code, chapter).toEqual(expected) + }, + includeIndex, + timeout + ) +} + +export function expectParsedError(code: string, chapter: Chapter, verbose?: boolean) { + const context = mockContext(chapter) + return expect( + runInContext(code, context).then(result => { + expect(result.status).toEqual('error') + return parseError(context.errors, verbose) + }) + ).resolves +} + +export function expectParsedErrorsToEqual( + snippets: (ExpectResultsTestCase | [...ExpectResultsTestCase, boolean])[], + defaultChapter: Chapter = Chapter.SOURCE_1, + includeIndex?: boolean, + timeout?: number +) { + const fullSnippets = snippets.map(snippet => { + const chapter = snippet.length >= 4 ? snippet[3] : defaultChapter + const verbose = snippet.length === 5 ? snippet[4] : false + return [snippet[0], snippet[1], snippet[2], chapter, verbose] as [ + string, + string, + any, + Chapter, + boolean + ] + }) + + testMultipleCases( + fullSnippets, + ([code, expected, chapter, verbose]) => { + return expectParsedError(code, chapter, verbose).toEqual(expected) + }, + includeIndex, + timeout + ) +} From f93f6a4b31a0e7d2923551af81d494935c999b57 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 21 May 2024 17:59:35 +0800 Subject: [PATCH 25/65] Fix stdlib tests --- .../__tests__/__snapshots__/list.ts.snap | 1093 ----------------- .../__snapshots__/localImport.ts.snap | 116 -- .../__tests__/__snapshots__/misc.ts.snap | 280 ----- .../__tests__/__snapshots__/stream.ts.snap | 303 ----- src/stdlib/__tests__/list.ts | 927 +++++--------- src/stdlib/__tests__/localImport.ts | 171 ++- src/stdlib/__tests__/misc.ts | 317 ++--- src/stdlib/__tests__/parser-errors.ts | 4 +- src/stdlib/__tests__/stream.ts | 339 +++-- 9 files changed, 678 insertions(+), 2872 deletions(-) delete mode 100644 src/stdlib/__tests__/__snapshots__/list.ts.snap delete mode 100644 src/stdlib/__tests__/__snapshots__/localImport.ts.snap delete mode 100644 src/stdlib/__tests__/__snapshots__/misc.ts.snap delete mode 100644 src/stdlib/__tests__/__snapshots__/stream.ts.snap diff --git a/src/stdlib/__tests__/__snapshots__/list.ts.snap b/src/stdlib/__tests__/__snapshots__/list.ts.snap deleted file mode 100644 index a48fd9d60..000000000 --- a/src/stdlib/__tests__/__snapshots__/list.ts.snap +++ /dev/null @@ -1,1093 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. bad index error list_ref: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3), 3);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 216: Error: head(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. bad index error list_ref: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3), -1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 217: Error: tail(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. bad index error list_ref: expectParsedError 3`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3), 1.5);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 217: Error: tail(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. bad index error list_ref: expectParsedError 4`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3), '1');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 215: Expected string on right hand side of operation, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. bad number error build_list: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "build_list(x => x, '1');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 63: Expected number on left hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. bad number error enum_list: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "enum_list('1', '5');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 203: Expected string on right hand side of operation, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. bad number error enum_list: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "enum_list('1', 5);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 201: Expected string on right hand side of operation, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. bad number error enum_list: expectParsedError 3`] = ` -Object { - "alertResult": Array [], - "code": "enum_list(1, '5');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 201: Expected number on right hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error accumulate: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "accumulate((x, y) => x + y, [1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 3 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error accumulate: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "accumulate((x, y) => x + y, [1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 3 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error append: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "append([1, 2, 3], list(1, 2, 3));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 121: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error assoc: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "assoc(1, [1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Name assoc not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error filter: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "filter(x => true, [1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 185: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error for_each: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for_each(x=>x, [1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 76: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error length: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "length([1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 33: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error map: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "map(x=>x, [1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 47: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error member: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "member(1, [1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 136: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error remove: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "remove(1, [1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 151: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error remove_all: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "remove_all(1, [1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 169: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error reverse: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "reverse([1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 106: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error set_head: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "set_head([1, 2, 3], 4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: set_head(xs,x) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`These tests are reporting weird line numbers, as list functions are now implemented in Source. non-list error set_tail: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "set_tail([1, 2, 3], 4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: set_tail(xs,x) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`accumulate works from right to left: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "accumulate((curr, acc) => curr + acc, '1', list('4','3','2'));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "4321", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`accumulate works properly: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`accumulate: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`append: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(append(list(123, 123), list(456, 456, 456)), list(123, 123, 456, 456, 456));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`build_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(build_list(x => x * x, 5), list(0, 1, 4, 9, 16));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list MCE fuzz test: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(parse('const twice = f => x => {const result = f(f(x)); return two;};')); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list(\\"constant_declaration\\", - list(\\"name\\", \\"twice\\"), - list(\\"lambda_expression\\", - list(list(\\"name\\", \\"f\\")), - list(\\"return_statement\\", - list(\\"lambda_expression\\", - list(list(\\"name\\", \\"x\\")), - list(\\"block\\", - list(\\"sequence\\", - list(list(\\"constant_declaration\\", - list(\\"name\\", \\"result\\"), - list(\\"application\\", - list(\\"name\\", \\"f\\"), - list(list(\\"application\\", list(\\"name\\", \\"f\\"), list(list(\\"name\\", \\"x\\")))))), - list(\\"return_statement\\", list(\\"name\\", \\"two\\")))))))))", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list checks prepend type: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(build_list(i => i, 5), true); -0; // suppress long result in snapshot", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: TypeError: display_list expects the second argument to be a string", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`display_list infinite list 2: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const p = list(1, 2, 3); -set_tail(tail(tail(p)), p); -display_list(p); -0; // suppress long result in snapshot", - "displayResult": Array [ - "[1, [2, [3, ...]]]", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list infinite list of list of infinite list: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const build_inf = (i, f) => { - const t = list(f(i)); - let p = t; - for (let n = i - 1; n >= 0; n = n - 1) { - p = pair(f(n), p); - } - set_tail(t, p); - return p; -}; -display_list(build_inf(3, i => build_list(i => build_inf(i, i=>i), i))); -0; // suppress long result in snapshot", - "displayResult": Array [ - "[ null, -[ list([0, ...]), -[ list([0, ...], [0, [1, ...]]), -[ list([0, ...], [0, [1, ...]], [0, [1, [2, ...]]]), -...]]]]", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list infinite list: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const p = list(1); -set_tail(p, p); -display_list(p); -0; // suppress long result in snapshot", - "displayResult": Array [ - "[1, ...]", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list list of infinite list of list: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const build_inf = (i, f) => { - const t = list(f(i)); - let p = t; - for (let n = i - 1; n >= 0; n = n - 1) { - p = pair(f(n), p); - } - set_tail(t, p); - return p; -}; -display_list(build_list(i => build_inf(i, i => build_list(i => i, i)), 3)); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list([null, ...], - [null, [list(0), ...]], - [null, [list(0), [list(0, 1), ...]]])", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list list of infinite list: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const build_inf = i => { - const t = list(i); - let p = t; - for (let n = i - 1; n >= 0; n = n - 1) { - p = pair(n, p); - } - set_tail(t, p); - return p; -}; -display_list(build_list(build_inf, 5)); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list([0, ...], - [0, [1, ...]], - [0, [1, [2, ...]]], - [0, [1, [2, [3, ...]]]], - [0, [1, [2, [3, [4, ...]]]]])", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list returns argument: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const xs = build_list(i => i, 5); -xs === display_list(xs); -// Note reference equality", - "displayResult": Array [ - "list(0, 1, 2, 3, 4)", - ], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list returns cyclic argument: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const build_inf = (i, f) => { - const t = list(f(i)); - let p = t; - for (let n = i - 1; n >= 0; n = n - 1) { - p = pair(f(n), p); - } - set_tail(t, p); - return p; -}; -const xs = build_inf(5, i=>i); -xs === display_list(xs); -// Note reference equality", - "displayResult": Array [ - "[0, [1, [2, [3, [4, [5, ...]]]]]]", - ], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list reusing lists 2: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const p1 = pair(1, null); -const p2 = pair(2, p1); -const p3 = list(p1, p2); -display_list(p3); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list(list(1), list(2, 1))", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list reusing lists: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const p = list(1); -const p2 = pair(p, p); -const p3 = list(p, p2); -display_list(p3); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list(list(1), list(list(1), 1))", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list standard acyclic 2: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(build_list(i => build_list(j => j, i), 5)); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list(null, list(0), list(0, 1), list(0, 1, 2), list(0, 1, 2, 3))", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list standard acyclic multiline: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(build_list(i => build_list(j => j, i), 20)); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list(null, - list(0), - list(0, 1), - list(0, 1, 2), - list(0, 1, 2, 3), - list(0, 1, 2, 3, 4), - list(0, 1, 2, 3, 4, 5), - list(0, 1, 2, 3, 4, 5, 6), - list(0, 1, 2, 3, 4, 5, 6, 7), - list(0, 1, 2, 3, 4, 5, 6, 7, 8), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), - list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18))", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list standard acyclic with pairs 2: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(build_list(i => build_list(j => pair(build_list(k => k, j), j), i), 5)); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list(null, - list([null, 0]), - list([null, 0], [list(0), 1]), - list([null, 0], [list(0), 1], [list(0, 1), 2]), - list([null, 0], [list(0), 1], [list(0, 1), 2], [list(0, 1, 2), 3]))", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list standard acyclic with pairs: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(build_list(i => build_list(j => pair(j, j), i), 5)); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list(null, - list([0, 0]), - list([0, 0], [1, 1]), - list([0, 0], [1, 1], [2, 2]), - list([0, 0], [1, 1], [2, 2], [3, 3]))", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list standard acyclic: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(build_list(i => i, 5)); -0; // suppress long result in snapshot", - "displayResult": Array [ - "list(0, 1, 2, 3, 4)", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display_list supports prepend string: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(build_list(i => i, 5), \\"build_list:\\"); -0; // suppress long result in snapshot", - "displayResult": Array [ - "build_list: list(0, 1, 2, 3, 4)", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`empty list is null: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`enum_list with floats: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(enum_list(1.5, 5), list(1.5, 2.5, 3.5, 4.5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`enum_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(enum_list(1, 5), list(1, 2, 3, 4, 5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`equal: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "!equal(1, x => x) && !equal(x => x, 1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`filter: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(filter(x => x <= 4, list(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)), list(2, 1, 3, 4, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for_each: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let sum = 0; -for_each(x => { - sum = sum + x; -}, list(1, 2, 3)); -sum;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`head works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "head(pair(1, 'a string \\"\\"'));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`length works with empty lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const xs = list(); -length(xs);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`length works with populated lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const xs = list(1,2,3,4); -length(xs);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`list creates list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { return 1; } -list(1, 'a string \\"\\"', () => f, f, true, 3.14);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "native:[ 1, -[ \\"a string \\\\\\"\\\\\\"\\", -[ () => f, -[ function f() { - return 1; - }, -[true, [3.14, null]]]]]] -interpreted:[ 1, -[ \\"a string \\\\\\"\\\\\\"\\", -[ () => f, -[ () => { - return 1; - }, -[true, [3.14, null]]]]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`list_ref: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list_ref(list(1, 2, 3, \\"4\\", 4), 4);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`list_to_string: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list_to_string(list(1, 2, 3));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[1,[2,[3,null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`map: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(map(x => 2 * x, list(12, 11, 3)), list(24, 22, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`member: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal( - member(4, list(1, 2, 3, 4, 123, 456, 789)), - list(4, 123, 456, 789));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`non-list error head: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "head([1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`non-list error tail: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "tail([1, 2, 3]);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`pair creates pair: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "pair(1, 'a string \\"\\"');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - "a string \\"\\"", - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove not found: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "remove(2, list(1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - null, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "remove(1, list(1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove_all not found: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(remove_all(1, list(2, 3, 4)), list(2, 3, 4));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove_all: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(remove_all(1, list(1, 2, 3, 4, 1, 1, 1, 5, 1, 1, 6)), list(2, 3, 4, 5, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`reverse: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(reverse(list(\\"string\\", \\"null\\", \\"undefined\\", \\"null\\", 123)), list(123, \\"null\\", \\"undefined\\", \\"null\\", \\"string\\"));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`set_head: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let p = pair(1, 2); -const q = p; -set_head(p, 3); -p === q && equal(p, pair(3, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`set_tail: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let p = pair(1, 2); -const q = p; -set_tail(p, 3); -p === q && equal(p, pair(1, 3));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`tail of a 1 element list is null: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(list(1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`tail works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(pair(1, 'a string \\"\\"'));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "a string \\"\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/stdlib/__tests__/__snapshots__/localImport.ts.snap b/src/stdlib/__tests__/__snapshots__/localImport.ts.snap deleted file mode 100644 index aa22047ce..000000000 --- a/src/stdlib/__tests__/__snapshots__/localImport.ts.snap +++ /dev/null @@ -1,116 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`__access_export__ returns default export if it exists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - return pair(cube, list(pair(\\"square\\", square))); -} -// When 'null' is passed in as the name of the export, -// '__access_export__' returns the default export. -const square = __access_export__(importedFile(), \\"default\\"); -square(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 125, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`__access_export__ returns named export if it exists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - return pair(1 + 2, list(pair(\\"square\\", square), pair(\\"cube\\", cube))); -} -const square = __access_export__(importedFile(), \\"square\\"); -square(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 25, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`__access_named_export__ returns first identifier if name exists multiple times in list of exported names: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - // The second instance of the name 'square' actually refers to the function 'cube'. - return list(pair(\\"square\\", square), pair(\\"square\\", cube), pair(\\"cube\\", cube)); -} -const square = __access_named_export__(importedFile(), \\"square\\"); -square(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 25, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`__access_named_export__ returns identifier if name exists in list of exported names: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - return list(pair(\\"square\\", square), pair(\\"cube\\", cube)); -} -const square = __access_named_export__(importedFile(), \\"square\\"); -square(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 25, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`__access_named_export__ returns undefined if list of exported names is empty: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - return list(); -} -__access_named_export__(importedFile(), \\"identity\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`__access_named_export__ returns undefined if name does not exist in list of exported names: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - return list(pair(\\"square\\", square), pair(\\"cube\\", cube)); -} -__access_named_export__(importedFile(), \\"identity\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/stdlib/__tests__/__snapshots__/misc.ts.snap b/src/stdlib/__tests__/__snapshots__/misc.ts.snap deleted file mode 100644 index 0e93053b8..000000000 --- a/src/stdlib/__tests__/__snapshots__/misc.ts.snap +++ /dev/null @@ -1,280 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`arity ignores the rest parameter: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "arity(display);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`arity with function with parameters is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "arity(arity);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`arity with non-function arg f throws error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "arity('function');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: arity expects a function as argument", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`arity with nullary function is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "arity(math_random);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`arity with user-made function is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(x, y) { - return x + y; -} -arity(test);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`arity with user-made function with rest parameter is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(...xs) { - return xs; -} -arity(test);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`arity with user-made lambda function is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "arity(x => x);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`arity with user-made nullary function is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "arity(() => undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`char_at with non nonnegative integer second argument errors: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "char_at('', -1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: char_at expects the second argument to be a nonnegative integer.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`char_at with non nonnegative integer second argument errors: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "char_at('', \\"\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: char_at expects the second argument to be a nonnegative integer.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`char_at with non string first argument errors: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "char_at(42, 123);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: char_at expects the first argument to be a string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`char_at with valid args (but index out of bounds) returns undefined: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "char_at(\\"123\\", 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`char_at with valid args is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "char_at(\\"123\\", 0);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "1", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`parse_int with non-integer arg radix throws error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int(42, 2.1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`parse_int with non-string arg str throws error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int(42, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`parse_int with radix outside [2, 36] throws error, radix=1: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`parse_int with radix outside [2, 36] throws error, radix=37: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 37);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`parse_int with string arg radix throws error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int(42, '2');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`parse_int with valid args is ok, but invalid str for radix: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('uu1', 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": NaN, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`parse_int with valid args is ok, radix 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('1100101010101', 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6485, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`parse_int with valid args is ok, radix 36: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('uu1', 36);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 39961, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/stdlib/__tests__/__snapshots__/stream.ts.snap b/src/stdlib/__tests__/__snapshots__/stream.ts.snap deleted file mode 100644 index 4ab231de7..000000000 --- a/src/stdlib/__tests__/__snapshots__/stream.ts.snap +++ /dev/null @@ -1,303 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`append: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(stream_append(stream(\\"string\\", 123), stream(456, null, undefined))) - , list(\\"string\\", 123, 456, null, undefined));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`build_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(build_stream(x => x * x, 5)), list(0, 1, 4, 9, 16));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`enum_list with floats: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(enum_stream(1.5, 5)), list(1.5, 2.5, 3.5, 4.5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`enum_list: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(enum_stream(1, 5)), list(1, 2, 3, 4, 5));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`filter: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal( - stream_to_list( - stream_filter(x => x <= 4, stream(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)) - ) -, list(2, 1, 3, 4, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for_each: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let sum = 0; -stream_for_each(x => { - sum = sum + x; -}, stream(1, 2, 3)); -sum;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`list_ref: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stream_ref(stream(1, 2, 3, \\"4\\", 4), 4);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`map: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(stream_map(x => 2 * x, stream(12, 11, 3))), list(24, 22, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`member: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal( - stream_to_list(stream_member(\\"string\\", stream(1, 2, 3, \\"string\\", 123, 456, null, undefined))), - list(\\"string\\", 123, 456, null, undefined));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`primitive stream functions empty stream is null: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stream();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`primitive stream functions stream is properly created: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const s = stream(true, false, undefined, 1, x=>x, null, -123, head); -const result = []; -stream_for_each(item => {result[array_length(result)] = item;}, s); -stream_ref(s,4)(22) === 22 && stream_ref(s,7)(pair('', '1')) === '1' && result;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`primitive stream functions stream_tail is lazy: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stream_tail(integers_from(0));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - [Function], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`primitive stream functions stream_tail works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "head(stream_tail(stream(1, 2)));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`primitive stream functions stream_to_list works for null: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stream_to_list(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`primitive stream functions stream_to_list works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stream_to_list(stream(1, true, 3, 4.4, [1, 2]));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - true, - Array [ - 3, - Array [ - 4.4, - Array [ - Array [ - 1, - 2, - ], - null, - ], - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove not found: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stream_to_list(stream_remove(2, stream(1)));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - null, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stream_remove(1, stream(1));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove_all not found: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(stream_remove_all(1, stream(2, 3, \\"1\\"))), list(2, 3, \\"1\\"));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`remove_all: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list(stream_remove_all(1, stream(1, 2, 3, 4, 1, 1, \\"1\\", 5, 1, 1, 6))), - list(2, 3, 4, \\"1\\", 5, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`reverse: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(stream_to_list( - stream_reverse( - stream(\\"string\\", null, undefined, null, 123))), -list(123, null, undefined, null, \\"string\\"));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/stdlib/__tests__/list.ts b/src/stdlib/__tests__/list.ts index b60dafeb9..91c97e6fa 100644 --- a/src/stdlib/__tests__/list.ts +++ b/src/stdlib/__tests__/list.ts @@ -1,6 +1,12 @@ import { Chapter } from '../../types' import { stripIndent } from '../../utils/formatters' -import { expectDisplayResult, expectParsedError, expectResult } from '../../utils/testing' +import { + expectDisplayResult, + expectParsedError, + expectParsedErrorsToEqual, + expectResult, + expectResultsToEqual +} from '../../utils/testing/testers' test('list creates list', () => { return expectResult( @@ -8,604 +14,313 @@ test('list creates list', () => { function f() { return 1; } list(1, 'a string ""', () => f, f, true, 3.14); `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(` - "native:[ 1, - [ \\"a string \\\\\\"\\\\\\"\\", - [ () => f, - [ function f() { - return 1; - }, - [true, [3.14, null]]]]]] - interpreted:[ 1, - [ \\"a string \\\\\\"\\\\\\"\\", - [ () => f, - [ () => { - return 1; - }, - [true, [3.14, null]]]]]]" - `) -}) - -test('pair creates pair', () => { - return expectResult( - stripIndent` - pair(1, 'a string ""'); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(` - Array [ - 1, - "a string \\"\\"", - ] - `) -}) - -test('head works', () => { - return expectResult( - stripIndent` - head(pair(1, 'a string ""')); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`1`) -}) - -test('tail works', () => { - return expectResult( - stripIndent` - tail(pair(1, 'a string ""')); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`"a string \\"\\""`) -}) - -test('tail of a 1 element list is null', () => { - return expectResult( - stripIndent` - tail(list(1)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`null`) -}) - -test('empty list is null', () => { - return expectResult( - stripIndent` - list(); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot('null') -}) - -test('equal', () => { - return expectResult( - stripIndent` - !equal(1, x => x) && !equal(x => x, 1); - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`true`) -}) - -test('for_each', () => { - return expectResult( - stripIndent` - let sum = 0; - for_each(x => { - sum = sum + x; - }, list(1, 2, 3)); - sum; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`6`) -}) - -test('map', () => { - return expectResult( - stripIndent` - equal(map(x => 2 * x, list(12, 11, 3)), list(24, 22, 6)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('filter', () => { - return expectResult( - stripIndent` - equal(filter(x => x <= 4, list(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)), list(2, 1, 3, 4, 2)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('build_list', () => { - return expectResult( - stripIndent` - equal(build_list(x => x * x, 5), list(0, 1, 4, 9, 16)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('reverse', () => { - return expectResult( - stripIndent` - equal(reverse(list("string", "null", "undefined", "null", 123)), list(123, "null", "undefined", "null", "string")); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('append', () => { - return expectResult( - stripIndent` - equal(append(list(123, 123), list(456, 456, 456)), list(123, 123, 456, 456, 456)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('member', () => { - return expectResult( - stripIndent` - equal( - member(4, list(1, 2, 3, 4, 123, 456, 789)), - list(4, 123, 456, 789)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('remove', () => { - return expectResult( - stripIndent` - remove(1, list(1)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`null`) -}) - -test('remove not found', () => { - return expectResult( - stripIndent` - remove(2, list(1)); - `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot(` Array [ 1, - null, + Array [ + "a string \\"\\"", + Array [ + [Function], + Array [ + [Function], + Array [ + true, + Array [ + 3.14, + null, + ], + ], + ], + ], + ], ] `) }) -test('remove_all', () => { - return expectResult( - stripIndent` - equal(remove_all(1, list(1, 2, 3, 4, 1, 1, 1, 5, 1, 1, 6)), list(2, 3, 4, 5, 6)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('remove_all not found', () => { - return expectResult( - stripIndent` - equal(remove_all(1, list(2, 3, 4)), list(2, 3, 4)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('enum_list', () => { - return expectResult( - stripIndent` - equal(enum_list(1, 5), list(1, 2, 3, 4, 5)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('enum_list with floats', () => { - return expectResult( - stripIndent` - equal(enum_list(1.5, 5), list(1.5, 2.5, 3.5, 4.5)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('list_ref', () => { - return expectResult( - stripIndent` - list_ref(list(1, 2, 3, "4", 4), 4); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`4`) -}) - -test('accumulate', () => { - return expectResult( - stripIndent` - accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`10`) -}) - -test('list_to_string', () => { - return expectResult( - stripIndent` - list_to_string(list(1, 2, 3)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`"[1,[2,[3,null]]]"`) -}) - -describe('accumulate', () => { - test('works properly', () => { - return expectResult( - stripIndent` - accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`10`) - }) - - it('works from right to left', () => { - return expectResult( - stripIndent` - accumulate((curr, acc) => curr + acc, '1', list('4','3','2'));`, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot('"4321"') - }) -}) - -describe('length', () => { - test('works with populated lists', () => { - return expectResult( - stripIndent` - const xs = list(1,2,3,4); - length(xs); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot('4') - }) - - test('works with empty lists', () => { - return expectResult( - stripIndent` - const xs = list(); - length(xs); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot('0') - }) -}) - -// assoc removed from Source -test.skip('assoc', () => { - return expectResult( - stripIndent` - equal(assoc(3, list(pair(1, 2), pair(3, 4))), pair(3, 4)); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test.skip('assoc not found', () => { - return expectResult( - stripIndent` - equal(assoc(2, list(pair(1, 2), pair(3, 4))), false); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('set_head', () => { - return expectResult( - stripIndent` - let p = pair(1, 2); - const q = p; - set_head(p, 3); - p === q && equal(p, pair(3, 2)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('set_tail', () => { - return expectResult( - stripIndent` - let p = pair(1, 2); - const q = p; - set_tail(p, 3); - p === q && equal(p, pair(1, 3)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('non-list error head', () => { - return expectParsedError( - stripIndent` - head([1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` +describe('Test regular functions', () => { + expectResultsToEqual( + [ + ['pair creates pair', 'pair(1, \'a string ""\');', [1, 'a string ""']], + [ + 'head()', + `head(pair(1, 'a string ""'));`, + 1 + ], + [ + 'tail() works', + `tail(pair(1, 'a string ""'));`, + 'a string ""' + ], + [ + 'tail of a 1 element list is null', + 'tail(list(1));', + null + ], + [ + 'empty list is null', + 'list();', + null + ], + [ + 'accumulate', + 'accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1));', + 10, + ], + [ + 'accumulate works right to left', + `accumulate((curr, acc) => curr + acc, '1', list('4','3','2'));`, + '4321' + ], + [ + 'append', + 'equal(append(list(123, 123), list(456, 456, 456)), list(123, 123, 456, 456, 456));', + true + ], + [ + 'build_list', + 'equal(build_list(x => x * x, 5), list(0, 1, 4, 9, 16));', + true + ], + [ + 'enum_list', + 'equal(enum_list(1, 5), list(1, 2, 3, 4, 5));', + true + ], + [ + 'enum_list with floats', + 'equal(enum_list(1.5, 5), list(1.5, 2.5, 3.5, 4.5));', + true + ], + [ + 'filter', + 'equal(filter(x => x <= 4, list(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)), list(2, 1, 3, 4, 2));', + true + ], + [ + 'for_each', + ` + let sum = 0; + for_each(x => { + sum = sum + x; + }, list(1, 2, 3)); + sum; + `, + 6 + ], + [ + 'length works with populated lists', + 'length(list(1,2,3,4));', + 4 + ], + [ + 'length works with empty lists', + 'length(list());', + 0 + ], + [ + 'list_ref', + 'list_ref(list(1, 2, 3, "4", 4), 4);', + 4 + ], + [ + 'list_to_string', + 'list_to_string(list(1, 2, 3));', + "[1,[2,[3,null]]]" + ], + [ + 'map', + 'equal(map(x => 2 * x, list(12, 11, 3)), list(24, 22, 6));', + true + ], + [ + 'member', + ` + equal( + member(4, list(1, 2, 3, 4, 123, 456, 789)), + list(4, 123, 456, 789)); + `, + true + ], + [ + 'remove', + 'remove(1, list(1));', + null + ], + [ + 'remove not found', + 'remove(2, list(1));', + [1, null] + ], + [ + 'remove_all', + 'equal(remove_all(1, list(2, 3, 4)), list(2, 3, 4));', + true + ], + [ + 'remove_all not found', + 'equal(remove_all(1, list(2, 3, 4)), list(2, 3, 4));', + true + ], + [ + 'reverse', + 'equal(reverse(list("string", "null", "undefined", "null", 123)), list(123, "null", "undefined", "null", "string"));', + true + ], + [ + 'set_head', + ` + let p = pair(1, 2); + const q = p; + set_head(p, 3); + p === q && equal(p, pair(3, 2)); + `, + true + ], + [ + 'set_tail', + ` + let p = pair(1, 2); + const q = p; + set_tail(p, 3); + p === q && equal(p, pair(1, 3)); + `, + true + ] + ], + Chapter.SOURCE_3 ) }) -test('non-list error tail', () => { - return expectParsedError( - stripIndent` - tail([1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) -}) - -describe('These tests are reporting weird line numbers, as list functions are now implemented in Source.', () => { - test('non-list error length', () => { - return expectParsedError( - stripIndent` - length([1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 33: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error map', () => { - return expectParsedError( - stripIndent` - map(x=>x, [1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 47: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error for_each', () => { - return expectParsedError( - stripIndent` - for_each(x=>x, [1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 76: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error reverse', () => { - return expectParsedError( - stripIndent` - reverse([1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 106: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error append', () => { - return expectParsedError( - stripIndent` - append([1, 2, 3], list(1, 2, 3)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 121: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error member', () => { - return expectParsedError( - stripIndent` - member(1, [1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 136: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error remove', () => { - return expectParsedError( - stripIndent` - remove(1, [1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 151: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error remove_all', () => { - return expectParsedError( - stripIndent` - remove_all(1, [1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 169: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error assoc', () => { - return expectParsedError( - stripIndent` - assoc(1, [1, 2, 3]); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Name assoc not declared."`) - }) - - test('non-list error filter', () => { - return expectParsedError( - stripIndent` - filter(x => true, [1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 185: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error accumulate', () => { - return expectParsedError( - stripIndent` - accumulate((x, y) => x + y, [1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected 3 arguments, but got 2."`) - }) - - test('non-list error accumulate', () => { - return expectParsedError( - stripIndent` - accumulate((x, y) => x + y, [1, 2, 3]); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected 3 arguments, but got 2."`) - }) - - test('non-list error set_head', () => { - return expectParsedError( - stripIndent` - set_head([1, 2, 3], 4); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: set_head(xs,x) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - test('non-list error set_tail', () => { - return expectParsedError( - stripIndent` - set_tail([1, 2, 3], 4); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: set_tail(xs,x) expects a pair as argument xs, but encountered [1, 2, 3]"` - ) - }) - - // skipped as implementation does not check types, causing infinite recursion. - test.skip('bad number error build_list', () => { - return expectParsedError( - stripIndent` - build_list(x => x, -1); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: build_list(fun, n) expects a positive integer as argument n, but encountered -1"` - ) - }) - - // skipped as implementation does not check types, causing infinite recursion. - test.skip('bad number error build_list', () => { - return expectParsedError( - stripIndent` - build_list(x => x, 1.5); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: build_list(fun, n) expects a positive integer as argument n, but encountered 1.5"` - ) - }) - - test('bad number error build_list', () => { - return expectParsedError( - stripIndent` - build_list(x => x, '1'); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 63: Expected number on left hand side of operation, got string."` - ) - }) - - test('bad number error enum_list', () => { - return expectParsedError( - stripIndent` - enum_list('1', '5'); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 203: Expected string on right hand side of operation, got number."` - ) - }) - - test('bad number error enum_list', () => { - return expectParsedError( - stripIndent` - enum_list('1', 5); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 201: Expected string on right hand side of operation, got number."` - ) - }) - - test('bad number error enum_list', () => { - return expectParsedError( - stripIndent` - enum_list(1, '5'); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 201: Expected number on right hand side of operation, got string."` - ) - }) - - test('bad index error list_ref', () => { - return expectParsedError( - stripIndent` - list_ref(list(1, 2, 3), 3); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 216: Error: head(xs) expects a pair as argument xs, but encountered null"` - ) - }) - - test('bad index error list_ref', () => { - return expectParsedError( - stripIndent` - list_ref(list(1, 2, 3), -1); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 217: Error: tail(xs) expects a pair as argument xs, but encountered null"` - ) - }) - - test('bad index error list_ref', () => { - return expectParsedError( - stripIndent` - list_ref(list(1, 2, 3), 1.5); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 217: Error: tail(xs) expects a pair as argument xs, but encountered null"` - ) - }) - - test('bad index error list_ref', () => { - return expectParsedError( - stripIndent` - list_ref(list(1, 2, 3), '1'); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 215: Expected string on right hand side of operation, got number."` - ) +describe('Test errors', () => { + expectParsedErrorsToEqual([ + [ + 'non-list error head', + 'head([1, 2, 3]);', + "Line 1: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error tail', + 'tail([1, 2, 3]);', + "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ] + ], Chapter.SOURCE_3) + + describe('These tests are reporting weird line numbers, as list functions are now implemented in Source.', () => { + expectParsedErrorsToEqual([ + [ + 'non-list error accumulate', + 'accumulate((x, y) => x + y, [1, 2, 3]);', + "Line 1: Expected 3 arguments, but got 2." + ], + [ + 'non-list error append', + 'append([1, 2, 3], list(1, 2, 3));', + "Line 121: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error filter', + 'filter(x => true, [1, 2, 3]);', + "Line 185: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error for_each', + 'for_each(x=>x, [1, 2, 3]);', + "Line 76: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error length', + 'length([1, 2, 3]);', + "Line 33: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error map', + 'map(x=>x, [1, 2, 3]);', + "Line 47: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error member', + 'member(1, [1, 2, 3]);', + "Line 136: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error remove', + 'remove(1, [1, 2, 3]);', + "Line 151: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error remove_all', + 'remove_all(1, [1, 2, 3]);', + "Line 169: Error: head(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error reverse', + 'reverse([1, 2, 3]);', + "Line 106: Error: tail(xs) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error set_head', + 'set_head([1, 2, 3], 4);', + "Line 1: Error: set_head(xs,x) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + [ + 'non-list error set_tail', + 'set_tail([1, 2, 3], 4);', + "Line 1: Error: set_tail(xs,x) expects a pair as argument xs, but encountered [1, 2, 3]" + ], + + // skipped as implementation does not check types, causing infinite recursion. + // [ + // 'bad number error build_list', + // 'build_list(x => x, -1);', + // "Line 1: Error: build_list(fun, n) expects a positive integer as argument n, but encountered -1" + // ], + // [ + // 'bad number error build_list', + // 'build_list(x => x, 1.5);', + // "Line 1: Error: build_list(fun, n) expects a positive integer as argument n, but encountered -1" + // ] + [ + 'bad number error build_list', + "build_list(x => x, '1');", + "Line 63: Expected number on left hand side of operation, got string." + ], + [ + 'bad number error enum_list 1', + "enum_list('1', '5');", + "Line 203: Expected string on right hand side of operation, got number." + ], + [ + 'bad number error enum_list 2', + "enum_list('1', 5);", + "Line 201: Expected string on right hand side of operation, got number." + ], + [ + 'bad number error enum_list 3', + "enum_list(1, '5');", + "Line 201: Expected number on right hand side of operation, got string." + ], + [ + 'bad index error list_ref 1', + 'list_ref(list(1, 2, 3), 3);', + "Line 216: Error: head(xs) expects a pair as argument xs, but encountered null" + ], + [ + 'bad index error list_ref 2', + 'list_ref(list(1, 2, 3), -1);', + "Line 217: Error: tail(xs) expects a pair as argument xs, but encountered null" + ], + [ + 'bad index error list_ref 3', + 'list_ref(list(1, 2, 3), -1);', + "Line 217: Error: tail(xs) expects a pair as argument xs, but encountered null" + ], + [ + 'bad index error list_ref 4', + 'list_ref(list(1, 2, 3), "1");', + "Line 215: Expected string on right hand side of operation, got number." + ] + ], Chapter.SOURCE_3) }) }) @@ -616,7 +331,7 @@ describe('display_list', () => { display_list(build_list(i => i, 5)); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot(` Array [ "list(0, 1, 2, 3, 4)", @@ -630,7 +345,7 @@ describe('display_list', () => { display_list(build_list(i => build_list(j => j, i), 5)); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot(` Array [ "list(null, list(0), list(0, 1), list(0, 1, 2), list(0, 1, 2, 3))", @@ -644,7 +359,7 @@ describe('display_list', () => { display_list(build_list(i => build_list(j => pair(j, j), i), 5)); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot(` Array [ "list(null, @@ -662,7 +377,7 @@ describe('display_list', () => { display_list(build_list(i => build_list(j => pair(build_list(k => k, j), j), i), 5)); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot(` Array [ "list(null, @@ -681,7 +396,7 @@ describe('display_list', () => { xs === display_list(xs); // Note reference equality `, - { chapter: Chapter.SOURCE_3, native: true } + Chapter.SOURCE_3 ).toMatchInlineSnapshot(`true`) }) @@ -701,7 +416,7 @@ describe('display_list', () => { xs === display_list(xs); // Note reference equality `, - { chapter: Chapter.SOURCE_3, native: true } + Chapter.SOURCE_3 ).toMatchInlineSnapshot(`true`) }) @@ -711,7 +426,7 @@ describe('display_list', () => { display_list(build_list(i => i, 5), "build_list:"); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot(` Array [ "build_list: list(0, 1, 2, 3, 4)", @@ -725,7 +440,7 @@ describe('display_list', () => { display_list(build_list(i => i, 5), true); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot( `"Line 1: TypeError: display_list expects the second argument to be a string"` ) @@ -741,7 +456,7 @@ describe('display_list', () => { display_list(parse('const twice = f => x => {const result = f(f(x)); return two;};')); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_4, native: true } + Chapter.SOURCE_4 ).toMatchInlineSnapshot(` Array [ "list(\\"constant_declaration\\", @@ -769,7 +484,7 @@ describe('display_list', () => { display_list(build_list(i => build_list(j => j, i), 20)); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot(` Array [ "list(null, @@ -804,7 +519,7 @@ describe('display_list', () => { display_list(p); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_3, native: true } + Chapter.SOURCE_3 ).toMatchInlineSnapshot(` Array [ "[1, ...]", @@ -820,7 +535,7 @@ describe('display_list', () => { display_list(p); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_3, native: true } + Chapter.SOURCE_3 ).toMatchInlineSnapshot(` Array [ "[1, [2, [3, ...]]]", @@ -837,7 +552,7 @@ describe('display_list', () => { display_list(p3); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot(` Array [ "list(list(1), list(list(1), 1))", @@ -854,7 +569,7 @@ describe('display_list', () => { display_list(p3); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_2, native: true } + Chapter.SOURCE_2 ).toMatchInlineSnapshot(` Array [ "list(list(1), list(2, 1))", @@ -876,7 +591,7 @@ describe('display_list', () => { display_list(build_list(build_inf, 5)); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_3, native: true } + Chapter.SOURCE_3 ).toMatchInlineSnapshot(` Array [ "list([0, ...], @@ -903,7 +618,7 @@ describe('display_list', () => { display_list(build_list(i => build_inf(i, i => build_list(i => i, i)), 3)); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_3, native: true } + Chapter.SOURCE_3 ).toMatchInlineSnapshot(` Array [ "list([null, ...], @@ -928,7 +643,7 @@ describe('display_list', () => { display_list(build_inf(3, i => build_list(i => build_inf(i, i=>i), i))); 0; // suppress long result in snapshot `, - { chapter: Chapter.SOURCE_3, native: true } + Chapter.SOURCE_3 ).toMatchInlineSnapshot(` Array [ "[ null, diff --git a/src/stdlib/__tests__/localImport.ts b/src/stdlib/__tests__/localImport.ts index 59658e031..f46b37122 100644 --- a/src/stdlib/__tests__/localImport.ts +++ b/src/stdlib/__tests__/localImport.ts @@ -1,99 +1,92 @@ import { Chapter } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { expectResult } from '../../utils/testing' +import { expectResultsToEqual } from '../../utils/testing/testers' import { defaultExportLookupName } from '../localImport.prelude' describe('__access_named_export__', () => { - it('returns identifier if name exists in list of exported names', () => { - return expectResult( - stripIndent` - function importedFile() { + expectResultsToEqual([ + [ + 'returns identifier if name exists in list of exported names', + ` + function importedFile() { + const square = x => x * x; + const cube = x => x * x * x; + return list(pair("square", square), pair("cube", cube)); + } + const square = __access_named_export__(importedFile(), "square"); + square(5); + `, + 25 + ], + [ + 'returns first identifier if name exists multiple times in list of exported names', + ` + function importedFile() { const square = x => x * x; const cube = x => x * x * x; - return list(pair("square", square), pair("cube", cube)); - } - const square = __access_named_export__(importedFile(), "square"); - square(5); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`25`) - }) - - it('returns first identifier if name exists multiple times in list of exported names', () => { - return expectResult( - stripIndent` - function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - // The second instance of the name 'square' actually refers to the function 'cube'. - return list(pair("square", square), pair("square", cube), pair("cube", cube)); - } - const square = __access_named_export__(importedFile(), "square"); - square(5); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`25`) - }) - - it('returns undefined if name does not exist in list of exported names', () => { - return expectResult( - stripIndent` - function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - return list(pair("square", square), pair("cube", cube)); - } - __access_named_export__(importedFile(), "identity"); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`undefined`) - }) - - it('returns undefined if list of exported names is empty', () => { - return expectResult( - stripIndent` - function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - return list(); - } - __access_named_export__(importedFile(), "identity"); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`undefined`) - }) + // The second instance of the name 'square' actually refers to the function 'cube'. + return list(pair("square", square), pair("square", cube), pair("cube", cube)); + } + const square = __access_named_export__(importedFile(), "square"); + square(5); + `, + 25 + ], + [ + 'returns undefined if name does not exist in list of exported names', + ` + function importedFile() { + const square = x => x * x; + const cube = x => x * x * x; + return list(pair("square", square), pair("cube", cube)); + } + __access_named_export__(importedFile(), "identity"); + `, + undefined + ], + [ + 'returns undefined if list of exported names is empty', + ` + function importedFile() { + const square = x => x * x; + const cube = x => x * x * x; + return list(); + } + __access_named_export__(importedFile(), "identity"); + `, + undefined + ] + ], Chapter.SOURCE_2) }) describe('__access_export__', () => { - it('returns named export if it exists', () => { - return expectResult( - stripIndent` - function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - return pair(1 + 2, list(pair("square", square), pair("cube", cube))); - } - const square = __access_export__(importedFile(), "square"); - square(5); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`25`) - }) - - it('returns default export if it exists', () => { - return expectResult( - stripIndent` - function importedFile() { - const square = x => x * x; - const cube = x => x * x * x; - return pair(cube, list(pair("square", square))); - } - // When 'null' is passed in as the name of the export, - // '__access_export__' returns the default export. - const square = __access_export__(importedFile(), "${defaultExportLookupName}"); - square(5); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`125`) - }) + expectResultsToEqual([ + [ + 'returns named export if it exists', + ` + function importedFile() { + const square = x => x * x; + const cube = x => x * x * x; + return pair(1 + 2, list(pair("square", square), pair("cube", cube))); + } + const square = __access_export__(importedFile(), "square"); + square(5); + `, + 25 + ], + [ + 'returns default export if it exists', + ` + function importedFile() { + const square = x => x * x; + const cube = x => x * x * x; + return pair(cube, list(pair("square", square))); + } + // When 'null' is passed in as the name of the export, + // '__access_export__' returns the default export. + const square = __access_export__(importedFile(), "${defaultExportLookupName}"); + square(5); + `, + 125 + ] + ], Chapter.SOURCE_2) }) diff --git a/src/stdlib/__tests__/misc.ts b/src/stdlib/__tests__/misc.ts index e0011efee..8c7a87833 100644 --- a/src/stdlib/__tests__/misc.ts +++ b/src/stdlib/__tests__/misc.ts @@ -1,186 +1,135 @@ import { Chapter } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { expectParsedError, expectResult } from '../../utils/testing' - -test('parse_int with valid args is ok, radix 2', () => { - return expectResult( - stripIndent` - parse_int('1100101010101', 2); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toBe(parseInt('1100101010101', 2)) -}) - -test('parse_int with valid args is ok, radix 36', () => { - return expectResult( - stripIndent` - parse_int('uu1', 36); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toBe(parseInt('uu1', 36)) -}) - -test('parse_int with valid args is ok, but invalid str for radix', () => { - return expectResult( - stripIndent` - parse_int('uu1', 2); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toBe(parseInt('uu1', 2)) -}) - -test('parse_int with non-string arg str throws error', () => { - return expectParsedError(stripIndent` - parse_int(42, 2); - `).toMatchInlineSnapshot( - `"Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive."` - ) -}) - -test('parse_int with non-integer arg radix throws error', () => { - return expectParsedError(stripIndent` - parse_int(42, 2.1); - `).toMatchInlineSnapshot( - `"Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive."` - ) -}) - -test('parse_int with radix outside [2, 36] throws error, radix=1', () => { - return expectParsedError(stripIndent` - parse_int('10', 1); - `).toMatchInlineSnapshot( - `"Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive."` - ) -}) - -test('parse_int with radix outside [2, 36] throws error, radix=37', () => { - return expectParsedError(stripIndent` - parse_int('10', 37); - `).toMatchInlineSnapshot( - `"Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive."` - ) -}) - -test('parse_int with string arg radix throws error', () => { - return expectParsedError(stripIndent` - parse_int(42, '2'); - `).toMatchInlineSnapshot( - `"Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive."` - ) -}) - -test('char_at with non string first argument errors', () => { - return expectParsedError(stripIndent` - char_at(42, 123); - `).toMatchInlineSnapshot(`"Line 1: Error: char_at expects the first argument to be a string."`) -}) - -test('char_at with non nonnegative integer second argument errors', () => { - return expectParsedError(stripIndent` - char_at('', -1); - `).toMatchInlineSnapshot( - `"Line 1: Error: char_at expects the second argument to be a nonnegative integer."` - ) -}) - -test('char_at with non nonnegative integer second argument errors', () => { - return expectParsedError(stripIndent` - char_at('', ""); - `).toMatchInlineSnapshot( - `"Line 1: Error: char_at expects the second argument to be a nonnegative integer."` - ) -}) - -test('char_at with valid args is ok', () => { - return expectResult( - stripIndent` - char_at("123", 0); - ` - ).toBe('1') -}) - -test('char_at with valid args (but index out of bounds) returns undefined', () => { - return expectResult( - stripIndent` - char_at("123", 3); - ` - ).toBe(undefined) -}) - -test('arity with nullary function is ok', () => { - return expectResult( - stripIndent` - arity(math_random); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toBe(0) -}) - -test('arity with function with parameters is ok', () => { - return expectResult( - stripIndent` - arity(arity); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toBe(1) -}) - -test('arity ignores the rest parameter', () => { - return expectResult( - stripIndent` - arity(display); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toBe(1) -}) - -test('arity with user-made function is ok', () => { - return expectResult( - stripIndent` - function test(x, y) { - return x + y; - } - arity(test); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toBe(2) -}) - -test('arity with user-made lambda function is ok', () => { - return expectResult( - stripIndent` - arity(x => x); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toBe(1) -}) - -test('arity with user-made nullary function is ok', () => { - return expectResult( - stripIndent` - arity(() => undefined); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toBe(0) -}) - -test('arity with user-made function with rest parameter is ok', () => { - return expectResult( - stripIndent` - function test(...xs) { - return xs; - } - arity(test); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toBe(0) -}) - -test('arity with non-function arg f throws error', () => { - return expectParsedError( - stripIndent` - arity('function'); - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toMatchInlineSnapshot(`"Line 1: Error: arity expects a function as argument"`) +import { expectParsedErrorsToEqual, expectResultsToEqual } from '../../utils/testing/testers' + +describe('Test regular function', () => { + expectResultsToEqual([ + // arity + [ + 'arity with nullary function is ok', + 'arity(math_random);', + 0 + ], + [ + 'arity with function with parameters is ok', + 'arity(arity);', + 1 + ], + [ + 'arity ignores the rest parameter', + 'arity(display);', + 1 + ], + [ + 'arity with user-made function is ok', + ` + function test(x, y) { return x; } + arity(test); + `, + 2 + ], + [ + 'arity with user-made lambda function is ok', + ` + const test = (x, y) => x; + arity(test); + `, + 2 + ], + [ + 'arity with user-made nullary function is ok', + 'arity(() => undefined);', + 0 + ], + [ + 'arity with user-made function with rest parameter is ok', + ` + function test(...args) { return 0; } + arity(test); + `, + 0 + ], + + // char_at + [ + 'char_at with valid args is ok', + 'char_at("123", 0);', + '1' + ], + [ + 'char_at with valid args (but index out of bounds) returns undefined', + 'char_at("123", 3);', + undefined + ], + + // parse_int + [ + 'parse_int with valid args is ok', + "parse_int('1100101010101', 2);", + parseInt('1100101010101', 2) + ], + [ + 'parse_int with valid args is ok, radix 36', + "parse_int('uu1', 36);", + parseInt('uu1', 36) + ], + [ + 'parse_int with valid args is ok, but invalid str for radix', + "parse_int('uu1', 2);", + parseInt('uu1', 2) + ] + ], Chapter.SOURCE_3) +}) + +describe('Test errors', () => { + expectParsedErrorsToEqual([ + [ + 'arity with non-function arg f throws error', + 'arity("function");', + "Line 1: Error: arity expects a function as argument" + ], + + // char_at + [ + 'char_at with non string first argument errors', + 'char_at(42, 123);', + "Line 1: Error: char_at expects the first argument to be a string." + ], + [ + 'char_at with non nonnegative integer second argument errors 1', + "char_at('', -1);", + "Line 1: Error: char_at expects the second argument to be a nonnegative integer." + ], + [ + 'char_at with non nonnegative integer second argument errors', + `char_at('', "");`, + "Line 1: Error: char_at expects the second argument to be a nonnegative integer." + ], + + // parse_int + [ + 'parse_int with non-string arg str throws error', + 'parse_int(42, 2);', + "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive." + ], + [ + 'parse_int with non-integer arg radix throws error', + 'parse_int(42, 2.1);', + "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive." + ], + [ + 'parse_int with radix outside [2, 36] throws error, radix=1', + "parse_int('10', 1);", + "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive." + ], + [ + 'parse_int with radix outside [2, 36] throws error, radix=37', + "parse_int('10', 37);", + "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive." + ], + [ + 'parse_int with string arg radix throws error', + "parse_int(42, '2');", + "Line 1: Error: parse_int expects two arguments a string s, and a positive integer i between 2 and 36, inclusive." + ] + ], Chapter.SOURCE_3) }) diff --git a/src/stdlib/__tests__/parser-errors.ts b/src/stdlib/__tests__/parser-errors.ts index 7196eb21f..e34c3cf7f 100644 --- a/src/stdlib/__tests__/parser-errors.ts +++ b/src/stdlib/__tests__/parser-errors.ts @@ -8,7 +8,7 @@ test('Blatant syntax error', () => { stringify(parse("'"), undefined, 2); `, Chapter.SOURCE_4 - ).toEqual("Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)") + ).toEqual('Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)') }) test('Blacklisted syntax', () => { @@ -17,5 +17,5 @@ test('Blacklisted syntax', () => { stringify(parse("function* f() { yield 1; } f();"), undefined, 2); `, Chapter.SOURCE_4 - ).toEqual("Line 1: ParseError: Yield expressions are not allowed.") + ).toEqual('Line 1: ParseError: Yield expressions are not allowed.') }) diff --git a/src/stdlib/__tests__/stream.ts b/src/stdlib/__tests__/stream.ts index c3e55ff7b..68162c590 100644 --- a/src/stdlib/__tests__/stream.ts +++ b/src/stdlib/__tests__/stream.ts @@ -1,26 +1,134 @@ import { Chapter } from '../../types' import { stripIndent } from '../../utils/formatters' -import { expectParsedErrorNoSnapshot, expectResult } from '../../utils/testing' +import { expectParsedError, expectResult, expectResultsToEqual } from '../../utils/testing/testers' -describe('primitive stream functions', () => { - test('empty stream is null', () => { - return expectResult('stream();', { chapter: Chapter.SOURCE_3, native: true }).toBe(null) - }) - - test('stream_tail works', () => { - return expectResult(`head(stream_tail(stream(1, 2)));`, { - chapter: Chapter.SOURCE_3, - native: true - }).toBe(2) - }) - - test('stream_tail is lazy', () => { - return expectResult( - stripIndent(` - stream_tail(integers_from(0)); - `), - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` +expectResultsToEqual([ + [ + 'empty stream is null', + 'stream();', + null + ], + [ + 'append', + ` + equal(stream_to_list(stream_append(stream("string", 123), stream(456, null, undefined))) + , list("string", 123, 456, null, undefined)); + `, + true + ], + [ + 'build_stream', + 'equal(stream_to_list(build_stream(x => x * x, 5)), list(0, 1, 4, 9, 16));', + true, + ], + [ + 'enum_stream', + 'equal(stream_to_list(enum_stream(1, 5)), list(1, 2, 3, 4, 5));', + true + ], + [ + 'enum_stream with floats', + 'equal(stream_to_list(enum_stream(1.5, 5)), list(1.5, 2.5, 3.5, 4.5));', + true + ], + [ + 'filter', + ` + equal( + stream_to_list( + stream_filter(x => x <= 4, stream(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)) + ) + , list(2, 1, 3, 4, 2)); + `, + true + ], + [ + 'for_each', + ` + let sum = 0; + stream_for_each(x => { + sum = sum + x; + }, stream(1, 2, 3)); + sum; + `, + 6 + ], + [ + 'map', + 'equal(stream_to_list(stream_map(x => 2 * x, stream(12, 11, 3))), list(24, 22, 6));', + true + ], + [ + 'member', + ` + equal( + stream_to_list(stream_member("string", stream(1, 2, 3, "string", 123, 456, null, undefined))), + list("string", 123, 456, null, undefined)); + `, + true + ], + [ + 'remove', + 'stream_remove(1, stream(1));', + null + ], + [ + 'remove not found', + 'stream_to_list(stream_remove(2, stream(1)));', + [1, null] + ], + [ + 'remove_all', + ` + equal(stream_to_list(stream_remove_all(1, stream(1, 2, 3, 4, 1, 1, "1", 5, 1, 1, 6))), + list(2, 3, 4, "1", 5, 6)); + `, + true + ], + [ + 'remove_all not found', + 'equal(stream_to_list(stream_remove_all(1, stream(2, 3, "1"))), list(2, 3, "1"));', + true + ], + [ + 'reverse', + ` + equal(stream_to_list( + stream_reverse( + stream("string", null, undefined, null, 123))), + list(123, null, undefined, null, "string")); + `, + true + ], + [ + 'stream_ref', + 'stream_ref(stream(1, 2, 3, "4", 4), 4);', + 4 + ], + [ + 'stream_tail works', + 'head(stream_tail(stream(1, 2)));', + 2 + ], + [ + 'stream is properly created', + ` + const s = stream(true, false, undefined, 1, x=>x, null, -123, head); + const result = []; + stream_for_each(item => {result[array_length(result)] = item;}, s); + stream_ref(s,4)(22) === 22 && stream_ref(s,7)(pair('', '1')) === '1' && result; + `, + false + ], + [ + 'stream_to_list works for null', + 'stream_to_list(null);', + null + ] +], Chapter.SOURCE_3) + +test('stream_tail is lazy', () => { + return expectResult('stream_tail(integers_from(0));', Chapter.SOURCE_3).toMatchInlineSnapshot(` Array [ 1, [Function], @@ -28,39 +136,19 @@ Array [ `) }) - test('infinite stream is infinite', () => { - return expectParsedErrorNoSnapshot( - stripIndent` - stream_length(integers_from(0)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatch(/(Maximum call stack size exceeded){1,2}/) - }, 15000) - - test('stream is properly created', () => { - return expectResult( - stripIndent` - const s = stream(true, false, undefined, 1, x=>x, null, -123, head); - const result = []; - stream_for_each(item => {result[array_length(result)] = item;}, s); - stream_ref(s,4)(22) === 22 && stream_ref(s,7)(pair('', '1')) === '1' && result; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`false`) - }) - - test('stream_to_list works for null', () => { - return expectResult(`stream_to_list(null);`, { - chapter: Chapter.SOURCE_3, - native: true - }).toMatchInlineSnapshot(`null`) - }) +test('infinite stream is infinite', () => { + return expectParsedError( + stripIndent` + stream_length(integers_from(0)); + `, + Chapter.SOURCE_3 + ).toMatch(/(Maximum call stack size exceeded){1,2}/) +}, 15000) - test('stream_to_list works', () => { - return expectResult(`stream_to_list(stream(1, true, 3, 4.4, [1, 2]));`, { - chapter: Chapter.SOURCE_3, - native: true - }).toMatchInlineSnapshot(` +test('stream_to_list works', () => { + return expectResult(`stream_to_list(stream(1, true, 3, 4.4, [1, 2]));`, + Chapter.SOURCE_3 + ).toMatchInlineSnapshot(` Array [ 1, Array [ @@ -82,150 +170,3 @@ Array [ ] `) }) -}) - -test('for_each', () => { - return expectResult( - stripIndent` - let sum = 0; - stream_for_each(x => { - sum = sum + x; - }, stream(1, 2, 3)); - sum; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`6`) -}) - -test('map', () => { - return expectResult( - stripIndent` - equal(stream_to_list(stream_map(x => 2 * x, stream(12, 11, 3))), list(24, 22, 6)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('filter', () => { - return expectResult( - stripIndent` - equal( - stream_to_list( - stream_filter(x => x <= 4, stream(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)) - ) - , list(2, 1, 3, 4, 2)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('build_list', () => { - return expectResult( - stripIndent` - equal(stream_to_list(build_stream(x => x * x, 5)), list(0, 1, 4, 9, 16)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('reverse', () => { - return expectResult( - stripIndent` - equal(stream_to_list( - stream_reverse( - stream("string", null, undefined, null, 123))), - list(123, null, undefined, null, "string")); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('append', () => { - return expectResult( - stripIndent` - equal(stream_to_list(stream_append(stream("string", 123), stream(456, null, undefined))) - , list("string", 123, 456, null, undefined)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('member', () => { - return expectResult( - stripIndent` - equal( - stream_to_list(stream_member("string", stream(1, 2, 3, "string", 123, 456, null, undefined))), - list("string", 123, 456, null, undefined)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('remove', () => { - return expectResult( - stripIndent` - stream_remove(1, stream(1)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`null`) -}) - -test('remove not found', () => { - return expectResult( - stripIndent` - stream_to_list(stream_remove(2, stream(1))); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` -Array [ - 1, - null, -] -`) -}) - -test('remove_all', () => { - return expectResult( - stripIndent` - equal(stream_to_list(stream_remove_all(1, stream(1, 2, 3, 4, 1, 1, "1", 5, 1, 1, 6))), - list(2, 3, 4, "1", 5, 6)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('remove_all not found', () => { - return expectResult( - stripIndent` - equal(stream_to_list(stream_remove_all(1, stream(2, 3, "1"))), list(2, 3, "1")); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('enum_list', () => { - return expectResult( - stripIndent` - equal(stream_to_list(enum_stream(1, 5)), list(1, 2, 3, 4, 5)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('enum_list with floats', () => { - return expectResult( - stripIndent` - equal(stream_to_list(enum_stream(1.5, 5)), list(1.5, 2.5, 3.5, 4.5)); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -test('list_ref', () => { - return expectResult( - stripIndent` - stream_ref(stream(1, 2, 3, "4", 4), 4); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`4`) -}) From a8f3dc5c0083f07049c9eaaba7e62030cc98c53c Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 21 May 2024 18:00:00 +0800 Subject: [PATCH 26/65] Fix misc tests --- .../__snapshots__/block-scoping.ts.snap | 161 ---- src/__tests__/__snapshots__/draw_data.ts.snap | 49 -- src/__tests__/__snapshots__/stdlib.ts.snap | 777 ------------------ src/__tests__/block-scoping.ts | 323 ++++---- src/__tests__/code-snippets.ts | 8 +- src/__tests__/display.ts | 52 +- src/__tests__/draw_data.ts | 10 +- src/__tests__/index.ts | 4 - src/__tests__/stdlib.ts | 9 +- 9 files changed, 203 insertions(+), 1190 deletions(-) delete mode 100644 src/__tests__/__snapshots__/draw_data.ts.snap delete mode 100644 src/__tests__/__snapshots__/stdlib.ts.snap diff --git a/src/__tests__/__snapshots__/block-scoping.ts.snap b/src/__tests__/__snapshots__/block-scoping.ts.snap index dc267db9d..68368e28f 100644 --- a/src/__tests__/__snapshots__/block-scoping.ts.snap +++ b/src/__tests__/__snapshots__/block-scoping.ts.snap @@ -1,25 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Cannot overwrite loop variables within a block: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let z = []; - for (let x = 0; x < 2; x = x + 1) { - x = 1; - } - return false; -} -test();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Assignment to a for loop variable in the for loop is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - exports[`Error when accessing temporal dead zone: expectParsedError 1`] = ` Object { "alertResult": Array [], @@ -55,23 +35,6 @@ Object { } `; -exports[`No hoisting of functions. Only the name is hoisted like let and const: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const v = f(); -function f() { - return 1; -} -v;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Name f declared later in current scope but not yet assigned", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - exports[`Shadowed variables may not be assigned to until declared in the current scope: expectParsedError 1`] = ` Object { "alertResult": Array [], @@ -90,127 +53,3 @@ test();", "visualiseListResult": Array [], } `; - -exports[`const uses block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const x = true; - if(true) { - const x = false; - } else { - const x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for loop \`let\` variables are copied into the block scope: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let z = []; - for (let x = 0; x < 10; x = x + 1) { - z[x] = () => x; - } - return z[1](); -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for loops use block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - for (let x = 1; x > 0; x = x - 1) { - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`let uses block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`standalone block statements: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const x = true; - { - const x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`while loops use block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - while (true) { - let x = false; - break; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/draw_data.ts.snap b/src/__tests__/__snapshots__/draw_data.ts.snap deleted file mode 100644 index 065789fe5..000000000 --- a/src/__tests__/__snapshots__/draw_data.ts.snap +++ /dev/null @@ -1,49 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`draw_data returns first argument if exactly one argument: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "draw_data(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [ - Array [ - 1, - ], - ], -} -`; - -exports[`draw_data returns first argument if more than one argument: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "draw_data(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [ - Array [ - 1, - 2, - ], - ], -} -`; - -exports[`draw_data with no arguments throws error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "draw_data();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 or more arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/stdlib.ts.snap b/src/__tests__/__snapshots__/stdlib.ts.snap deleted file mode 100644 index d9d0699d6..000000000 --- a/src/__tests__/__snapshots__/stdlib.ts.snap +++ /dev/null @@ -1,777 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Builtins work as expected 0: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display('message');", - "displayResult": Array [ - "\\"message\\"", - ], - "numErrors": 0, - "parsedErrors": "", - "result": "message", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 1: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "error('error!');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: \\"error!\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_undefined(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 3: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_undefined(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 4: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_null(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 5: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_null(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 6: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 7: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 8: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 9: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 10: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 11: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 12: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 13: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 14: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 15: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 16: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 17: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 18: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 19: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 20: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 21: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(display);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 22: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(x => x);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 23: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -is_function(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 24: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 25: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 26: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 27: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 28: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 29: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 30: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 33: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(x => x);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 34: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(display);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 35: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 36: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 37: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 38: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_NaN(1 / 0);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 39: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_NaN(NaN);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 40: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_NaN(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 41: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_NaN(x => x);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 44: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "array_length([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 45: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 10);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 46: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 47: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(get_time());", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 48: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const start = get_time(); -function repeatUntilDifferentTime() { - if (start === get_time()) { - return repeatUntilDifferentTime(); - } else { - return true; - } -} -repeatUntilDifferentTime();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 49: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "pair(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 50: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - 2, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 51: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 52: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 53: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(list(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 54: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "head(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 55: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 56: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "head(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: head(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 57: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 58: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "head(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: head(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 59: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 60: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "length(list(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 61: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "length(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 33: Error: tail(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/block-scoping.ts b/src/__tests__/block-scoping.ts index d7de4b3fb..5817102ef 100644 --- a/src/__tests__/block-scoping.ts +++ b/src/__tests__/block-scoping.ts @@ -1,181 +1,180 @@ import { Chapter } from '../types' -import { stripIndent } from '../utils/formatters' -import { expectParsedError, expectResult } from '../utils/testing' +import { expectParsedError, expectResultsToEqual } from '../utils/testing/testers' -// This is bad practice. Don't do this! -test('standalone block statements', () => { - return expectResult( - stripIndent` - function test(){ - const x = true; - { - const x = false; +expectResultsToEqual([ + [ + 'standalone block statements', + ` + function test(){ + const x = true; + { + const x = false; + } + return x; } - return x; - } - test(); - `, - { native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('const uses block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - const x = true; - if(true) { - const x = false; - } else { - const x = false; + test(); + `, + true + ], + [ + 'const uses block scoping instead of function scoping', + ` + function test(){ + const x = true; + if(true) { + const x = false; + } else { + const x = false; + } + return x; } - return x; - } - test(); - `, - { native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('let uses block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; + test(); + `, + true + ], + [ + 'let uses block scoping instead of function scoping', + ` + function test(){ + let x = true; + if(true) { + const x = false; + } else { + const x = false; + } + return x; } - return x; - } - test(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('for loops use block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - for (let x = 1; x > 0; x = x - 1) { + test(); + `, + true + ], + [ + 'for loops use block scoping instead of function scoping', + ` + function test(){ + let x = true; + for (let x = 1; x > 0; x = x - 1) { + } + return x; } - return x; - } - test(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('while loops use block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - while (true) { - let x = false; - break; + test(); + `, + true + ], + [ + 'while loops use block scoping instead of function scoping', + ` + function test(){ + let x = true; + while (true) { + let x = false; + break; + } + return x; } - return x; - } - test(); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation -// and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ -test('for loop `let` variables are copied into the block scope', () => { - return expectResult( - stripIndent` - function test(){ - let z = []; - for (let x = 0; x < 10; x = x + 1) { - z[x] = () => x; - } - return z[1](); - } - test(); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(`1`) -}) + test(); + `, + true + ], + // see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation + // and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ + [ + 'for loop `let` variables are copied into the block scope', + ` + function test(){ + let z = []; + for (let x = 0; x < 10; x = x + 1) { + z[x] = () => x; + } + return z[1](); + } + test(); + `, + 1 + ] +], Chapter.SOURCE_4) test('Cannot overwrite loop variables within a block', () => { return expectParsedError( - stripIndent` - function test(){ - let z = []; - for (let x = 0; x < 2; x = x + 1) { - x = 1; + ` + function test(){ + let z = []; + for (let x = 0; x < 2; x = x + 1) { + x = 1; + } + return false; } - return false; - } - test(); - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot( - `"Line 4: Assignment to a for loop variable in the for loop is not allowed."` - ) + test(); + `, + Chapter.SOURCE_3 + ).toEqual("Line 5: Assignment to a for loop variable in the for loop is not allowed.") }) -test('No hoisting of functions. Only the name is hoisted like let and const', () => { - return expectParsedError(stripIndent` - const v = f(); - function f() { - return 1; - } - v; - `).toMatchInlineSnapshot( - `"Line 1: Name f declared later in current scope but not yet assigned"` - ) -}, 30000) +// expectParsedErrorsToEqual([ +// [ +// 'Cannot overwrite loop variables within a block', +// ` +// function test(){ +// let z = []; +// for (let x = 0; x < 2; x = x + 1) { +// x = 1; +// } +// return false; +// } +// test(); +// `, +// "Line 4: Assignment to the loop variable in the for loop is not allowed." +// ], +// [ +// 'No hoisting of functions. Only the name is hoisted like let and const', +// ` +// const v = f(); +// function f() { +// return 1; +// } +// v; +// `, +// "Line 1: Name f declared later in current scope but not yet assigned" +// ] +// ], Chapter.SOURCE_4) -test('Error when accessing temporal dead zone', () => { - return expectParsedError(stripIndent` - const a = 1; - function f() { - display(a); - const a = 5; - } - f(); - `).toMatchInlineSnapshot( - `"Line 3: Name a declared later in current scope but not yet assigned"` - ) -}, 30000) +// test('Error when accessing temporal dead zone', () => { +// return expectParsedError(stripIndent` +// const a = 1; +// function f() { +// display(a); +// const a = 5; +// } +// f(); +// `).toMatchInlineSnapshot( +// `"Line 3: Name a declared later in current scope but not yet assigned"` +// ) +// }, 30000) -// tslint:disable-next-line:max-line-length -test('In a block, every going-to-be-defined variable in the block cannot be accessed until it has been defined in the block.', () => { - return expectParsedError(stripIndent` - const a = 1; - { - a + a; - const a = 10; - } - `).toMatchInlineSnapshot( - `"Line 3: Name a declared later in current scope but not yet assigned"` - ) -}, 30000) +// // tslint:disable-next-line:max-line-length +// test('In a block, every going-to-be-defined variable in the block cannot be accessed until it has been defined in the block.', () => { +// return expectParsedError(stripIndent` +// const a = 1; +// { +// a + a; +// const a = 10; +// } +// `).toMatchInlineSnapshot( +// `"Line 3: Name a declared later in current scope but not yet assigned"` +// ) +// }, 30000) test('Shadowed variables may not be assigned to until declared in the current scope', () => { return expectParsedError( - stripIndent` - let variable = 1; - function test(){ - variable = 100; - let variable = true; - return variable; - } - test(); - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(`"Line 3: Name variable not declared."`) + ` + let variable = 1; + function test(){ + variable = 100; + let variable = true; + return variable; + } + test(); + `, + Chapter.SOURCE_3 + ).toEqual("Line 4: Name variable not declared.") }) diff --git a/src/__tests__/code-snippets.ts b/src/__tests__/code-snippets.ts index ed8cbe1ab..ecb803aa8 100644 --- a/src/__tests__/code-snippets.ts +++ b/src/__tests__/code-snippets.ts @@ -4,7 +4,8 @@ import { defineBuiltin } from '../createContext' import { mockContext } from '../mocks/context' import { Chapter, type Value } from '../types' import { stripIndent } from '../utils/formatters' -import { expectFinishedResult, type TestBuiltins } from '../utils/testing' +import { type TestBuiltins } from '../utils/testing' +import { expectFinishedResult, expectResult } from '../utils/testing/testers' import { expectResultsToEqual, testMultipleCases } from '../utils/testing/testers' async function testCodeSnippet( @@ -181,6 +182,11 @@ describe('Test basic code snippets', () => { Chapter.SOURCE_3 ] ]) + + test('Arrow function definition returns itself', () => { + return expectResult('() => 42;', Chapter.SOURCE_1) + .toMatchInlineSnapshot(`[Function]`) + }) }) describe('Test equal', () => { diff --git a/src/__tests__/display.ts b/src/__tests__/display.ts index 9c54ffd6d..f303f36be 100644 --- a/src/__tests__/display.ts +++ b/src/__tests__/display.ts @@ -1,8 +1,6 @@ -import { runInContext } from '..' import { Chapter } from '../types' import { stripIndent } from '../utils/formatters' -import { createTestContext, expectFinishedResult, expectParsedError } from '../utils/testing' -import { testMultipleCases } from '../utils/testing/testers' +import { testMultipleCases, expectParsedError, expectDisplayResult } from '../utils/testing/testers' testMultipleCases<[string, ...string[]]>( [ @@ -35,18 +33,6 @@ testMultipleCases<[string, ...string[]]>( 'x => x', '(x, y) => x + y' ], - [ - 'display can be used to display functions', - ` - function f() { return 0; } - display(f); - `, - stripIndent` - function f() { - return 0; - } - ` - ], [ 'displaying builtins hides their implementation', 'display(pair);', @@ -64,23 +50,39 @@ testMultipleCases<[string, ...string[]]>( '{"a": 1, "b": 2, "c": {"d": 3}}' ] ], - async ([code, ...expected]) => { - const context = createTestContext({ chapter: Chapter.LIBRARY_PARSER }) - const result = await runInContext(code, context) - - expectFinishedResult(result) - expect(context.displayResult).toEqual(expected) + ([code, ...expected]) => { + return expectDisplayResult(code, Chapter.LIBRARY_PARSER).toEqual(expected) } ) +test('display can be used to display functions', () => { + return expectDisplayResult(`display(x => x); display((x, y) => x + y);`, Chapter.LIBRARY_PARSER) + .toMatchInlineSnapshot(` +Array [ + "x => x", + "(x, y) => x + y", +] +`) +}) + +test('String representation of builtins are nice', () => { + return expectDisplayResult('display(pair);', Chapter.SOURCE_2).toMatchInlineSnapshot(` + Array [ + "function pair(left, right) { + [implementation hidden] + }" + ] + `) +}) + test('display throw error if second argument is non-string when used', () => { - return expectParsedError(`display(31072020, 0xDEADC0DE);`).toMatchInlineSnapshot( - `"Line 1: TypeError: display expects the second argument to be a string"` + return expectParsedError(`display(31072020, 0xDEADC0DE);`, Chapter.LIBRARY_PARSER).toEqual( + 'Line 1: TypeError: display expects the second argument to be a string' ) }) test('display with no arguments throws an error', () => { - return expectParsedError(`display();`, { chapter: Chapter.LIBRARY_PARSER }).toMatchInlineSnapshot( - `"Line 1: Expected 1 or more arguments, but got 0."` + return expectParsedError(`display();`, Chapter.LIBRARY_PARSER).toEqual( + 'Line 1: Expected 1 or more arguments, but got 0.' ) }) diff --git a/src/__tests__/draw_data.ts b/src/__tests__/draw_data.ts index e9ab49d07..2de69f4f0 100644 --- a/src/__tests__/draw_data.ts +++ b/src/__tests__/draw_data.ts @@ -1,16 +1,16 @@ import { Chapter } from '../types' -import { expectParsedError, expectResult } from '../utils/testing' +import { expectParsedError, expectResult } from '../utils/testing/testers' test('draw_data returns first argument if more than one argument', () => { - return expectResult(`draw_data(1, 2);`, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot(`1`) + return expectResult(`draw_data(1, 2);`, Chapter.SOURCE_3).toEqual(1) }) test('draw_data returns first argument if exactly one argument', () => { - return expectResult(`draw_data(1);`, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot(`1`) + return expectResult(`draw_data(1);`, Chapter.SOURCE_3).toEqual(1) }) test('draw_data with no arguments throws error', () => { - return expectParsedError(`draw_data();`, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot( - `"Line 1: Expected 1 or more arguments, but got 0."` + return expectParsedError(`draw_data();`, Chapter.SOURCE_3).toEqual( + "Line 1: Expected 1 or more arguments, but got 0." ) }) diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts index f683bb05d..582f71b7e 100644 --- a/src/__tests__/index.ts +++ b/src/__tests__/index.ts @@ -3,10 +3,6 @@ import { expectResult } from '../utils/testing' const toString = (x: Value) => '' + x -test('Arrow function definition returns itself', () => { - return expectResult('() => 42;').toMatchInlineSnapshot(`[Function]`) -}) - test('Builtins hide their implementation when toString', () => { return expectResult('toString(pair);', { chapter: Chapter.SOURCE_2, diff --git a/src/__tests__/stdlib.ts b/src/__tests__/stdlib.ts index 9e77f2816..7e3db115d 100644 --- a/src/__tests__/stdlib.ts +++ b/src/__tests__/stdlib.ts @@ -1,6 +1,6 @@ import { Chapter, Value } from '../types' import { stripIndent } from '../utils/formatters' -import { expectResult, snapshotFailure } from '../utils/testing' +import { expectParsedError, expectResult } from '../utils/testing/testers' test.each([ [ @@ -614,12 +614,9 @@ test.each([ 'Builtins work as expected %#', (chapter: Chapter, snippet: string, passing: boolean, returnValue: Value) => { if (passing) { - return expectResult(stripIndent(snippet), { - chapter, - native: chapter !== Chapter.LIBRARY_PARSER - }).toEqual(returnValue) + return expectResult(stripIndent(snippet), chapter).toEqual(returnValue) } else { - return snapshotFailure(stripIndent(snippet), { chapter }, 'fails') + return expectParsedError(stripIndent(snippet), chapter).not.toEqual('') } } ) From 3689c218d1dd77008e4721801dcd6b38173b8501 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 21 May 2024 18:00:18 +0800 Subject: [PATCH 27/65] Run format --- src/parser/__tests__/allowed-syntax.ts | 7 +- src/parser/__tests__/disallowed-syntax.ts | 6 +- src/parser/__tests__/fullTS.ts | 51 ++++++------ src/repl/__tests__/repl.ts | 3 +- src/runner/__tests__/modules.ts | 2 +- src/runner/__tests__/runners.ts | 3 +- .../__tests__/__snapshots__/stringify.ts.snap | 61 -------------- src/utils/__tests__/stringify.ts | 83 ++++++++++++------- src/utils/testing/index.ts | 5 -- src/utils/testing/testers.ts | 25 +++++- 10 files changed, 108 insertions(+), 138 deletions(-) delete mode 100644 src/utils/__tests__/__snapshots__/stringify.ts.snap diff --git a/src/parser/__tests__/allowed-syntax.ts b/src/parser/__tests__/allowed-syntax.ts index 95426862e..ac2babd16 100644 --- a/src/parser/__tests__/allowed-syntax.ts +++ b/src/parser/__tests__/allowed-syntax.ts @@ -320,12 +320,9 @@ describe.each([ ` ], - [ - Chapter.LIBRARY_PARSER, - `import { default as x } from './a.js';`, - ], + [Chapter.LIBRARY_PARSER, `import { default as x } from './a.js';`], [Chapter.LIBRARY_PARSER, `import * as a from 'one_module';`] -])("%#", (chapter, code) => { +])('%#', (chapter, code) => { test(`Should pass for Chapter ${chapter}`, () => { expect(testParse(chapter, code)).toEqual(true) }) diff --git a/src/parser/__tests__/disallowed-syntax.ts b/src/parser/__tests__/disallowed-syntax.ts index cca034fa9..77e833c88 100644 --- a/src/parser/__tests__/disallowed-syntax.ts +++ b/src/parser/__tests__/disallowed-syntax.ts @@ -85,11 +85,7 @@ expectParsedErrorsToEqual( 'function any(x, x) {}', 'Line 1: SyntaxError: Argument name clash (1:16)' ], - [ - 'No empty statements', - ';', - 'Line 1: Empty statements are not allowed.' - ] + ['No empty statements', ';', 'Line 1: Empty statements are not allowed.'] ], Chapter.SOURCE_4 ) diff --git a/src/parser/__tests__/fullTS.ts b/src/parser/__tests__/fullTS.ts index d308a38f3..3ccb56161 100644 --- a/src/parser/__tests__/fullTS.ts +++ b/src/parser/__tests__/fullTS.ts @@ -7,42 +7,45 @@ import { FullTSParser } from '../fullTS' const parser = new FullTSParser() describe('fullTS parser', () => { - testMultipleCases<[string, string | undefined]>([ + testMultipleCases<[string, string | undefined]>( [ - 'formats errors correctly', - `type StringOrNumber = string | number; + [ + 'formats errors correctly', + `type StringOrNumber = string | number; const x: StringOrNumber = true; `, - "Line 2: Type 'boolean' is not assignable to type 'StringOrNumber'." - ], - [ - 'allows usage of builtins/preludes', - ` + "Line 2: Type 'boolean' is not assignable to type 'StringOrNumber'." + ], + [ + 'allows usage of builtins/preludes', + ` const xs = list(1); const ys = list(1); equal(xs, ys); `, - undefined - ], - [ - 'allows usage of imports from modules', - ` + undefined + ], + [ + 'allows usage of imports from modules', + ` import { show, heart } from 'rune'; show(heart); `, - undefined - ] - ], ([code, expected]) => { - const context = mockContext(Chapter.FULL_TS) - parser.parse(code, context) + undefined + ] + ], + ([code, expected]) => { + const context = mockContext(Chapter.FULL_TS) + parser.parse(code, context) - if (expected === undefined) { - expect(context.errors.length).toEqual(0) - } else { - expect(context.errors.length).toBeGreaterThanOrEqual(1) - expect(parseError(context.errors)).toEqual(expected) + if (expected === undefined) { + expect(context.errors.length).toEqual(0) + } else { + expect(context.errors.length).toBeGreaterThanOrEqual(1) + expect(parseError(context.errors)).toEqual(expected) + } } - }) + ) it('returns ESTree compliant program', () => { const code = `type StringOrNumber = string | number; diff --git a/src/repl/__tests__/repl.ts b/src/repl/__tests__/repl.ts index bf2b4b35e..b5724a5b1 100644 --- a/src/repl/__tests__/repl.ts +++ b/src/repl/__tests__/repl.ts @@ -4,6 +4,7 @@ import { Chapter } from '../../types' import { asMockedFunc } from '../../utils/testing' import { getReplCommand } from '../repl' import { chapterParser } from '../utils' +import { testMultipleCases } from '../../utils/testing/testers' const readFileMocker = jest.fn() @@ -129,7 +130,7 @@ describe('Test repl command', () => { "Error: [/a.js] Line 1: Module 'unknown_module' not found." ] ] - test.each(testCases)('%s', async (_, args, files, expected) => { + testMultipleCases(testCases, async ([args, files, expected]) => { mockReadFiles(files) await runCommand(...args) expect(mockedConsoleLog.mock.calls[0][0]).toEqual(expected) diff --git a/src/runner/__tests__/modules.ts b/src/runner/__tests__/modules.ts index 488cdab65..72fa2b94a 100644 --- a/src/runner/__tests__/modules.ts +++ b/src/runner/__tests__/modules.ts @@ -1,7 +1,7 @@ import { mockContext } from '../../mocks/context' import { Chapter } from '../../types' import { stripIndent } from '../../utils/formatters' -import { expectFinishedResult } from '../../utils/testing' +import { expectFinishedResult } from '../../utils/testing/testers' import { runCodeInSource } from '../sourceRunner' jest.mock('../../modules/loader/loaders') diff --git a/src/runner/__tests__/runners.ts b/src/runner/__tests__/runners.ts index 382fd582c..650e1fbca 100644 --- a/src/runner/__tests__/runners.ts +++ b/src/runner/__tests__/runners.ts @@ -4,7 +4,8 @@ import { mockContext } from '../../mocks/context' import { FatalSyntaxError } from '../../parser/errors' import { Chapter, Finished, Variant, type ExecutionMethod } from '../../types' import { locationDummyNode } from '../../utils/ast/astCreator' -import { CodeSnippetTestCase, expectFinishedResult } from '../../utils/testing' +import { CodeSnippetTestCase } from '../../utils/testing' +import { expectFinishedResult } from '../../utils/testing/testers' import { expectResultsToEqual } from '../../utils/testing/testers' import { htmlErrorHandlingScript } from '../htmlRunner' diff --git a/src/utils/__tests__/__snapshots__/stringify.ts.snap b/src/utils/__tests__/__snapshots__/stringify.ts.snap deleted file mode 100644 index fa6a7d840..000000000 --- a/src/utils/__tests__/__snapshots__/stringify.ts.snap +++ /dev/null @@ -1,61 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`String representation of builtins are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(pair);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "function pair(left, right) { - [implementation hidden] -}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with 1 space indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse('x=>x;'), 1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[\\"lambda_expression\\", -[[[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with default (2 space) indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse(\\"x=>x;\\"));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ \\"lambda_expression\\", -[ [[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with more than 10 space indent should trim to 10 space indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse(\\"x=>x;\\"), 100);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ \\"lambda_expression\\", -[ [[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/utils/__tests__/stringify.ts b/src/utils/__tests__/stringify.ts index 11e3f2ee3..d0d6f5577 100644 --- a/src/utils/__tests__/stringify.ts +++ b/src/utils/__tests__/stringify.ts @@ -10,18 +10,6 @@ const cases: TestCase[] = [ ['String representation of numbers are nice', 0, '0'], ['String representation of string are nice', 'a string', '"a string"'], ['String representation of booleans are nice', true, 'true'], - [ - 'String representation of functions are nice', - // @ts-ignore - function f(x, y) { - return x - }, - stripIndent` - function f(x, y) { - return x; - } - ` - ], [ 'String representation of arrow functions are nice', // @ts-ignore @@ -312,6 +300,19 @@ test('Correctly handles circular lists with multiple entry points', () => { ) }) +test('String representation of functions are nice', () => { + // @ts-ignore + function f(x, y) { + return x + } + + return expect(stringify(f)).toMatchInlineSnapshot(` + "function f(x, y) { + return x; + }" + `) +}) + test('String representation of non literal objects is nice', () => { const errorMsg: string = 'This is an error' const errorObj: Error = new Error(errorMsg) @@ -343,35 +344,55 @@ test('String representation of objects with toReplString member calls toReplStri }) test('String representation of builtins are nice', () => { - return expectResult(`stringify(pair);`, Chapter.SOURCE_2).toEqual( + return expectResult( stripIndent` - function pair(left, right) { - [implementation hidden] - }` - ) + stringify(pair); + `, + Chapter.SOURCE_2 + ).toMatchInlineSnapshot(` + "function pair(left, right) { + [implementation hidden] + }" + `) }) test('String representation with 1 space indent', () => { - return expectResult("stringify(parse('x=>x;'), 1);", Chapter.SOURCE_4).toEqual(` - ["lambda_expression", - [[["name", ["x", null]], null], - [["return_statement", [["name", ["x", null]], null]], null]]] - `) + return expectResult( + stripIndent` + stringify(parse('x=>x;'), 1); + `, + Chapter.SOURCE_4 + ).toMatchInlineSnapshot(` + "[\\"lambda_expression\\", + [[[\\"name\\", [\\"x\\", null]], null], + [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" + `) }) test('String representation with default (2 space) indent', () => { - return expectResult('stringify(parse("x=>x;"));', Chapter.SOURCE_4).toEqual(` - [ "lambda_expression", - [ [["name", ["x", null]], null], - [["return_statement", [["name", ["x", null]], null]], null]]]`) + return expectResult( + stripIndent` + stringify(parse('x=>x;')); + `, + Chapter.SOURCE_4 + ).toMatchInlineSnapshot(` + "[ \\"lambda_expression\\", + [ [[\\"name\\", [\\"x\\", null]], null], + [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" + `) }) test('String representation with more than 10 space indent should trim to 10 space indent', () => { - return expectResult('stringify(parse("x=>x;"), 100);', Chapter.SOURCE_4).toEqual(` -[ "lambda_expression", - [ [["name", ["x", null]], null], - [["return_statement", [["name", ["x", null]], null]], null]]] - `) + return expectResult( + stripIndent` + stringify(parse('x=>x;'), 100); + `, + Chapter.SOURCE_4 + ).toMatchInlineSnapshot(` + "[ \\"lambda_expression\\", + [ [[\\"name\\", [\\"x\\", null]], null], + [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" + `) }) test('lineTreeToString', () => { diff --git a/src/utils/testing/index.ts b/src/utils/testing/index.ts index 05bcce197..15ce08c9a 100644 --- a/src/utils/testing/index.ts +++ b/src/utils/testing/index.ts @@ -14,7 +14,6 @@ import { type SourceError, type Value, Variant, - type Finished, type Result } from '../../types' import { stringify } from '../stringify' @@ -308,10 +307,6 @@ export function asMockedFunc any>(func: T) { return func as MockedFunction } -export function expectFinishedResult(result: Result): asserts result is Finished { - expect(result.status).toEqual('finished') -} - export function expectTrue(cond: boolean): asserts cond { expect(cond).toEqual(true) } diff --git a/src/utils/testing/testers.ts b/src/utils/testing/testers.ts index 40acac7a4..fd02820bd 100644 --- a/src/utils/testing/testers.ts +++ b/src/utils/testing/testers.ts @@ -1,9 +1,9 @@ import type { Program } from 'estree' -import { Chapter, type Context } from '../../types' +import { Chapter, type Context, type Finished } from '../../types' import { mockContext } from '../../mocks/context' import { parse } from '../../parser/parser' -import { parseError, runInContext } from '../..' -import { expectFinishedResult } from '.' +import { parseError, runInContext, type Result } from '../..' +import { createTestContext } from '.' export type TestCase> = [string, ...T] @@ -82,9 +82,12 @@ export function astTester( } export function expectResult(code: string, chapter: Chapter) { - const context = mockContext(chapter) + const context = createTestContext({ chapter }) return expect( runInContext(code, context).then(result => { + if (result.status === 'error') { + console.log(context.errors) + } expectFinishedResult(result) return result.value }) @@ -150,3 +153,17 @@ export function expectParsedErrorsToEqual( timeout ) } + +export function expectFinishedResult(result: Result): asserts result is Finished { + expect(result.status).toEqual('finished') +} + +export function expectDisplayResult(code: string, chapter: Chapter) { + const context = createTestContext({ chapter }) + return expect( + runInContext(code, context).then(result => { + expectFinishedResult(result) + return context.displayResult + }) + ).resolves +} From 1993c124b6c71adfdc79b3ac9608dc72f3903ba2 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 22 May 2024 10:58:19 +0800 Subject: [PATCH 28/65] Continue to remove old expectResult --- .../__snapshots__/return-regressions.ts.snap | 359 -------- src/__tests__/code-snippets.ts | 11 + src/__tests__/index.ts | 16 - src/__tests__/return-regressions.ts | 299 ------- .../cse-machine-callcc-js.ts.snap | 77 -- .../cse-machine-return-regressions.ts.snap | 359 -------- .../__snapshots__/cse-machine-stdlib.ts.snap | 621 ------------- .../__snapshots__/cse-machine.ts.snap | 568 ------------ .../__tests__/cse-machine-callcc-js.ts | 74 +- .../__tests__/cse-machine-callcc.ts | 2 +- .../__tests__/cse-machine-errors.ts | 14 +- .../cse-machine-return-regressions.ts | 480 +++++----- .../__tests__/cse-machine-stdlib.ts | 15 +- .../__tests__/cse-machine-unique-id.ts | 12 +- src/cse-machine/__tests__/cse-machine.ts | 835 ++++++++---------- .../__tests__/native-return-regressions.ts | 283 ++++++ src/runner/sourceRunner.ts | 31 +- .../__tests__/__snapshots__/pylib.ts.snap | 352 -------- src/stdlib/__tests__/pylib.ts | 323 ++----- src/utils/testing/testers.ts | 46 +- 20 files changed, 1064 insertions(+), 3713 deletions(-) delete mode 100644 src/__tests__/__snapshots__/return-regressions.ts.snap delete mode 100644 src/__tests__/index.ts delete mode 100644 src/__tests__/return-regressions.ts delete mode 100644 src/cse-machine/__tests__/__snapshots__/cse-machine-callcc-js.ts.snap delete mode 100644 src/cse-machine/__tests__/__snapshots__/cse-machine-return-regressions.ts.snap delete mode 100644 src/cse-machine/__tests__/__snapshots__/cse-machine-stdlib.ts.snap delete mode 100644 src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap create mode 100644 src/runner/__tests__/native-return-regressions.ts delete mode 100644 src/stdlib/__tests__/__snapshots__/pylib.ts.snap diff --git a/src/__tests__/__snapshots__/return-regressions.ts.snap b/src/__tests__/__snapshots__/return-regressions.ts.snap deleted file mode 100644 index 4a6940382..000000000 --- a/src/__tests__/__snapshots__/return-regressions.ts.snap +++ /dev/null @@ -1,359 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Bare early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return i+1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - if (true) { - return 1; - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - while (true) { - return 1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - return 1; - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Calling unreachable results in error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - unreachable(); - return 0; - } - f(); - ", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Expected number on right hand side of operation, got boolean.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1) + id(i+2); - } - return 0; - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1) + id(2); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1) + id(2); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1) + id(2); - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1); - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/code-snippets.ts b/src/__tests__/code-snippets.ts index ecb803aa8..c531fd7d8 100644 --- a/src/__tests__/code-snippets.ts +++ b/src/__tests__/code-snippets.ts @@ -187,6 +187,17 @@ describe('Test basic code snippets', () => { return expectResult('() => 42;', Chapter.SOURCE_1) .toMatchInlineSnapshot(`[Function]`) }) + + test('Builtins hide their implementation when toString', () => { + return expectResult('toString(pair);', { + chapter: Chapter.SOURCE_2, + testBuiltins: { toString } + }).toMatchInlineSnapshot(` + "function pair(left, right) { + [implementation hidden] + }" + `) + }) }) describe('Test equal', () => { diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts deleted file mode 100644 index 582f71b7e..000000000 --- a/src/__tests__/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Chapter, type Value } from '../types' -import { expectResult } from '../utils/testing' - -const toString = (x: Value) => '' + x - -test('Builtins hide their implementation when toString', () => { - return expectResult('toString(pair);', { - chapter: Chapter.SOURCE_2, - native: true, - testBuiltins: { toString } - }).toMatchInlineSnapshot(` - "function pair(left, right) { - [implementation hidden] - }" - `) -}) diff --git a/src/__tests__/return-regressions.ts b/src/__tests__/return-regressions.ts deleted file mode 100644 index f690e5e74..000000000 --- a/src/__tests__/return-regressions.ts +++ /dev/null @@ -1,299 +0,0 @@ -/** - * This file contains tests for regressions that TCO may have caused. - * Please reference Issue #124 (https://github.com/source-academy/js-slang/issues/124) - */ - -import { Chapter } from '../types' -import { expectParsedError, expectResult } from '../utils/testing' - -// This is bad practice. Don't do this! -test('Calling unreachable results in error', () => { - return expectParsedError(` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - unreachable(); - return 0; - } - f(); - `).toMatchInlineSnapshot( - `"Line 3: Expected number on right hand side of operation, got boolean."` - ) -}) - -// This is bad practice. Don't do this! -test('Bare early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - return 1; - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1) + id(2); - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1); - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in if statements work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - if (true) { - return 1; - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in if statements work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1) + id(2); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in if statements work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in while loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - while (true) { - return 1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in while loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1) + id(2); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in while loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in for loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return i+1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in for loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1) + id(i+2); - } - return 0; - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in for loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`1`) -}) diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine-callcc-js.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine-callcc-js.ts.snap deleted file mode 100644 index 459de6d64..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine-callcc-js.ts.snap +++ /dev/null @@ -1,77 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`call_cc can be used to return early: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 1; -call_cc((cont) => { - x = 2; - cont(); - x = 3; -}); -x;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`call_cc throws error when given > 1 arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (cont) => cont; -1 + 2 + call_cc(f,f) + 4;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`call_cc throws error when given no arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "1 + 2 + call_cc() + 4;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`call_cc works with normal functions: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1 + 2 + call_cc((cont) => 3) + 4;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`continuations can be stored as a value: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let a = 0; -call_cc((cont) => { - a = cont; -}); -a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": [Function], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine-return-regressions.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine-return-regressions.ts.snap deleted file mode 100644 index 4a6940382..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine-return-regressions.ts.snap +++ /dev/null @@ -1,359 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Bare early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return i+1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - if (true) { - return 1; - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - while (true) { - return 1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - return 1; - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Calling unreachable results in error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - unreachable(); - return 0; - } - f(); - ", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Expected number on right hand side of operation, got boolean.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1) + id(i+2); - } - return 0; - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1) + id(2); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1) + id(2); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1) + id(2); - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1); - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine-stdlib.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine-stdlib.ts.snap deleted file mode 100644 index ef86d0b26..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine-stdlib.ts.snap +++ /dev/null @@ -1,621 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Builtins work as expected 0: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display('message');", - "displayResult": Array [ - "\\"message\\"", - ], - "numErrors": 0, - "parsedErrors": "", - "result": "message", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 1: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "error('error!');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: \\"error!\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_undefined(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 3: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_undefined(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 4: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_null(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 5: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_null(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 6: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 7: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 8: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 9: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 10: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 11: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 12: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 13: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 14: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 15: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 16: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 17: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 18: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 19: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 20: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 21: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(display);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 22: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(x => x);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 23: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -is_function(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 24: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 25: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 26: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 27: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 28: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "array_length([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 29: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 10);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 30: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 31: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(get_time());", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 32: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const start = get_time(); -function repeatUntilDifferentTime() { - if (start === get_time()) { - return repeatUntilDifferentTime(); - } else { - return true; - } -} -repeatUntilDifferentTime();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 33: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "pair(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 34: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - 2, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 35: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 36: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 37: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(list(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 38: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "head(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 39: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 40: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "head(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: head(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 41: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 42: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "head(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: head(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 43: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 44: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "length(list(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 45: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "length(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 33: Error: tail(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap deleted file mode 100644 index 68b85f4c7..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap +++ /dev/null @@ -1,568 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Array literals are unpacked in the correct order: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let d = 0; -let c = [ d = d * 10 + 1, d = d * 10 + 2, d = d * 10 + 3]; -d;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 123, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Array literals work as expected: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let c = [1, 2, 3]; -c;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - 3, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Breaks, continues and returns are detected properly inside loops: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - let i = 0; - while(i < 10) { - i = i + 1; - if (i === 1) { - i = 1; - i = 1; - } else if (i === 2) { - i = 2; - continue; - } else if (i === 3) { - i = 3; - return i; - } else if (i === 4) { - i = 4; - break; - } - } - return i; -} -f();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Conditional statements are value producing always: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function fact(n) { - if (n === 0) { - 2; - return 1; - } - if (true) { - let i = 1; - i = i - 1; - } else { - 2; - } - if (false) { - 2; - } else { - const i = 1; - } - return n * fact(n - 1); - } -fact(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 120, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Environment reset is inserted when only instructions are in control stack: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = (v => v)(0);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Nullary functions properly restore environment 1: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - function g(t) { - return 0; - } - return g; -} -const h = f(); -h(100);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Nullary functions properly restore environment 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - const a = 1; - return a; -} -const a = f(); -a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Simple tail call returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in boolean operators work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return false || f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in conditional expressions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x <= 0 ? y : f(x-1, y+1); -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in nested mix of conditional expressions boolean operators work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in arrow block functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -}; -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in arrow functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x <= 0 ? y : f(x-1, y+1); -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mixed tail-call/non-tail-call recursion work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y, z) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+f(0, z, 0), z); - } -} -f(5000, 5000, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 15000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mutual recursion with arrow functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x <= 0 ? y : g(x-1, y+1); -const g = (x, y) => x <= 0 ? y : f(x-1, y+1); -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mutual recursion work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return g(x-1, y+1); - } -} -function g(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`breaks, continues are properly detected in child blocks 1: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 0; -for (i = 1; i < 5; i = i + 1) { - { - const a = i; - if (i === 1) { - continue; - } - } - - { - const a = i; - if (i === 2) { - break; - } - } -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`breaks, continues are properly detected in child blocks 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let a = 0; -for (let i = 1; i < 5; i = i + 1) { - { - const x = 0; - a = i; - if (i === 1) { - continue; - } - } - - { - const x = 0; - a = i; - if (i === 2) { - break; - } - } -} -a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`const uses block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const x = true; - if(true) { - const x = false; - } else { - const x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`continue in while loops are working as intended: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let i = 0; - let j = false; - while (i <= 10){ - if (i === 10){ - j = true; - i = i + 1; - continue; - } - j = false; - i = i + 1; - } - return j; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for loop \`let\` variables are copied into the block scope: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let z = []; - for (let x = 0; x < 10; x = x + 1) { - z[x] = () => x; - } - return z[1](); -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for loops use block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - for (let x = 1; x > 0; x = x - 1) { - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`let uses block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`standalone block statements: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const x = true; - { - const x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`streams and its pre-defined/pre-built functions are working as intended: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function make_alternating_stream(stream) { - return pair(head(stream), () => make_alternating_stream( - negate_whole_stream( - stream_tail(stream)))); -} - -function negate_whole_stream(stream) { - return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); -} - -const ones = pair(1, () => ones); -list_ref(eval_stream(make_alternating_stream(enum_stream(1, 9)), 9), 8);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 9, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`streams can be created and functions with no return statements are still evaluated properly: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const s = stream(true, false, undefined, 1, x=>x, null, -123, head); -const result = []; -stream_for_each(item => {result[array_length(result)] = item;}, s); -stream_ref(s,4)(22) === 22 && stream_ref(s,7)(pair('', '1')) === '1' && result;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`while loops use block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - while (true) { - let x = false; - break; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/cse-machine-callcc-js.ts b/src/cse-machine/__tests__/cse-machine-callcc-js.ts index 375191497..4321b93b2 100644 --- a/src/cse-machine/__tests__/cse-machine-callcc-js.ts +++ b/src/cse-machine/__tests__/cse-machine-callcc-js.ts @@ -1,51 +1,47 @@ import { Chapter, Variant } from '../../types' -import { expectParsedError, expectResult } from '../../utils/testing' import { stripIndent } from '../../utils/formatters' +import { expectResult, expectParsedErrorsToEqual, expectResultsToEqual } from '../../utils/testing/testers' // Continuation tests for Source const optionEC4 = { chapter: Chapter.SOURCE_4, variant: Variant.EXPLICIT_CONTROL } -test('call_cc works with normal functions', () => { - return expectResult( - stripIndent` - 1 + 2 + call_cc((cont) => 3) + 4; +expectResultsToEqual([ + [ + 'call_cc works with normal functions', + '1 + 2 + call_cc((cont) => 3) + 4;', + 10 + ], + [ + 'call_cc can be used to return early', + ` + let x = 1; + call_cc((cont) => { + x = 2; + cont(); + x = 3; + }); + x; `, - optionEC4 - ).toMatchInlineSnapshot(`10`) -}) + 2 + ], -test('call_cc can be used to return early', () => { - return expectResult( - stripIndent` - let x = 1; - call_cc((cont) => { - x = 2; - cont(); - x = 3; - }); - x; - `, - optionEC4 - ).toMatchInlineSnapshot(`2`) -}) +], optionEC4) -test('call_cc throws error when given no arguments', () => { - return expectParsedError( - stripIndent` - 1 + 2 + call_cc() + 4; - `, - optionEC4 - ).toMatchInlineSnapshot(`"Line 1: Expected 1 arguments, but got 0."`) -}) +expectParsedErrorsToEqual([ + [ + 'call_cc throws error when given no arguments', + '1 + 2 + call_cc() + 4;', + "Line 1: Expected 1 arguments, but got 0." + ], + [ + 'call_cc throws error when given > 1 arguments', + ` + const f = (cont) => cont; + 1 + 2 + call_cc(f,f) + 4; + `, + "Line 3: Expected 1 arguments, but got 2." + ] +], optionEC4) -test('call_cc throws error when given > 1 arguments', () => { - return expectParsedError( - stripIndent` - const f = (cont) => cont; - 1 + 2 + call_cc(f,f) + 4; - `, - optionEC4 - ).toMatchInlineSnapshot(`"Line 2: Expected 1 arguments, but got 2."`) -}) test('continuations can be stored as a value', () => { return expectResult( diff --git a/src/cse-machine/__tests__/cse-machine-callcc.ts b/src/cse-machine/__tests__/cse-machine-callcc.ts index f26b52683..6ba0a94df 100644 --- a/src/cse-machine/__tests__/cse-machine-callcc.ts +++ b/src/cse-machine/__tests__/cse-machine-callcc.ts @@ -1,5 +1,5 @@ import { Chapter, Variant } from '../../types' -import { expectParsedError, expectResult } from '../../utils/testing' +import { expectParsedError, expectResult } from '../../utils/testing/testers' // Continuation tests for Scheme const optionECScm = { chapter: Chapter.FULL_SCHEME, variant: Variant.EXPLICIT_CONTROL } diff --git a/src/cse-machine/__tests__/cse-machine-errors.ts b/src/cse-machine/__tests__/cse-machine-errors.ts index 9fa1bdc78..82044d72a 100644 --- a/src/cse-machine/__tests__/cse-machine-errors.ts +++ b/src/cse-machine/__tests__/cse-machine-errors.ts @@ -4,11 +4,9 @@ import * as _ from 'lodash' import { Chapter, Variant } from '../../types' import { stripIndent } from '../../utils/formatters' import { - expectDifferentParsedErrors, expectParsedError, - expectParsedErrorNoSnapshot, expectResult -} from '../../utils/testing' +} from '../../utils/testing/testers' jest.spyOn(_, 'memoize').mockImplementation(func => func as any) @@ -32,7 +30,7 @@ test('Undefined variable error is thrown', () => { }) test('Undefined variable error is thrown - verbose', () => { - return expectParsedError(undefinedVariableVerbose).toMatchInlineSnapshot(` + return expectParsedError(undefinedVariableVerbose, optionEC).toMatchInlineSnapshot(` "Line 2, Column 0: Name im_undefined not declared. Before you can read the value of im_undefined, you need to declare it as a variable or a constant. You can do this using the let or const keywords. " @@ -149,7 +147,7 @@ test("Builtins don't create additional errors when it's not their fault", () => }) test('Infinite recursion with a block bodied function', () => { - return expectParsedErrorNoSnapshot( + return expectParsedError( stripIndent` function i(n) { return n === 0 ? 0 : 1 + i(n-1); @@ -161,7 +159,7 @@ test('Infinite recursion with a block bodied function', () => { }, 15000) test('Infinite recursion with function calls in argument', () => { - return expectParsedErrorNoSnapshot( + return expectParsedError( stripIndent` function i(n, redundant) { return n === 0 ? 0 : 1 + i(n-1, r()); @@ -178,7 +176,7 @@ test('Infinite recursion with function calls in argument', () => { }, 20000) test('Infinite recursion of mutually recursive functions', () => { - return expectParsedErrorNoSnapshot( + return expectParsedError( stripIndent` function f(n) { return n === 0 ? 0 : 1 + g(n - 1); @@ -931,7 +929,7 @@ test('Cascading js errors work properly', () => { }) test('Check that stack is at most 10k in size', () => { - return expectParsedErrorNoSnapshot( + return expectParsedError( stripIndent` function f(x) { if (x <= 0) { diff --git a/src/cse-machine/__tests__/cse-machine-return-regressions.ts b/src/cse-machine/__tests__/cse-machine-return-regressions.ts index 035690ad3..6bc5d7851 100644 --- a/src/cse-machine/__tests__/cse-machine-return-regressions.ts +++ b/src/cse-machine/__tests__/cse-machine-return-regressions.ts @@ -4,10 +4,7 @@ */ import { Chapter, Variant } from '../../types' -import { expectParsedError, expectResult } from '../../utils/testing' - -const optionEC = { variant: Variant.EXPLICIT_CONTROL } -const optionEC3 = { chapter: Chapter.SOURCE_3, variant: Variant.EXPLICIT_CONTROL } +import { expectParsedError, expectResult, testMultipleCases } from '../../utils/testing/testers' // This is bad practice. Don't do this! test('Calling unreachable results in error', () => { @@ -22,282 +19,263 @@ test('Calling unreachable results in error', () => { } f(); `, - optionEC + { variant: Variant.EXPLICIT_CONTROL } ).toMatchInlineSnapshot(`"Line 3: Expected number on right hand side of operation, got boolean."`) }) -// This is bad practice. Don't do this! -test('Bare early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - return 1; - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1) + id(2); - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1); - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in if statements work', () => { - return expectResult( +testMultipleCases<[string, any] | [string, any, Chapter]>([ + [ + 'Bare early returns work', ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - if (true) { + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { return 1; unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in if statements work', () => { - return expectResult( + return 0; + unreachable(); + } + f(); + `, + 1 + ], + [ + 'Recursive call early returns work', ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { return id(1) + id(2); unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in if statements work', () => { - return expectResult( + return 0; + unreachable(); + } + f(); + `, + 3 + ], + [ + 'Tail call early returns work', ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { return id(1); unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`1`) -}) + return 0; + unreachable(); + } + f(); + `, + 1 + ], -// This is bad practice. Don't do this! -test('Bare early returns in while loops work', () => { - return expectResult( + // if statements + [ + 'Bare early returns in if statements work', ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - while (true) { - return 1; + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + if (true) { + return 1; + unreachable(); + } else {} + unreachable(); + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in while loops work', () => { - return expectResult( + f(); + `, + 1 + ], + [ + 'Recursive call early returns in if statements work', ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1) + id(2); + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + if (true) { + return id(1) + id(2); + unreachable(); + } else {} + unreachable(); + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in while loops work', () => { - return expectResult( + f(); + `, + 3 + ], + [ + 'Taill call early returns in if statements work', ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1); + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + if (true) { + return id(1); + unreachable(); + } else {} + unreachable(); + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`1`) -}) + f(); + `, + 1 + ], -// This is bad practice. Don't do this! -test('Bare early returns in for loops work', () => { - return expectResult( + // while loops + [ + 'Bare early returns in while loops work', ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return i+1; + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + while (true) { + return 1; + unreachable(); + } + unreachable(); + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in for loops work', () => { - return expectResult( + f(); + `, + 1, + Chapter.SOURCE_3 + ], + [ + 'Recursive call early returns in while loops work', ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1) + id(i+2); + function unreachable() { + return 1 < true; // Will cause an error } - return 0; - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in for loops work', () => { - return expectResult( + function id(x) { + return x; + } + function f() { + while (true) { + return id(1) + id(2); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 3, + Chapter.SOURCE_3 + ], + [ + 'Tail call returns in while loops work', ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1); + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + while (true) { + return id(1); + unreachable(); + } + unreachable(); + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`1`) + f(); + `, + 1, + Chapter.SOURCE_3 + ], + + // for loops + [ + 'Bare early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return i+1; + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ], + [ + 'Recursive call early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return id(i+1) + id(i+2); + } + return 0; + } + f(); + `, + 3, + Chapter.SOURCE_3 + ], + [ + 'Tail call early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return id(i+1); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ] +], ([code, expected, chapter]) => { + return expectResult(code, { chapter, variant: Variant.EXPLICIT_CONTROL }).toEqual(expected) }) diff --git a/src/cse-machine/__tests__/cse-machine-stdlib.ts b/src/cse-machine/__tests__/cse-machine-stdlib.ts index a9c8d93ff..dcfd92b50 100644 --- a/src/cse-machine/__tests__/cse-machine-stdlib.ts +++ b/src/cse-machine/__tests__/cse-machine-stdlib.ts @@ -1,6 +1,6 @@ import { Chapter, Value, Variant } from '../../types' import { stripIndent } from '../../utils/formatters' -import { expectResult, snapshotFailure } from '../../utils/testing' +import { expectParsedError, expectResult } from '../../utils/testing/testers' test.each([ [ @@ -431,16 +431,11 @@ test.each([ 'Builtins work as expected %#', (chapter: Chapter, snippet: string, passing: boolean, returnValue: Value) => { if (passing) { - return expectResult(stripIndent(snippet), { - chapter, - variant: Variant.EXPLICIT_CONTROL - }).toEqual(returnValue) + return expectResult(stripIndent(snippet), { chapter, variant: Variant.EXPLICIT_CONTROL }) + .toEqual(returnValue) } else { - return snapshotFailure( - stripIndent(snippet), - { chapter, variant: Variant.EXPLICIT_CONTROL }, - 'fails' - ) + return expectParsedError(stripIndent(snippet), { chapter, variant: Variant.EXPLICIT_CONTROL }) + .not.toEqual('') } } ) diff --git a/src/cse-machine/__tests__/cse-machine-unique-id.ts b/src/cse-machine/__tests__/cse-machine-unique-id.ts index 75a972fef..4b90899a3 100644 --- a/src/cse-machine/__tests__/cse-machine-unique-id.ts +++ b/src/cse-machine/__tests__/cse-machine-unique-id.ts @@ -17,13 +17,13 @@ test("Program environment's id continues after prelude", async () => { const context = await getContextFrom('const a = list(1, 2, 3);') // 1 prelude environment + 46 prelude closures in Source 4, // so program environment has id of '47' - expect(context.runtime.environments[0].id).toMatchInlineSnapshot(`"47"`) + expect(context.runtime.environments[0].id).toEqual("47") }) test("Context runtime's objectCount continues after prelude", async () => { const context = await getContextFrom('const a = list(1, 2, 3);') // 1 program environment + 3 arrays from the list function, so final objectCount is 51 - expect(context.runtime.objectCount).toMatchInlineSnapshot(`51`) + expect(context.runtime.objectCount).toEqual(51) }) test("Context runtime's objectCount continues after apply_in_underlying_javascript call", async () => { @@ -37,7 +37,7 @@ test("Context runtime's objectCount continues after apply_in_underlying_javascri ) // 1 program environment + 1 closure + 1 function environment // 3 arrays from list + 1 array from variadic argument + 1 array from from function result - expect(context.runtime.objectCount).toMatchInlineSnapshot(`55`) + expect(context.runtime.objectCount).toEqual(55) }) test('Every environment/array/closure has a unique id', async () => { @@ -60,7 +60,7 @@ test('Every environment/array/closure has a unique id', async () => { // Arrays: 4 arrays created manually + 4 arrays from in-built functions (pair, list), total: 8 // Closures: 46 prelude closures + 1 closure in program (c) + 1 closure in block, total 48 // Total count: 4 + 8 + 48 = 60 - expect(context.runtime.objectCount).toMatchInlineSnapshot(`60`) + expect(context.runtime.objectCount).toEqual(60) }) test('CSE Machine stops at the given step number', async () => { @@ -74,7 +74,7 @@ test('CSE Machine stops at the given step number', async () => { 100 ) // 7 additional environments + 2 arrays created at step 100, so object count is 48 + 7 + 2 = 57 - expect(context.runtime.objectCount).toMatchInlineSnapshot(`57`) + expect(context.runtime.objectCount).toEqual(57) }) const programEnvName = createProgramEnvironment(mockContext(), false).name @@ -106,5 +106,5 @@ test('Program environment id stays the same regardless of amount of steps', asyn break } } - expect(programEnvId).toMatchInlineSnapshot(`"47"`) + expect(programEnvId).toEqual("47") }) diff --git a/src/cse-machine/__tests__/cse-machine.ts b/src/cse-machine/__tests__/cse-machine.ts index 6d9ae0cfe..b86e7aa90 100644 --- a/src/cse-machine/__tests__/cse-machine.ts +++ b/src/cse-machine/__tests__/cse-machine.ts @@ -1,480 +1,421 @@ import { Chapter, Variant } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { expectResult } from '../../utils/testing' - -// jest.mock('lodash', () => ({ -// ...jest.requireActual('lodash'), -// memoize: jest.fn(func => func) -// })) - -jest.mock('../../modules/loader/loaders') - -const optionEC = { variant: Variant.EXPLICIT_CONTROL } -const optionEC3 = { chapter: Chapter.SOURCE_3, variant: Variant.EXPLICIT_CONTROL } -const optionEC4 = { chapter: Chapter.SOURCE_4, variant: Variant.EXPLICIT_CONTROL } - -test('Simple tail call returns work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in conditional expressions work', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x <= 0 ? y : f(x-1, y+1); - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in boolean operators work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return false || f(x-1, y+1); - } - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in nested mix of conditional expressions boolean operators work', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in arrow functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => x <= 0 ? y : f(x-1, y+1); - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in arrow block functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - }; - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mutual recursion work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return g(x-1, y+1); - } - } - function g(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mutual recursion with arrow functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => x <= 0 ? y : g(x-1, y+1); - const g = (x, y) => x <= 0 ? y : f(x-1, y+1); - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mixed tail-call/non-tail-call recursion work', () => { - return expectResult( - stripIndent` - function f(x, y, z) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+f(0, z, 0), z); - } - } - f(5000, 5000, 2); - `, - optionEC - ).toMatchInlineSnapshot(`15000`) -}) - -// This is bad practice. Don't do this! -test('standalone block statements', () => { - return expectResult( - stripIndent` - function test(){ - const x = true; - { - const x = false; - } - return x; - } - test(); - `, - optionEC - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('const uses block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - const x = true; - if(true) { - const x = false; - } else { - const x = false; - } - return x; - } - test(); - `, - optionEC - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('let uses block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; - } - return x; - } - test(); - `, - optionEC3 - ).toMatchInlineSnapshot(`true`) -}) - -test('for loops use block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - for (let x = 1; x > 0; x = x - 1) { +import { expectResult, testMultipleCases } from '../../utils/testing/testers' + +testMultipleCases<[string, any] | [string, any, Chapter]>([ + [ + 'Simple tail call returns work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } } - return x; - } - test(); - `, - optionEC3 - ).toMatchInlineSnapshot(`true`) -}) - -test('while loops use block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - while (true) { - let x = false; - break; + f(5000, 5000); + `, + 10000, + ], + [ + 'Tail call in conditional expressions work', + ` + function f(x, y) { + return x <= 0 ? y : f(x-1, y+1); } - return x; - } - test(); - `, - optionEC4 - ).toMatchInlineSnapshot(`true`) -}) - -test('continue in while loops are working as intended', () => { - return expectResult( - stripIndent` - function test(){ - let i = 0; - let j = false; - while (i <= 10){ - if (i === 10){ - j = true; - i = i + 1; - continue; + f(5000, 5000); + `, + 10000, + ], + [ + 'Tail call in boolean operators work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return false || f(x-1, y+1); } - j = false; - i = i + 1; } - return j; - } - test(); - `, - optionEC4 - ).toMatchInlineSnapshot(`true`) -}) - -// see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation -// and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ -test('for loop `let` variables are copied into the block scope', () => { - return expectResult( - stripIndent` - function test(){ - let z = []; - for (let x = 0; x < 10; x = x + 1) { - z[x] = () => x; + f(5000, 5000); + `, + 10000, + ], + [ + 'Tail call in nested mix of conditional expressions and boolean operators', + ` + function f(x, y) { + return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; } - return z[1](); - } - test(); + f(5000, 5000); `, - optionEC4 - ).toMatchInlineSnapshot(`1`) -}) - -test('streams and its pre-defined/pre-built functions are working as intended', () => { - return expectResult( - stripIndent` - function make_alternating_stream(stream) { - return pair(head(stream), () => make_alternating_stream( - negate_whole_stream( - stream_tail(stream)))); - } - - function negate_whole_stream(stream) { - return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); - } - - const ones = pair(1, () => ones); - list_ref(eval_stream(make_alternating_stream(enum_stream(1, 9)), 9), 8); + 10000, + ], + [ + 'Tail calls in arrow functions work', + ` + const f = (x, y) => x <= 0 ? y : f(x-1, y+1); + f(5000, 5000); `, - optionEC4 - ).toMatchInlineSnapshot(`9`) -}) - -test('streams can be created and functions with no return statements are still evaluated properly', () => { - return expectResult( - stripIndent` - const s = stream(true, false, undefined, 1, x=>x, null, -123, head); - const result = []; - stream_for_each(item => {result[array_length(result)] = item;}, s); - stream_ref(s,4)(22) === 22 && stream_ref(s,7)(pair('', '1')) === '1' && result; + 10000, + ], + [ + 'Tail calls in block arrow functions work', + ` + const f = (x, y) => { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } + }; + f(5000, 5000); `, - optionEC4 - ).toMatchInlineSnapshot(`false`) -}) - -test('Conditional statements are value producing always', () => { - return expectResult( - stripIndent` - function fact(n) { - if (n === 0) { - 2; - return 1; + 10000, + ], + [ + 'Tail calls in mutual recursion work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return g(x-1, y+1); + } } - if (true) { - let i = 1; - i = i - 1; - } else { - 2; + function g(x, y) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } } - if (false) { - 2; - } else { - const i = 1; + f(5000, 5000); + `, + 10000, + ], + [ + 'Tail calls in mutual recursion with arrow functions work', + ` + const f = (x, y) => x <= 0 ? y : g(x-1, y+1); + const g = (x, y) => x <= 0 ? y : f(x-1, y+1); + f(5000, 5000); + `, + 10000, + ], + [ + 'Tail calls in mixed tail-call/non-tail-call recursion work', + ` + function f(x, y, z) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+f(0, z, 0), z); + } } - return n * fact(n - 1); + f(5000, 5000, 2); + `, + 15000, + ], + [ + 'Standalone block statements', + ` + function test(){ + const x = true; + { + const x = false; + } + return x; } - fact(5); + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`120`) -}) - -test('Nullary functions properly restore environment 1', () => { - return expectResult( - stripIndent` - function f() { - function g(t) { - return 0; + true, + ], + [ + 'const uses block scoping instead of function scoping', + ` + function test(){ + const x = true; + if(true) { + const x = false; + } else { + const x = false; + } + return x; } - return g; - } - const h = f(); - h(100); + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`0`) -}) - -test('Nullary functions properly restore environment 2', () => { - return expectResult( - stripIndent` - function f() { - const a = 1; - return a; - } - const a = f(); - a; + true + ], + [ + 'let uses block scoping instead of function scoping', + ` + function test(){ + let x = true; + if(true) { + let x = false; + } else { + let x = false; + } + return x; + } + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`1`) -}) - -test('Array literals work as expected', () => { - return expectResult( - stripIndent` - let c = [1, 2, 3]; - c; + true, + Chapter.SOURCE_3 + ], + [ + 'for loops use block scoping instead of function scoping', + ` + function test() { + let x = true; + for (let x = 1; x > 0; x = x - 1) { + } + return x; + } + test(); `, - optionEC3 - ).toMatchInlineSnapshot(` - Array [ - 1, - 2, - 3, - ] - `) -}) - -test('Array literals are unpacked in the correct order', () => { - return expectResult( - stripIndent` - let d = 0; - let c = [ d = d * 10 + 1, d = d * 10 + 2, d = d * 10 + 3]; - d; + true, + Chapter.SOURCE_3 + ], + [ + 'while loops use block scoping instead of function scoping', + ` + function test(){ + let x = true; + while (true) { + let x = false; + break; + } + return x; + } + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`123`) -}) - -test('Breaks, continues and returns are detected properly inside loops', () => { - return expectResult( - stripIndent` - function f() { - let i = 0; - while(i < 10) { - i = i + 1; - if (i === 1) { - i = 1; - i = 1; - } else if (i === 2) { - i = 2; + true, + Chapter.SOURCE_4 + ], + [ + 'continue in while loops work as intended', + ` + function test(){ + let i = 0; + let j = false; + while (i <= 10){ + if (i === 10){ + j = true; + i = i + 1; continue; - } else if (i === 3) { - i = 3; - return i; - } else if (i === 4) { - i = 4; - break; } + j = false; + i = i + 1; + } + return j; } - return i; - } - f(); + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`3`) -}) - -test('Environment reset is inserted when only instructions are in control stack', () => { - return expectResult( - stripIndent` - const a = (v => v)(0); + true, + Chapter.SOURCE_4 + ], + // see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation + // and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ + [ + 'for loop \'let\' variables are copied into the block scope', + ` + function test(){ + let z = []; + for (let x = 0; x < 10; x = x + 1) { + z[x] = () => x; + } + return z[1](); + } + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`undefined`) -}) + 1, + Chapter.SOURCE_4 + ], + [ + 'streams and its prelude functions work', + ` + function make_alternating_stream(stream) { + return pair(head(stream), () => make_alternating_stream( + negate_whole_stream( + stream_tail(stream)))); + } -test('breaks, continues are properly detected in child blocks 1', () => { - return expectResult( - stripIndent` - let i = 0; - for (i = 1; i < 5; i = i + 1) { - { - const a = i; - if (i === 1) { - continue; - } + function negate_whole_stream(stream) { + return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); + } + + const ones = pair(1, () => ones); + list_ref(eval_stream(make_alternating_stream(enum_stream(1, 9)), 9), 8); + `, + 9, + Chapter.SOURCE_4 + ], + [ + 'streams can be created and functions with no return statements are still evaluated properly', + ` + const s = stream(true, false, undefined, 1, x=>x, null, -123, head); + const result = []; + stream_for_each(item => {result[array_length(result)] = item;}, s); + stream_ref(s,4)(22) === 22 && stream_ref(s,7)(pair('', '1')) === '1' && result; + `, + false, + Chapter.SOURCE_4 + ], + [ + 'Conditional statements are value producing always', + ` + function fact(n) { + if (n === 0) { + 2; + return 1; } - - { - const a = i; - if (i === 2) { - break; - } + if (true) { + let i = 1; + i = i - 1; + } else { + 2; } - } - i; + if (false) { + 2; + } else { + const i = 1; + } + return n * fact(n - 1); + } + fact(5); `, - optionEC3 - ).toMatchInlineSnapshot(`2`) -}) - -test('breaks, continues are properly detected in child blocks 2', () => { - return expectResult( - stripIndent` - let a = 0; - for (let i = 1; i < 5; i = i + 1) { - { - const x = 0; - a = i; - if (i === 1) { - continue; - } + 120, + Chapter.SOURCE_3 + ], + [ + 'Nullary functions properly restore environment 1', + ` + function f() { + function g(t) { + return 0; } - - { - const x = 0; - a = i; - if (i === 2) { - break; + return g; + } + const h = f(); + h(100); + `, + 0, + Chapter.SOURCE_3 + ], + [ + 'Nullary functions properly restore environment 2', + ` + function f() { + const a = 1; + return a; + } + const a = f(); + a; + `, + 1, + Chapter.SOURCE_3 + ], + [ + 'Array literals work as expected', + 'let c = [1, 2, 3]; c;', + [1, 2, 3], + Chapter.SOURCE_3 + ], + [ + 'Array literals are unpacked in the correct order', + ` + let d = 0; + let c = [ d = d * 10 + 1, d = d * 10 + 2, d = d * 10 + 3]; + d; + `, + 123, + Chapter.SOURCE_3 + ], + [ + 'Breaks, continues and returns are detected properly inside loops', + ` + function f() { + let i = 0; + while(i < 10) { + i = i + 1; + if (i === 1) { + i = 1; + i = 1; + } else if (i === 2) { + i = 2; + continue; + } else if (i === 3) { + i = 3; + return i; + } else if (i === 4) { + i = 4; + break; } } - } - a; + return i; + } + f(); + `, + 3, + Chapter.SOURCE_3 + ], + [ + 'Environment reset is inserted when only instructions are in control stack', + 'const a = (v => v)(0);', + undefined, + Chapter.SOURCE_3 + ], + [ + 'breaks, continues are properly detected in child blocks 1', + ` + let i = 0; + for (i = 1; i < 5; i = i + 1) { + { + const a = i; + if (i === 1) { + continue; + } + } + + { + const a = i; + if (i === 2) { + break; + } + } + } + i; + `, + 2, + Chapter.SOURCE_3 + ], + [ + 'breaks, continues are properly detected in child blocks 2', + ` + let a = 0; + for (let i = 1; i < 5; i = i + 1) { + { + const x = 0; + a = i; + if (i === 1) { + continue; + } + } + + { + const x = 0; + a = i; + if (i === 2) { + break; + } + } + } + a; `, - optionEC3 - ).toMatchInlineSnapshot(`2`) + 2, + Chapter.SOURCE_3 + ] +], ([code, expected, chapter]) => { + return expectResult(code, { chapter, variant: Variant.EXPLICIT_CONTROL }).toEqual(expected) }) diff --git a/src/runner/__tests__/native-return-regressions.ts b/src/runner/__tests__/native-return-regressions.ts new file mode 100644 index 000000000..2ff984f90 --- /dev/null +++ b/src/runner/__tests__/native-return-regressions.ts @@ -0,0 +1,283 @@ +/** + * This file contains tests for regressions that TCO may have caused. + * Please reference Issue #124 (https://github.com/source-academy/js-slang/issues/124) + */ + +import { Chapter } from '../../types' +import { expectParsedError, expectResult, testMultipleCases } from '../../utils/testing/testers' + +// TODO Combine with cse-machine return regressions + +// This is bad practice. Don't do this! +test('Calling unreachable results in error', () => { + return expectParsedError( + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + unreachable(); + return 0; + } + f(); + `, + Chapter.SOURCE_1 + ).toMatchInlineSnapshot(`"Line 3: Expected number on right hand side of operation, got boolean."`) +}) + +testMultipleCases<[string, any] | [string, any, Chapter]>([ + [ + 'Bare early returns work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + return 1; + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1 + ], + [ + 'Recursive call early returns work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + return id(1) + id(2); + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 3 + ], + [ + 'Tail call early returns work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + return id(1); + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1 + ], + + // if statements + [ + 'Bare early returns in if statements work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + if (true) { + return 1; + unreachable(); + } else {} + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1 + ], + [ + 'Recursive call early returns in if statements work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + if (true) { + return id(1) + id(2); + unreachable(); + } else {} + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 3 + ], + [ + 'Taill call early returns in if statements work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + if (true) { + return id(1); + unreachable(); + } else {} + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1 + ], + + // while loops + [ + 'Bare early returns in while loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + while (true) { + return 1; + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ], + [ + 'Recursive call early returns in while loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + while (true) { + return id(1) + id(2); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 3, + Chapter.SOURCE_3 + ], + [ + 'Tail call returns in while loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + while (true) { + return id(1); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ], + + // for loops + [ + 'Bare early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return i+1; + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ], + [ + 'Recursive call early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return id(i+1) + id(i+2); + } + return 0; + } + f(); + `, + 3, + Chapter.SOURCE_3 + ], + [ + 'Tail call early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return id(i+1); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ] +], ([code, expected, chapter]) => { + return expectResult(code, chapter ?? Chapter.SOURCE_1).toEqual(expected) +}) diff --git a/src/runner/sourceRunner.ts b/src/runner/sourceRunner.ts index fea46f22d..032120b2b 100644 --- a/src/runner/sourceRunner.ts +++ b/src/runner/sourceRunner.ts @@ -3,7 +3,7 @@ import * as _ from 'lodash' import type { RawSourceMap } from 'source-map' import { type IOptions, type Result } from '..' -import { JSSLANG_PROPERTIES, UNKNOWN_LOCATION } from '../constants' +import { JSSLANG_PROPERTIES } from '../constants' import { CSEResultPromise, evaluate } from '../cse-machine/interpreter' import { ExceptionError } from '../errors/errors' import { RuntimeSourceError } from '../errors/runtimeSourceError' @@ -26,8 +26,6 @@ import { sandboxedEval } from '../transpiler/evalContainer' import { transpile } from '../transpiler/transpiler' import { Chapter, type Context, type RecursivePartial, Variant } from '../types' import { validateAndAnnotate } from '../validator/validator' -import { compileForConcurrent } from '../vm/svml-compiler' -import { runWithProgram } from '../vm/svml-machine' import type { FileGetter } from '../modules/moduleTypes' import { mapResult } from '../alt-langs/mapper' import { toSourceError } from './errors' @@ -59,29 +57,6 @@ let previousCode: { } | null = null let isPreviousCodeTimeoutError = false -function runConcurrent(program: es.Program, context: Context, options: IOptions): Promise { - if (context.shouldIncreaseEvaluationTimeout) { - context.nativeStorage.maxExecTime *= JSSLANG_PROPERTIES.factorToIncreaseBy - } else { - context.nativeStorage.maxExecTime = options.originalMaxExecTime - } - - try { - return Promise.resolve({ - status: 'finished', - context, - value: runWithProgram(compileForConcurrent(program, context), context) - }) - } catch (error) { - if (error instanceof RuntimeSourceError || error instanceof ExceptionError) { - context.errors.push(error) // use ExceptionErrors for non Source Errors - return resolvedErrorPromise - } - context.errors.push(new ExceptionError(error, UNKNOWN_LOCATION)) - return resolvedErrorPromise - } -} - function runSubstitution( program: es.Program, context: Context, @@ -220,10 +195,6 @@ async function sourceRunner( return resolvedErrorPromise } - if (context.variant === Variant.CONCURRENT) { - return runConcurrent(program, context, theOptions) - } - determineExecutionMethod(theOptions, context, program, isVerboseErrorsEnabled) if (context.executionMethod === 'stepper' || theOptions.useSubst) { diff --git a/src/stdlib/__tests__/__snapshots__/pylib.ts.snap b/src/stdlib/__tests__/__snapshots__/pylib.ts.snap deleted file mode 100644 index d945c0379..000000000 --- a/src/stdlib/__tests__/__snapshots__/pylib.ts.snap +++ /dev/null @@ -1,352 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`adding a string and an integer is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"a\\" + 1", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "a1", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`adding an integer and a float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 + 2", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`adding two floats is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 + 2.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`adding two integers is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1 + 2", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3n, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`cannot divide non-number values: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"a\\" / 2", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: Expected number on left hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`cannot floor non-number values: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"a\\" // 2", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: Expected number on left hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`cannot mod non-number values: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"a\\" % 2", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: Expected number on left hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`cannot multiply non-number values: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "True * 2", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: Expected number on left hand side of operation, got boolean.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`cannot power non-number values: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"a\\" ** 2", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: Expected number on left hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`dividing float and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 / 2.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0.5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`dividing integer and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "2 / 1.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`dividing integer and integer is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1 / 2", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0.5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`flooring float and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 // 2.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0n, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`flooring integer and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "2 // 1.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2n, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`flooring integer and integer is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "2 // 1", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2n, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`minusing an integer from a float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 - 2", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": -1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`minusing two floats is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 - 2.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": -1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`minusing two integers is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1 - 2", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": -1n, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`modding float and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 % 2.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`modding integer and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "2 % 1.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`modding integer and integer is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "2 % 1", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0n, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`multiplying float and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 * 2.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`multiplying integer and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 * 2", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`multiplying integer and integer is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1 * 2", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2n, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`powering float and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1.0 ** 2.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`powering integer and float is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "2 ** 1.0", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`powering integer and integer is ok: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "2 ** 1", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2n, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/stdlib/__tests__/pylib.ts b/src/stdlib/__tests__/pylib.ts index 6c24f7c6d..d5b4bf458 100644 --- a/src/stdlib/__tests__/pylib.ts +++ b/src/stdlib/__tests__/pylib.ts @@ -1,256 +1,69 @@ import { Chapter } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { expectResult, expectParsedError } from '../../utils/testing' - -test('adding two integers is ok', () => { - return expectResult( - stripIndent` - 1 + 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(3n) -}) - -test('adding two floats is ok', () => { - return expectResult( - stripIndent` - 1.0 + 2.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(3) -}) - -test('adding an integer and a float is ok', () => { - return expectResult( - stripIndent` - 1.0 + 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(3) -}) - -test('adding a string and an integer is ok', () => { - return expectResult( - stripIndent` - "a" + 1 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual('a1') -}) - -test('minusing two integers is ok', () => { - return expectResult( - stripIndent` - 1 - 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(-1n) -}) - -test('minusing two floats is ok', () => { - return expectResult( - stripIndent` - 1.0 - 2.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(-1) -}) - -test('minusing an integer from a float is ok', () => { - return expectResult( - stripIndent` - 1.0 - 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(-1) -}) - -test('multiplying integer and float is ok', () => { - return expectResult( - stripIndent` - 1.0 * 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(2) -}) - -test('multiplying integer and integer is ok', () => { - return expectResult( - stripIndent` - 1 * 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(2n) -}) - -test('multiplying float and float is ok', () => { - return expectResult( - stripIndent` - 1.0 * 2.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(2) -}) - -test('cannot multiply non-number values', () => { - return expectParsedError( - stripIndent` - True * 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: Expected number on left hand side of operation, got boolean."` - ) -}) - -test('dividing integer and float is ok', () => { - return expectResult( - stripIndent` - 2 / 1.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(2) -}) - -test('dividing integer and integer is ok', () => { - return expectResult( - stripIndent` - 1 / 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(0.5) -}) - -test('dividing float and float is ok', () => { - return expectResult( - stripIndent` - 1.0 / 2.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(0.5) -}) - -test('cannot divide non-number values', () => { - return expectParsedError( - stripIndent` - "a" / 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: Expected number on left hand side of operation, got string."` - ) -}) - -test('modding integer and float is ok', () => { - return expectResult( - stripIndent` - 2 % 1.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(0) -}) - -test('modding integer and integer is ok', () => { - return expectResult( - stripIndent` - 2 % 1 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(0n) -}) - -test('modding float and float is ok', () => { - return expectResult( - stripIndent` - 1.0 % 2.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(1.0) -}) - -test('cannot mod non-number values', () => { - return expectParsedError( - stripIndent` - "a" % 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: Expected number on left hand side of operation, got string."` - ) -}) - -test('powering integer and float is ok', () => { - return expectResult( - stripIndent` - 2 ** 1.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(2.0) -}) - -test('powering integer and integer is ok', () => { - return expectResult( - stripIndent` - 2 ** 1 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(2n) -}) - -test('powering float and float is ok', () => { - return expectResult( - stripIndent` - 1.0 ** 2.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(1.0) -}) - -test('cannot power non-number values', () => { - return expectParsedError( - stripIndent` - "a" ** 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: Expected number on left hand side of operation, got string."` - ) -}) - -test('flooring integer and float is ok', () => { - return expectResult( - stripIndent` - 2 // 1.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(2n) -}) - -test('flooring integer and integer is ok', () => { - return expectResult( - stripIndent` - 2 // 1 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(2n) -}) - -test('flooring float and float is ok', () => { - return expectResult( - stripIndent` - 1.0 // 2.0 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toEqual(0n) -}) - -test('cannot floor non-number values', () => { - return expectParsedError( - stripIndent` - "a" // 2 - `, - { chapter: Chapter.PYTHON_1, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: Expected number on left hand side of operation, got string."` - ) -}) +import { expectParsedErrorsToEqual, expectResultsToEqual } from '../../utils/testing/testers' + +expectResultsToEqual([ + // Addition + ['adding two integers is integer', '1 + 2', 3n], + ['adding two floats is ok', '1.0 + 2.0', 3], + ['adding integer to float is ok', '1.0 + 2', 3], + ['adding integer to string is string', '"a" + 1', 'a1'], + ['adding string to integer is string', '1 + "a"', '1a'], + ['adding string to string is string', '"a" + "b"', 'ab'], + + // Subtraction + ['subtracting two integers is integer', '1 - 2', -1n], + ['subtracting two floats is ok', '1.0 - 2.0', -1], + + // Multiplication + ['multiplying two integers is ok', '1 * 2', 2n], + ['multiplying two floats is ok', '1.0 * 2.0', 2], + ['multiplying integer and float is ok', '1.0 * 2', 2], + + // Division + ['dividing integer by float is ok', '2 / 1.0', 2], + ['dividing float by float is ok', '2.0 / 1.0', 2], + ['dividing integer by integer yields float', '1 / 2', 0.5], + + // Modulo + ['modulo integer by float is ok', '2 % 1.0', 0], + ['modulo float by float is ok', '2.0 % 1.0', 0], + ['modulo integer by integer is integer', '2 % 1', 0n], + + // Exponentiation + ['exponentiating integer by float is ok', '2 ** 1.0', 2], + ['exponentiating float by float is ok', '2.0 ** 1.0', 2], + ['exponentiating integer by integer is integer', '2 ** 1', 2n], + + // Floor Division + ['integer floor division by float is integer', '2 // 1.0', 2n], + ['integer floor division by integer is integer', '2 // 1', 2n], + ['float floor division by float is integer', '2.0 // 1.0', 2n] +], Chapter.PYTHON_1) + +expectParsedErrorsToEqual([ + [ + 'Cannot multiply non-number values', + 'True * 2', + "Line 1: Error: Expected number on left hand side of operation, got boolean." + ], + [ + 'Cannot divide non-number values', + '"a" / 2', + "Line 1: Error: Expected number on left hand side of operation, got string." + ], + [ + 'Cannot modulo non-number values', + '"a" % 2', + "Line 1: Error: Expected number on left hand side of operation, got string." + ], + [ + 'Cannot exponentiate non-number values', + '"a" ** 2', + "Line 1: Error: Expected number on left hand side of operation, got string." + ], + [ + 'Cannot floor divide non-number values', + '"a" // 2', + "Line 1: Error: Expected number on left hand side of operation, got string." + ] +], Chapter.PYTHON_1) diff --git a/src/utils/testing/testers.ts b/src/utils/testing/testers.ts index fd02820bd..d8064c35a 100644 --- a/src/utils/testing/testers.ts +++ b/src/utils/testing/testers.ts @@ -1,9 +1,9 @@ import type { Program } from 'estree' -import { Chapter, type Context, type Finished } from '../../types' +import { Chapter, type Context, type Finished, type Variant } from '../../types' import { mockContext } from '../../mocks/context' import { parse } from '../../parser/parser' import { parseError, runInContext, type Result } from '../..' -import { createTestContext } from '.' +import { createTestContext, type TestBuiltins } from '.' export type TestCase> = [string, ...T] @@ -81,10 +81,28 @@ export function astTester( testMultipleCases(fullCases, args => func(...args), includeIndex, timeout) } -export function expectResult(code: string, chapter: Chapter) { - const context = createTestContext({ chapter }) +export type TestOptions = { + chapter?: Chapter + variant?: Variant + testBuiltins?: TestBuiltins +} | Chapter + +async function testInContext(code: string, rawOptions: TestOptions) { + const options: TestOptions = typeof rawOptions === 'number' ? { + chapter: rawOptions + } : rawOptions + + const context = createTestContext(options) + const result = await runInContext(code, context) + return { + context, + result + } +} + +export function expectResult(code: string, options: TestOptions) { return expect( - runInContext(code, context).then(result => { + testInContext(code, options).then(({ result, context }) => { if (result.status === 'error') { console.log(context.errors) } @@ -97,12 +115,12 @@ export function expectResult(code: string, chapter: Chapter) { type ExpectResultsTestCase = [string, string, any] | [string, string, any, Chapter] export function expectResultsToEqual( snippets: ExpectResultsTestCase[], - defaultChapter: Chapter = Chapter.SOURCE_1, + options: TestOptions = Chapter.SOURCE_1, includeIndex?: boolean, timeout?: number ) { const fullSnippets = snippets.map(snippet => { - const chapter = snippet.length === 4 ? snippet[3] : defaultChapter + const chapter = snippet.length === 4 ? snippet[3] : options return [snippet[0], snippet[1], snippet[2], chapter] as [string, string, any, Chapter] }) @@ -116,10 +134,9 @@ export function expectResultsToEqual( ) } -export function expectParsedError(code: string, chapter: Chapter, verbose?: boolean) { - const context = mockContext(chapter) +export function expectParsedError(code: string, options: TestOptions, verbose?: boolean) { return expect( - runInContext(code, context).then(result => { + testInContext(code, options).then(({ result, context }) => { expect(result.status).toEqual('error') return parseError(context.errors, verbose) }) @@ -128,12 +145,12 @@ export function expectParsedError(code: string, chapter: Chapter, verbose?: bool export function expectParsedErrorsToEqual( snippets: (ExpectResultsTestCase | [...ExpectResultsTestCase, boolean])[], - defaultChapter: Chapter = Chapter.SOURCE_1, + options: TestOptions = Chapter.SOURCE_1, includeIndex?: boolean, timeout?: number ) { const fullSnippets = snippets.map(snippet => { - const chapter = snippet.length >= 4 ? snippet[3] : defaultChapter + const chapter = snippet.length >= 4 ? snippet[3] : options const verbose = snippet.length === 5 ? snippet[4] : false return [snippet[0], snippet[1], snippet[2], chapter, verbose] as [ string, @@ -158,10 +175,9 @@ export function expectFinishedResult(result: Result): asserts result is Finished expect(result.status).toEqual('finished') } -export function expectDisplayResult(code: string, chapter: Chapter) { - const context = createTestContext({ chapter }) +export function expectDisplayResult(code: string, options: TestOptions) { return expect( - runInContext(code, context).then(result => { + testInContext(code, options).then(({ context, result }) => { expectFinishedResult(result) return context.displayResult }) From 36834b58c86a44c3c378f1991dc3cb0ab2a1bebc Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 22 May 2024 11:33:29 +0800 Subject: [PATCH 29/65] Rewrite svmc and add it to the js-slang cli --- src/repl/main.ts | 6 +- src/repl/svmc.ts | 105 +++++++++++++++++++++ src/vm/svmc.ts | 231 ----------------------------------------------- 3 files changed, 110 insertions(+), 232 deletions(-) create mode 100644 src/repl/svmc.ts delete mode 100644 src/vm/svmc.ts diff --git a/src/repl/main.ts b/src/repl/main.ts index 0177a3873..912b0791f 100644 --- a/src/repl/main.ts +++ b/src/repl/main.ts @@ -2,8 +2,12 @@ import { Command } from '@commander-js/extra-typings' +import { getSVMCCommand } from './svmc' import { getReplCommand } from './repl' import { transpilerCommand } from './transpiler' export const getMainCommand = () => - new Command().addCommand(transpilerCommand).addCommand(getReplCommand(), { isDefault: true }) + new Command() + .addCommand(getSVMCCommand()) + .addCommand(transpilerCommand) + .addCommand(getReplCommand(), { isDefault: true }) diff --git a/src/repl/svmc.ts b/src/repl/svmc.ts new file mode 100644 index 000000000..fffda3c31 --- /dev/null +++ b/src/repl/svmc.ts @@ -0,0 +1,105 @@ +import pathlib from 'path' +import type fslib from 'fs/promises' + +import { Command, InvalidArgumentError, Option } from '@commander-js/extra-typings' +import { createEmptyContext } from '../createContext' +import { parse } from '../parser/parser' +import { INTERNAL_FUNCTIONS as concurrentInternalFunctions } from '../stdlib/vm.prelude' +import { Chapter, Variant } from '../types' +import { stripIndent } from '../utils/formatters' +import { parseError } from '..' +import { assemble } from '../vm/svml-assembler' +import { compileToIns } from '../vm/svml-compiler' +import { stringifyProgram } from '../vm/util' +import { chapterParser, getChapterOption, getVariantOption } from './utils' + +const compileToChoices = ['ast', 'binary', 'debug', 'json'] as const + +export const getSVMCCommand = () => + new Command('svmc') + .argument('', 'File to read code from') + .addOption(getChapterOption(Chapter.SOURCE_3, chapterParser)) + .addOption(getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.CONCURRENT])) + .addOption( + new Option( + '-t, --compileTo ', + stripIndent` + json: Compile only, but don't assemble. + binary: Compile and assemble. + debug: Compile and pretty-print the compiler output. For debugging the compiler. + ast: Parse and pretty-print the AST. For debugging the parser.` + ) + .choices(compileToChoices) + .default('binary' as (typeof compileToChoices)[number]) + ) + .option( + '-o, --out ', + stripIndent` + Sets the output filename. + Defaults to the input filename, minus any '.js' extension, plus '.svm'. + ` + ) + .addOption( + new Option( + '-i, --internals ', + `Sets the list of VM-internal functions. The argument should be a JSON array of +strings containing the names of the VM-internal functions.` + ) + .argParser(value => { + const parsed = JSON.parse(value) + if (!Array.isArray(parsed)) { + throw new InvalidArgumentError('Expected a JSON array of strings!') + } + + for (const each of parsed) { + if (typeof each !== 'string') { + throw new InvalidArgumentError('Expected a JSON array of strings!') + } + } + return parsed as string[] + }) + .default([] as string[]) + ) + .action(async (inputFile, opts) => { + const fs: typeof fslib = require('fs/promises') + + if (opts.variant === Variant.CONCURRENT && opts.internals) { + console.warn( + 'Warning: ignoring internal functions specified on command line for concurrent VM' + ) + } + + const vmInternalFunctions = + opts.variant === Variant.CONCURRENT + ? concurrentInternalFunctions.map(([name]) => name) + : opts.internals || [] + + const source = await fs.readFile(inputFile, 'utf-8') + const context = createEmptyContext(opts.chapter, opts.variant, [], null) + const program = parse(source, context) + if (program === null) { + console.error(parseError(context.errors)) + process.exit(1) + } + + // the current compiler does not differentiate between chapters 1, 2 or 3 + const compiled = compileToIns(program, undefined, vmInternalFunctions) + switch (opts.compileTo) { + case 'ast': { + console.log(JSON.stringify(program, undefined, 2)) + return + } + case 'debug': { + console.log(stringifyProgram(compiled).trimEnd()) + return + } + case 'json': { + console.log(JSON.stringify(compiled)) + return + } + } + + const binary = assemble(compiled) + const outputFilename = opts.out ?? `${pathlib.basename(inputFile)}.svm` + return fs.writeFile(outputFilename, binary) + }) diff --git a/src/vm/svmc.ts b/src/vm/svmc.ts deleted file mode 100644 index 0fe160ce1..000000000 --- a/src/vm/svmc.ts +++ /dev/null @@ -1,231 +0,0 @@ -import * as fs from 'fs' -import * as util from 'util' - -import { createEmptyContext } from '../createContext' -import { parse } from '../parser/parser' -import { INTERNAL_FUNCTIONS as concurrentInternalFunctions } from '../stdlib/vm.prelude' -import { Chapter, Variant } from '../types' -import { assemble } from './svml-assembler' -import { compileToIns } from './svml-compiler' -import { stringifyProgram } from './util' - -interface CliOptions { - compileTo: 'debug' | 'json' | 'binary' | 'ast' - sourceChapter: Chapter.SOURCE_1 | Chapter.SOURCE_2 | Chapter.SOURCE_3 - sourceVariant: Variant.DEFAULT | Variant.CONCURRENT // does not support other variants - inputFilename: string - outputFilename: string | null - vmInternalFunctions: string[] | null -} - -const readFileAsync = util.promisify(fs.readFile) -const writeFileAsync = util.promisify(fs.writeFile) - -// This is a console program. We're going to print. -/* tslint:disable:no-console */ - -function parseOptions(): CliOptions | null { - const ret: CliOptions = { - compileTo: 'binary', - sourceChapter: Chapter.SOURCE_3, - sourceVariant: Variant.DEFAULT, - inputFilename: '', - outputFilename: null, - vmInternalFunctions: null - } - - let endOfOptions = false - let error = false - const args = process.argv.slice(2) - while (args.length > 0) { - let option = args[0] - let argument = args[1] - let argShiftNumber = 2 - if (!endOfOptions && option.startsWith('--') && option.includes('=')) { - ;[option, argument] = option.split('=') - argShiftNumber = 1 - } - if (!endOfOptions && option.startsWith('-')) { - switch (option) { - case '--compile-to': - case '-t': - switch (argument) { - case 'debug': - case 'json': - case 'binary': - case 'ast': - ret.compileTo = argument - break - default: - console.error('Invalid argument to --compile-to: %s', argument) - error = true - break - } - args.splice(0, argShiftNumber) - break - case '--chapter': - case '-c': - const argInt = parseInt(argument, 10) - if (argInt === 1 || argInt === 2 || argInt === 3) { - ret.sourceChapter = argInt - } else { - console.error('Invalid Source chapter: %d', argInt) - error = true - } - args.splice(0, argShiftNumber) - break - case '--variant': - case '-v': - switch (argument) { - case Variant.DEFAULT: - case Variant.CONCURRENT: - ret.sourceVariant = argument - break - default: - console.error('Invalid/Unsupported Source Variant: %s', argument) - error = true - break - } - args.splice(0, argShiftNumber) - break - case '--out': - case '-o': - ret.outputFilename = argument - args.splice(0, argShiftNumber) - break - case '--internals': - case '-i': - ret.vmInternalFunctions = JSON.parse(argument) - args.splice(0, argShiftNumber) - break - case '--': - endOfOptions = true - args.shift() - break - default: - console.error('Unknown option %s', option) - args.shift() - error = true - break - } - } else { - if (ret.inputFilename === '') { - ret.inputFilename = args[0] - } else { - console.error('Excess non-option argument: %s', args[0]) - error = true - } - args.shift() - } - } - - if (ret.inputFilename === '') { - console.error('No input file specified') - error = true - } - - return error ? null : ret -} - -async function main() { - const options = parseOptions() - if (options == null) { - console.error(`Usage: svmc [options...] - -Options: --t, --compile-to