zoukankan      html  css  js  c++  java
  • VSX开发之语言服务系列(3)——再来看看ManagedMyC

    回顾

    在前面一篇中,我们浏览了ManagedMyC这个例子,其中的代码虽然不算庞大,但是有很多令人困惑的地方。这一篇中,我们来再来慢慢看看代码。

    Babel

    其实一直很困惑,为什么微软找这么个名字。反正不管那么多,只要记住,所谓的Babel(这里特指托管的Babel)就是帮助我们开发语言服务的一系列文件。在ManagedMyC这个例子中它们被放在ManagedBabel文件夹中,而且是以连接的形式。实际上这里只是为了防止更改文件而已。现在我们先看如下几个文件:

    Package.cs

    Babel提供了一个继承自Microsoft.VisualStudio.Shell.PackageIOleComponent的类BabelPackage。有了这个类,我们在编写Package类的时候只需要为其提供一个GUID,其他的全部继承便可以了。的确是个方便的方式。在构造函数中声明了一个委托、并将语言服务加到当前这个服务容器中:

            protected BabelPackage()
            {
                ServiceCreatorCallback callback = new ServiceCreatorCallback(
     	...
                // proffer the LanguageService
                (this as IServiceContainer).AddService(typeof(Babel.LanguageService), callback, true);
            }

    当Package被载入的时候,它相应的服务也会载入,这里指定加载一个叫LanguageService的类,并且在加载时调用callback回调,回调函数如下:

                   delegate(IServiceContainer container, Type serviceType)
                    {
                        if (typeof(Babel.LanguageService) == serviceType)
                        {
                            Babel.LanguageService language = new Babel.LanguageService();
                            language.SetSite(this);
    
                            // register for idle time callbacks
                            IOleComponentManager mgr = GetService(typeof(SOleComponentManager)) as IOleComponentManager;
                            if (componentID == 0 && mgr != null)
                            {
                                OLECRINFO[] crinfo = new OLECRINFO[1];
                                crinfo[0].cbSize = (uint)Marshal.SizeOf(typeof(OLECRINFO));
                                crinfo[0].grfcrf = (uint)_OLECRF.olecrfNeedIdleTime |
                                                              (uint)_OLECRF.olecrfNeedPeriodicIdleTime;
                                crinfo[0].grfcadvf = (uint)_OLECADVF.olecadvfModal |
                                                              (uint)_OLECADVF.olecadvfRedrawOff |
                                                              (uint)_OLECADVF.olecadvfWarningsOff;
                                crinfo[0].uIdleTimeInterval = 1000;
                                int hr = mgr.FRegisterComponent(this, crinfo, out componentID);
                            }
    
                            return language;
                        }
                        else
                        {
                            return null;
                        }
                    }

    先判断载入的服务是否是LanguageService,如果是则构造一个实例,并挂载这个服务,接着注册IOleComonent对象,并返回这个服务的实例。

    接着我们来讨论一下IOleComponent。大家一定想实现一些诸如错误提示的功能,大家也许也注意到,当我们在VS中以相对快的速度编写C#代码的时候,即便此时有语法错误,在代码输入的时候也不会提示,但是一旦我们停下来,这些错误便感知出来了。这是因为像错误提示这类功能是在语言服务“空闲”的时候进行的。将我们的包继承自IOleComponent并注册时,就是告诉IDE,这个包是支持空闲时的。在代码中的#region IOleComponent Members部分的代码便是实现了IOleComponent,而注册的动作是靠IOleComponentManager完成的。

    另外,应当注意到的是,这里的BabelPackage有个缺点,它指定了语言服务类的名字就是Babel.LanguageService,那么就是说我们必须要有个Babel.LanguageService的类才能使这个类工作!!如果你想要自己定义一个LS的名字,或者一个更常见的情况:名字空间不一样的话,要记得到这里来改代码。

    LanguageService.cs

    Microsoft.VisualStudio.Package.LanguageService是个抽象类,而我们的LanguageService必须继承自这个类,并且为了实现某些功能你还必需重写其中的方法。Babel为我们构建了一个BabelLanguageService,但是没有GUID,在简单的情况下,只要继承这个BabelLanguageService并且为他提供一个GUID就可以实现我们自己的LanguageService。UserSupplied\LanguageService.cs就是这样做的:

        [Guid("73DA124B-2CC5-4f79-A0DB-B11B6AAA2BE5")]
        class LanguageService : BabelLanguageService
        {
            public override string GetFormatFilterList()
            {
                return "MyC File (*.myc)\n*.myc";
            }
        }

    既然BabelLanguageService这么强大,我们就来剖析它吧。

    首先要花一些篇幅解释一下语法着色的工作原理,请看下图,此图是根据我的理解画的:

    image

    如图,一个LanguageService包含一个Colorizer对象,叫做着色器对象,负责对代码编辑区中的文本着色,LS(LanguageService缩写,下同)基类有个GetColorizer方法,返回默认的Colorizer实例,LS在内部调用这个方法。这个方法是可以覆盖的,但是我不建议你覆盖,因为要自己实现一个Colorizer应该不太简单,默认的应该够了。着色器对象虽然负责着色,但是它也“只会”着色,着色的原则是基于这样的规则:一个index对应一个颜色(确切的说是一个index对应一个

    IVsColorableItemIVsColorableItem可以通过IDE的Font and Color修改的,包括了前景色,背景色,加粗,划线)。也就是说,着色器需要通过调用LS中的GetColorableItem虚方法判断应该为哪个index用上什么颜色,上图的“判断着色”就是这个虚方法,我们需要在LS中重写这个方法;接下来的问题是着色器怎么知道哪些文本对应一个什么index呢?这要借助于Scanner扫描器,扫描器必须实现IScanner接口,这个接口有两个方法:ScanTokenAndProvideInfoAboutItSetSource,着色器会把文本通过SetSource传给扫描器,然后不断调用ScanTokenAndProvideInfoAboutIt来获取每个index。实际上扫描器返回的index不仅仅是个整型值,而是一个TokenInfo的结构,其中包含了某段文本的位置信息,类型信息,触发器信息,以及这个index(说到这里我终于打算把这个index改口为TokenColor枚举了),这段文本也有个特定的名称,称为token(标记)。OK,有点乱,我们来理顺一下思路,Colorizer将文本通过IScannerSetSource方法传给扫描器,扫描器需要实现IScanner,接着Colorizer反复调用ScanTokenAndProvideInfoAboutIt直到扫描器返回false,通过这种多次的调用,Colorizer获得了一些标记,Colorizer对每个标记中的TokenColor调用GetColorableItem方法判断这个标记应该是什么颜色。于是整个文本着色完成。

    标记的概念:由于标记的概念十分重要,我打算引用MSDN上的解释:

    解析器是语言服务的核心。解析器把文本分割成语法上的标记,然后为其赋予语义上的含义

    下面是一段C#代码:

    namespace MyNamespace
    {
        class MyClass
        {
            public void MyFunction(int arg1)
            {
                int var1 = arg1;
            }
        }
    }
    这段文本的标记可以是:

    Token Name

    Token Type

    namespace, class, public, void, int

    keyword

    =

    operator

    { } ( ) ;

    delimiter

    MyNamespace, MyClass, MyFunction, arg1, var1

    identifier

    MyNamespace

    namespace

    MyClass

    class

    MyFunction

    method

    arg1

    parameter

    var1

    local variable

    引自:MSDN:Language Service Parser and Scanner (Managed Package Framework)

    说到这里,其实已经解释完了BabelLanguageService类中#region Custom Colors部分的代码了,另外GetScanner方法就是Colorizer用来获得Scanner的方法,Babel为我们重写了这个方法,并实现了一个简单的LineScanner(在LineScanner.cs中),我们现在还不必深究LineScanner。

    此外,其他重写的函数:

    函数

    说明

    GetLanguagePreferences

    语言服务偏好

    Name

    语言服务的名字

    ParseSource

    每次解析文本时由LS调用,十分重要,以后会解释

    AuthoringScope.cs

    这里包含的类是AuthoringScope,是处理用户的操作的类,这些操作包括:

    函数

    说明

    GetDataTipText

    鼠标在某个标记上的时候显示ToolTip

    GetDeclarations

    自动完成操作、成员感知

    GetMethods

    方法参数感知

    Goto

    Goto操作

    如果要实现智能感知的功能,那么这个类是必须的。

    其他

    其他文件的内容有些比较单一,有些涉及到深层次的原理,这里只列出简介,以后的章节将涉及:

    文件

    说明

    Configuration.cs

    部分类。顾名思义,这个类是用来配置语言服务的某些参数,比如语言服务的名字、文件后缀以及着色器所需要的信息,即一个TokenColor如何对应一个IVsColorableItem。Babel在这里为我们实现了一些通用的方法,我们在编写配置的时候只要调用这些方法即可。值得注意的是,用CreateColor方法创建的IVsColorableItem,是会出现在Font and Color中的,这是个很不错的集成特性。

    Declaration.cs\ Declarations.cs

    通过重写Microsoft.VisualStudio.Package.Declarations,构造智能感知的感知项和感知项集合

    IASTResolver.cs

    Babel单独定义的实现智能感知的接口,开发人员可以实现这个接口以支持智能感知

    IScanner.cs

    注意这里与之前提到的IScanner接口不一样。扫描器其实有两个,一个给着色器用,一个给解析器用,但是核心的扫描规则只有一个,于是需要两种接口,这个文件包含了这两种接口分别是IColorScanAScanner。详细的解释将在以后的章节中提到。

    Method.cs\ Methods.cs

    通过重写Microsoft.VisualStudio.Package. Methods,构造函数参数感知的感知的参数等内容

    Parser.cs

    部分类,与parser.y生成的Parser.cs构成解析器类,LS通过这个实例完成对文本的解析。这里的代码是对parser.y的扩展。(可以在工程的obj下找到由parser.y生成的parser.cs)

    ParserStack.cs\Rule.cs\

    State.cs

    解析器内部需要的数据结构定义

    ShiftReduceParser.cs

    解析器需要的“移进”和“规约”行为的详细实现,Babel在这个类中为我们完成的是行为代码,我们在parser.y需要的是语法定义和配置。

    Source.cs

    继承自Microsoft.VisualStudio.Package.Source,是对Source对象的扩展。Babel在这里扩展了括号匹配和注释符号。

    UserSupplied

    UserSupplied文件夹下的文件大多是开发人员可以对Babel的定制扩展,下面以表格的形式列出:

    文件

    说明

    Configuration.cs

    部分类。与Babel的Configuration共同构成配置,详见上文

    LanguageService.cs

    语言服务类,简单继承BabelLanguageService,提供一个GUID,注意这里的类名必须是LanguageService,名字空间必须是Babel,如果想要定制,需要修改Babel中的部分代码。

    Package.cs

    包对象

    Resolver.cs

    用户实现IASTResolver,以支持智能感知。

    Generated

    Generated文件夹下的文件,值得深究的将是lexer.lexparser.y,他们分别是扫描器和解析器的核心“配置文件”,分别是以lex风格和Yacc风格的代码形式呈现的,在以后的章节中将专门讨论。另外,ErrorHandler.csLexDefs.cs是用来搜集目标代码中的语法错误的。

    小结

    在本篇中,我们详细看了示例程序的所有代码文件,并且我对其中的某些代码做了详细的分析。看完本篇后,应当对语言服务有了更深的理解,尤其应该理解代码着色的实现原理,我建议读者再深入研究一下Configuration.cs,这样可以完全理解代码着色。另外标记的概念尤为重要,在以后的篇幅中将一直提到。最后我推荐读者阅读一下MSDN中以下两个主题:Syntax Colorizing (Managed Package Framework)、Language Service Parser and Scanner (Managed Package Framework)。

  • 相关阅读:
    HDU 2108 Shape of HDU (判断是不是凸多边形 叉乘)
    三,对于printf函数和C语言编程的初步拓展
    二,养成良好的写代码习惯
    一,彻底理解第一个C语言程序 Hello World
    归并排序(看了别人的博客明白了也写个博客,希望这样不算抄袭~)
    汉诺塔
    最小生成树
    堆排序
    二叉排序树
    双关键字快排
  • 原文地址:https://www.cnblogs.com/P_Chou/p/1738534.html
Copyright © 2011-2022 走看看