Pegjs: Importar / incluir outras gramáticas

Criado em 16 ago. 2011  ·  32Comentários  ·  Fonte: pegjs/pegjs

Pode ser extremamente útil ter a habilidade de definir gramáticas importando regras de outras gramáticas.

Diversas ideias;

<strong i="7">@include</strong> "expression.pegjs"
(or <strong i="8">@from</strong> "expression.pegjs" import expression)

tag_if
    = "if" space? expression space? { ... }

<strong i="9">@import</strong> "expression.pegjs" as expr

tag_if
    = "if" space? expr.expression space?

Idealmente, isso não geraria novamente todo o código em cada .pegjs que inclui outro; talvez tivéssemos que modificar um pouco o comportamento de parse () para algo parecido;

Editando de acordo com o que você estava dizendo na edição options ;

parse(input, startRule)
->
parse(input, { startRule: "...", startPos : 9000 })

E no final, se startPos != 0 && result !== null , não verificamos se fomos até input.length , mas em vez disso, retornamos o resultado, bem como o endPos (não sei realmente como fazer isso elegantemente - talvez simplesmente modificando o parâmetro de opções?).

Isso permitiria a reutilização de gramáticas e modularização do código, que eu acho que são dois aspectos extremamente importantes da codificação em geral.

feature

Comentários muito úteis

@Dignifiedquire Atualmente, estou pensando sobre sintaxe e semântica que provavelmente podem ser melhor explicadas por um exemplo:

static-languages.pegjs

langauges  = "C" / "C++" / "Java" / "C#"

dynamic-languages.pegjs

languages = "Ruby" / "Python" / "JavaScript"

all-languages.pegjs

static  = require("./static-languages")
dynamic = require("./dynamic-languages")

all = static.languages / dynamic.languages

Cada arquivo .pegjs definiria implicitamente um módulo que exportaria todas as regras que ele contém. A construção <name> = require(<module>) importaria tal módulo. Suas regras estariam então disponíveis dentro de um namespace.

Este design é deliberadamente semelhante ao Node.js. O uso de namespaces evitará conflitos. Existem duas desvantagens que vejo:

  1. A construção <name> = require(<module>) é muito semelhante às definições de regra e, portanto, pode ser confusa (pode-se pensar que apenas uma regra é importada).
  2. A sintaxe . conflito com o significado atual de . , que é “qualquer caractere”. Isso pode ser resolvido por hacks feios (por exemplo, . cercado por espaço em branco significa “qualquer caractere”, enquanto . cercado por identificadores separa um nome de namespace de um nome de regra) ou alterando a sintaxe (por exemplo usando a palavra-chave any para representar “qualquer caractere”).

Todos 32 comentários

Concordo que este é um recurso importante, desejo fazê-lo após a versão 1.0.

(BTW, eu não gosto da sintaxe semelhante ao Python que você propõe - algo semelhante ao require do Node.js seria melhor porque seria mais familiar para os programadores de JavaScript. Mas isso é uma coisa menor que pode ser resolvida mais tarde.)

Você o consideraria para inclusão antes de 1.0 se fornecido com um patch?

Eu concordo com seu comentário sobre a sintaxe python.

+1 para este recurso

@ceymard Sim, eu consideraria isso.

+1 para o recurso e +1 para require inclusão de estilo

@dmajda @ceymard Você já tem alguma

@Dignifiedquire Atualmente, estou pensando sobre sintaxe e semântica que provavelmente podem ser melhor explicadas por um exemplo:

static-languages.pegjs

langauges  = "C" / "C++" / "Java" / "C#"

dynamic-languages.pegjs

languages = "Ruby" / "Python" / "JavaScript"

all-languages.pegjs

static  = require("./static-languages")
dynamic = require("./dynamic-languages")

all = static.languages / dynamic.languages

Cada arquivo .pegjs definiria implicitamente um módulo que exportaria todas as regras que ele contém. A construção <name> = require(<module>) importaria tal módulo. Suas regras estariam então disponíveis dentro de um namespace.

