Pegjs: Capacidade de ignorar certas produções

Criado em 8 out. 2010  ·  29Comentários  ·  Fonte: pegjs/pegjs

Seria bom ser capaz de dizer ao lexer / analisador para ignorar certas produções (ou seja, espaços em branco e produções de comentários) de forma que se torne desnecessário sujar todas as outras produções com permissões de comentários / espaços em branco. Isso pode não ser possível, devido ao fato de que lexing está embutido na análise?

Obrigado

feature

Comentários muito úteis

@atesgoral - Eu

Então eu fiz o que qualquer cara covarde faria - usei expressões regulares. (E então eu tive dois problemas :-)

Mas funcionou, então pude passar para o próximo desafio. Boa sorte no seu projeto!

Todos 29 comentários

Concordou. Existe uma maneira limpa de fazer isso no momento?

@benekastah : Não existe uma maneira limpa no momento.

Isso seria difícil de fazer sem alterar a forma como o PEG.js funciona. As soluções possíveis incluem:

  1. Permitir anexar um lexer antes do analisador gerado.
  2. Incorpore informações sobre regras ingorinadas em algum lugar da gramática. Isso provavelmente significaria também distinguir entre o nível léxico e sintático da gramática - algo que eu gostaria de evitar.

Não vou trabalhar nisso agora, mas é algo para se pensar no recurso.

Eu precisaria desse recurso para.

Pode ser que você possa introduzir um "skip" -Token. Portanto, se uma regra retornar esse token, ela será ignorada e não obterá nenhum nó no AST (também conhecido como entrada no array).

Estou procurando uma maneira de fazer isso também.

Eu tenho um grande arquivo de gramática (ele analisa o formato ASN.1 para arquivos MIB SNMP). Eu não o escrevi, mas trivialmente o transformei da forma original para criar um analisador em PEG.js. (Isso é bom. Na verdade, é extremamente engenhoso levar menos de 15 minutos para ajustá-lo para que o PEG.js o aceite.)

Infelizmente, a gramática foi escrita com a capacidade de simplesmente ignorar espaços em branco e comentários quando os encontra. Conseqüentemente, nenhum arquivo MIB real pode ser manipulado, porque o analisador para na primeira ocorrência de espaço em branco.

Não estou ansioso para ter que descobrir a gramática para que possa inserir todos os espaços em branco adequados em todas as regras (são cerca de 126 produções ...). Existe alguma outra maneira de fazer isso?

NB: No caso de eu ter que modificar a gramática manualmente, pedi ajuda com algumas questões em um tíquete na lista de Grupos do Google. http://groups.google.com/group/pegjs/browse_thread/thread/568b629f093983b7

Muito Obrigado!

Obrigado ao pessoal dos Grupos do Google. Acho que tenho informações suficientes para fazer o que quero.

Mas estou realmente ansioso para a capacidade do PEG.js de marcar espaços em branco / comentários como algo a ser completamente ignorado para que eu não tenha que levar algumas horas para modificar uma gramática limpa ... Obrigado!

Rico

Concordo com a afirmação de que os pegjs precisam da capacidade de pular tokens. Posso dar uma olhada nisso, pois se você quiser escrever uma gramática séria, ficará louco ao colocar ws entre cada símbolo.

Uma vez que os analisadores gerados são modulares. Como solução alternativa, crie um lexer simplista e use sua saída como entrada para o real, por exemplo:

elideWS.pegjs:

s = input: (whitespaceCharacter / textCharacter) *
{
var result = "";

para (var i = 0; i <comprimento.entrada; i ++) resultado + = entrada [i];
resultado de retorno;
}

whitespaceCharacter = [nt] {return ""; }
textCharacter = c: [^ nt] {return c; }

mas isso causa problemas quando o espaço em branco é um delimitador - como para identificadores

Esbarrando neste problema com bastante frequência.
Mas não é fácil escrever um bom lexer (você pode acabar duplicando uma boa parte da gramática inicial para ter um lexer coerente).

O que eu estava pensando é poder definir regras de pulo que podem ser usadas como alternativas sempre que não houver correspondência. No entanto, isso introduz a necessidade de uma aula ininterrupta. Exemplo com arithmetics.pegjs usando carros alegóricos

  = Term (("+" / "-") Term)*

Term
  = Factor (("*" / "/") Factor)*

