Pegjs: regra "inesperada"

Criado em 23 mar. 2019  ·  25Comentários  ·  Fonte: pegjs/pegjs

  • Solicitação de recurso:

Adicione a regra "inesperada" para substituir a mensagem de erro padrão.

unexpected = 
      keywords { return "Unexpected keyword "+text()+"."; }
    / expression { return "Unexpected expression «"+text()+"»."; }
    / lamdba_function { return "Unexpected lambda function."; } ;

Para implementá-lo, basta alterar peg$buildError por:

  function peg$buildError() {
    var expected = peg$expected[0];
    var failPos = expected.pos;

    // if "unexpected" rule exist this might throw the appropriate error.
    if (typeof peg$parseunexpected !== 'undefined') {
      // make peg$expect temporary unavailable, set the cursor position to the fail position
      // next get the output of the rule, if it's a string, return it.
      const tmp = peg$expect;
      peg$expect = new Function();
      peg$currPos = failPos;
      const unexpected = peg$parseunexpected();
      peg$expect = tmp;
      if (typeof unexpected === 'string') {
        const length = peg$currPos - failPos;
        const location = failPos < input.length
        ? peg$computeLocation(failPos, failPos + length)
        : peg$computeLocation(failPos, failPos);
        return new peg$SyntaxError(unexpected, expected.variants, unexpected, location);
      }
    }
    // else return standard error.
    const unexpected = input.charAt(failPos);
    const location = failPos < input.length
        ? peg$computeLocation(failPos, failPos + 1)
        : peg$computeLocation(failPos, failPos);
    return new peg$SyntaxError(
      peg$SyntaxError.buildMessage(expected.variants, unexpected), expected.variants, unexpected, location);
  }

Comportamento esperado:
Melhore o tratamento de erros.

Se não for ético, há alguém que possa me dizer como criar um plugin que faria a mudança?
Muito obrigado

feature

Comentários muito úteis

Apenas alguns bikesheddings, mas quando tal recurso for implementado, deve-se deixar ao usuário a escolha da regra a ser definida como a regra “inesperada”, por exemplo com

pegjs.generate( grammar, { unexpected: "unrecognised_token" } )

Todos 25 comentários

Eu concordo que esse recurso poderia ser adicionado, mas até agora uma maneira de fazer isso é usando a função error(...) .

Por exemplo, você tem algo que não pode corresponder:

  = FirstMatch
  / AnotherMatch

Você pode adicionar a seguinte expressão, correspondendo a cada caractere para ter certeza de que nenhuma outra correspondência será feita:

  =   .+
  {
    error("Unexpected thing: " + text().substring(0,1));
  }

E então você pode chamá-lo quando algo que deveria corresponder não corresponder.

  = FirstMatch
  / AnotherMatch
  / UnexpectedThing

sua regra UnexpectedThing não pode lidar com palavras-chave, expressões ou outros átomos de sua gramática, apenas retorna um único caractere: text().substring(0,1) , então o que você faz é exatamente a mesma coisa que o analisador pegjs produz. O recurso que propus é um pouco diferente porque trata de todas as regras especificadas dentro da regra unexpected em qualquer ponto da entrada, de forma totalmente implícita e não redonda.

Aqui está um exemplo do que o código acima produzirá para o meu analisador datilografado:
Entrada:

public function some_func(){
     public function wtf_here_func(){
     }
}

Erro de saída:

Linha X, Coluna 4: Esperado... mas "p" encontrado.

Erro de saída: (com recurso de regra inesperado)

Linha X, Coluna 4: declaração inesperada do método "wtf_here_func".

A única coisa que eu adicionei à minha gramática é:

unexpected = m:method_declaration { return `Unexpected "${m.identifier}" method declaration.` };

Percebi que usar a função error é mais elegante do que a declaração de retorno, você está certo. Assim, o acima deve se tornar:

unexpected = m:method_declaration { error(`Unexpected "${m.identifier}" method declaration.`) };

