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

source-academy / js-slang / 13622100329

03 Mar 2025 02:25AM UTC coverage: 81.126% (-0.005%) from 81.131%
13622100329

Pull #1743

github

web-flow
Merge 68a52672d into 3c5afad3e
Pull Request #1743: Remove lazy

3440 of 4608 branches covered (74.65%)

Branch coverage included in aggregate %.

13 of 13 new or added lines in 3 files covered. (100.0%)

15 existing lines in 4 files now uncovered.

10762 of 12898 relevant lines covered (83.44%)

134575.29 hits per line

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

89.21
/src/interpreter/interpreter.ts
1
/* tslint:disable:max-classes-per-file */
2
import type es from 'estree'
3
import { isEmpty } from 'lodash'
74✔
4

5
import { UNKNOWN_LOCATION } from '../constants'
74✔
6
import Heap from '../cse-machine/heap'
74✔
7
import { uniqueId } from '../cse-machine/utils'
74✔
8
import * as errors from '../errors/errors'
74✔
9
import { RuntimeSourceError } from '../errors/runtimeSourceError'
74✔
10
import { checkEditorBreakpoints } from '../stdlib/inspector'
74✔
11
import type { Context, ContiguousArrayElements, Environment, Node, Value } from '../types'
12
import * as create from '../utils/ast/astCreator'
74✔
13
import { conditionalExpression, literal, primitive } from '../utils/ast/astCreator'
74✔
14
import { getModuleDeclarationSource } from '../utils/ast/helpers'
74✔
15
import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators'
74✔
16
import * as rttc from '../utils/rttc'
74✔
17
import Closure from './closure'
74✔
18

19
class BreakValue {}
20

21
class ContinueValue {}
22

23
class ReturnValue {
24
  constructor(public value: Value) {}
6,133✔
25
}
26

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

31
class Thunk {
32
  public value: Value
33
  public isMemoized: boolean
UNCOV
34
  constructor(public exp: Node, public env: Environment) {
×
UNCOV
35
    this.isMemoized = false
×
UNCOV
36
    this.value = null
×
37
  }
38
}
39

40
function* forceIt(val: any, context: Context): Value {
41
  if (val instanceof Thunk) {
582,686!
UNCOV
42
    if (val.isMemoized) return val.value
×
43

UNCOV
44
    pushEnvironment(context, val.env)
×
UNCOV
45
    const evalRes = yield* actualValue(val.exp, context)
×
UNCOV
46
    popEnvironment(context)
×
UNCOV
47
    val.value = evalRes
×
UNCOV
48
    val.isMemoized = true
×
UNCOV
49
    return evalRes
×
50
  } else return val
582,686✔
51
}
52

53
export function* actualValue(exp: Node, context: Context): Value {
74✔
54
  const evalResult = yield* evaluate(exp, context)
579,735✔
55
  const forced = yield* forceIt(evalResult, context)
573,867✔
56
  return forced
573,867✔
57
}
58

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

88
export const createBlockEnvironment = (
74✔
89
  context: Context,
90
  name = 'blockEnvironment'
×
91
): Environment => {
92
  return {
92,872✔
93
    name,
94
    tail: currentEnvironment(context),
95
    head: {},
96
    heap: new Heap(),
97
    id: uniqueId(context)
98
  }
99
}
100

101
const handleRuntimeError = (context: Context, error: RuntimeSourceError): never => {
74✔
102
  context.errors.push(error)
117✔
103
  context.runtime.environments = context.runtime.environments.slice(
117✔
104
    -context.numberOfOuterEnvironments
105
  )
106
  throw error
117✔
107
}
108

109
const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol('Used to implement block scope')
74✔
110

111
function declareIdentifier(context: Context, name: string, node: Node) {
112
  const environment = currentEnvironment(context)
14,491✔
113
  if (environment.head.hasOwnProperty(name)) {
14,491!
114
    const descriptors = Object.getOwnPropertyDescriptors(environment.head)
×
115

116
    return handleRuntimeError(
×
117
      context,
118
      new errors.VariableRedeclaration(node, name, descriptors[name].writable)
119
    )
120
  }
121
  environment.head[name] = DECLARED_BUT_NOT_YET_ASSIGNED
14,491✔
122
  return environment
14,491✔
123
}
124

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

