Строковая интерполяция в Ragel
Я пытаюсь реализовать язык, и я использую Ragel в качестве лексера (и Bison в качестве парсера). Я хотел бы иметь возможность поддерживать интерполяцию строк в моем языке, но я не уверен, как это сделать.
Мой лексер использует сканер, как показано ниже, для основной части языка:
sstring = "'" ( ( any -- "'" ) | ( '\\' any ) )* "'";
# dstring = ?;
main := |*
comment => {};
'(' => { ADD_TOKEN(LPAREN); };
')' => { ADD_TOKEN(RPAREN); };
# ...
sstring => { ADD_TOKEN2(STRING); };
# dstring => ?;
*|;
Что мне нужно сделать, чтобы иметь возможность обрабатывать интерполяцию строк, как "hello #{world}"
?
2 ответа
Внутреннее содержание фигурных скобок может быть полным выражением. Интерполированная строка будет преобразована в операцию конкатенации. Это слишком много для лексера. Вам нужна сила парсера. Поэтому распознайте три разных вида токенов в каждой интерполированной строке:
istring_start = "'" ( ( any -- "'" ) | ( '\\' any ) )* "#{";
istring_middle = "}" ( ( any -- "'" ) | ( '\\' any ) )* "#{";
istring_end = "}" ( ( any -- "'" ) | ( '\\' any ) )* "'";
В парсере у вас будут такие правила:
istring : istring_prefix expr ISTRING_END
;
istring_prefix : ISTRING_START
| istring_prefix expr ISTRING_MIDDLE
;
Эти правила создают синтаксическое дерево или компилируют байт-код или все, что вы намереваетесь использовать в качестве кода для операции конкатенации строк.
Я бы сделал это с помощью действия, связанного с завершением строкового токена. В этом действии вы могли бы затем перебрать ts для te и добавить логику для вашей строковой интерполяции и вместо этого выдать токены в действии.
Нечто подобное, которое, вероятно, охватывает только самые основные формы, может помочь вам начать:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
enum token { STR, STR_I };
void emit(enum token tok, const char* start, const char* end) {
if (start == end)
return;
switch(tok) {
case STR:
printf("STR(\"%.*s\") ", (int)(end - start), start); break;
case STR_I:
printf("STR_I(%.*s) ", (int)(end - start), start); break;
}
}
%%{
machine interpolation;
write data;
action interpolate {
// this is the data input without ""
const char* data_start = ts + 1;
const char* data_end = te - 1;
// Use this to walk through the token to find interpolation points
const char *tok_start = data_start;
const char *tok_end = data_start;
for (;tok_end <= data_end; tok_end++) {
// Does it contain #{ ?
if (strncmp(tok_end,"#{", 2) == 0) {
emit(STR, tok_start, tok_end);
tok_start = tok_end + 2;
// fast-forward to } or end, whichever comes first
while (tok_end < data_end && *tok_end != '}') {
++tok_end;
}
if (tok_end == data_end) {
// we're at the end
emit(STR, tok_start - 2, data_end);
tok_start = tok_end;
break;
} else {
// found matching }
emit(STR_I, tok_start, tok_end);
tok_start = tok_end + 1;
}
}
}
if (tok_start != data_end) {
emit(STR, tok_start, data_end);
}
}
not_dquote_or_escape = [^"\\];
escaped_something = /\\./;
string_constant = '"' ( not_dquote_or_escape | escaped_something )* '"';
main := |*
string_constant => interpolate;
*|;
}%%
int main(int argc, char **argv) {
//char text[] = "\"hello #{first_name} #{last_name}, how are you?\"";
//char text[] = "\"#{first_name} is my name.\"";
//char text[] = "\"#{first_name}#{last_name}\"";
//char text[] = "\"#{ without finishing brace.\"";
//char text[] = "\" no starting brace }.\"";
char *p = &text[0];
char *pe = (text + strlen(text));
char *eof = pe;
int act, cs;
const char* ts;
const char* te;
%% write init;
%% write exec;
return 0;
}
Я почти уверен, что вы можете сделать это, перепрыгивая в разные состояния с помощью fgoto и тому подобного, но я когда-либо использовал Ragel только для простых сканеров, поэтому не могу вам в этом помочь.