Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : #include <EnergyPlus/FromChars.hh>
49 : #include <EnergyPlus/InputProcessing/IdfParser.hh>
50 : #include <cmath>
51 : #include <fast_float/fast_float.h>
52 : #include <fmt/format.h>
53 : #include <milo/dtoa.h>
54 : #include <milo/itoa.h>
55 :
56 : using json = nlohmann::json;
57 :
58 985574 : auto const icompare = [](std::string_view a, std::string_view b) { // (AUTO_OK)
59 985574 : return (a.length() == b.length()
60 1495560 : ? std::equal(a.begin(), a.end(), b.begin(), [](char const c, char const d) { return (::tolower(c) == ::tolower(d)); })
61 985574 : : false);
62 : };
63 :
64 0 : json IdfParser::decode(std::string_view idf, json const &schema)
65 : {
66 0 : bool success = true;
67 0 : return decode(idf, idf.size(), schema, success);
68 : }
69 :
70 1365 : json IdfParser::decode(std::string_view idf, json const &schema, bool &success)
71 : {
72 1365 : return decode(idf, idf.size(), schema, success);
73 : }
74 :
75 0 : json IdfParser::decode(std::string_view idf, size_t _idf_size, json const &schema)
76 : {
77 0 : bool success = true;
78 0 : return decode(idf, _idf_size, schema, success);
79 : }
80 :
81 1365 : json IdfParser::decode(std::string_view idf, size_t _idf_size, json const &schema, bool &success)
82 : {
83 1365 : success = true;
84 1365 : cur_line_num = 1;
85 1365 : index_into_cur_line = 0;
86 1365 : beginning_of_line_index = 0;
87 1365 : idf_size = _idf_size;
88 :
89 1365 : if (idf.empty()) {
90 0 : success = false;
91 0 : return nullptr;
92 : }
93 :
94 1365 : size_t index = 0;
95 1365 : return parse_idf(idf, index, success, schema);
96 : }
97 :
98 5 : std::string IdfParser::encode(json const &root, json const &schema)
99 : {
100 : static constexpr std::string_view end_of_field(",\n ", 4);
101 : static constexpr std::string_view end_of_object(";\n\n", 3);
102 :
103 5 : std::string encoded, extension_key;
104 5 : if (idf_size > 0) {
105 5 : encoded.reserve(idf_size);
106 : } else {
107 0 : encoded.reserve(root.size() * 1024);
108 : }
109 :
110 29 : for (auto obj = root.begin(); obj != root.end(); ++obj) {
111 24 : const auto &legacy_idd = schema["properties"][obj.key()]["legacy_idd"];
112 24 : const auto &legacy_idd_field = legacy_idd["fields"];
113 24 : auto key = legacy_idd.find("extension");
114 24 : if (key != legacy_idd.end()) {
115 2 : extension_key = key.value().get<std::string>();
116 : }
117 48 : for (auto obj_in = obj.value().begin(); obj_in != obj.value().end(); ++obj_in) {
118 24 : encoded += obj.key();
119 24 : size_t skipped_fields = 0;
120 134 : for (size_t i = 0; i < legacy_idd_field.size(); i++) {
121 110 : std::string const &entry = legacy_idd_field[i].get<std::string>();
122 110 : if (obj_in.value().find(entry) == obj_in.value().end()) {
123 16 : if (entry == "name") {
124 27 : encoded += std::string{end_of_field} + obj_in.key();
125 : } else {
126 7 : skipped_fields++;
127 : }
128 16 : continue;
129 : }
130 100 : for (size_t j = 0; j < skipped_fields; j++) {
131 6 : encoded += end_of_field;
132 : }
133 94 : skipped_fields = 0;
134 94 : encoded += end_of_field;
135 94 : auto const &val = obj_in.value()[entry];
136 94 : if (val.is_string()) {
137 52 : encoded += val.get<std::string>();
138 42 : } else if (val.is_number_integer()) {
139 21 : encoded += std::to_string(val.get<int>());
140 : } else {
141 21 : dtoa(val.get<double>(), s);
142 21 : encoded += s;
143 : }
144 110 : }
145 :
146 24 : if (obj_in.value().find(extension_key) == obj_in.value().end()) {
147 22 : encoded += end_of_object;
148 22 : continue;
149 : }
150 :
151 2 : auto &extensions = obj_in.value()[extension_key];
152 24 : for (size_t extension_i = 0; extension_i < extensions.size(); extension_i++) {
153 22 : auto const &cur_extension_obj = extensions[extension_i];
154 22 : auto const &extensible = schema["properties"][obj.key()]["legacy_idd"]["extensibles"];
155 52 : for (size_t i = 0; i < extensible.size(); i++) {
156 30 : std::string const &tmp = extensible[i].get<std::string>();
157 30 : if (cur_extension_obj.find(tmp) == cur_extension_obj.end()) {
158 1 : skipped_fields++;
159 1 : continue;
160 : }
161 30 : for (size_t j = 0; j < skipped_fields; j++) {
162 1 : encoded += end_of_field;
163 : }
164 29 : skipped_fields = 0;
165 29 : encoded += end_of_field;
166 29 : if (cur_extension_obj[tmp].is_string()) {
167 17 : encoded += cur_extension_obj[tmp].get<std::string>();
168 : } else {
169 12 : dtoa(cur_extension_obj[tmp].get<double>(), s);
170 12 : encoded += s;
171 : }
172 30 : }
173 : }
174 2 : encoded += end_of_object;
175 24 : }
176 29 : }
177 10 : return encoded;
178 5 : }
179 :
180 30831 : std::string IdfParser::normalizeObjectType(std::string const &objectType)
181 : {
182 30831 : if (objectType.empty()) {
183 0 : return std::string{};
184 : }
185 30831 : std::string key = convertToUpper(objectType);
186 30831 : auto tmp_umit = objectTypeMap.find(key);
187 30831 : if (tmp_umit != objectTypeMap.end()) {
188 30830 : return tmp_umit->second;
189 : }
190 1 : return std::string{};
191 30831 : }
192 :
193 1365 : std::vector<std::string> const &IdfParser::errors()
194 : {
195 1365 : return errors_;
196 : }
197 :
198 1365 : std::vector<std::string> const &IdfParser::warnings()
199 : {
200 1365 : return warnings_;
201 : }
202 :
203 1341 : bool IdfParser::hasErrors()
204 : {
205 1341 : return !errors_.empty();
206 : }
207 :
208 1365 : json IdfParser::parse_idf(std::string_view idf, size_t &index, bool &success, json const &schema)
209 : {
210 1365 : json root;
211 : Token token;
212 1365 : auto const &schema_properties = schema["properties"];
213 :
214 1365 : objectTypeMap.reserve(schema_properties.size());
215 1160250 : for (auto it = schema_properties.begin(); it != schema_properties.end(); ++it) {
216 1158885 : std::string key = convertToUpper(it.key());
217 1158885 : objectTypeMap.emplace(std::move(key), it.key());
218 1160250 : }
219 :
220 1365 : if (idf_size > 3) {
221 : // UTF-8 Byte Order Mark
222 1365 : if (idf[0] == '\xEF' && idf[1] == '\xBB' && idf[2] == '\xBF') {
223 1 : index += 3;
224 1 : index_into_cur_line += 3;
225 : }
226 : }
227 :
228 1365 : int idfObjectCount = 0;
229 : while (true) {
230 80251 : token = look_ahead(idf, index);
231 80251 : if (token == Token::END) {
232 1365 : break;
233 78886 : } else if (token == Token::NONE) {
234 0 : success = false;
235 0 : return root;
236 78886 : } else if (token == Token::SEMICOLON) {
237 0 : next_token(idf, index);
238 0 : continue;
239 78886 : } else if (token == Token::COMMA) {
240 0 : errors_.emplace_back(fmt::format("Line: {} Index: {} - Extraneous comma found.", cur_line_num, index_into_cur_line));
241 0 : success = false;
242 0 : return root;
243 78886 : } else if (token == Token::EXCLAMATION) {
244 48055 : eat_comment(idf, index);
245 : } else {
246 30831 : ++idfObjectCount;
247 30831 : std::string const parsed_obj_name = parse_string(idf, index);
248 30831 : std::string const obj_name = normalizeObjectType(parsed_obj_name);
249 30831 : if (obj_name.empty()) {
250 2 : errors_.emplace_back(
251 2 : fmt::format("Line: {} Index: {} - \"{}\" is not a valid Object Type.", cur_line_num, index_into_cur_line, parsed_obj_name));
252 4 : while (token != Token::SEMICOLON && token != Token::END) {
253 3 : token = next_token(idf, index);
254 : }
255 1 : continue;
256 : }
257 :
258 30830 : bool object_success = true;
259 30830 : json const &obj_loc = schema_properties[obj_name];
260 30830 : json const &legacy_idd = obj_loc["legacy_idd"];
261 30830 : json obj = parse_object(idf, index, object_success, legacy_idd, obj_loc, idfObjectCount);
262 30830 : if (!object_success) {
263 0 : auto found_index = idf.find_first_of('\n', beginning_of_line_index);
264 0 : std::string line;
265 0 : if (found_index != std::string::npos) {
266 0 : line = idf.substr(beginning_of_line_index, found_index - beginning_of_line_index - 1);
267 : }
268 0 : errors_.emplace_back(
269 0 : fmt::format("Line: {} Index: {} - Error parsing \"{}\". Error in following line.", cur_line_num, index_into_cur_line, obj_name));
270 0 : errors_.emplace_back(fmt::format("~~~ {}", line));
271 0 : success = false;
272 0 : continue;
273 0 : }
274 30830 : u64toa(root[obj_name].size() + 1, s);
275 30830 : std::string name = fmt::format("{} {}", obj_name, s);
276 :
277 30830 : if (!obj.is_null()) {
278 30830 : auto const name_iter = obj.find("name");
279 : // If you find a name field, use that
280 30830 : if (name_iter != obj.end()) {
281 24102 : name = name_iter.value().get<std::string>();
282 24102 : obj.erase(name_iter);
283 : } else {
284 : // Otherwise, see if it should have a name field
285 6728 : auto const it = obj_loc.find("name");
286 6728 : if (it != obj_loc.end()) {
287 : // Let it slide, as a blank string, to be handled in the appropriate GetInput routine
288 4 : name = "";
289 : }
290 6728 : }
291 30830 : }
292 :
293 30830 : if (root[obj_name].find(name) != root[obj_name].end()) {
294 1 : errors_.emplace_back(
295 1 : fmt::format(R"(Duplicate name found for object of type "{}" named "{}". Overwriting existing object.)", obj_name, name));
296 : }
297 :
298 30830 : root[obj_name][name] = std::move(obj);
299 30832 : }
300 78886 : }
301 :
302 1365 : return root;
303 0 : }
304 :
305 30830 : json IdfParser::parse_object(
306 : std::string_view idf, size_t &index, bool &success, json const &legacy_idd, json const &schema_obj_loc, int idfObjectCount)
307 : {
308 30830 : json root = json::object();
309 30830 : json extensible = json::object();
310 30830 : json array_of_extensions = json::array();
311 : Token token;
312 30830 : std::string extension_key;
313 30830 : size_t legacy_idd_index = 0;
314 30830 : size_t extensible_index = 0;
315 30830 : success = true;
316 30830 : bool was_value_parsed = false;
317 30830 : auto const &legacy_idd_fields_array = legacy_idd["fields"];
318 30830 : auto const legacy_idd_extensibles_iter = legacy_idd.find("extensibles");
319 :
320 30830 : auto const &schema_patternProperties = schema_obj_loc["patternProperties"];
321 30830 : std::string patternProperty;
322 61660 : int dot_star_present = schema_patternProperties.count(".*");
323 30830 : int no_whitespace_present = schema_patternProperties.count(R"(^.*\S.*$)");
324 30830 : if (dot_star_present) {
325 7192 : patternProperty = ".*";
326 23638 : } else if (no_whitespace_present) {
327 23638 : patternProperty = R"(^.*\S.*$)";
328 : } else {
329 0 : throw std::runtime_error(R"(The patternProperties value is not a valid choice (".*", "^.*\S.*$"))");
330 : }
331 30830 : auto const &schema_obj_props = schema_patternProperties[patternProperty]["properties"];
332 30830 : auto key = legacy_idd.find("extension");
333 :
334 30830 : json const *schema_obj_extensions = nullptr;
335 30830 : if (legacy_idd_extensibles_iter != legacy_idd.end()) {
336 8217 : if (key == legacy_idd.end()) {
337 0 : errors_.emplace_back("\"extension\" key not found in schema. Need to add to list in modify_schema.py.");
338 0 : success = false;
339 0 : return root;
340 : }
341 8217 : extension_key = key.value().get<std::string>();
342 8217 : schema_obj_extensions = &schema_obj_props[extension_key]["items"]["properties"];
343 : }
344 :
345 30830 : root["idf_order"] = idfObjectCount;
346 :
347 30830 : auto const found_min_fields = schema_obj_loc.find("min_fields");
348 :
349 30830 : index += 1;
350 :
351 : while (true) {
352 2876055 : token = look_ahead(idf, index);
353 2876055 : root["idf_max_fields"] = legacy_idd_index;
354 2876055 : root["idf_max_extensible_fields"] = extensible_index;
355 2876055 : if (token == Token::NONE) {
356 0 : success = false;
357 0 : return root;
358 2876055 : } else if (token == Token::END) {
359 3 : return root;
360 2876052 : } else if (token == Token::COMMA || token == Token::SEMICOLON) {
361 1105754 : if (!was_value_parsed) {
362 31917 : int ext_size = 0;
363 31917 : if (legacy_idd_index < legacy_idd_fields_array.size()) {
364 : // std::string_view field_name = legacy_idd_fields_array[ legacy_idd_index ];
365 : // root[ field_name ] = "";
366 : } else {
367 1314 : auto const &legacy_idd_extensibles_array = legacy_idd_extensibles_iter.value();
368 1314 : ext_size = static_cast<int>(legacy_idd_extensibles_array.size());
369 : // std::string_view field_name = legacy_idd_extensibles_array[ extensible_index % ext_size ];
370 1314 : extensible_index++;
371 : // extensible[ field_name ] = "";
372 : }
373 31917 : if (ext_size && extensible_index % ext_size == 0) {
374 325 : array_of_extensions.push_back(extensible);
375 325 : extensible.clear();
376 : }
377 : }
378 1105754 : legacy_idd_index++;
379 1105754 : was_value_parsed = false;
380 1105754 : next_token(idf, index);
381 1105754 : if (token == Token::SEMICOLON) {
382 30827 : size_t min_fields = 0;
383 30827 : if (found_min_fields != schema_obj_loc.end()) {
384 13779 : min_fields = found_min_fields.value().get<size_t>();
385 : }
386 33893 : for (; legacy_idd_index < min_fields; legacy_idd_index++) {
387 : // std::string_view field_name = legacy_idd_fields_array[ legacy_idd_index ];
388 : // root[ field_name ] = "";
389 : }
390 30827 : if (!extensible.empty()) {
391 179 : array_of_extensions.push_back(extensible);
392 179 : extensible.clear();
393 : }
394 30827 : root["idf_max_fields"] = legacy_idd_index;
395 30827 : root["idf_max_extensible_fields"] = extensible_index;
396 30827 : break;
397 : }
398 2845225 : } else if (token == Token::EXCLAMATION) {
399 696460 : eat_comment(idf, index);
400 1073838 : } else if (legacy_idd_index >= legacy_idd_fields_array.size()) {
401 680584 : if (legacy_idd_extensibles_iter == legacy_idd.end()) {
402 0 : errors_.emplace_back(
403 0 : fmt::format("Line: {} Index: {} - Object contains more field values than maximum number of IDD fields and is not extensible.",
404 0 : cur_line_num,
405 0 : index_into_cur_line));
406 0 : success = false;
407 0 : return root;
408 : }
409 680584 : if (schema_obj_extensions == nullptr) {
410 0 : errors_.emplace_back(fmt::format("Line: {} Index: {} - Object does not have extensible fields but should. Likely a parsing error.",
411 0 : cur_line_num,
412 0 : index_into_cur_line));
413 0 : success = false;
414 0 : return root;
415 : }
416 680584 : auto const &legacy_idd_extensibles_array = legacy_idd_extensibles_iter.value();
417 680584 : size_t const size = legacy_idd_extensibles_array.size();
418 680584 : std::string const &field_name = legacy_idd_extensibles_array[extensible_index % size].get<std::string>();
419 680584 : json val = parse_value(idf, index, success, schema_obj_extensions->at(field_name));
420 680584 : if (!success) {
421 0 : return root;
422 : }
423 680584 : extensible[field_name] = std::move(val);
424 680584 : was_value_parsed = true;
425 680584 : extensible_index++;
426 680584 : if (extensible_index && extensible_index % size == 0) {
427 654885 : array_of_extensions.push_back(extensible);
428 654885 : extensible.clear();
429 : }
430 680584 : } else {
431 393254 : was_value_parsed = true;
432 393254 : std::string const &field = legacy_idd_fields_array[legacy_idd_index].get<std::string>();
433 393254 : auto const find_field_iter = schema_obj_props.find(field);
434 393254 : if (find_field_iter == schema_obj_props.end()) {
435 24102 : if (field == "name") {
436 24102 : root[field] = parse_string(idf, index);
437 : } else {
438 0 : u64toa(cur_line_num, s);
439 0 : errors_.emplace_back(fmt::format("Line: {} - Field \"{}\" was not found.", s, field));
440 : }
441 : } else {
442 369152 : json val = parse_value(idf, index, success, find_field_iter.value());
443 369152 : if (!success) {
444 0 : return root;
445 : }
446 369152 : root[field] = std::move(val);
447 369152 : }
448 393254 : if (!success) {
449 0 : return root;
450 : }
451 393254 : }
452 2845225 : }
453 30827 : if (!array_of_extensions.empty()) {
454 7993 : root[extension_key] = std::move(array_of_extensions);
455 7993 : array_of_extensions = nullptr;
456 : }
457 30827 : return root;
458 30830 : }
459 :
460 592367 : json IdfParser::parse_number(std::string_view idf, size_t &index)
461 : {
462 592367 : eat_whitespace(idf, index);
463 :
464 592367 : size_t save_i = index;
465 :
466 592367 : bool running = true;
467 5029424 : while (running) {
468 4437057 : if (save_i == idf_size) {
469 0 : break;
470 : }
471 :
472 4437057 : char const c = idf[save_i];
473 4437057 : switch (c) {
474 592367 : case '!':
475 : case ',':
476 : case ';':
477 : case '\r':
478 : case '\n':
479 592367 : running = false;
480 592367 : break;
481 3844690 : default:
482 3844690 : ++save_i;
483 : }
484 : }
485 :
486 592367 : size_t diff = save_i - index;
487 592367 : std::string_view value = idf.substr(index, diff);
488 592367 : index_into_cur_line += diff;
489 592367 : index = save_i;
490 :
491 435802 : auto const convert_double = [&index, this](std::string_view str) -> json { // (AUTO_OK)
492 435802 : size_t plus_sign = 0;
493 435802 : if (str.front() == '+') {
494 4 : plus_sign = 1;
495 : }
496 435802 : auto const str_end = str.data() + str.size(); // have to do this for MSVC // (AUTO_OK)
497 : double val;
498 435802 : auto result = fast_float::from_chars(str.data() + plus_sign, str.data() + str.size(), val); // (AUTO_OK)
499 435802 : if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) {
500 3 : return rtrim(str);
501 435799 : } else if (result.ptr != str_end) {
502 230 : auto const initial_ptr = result.ptr; // (AUTO_OK)
503 456 : while (result.ptr != str_end) {
504 233 : if (*result.ptr != ' ') {
505 7 : break;
506 : }
507 226 : ++result.ptr;
508 : }
509 230 : if (result.ptr == str_end) {
510 223 : index -= (str_end - initial_ptr);
511 223 : this->index_into_cur_line -= (str_end - initial_ptr);
512 223 : return val;
513 : }
514 7 : return rtrim(str);
515 : }
516 435569 : return val;
517 592367 : };
518 :
519 592367 : auto const convert_int = [&convert_double, &index, this](std::string_view str) -> json { // (AUTO_OK)
520 592367 : auto const str_end = str.data() + str.size(); // have to do this for MSVC // (AUTO_OK)
521 : int val;
522 592367 : auto result = FromChars::from_chars(str.data(), str.data() + str.size(), val); // (AUTO_OK)
523 592367 : if (result.ec == std::errc::result_out_of_range || result.ec == std::errc::invalid_argument) {
524 1438 : return convert_double(str);
525 590929 : } else if (result.ptr != str_end) {
526 434377 : if (*result.ptr == '.' || *result.ptr == 'e' || *result.ptr == 'E') {
527 434364 : return convert_double(str);
528 : } else {
529 13 : auto const initial_ptr = result.ptr; // (AUTO_OK)
530 22 : while (result.ptr != str_end) {
531 13 : if (*result.ptr != ' ') {
532 4 : break;
533 : }
534 9 : ++result.ptr;
535 : }
536 13 : if (result.ptr == str_end) {
537 9 : index -= (str_end - initial_ptr);
538 9 : this->index_into_cur_line -= (str_end - initial_ptr);
539 9 : return val;
540 : }
541 4 : return rtrim(str);
542 : }
543 : }
544 156552 : return val;
545 592367 : };
546 :
547 1184734 : return convert_int(value);
548 : }
549 :
550 5498 : json IdfParser::parse_integer(std::string_view idf, size_t &index)
551 : {
552 5498 : eat_whitespace(idf, index);
553 :
554 5498 : size_t save_i = index;
555 :
556 5498 : bool running = true;
557 20190 : while (running) {
558 14692 : if (save_i == idf_size) {
559 0 : break;
560 : }
561 :
562 14692 : char const c = idf[save_i];
563 14692 : switch (c) {
564 5498 : case '!':
565 : case ',':
566 : case ';':
567 : case '\r':
568 : case '\n':
569 5498 : running = false;
570 5498 : break;
571 9194 : default:
572 9194 : ++save_i;
573 : }
574 : }
575 :
576 5498 : size_t diff = save_i - index;
577 5498 : std::string_view string_value = idf.substr(index, diff);
578 5498 : index_into_cur_line += diff;
579 5498 : index = save_i;
580 :
581 5498 : auto const string_end = string_value.data() + string_value.size(); // have to do this for MSVC // (AUTO_OK)
582 : int int_value;
583 : // Try using from_chars
584 5498 : auto result = FromChars::from_chars(string_value.data(), string_value.data() + string_value.size(), int_value); // (AUTO_OK)
585 5498 : if (result.ec == std::errc::result_out_of_range || result.ec == std::errc::invalid_argument) {
586 : // Failure, return the string
587 0 : return rtrim(string_value);
588 5498 : } else if (result.ptr != string_end) {
589 : // Didn't use the entire string, try again via double conversion + rounding
590 42 : size_t plus_sign = 0;
591 42 : if (string_value.front() == '+') {
592 0 : plus_sign = 1;
593 : }
594 : double double_value;
595 42 : auto fresult = fast_float::from_chars(string_value.data() + plus_sign, string_value.data() + string_value.size(), double_value); // (AUTO_OK)
596 42 : if (fresult.ec == std::errc::invalid_argument || fresult.ec == std::errc::result_out_of_range) {
597 : // Failure, return the string
598 0 : return rtrim(string_value);
599 : }
600 42 : int_value = static_cast<int>(std::round(double_value));
601 : }
602 5498 : return int_value;
603 : }
604 :
605 1049740 : json IdfParser::parse_value(std::string_view idf, size_t &index, bool &success, json const &field_loc)
606 : {
607 : Token token;
608 1049740 : auto const &field_type = field_loc.find("type");
609 1049740 : if (field_type != field_loc.end()) {
610 499879 : if (field_type.value() == "number") {
611 397859 : token = Token::NUMBER;
612 102020 : } else if (field_type.value() == "integer") {
613 5498 : token = Token::INTEGER;
614 : } else {
615 96522 : token = Token::STRING;
616 : }
617 : } else {
618 549861 : token = look_ahead(idf, index);
619 : }
620 :
621 1049740 : switch (token) {
622 451897 : case Token::STRING: {
623 451897 : std::string const parsed_string = parse_string(idf, index);
624 451897 : auto const enum_it = field_loc.find("enum");
625 451897 : if (enum_it != field_loc.end()) {
626 164180 : for (auto const &enum_str : enum_it.value()) {
627 164161 : std::string const str = enum_str.get<std::string>();
628 164161 : if (icompare(str, parsed_string)) {
629 39870 : return str;
630 : }
631 243920 : }
632 412008 : } else if (icompare(parsed_string, "Autosize") || icompare(parsed_string, "Autocalculate")) {
633 6806 : auto const default_it = field_loc.find("default");
634 3403 : auto const anyOf_it = field_loc.find("anyOf");
635 :
636 3403 : if (anyOf_it == field_loc.end()) {
637 4 : errors_.emplace_back(
638 4 : fmt::format("Line: {} Index: {} - Field cannot be Autosize or Autocalculate", cur_line_num, index_into_cur_line));
639 2 : return parsed_string;
640 : }
641 : // The following is hacky because it abuses knowing the consistent generated structure
642 : // in the future this might not hold true for the array indexes.
643 3401 : if (default_it != field_loc.end()) {
644 3574 : return field_loc.at("anyOf")[1]["enum"][1];
645 : } else {
646 3228 : return field_loc.at("anyOf")[1]["enum"][0];
647 : }
648 3403 : }
649 408624 : return parsed_string;
650 451897 : }
651 592345 : case Token::NUMBER: {
652 592345 : return parse_number(idf, index);
653 : }
654 5498 : case Token::INTEGER: {
655 5498 : return parse_integer(idf, index);
656 : }
657 0 : case Token::NONE:
658 : case Token::END:
659 : case Token::EXCLAMATION:
660 : case Token::COMMA:
661 : case Token::SEMICOLON:
662 : default:
663 0 : break;
664 : }
665 0 : success = false;
666 0 : return nullptr;
667 1049740 : }
668 :
669 506835 : std::string IdfParser::parse_string(std::string_view idf, size_t &index)
670 : {
671 506835 : eat_whitespace(idf, index);
672 :
673 506835 : std::string str;
674 :
675 : while (true) {
676 7494429 : if (index == idf_size) {
677 3 : break;
678 : }
679 :
680 7494426 : char c = idf[index];
681 7494426 : increment_both_index(index, index_into_cur_line);
682 7494426 : if (c == ',' || c == ';' || c == '!') {
683 506832 : decrement_both_index(index, index_into_cur_line);
684 506832 : break;
685 : } else {
686 6987594 : str += c;
687 : }
688 6987594 : }
689 :
690 1013670 : return rtrim(str);
691 506835 : }
692 :
693 52075003 : void IdfParser::increment_both_index(size_t &index, size_t &line_index)
694 : {
695 52075003 : index++;
696 52075003 : line_index++;
697 52075003 : }
698 :
699 506832 : void IdfParser::decrement_both_index(size_t &index, size_t &line_index)
700 : {
701 506832 : index--;
702 506832 : line_index--;
703 506832 : }
704 :
705 5716640 : void IdfParser::eat_whitespace(std::string_view idf, size_t &index)
706 : {
707 22711924 : while (index < idf_size) {
708 22710554 : switch (idf[index]) {
709 16844987 : case ' ':
710 : case '\r':
711 : case '\t':
712 16844987 : increment_both_index(index, index_into_cur_line);
713 16844987 : continue;
714 150297 : case '\n':
715 150297 : increment_both_index(index, cur_line_num);
716 150297 : beginning_of_line_index = index;
717 150297 : index_into_cur_line = 0;
718 150297 : continue;
719 5715270 : default:
720 5715270 : return;
721 : }
722 : }
723 : }
724 :
725 744519 : void IdfParser::eat_comment(std::string_view idf, size_t &index)
726 : {
727 : while (true) {
728 22974725 : if (index == idf_size) {
729 0 : break;
730 : }
731 22974725 : if (idf[index] == '\n') {
732 744519 : increment_both_index(index, cur_line_num);
733 744519 : index_into_cur_line = 0;
734 744519 : beginning_of_line_index = index;
735 744519 : break;
736 : }
737 22230206 : increment_both_index(index, index_into_cur_line);
738 : }
739 744519 : }
740 :
741 3506174 : IdfParser::Token IdfParser::look_ahead(std::string_view idf, size_t index)
742 : {
743 3506174 : size_t save_index = index;
744 3506174 : size_t save_line_num = cur_line_num;
745 3506174 : size_t save_line_index = index_into_cur_line;
746 3506174 : Token token = next_token(idf, save_index);
747 3506174 : cur_line_num = save_line_num;
748 3506174 : index_into_cur_line = save_line_index;
749 3506174 : return token;
750 : }
751 :
752 4611938 : IdfParser::Token IdfParser::next_token(std::string_view idf, size_t &index)
753 : {
754 4611938 : eat_whitespace(idf, index);
755 :
756 4611938 : if (index == idf_size) {
757 1370 : return Token::END;
758 : }
759 :
760 4610568 : char const c = idf[index];
761 4610568 : increment_both_index(index, index_into_cur_line);
762 4610568 : switch (c) {
763 744517 : case '!':
764 744517 : return Token::EXCLAMATION;
765 2149857 : case ',':
766 2149857 : return Token::COMMA;
767 61657 : case ';':
768 61657 : return Token::SEMICOLON;
769 1654537 : default:
770 : static constexpr std::string_view numeric(".-+0123456789");
771 1654537 : if (numeric.find_first_of(c) != std::string::npos) {
772 793332 : return Token::NUMBER;
773 : }
774 861205 : return Token::STRING;
775 : }
776 : decrement_both_index(index, index_into_cur_line);
777 : return Token::NONE;
778 : }
779 :
780 0 : IdfParser::Token IdfParser::next_limited_token(std::string_view idf, size_t &index)
781 : {
782 0 : if (index == idf_size) {
783 0 : return Token::END;
784 : }
785 :
786 0 : char const c = idf[index];
787 0 : increment_both_index(index, index_into_cur_line);
788 0 : switch (c) {
789 0 : case '!':
790 0 : return Token::EXCLAMATION;
791 0 : case ',':
792 0 : return Token::COMMA;
793 0 : case ';':
794 0 : return Token::SEMICOLON;
795 0 : default:
796 0 : return Token::NONE;
797 : }
798 : }
799 :
800 506849 : std::string IdfParser::rtrim(std::string_view str)
801 : {
802 : static constexpr std::string_view whitespace(" \t\0", 3);
803 506849 : if (str.empty()) {
804 1 : return std::string{};
805 : }
806 506848 : size_t const index = str.find_last_not_of(whitespace);
807 506848 : if (index == std::string::npos) {
808 0 : return std::string{};
809 506848 : } else if (index + 1 < str.length()) {
810 897 : return std::string{str.substr(0, index + 1)};
811 : }
812 1013098 : return std::string{str};
813 : }
|