Cantera  2.5.1
AnyMap.cpp
Go to the documentation of this file.
1 //! @file AnyMap.cpp
2 
3 // This file is part of Cantera. See License.txt in the top-level directory or
4 // at https://cantera.org/license.txt for license and copyright information.
5 
6 #include "cantera/base/AnyMap.h"
7 #include "application.h"
8 #include "cantera/base/yaml.h"
10 #ifdef CT_USE_DEMANGLE
11  #include <boost/core/demangle.hpp>
12 #endif
13 
14 #include <boost/algorithm/string.hpp>
15 #include <fstream>
16 #include <mutex>
17 #include <unordered_set>
18 
19 namespace ba = boost::algorithm;
20 
21 namespace { // helper functions
22 
23 std::mutex yaml_cache_mutex;
24 
25 bool isFloat(const std::string& val)
26 {
27  // This function duplicates the logic of fpValueCheck, but doesn't throw
28  // exceptions if the string isn't a float
29  std::string str = ba::trim_copy(val);
30  if (str.empty()) {
31  return false;
32  }
33  int numDot = 0;
34  int numExp = 0;
35  int istart = 0;
36  char ch = str[0];
37  if (ch == '+' || ch == '-') {
38  istart = 1;
39  if (str.size() == 1) {
40  return false;
41  }
42  }
43  for (size_t i = istart; i < str.size(); i++) {
44  ch = str[i];
45  if (isdigit(ch)) {
46  } else if (ch == '.') {
47  numDot++;
48  if (numDot > 1) {
49  return false;
50  }
51  if (numExp > 0) {
52  return false;
53  }
54  } else if (ch == 'e' || ch == 'E') {
55  numExp++;
56  if (numExp > 1 || i == str.size() - 1) {
57  return false;
58  }
59  ch = str[i+1];
60  if (ch == '+' || ch == '-') {
61  if (i + 1 == str.size() - 1) {
62  return false;
63  }
64  i++;
65  }
66  } else {
67  return false;
68  }
69  }
70  return true;
71 }
72 
73 bool isInt(const std::string& val)
74 {
75  std::string str = ba::trim_copy(val);
76  if (str.empty()) {
77  return false;
78  }
79  int istart = 0;
80  char ch = str[0];
81  if (ch == '+' || ch == '-') {
82  istart = 1;
83  if (str.size() == 1) {
84  return false;
85  }
86  }
87  for (size_t i = istart; i < str.size(); i++) {
88  if (!isdigit(str[i])) {
89  return false;
90  }
91  }
92  return true;
93 }
94 
95 bool isBool(const std::string& val) {
96  std::string str = ba::trim_copy(val);
97  return (val == "true" || val == "True" || val == "false" || val == "False");
98 }
99 
100 enum class Type : char {
101  Unknown = 0,
102  Integer = 1,
103  Double = 2,
104  String = 4,
105  Bool = 8,
106  Map = 16,
107  Sequence = 32
108 };
109 
110 Type operator|(Type lhs, Type rhs)
111 {
112  return Type(static_cast<char>(lhs) | static_cast<char>(rhs));
113 }
114 
115 Type elementTypes(const YAML::Node& node)
116 {
117  // See what kinds of elements we have:
118  Type types = Type::Unknown;
119  for (const auto& el : node) {
120  if (el.IsMap()) {
121  types = types | Type::Map;
122  } else if (el.IsSequence()) {
123  types = types | Type::Sequence;
124  } else if (el.IsScalar()) {
125  std::string nodestr = el.as<std::string>();
126  if (isInt(nodestr)) {
127  types = types | Type::Integer;
128  } else if (isFloat(nodestr)) {
129  types = types | Type::Double;
130  } else if (isBool(nodestr)) {
131  types = types | Type::Bool;
132  } else {
133  types = types | Type::String;
134  }
135  }
136  }
137  return types;
138 }
139 
140 Cantera::AnyValue Empty;
141 
142 } // end anonymous namespace
143 
144 namespace YAML { // YAML converters
145 
146 using namespace Cantera;
147 
148 template<>
149 struct convert<Cantera::AnyMap> {
150  static Node encode(const Cantera::AnyMap& rhs) {
151  throw NotImplementedError("AnyMap::encode");
152  }
153  static bool decode(const Node& node, Cantera::AnyMap& target) {
154  target.setLoc(node.Mark().line, node.Mark().column);
155  if (node.IsSequence()) {
156  // Convert a top-level list to a map with the key "items"
157  target["items"] = node.as<AnyValue>();
158  return true;
159  } else if (!node.IsMap()) {
160  std::string text = YAML::Dump(node);
161  if (text.size() > 300) {
162  text.resize(300);
163  }
164  throw CanteraError("YAML::convert<AnyMap>",
165  "YAML node is not a map. Node begins with:\n'''\n{}\n'''", text);
166  }
167  for (const auto& child : node) {
168  std::string key = child.first.as<std::string>();
169  const auto& loc = child.second.Mark();
170  target[key].setLoc(loc.line, loc.column);
171  if (child.second.IsMap()) {
172  target[key] = child.second.as<AnyMap>();
173  } else {
174  target[key] = child.second.as<AnyValue>();
175  target[key].setKey(key);
176  }
177  }
178  return true;
179  }
180 };
181 
182 template<>
183 struct convert<Cantera::AnyValue> {
184  static Node encode(const Cantera::AnyValue& rhs) {
185  throw NotImplementedError("AnyValue::encode");
186  }
187 
188  static bool decode(const Node& node, Cantera::AnyValue& target) {
189  target.setLoc(node.Mark().line, node.Mark().column);
190  if (node.IsScalar()) {
191  // Scalar nodes are int, doubles, or strings
192  std::string nodestr = node.as<std::string>();
193  if (node.Tag() == "!") {
194  // Prevent quoted strings from being implicitly converted to
195  // numeric types, e.g. the quoted YAML string '12345' should not
196  // be interpreted as an integer
197  target = nodestr;
198  } else if (isInt(nodestr)) {
199  try {
200  target = node.as<long int>();
201  } catch (YAML::BadConversion&) {
202  // This exception is raised if the value doesn't fit in a
203  // long int, in which case we would rather store it
204  // (possibly inexactly) as a double.
205  target = node.as<double>();
206  }
207  } else if (isFloat(nodestr)) {
208  target = fpValue(nodestr);
209  } else if (isBool(nodestr)) {
210  target = node.as<bool>();
211  } else {
212  target = nodestr;
213  }
214  return true;
215  } else if (node.IsSequence()) {
216  // Convert sequences of the same element type to vectors of that type
217  Type types = elementTypes(node);
218  if (types == Type::Integer) {
219  target = node.as<std::vector<long int>>();
220  } else if (types == (Type::Integer | Type::Double) || types == Type::Double) {
221  target = node.as<vector_fp>();
222  } else if (types == Type::String) {
223  target = node.as<std::vector<std::string>>();
224  } else if (types == Type::Bool) {
225  target = node.as<std::vector<bool>>();
226  } else if (types == Type::Map) {
227  target = node.as<std::vector<AnyMap>>();
228  } else if (types == Type::Sequence) {
229  // Create nested vectors when data types are compatible
230  Type subtypes = Type::Unknown;
231  for (const auto& el : node) {
232  subtypes = subtypes | elementTypes(el);
233  }
234  if (subtypes == Type::Integer) {
235  target = node.as<std::vector<std::vector<long int>>>();
236  } else if (subtypes == (Type::Integer | Type::Double) || subtypes == Type::Double) {
237  target = node.as<std::vector<std::vector<double>>>();
238  } else if (types == Type::String) {
239  target = node.as<std::vector<std::vector<std::string>>>();
240  } else if (types == Type::Bool) {
241  target = node.as<std::vector<std::vector<bool>>>();
242  } else {
243  target = node.as<std::vector<AnyValue>>();
244  }
245  } else {
246  // If types are different, create a vector of generic values
247  target = node.as<std::vector<AnyValue>>();
248  }
249  return true;
250  } else if (node.IsMap()) {
251  target = node.as<AnyMap>();
252  return true;
253  } else if (node.IsNull()) {
254  target = Empty;
255  return true;
256  }
257  return false;
258  }
259 };
260 
261 }
262 
263 namespace Cantera
264 {
265 
266 std::map<std::string, std::string> AnyValue::s_typenames = {
267  {typeid(double).name(), "double"},
268  {typeid(long int).name(), "long int"},
269  {typeid(std::string).name(), "string"},
270  {typeid(std::vector<double>).name(), "vector<double>"},
271  {typeid(AnyMap).name(), "AnyMap"},
272 };
273 
274 std::unordered_map<std::string, std::pair<AnyMap, int>> AnyMap::s_cache;
275 
276 // Methods of class AnyBase
277 
278 AnyBase::AnyBase()
279  : m_line(-1)
280  , m_column(-1)
281 {}
282 
283 void AnyBase::setLoc(int line, int column)
284 {
285  m_line = line;
286  m_column = column;
287 }
288 
289 const AnyValue& AnyBase::getMetadata(const std::string& key) const
290 {
291  if (m_metadata && m_metadata->hasKey(key)) {
292  return m_metadata->at(key);
293  } else {
294  return Empty;
295  }
296 }
297 
298 // Methods of class AnyValue
299 
300 AnyValue::AnyValue()
301  : m_key()
302  , m_value(new boost::any{})
303  , m_equals(eq_comparer<size_t>)
304 {}
305 
306 AnyValue::~AnyValue() = default;
307 
308 AnyValue::AnyValue(AnyValue const& other)
309  : AnyBase(other)
310  , m_key(other.m_key)
311  , m_value(new boost::any{*other.m_value})
312  , m_equals(other.m_equals)
313 {
314 }
315 
316 AnyValue::AnyValue(AnyValue&& other)
317  : AnyBase(std::move(other))
318  , m_key(std::move(other.m_key))
319  , m_value(std::move(other.m_value))
320  , m_equals(std::move(other.m_equals))
321 {
322 }
323 
324 AnyValue& AnyValue::operator=(AnyValue const& other) {
325  if (this == &other) {
326  return *this;
327  }
328  AnyBase::operator=(*this);
329  m_key = other.m_key;
330  m_value.reset(new boost::any{*other.m_value});
331  m_equals = other.m_equals;
332  return *this;
333 }
334 
335 AnyValue& AnyValue::operator=(AnyValue&& other) {
336  if (this == &other) {
337  return *this;
338  }
339  AnyBase::operator=(std::move(other));
340  m_key = std::move(other.m_key);
341  m_value = std::move(other.m_value);
342  m_equals = std::move(other.m_equals);
343  return *this;
344 }
345 
346 bool AnyValue::operator==(const AnyValue& other) const
347 {
348  return m_equals(*m_value, *other.m_value);
349 }
350 
351 bool AnyValue::operator!=(const AnyValue& other) const
352 {
353  return !m_equals(*m_value, *other.m_value);
354 }
355 
356 AnyValue& AnyValue::operator[](const std::string& key)
357 {
358  return as<AnyMap>()[key];
359 }
360 
361 const AnyValue& AnyValue::operator[](const std::string& key) const
362 {
363  return as<AnyMap>().at(key);
364 }
365 
366 bool AnyValue::hasKey(const std::string& key) const {
367  return (is<AnyMap>() && as<AnyMap>().hasKey(key));
368 }
369 
370 void AnyValue::setKey(const std::string &key) { m_key = key; }
371 
372 const std::type_info &AnyValue::type() const {
373  return m_value->type();
374 }
375 
376 void AnyValue::propagateMetadata(shared_ptr<AnyMap>& metadata)
377 {
378  m_metadata = metadata;
379  if (is<AnyMap>()) {
380  as<AnyMap>().propagateMetadata(m_metadata);
381  } else if (is<std::vector<AnyValue>>()) {
382  for (auto& item : asVector<AnyValue>()) {
383  item.propagateMetadata(m_metadata);
384  }
385  } else if (is<std::vector<AnyMap>>()) {
386  for (auto& item : asVector<AnyMap>()) {
387  item.propagateMetadata(m_metadata);
388  }
389  }
390 }
391 
392 std::string AnyValue::type_str() const {
393  return demangle(type());
394 }
395 
396 bool AnyValue::isScalar() const {
397  return is<double>() || is<long int>() || is<std::string>() || is<bool>();
398 }
399 
400 // Specializations for "std::string" and "const char*"
401 
402 AnyValue::AnyValue(const std::string& value)
403  : m_value(new boost::any{value})
404  , m_equals(eq_comparer<std::string>)
405 {}
406 
407 AnyValue::AnyValue(const char* value)
408  : m_value(new boost::any{std::string(value)})
409  , m_equals(eq_comparer<std::string>)
410 {}
411 
412 AnyValue &AnyValue::operator=(const std::string &value) {
413  *m_value = value;
414  m_equals = eq_comparer<std::string>;
415  return *this;
416 }
417 
418 AnyValue &AnyValue::operator=(const char *value) {
419  *m_value = std::string(value);
420  m_equals = eq_comparer<std::string>;
421  return *this;
422 }
423 
424 const std::string &AnyValue::asString() const {
425  return as<std::string>();
426 }
427 
428 bool AnyValue::operator==(const std::string& other) const
429 {
430  if (m_value->type() == typeid(std::string)) {
431  return boost::any_cast<std::string>(*m_value) == other;
432  } else {
433  return false;
434  }
435 }
436 
437 bool AnyValue::operator!=(const std::string& other) const
438 {
439  return !(*this == other);
440 }
441 
442 bool operator==(const std::string& lhs, const AnyValue& rhs)
443 {
444  return rhs == lhs;
445 }
446 
447 bool operator!=(const std::string& lhs, const AnyValue& rhs)
448 {
449  return rhs != lhs;
450 }
451 
452 // Specializations for "double"
453 
454 AnyValue::AnyValue(double value)
455  : m_value(new boost::any{value})
456  , m_equals(eq_comparer<double>)
457 {}
458 
459 AnyValue &AnyValue::operator=(double value) {
460  *m_value = value;
461  m_equals = eq_comparer<double>;
462  return *this;
463 }
464 
466  return as<double>();
467 }
468 
469 const double& AnyValue::asDouble() const {
470  return as<double>();
471 }
472 
473 bool AnyValue::operator==(const double& other) const
474 {
475  if (m_value->type() == typeid(double)) {
476  return boost::any_cast<double>(*m_value) == other;
477  } else if (m_value->type() == typeid(long int)) {
478  return boost::any_cast<long int>(*m_value) == other;
479  } else {
480  return false;
481  }
482 }
483 
484 bool AnyValue::operator!=(const double& other) const
485 {
486  return !(*this == other);
487 }
488 
489 bool operator==(const double& lhs, const AnyValue& rhs)
490 {
491  return rhs == lhs;
492 }
493 
494 bool operator!=(const double& lhs, const AnyValue& rhs)
495 {
496  return rhs != lhs;
497 }
498 
499 // Specializations for "bool"
500 
501 AnyValue::AnyValue(bool value)
502  : m_value(new boost::any{value})
503  , m_equals(eq_comparer<bool>)
504 {}
505 
506 AnyValue &AnyValue::operator=(bool value) {
507  *m_value = value;
508  m_equals = eq_comparer<bool>;
509  return *this;
510 }
511 
513  return as<bool>();
514 }
515 
516 const bool& AnyValue::asBool() const {
517  return as<bool>();
518 }
519 
520 // Specializations for "long int" and "int"
521 
522 AnyValue::AnyValue(long int value)
523  : m_value(new boost::any{value})
524  , m_equals(eq_comparer<long int>)
525 {}
526 
527 AnyValue::AnyValue(int value)
528  : m_value(new boost::any{static_cast<long int>(value)})
529  , m_equals(eq_comparer<long int>)
530 {}
531 
532 AnyValue &AnyValue::operator=(long int value) {
533  *m_value = value;
534  m_equals = eq_comparer<long int>;
535  return *this;
536 }
537 
538 AnyValue &AnyValue::operator=(int value) {
539  *m_value = static_cast<long int>(value);
540  m_equals = eq_comparer<long int>;
541  return *this;
542 }
543 
544 long int& AnyValue::asInt() {
545  return as<long int>();
546 }
547 
548 const long int& AnyValue::asInt() const {
549  return as<long int>();
550 }
551 
552 bool AnyValue::operator==(const long int& other) const
553 {
554  if (m_value->type() == typeid(long int)) {
555  return boost::any_cast<long int>(*m_value) == other;
556  } else if (m_value->type() == typeid(double)) {
557  return boost::any_cast<double>(*m_value) == other;
558  } else {
559  return false;
560  }
561 }
562 
563 bool AnyValue::operator!=(const long int& other) const
564 {
565  return !(*this == other);
566 }
567 
568 bool AnyValue::operator==(const int& other) const
569 {
570  return *this == static_cast<long int>(other);
571 }
572 
573 bool AnyValue::operator!=(const int& other) const
574 {
575  return *this != static_cast<long int>(other);
576 }
577 
578 bool operator==(const long int& lhs, const AnyValue& rhs)
579 {
580  return rhs == lhs;
581 }
582 
583 bool operator!=(const long int& lhs, const AnyValue& rhs)
584 {
585  return rhs != lhs;
586 }
587 
588 bool operator==(const int& lhs, const AnyValue& rhs)
589 {
590  return rhs == lhs;
591 }
592 
593 bool operator!=(const int& lhs, const AnyValue& rhs)
594 {
595  return rhs != lhs;
596 }
597 
598 // Specializations for "AnyMap"
599 
600 AnyValue::AnyValue(const AnyMap& value)
601  : m_value(new boost::any{value})
602  , m_equals(eq_comparer<AnyMap>)
603 {}
604 
605 AnyValue& AnyValue::operator=(const AnyMap& value) {
606  *m_value = value;
607  m_equals = eq_comparer<AnyMap>;
608  return *this;
609 }
610 
611 AnyValue& AnyValue::operator=(AnyMap&& value) {
612  *m_value = std::move(value);
613  m_equals = eq_comparer<AnyMap>;
614  return *this;
615 }
616 
617 std::unordered_map<std::string, const AnyMap*> AnyValue::asMap(
618  const std::string& name) const
619 {
620  std::unordered_map<std::string, const AnyMap*> mapped;
621  for (const auto& item : asVector<AnyMap>()) {
622  auto key = item[name].asString();
623  if (mapped.count(key)) {
624  throw InputFileError("AnyValue::asMap", *this,
625  "Duplicate key '{}'", key);
626  }
627  mapped.emplace(std::make_pair(key, &item));
628  }
629  return mapped;
630 }
631 
632 std::unordered_map<std::string, AnyMap*> AnyValue::asMap(const std::string& name)
633 {
634  std::unordered_map<std::string, AnyMap*> mapped;
635  for (auto& item : asVector<AnyMap>()) {
636  auto key = item.at(name).asString();
637  if (mapped.count(key)) {
638  throw InputFileError("AnyValue::asMap", *this,
639  "Duplicate key '{}'", key);
640  }
641  mapped.emplace(std::make_pair(key, &item));
642  }
643  return mapped;
644 }
645 
646 const AnyMap& AnyValue::getMapWhere(const std::string& key, const std::string& value) const
647 {
648  if (is<std::vector<AnyMap>>()) {
649  if (value == "") {
650  return asVector<AnyMap>().at(0);
651  }
652  for (auto& item : asVector<AnyMap>()) {
653  if (item.hasKey(key) && item[key] == value) {
654  return item;
655  }
656  }
657  throw InputFileError("AnyValue::getMapWhere", *this,
658  "List does not contain a map where '{}' = '{}'", key, value);
659  } else if (is<AnyMap>()) {
660  if (value == "" || (hasKey(key) && as<AnyMap>()[key] == value)) {
661  return as<AnyMap>();
662  } else {
663  throw InputFileError("AnyValue::getMapWhere", *this,
664  "Map does not contain a key where '{}' = '{}'", key, value);
665  }
666  } else if (is<void>()) {
667  throw InputFileError("AnyValue::getMapWhere", *this,
668  "Key '{}' not found", m_key);
669  } else {
670  throw InputFileError("AnyValue::getMapWhere", *this,
671  "Element is not a mapping or list of mappings");
672  }
673 }
674 
675 AnyMap& AnyValue::getMapWhere(const std::string& key, const std::string& value,
676  bool create)
677 {
678  if (is<std::vector<AnyMap>>()) {
679  if (value == "") {
680  return asVector<AnyMap>().at(0);
681  }
682  for (auto& item : asVector<AnyMap>()) {
683  if (item.hasKey(key) && item[key] == value) {
684  return item;
685  }
686  }
687  if (create) {
688  // If the map wasn't found, insert it
689  auto& vec = asVector<AnyMap>();
690  AnyMap child;
691  child[key] = value;
692  vec.push_back(std::move(child));
693  return vec.back();
694  } else {
695  throw InputFileError("AnyValue::getMapWhere", *this,
696  "List does not contain a map where '{}' = '{}'", key, value);
697  }
698  } else if (is<AnyMap>()) {
699  if (value == "" || (hasKey(key) && as<AnyMap>()[key] == value)) {
700  return as<AnyMap>();
701  } else if (create) {
702  AnyMap newChild;
703  newChild[key] = value;
704  std::vector<AnyMap> nodes{std::move(as<AnyMap>()), std::move(newChild)};
705  operator=(std::move(nodes));
706  return asVector<AnyMap>().back();
707  } else {
708  throw InputFileError("AnyValue::getMapWhere", *this,
709  "Map does not contain a key where '{}' = '{}'", key, value);
710  }
711  } else if (is<void>() && create) {
712  AnyMap child;
713  child[key] = value;
714  operator=(std::move(child));
715  return as<AnyMap>();
716  } else if (is<void>()) {
717  throw InputFileError("AnyValue::getMapWhere", *this,
718  "Key '{}' not found", m_key);
719  } else {
720  throw InputFileError("AnyValue::getMapWhere", *this,
721  "Element is not a mapping or list of mappings");
722  }
723 }
724 
725 bool AnyValue::hasMapWhere(const std::string& key, const std::string& value) const
726 {
727  if (is<std::vector<AnyMap>>()) {
728  if (value == "") {
729  return true;
730  }
731  for (auto& item : asVector<AnyMap>()) {
732  if (item.hasKey(key) && item[key] == value) {
733  return true;
734  }
735  }
736  return false;
737  } else if (is<AnyMap>()) {
738  if (value == "" || (hasKey(key) && as<AnyMap>()[key] == value)) {
739  return true;
740  } else {
741  return false;
742  }
743  } else {
744  return false;
745  }
746 }
747 
749 {
750  if (is<AnyMap>()) {
751  // Units declaration applicable to this map
752  as<AnyMap>().applyUnits(units);
753  } else if (is<std::vector<AnyMap>>()) {
754  auto& list = as<std::vector<AnyMap>>();
755  if (list.size() && list[0].hasKey("units") && list[0].size() == 1) {
756  // First item in the list is a units declaration, which applies to
757  // the items in the list
758  UnitSystem newUnits = units;
759  newUnits.setDefaults(list[0]["units"].asMap<std::string>());
760  list[0].m_data.erase("units");
761  for (auto& item : list) {
762  // Any additional units declarations are errors
763  if (item.size() == 1 && item.hasKey("units")) {
764  throw InputFileError("AnyValue::applyUnits", item,
765  "Found units entry as not the first item in a list.");
766  }
767  item.applyUnits(newUnits);
768  }
769  // Remove the "units" map after it has been applied
770  list.erase(list.begin());
771  } else {
772  // Simple downward propagation of the current units
773  for (auto& item : list) {
774  // Any later units declarations are errors
775  if (item.size() == 1 && item.hasKey("units")) {
776  throw InputFileError("AnyValue::applyUnits", item,
777  "Found units entry as not the first item in a list.");
778  }
779  item.applyUnits(units);
780  }
781  }
782  }
783 
784 }
785 
786 std::string AnyValue::demangle(const std::type_info& type) const
787 {
788  if (s_typenames.find(type.name()) != s_typenames.end()) {
789  return s_typenames[type.name()];
790  } else {
791  #ifdef CT_USE_DEMANGLE
792  return boost::core::demangle(type.name());
793  #else
794  return type.name();
795  #endif
796  }
797 }
798 
799 // Explicit template specializations to allow certain conversions
800 
801 template<>
802 const std::vector<AnyValue>& AnyValue::asVector<AnyValue>(size_t nMin, size_t nMax) const
803 {
804  if (!is<std::vector<AnyValue>>()) {
805  std::vector<AnyValue> v;
806  if (is<std::vector<double>>()) {
807  for (const auto& el : asVector<double>()) {
808  v.push_back(AnyValue(el));
809  }
810  *m_value = v;
811  } else if (is<std::vector<long int>>()) {
812  for (const auto& el : asVector<long int>()) {
813  v.push_back(AnyValue(el));
814  }
815  *m_value = v;
816  } else if (is<std::vector<std::string>>()) {
817  for (const auto& el : asVector<std::string>()) {
818  v.push_back(AnyValue(el));
819  }
820  *m_value = v;
821  }
822  // If none of these special cases match, the value won't be replaced,
823  // and an exception will be thrown.
824  }
825  const auto& vv = as<std::vector<AnyValue>>();
826  m_equals = eq_comparer<std::vector<AnyValue>>;
827  checkSize(vv, nMin, nMax);
828  return vv;
829 }
830 
831 template<>
832 std::vector<AnyValue>& AnyValue::asVector<AnyValue>(size_t nMin, size_t nMax)
833 {
834  auto& v = const_cast<std::vector<AnyValue>&>(
835  const_cast<const AnyValue*>(this)->asVector<AnyValue>());
836  checkSize(v, nMin, nMax);
837  return v;
838 }
839 
840 template<>
841 const std::vector<double>& AnyValue::asVector<double>(size_t nMin, size_t nMax) const
842 {
843  if (is<std::vector<long int>>()) {
844  std::vector<double> v;
845  for (const auto& el : asVector<long int>()) {
846  v.push_back(el);
847  }
848  *m_value = v;
849  }
850  const auto& vv = as<std::vector<double>>();
851  m_equals = eq_comparer<std::vector<double>>;
852  checkSize(vv, nMin, nMax);
853  return vv;
854 }
855 
856 template<>
857 std::vector<double>& AnyValue::asVector<double>(size_t nMin, size_t nMax)
858 {
859  if (is<std::vector<long int>>()) {
860  std::vector<double> v;
861  for (const auto& el : asVector<long int>()) {
862  v.push_back(el);
863  }
864  *m_value = v;
865  }
866  auto& vv = as<std::vector<double>>();
867  m_equals = eq_comparer<std::vector<double>>;
868  checkSize(vv, nMin, nMax);
869  return vv;
870 }
871 
872 template<>
873 const std::vector<vector_fp>& AnyValue::asVector<vector_fp>(size_t nMin, size_t nMax) const
874 {
875  if (is<std::vector<std::vector<long int>>>()) {
876  std::vector<vector_fp> v;
877  for (const auto& outer : asVector<std::vector<long int>>()) {
878  v.push_back(vector_fp());
879  for (const auto& inner : outer) {
880  v.back().push_back(inner);
881  }
882  }
883  *m_value = v;
884  }
885  const auto& vv = as<std::vector<vector_fp>>();
886  m_equals = eq_comparer<std::vector<vector_fp>>;
887  checkSize(vv, nMin, nMax);
888  return vv;
889 }
890 
891 template<>
892 std::vector<vector_fp>& AnyValue::asVector<vector_fp>(size_t nMin, size_t nMax)
893 {
894  if (is<std::vector<std::vector<long int>>>()) {
895  std::vector<vector_fp> v;
896  for (const auto& outer : asVector<std::vector<long int>>()) {
897  v.push_back(vector_fp());
898  for (const auto& inner : outer) {
899  v.back().push_back(inner);
900  }
901  }
902  *m_value = v;
903  }
904  auto& vv = as<std::vector<vector_fp>>();
905  m_equals = eq_comparer<std::vector<vector_fp>>;
906  checkSize(vv, nMin, nMax);
907  return vv;
908 }
909 
910 template<>
911 const std::vector<AnyMap>& AnyValue::asVector<AnyMap>(size_t nMin, size_t nMax) const
912 {
913  if (is<AnyMap>()) {
914  std::vector<AnyMap> v;
915  v.push_back(std::move(as<AnyMap>()));
916  *m_value = std::move(v);
917  } else if (is<std::vector<AnyValue>>() && asVector<AnyValue>().empty()) {
918  *m_value = std::vector<AnyMap>();
919  }
920  const auto& vv = as<std::vector<AnyMap>>();
921  checkSize(vv, nMin, nMax);
922  return vv;
923 }
924 
925 template<>
926 std::vector<AnyMap>& AnyValue::asVector<AnyMap>(size_t nMin, size_t nMax)
927 {
928  if (is<AnyMap>()) {
929  std::vector<AnyMap> v;
930  v.push_back(std::move(as<AnyMap>()));
931  *m_value = std::move(v);
932  } else if (is<std::vector<AnyValue>>() && asVector<AnyValue>().empty()) {
933  *m_value = std::vector<AnyMap>();
934  }
935  auto& vv = as<std::vector<AnyMap>>();
936  checkSize(vv, nMin, nMax);
937  return vv;
938 }
939 
940 // Methods of class AnyMap
941 
942 AnyValue& AnyMap::operator[](const std::string& key)
943 {
944  const auto& iter = m_data.find(key);
945  if (iter == m_data.end()) {
946  // Create a new key return it
947  // NOTE: 'insert' can be replaced with 'emplace' after support for
948  // G++ 4.7 is dropped.
949  AnyValue& value = m_data.insert({key, AnyValue()}).first->second;
950  value.setKey(key);
951  if (m_metadata) {
952  // Approximate location, useful mainly if this insertion is going to
953  // immediately result in an error that needs to be reported.
954  value.setLoc(m_line, m_column);
956  }
957  return value;
958  } else {
959  // Return an already-existing item
960  return iter->second;
961  }
962 }
963 
964 const AnyValue& AnyMap::operator[](const std::string& key) const
965 {
966  try {
967  return m_data.at(key);
968  } catch (std::out_of_range&) {
969  throw InputFileError("AnyMap::operator[]", *this,
970  "Key '{}' not found.\nExisting keys: {}", key, keys_str());
971  }
972 }
973 
974 const AnyValue& AnyMap::at(const std::string& key) const
975 {
976  try {
977  return m_data.at(key);
978  } catch (std::out_of_range&) {
979  throw InputFileError("AnyMap::at", *this,
980  "Key '{}' not found.\nExisting keys: {}", key, keys_str());
981  }
982 }
983 
984 bool AnyMap::hasKey(const std::string& key) const
985 {
986  return (m_data.find(key) != m_data.end());
987 }
988 
989 void AnyMap::erase(const std::string& key)
990 {
991  m_data.erase(key);
992 }
993 
995 {
996  m_data.clear();
997 }
998 
999 std::string AnyMap::keys_str() const
1000 {
1001  fmt::memory_buffer b;
1002  auto iter = this->begin();
1003  if (iter != this->end()) {
1004  format_to(b, "{}", iter->first);
1005  ++iter;
1006  }
1007  while (iter != this->end()) {
1008  format_to(b, ", {}", iter->first);
1009  ++iter;
1010  }
1011  return to_string(b);
1012 }
1013 
1014 void AnyMap::propagateMetadata(shared_ptr<AnyMap>& metadata)
1015 {
1016  m_metadata = metadata;
1017  for (auto& item : m_data) {
1018  item.second.propagateMetadata(m_metadata);
1019  }
1020 }
1021 
1022 void AnyMap::setMetadata(const std::string& key, const AnyValue& value)
1023 {
1024  if (m_metadata) {
1025  // Fork the metadata tree at this point to avoid affecting parent nodes
1026  m_metadata = make_shared<AnyMap>(*m_metadata);
1027  } else {
1028  m_metadata = make_shared<AnyMap>();
1029  }
1030  (*m_metadata)[key] = value;
1032 }
1033 
1034 bool AnyMap::getBool(const std::string& key, bool default_) const
1035 {
1036  return (hasKey(key)) ? m_data.at(key).asBool() : default_;
1037 }
1038 
1039 double AnyMap::getDouble(const std::string& key, double default_) const
1040 {
1041  return (hasKey(key)) ? m_data.at(key).asDouble() : default_;
1042 }
1043 
1044 long int AnyMap::getInt(const std::string& key, long int default_) const
1045 {
1046  return (hasKey(key)) ? m_data.at(key).asInt() : default_;
1047 }
1048 
1049 const std::string& AnyMap::getString(const std::string& key,
1050  const std::string& default_) const
1051 {
1052  return (hasKey(key)) ? m_data.at(key).asString() : default_;
1053 }
1054 
1055 double AnyMap::convert(const std::string& key, const std::string& dest) const
1056 {
1057  return units().convert(at(key), dest);
1058 }
1059 
1060 double AnyMap::convert(const std::string& key, const Units& dest) const
1061 {
1062  return units().convert(at(key), dest);
1063 }
1064 
1065 double AnyMap::convert(const std::string& key, const std::string& dest,
1066  double default_) const
1067 {
1068  if (hasKey(key)) {
1069  return units().convert(at(key), dest);
1070  } else {
1071  return default_;
1072  }
1073 }
1074 
1075 vector_fp AnyMap::convertVector(const std::string& key, const std::string& dest,
1076  size_t nMin, size_t nMax) const
1077 {
1078  return units().convert(at(key).asVector<AnyValue>(nMin, nMax), dest);
1079 }
1080 
1081 AnyMap::Iterator::Iterator(
1082  const std::unordered_map<std::string, AnyValue>::const_iterator& start,
1083  const std::unordered_map<std::string, AnyValue>::const_iterator& stop)
1084 {
1085  m_iter = start;
1086  m_stop = stop;
1087  while (m_iter != m_stop
1088  && ba::starts_with(m_iter->first, "__")
1089  && ba::ends_with(m_iter->first, "__")) {
1090  ++m_iter;
1091  }
1092 }
1093 
1094 AnyMap::Iterator& AnyMap::Iterator::operator++()
1095 {
1096  ++m_iter;
1097  while (m_iter != m_stop
1098  && ba::starts_with(m_iter->first, "__")
1099  && ba::ends_with(m_iter->first, "__")) {
1100  ++m_iter;
1101  }
1102  return *this;
1103 }
1104 
1105 bool AnyMap::operator==(const AnyMap& other) const
1106 {
1107  // First, make sure that 'other' has all of the non-hidden keys that are in
1108  // this map
1109  for (auto& item : *this) {
1110  if (!other.hasKey(item.first)) {
1111  return false;
1112  }
1113  }
1114  // Then check for equality, using the non-hidden keys from 'other'
1115  for (auto & item : other) {
1116  if (!hasKey(item.first) || item.second != at(item.first)) {
1117  return false;
1118  }
1119  }
1120  return true;
1121 }
1122 
1123 bool AnyMap::operator!=(const AnyMap& other) const
1124 {
1125  return m_data != other.m_data;
1126 }
1127 
1129  m_units = units;
1130 
1131  if (hasKey("units")) {
1132  m_units.setDefaults(at("units").asMap<std::string>());
1133  m_data.erase("units");
1134  }
1135  for (auto& item : m_data) {
1136  item.second.applyUnits(m_units);
1137  }
1138 }
1139 
1140 AnyMap AnyMap::fromYamlString(const std::string& yaml) {
1141  AnyMap amap;
1142  try {
1143  YAML::Node node = YAML::Load(yaml);
1144  amap = node.as<AnyMap>();
1145  } catch (YAML::Exception& err) {
1146  AnyMap fake;
1147  fake.setLoc(err.mark.line, err.mark.column);
1148  fake.setMetadata("file-contents", AnyValue(yaml));
1149  throw InputFileError("AnyMap::fromYamlString", fake, err.msg);
1150  }
1151  amap.setMetadata("file-contents", AnyValue(yaml));
1152  amap.applyUnits(UnitSystem());
1153  return amap;
1154 }
1155 
1156 AnyMap AnyMap::fromYamlFile(const std::string& name,
1157  const std::string& parent_name)
1158 {
1159  std::string fullName;
1160  // See if a file with this name exists in a path relative to the parent file
1161  size_t islash = parent_name.find_last_of("/\\");
1162  if (islash != npos) {
1163  std::string parent_path = parent_name.substr(0, islash);
1164  if (std::ifstream(parent_path + "/" + name).good()) {
1165  fullName = parent_path + "/" + name;
1166  }
1167  }
1168  // Otherwise, search the Cantera include path for the file
1169  if (fullName.empty()) {
1170  fullName = findInputFile(name);
1171  }
1172 
1173  // Check for an already-parsed YAML file with the same last-modified time,
1174  // and return that if possible
1175  int mtime = get_modified_time(fullName);
1176  std::unique_lock<std::mutex> lock(yaml_cache_mutex);
1177  auto iter = s_cache.find(fullName);
1178  if (iter != s_cache.end() && iter->second.second == mtime) {
1179  return iter->second.first;
1180  }
1181 
1182  if (!std::ifstream(fullName).good()) {
1183  throw CanteraError("AnyMap::fromYamlFile", "Input file '{}' not found "
1184  "on the Cantera search path.", name);
1185  }
1186 
1187  // Generate an AnyMap from the YAML file and store it in the cache
1188  auto& cache_item = s_cache[fullName];
1189  cache_item.second = mtime;
1190  try {
1191  YAML::Node node = YAML::LoadFile(fullName);
1192  cache_item.first = node.as<AnyMap>();
1193  cache_item.first.setMetadata("filename", AnyValue(fullName));
1194  cache_item.first.applyUnits(UnitSystem());
1195  } catch (YAML::Exception& err) {
1196  s_cache.erase(fullName);
1197  AnyMap fake;
1198  fake.setLoc(err.mark.line, err.mark.column);
1199  fake.setMetadata("filename", AnyValue(fullName));
1200  throw InputFileError("AnyMap::fromYamlFile", fake, err.msg);
1201  } catch (CanteraError&) {
1202  s_cache.erase(fullName);
1203  throw;
1204  }
1205  cache_item.first["__file__"] = fullName;
1206 
1207  if (cache_item.first.hasKey("deprecated")) {
1208  warn_deprecated(fullName, cache_item.first["deprecated"].asString());
1209  }
1210 
1211  // Return a copy of the AnyMap
1212  return cache_item.first;
1213 }
1214 
1215 AnyMap::Iterator begin(const AnyValue& v) {
1216  return v.as<AnyMap>().begin();
1217 }
1218 
1219 AnyMap::Iterator end(const AnyValue& v) {
1220  return v.as<AnyMap>().end();
1221 }
1222 
1223 namespace {
1224 void formatInputFile(fmt::memory_buffer& b, const shared_ptr<AnyMap>& metadata,
1225  const std::string& filename, int lineno, int column, int lineno2=-1, int column2=-1)
1226 {
1227  if (lineno2 == -1) {
1228  lineno2 = lineno;
1229  column2 = column;
1230  }
1231 
1232  format_to(b, "| Line |\n");
1233  if (!metadata->hasKey("file-contents")) {
1234  std::ifstream infile(findInputFile(filename));
1235  std::stringstream buffer;
1236  buffer << infile.rdbuf();
1237  (*metadata)["file-contents"] = buffer.str();
1238  }
1239  std::string line;
1240  int i = 0;
1241  int lastShown = -1;
1242  std::stringstream contents((*metadata)["file-contents"].asString());
1243  while (std::getline(contents, line)) {
1244  if (i == lineno || i == lineno2) {
1245  format_to(b, "> {: 5d} > {}\n", i+1, line);
1246  format_to(b, "{:>{}}\n", "^", column + 11);
1247  lastShown = i;
1248  } else if ((lineno + 4 > i && lineno < i + 6) ||
1249  (lineno2 + 4 > i && lineno2 < i + 6)) {
1250  if (lastShown >= 0 && i - lastShown > 1) {
1251  format_to(b, "...\n");
1252  }
1253  format_to(b, "| {: 5d} | {}\n", i+1, line);
1254  lastShown = i;
1255  }
1256  i++;
1257  }
1258 }
1259 }
1260 
1261 std::string InputFileError::formatError(const std::string& message,
1262  int lineno, int column,
1263  const shared_ptr<AnyMap>& metadata)
1264 {
1265  if (!metadata) {
1266  return message;
1267  }
1268  std::string filename = metadata->getString("filename", "input string");
1269 
1270  fmt::memory_buffer b;
1271  format_to(b, "Error on line {} of {}:\n{}\n", lineno+1, filename, message);
1272  formatInputFile(b, metadata, filename, lineno, column);
1273  return to_string(b);
1274 }
1275 
1276 std::string InputFileError::formatError2(const std::string& message,
1277  int line1, int column1,
1278  const shared_ptr<AnyMap>& metadata1,
1279  int line2, int column2,
1280  const shared_ptr<AnyMap>& metadata2)
1281 {
1282  if (!metadata1 || !metadata2) {
1283  return message;
1284  }
1285  std::string filename1 = metadata1->getString("filename", "input string");
1286  std::string filename2 = metadata2->getString("filename", "input string");
1287 
1288  fmt::memory_buffer b;
1289  if (filename1 == filename2) {
1290  format_to(b, "Error on lines {} and {} of {}:\n",
1291  std::min(line1, line2) + 1, std::max(line1, line2) + 1,
1292  filename1);
1293  format_to(b, "{}\n", message);
1294  formatInputFile(b, metadata1, filename1, line1, column1, line2, column2);
1295  } else {
1296  format_to(b, "Error on line {} of {} and line {} of {}:\n{}\n",
1297  line1+1, filename1, line2+1, filename2, message);
1298  formatInputFile(b, metadata1, filename1, line1, column1);
1299  format_to(b, "\n");
1300  formatInputFile(b, metadata2, filename2, line2, column2);
1301  }
1302 
1303  return to_string(b);
1304 }
1305 
1306 }
Base class defining common data possessed by both AnyMap and AnyValue objects.
Definition: AnyMap.h:30
const AnyValue & getMetadata(const std::string &key) const
Get a value from the metadata applicable to the AnyMap tree containing this node.
Definition: AnyMap.cpp:289
int m_column
Column where this node occurs in the input file.
Definition: AnyMap.h:49
void setLoc(int line, int column)
For values which are derived from an input file, set the line and column of this value in that file.
Definition: AnyMap.cpp:283
int m_line
Line where this node occurs in the input file.
Definition: AnyMap.h:46
shared_ptr< AnyMap > m_metadata
Metadata relevant to an entire AnyMap tree, such as information about.
Definition: AnyMap.h:53
Defined to allow use with range-based for loops.
Definition: AnyMap.h:452
A map of string keys to values whose type can vary at runtime.
Definition: AnyMap.h:360
AnyValue & operator[](const std::string &key)
Get the value of the item stored in key.
Definition: AnyMap.cpp:942
Iterator begin() const
Defined to allow use with range-based for loops.
Definition: AnyMap.h:474
std::unordered_map< std::string, AnyValue > m_data
The stored data.
Definition: AnyMap.h:514
vector_fp convertVector(const std::string &key, const std::string &units, size_t nMin=npos, size_t nMax=npos) const
Convert a vector of dimensional values.
Definition: AnyMap.cpp:1075
const AnyValue & at(const std::string &key) const
Get the value of the item stored in key.
Definition: AnyMap.cpp:974
double convert(const std::string &key, const std::string &units) const
Convert the item stored by the given key to the units specified in units.
Definition: AnyMap.cpp:1055
UnitSystem m_units
The default units that are used to convert stored values.
Definition: AnyMap.h:517
Iterator end() const
Defined to allow use with range-based for loops.
Definition: AnyMap.h:479
const std::string & getString(const std::string &key, const std::string &default_) const
If key exists, return it as a string, otherwise return default_.
Definition: AnyMap.cpp:1049
bool getBool(const std::string &key, bool default_) const
If key exists, return it as a bool, otherwise return default_.
Definition: AnyMap.cpp:1034
static AnyMap fromYamlFile(const std::string &name, const std::string &parent_name="")
Create an AnyMap from a YAML file.
Definition: AnyMap.cpp:1156
long int getInt(const std::string &key, long int default_) const
If key exists, return it as a long int, otherwise return default_.
Definition: AnyMap.cpp:1044
void erase(const std::string &key)
Erase the value held by key.
Definition: AnyMap.cpp:989
double getDouble(const std::string &key, double default_) const
If key exists, return it as a double, otherwise return default_.
Definition: AnyMap.cpp:1039
void propagateMetadata(shared_ptr< AnyMap > &file)
Propagate metadata to any child elements.
Definition: AnyMap.cpp:1014
std::string keys_str() const
Return a string listing the keys in this AnyMap, e.g.
Definition: AnyMap.cpp:999
static std::unordered_map< std::string, std::pair< AnyMap, int > > s_cache
Cache for previously-parsed input (YAML) files.
Definition: AnyMap.h:522
void clear()
Erase all items in the mapping.
Definition: AnyMap.cpp:994
static AnyMap fromYamlString(const std::string &yaml)
Create an AnyMap from a string containing a YAML document.
Definition: AnyMap.cpp:1140
bool hasKey(const std::string &key) const
Returns true if the map contains an item named key.
Definition: AnyMap.cpp:984
const UnitSystem & units() const
Return the default units that should be used to convert stored values.
Definition: AnyMap.h:492
void applyUnits(const UnitSystem &units)
Use the supplied UnitSystem to set the default units, and recursively process overrides from nodes na...
Definition: AnyMap.cpp:1128
void setMetadata(const std::string &key, const AnyValue &value)
Set a metadata value that applies to this AnyMap and its children.
Definition: AnyMap.cpp:1022
A wrapper for a variable whose type is determined at runtime.
Definition: AnyMap.h:77
AnyValue & operator[](const std::string &key)
If this AnyValue is an AnyMap, return the value stored in key.
Definition: AnyMap.cpp:356
bool hasMapWhere(const std::string &key, const std::string &value) const
Returns true when getMapWhere() would succeed.
Definition: AnyMap.cpp:725
void setKey(const std::string &key)
Set the name of the key storing this value in an AnyMap.
Definition: AnyMap.cpp:370
std::unique_ptr< boost::any > m_value
The held value.
Definition: AnyMap.h:237
const std::string & asString() const
Return the held value, if it is a string.
Definition: AnyMap.cpp:424
bool & asBool()
Return the held value, if it is a bool.
Definition: AnyMap.cpp:512
const std::vector< T > & asVector(size_t nMin=npos, size_t nMax=npos) const
Return the held value, if it is a vector of type T.
Definition: AnyMap.inl.h:73
static std::map< std::string, std::string > s_typenames
Human-readable names for some common types, for use when boost::demangle is not available.
Definition: AnyMap.h:241
long int & asInt()
Return the held value, if it is a long int.
Definition: AnyMap.cpp:544
const std::type_info & type() const
Returns the type of the held value.
Definition: AnyMap.cpp:372
double & asDouble()
Return the held value as a double, if it is a double or a long int.
Definition: AnyMap.cpp:465
bool isScalar() const
Returns true if the held value is a scalar type (e.g.
Definition: AnyMap.cpp:396
std::string type_str() const
Returns a string specifying the type of the held value.
Definition: AnyMap.cpp:392
void propagateMetadata(shared_ptr< AnyMap > &file)
Propagate metadata to any child elements.
Definition: AnyMap.cpp:376
std::string m_key
Key of this value in a parent AnyMap
Definition: AnyMap.h:234
bool is() const
Returns true if the held value is of the specified type.
Definition: AnyMap.inl.h:61
AnyMap & getMapWhere(const std::string &key, const std::string &value, bool create=false)
Treating the value as vector<AnyMap>, return the item where the given key has the specified value.
Definition: AnyMap.cpp:675
bool hasKey(const std::string &key) const
Returns true if this AnyValue is an AnyMap and that map contains a key with the given name.
Definition: AnyMap.cpp:366
std::map< std::string, T > asMap() const
Return the held AnyMap as a std::map where all of the values have the specified type.
Definition: AnyMap.inl.h:126
void applyUnits(const UnitSystem &units)
Definition: AnyMap.cpp:748
const T & as() const
Get the value of this key as the specified type.
Definition: AnyMap.inl.h:17
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:61
Error thrown for problems processing information contained in an AnyMap or AnyValue.
Definition: AnyMap.h:539
An error indicating that an unimplemented function has been called.
Definition: ctexceptions.h:187
Unit conversion utility.
Definition: Units.h:101
double convert(double value, const std::string &src, const std::string &dest) const
Convert value from the units of src to the units of dest.
Definition: Units.cpp:322
void setDefaults(std::initializer_list< std::string > units)
Set the default units to convert from when explicit units are not provided.
Definition: Units.cpp:241
A representation of the units associated with a dimensional quantity.
Definition: Units.h:30
std::string findInputFile(const std::string &name)
Find an input file.
Definition: global.cpp:135
void warn_deprecated(const std::string &method, const std::string &extra)
Print a warning indicating that method is deprecated.
Definition: global.cpp:54
const size_t npos
index returned by functions to indicate "no position"
Definition: ct_defs.h:188
std::vector< double > vector_fp
Turn on the use of stl vectors for the basic array type within cantera Vector of doubles.
Definition: ct_defs.h:180
Namespace for the Cantera kernel.
Definition: AnyMap.cpp:264
doublereal fpValue(const std::string &val)
Translate a string into one doublereal value.
Contains declarations for string manipulation functions within Cantera.