1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
|
// This source code is released into the public domain.
module;
#include <ucl.h>
export module nihil.ucl:parser;
import nihil.std;
import nihil.monad;
import :object;
import :map;
namespace nihil::ucl {
// UCL parser flags.
export inline constexpr int parser_key_lower = UCL_PARSER_KEY_LOWERCASE;
export inline constexpr int parser_zerocopy = UCL_PARSER_ZEROCOPY;
export inline constexpr int parser_no_time = UCL_PARSER_NO_TIME;
// A macro handler. This proxies the C API callback to the C++ API.
using macro_callback_t = bool(std::string_view);
struct macro_handler
{
std::function<macro_callback_t> callback;
// Handle a callback from the C API.
static auto handle(unsigned char const *data, std::size_t len, void *ud) -> bool
{
auto handler = static_cast<macro_handler *>(ud);
auto string = std::string_view(reinterpret_cast<char const *>(data), len);
return handler->callback(string);
}
};
// A UCL parser. This wraps the C ucl_parser API.
//
// parser itself is not exported; use make_parser() to create one.
struct parser
{
// Create a parser from a UCL parser.
explicit parser(::ucl_parser *uclp)
: m_parser(uclp)
{
}
// Destroy our parser when we're destroyed.
~parser()
{
if (m_parser != nullptr)
::ucl_parser_free(m_parser);
}
// Not copyable.
parser(parser const &) = delete;
auto operator=(this parser &, parser const &) -> parser & = delete;
// Movable.
parser(parser &&other) noexcept
: m_parser(std::exchange(other.m_parser, nullptr))
, m_macros(std::move(other.m_macros))
{
}
auto operator=(this parser &self, parser &&other) noexcept -> parser &
{
if (&self != &other) {
if (self.m_parser != nullptr)
::ucl_parser_free(self.m_parser);
self.m_parser = std::exchange(other.m_parser, nullptr);
self.m_macros = std::move(other.m_macros);
}
return self; // NOLINT
}
// Add a parser macro. Unlike ucl_parser_register_macro, this doesn't
// take a userdata parameter; it's assumed the user will use lambda
// capture or similar if needed.
template <std::invocable<std::string_view> F>
auto register_macro(this parser &self, std::string_view name, F &&func) -> void
requires(std::same_as<bool, std::invoke_result<F>>)
{
auto handler = std::make_unique<macro_handler>(std::forward<F>(func));
auto cname = std::string(name);
::ucl_parser_register_macro(self.get_parser(), cname.c_str(),
¯o_handler::handle, handler.get());
self.m_macros.emplace_back(std::move(handler));
}
// Add a parser variable.
auto
register_value(this parser &self, std::string_view variable, std::string_view value) -> void
{
::ucl_parser_register_variable(self.get_parser(), std::string(variable).c_str(),
std::string(value).c_str());
}
// Add data to the parser.
[[nodiscard]] auto add(this parser &self, std::ranges::contiguous_range auto &&data)
-> std::expected<void, error>
// Only bytes (chars) are permitted.
requires(sizeof(std::ranges::range_value_t<decltype(data)>) == 1)
{
auto *p = self.get_parser();
auto dptr = reinterpret_cast<unsigned char const *>(std::ranges::data(data));
auto ret = ::ucl_parser_add_chunk(p, dptr, std::ranges::size(data));
if (ret == true)
return {};
return std::unexpected(error(::ucl_parser_get_error(p)));
}
[[nodiscard]] auto
add(this parser &self, std::ranges::range auto &&data) -> std::expected<void, error>
requires(!std::ranges::contiguous_range<decltype(data)>)
{
auto cdata = std::vector<char>(std::from_range, std::forward<decltype(data)>(data));
co_await self.add(std::move(cdata));
co_return {};
}
// Return the top object of this parser.
[[nodiscard]] auto top(this parser &self) -> map<object>
{
auto *obj = ::ucl_parser_get_object(self.get_parser());
if (obj != nullptr)
// ucl_parser_get_object() refs the object for us.
return {noref, obj};
throw std::logic_error("attempt to call top() on an invalid ucl::parser");
}
// Return the stored parser object.
[[nodiscard]] auto get_parser(this parser &self) -> ::ucl_parser *
{
if (self.m_parser == nullptr)
throw std::logic_error("attempt to fetch a null ucl::parser");
return self.m_parser;
}
private:
// The parser object. Should never be null, unless we've been
// moved-from.
ucl_parser *m_parser;
// Functions added by register_macro. We have to store these as
// pointers because we pass the address to libucl.
std::vector<std::unique_ptr<macro_handler>> m_macros;
};
// Create a parser with the given flags.
export [[nodiscard]] auto make_parser(int flags = 0) -> std::expected<parser, error>
{
auto *p = ::ucl_parser_new(flags);
if (p != nullptr)
return {parser(p)};
// TODO: Is there a way to get the actual error here?
return std::unexpected(error("failed to create parser"));
}
// Utility function to parse something and return the top-level object.
export [[nodiscard]] auto
parse(int flags, std::ranges::range auto &&data) -> std::expected<map<object>, error>
{
auto p = co_await make_parser(flags);
co_await p.add(std::forward<decltype(data)>(data));
co_return p.top();
}
export [[nodiscard]] auto parse(std::ranges::range auto &&data) -> std::expected<map<object>, error>
{
co_return co_await parse(0, std::forward<decltype(data)>(data));
}
} // namespace nihil::ucl
|