diff --git a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs
index 64b44f83e3..9d3bbf8387 100644
--- a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs
+++ b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs
@@ -281,6 +281,16 @@ protected override object EvaluateInternal(EvaluationContext context)
var equal = left == right;
return equal ? JsBoolean.True : JsBoolean.False;
}
+
+ public override bool GetBooleanValue(EvaluationContext context)
+ {
+ if (!TryEvaluateOperands(context, out var left, out var right))
+ {
+ return false;
+ }
+
+ return left == right;
+ }
}
private sealed class StrictlyNotEqualBinaryExpression : JintBinaryExpression
@@ -298,6 +308,16 @@ protected override object EvaluateInternal(EvaluationContext context)
return left == right ? JsBoolean.False : JsBoolean.True;
}
+
+ public override bool GetBooleanValue(EvaluationContext context)
+ {
+ if (!TryEvaluateOperands(context, out var left, out var right))
+ {
+ return false;
+ }
+
+ return left != right;
+ }
}
private sealed class LessBinaryExpression : JintBinaryExpression
@@ -323,6 +343,23 @@ protected override object EvaluateInternal(EvaluationContext context)
return value._type == InternalTypes.Undefined ? JsBoolean.False : value;
}
+
+ public override bool GetBooleanValue(EvaluationContext context)
+ {
+ if (!TryEvaluateOperands(context, out var left, out var right))
+ {
+ return false;
+ }
+
+ if (context.OperatorOverloadingAllowed
+ && TryOperatorOverloading(context, left, right, "op_LessThan", out var opResult))
+ {
+ return TypeConverter.ToBoolean(JsValue.FromObject(context.Engine, opResult));
+ }
+
+ var value = Compare(left, right);
+ return value._type != InternalTypes.Undefined && ((JsBoolean) value)._value;
+ }
}
private sealed class GreaterBinaryExpression : JintBinaryExpression
@@ -348,6 +385,23 @@ protected override object EvaluateInternal(EvaluationContext context)
return value._type == InternalTypes.Undefined ? JsBoolean.False : value;
}
+
+ public override bool GetBooleanValue(EvaluationContext context)
+ {
+ if (!TryEvaluateOperands(context, out var left, out var right))
+ {
+ return false;
+ }
+
+ if (context.OperatorOverloadingAllowed
+ && TryOperatorOverloading(context, left, right, "op_GreaterThan", out var opResult))
+ {
+ return TypeConverter.ToBoolean(JsValue.FromObject(context.Engine, opResult));
+ }
+
+ var value = Compare(right, left, false);
+ return value._type != InternalTypes.Undefined && ((JsBoolean) value)._value;
+ }
}
private sealed class PlusBinaryExpression : JintBinaryExpression
@@ -374,6 +428,11 @@ protected override object EvaluateInternal(EvaluationContext context)
return JsNumber.Create((long) left.AsInteger() + right.AsInteger());
}
+ if (left._type == InternalTypes.Number && right._type == InternalTypes.Number)
+ {
+ return JsNumber.Create(((JsNumber) left)._value + ((JsNumber) right)._value);
+ }
+
var lprim = TypeConverter.ToPrimitive(left);
var rprim = TypeConverter.ToPrimitive(right);
JsValue result;
@@ -498,15 +557,21 @@ protected override object EvaluateInternal(EvaluationContext context)
return JsValue.FromObject(context.Engine, opResult);
}
- JsValue number;
- left = TypeConverter.ToNumeric(left);
- right = TypeConverter.ToNumeric(right);
-
if (AreIntegerOperands(left, right))
{
- number = JsNumber.Create((long) left.AsInteger() - right.AsInteger());
+ return JsNumber.Create((long) left.AsInteger() - right.AsInteger());
}
- else if (AreNonBigIntOperands(left, right))
+
+ if (left._type == InternalTypes.Number && right._type == InternalTypes.Number)
+ {
+ return JsNumber.Create(((JsNumber) left)._value - ((JsNumber) right)._value);
+ }
+
+ left = TypeConverter.ToNumeric(left);
+ right = TypeConverter.ToNumeric(right);
+
+ JsValue number;
+ if (AreNonBigIntOperands(left, right))
{
number = JsNumber.Create(left.AsNumber() - right.AsNumber());
}
@@ -543,6 +608,10 @@ protected override object EvaluateInternal(EvaluationContext context)
{
result = JsNumber.Create((long) left.AsInteger() * right.AsInteger());
}
+ else if (left._type == InternalTypes.Number && right._type == InternalTypes.Number)
+ {
+ result = JsNumber.Create(((JsNumber) left)._value * ((JsNumber) right)._value);
+ }
else
{
var leftNumeric = TypeConverter.ToNumeric(left);
@@ -582,6 +651,11 @@ protected override object EvaluateInternal(EvaluationContext context)
return JsValue.FromObject(context.Engine, opResult);
}
+ if (left._type == InternalTypes.Number && right._type == InternalTypes.Number)
+ {
+ return JsNumber.Create(((JsNumber) left)._value / ((JsNumber) right)._value);
+ }
+
left = TypeConverter.ToNumeric(left);
right = TypeConverter.ToNumeric(right);
return Divide(context, left, right);
@@ -617,6 +691,26 @@ protected override object EvaluateInternal(EvaluationContext context)
return equality == !_invert ? JsBoolean.True : JsBoolean.False;
}
+
+ public override bool GetBooleanValue(EvaluationContext context)
+ {
+ if (!TryEvaluateOperands(context, out var left, out var right))
+ {
+ return false;
+ }
+
+ if (context.OperatorOverloadingAllowed
+ && TryOperatorOverloading(context, left, right, _invert ? "op_Inequality" : "op_Equality", out var opResult))
+ {
+ return TypeConverter.ToBoolean(JsValue.FromObject(context.Engine, opResult));
+ }
+
+ var equality = left.Type == right.Type
+ ? left.Equals(right)
+ : left.IsLooselyEqual(right);
+
+ return _invert ? !equality : equality;
+ }
}
private sealed class CompareBinaryExpression : JintBinaryExpression
@@ -647,6 +741,26 @@ protected override object EvaluateInternal(EvaluationContext context)
var value = Compare(left, right, _leftFirst);
return value.IsUndefined() || ((JsBoolean) value)._value ? JsBoolean.False : JsBoolean.True;
}
+
+ public override bool GetBooleanValue(EvaluationContext context)
+ {
+ if (!TryEvaluateOperands(context, out var leftValue, out var rightValue))
+ {
+ return false;
+ }
+
+ if (context.OperatorOverloadingAllowed
+ && TryOperatorOverloading(context, leftValue, rightValue, _leftFirst ? "op_GreaterThanOrEqual" : "op_LessThanOrEqual", out var opResult))
+ {
+ return TypeConverter.ToBoolean(JsValue.FromObject(context.Engine, opResult));
+ }
+
+ var left = _leftFirst ? leftValue : rightValue;
+ var right = _leftFirst ? rightValue : leftValue;
+
+ var value = Compare(left, right, _leftFirst);
+ return !value.IsUndefined() && !((JsBoolean) value)._value;
+ }
}
private sealed class InstanceOfBinaryExpression : JintBinaryExpression
diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs
index c1f31998c0..6f2228b3b5 100644
--- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs
+++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs
@@ -54,6 +54,15 @@ internal object EvaluateWithoutNodeTracking(EvaluationContext context)
protected abstract object EvaluateInternal(EvaluationContext context);
+ ///
+ /// Resolves this expression as a boolean value.
+ /// Comparison expressions override this to avoid creating a JsBoolean wrapper.
+ ///
+ public virtual bool GetBooleanValue(EvaluationContext context)
+ {
+ return TypeConverter.ToBoolean(GetValue(context));
+ }
+
///
/// If we'd get Esprima source, we would just refer to it, but this makes error messages easier to decipher.
///
@@ -147,8 +156,6 @@ protected internal static JintExpression Build(Expression expression)
protected static JsValue Remainder(EvaluationContext context, JsValue left, JsValue right)
{
var result = JsValue.Undefined;
- left = TypeConverter.ToNumeric(left);
- right = TypeConverter.ToNumeric(right);
if (AreIntegerOperands(left, right))
{
var leftInteger = left.AsInteger();
@@ -171,50 +178,56 @@ protected static JsValue Remainder(EvaluationContext context, JsValue left, JsVa
}
}
}
- else if (JintBinaryExpression.AreNonBigIntOperands(left, right))
+ else
{
- var n = left.AsNumber();
- var d = right.AsNumber();
+ left = TypeConverter.ToNumeric(left);
+ right = TypeConverter.ToNumeric(right);
- if (double.IsNaN(n) || double.IsNaN(d) || double.IsInfinity(n))
- {
- result = JsNumber.DoubleNaN;
- }
- else if (double.IsInfinity(d))
- {
- result = n;
- }
- else if (NumberInstance.IsPositiveZero(d) || NumberInstance.IsNegativeZero(d))
- {
- result = JsNumber.DoubleNaN;
- }
- else if (NumberInstance.IsPositiveZero(n) || NumberInstance.IsNegativeZero(n))
+ if (JintBinaryExpression.AreNonBigIntOperands(left, right))
{
- result = n;
+ var n = left.AsNumber();
+ var d = right.AsNumber();
+
+ if (double.IsNaN(n) || double.IsNaN(d) || double.IsInfinity(n))
+ {
+ result = JsNumber.DoubleNaN;
+ }
+ else if (double.IsInfinity(d))
+ {
+ result = n;
+ }
+ else if (NumberInstance.IsPositiveZero(d) || NumberInstance.IsNegativeZero(d))
+ {
+ result = JsNumber.DoubleNaN;
+ }
+ else if (NumberInstance.IsPositiveZero(n) || NumberInstance.IsNegativeZero(n))
+ {
+ result = n;
+ }
+ else
+ {
+ result = JsNumber.Create(n % d);
+ }
}
else
{
- result = JsNumber.Create(n % d);
- }
- }
- else
- {
- JintBinaryExpression.AssertValidBigIntArithmeticOperands(left, right);
+ JintBinaryExpression.AssertValidBigIntArithmeticOperands(left, right);
- var n = TypeConverter.ToBigInt(left);
- var d = TypeConverter.ToBigInt(right);
+ var bn = TypeConverter.ToBigInt(left);
+ var bd = TypeConverter.ToBigInt(right);
- if (d == 0)
- {
- Throw.RangeError(context.Engine.Realm, "Division by zero");
- }
- else if (n == 0)
- {
- result = JsBigInt.Zero;
- }
- else
- {
- result = JsBigInt.Create(n % d);
+ if (bd == 0)
+ {
+ Throw.RangeError(context.Engine.Realm, "Division by zero");
+ }
+ else if (bn == 0)
+ {
+ result = JsBigInt.Zero;
+ }
+ else
+ {
+ result = JsBigInt.Create(bn % bd);
+ }
}
}
@@ -332,29 +345,29 @@ protected static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true) =>
private static JsValue CompareNumber(JsValue x, JsValue y, bool leftFirst)
{
+ if (x.IsInteger() && y.IsInteger())
+ {
+ return x.AsInteger() < y.AsInteger() ? JsBoolean.True : JsBoolean.False;
+ }
+
double nx, ny;
if (leftFirst)
{
- nx = x.AsNumber();
- ny = y.AsNumber();
+ nx = ((JsNumber) x)._value;
+ ny = ((JsNumber) y)._value;
}
else
{
- ny = y.AsNumber();
- nx = x.AsNumber();
+ ny = ((JsNumber) y)._value;
+ nx = ((JsNumber) x)._value;
}
- if (x.IsInteger() && y.IsInteger())
+ if (double.IsNaN(nx) || double.IsNaN(ny))
{
- return (int) nx < (int) ny ? JsBoolean.True : JsBoolean.False;
- }
-
- if (!double.IsInfinity(nx) && !double.IsInfinity(ny) && !double.IsNaN(nx) && !double.IsNaN(ny))
- {
- return nx < ny ? JsBoolean.True : JsBoolean.False;
+ return JsValue.Undefined;
}
- return CompareComplex(x, y, leftFirst);
+ return nx < ny ? JsBoolean.True : JsBoolean.False;
}
private static JsValue CompareComplex(JsValue x, JsValue y, bool leftFirst)
diff --git a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs
index 0f82c93795..d06c594b2e 100644
--- a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs
+++ b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs
@@ -261,16 +261,14 @@ private Completion ForBodyEvaluation(EvaluationContext context)
{
debugHandler?.OnStep(_test._expression);
- var testValue = _test.GetValue(context);
-
- // Check for async suspension in test expression
- if (context.IsSuspended())
+ if (!_test.GetBooleanValue(context))
{
- return new Completion(CompletionType.Return, JsValue.Undefined, ((JintStatement) this)._statement);
- }
+ // Check for async suspension in test expression
+ if (context.IsSuspended())
+ {
+ return new Completion(CompletionType.Return, JsValue.Undefined, ((JintStatement) this)._statement);
+ }
- if (!TypeConverter.ToBoolean(testValue))
- {
return new Completion(CompletionType.Normal, v, ((JintStatement) this)._statement);
}
}
diff --git a/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs
index fe9bff1cad..03b6079c2f 100644
--- a/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs
+++ b/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs
@@ -28,7 +28,7 @@ protected override void Initialize(EvaluationContext context)
protected override Completion ExecuteInternal(EvaluationContext context)
{
Completion result;
- if (TypeConverter.ToBoolean(_test.GetValue(context)))
+ if (_test.GetBooleanValue(context))
{
// B.3.2/B.3.3: IfStatement function declarations need runtime AnnexB handling
if (_consequentIsFunctionDecl && !StrictModeScope.IsStrictModeCode)
diff --git a/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs b/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs
index 5d98cbab47..d6f6c8a3db 100644
--- a/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs
+++ b/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs
@@ -44,18 +44,17 @@ protected override Completion ExecuteInternal(EvaluationContext context)
context.Engine.Debugger.OnStep(_test._expression);
}
- var jsValue = _test.GetValue(context);
-
- // Check for suspension after evaluating the test expression
- var suspendable = context.Engine.ExecutionContext.Suspendable;
- if (context.IsSuspended())
+ if (!_test.GetBooleanValue(context))
{
- var suspendedValue = suspendable?.SuspendedValue ?? JsValue.Undefined;
- return new Completion(CompletionType.Return, suspendedValue, _statement);
- }
+ // GetBooleanValue returns false for both actual false condition
+ // and suspended evaluation (async/generator); check which case
+ if (context.IsSuspended())
+ {
+ var suspendable = context.Engine.ExecutionContext.Suspendable;
+ var suspendedValue = suspendable?.SuspendedValue ?? JsValue.Undefined;
+ return new Completion(CompletionType.Return, suspendedValue, _statement);
+ }
- if (!TypeConverter.ToBoolean(jsValue))
- {
return new Completion(CompletionType.Normal, v, _statement);
}
@@ -69,7 +68,8 @@ protected override Completion ExecuteInternal(EvaluationContext context)
// Check for suspension - if suspended, we need to exit the loop
if (context.IsSuspended())
{
- var suspendedValue = suspendable?.SuspendedValue ?? completion.Value;
+ var bodySuspendable = context.Engine.ExecutionContext.Suspendable;
+ var suspendedValue = bodySuspendable?.SuspendedValue ?? completion.Value;
return new Completion(CompletionType.Return, suspendedValue, _statement);
}