Pular para o conteúdo

Arquitetura

Este documento abrange alguns dos elementos internos do Biome e como eles são usados dentro do projeto.

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 2
const 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}
│ ^

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.