如何用 C# 编写解析器? [关闭]

Posted

技术标签:

【中文标题】如何用 C# 编写解析器? [关闭]【英文标题】:How to write a Parser in C#? [closed] 【发布时间】:2011-11-14 16:41:02 【问题描述】:

如何在 C# 中编写解析器(递归下降?)?现在我只想要一个简单的解析器来解析算术表达式(并读取变量?)。虽然后来我打算写一个 xml 和 html 解析器(用于学习目的)。我这样做是因为解析器有用的东西范围很广:Web 开发、编程语言解释器、内部工具、游戏引擎、地图和瓷砖编辑器等。那么编写解析器的基本理论是什么,我该怎么做在 C# 中实现一个? C# 是解析器的正确语言吗(我曾经用 C++ 编写了一个简单的算术解析器,它很高效。JIT 编译是否同样好?)。任何有用的资源和文章。最重要的是,代码示例(或代码示例的链接)。

注意:出于好奇,回答这个问题的人是否曾经在 C# 中实现过解析器?

【问题讨论】:

你想自己实现解析器,还是想使用一个库,它接受一个语法然后自己创建解析器? ANTLR 是 .NET 社区中用于解析自定义语言的稳定器,尽管它会影响您的学习体验。 对于算术表达式解析器,我个人倾向于调车场。关于“有任何人” - 我已经完成了一些高度专业化的解析器,但我不知道用于 C# 的更通用的解析器生成器的可用性 在阅读了这篇文章 (cs.nott.ac.uk/~gmh/monparsing.pdf) 之后,我编写了我的第一个“真正的”解析器。它适用于函数式语言,但它应该提供一些关于如何设计可组合解析器的见解。 @TomTom,你错了。对于不同的语言,有非常不同的惯用方法。你不能在 Fortran 和 Haskell 中以同样的方式编写解析器。在 C# 中,您可以使用组合器,就像在真正的编程语言中一样,它对于某些语法来说是一种明智的方法。 【参考方案1】:

我已经用 C# 实现了几个解析器 - 手写和工具生成。

一个非常好的一般解析入门教程是Let's Build a Compiler - 它演示了如何构建递归下降解析器;对于任何称职的开发人员,这些概念很容易从他的语言(我认为是 Pascal)翻译成 C#。这将教您递归下降解析器的工作原理,但是手动编写完整的编程语言解析器是完全不切实际的。

如果您决定编写classical recursive descent parser(TinyPG、Coco/R、Irony),您应该研究一些工具来为您生成代码。请记住,现在还有其他编写解析器的方法,它们通常性能更好 - 并且定义更简单(例如 TDOP parsing 或 Monadic Parsing)。

关于 C# 是否适合这项任务 - C# 有一些最好的文本库。今天的很多解析器(在其他语言中)都有大量的代码来处理 Unicode 等。我不会对 JITted 代码发表太多评论,因为它可能会变得非常虔诚——但是你应该没问题。 IronJS 是 CLR 上的解析器/运行时的一个很好的例子(即使它是用 F# 编写的),它的性能只是略逊于 Google V8。

旁注:与语言解析器相比,标记解析器是完全不同的野兽——在大多数情况下,它们是手工编写的——并且在扫描器/解析器级别非常简单;它们通常不是递归下降——尤其是在 XML 的情况下,最好不要编写递归下降解析器(以避免堆栈溢出,并且因为可以在 SAX/推送模式下使用“平面”解析器)。

【讨论】:

谢谢。 (+1 虽然你应该得到 +10)。我想确认的另一件事是,如果我真的成功地用 C#(一些简单的语言)制作了一个实际的编程语言解释器,那么该编程语言是否会被视为 .NET 兼容语言(如 Iron-python 或 boo)或只是用 C# 编写的语言? @IntermediateHacker 这取决于您是否发出 MSIL。许多“.Net 语言”的生命周期始于 C#,最终被自己重写(这称为引导程序)。如果您制作解释器,它将是一种“C# 语言”(但仍将/仍可用于其他 .Net 语言)。 大声笑你不评论 JITed 代码是对的。我不希望这个问题引发旧的“哪个更快”的战争。 Jonathan 提到了很多好的框架,但我想补充一下:quanttec.com/fparsec。它适用于 F#,但它具有良好的配置、良好的性能并产生人类可读的解析器错误消息 OOB。 工作链接使用 WaybackMachine Let's Build a Compiler【参考方案2】:

Sprache 是一个强大而轻量级的框架,用于在 .NET 中编写解析器。还有一个Sprache NuGet package。为了让您了解这里的框架,samples 之一可以将简单的算术表达式解析为 .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));
    

【讨论】:

【参考方案3】:

