65.9K
CodeProject 正在变化。 阅读更多。
Home

Tiny Parser Generator v1.2

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (197投票s)

2008年8月1日

CPOL

25分钟阅读

viewsIcon

743730

downloadIcon

17676

@TinyPG是一个实用程序,它让编写和尝试您自己的解析器/编译器变得更容易

TinyPG/TinyPG_v1.1.png

引言

@TinyPG代表“一个微型解析器生成器”。这个特定的生成器是一个LL(1)递归下降解析器生成器。这意味着它不会像大多数编译器编译器那样从语法生成状态机,而是直接生成源代码;基本上为语法中的每个非终结符生成一个方法。终结符使用.NET强大的正则表达式表示。为了帮助程序员创建.NET正则表达式,TinyPG中嵌入了一个正则表达式(Regex)工具。语法可以使用扩展BNF符号编写。

TinyGP v1.2现在允许您以C#或VB代码(!)生成扫描器、解析器和解析树文件。这些可以直接编译到您自己的项目中。此外,现在可以为自己的文本高亮显示器生成代码,您可以直接在自己的文本编辑器项目中使用它。本文末尾添加了一个简单的示例。

在本文中,我不会深入探讨编译器理论,需要对编译器和语法有基本的了解。为了您的参考,我列出了本文中使用的术语,并在维基百科上进行了解释

如今,随着大量编译器编译器的出现,很难再为编译器编译器想出一个新名字。“又一个编译器编译器”已经被使用了,所以我决定用这个小工具的许多优点来命名它

  • 一个 功能强大的小型实用程序,用于定义新编译器的语法,它
    • 提供语法的语法和语义检查
    • 为解析器/扫描器/解析树生成 一小组源代码 (只有三个 .cs.vb 文件,没有任何外部依赖)
    • 允许每个生成的文件保持清晰可读,并可以使用 Visual Studio (!) 进行调试
    • 包括一个表达式求值工具,它生成一个可遍历的解析树
    • 可以选择在语法中包含 C# 代码块,只需几行代码即可添加即时功能
    • 包括一个 小型正则表达式工具
    • 尝试使事情 尽可能简单和微小

背景

既然已经有许多编译器编译器了,您可能会想为什么还要再写一个呢?编写这个实用程序的原因是

  • 乐趣因素。说真的,谁不想写自己的编译器编译器呢?!
  • 渴望一个能生成C#代码的编译器编译器。
  • 该实用程序应免费,允许开发人员将生成的代码用于任何目的,且无限制性许可证。
  • 生成的代码应可读,并在需要时相对容易调试。
  • 生成的代码应完全独立,不需要任何外部库即可运行(例如,来自编译器编译器本身的库)。
  • 源代码应可供开发人员修改实用程序。
  • 该工具应该是免费的。
  • 应该可以将语义与语法分离(使用子类化),或者将它们组合(使用内联代码块)。

使用工具

我将通过两个小型教程来解释该工具的用法,我们将从编写一个小型表达式计算器开始。在第一个教程中,我们将定义表达式求值器的语法;这将使我们能够解析表达式。在第二个教程中,我们将添加一些代码块来实现表达式求值器的语义;在此步骤之后,我们也将能够求值或计算简单表达式。

在开始之前,我想解释一下我偏爱使用的命名约定,因为没有明确的命名约定,并且该实用程序不要求您使用任何特定的约定。然而,为了生成代码的可读性,我建议如下

  • 对于终结符,只使用大写名称,可能带有下划线;例如,NUMBERIDENTIFIERWHITESPACEBRACKET_OPEN
  • 对于非终结符,使用帕斯卡命名法。这与 .NET 标准相符,由于每个非终结符都会生成一个方法,因此该方法也将采用帕斯卡命名法。例如,StartExpressionMultiplyExpression

要编写有效的语法,应遵循以下规则

  • 每个产生式规则必须以 ; 字符终止。
  • Start 是一个保留字,表示语法的起始符号。
  • 注释可以使用 // 注释一行,或 /* ... */ 注释一个段落。
  • 包含代码块时,代码块必须写在 { ... }; 之间。请注意,结束括号必须紧跟分号,不能有空格,否则会导致错误。

让我们开始教程。

简单表达式计算器语法

