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

source-academy / js-slang / 24834367427

23 Apr 2026 12:09PM UTC coverage: 78.541% (+0.2%) from 78.391%
24834367427

Pull #1893

github

web-flow
Merge ab101147d into 715603479
Pull Request #1893: Error Handling and Stringify Changes

3126 of 4197 branches covered (74.48%)

Branch coverage included in aggregate %.

801 of 975 new or added lines in 76 files covered. (82.15%)

20 existing lines in 11 files now uncovered.

7056 of 8767 relevant lines covered (80.48%)

173930.4 hits per line

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

90.48
/src/createContext.ts
1
// Variable determining chapter of Source is contained in this file.
2

3
import { GLOBAL, JSSLANG_PROPERTIES } from './constants';
4
import { call_with_current_continuation } from './cse-machine/continuations';
5
import Heap from './cse-machine/heap';
6
import { GeneralRuntimeError } from './errors/base';
7
import { InvalidParameterTypeError } from './errors/rttcErrors';
8
import { Chapter, Variant, type LanguageOptions } from './langs';
9
import { createEmptyModuleContexts } from './modules/utils';
10
import * as list from './stdlib/list';
11
import { list_to_vector } from './stdlib/list';
12
import { listPrelude } from './stdlib/list.prelude';
13
import { localImportPrelude } from './stdlib/localImport.prelude';
14
import * as misc from './stdlib/misc';
15
import * as parser from './stdlib/parser';
16
import * as stream from './stdlib/stream';
17
import { streamPrelude } from './stdlib/stream.prelude';
18
import { createTypeEnvironment, tForAll, tVar } from './typeChecker/utils';
19
import type { Context, CustomBuiltIns, Environment, NativeStorage, Value } from './types';
20
import * as operators from './utils/operators';
21
import { stringify } from './utils/stringify';
22

23
export class EnvTree {
24
  private _root: EnvTreeNode | null = null;
3,996✔
25
  private map = new Map<Environment, EnvTreeNode>();
3,996✔
26

27
  get root(): EnvTreeNode | null {
28
    return this._root;
3✔
29
  }
30

31
  public insert(environment: Environment): void {
32
    const tailEnvironment = environment.tail;
458,182✔
33
    if (tailEnvironment === null) {
458,182✔
34
      if (this._root === null) {
2,977!
35
        this._root = new EnvTreeNode(environment, null);
2,977✔
36
        this.map.set(environment, this._root);
2,977✔
37
      }
38
    } else {
39
      const parentNode = this.map.get(tailEnvironment);
455,205✔
40
      if (parentNode) {
455,205!
41
        const childNode = new EnvTreeNode(environment, parentNode);
455,205✔
42
        parentNode.addChild(childNode);
455,205✔
43
        this.map.set(environment, childNode);
455,205✔
44
      }
45
    }
46
  }
47

48
  public getTreeNode(environment: Environment): EnvTreeNode | undefined {
49
    return this.map.get(environment);
5✔
50
  }
51
}
52

53
export class EnvTreeNode {
54
  private _children: EnvTreeNode[] = [];
458,183✔
55

56
  constructor(
57
    readonly environment: Environment,
458,183✔
58
    public parent: EnvTreeNode | null,
458,183✔
59
  ) {}
60

61
  get children(): EnvTreeNode[] {
62
    return this._children;
3✔
63
  }
64

65
  public resetChildren(newChildren: EnvTreeNode[]): void {
66
    this.clearChildren();
1✔
67
    this.addChildren(newChildren);
1✔
68
    newChildren.forEach(c => (c.parent = this));
3✔
69
  }
70

71
  private clearChildren(): void {
72
    this._children = [];
1✔
73
  }
74

75
  private addChildren(newChildren: EnvTreeNode[]): void {
76
    this._children.push(...newChildren);
1✔
77
  }
78

79
  public addChild(newChild: EnvTreeNode): EnvTreeNode {
80
    this._children.push(newChild);
455,206✔
81
    return newChild;
455,206✔
82
  }
83
}
84

