aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/restricted/boost/locale/src/shared/mo_lambda.cpp
blob: e2a16c8c60ec36b1d6b4421b3843198501d92280 (plain) (blame)
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
//
// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
// Copyright (c) 2021-2023 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include "mo_lambda.hpp"
#include <boost/assert.hpp>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <limits>
#include <stdexcept>

#ifdef BOOST_MSVC
#    pragma warning(disable : 4512) // assignment operator could not be generated
#endif

namespace boost { namespace locale { namespace gnu_gettext { namespace lambda {

    namespace { // anon
        template<class TExp, typename... Ts>
        expr_ptr make_expr(Ts&&... ts)
        {
            return expr_ptr(new TExp(std::forward<Ts>(ts)...));
        }

        struct identity final : expr {
            value_type operator()(value_type n) const override { return n; }
        };

        using sub_expr_type = plural_expr;

        template<class Functor>
        struct unary final : expr, Functor {
            unary(expr_ptr p) : op1(std::move(p)) {}
            value_type operator()(value_type n) const override { return Functor::operator()(op1(n)); }

        protected:
            sub_expr_type op1;
        };

        template<class Functor, bool returnZeroOnZero2ndArg = false>
        struct binary final : expr, Functor {
            binary(expr_ptr p1, expr_ptr p2) : op1(std::move(p1)), op2(std::move(p2)) {}
            value_type operator()(value_type n) const override
            {
                const auto v1 = op1(n);
                const auto v2 = op2(n);
                BOOST_LOCALE_START_CONST_CONDITION
                if(returnZeroOnZero2ndArg && v2 == 0)
                    return 0;
                BOOST_LOCALE_END_CONST_CONDITION
                return Functor::operator()(v1, v2);
            }

        protected:
            sub_expr_type op1, op2;
        };

        struct number final : expr {
            number(value_type v) : val(v) {}
            value_type operator()(value_type /*n*/) const override { return val; }

        private:
            value_type val;
        };

        struct conditional final : public expr {
            conditional(expr_ptr p1, expr_ptr p2, expr_ptr p3) :
                op1(std::move(p1)), op2(std::move(p2)), op3(std::move(p3))
            {}
            value_type operator()(value_type n) const override { return op1(n) ? op2(n) : op3(n); }

        private:
            sub_expr_type op1, op2, op3;
        };

        using token_t = int;
        enum : token_t { END = 0, GTE = 256, LTE, EQ, NEQ, AND, OR, NUM, VARIABLE };

        expr_ptr bin_factory(const token_t value, expr_ptr left, expr_ptr right)
        {
#define BINOP_CASE(match, cls) \
    case match: return make_expr<cls>(std::move(left), std::move(right))
            // Special cases: Avoid division by zero
            using divides = binary<std::divides<expr::value_type>, true>;
            using modulus = binary<std::modulus<expr::value_type>, true>;
            switch(value) {
                BINOP_CASE('/', divides);
                BINOP_CASE('*', binary<std::multiplies<expr::value_type>>);
                BINOP_CASE('%', modulus);
                BINOP_CASE('+', binary<std::plus<expr::value_type>>);
                BINOP_CASE('-', binary<std::minus<expr::value_type>>);
                BINOP_CASE('>', binary<std::greater<expr::value_type>>);
                BINOP_CASE('<', binary<std::less<expr::value_type>>);
                BINOP_CASE(GTE, binary<std::greater_equal<expr::value_type>>);
                BINOP_CASE(LTE, binary<std::less_equal<expr::value_type>>);
                BINOP_CASE(EQ, binary<std::equal_to<expr::value_type>>);
                BINOP_CASE(NEQ, binary<std::not_equal_to<expr::value_type>>);
                BINOP_CASE(AND, binary<std::logical_and<expr::value_type>>);
                BINOP_CASE(OR, binary<std::logical_or<expr::value_type>>);
                default: throw std::logic_error("Unexpected binary operator"); // LCOV_EXCL_LINE
            }
#undef BINOP_CASE
        }

        template<size_t size>
        bool is_in(const token_t token, const token_t (&tokens)[size])
        {
            for(const auto el : tokens) {
                if(token == el)
                    return true;
            }
            return false;
        }

        class tokenizer {
        public:
            tokenizer(const char* s) : text_(s), next_tocken_(0), numeric_value_(0) { step(); }
            token_t get(long long* val = nullptr)
            {
                const token_t res = next(val);
                step();
                return res;
            }
            token_t next(long long* val = nullptr) const
            {
                if(val && next_tocken_ == NUM)
                    *val = numeric_value_;
                return next_tocken_;
            }

        private:
            const char* text_;
            token_t next_tocken_;
            long long numeric_value_;

