Nome para este tipo de analisador, OU por que não existe


27

Os analisadores convencionais consomem toda a entrada e produzem uma única árvore de análise. Estou procurando por um que consuma um fluxo contínuo e produz uma floresta de análise [ editar: veja a discussão nos comentários sobre por que esse uso desse termo pode não ser convencional ]. Meu intestino diz que não posso ser a primeira pessoa a precisar (ou achar que preciso) de um analisador, mas pesquisei sem parar por meses sem sucesso.

Reconheço que posso estar enredado pelo problema XY. Meu objetivo final é analisar um fluxo de texto, ignorando a maior parte, e produzir um fluxo de árvores de análise a partir das seções que são reconhecidas.

Portanto, minha pergunta é condicional: se existe uma classe de analisadores com essas características, como é chamada? E se não, por que não? Qual é a alternativa? Talvez esteja faltando alguma maneira de fazer com que os analisadores convencionais façam o que eu quero.


1
Basicamente, seu analisador analisa um único documento e produz uma árvore de análise, e imediatamente começa a analisar outro documento, etc. Suponho que essa modificação de comportamento seja trivial em comparação com a variedade de técnicas de análise aplicadas a um único documento. Daí a falta de um termo especial para isso.
9000

3
Fiz uma pesquisa no Google por "Parse Forest" e descobri que o Earley Parser os produz.
Robert Harvey

7
Você está possivelmente procurando por combinadores de analisador monádico - ou seja, um analisador maior composto por vários analisadores menores. Eles são úteis para situações em que uma "ilha" de um idioma está incorporada em outro. Meu ex-colega da equipe de design de C # Luke Hoban tem um bom artigo sobre eles: blogs.msdn.com/b/lukeh/archive/2007/08/19/…
Eric Lippert

3
Há alguma confusão. Você quer dizer que deseja uma árvore de análise para cada documento no seu fluxo e que eles formem uma floresta de análise? Esse não é o significado usual de analisar floresta. Uma floresta de análise é um conjunto de árvores de análise para um único documento ambíguo (simplificando um pouco) que pode ser analisado de maneiras diferentes. E é disso que trata todas as respostas. Seu fluxo é composto de muitos documentos completos, separados por lixo ou é um documento único que foi parcialmente ilegível. Seu documento deve estar sintaticamente correto ou não? A resposta técnica adequada depende disso.
babou

1
Em seguida, esqueça todas as respostas sobre florestas de análise e derivados de Earley, GLR, Marpa. Aparentemente, eles não são o que você deseja, a menos que outro motivo apareça. Seus documentos estão sintaticamente corretos? Algumas técnicas de análise podem recriar o contexto para documentos parcialmente ilegíveis. Você tem uma sintaxe precisa para esses documentos. É o mesmo para todos? Você realmente deseja as árvores de análise, ou ficaria satisfeito isolando os documentos e possivelmente analisando-os posteriormente, separadamente. Acho que sei o que poderia melhorar seu processamento, mas não tenho certeza de que você possa tirá-lo da prateleira.
babou 16/10

Respostas:


48

Um analisador que retorna um resultado (parcial) antes que toda a entrada tenha sido consumida é chamado de analisador incremental . A análise incremental pode ser difícil se houver ambiguidades locais em uma gramática que são decididas apenas posteriormente na entrada. Outra dificuldade é fingir as partes da árvore de análise que ainda não foram alcançadas.

Um analisador que retorna uma floresta de todas as possíveis árvores de análise - ou seja, retorna uma árvore de análise para cada derivação possível de uma gramática ambígua - é chamado ... Não tenho certeza se essas coisas ainda têm nome. Eu sei que o gerador de analisador Marpa é capaz disso, mas qualquer analisador baseado em Earley ou GLR deve ser capaz de fazer isso.


No entanto, você parece não querer nada disso. Você tem um fluxo com vários documentos incorporados, com lixo no meio:

 garbagegarbage{key:42}garbagegarbage[1,2,3]{id:0}garbage...

Você parece querer um analisador que pule o lixo e (preguiçosamente) produz uma sequência de ASTs para cada documento. Isso pode ser considerado um analisador incremental em seu sentido mais geral. Mas você realmente implementaria um loop como este:

while stream is not empty:
  try:
    yield parse_document(stream at current position)
  except:
    advance position in stream by 1 character or token

