Skip to content

Commit 8f6d7b0

Browse files
committed
Feat: Add Error Handling during Runtime
1 parent c6de056 commit 8f6d7b0

File tree

3 files changed

+96
-47
lines changed

3 files changed

+96
-47
lines changed

src/tracer/__tests__/tracer_debug.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,14 @@ function h(f, x) {
107107
const output = steps.map(stringifyWithExplanation)
108108
console.log(output.join('\n\n'))
109109
})
110+
111+
112+
test('general', () => {
113+
const code = `
114+
3 % 0;
115+
`
116+
const program = parse(code, { ecmaVersion: 10 })!
117+
const steps = getSteps(convert(program), { stepLimit: 200 })
118+
const output = steps.map(stringifyWithExplanation)
119+
console.log(output.join('\n\n'))
120+
})

src/tracer/nodes/Expression/BinaryExpression.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,16 @@ export class StepperBinaryExpression implements BinaryExpression, StepperBaseNod
5959
}
6060
return ret
6161
} else if (left_type === 'number' && right_type === 'number') {
62+
if (this.operator === '/' && this.right.value === 0) {
63+
throw new Error('Division by zero');
64+
}
65+
66+
if (this.operator === '%' && this.right.value === 0) {
67+
throw new Error('Modulo by zero');
68+
}
69+
6270
const ret =
63-
['*', '+', '/', '-', '===', '!==', '<', '>', '<=', '>=', '%'].includes(this.operator as string) &&
64-
!(this.operator === '/' && this.right.value === 0)
71+
['*', '+', '/', '-', '===', '!==', '<', '>', '<=', '>=', '%'].includes(this.operator as string)
6572
if (ret) {
6673
redex.preRedex = [this]
6774
}

src/tracer/steppers.ts

Lines changed: 76 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,72 @@ export function getSteps(
1616
const node: StepperBaseNode = prelude(convert(inputNode))
1717
const steps: IStepperPropContents[] = []
1818
const limit = stepLimit === undefined ? 1000 : stepLimit % 2 === 0 ? stepLimit : stepLimit + 1
19+
let hasError = false;
20+
1921
let numSteps = 0
2022
function evaluate(node: StepperBaseNode): StepperBaseNode {
2123
numSteps += 1
2224
if (numSteps >= limit) {
2325
return node;
2426
}
25-
const isOneStepPossible = node.isOneStepPossible()
26-
if (isOneStepPossible) {
27-
const oldNode = node
28-
const newNode = node.oneStep()
29-
if (redex) {
30-
const explanations: string[] = redex.preRedex.map(explain)
31-
const beforeMarkers: Marker[] = redex.preRedex.map((redex, index) => ({
32-
redex: redex,
33-
redexType: 'beforeMarker',
34-
explanation: explanations[index]
35-
}))
36-
steps.push({
37-
ast: oldNode,
38-
markers: beforeMarkers
39-
})
40-
const afterMarkers: Marker[] =
41-
redex.postRedex.length > 0
42-
? redex.postRedex.map((redex, index) => ({
43-
redex: redex,
44-
redexType: 'afterMarker',
45-
explanation: explanations[index]
46-
}))
47-
: [
48-
{
27+
28+
try {
29+
const isOneStepPossible = node.isOneStepPossible()
30+
if (isOneStepPossible) {
31+
const oldNode = node
32+
let newNode: StepperBaseNode;
33+
newNode = node.oneStep()
34+
35+
if (redex) {
36+
const explanations: string[] = redex.preRedex.map(explain)
37+
const beforeMarkers: Marker[] = redex.preRedex.map((redex, index) => ({
38+
redex: redex,
39+
redexType: 'beforeMarker',
40+
explanation: explanations[index]
41+
}))
42+
steps.push({
43+
ast: oldNode,
44+
markers: beforeMarkers
45+
})
46+
const afterMarkers: Marker[] =
47+
redex.postRedex.length > 0
48+
? redex.postRedex.map((redex, index) => ({
49+
redex: redex,
4950
redexType: 'afterMarker',
50-
explanation: explanations[0] // use explanation based on preRedex
51-
}
52-
]
53-
steps.push({
54-
ast: newNode,
55-
markers: afterMarkers
56-
})
51+
explanation: explanations[index]
52+
}))
53+
: [
54+
{
55+
redexType: 'afterMarker',
56+
explanation: explanations[0] // use explanation based on preRedex
57+
}
58+
]
59+
steps.push({
60+
ast: newNode,
61+
markers: afterMarkers
62+
})
63+
}
64+
// reset
65+
redex.preRedex = []
66+
redex.postRedex = []
67+
return evaluate(newNode)
68+
} else {
69+
return node
5770
}
58-
// reset
59-
redex.preRedex = []
60-
redex.postRedex = []
61-
return evaluate(newNode)
62-
} else {
63-
return node
71+
} catch (error) {
72+
// Handle error during step evaluation
73+
hasError = true;
74+
steps.push({
75+
ast: node,
76+
markers: [{
77+
redexType: 'beforeMarker',
78+
explanation: error instanceof Error ? error.message : String(error)
79+
}]
80+
});
81+
return node;
6482
}
6583
}
84+
6685
// First node
6786
steps.push({
6887
ast: node,
@@ -88,13 +107,25 @@ export function getSteps(
88107
if (result.type === 'Program' && (result as StepperProgram).body.length === 0) {
89108
result = new StepperExpressionStatement(undefinedNode)
90109
}
91-
steps.push({
92-
ast: result,
93-
markers: [
94-
{
95-
explanation: 'Evaluation complete'
96-
}
97-
]
98-
})
110+
111+
if (!hasError) {
112+
steps.push({
113+
ast: result,
114+
markers: [
115+
{
116+
explanation: 'Evaluation complete'
117+
}
118+
]
119+
})
120+
} else {
121+
steps.push({
122+
ast: result,
123+
markers: [
124+
{
125+
explanation: 'Evaluation stuck'
126+
}
127+
],
128+
})
129+
}
99130
return steps
100131
}

0 commit comments

Comments
 (0)