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

source-academy / js-slang / 5936042370

22 Aug 2023 07:58AM UTC coverage: 83.014% (+0.1%) from 82.893%
5936042370

Pull #1471

github

web-flow
Merge 6d88e2972 into 1d991166b
Pull Request #1471: Use Asynchronous Code for Loading Modules

3618 of 4779 branches covered (75.71%)

Branch coverage included in aggregate %.

269 of 269 new or added lines in 23 files covered. (100.0%)

10838 of 12635 relevant lines covered (85.78%)

106101.79 hits per line

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

89.9
/src/interpreter/interpreter.ts
1
/* tslint:disable:max-classes-per-file */
2
import * as es from 'estree'
3
import { isEmpty, uniqueId } from 'lodash'
67✔
4

5
import { UNKNOWN_LOCATION } from '../constants'
67✔
6
import { LazyBuiltIn } from '../createContext'
67✔
7
import * as errors from '../errors/errors'
67✔
8
import { RuntimeSourceError } from '../errors/runtimeSourceError'
67✔
9
import { UndefinedImportError } from '../modules/errors'
67✔
10
import { initModuleContext, loadModuleBundle } from '../modules/moduleLoader'
67✔
11
import { ModuleFunctions } from '../modules/moduleTypes'
12
import { checkEditorBreakpoints } from '../stdlib/inspector'
67✔
13
import { Context, ContiguousArrayElements, Environment, Frame, Value, Variant } from '../types'
67✔
14
import assert from '../utils/assert'
67✔
15
import * as create from '../utils/astCreator'
67✔
16
import { conditionalExpression, literal, primitive } from '../utils/astCreator'
67✔
17
import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators'
67✔
18
import * as rttc from '../utils/rttc'
67✔
19
import Closure from './closure'
67✔
20

21
class BreakValue {}
22

23
class ContinueValue {}
24

25
class ReturnValue {
26
  constructor(public value: Value) {}
8,779✔
27
}
28

29
class TailCallReturnValue {
30
  constructor(public callee: Closure, public args: Value[], public node: es.CallExpression) {}
47,419✔
31
}
32

33
class Thunk {
34
  public value: Value
35
  public isMemoized: boolean
36
  constructor(public exp: es.Node, public env: Environment) {
11,725✔
37
    this.isMemoized = false
11,725✔
38
    this.value = null
11,725✔
39
  }
40
}
41

42
const delayIt = (exp: es.Node, env: Environment): Thunk => new Thunk(exp, env)
11,725✔
43

44
function* forceIt(val: any, context: Context): Value {
45
  if (val instanceof Thunk) {
610,219✔
46
    if (val.isMemoized) return val.value
16,507✔
47

48
    pushEnvironment(context, val.env)
10,667✔
49
    const evalRes = yield* actualValue(val.exp, context)
10,667✔
50
    popEnvironment(context)
10,666✔
51
    val.value = evalRes
10,666✔
52
    val.isMemoized = true
10,666✔
53
    return evalRes
10,666✔
54
  } else return val
593,712✔
55
}
56

57
export function* actualValue(exp: es.Node, context: Context): Value {
67✔
58
  const evalResult = yield* evaluate(exp, context)
603,415✔
59
  const forced = yield* forceIt(evalResult, context)
597,546✔
60
  return forced
597,545✔
61
}
62

63
const createEnvironment = (
67✔
64
  closure: Closure,
65
  args: Value[],
66
  callExpression?: es.CallExpression
67
): Environment => {
68
  const environment: Environment = {
62,066✔
69
    name: closure.functionName, // TODO: Change this
70
    tail: closure.environment,
71
    head: {},
72
    id: uniqueId()
73
  }
74
  if (callExpression) {
62,066✔
75
    environment.callExpression = {
62,066✔
76
      ...callExpression,
77
      arguments: args.map(primitive)
78
    }
79
  }
80
  closure.node.params.forEach((param, index) => {
62,066✔
81
    if (param.type === 'RestElement') {
125,896✔
82
      environment.head[(param.argument as es.Identifier).name] = args.slice(index)
3✔
83
    } else {
84
      environment.head[(param as es.Identifier).name] = args[index]
125,893✔
85
    }
86
  })
87
  return environment
62,066✔
88
}
89

90
export const createBlockEnvironment = (
67✔
91
  context: Context,
92
  name = 'blockEnvironment',
×
93
  head: Frame = {}
93,829✔
94
): Environment => {
95
  return {
93,837✔
96
    name,
97
    tail: currentEnvironment(context),
98
    head,
99
    id: uniqueId()
100
  }
101
}
102

