Skip to content

Commit 24dcf50

Browse files
committed
Fix: Add Infinity and NaN support for BinaryExpressions
1 parent 8f6d7b0 commit 24dcf50

File tree

3 files changed

+102
-46
lines changed

3 files changed

+102
-46
lines changed

src/tracer/__tests__/tracer_debug.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,9 @@ function h(f, x) {
110110

111111

112112
test('general', () => {
113-
const code = `
114-
3 % 0;
113+
const code = `1 % 0;
115114
`
116-
const program = parse(code, { ecmaVersion: 10 })!
115+
const program = parse(code, { ecmaVersion: 10, locations: true })!
117116
const steps = getSteps(convert(program), { stepLimit: 200 })
118117
const output = steps.map(stringifyWithExplanation)
119118
console.log(output.join('\n\n'))

src/tracer/nodes/Expression/BinaryExpression.ts

+88-41
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { redex } from '../..'
44
import { StepperLiteral } from './Literal'
55
import { StepperExpression, StepperPattern } from '..'
66
import { convert } from '../../generator'
7+
import { StepperIdentifier } from './Identifier'
78
export class StepperBinaryExpression implements BinaryExpression, StepperBaseNode {
89
type: 'BinaryExpression'
910
operator: BinaryOperator
@@ -46,27 +47,32 @@ export class StepperBinaryExpression implements BinaryExpression, StepperBaseNod
4647
}
4748

4849
isContractible(): boolean {
49-
if (this.left.type !== 'Literal' || this.right.type !== 'Literal') {
50+
// Check if both operands are literals or special numeric values (NaN, Infinity)
51+
const isLeftLiteral = this.left.type === 'Literal' ||
52+
(this.left.type === 'Identifier' && ['NaN', 'Infinity'].includes(this.left.name));
53+
const isRightLiteral = this.right.type === 'Literal' ||
54+
(this.right.type === 'Identifier' && ['NaN', 'Infinity'].includes(this.right.name));
55+
56+
if (!isLeftLiteral || !isRightLiteral) {
5057
return false;
5158
}
52-
const left_type = typeof this.left.value
53-
const right_type = typeof this.right.value
54-
59+
60+
// Get values, handling special cases
61+
const leftValue = this.left.type === 'Literal' ? this.left.value :
62+
(this.left.type === 'Identifier' && this.left.name === 'NaN' ? NaN : Infinity);
63+
const rightValue = this.right.type === 'Literal' ? this.right.value :
64+
(this.right.type === 'Identifier' && this.right.name === 'NaN' ? NaN : Infinity);
65+
66+
const left_type = typeof leftValue;
67+
const right_type = typeof rightValue;
68+
5569
if (left_type === 'boolean' && right_type === 'boolean') {
5670
const ret = (this.operator as string) === '&&' || (this.operator as string) === '||'
5771
if (ret) {
5872
redex.preRedex = [this]
5973
}
6074
return ret
6175
} 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-
7076
const ret =
7177
['*', '+', '/', '-', '===', '!==', '<', '>', '<=', '>=', '%'].includes(this.operator as string)
7278
if (ret) {
@@ -84,57 +90,91 @@ export class StepperBinaryExpression implements BinaryExpression, StepperBaseNod
8490

8591
contract(): StepperExpression {
8692
redex.preRedex = [this]
87-
if (this.left.type !== 'Literal' || this.right.type !== 'Literal') throw new Error()
88-
89-
const left = this.left.value
90-
const right = this.right.value
93+
94+
// Get values, handling special cases
95+
const leftValue = this.left.type === 'Literal' ? this.left.value :
96+
(this.left.type === 'Identifier' && this.left.name === 'NaN' ? NaN : Infinity);
97+
const rightValue = this.right.type === 'Literal' ? this.right.value :
98+
(this.right.type === 'Identifier' && this.right.name === 'NaN' ? NaN : Infinity);
9199

92100
const op = this.operator as string
93101

94102
const value =
95103
(this.operator as string) === '&&'
96-
? left && right
104+
? leftValue && rightValue
97105
: op === '||'
98-
? left || right
99-
: op === '+' && typeof left === 'number' && typeof right === 'number'
100-
? (left as number) + (right as number)
101-
: op === '+' && typeof left === 'string' && typeof right === 'string'
102-
? (left as string) + (right as string)
106+
? leftValue || rightValue
107+
: op === '+' && typeof leftValue === 'number' && typeof rightValue === 'number'
108+
? (leftValue as number) + (rightValue as number)
109+
: op === '+' && typeof leftValue === 'string' && typeof rightValue === 'string'
110+
? (leftValue as string) + (rightValue as string)
103111
: op === '-'
104-
? (left as number) - (right as number)
112+
? (leftValue as number) - (rightValue as number)
105113
: op === '*'
106-
? (left as number) * (right as number)
114+
? (leftValue as number) * (rightValue as number)
107115
: op === '%'
108-
? (left as number) % (right as number)
116+
? (leftValue as number) % (rightValue as number)
109117
: op === '/'
110-
? (left as number) / (right as number)
118+
? (leftValue as number) / (rightValue as number)
111119
: op === '==='
112-
? left === right
120+
? leftValue === rightValue
113121
: op === '!=='
114-
? left !== right
122+
? leftValue !== rightValue
115123
: op === '<'
116-
? left! < right!
124+
? leftValue! < rightValue!
117125
: op === '<='
118-
? left! <= right!
126+
? leftValue! <= rightValue!
119127
: op === '>='
120-
? left! >= right!
121-
: left! > right!
122-
123-
let ret = new StepperLiteral(value)
124-
redex.postRedex = [ret]
125-
return ret
128+
? leftValue! >= rightValue!
129+
: leftValue! > rightValue!
130+
131+
132+
let ret: StepperExpression;
133+
if (Number.isNaN(value)) {
134+
ret = new StepperIdentifier('NaN');
135+
} else if (!Number.isFinite(value) && typeof value === 'number') {
136+
ret = new StepperIdentifier('Infinity');
137+
} else {
138+
ret = new StepperLiteral(value, undefined, undefined, undefined, this.loc, this.range);
139+
}
140+
redex.postRedex = [ret];
141+
return ret;
126142
}
127143

128144
oneStep(): StepperExpression {
129145
return this.isContractible()
130146
? this.contract()
131147
: this.left.isOneStepPossible()
132-
? new StepperBinaryExpression(this.operator, this.left.oneStep(), this.right)
133-
: new StepperBinaryExpression(this.operator, this.left, this.right.oneStep())
148+
? new StepperBinaryExpression(
149+
this.operator,
150+
this.left.oneStep(),
151+
this.right,
152+
this.leadingComments,
153+
this.trailingComments,
154+
this.loc,
155+
this.range
156+
)
157+
: new StepperBinaryExpression(
158+
this.operator,
159+
this.left,
160+
this.right.oneStep(),
161+
this.leadingComments,
162+
this.trailingComments,
163+
this.loc,
164+
this.range
165+
)
134166
}
135167

136168
substitute(id: StepperPattern, value: StepperExpression): StepperExpression {
137-
return new StepperBinaryExpression(this.operator, this.left.substitute(id, value), this.right.substitute(id, value))
169+
return new StepperBinaryExpression(
170+
this.operator,
171+
this.left.substitute(id, value),
172+
this.right.substitute(id, value),
173+
this.leadingComments,
174+
this.trailingComments,
175+
this.loc,
176+
this.range
177+
)
138178
}
139179

140180
freeNames(): string[] {
@@ -146,7 +186,14 @@ export class StepperBinaryExpression implements BinaryExpression, StepperBaseNod
146186
}
147187

148188
rename(before: string, after: string): StepperExpression {
149-
return new StepperBinaryExpression(this.operator,
150-
this.left.rename(before, after), this.right.rename(before, after));
189+
return new StepperBinaryExpression(
190+
this.operator,
191+
this.left.rename(before, after),
192+
this.right.rename(before, after),
193+
this.leadingComments,
194+
this.trailingComments,
195+
this.loc,
196+
this.range
197+
);
151198
}
152199
}

src/tracer/nodes/Program.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,12 @@ export class StepperProgram implements Program, StepperBaseNode {
149149

150150
substitute(id: StepperPattern, value: StepperExpression): StepperBaseNode {
151151
return new StepperProgram(
152-
this.body.map(statement => statement.substitute(id, value) as StepperStatement)
152+
this.body.map(statement => statement.substitute(id, value) as StepperStatement),
153+
this.comments,
154+
this.leadingComments,
155+
this.trailingComments,
156+
this.loc,
157+
this.range
153158
)
154159
}
155160

@@ -177,7 +182,12 @@ export class StepperProgram implements Program, StepperBaseNode {
177182

178183
rename(before: string, after: string): StepperProgram {
179184
return new StepperProgram(
180-
this.body.map(statement => statement.rename(before, after) as StepperStatement)
185+
this.body.map(statement => statement.rename(before, after) as StepperStatement),
186+
this.comments,
187+
this.leadingComments,
188+
this.trailingComments,
189+
this.loc,
190+
this.range
181191
)
182192
}
183193
}

0 commit comments

Comments
 (0)