Visual Studio .NET 的 Coco 自定义工具






4.64/5 (30投票s)
从 Visual Studio 内部直接使用屡获殊荣的 Coco 编译器的编译器。

引言
我曾发表过一两篇关于公式求值的文章,到目前为止,所有的程序都是手动编写的。对于最近的一个项目,我需要解析更复杂的语法,而且我真的需要一些帮助。
我上网看了看,发现了一个名为 Coco/R 的项目,它来自林茨约翰内斯·开普勒大学。这是他们对该产品的描述:“Coco/R 将编译器描述(形式为带属性的语法(EBNF 语法,包含属性和语义动作))转换为扫描器和递归下降解析器……Coco/R 在学术界和工业界都得到了成功的应用。它结合了著名的 Unix 工具 Lex 和 Yacc 的功能。”
我使用 Coco 一段时间了,尽管它非常好,但我发现与它快速工作的过程令人沮丧,因为我必须手动运行它,而且它与 Visual Studio 的集成程度不高。
背景
任何想使用此工具的人都应该熟悉 EBNF 语法。网上有很多很好的介绍。
我还特别推荐阅读 Coco/R 编译器生成器用户手册。
安装 vsCoco
您需要下载并运行文件 vsCocoRegistration.exe。该注册应该可以很好地与 Visual Studio 2003 协同工作。
如果您想使用随附的示例,那么无需再做其他操作。
如果您想用它处理自己的文件,则必须将您的语法添加到项目中,并设置其属性为
- 生成操作:内容
- 自定义工具:Coco
- 命名空间:随意填写或留空
- 您也可以选择在项目中提供
Parser.frame
和/或Scanner.frame
文件,如果您想自定义它们的话。
如果它与您的版本不兼容,请在下面的论坛发帖。
如果您能够修复 vsCoCoRegistration
,请将代码发送给我:pascal_cp@ga$naye.com(请删除 $)。
使用 vsCoco
首先,您可以尝试玩一下我提供的下载中的计算器示例。该计算器可以计算 12+34*55/2
这样的公式。
该示例仅包含 5 行 C# 代码。
private void button1_Click(object sender, System.EventArgs e)
{
Parser p = new Parser(comboBox1.Text);
p.Parse();
textBox1.AppendText(">" + comboBox1.Text + "\r\n"
+ p.result.ToString() + "\r\n");
}
正如您所见,大部分逻辑必须在 Parser 对象中。
解析器是从此语法自动生成的
COMPILER calc
public double result = 0;
IGNORECASE
// The $L option let you compile directly within your grammar
// You can comment and uncomment the line to fit your development requirements.
$L
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
CHARACTERS
digit = "0123456789".
cr = '\r'.
lf = '\n'.
tab = '\t'.
TOKENS
number = digit {digit} ['.' {digit}].
// We don't use comments here but this is only a sample
COMMENTS FROM "//" TO cr lf
IGNORE cr + lf + tab
PRODUCTIONS
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
OPERAND<OUT val double>
= (. val = 0; .)
(
number (. val = Double.Parse(t.val,
NumberStyles.Float,
CultureInfo.InvariantCulture);
.)
| "(" EXPR<OUT val> ")"
).
// Priorities in FGL
//
// () (Parenthesis)
// 10 - (Unary neg)
// 09 * / (Multiply and Divide)
// 07 + - (Add and Subtract)
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
EXPR10<OUT val double>
= (. bool neg=false; .)
{
( '-' (. neg=!neg; .)
| '+' (. /*nothing to do*/ .)
)
}
OPERAND<OUT val> (. if (neg) val*=-1; .)
.
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
EXPR09<OUT val double>
=
EXPR10<OUT val>
{ (. double val2; .)
( '*'
EXPR10<OUT val2> (. val*=val2; .)
| '/'
EXPR10<OUT val2> (. val/=val2; .)
)
}
.
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
EXPR<OUT val double>
=
EXPR09<OUT val>
{ (. double val2; .)
( '+'
EXPR09<OUT val2> (. val+=val2; .)
| '-'
EXPR09<OUT val2> (. val-=val2; .)
)
}
.
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
calc
=
EXPR<OUT result>.
END calc.
这是一个相当标准的语法。如果我尝试用英语阅读它,它会说
- 此语法将生成一个名为 calc 的解析器。
- Calc 解析器返回表达式
- 表达式可以是和,或者,如果不是和,则可以是带符号数字的乘积。
- 乘法应该在加法之前完成,但如果减号和加号是符号,则它们的优先级更高。
正如您所见,比看起来要复杂得多。我很难描述这个语法的作用及其工作原理,这也不是我的目标。
我想与您分享的是这个工具,并希望如果您是这个主题的新手,能引起您对编译器生成器的兴趣。
它是如何工作的?
Coco 修改 - 1:#line
我对 Coco/R 进行了几项重大修改。
首先,我想在语法中进行跟踪。这是容易的部分,我修改了 Coco 源代码并添加了 $L 选项。如果您在语法的开头插入 $L,Coco 编译器将在您的代码中添加许多 #line。
例如
COMPILER calc
public double result = 0;
IGNORECASE
// The $L option let you compile directly within your grammar
// You can comment and uncomment the line to fit your development requirements.
$L
...
OPERAND<OUT val double>
= (. val = 0; .)
(
number (. val = Double.Parse(t.val,
NumberStyles.Float,
CultureInfo.InvariantCulture);
.)
| "(" EXPR<OUT val> ")"
).
将生成
void OPERAND(
#line 31 "C:\dotnet\vsCoco\Calculator\Calc.atg"
out double val
#line hidden
) {
#line 32 "C:\dotnet\vsCoco\Calculator\Calc.atg"
val = 0;
#line hidden
if (la.kind == 1) {
Get();
#line 34 "C:\dotnet\vsCoco\Calculator\Calc.atg"
val = Double.Parse(t.val,
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture);
#line hidden
} else if (la.kind == 2) {
Get();
EXPR(
#line 38 "C:\dotnet\vsCoco\Calculator\Calc.atg"
out val
#line hidden
);
Expect(3);
} else SynErr(9);
}
这些 #line
非常有用,Visual Studio IDE 能够很好地理解它,并允许您使用原始源语法调试生成的程序。
我认为它非常有用;您可以注释掉或取消注释 $L 行以适应您的开发需求。
Coco 修改 - 2:真正的 Visual Studio 自定义工具
我的第二个目标是直接从 Visual Studio 中运行 Coco 作为自定义工具,而不是必须使用批处理文件。
自定义工具的主要优点是,当源语法发生变化时,它会被自动调用,而不是在每次编译时都调用。
Visual Studio 发布了一个名为 IVsSingleFileGenerator
的接口。
该接口定义了两个方法
int DefaultExtension(out string)
int Generate(string, string, string, System.IntPtr[], out uint, Microsoft.VisualStudio.Shell.Interop.IVsGeneratorProgress)
提供这两个接口是使 Visual Studio 自定义工具工作的基本内容。
有了好的信息,这毕竟是相当直接的。我使用了并修改了 GotDotNet 用户示例:BaseCodeGeneratorWithSite。
Coco 修改 - 3:Visual Studio 自定义工具的安装程序
既然我们有了一个可以作为 Visual Studio 插件的 DLL,您就需要注册它。这可能会比预期的要困难得多。幸运的是,我读了 Michael McKechney 写的题为 Visual Studio 自定义工具的自动注册 的精彩文章。
我修改了他的示例程序,并制作了 vsCocoRegistration.exe。
已知bug
vsCocoRegistration
目前并非对所有版本的 Visual Studio .NET 都有效。这仅仅是修改注册表 GUID 的问题,但我没有足够多的版本来测试它。所以,请随时在下面的论坛提问。
链接
- 林茨大学的 Hanspeter Mössenböck、Albrecht Wöß、Markus Löberbauer 编写的 Coco/R 编译器生成器
- 来自维基百科的 LL 解析器(自由的百科全书)。
- GotDotNet 用户示例:BaseCodeGeneratorWithSite
- Michael McKechney 写的关于“Visual Studio 自定义工具的自动注册”的文章。
历史
- 2005年10月29日 - 首次发布
- 2005年11月1日 - 修正了文章中的一些错误