Este design é deliberadamente semelhante ao Node.js. O uso de namespaces evitará conflitos. Existem duas desvantagens que vejo:

  1. A construção <name> = require(<module>) é muito semelhante às definições de regra e, portanto, pode ser confusa (pode-se pensar que apenas uma regra é importada).
  2. A sintaxe . conflito com o significado atual de . , que é “qualquer caractere”. Isso pode ser resolvido por hacks feios (por exemplo, . cercado por espaço em branco significa “qualquer caractere”, enquanto . cercado por identificadores separa um nome de namespace de um nome de regra) ou alterando a sintaxe (por exemplo usando a palavra-chave any para representar “qualquer caractere”).

@dmajda Como o padrão <identifier> = <expression> já é assumido pelas definições de regra, por que não fazer algo assim:

static := require("./static-languages")
dynamic := require("./dynamic-languages")

all = static::languages / dynamic::languages

O :: não é usado em nenhum lugar que eu conheça no PEG.js e torna mais fácil distinguir entre namespaces e outras coisas. Não tenho certeza sobre o := que mostra o ponto, mas parece muito estranho para Javascript.

Além disso, se quiser usar namespaces, você acha que deve haver apenas um namespace por arquivo ou deve haver uma maneira de criar vários namespaces em um arquivo como este:

static := {
  languages  = "C" / "C++" / "Java" / "C#"
}

dynamic := {
  languages = "Ruby" / "Python" / "JavaScript"
}

Não sou muito fã de :: e := , eles parecem estranhos no mundo javaScript / CoffeeScript.

Eu também gostaria de manter as coisas simples e definir namespaces implicitamente apenas exigindo arquivos. Não vejo grande necessidade de nada mais complicado.

Que tal simplesmente:

<strong i="6">@require</strong> foo = "./foo"

bar = foo:languages

Os dois pontos são um meio-termo, mas são usados ​​para separar namespaces em muitos lugares: C ++, C #, XML, etc.

: sempre estará associado a cons para muitos, muitos programadores funcionais. Eu sugiro ficar longe dessa operadora. :: parece bom para mim. Isso não é usado para namespaces C ++? Também não estou convencido de que . seja uma escolha ruim.

. não pode ser usado sem uma alteração significativa. Seria ambíguo na linguagem.

:: é usado em C ++ para namespaces e em C # para prefixos de namespace ( global::System , por exemplo).

Eu estava pensando em uma solução rápida neste tópico - para resolver apenas a herança simples - colar os arquivos pegjs juntos, mantendo tudo com namespace.

Isso pode tornar as gramáticas muito prolixas e envolver uma etapa de construção - mas olhando pelo lado bom, isso o forçaria a ter gramáticas DRY e OTW granulares

E em relação à marcação, sem dizer que se encaixa bem neste tópico, mas apenas uma opção a ser considerada, eu estava optando por um simples__

languages = static__languages / dynamic__languages
<static-languages.pegjs>
<dynamic-languages.pegjs>
/* alternative */
languages = STATIC__languages / DYNAMIC__languages

@andreineculau Eu basicamente já estou fazendo isso com uma etapa de construção, então se você e outros estão apenas procurando algo para gerar analisadores úteis de uma gramática com uma árvore de dependência (onde um único analisador implementando a gramática combinada é gerado), eu posso limpe o que tenho e libere para que a discussão possa se concentrar em como lidar com isso de uma forma mais permanente.

Outra coisa: abordar isso principalmente projetando extensões para a sintaxe da gramática deixa escapar algo importante, que é que uma das principais razões pelas quais todos temos vontade de extrair regras de outras gramáticas (outra sendo clareza) é a necessidade de escrever analisadores que compartilham muita lógica. Portanto, embora os analisadores analíticos gerados possam nunca ser recomponíveis de forma significativa no tempo de análise, parece importante que uma árvore de gramáticas gere uma árvore de analisadores, em vez de um analisador monolítico. É mais importante quando um conjunto de analisadores fará parte de uma IU da web, mas geralmente não custa evitar inchaço desnecessário no código gerado.

@odonnell +1 por liberar qualquer coisa - não importa se você tem tempo para limpar

e +1 para o esclarecimento. Isso deve ser tratado como uma solução alternativa rápida, não uma solução adequada de longo prazo.

@odonnell minha opinião sobre isso está online em https://github.com/andreineculau/core-pegjs - por favor, me cutuque se você tiver algo melhor.

