Calculator  Step 1
parse.cpp
Go to the documentation of this file.
1 #include <sstream>
2 #include <utility>
3 
4 #include "parse.hpp"
5 
6 parser::parser(std::istream& input)
7 : input_(input),
8  ctype_(std::use_facet<std::ctype<char>>(input.getloc()))
9 {}
10 
11 std::string parser::charify(char c)
12 {
13  if (c == '\a') return R"('\a')";
14  if (c == '\b') return R"('\b')";
15  if (c == '\f') return R"('\f')";
16  if (c == '\n') return R"('\n')";
17  if (c == '\r') return R"('\r')";
18  if (c == '\t') return R"('\t')";
19  if (c == '\v') return R"('\v')";
20  if (c == '\'') return R"('\'')";
21  if (c == '\\') return R"('\\')";
22 
23  if (isprint(c))
24  return std::string{"\'"} + std::string(1,c) + "\'";
25  else {
26  std::ostringstream stream{};
27  stream << "'\\x" << std::hex;
28  stream.fill('0');
29  stream.width(2);
30  stream << (std::char_traits<char>::to_int_type(c) & 0xFF) << '\'';
31  return stream.str();
32  }
33 }
34 
35 /* Parse a floating point number.
36  * NUMBER ::= SIGN? DIGIT+ ('.' DIGITS+)? ('E' SIGN? DIGITS+)?
37  */
38 bool parser::get_number(double& result)
39 {
40  std::string token{};
41  char c{};
42  if (not input_.get(c))
43  return false;
44  if (c == '+' or c == '-') {
45  token += c;
46  if (not input_.get(c))
47  throw parse_error{"unterminated number: expected a digit after the sign"};
48  }
49  if (c < '0' or c > '9') {
50  input_.unget();
51  throw parse_error{"syntax error: expected digit, got " + charify(c)};
52  }
53  while (c >= '0' and c <= '9') {
54  token += c;
55  if (not input_.get(c)) {
56  std::istringstream tmp{std::move(token)};
57  // If the value overflows, return false.
58  return (tmp >> result);
59  }
60  }
61  if (c == '.') {
62  token += c;
63  if (not input_.get(c))
64  throw parse_error{"unterminated number: expected digit after the decimal point"};
65  if (c < '0' or c > '9') {
66  input_.unget();
67  throw parse_error{"syntax error: expected digit after decimal point, got " + charify(c)};
68  }
69  while (c >= '0' and c <= '9') {
70  token += c;
71  if (not input_.get(c)) {
72  std::istringstream tmp{std::move(token)};
73  // If the value overflows or is otherwise invalid, return false.
74  return (tmp >> result);
75  }
76  }
77  }
78  if (c == 'e' or c == 'E') {
79  token += c;
80  if (not input_.get(c))
81  throw parse_error{"unterminated number: expected digit in the exponent"};
82  if (c == '-' or c == '+') {
83  token += c;
84  if (not input_.get(c))
85  throw parse_error{"unterminated number: expected digit after sign in the exponent"};
86  }
87  if (c < '0' or c > '9') {
88  input_.unget();
89  throw parse_error{"syntax error: expected digit in the exponent, got " + charify(c)};
90  }
91  while (c >= '0' and c <= '9') {
92  token += c;
93  if (not input_.get(c)) {
94  std::istringstream tmp{std::move(token)};
95  // If the value overflows or is otherwise invalid, return false.
96  return (tmp >> result);
97  }
98  }
99  }
100  input_.unget();
101 
102  std::istringstream tmp{std::move(token)};
103  // If the value overflows or is otherwise invalid, return false.
104  return (tmp >> result);
105 }
106 
107 /* Parse an expression.
108  * EXPR ::= MULT_EXPR | EXPR + MULT_EXPR | EXPR - MULT_EXP
109  */
110 bool parser::get_expr(double& result)
111 {
112  if (not get_mult_expr(result))
113  return false;
114  char c{};
115  while (input_ >> c) {
116  if (c != '+' and c != '-') {
117  input_.unget();
118  return true;
119  } else {
120  double right{};
121  if (not get_mult_expr(right))
122  throw parse_error{"syntax error: unterminated expression. Expected a multiplicative-exprssion after " + std::string(c,1)};
123  if (c == '+')
124  result += right;
125  else
126  result -= right;
127  }
128  }
129  return true;
130 }
131 
132 /* Parse a multiplicative-expression.
133  * MULT_EXPR ::= PRIMARY | MULT_EXPR + PRIMARY | MULT_EXPR - PRIMARY
134  */
135 bool parser::get_mult_expr(double& result)
136 {
137  if (not get_primary(result))
138  return false;
139  char c{};
140  while (input_ >> c) {
141  if (c != '*' and c != '/') {
142  input_.unget();
143  return true;
144  } else {
145  double right{};
146  if (not get_primary(right))
147  throw parse_error{"syntax error: unterminated expression. Expected a primary after " + std::string(c,1)};
148  if (c == '*')
149  result *= right;
150  else if (right == 0.0)
151  throw parse_error{"division by zero"};
152  else
153  result /= right;
154  }
155  }
156  return true;
157 }
158 
159 /* Parse a primary.
160  * PRIMARY ::= NUMBER | '(' EXPR ')'
161  */
162 bool parser::get_primary(double& result)
163 {
164  char c{};
165  if (not (input_ >> c))
166  // Can't read one character, so must be end-of-file
167  return false;
168  else if (c == '(') {
169  if (not get_expr(result))
170  return false;
171  if (not (input_ >> c))
172  throw parse_error{"syntax error: EOF when expecting ')'"};
173  else if (c != ')')
174  throw parse_error{"syntax error: expected ')', but got " + charify(c)};
175  else
176  return true;
177  } else {
178  input_.unget();
179  return get_number(result);
180  }
181 }
182 
183 void parse_loop(std::istream& input, std::ostream& output)
184 {
185  std::string line;
186  // No portable way to test whether the console is an interactive terminal
187  // vs. a non-interactive file. If you have a system-specific way to test,
188  // output the prompt only for the interactive case.
189  for (output << "> "; std::getline(input, line); output << "> ") {
190  std::istringstream input{std::move(line)};
191  parser p{input};
192  try {
193  double x{};
194  while (p.get_expr(x))
195  output << x << '\n';
196  } catch(parse_error const& ex) {
197  output << ex.what() << '\n';
198  } catch(std::exception const& ex) {
199  output << "exception: " << ex.what() << '\n';
200  }
201  }
202 }
bool get_primary(double &result)
Definition: parse.cpp:162
void parse_loop(std::istream &input, std::ostream &output)
Definition: parse.cpp:183
parser(std::istream &input)
Definition: parse.cpp:6
std::string charify(char c)
Definition: parse.cpp:11
bool get_number(double &result)
Definition: parse.cpp:38
Definition: parse.hpp:17
bool get_expr(double &result)
Definition: parse.cpp:110
std::istream & input_
Definition: parse.hpp:55
bool get_mult_expr(double &result)
Definition: parse.cpp:135