103
const handleRuntimeError = (context: Context, error: RuntimeSourceError): never => {
67✔
104
  context.errors.push(error)
127✔
105
  context.runtime.environments = context.runtime.environments.slice(
127✔
106
    -context.numberOfOuterEnvironments
107
  )
108
  throw error
127✔
109
}
110

111
const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol('Used to implement block scope')
67✔
112

113
function declareIdentifier(context: Context, name: string, node: es.Node) {
114
  const environment = currentEnvironment(context)
15,988✔
115
  if (environment.head.hasOwnProperty(name)) {
15,988!
116
    const descriptors = Object.getOwnPropertyDescriptors(environment.head)
×
117

118
    return handleRuntimeError(
×
119
      context,
120
      new errors.VariableRedeclaration(node, name, descriptors[name].writable)
121
    )
122
  }
123
  environment.head[name] = DECLARED_BUT_NOT_YET_ASSIGNED
15,988✔
124
  return environment
15,988✔
125
}
126

127
function declareVariables(context: Context, node: es.VariableDeclaration) {
128
  for (const declaration of node.declarations) {
1,830✔
129
    declareIdentifier(context, (declaration.id as es.Identifier).name, node)
1,830✔
130
  }
131
}
132

133
function declareFunctionsAndVariables(context: Context, node: es.BlockStatement) {
134
  for (const statement of node.body) {
93,650✔
135
    switch (statement.type) {
109,262✔
136
      case 'VariableDeclaration':
15,815✔
137
        declareVariables(context, statement)
1,805✔
138
        break
1,805✔
139
      case 'FunctionDeclaration':
140
        if (statement.id === null) {
14,010!
141
          throw new Error(
×
142
            'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.'
143
          )
144
        }
145
        declareIdentifier(context, statement.id.name, statement)
14,010✔
146
        break
14,010✔
147
    }
148
  }
149
}
150

151
function defineVariable(context: Context, name: string, value: Value, constant = false) {
×
152
  const environment = currentEnvironment(context)
15,976✔
153

154
  if (environment.head[name] !== DECLARED_BUT_NOT_YET_ASSIGNED) {
15,976!
155
    return handleRuntimeError(
×
156
      context,
157
      new errors.VariableRedeclaration(context.runtime.nodes[0]!, name, !constant)
158
    )
159
  }
160

161
  Object.defineProperty(environment.head, name, {
15,976✔
162
    value,
163
    writable: !constant,
164
    enumerable: true
165
  })
166

167
  return environment
15,976✔
168
}
169

170
function* visit(context: Context, node: es.Node) {
171
  checkEditorBreakpoints(context, node)
764,633✔
172
  context.runtime.nodes.unshift(node)
764,633✔
173
  yield context
764,633✔
174
}
175

176
function* leave(context: Context) {
177
  context.runtime.break = false
748,393✔
178
  context.runtime.nodes.shift()
748,393✔
179
  yield context
748,393✔
180
}
181

182
const currentEnvironment = (context: Context) => context.runtime.environments[0]
478,416✔
183
const replaceEnvironment = (context: Context, environment: Environment) => {
67✔
184
  context.runtime.environments[0] = environment
46,655✔
185
  context.runtime.environmentTree.insert(environment)
46,655✔
186
}
187
const popEnvironment = (context: Context) => context.runtime.environments.shift()
106,799✔
188
export const pushEnvironment = (context: Context, environment: Environment) => {
67✔
189
  context.runtime.environments.unshift(environment)
119,914✔
190
  context.runtime.environmentTree.insert(environment)
119,914✔
191
}
192

193
const getVariable = (context: Context, name: string) => {
67✔
194
  let environment: Environment | null = currentEnvironment(context)
260,401✔
195
  while (environment) {
260,401✔
196
    if (environment.head.hasOwnProperty(name)) {
701,671✔
197
      if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) {
260,393✔
198
        return handleRuntimeError(
3✔
199
          context,
200
          new errors.UnassignedVariable(name, context.runtime.nodes[0])
201
        )
202
      } else {
203
        return environment.head[name]
260,390✔
204
      }
205
    } else {
206
      environment = environment.tail
441,278✔
207
    }
208
  }
209
  return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0]))
8✔
210
}
211

212
const setVariable = (context: Context, name: string, value: any) => {
67✔
213
  let environment: Environment | null = currentEnvironment(context)
209✔
214
  while (environment) {
209✔
215
    if (environment.head.hasOwnProperty(name)) {
351✔
216
      if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) {
209✔
217
        break
1✔
218
      }
219
      const descriptors = Object.getOwnPropertyDescriptors(environment.head)
208✔
220
      if (descriptors[name].writable) {
208✔
221
        environment.head[name] = value
204✔
222
        return undefined
204✔
223
      }
224
      return handleRuntimeError(
4✔
225
        context,
226
        new errors.ConstAssignment(context.runtime.nodes[0]!, name)
227
      )
228
    } else {
229
      environment = environment.tail
142✔
230
    }
231
  }
