SoDaFormat-1.0.0-main:f160581  1.0.0
Format.cxx
Go to the documentation of this file.
1 #include "Format.hxx"
2 #include <iostream>
3 #include <sstream>
4 #include <iomanip>
5 #include <cmath>
6 #include <ctype.h>
7 #include <regex>
8 
9 /*
10 BSD 2-Clause License
11 
12 Copyright (c) 2020, Matt Reilly - kb1vc
13 All rights reserved.
14 
15 Redistribution and use in source and binary forms, with or without
16 modification, are permitted provided that the following conditions are met:
17 
18 1. Redistributions of source code must retain the above copyright notice, this
19  list of conditions and the following disclaimer.
20 
21 2. Redistributions in binary form must reproduce the above copyright notice,
22  this list of conditions and the following disclaimer in the documentation
23  and/or other materials provided with the distribution.
24 
25 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
29 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36 
37 
38 namespace SoDa {
39  char Format::separator = '.';
40 
41  Format::Format(const std::string & fmt_string) :
42  orig_fmt_string(fmt_string), cur_arg_number(0) {
43 
44  // turn all %% pairs into % and mark the position as *not* being the start
45  // of an insertion marker.
46  initialScan();
47  }
48 
49 
50  Format & Format::addI(int v, unsigned int w) {
51  std::stringstream ss;
52  if(w != 0) {
53  ss << std::setw(w);
54  }
55  ss << v;
56  insertField(ss.str());
57  return * this;
58  }
59 
60  Format & Format::addU(unsigned int v, unsigned int w) {
61  std::stringstream ss;
62  if(w != 0) {
63  ss << std::setw(w);
64  }
65  ss << v;
66  insertField(ss.str());
67  return *this;
68  }
69 
70  Format & Format::addF(double v, char fmt, unsigned int width, unsigned int frac_precision) {
71  std::stringstream ss;
72  switch (fmt) {
73  case 'f':
74  // fixed floating point format
75  if(width) ss << std::setw(width);
76  ss << std::fixed << std::setprecision(frac_precision) << v;
77  break;
78  case 's':
79  // scientific (who cares what the exponent is? format)
80  if(width) ss << std::setw(width);
81  ss << std::scientific << std::setprecision(frac_precision) << v;
82  break;
83  case 'g':
84  // general (who cares what the exponent is format, or how this looks)
85  ss.unsetf(std::ios::fixed | std::ios::scientific);
86  if(width) ss << std::setw(width);
87  ss << std::setprecision(frac_precision) << v;
88  break;
89  case 'e':
90  // now this is a tough one.
91  // the object is to print this number as xxx.fffeN where N is a multiple of 3.
92  // (engineering notation). It is beyond me how C and C++ have continued to print
93  // crap floating point formats that are only suited to imperial units (black and
94  // white TV) or astronomers. It is 2021 -- time to use the metric system, even
95  // in backwaters that still cling to the 16th century.
96 
97  {
98  // get the sign
99  bool is_neg = (v < 0.0);
100  double av = fabs(v);
101  // first find the log base 10.
102  double log_10 = log10(av);
103  // now remember the sign
104  double exp_sign_cor = (log_10 < 0.0) ? -1.0 : 1.0;
105  // truncate it toward 0
106  log_10 = floor(fabs(log_10));
107  double scale = pow(10, -1.0 * exp_sign_cor * log_10);
108  // scale the "mantissa" part
109  double mant = av * scale;
110  // now get the integer version of the exponent
111  int ilog_10 = rint(log_10);
112  // now we want to adjust ilog_10 until it is a multiple of 3
113  // we're always going to *decrease* the exponent, so we should
114  // always multiply the mantissa by 10
115  ilog_10 = ilog_10 * ((exp_sign_cor < 0.0) ? -1 : 1);
116 
117  while(((ilog_10 % 3) != 0) || (mant < 1.0)) {
118  mant = 10.0 * mant;
119  ilog_10 -= 1;
120  }
121  // now we have a mantissa in the range 1 to 999.99...
122  int whole = rint(floor(mant));
123  int frac = rint((mant - floor(mant)) * pow(10.0, frac_precision));
124  ss << (is_neg ? '-' : ' ') << std::setw(3) << whole << separator << std::setfill('0') << std::setw(frac_precision) << frac << 'e' << ilog_10 ;
125  }
126  }
127 
128  insertField(ss.str());
129  return *this;
130  }
131 
132  Format & Format::addS(const std::string & v, unsigned int width) {
133  std::stringstream ss;
134  if(width > 0) {
135  ss << std::setw(width);
136  }
137  ss << v;
138  insertField(ss.str());
139  return *this;
140  }
141 
142  Format & Format::addC(char c) {
143  std::stringstream ss;
144  ss << c;
145  insertField(ss.str());
146  return *this;
147  }
148 
149  void Format::insertField(const std::string & s) {
150  // scan the format string to find all the instances of "%n" where n is the current argument number.
151  if(cur_arg_number > max_field_num) {
152  std::stringstream ss;
153  ss << "Too many arguments (" << (cur_arg_number + 1) << ") passed to Format.";
154  throw BadFormat(ss.str(), *this);
155  }
156 
157  // build a regular expression from the pattern number
158  std::stringstream fpat;
159  fpat << "%" << cur_arg_number << "\\D";
160  std::smatch m;
161  std::regex re(fpat.str());
162  int pattern_length = fpat.str().size() - 2;
163 
164  std::list<int> match_positions;
165  for(auto sri = std::sregex_iterator(fmt_string.begin(), fmt_string.end(), re);
166  sri != std::sregex_iterator();
167  ++sri) {
168  std::smatch m = *sri;
169  int fpos = m.position();
170 
171  if(find(escape_positions.begin(), escape_positions.end(), fpos) == escape_positions.end()) {
172  // insert the field.
173  match_positions.push_front(fpos);
174  }
175  }
176 
177  for(auto p : match_positions) {
178  fmt_string.replace(p, pattern_length, s);
179  }
180  cur_arg_number++;
181  }
182 
183  void Format::initialScan() {
184  int i, j;
185  max_field_num = -1;
186  for(i = 0, j = 0; i < (orig_fmt_string.size() - 1); i++, j++) {
187  if(orig_fmt_string[i] == '%') {
188  if(orig_fmt_string[i+1] == '%') {
189  escape_positions.push_back(fmt_string.size()); // This character in the fmt string is a literal '%'
190  fmt_string.push_back('%');
191  i++; // bump I an extra bit.
192  }
193  else if(isdigit(orig_fmt_string[i+1])) {
194  int fnum = atoi(orig_fmt_string.substr(i+1).c_str());
195  if(fnum > max_field_num) max_field_num = fnum;
196  fmt_string.push_back('%');
197  // fmt_string.push_back(orig_fmt_string[i+1]);
198  }
199  }
200  else {
201  fmt_string.push_back(orig_fmt_string[i]);
202  }
203  }
204  fmt_string.push_back(orig_fmt_string[orig_fmt_string.size() - 1]);
205  }
206 
208  fmt_string = orig_fmt_string;
209  cur_arg_number = 0;
210  return *this;
211  }
212 
213  const std::string & Format::str(bool check_for_filled_out) const {
214  return fmt_string;
215  }
216 }
217 
218 std::ostream & operator<<(std::ostream & os, const SoDa::Format & f) {
219  os << f.str(true);
220  return os;
221 }
std::ostream & operator<<(std::ostream &os, const SoDa::Format &f)
Definition: Format.cxx:218
Format & reset()
reset the format string to its original value, with all the placeholders restored.
Definition: Format.cxx:207
Format & addI(int v, unsigned int width=0)
insert a signed integer into the format string
Definition: Format.cxx:50
Format(const std::string &fmt_string)
create a format object with placeholders and all that stuff.
Definition: Format.cxx:41
Format & addU(unsigned int v, unsigned int width=0)
insert an unsigned integer into the format string
Definition: Format.cxx:60
Format & addF(double v, char fmt='f', unsigned int width=0, unsigned int frac_precision=3)
insert a float or double into the format string
Definition: Format.cxx:70
Not much else is spelled that way, so we're probably not going to have too many collisions with code ...
Definition: Format.cxx:38
Format & addC(char v)
insert a character into the format string
Definition: Format.cxx:142
Format & addS(const std::string &v, unsigned int width=0)
insert a string into the format string
Definition: Format.cxx:132
const std::string & str(bool check_for_filled_out=false) const
provide a string representing the current state of the format string with all placeholders "filled in...
Definition: Format.cxx:213
A format object that may be "filled in" with integer, float, double, string, or character values.
Definition: Format.hxx:269
static char separator
the radix separator character.
Definition: Format.hxx:464