允许通过设计时 T4 模板生成词法分析器和语法分析器的实现。
-
通过 nuget 依赖运行时 Cyjb.Compilers.Runtime。
-
通过 nuget 依赖生成器 Cyjb.Compilers.Design,注意请如下指定引用配置,可以正常编译项目并避免产生运行时引用。
<ItemGroup>
<PackageReference Include="Cyjb.Compilers.Design" Version="1.0.5">
<GeneratePathProperty>True</GeneratePathProperty>
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
- 编写词法分析器的控制器类,例如:
using Cyjb.Compilers.Lexers;
enum Calc { Id, Add, Sub, Mul, Div, Pow, LBrace, RBrace }
[LexerSymbol("\\+", Kind = Calc.Add)]
[LexerSymbol("\\-", Kind = Calc.Sub)]
[LexerSymbol("\\*", Kind = Calc.Mul)]
[LexerSymbol("\\/", Kind = Calc.Div)]
[LexerSymbol("\\^", Kind = Calc.Pow)]
[LexerSymbol("\\(", Kind = Calc.LBrace)]
[LexerSymbol("\\)", Kind = Calc.RBrace)]
[LexerSymbol("\\s")]
// 必须是部分类,且继承自 LexerController<Calc>
public partial class CalcLexer : LexerController<Calc>
{
/// <summary>
/// 数字的终结符定义。
/// </summary>
[LexerSymbol("[0-9]+", Kind = Calc.Id)]
public void DigitAction()
{
Value = int.Parse(Text);
Accept();
}
}
或者语法分析器的控制器类,例如:
using Cyjb.Compilers.Parsers;
[ParserLeftAssociate(Calc.Add, Calc.Sub)]
[ParserLeftAssociate(Calc.Mul, Calc.Div)]
[ParserRightAssociate(Calc.Pow)]
[ParserNonAssociate(Calc.Id)]
// 必须是部分类,且继承自 ParserController<Calc>
public partial class CalcParser : 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;
}
}
- 添加与词法分析器同名的 tt 文件,内容如下:
<#@ include file="$(PkgCyjb_Compilers_Design)\content\CompilerTemplate.t4" #>
运行 T4 模板后即可生成同名的 .designed.cs
文件,包含了词法或语法分析器的实现。
通过 CalcLexer.Factory
或 CalcParser.Factory
即可访问创建词法/语法分析器的工厂类。
词法分析器使用 LexerSymbol 特性声明终结符,使用的正则表达式的定义与 C# 正则表达式一致,但不包含定位点、捕获、Lookaround、反向引用、替换构造和替代功能。
正则表达式支持通过 /
指定向前看符号,支持指定匹配的上下文,并在执行动作时根据需要切换上下文。
如果前缀可以与多个正则表达式匹配,那么:
- 总是选择最长的前缀。
- 如果最长的可能前缀与多个正则表达式匹配,总是选择先定义的正则表达式。
支持使用 LexerContext 或 LexerInclusiveContext 特性声明上下文或包含型上下文,在声明符号时可以通过 <ContextName>foo
指定生效的上下文。
支持使用 LexerRegex 特性声明公共正则表达式,在声明符号时可以通过 foo{RegexName}bar
引用公共正则表达式。
支持使用 LexerRejectable 特性开启 Reject 动作的支持。
语法分析器使用 LALR 语法分析实现,使用 ParserProduction 特性声明产生式,并且可以通过 SymbolOption 的 Optional
、ZeroOrMore
和 OneOrMore
支持简单的子产生式,其功能类似于正则表达式中的 A?
、A*
和 A+
。
支持使用 ParserNonAssociate、ParserLeftAssociate 和 ParserRightAssociate 声明符号的结合性。
默认使用首个出现的非终结符作为起始符号,也支持使用 ParserStart 指定起始符号。起始符号可以指定多个,并通过在语法分析器的 Parse
方法的参数来选择希望使用的起始符号。
还可以使用 ParseOption 指定起始符号的扫描方式。
欢迎访问我的博客获取更多信息。
C# 词法分析器系列博文
- C# 词法分析器(一)词法分析介绍
- C# 词法分析器(二)输入缓冲和代码定位
- C# 词法分析器(三)正则表达式
- C# 词法分析器(四)构造 NFA
- C# 词法分析器(五)转换 DFA
- C# 词法分析器(六)构造词法分析器
- C# 词法分析器(七)总结
C# 语法法分析器系列博文