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

source-academy / js-slang / 24599667526

18 Apr 2026 07:18AM UTC coverage: 78.498% (+0.1%) from 78.391%
24599667526

Pull #1893

github

web-flow
Merge a8d162f2c into 8a89e808f
Pull Request #1893: Error Handling and Stringify Changes

3116 of 4188 branches covered (74.4%)

Branch coverage included in aggregate %.

787 of 960 new or added lines in 76 files covered. (81.98%)

20 existing lines in 11 files now uncovered.

7055 of 8769 relevant lines covered (80.45%)

180596.21 hits per line

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

61.73
/src/stdlib/stream.ts
1
import { GeneralRuntimeError } from '../errors/base';
2
import { InvalidParameterTypeError } from '../errors/rttcErrors';
3
import { wrap } from '../utils/operators';
4
import { head, is_null, is_pair, type List, type Pair, pair, tail } from './list';
5
import { arity } from './misc';
6

7
type NonEmptyStream<T = unknown> = Pair<T, () => Stream<T>>;
8
export type Stream<T = unknown> = null | NonEmptyStream<T>;
9

10
function createStreamPair<T>(item: T, next: () => Stream<T>): NonEmptyStream<T> {
11
  const streamTail = wrap(next, '() => ...', false, null);
112✔
12
  return pair(item, streamTail);
112✔
13
}
14

15
/**
16
 * Makes a Stream out of its arguments\
17
 * LOW-LEVEL FUNCTION, NOT SOURCE
18
 */
19
export function stream(): null;
20
export function stream<T>(...elements: T[]): NonEmptyStream<T>;
21
export function stream<T>(...elements: T[]): Stream<T> {
22
  if (elements.length === 0) return null;
142✔
23

24
  const [item, ...rest] = elements;
112✔
25
  return createStreamPair(item, () => stream(...rest));
112✔
26
}
27

28
// same as list_to_stream in stream.prelude.ts
29
export function list_to_stream<T>(xs: List<T>): Stream<T> {
NEW
30
  return is_null(xs) ? null : createStreamPair(head(xs), () => list_to_stream(tail(xs)));
×
31
}
32

33
export function stream_tail<T>(stream: NonEmptyStream<T>): Stream<T>;
34
export function stream_tail(stream: unknown): Stream<unknown> {
35
  if (!is_pair(stream)) {
39✔
36
    throw new InvalidParameterTypeError('non-empty stream', stream, stream_tail.name);
1✔
37
  }
38

39
  const next = tail(stream);
38✔
40
  if (typeof next !== 'function' || arity(next) !== 0) {
38!
NEW
41
    throw new InvalidParameterTypeError('stream', stream, stream_tail.name);
×
42
  }
43

44
  return next();
38✔
45
}
46

47
/**
48
 * Returns `true` is the given object is a stream.
49
 *
50
 * NOT Lazy: The function must evaluate all the elements of the stream
51
 */
52
export function is_stream(obj: unknown): obj is Stream<unknown> {
NEW
53
  if (is_null(obj)) return true;
×
54

NEW
55
  if (!is_pair(obj)) return false;
×
NEW
56
  const next = tail(obj);
×
57

NEW
58
  if (typeof next !== 'function') return false;
×
NEW
59
  if (arity(next) !== 0) return false;
×
60

NEW
61
  return is_stream(next());
×
62
}
63

64
/**
65
 * Constructs an infinite stream of integers, beginning with n, incrementing 1 at
66
 * a time.
67
 *
68
 * Lazy.
69
 */
70
export function integers_from(n: number): Stream<number> {
NEW
71
  return pair(n, () => integers_from(n + 1));
×
72
}
73

74
/**
75
 * Builds a stream of n elements by applying the provided function to the
76
 * numbers [0, n-1].
77
 *
78
 * Lazy: f is only called when the resulting stream is forced.
79
 */
80
export function build_stream<T>(f: (n: number) => T, n: number): Stream<T> {
81
  function build(i: number): Stream<T> {
82
    return i >= n ? null : pair(f(i), () => build(i + 1));
28✔
83
  }
84

85
  return build(0);
7✔
86
}
87

88
/**
89
 * Applies the provided function to elements of the given stream.
90
 *
91
 * Lazy: the provided function does not execute until the
92
 * forced by the resulting stream.
93
 */
