/// <summary> /// Инициализирует новый экземпляр класса <see cref="CommandTree"/> с указанным начальным узлом. /// </summary> internal CommandTree(CommandTreeNode startNode) { StartNode = startNode; _globalCommands = new Dictionary <CommandTreeNode, RunMetadata>(); _contextCommands = new MultiValueDictionary <CommandTreeNode, RunMetadata>(new RunMetadataByGroupComparer()); }
/// <summary> /// Связывает указанный узел с метаданными запуска команды. /// </summary> /// <param name="endNode">Узел, на котором команда завершается.</param> /// <param name="metadata"> Метаданные запуска.</param> internal void AddMetadata(CommandTreeNode endNode, RunMetadata metadata) { if (metadata.IsGlobal) { _globalCommands.Add(endNode, metadata); } else { _contextCommands.Add(endNode, metadata); } }
/// <summary> /// Создаёт последовательность узлов в дереве c помощью указанного лексера шаблонов команд. /// </summary> /// <param name="lexer">Лексер.</param> /// <param name="startNode">Стартовый узел дерева.</param> /// <returns>Последний узел созданной последовательности типа <see cref="TokenType.EndOfText"/>.</returns> internal static CommandTreeNode CreateTreeNodes(IPatternLexer lexer, CommandTreeNode startNode) { Debug.Assert(lexer != null); Debug.Assert(startNode != null); var curPointer = startNode; var unprocessedOpenParen = 0; var stackAfterOpenParen = new Stack <CommandTreeNode>(); while (curPointer.TokenPrototype.Type != TokenType.EndOfText) { var token = lexer.NextPatternToken(); if (token.Type == TokenType.OpenParen) { unprocessedOpenParen++; } else if (token.Type == TokenType.CloseParen) { if (stackAfterOpenParen.Count == 0) { throw new WrongPatternException(token, "Количество закрывающих скобок превышает количество открывающих."); } if (unprocessedOpenParen > 0) { throw new WrongPatternException(token, "Группа не может быть пустой (внутри скобок должны быть значимые токены)."); } var startAfterNode = stackAfterOpenParen.Pop(); var endNode = curPointer; endNode.IsGroupEnd = true; var actionPeek = lexer.NextPatternToken(); if (actionPeek.Type == TokenType.Plus) { // создаём цикличную ссылку, тем самым, группа может повторяться. if (endNode.NextNodes.ContainsKey(startAfterNode.TokenPrototype)) { throw new WrongPatternException(actionPeek, "Группа не может содержать только одну подгруппу."); } var backReference = new CommandTreeNode.Reference(startAfterNode, true); endNode.NextNodes.Add(startAfterNode.TokenPrototype, backReference); } else { throw new WrongPatternException(actionPeek, $"После закрывающей скобки может быть только квантификатор '+' - 1 или более раз. Но был встречен токен: {actionPeek}."); } } else { if (curPointer.NextNodes.TryGetValue(token, out CommandTreeNode.Reference reference)) { curPointer = reference.Node; } else { var newNode = new CommandTreeNode(token) { InGroupStartCount = unprocessedOpenParen }; var newReference = new CommandTreeNode.Reference(newNode, false); while (unprocessedOpenParen > 0) { stackAfterOpenParen.Push(newNode); unprocessedOpenParen--; } curPointer.NextNodes.Add(token, newReference); curPointer = newNode; } } } if (stackAfterOpenParen.Count != 0) { throw new WrongPatternException(stackAfterOpenParen.Pop().TokenPrototype, "Количество открывающих скобок превышает количество закрывающих."); } Debug.Assert(curPointer.NextNodes.Count == 0); return(curPointer); }
/// <summary> /// Совершает обход префиксного дерева токенов с помощью указанного лексера. /// </summary> /// <param name="lexer">Лексер исходного текста.</param> /// <param name="start">Стартовый узел дерева.</param> /// <returns>Последний узел и контейнер значений. Если обход закончен неудачно, контейнер значений будет являться <see langword="null"/>.</returns> internal static (CommandTreeNode endNode, ValueContainer container) Traverse(ISourceLexer lexer, CommandTreeNode start) { var currentNode = start; var stack = new Stack <int>(); var values = new List <object>(); var isSequenceRepeat = false; while (true) { var token = lexer.NextSourceToken(); if (!currentNode.NextNodes.TryGetValue(token, out var reference)) { break; } currentNode = reference.Node; if (token.Type == TokenType.EndOfText) { return(currentNode, new ValueContainer(values.ToArray())); } if (reference.IsBack) { isSequenceRepeat = true; } var startCount = currentNode.InGroupStartCount; if (isSequenceRepeat) { if (startCount > 0) { stack.Push(values.Count); } } else { while (startCount > 0) { stack.Push(values.Count); startCount--; } } if (TokenFacts.IsPlaceholder(currentNode.TokenPrototype.Type)) { values.Add(token); } if (currentNode.IsGroupEnd) { Debug.Assert(stack.Count > 0); var index = stack.Pop(); var newContainerToken = ReduceToIterationContainer(values, index); if (isSequenceRepeat) { var groupContainerToken = values[^ 1] as TokenWithValue <GroupContainer>;
/// <summary> /// Получает метаданные запуска по указанному узлу контекстного типа. /// </summary> internal RunMetadata GetContextMetadata(CommandTreeNode endNode, RunMetadata executedCommand) { return(_contextCommands.TryGetActualValue(endNode, executedCommand, out var context) ? context : null); }
/// <summary> /// Получает метаданные запуска по указанному узлу глобального типа. /// </summary> internal RunMetadata GetGlobalMetadata(CommandTreeNode endNode) { return(_globalCommands.GetValueOrDefault(endNode, null)); }
/// <summary> /// Инициализирует новый экземпляр класса <see cref="CommandTreeBuilder"/>. /// </summary> public CommandTreeBuilder() { var startNode = new CommandTreeNode(new Token(TokenType.Unknown)); _tree = new CommandTree(startNode); }
internal Reference(CommandTreeNode node, bool isBack) { Node = node; IsBack = isBack; }