Как написать парсер на C#? [закрытый]

Как мне написать парсер (рекурсивный спуск?) в C#? Пока мне просто нужен простой парсер, который анализирует арифметические выражения (и читает переменные?). Хотя позже я намерен написать синтаксический анализатор xml и html (для учебных целей). Я делаю это из-за широкого спектра вещей, в которых полезны Парсеры: Веб-разработка, интерпретаторы языков программирования, внутренние инструменты, игровые движки, редакторы карт и плиток и т. д. Итак, какова основная теория написания синтаксических анализаторов и как я реализовать один в C#? Является ли C# правильным языком для парсеров (однажды я написал простой арифметический парсер на C++, и он был эффективным. Будет ли компиляция JIT одинаково хороша?). Любые полезные ресурсы и статьи. И лучше всего, примеры кода (или ссылки на примеры кода).

Примечание: из любопытства, кто-нибудь, отвечая на этот вопрос, когда-либо реализовал парсер в C#?

7 ответов


я реализовал несколько синтаксических анализаторов в C# - написанных вручную и созданных инструментах.

очень хороший вводный учебник по синтаксическому анализу в целом-это давайте построим компилятор - он демонстрирует, как построить рекурсивный парсер спуска; и понятия легко переводятся с его языка (я думаю, что это был Паскаль) на C# для любого компетентного разработчика. Это научит вас, как работает рекурсивный парсер спуска, но совершенно непрактично писать полное Программирование синтаксический анализатор языка вручную.

вы должны изучить некоторые инструменты для создания кода для вас - если вы намерены написать классический рекурсивный спуск парсер (TinyPG, Coco / R, Ирония). Имейте в виду, что есть и другие способы написания синтаксических анализаторов, которые обычно работают лучше - и имеют более простые определения (например,TDOP синтаксический анализ или Монадическом Разбора).

по вопросу о том, C# подходит для этой задачи - у C# есть некоторые из лучших текстовых библиотек. Многие Парсеры сегодня (на других языках)имеют непристойное количество кода для работы с Unicode и т. д. Я не буду слишком много комментировать JITted код, потому что он может стать довольно религиозным - однако вы должны быть в порядке. IronJS является хорошим примером синтаксического анализатора / среды выполнения на CLR (даже если он написан на F#), и его производительность просто стесняется Google V8.

Примечание: разметка Парсеры-это совершенно разные звери по сравнению с языковыми парсерами - они в большинстве случаев написаны от руки - и на уровне сканера / парсера очень просты; они обычно не рекурсивный спуск - и особенно в случае XML лучше, если вы не пишете рекурсивный парсер спуска (чтобы избежать переполнения стека, и потому что "плоский" парсер можно использовать в режиме SAX/push).


Sprache - это мощный, но легкий фреймворк для написания синтаксических анализаторов .Сеть. Существует также пакет Sprache NuGet. Чтобы дать вам представление о структуре, вот один из образцы это может анализировать простое арифметическое выражение в дереве выражений .NET. Довольно удивительно, я бы сказал.

using System;
using System.Linq.Expressions;
using Sprache;

namespace LinqyCalculator
{
    static class ExpressionParser
    {
        public static Expression<Func<decimal>> ParseExpression(string text)
        {
            return Lambda.Parse(text);
        }

        static Parser<ExpressionType> Operator(string op, ExpressionType opType)
        {
            return Parse.String(op).Token().Return(opType);
        }

        static readonly Parser<ExpressionType> Add = Operator("+", ExpressionType.AddChecked);
        static readonly Parser<ExpressionType> Subtract = Operator("-", ExpressionType.SubtractChecked);
        static readonly Parser<ExpressionType> Multiply = Operator("*", ExpressionType.MultiplyChecked);
        static readonly Parser<ExpressionType> Divide = Operator("/", ExpressionType.Divide);

        static readonly Parser<Expression> Constant =
            (from d in Parse.Decimal.Token()
             select (Expression)Expression.Constant(decimal.Parse(d))).Named("number");

        static readonly Parser<Expression> Factor =
            ((from lparen in Parse.Char('(')
              from expr in Parse.Ref(() => Expr)
              from rparen in Parse.Char(')')
              select expr).Named("expression")
             .XOr(Constant)).Token();

        static readonly Parser<Expression> Term = Parse.ChainOperator(Multiply.Or(Divide), Factor, Expression.MakeBinary);

        static readonly Parser<Expression> Expr = Parse.ChainOperator(Add.Or(Subtract), Term, Expression.MakeBinary);

        static readonly Parser<Expression<Func<decimal>>> Lambda =
            Expr.End().Select(body => Expression.Lambda<Func<decimal>>(body));
    }
}

