Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : #include "enums.hpp"
10 : #include "common.hpp"
11 : #include <command.hpp>
12 : #include <kdbease.h>
13 : #include <kdbhelper.h>
14 :
15 10 : kainjow::mustache::list EnumProcessor::getValues (const std::string & prefix, const kdb::Key & key, std::string & fromStringSwitch,
16 : std::string & valuesString, size_t & trieDepth)
17 : {
18 : using namespace kainjow::mustache;
19 :
20 40 : if (!key.hasMeta ("check/enum"))
21 : {
22 : return {};
23 : }
24 :
25 20 : list values;
26 20 : std::stringstream ss;
27 20 : std::set<std::pair<std::string, std::string>> stringValues;
28 :
29 50 : const auto end = key.getMeta<std::string> ("check/enum");
30 10 : if (ckdb::elektraArrayValidateBaseNameString (end.c_str ()) < 0)
31 : {
32 0 : throw CommandAbortException ("The key '" + key.getName () + "' has invalid check/enum metadata: " + end);
33 : }
34 :
35 10 : kdb::long_long_t i = 0;
36 30 : std::string cur = "#0";
37 1102 : while (cur <= end)
38 : {
39 1092 : if (key.hasMeta ("check/enum/" + cur))
40 : {
41 76 : auto name = prefix + "_";
42 114 : const std::string & stringValue = key.getMeta<std::string> ("check/enum/" + cur);
43 114 : name += camelCaseToMacroCase (stringValue);
44 38 : escapeNonAlphaNum (name);
45 76 : auto value = std::to_string (i);
46 114 : if (key.hasMeta ("gen/enum/" + cur + "/value"))
47 : {
48 64 : value = key.getMeta<std::string> ("gen/enum/" + cur + "/value");
49 : }
50 190 : values.emplace_back (object{ { "name", name }, { "value", value }, { "string_value", stringValue } });
51 76 : stringValues.insert ({ stringValue, name });
52 114 : ss << name << "=" << value << "\n";
53 : }
54 546 : ++i;
55 1092 : auto indexString = std::to_string (i);
56 4368 : cur = "#" + std::string (indexString.length () - 1, '_') + std::to_string (i);
57 : }
58 :
59 20 : EnumTrie trie (stringValues);
60 10 : trieDepth = trie.getDepth ();
61 20 : fromStringSwitch = trie.createSwitch ();
62 :
63 20 : valuesString = ss.str ();
64 :
65 10 : return values;
66 : }
67 :
68 10 : std::string EnumProcessor::getType (const kdb::Key & key, const std::string & tagName, bool & genType)
69 : {
70 40 : genType = key.hasMeta ("gen/enum/type");
71 22 : return genType ? key.getMeta<std::string> ("gen/enum/type") : snakeCaseToPascalCase (tagName);
72 : }
73 :
74 10 : bool EnumProcessor::shouldGenerateTypeDef (const kdb::Key & key)
75 : {
76 48 : return !key.hasMeta ("gen/enum/create") || key.getMeta<std::string> ("gen/enum/create") == "1";
77 : }
78 :
79 10 : kainjow::mustache::object EnumProcessor::process (const kdb::Key & key, const std::string & tagName)
80 : {
81 : using namespace kainjow::mustache;
82 :
83 20 : auto name = key.getName ();
84 10 : name.erase (0, sizeof ("spec") - 1);
85 :
86 : bool genType;
87 20 : auto enumType = getType (key, tagName, genType);
88 20 : auto typeName = "Enum" + enumType;
89 :
90 24 : auto nativeType = genType ? enumType : "Elektra" + typeName;
91 20 : std::string fromStringSwitch;
92 :
93 20 : std::string valuesString;
94 : size_t trieDepth;
95 30 : auto values = getValues (camelCaseToMacroCase (nativeType), key, fromStringSwitch, valuesString, trieDepth);
96 :
97 10 : auto isNew = true;
98 10 : auto generateTypeDef = shouldGenerateTypeDef (key);
99 :
100 20 : auto other = enumTypes.find (typeName);
101 20 : if (other != enumTypes.end ())
102 : {
103 6 : auto otherValuesString = other->second.second;
104 2 : if (otherValuesString != valuesString)
105 : {
106 0 : auto otherKey = other->second.first;
107 0 : auto msg = "The key '" + name;
108 0 : msg += "' uses the same 'gen/enum/type' as the key '" + otherKey +
109 0 : "', but their 'check/enum' values are different!";
110 0 : throw CommandAbortException (msg);
111 : }
112 :
113 2 : isNew = false;
114 : }
115 :
116 30 : enumTypes[typeName] = std::make_pair (name, valuesString);
117 :
118 10 : auto useTrie = conversion == EnumConversion::Trie || (conversion == EnumConversion::Auto && trieDepth < 3);
119 :
120 : return object{ { "new", isNew },
121 : { "name", name },
122 20 : { "tag_name", snakeCaseToMacroCase (tagName) },
123 : { "type_name", typeName },
124 : { "native_type", nativeType },
125 : { "generate_typedef?", generateTypeDef },
126 : { "values", values },
127 : { "switch_from_string?", useTrie },
128 80 : { "from_string_code", fromStringSwitch } };
129 : }
130 :
131 40 : EnumTrie::EnumTrie (const std::set<std::pair<std::string, std::string>> & values)
132 : {
133 84 : for (auto v = values.begin (); v != values.end ();)
134 : {
135 64 : char c = v->first.at (0);
136 64 : std::string p;
137 32 : if (c != '\0')
138 : {
139 32 : p += c;
140 : }
141 32 : std::set<std::pair<std::string, std::string>> vals;
142 :
143 260 : while (v != values.end () && v->first.at (0) == c)
144 : {
145 76 : vals.insert (*v);
146 : ++v;
147 : }
148 32 : insert (p, vals);
149 : }
150 10 : }
151 :
152 48 : void EnumTrie::insert (const std::string & prefix, const std::set<std::pair<std::string, std::string>> & values)
153 : {
154 144 : std::unique_ptr<EnumTrie> child (new EnumTrie ());
155 48 : if (values.size () == 1)
156 : {
157 144 : child->stringValue = values.begin ()->first;
158 108 : child->name = values.begin ()->second;
159 : }
160 : else
161 : {
162 42 : for (auto v = values.begin (); v != values.end ();)
163 : {
164 18 : if (v->first.size () == prefix.size ())
165 : {
166 8 : child->stringValue = values.begin ()->first;
167 8 : child->name = values.begin ()->second;
168 2 : ++v;
169 2 : continue;
170 : }
171 :
172 48 : char c = v->first.at (prefix.size ());
173 32 : std::string p = prefix;
174 32 : p += c;
175 16 : std::set<std::pair<std::string, std::string>> vals;
176 :
177 164 : while (v != values.end () && (v->first.size () > prefix.size () ? v->first.at (prefix.size ()) : '\0') == c)
178 : {
179 48 : vals.insert (*v);
180 : ++v;
181 : }
182 16 : child->insert (p, vals);
183 : }
184 : }
185 :
186 96 : children[prefix.back ()] = std::move (child);
187 48 : }
188 :
189 10 : std::string EnumTrie::createSwitch ()
190 : {
191 20 : std::stringstream ss;
192 10 : createSwitch (ss, 0);
193 20 : return ss.str ();
194 : }
195 :
196 58 : bool EnumTrie::createSwitch (std::stringstream & ss, size_t index)
197 : {
198 116 : if (children.empty ())
199 : {
200 72 : if (stringValue.empty ())
201 : {
202 : return false;
203 : }
204 :
205 180 : ss << "*variable = " << name << ";" << std::endl;
206 108 : ss << "return 1;" << std::endl;
207 36 : return false;
208 : }
209 :
210 110 : ss << "switch (string[" << index << "])" << std::endl;
211 66 : ss << "{" << std::endl;
212 114 : for (auto & child : children)
213 : {
214 240 : ss << "case '" << child.first << "':" << std::endl;
215 96 : if (child.second->createSwitch (ss, index + 1))
216 : {
217 20 : ss << "break;" << std::endl;
218 : }
219 : }
220 66 : ss << "}" << std::endl;
221 :
222 44 : if (!stringValue.empty ())
223 : {
224 10 : ss << "*variable = " << name << ";" << std::endl;
225 6 : ss << "return 1;" << std::endl;
226 2 : return false;
227 : }
228 : return true;
229 : }
230 :
231 58 : size_t EnumTrie::getDepth ()
232 : {
233 116 : if (children.empty ())
234 : {
235 : return 1;
236 : }
237 :
238 22 : std::vector<size_t> childDepths;
239 : std::transform (children.begin (), children.end (), std::back_inserter (childDepths),
240 162 : [](const std::pair<const char, std::unique_ptr<EnumTrie>> & p) { return p.second->getDepth (); });
241 :
242 66 : return *std::max_element (childDepths.begin (), childDepths.end ()) + 1;
243 7164 : }
|