• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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