Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 90 additions & 9 deletions ServerCodeExciser/ServerCodeExcisionProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Antlr4.Runtime;
using ServerCodeExcisionCommon;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Antlr4.Runtime;
using ServerCodeExcisionCommon;

namespace ServerCodeExcision
{
Expand Down Expand Up @@ -236,19 +238,25 @@ private ExcisionStats ProcessCodeFile(string fileName, string inputPath, EExcisi
stats.TotalNrCharacters = visitor.TotalNumberOfFunctionCharactersVisited;
}

// First process all server only scopes.
// Determine if there are any existing preprocessor server-code exclusions in the source file.
var detectedPreprocessorServerOnlyScopes = FindPreprocessorGuards(commonTokenStream)
.Where(x => x.Directive.Contains(excisionLanguage.ServerScopeStartString, StringComparison.Ordinal));

// Process scopes we've evaluated must be server only.
foreach (ServerOnlyScopeData currentScope in visitor.DetectedServerOnlyScopes)
{
if (currentScope.StartIndex == -1
|| currentScope.StopIndex == -1
|| InjectedMacroAlreadyExistsAtLocation(answerText, currentScope.StartIndex, true, true, excisionLanguage.ServerScopeStartString)
|| InjectedMacroAlreadyExistsAtLocation(answerText, currentScope.StartIndex, false, false, excisionLanguage.ServerScopeStartString)
|| InjectedMacroAlreadyExistsAtLocation(answerText, currentScope.StopIndex, false, false, excisionLanguage.ServerScopeEndString))
if (currentScope.StartIndex == -1 || currentScope.StopIndex == -1)
{
continue;
}

// If there are already injected macros where we want to go, we should skip injecting.
// Skip if there's already a server-code exclusion for the scope. (We don't want have duplicate guards.)
var (StartIndex, StopIndex) = TrimWhitespace(script, currentScope);
if (detectedPreprocessorServerOnlyScopes.Any(x => StartIndex >= x.StartIndex && StopIndex <= x.StopIndex))
{
continue; // We're inside an existing scope.
}

System.Diagnostics.Debug.Assert(currentScope.StopIndex > currentScope.StartIndex, "There must be some invalid pattern here! Stop is before start!");
serverCodeInjections.Add(new KeyValuePair<int, string>(currentScope.StartIndex, "\r\n" + excisionLanguage.ServerScopeStartString));
serverCodeInjections.Add(new KeyValuePair<int, string>(currentScope.StopIndex, currentScope.Opt_ElseContent + excisionLanguage.ServerScopeEndString + "\r\n"));
Expand Down Expand Up @@ -328,6 +336,79 @@ private static bool IsWhitespace(char c)
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

/// <summary>
/// Resize a scope range by excluding whitespace characters.
/// </summary>
private static (int StartIndex, int StopIndex) TrimWhitespace(string script, ServerOnlyScopeData scope)
{
var (startIndex, stopIndex) = (scope.StartIndex, scope.StopIndex);

while (IsWhitespace(script[startIndex]))
{
startIndex++;
}

while (IsWhitespace(script[stopIndex]))
{
stopIndex--;
}

return (startIndex, stopIndex);
}

private static List<(string Directive, int StartIndex, int StopIndex)> FindPreprocessorGuards(BufferedTokenStream tokenStream)
{
var preprocessorDirectives = tokenStream
.GetTokens()
.Where(t => t.Channel == UnrealAngelscriptLexer.PREPROCESSOR_CHANNEL)
.Where(t => t.Type == UnrealAngelscriptLexer.Directive)
.ToList();

var preprocessorGuards = new List<(string Directive, int StartIndex, int StopIndex)>();
var ifStack = new Stack<IToken>();

foreach (var token in preprocessorDirectives)
{
switch (token.Text)
{
case var t when t.StartsWith("#if", StringComparison.Ordinal): // #if, #ifdef, #ifndef
ifStack.Push(token);
break;

case var t when t.StartsWith("#elif", StringComparison.Ordinal): // #elif, #elifdef, #elifndef
{
if (ifStack.TryPop(out var removed))
{
preprocessorGuards.Add((removed.Text, removed.StartIndex, token.StopIndex));
}
ifStack.Push(token);
}
break;

case var t when t.StartsWith("#else", StringComparison.Ordinal):
{
if (ifStack.TryPop(out var removed))
{
preprocessorGuards.Add((removed.Text, removed.StartIndex, token.StopIndex));
}
ifStack.Push(token);
}
break;

case var t when t.StartsWith("#endif", StringComparison.Ordinal):
{
if (ifStack.TryPop(out var removed))
{
preprocessorGuards.Add((removed.Text, removed.StartIndex, token.StopIndex));
}
}
break;
}
}

return preprocessorGuards;
}

private bool InjectedMacroAlreadyExistsAtLocation(StringBuilder script, int index, bool lookAhead, bool ignoreWhitespace, string macro)
{
if (lookAhead)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ class UAlreadyHasGuardsTest
{
void Test()
{
// Commented out block.
//#ifdef WITH_SERVER
//#endif

int SomethingBefore = 0;
SomethingBefore++;

Expand All @@ -16,4 +20,35 @@ class UAlreadyHasGuardsTest
int ButNotThis = 0;
ButNotThis++;
}

void Test2()
{
#ifdef WITH_SERVER
check(System::IsServer());
int ThisMustBeGuarded = 0;
ThisMustBeGuarded++;
#endif // WITH_SERVER
}

void Test3()
{
check(System::IsServer());

#ifdef WITH_SERVER
int ThisMustBeGuarded = 0;
ThisMustBeGuarded++;
#endif
}

void Test4()
{
check(System::IsServer());
#ifdef WITH_SERVER

#if !RELEASE
int ThisMustBeGuarded = 0;
ThisMustBeGuarded++;
#endif
#endif
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ class UAlreadyHasGuardsTest
{
void Test()
{
// Commented out block.
//#ifdef WITH_SERVER
//#endif

int SomethingBefore = 0;
SomethingBefore++;

Expand All @@ -16,4 +20,35 @@ class UAlreadyHasGuardsTest
int ButNotThis = 0;
ButNotThis++;
}

void Test2()
{
#ifdef WITH_SERVER
check(System::IsServer());
int ThisMustBeGuarded = 0;
ThisMustBeGuarded++;
#endif // WITH_SERVER
}

void Test3()
{
check(System::IsServer());

#ifdef WITH_SERVER
int ThisMustBeGuarded = 0;
ThisMustBeGuarded++;
#endif
}

void Test4()
{
check(System::IsServer());
#ifdef WITH_SERVER

#if !RELEASE
int ThisMustBeGuarded = 0;
ThisMustBeGuarded++;
#endif
#endif
}
};
14 changes: 10 additions & 4 deletions UnrealAngelscriptParser/Grammar/UnrealAngelscriptLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

lexer grammar UnrealAngelscriptLexer;

channels { PREPROCESSOR_CHANNEL }

IntegerLiteral:
DecimalLiteral Integersuffix?
| OctalLiteral Integersuffix?
Expand Down Expand Up @@ -32,13 +34,19 @@ UserDefinedLiteral:
| UserDefinedStringLiteral
| UserDefinedCharacterLiteral;

MultiLineMacro: '#' (~[\n]*? '\\' '\r'? '\n')+ ~ [\n]+ -> channel (PREPROCESSOR_CHANNEL);

Directive: '#' ~ [\r\n]* -> channel (PREPROCESSOR_CHANNEL);

/*
Angelscript reserved keywords
https://www.angelcode.com/angelscript/sdk/docs/manual/doc_reserved_keywords.html
*/

Cast: 'cast';

From: 'from';

Import: 'import';

Int: 'int';
Expand All @@ -53,6 +61,8 @@ Int64: 'int64';

Mixin: 'mixin';

Out: 'out';

Property: 'property';

UInt: 'uint';
Expand Down Expand Up @@ -379,7 +389,3 @@ Newline: ('\r' '\n'? | '\n') -> skip;
BlockComment: '/*' .*? '*/' -> skip;

LineComment: '//' ~ [\r\n]* -> skip;

PreprocessorBranchRemoval: '#else' .*? '#endif' -> skip;

Preprocessor: ('#if' | '#ifdef' | '#else' | '#endif') ~ [\r\n]* -> skip;
Loading