From c923f377f34084487a0dd87ed59741db3c2dd328 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Mon, 1 Dec 2025 21:20:14 -0700 Subject: [PATCH 1/3] use MemoryAccessorAliases directly in ScriptBuilderContext --- .../Trigger/MemoryValueExpression.cs | 2 - Source/Parser/MemoryAccessorAlias.cs | 15 +- Source/Parser/ScriptBuilderContext.cs | 225 +++++++++------ Source/ViewModels/NewScriptDialogViewModel.cs | 261 ++++++++++-------- Tests/Parser/AchievementBuilderTests.cs | 2 +- .../Functions/MemoryAccessorFunctionTests.cs | 32 +-- .../Internal/RequirementsOptimizerTests.cs | 2 +- .../Internal/ScriptBuilderContextTests.cs | 48 +++- 8 files changed, 362 insertions(+), 225 deletions(-) diff --git a/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs b/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs index 039af0e0..c8c0c692 100644 --- a/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs +++ b/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs @@ -1327,8 +1327,6 @@ private MemoryAccessorExpression ConvertToMemoryAccessor() public ExpressionBase ClearConstant() { - Debug.Assert(!IsReadOnly); - if (_memoryAccessors == null) return new IntegerConstantExpression(0); diff --git a/Source/Parser/MemoryAccessorAlias.cs b/Source/Parser/MemoryAccessorAlias.cs index fa7f8aca..76a95995 100644 --- a/Source/Parser/MemoryAccessorAlias.cs +++ b/Source/Parser/MemoryAccessorAlias.cs @@ -212,6 +212,15 @@ public string GetAlias(FieldSize size) return alias; } + public void SetAlias(FieldSize size, string alias) + { + if (size != PrimarySize && HasReferencedSize(size)) + { + _aliases ??= new Dictionary(); + _aliases[size] = alias; + } + } + public void UpdateAliasFromNote(NameStyle style) { _aliases = null; @@ -513,10 +522,10 @@ private static MemoryAccessorAlias GetMemoryAccessor(MemoryAccessorAlias parentM if (index < 0) { CodeNote note = null; - if (parentNote == null) - codeNotes.TryGetValue(field.Value, out note); - else + if (parentNote != null) note = parentNote.OffsetNotes.FirstOrDefault(n => n.Address == field.Value); + else if (parentMemoryAccessor.Address == 0 && parentMemoryAccessor.PrimarySize == FieldSize.None) + codeNotes.TryGetValue(field.Value, out note); if (note != null) memoryAccessor.SetNote(note); diff --git a/Source/Parser/ScriptBuilderContext.cs b/Source/Parser/ScriptBuilderContext.cs index 2c599ff3..c6a8b838 100644 --- a/Source/Parser/ScriptBuilderContext.cs +++ b/Source/Parser/ScriptBuilderContext.cs @@ -11,13 +11,19 @@ public class ScriptBuilderContext public ScriptBuilderContext() { NumberFormat = NumberFormat.Decimal; + AddressWidth = 6; WrapWidth = Int32.MaxValue; Indent = 0; - _aliases = new List>(); + _aliases = new List(); } public NumberFormat NumberFormat { get; set; } + /// + /// Gets or sets the number of characters to use for addresses. + /// + public int AddressWidth { get; set; } + public int WrapWidth { get; set; } public int Indent { get; set; } @@ -40,38 +46,30 @@ public ScriptBuilderContext Clone() private StringBuilder _subSources; private StringBuilder _addHits; private StringBuilder _andNext; - private StringBuilder _addAddress; private StringBuilder _resetNextIf; private StringBuilder _measuredIf; private StringBuilder _remember; private Requirement _lastAndNext; private int _remainingWidth; - private List> _aliases; - - public void AddAlias(string memoryReference, string alias) + class MemoryAccessorAliasChain { - for (int i = 0; i < _aliases.Count; i++) - { - if (_aliases[i].Key == memoryReference) - { - _aliases[i] = new KeyValuePair(memoryReference, alias); - return; - } - } + public MemoryAccessorAlias Alias { get; set; } + public MemoryAccessorAliasChain Next { get; set; } + public Requirement Requirement { get; set; } + }; + private MemoryAccessorAliasChain _addAddress; - _aliases.Add(new KeyValuePair(memoryReference, alias)); - } + private List _aliases; - public string GetAliasDefinition(string alias) + public void AddAlias(MemoryAccessorAlias alias) { - foreach (var kvp in _aliases) - { - if (kvp.Value == alias) - return kvp.Key; - } + _aliases.Add(alias); + } - return null; + public void AddAliases(IEnumerable aliases) + { + _aliases.AddRange(aliases); } public override string ToString() @@ -102,7 +100,7 @@ internal void Reset() _remainingWidth = WrapWidth - Indent; } - private ScriptBuilderContext CreatedNestedContext() + private ScriptBuilderContext CreateNestedContext() { var context = new ScriptBuilderContext(); context.NumberFormat = NumberFormat; @@ -113,6 +111,7 @@ private ScriptBuilderContext CreatedNestedContext() context._addAddress = _addAddress; context._resetNextIf = _resetNextIf; context._measuredIf = _measuredIf; + context._aliases = _aliases; return context; } @@ -221,7 +220,8 @@ private void AppendRequirementEx(StringBuilder builder, RequirementEx requiremen if (requirementEx.Requirements.Last().HitCount > 0 && requirementEx.Requirements.Any(r => r.Type == RequirementType.AddHits)) { - var nestedContext = CreatedNestedContext(); + var nestedContext = CreateNestedContext(); + nestedContext._addAddress = null; nestedContext.WrapWidth = Int32.MaxValue; var tallyBuilder = new StringBuilder(); nestedContext.AppendTally(tallyBuilder, requirementEx.Requirements); @@ -244,10 +244,20 @@ private void AppendRequirementEx(StringBuilder builder, RequirementEx requiremen switch (requirement.Type) { case RequirementType.AddAddress: - if (_addAddress == null) - _addAddress = new StringBuilder(); - AppendFields(_addAddress, requirement); - _addAddress.Append(" + "); + if (requirement.Left.IsMemoryReference) + { + var currentAliases = (_addAddress != null) ? _addAddress.Alias.Children : _aliases; + var memoryAccessor = currentAliases.FirstOrDefault(a => a.Address == requirement.Left.Value); + if (memoryAccessor == null) + memoryAccessor = new MemoryAccessorAlias(requirement.Left.Value); + _addAddress = new MemoryAccessorAliasChain() + { + Alias = memoryAccessor, + Next = _addAddress, + Requirement = requirement, + }; + continue; + } break; case RequirementType.AddSource: @@ -317,9 +327,6 @@ private void AppendRequirementEx(StringBuilder builder, RequirementEx requiremen private void Append(StringBuilder builder, StringBuilder source) { - foreach (var alias in _aliases) - source.Replace(alias.Key, alias.Value); - if (source.Length <= _remainingWidth) { // full string fits on current line @@ -497,7 +504,7 @@ public void AppendRequirement(StringBuilder builder, Requirement requirement) case RequirementType.PauseIf: if (_resetNextIf != null || requirement.HitCount != 0) { - var nestedContext = CreatedNestedContext(); + var nestedContext = CreateNestedContext(); nestedContext._resetNextIf = null; var comparison = new StringBuilder(); @@ -520,6 +527,7 @@ public void AppendRequirement(StringBuilder builder, Requirement requirement) } builder.Append(')'); + _addAddress = null; } else { @@ -698,7 +706,7 @@ internal void AppendCondition(StringBuilder builder, Requirement requirement) default: if (requirement.Operator != RequirementOperator.None && - NullOrEmpty(_subSources) && NullOrEmpty(_addSources)) + NullOrEmpty(_subSources) && NullOrEmpty(_addSources) && _addAddress == null) { var result = requirement.Evaluate(); if (result == null && requirement.HitCount > 0) @@ -727,7 +735,7 @@ internal void AppendCondition(StringBuilder builder, Requirement requirement) } } - AppendField(builder, requirement.Left, _addAddress?.ToString()); + AppendField(builder, requirement.Left); break; } @@ -767,59 +775,109 @@ internal void AppendCondition(StringBuilder builder, Requirement requirement) builder.Append(requirement.Operator.ToOperatorString()); builder.Append(' '); - AppendField(builder, requirement.Right, _addAddress?.ToString()); + AppendField(builder, requirement.Right); } if (suffix != null) builder.Append(suffix); - _addAddress?.Clear(); + _addAddress = null; } private void AppendFields(StringBuilder builder, Requirement requirement) { - if (!NullOrEmpty(_addAddress)) + AppendField(builder, requirement.Left); + AppendFieldModifier(builder, requirement); + _addAddress = null; + } + + private void AppendField(StringBuilder builder, Field field) + { + if (field.Type == FieldType.Recall && _remember != null && _remember.Length > 0) + { + builder.Append('('); + builder.Append(_remember); + builder.Append(')'); + } + else if (field.IsMemoryReference) { - var addAddressString = _addAddress.ToString(); - _addAddress.Clear(); + var currentAliases = (_addAddress != null) ? _addAddress.Alias.Children : _aliases; + var memoryAccessor = currentAliases.FirstOrDefault(a => a.Address == field.Value); + AppendFieldAlias(builder, field, memoryAccessor, _addAddress, null); + } + else + { + field.AppendString(builder, NumberFormat); + } + } - if (!ReferenceEquals(_addAddress, builder)) - builder.Append('('); + private void AppendFieldAlias(StringBuilder builder, Field field, MemoryAccessorAlias alias, MemoryAccessorAliasChain parent, Requirement parentRequirement) + { + bool needClosingParenthesis = false; + switch (field.Type) + { + case FieldType.PreviousValue: + builder.Append("prev("); + needClosingParenthesis = true; + break; + case FieldType.PriorValue: + builder.Append("prior("); + needClosingParenthesis = true; + break; + case FieldType.BinaryCodedDecimal: + builder.Append("bcd("); + needClosingParenthesis = true; + break; + case FieldType.Invert: + builder.Append('~'); + break; + } - AppendField(builder, requirement.Left, addAddressString); + var functionName = alias?.GetAlias(field.Size); + if (!String.IsNullOrEmpty(functionName)) + { + builder.Append(functionName); + builder.Append("()"); + } + else + { + builder.Append(Field.GetSizeFunction(field.Size)); + builder.Append('('); - if (requirement.Operator != RequirementOperator.None && requirement.Right.IsMemoryReference) + if (parent != null) { - // make sure to include the AddAddress logic on the right side too. - _addAddress.Append(addAddressString); - AppendFieldModifier(builder, requirement); - _addAddress.Clear(); + AppendFieldAlias(builder, parent.Requirement.Left, parent.Alias, parent.Next, parent.Requirement); + if (field.Value != 0) + builder.AppendFormat(" + 0x{0:X2}", field.Value); + builder.Append(')'); } else { - AppendFieldModifier(builder, requirement); - } - - if (!ReferenceEquals(_addAddress, builder)) + builder.Append("0x"); + builder.Append(FormatAddress(field.Value)); builder.Append(')'); + } } - else - { - AppendField(builder, requirement.Left); - AppendFieldModifier(builder, requirement); - } + + if (parentRequirement != null) + AppendFieldModifier(builder, parentRequirement); + + if (needClosingParenthesis) + builder.Append(')'); } - private void AppendField(StringBuilder builder, Field field, string addAddressString = null) + public string FormatAddress(UInt32 address) { - if (field.Type == FieldType.Recall && _remember != null && _remember.Length > 0) + switch (AddressWidth) { - builder.Append('('); - builder.Append(_remember); - builder.Append(')'); + case 2: + return String.Format("{0:X2}", address); + case 4: + return String.Format("{0:X4}", address); + default: + return String.Format("{0:X6}", address); } - else - field.AppendString(builder, NumberFormat, addAddressString); + } private void AppendFieldModifier(StringBuilder builder, Requirement requirement) @@ -830,28 +888,34 @@ private void AppendFieldModifier(StringBuilder builder, Requirement requirement) builder.Append(requirement.Operator.ToOperatorString()); builder.Append(' '); - switch (requirement.Operator) + if (requirement.Right.Type == FieldType.Value) { - case RequirementOperator.BitwiseAnd: - case RequirementOperator.BitwiseXor: - // force right side to be hexadecimal for bitwise operators - var context = Clone(); - context.NumberFormat = NumberFormat.Hexadecimal; - context.AppendField(builder, requirement.Right, - ReferenceEquals(builder, _addAddress) ? null : _addAddress?.ToString()); - break; + switch (requirement.Operator) + { + case RequirementOperator.BitwiseAnd: + case RequirementOperator.BitwiseXor: + // force right side to be hexadecimal for bitwise operators + requirement.Right.AppendString(builder, NumberFormat.Hexadecimal); + return; - default: - AppendField(builder, requirement.Right, - ReferenceEquals(builder, _addAddress) ? null : _addAddress?.ToString()); - break; + default: + // force right side to decimal for single-digit decimal values + if (requirement.Right.Type == FieldType.Value && requirement.Right.Value < 10) + { + requirement.Right.AppendString(builder, NumberFormat.Decimal); + return; + } + break; + } } + + AppendField(builder, requirement.Right); } } private void AppendAndOrNext(Requirement requirement) { - var nestedContext = CreatedNestedContext(); + var nestedContext = CreateNestedContext(); nestedContext._addHits = null; nestedContext._measuredIf = null; nestedContext._resetNextIf = null; @@ -859,11 +923,12 @@ private void AppendAndOrNext(Requirement requirement) _andNext = new StringBuilder(); if (!NullOrEmpty(_addSources) || !NullOrEmpty(_subSources) || - !NullOrEmpty(_addAddress)) + _addAddress != null) { _andNext.Append('('); nestedContext.AppendRequirement(_andNext, requirement); _andNext.Append(')'); + _addAddress = null; } else { @@ -893,7 +958,7 @@ private void AppendAndOrNext(Requirement requirement) private void AppendModifyHits(StringBuilder builder, Requirement requirement) { - var nestedContext = CreatedNestedContext(); + var nestedContext = CreateNestedContext(); nestedContext._addHits = null; nestedContext._measuredIf = null; nestedContext._resetNextIf = null; @@ -919,7 +984,7 @@ private void AppendTally(StringBuilder builder, IEnumerable require } // the final clause will get generated as a "repeated" because we've ignored the AddHits subclauses - var nestedContext = CreatedNestedContext(); + var nestedContext = CreateNestedContext(); nestedContext.Reset(); nestedContext._measuredIf = _measuredIf; nestedContext.Indent += 4; diff --git a/Source/ViewModels/NewScriptDialogViewModel.cs b/Source/ViewModels/NewScriptDialogViewModel.cs index 10088212..ed9f8ae3 100644 --- a/Source/ViewModels/NewScriptDialogViewModel.cs +++ b/Source/ViewModels/NewScriptDialogViewModel.cs @@ -912,11 +912,14 @@ internal void Dump(Stream outStream) NumberFormat = _settings.HexValues ? NumberFormat.Hexadecimal : NumberFormat.Decimal, }; + if (_memoryItems.Count > 0 && _memoryItems[_memoryItems.Count - 1].Address <= 0xFFFF) + scriptBuilderContext.AddressWidth = 4; + var mask = GetMask(); - AddAliases(scriptBuilderContext, _memoryItems, mask); - foreach (var asset in _assets) - AddAliases(scriptBuilderContext, asset.MemoryAddresses, mask); + ApplyCustomNames(); + MakeAliasesUnique(); + scriptBuilderContext.AddAliases(_memoryAccessors); using (var stream = new StreamWriter(outStream)) { @@ -930,7 +933,7 @@ internal void Dump(Stream outStream) var lookupsToDump = _assets.Where(a => a.Type == DumpAssetType.Lookup && a.IsSelected).ToList(); - DumpMemoryAccessors(stream, lookupsToDump, scriptBuilderContext); + DumpMemoryAccessors(stream, lookupsToDump, scriptBuilderContext, mask); foreach (var dumpLookup in lookupsToDump) DumpLookup(stream, dumpLookup); @@ -941,110 +944,80 @@ internal void Dump(Stream outStream) } } - private void AddAliases(ScriptBuilderContext scriptBuilderContext, IEnumerable items, uint mask) + private void ApplyCustomNames() { - foreach (var memoryItem in items.Where(m => !String.IsNullOrEmpty(m.FunctionName))) + foreach (var memoryItem in _memoryItems.Where(m => !String.IsNullOrEmpty(m.FunctionName))) { - var functionCall = memoryItem.FunctionName + "()"; - string memoryReference; - - if (memoryItem.Parent == null) + var memoryAccessor = _memoryAccessors.FirstOrDefault(m => m.Address == memoryItem.Address); + if (memoryAccessor != null) { - memoryReference = Field.GetMemoryReference(memoryItem.Address, memoryItem.Size); + if (memoryAccessor.PrimarySize == memoryItem.Size) + memoryAccessor.Alias = memoryItem.FunctionName; + else + memoryAccessor.SetAlias(memoryItem.Size, memoryItem.FunctionName); } - else - { - var context = scriptBuilderContext.Clone(); - var requirements = new List(); - for (var parent = memoryItem.Parent; parent != null; parent = parent.Parent) - { - var requirement = new Requirement - { - Type = RequirementType.AddAddress, - Left = new Field { Type = FieldType.MemoryAddress, Size = parent.Size, Value = parent.Address }, - }; - - if (mask != 0xFFFFFFFF) - { - if (mask == 0x00FFFFFF) - { - if (Field.GetByteSize(requirement.Left.Size) > 3) - requirement.Left = requirement.Left.ChangeSize(FieldSize.TByte); - } - else - { - requirement.Operator = RequirementOperator.BitwiseAnd; - requirement.Right = new Field { Type = FieldType.Value, Size = FieldSize.None, Value = mask }; - } - } - - requirements.Add(requirement); - } + } + } - requirements.Reverse(); + private void MakeAliasesUnique() + { + var hashSet = new HashSet(); + MakeAliasesUnique(_memoryAccessors, hashSet, SelectedNameStyle); + } - requirements.Add(new Requirement + private static void MakeAliasesUnique(IEnumerable memoryAccessors, HashSet hashSet, NameStyle nameStyle) + { + foreach (var memoryAccessor in memoryAccessors) + { + var alias = memoryAccessor.Alias; + if (!String.IsNullOrEmpty(alias)) + { + var index = 2; + while (hashSet.Contains(alias)) { - Left = new Field { Type = FieldType.MemoryAddress, Size = memoryItem.Size, Value = memoryItem.Address } - }); - - var builder = new StringBuilder(); - context.AppendRequirements(builder, requirements); - memoryReference = builder.ToString(); + alias = memoryAccessor.Alias + index; + index++; + } + hashSet.Add(alias); } - if (memoryReference != functionCall) + if (memoryAccessor.HasMultipleReferencedSizes) { - var existing = scriptBuilderContext.GetAliasDefinition(functionCall); - if (existing != null && existing != memoryReference) + foreach (var size in memoryAccessor.ReferencedSizes) { - var updateItem = memoryItem; - var count = 1; + if (size == memoryAccessor.PrimarySize) + continue; - string suffixedFunctionName = memoryItem.FunctionName + "_"; - if (memoryItem.Note != null && memoryItem.Note.Size == memoryItem.Size) + var subAlias = memoryAccessor.GetAlias(size); + if (subAlias != null) { - // this size matches the note size, give it the unsuffixed alias - scriptBuilderContext.AddAlias(memoryReference, functionCall); - - memoryReference = existing; - suffixedFunctionName += existing.Substring(0, existing.IndexOf('(')); - - updateItem = FindAlternateMemoryItemByFunctionName(_memoryItems, memoryItem); - } - else - { - var suffix = Field.GetSizeFunction(memoryItem.Size); - if (!memoryItem.FunctionName.EndsWith(suffix)) - { - suffixedFunctionName += suffix; - } - else + if (hashSet.Contains(subAlias)) { - if (!Char.IsDigit(memoryItem.FunctionName.Last())) - suffixedFunctionName = memoryItem.FunctionName; - count = 2; - } - } + var sizeSuffix = nameStyle.BuildName("x " + Field.GetSizeFunction(size)).Substring(1); + if (!subAlias.EndsWith(sizeSuffix)) + subAlias += sizeSuffix; - do - { - updateItem.FunctionName = (count == 1) ? suffixedFunctionName : (suffixedFunctionName + count); - functionCall = updateItem.FunctionName + "()"; + alias = subAlias; + var index = 2; + while (hashSet.Contains(subAlias)) + { + subAlias = alias + '_' + index; + index++; + } - existing = scriptBuilderContext.GetAliasDefinition(functionCall); - if (existing == null || existing == memoryReference) - break; + memoryAccessor.SetAlias(size, subAlias); + } - count++; - } while (true); + hashSet.Add(subAlias); + } } - - scriptBuilderContext.AddAlias(memoryReference, functionCall); } + } - if (memoryItem.HasChainedItems) - AddAliases(scriptBuilderContext, memoryItem.ChainedItems, mask); + foreach (var memoryAccessor in memoryAccessors) + { + if (memoryAccessor.Children.Any()) + MakeAliasesUnique(memoryAccessor.Children, hashSet, nameStyle); } } @@ -1109,20 +1082,20 @@ private void DumpSets(StreamWriter stream, IEnumerable published } } - private void DumpMemoryAccessors(StreamWriter stream, List lookupsToDump, ScriptBuilderContext scriptBuilderContext) + private void DumpMemoryAccessors(StreamWriter stream, List lookupsToDump, ScriptBuilderContext scriptBuilderContext, UInt32 mask) { - string addressFormat = "{0:X4}"; - if (_memoryItems.Count > 0 && _memoryItems[_memoryItems.Count - 1].Address > 0xFFFF) - addressFormat = "{0:X6}"; // TODO: addressFormat is only used in note comments - also apply to generated code - bool needLine = true; bool hadFunction = false; var dumpNotes = SelectedNoteDump; var filter = SelectedCodeNotesFilter; + uint previousAddress = UInt32.MaxValue; uint previousNoteAddress = UInt32.MaxValue; foreach (var memoryItem in _memoryItems) { + if (memoryItem.Address == previousAddress) + continue; + if (dumpNotes != NoteDump.None) { if (memoryItem.Note != null) @@ -1153,7 +1126,7 @@ private void DumpMemoryAccessors(StreamWriter stream, List lookupsToD var lines = notes.Split('\n'); stream.Write("// $"); - var address = String.Format(addressFormat, memoryItem.Address); + var address = scriptBuilderContext.FormatAddress(memoryItem.Address); stream.Write(address); stream.Write(": "); stream.WriteLine(lines[0].Trim()); @@ -1175,9 +1148,10 @@ private void DumpMemoryAccessors(StreamWriter stream, List lookupsToD continue; } - if (!String.IsNullOrEmpty(memoryItem.FunctionName)) + var memoryAccessor = _memoryAccessors.FirstOrDefault(m => m.Address == memoryItem.Address); + if (memoryAccessor != null) { - DumpMemoryFunction(stream, lookupsToDump, scriptBuilderContext, memoryItem, ref needLine); + DumpMemoryFunction(stream, lookupsToDump, scriptBuilderContext, memoryAccessor, "", ref needLine); hadFunction = true; } else @@ -1186,61 +1160,110 @@ private void DumpMemoryAccessors(StreamWriter stream, List lookupsToD } if (memoryItem.HasChainedItems) - hadFunction |= DumpNestedMemoryFunctions(stream, lookupsToDump, scriptBuilderContext, memoryItem, ref needLine); + hadFunction |= DumpNestedMemoryFunctions(stream, lookupsToDump, scriptBuilderContext, memoryItem, memoryAccessor, "", mask, ref needLine); + + previousAddress = memoryItem.Address; } } - private bool DumpNestedMemoryFunctions(StreamWriter stream, List lookupsToDump, ScriptBuilderContext scriptBuilderContext, MemoryItem parent, ref bool needLine) + private bool DumpNestedMemoryFunctions(StreamWriter stream, List lookupsToDump, ScriptBuilderContext scriptBuilderContext, MemoryItem parent, MemoryAccessorAlias parentAccessor, string parentChain, UInt32 mask, ref bool needLine) { bool hadFunction = false; + string parentFunctionCall; + if (!String.IsNullOrEmpty(parentAccessor.Alias)) + { + parentFunctionCall = String.Format("{0}()", parentAccessor.Alias); + if (Field.GetMaxValue(parentAccessor.PrimarySize) > mask) + parentFunctionCall = String.Format("({0} & 0x{1:X2})", parentFunctionCall, mask); + parentChain += parentFunctionCall + " + "; + } + else + { + parentFunctionCall = String.Format("{0}({1}0x{2})", Field.GetSizeFunction(parentAccessor.PrimarySize), parentChain, scriptBuilderContext.FormatAddress(parentAccessor.Address)); + if (Field.GetMaxValue(parentAccessor.PrimarySize) > mask) + parentFunctionCall = String.Format("({0} & 0x{1:X2})", parentFunctionCall, mask); + parentChain = parentFunctionCall + " + "; + } + + UInt32 lastOffset = UInt32.MaxValue; foreach (var memoryItem in parent.ChainedItems) { - if (!String.IsNullOrEmpty(memoryItem.FunctionName)) + if (memoryItem.Address == lastOffset) + continue; + lastOffset = memoryItem.Address; + + var memoryAccessor = parentAccessor.Children.FirstOrDefault(m => m.Address == memoryItem.Address); + if (memoryAccessor != null) { - DumpMemoryFunction(stream, lookupsToDump, scriptBuilderContext, memoryItem, ref needLine); + DumpMemoryFunction(stream, lookupsToDump, scriptBuilderContext, memoryAccessor, parentChain, ref needLine); hadFunction = true; } if (memoryItem.HasChainedItems) - hadFunction |= DumpNestedMemoryFunctions(stream, lookupsToDump, scriptBuilderContext, memoryItem, ref needLine); + hadFunction |= DumpNestedMemoryFunctions(stream, lookupsToDump, scriptBuilderContext, memoryItem, memoryAccessor, parentChain, mask, ref needLine); } return hadFunction; } - private void DumpMemoryFunction(StreamWriter stream, List lookupsToDump, ScriptBuilderContext scriptBuilderContext, MemoryItem memoryItem, ref bool needLine) + private void DumpMemoryFunction(StreamWriter stream, List lookupsToDump, ScriptBuilderContext scriptBuilderContext, MemoryAccessorAlias memoryAccessor, string parentChain, ref bool needLine) { - string memoryReference; - - if (memoryItem.Parent == null) + string address; + if (memoryAccessor.Address == 0 && !String.IsNullOrEmpty(parentChain)) { - memoryReference = Field.GetMemoryReference(memoryItem.Address, memoryItem.Size); + address = parentChain.Substring(0, parentChain.Length - 3); // remove " + " + if (address[0] != '(') + address = '(' + address + ')'; } else { - memoryReference = scriptBuilderContext.GetAliasDefinition(memoryItem.FunctionName + "()"); - if (memoryReference == null) - return; + address = String.Format("({0}0x{1})", parentChain, scriptBuilderContext.FormatAddress(memoryAccessor.Address)); } - if (needLine) + if (!String.IsNullOrEmpty(memoryAccessor.Alias) && memoryAccessor.HasReferencedSize(memoryAccessor.PrimarySize)) { - needLine = false; - stream.WriteLine(); + if (needLine) + { + needLine = false; + stream.WriteLine(); + } + + stream.Write("function "); + stream.Write(memoryAccessor.Alias); + stream.Write("() => "); + stream.Write(Field.GetSizeFunction(memoryAccessor.PrimarySize)); + stream.WriteLine(address); } - if (memoryItem.FunctionName.EndsWith("()")) - memoryItem.FunctionName = memoryItem.FunctionName.Substring(0, memoryItem.FunctionName.Length - 2); + if (memoryAccessor.HasMultipleReferencedSizes) + { + foreach (var size in memoryAccessor.ReferencedSizes) + { + if (size == memoryAccessor.PrimarySize) + continue; - stream.Write("function "); - stream.Write(memoryItem.FunctionName); - stream.Write("() => "); - stream.WriteLine(memoryReference); + var alias = memoryAccessor.GetAlias(size); + if (!String.IsNullOrEmpty(alias)) + { + if (needLine) + { + needLine = false; + stream.WriteLine(); + } + + stream.Write("function "); + stream.Write(alias); + stream.Write("() => "); + stream.Write(Field.GetSizeFunction(size)); + stream.WriteLine(address); + } + } + } foreach (var dumpLookup in lookupsToDump) { - if (dumpLookup.MemoryAddresses.Count == 1 && dumpLookup.MemoryAddresses[0].Address == memoryItem.Address && dumpLookup.MemoryAddresses[0].Size == memoryItem.Size) + if (dumpLookup.MemoryAddresses.Count == 1 && dumpLookup.MemoryAddresses[0].Address == memoryAccessor.Address) { DumpLookup(stream, dumpLookup); lookupsToDump.Remove(dumpLookup); diff --git a/Tests/Parser/AchievementBuilderTests.cs b/Tests/Parser/AchievementBuilderTests.cs index a72cb22b..150ca07e 100644 --- a/Tests/Parser/AchievementBuilderTests.cs +++ b/Tests/Parser/AchievementBuilderTests.cs @@ -193,7 +193,7 @@ private static AchievementBuilder CreateAchievement(string input, string expecte "measured(byte(0x001234) >= 100, when=word(0x002345) == 7)")] [TestCase("Q:0x 002345=7", "measured_if(word(0x002345) == 7)")] // this is an error - measured_if without measured cannot be converted to a when clause [TestCase("C:0xH464a70>d0xH464a70.1._M:0=1.8._I:0xW5b9624_Q:0xM000612=0", - "measured(tally(8, once(byte(0x464A70) > prev(byte(0x464A70)))), when=bit0(tbyte(0x5B9624) + 0x000612) == 0)")] + "measured(tally(8, once(byte(0x464A70) > prev(byte(0x464A70)))), when=bit0(tbyte(0x5B9624) + 0x612) == 0)")] public void TestParseRequirements(string input, string expected) { var builder = new AchievementBuilder(); diff --git a/Tests/Parser/Functions/MemoryAccessorFunctionTests.cs b/Tests/Parser/Functions/MemoryAccessorFunctionTests.cs index a7e48bdb..81fd1caa 100644 --- a/Tests/Parser/Functions/MemoryAccessorFunctionTests.cs +++ b/Tests/Parser/Functions/MemoryAccessorFunctionTests.cs @@ -164,28 +164,28 @@ public void TestBitOffset() [Test] [TestCase("byte(word(0x1234))", "byte(word(0x001234))")] // direct pointer - [TestCase("byte(word(0x1234) + 10)", "byte(word(0x001234) + 0x00000A)")] // indirect pointer - [TestCase("byte(10 + word(0x1234))", "byte(word(0x001234) + 0x00000A)")] // indirect pointer - [TestCase("byte(0x1234 + word(0x2345))", "byte(word(0x002345) + 0x001234)")] // array index + [TestCase("byte(word(0x1234) + 10)", "byte(word(0x001234) + 0x0A)")] // indirect pointer + [TestCase("byte(10 + word(0x1234))", "byte(word(0x001234) + 0x0A)")] // indirect pointer + [TestCase("byte(0x1234 + word(0x2345))", "byte(word(0x002345) + 0x1234)")] // array index [TestCase("byte(word(word(0x1234)))", "byte(word(word(0x001234)))")] // double direct pointer - [TestCase("byte(0x1234 + word(word(0x2345) + 10))", "byte(word(word(0x002345) + 0x00000A) + 0x001234)")] // double indirect pointer + [TestCase("byte(0x1234 + word(word(0x2345) + 10))", "byte(word(word(0x002345) + 0x0A) + 0x1234)")] // double indirect pointer [TestCase("byte(prev(word(0x1234)))", "byte(prev(word(0x001234)))")] // direct pointer using prev data - [TestCase("byte(word(0x1234) * 2)", "byte(word(0x001234) * 0x00000002)")] // scaled direct pointer [unexpected] - [TestCase("byte(word(0x2345) * 2 + 0x1234)", "byte(word(0x002345) * 0x00000002 + 0x001234)")] // scaled array index - [TestCase("byte(0x1234 + word(0x2345) * 2)", "byte(word(0x002345) * 0x00000002 + 0x001234)")] // scaled array index + [TestCase("byte(word(0x1234) * 2)", "byte(word(0x001234) * 2)")] // scaled direct pointer [unexpected] + [TestCase("byte(word(0x2345) * 2 + 0x1234)", "byte(word(0x002345) * 2 + 0x1234)")] // scaled array index + [TestCase("byte(0x1234 + word(0x2345) * 2)", "byte(word(0x002345) * 2 + 0x1234)")] // scaled array index [TestCase("byte(word(word(0x2345) * 2 + 0x1234) * 4 + 0x3456)", - "byte(word(word(0x002345) * 0x00000002 + 0x001234) * 0x00000004 + 0x003456)")] // double scaled array index + "byte(word(word(0x002345) * 2 + 0x1234) * 4 + 0x3456)")] // double scaled array index [TestCase("bit(3, word(0x1234))", "bit3(word(0x001234))")] // direct pointer - [TestCase("bit(18, word(0x1234))", "bit2(word(0x001234) + 0x000002)")] // direct pointer - [TestCase("bit(3, word(0x1234) + 10)", "bit3(word(0x001234) + 0x00000A)")] // indirect pointer - [TestCase("bit(18, word(0x1234) + 10)", "bit2(word(0x001234) + 0x00000C)")] // indirect pointer - [TestCase("bit(18, 10 + word(0x1234))", "bit2(word(0x001234) + 0x00000C)")] // indirect pointer - [TestCase("bit(18, prev(word(0x1234)))", "bit2(prev(word(0x001234)) + 0x000002)")] // direct pointer using prev data - [TestCase("bit(18, 0x1234 + word(0x2345) * 2)", "bit2(word(0x002345) * 0x00000002 + 0x001236)")] // scaled array index + [TestCase("bit(18, word(0x1234))", "bit2(word(0x001234) + 0x02)")] // direct pointer + [TestCase("bit(3, word(0x1234) + 10)", "bit3(word(0x001234) + 0x0A)")] // indirect pointer + [TestCase("bit(18, word(0x1234) + 10)", "bit2(word(0x001234) + 0x0C)")] // indirect pointer + [TestCase("bit(18, 10 + word(0x1234))", "bit2(word(0x001234) + 0x0C)")] // indirect pointer + [TestCase("bit(18, prev(word(0x1234)))", "bit2(prev(word(0x001234)) + 0x02)")] // direct pointer using prev data + [TestCase("bit(18, 0x1234 + word(0x2345) * 2)", "bit2(word(0x002345) * 2 + 0x1236)")] // scaled array index [TestCase("byte(word(0x1234) - 10)", "byte(word(0x001234) + 0xFFFFFFF6)")] - [TestCase("byte(word(0x1234) / 2)", "byte(word(0x001234) / 0x00000002)")] + [TestCase("byte(word(0x1234) / 2)", "byte(word(0x001234) / 2)")] [TestCase("byte(word(0x1234) & 0x1FF)", "byte(word(0x001234) & 0x000001FF)")] - [TestCase("byte((word(0x1234) & 0x1FF) + 99)", "byte((word(0x001234) & 0x000001FF) + 0x000063)")] + [TestCase("byte((word(0x1234) & 0x1FF) + 99)", "byte(word(0x001234) & 0x000001FF + 0x63)")] public void TestAddAddress(string input, string expected) { var requirements = Evaluate(input); diff --git a/Tests/Parser/Internal/RequirementsOptimizerTests.cs b/Tests/Parser/Internal/RequirementsOptimizerTests.cs index da3bf19b..665b0bb9 100644 --- a/Tests/Parser/Internal/RequirementsOptimizerTests.cs +++ b/Tests/Parser/Internal/RequirementsOptimizerTests.cs @@ -334,7 +334,7 @@ public void TestOptimizeMergeBits(string input, string expected) } [TestCase("word(word(0x001234) + 138) + 1 >= word(0x2345)", // AddAddress compared to a non-AddAddress will generate an extra condition - "((word(word(0x001234) + 0x00008A)) + 1) >= word(0x002345)")] // to prevent the AddAddress from affecting the non-AddAddress. merge the +1 into that + "(word(word(0x001234) + 0x8A) + 1) >= word(0x002345)")] // to prevent the AddAddress from affecting the non-AddAddress. merge the +1 into that [TestCase("never(once(prev(byte(1)) - 1 == byte(1)) && repeated(10, always_true())", "never(repeated(10, (once((prev(byte(0x000001)) - 1) == byte(0x000001))) && always_true()))")] // don't merge the -1 in the prev clause with the 1 in the always_true clause public void TestOptimizeMergeAddSourceConstants(string input, string expected) diff --git a/Tests/Parser/Internal/ScriptBuilderContextTests.cs b/Tests/Parser/Internal/ScriptBuilderContextTests.cs index 162cbdea..a77685fb 100644 --- a/Tests/Parser/Internal/ScriptBuilderContextTests.cs +++ b/Tests/Parser/Internal/ScriptBuilderContextTests.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using RATools.Data; +using System.Collections.Generic; using System.Text; namespace RATools.Parser.Tests.Internal @@ -17,8 +18,8 @@ class ScriptBuilderContextTests [TestCase("A:0xH001234=0_0xH002345=2", "(byte(0x001234) + byte(0x002345)) == 2")] [TestCase("B:0xH001234=0_0xH002345=2", "(byte(0x002345) - byte(0x001234)) == 2")] [TestCase("A:0xH001234=0_R:0xH002345=2", "never((byte(0x001234) + byte(0x002345)) == 2)")] - [TestCase("I:0x 001234_0xH002345=2", "byte(word(0x001234) + 0x002345) == 2")] - [TestCase("I:0x 001234_I:0x 002222_0xH002345=2", "byte(word(word(0x001234) + 0x002222) + 0x002345) == 2")] + [TestCase("I:0x 001234_0xH002345=2", "byte(word(0x001234) + 0x2345) == 2")] + [TestCase("I:0x 001234_I:0x 002222_0xH002345=2", "byte(word(word(0x001234) + 0x2222) + 0x2345) == 2")] [TestCase("O:0xH001234=1_0xH002345=2", "(byte(0x001234) == 1 || byte(0x002345) == 2)")] [TestCase("O:0xH001234=1_0=1", "byte(0x001234) == 1")] [TestCase("O:0=1_0xH002345=2", "byte(0x002345) == 2")] @@ -67,7 +68,7 @@ class ScriptBuilderContextTests [TestCase("Z:0x 000001=1_N:0x 000002=2.1._O:0x 000003=3_0x 000004=4", "((once(word(0x000002) == 2) && word(0x000003) == 3) || word(0x000004) == 4) && never(word(0x000001) == 1)")] [TestCase("I:0xX001234_A:0xH000001/0xH000001_0=1", - "((byte(dword(0x001234) + 0x000001) / byte(dword(0x001234) + 0x000001))) == 1")] + "(byte(dword(0x001234) + 0x01) / byte(dword(0x001234) + 0x01)) == 1")] public void TestAppendRequirements(string input, string expected) { var trigger = Trigger.Deserialize(input); @@ -145,5 +146,46 @@ public void TestAppendRequirementsValue(string input, string expected) // make sure we didn't modify the source requirements Assert.That(trigger.Serialize(new SerializationContext()), Is.EqualTo(input)); } + + [TestCase("0xH1234=6", "lives() == 6")] + [TestCase("0xH1236=6", "byte(0x001236) == 6")] + [TestCase("0xH1234>d0xH1234", "lives() > prev(lives())")] + [TestCase("I:0xX2345_0xX0004<0xX0008", "stage_1_coins() < stage_2_coins()")] + [TestCase("I:0xX2345_0xX0008>99", "stage_2_coins() > 99")] + [TestCase("I:0xX2345_0xX000C!=0", "dword(pointer() + 0x0C) != 0")] + [TestCase("I:0xX2345&65535_0xX000C!=0", "dword(pointer() & 0xFFFF + 0x0C) != 0")] + [TestCase("I:0xX9999_0xX000C!=0", "dword(dword(0x009999) + 0x0C) != 0")] + [TestCase("I:0xX0100_I:0xX0004_0xX0008=5", "current_level() == 5")] + [TestCase("I:0xX2345_0xX0004>d0xX0004.1._0xH3333=6", "once(stage_1_coins() > prev(stage_1_coins())) && byte(0x003333) == 6")] + public void TestAppendRequirementAliased(string input, string expected) + { + var trigger = Trigger.Deserialize(input); + var context = new ScriptBuilderContext(); + + var notes = new CodeNote[] + { + new CodeNote(0x1234, "[8-bit] Lives"), + new CodeNote(0x2345, "[32-bit] Pointer\r\n+0x04=[32-bit] Stage 1 Coins\r\n+0x08=[32-bit] Stage 2 Coins\r\n"), + new CodeNote(0x0100, "[32-bit] Pointer\r\n+0x04=[32-bit] Nested Pointer\r\n++0x08=[32-bit] Current Level\r\n"), + }; + + var notesDict = new Dictionary(); + foreach (var note in notes) + notesDict[note.Address] = note; + + var memoryAccessors = new List(); + MemoryAccessorAlias.AddMemoryAccessors(memoryAccessors, trigger, notesDict); + + foreach (var memoryAccessor in memoryAccessors) + { + memoryAccessor.UpdateAliasFromNote(NameStyle.SnakeCase); + context.AddAlias(memoryAccessor); + } + + var builder = new StringBuilder(); + context.AppendRequirements(builder, trigger.Core.Requirements); + + Assert.That(builder.ToString(), Is.EqualTo(expected)); + } } } From d9b92167fa559b5b7cf261b5a554f8bdfdce6b4d Mon Sep 17 00:00:00 2001 From: Jamiras Date: Thu, 4 Dec 2025 09:32:00 -0700 Subject: [PATCH 2/3] fix AddAddress{recall} handling --- Source/Parser/ScriptBuilderContext.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Source/Parser/ScriptBuilderContext.cs b/Source/Parser/ScriptBuilderContext.cs index c6a8b838..d2a77141 100644 --- a/Source/Parser/ScriptBuilderContext.cs +++ b/Source/Parser/ScriptBuilderContext.cs @@ -112,6 +112,7 @@ private ScriptBuilderContext CreateNestedContext() context._resetNextIf = _resetNextIf; context._measuredIf = _measuredIf; context._aliases = _aliases; + context._remember = _remember; return context; } @@ -244,7 +245,17 @@ private void AppendRequirementEx(StringBuilder builder, RequirementEx requiremen switch (requirement.Type) { case RequirementType.AddAddress: - if (requirement.Left.IsMemoryReference) + if (requirement.Left.Type == FieldType.Recall) + { + _addAddress = new MemoryAccessorAliasChain() + { + Alias = new MemoryAccessorAlias(0), + Next = null, + Requirement = requirement, + }; + continue; + } + else if (requirement.Left.IsMemoryReference) { var currentAliases = (_addAddress != null) ? _addAddress.Alias.Children : _aliases; var memoryAccessor = currentAliases.FirstOrDefault(a => a.Address == requirement.Left.Value); @@ -831,6 +842,9 @@ private void AppendFieldAlias(StringBuilder builder, Field field, MemoryAccessor case FieldType.Invert: builder.Append('~'); break; + case FieldType.Recall: + builder.Append(_remember); + return; } var functionName = alias?.GetAlias(field.Size); From 9b62d48603a5441492b9f757a905e1055ec145ac Mon Sep 17 00:00:00 2001 From: Jamiras Date: Thu, 4 Dec 2025 10:53:55 -0700 Subject: [PATCH 3/3] don't include offset when generating Remember in a MemoryAccessor --- .../Trigger/MemoryAccessorExpression.cs | 10 +++++-- .../Functions/MemoryAccessorFunction.cs | 6 ++-- .../Parser/Expressions/ExpressionBaseTests.cs | 1 - .../Trigger/MemoryAccessorExpressionTests.cs | 10 +++---- .../Trigger/MemoryValueExpressionTests.cs | 30 +++++++++---------- .../ModifiedMemoryAcessorExpressionTests.cs | 12 ++++---- .../RequirementClauseExpressionTests.cs | 2 +- .../RequirementConditionExpression_Tests.cs | 22 +++++++------- .../Functions/MemoryAccessorFunctionTests.cs | 13 ++++---- Tests/Parser/Functions/TallyFunctionTests.cs | 2 +- .../Internal/TriggerBuilderContextTests.cs | 2 +- 11 files changed, 58 insertions(+), 52 deletions(-) diff --git a/Source/Parser/Expressions/Trigger/MemoryAccessorExpression.cs b/Source/Parser/Expressions/Trigger/MemoryAccessorExpression.cs index 8b633caa..0a67ea29 100644 --- a/Source/Parser/Expressions/Trigger/MemoryAccessorExpression.cs +++ b/Source/Parser/Expressions/Trigger/MemoryAccessorExpression.cs @@ -237,6 +237,12 @@ internal override void AppendString(StringBuilder builder) if (_rememberPointer != null) { _rememberPointer.AppendString(builder); + + if (Field.Value != 0) + { + builder.Append(" + "); + builder.AppendFormat("0x{0:X2}", Field.Value); + } } else if (_pointerChain != null) { @@ -253,7 +259,7 @@ internal override void AppendString(StringBuilder builder) if (i == 0) builder.AppendFormat("0x{0:X6}", _pointerChain[i].Left.Value); else if (_pointerChain[i].Left.Value != 0) - builder.Append(_pointerChain[i].Left.Value); + builder.AppendFormat("0x{0:X2}", _pointerChain[i].Left.Value); else builder.Length -= 3; @@ -292,7 +298,7 @@ internal override void AppendString(StringBuilder builder) } if (Field.Value != 0) - builder.Append(Field.Value); + builder.AppendFormat("0x{0:X2}", Field.Value); else builder.Length -= 3; } diff --git a/Source/Parser/Functions/MemoryAccessorFunction.cs b/Source/Parser/Functions/MemoryAccessorFunction.cs index 2cb9e113..7982f1d6 100644 --- a/Source/Parser/Functions/MemoryAccessorFunction.cs +++ b/Source/Parser/Functions/MemoryAccessorFunction.cs @@ -111,8 +111,10 @@ private static MemoryAccessorExpression CreateMemoryAccessorExpression(MemoryVal if (needRemember) { - memoryAccessor = new MemoryAccessorExpression(FieldType.MemoryAddress, size, 0U); - memoryAccessor.RememberPointer = new RememberRecallExpression(memoryValue); + memoryAccessor = new MemoryAccessorExpression(FieldType.MemoryAddress, size, (uint) memoryValue.IntegerConstant); + memoryAccessor.RememberPointer = memoryValue.HasConstant ? + new RememberRecallExpression((MemoryValueExpression)memoryValue.ClearConstant()) : + new RememberRecallExpression(memoryValue); } else { diff --git a/Tests/Parser/Expressions/ExpressionBaseTests.cs b/Tests/Parser/Expressions/ExpressionBaseTests.cs index 7e81760e..d237b107 100644 --- a/Tests/Parser/Expressions/ExpressionBaseTests.cs +++ b/Tests/Parser/Expressions/ExpressionBaseTests.cs @@ -1,7 +1,6 @@ using Jamiras.Components; using NUnit.Framework; using RATools.Parser.Expressions; -using RATools.Parser.Internal; using System.Linq; using System.Text; diff --git a/Tests/Parser/Expressions/Trigger/MemoryAccessorExpressionTests.cs b/Tests/Parser/Expressions/Trigger/MemoryAccessorExpressionTests.cs index 6191c2fe..65ec615d 100644 --- a/Tests/Parser/Expressions/Trigger/MemoryAccessorExpressionTests.cs +++ b/Tests/Parser/Expressions/Trigger/MemoryAccessorExpressionTests.cs @@ -10,11 +10,11 @@ class MemoryAccessorExpressionTests [Test] [TestCase("byte(0x001234)")] [TestCase("low4(word(0x001234))")] - [TestCase("word(dword(0x002345) + 4660)")] + [TestCase("word(dword(0x002345) + 0x1234)")] [TestCase("word(dword(0x002345) + dword(0x003456))")] - [TestCase("bit6(word(0x002345) * 106 + 4660)")] - [TestCase("byte(word(dword(0x002345) + 10) + 4660)")] - [TestCase("word((dword(0x002345) & 0x1FFFFFF) + 4660)")] + [TestCase("bit6(word(0x002345) * 106 + 0x1234)")] + [TestCase("byte(word(dword(0x002345) + 0x0A) + 0x1234)")] + [TestCase("word((dword(0x002345) & 0x1FFFFFF) + 0x1234)")] [TestCase("prev(high4(0x001234))")] [TestCase("prior(bit0(0x001234))")] public void TestAppendString(string input) @@ -42,7 +42,7 @@ public void TestAppendString(string input) [TestCase("prev(high4(0x001234))", "d0xU001234")] [TestCase("prior(bit0(0x001234))", "p0xM001234")] [TestCase("dword(dword(0x1234) + ((word(0x2345) & 0x3FF) * 8 + 4))", - "K:0x 002345&1023_A:{recall}*8_A:4_K:0xX001234_I:{recall}_0xX000000")] + "K:0x 002345&1023_A:{recall}*8_K:0xX001234_I:{recall}_0xX000004")] public void TestBuildTrigger(string input, string expected) { var accessor = TriggerExpressionTests.Parse(input); diff --git a/Tests/Parser/Expressions/Trigger/MemoryValueExpressionTests.cs b/Tests/Parser/Expressions/Trigger/MemoryValueExpressionTests.cs index 09ed0b15..23cc62d8 100644 --- a/Tests/Parser/Expressions/Trigger/MemoryValueExpressionTests.cs +++ b/Tests/Parser/Expressions/Trigger/MemoryValueExpressionTests.cs @@ -150,8 +150,8 @@ public void TestCombineInverse(string left, string operation, string right, Expr ExpressionType.None, null)] // no change necessary [TestCase("0 + byte(0x001234) - 9", "=", "0", ExpressionType.Comparison, "byte(0x001234) == 9")] - [TestCase("byte(tbyte(0x001234) + 1) + byte(tbyte(0x001234) + 2) - 7", ">=", "byte(tbyte(0x001234) + 3)", - ExpressionType.Comparison, "byte(tbyte(0x001234) + 1) + byte(tbyte(0x001234) + 2) - byte(tbyte(0x001234) + 3) + 255 >= 262")] // undeflow applied + [TestCase("byte(tbyte(0x001234) + 0x01) + byte(tbyte(0x001234) + 0x02) - 7", ">=", "byte(tbyte(0x001234) + 0x03)", + ExpressionType.Comparison, "byte(tbyte(0x001234) + 0x01) + byte(tbyte(0x001234) + 0x02) - byte(tbyte(0x001234) + 0x03) + 255 >= 262")] // undeflow applied [TestCase("dword(dword(0x001234)) + dword(dword(0x001236)) - prev(dword(dword(0x001234))) - prev(dword(dword(0x001236)))", "==", "0", ExpressionType.Comparison, "dword(dword(0x001234)) - prev(dword(dword(0x001234))) + dword(dword(0x001236)) == prev(dword(dword(0x001236)))")] // SubSource prev moved to right side, AddSource moved to line up with right side [TestCase("dword(dword(0x001234)) + dword(dword(0x001236)) - prev(dword(dword(0x001234))) - prev(dword(dword(0x001236)))", ">", "0", @@ -160,9 +160,9 @@ public void TestCombineInverse(string left, string operation, string right, Expr ExpressionType.Comparison, "dword(dword(0x001234)) - prev(dword(dword(0x001234))) + dword(dword(0x001236)) > prev(dword(dword(0x001236)))")] // AddSource moved to line up with right side [TestCase("dword(dword(0x001234)) - prev(dword(dword(0x001234))) + dword(dword(0x001236))", ">", "prev(dword(dword(0x001236)))", ExpressionType.None, null)] // no change necessary - [TestCase("dword(dword(0x001234) + 5) - dword(dword(0x001234) + 6) + dword(dword(0x001234) + 7)", "==", "500", - ExpressionType.Comparison, "dword(dword(0x001234) + 5) + dword(dword(0x001234) + 7) - 500 == dword(dword(0x001234) + 6)")] - [TestCase("dword(dword(0x001234) + 5) - dword(dword(0x001234) + 6) + dword(dword(0x001234) + 7)", ">", "500", + [TestCase("dword(dword(0x001234) + 0x05) - dword(dword(0x001234) + 0x06) + dword(dword(0x001234) + 0x07)", "==", "500", + ExpressionType.Comparison, "dword(dword(0x001234) + 0x05) + dword(dword(0x001234) + 0x07) - 500 == dword(dword(0x001234) + 0x06)")] + [TestCase("dword(dword(0x001234) + 0x05) - dword(dword(0x001234) + 0x06) + dword(dword(0x001234) + 0x07)", ">", "500", ExpressionType.None, null)] // non-equality comparison may underflow if the "500" is changed to "-500" and moved to the left. don't change it. public void TestNormalizeComparison(string left, string operation, string right, ExpressionType expectedType, string expected) { @@ -322,28 +322,28 @@ private static bool IsComparisonTrue(ExpressionBase expression, bool signed) // invalid syntax (indirect memory references are not allowed on the right side), so go // one step farther to see the final optimized logic. [TestCase("byte(byte(0x000002) + 1) - byte(byte(0x000002) + 2) > 100", - "byte(byte(0x000002) + 2) + 100 < byte(byte(0x000002) + 1)", // A - B > 100 ~> B + 100 < A + "byte(byte(0x000002) + 0x02) + 100 < byte(byte(0x000002) + 0x01)", // A - B > 100 ~> B + 100 < A "A:100_I:0xH000002_0xH000002<0xH000001")] // both A and B have the same base pointer [TestCase("byte(byte(0x000002) + 1) - byte(byte(0x000002) + 2) > -100", - "byte(byte(0x000002) + 1) + 100 > byte(byte(0x000002) + 2)", // A - B > -100 ~> A + 100 > B + "byte(byte(0x000002) + 0x01) + 100 > byte(byte(0x000002) + 0x02)", // A - B > -100 ~> A + 100 > B "A:100_I:0xH000002_0xH000001>0xH000002")] // both A and B have the same base pointer [TestCase("byte(byte(0x000002) + 1) - byte(byte(0x000003) + 2) > 100", - "byte(byte(0x000002) + 1) - byte(byte(0x000003) + 2) + 255 > 355", // different base pointer causes secondary AddSource + "byte(byte(0x000002) + 0x01) - byte(byte(0x000003) + 0x02) + 255 > 355", // different base pointer causes secondary AddSource "A:255_I:0xH000003_B:0xH000002_I:0xH000002_0xH000001>355")] [TestCase("byte(byte(0x000002) + 1) - 1 == prev(byte(byte(0x000002) + 1))", - "byte(byte(0x000002) + 1) - 1 == prev(byte(byte(0x000002) + 1))", + "byte(byte(0x000002) + 0x01) - 1 == prev(byte(byte(0x000002) + 0x01))", "B:1_I:0xH000002_0xH000001=d0xH000001")] // both A and B have the same base pointer [TestCase("prev(byte(byte(0x000002) + 1)) == byte(byte(0x000002) + 1) - 1", - "prev(byte(byte(0x000002) + 1)) + 1 == byte(byte(0x000002) + 1)", + "prev(byte(byte(0x000002) + 0x01)) + 1 == byte(byte(0x000002) + 0x01)", "A:1_I:0xH000002_d0xH000001=0xH000001")] // constant will be moved [TestCase("byte(byte(0x000002) + 1) - prev(byte(byte(0x000002) + 1)) == 1", - "byte(byte(0x000002) + 1) - 1 == prev(byte(byte(0x000002) + 1))", + "byte(byte(0x000002) + 0x01) - 1 == prev(byte(byte(0x000002) + 0x01))", "B:1_I:0xH000002_0xH000001=d0xH000001")] // prev(A) and -1 will be swapped to share pointer [TestCase("word(54) - word(word(43102) + 54) > 37", - "word(word(0x00A85E) + 54) + 37 < word(0x000036)", // A - B > 37 ~> B + 37 < A + "word(word(0x00A85E) + 0x36) + 37 < word(0x000036)", // A - B > 37 ~> B + 37 < A "I:0x 00a85e_A:0x 000036_37<0x 000036")] // underflow with combination of direct/indirect, word size [TestCase("word(0x000036) + 37 >= word(word(0x00A85E) + 54)", - "word(word(0x00A85E) + 54) - word(0x000036) + 65535 <= 65572", // move A to right side, reverse and adjust for underflow + "word(word(0x00A85E) + 0x36) - word(0x000036) + 65535 <= 65572", // move A to right side, reverse and adjust for underflow "A:65535_B:0x 000036_I:0x 00a85e_0x 000036<=65572")] // combination of direct/indirect, word size [TestCase("word(0x000001) - word(0x000002) + word(0x000003) < 100", "word(0x000001) - word(0x000002) + word(0x000003) + 65535 < 65635", // possible underflow of 65535 @@ -497,7 +497,7 @@ public void TestMergeBitCount(string input, string normalized) wrapped = false; break; case 'I': - builder.Append("(dword(0x005555) + 4106)"); // 4106 = 0x00100A + builder.Append("(dword(0x005555) + 0x100A)"); if (wrapped) { builder.Append(')'); @@ -505,7 +505,7 @@ public void TestMergeBitCount(string input, string normalized) } break; case 'J': - builder.Append("(dword(0x006666) + 4106)"); // 4106 = 0x00100A + builder.Append("(dword(0x006666) + 0x100A)"); if (wrapped) { builder.Append(')'); diff --git a/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs b/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs index 155097fb..d64ea2d4 100644 --- a/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs +++ b/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs @@ -21,7 +21,7 @@ class ModifiedMemoryAccessorExpressionTests [TestCase("byte(0x001234) * byte(0x001234)")] [TestCase("low4(word(0x001234)) * 20")] [TestCase("prev(high4(0x001234)) * 16")] - [TestCase("low4(word(0x001234)) * high4(word(0x001234) + 10)")] + [TestCase("low4(word(0x001234)) * high4(word(0x001234) + 0x10)")] [TestCase("byte(0x001234) * -1")] public void TestAppendString(string input) { @@ -110,15 +110,15 @@ public void TestBuildTrigger(string input, string expected) [TestCase("byte(0x001234) & 10", "^", "6", ExpressionType.MemoryAccessor, "remembered(byte(0x001234) & 0x0000000A) ^ 0x00000006")] // cannot merge [TestCase("byte(dword(0x001234) + 0x10)", "*", "byte(dword(0x002345) + 0x14)", - ExpressionType.MemoryAccessor, "byte(dword(0x001234) + 16) * remembered(byte(dword(0x002345) + 20))")] + ExpressionType.MemoryAccessor, "byte(dword(0x001234) + 0x10) * remembered(byte(dword(0x002345) + 0x14))")] [TestCase("byte(dword(0x001234) + 0x10)", "/", "byte(dword(0x002345) + 0x14)", - ExpressionType.MemoryAccessor, "byte(dword(0x001234) + 16) / remembered(byte(dword(0x002345) + 20))")] + ExpressionType.MemoryAccessor, "byte(dword(0x001234) + 0x10) / remembered(byte(dword(0x002345) + 0x14))")] [TestCase("byte(dword(0x001234) + 0x10)", "*", "byte(0x10)", - ExpressionType.MemoryAccessor, "byte(dword(0x001234) + 16) * remembered(byte(0x000010))")] + ExpressionType.MemoryAccessor, "byte(dword(0x001234) + 0x10) * remembered(byte(0x000010))")] [TestCase("byte(dword(0x001234) + 0x10)", "/", "byte(0x10)", - ExpressionType.MemoryAccessor, "byte(dword(0x001234) + 16) / remembered(byte(0x000010))")] + ExpressionType.MemoryAccessor, "byte(dword(0x001234) + 0x10) / remembered(byte(0x000010))")] [TestCase("byte(0x10) * 10", "*", "byte(dword(0x002345) + 0x14)", - ExpressionType.MemoryAccessor, "byte(dword(0x002345) + 20) * remembered(byte(0x000010) * 10)")] + ExpressionType.MemoryAccessor, "byte(dword(0x002345) + 0x14) * remembered(byte(0x000010) * 10)")] [TestCase("byte(0x10) * 10", "/", "byte(dword(0x002345) + 0x14)", ExpressionType.Error, "Cannot divide two complex expressions")] [TestCase("byte(0x001234) * 10", "&", "2.5", diff --git a/Tests/Parser/Expressions/Trigger/RequirementClauseExpressionTests.cs b/Tests/Parser/Expressions/Trigger/RequirementClauseExpressionTests.cs index 0880d740..902dd167 100644 --- a/Tests/Parser/Expressions/Trigger/RequirementClauseExpressionTests.cs +++ b/Tests/Parser/Expressions/Trigger/RequirementClauseExpressionTests.cs @@ -69,7 +69,7 @@ public void TestBuildTrigger(string input, string expected) [TestCase("byte(0x001234) == 3 && trigger_when(__ornext(byte(word(0x002345) + 8) == 4 || (byte(word(0x002345) + 8) >= 7 && byte(word(0x002345) + 8) <= 10)))", "0xH001234=3_I:0x 002345_N:0xH000008>=7_I:0x 002345_O:0xH000008<=10_I:0x 002345_T:0xH000008=4")] [TestCase("dword(dword(0x1234) + ((word(0x2345) & 0x3FF) * 8 + 4)) == 6 && prev(dword(dword(0x1234) + ((word(0x2345) & 0x3FF) * 8 + 4))) == 0", - "K:0x 002345&1023_A:{recall}*8_A:4_K:0xX001234_I:{recall}_0xX000000=6_K:0x 002345&1023_A:{recall}*8_A:4_K:0xX001234_I:{recall}_d0xX000000=0")] // remember chain will be duplicated here, it'll get optimized out later + "K:0x 002345&1023_A:{recall}*8_K:0xX001234_I:{recall}_0xX000004=6_K:0x 002345&1023_A:{recall}*8_K:0xX001234_I:{recall}_d0xX000004=0")] // remember chain will be duplicated here, it'll get optimized out later public void TestBuildAchievement(string input, string expected) { var clause = TriggerExpressionTests.Parse(input); diff --git a/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs b/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs index cadd0d96..eb6bfa8c 100644 --- a/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs +++ b/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs @@ -12,7 +12,7 @@ class RequirementConditionExpressionTests [Test] [TestCase("byte(0x001234) == 3")] [TestCase("low4(word(0x001234)) < 5")] - [TestCase("word(dword(0x002345) + 4660) >= 500")] + [TestCase("word(dword(0x002345) + 0x1234) >= 500")] [TestCase("byte(0x001234) > prev(byte(0x001234))")] public void TestAppendString(string input) { @@ -211,19 +211,19 @@ public void TestAddAddressCompareToAddress() [Test] [TestCase("byte(WA) > prev(byte(WA))", "byte(WA) > prev(byte(WA))")] // simple compare to prev of same address - [TestCase("byte(WA + 10) > prev(byte(WA + 10))", "byte(WA + 10) > prev(byte(WA + 10))")] // simple compare to prev of same address with offset - [TestCase("byte(WA + 10) > byte(WA + 11)", "byte(WA + 10) > byte(WA + 11)")] // same base, different offsets - [TestCase("byte(WA + 10) > byte(WB + 10)", "byte(WA + 10) - byte(WB + 10) + 255 > 255")] // different bases, same offset + [TestCase("byte(WA + 0x0A) > prev(byte(WA + 0x0A))", "byte(WA + 0x0A) > prev(byte(WA + 0x0A))")] // simple compare to prev of same address with offset + [TestCase("byte(WA + 0x0A) > byte(WA + 0x0B)", "byte(WA + 0x0A) > byte(WA + 0x0B)")] // same base, different offsets + [TestCase("byte(WA + 0x0A) > byte(WB + 0x0A)", "byte(WA + 0x0A) - byte(WB + 0x0A) + 255 > 255")] // different bases, same offset [TestCase("byte(WA) == byte(WB)", "byte(WA) - byte(WB) == 0")] // becomes B-A==0 [TestCase("byte(WA) != byte(WB)", "byte(WA) - byte(WB) != 0")] // becomes B-A!=0 [TestCase("byte(WA) > byte(WB)", "byte(WA) - byte(WB) + 255 > 255")] // becomes B-A>M [TestCase("byte(WA) >= byte(WB)", "byte(WA) - byte(WB) + 255 >= 255")] // becomes B-A-1>=M [TestCase("byte(WA) < byte(WB)", "byte(WA) - byte(WB) + 255 < 255")] // becomes B-A+M>M [TestCase("byte(WA) <= byte(WB)", "byte(WA) - byte(WB) + 255 <= 255")] // becomes B-A+M>=M - [TestCase("byte(WA + 10) + 20 > byte(WB)", "byte(word(0x002345)) - byte(word(0x001234) + 10) + 255 < 275")] - [TestCase("byte(WA + 10) > byte(WB) - 20", "byte(word(0x002345)) - byte(word(0x001234) + 10) + 255 < 275")] - [TestCase("byte(WA + 10) - 20 > byte(WB)", "byte(word(0x001234) + 10) - byte(word(0x002345)) + 255 > 275")] - [TestCase("byte(WA + 10) > byte(WB) + 20", "byte(word(0x001234) + 10) - byte(word(0x002345)) + 255 > 275")] + [TestCase("byte(WA + 0x0A) + 20 > byte(WB)", "byte(word(0x002345)) - byte(word(0x001234) + 0x0A) + 255 < 275")] + [TestCase("byte(WA + 0x0A) > byte(WB) - 20", "byte(word(0x002345)) - byte(word(0x001234) + 0x0A) + 255 < 275")] + [TestCase("byte(WA + 0x0A) - 20 > byte(WB)", "byte(word(0x001234) + 0x0A) - byte(word(0x002345)) + 255 > 275")] + [TestCase("byte(WA + 0x0A) > byte(WB) + 20", "byte(word(0x001234) + 0x0A) - byte(word(0x002345)) + 255 > 275")] [TestCase("word(WA) > word(WB)", "word(WA) - word(WB) + 65535 > 65535")] // different addresses (16-bit) [TestCase("byte(WA) > word(WB)", "byte(WA) - word(WB) + 65535 > 65535")] // different sizes and addresses [TestCase("word(WA) > byte(WB)", "word(WA) - byte(WB) + 255 > 255")] // different sizes and addresses @@ -231,9 +231,9 @@ public void TestAddAddressCompareToAddress() [TestCase("byte(WA) > tbyte(WB)", "byte(WA) - tbyte(WB) + 16777215 > 16777215")] // different sizes and addresses [TestCase("tbyte(WA) > word(WB)", "tbyte(WA) - word(WB) + 65535 > 65535")] // different sizes and addresses [TestCase("byte(WA) > dword(WB)", "byte(WA) - dword(WB) > 0")] // no underflow adjustment for 32-bit reads - [TestCase("byte(word(WA + 10) + 2) > prev(byte(word(WA + 10) + 2)", // simple compare to prev of same address (double indirect) - "byte(word(WA + 10) + 2) > prev(byte(word(WA + 10) + 2))")] - [TestCase("bit(18, WA + 10) > prev(bit(18, WA + 10))", "bit2(WA + 12) > prev(bit2(WA + 12))")] // simple compare to prev of same address with offset + [TestCase("byte(word(WA + 0x0A) + 2) > prev(byte(word(WA + 0x0A) + 2)", // simple compare to prev of same address (double indirect) + "byte(word(WA + 0x0A) + 0x02) > prev(byte(word(WA + 0x0A) + 0x02))")] + [TestCase("bit(18, WA + 0x0A) > prev(bit(18, WA + 0x0A))", "bit2(WA + 0x0C) > prev(bit2(WA + 0x0C))")] // simple compare to prev of same address with offset public void TestAddAddressAcrossCondition(string input, string expected) { input = input.Replace("WA", "word(0x1234)"); diff --git a/Tests/Parser/Functions/MemoryAccessorFunctionTests.cs b/Tests/Parser/Functions/MemoryAccessorFunctionTests.cs index 81fd1caa..f405728b 100644 --- a/Tests/Parser/Functions/MemoryAccessorFunctionTests.cs +++ b/Tests/Parser/Functions/MemoryAccessorFunctionTests.cs @@ -5,6 +5,7 @@ using RATools.Parser.Expressions.Trigger; using RATools.Parser.Functions; using RATools.Parser.Internal; +using RATools.Parser.Tests.Expressions; using System.Collections.Generic; using System.Linq; using System.Text; @@ -199,17 +200,15 @@ public void TestAddAddress(string input, string expected) [Test] [TestCase("byte(word(0x1234) + word(0x2345))", "byte(word(0x001234) + word(0x002345))")] - [TestCase("byte(0x5555 + word(0x1234) + word(0x2345))", "byte(0x00005555 + word(0x001234) + word(0x002345))")] - [TestCase("byte(word(0x1234) + 0x5555 + word(0x2345))", "byte(0x00005555 + word(0x001234) + word(0x002345))")] - [TestCase("byte(word(0x1234) + word(0x2345) + 0x5555)", "byte(0x00005555 + word(0x001234) + word(0x002345))")] + [TestCase("byte(0x5555 + word(0x1234) + word(0x2345))", "byte(remembered(word(0x001234) + word(0x002345)) + 0x5555)")] + [TestCase("byte(word(0x1234) + 0x5555 + word(0x2345))", "byte(remembered(word(0x001234) + word(0x002345)) + 0x5555)")] + [TestCase("byte(word(0x1234) + word(0x2345) + 0x5555)", "byte(remembered(word(0x001234) + word(0x002345)) + 0x5555)")] public void TestRememberRecall(string input, string expected) { - var requirements = Evaluate(input); + var expr = ExpressionTests.Parse(input); var builder = new StringBuilder(); - var context = new ScriptBuilderContext { NumberFormat = NumberFormat.Hexadecimal }; - context.AppendRequirements(builder, requirements); - + expr.AppendString(builder); Assert.That(builder.ToString(), Is.EqualTo(expected)); } diff --git a/Tests/Parser/Functions/TallyFunctionTests.cs b/Tests/Parser/Functions/TallyFunctionTests.cs index 058800d3..9fb905a3 100644 --- a/Tests/Parser/Functions/TallyFunctionTests.cs +++ b/Tests/Parser/Functions/TallyFunctionTests.cs @@ -26,7 +26,7 @@ public void TestDefinition() [TestCase("tally(8, byte(0x001234) == 56, byte(0x001234) == 67, byte(0x001234) == 78, byte(0x001234) == 89)")] [TestCase("tally(10, once(byte(0x001234) == 56), byte(0x001234) == 67)")] [TestCase("tally(13, byte(0x001234) == 56 && byte(0x002345) == 67, byte(0x001234) == 56 && byte(0x002345) == 45)")] - [TestCase("tally(15, byte(dword(0x002345) + 48) == 56, byte(dword(0x002345) + 32) == 34)")] + [TestCase("tally(15, byte(dword(0x002345) + 0x30) == 56, byte(dword(0x002345) + 0x20) == 34)")] [TestCase("tally(16, byte(0x001234) + byte(0x002345) == 56, byte(0x001234) - byte(0x002345) == 34)")] [TestCase("tally(18, byte(0x001234) == 56, deduct(byte(0x002345) == 99))")] [TestCase("tally(19, byte(0x001234) == 56, deduct(byte(0x002345) == 99), byte(0x005555) == 0, deduct(byte(0x005555) == 1))")] diff --git a/Tests/Parser/Internal/TriggerBuilderContextTests.cs b/Tests/Parser/Internal/TriggerBuilderContextTests.cs index 84210a01..64384382 100644 --- a/Tests/Parser/Internal/TriggerBuilderContextTests.cs +++ b/Tests/Parser/Internal/TriggerBuilderContextTests.cs @@ -22,7 +22,7 @@ private static ExpressionBase Parse(string input) [TestCase("byte(0x1234) / byte(0x2345) < 0.8", "A:0xH001234/0xH002345_0