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

source-academy / js-slang / 24834367427

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

Pull #1893

github

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

3126 of 4197 branches covered (74.48%)

Branch coverage included in aggregate %.

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

20 existing lines in 11 files now uncovered.

7056 of 8767 relevant lines covered (80.48%)

173930.4 hits per line

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

90.14
/src/stepper/steppers.ts
1
import type es from 'estree';
2
import { parseError, type Context, type IOptions } from '..';
3
import { UndefinedVariableError } from '../errors/errors';
4
import { checkProgramForUndefinedVariables } from '../validator/validator';
5
import { RuntimeSourceError } from '../errors/base';
6
import type { StepperProgram } from './nodes/Program';
7
import { prelude } from './builtins';
8
import { explain } from './generator';
9
import type { StepperBaseNode } from './interface';
10
import { undefinedNode } from './nodes';
11
import { StepperExpressionStatement } from './nodes/Statement/ExpressionStatement';
12
import type { IStepperPropContents, Marker, RedexInfo } from '.';
13

14
export function getSteps(
15
  inputNode: es.BaseNode,
16
  context: Context,
17
  { stepLimit }: Pick<IOptions, 'stepLimit'>,
18
): IStepperPropContents[] {
19
  const redex: RedexInfo = {
129✔
20
    preRedex: [],
21
    postRedex: [],
22
  };
23

24
  const node = prelude(inputNode, redex);
129✔
25
  const steps: IStepperPropContents[] = [];
129✔
26
  const limit = stepLimit === undefined ? 1000 : stepLimit % 2 === 0 ? stepLimit : stepLimit + 1;
129!
27
  let hasError = false;
129✔
28

29
  let numSteps = 1;
129✔
30
  function evaluate(node: StepperBaseNode): StepperBaseNode {
31
    try {
2,761✔
32
      if (node.isOneStepPossible(redex)) {
2,761✔
33
        const newNode = node.oneStep(redex);
2,639✔
34

35
        const explanations = redex.preRedex.map(explain);
2,639✔
36
        const beforeMarkers = redex.preRedex.map(
2,639✔
37
          (redex, index): Marker => ({
2,636✔
38
            redex,
39
            redexType: 'beforeMarker',
40
            explanation: explanations[index],
41
          }),
42
        );
43
        numSteps += 1;
2,639✔
44
        if (numSteps >= limit) {
2,639✔
45
          return node;
2✔
46
        }
47
        steps.push({
2,634✔
48
          ast: node,
49
          markers: beforeMarkers,
50
        });
51

52
        const afterMarkers: Marker[] =
53
          redex.postRedex.length > 0
2,634✔
54
            ? redex.postRedex.map((redex, index) => ({
2,669✔
55
                redex,
56
                redexType: 'afterMarker',
57
                explanation: explanations[index],
58
              }))
59
            : [
60
                {
61
                  redexType: 'afterMarker',
62
                  explanation: explanations[0], // use explanation based on preRedex
63
                },
64
              ];
65
        numSteps += 1;
2,639✔
66
        if (numSteps >= limit) {
2,639!
NEW
67
          return node;
×
68
        }
69
        steps.push({
2,634✔
70
          ast: newNode,
71
          markers: afterMarkers,
72
        });
73

74
        // reset
75
        redex.preRedex = [];
2,634✔
76
        redex.postRedex = [];
2,634✔
77
        return evaluate(newNode);
2,634✔
78
      } else {
79
        return node;
115✔
80
      }
81
    } catch (error) {
82
      if (!(error instanceof RuntimeSourceError)) {
10!
NEW
83
        throw error;
×
84
      }
85

86
      const errStr = parseError([error]);
10✔
87

88
      // Handle error during step evaluation
89
      hasError = true;
10✔
90
      steps.push({
10✔
91
        ast: node,
92
        markers: [
93
          {
94
            redexType: 'beforeMarker',
95
            explanation: errStr,
96
          },
97
        ],
98
      });
99
      return node;
10✔
100
    }
101
  }
102

103
  // First node
104
  steps.push({
129✔
105
    ast: node,
106
    markers: [
107
      {
108
        explanation: 'Start of evaluation',
109
      },
110
    ],
111
  });
112
  // check for undefined variables
113
  try {
129✔
114
    checkProgramForUndefinedVariables(inputNode as es.Program, context);
129✔
115
  } catch (error) {
116
    steps.push({
2✔
117
      ast: node,
118
      markers: [
119
        {
120
          redexType: 'beforeMarker',
121
          explanation:
122
            error instanceof UndefinedVariableError
2!
123
              ? `Line ${error.location.start.line}: Name ${error.varname} not declared.`
124
              : String(error),
125
        },
126
      ],
127
    });
128
    return steps;
2✔
129
  }
130

131
  let result = evaluate(node);
127✔
132
  // If program has not completed within the step limit, halt.
133
  if (numSteps >= limit) {
127✔
134
    steps.push({
2✔
135
      ast: result,
136
      markers: [
137
        {
138
          explanation: 'Maximum number of steps exceeded',
139
        },
140
      ],
141
    });
142
    return steps;
2✔
143
  }
144
  // If the program does not return anything, return undefined
145
  if (result.type === 'Program' && (result as StepperProgram).body.length === 0) {
125✔
146
    result = new StepperExpressionStatement(undefinedNode);
6✔
147
  }
148

149
  if (!hasError) {
125✔
150
    steps.push({
115✔
151
      ast: result,
152
      markers: [
153
        {
154
          explanation: 'Evaluation complete',
155
        },
156
      ],
157
    });
158
  } else {
159
    steps.push({
10✔
160
      ast: result,
161
      markers: [
162
        {
163
          explanation: 'Evaluation stuck',
164
        },
165
      ],
166
    });
167
  }
168
  return steps;
125✔
169
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc