Как разобрать языки шаблонов в Ragel? - PullRequest
9 голосов
/ 26 июля 2010

Я работал над парсером для простого языка шаблонов.Я использую Ragel.

Требования скромные.Я пытаюсь найти [[теги]], которые могут быть встроены в любом месте входной строки.

Я пытаюсь проанализировать простой язык шаблонов, который может содержать теги, такие как {{foo}}, встроенные в HTML.Я попробовал несколько подходов, чтобы разобрать это, но мне пришлось прибегнуть к использованию сканера Ragel и использовать неэффективный подход, состоящий в том, чтобы сопоставить только один символ как «поймать все».Я чувствую, что это неправильный путь.По сути, я использую смещение сканера с самым длинным соответствием для реализации моего правила по умолчанию (оно может быть длиной всего 1 символ, поэтому оно всегда должно быть последним средством).

%%{

  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;
  *|;

}%%

(действия написаны на ruby, но должны быть просты для понимания).

Как бы вы написали парсер для такого простого языка?Возможно, Ragel не тот инструмент?Кажется, вам нужно бороться с зубами и ногтями Рагеля, если синтаксис непредсказуем, как этот.

1 Ответ

20 голосов
/ 24 ноября 2010

Ragel работает отлично.Вам просто нужно быть осторожным с тем, что вы подходите.В вашем вопросе используются [[tag]] и {{tag}}, но в вашем примере используется [[tag]], поэтому я считаю, что вы пытаетесь считать его особенным.

То, что вы хотите сделать, это есть текст, пока выударил по открытой скобке.Если за этой скобкой следует другая скобка, то пора начинать есть строчные буквы, пока вы не достигнете закрывающей скобки.Поскольку текст в теге не может включать в себя какие-либо скобки, вы знаете, что единственный символ без ошибок, который может следовать за этой закрывающей скобкой, является другой закрывающей скобкой.В этот момент вы вернулись к тому, с чего начали.

Ну, дословное описание этой машины:

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

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

Сложный вопрос: как вы называете свои действия?Я не претендую на лучший ответ на этот вопрос, но вот что я придумал:

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);
}%%

Есть несколько неочевидных вещей:

  • The eof действие необходимо, потому что %PrintTextNode вызывается только при выходе из машины.Если ввод заканчивается обычным текстом, не будет ввода, чтобы заставить его выйти из этого состояния.Поскольку он также будет вызываться, когда ввод заканчивается тегом, и нет конечного ненапечатанного текстового узла, PrintTextNode проверяет наличие текста для печати. ​​
  • Действие %PrintTextNode, расположенное после^'[' необходим, потому что, хотя мы отмечали начало, когда нажимали [, после того, как мы нажали не [, мы начнем пытаться снова что-то проанализировать и отметить начальную точку.Нам нужно очистить эти два символа до того, как это произойдет, отсюда и вызов действия.

Далее следует полный анализатор.Я сделал это на C, потому что это то, что я знаю, но вы сможете легко превратить его в любой язык, который вам нужен:

/* 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;
}

Вот несколько тестовых вводов:

[[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]]

А вот вывод парсера:

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(
)

Последний текстовый узел содержит только символ новой строки в конце файла.

...