表达式求值器的目标是解析和求值由(整数)数字、+-*/ 符号组成的简单数字表达式。为了更有趣,我们还将允许使用带有 ( ) 符号的子表达式。例如,4*(24/2-5)+14 将是一个有效的表达式,其求值结果应为 42。此示例包含在TinyPG中,通过打开C#的 simple expression1.tpg 文件,或VB版本的 simple expression1 vb.tpg 文件。

终结符和正则表达式

既然我们已经决定了计算器中允许使用的符号,我们就可以开始使用终结符产生式规则和正则表达式来定义语法的终结符符号

NUMBER -> @"[0-9]+"; 
PLUSMINUS -> @"(\+|-)"; 
MULTDIV -> @"\*|/"; 
BROPEN -> @"\("; 
BRCLOSE -> @"\)";

终结符可以使用 .NET 的正则表达式语法定义,如果需要,可以包含 @ 符号。使用 .NET 的正则表达式可以节省大量编码,并使 scanner.cs 源文件非常小且易于阅读。您可能已经猜到,终结符定义将由生成的解析器直接在正则表达式中使用。

这可能是一个提升正则表达式技能的好机会。为了方便使用正则表达式,我已将正则表达式工具包含在该实用程序中。只需单击“正则表达式工具”选项卡并输入您的正则表达式。任何匹配项将立即在文本中突出显示。通过这种方式,您可以测试您的正则表达式是否匹配正确的符号。在本文的末尾,我将包含一些常用正则表达式,它们在您自己的语言中可能非常有用。

TinyPG evolvement cycle

请注意,TinyPG 没有任何保留或预定义的终结符符号。例如,一些解析器生成器保留 EOF 终结符,因为它可能难以表达。正则表达式几乎可以处理任何类型的终结符,包括 EOF 符号。EOF 符号可以定义如下

EOF -> @"^$";

^ 字符表示正则表达式将从文本字符串/文件的开头扫描,$ 表示正则表达式应扫描到字符串/文件的末尾。由于没有在两者之间指定字符,这一定是文件的结尾(或文本/字符串)。

为了减少表达式格式的限制,我们希望允许使用空格。然而,我们不希望在语法中检查空格。事实上,我们希望扫描器简单地忽略空格并继续扫描下一个符号/令牌。这可以通过为空格定义一个终结符产生式规则并以 [Skip] 属性作为前缀来完成,如下所示

[Skip] WHITESPACE -> @"\s+";

非终结符和产生式规则

一旦定义了终结符,我们就可以定义非终结符的产生式规则。TinyPG支持扩展BNF符号,允许在产生式规则中使用以下符号:*+?()| 和空格。它们的含义如下

  • * - 符号或子规则可以出现0次或更多次
  • + - 符号或子规则可以出现1次或更多次
  • ? - 符号或子规则可以出现0次或1次
  • | - 这定义了两个子规则之间的选择
  • 空格 - 符号或子规则必须依次出现
  • ( ... ) - 允许定义子规则

语法必须以 Start 非终结符开始,因此 Start 是一个保留字。让我们将语法定义如下

Start       -> (AddExpr)? EOF;
AddExpr     -> MultExpr (PLUSMINUS MultExpr)*;
MultExpr    -> Atom (MULTDIV Atom)*;
Atom        -> NUMBER | BROPEN AddExpr BRCLOSE;

Start 产生式规则将检查是否存在 AddExpr(可选),然后预期文件结束(不预期其他标记)。

AddExpr 只会加或减 MultExpr 表达式。MultExpr 会乘或除 Atom。通过这种方式编写语法,明确定义了 */ 符号相对于 +- 符号的优先级。Atom 目前可以是数字(整数)或另一个表达式。因此,仅用四个简单的产生式规则,您就可以解析像 (((5*8)/4+3*3-(7+2)) / 5) 这样复杂的表达式。

运行解析器

要编译语法,请按 F6。输出窗格应可见,再次显示语法(内部表示),但也显示非终结符的“First”符号。“First”符号是解析器在决定接下来应解析哪个非终结符或产生式规则时使用的符号。生成的C#代码也将在内部编译并可以运行。如果一切顺利,应显示“Compilation successful”。

Ctrl+E 打开 表达式求值器 窗格,然后输入一个表达式供语法求值,例如 (((5*8)/4+3*3-(7+2)) / 5),然后按 F5 运行解析器。表达式将被求值,并应显示“e;Parse was successful”。如果解析成功,求值器将继续求值结果解析树。因为我们尚未实现产生式规则背后的任何逻辑,您将收到以下警告:“Result: Could not interpret input; no semantics implemented.

