C# 语法分析器(六)构造语法分析器
系列导航
- (一)语法分析介绍
- (二)LR(0) 语法分析
- (三)LALR 语法分析
- (四)二义性文法
- (五)错误恢复
- (六)构造语法分析器
现在支持在运行时通过语法规则生成可以运行的语法分析器,也能够在设计时通过 T4 模板生成语法分析器,只需要依赖一个较小的运行时 Cyjb.Compilers.Runtime。
一、运行时语法规则的定义
语法分析器用到的所有规则都在 Parser<T, TController> 类中定义,这里的泛型参数 T
表示语法分析器的标识符的类型(一般是一个枚举类型),TController
表示语法分析器的控制器,可以使用默认实现 ParserController<T>
。定义规则方法包括:定义产生式的 DefineProduction 方法、定义优先级和结合性的 DefineAssociativity 方法以及定义入口的 AddStart 方法。
调用 DefineProduction 方法定义文法产生式,可以直接指定产生式的头和产生式体,产生式体支持简单的范式(“*”、“+”和“?”)。还可以设置产生式的优先级。
parser.DefineProduction(Calc.E, Calc.Id).Action(c => c[0].Value);
调用 DefineAssociativity 方法定义符号的优先级和结合性,这里的概念与 Bison 一致,也是分为左结合、右结合以及非结合的。
parser.DefineAssociativity(AssociativeType.Left, Calc.Add, Calc.Sub);
调用 AddStart 方法可以定义多个起始非终结符。默认会使用首个出现的非终结符作为起始符号,可以自行添加来覆盖默认符号。这里还支持指定多个起始符号,就可以在一个语法分析器中根据需要分析文法的不同组成部分。
同时,通过设置 ParseOption
参数,还允许扫描到匹配的语法单元后就暂停,而非固定的解析到输入结束,使用上更加灵活。
与词法分析器类似,定义好的语法分析器也可以通过 GetFactory 方法直接生成词法分析器的工厂类,或者也可以使用下面定义设计时语法分析器。
二、设计时语法规则的定义
为了简化设计时语法规则的定义和实现,选择使用 C# Attribute 来指定相关规则,而非语法规则文件。为了使用 T4 模板,需引入一些必备依赖:
-
通过 nuget 依赖运行时 Cyjb.Compilers.Runtime。
-
通过 nuget 依赖生成器 Cyjb.Compilers.Design,注意请如下指定引用配置,可以正常编译项目并避免产生运行时引用。
<ItemGroup>
<PackageReference Include="Cyjb.Compilers.Design" Version="1.0.6">
<GeneratePathProperty>True</GeneratePathProperty>
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
之后就是定义语法规则了。首先要求提供一个继承自 ParserController<T> 的部分类,通过在这个类上定义 Attribute 完成语法规则的定义。
public partial class CalcParser : ParserController<Calc> {
}
使用 ParserProductionAttribute 声明文法的产生式。例如:
public partial class CalcParser : ParserController<Calc> {
[ParserProduction(Calc.E, Calc.Id)]
private object? IdAction()
{
return this[0].Value;
}
}
使用 ParserLeftAssociateAttribute、ParserRightAssociateAttribute 和 ParserNonAssociateAttribute 声明优先级和结合性。例如:
// 声明优先级和结合性
[ParserLeftAssociate(Calc.Add, Calc.Sub)]
public partial class CalcParser : ParserController<Calc> {
}
使用 ParserStartAttribute 声明起始符号。例如:
// 声明终结符
[ParserStart(Calc.E)]
public partial class CalcParser : ParserController<Calc> {
}
最后是添加与语法分析器同名的 tt 文件,内容如下:
<#@ include file="$(PkgCyjb_Compilers_Design)\content\CompilerTemplate.t4" #>
运行 T4 模板后即可生成同名的 .designed.cs
文件,包含了语法分析器的实现。下图是一个简单的示例:
图 1 设计时语法分析器示例
语法分析器的核心就是一个 LR 语法分析器,具体实现请参见 LRParser。
三、语法分析的例子
以之前提到的算式文法为例,现在给出完整的实现:
enum Calc { Id, Add, Sub, Mul, Div, Pow, LBrace, RBrace }
Parser<Calc> parser = new();
// 定义产生式
parser.DefineProduction(Calc.E, Calc.Id).Action(c => c[0].Value);
parser.DefineProduction(Calc.E, Calc.E, Calc.Add, Calc.E)
.Action(c => (double)c[0].Value! + (double)c[2].Value!);
parser.DefineProduction(Calc.E, Calc.E, Calc.Sub, Calc.E)
.Action(c => (double)c[0].Value! - (double)c[2].Value!);
parser.DefineProduction(Calc.E, Calc.E, Calc.Mul, Calc.E)
.Action(c => (double)c[0].Value! * (double)c[2].Value!);
parser.DefineProduction(Calc.E, Calc.E, Calc.Div, Calc.E)
.Action(c =>
{
return (double)c[0].Value! / (double)c[2].Value!;
});
parser.DefineProduction(Calc.E, Calc.E, Calc.Pow, Calc.E)
.Action(c => Math.Pow((double)c[0].Value!, (double)c[2].Value!));
parser.DefineProduction(Calc.E, Calc.LBrace, Calc.E, Calc.RBrace)
.Action(c => c[1].Value);
// 定义运算符优先级。
parser.DefineAssociativity(AssociativeType.Left, Calc.Add, Calc.Sub);
parser.DefineAssociativity(AssociativeType.Left, Calc.Mul, Calc.Div);
parser.DefineAssociativity(AssociativeType.Right, Calc.Pow);
parser.DefineAssociativity(AssociativeType.NonAssociate, Calc.Id);
IParserFactory<Calc> factory = Parser.GetFactory();
string text = "1 + 20 * 3 / 4 * (5 + 6)";
Tokenlizer<Calc> tokenlizer = /* 之前词法分析中构造的词法分析器 */
ITokenParser<Calc> parser = factory.CreateParser(tokenizer);
Console.WriteLine(parser.Parse().Value);
// 输出为: 166
相应的设计时语法分析器定义为:
/// <summary>
/// 用于单元测试的计算器控制器。
/// </summary>
[ParserLeftAssociate(Calc.Add, Calc.Sub)]
[ParserLeftAssociate(Calc.Mul, Calc.Div)]
[ParserRightAssociate(Calc.Pow)]
[ParserNonAssociate(Calc.Id)]
public partial class TestCalcParser : ParserController<Calc>
{
[ParserProduction(Calc.E, Calc.Id)]
private object? IdAction()
{
return this[0].Value;
}
[ParserProduction(Calc.E, Calc.E, Calc.Add, Calc.E)]
[ParserProduction(Calc.E, Calc.E, Calc.Sub, Calc.E)]
[ParserProduction(Calc.E, Calc.E, Calc.Mul, Calc.E)]
[ParserProduction(Calc.E, Calc.E, Calc.Div, Calc.E)]
[ParserProduction(Calc.E, Calc.E, Calc.Pow, Calc.E)]
private object? BinaryAction()
{
double left = (double)this[0].Value!;
double right = (double)this[2].Value!;
return this[1].Kind switch
{
Calc.Add => left + right,
Calc.Sub => left - right,
Calc.Mul => left * right,
Calc.Div => left / right,
Calc.Pow => Math.Pow(left, right),
_ => throw CommonExceptions.Unreachable(),
};
}
[ParserProduction(Calc.E, Calc.LBrace, Calc.E, Calc.RBrace)]
private object? BraceAction()
{
return this[1].Value;
}
}
现在,完整的语法分析器已经成功构造出来,相关代码都可以在这里找到。
作者:CYJB
出处:http://www.cnblogs.com/cyjb/
GitHub:https://github.com/CYJB/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。