A parse_docmentfunção seria então um analisador convencional, não incremental. Há uma dificuldade menor de garantir que você tenha lido o suficiente do fluxo de entrada para uma análise bem-sucedida. Como isso pode ser tratado depende do tipo de analisador que você está usando. As possibilidades incluem aumentar um buffer em certos erros de análise ou usar tokenização lenta.

A tokenização lenta é provavelmente a solução mais elegante devido ao seu fluxo de entrada. Em vez de uma fase lexer produzir uma lista fixa de tokens, o analisador solicita preguiçosamente o próximo token de um retorno de chamada lexer [1] . O lexer consumiria o fluxo necessário. Dessa forma, o analisador só pode falhar quando o final real do fluxo é atingido ou quando ocorre um erro de análise real (ou seja, começamos a analisar ainda no lixo).

[1] um lexer acionado por retorno de chamada também é uma boa idéia em outros contextos, porque isso pode evitar alguns problemas com a correspondência de token mais longa .

Se você souber que tipo de documento está pesquisando, poderá otimizar o salto para parar apenas em locais promissores. Por exemplo, um documento JSON sempre começa com o caractere {ou [. Portanto, lixo é qualquer sequência que não contenha esses caracteres.


5
Seu pseudocódigo é realmente o que tenho feito, mas pensei que era apenas um truque feio. O analisador lança dois tipos de exceções ( NO_MATCHe UNDERFLOW) que me permitem distinguir se devo avançar a posição do fluxo ou aguardar mais entradas.
Kevin Krumwiede 16/10

5
@ Kevin: Eu também uso isso, com alguns recursos de segurança, para lidar com os dados recebidos de uma rede em um formato proprietário. Nada hacky sobre isso!
Lightness Races com Monica

5

Não há um nome específico para um analisador que faça isso. Mas vou destacar um algoritmo que faz isso: análise com derivadas .

Consome entrada, um token por vez. Ele produzirá uma floresta de análise no final da entrada. Como alternativa, você também pode obter toda a floresta de análise no meio da análise (uma análise parcial ).

A análise com derivadas lida com gramáticas sem contexto e produzirá uma floresta de análise para gramáticas ambíguas.

É uma teoria elegante, na verdade, mas está apenas na sua infância e não é amplamente utilizada. Matt Might tem uma lista de links para várias implementações no Scala / Racket / etc.

A teoria é mais fácil de aprender se você começar com o reconhecimento com derivadas (ou seja, começar com derivadas de idiomas , com o objetivo de reconhecer alguma entrada para determinar se é válida ou não) e depois alterar o programa para analisar com derivadas ( ou seja, altere-o para que, em vez de obter derivadas de idiomas , use derivadas de analisadores e calcule uma floresta de análise).


4
Downvoter: você poderia explicar o que era digno de um voto negativo? Se houver algo que eu precise corrigir ou melhorar, com certeza seria bom saber.
quer

Eu não sou o votante negativo, e não sonharia em votar sem um comentário. Mas seu artigo entusiasta não faz referência aos muitos analisadores existentes que alcançam o mesmo resultado, em relação à complexidade e à floresta de análise. A programação funcional é ótima, mas comparar um resultado com a literatura existente sobre o assunto também é bom. Quão conveniente é a sua floresta de análise para uso posterior?
babou 16/10

@ababou: para constar, eu não sou o autor desse blog / artigo. Mas sim, concordo que poderia adicionar mais detalhes comparando esse algoritmo com outros e explicá-lo em detalhes. Matt Might tem uma palestra inteira , mas seria bom consolidá-la nesta resposta. Se eu tiver tempo, tentarei expandir essa resposta.
quer

1
Não gaste muito tempo expandindo-o. Até onde eu sei, não é isso que o OP está buscando. Sua pergunta requer uma leitura cuidadosa. O uso dele da floresta de análise não é seu. - - Com relação a derivativos ... parece que deve ser interessante, mas é preciso relacioná-lo com trabalhos anteriores ... e há um corpo significativo disso. Mas não quero dizer nesta resposta, mas nos trabalhos de M Might, ou em seu blog.
babou

2

Longe do ideal, mas já vi isso mais de uma vez: em cada linha de entrada, tente analisar. se falhar, mantenha a linha e adicione a próxima. No pseudocódigo:

buffer = ''
for each line from input:
    buffer = buffer + line
    if can parse buffer:
        emit tree
        buffer = ''

O grande problema é que em alguns idiomas você não pode saber se uma expressão está completa antes de ler a próxima linha. Nesse caso, parece que você pode ler o próximo e verificar se é um começo válido ou uma continuação válida ... Mas, para isso, você precisa da sintaxe exata do idioma

Pior, nessas línguas, não é difícil criar um caso patológico que não possa ser analisado até o final do arquivo, mesmo que não tenha sido uma única declaração longa.


0

Em poucas palavras

Parece que a solução rápida para o seu problema é definir um REGEX, ou um FSA (autômato de estado finito), que reconheça todos os possíveis inícios de documentos (são permitidos falsos positivos, que na verdade não corresponderiam a um documento). Em seguida, você pode executá-lo com muita rapidez para identificar o próximo local onde um documento pode começar com poucos erros. Isso pode causar algumas posições incorretas no início do documento, mas elas serão reconhecidas pelo analisador e abandonadas.

Portanto, o Finite State Automaton pode ser o nome do analisador que você estava procurando. :)

O problema

É sempre difícil entender um problema prático, especialmente quando o vocabulário pode ter muitas interpretações. A palavra floresta de análise foi cunhada (afaik) para análise sem contexto (CF) de sentenças ambíguas que possuem várias árvores de análise. Pode ser generalizado um pouco para analisar uma treliça de frases ou para outros tipos de gramática. Portanto, todas as respostas sobre Earley, GLR, Marpa e analisadores de derivativos (existem muitas outras) que não foram relevantes neste caso.

Mas aparentemente não é isso que você tem em mente. Você deseja analisar uma sequência única que é uma sequência de documentos inequívocos e obter uma árvore de análise para cada um , ou algum tipo de representação estruturada, uma vez que você realmente não diz como a sintaxe de seus documentos é definida, de onde ela se encontra. um ponto de vista formal da linguagem. O que você tem é um algoritmo e tabelas que farão o trabalho de análise quando iniciados no início de um documento. Que assim seja.

O problema real é que seu fluxo de documentos contém um lixo considerável que separa os documentos. E parece que sua dificuldade é verificar esse lixo com rapidez suficiente. Sua técnica atual é começar do início e tentar digitalizar a partir do primeiro caractere, e pular para a reinicialização no próximo caractere sempre que falhar, até você digitalizar todo o documento. Em seguida, repita a indicação do primeiro caractere após a digitalização do documento.

Essa também é a solução sugerida por @amon na segunda parte de sua resposta .

Pode não ser uma solução muito rápida (não tenho como testar), porque é improvável que o código do analisador seja otimizado para ser iniciado com muita eficiência no início de um documento. Em uso normal, isso é feito apenas uma vez, para que não seja um ponto de acesso do ponto de vista da otimização. Portanto, sua felicidade moderada com esta solução não é muito surpreendente.

Então, o que você realmente precisa é de um algoritmo que encontre rapidamente o início de um documento que começa com uma grande quantidade de lixo. E você tem sorte: esses algoritmos existem. E eu tenho certeza que você sabe disso: é chamado de busca por um REGEX.

A solução simples

O que você precisa fazer é analisar a especificação de seus documentos para descobrir como esses documentos começam. Não sei exatamente como, pois não tenho certeza de como suas especificações de sintaxe são organizadas formalmente. Possivelmente todos eles começam com alguma palavra de uma lista finita, possivelmente misturada com alguma pontuação ou números. Isso é para você verificar.

O que você precisa fazer é definir um autômato de estado finito (FSA) ou, para a maioria dos programadores, uma expressão regular (REGEX) capaz de reconhecer os primeiros caracteres de um documento: quanto mais, melhor, mas não precisa ser muito. grande (pois isso pode levar tempo e espaço). Isso deve ser relativamente fácil de fazer a partir da especificação de seus documentos e provavelmente pode ser feito automaticamente com um programa que lê a especificação de seus documentos.

Depois de produzir seu regexp, você pode executá-lo no fluxo de entrada para chegar muito rapidamente ao início do seu primeiro (ou próximo) documento da seguinte maneira:

Eu assumo:
- docstarté um regex que corresponde ao início de todos os documentos
- search(regex, stream)é uma função que pesquisa streamum substring que corresponda regex. Quando ele retorna, o fluxo é reduzido ao seu sub-fluxo de sufixo, começando no início da primeira substring correspondente ou ao fluxo vazio, se nenhuma correspondência for encontrada.
- parse(stream)tenta analisar um documento desde o início do fluxo (o que resta dele) e retorna a árvore de análise em qualquer formato ou falha. Quando ele retorna, o fluxo é reduzido ao seu subfluxo de sufixo, iniciando na posição imediatamente após o final do documento analisado. Chama uma exceção se a análise falhar.

forest = empty_forest
search(docstart, stream)
while stream is not empty:
  try:
    forest = forest + parse(stream)
  except
    remove first character from stream
  search(docstart, stream)

Observe que a remoção do primeiro caractere é necessária para que a próxima pesquisa não encontre novamente a mesma correspondência.

Obviamente, encurtar o fluxo é uma imagem. Pode ser apenas um índice no fluxo.

Uma observação final é que seu regex não precisa ser muito preciso, desde que reconheça todos os inícios. Se, ocasionalmente, reconhecer uma sequência que não possa ser o início de um documento (falso positivo), a única penalidade será o custo de uma chamada inútil para o analisador.

Portanto, isso pode ajudar a simplificar a regex, se útil.

Sobre a possibilidade de uma solução mais rápida

A solução acima deve funcionar muito bem na maioria dos casos. No entanto, se você tiver realmente muito lixo e terabytes de arquivo para processar, pode haver outros algoritmos que são executados mais rapidamente.

A idéia é derivada do algoritmo de busca de string de Boyer-Moore . Esse algoritmo pode pesquisar um fluxo em busca de uma única sequência extremamente rápida, porque usa uma análise estrutural da sequência para pular a leitura da maior parte do fluxo, pulando fragmentos sem sequer olhar para eles. É o algoritmo de pesquisa mais rápida para uma única string.

A dificuldade é que sua adaptação à pesquisa de expressões regulares, em vez de uma única sequência, parece muito delicada e pode não funcionar tão bem, dependendo dos recursos da expressão regular que você está considerando. Por sua vez, isso pode depender da sintaxe dos documentos que você está analisando. Mas não confie muito em mim, pois não tive tempo de fazer uma leitura cuidadosa dos documentos que encontrei.

Estou deixando você com um ou dois indicadores que encontrei na Web, incluindo um que aparentemente é um trabalho de pesquisa de referência , mas você deve considerar isso como mais especulativo, possivelmente pesquisador, a ser considerado apenas se você tiver fortes problemas de desempenho. E provavelmente não existe um programa de prateleira que o faça.


-2

O que você está descrevendo pode ser descrito como SAX vs. SOM.

SAX - (API Simples para XML) é uma API do analisador de acesso seqüencial de eventos desenvolvida pela lista de correspondência XML-DEV para documentos XML.

SOM - (acesso aleatório do modelo de objeto de esquema XML) à representação na memória de um arquivo XML

Existem implementações de ambos os tipos em C # e Java e provavelmente muito mais. Normalmente, um XSD ou DTD é opcional.

A alegria do SAX é que ele possui pouca sobrecarga de memória, o que é ótimo para arquivos XML grandes. O problema é que o acesso aleatório usando o SAX é inexistente ou lento e, pior ainda, o tempo de desenvolvimento é consideravelmente maior do que com o SOM. O problema óbvio com o SOM são os requisitos de RAM potencialmente grandes.

Esta resposta não é aplicável a todas as plataformas e todos os idiomas.


1
Por que você acha que o OP está analisando XML?
Dan Pichelman

1
Isso não responde à pergunta.

@ Boneco de neve Quase nada até agora estava respondendo à pergunta, incluindo a primeira metade da resposta aceita. Não faz sentido escolher alguém. A questão precisa de uma leitura cuidadosa.
babou 16/10

@babou Eu não estava pegando ninguém, estava explicando meu voto negativo.

@Snowman explicando meu voto negativo . Isso é justo, e eu gostaria que mais usuários fizessem isso. Não sou falante nativo: cutucá-lo pode ser uma expressão muito forte. É que todos estão fazendo suposições injustificadas. Portanto, nem vale a pena notar. É verdade que este parece um pouco mais complicado que os outros.
21714
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.