Skip to content

Commit ffc8bca

Browse files
authored
Merge pull request #20 from EmbarkStudios/webbju/support-preprocessor-guard-parsing
Support preprocessor directives for server-code scopes.
2 parents 0187657 + 7e2ddec commit ffc8bca

File tree

4 files changed

+170
-13
lines changed

4 files changed

+170
-13
lines changed

ServerCodeExciser/ServerCodeExcisionProcessor.cs

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
using Antlr4.Runtime;
2+
using ServerCodeExcisionCommon;
13
using System;
4+
using System.Collections;
25
using System.Collections.Generic;
36
using System.IO;
7+
using System.Linq;
48
using System.Text;
59
using System.Text.RegularExpressions;
6-
using Antlr4.Runtime;
7-
using ServerCodeExcisionCommon;
810

911
namespace ServerCodeExcision
1012
{
@@ -236,19 +238,25 @@ private ExcisionStats ProcessCodeFile(string fileName, string inputPath, EExcisi
236238
stats.TotalNrCharacters = visitor.TotalNumberOfFunctionCharactersVisited;
237239
}
238240

239-
// First process all server only scopes.
241+
// Determine if there are any existing preprocessor server-code exclusions in the source file.
242+
var detectedPreprocessorServerOnlyScopes = FindPreprocessorGuards(commonTokenStream)
243+
.Where(x => x.Directive.Contains(excisionLanguage.ServerScopeStartString, StringComparison.Ordinal));
244+
245+
// Process scopes we've evaluated must be server only.
240246
foreach (ServerOnlyScopeData currentScope in visitor.DetectedServerOnlyScopes)
241247
{
242-
if (currentScope.StartIndex == -1
243-
|| currentScope.StopIndex == -1
244-
|| InjectedMacroAlreadyExistsAtLocation(answerText, currentScope.StartIndex, true, true, excisionLanguage.ServerScopeStartString)
245-
|| InjectedMacroAlreadyExistsAtLocation(answerText, currentScope.StartIndex, false, false, excisionLanguage.ServerScopeStartString)
246-
|| InjectedMacroAlreadyExistsAtLocation(answerText, currentScope.StopIndex, false, false, excisionLanguage.ServerScopeEndString))
248+
if (currentScope.StartIndex == -1 || currentScope.StopIndex == -1)
247249
{
248250
continue;
249251
}
250252

251-
// If there are already injected macros where we want to go, we should skip injecting.
253+
// Skip if there's already a server-code exclusion for the scope. (We don't want have duplicate guards.)
254+
var (StartIndex, StopIndex) = TrimWhitespace(script, currentScope);
255+
if (detectedPreprocessorServerOnlyScopes.Any(x => StartIndex >= x.StartIndex && StopIndex <= x.StopIndex))
256+
{
257+
continue; // We're inside an existing scope.
258+
}
259+
252260
System.Diagnostics.Debug.Assert(currentScope.StopIndex > currentScope.StartIndex, "There must be some invalid pattern here! Stop is before start!");
253261
serverCodeInjections.Add(new KeyValuePair<int, string>(currentScope.StartIndex, "\r\n" + excisionLanguage.ServerScopeStartString));
254262
serverCodeInjections.Add(new KeyValuePair<int, string>(currentScope.StopIndex, currentScope.Opt_ElseContent + excisionLanguage.ServerScopeEndString + "\r\n"));
@@ -328,6 +336,79 @@ private static bool IsWhitespace(char c)
328336
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
329337
}
330338

339+
/// <summary>
340+
/// Resize a scope range by excluding whitespace characters.
341+
/// </summary>
342+
private static (int StartIndex, int StopIndex) TrimWhitespace(string script, ServerOnlyScopeData scope)
343+
{
344+
var (startIndex, stopIndex) = (scope.StartIndex, scope.StopIndex);
345+
346+
while (IsWhitespace(script[startIndex]))
347+
{
348+
startIndex++;
349+
}
350+
351+
while (IsWhitespace(script[stopIndex]))
352+
{
353+
stopIndex--;
354+
}
355+
356+
return (startIndex, stopIndex);
357+
}
358+
359+
private static List<(string Directive, int StartIndex, int StopIndex)> FindPreprocessorGuards(BufferedTokenStream tokenStream)
360+
{
361+
var preprocessorDirectives = tokenStream
362+
.GetTokens()
363+
.Where(t => t.Channel == UnrealAngelscriptLexer.PREPROCESSOR_CHANNEL)
364+
.Where(t => t.Type == UnrealAngelscriptLexer.Directive)
365+
.ToList();
366+
367+
var preprocessorGuards = new List<(string Directive, int StartIndex, int StopIndex)>();
368+
var ifStack = new Stack<IToken>();
369+
370+
foreach (var token in preprocessorDirectives)
371+
{
372+
switch (token.Text)
373+
{
374+
case var t when t.StartsWith("#if", StringComparison.Ordinal): // #if, #ifdef, #ifndef
375+
ifStack.Push(token);
376+
break;
377+
378+
case var t when t.StartsWith("#elif", StringComparison.Ordinal): // #elif, #elifdef, #elifndef
379+
{
380+
if (ifStack.TryPop(out var removed))
381+
{
382+
preprocessorGuards.Add((removed.Text, removed.StartIndex, token.StopIndex));
383+
}
384+
ifStack.Push(token);
385+
}
386+
break;
387+
388+
case var t when t.StartsWith("#else", StringComparison.Ordinal):
389+
{
390+
if (ifStack.TryPop(out var removed))
391+
{
392+
preprocessorGuards.Add((removed.Text, removed.StartIndex, token.StopIndex));
393+
}
394+
ifStack.Push(token);
395+
}
396+
break;
397+
398+
case var t when t.StartsWith("#endif", StringComparison.Ordinal):
399+
{
400+
if (ifStack.TryPop(out var removed))
401+
{
402+
preprocessorGuards.Add((removed.Text, removed.StartIndex, token.StopIndex));
403+
}
404+
}
405+
break;
406+
}
407+
}
408+
409+
return preprocessorGuards;
410+
}
411+
331412
private bool InjectedMacroAlreadyExistsAtLocation(StringBuilder script, int index, bool lookAhead, bool ignoreWhitespace, string macro)
332413
{
333414
if (lookAhead)

ServerCodeExciserTest/Problems/Common/AlreadyHasGuardsTest.common

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ class UAlreadyHasGuardsTest
22
{
33
void Test()
44
{
5+
// Commented out block.
6+
//#ifdef WITH_SERVER
7+
//#endif
8+
59
int SomethingBefore = 0;
610
SomethingBefore++;
711

@@ -16,4 +20,35 @@ class UAlreadyHasGuardsTest
1620
int ButNotThis = 0;
1721
ButNotThis++;
1822
}
23+
24+
void Test2()
25+
{
26+
#ifdef WITH_SERVER
27+
check(System::IsServer());
28+
int ThisMustBeGuarded = 0;
29+
ThisMustBeGuarded++;
30+
#endif // WITH_SERVER
31+
}
32+
33+
void Test3()
34+
{
35+
check(System::IsServer());
36+
37+
#ifdef WITH_SERVER
38+
int ThisMustBeGuarded = 0;
39+
ThisMustBeGuarded++;
40+
#endif
41+
}
42+
43+
void Test4()
44+
{
45+
check(System::IsServer());
46+
#ifdef WITH_SERVER
47+
48+
#if !RELEASE
49+
int ThisMustBeGuarded = 0;
50+
ThisMustBeGuarded++;
51+
#endif
52+
#endif
53+
}
1954
};

ServerCodeExciserTest/Problems/Common/AlreadyHasGuardsTest.common.solution

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ class UAlreadyHasGuardsTest
22
{
33
void Test()
44
{
5+
// Commented out block.
6+
//#ifdef WITH_SERVER
7+
//#endif
8+
59
int SomethingBefore = 0;
610
SomethingBefore++;
711

@@ -16,4 +20,35 @@ class UAlreadyHasGuardsTest
1620
int ButNotThis = 0;
1721
ButNotThis++;
1822
}
23+
24+
void Test2()
25+
{
26+
#ifdef WITH_SERVER
27+
check(System::IsServer());
28+
int ThisMustBeGuarded = 0;
29+
ThisMustBeGuarded++;
30+
#endif // WITH_SERVER
31+
}
32+
33+
void Test3()
34+
{
35+
check(System::IsServer());
36+
37+
#ifdef WITH_SERVER
38+
int ThisMustBeGuarded = 0;
39+
ThisMustBeGuarded++;
40+
#endif
41+
}
42+
43+
void Test4()
44+
{
45+
check(System::IsServer());
46+
#ifdef WITH_SERVER
47+
48+
#if !RELEASE
49+
int ThisMustBeGuarded = 0;
50+
ThisMustBeGuarded++;
51+
#endif
52+
#endif
53+
}
1954
};

UnrealAngelscriptParser/Grammar/UnrealAngelscriptLexer.g4

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
lexer grammar UnrealAngelscriptLexer;
77

8+
channels { PREPROCESSOR_CHANNEL }
9+
810
IntegerLiteral:
911
DecimalLiteral Integersuffix?
1012
| OctalLiteral Integersuffix?
@@ -32,13 +34,19 @@ UserDefinedLiteral:
3234
| UserDefinedStringLiteral
3335
| UserDefinedCharacterLiteral;
3436

37+
MultiLineMacro: '#' (~[\n]*? '\\' '\r'? '\n')+ ~ [\n]+ -> channel (PREPROCESSOR_CHANNEL);
38+
39+
Directive: '#' ~ [\r\n]* -> channel (PREPROCESSOR_CHANNEL);
40+
3541
/*
3642
Angelscript reserved keywords
3743
https://www.angelcode.com/angelscript/sdk/docs/manual/doc_reserved_keywords.html
3844
*/
3945

4046
Cast: 'cast';
4147

48+
From: 'from';
49+
4250
Import: 'import';
4351

4452
Int: 'int';
@@ -53,6 +61,8 @@ Int64: 'int64';
5361

5462
Mixin: 'mixin';
5563

64+
Out: 'out';
65+
5666
Property: 'property';
5767

5868
UInt: 'uint';
@@ -379,7 +389,3 @@ Newline: ('\r' '\n'? | '\n') -> skip;
379389
BlockComment: '/*' .*? '*/' -> skip;
380390
381391
LineComment: '//' ~ [\r\n]* -> skip;
382-
383-
PreprocessorBranchRemoval: '#else' .*? '#endif' -> skip;
384-
385-
Preprocessor: ('#if' | '#ifdef' | '#else' | '#endif') ~ [\r\n]* -> skip;

0 commit comments

Comments
 (0)