Factor
  = "(" Expression ")"
  / Float

Float "float"
  = "-"? # [0-9]+ # ("." # [0-9]+) // # means that skip rules cannot match

// skip rule marked by "!="
// skip rules cannot match the empty string
_ "whitespace"
  != [ \t\n\r]+

Ainda digerindo isso. Algum feedback? Pode ser uma ideia muito estúpida.

Portanto, a diferença é que você deseja distinguir quando o mecanismo geral está
operando no modo lexer (espaço em branco é significativo) e quando não (espaço em branco é
ignorado).

Existe um caso em que você deseja não ignorar os espaços em branco quando estiver no modo lexer
como opção? Ou, inversamente, quando não está dentro de uma regex? Eu acho que não.

O seguinte seria equivalente?

Flutuador
"-? [0-9] + (". "[0-9] +)”

ou estender o peg para processar o regex típico diretamente e fora
um espaço em branco de string entre aspas (que inclui regexes) é ignorado.

Em 19 de abril de 2014, às 15:22, Andrei Neculau [email protected] escreveu:

Esbarrando neste problema com bastante frequência.
Mas não é fácil escrever um bom lexer (você pode acabar duplicando uma boa parte da gramática inicial para ter um lexer coerente).

O que eu estava pensando é poder definir regras de pulo que podem ser usadas como alternativas sempre que não houver correspondência. No entanto, isso introduz a necessidade de uma aula ininterrupta. Exemplo com arithmetics.pegjs usando flutuadores

Expressão
= Termo (("+" / "-") Termo) *

Prazo
= Fator (("_" / "/") Fator) _

Fator
= "(" Expressão ")"
/ Float

Flutuar "flutuar"
= "-"? # [0-9] + # ("." # [0-9] +) // # significa que as regras de pular não podem coincidir

// ignorar regra marcada por "! ="
// pular regras não pode coincidir com a string vazia
_ "espaço em branco"
! = [tnr] +
Ainda digerindo isso. Algum feedback? Pode ser uma ideia muito estúpida.

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

@waTeim Na verdade, não.

Tradicionalmente, o processo de análise é dividido em lexing e análise sintática. Durante o lexing, cada caractere é significativo, incluindo os espaços em branco. Mas estes são então transformados em um token de "descarte". O analisador, ao avançar para o próximo token, descartará todos os tokens de descarte. O importante é que você pode descartar qualquer coisa, não apenas os espaços em branco. Este comportamento é exatamente o que @andreineculau está descrevendo.

A ideia básica de como implementar isso é a necessidade de verificar adicionalmente todas as regras de descarte ao fazer a transição de um estado para o próximo.

Em 23 de abril de 2014, às 14h54, Sean Farrell [email protected] escreveu:

@waTeim Na verdade, não.

Então, nós concordamos. A abordagem tradicional é suficiente. Não precisa ter
a parte do analisador estrito reconhece a existência de tokens descartados e há
nenhuma razão para fazer a parte lexer se comportar condicionalmente (de uma forma sensível ao contexto)
wrt reconhecendo tokens.

Portanto, não há necessidade de elementos de cola (por exemplo, '#') na linguagem
porque basta que

1) os tokens podem ser criados exclusivamente a partir de regex e não são sensíveis ao contexto.
2) os tokens podem ser marcados para serem descartados, sem exceção.

Tradicionalmente, o processo de análise é dividido em lexing e análise sintática. Durante o lexing, cada caractere é significativo, incluindo os espaços em branco. Mas estes são então transformados em um token de "descarte". O analisador, ao avançar para o próximo token, descartará todos os tokens de descarte. O importante é que você pode descartar qualquer coisa, não apenas os espaços em branco. Este comportamento é exatamente o que @andreineculau está descrevendo.

A ideia básica de como implementar isso é a necessidade de verificar adicionalmente todas as regras de descarte ao fazer a transição de um estado para o próximo.

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

Ok, então eu te entendi mal. Pode haver casos para estados lexer, mas esse é um requisito totalmente diferente e IMHO fora do escopo do peg.js.

@waTeim @rioki Esqueça minha sugestão.

Mãos à obra, siga esta regra . Se você quiser simplificar a gramática da regra eliminando *WS , como instruiria os PEGjs a não permitir *WS entre field_name e : ?

