Jak napisać Parser w C#? [zamknięte]

Jak napisać Parser (recursive Descent?) w C#? Na razie chcę tylko prosty parser, który parsuje wyrażenia arytmetyczne (i odczytuje zmienne?). Chociaż później zamierzam napisać parser xml i html (w celach edukacyjnych). Robię to ze względu na szeroki zakres rzeczy, w których parsery są przydatne: tworzenie stron internetowych, interpretery języków programowania, narzędzia wewnętrzne, silniki gier, Edytory Map i płytek itp. Jaka jest więc podstawowa teoria pisania parserów i jak zaimplementować w C#? Czy C# jest właściwym językiem dla parserów (kiedyś napisałem prosty parser arytmetyczny w C++ i był wydajny. Czy kompilacja JIT okaże się równie dobra?). Wszelkie pomocne zasoby i artykuły. A co najlepsze przykłady kodu (lub linki do przykładów kodu).

Uwaga: z ciekawości, czy ktoś odpowiedział na to pytanie zaimplementował kiedyś parser w C#?

Author: Kirill Kobelev, 2011-09-11

7 answers

Zaimplementowałem kilka parserów w C# - ręcznie pisanych i generowanych narzędzi.

Bardzo dobrym tutorialem wprowadzającym na temat parsowania w ogóle jest zbudujmy kompilator - pokazuje, jak zbudować rekurencyjny Parser zejścia; a pojęcia są łatwo przetłumaczone z jego języka (myślę, że był to Pascal) do C# dla każdego kompetentnego programisty. To nauczy Cię, jak działa rekurencyjny Parser descent, ale jest całkowicie niepraktyczne, aby napisać pełny Parser języka programowania przez ręka.

Powinieneś zajrzeć do niektórych narzędzi, aby wygenerować kod dla Ciebie-jeśli jesteś zdecydowany napisać klasyczny rekurencyjny Parser zejścia (TinyPG, Coco / R, ironia ). Należy pamiętać, że istnieją inne sposoby zapisu parserów, które zwykle działają lepiej - i mają łatwiejsze definicje(np. tdop parsing lub monadic Parsing ).

W temacie czy C# nadaje się do zadania - C# ma jedne z najlepszych bibliotek tekstowych tam. Wiele parserów dzisiaj (w innych językach) ma obsceniczną ilość kodu do radzenia sobie z Unicode itp. Nie będę komentował zbyt wiele kodu JITted, ponieważ może być dość religijny - jednak powinieneś być w porządku. Ironjs jest dobrym przykładem parsera / runtime na CLR (mimo że jest napisany w F#), a jego wydajność jest po prostu nieśmiała od Google V8.

Uwaga na marginesie: parsery znaczników są zupełnie inne w porównaniu do parserów językowych - są, w większość przypadków, pisanych ręcznie - i na poziomie skanera / parsera jest bardzo prosta; zazwyczaj nie są to rekurencyjne opadanie - a zwłaszcza w przypadku XML lepiej jest, jeśli nie piszesz rekurencyjnego parsera opadania (aby uniknąć przepełnienia stosu, a ponieważ "płaski" parser może być używany w trybie SAX/push).

 78
Author: Jonathan Dickinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-25 14:49:32

Sprache jest potężnym, ale lekkim frameworkiem do pisania parserów w .NET. istnieje również Pakiet Sprache NuGet . Aby dać ci pomysł na Framework tutaj jest jeden z próbek , które mogą parsować proste wyrażenie arytmetyczne do drzewa wyrażeń. NET. Powiedziałbym, że całkiem niesamowite.

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));
    }
}
 16
Author: Martin Liversage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-23 20:46:58

C # jest prawie przyzwoitym językiem funkcyjnym, więc implementacja w nim czegoś takiego jak Parsec nie jest taka wielka. Oto jeden z przykładów jak to zrobić: http://jparsec.codehaus.org/NParsec + Tutorial

Możliwe jest również zaimplementowanie packrat, w bardzo podobny sposób, ale tym razem zachowując globalny stan parsowania, zamiast robić czysto funkcjonalne rzeczy. W mojej (bardzo podstawowej i doraźnej) realizacji było to w miarę szybkie, ale z oczywiście generator kodu jak to {[2] } musi działać lepiej.

 3
Author: SK-logic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-11 18:55:58

Wiem, że jestem trochę spóźniony, ale właśnie opublikowałem bibliotekę parsera/gramatyki / generatora AST o nazwie Ve Parser. znajdziesz go pod adresem http://veparser.codeplex.com lub dodać do swojego projektu wpisując 'Install-Package veparser' w konsoli Menedżera pakietów. Ta Biblioteka jest rodzajem rekurencyjnego parsera opadania, który ma być łatwy w użyciu i elastyczny. Ponieważ jego źródło jest dostępne dla ciebie, możesz dowiedzieć się z jego kodów źródłowych. Mam nadzieję, że to pomoże.

 2
