diff --git a/Source/Parser/Expressions/IntegerConstantExpression.cs b/Source/Parser/Expressions/IntegerConstantExpression.cs
index 0af38c40..08eff611 100644
--- a/Source/Parser/Expressions/IntegerConstantExpression.cs
+++ b/Source/Parser/Expressions/IntegerConstantExpression.cs
@@ -44,7 +44,7 @@ public bool IsZero
///
/// Returns true if the constant is numerically negative
///
- public bool IsNegative
+ public virtual bool IsNegative
{
get { return Value < 0; }
}
@@ -52,7 +52,7 @@ public bool IsNegative
///
/// Returns true if the constant is numerically positive
///
- public bool IsPositive
+ public virtual bool IsPositive
{
get { return Value > 0; }
}
@@ -91,36 +91,52 @@ public ExpressionBase Combine(ExpressionBase right, MathematicOperation operatio
var integerExpression = right as IntegerConstantExpression;
if (integerExpression != null)
{
+ var newValue = 0;
switch (operation)
{
case MathematicOperation.Add:
- return new IntegerConstantExpression(Value + integerExpression.Value);
+ newValue = Value + integerExpression.Value;
+ break;
case MathematicOperation.Subtract:
- return new IntegerConstantExpression(Value - integerExpression.Value);
+ newValue = Value - integerExpression.Value;
+ break;
case MathematicOperation.Multiply:
- return new IntegerConstantExpression(Value * integerExpression.Value);
+ newValue = Value * integerExpression.Value;
+ break;
case MathematicOperation.Divide:
if (integerExpression.Value == 0)
return new ErrorExpression("Division by zero");
- return new IntegerConstantExpression(Value / integerExpression.Value);
+ newValue = Value / integerExpression.Value;
+ break;
case MathematicOperation.Modulus:
if (integerExpression.Value == 0)
return new ErrorExpression("Division by zero");
- return new IntegerConstantExpression(Value % integerExpression.Value);
+ newValue = Value % integerExpression.Value;
+ break;
case MathematicOperation.BitwiseAnd:
- return new IntegerConstantExpression(Value & integerExpression.Value);
+ newValue = Value & integerExpression.Value;
+ break;
case MathematicOperation.BitwiseXor:
- return new IntegerConstantExpression(Value ^ integerExpression.Value);
+ newValue = Value ^ integerExpression.Value;
+ break;
default:
break;
}
+
+ if (right is UnsignedIntegerConstantExpression ||
+ this is UnsignedIntegerConstantExpression)
+ {
+ return new UnsignedIntegerConstantExpression((uint)newValue);
+ }
+
+ return new IntegerConstantExpression(newValue);
}
if (right is FloatConstantExpression)
@@ -182,4 +198,37 @@ public ExpressionBase NormalizeComparison(ExpressionBase right, ComparisonOperat
return null;
}
}
+
+ internal class UnsignedIntegerConstantExpression : IntegerConstantExpression
+ {
+ public UnsignedIntegerConstantExpression(uint value)
+ : base((int)value)
+ {
+ }
+
+ ///
+ /// Returns true if the constant is numerically negative
+ ///
+ public override bool IsNegative
+ {
+ get { return false; }
+ }
+
+ ///
+ /// Returns true if the constant is numerically positive
+ ///
+ public override bool IsPositive
+ {
+ get { return Value != 0; }
+ }
+
+ ///
+ /// Appends the textual representation of this expression to .
+ ///
+ internal override void AppendString(StringBuilder builder)
+ {
+ builder.Append((uint)Value);
+ builder.Append('U');
+ }
+ }
}
diff --git a/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs b/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs
index 7a9cd505..d4f2305a 100644
--- a/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs
+++ b/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs
@@ -548,7 +548,7 @@ private ExpressionBase EnsureSingleExpressionOnRightHandSide(ExpressionBase righ
return new ComparisonExpression(cloneLeft, operation, constant);
}
- private MemoryValueExpression InvertAndMigrateAccessorsTo(ExpressionBase target)
+ internal MemoryValueExpression InvertAndMigrateAccessorsTo(ExpressionBase target)
{
var value = target as MemoryValueExpression;
if (value == null)
@@ -1004,6 +1004,7 @@ private static ExpressionBase ApplyUnderflowAdjustment(ComparisonExpression comp
{
Debug.Assert(comparison.Left is MemoryValueExpression);
var leftMemoryValue = (MemoryValueExpression)comparison.Left;
+ IntegerConstantExpression integerConstant;
var newLeft = leftMemoryValue.Clone();
newLeft.IntegerConstant += underflowAdjustment;
@@ -1036,7 +1037,7 @@ private static ExpressionBase ApplyUnderflowAdjustment(ComparisonExpression comp
else if (underflowAdjustment < 0)
{
// attempting to clamp adjustment, see if we can clamp it even more
- var integerConstant = newRight as IntegerConstantExpression;
+ integerConstant = newRight as IntegerConstantExpression;
if (integerConstant != null)
{
var newConstant = integerConstant.Value - newLeft.IntegerConstant;
@@ -1049,6 +1050,12 @@ private static ExpressionBase ApplyUnderflowAdjustment(ComparisonExpression comp
}
}
+ // if the resulting underflow comparison target is negative, explicitly make it positive
+ // to prevent further underflow calculations.
+ integerConstant = newRight as IntegerConstantExpression;
+ if (integerConstant != null && integerConstant.IsNegative)
+ newRight = new UnsignedIntegerConstantExpression((uint)integerConstant.Value);
+
return new ComparisonExpression(newLeft, operation, newRight);
}
@@ -1063,6 +1070,9 @@ private int GetUnderflowAdjustment(ExpressionBase right)
switch (right.Type)
{
case ExpressionType.IntegerConstant:
+ if (right is UnsignedIntegerConstantExpression)
+ return 0; // explicit unsigned in comparison, do not adjust for underflow
+
integerOffset = ((IntegerConstantExpression)right).Value;
break;
diff --git a/Source/Parser/Expressions/Trigger/ModifiedMemoryAccessorExpression.cs b/Source/Parser/Expressions/Trigger/ModifiedMemoryAccessorExpression.cs
index 3e568300..10a4f7db 100644
--- a/Source/Parser/Expressions/Trigger/ModifiedMemoryAccessorExpression.cs
+++ b/Source/Parser/Expressions/Trigger/ModifiedMemoryAccessorExpression.cs
@@ -700,7 +700,50 @@ public ExpressionBase NormalizeComparison(ExpressionBase right, ComparisonOperat
result = newRight.ApplyMathematic(modifier, opposingOperator);
if (result is MathematicExpression)
- return new ErrorExpression("Result can never be true using integer math");
+ {
+ var zero = new UnsignedIntegerConstantExpression(0U);
+ var negative = new UnsignedIntegerConstantExpression(0x80000000U);
+
+ // left and right are both modified memory expressions. use subsource and check the negative bit
+ var memoryValue = new MemoryValueExpression(this);
+ memoryValue.ApplyMathematic(modifiedMemoryAccessor.Clone(), MathematicOperation.Subtract);
+ switch (operation)
+ {
+ case ComparisonOperation.Equal:
+ case ComparisonOperation.NotEqual:
+ // A * 3 == B * 4 => A * 3 - B * 4 == 0 => A * 3 - B * 4 == 0
+ // A * 3 != B * 4 => A * 3 - B * 4 != 0 => A * 3 - B * 4 != 0
+ return new ComparisonExpression(memoryValue, operation, zero);
+
+ case ComparisonOperation.LessThan:
+ case ComparisonOperation.GreaterThanOrEqual:
+ // A * 3 < B * 4 => A * 3 - B * 4 < 0 => A * 3 - B * 4 >= 0x80000000
+ // A * 3 >= B * 4 => A * 3 - B * 4 >= 0 => A * 3 - B * 4 < 0x80000000
+ var newOperation = ComparisonExpression.GetOppositeComparisonOperation(operation);
+ return new ComparisonExpression(memoryValue, newOperation, negative);
+
+ case ComparisonOperation.LessThanOrEqual:
+ // A * 3 <= B * 4 => A * 3 - B * 4 <= 0 => A * 3 - B * 4 >= 0x80000000 || == 0
+ var negativeComparison = new RequirementConditionExpression() { Left = memoryValue, Comparison = ComparisonOperation.GreaterThanOrEqual, Right = negative };
+ var zeroComparison = new RequirementConditionExpression() { Left = memoryValue, Comparison = ComparisonOperation.Equal, Right = zero };
+ var lessThanOrEqualClause = new RequirementClauseExpression() { Operation = ConditionalOperation.Or };
+ lessThanOrEqualClause.AddCondition(negativeComparison);
+ lessThanOrEqualClause.AddCondition(zeroComparison);
+ return lessThanOrEqualClause;
+
+ case ComparisonOperation.GreaterThan:
+ // A * 3 > B * 4 => A * 3 - B * 4 > 0 => A * 3 - B * 4 < 0x80000000 && != 0
+ var positiveComparison = new RequirementConditionExpression() { Left = memoryValue, Comparison = ComparisonOperation.LessThan, Right = negative };
+ var nonZeroComparison = new RequirementConditionExpression() { Left = memoryValue, Comparison = ComparisonOperation.NotEqual, Right = zero };
+ var greaterThanClause = new RequirementClauseExpression() { Operation = ConditionalOperation.And };
+ greaterThanClause.AddCondition(positiveComparison);
+ greaterThanClause.AddCondition(nonZeroComparison);
+ return greaterThanClause;
+
+ default:
+ return new ErrorExpression("Result can never be true using integer math");
+ }
+ }
// swap so modifier is on left
newRight = newLeft;
diff --git a/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs b/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs
index fe11f6a5..42fcc684 100644
--- a/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs
+++ b/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs
@@ -57,7 +57,7 @@ internal override void AppendString(StringBuilder builder)
// special handling: comparisons use unsigned values
var rightInteger = Right as IntegerConstantExpression;
- if (rightInteger != null && rightInteger.Value < 0)
+ if (rightInteger != null && rightInteger.IsNegative)
builder.Append((uint)rightInteger.Value);
else
Right.AppendString(builder);
@@ -419,19 +419,26 @@ private static void NormalizeLimits(ref RequirementExpressionBase expression)
}
else if (value > max)
{
- switch (condition.Comparison)
+ if (value > Int32.MaxValue && min < 0)
{
- case ComparisonOperation.GreaterThan:
- case ComparisonOperation.GreaterThanOrEqual:
- case ComparisonOperation.Equal:
- expression = new AlwaysFalseExpression();
- return;
+ // value is implicitly negative, ignore it.
+ }
+ else
+ {
+ switch (condition.Comparison)
+ {
+ case ComparisonOperation.GreaterThan:
+ case ComparisonOperation.GreaterThanOrEqual:
+ case ComparisonOperation.Equal:
+ expression = new AlwaysFalseExpression();
+ return;
- case ComparisonOperation.LessThan:
- case ComparisonOperation.LessThanOrEqual:
- case ComparisonOperation.NotEqual:
- expression = new AlwaysTrueExpression();
- return;
+ case ComparisonOperation.LessThan:
+ case ComparisonOperation.LessThanOrEqual:
+ case ComparisonOperation.NotEqual:
+ expression = new AlwaysTrueExpression();
+ return;
+ }
}
}
else if (value == max)
@@ -504,6 +511,26 @@ public ExpressionBase Normalize()
return reversed.Normalize();
}
+ var integerRight = Right as IntegerConstantExpression;
+ if (integerRight != null && integerRight.IsNegative && integerRight.Value > -100000)
+ {
+ var memoryValue = Left as MemoryValueExpression;
+ if (memoryValue != null && memoryValue.MemoryAccessors.Any(a => a.CombiningOperator == RequirementType.SubSource))
+ {
+ // A - B < -2 => B - A > 2
+ var newMemoryValue = new MemoryValueExpression();
+ newMemoryValue = memoryValue.InvertAndMigrateAccessorsTo(newMemoryValue);
+ var integerConstant = new IntegerConstantExpression(-integerRight.Value);
+ var normalized = new RequirementConditionExpression
+ {
+ Left = newMemoryValue,
+ Comparison = ComparisonExpression.ReverseComparisonOperation(Comparison),
+ Right = integerConstant
+ };
+ return normalized.Normalize();
+ }
+ }
+
var modifiedMemoryAccessor = Left as ModifiedMemoryAccessorExpression;
if (modifiedMemoryAccessor != null && modifiedMemoryAccessor.ModifyingOperator != RequirementOperator.None)
{
diff --git a/Source/Parser/Internal/ExpressionBase.cs b/Source/Parser/Internal/ExpressionBase.cs
index c005d426..bb840d3b 100644
--- a/Source/Parser/Internal/ExpressionBase.cs
+++ b/Source/Parser/Internal/ExpressionBase.cs
@@ -480,8 +480,15 @@ private static ExpressionBase ParseNumber(PositionalTokenizer tokenizer, bool is
return new ErrorExpression("Number too large");
}
- if (value > Int32.MaxValue && !isUnsigned)
- return new ErrorExpression("Number too large");
+ if (value > Int32.MaxValue)
+ {
+ if (!isUnsigned)
+ return new ErrorExpression("Number too large");
+
+ var unsignedIntegerExpression = new UnsignedIntegerConstantExpression(value);
+ unsignedIntegerExpression.Location = new TextRange(line, column, endLine, endColumn);
+ return unsignedIntegerExpression;
+ }
var integerExpression = new IntegerConstantExpression((int)value);
integerExpression.Location = new TextRange(line, column, endLine, endColumn);
diff --git a/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs b/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs
index d1250ac9..fdcab6fa 100644
--- a/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs
+++ b/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs
@@ -152,8 +152,6 @@ public void TestCombine(string left, string operation, string right, ExpressionT
ExpressionType.Comparison, "float(0x001234) != 3.363636")]
[TestCase("byte(0x001234) * 10", "=", "byte(0x002345) * 5",
ExpressionType.Comparison, "byte(0x001234) * 2 == byte(0x002345)")]
- [TestCase("byte(0x001234) * 10", "=", "byte(0x002345) * 3",
- ExpressionType.Error, "Result can never be true using integer math")]
[TestCase("byte(0x001234) * 10", "=", "byte(0x002345) / 3",
ExpressionType.Comparison, "byte(0x001234) * 30 == byte(0x002345)")]
[TestCase("byte(0x001234) / 10", "=", "byte(0x002345) * 3",
@@ -178,6 +176,18 @@ public void TestCombine(string left, string operation, string right, ExpressionT
ExpressionType.None, null)] // division cannot be moved; multiplication cannot be extracted
[TestCase("byte(0x001234) / byte(0x002345) * 10", ">", "80",
ExpressionType.Comparison, "byte(0x001234) / byte(0x002345) > 8")]
+ [TestCase("byte(0x001234) * 10", "=", "byte(0x002345) * 8",
+ ExpressionType.Comparison, "byte(0x001234) * 10 - byte(0x002345) * 8 == 0U")] // comparison of two modified memory accesors results in subsource chain
+ [TestCase("byte(0x001234) * 10", "!=", "byte(0x002345) * 8",
+ ExpressionType.Comparison, "byte(0x001234) * 10 - byte(0x002345) * 8 != 0U")] // comparison of two modified memory accesors results in subsource chain
+ [TestCase("byte(0x001234) * 10", "<", "byte(0x002345) * 8",
+ ExpressionType.Comparison, "byte(0x001234) * 10 - byte(0x002345) * 8 >= 2147483648U")] // comparison of two modified memory accesors results in subsource chain
+ [TestCase("byte(0x001234) * 10", "<=", "byte(0x002345) * 8",
+ ExpressionType.Requirement, "byte(0x001234) * 10 - byte(0x002345) * 8 >= 2147483648U || byte(0x001234) * 10 - byte(0x002345) * 8 == 0U")] // comparison of two modified memory accesors results in subsource chain
+ [TestCase("byte(0x001234) * 10", ">", "byte(0x002345) * 8",
+ ExpressionType.Requirement, "byte(0x001234) * 10 - byte(0x002345) * 8 < 2147483648U && byte(0x001234) * 10 - byte(0x002345) * 8 != 0U")] // comparison of two modified memory accesors results in subsource chain
+ [TestCase("byte(0x001234) * 10", ">=", "byte(0x002345) * 8",
+ ExpressionType.Comparison, "byte(0x001234) * 10 - byte(0x002345) * 8 < 2147483648U")] // comparison of two modified memory accesors results in subsource chain
public void TestNormalizeComparison(string left, string operation, string right, ExpressionType expectedType, string expected)
{
ExpressionTests.AssertNormalizeComparison(left, operation, right, expectedType, expected);
diff --git a/Tests/Parser/Expressions/Trigger/RequirementClauseExpression_Tests.cs b/Tests/Parser/Expressions/Trigger/RequirementClauseExpression_Tests.cs
index 1ad3b66d..5fcb2154 100644
--- a/Tests/Parser/Expressions/Trigger/RequirementClauseExpression_Tests.cs
+++ b/Tests/Parser/Expressions/Trigger/RequirementClauseExpression_Tests.cs
@@ -1,11 +1,9 @@
using Jamiras.Components;
using NUnit.Framework;
-using RATools.Data;
using RATools.Parser;
using RATools.Parser.Expressions;
using RATools.Parser.Expressions.Trigger;
using RATools.Parser.Internal;
-using System.Collections.Generic;
namespace RATools.Tests.Parser.Expressions.Trigger
{
diff --git a/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs b/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs
index 17a81d3d..9551428f 100644
--- a/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs
+++ b/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs
@@ -54,6 +54,11 @@ public void TestAppendString(string input)
[TestCase("low4(0x001234) * 10000000 + byte(0x1235) != prev(low4(0x002345)) * 10000000 + prev(byte(0x2346))", "A:0xL001234*10000000_B:d0xL002345*10000000_0xH001235!=d0xH002346")] // don't need underflow adjustment for inequality
[TestCase("dword(dword(0x001234) + 8) - dword(dword(0x001234) + 12) > 100000", "A:100000_I:0xX001234_0xX00000c<0xX000008")] // AddAddress can be shared
[TestCase("dword(dword(0x001234) + 8) - dword(dword(0x001234) + 12) * 4 > 100000", "I:0xX001234_B:0xX00000c*4_I:0xX001234_0xX000008>100000")] // AddAddress cannot be shared
+ [TestCase("word(0x18294A) * 10 - word(0x182946) * 8 < 0x80000000", "A:0x 18294a*10_B:0x 182946*8_0<2147483648")]
+ [TestCase("byte(0x1234) - byte(0x2345) == 1", "B:0xH002345=0_0xH001234=1")]
+ [TestCase("byte(0x1234) - byte(0x2345) == -1", "B:0xH001234=0_0xH002345=1")] // invert to eliminate negative value
+ [TestCase("byte(0x1234) - byte(0x2345) == 4294967295", "B:0xH002345=0_0xH001234=4294967295")] // don't invert very high positive value
+ [TestCase("byte(0x1234) - byte(0x2345) <= -1", "A:1=0_0xH001234<=0xH002345")]
public void TestBuildTrigger(string input, string expected)
{
var clause = TriggerExpressionTests.Parse(input);
@@ -85,12 +90,12 @@ public void TestBuildTrigger(string input, string expected)
[TestCase("tbyte(0x001234) <= 16777215", "always_true()")]
[TestCase("tbyte(0x001234) > 16777215", "always_false()")]
[TestCase("tbyte(0x001234) >= 16777215", "tbyte(0x001234) == 16777215")]
- [TestCase("dword(0x001234) == 4294967295", "dword(0x001234) == 4294967295")]
- [TestCase("dword(0x001234) != 4294967295", "dword(0x001234) != 4294967295")]
- [TestCase("dword(0x001234) < 4294967295", "dword(0x001234) < 4294967295")]
+ [TestCase("dword(0x001234) == 4294967295", "dword(0x001234) == 4294967295U")]
+ [TestCase("dword(0x001234) != 4294967295", "dword(0x001234) != 4294967295U")]
+ [TestCase("dword(0x001234) < 4294967295", "dword(0x001234) < 4294967295U")]
[TestCase("dword(0x001234) <= 4294967295", "always_true()")]
[TestCase("dword(0x001234) > 4294967295", "always_false()")]
- [TestCase("dword(0x001234) >= 4294967295", "dword(0x001234) == 4294967295")]
+ [TestCase("dword(0x001234) >= 4294967295", "dword(0x001234) == 4294967295U")]
[TestCase("bit3(0x001234) == 0", "bit3(0x001234) == 0")]
[TestCase("bit3(0x001234) != 0", "bit3(0x001234) == 1")]
[TestCase("bit3(0x001234) < 0", "always_false()")]