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