From bb0edcb62a0bea1fa7e2fcf8f064a387aca36c35 Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Thu, 16 Jun 2011 10:14:52 -0600 Subject: [PATCH] Add exception_specification and unittests for it. #6 --- include/chaiscript/chaiscript.hpp | 33 +++- .../dispatchkit/exception_specification.hpp | 176 ++++++++++++++++++ .../chaiscript/language/chaiscript_engine.hpp | 61 +++++- .../chaiscript/language/chaiscript_parser.hpp | 2 +- unittests/eval_catch_exception_test.cpp | 105 ++++++++++- 5 files changed, 362 insertions(+), 15 deletions(-) create mode 100644 include/chaiscript/dispatchkit/exception_specification.hpp diff --git a/include/chaiscript/chaiscript.hpp b/include/chaiscript/chaiscript.hpp index bc5762d..0f17284 100644 --- a/include/chaiscript/chaiscript.hpp +++ b/include/chaiscript/chaiscript.hpp @@ -140,7 +140,7 @@ /// /// \subsubsection addingobjects Adding Objects /// -/// Named objects can be created with the #var function. +/// Named objects can be created with the chaiscript::var function. /// /// \code /// using namespace chaiscript; @@ -300,7 +300,7 @@ /// As much as possible, ChaiScript attempts to convert between &, *, const &, const *, boost::shared_ptr, /// boost::shared_ptr, boost::reference_wrapper, boost::reference_wrapper and value types automatically. /// -/// If a var object was created in C++ from a pointer, it cannot be convered to a shared_ptr (this would add invalid reference counting). +/// If a chaiscript::var object was created in C++ from a pointer, it cannot be convered to a shared_ptr (this would add invalid reference counting). /// Const may be added, but never removed. /// /// The take away is that you can pretty much expect function calls to Just Work when you need them to. @@ -413,6 +413,8 @@ /// /// \subsection exceptions Exception Handling /// +/// \subsubsection exceptionsbasics Exception Handling Basics +/// /// Exceptions can be thrown in ChaiScript and caught in C++ or thrown in C++ and caught in /// ChaiScript. /// @@ -424,10 +426,12 @@ /// /// int main() /// { +/// // Throw in C++, catch in ChaiScript /// chaiscript::ChaiScript chai; /// chai.add(chaiscript::fun(&throwexception), "throwexception"); /// chai("try { throwexception(); } catch (e) { print(e.what()); }"); // prints "err" /// +/// // Throw in ChaiScript, catch in C++ /// try { /// chai("throw(1)"); /// } catch (chaiscript::Boxed_Value bv) { @@ -436,6 +440,31 @@ /// } /// } /// \endcode +/// +/// \subsubsection exceptionsautomatic Exception Handling Automatic Unboxing +/// +/// As an alternative to the manual unboxing of exceptions shown above, exception specifications allow the user to tell +/// ChaiScript what possible exceptions are expected from the script being executed. +/// +/// Example: +/// \code +/// chaiscript::ChaiScript chai; +/// +/// try { +/// chai.eval("throw(runtime_error(\"error\"))", chaiscript::exception_specification()); +/// } catch (const double e) { +/// } catch (int) { +/// } catch (float) { +/// } catch (const std::string &) { +/// } catch (const std::exception &e) { +/// // This is the one what will be called in the specific throw() above +/// } +/// \endcode +/// +/// \sa chaiscript::Exception_Handler for details on automatic exception unboxing +/// \sa chaiscript::exception_specification + + /// \page LangObjectSystemRef ChaiScript Language Object Model Reference /// diff --git a/include/chaiscript/dispatchkit/exception_specification.hpp b/include/chaiscript/dispatchkit/exception_specification.hpp new file mode 100644 index 0000000..4c0d19a --- /dev/null +++ b/include/chaiscript/dispatchkit/exception_specification.hpp @@ -0,0 +1,176 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2011, Jonathan Turner (jonathan@emptycrate.com) +// and Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_EXCEPTION_SPECIFICATION_HPP_ +#define CHAISCRIPT_EXCEPTION_SPECIFICATION_HPP_ + +#include "boxed_cast.hpp" + +namespace chaiscript +{ + namespace detail + { + struct Exception_Handler_Base + { + virtual void handle(const Boxed_Value &bv) = 0; + + protected: + template + void throw_type(const Boxed_Value &bv) + { + try { T t = boxed_cast(bv); throw t; } catch (const exception::bad_boxed_cast &) {} + } + }; + + template + struct Exception_Handler_Impl1 : Exception_Handler_Base + { + virtual void handle(const Boxed_Value &bv) + { + throw_type(bv); + } + }; + template + struct Exception_Handler_Impl2 : Exception_Handler_Base + { + virtual void handle(const Boxed_Value &bv) + { + throw_type(bv); + throw_type(bv); + } + }; + + template + struct Exception_Handler_Impl3 : Exception_Handler_Base + { + virtual void handle(const Boxed_Value &bv) + { + throw_type(bv); + throw_type(bv); + throw_type(bv); + } + }; + template + struct Exception_Handler_Impl4 : Exception_Handler_Base + { + virtual void handle(const Boxed_Value &bv) + { + throw_type(bv); + throw_type(bv); + throw_type(bv); + throw_type(bv); + } + }; + template + struct Exception_Handler_Impl5 : Exception_Handler_Base + { + virtual void handle(const Boxed_Value &bv) + { + throw_type(bv); + throw_type(bv); + throw_type(bv); + throw_type(bv); + throw_type(bv); + } + }; + } + + /// \brief Used in the automatic unboxing of exceptions thrown during script evaluation + /// + /// Exception specifications allow the user to tell ChaiScript what possible exceptions are expected from the script + /// being executed. Exception_Handler objects are created with the chaiscript::exception_specification() function. + /// + /// Example: + /// \code + /// chaiscript::ChaiScript chai; + /// + /// try { + /// chai.eval("throw(runtime_error(\"error\"))", chaiscript::exception_specification()); + /// } catch (const double e) { + /// } catch (int) { + /// } catch (float) { + /// } catch (const std::string &) { + /// } catch (const std::exception &e) { + /// // This is the one what will be called in the specific throw() above + /// } + /// \endcode + /// + /// It is recommended that if catching the generic \c std::exception& type that you specifically catch + /// the chaiscript::exception::eval_error type, so that there is no confusion. + /// + /// \code + /// try { + /// chai.eval("throw(runtime_error(\"error\"))", chaiscript::exception_specification()); + /// } catch (const chaiscript::exception::eval_error &) { + /// // Error in script parsing / execution + /// } catch (const std::exception &e) { + /// // Error explicitly thrown from script + /// } + /// \endcode + /// + /// Similarly, if you are using the ChaiScript::eval form that unboxes the return value, then chaiscript::exception::bad_boxed_cast + /// should be handled as well. + /// + /// \code + /// try { + /// chai.eval("1.0", chaiscript::exception_specification()); + /// } catch (const chaiscript::exception::eval_error &) { + /// // Error in script parsing / execution + /// } catch (const chaiscript::exception::bad_boxed_cast &) { + /// // Error unboxing return value + /// } catch (const std::exception &e) { + /// // Error explicitly thrown from script + /// } + /// \endcode + /// + /// \sa chaiscript::exception_specification for creation of chaiscript::Exception_Handler objects + /// \sa \ref exceptions + typedef boost::shared_ptr Exception_Handler; + + /// \brief creates a chaiscript::Exception_Handler which handles one type of exception unboxing + /// \sa \ref exceptions + template + Exception_Handler exception_specification() + { + return Exception_Handler(new detail::Exception_Handler_Impl1()); + } + + /// \brief creates a chaiscript::Exception_Handler which handles two types of exception unboxing + /// \sa \ref exceptions + template + Exception_Handler exception_specification() + { + return Exception_Handler(new detail::Exception_Handler_Impl2()); + } + + /// \brief creates a chaiscript::Exception_Handler which handles three types of exception unboxing + /// \sa \ref exceptions + template + Exception_Handler exception_specification() + { + return Exception_Handler(new detail::Exception_Handler_Impl3()); + } + + /// \brief creates a chaiscript::Exception_Handler which handles four types of exception unboxing + /// \sa \ref exceptions + template + Exception_Handler exception_specification() + { + return Exception_Handler(new detail::Exception_Handler_Impl4()); + } + + /// \brief creates a chaiscript::Exception_Handler which handles five types of exception unboxing + /// \sa \ref exceptions + template + Exception_Handler exception_specification() + { + return Exception_Handler(new detail::Exception_Handler_Impl5()); + } +} + + +#endif + diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index 6db9804..7fac714 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -25,6 +25,7 @@ #include #include +#include "../dispatchkit/exception_specification.hpp" namespace chaiscript { @@ -598,19 +599,28 @@ namespace chaiscript /// \brief Evaluates a string. Equivalent to ChaiScript::eval. /// /// \param[in] t_script Script to execute + /// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions /// /// \return result of the script execution /// /// \throw exception::eval_error In the case that evaluation fails. - Boxed_Value operator()(const std::string &t_script) + Boxed_Value operator()(const std::string &t_script, const Exception_Handler &t_handler = Exception_Handler()) { - return do_eval(t_script); + try { + return do_eval(t_script); + } catch (Boxed_Value &bv) { + if (t_handler) { + t_handler->handle(bv); + } + throw bv; + } } /// \brief Evaluates a string and returns a typesafe result. /// /// \tparam T Type to extract from the result value of the script execution /// \param[in] t_input Script to execute + /// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions /// /// \return result of the script execution /// @@ -618,41 +628,72 @@ namespace chaiscript /// \throw exception::bad_boxed_cast In the case that evaluation succeeds but the result value cannot be converted /// to the requested type. template - T eval(const std::string &t_input) + T eval(const std::string &t_input, const Exception_Handler &t_handler = Exception_Handler()) { - return boxed_cast(do_eval(t_input)); + try { + return boxed_cast(do_eval(t_input)); + } catch (Boxed_Value &bv) { + if (t_handler) { + t_handler->handle(bv); + } + throw bv; + } } /// \brief Evaluates a string. /// /// \param[in] t_input Script to execute + /// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions /// /// \return result of the script execution /// /// \throw exception::eval_error In the case that evaluation fails. - Boxed_Value eval(const std::string &t_input) + Boxed_Value eval(const std::string &t_input, const Exception_Handler &t_handler = Exception_Handler()) { - return do_eval(t_input); + try { + return do_eval(t_input); + } catch (Boxed_Value &bv) { + if (t_handler) { + t_handler->handle(bv); + } + throw bv; + } } /// \brief Loads the file specified by filename, evaluates it, and returns the result. /// \param[in] t_filename File to load and parse. + /// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions /// \return result of the script execution /// \throw exception::eval_error In the case that evaluation fails. - Boxed_Value eval_file(const std::string &t_filename) { - return do_eval(load_file(t_filename), t_filename); + Boxed_Value eval_file(const std::string &t_filename, const Exception_Handler &t_handler = Exception_Handler()) { + try { + return do_eval(load_file(t_filename), t_filename); + } catch (Boxed_Value &bv) { + if (t_handler) { + t_handler->handle(bv); + } + throw bv; + } } /// \brief Loads the file specified by filename, evaluates it, and returns the typesafe result. /// \tparam T Type to extract from the result value of the script execution /// \param[in] t_filename File to load and parse. + /// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions /// \return result of the script execution /// \throw exception::eval_error In the case that evaluation fails. /// \throw exception::bad_boxed_cast In the case that evaluation succeeds but the result value cannot be converted /// to the requested type. template - T eval_file(const std::string &t_filename) { - return boxed_cast(do_eval(load_file(t_filename), t_filename)); + T eval_file(const std::string &t_filename, const Exception_Handler &t_handler = Exception_Handler()) { + try { + return boxed_cast(do_eval(load_file(t_filename), t_filename)); + } catch (Boxed_Value &bv) { + if (t_handler) { + t_handler->handle(bv); + } + throw bv; + } } }; diff --git a/include/chaiscript/language/chaiscript_parser.hpp b/include/chaiscript/language/chaiscript_parser.hpp index eb3a897..aa58999 100644 --- a/include/chaiscript/language/chaiscript_parser.hpp +++ b/include/chaiscript/language/chaiscript_parser.hpp @@ -1505,7 +1505,7 @@ namespace chaiscript } build_match(AST_NodePtr(new eval::Fun_Call_AST_Node()), prev_stack_top); - //FIXME: Work around for method calls until we have a better solution + /// \todo Work around for method calls until we have a better solution if (!m_match_stack.back()->children.empty()) { if (m_match_stack.back()->children[0]->identifier == AST_Node_Type::Dot_Access) { AST_NodePtr dot_access = m_match_stack.back()->children[0]; diff --git a/unittests/eval_catch_exception_test.cpp b/unittests/eval_catch_exception_test.cpp index 8b08733..c599c09 100644 --- a/unittests/eval_catch_exception_test.cpp +++ b/unittests/eval_catch_exception_test.cpp @@ -2,8 +2,7 @@ #include - -int main() +int test_generic() { chaiscript::ChaiScript chai; @@ -17,5 +16,107 @@ int main() } } + std::cout << "test_generic failed" << std::endl; return EXIT_FAILURE; } + +int test_1() +{ + chaiscript::ChaiScript chai; + + try { + chai.eval("throw(1)", chaiscript::exception_specification()); + } catch (int e) { + if (e == 1) + { + return EXIT_SUCCESS; + } + } + + std::cout << "test_1 failed" << std::endl; + return EXIT_FAILURE; +} + +int test_2() +{ + chaiscript::ChaiScript chai; + + try { + chai.eval("throw(1.0)", chaiscript::exception_specification()); + } catch (const double e) { + if (e == 1.0) + { + return EXIT_SUCCESS; + } + } + + std::cout << "test_2 failed" << std::endl; + return EXIT_FAILURE; +} + +int test_5() +{ + chaiscript::ChaiScript chai; + + try { + chai.eval("throw(runtime_error(\"error\"))", chaiscript::exception_specification()); + } catch (const double e) { + std::cout << "test_5 failed with double" << std::endl; + return EXIT_FAILURE; + } catch (int) { + std::cout << "test_5 failed with int" << std::endl; + return EXIT_FAILURE; + } catch (float) { + std::cout << "test_5 failed with float" << std::endl; + return EXIT_FAILURE; + } catch (const std::string &) { + std::cout << "test_5 failed with string" << std::endl; + return EXIT_FAILURE; + } catch (const std::exception &e) { + return EXIT_SUCCESS; + } + + std::cout << "test_5 failed" << std::endl; + return EXIT_FAILURE; +} + +int test_unhandled() +{ + chaiscript::ChaiScript chai; + + try { + chai.eval("throw(\"error\")", chaiscript::exception_specification()); + } catch (double) { + std::cout << "test_unhandled failed with double" << std::endl; + return EXIT_FAILURE; + } catch (int) { + std::cout << "test_unhandled failed with int" << std::endl; + return EXIT_FAILURE; + } catch (float) { + std::cout << "test_unhandled failed with float" << std::endl; + return EXIT_FAILURE; + } catch (const std::exception &e) { + std::cout << "test_unhandled failed with std::exception" << std::endl; + return EXIT_FAILURE; + } catch (const chaiscript::Boxed_Value &bv) { + return EXIT_SUCCESS; + } + + std::cout << "test_unhandled failed" << std::endl; + return EXIT_FAILURE; +} + + +int main() +{ + if (test_generic() == EXIT_SUCCESS + && test_1() == EXIT_SUCCESS + && test_2() == EXIT_SUCCESS + && test_5() == EXIT_SUCCESS + && test_unhandled() == EXIT_SUCCESS) + { + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } +}