Вопрос

Я работал над синтаксическим анализатором для простого языка шаблонов.Я использую 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 - неподходящий инструмент?Кажется, вам приходится бороться с 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);
}%%

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

  • Тот Самый 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(
)

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top