As passagens de análise e lexing separadas são uma boa prática com combinadores de analisadores?

18

Quando comecei a usar combinadores de analisadores, minha primeira reação foi uma sensação de libertação do que parecia uma distinção artificial entre análise e lexing. De repente, tudo estava apenas analisando!

No entanto, recentemente deparei com esta postagem no codereview.stackexchange ilustrando alguém que restabelece essa distinção. A princípio, achei que isso era muito bobo, mas o fato de existirem funções no Parsec para apoiar esse comportamento me leva a me questionar.

Quais são as vantagens / desvantagens de analisar em um fluxo já flexível nos combinadores de analisadores?

Eli Frey
fonte
Alguém poderia adicionar a tag [parser-combinator]?
Eli Frey

Respostas:

15

Na análise, entendemos com mais frequência a análise de linguagens livres de contexto. Uma linguagem livre de contexto é mais poderosa que a linguagem comum; portanto, o analisador pode (na maioria das vezes) executar o trabalho do analisador lexical imediatamente.

Mas isso é a) bastante antinatural b) muitas vezes ineficiente.

Para a), se eu pensar em como, por exemplo, uma ifexpressão, acho que, se expr THEN expr ELSE expr e não 'i' 'f', talvez alguns espaços, qualquer caractere com o qual uma expressão possa começar etc. idéia.

Para b) existem ferramentas poderosas que fazem um excelente trabalho ao reconhecer entidades lexicais, como identificadores, literais, colchetes de todos os tipos, etc. Eles farão seu trabalho praticamente em pouco tempo e fornecerão uma ótima interface: uma lista de tokens. Não se preocupe em ignorar mais espaços no analisador, seu analisador será muito mais abstrato quando lidar com tokens e não com caracteres.

Afinal, se você acha que um analisador deve estar ocupado com coisas de baixo nível, por que então processar caracteres? Pode-se escrever também no nível de bits! Veja bem, esse analisador que funciona no nível de bits seria quase incompreensível. É o mesmo com personagens e tokens.

Apenas meus 2 centavos.

Ingo
fonte
3
Apenas por uma questão de precisão: um analisador sempre pode fazer o trabalho de um analisador lexical.
Giorgio
Além disso, com relação à eficiência: não tenho certeza se um analisador seria menos eficiente (mais lento). Eu esperaria que a gramática resultante contivesse uma sub-gramática que descreva uma linguagem comum, e o código para essa sub-gramática fosse tão rápido quanto um analisador lexical correspondente. Na IMO, o ponto real é (a): quão natural e intuitivo é trabalhar com um analisador mais simples e abstrato.
Giorgio
@Giorgio - Quanto ao seu primeiro comentário: Você está certo. O que eu tinha em mente aqui são casos em que o lexer pratica de forma pragmática algum trabalho que facilita a gramática, para que se possa usar LALR (1) em vez de LALR (2).
Ingo
2
Retirei minha aceitação de sua resposta depois de mais experimentação e reflexão. Parece que vocês dois vêm do mundo de Antlr et all. Considerando a natureza de primeira classe dos combinadores de analisadores, geralmente acabo definindo um analisador de wrapper para meus analisadores de token, deixando cada token como um nome único na camada de análise dos analisadores. por exemplo, seu exemplo seria if = string "if" >> expr >> string "then" >> expr >> string "else" >> expr.
Eli Frey
1
O desempenho ainda é uma questão em aberto, vou fazer alguns benchmarks.
Eli Frey
8

Todo mundo sugerindo que separar lexing e análise é uma "boa prática" - eu tenho que discordar - em muitos casos, executar lexing e analisar em uma única passagem dá muito mais poder, e as implicações de desempenho não são tão ruins quanto são apresentadas no outras respostas (consulte Packrat ).

Essa abordagem brilha quando é preciso misturar vários idiomas diferentes em um único fluxo de entrada. Isso não é apenas necessário por linguagens estranhas orientadas à metaprogramação, como Katahdin e similares , mas também para aplicativos muito mais populares, como programação alfabetizada (misturando latex e, digamos, C ++), usando HTML em comentários, colocando Javascript em HTML e em breve.

SK-logic
fonte
Na minha resposta, sugeri que é uma "boa prática em certos contextos" e não que seja uma "melhor prática em todos os contextos".
Giorgio
5

Um analisador lexical reconhece uma linguagem regular e um analisador reconhece uma linguagem livre de contexto. Como cada linguagem regular também é livre de contexto (pode ser definida por uma gramática linear-direita ), um analisador também pode reconhecer uma linguagem regular e a distinção entre analisador e analisador lexical parece adicionar alguma complexidade desnecessária: um único contexto A gramática livre (analisador) poderia fazer o trabalho de um analisador e de um analisador lexical.

Por outro lado, pode ser útil capturar alguns elementos de uma linguagem livre de contexto por meio de uma linguagem regular (e, portanto, um analisador lexical) porque

  1. Freqüentemente, esses elementos aparecem com tanta frequência que podem ser tratados de maneira padrão: reconhecendo literais de número e sequência, palavras-chave, identificadores, ignorando espaços em branco e assim por diante.
  2. Definir uma linguagem regular de tokens torna a gramática livre de contexto resultante mais simples, por exemplo, pode-se raciocinar em termos de identificadores, não em caracteres individuais, ou pode-se ignorar completamente o espaço em branco se não for relevante para esse idioma em particular.

