/// <summary> /// Создать новый экземпляр парсера осуществляющего LL(1) разбор по таблице разбора. /// </summary> /// <param name="data">Строка принадлежность к граматике которой, необходимо определить.</param> /// <param name="startedRule">Стартовое правило грамматики.</param> internal Parser(string data, GrammarElement startedElement, Table table) { this.data = data; this.table = table; stack = new Stack <GrammarElement>(); stack.Push(startedElement); }
/// <summary> /// Развернуть элемент грамматики по направлющим символам. /// </summary> /// <param name="startingRule">Элемент грамматики, подлежащий развертыванию.</param> /// <param name="guideChars">Направляющие символы.</param> internal GrammarRulePart Unfold(GrammarElement startingRule, string guideChars) { if (!table.ContainsKey(startingRule)) { throw new Exception("Строка не пренадлежит грамматике."); } GrammarRulePart result = null; foreach (var key in table[startingRule].Keys) { if (key == "" && result == null) { result = table[startingRule][key]; } else if (key.Length <= guideChars.Length && key == guideChars.Substring(0, key.Length)) { result = table[startingRule][key]; break; } } if (result == null) { throw new Exception("Строка не пренадлежит грамматике."); } return(result); }
/// <summary> /// Дополняет сообщение об ошибке данными о номере строки и символа, в котором возникла ошибка. Также состоянием стека /// и оставшейся строкой разбора. /// </summary> /// <param name="mainMsg">Описание причины возникновения ошибки.</param> /// <param name="element">Елемент, на котором закончился разбор.</param> /// <param name="compareData">Оставшаяся, неразобранная строка.</param> private string GetExceptionMsg(string mainMsg, GrammarElement element, string compareData) { return($"{mainMsg + Environment.NewLine + Environment.NewLine}Исключение возникло при считывании символа или лексемы в " + $"строке №:{lineCounter}, символ №:{charCounter}.{Environment.NewLine + Environment.NewLine}Оставшаяся строка: " + $"{Environment.NewLine + compareData + Environment.NewLine + Environment.NewLine}Считан элемент со стека: " + $"{element + Environment.NewLine}Осталось на стеке: {string.Join(Environment.NewLine, stack.ToList())}"); }
/// <summary> /// Открывает нетерминал, проверяя возможные символьные совпадения, использую таблицу разбора. /// </summary> private void UnpackNonTerminal(GrammarElement element, string compareData) { try { ToStack(table.Unfold(element, compareData).Elements); } catch (Exception ex) { throw new Exception(GetExceptionMsg(ex.Message, element, compareData.ToString())); } }
/// <summary> /// Вызов прикрепленных к элементу грамматики действий. /// </summary> /// <param name="element">Элемент.</param> /// <param name="compareData">Оставшаяся строка.</param> /// <param name="parameters">Параметры действия.</param> private void ExecuteActions(GrammarElement element, string compareData, object parameters) { try { element.Actions.ForEach(a => a.Invoke(parameters)); } catch (Exception ex) { throw new Exception(GetExceptionMsg(ex.Message, element, compareData.ToString())); } }
/// <summary> /// Рекурсивный метод, находящий коллекцию нетерминалов грамматики. Все найденные результаты добавляются в коллекцию nonterminals. /// </summary> /// <param name="element">Текущий рассматриваемый элемент.</param> /// <param name="nonterminals">Коллекция нетерминалов.</param> private void FindNonterminal(GrammarElement element, List <GrammarElement> nonterminals) { foreach (var rulePart in element.Rule.Right) { foreach (var elem in rulePart.Elements) { if (elem.Type == ElementType.NonTerminal && !nonterminals.Contains(elem)) { nonterminals.Add(elem); FindNonterminal(elem, nonterminals); } } } }
/// <summary> /// Создать новый экземпляр грамматики. /// </summary> /// <param name="grammar">Строка с описанием грамматики.</param> /// <param name="specialSymbols">Специальные символы грамматики.</param> public Grammar(string grammar, SpecialSymbols specialSymbols, ActionsContainer actions) { this.specialSymbols = specialSymbols; foreach (var line in grammar.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) { Rules.Add(new GrammarRule(line, this.specialSymbols)); } foreach (var rule in Rules) { rule.BuildRightPart(Rules, actions); } StartedElement = new GrammarElement(Rules.FirstOrDefault()); }
/// <summary> /// Проверка следующего элемента на стеке. Если элемент терминал, то выполняется проверка на символьное /// совпадение с входной строкой. Если элемент нетерминал, то выполняется распаковка (открытие) элемента. /// </summary> private void NextElementCheck(GrammarElement element, StringBuilder compareData) { switch (element.Type) { case ElementType.NonTerminal: ExecuteActions(element, compareData.ToString(), null); UnpackNonTerminal(element, compareData.ToString()); break; case ElementType.Terminal: if (element.Characters.Length <= compareData.Length && element.Characters == compareData.ToString().Substring(0, element.Characters.Length)) { var data = compareData.ToString().Substring(0, element.Characters.Length); RefreshCounters(data); ExecuteActions(element, compareData.ToString(), data); compareData.Remove(0, element.Characters.Length); } else { throw new Exception(GetExceptionMsg("Данная строка не принадлежит граматике.", element, compareData.ToString())); } break; case ElementType.Range: if (compareData.Length > 0 && element.Characters.Contains(compareData.ToString().Substring(0, 1))) { var data = compareData.ToString().Substring(0, 1); RefreshCounters(data); ExecuteActions(element, compareData.ToString(), data); compareData.Remove(0, 1); } else { throw new Exception(GetExceptionMsg("Данная строка не принадлежит граматике.", element, compareData.ToString())); } break; case ElementType.Empty: ExecuteActions(element, compareData.ToString(), null); break; default: throw new Exception("Неизвестный тип элемента."); } }
/// <summary> /// Найти все варианты раскрытия переданного параметром нетерминала. Результат возвращается в /// виде словаря где TKey - направляющие символы, TValue - часть правила грамматики в которою /// раскроется нетерминал по направляющим символам TKey. /// </summary> private Dictionary <string, GrammarRulePart> FindUnfoldedWays(GrammarElement nonterm) { Dictionary <string, GrammarRulePart> result = new Dictionary <string, GrammarRulePart>(); foreach (var rulePart in nonterm.Rule.Right) { List <string> guideChars = new List <string>(); GetGuideChars(rulePart.Elements.First(), guideChars); foreach (var str in guideChars) { if (result.ContainsKey(str)) { throw new Exception("Ne LL1"); } result.Add(str, rulePart); } } return(result); }
/// <summary> /// Рекурсивно определяет все направляющие символы, которые может генерировать переданный элемент грамматики. /// Найденные результаты сохраняются в коллекцию guideChars. /// </summary> private void GetGuideChars(GrammarElement element, List <string> guideChars) { switch (element.Type) { case ElementType.NonTerminal: foreach (var rulePart in element.Rule.Right) { GetGuideChars(rulePart.Elements.First(), guideChars); } break; case ElementType.Terminal: if (!guideChars.Contains(element.Characters)) { guideChars.Add(element.Characters); } break; case ElementType.Range: foreach (var c in element.Characters) { if (!guideChars.Contains(c.ToString())) { guideChars.Add(c.ToString()); } } break; case ElementType.Empty: if (!guideChars.Contains("")) { guideChars.Add(""); } break; default: break; } }
public override bool Equals(object obj) { GrammarElement elem = (GrammarElement)obj; return(Type == elem.Type && Characters == elem.Characters && Rule == elem.Rule); }
/// <summary> /// Построить таблицу разбора по стартовому элементу грамматики. /// </summary> /// <param name="startedElement">Стартовый элемент грамматики.</param> internal Dictionary <GrammarElement, Dictionary <string, GrammarRulePart> > Build(GrammarElement startedElement) { Dictionary <GrammarElement, Dictionary <string, GrammarRulePart> > table = new Dictionary <GrammarElement, Dictionary <string, GrammarRulePart> >(); List <GrammarElement> nonterminals = new List <GrammarElement> { startedElement }; FindNonterminal(startedElement, nonterminals); foreach (var nonterm in nonterminals) { Dictionary <string, GrammarRulePart> map = FindUnfoldedWays(nonterm); table.Add(nonterm, map); } return(table); }