diff --git a/Source/Data/FieldSize.cs b/Source/Data/FieldSize.cs index 58928235..bf869b54 100644 --- a/Source/Data/FieldSize.cs +++ b/Source/Data/FieldSize.cs @@ -288,5 +288,29 @@ public static bool IsBigEndian(this FieldSize size) return false; } } + + /// + /// Gets the big-endian size corresponding to the provided little-endian size or + /// the little-endian size corresponding to the provided big-endian size. + /// + public static FieldSize ToggleEndianness(this FieldSize size) + { + switch (size) + { + case FieldSize.Word: return FieldSize.BigEndianWord; + case FieldSize.TByte: return FieldSize.BigEndianTByte; + case FieldSize.DWord: return FieldSize.BigEndianDWord; + case FieldSize.Float: return FieldSize.BigEndianFloat; + case FieldSize.Double32: return FieldSize.BigEndianDouble32; + case FieldSize.BigEndianWord: return FieldSize.Word; + case FieldSize.BigEndianTByte: return FieldSize.TByte; + case FieldSize.BigEndianDWord: return FieldSize.DWord; + case FieldSize.BigEndianFloat: return FieldSize.Float; + case FieldSize.BigEndianDouble32: return FieldSize.Double32; + case FieldSize.MBF32: return FieldSize.LittleEndianMBF32; + case FieldSize.LittleEndianMBF32: return FieldSize.MBF32; + default: return size; + } + } } -} \ No newline at end of file +} diff --git a/Source/Parser/Expressions/ComparisonExpression.cs b/Source/Parser/Expressions/ComparisonExpression.cs index 1ed59f33..01c20366 100644 --- a/Source/Parser/Expressions/ComparisonExpression.cs +++ b/Source/Parser/Expressions/ComparisonExpression.cs @@ -166,7 +166,7 @@ public override bool ReplaceVariables(InterpreterScope scope, out ExpressionBase var comparisonNormalize = left as IComparisonNormalizeExpression; if (comparisonNormalize != null) { - var normalized = comparisonNormalize.NormalizeComparison(right, Operation, true); + var normalized = comparisonNormalize.NormalizeComparison(right, Operation, false); if (normalized != null && normalized is not ComparisonExpression) { result = normalized; @@ -257,11 +257,11 @@ public static ExpressionBase NormalizeFloatComparisonForInteger(ExpressionBase l { case ComparisonOperation.Equal: // integer a == 4.2 can never be true - return new ErrorExpression("Result can never be true using integer math"); + return new ErrorExpression("Result can never be true using integer math") { Location = left.Location.Union(right.Location) }; case ComparisonOperation.NotEqual: // integer a != 4.2 is always true - return new ErrorExpression("Result is always true using integer math"); + return new ErrorExpression("Result is always true using integer math") { Location = left.Location.Union(right.Location) }; case ComparisonOperation.LessThan: // integer a < 4.2 becomes integer a <= 4 diff --git a/Source/Parser/Expressions/Trigger/ErrorRequirementExpression.cs b/Source/Parser/Expressions/Trigger/ErrorRequirementExpression.cs new file mode 100644 index 00000000..8f6a5379 --- /dev/null +++ b/Source/Parser/Expressions/Trigger/ErrorRequirementExpression.cs @@ -0,0 +1,31 @@ +using RATools.Parser.Internal; +using System.Text; + +namespace RATools.Parser.Expressions.Trigger +{ + internal class ErrorRequirementExpression : RequirementExpressionBase + { + public ErrorRequirementExpression(ErrorExpression error) + { + _error = error; + } + + private readonly ErrorExpression _error; + + public override ErrorExpression BuildTrigger(TriggerBuilderContext context) + { + return _error; + } + + internal override void AppendString(StringBuilder builder) + { + _error.AppendString(builder); + } + + protected override bool Equals(ExpressionBase obj) + { + var that = obj as ErrorRequirementExpression; + return (that != null && that._error == _error); + } + } +} diff --git a/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs b/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs index a88546e4..529caa5c 100644 --- a/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs +++ b/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs @@ -323,6 +323,7 @@ private ExpressionBase ApplyAddition(ExpressionBase right, RequirementType combi if (_memoryAccessors == null) _memoryAccessors = new List(); _memoryAccessors.Add(modifiedMemoryAccessor); + break; default: @@ -1543,8 +1544,16 @@ public ErrorExpression BuildTrigger(TriggerBuilderContext context, ExpressionBas } if (_memoryAccessors != null) + { memoryAccessors.AddRange(_memoryAccessors); + if (_memoryAccessors.Any(a => a.ModifyingOperator == RequirementOperator.Multiply) && + _memoryAccessors.Any(a => a.ModifyingOperator == RequirementOperator.None)) + { + CombineNeighboringMemoryReads(memoryAccessors); + } + } + if (appendConstantAccessor) memoryAccessors.Add(constantAccessor); @@ -1658,6 +1667,229 @@ public ErrorExpression BuildTrigger(TriggerBuilderContext context, ExpressionBas return null; } + internal MemoryValueExpression NormalizeMemoryReads() + { + if (_memoryAccessors.Any(a => a.ModifyingOperator == RequirementOperator.Multiply) && + _memoryAccessors.Any(a => a.ModifyingOperator == RequirementOperator.None)) + { + var memoryAccessors = new List(_memoryAccessors); + CombineNeighboringMemoryReads(memoryAccessors); + if (memoryAccessors.Count != _memoryAccessors.Count) + { + var newMemoryValue = Clone(); + newMemoryValue._memoryAccessors = memoryAccessors; + return newMemoryValue; + } + } + + return this; + } + + private static void CombineNeighboringMemoryReads(List memoryAccessors) + { + int initialCount; + do + { + initialCount = memoryAccessors.Count; + + for (int i = memoryAccessors.Count - 1; i >= 0; --i) + { + var modifiedMemoryAccessor = memoryAccessors[i]; + if (modifiedMemoryAccessor.ModifyingOperator != RequirementOperator.Multiply) + continue; + + if (modifiedMemoryAccessor.Modifier.Type != FieldType.Value) + continue; + if (!modifiedMemoryAccessor.MemoryAccessor.Field.IsMemoryReference) + continue; + if (modifiedMemoryAccessor.MemoryAccessor.Field.IsFloat) + continue; + if (modifiedMemoryAccessor.MemoryAccessor.Field.Size.GetByteSize() == 4) + continue; + + switch (modifiedMemoryAccessor.Modifier.Value) + { + case 0x10: + MergeLowNibble(memoryAccessors, i, modifiedMemoryAccessor); + break; + + case 0x100: + MergeLowByte(memoryAccessors, i, modifiedMemoryAccessor, false); + break; + + case 0x10000: + MergeLowWord(memoryAccessors, i, modifiedMemoryAccessor, false); + break; + + case 0x1000000: + MergeLowTByte(memoryAccessors, i, modifiedMemoryAccessor, false); + break; + + case 100: + MergeLowByte(memoryAccessors, i, modifiedMemoryAccessor, true); + break; + + case 10000: + MergeLowWord(memoryAccessors, i, modifiedMemoryAccessor, true); + break; + + case 1000000: + MergeLowTByte(memoryAccessors, i, modifiedMemoryAccessor, true); + break; + } + } + } while (memoryAccessors.Count != initialCount && memoryAccessors.Count > 1); + } + + private static void MergeLowNibble(List memoryAccessors, int modifiedIndex, ModifiedMemoryAccessorExpression modifiedMemoryAccessor) + { + if (modifiedMemoryAccessor.MemoryAccessor.Field.Size == FieldSize.HighNibble) + { + var lowNibbleIndex = FindMemoryRead(memoryAccessors, FieldSize.LowNibble, + modifiedMemoryAccessor, modifiedMemoryAccessor.MemoryAccessor.Field.Value); + if (lowNibbleIndex != -1) + MergeMemoryReads(memoryAccessors, modifiedIndex, lowNibbleIndex, FieldSize.Byte); + } + } + + private static void MergeLowByte(List memoryAccessors, int modifiedIndex, ModifiedMemoryAccessorExpression modifiedMemoryAccessor, bool matchBcd) + { + bool isBcd = modifiedMemoryAccessor.MemoryAccessor is BinaryCodedDecimalExpression; + if (isBcd != matchBcd) + return; + + FieldSize targetSize; + var fieldSize = modifiedMemoryAccessor.MemoryAccessor.Field.Size; + switch (fieldSize) + { + case FieldSize.Byte: + targetSize = FieldSize.Word; + break; + case FieldSize.Word: + targetSize = FieldSize.TByte; + break; + case FieldSize.TByte: + targetSize = FieldSize.DWord; + break; + case FieldSize.BigEndianWord: + targetSize = FieldSize.BigEndianTByte; + break; + case FieldSize.BigEndianTByte: + targetSize = FieldSize.BigEndianDWord; + break; + default: + return; + } + + MergeAccessors(memoryAccessors, modifiedIndex, modifiedMemoryAccessor, + FieldSize.Byte, fieldSize, targetSize, isBcd); + } + + private static void MergeLowWord(List memoryAccessors, int modifiedIndex, ModifiedMemoryAccessorExpression modifiedMemoryAccessor, bool matchBcd) + { + bool isBcd = modifiedMemoryAccessor.MemoryAccessor is BinaryCodedDecimalExpression; + if (isBcd != matchBcd) + return; + + FieldSize targetSize; + var fieldSize = modifiedMemoryAccessor.MemoryAccessor.Field.Size; + switch (fieldSize) + { + case FieldSize.Byte: + targetSize = FieldSize.TByte; + break; + case FieldSize.Word: + targetSize = FieldSize.DWord; + break; + case FieldSize.BigEndianWord: + targetSize = FieldSize.BigEndianDWord; + break; + default: + return; + } + + MergeAccessors(memoryAccessors, modifiedIndex, modifiedMemoryAccessor, + FieldSize.Word, fieldSize, targetSize, isBcd); + } + + private static void MergeLowTByte(List memoryAccessors, int modifiedIndex, ModifiedMemoryAccessorExpression modifiedMemoryAccessor, bool matchBcd) + { + bool isBcd = modifiedMemoryAccessor.MemoryAccessor is BinaryCodedDecimalExpression; + if (isBcd != matchBcd) + return; + + if (modifiedMemoryAccessor.MemoryAccessor.Field.Size == FieldSize.Byte) + { + MergeAccessors(memoryAccessors, modifiedIndex, modifiedMemoryAccessor, + FieldSize.TByte, FieldSize.Byte, FieldSize.DWord, isBcd); + } + } + + private static void MergeAccessors(List memoryAccessors, + int modifiedIndex, ModifiedMemoryAccessorExpression modifiedMemoryAccessor, + FieldSize mergeSize, FieldSize fieldSize, FieldSize targetSize, bool isBcd) + { + var address = modifiedMemoryAccessor.MemoryAccessor.Field.Value; + + if (targetSize.IsBigEndian()) + { + var lowIndex = FindMemoryRead(memoryAccessors, mergeSize.ToggleEndianness(), + modifiedMemoryAccessor, + address + fieldSize.GetByteSize(), isBcd); + if (lowIndex != -1) + MergeMemoryReads(memoryAccessors, lowIndex, modifiedIndex, targetSize, isBcd); + } + else + { + var lowIndex = FindMemoryRead(memoryAccessors, mergeSize, + modifiedMemoryAccessor, address - mergeSize.GetByteSize(), isBcd); + if (lowIndex != -1) + { + MergeMemoryReads(memoryAccessors, modifiedIndex, lowIndex, targetSize, isBcd); + } + else if (fieldSize == FieldSize.Byte) + { + lowIndex = FindMemoryRead(memoryAccessors, mergeSize.ToggleEndianness(), + modifiedMemoryAccessor, address + 1, isBcd); + if (lowIndex != -1) + MergeMemoryReads(memoryAccessors, lowIndex, modifiedIndex, targetSize.ToggleEndianness(), isBcd); + } + } + } + + private static void MergeMemoryReads(List memoryAccessors, int fromIndex, int toIndex, FieldSize newSize, bool makeBcd = false) + { + var newAccessor = memoryAccessors[toIndex].MemoryAccessor.ChangeFieldSize(newSize); + if (makeBcd) + newAccessor = new BinaryCodedDecimalExpression(newAccessor); + + var newModifiedAccessor = new ModifiedMemoryAccessorExpression(newAccessor) { CombiningOperator = memoryAccessors[toIndex].CombiningOperator }; + newModifiedAccessor.Location = newAccessor.Location.Union(memoryAccessors[fromIndex].Location); + memoryAccessors[toIndex] = newModifiedAccessor; + memoryAccessors.RemoveAt(fromIndex); + } + + private static int FindMemoryRead(List memoryAccessors, FieldSize size, ModifiedMemoryAccessorExpression matchMemoryAccessor, uint address, bool matchBcd = false) + { + for (int i = 0; i < memoryAccessors.Count; ++i) + { + var modifiedMemoryAccessor = memoryAccessors[i]; + if (modifiedMemoryAccessor.ModifyingOperator == RequirementOperator.None && + modifiedMemoryAccessor.MemoryAccessor.Field.Value == address && + modifiedMemoryAccessor.MemoryAccessor.Field.Size == size && + modifiedMemoryAccessor.CombiningOperator == matchMemoryAccessor.CombiningOperator && + modifiedMemoryAccessor.MemoryAccessor.Field.Type == matchMemoryAccessor.MemoryAccessor.Field.Type && + modifiedMemoryAccessor.MemoryAccessor.PointerChainMatches(matchMemoryAccessor.MemoryAccessor)) + { + bool isBcd = (modifiedMemoryAccessor.MemoryAccessor is BinaryCodedDecimalExpression); + if (isBcd == matchBcd) + return i; + } + } + + return -1; + } + public ErrorExpression Execute(InterpreterScope scope) { if (_memoryAccessors == null || _memoryAccessors.Count == 0) diff --git a/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs b/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs index 76f36e2f..3c9e2ee7 100644 --- a/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs +++ b/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs @@ -368,22 +368,52 @@ private static bool ConvertToBCD(ExpressionBase expression, out ExpressionBase n return false; } - private ErrorExpression NormalizeBCD(TriggerBuilderContext context, out RequirementExpressionBase result) + private void NormalizeMemoryReads(ref RequirementExpressionBase result) { + var condition = result as RequirementConditionExpression; + if (condition == null) + return; + + ExpressionBase newLeft = condition.Left; + ExpressionBase newRight = condition.Right; + + var memoryValue = Left as MemoryValueExpression; + if (memoryValue != null) + newLeft = memoryValue.NormalizeMemoryReads(); + + memoryValue = Right as MemoryValueExpression; + if (memoryValue != null) + newRight = memoryValue.NormalizeMemoryReads(); + + if (!ReferenceEquals(Left, newLeft) || !ReferenceEquals(Right, newRight)) + { + result = new RequirementConditionExpression + { + Left = newLeft, + Comparison = condition.Comparison, + Right = newRight, + Location = condition.Location, + }; + } + } + + private static ErrorExpression NormalizeBCD(TriggerBuilderContext context, ref RequirementExpressionBase result) + { + var condition = result as RequirementConditionExpression; + if (condition == null) + return null; + ExpressionBase newLeft; ExpressionBase newRight; - bool leftHasBCD = ExtractBCD(Left, out newLeft); - bool rightHasBCD = ExtractBCD(Right, out newRight); + bool leftHasBCD = ExtractBCD(condition.Left, out newLeft); + bool rightHasBCD = ExtractBCD(condition.Right, out newRight); if (!rightHasBCD) { if (!leftHasBCD) - { - result = this; return null; - } - newRight = Right; + newRight = condition.Right; var rightConstant = newRight as IntegerConstantExpression; if (rightConstant != null) { @@ -399,7 +429,7 @@ private ErrorExpression NormalizeBCD(TriggerBuilderContext context, out Requirem if (newRight == null) { // right value cannot be decoded into 32-bits - switch (Comparison) + switch (condition.Comparison) { case ComparisonOperation.NotEqual: case ComparisonOperation.LessThan: @@ -415,11 +445,11 @@ private ErrorExpression NormalizeBCD(TriggerBuilderContext context, out Requirem } else if (!leftHasBCD) { - leftHasBCD = ConvertToBCD(Right, out newLeft); + leftHasBCD = ConvertToBCD(condition.Right, out newLeft); if (newLeft == null) { // left value cannot be decoded into 32-bits - switch (Comparison) + switch (condition.Comparison) { case ComparisonOperation.NotEqual: case ComparisonOperation.GreaterThan: @@ -436,7 +466,7 @@ private ErrorExpression NormalizeBCD(TriggerBuilderContext context, out Requirem if (leftHasBCD && rightHasBCD && context.CanModifyComparison) { - if (Comparison == ComparisonOperation.Equal || Comparison == ComparisonOperation.NotEqual) + if (condition.Comparison == ComparisonOperation.Equal || condition.Comparison == ComparisonOperation.NotEqual) { var leftMemoryValue = newLeft as MemoryValueExpression; if (leftMemoryValue != null && leftMemoryValue.HasConstant) @@ -456,15 +486,14 @@ private ErrorExpression NormalizeBCD(TriggerBuilderContext context, out Requirem result = new RequirementConditionExpression() { Left = newLeft, - Comparison = Comparison, + Comparison = condition.Comparison, Right = newRight, - Location = Location, + Location = condition.Location, }; return null; } - result = this; return null; } @@ -756,8 +785,10 @@ public ExpressionBase Normalize(TriggerBuilderContext context) Left = new MemoryValueExpression(modifiedMemoryAccessor); } - RequirementExpressionBase result; - var error = NormalizeBCD(context, out result); + RequirementExpressionBase result = this; + NormalizeMemoryReads(ref result); + + var error = NormalizeBCD(context, ref result); if (error != null) return error; diff --git a/Source/Parser/Expressions/Trigger/RequirementExpressionBase.cs b/Source/Parser/Expressions/Trigger/RequirementExpressionBase.cs index 2be887f1..922f6ea2 100644 --- a/Source/Parser/Expressions/Trigger/RequirementExpressionBase.cs +++ b/Source/Parser/Expressions/Trigger/RequirementExpressionBase.cs @@ -180,6 +180,10 @@ internal static RequirementExpressionBase ConvertToRequirementExpression(Express if (memoryValue != null) return new MemoryValueExpression.MemoryValueRequirementExpression(memoryValue) { Location = expression.Location }; + var error = expression as ErrorExpression; + if (error != null) + return new ErrorRequirementExpression(error); + return null; } } diff --git a/Tests/Parser/Expressions/Trigger/MemoryValueExpressionTests.cs b/Tests/Parser/Expressions/Trigger/MemoryValueExpressionTests.cs index b9db58e9..3d574049 100644 --- a/Tests/Parser/Expressions/Trigger/MemoryValueExpressionTests.cs +++ b/Tests/Parser/Expressions/Trigger/MemoryValueExpressionTests.cs @@ -446,9 +446,9 @@ public void TestUnderflowAdjustmentImpossible() scope.AddFunction(new MemoryAccessorFunction("byte", FieldSize.Byte)); ExpressionBase result; - Assert.That(expr.ReplaceVariables(scope, out result), Is.False); - Assert.That(result, Is.InstanceOf()); - Assert.That(((ErrorExpression)result).Message, Is.EqualTo("Expression can never be true")); + Assert.That(expr.ReplaceVariables(scope, out result), Is.True); + result = ((RequirementConditionExpression)result).Normalize(new TriggerBuilderContext()); + Assert.That(result, Is.InstanceOf()); } [Test] @@ -571,5 +571,91 @@ public void TestMergeBitCountLimits(ComparisonOperation comparisonOperation, int ExpressionTests.AssertAppendString(comparison, expected); } } + + + [Test] + // low4 + high4 * 16 + [TestCase("low4(0x1234) + high4(0x1234) * 16", "0xH001234")] + [TestCase("high4(0x1234) * 16 + low4(0x1234)", "0xH001234")] + [TestCase("low4(0x1234) + high4(0x1235) * 16", "A:0xU001235*16_0xL001234")] + [TestCase("low4(0x1234) - high4(0x1234) * 16", "B:0xU001234*16_0xL001234")] + // byte + byte * 256 + [TestCase("byte(0x1234) + byte(0x1235) * 256", "0x 001234")] + [TestCase("byte(0x1235) * 256 + byte(0x1234)", "0x 001234")] + [TestCase("byte(0x1234) + byte(0x1234) * 256", "A:0xH001234*256_0xH001234")] + [TestCase("byte(0x1235) + byte(0x1234) * 256", "0xI001234")] + [TestCase("byte(0x1234) * 256 + byte(0x1235)", "0xI001234")] + // byte + word * 256 + [TestCase("byte(0x1234) + word(0x1235) * 256", "0xW001234")] + [TestCase("word(0x1235) * 256 + byte(0x1234)", "0xW001234")] + [TestCase("byte(0x1235) + word(0x1234) * 256", "A:0x 001234*256_0xH001235")] + // byte + tbyte * 256 + [TestCase("byte(0x1234) + tbyte(0x1235) * 256", "0xX001234")] + [TestCase("tbyte(0x1235) * 256 + byte(0x1234)", "0xX001234")] + // byte + word_be * 256 + [TestCase("byte(0x1236) + word_be(0x1234) * 256", "0xJ001234")] + [TestCase("word_be(0x1234) * 256 + byte(0x1236)", "0xJ001234")] + [TestCase("byte(0x1234) + word_be(0x1235) * 256", "A:0xI001235*256_0xH001234")] + // word + word * 65536 + [TestCase("word(0x1234) + word(0x1236) * 65536", "0xX001234")] + [TestCase("word(0x1236) * 65536 + word(0x1234)", "0xX001234")] + [TestCase("word(0x1234) + word(0x1235) * 65536", "A:0x 001235*65536_0x 001234")] + // word_be + byte * 65536 + [TestCase("word_be(0x1235) + byte(0x1234) * 65536", "0xJ001234")] + [TestCase("byte(0x1234) * 65536 + word_be(0x1235)", "0xJ001234")] + [TestCase("word_be(0x1234) + byte(0x1236) * 65536", "A:0xH001236*65536_0xI001234")] + // word_be + word_be * 65536 + [TestCase("word_be(0x1236) + word_be(0x1234) * 65536", "0xG001234")] + [TestCase("word_be(0x1234) * 65536 + word_be(0x1236)", "0xG001234")] + [TestCase("word_be(0x1234) + word_be(0x1236) * 65536", "A:0xI001236*65536_0xI001234")] + // tbyte + byte * 16777216 + [TestCase("tbyte(0x1234) + byte(0x1237) * 16777216", "0xX001234")] + [TestCase("byte(0x1237) * 16777216 + tbyte(0x1234)", "0xX001234")] + [TestCase("tbyte(0x1234) + byte(0x1235) * 16777216", "A:0xH001235*16777216_0xW001234")] + // tbyte_be + byte * 16777216 + [TestCase("tbyte_be(0x1235) + byte(0x1234) * 16777216", "0xG001234")] + [TestCase("byte(0x1234) * 16777216 + tbyte_be(0x1235)", "0xG001234")] + [TestCase("tbyte_be(0x1234) + byte(0x1237) * 16777216", "A:0xH001237*16777216_0xJ001234")] + // prev interference + [TestCase("byte(0x1234) + prev(byte(0x1235)) * 256", "A:d0xH001235*256_0xH001234")] + [TestCase("prev(byte(0x1234)) + byte(0x1235) * 256", "A:0xH001235*256_d0xH001234")] + [TestCase("prev(byte(0x1234)) + prev(byte(0x1235) * 256)", "d0x 001234")] + [TestCase("byte(0x1234) + byte(0x1235) * 256 - prev(byte(0x1234)) - prev(byte(0x1235) * 256)", "B:d0x 001234=0_0x 001234")] + // pointer + [TestCase("byte(dword(0x1000) + 0x1234) + byte(dword(0x1000) + 0x1235) * 256", "I:0xX001000_0x 001234")] + [TestCase("byte(dword(0x1000) + 0x1234) + byte(dword(0x1004) + 0x1235) * 256", "I:0xX001004_A:0xH001235*256_I:0xX001000_0xH001234")] + [TestCase("byte(dword(0x1000) + 0x1234) + byte(0x1235) * 256", "A:0xH001235*256_I:0xX001000_0xH001234")] + // bcd byte + bcd byte * 100 + [TestCase("bcd(byte(0x1234)) + bcd(byte(0x1235)) * 100", "b0x 001234")] + [TestCase("bcd(byte(0x1235)) * 100 + bcd(byte(0x1234))", "b0x 001234")] + [TestCase("bcd(byte(0x1234)) + byte(0x1235) * 100", "A:0xH001235*100_b0xH001234")] + // bcd word + bcd byte * 10000 + [TestCase("bcd(word(0x1234)) + bcd(byte(0x1236)) * 10000", "b0xW001234")] + [TestCase("bcd(byte(0x1236)) * 10000 + bcd(word(0x1234))", "b0xW001234")] + [TestCase("bcd(word(0x1234)) + byte(0x1236) * 10000", "A:0xH001236*10000_b0x 001234")] + // bcd word + bcd word * 10000 + [TestCase("bcd(word(0x1234)) + bcd(word(0x1236)) * 10000", "b0xX001234")] + [TestCase("bcd(word(0x1236)) * 10000 + bcd(word(0x1234))", "b0xX001234")] + [TestCase("bcd(word(0x1234)) + word(0x1236) * 10000", "A:0x 001236*10000_b0x 001234")] + // bcd tbyte + bcd byte * 1000000 + [TestCase("bcd(tbyte(0x1234)) + bcd(byte(0x1237)) * 1000000", "b0xX001234")] + [TestCase("bcd(byte(0x1237)) * 1000000 + bcd(tbyte(0x1234))", "b0xX001234")] + // bcd word_be + bcd byte * 10000 + [TestCase("bcd(word_be(0x1235)) + bcd(byte(0x1234)) * 10000", "b0xJ001234")] + [TestCase("bcd(byte(0x1234)) * 10000 + bcd(word_be(0x1235))", "b0xJ001234")] + // bcd word_be + bcd word_be * 10000 + [TestCase("bcd(word_be(0x1236)) + bcd(word_be(0x1234)) * 10000", "b0xG001234")] + [TestCase("bcd(word_be(0x1234)) * 10000 + bcd(word_be(0x1236))", "b0xG001234")] + // longer chains + [TestCase("byte(0x1234) + byte(0x1235)*256 + byte(0x1236)*65536", "0xW001234")] + [TestCase("byte(0x1236)*65536 + byte(0x1235)*256 + byte(0x1234)", "0xW001234")] + [TestCase("bcd(byte(0x1234)) + bcd(byte(0x1235))*100 + bcd(byte(0x1236))*10000", "b0xW001234")] + [TestCase("bcd(byte(0x1236))*10000 + bcd(byte(0x1235))*100 + bcd(byte(0x1234))", "b0xW001234")] + public void TestCombineNeighboringMemoryReads(string input, string expected) + { + var expr = TriggerExpressionTests.Parse(input); + Assert.That(expr, Is.InstanceOf()); + TriggerExpressionTests.AssertSerialize((ITriggerExpression)expr, expected); + } } } \ No newline at end of file