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

source-academy / js-slang / 28499948653

01 Jul 2026 07:06AM UTC coverage: 78.717% (+0.2%) from 78.53%
28499948653

push

github

web-flow
Operators Fix (#1995)

* Add tests for getVariantName

(cherry picked from commit db806e4d2)

* Fix callIfFuncAndRightArgs not being exported

* Remove the unknown overload from callWithoutMetadata

* Add type guard checking to isTupleOfLength

* Fix TypeOfConstantsToType type

* Fix format

* Move ChapterStrings type to langs file

* Add some missing tests

* Make sure that library functions also use callWithoutMetadata

* Fix potential transpiler case where an object's methods might not get called correctly

* Fix incorrect error message when using InvalidNumberParameterError

* Change toThrowError, Fix incorrect array access, Use unicode for number errors

* Revert change to memberexpressions

* Add @internal specifier for ReplResult

* Test max args

* Make wrap work

* General cleanup

* Remove fs

* Add wrapUnsafe

* Add tests for handling default arguments

* Run format

* Use original definition of FunctionOfLength

* Add docstring to wrap

* Allow js-slang to handle rest and optional parameters

* Export parameter specifier

* Add tests for docsToHtml

* Update docstrings

* Add utility for validating function arguments

* Fix stepper and cse-machine potentially not treating function arity correctly

* Fix stepper call to builtins not checking arity correctly

* Run format

* Add function names to errors

3181 of 4249 branches covered (74.86%)

Branch coverage included in aggregate %.

117 of 124 new or added lines in 19 files covered. (94.35%)

7090 of 8799 relevant lines covered (80.58%)

173917.05 hits per line

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

60.98
/src/stdlib/stream.ts
1
import { GeneralRuntimeError } from '../errors/base';
2
import { InvalidParameterTypeError } from '../errors/rttcErrors';
3
import { callWithoutMetadata, 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, undefined, undefined, '() => ...');
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> {
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!
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> {
53
  if (is_null(obj)) return true;
×
54

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

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

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> {
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(callWithoutMetadata(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)
1!
96
    ? null
NEW
97
    : pair(callWithoutMetadata(f, head(s)), () => stream_map(f, stream_tail(s)));
×
98
}
99

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

110
  const should = callWithoutMetadata(f, head(s));
8✔
111

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

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

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

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

155
  return res;
×
156
}
157

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

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

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

181
  return head(s);
1✔
182
}
183

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

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