可能来自对编写编程语言的兴趣,也可能是对创建IDE/编辑器的兴趣。我对“IDE/编辑器如何提供编程语言支持”充满了兴趣。主要原因之一是这是我们每天处理最多的工具。另一个原因可能是,哎,我们为什么没有国产的IDE(手动狗头)。Editor&IDE之前我在那篇文章《编辑器的自制》介绍过,如何制作一个简单的文本编辑器?这是一个比较简单的问题。对于一个好用的代码编辑器,我们对它的基本要求是:快速上手+语法高亮,然后是基本的文本编辑。但是,这是从我的角度来看的。在我看来:编辑应该做编辑应该做的事。对于一些开发者来说,他们会配置强大的各种支持功能,让它看起来像一个IDE。然后,它失去了快速启动的能力,或者说失去了部分快速启动的速度,有点遗憾。这种关于编辑器与IDE的讨论似乎有点偏颇。我知道我是IDE党,有公司提供的Jetbrains全家桶。每天我也会使用SublimeText、XiEditor、Vim、VSCode进行一些快速的文件修改和搜索。顺带一提,虽然我过去一直是Emacs的粉丝,但自从我自己写了Markdown编辑器后,我......幸运的是,下一步是制作我自己的代码编辑器,所以也许我不会这么觉得有罪的。也许,我已经在路上了。回到正题,如果是IDE(根据IDEA老用户的感受),那么我估计需要一些功能:语法高亮文本编辑子系统关联与集成跳转与引用分析intellisense重构quick-fix语言特性分析StructuredView...PS:仔细一看,在VSCode这种强大的编辑器中,大部分功能都已经内置了,而且是免费的。你也只需要一个,无需启动多个不同的IDE,节省硬盘空间。笑~不过一般来说,这些函数都是依赖于词法分析的。有了这个支持,就可以进行其他操作了。语法分析对于开发工具来说,语法分析有几个重要的功能:语法高亮是指一种编辑器功能,可以根据术语类别显示不同的颜色和字体,以增强可读性。实现IntelliSense实现跳转和引用分析我粗略的查了一下大致可以分为四类:基于正则表达式的语法分析YAML形式的基于正则匹配的SublimeText:SublimeSyntaxfilesTextmate,VSCode基于JSON的正则匹配方法:LanguageGrammars生成基于解析器的中间代码(比如BNF)Jetbrins生成代码基于BNF:GrammarandParserSelf-madeDSLforgrammaranalysisVimbasedonregular+self-madeDSL:Vimdocumentation:syntax,Rustexamplehandwritten分析语法EclipseIDE提供了一个JFace编辑器,但它似乎是手写的:FAQHowdoIprovidesyntaxcoloringinaneditor?EmacsMode:ModeTutorial每一类都有自己的优缺点和编写难度。但是,总的来说,没有简单的方法。用正则规则实现语法分析对于正则方法,无论是SublimeText还是Textmate和基于Textmate语法规则的VSCode,都有一个明显的劣势:长,比如VCode的java.tmLanguage.json,在长度上,我看到的版本有1831行。表达式也有点繁琐:"comments":{"patterns":[{"captures":{"0":{"name":"punctuation.definition.comment.java"}},"match":"/\\*\\*/","name":"comment.block.empty.java"},{"include":"#comments-inline"}]},还有各种include关系等等。就是与SublimeText类似:comments:-match:/\*\*/scope:comment.block.empty.javapunctuation.definition.comment.java-include:scope:text.html.javadoc-include:comments-inlineSee如果你看看到这里,您可能会怀疑他们建立了语法联盟。但是,yaml和json是与编程语言无关的东西。因此,VSCode和Atom可以基于Textmate语法规则快速建立对主流语言的词法分析,从而建立语法高亮支持。我们也可以说BNF是一种独立于编程语言的东西。但是,实际上我们在操作的时候,会加入一些编程语言特有的元素。语法分析器分析由于之前写过系统分析工具Coca和通用解析器Chapi,所以对BNF的词法分析也比较熟悉——其实并不难。唯一麻烦的是,写完之后还要写代码做一些转换,所以我们看Jetbrians插件的例子:COMMENT='regexp://[^\r\n]*'BLOCK_COMMENT='regexp:[/][*][^*]*[*]+([^/*][^*]*[*]+)*[/]'和antlr区别不大:WS:[\t\r\n\u000C]+->channel(HIDDEN);COMMENT:'/*'.*?'*/'->channel(HIDDEN);LINE_COMMENT:'//'~[\r\n]*->channel(隐);然后,就是设计分析词法:functionParameters::=LPARENinputParametersRPARENoutputParameters?|INSUBGTinputParameters|outputParameters然后,在IDEA中,我们可以通过这个BNF文件生成对应的Lexer文件和代码。对于Antlr写的词法,Java部分的代码量在800左右。不过从两者阅读体验的对比来看,显然BNF会更友好一些。自制DSL语法分析没有写过Vim插件,有点遗憾。幸运的是,我还知道Vim是如何退出的。我使用Vim作为git的编辑器,也熟悉Vim编辑的一些常用快捷键。所以这部分语法高亮主要参考了Vim的文档和代码示例。在这里我找到了一个很好的中文翻译:syntaxhighlighting语法规则一般是:synvim关键字匹配规则,如:synregionrustCommentLinestart="//"end="$"contains=rustTodo,@SpellsynregionrustCommentLineDocstart="//\%(//\@!\|!\)"end="$"contains=rustTodo,@SpellsynregionrustCommentLineDocErrorstart="//\%(//\@!\|!\)"end="$"contains=rustTodo,@SpellcontainedsynregionrustCommentBlockmatchgroup=rustCommentBlockstart="/\*\%(!\|\*[*/]\@!\)\@!"end="\*/"contains=rustTodo,看起来还是常规的Match,比如Float:syncmatchrustFloatdisplay"\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\%([eE][+-]\=[0-9_]\+\)\=\(f32\|f64\)\="syncmatchrustFloatdisplay"\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE][+-]\=[0-9_]\+\)\(f32\|f64\)\="synmatchrustFloatdisplay"\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE][+-]\=[0-9_]\+\)\=\(f32\|f64\)“不过,从算法形式上来说,它胜过Textmate和Sublime,毕竟是高级的DSL。Emacs的编程语言语法分析模式包含了语法高亮的处理,所以对于这个高亮,我们需要编写emacslisp代码。如:(defvarrust-formatting-macro-opening-re[[:space:]\n]*[({[][[:space:]\n]*""正则表达式tomatchtheopeningdelimiterofaRustformattingmacro.")(defvarrust-start-of-string-re"\\(?:r#*\\)?\"""RegularexpressiontomatchthestartofaRustrawstring.")对于Eclipse来说,这个过程就更麻烦了,语言的高级支持是在词法分析中实现的开发工具接口之后,我们可以根据不同的IDE/编辑器定义的接口进行定制,这是一个复杂且具有挑战性的工作,对于不同的工具,它们的接口也是非常相关的,我不是每个人都一一知道API的,所以我只能以IDEA为例来展示一下,大概有两个主要原因:1.我每天都用Jetbrains相关的IDE;2.我已经有了部分代码语法高亮经过复杂的语法分析,我们可以然后快速输入一个简单的链接来高亮代码,关于高亮,我们可以快速做一个分类:key字。也就是编程语言的关键词,比如C语言中的32个关键字。身份标识。用户定义的字符串,如变量名、结构名、函数名等。特殊词法。重要的词汇。标识静态函数名等,提高识别度。下面是Go语言的一些关键字:""else""goto""package""switch""const""fallthrough""if""range""type""continue""for""import""return""var")"Go语言中的所有关键字。用于字体锁定。")因此,在这种场景下,无论是什么样的IDE,什么样的编辑器,都可以快速实现。跳转goto不同的开发工具有不同的跳转规则,不同的语言有自己的跳转方式。比如Emacs的go-mode定义了一系列的跳转:(let((m(define-prefix-command'go-goto-map)))(define-keym"a"#'go-goto-arguments)(define-keym"d"#'go-goto-docstring)(define-keym"f"#'go-goto-function)(define-keym"i"#'go-goto-imports)(define-keym"m"#'go-goto-method-receiver)(define-keym"n"#'go-goto-function-name)(define-keym"r"#'go-goto-return-values))和IDEA也提供了一系列接口来实现类似的功能,比如:gotoActionAliasMatchergotoClassContributorgotoSymbolContributorgotoFileContributorgotoRelatedProvider我们只需要分析光标的位置,它定义的语法,比如IDEA中的PSI,然后实现相应的逻辑。如:@Overridepublic@NotNullNavigationItem[]getItemsByName(Stringname,Stringpattern,Projectproject,booleanincludeNonProjectItems){List
