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

source-academy / js-slang / 13750610219

09 Mar 2025 05:01PM UTC coverage: 81.086% (-0.04%) from 81.126%
13750610219

Pull #1741

github

web-flow
Merge 561e360c2 into 6aad26cce
Pull Request #1741: Bump prettier, ace-builds, and misc clean up

3438 of 4606 branches covered (74.64%)

Branch coverage included in aggregate %.

66 of 89 new or added lines in 12 files covered. (74.16%)

9 existing lines in 4 files now uncovered.

10782 of 12931 relevant lines covered (83.38%)

143543.43 hits per line

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

89.07
/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(
29
    public callee: Closure,
47,992✔
30
    public args: Value[],
47,992✔
31
    public node: es.CallExpression
47,992✔
32
  ) {}
33
}
34

35
class Thunk {
36
  public value: Value
37
  public isMemoized: boolean
38
  constructor(
NEW
39
    public exp: Node,
×
NEW
40
    public env: Environment
×
41
  ) {
42
    this.isMemoized = false
×
43
    this.value = null
×
44
  }
45
}
46

47
function* forceIt(val: any, context: Context): Value {
48
  if (val instanceof Thunk) {
582,686!
49
    if (val.isMemoized) return val.value
×
50

51
    pushEnvironment(context, val.env)
×
52
    const evalRes = yield* actualValue(val.exp, context)
×
53
    popEnvironment(context)
×
54
    val.value = evalRes
×
55
    val.isMemoized = true
×
56
    return evalRes
×
57
  } else return val
582,686✔
58
}
59

60
export function* actualValue(exp: Node, context: Context): Value {
74✔
61
  const evalResult = yield* evaluate(exp, context)
579,735✔
62
  const forced = yield* forceIt(evalResult, context)
573,867✔
63
  return forced
573,867✔
64
}
65

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

95
export const createBlockEnvironment = (
74✔
96
  context: Context,
97
  name = 'blockEnvironment'
×
98
): Environment => {
99
  return {
92,872✔
100
    name,
101
    tail: currentEnvironment(context),
102
    head: {},
103
    heap: new Heap(),
104
    id: uniqueId(context)
105
  }
106
}
107

108
const handleRuntimeError = (context: Context, error: RuntimeSourceError): never => {
74✔
109
  context.errors.push(error)
117✔
110
  context.runtime.environments = context.runtime.environments.slice(
117✔
111
    -context.numberOfOuterEnvironments
112
  )
113
  throw error
117✔
114
}
115

116
const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol('Used to implement block scope')
74✔
117

118
function declareIdentifier(context: Context, name: string, node: Node) {
119
  const environment = currentEnvironment(context)
14,491✔
120
  if (environment.head.hasOwnProperty(name)) {
14,491!
121
    const descriptors = Object.getOwnPropertyDescriptors(environment.head)
×
122

123
    return handleRuntimeError(
×
124
      context,
125
      new errors.VariableRedeclaration(node, name, descriptors[name].writable)
126
    )
127
  }
128
  environment.head[name] = DECLARED_BUT_NOT_YET_ASSIGNED
14,491✔
129
  return environment
14,491✔
130
}
131

132
function declareVariables(context: Context, node: es.VariableDeclaration) {
133
  for (const declaration of node.declarations) {
1,034✔
134
    declareIdentifier(context, (declaration.id as es.Identifier).name, node)
1,034✔
135
  }
136
}
137

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

156
function defineVariable(context: Context, name: string, value: Value, constant = false) {
×
157
  const environment = currentEnvironment(context)
14,484✔
158

159
  if (environment.head[name] !== DECLARED_BUT_NOT_YET_ASSIGNED) {
14,484!
160
    return handleRuntimeError(
×
161
      context,
162
      new errors.VariableRedeclaration(context.runtime.nodes[0]!, name, !constant)
163
    )
164
  }
165

166
  Object.defineProperty(environment.head, name, {
14,484✔
167
    value,
168
    writable: !constant,
169
    enumerable: true
170
  })
171

172
  return environment
14,484✔
173
}
174

