123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- // Copyright Nat Goodspeed 2013.
- // Distributed under the Boost Software License, Version 1.0.
- // (See accompanying file LICENSE_1_0.txt or copy at
- // http://www.boost.org/LICENSE_1_0.txt)
- #include <boost/coroutine/all.hpp>
- #include <iostream>
- #include <iomanip>
- #include <string>
- #include <cctype>
- #include <sstream>
- #include <boost/bind.hpp>
- #include <boost/foreach.hpp>
- typedef boost::coroutines::asymmetric_coroutine<std::string> coro_t;
- // deliver each line of input stream to sink as a separate string
- void readlines(coro_t::push_type& sink, std::istream& in)
- {
- std::string line;
- while (std::getline(in, line))
- sink(line);
- }
- void tokenize(coro_t::push_type& sink, coro_t::pull_type& source)
- {
- // This tokenizer doesn't happen to be stateful: you could reasonably
- // implement it with a single call to push each new token downstream. But
- // I've worked with stateful tokenizers, in which the meaning of input
- // characters depends in part on their position within the input line. At
- // the time, I wished for a way to resume at the suspend point!
- BOOST_FOREACH(std::string line, source)
- {
- std::string::size_type pos = 0;
- while (pos < line.length())
- {
- if (line[pos] == '"')
- {
- std::string token;
- ++pos; // skip open quote
- while (pos < line.length() && line[pos] != '"')
- token += line[pos++];
- ++pos; // skip close quote
- sink(token); // pass token downstream
- }
- else if (std::isspace(line[pos]))
- {
- ++pos; // outside quotes, ignore whitespace
- }
- else if (std::isalpha(line[pos]))
- {
- std::string token;
- while (pos < line.length() && std::isalpha(line[pos]))
- token += line[pos++];
- sink(token); // pass token downstream
- }
- else // punctuation
- {
- sink(std::string(1, line[pos++]));
- }
- }
- }
- }
- void only_words(coro_t::push_type& sink, coro_t::pull_type& source)
- {
- BOOST_FOREACH(std::string token, source)
- {
- if (! token.empty() && std::isalpha(token[0]))
- sink(token);
- }
- }
- void trace(coro_t::push_type& sink, coro_t::pull_type& source)
- {
- BOOST_FOREACH(std::string token, source)
- {
- std::cout << "trace: '" << token << "'\n";
- sink(token);
- }
- }
- struct FinalEOL
- {
- ~FinalEOL() { std::cout << std::endl; }
- };
- void layout(coro_t::pull_type& source, int num, int width)
- {
- // Finish the last line when we leave by whatever means
- FinalEOL eol;
- // Pull values from upstream, lay them out 'num' to a line
- for (;;)
- {
- for (int i = 0; i < num; ++i)
- {
- // when we exhaust the input, stop
- if (! source)
- return;
- std::cout << std::setw(width) << source.get();
- // now that we've handled this item, advance to next
- source();
- }
- // after 'num' items, line break
- std::cout << std::endl;
- }
- }
- int main(int argc, char *argv[])
- {
- // For example purposes, instead of having a separate text file in the
- // local filesystem, construct an istringstream to read.
- std::string data(
- "This is the first line.\n"
- "This, the second.\n"
- "The third has \"a phrase\"!\n"
- );
- {
- std::cout << "\nreadlines:\n";
- std::istringstream infile(data);
- // Each coroutine-function has a small, specific job to do. Instead of
- // adding conditional logic to a large, complex input function, the
- // caller composes smaller functions into the desired processing
- // chain.
- coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
- coro_t::pull_type tracer(boost::bind(trace, _1, boost::ref(reader)));
- BOOST_FOREACH(std::string line, tracer)
- {
- std::cout << "got: " << line << "\n";
- }
- }
- {
- std::cout << "\ncompose a chain:\n";
- std::istringstream infile(data);
- coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
- coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
- coro_t::pull_type tracer(boost::bind(trace, _1, boost::ref(tokenizer)));
- BOOST_FOREACH(std::string token, tracer)
- {
- // just iterate, we're already pulling through tracer
- }
- }
- {
- std::cout << "\nfilter:\n";
- std::istringstream infile(data);
- coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
- coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
- coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer)));
- coro_t::pull_type tracer(boost::bind(trace, _1, boost::ref(filter)));
- BOOST_FOREACH(std::string token, tracer)
- {
- // just iterate, we're already pulling through tracer
- }
- }
- {
- std::cout << "\nlayout() as coroutine::push_type:\n";
- std::istringstream infile(data);
- coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
- coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
- coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer)));
- coro_t::push_type writer(boost::bind(layout, _1, 5, 15));
- BOOST_FOREACH(std::string token, filter)
- {
- writer(token);
- }
- }
- {
- std::cout << "\ncalling layout() directly:\n";
- std::istringstream infile(data);
- coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
- coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
- coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer)));
- // Because of the symmetry of the API, we can directly call layout()
- // instead of using it as a coroutine-function.
- layout(filter, 5, 15);
- }
- {
- std::cout << "\nfiltering output:\n";
- std::istringstream infile(data);
- coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
- coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
- coro_t::push_type writer(boost::bind(layout, _1, 5, 15));
- // Because of the symmetry of the API, we can use any of these
- // chaining functions in a push_type coroutine chain as well.
- coro_t::push_type filter(boost::bind(only_words, boost::ref(writer), _1));
- BOOST_FOREACH(std::string token, tokenizer)
- {
- filter(token);
- }
- }
- std::cout << "\nDone" << std::endl;
- return EXIT_SUCCESS;
- }
|