public void TestMethod1() { List <Node> nodes = new List <Node>(); NodeGenerator.Generate(5000, nodes); BlockManager bm = new BlockManager(); bm.Build(nodes); // vystav strukturu bm.LoadMemoryBlock(49); //nacti memory block 5 // bm.TESTING_METHOD_REMOVE_AFTER_TESTING(); // TODO: hledani (Dodelat binar, interpolacni) // TODO: odstranovani - kontanta jako prazdne misto ? // TODO: rebuild pri pridavani ? int a = 0; bm.SaveMemoryBlock(); bm.LoadMemoryBlock(5); Node nn = null; int MemBlock = -1; bm.Search(1, new Point(597, 100), out nn, out MemBlock); int searchx = 0; searchx = Convert.ToInt32(Console.ReadLine()); bm.Search(1, new Point(searchx, 100), out nn, out MemBlock); bm.Search(0, new Point(900, 100), out nn, out MemBlock); }
private void ApplyAlternative(Alternative alternativeToApply) { var stackTop = Stack.Pop(); if (!String.IsNullOrEmpty(alternativeToApply.Alias)) { stackTop.Alias = alternativeToApply.Alias; } NestingLevel[stackTop] = LexingStream.GetPairsCount(); for (var i = alternativeToApply.Count - 1; i >= 0; --i) { var newNode = NodeGenerator.Generate(alternativeToApply[i].Symbol, alternativeToApply[i].Options.Clone()); stackTop.AddFirstChild(newNode); Stack.Push(newNode); } }
private IToken ErrorRecovery(HashSet <string> stopTokens = null, string avoidedToken = null) { if (!GrammarObject.Options.IsSet(ParsingOption.RECOVERY)) { Log.Add(Message.Error( $"Возобновление разбора в случае ошибки отключено", LexingStream.CurrentToken.Location.Start )); return(Lexer.CreateToken(Grammar.ERROR_TOKEN_NAME)); } if (!PositionsWhereRecoveryStarted.Add(LexingStream.CurrentIndex)) { Log.Add(Message.Error( $"Возобновление разбора невозможно: восстановление в позиции токена {this.GetTokenInfoForMessage(LexingStream.CurrentToken)} уже проводилось", LexingStream.CurrentToken.Location.Start )); return(Lexer.CreateToken(Grammar.ERROR_TOKEN_NAME)); } Log.Add(Message.Warning( $"Процесс восстановления запущен в позиции токена {this.GetTokenInfoForMessage(LexingStream.CurrentToken)}", LexingStream.CurrentToken.Location.Start )); var recoveryStartTime = DateTime.Now; PointLocation startLocation = null; PointLocation endLocation = null; var value = new List <string>(); var previouslyMatched = (Node)null; var derivationProds = new HashSet <PathFragment>(); var initialDerivationProds = new HashSet <PathFragment>(); /// Снимаем со стека состояния до тех пор, пока не находим состояние, /// в котором есть пункт A -> * Any ... do { if (Stack.CountSymbols > 0) { if (Stack.PeekSymbol().Location != null) { startLocation = Stack.PeekSymbol().Location.Start; if (endLocation == null) { endLocation = Stack.PeekSymbol().Location.End; } } value = Stack.PeekSymbol().GetValue() .Concat(value).ToList(); /// Запоминаем снятый со стека символ - это то, что было успешно распознано previouslyMatched = Stack.PeekSymbol(); } Stack.Pop(); NestingStack.Pop(); if (Stack.CountStates > 0) { /// Выбираем пункты, продукции которых потенциально могут участвовать /// в выводе текущего префикса из стартового символа initialDerivationProds = new HashSet <PathFragment>( Table.Items[Stack.PeekState()] .Where (i => /// Точка должна стоять перед символом, только что снятым со стека i.Next == previouslyMatched.Symbol && /// Если это не первая выборка, на предыдущем шаге в выборке должен был быть пункт /// с той же альтернативой, но точкой на один символ дальше (derivationProds.Count == 0 || derivationProds.Any(p => p.Alt.Equals(i.Alternative) && p.Pos == i.Position + 1)) ) .Select(i => new PathFragment { Alt = i.Alternative, Pos = i.Position }) ); derivationProds = new HashSet <PathFragment>(initialDerivationProds); var oldCount = 0; while (oldCount != derivationProds.Count) { oldCount = derivationProds.Count; /// Добавляем к списку пункты, порождающие уже добавленные пункты derivationProds.UnionWith(Table.Items[Stack.PeekState()] .Where(i => derivationProds.Any(p => p.Pos == 0 && p.Alt.NonterminalSymbolName == i.Next)) .Select(i => new PathFragment { Alt = i.Alternative, Pos = i.Position }) ); } } }while (Stack.CountStates > 0 && (derivationProds.Count == initialDerivationProds.Count || derivationProds.Except(initialDerivationProds).All(p => !GrammarObject.Options.IsSet(ParsingOption.RECOVERY, p.Alt[p.Pos])) || StartsWithAny(previouslyMatched) || IsUnsafeAny(stopTokens, avoidedToken)) ); if (Stack.CountStates > 0) { if (LexingStream.GetPairsCount() != NestingStack.Peek()) { var skippedBuffer = new List <IToken>(); /// Запоминаем токен, на котором произошла ошибка var currentToken = LexingStream.CurrentToken; /// Пропускаем токены, пока не поднимемся на тот же уровень вложенности, /// на котором раскрывали нетерминал LexingStream.GetNextToken(NestingStack.Peek(), out skippedBuffer); skippedBuffer.Insert(0, currentToken); value.AddRange(skippedBuffer.Select(t => t.Text)); endLocation = skippedBuffer.Last().Location.End; } /// Пытаемся пропустить Any в этом месте, /// Any захватывает участок с начала последнего /// снятого со стека символа до места восстановления var anyNode = NodeGenerator.Generate(Grammar.ANY_TOKEN_NAME); if (startLocation != null) { anyNode.SetLocation(startLocation, endLocation); } anyNode.Value = value.ToList(); Log.Add(Message.Warning( $"Найдено предполагаемое начало {Grammar.ANY_TOKEN_NAME}", anyNode.Location?.Start ?? LexingStream.CurrentToken.Location.Start )); Log.Add(Message.Warning( $"Попытка продолжить разбор в состоянии {Environment.NewLine}\t\t{Table.ToString(Stack.PeekState(), null, "\t\t")}\tв позиции токена {this.GetTokenInfoForMessage(LexingStream.CurrentToken)}", LexingStream.CurrentToken.Location.Start )); var token = SkipAny(anyNode, false); /// Если Any успешно пропустили и возобновили разбор, /// возвращаем токен, с которого разбор продолжается if (token.Name != Grammar.ERROR_TOKEN_NAME) { Statistics.RecoveryTimes += 1; Statistics.RecoveryTimeSpent += DateTime.Now - recoveryStartTime; return(token); } } Log.Add(Message.Error( $"Не удалось продолжить разбор", null )); return(Lexer.CreateToken(Grammar.ERROR_TOKEN_NAME)); }
protected override Node ParsingAlgorithm(string text) { Node root = null; /// Множество индексов токенов, на которых запускалось восстановление PositionsWhereRecoveryStarted = new HashSet <int>(); /// Создаём стек для уровней вложенности пар NestingStack = new Stack <int>(); /// Готовим лексер LexingStream = new ComplexTokenStream(GrammarObject, Lexer, text, Log); /// Читаем первую лексему из входного потока var token = LexingStream.GetNextToken(); /// Создаём стек Stack = new ParsingStack(); Stack.Push(0); NestingStack.Push(0); while (true) { if (token.Name == Grammar.ERROR_TOKEN_NAME) { break; } var currentState = Stack.PeekState(); if (EnableTracing && token.Name != Grammar.ERROR_TOKEN_NAME && token.Name != Grammar.ANY_TOKEN_NAME) { Log.Add(Message.Trace( $"Текущий токен: {this.GetTokenInfoForMessage(token)} | Стек: {Stack.ToString(GrammarObject)}", token.Location.Start )); } if (Table[currentState, token.Name].Count > 0) { if (token.Name == Grammar.ANY_TOKEN_NAME) { token = SkipAny(NodeGenerator.Generate(Grammar.ANY_TOKEN_NAME), true); /// Если при пропуске текста произошла ошибка, прерываем разбор if (token.Name == Grammar.ERROR_TOKEN_NAME) { break; } else { continue; } } var action = GetAction(currentState, token.Name); /// Если нужно произвести перенос if (action is ShiftAction) { var tokenNode = NodeGenerator.Generate(token.Name); tokenNode.SetValue(token.Text); tokenNode.SetLocation(token.Location.Start, token.Location.End); var shift = (ShiftAction)action; /// Вносим в стек новое состояние Stack.Push(tokenNode, shift.TargetItemIndex); NestingStack.Push(LexingStream.GetPairsCount()); if (EnableTracing) { Log.Add(Message.Trace( $"Перенос", token.Location.Start )); } token = LexingStream.GetNextToken(); } /// Если нужно произвести свёртку else if (action is ReduceAction reduce) { var parentNode = NodeGenerator.Generate(reduce.ReductionAlternative.NonterminalSymbolName); /// Снимаем со стека символы ветки, по которой нужно произвести свёртку for (var i = 0; i < reduce.ReductionAlternative.Count; ++i) { parentNode.AddFirstChild(Stack.PeekSymbol()); Stack.Pop(); NestingStack.Pop(); } currentState = Stack.PeekState(); /// Кладём на стек состояние, в которое нужно произвести переход Stack.Push( parentNode, Table.Transitions[currentState][reduce.ReductionAlternative.NonterminalSymbolName] ); NestingStack.Push(LexingStream.GetPairsCount()); if (EnableTracing) { Log.Add(Message.Trace( $"Свёртка по правилу {GrammarObject.Userify(reduce.ReductionAlternative)} -> {GrammarObject.Userify(reduce.ReductionAlternative.NonterminalSymbolName)}", token.Location.Start )); } continue; } else if (action is AcceptAction) { root = Stack.PeekSymbol(); break; } } else if (token.Name == Grammar.ANY_TOKEN_NAME) { Log.Add(Message.Warning( $"Неожиданный символ {this.GetTokenInfoForMessage(LexingStream.CurrentToken)} для состояния{Environment.NewLine}\t\t" + Table.ToString(Stack.PeekState(), null, "\t\t"), LexingStream.CurrentToken.Location.Start )); token = ErrorRecovery(); } else { /// Если встретился неожиданный токен, но он в списке пропускаемых if (GrammarObject.Options.IsSet(ParsingOption.SKIP, token.Name)) { token = LexingStream.GetNextToken(); } else { if (EnableTracing) { Log.Add(Message.Trace( $"Попытка трактовать текущий токен как начало участка, соответствующего Any", token.Location.Start )); } token = Lexer.CreateToken(Grammar.ANY_TOKEN_NAME); } } } if (root != null) { TreePostProcessing(root); if (LexingStream.CustomBlocks?.Count > 0) { var visitor = new InsertCustomBlocksVisitor(GrammarObject, LexingStream.CustomBlocks); root.Accept(visitor); root = visitor.Root; foreach (var block in visitor.CustomBlocks) { Log.Add(Message.Error( $"Блок \"{block.Start.Value[0]}\" прорезает несколько сущностей программы или находится в области, " + $"не учитываемой при синтаксическом анализе", block.Start.Location.Start )); } } } return(root); }
private IToken SkipAny(Node anyNode, bool enableRecovery) { var nestingCopy = LexingStream.GetPairsState(); var token = LexingStream.CurrentToken; var tokenIndex = LexingStream.CurrentIndex; var rawActions = Table[Stack.PeekState(), Grammar.ANY_TOKEN_NAME]; if (EnableTracing) { Log.Add(Message.Trace( $"Инициирован пропуск Any | Стек: {Stack.ToString(GrammarObject)} | Состояние: {Environment.NewLine}\t\t" + Table.ToString(Stack.PeekState(), null, "\t\t"), token.Location.Start )); } /// Пока по Any нужно производить свёртки (ячейка таблицы непуста и нет конфликтов) while (rawActions.Count == 1 && rawActions.First() is ReduceAction) { var reduce = (ReduceAction)rawActions.First(); var parentNode = NodeGenerator.Generate(reduce.ReductionAlternative.NonterminalSymbolName); /// Снимаем со стека символы ветки, по которой нужно произвести свёртку for (var i = 0; i < reduce.ReductionAlternative.Count; ++i) { parentNode.AddFirstChild(Stack.PeekSymbol()); Stack.Pop(); NestingStack.Pop(); } /// Кладём на стек состояние, в которое нужно произвести переход Stack.Push( parentNode, Table.Transitions[Stack.PeekState()][reduce.ReductionAlternative.NonterminalSymbolName] ); NestingStack.Push(LexingStream.GetPairsCount()); rawActions = Table[Stack.PeekState(), Grammar.ANY_TOKEN_NAME]; } /// Берём опции из нужного вхождения Any var marker = Table.Items[Stack.PeekState()].First(i => i.Next == Grammar.ANY_TOKEN_NAME); anyNode.Options = marker.Alternative[marker.Position].Options; /// Производим перенос var shift = (ShiftAction)rawActions.Where(a => a is ShiftAction).Single(); /// Вносим в стек новое состояние Stack.Push(anyNode, shift.TargetItemIndex); NestingStack.Push(LexingStream.GetPairsCount()); if (EnableTracing) { Log.Add(Message.Trace( $"Поиск окончания последовательности, соответствующей Any | Стек: {Stack.ToString(GrammarObject)} | Состояние: {Environment.NewLine}\t\t" + Table.ToString(Stack.PeekState(), null, "\t\t"), token.Location.Start )); } var stopTokens = GetStopTokens(anyNode.Options, Stack.PeekState()); var ignorePairs = anyNode.Options.AnyOptions.ContainsKey(AnyOption.IgnorePairs); var startLocation = anyNode.Location?.Start ?? token.Location.Start; var endLocation = anyNode.Location?.End; var anyLevel = LexingStream.GetPairsCount(); /// Пропускаем токены, пока не найдём тот, для которого /// в текущем состоянии нужно выполнить перенос или свёртку while (!stopTokens.Contains(token.Name) && (ignorePairs || LexingStream.CurrentTokenDirection != Direction.Up) && !anyNode.Options.Contains(AnyOption.Avoid, token.Name) && token.Name != Grammar.EOF_TOKEN_NAME && token.Name != Grammar.ERROR_TOKEN_NAME) { anyNode.Value.Add(token.Text); endLocation = token.Location.End; if (ignorePairs) { token = LexingStream.GetNextToken(); } else { token = LexingStream.GetNextToken(anyLevel, out List <IToken> skippedBuffer); if (skippedBuffer.Count > 0) { anyNode.Value.AddRange(skippedBuffer.Select(t => t.Text)); endLocation = skippedBuffer.Last().Location.End; } } } if (endLocation != null) { anyNode.SetLocation(startLocation, endLocation); } if (token.Name == Grammar.ERROR_TOKEN_NAME) { return(token); } /// Если дошли до конца входной строки, и это было не по плану if (!stopTokens.Contains(token.Name)) { if (enableRecovery) { var message = Message.Trace( $"Ошибка при пропуске {Grammar.ANY_TOKEN_NAME}: неожиданный токен {GrammarObject.Userify(token.Name)}, ожидался один из токенов {String.Join(", ", stopTokens.Select(t => GrammarObject.Userify(t)))}", token.Location.Start ); if (GrammarObject.Options.IsSet(ParsingOption.RECOVERY)) { ++Statistics.RecoveryTimesAny; Statistics.LongestRollback = Math.Max(Statistics.LongestRollback, LexingStream.CurrentIndex - tokenIndex); message.Type = MessageType.Warning; Log.Add(message); LexingStream.MoveTo(tokenIndex, nestingCopy); return(ErrorRecovery(stopTokens, anyNode.Options.Contains(AnyOption.Avoid, token.Name) ? token.Name : null)); } else { message.Type = MessageType.Error; Log.Add(message); return(Lexer.CreateToken(Grammar.ERROR_TOKEN_NAME)); } } else { Log.Add(Message.Error( $"Ошибка при пропуске {Grammar.ANY_TOKEN_NAME} в процессе восстановления: неожиданный токен {GrammarObject.Userify(token.Name)}, ожидался один из токенов {String.Join(", ", stopTokens.Select(t => GrammarObject.Userify(t)))}", token.Location.Start )); return(Lexer.CreateToken(Grammar.ERROR_TOKEN_NAME)); } } return(token); }
/// <summary> /// LL(1) разбор /// </summary> /// <returns> /// Корень дерева разбора /// </returns> protected override Node ParsingAlgorithm(string text) { /// Контроль вложенностей пар NestingLevel = new Dictionary <Node, int>(); PositionsWhereRecoveryStarted = new HashSet <int>(); /// Готовим лексер и стеки LexingStream = new ComplexTokenStream(GrammarObject, Lexer, text, Log); Stack = new Stack <Node>(); /// Кладём на стек стартовый символ var root = NodeGenerator.Generate(GrammarObject.StartSymbol); Stack.Push(NodeGenerator.Generate(Grammar.EOF_TOKEN_NAME)); Stack.Push(root); /// Читаем первую лексему из входного потока var token = LexingStream.GetNextToken(); /// Пока не прошли полностью правило для стартового символа while (Stack.Count > 0) { if (token.Name == Grammar.ERROR_TOKEN_NAME) { break; } var stackTop = Stack.Peek(); if (EnableTracing) { Log.Add(Message.Trace( $"Текущий токен: {this.GetTokenInfoForMessage(token)}\t |\t Стек: {StackString}", LexingStream.CurrentToken.Location.Start )); } /// Если символ на вершине стека совпадает с текущим токеном if (stackTop.Symbol == token.Name) { if (token.Name == Grammar.ANY_TOKEN_NAME) { token = SkipAny(NodeGenerator.Generate(Grammar.ANY_TOKEN_NAME), true); } else { var node = Stack.Pop(); node.SetLocation(token.Location.Start, token.Location.End); node.SetValue(token.Text); token = LexingStream.GetNextToken(); } continue; } /// Если на вершине стека нетерминал, выбираем альтернативу по таблице if (GrammarObject[stackTop.Symbol] is NonterminalSymbol) { var alternatives = Table[stackTop.Symbol, token.Name]; if (alternatives.Count > 0) { if (token.Name == Grammar.ANY_TOKEN_NAME) { /// Поддерживаем свойство immediate error detection для Any var runtimeFirst = Stack.Select(e => e.Symbol).ToList(); if (GrammarObject.First(runtimeFirst).Contains(Grammar.ANY_TOKEN_NAME)) { token = SkipAny(NodeGenerator.Generate(Grammar.ANY_TOKEN_NAME), true); } else { Log.Add(Message.Warning( $"Неожиданный символ {this.GetTokenInfoForMessage(LexingStream.CurrentToken)}, ожидался один из следующих символов: {String.Join(", ", runtimeFirst.Select(t => GrammarObject.Userify(t)))}", token.Location.Start )); token = ErrorRecovery(); } } else { ApplyAlternative(alternatives[0]); } continue; } } /// Если не смогли ни сопоставить текущий токен с терминалом на вершине стека, /// ни найти ветку правила для нетерминала на вершине стека if (token.Name == Grammar.ANY_TOKEN_NAME) { Log.Add(Message.Warning( GrammarObject.Tokens.ContainsKey(stackTop.Symbol) ? $"Неожиданный символ {this.GetTokenInfoForMessage(LexingStream.CurrentToken)}, ожидался символ {GrammarObject.Userify(stackTop.Symbol)}" : $"Неожиданный символ {this.GetTokenInfoForMessage(LexingStream.CurrentToken)}, ожидался один из следующих символов: {String.Join(", ", Table[stackTop.Symbol].Where(t => t.Value.Count > 0).Select(t => GrammarObject.Userify(t.Key)))}", LexingStream.CurrentToken.Location.Start )); token = ErrorRecovery(); } /// Если непонятно, что делать с текущим токеном, и он конкретный /// (не Any), заменяем его на Any else { /// Если встретился неожиданный токен, но он в списке пропускаемых if (GrammarObject.Options.IsSet(ParsingOption.SKIP, token.Name)) { token = LexingStream.GetNextToken(); } else { Log.Add(Message.Trace( $"Попытка трактовать текущий токен как начало участка, соответствующего Any", token.Location.Start )); token = Lexer.CreateToken(Grammar.ANY_TOKEN_NAME); } } } TreePostProcessing(root); if (LexingStream.CustomBlocks?.Count > 0) { var visitor = new InsertCustomBlocksVisitor(GrammarObject, LexingStream.CustomBlocks); root.Accept(visitor); root = visitor.Root; foreach (var block in visitor.CustomBlocks) { Log.Add(Message.Error( $"Блок \"{block.Start.Value[0]}\" прорезает несколько сущностей программы или находится в области, " + $"не учитываемой при синтаксическом анализе", block.Start.Location.Start )); } } return(root); }
private IToken ErrorRecovery(HashSet <string> stopTokens = null, string avoidedToken = null) { if (!GrammarObject.Options.IsSet(ParsingOption.RECOVERY)) { Log.Add(Message.Error( $"Возобновление разбора в случае ошибки отключено", LexingStream.CurrentToken.Location.Start )); return(Lexer.CreateToken(Grammar.ERROR_TOKEN_NAME)); } if (!PositionsWhereRecoveryStarted.Add(LexingStream.CurrentIndex)) { Log.Add(Message.Error( $"Возобновление разбора невозможно: восстановление в позиции токена {this.GetTokenInfoForMessage(LexingStream.CurrentToken)} уже проводилось", LexingStream.CurrentToken.Location.Start )); return(Lexer.CreateToken(Grammar.ERROR_TOKEN_NAME)); } Log.Add(Message.Warning( $"Процесс восстановления запущен в позиции токена {this.GetTokenInfoForMessage(LexingStream.CurrentToken)}", LexingStream.CurrentToken.Location.Start )); var recoveryStartTime = DateTime.Now; /// То, что мы хотели разобрать, и не смогли var currentNode = Stack.Pop(); /// Поднимаемся по уже построенной части дерева, пока не встретим /// пригодный для восстановления нетерминал. do { if (currentNode.Parent != null) { var childIndex = currentNode.Parent.Children.IndexOf(currentNode); for (var i = 0; i < currentNode.Parent.Children.Count - childIndex - 1; ++i) { Stack.Pop(); } } /// Переходим к родителю currentNode = currentNode.Parent; } /// Ищем дальше, если while (currentNode != null && ( /// текущий символ не входит в список тех, на которых можно восстановиться, или !GrammarObject.Options.IsSet(ParsingOption.RECOVERY, currentNode.Symbol) || /// при разборе соответствующей сущности уже пошли по Any-ветке ParsedStartsWithAny(currentNode) || /// ошибка произошла на таком же Any IsUnsafeAny(stopTokens, avoidedToken, currentNode) )); if (currentNode != null) { List <IToken> skippedBuffer; if (LexingStream.GetPairsCount() != NestingLevel[currentNode]) { var currentToken = LexingStream.CurrentToken; /// Пропускаем токены, пока не поднимемся на тот же уровень вложенности, /// на котором раскрывали нетерминал LexingStream.GetNextToken(NestingLevel[currentNode], out skippedBuffer); skippedBuffer.Insert(0, currentToken); } else { skippedBuffer = new List <IToken>(); } var anyNode = NodeGenerator.Generate(Grammar.ANY_TOKEN_NAME); anyNode.Value = currentNode.GetValue(); anyNode.Value.AddRange(skippedBuffer.Select(t => t.Text)); if (currentNode.Location != null) { anyNode.SetLocation(currentNode.Location.Start, currentNode.Location.End); } currentNode.ResetChildren(); Stack.Push(currentNode); if (skippedBuffer.Count > 0) { anyNode.SetLocation( anyNode.Location?.Start ?? skippedBuffer[0].Location.Start, skippedBuffer.Last().Location.End ); } Log.Add(Message.Warning( $"Найдено предполагаемое начало {Grammar.ANY_TOKEN_NAME}", anyNode.Location?.Start ?? LexingStream.CurrentToken.Location.Start )); Log.Add(Message.Warning( $"Попытка продолжить разбор на нетерминале {GrammarObject.Userify(currentNode.Symbol)} в позиции токена {this.GetTokenInfoForMessage(LexingStream.CurrentToken)}", LexingStream.CurrentToken.Location.Start )); /// Пытаемся пропустить Any в этом месте var token = SkipAny(anyNode, false); /// Если Any успешно пропустили и возобновили разбор, /// возвращаем токен, с которого разбор продолжается if (token.Name != Grammar.ERROR_TOKEN_NAME) { Log.Add(Message.Warning( $"Произведено восстановление на уровне {GrammarObject.Userify(currentNode.Symbol)}, разбор продолжен с токена {this.GetTokenInfoForMessage(token)}", token.Location.Start )); Statistics.RecoveryTimes += 1; Statistics.RecoveryTimeSpent += DateTime.Now - recoveryStartTime; return(token); } } Log.Add(Message.Error( $"Не удалось продолжить разбор", null )); return(Lexer.CreateToken(Grammar.ERROR_TOKEN_NAME)); }