@andreineculau Como sua gramática é sensível a espaços em branco, isso não é aplicável. Os tokens de descarte fariam parte da gramática, a parte lexing para ser exato. Não sei qual é o grande problema aqui, isso já estava suficientemente resolvido nos anos 70. Cada idioma tem seus próprios tokens puláveis ​​e onde eles são aplicáveis. Os espaços em branco e os comentários fazem parte da definição da linguagem e, portanto, da gramática. Acontece que, com a maioria das linguagens, os tokens puláveis ​​podem estar entre todos os outros tokens e usar uma regra de descarte torna isso muito mais simples do que escrever expr = WS lit WS op WS expr WS ";" para cada regra. Imagine uma gramática como a de C com tratamento de whitepsace?

Eu entendo que reconstituir regras de descarte em pegjs não seja fácil, mas isso não significa que não seja um objetivo louvável.

Oh cara, seção de resposta gratuita! Eu tenho muito a dizer, desculpe pela extensão.

1) Para o pessoal de TL; DR, se eu pudesse adicionar quaisquer elementos de fixação, eu quisesse, eu teria escrito assim

header_field
= field_name ":" field_value

espaço em branco (IGNORE)
= [t] +

A adição que eu faria é uma seção de opções que pode ser incluída em qualquer produção

A linguagem http-bis não seria limitada por esta reescrita (consulte o apêndice a).

2) Meu problema com a proposta #

Parece que você está trocando exigindo que o usuário preencha a definição do analisador com um monte
de descartar não terminais (geralmente espaços em branco / delimitadores) exigindo que o usuário preencha
a definição do analisador com um monte de metacaracteres "aqui os caracteres não são descartados"
desnecessariamente. É certo que haveria menos ocorrências disso. É o caso raro quando
as pessoas realmente consomem delimitadores e fazem algo com eles, e como eu comento em

apêndice a, HTTP-bis não é uma dessas ocorrências, apenas mal documentada.

3) Estados do analisador definidos pelo usuário

Mas posso ver como seria mais fácil para o definidor do analisador simplesmente recortar e colar o
especificação de idioma da definição, então se você deve ter algo assim, então
isso poderia ser feito com estados lexicais, conforme aludido anteriormente por Sean. Eu acho que faria isso
Da seguinte maneira.

produção1 (estado == 1)
= coisas

produção2 (estado == 2)
= coisas

produção 3
= coisas {estado = 1}

produção 4
= coisas {estado = 2}

Em outras palavras, assim como lex / yacc torna possível que as produções só estejam disponíveis

se o sistema estiver em um determinado estado e permitir que o usuário defina esse valor de estado.

4) Mais opções

Ou você pode tornar mais fácil para o usuário e mais aparente para o leitor com outro
opção

produção (DONTIGNORE)
= coisas

O que permitiria ao analisador substituir a ação padrão de descartar tokens marcados
como descarte, mas apenas para aquela produção. Este é realmente o mesmo que 3, apenas um mais fácil
leitura. Isso é menos flexível do que a # proposta porque uma produção é totalmente ignorada

ou não ignore, mas não acho que flexibilidade extra seja necessária.

5) Adicionar um parâmetro a getNextToken () permite a sensibilidade ao contexto

Acho que tudo isso se resume (estou fazendo algumas suposições aqui) atualmente, o
a parte do analisador chama getNextToken (input), e o que precisa acontecer é adicionar um

parâmetro para ele getNextToken (entrada, opções).

Apêndice a) Essa especificação HTTP-bis

Ok, eu li alguns, mas não li tudo isso

Protocolo de transferência de hipertexto (HTTP / 1.1): Sintaxe e roteamento de mensagens
draft-ietf-httpbis-p1-messaging-26

Não gosto da maneira como eles definiram sua gramática. Eu não sugiro mudar a entrada
aceita, mas eu não o teria definido como eles fizeram. Em particular, eu não gosto de por que eles têm
definiu OWS e RWS e BWS que equivalem exatamente à mesma sequência de caracteres
mas em contextos diferentes. Eles definiram

OWS :: == (SP | HTAB) *
RWS :: == (SP | HTAB) +
BWS :: == OWS

que é apenas a repetição de tabulações e espaços

Por nenhuma boa razão. Eles tornaram a linguagem mais difícil de analisar - exigem o analisador léxico
para rastrear seu contexto - e eles não precisaram fazer isso.

