Files
UnrealEngine/Engine/Source/Programs/UnrealBuildTool/Preprocessor/PreprocessorState.cs
2025-05-18 13:04:45 +08:00

349 lines
9.3 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace UnrealBuildTool
{
/// <summary>
/// Flags indicating the state of each nested conditional block in a stack.
/// </summary>
[Flags]
enum PreprocessorBranch
{
/// <summary>
/// The current conditional branch is active.
/// </summary>
Active = 0x01,
/// <summary>
/// A branch within the current conditional block has been taken. Any #else/#elif blocks will not be taken.
/// </summary>
Taken = 0x02,
/// <summary>
/// The #else directive for this conditional block has been encountered. An #endif directive is expected next.
/// </summary>
Complete = 0x04,
/// <summary>
/// The first condition in this branch was an #if directive (as opposed to an #ifdef or #ifndef directive)
/// </summary>
HasIfDirective = 0x10,
/// <summary>
/// The first condition in this branch was an #if directive (as opposed to an #ifdef or #ifndef directive)
/// </summary>
HasIfdefDirective = 0x20,
/// <summary>
/// The first condition in this branch was an #ifndef directive (as opposed to an #ifdef or #if directive)
/// </summary>
HasIfndefDirective = 0x40,
/// <summary>
/// The branch has an #elif directive
/// </summary>
HasElifDirective = 0x80,
}
/// <summary>
/// Marshals access to the preprocessor state, ensuring that any changes are tracked within the active transform
/// </summary>
class PreprocessorState
{
/// <summary>
/// Stack of conditional branch states
/// </summary>
readonly List<PreprocessorBranch> _branches = new();
/// <summary>
/// Mapping of name to macro definition
/// </summary>
readonly Dictionary<Identifier, PreprocessorMacro> _nameToMacro = new();
/// <summary>
/// The current transform. Any queries or state modifications will be recorded in this.
/// </summary>
PreprocessorTransform? _transform;
/// <summary>
/// Enumerates the current branches. Do not use this for decision logic; any dependencies will not be tracked.
/// </summary>
public IEnumerable<PreprocessorBranch> CurrentBranches => _branches;
/// <summary>
/// Enumerates the current macros. Do not use this for decision logic; any dependencies will not be tracked.
/// </summary>
public IEnumerable<PreprocessorMacro> CurrentMacros => _nameToMacro.Values;
/// <summary>
/// Initialize an empty preprocessor state
/// </summary>
public PreprocessorState()
{
}
/// <summary>
/// Duplicates another preprocessor state. Throws an exception if a transform is currently being built in the other state.
/// </summary>
/// <param name="other">The preprocessor state to copy</param>
public PreprocessorState(PreprocessorState other)
{
if (other._transform != null)
{
throw new Exception("Unable to copy another preprocessor state while a transform is being built.");
}
_branches.AddRange(other._branches);
foreach (KeyValuePair<Identifier, PreprocessorMacro> pair in other._nameToMacro)
{
_nameToMacro[pair.Key] = pair.Value;
}
}
/// <summary>
/// Create a new transform object, and assign it to be current
/// </summary>
/// <returns>The new transform object</returns>
public PreprocessorTransform BeginCapture()
{
_transform = new PreprocessorTransform();
return _transform;
}
/// <summary>
/// Detach the current transform
/// </summary>
/// <returns>The transform object</returns>
public PreprocessorTransform? EndCapture()
{
PreprocessorTransform? prevTransform = _transform;
_transform = null;
return prevTransform;
}
/// <summary>
/// Sets a flag indicating that a #pragma once directive was encountered
/// </summary>
public void MarkPragmaOnce()
{
if (_transform != null)
{
_transform.HasPragmaOnce = true;
}
}
/// <summary>
/// Set a macro to a specific definition
/// </summary>
/// <param name="macro">Macro definition</param>
public void DefineMacro(PreprocessorMacro macro)
{
_nameToMacro[macro.Name] = macro;
if (_transform != null)
{
_transform.NewMacros[macro.Name] = macro;
}
}
/// <summary>
/// Checks if a macro with the given name is defined
/// </summary>
/// <param name="name">The macro name</param>
/// <returns>True if the macro is defined</returns>
public bool IsMacroDefined(Identifier name)
{
// Could account for the fact that we don't need the full definition later...
return TryGetMacro(name, out _);
}
/// <summary>
/// Removes a macro definition
/// </summary>
/// <param name="name">Name of the macro</param>
public void UndefMacro(Identifier name)
{
_nameToMacro.Remove(name);
if (_transform != null)
{
_transform.NewMacros[name] = null;
}
}
/// <summary>
/// Attemps to get the definition for a macro
/// </summary>
/// <param name="name">Name of the macro</param>
/// <param name="macro">Receives the macro definition, or null if it's not defined</param>
/// <returns>True if the macro is defined, otherwise false</returns>
public bool TryGetMacro(Identifier name, [NotNullWhen(true)] out PreprocessorMacro? macro)
{
_nameToMacro.TryGetValue(name, out macro);
if (_transform != null && !_transform.NewMacros.ContainsKey(name))
{
_transform.RequiredMacros[name] = macro;
}
return macro != null;
}
/// <summary>
/// Pushes the preprocessor branch onto the stack
/// </summary>
/// <param name="branch">The branch state</param>
public void PushBranch(PreprocessorBranch branch)
{
_branches.Add(branch);
_transform?.NewBranches.Add(branch);
}
/// <summary>
/// Pops a preprocessor branch from the stack
/// </summary>
/// <returns>The popped branch state</returns>
public PreprocessorBranch PopBranch()
{
if (!TryPopBranch(out PreprocessorBranch branch))
{
throw new InvalidOperationException("Branch stack is empty");
}
return branch;
}
/// <summary>
/// Attempts to pops a preprocessor branch from the stack
/// </summary>
/// <param name="branch">On success, receives the preprocessor branch state</param>
/// <returns>True if a branch was active, else false</returns>
public bool TryPopBranch(out PreprocessorBranch branch)
{
if (_branches.Count == 0)
{
branch = 0;
return false;
}
else
{
branch = _branches[^1];
_branches.RemoveAt(_branches.Count - 1);
if (_transform != null)
{
if (_transform.NewBranches.Count > 0)
{
_transform.NewBranches.RemoveAt(_transform.NewBranches.Count - 1);
}
else
{
_transform.RequiredBranches.Add(branch);
_transform.RequireTopmostActive = null;
}
}
return true;
}
}
/// <summary>
/// Determines if the branch that the preprocessor is in is active
/// </summary>
/// <returns>True if the branch is active, false otherwise</returns>
public bool IsCurrentBranchActive()
{
bool active = (_branches.Count == 0 || _branches[^1].HasFlag(PreprocessorBranch.Active));
if (_transform != null && _transform.NewBranches.Count == 0)
{
_transform.RequireTopmostActive = active;
}
return active;
}
/// <summary>
/// Determines if the given transform can apply to the current preprocessor state
/// </summary>
/// <param name="transform">The transform to test</param>
/// <returns>True if the transform can be applied to the current state</returns>
public bool CanApply(PreprocessorTransform transform)
{
// Check all the required branches match
for (int idx = 0; idx < transform.RequiredBranches.Count; idx++)
{
if (_branches[_branches.Count - idx - 1] != transform.RequiredBranches[idx])
{
return false;
}
}
// Check the topmost branch is active
if (transform.RequireTopmostActive.HasValue)
{
bool topmostActive = _branches.Count == transform.RequiredBranches.Count || _branches[_branches.Count - transform.RequiredBranches.Count - 1].HasFlag(PreprocessorBranch.Active);
if (transform.RequireTopmostActive.Value != topmostActive)
{
return false;
}
}
// Check all the required macros match
foreach (KeyValuePair<Identifier, PreprocessorMacro?> requiredPair in transform.RequiredMacros)
{
if (_nameToMacro.TryGetValue(requiredPair.Key, out PreprocessorMacro? macro))
{
if (requiredPair.Value == null || !macro.IsEquivalentTo(requiredPair.Value))
{
return false;
}
}
else
{
if (requiredPair.Value != null)
{
return false;
}
}
}
return true;
}
/// <summary>
/// Apply the given transform to the current preprocessor state
/// </summary>
/// <param name="transform">The transform to apply</param>
/// <returns>True if the transform was applied to the current state</returns>
public bool TryToApply(PreprocessorTransform transform)
{
if (!CanApply(transform))
{
return false;
}
// Update the branch state
_branches.RemoveRange(_branches.Count - transform.RequiredBranches.Count, transform.RequiredBranches.Count);
_branches.AddRange(transform.NewBranches);
// Update the macro definitions
foreach (KeyValuePair<Identifier, PreprocessorMacro?> newMacro in transform.NewMacros)
{
if (newMacro.Value == null)
{
_nameToMacro.Remove(newMacro.Key);
}
else
{
_nameToMacro[newMacro.Key] = newMacro.Value;
}
}
return true;
}
}
}