85
const createEmptyRuntime = () => ({
2,979✔
86
  break: false,
87
  debuggerOn: true,
88
  isRunning: false,
89
  environmentTree: new EnvTree(),
90
  environments: [],
91
  value: undefined,
92
  nodes: [],
93
  control: null,
94
  stash: null,
95
  objectCount: 0,
96
  envSteps: -1,
97
  envStepsTotal: 0,
98
  breakpointSteps: [],
99
  changepointSteps: [],
100
});
101

102
const createEmptyDebugger = () => ({
2,979✔
103
  observers: { callbacks: Array<() => void>() },
104
  status: false,
105
  state: {
106
    it: (function* (): any {
107
      return;
2,979✔
108
    })(),
109
  },
110
});
111

112
export const createGlobalEnvironment = (): Environment => ({
2,977✔
113
  tail: null,
114
  name: 'global',
115
  head: {},
116
  heap: new Heap(),
117
  id: '-1',
118
});
119

120
const createNativeStorage = (): NativeStorage => ({
2,979✔
121
  builtins: new Map(),
122
  previousProgramsIdentifiers: new Set(),
123
  operators: new Map(Object.entries(operators)),
124
  maxExecTime: JSSLANG_PROPERTIES.maxExecTime,
125
  evaller: null,
126
  loadedModules: {},
127
  loadedModuleTypes: {},
128
});
129

130
export const createEmptyContext = <T>(
63✔
131
  chapter: Chapter,
132
  variant: Variant = Variant.DEFAULT,
2,979✔
133
  languageOptions: LanguageOptions = {},
2,979✔
134
  externalSymbols: string[],
135
  externalContext?: T,
136
): Context<T> => {
137
  return {
2,979✔
138
    chapter,
139
    externalSymbols,
140
    errors: [],
141
    externalContext,
142
    runtime: createEmptyRuntime(),
143
    numberOfOuterEnvironments: 1,
144
    prelude: null,
145
    pendingStreamFnStack: [],
146
    streamLineage: new Map<string, string[]>(),
147
    debugger: createEmptyDebugger(),
148
    nativeStorage: createNativeStorage(),
149
    executionMethod: 'auto',
150
    variant,
151
    languageOptions,
152
    moduleContexts: createEmptyModuleContexts(),
153
    unTypecheckedCode: [],
154
    typeEnvironment: createTypeEnvironment(chapter),
155
    previousPrograms: [],
156
    shouldIncreaseEvaluationTimeout: false,
157
  };
158
};
159

160
export const ensureGlobalEnvironmentExist = (context: Context) => {
63✔
161
  if (!context.runtime) {
5,950!
162
    context.runtime = createEmptyRuntime();
×
163
  }
164
  if (!context.runtime.environments) {
5,950!
165
    context.runtime.environments = [];
×
166
  }
167
  if (!context.runtime.environmentTree) {
5,950!
168
    context.runtime.environmentTree = new EnvTree();
×
169
  }
170
  if (context.runtime.environments.length === 0) {
5,950✔
171
    const globalEnvironment = createGlobalEnvironment();
2,975✔
172
    context.runtime.environments.push(globalEnvironment);
2,975✔
173
    context.runtime.environmentTree.insert(globalEnvironment);
2,975✔
174
  }
175
};
176

177
export function defineSymbol(context: Context, name: string, value: Value) {
178
  const globalEnvironment = context.runtime.environments[0];
204,086✔
179

180
  if (!(name in globalEnvironment.head)) {
204,086✔
181
    Object.defineProperty(globalEnvironment.head, name, {
204,070✔
182
      value,
183
      writable: false,
184
      enumerable: true,
185
    });
186
  }
187

188
  context.nativeStorage.builtins.set(name, value);
204,086✔
189
  const typeEnv = context.typeEnvironment[0];
204,086✔
190
  // if the global type env doesn't already have the imported symbol,
191
  // we set it to a type var T that can typecheck with anything.
192
  if (!typeEnv.declKindMap.has(name)) {
204,086✔
193
    typeEnv.typeMap.set(name, tForAll(tVar('T1')));
19,501✔
194
    typeEnv.declKindMap.set(name, 'const');
19,501✔
195
  }
196
}
197