简单表达式计算器语义

添加代码块

既然我们已经有了一个可用的语法,我们就可以开始以代码块的形式向产生式规则添加语义。代码块是 C#(或 VB)代码片段,在替换一些变量后,它们几乎直接插入到生成的解析器中。让我们从第一个产生式规则开始

Start -> (AddExpr)? EOF { return $AddExpr; }; 

请注意变量 $AddExpr$AddExpr 对应于非终结符 AddExpr 的值。对于生产规则中的每个终结符和非终结符,都定义了一个 $ 变量,在代码生成期间,该变量将被一个表达式替换。在本例中,$AddExpr 被一个 .NET 表达式替换,该表达式将计算 AddExpr 非终结符。在本例中,$EOF 也是一个有效的变量。请注意,终结符和非终结符始终返回 object 类型的值。如果您想进行以下代码片段中的计算,则需要进行显式类型转换

AddExpr -> MultExpr (PLUSMINUS MultExpr)* 
{ 
    int Value = Convert.ToInt32($MultExpr); 
    int i = 1; 
    while ($MultExpr[i] != null) { 
        string sign = $PLUSMINUS[i-1].ToString(); 
        if (sign == "+") 
            Value += Convert.ToInt32($MultExpr[i++]); 
        else 
            Value -= Convert.ToInt32($MultExpr[i++]); 
    } 
    return Value; 
}; 

请注意,在此产生式规则中,术语 MultExpr 被定义了两次。那么 $MultExpr 指的是 MultExpr 的哪个值实例呢?更重要的是,后面的 MultExpr 可以无限重复。要引用 MultExpr 值的特定实例,您可以使用基于零的索引器在 $ 变量上,这样 $MultExpr[1] 将引用输入表达式中定义的第二个 MultExpr 实例。因此,如果我们有表达式 3+4*2-6,我们将有三个 MultExpr 非终结符:34*2 (= 8) 和 6。因此,$MultExpr[1] 将求值为 8。然而,$MultExpr[3] 不可用,因此将求值为 .NET 的 null 值。

所以这段代码会计算第一个 MultExpr(换句话说,$MultExpr$MultExpr[0] 的缩写),并将其赋值给 Value。根据语法,我们知道它总是存在的。然后,我们遍历所有的 $MultExpr[i],并对 Value 进行加或减,直到 $MultExpr[i] 求值为 null。为了决定一个 MultExpr 应该被加还是被减,我们评估 $PLUSMINUS 标记。通过这种方式,我们现在实际上可以计算加法和减法。

同样的方法也可以用于 MultExpr 产生式规则。此处不提供代码,但可以在 simple expression2.tpg 文件中找到。

最后,还有 Atom 产生式规则,它可以定义如下

Atom -> NUMBER | BROPEN AddExpr BRCLOSE 
{ if ($NUMBER != null) 
        return $NUMBER; 
    else 
        return $AddExpr; 
};

因为 Atom 规则包含 NUMBER 和子表达式之间的选择,所以代码会检查两个子规则是否都为 null。如果不是,我们返回这个值。

运行生成的解析器/编译器

再次按下 F6 编译语法,应该不会出现任何错误。然后输入表达式 (((5*8)/4+3*3-(7+2)) / 5) 并按下 F5。这次,表达式应该成功解析,结果被计算出来,并应该返回“Result: 2”。

恭喜,您已经编写了您的第一个 TinyPG 编译器!

突出显示您的表达式

为了让事情更有趣,版本 1.2 允许您为语法添加文本高亮显示。添加文本高亮显示分两步完成

  1. TextHighlighter 指令添加到语法中
  2. Color 属性添加到您想要突出显示的终结符

添加 TextHighlighter 指令后,@TinyPG 将生成一个 TextHighlighter.cs(或 .vb)文件。生成的 TextHighlighter 使用生成的解析器/扫描器解析输入文本,并将 Color 属性中的颜色应用于它识别的任何终结符。例如,简单表达式求值器的指令和终结符可能如下所示

TinyPG evolvement cycle

// By default the TextHighlighter is not generated.
// Set its Generate attribute to true
<% @TextHighlighter Generate="true" %>

