Создание парсера boost :: spirit :: x3 для строк в кавычках с обработкой escape-последовательностей - PullRequest
2 голосов
/ 09 мая 2020

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

x3::lexeme[quote > *(x3::char_ - quote) > quote]

, где quote - это просто постоянное выражение для '"'. Он не обрабатывает управляющую последовательность. Я знаю про boost::spirit::classic::lex_escape_ch_p, но понятия не имею, как использовать это с инструментами boost::spirit::x3 (или вообще). Как я мог создать парсер, который бы это делал? Синтаксическому анализатору придется распознавать большинство escape-последовательностей, таких как обычные, такие как '\n', '\t', и более сложные вещи, такие как escape-последовательности hex, oct и ansi.

Мои извинения, если что-то не так с этот пост, это моя первая публикация на SO.

EDIT:

Вот как я реализовал парсер:

x3::lexeme[quote > *(
    ("\\\"" >> &x3::char_) >> x3::attr(quote) | ~x3::char_(quote)
    ) > quote]
[handle_escape_sequences];

где handle_escape_sequences - это lambda:

auto handle_escape_sequences = [&](auto&& context) -> void {
    std::string& str = x3::_val(context);

    uint32_t i{};

    static auto replace = [&](const char replacement) -> void {
        str[i++] = replacement;
    };

    if (!classic::parse(std::begin(str), std::end(str), *classic::lex_escape_ch_p[replace]).full)
        throw Error{ "invalid literal" }; // invalid escape sequence most likely

    str.resize(i);
};

Он выполняет полный синтаксический анализ управляющей последовательности ANSI, что означает, что вы можете использовать его для выполнения всех видов причудливых манипуляций с терминалом, таких как установка цвета текста, положения курсора и т. д. c. с ним.

Вот полное определение правила, а также все, от чего оно зависит (я просто выбрал все, что с ним связано, из своего кода, поэтому результат выглядит как правильное спагетти) на случай кому-то это понадобится:

#include <boost\spirit\home\x3.hpp>
#include <boost\spirit\include\classic_utility.hpp>

using namespace boost::spirit;

#define RULE_DECLARATION(rule_name, attribute_type)                            \
inline namespace Tag { class rule_name ## _tag; }                              \
x3::rule<Tag::rule_name ## _tag, attribute_type, true> rule_name = #rule_name; \

#define SIMPLE_RULE_DEFINITION(rule_name, attribute_type, definition) \
RULE_DECLARATION(rule_name, attribute_type)                           \
auto rule_name ## _def = definition;                                  \
BOOST_SPIRIT_DEFINE(rule_name);

constexpr char quote = '"';


template <class Base, class>
struct Access_base_s : Base {
    using Base::Base, Base::operator=;
};

template <class Base, class Tag>
using Unique_alias_for = Access_base_s<Base, Tag>;


using String_literal = Unique_alias_for<std::string, class String_literal_tag>;

SIMPLE_RULE_DEFINITION(string_literal, String_literal,
    x3::lexeme[quote > *(
        ("\\\"" >> &x3::char_) >> x3::attr(quote) | ~x3::char_(quote)
        ) > quote]
    [handle_escape_sequences];
);

1 Ответ

1 голос
/ 09 мая 2020

У меня есть много примеров этого на этом сайте¹

Позвольте начать с упрощения вашего выражения (~charset, вероятно, более эффективно, чем charset - exceptions):

x3::lexeme['"' > *~x3::char_('"')) > '"']

Теперь, чтобы разрешить экранирование, мы можем декодировать их adho c:

auto qstring = x3::lexeme['"' > *(
         "\\n" >> x3::attr('\n')
       | "\\b" >> x3::attr('\b')
       | "\\f" >> x3::attr('\f')
       | "\\t" >> x3::attr('\t')
       | "\\v" >> x3::attr('\v')
       | "\\0" >> x3::attr('\0')
       | "\\r" >> x3::attr('\r')
       | "\\n" >> x3::attr('\n')
       | "\\"  >> x3::char_("\"\\")
       | ~x3::char_('"')
   ) > '"'];

В качестве альтернативы вы можете использовать подход символов, включая или исключая sla sh:

x3::symbols<char> escapes;
escapes.add
    ( "\\n", '\n')
    ( "\\b", '\b')
    ( "\\f", '\f')
    ( "\\t", '\t')
    ( "\\v", '\v')
    ( "\\0", '\0')
    ( "\\r", '\r')
    ( "\\n", '\n')
    ( "\\\\", '\\')
    ( "\\\"", '"');

auto qstring = x3::lexeme['"' > *(escapes | ~x3::char_('"')) > '"'];

См. it Live On Coliru тоже.

Думаю, я предпочитаю скрученные вручную ветки, потому что они дают вам гибкость, например, для его / восьмеричного побега (обратите внимание на конфликт с \0, хотя):

       | "\\" >> x3::int_parser<char, 8, 1, 3>()
       | "\\x" >> x3::int_parser<char, 16, 2, 2>()

Что также отлично работает:

Live On Coliru

#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <iomanip>

int main() {
    namespace x3 = boost::spirit::x3;

    auto qstring = x3::lexeme['"' > *(
             "\\n" >> x3::attr('\n')
           | "\\b" >> x3::attr('\b')
           | "\\f" >> x3::attr('\f')
           | "\\t" >> x3::attr('\t')
           | "\\v" >> x3::attr('\v')
           | "\\r" >> x3::attr('\r')
           | "\\n" >> x3::attr('\n')
           | "\\"  >> x3::char_("\"\\")
           | "\\" >> x3::int_parser<char, 8, 1, 3>()
           | "\\x" >> x3::int_parser<char, 16, 2, 2>()
           | ~x3::char_('"')
       ) > '"'];

    for (std::string const input : { R"("\ttest\x41\x42\x43 \x031\x032\x033 \"hello\"\r\n")" }) {
        std::string output;
        auto f = begin(input), l = end(input);
        if (x3::phrase_parse(f, l, qstring, x3::blank, output)) {
            std::cout << "[" << output << "]\n";
        } else {
            std::cout << "Failed\n";
        }
        if (f != l) {
            std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << "\n";
        }
    }
}

Отпечатки

[   testABC 123 "hello"
]

¹ Посмотрите на эти

...