Ragel에서 템플릿 언어를 구문 분석하는 방법은 무엇입니까?
문제
저는 간단한 템플릿 언어에 대한 파서 작업을 해왔습니다.저는 라겔을 사용하고 있습니다.
요구 사항은 적당합니다.입력 문자열의 어느 곳에나 삽입할 수 있는 [[tags]]를 찾으려고 합니다.
저는 HTML에 {{foo}}와 같은 태그를 포함할 수 있는 간단한 템플릿 언어를 구문 분석하려고 합니다.나는 이것을 구문 분석하기 위해 여러 가지 접근 방식을 시도했지만 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;
*|;
}%%
(액션은 루비로 작성되었지만 이해하기 쉬워야 합니다).
이렇게 간단한 언어에 대한 파서를 작성하려면 어떻게 하시겠습니까?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(
)
최종 텍스트 노드에는 파일 끝에 줄 바꿈만 포함됩니다.