// highlight numbers in red and symbols in blue
[Color(255, 0, 0)] NUMBER -> @"[0-9]+"; 
[Color(0, 0, 255)] PLUSMINUS -> @"(\+|-)"; 
[Color(0, 0, 255)] MULTDIV -> @"\*|/"; 
[Color(0, 0, 255)] BROPEN -> @"\("; 
[Color(0, 0, 255)] BRCLOSE -> @"\)";

当在输入窗格中运行表达式时,表达式将被计算并额外在输入窗格中高亮显示!这使得编写自己的文本高亮显示器变得非常简单。

其他备注

注1:在产生式规则中直接使用终结符字符是不允许的。一些编译器编译器允许这样做;但是,我觉得这不会增加生成代码的可读性。因此,每个终结符都必须明确定义为正则表达式。非终结符产生式规则只能以 LL(1) 方式引用终结符或其他非终结符。这意味着解析器只能向前看一个标记。在扫描和解析时,输入标记始终对应于终结符符号或是一个语法错误。因此,解析器只需要向前看一个终结符来决定选择哪个产生式规则。

例如,以下语法将导致错误,因为它不是 LL(1)

// this is an LL(2) rule, you need to look
// ahead 2 symbols to determine which rule to choose 
Start -> PLUSMINUS NUMBER | PLUSMINUS Expression;

解析器必须根据一个先行选择产生式(子)规则。因为两个子规则都以 PLUSMINUS 终结符开头,所以解析器无法决定。TinyPG 不检查语法是否是 LL(1),而只是简单地生成代码。然而,在编译代码时,您会遇到编译错误。幸运的是,LL(k) (k > 1) 规则总是可以改写为 LL(1) 形式,如下所示

// the rule has been rewritten to LL(1), now only 1 symbol at a time is 
// required to be looked at to make the decision
Start -> PLUSMINUS ( NUMBER | Expression);

如所示重写规则后,产生式规则现在是 LL(1) 并且可以成功地在 LL(1) 解析器中生成……或者可以吗?LL(k) 问题可能稍微复杂一些。如果 Expression 定义为

Expression -> NUMBER | IDENTIFIER;

同样,解析器会遇到同样的问题;当它遇到一个 NUMBER 令牌时,它应该选择 Start 的 NUMBER 规则还是应该继续解析一个 Expression?这次,解决问题更困难,在这种情况下,没有简单的解决方案。需要重新思考(部分重写)您的语法。

注2:TinyPG不会检测代码块内的错误,但当然,.NET编译器会。这可能导致难以跟踪和映射回语法代码块的.NET编译错误。可能很难看出问题所在。调试此问题的最佳方法是在Visual Studio中打开源代码并尝试编译它。

注3:也可以通过不将代码块直接插入到语法中来分离语义和语法。TinyPG将生成三个源代码文件:扫描器、解析器和解析树。成功解析输入字符串后,解析器将返回一个已填充的解析树。通常,TinyPG会将代码块直接插入到 parsetree 中。然后可以单独评估 parsetree。在这种情况下,我们创建一个 parsetree 的子类并将我们自己的代码插入到其中(要实现的方法可以被子类覆盖)。然后,在调用解析器时,您向它提供您自己的解析树的新实例。解析器将填充此 parsetree 并再次返回。

当然,另一种方法是直接在代码中通过遍历树节点来评估 parsetree;但是,不知何故我觉得这种选择不够“干净”。

部分上下文敏感/模糊语法

@TinyPG V1.2 现在支持部分模糊语法。给定简单的表达式语法,假设我们想区分 FACTORTERM。问题是,FACTORTERM 都是数字,可以定义为

[Color(255, 0, 0)] FACTOR_NUMBER -> @"[0-9]+";    // mark factors in red
[Color(0, 255, 0)] TERM_NUMBER -> @"[0-9]+";      // mark terms in green

这通常是一个模糊的语法,因为一个 NUMBER 作为输入可以匹配这两个符号,除非,例如,您将语法定义为只期望一个 TERM。例如

Start -> TERM_NUMBER (MULTDIV) FACTOR_NUMBER;

第一个输入数字预计为 TERM,而第二个数字预计为 FACTOR。根据上下文(解析器正在解析的规则),扫描器将分别将数字解释为 TERMFACTOR。在示例中,第一个数字将标记为绿色,第二个标记为红色。

Using the Code