+1 para este recurso

: +1:

: +1:

: +1:

Fui e escrevi um plugin / extensão para PEG.js que faz importações: https://github.com/casetext/pegjs-import.

1 para isso também.

Eu implemento isso no # 308 de maneira genérica: a inclusão de gramática é apenas uma maneira de implementar regras de decomposição.

Excelente recurso: +1:

Ansioso para vê-lo lançado.

: +1:

Impressionante! : +1:

@dmajda Estou chegando atrasado para esta festa, mas me pergunto com que freqüência precisamos importar muitas regras de outra biblioteca. Eu adoraria poder importar coisas como Url e Email para minhas gramáticas compostas, mas não me importo que Url também possa ter coisas como HierarchicalPart e AsciiLetter . Você acha que algo como as exportações nomeadas do Node seria uma maneira viável de avançar, mantendo os benefícios do namespacing, mas permitindo importações nomeadas diretas?

import { SchemalessUrl, Url } from "./Urls.pegjs"

Token
  = PhoneNumber
  / Url
  / SchemalessUrl

O namespacing tem sido um problema para mim enquanto tento explorar a escrita de gramáticas que podem ser compostas de outra forma. Estou preso agora, incluindo arquivos em arquivos e nomeando coisas da maneira como as funções PHP eram nomeadas antes de introduzirem os namespaces apropriados: UrlIpHost , HtmlQuotedString , etc ...

@dmajda @futagoza

Algum progresso neste assunto? ou a discussão principal vivendo agora no # 473?
Meu arquivo de gramática está crescendo muito rápido :(
Seria bom dividi-lo em vários

Eu não me importaria de poder dividir gramáticas entre arquivos, simplesmente para organização e composição. Isso os tornaria mais fáceis de testar e reutilizar, além de fornecer uma maneira de trocar gramáticas dinamicamente, talvez? Apenas alguns pensamentos.

O exemplo de JavaScript que usei como base tem mais de 1.300 linhas. Demorou um pouco para aprender onde estava tudo, pular e editar as diferentes seções.

@mikeaustin Eu vejo esse recurso como uma espécie de Node.JS required :

cat bash.pegjs
{
const _ = require ("espaço em branco");
const LB = requer ("line_break");
const CodeBlock = require ("code_block");
const BoolExpr = require ("boolean_expression");
}
...
IfStatement = "if" _ "[" BoolExpr "]" _ ";" _ "então" LB? CodeBlock "fi"

Eu concordo, dividir gramáticas e torná-las modulares é um ótimo recurso, mas lidar com esses casos seria um problema:
1- sub-gramática que depende de uma variável global que foi definida no código da gramática principal?
2- variáveis ​​duplicadas e nome da gramática?

IMO, uma abordagem temporalmente conveniente seria criar um novo addon para PEG.js (independente de PEG.js) que define uma palavra-chave para importação (por exemplo @load (anotherGrammarFileLocation)) palavra-chave não deve fazer parte da gramática javacsript / peg.js,
construir um reg-exp ou uma gramática de peg para detectar essa palavra-chave e substituí-la pelo conteúdo de "anotherGrammarFile Location" e enviar o código substituído para PEG.js

Exemplo:

integers.pegjs

integers=[0-9]* {return parseInt(text())}

main.pegjs
arrayOfInteger="["(integers ",")* integers"]"
@load("integers.pegjs")

Observe o uso deste método, se alguém não definiu a gramática inicial e colocou @load antes de "arrayOfInteger" peg.js assumirá a primeira gramática como o início (gramática de inteiros)

Uma abordagem para lidar com isso é usar os mesmos nomes de nome de arquivo e iniciar a gramática e deixar o novo ad-don configurar manualmente o atributo start como o nome do arquivo ou substituir todo o conteúdo no final do arquivo.

o usuário deve ser responsável por qualquer duplicação.

Só quero destacar que esse problema é principalmente uma solicitação de otimização, porque composibilidade / modularidade é algo que você pode alcançar por conta própria, especialmente quando controla todo o espectro da gramática.

Se você não se sentir confortável com uma gramática de 1k linhas de comprimento, divida-a e concatene-a de volta como achar melhor antes de colocá-la nos pegjs.

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