1+ #pragma once
2+
3+ #include <algorithm>
4+ #include <string_view>
5+ #include <string>
6+ #include <cstddef>
7+ #include <vector>
8+ #include <utility>
9+ #include <iterator>
10+ #include <meta>
11+
12+ #include <rsl/string_constant>
13+ #include <rsl/string_view>
14+ #include <rsl/span>
15+ #include <rsl/assert>
16+
17+ namespace rsl {
18+ namespace _trie_impl {
19+ template <auto... Transitions>
20+ struct State {
21+ rsl::string_view prefix;
22+ int word_index;
23+
24+ [[clang::always_inline]]
25+ constexpr int visit(std::string_view str) const {
26+ // assumes the prefix is never empty
27+ //? empty prefixes are only allowed at the root note
28+ if (str.empty() || str[0] != prefix[0] || str.size() < prefix.size()) {
29+ return 0;
30+ }
31+
32+ if (str.size() == prefix.size()) {
33+ // str would become empty after removing the prefix
34+ return word_index;
35+ }
36+
37+ str.remove_prefix(prefix.size());
38+ template for (constexpr auto T : {Transitions...}) {
39+ if (int result = T.visit(str); result) {
40+ return result;
41+ }
42+ }
43+ return 0;
44+ }
45+ };
46+
47+ template <std::meta::info Words, auto... Transitions>
48+ struct Root {
49+ static constexpr auto&& words = [:Words:];
50+
51+ constexpr static bool matches(std::string_view str) {
52+ if (str.empty()) {
53+ return false;
54+ }
55+ return find(str) >= 0;
56+ }
57+
58+ constexpr static int find(std::string_view str) {
59+ int result = -1;
60+ if (str.empty()) {
61+ return -1;
62+ }
63+ template for (constexpr auto T : {Transitions...}) {
64+ if (int result = T.visit(str); result && str == words[result - 1]) {
65+ return result - 1;
66+ }
67+ }
68+ return -1;
69+ }
70+ };
71+
72+ template <auto... Transitions>
73+ constexpr inline Root<Transitions...> make_trie{};
74+
75+ template <string_constant transition, int word_index, auto... Transitions>
76+ constexpr inline auto make_state = State<Transitions...>{
77+ {transition.data, transition.size},
78+ word_index
79+ };
80+
81+ struct ParsedState {
82+ std::string prefix;
83+ std::vector<ParsedState> transitions;
84+ rsl::span<rsl::string_view> words;
85+ int word_index = 0;
86+
87+ static constexpr ParsedState make(rsl::span<rsl::string_view> words) {
88+ std::vector<rsl::string_view> sorted_words{};
89+ sorted_words.assign_range(words);
90+ std::ranges::sort(sorted_words);
91+
92+ ParsedState root{"", {}, words};
93+
94+ for (auto word : sorted_words) {
95+ ParsedState* node = &root;
96+ std::size_t i = 0;
97+
98+ while (i < word.size()) {
99+ auto it = std::ranges::find_if(node->transitions, [&](const ParsedState& s) {
100+ return s.prefix.starts_with(word[i]);
101+ });
102+
103+ if (it == node->transitions.end()) {
104+ int word_idx = std::ranges::distance(words.begin(), std::ranges::find(words, word)) + 1;
105+
106+ node->transitions.emplace_back(std::string(word.substr(i)),
107+ std::vector<ParsedState>{
108+ {"", {}, {}, word_idx}
109+ });
110+ break;
111+ }
112+
113+ std::size_t const match_len =
114+ std::mismatch(it->prefix.begin(), it->prefix.end(), word.begin() + i).first -
115+ it->prefix.begin();
116+
117+ if (match_len < it->prefix.size()) {
118+ ParsedState split_node{it->prefix.substr(match_len), std::move(it->transitions)};
119+ it->prefix.resize(match_len);
120+ it->transitions = {std::move(split_node)};
121+ }
122+
123+ i += match_len;
124+ node = &*it;
125+ }
126+ }
127+ return root;
128+ }
129+
130+ explicit(false) consteval operator std::meta::info() const {
131+ std::vector<std::meta::info> states = {};
132+ int index = 0;
133+ for (auto&& state : transitions) {
134+ if (state.prefix.empty()) {
135+ index = state.word_index;
136+ continue;
137+ }
138+ states.push_back(state);
139+ }
140+
141+ if (!transitions.empty() && prefix.empty()) {
142+ // prevent aggressive inlining at the root level
143+ std::vector<rsl::string_view> word_list;
144+ for (auto word : words) {
145+ word_list.emplace_back(define_static_string(word));
146+ }
147+ states.insert(states.begin(), reflect_constant(std::meta::reflect_constant_array(word_list)));
148+ return substitute(^^make_trie, states);
149+ }
150+
151+ std::vector args = {std::meta::reflect_constant_string(prefix),
152+ std::meta::reflect_constant(index)};
153+ for (auto state : states) {
154+ args.push_back(state);
155+ }
156+ return substitute(^^make_state, args);
157+ }
158+ };
159+
160+ constexpr bool has_duplicates(std::span<rsl::string_view> wordlist) {
161+ std::ranges::sort(wordlist);
162+ return std::ranges::adjacent_find(wordlist) != wordlist.end();
163+ }
164+ } // namespace _trie_impl
165+
166+ struct trie {
167+ bool (*matches)(std::string_view str) = nullptr;
168+ int (*find)(std::string_view str) = nullptr;
169+
170+ explicit consteval trie(std::vector<rsl::string_view> words) {
171+ constexpr_assert(!words.empty(), "An empty word list is not allowed.");
172+ constexpr_assert(!_trie_impl::has_duplicates(words),
173+ "Duplicates in the word list are not allowed.");
174+
175+ (this->*extract<void (trie::*)()>(
176+ substitute(^^assign_handlers, {_trie_impl::ParsedState::make(words)})))();
177+ }
178+
179+ private:
180+ template <auto Parser>
181+ constexpr void assign_handlers() {
182+ matches = &Parser.matches;
183+ find = &Parser.find;
184+ }
185+ };
186+ } // namespace rsl::cli
0 commit comments