198
// Defines a builtin in the given context
199
// If the builtin is a function, wrap it such that its toString hides the implementation
200
export function defineBuiltin(
201
  context: Context,
202
  name: string,
203
  value: Value,
204
  minArgsNeeded?: number,
205
) {
206
  function extractName(name: string): string {
207
    return name.split('(')[0].trim();
171,636✔
208
  }
209

210
  function extractParameters(name: string): string[] {
211
    // if the function has no () in its name, it has no parameters
212
    if (!name.includes('(')) {
171,636✔
213
      return [];
20✔
214
    }
215
    return name
171,616✔
216
      .split('(')[1]
217
      .split(')')[0]
218
      .split(',')
219
      .map(s => s.trim());
211,346✔
220
  }
221

222
  if (typeof value === 'function') {
204,086✔
223
    const funName = extractName(name);
171,636✔
224
    const funParameters = extractParameters(name);
171,636✔
225

226
    const wrapped = operators.wrap(
171,636✔
227
      value,
228
      minArgsNeeded,
229
      `function ${name} {\n\t[implementation hidden]\n}`,
230
      null,
231
      funName,
232
    );
233

234
    // value.toString = () => repr;
235
    // value.minArgsNeeded = minArgsNeeded;
236
    value.funName = funName;
171,636✔
237
    value.funParameters = funParameters;
171,636✔
238

239
    defineSymbol(context, funName, wrapped);
171,636✔
240
  } else {
241
    defineSymbol(context, name, value);
32,450✔
242
  }
243
}
244

245
export const importExternalSymbols = (context: Context, externalSymbols: string[]) => {
63✔
246
  ensureGlobalEnvironmentExist(context);
2,975✔
247

248
  externalSymbols.forEach(symbol => {
2,975✔
249
    defineSymbol(context, symbol, GLOBAL[symbol as keyof typeof GLOBAL]);
×
250
  });
251
};
252

253
/**
254
 * Imports builtins from standard and external libraries.
255
 */
