如何在C#中编写parsing器?
我如何去写在C#中的parsing器(recursion下降?)? 现在我只想要一个简单的parsing器来分析算术expression式(并读取variables?)。 虽然后来我打算写一个XML和HTMLparsing器(用于学习的目的)。 我这样做是因为parsing器有用的各种各样的东西:Web开发,编程语言解释器,内部工具,游戏引擎,地图和瓷砖编辑器等等。那么编写parsing器的基本理论是什么?在C#中实现一个? C#是parsing器的正确语言(我曾经用C ++编写过一个简单的算术parsing器,效率很高,JIT编译certificate是否合适?)。 任何有用的资源和文章。 最重要的是,代码示例(或代码示例的链接)。
注意:出于好奇,有没有人回答这个问题在C#中实现了一个parsing器?
我已经在C#中实现了几个parsing器 – 手写和工具生成。
一般而言,一个非常好的parsing入门教程是让我们构build一个编译器 – 它演示了如何构build一个recursion下降parsing器; 而且这些概念很容易从他的语言(我认为是Pascal)翻译成任何有能力的开发人员的C#语言。 这将教你如何recursion下降parsing器的工作,但手工编写完整的编程语言parsing器是完全不切实际的。
如果你决定编写一个经典的recursion下降parsing器 ( TinyPG , Coco / R , Irony ),你应该研究一些工具来为你生成代码。 请记住,现在还有其他方法来编写parsing器,通常执行得更好 – 并且定义更简单(例如TDOPparsing或Monadicparsing )。
关于C#是否适合这个任务–C#有一些最好的文本库。 今天很多parsing器(使用其他语言)都有大量的代码来处理Unicode等问题。我不会在JITted代码上发表太多的评论,因为它会变得非常虔诚 – 但是你应该没问题。 IronJS就是CLR上的一个parsing器/运行时的一个很好的例子(尽pipe它是用F#编写的),它的性能只是Google V8的一小部分。
注意:与语言parsing器相比,标记parsing器是完全不同的 – 它们在大多数情况下是手工编写的 – 而且在扫描器/parsing器级别非常简单; 它们通常不是recursion下降 – 特别是在XML的情况下,如果不写recursion下降parsing器(避免堆栈溢出,并且因为可以在SAX / push模式下使用“平坦”parsing器),效果会更好。
Sprache是一个用.NET编写parsing器的强大而轻量级的框架。 还有一个Sprache NuGet包 。 为了让你对这个框架有一个概念,可以将一个简单的算术expression式parsing成一个.NETexpression式树。 我会说很惊人的。
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+Tutorial
也可以以非常相似的方式实现一个基于combinator的Packrat ,但这次保持全局parsing状态,而不是做一个纯粹的function性的东西。 在我的(非常基础的和临时的)实现中,它是相当快的,但是当然这样的代码生成器必须执行得更好。
我知道我有点晚了,但我刚刚发布了一个名为Ve Parser的parsing器/语法/ AST生成器库。 你可以在http://veparser.codeplex.com上find它,或者在包pipe理器控制台中input“Install-Package veparser”来添加到你的项目中。 这个库是一种recursion下降parsing器,旨在易于使用和灵活。 由于其源代码可供您使用,您可以从源代码中学习。 我希望它有帮助。
在我看来,有一种比传统方法更好的实现parsing器的方法,它使得代码更简单,更容易理解,特别是通过在一个非常对象的对象中插入一个新的类,导向的方式。 我写的一个更大的系列文章中的一篇文章重点介绍了这种parsing方法,C#2.0parsing器包含完整的源代码: http : //www.codeproject.com/Articles/492466/Object-Oriented-Parsing-Breaking-With -传统霸
那么…从哪一个开始呢…
首先,写一个parsing器,这是一个非常广泛的陈述,尤其是对于你提出的问题。
你的开头语句是你想要一个简单的算术“parsing器”,在技术上这不是一个parsing器,它是一个词法分析器,类似于你可能用来创build一种新的语言。 ( http://en.wikipedia.org/wiki/Lexical_analysis )不pipe怎样,我都明白,他们的混淆是同一件事,可能来自哪里。 重要的是要注意,词法分析也是你想要了解的,如果你也要写语言/脚本parsing器,这是严格的parsing,因为你正在解释的说明,而不是使用它们。
回到parsing问题….
如果您采取严格定义的文件结构来从中提取信息,那么您将会这样做。
一般来说,你不必为XML / HTML编写一个parsing器,因为已经有很多parsing器了,如果你parsing由.NET生成的XML,那么你甚至不需要parsing,你只需要“序列化”和“反序列化”。
但是为了学习,在大多数情况下,parsingXML(或类似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'来获取'电影'根元素
MOre有趣的是,你可以很容易地使用Linq获取嵌套标签:
var myElements = from p in myXML.Root.Elements("movie") select p;
会给你一个XElements的var,每个都包含一个'…',你可以使用如下的东西:
foreach(var v in myElements) { Console.WriteLine(string.Format("ID {0} = {1}",(int)v.Attributes["id"],(string)v.Element("movie")); }
除了像XML这样的数据结构之外,恐怕你将不得不开始学习正则expression式的艺术,像“正则expression式教练”这样的工具将帮助你imensly( http://weitz.de/regex )或者其中一个更加类似的工具。
你还需要熟悉.NET的正则expression式对象,它会给你一个良好的开端。
一旦你知道了你的reg-ex的东西是如何工作的,那么在大多数情况下,这是一个简单的案例,一次一行地读取这些文件,并使用你喜欢的任何方法来理解它们。
( http://www.wotsit.org/ )提供了几乎所有您可以想象的文件格式的免费资源。
为了logging,我在C#中实现了parsing器生成器,只是因为找不到任何正确或类似于YACC的工作(请参阅: http : //sourceforge.net/projects/naivelangtools/ )。
然而,在ANTLR的一些经验之后,我决定和LALR一起,而不是LL。 我知道理论上LL比较容易实现(生成器或分析器),但我不能仅仅为了表示运算符的优先级(如*
在+
2 + 5 * 3之前)而生活在一堆expression式中。 在LL中,你说mult_expr被embedded在add_expr中,这对我来说不是很自然。