27 Kasım 2017 Pazartesi

spirit qi Parser Directives

Giriş
Çoğu directive'ler [...] şeklindedir.

qi::as_string
Yakalanan değeri string'e çevirir. Şöyle yaparız.
struct StateStruct { std::string Name; float avalue; };

BOOST_FUSION_ADAPT_STRUCT(StateStruct, Name, avalue)

void print(StateStruct const& ss) { std::cout << "print: " << ss.Name << "\n"; }

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

using skipper_type = qi::space_type;
qi::rule<Iterator, StateStruct(), skipper_type> state;

state %=
( qi::as_string [ qi::lexeme[qi::char_("a-zA-Z") >> +qi::char_("-a-zA-Z0-9_")] ] >>
  qi::float_
)
[ px::bind(&print, qi::_val) ]
;
qi::distinct
Şu satırı dahil ederiz.
#include <boost/spirit/repository/include/qi_distinct.hpp>
Açıklaması şöyle. keyword tanımlamak için kullanılır.
The distinct directive takes the subject parser inside the [] block instead of the(). Inside the () specify the exclusion to disallow at the boundary (most often a character set comprising identifier characters).
Örnek
Şöyle yaparız
auto kw = distinct(qi::char_("a-zA-Z_0-9"));
module_definition    = (kw["module"] >> ident >> '(' >> ident_list >> ')' >> ';');
Örnek
Elimizde şöyle bir gramer olsun. identifier keyword olmayan ve _ veya alpha ile başlayıp ve n tane alnum veya _ ile devam eder. identifier() şeklinde sonlanır.
template <typename It>
struct Grammar : qi::grammar<It> {

  Grammar() : Grammar::base_type(start) {
    using namespace qi;
    auto kw = qr::distinct(copy(enc::alnum | L'_'));

    start         = skip(enc::space) [function_call];
    function_call = identifier >> L'(' >> L')';
    identifier    = !keyword >> raw[(enc::alpha|L'_') >> *(enc::alnum|L'_')];

    keyword       = kw[ no_case[keywords] ];
    BOOST_SPIRIT_DEBUG_NODES((start)(function_call)(identifier)(keyword));
  }
private:
  qi::rule<It> start;
  qi::rule<It, enc::space_type> function_call;

  // implicit lexemes
  struct keywords_t : qi::symbols<wchar_t> {
    keywords_t() { 
      this->add
        (L"as")(L"class")(L"dim")(L"else")(L"end")(L"false")
        (L"for")(L"function")(L"if")(L"new")(L"next")(L"sub")
        (L"then")(L"to")(L"true");
      }
  } keywords;
  qi::rule<It, std::string()> identifier, keyword;
};

qi::hold
boost::optional parse etmek isteyelim
typedef std::pair<boost::optional<std::string>, std::string> pair_type;
typedef std::vector<pair_type> pairs_type;
Şöyle yaparız.
query =  pair % ' ';
pair  = -qi::hold[field >> ':'] >> value;
field = +qi::char_("a-zA-Z0-9");
value = +qi::char_("a-zA-Z0-9+-\\.");

qi::rule<Iterator, pairs_type()> query;
qi::rule<Iterator, pair_type()> pair;
qi::rule<Iterator, std::string()> field, value;
qi::lexeme
Açıklaması şöyle. İlk eşleşmeden sonra whitespace'in atlanmamasını sağlar.
Keep in mind that lexeme[] pre-skips spaces. If this is not desired, use the no_skip directive instead.
Açıklaması şöyle
That means that lexeme will apply skipper before the first match by the enclosed parser. no_skip doesn't do that. Imagine you parse " abc " . Using +char_ and space skipper, result is "abc". WIth lexeme[+char_] it's "abc ". With no_skip[+char_] it's " abc "
Kullanımı şöyledir
qi::lexeme[...]
Özellikle anahtar kelimelerde işe yarar. lexeme kullanmazsak şu kelimeleri yanlışlıkla parse edebiliriz.
Örnek
Şöyle yaparız.
SE L ECT ...

SELECTa ...
Ama aslında şunu istiyoruzdur
SELECT ...
Dolayısıyla lexeme ile skipper'ın beraber kullanılması anlamsız.
template <typename Iterator>
struct Grammar: qi::grammar<Iterator, std::vector<std::string>, ascii::space_type>
{
  Grammar(): Grammar::base_type(ident_list)
  {
    ident = qi::lexeme[*qi::alnum];
    ident_list = *ident;
  }
  ...
  qi::rule<Iterator, std::string, ascii::space_type> ident;
};
Örnek
Şöyle yaparız.
token = qi::raw[ qi::lexeme[+(qi::alnum | '-')] ];
Örnek
Şöyle yaparız.
std::string const input { "My list of ident"};
auto f = input.begin(), l = input.end();

std::list<std::string> result;
qi::phrase_parse(f, l, *qi::lexeme[+qi::graph], qi::space, result);
Örnek
Şöyle yaparız.
quoted_string %= lexeme['"' > +(char_ - '"') > '"'];
Örnek
Şöyle yaparız.
template <typename IT>
struct cppIdentifier : qi::grammar<IT, std::string(), qi::space_type>
{
  cppIdentifier() : cppIdentifier::base_type(start)
  {
    start = qi::lexeme[char_("a-zA-Z_") >> *(char_("a-zA-Z0-9_"))];
  }

