diff --git a/src/War3Net.Build/Extensions/SoundExtensions.cs b/src/War3Net.Build/Extensions/SoundExtensions.cs new file mode 100644 index 00000000..b70f8aa3 --- /dev/null +++ b/src/War3Net.Build/Extensions/SoundExtensions.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------ +// +// Licensed under the MIT license. +// See the LICENSE file in the project root for more information. +// +// ------------------------------------------------------------------------------ + +using War3Net.Build.Audio; +using War3Net.Build.Environment; + +namespace War3Net.Build.Extensions +{ + + public static class SoundExtensions + { + public static string GetVariableName(this Sound sound) + { + return $"gg_snd_{sound.Name.Replace(' ', '_')}"; + } + } +} \ No newline at end of file diff --git a/src/War3Net.Build/Extensions/UnitDataExtensions.cs b/src/War3Net.Build/Extensions/UnitDataExtensions.cs index e71b771c..adf3db9b 100644 --- a/src/War3Net.Build/Extensions/UnitDataExtensions.cs +++ b/src/War3Net.Build/Extensions/UnitDataExtensions.cs @@ -25,7 +25,7 @@ public static class UnitDataExtensions public static string GetVariableName(this UnitData unitData) { - return $"gg_unit_{unitData.TypeId.ToRawcode()}_{unitData.CreationNumber:D4}"; + return $"{(unitData.IsItem() ? "gg_item_" : "gg_unit_")}{unitData.TypeId.ToRawcode()}_{unitData.CreationNumber:D4}"; } public static string GetDropItemsFunctionName(this UnitData unitData, int id) diff --git a/src/War3Net.Build/War3Net.Build.csproj b/src/War3Net.Build/War3Net.Build.csproj index 126a36f1..0fafc698 100644 --- a/src/War3Net.Build/War3Net.Build.csproj +++ b/src/War3Net.Build/War3Net.Build.csproj @@ -20,4 +20,8 @@ + + + + diff --git a/src/War3Net.CodeAnalysis.Decompilers/Audio/MapSoundsDecompiler.cs b/src/War3Net.CodeAnalysis.Decompilers/Audio/MapSoundsDecompiler.cs index d8297c7a..bd37dfde 100644 --- a/src/War3Net.CodeAnalysis.Decompilers/Audio/MapSoundsDecompiler.cs +++ b/src/War3Net.CodeAnalysis.Decompilers/Audio/MapSoundsDecompiler.cs @@ -1,390 +1,381 @@ // ------------------------------------------------------------------------------ -// +// // Licensed under the MIT license. // See the LICENSE file in the project root for more information. -// +// // ------------------------------------------------------------------------------ using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using War3Net.Build.Audio; -using War3Net.CodeAnalysis.Jass.Extensions; using War3Net.CodeAnalysis.Jass.Syntax; namespace War3Net.CodeAnalysis.Decompilers { public partial class JassScriptDecompiler { - public bool TryDecompileMapSounds(MapSoundsFormatVersion formatVersion, [NotNullWhen(true)] out MapSounds? mapSounds) + [RegisterStatementParser] + internal void ParseSoundCreation(StatementParserInput input) { - foreach (var candidateFunction in GetCandidateFunctions("InitSounds")) + var variableAssignment = GetVariableAssignment(input.StatementChildren); + if (variableAssignment == null || !variableAssignment.StartsWith("gg_snd_")) { - if (TryDecompileMapSounds(candidateFunction.FunctionDeclaration, formatVersion, out mapSounds)) + return; + } + + var sound = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "CreateSound") + .SafeMapFirst(x => + { + var filePath = Regex.Unescape(((JassStringLiteralExpressionSyntax)x.Arguments.Arguments[0]).Value); + Context.ImportedFileNames.Add(filePath); + + return new Sound + { + FilePath = filePath, + Flags = ParseSoundFlags(x.Arguments.Arguments, filePath), + FadeInRate = x.Arguments.Arguments[4].GetValueOrDefault(), + FadeOutRate = x.Arguments.Arguments[5].GetValueOrDefault(), + EaxSetting = ((JassStringLiteralExpressionSyntax)x.Arguments.Arguments[6]).Value, + DialogueTextKey = -1, + DialogueSpeakerNameKey = -1 + }; + }); + + if (sound != null) + { + sound.Name = variableAssignment; + Context.Add(sound, variableAssignment); + Context.HandledStatements.Add(input.Statement); + } + } + + [RegisterStatementParser] + internal void ParseSetSoundParamsFromLabel(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundParamsFromLabel") + .SafeMapFirst(x => + { + return new { - candidateFunction.Handled = true; + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + SoundName = (x.Arguments.Arguments[1] as JassStringLiteralExpressionSyntax)?.Value, + }; + }); - return true; + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + sound.SoundName = match.SoundName; + Context.HandledStatements.Add(input.Statement); } } + } - mapSounds = null; - return false; + [RegisterStatementParser] + internal void ParseSetSoundFacialAnimationLabel(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundFacialAnimationLabel") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + FacialAnimationLabel = (x.Arguments.Arguments[1] as JassStringLiteralExpressionSyntax)?.Value, + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + sound.FacialAnimationLabel = match.FacialAnimationLabel; + Context.HandledStatements.Add(input.Statement); + } + } } - public bool TryDecompileMapSounds(JassFunctionDeclarationSyntax functionDeclaration, MapSoundsFormatVersion formatVersion, [NotNullWhen(true)] out MapSounds? mapSounds) + [RegisterStatementParser] + internal void ParseSetSoundFacialAnimationGroupLabel(StatementParserInput input) { - if (functionDeclaration is null) + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundFacialAnimationGroupLabel") + .SafeMapFirst(x => { - throw new ArgumentNullException(nameof(functionDeclaration)); + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + FacialAnimationGroupLabel = (x.Arguments.Arguments[1] as JassStringLiteralExpressionSyntax)?.Value, + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + sound.FacialAnimationGroupLabel = match.FacialAnimationGroupLabel; + Context.HandledStatements.Add(input.Statement); + } } + } - var sounds = new Dictionary(StringComparer.Ordinal); + [RegisterStatementParser] + internal void ParseSetSoundFacialAnimationSetFilepath(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundFacialAnimationSetFilepath") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + FacialAnimationSetFilepath = (x.Arguments.Arguments[1] as JassStringLiteralExpressionSyntax)?.Value, + }; + }); - foreach (var statement in functionDeclaration.Body.Statements) + if (match != null) { - if (statement is JassCommentSyntax || - statement is JassEmptySyntax) + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) { - continue; + sound.FacialAnimationSetFilepath = match.FacialAnimationSetFilepath; + Context.HandledStatements.Add(input.Statement); } - else if (statement is JassSetStatementSyntax setStatement) + } + } + + [RegisterStatementParser] + internal void ParseSetDialogueSpeakerNameKey(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetDialogueSpeakerNameKey") + .SafeMapFirst(x => + { + return new { - if (setStatement.Indexer is null && - setStatement.IdentifierName.Name.StartsWith("gg_snd_", StringComparison.Ordinal)) - { - if (setStatement.Value.Expression is JassInvocationExpressionSyntax invocationExpression && - string.Equals(invocationExpression.IdentifierName.Name, "CreateSound", StringComparison.Ordinal)) - { - if (invocationExpression.Arguments.Arguments.Length == 7 && - invocationExpression.Arguments.Arguments[0] is JassStringLiteralExpressionSyntax fileNameLiteralExpression && - invocationExpression.Arguments.Arguments[1] is JassBooleanLiteralExpressionSyntax loopingLiteralExpression && - invocationExpression.Arguments.Arguments[2] is JassBooleanLiteralExpressionSyntax is3DLiteralExpression && - invocationExpression.Arguments.Arguments[3] is JassBooleanLiteralExpressionSyntax stopWhenOutOfRangeLiteralExpression && - invocationExpression.Arguments.Arguments[4].TryGetIntegerExpressionValue(out var fadeInRate) && - invocationExpression.Arguments.Arguments[5].TryGetIntegerExpressionValue(out var fadeOutRate) && - invocationExpression.Arguments.Arguments[6] is JassStringLiteralExpressionSyntax eaxSettingLiteralExpression) - { - var flags = (SoundFlags)0; - if (loopingLiteralExpression.Value) - { - flags |= SoundFlags.Looping; - } - - if (is3DLiteralExpression.Value) - { - flags |= SoundFlags.Is3DSound; - } - - if (stopWhenOutOfRangeLiteralExpression.Value) - { - flags |= SoundFlags.StopWhenOutOfRange; - } - - var filePath = Regex.Unescape(fileNameLiteralExpression.Value); - Context.ImportedFileNames.Add(filePath); - - if (!is3DLiteralExpression.Value && !IsInternalSound(filePath)) - { - flags |= SoundFlags.UNK16; - } - - sounds.Add(setStatement.IdentifierName.Name, new Sound - { - Name = setStatement.IdentifierName.Name, - FilePath = filePath, - EaxSetting = eaxSettingLiteralExpression.Value, - Flags = flags, - FadeInRate = fadeInRate, - FadeOutRate = fadeOutRate, - - DialogueTextKey = -1, - DialogueSpeakerNameKey = -1, - FacialAnimationLabel = string.Empty, - FacialAnimationGroupLabel = string.Empty, - FacialAnimationSetFilepath = string.Empty, - }); - } - else - { - mapSounds = null; - return false; - } - } - else if (setStatement.Value.Expression is JassStringLiteralExpressionSyntax stringLiteralExpression) - { - var flags = SoundFlags.Music; - - var filePath = Regex.Unescape(stringLiteralExpression.Value); - Context.ImportedFileNames.Add(filePath); - - if (!IsInternalSound(filePath)) - { - flags |= SoundFlags.UNK16; - } - - sounds.Add(setStatement.IdentifierName.Name, new Sound - { - Name = setStatement.IdentifierName.Name, - FilePath = filePath, - EaxSetting = string.Empty, - Flags = flags, - FadeInRate = 10, - FadeOutRate = 10, - }); - } - else - { - continue; - } - } - else + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + DialogueSpeakerNameKey_TriggerString = (x.Arguments.Arguments[1] as JassStringLiteralExpressionSyntax).Value, + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + if (match.DialogueSpeakerNameKey_TriggerString.StartsWith("TRIGSTR_", StringComparison.Ordinal) && int.TryParse(match.DialogueSpeakerNameKey_TriggerString["TRIGSTR_".Length..], NumberStyles.None, CultureInfo.InvariantCulture, out var dialogueSpeakerNameKey)) { - continue; + sound.DialogueSpeakerNameKey = dialogueSpeakerNameKey; + Context.HandledStatements.Add(input.Statement); } } - else if (statement is JassCallStatementSyntax callStatement) + } + } + + [RegisterStatementParser] + internal void ParseSetDialogueTextKey(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetDialogueTextKey") + .SafeMapFirst(x => + { + return new { - if (string.Equals(callStatement.IdentifierName.Name, "SetSoundParamsFromLabel", StringComparison.Ordinal)) - { - continue; - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundFacialAnimationLabel", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1] is JassStringLiteralExpressionSyntax stringLiteralExpression && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound)) - { - sound.FacialAnimationLabel = stringLiteralExpression.Value; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundFacialAnimationGroupLabel", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1] is JassStringLiteralExpressionSyntax stringLiteralExpression && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound)) - { - sound.FacialAnimationGroupLabel = stringLiteralExpression.Value; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundFacialAnimationSetFilepath", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1] is JassStringLiteralExpressionSyntax stringLiteralExpression && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound)) - { - sound.FacialAnimationSetFilepath = stringLiteralExpression.Value; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetDialogueSpeakerNameKey", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1] is JassStringLiteralExpressionSyntax stringLiteralExpression && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound) && - stringLiteralExpression.Value.StartsWith("TRIGSTR_", StringComparison.Ordinal) && - int.TryParse(stringLiteralExpression.Value["TRIGSTR_".Length..], NumberStyles.None, CultureInfo.InvariantCulture, out var dialogueSpeakerNameKey)) - { - sound.DialogueSpeakerNameKey = dialogueSpeakerNameKey; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetDialogueTextKey", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1] is JassStringLiteralExpressionSyntax stringLiteralExpression && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound) && - stringLiteralExpression.Value.StartsWith("TRIGSTR_", StringComparison.Ordinal) && - int.TryParse(stringLiteralExpression.Value["TRIGSTR_".Length..], NumberStyles.None, CultureInfo.InvariantCulture, out var dialogueTextKey)) - { - sound.DialogueTextKey = dialogueTextKey; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundDuration", StringComparison.Ordinal)) - { - continue; - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundDistanceCutoff", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var cutoff) && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound)) - { - sound.DistanceCutoff = cutoff; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundChannel", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var channel) && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound)) - { - sound.Channel = (SoundChannel)channel; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundVolume", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var volume) && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound)) - { - sound.Volume = volume; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundPitch", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var pitch) && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound)) - { - sound.Pitch = pitch; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundDistances", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 3 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var minDist) && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var maxDist) && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound) && - sound.Flags.HasFlag(SoundFlags.Is3DSound)) - { - sound.MinDistance = minDist; - sound.MaxDistance = maxDist; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundConeAngles", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 4 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var inside) && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var outside) && - callStatement.Arguments.Arguments[3].TryGetIntegerExpressionValue(out var outsideVolume) && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound) && - sound.Flags.HasFlag(SoundFlags.Is3DSound)) - { - sound.ConeAngleInside = inside; - sound.ConeAngleOutside = outside; - sound.ConeOutsideVolume = outsideVolume; - } - else - { - mapSounds = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetSoundConeOrientation", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 4 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var x) && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var y) && - callStatement.Arguments.Arguments[3].TryGetRealExpressionValue(out var z) && - sounds.TryGetValue(variableReferenceExpression.IdentifierName.Name, out var sound) && - sound.Flags.HasFlag(SoundFlags.Is3DSound)) - { - sound.ConeOrientation = new(x, y, z); - } - else - { - mapSounds = null; - return false; - } - } - else + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + DialogueTextKey_TriggerString = (x.Arguments.Arguments[1] as JassStringLiteralExpressionSyntax).Value, + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + if (match.DialogueTextKey_TriggerString.StartsWith("TRIGSTR_", StringComparison.Ordinal) && int.TryParse(match.DialogueTextKey_TriggerString["TRIGSTR_".Length..], NumberStyles.None, CultureInfo.InvariantCulture, out var dialogueTextKey)) { - mapSounds = null; - return false; + sound.DialogueTextKey = dialogueTextKey; + Context.HandledStatements.Add(input.Statement); } } - else + } + } + + [RegisterStatementParser] + internal void ParseSetSoundDistanceCutoff(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundDistanceCutoff") + .SafeMapFirst(x => + { + return new { - mapSounds = null; - return false; + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + DistanceCutoff = x.Arguments.Arguments[1].GetValueOrDefault(), + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + sound.DistanceCutoff = match.DistanceCutoff; + Context.HandledStatements.Add(input.Statement); } } + } - if (sounds.Any()) + [RegisterStatementParser] + internal void ParseSetSoundChannel(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundChannel") + .SafeMapFirst(x => { - mapSounds = new MapSounds(formatVersion); - mapSounds.Sounds.AddRange(sounds.Values); - return true; + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + Channel = (SoundChannel)x.Arguments.Arguments[1].GetValueOrDefault(), + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + sound.Channel = match.Channel; + Context.HandledStatements.Add(input.Statement); + } } + } - mapSounds = null; - return false; + [RegisterStatementParser] + internal void ParseSetSoundVolume(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundVolume") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + Volume = x.Arguments.Arguments[1].GetValueOrDefault(), + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + sound.Volume = match.Volume; + Context.HandledStatements.Add(input.Statement); + } + } } - [Obsolete] - private static bool IsInternalSound(string filePath) + [RegisterStatementParser] + internal void ParseSetSoundPitch(StatementParserInput input) { - return filePath.StartsWith(@"Sound\", StringComparison.OrdinalIgnoreCase) - || filePath.StartsWith(@"Sound/", StringComparison.OrdinalIgnoreCase) - || filePath.StartsWith(@"UI\", StringComparison.OrdinalIgnoreCase) - || filePath.StartsWith(@"UI/", StringComparison.OrdinalIgnoreCase) - || filePath.StartsWith(@"Units\", StringComparison.OrdinalIgnoreCase) - || filePath.StartsWith(@"Units/", StringComparison.OrdinalIgnoreCase); + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundPitch") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + Pitch = x.Arguments.Arguments[1].GetValueOrDefault(), + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + sound.Pitch = match.Pitch; + Context.HandledStatements.Add(input.Statement); + } + } + } + + [RegisterStatementParser] + internal void ParseSetSoundDistances(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundDistances") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + MinDistance = x.Arguments.Arguments[1].GetValueOrDefault(), + MaxDistance = x.Arguments.Arguments[2].GetValueOrDefault() + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null) + { + sound.MinDistance = match.MinDistance; + sound.MaxDistance = match.MaxDistance; + Context.HandledStatements.Add(input.Statement); + } + } + } + + [RegisterStatementParser] + internal void ParseSetSoundConeAngles(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundConeAngles") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + ConeAngleInside = x.Arguments.Arguments[1].GetValueOrDefault(), + ConeAngleOutside = x.Arguments.Arguments[2].GetValueOrDefault(), + ConeOutsideVolume = x.Arguments.Arguments[3].GetValueOrDefault() + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null && sound.Flags.HasFlag(SoundFlags.Is3DSound)) + { + sound.ConeAngleInside = match.ConeAngleInside; + sound.ConeAngleOutside = match.ConeAngleOutside; + sound.ConeOutsideVolume = match.ConeOutsideVolume; + Context.HandledStatements.Add(input.Statement); + } + } + } + + [RegisterStatementParser] + internal void ParseSetSoundConeOrientation(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundConeOrientation") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + x = x.Arguments.Arguments[1].GetValueOrDefault(), + y = x.Arguments.Arguments[2].GetValueOrDefault(), + z = x.Arguments.Arguments[3].GetValueOrDefault(), + }; + }); + + if (match != null) + { + var sound = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (sound != null && sound.Flags.HasFlag(SoundFlags.Is3DSound)) + { + sound.ConeOrientation = new(match.x, match.y, match.z); + Context.HandledStatements.Add(input.Statement); + } + } } } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/DecompilationContext.cs b/src/War3Net.CodeAnalysis.Decompilers/DecompilationContext.cs index e26efe73..41e2da34 100644 --- a/src/War3Net.CodeAnalysis.Decompilers/DecompilationContext.cs +++ b/src/War3Net.CodeAnalysis.Decompilers/DecompilationContext.cs @@ -1,56 +1,35 @@ // ------------------------------------------------------------------------------ -// +// // Licensed under the MIT license. // See the LICENSE file in the project root for more information. -// // ------------------------------------------------------------------------------ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; -using War3Net.Build; using War3Net.Build.Info; using War3Net.Build.Script; -using War3Net.CodeAnalysis.Jass; using War3Net.CodeAnalysis.Jass.Syntax; namespace War3Net.CodeAnalysis.Decompilers { - internal sealed class DecompilationContext + public class DecompilationContext { - public DecompilationContext(Map map, Campaign? campaign, TriggerData? triggerData) + public DecompilationContext(JassCompilationUnitSyntax compilationUnit, DecompileOptions options = null, MapInfo mapInfo = null, TriggerData triggerData = null) { - if (map is null) - { - throw new ArgumentNullException(nameof(map)); - } - - if (map.Info is null) - { - throw new Exception(); - } - - if (map.Info.ScriptLanguage != ScriptLanguage.Jass) - { - throw new Exception(); - } + CompilationUnit = compilationUnit; + Options = options ?? new DecompileOptions(); + MapInfo = mapInfo; - if (string.IsNullOrEmpty(map.Script)) - { - throw new Exception(); - } - - ObjectData = new ObjectDataContext(map, campaign); TriggerData = new TriggerDataContext(triggerData); - var compilationUnit = JassSyntaxFactory.ParseCompilationUnit(map.Script); - var comments = new List(); var functionDeclarationsBuilder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); var variableDeclarationsBuilder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); - foreach (var declaration in compilationUnit.Declarations) + foreach (var declaration in CompilationUnit.Declarations) { if (declaration is JassCommentSyntax comment) { @@ -80,21 +59,91 @@ public DecompilationContext(Map map, Campaign? campaign, TriggerData? triggerDat FunctionDeclarations = functionDeclarationsBuilder.ToImmutable(); VariableDeclarations = variableDeclarationsBuilder.ToImmutable(); + ImportedFileNames = new(StringComparer.OrdinalIgnoreCase); + MaxPlayerSlots = mapInfo != null && mapInfo.EditorVersion >= EditorVersion.v6060 ? 24 : 12; + } + + public TriggerDataContext TriggerData { get; } + public ImmutableDictionary FunctionDeclarations { get; } + public ImmutableDictionary VariableDeclarations { get; } + public HashSet ImportedFileNames { get; } + public int MaxPlayerSlots { get; } + + public HashSet HandledStatements = new HashSet(); + public JassCompilationUnitSyntax CompilationUnit { get; } + public MapInfo MapInfo { get; } + public DecompileOptions Options { get; } - MaxPlayerSlots = map.Info.EditorVersion >= EditorVersion.v6060 ? 24 : 12; + private readonly Dictionary _variableNameToValueMapping = new(); + private readonly List _values = new(); + private int _lastCreationNumber; + + public int GetNextCreationNumber() + { + return _lastCreationNumber++; } - public ObjectDataContext ObjectData { get; } + public string GetVariableName(object value) + { + return _variableNameToValueMapping.FirstOrDefault(x => x.Value == value).Key; + } - public TriggerDataContext TriggerData { get; } + public void Add(T value, string variableName = null) where T : class + { + if (variableName != null) + { + _variableNameToValueMapping[variableName] = value; + } - public ImmutableDictionary FunctionDeclarations { get; } + _values.Add(value); + } - public ImmutableDictionary VariableDeclarations { get; } + public void Add_Struct(T value, string variableName = null) where T : struct + { + Add(new Nullable_Class(value), variableName); + } - public HashSet ImportedFileNames { get; } + public T Get(string variableName) where T : class + { + if (variableName == null) + { + return default; + } + + return _variableNameToValueMapping.GetValueOrDefault(variableName) as T; + } + + public Nullable_Class Get_Struct(string variableName = null) where T : struct + { + return Get>(variableName); + } + + public T GetLastCreated() where T : class + { + return _values.OfType().LastOrDefault(); + } + + public Nullable_Class GetLastCreated_Struct() where T : struct + { + return GetLastCreated>(); + } + + public IEnumerable GetAll() where T : class + { + return _values.OfType(); + } + + public IEnumerable> GetAll_Struct() where T : struct + { + return GetAll>(); + } + + private const string PSEUDO_VARIABLE_PREFIX = "##PSEUDO_VARIABLE_PREFIX##"; + internal string CreatePseudoVariableName(string type, string name = "") + { + return PSEUDO_VARIABLE_PREFIX + "_" + type.ToString() + "_" + name; + } - public int MaxPlayerSlots { get; } } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/Environment/DecompileOptions.cs b/src/War3Net.CodeAnalysis.Decompilers/Environment/DecompileOptions.cs new file mode 100644 index 00000000..063578f8 --- /dev/null +++ b/src/War3Net.CodeAnalysis.Decompilers/Environment/DecompileOptions.cs @@ -0,0 +1,25 @@ +// ------------------------------------------------------------------------------ +// +// Licensed under the MIT license. +// See the LICENSE file in the project root for more information. +// +// ------------------------------------------------------------------------------ + +using War3Net.Build.Audio; +using War3Net.Build.Environment; +using War3Net.Build.Widget; + +namespace War3Net.CodeAnalysis.Decompilers +{ + public class DecompileOptions + { + public MapCamerasFormatVersion mapCamerasFormatVersion; + public bool mapCamerasUseNewFormat; + public MapRegionsFormatVersion mapRegionsFormatVersion; + public MapSoundsFormatVersion mapSoundsFormatVersion; + public MapWidgetsFormatVersion mapWidgetsFormatVersion; + public MapWidgetsSubVersion mapWidgetsSubVersion; + public bool mapWidgetsUseNewFormat = default; + public SpecialDoodadVersion specialDoodadVersion; + } +} \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/Environment/MapCamerasDecompiler.cs b/src/War3Net.CodeAnalysis.Decompilers/Environment/MapCamerasDecompiler.cs index b8e4dc26..65ddcd4a 100644 --- a/src/War3Net.CodeAnalysis.Decompilers/Environment/MapCamerasDecompiler.cs +++ b/src/War3Net.CodeAnalysis.Decompilers/Environment/MapCamerasDecompiler.cs @@ -1,176 +1,140 @@ // ------------------------------------------------------------------------------ -// +// // Licensed under the MIT license. // See the LICENSE file in the project root for more information. -// +// // ------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using War3Net.Build.Environment; -using War3Net.CodeAnalysis.Jass.Extensions; using War3Net.CodeAnalysis.Jass.Syntax; namespace War3Net.CodeAnalysis.Decompilers { + public partial class JassScriptDecompiler { - public bool TryDecompileMapCameras(MapCamerasFormatVersion formatVersion, bool useNewFormat, [NotNullWhen(true)] out MapCameras? mapCameras) + [RegisterStatementParser] + internal void ParseCameraCreation(StatementParserInput input) { - foreach (var candidateFunction in GetCandidateFunctions("CreateCameras")) + var variableAssignment = GetVariableAssignment(input.StatementChildren); + if (variableAssignment == null || !variableAssignment.StartsWith("gg_cam_")) + { + return; + } + + var camera = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "CreateCameraSetup") + .SafeMapFirst(x => { - if (TryDecompileMapCameras(candidateFunction.FunctionDeclaration, formatVersion, useNewFormat, out mapCameras)) + return new Camera { - candidateFunction.Handled = true; + NearClippingPlane = Context.Options.mapCamerasUseNewFormat ? default : 100f, + }; + }); - return true; - } + if (camera != null) + { + camera.Name = variableAssignment["gg_cam_".Length..].Replace('_', ' '); + Context.Add(camera, variableAssignment); + Context.HandledStatements.Add(input.Statement); } - - mapCameras = null; - return false; } - public bool TryDecompileMapCameras(JassFunctionDeclarationSyntax functionDeclaration, MapCamerasFormatVersion formatVersion, bool useNewFormat, [NotNullWhen(true)] out MapCameras? mapCameras) + [RegisterStatementParser] + internal void ParseCameraSetupSetField(StatementParserInput input) { - if (functionDeclaration is null) + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "CameraSetupSetField") + .SafeMapFirst(x => { - throw new ArgumentNullException(nameof(functionDeclaration)); - } - - var cameras = new Dictionary(StringComparer.Ordinal); + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + FieldName = ((JassVariableReferenceExpressionSyntax)x.Arguments.Arguments[1]).IdentifierName.Name, + Value = x.Arguments.Arguments[2].GetValueOrDefault(), + Duration = x.Arguments.Arguments[3].GetValueOrDefault(), + }; + }); - foreach (var statement in functionDeclaration.Body.Statements) + if (match != null) { - if (statement is JassCommentSyntax || - statement is JassEmptySyntax) - { - continue; - } - else if (statement is JassSetStatementSyntax setStatement) + var camera = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (camera != null) { - if (setStatement.Indexer is null && - setStatement.IdentifierName.Name.StartsWith("gg_cam_", StringComparison.Ordinal) && - setStatement.Value.Expression is JassInvocationExpressionSyntax invocationExpression && - string.Equals(invocationExpression.IdentifierName.Name, "CreateCameraSetup", StringComparison.Ordinal)) + var handled = true; + var value = match.Value; + switch (match.FieldName) { - if (invocationExpression.Arguments.Arguments.IsEmpty) - { - cameras.Add(setStatement.IdentifierName.Name, new Camera - { - Name = setStatement.IdentifierName.Name["gg_cam_".Length..].Replace('_', ' '), - NearClippingPlane = useNewFormat ? default : 100f, - }); - } - else - { - mapCameras = null; - return false; - } + case "CAMERA_FIELD_ZOFFSET": + camera.ZOffset = value; + break; + case "CAMERA_FIELD_ROTATION": + camera.Rotation = value; + break; + case "CAMERA_FIELD_ANGLE_OF_ATTACK": + camera.AngleOfAttack = value; + break; + case "CAMERA_FIELD_TARGET_DISTANCE": + camera.TargetDistance = value; + break; + case "CAMERA_FIELD_ROLL": + camera.Roll = value; + break; + case "CAMERA_FIELD_FIELD_OF_VIEW": + camera.FieldOfView = value; + break; + case "CAMERA_FIELD_FARZ": + camera.FarClippingPlane = value; + break; + case "CAMERA_FIELD_NEARZ": + camera.NearClippingPlane = value; + break; + case "CAMERA_FIELD_LOCAL_PITCH": + camera.LocalPitch = value; + break; + case "CAMERA_FIELD_LOCAL_YAW": + camera.LocalYaw = value; + break; + case "CAMERA_FIELD_LOCAL_ROLL": + camera.LocalRoll = value; + break; + default: + handled = false; + break; } - else - { - continue; - } - } - else if (statement is JassCallStatementSyntax callStatement) - { - if (string.Equals(callStatement.IdentifierName.Name, "CameraSetupSetField", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 4 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax cameraVariableReferenceExpression && - callStatement.Arguments.Arguments[1] is JassVariableReferenceExpressionSyntax cameraFieldVariableReferenceExpression && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var value) && - callStatement.Arguments.Arguments[3].TryGetRealExpressionValue(out var duration) && - duration == 0f && - cameras.TryGetValue(cameraVariableReferenceExpression.IdentifierName.Name, out var camera)) - { - switch (cameraFieldVariableReferenceExpression.IdentifierName.Name) - { - case "CAMERA_FIELD_ZOFFSET": - camera.ZOffset = value; - break; - case "CAMERA_FIELD_ROTATION": - camera.Rotation = value; - break; - case "CAMERA_FIELD_ANGLE_OF_ATTACK": - camera.AngleOfAttack = value; - break; - case "CAMERA_FIELD_TARGET_DISTANCE": - camera.TargetDistance = value; - break; - case "CAMERA_FIELD_ROLL": - camera.Roll = value; - break; - case "CAMERA_FIELD_FIELD_OF_VIEW": - camera.FieldOfView = value; - break; - case "CAMERA_FIELD_FARZ": - camera.FarClippingPlane = value; - break; - case "CAMERA_FIELD_NEARZ": - camera.NearClippingPlane = value; - break; - case "CAMERA_FIELD_LOCAL_PITCH": - camera.LocalPitch = value; - break; - case "CAMERA_FIELD_LOCAL_YAW": - camera.LocalYaw = value; - break; - case "CAMERA_FIELD_LOCAL_ROLL": - camera.LocalRoll = value; - break; - } - } - else - { - mapCameras = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "CameraSetupSetDestPosition", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 4 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax cameraVariableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var x) && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var y) && - callStatement.Arguments.Arguments[3].TryGetRealExpressionValue(out var duration) && - duration == 0f && - cameras.TryGetValue(cameraVariableReferenceExpression.IdentifierName.Name, out var camera)) - { - camera.TargetPosition = new(x, y); - } - else - { - mapCameras = null; - return false; - } - } - else + + if (handled) { - mapCameras = null; - return false; + Context.HandledStatements.Add(input.Statement); } } - else - { - mapCameras = null; - return false; - } } + } - if (cameras.Any()) + [RegisterStatementParser] + internal void ParseCameraSetupSetDestPosition(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "CameraSetupSetDestPosition") + .SafeMapFirst(x => { - mapCameras = new MapCameras(formatVersion, useNewFormat); - mapCameras.Cameras.AddRange(cameras.Values); - return true; - } + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + X = x.Arguments.Arguments[1].GetValueOrDefault(), + Y = x.Arguments.Arguments[2].GetValueOrDefault(), + Duration = x.Arguments.Arguments[3].GetValueOrDefault() + }; + }); - mapCameras = null; - return false; + if (match != null) + { + var camera = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (camera != null) + { + camera.TargetPosition = new(match.X, match.Y); + Context.HandledStatements.Add(input.Statement); + } + } } } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/Environment/MapRegionsDecompiler.cs b/src/War3Net.CodeAnalysis.Decompilers/Environment/MapRegionsDecompiler.cs index 0e1149ec..273e80d1 100644 --- a/src/War3Net.CodeAnalysis.Decompilers/Environment/MapRegionsDecompiler.cs +++ b/src/War3Net.CodeAnalysis.Decompilers/Environment/MapRegionsDecompiler.cs @@ -1,192 +1,130 @@ // ------------------------------------------------------------------------------ -// +// // Licensed under the MIT license. // See the LICENSE file in the project root for more information. -// +// // ------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using War3Net.Build; -using War3Net.Build.Environment; using War3Net.CodeAnalysis.Jass.Extensions; using War3Net.CodeAnalysis.Jass.Syntax; +using Region = War3Net.Build.Environment.Region; namespace War3Net.CodeAnalysis.Decompilers { public partial class JassScriptDecompiler { - public bool TryDecompileMapRegions(MapRegionsFormatVersion formatVersion, [NotNullWhen(true)] out MapRegions? mapRegions) + [RegisterStatementParser] + internal void ParseRegionCreation(StatementParserInput input) { - foreach (var candidateFunction in GetCandidateFunctions("CreateRegions")) + var variableAssignment = GetVariableAssignment(input.StatementChildren); + if (variableAssignment == null || !variableAssignment.StartsWith("gg_rct_")) { - if (TryDecompileMapRegions(candidateFunction.FunctionDeclaration, formatVersion, out mapRegions)) + return; + } + + var region = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "Rect") + .SafeMapFirst(x => + { + return new Region { - candidateFunction.Handled = true; + Left = x.Arguments.Arguments[0].GetValueOrDefault(), + Bottom = x.Arguments.Arguments[1].GetValueOrDefault(), + Right = x.Arguments.Arguments[2].GetValueOrDefault(), + Top = x.Arguments.Arguments[3].GetValueOrDefault(), + Color = System.Drawing.Color.FromArgb(unchecked((int)0xFF8080FF)), + CreationNumber = Context.GetNextCreationNumber() + }; + }); - return true; - } + if (region != null) + { + region.Name = variableAssignment["gg_rct_".Length..].Replace('_', ' '); + Context.Add(region, variableAssignment); + Context.HandledStatements.Add(input.Statement); } - - mapRegions = null; - return false; } - public bool TryDecompileMapRegions(JassFunctionDeclarationSyntax functionDeclaration, MapRegionsFormatVersion formatVersion, [NotNullWhen(true)] out MapRegions? mapRegions) + [RegisterStatementParser] + internal void ParseAddWeatherEffect(StatementParserInput input) { - if (functionDeclaration is null) + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "AddWeatherEffect") + .SafeMapFirst(x => { - throw new ArgumentNullException(nameof(functionDeclaration)); - } - - Region? currentRegion = null; - var createdRegions = new List(); - var regions = new Dictionary(StringComparer.Ordinal); + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + WeatherType = (WeatherType)((JassFourCCLiteralExpressionSyntax)x.Arguments.Arguments[1]).Value.InvertEndianness(), + }; + }); - foreach (var statement in functionDeclaration.Body.Statements) + if (match != null) { - if (statement is JassLocalVariableDeclarationStatementSyntax || - statement is JassCommentSyntax || - statement is JassEmptySyntax) + var region = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (region != null) { - continue; + region.WeatherType = match.WeatherType; + Context.HandledStatements.Add(input.Statement); } - else if (statement is JassSetStatementSyntax setStatement) - { - if (setStatement.Indexer is null && - setStatement.Value.Expression is JassInvocationExpressionSyntax invocationExpression) - { - if (setStatement.IdentifierName.Name.StartsWith("gg_rct_", StringComparison.Ordinal) && - string.Equals(invocationExpression.IdentifierName.Name, "Rect", StringComparison.Ordinal)) - { - if (invocationExpression.Arguments.Arguments.Length == 4 && - invocationExpression.Arguments.Arguments[0].TryGetRealExpressionValue(out var minx) && - invocationExpression.Arguments.Arguments[1].TryGetRealExpressionValue(out var miny) && - invocationExpression.Arguments.Arguments[2].TryGetRealExpressionValue(out var maxx) && - invocationExpression.Arguments.Arguments[3].TryGetRealExpressionValue(out var maxy)) - { - currentRegion = new Region - { - Name = setStatement.IdentifierName.Name["gg_rct_".Length..].Replace('_', ' '), - Left = minx, - Bottom = miny, - Right = maxx, - Top = maxy, - Color = System.Drawing.Color.FromArgb(unchecked((int)0xFF8080FF)), - CreationNumber = regions.Count, - AmbientSound = string.Empty, - }; + } + } - createdRegions.Add(currentRegion); + [RegisterStatementParser] + internal void ParseSetSoundPosition(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetSoundPosition") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + x = x.Arguments.Arguments[1].GetValueOrDefault(), + y = x.Arguments.Arguments[2].GetValueOrDefault() + }; + }); - if (!regions.TryAdd(setStatement.IdentifierName.Name, currentRegion)) - { - regions[setStatement.IdentifierName.Name] = currentRegion; - } - } - else - { - mapRegions = null; - return false; - } - } - else if (string.Equals(setStatement.IdentifierName.Name, "we", StringComparison.Ordinal) && - string.Equals(invocationExpression.IdentifierName.Name, "AddWeatherEffect", StringComparison.Ordinal)) - { - if (invocationExpression.Arguments.Arguments.Length == 2 && - invocationExpression.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax regionVariableReferenceExpression && - invocationExpression.Arguments.Arguments[1] is JassFourCCLiteralExpressionSyntax fourCCLiteralExpression && - regions.TryGetValue(regionVariableReferenceExpression.IdentifierName.Name, out var region)) - { - region.WeatherType = (WeatherType)fourCCLiteralExpression.Value.InvertEndianness(); - } - else - { - mapRegions = null; - return false; - } - } - else - { - continue; - } - } - else - { - continue; - } - } - else if (statement is JassCallStatementSyntax callStatement) + if (match != null) + { + var region = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (region != null) { - if (string.Equals(callStatement.IdentifierName.Name, "SetSoundPosition", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 4 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax soundVariableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var x) && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var y) && - currentRegion is not null && - currentRegion.CenterX == x && - currentRegion.CenterY == y && - (string.IsNullOrEmpty(currentRegion.AmbientSound) || - string.Equals(currentRegion.AmbientSound, soundVariableReferenceExpression.IdentifierName.Name, StringComparison.Ordinal))) - { - currentRegion.AmbientSound = soundVariableReferenceExpression.IdentifierName.Name; - } - else - { - mapRegions = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "RegisterStackedSound", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 4 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax soundVariableReferenceExpression && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var rectWidth) && - callStatement.Arguments.Arguments[3].TryGetRealExpressionValue(out var rectHeight) && - currentRegion is not null && - currentRegion.Width == rectWidth && - currentRegion.Height == rectHeight && - (string.IsNullOrEmpty(currentRegion.AmbientSound) || - string.Equals(currentRegion.AmbientSound, soundVariableReferenceExpression.IdentifierName.Name, StringComparison.Ordinal))) - { - currentRegion.AmbientSound = soundVariableReferenceExpression.IdentifierName.Name; - } - else - { - mapRegions = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "EnableWeatherEffect", StringComparison.Ordinal)) + if (region.CenterX == match.x && region.CenterY == match.y) { - continue; - } - else - { - mapRegions = null; - return false; + region.AmbientSound = match.VariableName; + Context.HandledStatements.Add(input.Statement); } } - else - { - mapRegions = null; - return false; - } } + } - if (regions.Any()) + [RegisterStatementParser] + internal void ParseRegisterStackedSound(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "RegisterStackedSound") + .SafeMapFirst(x => { - mapRegions = new MapRegions(formatVersion); - mapRegions.Regions.AddRange(createdRegions); - return true; - } + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + Width = x.Arguments.Arguments[2].GetValueOrDefault(), + Height = x.Arguments.Arguments[3].GetValueOrDefault() + }; + }); - mapRegions = null; - return false; + if (match != null) + { + var region = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (region != null) + { + if (region.Width == match.Width && region.Height == match.Height) + { + region.AmbientSound = match.VariableName; + Context.HandledStatements.Add(input.Statement); + } + } + } } } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/FunctionDeclarationContext.cs b/src/War3Net.CodeAnalysis.Decompilers/FunctionDeclarationContext.cs index 7a3b155e..54d760f0 100644 --- a/src/War3Net.CodeAnalysis.Decompilers/FunctionDeclarationContext.cs +++ b/src/War3Net.CodeAnalysis.Decompilers/FunctionDeclarationContext.cs @@ -12,7 +12,7 @@ namespace War3Net.CodeAnalysis.Decompilers { - internal sealed class FunctionDeclarationContext + public sealed class FunctionDeclarationContext { public FunctionDeclarationContext(JassFunctionDeclarationSyntax functionDeclaration, IEnumerable comments) { diff --git a/src/War3Net.CodeAnalysis.Decompilers/JassDecompilerExtensions.cs b/src/War3Net.CodeAnalysis.Decompilers/JassDecompilerExtensions.cs new file mode 100644 index 00000000..0bc6eb80 --- /dev/null +++ b/src/War3Net.CodeAnalysis.Decompilers/JassDecompilerExtensions.cs @@ -0,0 +1,63 @@ +// ------------------------------------------------------------------------------ +// +// Licensed under the MIT license. +// See the LICENSE file in the project root for more information. +// +// ------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace War3Net.CodeAnalysis.Decompilers +{ + public static class JassDecompilerExtensions + { + public static T2 OneOf(this IEnumerable tokens, params Func, T2>[] mapping) where T2 : class + { + foreach (var map in mapping) + { + try + { + var result = map(tokens); + if (result != null) + { + return result; + } + } + catch + { + //swallow exceptions + } + } + + return default; + } + + public static Nullable_Class OneOf_Struct(this IEnumerable tokens, params Func, Nullable_Class>[] mapping) where T2 : struct + { + return OneOf(tokens, mapping); + } + + public static T2 SafeMapFirst(this IEnumerable tokens, Func mapping) where T2 : class + { + return tokens.Select(x => { + try + { + return mapping(x); + } + catch + { + //swallow exceptions + } + + return default; + }).Where(x => x != null).FirstOrDefault(); + } + + public static Nullable_Class SafeMapFirst_Struct(this IEnumerable tokens, Func> mapping) where T2 : struct + { + return SafeMapFirst(tokens, mapping); + } + } +} \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/JassScriptDecompiler.cs b/src/War3Net.CodeAnalysis.Decompilers/JassScriptDecompiler.cs index 1ccf7fe4..c4e5a843 100644 --- a/src/War3Net.CodeAnalysis.Decompilers/JassScriptDecompiler.cs +++ b/src/War3Net.CodeAnalysis.Decompilers/JassScriptDecompiler.cs @@ -1,90 +1,146 @@ // ------------------------------------------------------------------------------ -// +// // Licensed under the MIT license. // See the LICENSE file in the project root for more information. -// // ------------------------------------------------------------------------------ -using System; using System.Collections.Generic; +using System.Linq; +using System; +using System.Reflection; +using War3Net.Build.Environment; using War3Net.Build; -using War3Net.Build.Script; using War3Net.CodeAnalysis.Jass.Syntax; +using War3Net.Build.Audio; +using War3Net.Build.Widget; +using War3Net.Build.Info; namespace War3Net.CodeAnalysis.Decompilers { public partial class JassScriptDecompiler { - public JassScriptDecompiler(Map map) - : this(map, null, null) + [AttributeUsage(AttributeTargets.Method)] + private class RegisterStatementParserAttribute : Attribute { } - public JassScriptDecompiler(Map map, Campaign? campaign) - : this(map, campaign, null) + internal class StatementParserInput { + public IStatementLineSyntax Statement; + public List StatementChildren; } - public JassScriptDecompiler(Map map, TriggerData? triggerData) - : this(map, null, triggerData) + private static List _statementParsers; + static JassScriptDecompiler() { + _statementParsers = typeof(JassScriptDecompiler) + .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .Where(m => m.GetCustomAttributes(typeof(RegisterStatementParserAttribute), false).Any()).ToList(); } - public JassScriptDecompiler(Map map, Campaign? campaign, TriggerData? triggerData) + public DecompilationContext Context { get; } + + public JassScriptDecompiler(JassCompilationUnitSyntax compilationUnit, DecompileOptions options = null, MapInfo mapInfo = null) { - Context = new DecompilationContext(map, campaign, triggerData); + Context = new DecompilationContext(compilationUnit, options, mapInfo); } - internal DecompilationContext Context { get; } - - private FunctionDeclarationContext? GetFunction(string functionName) + private void ProcessStatementParsers(IStatementLineSyntax statement, IEnumerable> statementParsers) { - if (Context.FunctionDeclarations.TryGetValue(functionName, out var functionDeclaration)) + var input = new StatementParserInput() { Statement = statement, StatementChildren = statement.GetChildren_RecursiveDepthFirst().ToList() }; + foreach (var parser in statementParsers) { - if (functionDeclaration.Handled) + try + { + parser(input); + if (Context.HandledStatements.Contains(statement)) + { + break; + } + } + catch { - throw new ArgumentException("Function has already been handled.", nameof(functionName)); + //swallow exceptions } + } + } + private FunctionDeclarationContext? GetFunction(string functionName) + { + if (Context.FunctionDeclarations.TryGetValue(functionName, out var functionDeclaration)) + { return functionDeclaration; } return null; } - private IEnumerable GetCandidateFunctions(string? expectedFunctionName = null) + public List GetFunctionStatements_EnteringCalls(string startingFunctionName) { - if (Context.FunctionDeclarations.TryGetValue("main", out var mainFunction)) + var functionDeclaration = Context.FunctionDeclarations.GetValueOrDefault(startingFunctionName); + if (functionDeclaration == null || functionDeclaration.Handled) { - if (!string.IsNullOrEmpty(expectedFunctionName) && Context.FunctionDeclarations.TryGetValue(expectedFunctionName, out var expectedFunction)) - { - if (expectedFunction.Handled) - { - throw new ArgumentException("Expected function has already been handled.", nameof(expectedFunctionName)); - } + return new List(); + } - yield return expectedFunction; + functionDeclaration.Handled = true; + var result = new List(); + foreach (var child in functionDeclaration.FunctionDeclaration.GetChildren_RecursiveDepthFirst()) + { + if (child is IStatementLineSyntax statement) + { + result.Add(statement); } - foreach (var statement in mainFunction.FunctionDeclaration.Body.Statements) + if (child is JassFunctionReferenceExpressionSyntax functionReference) { - if (statement is JassCallStatementSyntax callStatement && callStatement.Arguments.Arguments.IsEmpty) + result.AddRange(GetFunctionStatements_EnteringCalls(functionReference.IdentifierName.Name)); + } + else if (child is JassCallStatementSyntax callStatement) + { + if (string.Equals(callStatement.IdentifierName.Name, "ExecuteFunc", StringComparison.InvariantCultureIgnoreCase) && callStatement.Arguments.Arguments.FirstOrDefault() is JassStringLiteralExpressionSyntax execFunctionName) + { + result.AddRange(GetFunctionStatements_EnteringCalls(execFunctionName.Value)); + } + else { - if (string.Equals(callStatement.IdentifierName.Name, expectedFunctionName, StringComparison.Ordinal)) - { - continue; - } - - if (Context.FunctionDeclarations.TryGetValue(callStatement.IdentifierName.Name, out var candidateFunction) && - candidateFunction.IsActionsFunction && - !candidateFunction.Handled) - { - yield return candidateFunction; - } + result.AddRange(GetFunctionStatements_EnteringCalls(callStatement.IdentifierName.Name)); } } } + + return result.ToList(); + } + + public Map DecompileObjectManagerData() + { + if (Context.MapInfo?.ScriptLanguage == ScriptLanguage.Lua) + { + throw new Exception("Lua decompilation is not supported yet"); + } + + //todo: Run through Jass=>Lua transpiler, then run with NLua with native polyfills & method execution logging to better handle obfuscated code & to re-use the same logic for both Jass & Lua Decompilation (still need parser to direct interpreter what functions to execute for non-iterative patterns like TriggerRegisterUnitEvent for unit death item drop tables). + + var actions = _statementParsers.Select(parser => (Action)((StatementParserInput input) => parser.Invoke(this, new[] { input }))).ToList(); + + foreach (var function in Context.FunctionDeclarations) + { + function.Value.Handled = false; + } + var statements = GetFunctionStatements_EnteringCalls("config").Concat(GetFunctionStatements_EnteringCalls("main")).ToList(); + foreach (var statement in statements) + { + ProcessStatementParsers(statement, actions); + } + + var map = new Map() { Info = Context.MapInfo }; + map.Cameras = new MapCameras(Context.Options.mapCamerasFormatVersion, Context.Options.mapCamerasUseNewFormat) { Cameras = Context.GetAll().ToList() }; + map.Regions = new MapRegions(Context.Options.mapRegionsFormatVersion) { Regions = Context.GetAll().ToList() }; + map.Sounds = new MapSounds(Context.Options.mapSoundsFormatVersion) { Sounds = Context.GetAll().ToList() }; + map.Units = new MapUnits(Context.Options.mapWidgetsFormatVersion, Context.Options.mapWidgetsSubVersion, Context.Options.mapWidgetsUseNewFormat) { Units = Context.GetAll().ToList() }; + map.Doodads = new MapDoodads(Context.Options.mapWidgetsFormatVersion, Context.Options.mapWidgetsSubVersion, Context.Options.mapWidgetsUseNewFormat) { Doodads = Context.GetAll().ToList(), SpecialDoodads = Context.GetAll().ToList(), SpecialDoodadVersion = Context.Options.specialDoodadVersion }; + return map; } } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/JassScriptDecompilerHelpers.cs b/src/War3Net.CodeAnalysis.Decompilers/JassScriptDecompilerHelpers.cs new file mode 100644 index 00000000..a2ccdb90 --- /dev/null +++ b/src/War3Net.CodeAnalysis.Decompilers/JassScriptDecompilerHelpers.cs @@ -0,0 +1,115 @@ +// ------------------------------------------------------------------------------ +// +// Licensed under the MIT license. +// See the LICENSE file in the project root for more information. +// +// ------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +using War3Net.Build.Audio; +using War3Net.CodeAnalysis.Jass.Syntax; + +namespace War3Net.CodeAnalysis.Decompilers +{ + public partial class JassScriptDecompiler + { + private int? GetLastCreatedPlayerIndex() + { + return Context.Get_Struct(Context.CreatePseudoVariableName(nameof(ParsePlayerIndex))); + } + + private int? GetPlayerIndex(JassVariableReferenceExpressionSyntax variableReference) + { + if (string.Equals(variableReference.IdentifierName.Name, "PLAYER_NEUTRAL_AGGRESSIVE", StringComparison.Ordinal)) + { + return Context.MaxPlayerSlots; + } + else if (string.Equals(variableReference.IdentifierName.Name, "PLAYER_NEUTRAL_PASSIVE", StringComparison.Ordinal)) + { + return Context.MaxPlayerSlots + 3; + } + else + { + return Context.Get_Struct(Context.CreatePseudoVariableName(nameof(ParsePlayerIndex), variableReference.IdentifierName.Name)); + } + } + + private int? GetPlayerIndex(IInvocationSyntax invocationSyntax) + { + if (invocationSyntax.IdentifierName.Name != "Player") + { + return null; + } + + if (invocationSyntax.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax variableReferenceExpression) + { + return GetPlayerIndex(variableReferenceExpression); + } + else if (invocationSyntax.Arguments.Arguments[0].TryGetValue(out var playerId)) + { + return playerId; + } + + return null; + } + + private int? GetPlayerIndex(IJassSyntaxToken parameter) + { + if (parameter is IInvocationSyntax invocationSyntax) + { + return GetPlayerIndex(invocationSyntax); + } + else if (parameter is JassVariableReferenceExpressionSyntax variableReferenceExpression) + { + return GetPlayerIndex(variableReferenceExpression); + } + + return null; + } + + private string GetVariableAssignment(IEnumerable statementChildren) + { + return statementChildren.OneOf(x => x.OfType().SafeMapFirst(x => x.IdentifierName.Name), + x => x.OfType().SafeMapFirst(x => x.IdentifierName.Name)); + } + + private SoundFlags ParseSoundFlags(ImmutableArray arguments, string filePath = default) + { + var flags = (SoundFlags)0; + if (((JassBooleanLiteralExpressionSyntax)arguments[1]).Value) + { + flags |= SoundFlags.Looping; + } + if (((JassBooleanLiteralExpressionSyntax)arguments[2]).Value) + { + flags |= SoundFlags.Is3DSound; + } + if (((JassBooleanLiteralExpressionSyntax)arguments[3]).Value) + { + flags |= SoundFlags.StopWhenOutOfRange; + } + + if ((flags & SoundFlags.Is3DSound) == SoundFlags.Is3DSound && !IsInternalSound(filePath ?? "")) + { + flags |= SoundFlags.UNK16; + } + + return flags; + } + + [Obsolete] + private static bool IsInternalSound(string filePath) + { + return filePath.StartsWith(@"Sound\", StringComparison.OrdinalIgnoreCase) + || filePath.StartsWith(@"Sound/", StringComparison.OrdinalIgnoreCase) + || filePath.StartsWith(@"UI\", StringComparison.OrdinalIgnoreCase) + || filePath.StartsWith(@"UI/", StringComparison.OrdinalIgnoreCase) + || filePath.StartsWith(@"Units\", StringComparison.OrdinalIgnoreCase) + || filePath.StartsWith(@"Units/", StringComparison.OrdinalIgnoreCase); + } + } +} \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/Nullable_Class.cs b/src/War3Net.CodeAnalysis.Decompilers/Nullable_Class.cs new file mode 100644 index 00000000..bacf6e32 --- /dev/null +++ b/src/War3Net.CodeAnalysis.Decompilers/Nullable_Class.cs @@ -0,0 +1,87 @@ +// ------------------------------------------------------------------------------ +// +// Licensed under the MIT license. +// See the LICENSE file in the project root for more information. +// +// ------------------------------------------------------------------------------ + +using System; + +namespace War3Net.CodeAnalysis.Decompilers +{ + public class Nullable_Class where T : struct + { + private Nullable _value { get; } + public T Value + { + get + { + return _value.Value; + } + } + + public Nullable_Class(T value) + : base() + { + _value = new Nullable(value); + } + + public bool HasValue + { + get + { + return _value.HasValue; + } + } + + public T GetValueOrDefault(T defaultValue = default) + { + return _value ?? defaultValue; + } + + public override string ToString() + { + return _value?.ToString(); + } + + public override bool Equals(object? other) + { + return _value.Equals(other); + } + + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + public static implicit operator Nullable_Class(T value) + { + return new Nullable_Class(value); + } + + public static explicit operator T(Nullable_Class value) + { + return value.Value; + } + + public static implicit operator Nullable_Class(Nullable value) + { + if (!value.HasValue) + { + return null; + } + + return new Nullable_Class(value.Value); + } + + public static implicit operator Nullable(Nullable_Class value) + { + if (value == null || !value.HasValue) + { + return null; + } + + return new Nullable(value.Value); + } + } +} \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/ObjectDataContext.cs b/src/War3Net.CodeAnalysis.Decompilers/ObjectDataContext.cs index 8a63c6fc..b86f9be1 100644 --- a/src/War3Net.CodeAnalysis.Decompilers/ObjectDataContext.cs +++ b/src/War3Net.CodeAnalysis.Decompilers/ObjectDataContext.cs @@ -11,13 +11,22 @@ namespace War3Net.CodeAnalysis.Decompilers { - internal sealed class ObjectDataContext + public sealed class ObjectDataContext { + /* + public readonly Map map; + public readonly Campaign campaign; + */ + public ObjectDataContext(Map map, Campaign? campaign) { - // TODO + /* + this.map = map; + this.campaign = campaign; + */ } + /* public ImmutableHashSet KnownUnitIds { get; set; } public ImmutableHashSet KnownItemIds { get; set; } @@ -35,5 +44,6 @@ public ObjectDataContext(Map map, Campaign? campaign) public ImmutableHashSet KnownTechIds { get; set; } public ImmutableHashSet KnownObjectIds { get; set; } + */ } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Decompilers/VariableDeclarationContext.cs b/src/War3Net.CodeAnalysis.Decompilers/VariableDeclarationContext.cs index d37bb87e..95bc1ef5 100644 --- a/src/War3Net.CodeAnalysis.Decompilers/VariableDeclarationContext.cs +++ b/src/War3Net.CodeAnalysis.Decompilers/VariableDeclarationContext.cs @@ -10,7 +10,7 @@ namespace War3Net.CodeAnalysis.Decompilers { - internal sealed class VariableDeclarationContext + public sealed class VariableDeclarationContext { public VariableDeclarationContext(JassGlobalDeclarationSyntax globalDeclaration) { diff --git a/src/War3Net.CodeAnalysis.Decompilers/Widget/MapUnitsDecompiler.cs b/src/War3Net.CodeAnalysis.Decompilers/Widget/MapUnitsDecompiler.cs index aa480b99..3da02c55 100644 --- a/src/War3Net.CodeAnalysis.Decompilers/Widget/MapUnitsDecompiler.cs +++ b/src/War3Net.CodeAnalysis.Decompilers/Widget/MapUnitsDecompiler.cs @@ -1,15 +1,15 @@ // ------------------------------------------------------------------------------ -// +// // Licensed under the MIT license. // See the LICENSE file in the project root for more information. -// // ------------------------------------------------------------------------------ using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Numerics; +using War3Net.Build.Common; +using War3Net.Build.Environment; using War3Net.Build.Widget; using War3Net.CodeAnalysis.Jass.Extensions; using War3Net.CodeAnalysis.Jass.Syntax; @@ -19,734 +19,620 @@ namespace War3Net.CodeAnalysis.Decompilers { public partial class JassScriptDecompiler { - private int CreationNumber = 0; - - public bool TryDecompileMapUnits( - MapWidgetsFormatVersion formatVersion, - MapWidgetsSubVersion subVersion, - bool useNewFormat, - [NotNullWhen(true)] out MapUnits? mapUnits) + [RegisterStatementParser] + internal void ParseUnitCreation(StatementParserInput input) { - var createAllUnits = GetFunction("CreateAllUnits"); - var createAllItems = GetFunction("CreateAllItems"); - var config = GetFunction("config"); - var initCustomPlayerSlots = GetFunction("InitCustomPlayerSlots"); - - if (createAllUnits is null || - createAllItems is null || - config is null || - initCustomPlayerSlots is null) - { - mapUnits = null; - return false; - } + var variableAssignment = GetVariableAssignment(input.StatementChildren); + var unitData = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "CreateUnit" || x.IdentifierName.Name == "BlzCreateUnitWithSkin").SafeMapFirst(x => + { + var args = x.Arguments.Arguments; - if (TryDecompileMapUnits( - createAllUnits.FunctionDeclaration, - createAllItems.FunctionDeclaration, - config.FunctionDeclaration, - initCustomPlayerSlots.FunctionDeclaration, - formatVersion, - subVersion, - useNewFormat, - out mapUnits)) - { - createAllUnits.Handled = true; - createAllItems.Handled = true; - initCustomPlayerSlots.Handled = true; - - return true; - } + int? ownerId = GetPlayerIndex(args[0]) ?? GetLastCreatedPlayerIndex(); + + if (ownerId == null) + { + return null; + } - mapUnits = null; - return false; + if (!args[1].TryGetValue(out var typeId)) + { + return null; + } + + var result = new UnitData + { + OwnerId = ownerId.Value, + TypeId = typeId.InvertEndianness(), + Position = new Vector3( + args[2].GetValueOrDefault(), + args[3].GetValueOrDefault(), + 0f + ), + Rotation = args[4].GetValueOrDefault() * (MathF.PI / 180f), + Scale = Vector3.One, + Flags = 2, + GoldAmount = 12500, + HeroLevel = 1, + CreationNumber = Context.GetNextCreationNumber() + }; + + result.SkinId = args.Length > 5 ? args[5].GetValueOrDefault() : result.TypeId; + return result; + }); + + if (unitData != null) + { + Context.HandledStatements.Add(input.Statement); + Context.Add(unitData, variableAssignment); + } } - public bool TryDecompileMapUnits( - JassFunctionDeclarationSyntax createAllUnitsFunction, - JassFunctionDeclarationSyntax createAllItemsFunction, - JassFunctionDeclarationSyntax configFunction, - JassFunctionDeclarationSyntax initCustomPlayerSlotsFunction, - MapWidgetsFormatVersion formatVersion, - MapWidgetsSubVersion subVersion, - bool useNewFormat, - [NotNullWhen(true)] out MapUnits? mapUnits) + [RegisterStatementParser] + internal void ParsePlayerIndex(StatementParserInput input) { - if (createAllUnitsFunction is null) + var variableAssignment = GetVariableAssignment(input.StatementChildren); + var match = input.StatementChildren.OfType().Where(x => x.Expression is IInvocationSyntax invocationSyntax && invocationSyntax.IdentifierName.Name == "Player").SafeMapFirst(x => { - throw new ArgumentNullException(nameof(createAllUnitsFunction)); - } + var playerId = GetPlayerIndex(x.Expression); + if (!playerId.HasValue) + { + return null; + } - if (createAllItemsFunction is null) + return new + { + PlayerIndex = playerId.Value + }; + }); + + if (match != null && variableAssignment != null) { - throw new ArgumentNullException(nameof(createAllItemsFunction)); + Context.Add_Struct(match.PlayerIndex, Context.CreatePseudoVariableName(nameof(ParsePlayerIndex), variableAssignment)); + Context.Add_Struct(match.PlayerIndex, Context.CreatePseudoVariableName(nameof(ParsePlayerIndex))); + Context.HandledStatements.Add(input.Statement); } + } - if (configFunction is null) + [RegisterStatementParser] + internal void ParseResourceAmount(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetResourceAmount").SafeMapFirst(x => { - throw new ArgumentNullException(nameof(configFunction)); - } + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + Amount = x.Arguments.Arguments[1].GetValueOrDefault() + }; + }); - if (initCustomPlayerSlotsFunction is null) + if (match != null) { - throw new ArgumentNullException(nameof(initCustomPlayerSlotsFunction)); + var unit = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (unit != null) + { + unit.GoldAmount = match.Amount; + Context.HandledStatements.Add(input.Statement); + } } + } - if (TryDecompileCreateUnitsFunction(createAllUnitsFunction, out var units) && - TryDecompileCreateItemsFunction(createAllItemsFunction, out var items) && - TryDecompileStartLocationPositionsConfigFunction(configFunction, out var startLocationPositions) && - TryDecompileInitCustomPlayerSlotsFunction(initCustomPlayerSlotsFunction, startLocationPositions, out var startLocations)) + [RegisterStatementParser] + internal void ParseSetUnitColor(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetUnitColor").SafeMapFirst(x => { - mapUnits = new MapUnits(formatVersion, subVersion, useNewFormat); - - mapUnits.Units.AddRange(units); - mapUnits.Units.AddRange(items); - mapUnits.Units.AddRange(startLocations); + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + CustomPlayerColorId = x.Arguments.Arguments[1].GetValueOrDefault() + }; + }); - return true; + if (match != null) + { + var unit = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (unit != null) + { + unit.CustomPlayerColorId = match.CustomPlayerColorId; + Context.HandledStatements.Add(input.Statement); + } } - - mapUnits = null; - return false; } - private bool TryDecompileCreateUnitsFunction(JassFunctionDeclarationSyntax createUnitsFunction, [NotNullWhen(true)] out List? units) + [RegisterStatementParser] + internal void ParseAcquireRange(StatementParserInput input) { - var localPlayerVariableName = (string?)null; - var localPlayerVariableValue = (int?)null; - - var result = new List(); + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "SetUnitAcquireRange") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + Range = x.Arguments.Arguments[1].GetValueOrDefault() + }; + }); - foreach (var statement in createUnitsFunction.Body.Statements) + if (match != null) { - if (statement is JassCommentSyntax || - statement is JassEmptySyntax) + var unit = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (unit != null) { - continue; + unit.TargetAcquisition = match.Range; + Context.HandledStatements.Add(input.Statement); } - else if (statement is JassLocalVariableDeclarationStatementSyntax localVariableDeclarationStatement) + } + } + + [RegisterStatementParser] + internal void ParseUnitState(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "SetUnitState") + .SafeMapFirst(x => + { + return new { - var typeName = localVariableDeclarationStatement.Declarator.Type.TypeName.Name; + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + StateType = ((JassVariableReferenceExpressionSyntax)x.Arguments.Arguments[1]).IdentifierName.Name, + Value = x.Arguments.Arguments[2].GetValueOrDefault() + }; + }); - if (string.Equals(typeName, "player", StringComparison.Ordinal)) - { - if (localVariableDeclarationStatement.Declarator is JassVariableDeclaratorSyntax variableDeclarator && - variableDeclarator.Value is not null && - variableDeclarator.Value.Expression is JassInvocationExpressionSyntax playerInvocationExpression && - string.Equals(playerInvocationExpression.IdentifierName.Name, "Player", StringComparison.Ordinal) && - playerInvocationExpression.Arguments.Arguments.Length == 1 && - playerInvocationExpression.Arguments.Arguments[0].TryGetPlayerIdExpressionValue(Context.MaxPlayerSlots, out var playerId)) - { - localPlayerVariableName = variableDeclarator.IdentifierName.Name; - localPlayerVariableValue = playerId; - } - else - { - units = null; - return false; - } - } - else if (string.Equals(typeName, "unit", StringComparison.Ordinal) || - string.Equals(typeName, "integer", StringComparison.Ordinal) || - string.Equals(typeName, "trigger", StringComparison.Ordinal) || - string.Equals(typeName, "real", StringComparison.Ordinal)) + + if (match != null) + { + var unit = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (unit != null) + { + if (match.StateType == "UNIT_STATE_LIFE") { - // TODO - - if (localVariableDeclarationStatement.Declarator is not JassVariableDeclaratorSyntax variableDeclarator || - variableDeclarator.Value is not null) - { - units = null; - return false; - } + unit.HP = (int)match.Value; + Context.HandledStatements.Add(input.Statement); } - else + else if (match.StateType == "UNIT_STATE_MANA") { - units = null; - return false; + unit.MP = (int)match.Value; + Context.HandledStatements.Add(input.Statement); } } - else if (statement is JassSetStatementSyntax setStatement) + } + } + + [RegisterStatementParser] + internal void ParseUnitInventory(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "UnitAddItemToSlotById") + .SafeMapFirst(x => + { + return new { - if (setStatement.Indexer is null) - { - if (setStatement.Value.Expression is JassInvocationExpressionSyntax invocationExpression) - { - if (string.Equals(invocationExpression.IdentifierName.Name, "CreateUnit", StringComparison.Ordinal)) - { - if (invocationExpression.Arguments.Arguments.Length == 5 && - invocationExpression.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax playerVariableReferenceExpression && - invocationExpression.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var unitId) && - invocationExpression.Arguments.Arguments[2].TryGetRealExpressionValue(out var x) && - invocationExpression.Arguments.Arguments[3].TryGetRealExpressionValue(out var y) && - invocationExpression.Arguments.Arguments[4].TryGetRealExpressionValue(out var face) && - string.Equals(playerVariableReferenceExpression.IdentifierName.Name, localPlayerVariableName, StringComparison.Ordinal)) - { - var unit = new UnitData - { - OwnerId = localPlayerVariableValue.Value, - TypeId = unitId.InvertEndianness(), - Position = new Vector3(x, y, 0f), - Rotation = face * (MathF.PI / 180f), - Scale = Vector3.One, - Flags = 2, - GoldAmount = 12500, - HeroLevel = 1, - CreationNumber = CreationNumber++ - }; - - unit.SkinId = unit.TypeId; - - result.Add(unit); - } - else - { - units = null; - return false; - } - } - else if (string.Equals(invocationExpression.IdentifierName.Name, "BlzCreateUnitWithSkin", StringComparison.Ordinal)) - { - if (invocationExpression.Arguments.Arguments.Length == 6 && - invocationExpression.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax playerVariableReferenceExpression && - invocationExpression.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var unitId) && - invocationExpression.Arguments.Arguments[2].TryGetRealExpressionValue(out var x) && - invocationExpression.Arguments.Arguments[3].TryGetRealExpressionValue(out var y) && - invocationExpression.Arguments.Arguments[4].TryGetRealExpressionValue(out var face) && - invocationExpression.Arguments.Arguments[5].TryGetIntegerExpressionValue(out var skinId) && - string.Equals(playerVariableReferenceExpression.IdentifierName.Name, localPlayerVariableName, StringComparison.Ordinal)) - { - var unit = new UnitData - { - OwnerId = localPlayerVariableValue.Value, - TypeId = unitId.InvertEndianness(), - Position = new Vector3(x, y, 0f), - Rotation = face * (MathF.PI / 180f), - Scale = Vector3.One, - SkinId = skinId.InvertEndianness(), - Flags = 2, - GoldAmount = 12500, - HeroLevel = 1, - CreationNumber = CreationNumber++ - }; - - result.Add(unit); - } - else - { - units = null; - return false; - } - } - else if (string.Equals(invocationExpression.IdentifierName.Name, "CreateTrigger", StringComparison.Ordinal)) - { - // TODO - continue; - } - else if (string.Equals(invocationExpression.IdentifierName.Name, "GetUnitState", StringComparison.Ordinal)) - { - // TODO - continue; - } - else if (string.Equals(invocationExpression.IdentifierName.Name, "RandomDistChoose", StringComparison.Ordinal)) - { - // TODO - continue; - } - else - { - units = null; - return false; - } - } - else if (setStatement.Value.Expression is JassArrayReferenceExpressionSyntax) - { - // TODO - continue; - } - else - { - units = null; - return false; - } - } - else + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + ItemId = x.Arguments.Arguments[1].GetValueOrDefault(), + Slot = x.Arguments.Arguments[2].GetValueOrDefault() + }; + }); + + + if (match != null) + { + var unit = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (unit != null) + { + unit.InventoryData.Add(new InventoryItemData { - units = null; - return false; - } + ItemId = match.ItemId, + Slot = match.Slot + }); + Context.HandledStatements.Add(input.Statement); } - else if (statement is JassCallStatementSyntax callStatement) + } + } + + [RegisterStatementParser] + internal void ParseHeroLevel(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "SetHeroLevel").SafeMapFirst(x => + { + return new { - if (string.Equals(callStatement.IdentifierName.Name, "SetResourceAmount", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax unitVariableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var amount)) - { - result[^1].GoldAmount = amount; - } - else - { - units = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetUnitColor", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax unitVariableReferenceExpression && - callStatement.Arguments.Arguments[1] is JassInvocationExpressionSyntax convertPlayerColorInvocationExpression && - string.Equals(convertPlayerColorInvocationExpression.IdentifierName.Name, "ConvertPlayerColor", StringComparison.Ordinal) && - convertPlayerColorInvocationExpression.Arguments.Arguments.Length == 1 && - convertPlayerColorInvocationExpression.Arguments.Arguments[0].TryGetIntegerExpressionValue(out var playerColorId)) - { - result[^1].CustomPlayerColorId = playerColorId; - } - else - { - units = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetUnitAcquireRange", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax unitVariableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var acquireRange)) - { - const float CampAcquireRange = 200f; - result[^1].TargetAcquisition = acquireRange == CampAcquireRange ? -2f : acquireRange; - } - else - { - units = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetUnitState", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 3 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax unitVariableReferenceExpression && - callStatement.Arguments.Arguments[1] is JassVariableReferenceExpressionSyntax unitStateVariableReferenceExpression) - { - if (string.Equals(unitStateVariableReferenceExpression.IdentifierName.Name, "UNIT_STATE_LIFE", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments[2] is JassBinaryExpressionSyntax binaryExpression && - binaryExpression.Left.TryGetRealExpressionValue(out var hp) && - binaryExpression.Operator == BinaryOperatorType.Multiplication && - binaryExpression.Right is JassVariableReferenceExpressionSyntax) - { - result[^1].HP = (int)(100 * hp); - } - else - { - units = null; - return false; - } - } - else if (string.Equals(unitStateVariableReferenceExpression.IdentifierName.Name, "UNIT_STATE_MANA", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments[2].TryGetIntegerExpressionValue(out var mp)) - { - result[^1].MP = mp; - } - else - { - units = null; - return false; - } - } - else - { - units = null; - return false; - } - } - else - { - units = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "UnitAddItemToSlotById", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 3 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax && - callStatement.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var itemId) && - callStatement.Arguments.Arguments[2].TryGetIntegerExpressionValue(out var slot)) - { - result[^1].InventoryData.Add(new InventoryItemData - { - ItemId = itemId, - Slot = slot, - }); - } - else - { - units = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetHeroLevel", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 3 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax unitVariableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var level) && - callStatement.Arguments.Arguments[2] is JassBooleanLiteralExpressionSyntax) - { - result[^1].HeroLevel = level; - } - else - { - units = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetHeroStr", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 3 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax unitVariableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var value) && - callStatement.Arguments.Arguments[2] is JassBooleanLiteralExpressionSyntax) - { - result[^1].HeroStrength = value; - } - else - { - units = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetHeroAgi", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 3 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax unitVariableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var value) && - callStatement.Arguments.Arguments[2] is JassBooleanLiteralExpressionSyntax) - { - result[^1].HeroAgility = value; - } - else - { - units = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SetHeroInt", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 3 && - callStatement.Arguments.Arguments[0] is JassVariableReferenceExpressionSyntax unitVariableReferenceExpression && - callStatement.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var value) && - callStatement.Arguments.Arguments[2] is JassBooleanLiteralExpressionSyntax) - { - result[^1].HeroIntelligence = value; - } - else - { - units = null; - return false; - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "SelectHeroSkill", StringComparison.Ordinal)) - { - // TODO - continue; - } - else if (string.Equals(callStatement.IdentifierName.Name, "IssueImmediateOrder", StringComparison.Ordinal)) - { - // TODO - continue; - } - else if (string.Equals(callStatement.IdentifierName.Name, "RandomDistReset", StringComparison.Ordinal)) - { - // TODO - continue; - } - else if (string.Equals(callStatement.IdentifierName.Name, "RandomDistAddItem", StringComparison.Ordinal)) - { - // TODO - continue; - } - else if (string.Equals(callStatement.IdentifierName.Name, "TriggerRegisterUnitEvent", StringComparison.Ordinal)) - { - // TODO - continue; - } - else if (string.Equals(callStatement.IdentifierName.Name, "TriggerAddAction", StringComparison.Ordinal)) - { - // TODO - continue; - } - else if (callStatement.Arguments.Arguments.IsEmpty) - { - if (Context.FunctionDeclarations.TryGetValue(callStatement.IdentifierName.Name, out var subFunction) && - TryDecompileCreateUnitsFunction(subFunction.FunctionDeclaration, out var subFunctionResult)) - { - result.AddRange(subFunctionResult); - } - else - { - units = null; - return false; - } - } - else - { - units = null; - return false; - } + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + Level = x.Arguments.Arguments[1].GetValueOrDefault() + }; + }); + + if (match != null) + { + var unit = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (unit != null) + { + unit.HeroLevel = match.Level; + Context.HandledStatements.Add(input.Statement); } - else if (statement is JassIfStatementSyntax ifStatement) + } + } + + [RegisterStatementParser] + internal void ParseHeroAttributes(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "SetHeroStr" || + x.IdentifierName.Name == "SetHeroAgi" || + x.IdentifierName.Name == "SetHeroInt") + .SafeMapFirst(x => + { + return new { - if (ifStatement.Condition.Deparenthesize() is JassBinaryExpressionSyntax binaryExpression && - binaryExpression.Left is JassVariableReferenceExpressionSyntax && - binaryExpression.Operator == BinaryOperatorType.NotEquals && - binaryExpression.Right.TryGetIntegerExpressionValue(out var value) && - value == -1) + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + Attribute = x.IdentifierName.Name, + Value = x.Arguments.Arguments[1].GetValueOrDefault() + }; + }); + + + if (match != null) + { + var unit = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (unit != null) + { + var handled = true; + switch (match.Attribute) { - // TODO - continue; + case "SetHeroStr": + unit.HeroStrength = match.Value; + break; + case "SetHeroAgi": + unit.HeroAgility = match.Value; + break; + case "SetHeroInt": + unit.HeroIntelligence = match.Value; + break; + default: + handled = false; + break; } - else + + if (handled) { - units = null; - return false; + Context.HandledStatements.Add(input.Statement); } } - else + } + } + + [RegisterStatementParser] + internal void ParseHeroSkills(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "SelectHeroSkill") + .SafeMapFirst(x => + { + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + SkillId = x.Arguments.Arguments[1].GetValueOrDefault() + }; + }); + + if (match != null) + { + var unit = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (unit != null) { - units = null; - return false; + unit.AbilityData.Add(new ModifiedAbilityData + { + AbilityId = match.SkillId, + HeroAbilityLevel = 1, + IsAutocastActive = false + }); + Context.HandledStatements.Add(input.Statement); } } - - units = result; - return true; } - private bool TryDecompileCreateItemsFunction(JassFunctionDeclarationSyntax createItemsFunction, [NotNullWhen(true)] out List? items) + [RegisterStatementParser] + internal void ParseIssueImmediateOrder(StatementParserInput input) { - var result = new List(); - - foreach (var statement in createItemsFunction.Body.Statements) + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "IssueImmediateOrder") + .SafeMapFirst(x => { - if (statement is JassCommentSyntax || - statement is JassEmptySyntax) + var abilityName = (x.Arguments.Arguments[1] as JassStringLiteralExpressionSyntax)?.Value; + bool? isAutoCastActive = null; + if (abilityName?.EndsWith("on", StringComparison.OrdinalIgnoreCase) == true) { - continue; + isAutoCastActive = true; } - else if (statement is JassSetStatementSyntax setStatement) + else if (abilityName?.EndsWith("off", StringComparison.OrdinalIgnoreCase) == true) { - if (setStatement.Value.Expression is JassInvocationExpressionSyntax invocationExpression) - { - if (string.Equals(invocationExpression.IdentifierName.Name, "CreateItem", StringComparison.Ordinal)) - { - if (invocationExpression.Arguments.Arguments.Length == 3 && - invocationExpression.Arguments.Arguments[0].TryGetIntegerExpressionValue(out var unitId) && - invocationExpression.Arguments.Arguments[1].TryGetRealExpressionValue(out var x) && - invocationExpression.Arguments.Arguments[2].TryGetRealExpressionValue(out var y)) - { - var unit = new UnitData - { - OwnerId = Context.MaxPlayerSlots + 3, // NEUTRAL_PASSIVE - TypeId = unitId.InvertEndianness(), - Position = new Vector3(x, y, 0f), - Rotation = 0, - Scale = Vector3.One, - Flags = 2, - GoldAmount = 12500, - HeroLevel = 1, - CreationNumber = CreationNumber++ - }; - - unit.SkinId = unit.TypeId; - - result.Add(unit); - } - } - else if (string.Equals(invocationExpression.IdentifierName.Name, "BlzCreateItemWithSkin", StringComparison.Ordinal)) - { - if (invocationExpression.Arguments.Arguments.Length == 4 && - invocationExpression.Arguments.Arguments[0].TryGetIntegerExpressionValue(out var unitId) && - invocationExpression.Arguments.Arguments[1].TryGetRealExpressionValue(out var x) && - invocationExpression.Arguments.Arguments[2].TryGetRealExpressionValue(out var y) && - invocationExpression.Arguments.Arguments[3].TryGetIntegerExpressionValue(out var skinId)) - { - var unit = new UnitData - { - OwnerId = Context.MaxPlayerSlots + 3, // NEUTRAL_PASSIVE - TypeId = unitId.InvertEndianness(), - Position = new Vector3(x, y, 0f), - Rotation = 0, - Scale = Vector3.One, - SkinId = skinId.InvertEndianness(), - Flags = 2, - GoldAmount = 12500, - HeroLevel = 1, - CreationNumber = CreationNumber++ - }; - - result.Add(unit); - } - } - } + isAutoCastActive = false; } - else if (statement is JassCallStatementSyntax callStatement) + else { - if (string.Equals(callStatement.IdentifierName.Name, "CreateItem", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 3 && - callStatement.Arguments.Arguments[0].TryGetIntegerExpressionValue(out var itemId) && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var x) && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var y)) - { - var item = new UnitData - { - OwnerId = Context.MaxPlayerSlots + 3, // NEUTRAL_PASSIVE - TypeId = itemId.InvertEndianness(), - Position = new Vector3(x, y, 0f), - Rotation = 0, - Scale = Vector3.One, - Flags = 2, - GoldAmount = 12500, - HeroLevel = 1, - CreationNumber = CreationNumber++ - }; - - item.SkinId = item.TypeId; - - result.Add(item); - } - } - else if (string.Equals(callStatement.IdentifierName.Name, "BlzCreateItemWithSkin", StringComparison.Ordinal)) + return null; + } + + return new + { + VariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + //AbilityName = abilityName, + IsAutocastActive = isAutoCastActive.Value + }; + }); + + if (match != null) + { + var unit = Context.Get(match.VariableName) ?? Context.GetLastCreated(); + if (unit != null) + { + //todo: lookup abilityId by searching for name in _context.ObjectData.map.AbilityObjectData + //todo: lookup on/off via SLK Metadata instead of abilityName suffix [example: Modifications with FourCC aoro/aord are names for "on", aorf/aoru are names for "off"] + var ability = unit.AbilityData.LastOrDefault(); + if (ability != null) { - if (callStatement.Arguments.Arguments.Length == 4 && - callStatement.Arguments.Arguments[0].TryGetIntegerExpressionValue(out var itemId) && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var x) && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var y) && - callStatement.Arguments.Arguments[3].TryGetIntegerExpressionValue(out var skinId)) - { - var item = new UnitData - { - OwnerId = Context.MaxPlayerSlots + 3, // NEUTRAL_PASSIVE - TypeId = itemId.InvertEndianness(), - Position = new Vector3(x, y, 0f), - Rotation = 0, - Scale = Vector3.One, - SkinId = skinId.InvertEndianness(), - Flags = 2, - GoldAmount = 12500, - HeroLevel = 1, - CreationNumber = CreationNumber++ - }; - - result.Add(item); - } + ability.IsAutocastActive = match.IsAutocastActive; + Context.HandledStatements.Add(input.Statement); } } } + } + + [RegisterStatementParser] + internal void ParseDropItemsOnDeath_TriggerRegister(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "TriggerRegisterUnitEvent") + .SafeMapFirst(x => + { + var eventName = (x.Arguments.Arguments[2] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name; + if (eventName != "EVENT_UNIT_DEATH" && eventName != "EVENT_UNIT_CHANGE_OWNER") + { + return null; + } - items = result; - return true; + return new + { + TriggerVariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + UnitVariableName = (x.Arguments.Arguments[1] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name + }; + }); + + if (match != null) + { + Context.Add(match.UnitVariableName, Context.CreatePseudoVariableName(nameof(ParseDropItemsOnDeath_TriggerRegister), match.TriggerVariableName)); + Context.HandledStatements.Add(input.Statement); + } } - private bool TryDecompileStartLocationPositionsConfigFunction(JassFunctionDeclarationSyntax configFunction, [NotNullWhen(true)] out Dictionary? startLocationPositions) + [RegisterStatementParser] + internal void ParseDropItemsOnDeath_TriggerAction(StatementParserInput input) { - var result = new Dictionary(); + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "TriggerAddAction") + .SafeMapFirst(x => + { + return new + { + TriggerVariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + FunctionName = (x.Arguments.Arguments[1] as JassFunctionReferenceExpressionSyntax)?.IdentifierName?.Name + }; + }); - foreach (var statement in configFunction.Body.Statements) + if (match != null) { - if (statement is JassCallStatementSyntax callStatement && - string.Equals(callStatement.IdentifierName.Name, "DefineStartLocation", StringComparison.Ordinal)) + var unitVariableName = Context.Get(Context.CreatePseudoVariableName(nameof(ParseDropItemsOnDeath_TriggerRegister), match.TriggerVariableName)); + + if (match.FunctionName != null && Context.FunctionDeclarations.TryGetValue(match.FunctionName, out var function)) { - if (callStatement.Arguments.Arguments.Length == 3 && - callStatement.Arguments.Arguments[0].TryGetIntegerExpressionValue(out var index) && - callStatement.Arguments.Arguments[1].TryGetRealExpressionValue(out var x) && - callStatement.Arguments.Arguments[2].TryGetRealExpressionValue(out var y)) + var statements = function.FunctionDeclaration.GetChildren_RecursiveDepthFirst().OfType().ToList(); + foreach (var statement in statements) { - result.Add(index, new Vector2(x, y)); + ProcessStatementParsers(statement, new Action[] { + input => ParseRandomDistReset(input, unitVariableName), + input => ParseRandomDistAddItem(input, unitVariableName), + }); } - else + + Context.HandledStatements.Add(input.Statement); + } + } + } + + private void ParseRandomDistReset(StatementParserInput input, string unitVariableName) + { + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "RandomDistReset") + .SafeMapFirst(x => + { + return new RandomItemSet(); + }); + + if (match != null) + { + var unit = Context.Get(unitVariableName) ?? Context.GetLastCreated(); + if (unit != null) + { + unit.ItemTableSets.Add(match); + } + Context.Add(match); + Context.HandledStatements.Add(input.Statement); + } + } + + private void ParseRandomDistAddItem(StatementParserInput input, string unitVariableName) + { + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "RandomDistAddItem") + .SafeMapFirst(x => + { + return new RandomItemSetItem() + { + ItemId = x.Arguments.Arguments[0].GetValueOrDefault().InvertEndianness(), + Chance = x.Arguments.Arguments[1].GetValueOrDefault(), + }; + }); + + if (match != null) + { + var unit = Context.Get(unitVariableName) ?? Context.GetLastCreated(); + if (unit != null) + { + var itemSet = unit.ItemTableSets.LastOrDefault(); + if (itemSet != null) { - startLocationPositions = null; - return false; + itemSet.Items.Add(match); + Context.HandledStatements.Add(input.Statement); } } - else + } + } + + [RegisterStatementParser] + internal void ParseItemCreation(StatementParserInput input) + { + var variableAssignment = GetVariableAssignment(input.StatementChildren); + var unitData = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "CreateItem" || x.IdentifierName.Name == "BlzCreateItemWithSkin").SafeMapFirst(x => + { + var args = x.Arguments.Arguments; + + if (!args[0].TryGetValue(out var typeId)) { - continue; + return null; } + + var result = new UnitData + { + OwnerId = Context.MaxPlayerSlots + 3, // NEUTRAL_PASSIVE + TypeId = typeId.InvertEndianness(), + Position = new Vector3( + args[1].GetValueOrDefault(), + args[2].GetValueOrDefault(), + 0f + ), + Rotation = 0, + Scale = Vector3.One, + Flags = 2, + GoldAmount = 12500, + HeroLevel = 1, + CreationNumber = Context.GetNextCreationNumber() + }; + + result.SkinId = args.Length > 3 ? args[3].GetValueOrDefault() : result.TypeId; + + return result; + }); + + if (unitData != null) + { + Context.Add(unitData, variableAssignment); + Context.HandledStatements.Add(input.Statement); } + } + + [RegisterStatementParser] + internal void ParseDefineStartLocation(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "DefineStartLocation") + .SafeMapFirst(x => + { + return new + { + Index = x.Arguments.Arguments[0].GetValueOrDefault(), + Location = new Vector2(x.Arguments.Arguments[1].GetValueOrDefault(), x.Arguments.Arguments[2].GetValueOrDefault()) + }; + }); - startLocationPositions = result; - return true; + if (match != null) + { + Context.Add_Struct(match.Location, Context.CreatePseudoVariableName(nameof(ParseDefineStartLocation), match.Index.ToString())); + Context.HandledStatements.Add(input.Statement); + } } - private bool TryDecompileInitCustomPlayerSlotsFunction( - JassFunctionDeclarationSyntax initCustomPlayerSlotsFunction, - Dictionary startLocationPositions, - [NotNullWhen(true)] out List? startLocations) + [RegisterStatementParser] + internal void ParseSetPlayerStartLocation(StatementParserInput input) { - var result = new List(); + var variableAssignment = GetVariableAssignment(input.StatementChildren); + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "SetPlayerStartLocation") + .SafeMapFirst(x => + { + int? playerId = GetPlayerIndex(x.Arguments.Arguments[0]) ?? GetLastCreatedPlayerIndex(); + + if (playerId == null) + { + return null; + } + + var startLocationIndex = x.Arguments.Arguments[1].GetValueOrDefault(); + var startLocationPosition = Context.Get_Struct(Context.CreatePseudoVariableName(nameof(ParseDefineStartLocation), startLocationIndex.ToString())); + + if (startLocationPosition == null) + { + return null; + } + + var args = x.Arguments.Arguments; + var result = new UnitData + { + OwnerId = playerId.Value, + TypeId = "sloc".FromRawcode(), + Position = new Vector3(startLocationPosition.Value, 0f), + Rotation = MathF.PI * 1.5f, + Scale = Vector3.One, + Flags = 2, + GoldAmount = 12500, + HeroLevel = 0, + TargetAcquisition = 0, + CreationNumber = Context.GetNextCreationNumber() + }; + + result.SkinId = result.TypeId; + + return result; + }); + + if (match != null) + { + Context.Add(match, variableAssignment); + Context.HandledStatements.Add(input.Statement); + } + } - foreach (var statement in initCustomPlayerSlotsFunction.Body.Statements) + [RegisterStatementParser] + internal void ParseWaygateDestination(StatementParserInput input) + { + var match = input.StatementChildren.OfType().Where(x => + x.IdentifierName.Name == "WaygateSetDestination") + .SafeMapFirst(x => { - if (statement is JassCommentSyntax || - statement is JassEmptySyntax) + var regionVariableName = input.StatementChildren.OfType().Where(x => x.IdentifierName.Name == "GetRectCenterX" || x.IdentifierName.Name == "GetRectCenterY").SafeMapFirst(x => ((JassVariableReferenceExpressionSyntax)x.Arguments.Arguments[0]).IdentifierName.Name); + if (regionVariableName == null) { - continue; + var destination = new Vector2(x.Arguments.Arguments[1].GetValueOrDefault(), x.Arguments.Arguments[2].GetValueOrDefault()); + var region = Context.GetAll().LastOrDefault(x => x.CenterX == destination.X && x.CenterY == destination.Y); + regionVariableName = Context.GetVariableName(region); } - else if (statement is JassCallStatementSyntax callStatement) + + return new { - if (string.Equals(callStatement.IdentifierName.Name, "SetPlayerStartLocation", StringComparison.Ordinal)) - { - if (callStatement.Arguments.Arguments.Length == 2 && - callStatement.Arguments.Arguments[0] is JassInvocationExpressionSyntax playerInvocationExpression && - string.Equals(playerInvocationExpression.IdentifierName.Name, "Player", StringComparison.Ordinal) && - playerInvocationExpression.Arguments.Arguments.Length == 1 && - playerInvocationExpression.Arguments.Arguments[0].TryGetPlayerIdExpressionValue(Context.MaxPlayerSlots, out var playerId) && - callStatement.Arguments.Arguments[1].TryGetIntegerExpressionValue(out var startLocationNumber) && - startLocationPositions.TryGetValue(startLocationNumber, out var startLocationPosition)) - { - var unit = new UnitData - { - OwnerId = playerId, - TypeId = "sloc".FromRawcode(), - Position = new Vector3(startLocationPosition, 0f), - Rotation = MathF.PI * 1.5f, - Scale = Vector3.One, - Flags = 2, - GoldAmount = 12500, - HeroLevel = 0, - TargetAcquisition = 0, - CreationNumber = CreationNumber++ - }; - - unit.SkinId = unit.TypeId; - - result.Add(unit); - } - else - { - startLocations = null; - return false; - } - } - else - { - continue; - } - } - else + UnitVariableName = (x.Arguments.Arguments[0] as JassVariableReferenceExpressionSyntax)?.IdentifierName?.Name, + RegionVariableName = regionVariableName + }; + }); + + if (match != null) + { + var unit = Context.Get(match.UnitVariableName) ?? Context.GetLastCreated(); + var region = Context.Get(match.RegionVariableName) ?? Context.GetLastCreated(); + if (unit != null && region != null) { - startLocations = null; - return false; + unit.WaygateDestinationRegionId = region.CreationNumber; + Context.HandledStatements.Add(input.Statement); } } - - startLocations = result; - return true; } } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Extensions/ExpressionSyntaxExtensions.cs b/src/War3Net.CodeAnalysis.Jass/Extensions/ExpressionSyntaxExtensions.cs index 620a3cc1..40f91a9e 100644 --- a/src/War3Net.CodeAnalysis.Jass/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/War3Net.CodeAnalysis.Jass/Extensions/ExpressionSyntaxExtensions.cs @@ -1,16 +1,16 @@ // ------------------------------------------------------------------------------ -// +// // Licensed under the MIT license. // See the LICENSE file in the project root for more information. // // ------------------------------------------------------------------------------ using System; -using System.Globalization; +using War3Net.CodeAnalysis.Jass; using War3Net.CodeAnalysis.Jass.Syntax; -namespace War3Net.CodeAnalysis.Jass.Extensions +namespace War3Net.CodeAnalysis.Decompilers { public static class ExpressionSyntaxExtensions { @@ -24,79 +24,159 @@ public static IExpressionSyntax Deparenthesize(this IExpressionSyntax expression return expression; } - public static bool TryGetIntegerExpressionValue(this IExpressionSyntax expression, out int value) + public static T GetValueOrDefault(this IExpressionSyntax expression, T defaultValue = default) { - switch (expression) + if (expression.TryGetValue(out var value)) { - case JassDecimalLiteralExpressionSyntax decimalLiteralExpression: - value = decimalLiteralExpression.Value; - return true; - - case JassOctalLiteralExpressionSyntax octalLiteralExpression: - value = octalLiteralExpression.Value; - return true; - - case JassFourCCLiteralExpressionSyntax fourCCLiteralExpression: - value = fourCCLiteralExpression.Value; - return true; + return value; + } - case JassUnaryExpressionSyntax unaryExpression: - return int.TryParse(unaryExpression.ToString(), out value); + return defaultValue; + } - case JassHexadecimalLiteralExpressionSyntax hexLiteralExpression: - value = hexLiteralExpression.Value; - return true; + public static bool TryGetValue(this IExpressionSyntax expression, out T value) + { + value = default; - default: - value = default; - return false; + if (!expression.TryGetStringExpressionValue(out var stringValue)) + { + return false; } - } - public static bool TryGetPlayerIdExpressionValue(this IExpressionSyntax expression, int maxPlayerSlots, out int value) - { - if (expression is JassVariableReferenceExpressionSyntax variableReferenceExpression) + if (typeof(T) == typeof(string)) { - if (string.Equals(variableReferenceExpression.IdentifierName.Name, "PLAYER_NEUTRAL_AGGRESSIVE", StringComparison.Ordinal)) - { - value = maxPlayerSlots; - return true; - } - else if (string.Equals(variableReferenceExpression.IdentifierName.Name, "PLAYER_NEUTRAL_PASSIVE", StringComparison.Ordinal)) - { - value = maxPlayerSlots + 3; - return true; - } - else - { - value = default; - return false; - } + value = (T)(object)stringValue; + return true; } - else + + if (decimal.TryParse(stringValue, out var decimalValue)) { - return expression.TryGetIntegerExpressionValue(out value); + value = SafeConvertDecimalTo(decimalValue); + return true; } + + return false; } - public static bool TryGetRealExpressionValue(this IExpressionSyntax expression, out float value) + private static T SafeConvertDecimalTo(decimal value) { - if (expression.TryGetIntegerExpressionValue(out var intValue)) + if (Nullable.GetUnderlyingType(typeof(T)) != null) { - value = intValue; - return true; + var nonNullableType = Nullable.GetUnderlyingType(typeof(T)); + var method = typeof(ExpressionSyntaxExtensions).GetMethod(nameof(SafeConvertDecimalTo), + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var genericMethod = method.MakeGenericMethod(nonNullableType); + var result = genericMethod.Invoke(null, new object[] { value }); + return (T)result; } - if (expression is JassRealLiteralExpressionSyntax realLiteralExpression && - float.TryParse(realLiteralExpression.ToString(), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out value)) + if (typeof(T) == typeof(int)) { - return true; + return (T)(object)(int)Math.Clamp(value, int.MinValue, int.MaxValue); + } + else if (typeof(T) == typeof(uint)) + { + return (T)(object)(uint)Math.Clamp(value, uint.MinValue, uint.MaxValue); + } + else if (typeof(T) == typeof(byte)) + { + return (T)(object)(byte)Math.Clamp(value, byte.MinValue, byte.MaxValue); + } + else if (typeof(T) == typeof(sbyte)) + { + return (T)(object)(sbyte)Math.Clamp(value, sbyte.MinValue, sbyte.MaxValue); + } + else if (typeof(T) == typeof(short)) + { + return (T)(object)(short)Math.Clamp(value, short.MinValue, short.MaxValue); + } + else if (typeof(T) == typeof(ushort)) + { + return (T)(object)(ushort)Math.Clamp(value, ushort.MinValue, ushort.MaxValue); + } + else if (typeof(T) == typeof(long)) + { + return (T)(object)(long)Math.Clamp(value, long.MinValue, long.MaxValue); + } + else if (typeof(T) == typeof(ulong)) + { + return (T)(object)(ulong)Math.Clamp(value, ulong.MinValue, ulong.MaxValue); + } + else if (typeof(T) == typeof(bool)) + { + return (T)(object)(value != 0); + } + else if (typeof(T) == typeof(decimal)) + { + return (T)(object)value; + } + else if (typeof(T) == typeof(float)) + { + return (T)(object)(float)value; + } + else if (typeof(T) == typeof(double)) + { + return (T)(object)(double)value; } - if (expression is JassUnaryExpressionSyntax unaryExpression && - float.TryParse(unaryExpression.ToString(), NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out value)) + return default; + } + + private static bool TryGetStringExpressionValue(this IExpressionSyntax expression, out string value) + { + switch (expression) { - return true; + case JassBooleanLiteralExpressionSyntax booleanLiteralExpression: + value = booleanLiteralExpression.Value ? "1" : "0"; + return true; + + case JassDecimalLiteralExpressionSyntax decimalLiteralExpression: + value = decimalLiteralExpression.Value.ToString(); + return true; + + case JassRealLiteralExpressionSyntax realLiteralExpression: + value = realLiteralExpression.IntPart + "." + realLiteralExpression.FracPart; + return true; + + case JassOctalLiteralExpressionSyntax octalLiteralExpression: + value = octalLiteralExpression.Value.ToString(); + return true; + + case JassFourCCLiteralExpressionSyntax fourCCLiteralExpression: + value = fourCCLiteralExpression.Value.ToString(); + return true; + + case JassUnaryExpressionSyntax unaryExpression: + if (unaryExpression.Operator != UnaryOperatorType.Not && TryGetStringExpressionValue(unaryExpression.Expression, out var unaryExpressionValue)) + { + value = (unaryExpression.Operator == UnaryOperatorType.Minus ? "-" : "") + unaryExpressionValue; + return true; + } + break; + + case JassHexadecimalLiteralExpressionSyntax hexLiteralExpression: + value = hexLiteralExpression.Value.ToString(); + return true; + + case JassStringLiteralExpressionSyntax stringLiteralExpression: + value = stringLiteralExpression.Value; + return true; + + case JassParenthesizedExpressionSyntax parenthesizedExpression: + if (TryGetStringExpressionValue(parenthesizedExpression.Expression, out var parenthesizedExpressionValue)) + { + value = parenthesizedExpressionValue; + return true; + } + break; + + case JassCharacterLiteralExpressionSyntax charLiteralExpression: + value = charLiteralExpression.Value.ToString(); + return true; + + case JassNullLiteralExpressionSyntax: + value = JassKeyword.Null; + return true; } value = default; diff --git a/src/War3Net.CodeAnalysis.Jass/JassRenderer.cs b/src/War3Net.CodeAnalysis.Jass/JassRenderer.cs index 5ebd51c7..85756e90 100644 --- a/src/War3Net.CodeAnalysis.Jass/JassRenderer.cs +++ b/src/War3Net.CodeAnalysis.Jass/JassRenderer.cs @@ -8,6 +8,8 @@ using System.IO; using System.Linq; +using War3Net.CodeAnalysis.Jass.Syntax; + namespace War3Net.CodeAnalysis.Jass { public partial class JassRenderer @@ -81,5 +83,177 @@ private void Outdent() { _currentIndentation--; } + + public void Render(IJassSyntaxToken syntaxToken) + { + switch (syntaxToken) + { + case JassArgumentListSyntax token: + Render(token); + return; + case JassArrayDeclaratorSyntax token: + Render(token); + return; + case JassArrayReferenceExpressionSyntax token: + Render(token); + return; + case JassBinaryExpressionSyntax token: + Render(token); + return; + case JassBooleanLiteralExpressionSyntax token: + Render(token); + return; + case JassCallStatementSyntax token: + Render(token); + return; + case JassCharacterLiteralExpressionSyntax token: + Render(token); + return; + case JassCommentSyntax token: + Render(token); + return; + case JassCompilationUnitSyntax token: + Render(token); + return; + case JassDebugCustomScriptAction token: + Render(token); + return; + case JassDebugStatementSyntax token: + Render(token); + return; + case JassDecimalLiteralExpressionSyntax token: + Render(token); + return; + case JassElseClauseSyntax token: + Render(token); + return; + case JassElseCustomScriptAction token: + Render(token); + return; + case JassElseIfClauseSyntax token: + Render(token); + return; + case JassElseIfCustomScriptAction token: + Render(token); + return; + case JassEmptySyntax token: + Render(token); + return; + case JassEndFunctionCustomScriptAction token: + Render(token); + return; + case JassEndGlobalsCustomScriptAction token: + Render(token); + return; + case JassEndIfCustomScriptAction token: + Render(token); + return; + case JassEndLoopCustomScriptAction token: + Render(token); + return; + case JassEqualsValueClauseSyntax token: + Render(token); + return; + case JassExitStatementSyntax token: + Render(token); + return; + case JassFourCCLiteralExpressionSyntax token: + Render(token); + return; + case JassFunctionCustomScriptAction token: + Render(token); + return; + case JassFunctionDeclarationSyntax token: + Render(token); + return; + case JassFunctionDeclaratorSyntax token: + Render(token); + return; + case JassFunctionReferenceExpressionSyntax token: + Render(token); + return; + case JassGlobalDeclarationListSyntax token: + Render(token); + return; + case JassGlobalDeclarationSyntax token: + Render(token); + return; + case JassGlobalsCustomScriptAction token: + Render(token); + return; + case JassHexadecimalLiteralExpressionSyntax token: + Render(token); + return; + case JassIdentifierNameSyntax token: + Render(token); + return; + case JassIfCustomScriptAction token: + Render(token); + return; + case JassIfStatementSyntax token: + Render(token); + return; + case JassInvocationExpressionSyntax token: + Render(token); + return; + case JassLocalVariableDeclarationStatementSyntax token: + Render(token); + return; + case JassLoopCustomScriptAction token: + Render(token); + return; + case JassLoopStatementSyntax token: + Render(token); + return; + case JassNativeFunctionDeclarationSyntax token: + Render(token); + return; + case JassNullLiteralExpressionSyntax token: + Render(token); + return; + case JassOctalLiteralExpressionSyntax token: + Render(token); + return; + case JassParameterListSyntax token: + Render(token); + return; + case JassParameterSyntax token: + Render(token); + return; + case JassParenthesizedExpressionSyntax token: + Render(token); + return; + case JassRealLiteralExpressionSyntax token: + Render(token); + return; + case JassReturnStatementSyntax token: + Render(token); + return; + case JassSetStatementSyntax token: + Render(token); + return; + case JassStatementListSyntax token: + Render(token); + return; + case JassStringLiteralExpressionSyntax token: + Render(token); + return; + case JassTypeDeclarationSyntax token: + Render(token); + return; + case JassTypeSyntax token: + Render(token); + return; + case JassUnaryExpressionSyntax token: + Render(token); + return; + case JassVariableDeclaratorSyntax token: + Render(token); + return; + case JassVariableReferenceExpressionSyntax token: + Render(token); + return; + } + } } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IDeclarationLineSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IDeclarationLineSyntax.cs index ff4111ca..47ec7e34 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IDeclarationLineSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IDeclarationLineSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IDeclarationLineSyntax : IEquatable + public interface IDeclarationLineSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IExpressionSyntax.cs index 4c015ece..b223fad1 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IExpressionSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IExpressionSyntax : IEquatable + public interface IExpressionSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IGlobalDeclarationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IGlobalDeclarationSyntax.cs index 685ac3e3..5e104850 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IGlobalDeclarationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IGlobalDeclarationSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IGlobalDeclarationSyntax : IEquatable + public interface IGlobalDeclarationSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IGlobalLineSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IGlobalLineSyntax.cs index 221f11c9..48009524 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IGlobalLineSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IGlobalLineSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IGlobalLineSyntax : IEquatable + public interface IGlobalLineSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IInvocationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IInvocationSyntax.cs index 39d087a4..a7b42ae5 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IInvocationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IInvocationSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IInvocationSyntax + public interface IInvocationSyntax : IJassSyntaxToken { public JassIdentifierNameSyntax IdentifierName { get; init; } diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IJassSyntaxToken.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IJassSyntaxToken.cs new file mode 100644 index 00000000..eb77bc75 --- /dev/null +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IJassSyntaxToken.cs @@ -0,0 +1,16 @@ +// ------------------------------------------------------------------------------ +// +// Licensed under the MIT license. +// See the LICENSE file in the project root for more information. +// +// ------------------------------------------------------------------------------ + +using System.Collections; + +namespace War3Net.CodeAnalysis.Jass.Syntax +{ + public interface IJassSyntaxToken + { + + } +} \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IJassSyntaxTokenExtensions.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IJassSyntaxTokenExtensions.cs new file mode 100644 index 00000000..0132a645 --- /dev/null +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IJassSyntaxTokenExtensions.cs @@ -0,0 +1,110 @@ +// ------------------------------------------------------------------------------ +// +// Licensed under the MIT license. +// See the LICENSE file in the project root for more information. +// +// ------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace War3Net.CodeAnalysis.Jass.Syntax +{ + public static class IJassSyntaxTokenExtensions + { + public static IEnumerable GetChildren(this IJassSyntaxToken syntaxToken) + { + if (syntaxToken == null) + { + return Array.Empty(); + } + + var result = syntaxToken switch + { + JassArgumentListSyntax token => token.Arguments.Cast(), + JassArrayDeclaratorSyntax token => new IJassSyntaxToken[] { token.Type, token.IdentifierName }, + JassArrayReferenceExpressionSyntax token => new IJassSyntaxToken[] { token.IdentifierName, token.Indexer }, + JassBinaryExpressionSyntax token => new IJassSyntaxToken[] { token.Left, token.Right }, + JassBooleanLiteralExpressionSyntax _ => Array.Empty(), + JassCallStatementSyntax token => new IJassSyntaxToken[] { token.IdentifierName, token.Arguments }, + JassCharacterLiteralExpressionSyntax _ => Array.Empty(), + JassCommentSyntax _ => Array.Empty(), + JassCompilationUnitSyntax token => token.Declarations.Cast(), + JassDebugCustomScriptAction token => new IJassSyntaxToken[] { token.Action }, + JassDebugStatementSyntax token => new IJassSyntaxToken[] { token.Statement }, + JassDecimalLiteralExpressionSyntax _ => Array.Empty(), + JassElseClauseSyntax token => new IJassSyntaxToken[] { token.Body }, + JassElseCustomScriptAction _ => Array.Empty(), + JassElseIfClauseSyntax token => new IJassSyntaxToken[] { token.Condition, token.Body }, + JassElseIfCustomScriptAction token => new IJassSyntaxToken[] { token.Condition }, + JassEmptySyntax _ => Array.Empty(), + JassEndFunctionCustomScriptAction _ => Array.Empty(), + JassEndGlobalsCustomScriptAction _ => Array.Empty(), + JassEndIfCustomScriptAction _ => Array.Empty(), + JassEndLoopCustomScriptAction _ => Array.Empty(), + JassEqualsValueClauseSyntax token => new IJassSyntaxToken[] { token.Expression }, + JassExitStatementSyntax token => new IJassSyntaxToken[] { token.Condition }, + JassFourCCLiteralExpressionSyntax _ => Array.Empty(), + JassFunctionCustomScriptAction token => new IJassSyntaxToken[] { token.FunctionDeclarator }, + JassFunctionDeclarationSyntax token => new IJassSyntaxToken[] { token.FunctionDeclarator, token.Body }, + JassFunctionDeclaratorSyntax token => new IJassSyntaxToken[] { token.IdentifierName, token.ParameterList, token.ReturnType }, + JassFunctionReferenceExpressionSyntax token => new IJassSyntaxToken[] { token.IdentifierName }, + JassGlobalDeclarationListSyntax token => token.Globals.Cast(), + JassGlobalDeclarationSyntax token => new IJassSyntaxToken[] { token.Declarator }, + JassGlobalsCustomScriptAction _ => Array.Empty(), + JassHexadecimalLiteralExpressionSyntax _ => Array.Empty(), + JassIdentifierNameSyntax _ => Array.Empty(), + JassIfCustomScriptAction token => new IJassSyntaxToken[] { token.Condition }, + JassIfStatementSyntax token => new IJassSyntaxToken[] { token.Condition } + .Concat(new[] { token.Body }) + .Concat(token.ElseIfClauses.Cast()) + .Concat(new[] { token.ElseClause }), + JassInvocationExpressionSyntax token => new IJassSyntaxToken[] { token.IdentifierName, token.Arguments }, + JassLocalVariableDeclarationStatementSyntax token => new IJassSyntaxToken[] { token.Declarator }, + JassLoopCustomScriptAction _ => Array.Empty(), + JassLoopStatementSyntax token => new IJassSyntaxToken[] { token.Body }, + JassNativeFunctionDeclarationSyntax token => new IJassSyntaxToken[] { token.FunctionDeclarator }, + JassNullLiteralExpressionSyntax _ => Array.Empty(), + JassOctalLiteralExpressionSyntax _ => Array.Empty(), + JassParameterListSyntax token => token.Parameters.Cast(), + JassParameterSyntax token => new IJassSyntaxToken[] { token.Type, token.IdentifierName }, + JassParenthesizedExpressionSyntax token => new IJassSyntaxToken[] { token.Expression }, + JassRealLiteralExpressionSyntax _ => Array.Empty(), + JassReturnStatementSyntax token => new IJassSyntaxToken[] { token.Value }, + JassSetStatementSyntax token => new IJassSyntaxToken[] { token.IdentifierName, token.Indexer, token.Value }, + JassStatementListSyntax token => token.Statements.Cast(), + JassStringLiteralExpressionSyntax _ => Array.Empty(), + JassTypeDeclarationSyntax token => new IJassSyntaxToken[] { token.IdentifierName, token.BaseType }, + JassTypeSyntax token => new IJassSyntaxToken[] { token.TypeName }, + JassUnaryExpressionSyntax token => new IJassSyntaxToken[] { token.Expression }, + JassVariableDeclaratorSyntax token => new IJassSyntaxToken[] { token.Type, token.IdentifierName, token.Value }, + JassVariableReferenceExpressionSyntax token => new IJassSyntaxToken[] { token.IdentifierName }, + _ => throw new ArgumentException($"Argument of type '{syntaxToken?.GetType()?.Name ?? string.Empty}' isn't supported by {nameof(IJassSyntaxTokenExtensions)}.{nameof(GetChildren)} extension method.", nameof(syntaxToken)) + }; + + return result.Where(x => x != null); + } + + public static IEnumerable GetChildren_RecursiveDepthFirst(this IJassSyntaxToken syntaxToken) + { + var stack = new Stack(); + stack.Push(syntaxToken); + + while (stack.Count > 0) + { + var current = stack.Pop(); + yield return current; + + var children = current.GetChildren(); + if (children != null) + { + foreach (var child in children.Reverse()) + { + stack.Push(child); + } + } + } + } + } +} diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IMemberDeclarationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IMemberDeclarationSyntax.cs index 953e24a4..2f14d796 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IMemberDeclarationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IMemberDeclarationSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IMemberDeclarationSyntax : IEquatable + public interface IMemberDeclarationSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IScopedDeclarationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IScopedDeclarationSyntax.cs index 5b3b67ac..821fd15e 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IScopedDeclarationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IScopedDeclarationSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IScopedDeclarationSyntax : IEquatable + public interface IScopedDeclarationSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IScopedGlobalDeclarationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IScopedGlobalDeclarationSyntax.cs index 6ac2795f..733d2406 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IScopedGlobalDeclarationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IScopedGlobalDeclarationSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IScopedGlobalDeclarationSyntax : IEquatable + public interface IScopedGlobalDeclarationSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IStatementLineSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IStatementLineSyntax.cs index 6e5a531f..d90bd650 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IStatementLineSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IStatementLineSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IStatementLineSyntax : IEquatable + public interface IStatementLineSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IStatementSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IStatementSyntax.cs index b618f868..4f975da2 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IStatementSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IStatementSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IStatementSyntax : IEquatable + public interface IStatementSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/ITopLevelDeclarationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/ITopLevelDeclarationSyntax.cs index 70abd3ed..bfba16d2 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/ITopLevelDeclarationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/ITopLevelDeclarationSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface ITopLevelDeclarationSyntax : IEquatable + public interface ITopLevelDeclarationSyntax : IEquatable, IJassSyntaxToken { } } \ No newline at end of file diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/IVariableDeclaratorSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/IVariableDeclaratorSyntax.cs index 455f261d..3afd47a0 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/IVariableDeclaratorSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/IVariableDeclaratorSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public interface IVariableDeclaratorSyntax : IEquatable + public interface IVariableDeclaratorSyntax : IEquatable, IJassSyntaxToken { JassTypeSyntax Type { get; init; } diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassArgumentListSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassArgumentListSyntax.cs index 7f5d431a..5885f14e 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassArgumentListSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassArgumentListSyntax.cs @@ -11,7 +11,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassArgumentListSyntax : IEquatable + public class JassArgumentListSyntax : IEquatable, IJassSyntaxToken { public JassArgumentListSyntax(ImmutableArray arguments) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassArrayDeclaratorSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassArrayDeclaratorSyntax.cs index ed4d9c6e..34ef52e8 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassArrayDeclaratorSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassArrayDeclaratorSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassArrayDeclaratorSyntax : IVariableDeclaratorSyntax + public class JassArrayDeclaratorSyntax : IVariableDeclaratorSyntax, IJassSyntaxToken { public JassArrayDeclaratorSyntax(JassTypeSyntax type, JassIdentifierNameSyntax identifierName) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassArrayReferenceExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassArrayReferenceExpressionSyntax.cs index da61f525..0f848a2a 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassArrayReferenceExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassArrayReferenceExpressionSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassArrayReferenceExpressionSyntax : IExpressionSyntax + public class JassArrayReferenceExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassArrayReferenceExpressionSyntax(JassIdentifierNameSyntax identifierName, IExpressionSyntax indexer) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassBinaryExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassBinaryExpressionSyntax.cs index 3bc5517f..341945e6 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassBinaryExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassBinaryExpressionSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassBinaryExpressionSyntax : IExpressionSyntax + public class JassBinaryExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassBinaryExpressionSyntax(BinaryOperatorType @operator, IExpressionSyntax left, IExpressionSyntax right) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassBooleanLiteralExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassBooleanLiteralExpressionSyntax.cs index 90569d5d..4fa4f4ad 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassBooleanLiteralExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassBooleanLiteralExpressionSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassBooleanLiteralExpressionSyntax : IExpressionSyntax + public class JassBooleanLiteralExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public static readonly JassBooleanLiteralExpressionSyntax True = new JassBooleanLiteralExpressionSyntax(true); public static readonly JassBooleanLiteralExpressionSyntax False = new JassBooleanLiteralExpressionSyntax(false); diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassCallStatementSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassCallStatementSyntax.cs index 8d42270f..22ca8548 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassCallStatementSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassCallStatementSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassCallStatementSyntax : IStatementSyntax, IStatementLineSyntax, IInvocationSyntax + public class JassCallStatementSyntax : IStatementSyntax, IStatementLineSyntax, IInvocationSyntax, IJassSyntaxToken { public JassCallStatementSyntax(JassIdentifierNameSyntax identifierName, JassArgumentListSyntax arguments) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassCharacterLiteralExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassCharacterLiteralExpressionSyntax.cs index 52d5461c..244698bf 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassCharacterLiteralExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassCharacterLiteralExpressionSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassCharacterLiteralExpressionSyntax : IExpressionSyntax + public class JassCharacterLiteralExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassCharacterLiteralExpressionSyntax(char value) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassCommentSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassCommentSyntax.cs index 9a048603..06b72b5a 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassCommentSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassCommentSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassCommentSyntax : ITopLevelDeclarationSyntax, IScopedDeclarationSyntax, IGlobalDeclarationSyntax, IScopedGlobalDeclarationSyntax, IMemberDeclarationSyntax, IStatementSyntax, IDeclarationLineSyntax, IGlobalLineSyntax, IStatementLineSyntax + public class JassCommentSyntax : ITopLevelDeclarationSyntax, IScopedDeclarationSyntax, IGlobalDeclarationSyntax, IScopedGlobalDeclarationSyntax, IMemberDeclarationSyntax, IStatementSyntax, IDeclarationLineSyntax, IGlobalLineSyntax, IStatementLineSyntax, IJassSyntaxToken { public JassCommentSyntax(string comment) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassCompilationUnitSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassCompilationUnitSyntax.cs index 0c1e458b..3c2a8c50 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassCompilationUnitSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassCompilationUnitSyntax.cs @@ -11,7 +11,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassCompilationUnitSyntax : IEquatable + public class JassCompilationUnitSyntax : IEquatable, IJassSyntaxToken { public JassCompilationUnitSyntax(ImmutableArray declarations) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassDebugStatementSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassDebugStatementSyntax.cs index a403a987..8216bd44 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassDebugStatementSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassDebugStatementSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassDebugStatementSyntax : IStatementSyntax + public class JassDebugStatementSyntax : IStatementSyntax, IJassSyntaxToken { public JassDebugStatementSyntax(IStatementSyntax statement) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassDecimalLiteralExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassDecimalLiteralExpressionSyntax.cs index 2f5a6b36..6627921e 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassDecimalLiteralExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassDecimalLiteralExpressionSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassDecimalLiteralExpressionSyntax : IExpressionSyntax + public class JassDecimalLiteralExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassDecimalLiteralExpressionSyntax(int value) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassElseClauseSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassElseClauseSyntax.cs index 46e279d1..e53f3f14 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassElseClauseSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassElseClauseSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassElseClauseSyntax : IEquatable + public class JassElseClauseSyntax : IEquatable, IJassSyntaxToken { public JassElseClauseSyntax(JassStatementListSyntax body) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassElseIfClauseSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassElseIfClauseSyntax.cs index d5393202..a2d72c89 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassElseIfClauseSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassElseIfClauseSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassElseIfClauseSyntax : IEquatable + public class JassElseIfClauseSyntax : IEquatable, IJassSyntaxToken { public JassElseIfClauseSyntax(IExpressionSyntax condition, JassStatementListSyntax body) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassEmptySyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassEmptySyntax.cs index 5b13cec4..66a5e887 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassEmptySyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassEmptySyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassEmptySyntax : ITopLevelDeclarationSyntax, IScopedDeclarationSyntax, IGlobalDeclarationSyntax, IScopedGlobalDeclarationSyntax, IMemberDeclarationSyntax, IStatementSyntax, IDeclarationLineSyntax, IGlobalLineSyntax, IStatementLineSyntax + public class JassEmptySyntax : ITopLevelDeclarationSyntax, IScopedDeclarationSyntax, IGlobalDeclarationSyntax, IScopedGlobalDeclarationSyntax, IMemberDeclarationSyntax, IStatementSyntax, IDeclarationLineSyntax, IGlobalLineSyntax, IStatementLineSyntax, IJassSyntaxToken { public static readonly JassEmptySyntax Value = new JassEmptySyntax(); diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassEqualsValueClauseSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassEqualsValueClauseSyntax.cs index 5601ff8c..b4a77280 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassEqualsValueClauseSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassEqualsValueClauseSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassEqualsValueClauseSyntax : IEquatable + public class JassEqualsValueClauseSyntax : IEquatable, IJassSyntaxToken { public JassEqualsValueClauseSyntax(IExpressionSyntax expression) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassExitStatementSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassExitStatementSyntax.cs index 0ce16e9f..42c4cb03 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassExitStatementSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassExitStatementSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassExitStatementSyntax : IStatementSyntax, IStatementLineSyntax + public class JassExitStatementSyntax : IStatementSyntax, IStatementLineSyntax, IJassSyntaxToken { public JassExitStatementSyntax(IExpressionSyntax condition) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassFourCCLiteralExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassFourCCLiteralExpressionSyntax.cs index 55031196..240b28b9 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassFourCCLiteralExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassFourCCLiteralExpressionSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassFourCCLiteralExpressionSyntax : IExpressionSyntax + public class JassFourCCLiteralExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassFourCCLiteralExpressionSyntax(int value) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionDeclarationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionDeclarationSyntax.cs index a0f9a8e7..fa024772 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionDeclarationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionDeclarationSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassFunctionDeclarationSyntax : ITopLevelDeclarationSyntax + public class JassFunctionDeclarationSyntax : ITopLevelDeclarationSyntax, IJassSyntaxToken { public JassFunctionDeclarationSyntax(JassFunctionDeclaratorSyntax functionDeclarator, JassStatementListSyntax body) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionDeclaratorSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionDeclaratorSyntax.cs index 4abf03fa..3df19389 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionDeclaratorSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionDeclaratorSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassFunctionDeclaratorSyntax : IEquatable + public class JassFunctionDeclaratorSyntax : IEquatable, IJassSyntaxToken { public JassFunctionDeclaratorSyntax(JassIdentifierNameSyntax identifierName, JassParameterListSyntax parameterList, JassTypeSyntax returnType) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionReferenceExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionReferenceExpressionSyntax.cs index 55104b34..bba28e6c 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionReferenceExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassFunctionReferenceExpressionSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassFunctionReferenceExpressionSyntax : IExpressionSyntax + public class JassFunctionReferenceExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassFunctionReferenceExpressionSyntax(JassIdentifierNameSyntax identifierName) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassGlobalDeclarationListSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassGlobalDeclarationListSyntax.cs index 3474a5ac..85a2363a 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassGlobalDeclarationListSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassGlobalDeclarationListSyntax.cs @@ -10,7 +10,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassGlobalDeclarationListSyntax : ITopLevelDeclarationSyntax + public class JassGlobalDeclarationListSyntax : ITopLevelDeclarationSyntax, IJassSyntaxToken { public JassGlobalDeclarationListSyntax(ImmutableArray globals) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassGlobalDeclarationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassGlobalDeclarationSyntax.cs index 3d432e0b..cc516a88 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassGlobalDeclarationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassGlobalDeclarationSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassGlobalDeclarationSyntax : IGlobalDeclarationSyntax, IGlobalLineSyntax + public class JassGlobalDeclarationSyntax : IGlobalDeclarationSyntax, IGlobalLineSyntax, IJassSyntaxToken { public JassGlobalDeclarationSyntax(IVariableDeclaratorSyntax declarator) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassHexadecimalLiteralExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassHexadecimalLiteralExpressionSyntax.cs index d3a88dab..23d302af 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassHexadecimalLiteralExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassHexadecimalLiteralExpressionSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassHexadecimalLiteralExpressionSyntax : IExpressionSyntax + public class JassHexadecimalLiteralExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassHexadecimalLiteralExpressionSyntax(int value) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassIdentifierNameSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassIdentifierNameSyntax.cs index 8ca91e87..971d803f 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassIdentifierNameSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassIdentifierNameSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassIdentifierNameSyntax : IEquatable + public class JassIdentifierNameSyntax : IEquatable, IJassSyntaxToken { public JassIdentifierNameSyntax(string name) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassIfStatementSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassIfStatementSyntax.cs index 51aa2129..7447f5c2 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassIfStatementSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassIfStatementSyntax.cs @@ -13,7 +13,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassIfStatementSyntax : IStatementSyntax + public class JassIfStatementSyntax : IStatementSyntax, IJassSyntaxToken { public JassIfStatementSyntax(IExpressionSyntax condition, JassStatementListSyntax body, ImmutableArray elseIfClauses, JassElseClauseSyntax? elseClause) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassInvocationExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassInvocationExpressionSyntax.cs index 8b6d1c91..586ba7a8 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassInvocationExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassInvocationExpressionSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassInvocationExpressionSyntax : IExpressionSyntax, IInvocationSyntax + public class JassInvocationExpressionSyntax : IExpressionSyntax, IInvocationSyntax, IJassSyntaxToken { public JassInvocationExpressionSyntax(JassIdentifierNameSyntax identifierName, JassArgumentListSyntax arguments) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassLocalVariableDeclarationStatementSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassLocalVariableDeclarationStatementSyntax.cs index ea62a8d3..10ebf78f 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassLocalVariableDeclarationStatementSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassLocalVariableDeclarationStatementSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassLocalVariableDeclarationStatementSyntax : IStatementSyntax, IStatementLineSyntax + public class JassLocalVariableDeclarationStatementSyntax : IStatementSyntax, IStatementLineSyntax, IJassSyntaxToken { public JassLocalVariableDeclarationStatementSyntax(IVariableDeclaratorSyntax declarator) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassLoopStatementSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassLoopStatementSyntax.cs index b07bcc1e..aa7c4c24 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassLoopStatementSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassLoopStatementSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassLoopStatementSyntax : IStatementSyntax + public class JassLoopStatementSyntax : IStatementSyntax, IJassSyntaxToken { public JassLoopStatementSyntax(JassStatementListSyntax body) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassNativeFunctionDeclarationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassNativeFunctionDeclarationSyntax.cs index fa2ed743..775ab558 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassNativeFunctionDeclarationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassNativeFunctionDeclarationSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassNativeFunctionDeclarationSyntax : ITopLevelDeclarationSyntax, IDeclarationLineSyntax + public class JassNativeFunctionDeclarationSyntax : ITopLevelDeclarationSyntax, IDeclarationLineSyntax, IJassSyntaxToken { public JassNativeFunctionDeclarationSyntax(JassFunctionDeclaratorSyntax functionDeclarator) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassNullLiteralExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassNullLiteralExpressionSyntax.cs index d130e7ee..1273568a 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassNullLiteralExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassNullLiteralExpressionSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassNullLiteralExpressionSyntax : IExpressionSyntax + public class JassNullLiteralExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public static readonly JassNullLiteralExpressionSyntax Value = new JassNullLiteralExpressionSyntax(); diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassOctalLiteralExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassOctalLiteralExpressionSyntax.cs index e2f2630f..d06bc14b 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassOctalLiteralExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassOctalLiteralExpressionSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassOctalLiteralExpressionSyntax : IExpressionSyntax + public class JassOctalLiteralExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassOctalLiteralExpressionSyntax(int value) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassParameterListSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassParameterListSyntax.cs index 212b22d3..92224498 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassParameterListSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassParameterListSyntax.cs @@ -11,7 +11,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassParameterListSyntax : IEquatable + public class JassParameterListSyntax : IEquatable, IJassSyntaxToken { public static readonly JassParameterListSyntax Empty = new JassParameterListSyntax(ImmutableArray.Empty); diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassParameterSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassParameterSyntax.cs index 054949d4..c44f8950 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassParameterSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassParameterSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassParameterSyntax : IEquatable + public class JassParameterSyntax : IEquatable, IJassSyntaxToken { public JassParameterSyntax(JassTypeSyntax type, JassIdentifierNameSyntax identifierName) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassParenthesizedExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassParenthesizedExpressionSyntax.cs index 3ea77c4b..80e7e719 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassParenthesizedExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassParenthesizedExpressionSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassParenthesizedExpressionSyntax : IExpressionSyntax + public class JassParenthesizedExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassParenthesizedExpressionSyntax(IExpressionSyntax expression) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassRealLiteralExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassRealLiteralExpressionSyntax.cs index a16a06b0..2201fcaa 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassRealLiteralExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassRealLiteralExpressionSyntax.cs @@ -10,7 +10,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassRealLiteralExpressionSyntax : IExpressionSyntax + public class JassRealLiteralExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { #if true public JassRealLiteralExpressionSyntax(string intPart, string fracPart) diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassReturnStatementSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassReturnStatementSyntax.cs index 4756f94f..2fc47f9a 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassReturnStatementSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassReturnStatementSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassReturnStatementSyntax : IStatementSyntax, IStatementLineSyntax + public class JassReturnStatementSyntax : IStatementSyntax, IStatementLineSyntax, IJassSyntaxToken { public static readonly JassReturnStatementSyntax Empty = new JassReturnStatementSyntax(null); diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassSetStatementSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassSetStatementSyntax.cs index 006dd56a..900e1e25 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassSetStatementSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassSetStatementSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassSetStatementSyntax : IStatementSyntax, IStatementLineSyntax + public class JassSetStatementSyntax : IStatementSyntax, IStatementLineSyntax, IJassSyntaxToken { public JassSetStatementSyntax(JassIdentifierNameSyntax identifierName, IExpressionSyntax? indexer, JassEqualsValueClauseSyntax value) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassStatementListSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassStatementListSyntax.cs index 4b73caac..6e24bb28 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassStatementListSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassStatementListSyntax.cs @@ -11,7 +11,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassStatementListSyntax : IEquatable + public class JassStatementListSyntax : IEquatable, IJassSyntaxToken { public JassStatementListSyntax(ImmutableArray statements) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassStringLiteralExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassStringLiteralExpressionSyntax.cs index 5fdf9433..8b071149 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassStringLiteralExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassStringLiteralExpressionSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassStringLiteralExpressionSyntax : IExpressionSyntax + public class JassStringLiteralExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassStringLiteralExpressionSyntax(string value) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassTypeDeclarationSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassTypeDeclarationSyntax.cs index 1bbc1481..f6d6b894 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassTypeDeclarationSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassTypeDeclarationSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassTypeDeclarationSyntax : ITopLevelDeclarationSyntax, IDeclarationLineSyntax + public class JassTypeDeclarationSyntax : ITopLevelDeclarationSyntax, IDeclarationLineSyntax, IJassSyntaxToken { public JassTypeDeclarationSyntax(JassIdentifierNameSyntax identifierName, JassTypeSyntax baseType) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassTypeSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassTypeSyntax.cs index 049c7dca..c8025c4a 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassTypeSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassTypeSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassTypeSyntax : IEquatable + public class JassTypeSyntax : IEquatable, IJassSyntaxToken { public static readonly JassTypeSyntax Boolean = new JassTypeSyntax(new JassIdentifierNameSyntax(JassKeyword.Boolean)); public static readonly JassTypeSyntax Code = new JassTypeSyntax(new JassIdentifierNameSyntax(JassKeyword.Code)); diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassUnaryExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassUnaryExpressionSyntax.cs index 0d60e508..fc1ef4f6 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassUnaryExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassUnaryExpressionSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassUnaryExpressionSyntax : IExpressionSyntax + public class JassUnaryExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassUnaryExpressionSyntax(UnaryOperatorType @operator, IExpressionSyntax expression) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassVariableDeclaratorSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassVariableDeclaratorSyntax.cs index 6d2e461d..1465b541 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassVariableDeclaratorSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassVariableDeclaratorSyntax.cs @@ -9,7 +9,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassVariableDeclaratorSyntax : IVariableDeclaratorSyntax + public class JassVariableDeclaratorSyntax : IVariableDeclaratorSyntax, IJassSyntaxToken { public JassVariableDeclaratorSyntax(JassTypeSyntax type, JassIdentifierNameSyntax identifierName, JassEqualsValueClauseSyntax? value) { diff --git a/src/War3Net.CodeAnalysis.Jass/Syntax/JassVariableReferenceExpressionSyntax.cs b/src/War3Net.CodeAnalysis.Jass/Syntax/JassVariableReferenceExpressionSyntax.cs index a95e523c..10af8d3c 100644 --- a/src/War3Net.CodeAnalysis.Jass/Syntax/JassVariableReferenceExpressionSyntax.cs +++ b/src/War3Net.CodeAnalysis.Jass/Syntax/JassVariableReferenceExpressionSyntax.cs @@ -7,7 +7,7 @@ namespace War3Net.CodeAnalysis.Jass.Syntax { - public class JassVariableReferenceExpressionSyntax : IExpressionSyntax + public class JassVariableReferenceExpressionSyntax : IExpressionSyntax, IJassSyntaxToken { public JassVariableReferenceExpressionSyntax(JassIdentifierNameSyntax identifierName) { diff --git a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Audio/MapSoundsDecompilerTests.cs b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Audio/MapSoundsDecompilerTests.cs index 13c1d06e..45c195cd 100644 --- a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Audio/MapSoundsDecompilerTests.cs +++ b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Audio/MapSoundsDecompilerTests.cs @@ -14,6 +14,7 @@ using War3Net.Build; using War3Net.Build.Audio; using War3Net.Build.Info; +using War3Net.CodeAnalysis.Jass; using War3Net.TestTools.UnitTesting; namespace War3Net.CodeAnalysis.Decompilers.Tests.Audio @@ -23,9 +24,10 @@ public class MapSoundsDecompilerTests { [DataTestMethod] [DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)] - public void TestDecompileMapSounds(Map map) + public void TestDecompileMapSounds(Map testMap) { - Assert.IsTrue(new JassScriptDecompiler(map).TryDecompileMapSounds(map.Sounds.FormatVersion, out var decompiledMapSounds), "Failed to decompile map sounds."); + var map = new JassScriptDecompiler(JassSyntaxFactory.ParseCompilationUnit(testMap.Script), new DecompileOptions() { mapSoundsFormatVersion = testMap.Sounds.FormatVersion }, testMap.Info).DecompileObjectManagerData(); + var decompiledMapSounds = map.Sounds; Assert.AreEqual(map.Sounds.Sounds.Count, decompiledMapSounds.Sounds.Count); for (var i = 0; i < decompiledMapSounds.Sounds.Count; i++) diff --git a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Environment/MapCamerasDecompilerTests.cs b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Environment/MapCamerasDecompilerTests.cs index df16dd50..12b0f28d 100644 --- a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Environment/MapCamerasDecompilerTests.cs +++ b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Environment/MapCamerasDecompilerTests.cs @@ -13,6 +13,7 @@ using War3Net.Build; using War3Net.Build.Info; +using War3Net.CodeAnalysis.Jass; using War3Net.TestTools.UnitTesting; namespace War3Net.CodeAnalysis.Decompilers.Tests.Environment @@ -22,9 +23,10 @@ public class MapCamerasDecompilerTests { [DataTestMethod] [DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)] - public void TestDecompileMapCameras(Map map) + public void TestDecompileMapCameras(Map testMap) { - Assert.IsTrue(new JassScriptDecompiler(map).TryDecompileMapCameras(map.Cameras.FormatVersion, map.Cameras.UseNewFormat, out var decompiledMapCameras), "Failed to decompile map cameras."); + var map = new JassScriptDecompiler(JassSyntaxFactory.ParseCompilationUnit(testMap.Script), new DecompileOptions() { mapCamerasFormatVersion = testMap.Cameras.FormatVersion, mapCamerasUseNewFormat = testMap.Cameras.UseNewFormat }, testMap.Info).DecompileObjectManagerData(); + var decompiledMapCameras = map.Cameras; Assert.AreEqual(map.Cameras.Cameras.Count, decompiledMapCameras.Cameras.Count); for (var i = 0; i < decompiledMapCameras.Cameras.Count; i++) diff --git a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Environment/MapRegionsDecompilerTests.cs b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Environment/MapRegionsDecompilerTests.cs index c0015fc0..79ff6027 100644 --- a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Environment/MapRegionsDecompilerTests.cs +++ b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Environment/MapRegionsDecompilerTests.cs @@ -13,6 +13,7 @@ using War3Net.Build; using War3Net.Build.Info; +using War3Net.CodeAnalysis.Jass; using War3Net.TestTools.UnitTesting; namespace War3Net.CodeAnalysis.Decompilers.Tests.Environment @@ -22,9 +23,10 @@ public class MapRegionsDecompilerTests { [DataTestMethod] [DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)] - public void TestDecompileMapRegions(Map map) + public void TestDecompileMapRegions(Map testMap) { - Assert.IsTrue(new JassScriptDecompiler(map).TryDecompileMapRegions(map.Regions.FormatVersion, out var decompiledMapRegions), "Failed to decompile map regions."); + var map = new JassScriptDecompiler(JassSyntaxFactory.ParseCompilationUnit(testMap.Script), new DecompileOptions() { mapRegionsFormatVersion = testMap.Regions.FormatVersion }, testMap.Info).DecompileObjectManagerData(); + var decompiledMapRegions = map.Regions; Assert.AreEqual(map.Regions.Regions.Count, decompiledMapRegions.Regions.Count); for (var i = 0; i < decompiledMapRegions.Regions.Count; i++) diff --git a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Script/MapTriggersDecompilerTests.cs b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Script/MapTriggersDecompilerTests.cs index bc725845..7beeac78 100644 --- a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Script/MapTriggersDecompilerTests.cs +++ b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Script/MapTriggersDecompilerTests.cs @@ -14,6 +14,7 @@ using War3Net.Build; using War3Net.Build.Info; using War3Net.Build.Script; +using War3Net.CodeAnalysis.Jass; using War3Net.TestTools.UnitTesting; namespace War3Net.CodeAnalysis.Decompilers.Tests.Script @@ -25,7 +26,8 @@ public class MapTriggersDecompilerTests [DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)] public void TestDecompileMapTriggers(Map map) { - Assert.IsTrue(new JassScriptDecompiler(map).TryDecompileMapTriggers(map.Triggers.FormatVersion, map.Triggers.SubVersion, out var decompiledMapTriggers), "Failed to decompile map triggers."); + var decompiler = new JassScriptDecompiler(JassSyntaxFactory.ParseCompilationUnit(map.Script), new DecompileOptions(), map.Info); + Assert.IsTrue(decompiler.TryDecompileMapTriggers(map.Triggers.FormatVersion, map.Triggers.SubVersion, out var decompiledMapTriggers), "Failed to decompile map triggers."); Assert.AreEqual(map.Triggers.Variables.Count, decompiledMapTriggers.Variables.Count); for (var i = 0; i < decompiledMapTriggers.Variables.Count; i++) diff --git a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Widget/MapUnitsDecompilerTests.cs b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Widget/MapUnitsDecompilerTests.cs index 82c4359e..4b805ace 100644 --- a/tests/War3Net.CodeAnalysis.Decompilers.Tests/Widget/MapUnitsDecompilerTests.cs +++ b/tests/War3Net.CodeAnalysis.Decompilers.Tests/Widget/MapUnitsDecompilerTests.cs @@ -14,6 +14,7 @@ using War3Net.Build; using War3Net.Build.Extensions; using War3Net.Build.Info; +using War3Net.CodeAnalysis.Jass; using War3Net.Common.Extensions; using War3Net.TestTools.UnitTesting; @@ -24,9 +25,10 @@ public class MapUnitsDecompilerTests { [DataTestMethod] [DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)] - public void TestDecompileMapUnits(Map map) + public void TestDecompileMapUnits(Map testMap) { - Assert.IsTrue(new JassScriptDecompiler(map).TryDecompileMapUnits(map.Units.FormatVersion, map.Units.SubVersion, map.Units.UseNewFormat, out var decompiledMapUnits), "Failed to decompile map units."); + var map = new JassScriptDecompiler(JassSyntaxFactory.ParseCompilationUnit(testMap.Script), new DecompileOptions() { mapWidgetsFormatVersion = testMap.Units.FormatVersion, mapWidgetsSubVersion = testMap.Units.SubVersion, mapWidgetsUseNewFormat = testMap.Units.UseNewFormat }, testMap.Info).DecompileObjectManagerData(); + var decompiledMapUnits = map.Units; var expectedMapUnits = map.Units.Units #if true