Author: 000,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-16 09:39:16

Moim zdaniem, istnieje lepszy sposób implementacji parserów niż tradycyjne metody, który skutkuje prostszym i łatwiejszym do zrozumienia kodem, a zwłaszcza ułatwia rozszerzenie dowolnego języka, który analizujesz, po prostu podłączając nową klasę w bardzo obiektowy sposób. Jeden artykuł z większej serii, który napisałem skupia się na tej metodzie parsowania, a Pełny kod źródłowy jest dołączony do C# 2.0 parser: http://www.codeproject.com/Articles/492466/Object-Oriented-Parsing-Breaking-With-Tradition-Pa

 1
Author: Ken Beckett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-17 16:11:11

Cóż... od czego zacząć....

Po pierwsze, pisanie parsera, cóż, to bardzo szerokie stwierdzenie, szczególnie z pytaniem, które zadajesz.

Twoje pierwsze stwierdzenie brzmiało, że chcesz prostego arytmatycznego "parsera" , cóż technicznie nie jest to parser, to analizator leksykalny, podobny do tego, czego możesz użyć do tworzenia nowego języka. ( http://en.wikipedia.org/wiki/Lexical_analysis [[10]} ) rozumiem jednak dokładnie, gdzie zamieszanie z nimi jest to samo może wynikać. Ważne jest, aby pamiętać, że analiza leksykalna jest również to, co chcesz zrozumieć, jeśli zamierzasz pisać parsery językowe/Skryptowe, to jest ściśle nie parsowanie, ponieważ interpretujesz instrukcje w przeciwieństwie do korzystania z nich.

Wracając do pytania parsującego....

To właśnie będziesz robił, jeśli będziesz pobierał sztywno zdefiniowaną strukturę plików, aby wydobyć z niej informacje.

Ogólnie rzecz biorąc, nie musisz pisać parsera w przypadku XML / HTML, ponieważ jest ich już mnóstwo, a nawet jeśli parsujesz XML wyprodukowany przez. Net run time, to nawet nie musisz parsować, wystarczy "serializacja " i"de-serializacja".

W interesie nauki jednak, parsowanie XML (lub czegokolwiek podobnego jak html) jest bardzo proste w większości przypadków.

Jeśli zaczniemy od następującego XML:

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

Możemy załadować dane do Xelementu w następujący sposób:

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

Można wtedy uzyskać na element główny 'movies' za pomocą 'myXML.Root'

Ciekawsze jest jednak to, że możesz łatwo użyć Linq, aby uzyskać zagnieżdżone Tagi:

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

Da ci var Xelementów, z których każdy zawiera jeden'...'które można uzyskać przy użyciu czegoś takiego jak:

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

Jeśli chodzi o cokolwiek innego niż XML, jak struktury danych, to obawiam się, że będziesz musiał zacząć uczyć się sztuki wyrażeń regularnych, narzędzie takie jak "Regular Expression Coach" pomoże Ci imensly ( http://weitz.de/regex-coach/) lub jednym z bardziej uptodate podobnych narzędzi.

Musisz również zapoznać się z obiektami wyrażenia regularnego. NET, ( http://www.codeproject.com/KB/dotnet/regextutorial.aspx ) powinno dać Ci dobrą przewagę.

Kiedy już wiesz, jak działa twój Reg-ex, to w większości przypadków jest to prosty przypadek czytania w plikach po jednej linijce na raz i zrozumienia ich za pomocą jakiej metody czujesz się komfortowo z.

Dobre, darmowe źródło formatów plików dla prawie wszystkiego, co możesz sobie wyobrazić, można znaleźć na ( http://www.wotsit.org/ )

 0
Author: shawty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-11 10:02:52

Dla przypomnienia zaimplementowałem generator parsera w C# tylko dlatego, że nie mogłem znaleźć żadnego działającego poprawnie lub podobnego do YACC (patrz: http://sourceforge.net/projects/naivelangtools/).

Jednak po pewnym doświadczeniu z ANTLR zdecydowałem się na LALR zamiast LL. Wiem, że teoretycznie LL jest łatwiejsze do zaimplementowania (generator lub parser), ale po prostu nie mogę żyć ze stosem wyrażeń tylko po to, aby wyrazić priorytety operatorów (jak * idzie przed + w "2+5*3"). W Mówisz, że mult_expr jest wbudowany w add_expr, co nie wydaje mi się naturalne.

 0
Author: greenoldman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-20 19:54:52