motivation.qbk 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. [/
  2. Copyright Oliver Kowalke 2009.
  3. Distributed under the Boost Software License, Version 1.0.
  4. (See accompanying file LICENSE_1_0.txt or copy at
  5. http://www.boost.org/LICENSE_1_0.txt
  6. ]
  7. [section:motivation Motivation]
  8. In order to support a broad range of execution control behaviour the coroutine
  9. types of __scoro__ and __acoro__ can be used to ['escape-and-reenter] loops, to
  10. ['escape-and-reenter] recursive computations and for ['cooperative] multitasking
  11. helping to solve problems in a much simpler and more elegant way than with only
  12. a single flow of control.
  13. [heading event-driven model]
  14. The event-driven model is a programming paradigm where the flow of a program is
  15. determined by events. The events are generated by multiple independent sources
  16. and an event-dispatcher, waiting on all external sources, triggers callback
  17. functions (event-handlers) whenever one of those events is detected (event-loop).
  18. The application is divided into event selection (detection) and event handling.
  19. [$../../../../libs/coroutine/doc/images/event_model.png [align center]]
  20. The resulting applications are highly scalable, flexible, have high
  21. responsiveness and the components are loosely coupled. This makes the event-driven
  22. model suitable for user interface applications, rule-based productions systems
  23. or applications dealing with asynchronous I/O (for instance network servers).
  24. [heading event-based asynchronous paradigm]
  25. A classic synchronous console program issues an I/O request (e.g. for user
  26. input or filesystem data) and blocks until the request is complete.
  27. In contrast, an asynchronous I/O function initiates the physical operation but
  28. immediately returns to its caller, even though the operation is not yet
  29. complete. A program written to leverage this functionality does not block: it
  30. can proceed with other work (including other I/O requests in parallel) while
  31. the original operation is still pending. When the operation completes, the
  32. program is notified. Because asynchronous applications spend less overall time
  33. waiting for operations, they can outperform synchronous programs.
  34. Events are one of the paradigms for asynchronous execution, but
  35. not all asynchronous systems use events.
  36. Although asynchronous programming can be done using threads, they come with
  37. their own costs:
  38. * hard to program (traps for the unwary)
  39. * memory requirements are high
  40. * large overhead with creation and maintenance of thread state
  41. * expensive context switching between threads
  42. The event-based asynchronous model avoids those issues:
  43. * simpler because of the single stream of instructions
  44. * much less expensive context switches
  45. The downside of this paradigm consists in a sub-optimal program
  46. structure. An event-driven program is required to split its code into
  47. multiple small callback functions, i.e. the code is organized in a sequence of
  48. small steps that execute intermittently. An algorithm that would usually be expressed
  49. as a hierarchy of functions and loops must be transformed into callbacks. The
  50. complete state has to be stored into a data structure while the control flow
  51. returns to the event-loop.
  52. As a consequence, event-driven applications are often tedious and confusing to
  53. write. Each callback introduces a new scope, error callback etc. The
  54. sequential nature of the algorithm is split into multiple callstacks,
  55. making the application hard to debug. Exception handlers are restricted to
  56. local handlers: it is impossible to wrap a sequence of events into a single
  57. try-catch block.
  58. The use of local variables, while/for loops, recursions etc. together with the
  59. event-loop is not possible. The code becomes less expressive.
  60. In the past, code using asio's ['asynchronous operations] was convoluted by
  61. callback functions.
  62. class session
  63. {
  64. public:
  65. session(boost::asio::io_service& io_service) :
  66. socket_(io_service) // construct a TCP-socket from io_service
  67. {}
  68. tcp::socket& socket(){
  69. return socket_;
  70. }
  71. void start(){
  72. // initiate asynchronous read; handle_read() is callback-function
  73. socket_.async_read_some(boost::asio::buffer(data_,max_length),
  74. boost::bind(&session::handle_read,this,
  75. boost::asio::placeholders::error,
  76. boost::asio::placeholders::bytes_transferred));
  77. }
  78. private:
  79. void handle_read(const boost::system::error_code& error,
  80. size_t bytes_transferred){
  81. if (!error)
  82. // initiate asynchronous write; handle_write() is callback-function
  83. boost::asio::async_write(socket_,
  84. boost::asio::buffer(data_,bytes_transferred),
  85. boost::bind(&session::handle_write,this,
  86. boost::asio::placeholders::error));
  87. else
  88. delete this;
  89. }
  90. void handle_write(const boost::system::error_code& error){
  91. if (!error)
  92. // initiate asynchronous read; handle_read() is callback-function
  93. socket_.async_read_some(boost::asio::buffer(data_,max_length),
  94. boost::bind(&session::handle_read,this,
  95. boost::asio::placeholders::error,
  96. boost::asio::placeholders::bytes_transferred));
  97. else
  98. delete this;
  99. }
  100. boost::asio::ip::tcp::socket socket_;
  101. enum { max_length=1024 };
  102. char data_[max_length];
  103. };
  104. In this example, a simple echo server, the logic is split into three member
  105. functions - local state (such as data buffer) is moved to member variables.
  106. __boost_asio__ provides with its new ['asynchronous result] feature a new
  107. framework combining event-driven model and coroutines, hiding the complexity
  108. of event-driven programming and permitting the style of classic sequential code.
  109. The application is not required to pass callback functions to asynchronous
  110. operations and local state is kept as local variables. Therefore the code
  111. is much easier to read and understand.
  112. [footnote Christopher Kohlhoff,
  113. [@ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3964.pdf
  114. N3964 - Library Foundations for Asynchronous Operations, Revision 1]].
  115. __yield_context__ internally uses __boost_coroutine__:
  116. void session(boost::asio::io_service& io_service){
  117. // construct TCP-socket from io_service
  118. boost::asio::ip::tcp::socket socket(io_service);
  119. try{
  120. for(;;){
  121. // local data-buffer
  122. char data[max_length];
  123. boost::system::error_code ec;
  124. // read asynchronous data from socket
  125. // execution context will be suspended until
  126. // some bytes are read from socket
  127. std::size_t length=socket.async_read_some(
  128. boost::asio::buffer(data),
  129. boost::asio::yield[ec]);
  130. if (ec==boost::asio::error::eof)
  131. break; //connection closed cleanly by peer
  132. else if(ec)
  133. throw boost::system::system_error(ec); //some other error
  134. // write some bytes asynchronously
  135. boost::asio::async_write(
  136. socket,
  137. boost::asio::buffer(data,length),
  138. boost::asio::yield[ec]);
  139. if (ec==boost::asio::error::eof)
  140. break; //connection closed cleanly by peer
  141. else if(ec)
  142. throw boost::system::system_error(ec); //some other error
  143. }
  144. } catch(std::exception const& e){
  145. std::cerr<<"Exception: "<<e.what()<<"\n";
  146. }
  147. }
  148. In contrast to the previous example this one gives the impression of sequential
  149. code and local data (['data]) while using asynchronous operations
  150. (['async_read()], ['async_write()]). The algorithm is implemented in one
  151. function and error handling is done by one try-catch block.
  152. [heading recursive SAX parsing]
  153. To someone who knows SAX, the phrase "recursive SAX parsing" might sound
  154. nonsensical. You get callbacks from SAX; you have to manage the element stack
  155. yourself. If you want recursive XML processing, you must first read the entire
  156. DOM into memory, then walk the tree.
  157. But coroutines let you invert the flow of control so you can ask for SAX
  158. events. Once you can do that, you can process them recursively.
  159. // Represent a subset of interesting SAX events
  160. struct BaseEvent{
  161. BaseEvent(const BaseEvent&)=delete;
  162. BaseEvent& operator=(const BaseEvent&)=delete;
  163. };
  164. // End of document or element
  165. struct CloseEvent: public BaseEvent{
  166. // CloseEvent binds (without copying) the TagType reference.
  167. CloseEvent(const xml::sax::Parser::TagType& name):
  168. mName(name)
  169. {}
  170. const xml::sax::Parser::TagType& mName;
  171. };
  172. // Start of document or element
  173. struct OpenEvent: public CloseEvent{
  174. // In addition to CloseEvent's TagType, OpenEvent binds AttributeIterator.
  175. OpenEvent(const xml::sax::Parser::TagType& name,
  176. xml::sax::AttributeIterator& attrs):
  177. CloseEvent(name),
  178. mAttrs(attrs)
  179. {}
  180. xml::sax::AttributeIterator& mAttrs;
  181. };
  182. // text within an element
  183. struct TextEvent: public BaseEvent{
  184. // TextEvent binds the CharIterator.
  185. TextEvent(xml::sax::CharIterator& text):
  186. mText(text)
  187. {}
  188. xml::sax::CharIterator& mText;
  189. };
  190. // The parsing coroutine instantiates BaseEvent subclass instances and
  191. // successively shows them to the main program. It passes a reference so we
  192. // don't slice the BaseEvent subclass.
  193. typedef boost::coroutines::asymmetric_coroutine<const BaseEvent&> coro_t;
  194. void parser(coro_t::push_type& sink,std::istream& in){
  195. xml::sax::Parser xparser;
  196. // startDocument() will send OpenEvent
  197. xparser.startDocument([&sink](const xml::sax::Parser::TagType& name,
  198. xml::sax::AttributeIterator& attrs)
  199. {
  200. sink(OpenEvent(name,attrs));
  201. });
  202. // startTag() will likewise send OpenEvent
  203. xparser.startTag([&sink](const xml::sax::Parser::TagType& name,
  204. xml::sax::AttributeIterator& attrs)
  205. {
  206. sink(OpenEvent(name,attrs));
  207. });
  208. // endTag() will send CloseEvent
  209. xparser.endTag([&sink](const xml::sax::Parser::TagType& name)
  210. {
  211. sink(CloseEvent(name));
  212. });
  213. // endDocument() will likewise send CloseEvent
  214. xparser.endDocument([&sink](const xml::sax::Parser::TagType& name)
  215. {
  216. sink(CloseEvent(name));
  217. });
  218. // characters() will send TextEvent
  219. xparser.characters([&sink](xml::sax::CharIterator& text)
  220. {
  221. sink(TextEvent(text));
  222. });
  223. try
  224. {
  225. // parse the document, firing all the above
  226. xparser.parse(in);
  227. }
  228. catch (xml::Exception e)
  229. {
  230. // xml::sax::Parser throws xml::Exception. Helpfully translate the
  231. // name and provide it as the what() string.
  232. throw std::runtime_error(exception_name(e));
  233. }
  234. }
  235. // Recursively traverse the incoming XML document on the fly, pulling
  236. // BaseEvent& references from 'events'.
  237. // 'indent' illustrates the level of recursion.
  238. // Each time we're called, we've just retrieved an OpenEvent from 'events';
  239. // accept that as a param.
  240. // Return the CloseEvent that ends this element.
  241. const CloseEvent& process(coro_t::pull_type& events,const OpenEvent& context,
  242. const std::string& indent=""){
  243. // Capture OpenEvent's tag name: as soon as we advance the parser, the
  244. // TagType& reference bound in this OpenEvent will be invalidated.
  245. xml::sax::Parser::TagType tagName = context.mName;
  246. // Since the OpenEvent is still the current value from 'events', pass
  247. // control back to 'events' until the next event. Of course, each time we
  248. // come back we must check for the end of the results stream.
  249. while(events()){
  250. // Another event is pending; retrieve it.
  251. const BaseEvent& event=events.get();
  252. const OpenEvent* oe;
  253. const CloseEvent* ce;
  254. const TextEvent* te;
  255. if((oe=dynamic_cast<const OpenEvent*>(&event))){
  256. // When we see OpenEvent, recursively process it.
  257. process(events,*oe,indent+" ");
  258. }
  259. else if((ce=dynamic_cast<const CloseEvent*>(&event))){
  260. // When we see CloseEvent, validate its tag name and then return
  261. // it. (This assert is really a check on xml::sax::Parser, since
  262. // it already validates matching open/close tags.)
  263. assert(ce->mName == tagName);
  264. return *ce;
  265. }
  266. else if((te=dynamic_cast<const TextEvent*>(&event))){
  267. // When we see TextEvent, just report its text, along with
  268. // indentation indicating recursion level.
  269. std::cout<<indent<<"text: '"<<te->mText.getText()<<"'\n";
  270. }
  271. }
  272. }
  273. // pretend we have an XML file of arbitrary size
  274. std::istringstream in(doc);
  275. try
  276. {
  277. coro_t::pull_type events(std::bind(parser,_1,std::ref(in)));
  278. // We fully expect at least ONE event.
  279. assert(events);
  280. // This dynamic_cast<&> is itself an assertion that the first event is an
  281. // OpenEvent.
  282. const OpenEvent& context=dynamic_cast<const OpenEvent&>(events.get());
  283. process(events, context);
  284. }
  285. catch (std::exception& e)
  286. {
  287. std::cout << "Parsing error: " << e.what() << '\n';
  288. }
  289. This problem does not map at all well to communicating between independent
  290. threads. It makes no sense for either side to proceed independently of the
  291. other. You want them to pass control back and forth.
  292. The solution involves a small polymorphic class event hierarchy, to which
  293. we're passing references. The actual instances are temporaries on the
  294. coroutine's stack; the coroutine passes each reference in turn to the main
  295. logic. Copying them as base-class values would slice them.
  296. If we were trying to let the SAX parser proceed independently of the consuming
  297. logic, one could imagine allocating event-subclass instances on the heap,
  298. passing them along on a thread-safe queue of pointers. But that doesn't work
  299. either, because these event classes bind references passed by the SAX parser.
  300. The moment the parser moves on, those references become invalid.
  301. Instead of binding a ['TagType&] reference, we could store a copy of
  302. the ['TagType] in ['CloseEvent]. But that doesn't solve the whole
  303. problem. For attributes, we get an ['AttributeIterator&]; for text we get
  304. a ['CharIterator&]. Storing a copy of those iterators is pointless: once
  305. the parser moves on, those iterators are invalidated. You must process the
  306. attribute iterator (or character iterator) during the SAX callback for that
  307. event.
  308. Naturally we could retrieve and store a copy of every attribute and its value;
  309. we could store a copy of every chunk of text. That would effectively be all
  310. the text in the document -- a heavy price to pay, if the reason we're using
  311. SAX is concern about fitting the entire DOM into memory.
  312. There's yet another advantage to using coroutines. This SAX parser throws an
  313. exception when parsing fails. With a coroutine implementation, you need only
  314. wrap the calling code in try/catch.
  315. With communicating threads, you would have to arrange to catch the exception
  316. and pass along the exception pointer on the same queue you're using to deliver
  317. the other events. You would then have to rethrow the exception to unwind the
  318. recursive document processing.
  319. The coroutine solution maps very naturally to the problem space.
  320. [heading 'same fringe' problem]
  321. The advantages of suspending at an arbitrary call depth can be seen
  322. particularly clearly with the use of a recursive function, such as traversal
  323. of trees.
  324. If traversing two different trees in the same deterministic order produces the
  325. same list of leaf nodes, then both trees have the same fringe.
  326. [$../../../../libs/coroutine/doc/images/same_fringe.png [align center]]
  327. Both trees in the picture have the same fringe even though the structure of the
  328. trees is different.
  329. The same fringe problem could be solved using coroutines by iterating over the
  330. leaf nodes and comparing this sequence via ['std::equal()]. The range of data
  331. values is generated by function ['traverse()] which recursively traverses the
  332. tree and passes each node's data value to its __push_coro__.
  333. __push_coro__ suspends the recursive computation and transfers the data value to
  334. the main execution context.
  335. __pull_coro_it__, created from __pull_coro__, steps over those data values and
  336. delivers them to ['std::equal()] for comparison. Each increment of
  337. __pull_coro_it__ resumes ['traverse()]. Upon return from
  338. ['iterator::operator++()], either a new data value is available, or tree
  339. traversal is finished (iterator is invalidated).
  340. In effect, the coroutine iterator presents a flattened view of the recursive
  341. data structure.
  342. struct node{
  343. typedef boost::shared_ptr<node> ptr_t;
  344. // Each tree node has an optional left subtree,
  345. // an optional right subtree and a value of its own.
  346. // The value is considered to be between the left
  347. // subtree and the right.
  348. ptr_t left,right;
  349. std::string value;
  350. // construct leaf
  351. node(const std::string& v):
  352. left(),right(),value(v)
  353. {}
  354. // construct nonleaf
  355. node(ptr_t l,const std::string& v,ptr_t r):
  356. left(l),right(r),value(v)
  357. {}
  358. static ptr_t create(const std::string& v){
  359. return ptr_t(new node(v));
  360. }
  361. static ptr_t create(ptr_t l,const std::string& v,ptr_t r){
  362. return ptr_t(new node(l,v,r));
  363. }
  364. };
  365. node::ptr_t create_left_tree_from(const std::string& root){
  366. /* --------
  367. root
  368. / \
  369. b e
  370. / \
  371. a c
  372. -------- */
  373. return node::create(
  374. node::create(
  375. node::create("a"),
  376. "b",
  377. node::create("c")),
  378. root,
  379. node::create("e"));
  380. }
  381. node::ptr_t create_right_tree_from(const std::string& root){
  382. /* --------
  383. root
  384. / \
  385. a d
  386. / \
  387. c e
  388. -------- */
  389. return node::create(
  390. node::create("a"),
  391. root,
  392. node::create(
  393. node::create("c"),
  394. "d",
  395. node::create("e")));
  396. }
  397. // recursively walk the tree, delivering values in order
  398. void traverse(node::ptr_t n,
  399. boost::coroutines::asymmetric_coroutine<std::string>::push_type& out){
  400. if(n->left) traverse(n->left,out);
  401. out(n->value);
  402. if(n->right) traverse(n->right,out);
  403. }
  404. // evaluation
  405. {
  406. node::ptr_t left_d(create_left_tree_from("d"));
  407. boost::coroutines::asymmetric_coroutine<std::string>::pull_type left_d_reader(
  408. [&]( boost::coroutines::asymmetric_coroutine<std::string>::push_type & out){
  409. traverse(left_d,out);
  410. });
  411. node::ptr_t right_b(create_right_tree_from("b"));
  412. boost::coroutines::asymmetric_coroutine<std::string>::pull_type right_b_reader(
  413. [&]( boost::coroutines::asymmetric_coroutine<std::string>::push_type & out){
  414. traverse(right_b,out);
  415. });
  416. std::cout << "left tree from d == right tree from b? "
  417. << std::boolalpha
  418. << std::equal(boost::begin(left_d_reader),
  419. boost::end(left_d_reader),
  420. boost::begin(right_b_reader))
  421. << std::endl;
  422. }
  423. {
  424. node::ptr_t left_d(create_left_tree_from("d"));
  425. boost::coroutines::asymmetric_coroutine<std::string>::pull_type left_d_reader(
  426. [&]( boost::coroutines::asymmetric_coroutine<std::string>::push_type & out){
  427. traverse(left_d,out);
  428. });
  429. node::ptr_t right_x(create_right_tree_from("x"));
  430. boost::coroutines::asymmetric_coroutine<std::string>::pull_type right_x_reader(
  431. [&]( boost::coroutines::asymmetric_coroutine<std::string>::push_type & out){
  432. traverse(right_x,out);
  433. });
  434. std::cout << "left tree from d == right tree from x? "
  435. << std::boolalpha
  436. << std::equal(boost::begin(left_d_reader),
  437. boost::end(left_d_reader),
  438. boost::begin(right_x_reader))
  439. << std::endl;
  440. }
  441. std::cout << "Done" << std::endl;
  442. output:
  443. left tree from d == right tree from b? true
  444. left tree from d == right tree from x? false
  445. Done
  446. [heading merging two sorted arrays]
  447. This example demonstrates how symmetric coroutines merge two sorted arrays.
  448. std::vector<int> merge(const std::vector<int>& a,const std::vector<int>& b){
  449. std::vector<int> c;
  450. std::size_t idx_a=0,idx_b=0;
  451. boost::coroutines::symmetric_coroutine<void>::call_type *other_a=0,*other_b=0;
  452. boost::coroutines::symmetric_coroutine<void>::call_type coro_a(
  453. [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){
  454. while(idx_a<a.size()){
  455. if(b[idx_b]<a[idx_a]) // test if element in array b is less than in array a
  456. yield(*other_b); // yield to coroutine coro_b
  457. c.push_back(a[idx_a++]); // add element to final array
  458. }
  459. // add remaining elements of array b
  460. while(idx_b<b.size())
  461. c.push_back(b[idx_b++]);
  462. });
  463. boost::coroutines::symmetric_coroutine<void>::call_type coro_b(
  464. [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){
  465. while(idx_b<b.size()){
  466. if(a[idx_a]<b[idx_b]) // test if element in array a is less than in array b
  467. yield(*other_a); // yield to coroutine coro_a
  468. c.push_back(b[idx_b++]); // add element to final array
  469. }
  470. // add remaining elements of array a
  471. while(idx_a<a.size())
  472. c.push_back(a[idx_a++]);
  473. });
  474. other_a=&coro_a;
  475. other_b=&coro_b;
  476. coro_a(); // enter coroutine-fn of coro_a
  477. return c;
  478. }
  479. [heading chaining coroutines]
  480. This code shows how coroutines could be chained.
  481. typedef boost::coroutines::asymmetric_coroutine<std::string> coro_t;
  482. // deliver each line of input stream to sink as a separate string
  483. void readlines(coro_t::push_type& sink,std::istream& in){
  484. std::string line;
  485. while(std::getline(in,line))
  486. sink(line);
  487. }
  488. void tokenize(coro_t::push_type& sink, coro_t::pull_type& source){
  489. // This tokenizer doesn't happen to be stateful: you could reasonably
  490. // implement it with a single call to push each new token downstream. But
  491. // I've worked with stateful tokenizers, in which the meaning of input
  492. // characters depends in part on their position within the input line.
  493. BOOST_FOREACH(std::string line,source){
  494. std::string::size_type pos=0;
  495. while(pos<line.length()){
  496. if(line[pos]=='"'){
  497. std::string token;
  498. ++pos; // skip open quote
  499. while(pos<line.length()&&line[pos]!='"')
  500. token+=line[pos++];
  501. ++pos; // skip close quote
  502. sink(token); // pass token downstream
  503. } else if (std::isspace(line[pos])){
  504. ++pos; // outside quotes, ignore whitespace
  505. } else if (std::isalpha(line[pos])){
  506. std::string token;
  507. while (pos < line.length() && std::isalpha(line[pos]))
  508. token += line[pos++];
  509. sink(token); // pass token downstream
  510. } else { // punctuation
  511. sink(std::string(1,line[pos++]));
  512. }
  513. }
  514. }
  515. }
  516. void only_words(coro_t::push_type& sink,coro_t::pull_type& source){
  517. BOOST_FOREACH(std::string token,source){
  518. if (!token.empty() && std::isalpha(token[0]))
  519. sink(token);
  520. }
  521. }
  522. void trace(coro_t::push_type& sink, coro_t::pull_type& source){
  523. BOOST_FOREACH(std::string token,source){
  524. std::cout << "trace: '" << token << "'\n";
  525. sink(token);
  526. }
  527. }
  528. struct FinalEOL{
  529. ~FinalEOL(){
  530. std::cout << std::endl;
  531. }
  532. };
  533. void layout(coro_t::pull_type& source,int num,int width){
  534. // Finish the last line when we leave by whatever means
  535. FinalEOL eol;
  536. // Pull values from upstream, lay them out 'num' to a line
  537. for (;;){
  538. for (int i = 0; i < num; ++i){
  539. // when we exhaust the input, stop
  540. if (!source) return;
  541. std::cout << std::setw(width) << source.get();
  542. // now that we've handled this item, advance to next
  543. source();
  544. }
  545. // after 'num' items, line break
  546. std::cout << std::endl;
  547. }
  548. }
  549. // For example purposes, instead of having a separate text file in the
  550. // local filesystem, construct an istringstream to read.
  551. std::string data(
  552. "This is the first line.\n"
  553. "This, the second.\n"
  554. "The third has \"a phrase\"!\n"
  555. );
  556. {
  557. std::cout << "\nfilter:\n";
  558. std::istringstream infile(data);
  559. coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
  560. coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
  561. coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer)));
  562. coro_t::pull_type tracer(boost::bind(trace, _1, boost::ref(filter)));
  563. BOOST_FOREACH(std::string token,tracer){
  564. // just iterate, we're already pulling through tracer
  565. }
  566. }
  567. {
  568. std::cout << "\nlayout() as coroutine::push_type:\n";
  569. std::istringstream infile(data);
  570. coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile)));
  571. coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader)));
  572. coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer)));
  573. coro_t::push_type writer(boost::bind(layout, _1, 5, 15));
  574. BOOST_FOREACH(std::string token,filter){
  575. writer(token);
  576. }
  577. }
  578. {
  579. std::cout << "\nfiltering output:\n";
  580. std::istringstream infile(data);
  581. coro_t::pull_type reader(boost::bind(readlines,_1,boost::ref(infile)));
  582. coro_t::pull_type tokenizer(boost::bind(tokenize,_1,boost::ref(reader)));
  583. coro_t::push_type writer(boost::bind(layout,_1,5,15));
  584. // Because of the symmetry of the API, we can use any of these
  585. // chaining functions in a push_type coroutine chain as well.
  586. coro_t::push_type filter(boost::bind(only_words,boost::ref(writer),_1));
  587. BOOST_FOREACH(std::string token,tokenizer){
  588. filter(token);
  589. }
  590. }
  591. [endsect]