新聞中心
或許是出自于對(duì)編寫編程語(yǔ)言的興趣,又或許是對(duì)于創(chuàng)建 IDE/編輯器的興趣,對(duì)于『IDE/編輯器是如何提供編程語(yǔ)言的支持』,我充滿了興趣。其中的一個(gè)主要原因是,這是每天我們打交道最多的工具,另外一個(gè)原因可能是,咦,我們?cè)趺礇]有國(guó)產(chǎn)的 IDE(手動(dòng)狗頭)。

編輯器 & IDE
先前,我已經(jīng)在那篇《編輯器的自制》中介紹了,怎么去創(chuàng)建一個(gè)簡(jiǎn)單的文本編輯器?這是一個(gè)相對(duì)簡(jiǎn)單的問題。對(duì)于一個(gè)可用的代碼編輯器來(lái)說,我們對(duì)它的基本訴求是:快速啟動(dòng) + 語(yǔ)法高亮,然后能進(jìn)行基本的文本編輯。不過呢,這是以我角度來(lái)看待問題的,我的想法里:一個(gè)編輯器,就干好一個(gè)編輯器應(yīng)該做的事情。對(duì)于一些開發(fā)人員而言,他/她們會(huì)配置上強(qiáng)大的各種支持功能,以使它看上去像是一個(gè) IDE。而后呢,它失去了快速啟動(dòng)的能力,或者失去了一部分的快速啟動(dòng)的速度,這便是有些遺憾的。
關(guān)于編輯器與 IDE 的這一一點(diǎn)的討論,似乎會(huì)有些偏頗。我自知我是一個(gè) IDE 黨,擁有公司提供的 Jetbrains 全家桶。日常我也會(huì)使用 Sublime Text、Xi Editor、Vim、VS Code 進(jìn)行一些快速的文件修改和查找。順便提一句,盡管過去我是一個(gè) Emacs 粉,但是自我寫了自己的 Markdown 編輯器之后,我已經(jīng)……。好在下一步,我打算做一個(gè)自己的代碼編輯器,這樣一來(lái),也許就不會(huì)那么內(nèi)疚了?;蛟S呢,我已經(jīng)在實(shí)現(xiàn)的路上了。
回到正題上,如果是一個(gè) IDE 的話(以 IDEA 老用戶的感受),那么我估摸著需要這么一些功能:
- 語(yǔ)法高亮
- 文本編輯
- 子系統(tǒng)關(guān)聯(lián)與集成
- 跳轉(zhuǎn)與引用分析
- 智能感知
- 重構(gòu)
- 快速修復(fù)
- 語(yǔ)言特性分析
- 結(jié)構(gòu)化視圖
- ……
PS:仔細(xì)一看,諸如于 VSCode 這一類強(qiáng)大的編輯器里,已經(jīng)內(nèi)置了大部分的功能,而且它還是免費(fèi)的。你還只需要一個(gè),不需要啟動(dòng)多個(gè)不同的 IDE,還省下了硬盤空間。笑~
不過,總的來(lái)說,這些功能都依賴于詞法分析,有了這個(gè)支持,才能進(jìn)行其它部分的操作。
語(yǔ)法分析
對(duì)于開發(fā)工具來(lái)說,語(yǔ)法分析有幾個(gè)重要的功能:
- 語(yǔ)法高亮,是指根據(jù)術(shù)語(yǔ)類別來(lái)顯示不同的顏色與字體以增強(qiáng)可讀性的一種編輯器特性。
- 實(shí)現(xiàn)智能感知
- 實(shí)現(xiàn)跳轉(zhuǎn)和引用分析
從我粗糙的調(diào)查來(lái)看,大致可以分析為四類:
- 基于正則表達(dá)式來(lái)實(shí)現(xiàn)語(yǔ)法分析
Sublime Text 基于 YAML 形式的正則匹配方式:Sublime Syntax files
Textmate、VS Code 基于 JSON 的正則匹配方式:Language Grammars
- 基于語(yǔ)法分析器(如 BNF)生成中間代碼
Jetbrins 基于 BNF 生成代碼的方式:Grammar and Parser
- 自制 DSL 進(jìn)行語(yǔ)法解析
Vim 基于正則 + 自制 DSL:Vim documentation: syntax、Rust 示例
- 手寫解析語(yǔ)法
Eclipse IDE 提供了個(gè) JFace editor,但是似乎是要手寫:FAQ How do I provide syntax coloring in an editor?
Emacs Mode: ModeTutorial
每一類各自有各自的優(yōu)缺點(diǎn)和編寫難度。但是,總的來(lái)說,沒有一個(gè)方式是簡(jiǎn)單的。
正則實(shí)現(xiàn)語(yǔ)法分析
對(duì)于正則方式來(lái)說,不論是 Sublime Text 還是 Textmate 及基于 Textmate 語(yǔ)法規(guī)則的 VS Code,它們都有一個(gè)顯著的缺點(diǎn):長(zhǎng),如 VCode 的java.tmLanguage.json,從長(zhǎng)度上來(lái)說,我看到的這個(gè)版本有 1831 行。表達(dá)方式也有些繁瑣:
- "comments": {
- "patterns": [
- {
- "captures": {
- "0": {
- "name": "punctuation.definition.comment.java"
- }
- },
- "match": "/\\*\\*/",
- "name": "comment.block.empty.java"
- },
- {
- "include": "#comments-inline"
- }
- ]
- },
其中還有各種 include 關(guān)系等。對(duì)于 Sublime Text 也是類似的:
- comments:
- - match: /\*\*/
- scope: comment.block.empty.java punctuation.definition.comment.java
- - include: scope:text.html.javadoc
- - include: comments-inline
看了看,是不是會(huì)懷疑他們建立了語(yǔ)法同盟。
但是呢,yaml 和 json 是一個(gè)編程語(yǔ)言無(wú)關(guān)的東西。所以,VS Code 和 Atom 可以基于 Textmate 語(yǔ)法規(guī)則,快速建立對(duì)于主流語(yǔ)言的詞法分析,從而建立了語(yǔ)法高亮的支持。
我們也可以說 BNF 是一種編程語(yǔ)言無(wú)關(guān)的東西。但是,實(shí)際上在我們操作的時(shí)候,就會(huì)加入一些編程語(yǔ)言特定的要素。
語(yǔ)法分析器分析
由于先前編寫系統(tǒng)分析工具 Coca 和通用語(yǔ)法分析器 Chapi ,我對(duì)于 BNF 的詞法也是頗為上手的——實(shí)際上不難。唯一麻煩的地方就是,寫完之后,我們要編寫代碼做一些轉(zhuǎn)換,所以讓我們來(lái)看看 Jetbrians 插件的示例:
- COMMENT = 'regexp://[^\r\n]*'
- BLOCK_COMMENT = 'regexp:[/][*][^*]*[*]+([^/*][^*]*[*]+)*[/]'
這一點(diǎn)上和 antlr 沒有太大的區(qū)別:
- WS: [ \t\r\n\u000C]+ -> channel(HIDDEN);
- COMMENT: '/*' .*? '*/' -> channel(HIDDEN);
- LINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN);
然后,就是設(shè)計(jì)和分析詞法了:
- functionParameters ::=
- LPAREN inputParameters RPAREN outputParameters?
- | IN SUB GT inputParameters
- | outputParameters
接著,在 IDEA 里面,我們可以通過這個(gè) BNF 文件生成對(duì)應(yīng)的 Lexer 文件和代碼等。對(duì)于使用 Antlr 編寫的詞法來(lái)說,Java 部分的代碼規(guī)模也就在 800 左右。
不過呢,從兩者的閱讀體驗(yàn)對(duì)比來(lái)看,顯然 BNF 會(huì)更加友好一點(diǎn)。
自制 DSL 語(yǔ)法解析
頗為遺憾的是,我尚未寫過任何的 Vim 插件,好在我還知道 Vim 是如何退出來(lái)的。我使用 Vim 作為 git 的 editor,還熟知一些 Vim 編輯的常用快捷鍵。所以,語(yǔ)法高亮這一部分主要是參考 Vim 的文檔編寫和代碼示例。這里我找到了一個(gè)不錯(cuò)的中文翻譯:語(yǔ)法高亮
總的來(lái)說,語(yǔ)法規(guī)則就是: syn vim關(guān)鍵字 匹配規(guī)則,如:
- syn region rustCommentLine start="http://" end="$" contains=rustTodo,@Spell
- syn region rustCommentLineDoc start="http://\%(//\@!\|!\)" end="$" contains=rustTodo,@Spell
- syn region rustCommentLineDocError start="http://\%(//\@!\|!\)" end="$" contains=rustTodo,@Spell contained
- syn region rustCommentBlock matchgroup=rustCommentBlock start="/\*\%(!\|\*[*/]\@!\)\@!" end="\*/" contains=rustTodo,
看上去依舊是正則匹配,如 Float:
- syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\%([eE][+-]\=[0-9_]\+\)\=\(f32\|f64\)\="
- syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE][+-]\=[0-9_]\+\)\(f32\|f64\)\="
- syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE][+-]\=[0-9_]\+\)\=\(f32\|f64\)"
不過,從算法形式上來(lái)說,完勝 Textmate 和 Sublime,畢竟是高級(jí)的 DSL。
編程語(yǔ)言語(yǔ)法解析
Emacs 的 mode 里包含了對(duì)于語(yǔ)法高亮的處理,于是為了這個(gè)高亮,我們需要寫寫 emacs lisp 代碼。如:
- (defvar rust-formatting-macro-opening-re
- "[[:space:]\n]*[({[][[:space:]\n]*"
- "Regular expression to match the opening delimiter of a Rust formatting macro.")
- (defvar rust-start-of-string-re
- "\\(?:r#*\\)?\""
- "Regular expression to match the start of a Rust raw string.")
對(duì)于 Eclipse 來(lái)說,這個(gè)過程就更加麻煩了。
語(yǔ)言的高級(jí)支持
在我們實(shí)現(xiàn)了開發(fā)工具的詞法分析接口之后,我們就能按不同的 IDE/編輯器所定義的接口,進(jìn)行定制了。這是一個(gè)繁雜,而又充滿挑戰(zhàn)的工作。對(duì)于不同的工具來(lái)說,它們的接口相關(guān)也甚多。我也并非都能一一了解 API,所以只能簡(jiǎn)單的以 IDEA 作為一個(gè)示例來(lái)展示。主要原因大概有兩個(gè):1. 我日常使用的是 Jetbrains 相關(guān)的 IDE;2. 我已經(jīng)有一部分代碼了。
語(yǔ)法高亮
在進(jìn)行了復(fù)雜的語(yǔ)法分析之后,接著,我們就可以快速進(jìn)入一個(gè)簡(jiǎn)單的環(huán)節(jié),對(duì)代碼進(jìn)行高亮。關(guān)于高亮的話,我們可以快速進(jìn)行一個(gè)分類:
- 關(guān)鍵詞。即編程語(yǔ)言的關(guān)鍵詞,如 C 語(yǔ)言中的 32 個(gè)關(guān)鍵詞。
- 標(biāo)識(shí)符。用戶定義的字符串,如變量名、結(jié)構(gòu)體名、函數(shù)名等等。
- 特殊詞法。
- 重要的詞法。根據(jù)需要,可以針對(duì)于函數(shù)名、靜態(tài)函數(shù)名等進(jìn)行標(biāo)識(shí),以提升識(shí)別度。
如下是 Go 語(yǔ)言的一些關(guān)鍵詞:
- (defconst go-mode-keywords
- '("break" "default" "func" "interface" "select"
- "case" "defer" "go" "map" "struct"
- "chan" "else" "goto" "package" "switch"
- "const" "fallthrough" "if" "range" "type"
- "continue" "for" "import" "return" "var")
- "All keywords in the Go language. Used for font locking.")
所以,在這個(gè)場(chǎng)景之下,不論是何種的 IDE 又或者是編輯器都可以快速實(shí)現(xiàn)。
跳轉(zhuǎn) goto
不同開發(fā)工具,有各種的跳轉(zhuǎn)規(guī)則,不同的語(yǔ)言也有各自的跳轉(zhuǎn)方式。如 Emacs 的 go-mode 就定義了一系列的跳轉(zhuǎn):
- (let ((m (define-prefix-command 'go-goto-map)))
- (define-key m "a" #'go-goto-arguments)
- (define-key m "d" #'go-goto-docstring)
- (define-key m "f" #'go-goto-function)
- (define-key m "i" #'go-goto-imports)
- (define-key m "m" #'go-goto-method-receiver)
- (define-key m "n" #'go-goto-function-name)
- (define-key m "r" #'go-goto-return-values))
而 IDEA 也提供了一系列接口來(lái)實(shí)現(xiàn)類似的功能,如:
- gotoActionAliasMatcher
- gotoClassContributor
- gotoSymbolContributor
- gotoFileContributor
- gotoRelatedProvider
我們只需要分析光標(biāo)符所在的位置,其所定義的語(yǔ)法,如 IDEA 里是 PSI,再實(shí)現(xiàn)對(duì)應(yīng)的邏輯即可。如:
- @Override
- public @NotNull NavigationItem[] getItemsByName(String name, String pattern, Project project, boolean includeNonProjectItems) {
- List
properties = findStructByKey(project, name); - return properties.toArray(new NavigationItem[properties.size()]);
- }
這里定義的是數(shù)據(jù)結(jié)構(gòu)的導(dǎo)航。當(dāng)我們按下快捷鍵的時(shí)候,會(huì)傳入 name、pattern 等信息。接著,從所有相關(guān)的文件(VirtualFile)中尋找對(duì)應(yīng)的 struct,返回即可。
自動(dòng)填充
主要可以分為兩類,一類是:代碼段(Snippets),一類是:自動(dòng)填充(Completion)
好像也沒啥說的,就是綁定在特定關(guān)鍵字上的內(nèi)容。
其它
剩下的就是一些比較有意思的功能,諸如于:
- fileType。文件圖標(biāo)支持。即某一類型的文件,使用特定的圖標(biāo)來(lái)展示。
- commet 。即按下注釋的快捷鍵,能快速的注釋和反注釋代碼。
- line marker。IDEA 提供的功能,用于在行上通過圖標(biāo)來(lái)展示特定的功能。
- folding。提供特定的代碼段的折疊功能。
- 數(shù)據(jù)視圖。展示特定數(shù)據(jù)結(jié)構(gòu)關(guān)系及參數(shù)等的視圖。
- ……
其它
我一直在尋找一直簡(jiǎn)易的方式,以快速識(shí)別編程語(yǔ)言,并標(biāo)識(shí)它們。所以,也就有了這篇文章。
雖然,還在探尋,但是呢,似乎已經(jīng)有了一個(gè)初步的結(jié)果。
本文轉(zhuǎn)載自微信公眾號(hào)「 phodal」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 phodal公眾號(hào)。
文章名稱:編程語(yǔ)言的IDE支持
當(dāng)前網(wǎng)址:http://m.fisionsoft.com.cn/article/cddghop.html


咨詢
建站咨詢