Experimente você mesmo e verá tudo o que ele pode trazer para nossos analisadores.

Você sempre pode escrever UnexpectedThing conforme necessário. Na verdade, você já faz isso, basta nomeá-lo unexpected . Mas na sua variante implícita você não pode ter diferentes regras inesperadas para diferentes partes da gramática

@Mingun
O que você diz está errado, você pode substituir minha "regra inesperada" implícita adicionando uma "Coisa Inesperada" na regra em questão, como você mesmo disse.
Uma regra implícita é totalmente necessária, aqui está um exemplo concreto, a regra para declaração de método:

method_declaration = (privacy __)? "function" _ identifier _ '(' _ args _ ')' _ '{' _ instruction* _ '}';

Com o UnexpectedThing explícito no final:

method_declaration = (privacy __)? "function" _ identifier _ '(' _ args _ ')' _ '{' _ instruction* _ '}' / UnexpectedThing;

Isso não funciona se algo inesperado aparecer entre "função" e o identificador, ou entre argumentos e ')' etc.
Então você deve fazer isso:

method_declaration = (privacy __)? ("function"/UnexpectedThing) _ identifier _ ('('/UnexpectedThing) _ args _ (')'/UnexpectedThing) _ ('{'/UnexpectedThing) _ instruction* _ ('}'/UnexpectedThing) / UnexpectedThing;

privacy = ... / UnexpectedThing;
_ = ... / UnexpectedThing;
identifier = ... / UnexpectedThing;
instruction = ... / UnexpectedThing;

...

Por que isso é ruim?

  • A complexidade gramatical cresce O(n*2) em vez de O(n)
  • A mensagem de erro confunde as regras esperadas e as regras inesperadas, assim:

Esperado... ou UnexpectedThing1, UnexpectedThing2..., mas encontrado "x".

Você entende meu argumento agora?
Não importa se você usa meu código ou não, precisamos de uma regra implícita para nossos analisadores.

Se você quiser apenas em vez de um símbolo na mensagem Esperado... mas encontrou X para ver uma palavra Esperado... mas encontrou XXX , ela se torna elementar e não exige nenhuma alteração. Apenas pegue SyntaxError e analise novamente a entrada da posição de erro com o analisador "lexer" especial. Você pode até defini-lo no mesmo arquivo da gramática principal e reutilizar algumas regras. No código:

let parser = PEG.generate(<main grammar>);
// For correct work this parser must parse any input and return string as result
let lexer = PEG.generate(<lexer grammar>);
try {
  return parser.parse(<input>);
} catch (e) {
  if (!(e instanceof parser.SyntaxError)) throw e;

  // lexer must return string
  let found = lexer.parse(input.substr(e.location.start.offset));
  // Or you can use specific rule from the same parser
  //let found = parser.parse(input.substr(e.location.start.offset), { startRule: "unexpected" });
  throw new parser.SyntaxError(
    parser.SyntaxError.buildMessage(e.expected, found),
    e.expected,
    found,
    e.location
  );
  // or you can throw you own exception type
}

Introduzir algum suporte especial no gerador para este propósito me parece excessivo, embora não contra que houvesse uma anotação que marcará a regra como um ponto de entrada do lexer ~quando~ se as anotações serão implementadas.

É uma solução certamente, mas muito pouco acessível e difícil de manter. De qualquer forma obrigado pelo seu código, é sempre bom levar.

Concordo que a implementação direta de um lexer no gerador seria bem-vinda.
Que preocupações você tem para que as anotações não possam ser implementadas?

Que preocupações você tem para que as anotações não possam ser implementadas?

Infelizmente, como você pode ver, o projeto está morto ou, pelo menos, em uma profunda estagnação

Na verdade, gosto da ideia de uma regra unexpected , mas estou pensando em colocá-la como opcional através da opção features . Tudo bem com você @log4b0at?

@futagoza Com certeza

@log4b0at - A regra unexpected já existe.

