The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
default_plural_forms_expressions.hpp
Go to the documentation of this file.
1// (C) Copyright 2015 - 2017 Christopher Beck
2
3// Distributed under the Boost Software License, Version 1.0. (See accompanying
4// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
5
6#ifndef SPIRIT_PO_DEFAULT_PLURAL_FORMS_EXPRESSIONS_HPP_INCLUDED
7#define SPIRIT_PO_DEFAULT_PLURAL_FORMS_EXPRESSIONS_HPP_INCLUDED
8
9/***
10 * The namespace default_plural_forms contains all the details to implement
11 * the subset of the C grammar used by standard GNU gettext po headers.
12 *
13 * Boolean expressions return uint 0 or 1.
14 *
15 * The 'compiler' is a spirit grammar which parses a string into an expression
16 * object. The expressions are evaluated by a simple stack machine.
17 */
18
19#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
20#define BOOST_SPIRIT_USE_PHOENIX_V3
21#endif
22
23#include <algorithm>
24#include <vector>
25#include <boost/spirit/include/qi.hpp>
26#include <boost/phoenix/core.hpp>
27#include <boost/phoenix/operator.hpp>
28#include <boost/fusion/include/adapt_struct.hpp>
29#include <boost/variant/variant.hpp>
30#include <boost/variant/recursive_wrapper.hpp>
31
32#ifdef SPIRIT_PO_DEBUG
33#include <cassert>
34#include <string>
35#endif
36
37namespace spirit_po {
38
39namespace qi = boost::spirit::qi;
40typedef unsigned int uint;
41
42namespace default_plural_forms {
43
44// X Macro for repetitive binary ops declarations
45
46#define FOREACH_SPIRIT_PO_BINARY_OP(X_) \
47 X_(eq_op, ==) X_(neq_op, !=) X_(ge_op, >=) X_(le_op, <=) X_(gt_op, >) X_(lt_op, <) X_(mod_op, %)
48
49// && and || are treated slightly differently from other binary ops
50
51#define FOREACH_SPIRIT_PO_CONJUNCTION(X_) \
52 X_(and_op, &&) X_(or_op, ||)
53
54/***
55 * Declare / forward declare expr struct types
56 */
57
58struct constant { uint value; };
59struct n_var { n_var() = default; explicit n_var(char) {}}; // work around a quirk in spirit
60struct not_op;
61struct ternary_op;
62
63#define FWD_DECL_(name, op) \
64struct name ; \
65
68
69#undef FWD_DECL_
70
71/***
72 * Define expr variant type
73 */
74
75#define WRAP_(name, op) boost::recursive_wrapper< name >, \
76
77typedef boost::variant<constant, n_var, boost::recursive_wrapper<not_op>,
80boost::recursive_wrapper<ternary_op>> expr;
81
82#undef WRAP_
83
84/***
85 * Define structs
86 */
87
88struct not_op { expr e1; };
89struct ternary_op { expr e1, e2, e3; };
90
91#define DECL_(name, op) \
92struct name { expr e1, e2; }; \
93
96
97#undef DECL_
98
99/***
100 * Visitor that naively evaluates expressions
101 */
102struct evaluator : public boost::static_visitor<uint> {
104 explicit evaluator(uint n) : n_value_(n) {}
105
106 uint operator()(const constant & c) const { return c.value; }
107 uint operator()(n_var) const { return n_value_; }
108 uint operator()(const not_op & op) const { return !boost::apply_visitor(*this, op.e1); }
109
110#define EVAL_OP_(name, OPERATOR) \
111 uint operator()(const name & op) const { return (boost::apply_visitor(*this, op.e1)) OPERATOR (boost::apply_visitor(*this, op.e2)); } \
112
115#undef EVAL_OP_
116
117 uint operator()(const ternary_op & op) const { return boost::apply_visitor(*this, op.e1) ? boost::apply_visitor(*this, op.e2) : boost::apply_visitor(*this, op.e3); }
118};
119
120} // end namespace default_plural_forms
121
122} // end namespace spirit_po
123
124/***
125 * Adapt structs for fusion / qi
126 */
127
128BOOST_FUSION_ADAPT_STRUCT(spirit_po::default_plural_forms::constant,
129 (unsigned int, value))
130BOOST_FUSION_ADAPT_STRUCT(spirit_po::default_plural_forms::not_op,
132BOOST_FUSION_ADAPT_STRUCT(spirit_po::default_plural_forms::ternary_op,
136
137#define ADAPT_STRUCT_(name, op) \
138BOOST_FUSION_ADAPT_STRUCT(spirit_po::default_plural_forms:: name, \
139 (spirit_po::default_plural_forms::expr, e1) \
140 (spirit_po::default_plural_forms::expr, e2)) \
141
144
145#undef ADAPT_STRUCT_
146
147namespace spirit_po {
148
149namespace default_plural_forms {
150
151/***
152 * Pseudo-C Grammar
153 *
154 * Note that the grammar has been somewhat optimized by using local variables
155 * and inherited attributes, in order to avoid exponential backtracking overhead.
156 * This makes it a little harder to read than if we got rid of all local variables,
157 * but then it is too slow to parse the expressions for certain languages.
158 *
159 * The main idea is that instead of parsing things like
160 *
161 * BINARY_OP = LOWER_PRECENDENCE >> BINARY_OP_LITERAL >> CURRENT_PRECEDENCE
162 * CURRENT_PRECEDENCE = BINARY_OP | OTHER_OP | YET_ANOTHER_OP | LOWER_PRECEDENCE
163 *
164 * (which is bad because if the binary op literal is not there then we have to
165 * backtrack through an entire subexpression)
166 *
167 * we make BINARY_OP take the subexpression as a parameter, and in each
168 * precedence level, we capture the subexpression first and store it in a local
169 * variable, so that it does not get reparsed when we backtrack.
170 *
171 * BINARY_OP = BINARY_OP_LITERAL >> qi::attr(parameter) >> CURRENT_PRECEDENCE
172 *
173 * CURRENT_PRECEDENCE = LOWER_PRECEDENCE[local_var = result] >>
174 * (BINARY_OP(local_var) | OTHER_OP(local_var) | YET_ANOTHER_OP(local_var) | qi::attr(local_var)
175 *
176 */
177
178template <typename Iterator>
179struct op_grammar : qi::grammar<Iterator, expr(), qi::space_type> {
180 qi::rule<Iterator, constant(), qi::space_type> constant_;
181 qi::rule<Iterator, n_var(), qi::space_type> n_;
182 qi::rule<Iterator, not_op(), qi::space_type> not_;
183 qi::rule<Iterator, and_op(expr), qi::space_type> and_;
184 qi::rule<Iterator, or_op(expr), qi::space_type> or_;
185 qi::rule<Iterator, eq_op(expr), qi::space_type> eq_;
186 qi::rule<Iterator, neq_op(expr), qi::space_type> neq_;
187 qi::rule<Iterator, ge_op(expr), qi::space_type> ge_;
188 qi::rule<Iterator, le_op(expr), qi::space_type> le_;
189 qi::rule<Iterator, gt_op(expr), qi::space_type> gt_;
190 qi::rule<Iterator, lt_op(expr), qi::space_type> lt_;
191 qi::rule<Iterator, mod_op(expr), qi::space_type> mod_;
192 qi::rule<Iterator, ternary_op(expr), qi::space_type> ternary_;
193 qi::rule<Iterator, expr(), qi::space_type> paren_expr_;
194
195 // expression precedence levels
196 qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> ternary_level_;
197 qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> or_level_;
198 qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> and_level_;
199 qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> eq_level_;
200 qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> rel_level_;
201 qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> mod_level_;
202 qi::rule<Iterator, expr(), qi::space_type> atom_level_;
203 qi::rule<Iterator, expr(), qi::space_type> expr_;
204
205 // handle optional ';' at end
206 qi::rule<Iterator, expr(), qi::space_type> main_;
207
208 op_grammar() : op_grammar::base_type(main_) {
209 using qi::attr;
210 using qi::lit;
211
212 constant_ = qi::uint_;
213 n_ = qi::char_('n');
214 paren_expr_ = lit('(') >> expr_ >> lit(')');
215 not_ = lit('!') >> atom_level_;
216 atom_level_ = paren_expr_ | not_ | n_ | constant_;
217
218 mod_ = lit('%') >> attr(qi::_r1) >> atom_level_;
219 mod_level_ = qi::omit[atom_level_[qi::_a = qi::_1]] >> (mod_(qi::_a) | attr(qi::_a));
220
221 ge_ = lit(">=") >> attr(qi::_r1) >> mod_level_;
222 le_ = lit("<=") >> attr(qi::_r1) >> mod_level_;
223 gt_ = lit('>') >> attr(qi::_r1) >> mod_level_;
224 lt_ = lit('<') >> attr(qi::_r1) >> mod_level_;
225 rel_level_ = qi::omit[mod_level_[qi::_a = qi::_1]] >> (ge_(qi::_a) | le_(qi::_a) | gt_(qi::_a) | lt_(qi::_a) | attr(qi::_a));
226
227 eq_ = lit("==") >> attr(qi::_r1) >> rel_level_;
228 neq_ = lit("!=") >> attr(qi::_r1) >> rel_level_;
229 eq_level_ = qi::omit[rel_level_[qi::_a = qi::_1]] >> (eq_(qi::_a) | neq_(qi::_a) | attr(qi::_a));
230
231 and_ = lit("&&") >> attr(qi::_r1) >> and_level_;
232 and_level_ = qi::omit[eq_level_[qi::_a = qi::_1]] >> (and_(qi::_a) | attr(qi::_a));
233
234 or_ = lit("||") >> attr(qi::_r1) >> or_level_;
235 or_level_ = qi::omit[and_level_[qi::_a = qi::_1]] >> (or_(qi::_a) | attr(qi::_a));
236
237 ternary_ = lit('?') >> attr(qi::_r1) >> ternary_level_ >> lit(':') >> ternary_level_;
238 ternary_level_ = qi::omit[or_level_[qi::_a = qi::_1]] >> (ternary_(qi::_a) | attr(qi::_a));
239
240 expr_ = ternary_level_;
241
242 main_ = expr_ >> -lit(';');
243 }
244};
245
246/***
247 * Now define a simple stack machine to evaluate the expressions efficiently.
248 *
249 * First define op_codes
250 */
251
252#define ENUMERATE(X, Y) X,
253
254enum class op_code { n_var, FOREACH_SPIRIT_PO_BINARY_OP(ENUMERATE) not_op };
255
256#undef ENUMERATE
257
258/** Instruction that causes us to skip upcoming instructions */
259struct skip {
260 uint distance;
261};
262
263/** Instructions that conditionally cause us to skip upcoming instructions */
264struct skip_if {
265 uint distance;
266};
267
268struct skip_if_not {
269 uint distance;
270};
271
272/***
273 * Instruction is a variant type that represents either a push_constant, branch, jump, or arithmetic op.
274 */
275typedef boost::variant<constant, skip, skip_if, skip_if_not, op_code> instruction;
276
277/***
278 * Debug strings for instruction set
279 */
280#ifdef SPIRIT_PO_DEBUG
281inline std::string op_code_string(op_code oc) {
282 std::string result = "[ ";
283 switch (oc) {
284 case op_code::n_var: {
285 result += "n ";
286 break;
287 }
288 case op_code::not_op: {
289 result += "! ";
290 break;
291 }
292#define OP_CODE_STR_CASE_(X, Y) \
293 case op_code::X: { \
294 result += #Y; \
295 break; \
296 }
297
298FOREACH_SPIRIT_PO_BINARY_OP(OP_CODE_STR_CASE_)
299
300#undef OP_CODE_STR_CASE_
301 }
302
303 if (result.size() < 5) { result += ' '; } \
304 result += " : ]";
305 return result;
306}
307
308struct instruction_debug_string_maker : boost::static_visitor<std::string> {
309 std::string operator()(const constant & c) const {
310 return "[ push : " + std::to_string(c.value) + " ]";
311 }
312 std::string operator()(const skip & s) const {
313 return "[ skip : " + std::to_string(s.distance) + " ]";
314 }
315 std::string operator()(const skip_if & s) const {
316 return "[ sif : " + std::to_string(s.distance) + " ]";
317 }
318 std::string operator()(const skip_if_not & s) const {
319 return "[ sifn : " + std::to_string(s.distance) + " ]";
320 }
321 std::string operator()(const op_code & oc) const {
322 return op_code_string(oc);
323 }
324};
325
326inline std::string debug_string(const instruction & i) {
327 return boost::apply_visitor(instruction_debug_string_maker{}, i);
328}
329
330#endif // SPIRIT_PO_DEBUG
331
332/***
333 * Helper: Check if an expression obviously is zero-one valued
334 */
335struct is_boolean : public boost::static_visitor<bool> {
336 bool operator()(const and_op &) const { return true; }
337 bool operator()(const or_op &) const { return true; }
338 bool operator()(const not_op &) const { return true; }
339 bool operator()(const eq_op &) const { return true; }
340 bool operator()(const neq_op &) const { return true; }
341 bool operator()(const ge_op &) const { return true; }
342 bool operator()(const le_op &) const { return true; }
343 bool operator()(const gt_op &) const { return true; }
344 bool operator()(const lt_op &) const { return true; }
345 bool operator()(const n_var &) const { return false; }
346 bool operator()(const constant & c) const { return (c.value == 0 || c.value == 1); }
347 bool operator()(const mod_op & m) const { return boost::apply_visitor(*this, m.e1); }
348 bool operator()(const ternary_op & t) const { return boost::apply_visitor(*this, t.e2) && boost::apply_visitor(*this, t.e3); }
349};
350
351
352/***
353 * Visitor that maps expressions to instruction sequences
354 */
355struct emitter : public boost::static_visitor<std::vector<instruction>> {
356 std::vector<instruction> operator()(const constant & c) const {
357 return std::vector<instruction>{instruction{c}};
358 }
359 std::vector<instruction> operator()(const n_var &) const {
360 return std::vector<instruction>{instruction{op_code::n_var}};
361 }
362 std::vector<instruction> operator()(const not_op & o) const {
363 auto result = boost::apply_visitor(*this, o.e1);
364 result.emplace_back(op_code::not_op);
365 return result;
366 }
367#define EMIT_OP_(name, op) \
368 std::vector<instruction> operator()(const name & o) const { \
369 auto result = boost::apply_visitor(*this, o.e1); \
370 auto temp = boost::apply_visitor(*this, o.e2); \
371 std::move(temp.begin(), temp.end(), std::back_inserter(result)); \
372 result.emplace_back(op_code::name); \
373 return result; \
374 }
375
377
378#undef EMIT_OP_
379
380 /***
381 * We make &&, ||, and ? shortcut
382 */
383 std::vector<instruction> operator()(const and_op & o) const {
384 auto result = boost::apply_visitor(*this, o.e1);
385 auto second = boost::apply_visitor(*this, o.e2);
386 bool second_is_boolean = boost::apply_visitor(is_boolean(), o.e2);
387
388 uint sec_size = static_cast<uint>(second.size());
389 if (!second_is_boolean) { sec_size += 2; }
390
391 result.emplace_back(skip_if{2});
392 result.emplace_back(constant{0});
393 result.emplace_back(skip{sec_size});
394
395 std::move(second.begin(), second.end(), std::back_inserter(result));
396 if (!second_is_boolean) {
397 result.emplace_back(op_code::not_op);
398 result.emplace_back(op_code::not_op);
399 }
400
401 return result;
402 }
403
404 std::vector<instruction> operator()(const or_op & o) const {
405 auto result = boost::apply_visitor(*this, o.e1);
406 auto second = boost::apply_visitor(*this, o.e2);
407 bool second_is_boolean = boost::apply_visitor(is_boolean(), o.e2);
408
409 uint sec_size = static_cast<uint>(second.size());
410 if (!second_is_boolean) { sec_size += 2; }
411
412 result.emplace_back(skip_if_not{2});
413 result.emplace_back(constant{1});
414 result.emplace_back(skip{sec_size});
415
416 std::move(second.begin(), second.end(), std::back_inserter(result));
417 if (!second_is_boolean) {
418 result.emplace_back(op_code::not_op);
419 result.emplace_back(op_code::not_op);
420 }
421
422 return result;
423 }
424
425 std::vector<instruction> operator()(const ternary_op & o) const {
426 auto result = boost::apply_visitor(*this, o.e1);
427 auto tbranch = boost::apply_visitor(*this, o.e2);
428 auto fbranch = boost::apply_visitor(*this, o.e3);
429
430 uint tsize = static_cast<uint>(tbranch.size());
431 uint fsize = static_cast<uint>(fbranch.size());
432
433 // We use jump if / jump if not in the way that will let us put the shorter branch first.
434 if (tbranch.size() > fbranch.size()) {
435 // + 1 to size because we have to put a jump at end of this branch also
436 result.emplace_back(skip_if{fsize + 1});
437 std::move(fbranch.begin(), fbranch.end(), std::back_inserter(result));
438 result.emplace_back(skip{tsize});
439 std::move(tbranch.begin(), tbranch.end(), std::back_inserter(result));
440 } else {
441 result.emplace_back(skip_if_not{tsize + 1});
442 std::move(tbranch.begin(), tbranch.end(), std::back_inserter(result));
443 result.emplace_back(skip{fsize});
444 std::move(fbranch.begin(), fbranch.end(), std::back_inserter(result));
445 }
446 return result;
447 }
448};
449
450/***
451 * Actual stack machine
452 */
453
454class stack_machine : public boost::static_visitor<uint> {
455 std::vector<instruction> instruction_seq_;
456 std::vector<uint> stack_;
457 uint n_value_;
458
459#ifdef SPIRIT_PO_DEBUG
460public:
461 void debug_print_instructions() const {
462 std::cerr << "Instruction sequence:\n";
463 for (const auto & i : instruction_seq_) {
464 std::cerr << debug_string(i) << std::endl;
465 }
466 }
467private:
468
469#define MACHINE_ASSERT(X) \
470 do { \
471 if (!(X)) { \
472 std::cerr << "Stack machine failure:\n"; \
473 debug_print_instructions(); \
474 assert(false && #X); \
475 } \
476 } while(0)
477
478#else // SPIRIT_PO_DEBUG
479
480#define MACHINE_ASSERT(...) do {} while(0)
481
482#endif // SPIRIT_PO_DEBUG
483
484 uint pop_one() {
485 MACHINE_ASSERT(stack_.size());
486
487 uint result = stack_.back();
488 stack_.resize(stack_.size() - 1);
489 return result;
490 }
491
492public:
493 explicit stack_machine(const expr & e)
494 : instruction_seq_(boost::apply_visitor(emitter(), e))
495 , stack_()
496 , n_value_()
497 {}
498
499 /***
500 * operator() takes the instruction that we should execute
501 * It should perform the operation adjusting the stack
502 * It returns the amount by which we should increment the
503 * program counter.
504 */
505 uint operator()(const constant & c) {
506 stack_.emplace_back(c.value);
507 return 1;
508 }
509
510 uint operator()(const skip & s) {
511 return 1 + s.distance;
512 }
513
514 uint operator()(const skip_if & s) {
515 return 1 + (pop_one() ? s.distance : 0);
516 }
517
518 uint operator()(const skip_if_not & s) {
519 return 1 + (pop_one() ? 0 : s.distance);
520 }
521
522 uint operator()(op_code oc) {
523 switch (oc) {
524 case op_code::n_var: {
525 stack_.emplace_back(n_value_);
526 return 1;
527 }
528 case op_code::not_op: {
529 MACHINE_ASSERT(stack_.size());
530 stack_.back() = !stack_.back();
531 return 1;
532 }
533#define STACK_MACHINE_CASE_(name, op) \
534 case op_code::name: { \
535 MACHINE_ASSERT(stack_.size() >= 2); \
536 uint parm2 = pop_one(); \
537 \
538 if (op_code::name == op_code::mod_op) { \
539 MACHINE_ASSERT(parm2 && "Division by zero when evaluating gettext plural form expression"); \
540 } \
541 \
542 stack_.back() = (stack_.back() op parm2); \
543 return 1; \
544 }
545
547
548#undef STACK_MACHINE_CASE_
549 }
550 MACHINE_ASSERT(false);
551 return 1;
552 }
553
554 uint compute(uint arg) {
555 n_value_ = arg;
556 stack_.resize(0);
557 uint pc = 0;
558 while (pc < instruction_seq_.size()) {
559 pc += boost::apply_visitor(*this, instruction_seq_[pc]);
560 }
561 MACHINE_ASSERT(pc == instruction_seq_.size());
562 MACHINE_ASSERT(stack_.size() == 1);
563 return stack_[0];
564 }
565};
566
567#undef MACHINE_ASSERT
568
569// X macros not used anymore
570#undef FOREACH_SPIRIT_PO_BINARY_OP
571#undef FOREACH_SPIRIT_PO_CONJUNCTION
572
573} // end namespace default_plural_forms
574
575} // end namespace spirit_po
576
577#endif // SPIRIT_PO_DEFAULT_PLURAL_FORMS_EXPRESSIONS_HPP_INCLUDED
double t
#define EVAL_OP_(name, OPERATOR)
#define EMIT_OP_(name, op)
#define ENUMERATE(X, Y)
#define WRAP_(name, op)
#define DECL_(name, op)
#define FOREACH_SPIRIT_PO_CONJUNCTION(X_)
#define STACK_MACHINE_CASE_(name, op)
#define ADAPT_STRUCT_(name, op)
#define MACHINE_ASSERT(...)
#define FOREACH_SPIRIT_PO_BINARY_OP(X_)
#define FWD_DECL_(name, op)
expression_ptr expr_
Definition function.cpp:819
Definition span.hpp:53
boost::variant< constant, n_var, boost::recursive_wrapper< not_op >, boost::recursive_wrapper< ternary_op > > expr
unsigned int uint
Definition catalog.hpp:39
V::result_t apply_visitor(typename V::param_t state, T &&... args)
Helper function to apply the result of a specified visitor to a variable_info object.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
static map_location::direction n
static map_location::direction s
static std::size_t compute(std::string expr, std::size_t ref1, std::size_t ref2=0)
Definition theme.cpp:60
#define i
#define e
#define c