1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
#ifndef COMSERVATORY_CONVERT_HPP
#define COMSERVATORY_CONVERT_HPP
#include <string>
#include <cctype>
#include <complex>
#include <cmath>
#include <array>
namespace comservatory {
inline std::string get_location(size_t column, size_t line) {
return std::string("field ") + std::to_string(column + 1) + " of line " + std::to_string(line + 1);
}
// Assumes that 'input' is located on the opening double quote. On return,
// 'input' is left on the next character _after_ the closing double quote.
template<class Input>
std::string to_string(Input& input, size_t column, size_t line) {
std::string output;
while (1) {
input.advance();
if (!input.valid()) {
throw std::runtime_error("truncated string in " + get_location(column, line));
}
char next = input.get();
if (next == '"') {
input.advance();
if (!input.valid()) {
throw std::runtime_error("line " + std::to_string(line + 1) + " should be terminated with a newline");
}
if (input.get() != '"') {
break;
} else {
output += '"';
}
} else {
output += next;
}
}
return output;
}
// Assumes that 'input' is located on the first letter of the keyword (i.e.,
// before the first letter of 'lower'). On return, 'input' is left on the
// first character _after_ the end of the keyword.
template<class Input>
void expect_fixed(Input& input, const std::string& lower, const std::string& upper, size_t column, size_t line) {
for (size_t i = 0; i < lower.size(); ++i) {
if (!input.valid()) {
throw std::runtime_error("truncated keyword in " + get_location(column, line));
}
char x = input.get();
if (x != lower[i] && x != upper[i]) {
throw std::runtime_error("unknown keyword in " + get_location(column, line));
}
input.advance();
}
}
// Assumes that 'input' is located on the first digit. On return, 'input' is
// left on the first character _after_ the end of the number.
template<class Input>
double to_number(Input& input, size_t column, size_t line) {
double value = 0;
double fractional = 10;
double exponent = 0;
bool negative_exponent = false;
auto is_terminator = [](char v) -> bool {
return v == ',' || v == '\n' || v == '+' || v == '-' || v == 'i'; // last three are terminators for complex numbers.
};
bool in_fraction = false;
bool in_exponent = false;
// We assume we're starting from a digit, after handling any preceding sign.
char lead = input.get();
value += lead - '0';
input.advance();
while (1) {
if (!input.valid()) {
throw std::runtime_error("line " + std::to_string(line + 1) + " should be terminated with a newline");
}
char val = input.get();
if (val == '.') {
in_fraction = true;
break;
} else if (val == 'e' || val == 'E') {
in_exponent = true;
break;
} else if (is_terminator(val)) {
return value;
} else if (!std::isdigit(val)) {
throw std::runtime_error("invalid number containing '" + std::string(1, val) + "' at " + get_location(column, line));
}
value *= 10;
value += val - '0';
input.advance();
}
if (in_fraction) {
input.advance();
if (!input.valid()) {
throw std::runtime_error("line " + std::to_string(line + 1) + " should be terminated with a newline");
}
char val = input.get();
if (!std::isdigit(val)) {
throw std::runtime_error("'.' must be followed by at least one digit at " + get_location(column, line));
}
value += (val - '0') / fractional;
input.advance();
while (1) {
if (!input.valid()) {
throw std::runtime_error("line " + std::to_string(line + 1) + " should be terminated with a newline");
}
char val = input.get();
if (val == 'e' || val == 'E') {
in_exponent = true;
break;
} else if (is_terminator(val)) {
return value;
} else if (!std::isdigit(val)) {
throw std::runtime_error("invalid fraction containing '" + std::string(1, val) + "' at " + get_location(column, line));
}
fractional *= 10;
value += (val - '0') / fractional;
input.advance();
}
}
if (in_exponent) {
if (value < 1 || value >= 10) {
throw std::runtime_error("absolute value of mantissa should be within [1, 10) at " + get_location(column, line));
}
input.advance();
if (!input.valid()) {
throw std::runtime_error("line " + std::to_string(line + 1) + " should be terminated with a newline");
}
char val = input.get();
if (!std::isdigit(val)) {
if (val == '-') {
negative_exponent = true;
} else if (val != '+') {
throw std::runtime_error("'e/E' should be followed by a sign or digit in number at " + get_location(column, line));
}
input.advance();
if (!input.valid()) {
throw std::runtime_error("line " + std::to_string(line + 1) + " should be terminated with a newline");
}
val = input.get();
if (!std::isdigit(val)) {
throw std::runtime_error("exponent sign must be followed by at least one digit in number at " + get_location(column, line));
}
}
exponent += (val - '0');
input.advance();
while (1) {
if (!input.valid()) {
throw std::runtime_error("line " + std::to_string(line + 1) + " should be terminated with a newline");
}
char val = input.get();
if (is_terminator(val)) {
break;
} else if (!std::isdigit(val)) {
throw std::runtime_error("invalid exponent containing '" + std::string(1, val) + "' at " + get_location(column, line));
}
exponent *= 10;
exponent += (val - '0');
input.advance();
}
if (exponent) {
if (negative_exponent) {
exponent *= -1;
}
value *= std::pow(10.0, exponent);
}
}
return value;
}
}
#endif
|