Portanto, separar a análise da análise lexical tem a vantagem de poder trabalhar com uma gramática livre de contexto mais simples e encapsular algumas tarefas básicas (geralmente rotineiras) no analisador lexical (divide et impera).

EDITAR

Eu não estou familiarizado com os combinadores de analisadores, portanto, não tenho certeza de como as considerações acima se aplicam nesse contexto. Minha impressão é que, mesmo com os combinadores de analisadores, apenas um tenha uma gramática livre de contexto, a distinção entre dois níveis (análise / análise lexical) poderia ajudar a tornar essa gramática mais modular. Como dito, a camada de análise lexical inferior pode conter analisadores básicos reutilizáveis ​​para identificadores, literais e assim por diante.

Giorgio
fonte
2
Lexemes cai em gramáticas regulares não naturalmente, mas por convenção, uma vez que todos os lexers são construídos sobre mecanismos de expressão regular. Isso está limitando o poder expressivo dos idiomas que você pode criar.
SK-logic
1
Você pode dar um exemplo de um idioma para o qual seria apropriado definir lexemes que não podem ser descritos como um idioma regular?
Giorgio
1
por exemplo, em algumas das linguagens específicas de domínio que eu criei, os identificadores poderiam ter sido expressões TeX, o que simplificou a impressão bonita do código, por exemplo, uma expressão como \alpha'_1 (K_0, \vec{T}), onde \ alpha'_1, K_0 e \ vec {T} são identificadores.
SK-logic
1
Dada uma gramática livre de contexto, você sempre pode usar um N não terminal e tratar as palavras que podem derivar como unidades que possuem um significado útil em si mesmas (por exemplo, uma expressão, um termo, um número, uma declaração). Isso pode ser feito independentemente de como você analisa essa unidade (analisador, analisador + lexer, etc.). Na IMO, a escolha de um analisador + lexer é mais técnica (como implementar a análise) do que semântica (qual é o significado dos blocos de código-fonte que você analisa). Talvez eu esteja ignorando alguma coisa, mas os dois aspectos parecem ortogonais para mim.
Giorgio
3
Então, eu concordo com você: se você definir alguns blocos de construção básicos arbitrários ( lexemes ) e quiser usar um analisador lexical para reconhecê-los, isso nem sempre é possível. Só me pergunto se esse é o objetivo de um lexer. Até onde eu entendo, o objetivo de um analisador lexical é mais técnico: tirar alguns detalhes de implementação tediosos e de baixo nível do analisador.
Giorgio
3

Simplesmente, lexing e análise devem ser separados porque são complexidades diferentes. Lexing é um DFA (autômato finito determinístico) e um analisador é um PDA (autômato push-down). Isso significa que a análise consome inerentemente mais recursos do que a lexing, e existem técnicas de otimização específicas disponíveis apenas para os DFAs. Além disso, escrever uma máquina de estados finitos é muito menos complexo e é mais fácil de automatizar.

Você está sendo um desperdício usando um algoritmo de análise para lex.

DeadMG
fonte
Se você usar um analisador para fazer uma análise lexical, o PDA nunca usaria a pilha, funcionaria basicamente como um DFA: apenas consumindo entrada e saltando entre estados. Não tenho 100% de certeza, mas acho que as técnicas de otimização (reduzindo o número de estados) que podem ser aplicadas a um DFA também podem ser aplicadas a um PDA. Mas sim: é mais fácil escrever o analisador lexical como tal sem usar uma ferramenta mais poderosa e, em seguida, escrever um analisador mais simples em cima dele.
Giorgio
Além disso, torna tudo mais flexível e sustentável. Por exemplo, suponha que tenhamos um analisador para a linguagem Haskell sem a regra de layout (ou seja, com ponto e vírgula e chaves). Se tivermos um lexer separado, agora poderemos adicionar as regras de layout fazendo outra passagem sobre os tokens, adicionando chaves e ponto e vírgula conforme necessário. Ou, para um exemplo mais fácil: suponha que começamos com uma linguagem que suporta caracteres ASCII apenas nos identificadores e agora queremos oferecer suporte a letras unicode nos identificadores.
Ingo
1
@ Ingo, e por que você precisaria fazê-lo em um lexer separado? Apenas considere esses terminais.
SK-logic
1
@ SK-logic: Não sei se entendi sua pergunta. Por que um lexer separado pode ser uma boa escolha, tentei comprovar no meu post.
Ingo
Giorgio, não. A pilha é um componente crucial de um analisador de estilo LALR normal. Fazer lexing com um analisador é um terrível desperdício de memória (armazenamento estático e alocado dinamicamente) e será muito mais lento. O modelo Lexer / Analisador é eficiente - usá-lo :)
riwalk
1

Uma das principais vantagens da análise / análise separada é a representação intermediária - o fluxo de token. Isso pode ser processado de várias maneiras que, de outra forma, não seriam possíveis com uma combinação lex / análise.

Dito isso, descobri que um bom e decente recursivo pode ser menos complicado e mais fácil de trabalhar do que aprender um gerador de analisador, e ter que descobrir como expressar a fraqueza da gramática dentro das regras do gerador de analisador.

sylvanaar
fonte
Você poderia explicar mais sobre gramáticas que são mais facilmente expressas em um fluxo pré-fabricado do que executadas no momento da análise? Eu só tenho experiência em implementar linguagens de brinquedo e poucos formatos de dados, então talvez eu tenha perdido alguma coisa. Você notou alguma característica de desempenho entre o seu analisador RD / combos de lex rolados à mão e os geradores alimentados por BNF (presumo)?
Eli Frey