C# 几乎是一种不错的函数式语言,因此在其中实现 Parsec 之类的东西并不是什么大不了的事。以下是如何做到这一点的示例之一:http://jparsec.codehaus.org/NParsec+Tutorial

也可以以非常相似的方式实现基于组合器的Packrat,但这次将全局解析状态保持在某处,而不是执行纯函数式的东西。在我的(非常基本的和临时的)实现中,它相当快,但当然像 this 这样的代码生成器必须表现得更好。

【讨论】:

【参考方案4】:

我知道我来晚了,但我刚刚发布了一个名为 Ve Parser 的解析器/语法/AST 生成器库。您可以在http://veparser.codeplex.com 找到它,或者通过在包管理器控制台中键入“Install-Package veparser”来添加到您的项目中。这个库是一种递归下降解析器,旨在易于使用和灵活。由于您可以使用它的源代码,因此您可以从它的源代码中学习。希望对你有帮助。

【讨论】:

【参考方案5】:

在我看来,有一种比传统方法更好的实现解析器的方法,它可以使代码更简单、更容易理解,尤其是通过在非常面向对象的方式。我写的更大系列的一篇文章重点介绍了这种解析方法,并且包含 C# 2.0 解析器的完整源代码: http://www.codeproject.com/Articles/492466/Object-Oriented-Parsing-Breaking-With-Tradition-Pa

【讨论】:

【参考方案6】:

嗯……从这个开始……

首先,编写一个解析器,这是一个非常广泛的陈述,尤其是您提出的问题。

您的开场白是您想要一个简单的算术“解析器”,从技术上讲,它不是解析器,它是一个词法分析器,类似于您可能用于创建新语言的工具。 (http://en.wikipedia.org/wiki/Lexical_analysis)但是我完全理解它们是同一事物的混淆可能来自哪里。重要的是要注意,如果您还要编写语言/脚本解析器,词法分析也是您想要了解的内容,这严格来说不是解析,因为您是在解释指令而不是使用它们。

回到解析问题......

如果您采用严格定义的文件结构从中提取信息,这就是您将要做的事情。

一般来说,你真的不需要为 XML / HTML 编写解析器,因为已经有很多这样的解析器了,而且如果你解析由 .NET 运行时生成的 XML,那么你就不需要甚至需要解析,只需要“序列化”和“反序列化”即可。

然而,出于学习的兴趣,在大多数情况下,解析 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'

获取 'movies' 根元素

更有趣的是,您可以轻松使用 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 之外的任何其他数据结构,那么恐怕您将不得不开始学习正则表达式的艺术,“正则表达式教练”之类的工具将帮助您非常好 (http://weitz.de/regex-coach/) 或更新的类似工具。

您还需要熟悉 .NET 正则表达式对象,(http://www.codeproject.com/KB/dotnet/regextutorial.aspx) 应该会给您一个良好的开端。

一旦你知道你的 reg-ex 东西是如何工作的,那么在大多数情况下,这是一个简单的案例,一次读取一行文件并使用你觉得舒服的任何方法来理解它们。

可以在 (http://www.wotsit.org/) 找到几乎所有您能想象到的所有文件格式的免费资源

【讨论】:

虽然我同意他应该使用 .Net 中的内置 XML 解析 - 他确实想将解析器编写为学术练习。 究竟为什么,我还展示了如何使用原始 XElement 对象。 附带说明,在 C# 和 .NET 中有 6 或 7 个不同的 API 可以使用,但是我没有书可以列出它们,但是在 XDoc/XElement I 之后相信是 XPath 的。 @shawty XElement 是 XML 解析器的一部分;要创建解析器,他需要获取 XML stringq 并将其转换为可用的东西 也许这是一个术语,但在 XML 的情况下,我会认为 XmlReader 是这里的“解析器”......虽然不是我的专业主题。【参考方案7】:

我在 C# 中实现解析器生成器只是因为我找不到任何正常工作或类似于 YACC 的记录(请参阅:http://sourceforge.net/projects/naivelangtools/)。

但是,在使用了 ANTLR 之后,我决定使用 LALR 而不是 LL。我知道理论上 LL 更容易实现(生成器或解析器),但我根本无法忍受仅仅为了表达运算符优先级的表达式堆栈(比如 * 在“2+5*3”中位于 + 之前)。在 LL 中,您说 mult_expr 嵌入在 add_expr 中,这对我来说似乎并不自然。

【讨论】:

以上是关于如何用 C# 编写解析器? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何为 GraphQL Mutation 字段编写解析器

如何用触摸数据模拟鼠标点击?

分析使用 Haskell 中的解析器组合库编写的解析器

寻找 QML 格式文件的解析器 [关闭]

哪个是更快的 XML 解析器? [关闭]

编写可在 8 位嵌入式系统上使用的解析器,如 Flex/Bison