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