131
function declareFunctionsAndVariables(context: Context, node: es.BlockStatement) {
132
  for (const statement of node.body) {
92,683✔
133
    switch (statement.type) {
106,841✔
134
      case 'VariableDeclaration':
135
        declareVariables(context, statement)
1,009✔
136
        break
1,009✔
137
      case 'FunctionDeclaration':
138
        if (statement.id === null) {
13,308!
139
          throw new Error(
×
140
            'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.'
141
          )
142
        }
143
        declareIdentifier(context, statement.id.name, statement)
13,308✔
144
        break
13,308✔
145
    }
146
  }
147
}
148

149
function defineVariable(context: Context, name: string, value: Value, constant = false) {
×
150
  const environment = currentEnvironment(context)
14,484✔
151

152
  if (environment.head[name] !== DECLARED_BUT_NOT_YET_ASSIGNED) {
14,484!
153
    return handleRuntimeError(
×
154
      context,
155
      new errors.VariableRedeclaration(context.runtime.nodes[0]!, name, !constant)
156
    )
157
  }
158

159
  Object.defineProperty(environment.head, name, {
14,484✔
160
    value,
161
    writable: !constant,
162
    enumerable: true
163
  })
164

165
  return environment
14,484✔
166
}
167

168
function* visit(context: Context, node: Node) {
169
  checkEditorBreakpoints(context, node)
735,992✔
170
  context.runtime.nodes.unshift(node)
735,989✔
171
  yield context
735,989✔
172
}
173

174
function* leave(context: Context) {
175
  context.runtime.break = false
719,810✔
176
  context.runtime.nodes.shift()
719,810✔
177
  yield context
719,810✔
178
}
179

180
const currentEnvironment = (context: Context) => context.runtime.environments[0]
447,454✔
181
const replaceEnvironment = (context: Context, environment: Environment) => {
74✔
182
  context.runtime.environments[0] = environment
47,204✔
183
  context.runtime.environmentTree.insert(environment)
47,204✔
184
}
185
const popEnvironment = (context: Context) => context.runtime.environments.shift()
92,782✔
186
export const pushEnvironment = (context: Context, environment: Environment) => {
74✔
187
  context.runtime.environments.unshift(environment)
105,638✔
188
  context.runtime.environmentTree.insert(environment)
105,638✔
189
}
190

191
const getVariable = (context: Context, name: string) => {
74✔
192
  let environment: Environment | null = currentEnvironment(context)
246,198✔
193
  while (environment) {
246,198✔
194
    if (environment.head.hasOwnProperty(name)) {
665,239✔
195
      if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) {
246,189✔
196
        return handleRuntimeError(
3✔
197
          context,
198
          new errors.UnassignedVariable(name, context.runtime.nodes[0])
199
        )
200
      } else {
201
        return environment.head[name]
246,186✔
202
      }
203
    } else {
204
      environment = environment.tail
419,049✔
205
    }
206
  }
207
  return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0]))
8✔
208
}
209

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

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

270
function* getArgs(context: Context, call: es.CallExpression) {
271
  const args = []
67,723✔
272
  for (const arg of call.arguments) {
67,723✔
273
    if (arg.type === 'SpreadElement') {
130,533✔
274
      args.push(...(yield* actualValue(arg.argument, context)))
4✔
275
    } else {
276
      args.push(yield* actualValue(arg, context))
130,529✔
277
    }
278
  }
279
  return args
66,574✔
280
}
281

282
function transformLogicalExpression(node: es.LogicalExpression): es.ConditionalExpression {
283
  if (node.operator === '&&') {
10,336✔
284
    return conditionalExpression(node.left, node.right, literal(false), node.loc)
330✔
285
  } else {
286
    return conditionalExpression(node.left, literal(true), node.right, node.loc)
10,006✔
287
  }
288
}
289

290
function* reduceIf(
291
  node: es.IfStatement | es.ConditionalExpression,
292
  context: Context
293
): IterableIterator<null | Node> {
294
  const test = yield* actualValue(node.test, context)
70,393✔
295

296
  const error = rttc.checkIfStatement(node, test, context.chapter)
70,385✔
297
  if (error) {
70,385✔
298
    return handleRuntimeError(context, error)
1✔
299
  }
300

301
  return test ? node.consequent : node.alternate
70,384✔
302
}
303

304
export type Evaluator<T extends Node> = (node: T, context: Context) => IterableIterator<Value>
305

