Repository: source-academy/js-slang
Pull request #143 (2018-12-08 08:32:34)

Changed empty list to null.
Hopefully all the things depending on empty lists have been updated too.

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

* **30** of **38**   **(78.95%)**  changed or added relevant lines in **2** files are covered.
* **2** unchanged lines in **2** files lost coverage.
* Overall coverage increased (+**4.05%**) to **65.734%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/stdlib/list.ts](https://coveralls.io/builds/20525785/source?filename=src%2Fstdlib%2Flist.ts#L119) | 26 | 34 | 76.47%
<!-- | **Total:** | **30** | **38** | **78.95%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/stdlib/list.ts](https://coveralls.io/builds/20525785/source?filename=src%2Fstdlib%2Flist.ts#L121) | 1 | 77.0% |
| [src/interop.ts](https://coveralls.io/builds/20525785/source?filename=src%2Finterop.ts#L52) | 1 | 74.03% |
<!-- | **Total:** | **2** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/20525785/badge)](https://coveralls.io/builds/20525785) |
| :-- | --: |
| Change from base [Build 370](https://coveralls.io/builds/20523608): |  4.05% |
| Covered Lines: | 1261 |
| Relevant Lines: | 1846 |

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


Patch:
diff --git a/src/createContext.ts b/src/createContext.ts
index 12255e04..d15f2fba 100644
--- a/src/createContext.ts
+++ b/src/createContext.ts
@@ -2,6 +2,7 @@ import * as list from './stdlib/list'
 import * as misc from './stdlib/misc'
 import { Context, CustomBuiltIns, Value } from './types'
 import { toString } from '.';
+import { list_to_vector } from './stdlib/list'
 
 /** Import meta-circular parser */
 const createParserModule = require('./stdlib/parser')
@@ -114,11 +115,12 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn
 
   if (context.chapter >= 2) {
     // List library
+    defineSymbol(context, 'null', null)
     defineSymbol(context, 'pair', list.pair)
     defineSymbol(context, 'is_pair', list.is_pair)
     defineSymbol(context, 'head', list.head)
     defineSymbol(context, 'tail', list.tail)
-    defineSymbol(context, 'is_empty_list', list.is_empty_list)
+    defineSymbol(context, 'is_null', list.is_null)
     defineSymbol(context, 'is_list', list.is_list)
     defineSymbol(context, 'list', list.list)
     defineSymbol(context, 'length', list.length)
@@ -156,14 +158,7 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn
       fun: Function,
       args: Value
     ) {
-      const res = []
-      var i = 0
-      while (!(args.length === 0)) {
-        res[i] = args[0]
-        i = i + 1
-        args = args[1]
-      }
-      return fun.apply(fun, res)
+      return fun.apply(fun, list_to_vector(args))
     })
     defineSymbol(context, 'is_number', misc.is_number)
     defineSymbol(context, 'is_array', misc.is_array)
diff --git a/src/stdlib/list.ts b/src/stdlib/list.ts
index 87c3abb0..e8f49088 100644
--- a/src/stdlib/list.ts
+++ b/src/stdlib/list.ts
@@ -65,29 +65,19 @@ export function tail(xs: List) {
 }
 tail.__SOURCE__ = 'tail(xs)'
 
-// is_empty_list returns true if arg is []
+// is_null returns true if arg is exactly null
 // LOW-LEVEL FUNCTION, NOT SOURCE
-export function is_empty_list(xs: List) {
-  if (array_test(xs)) {
-    if (xs.length === 0) {
-      return true
-    } else if (xs.length === 2) {
-      return false
-    } else {
-      return false
-    }
-  } else {
-    return false
-  }
+export function is_null(xs: List) {
+  return xs === null
 }
-is_empty_list.__SOURCE__ = 'is_empty_list(xs)'
+is_null.__SOURCE__ = 'is_null(xs)'
 
 // is_list recurses down the list and checks that it ends with the empty list []
 // does not throw Value exceptions
 // LOW-LEVEL FUNCTION, NOT SOURCE
 export function is_list(xs: List) {
   for (; ; xs = tail(xs)) {
-    if (is_empty_list(xs)) {
+    if (is_null(xs)) {
       return true
     } else if (!is_pair(xs)) {
       return false
@@ -99,8 +89,8 @@ is_list.__SOURCE__ = 'is_list(xs)'
 // list makes a list out of its arguments
 // LOW-LEVEL FUNCTION, NOT SOURCE
 export function list() {
-  var the_list = []
-  for (var i = arguments.length - 1; i >= 0; i--) {
+  let the_list = null
+  for (let i = arguments.length - 1; i >= 0; i--) {
     the_list = pair(arguments[i], the_list)
   }
   return the_list
@@ -112,8 +102,8 @@ list.__SOURCE__ = 'list(x, y, ...)'
 // list_to_vector throws an exception if the argument is not a list
 // LOW-LEVEL FUNCTION, NOT SOURCE
 export function list_to_vector(lst: List) {
-  var vector = []
-  while (!is_empty_list(lst)) {
+  const vector = []
+  while (!is_null(lst)) {
     vector.push(head(lst))
     lst = tail(lst)
   }
@@ -126,12 +116,8 @@ list_to_vector.__SOURCE__ = 'list_to_vector(xs)'
 // vector_to_list throws an exception if the argument is not a vector
 // LOW-LEVEL FUNCTION, NOT SOURCE
 export function vector_to_list(vector: Value[]) {
-  if (vector.length === 0) {
-    return []
-  }
-
-  var result = []
-  for (var i = vector.length - 1; i >= 0; i = i - 1) {
+  let result = null
+  for (let i = vector.length - 1; i >= 0; i = i - 1) {
     result = pair(vector[i], result)
   }
   return result
@@ -141,8 +127,9 @@ vector_to_list.__SOURCE__ = 'vector_to_list(vs)'
 // returns the length of a given argument list
 // throws an exception if the argument is not a list
 export function length(xs: List) {
-  for (var i = 0; !is_empty_list(xs); ++i) {
-    xs = tail(xs)
+  let i = 0
+  while (!is_null(xs)) {
+    i += 1
   }
   return i
 }
@@ -155,8 +142,8 @@ length.__SOURCE__ = 'length(xs)'
 // map throws an exception if the second argument is not a list,
 // and if the second argument is a non-empty list and the first
 // argument is not a function.
-export function map(f: Function, xs: List): List {
-  return is_empty_list(xs) ? [] : pair(f(head(xs)), map(f, tail(xs)))
+export function map(f: Function, xs: List): List | null {
+  return is_null(xs) ? null : pair(f(head(xs)), map(f, tail(xs)))
 }
 map.__SOURCE__ = 'map(f, xs)'
 
@@ -164,15 +151,15 @@ map.__SOURCE__ = 'map(f, xs)'
 // 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.
-export function build_list(n: number, fun: Function) {
-  function build(i: number, fun: Function, already_built: List): List {
+export function build_list(n: number, fun: Function): List | null {
+  function build(i: number, fun: Function, already_built: List | null): List | null {
     if (i < 0) {
       return already_built
     } else {
       return build(i - 1, fun, pair(fun(i), already_built))
     }
   }
-  return build(n - 1, fun, [])
+  return build(n - 1, fun, null)
 }
 build_list.__SOURCE__ = 'build_list(n, fun)'
 
@@ -187,7 +174,7 @@ export function for_each(fun: Function, xs: List) {
   if (!is_list(xs)) {
     throw new Error('for_each expects a list as argument xs, but ' + 'encountered ' + xs)
   }
-  for (; !is_empty_list(xs); xs = tail(xs)) {
+  for (; !is_null(xs); xs = tail(xs)) {
     fun(head(xs))
   }
   return true
@@ -208,8 +195,8 @@ export function reverse(xs: List) {
   if (!is_list(xs)) {
     throw new Error('reverse(xs) expects a list as argument xs, but ' + 'encountered ' + xs)
   }
-  var result = []
-  for (; !is_empty_list(xs); xs = tail(xs)) {
+  let result = null
+  for (; !is_null(xs); xs = tail(xs)) {
     result = pair(head(xs), result)
   }
   return result
@@ -221,7 +208,7 @@ reverse.__SOURCE__ = 'reverse(xs)'
 // is replaced by the second argument list
 // append throws an exception if the first argument is not a list
 export function append(xs: List, ys: List): List {
-  if (is_empty_list(xs)) {
+  if (is_null(xs)) {
     return ys
   } else {
     return pair(head(xs), append(tail(xs), ys))
@@ -234,21 +221,21 @@ append.__SOURCE__ = 'append(xs, ys)'
 // that starts with the given element. It returns [] if the
 // element does not occur in the list
 export function member(v: Value, xs: List) {
-  for (; !is_empty_list(xs); xs = tail(xs)) {
+  for (; !is_null(xs); xs = tail(xs)) {
     if (head(xs) === v) {
       return xs
     }
   }
-  return []
+  return null
 }
 member.__SOURCE__ = 'member(x, xs)'
 
 // removes the first occurrence of a given first-argument element
 // in a given second-argument list. Returns the original list
 // if there is no occurrence.
-export function remove(v: Value, xs: List): List {
-  if (is_empty_list(xs)) {
-    return []
+export function remove(v: Value, xs: List): List | null {
+  if (is_null(xs)) {
+    return null
   } else {
     if (v === head(xs)) {
       return tail(xs)
@@ -260,9 +247,9 @@ export function remove(v: Value, xs: List): List {
 remove.__SOURCE__ = 'remove(x, xs)'
 
 // Similar to remove. But removes all instances of v instead of just the first
-export function remove_all(v: Value, xs: List): List {
-  if (is_empty_list(xs)) {
-    return []
+export function remove_all(v: Value, xs: List): List | null {
+  if (is_null(xs)) {
+    return null
   } else {
     if (v === head(xs)) {
       return remove_all(v, tail(xs))
@@ -280,8 +267,6 @@ export const removeAll = remove_all
 export function equal(item1: Value, item2: Value): boolean {
   if (is_pair(item1) && is_pair(item2)) {
     return equal(head(item1), head(item2)) && equal(tail(item1), tail(item2))
-  } else if (array_test(item1) && item1.length === 0 && array_test(item2) && item2.length === 0) {
-    return true
   } else {
     return item1 === item2
   }
@@ -295,7 +280,7 @@ equal.__SOURCE__ = 'equal(x, y)'
 // first argument v. Returns false if there is no such
 // pair
 export function assoc(v: Value, xs: List): boolean {
-  if (is_empty_list(xs)) {
+  if (is_null(xs)) {
     return false
   } else if (equal(v, head(head(xs)))) {
     return head(xs)
@@ -308,7 +293,7 @@ assoc.__SOURCE__ = 'assoc(v, xs)'
 // filter returns the sublist of elements of given list xs
 // for which the given predicate function returns true.
 export function filter(pred: Function, xs: List): List {
-  if (is_empty_list(xs)) {
+  if (is_null(xs)) {
     return xs
   } else {
     if (pred(head(xs))) {
@@ -323,9 +308,9 @@ filter.__SOURCE__ = 'filter(pred, xs)'
 // enumerates numbers starting from start,
 // using a step size of 1, until the number
 // exceeds end.
-export function enum_list(start: number, end: number): List {
+export function enum_list(start: number, end: number): List | null {
   if (start > end) {
-    return []
+    return null
   } else {
     return pair(start, enum_list(start + 1, end))
   }
@@ -357,7 +342,7 @@ list_ref.__SOURCE__ = 'list_ref(xs, n)'
 // op(1, op(2, op(3, zero)))
 
 export function accumulate<T>(op: (value: Value, acc: T) => T, initial: T, sequence: List): T {
-  if (is_empty_list(sequence)) {
+  if (is_null(sequence)) {
     return initial
   } else {
     return op(head(sequence), accumulate(op, initial, tail(sequence)))
diff --git a/src/stdlib/metacircular-interpreter/lib/exports.js b/src/stdlib/metacircular-interpreter/lib/exports.js
index e13670e5..9a8a563d 100644
--- a/src/stdlib/metacircular-interpreter/lib/exports.js
+++ b/src/stdlib/metacircular-interpreter/lib/exports.js
@@ -76,7 +76,7 @@ export_symbol('pair', pair, "(x, y)");
 export_symbol('is_pair', is_pair, "(x)");
 export_symbol('head', head, "(xs)");
 export_symbol('tail', tail, "(xs)");
-export_symbol('is_empty_list', is_empty_list, "(xs)");
+export_symbol('is_null', is_empty_list, "(xs)");
 export_symbol('is_list', is_list, "(x)");
 export_symbol('list', list);
 export_symbol('list_to_vector', list_to_vector, "(xs)");
diff --git a/src/stdlib/metacircular-interpreter/lib/interpreter/interpreter.js b/src/stdlib/metacircular-interpreter/lib/interpreter/interpreter.js
index 154ea4f9..88088162 100644
--- a/src/stdlib/metacircular-interpreter/lib/interpreter/interpreter.js
+++ b/src/stdlib/metacircular-interpreter/lib/interpreter/interpreter.js
@@ -725,7 +725,7 @@ var primitive_functions =
 	pair("list", list),
 	pair("length", length),
 	pair("map", map),
-	pair("is_empty_list", is_empty_list),
+	pair("is_null", is_empty_list),
 
 	//Intepreter functions
 	pair("parse", parse),


Test patch:
diff --git a/src/__tests__/__snapshots__/allowed_syntax.ts.snap b/src/__tests__/__snapshots__/allowed_syntax.ts.snap
index 760b7b23..6d238da5 100644
--- a/src/__tests__/__snapshots__/allowed_syntax.ts.snap
+++ b/src/__tests__/__snapshots__/allowed_syntax.ts.snap
@@ -143,7 +143,7 @@ exports[`Syntaxes are allowed in the chapter they are introduced 35`] = `""`;
 exports[`Syntaxes are allowed in the chapter they are introduced 36`] = `
 Array [
   1,
-  Array [],
+  null,
 ]
 `;
 
diff --git a/src/__tests__/__snapshots__/index.ts.snap b/src/__tests__/__snapshots__/index.ts.snap
index f6c199b8..36a96c2c 100644
--- a/src/__tests__/__snapshots__/index.ts.snap
+++ b/src/__tests__/__snapshots__/index.ts.snap
@@ -43,7 +43,7 @@ Object {
 
 exports[`Inifinite recursion with list args represents CallExpression well 1`] = `
 "Line 2: Infinite recursion
-  f([1, [2, []]])..  f([1, [2, []]])..  f([1, [2, []]]).."
+  f([1, [2, null]])..  f([1, [2, null]])..  f([1, [2, null]]).."
 `;
 
 exports[`Metacircular Interpreter parses Arrow Function Assignments properly 1`] = `
@@ -110,7 +110,7 @@ Object {
 exports[`list now uses Source toString instead of native when +ed with another string 1`] = `
 Object {
   "status": "finished",
-  "value": "123[4, [5, [6, []]]]",
+  "value": "123[4, [5, [6, null]]]",
 }
 `;
 
diff --git a/src/__tests__/__snapshots__/lists.ts.snap b/src/__tests__/__snapshots__/lists.ts.snap
new file mode 100644
index 00000000..2a721ba6
--- /dev/null
+++ b/src/__tests__/__snapshots__/lists.ts.snap
@@ -0,0 +1,148 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`accumulate 1`] = `
+Object {
+  "status": "finished",
+  "value": 10,
+}
+`;
+
+exports[`append 1`] = `
+Object {
+  "status": "finished",
+  "value": true,
+}
+`;
+
+exports[`build_list 1`] = `
+Object {
+  "status": "finished",
+  "value": true,
+}
+`;
+
+exports[`empty list is null 1`] = `
+Object {
+  "status": "finished",
+  "value": null,
+}
+`;
+
+exports[`enum_list 1`] = `
+Object {
+  "status": "finished",
+  "value": true,
+}
+`;
+
+exports[`filter 1`] = `
+Object {
+  "status": "finished",
+  "value": true,
+}
+`;
+
+exports[`for_each 1`] = `
+Object {
+  "status": "finished",
+  "value": 6,
+}
+`;
+
+exports[`head works 1`] = `
+Object {
+  "status": "finished",
+  "value": 1,
+}
+`;
+
+exports[`list creates list 1`] = `
+Object {
+  "status": "finished",
+  "value": Array [
+    1,
+    Array [
+      "a string \\"\\"",
+      Array [
+        [Function],
+        Array [
+          [Function],
+          Array [
+            true,
+            Array [
+              3.14,
+              null,
+            ],
+          ],
+        ],
+      ],
+    ],
+  ],
+}
+`;
+
+exports[`list_ref 1`] = `
+Object {
+  "status": "finished",
+  "value": 4,
+}
+`;
+
+exports[`map 1`] = `
+Object {
+  "status": "finished",
+  "value": true,
+}
+`;
+
+exports[`member 1`] = `
+Object {
+  "status": "finished",
+  "value": true,
+}
+`;
+
+exports[`pair creates pair 1`] = `
+Object {
+  "status": "finished",
+  "value": Array [
+    1,
+    "a string \\"\\"",
+  ],
+}
+`;
+
+exports[`remove 1`] = `
+Object {
+  "status": "finished",
+  "value": null,
+}
+`;
+
+exports[`remove_all 1`] = `
+Object {
+  "status": "finished",
+  "value": true,
+}
+`;
+
+exports[`reverse 1`] = `
+Object {
+  "status": "finished",
+  "value": true,
+}
+`;
+
+exports[`tail of a 1 element list is null 1`] = `
+Object {
+  "status": "finished",
+  "value": null,
+}
+`;
+
+exports[`tail works 1`] = `
+Object {
+  "status": "finished",
+  "value": "a string \\"\\"",
+}
+`;
diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts
index e6284b52..e7def124 100644
--- a/src/__tests__/index.ts
+++ b/src/__tests__/index.ts
@@ -80,7 +80,7 @@ test('list now uses Source toString instead of native when +ed with another stri
   return promise.then(obj => {
     expect(obj).toMatchSnapshot()
     expect(obj.status).toBe('finished')
-    expect((obj as Finished).value).toBe('123[4, [5, [6, []]]]')
+    expect((obj as Finished).value).toBe('123[4, [5, [6, null]]]')
   })
 })
 
@@ -297,3 +297,51 @@ test('Deep object assignment and retrieval', () => {
     expect((obj as Finished).value).toBe('string')
   })
 })
+
+test('Test apply_in_underlying_javascript', () => {
+  const code = `
+  apply_in_underlying_javascript((a, b, c) => a * b * c, list(2, 5, 6));
+  `
+  const context = mockContext(4)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(60)
+  })
+})
+
+test('Test equal for primitives', () => {
+  const code = `
+  equal(1, 1) && equal("str", "str") && equal(null, null) && !equal(1, 2) && !equal("str", "");
+  `
+  const context = mockContext(4)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('Test equal for lists', () => {
+  const code = `
+  equal(list(1, 2), pair(1, pair(2, null))) && equal(list(1, 2, 3, 4), list(1, 2, 3, 4));
+  `
+  const context = mockContext(4)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('Test equal for different lists', () => {
+  const code = `
+  !equal(list(1, 2), pair(1, 2)) && !equal(list(1, 2, 3), list(1, list(2, 3)));
+  `
+  const context = mockContext(4)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
diff --git a/src/__tests__/lists.ts b/src/__tests__/lists.ts
new file mode 100644
index 00000000..820c51be
--- /dev/null
+++ b/src/__tests__/lists.ts
@@ -0,0 +1,257 @@
+import { mockContext } from '../mocks/context'
+import { parseError, runInContext } from '../index'
+import { Finished } from '../types'
+
+test('list creates list', () => {
+  const code = `
+    function f() { return 1; }
+    list(1, 'a string ""', () => a, f, true, 3.14);
+  `
+  const context = mockContext(2)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+  })
+})
+
+test('pair creates pair', () => {
+  const code = `
+    pair(1, 'a string ""');
+  `
+  const context = mockContext(2)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+  })
+})
+
+test('head works', () => {
+  const code = `
+    head(pair(1, 'a string ""'));
+  `
+  const context = mockContext(2)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(1)
+  })
+})
+
+test('tail works', () => {
+  const code = `
+    tail(pair(1, 'a string ""'));
+  `
+  const context = mockContext(2)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe('a string ""')
+  })
+})
+
+test('tail of a 1 element list is null', () => {
+  const code = `
+    tail(list(1));
+  `
+  const context = mockContext(2)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(null)
+  })
+})
+
+test('empty list is null', () => {
+  const code = `
+    list();
+  `
+  const context = mockContext(2)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(null)
+  })
+})
+
+test('for_each', () => {
+  const code = `
+    let sum = 0;
+    for_each(x => {sum = sum + x;}, list(1, 2, 3));
+    sum;
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(6)
+  })
+})
+
+test('map', () => {
+  const code = `
+    equal(map(x => 2 * x, list(12, 11, 3)), list(24, 22, 6));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('filter', () => {
+  const code = `
+    equal(filter(x => x <= 4, list(2, 10, 1000, 1, 3, 100, 4, 5, 2, 1000)), list(2, 1, 3, 4, 2));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('build_list', () => {
+  const code = `
+    equal(build_list(5, x => x * x), list(0, 1, 4, 9, 16));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('reverse', () => {
+  const code = `
+    equal(reverse(list("string", null, undefined, null, 123)), list(123, null, undefined, null, "string"));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('append', () => {
+  const code = `
+    equal(append(list("string", 123), list(456, null, undefined)), list("string", 123, 456, null, undefined));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('member', () => {
+  const code = `
+    equal(member("string", list(1, 2, 3, "string", 123, 456, null, undefined)), list("string", 123, 456, null, undefined));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('remove', () => {
+  const code = `
+    remove(1, list(1));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(null)
+  })
+})
+
+test('remove_all', () => {
+  const code = `
+    equal(remove_all(1, list(1, 2, 3, 4, 1, 1, "1", 5, 1, 1, 6)), list(2, 3, 4, "1", 5, 6));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('enum_list', () => {
+  const code = `
+    equal(enum_list(1, 5), list(1, 2, 3, 4, 5));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(true)
+  })
+})
+
+test('list_ref', () => {
+  const code = `
+    list_ref(list(1, 2, 3, "4", 4), 4);
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(4)
+  })
+})
+
+test('accumulate', () => {
+  const code = `
+    accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1));
+  `
+  const context = mockContext(3)
+  const promise = runInContext(code, context, { scheduler: 'preemptive' })
+  return promise.then(obj => {
+    expect(obj).toMatchSnapshot()
+    expect(parseError(context.errors)).toBe('')
+    expect(obj.status).toBe('finished')
+    expect((obj as Finished).value).toBe(10)
+  })
+})
+