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

Visual Studio .NET 的 Coco 自定义工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (30投票s)

2005年10月29日

CPOL

4分钟阅读

viewsIcon

138540

downloadIcon

722

从 Visual Studio 内部直接使用屡获殊荣的 Coco 编译器的编译器。

Sample Image - vsCoco.png

引言

我曾发表过一两篇关于公式求值的文章,到目前为止,所有的程序都是手动编写的。对于最近的一个项目,我需要解析更复杂的语法,而且我真的需要一些帮助。

我上网看了看,发现了一个名为 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日 - 修正了文章中的一些错误
© . All rights reserved.