306
function* evaluateBlockStatement(context: Context, node: es.BlockStatement) {
307
  declareFunctionsAndVariables(context, node)
92,683✔
308
  let result
309
  for (const statement of node.body) {
92,683✔
310
    result = yield* evaluate(statement, context)
106,789✔
311
    if (
100,472✔
312
      result instanceof ReturnValue ||
219,727✔
313
      result instanceof TailCallReturnValue ||
314
      result instanceof BreakValue ||
315
      result instanceof ContinueValue
316
    ) {
317
      break
85,513✔
318
    }
319
  }
320
  return result
86,366✔
321
}
322

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

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

346
  ThisExpression: function*(node: es.ThisExpression, context: Context) {
347
    return currentEnvironment(context).thisContext
2✔
348
  },
349

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

358
  DebuggerStatement: function*(node: es.DebuggerStatement, context: Context) {
359
    context.runtime.break = true
×
360
    yield
×
361
  },
362

363
  FunctionExpression: function*(node: es.FunctionExpression, context: Context) {
364
    return new Closure(node, currentEnvironment(context), context)
×
365
  },
366

367
  ArrowFunctionExpression: function*(node: es.ArrowFunctionExpression, context: Context) {
368
    return Closure.makeFromArrowFunction(node, currentEnvironment(context), context)
797✔
369
  },
370

371
  Identifier: function*(node: es.Identifier, context: Context) {
372
    return getVariable(context, node.name)
246,198✔
373
  },
374

375
  CallExpression: function*(node: es.CallExpression, context: Context) {
376
    const callee = yield* actualValue(node.callee, context)
18,606✔
377
    const args = yield* getArgs(context, node)
18,593✔
378
    let thisContext
379
    if (node.callee.type === 'MemberExpression') {
18,582✔
380
      thisContext = yield* actualValue(node.callee.object, context)
2✔
381
    }
382
    const result = yield* apply(context, callee, args, node, thisContext)
18,582✔
383
    return result
12,650✔
384
  },
385

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

403
  UnaryExpression: function*(node: es.UnaryExpression, context: Context) {
404
    const value = yield* actualValue(node.argument, context)
48✔
405

406
    const error = rttc.checkUnaryExpression(node, node.operator, value, context.chapter)
47✔
407
    if (error) {
47!
408
      return handleRuntimeError(context, error)
×
409
    }
410
    return evaluateUnaryExpression(node.operator, value)
47✔
411
  },
412

413
  BinaryExpression: function*(node: es.BinaryExpression, context: Context) {
414
    const left = yield* actualValue(node.left, context)
154,961✔
415
    const right = yield* actualValue(node.right, context)
153,939✔
416
    const error = rttc.checkBinaryExpression(node, node.operator, context.chapter, left, right)
151,556✔
417
    if (error) {
151,556✔
418
      return handleRuntimeError(context, error)
7✔
419
    }
420
    return evaluateBinaryExpression(node.operator, left, right)
151,549✔
421
  },
422

423
  ConditionalExpression: function*(node: es.ConditionalExpression, context: Context) {
424
    return yield* this.IfStatement(node, context)
5,135✔
425
  },
426

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

431
  VariableDeclaration: function*(node: es.VariableDeclaration, context: Context) {
432
    const declaration = node.declarations[0]
1,031✔
433
    const constant = node.kind === 'const'
1,031✔
434
    const id = declaration.id as es.Identifier
1,031✔
435
    const value = yield* evaluate(declaration.init!, context)
1,031✔
436
    defineVariable(context, id.name, value, constant)
1,028✔
437
    return undefined
1,028✔
438
  },
439

440
  ContinueStatement: function*(_node: es.ContinueStatement, _context: Context) {
441
    return new ContinueValue()
1✔
442
  },
443

444
  BreakStatement: function*(_node: es.BreakStatement, _context: Context) {
445
    return new BreakValue()
2✔
446
  },
447

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

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

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

477
      value = yield* actualValue(node.body, context)
152✔
478

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

492
      yield* actualValue(updateNode, context)
148✔
493
    }
494

495
    popEnvironment(context)
26✔
496

497
    return value
26✔
498
  },
499

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

512
    const error = rttc.checkMemberAccess(node, obj, prop)
32✔
513
    if (error) {
32✔
514
      return handleRuntimeError(context, error)
3✔
515
    }
516

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

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

543
      const error = rttc.checkMemberAccess(node, obj, prop)
