From 1dd1c62cf6d5122ee0e7784c76be5ec843208c8f Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Sat, 6 Feb 2021 13:00:47 +0100 Subject: [PATCH] Add Expander.SpanBasedConcatenator --- src/Build/Evaluation/Expander.cs | 117 +++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 77b0417866e..f03b8a2858d 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -123,6 +123,123 @@ internal class Expander where P : class, IProperty where I : class, IItem { + /// + /// A helper struct wrapping a and providing file path conversion + /// as used in e.g. property expansion. + /// + /// + /// If exactly one value is added and no concatenation takes places, this value is returned without + /// conversion. In other cases values are stringified and attempted to be interpreted as file paths + /// before concatenation. + /// + private struct SpanBasedConcatenator : IDisposable + { + /// + /// The backing , null until the second value is added. + /// + private SpanBasedStringBuilder _builder; + + /// + /// The first value added to the concatenator. Tracked in its own field so it can be returned + /// without conversion if no concatenation takes place. + /// + private object _firstObject; + + /// + /// The first value added to the concatenator if it is a span. Tracked in its own field so the + /// functionality doesn't have to be invoked if no concatenation + /// takes place. + /// + private ReadOnlyMemory _firstSpan; + + /// + /// Adds an object to be concatenated. + /// + public void Add(object obj) + { + FlushFirstValueIfNeeded(); + if (_builder != null) + { + _builder.Append(FileUtilities.MaybeAdjustFilePath(obj.ToString())); + } + else + { + _firstObject = obj; + } + } + + /// + /// Adds a span to be concatenated. + /// + public void Add(ReadOnlyMemory span) + { + FlushFirstValueIfNeeded(); + if (_builder != null) + { + _builder.Append(FileUtilities.MaybeAdjustFilePath(span)); + } + else + { + _firstSpan = span; + } + } + + /// + /// Lazily initializes and populates it with the first value + /// when the second value is being added. + /// + private void FlushFirstValueIfNeeded() + { + if (_firstObject != null) + { + _builder = Strings.GetSpanBasedStringBuilder(); + _builder.Append(FileUtilities.MaybeAdjustFilePath(_firstObject.ToString())); + _firstObject = null; + } + else if (!_firstSpan.IsEmpty) + { + _builder = Strings.GetSpanBasedStringBuilder(); +#if FEATURE_SPAN + _builder.Append(FileUtilities.MaybeAdjustFilePath(_firstSpan)); +#else + _builder.Append(FileUtilities.MaybeAdjustFilePath(_firstSpan.ToString())); +#endif + _firstSpan = new ReadOnlyMemory(); + } + } + + /// + /// Returns the result of the concatenation. + /// + /// + /// If only one value has been added and it is not a string, it is returned unchanged. + /// In all other cases (no value, one string value, multiple values) the result is a + /// concatenation of the string representation of the values, each additionally subjected + /// to file path adjustment. + /// + public object GetResult() + { + if (_firstObject != null) + { + if (_firstObject is string stringValue) + { + return FileUtilities.MaybeAdjustFilePath(stringValue); + } + return _firstObject; + } + if (!_firstSpan.IsEmpty) + { + return FileUtilities.MaybeAdjustFilePath(_firstSpan).ToString(); + } + return _builder?.ToString() ?? string.Empty; + } + + /// + /// Disposes of the struct by delegating the call to the underlying . + /// + public void Dispose() => _builder?.Dispose(); + } + /// /// A limit for truncating string expansions within an evaluated Condition. Properties, item metadata, or item groups will be truncated to N characters such as 'N...'. /// Enabled by ExpanderOptions.Truncate.