diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d62f3f2..9c8eaaa 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -17,26 +17,32 @@ jobs: strategy: matrix: cmake_build_type: [Asan, Release] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 + - name: Install dependencies run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main" sudo apt update sudo apt install build-essential + sudo apt install clang-format-18 clang-tidy-18 + - name: Setup ccache uses: hendrikmuhs/ccache-action@v1.2 with: create-symlink: true - key: ${{ github.job }}-${{ matrix.os }} + key: ${{ github.job }}-${{ matrix.cmake_build_type }} + - name: Clang-Format working-directory: ${{github.workspace}}/ports/cpp run: | - ( \ - find source test -iname '*.hpp' -o -iname '*.cpp' \ - | xargs clang-format -Werror --dry-run \ - --fallback-style=Google --verbose \ - ) + find source test -iname '*.hpp' -o -iname '*.cpp' \ + | xargs clang-format-18 -Werror --dry-run \ + --fallback-style=Google --verbose + - name: Configure working-directory: ${{github.workspace}}/ports/cpp run: | @@ -46,11 +52,20 @@ jobs: -DANTLR4C3_DEVELOPER=ON \ -DCMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }} \ .. + - name: Build working-directory: ${{github.workspace}}/ports/cpp/build run: make + - name: Unit Test working-directory: ${{github.workspace}}/ports/cpp/build/test run: | ctest cat Testing/Temporary/LastTest.log + + - name: Clang-Tidy on sources + if: matrix.cmake_build_type == 'Release' + working-directory: ${{github.workspace}}/ports/cpp + run: | + find source -iname '*.hpp' -o -iname '*.cpp' \ + | xargs clang-tidy-18 -p build/compile_commands.json diff --git a/ports/cpp/.clang-format b/ports/cpp/.clang-format index 375a5a1..2961714 100644 --- a/ports/cpp/.clang-format +++ b/ports/cpp/.clang-format @@ -2,6 +2,7 @@ Language: Cpp BasedOnStyle: Google +ColumnLimit: 80 AccessModifierOffset: -2 AllowShortFunctionsOnASingleLine: None diff --git a/ports/cpp/.clang-tidy b/ports/cpp/.clang-tidy new file mode 100644 index 0000000..5db4aee --- /dev/null +++ b/ports/cpp/.clang-tidy @@ -0,0 +1,22 @@ +--- +Checks: '-*, + bugprone-*, + cert-*, + concurrency-*, + cppcoreguidelines-*, + google-*, + hicpp-*, + misc-*, + -misc-no-recursion, + modernize-*, + -modernize-use-trailing-return-type, + performance-*, + readability-*' +WarningsAsErrors: '*' +HeaderFileExtensions: + - hpp +ImplementationFileExtensions: + - cpp +HeaderFilterRegex: '' +FormatStyle: '.clang-format' +SystemHeaders: false diff --git a/ports/cpp/source/antlr4-c3/CodeCompletionCore.cpp b/ports/cpp/source/antlr4-c3/CodeCompletionCore.cpp index 3da04f0..d73e333 100644 --- a/ports/cpp/source/antlr4-c3/CodeCompletionCore.cpp +++ b/ports/cpp/source/antlr4-c3/CodeCompletionCore.cpp @@ -6,62 +6,82 @@ // #include "CodeCompletionCore.hpp" -using namespace antlr4; -using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace c3 { -// ---------------------------------------------------------------------------- -// MARK: - Utilities -// ---------------------------------------------------------------------------- +namespace { -static std::vector longestCommonPrefix( - std::vector a, std::vector b +std::vector longestCommonPrefix( + std::vector lhs, std::vector rhs ) { - size_t i = 0; - for (; i < std::min(a.size(), b.size()); i++) { - if (a[i] != b[i]) { + size_t index = 0; + for (; index < std::min(lhs.size(), rhs.size()); index++) { + if (lhs[index] != rhs[index]) { break; } } - - return std::vector(a.begin(), a.begin() + i); + return { + lhs.begin(), + std::next(lhs.begin(), static_cast(index)), + }; } -// ---------------------------------------------------------------------------- -// MARK: - Static -// ---------------------------------------------------------------------------- +} // namespace -std::map +std::map // NOLINT c3::CodeCompletionCore::followSetsByATN = {}; // Matches ATNStateType enum -std::vector c3::CodeCompletionCore::atnStateTypeMap = { - "invalid", "basic", - "rule start", "block start", - "plus block start", "star block start", - "token start", "rule stop", - "block end", "star loop back", - "star loop entry", "plus loop back", - "loop end", -}; - -// ---------------------------------------------------------------------------- -// MARK: - Construction -// ---------------------------------------------------------------------------- +std::vector c3::CodeCompletionCore::atnStateTypeMap // NOLINT + { + "invalid", "basic", + "rule start", "block start", + "plus block start", "star block start", + "token start", "rule stop", + "block end", "star loop back", + "star loop entry", "plus loop back", + "loop end", + }; CodeCompletionCore::CodeCompletionCore(antlr4::Parser* parser) : parser(parser) , atn(parser->getATN()) , vocabulary(parser->getVocabulary()) - , ruleNames(parser->getRuleNames()) { + , ruleNames(parser->getRuleNames()) + , timeoutMS(0) + , cancel(nullptr) { } -// ---------------------------------------------------------------------------- -// MARK: - Collecting -// ---------------------------------------------------------------------------- - -CandidatesCollection CodeCompletionCore::collectCandidates( +CandidatesCollection CodeCompletionCore::collectCandidates( // NOLINT size_t caretTokenIndex, antlr4::ParserRuleContext* context, size_t timeoutMS, std::atomic* cancel ) { @@ -75,31 +95,31 @@ CandidatesCollection CodeCompletionCore::collectCandidates( this->cancel = cancel; this->timeoutMS = timeoutMS; - tokenStartIndex = context ? context->start->getTokenIndex() : 0; - const auto tokenStream = parser->getTokenStream(); + tokenStartIndex = (context != nullptr) ? context->start->getTokenIndex() : 0; + auto* const tokenStream = parser->getTokenStream(); tokens = {}; size_t offset = tokenStartIndex; while (true) { antlr4::Token* token = tokenStream->get(offset++); - if (token->getChannel() == Token::DEFAULT_CHANNEL) { + if (token->getChannel() == antlr4::Token::DEFAULT_CHANNEL) { tokens.push_back(token); if (token->getTokenIndex() >= caretTokenIndex || - token->getType() == Token::EOF) { + token->getType() == antlr4::Token::EOF) { break; } } // Do not check for the token index here, as we want to end with the first // unhidden token on or after the caret. - if (token->getType() == Token::EOF) { + if (token->getType() == antlr4::Token::EOF) { break; } } RuleWithStartTokenList callStack = {}; - size_t startRule = context ? context->getRuleIndex() : 0; + const size_t startRule = (context != nullptr) ? context->getRuleIndex() : 0; bool cancelled = false; processRule(atn.ruleToStartState[startRule], 0, callStack, 0, 0, cancelled); @@ -118,19 +138,19 @@ CandidatesCollection CodeCompletionCore::collectCandidates( std::cout << ruleNames[tokenIndex]; std::cout << ", path: "; - for (size_t token : rule.ruleList) { + for (const size_t token : rule.ruleList) { std::cout << ruleNames[token] + " "; } } std::cout << "\n\n"; std::set sortedTokens; - for (auto entry : candidates.tokens) { - size_t token = entry.first; - std::vector tokenList = entry.second; + for (const auto& entry : candidates.tokens) { + const size_t token = entry.first; + const std::vector tokenList = entry.second; std::string value = vocabulary.getDisplayName(token); - for (size_t following : tokenList) { + for (const size_t following : tokenList) { value += " " + vocabulary.getDisplayName(following); } @@ -138,7 +158,7 @@ CandidatesCollection CodeCompletionCore::collectCandidates( } std::cout << "Collected tokens:\n"; - for (std::string symbol : sortedTokens) { + for (const std::string& symbol : sortedTokens) { std::cout << symbol; } std::cout << "\n\n"; @@ -157,7 +177,9 @@ CandidatesCollection CodeCompletionCore::collectCandidates( bool CodeCompletionCore::checkPredicate( const antlr4::atn::PredicateTransition* transition ) { - return transition->getPredicate()->eval(parser, &ParserRuleContext::EMPTY); + return transition->getPredicate()->eval( + parser, &antlr4::ParserRuleContext::EMPTY + ); } /** @@ -171,17 +193,19 @@ bool CodeCompletionCore::checkPredicate( bool CodeCompletionCore::translateStackToRuleIndex( RuleWithStartTokenList const& ruleWithStartTokenList ) { - if (preferredRules.size() == 0) { + if (preferredRules.empty()) { return false; } // Change the direction we iterate over the rule stack + auto forward = std::views::iota(0U, ruleWithStartTokenList.size()); + auto backward = forward | std::views::reverse; if (translateRulesTopDown) { // Loop over the rule stack from lowest to highest rule level. This will // prioritize a lower preferred rule if it is a child of a higher one that // is also a preferred rule. - for (int64_t i = ruleWithStartTokenList.size() - 1; i >= 0; i--) { - if (translateToRuleIndex(i, ruleWithStartTokenList)) { + for (const auto index : backward) { + if (translateToRuleIndex(index, ruleWithStartTokenList)) { return true; } } @@ -189,8 +213,8 @@ bool CodeCompletionCore::translateStackToRuleIndex( // Loop over the rule stack from highest to lowest rule level. This will // prioritize a higher preferred rule if it contains a lower one that is // also a preferred rule. - for (size_t i = 0; i < ruleWithStartTokenList.size(); i++) { - if (translateToRuleIndex(i, ruleWithStartTokenList)) { + for (const auto index : forward) { + if (translateToRuleIndex(index, ruleWithStartTokenList)) { return true; } } @@ -209,18 +233,17 @@ bool CodeCompletionCore::translateStackToRuleIndex( * @returns true if the specified rule is in the list of preferred rules. */ bool CodeCompletionCore::translateToRuleIndex( - size_t i, RuleWithStartTokenList const& ruleWithStartTokenList + size_t index, RuleWithStartTokenList const& ruleWithStartTokenList ) { - RuleWithStartToken rwst = ruleWithStartTokenList[i]; + const RuleWithStartToken rwst = ruleWithStartTokenList[index]; if (preferredRules.contains(rwst.ruleIndex)) { // Add the rule to our candidates list along with the current rule path, // but only if there isn't already an entry like that. std::vector path; - { - for (size_t subrangeIndex = 0; subrangeIndex < i; subrangeIndex++) { - path.push_back(ruleWithStartTokenList[subrangeIndex].ruleIndex); - } + path.reserve(index); + for (size_t subrangeIndex = 0; subrangeIndex < index; subrangeIndex++) { + path.push_back(ruleWithStartTokenList[subrangeIndex].ruleIndex); } bool addNew = true; @@ -250,7 +273,8 @@ bool CodeCompletionCore::translateToRuleIndex( if (addNew) { candidates.rules[rwst.ruleIndex] = { - .startTokenIndex = rwst.startTokenIndex, .ruleList = path + .startTokenIndex = rwst.startTokenIndex, + .ruleList = path, }; if (showDebugOutput) { std::cout << "=====> collected: " << ruleNames[rwst.ruleIndex] << "\n"; @@ -273,21 +297,22 @@ bool CodeCompletionCore::translateToRuleIndex( */ std::vector CodeCompletionCore::getFollowingTokens( const antlr4::atn::Transition* transition -) { +) const { std::vector result = {}; std::vector pipeline = {transition->target}; - while (pipeline.size() > 0) { + while (!pipeline.empty()) { antlr4::atn::ATNState* state = pipeline.back(); pipeline.pop_back(); - if (state) { - for (antlr4::atn::ConstTransitionPtr& outgoing : state->transitions) { + if (state != nullptr) { + for (const antlr4::atn::ConstTransitionPtr& outgoing : + state->transitions) { if (outgoing->getTransitionType() == antlr4::atn::TransitionType::ATOM) { if (!outgoing->isEpsilon()) { - std::vector list = outgoing->label().toList(); + std::vector list = outgoing->label().toList(); if (list.size() == 1 && !ignoredTokens.contains(list[0])) { result.push_back(list[0]); pipeline.push_back(outgoing->target); @@ -316,14 +341,14 @@ FollowSetsHolder CodeCompletionCore::determineFollowSets( std::vector sets = {}; std::vector stateStack = {}; std::vector ruleStack = {}; - bool isExhaustive = + const bool isExhaustive = collectFollowSets(start, stop, sets, stateStack, ruleStack); // Sets are split by path to allow translating them to preferred rules. But // for quick hit tests it is also useful to have a set with all symbols // combined. antlr4::misc::IntervalSet combined; - for (auto set : sets) { + for (const auto& set : sets) { combined.addAll(set.intervals); } @@ -347,30 +372,33 @@ FollowSetsHolder CodeCompletionCore::determineFollowSets( * @returns true if the follow sets is exhaustive, i.e. we terminated before the * rule end was reached, so no subsequent rules could add tokens */ -bool CodeCompletionCore::collectFollowSets( - antlr4::atn::ATNState* s, antlr4::atn::ATNState* stopState, +bool CodeCompletionCore::collectFollowSets( // NOLINT + antlr4::atn::ATNState* state, antlr4::atn::ATNState* stopState, std::vector& followSets, std::vector& stateStack, std::vector& ruleStack ) { - if (std::find(stateStack.begin(), stateStack.end(), s) != stateStack.end()) { + if (std::find(stateStack.begin(), stateStack.end(), state) != + stateStack.end()) { return true; } - stateStack.push_back(s); - if (s == stopState || - s->getStateType() == antlr4::atn::ATNStateType::RULE_STOP) { + stateStack.push_back(state); + + if (state == stopState || + state->getStateType() == antlr4::atn::ATNStateType::RULE_STOP) { stateStack.pop_back(); return false; } bool isExhaustive = true; - for (antlr4::atn::ConstTransitionPtr& tp : s->transitions) { - const antlr4::atn::Transition* transition = tp.get(); + for (const antlr4::atn::ConstTransitionPtr& transitionPtr : + state->transitions) { + const antlr4::atn::Transition* transition = transitionPtr.get(); if (transition->getTransitionType() == antlr4::atn::TransitionType::RULE) { - const antlr4::atn::RuleTransition* ruleTransition = - static_cast(transition); + const auto* ruleTransition = + dynamic_cast(transition); if (std::find( ruleStack.begin(), ruleStack.end(), @@ -381,7 +409,7 @@ bool CodeCompletionCore::collectFollowSets( ruleStack.push_back(ruleTransition->target->ruleIndex); - bool ruleFollowSetsIsExhaustive = collectFollowSets( + const bool ruleFollowSetsIsExhaustive = collectFollowSets( transition->target, stopState, followSets, stateStack, ruleStack ); ruleStack.pop_back(); @@ -390,7 +418,7 @@ bool CodeCompletionCore::collectFollowSets( // added to the follow set are non-exhaustive and we should continue // processing subsequent transitions post-rule if (!ruleFollowSetsIsExhaustive) { - bool nextStateFollowSetsIsExhaustive = collectFollowSets( + const bool nextStateFollowSetsIsExhaustive = collectFollowSets( ruleTransition->followState, stopState, followSets, stateStack, ruleStack ); @@ -400,15 +428,15 @@ bool CodeCompletionCore::collectFollowSets( } else if (transition->getTransitionType() == antlr4::atn::TransitionType::PREDICATE) { if (checkPredicate( - static_cast(transition) + dynamic_cast(transition) )) { - bool nextStateFollowSetsIsExhaustive = collectFollowSets( + const bool nextStateFollowSetsIsExhaustive = collectFollowSets( transition->target, stopState, followSets, stateStack, ruleStack ); isExhaustive = isExhaustive && nextStateFollowSetsIsExhaustive; } } else if (transition->isEpsilon()) { - bool nextStateFollowSetsIsExhaustive = collectFollowSets( + const bool nextStateFollowSetsIsExhaustive = collectFollowSets( transition->target, stopState, followSets, stateStack, ruleStack ); isExhaustive = isExhaustive && nextStateFollowSetsIsExhaustive; @@ -416,7 +444,8 @@ bool CodeCompletionCore::collectFollowSets( antlr4::atn::TransitionType::WILDCARD) { FollowSetWithPath set; set.intervals = antlr4::misc::IntervalSet::of( - antlr4::Token::MIN_USER_TOKEN_TYPE, atn.maxTokenType + antlr4::Token::MIN_USER_TOKEN_TYPE, + static_cast(atn.maxTokenType) ); set.path = ruleStack; followSets.push_back(set); @@ -426,7 +455,8 @@ bool CodeCompletionCore::collectFollowSets( if (transition->getTransitionType() == antlr4::atn::TransitionType::NOT_SET) { label = label.complement(antlr4::misc::IntervalSet::of( - antlr4::Token::MIN_USER_TOKEN_TYPE, atn.maxTokenType + antlr4::Token::MIN_USER_TOKEN_TYPE, + static_cast(atn.maxTokenType) )); } FollowSetWithPath set; @@ -457,23 +487,24 @@ bool CodeCompletionCore::collectFollowSets( * @returns the set of token stream indexes (which depend on the ways that had * to be taken). */ -RuleEndStatus CodeCompletionCore::processRule( +RuleEndStatus CodeCompletionCore::processRule( // NOLINT antlr4::atn::RuleStartState* startState, size_t tokenListIndex, - RuleWithStartTokenList& callStack, int precedence, size_t indentation, - bool& cancelled + RuleWithStartTokenList& callStack, int precedence, // NOLINT + size_t indentation, // NOLINT + bool& timedOut ) { // Cancelled by external caller? - if (cancel && cancel->load()) { - cancelled = true; + if (cancel != nullptr && cancel->load()) { + timedOut = true; return {}; } // Check for timeout - cancelled = false; + timedOut = false; if (timeoutMS > 0) { - std::chrono::duration timeout(timeoutMS); + const std::chrono::duration timeout(timeoutMS); if (std::chrono::steady_clock::now() - timeoutStart > timeout) { - cancelled = true; + timedOut = true; return {}; } } @@ -513,15 +544,16 @@ RuleEndStatus CodeCompletionCore::processRule( FollowSetsPerState& setsPerState = followSetsByATN[typeid(parser)]; if (!setsPerState.contains(startState->stateNumber)) { - auto stop = atn.ruleToStopState[startState->ruleIndex]; + antlr4::atn::RuleStopState* stop = + atn.ruleToStopState[startState->ruleIndex]; auto followSets = determineFollowSets(startState, stop); setsPerState[startState->stateNumber] = followSets; } - FollowSetsHolder followSets = setsPerState[startState->stateNumber]; + const FollowSetsHolder followSets = setsPerState[startState->stateNumber]; // Get the token index where our rule starts from our (possibly filtered) // token list - size_t startTokenIndex = tokens[tokenListIndex]->getTokenIndex(); + const size_t startTokenIndex = tokens[tokenListIndex]->getTokenIndex(); callStack.push_back({ .startTokenIndex = startTokenIndex, @@ -536,13 +568,13 @@ RuleEndStatus CodeCompletionCore::processRule( } else { // Convert all follow sets to either single symbols or their associated // preferred rule and add the result to our candidates list. - for (FollowSetWithPath& set : followSets.sets) { + for (const FollowSetWithPath& set : followSets.sets) { RuleWithStartTokenList fullPath = callStack; // Rules derived from our followSet will always start at the same token // as our current rule. RuleWithStartTokenList followSetPath; - for (size_t rule : set.path) { + for (const size_t rule : set.path) { followSetPath.push_back({ .startTokenIndex = startTokenIndex, .ruleIndex = rule, @@ -554,8 +586,8 @@ RuleEndStatus CodeCompletionCore::processRule( ); if (!translateStackToRuleIndex(fullPath)) { - for (ssize_t symbol : set.intervals.toList()) { - if (!ignoredTokens.contains((size_t)symbol)) { + for (ptrdiff_t symbol : set.intervals.toList()) { + if (!ignoredTokens.contains(static_cast(symbol))) { if (showDebugOutput) { std::cout << "=====> collected: " << vocabulary.getDisplayName(symbol) << "\n"; @@ -586,18 +618,16 @@ RuleEndStatus CodeCompletionCore::processRule( callStack.pop_back(); return result; + } - } else { - // Process the rule if we either could pass it without consuming anything - // (epsilon transition) or if the current input symbol will be matched - // somewhere after this entry point. Otherwise stop here. - size_t currentSymbol = tokens[tokenListIndex]->getType(); - if (followSets.isExhaustive && - !followSets.combined.contains(currentSymbol)) { - callStack.pop_back(); - - return result; - } + // Process the rule if we either could pass it without consuming anything + // (epsilon transition) or if the current input symbol will be matched + // somewhere after this entry point. Otherwise stop here. + const size_t currentSymbol = tokens[tokenListIndex]->getType(); + if (followSets.isExhaustive && !followSets.combined.contains(currentSymbol)) { + callStack.pop_back(); + + return result; } if (startState->isLeftRecursiveRule) { @@ -614,19 +644,19 @@ RuleEndStatus CodeCompletionCore::processRule( {.state = startState, .tokenListIndex = tokenListIndex} ); - while (statePipeline.size() > 0) { - if (cancel && cancel->load()) { - cancelled = true; + while (!statePipeline.empty()) { + if (cancel != nullptr && cancel->load()) { + timedOut = true; return {}; } - PipelineEntry currentEntry = statePipeline.back(); + const PipelineEntry currentEntry = statePipeline.back(); statePipeline.pop_back(); ++statesProcessed; - size_t currentSymbol = tokens[currentEntry.tokenListIndex]->getType(); + const size_t currentSymbol = tokens[currentEntry.tokenListIndex]->getType(); - bool atCaret = currentEntry.tokenListIndex >= tokens.size() - 1; + const bool atCaret = currentEntry.tokenListIndex >= tokens.size() - 1; if (showDebugOutput) { printDescription( indentation, currentEntry.state, @@ -648,25 +678,27 @@ RuleEndStatus CodeCompletionCore::processRule( // We simulate here the same precedence handling as the parser does, which // uses hard coded values. For rules that are not left recursive this value // is ignored (since there is no precedence transition). - for (antlr4::atn::ConstTransitionPtr& transition : + for (const antlr4::atn::ConstTransitionPtr& transition : currentEntry.state->transitions) { switch (transition->getTransitionType()) { case antlr4::atn::TransitionType::RULE: { - const atn::RuleTransition* ruleTransition = - static_cast(transition.get()); - atn::RuleStartState* ruleStartState = - static_cast(ruleTransition->target); + const auto* ruleTransition = + dynamic_cast(transition.get() + ); + auto* ruleStartState = + dynamic_cast(ruleTransition->target + ); bool innerCancelled = false; - RuleEndStatus endStatus = processRule( + const RuleEndStatus endStatus = processRule( ruleStartState, currentEntry.tokenListIndex, callStack, ruleTransition->precedence, indentation + 1, innerCancelled ); if (innerCancelled) { - cancelled = true; + timedOut = true; return {}; } - for (size_t position : endStatus) { + for (const size_t position : endStatus) { statePipeline.push_back({ .state = ruleTransition->followState, .tokenListIndex = position, @@ -676,8 +708,10 @@ RuleEndStatus CodeCompletionCore::processRule( } case antlr4::atn::TransitionType::PREDICATE: { - const atn::PredicateTransition* predTransition = - static_cast(transition.get()); + const auto* predTransition = + dynamic_cast( + transition.get() + ); if (checkPredicate(predTransition)) { statePipeline.push_back({ .state = transition->target, @@ -688,8 +722,8 @@ RuleEndStatus CodeCompletionCore::processRule( } case antlr4::atn::TransitionType::PRECEDENCE: { - const atn::PrecedencePredicateTransition* predTransition = - static_cast( + const auto* predTransition = + dynamic_cast( transition.get() ); if (predTransition->getPrecedence() >= @@ -706,11 +740,11 @@ RuleEndStatus CodeCompletionCore::processRule( case antlr4::atn::TransitionType::WILDCARD: { if (atCaret) { if (!translateStackToRuleIndex(callStack)) { - for (auto token : - antlr4::misc::IntervalSet::of( - antlr4::Token::MIN_USER_TOKEN_TYPE, atn.maxTokenType - ) - .toList()) { + const auto tokens = antlr4::misc::IntervalSet::of( + antlr4::Token::MIN_USER_TOKEN_TYPE, + static_cast(atn.maxTokenType) + ); + for (auto token : tokens.toList()) { if (!ignoredTokens.contains(token)) { candidates.tokens[token] = {}; } @@ -741,14 +775,15 @@ RuleEndStatus CodeCompletionCore::processRule( if (transition->getTransitionType() == antlr4::atn::TransitionType::NOT_SET) { set = set.complement(antlr4::misc::IntervalSet::of( - antlr4::Token::MIN_USER_TOKEN_TYPE, atn.maxTokenType + antlr4::Token::MIN_USER_TOKEN_TYPE, + static_cast(atn.maxTokenType) )); } if (atCaret) { if (!translateStackToRuleIndex(callStack)) { - std::vector list = set.toList(); - bool hasTokenSequence = list.size() == 1; - for (size_t symbol : list) { + const std::vector list = set.toList(); + const bool hasTokenSequence = list.size() == 1; + for (const size_t symbol : list) { if (!ignoredTokens.contains(symbol)) { if (showDebugOutput) { std::cout << "=====> collected: " @@ -806,14 +841,14 @@ RuleEndStatus CodeCompletionCore::processRule( std::string CodeCompletionCore::generateBaseDescription( antlr4::atn::ATNState* state ) { - std::string stateValue = - (state->stateNumber == atn::ATNState::INVALID_STATE_NUMBER) + const std::string stateValue = + (state->stateNumber == antlr4::atn::ATNState::INVALID_STATE_NUMBER) ? "Invalid" : std::to_string(state->stateNumber); std::stringstream output; output << "[" << stateValue << " " - << atnStateTypeMap[(size_t)state->getStateType()] << "]"; + << atnStateTypeMap[static_cast(state->getStateType())] << "]"; output << " in "; output << ruleNames[state->ruleIndex]; return output.str(); @@ -823,39 +858,48 @@ void CodeCompletionCore::printDescription( size_t indentation, antlr4::atn::ATNState* state, std::string const& baseDescription, size_t tokenIndex ) { - std::string indent = std::string(indentation * 2, ' '); - std::string output = ""; - std::string transitionDescription = ""; + const std::string indent = std::string(indentation * 2, ' '); + std::string output; + std::string transitionDescription; if (debugOutputWithTransitions) { - for (antlr4::atn::ConstTransitionPtr& transition : state->transitions) { - std::string labels = ""; - std::vector symbols = transition->label().toList(); + for (const antlr4::atn::ConstTransitionPtr& transition : + state->transitions) { + std::string labels; + std::vector symbols = transition->label().toList(); if (symbols.size() > 2) { // Only print start and end symbols to avoid large lists in debug // output. - labels = vocabulary.getDisplayName((size_t)symbols[0]) + " .. " + - vocabulary.getDisplayName((size_t)symbols[symbols.size() - 1]); + labels = vocabulary.getDisplayName(static_cast(symbols[0])) + + " .. " + + vocabulary.getDisplayName( + static_cast(symbols[symbols.size() - 1]) + ); } else { - for (size_t symbol : symbols) { - if (labels.size() > 0) { + for (const size_t symbol : symbols) { + if (!labels.empty()) { labels += ", "; } labels += vocabulary.getDisplayName(symbol); } } - if (labels.size() == 0) { + if (labels.empty()) { labels = "ε"; } + transitionDescription += "\n"; + transitionDescription += indent; + transitionDescription += "\t("; + transitionDescription += labels; + transitionDescription += ") ["; + transitionDescription += std::to_string(transition->target->stateNumber); + transitionDescription += " "; transitionDescription += - "\n" + indent + "\t(" + labels + ") " + "[" + - std::to_string(transition->target->stateNumber) + " " + - atnStateTypeMap[(size_t)transition->target->getStateType()] + - "]" - " in " + - ruleNames[transition->target->ruleIndex]; + atnStateTypeMap[static_cast(transition->target->getStateType() + )]; + transitionDescription += "] in "; + transitionDescription += ruleNames[transition->target->ruleIndex]; } } @@ -871,17 +915,15 @@ void CodeCompletionCore::printDescription( } void CodeCompletionCore::printRuleState(RuleWithStartTokenList const& stack) { - if (stack.size() == 0) { + if (stack.empty()) { std::cout << "\n"; return; } - if (stack.size() > 0) { - for (RuleWithStartToken rule : stack) { - std::cout << ruleNames[rule.ruleIndex]; - } - std::cout << "\n"; + for (const RuleWithStartToken rule : stack) { + std::cout << ruleNames[rule.ruleIndex]; } + std::cout << "\n"; } } // namespace c3 diff --git a/ports/cpp/source/antlr4-c3/CodeCompletionCore.hpp b/ports/cpp/source/antlr4-c3/CodeCompletionCore.hpp index 5e88065..6a54b99 100644 --- a/ports/cpp/source/antlr4-c3/CodeCompletionCore.hpp +++ b/ports/cpp/source/antlr4-c3/CodeCompletionCore.hpp @@ -8,21 +8,30 @@ #ifndef CodeCompletionCore_hpp #define CodeCompletionCore_hpp +#include +#include +#include +#include +#include +#include +#include +#include + #include #include +#include #include #include #include #include #include -#include "antlr4-runtime.h" - // ---------------------------------------------------------------------------- // Supporting Types // ---------------------------------------------------------------------------- namespace c3 { + using TokenList = std::vector; using RuleList = std::vector; @@ -104,7 +113,7 @@ class CodeCompletionCore { // Construction // -------------------------------------------------------- - CodeCompletionCore(antlr4::Parser* parser); + explicit CodeCompletionCore(antlr4::Parser* parser); // -------------------------------------------------------- // Configuration @@ -114,20 +123,20 @@ class CodeCompletionCore { * Tailoring of the result: * Tokens which should not appear in the candidates set. */ - std::unordered_set ignoredTokens; + std::unordered_set ignoredTokens; // NOLINT: public field /** * Rules which replace any candidate token they contain. * This allows to return descriptive rules (e.g. className, instead of * ID/identifier). */ - std::unordered_set preferredRules; + std::unordered_set preferredRules; // NOLINT: public field /** * Specify if preferred rules should translated top-down (higher index rule * returns first) or bottom-up (lower index rule returns first). */ - bool translateRulesTopDown = false; + bool translateRulesTopDown = false; // NOLINT: public field // -------------------------------------------------------- // Debugging Options @@ -136,18 +145,18 @@ class CodeCompletionCore { /** Not dependent on showDebugOutput. Prints the collected rules + tokens to * terminal. */ - bool showResult = false; + bool showResult = false; // NOLINT: public field /** Enables printing ATN state info to terminal. */ - bool showDebugOutput = false; + bool showDebugOutput = false; // NOLINT: public field /** Only relevant when showDebugOutput is true. Enables transition printing * for a state. */ - bool debugOutputWithTransitions = false; + bool debugOutputWithTransitions = false; // NOLINT: public field /** Also depends on showDebugOutput. Enables call stack printing for each rule * recursion. */ - bool showRuleStack = false; + bool showRuleStack = false; // NOLINT: public field // -------------------------------------------------------- // Usage @@ -185,9 +194,9 @@ class CodeCompletionCore { static std::vector atnStateTypeMap; antlr4::Parser* parser; - antlr4::atn::ATN const& atn; - antlr4::dfa::Vocabulary const& vocabulary; - std::vector const& ruleNames; + antlr4::atn::ATN const& atn; // NOLINT: reference field + antlr4::dfa::Vocabulary const& vocabulary; // NOLINT: reference field + std::vector const& ruleNames; // NOLINT: reference field std::vector tokens; std::vector precedenceStack; @@ -208,24 +217,30 @@ class CodeCompletionCore { std::chrono::steady_clock::time_point timeoutStart; bool checkPredicate(const antlr4::atn::PredicateTransition* transition); + bool translateStackToRuleIndex( RuleWithStartTokenList const& ruleWithStartTokenList ); + bool translateToRuleIndex( - size_t i, RuleWithStartTokenList const& ruleWithStartTokenList + size_t index, RuleWithStartTokenList const& ruleWithStartTokenList ); + std::vector getFollowingTokens( const antlr4::atn::Transition* transition - ); + ) const; + FollowSetsHolder determineFollowSets( antlr4::atn::ATNState* start, antlr4::atn::ATNState* stop ); + bool collectFollowSets( - antlr4::atn::ATNState* s, antlr4::atn::ATNState* stopState, + antlr4::atn::ATNState* state, antlr4::atn::ATNState* stopState, std::vector& followSets, std::vector& stateStack, std::vector& ruleStack ); + RuleEndStatus processRule( antlr4::atn::RuleStartState* startState, size_t tokenListIndex, RuleWithStartTokenList& callStack, int precedence, size_t indentation, @@ -233,10 +248,12 @@ class CodeCompletionCore { ); std::string generateBaseDescription(antlr4::atn::ATNState* state); + void printDescription( size_t indentation, antlr4::atn::ATNState* state, std::string const& baseDescription, size_t tokenIndex ); + void printRuleState(RuleWithStartTokenList const& stack); }; diff --git a/ports/cpp/test/cpp14/Cpp14Test.cpp b/ports/cpp/test/cpp14/Cpp14Test.cpp index 9b202ed..f711017 100644 --- a/ports/cpp/test/cpp14/Cpp14Test.cpp +++ b/ports/cpp/test/cpp14/Cpp14Test.cpp @@ -1,9 +1,17 @@ #include #include +#include +#include #include #include +#include +#include +#include +#include +#include #include +#include namespace c3::test { @@ -12,7 +20,7 @@ struct Cpp14Grammar { using Parser = CPP14Parser; }; -TEST(CPP14Parser, SimpleExample) { +TEST(CPP14Parser, SimpleExample) { // NOLINT: complexity // We are trying here to get useful code completion candidates without // adjusting the grammar in any way. We use the grammar as downloaded from the // ANTLR grammar directory and set up the c3 engine instead in a way that @@ -105,7 +113,7 @@ TEST(CPP14Parser, SimpleExample) { // Note when counting token indexes: the C++14 grammar skips all // whitespaces, hence there are no tokens for them. completion.translateRulesTopDown = translateRulesTopDown; - auto candidates = completion.collectCandidates(10); + auto candidates = completion.collectCandidates(10); // NOLINT: magic const std::vector idexpressionStack = { CPP14Parser::RuleTranslationunit, @@ -195,7 +203,7 @@ TEST(CPP14Parser, SimpleExample) { // Note when counting token indexes: the C++14 grammar skips all // whitespaces, hence there are no tokens for them. completion.translateRulesTopDown = true; - auto candidates = completion.collectCandidates(10); + auto candidates = completion.collectCandidates(10); // NOLINT: magic EXPECT_EQ(candidates.tokens.size(), 82); @@ -259,7 +267,7 @@ TEST(CPP14Parser, SimpleCppExampleWithErrorsInInput) { { // At the opening parenthesis. - auto candidates = completion.collectCandidates(11); + auto candidates = completion.collectCandidates(11); // NOLINT: magic EXPECT_THAT( Keys(candidates.tokens), UnorderedElementsAre(CPP14Lexer::LeftParen) @@ -268,7 +276,7 @@ TEST(CPP14Parser, SimpleCppExampleWithErrorsInInput) { { // At the closing parenthesis -> again everything in an expression allowed // (no control flow this time, though). - auto candidates = completion.collectCandidates(12); + auto candidates = completion.collectCandidates(12); // NOLINT: magic EXPECT_EQ(candidates.tokens.size(), 65); @@ -290,14 +298,14 @@ TEST(CPP14Parser, SimpleCppExampleWithErrorsInInput) { } { // After the error position -> no suggestions. - auto candidates = completion.collectCandidates(13); + auto candidates = completion.collectCandidates(13); // NOLINT: magic EXPECT_EQ(candidates.tokens.size(), 0); EXPECT_EQ(candidates.rules.size(), 0); } } -TEST(CPP14Parser, RealCppFile) { +TEST(CPP14Parser, RealCppFile) { // NOLINT: complexity { const auto path = std::filesystem::current_path().string(); EXPECT_TRUE(path.ends_with("ports/cpp/build/test/cpp14")); @@ -332,7 +340,7 @@ TEST(CPP14Parser, RealCppFile) { CPP14Parser::RuleIdexpression, }; - std::vector idexpressionStack = { + const std::vector idexpressionStack = { CPP14Parser::RuleTranslationunit, CPP14Parser::RuleDeclarationseq, CPP14Parser::RuleDeclaration, @@ -352,7 +360,7 @@ TEST(CPP14Parser, RealCppFile) { CPP14Parser::RuleDeclaratorid, }; - std::vector classnameStack = Concat( + const std::vector classnameStack = Concat( idexpressionStack, { CPP14Parser::RuleIdexpression, @@ -362,7 +370,7 @@ TEST(CPP14Parser, RealCppFile) { } ); - std::vector namespacenameStack = Concat( + const std::vector namespacenameStack = Concat( idexpressionStack, { CPP14Parser::RuleIdexpression, @@ -372,7 +380,7 @@ TEST(CPP14Parser, RealCppFile) { ); { - auto candidates = completion.collectCandidates(3469); + auto candidates = completion.collectCandidates(3469); // NOLINT: magic EXPECT_THAT( Keys(candidates.rules), @@ -391,7 +399,7 @@ TEST(CPP14Parser, RealCppFile) { // We should receive more specific rules when translating top down. completion.translateRulesTopDown = true; - auto candidates = completion.collectCandidates(3469); + auto candidates = completion.collectCandidates(3469); // NOLINT: magic EXPECT_THAT( candidates.rules[CPP14Parser::RuleClassname].ruleList, diff --git a/ports/cpp/test/expr/ExprTest.cpp b/ports/cpp/test/expr/ExprTest.cpp index b3f1a65..e979abd 100644 --- a/ports/cpp/test/expr/ExprTest.cpp +++ b/ports/cpp/test/expr/ExprTest.cpp @@ -1,7 +1,11 @@ #include #include +#include +#include #include +#include +#include #include namespace c3::test { @@ -58,13 +62,13 @@ TEST(SimpleExpressionParser, MostSimpleSetup) { // 5) On the variable reference 'a'. But since we have not configure the c3 // engine to return us var refs (or function refs for that matter) we only // get an ID here. - auto candidates = completion.collectCandidates(6); + auto candidates = completion.collectCandidates(6); // NOLINT: magic EXPECT_THAT(Keys(candidates.tokens), UnorderedElementsAre(ExprLexer::ID)); } { // 6) On the '+' operator. Usually you would not show operators as // candidates, but we have not set up the c3 engine yet to not return them. - auto candidates = completion.collectCandidates(8); + auto candidates = completion.collectCandidates(8); // NOLINT: magic EXPECT_THAT( Keys(candidates.tokens), UnorderedElementsAre( @@ -114,7 +118,7 @@ TEST(SimpleExpressionParser, TypicalSetup) { } { // 5) On the variable reference 'a'. - auto candidates = completion.collectCandidates(6); + auto candidates = completion.collectCandidates(6); // NOLINT: magic EXPECT_EQ(candidates.tokens.size(), 0); // Here we get 2 rule indexes, derived from 2 different IDs possible at this // caret position. These are what we told the engine above to be preferred @@ -131,7 +135,7 @@ TEST(SimpleExpressionParser, TypicalSetup) { { // 6) On the whitespace just after the variable reference 'a' (but it could // still be a function reference!). - auto candidates = completion.collectCandidates(7); + auto candidates = completion.collectCandidates(7); // NOLINT: magic EXPECT_EQ(candidates.tokens.size(), 0); EXPECT_THAT( Keys(candidates.rules), @@ -151,7 +155,7 @@ TEST(SimpleExpressionParser, RecursivePreferredRule) { { // 1) On the variable reference 'a'. - auto candidates = completion.collectCandidates(6); + auto candidates = completion.collectCandidates(6); // NOLINT: magic EXPECT_THAT( Keys(candidates.rules), UnorderedElementsAre(ExprParser::RuleSimpleExpression) @@ -164,7 +168,7 @@ TEST(SimpleExpressionParser, RecursivePreferredRule) { { // 2) On the variable reference 'b'. completion.translateRulesTopDown = false; - auto candidates = completion.collectCandidates(10); + auto candidates = completion.collectCandidates(10); // NOLINT: magic EXPECT_THAT( Keys(candidates.rules), UnorderedElementsAre(ExprParser::RuleSimpleExpression) @@ -179,7 +183,7 @@ TEST(SimpleExpressionParser, RecursivePreferredRule) { { // 3) On the variable reference 'b' topDown preferred rules. completion.translateRulesTopDown = true; - auto candidates = completion.collectCandidates(10); + auto candidates = completion.collectCandidates(10); // NOLINT: magic EXPECT_THAT( Keys(candidates.rules), UnorderedElementsAre(ExprParser::RuleSimpleExpression) @@ -221,7 +225,7 @@ TEST(SimpleExpressionParser, CandidateRulesWithDifferentStartTokens) { } { // 2) On the variable reference 'a'. - auto candidates = completion.collectCandidates(6); + auto candidates = completion.collectCandidates(6); // NOLINT: magic EXPECT_THAT( Keys(candidates.rules), UnorderedElementsAre( diff --git a/ports/cpp/test/utility/AntlrPipeline.hpp b/ports/cpp/test/utility/AntlrPipeline.hpp index 406317e..e11a110 100644 --- a/ports/cpp/test/utility/AntlrPipeline.hpp +++ b/ports/cpp/test/utility/AntlrPipeline.hpp @@ -1,8 +1,14 @@ #pragma once -#include +#include +#include +#include +#include +#include #include +#include +#include #include namespace c3::test { @@ -10,14 +16,14 @@ namespace c3::test { class CountingErrorListener final : public antlr4::BaseErrorListener { public: void syntaxError( - antlr4::Recognizer* recognizer, antlr4::Token* offendingSymbol, - std::size_t line, std::size_t charPositionInLine, const std::string& msg, - std::exception_ptr e + antlr4::Recognizer* /*recognizer*/, antlr4::Token* /*offendingSymbol*/, + std::size_t /*line*/, std::size_t /*charPositionInLine*/, + const std::string& /*msg*/, std::exception_ptr /*e*/ ) override { errorCount += 1; } - std::size_t GetErrorCount() const { + [[nodiscard]] std::size_t GetErrorCount() const { return errorCount; } @@ -27,17 +33,17 @@ class CountingErrorListener final : public antlr4::BaseErrorListener { template struct AntlrPipeline { - AntlrPipeline(std::string_view text) + explicit AntlrPipeline(std::string_view text) : chars(text), lexer(&chars), tokens(&lexer), parser(&tokens) { parser.removeErrorListeners(); parser.addErrorListener(&listener); } - antlr4::ANTLRInputStream chars; - Grammar::Lexer lexer; - antlr4::CommonTokenStream tokens; - Grammar::Parser parser; - CountingErrorListener listener; + antlr4::ANTLRInputStream chars; // NOLINT: public + Grammar::Lexer lexer; // NOLINT: public + antlr4::CommonTokenStream tokens; // NOLINT: public + Grammar::Parser parser; // NOLINT: public + CountingErrorListener listener; // NOLINT: public }; } // namespace c3::test diff --git a/ports/cpp/test/utility/Testing.hpp b/ports/cpp/test/utility/Testing.hpp index bccd226..bef9d1b 100644 --- a/ports/cpp/test/utility/Testing.hpp +++ b/ports/cpp/test/utility/Testing.hpp @@ -1,10 +1,6 @@ #pragma once #include -#include - -#include "AntlrPipeline.hpp" -#include "Collections.hpp" namespace c3::test { diff --git a/ports/cpp/test/whitebox/WhiteboxTest.cpp b/ports/cpp/test/whitebox/WhiteboxTest.cpp index bf328a8..a160779 100644 --- a/ports/cpp/test/whitebox/WhiteboxTest.cpp +++ b/ports/cpp/test/whitebox/WhiteboxTest.cpp @@ -1,7 +1,13 @@ +#include #include #include +#include +#include #include +#include +#include +#include #include namespace c3::test { @@ -55,11 +61,11 @@ TEST(WhiteboxGrammarTests, CaretAtOneOfMultiplePossibleStates) { switch (index) { case 4: return pipeline.parser.test4(); - case 5: + case 5: // NOLINT: magic return pipeline.parser.test5(); - case 6: + case 6: // NOLINT: magic return pipeline.parser.test6(); - case 7: + case 7: // NOLINT: magic return pipeline.parser.test7(); default: std::abort();