Fenner
Compilador

"fc --symbols, fc --lsp e a extensão do VS Code"

O índice de símbolos em JSON, o servidor de linguagem (definição, referências, hover, renomear) e o que a extensão do VS Code usa.

O que é e por que existe#

Para um editor oferecer "ir para a definição", "achar referências" e "renomear", alguém precisa entender o programa. Em vez de reimplementar o parser dentro de cada editor, o próprio fc expõe o que entende, em duas camadas:

CamadaComandoO que é
Dadosfc --symbolsescreve em JSON as declarações, as referências e os diagnósticos do fonte, sem compilar
Protocolofc --lspum servidor de linguagem (LSP 3.17, JSON-RPC por stdio) construído em cima do --symbols
Editorextensão do VS Coderealce, trechos, leitura de erros e o cliente do --lsp (e do depurador --dap)

fc --symbols#

terminal
fc --symbols prog.fxp [--symbols-out=arq.json] [--symbols-at=arq:linha:col]

Faz só o parse (que aqui já resolve nomes e tipos), sem codegen nem ligador. Sem --symbols-out, o JSON vai para a saída padrão. A saída é 0 sempre que o JSON foi escrito, mesmo com erros no fonte: o parser é tolerante (sincroniza e segue), e o JSON traz "complete": false. Uma .fxm como entrada é lida com um programa-sonda virtual. A RTL é lida do fonte, para que TObject.Create etc. tenham arquivo e linha (origin: rtl).

O formato, resumido:

texto
{ "symbols": {
    "version": 1, "tool": "fc --symbols", "positionEncoding": "utf-8", "lineBase": 1, "columnBase": 1,
    "complete": true,
    "files": [ { "id": 0, "path": "...", "origin": "project|rtl", "module": "...", "readable": true } ],
    "decls": [ { "id": 6, "name": "ScaledArea", "kind": "method", "file": 0, "line": 28, "col": 14,
                 "container": 4, "containerName": "TCircle", "visibility": "public",
                 "signature": "function TCircle.ScaledArea(AScale: Float64): Float64", "type": "Float64" } ],
    "refs": [ { "file": 0, "line": 57, "col": 13, "endLine": 57, "endCol": 23, "decl": 6 } ],
    "diagnostics": [ { "severity": "error|warning|hint", "file": 0, "line": 16, "col": 16, "message": "..." } ] } }

kind pode ser module, type, class, record, interface, enum, enumMember, procedure, function, method, constructor, destructor, field, property, const, var ou param. Linha e coluna são base 1; a coluna é em bytes UTF-8 (a mesma das mensagens), com colU16 onde difere (o LSP padrão conta UTF-16). --symbols-at=arq:linha:col devolve só o símbolo naquela posição (uso ou declaração), com a declaração completa.

Com isso um adaptador faz: diagnostics → avisos do editor; definition = achar em refs o item cujo intervalo contém a posição e devolver a decl; references = todos os refs com a mesma decl; rename = declaração mais referências (recusar se origin for rtl); hover = decl.signature.

fc --lsp#

fc --lsp fala LSP 3.17 por stdio e oferece: diagnósticos enquanto se digita, ir para a definição, referências, hover (assinatura, tipo e o comentário ///), símbolos do documento e do workspace, completar (membros depois do . e identificadores do escopo) e renomear (com prepareRename; símbolos da RTL são recusados). O texto não salvo do editor é o que vale: o servidor o passa ao compilador por um arquivo de sobreposição (--symbols-overlay).

Cada diagnóstico leva code, source: "fc" e um codeDescription.href para o erros.md (códigos de diagnóstico).

A extensão do VS Code#

Além do realce e dos trechos (descritos em Extensão do VS Code), as versões recentes da extensão iniciam o fc --lsp sozinhas e registram o depurador fenner (fc --dap).

  1. Na pasta da extensão: npm install (traz o vscode-languageclient), ou abra a pasta no VS Code e aperte F5 para testar.
  2. Aponte o fc: configuração mycompiler.lsp.path (ou a variável MYCOMPILER_FC, ou o fc no PATH). O fc.config ao lado do fc.exe (ou MYCOMPILER_RTL) precisa achar a RTL.
  3. Abra um .fxp ou .fxm. A saída do servidor aparece em Output > MyCompiler LSP.

Outras configurações: mycompiler.lsp.enabled, mycompiler.lsp.debounceMs (espera sem digitar antes de reanalisar; padrão 700), mycompiler.lsp.check, workspaceIndex, rtlSnapshot e renameUnsafe (renomear mesmo com usos não resolvidos; padrão: recusa).

O que esperar (e os limites)#

  • Cada análise é um fc --symbols de ~7 a 10 s (lê a RTL do fonte). O servidor nunca trava o editor: roda uma análise por vez, em segundo plano, e cancela as velhas. Não há análise residente (o parser tem estado global).
  • Os diagnósticos aparecem depois da primeira análise e são só os do --symbols: erros de geração de código e de ligação não aparecem (o mycompiler.lsp.check roda um fc -S em segundo plano).
  • O completar de membros só conhece a RTL que o projeto já cita, e acha o tipo do receptor pelo nome (não entende X[i]., as, inherited, with).
  • Não geram referência: Free, os intrínsecos (Ord, Inc, WriteLn…), os tipos embutidos (Int32, UString) e identificadores dentro de {…} de uma cadeia interpolada. A heurística de posição pode perder uma referência, nunca apontar para o alvo errado; por isso o rename pode sair incompleto.
  • Só Windows. O cliente do VS Code foi validado por um cliente de teste do protocolo; o próprio README admite que não foi executado num VS Code real.

Veja também#