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

rieske / trans / 29685214814

19 Jul 2026 11:29AM UTC coverage: 90.794% (+0.3%) from 90.53%
29685214814

Pull #46

github

rieske
Experimental parser generator optimization
Pull Request #46: Experimental parser generator optimization

492 of 500 new or added lines in 17 files covered. (98.4%)

16 existing lines in 6 files now uncovered.

5286 of 5822 relevant lines covered (90.79%)

309852.94 hits per line

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

98.89
/src/parser/CanonicalCollection.cpp
1
#include "CanonicalCollection.h"
2

3
#include "HashCombine.h"
4
#include "util/LogManager.h"
5
#include "util/Logger.h"
6

7
#include <algorithm>
8
#include <cstdint>
9
#include <deque>
10
#include <unordered_map>
11
#include <utility>
12
#include <vector>
13

14
namespace {
15

16
Logger& logger = LogManager::getComponentLogger(Component::PARSER);
17

18
using namespace parser;
19

20

21
void closeItemSet(
113,346✔
22
        const FirstTable& first,
23
        const Grammar& grammar,
24
        GenerationBuffers& buffers,
25
        std::vector<LR1Item>& items) {
26
    auto& coreIndex = buffers.coreIndex;
113,346✔
27
    auto& worklist = buffers.worklist;
113,346✔
28
    buffers.prepareClosure(items.size());
113,346✔
29

30
    for (std::size_t i = 0; i < items.size(); ++i) {
304,860✔
31
        coreIndex.emplace(items[i].coreKey(), i);
191,514✔
32
        worklist.push_back(i);
191,514✔
33
    }
34
    items.reserve(std::max(items.capacity(), items.size() + 64));
113,346✔
35

36
    std::size_t head = 0;
113,346✔
37
    while (head < worklist.size()) {
3,039,418✔
38
        const std::size_t index = worklist[head++];
2,926,072✔
39
        if (!items[index].hasUnvisitedSymbols() || grammar.isTerminal(items[index].nextUnvisitedSymbol())) {
2,926,072✔
40
            continue;
896,562✔
41
        }
42

43
        const int B = items[index].nextUnvisitedSymbol();
2,029,510✔
44
        const LR1Item::LookaheadSet propagated = items[index].hasSymbolsAfterNext()
2,029,510✔
45
                ? first.firstBits(items[index].symbolAfterNext())
2,029,510✔
46
                : items[index].lookaheads();
568,156✔
47

48
        for (const auto& production : grammar.getProductionsOfSymbol(B)) {
12,826,582✔
49
            const std::uint64_t key =
50
                    (static_cast<std::uint64_t>(static_cast<std::uint32_t>(production.getId())) << 32);
10,797,072✔
51
            const auto existing = coreIndex.find(key);
10,797,072✔
52
            if (existing == coreIndex.end()) {
10,797,072✔
53
                items.emplace_back(production, propagated);
991,300✔
54
                const std::size_t newIndex = items.size() - 1;
991,300✔
55
                coreIndex.emplace(key, newIndex);
991,300✔
56
                worklist.push_back(newIndex);
991,300✔
57
            } else if (items[existing->second].mergeLookaheads(propagated)) {
9,805,772✔
58
                worklist.push_back(existing->second);
1,743,258✔
59
            }
60
        }
61
    }
62
}
113,346✔
63

64
std::unordered_map<int, std::vector<LR1Item>> gotoAllFrom(
12,004✔
65
        const std::vector<LR1Item>& I,
66
        const FirstTable& first,
67
        const Grammar& grammar,
68
        GenerationBuffers& buffers) {
69
    std::unordered_map<int, std::vector<LR1Item>> bySymbol;
12,004✔
70
    bySymbol.reserve(I.size());
12,004✔
71
    for (const auto& item : I) {
210,548✔
72
        if (item.hasUnvisitedSymbols()) {
198,544✔
73
            bySymbol[item.nextUnvisitedSymbol()].push_back(item.advance());
191,488✔
74
        }
75
    }
76
    for (auto& entry : bySymbol) {
125,324✔
77
        entry.second.reserve(entry.second.size() + 16);
113,320✔
78
        closeItemSet(first, grammar, buffers, entry.second);
113,320✔
79
    }
80
    return bySymbol;
12,004✔
NEW
UNCOV
81
}
×
82

83
// --- LALR(1) ---
84

85
std::vector<std::uint64_t> makeCoreSignature(const std::vector<LR1Item>& items, GenerationBuffers& buffers) {
54,632✔
86
    auto& signature = buffers.coreSignature;
54,632✔
87
    signature.clear();
54,632✔
88
    signature.reserve(items.size());
54,632✔
89
    for (const auto& item : items) {
646,946✔
90
        signature.push_back(item.coreKey());
592,314✔
91
    }
92
    std::sort(signature.begin(), signature.end());
54,632✔
93
    return signature;
54,632✔
94
}
95

96
struct CoreSignatureHash {
97
    std::size_t operator()(const std::vector<std::uint64_t>& signature) const noexcept {
74,586✔
98
        std::size_t hash = signature.size();
74,586✔
99
        for (const std::uint64_t key : signature) {
890,124✔
100
            hashCombine(hash, key);
815,538✔
101
        }
102
        return hash;
74,586✔
103
    }
104
};
105

106
bool mergeLookaheadsByCore(
51,370✔
107
        std::vector<LR1Item>& existing,
108
        const std::vector<LR1Item>& incoming,
109
        GenerationBuffers& buffers) {
110
    buffers.prepareMerge(existing.size());
51,370✔
111
    auto& index = buffers.mergeIndexByCore;
51,370✔
112
    for (std::size_t i = 0; i < existing.size(); ++i) {
592,620✔
113
        index.emplace(existing[i].coreKey(), i);
541,250✔
114
    }
115
    bool merged = false;
51,370✔
116
    for (const auto& item : incoming) {
592,620✔
117
        merged |= existing[index.at(item.coreKey())].mergeLookaheads(item.lookaheads());
541,250✔
118
    }
119
    return merged;
51,370✔
120
}
121

122
void computeLALR1(
16✔
123
        std::vector<std::vector<LR1Item>>& canonicalCollection,
124
        GotoMap& computedGotos,
125
        const std::vector<int>& symbolOrder,
126
        const FirstTable& first,
127
        const Grammar& grammar,
128
        GenerationBuffers& buffers) {
129
    std::unordered_map<std::vector<std::uint64_t>, std::size_t, CoreSignatureHash> stateByCores;
16✔
130
    stateByCores.reserve(512);
16✔
131
    stateByCores.emplace(makeCoreSignature(canonicalCollection[0], buffers), 0);
16✔
132
    computedGotos.reserve(symbolOrder.size() * 8);
16✔
133

134
    std::deque<std::size_t> worklist { 0 };
48✔
135
    std::vector<char> inQueue(1, 1);
16✔
136

137
    auto enqueue = [&](std::size_t state) {
6,470✔
138
        if (state >= inQueue.size()) {
6,470✔
139
            inQueue.resize(state + 1, 0);
3,246✔
140
        }
141
        if (!inQueue[state]) {
6,470✔
142
            inQueue[state] = 1;
5,550✔
143
            worklist.push_back(state);
5,550✔
144
        }
145
    };
6,470✔
146

147
    while (!worklist.empty()) {
5,582✔
148
        const std::size_t state = worklist.front();
5,566✔
149
        worklist.pop_front();
5,566✔
150
        inQueue[state] = 0;
5,566✔
151

152
        auto gotos = gotoAllFrom(canonicalCollection[state], first, grammar, buffers);
5,566✔
153
        // Walk symbols in stable grammar order; only process non-empty gotos.
154
        for (const int X : symbolOrder) {
783,300✔
155
            const auto git = gotos.find(X);
777,734✔
156
            if (git == gotos.end() || git->second.empty()) {
777,734✔
157
                continue;
723,118✔
158
            }
159
            auto& gotoSet = git->second;
54,616✔
160
            auto signature = makeCoreSignature(gotoSet, buffers);
54,616✔
161
            const auto existing = stateByCores.find(signature);
54,616✔
162
            if (existing == stateByCores.end()) {
54,616✔
163
                const std::size_t newState = canonicalCollection.size();
3,246✔
164
                canonicalCollection.push_back(std::move(gotoSet));
3,246✔
165
                stateByCores.emplace(std::move(signature), newState);
3,246✔
166
                computedGotos[{ state, X }] = newState;
3,246✔
167
                enqueue(newState);
3,246✔
168
            } else {
169
                const std::size_t target = existing->second;
51,370✔
170
                computedGotos[{ state, X }] = target;
51,370✔
171
                if (mergeLookaheadsByCore(canonicalCollection[target], gotoSet, buffers)) {
51,370✔
172
                    enqueue(target);
3,224✔
173
                }
174
            }
175
        }
54,616✔
176
    }
5,566✔
177
}
16✔
178

179
// --- LR(1) ---
180

181
using ItemSetSignature = std::vector<std::pair<std::uint64_t, LR1Item::LookaheadSet>>;
182

183
ItemSetSignature makeItemSetSignature(const std::vector<LR1Item>& items) {
58,714✔
184
    ItemSetSignature signature;
58,714✔
185
    signature.reserve(items.size());
58,714✔
186
    for (const auto& item : items) {
649,214✔
187
        signature.emplace_back(item.coreKey(), item.lookaheads());
590,500✔
188
    }
189
    // Cores are unique within a closed item set; sort by core only.
190
    std::sort(signature.begin(), signature.end(),
58,714✔
191
            [](const auto& left, const auto& right) {
3,577,638✔
192
                return left.first < right.first;
3,577,638✔
193
            });
194
    return signature;
58,714✔
NEW
UNCOV
195
}
×
196

197
struct ItemSetSignatureHash {
198
    std::size_t operator()(const ItemSetSignature& signature) const noexcept {
104,314✔
199
        std::size_t hash = signature.size();
104,314✔
200
        for (const auto& entry : signature) {
1,282,006✔
201
            hashCombine(hash, entry.first);
1,177,692✔
202
            for (std::size_t i = 0; i < entry.second.size(); i += 64) {
3,533,076✔
203
                unsigned long long word = 0;
2,355,384✔
204
                for (std::size_t b = 0; b < 64 && i + b < entry.second.size(); ++b) {
153,099,960✔
205
                    if (entry.second.test(i + b)) {
150,744,576✔
206
                        word |= 1ULL << b;
23,600,556✔
207
                    }
208
                }
209
                hashCombine(hash, word);
2,355,384✔
210
            }
211
        }
212
        return hash;
104,314✔
213
    }
214
};
215

216
void computeLR1(
10✔
217
        std::vector<std::vector<LR1Item>>& canonicalCollection,
218
        GotoMap& computedGotos,
219
        const std::vector<int>& symbolOrder,
220
        const FirstTable& first,
221
        const Grammar& grammar,
222
        GenerationBuffers& buffers) {
223
    std::unordered_map<ItemSetSignature, std::size_t, ItemSetSignatureHash> stateByItems;
10✔
224
    stateByItems.reserve(256);
10✔
225
    stateByItems.emplace(makeItemSetSignature(canonicalCollection[0]), 0);
10✔
226

227
    for (std::size_t i = 0; i < canonicalCollection.size(); ++i) {
6,448✔
228
        auto gotos = gotoAllFrom(canonicalCollection[i], first, grammar, buffers);
6,438✔
229
        for (const int X : symbolOrder) {
839,604✔
230
            const auto git = gotos.find(X);
833,166✔
231
            if (git == gotos.end() || git->second.empty()) {
833,166✔
232
                continue;
774,462✔
233
            }
234
            auto& gotoSet = git->second;
58,704✔
235
            auto signature = makeItemSetSignature(gotoSet);
58,704✔
236
            const auto existing = stateByItems.find(signature);
58,704✔
237
            if (existing == stateByItems.end()) {
58,704✔
238
                const std::size_t newState = canonicalCollection.size();
6,428✔
239
                canonicalCollection.push_back(std::move(gotoSet));
6,428✔
240
                stateByItems.emplace(std::move(signature), newState);
6,428✔
241
                computedGotos[{ i, X }] = newState;
6,428✔
242
            } else {
243
                computedGotos[{ i, X }] = existing->second;
52,276✔
244
            }
245
        }
58,704✔
246
    }
6,438✔
247
}
10✔
248

249
} // namespace
250

