summaryrefslogtreecommitdiffstats
path: root/server/src/xml.cppm
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/xml.cppm')
-rw-r--r--server/src/xml.cppm632
1 files changed, 632 insertions, 0 deletions
diff --git a/server/src/xml.cppm b/server/src/xml.cppm
new file mode 100644
index 0000000..957f149
--- /dev/null
+++ b/server/src/xml.cppm
@@ -0,0 +1,632 @@
1module;
2
3#include <cassert>
4#include <expat.h>
5
6export module routemon:xml;
7
8import std;
9import :util;
10
11// XML parsing module.
12//
13// Makes heavy use of C++20 coroutines. Provides combinators to build
14// XML (data format) parsers with. To keep overhead low (and allow for
15// HALO/CoroElide), many non-polymorphic definitions are marked inline
16// (which is not the default in module units). This also has the added
17// benefit of allowing HALO across TU boundaries, which is practically
18// necessary to reduce unnecessary allocations when building parsers
19// using the provided combinators.
20
21// Ensure that Expat is speaking UTF-8
22static_assert(std::is_same_v<XML_Char, char>);
23
24namespace routemon::xml {
25
26 struct qname_view {
27 std::string_view ns_uri;
28 std::string_view local;
29
30 auto operator==(qname_view const& rhs) const -> bool;
31 };
32
33 namespace detail {
34
35 constexpr auto qname_sep = '\xFF';
36 auto split_name(char const* name) noexcept -> qname_view {
37 auto [l, r] = util::split_on(name, qname_sep);
38 if (r) return { .ns_uri = l, .local = *r };
39 else return { .ns_uri = std::string_view{}, .local = l };
40 }
41
42 } // namespace detail
43
44 class attribute_view_iterator {
45 std::string_view default_ns_uri_;
46 char const* const* attrs_;
47
48 auto advance() -> void {
49 attrs_ += 2;
50 }
51
52 public:
53 using difference_type = std::ptrdiff_t;
54 using value_type = std::pair<qname_view, char const*>;
55
56 struct sentinel {
57 friend constexpr auto operator==(attribute_view_iterator const& it, sentinel) noexcept -> bool {
58 return !*it.attrs_;
59 }
60 };
61
62 inline explicit attribute_view_iterator(std::string_view default_ns_uri, char const* const* attrs)
63 : default_ns_uri_{default_ns_uri}, attrs_{attrs}
64 {}
65
66 inline auto operator*() const -> std::pair<qname_view, char const*> {
67 if (!*attrs_)
68 throw std::runtime_error{"end of attribute list"};
69 auto qname = detail::split_name(attrs_[0]);
70 if (qname.ns_uri.empty())
71 qname.ns_uri = default_ns_uri_;
72 return std::make_pair(qname, attrs_[1]);
73 }
74
75 // Pre-increment
76 inline auto operator++() -> attribute_view_iterator& {
77 advance();
78 return *this;
79 }
80
81 // Post-increment
82 inline auto operator++(int) -> attribute_view_iterator {
83 auto pre = *this;
84 advance();
85 return pre;
86 }
87 };
88 static_assert(std::input_iterator<attribute_view_iterator>);
89
90 class attribute_view : std::ranges::view_base {
91 std::string_view default_ns_uri_;
92 char const* const* attrs_;
93
94 public:
95 inline explicit attribute_view(std::string_view default_ns_uri, char const** attrs)
96 : default_ns_uri_{default_ns_uri}, attrs_{const_cast<char const* const*>(attrs)}
97 {}
98
99 [[nodiscard]] inline auto begin() const -> attribute_view_iterator {
100 return attribute_view_iterator{default_ns_uri_, attrs_};
101 }
102
103 [[nodiscard]] inline auto end() const -> attribute_view_iterator::sentinel {
104 return {};
105 }
106
107 inline auto lookup(qname_view want) -> std::optional<util::not_null<util::lazy_zstring_view>> {
108 for (auto const& [name, v] : *this) {
109 if (name == want) {
110 return util::not_null{util::lazy_zstring_view{v}};
111 }
112 }
113 return std::nullopt;
114 }
115 };
116 static_assert(std::ranges::input_range<attribute_view>);
117
118 template<class T> class promise;
119
120 template<class T>
121 struct [[clang::coro_await_elidable, clang::coro_return_type]] parser {
122 using promise_type = promise<T>;
123 using result_type = promise_type::result_type;
124 using handle_type = std::coroutine_handle<promise_type>;
125
126 private:
127 handle_type h_;
128
129 public:
130 explicit parser(handle_type h)
131 : h_{h}
132 { assert(h); }
133
134 parser(const parser&) = delete;
135 parser(parser&& c) noexcept
136 : h_{std::exchange(c.h_, nullptr)}
137 {}
138 auto operator=(const parser&) -> parser& = delete;
139 auto operator=(parser&&) -> parser& = delete;
140
141 [[nodiscard]] auto promise() const -> promise_type& {
142 return h_.promise();
143 }
144
145 ~parser() {
146 if (h_) h_.destroy();
147 }
148 };
149
150 struct start_element_event {
151 qname_view name;
152 attribute_view attrs;
153 };
154 struct end_element_event {
155 qname_view name;
156 };
157 struct character_data_event {
158 std::string_view data;
159 };
160 struct processing_instructions_event {
161 util::lazy_zstring_view target;
162 util::lazy_zstring_view data;
163 };
164 struct xml_decl_event {
165 util::lazy_zstring_view version;
166 util::lazy_zstring_view encoding;
167 std::optional<bool> standalone;
168 };
169 struct eof_event {};
170 using event = std::variant<start_element_event,
171 end_element_event,
172 character_data_event,
173 processing_instructions_event,
174 xml_decl_event,
175 eof_event>;
176 template<class T>
177 concept event_type = requires(event ev) { std::get<T>(ev); };
178
179 class executor;
180 using executor_ref = util::not_null<executor*>;
181
182 struct current_event_t {
183 executor_ref executor;
184 };
185 auto current_event(executor_ref executor) -> current_event_t {
186 return current_event_t{executor};
187 }
188
189 class promise_base {
190 executor_ref executor_;
191 std::coroutine_handle<promise_base> continuation_ = nullptr;
192
193 public:
194 // Not having this constructor marked inline messes with coroutine
195 // HALO. (Hours 'wasted': many)
196 inline explicit promise_base(executor_ref executor)
197 : executor_{executor}
198 {}
199
200 [[nodiscard]] inline auto executor() const -> executor& {
201 return *executor_;
202 }
203
204 inline auto base_handle() -> std::coroutine_handle<promise_base> {
205 return std::coroutine_handle<promise_base>::from_promise(*this);
206 }
207
208 [[nodiscard]] inline auto continuation() const -> std::coroutine_handle<promise_base> {
209 return continuation_;
210 }
211 inline auto set_continuation(std::coroutine_handle<promise_base> c) -> void {
212 continuation_ = c;
213 }
214 };
215
216 struct position {
217 std::size_t line;
218 std::size_t col;
219 };
220
221 class executor {
222 XML_Parser p_;
223 std::exception_ptr ex_ = nullptr;
224 std::coroutine_handle<promise_base> continuation_ = nullptr;
225 std::vector<std::optional<std::string>> default_namespace_;
226 std::unordered_map<std::string_view, std::vector<std::string>> namespaces_;
227 std::optional<event> ev_;
228 bool advance_ = true;
229
230 inline auto try_handle_event(event ev) noexcept -> void {
231 assert(!ex_);
232
233 try {
234 ev_ = std::move(ev);
235 } catch (...) {
236 ex_ = std::current_exception();
237 return;
238 }
239 advance_ = false;
240 if (!continuation_) {
241 // Parser returned (all subparsers are done) and has set the continuation to nullptr.
242 if (auto s = XML_StopParser(p_, /* resumable */ false); s != XML_STATUS_OK) {
243 ex_ = std::make_exception_ptr(std::runtime_error{"unexpected error when stopping XML parser"});
244 return;
245 }
246 ex_ = std::make_exception_ptr(std::runtime_error{"parser did not consume entire XML document"});
247 return;
248 }
249 continuation_.resume();
250 if (ex_) {
251 // Not sure if it's useful to report this error.
252 std::ignore = XML_StopParser(p_, /* resumable */ false);
253 }
254 }
255
256 static auto handle_start_element(void* ctx, char const* name, char const** attrs) noexcept -> void {
257 auto qname = detail::split_name(name);
258 static_cast<executor*>(ctx)->try_handle_event(start_element_event{
259 .name = qname,
260 .attrs = attribute_view{qname.ns_uri, attrs},
261 });
262 }
263 static auto handle_end_element(void* ctx, char const* name) noexcept -> void {
264 static_cast<executor*>(ctx)->try_handle_event(end_element_event{
265 .name = detail::split_name(name),
266 });
267 }
268 static auto handle_character_data(void* ctx, char const* s, int len) noexcept -> void {
269 static_cast<executor*>(ctx)->try_handle_event(character_data_event{
270 .data = std::string_view{s, static_cast<std::size_t>(len)},
271 });
272 }
273 static auto handle_processing_instructions(void* ctx, char const* target, char const* data) noexcept -> void {
274 static_cast<executor*>(ctx)->try_handle_event(processing_instructions_event{
275 .target = util::lazy_zstring_view{target},
276 .data = util::lazy_zstring_view{data},
277 });
278 }
279 static auto handle_external_entity_ref(XML_Parser, char const* /* context */, char const* /* base */, char const* /* system_id */, char const* /* public_id */) noexcept -> int {
280 return XML_STATUS_ERROR;
281 }
282 static auto handle_start_namespace_decl(void* ctx, char const* prefix, char const* uri) noexcept -> void {
283 if (prefix) {
284 static_cast<executor*>(ctx)->namespaces_[std::string_view{prefix}].emplace_back(uri);
285 } else {
286 static_cast<executor*>(ctx)->default_namespace_.push_back(uri ? std::make_optional<std::string>(uri) : std::nullopt);
287 }
288 }
289 static auto handle_end_namespace_decl(void* ctx, char const* prefix) noexcept -> void {
290 if (prefix) {
291 static_cast<executor*>(ctx)->namespaces_[std::string_view{prefix}].pop_back();
292 } else {
293 static_cast<executor*>(ctx)->default_namespace_.pop_back();
294 }
295 }
296 static auto handle_xml_decl(void * ctx, char const* version, char const* encoding, int standalone) noexcept -> void {
297 static_cast<executor*>(ctx)->try_handle_event(xml_decl_event{
298 .version = util::lazy_zstring_view{version},
299 .encoding = util::lazy_zstring_view{encoding},
300 .standalone = standalone < 0 ? std::nullopt : std::make_optional(standalone > 0),
301 });
302 }
303
304 inline auto advance_flag() -> bool {
305 return advance_;
306 }
307 inline auto set_exception(std::exception_ptr ex) -> void {
308 ex_ = std::move(ex);
309 }
310 [[nodiscard]] inline auto take_exception() -> std::exception_ptr {
311 return std::exchange(ex_, nullptr);
312 }
313
314 template<class T> friend class promise;
315
316 public:
317 executor();
318
319 executor(executor const&) = delete;
320 executor(executor&&) = delete;
321 auto operator=(executor const&) -> executor& = delete;
322 auto operator=(executor&&) -> executor& = delete;
323
324 ~executor();
325
326 inline auto set_continuation(std::coroutine_handle<promise_base> c) -> void {
327 continuation_ = c;
328 }
329 inline auto set_advance_flag() -> void {
330 if (!ev_ || !std::holds_alternative<eof_event>(ev_.value())) {
331 advance_ = true;
332 }
333 }
334 inline auto event() const -> std::optional<event> const& {
335 return ev_;
336 }
337 inline auto resolve_namespace(std::string_view prefix) -> std::optional<std::string_view> {
338 if (auto it = namespaces_.find(prefix); it != namespaces_.end() && !it->second.empty())
339 return it->second.back();
340 return std::nullopt;
341 }
342 [[nodiscard]] inline auto position() -> position {
343 return {
344 .line = XML_GetCurrentLineNumber(p_),
345 .col = XML_GetCurrentColumnNumber(p_),
346 };
347 }
348
349 auto start() -> void;
350 auto read(std::string_view xml, bool is_final) -> void;
351 auto end() -> void;
352 };
353
354 template<class T>
355 class promise_returnable : public promise_base {
356 std::optional<T> returned_value_;
357
358 public:
359 using result_type = T;
360 using promise_base::promise_base;
361
362 template<class U>
363 auto return_value(U&& v) -> void {
364 returned_value_.emplace(std::forward<U>(v));
365 }
366 auto returned_value() -> T&& {
367 if (!returned_value_)
368 throw std::runtime_error{"XML coroutine did not return"};
369 return std::forward<T>(returned_value_.value());
370 }
371 };
372
373 template<>
374 class promise_returnable<void> : public promise_base {
375 public:
376 using result_type = void;
377 using promise_base::promise_base;
378
379 auto return_void() -> void {}
380 };
381
382 template<class T>
383 class promise : public promise_returnable<T> {
384 public:
385 // Called with all the coroutine's arguments.
386 // Ignoring all but the first argument, which should be the executor.
387 template<class... Args>
388 explicit promise(executor_ref executor, Args&&...)
389 : promise_returnable<T>{executor}
390 {}
391
392 auto handle() -> std::coroutine_handle<promise<T>> {
393 return {parser<T>::handle_type::from_promise(*this)};
394 }
395
396 auto get_return_object() -> parser<T> {
397 return parser<T>{handle()};
398 }
399
400 auto initial_suspend() {
401 return std::suspend_always{};
402 }
403 auto final_suspend() noexcept {
404 struct awaiter {
405 std::coroutine_handle<> h_;
406
407 [[nodiscard]] constexpr auto await_ready() const noexcept -> bool { return false; }
408 auto await_suspend(std::coroutine_handle<>) -> std::coroutine_handle<> { return h_; }
409 constexpr auto await_resume() const noexcept -> void { return; }
410 };
411 if (this->continuation()) {
412 return awaiter{this->continuation()};
413 } else {
414 this->executor().set_continuation(nullptr);
415 return awaiter{std::noop_coroutine()};
416 }
417 }
418
419 auto unhandled_exception() -> void {
420 this->executor().set_exception(std::current_exception());
421 }
422
423 auto await_transform(current_event_t const& req) {
424 struct awaiter {
425 executor_ref executor_;
426
427 [[nodiscard]] constexpr auto await_ready() const noexcept -> bool {
428 return !executor_->advance_flag();
429 }
430 auto await_suspend(std::coroutine_handle<promise<T>> h) -> void {
431 executor_->set_continuation(h.promise().base_handle());
432 }
433 [[nodiscard]] auto await_resume() const -> event {
434 assert(executor_->event());
435 return executor_->event().value();
436 }
437 };
438 return awaiter{req.executor};
439 }
440
441 template<class U>
442 auto await_transform(parser<U> const& coro) {
443 struct [[clang::coro_await_elidable]] awaiter {
444 util::not_null<promise<U>*> next_;
445
446 [[nodiscard]] constexpr auto await_ready() const noexcept -> bool { return false; }
447 auto await_suspend(std::coroutine_handle<promise<T>> h) -> std::coroutine_handle<> {
448 // Passed coroutine handle will be the same as parser<T>::handle_type::from_promise(*this)
449 next_->set_continuation(h.promise().base_handle());
450 return next_->handle();
451 }
452 auto await_resume() -> U {
453 // Promise is still valid since coroutine frame is still alive (and suspended):
454 // control was transferred back to this coroutine via symmetric transfer in
455 // final_suspend(). Assuming that the destructor for coro still needs to run.
456 if (auto ex = next_->executor().take_exception()) {
457 std::rethrow_exception(ex);
458 } else {
459 if constexpr (!std::is_void_v<U>) {
460 return std::move(next_->returned_value());
461 }
462 }
463 }
464 };
465 return awaiter{util::not_null{&coro.promise()}};
466 }
467 };
468
469 // Helpers for handling XML documents. Non-polymorphic functions
470 // should be marked inline to allow HALO across TU boundaries.
471
472 template<class T>
473 concept unconstrained = true;
474
475 template<class T, template<class U> concept C>
476 concept parser_of = requires {
477 typename T::result_type;
478 requires std::same_as<parser<typename T::result_type>, T>;
479 requires C<typename T::result_type>;
480 };
481
482 template<parser_of<unconstrained> T>
483 using parser_result_t = T::result_type;
484
485 template<class T, class... Args>
486 concept parser_invocable = std::invocable<T, Args...> && parser_of<std::invoke_result_t<T, Args...>, unconstrained>;
487
488 template<class Fn, class... Args>
489 requires parser_invocable<Fn, Args...>
490 using parser_invoke_result_t = parser_result_t<std::invoke_result_t<Fn, Args...>>;
491
492 template<event_type T> auto expect_event(executor_ref e) -> parser<T> {
493 auto ev = co_await current_event(e);
494 if (!std::holds_alternative<T>(ev)) {
495 auto pos = e->position();
496 throw std::runtime_error{std::format("at {}:{}: unexpected event type, have {}", pos.line, pos.col, ev.index())};
497 }
498 e->set_advance_flag();
499 co_return std::get<T>(ev);
500 }
501
502 inline auto expect_start_element(executor_ref e, qname_view want) -> parser<attribute_view> {
503 auto ev = co_await expect_event<start_element_event>(e);
504 if (ev.name != want) {
505 auto pos = e->position();
506 throw std::runtime_error{std::format("at {}:{}: unexpected element started", pos.line, pos.col)};
507 }
508 co_return ev.attrs;
509 }
510
511 inline auto allow_start_element(executor_ref e, qname_view want) -> parser<std::optional<attribute_view>> {
512 auto ev = co_await current_event(e);
513 if (auto const* pev = std::get_if<start_element_event>(&ev)) {
514 if (pev->name == want) {
515 e->set_advance_flag();
516 co_return pev->attrs;
517 }
518 }
519 co_return std::nullopt;
520 }
521
522 inline auto expect_end_element(executor_ref e, qname_view want) -> parser<void> {
523 auto ev = co_await expect_event<end_element_event>(e);
524 if (ev.name != want) {
525 throw std::runtime_error{"unexpected element ended"};
526 }
527 }
528
529 inline auto ignore_whitespace(executor_ref e) -> parser<void> {
530 auto all_whitespace = [](std::string_view s) -> bool {
531 for (auto c : s)
532 if (c != ' ' && c != '\r' && c != '\n' && c != '\t')
533 return false;
534 return true;
535 };
536
537 while (true) {
538 auto ev = co_await current_event(e);
539 if (auto const* pev = std::get_if<character_data_event>(&ev); pev && all_whitespace(pev->data)) {
540 e->set_advance_flag();
541 } else {
542 co_return;
543 }
544 }
545 }
546
547 auto expect_element(executor_ref e, qname_view want, parser_invocable<executor_ref, attribute_view> auto p)
548 -> parser<parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>
549 {
550 co_await ignore_whitespace(e);
551 auto attrs = co_await expect_start_element(e, want);
552 auto&& res = co_await p(e, attrs);
553 co_await expect_end_element(e, want);
554 co_await ignore_whitespace(e);
555 co_return std::forward<parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>(res);
556 }
557
558 auto allow_element(executor_ref e, qname_view want, parser_invocable<executor_ref, attribute_view> auto p)
559 -> parser<std::optional<parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>>
560 {
561 co_await ignore_whitespace(e);
562 if (auto mattrs = co_await allow_start_element(e, want)) {
563 auto&& res = co_await p(e, *mattrs);
564 co_await expect_end_element(e, want);
565 co_await ignore_whitespace(e);
566 co_return std::make_optional(std::forward<parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>(res));
567 }
568 co_return std::nullopt;
569 }
570
571 auto allow_element(executor_ref e, qname_view want, parser_invocable<executor_ref, attribute_view> auto p)
572 -> parser<bool>
573 requires std::is_void_v<parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>
574 {
575 co_await ignore_whitespace(e);
576 if (auto mattrs = co_await allow_start_element(e, want)) {
577 co_await p(e, *mattrs);
578 co_await expect_end_element(e, want);
579 co_await ignore_whitespace(e);
580 co_return true;
581 }
582 co_return false;
583 }
584
585 inline auto ignore_contents(executor_ref e, std::optional<qname_view> muntil = std::nullopt) -> parser<void> {
586 std::size_t depth = 0;
587 while (true) {
588 auto ev = co_await current_event(e);
589 if (auto* pev = std::get_if<start_element_event>(&ev)) {
590 if (depth == 0 && muntil && pev->name == *muntil) {
591 co_return;
592 } else {
593 depth++;
594 }
595 } else if (std::holds_alternative<end_element_event>(ev)) {
596 if (depth == 0) {
597 co_return;
598 } else {
599 depth--;
600 }
601 }
602 e->set_advance_flag();
603 }
604 }
605 inline auto ignore_element_contents(executor_ref e, attribute_view) -> parser<void> {
606 co_await ignore_contents(e);
607 }
608
609 inline auto read_string(executor_ref e) -> parser<std::string> {
610 std::string s;
611 while (true) {
612 auto ev = co_await current_event(e);
613 if (auto* pev = std::get_if<character_data_event>(&ev)) {
614 e->set_advance_flag();
615 s += pev->data;
616 } else {
617 co_return s;
618 }
619 }
620 }
621 inline auto read_string_contents(executor_ref e, attribute_view) -> parser<std::string> {
622 co_return co_await read_string(e);
623 }
624
625 template<auto f>
626 auto hohalo() {
627 return []<class... Args>(Args&&... args) -> std::invoke_result_t<decltype(f), Args...> {
628 co_return co_await f(std::forward<Args>(args)...);
629 };
630 }
631
632} // namespace routemon::xml