C# - почти приличный функциональный язык, поэтому не так уж важно реализовать в нем что-то вроде Parsec. Вот один из примеров того, как это сделать:http://jparsec.codehaus.org/NParsec + учебник

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


Я знаю, что немного опоздал, но я только что опубликовал библиотеку генератора синтаксического анализатора/грамматики/AST с именем ve Parser. вы можете найти его в http://veparser.codeplex.com или добавьте в свой проект, введя "Install-Package veparser" в консоли диспетчера пакетов. Эта библиотека является своего рода рекурсивным анализатором спуска, который предназначен для простого в использовании и гибкого. Поскольку его источник доступен для вас, вы можете узнать из его исходных кодов. Надеюсь, это поможет.


на мой взгляд, есть лучший способ реализовать Парсеры, чем традиционные методы, которые приводят к более простому и понятному коду, и особенно облегчает расширение любого языка, который вы анализируете, просто подключив новый класс очень объектно-ориентированным способом. Одна статья из большой серии, которую я написал, посвящена этому методу анализа, и полный исходный код включен на C# 2.0 синтаксический анализатор: http://www.codeproject.com/Articles/492466/Object-Oriented-Parsing-Breaking-With-Tradition-Pa


хорошо... с чего начать?...

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

вашим вступительным заявлением было то , что вы хотели простой арифметический "парсер", ну технически это не парсер, это лексический анализатор, подобный тому, что вы можете использовать для создания нового языка. ( http://en.wikipedia.org/wiki/Lexical_analysis ) я понимаю, однако, где именно путаница в том, что это одно и то же, может возникнуть. Важно отметить, что лексический анализ также является тем, что вы хотите понять, если вы собираетесь писать синтаксические анализаторы языка/скрипта, это строго не синтаксический анализ, потому что вы интерпретируете инструкции, а не используете их.

вернемся к вопросу разбора....

это то, что вы будете делать, если вы берете жестко определенную файловую структуру для извлечения из нее информации.

In вообще вам действительно не нужно писать парсер для XML / HTML, потому что их уже много, и более того, если ваш синтаксический анализ XML производится во время выполнения .NET, вам даже не нужно анализировать, вам просто нужно "сериализовать" и "де-сериализовать".

в интересах обучения, однако, разбор XML (или что-то подобное, как html) в большинстве случаев очень прямо вперед.

если мы начнем со следующего XML:

    <movies>
      <movie id="1">
        <name>Tron</name>
      </movie>
      <movie id="2">
        <name>Tron Legacy</name>
      </movie>
    <movies>

мы можем загрузить данные в XElement следующим образом:

    XElement myXML = XElement.Load("mymovies.xml");

затем вы можете добраться до 'фильмы' корневой элемент с помощью ' myXML.Root'

более интересно, однако, вы можете легко использовать Linq для получения вложенных тегов:

    var myElements = from p in myXML.Root.Elements("movie")
                     select p;

даст вам var XElements, каждый из которых содержит один"..."который вы можете получить, используя что-то вроде:

    foreach(var v in myElements)
    {
      Console.WriteLine(string.Format("ID {0} = {1}",(int)v.Attributes["id"],(string)v.Element("movie"));
    }

для чего-либо еще, кроме XML, как структуры данных, то я боюсь, что вам придется начните изучать искусство регулярных выражений, такой инструмент, как "тренер регулярных выражений" поможет вам imensly ( http://weitz.de/regex-coach/) или один из более uptodate подобных инструментов.

вам также необходимо ознакомиться с объектами регулярных выражений .NET, (http://www.codeproject.com/KB/dotnet/regextutorial.aspx ) должно дать вам хорошую фору.

как только вы узнаете, как работает ваш reg-ex, в большинстве случаев это простой случай случай чтения в файлах по одной строке за раз и смысл их использования, с помощью которого вы чувствуете себя комфортно.

хороший бесплатный источник форматов файлов для почти всего, что вы можете себе представить, можно найти по адресу (http://www.wotsit.org/ )


для записи я реализовал генератор парсера в C# только потому, что я не мог найти работу должным образом или похожую на YACC (см.:http://sourceforge.net/projects/naivelangtools/).

однако после некоторого опыта работы с ANTLR я решил пойти с LALR вместо LL. Я знаю, что теоретически LL проще реализовать (генератор или парсер), но я просто не могу жить со стеком выражений, чтобы выразить приоритеты операторов (например,* идет перед + в "2+5*3"). В LL вы говорите, что mult_expr встроен в add_expr, что не кажется мне естественным.