boost :: spirit access итератор позиции из семантических действий

Допустим, у меня есть такой код (номера строк для справки):

1:
2:function FuncName_1 {
3:    var Var_1 = 3;
4:    var  Var_2 = 4;
5:    ...

Я хочу написать грамматику, которая анализирует такой текст, помещает все идентификаторы (имена функций и переменных) в дерево (utree?).
Каждый узел должен сохранять: line_num, column_num и значение символа. пример:

root: FuncName_1 (line:2,col:10)
children[0]: Var_1 (line:3, col:8)
children[1]: Var_1 (line:4, col:9)

Я хочу поместить его в дерево, потому что я планирую пройти через это дерево, и для каждого узла я должен знать «контекст»: (все родительские узлы текущих узлов).

Например, при обработке узла с помощью Var_1 я должен знать, что это имя локальной переменной для функции FuncName_1 (которая в настоящее время обрабатывается как узел, но на один уровень раньше)

Я не могу понять несколько вещей

  1. Может ли это быть сделано в Духе с помощью семантических действий и утри? Или я должен использовать вариант<> деревья?
  2. Как передать узлу эти три информации (столбец, строка, символ_имя) одновременно? Я знаю, что должен использовать pos_iterator как тип итератора для грамматики, но как получить доступ к этой информации в семантическом действии?

Я новичок в Boost, поэтому я перечитываю документацию Spirit снова и снова, я пытаюсь погуглить свои проблемы, но почему-то не могу собрать все части вместе, чтобы найти решение. Похоже, что раньше никого не было с таким вариантом использования, как у меня (или я просто не могу его найти)
Похоже, единственными решениями с итератором позиции являются решения с обработкой парсинга ошибок, но это не тот случай, который меня интересует.
Код, который только анализирует код, который я использовал, приведен ниже, но я не знаю, как двигаться дальше.

  #include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/support_line_pos_iterator.hpp>

namespace qi = boost::spirit::qi;
typedef boost::spirit::line_pos_iterator<std::string::const_iterator> pos_iterator_t;

template<typename Iterator=pos_iterator_t, typename Skipper=qi::space_type>
struct ParseGrammar: public qi::grammar<Iterator, Skipper>
{
ParseGrammar():ParseGrammar::base_type(SourceCode)
{
using namespace qi;
KeywordFunction = lit("function");
KeywordVar    = lit("var");
SemiColon     = lit(';');

Identifier = lexeme [alpha >> *(alnum | '_')];
VarAssignemnt = KeywordVar >> Identifier >> char_('=') >> int_ >> SemiColon;
SourceCode = KeywordFunction >> Identifier >> '{' >> *VarAssignemnt >> '}';
}

qi::rule<Iterator, Skipper> SourceCode;
qi::rule<Iterator > KeywordFunction;
qi::rule<Iterator,  Skipper> VarAssignemnt;
qi::rule<Iterator> KeywordVar;
qi::rule<Iterator> SemiColon;
qi::rule<Iterator > Identifier;
};

int main()
{
std::string const content = "function FuncName_1 {\n var Var_1 = 3;\n var  Var_2 = 4; }";

pos_iterator_t first(content.begin()), iter = first, last(content.end());
ParseGrammar<pos_iterator_t> resolver;    //  Our parser
bool ok = phrase_parse(iter,
last,
resolver,
qi::space);

std::cout << std::boolalpha;
std::cout << "\nok : " << ok << std::endl;
std::cout << "full   : " << (iter == last) << std::endl;
if(ok && iter == last)
{
std::cout << "OK: Parsing fully succeeded\n\n";
}
else
{
int line   = get_line(iter);
int column = get_column(first, iter);
std::cout << "-------------------------\n";
std::cout << "ERROR: Parsing failed or not complete\n";
std::cout << "stopped at: " << line  << ":" << column << "\n";
std::cout << "remaining: '" << std::string(iter, last) << "'\n";
std::cout << "-------------------------\n";
}
return 0;
}

10

Решение

Это было забавное упражнение, где я в конце концов собрать рабочую демонстрацию on_success[1] аннотировать узлы AST.

Давайте предположим, что мы хотим AST как:

namespace ast
{
struct LocationInfo {
unsigned line, column, length;
};

struct Identifier     : LocationInfo {
std::string name;
};

struct VarAssignment  : LocationInfo {
Identifier id;
int value;
};

struct SourceCode     : LocationInfo {
Identifier function;
std::vector<VarAssignment> assignments;
};
}

Я знаю, «информация о местоположении», вероятно, излишня для SourceCode узел, но вы знаете … В любом случае, чтобы упростить назначение атрибутов этим узлам без необходимости семантические действия или множество специально созданных конструкторов:

#include <boost/fusion/adapted/struct.hpp>
BOOST_FUSION_ADAPT_STRUCT(ast::Identifier,    (std::string, name))
BOOST_FUSION_ADAPT_STRUCT(ast::VarAssignment, (ast::Identifier, id)(int, value))
BOOST_FUSION_ADAPT_STRUCT(ast::SourceCode,    (ast::Identifier, function)(std::vector<ast::VarAssignment>, assignments))

Там. Теперь мы можем объявить правила для выставления этих атрибутов:

qi::rule<Iterator, ast::SourceCode(),    Skipper> SourceCode;
qi::rule<Iterator, ast::VarAssignment(), Skipper> VarAssignment;
qi::rule<Iterator, ast::Identifier()>         Identifier;
// no skipper, no attributes:
qi::rule<Iterator> KeywordFunction, KeywordVar, SemiColon;

Мы вообще не изменяем грамматику (по сути): распространение атрибута «просто автоматическое»[2] :

KeywordFunction = lit("function");
KeywordVar      = lit("var");
SemiColon       = lit(';');

Identifier      = as_string [ alpha >> *(alnum | char_("_")) ];
VarAssignment   = KeywordVar >> Identifier >> '=' >> int_ >> SemiColon;
SourceCode      = KeywordFunction >> Identifier >> '{' >> *VarAssignment >> '}';

Магия

Как мы получаем информацию о местоположении источника, прикрепленную к нашим узлам?

auto set_location_info = annotate(_val, _1, _3);
on_success(Identifier,    set_location_info);
on_success(VarAssignment, set_location_info);
on_success(SourceCode,    set_location_info);

Сейчас, annotate это просто ленивая версия вызываемого, которая определяется как:

template<typename It>
struct annotation_f {
typedef void result_type;

annotation_f(It first) : first(first) {}
It const first;

template<typename Val, typename First, typename Last>
void operator()(Val& v, First f, Last l) const {
do_annotate(v, f, l, first);
}
private:
void static do_annotate(ast::LocationInfo& li, It f, It l, It first) {
using std::distance;
li.line   = get_line(f);
li.column = get_column(first, f);
li.length = distance(f, l);
}
static void do_annotate(...) { }
};

Из-за способа, которым get_column работает, функтор есть Stateful (как он запоминает стартовый итератор)[3]. Как вы видете do_annotate просто принимает все, что происходит от LocationInfo,

Теперь доказательство пудинга:

std::string const content = "function FuncName_1 {\n var Var_1 = 3;\n var  Var_2 = 4; }";

pos_iterator_t first(content.begin()), iter = first, last(content.end());
ParseGrammar<pos_iterator_t> resolver(first);    //  Our parser

ast::SourceCode program;
bool ok = phrase_parse(iter,
last,
resolver,
qi::space,
program);

std::cout << std::boolalpha;
std::cout << "ok  : " << ok << std::endl;
std::cout << "full: " << (iter == last) << std::endl;
if(ok && iter == last)
{
std::cout << "OK: Parsing fully succeeded\n\n";

std::cout << "Function name: " << program.function.name << " (see L" << program.printLoc() << ")\n";
for (auto const& va : program.assignments)
std::cout << "variable " << va.id.name << " assigned value " << va.value << " at L" << va.printLoc() << "\n";
}
else
{
int line   = get_line(iter);
int column = get_column(first, iter);
std::cout << "-------------------------\n";
std::cout << "ERROR: Parsing failed or not complete\n";
std::cout << "stopped at: " << line  << ":" << column << "\n";
std::cout << "remaining: '" << std::string(iter, last) << "'\n";
std::cout << "-------------------------\n";
}

Это печатает:

ok  : true
full: true
OK: Parsing fully succeeded

Function name: FuncName_1 (see L1:1:56)
variable Var_1 assigned value 3 at L2:3:14
variable Var_2 assigned value 4 at L3:3:15

Видеть это Жить на Колиру

Также показаны:

  • обработка ошибок, например:

    Error: expecting "=" in line 3:
    
    var  Var_2 - 4; }
    ^---- here
    ok  : false
    full: false
    -------------------------
    ERROR: Parsing failed or not complete
    stopped at: 1:1
    remaining: 'function FuncName_1 {
    var Var_1 = 3;
    var  Var_2 - 4; }'
    -------------------------
    
  • BOOST_SPIRIT_DEBUG макросы

  • Немного хакерский способ для удобной потоковой передачи LocationInfo часть любого узла AST, извините 🙂
