diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cf52a50 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +tab_width = 4 +trim_trailing_whitespace = true +insert_final_newline = true +charset=utf-8 + +[*.{cs,vb}] +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false diff --git a/ServerCodeExciser.sln b/ServerCodeExciser.sln index 1e0d45e..be31117 100644 --- a/ServerCodeExciser.sln +++ b/ServerCodeExciser.sln @@ -3,6 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32526.322 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FA80E53A-9EEF-4F05-BEE3-EC8393F1975B}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerCodeExciser", "ServerCodeExciser\ServerCodeExciser.csproj", "{6E4116C9-9BEB-4FBA-B035-D91555E2C981}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerCodeExciserTest", "ServerCodeExciserTest\ServerCodeExciserTest.csproj", "{6E4116C9-9BEB-4FBA-B035-D91555E2C982}" diff --git a/ServerCodeExciser/ServerCodeExciser.cs b/ServerCodeExciser/ServerCodeExciser.cs index d566762..baac2f8 100644 --- a/ServerCodeExciser/ServerCodeExciser.cs +++ b/ServerCodeExciser/ServerCodeExciser.cs @@ -1,12 +1,12 @@ -using ServerCodeExcisionCommon; -using Spectre.Console; -using Spectre.Console.Cli; using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; +using ServerCodeExcisionCommon; +using Spectre.Console; +using Spectre.Console.Cli; using UnrealAngelscriptServerCodeExcision; namespace ServerCodeExciser @@ -69,7 +69,7 @@ public sealed class Settings : CommandSettings class RootPaths { [JsonPropertyName("AngelscriptScriptRoots")] - public string[] AngelscriptScriptRoots { get;set;} = Array.Empty(); + public string[] AngelscriptScriptRoots { get; set; } = Array.Empty(); } public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) @@ -91,7 +91,7 @@ public override int Execute([NotNull] CommandContext context, [NotNull] Settings var desc = File.ReadAllText(settings.InputPath); var paths = JsonSerializer.Deserialize(desc); if (paths != null) - { + { parameters.InputPaths.UnionWith(paths.AngelscriptScriptRoots); } else diff --git a/ServerCodeExciser/ServerCodeExcisionProcessor.cs b/ServerCodeExciser/ServerCodeExcisionProcessor.cs index 9236ea4..969f2f5 100644 --- a/ServerCodeExciser/ServerCodeExcisionProcessor.cs +++ b/ServerCodeExciser/ServerCodeExcisionProcessor.cs @@ -1,5 +1,3 @@ -using Antlr4.Runtime; -using ServerCodeExcisionCommon; using System; using System.Collections.Generic; using System.Diagnostics; @@ -7,6 +5,8 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Antlr4.Runtime; +using ServerCodeExcisionCommon; namespace ServerCodeExciser { diff --git a/ServerCodeExciserTest/ExcisionIntegrationTests.cs b/ServerCodeExciserTest/ExcisionIntegrationTests.cs index 09102e4..46cfabc 100644 --- a/ServerCodeExciserTest/ExcisionIntegrationTests.cs +++ b/ServerCodeExciserTest/ExcisionIntegrationTests.cs @@ -1,8 +1,8 @@ +using System; +using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; using ServerCodeExciser; using ServerCodeExcisionCommon; -using System; -using System.IO; using UnrealAngelscriptServerCodeExcision; [TestClass] @@ -110,5 +110,5 @@ private static EExciserReturnValues RunExciserIntegrationTests(string fileExtens } return returnCode; - } + } } diff --git a/ServerCodeExcisionCommon/IServerCodeExcisionLanguage.cs b/ServerCodeExcisionCommon/IServerCodeExcisionLanguage.cs index c892abd..9983168 100644 --- a/ServerCodeExcisionCommon/IServerCodeExcisionLanguage.cs +++ b/ServerCodeExcisionCommon/IServerCodeExcisionLanguage.cs @@ -2,30 +2,30 @@ namespace ServerCodeExcisionCommon { - public interface IServerCodeExcisionLanguage - { - List ServerOnlySymbolRegexes { get; } + public interface IServerCodeExcisionLanguage + { + List ServerOnlySymbolRegexes { get; } - List ServerOnlySymbols { get; } + List ServerOnlySymbols { get; } - string ServerPrecompilerSymbol { get; } + string ServerPrecompilerSymbol { get; } - string ServerScopeStartString { get; } + string ServerScopeStartString { get; } - string ServerScopeEndString { get; } + string ServerScopeEndString { get; } - T CreateLexer(Antlr4.Runtime.AntlrInputStream inputStream) - where T : Antlr4.Runtime.Lexer; + T CreateLexer(Antlr4.Runtime.AntlrInputStream inputStream) + where T : Antlr4.Runtime.Lexer; - T CreateParser(Antlr4.Runtime.CommonTokenStream tokenStream) - where T : Antlr4.Runtime.Parser; + T CreateParser(Antlr4.Runtime.CommonTokenStream tokenStream) + where T : Antlr4.Runtime.Parser; - IServerCodeVisitor CreateSimpleVisitor(string code); + IServerCodeVisitor CreateSimpleVisitor(string code); - IServerCodeVisitor CreateFunctionVisitor(string code); + IServerCodeVisitor CreateFunctionVisitor(string code); - IServerCodeVisitor CreateSymbolVisitor(string code); + IServerCodeVisitor CreateSymbolVisitor(string code); - bool AnyServerOnlySymbolsInScript(string script); - } -} \ No newline at end of file + bool AnyServerOnlySymbolsInScript(string script); + } +} diff --git a/ServerCodeExcisionCommon/IServerCodeVisitor.cs b/ServerCodeExcisionCommon/IServerCodeVisitor.cs index f9953b7..741372a 100644 --- a/ServerCodeExcisionCommon/IServerCodeVisitor.cs +++ b/ServerCodeExcisionCommon/IServerCodeVisitor.cs @@ -2,26 +2,26 @@ namespace ServerCodeExcisionCommon { - public struct ServerOnlyScopeData - { - public int StartIndex; - public int StopIndex; - public string Opt_ElseContent; + public struct ServerOnlyScopeData + { + public int StartIndex; + public int StopIndex; + public string Opt_ElseContent; - public ServerOnlyScopeData(int startIndex, int stopIndex) - { - StartIndex = startIndex; - StopIndex = stopIndex; - Opt_ElseContent = ""; - } - } + public ServerOnlyScopeData(int startIndex, int stopIndex) + { + StartIndex = startIndex; + StopIndex = stopIndex; + Opt_ElseContent = ""; + } + } - public interface IServerCodeVisitor - { - List DetectedServerOnlyScopes { get; } - Dictionary> ClassStartIdxDummyReferenceData { get; } - int TotalNumberOfFunctionCharactersVisited { get; } + public interface IServerCodeVisitor + { + List DetectedServerOnlyScopes { get; } + Dictionary> ClassStartIdxDummyReferenceData { get; } + int TotalNumberOfFunctionCharactersVisited { get; } - void VisitContext(Antlr4.Runtime.Tree.IParseTree context); - } -} \ No newline at end of file + void VisitContext(Antlr4.Runtime.Tree.IParseTree context); + } +} diff --git a/ServerCodeExcisionCommon/ServerCodeExcisionUtils.cs b/ServerCodeExcisionCommon/ServerCodeExcisionUtils.cs index 58aac52..a096658 100644 --- a/ServerCodeExcisionCommon/ServerCodeExcisionUtils.cs +++ b/ServerCodeExcisionCommon/ServerCodeExcisionUtils.cs @@ -1,206 +1,205 @@ using System; using System.IO; -using System.Linq; using Antlr4.Runtime; namespace ServerCodeExcisionCommon { - public enum EExcisionLanguage - { - Unknown, - Angelscript - } - - public enum EExcisionMode - { - Full, - AllFunctions, - ServerOnlyScopes - } - - public enum EExpressionType - { - NotServerOnly, - ElseIsServerOnly, - EverythingAfterBranchIsServerOnly, - ServerOnly - } - - public enum EReturnType - { - NoReturn, - ReplacedReturn, - ReferenceReturn, - RootScopeReferenceReturn - } - - public struct ExcisionStats - { - // How many characters were actually removed. - public int CharactersExcised; - - // Could be file or function, depending on stats mode. - public int TotalNrCharacters; - } - - public struct StatementRun - { - public int StartLine; - public int StartColumn; - public int StopLine; - public int StopColumn; - - public StatementRun(int initialVal = -1) - { - StartLine = initialVal; - StartColumn = initialVal; - StopLine = initialVal; - StopColumn = initialVal; - } - } - - public struct ReturnData - { - public EReturnType ReturnType; - public string DefaultReturnString; - public StatementRun ReturnStatementRun; - - public ReturnData(string defaultReturnString = "") - { - ReturnType = EReturnType.NoReturn; - DefaultReturnString = defaultReturnString; - ReturnStatementRun = new StatementRun(); - } - } - - public enum EExciserReturnValues - { - Success, - BadInputPath, - InputPathEmpty, - BadOutputPath, - BadArgument, - UnknownExcisionLanguage, - NothingExcised, - InternalExcisionError, - RequiredExcisionRatioNotReached, - RequiresExcision - } - - public class ExcisionException : Exception - { - public ExcisionException(string excisionError, Exception innerException) - : base(excisionError, innerException) - { - } - } - - public class ExcisionParserErrorListener : Antlr4.Runtime.IAntlrErrorListener - { - public void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) - { - throw new ExcisionException(string.Format("({0}:{1} - {2})", line, charPositionInLine, msg), e); - } - } - - public class ExcisionLexerErrorListener : Antlr4.Runtime.IAntlrErrorListener - { - public void SyntaxError(TextWriter output, IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) - { - throw new ExcisionException(string.Format("({0}:{1} - {2})", line, charPositionInLine, msg), e); - } - } - - public static class ExcisionUtils - { - private static char[] NewLineChars = { '\r', '\n' }; - private static char[] SkippableScopeChars = { '\t', '\r', '\n' }; - - public static int FindScriptIndexForCodePoint(string script, int line, int column) - { - int cursor = 0; - int linesTraversed = 1; - while(cursor != -1) - { - if (linesTraversed == line) - { - break; - } - - int searchIdx = cursor; - int windows = script.IndexOf("\r\n", searchIdx); - int other = script.IndexOfAny(NewLineChars, searchIdx); - - if (windows <= other) - { - cursor = windows + 2; - } - else - { - cursor = other + 1; - } - - ++linesTraversed; - } - - return (linesTraversed == line) ? (cursor + column) : -1; - } - - public static Type FindFirstDirectChildOfType(Antlr4.Runtime.Tree.IParseTree currentContext) - where Type : class - { - if (currentContext == null) - { - return null; - } - - for(int childIdx = 0; childIdx < currentContext.ChildCount; childIdx++) - { - var child = currentContext.GetChild(childIdx) as Type; - if (child != null) - { - return child; - } - } - - return null; - } - - public static Type FindParentContextOfType(Antlr4.Runtime.Tree.IParseTree currentContext) - where Type : class - { - if (currentContext == null) - { - return null; - } - - Type candidate = currentContext as Type; - if (candidate != null) - { - return candidate; - } - - return FindParentContextOfType(currentContext.Parent); - } - - public static Type FindDirectParentContextOfTypeWithDifferentSourceInterval(Antlr4.Runtime.Tree.IParseTree currentContext, Antlr4.Runtime.Misc.Interval initialSourceInterval) - where Type : class - { - if (currentContext == null) - { - return null; - } - - if (currentContext.SourceInterval.a == initialSourceInterval.a && currentContext.SourceInterval.b == initialSourceInterval.b) - { - // Go further up - return FindDirectParentContextOfTypeWithDifferentSourceInterval(currentContext.Parent, initialSourceInterval); - } - else - { - // We are now at the first ancestor with a different source interval - return currentContext as Type; - } - } - } + public enum EExcisionLanguage + { + Unknown, + Angelscript + } + + public enum EExcisionMode + { + Full, + AllFunctions, + ServerOnlyScopes + } + + public enum EExpressionType + { + NotServerOnly, + ElseIsServerOnly, + EverythingAfterBranchIsServerOnly, + ServerOnly + } + + public enum EReturnType + { + NoReturn, + ReplacedReturn, + ReferenceReturn, + RootScopeReferenceReturn + } + + public struct ExcisionStats + { + // How many characters were actually removed. + public int CharactersExcised; + + // Could be file or function, depending on stats mode. + public int TotalNrCharacters; + } + + public struct StatementRun + { + public int StartLine; + public int StartColumn; + public int StopLine; + public int StopColumn; + + public StatementRun(int initialVal = -1) + { + StartLine = initialVal; + StartColumn = initialVal; + StopLine = initialVal; + StopColumn = initialVal; + } + } + + public struct ReturnData + { + public EReturnType ReturnType; + public string DefaultReturnString; + public StatementRun ReturnStatementRun; + + public ReturnData(string defaultReturnString = "") + { + ReturnType = EReturnType.NoReturn; + DefaultReturnString = defaultReturnString; + ReturnStatementRun = new StatementRun(); + } + } + + public enum EExciserReturnValues + { + Success, + BadInputPath, + InputPathEmpty, + BadOutputPath, + BadArgument, + UnknownExcisionLanguage, + NothingExcised, + InternalExcisionError, + RequiredExcisionRatioNotReached, + RequiresExcision + } + + public class ExcisionException : Exception + { + public ExcisionException(string excisionError, Exception innerException) + : base(excisionError, innerException) + { + } + } + + public class ExcisionParserErrorListener : Antlr4.Runtime.IAntlrErrorListener + { + public void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) + { + throw new ExcisionException(string.Format("({0}:{1} - {2})", line, charPositionInLine, msg), e); + } + } + + public class ExcisionLexerErrorListener : Antlr4.Runtime.IAntlrErrorListener + { + public void SyntaxError(TextWriter output, IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) + { + throw new ExcisionException(string.Format("({0}:{1} - {2})", line, charPositionInLine, msg), e); + } + } + + public static class ExcisionUtils + { + private static char[] NewLineChars = { '\r', '\n' }; + private static char[] SkippableScopeChars = { '\t', '\r', '\n' }; + + public static int FindScriptIndexForCodePoint(string script, int line, int column) + { + int cursor = 0; + int linesTraversed = 1; + while (cursor != -1) + { + if (linesTraversed == line) + { + break; + } + + int searchIdx = cursor; + int windows = script.IndexOf("\r\n", searchIdx); + int other = script.IndexOfAny(NewLineChars, searchIdx); + + if (windows <= other) + { + cursor = windows + 2; + } + else + { + cursor = other + 1; + } + + ++linesTraversed; + } + + return (linesTraversed == line) ? (cursor + column) : -1; + } + + public static Type FindFirstDirectChildOfType(Antlr4.Runtime.Tree.IParseTree currentContext) + where Type : class + { + if (currentContext == null) + { + return null; + } + + for (int childIdx = 0; childIdx < currentContext.ChildCount; childIdx++) + { + var child = currentContext.GetChild(childIdx) as Type; + if (child != null) + { + return child; + } + } + + return null; + } + + public static Type FindParentContextOfType(Antlr4.Runtime.Tree.IParseTree currentContext) + where Type : class + { + if (currentContext == null) + { + return null; + } + + Type candidate = currentContext as Type; + if (candidate != null) + { + return candidate; + } + + return FindParentContextOfType(currentContext.Parent); + } + + public static Type FindDirectParentContextOfTypeWithDifferentSourceInterval(Antlr4.Runtime.Tree.IParseTree currentContext, Antlr4.Runtime.Misc.Interval initialSourceInterval) + where Type : class + { + if (currentContext == null) + { + return null; + } + + if (currentContext.SourceInterval.a == initialSourceInterval.a && currentContext.SourceInterval.b == initialSourceInterval.b) + { + // Go further up + return FindDirectParentContextOfTypeWithDifferentSourceInterval(currentContext.Parent, initialSourceInterval); + } + else + { + // We are now at the first ancestor with a different source interval + return currentContext as Type; + } + } + } } diff --git a/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptFunctionVisitor.cs b/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptFunctionVisitor.cs index a1d2b1f..7d2fcc2 100644 --- a/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptFunctionVisitor.cs +++ b/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptFunctionVisitor.cs @@ -1,17 +1,17 @@ namespace UnrealAngelscriptServerCodeExcision { - public class UnrealAngelscriptFunctionVisitor : UnrealAngelscriptSimpleVisitor - { - public UnrealAngelscriptFunctionVisitor(string script) - : base(script) - { - } + public class UnrealAngelscriptFunctionVisitor : UnrealAngelscriptSimpleVisitor + { + public UnrealAngelscriptFunctionVisitor(string script) + : base(script) + { + } - public override UnrealAngelscriptNode VisitFunctionBody(UnrealAngelscriptParser.FunctionBodyContext context) - { - // We want to decorate all function bodies! - DecorateFunctionBody(context); - return base.VisitFunctionBody(context); - } - } + public override UnrealAngelscriptNode VisitFunctionBody(UnrealAngelscriptParser.FunctionBodyContext context) + { + // We want to decorate all function bodies! + DecorateFunctionBody(context); + return base.VisitFunctionBody(context); + } + } } diff --git a/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptServerCodeExcisionLanguage.cs b/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptServerCodeExcisionLanguage.cs index fa80362..7648e8a 100644 --- a/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptServerCodeExcisionLanguage.cs +++ b/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptServerCodeExcisionLanguage.cs @@ -1,71 +1,71 @@ -using ServerCodeExcisionCommon; using System; using System.Collections.Generic; +using ServerCodeExcisionCommon; namespace UnrealAngelscriptServerCodeExcision { - public class UnrealAngelscriptServerCodeExcisionLanguage : IServerCodeExcisionLanguage - { - private List _angelscriptServerOnlySymbolRegexes = new List - { - @"^System::IsServer\(\)$", - @"^[A-z]+\.HasAuthority\(\)$" - }; + public class UnrealAngelscriptServerCodeExcisionLanguage : IServerCodeExcisionLanguage + { + private List _angelscriptServerOnlySymbolRegexes = new List + { + @"^System::IsServer\(\)$", + @"^[A-z]+\.HasAuthority\(\)$" + }; - public List ServerOnlySymbolRegexes { get { return _angelscriptServerOnlySymbolRegexes; } } + public List ServerOnlySymbolRegexes { get { return _angelscriptServerOnlySymbolRegexes; } } - private List _angelscriptServerOnlySymbols = new List - { - "hasauthority()", - "server" - }; + private List _angelscriptServerOnlySymbols = new List + { + "hasauthority()", + "server" + }; - public List ServerOnlySymbols { get { return _angelscriptServerOnlySymbols; } } + public List ServerOnlySymbols { get { return _angelscriptServerOnlySymbols; } } - public string ServerPrecompilerSymbol { get { return "WITH_SERVER"; } } + public string ServerPrecompilerSymbol { get { return "WITH_SERVER"; } } - public string ServerScopeStartString { get { return "#ifdef " + ServerPrecompilerSymbol; } } + public string ServerScopeStartString { get { return "#ifdef " + ServerPrecompilerSymbol; } } - public string ServerScopeEndString { get { return "#endif // " + ServerPrecompilerSymbol; } } + public string ServerScopeEndString { get { return "#endif // " + ServerPrecompilerSymbol; } } - public T CreateLexer(Antlr4.Runtime.AntlrInputStream inputStream) + public T CreateLexer(Antlr4.Runtime.AntlrInputStream inputStream) where T : Antlr4.Runtime.Lexer { - return (T)Activator.CreateInstance(typeof(T), inputStream); - } + return (T)Activator.CreateInstance(typeof(T), inputStream); + } + + public T CreateParser(Antlr4.Runtime.CommonTokenStream tokenStream) + where T : Antlr4.Runtime.Parser + { + return (T)Activator.CreateInstance(typeof(T), tokenStream); + } - public T CreateParser(Antlr4.Runtime.CommonTokenStream tokenStream) - where T : Antlr4.Runtime.Parser - { - return (T)Activator.CreateInstance(typeof(T), tokenStream); - } + public IServerCodeVisitor CreateSimpleVisitor(string code) + { + return new UnrealAngelscriptSimpleVisitor(code); + } - public IServerCodeVisitor CreateSimpleVisitor(string code) - { - return new UnrealAngelscriptSimpleVisitor(code); - } + public IServerCodeVisitor CreateFunctionVisitor(string code) + { + return new UnrealAngelscriptFunctionVisitor(code); + } - public IServerCodeVisitor CreateFunctionVisitor(string code) - { - return new UnrealAngelscriptFunctionVisitor(code); - } + public IServerCodeVisitor CreateSymbolVisitor(string code) + { + return new UnrealAngelscriptSymbolVisitor(code, this); + } - public IServerCodeVisitor CreateSymbolVisitor(string code) - { - return new UnrealAngelscriptSymbolVisitor(code, this); - } - - public bool AnyServerOnlySymbolsInScript(string script) - { - foreach (var serverOnlySymbol in _angelscriptServerOnlySymbols) - { - if (script.ToLower().Contains(serverOnlySymbol)) - { - return true; - } - } + public bool AnyServerOnlySymbolsInScript(string script) + { + foreach (var serverOnlySymbol in _angelscriptServerOnlySymbols) + { + if (script.ToLower().Contains(serverOnlySymbol)) + { + return true; + } + } - return false; - } - } + return false; + } + } } diff --git a/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptSimpleVisitor.cs b/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptSimpleVisitor.cs index 4f96c1b..fb25231 100644 --- a/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptSimpleVisitor.cs +++ b/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptSimpleVisitor.cs @@ -1,313 +1,312 @@ -using Antlr4.Runtime; -using ServerCodeExcisionCommon; using System; using System.Collections.Generic; using System.Text; +using ServerCodeExcisionCommon; namespace UnrealAngelscriptServerCodeExcision { - public class UnrealAngelscriptNode - { - } - - public class UnrealAngelscriptSimpleVisitor : UnrealAngelscriptParserBaseVisitor, IServerCodeVisitor - { - public List DetectedServerOnlyScopes { get; protected set; } - public Dictionary> ClassStartIdxDummyReferenceData { get; protected set; } - public int TotalNumberOfFunctionCharactersVisited { get; protected set; } - - protected string Script; - - private static int Salt = 0; - - public UnrealAngelscriptSimpleVisitor(string script) - { - ClassStartIdxDummyReferenceData = new Dictionary>(); - DetectedServerOnlyScopes = new List(); - - TotalNumberOfFunctionCharactersVisited = 0; - Script = script; - } - - public void VisitContext(Antlr4.Runtime.Tree.IParseTree context) - { - Visit(context); - } - - public override UnrealAngelscriptNode VisitFunctionBody(UnrealAngelscriptParser.FunctionBodyContext context) - { - var functionStartIndex = ExcisionUtils.FindScriptIndexForCodePoint(Script, context.Start.Line, context.Start.Column) + 1; - var functionEndIndex = ExcisionUtils.FindScriptIndexForCodePoint(Script, context.Stop.Line, context.Stop.Column) + 1; - TotalNumberOfFunctionCharactersVisited += Math.Abs(functionEndIndex - functionStartIndex); - - return VisitChildren(context); - } - - protected ReturnData GetDefaultReturnStatementForScope(Antlr4.Runtime.Tree.IParseTree scopeContext) - { - // If the function has a return type, we must provide a valid replacement in the case the original one is compiled out. - var returnData = new ReturnData(); - - // First figure out the function's return type. - var functionDefinition = ExcisionUtils.FindParentContextOfType(scopeContext); - if (functionDefinition != null && functionDefinition.ChildCount > 1) - { - var returnTypeContext = ExcisionUtils.FindFirstDirectChildOfType(functionDefinition); - - // Now figure out if and what we should replace the return with. - returnData.ReturnType = GetDefaultReturnStatementForReturnType(returnTypeContext, out returnData.DefaultReturnString); - - if (returnData.ReturnType != EReturnType.NoReturn) - { - // Okay, we have a return type. We should check for a final return statement, and gather info about it. - var functionBody = functionDefinition.GetChild(functionDefinition.ChildCount - 1) as UnrealAngelscriptParser.CompoundStatementContext; - if (!IsLastStatementInScopeAReturn(scopeContext, ref returnData.ReturnStatementRun) - && functionBody == scopeContext) - { - // It seems our function has a return value, but doesn't end with a return statement. - // This must mean that all the return statements are in branches of the expression, and we should add our return definition at the end. - - returnData.ReturnStatementRun.StartLine = functionBody.Stop.Line; - returnData.ReturnStatementRun.StartColumn = functionBody.Stop.Column; - returnData.ReturnStatementRun.StopLine = functionBody.Stop.Line; - returnData.ReturnStatementRun.StopColumn = functionBody.Stop.Column; - } - } - } - - return returnData; - } - - protected bool IsLastStatementInScopeAReturn(Antlr4.Runtime.Tree.IParseTree scopeContext, ref StatementRun returnStatementRun) - { - if (scopeContext == null) - { - return false; - } - - var jumpContext = scopeContext as UnrealAngelscriptParser.JumpStatementContext; - if (jumpContext != null && jumpContext.GetChild(0).GetText() == "return") - { - returnStatementRun.StartLine = jumpContext.Start.Line; - returnStatementRun.StartColumn = jumpContext.Start.Column; - returnStatementRun.StopLine = jumpContext.Stop.Line; - returnStatementRun.StopColumn = jumpContext.Stop.Column; - return true; - } - - var compoundStatementContext = scopeContext as UnrealAngelscriptParser.CompoundStatementContext; - if (compoundStatementContext != null) - { - return IsLastStatementInScopeAReturn(compoundStatementContext.GetChild(scopeContext.ChildCount - 2), ref returnStatementRun); - } - - if (scopeContext.ChildCount < 1) - { - return false; - } - - var nextChild = scopeContext.GetChild(scopeContext.ChildCount - 1); - if (nextChild is UnrealAngelscriptParser.SelectionStatementContext) - { - // Disallow entering further branches. - return false; - } - - return IsLastStatementInScopeAReturn(nextChild, ref returnStatementRun); - } - - protected EReturnType GetDefaultReturnStatementForReturnType(UnrealAngelscriptParser.DeclSpecifierSeqContext returnTypeContext, out string defaultReturnStatement) - { - defaultReturnStatement = ""; - - if (returnTypeContext == null || returnTypeContext.GetText() == "void") - { - // Void return types means we don't have to do anything. - return EReturnType.NoReturn; - } - - // First, we need to figure out the type text. This is the full type without qualifiers. - string typeString = ""; - bool returnTypeFound = false; - - var asGenericContext = GetFirstChildOfType(returnTypeContext); - var simpleTypeContext = GetFirstChildOfType(returnTypeContext); - if (asGenericContext != null) - { - // It is some type of generic, we should probably just try to construct it. - typeString = asGenericContext.GetText().Replace("const", "const "); - defaultReturnStatement = string.Format("return {0}();", typeString); - returnTypeFound = true; - } - else if (simpleTypeContext != null) - { - // Could this simple type even be a class type..? - var classTypeContext = GetFirstChildOfType(simpleTypeContext, true); - if (classTypeContext != null) - { - if (classTypeContext.GetText().StartsWith("F")) - { - // Struct. Construct in place. - typeString = simpleTypeContext.GetText(); - defaultReturnStatement = string.Format("return {0}();", typeString); - } - else if (classTypeContext.GetText().StartsWith("E")) - { - // Enum. Force cast. - typeString = simpleTypeContext.GetText(); - defaultReturnStatement = string.Format("return {0}(0);", typeString); - } - else - { - // Class type, return null - defaultReturnStatement = "return nullptr;"; - } - } - else - { - // No, it was just a simple type, we'll use default values! - switch (simpleTypeContext.GetText()) - { - case "bool": - defaultReturnStatement = "return false;"; - break; - case "float": - case "float32": - defaultReturnStatement = "return 0.0f;"; - break; - case "double": - case "float64": - defaultReturnStatement = "return 0.0;"; - break; - default: - // All kinds of different ints :) - defaultReturnStatement = "return 0;"; - break; - } - } - - returnTypeFound = true; - } - - // If the return type is some type of reference, we cannot replace it with a stack variable. - // This means we need to create a new dummy variable, and we also need to return a reference to it instead of something else. - if (returnTypeFound && returnTypeContext.ChildCount > 0 && - (returnTypeContext.GetChild(returnTypeContext.ChildCount - 1).GetText() == "&")) - { - string dummyReferenceVariableName = typeString.Replace("::", "").Replace(",", "").Replace("<", "").Replace(">", "") + "ReferenceDummy" + Salt++; - string dummyReferenceVariable = string.Format("{0} {1};", typeString, dummyReferenceVariableName); - - // For reference variables, we must register dummy variables to make sure something with longer lifetime scope can be returned. - // I'd really like to just use a static variable here, but since AS doesn't support that, things get messy. - int classStartIdx = -1; - var classSpecifierContext = ExcisionUtils.FindParentContextOfType(returnTypeContext); - if (classSpecifierContext != null && classSpecifierContext.ChildCount > 2) - { - var classMemberSpec = classSpecifierContext.GetChild(2) as UnrealAngelscriptParser.MemberSpecificationContext; - if (classMemberSpec != null) - { - classStartIdx = ExcisionUtils.FindScriptIndexForCodePoint(Script, classMemberSpec.Start.Line, 0); - } - } - - if (classStartIdx < 0) - { - // Reference return dected in non-class. Just leave this alone for now. - return EReturnType.RootScopeReferenceReturn; - } - - HashSet dummyVarSet = null; - if (ClassStartIdxDummyReferenceData.ContainsKey(classStartIdx)) - { - dummyVarSet = ClassStartIdxDummyReferenceData[classStartIdx]; - } - else - { - dummyVarSet = new HashSet(); - } - - dummyVarSet.Add(dummyReferenceVariable); - ClassStartIdxDummyReferenceData[classStartIdx] = dummyVarSet; - - defaultReturnStatement = string.Format("return {0};", dummyReferenceVariableName); - return EReturnType.ReferenceReturn; - } - - return returnTypeFound ? EReturnType.ReplacedReturn : EReturnType.NoReturn; - } - - protected Antlr4.Runtime.Tree.IParseTree GetFirstChildOfType(Antlr4.Runtime.Tree.IParseTree specifierSequence, bool searchReverse = false) - where T : class - { - if ((specifierSequence as T) != null) - { - return specifierSequence; - } - - if (searchReverse) - { - for (int childIdx = specifierSequence.ChildCount - 1; childIdx >= 0; childIdx--) - { - var childResult = GetFirstChildOfType(specifierSequence.GetChild(childIdx), searchReverse); - if (childResult != null) - { - return childResult; - } - } - } - else - { - for (int childIdx = 0; childIdx < specifierSequence.ChildCount; childIdx++) - { - var childResult = GetFirstChildOfType(specifierSequence.GetChild(childIdx), searchReverse); - if (childResult != null) - { - return childResult; - } - } - } - - return null; - } - - protected string BuildIndentationForColumnCount(int nrCols) - { - var indentation = new StringBuilder(""); - for (int indentIdx = 0; indentIdx < nrCols; indentIdx++) - { - indentation.Append("\t"); - } - - return indentation.ToString(); - } - - protected void DecorateFunctionBody(UnrealAngelscriptParser.FunctionBodyContext context) - { - if (context == null) - { - return; - } - - // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. - var returnData = GetDefaultReturnStatementForScope(context); - - ServerOnlyScopeData newData = new ServerOnlyScopeData( - ExcisionUtils.FindScriptIndexForCodePoint(Script, context.Start.Line, context.Start.Column) + 1, - ExcisionUtils.FindScriptIndexForCodePoint(Script, context.Stop.Line, 0)); - - if (returnData.ReturnType != EReturnType.NoReturn) - { - // We want to be one step inside the scope! - string scopeIndentation = BuildIndentationForColumnCount(context.Start.Column + 1); - newData.Opt_ElseContent = string.Format("#else\r\n{0}{1}\r\n", scopeIndentation, returnData.DefaultReturnString); - } - - // Don't detect scopes that are just one line. - if (context.Start.Line != context.Stop.Line - && returnData.ReturnType != EReturnType.RootScopeReferenceReturn) - { - DetectedServerOnlyScopes.Add(newData); - } - } - } + public class UnrealAngelscriptNode + { + } + + public class UnrealAngelscriptSimpleVisitor : UnrealAngelscriptParserBaseVisitor, IServerCodeVisitor + { + public List DetectedServerOnlyScopes { get; protected set; } + public Dictionary> ClassStartIdxDummyReferenceData { get; protected set; } + public int TotalNumberOfFunctionCharactersVisited { get; protected set; } + + protected string Script; + + private static int Salt = 0; + + public UnrealAngelscriptSimpleVisitor(string script) + { + ClassStartIdxDummyReferenceData = new Dictionary>(); + DetectedServerOnlyScopes = new List(); + + TotalNumberOfFunctionCharactersVisited = 0; + Script = script; + } + + public void VisitContext(Antlr4.Runtime.Tree.IParseTree context) + { + Visit(context); + } + + public override UnrealAngelscriptNode VisitFunctionBody(UnrealAngelscriptParser.FunctionBodyContext context) + { + var functionStartIndex = ExcisionUtils.FindScriptIndexForCodePoint(Script, context.Start.Line, context.Start.Column) + 1; + var functionEndIndex = ExcisionUtils.FindScriptIndexForCodePoint(Script, context.Stop.Line, context.Stop.Column) + 1; + TotalNumberOfFunctionCharactersVisited += Math.Abs(functionEndIndex - functionStartIndex); + + return VisitChildren(context); + } + + protected ReturnData GetDefaultReturnStatementForScope(Antlr4.Runtime.Tree.IParseTree scopeContext) + { + // If the function has a return type, we must provide a valid replacement in the case the original one is compiled out. + var returnData = new ReturnData(); + + // First figure out the function's return type. + var functionDefinition = ExcisionUtils.FindParentContextOfType(scopeContext); + if (functionDefinition != null && functionDefinition.ChildCount > 1) + { + var returnTypeContext = ExcisionUtils.FindFirstDirectChildOfType(functionDefinition); + + // Now figure out if and what we should replace the return with. + returnData.ReturnType = GetDefaultReturnStatementForReturnType(returnTypeContext, out returnData.DefaultReturnString); + + if (returnData.ReturnType != EReturnType.NoReturn) + { + // Okay, we have a return type. We should check for a final return statement, and gather info about it. + var functionBody = functionDefinition.GetChild(functionDefinition.ChildCount - 1) as UnrealAngelscriptParser.CompoundStatementContext; + if (!IsLastStatementInScopeAReturn(scopeContext, ref returnData.ReturnStatementRun) + && functionBody == scopeContext) + { + // It seems our function has a return value, but doesn't end with a return statement. + // This must mean that all the return statements are in branches of the expression, and we should add our return definition at the end. + + returnData.ReturnStatementRun.StartLine = functionBody.Stop.Line; + returnData.ReturnStatementRun.StartColumn = functionBody.Stop.Column; + returnData.ReturnStatementRun.StopLine = functionBody.Stop.Line; + returnData.ReturnStatementRun.StopColumn = functionBody.Stop.Column; + } + } + } + + return returnData; + } + + protected bool IsLastStatementInScopeAReturn(Antlr4.Runtime.Tree.IParseTree scopeContext, ref StatementRun returnStatementRun) + { + if (scopeContext == null) + { + return false; + } + + var jumpContext = scopeContext as UnrealAngelscriptParser.JumpStatementContext; + if (jumpContext != null && jumpContext.GetChild(0).GetText() == "return") + { + returnStatementRun.StartLine = jumpContext.Start.Line; + returnStatementRun.StartColumn = jumpContext.Start.Column; + returnStatementRun.StopLine = jumpContext.Stop.Line; + returnStatementRun.StopColumn = jumpContext.Stop.Column; + return true; + } + + var compoundStatementContext = scopeContext as UnrealAngelscriptParser.CompoundStatementContext; + if (compoundStatementContext != null) + { + return IsLastStatementInScopeAReturn(compoundStatementContext.GetChild(scopeContext.ChildCount - 2), ref returnStatementRun); + } + + if (scopeContext.ChildCount < 1) + { + return false; + } + + var nextChild = scopeContext.GetChild(scopeContext.ChildCount - 1); + if (nextChild is UnrealAngelscriptParser.SelectionStatementContext) + { + // Disallow entering further branches. + return false; + } + + return IsLastStatementInScopeAReturn(nextChild, ref returnStatementRun); + } + + protected EReturnType GetDefaultReturnStatementForReturnType(UnrealAngelscriptParser.DeclSpecifierSeqContext returnTypeContext, out string defaultReturnStatement) + { + defaultReturnStatement = ""; + + if (returnTypeContext == null || returnTypeContext.GetText() == "void") + { + // Void return types means we don't have to do anything. + return EReturnType.NoReturn; + } + + // First, we need to figure out the type text. This is the full type without qualifiers. + string typeString = ""; + bool returnTypeFound = false; + + var asGenericContext = GetFirstChildOfType(returnTypeContext); + var simpleTypeContext = GetFirstChildOfType(returnTypeContext); + if (asGenericContext != null) + { + // It is some type of generic, we should probably just try to construct it. + typeString = asGenericContext.GetText().Replace("const", "const "); + defaultReturnStatement = string.Format("return {0}();", typeString); + returnTypeFound = true; + } + else if (simpleTypeContext != null) + { + // Could this simple type even be a class type..? + var classTypeContext = GetFirstChildOfType(simpleTypeContext, true); + if (classTypeContext != null) + { + if (classTypeContext.GetText().StartsWith("F")) + { + // Struct. Construct in place. + typeString = simpleTypeContext.GetText(); + defaultReturnStatement = string.Format("return {0}();", typeString); + } + else if (classTypeContext.GetText().StartsWith("E")) + { + // Enum. Force cast. + typeString = simpleTypeContext.GetText(); + defaultReturnStatement = string.Format("return {0}(0);", typeString); + } + else + { + // Class type, return null + defaultReturnStatement = "return nullptr;"; + } + } + else + { + // No, it was just a simple type, we'll use default values! + switch (simpleTypeContext.GetText()) + { + case "bool": + defaultReturnStatement = "return false;"; + break; + case "float": + case "float32": + defaultReturnStatement = "return 0.0f;"; + break; + case "double": + case "float64": + defaultReturnStatement = "return 0.0;"; + break; + default: + // All kinds of different ints :) + defaultReturnStatement = "return 0;"; + break; + } + } + + returnTypeFound = true; + } + + // If the return type is some type of reference, we cannot replace it with a stack variable. + // This means we need to create a new dummy variable, and we also need to return a reference to it instead of something else. + if (returnTypeFound && returnTypeContext.ChildCount > 0 && + (returnTypeContext.GetChild(returnTypeContext.ChildCount - 1).GetText() == "&")) + { + string dummyReferenceVariableName = typeString.Replace("::", "").Replace(",", "").Replace("<", "").Replace(">", "") + "ReferenceDummy" + Salt++; + string dummyReferenceVariable = string.Format("{0} {1};", typeString, dummyReferenceVariableName); + + // For reference variables, we must register dummy variables to make sure something with longer lifetime scope can be returned. + // I'd really like to just use a static variable here, but since AS doesn't support that, things get messy. + int classStartIdx = -1; + var classSpecifierContext = ExcisionUtils.FindParentContextOfType(returnTypeContext); + if (classSpecifierContext != null && classSpecifierContext.ChildCount > 2) + { + var classMemberSpec = classSpecifierContext.GetChild(2) as UnrealAngelscriptParser.MemberSpecificationContext; + if (classMemberSpec != null) + { + classStartIdx = ExcisionUtils.FindScriptIndexForCodePoint(Script, classMemberSpec.Start.Line, 0); + } + } + + if (classStartIdx < 0) + { + // Reference return dected in non-class. Just leave this alone for now. + return EReturnType.RootScopeReferenceReturn; + } + + HashSet dummyVarSet = null; + if (ClassStartIdxDummyReferenceData.ContainsKey(classStartIdx)) + { + dummyVarSet = ClassStartIdxDummyReferenceData[classStartIdx]; + } + else + { + dummyVarSet = new HashSet(); + } + + dummyVarSet.Add(dummyReferenceVariable); + ClassStartIdxDummyReferenceData[classStartIdx] = dummyVarSet; + + defaultReturnStatement = string.Format("return {0};", dummyReferenceVariableName); + return EReturnType.ReferenceReturn; + } + + return returnTypeFound ? EReturnType.ReplacedReturn : EReturnType.NoReturn; + } + + protected Antlr4.Runtime.Tree.IParseTree GetFirstChildOfType(Antlr4.Runtime.Tree.IParseTree specifierSequence, bool searchReverse = false) + where T : class + { + if ((specifierSequence as T) != null) + { + return specifierSequence; + } + + if (searchReverse) + { + for (int childIdx = specifierSequence.ChildCount - 1; childIdx >= 0; childIdx--) + { + var childResult = GetFirstChildOfType(specifierSequence.GetChild(childIdx), searchReverse); + if (childResult != null) + { + return childResult; + } + } + } + else + { + for (int childIdx = 0; childIdx < specifierSequence.ChildCount; childIdx++) + { + var childResult = GetFirstChildOfType(specifierSequence.GetChild(childIdx), searchReverse); + if (childResult != null) + { + return childResult; + } + } + } + + return null; + } + + protected string BuildIndentationForColumnCount(int nrCols) + { + var indentation = new StringBuilder(""); + for (int indentIdx = 0; indentIdx < nrCols; indentIdx++) + { + indentation.Append("\t"); + } + + return indentation.ToString(); + } + + protected void DecorateFunctionBody(UnrealAngelscriptParser.FunctionBodyContext context) + { + if (context == null) + { + return; + } + + // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. + var returnData = GetDefaultReturnStatementForScope(context); + + ServerOnlyScopeData newData = new ServerOnlyScopeData( + ExcisionUtils.FindScriptIndexForCodePoint(Script, context.Start.Line, context.Start.Column) + 1, + ExcisionUtils.FindScriptIndexForCodePoint(Script, context.Stop.Line, 0)); + + if (returnData.ReturnType != EReturnType.NoReturn) + { + // We want to be one step inside the scope! + string scopeIndentation = BuildIndentationForColumnCount(context.Start.Column + 1); + newData.Opt_ElseContent = string.Format("#else\r\n{0}{1}\r\n", scopeIndentation, returnData.DefaultReturnString); + } + + // Don't detect scopes that are just one line. + if (context.Start.Line != context.Stop.Line + && returnData.ReturnType != EReturnType.RootScopeReferenceReturn) + { + DetectedServerOnlyScopes.Add(newData); + } + } + } } diff --git a/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptSymbolVisitor.cs b/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptSymbolVisitor.cs index 91cbf87..9a08ee6 100644 --- a/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptSymbolVisitor.cs +++ b/UnrealAngelscriptServerCodeExcision/UnrealAngelscriptSymbolVisitor.cs @@ -1,413 +1,413 @@ -using ServerCodeExcisionCommon; using System.Collections.Generic; using System.Text.RegularExpressions; +using ServerCodeExcisionCommon; namespace UnrealAngelscriptServerCodeExcision { - public class UnrealAngelscriptSymbolVisitor : UnrealAngelscriptSimpleVisitor - { - private IServerCodeExcisionLanguage _language; - - public UnrealAngelscriptSymbolVisitor(string script, IServerCodeExcisionLanguage language) - : base(script) - { - _language = language; - } - - public override UnrealAngelscriptNode VisitFunctionDefinition(UnrealAngelscriptParser.FunctionDefinitionContext context) - { - if (context.ChildCount > 2) - { - bool isMemberServerOnlyFunction = false; - - // Check to see for UFUNCTION Server meta. - var maybeUfunctionSpec = context.GetChild(0) as UnrealAngelscriptParser.UfunctionContext; - if (maybeUfunctionSpec != null) - { - // Check all annotations for server only - for (int childIdx = 0; childIdx < maybeUfunctionSpec.ChildCount; childIdx++) - { - var annotationList = maybeUfunctionSpec.GetChild(childIdx) as UnrealAngelscriptParser.AnnotationListContext; - if (annotationList != null) - { - for (int listChildIdx = 0; listChildIdx < annotationList.ChildCount; listChildIdx++) - { - var annotation = annotationList.GetChild(listChildIdx) as UnrealAngelscriptParser.AnnotationContext; - if (annotation != null && annotation.GetText().ToLower() == "server") - { - isMemberServerOnlyFunction = true; - break; - } - } - } - } - } - - // Check to see if function name ends with _Server - for (int childIdx = 0; childIdx < context.ChildCount; childIdx++) - { - var functionDeclarator = context.GetChild(childIdx) as UnrealAngelscriptParser.DeclaratorContext; - if (functionDeclarator != null && functionDeclarator.ChildCount > 0) - { - var name = functionDeclarator.GetChild(0); - if (name != null && name.GetText().ToLower().EndsWith("_server")) - { - isMemberServerOnlyFunction = true; - break; - } - } - } - - if (isMemberServerOnlyFunction) - { - var functionBody = context.GetChild(context.ChildCount - 1) as UnrealAngelscriptParser.FunctionBodyContext; - DecorateFunctionBody(functionBody); - - // Don't visit further children. - return null; - } - } - - return VisitChildren(context); - } - - public override UnrealAngelscriptNode VisitSelectionStatement(UnrealAngelscriptParser.SelectionStatementContext context) - { - // In selection statements, we want to find the compound statement at the end of us and inject macros there. - - var conditionExpression = context.GetChild(2) as UnrealAngelscriptParser.ConditionContext; - switch (IsExpressionServerOnly(conditionExpression)) - { - case EExpressionType.ServerOnly: - { - // Index 4 is the control scope for ifs - AddDetectedScopeFromSelectionChild(context, 4); - break; - } - - case EExpressionType.EverythingAfterBranchIsServerOnly: - { - // We want to inject right after the if-branch child, and continue all the way to the end of the parent scope. - AddParentScopePostIfScope(context); - break; - } - - case EExpressionType.ElseIsServerOnly: - { - // Index 5 is the control scope for elses - AddDetectedScopeFromSelectionChild(context, 6); - break; - } - - default: - break; - } - - return VisitChildren(context); - } - - // Add injections to scopes either in if or else scopes. - private void AddDetectedScopeFromSelectionChild(UnrealAngelscriptParser.SelectionStatementContext context, int childIdx) - { - var selectionScope = context.GetChild(childIdx)?.GetChild(0) as UnrealAngelscriptParser.CompoundStatementContext; - if (selectionScope != null) - { - if (selectionScope.SourceInterval.Length > 2) - { - // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. - // We want to move in one step, since our reference scope is the lower one. - var returnData = GetDefaultReturnStatementForScope(selectionScope); - - ServerOnlyScopeData newData = new ServerOnlyScopeData( - ExcisionUtils.FindScriptIndexForCodePoint(Script, selectionScope.Start.Line, selectionScope.Start.Column) + 1, - ExcisionUtils.FindScriptIndexForCodePoint(Script, selectionScope.Stop.Line, 0)); - - if (returnData.ReturnType != EReturnType.NoReturn) - { - string scopeIndentation = BuildIndentationForColumnCount(selectionScope.Start.Column + 1); - newData.Opt_ElseContent = string.Format("#else\r\n{0}{1}\r\n", scopeIndentation, returnData.DefaultReturnString); - } - - if (returnData.ReturnType != EReturnType.RootScopeReferenceReturn) - { - DetectedServerOnlyScopes.Add(newData); - } - } - } - else - { - // Perhaps it's a one-liner..? - var oneLineScope = context.GetChild(childIdx) as UnrealAngelscriptParser.StatementContext; - if (oneLineScope != null) - { - ServerOnlyScopeData newData = new ServerOnlyScopeData( - MoveOneLine(ExcisionUtils.FindScriptIndexForCodePoint(Script, oneLineScope.Start.Line, 0), false), - MoveOneLine(ExcisionUtils.FindScriptIndexForCodePoint(Script, oneLineScope.Stop.Line, oneLineScope.Stop.Column) + 1, true)); - - // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. - // For one-liners, we actually remove the entire scope, which means we must replace it completely. - string elseScopeContent = ""; - var returnData = GetDefaultReturnStatementForScope(oneLineScope); - if (returnData.ReturnType != EReturnType.NoReturn) - { - // If the one liner is a return value, we must provide a replacement return statement as well. - string elseScopeIndentation = BuildIndentationForColumnCount(oneLineScope.Start.Column); - elseScopeContent = string.Format("\r\n{0}{1}", elseScopeIndentation, returnData.DefaultReturnString); - } - - // Since this was a one-liner, scope indentation should be one level lower than the one-liner - string scopeIndentation = BuildIndentationForColumnCount(oneLineScope.Start.Column - 1); - newData.Opt_ElseContent = string.Format("#else\r\n{0}{{{1}\r\n{0}}}\r\n", scopeIndentation, elseScopeContent); - - if (returnData.ReturnType != EReturnType.RootScopeReferenceReturn) - { - DetectedServerOnlyScopes.Add(newData); - } - } - } - } - - // Add injections after an if scope, until the end of the selection statement's parent scope. - private void AddParentScopePostIfScope(UnrealAngelscriptParser.SelectionStatementContext context) - { - var ifScope = context.GetChild(4); - if (ifScope == null) - { - return; - } - - int ifScopeStopLine = -1; - int ifScopeStopColumn = -1; - var selectionScope = ifScope.GetChild(0) as UnrealAngelscriptParser.CompoundStatementContext; - if (selectionScope != null) - { - ifScopeStopLine = selectionScope.Stop.Line; - ifScopeStopColumn = selectionScope.Stop.Column; - } - else - { - // Perhaps it's a one-liner..? - var oneLineScope = ifScope as UnrealAngelscriptParser.StatementContext; - if (oneLineScope != null) - { - ifScopeStopLine = oneLineScope.Stop.Line; - ifScopeStopColumn = oneLineScope.Stop.Column; - } - } - - var parentScope = ExcisionUtils.FindParentContextOfType(context); - if (parentScope != null && ifScopeStopLine > 0 && ifScopeStopColumn > 0) - { - ServerOnlyScopeData newData = new ServerOnlyScopeData( - ExcisionUtils.FindScriptIndexForCodePoint(Script, ifScopeStopLine, ifScopeStopColumn) + 1, - ExcisionUtils.FindScriptIndexForCodePoint(Script, parentScope.Stop.Line, 0)); - - // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. - var returnData = GetDefaultReturnStatementForScope(parentScope); - if (returnData.ReturnType != EReturnType.NoReturn) - { - string scopeIndentation = BuildIndentationForColumnCount(returnData.ReturnStatementRun.StartColumn); - newData.Opt_ElseContent = string.Format("#else\r\n{0}{1}\r\n", scopeIndentation, returnData.DefaultReturnString); - } - - if (returnData.ReturnType != EReturnType.RootScopeReferenceReturn) - { - DetectedServerOnlyScopes.Add(newData); - } - } - } - - private int MoveOneLine(int curScriptIdx, bool forward) - { - int lineTerminatorsFound = 0; - int testTerm = forward ? 0 : -1; - while (curScriptIdx >= 0 && curScriptIdx < Script.Length && lineTerminatorsFound < 2) - { - if (Script[curScriptIdx + testTerm] == '\r' || Script[curScriptIdx + testTerm] == '\n') - { - lineTerminatorsFound++; - } - - if (forward) - { - curScriptIdx++; - } - else - { - curScriptIdx--; - } - } - - return curScriptIdx; - } - - public override UnrealAngelscriptNode VisitPostfixExpression(UnrealAngelscriptParser.PostfixExpressionContext context) - { - // In assert statements, we want to find the compound statement we ourselves belong to, and inject macros after ourselves. - var maybeAssertSpecifier = context.GetChild(0) as UnrealAngelscriptParser.AssertSpecifierContext; - if (maybeAssertSpecifier != null) - { - var assertExpression = context.GetChild(2) as UnrealAngelscriptParser.ExpressionListContext; - if (IsExpressionServerOnly(assertExpression) == EExpressionType.ServerOnly) - { - // Finding the simple declaration here lets us find the end of the assert line. - var simpleDeclaration = ExcisionUtils.FindDirectParentContextOfTypeWithDifferentSourceInterval(context, context.SourceInterval); - var parentScope = ExcisionUtils.FindParentContextOfType(context); - if (simpleDeclaration != null && parentScope != null) - { - // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. - var returnData = GetDefaultReturnStatementForScope(parentScope); - - ServerOnlyScopeData newData = new ServerOnlyScopeData( - ExcisionUtils.FindScriptIndexForCodePoint(Script, simpleDeclaration.Stop.Line, simpleDeclaration.Stop.Column) + 1, - ExcisionUtils.FindScriptIndexForCodePoint(Script, parentScope.Stop.Line, 0)); - - if (returnData.ReturnType != EReturnType.NoReturn) - { - string scopeIndentation = BuildIndentationForColumnCount(simpleDeclaration.Start.Column); - newData.Opt_ElseContent = string.Format("#else\r\n{0}{1}\r\n", scopeIndentation, returnData.DefaultReturnString); - } - - if (returnData.ReturnType != EReturnType.RootScopeReferenceReturn) - { - DetectedServerOnlyScopes.Add(newData); - } - } - } - } - - return VisitChildren(context); - } - - private EExpressionType IsExpressionServerOnly(Antlr4.Runtime.Tree.IParseTree expressionTree) - { - if (expressionTree == null) - { - return EExpressionType.NotServerOnly; - } - - // First, we drill down into the expression, looking for the server only symbol. - var serverOnlyExpression = FindServerOnlySymbolTree(expressionTree); - if (serverOnlyExpression == null) - { - return EExpressionType.NotServerOnly; - } - - // Next, we need to walk the expression upward until we hit the statement it belongs to. As we do this, we take note of boolean operators along the way, and mutate our path accordingly. - var conjunctivesInExpressionPath = new List(); - var isServerIsNegated = false; - var isExpressionSimple = true; - var currentExpression = serverOnlyExpression; - while (currentExpression != null && currentExpression != expressionTree) - { - if (currentExpression is UnrealAngelscriptParser.LogicalOrExpressionContext && currentExpression.ChildCount > 1) - { - // We must check child count to make sure we are actually in a boolean expression, and not just an empty base. - // This was a disjunctive, so add false to the path. - conjunctivesInExpressionPath.Add(false); - isExpressionSimple = false; - } - else if (currentExpression is UnrealAngelscriptParser.LogicalAndExpressionContext && currentExpression.ChildCount > 1) - { - // This was a conjunctive, so add true to the path. - conjunctivesInExpressionPath.Add(true); - isExpressionSimple = false; - } - else if (IsExpressionNegative(currentExpression)) - { - // This was a negation, invert all entries in our path. Also invert the seed (the ) - isServerIsNegated = !isServerIsNegated; - for (int pathIdx = 0; pathIdx < conjunctivesInExpressionPath.Count; pathIdx++) - { - conjunctivesInExpressionPath[pathIdx] = !conjunctivesInExpressionPath[pathIdx]; - } - } - - currentExpression = currentExpression.Parent; - } - - // After our path has been fully constructed, we now need to make sure that all the operators all the way up to the root expression are conjunctions. If they are, then this expression is server only. - bool isExpressionConjunctive = true; - foreach (bool pathEntry in conjunctivesInExpressionPath) - { - if (!pathEntry) - { - isExpressionConjunctive = false; - break; - } - } - - if (isExpressionConjunctive) - { - if (isServerIsNegated) - { - // If we have a simple branch expression where IsServer is explicitly negated and conjunctive, then we should either guard the else scope, or everything after the if scope (if we can get away with it) - var selectionContext = expressionTree.Parent as UnrealAngelscriptParser.SelectionStatementContext; - if (isExpressionSimple && selectionContext != null && selectionContext.ChildCount > 4) - { - var dummyReturnStatement = new StatementRun(); - return IsLastStatementInScopeAReturn(selectionContext.GetChild(4), ref dummyReturnStatement) - ? EExpressionType.EverythingAfterBranchIsServerOnly - : EExpressionType.ElseIsServerOnly; - } - } - else - { - return EExpressionType.ServerOnly; - } - } - - return EExpressionType.NotServerOnly; - } - - private bool IsExpressionNegative(Antlr4.Runtime.Tree.IParseTree expression) - { - if (expression is UnrealAngelscriptParser.UnaryExpressionContext && expression.ChildCount > 1 && expression.GetText().StartsWith("!")) - { - return true; - } - - if (expression is UnrealAngelscriptParser.EqualityExpressionContext && expression.ChildCount > 2) - { - return expression.GetChild(0).GetText() == "false" || expression.GetChild(2).GetText() == "false"; - } - - return false; - } - - private Antlr4.Runtime.Tree.IParseTree FindServerOnlySymbolTree(Antlr4.Runtime.Tree.IParseTree currentTree) - { - for (int childIdx = 0; childIdx < currentTree.ChildCount; childIdx++) - { - var childTree = currentTree.GetChild(childIdx); - var childTreeContent = childTree.GetText().Trim(); - - if (MatchesAnyRegex(childTreeContent, _language.ServerOnlySymbolRegexes)) - { - return childTree; - } - - var maybeChild = FindServerOnlySymbolTree(childTree); - if(maybeChild != null) - { - return maybeChild; - } - } - - return null; - } - - private bool MatchesAnyRegex(string expression, List Regexes) - { - foreach (var serverOnlySymbolRegex in Regexes) - { - if (Regex.IsMatch(expression, serverOnlySymbolRegex)) - { - return true; - } - } - - return false; - } - } + public class UnrealAngelscriptSymbolVisitor : UnrealAngelscriptSimpleVisitor + { + private IServerCodeExcisionLanguage _language; + + public UnrealAngelscriptSymbolVisitor(string script, IServerCodeExcisionLanguage language) + : base(script) + { + _language = language; + } + + public override UnrealAngelscriptNode VisitFunctionDefinition(UnrealAngelscriptParser.FunctionDefinitionContext context) + { + if (context.ChildCount > 2) + { + bool isMemberServerOnlyFunction = false; + + // Check to see for UFUNCTION Server meta. + var maybeUfunctionSpec = context.GetChild(0) as UnrealAngelscriptParser.UfunctionContext; + if (maybeUfunctionSpec != null) + { + // Check all annotations for server only + for (int childIdx = 0; childIdx < maybeUfunctionSpec.ChildCount; childIdx++) + { + var annotationList = maybeUfunctionSpec.GetChild(childIdx) as UnrealAngelscriptParser.AnnotationListContext; + if (annotationList != null) + { + for (int listChildIdx = 0; listChildIdx < annotationList.ChildCount; listChildIdx++) + { + var annotation = annotationList.GetChild(listChildIdx) as UnrealAngelscriptParser.AnnotationContext; + if (annotation != null && annotation.GetText().ToLower() == "server") + { + isMemberServerOnlyFunction = true; + break; + } + } + } + } + } + + // Check to see if function name ends with _Server + for (int childIdx = 0; childIdx < context.ChildCount; childIdx++) + { + var functionDeclarator = context.GetChild(childIdx) as UnrealAngelscriptParser.DeclaratorContext; + if (functionDeclarator != null && functionDeclarator.ChildCount > 0) + { + var name = functionDeclarator.GetChild(0); + if (name != null && name.GetText().ToLower().EndsWith("_server")) + { + isMemberServerOnlyFunction = true; + break; + } + } + } + + if (isMemberServerOnlyFunction) + { + var functionBody = context.GetChild(context.ChildCount - 1) as UnrealAngelscriptParser.FunctionBodyContext; + DecorateFunctionBody(functionBody); + + // Don't visit further children. + return null; + } + } + + return VisitChildren(context); + } + + public override UnrealAngelscriptNode VisitSelectionStatement(UnrealAngelscriptParser.SelectionStatementContext context) + { + // In selection statements, we want to find the compound statement at the end of us and inject macros there. + + var conditionExpression = context.GetChild(2) as UnrealAngelscriptParser.ConditionContext; + switch (IsExpressionServerOnly(conditionExpression)) + { + case EExpressionType.ServerOnly: + { + // Index 4 is the control scope for ifs + AddDetectedScopeFromSelectionChild(context, 4); + break; + } + + case EExpressionType.EverythingAfterBranchIsServerOnly: + { + // We want to inject right after the if-branch child, and continue all the way to the end of the parent scope. + AddParentScopePostIfScope(context); + break; + } + + case EExpressionType.ElseIsServerOnly: + { + // Index 5 is the control scope for elses + AddDetectedScopeFromSelectionChild(context, 6); + break; + } + + default: + break; + } + + return VisitChildren(context); + } + + // Add injections to scopes either in if or else scopes. + private void AddDetectedScopeFromSelectionChild(UnrealAngelscriptParser.SelectionStatementContext context, int childIdx) + { + var selectionScope = context.GetChild(childIdx)?.GetChild(0) as UnrealAngelscriptParser.CompoundStatementContext; + if (selectionScope != null) + { + if (selectionScope.SourceInterval.Length > 2) + { + // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. + // We want to move in one step, since our reference scope is the lower one. + var returnData = GetDefaultReturnStatementForScope(selectionScope); + + ServerOnlyScopeData newData = new ServerOnlyScopeData( + ExcisionUtils.FindScriptIndexForCodePoint(Script, selectionScope.Start.Line, selectionScope.Start.Column) + 1, + ExcisionUtils.FindScriptIndexForCodePoint(Script, selectionScope.Stop.Line, 0)); + + if (returnData.ReturnType != EReturnType.NoReturn) + { + string scopeIndentation = BuildIndentationForColumnCount(selectionScope.Start.Column + 1); + newData.Opt_ElseContent = string.Format("#else\r\n{0}{1}\r\n", scopeIndentation, returnData.DefaultReturnString); + } + + if (returnData.ReturnType != EReturnType.RootScopeReferenceReturn) + { + DetectedServerOnlyScopes.Add(newData); + } + } + } + else + { + // Perhaps it's a one-liner..? + var oneLineScope = context.GetChild(childIdx) as UnrealAngelscriptParser.StatementContext; + if (oneLineScope != null) + { + ServerOnlyScopeData newData = new ServerOnlyScopeData( + MoveOneLine(ExcisionUtils.FindScriptIndexForCodePoint(Script, oneLineScope.Start.Line, 0), false), + MoveOneLine(ExcisionUtils.FindScriptIndexForCodePoint(Script, oneLineScope.Stop.Line, oneLineScope.Stop.Column) + 1, true)); + + // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. + // For one-liners, we actually remove the entire scope, which means we must replace it completely. + string elseScopeContent = ""; + var returnData = GetDefaultReturnStatementForScope(oneLineScope); + if (returnData.ReturnType != EReturnType.NoReturn) + { + // If the one liner is a return value, we must provide a replacement return statement as well. + string elseScopeIndentation = BuildIndentationForColumnCount(oneLineScope.Start.Column); + elseScopeContent = string.Format("\r\n{0}{1}", elseScopeIndentation, returnData.DefaultReturnString); + } + + // Since this was a one-liner, scope indentation should be one level lower than the one-liner + string scopeIndentation = BuildIndentationForColumnCount(oneLineScope.Start.Column - 1); + newData.Opt_ElseContent = string.Format("#else\r\n{0}{{{1}\r\n{0}}}\r\n", scopeIndentation, elseScopeContent); + + if (returnData.ReturnType != EReturnType.RootScopeReferenceReturn) + { + DetectedServerOnlyScopes.Add(newData); + } + } + } + } + + // Add injections after an if scope, until the end of the selection statement's parent scope. + private void AddParentScopePostIfScope(UnrealAngelscriptParser.SelectionStatementContext context) + { + var ifScope = context.GetChild(4); + if (ifScope == null) + { + return; + } + + int ifScopeStopLine = -1; + int ifScopeStopColumn = -1; + var selectionScope = ifScope.GetChild(0) as UnrealAngelscriptParser.CompoundStatementContext; + if (selectionScope != null) + { + ifScopeStopLine = selectionScope.Stop.Line; + ifScopeStopColumn = selectionScope.Stop.Column; + } + else + { + // Perhaps it's a one-liner..? + var oneLineScope = ifScope as UnrealAngelscriptParser.StatementContext; + if (oneLineScope != null) + { + ifScopeStopLine = oneLineScope.Stop.Line; + ifScopeStopColumn = oneLineScope.Stop.Column; + } + } + + var parentScope = ExcisionUtils.FindParentContextOfType(context); + if (parentScope != null && ifScopeStopLine > 0 && ifScopeStopColumn > 0) + { + ServerOnlyScopeData newData = new ServerOnlyScopeData( + ExcisionUtils.FindScriptIndexForCodePoint(Script, ifScopeStopLine, ifScopeStopColumn) + 1, + ExcisionUtils.FindScriptIndexForCodePoint(Script, parentScope.Stop.Line, 0)); + + // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. + var returnData = GetDefaultReturnStatementForScope(parentScope); + if (returnData.ReturnType != EReturnType.NoReturn) + { + string scopeIndentation = BuildIndentationForColumnCount(returnData.ReturnStatementRun.StartColumn); + newData.Opt_ElseContent = string.Format("#else\r\n{0}{1}\r\n", scopeIndentation, returnData.DefaultReturnString); + } + + if (returnData.ReturnType != EReturnType.RootScopeReferenceReturn) + { + DetectedServerOnlyScopes.Add(newData); + } + } + } + + private int MoveOneLine(int curScriptIdx, bool forward) + { + int lineTerminatorsFound = 0; + int testTerm = forward ? 0 : -1; + while (curScriptIdx >= 0 && curScriptIdx < Script.Length && lineTerminatorsFound < 2) + { + if (Script[curScriptIdx + testTerm] == '\r' || Script[curScriptIdx + testTerm] == '\n') + { + lineTerminatorsFound++; + } + + if (forward) + { + curScriptIdx++; + } + else + { + curScriptIdx--; + } + } + + return curScriptIdx; + } + + public override UnrealAngelscriptNode VisitPostfixExpression(UnrealAngelscriptParser.PostfixExpressionContext context) + { + // In assert statements, we want to find the compound statement we ourselves belong to, and inject macros after ourselves. + var maybeAssertSpecifier = context.GetChild(0) as UnrealAngelscriptParser.AssertSpecifierContext; + if (maybeAssertSpecifier != null) + { + var assertExpression = context.GetChild(2) as UnrealAngelscriptParser.ExpressionListContext; + if (IsExpressionServerOnly(assertExpression) == EExpressionType.ServerOnly) + { + // Finding the simple declaration here lets us find the end of the assert line. + var simpleDeclaration = ExcisionUtils.FindDirectParentContextOfTypeWithDifferentSourceInterval(context, context.SourceInterval); + var parentScope = ExcisionUtils.FindParentContextOfType(context); + if (simpleDeclaration != null && parentScope != null) + { + // If there is a return statement at the end, we must replace it with a suitable replacement, or code will stop compiling. + var returnData = GetDefaultReturnStatementForScope(parentScope); + + ServerOnlyScopeData newData = new ServerOnlyScopeData( + ExcisionUtils.FindScriptIndexForCodePoint(Script, simpleDeclaration.Stop.Line, simpleDeclaration.Stop.Column) + 1, + ExcisionUtils.FindScriptIndexForCodePoint(Script, parentScope.Stop.Line, 0)); + + if (returnData.ReturnType != EReturnType.NoReturn) + { + string scopeIndentation = BuildIndentationForColumnCount(simpleDeclaration.Start.Column); + newData.Opt_ElseContent = string.Format("#else\r\n{0}{1}\r\n", scopeIndentation, returnData.DefaultReturnString); + } + + if (returnData.ReturnType != EReturnType.RootScopeReferenceReturn) + { + DetectedServerOnlyScopes.Add(newData); + } + } + } + } + + return VisitChildren(context); + } + + private EExpressionType IsExpressionServerOnly(Antlr4.Runtime.Tree.IParseTree expressionTree) + { + if (expressionTree == null) + { + return EExpressionType.NotServerOnly; + } + + // First, we drill down into the expression, looking for the server only symbol. + var serverOnlyExpression = FindServerOnlySymbolTree(expressionTree); + if (serverOnlyExpression == null) + { + return EExpressionType.NotServerOnly; + } + + // Next, we need to walk the expression upward until we hit the statement it belongs to. As we do this, we take note of boolean operators along the way, and mutate our path accordingly. + var conjunctivesInExpressionPath = new List(); + var isServerIsNegated = false; + var isExpressionSimple = true; + var currentExpression = serverOnlyExpression; + while (currentExpression != null && currentExpression != expressionTree) + { + if (currentExpression is UnrealAngelscriptParser.LogicalOrExpressionContext && currentExpression.ChildCount > 1) + { + // We must check child count to make sure we are actually in a boolean expression, and not just an empty base. + // This was a disjunctive, so add false to the path. + conjunctivesInExpressionPath.Add(false); + isExpressionSimple = false; + } + else if (currentExpression is UnrealAngelscriptParser.LogicalAndExpressionContext && currentExpression.ChildCount > 1) + { + // This was a conjunctive, so add true to the path. + conjunctivesInExpressionPath.Add(true); + isExpressionSimple = false; + } + else if (IsExpressionNegative(currentExpression)) + { + // This was a negation, invert all entries in our path. Also invert the seed (the ) + isServerIsNegated = !isServerIsNegated; + for (int pathIdx = 0; pathIdx < conjunctivesInExpressionPath.Count; pathIdx++) + { + conjunctivesInExpressionPath[pathIdx] = !conjunctivesInExpressionPath[pathIdx]; + } + } + + currentExpression = currentExpression.Parent; + } + + // After our path has been fully constructed, we now need to make sure that all the operators all the way up to the root expression are conjunctions. If they are, then this expression is server only. + bool isExpressionConjunctive = true; + foreach (bool pathEntry in conjunctivesInExpressionPath) + { + if (!pathEntry) + { + isExpressionConjunctive = false; + break; + } + } + + if (isExpressionConjunctive) + { + if (isServerIsNegated) + { + // If we have a simple branch expression where IsServer is explicitly negated and conjunctive, then we should either guard the else scope, or everything after the if scope (if we can get away with it) + var selectionContext = expressionTree.Parent as UnrealAngelscriptParser.SelectionStatementContext; + if (isExpressionSimple && selectionContext != null && selectionContext.ChildCount > 4) + { + var dummyReturnStatement = new StatementRun(); + return IsLastStatementInScopeAReturn(selectionContext.GetChild(4), ref dummyReturnStatement) + ? EExpressionType.EverythingAfterBranchIsServerOnly + : EExpressionType.ElseIsServerOnly; + } + } + else + { + return EExpressionType.ServerOnly; + } + } + + return EExpressionType.NotServerOnly; + } + + private bool IsExpressionNegative(Antlr4.Runtime.Tree.IParseTree expression) + { + if (expression is UnrealAngelscriptParser.UnaryExpressionContext && expression.ChildCount > 1 && expression.GetText().StartsWith("!")) + { + return true; + } + + if (expression is UnrealAngelscriptParser.EqualityExpressionContext && expression.ChildCount > 2) + { + return expression.GetChild(0).GetText() == "false" || expression.GetChild(2).GetText() == "false"; + } + + return false; + } + + private Antlr4.Runtime.Tree.IParseTree FindServerOnlySymbolTree(Antlr4.Runtime.Tree.IParseTree currentTree) + { + for (int childIdx = 0; childIdx < currentTree.ChildCount; childIdx++) + { + var childTree = currentTree.GetChild(childIdx); + var childTreeContent = childTree.GetText().Trim(); + + if (MatchesAnyRegex(childTreeContent, _language.ServerOnlySymbolRegexes)) + { + return childTree; + } + + var maybeChild = FindServerOnlySymbolTree(childTree); + if (maybeChild != null) + { + return maybeChild; + } + } + + return null; + } + + private bool MatchesAnyRegex(string expression, List Regexes) + { + foreach (var serverOnlySymbolRegex in Regexes) + { + if (Regex.IsMatch(expression, serverOnlySymbolRegex)) + { + return true; + } + } + + return false; + } + } }