-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
61 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
title: LSP 与 VS Code 插件开发 第二章 | ||
date: 2024-12-31 | ||
tags: [LSP, VS Code, 语言服务器] | ||
--- | ||
|
||
上一章我们讲到,语言服务器的输入是源码,而输出是结构化的数据。代码编辑器(客户端)某个位置显示什么颜色,鼠标悬浮到某个位置提示什么信息,都由客户端向语言服务器请求,获取数据后,渲染到用户界面。 | ||
|
||
因此语言服务器需要编译源码,构建语义模型,以为客户端提供*智能编程服务*。 | ||
|
||
所谓的 `编译` 是怎么回事?它和更常见的编译器是什么关系?本章会和大学里的编译原理知识有些关系,但保证比课本上的更有趣、更好玩! | ||
|
||
## 语言服务器与编译器的关系 | ||
|
||
先回顾一下编译原理的基本流程: | ||
`词法分析` -> `语法分析` -> `语义分析` -> `中间代码生成` -> `代码优化` -> `目标代码生成` -> `...` | ||
|
||
## 前端相似 | ||
|
||
具体各流程的作用就不赘述了,大学课本里那些长篇大论介绍 LL(1) 文法过于乏味。 | ||
我们基本可以把编译器的工作分为`前端`、(`中端`)、`后端`几个流程,先说结论,编译器和语言服务器在编译的过程中,`前端`的过程是非常相似的。也就是 `词法分析`、`语法分析`、`语义分析` 几个阶段。 | ||
|
||
通过这三个阶段,从源码中获取语义信息后,语言服务器等待客户端请求,为编程体验服务,而编译器则是继续中端、后端,为生成目标代码服务。在这之后,两种做的事情就大相径庭了。 | ||
|
||
换句话说,对于 `console.log` 这一行代码,两者都经历了这样的阶段: | ||
|
||
``` | ||
IDENTIFIER DOT IDENTIFIER // 词法分析,得知输入是两个符号中间有个点号 | ||
``` | ||
|
||
``` | ||
DomainDotAccess // 语法分析,合并 token 得知这是通过点号访问域的语法 | ||
``` | ||
|
||
最终在语义上,就知道 `console` 这个符号的语义是接口 (interface),`log` 符号是其中的一个方法 (method) | ||
|
||
### 容错 | ||
|
||
此外,两者的容错处理也是不一样的。当源码中出现了错误: | ||
编译器会停止编译,通过标准输出等方式抛出`编译错误`,自然也没有目标产物的输出了,当然,一行代码的改动可能引起数个文件的错误,如果只遇到一个错误就停止编译,也不利于 debug,往往会尝试尽可能多的抛出错误; | ||
而用户会持续不断的编码,你可以想一下,输入编写一行代码时,可能只有输入了最后一个分号 `;` 后,编译才会通过,而这期间语言服务器则会持续工作,进程不会停止,而是收集 [`Diagnostic`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic),也就是诊断信息,客户端根据这些信息,在代码编辑器上显示<font color="red">红色</font>或者<font color="orange">橙色</font>的波浪线。 | ||
|
||
通常来说,编译器能顺利编译通过的工程,语言服务器也不应有诊断的出现。反过来,代码编辑器中没有波浪线,理论上编译也是能过的。 | ||
碰到代码编辑器中标红,但能编译通过的情况倒是还好,如果代码编辑器没问题,但编译时报错,那就痛苦了。这种一般都是由于编译器和语言服务器没有统一编译标准导致的,例如两种的编译配置不同,同样一段代码,语言服务器认为是 warning,但编译器认为是 error,甚至是两者的语言版本不同(比如 python2 和 python3)。 | ||
另外,像 VS Code 这样的编辑器都支持插件,可能除了语言官方的语言服务器,还有别的(例如 lint 工具)在同时工作,这也会导致代码编辑器里看到的诊断比编译输出的多。 | ||
|
||
总的来说,编译器在构建时一次性执行,要求“严谨”,而语言服务器在用户编码时持续服务,要求“健壮” | ||
|
||
### 使用同一种语言构建 | ||
|
||
上一章提到,语言服务器与编译器往往是同一种编程语言构建的程序。现在你应该更理解了,在编译原理前端,两种的逻辑高度相似,往后才开始异化。使用同一种语言,甚至在同一个工程中,能最大的复用代码,减少维护成本。 | ||
事实上很多语言的编译器是自带语言服务器的,例如 [Deno](https://docs.deno.com/runtime/reference/lsp_integration/)、[TypeScript](https://github.com/microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29) 等。 | ||
|
||
当然,语言服务器的实现也并不仅是官方一种,VS Code 就[受不了](https://code.visualstudio.com/api/language-extensions/language-server-extension-guide#error-tolerant-parser-for-language-server) PHP 解析器不能容错,直接新写了一个语言服务器 | ||
|
||
## 语法错误与语义错误 | ||
|
||
语言服务器的编译和编译器的编译的区别:语言服务器服务语言客户端,语言客户端数据驱动,语言服务器只提供智能编程数据。而编译器则需要输出目标代码。编译前端是相似的,词法分析、语法分析、语义分析,这之后两者做的事情就不一样了。 | ||
(因此,实现语言服务器和编译器的往往是同一种编程语言。这也是促成语言服务器架构的原因之一) | ||
|
||
容错:有语法错误:靠词法工具容错。有语义错误:靠语言服务器自身容错。 |