232
  return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0]))
1✔
233
}
234

235
const checkNumberOfArguments = (
67✔
236
  context: Context,
237
  callee: Closure | Value,
238
  args: Value[],
239
  exp: es.CallExpression
240
) => {
241
  if (callee instanceof Closure) {
67,591✔
242
    const params = callee.node.params
62,081✔
243
    const hasVarArgs = params[params.length - 1]?.type === 'RestElement'
62,081✔
244
    if (hasVarArgs ? params.length - 1 > args.length : params.length !== args.length) {
62,081✔
245
      return handleRuntimeError(
15✔
246
        context,
247
        new errors.InvalidNumberOfArguments(
248
          exp,
249
          hasVarArgs ? params.length - 1 : params.length,
15✔
250
          args.length,
251
          hasVarArgs
252
        )
253
      )
254
    }
255
  } else {
256
    const hasVarArgs = callee.minArgsNeeded != undefined
5,510✔
257
    if (hasVarArgs ? callee.minArgsNeeded > args.length : callee.length !== args.length) {
5,510✔
258
      return handleRuntimeError(
6✔
259
        context,
260
        new errors.InvalidNumberOfArguments(
261
          exp,
262
          hasVarArgs ? callee.minArgsNeeded : callee.length,
6✔
263
          args.length,
264
          hasVarArgs
265
        )
266
      )
267
    }
268
  }
269
  return undefined
67,570✔
270
}
271

272
function* getArgs(context: Context, call: es.CallExpression) {
273
  const args = []
73,477✔
274
  for (const arg of call.arguments) {
73,477✔
275
    if (context.variant === Variant.LAZY) {
140,727✔
276
      args.push(delayIt(arg, currentEnvironment(context)))
11,725✔
277
    } else if (arg.type === 'SpreadElement') {
129,002✔
278
      args.push(...(yield* actualValue(arg.argument, context)))
4✔
279
    } else {
280
      args.push(yield* actualValue(arg, context))
128,998✔
281
    }
282
  }
283
  return args
72,326✔
284
}
285

286
function transformLogicalExpression(node: es.LogicalExpression): es.ConditionalExpression {
287
  if (node.operator === '&&') {
10,472✔
288
    return conditionalExpression(node.left, node.right, literal(false), node.loc)
466✔
289
  } else {
290
    return conditionalExpression(node.left, literal(true), node.right, node.loc)
10,006✔
291
  }
292
}
293

294
function* reduceIf(
295
  node: es.IfStatement | es.ConditionalExpression,
296
  context: Context
297
): IterableIterator<null | es.Node> {
298
  const test = yield* actualValue(node.test, context)
72,350✔
299

300
  const error = rttc.checkIfStatement(node, test, context.chapter)
72,340✔
301
  if (error) {
72,340✔
302
    return handleRuntimeError(context, error)
1✔
303
  }
304

305
  return test ? node.consequent : node.alternate
72,339✔
306
}
307

308
export type Evaluator<T extends es.Node> = (node: T, context: Context) => IterableIterator<Value>
309

310
function* evaluateBlockStatement(context: Context, node: es.BlockStatement) {
311
  declareFunctionsAndVariables(context, node)
93,650✔
312
  let result
313
  for (const statement of node.body) {
93,650✔
314
    result = yield* evaluate(statement, context)
109,205✔
315
    if (
102,859✔
316
      result instanceof ReturnValue ||
225,176✔
317
      result instanceof TailCallReturnValue ||
318
      result instanceof BreakValue ||
319
      result instanceof ContinueValue
320
    ) {
321
      break
86,239✔
322
    }
323
  }
324
  return result
87,304✔
325
}
326

327
/**
328
 * WARNING: Do not use object literal shorthands, e.g.
329
 *   {
330
 *     *Literal(node: es.Literal, ...) {...},
331
 *     *ThisExpression(node: es.ThisExpression, ..._ {...},
332
 *     ...
333
 *   }
334
 * They do not minify well, raising uncaught syntax errors in production.
335
 * See: https://github.com/webpack/webpack/issues/7566
336
 */