一旦您生成了 ScannerParserParseTree,以及可选的 TextHighlighter 类,并使用 TinyPG 对它们进行了测试,您现在显然想在自己的项目中使用这些代码。这可以通过使用 Visual Studio 创建一个新的 C# 项目来完成。将生成的文件添加到项目中并编译,以确保没有错误。

要调用解析器,请使用以下代码

#using TinyPG; // add the TinyPG namespace

...

// create the scanner to use
Scanner scanner = new Scanner(); 

// create the parser, and supply the scanner it should use
Parser parser = new Parser(scanner); 

//create a texthighligher (if one was generated)
//and attach the RichTextbox and parser and scanner.
TextHighlighter highlighter = 
    new TextHighlighter(richTextbox, scanner, parser);

// define the input for the parser
string input = "... your expression here ...";

// parse the input. the result is a parse tree.
ParseTree tree = parser.Parse(input);

请注意,返回的是一个 ParseTree 对象。解析树包含输入的结构。如果输入的语法不正确,ParseTree 将包含错误。您可以通过检查 ParseTree.Errors 属性来检查错误。

还要注意,TextHighlighter 接受一个 RichTextBox 控件。TextHighlighter 将自动开始捕获其事件,分析其内容,并更新 RichTextBox 控件的内容。

如果一切顺利,您可以继续评估解析树

// evaluate the parse tree; do not pass any additional parameters
object result = ParseTree.Eval(null);

// write the result of the evaluation to the console:
Console.WriteLine("result: " + result.ToString());

请注意,Eval(...) 函数返回一个 object 类型的结果。在求值期间,您可以自由决定返回类型。这为您提供了更大的自由度;但是,这也意味着您必须显式转换类型。要显示结果,请使用 result.ToString() 方法将其转换为 string

总而言之,这就是构建和实现您自己的语法和解析器所需的全部。生成的代码没有任何外部依赖,也不需要任何额外的库才能使用。

Tiny Parser Generator 演进周期

我一直觉得编译器编译器令人着迷的地方在于它们能够编译自己的语法,从而生成一个编译器,在这种情况下,它本身就是一个编译器编译器。当然,一开始并没有可用的编译器编译器。那么如何构建一个呢?在本节中,我将解释我为 TinyPG 应用的演进周期,如下图所示

TinyPG evolvement cycle

  • 步骤 1:将语法定义为要解析的输入文本。
  • 步骤 2:通过解析输入语法,推导出具体的解析树。
  • 步骤 3:将解析树转换为抽象解析树:语法树。
  • 步骤 4:语法树包含所有关于语法的信息,以树的形式存储。
    • 步骤 4a:从语法树生成语法文本。这是一个检查,以确定输入语法是否与语法树中的语法对应。如果它们不对应,那么很可能转换出了问题。
    • 步骤 4b:生成解析器 C# 源代码
  • 步骤 5:将源代码编译成实际的解析器。
  • 后续步骤:取步骤4a生成的语法,作为解析器的输入,继续步骤2的循环。

要启动整个过程,请从手动(在代码中)创建抽象语法树开始。

最棘手的部分是转换和生成过程。一旦您掌握了这些,其余的就相对容易了。

使用 TinyPG 指令

有时您希望能够为工具设置一些额外的参数,以便它知道如何以及在何处生成代码;例如,指定要生成 C# 或 VB 代码。为此,我包含了通过指令直接将元信息插入到语法中的选项。我受到了 ASPX 页面使用 <% ... %> 标签处理方式的启发。我决定这将是一种方便紧凑的格式,它足够严格以允许指定一些参数,并且在以后阶段添加更多指令时易于扩展。

请注意,V1.2 中现在也实现了代码块的语法高亮。代码块将根据 @TinyPG 指令的相应 Language 设置进行高亮显示。

指令必须在语法实现之前定义。目前支持以下指令:@TinyPG@Parser@Scanner@ParseTree,可以按如下方式使用

// Namespace allows you to define a different namespace for the Scanner, 
//     Parser and ParseTree generated classes. By default this is set to "TinyPG"
// Language allows you to define the language to generate.
// Only C# (default) or VB are supported for now. 
// OutputPath allows you to define the absolute or relative outputpath for the
//     files. The relative path is relative to the grammar file. 
//     By default this is set to "./"
// Template path is relative to the TinyPG.exe file. By default this is set to 
//     "Templates"
<% @TinyPG Namespace="MyNamespace" Language="C#" 
           OutputPath="MyGrammarPath" TemplatePath="MyParserTemplates" %>