134✔
544
      if (error) {
134✔
545
        return handleRuntimeError(context, error)
4✔
546
      }
547

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

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

574
  IfStatement: function*(node: es.IfStatement | es.ConditionalExpression, context: Context) {
575
    const result = yield* reduceIf(node, context)
36,878✔
576
    if (result === null) {
36,876✔
577
      return undefined;
2✔
578
    }
579
    return yield* evaluate(result, context)
36,874✔
580
  },
581

582
  ExpressionStatement: function*(node: es.ExpressionStatement, context: Context) {
583
    return yield* evaluate(node.expression, context)
745✔
584
  },
585

586
  ReturnStatement: function*(node: es.ReturnStatement, context: Context) {
587
    let returnExpression = node.argument!
59,952✔
588

589
    // If we have a conditional expression, reduce it until we get something else
590
    while (
59,952✔
591
      returnExpression.type === 'LogicalExpression' ||
181,716✔
592
      returnExpression.type === 'ConditionalExpression'
593
    ) {
594
      if (returnExpression.type === 'LogicalExpression') {
33,515✔
595
        returnExpression = transformLogicalExpression(returnExpression)
5,204✔
596
      }
597
      returnExpression = yield* reduceIf(returnExpression, context)
33,515✔
598
    }
599

600
    // If we are now left with a CallExpression, then we use TCO
601
    if (returnExpression.type === 'CallExpression') {
59,945✔
602
      const callee = yield* actualValue(returnExpression.callee, context)
50,424✔
603
      const args = yield* getArgs(context, returnExpression)
49,130✔
604
      return new TailCallReturnValue(callee, args, returnExpression)
47,992✔
605
    } else {
606
      return new ReturnValue(yield* evaluate(returnExpression, context))
9,521✔
607
    }
608
  },
609

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

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

645
  BlockStatement: function*(node: es.BlockStatement, context: Context) {
646
    // Create a new environment (block scoping)
647
    const environment = createBlockEnvironment(context, 'blockEnvironment')
31,901✔
648
    pushEnvironment(context, environment)
31,901✔
649
    const result: Value = yield* evaluateBlockStatement(context, node)
31,901✔
650
    popEnvironment(context)
31,562✔
651
    return result
31,562✔
652
  },
653

654
  ImportDeclaration: function*(node: es.ImportDeclaration, context: Context) {
655
    throw new Error('ImportDeclarations should already have been removed')
×
656
  },
657

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

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

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

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

685
// TODO: move to util
686
/**
687
 * Checks if `env` is empty (that is, head of env is an empty object)
688
 */
689
function isEmptyEnvironment(env: Environment) {
690
  return isEmpty(env.head)
160,214✔
691
}
692

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

709
export function* evaluateProgram(program: es.Program, context: Context) {
74✔
710
  yield* visit(context, program)
808✔
711

712
  context.numberOfOuterEnvironments += 1
808✔
713
  const environment = createBlockEnvironment(context, 'programEnvironment')
808✔
714
  pushEnvironment(context, environment)
808✔
715

716
  const otherNodes: es.Statement[] = []
808✔
717

718
  try {
808✔
719
    for (const node of program.body) {
808✔
720
      if (node.type !== 'ImportDeclaration') {
13,949✔
721
        otherNodes.push(node as es.Statement)
13,948✔
722
        continue
13,948✔
723
      }
724

725
      yield* visit(context, node)
1✔
726

727
      const moduleName = getModuleDeclarationSource(node)
1✔
728
      const functions = context.nativeStorage.loadedModules[moduleName]
1✔
729

730
      for (const spec of node.specifiers) {
1✔
731
        declareIdentifier(context, spec.local.name, node)
1✔
732
        let obj: any
733

734
        switch (spec.type) {
1!
735
          case 'ImportSpecifier': {
736
            obj = functions[spec.imported.name]
×
737
            break
×
738
          }
739
          case 'ImportDefaultSpecifier': {
740
            obj = functions.default
1✔
741
            break
1✔
742
          }
743
          case 'ImportNamespaceSpecifier': {
744
            obj = functions
×
745
            break
×
746
          }
747
        }
748

749
        defineVariable(context, spec.local.name, obj, true)
1✔
750
      }
751
      yield* leave(context)
1✔
752
    }
753
  } catch (error) {
754
    handleRuntimeError(context, error)
×
755
  }
756

757
  const newProgram = create.blockStatement(otherNodes)
808✔
758
  const result = yield* forceIt(yield* evaluateBlockStatement(context, newProgram), context)
808✔
759

760
  yield* leave(context) // Done visiting program
679✔
761

762
  if (result instanceof Closure) {
679✔
763
    Object.defineProperty(getNonEmptyEnv(currentEnvironment(context)).head, uniqueId(context), {
2✔
764
      value: result,
765
      writable: false,
766
      enumerable: true
767
    })
768
  }
769
  return result
679✔
770
}
771

