Pergunta

Tenho trabalhado em um analisador para linguagem de modelo simples.Estou usando Ragel.

Os requisitos são modestos.Estou tentando encontrar [[tags]] que possam ser incorporadas em qualquer lugar da string de entrada.

Estou tentando analisar uma linguagem de modelo simples, algo que pode ter tags como {{foo}} incorporadas ao HTML.Tentei várias abordagens para analisar isso, mas tive que recorrer ao uso de um scanner Ragel e usar a abordagem ineficiente de combinar apenas um único caractere como "pega tudo".Eu sinto que esta é a maneira errada de fazer isso.Basicamente, estou abusando do preconceito de correspondência mais longa do scanner para implementar minha regra padrão (ela pode ter apenas 1 caractere, portanto deve ser sempre o último recurso).

%%{

  machine parser;

  action start      { tokstart = p; }          
  action on_tag     { results << [:tag, data[tokstart..p]] }            
  action on_static  { results << [:static, data[p..p]] }            

  tag  = ('[[' lower+ ']]') >start @on_tag;

  main := |*
    tag;
    any      => on_static;
  *|;

}%%

(ações escritas em Ruby, mas devem ser fáceis de entender).

Como você escreveria um analisador para uma linguagem tão simples?Ragel talvez não seja a ferramenta certa?Parece que você tem que lutar contra Ragel com unhas e dentes se a sintaxe for imprevisível como esta.

Foi útil?

Solução

Ragel funciona bem.Você só precisa ter cuidado com o que está combinando.Sua pergunta usa ambos [[tag]] e {{tag}}, mas seu exemplo usa [[tag]], então acho que é isso que você está tentando tratar como especial.

O que você quer fazer é comer o texto até atingir um colchete aberto.Se esse colchete for seguido por outro colchete, então é hora de começar a comer caracteres minúsculos até chegar a um colchete fechado.Como o texto na tag não pode incluir nenhum colchete, você sabe que o único caractere sem erro que pode seguir esse colchete é outro colchete.Nesse ponto, você está de volta ao ponto de partida.

Bem, essa é uma descrição literal desta máquina:

tag = '[[' lower+ ']]';

main := (
  (any - '[')*  # eat text
  ('[' ^'[' | tag)  # try to eat a tag
)*;

A parte complicada é: onde você chama suas ações?Não pretendo ter a melhor resposta para isso, mas aqui está o que descobri:

static char *text_start;

%%{
  machine parser;

  action MarkStart { text_start = fpc; }
  action PrintTextNode {
    int text_len = fpc - text_start;
    if (text_len > 0) {
      printf("TEXT(%.*s)\n", text_len, text_start);
    }
  }
  action PrintTagNode {
    int text_len = fpc - text_start - 1;  /* drop closing bracket */
    printf("TAG(%.*s)\n", text_len, text_start);
  }

  tag = '[[' (lower+ >MarkStart) ']]' @PrintTagNode;

  main := (
    (any - '[')* >MarkStart %PrintTextNode
    ('[' ^'[' %PrintTextNode | tag) >MarkStart
  )* @eof(PrintTextNode);
}%%

Existem algumas coisas não óbvias:

  • O eof é necessária acção porque %PrintTextNode só é invocado ao sair de uma máquina.Se a entrada terminar com texto normal, não haverá entrada para fazê-la sair desse estado.Como também será chamado quando a entrada terminar com uma tag e não houver nenhum nó de texto final não impresso, PrintTextNode testa se tem algum texto para imprimir.
  • O %PrintTextNode ação aninhada após o ^'[' é necessário porque, embora tenhamos marcado o início quando atingimos o [, depois de atingirmos um não-[, começaremos a tentar analisar qualquer coisa novamente e observaremos o ponto inicial.Precisamos liberar esses dois caracteres antes que isso aconteça, daí a invocação da ação.

O analisador completo segue.Eu fiz isso em C porque é isso que eu sei, mas você deve ser capaz de transformá-lo em qualquer idioma que precisar facilmente:

/* ragel so_tag.rl && gcc so_tag.c -o so_tag */
#include <stdio.h>
#include <string.h>

static char *text_start;

%%{
  machine parser;

  action MarkStart { text_start = fpc; }
  action PrintTextNode {
    int text_len = fpc - text_start;
    if (text_len > 0) {
      printf("TEXT(%.*s)\n", text_len, text_start);
    }
  }
  action PrintTagNode {
    int text_len = fpc - text_start - 1;  /* drop closing bracket */
    printf("TAG(%.*s)\n", text_len, text_start);
  }

  tag = '[[' (lower+ >MarkStart) ']]' @PrintTagNode;

  main := (
    (any - '[')* >MarkStart %PrintTextNode
    ('[' ^'[' %PrintTextNode | tag) >MarkStart
  )* @eof(PrintTextNode);
}%%

%% write data;

int
main(void) {
  char buffer[4096];
  int cs;
  char *p = NULL;
  char *pe = NULL;
  char *eof = NULL;

  %% write init;

  do {
    size_t nread = fread(buffer, 1, sizeof(buffer), stdin);
    p = buffer;
    pe = p + nread;
    if (nread < sizeof(buffer) && feof(stdin)) eof = pe;

    %% write exec;

    if (eof || cs == %%{ write error; }%%) break;
  } while (1);
  return 0;
}

Aqui estão algumas entradas de teste:

[[header]]
<html>
<head><title>title</title></head>
<body>
<h1>[[headertext]]</h1>
<p>I am feeling very [[emotion]].</p>
<p>I like brackets: [ is cool. ] is cool. [] are cool. But [[tag]] is special.</p>
</body>
</html>
[[footer]]

E aqui está a saída do analisador:

TAG(header)
TEXT(
<html>
<head><title>title</title></head>
<body>
<h1>)
TAG(headertext)
TEXT(</h1>
<p>I am feeling very )
TAG(emotion)
TEXT(.</p>
<p>I like brackets: )
TEXT([ )
TEXT(is cool. ] is cool. )
TEXT([])
TEXT( are cool. But )
TAG(tag)
TEXT( is special.</p>
</body>
</html>
)
TAG(footer)
TEXT(
)

O nó de texto final contém apenas a nova linha no final do arquivo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top