Calculator  Step 2
parse.cpp
Go to the documentation of this file.
1 #include <sstream>
2 
3 #include "parse.hpp"
4 
5 parser::parser(std::istream& input)
6 : input_(input),
7  ctype_(std::use_facet<std::ctype<char>>(input.getloc())),
8  token_{},
9  kind_{}
10 {}
11 
12 std::string parser::charify(char c)
13 {
14  if (c == '\a') return R"('\a')";
15  if (c == '\b') return R"('\b')";
16  if (c == '\f') return R"('\f')";
17  if (c == '\n') return R"('\n')";
18  if (c == '\r') return R"('\r')";
19  if (c == '\t') return R"('\t')";
20  if (c == '\v') return R"('\v')";
21  if (c == '\'') return R"('\'')";
22  if (c == '\\') return R"('\\')";
23 
24  if (isprint(c))
25  return std::string{"\'"} + std::string(1,c) + "\'";
26  else {
27  std::ostringstream stream{};
28  stream << "'\\x" << std::hex;
29  stream.fill('0');
30  stream.width(2);
31  stream << (std::char_traits<char>::to_int_type(c) & 0xFF) << '\'';
32  return stream.str();
33  }
34 }
35 
36 void parser::get_identifier(std::string& identifier)
37 {
38  identifier.clear();
39  char c{};
40  if (not input_.get(c))
41  return;
42  if (not isalpha(c))
43  throw parse_error{"syntax error: expected alphabetic, got " + charify(c)};
44  identifier += c;
45  while (input_.get(c)) {
46  if (not isalnum(c)) {
47  input_.unget();
48  return;
49  }
50  identifier += c;
51  }
52  return;
53 }
54 
55 /* Parse a token.
56  * A token can be a keyword, a literal or a symbol.
57  * TOKEN ::= IDENTIFIER | NUMBER | SYMBOL
58  * IDENTIIFER ::= ALPHA (ALPHA | DIGIT)*
59  * NUMBER ::= DIGIT+ ('.' DIGITS+)? ('E' SIGN? DIGITS+)?
60  * SYMBOL ::= '+' | '-' | '*' | '/' | '%' | '(' | ')' | '='
61  */
62 parser::kind parser::get_token(std::string& token)
63 {
64  if (not token_.empty())
65  {
66  token = token_;
67  kind result(kind_);
68  token_.clear();
69  kind_ = eof;
70  return result;
71  }
72 
73  char c{};
74  if (not (input_ >> c))
75  return eof;
76  if (isalpha(c)) {
77  input_.unget();
78  get_identifier(token);
79  return identifier;
80  }
81 
82  // Get a numeric literal.
83  token.clear();
84  if (c == '+' or c == '-' or c == '*' or c == '/' or c == '%' or c == '(' or c == ')' or c == '=') {
85  token += c;
86  return kind(c);
87  }
88 
89  if (c < '0' or c > '9') {
90  input_.unget();
91  throw parse_error{"syntax error: expected digit, got " + charify(c)};
92  }
93  while (c >= '0' and c <= '9') {
94  token += c;
95  if (not input_.get(c))
96  return number;
97  }
98  if (c == '.') {
99  token += c;
100  if (not input_.get(c))
101  throw parse_error{"unterminated number: expected digit after the decimal point"};
102  if (c < '0' or c > '9') {
103  input_.unget();
104  throw parse_error{"syntax error: expected digit after decimal point, got " + charify(c)};
105  }
106  while (c >= '0' and c <= '9') {
107  token += c;
108  if (not input_.get(c))
109  return number;
110  }
111  }
112  if (c == 'e' or c == 'E') {
113  token += c;
114  if (not input_.get(c))
115  throw parse_error{"unterminated number: expected digit in the exponent"};
116  if (c == '-' or c == '+') {
117  token += c;
118  if (not input_.get(c))
119  throw parse_error{"unterminated number: expected digit after sign in the exponent"};
120  }
121  if (c < '0' or c > '9') {
122  input_.unget();
123  throw parse_error{"syntax error: expected digit in the exponent, got " + charify(c)};
124  }
125  while (c >= '0' and c <= '9') {
126  token += c;
127  if (not input_.get(c))
128  return number;
129  }
130  }
131  input_.unget();
132  return number;
133 }
134 
135 bool parser::get_number(std::string const& token, double& result)
136 {
137  std::istringstream stream{token};
138  // If the value overflows or is otherwise invalid, return false.
139  return (stream >> result);
140 }
141 
142 bool parser::get_expr(double& result)
143 {
144  std::string token{};
145  kind k{get_token(token)};
146  if (k == eof)
147  return false;
148 
149  if (k == identifier and token == "var") {
150  std::string name{};
151  // Define a variable.
152  k = get_token(name);
153  if (k != identifier)
154  throw parse_error{"syntax error: expected IDENTIFIER, but got " + name};
155  k = get_token(token);
156  if (k != '=')
157  throw parse_error{"syntax error: expected =, but got " + token};
158  if (not get_add_expr(result))
159  throw parse_error{"syntax error: expected additive-exprssion in assignment"};
160  set_variable(name, result);
161  return true;
162  }
163 
164  if (k == identifier and token == "quit")
165  std::exit(0);
166 
167  push_back(token, k);
168  if (not get_add_expr(result))
169  throw parse_error{"syntax error: expected an additive-expression"};
170 
171  return true;
172 }
173 
174 bool parser::get_add_expr(double& result)
175 {
176  if (not get_mul_expr(result))
177  return false;
178  std::string token{};
179  while (kind k = get_token(token)) {
180  if (k != '+' and k != '-') {
181  push_back(token, k);
182  return true;
183  } else {
184  double right{};
185  if (not get_mul_expr(right))
186  throw parse_error{"syntax error: unterminated expression. Expected a multiplicative-expression after " + token};
187  if (k == '+')
188  result += right;
189  else
190  result -= right;
191  }
192  }
193  return true;
194 }
195 
196 bool parser::get_mul_expr(double& result)
197 {
198  if (not get_unary(result))
199  return false;
200  std::string token{};
201  while (kind k = get_token(token)) {
202  if (k != '*' and k != '/') {
203  push_back(token, k);
204  return true;
205  } else {
206  double right{};
207  if (not get_unary(right))
208  throw parse_error{"syntax error: unterminated expression. Expected a unary-expression after " + token};
209  if (k == '*')
210  result *= right;
211  else if (right == 0.0)
212  throw parse_error{"division by zero"};
213  else
214  result /= right;
215  }
216  }
217  return true;
218 }
219 
220 bool parser::get_unary(double& result)
221 {
222  std::string token{};
223  if (kind k = get_token(token)) {
224  if (k == '-') {
225  if (not get_primary(result))
226  return false;
227  result = -result;
228  return true;
229  } else if (k == '+') {
230  return get_primary(result);
231  } else {
232  push_back(token, k);
233  return get_primary(result);
234  }
235  }
236  return false;
237 }
238 
239 bool parser::get_primary(double& result)
240 {
241  std::string token{};
242  if (kind k = get_token(token)) {
243  if (k == '(') {
244  if (not get_expr(result))
245  return false;
246  k = get_token(token);
247  if (k == eof)
248  throw parse_error{"syntax error: EOF when expecting ')'"};
249  else if (k != ')')
250  throw parse_error{"syntax error: expected ')', but got " + token};
251  else
252  return true;
253  } else if (k == number) {
254  if (not get_number(token, result))
255  throw parse_error{"Invalid numeric literal: " + token};
256  return true;
257  } else if (k == identifier) {
258  result = get_variable(token);
259  return true;
260  } else {
261  throw parse_error{"syntax error: expected a primary, but got " + token};
262  }
263  }
264  return false;
265 }
266 
267 void parse_loop(std::istream& input, std::ostream& output)
268 {
269  std::string line{};
270  // No portable way to test whether the console is an interactive terminal
271  // vs. a non-interactive file. If you have a system-specific way to test,
272  // output the prompt only for the interactive case.
273  for (output << "> "; std::getline(input, line); output << "> ") {
274  std::istringstream input{std::move(line)};
275  parser p(input);
276  try {
277  double x;
278  while (p.get_expr(x))
279  output << x << '\n';
280  } catch(parse_error const& ex) {
281  output << ex.what() << '\n';
282  } catch(std::exception const& ex) {
283  output << "exception: " << ex.what() << '\n';
284  }
285  }
286 }
bool get_primary(double &result)
Definition: parse.cpp:239
void parse_loop(std::istream &input, std::ostream &output)
Definition: parse.cpp:267
bool isprint(char c) const
Definition: parse.hpp:140
void set_variable(std::string name, double value)
Definition: variables.cpp:23
parser(std::istream &input)
Definition: parse.cpp:5
void get_identifier(std::string &identifier)
Definition: parse.cpp:36
std::string charify(char c)
Definition: parse.cpp:12
void push_back(std::string const &token, kind k)
Definition: parse.hpp:119
Definition: parse.hpp:31
bool get_mul_expr(double &result)
Definition: parse.cpp:196
bool isalpha(char c) const
Definition: parse.hpp:125
kind get_token(std::string &token)
Definition: parse.cpp:62
std::string token_
One token push-back.
Definition: parse.hpp:144
bool get_number(std::string const &token, double &result)
Definition: parse.cpp:135
bool get_expr(double &result)
Definition: parse.cpp:142
bool get_add_expr(double &result)
Definition: parse.cpp:174
std::istream & input_
Share the input stream.
Definition: parse.hpp:142
bool get_unary(double &result)
Definition: parse.cpp:220
kind kind_
The kind of token that was pushed back.
Definition: parse.hpp:145
kind
Definition: parse.hpp:37
double get_variable(std::string const &name)
Definition: variables.cpp:18
bool isalnum(char c) const
Definition: parse.hpp:130