• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / js-slang / 19080105112

04 Nov 2025 07:13PM UTC coverage: 76.655% (-1.5%) from 78.192%
19080105112

push

github

web-flow
Migrate to Vitest (#1815)

* Upgrade TS to 5.8

* Remove deprecated tsconfig option

* Remove duplicate properties

* Upgrade TS to v5.9

* Add types for mathjs

* Fix some type errors

* Update tsconfig

* Fix more type errors

* Fix remaining errors

* Update GitHub workflows

* Fix type error

* Update scm-slang to latest

* Add newline to EOF

* Fix cse-machine types and utils to use fewer type assertions

* Migrate to vitest tests

* Migrate tests to vitest

* Relocate base error files and types

* Get modules tests working

* run format

* Sort tsconfig compiler options

* Update eslint packages to match typescript version

* Small linting change

* Use function names instead of strings for describe blocks

* Include scripts in linting

* Move tests and replace describe titles with functions

* Add type modifiers and reformat tests

* Simplify isEnvDependent code

* Instruct tsc to ignore py-slang's tests during build

* Move walkers to be under utils/ast

* Update tests failing due to timeout

* Update cse-machine typings

* Incorporate import assertions into docs importer

* Add context property to error result

* Update test timeout and add no-restricted-import rule for commander imports

* Update snapshots

* Run format

* Update snapshots again....

* Run format

* Change to use the test.each

* Disable the svmc snapshot test cause it doesn't work

* Add a new test for properties when loading modules

* Run format

* Convert stdlib parser to use nodetypetonode helper type

* A working version of the statementSeqTransform

* More compact version of seq transform

* Remove unnecessary type assertions

* Clean up some documentation bits and pieces

* Use type imports for tracer

* Swap the list library to use generics

* Fix some error messages and tests

* Fix list tests

* Run format

* Update stream library and tests

* Running format

* Add some documentation for the scripts

* Remove unnecessary packages

* Remove even more unnecessary ty... (continued)

3501 of 4761 branches covered (73.53%)

Branch coverage included in aggregate %.

429 of 636 new or added lines in 54 files covered. (67.45%)

34 existing lines in 12 files now uncovered.

7085 of 9049 relevant lines covered (78.3%)

193248.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.04
/src/stdlib/list.ts
1
/**
2
 * list.ts: Supporting lists in the Scheme style, using pairs made
3
 *          up of two-element JavaScript array (vector)
4
 * @author: Martin Henz
5
 * Translated to TypeScript by Evan Sebastian
6
 */
7

8
import type { Value } from '../types'
9
import { type ArrayLike, stringify } from '../utils/stringify'
10

11
export type Pair<H, T> = [H, T]
12
export type List<T = unknown> = null | NonEmptyList<T>
13
type NonEmptyList<T> = Pair<T, any>
14

15
// array test works differently for Rhino and
16
// the Firefox environment (especially Web Console)
17
function array_test(x: unknown): x is unknown[] {
18
  if (Array.isArray === undefined) {
11,307!
19
    return x instanceof Array
×
20
  } else {
21
    return Array.isArray(x)
11,307✔
22
  }
23
}
24

25
/**
26
 * constructs a pair using a two-element array\
27
 * LOW-LEVEL FUNCTION, NOT SOURCE
28
 */
29
export function pair<H, T>(x: H, xs: T): Pair<H, T> {
30
  return [x, xs]
4,929✔
31
}
32

33
/**
34
 * returns true iff arg is a two-element array\
35
 * LOW-LEVEL FUNCTION, NOT SOURCE
36
 */
37
export function is_pair(x: unknown): x is Pair<unknown, unknown> {
38
  return array_test(x) && x.length === 2
11,307✔
39
}
40

41
/**
42
 * returns the first component of the given pair\
43
 * LOW-LEVEL FUNCTION, NOT SOURCE
44
 * @throws an exception if the argument is not a pair
45
 */
46
export function head<T>(xs: Pair<T, unknown> | NonEmptyList<T>): T
47
export function head(xs: unknown): unknown
48
export function head(xs: unknown) {
49
  if (is_pair(xs)) {
2,687✔
50
    return xs[0]
2,674✔
51
  } else {
52
    throw new Error(
13✔
53
      `${head.name}(xs) expects a pair as argument xs, but encountered ${stringify(xs)}`
54
    )
55
  }
56
}
57

58
/**
59
 * returns the second component of the given pair\
60
 * LOW-LEVEL FUNCTION, NOT SOURCE
61
 * @throws an exception if the argument is not a pair
62
 */
63
export function tail<T>(xs: NonEmptyList<T>): List<T>
64
export function tail<T>(xs: Pair<unknown, T>): T
65
export function tail(xs: unknown): unknown
66
export function tail(xs: unknown) {
67
  if (is_pair(xs)) {
3,842✔
68
    return xs[1]
3,828✔
69
  } else {
70
    throw new Error(
14✔
71
      `${tail.name}(xs) expects a pair as argument xs, but encountered ${stringify(xs)}`
72
    )
73
  }
74
}
75

76
/**
77
 * returns true if arg is exactly null\
78
 * LOW-LEVEL FUNCTION, NOT SOURCE
79
 */
80
export function is_null(xs: unknown): xs is null {
81
  return xs === null
2,372✔
82
}
83

84
/**
85
 * makes a list out of its arguments\
86
 * LOW-LEVEL FUNCTION, NOT SOURCE
87
 */
88
export function list<T>(...elements: T[]): List<T> {
89
  let theList = null
897✔
90
  for (let i = elements.length - 1; i >= 0; i -= 1) {
897✔
91
    theList = pair(elements[i], theList)
2,184✔
92
  }
93
  return theList
897✔
94
}
95

96
/**
97
 * recurses down the list and checks that it ends with the empty list null\
98
 * LOW-LEVEL FUNCTION, NOT SOURCE
99
 */
100
export function is_list(xs: unknown): xs is List {
101
  while (is_pair(xs)) {
6✔
102
    xs = tail(xs)
6✔
103
  }
104
  return is_null(xs)
6✔
105
}
106

107
/**
108
 * returns vector that contains the elements of the argument list
109
 * in the given order.\
110
 * LOW-LEVEL FUNCTION, NOT SOURCE
111
 * @throws an exception if the argument is not a list
112
 */
113
export function list_to_vector<T>(lst: List<T>): T[] {
114
  const vector = []
6✔
115
  while (!is_null(lst)) {
6✔
116
    vector.push(head(lst))
14✔
117
    lst = tail(lst)
14✔
118
  }
119
  return vector
6✔
120
}
121

122
/**
123
 * returns a list that contains the elements of the argument vector
124
 * in the given order\
125
 * LOW-LEVEL FUNCTION, NOT SOURCE
126
 * @throws an exception if the argument is not a vector
127
 */
128
export function vector_to_list<T>(vector: T[]): List<T> {
129
  return list(...vector)
782✔
130
}
131

132
/**
133
 * changes the head of given pair xs to be x\
134
 * LOW-LEVEL FUNCTION, NOT SOURCE
135
 * @throws an exception if the argument is not a pair
136
 */
137
export function set_head(xs: unknown, x: any): void {
138
  if (is_pair(xs)) {
480✔
139
    xs[0] = x
478✔
140
  } else {
141
    throw new Error(
2✔
142
      `${set_head.name}(xs,x) expects a pair as argument xs, but encountered ${stringify(xs)}`
143
    )
144
  }
145
}
146

147
/**
148
 * changes the tail of given pair xs to be x\
149
 * LOW-LEVEL FUNCTION, NOT SOURCE
150
 * @throws an exception if the argument is not a pair
151
 */
152
export function set_tail(xs: unknown, x: any): void {
153
  if (is_pair(xs)) {
499✔
154
    xs[1] = x
497✔
155
  } else {
156
    throw new Error(
2✔
157
      `${set_tail.name}(xs,x) expects a pair as argument xs, but encountered ${stringify(xs)}`
158
    )
159
  }
160
}
161

162
/**
163
 * Accumulate applies given operation op to elements of a list
164
 * in a right-to-left order, first apply op to the last element
165
 * and an initial element, resulting in r1, then to the second-last
166
 * element and r1, resulting in r2, etc, and finally to the first element
167
 * and r_n-1, where n is the length of the list. `accumulate(op,zero,list(1,2,3))`
168
 * results in `op(1, op(2, op(3, zero)))`
169
 */
170
export function accumulate<T, U>(op: (each: T, result: U) => U, initial: U, sequence: List<T>): U {
171
  // Use CPS to prevent stack overflow
172
  function $accumulate(xs: typeof sequence, cont: (each: U) => U): U {
173
    return is_null(xs) ? cont(initial) : $accumulate(tail(xs), x => cont(op(head(xs), x)))
9✔
174
  }
175
  return $accumulate(sequence, x => x)
2✔
176
}
177

178
/**
179
 * returns the length of a List xs. Throws an exception if xs is not a List
180
 */
181
export function length(xs: unknown): number {
182
  if (!is_list(xs)) {
×
NEW
183
    throw new Error(`${length.name}(xs) expects a list`)
×
184
  }
185

186
  return accumulate((_, total) => total + 1, 0, xs)
×
187
}
188

189
export function rawDisplayList(display: any, xs: Value, prepend: string) {
190
  const visited: Set<Value> = new Set() // Everything is put into this set, values, arrays, and even objects if they exist
18✔
191
  const asListObjects: Map<NonEmptyList<unknown>, NonEmptyList<unknown> | ListObject> = new Map() // maps original list nodes to new list nodes
18✔
192

193
  // We will convert list-like structures in xs to ListObject.
194
  class ListObject implements ArrayLike {
195
    replPrefix = 'list('
415✔
196
    replSuffix = ')'
415✔
197
    replArrayContents(): Value[] {
198
      const result: Value[] = []
84✔
199
      let curXs = this.listNode
84✔
200
      while (curXs !== null) {
84✔
201
        result.push(head(curXs))
415✔
202
        curXs = tail(curXs)
415✔
203
      }
204
      return result
84✔
205
    }
206
    listNode: List
207

208
    constructor(listNode: List) {
209
      this.listNode = listNode
415✔
210
    }
211
  }
212
  function getListObject(curXs: Value): Value {
213
    return asListObjects.get(curXs) || curXs
1,470✔
214
  }
215

216
  const pairsToProcess: Value[] = []
18✔
217
  let i = 0
18✔
218
  pairsToProcess.push(xs)
18✔
219
  // we need the guarantee that if there are any proper lists,
220
  // then the nodes of the proper list appear as a subsequence of this array.
221
  // We ensure this by always adding the tail after the current node is processed.
222
  // This means that sometimes, we add the same pair more than once!
223
  // But because we only process each pair once due to the visited check,
224
  // and each pair can only contribute to at most 3 items in this array,
225
  // this array has O(n) elements.
226
  while (i < pairsToProcess.length) {
18✔
227
    const curXs = pairsToProcess[i]
972✔
228
    i++
972✔
229
    if (visited.has(curXs)) {
972✔
230
      continue
369✔
231
    }
232
    visited.add(curXs)
603✔
233
    if (!is_pair(curXs)) {
603✔
234
      continue
126✔
235
    }
236
    pairsToProcess.push(head(curXs), tail(curXs))
477✔
237
  }
238

239
  // go through pairs in reverse to ensure the dependencies are resolved first
240
  while (pairsToProcess.length > 0) {
18✔
241
    const curXs = pairsToProcess.pop()
972✔
242
    if (!is_pair(curXs)) {
972✔
243
      continue
474✔
244
    }
245
    const h = head(curXs)
498✔
246
    const t = tail(curXs)
498✔
247
    const newTail = getListObject(t) // the reason why we need the above guarantee
498✔
248
    const newXs =
249
      is_null(newTail) || newTail instanceof ListObject
498✔
250
        ? new ListObject(pair(h, t)) // tail is a proper list
251
        : pair(h, t) // it's not a proper list, make a copy of the pair so we can change references below
252
    asListObjects.set(curXs, newXs)
972✔
253
  }
254

255
  for (const curXs of asListObjects.values()) {
18✔
256
    if (is_pair(curXs)) {
477✔
257
      set_head(curXs, getListObject(head(curXs)))
65✔
258
      set_tail(curXs, getListObject(tail(curXs)))
65✔
259
    } else if (curXs instanceof ListObject) {
412!
260
      set_head(curXs.listNode, getListObject(head(curXs.listNode)))
412✔
261
      let newTail = getListObject(tail(curXs.listNode))
412✔
262
      if (newTail instanceof ListObject) {
412✔
263
        newTail = newTail.listNode
331✔
264
      }
265
      set_tail(curXs.listNode, newTail)
412✔
266
    }
267
  }
268
  display(getListObject(xs), prepend)
18✔
269
  return xs
18✔
270
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc