restructure: move el compiler content into lang/
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
# El LSP — Language Server for El
|
||||
|
||||
Full Language Server Protocol implementation for the El programming language.
|
||||
|
||||
## Features
|
||||
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Syntax highlighting | Full TextMate grammar |
|
||||
| Completions | Builtins (130+), user-defined fns, keywords, types |
|
||||
| Hover | Signatures + descriptions for all builtins and user fns |
|
||||
| Go-to-definition | Jump to `fn name(` in the open document |
|
||||
| Diagnostics | Unclosed braces/parens/brackets, unterminated strings |
|
||||
| Document sync | Full (re-sends entire document on every change) |
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- The `elc` compiler binary at `dist/platform/elc` (built from the repo root)
|
||||
- `cc` (clang or gcc), `libcurl`, `pthreads`
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# From the el repo root:
|
||||
./tools/lsp/build.sh
|
||||
|
||||
# Or with a custom elc path:
|
||||
ELC=/path/to/elc ./tools/lsp/build.sh
|
||||
```
|
||||
|
||||
Output: `tools/lsp/dist/el-lsp`
|
||||
|
||||
### Install system-wide
|
||||
|
||||
```bash
|
||||
sudo cp tools/lsp/dist/el-lsp /usr/local/bin/el-lsp
|
||||
```
|
||||
|
||||
## VSCode Extension
|
||||
|
||||
### Development install (recommended)
|
||||
|
||||
1. Build the binary first (see above).
|
||||
2. Install the npm dependency:
|
||||
```bash
|
||||
cd tools/lsp/vscode-extension
|
||||
npm install
|
||||
```
|
||||
3. Open `tools/lsp/vscode-extension/` in VSCode.
|
||||
4. Press **F5** — this launches the Extension Development Host.
|
||||
5. Open any `.el` file in the dev host window.
|
||||
|
||||
### Package as .vsix
|
||||
|
||||
```bash
|
||||
npm install -g @vscode/vsce
|
||||
cd tools/lsp/vscode-extension
|
||||
vsce package
|
||||
# Produces: el-language-1.0.0.vsix
|
||||
```
|
||||
|
||||
Install the .vsix:
|
||||
```bash
|
||||
code --install-extension el-language-1.0.0.vsix
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `el.lspPath` | (bundled) | Path to `el-lsp` binary. Empty = use `../dist/el-lsp`. |
|
||||
| `el.trace.server` | `off` | LSP message tracing. Set to `verbose` to see all messages. |
|
||||
|
||||
## Neovim / other editors
|
||||
|
||||
Any editor that supports LSP can use `el-lsp`. Example configuration for
|
||||
Neovim with `nvim-lspconfig`:
|
||||
|
||||
```lua
|
||||
local lspconfig = require('lspconfig')
|
||||
local configs = require('lspconfig.configs')
|
||||
|
||||
if not configs.el then
|
||||
configs.el = {
|
||||
default_config = {
|
||||
cmd = { 'el-lsp' },
|
||||
filetypes = { 'el' },
|
||||
root_dir = lspconfig.util.root_pattern('.git', '*.el'),
|
||||
settings = {},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
lspconfig.el.setup({})
|
||||
```
|
||||
|
||||
Add to `ftdetect/el.vim`:
|
||||
```vim
|
||||
au BufRead,BufNewFile *.el set filetype=el
|
||||
```
|
||||
|
||||
## Wire protocol
|
||||
|
||||
El LSP communicates over stdin/stdout using standard LSP framing:
|
||||
|
||||
```
|
||||
Content-Length: <N>\r\n
|
||||
\r\n
|
||||
<N bytes of UTF-8 JSON>
|
||||
```
|
||||
|
||||
The `__read_n(n: Int) -> String` primitive in `el_runtime.c` reads exactly
|
||||
`n` bytes from stdin — needed because `readline()` stops at `\n` and LSP
|
||||
JSON bodies are not newline-terminated. `__print_raw(s: String)` writes with
|
||||
`fwrite + fflush` to preserve embedded `\r\n` in headers.
|
||||
|
||||
## Smoke test
|
||||
|
||||
After building, verify the server responds to `initialize`:
|
||||
|
||||
```bash
|
||||
python3 - << 'PY'
|
||||
import subprocess, json
|
||||
|
||||
def frame(obj):
|
||||
body = json.dumps(obj).encode()
|
||||
return f"Content-Length: {len(body)}\r\n\r\n".encode() + body
|
||||
|
||||
def read_response(proc):
|
||||
hdr = b""
|
||||
while not hdr.endswith(b"\r\n\r\n"):
|
||||
hdr += proc.stdout.read(1)
|
||||
cl = int([l for l in hdr.decode().split("\r\n") if "Content-Length" in l][0].split(": ")[1])
|
||||
return json.loads(proc.stdout.read(cl))
|
||||
|
||||
proc = subprocess.Popen(["./dist/el-lsp"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
proc.stdin.write(frame({"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}))
|
||||
proc.stdin.flush()
|
||||
r = read_response(proc)
|
||||
print("Server name:", r["result"]["serverInfo"]["name"])
|
||||
print("Capabilities:", list(r["result"]["capabilities"].keys()))
|
||||
proc.stdin.write(frame({"jsonrpc":"2.0","method":"exit","params":{}}))
|
||||
proc.stdin.flush()
|
||||
proc.wait()
|
||||
print("OK")
|
||||
PY
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
el-lsp.el
|
||||
lsp_read_message() reads header bytes one-by-one, then __read_n(body_len)
|
||||
lsp_write_message() __print_raw("Content-Length: N\r\n\r\n" + json)
|
||||
lsp_dispatch() routes method string to handler
|
||||
lsp_builtin_catalog() [String] of "name|signature|description" entries
|
||||
lsp_extract_fns() scan source for "fn name(" patterns
|
||||
lsp_word_at() expand identifier under cursor
|
||||
lsp_compute_diagnostics() scan for unclosed brackets + unterminated strings
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
tools/lsp/
|
||||
el-lsp.el LSP server source (El language)
|
||||
build.sh Build script
|
||||
README.md This file
|
||||
dist/
|
||||
el-lsp Compiled binary (after build)
|
||||
el-lsp.c Generated C (after build)
|
||||
vscode-extension/
|
||||
extension.js Extension entry point
|
||||
package.json Extension manifest
|
||||
language-configuration.json Bracket matching, comment config
|
||||
syntaxes/
|
||||
el.tmGrammar.json TextMate syntax grammar
|
||||
.vscode/
|
||||
launch.json F5 debug configuration
|
||||
tasks.json Pre-launch npm install task
|
||||
```
|
||||
|
||||
## Runtime additions
|
||||
|
||||
Two new primitives added to `el-compiler/runtime/`:
|
||||
|
||||
### `__read_n(n: Int) -> String`
|
||||
Reads exactly `n` bytes from stdin using `fread`. Returns `""` on EOF.
|
||||
Required for reading JSON-RPC message bodies.
|
||||
|
||||
### `__print_raw(s: String) -> Void`
|
||||
Writes a string to stdout using `fwrite + fflush`. Preserves embedded
|
||||
`\r\n` bytes exactly. Required for LSP Content-Length headers.
|
||||
|
||||
Both are declared in `el_runtime.h` and registered in the `builtin_arity`
|
||||
table in `el-compiler/src/codegen.el`.
|
||||
Reference in New Issue
Block a user