256
export function importBuiltins(context: Context, externalBuiltIns: Partial<CustomBuiltIns> = {}) {
2,975✔
257
  ensureGlobalEnvironmentExist(context);
2,975✔
258
  const rawDisplay = (v: Value, ...s: string[]) =>
2,975✔
259
    (externalBuiltIns.rawDisplay ?? defaultBuiltIns.rawDisplay)(v, s[0], context.externalContext);
41✔
260

261
  const display = (v: Value, ...s: string[]) => {
2,975✔
262
    if (s.length === 1 && s[0] !== undefined && typeof s[0] !== 'string') {
40✔
263
      throw new InvalidParameterTypeError('string', s[0], display.name, 'second argument');
1✔
264
    }
265

266
    rawDisplay(stringify(v), s[0]);
39✔
267
    return v;
39✔
268
  };
269

270
  const display_list = (v: Value, ...s: string[]) => {
2,975✔
271
    if (s.length === 1 && s[0] !== undefined && typeof s[0] !== 'string') {
19✔
272
      throw new InvalidParameterTypeError('string', s[0], display_list.name, 'second argument');
1✔
273
    }
274
    return list.rawDisplayList(display, v, s[0]);
18✔
275
  };
276

277
  const prompt = (v: Value) => {
2,975✔
278
    const start = Date.now();
×
NEW
279
    const promptResult = (externalBuiltIns.prompt ?? defaultBuiltIns.prompt)(
×
280
      v,
281
      '',
282
      context.externalContext,
283
    );
284
    context.nativeStorage.maxExecTime += Date.now() - start;
×
285
    return promptResult;
×
286
  };
287
  const alert = (v: Value) => {
2,975✔
288
    const start = Date.now();
2✔
289
    (externalBuiltIns.alert ?? defaultBuiltIns.alert)(v, '', context.externalContext);
2!
290
    context.nativeStorage.maxExecTime += Date.now() - start;
2✔
291
  };
292
  const visualise_list = (...v: Value[]) => {
2,975✔
293
    (externalBuiltIns.visualiseList ?? defaultBuiltIns.visualiseList)(v, context.externalContext);
3!
294
    return v[0];
3✔
295
  };
296

297
  if (context.chapter >= 1) {
2,975✔
298
    defineBuiltin(context, 'get_time()', misc.get_time);
2,950✔
299
    defineBuiltin(context, 'display(val, prepend = undefined)', display, 1);
2,950✔
300
    defineBuiltin(context, 'raw_display(str, prepend = undefined)', rawDisplay, 1);
2,950✔
301
    defineBuiltin(context, 'stringify(val, indent = 2, maxLineLength = 80)', stringify, 1);
2,950✔
302
    defineBuiltin(context, 'error(str, prepend = undefined)', misc.error_message, 1);
2,950✔
303
    defineBuiltin(context, 'prompt(str)', prompt);
2,950✔
304
    defineBuiltin(context, 'is_number(val)', misc.is_number);
2,950✔
305
    defineBuiltin(context, 'is_string(val)', misc.is_string);
2,950✔
306
    defineBuiltin(context, 'is_function(val)', misc.is_function);
2,950✔
307
    defineBuiltin(context, 'is_boolean(val)', misc.is_boolean);
2,950✔
308
    defineBuiltin(context, 'is_undefined(val)', misc.is_undefined);
2,950✔
309
    defineBuiltin(context, 'parse_int(str, radix)', misc.parse_int);
2,950✔
310
    defineBuiltin(context, 'char_at(str, index)', misc.char_at);
2,950✔
311
    defineBuiltin(context, 'arity(f)', misc.arity);
2,950✔
312
    defineBuiltin(context, 'undefined', undefined);
2,950✔
313
    defineBuiltin(context, 'NaN', NaN);
2,950✔
314
    defineBuiltin(context, 'Infinity', Infinity);
2,950✔
315
    // Define all Math libraries
316
    const mathLibraryNames = Object.getOwnPropertyNames(Math);
2,950✔
317
    // Short param names for stringified version of math functions
318
    const parameterNames = [...'abcdefghijklmnopqrstuvwxyz'];
2,950✔
319
    for (const name of mathLibraryNames) {
2,950✔
320
      const value = Math[name as keyof typeof Math];
126,850✔
321
      if (typeof value === 'function') {
126,850✔
322
        let paramString: string;
323
        let minArgsNeeded = undefined;
103,250✔
324
        if (name === 'max' || name === 'min') {
103,250✔
325
          paramString = '...values';
5,900✔
326
          minArgsNeeded = 0;
5,900✔
327
        } else {
328
          paramString = parameterNames.slice(0, value.length).join(', ');
97,350✔
329
        }
330
        defineBuiltin(context, `math_${name}(${paramString})`, value, minArgsNeeded);
103,250✔
331
      } else {
332
        defineBuiltin(context, `math_${name}`, value);
23,600✔
333
      }
334
    }
335
  }
336

337
  if (context.chapter >= 2) {
2,975✔
338
    // List library
339
    defineBuiltin(context, 'pair(left, right)', list.pair);
1,594✔
340
    defineBuiltin(context, 'is_pair(val)', list.is_pair);
1,594✔
341
    defineBuiltin(context, 'head(xs)', list.head);
1,594✔
342
    defineBuiltin(context, 'tail(xs)', list.tail);
1,594✔
343
    defineBuiltin(context, 'is_null(val)', list.is_null);
1,594✔
344
    defineBuiltin(context, 'list(...values)', list.list, 0);
1,594✔
345
    defineBuiltin(context, 'draw_data(...xs)', visualise_list, 1);
1,594✔
346
    defineBuiltin(context, 'display_list(val, prepend = undefined)', display_list, 0);
1,594✔
347
    defineBuiltin(context, 'is_list(val)', list.is_list);
1,594✔
348
  }
349

350
  if (context.chapter >= 3) {
2,975✔
351
    defineBuiltin(context, 'set_head(xs, val)', list.set_head);
1,319✔
352
    defineBuiltin(context, 'set_tail(xs, val)', list.set_tail);
1,319✔
353
    defineBuiltin(context, 'array_length(arr)', misc.array_length);
1,319✔
354
    defineBuiltin(context, 'is_array(val)', misc.is_array);
1,319✔
355

356
    // Stream library
357
    defineBuiltin(context, 'stream(...values)', stream.stream, 0);
1,319✔
358
  }
359

360
  if (context.chapter >= 4) {
2,975✔
361
    defineBuiltin(context, 'parse(program_string)', (str: string) =>
1,146✔
362
      parser.parse(str, createContext(context.chapter)),
71✔
363
    );
364
    defineBuiltin(context, 'tokenize(program_string)', (str: string) =>
1,146✔
365
      parser.tokenize(str, createContext(context.chapter), true),
3✔
366
    );
367
    defineBuiltin(
1,146✔
368
      context,
369
      'apply_in_underlying_javascript(fun, args)',
370
      (fun: Function, args: Value) => fun.apply(fun, list_to_vector(args)),
5✔
371
    );
372

373
    // Continuations for explicit-control variant
374
    if (context.chapter >= 4) {
1,146!
375
      defineBuiltin(
1,146✔
376
        context,
377
        'call_cc(f)',
378
        context.variant === Variant.EXPLICIT_CONTROL
24!
379
          ? call_with_current_continuation
380
          : (_f: any) => {
381
              throw new Error('call_cc is only available in Explicit-Control variant');
×
382
            },
383
      );
384
    }
385
  }
386

387
  if (context.chapter === Chapter.LIBRARY_PARSER) {
2,975✔
388
    defineBuiltin(context, 'is_object(val)', misc.is_object);
308✔
389
    defineBuiltin(context, 'is_NaN(val)', misc.is_NaN);
308✔
390
    defineBuiltin(context, 'has_own_property(obj, prop)', misc.has_own_property);
308✔
391
    defineBuiltin(context, 'alert(val)', alert);
308✔
392
    defineBuiltin(context, 'timed(fun)', (f: Function) =>
308✔
NEW
393
      misc.timed(
×
394
        context,
395
        f,
396
        context.externalContext,
397
        externalBuiltIns.rawDisplay ?? defaultBuiltIns.rawDisplay,
×
398
      ),
399
    );
400
  }
401
}
402