175
function* visit(context: Context, node: Node) {
176
  checkEditorBreakpoints(context, node)
735,992✔
177
  context.runtime.nodes.unshift(node)
735,989✔
178
  yield context
735,989✔
179
}
180

181
function* leave(context: Context) {
182
  context.runtime.break = false
719,810✔
183
  context.runtime.nodes.shift()
719,810✔
184
  yield context
719,810✔
185
}
186

187
const currentEnvironment = (context: Context) => context.runtime.environments[0]
447,454✔
188
const replaceEnvironment = (context: Context, environment: Environment) => {
74✔
189
  context.runtime.environments[0] = environment
47,204✔
190
  context.runtime.environmentTree.insert(environment)
47,204✔
191
}
192
const popEnvironment = (context: Context) => context.runtime.environments.shift()
92,782✔
193
export const pushEnvironment = (context: Context, environment: Environment) => {
74✔
194
  context.runtime.environments.unshift(environment)
105,638✔
195
  context.runtime.environmentTree.insert(environment)
105,638✔
196
}
197

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

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

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

277
function* getArgs(context: Context, call: es.CallExpression) {
278
  const args = []
67,723✔
279
  for (const arg of call.arguments) {
67,723✔
280
    if (arg.type === 'SpreadElement') {
130,533✔
281
      args.push(...(yield* actualValue(arg.argument, context)))
4✔
282
    } else {
283
      args.push(yield* actualValue(arg, context))
130,529✔
284
    }
285
  }
286
  return args
66,574✔
287
}
288

289
function transformLogicalExpression(node: es.LogicalExpression): es.ConditionalExpression {
290
  if (node.operator === '&&') {
10,336✔
291
    return conditionalExpression(node.left, node.right, literal(false), node.loc)
330✔
292
  } else {
293
    return conditionalExpression(node.left, literal(true), node.right, node.loc)
10,006✔
294
  }
295
}
296

297
function* reduceIf(
298
  node: es.IfStatement | es.ConditionalExpression,
299
  context: Context
300
): IterableIterator<null | Node> {
301
  const test = yield* actualValue(node.test, context)
70,393✔
302

303
  const error = rttc.checkIfStatement(node, test, context.chapter)
70,385✔
304
  if (error) {
70,385✔
305
    return handleRuntimeError(context, error)
1✔
306
  }
307

308
  return test ? node.consequent : node.alternate
70,384✔
309
}
310

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

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

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

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

353
  ThisExpression: function*(node: es.ThisExpression, context: Context) {
354
    return currentEnvironment(context).thisContext
2✔
355
  },
356

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

365
  DebuggerStatement: function*(node: es.DebuggerStatement, context: Context) {
366
    context.runtime.break = true
×
367
    yield
×
368
  },
369

370
  FunctionExpression: function*(node: es.FunctionExpression, context: Context) {
371
    return new Closure(node, currentEnvironment(context), context)
×
372
  },
373

374
  ArrowFunctionExpression: function*(node: es.ArrowFunctionExpression, context: Context) {
375
    return Closure.makeFromArrowFunction(node, currentEnvironment(context), context)
797✔
376
  },
377

378
  Identifier: function*(node: es.Identifier, context: Context) {
379
    return getVariable(context, node.name)
246,198✔
380
  },
381

382
  CallExpression: function*(node: es.CallExpression, context: Context) {
383
    const callee = yield* actualValue(node.callee, context)
18,606✔
384
    const args = yield* getArgs(context, node)
18,593✔
385
    let thisContext
386
    if (node.callee.type === 'MemberExpression') {
18,582✔
387
      thisContext = yield* actualValue(node.callee.object, context)
2✔
388
    }
389
    const result = yield* apply(context, callee, args, node, thisContext)
18,582✔
390
    return result
12,650✔
391
  },
392

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

