2010-06-20 3 views
9

Je suis en train d'analyser une fonction C comme des expressions d'arbres comme les suivantes (en utilisant le Spirit Parser Framework):Parsing une grammaire avec l'Esprit Boost

F(A() , B(GREAT(SOME , NOT)) , C(YES)) 

Pour cela, je suis en train d'utiliser les trois règles sur les points suivants grammaire:

template< typename Iterator , typename ExpressionAST > 
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), space_type> { 

    InputGrammar() : InputGrammar::base_type() { 
     tag = (qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9"))[ push_back(at_c<0>(qi::_val) , qi::_1) ]; 
     command = tag [ at_c<0>(qi::_val) = at_c<0>(qi::_1) ] >> "(" >> (*instruction >> ",") 
             [ push_back(at_c<1>(qi::_val) , qi::_1) ] >> ")"; 
     instruction = (command | tag) [qi::_val = qi::_1]; 
    } 
    qi::rule< Iterator , ExpressionAST() , space_type > tag; 
    qi::rule< Iterator , ExpressionAST() , space_type > command; 
    qi::rule< Iterator , ExpressionAST() , space_type > instruction; 
}; 

Notez que ma règle d'étiquette essaie juste de capturer les identifiants utilisés dans les expressions (les noms de fonction « »). Notez également que la signature de la règle d'étiquette renvoie un ExpressionAST au lieu d'un std::string, comme dans la plupart des exemples. La raison pour laquelle je veux faire comme ceci est en fait assez simple: je déteste utiliser des variantes et je les éviterai si possible. Ce serait génial de garder le gâteau et de le manger aussi je suppose. Une commande doit commencer par une balise (le nom du nœud actuel, le premier champ de chaîne du nœud AST) et un nombre variable d'arguments entre parenthèses, et chacun des arguments peut être une balise elle-même ou une autre commande .

Cependant, cet exemple ne fonctionne pas du tout. Il compile et tout, mais au moment de l'exécution, il ne parvient pas à analyser toutes mes chaînes de test. Et la chose qui m'ennuie vraiment, c'est que je n'arrive pas à comprendre comment le réparer, puisque je ne peux pas vraiment déboguer le code ci-dessus, au moins dans le sens traditionnel du mot. Fondamentalement, la seule façon dont je vois que je peux réparer le code ci-dessus est de savoir ce que je fais mal. Donc, la question est que je ne sais pas quel est le problème avec le code ci-dessus. Comment définiriez-vous la grammaire ci-dessus?

Le type ExpressionAST J'utilise est:

struct MockExpressionNode { 
    std::string name; 
    std::vector<MockExpressionNode> operands; 

    typedef std::vector<MockExpressionNode>::iterator iterator; 
    typedef std::vector<MockExpressionNode>::const_iterator const_iterator; 

    iterator begin() { return operands.begin(); } 
    const_iterator begin() const { return operands.begin(); } 
    iterator end() { return operands.end(); } 
    const_iterator end() const { return operands.end(); } 

    bool is_leaf() const { 
     return (operands.begin() == operands.end()); 
    } 
}; 

BOOST_FUSION_ADAPT_STRUCT(
    MockExpressionNode, 
    (std::string, name) 
    (std::vector<MockExpressionNode>, operands) 
) 
+0

Quelque chose que j'ai découvert récemment est que les identificateurs C et C++ peuvent avoir des caractères '$' dans leurs noms. Alors que a-z, A-Z, 0-9 (sauf pour le premier caractère), _ et $ sont valides dans un identifiant C/C++. – Cthutu

+2

@Cthutu MSVC autorise les caractères accentués dans les identifiants. Cela ne veut pas dire que c'est conforme aux normes. –

+0

Plus important encore, quel est le point que vous essayez de faire @Cthutu? Y a-t-il une pénurie d'identifiants? Votre compilateur ne prend-il pas en charge les espaces de noms correctement? – sehe

Répondre

11

En ce qui concerne le débogage, il est possible d'utiliser une approche pause et regarder normale. Ceci est rendu difficile par la manière dont vous avez formaté les règles. Si vous formatez par les exemples d'esprit (~ un parseur par ligne, une déclaration de phoenix par ligne), les points de rupture seront beaucoup plus informatifs.

Votre structure de données n'a pas un moyen de distinguer A() de SOME en ce qu'ils sont à la fois des feuilles (laissez-moi savoir si quelque chose me manque). De votre commentaire de variante, je ne pense pas que c'était votre intention, ainsi pour distinguer ces deux cas, j'ai ajouté une variable de membre bool commandFlag à MockExpressionNode (vrai pour A() et faux pour SOME), avec une ligne d'adaptateur de fusion correspondante.

Pour le code spécifique, vous devez passer la règle de départ au constructeur de base, i.e. .:

InputGrammar() : InputGrammar::base_type(instruction) {...} 

Ceci est le point d'entrée dans la grammaire, et est la raison pour laquelle vous n'obtenez des données analysées. Je suis surpris qu'il compilé sans cela, je pensais que le type de grammaire était nécessaire pour correspondre au type de la première règle. Même ainsi, c'est une convention pratique à suivre.

Pour la règle tag, il y a en fait deux parseurs qi::char_("a-zA-Z_"), qui est _1 avec le type char et *qi::char_("a-zA-Z_0-9") qui est _2 avec le type (essentiellement) vector<char>.Ce ne est pas possible de contraindre ceux-ci dans une chaîne sans autorules, mais il peut être fait en attachant une règle à chaque omble chevalier analysable:

tag = qi::char_("a-zA-Z_") 
     [ at_c<0>(qi::_val) = qi::_1 ]; 
    >> *qi::char_("a-zA-Z_0-9")   //[] has precedence over *, so _1 is 
     [ at_c<0>(qi::_val) += qi::_1 ]; // a char rather than a vector<char> 

Cependant, son plus propre à laisser l'esprit faire cette conversion. Donc, définissez une nouvelle règle:

qi::rule< Iterator , std::string(void) , ascii::space_type > identifier; 
identifier %= qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9"); 

Et ne vous inquiétez pas à ce sujet;). Étiquetterons devient

