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