337
// tslint:disable:object-literal-shorthand
338
// prettier-ignore
339
export const evaluators: { [nodeType: string]: Evaluator<es.Node> } = {
67✔
340
  /** Simple Values */
341
  Literal: function*(node: es.Literal, _context: Context) {
342
    return node.value
173,535✔
343
  },
344

345
  TemplateLiteral: function*(node: es.TemplateLiteral) {
346
    // Expressions like `${1}` are not allowed, so no processing needed
347
    return node.quasis[0].value.cooked
1✔
348
  },
349

350
  ThisExpression: function*(node: es.ThisExpression, context: Context) {
351
    return currentEnvironment(context).thisContext
2✔
352
  },
353

354
  ArrayExpression: function*(node: es.ArrayExpression, context: Context) {
355
    const res = []
46✔
356
    for (const n of node.elements as ContiguousArrayElements) {
46✔
357
      res.push(yield* evaluate(n, context))
91✔
358
    }
359
    return res
46✔
360
  },
361

362
  DebuggerStatement: function*(node: es.DebuggerStatement, context: Context) {
363
    context.runtime.break = true
×
364
    yield
×
365
  },
366

367
  FunctionExpression: function*(node: es.FunctionExpression, context: Context) {
368
    return new Closure(node, currentEnvironment(context), context)
×
369
  },
370

371
  ArrowFunctionExpression: function*(node: es.ArrowFunctionExpression, context: Context) {
372
    return Closure.makeFromArrowFunction(node, currentEnvironment(context), context)
869✔
373
  },
374

375
  Identifier: function*(node: es.Identifier, context: Context) {
376
    return getVariable(context, node.name)
260,401✔
377
  },
378

379
  CallExpression: function*(node: es.CallExpression, context: Context) {
380
    const callee = yield* actualValue(node.callee, context)
24,928✔
381
    const args = yield* getArgs(context, node)
24,919✔
382
    let thisContext
383
    if (node.callee.type === 'MemberExpression') {
24,907✔
384
      thisContext = yield* actualValue(node.callee.object, context)
2✔
385
    }
386
    const result = yield* apply(context, callee, args, node, thisContext)
24,907✔
387
    return result
18,953✔
388
  },
389

390
  NewExpression: function*(node: es.NewExpression, context: Context) {
391
    const callee = yield* evaluate(node.callee, context)
2✔
392
    const args = []
2✔
393
    for (const arg of node.arguments) {
2✔
394
      args.push(yield* evaluate(arg, context))
×
395
    }
396
    const obj: Value = {}
2✔
397
    if (callee instanceof Closure) {
2!
398
      obj.__proto__ = callee.fun.prototype
2✔
399
      callee.fun.apply(obj, args)
2✔
400
    } else {
401
      obj.__proto__ = callee.prototype
×
402
      callee.apply(obj, args)
×
403
    }
404
    return obj
×
405
  },
406

407
  UnaryExpression: function*(node: es.UnaryExpression, context: Context) {
408
    const value = yield* actualValue(node.argument, context)
88✔
409

410
    const error = rttc.checkUnaryExpression(node, node.operator, value, context.chapter)
87✔
411
    if (error) {
87!
412
      return handleRuntimeError(context, error)
×
413
    }
414
    return evaluateUnaryExpression(node.operator, value)
87✔
415
  },
416

417
  BinaryExpression: function*(node: es.BinaryExpression, context: Context) {
418
    const left = yield* actualValue(node.left, context)
158,356✔
419
    const right = yield* actualValue(node.right, context)
157,333✔
420
    const error = rttc.checkBinaryExpression(node, node.operator, context.chapter, left, right)
154,958✔
421
    if (error) {
154,958✔
422
      return handleRuntimeError(context, error)
14✔
423
    }
424
    return evaluateBinaryExpression(node.operator, left, right)
154,944✔
425
  },
426

427
  ConditionalExpression: function*(node: es.ConditionalExpression, context: Context) {
428
    return yield* this.IfStatement(node, context)
5,181✔
429
  },
430

431
  LogicalExpression: function*(node: es.LogicalExpression, context: Context) {
432
    return yield* this.ConditionalExpression(transformLogicalExpression(node), context)
5,177✔
433
  },
434

435
  VariableDeclaration: function*(node: es.VariableDeclaration, context: Context) {
436
    const declaration = node.declarations[0]
1,827✔
437
    const constant = node.kind === 'const'
1,827✔
438
    const id = declaration.id as es.Identifier
1,827✔
439
    const value = yield* evaluate(declaration.init!, context)
1,827✔
440
    defineVariable(context, id.name, value, constant)
1,819✔
441
    return undefined
1,819✔
442
  },
443

444
  ContinueStatement: function*(_node: es.ContinueStatement, _context: Context) {
445
    return new ContinueValue()
1✔
446
  },
447

448
  BreakStatement: function*(_node: es.BreakStatement, _context: Context) {
449
    return new BreakValue()
2✔
450
  },
451

452
  ForStatement: function*(node: es.ForStatement, context: Context) {
453
    // Create a new block scope for the loop variables
454
    const loopEnvironment = createBlockEnvironment(context, 'forLoopEnvironment')
26✔
455
    pushEnvironment(context, loopEnvironment)
26✔
456

457
    const initNode = node.init!
26✔
458
    const testNode = node.test!
26✔
459
    const updateNode = node.update!
26✔
460
    if (initNode.type === 'VariableDeclaration') {
26✔
461
      declareVariables(context, initNode)
25✔
462
    }
463
    yield* actualValue(initNode, context)
26✔
464

465
    let value
466
    while (yield* actualValue(testNode, context)) {
26✔
467
      // create block context and shallow copy loop environment head
468
      // see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation
469
      // and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/
470
      // We copy this as a const to avoid ES6 funkiness when mutating loop vars
471
      // https://github.com/source-academy/js-slang/issues/65#issuecomment-425618227
472
      const environment = createBlockEnvironment(context, 'forBlockEnvironment')
152✔
473
      pushEnvironment(context, environment)
152✔
474
      for (const name in loopEnvironment.head) {
152✔
475
        if (loopEnvironment.head.hasOwnProperty(name)) {
148✔
476
          declareIdentifier(context, name, node)
148✔
477
          defineVariable(context, name, loopEnvironment.head[name], true)
148✔
478
        }
479
      }
480

481
      value = yield* actualValue(node.body, context)
152✔
482

483
      // Remove block context
484
      popEnvironment(context)
152✔
485
      if (value instanceof ContinueValue) {
152✔
486
        value = undefined
1✔
487
      }
488
      if (value instanceof BreakValue) {
152✔
489
        value = undefined
1✔
490
        break
1✔
491
      }
492
      if (value instanceof ReturnValue || value instanceof TailCallReturnValue) {
151✔
493
        break
3✔
494
      }
495

496
      yield* actualValue(updateNode, context)
148✔
497
    }
498

499
    popEnvironment(context)
26✔
500

501
    return value
26✔
502
  },
503

504
  MemberExpression: function*(node: es.MemberExpression, context: Context) {
505
    let obj = yield* actualValue(node.object, context)
32✔
506
    if (obj instanceof Closure) {
32✔
507
      obj = obj.fun
1✔
508
    }
509
    let prop
510
    if (node.computed) {
32✔
511
      prop = yield* actualValue(node.property, context)
18✔
512
    } else {
513
      prop = (node.property as es.Identifier).name
14✔
514
    }
515

516
    const error = rttc.checkMemberAccess(node, obj, prop)
32✔
517
    if (error) {
32✔
518
      return handleRuntimeError(context, error)
3✔
519
    }
520

521
    if (
29✔
522
      obj !== null &&
114✔
523
      obj !== undefined &&
524
      typeof obj[prop] !== 'undefined' &&
525
      !obj.hasOwnProperty(prop)
526
    ) {
527
      return handleRuntimeError(context, new errors.GetInheritedPropertyError(node, obj, prop))
1✔
528
    }
529
    try {
28✔
530
      return obj[prop]
28✔
531
    } catch {
532
      return handleRuntimeError(context, new errors.GetPropertyError(node, obj, prop))
×
533
    }
534
  },
535

536
  AssignmentExpression: function*(node: es.AssignmentExpression, context: Context) {
537
    if (node.left.type === 'MemberExpression') {
343✔
538
      const left = node.left
134✔
539
      const obj = yield* actualValue(left.object, context)
134✔
540
      let prop
541
      if (left.computed) {
134✔
542
        prop = yield* actualValue(left.property, context)
126✔
543
      } else {
544
        prop = (left.property as es.Identifier).name
8✔
545
      }
546

547
      const error = rttc.checkMemberAccess(node, obj, prop)
134✔
548
      if (error) {
134✔
549
        return handleRuntimeError(context, error)
4✔
550
      }
551

552
      const val = yield* evaluate(node.right, context)
130✔
553
      try {
130✔
554
        obj[prop] = val
130✔
555
      } catch {
556
        return handleRuntimeError(context, new errors.SetPropertyError(node, obj, prop))
×
557
      }
558
      return val
130✔
559
    }
560
    const id = node.left as es.Identifier
209✔
561
    // Make sure it exist
562
    const value = yield* evaluate(node.right, context)
209✔
563
    setVariable(context, id.name, value)
209✔
564
    return value
204✔
565
  },
566

567
  FunctionDeclaration: function*(node: es.FunctionDeclaration, context: Context) {
568
    const id = node.id
14,009✔
569
    if (id === null) {
14,009!
570
      throw new Error("Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.")
×
571
    }
572
    // tslint:disable-next-line:no-any
573
    const closure = new Closure(node, currentEnvironment(context), context)
14,009✔
574
    defineVariable(context, id.name, closure, true)
14,009✔
575
    return undefined
14,009✔
576
  },
577

578
  IfStatement: function*(node: es.IfStatement | es.ConditionalExpression, context: Context) {
579
    const result = yield* reduceIf(node, context)
35,580✔
580
    if (result === null) {
35,579✔
581
      return undefined;
2✔
582
    }
583
    return yield* evaluate(result, context)
35,577✔
584
  },
585

586
  ExpressionStatement: function*(node: es.ExpressionStatement, context: Context) {
587
    return yield* evaluate(node.expression, context)
923✔
588
  },
589

590
  ReturnStatement: function*(node: es.ReturnStatement, context: Context) {
591
    let returnExpression = node.argument!
62,036✔
592

593
    // If we have a conditional expression, reduce it until we get something else
594
    while (
62,036✔
595
      returnExpression.type === 'LogicalExpression' ||
192,297✔
596
      returnExpression.type === 'ConditionalExpression'
597
    ) {
598
      if (returnExpression.type === 'LogicalExpression') {
36,771✔
599
        returnExpression = transformLogicalExpression(returnExpression)
5,295✔
600
      }
601
      returnExpression = yield* reduceIf(returnExpression, context)
36,771✔
602
    }
603

604
    // If we are now left with a CallExpression, then we use TCO
605
    if (returnExpression.type === 'CallExpression' && context.variant !== Variant.LAZY) {
62,025✔
606
      const callee = yield* actualValue(returnExpression.callee, context)
49,858✔
607
      const args = yield* getArgs(context, returnExpression)
48,558✔
608
      return new TailCallReturnValue(callee, args, returnExpression)
47,419✔
609
    } else {
610
      return new ReturnValue(yield* evaluate(returnExpression, context))
12,167✔
611
    }
612
  },
613

614
  WhileStatement: function*(node: es.WhileStatement, context: Context) {
615
    let value: any // tslint:disable-line
616
    while (
5✔
617
      // tslint:disable-next-line
618
      (yield* actualValue(node.test, context)) &&
44✔
619
      !(value instanceof ReturnValue) &&
620
      !(value instanceof BreakValue) &&
621
      !(value instanceof TailCallReturnValue)
622
    ) {
623
      value = yield* actualValue(node.body, context)
8✔
624
    }
625
    if (value instanceof BreakValue) {
5✔
626
      return undefined
1✔
627
    }
628
    return value
4✔
629
  },
630

631
  ObjectExpression: function*(node: es.ObjectExpression, context: Context) {
632
    const obj = {}
34✔
633
    for (const propUntyped of node.properties) {
34✔
634
      // node.properties: es.Property | es.SpreadExpression, but
635
      // our Acorn is set to ES6 which cannot have a es.SpreadExpression
636
      // at this point. Force the type.
637
      const prop = propUntyped as es.Property
56✔
638
      let key
639
      if (prop.key.type === 'Identifier') {
56✔
640
        key = prop.key.name
52✔
641
      } else {
642
        key = yield* evaluate(prop.key, context)
4✔
643
      }
644
      obj[key] = yield* evaluate(prop.value, context)
56✔
645
    }
646
    return obj
34✔
647
  },
648

649
  BlockStatement: function*(node: es.BlockStatement, context: Context) {
650
    // Create a new environment (block scoping)
651
    const environment = createBlockEnvironment(context, 'blockEnvironment')
30,558✔
652
    pushEnvironment(context, environment)
30,558✔
653
    const result: Value = yield* evaluateBlockStatement(context, node)
30,558✔
654
    popEnvironment(context)
30,218✔
655
    return result
30,218✔
656
  },
657

658
  ImportDeclaration: function*(node: es.ImportDeclaration, context: Context) {
659
    throw new Error('ImportDeclarations should already have been removed')
×
660
  },
661

662
  ExportNamedDeclaration: function*(_node: es.ExportNamedDeclaration, _context: Context) {
663
    // Exports are handled as a separate pre-processing step in 'transformImportedFile'.
664
    // Subsequently, they are removed from the AST by 'removeExports' before the AST is evaluated.
665
    // As such, there should be no ExportNamedDeclaration nodes in the AST.
666
    throw new Error('Encountered an ExportNamedDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.')
×
667
  },
668

669
  ExportDefaultDeclaration: function*(_node: es.ExportDefaultDeclaration, _context: Context) {
670
    // Exports are handled as a separate pre-processing step in 'transformImportedFile'.
671
    // Subsequently, they are removed from the AST by 'removeExports' before the AST is evaluated.
672
    // As such, there should be no ExportDefaultDeclaration nodes in the AST.
673
    throw new Error('Encountered an ExportDefaultDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.')
×
674
  },
675

676
  ExportAllDeclaration: function*(_node: es.ExportAllDeclaration, _context: Context) {
677
    // Exports are handled as a separate pre-processing step in 'transformImportedFile'.
678
    // Subsequently, they are removed from the AST by 'removeExports' before the AST is evaluated.
679
    // As such, there should be no ExportAllDeclaration nodes in the AST.
680
    throw new Error('Encountered an ExportAllDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.')
×
681
  },
682

683
  Program: function*(node: es.BlockStatement, context: Context) {
684
    throw new Error('A program should not contain another program within itself')
×
685
  }
686
}
687
// tslint:enable:object-literal-shorthand
688

