Pegjs: Redesenho do relatório de erros

Criado em 14 ago. 2013  ·  7Comentários  ·  Fonte: pegjs/pegjs

Vejo três problemas com o sistema de relatório de erros atual no PEG.js:

  1. As ações relatam erros retornando null
Reporting using `null` is inflexible (it doesn't allow to carry any information about the kind of error) and makes it impossible to return `null` as a regular value from actions, which would be useful sometimes (for example [in a JSON parser](https://github.com/dmajda/pegjs/blob/791034fad92c9cd7a9d1c71187df03441bbfd521/examples/json.pegjs#L45)).

Veja também #17.

  1. Não há como produzir erros com mensagens totalmente personalizadas
For example, imagine a HTTP 1.1 parser that wants to report an error about missing `Host` header in a HTTP request with a message like "A HTTP 1.1 request must contain a Host header". Currently the only way to do this is to throw an exception with desired message manually. This is cumbersome and it does not interact well with the backtracking behavior (throwing an exception halts the parsing completely, even when it is possible to backtrack and try another alternatives).

  1. A propriedade expected de SyntaxError é difícil de processar mecanicamente
The `expected` property of `SyntaxError` is either an array of expectations (each describing one alternative which the parser expected at the position of error) or `null` (meaning that the end of input was expected).

Cada expectativa é representada por uma string. Essa string pode significar um literal esperado (representado usando sua sintaxe PEG.js — uma string entre aspas), classe de caractere (representada usando sua sintaxe PEG.js — [...] ) ou qualquer caractere (representado pela string "any" ). Para diferenciar esses casos, é preciso analisar as representações de string manualmente, o que torna desnecessariamente difícil construir ferramentas baseadas em PEG.js, como autocompleters.

Pretendo redesenhar o sistema de relatórios de erros para corrigir esses problemas. Antes de descrever as mudanças em que estou pensando, é necessária uma pequena descrição de como o sistema atual funciona.

Como o relatório de erros funciona atualmente?

Quando um analisador PEG.js tenta corresponder a um literal de string, classe de caractere ou . e falha, ele produz uma _match failure_. Ele consiste em uma _posição_ e uma _expectação_ (descrição do que o analisador tentou corresponder).

Depois de produzir uma falha de correspondência, o analisador retrocede e possivelmente tenta outras alternativas. Se nenhum deles for bem-sucedido e não houver mais nada para tentar, o analisador lançará a exceção SyntaxError . Ao definir suas propriedades (como posição, expectativas, mensagem de erro, etc.), o analisador considera apenas a falha de correspondência mais distante (aquela com a maior posição). Se houver mais falhas desse tipo, suas expectativas são combinadas.

A situação é um pouco mais complicada se forem usadas regras nomeadas, mas isso é irrelevante para esta discussão.

Alterações propostas

Estou pensando nas seguintes alterações no sistema de relatório de erros:

  1. As expectativas em SyntaxError.expected não serão representadas por strings, mas por objetos. Os objetos terão uma propriedade type (com valores como "literal" , "class" , "any" , "eof" ) que determinará o tipo de expectativa. Para alguns tipos de expectativa, outras propriedades conterão os detalhes (por exemplo, para "literal" expectativa onde seria uma propriedade value contendo a string esperada). Isso simplificará o processamento mecânico das expectativas.

Uma alternativa para a propriedade type é usar classes. Mas acho que a propriedade type será mais fácil de manusear para os usuários.

  1. As ações poderão retornar null . Será um valor regular e não significará um erro.
  2. As ações poderão acionar uma falha de correspondência usando uma nova função error . Levará uma mensagem de erro como seu parâmetro. As falhas acionadas por esta função (chamadas _custom match failures_) consistirão em uma _position_ e uma _error message_. Eles não terão nenhuma expectativa.

A função error não interromperá a execução da ação, apenas a marcará como falha e salvará a mensagem de erro. A falha real será produzida somente após o término da execução da ação. Se a função error for invocada várias vezes, a última chamada vencerá (sua mensagem de erro será usada).

As falhas de correspondência personalizadas serão tratadas como falhas de correspondência regulares, o que significa que elas não interromperão a análise completamente e deixarão o analisador retroceder e possivelmente tentar outras alternativas. Porém, há uma diferença - quando finalmente lançar a exceção SyntaxError , a regra de combinação de expectativa que se aplica a falhas de correspondência regulares não se aplicará às personalizadas. Se no conjunto de falhas de correspondência com a posição mais distante houver pelo menos uma personalizada, ela simplesmente substituirá as regulares completamente. Se houver mais falhas personalizadas, a que foi produzida por último vencerá.

Uma exceção SyntaxError baseada em uma falha de correspondência personalizada será diferente das exceções baseadas em uma falha regular. Sua propriedade message será igual à mensagem de erro da falha e sua propriedade expected será null .

Exemplo

```
inicio = sinal:[+-]? dígitos:[0-9]+ {
var resultado = parseInt((sinal || "") + digits.join(""), 10);

 if (result % 2 == 0) {
   error("The number must be an odd integer.");
 }

 return result;

}
```

Na entrada 2 , o analisador gerado a partir da gramática acima produzirá um SyntaxError com message definido como "The number must be an odd integer." e expected definido como null

  1. As ações também poderão acionar uma falha de correspondência regular usando a função expected . Levará uma descrição de valor esperado como parâmetro. Esta função será fornecida principalmente como uma conveniência para situações em que não é necessário gerar uma mensagem de erro completa e gerar automaticamente o formulário "Esperado _X_ mas "2" encontrado." basta.

Uma exceção SyntaxError baseada em uma falha de correspondência gerada pela função expected será semelhante a exceções baseadas em uma falha regular.

Exemplo

```
inicio = sinal:[+-]? dígitos:[0-9]+ {
var resultado = parseInt((sinal || "") + digits.join(""), 10);

 if (result % 2 == 0) {
   expected("odd integer");
 }

 return result;

}
```

Na entrada 2 , o analisador gerado a partir da gramática acima produzirá um SyntaxError com message definido como "Expected odd integer but "2" found." e expected definido como [ { type: "user", description: "odd integer" } ]

Próximos passos

Congratulo-me com quaisquer notas às alterações propostas - por favor, adicione-as como comentários. Pretendo começar a implementar a proposta (ou alguma versão modificada com base no feedback) em breve.

feature

Comentários muito úteis

Então, ainda não existe uma função de "aviso"?

Todos 7 comentários

Seria melhor se várias invocações da função de erro levassem a vários erros. Você pode continuar analisando o máximo possível.

string = '"' value:(!(eol / '"') .)+ '"' { return value; }
       / '"' value:(!(eol / '"') .)+     { error('unterminated string constant'); return value; }

Eu também recomendaria adicionar suporte para avisos também.

Seria melhor se várias invocações da função de erro levassem a vários erros. Você pode continuar analisando o máximo possível.

Você poderia citar alguns casos de uso em que você precisaria disso? Parece-me que o exemplo que você forneceu também funcionaria com minha proposta.

Já tenho alguns casos de uso, mas não sei o quão representativos eles são, então gostaria de ver mais alguns.

Eu também recomendaria adicionar suporte para avisos também.

Eu também estou pensando nisso.

Um problema com vários erros e avisos é que eles precisariam de uma interface diferente da simples e intuitiva “ parse retorna algum valor em caso de sucesso ou uma exceção em caso de erro”. O analisador precisaria relatar vários erros na análise malsucedida, além de avisos na análise bem-sucedida e malsucedida.

Que tipo de API você acharia mais intuitiva aqui? Mais uma vez, tenho algumas ideias, mas gostaria de ver o que os outros pensam.

Você poderia citar alguns casos de uso em que você precisaria disso? Parece-me que o exemplo que você forneceu também funcionaria com minha proposta.

Meu caso de uso principal é para erros de análise não fatais. Strings não terminadas, identificadores que começam com um número, comentários aninhados, ponto-e-vírgula ausente, etc.

Geralmente, é melhor deixar o analisador avançar o máximo possível, para que o maior número possível de erros possa ser mostrado ao usuário. Se o analisador puder terminar a análise e permitir que as próximas etapas (por exemplo, compilação) relatem erros também, isso seria ótimo.

Isso agora está implementado . O script tools/impact relata o seguinte impacto no desempenho de todo o conjunto de confirmações:

Speed impact
------------
Before:     1144.21 kB/s
After:      999.89 kB/s
Difference: -12.62%

Size impact
-----------
Before:     863523 b
After:      1019968 b
Difference: 18.11%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)

Eu acho que 12,62% de penalidade de velocidade e 18,11% de penalidade de tamanho é bom para resolver um conjunto de problemas tão antigo.

Fechando.

@dmajda : Isso é uma ótima notícia! Estou tão feliz que null não sinaliza mais falha.

Então, ainda não existe uma função de "aviso"?

O tópico da função de aviso é rastreado em #325

Esta página foi útil?
0 / 5 - 0 avaliações