UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

241 lines 7.65 kB
"use strict"; /** * list.ts: Supporting lists in the Scheme style, using pairs made * up of two-element JavaScript array (vector) * @author: Martin Henz * Translated to TypeScript by Evan Sebastian */ Object.defineProperty(exports, "__esModule", { value: true }); exports.pair = pair; exports.is_pair = is_pair; exports.head = head; exports.tail = tail; exports.is_null = is_null; exports.list = list; exports.is_list = is_list; exports.list_to_vector = list_to_vector; exports.vector_to_list = vector_to_list; exports.set_head = set_head; exports.set_tail = set_tail; exports.accumulate = accumulate; exports.length = length; exports.rawDisplayList = rawDisplayList; const stringify_1 = require("../utils/stringify"); // array test works differently for Rhino and // the Firefox environment (especially Web Console) function array_test(x) { if (Array.isArray === undefined) { return x instanceof Array; } else { return Array.isArray(x); } } /** * constructs a pair using a two-element array\ * LOW-LEVEL FUNCTION, NOT SOURCE */ function pair(x, xs) { return [x, xs]; } /** * returns true iff arg is a two-element array\ * LOW-LEVEL FUNCTION, NOT SOURCE */ function is_pair(x) { return array_test(x) && x.length === 2; } function head(xs) { if (is_pair(xs)) { return xs[0]; } else { throw new Error(`${head.name}(xs) expects a pair as argument xs, but encountered ${(0, stringify_1.stringify)(xs)}`); } } function tail(xs) { if (is_pair(xs)) { return xs[1]; } else { throw new Error(`${tail.name}(xs) expects a pair as argument xs, but encountered ${(0, stringify_1.stringify)(xs)}`); } } /** * returns true if arg is exactly null\ * LOW-LEVEL FUNCTION, NOT SOURCE */ function is_null(xs) { return xs === null; } /** * makes a list out of its arguments\ * LOW-LEVEL FUNCTION, NOT SOURCE */ function list(...elements) { let theList = null; for (let i = elements.length - 1; i >= 0; i -= 1) { theList = pair(elements[i], theList); } return theList; } /** * recurses down the list and checks that it ends with the empty list null\ * LOW-LEVEL FUNCTION, NOT SOURCE */ function is_list(xs) { while (is_pair(xs)) { xs = tail(xs); } return is_null(xs); } /** * returns vector that contains the elements of the argument list * in the given order.\ * LOW-LEVEL FUNCTION, NOT SOURCE * @throws an exception if the argument is not a list */ function list_to_vector(lst) { const vector = []; while (!is_null(lst)) { vector.push(head(lst)); lst = tail(lst); } return vector; } /** * returns a list that contains the elements of the argument vector * in the given order\ * LOW-LEVEL FUNCTION, NOT SOURCE * @throws an exception if the argument is not a vector */ function vector_to_list(vector) { return list(...vector); } /** * changes the head of given pair xs to be x\ * LOW-LEVEL FUNCTION, NOT SOURCE * @throws an exception if the argument is not a pair */ function set_head(xs, x) { if (is_pair(xs)) { xs[0] = x; } else { throw new Error(`${set_head.name}(xs,x) expects a pair as argument xs, but encountered ${(0, stringify_1.stringify)(xs)}`); } } /** * changes the tail of given pair xs to be x\ * LOW-LEVEL FUNCTION, NOT SOURCE * @throws an exception if the argument is not a pair */ function set_tail(xs, x) { if (is_pair(xs)) { xs[1] = x; } else { throw new Error(`${set_tail.name}(xs,x) expects a pair as argument xs, but encountered ${(0, stringify_1.stringify)(xs)}`); } } /** * Accumulate applies given operation op to elements of a list * in a right-to-left order, first apply op to the last element * and an initial element, 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(op, initial, sequence) { // Use CPS to prevent stack overflow function $accumulate(xs, cont) { return is_null(xs) ? cont(initial) : $accumulate(tail(xs), x => cont(op(head(xs), x))); } return $accumulate(sequence, x => x); } /** * returns the length of a List xs. Throws an exception if xs is not a List */ function length(xs) { if (!is_list(xs)) { throw new Error(`${length.name}(xs) expects a list`); } return accumulate((_, total) => total + 1, 0, xs); } function rawDisplayList(display, xs, prepend) { const visited = new Set(); // Everything is put into this set, values, arrays, and even objects if they exist const asListObjects = new Map(); // maps original list nodes to new list nodes // We will convert list-like structures in xs to ListObject. class ListObject { replArrayContents() { const result = []; let curXs = this.listNode; while (curXs !== null) { result.push(head(curXs)); curXs = tail(curXs); } return result; } constructor(listNode) { this.replPrefix = 'list('; this.replSuffix = ')'; this.listNode = listNode; } } function getListObject(curXs) { return asListObjects.get(curXs) || curXs; } const pairsToProcess = []; let i = 0; pairsToProcess.push(xs); // we need the guarantee that if there are any proper lists, // then the nodes of the proper list appear as a subsequence of this array. // We ensure this by always adding the tail after the current node is processed. // This means that sometimes, we add the same pair more than once! // But because we only process each pair once due to the visited check, // and each pair can only contribute to at most 3 items in this array, // this array has O(n) elements. while (i < pairsToProcess.length) { const curXs = pairsToProcess[i]; i++; if (visited.has(curXs)) { continue; } visited.add(curXs); if (!is_pair(curXs)) { continue; } pairsToProcess.push(head(curXs), tail(curXs)); } // go through pairs in reverse to ensure the dependencies are resolved first while (pairsToProcess.length > 0) { const curXs = pairsToProcess.pop(); if (!is_pair(curXs)) { continue; } const h = head(curXs); const t = tail(curXs); const newTail = getListObject(t); // the reason why we need the above guarantee const newXs = is_null(newTail) || newTail instanceof ListObject ? new ListObject(pair(h, t)) // tail is a proper list : pair(h, t); // it's not a proper list, make a copy of the pair so we can change references below asListObjects.set(curXs, newXs); } for (const curXs of asListObjects.values()) { if (is_pair(curXs)) { set_head(curXs, getListObject(head(curXs))); set_tail(curXs, getListObject(tail(curXs))); } else if (curXs instanceof ListObject) { set_head(curXs.listNode, getListObject(head(curXs.listNode))); let newTail = getListObject(tail(curXs.listNode)); if (newTail instanceof ListObject) { newTail = newTail.listNode; } set_tail(curXs.listNode, newTail); } } display(getListObject(xs), prepend); return xs; } //# sourceMappingURL=list.js.map