410
  UnaryExpression: function*(node: es.UnaryExpression, context: Context) {
411
    const value = yield* actualValue(node.argument, context)
48✔
412

413
    const error = rttc.checkUnaryExpression(node, node.operator, value, context.chapter)
47✔
414
    if (error) {
47!
415
      return handleRuntimeError(context, error)
×
416
    }
417
    return evaluateUnaryExpression(node.operator, value)
47✔
418
  },
419

420
  BinaryExpression: function*(node: es.BinaryExpression, context: Context) {
421
    const left = yield* actualValue(node.left, context)
154,961✔
422
    const right = yield* actualValue(node.right, context)
153,939✔
423
    const error = rttc.checkBinaryExpression(node, node.operator, context.chapter, left, right)
151,556✔
424
    if (error) {
151,556✔
425
      return handleRuntimeError(context, error)
7✔
426
    }
427
    return evaluateBinaryExpression(node.operator, left, right)
151,549✔
428
  },
429

430
  ConditionalExpression: function*(node: es.ConditionalExpression, context: Context) {
431
    return yield* this.IfStatement(node, context)
5,135✔
432
  },
433

434
  LogicalExpression: function*(node: es.LogicalExpression, context: Context) {
435
    return yield* this.ConditionalExpression(transformLogicalExpression(node), context)
5,132✔
436
  },
437

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

447
  ContinueStatement: function*(_node: es.ContinueStatement, _context: Context) {
448
    return new ContinueValue()
1✔
449
  },
450

451
  BreakStatement: function*(_node: es.BreakStatement, _context: Context) {
452
    return new BreakValue()
2✔
453
  },
454

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

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

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

484
      value = yield* actualValue(node.body, context)
152✔
485

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

499
      yield* actualValue(updateNode, context)
148✔
500
    }
501

502
    popEnvironment(context)
26✔
503

504
    return value
26✔
505
  },
506

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

519
    const error = rttc.checkMemberAccess(node, obj, prop)
32✔
520
    if (error) {
32✔
521
      return handleRuntimeError(context, error)
3✔
522
    }
523

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

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

550
      const error = rttc.checkMemberAccess(node, obj, prop)
134✔
551
      if (error) {
134✔
552
        return handleRuntimeError(context, error)
4✔
553
      }
554

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

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

581
  IfStatement: function*(node: es.IfStatement | es.ConditionalExpression, context: Context) {
582
    const result = yield* reduceIf(node, context)
36,878✔
583
    if (result === null) {
36,876✔
584
      return undefined;
2✔
585
    }
586
    return yield* evaluate(result, context)
36,874✔
587
  },
588

589
  ExpressionStatement: function*(node: es.ExpressionStatement, context: Context) {
590
    return yield* evaluate(node.expression, context)
745✔
591
  },
592

593
  ReturnStatement: function*(node: es.ReturnStatement, context: Context) {
594
    let returnExpression = node.argument!
59,952✔
595

596
    // If we have a conditional expression, reduce it until we get something else
597
    while (
59,952✔
598
      returnExpression.type === 'LogicalExpression' ||
181,716✔
599
      returnExpression.type === 'ConditionalExpression'
600
    ) {
601
      if (returnExpression.type === 'LogicalExpression') {
33,515✔
602
        returnExpression = transformLogicalExpression(returnExpression)
5,204✔
603
      }
604
      returnExpression = yield* reduceIf(returnExpression, context)
33,515✔
605
    }
606

607
    // If we are now left with a CallExpression, then we use TCO
608
    if (returnExpression.type === 'CallExpression') {
59,945✔
609
      const callee = yield* actualValue(returnExpression.callee, context)
50,424✔
610
      const args = yield* getArgs(context, returnExpression)
49,130✔
611
      return new TailCallReturnValue(callee, args, returnExpression)
47,992✔
612
    } else {
613
      return new ReturnValue(yield* evaluate(returnExpression, context))
9,521✔
614
    }
615
  },