403
function importPrelude(context: Context) {
404
  let prelude = '';
2,975✔
405
  if (context.chapter >= 2) {
2,975✔
406
    prelude += listPrelude;
1,594✔
407
    prelude += localImportPrelude;
1,594✔
408
  }
409
  if (context.chapter >= 3) {
2,975✔
410
    prelude += streamPrelude;
1,319✔
411
  }
412

413
  if (prelude !== '') {
2,975✔
414
    context.prelude = prelude;
1,594✔
415
  }
416
}
417

418
export const defaultBuiltIns: CustomBuiltIns = {
63✔
419
  rawDisplay: misc.rawDisplay,
420
  // See issue #5
421
  prompt: misc.rawDisplay,
422
  // See issue #11
423
  alert: misc.rawDisplay,
424
  visualiseList: (_v: Value) => {
NEW
425
    throw new GeneralRuntimeError('List visualizer is not enabled');
×
426
  },
427
};
428

429
const createContext = <T>(
63✔
430
  chapter: Chapter = Chapter.SOURCE_1,
3,126✔
431
  variant: Variant = Variant.DEFAULT,
3,126✔
432
  languageOptions: LanguageOptions = {},
3,126✔
433
  externalSymbols: string[] = [],
3,126✔
434
  externalContext?: T,
435
  externalBuiltIns: Partial<CustomBuiltIns> = {},
3,126✔
436
): Context => {
437
  if (chapter === Chapter.FULL_JS || chapter === Chapter.FULL_TS) {
3,126✔
438
    // fullJS will include all builtins and preludes of source 4
439
    return {
151✔
440
      ...createContext(
441
        Chapter.SOURCE_4,
442
        variant,
443
        languageOptions,
444
        externalSymbols,
445
        externalContext,
446
        externalBuiltIns,
447
      ),
448
      chapter,
449
    } as Context;
450
  }
451

452
  const context = createEmptyContext(
2,975✔
453
    chapter,
454
    variant,
455
    languageOptions,
456
    externalSymbols,
457
    externalContext,
458
  );
459

460
  importBuiltins(context, externalBuiltIns);
2,975✔
461
  importPrelude(context);
2,975✔
462
  importExternalSymbols(context, externalSymbols);
2,975✔
463

464
  return context;
2,975✔
465
};
466

467
export default createContext;
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