|
| 1 | +using Antlr4.Runtime; |
| 2 | +using ServerCodeExcisionCommon; |
1 | 3 | using System; |
| 4 | +using System.Collections; |
2 | 5 | using System.Collections.Generic; |
3 | 6 | using System.IO; |
| 7 | +using System.Linq; |
4 | 8 | using System.Text; |
5 | 9 | using System.Text.RegularExpressions; |
6 | | -using Antlr4.Runtime; |
7 | | -using ServerCodeExcisionCommon; |
8 | 10 |
|
9 | 11 | namespace ServerCodeExcision |
10 | 12 | { |
@@ -236,19 +238,25 @@ private ExcisionStats ProcessCodeFile(string fileName, string inputPath, EExcisi |
236 | 238 | stats.TotalNrCharacters = visitor.TotalNumberOfFunctionCharactersVisited; |
237 | 239 | } |
238 | 240 |
|
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. |
240 | 246 | foreach (ServerOnlyScopeData currentScope in visitor.DetectedServerOnlyScopes) |
241 | 247 | { |
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) |
247 | 249 | { |
248 | 250 | continue; |
249 | 251 | } |
250 | 252 |
|
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 | + |
252 | 260 | System.Diagnostics.Debug.Assert(currentScope.StopIndex > currentScope.StartIndex, "There must be some invalid pattern here! Stop is before start!"); |
253 | 261 | serverCodeInjections.Add(new KeyValuePair<int, string>(currentScope.StartIndex, "\r\n" + excisionLanguage.ServerScopeStartString)); |
254 | 262 | 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) |
328 | 336 | return c == ' ' || c == '\t' || c == '\r' || c == '\n'; |
329 | 337 | } |
330 | 338 |
|
| 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 | + |
331 | 412 | private bool InjectedMacroAlreadyExistsAtLocation(StringBuilder script, int index, bool lookAhead, bool ignoreWhitespace, string macro) |
332 | 413 | { |
333 | 414 | if (lookAhead) |
|
0 commit comments