data/SieveScript.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "SieveScript.h" | ||
| 2 | |||
| 3 | #include <QLoggingCategory> | ||
| 4 | |||
| 5 |
4/7✓ Branch 2 → 3 taken 10 times.
✓ Branch 2 → 8 taken 62 times.
✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 10 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
72 | Q_LOGGING_CATEGORY(lcSieveParse, "mailjd.sieve.parse") |
| 6 | |||
| 7 | namespace Sieve { | ||
| 8 | |||
| 9 | 160108 | QStringList Argument::values() const { | |
| 10 | 160108 | QStringList result; | |
| 11 |
2/2✓ Branch 8 → 4 taken 435784 times.
✓ Branch 8 → 9 taken 160108 times.
|
595892 | for (const StringValue &string : strings) |
| 12 |
1/1✓ Branch 5 → 6 taken 435784 times.
|
435784 | result.append(string.value); |
| 13 | 160108 | return result; | |
| 14 | ✗ | } | |
| 15 | |||
| 16 | namespace { | ||
| 17 | |||
| 18 | enum class TokenKind { | ||
| 19 | Identifier, | ||
| 20 | Tag, | ||
| 21 | Number, | ||
| 22 | QuotedString, | ||
| 23 | MultiLineString, | ||
| 24 | LeftBracket, | ||
| 25 | RightBracket, | ||
| 26 | LeftParen, | ||
| 27 | RightParen, | ||
| 28 | LeftBrace, | ||
| 29 | RightBrace, | ||
| 30 | Comma, | ||
| 31 | Semicolon, | ||
| 32 | End, | ||
| 33 | Invalid, | ||
| 34 | }; | ||
| 35 | |||
| 36 | struct Token { | ||
| 37 | TokenKind kind = TokenKind::End; | ||
| 38 | int begin = 0; | ||
| 39 | int end = 0; | ||
| 40 | QString text; // identifier/tag lower case, decoded string value, number text | ||
| 41 | quint64 number = 0; | ||
| 42 | QString error; // Invalid only | ||
| 43 | }; | ||
| 44 | |||
| 45 | 3310468 | bool isAlpha(char c) { | |
| 46 |
5/10✓ Branch 2 → 3 taken 2874311 times.
✓ Branch 2 → 4 taken 436157 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 7 taken 2874311 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 436157 times.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 436157 times.
|
3310468 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; |
| 47 | } | ||
| 48 | |||
| 49 |
4/4✓ Branch 2 → 3 taken 431189 times.
✓ Branch 2 → 5 taken 361082 times.
✓ Branch 3 → 4 taken 1347 times.
✓ Branch 3 → 5 taken 429842 times.
|
792271 | bool isDigit(char c) { return c >= '0' && c <= '9'; } |
| 50 | |||
| 51 | // Scans tokens and skips whitespace, hash comments and bracket comments. | ||
| 52 | class Lexer { | ||
| 53 | public: | ||
| 54 | 4204 | explicit Lexer(const QByteArray &source) : m_source(source) {} | |
| 55 | |||
| 56 | 1644854 | Token next() { | |
| 57 | 1644854 | int errorPos = 0; | |
| 58 |
1/1✓ Branch 2 → 3 taken 1644854 times.
|
1644854 | const QString triviaError = skipTrivia(&errorPos); |
| 59 | 1644854 | Token token; | |
| 60 | 1644854 | token.begin = m_pos; | |
| 61 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 10 taken 1644853 times.
|
1644854 | if (!triviaError.isEmpty()) { |
| 62 | 1 | token.begin = errorPos; | |
| 63 |
1/1✓ Branch 6 → 7 taken 1 time.
|
1 | return invalid(token, triviaError); |
| 64 | } | ||
| 65 |
2/2✓ Branch 11 → 12 taken 4155 times.
✓ Branch 11 → 13 taken 1640698 times.
|
1644853 | if (m_pos >= m_source.size()) { |
| 66 | 4155 | token.kind = TokenKind::End; | |
| 67 | 4155 | token.end = m_pos; | |
| 68 | 4155 | return token; | |
| 69 | } | ||
| 70 | 1640698 | const char c = m_source.at(m_pos); | |
| 71 |
11/11✓ Branch 14 → 15 taken 8561 times.
✓ Branch 14 → 19 taken 8562 times.
✓ Branch 14 → 23 taken 17619 times.
✓ Branch 14 → 27 taken 17619 times.
✓ Branch 14 → 31 taken 76135 times.
✓ Branch 14 → 35 taken 76048 times.
✓ Branch 14 → 39 taken 280427 times.
✓ Branch 14 → 43 taken 178839 times.
✓ Branch 14 → 47 taken 540092 times.
✓ Branch 14 → 52 taken 82019 times.
✓ Branch 14 → 57 taken 354777 times.
|
1640698 | switch (c) { |
| 72 | 8561 | case '[': return single(token, TokenKind::LeftBracket); | |
| 73 | 8562 | case ']': return single(token, TokenKind::RightBracket); | |
| 74 | 17619 | case '(': return single(token, TokenKind::LeftParen); | |
| 75 | 17619 | case ')': return single(token, TokenKind::RightParen); | |
| 76 | 76135 | case '{': return single(token, TokenKind::LeftBrace); | |
| 77 | 76048 | case '}': return single(token, TokenKind::RightBrace); | |
| 78 | 280427 | case ',': return single(token, TokenKind::Comma); | |
| 79 | 178839 | case ';': return single(token, TokenKind::Semicolon); | |
| 80 |
1/1✓ Branch 48 → 49 taken 540092 times.
|
540092 | case '"': return quotedString(token); |
| 81 |
1/1✓ Branch 53 → 54 taken 82019 times.
|
82019 | case ':': return tag(token); |
| 82 | 354777 | default: break; | |
| 83 | } | ||
| 84 |
2/2✓ Branch 59 → 60 taken 546 times.
✓ Branch 59 → 65 taken 354231 times.
|
354777 | if (isDigit(c)) |
| 85 |
1/1✓ Branch 61 → 62 taken 546 times.
|
546 | return numberToken(token); |
| 86 |
2/2✓ Branch 66 → 67 taken 354221 times.
✓ Branch 66 → 72 taken 10 times.
|
354231 | if (isAlpha(c)) |
| 87 |
1/1✓ Branch 68 → 69 taken 354221 times.
|
354221 | return identifier(token); |
| 88 | 10 | ++m_pos; | |
| 89 |
1/1✓ Branch 76 → 77 taken 10 times.
|
10 | return invalid(token, QStringLiteral("Unexpected character")); |
| 90 | 1644854 | } | |
| 91 | |||
| 92 | int position() const { return m_pos; } | ||
| 93 | void setPosition(int pos) { m_pos = pos; } | ||
| 94 | |||
| 95 | private: | ||
| 96 | 663810 | Token single(Token token, TokenKind kind) { | |
| 97 | 663810 | token.kind = kind; | |
| 98 | 663810 | ++m_pos; | |
| 99 | 663810 | token.end = m_pos; | |
| 100 | 663810 | return token; | |
| 101 | } | ||
| 102 | |||
| 103 | 28 | Token invalid(Token token, const QString &message) { | |
| 104 | 28 | token.kind = TokenKind::Invalid; | |
| 105 | 28 | token.end = qMax(m_pos, token.begin); | |
| 106 | 28 | token.error = message; | |
| 107 | 28 | return token; | |
| 108 | } | ||
| 109 | |||
| 110 | 1644854 | QString skipTrivia(int *errorPos) { | |
| 111 |
2/2✓ Branch 36 → 3 taken 2768228 times.
✓ Branch 36 → 37 taken 4155 times.
|
2772383 | while (m_pos < m_source.size()) { |
| 112 | 2768228 | const char c = m_source.at(m_pos); | |
| 113 |
8/8✓ Branch 4 → 5 taken 2168205 times.
✓ Branch 4 → 8 taken 600023 times.
✓ Branch 5 → 6 taken 2083118 times.
✓ Branch 5 → 8 taken 85087 times.
✓ Branch 6 → 7 taken 2082987 times.
✓ Branch 6 → 8 taken 131 times.
✓ Branch 7 → 8 taken 365115 times.
✓ Branch 7 → 9 taken 1717872 times.
|
2768228 | if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { |
| 114 | 1050356 | ++m_pos; | |
| 115 |
2/2✓ Branch 9 → 10 taken 77046 times.
✓ Branch 9 → 16 taken 1640826 times.
|
1717872 | } else if (c == '#') { |
| 116 | 77046 | const int lineEnd = m_source.indexOf('\n', m_pos); | |
| 117 |
2/2✓ Branch 11 → 12 taken 21 times.
✓ Branch 11 → 14 taken 77025 times.
|
77046 | m_pos = lineEnd < 0 ? m_source.size() : lineEnd + 1; |
| 118 |
6/8✓ Branch 16 → 17 taken 128 times.
✓ Branch 16 → 22 taken 1640698 times.
✓ Branch 18 → 19 taken 128 times.
✗ Branch 18 → 22 not taken.
✓ Branch 20 → 21 taken 128 times.
✗ Branch 20 → 22 not taken.
✓ Branch 23 → 24 taken 128 times.
✓ Branch 23 → 34 taken 1640698 times.
|
1640954 | } else if (c == '/' && m_pos + 1 < m_source.size() && |
| 119 | 128 | m_source.at(m_pos + 1) == '*') { | |
| 120 | 128 | const int close = m_source.indexOf("*/", m_pos + 2); | |
| 121 |
2/2✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 33 taken 127 times.
|
128 | if (close < 0) { |
| 122 | 1 | *errorPos = m_pos; | |
| 123 | 1 | m_pos = m_source.size(); | |
| 124 | 1 | return QStringLiteral("Unterminated bracket comment"); | |
| 125 | } | ||
| 126 | 127 | m_pos = close + 2; | |
| 127 | } else { | ||
| 128 | 1640698 | break; | |
| 129 | } | ||
| 130 | } | ||
| 131 | 1644853 | return {}; | |
| 132 | } | ||
| 133 | |||
| 134 | 540092 | Token quotedString(Token token) { | |
| 135 | 540092 | QByteArray value; | |
| 136 | 540092 | int pos = m_pos + 1; | |
| 137 |
2/2✓ Branch 21 → 3 taken 4601631 times.
✓ Branch 21 → 22 taken 8 times.
|
4601639 | while (pos < m_source.size()) { |
| 138 | 4601631 | const char c = m_source.at(pos); | |
| 139 |
2/2✓ Branch 4 → 5 taken 540084 times.
✓ Branch 4 → 9 taken 4061547 times.
|
4601631 | if (c == '"') { |
| 140 | 540084 | m_pos = pos + 1; | |
| 141 | 540084 | token.kind = TokenKind::QuotedString; | |
| 142 | 540084 | token.end = m_pos; | |
| 143 |
1/1✓ Branch 5 → 6 taken 540084 times.
|
540084 | token.text = QString::fromUtf8(value); |
| 144 | 540084 | return token; | |
| 145 | } | ||
| 146 |
5/6✓ Branch 9 → 10 taken 5152 times.
✓ Branch 9 → 13 taken 4056395 times.
✓ Branch 11 → 12 taken 5152 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 15 taken 5152 times.
✓ Branch 14 → 18 taken 4056395 times.
|
4061547 | if (c == '\\' && pos + 1 < m_source.size()) { |
| 147 |
1/1✓ Branch 16 → 17 taken 5152 times.
|
5152 | value.append(m_source.at(pos + 1)); |
| 148 | 5152 | pos += 2; | |
| 149 | 5152 | continue; | |
| 150 | } | ||
| 151 |
1/1✓ Branch 18 → 19 taken 4056395 times.
|
4056395 | value.append(c); |
| 152 | 4056395 | ++pos; | |
| 153 | } | ||
| 154 | 8 | m_pos = m_source.size(); | |
| 155 |
1/1✓ Branch 27 → 28 taken 8 times.
|
8 | return invalid(token, QStringLiteral("Unterminated string")); |
| 156 | 540092 | } | |
| 157 | |||
| 158 | 82019 | Token tag(Token token) { | |
| 159 | 82019 | int pos = m_pos + 1; | |
| 160 |
3/6✓ Branch 3 → 4 taken 82019 times.
✗ Branch 3 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 82019 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 20 taken 82019 times.
|
82019 | if (pos >= m_source.size() || !isAlpha(m_source.at(pos))) { |
| 161 | ✗ | m_pos = pos; | |
| 162 | ✗ | return invalid(token, QStringLiteral("Expected a tag name after ':'")); | |
| 163 | } | ||
| 164 |
5/6✓ Branch 23 → 24 taken 657209 times.
✗ Branch 23 → 31 not taken.
✓ Branch 26 → 27 taken 82019 times.
✓ Branch 26 → 30 taken 575190 times.
✓ Branch 32 → 21 taken 575190 times.
✓ Branch 32 → 33 taken 82019 times.
|
1396437 | while (pos < m_source.size() && |
| 165 |
1/2✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 82019 times.
|
739228 | (isAlpha(m_source.at(pos)) || isDigit(m_source.at(pos)))) |
| 166 | 575190 | ++pos; | |
| 167 | 82019 | token.kind = TokenKind::Tag; | |
| 168 |
3/3✓ Branch 33 → 34 taken 82019 times.
✓ Branch 34 → 35 taken 82019 times.
✓ Branch 35 → 36 taken 82019 times.
|
82019 | token.text = QString::fromLatin1(m_source.mid(m_pos, pos - m_pos)).toLower(); |
| 169 | 82019 | m_pos = pos; | |
| 170 | 82019 | token.end = pos; | |
| 171 | 82019 | return token; | |
| 172 | } | ||
| 173 | |||
| 174 | 546 | Token numberToken(Token token) { | |
| 175 | 546 | int pos = m_pos; | |
| 176 | 546 | quint64 value = 0; | |
| 177 | 546 | bool overflow = false; | |
| 178 |
5/6✓ Branch 8 → 9 taken 1347 times.
✗ Branch 8 → 13 not taken.
✓ Branch 11 → 12 taken 801 times.
✓ Branch 11 → 13 taken 546 times.
✓ Branch 14 → 3 taken 801 times.
✓ Branch 14 → 15 taken 546 times.
|
1347 | while (pos < m_source.size() && isDigit(m_source.at(pos))) { |
| 179 | 801 | const quint64 digit = static_cast<quint64>(m_source.at(pos) - '0'); | |
| 180 |
2/2✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 797 times.
|
801 | if (value > (Q_UINT64_C(0xFFFFFFFFFFFFFFFF) - digit) / 10) |
| 181 | 4 | overflow = true; | |
| 182 | 801 | value = value * 10 + digit; | |
| 183 | 801 | ++pos; | |
| 184 | } | ||
| 185 | 546 | quint64 multiplier = 1; | |
| 186 |
1/2✓ Branch 16 → 17 taken 546 times.
✗ Branch 16 → 28 not taken.
|
546 | if (pos < m_source.size()) { |
| 187 | 546 | const char unit = static_cast<char>(m_source.at(pos) | 0x20); | |
| 188 |
6/6✓ Branch 18 → 19 taken 472 times.
✓ Branch 18 → 21 taken 74 times.
✓ Branch 19 → 20 taken 348 times.
✓ Branch 19 → 21 taken 124 times.
✓ Branch 20 → 21 taken 28 times.
✓ Branch 20 → 28 taken 320 times.
|
546 | if (unit == 'k' || unit == 'm' || unit == 'g') { |
| 189 |
4/4✓ Branch 21 → 22 taken 152 times.
✓ Branch 21 → 26 taken 74 times.
✓ Branch 22 → 23 taken 124 times.
✓ Branch 22 → 24 taken 28 times.
|
226 | multiplier = unit == 'k' ? 1024ULL : unit == 'm' ? 1024ULL * 1024 : 1024ULL * 1024 * 1024; |
| 190 | 226 | ++pos; | |
| 191 | } | ||
| 192 | } | ||
| 193 | 546 | token.kind = TokenKind::Number; | |
| 194 |
2/2✓ Branch 28 → 29 taken 546 times.
✓ Branch 29 → 30 taken 546 times.
|
546 | token.text = QString::fromLatin1(m_source.mid(m_pos, pos - m_pos)); |
| 195 |
5/6✓ Branch 33 → 34 taken 545 times.
✓ Branch 33 → 36 taken 1 time.
✓ Branch 34 → 35 taken 226 times.
✓ Branch 34 → 46 taken 319 times.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 46 taken 226 times.
|
546 | if (overflow || (multiplier > 1 && value > Q_UINT64_C(0xFFFFFFFFFFFFFFFF) / multiplier)) { |
| 196 | 1 | m_pos = pos; | |
| 197 |
1/1✓ Branch 40 → 41 taken 1 time.
|
1 | return invalid(token, QStringLiteral("Number too large")); |
| 198 | } | ||
| 199 | 545 | token.number = value * multiplier; | |
| 200 | 545 | m_pos = pos; | |
| 201 | 545 | token.end = pos; | |
| 202 | 545 | return token; | |
| 203 | } | ||
| 204 | |||
| 205 | 354221 | Token identifier(Token token) { | |
| 206 | 354221 | int pos = m_pos; | |
| 207 |
6/6✓ Branch 5 → 6 taken 2217009 times.
✓ Branch 5 → 13 taken 93 times.
✓ Branch 8 → 9 taken 354128 times.
✓ Branch 8 → 12 taken 1862881 times.
✓ Branch 14 → 3 taken 1862881 times.
✓ Branch 14 → 15 taken 354221 times.
|
4788239 | while (pos < m_source.size() && |
| 208 |
1/2✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 354128 times.
|
2571137 | (isAlpha(m_source.at(pos)) || isDigit(m_source.at(pos)))) |
| 209 | 1862881 | ++pos; | |
| 210 |
3/3✓ Branch 15 → 16 taken 354221 times.
✓ Branch 16 → 17 taken 354221 times.
✓ Branch 17 → 18 taken 354221 times.
|
354221 | const QString name = QString::fromLatin1(m_source.mid(m_pos, pos - m_pos)).toLower(); |
| 211 |
6/8✓ Branch 22 → 23 taken 78 times.
✓ Branch 22 → 28 taken 354143 times.
✓ Branch 24 → 25 taken 78 times.
✗ Branch 24 → 28 not taken.
✓ Branch 26 → 27 taken 78 times.
✗ Branch 26 → 28 not taken.
✓ Branch 29 → 30 taken 78 times.
✓ Branch 29 → 35 taken 354143 times.
|
354299 | if (name == QLatin1String("text") && pos < m_source.size() && |
| 212 | 78 | m_source.at(pos) == ':') { | |
| 213 | 78 | m_pos = pos + 1; | |
| 214 |
1/1✓ Branch 31 → 32 taken 78 times.
|
78 | return multiLine(token); |
| 215 | } | ||
| 216 | 354143 | token.kind = TokenKind::Identifier; | |
| 217 | 354143 | token.text = name; | |
| 218 | 354143 | m_pos = pos; | |
| 219 | 354143 | token.end = pos; | |
| 220 | 354143 | return token; | |
| 221 | 354221 | } | |
| 222 | |||
| 223 | // RFC 5228 §2.4.2: "text:" *(SP / HTAB) (hash-comment / CRLF), then lines | ||
| 224 | // up to a line that is a single ".". Bare LF line ends are accepted. | ||
| 225 | 78 | Token multiLine(Token token) { | |
| 226 | 78 | int pos = m_pos; | |
| 227 |
6/8✓ Branch 5 → 6 taken 80 times.
✗ Branch 5 → 11 not taken.
✓ Branch 7 → 8 taken 78 times.
✓ Branch 7 → 10 taken 2 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 78 times.
✓ Branch 12 → 3 taken 2 times.
✓ Branch 12 → 13 taken 78 times.
|
80 | while (pos < m_source.size() && (m_source.at(pos) == ' ' || m_source.at(pos) == '\t')) |
| 228 | 2 | ++pos; | |
| 229 |
5/6✓ Branch 14 → 15 taken 78 times.
✗ Branch 14 → 18 not taken.
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 18 taken 76 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 19 → 26 taken 76 times.
|
78 | if (pos < m_source.size() && m_source.at(pos) == '#') { |
| 230 | 2 | const int lineEnd = m_source.indexOf('\n', pos); | |
| 231 |
1/2✗ Branch 21 → 22 not taken.
✓ Branch 21 → 24 taken 2 times.
|
2 | pos = lineEnd < 0 ? m_source.size() : lineEnd + 1; |
| 232 |
3/4✓ Branch 29 → 30 taken 4 times.
✓ Branch 29 → 35 taken 72 times.
✓ Branch 31 → 32 taken 4 times.
✗ Branch 31 → 35 not taken.
|
156 | } else if (pos < m_source.size() && m_source.at(pos) == '\r' && |
| 233 |
4/6✓ Branch 27 → 28 taken 76 times.
✗ Branch 27 → 35 not taken.
✓ Branch 33 → 34 taken 4 times.
✗ Branch 33 → 35 not taken.
✓ Branch 36 → 37 taken 4 times.
✓ Branch 36 → 38 taken 72 times.
|
156 | pos + 1 < m_source.size() && m_source.at(pos + 1) == '\n') { |
| 234 | 4 | pos += 2; | |
| 235 |
3/6✓ Branch 39 → 40 taken 72 times.
✗ Branch 39 → 43 not taken.
✓ Branch 41 → 42 taken 72 times.
✗ Branch 41 → 43 not taken.
✓ Branch 44 → 45 taken 72 times.
✗ Branch 44 → 46 not taken.
|
72 | } else if (pos < m_source.size() && m_source.at(pos) == '\n') { |
| 236 | 72 | pos += 1; | |
| 237 | } else { | ||
| 238 | ✗ | m_pos = pos; | |
| 239 | ✗ | return invalid(token, QStringLiteral("Expected a line break after 'text:'")); | |
| 240 | } | ||
| 241 | 78 | QByteArray value; | |
| 242 |
2/2✓ Branch 96 → 57 taken 218 times.
✓ Branch 96 → 97 taken 8 times.
|
226 | while (pos < m_source.size()) { |
| 243 | 218 | int lineEnd = m_source.indexOf('\n', pos); | |
| 244 |
2/2✓ Branch 58 → 59 taken 1 time.
✓ Branch 58 → 69 taken 217 times.
|
218 | if (lineEnd < 0) { |
| 245 | // A final "." without a line break still ends the string. | ||
| 246 |
2/3✓ Branch 59 → 60 taken 1 time.
✓ Branch 62 → 63 taken 1 time.
✗ Branch 62 → 68 not taken.
|
1 | if (m_source.mid(pos) == ".") { |
| 247 | 1 | m_pos = m_source.size(); | |
| 248 | 1 | token.kind = TokenKind::MultiLineString; | |
| 249 | 1 | token.end = m_pos; | |
| 250 |
1/1✓ Branch 64 → 65 taken 1 time.
|
1 | token.text = QString::fromUtf8(value); |
| 251 | 70 | return token; | |
| 252 | } | ||
| 253 | ✗ | break; | |
| 254 | } | ||
| 255 |
1/1✓ Branch 69 → 70 taken 217 times.
|
217 | QByteArray line = m_source.mid(pos, lineEnd - pos); |
| 256 |
1/1✓ Branch 70 → 71 taken 217 times.
|
217 | const bool crlf = line.endsWith('\r'); |
| 257 |
2/2✓ Branch 71 → 72 taken 13 times.
✓ Branch 71 → 73 taken 204 times.
|
217 | if (crlf) |
| 258 |
1/1✓ Branch 72 → 73 taken 13 times.
|
13 | line.chop(1); |
| 259 |
2/2✓ Branch 74 → 75 taken 69 times.
✓ Branch 74 → 80 taken 148 times.
|
217 | if (line == ".") { |
| 260 | 69 | m_pos = lineEnd + 1; | |
| 261 | 69 | token.kind = TokenKind::MultiLineString; | |
| 262 | 69 | token.end = m_pos; | |
| 263 |
1/1✓ Branch 75 → 76 taken 69 times.
|
69 | token.text = QString::fromUtf8(value); |
| 264 | 69 | return token; | |
| 265 | } | ||
| 266 |
2/2✓ Branch 82 → 83 taken 59 times.
✓ Branch 82 → 84 taken 89 times.
|
148 | if (line.startsWith("..")) |
| 267 |
1/1✓ Branch 83 → 84 taken 59 times.
|
59 | line.remove(0, 1); |
| 268 |
1/1✓ Branch 84 → 85 taken 148 times.
|
148 | value.append(line); |
| 269 |
3/3✓ Branch 85 → 86 taken 8 times.
✓ Branch 85 → 87 taken 140 times.
✓ Branch 88 → 89 taken 148 times.
|
148 | value.append(crlf ? "\r\n" : "\n"); |
| 270 | 148 | pos = lineEnd + 1; | |
| 271 | 217 | } | |
| 272 | 8 | m_pos = m_source.size(); | |
| 273 |
1/1✓ Branch 102 → 103 taken 8 times.
|
8 | return invalid(token, QStringLiteral("Unterminated multi-line string")); |
| 274 | 78 | } | |
| 275 | |||
| 276 | const QByteArray &m_source; | ||
| 277 | int m_pos = 0; | ||
| 278 | }; | ||
| 279 | |||
| 280 | class Parser { | ||
| 281 | public: | ||
| 282 | 4204 | Parser(const QByteArray &source, const Limits &limits) | |
| 283 | 4204 | : m_source(source), m_lexer(source), m_limits(limits) { | |
| 284 |
1/1✓ Branch 5 → 6 taken 4204 times.
|
4204 | advance(); |
| 285 | 4204 | } | |
| 286 | |||
| 287 | 3638 | void parseScript(Script *script) { | |
| 288 |
6/6✓ Branch 13 → 14 taken 108887 times.
✓ Branch 13 → 17 taken 3567 times.
✓ Branch 15 → 16 taken 108886 times.
✓ Branch 15 → 17 taken 1 time.
✓ Branch 18 → 3 taken 108886 times.
✓ Branch 18 → 19 taken 3568 times.
|
112454 | while (m_token.kind != TokenKind::End && m_error.isNull()) { |
| 289 | 108886 | Command command; | |
| 290 |
3/3✓ Branch 3 → 4 taken 108886 times.
✓ Branch 4 → 5 taken 70 times.
✓ Branch 4 → 6 taken 108816 times.
|
108886 | if (!parseCommand(&command, 0)) |
| 291 | 70 | break; | |
| 292 |
1/1✓ Branch 6 → 7 taken 108816 times.
|
108816 | script->commands.append(command); |
| 293 | 108886 | } | |
| 294 | 3638 | script->error = m_error; | |
| 295 | 3638 | } | |
| 296 | |||
| 297 | 566 | bool parseSingleTest(Test *test) { | |
| 298 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 565 times.
|
566 | if (m_token.kind != TokenKind::Identifier) |
| 299 | 1 | return false; | |
| 300 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 565 times.
|
565 | if (!parseTest(test, 0)) |
| 301 | ✗ | return false; | |
| 302 | 565 | return m_token.kind == TokenKind::End; | |
| 303 | } | ||
| 304 | |||
| 305 | private: | ||
| 306 | 1644854 | void advance() { | |
| 307 | 1644854 | m_previousEnd = m_token.end; | |
| 308 |
1/1✓ Branch 2 → 3 taken 1644854 times.
|
1644854 | m_token = m_lexer.next(); |
| 309 |
2/2✓ Branch 5 → 6 taken 28 times.
✓ Branch 5 → 7 taken 1644826 times.
|
1644854 | if (m_token.kind == TokenKind::Invalid) |
| 310 | 28 | fail(m_token.begin, m_token.error); | |
| 311 | 1644854 | } | |
| 312 | |||
| 313 | 71 | bool fail(int offset, const QString &message) { | |
| 314 |
1/2✓ Branch 3 → 4 taken 71 times.
✗ Branch 3 → 8 not taken.
|
71 | if (m_error.isNull()) { |
| 315 | 71 | m_error.message = message; | |
| 316 | 71 | m_error.offset = offset; | |
| 317 | 71 | m_error.line = lineOfOffset(m_source, offset); | |
| 318 | 71 | m_error.column = columnOfOffset(m_source, offset); | |
| 319 | } | ||
| 320 | 71 | return false; | |
| 321 | } | ||
| 322 | |||
| 323 | 255011 | bool parseCommand(Command *command, int depth) { | |
| 324 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 11 taken 255011 times.
|
255011 | if (depth > m_limits.maxDepth) |
| 325 | ✗ | return fail(m_token.begin, QStringLiteral("Nesting too deep")); | |
| 326 |
2/2✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 20 taken 255010 times.
|
255011 | if (++m_commandCount > m_limits.maxCommands) |
| 327 |
1/1✓ Branch 15 → 16 taken 1 time.
|
1 | return fail(m_token.begin, QStringLiteral("Too many commands")); |
| 328 |
1/2✗ Branch 20 → 21 not taken.
✓ Branch 20 → 29 taken 255010 times.
|
255010 | if (m_token.kind != TokenKind::Identifier) |
| 329 | ✗ | return fail(m_token.begin, QStringLiteral("Expected a command")); | |
| 330 | 255010 | command->name = m_token.text; | |
| 331 | 255010 | command->span.begin = m_token.begin; | |
| 332 | 255010 | command->line = lineAt(m_token.begin); | |
| 333 | 255010 | advance(); | |
| 334 |
2/2✓ Branch 33 → 34 taken 28 times.
✓ Branch 33 → 35 taken 254982 times.
|
255010 | if (!parseArguments(&command->arguments, &command->tests, &command->testList, depth)) |
| 335 | 28 | return false; | |
| 336 |
2/2✓ Branch 35 → 36 taken 178838 times.
✓ Branch 35 → 38 taken 76144 times.
|
254982 | if (m_token.kind == TokenKind::Semicolon) { |
| 337 | 178838 | command->span.end = m_token.end; | |
| 338 | 178838 | advance(); | |
| 339 | 178838 | return m_error.isNull(); | |
| 340 | } | ||
| 341 |
2/2✓ Branch 38 → 39 taken 9 times.
✓ Branch 38 → 50 taken 76135 times.
|
76144 | if (m_token.kind != TokenKind::LeftBrace) |
| 342 |
1/1✓ Branch 44 → 45 taken 9 times.
|
27 | return fail(m_token.begin, QStringLiteral("Expected ';' or '{' after \"%1\"") |
| 343 |
1/1✓ Branch 43 → 44 taken 9 times.
|
27 | .arg(command->name)); |
| 344 | 76135 | command->hasBlock = true; | |
| 345 | 76135 | command->blockSpan.begin = m_token.begin; | |
| 346 | 76135 | advance(); | |
| 347 |
2/2✓ Branch 74 → 51 taken 146148 times.
✓ Branch 74 → 75 taken 76040 times.
|
222188 | while (m_token.kind != TokenKind::RightBrace) { |
| 348 |
2/2✓ Branch 51 → 52 taken 23 times.
✓ Branch 51 → 60 taken 146125 times.
|
146148 | if (m_token.kind == TokenKind::End) |
| 349 |
1/1✓ Branch 55 → 56 taken 23 times.
|
23 | return fail(m_token.begin, QStringLiteral("Missing '}'")); |
| 350 |
1/2✗ Branch 61 → 62 not taken.
✓ Branch 61 → 63 taken 146125 times.
|
146125 | if (!m_error.isNull()) |
| 351 | ✗ | return false; | |
| 352 | 146125 | Command child; | |
| 353 |
3/3✓ Branch 63 → 64 taken 146125 times.
✓ Branch 64 → 65 taken 72 times.
✓ Branch 64 → 66 taken 146053 times.
|
146125 | if (!parseCommand(&child, depth + 1)) |
| 354 | 72 | return false; | |
| 355 |
1/1✓ Branch 66 → 67 taken 146053 times.
|
146053 | command->block.append(child); |
| 356 | 146125 | } | |
| 357 | 76040 | command->blockSpan.end = m_token.end; | |
| 358 | 76040 | command->span.end = m_token.end; | |
| 359 | 76040 | advance(); | |
| 360 | 76040 | return m_error.isNull(); | |
| 361 | } | ||
| 362 | |||
| 363 | 354140 | bool parseArguments(QList<Argument> *arguments, QList<Test> *tests, bool *testList, | |
| 364 | int depth) { | ||
| 365 |
2/2✓ Branch 37 → 3 taken 701029 times.
✓ Branch 37 → 38 taken 18 times.
|
701047 | while (m_error.isNull()) { |
| 366 |
2/2✓ Branch 3 → 4 taken 82019 times.
✓ Branch 3 → 9 taken 619010 times.
|
701029 | if (m_token.kind == TokenKind::Tag) { |
| 367 | 82019 | Argument argument; | |
| 368 | 82019 | argument.kind = Argument::Kind::Tag; | |
| 369 | 82019 | argument.span = {m_token.begin, m_token.end}; | |
| 370 | 82019 | argument.text = m_token.text; | |
| 371 |
1/1✓ Branch 5 → 6 taken 82019 times.
|
82019 | arguments->append(argument); |
| 372 |
1/1✓ Branch 6 → 7 taken 82019 times.
|
82019 | advance(); |
| 373 |
2/2✓ Branch 9 → 10 taken 545 times.
✓ Branch 9 → 15 taken 618465 times.
|
701029 | } else if (m_token.kind == TokenKind::Number) { |
| 374 | 545 | Argument argument; | |
| 375 | 545 | argument.kind = Argument::Kind::Number; | |
| 376 | 545 | argument.span = {m_token.begin, m_token.end}; | |
| 377 | 545 | argument.text = m_token.text; | |
| 378 | 545 | argument.number = m_token.number; | |
| 379 |
1/1✓ Branch 11 → 12 taken 545 times.
|
545 | arguments->append(argument); |
| 380 |
1/1✓ Branch 12 → 13 taken 545 times.
|
545 | advance(); |
| 381 |
2/2✓ Branch 15 → 16 taken 362753 times.
✓ Branch 15 → 17 taken 255712 times.
|
619010 | } else if (m_token.kind == TokenKind::QuotedString || |
| 382 |
2/2✓ Branch 16 → 17 taken 70 times.
✓ Branch 16 → 24 taken 362683 times.
|
362753 | m_token.kind == TokenKind::MultiLineString) { |
| 383 | 255782 | Argument argument; | |
| 384 | 255782 | argument.kind = Argument::Kind::StringList; | |
| 385 | 255782 | argument.span = {m_token.begin, m_token.end}; | |
| 386 |
1/1✓ Branch 18 → 19 taken 255782 times.
|
255782 | argument.strings.append(stringValue()); |
| 387 |
1/1✓ Branch 20 → 21 taken 255782 times.
|
255782 | arguments->append(argument); |
| 388 |
1/1✓ Branch 21 → 22 taken 255782 times.
|
255782 | advance(); |
| 389 |
2/2✓ Branch 24 → 25 taken 8561 times.
✓ Branch 24 → 35 taken 354122 times.
|
618465 | } else if (m_token.kind == TokenKind::LeftBracket) { |
| 390 | 8561 | Argument argument; | |
| 391 |
2/3✓ Branch 25 → 26 taken 8561 times.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 8561 times.
|
8561 | if (!parseStringList(&argument)) |
| 392 | ✗ | return false; | |
| 393 |
1/1✓ Branch 28 → 29 taken 8561 times.
|
8561 | arguments->append(argument); |
| 394 |
1/2✓ Branch 31 → 32 taken 8561 times.
✗ Branch 31 → 34 not taken.
|
8561 | } else { |
| 395 | 354122 | break; | |
| 396 | } | ||
| 397 | } | ||
| 398 |
2/2✓ Branch 39 → 40 taken 18 times.
✓ Branch 39 → 41 taken 354122 times.
|
354140 | if (!m_error.isNull()) |
| 399 | 18 | return false; | |
| 400 |
2/2✓ Branch 41 → 42 taken 76340 times.
✓ Branch 41 → 49 taken 277782 times.
|
354122 | if (m_token.kind == TokenKind::Identifier) { |
| 401 | 76340 | Test test; | |
| 402 |
3/3✓ Branch 42 → 43 taken 76340 times.
✓ Branch 43 → 44 taken 81 times.
✓ Branch 43 → 45 taken 76259 times.
|
76340 | if (!parseTest(&test, depth + 1)) |
| 403 | 81 | return false; | |
| 404 |
1/1✓ Branch 45 → 46 taken 76259 times.
|
76259 | tests->append(test); |
| 405 | 76259 | return true; | |
| 406 | 76340 | } | |
| 407 |
2/2✓ Branch 49 → 50 taken 17619 times.
✓ Branch 49 → 87 taken 260163 times.
|
277782 | if (m_token.kind == TokenKind::LeftParen) { |
| 408 | 17619 | *testList = true; | |
| 409 | 17619 | advance(); | |
| 410 |
1/2✓ Branch 85 → 52 taken 22235 times.
✗ Branch 85 → 86 not taken.
|
22235 | while (m_error.isNull()) { |
| 411 | 22235 | Test test; | |
| 412 |
2/2✓ Branch 52 → 53 taken 8 times.
✓ Branch 52 → 61 taken 22227 times.
|
22235 | if (m_token.kind != TokenKind::Identifier) |
| 413 |
1/1✓ Branch 56 → 57 taken 8 times.
|
8 | return fail(m_token.begin, QStringLiteral("Expected a test")); |
| 414 |
2/3✓ Branch 61 → 62 taken 22227 times.
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 64 taken 22227 times.
|
22227 | if (!parseTest(&test, depth + 1)) |
| 415 | ✗ | return false; | |
| 416 |
1/1✓ Branch 64 → 65 taken 22227 times.
|
22227 | tests->append(test); |
| 417 |
2/2✓ Branch 65 → 66 taken 4616 times.
✓ Branch 65 → 68 taken 17611 times.
|
22227 | if (m_token.kind == TokenKind::Comma) { |
| 418 |
1/1✓ Branch 66 → 67 taken 4616 times.
|
4616 | advance(); |
| 419 | 4616 | continue; | |
| 420 | } | ||
| 421 |
1/2✓ Branch 68 → 69 taken 17611 times.
✗ Branch 68 → 72 not taken.
|
17611 | if (m_token.kind == TokenKind::RightParen) { |
| 422 |
1/1✓ Branch 69 → 70 taken 17611 times.
|
17611 | advance(); |
| 423 | 17611 | return m_error.isNull(); | |
| 424 | } | ||
| 425 | ✗ | return fail(m_token.begin, QStringLiteral("Expected ',' or ')' in test list")); | |
| 426 | 22235 | } | |
| 427 | ✗ | return false; | |
| 428 | } | ||
| 429 | 260163 | return true; | |
| 430 | } | ||
| 431 | |||
| 432 | 8561 | bool parseStringList(Argument *argument) { | |
| 433 | 8561 | argument->kind = Argument::Kind::StringList; | |
| 434 | 8561 | argument->bracketed = true; | |
| 435 | 8561 | argument->span.begin = m_token.begin; | |
| 436 | 8561 | advance(); | |
| 437 |
1/2✓ Branch 33 → 3 taken 284372 times.
✗ Branch 33 → 34 not taken.
|
284372 | while (m_error.isNull()) { |
| 438 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 13 taken 284372 times.
|
284372 | if (m_token.kind != TokenKind::QuotedString && |
| 439 | ✗ | m_token.kind != TokenKind::MultiLineString) | |
| 440 | ✗ | return fail(m_token.begin, QStringLiteral("Expected a string in string list")); | |
| 441 |
1/1✓ Branch 14 → 15 taken 284372 times.
|
284372 | argument->strings.append(stringValue()); |
| 442 | 284372 | advance(); | |
| 443 |
2/2✓ Branch 17 → 18 taken 275811 times.
✓ Branch 17 → 20 taken 8561 times.
|
284372 | if (m_token.kind == TokenKind::Comma) { |
| 444 | 275811 | advance(); | |
| 445 | 275811 | continue; | |
| 446 | } | ||
| 447 |
1/2✓ Branch 20 → 21 taken 8561 times.
✗ Branch 20 → 23 not taken.
|
8561 | if (m_token.kind == TokenKind::RightBracket) { |
| 448 | 8561 | argument->span.end = m_token.end; | |
| 449 | 8561 | advance(); | |
| 450 | 8561 | return m_error.isNull(); | |
| 451 | } | ||
| 452 | ✗ | return fail(m_token.begin, QStringLiteral("Expected ',' or ']' in string list")); | |
| 453 | } | ||
| 454 | ✗ | return false; | |
| 455 | } | ||
| 456 | |||
| 457 | 99132 | bool parseTest(Test *test, int depth) { | |
| 458 |
2/2✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 11 taken 99130 times.
|
99132 | if (depth > m_limits.maxDepth) |
| 459 |
1/1✓ Branch 6 → 7 taken 2 times.
|
2 | return fail(m_token.begin, QStringLiteral("Nesting too deep")); |
| 460 | 99130 | test->name = m_token.text; | |
| 461 | 99130 | test->span.begin = m_token.begin; | |
| 462 |
1/1✓ Branch 12 → 13 taken 99130 times.
|
99130 | test->line = lineAt(m_token.begin); |
| 463 | 99130 | int end = m_token.end; | |
| 464 |
1/1✓ Branch 13 → 14 taken 99130 times.
|
99130 | advance(); |
| 465 |
3/3✓ Branch 14 → 15 taken 99130 times.
✓ Branch 15 → 16 taken 79 times.
✓ Branch 15 → 17 taken 99051 times.
|
99130 | if (!parseArguments(&test->arguments, &test->tests, &test->testList, depth)) |
| 466 | 79 | return false; | |
| 467 | // The test ends where the last consumed token ended. | ||
| 468 | 99051 | test->span.end = qMax(end, m_previousEnd); | |
| 469 | 99051 | return true; | |
| 470 | } | ||
| 471 | |||
| 472 | // Line of `offset`. Commands and tests are met in source order, so the | ||
| 473 | // count resumes where the previous call stopped: linear in the script | ||
| 474 | // size instead of quadratic (counting from the start for every command). | ||
| 475 | 354140 | int lineAt(int offset) { | |
| 476 |
1/1✓ Branch 3 → 4 taken 354140 times.
|
354140 | offset = qBound(0, offset, static_cast<int>(m_source.size())); |
| 477 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 354140 times.
|
354140 | if (offset < m_lineOffset) { |
| 478 | ✗ | m_lineOffset = 0; | |
| 479 | ✗ | m_line = 1; | |
| 480 | } | ||
| 481 |
2/2✓ Branch 11 → 7 taken 9075715 times.
✓ Branch 11 → 12 taken 354140 times.
|
9429855 | for (int i = m_lineOffset; i < offset; ++i) |
| 482 |
2/2✓ Branch 8 → 9 taken 436204 times.
✓ Branch 8 → 10 taken 8639511 times.
|
9075715 | if (m_source.at(i) == '\n') |
| 483 | 436204 | ++m_line; | |
| 484 | 354140 | m_lineOffset = offset; | |
| 485 | 354140 | return m_line; | |
| 486 | } | ||
| 487 | |||
| 488 | 540154 | StringValue stringValue() const { | |
| 489 | 540154 | StringValue value; | |
| 490 | 540154 | value.value = m_token.text; | |
| 491 | 540154 | value.span = {m_token.begin, m_token.end}; | |
| 492 | 540154 | value.multiLine = m_token.kind == TokenKind::MultiLineString; | |
| 493 | 540154 | return value; | |
| 494 | } | ||
| 495 | |||
| 496 | const QByteArray &m_source; | ||
| 497 | Lexer m_lexer; | ||
| 498 | Limits m_limits; | ||
| 499 | Token m_token; | ||
| 500 | int m_previousEnd = 0; | ||
| 501 | int m_commandCount = 0; | ||
| 502 | int m_lineOffset = 0; | ||
| 503 | int m_line = 1; | ||
| 504 | ParseError m_error; | ||
| 505 | }; | ||
| 506 | |||
| 507 | } // namespace | ||
| 508 | |||
| 509 | 3639 | Script parse(const QByteArray &source, const Limits &limits) { | |
| 510 | 3639 | Script script; | |
| 511 | 3639 | script.source = source; | |
| 512 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 25 taken 3638 times.
|
3639 | if (source.size() > limits.maxBytes) { |
| 513 | 1 | script.error.message = QStringLiteral("Script too large"); | |
| 514 | 1 | script.error.offset = 0; | |
| 515 | 1 | script.error.line = 1; | |
| 516 | 1 | script.error.column = 1; | |
| 517 |
7/7✓ Branch 11 → 12 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 19 → 20 taken 1 time.
✓ Branch 23 → 13 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
|
2 | qCInfo(lcSieveParse) << "Refused to parse a script of" << source.size() << "bytes"; |
| 518 | 1 | return script; | |
| 519 | } | ||
| 520 |
1/1✓ Branch 25 → 26 taken 3638 times.
|
3638 | Parser parser(script.source, limits); |
| 521 |
1/1✓ Branch 26 → 27 taken 3638 times.
|
3638 | parser.parseScript(&script); |
| 522 |
2/2✓ Branch 28 → 29 taken 71 times.
✓ Branch 28 → 48 taken 3567 times.
|
3638 | if (!script.isValid()) |
| 523 |
7/7✓ Branch 29 → 30 taken 71 times.
✓ Branch 33 → 34 taken 65 times.
✓ Branch 35 → 36 taken 65 times.
✓ Branch 36 → 37 taken 65 times.
✓ Branch 37 → 38 taken 65 times.
✓ Branch 46 → 31 taken 65 times.
✓ Branch 46 → 47 taken 71 times.
|
136 | qCInfo(lcSieveParse).nospace() << "Parse error at " << script.error.line << ':' |
| 524 |
3/3✓ Branch 38 → 39 taken 65 times.
✓ Branch 39 → 40 taken 65 times.
✓ Branch 41 → 42 taken 65 times.
|
65 | << script.error.column << " (" << source.size() |
| 525 |
1/1✓ Branch 42 → 43 taken 65 times.
|
65 | << " bytes)"; |
| 526 | 3638 | return script; | |
| 527 | 3638 | } | |
| 528 | |||
| 529 | 566 | bool parseTestExpression(const QByteArray &text, Test *test) { | |
| 530 | 566 | Limits limits; | |
| 531 |
1/1✓ Branch 2 → 3 taken 566 times.
|
566 | Parser parser(text, limits); |
| 532 |
1/1✓ Branch 3 → 4 taken 566 times.
|
1132 | return parser.parseSingleTest(test); |
| 533 | 566 | } | |
| 534 | |||
| 535 | 66 | QByteArray serialize(const Script &script) { | |
| 536 | 66 | QByteArray out; | |
| 537 |
1/1✓ Branch 3 → 4 taken 66 times.
|
66 | out.reserve(script.source.size()); |
| 538 | 66 | int pos = 0; | |
| 539 |
2/2✓ Branch 16 → 6 taken 216 times.
✓ Branch 16 → 17 taken 66 times.
|
282 | for (const Command &command : script.commands) { |
| 540 |
2/2✓ Branch 7 → 8 taken 216 times.
✓ Branch 8 → 9 taken 216 times.
|
216 | out.append(script.source.mid(pos, command.span.begin - pos)); |
| 541 |
2/2✓ Branch 11 → 12 taken 216 times.
✓ Branch 12 → 13 taken 216 times.
|
216 | out.append(script.source.mid(command.span.begin, command.span.length())); |
| 542 | 216 | pos = command.span.end; | |
| 543 | } | ||
| 544 |
2/2✓ Branch 17 → 18 taken 66 times.
✓ Branch 18 → 19 taken 66 times.
|
66 | out.append(script.source.mid(pos)); |
| 545 | 66 | return out; | |
| 546 | ✗ | } | |
| 547 | |||
| 548 | 71 | int lineOfOffset(const QByteArray &source, int offset) { | |
| 549 |
1/1✓ Branch 3 → 4 taken 71 times.
|
71 | const int limit = qBound(0, offset, static_cast<int>(source.size())); |
| 550 | 71 | int line = 1; | |
| 551 |
2/2✓ Branch 9 → 5 taken 56640 times.
✓ Branch 9 → 10 taken 71 times.
|
56711 | for (int i = 0; i < limit; ++i) |
| 552 |
2/2✓ Branch 6 → 7 taken 285 times.
✓ Branch 6 → 8 taken 56355 times.
|
56640 | if (source.at(i) == '\n') |
| 553 | 285 | ++line; | |
| 554 | 71 | return line; | |
| 555 | } | ||
| 556 | |||
| 557 | 71 | int columnOfOffset(const QByteArray &source, int offset) { | |
| 558 |
1/1✓ Branch 3 → 4 taken 71 times.
|
71 | const int limit = qBound(0, offset, static_cast<int>(source.size())); |
| 559 | // S106-090: lastIndexOf(…, -1) would search from the end of the source. | ||
| 560 |
2/2✓ Branch 4 → 5 taken 70 times.
✓ Branch 4 → 7 taken 1 time.
|
71 | const int lineStart = limit > 0 ? source.lastIndexOf('\n', limit - 1) + 1 : 0; |
| 561 |
2/2✓ Branch 8 → 9 taken 71 times.
✓ Branch 9 → 10 taken 71 times.
|
71 | return static_cast<int>(QString::fromUtf8(source.mid(lineStart, limit - lineStart)).size()) + 1; |
| 562 | } | ||
| 563 | |||
| 564 | 398284 | bool representableString(const QString &value) { | |
| 565 |
2/2✓ Branch 25 → 3 taken 2307078 times.
✓ Branch 25 → 26 taken 398280 times.
|
2705358 | for (int i = 0; i < value.size(); ++i) { |
| 566 | 2307078 | const QChar c = value.at(i); | |
| 567 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 2307077 times.
|
2307078 | if (c.unicode() == 0) |
| 568 | 4 | return false; | |
| 569 |
8/8✓ Branch 10 → 11 taken 6 times.
✓ Branch 10 → 19 taken 2307071 times.
✓ Branch 12 → 13 taken 5 times.
✓ Branch 12 → 18 taken 1 time.
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 3 times.
✓ Branch 20 → 21 taken 3 times.
✓ Branch 20 → 22 taken 2307074 times.
|
2307077 | if (c == QLatin1Char('\r') && (i + 1 >= value.size() || value.at(i + 1) != QLatin1Char('\n'))) |
| 570 | 3 | return false; | |
| 571 | } | ||
| 572 | 398280 | return true; | |
| 573 | } | ||
| 574 | |||
| 575 | 2937 | QByteArray stringLiteral(const QString &value, const QByteArray &lineEnd) { | |
| 576 | 2937 | QString body = value; | |
| 577 |
1/1✓ Branch 9 → 10 taken 2937 times.
|
5874 | body.replace(QStringLiteral("\r\n"), QStringLiteral("\n")); |
| 578 |
1/1✓ Branch 18 → 19 taken 2937 times.
|
2937 | body.replace(QLatin1Char('\r'), QLatin1Char('\n')); |
| 579 |
1/1✓ Branch 20 → 21 taken 2937 times.
|
2937 | body.remove(QChar(0)); |
| 580 |
3/3✓ Branch 23 → 24 taken 2937 times.
✓ Branch 24 → 25 taken 22 times.
✓ Branch 24 → 50 taken 2915 times.
|
2937 | if (body.endsWith(QLatin1Char('\n'))) { |
| 581 |
2/5✓ Branch 25 → 26 taken 22 times.
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 22 times.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 101 not taken.
|
44 | QByteArray out = "text:" + lineEnd; |
| 582 |
1/1✓ Branch 26 → 27 taken 22 times.
|
22 | body.chop(1); |
| 583 |
1/1✓ Branch 30 → 31 taken 22 times.
|
22 | const QStringList lines = body.split(QLatin1Char('\n')); |
| 584 |
2/2✓ Branch 43 → 33 taken 45 times.
✓ Branch 43 → 44 taken 22 times.
|
67 | for (const QString &line : lines) { |
| 585 |
1/1✓ Branch 34 → 35 taken 45 times.
|
45 | QByteArray bytes = line.toUtf8(); |
| 586 |
3/3✓ Branch 35 → 36 taken 45 times.
✓ Branch 36 → 37 taken 17 times.
✓ Branch 36 → 38 taken 28 times.
|
45 | if (bytes.startsWith('.')) |
| 587 |
1/1✓ Branch 37 → 38 taken 17 times.
|
17 | bytes.prepend('.'); |
| 588 |
1/1✓ Branch 38 → 39 taken 45 times.
|
45 | out.append(bytes); |
| 589 |
1/1✓ Branch 39 → 40 taken 45 times.
|
45 | out.append(lineEnd); |
| 590 | 45 | } | |
| 591 |
1/1✓ Branch 44 → 45 taken 22 times.
|
22 | out.append('.'); |
| 592 |
1/1✓ Branch 45 → 46 taken 22 times.
|
22 | out.append(lineEnd); |
| 593 | 22 | return out; | |
| 594 | 22 | } | |
| 595 |
1/1✓ Branch 50 → 51 taken 2915 times.
|
2915 | QByteArray out = "\""; |
| 596 |
1/1✓ Branch 51 → 52 taken 2915 times.
|
2915 | const QByteArray bytes = body.toUtf8(); |
| 597 |
2/2✓ Branch 62 → 54 taken 27423 times.
✓ Branch 62 → 63 taken 2915 times.
|
30338 | for (const char c : bytes) { |
| 598 |
2/2✓ Branch 54 → 55 taken 6 times.
✓ Branch 54 → 57 taken 27417 times.
|
27423 | if (c == '\n') { |
| 599 |
1/1✓ Branch 55 → 56 taken 6 times.
|
6 | out.append(lineEnd); // quoted strings may span lines (RFC 5228 §2.4.2) |
| 600 | 6 | continue; | |
| 601 | } | ||
| 602 |
4/4✓ Branch 57 → 58 taken 27401 times.
✓ Branch 57 → 59 taken 16 times.
✓ Branch 58 → 59 taken 123 times.
✓ Branch 58 → 60 taken 27278 times.
|
27417 | if (c == '"' || c == '\\') |
| 603 |
1/1✓ Branch 59 → 60 taken 139 times.
|
139 | out.append('\\'); |
| 604 |
1/1✓ Branch 60 → 61 taken 27417 times.
|
27417 | out.append(c); |
| 605 | } | ||
| 606 |
1/1✓ Branch 63 → 64 taken 2915 times.
|
2915 | out.append('"'); |
| 607 | 2915 | return out; | |
| 608 | 2937 | } | |
| 609 | |||
| 610 | 1739 | QByteArray stringListLiteral(const QStringList &values, const QByteArray &lineEnd) { | |
| 611 |
2/2✓ Branch 3 → 4 taken 1667 times.
✓ Branch 3 → 7 taken 72 times.
|
1739 | if (values.size() == 1) |
| 612 |
1/1✓ Branch 5 → 6 taken 1667 times.
|
1667 | return stringLiteral(values.first(), lineEnd); |
| 613 |
1/1✓ Branch 7 → 8 taken 72 times.
|
72 | QByteArray out = "["; |
| 614 |
2/2✓ Branch 17 → 9 taken 172 times.
✓ Branch 17 → 18 taken 72 times.
|
244 | for (int i = 0; i < values.size(); ++i) { |
| 615 |
2/2✓ Branch 9 → 10 taken 100 times.
✓ Branch 9 → 11 taken 72 times.
|
172 | if (i > 0) |
| 616 |
1/1✓ Branch 10 → 11 taken 100 times.
|
100 | out.append(", "); |
| 617 |
2/2✓ Branch 12 → 13 taken 172 times.
✓ Branch 13 → 14 taken 172 times.
|
172 | out.append(stringLiteral(values.at(i), lineEnd)); |
| 618 | } | ||
| 619 |
1/1✓ Branch 18 → 19 taken 72 times.
|
72 | out.append(']'); |
| 620 | 72 | return out; | |
| 621 | 72 | } | |
| 622 | |||
| 623 | } // namespace Sieve | ||
| 624 |