«Lexer» для человека на C #

Я пытаюсь написать очень простой парсер в C #.

Мне нужен лексер – что-то, что позволяет связать регулярные выражения с токенами, поэтому он читает в регулярных выражениях и возвращает мне символы.

Мне кажется, что я должен использовать Regex для выполнения тяжелой работы, но я не вижу простого способа сделать это. Во-первых, Regex работает только на строках, а не на streamах (почему это??!?).

В принципе, я хочу реализовать следующий интерфейс:

interface ILexer : IDisposable { ///  /// Return true if there are more tokens to read ///  bool HasMoreTokens { get; } ///  /// The actual contents that matched the token ///  string TokenContents { get; } ///  /// The particular token in "tokenDefinitions" that was matched (eg "STRING", "NUMBER", "OPEN PARENS", "CLOSE PARENS" ///  object Token { get; } ///  /// Move to the next token ///  void Next(); } interface ILexerFactory { ///  /// Create a Lexer for converting a stream of characters into tokens ///  /// TextReader that supplies the underlying stream /// A dictionary from regular expressions to their "token identifers" /// The lexer ILexer CreateLexer(TextReader reader, IDictionary tokenDefinitions); } 

Итак, pluz отправил codz …
Нет, серьезно, я собираюсь начать писать реализацию вышеупомянутого интерфейса, но мне трудно поверить, что в .NET (2.0) уже нет простого способа сделать это.

Итак, любые предложения по простому способу сделать выше? (Кроме того, мне не нужны «генераторы кода». Производительность не важна для этой вещи, и я не хочу вводить какую-либо сложность в процесс сборки.)

Первоначальная версия, которую я опубликовал здесь в качестве ответа, была связана с тем, что она работала только тогда, когда было найдено более одного «регулярного выражения», совпадающего с текущим выражением. То есть, как только только одно регулярное выражение соответствует, оно вернет токен, тогда как большинство людей хотят, чтобы Regex был «жадным». Это особенно касается таких вещей, как «цитируемые строки».

Единственное решение, которое сидит поверх Regex, – это чтение строк за строкой (что означает, что вы не можете иметь токены, которые охватывают несколько строк). Я могу жить с этим – это, в конце концов, лексер бедного человека! Кроме того, обычно полезно получить информацию о номере линии из Lexer в любом случае.

Итак, вот новая версия, которая решает эти проблемы. Кредит также относится к этому

 public interface IMatcher { ///  /// Return the number of characters that this "regex" or equivalent /// matches. ///  /// The text to be matched /// The number of characters that matched int Match(string text); } sealed class RegexMatcher : IMatcher { private readonly Regex regex; public RegexMatcher(string regex) => this.regex = new Regex(string.Format("^{0}", regex)); public int Match(string text) { var m = regex.Match(text); return m.Success ? m.Length : 0; } public override string ToString() => regex.ToString(); } public sealed class TokenDefinition { public readonly IMatcher Matcher; public readonly object Token; public TokenDefinition(string regex, object token) { this.Matcher = new RegexMatcher(regex); this.Token = token; } } public sealed class Lexer : IDisposable { private readonly TextReader reader; private readonly TokenDefinition[] tokenDefinitions; private string lineRemaining; public Lexer(TextReader reader, TokenDefinition[] tokenDefinitions) { this.reader = reader; this.tokenDefinitions = tokenDefinitions; nextLine(); } private void nextLine() { do { lineRemaining = reader.ReadLine(); ++LineNumber; Position = 0; } while (lineRemaining != null && lineRemaining.Length == 0); } public bool Next() { if (lineRemaining == null) return false; foreach (var def in tokenDefinitions) { var matched = def.Matcher.Match(lineRemaining); if (matched > 0) { Position += matched; Token = def.Token; TokenContents = lineRemaining.Substring(0, matched); lineRemaining = lineRemaining.Substring(matched); if (lineRemaining.Length == 0) nextLine(); return true; } } throw new Exception(string.Format("Unable to match against any tokens at line {0} position {1} \"{2}\"", LineNumber, Position, lineRemaining)); } public string TokenContents { get; private set; } public object Token { get; private set; } public int LineNumber { get; private set; } public int Position { get; private set; } public void Dispose() => reader.Dispose(); } 

Пример программы:

