#ifndef CTLL__PARSER__HPP #define CTLL__PARSER__HPP #include "fixed_string.hpp" #include "list.hpp" #include "grammars.hpp" #include "actions.hpp" #ifndef CTLL_IN_A_MODULE #include #endif namespace ctll { enum class decision { reject, accept, undecided }; struct placeholder { }; template using index_placeholder = placeholder; #if CTLL_CNTTP_COMPILER_CHECK template struct parser { // in c++20 #else template struct parser { #endif #ifdef __GNUC__ // workaround to GCC bug #if CTLL_CNTTP_COMPILER_CHECK static constexpr auto _input = input; // c++20 mode #else static constexpr auto & _input = input; // c++17 mode #endif #else static constexpr auto _input = input; // everyone else #endif using Actions = ctll::conditional, identity>; using grammar = augment_grammar; template struct results { static constexpr bool is_correct = Decision == decision::accept; constexpr inline CTLL_FORCE_INLINE operator bool() const noexcept { return is_correct; } #ifdef __GNUC__ // workaround to GCC bug #if CTLL_CNTTP_COMPILER_CHECK static constexpr auto _input = input; // c++20 mode #else static constexpr auto & _input = input; // c++17 mode #endif #else static constexpr auto _input = input; // everyone else #endif using output_type = Subject; static constexpr size_t position = Pos; constexpr auto operator+(placeholder) const noexcept { if constexpr (Decision == decision::undecided) { // parse for current char (RPos) with previous stack and subject :) return parser::template decide({}, {}); } else { // if there is decision already => just push it to the end of fold expression return *this; } } }; template static constexpr auto get_current_term() noexcept { if constexpr (Pos < input.size()) { constexpr auto value = input[Pos]; if constexpr (value <= static_cast((std::numeric_limits::max)())) { return term(value)>{}; } else { return term{}; } } else { // return epsilon if we are past the input return epsilon{}; } } template static constexpr auto get_previous_term() noexcept { if constexpr (Pos == 0) { // there is no previous character on input if we are on start return epsilon{}; } else if constexpr ((Pos-1) < input.size()) { constexpr auto value = input[Pos-1]; if constexpr (value <= static_cast((std::numeric_limits::max)())) { return term(value)>{}; } else { return term{}; } } else { return epsilon{}; } } // if rule is accept => return true and subject template static constexpr auto move(ctll::accept, Terminal, Stack, Subject) noexcept { return typename parser::template results(); } // if rule is reject => return false and subject template static constexpr auto move(ctll::reject, Terminal, Stack, Subject) noexcept { return typename parser::template results(); } // if rule is pop_input => move to next character template static constexpr auto move(ctll::pop_input, Terminal, Stack, Subject) noexcept { return typename parser::template results(); } // if rule is string => push it to the front of stack template static constexpr auto move(push string, Terminal, Stack stack, Subject subject) noexcept { return decide(push_front(string, stack), subject); } // if rule is epsilon (empty string) => continue template static constexpr auto move(epsilon, Terminal, Stack stack, Subject subject) noexcept { return decide(stack, subject); } // if rule is string with current character at the beginning (term) => move to next character // and push string without the character (quick LL(1)) template static constexpr auto move(push, Content...>, term, Stack stack, Subject) noexcept { constexpr auto local_input = input; return typename parser::template results(), stack)), Subject, decision::undecided>(); } // if rule is string with any character at the beginning (compatible with current term) => move to next character // and push string without the character (quick LL(1)) template static constexpr auto move(push, term, Stack stack, Subject) noexcept { constexpr auto local_input = input; return typename parser::template results(), stack)), Subject, decision::undecided>(); } // decide if we need to take action or move template static constexpr auto decide(Stack previous_stack, Subject previous_subject) noexcept { // each call means we pop something from stack auto top_symbol = decltype(ctll::front(previous_stack, empty_stack_symbol()))(); // gcc pedantic warning [[maybe_unused]] auto stack = decltype(ctll::pop_front(previous_stack))(); // in case top_symbol is action type (apply it on previous subject and get new one) if constexpr (std::is_base_of_v) { auto subject = Actions::apply(top_symbol, get_previous_term(), previous_subject); // in case that semantic action is error => reject input if constexpr (std::is_same_v) { return typename parser::template results(); } else { return decide(stack, subject); } } else { // all other cases are ordinary for LL(1) parser auto current_term = get_current_term(); auto rule = decltype(grammar::rule(top_symbol,current_term))(); return move(rule, current_term, stack, previous_subject); } } // trampolines with folded expression template static constexpr auto trampoline_decide(Subject, std::index_sequence) noexcept { // parse everything for first char and than for next and next ... // Pos+1 is needed as we want to finish calculation with epsilons on stack auto v = (decide<0, typename grammar::start_stack, Subject>({}, {}) + ... + index_placeholder()); return v; } template static constexpr auto trampoline_decide(Subject subject = {}) noexcept { // there will be no recursion, just sequence long as the input return trampoline_decide(subject, std::make_index_sequence()); } template using output = decltype(trampoline_decide()); template static inline constexpr bool correct_with = trampoline_decide(); }; } // end of ctll namespace #endif