magpie
Loading...
Searching...
No Matches
Compile.hpp
1#pragma once
2
3#include "magpie/data/CommonData.hpp"
4#include <algorithm>
5#include <array>
6#include <charconv>
7#include <cstddef>
8#include <cstdint>
9#include <functional>
10#include <stdexcept>
11#include <type_traits>
12#include <utility>
13#include <vector>
14
15namespace magpie::routing {
16
17struct ConstString;
18template <size_t N>
19struct FixedString {
20 std::array<char, N + 1> data{};
21 const size_t size = N;
22
23 constexpr FixedString(const char (&str)[N + 1]) {
24 std::copy_n(str, N + 1, std::data(data));
25 }
26 constexpr explicit FixedString(const ConstString& src);
27
28 constexpr explicit FixedString(const char* begin) {
29 std::copy_n(begin, N, std::data(data));
30 data.back() = '\0';
31 }
32 constexpr char operator[](size_t idx) const {
33 if (idx >= size) {
34 throw std::runtime_error("Invalid string index");
35 }
36 return *(data.data() + idx);
37 }
38 constexpr ConstString toConstString() const;
39
40 constexpr auto c_str() const -> char const* { return std::data(data); }
41};
42
43template <std::size_t N>
44FixedString(const char (&str)[N]) -> FixedString<N - 1>;
45
46struct ConstString {
47 const char* str;
48 size_t size;
49
50 constexpr ConstString() {
51 str = nullptr;
52 size = 0;
53 }
54 template <size_t N>
55 constexpr ConstString(const char (&str)[N]): str(str), size(N) {
56 static_assert(N >= 1, "Invalid string");
57 }
58 constexpr ConstString(const ConstString& other): str(other.str), size(other.size) {}
59
60 constexpr explicit ConstString(const char* str, size_t N): str(str), size(N) {
61 if (N <= 1) {
62 throw std::runtime_error("Invalid string");
63 }
64 }
65
66 template <size_t N, size_t Offset>
67 constexpr FixedString<N> subrangeToFixedString() const {
68 if (Offset + N >= size) {
69 throw std::runtime_error("offset and length is out of bounds");
70 }
71 return FixedString<N>(str + Offset);
72 }
73
74 constexpr void operator=(const ConstString& rhs) {
75 this->str = rhs.str;
76 this->size = rhs.size;
77 }
78
79 constexpr char operator[](size_t idx) const {
80 if (idx >= size) {
81 throw std::runtime_error("Invalid string index");
82 }
83 return *(str + idx);
84 }
85 constexpr const char* end() const { return str + size; }
86};
87
88template <size_t N>
89constexpr ConstString FixedString<N>::toConstString() const {
90 return ConstString((const char*) std::data(data), N + 1);
91}
92template <size_t N>
93constexpr FixedString<N>::FixedString(const ConstString& src) {
94 std::copy_n(src.str, N + 1, std::data(data));
95}
96
97template <int offset>
98constexpr bool startsWithAtOffset(const ConstString& source, const ConstString& substring) {
99 if (substring.size > source.size - offset) {
100 return false;
101 }
102
103 for (size_t i = 0; i < substring.size; ++i) {
104 if (source[i + offset] != substring[i]) {
105 return false;
106 }
107 }
108 return true;
109}
110
111constexpr bool startsWithAtOffset(const ConstString& source, const ConstString& substring, size_t offset) {
112 if (substring.size > source.size - offset) {
113 return false;
114 }
115
116 for (size_t i = 0; i < substring.size; ++i) {
117 if (source[i + offset] != substring[i] && substring[i] != '\0') {
118 return false;
119 }
120 }
121 return true;
122}
123
124template <FixedString source>
125constexpr size_t guessParams() {
126 size_t result = 0;
127 for (size_t i = 0; i < source.size; ++i) {
128 if (source[i] == '{') {
129 ++result;
130 }
131 }
132 return result;
133}
134
135template <FixedString s>
136struct TypeInfo : public std::false_type {
137 using type = void;
138};
139
140template<>
141struct TypeInfo<"{int}"> {
142 using type = int64_t;
143 static constexpr type convert(const std::string_view& v) {
144 type out;
145 std::from_chars(v.data(), v.data() + v.size(), out);
146 return out;
147 }
148};
149
150template<>
151struct TypeInfo<"{string}"> {
152 using type = std::string_view;
153 static constexpr type convert(const std::string_view& v) {
154 return v.back() == '/' ?
155 v.substr(0, v.size() - 1)
156 : v;
157 }
158};
159
160constexpr static inline ConstString INT_VALUE("{int}", 6);
161constexpr static inline ConstString STRING_VALUE("{string}", 9);
162
163template <FixedString s, size_t matched, size_t params>
164constexpr void parse(std::array<ConstString, params>& out, size_t i = 0) {
165 if constexpr (matched == params) {
166 return;
167 } else {
168 if (i >= s.size) {
169 return;
170 }
171 if (s[i] == '{') {
172 constexpr auto constStr = s.toConstString();
173 if (startsWithAtOffset(constStr, INT_VALUE, i)) {
174 out.at(matched) = INT_VALUE;
175 parse<s, matched + 1, params>(out, i + 5);
176 } else if (startsWithAtOffset(constStr, STRING_VALUE, i)) {
177 out.at(matched) = STRING_VALUE;
178 parse<s, matched + 1, params>(out, i + 8);
179 } else {
180 throw "invalid";
181 }
182 } else {
183 parse<s, matched, params>(out, i + 1);
184 }
185 }
186}
187
188template <FixedString s, size_t matched = 0, size_t params = guessParams<s>()>
189constexpr std::array<ConstString, params> getParameterTypes() {
190 if constexpr (params == 0) {
191 return std::array<ConstString, params>{};
192 } else {
193 std::array<ConstString, params> out;
194 parse<s, matched, params>(out);
195
196 return out;
197 }
198}
199
200template <FixedString s, size_t matched, size_t params>
201constexpr void parseForIndices(
202 std::array<std::pair<ConstString, size_t>, params>& out,
203 size_t slashIndex = 1,
204 size_t i = 1
205) {
206 if constexpr (matched == params) {
207 return;
208 } else {
209 // This logic should hold:
210 // / [ "/ [idx=1]" ]
211 // fails but irrelevant because params = 0]
212 // /whatever : [ "/", "whatever [idx=1]" ]
213 // /whatever/nested : [ "/", "whatever [idx = 1]", "/ [idx = 3]", "nested" ]
214 // /whatever/nested/ : [ "/", "whatever [idx = 1]", "/ [idx = 3]", "nested", "/ [idx = 5]" ]
215 // idx=5 fails but it's ignored since it's unreachable (matched == params at idx=3 when nested is found)
216 // ... assuming these were actually templates and not literal strings, but you get the point
217 auto newSlashIndex = slashIndex + (s[i] == '/') * 2;
218 if (i >= s.size) {
219 return;
220 }
221 if (s[i] == '{') {
222 constexpr auto constStr = s.toConstString();
223 if (startsWithAtOffset(constStr, INT_VALUE, i)) {
224 std::get<matched>(out) = {INT_VALUE, slashIndex};
225 parseForIndices<s, matched + 1, params>(out, newSlashIndex, i + 5);
226 } else if (startsWithAtOffset(constStr, STRING_VALUE, i)) {
227 std::get<matched>(out) = {STRING_VALUE, slashIndex};
228 parseForIndices<s, matched + 1, params>(out, newSlashIndex, i + 8);
229 } else {
230 throw "invalid";
231 }
232 } else {
233 parseForIndices<s, matched, params>(out, newSlashIndex, i + 1);
234 }
235 }
236}
237
238template <FixedString s, size_t Size = guessParams<s>()>
239consteval auto getForwardableIndices() {
240 std::array<std::pair<ConstString, size_t>, Size> out;
241 parseForIndices<s, 0, Size>(out);
242 return out;
243}
244
245template <FixedString s>
246consteval bool isValidPath() {
247 for (size_t i = 0; i < s.size; ++i) {
248 if (s[i] == '{') {
249 if (i == 0 || s[i - 1] != '/') {
250 return false;
251 }
252 } else if (s[i] == '}') {
253 if (i != s.size - 1 && s[i + 1] != '/') {
254 return false;
255 }
256 }
257 }
258 return true;
259}
260
261template <
262 FixedString s,
263 typename ReturnType = void,
264 typename ...Extras
265>
267private:
268 static_assert(s[0] == '/', "Invalid route: must start with /");
269 static_assert(isValidPath<s>(), "Invalid route: each placeholder value must be a separate segment");
270
271 constexpr static auto typeArray = getParameterTypes<s>();
272
273 template <std::size_t... I>
274 static auto compileType(std::index_sequence<I...>)
275 -> typename std::function<
276 ReturnType(
277 Extras...,
278 typename TypeInfo<
279 FixedString<typeArray[I].size - 1>(typeArray[I])
280 >::type...
281 )
282 >;
283
284public:
285 using type = decltype(
286 compileType(
287 std::make_index_sequence<typeArray.size()>()
288 )
289 );
290
291};
292
293}
Definition Compile.hpp:46
Definition Compile.hpp:19
Definition Compile.hpp:266
Definition Compile.hpp:136