 string sample = @"( one (two 456 -43.2 "" \"" quoted"" ))"; var defs = new TokenDefinition[] { // Thanks to [steven levithan][2] for this great quoted string // regex new TokenDefinition(@"([""'])(?:\\\1|.)*?\1", "QUOTED-STRING"), // Thanks to http://www.regular-expressions.info/floatingpoint.html new TokenDefinition(@"[-+]?\d*\.\d+([eE][-+]?\d+)?", "FLOAT"), new TokenDefinition(@"[-+]?\d+", "INT"), new TokenDefinition(@"#t", "TRUE"), new TokenDefinition(@"#f", "FALSE"), new TokenDefinition(@"[*<>\?\-+/A-Za-z->!]+", "SYMBOL"), new TokenDefinition(@"\.", "DOT"), new TokenDefinition(@"\(", "LEFT"), new TokenDefinition(@"\)", "RIGHT"), new TokenDefinition(@"\s", "SPACE") }; TextReader r = new StringReader(sample); Lexer l = new Lexer(r, defs); while (l.Next()) Console.WriteLine("Token: {0} Contents: {1}", l.Token, l.TokenContents); 

Вывод:

 Token: LEFT Contents: ( Token: SPACE Contents: Token: SYMBOL Contents: one Token: SPACE Contents: Token: LEFT Contents: ( Token: SYMBOL Contents: two Token: SPACE Contents: Token: INT Contents: 456 Token: SPACE Contents: Token: FLOAT Contents: -43.2 Token: SPACE Contents: Token: QUOTED-STRING Contents: " \" quoted" Token: SPACE Contents: Token: RIGHT Contents: ) Token: RIGHT Contents: ) 

Если у вас нет очень нетрадиционной грамматики, я настоятельно рекомендую не сворачивать ваш собственный лексер / парсер.

Я обычно считаю, что lexer / parsers для C # действительно не хватает. Тем не менее, F # поставляется с fslex и fsyacc, которые вы можете узнать, как использовать в этом учебнике . Я написал несколько лексеров / парсеров в F # и использовал их на C #, и это очень легко сделать.

Я полагаю, что это не лексер / парсер бедных людей, потому что вам нужно изучить совершенно новый язык для начала, но это начало.

Это может быть излишним, но посмотрите на Ирония на CodePlex.

Irony – это набор для разработки языков на платформе .NET. Он использует гибкость и мощь языка c # и .NET Framework 3.5 для реализации совершенно новой и оптимизированной технологии построения компилятора. В отличие от большинства существующих решений yacc / lex-стиля, Irony не использует генерации кода сканера или парсера из грамматических спецификаций, написанных на специальном метаязыке. В Иронии грамматика целевого языка кодируется непосредственно в c #, используя перегрузку оператора для выражения конструкций грамматики. Модули сканера и парсера Иронии используют грамматику, закодированную как class c #, для управления процессом синтаксического анализа. См. Образец грамматики выражения для примера определения грамматики в classе c # и использования его в рабочем синтаксическом анализаторе.

Изменение моего первоначального ответа.

Взгляните на SharpTemplate , где есть синтаксические анализаторы для разных типов синтаксиса, например

 #foreach ($product in $Products) $product.Name #if ($product.Stock > 0) In stock #else Backordered #end  #end 

Он использует регулярные выражения для каждого типа токена:

 public class Velocity : SharpTemplateConfig { public Velocity() { AddToken(TemplateTokenType.ForEach, @"#(foreach|{foreach})\s+\(\s*(?[a-z_][a-z0-9_]*)\s+in\s+(?.*?)\s*\)", true); AddToken(TemplateTokenType.EndBlock, @"#(end|{end})", true); AddToken(TemplateTokenType.If, @"#(if|{if})\s+\((?.*?)\s*\)", true); AddToken(TemplateTokenType.ElseIf, @"#(elseif|{elseif})\s+\((?.*?)\s*\)", true); AddToken(TemplateTokenType.Else, @"#(else|{else})", true); AddToken(TemplateTokenType.Expression, @"\${(?.*?)}", false); AddToken(TemplateTokenType.Expression, @"\$(?[a-zA-Z_][a-zA-Z0-9_\[email protected]]*?)(?![a-zA-Z0-9_\[email protected]])", false); } } 

Что используется как это

 foreach (Match match in regex.Matches(inputString)) { ... switch (tokenMatch.TokenType) { case TemplateTokenType.Expression: { currentNode.Add(new ExpressionNode(tokenMatch)); } break; case TemplateTokenType.ForEach: { nodeStack.Push(currentNode); currentNode = currentNode.Add(new ForEachNode(tokenMatch)); } break; .... } .... } 

Он толкает и выскакивает из стека, чтобы сохранить состояние.

Malcolm Crowe имеет отличную реализацию LEX / YACC для C # здесь . Работает путем создания регулярных выражений для LEX …

Прямая загрузка

Можно использовать Flex и Bison для C #.

Исследователь из Университета Ирландии разработал частичную реализацию, которая может быть найдена по следующей ссылке: Flex / Bison для C #

Это определенно можно считать «бедным мужчиной lexer», поскольку у него все еще есть некоторые проблемы с его реализацией, такие как отсутствие препроцессора, проблемы с «болтающимся другим» случаем и т. Д.

Если вы посмотрите на ExpressionConverter в моей библиотеке WPF Converters , он имеет базовый лексинг и синтаксический анализ выражений C #. Никакого регулярного выражения, из памяти.

  • Регулярное выражение для соответствия 3 или более последовательным последовательным символам и последовательным идентичным символам
  • Поиск замещения регулярного выражения в Sublime Text 2
  • Замена всех не-буквенно-цифровых символов пустыми строками
  • Перекрывающиеся совпадения в R
  • Возможно ли, чтобы компьютер «научил» регулярное выражение примерами, предоставленными пользователем?
  • Возможно ли избежать метасимволов регулярных выражений с помощью sed
  • Получение частей URL (регулярное выражение)
  • Регулярное выражение для asp: RegularExpressionValidator с форматом MMddyy (выпуск високосного года)
  • Полное регулярное выражение для проверки номера телефона
  • Разделительная строка с символом трубы ("|")
  • замените \ n и \ r \ n в java
  • Interesting Posts

    Сравнение таблиц Excel

    Асинхронно ждать Task , чтобы завершить таймаут

    Как конвертировать WinXP + Apps на RAID0, чтобы использовать только один из двух дисков RAID?

    Полноэкранный UIView со строкой состояния и наложением панели навигации наверху

    Колонка заказа в Bootstrap 4

    Opencv виртуальная камера, вращающаяся / перевод для просмотра с высоты птичьего полета

    Статический вложенный class в Java, почему?

    Наглядно ли это влияет на то, какие языки могут быть сопоставлены регулярными выражениями?

    Выбор диапазона строк в Notepad ++

    Требуется ли для маршрутизатора 2,4 ГГц и 5 ГГц специальная Wi-Fi-карта?

    Как загрузить последние вкладки в Chrome с помощью файлов Last Tabs, Last Session?

    Брандмауэр или отказ материнской платы?

    Как работают развалы вложенных вертикальных краев?

    Почему в некоторых телефонах ошибка «Это приложение было построено с неправильной конфигурацией»?

    Symfony 2: множественное и динамическое подключение к базе данных

    Давайте будем гением компьютера.