chaining.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // Copyright Nat Goodspeed 2013.
  2. // Distributed under the Boost Software License, Version 1.0.
  3. // (See accompanying file LICENSE_1_0.txt or copy at
  4. // http://www.boost.org/LICENSE_1_0.txt)
  5. #include <boost/coroutine/all.hpp>
  6. #include <iostream>
  7. #include <iomanip>
  8. #include <string>
  9. #include <cctype>
  10. #include <sstream>
  11. #include <boost/bind.hpp>
  12. #include <boost/foreach.hpp>
  13. typedef boost::coroutines::asymmetric_coroutine<std::string> coro_t;
  14. // deliver each line of input stream to sink as a separate string
  15. void readlines(coro_t::push_type& sink, std::istream& in)
  16. {
  17. std::string line;
  18. while (std::getline(in, line))
  19. sink(line);
  20. }
  21. void tokenize(coro_t::push_type& sink, coro_t::pull_type& source)
  22. {
  23. // This tokenizer doesn't happen to be stateful: you could reasonably
  24. // implement it with a single call to push each new token downstream. But
  25. // I've worked with stateful tokenizers, in which the meaning of input
  26. // characters depends in part on their position within the input line. At
  27. // the time, I wished for a way to resume at the suspend point!
  28. BOOST_FOREACH(std::string line, source)
  29. {
  30. std::string::size_type pos = 0;
  31. while (pos < line.length())
  32. {
  33. if (line[pos] == '"')
  34. {
  35. std::string token;
  36. ++pos; // skip open quote
  37. while (pos < line.length() && line[pos] != '"')
  38. token += line[pos++];
  39. ++pos; // skip close quote
  40. sink(token); // pass token downstream
  41. }
  42. else if (std::isspace(line[pos]))
  43. {
  44. ++pos; // outside quotes, ignore whitespace
  45. }
  46. else if (std::isalpha(line[pos]))
  47. {
  48. std::string token;
  49. while (pos < line.length() && std::isalpha(line[pos]))
  50. token += line[pos++];
  51. sink(token); // pass token downstream
  52. }
  53. else // punctuation
  54. {
  55. sink(std::string(1, line[pos++]));
  56. }
  57. }
  58. }
  59. }
  60. void only_words(coro_t::push_type& sink, coro_t::pull_type& source)
  61. {
  62. BOOST_FOREACH(std::string token, source)
  63. {
  64. if (! token.empty() && std::isalpha(token[0]))
  65. sink(token);
  66. }
  67. }
  68. void trace(coro_t::push_type& sink, coro_t::pull_type& source)
  69. {
  70. BOOST_FOREACH(std::string token, source)
  71. {
  72. std::cout << "trace: '" << token << "'\n";
  73. sink(token);
  74. }
  75. }
  76. struct FinalEOL
  77. {
  78. ~FinalEOL() { std::cout << std::endl; }
  79. };
  80. void layout(coro_t::pull_type& source, int num, int width)
  81. {
  82. // Finish the last line when we leave by whatever means
  83. FinalEOL eol;
  84. // Pull values from upstream, lay them out 'num' to a line
  85. for (;;)
  86. {
  87. for (int i = 0; i < num; ++i)
  88. {
  89. // when we exhaust the input, stop
  90. if (! source)
  91. return;
  92. std::cout << std::setw(width) << source.get();
  93. // now that we've handled this item, advance to next
  94. source();
  95. }
  96. // after 'num' items, line break
  97. std::cout << std::endl;
  98. }
  99. }
  100. int main(int argc, char *argv[])
  101. {
  102. // For example purposes, instead of having a separate text file in the
  103. // local filesystem, construct an istringstream to read.
  104. std::string data(
  105. "This is the first line.\n"
  106. "This, the second.\n"
  107. "The third has \"a phrase\"!\n"
  108. );
  109. {
  110. std::cout << "\nreadlines:\n";
  111. std::istringstream infile(data);
  112. // Each coroutine-function has a small, specific job to do. Instead of
  113. // adding conditional logic to a large, complex input function, the
  114. // caller composes smaller functions into the desired processing
  115. // chain.
  116. coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
  117. coro_t::pull_type tracer(boost::bind(trace, _1, boost::ref(reader)));
  118. BOOST_FOREACH(std::string line, tracer)
  119. {
  120. std::cout << "got: " << line << "\n";
  121. }
  122. }
  123. {
  124. std::cout << "\ncompose a chain:\n";
  125. std::istringstream infile(data);
  126. coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
  127. coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
  128. coro_t::pull_type tracer(boost::bind(trace, _1, boost::ref(tokenizer)));
  129. BOOST_FOREACH(std::string token, tracer)
  130. {
  131. // just iterate, we're already pulling through tracer
  132. }
  133. }
  134. {
  135. std::cout << "\nfilter:\n";
  136. std::istringstream infile(data);
  137. coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
  138. coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
  139. coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer)));
  140. coro_t::pull_type tracer(boost::bind(trace, _1, boost::ref(filter)));
  141. BOOST_FOREACH(std::string token, tracer)
  142. {
  143. // just iterate, we're already pulling through tracer
  144. }
  145. }
  146. {
  147. std::cout << "\nlayout() as coroutine::push_type:\n";
  148. std::istringstream infile(data);
  149. coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
  150. coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
  151. coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer)));
  152. coro_t::push_type writer(boost::bind(layout, _1, 5, 15));
  153. BOOST_FOREACH(std::string token, filter)
  154. {
  155. writer(token);
  156. }
  157. }
  158. {
  159. std::cout << "\ncalling layout() directly:\n";
  160. std::istringstream infile(data);
  161. coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
  162. coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
  163. coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer)));
  164. // Because of the symmetry of the API, we can directly call layout()
  165. // instead of using it as a coroutine-function.
  166. layout(filter, 5, 15);
  167. }
  168. {
  169. std::cout << "\nfiltering output:\n";
  170. std::istringstream infile(data);
  171. coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
  172. coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
  173. coro_t::push_type writer(boost::bind(layout, _1, 5, 15));
  174. // Because of the symmetry of the API, we can use any of these
  175. // chaining functions in a push_type coroutine chain as well.
  176. coro_t::push_type filter(boost::bind(only_words, boost::ref(writer), _1));
  177. BOOST_FOREACH(std::string token, tokenizer)
  178. {
  179. filter(token);
  180. }
  181. }
  182. std::cout << "\nDone" << std::endl;
  183. return EXIT_SUCCESS;
  184. }