1

I can parse one float and print it. (test1, test2) Somehow I am unable to build a rule that parses three floats. My final goal is to parse three floats and save them to a glm::vec3.

  • My rule seems to be incorrect:

qi::lexeme[qi::float_ << ' ' << qi::float_ << ' ' << qi::float_]

  • I am not sure if i am using BOOST_FUSION_ADAPT_STRUCT correctly

Here is a source to show what i came up with so far:

#include <iostream>
#include <string>
#include <glm\glm.hpp>
#include <boost\spirit\include\qi.hpp>
#include <boost\fusion\include\adapt_struct.hpp>

namespace qi = boost::spirit::qi;

void test1()
{
    std::string test = "1.2";
    auto it = test.begin();
    if (qi::phrase_parse(it, test.end(), qi::float_, qi::space) && (it == test.end()))
        std::cout << "test 1 ok" << std::endl;
    else
        std::cout << "test 1 not ok" << std::endl;
}

void test2()
{
    std::string test = "1.2";
    auto it = test.begin();
    float f;
    if (qi::phrase_parse(it, test.end(), qi::float_, qi::space, f) && (it == test.end()))
        std::cout << "test 2 ok " << f << std::endl;
    else
        std::cout << "test 2 not ok" << std::endl;
}

void test3()
{
    std::string test = "1.2 2.2 3.3";
    qi::rule<std::string::iterator, qi::space_type> VEC3;
    //error_invalid_expression
    VEC3 = qi::lexeme[qi::float_ << ' ' << qi::float_ << ' ' << qi::float_];
    auto it = test.begin();
    if (qi::phrase_parse(it, test.end(), VEC3, qi::space) && (it == test.end()))
        std::cout << "test 3 ok " << std::endl;
    else
        std::cout << "test 3 not ok" << std::endl;
}

BOOST_FUSION_ADAPT_STRUCT(
    glm::vec3,
    (float, x)
    (float, y)
    (float, z)
    )

void test4()
{
    std::string test = "1.2 2.2 3.3";
    qi::rule<std::string::iterator, qi::space_type> VEC3;
    //error_invalid_expression
    VEC3 = qi::lexeme[qi::float_ << ' ' << qi::float_ << ' ' << qi::float_];
    glm::vec3 result;
    auto it = test.begin();
    if (qi::phrase_parse(it, test.end(), VEC3, qi::space, result) && (it == test.end()))
    {
        std::cout << "test 4 ok (" << result.x << ", " << result.y << ", " << result.z << ")"<< std::endl;
    }
    else
        std::cout << "test 4 not ok" << std::endl;
}

int main(int argc, char ** argv)
{
    test1();
    test2();
    test3();
    test4();
}

The test4 function contains everything I try to get done.

EDIT:

As suggested two things need to be changed:

void test4()
{
    std::string test = "1.2 2.2 3.3";
    qi::rule<std::string::iterator, glm::vec3(), qi::space_type> VEC3;
    //error_invalid_expression
    VEC3 = qi::lexeme[qi::float_ >> ' ' >> qi::float_ >> ' ' >> qi::float_];
    glm::vec3 result;
    auto it = test.begin();
    if (qi::phrase_parse(it, test.end(), VEC3, qi::space, result) && (it == test.end()))
    {
        std::cout << "test 4 ok (" << result.x << ", " << result.y << ", " << result.z << ")"<< std::endl;
    }
    else
        std::cout << "test 4 not ok" << std::endl;
}
Community
  • 1
  • 1
Johannes
  • 6,490
  • 10
  • 59
  • 108

3 Answers3

3

I agree with Pete Becker. Simpler is usually better.

Now, since you are going to end up using spirit, let's look at what can be simpler:

  1. use c++11 adapt:

    BOOST_FUSION_ADAPT_STRUCT(glm::vec3, x, y, z)
    
  2. you can do without the adapt (simpler in the demo)

  3. you can (should) drop the skipper from the rule declaration if you're doing a toplevel lexeme[] directive anyways¹. See stackoverflow.com/questions/17072987/boost-spirit-skipper-issues/17073965#17073965

  4. better yet, do without the rule for readability here. In reality, you would have a larger grammar with multiple rules. Be sure not to instantiate the grammar each parse (for efficiency).

Demo testbed Live On Coliru

//#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <glm/glm.hpp>
#include <iostream>
#include <string>

namespace qi = boost::spirit::qi;

glm::vec3 simpler(std::string const& test) {
    auto it = test.begin();

    glm::vec3 result;
    using namespace qi;

    if (parse(it, test.end(), 
            float_ >> ' ' >> float_ >> ' ' >> float_ >> eoi,
            result.x, result.y, result.z))
    {
        return result;
    }

    throw std::runtime_error("invalid input");
}

glm::vec3 use_skipper(std::string const& test) {
    auto it = test.begin();

    glm::vec3 result;
    using namespace qi;

    if (phrase_parse(it, test.end(), 
            float_ >> float_ >> float_ >> eoi,
            space, result.x, result.y, result.z))
    {
        return result;
    }

    throw std::runtime_error("invalid input");
}

BOOST_FUSION_ADAPT_STRUCT(glm::vec3, x, y, z)

glm::vec3 with_adapt(std::string const& test) {
    auto it = test.begin();

    glm::vec3 result;
    using namespace qi;

    if (phrase_parse(it, test.end(), float_ >> float_ >> float_ >>eoi, space, result))
    {
        return result;
    }

    throw std::runtime_error("invalid input");
}

int main() {
    struct { glm::vec3 (&f)(std::string const&); std::string name; } tests[] = {
        {simpler,     "simpler"},
        {use_skipper, "use_skipper"},
        {with_adapt,  "with_adapt"}
    };

    for (auto t : tests) try {
        glm::vec3 v = t.f("1.2 2.2 3.3");
        std::cout << t.name << " ok (" << v.x << ", " << v.y << ", " << v.z << ")\n";
    } catch(std::exception const& e) {
        std::cout << t.name << " fail (" << e.what() << ")\n";
    }
}

You can be slightly more lightweight Using x3. Not really less work, but certainly less heavy on the compiler.

¹ subtle difference w.r.t. pre/post skipping, but that difference would be gone if there's a containing grammar with skipper

sehe
  • 374,641
  • 47
  • 450
  • 633
2

The sequence parser operator is >>, use that instead of << in your rules

VEC3 = qi::lexeme[qi::float_ >> ' ' >> qi::float_ >> ' ' >> qi::float_];

You've specified a whitespace skipper in your rule, so it can be further simplified by removing the lexeme directive and letting the skipper skip spaces automatically (unless you want to ensure there is a single space character in between the inputs).

VEC3 = qi::float_ >> qi::float_ >> qi::float_;

Also, if you want your rule to return a value, you need to add that signature to the rule type

qi::rule<std::string::iterator, glm::vec3(), qi::space_type> VEC3;

This is unrelated to the compilation error you're seeing, but use forward slashes in your #include directives, that works on all platforms.

#include <boost/spirit/include/qi.hpp>
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Nice... i feel a bit dumb, but thanks :). Can you also help with the 'BOOST_FUSION_ADAPT_STRUCT' portion of the question? the result in test4 is not getting saved on my computer. – Johannes Jan 07 '16 at 18:57
  • @Johannes Ah yes, missed that, when want the rule to synthesize a return value, that needs to be added to the rule type. – Praetorian Jan 07 '16 at 19:17
1

Seems like an awful lot of work for something that's straightforward.

#include <iostream>
#include <string>
#include <sstream>

int main() {
    std::string test = "1.2 2.2 3.3";
    float f1, f2, f3;
    std::istringstream in(test);
    in >> f1 >> f2 >> f3;
    return 0;
}
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • The whole thing will at one point include all of the wavefront object and wavefront material files. So it wont stay as simple as that - but you are correct to a degree. – Johannes Jan 07 '16 at 18:54
  • @Johannes - I figured there might be more to it than that. Still, simpler is usually better. – Pete Becker Jan 07 '16 at 18:55