| File: Expressions\ScopeBuilder.cs | Web Access |
| Project: src\sdk\src\TemplateEngine\Microsoft.TemplateEngine.Core\Microsoft.TemplateEngine.Core.csproj (Microsoft.TemplateEngine.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.TemplateEngine.Core.Contracts; using Microsoft.TemplateEngine.Core.Util; namespace Microsoft.TemplateEngine.Core.Expressions { public class ScopeBuilder<TOperator, TToken> where TOperator : struct where TToken : struct { private readonly ISet<TToken> _badSyntaxTokens; private readonly TToken _closeGroup; private readonly TOperator _identity; private readonly bool _isSymbolDereferenceInLiteralSequenceRequired; private readonly int _knownTokensCount; private readonly TToken _literal; private readonly ISet<TToken> _literalSequenceBoundsMarkers; private readonly ISet<TToken> _noops; private readonly TToken _openGroup; private readonly IReadOnlyDictionary<TOperator, Func<IEvaluable, IEvaluable>> _operatorScopeFactory; private readonly IProcessorState _processor; private readonly IReadOnlyList<object> _symbolValues; private readonly IReadOnlyList<string> _symbolKeys; private readonly ISet<TToken> _terminators; private readonly ITokenTrie _tokens; private readonly IReadOnlyDictionary<TToken, TOperator> _tokenToOperatorMap; private readonly Func<string, string> _valueDecoder; private readonly Func<string, string> _valueEncoder; public ScopeBuilder(IProcessorState processor, ITokenTrie tokens, IOperatorMap<TOperator, TToken> operatorMap, bool dereferenceInLiterals) { TokenTrie trie = new TokenTrie(); trie.Append(tokens); _badSyntaxTokens = operatorMap.BadSyntaxTokens; _noops = operatorMap.NoOpTokens; _openGroup = operatorMap.OpenGroupToken; _closeGroup = operatorMap.CloseGroupToken; _literal = operatorMap.LiteralToken; _identity = operatorMap.Identity; _literalSequenceBoundsMarkers = operatorMap.LiteralSequenceBoundsMarkers; _terminators = operatorMap.Terminators; _isSymbolDereferenceInLiteralSequenceRequired = dereferenceInLiterals; _processor = processor; _knownTokensCount = tokens.Count; _valueEncoder = operatorMap.Encode; _valueDecoder = operatorMap.Decode; List<object> symbolValues = new List<object>(); List<string> symbolKeys = new List<string>(); foreach (KeyValuePair<string, object> variable in processor.Config.Variables) { trie.AddToken(processor.Encoding.GetBytes(string.Format(processor.Config.VariableFormatString, variable.Key))); symbolValues.Add(variable.Value); symbolKeys.Add(variable.Key); } _symbolValues = symbolValues; _symbolKeys = symbolKeys; _tokenToOperatorMap = operatorMap.TokensToOperatorsMap; _operatorScopeFactory = operatorMap.OperatorScopeLookupFactory; _tokens = trie; } /// <summary> /// Traverses the given buffer position and creates an evaluable expression. /// If non-null bag for variable references is passed, it will be populated with references of variables used within the evaluable expression. /// </summary> /// <param name="bufferLength"></param> /// <param name="bufferPosition"></param> /// <param name="onFault"></param> /// <param name="referencedVariablesKeys">If passed (if not null) it will be populated with references to variables used within the inspected expression.</param> /// <returns></returns> public IEvaluable? Build(ref int bufferLength, ref int bufferPosition, Action<IReadOnlyList<byte>> onFault, HashSet<string>? referencedVariablesKeys = null) { Stack<ScopeIsolator> parents = new Stack<ScopeIsolator>(); ScopeIsolator isolator = new ScopeIsolator { Root = new UnaryScope<TOperator>(null, _identity, o => o) }; isolator.Active = isolator.Root; TToken? activeLiteralSequenceBoundsMarker = null; List<byte> currentLiteral = new List<byte>(); List<byte> allData = new List<byte>(); while (bufferLength > 0) { int targetLen = Math.Min(bufferLength, _tokens.MaxLength); for (; bufferPosition < bufferLength - targetLen + 1;) { int oldBufferPos = bufferPosition; if (_tokens.GetOperation(_processor.CurrentBuffer, bufferLength, ref bufferPosition, out int token)) { allData.AddRange(_tokens.Tokens[token].Value); TToken mappedToken = (TToken)(object)token; if (_badSyntaxTokens.Contains(mappedToken)) { onFault(allData); return null; } //Hit a terminator? Return the root if (_terminators.Contains(mappedToken)) { bufferPosition = oldBufferPos; //If we had an active literal, it has to be over now - all literal types have already been processed if (currentLiteral.Count > 0) { string value = _processor.Encoding.GetString(currentLiteral.ToArray()); value = _valueDecoder(value); currentLiteral.Clear(); Token<TToken> t = new Token<TToken>(_literal, value); TokenScope<TToken> scope = new TokenScope<TToken>(isolator.Active, t); while (isolator.Active != null && !isolator.Active.TryAccept(scope)) { isolator.Active = isolator.Active.Parent; } if (isolator.Active == null) { onFault(allData); return null; } } return isolator.Root; } //Start or end of a literal sequence (string)? if (_literalSequenceBoundsMarkers.Contains(mappedToken)) { //Don't add the literal start/end, otherwise we'll have to deal with them // in strings, guessing whether they're supposed to be there or not if (activeLiteralSequenceBoundsMarker.HasValue) { if (Equals(activeLiteralSequenceBoundsMarker.Value, mappedToken)) { activeLiteralSequenceBoundsMarker = null; string value = _processor.Encoding.GetString(currentLiteral.ToArray()); value = _valueDecoder(value); currentLiteral.Clear(); Token<TToken> t = new Token<TToken>(_literal, value); TokenScope<TToken> scope = new TokenScope<TToken>(isolator.Active, t) { IsQuoted = true }; while (isolator.Active != null && !isolator.Active.TryAccept(scope)) { isolator.Active = isolator.Active.Parent; } if (isolator.Active == null) { onFault(allData); return null; } } } else { activeLiteralSequenceBoundsMarker = mappedToken; } } //In a literal sequence (string)? else if (activeLiteralSequenceBoundsMarker.HasValue) { //Have a symbol & dereferencing in literal sequences is on? if (_knownTokensCount <= token && _isSymbolDereferenceInLiteralSequenceRequired) { object val = _symbolValues[token - _knownTokensCount]; referencedVariablesKeys?.Add(_symbolKeys[token - _knownTokensCount]); string valText = (val ?? "null").ToString(); valText = _valueEncoder(valText); byte[] data = _processor.Encoding.GetBytes(valText); currentLiteral.AddRange(data); } else { currentLiteral.AddRange(_tokens.Tokens[token].Value); } } else { //If we had an active literal, it has to be over now - all literal types have already been processed if (currentLiteral.Count > 0) { activeLiteralSequenceBoundsMarker = null; string value = _processor.Encoding.GetString(currentLiteral.ToArray()); value = _valueDecoder(value); currentLiteral.Clear(); Token<TToken> t = new Token<TToken>(_literal, value); TokenScope<TToken> scope = new TokenScope<TToken>(isolator.Active, t); while (isolator.Active != null && !isolator.Active.TryAccept(scope)) { isolator.Active = isolator.Active.Parent; } if (isolator.Active == null) { onFault(allData); return null; } } //Start of a group? if (Equals(_openGroup, mappedToken)) { parents.Push(isolator); isolator = new ScopeIsolator { Root = new UnaryScope<TOperator>(null, _identity, o => o) }; isolator.Active = isolator.Root; } //End of a group? else if (Equals(_closeGroup, mappedToken)) { ScopeIsolator tmp = parents.Pop(); tmp.Active!.TryAccept(isolator.Root); isolator.Root = tmp.Active; isolator = tmp; } //Is it a variable? else if (_knownTokensCount <= token) { object? value = _symbolValues[token - _knownTokensCount] ?? null; referencedVariablesKeys?.Add(_symbolKeys[token - _knownTokensCount]); Token<TToken> t = new Token<TToken>(_literal, value); TokenScope<TToken> scope = new TokenScope<TToken>(isolator.Active, t); while (isolator.Active != null && !isolator.Active.TryAccept(scope)) { isolator.Active = isolator.Active.Parent; } if (isolator.Active == null) { onFault(allData); return null; } } //Discardable tokens? else if (_noops.Contains(mappedToken)) { } //All the special possibilities have been exhausted, try to process operations else { //We got a token we understand, but it's not an operator if (_tokenToOperatorMap.TryGetValue(mappedToken, out TOperator op)) { if (_operatorScopeFactory.TryGetValue(op, out Func<IEvaluable, IEvaluable> factory)) { IEvaluable oldActive = isolator.Active; isolator.Active = factory(isolator.Active); if (oldActive.Parent == isolator.Active && (oldActive == isolator.Root || (isolator.Root is UnaryScope<TOperator> o && Equals(o.Operator, _identity) && o.Operand == oldActive))) { isolator.Root = isolator.Active; } } } } } } //If we've encountered a literal after fully filling the tree, return else if (isolator.Active.IsFull) { IEvaluable? parent = isolator.Active.Parent; while (parent != null && parent.IsFull) { parent = parent.Parent; } if (parent == null) { return isolator.Root; } } else { allData.Add(_processor.CurrentBuffer[bufferPosition]); currentLiteral.Add(_processor.CurrentBuffer[bufferPosition]); ++bufferPosition; } } _processor.AdvanceBuffer(bufferPosition); bufferPosition = _processor.CurrentBufferPosition; bufferLength = _processor.CurrentBufferLength; } //If we had an active literal, it has to be over now - all literal types have already been processed if (currentLiteral.Count > 0) { string value = _processor.Encoding.GetString(currentLiteral.ToArray()); value = _valueDecoder(value); currentLiteral.Clear(); Token<TToken> t = new Token<TToken>(_literal, value); TokenScope<TToken> scope = new TokenScope<TToken>(isolator.Active, t); while (isolator.Active != null && !isolator.Active.TryAccept(scope)) { isolator.Active = isolator.Active.Parent; } if (isolator.Active == null) { onFault(allData); return null; } } return isolator.Root; } private class ScopeIsolator { public IEvaluable? Active { get; set; } public IEvaluable? Root { get; set; } } } }