O problema de ter uma regra inesperada se especializando na natureza da coisa que ela não conseguiu analisar é que ela não sabe o que é isso, porque não conseguiu analisá-la.

Considere a seguinte gramática:

Document = (DadJoke "\n"?)+

Kind = [a-zA-Z0-9 ]+

Car    = "car: "    Kind ".";
Insect = "insect: " Kind ".";
Annoy  = "annoy: "  Kind ".";

DadJoke = Car / Insect / Annoy

(Esta é uma piada de pai porque, obviamente, a resposta correta para cada regra é "bug".)

Isso deve sem problemas analisar a entrada como

car: Honda
insect: Beetle
annoy: Whine

Existem duas maneiras de ler manipulando o unexpected . Ou é que a regra da transportadora está errada e você vai lidar com isso, ou a regra subsumida está errada e você vai lidar com isso.

Pelo que entendi, o que você está pedindo é uma regra para unexpected que permita fornecer saídas diferentes com base em se o inesperado foi um carro, um inseto ou um aborrecimento.

Vamos supor que você tenha uma especialização como:

const UnexpectedCustoms = { // no, not shoes off
  'annoy'  : 'Unexpected annoyance',
  'insect' : 'Unexpected insect',
  'car'    : 'Unexpected car'
};

Então, o que deve dar como uma mensagem de erro quando eu der essa entrada?

defect: bug
microphone: bug
disease: bug
hobbyist: bug

Quais desses são carros?

Quais devem ser as mensagens de erro?

Isso não tem solução. Este problema é equivalente a dizer "ei peg, dado que a próxima coisa não pode ser interpretada, por que você não me diz o que é para que eu possa contar a alguém?"

Primeiro você precisa ensiná-lo a interpretar isso. Em seguida, você não precisa de nada.

Esta é, em essência, a razão pela qual algumas coisas são configuradas como um tokenizer e depois um lexer. Apenas use peg como um tokenizer, neste caso, escreva um lexer que analise o AST gerado e diga "uh, você está ... você não tem permissão para ter um parêntese de fechamento sem um aberto "


Como alternativa, se for sobre a regra subsumida, em vez da regra da transportadora, você terá uma entrada do formulário

car: car: car: ...

Existe alguma maneira de pegjs saber que é um carro, além de escrever a regra que você já pode escrever para corresponder a isso e depois manuseá-lo?

Isso é bastante simples de lidar com a gramática hoje, e muitas gramáticas o fazem. Por que você quer recursos extras para isso?

Basta escrever uma regra com o nome do recurso que você está solicitando. Pow: feito.

Nenhuma complexidade extra peg.js é necessária.


Aqui está a outra maneira de dizer isso.

Para fornecer uma mensagem de erro para a análise incorreta específica, você precisaria de uma análise correta do material incorreto para interpretar. Ou escreva um analisador que aceite as coisas erradas e as rejeite nos manipuladores, ou escreva um analisador secundário para lidar com a parcial, ou escreva um AST que possa aceitar a coisa errada e interpretar o AST como errado."


Finalmente, isso realmente não deveria ser feito, porque unknown é o quinto ou sexto nome mais comum para uma regra, depois de coisas como document e operator

Eu garantiria que mais de um terço das gramáticas já tem isso, porque a linguagem já é capaz de expressá-lo sem recursos

Se você tentar adicionar isso, tudo o que você faz é quebrar as gramáticas existentes para adicionar algo que já temos

Isso deve ser recusado

@Mingun - Eu quero ressuscitar este projeto. Não há nenhuma boa razão para ele estar morto

Eu acho que esse recurso serve para tornar os erros mais claros, mesmo que uma regra unexpected não seja a maneira correta de implementar esse tipo de coisa.

O ponto é que ter um erro Unexpected X parece tornar mais prático identificar os erros de análise (pelo menos em muitos casos) em vez de ter Expected A, B, C, D, E, X, Y or Z ou Expected expression .

Seria ótimo ver o PEG.js sendo capaz de fazer uma coisa dessas.

Então, como você identifica qual é o X inesperado?