772
function* evaluate(node: Node, context: Context) {
773
  yield* visit(context, node)
735,183✔
774
  const result = yield* evaluators[node.type](node, context)
735,180✔
775
  yield* leave(context)
719,130✔
776
  if (result instanceof Closure) {
719,129✔
777
    Object.defineProperty(getNonEmptyEnv(currentEnvironment(context)).head, uniqueId(context), {
65,096✔
778
      value: result,
779
      writable: false,
780
      enumerable: true
781
    })
782
  }
783
  return result
719,124✔
784
}
785

786
export function* apply(
74✔
787
  context: Context,
788
  fun: Closure | Value,
789
  args: (Thunk | Value)[],
790
  node: es.CallExpression,
791
  thisContext?: Value
792
) {
793
  let result: Value
794
  let total = 0
18,586✔
795

796
  while (!(result instanceof ReturnValue)) {
18,586✔
797
    if (fun instanceof Closure) {
66,578✔
798
      checkNumberOfArguments(context, fun, args, node!)
59,989✔
799
      const environment = createEnvironment(context, fun, args, node)
59,974✔
800
      if (result instanceof TailCallReturnValue) {
59,974✔
801
        replaceEnvironment(context, environment)
47,204✔
802
      } else {
803
        pushEnvironment(context, environment)
12,770✔
804
        total++
12,770✔
805
      }
806
      const bodyEnvironment = createBlockEnvironment(context, 'functionBodyEnvironment')
59,974✔
807
      bodyEnvironment.thisContext = thisContext
59,974✔
808
      pushEnvironment(context, bodyEnvironment)
59,974✔
809
      result = yield* evaluateBlockStatement(context, fun.node.body as es.BlockStatement)
59,974✔
810
      popEnvironment(context)
54,125✔
811
      if (result instanceof TailCallReturnValue) {
54,125✔
812
        fun = result.callee
47,992✔
813
        node = result.node
47,992✔
814
        args = result.args
47,992✔
815
      } else if (!(result instanceof ReturnValue)) {
6,133✔
816
        // No Return Value, set it as undefined
817
        result = new ReturnValue(undefined)
14✔
818
      }
819
    } else if (typeof fun === 'function') {
6,589✔
820
      checkNumberOfArguments(context, fun, args, node!)
6,564✔
821
      try {
6,558✔
822
        const forcedArgs = []
6,558✔
823

824
        for (const arg of args) {
6,558✔
825
          forcedArgs.push(yield* forceIt(arg, context))
8,140✔
826
        }
827

828
        result = fun.apply(thisContext, forcedArgs)
6,558✔
829
        break
6,519✔
830
      } catch (e) {
831
        // Recover from exception
832
        context.runtime.environments = context.runtime.environments.slice(
39✔
833
          -context.numberOfOuterEnvironments
834
        )
835

836
        const loc = node.loc ?? UNKNOWN_LOCATION
39!
837
        if (!(e instanceof RuntimeSourceError || e instanceof errors.ExceptionError)) {
39✔
838
          // The error could've arisen when the builtin called a source function which errored.
839
          // If the cause was a source error, we don't want to include the error.
840
          // However if the error came from the builtin itself, we need to handle it.
841
          return handleRuntimeError(context, new errors.ExceptionError(e, loc))
39✔
842
        }
843
        result = undefined
×
844
        throw e
×
845
      }
846
    } else {
847
      return handleRuntimeError(context, new errors.CallingNonFunctionValue(fun, node))
25✔
848
    }
849
  }
850
  // Unwraps return value and release stack environment
851
  if (result instanceof ReturnValue) {
12,652✔
852
    result = result.value
6,133✔
853
  }
854
  for (let i = 1; i <= total; i++) {
12,652✔
855
    popEnvironment(context)
6,917✔
856
  }
857
  return result
12,652✔
858
}
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