689
// TODO: move to util
690
/**
691
 * Checks if `env` is empty (that is, head of env is an empty object)
692
 */
693
function isEmptyEnvironment(env: Environment) {
694
  return isEmpty(env.head)
160,515✔
695
}
696

697
/**
698
 * Extracts the non-empty tail environment from the given environment and
699
 * returns current environment if tail environment is a null.
700
 */
701
function getNonEmptyEnv(environment: Environment): Environment {
702
  if (isEmptyEnvironment(environment)) {
160,515✔
703
    const tailEnvironment = environment.tail
95,121✔
704
    if (tailEnvironment === null) {
95,121!
705
      return environment
×
706
    }
707
    return getNonEmptyEnv(tailEnvironment)
95,121✔
708
  } else {
709
    return environment
65,394✔
710
  }
711
}
712

713
export function* evaluateProgram(
67✔
714
  program: es.Program,
715
  context: Context,
716
  checkImports: boolean,
717
  loadTabs: boolean
718
) {
719
  yield* visit(context, program)
1,028✔
720

721
  context.numberOfOuterEnvironments += 1
1,027✔
722
  const environment = createBlockEnvironment(context, 'programEnvironment')
1,027✔
723
  pushEnvironment(context, environment)
1,027✔
724

725
  const otherNodes: es.Statement[] = []
1,027✔
726
  const moduleFunctions: Record<string, ModuleFunctions> = {}
1,027✔
727

728
  try {
1,027✔
729
    for (const node of program.body) {
1,027✔
730
      if (node.type !== 'ImportDeclaration') {
14,847✔
731
        otherNodes.push(node as es.Statement)
14,847✔
732
        continue
14,847✔
733
      }
734

735
      yield* visit(context, node)
×
736

737
      const moduleName = node.source.value
×
738
      assert(
×
739
        typeof moduleName === 'string',
740
        `ImportDeclarations should have string sources, got ${moduleName}`
741
      )
742

743
      if (!(moduleName in moduleFunctions)) {
×
744
        initModuleContext(moduleName, context, loadTabs)
×
745
        moduleFunctions[moduleName] = loadModuleBundle(moduleName, context, node)
×
746
      }
747

748
      const functions = moduleFunctions[moduleName]
×
749

750
      for (const spec of node.specifiers) {
×
751
        assert(
×
752
          spec.type === 'ImportSpecifier',
753
          `Only Import Specifiers are supported, got ${spec.type}`
754
        )
755

756
        if (checkImports && !(spec.imported.name in functions)) {
×
757
          throw new UndefinedImportError(spec.imported.name, moduleName, spec)
×
758
        }
759

760
        declareIdentifier(context, spec.local.name, node)
×
761
        defineVariable(context, spec.local.name, functions[spec.imported.name], true)
×
762
      }
763
      yield* leave(context)
×
764
    }
765
  } catch (error) {
766
    handleRuntimeError(context, error)
×
767
  }
768

769
  const newProgram = create.blockStatement(otherNodes)
1,027✔
770
  const result = yield* forceIt(yield* evaluateBlockStatement(context, newProgram), context)
1,027✔
771

772
  yield* leave(context) // Done visiting program
888✔
773

774
  if (result instanceof Closure) {
888✔
775
    Object.defineProperty(getNonEmptyEnv(currentEnvironment(context)).head, uniqueId(), {
2✔
776
      value: result,
777
      writable: false,
778
      enumerable: true
779
    })
780
  }
781
  return result
888✔
782
}
783

