it-swarm.it

Come scrivere un parser in C #?

Come faccio a scrivere un parser (Discesa ricorsiva?) In C #? Per ora voglio solo un parser semplice che analizzi le espressioni aritmetiche (e legga le variabili?). Sebbene in seguito intendo scrivere un parser xml e html (per scopi di apprendimento). Lo sto facendo a causa della vasta gamma di elementi in cui i parser sono utili: sviluppo Web, programmazione interpreti di lingua, strumenti interni, motori di gioco, editor di mappe e riquadri, ecc. Quindi, qual è la teoria di base della scrittura dei parser e come implementare uno in C #? C # è la lingua giusta per i parser (una volta ho scritto un semplice parser aritmetico in C++ ed è stato efficiente. La compilazione JIT si dimostrerà altrettanto valida?). Eventuali risorse e articoli utili. E soprattutto, esempi di codice (o collegamenti a esempi di codice). 

Nota: per curiosità, qualcuno che ha risposto a questa domanda ha mai implementato un parser in C #?

59
ApprenticeHacker

Ho implementato diversi parser in C #: scritti a mano e generati da strumenti.

Un ottimo tutorial introduttivo sull'analisi in generale è Costruiamo un compilatore - mostra come costruire un parser di discesa ricorsivo; e i concetti sono facilmente traducibili dal suo linguaggio (penso che fosse Pascal) in C # per qualsiasi sviluppatore competente. Questo ti insegnerà come funziona un parser di discesa ricorsivo, ma è del tutto impossibile scrivere a mano un parser completo del linguaggio di programmazione.

Dovresti esaminare alcuni strumenti per generare il codice per te - se sei determinato a scrivere un parser di discesa ricorsivo classico ( TinyPG , Coco/R , Irony ). Tieni presente che esistono altri modi per scrivere i parser ora, che di solito hanno un rendimento migliore e hanno definizioni più semplici (ad es. TDOP parsing o Monadic Parsing ).

In merito al fatto che C # sia pronto per l'attività: C # ha alcune delle migliori librerie di testi disponibili. Molti parser oggi (in altre lingue) hanno un'oscena quantità di codice per gestire Unicode, ecc. Non commenterò troppo sul codice JITted perché può diventare piuttosto religioso - comunque dovresti stare bene. IronJS è un buon esempio di parser/runtime su CLR (anche se è scritto in F #) e le sue prestazioni sono solo timide di Google V8.

Nota a margine: I parser di markup sono bestie completamente diverse rispetto ai parser di lingua - sono, nella maggior parte dei casi, scritti a mano - e al livello scanner/parser molto semplici; di solito non sono discendenti ricorsivi, e specialmente nel caso di XML è meglio se non si scrive un parser di discesa ricorsiva (per evitare gli overflow dello stack, e perché un parser 'flat' può essere usato in modalità SAX/Push).

79

Sprache è un framework potente e leggero per la scrittura di parser in .NET. C'è anche un pacchetto Sprache NuGet . Per darvi un'idea del framework qui è uno dei samples che può analizzare una semplice espressione aritmetica in un albero di espressioni .NET. Piuttosto sorprendente, direi.

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));
    }
}
17

C # è quasi un linguaggio funzionale decente, quindi non è un grosso problema implementare qualcosa come Parsec. Ecco uno degli esempi su come farlo: http://jparsec.codehaus.org/NParsec+Tutorial

È anche possibile implementare un combinatore Packrat , in un modo molto simile, ma questa volta mantenendo uno stato di analisi globale da qualche parte invece di fare una pura funzionalità. Nella mia implementazione (molto basilare e ad hoc) era ragionevolmente veloce, ma ovviamente un generatore di codice come this deve funzionare meglio.

3
SK-logic

So che sono un po 'in ritardo, ma ho appena pubblicato una libreria di parser/grammar/generatore AST di nome Ve Parser. puoi trovarlo su http://veparser.codeplex.com o aggiungerlo al tuo progetto digitando "Install-Package veparser" nella Console di Gestione pacchetti. Questa libreria è una sorta di parser di discen- sione ricorsiva concepito per essere facile da usare e flessibile. Poiché la sua fonte è a tua disposizione, puoi imparare dai suoi codici sorgente. Spero possa essere d'aiuto.