94
export function stream_map<T, U>(f: (arg: T) => U, s: Stream<T>): Stream<U> {
95
  return is_null(s) ? null : pair(f(head(s)), () => stream_map(f, stream_tail(s)));
1!
96
}
97

98
/**
99
 * Returns a substream containing the elements that the provided predicate returned `true` for.
100
 *
101
 * Partially Lazy: Both the predicate and stream are evaluated as necessary
102
 */
103
export function stream_filter<T, U extends T>(f: (arg: T) => arg is U, s: Stream<T>): Stream<U>;
104
export function stream_filter<T>(f: (arg: T) => boolean, s: Stream<T>): Stream<T>;
105
export function stream_filter<T>(f: (arg: T) => boolean, s: Stream<T>): Stream<T> {
106
  if (is_null(s)) return null;
9✔
107

108
  const should = f(head(s));
8✔
109

110
  return should
8✔
111
    ? pair(head(s), () => stream_filter(f, stream_tail(s)))
2✔
112
    : stream_filter(f, stream_tail(s));
113
}
114

115
/**
116
 * Applies the given function to every single element of the stream.
117
 *
118
 * NOT lazy: This function evaluates the entire stream.
119
 */
120
export function stream_for_each<T>(f: (arg: T) => void, s: Stream<T>) {
121
  if (is_null(s)) {
4✔
122
    return true;
2✔
123
  } else {
124
    f(head(s));
2✔
125
    return stream_for_each(f, stream_tail(s));
2✔
126
  }
127
}
128

129
/**
130
 * Accumulate applies given operation op to elements of a stream
131
 * in a right-to-left order, first apply op to the last element
132
 * and an initial element, resulting in r1, then to the second-last
133
 * element and r1, resulting in r2, etc, and finally to the first element
134
 * and r_n-1, where n is the length of the list. `accumulate(op,zero,list(1,2,3))`
135
 * results in `op(1, op(2, op(3, zero)))`.
136
 *
137
 * NOT lazy: This function evaluates the entire stream when called
138
 */
139
export function stream_accumulate<T, U>(
140
  f: (each: T, result: U) => U,
141
  initial: U,
142
  stream: Stream<T>,
143
): U {
NEW
144
  let res = initial;
×
NEW
145
  let entry = stream;
×
146

NEW
147
  while (!is_null(entry)) {
×
NEW
148
    const element = head(entry);
×
NEW
149
    res = f(element, res);
×
NEW
150
    entry = stream_tail(entry);
×
151
  }
152

NEW
153
  return res;
×
154
}
155

156
/**
157
 * Returns the length of the stream.
158
 *
159
 * NOT lazy: The function must evaluate the entire stream
160
 */
161
export function stream_length(s: Stream<unknown>): number {
NEW
162
  return stream_accumulate((_, res) => res + 1, 0, s);
×
163
}
164

165
/**
166
 * Returns the nth element of the stream.
167
 *
168
 * NOT lazy: The stream must be evaluated up to the nth element.
169
 */
170
export function stream_ref<T>(s: Stream<T>, n: number): T {
171
  for (let i = 0; i < n && !is_null(s); i++) {
1✔
172
    s = stream_tail(s);
3✔
173
  }
174

175
  if (is_null(s)) {
1!
NEW
176
    throw new GeneralRuntimeError(`${stream_ref.name}: Index ${n} out of bounds!`);
×
177
  }
178

179
  return head(s);
1✔
180
}
181

182
/**
183
 * Appends a stream to the end of another stream.
184
 *
185
 * @param lhs Stream to append to
186
 * @param rhs Stream to be appending
187
 *
188
 * Lazy: `rhs` is only evaluated after `lhs` is fully evaluated. This
189
 * does mean that if `lhs` is infinite elements from `rhs` will never be evaluated.
190
 */
191
export function stream_append<T>(lhs: Stream<T>, rhs: Stream<T>): Stream<T> {
192
  return is_null(lhs) ? rhs : pair(head(lhs), () => stream_append(stream_tail(lhs), rhs));
8✔
193
}
194

195
/**
196
 * Converts the given stream to a {@link List|list}.
197
 *
198
 * NOT Lazy: Function has to evaluate every element of the stream
199
 * to populate the resulting list
200
 */
201
export function stream_to_list<T>(stream: Stream<T>): List<T> {
202
  return is_null(stream) ? null : pair(head(stream), stream_to_list(stream_tail(stream)));
17✔
203
}
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