Eles definiram OWS como "espaço em branco opcional" BWS como "espaço em branco inválido" ou de outra forma opcional
espaço em branco, mas no contexto "ruim" - onde não é necessário - e o RWS requer espaço em branco onde é
necessário para delimitar tokens. Em nenhum lugar este espaço em branco é usado exceto talvez possa haver um analisador
aviso se corresponder ao BWS ("detectou espaços em branco desnecessários" ou algo assim), que é tudo
delimitadores fazem de qualquer maneira.

Em suas especificações, o único lugar onde o RWS é usado é aqui

Via = 1 # (protocolo RWS recebido recebido por [comentário RWS])

 received-protocol = [ protocol-name "/" ] protocol-version
                     ; see Section 6.7
 received-by       = ( uri-host [ ":" port ] ) / pseudonym
 pseudonym         = token

mas 'versão do protocolo' são números e talvez letras, enquanto 'recebido por' são números e letras. Em outras palavras,
o analisador léxico não reconhecerá corretamente essas 2 partes, a menos que estejam separadas por um espaço em branco
e vai ser um erro de sintaxe com ou sem RWS sendo explicitamente identificado se não houver pelo menos 1
caractere de espaço em branco. Portanto, apenas remova o RWS das produções e trate os espaços em branco
em todos os lugares como um delimitador e não muda o idioma, apenas como é documentado.

Em 24 de abril de 2014, às 13h23, Andrei Neculau [email protected] escreveu:

@waTeim @rioki Esqueça minha sugestão.

Mãos à obra, siga esta regra. Se você gostaria de simplificar a gramática da regra removendo o OWS, então como você instruiria os PEGjs a não permitir o OWS entre field_name e:?

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

@waTeim Acho que você está

Nunca vi qualquer uso adequado de estados lexer provenientes do analisador. O problema fundamental aqui é que, olhando para frente, quando o analisador vê o token para alternar os estados, o lexer já lexou erroneamente o próximo token. O que você propõe é quase impossível de implementar sem back-tracking e isso nunca é um bom recurso em um analisador.