2
000

Secondo me, c'è un modo migliore per implementare i parser rispetto ai metodi tradizionali che si traducono in un codice più semplice e più facile da capire, e soprattutto rende più facile estendere qualsiasi linguaggio si sta analizzando semplicemente inserendo una nuova classe in un oggetto molto modo orientato. Un articolo di una serie più grande che ho scritto si concentra su questo metodo di analisi e il codice sorgente completo è incluso per un parser C # 2.0: http://www.codeproject.com/Articles/492466/Object-Oriented- Parsing-Breaking-With-Tradition-Pa

1
Ken Beckett

Bene ... da dove cominciare?.

Prima di tutto, scrivendo un parser, beh questa è una frase molto ampia, specialmente con la domanda che stai facendo.

La tua dichiarazione di apertura era che volevi un semplice "parser" aritmetico, tecnicamente non è un parser, è un analizzatore lessicale, simile a quello che puoi usare per creare una nuova lingua. ( http://en.wikipedia.org/wiki/Lexical_analysis ) Capisco però esattamente da dove possa provenire la confusione di essere la stessa cosa. È importante notare che l'analisi lessicale è ANCHE quello che vorrete capire se scriverete anche parser di lingua/script, questo non è strettamente analogo perché state interpretando le istruzioni piuttosto che farne uso.

Torna alla domanda di analisi ....

Questo è quello che farai se prendi una struttura di file rigidamente definita per estrarre informazioni da esso.

In generale, non è necessario scrivere un parser per XML/HTML, perché ce ne sono già in giro e, a maggior ragione se si esegue l'analisi XML prodotta dal tempo di esecuzione .NET, non è nemmeno necessario parse, hai solo bisogno di "serializzare" e "de-serializzare".

Nell'interesse dell'apprendimento, tuttavia, l'analisi di XML (o qualcosa di simile come html) è molto semplice nella maggior parte dei casi.

se iniziamo con il seguente XML:

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

possiamo caricare i dati in un XElement come segue:

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

puoi quindi ottenere l'elemento radice 'movies' usando 'myXML.Root'

MOre interessante tuttavia, è possibile utilizzare facilmente Linq per ottenere i tag nidificati:

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

Ti fornirà una varietà di XElements contenenti ciascuno un '...' che puoi ottenere usando qualcosa come:

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

Per qualsiasi altra cosa diversa dalle strutture dati XML, allora temo che tu debba iniziare a imparare l'arte delle espressioni regolari, uno strumento come "Regular Expression Coach" ti aiuterà moltissimo ( http://weitz.de/regex-coach/ ) o uno degli strumenti simili più aggiornati.

Avrai anche bisogno di familiarizzare con gli oggetti di espressioni regolari .NET, ( http://www.codeproject.com/KB/dotnet/regextutorial.aspx ) dovrebbe darti un buon vantaggio.

Una volta che sai come funziona il tuo reg-ex, nella maggior parte dei casi si tratta di una semplice caso di lettura dei file una riga alla volta e di averne un senso usando il metodo con cui ti trovi a tuo agio.

Una buona fonte gratuita di formati di file per qualsiasi cosa tu possa immaginare può essere trovata a ( http://www.wotsit.org/ )

0
shawty

Per la cronaca ho implementato il parser generator in C # solo perché non sono riuscito a trovare alcun funzionamento corretto o simile a YACC (si veda: http://sourceforge.net/projects/naivelangtools/ ).

Comunque dopo qualche esperienza con ANTLR ho deciso di andare con LALR al posto di LL. So che teoricamente LL è più facile da implementare (generatore o parser) ma semplicemente non posso vivere con stack di espressioni solo per esprimere priorità di operatori (come * precede + in "2 + 5 * 3"). In LL tu dici che mult_expr è incorporato all'interno di add_expr che non mi sembra naturale.

0
greenoldman