251
namespace parser {
252

253
CanonicalCollection::CanonicalCollection(
26✔
254
        const FirstTable& firstTable,
255
        const Grammar& grammar,
256
        AutomatonKind kind) :
26✔
257
        firstTable { firstTable }
26✔
258
{
259
    // Nonterminals then terminals — stable order for state numbering / table identity.
260
    std::vector<int> symbolOrder = grammar.getNonterminalIDs();
26✔
261
    const auto& terminals = grammar.getTerminalIDs();
26✔
262
    symbolOrder.insert(symbolOrder.end(), terminals.begin(), terminals.end());
26✔
263

264
    std::vector<LR1Item> initialSet {
265
        LR1Item { grammar.getTopRule(), std::vector<int>{ grammar.getEndSymbol() }, grammar }
52✔
266
    };
104✔
267
    closeItemSet(firstTable, grammar, buffers, initialSet);
26✔
268
    canonicalCollection.push_back(std::move(initialSet));
26✔
269

270
    if (kind == AutomatonKind::LALR1) {
26✔
271
        computeLALR1(canonicalCollection, computedGotos, symbolOrder, firstTable, grammar, buffers);
16✔
272
    } else {
273
        computeLR1(canonicalCollection, computedGotos, symbolOrder, firstTable, grammar, buffers);
10✔
274
    }
275

276
    logCollection(grammar);
26✔
277
}
26✔
278

279
std::size_t CanonicalCollection::stateCount() const noexcept {
42✔
280
    return canonicalCollection.size();
42✔
281
}
282

283
const std::vector<LR1Item>& CanonicalCollection::setOfItemsAtState(size_t state) const {
3,492✔
284
    return canonicalCollection.at(state);
3,492✔
285
}
286

287
std::size_t CanonicalCollection::goTo(std::size_t stateFrom, int symbol) const {
21,276✔
288
    return computedGotos.at({ stateFrom, symbol });
21,276✔
289
}
290

291
std::optional<std::size_t> CanonicalCollection::findGoTo(std::size_t stateFrom, int symbol) const noexcept {
188,684✔
292
    const auto it = computedGotos.find({ stateFrom, symbol });
188,684✔
293
    return it != computedGotos.end() ? std::optional<std::size_t>{ it->second } : std::nullopt;
188,684✔
294
}
295

296
void CanonicalCollection::logCollection(const Grammar& grammar) const {
26✔
297
    if (logger.isNull()) {
26✔
298
        return;
24✔
299
    }
300
    logger << "\n*********************\nCanonical collection:\n*********************\n";
2✔
301
    int setNo = 0;
2✔
302
    for (const auto& setOfItems : canonicalCollection) {
16✔
303
        logger << "Set " << setNo++ << ":\n";
14✔
304
        for (const auto& item : setOfItems) {
42✔
305
            logger << item.str(grammar);
28✔
306
        }
307
        logger << "\n";
14✔
308
    }
309
    logger << "Computed GOTOs:\n";
2✔
310
    for (const auto& g : computedGotos) {
22✔
311
        logger << g.first.first << "\t" << g.first.second << "\t" << g.second << "\n";
20✔
312
    }
313
}
314

315
} // namespace parser
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