ʻOhana
Population structure, admixture history, and selection using learning methods.
jade.shunting_yard.hpp
1 /* -------------------------------------------------------------------------
2  Ohana
3  Copyright (c) 2015-2020 Jade Cheng (\___/)
4  Jade Cheng <info@jade-cheng.com> (='.'=)
5  ------------------------------------------------------------------------- */
6 
7 #ifndef JADE_SHUNTING_YARD_HPP__
8 #define JADE_SHUNTING_YARD_HPP__
9 
10 #include "jade.error.hpp"
11 
12 namespace jade
13 {
14  ///
15  /// A template for a class that implements the shunting-yard algorithm. The
16  /// implementation supports '+', '-', '*', and '/' operators; the '(' and
17  /// ')' parentheses; floating-point numbers; and variables. After parsing
18  /// an expression, the instance can evaluate the expression if given a
19  /// table of variable values.
20  ///
21  template <typename TFloat>
23  {
24  public:
25  typedef TFloat float_type; ///< The floating-point type.
26 
27  ///
28  /// A map of strings to floating-point values. The string represent the
29  /// variable names parsed from the expression passed to the
30  /// constructor, and the floating-point values are used to evaluate the
31  /// expression.
32  ///
33  typedef std::map<std::string, float_type> args_type;
34 
35  ///
36  /// Initializes a new instance of the class. The specified input stream
37  /// provides tokens for the expression.
38  /// \param in The input stream providing tokens.
39  ///
40  explicit basic_shunting_yard(std::istream & in)
41  : _args ()
42  , _queue ()
43  {
44  std::vector<_token> tokens;
45  _scan(in, tokens);
46  _enqueue(tokens);
47 
48  for (const auto & t : _queue)
49  if (t.type == _token::variable)
50  _args[t.text] = float_type(0);
51 
52  evaluate(_args);
53  }
54 
55  ///
56  /// Initializes a new instance of the class. The specified string
57  /// provides tokens for the expression.
58  /// \param expression The string providing tokens.
59  ///
60  explicit basic_shunting_yard(const std::string & expression)
61  : _args ()
62  , _queue ()
63  {
64  std::istringstream in (expression);
65  *this = basic_shunting_yard(in);
66  }
67 
68  ///
69  /// Returns a table of arguments that maps variables to values. The
70  /// table reference returned has all values set to zero. The table can
71  /// be used to evaluate the expression.
72  /// \return A table mapping variables to values.
73  ///
74  const args_type & get_args() const
75  {
76  return _args;
77  }
78 
79  ///
80  /// Evaluates the expression using values from the specified table.
81  /// \param args The table of variable values.
82  /// \return The output from the expression.
83  ///
85  {
86  std::vector<float_type> stack;
87 
88  for (const auto & t : _queue)
89  {
90  if (t.type == _token::number)
91  {
92  stack.push_back(t.get_value());
93  }
94  else if (t.type == _token::variable)
95  {
96  const auto item = args.find(t.text);
97 
98  if (item == args.end())
99  throw jade::error()
100  << "undefined variable '"
101  << t.text
102  << "'";
103 
104  stack.push_back(item->second);
105  }
106  else
107  {
108  if (stack.size() < 2)
109  throw jade::error("invalid expression");
110 
111  const auto n2 = stack.back();
112  stack.pop_back();
113  const auto n1 = stack.back();
114  stack.pop_back();
115 
116  if (t.type == _token::plus ) stack.push_back(n1 + n2);
117  else if (t.type == _token::minus) stack.push_back(n1 - n2);
118  else if (t.type == _token::star ) stack.push_back(n1 * n2);
119  else if (t.type == _token::slash) stack.push_back(n1 / n2);
120  else throw jade::error("invalid operator");
121  }
122  }
123 
124  if (stack.size() != 1)
125  throw jade::error("invalid expression");
126 
127  return stack[0];
128  }
129 
130  ///
131  /// Evaluates the expression using values from the specified table.
132  /// \param args The table of variable values.
133  /// \return The output from the expression.
134  ///
136  const args_type & args = args_type())
137  const
138  {
139  return evaluate(args);
140  }
141 
142  private:
143  // --------------------------------------------------------------------
144  struct _token
145  {
146  // ----------------------------------------------------------------
147  enum type_t
148  {
149  end,
150  minus,
151  number,
152  paren_lhs,
153  paren_rhs,
154  plus,
155  slash,
156  star,
157  variable
158  };
159 
160  // ----------------------------------------------------------------
161  explicit _token(const type_t type_)
162  : type (type_)
163  , text ()
164  {
165  }
166 
167  // ----------------------------------------------------------------
168  int get_precedence() const
169  {
170  return type == plus || type == minus ? 1 :
171  type == star || type == slash ? 2 :
172  0;
173  }
174 
175  // ----------------------------------------------------------------
176  float_type get_value() const
177  {
178  float_type value;
179  std::istringstream(text) >> value;
180  return value;
181  }
182 
183  // ----------------------------------------------------------------
184  bool is_numeric() const
185  {
186  return type == variable || type == number;
187  }
188 
189  // ----------------------------------------------------------------
190  bool is_operator() const
191  {
192  return type == plus || type == minus
193  || type == star || type == slash;
194  }
195 
196  type_t type;
197  std::string text;
198  };
199 
200  // --------------------------------------------------------------------
201  void _enqueue(const std::vector<_token> & tokens)
202  {
203  std::vector<_token> stack;
204 
205  for (size_t i = 0; i < tokens.size(); i++)
206  {
207  const auto & t1 = tokens[i];
208 
209  if (t1.is_numeric())
210  {
211  _queue.push_back(t1);
212  }
213  else if (t1.is_operator())
214  {
215  while (!stack.empty())
216  {
217  const auto & t2 = stack.back();
218 
219  if (!t2.is_operator())
220  break;
221  if (t1.get_precedence() > t2.get_precedence())
222  break;
223 
224  _queue.push_back(t2);
225  stack.pop_back();
226  }
227 
228  stack.push_back(t1);
229  }
230  else if (t1.type == _token::paren_lhs)
231  {
232  stack.push_back(t1);
233  }
234  else if (t1.type == _token::paren_rhs)
235  {
236  while (!stack.empty())
237  {
238  const auto & t2 = stack.back();
239 
240  if (t2.type == _token::paren_lhs)
241  break;
242 
243  _queue.push_back(t2);
244  stack.pop_back();
245  }
246 
247  if (stack.empty())
248  throw jade::error("mismatched parentheses");
249 
250  stack.pop_back();
251  }
252  }
253 
254  while (!stack.empty())
255  {
256  const auto & t2 = stack.back();
257 
258  if (t2.type == _token::paren_lhs)
259  throw jade::error("mismatched parentheses");
260 
261  _queue.push_back(t2);
262  stack.pop_back();
263  }
264  }
265 
266  // --------------------------------------------------------------------
267  static void _scan(std::istream & in, std::vector<_token> & tokens)
268  {
269  const auto cat = [&tokens](const int ch)
270  {
271  tokens.back().text.push_back(static_cast<char>(ch));
272  };
273 
274  for (auto ch = in.get(); ch >= 0; ch = in.get())
275  {
276  if (std::isspace(ch)) continue;
277  else if (ch == '(') tokens.emplace_back(_token::paren_lhs);
278  else if (ch == ')') tokens.emplace_back(_token::paren_rhs);
279  else if (ch == '+') tokens.emplace_back(_token::plus);
280  else if (ch == '-') tokens.emplace_back(_token::minus);
281  else if (ch == '*') tokens.emplace_back(_token::star);
282  else if (ch == '/') tokens.emplace_back(_token::slash);
283  else if (std::isalpha(ch))
284  {
285  tokens.emplace_back(_token::variable);
286  cat(ch);
287 
288  while (in.peek() == '_' || 0 != std::isalnum(in.peek()))
289  cat(in.get());
290  }
291  else if (std::isdigit(ch))
292  {
293  tokens.emplace_back(_token::number);
294  cat(ch);
295 
296  while (std::isdigit(in.peek()))
297  cat(in.get());
298 
299  if (in.peek() == '.')
300  {
301  cat(in.get());
302 
303  while (std::isdigit(in.peek()))
304  cat(in.get());
305  }
306  }
307  else
308  {
309  throw jade::error()
310  << "invalid symbol '"
311  << static_cast<char>(ch)
312  << "'";
313  }
314  }
315 
316  tokens.emplace_back(_token::end);
317  }
318 
319  args_type _args; // A default set of arguments.
320  std::vector<_token> _queue; // The tokens in reverse polish notation.
321  };
322 }
323 
324 #endif // JADE_SHUNTING_YARD_HPP__
jade::basic_shunting_yard::args_type
std::map< std::string, float_type > args_type
A map of strings to floating-point values. The string represent the variable names parsed from the ex...
Definition: jade.shunting_yard.hpp:33
jade::basic_shunting_yard::float_type
TFloat float_type
The floating-point type.
Definition: jade.shunting_yard.hpp:25
jade::basic_shunting_yard::basic_shunting_yard
basic_shunting_yard(const std::string &expression)
Initializes a new instance of the class. The specified string provides tokens for the expression.
Definition: jade.shunting_yard.hpp:60
jade::basic_shunting_yard::get_args
const args_type & get_args() const
Returns a table of arguments that maps variables to values. The table reference returned has all valu...
Definition: jade.shunting_yard.hpp:74
jade::basic_shunting_yard::basic_shunting_yard
basic_shunting_yard(std::istream &in)
Initializes a new instance of the class. The specified input stream provides tokens for the expressio...
Definition: jade.shunting_yard.hpp:40
jade::basic_args
A template for a class that helps process command-line arguments.
Definition: jade.args.hpp:19
jade::basic_shunting_yard
A template for a class that implements the shunting-yard algorithm. The implementation supports '+',...
Definition: jade.shunting_yard.hpp:23
jade::basic_shunting_yard::operator()
float_type operator()(const args_type &args=args_type()) const
Evaluates the expression using values from the specified table.
Definition: jade.shunting_yard.hpp:135
jade::basic_shunting_yard::evaluate
float_type evaluate(const args_type &args=args_type()) const
Evaluates the expression using values from the specified table.
Definition: jade.shunting_yard.hpp:84
jade::basic_error
A template for a class representing an exception thrown from this namespace.
Definition: jade.error.hpp:20