tag = identifier 
     [ 
      at_c<0>(qi::_val) = qi::_1, 
      ph::at_c<2>(qi::_val) = false //commandFlag 
     ] 

Pour la commande, la première partie est très bien, mais Theres problèmes de couple avec (*instruction >> ",")[ push_back(at_c<1>(qi::_val) , qi::_1) ]. Cela va analyser zéro ou plusieurs règles d'instruction suivies d'un ",". Il tente également de pousser un vector<MockExpressionNode> (ne savez pas pourquoi cela a été compilé, peut-être pas instancié à cause de la règle de départ manquante?). Je pense que vous voulez que le suivant (avec la modification d'identification):

command = 
     identifier 
     [ 
      ph::at_c<0>(qi::_val) = qi::_1, 
      ph::at_c<2>(qi::_val) = true //commandFlag 
     ] 
    >> "(" 
    >> -(instruction % ",") 
     [ 
      ph::at_c<1>(qi::_val) = qi::_1 
     ] 
    >> ")"; 

Cette option utilise l'opérateur - et l'opérateur de liste %, ce dernier est équivalent à instruction >> *("," >> instruction). L'expression phoenix affecte ensuite directement le vecteur au membre de structure, mais vous pouvez également attacher l'action directement à la correspondance d'instruction et utiliser push_back.

La règle d'instruction est correcte, je vais juste mentionner qu'elle est équivalente à instruction %= (command|tag).

Une dernière chose, s'il n'y a en fait pas de distinction entre A() et SOME (votre structure originale sans commandFlag), vous pouvez écrire cet analyseur en utilisant seulement autorules:

template< typename Iterator , typename ExpressionAST > 
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), ascii::space_type> { 
    InputGrammar() : InputGrammar::base_type(command) { 
     identifier %= 
      qi::char_("a-zA-Z_") 
     >> *qi::char_("a-zA-Z_0-9"); 
     command %= 
      identifier 
     >> -(
      "(" 
     >> -(command % ",") 
     >> ")"); 
    } 
    qi::rule< Iterator , std::string(void) , ascii::space_type > identifier; 
    qi::rule< Iterator , ExpressionAST(void) , ascii::space_type > command; 
}; 

C'est le grand avantage de en utilisant une structure enveloppée par fusion qui modélise l'entrée de près.

+0

Salut AcademicRobot, excellent post. J'ai pris quelques jours pour répondre juste parce qu'il y avait tellement à digérer sur les opérateurs que je n'ai pas vraiment lu sur les docs. Essayait également de remplacer vos setters commandFlag par qi :: _ val.setAsFlag(); mais apparemment le type de _val n'est pas le même que ExpressionAST mais un acteur phoenix wrapper d'une sorte – lurscher

+1

@lurscher - Heureux que vous l'ayez trouvé utile. Oui, qi :: _val va évaluer un ExpressionAST, mais ce n'est pas vraiment ce type. Pour appeler les fonctions membres, vous devez utiliser phoenix bind (pour memfun 'void setAsFlag (drapeau booléen)'): 'phoenix :: bind (& ExpressionAST :: setAsFlag, qi :: _ val, true)'. – academicRobot

+0

étrange, qui a une certaine saveur void * .. – lurscher

Questions connexes