1 // This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
2 // the main distribution directory for license terms and copyright or visit
3 // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
4
5 #pragma once
6
7 #include <ctype.h>
8
9 #include <stack>
10
11 #include "caf/config.hpp"
12 #include "caf/detail/parser/chars.hpp"
13 #include "caf/detail/parser/read_bool.hpp"
14 #include "caf/detail/parser/read_number_or_timespan.hpp"
15 #include "caf/detail/parser/read_string.hpp"
16 #include "caf/detail/parser/read_uri.hpp"
17 #include "caf/detail/scope_guard.hpp"
18 #include "caf/pec.hpp"
19 #include "caf/uri_builder.hpp"
20
21 CAF_PUSH_UNUSED_LABEL_WARNING
22
23 #include "caf/detail/parser/fsm.hpp"
24
25 namespace caf::detail::parser {
26
27 // Example input:
28 //
29 // section1 {
30 // value1 = 123
31 // value2 = "string"
32 // subsection1 = {
33 // value3 = 1.23
34 // value4 = 4e20
35 // }
36 // }
37 // section2 {
38 // value5 = 'atom'
39 // value6 = [1, 'two', "three", {
40 // a = "b",
41 // b = "c",
42 // }]
43 // }
44 //
45
46 template <class State, class Consumer>
read_config_comment(State & ps,Consumer &&)47 void read_config_comment(State& ps, Consumer&&) {
48 // clang-format off
49 start();
50 term_state(init) {
51 transition(done, '\n')
52 transition(init)
53 }
54 term_state(done) {
55 // nop
56 }
57 fin();
58 // clang-format on
59 }
60
61 template <class State, class Consumer, class InsideList = std::false_type>
62 void read_config_value(State& ps, Consumer&& consumer,
63 InsideList inside_list = {});
64
65 template <class State, class Consumer>
read_config_list(State & ps,Consumer && consumer)66 void read_config_list(State& ps, Consumer&& consumer) {
67 // clang-format off
68 start();
69 state(init) {
70 epsilon(before_value)
71 }
72 state(before_value) {
73 transition(before_value, " \t\n")
74 transition(done, ']', consumer.end_list())
75 fsm_epsilon(read_config_comment(ps, consumer), before_value, '#')
76 fsm_epsilon(read_config_value(ps, consumer, std::true_type{}), after_value)
77 }
78 state(after_value) {
79 transition(after_value, " \t\n")
80 transition(before_value, ',')
81 transition(done, ']', consumer.end_list())
82 fsm_epsilon(read_config_comment(ps, consumer), after_value, '#')
83 }
84 term_state(done) {
85 // nop
86 }
87 fin();
88 // clang-format on
89 }
90
91 // Like read_config_list, but without surrounding '[]'.
92 template <class State, class Consumer>
lift_config_list(State & ps,Consumer && consumer)93 void lift_config_list(State& ps, Consumer&& consumer) {
94 // clang-format off
95 start();
96 state(init) {
97 epsilon(before_value)
98 }
99 term_state(before_value) {
100 transition(before_value, " \t\n")
101 fsm_epsilon(read_config_comment(ps, consumer), before_value, '#')
102 fsm_epsilon(read_config_value(ps, consumer, std::true_type{}), after_value)
103 }
104 term_state(after_value) {
105 transition(after_value, " \t\n")
106 transition(before_value, ',')
107 fsm_epsilon(read_config_comment(ps, consumer), after_value, '#')
108 }
109 fin();
110 // clang-format on
111 }
112
113 template <bool Nested = true, class State, class Consumer>
read_config_map(State & ps,Consumer && consumer)114 void read_config_map(State& ps, Consumer&& consumer) {
115 std::string key;
116 auto alnum_or_dash = [](char x) {
117 return isalnum(x) || x == '-' || x == '_';
118 };
119 auto set_key = [&consumer, &key] {
120 std::string tmp;
121 tmp.swap(key);
122 consumer.key(std::move(tmp));
123 };
124 auto recurse = [&consumer, &set_key]() -> decltype(auto) {
125 set_key();
126 return consumer.begin_map();
127 };
128 // clang-format off
129 start();
130 term_state(init) {
131 epsilon(await_key_name)
132 }
133 state(await_key_name) {
134 transition(await_key_name, " \t\n")
135 fsm_epsilon(read_config_comment(ps, consumer), await_key_name, '#')
136 fsm_epsilon(read_string(ps, key), await_assignment, quote_marks)
137 transition(read_key_name, alnum_or_dash, key = ch)
138 transition_if(Nested, done, '}', consumer.end_map())
139 }
140 // Reads a key of a "key=value" line.
141 state(read_key_name) {
142 transition(read_key_name, alnum_or_dash, key += ch)
143 fsm_transition(read_config_map(ps, recurse()), done, '.')
144 epsilon(await_assignment)
145 }
146 // Reads the assignment operator in a "key=value" line.
147 state(await_assignment) {
148 transition(await_assignment, " \t")
149 transition(await_value, "=:", set_key())
150 epsilon(await_value, '{', set_key())
151 }
152 // Reads the value in a "key=value" line.
153 state(await_value) {
154 transition(await_value, " \t")
155 fsm_epsilon(read_config_value(ps, consumer), after_value)
156 }
157 // Waits for end-of-line after reading a value
158 unstable_state(after_value) {
159 transition(after_value, " \t")
160 transition(had_newline, "\n")
161 transition_if(!Nested, after_comma, ',')
162 transition(await_key_name, ',')
163 transition_if(Nested, done, '}', consumer.end_map())
164 fsm_epsilon(read_config_comment(ps, consumer), had_newline, '#')
165 epsilon_if(!Nested, done)
166 epsilon(unexpected_end_of_input)
167 }
168 // Allows users to skip the ',' for separating key/value pairs
169 unstable_state(had_newline) {
170 transition(had_newline, " \t\n")
171 transition(await_key_name, ',')
172 transition_if(Nested, done, '}', consumer.end_map())
173 fsm_epsilon(read_config_comment(ps, consumer), had_newline, '#')
174 fsm_epsilon(read_string(ps, key), await_assignment, quote_marks)
175 epsilon(read_key_name, alnum_or_dash)
176 epsilon_if(!Nested, done)
177 epsilon(unexpected_end_of_input)
178 }
179 term_state(after_comma) {
180 epsilon(await_key_name)
181 }
182 state(unexpected_end_of_input) {
183 // no transitions, only needed for the unstable states
184 }
185 term_state(done) {
186 //nop
187 }
188 fin();
189 // clang-format on
190 }
191
192 template <class State, class Consumer>
read_config_uri(State & ps,Consumer && consumer)193 void read_config_uri(State& ps, Consumer&& consumer) {
194 uri_builder builder;
195 auto g = make_scope_guard([&] {
196 if (ps.code <= pec::trailing_character)
197 consumer.value(builder.make());
198 });
199 // clang-format off
200 start();
201 state(init) {
202 transition(init, " \t\n")
203 transition(before_uri, '<')
204 }
205 state(before_uri) {
206 transition(before_uri, " \t\n")
207 fsm_epsilon(read_uri(ps, builder), after_uri)
208 }
209 state(after_uri) {
210 transition(after_uri, " \t\n")
211 transition(done, '>')
212 }
213 term_state(done) {
214 // nop
215 }
216 fin();
217 // clang-format on
218 }
219
220 template <class State, class Consumer, class InsideList>
read_config_value(State & ps,Consumer && consumer,InsideList inside_list)221 void read_config_value(State& ps, Consumer&& consumer, InsideList inside_list) {
222 // clang-format off
223 start();
224 state(init) {
225 fsm_epsilon(read_string(ps, consumer), done, quote_marks)
226 fsm_epsilon(read_number(ps, consumer), done, '.')
227 fsm_epsilon(read_bool(ps, consumer), done, "ft")
228 fsm_epsilon(read_number_or_timespan(ps, consumer, inside_list),
229 done, "0123456789+-")
230 fsm_epsilon(read_config_uri(ps, consumer), done, '<')
231 fsm_transition(read_config_list(ps, consumer.begin_list()), done, '[')
232 fsm_transition(read_config_map(ps, consumer.begin_map()), done, '{')
233 }
234 term_state(done) {
235 // nop
236 }
237 fin();
238 // clang-format on
239 }
240
241 template <class State, class Consumer>
read_config(State & ps,Consumer && consumer)242 void read_config(State& ps, Consumer&& consumer) {
243 auto key_char = [](char x) {
244 return isalnum(x) || x == '-' || x == '_' || x == '"';
245 };
246 // clang-format off
247 start();
248 // Checks whether there's a top-level '{'.
249 term_state(init) {
250 transition(init, " \t\n")
251 fsm_epsilon(read_config_comment(ps, consumer), init, '#')
252 fsm_transition(read_config_map<false>(ps, consumer),
253 await_closing_brace, '{')
254 fsm_epsilon(read_config_map<false>(ps, consumer), init, key_char)
255 }
256 state(await_closing_brace) {
257 transition(await_closing_brace, " \t\n")
258 fsm_epsilon(read_config_comment(ps, consumer), await_closing_brace, '#')
259 transition(done, '}')
260 }
261 term_state(done) {
262 transition(done, " \t\n")
263 fsm_epsilon(read_config_comment(ps, consumer), done, '#')
264 }
265 fin();
266 // clang-format on
267 }
268
269 } // namespace caf::detail::parser
270
271 #include "caf/detail/parser/fsm_undef.hpp"
272
273 CAF_POP_WARNINGS
274