//#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/support_line_pos_iterator.hpp>
#include <iomanip>

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

typedef boost::spirit::line_pos_iterator<std::string::const_iterator> pos_iterator_t;

namespace ast
{
namespace manip { struct LocationInfoPrinter; }

struct LocationInfo {
unsigned line, column, length;
manip::LocationInfoPrinter printLoc() const;
};

struct Identifier     : LocationInfo {
std::string name;
};

struct VarAssignment  : LocationInfo {
Identifier id;
int value;
};

struct SourceCode     : LocationInfo {
Identifier function;
std::vector<VarAssignment> assignments;
};

///////////////////////////////////////////////////////////////////////////
// Completely unnecessary tweak to get a "poor man's" io manipulator going
// so we can do `std::cout << x.printLoc()` on types of `x` deriving from
// LocationInfo
namespace manip {
struct LocationInfoPrinter {
LocationInfoPrinter(LocationInfo const& ref) : ref(ref) {}
LocationInfo const& ref;
friend std::ostream& operator<<(std::ostream& os, LocationInfoPrinter const& lip) {
return os << lip.ref.line << ':' << lip.ref.column << ':' << lip.ref.length;
}
};
}

manip::LocationInfoPrinter LocationInfo::printLoc() const { return { *this }; }
// feel free to disregard this hack
///////////////////////////////////////////////////////////////////////////
}