  qi::rule<IT, std::string(), qi::space_type> start;
};
Örnek
Şöyle yaparız.
qi::lexeme[":[" >> +ascii::char_("0-9a-fA-F-")]
qi::nocase
Şöyle yaparız
qi::no_case [ 
     qi::lit("my_cmd1") | qi::lit("my_cmd2") 
];
Şöyle yaparız.
qi::no_case [ -qi::int_ >> qi::lit("no") | "bad" ]
qi::noskip
Örnek ver

qi::omit
Normalde out parametresi veren bir parser'ın vermemesini sağlar.

Örnek
Arka arkaya gelen ayraç ile ayrılmış listeyi parse etmek için % kullanılır.
a % b
ile şu aynıdır.
a >> *(omit[b] >> a)
Örnek
Şöyle yaparız. Böylece sadece qi::space out parametresi yakalamaz.
-(qi::omit[+qi::space]
  >> qi::lit("C:")
  >> qi::int_
)
Örnek
Elimizde şöyle bir json olsun. productionYear değerini almak isteyelim.
auto data = {
  "cars" : [
    {
      "name" : "BMW",
      "engine" : 3.0
    },
    {
      "name" : "Citroen",
      "engine" : 3.6
    },
    {
      "name" : "Ferrari",
      "engine" : 4.2
    }
  ],
  "productionYear" : 1999
}
Şöyle yaparız.
boost::spirit::qi::rule<std::string::const_iterator, int()> production_;
production_ = qi::omit[+(qi::char_ - "productionYear") >> "productionYear\"" 
  >> ' ' >> ':' >> ' '] >> qi::int_;
Çıktı olarak şunu alırız
1999
Örnek
Şöyle yaparız. include kuralına kadar tüm boşluğun atlanmasını sağlar.
*(omit[*(char_ - include_)] >> include_)
Örnek
Şöyle yaparız.
template <typename It>
struct FileFormat : qi::grammar<It, FormatData()> {
  FileFormat() : FileFormat::base_type(start) {
    using namespace qi;

    signature  = string("SIGN");     // 4 byte signature, just for example
    header     = repeat(16) [byte_]; // 16 byte header, same

    payload   %= omit[little_word[_len=_1]] >> repeat(_len) [byte_];
    start      = signature >> header >> payload;

     //BOOST_SPIRIT_DEBUG_NODES((start)(signature)(header)(payload))
  }
private:
  qi::rule<It, FormatData()> start;
  qi::rule<It, std::string()> signature, header;

  qi::_a_type _len;
  qi::rule<It, std::string(), qi::locals<uint16_t> > payload;
};

qi::raw
Anlamadım

qi::seeek
Eşleşme oluncaya kadar girdi üzerinde yürür. Şöyle yaparız.
namespace repo = boost::spirit::repository;
namespace ascii = boost::spirit::ascii;

std::ifstream in("C:/log.txt", std::ios_base::in);
in >> std::noskipws;//No white space skipping

boost::spirit::istream_iterator first(in);
boost::spirit::istream_iterator last;

bool result = qi::phrase_parse(first, last, 
  *repo::seek[qi::eol
    >> +ascii::char_("0-9") 
    >> ":["
    ...
    >> qi::eol],
  ascii::blank,
  messages);
qi::skip
Belirtilen karaketerin atlanmasını sağlar. [] içinde gramer verilir.
Örnek
qi::lexeme'in tersine whitespace'in atlanmasını sağlar. Şöyle yaparız
qi::rule<It, qi::space_type> b = "stuff" >> '{' >>
  qi::skip(qi::blank) [ int_ >> int_] >> '}';
Örnek
Şöyle yaparız.
struct Record {
  int id;
  std::string full_title;
  int64_t some;
  int64_t data;
};

BOOST_FUSION_ADAPT_STRUCT(Record, id, full_title, some, data)

namespace qi = boost::spirit::qi;


using It = std::string::const_iterator;
qi::rule<It, std::string()> quoted= '"' >> *('\\' >> qi::char_ | ~qi::char_('"')) >> '"';
qi::rule<It, Record()> parser= qi::skip(',') [qi::int_ >> quoted >> qi::int_ >> qi::int_];

std::string const line1(R"(1500,"Rev, H., Tintin, K.H. Ken",204400,350)");

Record record;
if (parse(line1.begin(), line1.end(), parser, record))
{
  std::cout << "Parsed: \n";
  std::cout << "  record.id = " << record.id << "\n";
  std::cout << "  record.full_title = " << record.full_title << "\n";
  std::cout << "  record.some = " << record.some << "\n";
  std::cout << "  record.data = " << record.data << "\n";
}
Çıktı olarak şunu alırız.
Parsed: 
  record.id = 1500
  record.full_title = Rev, H., Tintin, K.H. Ken
  record.some = 204400
  record.data = 350