cpp

Coverage Report

Created: 2022-07-20 01:16

/home/uke/oil/mycpp/mylib_leaky.cc
Line
Count
Source (jump to first uncovered line)
1
// mylib_leaky.cc
2
3
#include "mylib_leaky.h"
4
5
#include <errno.h>
6
#include <unistd.h>  // isatty
7
8
#include <cassert>
9
#include <cstdio>
10
#include <exception>  // std::exception
11
12
Str* kEmptyString = new Str("", 0);
13
14
// Translation of Python's print().
15
9
void print(Str* s) {
16
9
  mylib::Str0 s0(s);
17
9
  fputs(s0.Get(), stdout);
18
9
  fputs("\n", stdout);
19
9
}
20
21
// Like print(..., file=sys.stderr), but Python code explicitly calls it.
22
0
void println_stderr(Str* s) {
23
0
  mylib::Str0 s0(s);
24
0
  fputs(s0.Get(), stderr);
25
0
  fputs("\n", stderr);
26
0
}
27
28
// NOTE:
29
// - Oil interpreter code only uses the very common case of len(old) == 1.
30
//   - But we probably want to expose this more general function to OIL USERS.
31
//   - We could have a special case for this like CPython.  It detects if
32
//   len(old) == len(new) as well.
33
// - Python replace() has a third 'count' argument we're not using.
34
36
Str* Str::replace(Str* old, Str* new_str) {
35
  // log("replacing %s with %s", old_data, new_str->data_);
36
37
36
  const char* old_data = old->data_;
38
36
  int old_len = old->len_;
39
36
  const char* last_possible = data_ + len_ - old_len;
40
41
36
  const char* p_this = data_;  // advances through 'this'
42
43
  // First pass: Calculate number of replacements, and hence new length
44
36
  int replace_count = 0;
45
206
  while (p_this <= last_possible) {
46
170
    if (memcmp(p_this, old_data, old_len) == 0) {  // equal
47
60
      replace_count++;
48
60
      p_this += old_len;
49
110
    } else {
50
110
      p_this++;
51
110
    }
52
170
  }
53
54
  // log("replacements %d", replace_count);
55
56
36
  if (replace_count == 0) {
57
2
    return this;  // Reuse the string if there were no replacements
58
2
  }
59
60
34
  int result_len =
61
34
      this->len_ - (replace_count * old_len) + (replace_count * new_str->len_);
62
63
34
  char* result = static_cast<char*>(malloc(result_len + 1));  // +1 for NUL
64
65
34
  const char* new_data = new_str->data_;
66
34
  const size_t new_len = new_str->len_;
67
68
  // Second pass: Copy pieces into 'result'
69
34
  p_this = data_;           // back to beginning
70
34
  char* p_result = result;  // advances through 'result'
71
72
196
  while (p_this <= last_possible) {
73
    // Note: would be more efficient if we remembered the match positions
74
162
    if (memcmp(p_this, old_data, old_len) == 0) {  // equal
75
60
      memcpy(p_result, new_data, new_len);         // Copy from new_str
76
60
      p_result += new_len;
77
60
      p_this += old_len;
78
102
    } else {  // copy 1 byte
79
102
      *p_result = *p_this;
80
102
      p_result++;
81
102
      p_this++;
82
102
    }
83
162
  }
84
34
  memcpy(p_result, p_this, data_ + len_ - p_this);  // last part of string
85
34
  result[result_len] = '\0';                        // NUL terminate
86
87
34
  return new Str(result, result_len);
88
36
}
_ZN3Str7replaceEPS_S0_
Line
Count
Source
34
18
Str* Str::replace(Str* old, Str* new_str) {
35
  // log("replacing %s with %s", old_data, new_str->data_);
36
37
18
  const char* old_data = old->data_;
38
18
  int old_len = old->len_;
39
18
  const char* last_possible = data_ + len_ - old_len;
40
41
18
  const char* p_this = data_;  // advances through 'this'
42
43
  // First pass: Calculate number of replacements, and hence new length
44
18
  int replace_count = 0;
45
103
  while (p_this <= last_possible) {
46
85
    if (memcmp(p_this, old_data, old_len) == 0) {  // equal
47
30
      replace_count++;
48
30
      p_this += old_len;
49
55
    } else {
50
55
      p_this++;
51
55
    }
52
85
  }
53
54
  // log("replacements %d", replace_count);
55
56
18
  if (replace_count == 0) {
57
1
    return this;  // Reuse the string if there were no replacements
58
1
  }
59
60
17
  int result_len =
61
17
      this->len_ - (replace_count * old_len) + (replace_count * new_str->len_);
62
63
17
  char* result = static_cast<char*>(malloc(result_len + 1));  // +1 for NUL
64
65
17
  const char* new_data = new_str->data_;
66
17
  const size_t new_len = new_str->len_;
67
68
  // Second pass: Copy pieces into 'result'
69
17
  p_this = data_;           // back to beginning
70
17
  char* p_result = result;  // advances through 'result'
71
72
98
  while (p_this <= last_possible) {
73
    // Note: would be more efficient if we remembered the match positions
74
81
    if (memcmp(p_this, old_data, old_len) == 0) {  // equal
75
30
      memcpy(p_result, new_data, new_len);         // Copy from new_str
76
30
      p_result += new_len;
77
30
      p_this += old_len;
78
51
    } else {  // copy 1 byte
79
51
      *p_result = *p_this;
80
51
      p_result++;
81
51
      p_this++;
82
51
    }
83
81
  }
84
17
  memcpy(p_result, p_this, data_ + len_ - p_this);  // last part of string
85
17
  result[result_len] = '\0';                        // NUL terminate
86
87
17
  return new Str(result, result_len);
88
18
}
_ZN3Str7replaceEPS_S0_
Line
Count
Source
34
18
Str* Str::replace(Str* old, Str* new_str) {
35
  // log("replacing %s with %s", old_data, new_str->data_);
36
37
18
  const char* old_data = old->data_;
38
18
  int old_len = old->len_;
39
18
  const char* last_possible = data_ + len_ - old_len;
40
41
18
  const char* p_this = data_;  // advances through 'this'
42
43
  // First pass: Calculate number of replacements, and hence new length
44
18
  int replace_count = 0;
45
103
  while (p_this <= last_possible) {
46
85
    if (memcmp(p_this, old_data, old_len) == 0) {  // equal
47
30
      replace_count++;
48
30
      p_this += old_len;
49
55
    } else {
50
55
      p_this++;
51
55
    }
52
85
  }
53
54
  // log("replacements %d", replace_count);
55
56
18
  if (replace_count == 0) {
57
1
    return this;  // Reuse the string if there were no replacements
58
1
  }
59
60
17
  int result_len =
61
17
      this->len_ - (replace_count * old_len) + (replace_count * new_str->len_);
62
63
17
  char* result = static_cast<char*>(malloc(result_len + 1));  // +1 for NUL
64
65
17
  const char* new_data = new_str->data_;
66
17
  const size_t new_len = new_str->len_;
67
68
  // Second pass: Copy pieces into 'result'
69
17
  p_this = data_;           // back to beginning
70
17
  char* p_result = result;  // advances through 'result'
71
72
98
  while (p_this <= last_possible) {
73
    // Note: would be more efficient if we remembered the match positions
74
81
    if (memcmp(p_this, old_data, old_len) == 0) {  // equal
75
30
      memcpy(p_result, new_data, new_len);         // Copy from new_str
76
30
      p_result += new_len;
77
30
      p_this += old_len;
78
51
    } else {  // copy 1 byte
79
51
      *p_result = *p_this;
80
51
      p_result++;
81
51
      p_this++;
82
51
    }
83
81
  }
84
17
  memcpy(p_result, p_this, data_ + len_ - p_this);  // last part of string
85
17
  result[result_len] = '\0';                        // NUL terminate
86
87
17
  return new Str(result, result_len);
88
18
}
89
90
0
Str* Str::ljust(int width, Str* fillchar) {
91
0
  assert(len(fillchar) == 1);
92
93
0
  int num_fill = width - len_;
94
0
  if (num_fill < 0) {
95
0
    return this;
96
0
  } else {
97
0
    char* buf = static_cast<char*>(malloc(width + 1));
98
0
    char c = fillchar->data_[0];
99
0
    memcpy(buf, data_, len_);
100
0
    for (int i = len_; i < width; ++i) {
101
0
      buf[i] = c;
102
0
    }
103
0
    buf[width] = '\0';
104
0
    return new Str(buf, width);
105
0
  }
106
0
}
Unexecuted instantiation: _ZN3Str5ljustEiPS_
Unexecuted instantiation: _ZN3Str5ljustEiPS_
107
108
0
Str* Str::rjust(int width, Str* fillchar) {
109
0
  assert(len(fillchar) == 1);
110
111
0
  int num_fill = width - len_;
112
0
  if (num_fill < 0) {
113
0
    return this;
114
0
  } else {
115
0
    char* buf = static_cast<char*>(malloc(width + 1));
116
0
    char c = fillchar->data_[0];
117
0
    for (int i = 0; i < num_fill; ++i) {
118
0
      buf[i] = c;
119
0
    }
120
0
    memcpy(buf + num_fill, data_, len_);
121
0
    buf[width] = '\0';
122
0
    return new Str(buf, width);
123
0
  }
124
0
}
Unexecuted instantiation: _ZN3Str5rjustEiPS_
Unexecuted instantiation: _ZN3Str5rjustEiPS_
125
126
0
List<Str*>* Str::split(Str* sep) {
127
0
  assert(sep->len_ == 1);  // we can only split one char
128
0
  char sep_char = sep->data_[0];
129
130
0
  if (len_ == 0) {
131
    // weird case consistent with Python: ''.split(':') == ['']
132
0
    return new List<Str*>({kEmptyString});
133
0
  }
134
135
  // log("--- split()");
136
  // log("data [%s]", data_);
137
138
0
  auto result = new List<Str*>({});
139
140
0
  int n = len_;
141
0
  const char* pos = data_;
142
0
  const char* end = data_ + len_;
143
144
  // log("pos %p", pos);
145
0
  while (true) {
146
    // log("n %d, pos %p", n, pos);
147
148
0
    const char* new_pos = static_cast<const char*>(memchr(pos, sep_char, n));
149
0
    if (new_pos == nullptr) {
150
0
      result->append(new Str(pos, end - pos));  // rest of the string
151
0
      break;
152
0
    }
153
0
    int new_len = new_pos - pos;
154
155
0
    result->append(new Str(pos, new_len));
156
0
    n -= new_len + 1;
157
0
    pos = new_pos + 1;
158
0
    if (pos >= end) {  // separator was at end of string
159
0
      result->append(kEmptyString);
160
0
      break;
161
0
    }
162
0
  }
163
164
0
  return result;
165
0
}
166
167
0
List<Str*>* Str::splitlines(bool keep) {
168
0
  assert(keep == true);
169
0
  return nullptr;
170
0
}
171
172
0
Str* Str::join(List<Str*>* items) {
173
0
  int len = 0;
174
0
  const std::vector<Str*>& v = items->v_;
175
0
  int num_parts = v.size();
176
0
  if (num_parts == 0) {  // " ".join([]) == ""
177
0
    return kEmptyString;
178
0
  }
179
0
  for (int i = 0; i < num_parts; ++i) {
180
0
    len += v[i]->len_;
181
0
  }
182
  // add length of all the separators
183
0
  len += len_ * (num_parts - 1);
184
185
  // log("len: %d", len);
186
  // log("v.size(): %d", v.size());
187
188
0
  char* result = static_cast<char*>(malloc(len + 1));
189
0
  char* p_result = result;  // advances through
190
191
0
  for (int i = 0; i < num_parts; ++i) {
192
    // log("i %d", i);
193
0
    if (i != 0 && len_) {             // optimize common case of ''.join()
194
0
      memcpy(p_result, data_, len_);  // copy the separator
195
0
      p_result += len_;
196
      // log("len_ %d", len_);
197
0
    }
198
199
0
    int n = v[i]->len_;
200
    // log("n: %d", n);
201
0
    memcpy(p_result, v[i]->data_, n);  // copy the list item
202
0
    p_result += n;
203
0
  }
204
205
0
  result[len] = '\0';  // NUL terminator
206
207
0
  return new Str(result, len);
208
0
}
Unexecuted instantiation: _ZN3Str4joinEP4ListIPS_E
Unexecuted instantiation: _ZN3Str4joinEP4ListIPS_E
209
210
0
Str* Str::upper() {
211
0
  Str* result = mylib::AllocStr(len_);
212
0
  char* buffer = result->data();
213
0
  for (int char_index = 0; char_index < len_; ++char_index) {
214
0
    buffer[char_index] = toupper(data_[char_index]);
215
0
  }
216
0
  return result;
217
0
}
218
219
0
Str* Str::lower() {
220
0
  Str* result = mylib::AllocStr(len_);
221
0
  char* buffer = result->data();
222
0
  for (int char_index = 0; char_index < len_; ++char_index) {
223
0
    buffer[char_index] = tolower(data_[char_index]);
224
0
  }
225
0
  return result;
226
0
}
227
228
// Get a string with one character
229
294
Str* StrIter::Value() {
230
294
  char* buf = static_cast<char*>(malloc(2));
231
294
  buf[0] = s_->data_[i_];
232
294
  buf[1] = '\0';
233
294
  return new Str(buf, 1);
234
294
}
_ZN7StrIter5ValueEv
Line
Count
Source
229
147
Str* StrIter::Value() {
230
147
  char* buf = static_cast<char*>(malloc(2));
231
147
  buf[0] = s_->data_[i_];
232
147
  buf[1] = '\0';
233
147
  return new Str(buf, 1);
234
147
}
_ZN7StrIter5ValueEv
Line
Count
Source
229
147
Str* StrIter::Value() {
230
147
  char* buf = static_cast<char*>(malloc(2));
231
147
  buf[0] = s_->data_[i_];
232
147
  buf[1] = '\0';
233
147
  return new Str(buf, 1);
234
147
}
235
236
namespace mylib {
237
238
4
Tuple2<Str*, Str*> split_once(Str* s, Str* delim) {
239
4
  assert(delim->len_ == 1);
240
241
0
  const char* start = s->data_;
242
4
  char c = delim->data_[0];
243
4
  int len = s->len_;
244
245
4
  const char* p = static_cast<const char*>(memchr(start, c, len));
246
247
4
  if (p) {
248
    // NOTE: Using SHARED SLICES, not memcpy() like some other functions.
249
2
    int len1 = p - start;
250
2
    Str* first = new Str(start, len1);
251
2
    Str* second = new Str(p + 1, len - len1 - 1);
252
2
    return Tuple2<Str*, Str*>(first, second);
253
2
  } else {
254
2
    return Tuple2<Str*, Str*>(s, nullptr);
255
2
  }
256
4
}
257
258
//
259
// LineReader
260
//
261
262
LineReader* gStdin;
263
264
267
Str* CFileLineReader::readline() {
265
267
  char* line = nullptr;
266
267
  size_t allocated_size = 0;  // unused
267
268
267
  errno = 0;  // must be reset because we check it below!
269
267
  ssize_t len = getline(&line, &allocated_size, f_);
270
267
  if (len < 0) {
271
    // log("getline() result: %d", len);
272
1
    if (errno != 0) {
273
      // Unexpected error
274
0
      log("getline() error: %s", strerror(errno));
275
0
      throw new AssertionError(errno);
276
0
    }
277
    // Expected EOF
278
1
    return kEmptyString;
279
1
  }
280
  // log("len = %d", len);
281
282
  // Note: it's NUL terminated
283
266
  return new Str(line, len);
284
267
}
285
286
// problem: most Str methods like index() and slice() COPY so they have a
287
// NUL terminator.
288
// log("%s") falls back on sprintf, so it expects a NUL terminator.
289
// It would be easier for us to just share.
290
0
Str* BufLineReader::readline() {
291
0
  const char* end = s_->data_ + s_->len_;
292
0
  if (pos_ == end) {
293
0
    return kEmptyString;
294
0
  }
295
296
0
  const char* orig_pos = pos_;
297
0
  const char* new_pos = strchr(pos_, '\n');
298
  // log("pos_ = %s", pos_);
299
0
  int len;
300
0
  if (new_pos) {
301
0
    len = new_pos - pos_ + 1;  // past newline char
302
0
    pos_ = new_pos + 1;
303
0
  } else {  // leftover line
304
0
    len = end - pos_;
305
0
    pos_ = end;
306
0
  }
307
308
0
  char* result = static_cast<char*>(malloc(len + 1));
309
0
  memcpy(result, orig_pos, len);  // copy the list item
310
0
  result[len] = '\0';
311
0
  Str* line = new Str(result, len);
312
313
  // Easier way:
314
  // Str* line = new Str(pos_, new_pos - pos_);
315
0
  return line;
316
0
}
Unexecuted instantiation: _ZN5mylib13BufLineReader8readlineEv
Unexecuted instantiation: _ZN5mylib13BufLineReader8readlineEv
317
318
//
319
// Writer
320
//
321
322
Writer* gStdout;
323
Writer* gStderr;
324
325
0
void BufWriter::write(Str* s) {
326
0
  int orig_len = len_;
327
0
  len_ += s->len_;
328
329
  // BUG: This is quadratic!
330
331
  // data_ is nullptr at first
332
0
  data_ = static_cast<char*>(realloc(data_, len_ + 1));
333
334
  // Append to the end
335
0
  memcpy(data_ + orig_len, s->data_, s->len_);
336
0
  data_[len_] = '\0';
337
0
}
338
339
430
void BufWriter::write_const(const char* s, int len) {
340
430
  int orig_len = len_;
341
430
  len_ += len;
342
  // data_ is nullptr at first
343
430
  data_ = static_cast<char*>(realloc(data_, len_ + 1));
344
345
  // Append to the end
346
430
  memcpy(data_ + orig_len, s, len);
347
430
  data_[len_] = '\0';
348
430
}
349
350
0
void BufWriter::format_s(Str* s) {
351
0
  this->write(s);
352
0
}
353
354
89
void BufWriter::format_d(int i) {
355
  // extend to the maximum size
356
89
  data_ = static_cast<char*>(realloc(data_, len_ + kIntBufSize));
357
89
  int len = snprintf(data_ + len_, kIntBufSize, "%d", i);
358
  // but record only the number of bytes written
359
89
  len_ += len;
360
89
}
361
362
0
void BufWriter::format_o(int i) {
363
0
  NotImplemented();
364
0
}
365
366
// repr() calls this too
367
//
368
// TODO: This could be replaced with QSN?  The upper bound is greater there
369
// because of \u{}.
370
0
void BufWriter::format_r(Str* s) {
371
  // Worst case: \0 becomes 4 bytes as '\\x00', and then two quote bytes.
372
0
  int upper_bound = s->len_ * 4 + 2;
373
374
  // Extend the buffer
375
0
  data_ = static_cast<char*>(realloc(data_, len_ + upper_bound + 1));
376
377
0
  char quote = '\'';
378
0
  if (memchr(s->data_, '\'', s->len_) && !memchr(s->data_, '"', s->len_)) {
379
0
    quote = '"';
380
0
  }
381
0
  char* p = data_ + len_;  // end of valid data
382
383
  // From PyString_Repr()
384
0
  *p++ = quote;
385
0
  for (int i = 0; i < s->len_; ++i) {
386
0
    char c = s->data_[i];
387
0
    if (c == quote || c == '\\') {
388
0
      *p++ = '\\';
389
0
      *p++ = c;
390
0
    } else if (c == '\t') {
391
0
      *p++ = '\\';
392
0
      *p++ = 't';
393
0
    } else if (c == '\n') {
394
0
      *p++ = '\\';
395
0
      *p++ = 'n';
396
0
    } else if (c == '\r') {
397
0
      *p++ = '\\';
398
0
      *p++ = 'r';
399
0
    } else if (c < ' ' || c >= 0x7f) {
400
0
      sprintf(p, "\\x%02x", c & 0xff);
401
0
      p += 4;
402
0
    } else {
403
0
      *p++ = c;
404
0
    }
405
0
  }
406
0
  *p++ = quote;
407
0
  *p = '\0';
408
409
0
  len_ = p - data_;
410
  // Shrink the buffer.  This is valid usage and GNU libc says it can actually
411
  // release.
412
0
  data_ = static_cast<char*>(realloc(data_, len_ + 1));
413
0
}
414
415
// void BufWriter::format_s(const char* s) {
416
//  this->write_const(s, strlen(s));
417
//}
418
419
0
void CFileWriter::write(Str* s) {
420
  // note: throwing away the return value
421
0
  fwrite(s->data_, s->len_, 1, f_);
422
423
  // Necessary for 'echo hi > x' to work.  Otherwise it gets buffered so the
424
  // write() happens AFTER ~ctx_Redirect().
425
  //
426
  // TODO: use write() directly to avoid buffering problems?  But we also want
427
  // fast command sub like ${.echo x}
428
0
  fflush(f_);
429
0
}
430
431
0
void CFileWriter::flush() {
432
0
  fflush(f_);
433
0
}
434
435
1
bool CFileWriter::isatty() {
436
1
  return ::isatty(fileno(f_));
437
1
}
438
439
}  // namespace mylib
440
441
//
442
// Free functions
443
//
444
445
0
Str* repr(Str* s) {
446
0
  mylib::BufWriter f;
447
0
  f.format_r(s);
448
0
  return f.getvalue();
449
0
}
450
451
0
Str* str_concat(Str* a, Str* b) {
452
0
  int new_len = a->len_ + b->len_;
453
0
  char* buf = static_cast<char*>(malloc(new_len + 1));
454
455
0
  int len_a = a->len_;
456
0
  memcpy(buf, a->data_, len_a);
457
0
  memcpy(buf + len_a, b->data_, b->len_);
458
0
  buf[new_len] = '\0';
459
460
0
  return new Str(buf, new_len);
461
0
}
Unexecuted instantiation: _Z10str_concatP3StrS0_
Unexecuted instantiation: _Z10str_concatP3StrS0_
462
463
// for os_path.join()
464
0
Str* str_concat3(Str* a, Str* b, Str* c) {
465
0
  int new_len = a->len_ + b->len_ + c->len_;
466
0
  char* buf = static_cast<char*>(malloc(new_len + 1));
467
0
  char* pos = buf;
468
469
0
  memcpy(pos, a->data_, a->len_);
470
0
  pos += a->len_;
471
472
0
  memcpy(pos, b->data_, b->len_);
473
0
  pos += b->len_;
474
475
0
  memcpy(pos, c->data_, c->len_);
476
477
0
  buf[new_len] = '\0';
478
479
0
  return new Str(buf, new_len);
480
0
}
Unexecuted instantiation: _Z11str_concat3P3StrS0_S0_
Unexecuted instantiation: _Z11str_concat3P3StrS0_S0_
481
482
0
Str* str_repeat(Str* s, int times) {
483
  // Python allows -1 too, and Oil used that
484
0
  if (times <= 0) {
485
0
    return kEmptyString;
486
0
  }
487
0
  int len = s->len_;
488
0
  int new_len = len * times;
489
0
  char* data = static_cast<char*>(malloc(new_len + 1));
490
491
0
  char* dest = data;
492
0
  for (int i = 0; i < times; i++) {
493
0
    memcpy(dest, s->data_, len);
494
0
    dest += len;
495
0
  }
496
0
  data[new_len] = '\0';
497
0
  return new Str(data, new_len);
498
0
}
Unexecuted instantiation: _Z10str_repeatP3Stri
Unexecuted instantiation: _Z10str_repeatP3Stri
499
500
// Helper for str_to_int() that doesn't use exceptions.
501
// Like atoi(), but with better error checking.
502
18
bool _str_to_int(Str* s, int* result, int base) {
503
18
  if (s->len_ == 0) {
504
1
    return false;  // special case for empty string
505
1
  }
506
507
17
  char* p;  // mutated by strtol
508
509
17
  mylib::Str0 s0(s);
510
17
  long v = strtol(s0.Get(), &p, base);  // base 10
511
17
  switch (v) {
512
1
  case LONG_MIN:
513
    // log("underflow");
514
1
    return false;
515
1
  case LONG_MAX:
516
    // log("overflow");
517
1
    return false;
518
17
  }
519
520
15
  *result = v;
521
522
  // Return true if it consumed ALL characters.
523
15
  const char* end = s->data_ + s->len_;
524
525
  // log("start %p   p %p   end %p", s->data_, p, end);
526
15
  if (p == end) {
527
9
    return true;
528
9
  }
529
530
  // Trailing space is OK!
531
8
  while (p < end) {
532
6
    if (!isspace(*p)) {
533
4
      return false;
534
4
    }
535
2
    p++;
536
2
  }
537
2
  return true;
538
6
}
539
540
// Python-like wrapper
541
3
int to_int(Str* s) {
542
3
  int i;
543
3
  if (_str_to_int(s, &i, 10)) {
544
1
    return i;
545
2
  } else {
546
2
    throw new ValueError();
547
2
  }
548
3
}
549
550
4
int to_int(Str* s, int base) {
551
4
  int i;
552
4
  if (_str_to_int(s, &i, base)) {
553
4
    return i;
554
4
  } else {
555
0
    throw new ValueError();
556
0
  }
557
4
}
558
559
//
560
// Formatter
561
//
562
563
mylib::BufWriter gBuf;