Строковая интерполяция в 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 только для простых сканеров, поэтому не могу вам в этом помочь.

Другие вопросы по тегам