Arquitetura
Este documento abrange alguns dos elementos internos do Biome e como eles são usados dentro do projeto.
Parser e CST
Section titled “Parser e CST”A arquitetura do parser é impulsionada por um fork interno do rowan, uma biblioteca que implementa o padrão Green and Red tree.
O CST (Árvore de Sintaxe Concreta) é uma estrutura de dados muito semelhante à AST (Árvore de Sintaxe Abstrata) que mantém o registro de todas as informações de um programa, incluindo trivialidades.
Trivialidades são representadas por todas aquelas informações que são importantes para a execução de um programa:
- espaços
- tabs
- comentários
As trivialidades são anexadas a um nó. Um nó pode ter trivialidades antecedentes e subsequentes. Se você ler o código da esquerda para a direita, as trivialidades antecedentes aparecem antes de uma palavra-chave e as trivialidades subsequentes aparecem depois de uma palavra-chave.
As trivialidades antecedentes e subsequentes são categorizadas da seguinte forma:
- Toda trivialidade até o token/palavra-chave (incluindo quebras de linha) será a trivialidade antecedente;
- Tudo até a próxima quebra de linha (mas não incluindo-a) será a trivialidade subsequente;
Dado o seguinte trecho de JavaScript, // comentário 1
é uma trivialidade subsequente do token ;
, e // comentário 2
é uma trivialidade antecedente da palavra-chave const
. Abaixo uma versão minimizada do CST representado pelo Biome:
const a = "foo"; // comentário 1// comentário 2const b = "bar";
0: JS_MODULE@0..55 ... 1: SEMICOLON@15..27 ";" [] [Whitespace(" "), Comments("// comment 1")] 1: JS_VARIABLE_STATEMENT@27..55 ... 1: CONST_KW@27..45 "const" [Newline("\n"), Comments("// comment 2"), Newline("\n")] [Whitespace(" ")] 3: EOF@55..55 "" [] []
O CST nunca é diretamente acessível por design, um desenvolvedor pode ler suas informações usando a Árvore Vermelha, usando uma série de APIs que são geradas automaticamente a partir da gramática da linguagem.
Parser resiliente e recuperável
Section titled “Parser resiliente e recuperável”Para construir um CST, é necessário um parser resiliente e recuperável:
- resiliente: um parser capaz de retomar a análise após encontrar um erro de sintaxe pertencente à linguagem;
- recuperável: um parser capaz de entender onde ocorreu um erro e ser capaz de retomar a análise criando informações corretas;
A parte recuperável do parser não é uma ciência exata, e não há regras definitivas. Isso significa que, dependendo do que o parser estava analisando e onde ocorreu um erro, ele pode se recuperar de maneiras esperadas.
Para proteger os consumidores de consumir sintaxe incorreta, o parser também usa nós Bogus
. Esses nós são usados para decorar o código quebrado causado por um erro de sintaxe.
No seguinte exemplo, os parênteses no while
estão faltando, embora o parser seja capaz de se recuperar de maneira satisfatória, e ele consegue representar o código com um CST decente. Os parênteses e a condição do loop são marcados como ausentes, e o bloco de código é analisado corretamente:
while {}
JsModule { interpreter_token: missing (optional), directives: JsDirectiveList [], items: JsModuleItemList [ JsWhileStatement { while_token: WHILE_KW@0..6 "while" [] [Whitespace(" ")], l_paren_token: missing (required), test: missing (required), r_paren_token: missing (required), body: JsBlockStatement { l_curly_token: L_CURLY@6..7 "{" [] [], statements: JsStatementList [], r_curly_token: R_CURLY@7..8 "}" [] [], }, }, ], eof_token: EOF@8..8 "" [] [],}
Este é o erro emitido durante a análise:
main.tsx:1:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ expected `(` but instead found `{`
> 1 │ while {} │ ^
ℹ Remove {
O mesmo não pode ser dito para o seguinte trecho. O parser não consegue entender corretamente a sintaxe durante a fase de recuperação, então ele precisa confiar nos nós de bogus para marcar alguma sintaxe como errônea. Note o JsBogusStatement
:
function}
JsModule { interpreter_token: missing (optional), directives: JsDirectiveList [], items: JsModuleItemList [ TsDeclareFunctionDeclaration { async_token: missing (optional), function_token: FUNCTION_KW@0..8 "function" [] [], id: missing (required), type_parameters: missing (optional), parameters: missing (required), return_type_annotation: missing (optional), semicolon_token: missing (optional), }, JsBogusStatement { items: [ R_CURLY@8..9 "}" [] [], ], }, ], eof_token: EOF@9..9 "" [] [],}
Este é o erro que obtemos da fase de análise:
main.tsx:1:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ expected a name for the function in a function declaration, but found none
> 1 │ function} │ ^
Formatador (Em Progresso)
Section titled “Formatador (Em Progresso)”Lint (Em Progresso)
Section titled “Lint (Em Progresso)”Daemon (Em Progresso)
Section titled “Daemon (Em Progresso)”O Biome usa uma arquitetura de servidor-cliente para executar suas tarefas.
Um daemon é um servidor de longa duração que o Biome inicia em segundo plano e usa para processar solicitações do editor e da CLI.