            static constexpr bool is_blank(char c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t'; }
            static constexpr bool is_digit(char c) { return '0' <= c && c <= '9'; }
            template<size_t size>
            static bool is(const char* s, const char (&search)[size])
            {
                return strncmp(s, search, size - 1) == 0;
            }
            void step()
            {
                while(is_blank(*text_))
                    text_++;
                const char* text = text_;
                if(is(text, "&&")) {
                    text_ += 2;
                    next_tocken_ = AND;
                } else if(is(text, "||")) {
                    text_ += 2;
                    next_tocken_ = OR;
                } else if(is(text, "<=")) {
                    text_ += 2;
                    next_tocken_ = LTE;
                } else if(is(text, ">=")) {
                    text_ += 2;
                    next_tocken_ = GTE;
                } else if(is(text, "==")) {
                    text_ += 2;
                    next_tocken_ = EQ;
                } else if(is(text, "!=")) {
                    text_ += 2;
                    next_tocken_ = NEQ;
                } else if(*text == 'n') {
                    text_++;
                    next_tocken_ = VARIABLE;
                } else if(is_digit(*text)) {
                    char* tmp_ptr;
                    // strtoll not always available -> parse as unsigned long
                    const auto value = std::strtoul(text, &tmp_ptr, 10);
                    // Saturate in case long=long long
                    numeric_value_ = std::min<unsigned long long>(std::numeric_limits<long long>::max(), value);
                    text_ = tmp_ptr;
                    next_tocken_ = NUM;
                } else if(*text == '\0')
                    next_tocken_ = END;
                else {
                    next_tocken_ = *text;
                    text_++;
                }
            }
        };

        constexpr token_t level6[] = {'*', '/', '%'};
        constexpr token_t level5[] = {'+', '-'};
        constexpr token_t level4[] = {'<', '>', GTE, LTE};
        constexpr token_t level3[] = {EQ, NEQ};
        constexpr token_t level2[] = {AND};
        constexpr token_t level1[] = {OR};

        class parser {
        public:
            parser(const char* str) : t(str) {}

            expr_ptr compile()
            {
                expr_ptr res = cond_expr();
                if(res && t.next() != END)
                    return expr_ptr();
                return res;
            }

        private:
            expr_ptr value_expr()
            {
                expr_ptr op;
                if(t.next() == '(') {
                    t.get();
                    if(!(op = cond_expr()))
                        return expr_ptr();
                    if(t.get() != ')')
                        return expr_ptr();
                    return op;
                } else if(t.next() == NUM) {
                    expr::value_type value;
                    t.get(&value);
                    return make_expr<number>(value);
                } else if(t.next() == VARIABLE) {
                    t.get();
                    return make_expr<identity>();
                }
                return expr_ptr();
            }

            expr_ptr unary_expr()
            {
                constexpr token_t level_unary[] = {'!', '-'};
                if(is_in(t.next(), level_unary)) {
                    const token_t op = t.get();
                    expr_ptr op1 = unary_expr();
                    if(!op1)
                        return expr_ptr();
                    if(BOOST_LIKELY(op == '!'))
                        return make_expr<unary<std::logical_not<expr::value_type>>>(std::move(op1));
                    else {
                        BOOST_ASSERT(op == '-');
                        return make_expr<unary<std::negate<expr::value_type>>>(std::move(op1));
                    }
                } else
                    return value_expr();
            }

#define BINARY_EXPR(lvl, nextLvl, list)                           \
    expr_ptr lvl()                                                \
    {                                                             \
        expr_ptr op1 = nextLvl();                                 \
        if(!op1)                                                  \
            return expr_ptr();                                    \
        while(is_in(t.next(), list)) {                            \
            const token_t o = t.get();                            \
            expr_ptr op2 = nextLvl();                             \
            if(!op2)                                              \
                return expr_ptr();                                \
            op1 = bin_factory(o, std::move(op1), std::move(op2)); \
        }                                                         \
        return op1;                                               \
    }

            BINARY_EXPR(l6, unary_expr, level6);
            BINARY_EXPR(l5, l6, level5);
            BINARY_EXPR(l4, l5, level4);
            BINARY_EXPR(l3, l4, level3);
            BINARY_EXPR(l2, l3, level2);
            BINARY_EXPR(l1, l2, level1);
#undef BINARY_EXPR

            expr_ptr cond_expr()
            {
                expr_ptr cond;
                if(!(cond = l1()))
                    return expr_ptr();
                if(t.next() != '?')
                    return cond;
                t.get();
                expr_ptr case1, case2;
                if(!(case1 = cond_expr()))
                    return expr_ptr();
                if(t.get() != ':')
                    return expr_ptr();
                if(!(case2 = cond_expr()))
                    return expr_ptr();
                return make_expr<conditional>(std::move(cond), std::move(case1), std::move(case2));
            }

            tokenizer t;
        };

    } // namespace

    plural_expr compile(const char* str)
    {
        parser p(str);
        return plural_expr(p.compile());
    }

}}}} // namespace boost::locale::gnu_gettext::lambda