616

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

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

652
  BlockStatement: function*(node: es.BlockStatement, context: Context) {
653
    // Create a new environment (block scoping)
654
    const environment = createBlockEnvironment(context, 'blockEnvironment')
31,901✔
655
    pushEnvironment(context, environment)
31,901✔
656
    const result: Value = yield* evaluateBlockStatement(context, node)
31,901✔
657
    popEnvironment(context)
31,562✔
658
    return result
31,562✔
659
  },
660

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

665
  ExportNamedDeclaration: function*(_node: es.ExportNamedDeclaration, _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 ExportNamedDeclaration nodes in the AST.
669
    throw new Error('Encountered an ExportNamedDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.')
×
670
  },
671

672
  ExportDefaultDeclaration: function*(_node: es.ExportDefaultDeclaration, _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 ExportDefaultDeclaration nodes in the AST.
676
    throw new Error('Encountered an ExportDefaultDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.')
×
677
  },
678

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

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

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

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

716
export function* evaluateProgram(program: es.Program, context: Context) {
74✔
717
  yield* visit(context, program)
808✔
718

719
  context.numberOfOuterEnvironments += 1
808✔
720
  const environment = createBlockEnvironment(context, 'programEnvironment')
808✔
721
  pushEnvironment(context, environment)
808✔
722

723
  const otherNodes: es.Statement[] = []
808✔
724

725
  try {
808✔
726
    for (const node of program.body) {
808✔
727
      if (node.type !== 'ImportDeclaration') {
13,949✔
728
        otherNodes.push(node as es.Statement)
13,948✔
729
        continue
13,948✔
730
      }
731

732
      yield* visit(context, node)
1✔
733

734
      const moduleName = getModuleDeclarationSource(node)
1✔
735
      const functions = context.nativeStorage.loadedModules[moduleName]
1✔
736

737
      for (const spec of node.specifiers) {
1✔
738
        declareIdentifier(context, spec.local.name, node)
1✔
739
        let obj: any
740

741
        switch (spec.type) {
1!
742
          case 'ImportSpecifier': {
743
            obj = functions[spec.imported.name]
×
744
            break
×
745
          }
746
          case 'ImportDefaultSpecifier': {
747
            obj = functions.default
1✔
748
            break
1✔
749
          }
750
          case 'ImportNamespaceSpecifier': {
751
            obj = functions
×
752
            break
×
753
          }
754
        }
755

756
        defineVariable(context, spec.local.name, obj, true)
1✔
757
      }
758
      yield* leave(context)
1✔
759
    }
760
  } catch (error) {
761
    handleRuntimeError(context, error)
×
762
  }
763

764
  const newProgram = create.blockStatement(otherNodes)
808✔
765
  const result = yield* forceIt(yield* evaluateBlockStatement(context, newProgram), context)
808✔
766

767
  yield* leave(context) // Done visiting program
679✔
768

769
  if (result instanceof Closure) {
679✔
770
    Object.defineProperty(getNonEmptyEnv(currentEnvironment(context)).head, uniqueId(context), {
2✔
771
      value: result,
772
      writable: false,
773
      enumerable: true
774
    })
775
  }
776
  return result
679✔
777
}
778

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

793
export function* apply(
74✔
794
  context: Context,
795
  fun: Closure | Value,
796
  args: (Thunk | Value)[],
797
  node: es.CallExpression,
798
  thisContext?: Value
799
) {
800
  let result: Value
801
  let total = 0
18,586✔
802

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

831
        for (const arg of args) {
6,558✔
832
          forcedArgs.push(yield* forceIt(arg, context))
8,140✔
833
        }
834

835
        result = fun.apply(thisContext, forcedArgs)
6,558✔
836
        break
6,519✔
837
      } catch (e) {
838
        // Recover from exception
839
        context.runtime.environments = context.runtime.environments.slice(
39✔
840
          -context.numberOfOuterEnvironments
841
        )
842

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