├── LICENSE.txt ├── README.md ├── conversions.cpp ├── hello.cpp ├── hello.mak ├── input-test.txt ├── instream.cpp ├── instream.h ├── lakefile ├── log4cpp.properties ├── logger.cpp ├── logger.h ├── makefile ├── outstream.cpp ├── outstream.h ├── print.cpp ├── print.h ├── print.mak ├── read.results ├── reader-lineinfo.cpp ├── speedtest.cpp ├── templ-read.cpp ├── test.results ├── testins.cpp ├── testlog.cpp └── testout.cpp /LICENSE.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------- 2 | Copyright (c) 2016 Steve Donovan 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. The name of the author may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## An alternative to either stdio or iostreams 2 | 3 | The standard C++ iostreams library is generally easier to use 4 | than stdio, if you don't particularly care about the shape of the 5 | output and have no resource constraints. No ugly `.c_str()` and 6 | the nasty `"%" PRIu64`-style formats needed to consistently output integers 7 | across different platforms. 8 | 9 | Embedded platforms can benefit from the better program structuring and abstraction 10 | capabilities of C++ over C, although as always you have to know what 11 | you're doing. Even so, embedded developers still prefer stdio. iostreams is 12 | a large and sprawling library, and it represents a measurable cost in 13 | systems with limited program space. Even `std::string` is now entangled 14 | with `iostreams`. I've written about this unfortunate [state of affairs] before, 15 | but the standard is the standard. 16 | 17 | Apart from the bloat, 18 | getting precise output with iostreams is harder than with stdio - once you've 19 | marshalled all the necessary manipulators to force the correct format, the 20 | result is more verbose. Then there are some misfeatures; a lot of people 21 | don't know that `endl` is more than just a line-end; it _flushes the stream_. 22 | They wonder why their C++ programs have such bad I/O performance 23 | and end up on Stackoverflow. 24 | 25 | ## Basic outstreams: Writer and StrWriter 26 | 27 | Here are the basic capabilties of outstreams. The `Writer` class can be 28 | initialized with a standard stream (like `stdout` and `stderr`, or a file name. 29 | The object maintans ownership of the `FILE*` in this case, so destruction 30 | closes the file. `Writer` is overridable so constructing strings is 31 | straightforward, avoiding the ugliness of naked `sprintf`. Everything is inside 32 | the `stream` namespace, although I'm careful to avoid collisions with `std` 33 | - [this table](http://www.bobarcher.org/software/C++11%20Symbols.html) is 34 | an excellent resource. 35 | 36 | ```cpp 37 | using namespace stream; 38 | double x = 1.2; 39 | const char *msg = "hello"; 40 | int i = 42; 41 | 42 | outs.fmt("%g %s %d",x,msg,i); 43 | // --> 1.2 hello 42 44 | 45 | // dtor closes file 46 | Writer("/tmp/myfile").fmt("answer = %d\n",i); 47 | 48 | // StrWriter converts to std::string 49 | string s = StrWriter().fmt("greeting is '%s'",msg); 50 | // s = "greeting is 'hello'" 51 | ``` 52 | ## Using the Call Operator for Fun and Profit 53 | 54 | These are modest advantages, but outstreams goes further. 55 | A char separator can be defined between fields, and `operator()` 56 | is overriden to mean 'write out a field" 57 | 58 | ```cpp 59 | // assuming above declarations 60 | outs.sep(' '); 61 | // separator is 'sticky' - remains set until reset. 62 | 63 | outs(x)(msg)(i)('\n\); 64 | // --> 1.2 hello 42 65 | 66 | outs(msg,"'%s'")('\n'); 67 | // --> 'hello' 68 | ``` 69 | 70 | The advantage of the call operator is that it may have extra optional arguments, 71 | in this case an explicit format. "'%s'" is a bit clumsy, so `quote_s` can be used here 72 | (`quote_d` for double quotes). The real power of these constants is when applied to 73 | multiple fields, since only the string fields will obey it. 74 | 75 | The standard algorithm `for_each` can be used to dump out a container, since 76 | it expects a _callable. 77 | 78 | ```cpp 79 | vector vi {10,20,30}; 80 | for_each(vi.begin(),vi.end(),outs); 81 | outs(); 82 | // ---> 10 20 30 83 | ``` 84 | 85 | ## Support for Containers 86 | 87 | The above idiom is common enough that there is a template version of 88 | `operator()` for displaying the elements of a container. Note that the 89 | sticky separator is not used - since we cannot guarantee it will be suitable; you provide 90 | a separator in addition to the usual format. The `trace()` adapter takes two forms; 91 | the first wraps a _container_ and the second an explicit iterator range. 92 | 93 | ```cpp 94 | // default format, list separator 95 | outs(range(vi),0,',')('\n'); 96 | // --> 10,20,30 97 | 98 | outs(range(vi),"'%d'",' ')('\n'); 99 | // --> '10' '20' '30' 100 | 101 | int arr[] {10,20,30}; 102 | outs(range(arr,arr+3),0,' ')('\n'); 103 | // --> 10 20 30 104 | 105 | string s = "\xFE\xEE\xAA"; 106 | outs(range(s),hex_u)("and that's all")('\n'); 107 | // --> FEEEAA and that's all 108 | ``` 109 | `hex_u` and `hex_l` are constants for hex format; if you try the obvious '%X' you 110 | will notice the problem; without the length modifier the value is printed out as an `int` 111 | and sign-extended. 112 | 113 | In the C++11 standard there is a marvelous class called `std::intializer_list` 114 | which is implicitly used in bracket initialization of containers. We overload it 115 | directly to support brace lists of objects of the same type; there is also an 116 | overload for `std::pair` 117 | 118 | ```cpp 119 | outs({10,20,30})(); 120 | // --> 10 20 30 121 | 122 | // little burst of JSON - note "Q" format! 123 | outs('{')({ 124 | make_pair("hello",42), 125 | make_pair("dolly",99), 126 | make_pair("frodo",111) 127 | },quote_d,',')('}')(); 128 | // --> { "hello":42,"dolly":99,"frodo":111 } 129 | 130 | // which will also work when iterating over `std::map` 131 | ``` 132 | 133 | ## Overriding Writer 134 | 135 | Here is `StrWriter` presented in inline form for convenience. 136 | `write_char` is used to output any separator and '\n' at the end 137 | of lines; `write_out` takes a format and a `va_list`. 138 | 139 | ```cpp 140 | static char buf[128]; 141 | 142 | class StrWriter: public Writer { 143 | std::string s; 144 | public: 145 | StrWriter(char sepr=0, size_t capacity = 0) 146 | : Writer(stderr) 147 | { 148 | if (capacity != 0) { 149 | s.reserve(capacity); 150 | } 151 | sep(sepr); 152 | } 153 | 154 | std::string str() { return s; } 155 | operator std::string () { return s; } 156 | void clear() { s.clear(); } 157 | 158 | virtual void write_char(char ch) { 159 | s += ch; 160 | } 161 | 162 | virtual void write_out(const char *fmt, va_list ap) { 163 | int nch = vsnprintf(buf,sizeof(buf),fmt,ap); 164 | s.append(buf,nch); 165 | } 166 | 167 | virtual Writer& flush() { return *this; } 168 | }; 169 | ``` 170 | No doubt this can be improved (keep a resizable line buffer) but this is 171 | intended as a humble 'serving suggestion'. 172 | 173 | The more common form of extension in iostreams is to teach it to output 174 | your own types, simply by adding yet another overload for `operator<<`. 175 | Alas, `operator()` may only be defined as a method of a type. In the first 176 | version of this library I had an ugly and inconsistent scheme, and finally 177 | settled on an old-fashioned solution; your types must implement a `Writeable` interface: 178 | 179 | ```cpp 180 | struct Point: public Writeable { 181 | int X; 182 | int Y; 183 | 184 | Point(int X, int Y) : X(X),Y(Y) {} 185 | 186 | void write_to(Writer& w, const char *format) { 187 | w.fmt("(%d,%d)",p.X,p.Y); 188 | } 189 | 190 | }; 191 | 192 | ... 193 | 194 | Point P(10,100); 195 | outs(P)('\n'); 196 | // --> (10,20) 197 | 198 | ``` 199 | 200 | ## Output in a Hurry 201 | 202 | There are some kinds of debugging which can only be done by selectively 203 | dumping out values, for instance real-time or heavily-threaded code. 204 | outstreams provides a useful low-cost alternative for `printf` here where 205 | the exact formatting isn't so important. 206 | 207 | ```cpp 208 | #define VA(var) (#var "=")(var,quote_s) 209 | ... 210 | string full_name; 211 | uint64_t id_number; 212 | ... 213 | outs VA(full_name) VA(id_number)(); 214 | // --> full_name "bonzo the dog" id_number 666 215 | ... 216 | #define VX64(var) (#var)(var,"%#016" PRIX64) 217 | ... 218 | outs VX64(id_number) (); 219 | // --> id_number 0X0000000000029A 220 | 221 | // if the FILE* is NULL, then a Writer instance converts to false 222 | // can say logs.set(nullptr) to switch off tracing 223 | #define TRACE if (logs) logs ("TRACE") 224 | TRACE VA(full_name) (); 225 | 226 | ``` 227 | Speaking of speed, how much speed are we losing relative to stdio? 228 | 229 | `speedtest.cpp` writes a million lines to a file; each line consists 230 | of five double values separated by spaces. Obviously we are 231 | calling the underlying formatted write function a lot more, but 232 | in the end it makes little difference, except that using iostreams seems 233 | rather slower: 234 | 235 | ``` 236 | stdio 2720ms 237 | outstreams 2937ms 238 | iostreams 4541ms 239 | 240 | ``` 241 | 242 | ## 'scanf' Considered Harmful 243 | 244 | Many still like using the `printf` family of functions, but the reputation of 245 | the `scanf` family is less certain, even among C programmers. The problem 246 | is that handling errors with `scanf` is a mission. Jonathan Leffler on Stackoverflow 247 | expresses the conventional wisdom: "I don't use scanf() in production code; it is just 248 | too damn hard to control properly". And (for that matter) neither have I. 249 | 250 | However, there are some immediate advantages to doing a wrapper around 251 | `fgets`. Here is the very useful 'all lines in a file' pattern. 252 | 253 | ```cpp 254 | #include "instream.h" 255 | ... 256 | string line; 257 | Reader inf("instream.cpp"); 258 | while (inf.getline(line)) { 259 | ... 260 | } 261 | ``` 262 | This is very much like the standard iostreams way of doing things; `Reader` objects 263 | convert to a status `bool`, so this loop will end when we hit an error, which is _usually_ 264 | EOF. The file handle will be closed when `inf` goes out of scope. 265 | 266 | Another way to do this is use the template method `getlines`, which will work with 267 | any container that understands `push_back`. This has a convenient optional argument 268 | that is the maximum number of lines you wish to collect. 269 | 270 | ```cpp 271 | vector lines; 272 | Reader inf("instream.cpp"); 273 | inf.getlines(lines); 274 | ``` 275 | 276 | There is a `read(void *,int)` for reading binary objects, and `readall(string&)` which 277 | slurps in the whole file without needing to know its size. (`std::string` is a very 278 | flexible data structure, if considered as a bunch of bytes, since its size does not 279 | depend on any silly NUL ending in the data.) 280 | 281 | ## A Symmetrical approach to Wrapping stdio Input 282 | 283 | One way to explore an idea is to see how far you can push it. `Reader` overloads 284 | `operator()` just like `Writer`: 285 | 286 | ```cpp 287 | int i; 288 | double x; 289 | string s; 290 | 291 | Reader rdr("input-test.txt"); 292 | // first line is '1 3.14 lines' 293 | rdr (i) (x) (s); 294 | ``` 295 | Which is certainly compact! But in an imperfect world, there are errors. 296 | 297 | The approach I take is for `Reader` to note errors when they first occur and 298 | thereafter perform _no_ input operations. So even if this file doesn't exist 299 | no terrible things will happen. But you must check the error state afterwards! 300 | 301 | ```cpp 302 | if (! rdr) { 303 | outs(rdr.error())(); 304 | } 305 | ``` 306 | 307 | This just gives you a descriptive error string. More details are available using 308 | `Reader::Error` which is read as a pseudo-input-field. 309 | 310 | ```cpp 311 | Reader::Error err; 312 | if (! rdr (i) (x) (s) (err)) { 313 | outs(err.errcode)(err.msg)(err.pos)(); 314 | } 315 | ``` 316 | If the input line had a non-convertable field - say it is "1 xx3 lines" - then 317 | the error message will look like "error reading double '%lf%n' 'xx'" and 318 | `err.pos` will tell you where in the file this happened. Although not strictly 319 | necessary, it is useful to check the error as soon as possible, close to the 320 | context where it happened. 321 | 322 | ## Reading Strings and the Output of Commands 323 | 324 | `Reader` is overrideable, like `Writer`. In particular, can use `StrReader` to parse 325 | strings - this overrides `raw_read_line` and `read_fmt`. I went to some trouble 326 | to track position in the stream manually (using '%n') and not depend on the 327 | underlying `FILE*`, precisely to make this specialization possible. 328 | 329 | This is useful because C++ string handling is not very complete and instream 330 | operations complement them well, just as with `std::istrstream`. Strings come to 331 | us from many sources that are not files. 332 | 333 | `CmdReader` wraps `popen` and overrides `close_handle` so that `pclose` 334 | is called on the handle after destruction. `stderr` is redirected to `stdout` so 335 | that the stream captures all of the output, good or bad. 336 | 337 | ```cpp 338 | vector header_files; 339 | CmdReader("ls *.h").getlines(header_files); 340 | ``` 341 | A common pattern is to invoke a simple command and capture the first line 342 | of output. Can use `getline` but a convenient `line` method 343 | is provided: 344 | 345 | ```cpp 346 | string platform = CmdReader("uname").line(); 347 | ``` 348 | There is no reliable way of getting the _actual error_ when using `popen`, so 349 | workarounds are useful. If it is a command where there is no output, or the output 350 | is unneeded, then a shell trick is to silence all output and conditionally execute 351 | a following command. 352 | 353 | ```cpp 354 | if (CmdReader("true",cmd_ok).line() == "OK") { 355 | outs("true is OK"); 356 | } 357 | if (CmdReader("false",cmd_ok).line() != "OK") { 358 | outs("false is not OK"); 359 | } 360 | 361 | if (CmdReader("g++",cmd_retcode) == "4") { 362 | outs("no files provided")(); 363 | } 364 | 365 | ``` 366 | 367 | -------------------------------------------------------------------------------- /conversions.cpp: -------------------------------------------------------------------------------- 1 | #include "outstream.h" 2 | #include "instream.h" 3 | using namespace std; 4 | using namespace stream; 5 | 6 | void test(const char *str) { 7 | Reader::Error err; 8 | int res = 0; 9 | double x = 0; 10 | string s; 11 | short b = 0; 12 | 13 | outs("input")(str)(); 14 | //~ Writer("/tmp/tmp.tmp")(str); 15 | //~ Reader s1("/tmp/tmp.tmp"); 16 | StrReader s1(str); 17 | s1 (res)(x)(s)(b)(err); 18 | outs("output")(res)(x)(s)(b)(); 19 | outs("error")(err.msg)(); 20 | 21 | } 22 | 23 | int main() 24 | { 25 | outs.sep(' '); // DEFAULT 26 | 27 | test("10 4.2 boo 34"); 28 | test("10 4.2 boo 344455555"); 29 | test("10 x4.2 boo 23"); 30 | test("x10 4.2 boo 52"); 31 | test("10 4.2"); 32 | 33 | return 0; 34 | } -------------------------------------------------------------------------------- /hello.cpp: -------------------------------------------------------------------------------- 1 | // outstreams works fine with older standard 2 | // (without intializer lists) 3 | #include "outstream.h" 4 | #include 5 | using namespace std; 6 | using namespace stream; 7 | 8 | int main() 9 | { 10 | 11 | string who = "world!"; 12 | outs("hello")(who)("answer is")(42)(); 13 | 14 | vector vi; 15 | vi.push_back(10); 16 | vi.push_back(20); 17 | vi.push_back(30); 18 | outs(range(vi),0,',')(); 19 | 20 | 21 | float floats[] = {1.2,4.2,5}; 22 | StrWriter sw; 23 | sw(range(floats,floats+3),0,' '); 24 | outs(sw.str())(); 25 | 26 | char buff[1024]; 27 | BufWriter bw(buff,sizeof(buff)); 28 | bw(range(floats,floats+3),0,' ')(" and that's it")('\0'); 29 | outs(buff)(); 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /hello.mak: -------------------------------------------------------------------------------- 1 | # 'Hello world' 2 | DEFINES = -DOLD_STD_CPP 3 | STD=c++03 4 | #STD=c++11 5 | CXXFLAGS = -std=$(STD) -Os $(DEFINES) 6 | OUTSTREAM = outstream.o 7 | LDFLAGS = outstream.o 8 | TARGET = hello 9 | 10 | $(TARGET): $(TARGET).o $(OUTSTREAM) 11 | $(CXX) -o $@ $< $(OUTSTREAM) 12 | 13 | clean: 14 | rm $(TARGET).o 15 | rm $(TARGET) 16 | -------------------------------------------------------------------------------- /input-test.txt: -------------------------------------------------------------------------------- 1 | 1 3.14 lines 2 | 2 generally better to process individual lines 3 | .3 than token by token 4 | 4 unless you can handle scanf errors cleanly! 5 | -------------------------------------------------------------------------------- /instream.cpp: -------------------------------------------------------------------------------- 1 | #include "instream.h" 2 | #include 3 | #include 4 | 5 | namespace stream { 6 | 7 | const int line_size = 256; 8 | 9 | Reader::Reader(FILE *in) 10 | : in(in), owner(false),fpos(0),pos(0),bad(0) 11 | { 12 | } 13 | 14 | Reader::Reader(const char *file, const char *how) 15 | : in((FILE*)nullptr), owner(true),fpos(0),pos(0) 16 | { 17 | open(file,how); 18 | } 19 | 20 | Reader::Reader(const std::string& file, const char *how) 21 | : in((FILE*)nullptr), owner(true),fpos(0),pos(0) 22 | { 23 | open(file,how); 24 | } 25 | 26 | Reader::~Reader() { 27 | close(); 28 | } 29 | 30 | void Reader::close() { 31 | if (owner && in != nullptr) { 32 | close_handle(); 33 | in = nullptr; 34 | } 35 | } 36 | 37 | bool Reader::fail() { 38 | return bad != 0; 39 | } 40 | 41 | Reader::operator bool () { 42 | return ! fail(); 43 | } 44 | 45 | int Reader::error_code() { 46 | return bad; 47 | } 48 | 49 | std::string Reader::error() { 50 | return err_msg; 51 | } 52 | 53 | void Reader::set(FILE *new_in, bool own) { 54 | close(); 55 | in = new_in; 56 | owner = own; 57 | pos = 0; 58 | bad = in == nullptr; 59 | } 60 | 61 | bool Reader::open(const std::string& file, const char *how) { 62 | in = fopen(file.c_str(),how); 63 | bad = errno; 64 | if (bad) { 65 | err_msg = strerror(errno); 66 | } 67 | return ! bad; 68 | } 69 | 70 | void Reader::close_handle() { 71 | fclose(in); 72 | } 73 | 74 | int Reader::read_fmt(const char *fmt, va_list ap) { 75 | return vfscanf(in,fmt,ap); 76 | } 77 | 78 | char *Reader::read_raw_line(char *buff, int buffsize) { 79 | return fgets(buff,buffsize,in); 80 | } 81 | 82 | size_t Reader::read(void *buff, int buffsize) { 83 | if (fail()) return 0; 84 | size_t sz = fread(buff,1,buffsize,in); 85 | return sz; 86 | } 87 | 88 | void Reader::set_error(const std::string& msg, int code) { 89 | err_msg = msg; 90 | bad = code; 91 | } 92 | 93 | Reader& Reader::formatted_read(const char *ctype, const char *def, const char *fmt, ...) { 94 | if (fail()) return *this; 95 | va_list ap; 96 | va_start(ap,fmt); 97 | if (fmt == nullptr) { 98 | fmt = def; 99 | } 100 | int res = read_fmt(fmt,ap); 101 | if (res == EOF) { 102 | if (errno != 0) { // we remain in hope 103 | set_error(strerror(errno),errno); 104 | } else { // but invariably it just means EOF 105 | set_error("EOF reading " + std::string(ctype),EOF); 106 | } 107 | } else 108 | if (res != 1 && *ctype != 'S') { 109 | std::string chars; 110 | (*this)(chars,"%5s"); 111 | set_error("error reading " + std::string(ctype) + " at '" + chars + "'",1); 112 | } 113 | pos += fpos; 114 | va_end(ap); 115 | return *this; 116 | } 117 | 118 | Reader& Reader::conversion_error(const char *kind, uint64_t val, bool was_unsigned) { 119 | bad = ERANGE; 120 | std::string msg = "error converting " + std::string(kind) + " out of range "; 121 | if (was_unsigned) { 122 | msg += std::to_string(val); 123 | } else { 124 | msg += std::to_string((int64_t)val); 125 | } 126 | set_error(msg,1); 127 | return *this; 128 | } 129 | 130 | long Reader::getpos() { 131 | return ftell(in); 132 | } 133 | 134 | void Reader::setpos(long p, char end) { 135 | int whence = SEEK_SET; 136 | if (end == '$') { 137 | whence = SEEK_END; 138 | } else 139 | if (end == '.') { 140 | whence = SEEK_CUR; 141 | } 142 | fseek(in,p,whence); 143 | } 144 | 145 | int Reader::read_line(char *buff, int buffsize) { 146 | char *res = read_raw_line(buff,buffsize); 147 | if (res == nullptr) { 148 | set_error("EOF",EOF); 149 | return 0; 150 | } 151 | int sz = res != nullptr ? strlen(res) : 0; 152 | pos += sz; 153 | return sz; 154 | } 155 | 156 | bool Reader::readall (std::string& s) { 157 | char buff[512]; 158 | size_t sz; 159 | s.clear(); 160 | while ((sz = read(buff,sizeof(buff))) > 0) { 161 | s.append(buff,sz); 162 | } 163 | return errno != 0; 164 | } 165 | 166 | Reader& Reader::getfpos(int& p) { 167 | p = pos; 168 | return *this; 169 | } 170 | 171 | Reader& Reader::operator() (Reader::Error& err) { 172 | err.errcode = bad; 173 | err.msg = err_msg; 174 | err.pos = pos; 175 | return *this; 176 | } 177 | 178 | Reader& Reader::operator() (double &i,const char *fmt) { 179 | return formatted_read("double","%lf%n",fmt,&i,&fpos); 180 | } 181 | 182 | Reader& Reader::operator() (float &i,const char *fmt) { 183 | return formatted_read("float","%f%n",fmt,&i,&fpos); 184 | } 185 | 186 | Reader& Reader::operator() (int64_t &i,const char *fmt) { 187 | return formatted_read("int64","%" SCNd64 "%n",fmt,&i,&fpos); 188 | } 189 | 190 | Reader& Reader::operator() (uint64_t &i,const char *fmt) { 191 | return formatted_read("uint64","%" SCNu64 "%n",fmt,&i,&fpos); 192 | } 193 | 194 | Reader& Reader::operator() (int32_t &i,const char *fmt) { 195 | int64_t val; 196 | if (! (*this)(val)) return *this; 197 | if (val < INT32_MIN || val > INT32_MAX) return conversion_error("int32",val,false); 198 | i = (int16_t)val; 199 | return *this; 200 | } 201 | 202 | Reader& Reader::operator() (uint32_t &i,const char *fmt) { 203 | uint64_t val; 204 | if (! (*this)(val)) return *this; 205 | if (val > UINT32_MAX) return conversion_error("uint32",val,true); 206 | i = (uint32_t)val; 207 | return *this; 208 | } 209 | 210 | Reader& Reader::operator() (int16_t &i,const char *fmt) { 211 | int64_t val; 212 | if (! (*this)(val)) return *this; 213 | if (val < INT16_MIN || val > INT16_MAX) return conversion_error("int16",val,false); 214 | i = (int16_t)val; 215 | return *this; 216 | } 217 | 218 | Reader& Reader::operator() (uint16_t &i,const char *fmt) { 219 | uint64_t val; 220 | if (! (*this)(val)) return *this; 221 | if (val > UINT16_MAX) return conversion_error("uint16",val,true); 222 | i = (uint16_t)val; 223 | return *this; 224 | } 225 | 226 | Reader& Reader::operator() (char &i,const char *fmt) { 227 | return formatted_read("char","%c%n",fmt,&i,&fpos); 228 | } 229 | 230 | Reader& Reader::operator() (uint8_t &i,const char *fmt) { 231 | uint64_t val; 232 | if (! (*this)(val)) return *this; 233 | if (val > UINT8_MAX) return conversion_error("uchar",val,true); 234 | i = (uint8_t)val; 235 | return *this; 236 | } 237 | 238 | Reader& Reader::operator() (std::string &s,const char *fmt) { 239 | if (fail()) return *this; 240 | char buff[line_size]; 241 | if (! formatted_read("string","%s%n",fmt,buff,&fpos)) return *this; 242 | s = buff; 243 | return *this; 244 | } 245 | 246 | Reader& Reader::operator() (const char *extra) { 247 | char buff[line_size]; 248 | strcpy(buff,extra); 249 | strcat(buff,"%n"); 250 | return formatted_read("S",extra,nullptr,&fpos); 251 | } 252 | 253 | Reader& Reader::operator() () { 254 | return skip(); 255 | } 256 | 257 | Reader& Reader::getline(std::string& s) { 258 | if (fail()) return *this; 259 | char buff[line_size]; 260 | s.clear(); 261 | int n = read_line(buff,line_size); 262 | while (n > 1) { 263 | if (buff[n-1]=='\n') { 264 | --n; 265 | buff[n] = 0; // trim \n 266 | s.append(buff,n); 267 | break; 268 | } else { 269 | s.append(buff,n); 270 | } 271 | n = read_line(buff,line_size); 272 | } 273 | return *this; 274 | } 275 | 276 | Reader& Reader::skip(int lines) { 277 | if (fail()) return *this; 278 | int i = 0; 279 | char buff[line_size]; 280 | while (i < lines && read_line(buff,line_size) > 0) { 281 | ++i; 282 | } 283 | return *this; 284 | } 285 | 286 | Reader::LineInfo Reader::getlineinfo (long p) { 287 | if (p == -1) 288 | p = pos; 289 | setpos(0,'^'); 290 | pos = 0; 291 | char buff[line_size]; 292 | int last_pos, lineno = 1; 293 | read_line(buff,line_size); 294 | while (pos < p) { 295 | lineno++; 296 | last_pos = pos; 297 | read_line(buff,line_size); 298 | } 299 | int col = p - last_pos; 300 | setpos(p,'^'); 301 | return {lineno, col}; 302 | } 303 | 304 | Reader ins(stdin); 305 | 306 | CmdReader::CmdReader(std::string cmd, std::string extra) 307 | : Reader((FILE*)nullptr) { 308 | std::string cmdline = cmd + " 2>&1 " + extra; 309 | set(popen(cmdline.c_str(),"r"),true); 310 | } 311 | 312 | void CmdReader::close_handle() { 313 | pclose(in); 314 | } 315 | 316 | std::string CmdReader::line() { 317 | std::string tmp; 318 | getline(tmp); 319 | return tmp; 320 | } 321 | 322 | 323 | StrReader::StrReader(const std::string& s) : Reader((FILE*)nullptr), pc(s.c_str()) { 324 | size = s.size(); 325 | } 326 | 327 | StrReader::StrReader(const char* pc) : Reader((FILE*)nullptr), pc(pc) { 328 | size = strlen(pc); 329 | } 330 | 331 | int StrReader::read_fmt(const char *fmt, va_list ap) { 332 | if (pos >= size) { 333 | bad = 1; return 0; 334 | } 335 | return vsscanf(pc+pos,fmt,ap); 336 | } 337 | 338 | char *StrReader::read_raw_line(char *buff, int buffsize) { 339 | char *P = buff; 340 | const char *Q = pc+pos; 341 | int i = 1; 342 | while (*Q != '\n' && i < buffsize && *Q != 0) { 343 | *P++ = *Q++; 344 | ++i; 345 | } 346 | *P = 0; 347 | return buff; 348 | } 349 | 350 | long StrReader::getpos() { 351 | return pos; 352 | } 353 | 354 | void StrReader::setpos(long p, char end) { 355 | if (end == '^') { 356 | pos = p; 357 | } else 358 | if (end == '.') { 359 | pos += p; 360 | } else 361 | if (end == '$') { 362 | pos = size + p; 363 | } 364 | } 365 | 366 | } 367 | 368 | 369 | -------------------------------------------------------------------------------- /instream.h: -------------------------------------------------------------------------------- 1 | #ifndef INSTREAMS_H 2 | #define INSTREAMS_H 3 | #include 4 | #define __STDC_FORMAT_MACROS 5 | #include 6 | #include 7 | #include 8 | 9 | namespace stream { 10 | class Reader { 11 | protected: 12 | FILE *in; 13 | bool owner; 14 | int fpos; 15 | int pos; 16 | int bad; 17 | std::string err_msg; 18 | 19 | public: 20 | struct Error { 21 | int errcode; 22 | std::string msg; 23 | long pos; 24 | 25 | operator bool () { return errcode != 0; } 26 | }; 27 | 28 | struct LineInfo { 29 | int line; 30 | int column; 31 | }; 32 | 33 | Reader(FILE *in); 34 | Reader(const char *file, const char *how="r"); 35 | Reader(const std::string& file, const char *how="r"); 36 | ~Reader(); 37 | void close(); 38 | 39 | bool fail(); 40 | operator bool (); 41 | std::string error(); 42 | int error_code(); 43 | void set_error(const std::string& msg, int code); 44 | 45 | void set(FILE *new_in, bool own); 46 | bool open(const std::string& file, const char *how="r"); 47 | 48 | virtual void close_handle(); 49 | virtual int read_fmt(const char *fmt, va_list ap); 50 | virtual char *read_raw_line(char *buff, int buffsize); 51 | virtual size_t read(void *buff, int buffsize); 52 | 53 | template 54 | Reader& read(T& data) { 55 | if (! fail()) { 56 | size_t sz = fread(&data,1,sizeof(T),in); 57 | if (sz != sizeof(T)) { 58 | set_error("expected " + std::to_string(sizeof(T)) + " got " + std::to_string(sz) + " bytes",EOF); 59 | } 60 | } 61 | return *this; 62 | } 63 | 64 | Reader& formatted_read(const char *ctype, const char *def, const char *fmt, ...); 65 | Reader& conversion_error(const char *kind, uint64_t val, bool was_unsigned); 66 | 67 | virtual long getpos(); 68 | virtual void setpos(long p, char end='^'); 69 | 70 | int read_line(char *buff, int buffsize); 71 | bool readall (std::string& s); 72 | Reader& getfpos(int& p); 73 | 74 | Reader& operator() (Error& err); 75 | Reader& operator() (double &i,const char *fmt = nullptr); 76 | Reader& operator() (float &i,const char *fmt = nullptr); 77 | Reader& operator() (int64_t &i,const char *fmt = nullptr); 78 | Reader& operator() (uint64_t &i,const char *fmt = nullptr); 79 | Reader& operator() (int32_t &i,const char *fmt = nullptr); 80 | Reader& operator() (uint32_t &i,const char *fmt = nullptr); 81 | Reader& operator() (int16_t &i,const char *fmt = nullptr); 82 | Reader& operator() (uint16_t &i,const char *fmt = nullptr); 83 | Reader& operator() (char &i,const char *fmt = nullptr); 84 | Reader& operator() (uint8_t &i,const char *fmt = nullptr); 85 | Reader& operator() (std::string &s,const char *fmt = nullptr); 86 | Reader& operator() (const char *extra); 87 | Reader& operator() (); 88 | 89 | Reader& getline(std::string& s); 90 | 91 | Reader& skip(int lines=1); 92 | 93 | LineInfo getlineinfo (long p=-1); 94 | 95 | template 96 | Reader& getlines(C& c, size_t lines=-1) { 97 | if (fail()) return *this; 98 | std::string tmp; 99 | size_t i = 0; 100 | while (i < lines && getline(tmp)) { 101 | c.push_back(tmp); 102 | ++i; 103 | } 104 | return *this; 105 | } 106 | 107 | }; 108 | 109 | extern Reader ins; 110 | 111 | class CmdReader: public Reader { 112 | public: 113 | CmdReader(std::string cmd, std::string extra=""); 114 | 115 | virtual void close_handle(); 116 | 117 | std::string line(); 118 | 119 | }; 120 | 121 | const std::string cmd_ok = "> /dev/null && echo OK"; 122 | const std::string cmd_retcode = "> /dev/null || echo $?"; 123 | 124 | class StrReader: public Reader { 125 | protected: 126 | const char * pc; 127 | size_t size; 128 | public: 129 | StrReader(const std::string& s); 130 | StrReader(const char *pc); 131 | 132 | virtual int read_fmt(const char *fmt, va_list ap); 133 | virtual char *read_raw_line(char *buff, int buffsize); 134 | virtual long getpos(); 135 | virtual void setpos(long p, char end='^'); 136 | }; 137 | } 138 | #endif 139 | 140 | 141 | -------------------------------------------------------------------------------- /lakefile: -------------------------------------------------------------------------------- 1 | default { 2 | cpp11.program {'testout',src='testout outstream'}, 3 | cpp11.program {'speedtest',src='speedtest outstream'} 4 | } -------------------------------------------------------------------------------- /log4cpp.properties: -------------------------------------------------------------------------------- 1 | # logging properties 2 | log4cpp.rootCategory=NOTICE, rootAppender 3 | log4cpp.category.testlog=NOTICE, testlog 4 | 5 | log4cpp.appender.rootAppender=ConsoleAppender 6 | log4cpp.appender.rootAppender.threshold=NOTICE 7 | log4cpp.appender.rootAppender.layout=PatternLayout 8 | log4cpp.appender.rootAppender.layout.ConversionPattern=%d [%p] %m%n 9 | 10 | log4cpp.appender.testlog=RollingFileAppender 11 | log4cpp.appender.testlog.threshold=NOTICE 12 | log4cpp.appender.testlog.maxFileSize=100000 13 | log4cpp.appender.testlog.maxBackupIndex=1 14 | log4cpp.appender.testlog.fileName=./testlog.log 15 | log4cpp.appender.testlog.layout=PatternLayout 16 | log4cpp.appender.testlog.layout.ConversionPattern=%d [%p] %m%n 17 | -------------------------------------------------------------------------------- /logger.cpp: -------------------------------------------------------------------------------- 1 | #include "logger.h" 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | static log4cpp::Category* plogger; 8 | 9 | class LogWriter: public StrWriter { 10 | log4cpp::Priority::PriorityLevel level; 11 | public: 12 | LogWriter(log4cpp::Priority::PriorityLevel level) : StrWriter(' '),level(level) {} 13 | 14 | virtual void put_eoln() { 15 | plogger->log(level,"%s",str().c_str()); 16 | clear(); 17 | } 18 | }; 19 | 20 | namespace logging { 21 | bool initialize_logging(string log_properties) { 22 | try { 23 | log4cpp::PropertyConfigurator::configure(log_properties); 24 | plogger = &(log4cpp::Category::getInstance("testlog")); 25 | atexit (log4cpp::Category::shutdown); 26 | return true; 27 | } catch(log4cpp::ConfigureFailure& err) { 28 | errs("log4cpp")(err.what())(); 29 | return false; 30 | } 31 | 32 | 33 | } 34 | Writer& error = *new LogWriter(log4cpp::Priority::ERROR); 35 | Writer& warn = *new LogWriter(log4cpp::Priority::WARN); 36 | Writer& info = *new LogWriter(log4cpp::Priority::INFO); 37 | Writer& debug = *new LogWriter(log4cpp::Priority::DEBUG); 38 | Writer& notice = *new LogWriter(log4cpp::Priority::NOTICE); 39 | 40 | } -------------------------------------------------------------------------------- /logger.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOGGER_H 2 | #define _LOGGER_H 3 | #include "outstream.h" 4 | namespace logging { 5 | bool initialize_logging(std::string log_properties); 6 | 7 | extern Writer& error; 8 | extern Writer& warn; 9 | extern Writer& notice; 10 | extern Writer& info; 11 | extern Writer& debug; 12 | } 13 | #endif -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # building and testing outstreams 2 | CXXFLAGS = -std=c++11 -g 3 | OUTSTREAM = outstream.o 4 | INSTREAM = instream.o 5 | LDFLAGS = outstream.o 6 | TESTS = testout speedtest testins 7 | all: $(TESTS) conversions reader-lineinfo 8 | 9 | testout: testout.o $(OUTSTREAM) 10 | $(CXX) -o $@ $< $(OUTSTREAM) 11 | 12 | test_out: testout 13 | ./testout > test.tmp 14 | diff test.tmp test.results 15 | 16 | speed: speedtest 17 | ./speedtest 18 | 19 | test_in: testins 20 | ./testins > test.tmp 21 | diff test.tmp read.results 22 | 23 | tests: test_out test_in 24 | 25 | $(INSTREAM): instream.cpp instream.h 26 | 27 | $(OUTSTREAM): outstream.cpp outstream.h 28 | 29 | speedtest: speedtest.o $(OUTSTREAM) 30 | $(CXX) -o $@ $< $(OUTSTREAM) 31 | 32 | testins: testins.o $(INSTREAM) $(OUTSTREAM) 33 | $(CXX) -o $@ $< $(INSTREAM) $(OUTSTREAM) 34 | 35 | conversions: conversions.o $(INSTREAM) $(OUTSTREAM) 36 | $(CXX) -o $@ $< $(INSTREAM) $(OUTSTREAM) 37 | 38 | reader-lineinfo: reader-lineinfo.o $(INSTREAM) $(OUTSTREAM) 39 | $(CXX) -o $@ $< $(INSTREAM) $(OUTSTREAM) 40 | 41 | testlog: testlog.o logger.o $(OUTSTREAM) 42 | $(CXX) -o $@ $< logger.o $(OUTSTREAM) -llog4cpp 43 | 44 | clean: 45 | rm *.o 46 | rm $(TESTS) 47 | -------------------------------------------------------------------------------- /outstream.cpp: -------------------------------------------------------------------------------- 1 | // Lightweight operator() overloading stdio wrapper 2 | // Steve Donovan, (c) 2016 3 | // MIT license 4 | #include "outstream.h" 5 | using namespace std; 6 | extern "C" char *strerror(int); 7 | 8 | namespace stream { 9 | 10 | string Writeable::to_string(const char* fmt) { 11 | StrWriter sw; 12 | write_to(sw,fmt); 13 | return sw.str(); 14 | } 15 | 16 | string Writer::error() { 17 | return strerror(errno); 18 | } 19 | 20 | void Writer::write_char(char ch) { 21 | fputc(ch,out); 22 | } 23 | 24 | void Writer::put_eoln() { 25 | write_char('\n'); 26 | } 27 | 28 | void Writer::write_out(const char *fmt, va_list ap) { 29 | vfprintf(out,fmt,ap); 30 | } 31 | 32 | Writer& Writer::fmt(const char *fmtstr,...) { 33 | va_list ap; 34 | va_start(ap,fmtstr); 35 | write_out(fmtstr,ap); 36 | va_end(ap); 37 | return *this; 38 | } 39 | 40 | void Writer::sep_out() { 41 | if (eoln) { 42 | eoln = false; 43 | } else { 44 | if (sepc != next_sepc) { 45 | next_sepc = sepc; 46 | } 47 | if (next_sepc) { 48 | write_char(next_sepc); 49 | } 50 | next_sepc = sepc; 51 | } 52 | } 53 | 54 | Writer& Writer::formatted_write(const char *def, const char *fmt,...) { 55 | char copy_def[30]; 56 | if (fmt!=nullptr && fmt[1]==0) { // one-character special shortcut format codes 57 | if (def[1] == 's') { // 'quote' or "quote" strings 58 | if (fmt[0] == 'q') fmt = "'%s'"; else 59 | if (fmt[0] == 'Q') fmt = "\"%s\""; 60 | } else { 61 | if (fmt[0] == 'x' || fmt[0] == 'X') { // numbers as hex 62 | const char *in = def; 63 | char *out = copy_def; 64 | while (*(in+1)) *out++ = *in++; 65 | if (*in == 'c') { // special case - characters as bytes 66 | *out++ = 'h'; *out++ = 'h'; 67 | } 68 | *out++ = fmt[0]; 69 | *out = 0; 70 | fmt = copy_def; 71 | } else { 72 | fmt = nullptr; 73 | } 74 | } 75 | } 76 | sep_out(); 77 | va_list ap; 78 | va_start(ap,fmt); 79 | write_out(fmt ? fmt : def,ap); 80 | va_end(ap); 81 | return *this; 82 | } 83 | 84 | Writer::Writer(FILE *out,char sep) 85 | : out(out),sepc(sep),eoln(true),owner(false),old_sepc(0),next_sepc(0) 86 | { 87 | } 88 | 89 | Writer::Writer(const char *file, const char *how) 90 | : out(fopen(file,how)), sepc(0), eoln(true), owner(true),old_sepc(0),next_sepc(0) 91 | { 92 | } 93 | 94 | Writer::Writer(const string& file, const char *how) 95 | : out(fopen(file.c_str(),how)), sepc(0), eoln(true), owner(true),old_sepc(0),next_sepc(0) 96 | { 97 | } 98 | 99 | Writer::Writer(const Writer& w, char sepc) 100 | : out(w.out),sepc(sepc),eoln(w.eoln),owner(false),old_sepc(0),next_sepc(0) 101 | { 102 | } 103 | 104 | Writer::~Writer() { 105 | close(); 106 | } 107 | 108 | void Writer::close() { 109 | if (owner && out != nullptr) { 110 | fclose(out); 111 | } 112 | } 113 | 114 | Writer& Writer::set(FILE *nout) { 115 | close(); 116 | out = nout; 117 | return *this; 118 | } 119 | 120 | Writer& Writer::sep(int ch) { 121 | if (ch == -1) { 122 | sepc = old_sepc; 123 | } else { 124 | old_sepc = sepc; 125 | sepc = ch; 126 | } 127 | return *this; 128 | } 129 | 130 | char Writer::reset_sep(char sep) { 131 | char res = sepc; 132 | sepc = sep; 133 | eoln=true; 134 | return res; 135 | } 136 | 137 | Writer& Writer::restore_sep(char sepr) { 138 | sep(sepr); 139 | return *this; 140 | } 141 | 142 | Writer& Writer::operator() () { 143 | eoln = true; 144 | put_eoln(); 145 | next_sepc = 0; 146 | return *this; 147 | } 148 | 149 | Writer& Writer::flush() { 150 | fflush(out); 151 | return *this; 152 | } 153 | 154 | long Writer::getpos() { 155 | return ftell(out); 156 | } 157 | 158 | void Writer::setpos(long p, char end) { 159 | int whence = SEEK_SET; 160 | if (end == '$') { 161 | whence = SEEK_END; 162 | } else 163 | if (end == '.') { 164 | whence = SEEK_CUR; 165 | } 166 | fseek(out,p,whence); 167 | } 168 | 169 | int Writer::write(void *buf, int bufsize) { 170 | return fwrite(buf, bufsize, 1, out); 171 | } 172 | 173 | Writer outs(stdout,' '); 174 | Writer errs(stderr,' '); 175 | 176 | static char buf[128]; 177 | 178 | StrWriter::StrWriter(char sepr, size_t capacity) : Writer(stderr) { 179 | if (capacity != 0) { 180 | s.reserve(capacity); 181 | } 182 | sep(sepr); 183 | } 184 | 185 | void StrWriter::write_char(char ch) { 186 | s += ch; 187 | } 188 | 189 | void StrWriter::write_out(const char *fmt, va_list ap) { 190 | int nch = vsnprintf(buf,sizeof(buf),fmt,ap); 191 | s.append(buf,nch); 192 | } 193 | 194 | BufWriter::BufWriter(char *buff, int size, char sepr): Writer(stderr), P(buff),P_end(buff+size) { 195 | sep(sepr); 196 | } 197 | 198 | void BufWriter::write_char(char ch) { 199 | *P++ = ch; 200 | } 201 | 202 | void BufWriter::write_out(const char *fmt, va_list ap) { 203 | int nch = vsnprintf(P,sizeof(buf),fmt,ap); 204 | char *next = P + nch; 205 | if (next < P_end) { 206 | P = next; 207 | } else { 208 | out = nullptr; 209 | } 210 | } 211 | 212 | } 213 | 214 | 215 | //// FINIS 216 | 217 | -------------------------------------------------------------------------------- /outstream.h: -------------------------------------------------------------------------------- 1 | // Lightweight operator() overloading stdio wrapper 2 | // Steve Donovan, (c) 2016 3 | // MIT license 4 | 5 | #ifndef __OUTSTREAM_H 6 | #define __OUTSTREAM_H 7 | #include 8 | #ifndef OLD_STD_CPP 9 | #include 10 | #else 11 | #define nullptr NULL 12 | #include 13 | #endif 14 | #include 15 | #include 16 | #define __STDC_FORMAT_MACROS 17 | #include 18 | 19 | namespace stream { 20 | 21 | class Writer; 22 | 23 | /// implement this interface for your type to be printable with outstreams 24 | class Writeable { 25 | public: 26 | virtual void write_to(Writer&,const char*) const = 0; 27 | std::string to_string(const char* fmt = nullptr); 28 | }; 29 | 30 | template 31 | struct Range_ { 32 | It begin; 33 | It end; 34 | Range_(It begin, It end) : begin(begin), end(end) { } 35 | }; 36 | 37 | /// the range() helper for printing explicit begin..end iteration - concept Container 38 | template 39 | Range_ range(It begin, It end) { 40 | return Range_(begin,end); 41 | } 42 | 43 | /// the range() helper for containers that support begin/end iteration 44 | template 45 | Range_ range(const C& c) { 46 | return Range_(c.begin(),c.end()); 47 | } 48 | 49 | /// Writer is a class that overloads operator() for outputing values; 50 | // this implementation is over stdio 51 | class Writer { 52 | protected: 53 | FILE *out; 54 | char sepc; 55 | bool eoln; 56 | bool owner; 57 | char old_sepc; 58 | char next_sepc; 59 | 60 | virtual void write_char(char ch); 61 | virtual void write_out(const char *fmt, va_list ap); 62 | virtual void put_eoln(); 63 | 64 | void sep_out(); 65 | Writer& formatted_write(const char *def, const char *fmt,...); 66 | 67 | public: 68 | /// wrap a stdio stream, with optional field separator 69 | Writer(FILE *out, char sep=0); 70 | 71 | // open a file for writing 72 | // see also open() and close() 73 | Writer(const char *file, const char *how="w"); 74 | Writer(const std::string& file, const char *how="w"); 75 | 76 | Writer(const Writer& w, char sepc); 77 | 78 | virtual ~Writer(); 79 | 80 | // access to the stdio stream 81 | FILE *stream() { return out; } 82 | // this object fails if there's no stream defined 83 | operator bool () { return out != nullptr; } 84 | // provide actual error string 85 | std::string error(); 86 | 87 | void close(); 88 | Writer& set(FILE *nout); 89 | 90 | /// simple wrapper over `fprintf`, same limitations 91 | Writer& fmt(const char *fmtstr,...); 92 | 93 | ///// Field Separator Control ///// 94 | /// set the field separator (default none) 95 | Writer& sep(int ch = 0); 96 | /// reset the field separator 97 | char reset_sep(char sep=0); 98 | Writer& restore_sep(char sepr); 99 | 100 | /// overloads of operator() for various types 101 | Writer& operator() (const char *s, const char *fmt=nullptr) { 102 | return formatted_write("%s",fmt,s); 103 | } 104 | 105 | Writer& operator() (const std::string& s, const char *fmt=nullptr) { 106 | return (*this)(s.c_str(),fmt); 107 | } 108 | 109 | Writer& operator() (int32_t i, const char *fmt=nullptr) { 110 | return formatted_write("%" PRIi32,fmt,i); 111 | } 112 | 113 | Writer& operator() (uint32_t i, const char *fmt=nullptr) { 114 | return formatted_write("%" PRIu32,fmt,i); 115 | } 116 | 117 | Writer& operator() (char ch, const char *fmt=nullptr) { 118 | if (ch == '\n') return (*this)(); 119 | return formatted_write("%c",fmt,ch); 120 | } 121 | 122 | Writer& operator() (uint64_t i, const char *fmt=nullptr) { 123 | return formatted_write("%" PRIu64,fmt,i); 124 | } 125 | 126 | Writer& operator() (int64_t i, const char *fmt=nullptr) { 127 | return formatted_write("%" PRIi64,fmt,i); 128 | } 129 | 130 | Writer& operator() (double x, const char *fmt=nullptr) { 131 | return formatted_write("%g",fmt,x); 132 | } 133 | 134 | Writer& operator() (float x, const char *fmt=nullptr) { 135 | return (*this)((double)x,fmt); 136 | } 137 | 138 | Writer& operator() (void *p, const char *fmt=nullptr) { 139 | return formatted_write("%p",fmt,p); 140 | } 141 | 142 | Writer& operator() (const Writeable& w, const char *fmt=nullptr) { 143 | w.write_to(*this,fmt); 144 | return *this; 145 | } 146 | 147 | Writer& operator() (const Writeable* w, const char *fmt=nullptr) { 148 | w->write_to(*this,fmt); 149 | return *this; 150 | } 151 | 152 | /// empty operator() means 'end of line'; use ('\n') as an equivalent form if this is too terse 153 | Writer& operator() (); 154 | 155 | int write(void *buf, int bufsize); 156 | 157 | /// flush the stream _explicitly_ 158 | virtual Writer& flush(); 159 | virtual long getpos(); 160 | virtual void setpos(long p, char end='^'); 161 | 162 | 163 | /// convienient overload for std::pair - puts a colon between two printable values 164 | template 165 | Writer& operator() (std::pair pp, const char *fmt=nullptr) { 166 | sep_out(); 167 | char osep = reset_sep(); 168 | (*this)(pp.first,fmt)(':')(pp.second,fmt); 169 | return restore_sep(osep); 170 | } 171 | 172 | // overload for range() wrapper representing an iterator sequence 173 | template 174 | Writer& operator() (const Range_& rr, const char *fmt=nullptr, char sepr=' ') { 175 | sep_out(); 176 | char osep = reset_sep(sepr); 177 | It ii = rr.begin; 178 | for(; ii != rr.end; ++ii) { 179 | (*this)(*ii,fmt); 180 | } 181 | return restore_sep(osep); 182 | } 183 | 184 | #ifndef OLD_STD_CPP 185 | template 186 | Writer& operator() (const std::initializer_list& arr, const char *fmt=nullptr, char sepr=' ') { 187 | return (*this)(range(arr),fmt,sepr); 188 | } 189 | #endif 190 | 191 | }; 192 | 193 | extern Writer outs; 194 | extern Writer errs; 195 | 196 | class StrWriter: public Writer { 197 | std::string s; 198 | public: 199 | StrWriter(char sepr=0, size_t capacity = 0); 200 | 201 | std::string str() { return s; } 202 | operator std::string () { return s; } 203 | void clear() { s.clear(); } 204 | 205 | virtual void write_char(char ch); 206 | virtual void write_out(const char *fmt, va_list ap); 207 | virtual Writer& flush() { return *this; } 208 | }; 209 | 210 | class BufWriter: public Writer { 211 | char *P; 212 | char *P_end; 213 | public: 214 | BufWriter(char *buff, int size, char sep=0); 215 | 216 | virtual void write_char(char ch); 217 | virtual void write_out(const char *fmt, va_list ap); 218 | virtual Writer& flush() { *P++ = '\0'; return *this; } 219 | }; 220 | 221 | typedef const char *str_t_; 222 | const str_t_ hex_u="X"; 223 | const str_t_ hex_l="x"; 224 | const str_t_ quote_d="Q"; 225 | const str_t_ quote_s="q"; 226 | const char eol='\n'; 227 | const char eos='\0'; 228 | 229 | } // namespace stream 230 | 231 | 232 | #endif 233 | -------------------------------------------------------------------------------- /print.cpp: -------------------------------------------------------------------------------- 1 | // A simple print function for C++11 2 | #include "print.h" 3 | using namespace std; 4 | using namespace stream; 5 | 6 | int main() 7 | { 8 | int answer = 42; 9 | string who = "world"; 10 | double x = 23; 11 | 12 | print("hello",who,"answer",answer,'\n'); 13 | 14 | print("answer in hex",fmt(answer,hex_u),fmt(x,"%5.2f"))('\n'); 15 | 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /print.h: -------------------------------------------------------------------------------- 1 | // Lightweight operator() overloading stdio wrapper 2 | // Steve Donovan, (c) 2016 3 | // MIT license 4 | #ifndef __OUTSTREAM_PRINT_H 5 | #define __OUTSTREAM_PRINT_H 6 | #include "outstream.h" 7 | namespace stream { 8 | template 9 | struct Fmt_ { 10 | const T& v; 11 | const char *fmt; 12 | Fmt_(const T& v, const char *fmt) : v(v),fmt(fmt) {} 13 | }; 14 | 15 | template 16 | Fmt_ fmt(const T& v, const char *f) { 17 | return Fmt_(v,f); 18 | } 19 | 20 | template 21 | Writer& print(T v) { 22 | return outs(v); 23 | } 24 | 25 | template 26 | Writer& print(const Fmt_& vf) { 27 | return outs(vf.v,vf.fmt); 28 | } 29 | 30 | template 31 | Writer& print(T first, Args... args) { 32 | print(first); 33 | return print(args...); 34 | } 35 | 36 | } 37 | #endif -------------------------------------------------------------------------------- /print.mak: -------------------------------------------------------------------------------- 1 | # print template 2 | STD=c++11 3 | CXXFLAGS = -std=$(STD) -g $(DEFINES) 4 | OUTSTREAM = outstream.o 5 | LDFLAGS = outstream.o 6 | TARGET = print 7 | 8 | $(TARGET): $(TARGET).o $(OUTSTREAM) 9 | $(CXX) -o $@ $< $(OUTSTREAM) 10 | 11 | clean: 12 | rm $(TARGET).o 13 | rm $(TARGET) 14 | -------------------------------------------------------------------------------- /read.results: -------------------------------------------------------------------------------- 1 | +++all lines from file matching some condition 2 | #include "instream.h" 3 | #include 4 | #include 5 | '#include "instream.h"' '#include ' '#include ' '' 6 | +++read variables from file 7 | 1 3.14 'lines' 8 | failed 1 error reading int64 at '.3' 9 | 2 generally better 0 X 10 | +++read input-test.txt contents 11 | 1 3.14 lines 12 | 2 generally better to process individual lines 13 | .3 than token by token 14 | 4 unless you can handle scanf errors cleanly! 15 | 16 | +++read from string with errors 17 | failed 1 error reading int64 at '.3' 18 | 2 generally better 0 X 19 | +++all header files in this directory 20 | 'instream.h' 21 | 'logger.h' 22 | 'outstream.h' 23 | 'print.h' 24 | +++file doesn't exist 25 | bonzo.txt doesn't exist No such file or directory 26 | +++CmdReader result 27 | uname Linux 28 | +++How to execute commands silently and test errors 29 | true is OK 30 | false is not OK 31 | actual retcode 1 32 | -------------------------------------------------------------------------------- /reader-lineinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "instream.h" 2 | #include "outstream.h" 3 | using namespace std; 4 | using namespace stream; 5 | 6 | int main() 7 | { 8 | outs.sep(' '); 9 | Reader rdr("instream.cpp"); 10 | string tok; 11 | while (rdr(tok)) { 12 | if (tok == "Reader::getlineinfo") { 13 | // Reader::LineInfo Reader::getlineinfo (long p) { 14 | string type, mname; 15 | rdr (type)(mname); 16 | outs("type")(type,"q")(",")("name")(mname,"q")(); 17 | break; 18 | } 19 | } 20 | auto pos = rdr.getlineinfo(); 21 | outs("line")(pos.line)("column")(pos.column)(); 22 | 23 | return 0; 24 | } -------------------------------------------------------------------------------- /speedtest.cpp: -------------------------------------------------------------------------------- 1 | #include "outstream.h" 2 | #include 3 | #include 4 | using namespace std; 5 | using namespace stream; 6 | 7 | static uint64_t millisecs() { 8 | struct timespec ts; 9 | clock_gettime(CLOCK_REALTIME,&ts); 10 | return 1000L*ts.tv_sec + ts.tv_nsec/1000000L; 11 | } 12 | 13 | double x1 = 1000,x2 = 1001,x3 = 1003,x4 = 1004,x5 = 1005; 14 | const auto N = 1000000; 15 | 16 | void testwrite_s(string file) { 17 | Writer w(file.c_str()); 18 | w.sep(' '); 19 | 20 | for (int i = 0; i < N; i++) { 21 | w(x1)(x2)(x3)(x4)(x5)(); 22 | } 23 | } 24 | 25 | void testwrite_f(string file) { 26 | FILE *out = fopen(file.c_str(),"w"); 27 | 28 | for (int i = 0; i < N; i++) { 29 | fprintf(out,"%g %g %g %g %g\n",x1,x2,x3,x4,x5); 30 | } 31 | 32 | fclose(out); 33 | } 34 | 35 | void testwrite_i(string file) { 36 | ofstream out(file); 37 | 38 | for (int i = 0; i < N; i++) { 39 | out << x1 << ' ' << x2 << ' ' << x3 40 | << ' ' << x4 << ' ' << x5 << '\n'; 41 | } 42 | 43 | } 44 | 45 | typedef uint64_t U64; 46 | 47 | int exec(const char *cmd) { return system(cmd); } 48 | 49 | int main(int argc, char **argv) 50 | { 51 | U64 start = millisecs(); 52 | testwrite_f("f.dat"); 53 | U64 diff = millisecs() - start; 54 | U64 stdio_ms = diff; 55 | outs("stdio ")(diff)("ms")(); 56 | start = millisecs(); 57 | testwrite_s("s.dat"); 58 | diff = millisecs() - start; 59 | outs("outstreams ")(diff)("ms")(); 60 | start = millisecs(); 61 | testwrite_i("io.dat"); 62 | diff = millisecs() - start; 63 | outs("iostreams ")(diff)("ms")(); 64 | exec("rm f.dat s.dat io.dat"); 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /templ-read.cpp: -------------------------------------------------------------------------------- 1 | /*** 2 | * An example of a Reader which operates on input consisting 3 | * of 'parts' returned from a iterator. You may then split the input 4 | * string how you wish, and still use the reader for conversion. 5 | */ 6 | 7 | #include "instream.h" 8 | #include "outstream.h" 9 | #include 10 | using namespace std; 11 | using namespace stream; 12 | 13 | template 14 | class PartsReader: public Reader { 15 | It start; 16 | It finish; 17 | public: 18 | // note Reader(stderr) - so that the reader's FILE* isn't NULL 19 | PartsReader(It start, It finish) 20 | : Reader(stderr), start(start),finish(finish) { 21 | } 22 | 23 | // only method that needs to be overriden 24 | virtual int read_fmt(const char *fmt, va_list ap) { 25 | if (start == finish) { 26 | return 0; 27 | } 28 | const char *s = *start++; 29 | if (fmt[0] == '%' and fmt[1] == 's') { 30 | strncpy(va_arg(ap, char *),s,256); 31 | return 1; 32 | } else { 33 | return vsscanf(s,fmt,ap); 34 | } 35 | } 36 | 37 | }; 38 | 39 | // a helper function, until C++17 class template type deduction 40 | // arrives 41 | template 42 | PartsReader make_parts_reader(It start, It finish) { 43 | return PartsReader(start,finish); 44 | } 45 | 46 | int main() 47 | { 48 | int n; 49 | double x; 50 | string s; 51 | 52 | auto parts = {"42","5.2","hello"}; 53 | auto rdr = make_parts_reader(parts.begin(),parts.end()); 54 | 55 | rdr (n) (x) (s); 56 | 57 | outs(n)(x)(s)(eol); 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /test.results: -------------------------------------------------------------------------------- 1 | 1.1,2,2.8 2 | *basic tests 3 | hello you 10 3.1412 34343 finis 4 | *iterator range 5 | 10 20 30 6 | 1.2 1.5 2.1 7 | 10 2 5 11 4 8 | FEEEAA and that's all 9 | bork 0XA,0X2,0X5,0XB,0X4 heh 10 | { "hello":42,"dolly":99,"frodo":111 } 11 | *writing to file 12 | *building strings 13 | 42 and 3.4 14 | *custom Point output 15 | (10,100)! 16 | *macro magic 17 | full_name "bonzo the dog" id_number 666 18 | id_number 0X0000000000029A 19 | -------------------------------------------------------------------------------- /testins.cpp: -------------------------------------------------------------------------------- 1 | #include "instream.h" 2 | #include "outstream.h" 3 | #include 4 | using namespace std; 5 | using namespace stream; 6 | 7 | int main(int argc, char **argv) 8 | { 9 | int i; 10 | double x = 1.2; 11 | int res = 0, N = 0; 12 | string s1,s2,s3; 13 | vector lines; 14 | Reader::Error err; 15 | char ch = 'X'; 16 | 17 | outs.sep(' '); 18 | 19 | outs("+++all lines from file matching some condition")(); 20 | Reader inf("instream.cpp"); 21 | while (inf.getline(s3)) { 22 | if (s3.find('#')==0) 23 | outs(s3)(eol); 24 | } 25 | // this will normally happen when inf goes out of scope 26 | inf.close(); 27 | 28 | // alternatively, getlines will append lines using push_back 29 | // and an optional number of lines to grab can be set 30 | inf.open("instream.cpp"); 31 | inf.getlines(lines,4); 32 | inf.close(); 33 | outs(range(lines),quote_s)(eol); 34 | 35 | outs("+++read variables from file")(); 36 | Reader rdr("input-test.txt"); 37 | 38 | rdr(i)(x)(s1); 39 | outs(i)(x)(s1,quote_s)(eol); 40 | 41 | // this line fails since there's no good match for res 42 | // () means grab rest of line and continue... 43 | // (err) captures error state of instream 44 | rdr(i)(s1)(s2)()(res)(ch)(err); 45 | if (err) { 46 | outs("failed")(err.errcode)(err.msg)(eol); 47 | } 48 | outs(i)(s1)(s2)(res)(ch)(eol); 49 | 50 | 51 | outs("+++read input-test.txt contents")(); 52 | string contents; 53 | Reader("input-test.txt").readall(contents); 54 | outs(contents)(eol); 55 | 56 | outs("+++read from string with errors")(); 57 | StrReader sc(contents); 58 | sc()(i)(s1)(s2)()(res)(ch)(err); 59 | if (err) { 60 | outs("failed")(err.errcode)(err.msg)(eol); 61 | } 62 | outs(i)(s1)(s2)(res)(ch)(eol); 63 | 64 | outs("+++all header files in this directory")(); 65 | lines.clear(); 66 | CmdReader("ls *.h").getlines(lines); 67 | for (string L : lines) outs(L,quote_s)(eol); 68 | 69 | outs("+++file doesn't exist")(); 70 | Reader b("bonzo.txt"); 71 | b(res); 72 | if (! b) { 73 | outs("bonzo.txt doesn't exist")(b.error())(eol); 74 | } 75 | 76 | outs("+++CmdReader result")(); 77 | string line = CmdReader("uname").line(); 78 | outs("uname")(line)(eol); 79 | 80 | outs("+++How to execute commands silently and test errors")(); 81 | string result = CmdReader("true",cmd_ok).line(); 82 | if (result == "OK") { 83 | outs("true is OK")(eol); 84 | } 85 | if (CmdReader("false",cmd_ok).line() != "OK") { 86 | outs("false is not OK")(eol); 87 | } 88 | 89 | int retcode; 90 | CmdReader("false",cmd_retcode) (retcode); 91 | outs("actual retcode")(retcode)(eol); 92 | 93 | /* 94 | 95 | s = "one two 30"; 96 | StrReader ss(s); 97 | ss(s1)(s2)(res); 98 | 99 | printf("'%s' '%s' '%s' %d\n",C(s1),C(s2),C(s3),res); 100 | */ 101 | return 0; 102 | } 103 | -------------------------------------------------------------------------------- /testlog.cpp: -------------------------------------------------------------------------------- 1 | #include "logger.h" 2 | using namespace std; 3 | using namespace logging; 4 | 5 | int main(int argc, char **argv) 6 | { 7 | string log_config = "log4cpp.properties"; 8 | initialize_logging(log_config); 9 | 10 | error("hello")(42)(); 11 | warn("this is a warning")(); 12 | debug("won't appear in default setting")(); 13 | 14 | return 0; 15 | } -------------------------------------------------------------------------------- /testout.cpp: -------------------------------------------------------------------------------- 1 | #include "outstream.h" 2 | #include 3 | using namespace std; 4 | using namespace stream; 5 | 6 | class Point: public Writeable { 7 | int X; 8 | int Y; 9 | public: 10 | Point(int X, int Y) : X(X),Y(Y) { } 11 | 12 | virtual void write_to(Writer& out, const char *) const { 13 | out.fmt("(%d,%d)",X,Y); 14 | } 15 | }; 16 | 17 | void custom_type_point() { 18 | outs("*custom Point output")(); 19 | Point P(10,100); 20 | outs(P)('!')('\n'); 21 | } 22 | 23 | void writing_iterator_range() { 24 | outs("*iterator range")(); 25 | // the convenience of initializer lists 26 | /// empty () is equivalent to ('\n') 27 | outs({10,20,30})(); 28 | 29 | float arr[] {1.2,1.5,2.1}; 30 | vector vi {10,2,5,11,4}; 31 | outs(range(arr,arr+3))(); 32 | outs(range(vi))(); 33 | 34 | // can specify the FORMAT and the SEPARATOR for a range 35 | string s = "\xFE\xEE\xAA"; 36 | outs(range(s),"X",0)("and that's all")(); 37 | 38 | 39 | outs("bork")(range(vi),"%#X",',')("heh")(); 40 | 41 | // write out a quick little burst of json 42 | // The hex_u format (double-quoted) only applies to text fields 43 | // so can be safely passed for all times. q means single-quoted 44 | outs('{')({ 45 | make_pair("hello",42), 46 | make_pair("dolly",99), 47 | make_pair("frodo",111) 48 | },quote_d,',')('}')(); 49 | 50 | } 51 | 52 | int writing_to_file() { 53 | outs("*writing to file")(); 54 | double x = 1.1, y = 2.0, z = 2.8; 55 | Writer("test.txt").sep(',')(x)(y)(z)(); 56 | return system("cat test.txt"); 57 | } 58 | 59 | void building_strings() { 60 | outs("*building strings")(); 61 | StrWriter sw(' '); 62 | sw(42)("and")(3.4); 63 | 64 | outs(sw.str())(); 65 | } 66 | 67 | void macro_magic() { 68 | outs("*macro magic")(); 69 | #define VA(var) (#var)(var,"Q") 70 | #define VX64(var) (#var)(var,"%#016" PRIX64) 71 | string full_name = "bonzo the dog"; 72 | uint64_t id_number = 666; 73 | 74 | outs VA(full_name) VA(id_number) (); 75 | 76 | outs VX64(id_number) (); 77 | 78 | #undef VA 79 | #undef VX64 80 | } 81 | 82 | void outstream_tests() { 83 | outs("*basic tests")(); 84 | // not initially true 85 | outs.sep(' '); 86 | 87 | // outstreams are good for quick dumps of variable values 88 | int i = 10; 89 | string s = "hello you"; 90 | double x = 3.1412; 91 | long n = 34343; 92 | 93 | outs(s)(i)(x)(n)("finis")('\n'); 94 | 95 | writing_iterator_range(); 96 | 97 | writing_to_file(); 98 | 99 | building_strings(); 100 | 101 | custom_type_point(); 102 | 103 | macro_magic(); 104 | 105 | } 106 | 107 | 108 | int main(int argc, char **argv) 109 | { 110 | outstream_tests(); 111 | return 0; 112 | } 113 | --------------------------------------------------------------------------------