Skip to content

Commit

Permalink
lsp2 init
Browse files Browse the repository at this point in the history
  • Loading branch information
imbant committed Jan 13, 2025
1 parent 1fac34d commit f949a8e
Showing 1 changed file with 61 additions and 0 deletions.
61 changes: 61 additions & 0 deletions source/_posts/LSP2.md
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 解析器不能容错,直接新写了一个语言服务器

## 语法错误与语义错误

语言服务器的编译和编译器的编译的区别:语言服务器服务语言客户端,语言客户端数据驱动,语言服务器只提供智能编程数据。而编译器则需要输出目标代码。编译前端是相似的,词法分析、语法分析、语义分析,这之后两者做的事情就不一样了。
(因此,实现语言服务器和编译器的往往是同一种编程语言。这也是促成语言服务器架构的原因之一)

容错:有语法错误:靠词法工具容错。有语义错误:靠语言服务器自身容错。

0 comments on commit f949a8e

Please sign in to comment.