Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief A listener reacting to matches for the grammar rules defined in `YAML.g4`
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : // -- Imports ------------------------------------------------------------------
10 :
11 : #include <kdbmacros.h>
12 :
13 : #include "listener.hpp"
14 :
15 : using std::string;
16 :
17 : // -- Functions ----------------------------------------------------------------
18 :
19 : namespace
20 : {
21 :
22 : /**
23 : * @brief This function converts a given number to an array base name.
24 : *
25 : * @param index This number specifies the index of the array entry.
26 : *
27 : * @return A string representing the given indices as Elektra array name.
28 : */
29 25 : string indexToArrayBaseName (uintmax_t const index)
30 : {
31 : using std::to_string;
32 :
33 25 : size_t digits = 1;
34 :
35 25 : for (uintmax_t value = index; value > 9; digits++)
36 : {
37 0 : value /= 10;
38 : }
39 :
40 175 : return "#" + string (digits - 1, '_') + to_string (index);
41 : }
42 :
43 : /**
44 : * @brief This function converts a YAML scalar to a string.
45 : *
46 : * @param text This string contains a YAML scalar (including quote
47 : * characters).
48 : *
49 : * @return A string without leading and trailing quote characters
50 : */
51 155 : string scalarToText (string const & text)
52 : {
53 155 : if (text.length () == 0)
54 : {
55 : return text;
56 : }
57 408 : if (*(text.begin ()) == '"' || *(text.begin ()) == '\'')
58 : {
59 57 : return text.substr (1, text.length () - 2);
60 : }
61 : return text;
62 : }
63 :
64 : } // namespace
65 :
66 : // -- Class --------------------------------------------------------------------
67 :
68 : namespace yanlr
69 : {
70 :
71 : using kdb::Key;
72 : using kdb::KeySet;
73 :
74 : using ElementContext = yanlr::YAML::ElementContext;
75 : using EmptyContext = yanlr::YAML::EmptyContext;
76 : using PairContext = YAML::PairContext;
77 : using ValueContext = YAML::ValueContext;
78 : using SequenceContext = yanlr::YAML::SequenceContext;
79 :
80 : /**
81 : * @brief This constructor creates a new empty key storage using the given
82 : * parent key.
83 : *
84 : * @param parent This key specifies the parent of all keys stored in the
85 : * object.
86 : */
87 248 : KeyListener::KeyListener (Key parent) : keys{}
88 : {
89 124 : parents.push (parent.dup ());
90 31 : }
91 :
92 : /**
93 : * @brief This function returns the data read by the parser.
94 : *
95 : * @return The key set representing the data from the textual input
96 : */
97 28 : KeySet KeyListener::keySet ()
98 : {
99 56 : return keys;
100 : }
101 :
102 : /**
103 : * @brief This function will be called when the listener enters an empty file (that might contain comments).
104 : *
105 : * @param context The context specifies data matched by the rule.
106 : */
107 0 : void KeyListener::enterEmpty (EmptyContext * context ELEKTRA_UNUSED)
108 : {
109 : // We add a parent key that stores nothing representing an empty file.
110 0 : keys.append (Key{ parents.top ().getName (), KEY_BINARY, KEY_END });
111 0 : }
112 :
113 : /**
114 : * @brief This function will be called after the parser exits a value.
115 : *
116 : * @param context The context specifies data matched by the rule.
117 : */
118 79 : void KeyListener::exitValue (ValueContext * context)
119 : {
120 316 : Key key = parents.top ();
121 158 : string value = context->getText ();
122 156 : if (value == "true" || value == "false")
123 : {
124 2 : key.set<bool> (value == "true");
125 : }
126 : else
127 : {
128 231 : key.setString (scalarToText (value));
129 : }
130 158 : keys.append (key);
131 79 : }
132 :
133 : /**
134 : * @brief This function will be called after the parser enters a key-value pair.
135 : *
136 : * @param context The context specifies data matched by the rule.
137 : */
138 78 : void KeyListener::enterPair (PairContext * context)
139 : {
140 : // Entering a mapping such as `part: …` means that we need to add `part` to
141 : // the key name
142 312 : Key child{ parents.top ().getName (), KEY_END };
143 234 : child.addBaseName (scalarToText (context->key ()->getText ()));
144 156 : parents.push (child);
145 78 : if (!context->child ())
146 : {
147 : // Add key with empty value
148 : // The parser does not visit `exitValue` in that case
149 3 : child.setBinary (NULL, 0);
150 3 : keys.append (child);
151 : }
152 78 : }
153 :
154 : /**
155 : * @brief This function will be called after the parser exits a key-value pair.
156 : *
157 : * @param context The context specifies data matched by the rule.
158 : */
159 78 : void KeyListener::exitPair (PairContext * context ELEKTRA_UNUSED)
160 : {
161 : // Returning from a mapping such as `part: …` means that we need need to
162 : // remove the key for `part` from the stack.
163 156 : parents.pop ();
164 78 : }
165 :
166 : /**
167 : * @brief This function will be called after the parser enters a sequence.
168 : *
169 : * @param context The context specifies data matched by the rule.
170 : */
171 10 : void KeyListener::enterSequence (SequenceContext * context ELEKTRA_UNUSED)
172 : {
173 20 : indices.push (0);
174 50 : parents.top ().setMeta ("array", ""); // We start with an empty array
175 10 : }
176 :
177 : /**
178 : * @brief This function will be called after the parser exits a sequence.
179 : *
180 : * @param context The context specifies data matched by the rule.
181 : */
182 10 : void KeyListener::exitSequence (SequenceContext * context ELEKTRA_UNUSED)
183 : {
184 : // We add the parent key of all array elements after we leave the sequence
185 30 : keys.append (parents.top ());
186 20 : indices.pop ();
187 10 : }
188 :
189 : /**
190 : * @brief This function will be called after the parser recognizes an element
191 : * of a sequence.
192 : *
193 : * @param context The context specifies data matched by the rule.
194 : */
195 25 : void KeyListener::enterElement (ElementContext * context ELEKTRA_UNUSED)
196 : {
197 :
198 100 : Key key{ parents.top ().getName (), KEY_END };
199 75 : key.addBaseName (indexToArrayBaseName (indices.top ()));
200 :
201 50 : uintmax_t index = indices.top ();
202 50 : indices.pop ();
203 25 : if (index < UINTMAX_MAX)
204 : {
205 25 : index++;
206 : }
207 50 : indices.push (index);
208 :
209 150 : parents.top ().setMeta ("array", key.getBaseName ());
210 50 : parents.push (key);
211 25 : }
212 :
213 : /**
214 : * @brief This function will be called after the parser read an element of a
215 : * sequence.
216 : *
217 : * @param context The context specifies data matched by the rule.
218 : */
219 25 : void KeyListener::exitElement (ElementContext * context ELEKTRA_UNUSED)
220 : {
221 50 : parents.pop (); // Remove the key for the current array entry
222 25 : }
223 140 : }
|