// the Generate parameter specifies wether the file
// should be generated. By default this is set to True
<% @Parser Generate="True" %>
<% @ParserTree Generate="False" %>  // turn off generation of the ParseTree.cs file
<% @Scanner Generate="True" %>
<% @TextHighlighter Generate="True" %>
// indicates code for the TextHighlighter should be generated also

一些方便的正则表达式

编写自己的正则表达式并不总是容易的,特别是当您想匹配编程语言中也经常使用的标记时。我在这里总结了一些非常有用的正则表达式。

// codeblock will match any text between { ... }; 
Regex codeblock = new Regex(@"\s*\{[^\}]*\}([^};][^}]*\})*;"); 

//eof will match the end of an input string only
Regex eof = new Regex(@"^$");

//whitespace will match any whitespace including tabs.
//This one is trivial, but often required.
Regex whitespace = new Regex(@"\s+");

//regex_string will match any text that is a .Net string.
// It also takes the ", "", @ and \ into account
Regex regex_string = new Regex(@"@?\""(\""\""|[^\""])*\""");

//commentline will match with a single line of text that starts with
// and scan it until the end of the line
//very handy if you want to support commenting by your parser
Regex commentline = new Regex(@"//[^\n]*\n?"); 

//commentblock will match any text between /* ... */ .
//Very handy if you want to support commenting by your parser
Regex commentblock = new Regex(@"/\*[^*]*\*+(?:[^/*][^*]*\*+)*/", RegexOptions.Compiled);

这就是我从网上找到或自己编写的一些有趣的正则表达式。如果您有任何有趣/复杂的正则表达式,请告诉我。

关注点

除了解析器生成器功能外,TinyPG 工具还包含一些可能有趣的其他组件。1.2 版新增的控件/功能已加粗显示

  • C# 或 VB 代码生成。我已将生成代码和模板分离到项目中,并添加了对 VB.NET 和 C# 的支持。现在可以直接创建语法以生成 C# 或 VB 代码。支持内联 C# 或 VB 代码块。
  • 部分支持上下文敏感/模糊语法。我没有评估所有可能的终结符作为语法的可能输入,而是调整了 LookAhead 函数,使其仅查找预期的终结符。因此,如果例如,两个终结符被定义为匹配相同的输入,导致模糊语法,解析器仍然知道根据其正在解析的规则查找哪个终结符。
  • 代码高亮。一旦有了解析树,高亮显示终结符和非终结符就相对容易了。因为TinyPG V1.1的代码高亮在文本过长时变得相当慢,所以我在V1.2中决定将代码高亮设为异步。这似乎工作得很好;然而,当文本太大(例如超过500行代码)时,高亮可能需要一些时间才能完成。此外,我包含了C#和VB内联代码块的代码高亮,但不幸的是,还没有代码补全功能。
  • 语法高亮。因为 EBNF 符号本身就是一种语言,为什么不在您输入时检查语法的语法和语义呢?发现的任何语法或语义错误都将通过在代码中用红色下划线突出显示。将鼠标悬停在错误上甚至会显示一个工具提示,显示错误是什么,就像在 Visual Studio 中一样。TextMarker 类负责下划线错误的单词。该类可以轻松地在您自己的项目中重复使用,因为它没有依赖项。将其添加到您的项目,将其与 RichtTextbox 控件连接,并为它分配错误的单词。这些单词将自动标记。
  • 上下文敏感代码补全。根据您正在输入的区域,代码补全将显示相关的动词来补全您输入的单词。显然,此功能也受到了 Visual Studio 的启发。此功能在 AutoComplete 表单中实现,该表单没有依赖项,因此也可以重复使用。只需将其添加到您自己的项目,将其与 RichTextbox 控件连接,添加关键字,即可开始使用。可以通过 AutoCompletion 控件的 Enabled 属性管理自动补全的开启或关闭。
  • TabControlEx。我包含了标准 .NET TabControl 的扩展,名为 TabControlEx。这个控件在倒置时能正确渲染选项卡,不像它的超类。它目前唯一的问题是当选项卡位于左侧或右侧时,它无法正确渲染。
  • RegexControl 是一个功能齐全的即插即用工具,用于测试您的正则表达式。但这并不太令人兴奋,因为市面上已经有很多这样的工具了。
  • DockExtender。我从我在 CodeProject 上发布的另一个项目中重用了这段代码。它将允许您拖放和停靠主窗口上的面板。
  • HeaderLabel 控件。这是 Label 控件的扩展。该标签会根据当前选择的 Windows 主题获得渐变背景色。还可以激活或停用该标签,为其提供活动或非活动的背景色。最重要的是,我甚至在标签上添加了一个小的关闭“x”,当您将鼠标悬停在其上时会激活,实际上创建了一个类似于窗体标题的标题。
  • 解析树。一旦您能够编译您的语法并解析您自己的表达式,解析树就可用了。当点击解析树中的节点时,其对应的令牌将在表达式求值器窗格中突出显示,提供了一种浏览树并查看在解析树的哪个点解析了哪些令牌的方法。这可能有助于调试语法中潜在的错误。

总而言之,尽管我将其命名为 Tiny Parser Generator,但这已经不仅仅是一个小型项目了。我付出了相当大的努力使这一切协同工作,现在我觉得这值得与社区分享。在未来的版本中,我希望添加以下功能

  • 指定生成类的命名空间的选项。
  • 支持 LL(k) (多重先行);这使得编写语法更容易一些;尽管 LL(1) 会生成更好的代码。
  • 更好的代码高亮(可能需要部分重写 RichtTextBox 控件)。这本身可以成为一个单独的项目。社区在这方面的任何帮助都将不胜感激!
  • 代码块高亮!这当然需要解析 C#,但是嘿,我们现在有一个解析器生成器!我只需要找到 C# 的语法。有人知道吗?这仍然是我的优先事项之一,但我正在接近实现它。
  • 生成不同语言的代码。由于这很可能需要对部分代码进行重大修改,我暂时将只支持 C# 和 VB.NET。
  • 也许解析树的图形显示更美观(例如,类似于 Antlr 中使用的东西)。
  • 显示类似于 Antlr 中所做的状态机。这是一个不错的功能,也可以使生产规则更清晰。
  • 更好的错误处理和显示。例如,使其可点击以跳转到发生错误的位置。
  • 在单独的线程上运行求值器。如果您插入错误的的代码块(例如,无限循环),该工具目前会卡住。通过在单独的线程中运行,可以控制这种情况。

如果您对新功能、评论或建议有任何想法,请留言!

历史

@TinyPG v1.0

Tiny Parser Generator 版本 1.0 于 2008 年 8 月 1 日发布。此版本包含了生成简单自顶向下解析器的基本实现。

@TinyPG v1.1

Tiny Parser Generator 1.1 版于 8 月 17 日发布。1.1 版包含附加功能,使编辑器更易于使用

  • 文本和语法高亮
  • 自动补全
  • 改进/修订了 EBNF 语言的语法
  • 支持指令
  • 改进的 FIRST 算法

@TinyPG v1.2

Tiny Parser Generator 1.2 版于 9 月 1 日发布。1.2 版包含附加功能,使编辑器更易于使用

  • 生成 C# 和/或 VB 代码 (!)
  • 允许上下文部分敏感/模糊语法
  • C# 或 VB 代码的代码块高亮显示
  • 属性现在允许参数
  • 添加了 Color(R,G,B) 属性
  • 生成文本高亮代码
  • 异步文本和语法高亮

@TinyPG v1.3

Tiny Parser Generator 1.3 版于 2010 年 9 月 19 日发布。1.3 版是一个次要升级,主要修复了一些问题

  • 已解决跳过令牌的语法高亮问题。现在所有跳过的令牌都附加到下一个实际令牌 (Token.Skipped)。所有尾随的跳过令牌都保留在扫描器中 (Scanner.Skipped)。
  • TextHighlighter 中增加了对 Unicode 的支持。
  • ParseTree 输出添加了序列化功能。因此现在可以将其保存为 XML,并使用例如 XPath 进行查询。这在您希望将解析过程与解释过程分开的场景中可能派上用场。
  • 修复了 TinyPG 编辑器中高亮显示的错误。

特别感谢 William A. McKee 积极参与 @TinyPG,激励我进一步改进工具,并帮助我修订 EBNF 语法。我已在 @TinyPG v1.2 中实现了他的一些想法,包括对(部分)模糊语法的支持。此外,我要感谢 Vicor Morgante 在使用该工具方面的奉献精神,并帮助我进一步改进。

© . All rights reserved.