Essa é provavelmente a questão deste recurso: ser capaz de identificar claramente o que está errado.

O problema aqui não é tentar identificar o que X poderia ser, mas ter certeza do que é X.

Como tentei explicar acima, isso se chama "parsing", e a maneira de fazer isso é especificá-lo na gramática

Eu não entendo bem o problema que você está tentando levantar, você pode me dar mais exemplos?

Eles serão literalmente idênticos ao existente.

Tente responder a pergunta. Está lá socraticamente e retoricamente; você deve aprender qual é o problema ao tentar responder.


Considere a seguinte gramática:

Document = (DadJoke "\n"?)+

Kind = [a-zA-Z0-9 ]+

Car    = "car: "    Kind ".";
Insect = "insect: " Kind ".";
Annoy  = "annoy: "  Kind ".";

DadJoke = Car / Insect / Annoy

Então, o que deve dar como uma mensagem de erro quando eu der essa entrada?

defect: bug
microphone: bug
disease: bug
hobbyist: bug

Pelo que entendi, o analisador deve dizer algo como "Encontrei uma doença quando esperava um carro, um inseto ou um aborrecimento".

Como é suposto saber que é uma doença?

A análise falha quando o analisador não sabe qual é a próxima coisa.

Uma mensagem de erro para falha de análise que exige saber qual é a próxima coisa é contraditória com a situação contextual

defect: bug
microphone: bug
disease: bug
hobbyist: bug

"Quais devem ser as mensagens de erro?"

Você obtém isso com o tratamento de erros real:

Linha 1, coluna 1: Esperado "annoy: ", "car: " ou "insect: " mas "d" encontrado.

Se eu definir uma regra inesperada como essa

Identifier = [a-zA-Z]+;

unexpected = 
    DadJoke { error("Unexpected dad joke here"); }
/ i:$Identifier { error(`Unexpected identifier "${i}"`); };

eu vou conseguir

Linha 1, coluna 1: identificador inesperado "defeito"

"Alternativamente, se for sobre a regra subsumida, em vez da regra da transportadora, você terá uma entrada do formulário"
car: car: car:

Aqui você receberá a mensagem padrão, porque nenhuma correspondência de regra inesperada ":" pontuação

Linha 1, coluna 8: Esperado ... blabla ... mas encontrado ":"

Além disso, o processo de detecção de coisas inesperadas é totalmente passivo e acontece apenas quando o pegjs detecta um erro, e não adiciona nenhuma sobrecarga em termos de desempenho.

Isso responde sua pergunta corretamente?

se você quiser tentar, eu rapidamente fiz um código para a versão 0.10 do pegjs, substitua (no seu analisador) peg$buildStructuredError por

function peg$buildStructuredError(expected, found, location) {
    if (typeof peg$parseunexpected !== 'undefined') {
        peg$fail = new Function();
        peg$currPos = location.start.offset;
        peg$parseunexpected();
    }
    return new peg$SyntaxError(peg$SyntaxError.buildMessage(expected, found), expected, found, location);
}

Não é mais ou menos o que mingun disse em 2019?

Agora eu me preocupo que estou entendendo mal alguma coisa aqui

Usar um tokenizer tem um custo.
tal recurso é simples de implementar e não custa nada a ninguém

ok, esse é um ponto justo

Apenas alguns bikesheddings, mas quando tal recurso for implementado, deve-se deixar ao usuário a escolha da regra a ser definida como a regra “inesperada”, por exemplo com

pegjs.generate( grammar, { unexpected: "unrecognised_token" } )

Olá, acabei de fazer um pull request para esta funcionalidade, seguindo seus conselhos, ou seja, o uso da função error, muito mais consistente que um return, sugerido por @norech.
E o uso de uma opção para alterar o nome da regra, sugerida por @Seb35
Como disse o @futagoza , o recurso é opcional e está desabilitado por padrão. (Eu não sei sobre a opção de recursos, mas por padrão não há regra inesperada)
Veja #661 puxar

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