BOOST_FUSION_ADAPT_STRUCT(ast::Identifier,    (std::string, name))
BOOST_FUSION_ADAPT_STRUCT(ast::VarAssignment, (ast::Identifier, id)(int, value))
BOOST_FUSION_ADAPT_STRUCT(ast::SourceCode,    (ast::Identifier, function)(std::vector<ast::VarAssignment>, assignments))

struct error_handler_f {
typedef qi::error_handler_result result_type;
template<typename T1, typename T2, typename T3, typename T4>
qi::error_handler_result operator()(T1 b, T2 e, T3 where, T4 const& what) const {
std::cerr << "Error: expecting " << what << " in line " << get_line(where) << ": \n"<< std::string(b,e) << "\n"<< std::setw(std::distance(b, where)) << '^' << "---- here\n";
return qi::fail;
}
};

template<typename It>
struct annotation_f {
typedef void result_type;

annotation_f(It first) : first(first) {}
It const first;

template<typename Val, typename First, typename Last>
void operator()(Val& v, First f, Last l) const {
do_annotate(v, f, l, first);
}
private:
void static do_annotate(ast::LocationInfo& li, It f, It l, It first) {
using std::distance;
li.line   = get_line(f);
li.column = get_column(first, f);
li.length = distance(f, l);
}
static void do_annotate(...) {}
};

template<typename Iterator=pos_iterator_t, typename Skipper=qi::space_type>
struct ParseGrammar: public qi::grammar<Iterator, ast::SourceCode(), Skipper>
{
ParseGrammar(Iterator first) :
ParseGrammar::base_type(SourceCode),
annotate(first)
{
using namespace qi;
KeywordFunction = lit("function");
KeywordVar      = lit("var");
SemiColon       = lit(';');

Identifier      = as_string [ alpha >> *(alnum | char_("_")) ];
VarAssignment   = KeywordVar > Identifier > '=' > int_ > SemiColon; // note: expectation points
SourceCode      = KeywordFunction >> Identifier >> '{' >> *VarAssignment >> '}';

on_error<fail>(VarAssignment, handler(_1, _2, _3, _4));
on_error<fail>(SourceCode, handler(_1, _2, _3, _4));

auto set_location_info = annotate(_val, _1, _3);
on_success(Identifier,    set_location_info);
on_success(VarAssignment, set_location_info);
on_success(SourceCode,    set_location_info);

BOOST_SPIRIT_DEBUG_NODES((KeywordFunction)(KeywordVar)(SemiColon)(Identifier)(VarAssignment)(SourceCode))
}

phx::function<error_handler_f> handler;
phx::function<annotation_f<Iterator>> annotate;

qi::rule<Iterator, ast::SourceCode(),    Skipper> SourceCode;
qi::rule<Iterator, ast::VarAssignment(), Skipper> VarAssignment;
qi::rule<Iterator, ast::Identifier()>             Identifier;
// no skipper, no attributes:
qi::rule<Iterator> KeywordFunction, KeywordVar, SemiColon;
};

int main()
{
std::string const content = "function FuncName_1 {\n var Var_1 = 3;\n var  Var_2 - 4; }";

pos_iterator_t first(content.begin()), iter = first, last(content.end());
ParseGrammar<pos_iterator_t> resolver(first);    //  Our parser

ast::SourceCode program;
bool ok = phrase_parse(iter,
last,
resolver,
qi::space,
program);

std::cout << std::boolalpha;
std::cout << "ok  : " << ok << std::endl;
std::cout << "full: " << (iter == last) << std::endl;
if(ok && iter == last)
{
std::cout << "OK: Parsing fully succeeded\n\n";

std::cout << "Function name: " << program.function.name << " (see L" << program.printLoc() << ")\n";
for (auto const& va : program.assignments)
std::cout << "variable " << va.id.name << " assigned value " << va.value << " at L" << va.printLoc() << "\n";
}
else
{
int line   = get_line(iter);
int column = get_column(first, iter);
std::cout << "-------------------------\n";
std::cout << "ERROR: Parsing failed or not complete\n";
std::cout << "stopped at: " << line  << ":" << column << "\n";
std::cout << "remaining: '" << std::string(iter, last) << "'\n";
std::cout << "-------------------------\n";
}
return 0;
}

[1] к сожалению, документировано, за исключением образца (ов) заклинаний

[2] ну я использовал as_string чтобы получить правильное назначение Identifier без особого труда

[3] Там могут быть более разумные способы с точки зрения производительности, но сейчас давайте будем простыми

13

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector