1

I want to output a vector of strings using Boost Spirit Karma. The output generation should fail if any of the strings don't satisfy the constraints. I have tried the following:

#include <boost/spirit/include/karma.hpp>

namespace ka = boost::spirit::karma;

int main()
{
    typedef std::ostream_iterator<char> iterator_t;

    std::string is1{"123"}, is2{"def"};
    std::vector<std::string> iv1{"123", "456"}, iv2{"123","def"};

    auto num = +ka::char_("0-9");
    auto nums = num % ka::lit(";");

    assert(ka::generate(iterator_t{std::cout}, num << ka::eol, is1) == true);
    assert(ka::generate(iterator_t{std::cout}, num << ka::eol, is2) == false);

    assert(ka::generate(iterator_t{std::cout}, nums << ka::eol, iv1) == true);
    assert(ka::generate(iterator_t{std::cout}, nums << ka::eol, iv2) == false); // Assertion Fails
}

Is there a way to make the rule fail if any of the sub-rules do not succeed?

kloffy
  • 2,928
  • 2
  • 25
  • 34
  • note, using `auto` on Spirit's expression templates is inherently unsafe. See BOOST_SPIRIT_AUTO – sehe Oct 17 '13 at 08:32

2 Answers2

3

I don't know what version of boost you use, but

  • 1_42_0 doesn't compile your snippet
  • 1_49_0 doesn't fail and prints:

    123
    123;456
    
  • 1_54_0 doesn't fail and prints:

    123
    123;456
    

So, I can't reproduce the problem. However, conceptually, I think you are looking for karma::buffer[]:

Generator Directive for Temporary Output Buffering (buffer[])

All generator components (except the Alternative (|) generator) pass their generated output directly to the underlying output stream. If a generator fails halfway through, the output generated so far is not 'rolled back'. The buffering generator directive allows to avoid this unwanted output to be generated. It temporarily redirects the output produced by the embedded generator into a buffer. This buffer is flushed to the underlying stream only after the embedded generator succeeded, but is discarded otherwise.

So you could add

ka::rule<iterator_t, std::string()>              num  = +ka::char_("0-9");
ka::rule<iterator_t, std::vector<std::string>()> nums = ka::buffer [ num % ka::lit(";") ];

Note I wouldn't rule out that you were looking at Undefined Behaviour because Proto expression trees don't mix well with auto because of stale references to temporaries in the sub-expressions.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • I have just tested again with Xcode 5.0 and Boost 1_54_0, as well as Visual Studio 2013 RC and Boost 1_53_0. It is definitely still not working for me, the last assert fails and the program terminates. Interestingly, while testing on Windows, I ran into the invalid iterator issue mentioned in your post: http://stackoverflow.com/questions/18579660/generating-default-value-when-none-is-found – kloffy Oct 17 '13 at 11:27
  • 1
    @sehe I think that @kloffy wants something analogous to `qi::eoi`, that I think it's not available, but I would love to be proven wrong. [Here](http://coliru.stacked-crooked.com/a/b7cf4806e15f86b3) is a really ugly semantic action based alternative that works for the simple test in the question. – llonesmiz Oct 17 '13 at 16:52
2

Here is one posible solution that creates a custom directive (heavily based on the one explained here, full code) called full that only returns true when its subject returns true and the number of elements generated is equal to the number of elements in the container passed as attribute.

The changes I've made are:

  • replaced columns_delimiter with element_counter_delimiter.
  • replaced simple_columns_generator with full_container_generator.
  • replaced columns with full.
  • deleted the member function final_delimit_out.
  • modified slightly generate in element_counter_delimiter and full_container_generator.
  • added adjust_size in order to account for the fact that % generates 2*num_elem - 1 times (n ints and n-1 semicolons)

Live Example

#include <iostream>
#include <boost/spirit/include/karma.hpp>

//START OF FULL.HPP
#include <boost/spirit/include/karma_generate.hpp>

///////////////////////////////////////////////////////////////////////////////
// definition the place holder 
namespace custom_generator 
{ 
    BOOST_SPIRIT_TERMINAL(full);
} 

///////////////////////////////////////////////////////////////////////////////
// implementation the enabler
namespace boost { namespace spirit 
{ 
    // We want custom_generator::full to be usable as a directive only, 
    // and only for generator expressions (karma::domain).
    template <>
    struct use_directive<karma::domain, custom_generator::tag::full> 
      : mpl::true_ {}; 
}}

///////////////////////////////////////////////////////////////////////////////
// implementation of the generator
namespace custom_generator
{ 
    template <typename T>
    struct adjust_size
    {
        static std::size_t call(std::size_t val)
        {
            return val; //with kleene and repeat just return the value
        }
    };

    template <typename Left, typename Right>
    struct adjust_size<boost::spirit::karma::list<Left,Right> >
    {
        static std::size_t call(std::size_t val)
        {
            return (val+1)/2; //with list you output n elements and n-1 semicolons
        }
    };
    // special delimiter wrapping the original one that counts the number of elements
    template <typename Delimiter>
    struct element_counter_delimiter 
    {
        element_counter_delimiter(Delimiter const& delim)
          : delimiter(delim), count(0) {}

        // This function is called during the actual delimiter output 
        template <typename OutputIterator, typename Context
          , typename Delimiter_, typename Attribute>
        bool generate(OutputIterator& sink, Context&, Delimiter_ const&
          , Attribute const&) const
        {
            // first invoke the wrapped delimiter
            if (!boost::spirit::karma::delimit_out(sink, delimiter))
                return false;

            // now we count the number of invocations 
            ++count;

            return true;
        }

        Delimiter const& delimiter;   // wrapped delimiter
        mutable unsigned int count;   // invocation counter
    };

    // That's the actual full generator
    template <typename Subject>
    struct full_container_generator
      : boost::spirit::karma::unary_generator<
            full_container_generator<Subject> >
    {
        // Define required output iterator properties
        typedef typename Subject::properties properties;

        // Define the attribute type exposed by this parser component
        template <typename Context, typename Iterator>
        struct attribute 
          : boost::spirit::traits::attribute_of<Subject, Context, Iterator> 
        {};

        full_container_generator(Subject const& s)
          : subject(s)
        {}

        // This function is called during the actual output generation process.
        // It dispatches to the embedded generator while supplying a new 
        // delimiter to use, wrapping the outer delimiter.
        template <typename OutputIterator, typename Context
          , typename Delimiter, typename Attribute>
        bool generate(OutputIterator& sink, Context& ctx
          , Delimiter const& delimiter, Attribute const& attr) const
        {
            std::size_t elems_in_container = boost::spirit::traits::size(attr);
            element_counter_delimiter<Delimiter> d(delimiter);
            if (!subject.generate(sink, ctx, d, attr))
                return false;
            return elems_in_container == adjust_size<Subject>::call(d.count);
        }

        // This function is called during error handling to create
        // a human readable string for the error context.
        template <typename Context>
        boost::spirit::info what(Context& ctx) const
        {
            return boost::spirit::info("full", subject.what(ctx));
        }

        Subject subject;
    };
}

///////////////////////////////////////////////////////////////////////////////
// instantiation of the generator
namespace boost { namespace spirit { namespace karma
{
    // This is the factory function object invoked in order to create 
    // an instance of our full_container_generator.
    template <typename Subject, typename Modifiers>
    struct make_directive<custom_generator::tag::full, Subject, Modifiers>
    {
        typedef custom_generator::full_container_generator<Subject> result_type;

        result_type operator()(unused_type, Subject const& s, unused_type) const
        {
            return result_type(s);
        }
    };
}}}

//END OF FULL.HPP


int main()
{
    typedef std::ostream_iterator<char> iterator_t;
    namespace ka=boost::spirit::karma;

    std::string is1{"123"}, is2{"def"};
    std::vector<std::string> iv1{"123", "456"}, iv2{"123","def"}, iv3{"123", "456", "789"}, iv4{"123", "456", "def"};

    using custom_generator::full;

    ka::rule<iterator_t,std::string()> num = +ka::char_("0-9"); //this rule needs to have attribute std::string
                                                                //that wasn't the case with the original "auto num =..."
                                                                //and it caused that the delimiter count went way higher than it should
    ka::rule<iterator_t,std::vector<std::string>()> nums = full[num%ka::lit(";")];


    assert(ka::generate(iterator_t{std::cout}, num << ka::eol, is1) == true);
    assert(ka::generate(iterator_t{std::cout}, num << ka::eol, is2) == false);

    assert(ka::generate(iterator_t{std::cout}, nums << ka::eol, iv1) == true);
    assert(ka::generate(iterator_t{std::cout}, ka::buffer[nums << ka::eol], iv2) == false); //using buffer as mentioned by sehe
    assert(ka::generate(iterator_t{std::cout}, nums << ka::eol, iv3) == true);
    assert(ka::generate(iterator_t{std::cout}, ka::buffer[nums << ka::eol], iv4) == false);
}
llonesmiz
  • 155
  • 2
  • 11
  • 20
  • Yes, this is an appropriate answer for my question. I take it that you were able to reproduce the failure in my original code? (I was beginning to doubt my sanity after sehe claimed everything worked fine.) I was hoping there would be a more general mechanism in karma to support this kind of constraint, i.e. if any of the sub-rules fail, the enclosing rule fails. Nevertheless, this is the best answer so far! – kloffy Oct 18 '13 at 00:41
  • There was no problem with your initial code, just a misunderstanding of karma list operator semantics. As you can see [here](http://www.boost.org/libs/spirit/doc/html/spirit/karma/reference/operator/list.html#spirit.karma.reference.operator.list.expression_semantics) it only fails if the first embedded generator (`num` in your code) does not successfully execute even one time. So you can't get the behaviour you want without imposing extra constraints on your rule. This solution may be overcomplicated but I like playing with custom directives(they make your grammar/rules a lot less cluttered). – llonesmiz Oct 18 '13 at 05:58
  • I never claimed that the code was broken. Instead, I was asking for a way to get the semantics that I want. Your answer is great and does the job (and I will most likely accept it). There is only a slight conceptual difference, since what I want should not require counting. – kloffy Oct 18 '13 at 06:14
  • I think what you want could be achieved "easily" by creating a custom list generator. But I'm afraid that I don't know enough about spirit to create that(I find its code really scary :)). – llonesmiz Oct 18 '13 at 06:22
  • Modifying karma::list to do what you want seems to be easy after all. [Here](http://coliru.stacked-crooked.com/a/f28974e78917284f) you can find it. I have taken [this header](http://www.boost.org/boost/spirit/home/karma/operator/list.hpp), renamed `base_list`, `list` and `strict_list`, changed the operator from `%` to `<<=`, and changed a single line, in: `if (!generate_left(pass, attr, Strict()))break;`, change `break` with `return false;`. I haven't tested this thoroughly (I haven't tested it at all :)) but I think it should work, I like the other approach more, since I can understand it. – llonesmiz Oct 18 '13 at 09:09