// 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; } } }