Ao escrever uma gramática, você basicamente define quais produções são consideradas analisadas e quais podem ser saboreadas. No exemplo de @andreineculau , existem duas opções, ou você lida com espaços em branco no analisador ou define a parte ":" à direita do token. ( [a-zA-Z0-9!#$%&'+-.^_|~]+ ":" ).

Eu poderia sugerir transformar o problema em especificar uma lista branca - quais partes eu desejo capturar e transformar - em vez de uma lista negra. Embora o espaço em branco seja um problema com o sistema de captura atual, o aninhamento de regras é outro. Como escrevi na edição 66, o sistema LPeg de especificar o que você deseja capturar diretamente, por meio de transformações ou capturas de string, parece mais útil para mim do que especificar um punhado de produções para pular e ainda lidar com o aninhamento de todas as outras produções.

Veja meu comentário na edição nº 66 para um exemplo simples de LPeg versus PEG.js com relação a capturas. Embora os nomes sejam um pouco enigmáticos, consulte a seção Capturas da documentação LPeg para as várias maneiras de capturar ou transformar uma determinada produção (ou parte dela).

Olá, criei um snippet para ignorar alguns casos gerais: null , undefined e strings com apenas símbolos de espaço.
Pode ser exigido no cabeçalho do arquivo de gramática, como:

{
  var strip = require('./strip-ast');
}

As duas maneiras de melhorá-lo:

  • Filtro personalizável para termos - para ignorar os termos específicos que uma determinada gramática é necessária.
  • Ignorar matrizes vazias aninhadas - isso pode ser feito no segundo estágio depois de strip , removerá «pirâmides» de matrizes vazias aninhadas.
    Se houver alguém interessado, podemos atualizá-lo para um pacote.

@ richb-hanover Onde foram parar seus esforços de analisador de definição ASN.1?

@atesgoral - Eu

Então eu fiz o que qualquer cara covarde faria - usei expressões regulares. (E então eu tive dois problemas :-)

Mas funcionou, então pude passar para o próximo desafio. Boa sorte no seu projeto!

Depois de dar uma olhada no chevrotain e sua opção de ignorar , algo como isso é extremamente desejável.

Muitas vezes nos pegamos escrevendo algo assim:

Pattern = head:PatternPart tail:( WS "," WS PatternPart )*
{
  return {
    type: 'pattern',
    elements: buildList( head, tail, 3 )
  };
}

Seria legal se pudéssemos escrever isso em vez disso:

WS "whitespace" = [ \t\n\r] { return '@<strong i="11">@skipped</strong>' }

IgnoredComma = "," { return '@<strong i="12">@skipped</strong>' }

Pattern = head:PatternPart tail:( WS IgnoredComma WS PatternPart )*
{
  return {
    type: 'pattern',
    elements: [head].concat(tail)
  };
}

@ richb-hanover, e qualquer outra pessoa que chegou aqui em busca de uma necessidade semelhante, acabei escrevendo meus próprios analisadores também: https://www.npmjs.com/package/asn1exp e https: //www.npmjs. com / package / asn1-tree

Um salto seria relativamente fácil de implementar usando es6 symbol , ou talvez mais duramente, passando ao analisador um predicado no tempo de análise (eu prefiro a última opção)

Também tropecei nisso.
Sem saber nada sobre as entranhas do PEG.js, deixe-me jogar um osso lá fora ...

Quando escrevemos uma regra, no final dela podemos adicionar um bloco de retorno.
Nesse bloco, podemos chamar coisas como text() e location() . Estas são funções internas.

Em algum lugar do código, o valor retornado desse bloco vai para o fluxo de saída.

Então, o que precisaria mudar em PEG.js se eu quiser pular um valor retornado por uma regra se esse valor for o retorno da chamada de uma função local skip ?

por exemplo, comment = "//" space ([^\n])* newline { return skip() }

Como mencionado acima, skip () pode retornar um símbolo, que é então verificado pelo código em algum lugar e removido.
Algo parecido com o que lzhaki disse, mas interno à biblioteca

Eu não entendo sua pergunta. Você está procurando uma maneira de falhar com uma regra em algumas circunstâncias? Use &{...} ou !{...} . Caso contrário, não use o valor retornado da regra comment :

seq = comment r:another_rule { return r; };
choice = (comment / another_rule) { <you need to decide what to return instead of "comment" result> };

Se isso ajudar alguém, eu ignoro os espaços em branco, fazendo com que minha regra de nível superior filtre o conjunto de resultados.

Exemplo:

    = prog:expression+ {return prog.filter(a => a)}

expression
    = float
    / number
    / whitespace

float
    = digits:(number"."number) {return parseFloat(digits.join(""),10)}

number 
    = digits:digit+ {return parseInt(digits.join(""),10)}

digit 
    = [0-9]

whitespace
    = [ \t\r\n] {return undefined}

Isso analisará alegremente a entrada enquanto mantém os espaços em branco fora do array de resultados.
Isso também funcionará para coisas como comentários, basta ter a regra de retorno indefinida e a regra de nível superior irá filtrá-la

Isso só funciona para produções de nível superior. Você deve filtrar manualmente todos os pais que podem conter um filho filtrável.

@StoneCypher Verdadeiro, requer algum trabalho de nível superior, mas funciona para mim, e acho que, desde que o gammar não seja muito complexo, deve-se conseguir ter um filtro de nível superior.

Fora isso, tudo o que posso pensar é em ter uma função de nível superior que filtra os espaços em branco da entrada e passa todas as correspondências por meio deles. Mais lento com certeza e requer muito mais chamadas, mas fácil se você (como eu) passar tudo para um gerador de tokens. Você pode chamar a função de filtro de onde você gera tokens, e só precisa se preocupar em gerar seus tokens e o espaço em branco é mais ou menos filtrado automaticamente

Uma das coisas que gostei no HEAD atual dos pegjs é seu suporte (não documentado) para separar campos sem ter que criar rótulos e fazer declarações de retorno. Se parece com isso:

foo = <strong i="6">@bar</strong> _ <strong i="7">@baz</strong>
bar = $"bar"i
baz = $"baz"i
_ = " "*
parse('barbaz') // returns [ 'bar', 'baz' ]

Acho que isso fornece uma sintaxe agradável, limpa e explícita para este caso de uso, além de vários outros.

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

Questões relacionadas

brettz9 picture brettz9  ·  8Comentários

mattkanwisher picture mattkanwisher  ·  5Comentários

doersino picture doersino  ·  15Comentários

richb-hanover picture richb-hanover  ·  7Comentários

dmajda picture dmajda  ·  7Comentários