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