как избавиться от escape-символа в токене с spirit::lex?

Я хочу токенизировать собственное расширение синтаксиса SQL. Это включает в себя распознавание экранированной двойной кавычки внутри строки с двойными кавычками. Например. в MySQL эти две строковые лексемы эквивалентны: """" (вторая двойная кавычка действует как escape-символ) и '"'. Я пробовал разные вещи, но я застрял в том, как заменить значение токена.

#include <boost/spirit/include/lex_lexertl.hpp>
namespace lex = boost::spirit::lex;

template <typename Lexer>
struct sql_tokens : lex::lexer<Lexer>
{
  sql_tokens()
  {
    string_quote_double = "\\\"";    // '"'

    this->self("INITIAL")
      = string_quote_double [ lex::_state = "STRING_DOUBLE" ] // how to also ignore + ctx.more()?
      | ...
      ;

    this->self("STRING_DOUBLE") 
      = lex::token_def<>("[^\\\"]*") // action: ignore + ctx.more()
      | lex::token_def<>("\\\"\\\"") // how to set token value to '"' ?
      | lex::token_def<>("\\\"") [ lex::_state = "INITIAL" ]
      ;
  }

  lex::token_def<> string_quote_double, ...;
};

Итак, как установить значение токена ", когда найдено ""?

Кроме того, у меня также есть следующий вопрос: я могу написать функтор для семантического действия, чтобы вызвать ctx.more() и одновременно игнорировать токен (таким образом, объединяя токены «низкого уровня» в токен строки «высокого уровня»). ). Но как элегантно совместить это с lex::_state = ".." ?


person coproc    schedule 13.05.2013    source источник


Ответы (2)


ОТРЕДАКТИРОВАНО в ответ на комментарий, см. ниже "ОБНОВЛЕНИЕ""


Я предлагаю не пытаться решить это в лексере. Пусть лексер выдает необработанные строки:

template <typename Lexer>
    struct mylexer_t : lex::lexer<Lexer>
{
    mylexer_t()
    {
        string_quote_double = "\\\"([^\"]|\\\"\\\")*\\\"";

        this->self("INITIAL")
            = string_quote_double
            | lex::token_def<>("[ \t\r\n]") [ lex::_pass = lex::pass_flags::pass_ignore ]
            ;
    }

    lex::token_def<std::string> string_quote_double;
};

ПРИМЕЧАНИЕ. Для раскрытия такого атрибута токена требуется измененное определение типа токена:

typedef lex::lexertl::token<char const*, boost::mpl::vector<char, std::string> > token_type;
typedef lex::lexertl::actor_lexer<token_type> lexer_type;

Постпроцесс в парсере:

template <typename Iterator> struct mygrammar_t
    : public qi::grammar<Iterator, std::vector<std::string>()>
{
    typedef mygrammar_t<Iterator> This;

    template <typename TokenDef>
        mygrammar_t(TokenDef const& tok) : mygrammar_t::base_type(start)
    {
        using namespace qi;

        string_quote_double %= tok.string_quote_double [ undoublequote ];
        start = *string_quote_double;

        BOOST_SPIRIT_DEBUG_NODES((start)(string_quote_double));
    }

  private:
    qi::rule<Iterator, std::vector<std::string>()> start;
    qi::rule<Iterator, std::string()> string_quote_double;
};

Как видите, undoubleqoute может быть любым актором Phoenix, удовлетворяющим критериям семантического действия Spirit. Пример реализации с мертвым мозгом будет таким:

static bool undoublequote(std::string& val)
{
    auto outidx = 0;
    for(auto in = val.begin(); in!=val.end(); ++in) {
        switch(*in) {
            case '"': 
                if (++in == val.end()) { // eat the escape
                    // end of input reached
                    val.resize(outidx); // resize to effective chars
                    return true;
                }
                // fall through
            default:
                val[outidx++] = *in; // append the character
        }
    }

    return false; // not ended with double quote as expected
}

Но я предлагаю вам написать «правильный» деэкранер (поскольку я почти уверен, что MySql позволит использовать \t, \r, \u001e или даже более архаичные вещи).

У меня есть еще несколько полных образцов в старых ответах здесь:


ОБНОВИТЬ

На самом деле, как вы указали, довольно легко интегрировать нормализацию значения атрибута в сам лексер:

template <typename Lexer>
    struct mylexer_t : lex::lexer<Lexer>
{
    struct undoublequote_lex_type {
        template <typename, typename, typename, typename> struct result { typedef void type; };

        template <typename It, typename IdType, typename pass_flag, typename Ctx>
            void operator()(It& f, It& l, pass_flag& pass, IdType& id, Ctx& ctx) const {
                std::string raw(f,l);
                if (undoublequote(raw))
                    ctx.set_value(raw);
                else
                    pass = lex::pass_flags::pass_fail;
            }
    } undoublequote_lex;

    mylexer_t()
    {
        string_quote_double = "\\\"([^\"]|\\\"\\\")*\\\"";

        const static undoublequote_lex_type undoublequote_lex;
        this->self("INITIAL")
            = string_quote_double [ undoublequote_lex ]
            | lex::token_def<>("[ \t\r\n]") [ lex::_pass = lex::pass_flags::pass_ignore ]
            ;
    }

    lex::token_def<std::string> string_quote_double;
};

Здесь повторно используется та же функция undoublequote, показанная выше, но она заключена в объект Deferred Callable Object (или «полиморфный функтор») undoublequote_lex_type, который удовлетворяет критерии семантического действия Lexer.


Вот полностью работающее доказательство концепции:

//#include <boost/config/warning_disable.hpp>
//#define BOOST_SPIRIT_DEBUG_PRINT_SOME 80
//#define BOOST_SPIRIT_DEBUG // before including Spirit
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/qi.hpp>
#include <fstream>
#ifdef MEMORY_MAPPED
#   include <boost/iostreams/device/mapped_file.hpp>
#endif
//#include <boost/spirit/include/lex_generate_static_lexertl.hpp>

namespace /*anon*/
{
    namespace phx=boost::phoenix;
    namespace qi =boost::spirit::qi;
    namespace lex=boost::spirit::lex;

    template <typename Lexer>
        struct mylexer_t : lex::lexer<Lexer>
    {
        mylexer_t()
        {
            string_quote_double = "\\\"([^\"]|\\\"\\\")*\\\"";

            this->self("INITIAL")
                = string_quote_double
                | lex::token_def<>("[ \t\r\n]") [ lex::_pass = lex::pass_flags::pass_ignore ]
                ;
        }

        lex::token_def<std::string> string_quote_double;
    };

    static bool undoublequote(std::string& val)
    {
        auto outidx = 0;
        for(auto in = val.begin(); in!=val.end(); ++in) {
            switch(*in) {
                case '"': 
                    if (++in == val.end()) { // eat the escape
                        // end of input reached
                        val.resize(outidx); // resize to effective chars
                        return true;
                    }
                    // fall through
                default:
                    val[outidx++] = *in; // append the character
            }
        }

        return false; // not ended with double quote as expected
    }

    template <typename Iterator> struct mygrammar_t
        : public qi::grammar<Iterator, std::vector<std::string>()>
    {
        typedef mygrammar_t<Iterator> This;

        template <typename TokenDef>
            mygrammar_t(TokenDef const& tok) : mygrammar_t::base_type(start)
        {
            using namespace qi;

            string_quote_double %= tok.string_quote_double [ undoublequote ];
            start = *string_quote_double;

            BOOST_SPIRIT_DEBUG_NODES((start)(string_quote_double));
        }

      private:
        qi::rule<Iterator, std::vector<std::string>()> start;
        qi::rule<Iterator, std::string()> string_quote_double;
    };
}

std::vector<std::string> do_test_parse(const std::string& v)
{
    char const *first = &v[0];
    char const *last = first+v.size();

    typedef lex::lexertl::token<char const*, boost::mpl::vector<char, std::string> > token_type;
    typedef lex::lexertl::actor_lexer<token_type> lexer_type;

    typedef mylexer_t<lexer_type>::iterator_type iterator_type;
    const static mylexer_t<lexer_type> mylexer;
    const static mygrammar_t<iterator_type> parser(mylexer);

    auto iter = mylexer.begin(first, last);
    auto end = mylexer.end();

    std::vector<std::string> data;
    bool r = qi::parse(iter, end, parser, data);

    r = r && (iter == end);

    if (!r)
        std::cerr << "parsing (" << iter->state() << ") failed at: '" << std::string(first, last) << "'\n";

    return data;
}

int main(int argc, const char *argv[])
{
    for (auto&& s : do_test_parse( "\"bla\"\"blo\""))
        std::cout << s << std::endl;
}
person sehe    schedule 13.05.2013
comment
Мне нравится идея, как сопоставить такую ​​строку с экранированием одним регулярным выражением - на самом деле я считал это невозможным. Тем не менее я стремился/надеялся на более простое решение, т.е. не связанное с грамматикой. Это действительно необходимо? Может быть, просто для облегчения тестирования/отладки? - person coproc; 13.05.2013
comment
@coproc Конечно, можно :/ Я просто не предлагал этого. Я добавил функтор-оболочку undouble_quote_lex, который показывает, как это сделать в чистом lex (см. ОБНОВЛЕНИЕ). похожий адаптированный образец программы по-прежнему печатает bla"blo, как и ожидалось. - person sehe; 14.05.2013
comment
Я все еще пережевываю тип токена. Каково назначение типа char в mpl::vector для AttributeTypes? Разве не подойдет тип std::string? На самом деле я не понимаю, почему в определении типа токена может быть несколько типов атрибутов и как их можно использовать. - person coproc; 14.05.2013
comment
@coproc О, вы, очевидно, можете отказаться от символа. У меня он был там, потому что я изначально пытался работать с вашими оригинальными определениями токенов. Значение токена представляет собой вариант по сравнению с перечисленными типами. Все это задокументировано здесь: boost.org/doc/libs/1_53_0/libs/spirit/doc/html/spirit/lex/ - person sehe; 14.05.2013

Предлагаю решить эту и подобные задачи в лексере, а не заставлять лексер возвращать что-то промежуточное, а потом парсить его дополнительным кодом. Двойные кавычки могут быть не единственной сложностью внутри строк, могут быть и другие escape-последовательности, и лучше иметь четкое описание процесса разбора строки в одном месте и заставить лексера делать ВСЕ работу.

Вот решение вопроса в теме с использованием только лексера:

using namespace boost::spirit;
namespace px = boost::phoenix;

template <typename Lexer>
struct sql_tokens : public lex::lexer<Lexer>
{
  sql_tokens()
  {
    string = '"';

    this->self +=
      lex::token_def<>('"')
      [
        lex::_state = "STRING",
        lex::_pass = lex::pass_flags::pass_ignore,
        px::ref(curString) = std::string()
      ];

    std::string& (std::string::*append)(std::string::iterator,
                                        std::string::iterator)
    { &std::string::append<std::string::iterator> };

    this->self("STRING") =
      lex::token_def<>("[^\"]*")
      [
        lex::_pass = lex::pass_flags::pass_ignore,
        px::bind(append, curString, lex::_start, lex::_end)
      ] |
      lex::token_def<>("\\\"\\\"")
      [
        lex::_pass = lex::pass_flags::pass_ignore,
        px::ref(curString) += px::val("\"")
      ] |
      string
      [
        lex::_val = px::ref(curString),
        lex::_state = "INITIAL"
      ];

    this->self("WS") = lex::token_def<>("[ \\t\\n]+");
  }

  std::string curString;
  lex::token_def<std::string> string;
};
person facetus    schedule 09.01.2014