• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyberZHG / MIXAL / 38359070436

13 Oct 2026 06:51AM UTC coverage: 98.615% (-0.06%) from 98.676%
38359070436

Pull #32

github

web-flow
Merge d0d5c42b4 into 23d627bdb
Pull Request #32: Add more MIX examples

200 of 205 new or added lines in 8 files covered. (97.56%)

1 existing line in 1 file now uncovered.

3915 of 3970 relevant lines covered (98.61%)

18050.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.67
/src/expression.cpp
1
#include <cassert>
2
#include <cstdlib>
3
#include <limits>
4
#include <iostream>
5
#include "expression.h"
6

7
/**
8
 * @file
9
 * @brief The parsing and evaluation of expressions.
10
 */
11

12
namespace mixal {
13

14
Expression::Expression() : _evaluated(false), _literalConstant(false) {
6,706 ✔
15
}
6,706 ✔
16

17
Expression::Expression(const std::string& expression, const std::string& lineSymbol) :
50 ✔
18
    _evaluated(false), _literalConstant(false) {
50 ✔
19
    parse(expression, lineSymbol);
50 ✔
20
}
62 ✔
21

22
Expression Expression::getConstExpression(const AtomicValue& value) {
107 ✔
23
    auto expr = Expression();
107 ✔
24
    expr._evaluated = true;
107 ✔
25
    expr._result = value;
107 ✔
26
    expr._atomics.emplace_back(AtomicType::INTEGER, value.value);
107 ✔
27
    return expr;
214 ✔
28
}
107 ✔
29

30
Expression Expression::getConstExpression(const std::string& symbol) {
77 ✔
31
    auto expr = Expression();
77 ✔
32
    expr._atomics.emplace_back(AtomicType::SYMBOL, symbol);
77 ✔
33
    expr._depends.insert(symbol);
77 ✔
34
    return expr;
154 ✔
35
}
77 ✔
36

37
Expression Expression::getConstOffsetExpression(const std::string& symbol, int32_t offset) {
617 ✔
38
    auto expr = Expression();
617 ✔
39
    expr._atomics = {Atomic(AtomicType::SYMBOL, symbol), Atomic(AtomicType::INTEGER, offset)};
1,851 ✔
40
    expr._operations = {Operation::ADD};
617 ✔
41
    expr._depends.insert(symbol);
617 ✔
42
    return expr;
1,234 ✔
43
}
1,234 ✔
44

45
bool Expression::isValidFirst(char ch) {
2,952 ✔
46
    return isalnum(ch) || ch == '+' || ch == '-' || ch == '*' || ch == '=';
2,952 ✔
47
}
48

49
bool Expression::isValidChar(char ch) {
2,010 ✔
50
    return isValidFirst(ch) || ch == '/' || ch == ':';
2,010 ✔
51
}
52

53
/** The states while parsing the expression.
54
 * 
55
 * ```
56
 * EXPRESSION -> + EXPRESSION
57
 *             | - EXPRESSION
58
 *             | EXPRESSION OPERATOR EXPRESSION
59
 *             | ATOMIC
60
 * OPERATOR -> + | - | * | / | // | :
61
 * ATOMIC -> INTEGER
62
 *         | SYMBOL
63
 *         | *
64
 * INTEGER -> DIGIT
65
 *          | DIGIT INTEGER
66
 * SYMBOL -> ALPHA
67
 *         | DIGIT
68
 *         | ALPHS SYMBOL
69
 *         | DIGIT SYMBOL
70
 * DIGIT -> [0-9]
71
 * ALPHA -> [A-Z]
72
 * ```
73
 * 
74
 * However, multiple signs like `++` or `---` will be rejected as they are useless.
75
 */
76
enum class ExprParseState {
77
    START,      /**< The start state. */
78
    ATOMIC,     /**< Parsing the atomic. */
79
    OPERATION,  /**< Parsing the operation. */
80
    END,        /**< The end of parsing. */
81
};
82

83
/** Whether an operation can start with the given character. */
84
inline bool isOperationFirst(char ch) {
160 ✔
85
    return ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == ':';
160 ✔
86
}
87

88
void Expression::parse(const std::string& expression, const std::string& lineSymbol) {
1,020 ✔
89
    this->reset();
1,020 ✔
90
    const char END_CHAR = '#';  // The character that indicates the end of the input.
1,020 ✔
91
    constexpr int INIT_INDEX = -1;  // The uninitialized index value.
1,020 ✔
92
    auto state = ExprParseState::START;
1,020 ✔
93
    int lastAtomicStart = INIT_INDEX;     // The start index of the last atomic.
1,020 ✔
94
    int lastOperationStart = INIT_INDEX;  // The start index of the last operation.
1,020 ✔
95

96
    // Check whether this is a literal constant that starts and ends with a `=`.
97
    int start = 0;
1,020 ✔
98
    int exprLen = static_cast<int>(expression.size());
1,020 ✔
99
    if (exprLen > 2 && expression[0] == '=' && expression[exprLen - 1] == '=') {
1,020 ✔
100
        _literalConstant = true;
79 ✔
101
        start = 1;
79 ✔
102
        --exprLen;
79 ✔
103
    }
104
    if (expression.find_first_of(",(", start) < static_cast<size_t>(exprLen)) {
1,020 ✔
105
        parseWValue(expression.substr(start, exprLen - start), start, lineSymbol);
16 ✔
106
        return;
12 ✔
107
    }
108

109
    // Parsing the expression.
110
    for (int i = start; i <= exprLen; ++i) {
4,976 ✔
111
        char ch = i < exprLen ? expression[i] : END_CHAR;
3,989 ✔
112
        switch (state) {
3,989 ✔
113
        case ExprParseState::START:
1,006 ✔
114
            if (ch == '+' || ch == '-' || ch == '*' || isalnum(ch)) {
1,006 ✔
115
                state = ExprParseState::ATOMIC;
1,003 ✔
116
                lastAtomicStart = i;
1,003 ✔
117
            } else if (ch == END_CHAR) {
3 ✔
118
                throw ExpressionError(i, "The expression is empty.");
6 ✔
119
            } else {
120
                throw ExpressionError(i,
121
                    "Invalid character found at the start of the expression: " + std::string(1, ch));
3 ✔
122
            }
123
            break;
1,003 ✔
124

125
        case ExprParseState::ATOMIC:
2,814 ✔
126
            assert(lastAtomicStart != INIT_INDEX);
127
            if (expression[lastAtomicStart] == '*' ||
5,594 ✔
128
                (lastAtomicStart + 1 < i &&
2,780 ✔
129
                 (expression[lastAtomicStart] == '+' || expression[lastAtomicStart] == '-') &&
1,663 ✔
130
                 expression[lastAtomicStart + 1] == '*')) {
172 ✔
131
                // The atomic that matches `*`, `+*`, or `-*`.
132
                if (ch == END_CHAR) {
38 ✔
133
                    state = ExprParseState::END;
16 ✔
134
                } else if (isOperationFirst(ch)) {
22 ✔
135
                    state = ExprParseState::OPERATION;
21 ✔
136
                    lastOperationStart = i;
21 ✔
137
                } else {
138
                    throw ExpressionError(i,
139
                        "Invalid character found while trying to find an operation: " + std::string(1, ch));
3 ✔
140
                }
141
                const bool negative = expression[lastAtomicStart] == '-';
37 ✔
142
                _atomics.emplace_back(AtomicType::ASTERISK, lineSymbol, negative);
37 ✔
143
                _depends.insert(lineSymbol);
37 ✔
144
            } else if (isalnum(ch) ||
3,893 ✔
145
                ((expression[lastAtomicStart] == '+' || expression[lastAtomicStart] == '-') &&
1,117 ✔
146
                 lastAtomicStart + 1 == i && ch == '*')) {
89 ✔
147
            } else {
148
                if (ch == END_CHAR) {
1,113 ✔
149
                    state = ExprParseState::END;
975 ✔
150
                } else if (isOperationFirst(ch)) {
138 ✔
151
                    state = ExprParseState::OPERATION;
136 ✔
152
                    lastOperationStart = i;
136 ✔
153
                } else {
154
                    throw ExpressionError(i,
155
                        "Invalid character found while trying to find an operation: " + std::string(1, ch));
6 ✔
156
                }
157
                bool isInteger = true, negative = false;
1,111 ✔
158
                int32_t integerValue = 0;
1,111 ✔
159
                if (expression[lastAtomicStart] == '-') {
1,111 ✔
160
                    negative = true;
79 ✔
161
                    ++lastAtomicStart;
79 ✔
162
                } else if (expression[lastAtomicStart] == '+') {
1,032 ✔
163
                    ++lastAtomicStart;
6 ✔
164
                }
165
                if (lastAtomicStart == i) {
1,111 ✔
166
                    throw ExpressionError(i, "The atomic is empty: " +
8 ✔
167
                        std::to_string(expression[lastAtomicStart - 1]));
12 ✔
168
                }
169
                // Check the type of the atomic. Note that symbols can start with digits.
170
                for (int j = lastAtomicStart; j < i; ++j) {
2,978 ✔
171
                    if (isalpha(expression[j])) {
2,108 ✔
172
                        isInteger = false;
236 ✔
173
                        break;
236 ✔
174
                    }
175
                    const int32_t digit = expression[j] - '0';
1,872 ✔
176
                    if (integerValue > (std::numeric_limits<int32_t>::max() - digit) / 10) {
1,872 ✔
177
                        throw ExpressionError(i, "The integer value is too large: " +
2 ✔
178
                            expression.substr(lastAtomicStart, i - lastAtomicStart));
3 ✔
179
                    }
180
                    integerValue = integerValue * 10 + digit;
1,871 ✔
181
                }
182
                if (isInteger) {
1,106 ✔
183
                    _atomics.emplace_back(AtomicType::INTEGER, integerValue, negative);
870 ✔
184
                } else {
185
                    auto symbol = expression.substr(lastAtomicStart, i - lastAtomicStart);
236 ✔
186
                    _atomics.emplace_back(AtomicType::SYMBOL, symbol, negative);
236 ✔
187
                    _depends.insert(symbol);
236 ✔
188
                }
236 ✔
189
            }
190
            break;
2,806 ✔
191

192
        case ExprParseState::OPERATION:
165 ✔
193
            assert(lastOperationStart != INIT_INDEX);
194
            if (ch == '/') {
165 ✔
195
                // The `/` could be continuous for `//`.
196
            } else if (ch == END_CHAR) {
156 ✔
197
                throw ExpressionError(i, "No atomic found after the binary operator");
15 ✔
198
            } else {
199
                state = ExprParseState::ATOMIC;
151 ✔
200
                lastAtomicStart = i;
151 ✔
201
                if (i - lastOperationStart == 1) {
151 ✔
202
                    switch (expression[lastOperationStart]) {
145 ✔
203
                    case '+':
38 ✔
204
                        _operations.emplace_back(Operation::ADD);
38 ✔
205
                        break;
38 ✔
206
                    case '-':
14 ✔
207
                        _operations.emplace_back(Operation::SUBTRACT);
14 ✔
208
                        break;
14 ✔
209
                    case '*':
15 ✔
210
                        _operations.emplace_back(Operation::MULTIPLY);
15 ✔
211
                        break;
15 ✔
212
                    case '/':
3 ✔
213
                        _operations.emplace_back(Operation::FLOOR_DIV);
3 ✔
214
                        break;
3 ✔
215
                    case ':':
75 ✔
216
                        _operations.emplace_back(Operation::FIELD);
75 ✔
217
                        break;
75 ✔
218
                    }
219
                } else if (i - lastOperationStart == 2 && expression[lastOperationStart] == '/') {
6 ✔
220
                    _operations.emplace_back(Operation::FLOAT_DIV);
3 ✔
221
                } else {
222
                    throw ExpressionError(i, "Unknown binary operator found");
9 ✔
223
                }
224
            }
225
            break;
157 ✔
226

227
        case ExprParseState::END:
4 ✔
228
            break;
4 ✔
229
        }
230
    }
231
    // The parsing should always end with END state.
232
    assert(state == ExprParseState::END);
233
    // Since all the operations are binary, there should be exactly one more operations than atomics.
234
    assert(_atomics.size() == _operations.size() + 1);
235
}
236

237
void Expression::parseWValue(const std::string& expression, const int offset, const std::string& lineSymbol) {
14 ✔
238
    auto parsePart = [&](const std::string& part, const size_t partOffset) {
37 ✔
239
        try {
240
            return Expression(part, lineSymbol);
73 ✔
241
        } catch (const ExpressionError& e) {
1 ✔
242
            throw ExpressionError(offset + static_cast<int>(partOffset) + e.index(), e.what());
3 ✔
243
        }
1 ✔
244
    };
14 ✔
245
    size_t partStart = 0;
14 ✔
246
    while (partStart <= expression.size()) {
34 ✔
247
        size_t partEnd = expression.find(',', partStart);
22 ✔
248
        if (partEnd == std::string::npos) {
22 ✔
249
            partEnd = expression.size();
14 ✔
250
        }
251
        const std::string part = expression.substr(partStart, partEnd - partStart);
22 ✔
252
        const size_t open = part.find('(');
22 ✔
253
        if (open == std::string::npos) {
22 ✔
254
            _wValueExpressions.emplace_back(parsePart(part, partStart));
5 ✔
255
            _wValueFields.emplace_back(getConstExpression(AtomicValue(5)));
4 ✔
256
        } else {
257
            if (part.back() != ')' || part.find(')') != part.size() - 1) {
17 ✔
258
                throw ExpressionError(offset + static_cast<int>(partEnd), "Invalid field in W-value: " + part);
1 ✔
259
            }
260
            _wValueExpressions.emplace_back(parsePart(part.substr(0, open), partStart));
16 ✔
261
            _wValueFields.emplace_back(parsePart(part.substr(open + 1, part.size() - open - 2), partStart + open + 1));
16 ✔
262
        }
263
        _depends.insert(_wValueExpressions.back().depends().begin(), _wValueExpressions.back().depends().end());
20 ✔
264
        _depends.insert(_wValueFields.back().depends().begin(), _wValueFields.back().depends().end());
20 ✔
265
        partStart = partEnd + 1;
20 ✔
266
    }
22 ✔
267
}
12 ✔
268

269
bool Expression::evaluateWValue(const std::unordered_map<std::string, AtomicValue>& constants) {
24 ✔
270
    bool negative = false;
24 ✔
271
    int32_t bytes[6] = {};
24 ✔
272
    for (size_t i = 0; i < _wValueExpressions.size(); ++i) {
56 ✔
273
        auto& value = _wValueExpressions[i];
35 ✔
274
        auto& field = _wValueFields[i];
35 ✔
275
        if ((!value.evaluated() && !value.evaluate(constants)) || (!field.evaluated() && !field.evaluate(constants))) {
35 ✔
276
            return false;
2 ✔
277
        }
278
        const int32_t fieldValue = field.result().value;
33 ✔
279
        int32_t left = fieldValue / 8;
33 ✔
280
        const int32_t right = fieldValue % 8;
33 ✔
281
        if (fieldValue < 0 || left > right || right > 5) {
33 ✔
282
            throw ExpressionError(0, "Invalid field in W-value: " + std::to_string(fieldValue));
1 ✔
283
        }
284
        if (left == 0) {
32 ✔
285
            negative = value.result().negative;
13 ✔
286
            left = 1;
13 ✔
287
        }
288
        int32_t magnitude = std::abs(value.result().value);
32 ✔
289
        for (int32_t k = right; k >= left; --k) {
89 ✔
290
            bytes[k] = magnitude % 64;
57 ✔
291
            magnitude /= 64;
57 ✔
292
        }
293
    }
294
    int32_t magnitude = 0;
21 ✔
295
    for (int32_t k = 1; k <= 5; ++k) {
126 ✔
296
        magnitude = magnitude * 64 + bytes[k];
105 ✔
297
    }
298
    _result.negative = negative;
21 ✔
299
    _result.value = negative ? -magnitude : magnitude;
21 ✔
300
    _evaluated = true;
21 ✔
301
    return true;
21 ✔
302
}
303

304
bool Expression::evaluate(const std::unordered_map<std::string, AtomicValue>& constants) {
2,707 ✔
305
    if (isWValue()) {
2,707 ✔
306
        return evaluateWValue(constants);
24 ✔
307
    }
308
    if (_atomics.empty()) {
2,683 ✔
309
        return false;
1 ✔
310
    }
311
    auto evalAtomic = [&constants](const Atomic& atomic, AtomicValue* result) -> bool {
3,605 ✔
312
        if (atomic.type == AtomicType::INTEGER) {
3,605 ✔
313
            result->negative = atomic.negative;
2,232 ✔
314
            result->value = atomic.integer;
2,232 ✔
315
            if (atomic.negative) {
2,232 ✔
316
                result->value = -result->value;
104 ✔
317
            }
318
        } else {
319
            auto it = constants.find(atomic.symbol);
1,373 ✔
320
            if (it == constants.end()) {
1,373 ✔
321
                return false;
258 ✔
322
            }
323
            *result = it->second;
1,115 ✔
324
            if (atomic.negative) {
1,115 ✔
325
                result->negative = !result->negative;
2 ✔
326
                result->value = -result->value;
2 ✔
327
            }
328
        }
329
        return true;
3,347 ✔
330
    };
2,682 ✔
331
    AtomicValue first, second;
2,682 ✔
332
    if (!evalAtomic(_atomics[0], &first)) {
2,682 ✔
333
        return false;
256 ✔
334
    }
335
    for (size_t i = 0; i < _operations.size(); ++i) {
3,347 ✔
336
        if (!evalAtomic(_atomics[i + 1], &second)) {
923 ✔
337
            return false;
2 ✔
338
        }
339
        switch (_operations[i]) {
921 ✔
340
        case Operation::ADD:
808 ✔
341
            first.value += second.value;
808 ✔
342
            break;
808 ✔
343
        case Operation::SUBTRACT:
17 ✔
344
            first.value -= second.value;
17 ✔
345
            break;
17 ✔
346
        case Operation::MULTIPLY:
14 ✔
347
            first.value *= second.value;
14 ✔
348
            break;
14 ✔
349
        case Operation::FLOOR_DIV:
2 ✔
350
            first.value /= second.value;
2 ✔
351
            break;
2 ✔
352
        case Operation::FLOAT_DIV:
3 ✔
353
            first.value = static_cast<int32_t>((static_cast<int64_t>(first.value) << 30) / second.value);
3 ✔
354
            break;
3 ✔
355
        case Operation::FIELD:
77 ✔
356
            first.value = first.value * 8 + second.value;
77 ✔
357
            break;
77 ✔
358
        }
359
        if (first.value > 0) {
921 ✔
360
            first.negative = false;
888 ✔
361
        } else if (first.value < 0) {
33 ✔
362
            first.negative = true;
12 ✔
363
        }
364
    }
365
    _evaluated = true;
2,424 ✔
366
    _result = first;
2,424 ✔
367
    return true;
2,424 ✔
368
}
369

370
void Expression::replaceSymbol(const std::unordered_map<std::string, std::string>& mapping) {
1,164 ✔
371
    if (isWValue()) {
1,164 ✔
372
        _depends.clear();
12 ✔
373
        for (auto* parts : {&_wValueExpressions, &_wValueFields}) {
36 ✔
374
            for (auto& part : *parts) {
60 ✔
375
                part.replaceSymbol(mapping);
36 ✔
376
                _depends.insert(part.depends().begin(), part.depends().end());
36 ✔
377
            }
378
        }
379
        return;
12 ✔
380
    }
381
    for (auto& atomic : _atomics) {
2,314 ✔
382
        if (atomic.type == AtomicType::SYMBOL) {
1,162 ✔
383
            auto it = mapping.find(atomic.symbol);
410 ✔
384
            if (it != mapping.end()) {
410 ✔
385
                atomic.replaceSymbol(it->second);
20 ✔
386
                _depends.erase(it->first);
20 ✔
387
                _depends.insert(it->second);
20 ✔
388
            }
389
        }
390
    }
391
}
392

393
void Expression::reset() {
1,020 ✔
394
    _evaluated = false;
1,020 ✔
395
    _literalConstant = false;
1,020 ✔
396
    _depends.clear();
1,020 ✔
397
    _atomics.clear();
1,020 ✔
398
    _operations.clear();
1,020 ✔
399
    _wValueExpressions.clear();
1,020 ✔
400
    _wValueFields.clear();
1,020 ✔
401
}
1,020 ✔
402

403
bool Expression::operator==(const Expression& expression) const {
4 ✔
404
    if (_wValueExpressions != expression._wValueExpressions || _wValueFields != expression._wValueFields) {
4 ✔
NEW
405
        return false;
×
406
    }
407
    if (_atomics.size() != expression._atomics.size()) {
4 ✔
408
        return false;
1 ✔
409
    }
410
    for (size_t i = 0; i < _atomics.size(); ++i) {
8 ✔
411
        if (_atomics[i] != expression._atomics[i]) {
5 ✔
412
            return false;
×
413
        }
414
    }
415
    for (size_t i = 0; i < _operations.size(); ++i) {
4 ✔
416
        if (_operations[i] != expression._operations[i]) {
2 ✔
417
            return false;
1 ✔
418
        }
419
    }
420
    return true;
2 ✔
421
}
422

423
bool Expression::operator!=(const Expression& expression) const {
2 ✔
424
    return !((*this) == expression);
2 ✔
425
}
426

427
std::ostream& operator<<(std::ostream& out, Operation operation) {
×
428
    switch (operation) {
×
429
    case Operation::ADD: out << '+'; break;
×
430
    case Operation::SUBTRACT: out << '-'; break;
×
431
    case Operation::MULTIPLY: out << '*'; break;
×
432
    case Operation::FLOOR_DIV: out << '/'; break;
×
NEW
433
    case Operation::FLOAT_DIV: out << "//"; break;
×
UNCOV
434
    case Operation::FIELD: out << ':'; break;
×
435
    }
436
    return out;
×
437
}
438

439
std::ostream& operator<<(std::ostream& out, const Expression& expression) {
4 ✔
440
    for (size_t i = 0; i < expression._wValueExpressions.size(); ++i) {
4 ✔
NEW
441
        if (i > 0) {
×
NEW
442
            out << ',';
×
443
        }
NEW
444
        out << expression._wValueExpressions[i] << '(' << expression._wValueFields[i] << ')';
×
445
    }
446
    if (!expression._atomics.empty()) {
4 ✔
447
        out << expression._atomics[0];
4 ✔
448
    }
449
    for (size_t i = 0; i < expression._operations.size(); ++i) {
4 ✔
450
        out << expression._operations[i];
×
451
        out << expression._atomics[i + 1];
×
452
    }
453
    return out;
4 ✔
454
}
455

456
}  // namespace mixal
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc