ChaiScript
type_conversions.hpp
1 // This file is distributed under the BSD License.
2 // See "license.txt" for details.
3 // Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com)
4 // Copyright 2009-2018, Jason Turner (jason@emptycrate.com)
5 // http://www.chaiscript.com
6 
7 // This is an open source non-commercial project. Dear PVS-Studio, please check it.
8 // PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
9 
10 #ifndef CHAISCRIPT_DYNAMIC_CAST_CONVERSION_HPP_
11 #define CHAISCRIPT_DYNAMIC_CAST_CONVERSION_HPP_
12 
13 #include <algorithm>
14 #include <atomic>
15 #include <memory>
16 #include <set>
17 #include <stdexcept>
18 #include <string>
19 #include <type_traits>
20 #include <typeinfo>
21 
22 #include "../chaiscript_threading.hpp"
23 #include "../utility/static_string.hpp"
24 #include "bad_boxed_cast.hpp"
25 #include "boxed_cast_helper.hpp"
26 #include "boxed_value.hpp"
27 #include "type_info.hpp"
28 
29 namespace chaiscript {
30  namespace exception {
33  public:
34  conversion_error(const Type_Info t_to, const Type_Info t_from, const utility::Static_String what) noexcept
35  : bad_boxed_cast(t_from, (*t_to.bare_type_info()), what)
36  , type_to(t_to) {
37  }
38 
39  Type_Info type_to;
40  };
41 
43  public:
44  bad_boxed_dynamic_cast(const Type_Info &t_from, const std::type_info &t_to, const utility::Static_String &t_what) noexcept
45  : bad_boxed_cast(t_from, t_to, t_what) {
46  }
47 
48  bad_boxed_dynamic_cast(const Type_Info &t_from, const std::type_info &t_to) noexcept
49  : bad_boxed_cast(t_from, t_to) {
50  }
51 
52  explicit bad_boxed_dynamic_cast(const utility::Static_String &w) noexcept
53  : bad_boxed_cast(w) {
54  }
55 
57 
58  ~bad_boxed_dynamic_cast() noexcept override = default;
59  };
60 
62  public:
63  bad_boxed_type_cast(const Type_Info &t_from, const std::type_info &t_to, const utility::Static_String &t_what) noexcept
64  : bad_boxed_cast(t_from, t_to, t_what) {
65  }
66 
67  bad_boxed_type_cast(const Type_Info &t_from, const std::type_info &t_to) noexcept
68  : bad_boxed_cast(t_from, t_to) {
69  }
70 
71  explicit bad_boxed_type_cast(const utility::Static_String &w) noexcept
72  : bad_boxed_cast(w) {
73  }
74 
75  bad_boxed_type_cast(const bad_boxed_type_cast &) = default;
76 
77  ~bad_boxed_type_cast() noexcept override = default;
78  };
79  } // namespace exception
80 
81  namespace detail {
83  public:
84  virtual Boxed_Value convert(const Boxed_Value &from) const = 0;
85  virtual Boxed_Value convert_down(const Boxed_Value &to) const = 0;
86 
87  const Type_Info &to() const noexcept { return m_to; }
88  const Type_Info &from() const noexcept { return m_from; }
89 
90  virtual bool bidir() const noexcept { return true; }
91 
92  virtual ~Type_Conversion_Base() = default;
93 
94  protected:
96  : m_to(std::move(t_to))
97  , m_from(std::move(t_from)) {
98  }
99 
100  private:
101  const Type_Info m_to;
102  const Type_Info m_from;
103  };
104 
105  template<typename From, typename To>
107  public:
108  static Boxed_Value cast(const Boxed_Value &t_from) {
109  if (t_from.get_type_info().bare_equal(chaiscript::user_type<From>())) {
110  if (t_from.is_pointer()) {
111  // Dynamic cast out the contained boxed value, which we know is the type we want
112  if (t_from.is_const()) {
113  return Boxed_Value([&]() {
114  if (auto data
115  = std::static_pointer_cast<const To>(detail::Cast_Helper<std::shared_ptr<const From>>::cast(t_from, nullptr))) {
116  return data;
117  } else {
118  throw std::bad_cast();
119  }
120  }());
121  } else {
122  return Boxed_Value([&]() {
123  if (auto data = std::static_pointer_cast<To>(detail::Cast_Helper<std::shared_ptr<From>>::cast(t_from, nullptr))) {
124  return data;
125  } else {
126  throw std::bad_cast();
127  }
128  }());
129  }
130  } else {
131  // Pull the reference out of the contained boxed value, which we know is the type we want
132  if (t_from.is_const()) {
133  const From &d = detail::Cast_Helper<const From &>::cast(t_from, nullptr);
134  const To &data = static_cast<const To &>(d);
135  return Boxed_Value(std::cref(data));
136  } else {
137  From &d = detail::Cast_Helper<From &>::cast(t_from, nullptr);
138  To &data = static_cast<To &>(d);
139  return Boxed_Value(std::ref(data));
140  }
141  }
142  } else {
143  throw chaiscript::exception::bad_boxed_dynamic_cast(t_from.get_type_info(), typeid(To), "Unknown dynamic_cast_conversion");
144  }
145  }
146  };
147 
148  template<typename From, typename To>
150  public:
151  static Boxed_Value cast(const Boxed_Value &t_from) {
152  if (t_from.get_type_info().bare_equal(chaiscript::user_type<From>())) {
153  if (t_from.is_pointer()) {
154  // Dynamic cast out the contained boxed value, which we know is the type we want
155  if (t_from.is_const()) {
156  return Boxed_Value([&]() {
157  if (auto data
158  = std::dynamic_pointer_cast<const To>(detail::Cast_Helper<std::shared_ptr<const From>>::cast(t_from, nullptr))) {
159  return data;
160  } else {
161  throw std::bad_cast();
162  }
163  }());
164  } else {
165  return Boxed_Value([&]() {
166  if (auto data = std::dynamic_pointer_cast<To>(detail::Cast_Helper<std::shared_ptr<From>>::cast(t_from, nullptr))) {
167  return data;
168  } else {
169 #ifdef CHAISCRIPT_LIBCPP
170  if (std::string(typeid(To).name()).find("Assignable_Proxy_Function") != std::string::npos) {
172  auto from = detail::Cast_Helper<std::shared_ptr<From>>::cast(t_from, nullptr);
173  if (std::string(typeid(*from).name()).find("Assignable_Proxy_Function_Impl") != std::string::npos) {
174  return std::static_pointer_cast<To>(from);
175  }
176  }
177 #endif
178  throw std::bad_cast();
179  }
180  }());
181  }
182  } else {
183  // Pull the reference out of the contained boxed value, which we know is the type we want
184  if (t_from.is_const()) {
185  const From &d = detail::Cast_Helper<const From &>::cast(t_from, nullptr);
186  const To &data = dynamic_cast<const To &>(d);
187  return Boxed_Value(std::cref(data));
188  } else {
189  From &d = detail::Cast_Helper<From &>::cast(t_from, nullptr);
190  To &data = dynamic_cast<To &>(d);
191  return Boxed_Value(std::ref(data));
192  }
193  }
194  } else {
195  throw chaiscript::exception::bad_boxed_dynamic_cast(t_from.get_type_info(), typeid(To), "Unknown dynamic_cast_conversion");
196  }
197  }
198  };
199 
200  template<typename Base, typename Derived>
202  public:
204  : Type_Conversion_Base(chaiscript::user_type<Base>(), chaiscript::user_type<Derived>()) {
205  }
206 
207  Boxed_Value convert_down(const Boxed_Value &t_base) const override { return Dynamic_Caster<Base, Derived>::cast(t_base); }
208 
209  Boxed_Value convert(const Boxed_Value &t_derived) const override { return Static_Caster<Derived, Base>::cast(t_derived); }
210  };
211 
212  template<typename Base, typename Derived>
214  public:
216  : Type_Conversion_Base(chaiscript::user_type<Base>(), chaiscript::user_type<Derived>()) {
217  }
218 
219  Boxed_Value convert_down(const Boxed_Value &t_base) const override {
220  throw chaiscript::exception::bad_boxed_dynamic_cast(t_base.get_type_info(),
221  typeid(Derived),
222  "Unable to cast down inheritance hierarchy with non-polymorphic types");
223  }
224 
225  bool bidir() const noexcept override { return false; }
226 
227  Boxed_Value convert(const Boxed_Value &t_derived) const override { return Static_Caster<Derived, Base>::cast(t_derived); }
228  };
229 
230  template<typename Callable>
232  public:
233  Type_Conversion_Impl(Type_Info t_from, Type_Info t_to, Callable t_func)
234  : Type_Conversion_Base(t_to, t_from)
235  , m_func(std::move(t_func)) {
236  }
237 
238  Boxed_Value convert_down(const Boxed_Value &) const override {
239  throw chaiscript::exception::bad_boxed_type_cast("No conversion exists");
240  }
241 
242  Boxed_Value convert(const Boxed_Value &t_from) const override {
244  return m_func(t_from);
245  }
246 
247  bool bidir() const noexcept override { return false; }
248 
249  private:
250  Callable m_func;
251  };
252  } // namespace detail
253 
255  public:
257  bool enabled = false;
258  std::vector<Boxed_Value> saves;
259  };
260 
261  struct Less_Than {
262  bool operator()(const std::type_info *t_lhs, const std::type_info *t_rhs) const noexcept {
263  return *t_lhs != *t_rhs && t_lhs->before(*t_rhs);
264  }
265  };
266 
268  : m_mutex()
269  , m_conversions()
270  , m_convertableTypes()
271  , m_num_types(0) {
272  }
273 
274  Type_Conversions(const Type_Conversions &t_other) = delete;
275  Type_Conversions(Type_Conversions &&) = delete;
276 
277  Type_Conversions &operator=(const Type_Conversions &) = delete;
278  Type_Conversions &operator=(Type_Conversions &&) = delete;
279 
280  const std::set<const std::type_info *, Less_Than> &thread_cache() const {
281  auto &cache = *m_thread_cache;
282  if (cache.size() != m_num_types) {
283  chaiscript::detail::threading::shared_lock<chaiscript::detail::threading::shared_mutex> l(m_mutex);
284  cache = m_convertableTypes;
285  }
286 
287  return cache;
288  }
289 
290  void add_conversion(const std::shared_ptr<detail::Type_Conversion_Base> &conversion) {
291  chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::shared_mutex> l(m_mutex);
292  if (find_bidir(conversion->to(), conversion->from()) != m_conversions.end()) {
293  throw exception::conversion_error(conversion->to(), conversion->from(), "Trying to re-insert an existing conversion!");
294  }
295  m_conversions.insert(conversion);
296  m_convertableTypes.insert({conversion->to().bare_type_info(), conversion->from().bare_type_info()});
297  m_num_types = m_convertableTypes.size();
298  }
299 
300  template<typename T>
301  bool convertable_type() const noexcept {
302  const auto type = user_type<T>().bare_type_info();
303  return thread_cache().count(type) != 0;
304  }
305 
306  template<typename To, typename From>
307  bool converts() const noexcept {
308  return converts(user_type<To>(), user_type<From>());
309  }
310 
311  bool converts(const Type_Info &to, const Type_Info &from) const noexcept {
312  const auto &types = thread_cache();
313  if (types.count(to.bare_type_info()) != 0 && types.count(from.bare_type_info()) != 0) {
314  return has_conversion(to, from);
315  } else {
316  return false;
317  }
318  }
319 
320  template<typename To>
321  Boxed_Value boxed_type_conversion(Conversion_Saves &t_saves, const Boxed_Value &from) const {
322  return boxed_type_conversion(user_type<To>(), t_saves, from);
323  }
324 
325  template<typename From>
326  Boxed_Value boxed_type_down_conversion(Conversion_Saves &t_saves, const Boxed_Value &to) const {
327  return boxed_type_down_conversion(user_type<From>(), t_saves, to);
328  }
329 
330  Boxed_Value boxed_type_conversion(const Type_Info &to, Conversion_Saves &t_saves, const Boxed_Value &from) const {
331  try {
332  Boxed_Value ret = get_conversion(to, from.get_type_info())->convert(from);
333  if (t_saves.enabled) {
334  t_saves.saves.push_back(ret);
335  }
336  return ret;
337  } catch (const std::out_of_range &) {
338  throw exception::bad_boxed_dynamic_cast(from.get_type_info(), *to.bare_type_info(), "No known conversion");
339  } catch (const std::bad_cast &) {
340  throw exception::bad_boxed_dynamic_cast(from.get_type_info(), *to.bare_type_info(), "Unable to perform dynamic_cast operation");
341  }
342  }
343 
344  Boxed_Value boxed_type_down_conversion(const Type_Info &from, Conversion_Saves &t_saves, const Boxed_Value &to) const {
345  try {
346  Boxed_Value ret = get_conversion(to.get_type_info(), from)->convert_down(to);
347  if (t_saves.enabled) {
348  t_saves.saves.push_back(ret);
349  }
350  return ret;
351  } catch (const std::out_of_range &) {
352  throw exception::bad_boxed_dynamic_cast(to.get_type_info(), *from.bare_type_info(), "No known conversion");
353  } catch (const std::bad_cast &) {
354  throw exception::bad_boxed_dynamic_cast(to.get_type_info(), *from.bare_type_info(), "Unable to perform dynamic_cast operation");
355  }
356  }
357 
358  static void enable_conversion_saves(Conversion_Saves &t_saves, bool t_val) { t_saves.enabled = t_val; }
359 
360  std::vector<Boxed_Value> take_saves(Conversion_Saves &t_saves) {
361  std::vector<Boxed_Value> ret;
362  std::swap(ret, t_saves.saves);
363  return ret;
364  }
365 
366  bool has_conversion(const Type_Info &to, const Type_Info &from) const {
367  chaiscript::detail::threading::shared_lock<chaiscript::detail::threading::shared_mutex> l(m_mutex);
368  return find_bidir(to, from) != m_conversions.end();
369  }
370 
371  std::shared_ptr<detail::Type_Conversion_Base> get_conversion(const Type_Info &to, const Type_Info &from) const {
372  chaiscript::detail::threading::shared_lock<chaiscript::detail::threading::shared_mutex> l(m_mutex);
373 
374  const auto itr = find(to, from);
375 
376  if (itr != m_conversions.end()) {
377  return *itr;
378  } else {
379  throw std::out_of_range(std::string("No such conversion exists from ") + from.bare_name() + " to " + to.bare_name());
380  }
381  }
382 
383  Conversion_Saves &conversion_saves() const noexcept { return *m_conversion_saves; }
384 
385  private:
386  std::set<std::shared_ptr<detail::Type_Conversion_Base>>::const_iterator find_bidir(const Type_Info &to, const Type_Info &from) const {
387  return std::find_if(m_conversions.begin(),
388  m_conversions.end(),
389  [&to, &from](const std::shared_ptr<detail::Type_Conversion_Base> &conversion) -> bool {
390  return (conversion->to().bare_equal(to) && conversion->from().bare_equal(from))
391  || (conversion->bidir() && conversion->from().bare_equal(to) && conversion->to().bare_equal(from));
392  });
393  }
394 
395  std::set<std::shared_ptr<detail::Type_Conversion_Base>>::const_iterator find(const Type_Info &to, const Type_Info &from) const {
396  return std::find_if(m_conversions.begin(),
397  m_conversions.end(),
398  [&to, &from](const std::shared_ptr<detail::Type_Conversion_Base> &conversion) {
399  return conversion->to().bare_equal(to) && conversion->from().bare_equal(from);
400  });
401  }
402 
403  std::set<std::shared_ptr<detail::Type_Conversion_Base>> get_conversions() const {
404  chaiscript::detail::threading::shared_lock<chaiscript::detail::threading::shared_mutex> l(m_mutex);
405 
406  return m_conversions;
407  }
408 
409  mutable chaiscript::detail::threading::shared_mutex m_mutex;
410  std::set<std::shared_ptr<detail::Type_Conversion_Base>> m_conversions;
411  std::set<const std::type_info *, Less_Than> m_convertableTypes;
412  std::atomic_size_t m_num_types;
415  };
416 
418  public:
420  : m_conversions(t_conversions)
421  , m_saves(t_saves) {
422  }
423 
424  const Type_Conversions *operator->() const noexcept { return &m_conversions.get(); }
425 
426  const Type_Conversions *get() const noexcept { return &m_conversions.get(); }
427 
428  Type_Conversions::Conversion_Saves &saves() const noexcept { return m_saves; }
429 
430  private:
431  std::reference_wrapper<const Type_Conversions> m_conversions;
432  std::reference_wrapper<Type_Conversions::Conversion_Saves> m_saves;
433  };
434 
435  using Type_Conversion = std::shared_ptr<chaiscript::detail::Type_Conversion_Base>;
436 
458  template<typename Base, typename Derived>
459  Type_Conversion base_class() {
460  // Can only be used with related polymorphic types
461  // may be expanded some day to support conversions other than child -> parent
462  static_assert(std::is_base_of<Base, Derived>::value, "Classes are not related by inheritance");
463 
464  if constexpr (std::is_polymorphic<Base>::value && std::is_polymorphic<Derived>::value) {
465  return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Dynamic_Conversion_Impl<Base, Derived>>();
466  } else {
467  return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Static_Conversion_Impl<Base, Derived>>();
468  }
469  }
470 
471  template<typename Callable>
472  Type_Conversion type_conversion(const Type_Info &t_from, const Type_Info &t_to, const Callable &t_func) {
473  return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Type_Conversion_Impl<Callable>>(t_from, t_to, t_func);
474  }
475 
476  template<typename From, typename To, typename Callable>
477  Type_Conversion type_conversion(const Callable &t_function) {
478  auto func = [t_function](const Boxed_Value &t_bv) -> Boxed_Value {
479  // not even attempting to call boxed_cast so that we don't get caught in some call recursion
480  return chaiscript::Boxed_Value(t_function(detail::Cast_Helper<const From &>::cast(t_bv, nullptr)));
481  };
482 
483  return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Type_Conversion_Impl<decltype(func)>>(user_type<From>(),
484  user_type<To>(),
485  func);
486  }
487 
488  template<typename From, typename To>
489  Type_Conversion type_conversion() {
490  static_assert(std::is_convertible<From, To>::value, "Types are not automatically convertible");
491  auto func = [](const Boxed_Value &t_bv) -> Boxed_Value {
492  // not even attempting to call boxed_cast so that we don't get caught in some call recursion
493  return chaiscript::Boxed_Value(To(detail::Cast_Helper<From>::cast(t_bv, nullptr)));
494  };
495 
496  return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Type_Conversion_Impl<decltype(func)>>(user_type<From>(),
497  user_type<To>(),
498  func);
499  }
500 
501  template<typename To>
502  Type_Conversion vector_conversion() {
503  auto func = [](const Boxed_Value &t_bv) -> Boxed_Value {
504  const std::vector<Boxed_Value> &from_vec = detail::Cast_Helper<const std::vector<Boxed_Value> &>::cast(t_bv, nullptr);
505 
506  To vec;
507  vec.reserve(from_vec.size());
508  for (const Boxed_Value &bv : from_vec) {
509  vec.push_back(detail::Cast_Helper<typename To::value_type>::cast(bv, nullptr));
510  }
511 
512  return Boxed_Value(std::move(vec));
513  };
514 
515  return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Type_Conversion_Impl<decltype(func)>>(user_type<std::vector<Boxed_Value>>(),
516  user_type<To>(),
517  func);
518  }
519 
520  template<typename To>
521  Type_Conversion map_conversion() {
522  auto func = [](const Boxed_Value &t_bv) -> Boxed_Value {
523  const std::map<std::string, Boxed_Value> &from_map
525 
526  To map;
527  for (const std::pair<const std::string, Boxed_Value> &p : from_map) {
528  map.insert(std::make_pair(p.first, detail::Cast_Helper<typename To::mapped_type>::cast(p.second, nullptr)));
529  }
530 
531  return Boxed_Value(std::move(map));
532  };
533 
534  return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Type_Conversion_Impl<decltype(func)>>(
535  user_type<std::map<std::string, Boxed_Value>>(), user_type<To>(), func);
536  }
537 } // namespace chaiscript
538 
539 #endif
Definition: type_conversions.hpp:82
Compile time deduced information about a type.
Definition: type_info.hpp:27
Type_Conversion base_class()
Used to register a to / parent class relationship with ChaiScript.
Definition: type_conversions.hpp:459
Definition: type_conversions.hpp:417
Namespace chaiscript contains every API call that the average user will be concerned with...
Error thrown when there&#39;s a problem with type conversion.
Definition: type_conversions.hpp:32
Definition: type_conversions.hpp:261
Definition: type_conversions.hpp:201
Definition: type_conversions.hpp:149
A wrapper for holding any valid C++ type.
Definition: boxed_value.hpp:24
Definition: type_conversions.hpp:213
Thrown in the event that a Boxed_Value cannot be cast to the desired type.
Definition: bad_boxed_cast.hpp:31
Type_Info from
Type_Info contained in the Boxed_Value.
Definition: bad_boxed_cast.hpp:55
Definition: type_conversions.hpp:231
Typesafe thread specific storage.
Definition: chaiscript_threading.hpp:59
Definition: static_string.hpp:11
Definition: type_conversions.hpp:42
const char * what() const noexcept override
Description of what error occurred.
Definition: bad_boxed_cast.hpp:53
Definition: type_conversions.hpp:106
Definition: type_conversions.hpp:256
Boxed_Value convert(const Boxed_Value &t_from) const override
Definition: type_conversions.hpp:242
const std::type_info * to
std::type_info of the desired (but failed) result type
Definition: bad_boxed_cast.hpp:56
Definition: type_conversions.hpp:254
The exposed Cast_Helper object that by default just calls the Cast_Helper_Inner.
Definition: boxed_cast_helper.hpp:251
Definition: type_conversions.hpp:61