回顾
在前面两篇中,我们研究了ManagedMyC这个例子的代码结构,了解了一个语言服务基本组成以及Managed Babel在语言服务中是如何工作的。在接下来的两篇中,我将从一个空的集成模式Package开始,一步步创建一个简单的语言服务:在这个语言中,没有语法,我们只是简单的利用着色器区分数字和字母,该服务定义的文件后缀为".sls"。
逐步开始
一、创建空Package
语言服务的载体是包,因此我们需要创建一个空的Package来承载。创建空包可以通过向导完成,这里不再累述,读者可参考:LearnVSXNow!-#2 创建一个空的VS Package。我将命名这个包为SimpleLSHost。
二、代码结构的建立
1.仿照ManagedMyC为工程建立相应的目录结构,这有助于更好的管理代码,可以像这样建立:
2.添加Babel的代码。定位到C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\Source\CSharp\Babel,可以看到Babel的代码文件。如果你SDK的安装路径与我不同,你需要自己查找。将这些cs文件全部拷贝到当前工程下的ManagedBabel文件夹内,然后在工程中的ManagedBabel下添加这些.cs文件。之所以这样做,是因为根据我的经验,Babel的代码往往是要更改的,所以我们干脆拷贝一份出来。
3.在UserSupplied目录下添加两个.cs:Configuration.cs和LanguageService.cs,把SimpleLSHostPackage.cs转移至UserSupplied下。
4.找到ManagedMyC所在目录,我的是:...\AppData\Local\VSSDK_9.0_1.1\Example.ManagedMyC_F3D35F91\,把其中Generated里面的文件拷贝到我们自己的Generated里面,并把他们添加到工程中的Generated中。完成以上步骤后,目录结构如下:
三、快速建立的语言服务
打开UserSupplied\LanguageService.cs,添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; namespace Company.SimpleLSHost.UserSupplied { [Guid( "EDA0749C-7AA5-43b8-97CC-FBD21040AD7A" )] class LanguageService : Babel.BabelLanguageService { public override string GetFormatFilterList() { return "Sls File (*.sls)\n*.sls" ; } } } |
这里的代码便是我们的LS类,可以看到我们继承了Babel.BabelLanguageService,并且给这个服务指定了一个GUID,可以通过IDE的Tool->Create GUID这个工具来随即生成一个GUID:
四、为Package添加注册信息
为SimpleLSHostPackage加上如下属性定义,并继承自Babel.BabelPackage,删除自动生成的构造函数和初始化函数。
1 2 3 4 5 6 7 8 9 10 11 12 | [ProvideService( typeof (Company.SimpleLSHost.UserSupplied.LanguageService))] [ProvideLanguageExtension( typeof (Company.SimpleLSHost.UserSupplied.LanguageService), ".sls" )] [ProvideLanguageService( typeof (Company.SimpleLSHost.UserSupplied.LanguageService), "Simple LS" , 0, CodeSense = true , EnableCommenting = true , MatchBraces = true , ShowCompletion = true , ShowMatchingBrace = true , AutoOutlining = true , EnableAsyncCompletion = true , CodeSenseDelay = 0)] [Guid(GuidList.guidSimpleLSHostPkgString)] |
五、补充程序集引用
添加如下两个程序集:Microsoft.VisualStudio.Package.LanguageService.9.0、Microsoft.VisualStudio.TextManager.Interop.8.0
六、编辑工程设置
之前我们提到过lexer.lex和parser.y。这两种格式是微软定义的。用户分别用lex和yacc语法的特殊语言来编辑这两个文件。这两者的鼻祖就是lex和yacc,最初出现在Unix中,后来linux出现了两个工具flex和bison,它们负责将lex和yacc定义的文件编译成C代码。在VS2008SDK中,微软提供了类似的两个工具MPLex、MPPG用于编译lexer.lex和parser.y(可以在SDK工具中找到它们),输出文件分别是lexer.cs和parser.cs,它们都是C#代码,于是它们就可以和我们的其他C#代码共同被C#编译器编译运行了。但是IDE并不认识lexer.lex和parser.y,我们需要手动为这两个文件设置编译器,然而微软在这里犯了个错误:
如上图,在可选的编译行为中,我们找不到MPLexCompile和MPPGCompile,而且手动输入是无效的。因此,我们只能手动修改工程的配置文件:保存工程,右击SimpleLSHost工程,选择Unload Project,再次右击工程,选择Edit SimpleLSHost.csproj,找到如下代码段:
1 2 3 4 | < ItemGroup > < None Include="Generated\lexer.lex" /> < None Include="Generated\parser.y" /> </ ItemGroup > |
修改为:
1 2 3 4 | < ItemGroup > < MPLexCompile Include="Generated\lexer.lex" /> < MPPGCompile Include="Generated\parser.y" /> </ ItemGroup > |
保存后,右击工程,选择Reload Project。我们再看lexer.lex和parser.y的属性->Build Action,分别设置成了MPLexCompile、MPPGCompile。这样在每次编译的时候,都会使用MPLex和MPPG编译它们,并且输出的文件可以在obj文件夹中看到。
七、编译查错
现在我们可以ReBuild一下,看看结果,一堆错误是吧!没关系,我们慢慢看。
最多的问题是,找不到语言服务类,这个问题就是我在上一篇中提到的BabelPackage的一个缺点造成的,BabelPackage代码指定加载的是一个Babel.LanguageService,然而我们的LanguageService是Company.SimpleLSHost.UserSupplied.LanguageService!当然找不到啦!把Babel.LanguageService改成Company.SimpleLSHost.UserSupplied.LanguageService即可。剩下的问题是:我们还没有编辑过Configuration.cs,我们也没有实现一个IASTResolver接口的Resolver。首先,在UserSupplied目录下,添加一个Resolver.cs,我们还是偷偷懒,把ManagedMyC中Resolver.cs的代码拷贝过来,注意只要拷贝类部分,名字空间不必相同,最后AuthoringScope.cs中添加名字空间,using Company.SimpleLSHost.UserSupplied。最后,将Configuration.cs中的代码用ManagedMyC的Configuration.cs的代码覆盖,注意,这里全都要拷贝,包括名字空间。编译,直到没有错误为止。一般这里如果还有错误,基本上是名字空间不对应,读者可以自己检查。
八、稍作改动
现在我们要对UserSupplied\Configuration.cs稍作改动,将下面代码覆盖原来的代码:
1 2 | public const string Name = "Simple LS" ; public const string Extension = ".sls" ; |
然后可以用这两个常量替换package注册信息中的常量:
1 2 | [ProvideLanguageExtension( typeof (Company.SimpleLSHost.UserSupplied.LanguageService), Babel.Configuration.Extension)] [ProvideLanguageService( typeof (Company.SimpleLSHost.UserSupplied.LanguageService), Babel.Configuration.Name, 0, |
运行调试
重新编译,并按F5运行,打开一个测试文件1.sls,结果如下:
可以看到,这个语言服务的结果更ManagedMyC一样。因为我们的核心lexer和parser是从ManagedMyC中拷贝来的,Configuration也是用的ManagedMyC的,当然结果一样了,唯一不同的是我们这个包注册了不同的后缀而已!
小结
本文我们从一个空的Package开始逐步构建了一个语言服务框架,并且应用了ManagedMyC的lexer和parser,作了简单的测试。本文所提到的步骤是今后构建语言服务的基础,大家也可以参考”How Do I?”视频的内容。在下一篇中,我们将在此基础上尝试修改lexer和parser,以及Configuration。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义