784
function* evaluate(node: es.Node, context: Context) {
785
  yield* visit(context, node)
763,606✔
786
  const result = yield* evaluators[node.type](node, context)
763,605✔
787
  yield* leave(context)
747,505✔
788
  if (result instanceof Closure) {
747,504✔
789
    Object.defineProperty(getNonEmptyEnv(currentEnvironment(context)).head, uniqueId(), {
65,398✔
790
      value: result,
791
      writable: false,
792
      enumerable: true
793
    })
794
  }
795
  return result
747,498✔
796
}
797

798
export function* apply(
67✔
799
  context: Context,
800
  fun: Closure | Value,
801
  args: (Thunk | Value)[],
802
  node: es.CallExpression,
803
  thisContext?: Value
804
) {
805
  let result: Value
806
  let total = 0
25,554✔
807

808
  while (!(result instanceof ReturnValue)) {
25,554✔
809
    if (fun instanceof Closure) {
72,973✔
810
      checkNumberOfArguments(context, fun, args, node!)
62,081✔
811
      const environment = createEnvironment(fun, args, node)
62,066✔
812
      if (result instanceof TailCallReturnValue) {
62,066✔
813
        replaceEnvironment(context, environment)
46,655✔
814
      } else {
815
        pushEnvironment(context, environment)
15,411✔
816
        total++
15,411✔
817
      }
818
      const bodyEnvironment = createBlockEnvironment(context, 'functionBodyEnvironment')
62,066✔
819
      bodyEnvironment.thisContext = thisContext
62,066✔
820
      pushEnvironment(context, bodyEnvironment)
62,066✔
821
      result = yield* evaluateBlockStatement(context, fun.node.body as es.BlockStatement)
62,065✔
822
      popEnvironment(context)
56,198✔
823
      if (result instanceof TailCallReturnValue) {
56,198✔
824
        fun = result.callee
47,419✔
825
        node = result.node
47,419✔
826
        args = result.args
47,419✔
827
      } else if (!(result instanceof ReturnValue)) {
8,779✔
828
        // No Return Value, set it as undefined
829
        result = new ReturnValue(undefined)
17✔
830
      }
831
    } else if (fun instanceof LazyBuiltIn) {
10,892✔
832
      try {
5,357✔
833
        let finalArgs = args
5,357✔
834
        if (fun.evaluateArgs) {
5,357✔
835
          finalArgs = []
4,460✔
836
          for (const arg of args) {
4,460✔
837
            finalArgs.push(yield* forceIt(arg, context))
4,460✔
838
          }
839
        }
840
        result = fun.func.apply(thisContext, finalArgs)
5,357✔
841
        break
5,354✔
842
      } catch (e) {
843
        // Recover from exception
844
        context.runtime.environments = context.runtime.environments.slice(
3✔
845
          -context.numberOfOuterEnvironments
846
        )
847

848
        const loc = node.loc ?? UNKNOWN_LOCATION
3!
849
        if (!(e instanceof RuntimeSourceError || e instanceof errors.ExceptionError)) {
3✔
850
          // The error could've arisen when the builtin called a source function which errored.
851
          // If the cause was a source error, we don't want to include the error.
852
          // However if the error came from the builtin itself, we need to handle it.
853
          return handleRuntimeError(context, new errors.ExceptionError(e, loc))
3✔
854
        }
855
        result = undefined
×
856
        throw e
×
857
      }
858
    } else if (typeof fun === 'function') {
5,535✔
859
      checkNumberOfArguments(context, fun, args, node!)
5,510✔
860
      try {
5,504✔
861
        const forcedArgs = []
5,504✔
862

863
        for (const arg of args) {
5,504✔
864
          forcedArgs.push(yield* forceIt(arg, context))
7,325✔
865
        }
866

867
        result = fun.apply(thisContext, forcedArgs)
5,504✔
868
        break
5,455✔
869
      } catch (e) {
870
        // Recover from exception
871
        context.runtime.environments = context.runtime.environments.slice(
49✔
872
          -context.numberOfOuterEnvironments
873
        )
874

875
        const loc = node.loc ?? UNKNOWN_LOCATION
49!
876
        if (!(e instanceof RuntimeSourceError || e instanceof errors.ExceptionError)) {
49✔
877
          // The error could've arisen when the builtin called a source function which errored.
878
          // If the cause was a source error, we don't want to include the error.
879
          // However if the error came from the builtin itself, we need to handle it.
880
          return handleRuntimeError(context, new errors.ExceptionError(e, loc))
40✔
881
        }
882
        result = undefined
9✔
883
        throw e
9✔
884
      }
885
    } else {
886
      return handleRuntimeError(context, new errors.CallingNonFunctionValue(fun, node))
25✔
887
    }
888
  }
889
  // Unwraps return value and release stack environment
890
  if (result instanceof ReturnValue) {
19,588✔
891
    result = result.value
8,779✔
892
  }
893
  for (let i = 1; i <= total; i++) {
19,588✔
894
    popEnvironment(context)
9,539✔
895
  }
896
  return result
19,588✔
897
}
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

© 2025 Coveralls, Inc