diff --git a/chaiscript/chaiscript.hpp b/chaiscript/chaiscript.hpp new file mode 100644 index 0000000..dbf6d73 --- /dev/null +++ b/chaiscript/chaiscript.hpp @@ -0,0 +1,845 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_HPP_ +#define CHAISCRIPT_HPP_ + + + +/// @mainpage +/// [ChaiScript](http://www.chaiscript.com") is a scripting language designed specifically for integration with C++. It provides +/// seamless integration with C++ on all levels, including shared_ptr objects, functors and exceptions. +/// +/// The parts of the ChaiScript API that the average user will be concerned with are contained in the +/// chaiscript namespace and the chaiscript::ChaiScript class. +/// +/// The end user parts of the API are extremely simple both in size and ease of use. +/// +/// Currently, all source control and project management aspects of ChaiScript occur on [github](http://www.github.com/ChaiScript/ChaiScript"). +/// +/// ------------------------------------------------------------ +/// +/// @sa chaiscript +/// @sa chaiscript::ChaiScript +/// @sa ChaiScript_Language for Built in Functions +/// @sa @ref LangGettingStarted +/// @sa @ref LangKeywordRef +/// @sa @ref LangInPlaceRef +/// @sa @ref LangObjectSystemRef +/// @sa http://www.chaiscript.com +/// @sa http://www.github.com/ChaiScript/ChaiScript +/// +/// ----------------------------------------------------------- +/// +/// @section gettingstarted API Getting Started +/// +/// - @ref basics +/// - @ref compiling +/// - @ref eval +/// - @ref adding_items +/// - @ref operatoroverloading +/// - @ref add_class +/// - @ref pointer_conversions +/// - @ref baseclasses +/// - @ref functionobjects +/// - @ref threading +/// - @ref exceptions +/// +/// +/// @subsection basics Basics +/// +/// Basic simple example: +/// +/// ~~~~~~~{.cpp} +/// //main.cpp +/// #include +/// +/// double function(int i, double j) +/// { +/// return i * j; +/// } +/// +/// int main() +/// { +/// chaiscript::ChaiScript chai; +/// chai.add(chaiscript::fun(&function), "function"); +/// +/// double d = chai.eval("function(3, 4.75);"); +/// } +/// ~~~~~~~ +/// +/// ------------------------------------------------------ +/// +/// @subsection compiling Compiling ChaiScript Applications +/// +/// ChaiScript is a header only library with only one dependency: The +/// operating system provided dynamic library loader, which has to be specified on some platforms. +/// +/// @subsubsection compilinggcc Compiling with GCC +/// +/// To compile the above application on a Unix like operating system (MacOS, Linux) with GCC you need to link +/// the dynamic loader. For example: +/// +/// ~~~~~~~~ +/// gcc main.cpp -I/path/to/chaiscript/headers -ldl +/// ~~~~~~~~ +/// +/// Alternatively, you may compile without threading support. +/// +/// ~~~~~~~~ +/// gcc main.cpp -I/path/to/chaiscript/headers -ldl -DCHAISCRIPT_NO_THREADS +/// ~~~~~~~~ +/// +/// ------------------------------------------ +/// +/// @subsection eval Evaluating Scripts +/// +/// Scripts can be evaluated with the () operator, eval method or eval_file method. +/// +/// @subsubsection parenoperator () Operator +/// +/// operator() can be used as a handy shortcut for evaluating ChaiScript snippets. +/// +/// ~~~~~~~~{.cpp} +/// chaiscript::ChaiScript chai; +/// chai("print(@"hello world@")"); +/// ~~~~~~~~ +/// +/// @sa chaiscript::ChaiScript::operator()(const std::string &) +/// +/// @subsubsection evalmethod Method 'eval' +/// +/// The eval method is somewhat more verbose and can be used to get type safely return values +/// from the script. +/// +/// ~~~~~~~~{.cpp} +/// chaiscript::ChaiScript chai; +/// chai.eval("callsomefunc()"); +/// int result = chai.eval("1 + 3"); +/// // result now equals 4 +/// ~~~~~~~~ +/// +/// @sa chaiscript::ChaiScript::eval +/// +/// @subsubsection evalfilemethod Method 'eval_file' +/// +/// The 'eval_file' method loads a file from disk and executes the script in it +/// +/// ~~~~~~~~{.cpp} +/// chaiscript::ChaiScript chai; +/// chai.eval_file("myfile.chai"); +/// std::string result = chai.eval_file("myfile.chai") // extract the last value returned from the file +/// ~~~~~~~~ +/// +/// @sa chaiscript::ChaiScript::eval_file +/// +/// -------------------------------------------------- +/// +/// @subsection adding_items Adding Items to ChaiScript +/// +/// ChaiScript supports 4 basic things that can be added: objects, functions, type infos and Modules +/// +/// @subsubsection adding_objects Adding Objects +/// +/// Named objects can be created with the chaiscript::var function. Note: adding a object +/// adds it to the current thread scope, not to a global scope. If you have multiple +/// threads that need to access the same variables you will need to add them +/// separately for each thread, from the thread itself. +/// +/// ~~~~~~~~~{.cpp} +/// using namespace chaiscript; +/// ChaiScript chai; +/// int i = 5; +/// chai.add(var(i), "i"); +/// chai("print(i)"); +/// ~~~~~~~~~ +/// +/// Immutable objects can be created with the chaiscript::const_var function. +/// +/// ~~~~~~~~~{.cpp} +/// chai.add(const_var(i), "i"); +/// chai("i = 5"); // exception throw, cannot assign const var +/// ~~~~~~~~~ +/// +/// Named variables can only be accessed from the context they are created in. +/// If you want a global variable, it must be const, and created with the +/// chaiscript::ChaiScript::add_global_const function. +/// +/// ~~~~~~~~~{.cpp} +/// chai.add_global_const(const_var(i), "i"); +/// chai("def somefun() { print(i); }; somefun();"); +/// ~~~~~~~~~ +/// +/// @subsubsection adding_functions Adding Functions +/// +/// Functions, methods and members are all added using the same function: chaiscript::fun. +/// +/// ~~~~~~~~~{.cpp} +/// using namespace chaiscript; +/// +/// class MyClass { +/// public: +/// int memberdata; +/// void method(); +/// void method2(int); +/// static void staticmethod(); +/// void overloadedmethod(); +/// void overloadedmethod(const std::string &); +/// }; +/// +/// ChaiScript chai; +/// chai.add(fun(&MyClass::memberdata), "memberdata"); +/// chai.add(fun(&MyClass::method), "method"); +/// chai.add(fun(&MyClass::staticmethod), "staticmethod"); +/// ~~~~~~~~~ +/// +/// Overloaded methods will need some help, to hint the compiler as to which overload you want: +/// +/// ~~~~~~~~~{.cpp} +/// chai.add(fun(&MyClass::overloadedmethod), "overloadedmethod"); +/// chai.add(fun(&MyClass::overloadedmethod), "overloadedmethod"); +/// ~~~~~~~~~ +/// +/// There are also shortcuts built into chaiscript::fun for binding up to the first two parameters of the function. +/// +/// ~~~~~~~~~{.cpp} +/// MyClass obj; +/// chai.add(fun(&MyClass::method, &obj), "method"); +/// chai("method()"); // equiv to obj.method() +/// chai.add(fun(&MyClass::method2, &obj, 3), "method2"); +/// chai("method2()"); // equiv to obj.method2(3) +/// ~~~~~~~~~ +/// +/// @subsubsection addingtypeinfo Adding Type Info +/// +/// ChaiScript will automatically support any type implicitly provided to it in the form +/// of objects and function parameters / return types. However, it can be nice to let ChaiScript +/// know more details about the types you are giving it. For instance, the "clone" functionality +/// cannot work unless there is a copy constructor registered and the name of the type is known +/// (so that ChaiScript can look up the copy constructor). +/// +/// Continuing with the example "MyClass" from above: +/// +/// ~~~~~~~~{.cpp} +/// chai.add(user_type(), "MyClass"); +/// ~~~~~~~~ +/// +/// @subsubsection adding_modules Adding Modules +/// +/// Modules are holders for collections of ChaiScript registrations. +/// +/// ~~~~~~~~{.cpp} +/// ModulePtr module = get_sum_module(); +/// chai.add(module); +/// ~~~~~~~~ +/// +/// @sa chaiscript::Module +/// +/// ----------------------------------------------------------------------- +/// +/// @subsection operatoroverloading Operator Overloading +/// +/// Operators are just like any other function in ChaiScript, to overload an operator, simply register it. +/// +/// ~~~~~~~~{.cpp} +/// class MyClass { +/// MyClass operator+(const MyClass &) const; +/// }; +/// +/// chai.add(fun(&MyClass::operator+), "+"); +/// +/// std::string append_string_int(const std::string &t_lhs, int t_rhs) +/// { +/// std::stringstream ss; +/// ss << t_lhs << t_rhs; +/// return ss.str(); +/// } +/// +/// chai.add(fun(append_string_int), "+"); +/// ~~~~~~~~ +/// +/// @sa @ref adding_functions +/// +/// ----------------------------------------------------------------------- +/// +/// @subsection add_class Class Helper Utility +/// +/// Much of the work of adding new classes to ChaiScript can be reduced with the help +/// of the add_class helper utility. +/// +/// ~~~~~~~~{.cpp} +/// class Test +/// { +/// public: +/// void function() {} +/// std::string function2() { return "Function2"; } +/// void function3() {} +/// std::string functionOverload(double) { return "double"; } +/// std::string functionOverload(int) { return "int"; } +/// }; +/// +/// int main() +/// { +/// chaiscript::ModulePtr m = chaiscript::ModulePtr(new chaiscript::Module()); +/// +/// chaiscript::utility::add_class(*m, +/// "Test", +/// { constructor(), +/// constructor() }, +/// { {fun(&Test::function), "function"}, +/// {fun(&Test::function2), "function2"}, +/// {fun(&Test::function2), "function3"} +/// {fun(static_cast(&Test::functionOverload)), "functionOverload"} +/// {fun(static_cast(&Test::functionOverload)), "functionOverload"} } +/// ); +/// +/// +/// chaiscript::ChaiScript chai; +/// chai.add(m); +/// } +/// ~~~~~~~~ +/// +/// @sa @ref adding_modules +/// +/// ----------------------------------------------------------------------- +/// +/// @subsection pointer_conversions Pointer / Object Conversions +/// +/// As much as possible, ChaiScript attempts to convert between &, *, const &, const *, std::shared_ptr, +/// std::shared_ptr, std::reference_wrapper, std::reference_wrapper and value types automatically. +/// +/// If a chaiscript::var object was created in C++ from a pointer, it cannot be converted 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. +/// +/// ~~~~~~~~{.cpp} +/// void fun1(const int *); +/// void fun2(int *); +/// void fun3(int); +/// void fun4(int &); +/// void fun5(const int &); +/// void fun5(std::shared_ptr); +/// void fun6(std::shared_ptr); +/// void fun7(const std::shared_ptr &); +/// void fun8(const std::shared_ptr &); +/// void fun9(std::reference_wrapper); +/// void fun10(std::reference_wrapper); +/// +/// int main() +/// { +/// using namespace chaiscript +/// chaiscript::ChaiScript chai; +/// chai.add(fun(fun1), "fun1"); +/// chai.add(fun(fun2), "fun2"); +/// chai.add(fun(fun3), "fun3"); +/// chai.add(fun(fun4), "fun4"); +/// chai.add(fun(fun5), "fun5"); +/// chai.add(fun(fun6), "fun6"); +/// chai.add(fun(fun7), "fun7"); +/// chai.add(fun(fun8), "fun8"); +/// chai.add(fun(fun9), "fun9"); +/// chai.add(fun(fun10), "fun10"); +/// +/// chai("var i = 10;"); +/// chai("fun1(i)"); +/// chai("fun2(i)"); +/// chai("fun3(i)"); +/// chai("fun4(i)"); +/// chai("fun5(i)"); +/// chai("fun6(i)"); +/// chai("fun7(i)"); +/// chai("fun8(i)"); +/// chai("fun9(i)"); +/// chai("fun10(i)"); +/// } +/// ~~~~~~~~ +/// +/// See the unit test unittests/boxed_cast_test.cpp for a complete breakdown of the automatic casts that +/// available and tested. +/// +/// ----------------------------------------------------------------------- +/// +/// @subsection baseclasses Base Classes +/// +/// ChaiScript supports handling of passing a derived class object to a function expecting a base class object. +/// For the process to work, the base/derived relationship must be registered with the engine. +/// +/// ~~~~~~~~{.cpp} +/// class Base {}; +/// class Derived : public Base {}; +/// void myfunction(Base *b); +/// +/// int main() +/// { +/// chaiscript::ChaiScript chai; +/// chai.add(chaiscript::base_class()); +/// Derived d; +/// chai.add(chaiscript::var(&d), "d"); +/// chai.add(chaiscript::fun(&myfunction), "myfunction"); +/// chai("myfunction(d)"); +/// } +/// ~~~~~~~~ +/// +/// ----------------------------------------------------------------------- +/// +/// +/// @subsection functionobjects Function Objects +/// +/// Functions are first class objects in ChaiScript and ChaiScript supports automatic conversion +/// between ChaiScript functions and std::function objects. +/// +/// ~~~~~~~~{.cpp} +/// void callafunc(const std::function &t_func) +/// { +/// t_func("bob"); +/// } +/// +/// int main() +/// { +/// chaiscript::ChaiScript chai; +/// chai.add(chaiscript::fun(&callafunc), "callafunc"); +/// chai("callafunc(fun(x) { print(x); })"); // pass a lambda function to the registered function +/// // which expects a typed std::function +/// +/// std::function f = chai.eval >("dump_system"); +/// f(); // call the ChaiScript function dump_system, from C++ +/// } +/// ~~~~~~~~ +/// +/// ----------------------------------------------------------------------- +/// +/// +/// @subsection threading Threading +/// +/// Thread safety is automatically handled within the ChaiScript system. Objects can be added +/// and scripts executed from multiple threads. For each thread that executes scripts, a new +/// context is created and managed by the engine. +/// +/// Thread safety can be disabled by defining CHAISCRIPT_NO_THREADS when using the library. +/// +/// Disabling thread safety increases performance in many cases. +/// +/// ----------------------------------------------------------------------- +/// +/// +/// @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. +/// +/// ~~~~~~~~{.cpp} +/// void throwexception() +/// { +/// throw std::runtime_error("err"); +/// } +/// +/// 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) { +/// int i = chaiscript::boxed_cast(bv); +/// // i == 1 +/// } +/// } +/// ~~~~~~~~ +/// +/// @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: +/// ~~~~~~~~{.cpp} +/// 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 +/// } +/// ~~~~~~~~ +/// +/// @sa chaiscript::Exception_Handler for details on automatic exception unboxing +/// @sa chaiscript::exception_specification + + + +/// @page LangObjectSystemRef ChaiScript Language Object Model Reference +/// +/// +/// ChaiScript has an object system built in, for types defined within the ChaiScript system. +/// +/// ~~~~~~~~~ +/// attr Rectangle::height +/// attr Rectangle::width +/// def Rectangle::Rectangle() { this.height = 10; this.width = 20 } +/// def Rectangle::area() { this.height * this.width } +/// +/// var rect = Rectangle() +/// rect.height = 30 +/// print(rect.area()) +/// ~~~~~~~~~ +/// +/// Since ChaiScript 5.4.0 it has been possible to use the "class" keyword to simplify this code. +/// +/// ~~~~~~~~~ +/// class Rectangle { +/// attr height +/// attr width +/// def Rectangle() { this.height = 10; this.width = 20 } +/// def area() { this.height * this.width } +/// } +/// +/// var rect = Rectangle() +/// rect.height = 30 +/// print(rect.area()) +/// ~~~~~~~~~ +/// +/// @sa @ref keywordattr +/// @sa @ref keyworddef + +/// @page LangInPlaceRef ChaiScript Language In-Place Creation Reference +/// @section inplacevector Vector +/// +/// ~~~~~~~~~ +/// In-place Vector ::= "[" [expression ("," expression)*] "]" +/// ~~~~~~~~~ +/// +/// @section inplacerangedvector Ranged Vector +/// +/// ~~~~~~~~~ +/// In-place Ranged Vector ::= "[" value ".." value "]" +/// ~~~~~~~~~ +/// +/// Creates a vector over a range (eg. 1..10) +/// +/// @section inplacemap Map +/// +/// ~~~~~~~~ +/// In-place Map ::= "[" (string ":" expression)+ "]" +/// ~~~~~~~~ + +/// @page LangGettingStarted ChaiScript Language Getting Started +/// +/// ChaiScript is a simple language that should feel familiar to anyone who knows +/// C++ or ECMAScript (JavaScript). +/// +/// ----------------------------------------------------------------------- +/// +/// @section chaiscriptloops Loops +/// +/// Common looping constructs exist in ChaiScript +/// +/// ~~~~~~~~ +/// var i = 0; +/// while (i < 10) +/// { +/// // do something +/// ++i; +/// } +/// ~~~~~~~~ +/// +/// ~~~~~~~~ +/// for (var i = 0; i < 10; ++i) +/// { +/// // do something +/// } +/// ~~~~~~~~ +/// +/// @sa @ref keywordfor +/// @sa @ref keywordwhile +/// +/// ----------------------------------------------------------------------- +/// +/// @section chaiscriptifs Conditionals +/// +/// If statements work as expected +/// +/// ~~~~~~~~ +/// var b = true; +/// +/// if (b) { +/// // do something +/// } else if (c < 10) { +/// // do something else +/// } else { +/// // or do this +/// } +/// ~~~~~~~~ +/// +/// @sa @ref keywordif +/// +/// ----------------------------------------------------------------------- +/// +/// @section chaiscriptfunctions Functions +/// +/// Functions are defined with the def keyword +/// +/// ~~~~~~~~ +/// def myfun(x) { print(x); } +/// +/// myfun(10); +/// ~~~~~~~~ +/// +/// Functions may have "guards" which determine if which is called. +/// +/// ~~~~~~~~ +/// eval> def myfun2(x) : x < 10 { print("less than 10"); } +/// eval> def myfun2(x) : x >= 10 { print("10 or greater"); } +/// eval> myfun2(5) +/// less than 10 +/// eval> myfun2(12) +/// 10 or greater +/// ~~~~~~~~ +/// +/// @sa @ref keyworddef +/// @sa @ref keywordattr +/// @sa @ref LangObjectSystemRef +/// +/// ----------------------------------------------------------------------- +/// +/// @section chaiscriptfunctionobjects Function Objects +/// +/// Functions are first class types in ChaiScript and can be used as variables. +/// +/// ~~~~~~~~ +/// eval> var p = print; +/// eval> p(1); +/// 1 +/// ~~~~~~~~ +/// +/// They can also be passed to functions. +/// +/// ~~~~~~~~ +/// eval> def callfunc(f, lhs, rhs) { return f(lhs, rhs); } +/// eval> def do_something(lhs, rhs) { print("lhs: ${lhs}, rhs: ${rhs}"); } +/// eval> callfunc(do_something, 1, 2); +/// lhs: 1, rhs: 2 +/// ~~~~~~~~ +/// +/// Operators can also be treated as functions by using the back tick operator. Building on the above example: +/// +/// ~~~~~~~~ +/// eval> callfunc(`+`, 1, 4); +/// 5 +/// eval> callfunc(`*`, 3, 2); +/// 6 +/// ~~~~~~~~ +/// +/// ----------------------------------------------------------------------- +/// +/// @sa @ref LangKeywordRef +/// @sa ChaiScript_Language for Built in Functions + + +/// @page LangKeywordRef ChaiScript Language Keyword Reference +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordattr attr +/// Defines a ChaiScript object attribute +/// +/// ~~~~~~~~ +/// Attribute Definition ::= "attr" class_name "::" attribute_name +/// ~~~~~~~~ +/// +/// @sa @ref LangObjectSystemRef +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordauto auto +/// +/// Defines a variable +/// +/// ~~~~~~~~ +/// Variable ::= "auto" identifier +/// ~~~~~~~~ +/// +/// Synonym for @ref keywordvar +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordbreak break +/// Stops execution of a looping block. +/// +/// ~~~~~~~~ +/// Break Statement ::= "break" +/// ~~~~~~~~ +/// +/// @sa @ref keywordfor +/// @sa @ref keywordwhile +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keyworddef def +/// Begins a function or method definition +/// +/// ~~~~~~~~ +/// Function Definition ::= "def" identifier "(" [[type] arg ("," [type] arg)*] ")" [":" guard] block +/// Method Definition ::= "def" class_name "::" method_name "(" [[type] arg ("," [type] arg)*] ")" [":" guard] block +/// ~~~~~~~~ +/// +/// identifier: name of function. Required. +/// args: comma-delimited list of parameter names with optional type specifiers. Optional. +/// guards: guarding statement that act as a prerequisite for the function. Optional. +/// { }: scoped block as function body. Required. +/// +/// Functions return values in one of two ways: +/// +/// By using an explicit return call, optionally passing the value to be returned. +/// By implicitly returning the value of the last expression (if it is not a while or for loop). +/// +/// Method definitions for known types extend those types with new methods. This includes C++ and ChaiScript defined types. +/// Method definitions for unknown types implicitly define the named type. +/// +/// @sa @ref LangObjectSystemRef +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordelse else +/// @sa @ref keywordif +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordfor for +/// ~~~~~~~~ +/// For Block ::= "for" "(" [initial] ";" stop_condition ";" loop_expression ")" block +/// ~~~~~~~~ +/// This loop can be broken using the @ref keywordbreak command. +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordfun fun +/// Begins an anonymous function declaration (sometimes called a lambda). +/// +/// ~~~~~~~~ +/// Lambda ::= "fun" "(" [variable] ("," variable)* ")" block +/// ~~~~~~~~ +/// +/// _Example_ +/// +/// ~~~~~~~~ +/// // Generate an anonymous function object that adds 2 to its parameter +/// var f = fun(x) { x + 2; } +/// ~~~~~~~~ +/// +/// @sa @ref keyworddef for more details on ChaiScript functions +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordif if +/// Begins a conditional block of code that only executes if the condition evaluates as true. +/// ~~~~~~~~ +/// If Block ::= "if" "(" condition ")" block +/// Else If Block ::= "else if" "(" condition ")" block +/// Else Block ::= "else" block +/// ~~~~~~~~ +/// +/// _Example_ +/// +/// ~~~~~~~~ +/// if (true) { +/// // do something +/// } else if (false) { +/// // do something else +/// } else { +/// // otherwise do this +/// } +/// ~~~~~~~~ +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordtry try +/// ~~~~~~~~ +/// Try Block ::= "try" block +/// ("catch" ["(" [type] variable ")"] [":" guards] block)+ +/// ["finally" block] +/// ~~~~~~~~ +/// +/// @sa ChaiScript_Language::throw +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordwhile while +/// +/// Begins a conditional block of code that loops 0 or more times, as long as the condition is true +/// +/// ~~~~~~~~ +/// While Block ::= "while" "(" condition ")" block +/// ~~~~~~~~ +/// +/// This loop can be broken using the @ref keywordbreak command. +/// +/// +/// ----------------------------------------------------------------------- +/// +/// @section keywordvar var +/// +/// Defines a variable +/// +/// ~~~~~~~~ +/// Variable ::= "var" identifier +/// ~~~~~~~~ +/// +/// Synonym for @ref keywordauto + + +/// @namespace chaiscript +/// @brief Namespace chaiscript contains every API call that the average user will be concerned with. + +/// @namespace chaiscript::detail +/// @brief Classes and functions reserved for internal use. Items in this namespace are not supported. + +#include "chaiscript_basic.hpp" +#include "language/chaiscript_parser.hpp" +#include "chaiscript_stdlib.hpp" + + +namespace chaiscript +{ + class ChaiScript : public ChaiScript_Basic + { + public: + ChaiScript(std::vector t_modulepaths = {}, + std::vector t_usepaths = {}, + const std::vector &t_opts = chaiscript::default_options()) + : ChaiScript_Basic( + chaiscript::Std_Lib::library(), + std::make_unique>(), + t_modulepaths, t_usepaths, t_opts) + { + } + }; +} + +#endif /* CHAISCRIPT_HPP_ */ diff --git a/chaiscript/chaiscript_basic.hpp b/chaiscript/chaiscript_basic.hpp new file mode 100644 index 0000000..b770b1b --- /dev/null +++ b/chaiscript/chaiscript_basic.hpp @@ -0,0 +1,39 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_BASIC_HPP_ +#define CHAISCRIPT_BASIC_HPP_ + +#include "chaiscript_defines.hpp" + +#include "dispatchkit/dispatchkit.hpp" +#include "dispatchkit/function_call.hpp" +#include "dispatchkit/dynamic_object.hpp" +#include "dispatchkit/boxed_number.hpp" + +#include "language/chaiscript_eval.hpp" +#include "language/chaiscript_engine.hpp" + +// This file includes all of the basic requirements for ChaiScript, +// to use, you might do something like: +// + +/* + +#include "chaiscript_stdlib.hpp" +#include "language/chaiscript_parser.hpp" + +ChaiScript_Basic chai( + chaiscript::Std_Lib::library(), + std::make_unique>()); + +*/ + +// If you want a fully packaged ready to go ChaiScript, use chaiscript.hpp + + + +#endif /* CHAISCRIPT_BASIC_HPP_ */ diff --git a/chaiscript/chaiscript_defines.hpp b/chaiscript/chaiscript_defines.hpp new file mode 100644 index 0000000..4508d60 --- /dev/null +++ b/chaiscript/chaiscript_defines.hpp @@ -0,0 +1,245 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_DEFINES_HPP_ +#define CHAISCRIPT_DEFINES_HPP_ + +#ifdef _MSC_VER +#define CHAISCRIPT_STRINGIZE(x) "" #x +#define CHAISCRIPT_STRINGIZE_EXPANDED(x) CHAISCRIPT_STRINGIZE(x) +#define CHAISCRIPT_COMPILER_VERSION CHAISCRIPT_STRINGIZE_EXPANDED(_MSC_FULL_VER) +#define CHAISCRIPT_MSVC _MSC_VER +#define CHAISCRIPT_HAS_DECLSPEC + +static_assert(_MSC_FULL_VER >= 190024210, "Visual C++ 2015 Update 3 or later required"); + +#else +#define CHAISCRIPT_COMPILER_VERSION __VERSION__ +#endif + +#include + +#if defined( _LIBCPP_VERSION ) +#define CHAISCRIPT_LIBCPP +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +#define CHAISCRIPT_WINDOWS +#endif + +#if defined(_WIN32) +#if defined(__llvm__) +#define CHAISCRIPT_COMPILER_NAME "clang(windows)" +#elif defined(__GNUC__) +#define CHAISCRIPT_COMPILER_NAME "gcc(mingw)" +#else +#define CHAISCRIPT_COMPILER_NAME "msvc" +#endif +#else +#if defined(__llvm__) +#define CHAISCRIPT_COMPILER_NAME "clang" +#elif defined(__GNUC__) +#define CHAISCRIPT_COMPILER_NAME "gcc" +#else +#define CHAISCRIPT_COMPILER_NAME "unknown" +#endif +#endif + + +#if defined(__llvm__) +#define CHAISCRIPT_CLANG +#endif + + +#ifdef CHAISCRIPT_HAS_DECLSPEC +#define CHAISCRIPT_MODULE_EXPORT extern "C" __declspec(dllexport) +#else +#define CHAISCRIPT_MODULE_EXPORT extern "C" +#endif + +#if defined(CHAISCRIPT_MSVC) || (defined(__GNUC__) && __GNUC__ >= 5) || defined(CHAISCRIPT_CLANG) +#define CHAISCRIPT_UTF16_UTF32 +#endif + +#ifdef _DEBUG +#define CHAISCRIPT_DEBUG true +#else +#define CHAISCRIPT_DEBUG false +#endif + +#include +#include +#include + +namespace chaiscript { + static const int version_major = 6; + static const int version_minor = 1; + static const int version_patch = 0; + + static const char *compiler_version = CHAISCRIPT_COMPILER_VERSION; + static const char *compiler_name = CHAISCRIPT_COMPILER_NAME; + static const bool debug_build = CHAISCRIPT_DEBUG; + + template + inline std::shared_ptr make_shared(Arg && ... arg) + { +#ifdef CHAISCRIPT_USE_STD_MAKE_SHARED + return std::make_shared(std::forward(arg)...); +#else + return std::shared_ptr(static_cast(new D(std::forward(arg)...))); +#endif + } + + template + inline std::unique_ptr make_unique(Arg && ... arg) + { +#ifdef CHAISCRIPT_USE_STD_MAKE_SHARED + return std::make_unique(std::forward(arg)...); +#else + return std::unique_ptr(static_cast(new D(std::forward(arg)...))); +#endif + } + + struct Build_Info { + static int version_major() + { + return chaiscript::version_major; + } + + static int version_minor() + { + return chaiscript::version_minor; + } + + static int version_patch() + { + return chaiscript::version_patch; + } + + static std::string version() + { + return std::to_string(version_major()) + '.' + std::to_string(version_minor()) + '.' + std::to_string(version_patch()); + } + + static std::string compiler_id() + { + return compiler_name() + '-' + compiler_version(); + } + + static std::string build_id() + { + return compiler_id() + (debug_build()?"-Debug":"-Release"); + } + + static std::string compiler_version() + { + return chaiscript::compiler_version; + } + + static std::string compiler_name() + { + return chaiscript::compiler_name; + } + + static bool debug_build() + { + return chaiscript::debug_build; + } + }; + + + template + auto parse_num(const char *t_str) -> typename std::enable_if::value, T>::type + { + T t = 0; + for (char c = *t_str; (c = *t_str) != 0; ++t_str) { + if (c < '0' || c > '9') { + return t; + } + t *= 10; + t += c - '0'; + } + return t; + } + + + template + auto parse_num(const char *t_str) -> typename std::enable_if::value, T>::type + { + T t = 0; + T base{}; + T decimal_place = 0; + int exponent = 0; + + for (char c;; ++t_str) { + c = *t_str; + switch (c) + { + case '.': + decimal_place = 10; + break; + case 'e': + case 'E': + exponent = 1; + decimal_place = 0; + base = t; + t = 0; + break; + case '-': + exponent = -1; + break; + case '+': + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (decimal_place < 10) { + t *= 10; + t += static_cast(c - '0'); + } + else { + t += static_cast(c - '0') / decimal_place; + decimal_place *= 10; + } + break; + default: + return exponent ? base * std::pow(T(10), t * static_cast(exponent)) : t; + } + } + } + + template + T parse_num(const std::string &t_str) + { + return parse_num(t_str.c_str()); + } + + enum class Options + { + No_Load_Modules, + Load_Modules, + No_External_Scripts, + External_Scripts + }; + + static inline std::vector default_options() + { +#ifdef CHAISCRIPT_NO_DYNLOAD + return {Options::No_Load_Modules, Options::External_Scripts}; +#else + return {Options::Load_Modules, Options::External_Scripts}; +#endif + } +} +#endif + diff --git a/chaiscript/chaiscript_stdlib.hpp b/chaiscript/chaiscript_stdlib.hpp new file mode 100644 index 0000000..ed56b23 --- /dev/null +++ b/chaiscript/chaiscript_stdlib.hpp @@ -0,0 +1,71 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// and Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_STDLIB_HPP_ +#define CHAISCRIPT_STDLIB_HPP_ + +#include +#include +#include +#include +#include + +#include "chaiscript_defines.hpp" +#include "language/chaiscript_common.hpp" + +#include "dispatchkit/function_call.hpp" + +//#include "dispatchkit/dispatchkit.hpp" +#include "dispatchkit/operators.hpp" +#include "dispatchkit/bootstrap.hpp" +#include "dispatchkit/bootstrap_stl.hpp" +//#include "dispatchkit/boxed_value.hpp" +#include "language/chaiscript_prelude.hpp" +#include "dispatchkit/register_function.hpp" +#include "utility/json_wrap.hpp" + +#ifndef CHAISCRIPT_NO_THREADS +#include +#endif + + +/// @file +/// +/// This file generates the standard library that normal ChaiScript usage requires. + +namespace chaiscript +{ + class Std_Lib + { + public: + + static ModulePtr library() + { + auto lib = std::make_shared(); + bootstrap::Bootstrap::bootstrap(*lib); + + bootstrap::standard_library::vector_type >("Vector", *lib); + bootstrap::standard_library::string_type("string", *lib); + bootstrap::standard_library::map_type >("Map", *lib); + bootstrap::standard_library::pair_type >("Pair", *lib); + +#ifndef CHAISCRIPT_NO_THREADS + bootstrap::standard_library::future_type>("future", *lib); + lib->add(chaiscript::fun([](const std::function &t_func){ return std::async(std::launch::async, t_func);}), "async"); +#endif + + json_wrap::library(*lib); + + lib->eval(ChaiScript_Prelude::chaiscript_prelude() /*, "standard prelude"*/ ); + + return lib; + } + + }; +} + +#endif + diff --git a/chaiscript/chaiscript_threading.hpp b/chaiscript/chaiscript_threading.hpp new file mode 100644 index 0000000..79feaa1 --- /dev/null +++ b/chaiscript/chaiscript_threading.hpp @@ -0,0 +1,173 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_THREADING_HPP_ +#define CHAISCRIPT_THREADING_HPP_ + + +#include + +#ifndef CHAISCRIPT_NO_THREADS +#include +#include +#else +#ifndef CHAISCRIPT_NO_THREADS_WARNING +#pragma message ("ChaiScript is compiling without thread safety.") +#endif +#endif + +#include "chaiscript_defines.hpp" + +/// \file +/// +/// This file contains code necessary for thread support in ChaiScript. +/// If the compiler definition CHAISCRIPT_NO_THREADS is defined then thread safety +/// is disabled in ChaiScript. This has the result that some code is faster, because mutex locks are not required. +/// It also has the side effect that the chaiscript::ChaiScript object may not be accessed from more than +/// one thread simultaneously. + +namespace chaiscript +{ + namespace detail + { + /// If threading is enabled, then this namespace contains std thread classes. + /// If threading is not enabled, then stubbed in wrappers that do nothing are provided. + /// This allows us to avoid \#ifdef code in the sections that need thread safety. + namespace threading + { + +#ifndef CHAISCRIPT_NO_THREADS + + template + using unique_lock = std::unique_lock; + + template + using shared_lock = std::unique_lock; + + template + using lock_guard = std::lock_guard; + + + using shared_mutex = std::mutex; + + using std::mutex; + + using std::recursive_mutex; + + /// Typesafe thread specific storage. If threading is enabled, this class uses a mutex protected map. If + /// threading is not enabled, the class always returns the same data, regardless of which thread it is called from. + template + class Thread_Storage + { + public: + Thread_Storage() = default; + Thread_Storage(const Thread_Storage &) = delete; + Thread_Storage(Thread_Storage &&) = delete; + Thread_Storage &operator=(const Thread_Storage &) = delete; + Thread_Storage &operator=(Thread_Storage &&) = delete; + + ~Thread_Storage() + { + t().erase(this); + } + + inline const T *operator->() const + { + return &(t()[this]); + } + + inline const T &operator*() const + { + return t()[this]; + } + + inline T *operator->() + { + return &(t()[this]); + } + + inline T &operator*() + { + return t()[this]; + } + + + void *m_key; + + private: + static std::unordered_map &t() + { + thread_local std::unordered_map my_t; + return my_t; + } + }; + +#else // threading disabled + template + class unique_lock + { + public: + explicit unique_lock(T &) {} + void lock() {} + void unlock() {} + }; + + template + class shared_lock + { + public: + explicit shared_lock(T &) {} + void lock() {} + void unlock() {} + }; + + template + class lock_guard + { + public: + explicit lock_guard(T &) {} + }; + + class shared_mutex { }; + + class recursive_mutex {}; + + + template + class Thread_Storage + { + public: + explicit Thread_Storage() + { + } + + inline T *operator->() const + { + return &obj; + } + + inline T &operator*() const + { + return obj; + } + + private: + mutable T obj; + }; + +#endif + } + } +} + + + +#endif + diff --git a/chaiscript/dispatchkit/any.hpp b/chaiscript/dispatchkit/any.hpp new file mode 100644 index 0000000..ae8035c --- /dev/null +++ b/chaiscript/dispatchkit/any.hpp @@ -0,0 +1,164 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// and Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_ANY_HPP_ +#define CHAISCRIPT_ANY_HPP_ + +#include + +namespace chaiscript { + namespace detail { + namespace exception + { + /// \brief Thrown in the event that an Any cannot be cast to the desired type + /// + /// It is used internally during function dispatch. + /// + /// \sa chaiscript::detail::Any + class bad_any_cast : public std::bad_cast + { + public: + bad_any_cast() = default; + + bad_any_cast(const bad_any_cast &) = default; + + ~bad_any_cast() noexcept override = default; + + /// \brief Description of what error occurred + const char * what() const noexcept override + { + return m_what.c_str(); + } + + private: + std::string m_what = "bad any cast"; + }; + } + + + class Any { + private: + struct Data + { + explicit Data(const std::type_info &t_type) + : m_type(t_type) + { + } + + Data &operator=(const Data &) = delete; + + virtual ~Data() = default; + + virtual void *data() = 0; + + const std::type_info &type() const + { + return m_type; + } + + virtual std::unique_ptr clone() const = 0; + const std::type_info &m_type; + }; + + template + struct Data_Impl : Data + { + explicit Data_Impl(T t_type) + : Data(typeid(T)), + m_data(std::move(t_type)) + { + } + + void *data() override + { + return &m_data; + } + + std::unique_ptr clone() const override + { + return std::unique_ptr(new Data_Impl(m_data)); + } + + Data_Impl &operator=(const Data_Impl&) = delete; + + T m_data; + }; + + std::unique_ptr m_data; + + public: + // construct/copy/destruct + Any() = default; + Any(Any &&) = default; + Any &operator=(Any &&t_any) = default; + + Any(const Any &t_any) + { + if (!t_any.empty()) + { + m_data = t_any.m_data->clone(); + } else { + m_data.reset(); + } + } + + + template::type>::value>::type> + explicit Any(ValueType &&t_value) + : m_data(std::unique_ptr(new Data_Impl::type>(std::forward(t_value)))) + { + } + + + Any & operator=(const Any &t_any) + { + Any copy(t_any); + swap(copy); + return *this; + } + + template + ToType &cast() const + { + if (m_data && typeid(ToType) == m_data->type()) + { + return *static_cast(m_data->data()); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + + // modifiers + Any & swap(Any &t_other) + { + std::swap(t_other.m_data, m_data); + return *this; + } + + // queries + bool empty() const + { + return !bool(m_data); + } + + const std::type_info & type() const + { + if (m_data) { + return m_data->type(); + } else { + return typeid(void); + } + } + }; + + } +} + +#endif + + diff --git a/chaiscript/dispatchkit/bad_boxed_cast.hpp b/chaiscript/dispatchkit/bad_boxed_cast.hpp new file mode 100644 index 0000000..1c1d729 --- /dev/null +++ b/chaiscript/dispatchkit/bad_boxed_cast.hpp @@ -0,0 +1,73 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_BAD_BOXED_CAST_HPP_ +#define CHAISCRIPT_BAD_BOXED_CAST_HPP_ + +#include +#include + +#include "../chaiscript_defines.hpp" +#include "type_info.hpp" + +namespace chaiscript { +class Type_Info; +} // namespace chaiscript + +namespace chaiscript +{ + namespace exception + { + /// \brief Thrown in the event that a Boxed_Value cannot be cast to the desired type + /// + /// It is used internally during function dispatch and may be used by the end user. + /// + /// \sa chaiscript::boxed_cast + class bad_boxed_cast : public std::bad_cast + { + public: + bad_boxed_cast(Type_Info t_from, const std::type_info &t_to, + std::string t_what) noexcept + : from(t_from), to(&t_to), m_what(std::move(t_what)) + { + } + + bad_boxed_cast(Type_Info t_from, const std::type_info &t_to) + : from(t_from), to(&t_to), m_what("Cannot perform boxed_cast: " + t_from.name() + " to: " + t_to.name()) + { + } + + explicit bad_boxed_cast(std::string t_what) noexcept + : m_what(std::move(t_what)) + { + } + + bad_boxed_cast(const bad_boxed_cast &) = default; + ~bad_boxed_cast() noexcept override = default; + + /// \brief Description of what error occurred + const char * what() const noexcept override + { + return m_what.c_str(); + } + + Type_Info from; ///< Type_Info contained in the Boxed_Value + const std::type_info *to = nullptr; ///< std::type_info of the desired (but failed) result type + + private: + std::string m_what; + }; + } +} + + + +#endif + diff --git a/chaiscript/dispatchkit/bind_first.hpp b/chaiscript/dispatchkit/bind_first.hpp new file mode 100644 index 0000000..c65c838 --- /dev/null +++ b/chaiscript/dispatchkit/bind_first.hpp @@ -0,0 +1,85 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_BIND_FIRST_HPP_ +#define CHAISCRIPT_BIND_FIRST_HPP_ + +#include + +namespace chaiscript +{ + namespace detail + { + + template + T* get_pointer(T *t) + { + return t; + } + + template + T* get_pointer(const std::reference_wrapper &t) + { + return &t.get(); + } + + template + auto bind_first(Ret (*f)(P1, Param...), O&& o) + { + return [f, o](Param...param) -> Ret { + return f(std::forward(o), std::forward(param)...); + }; + } + + template + auto bind_first(Ret (Class::*f)(Param...), O&& o) + { + return [f, o](Param...param) -> Ret { + return (get_pointer(o)->*f)(std::forward(param)...); + }; + } + + template + auto bind_first(Ret (Class::*f)(Param...) const, O&& o) + { + return [f, o](Param...param) -> Ret { + return (get_pointer(o)->*f)(std::forward(param)...); + }; + + } + + template + auto bind_first(const std::function &f, O&& o) + { + return [f, o](Param...param) -> Ret { + return f(o, std::forward(param)...); + }; + } + + template + auto bind_first(const F &fo, O&& o, Ret (Class::*f)(P1, Param...) const) + { + return [fo, o, f](Param ...param) -> Ret { + return (fo.*f)(o, std::forward(param)...); + }; + + } + + template + auto bind_first(const F &f, O&& o) + { + return bind_first(f, std::forward(o), &F::operator()); + } + + } +} + + +#endif diff --git a/chaiscript/dispatchkit/bootstrap.hpp b/chaiscript/dispatchkit/bootstrap.hpp new file mode 100644 index 0000000..6fb2c42 --- /dev/null +++ b/chaiscript/dispatchkit/bootstrap.hpp @@ -0,0 +1,594 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_BOOTSTRAP_HPP_ +#define CHAISCRIPT_BOOTSTRAP_HPP_ + +#include "../utility/utility.hpp" +#include "register_function.hpp" + +namespace chaiscript +{ + /// \brief Classes and functions useful for bootstrapping of ChaiScript and adding of new types + namespace bootstrap + { + template::value>::type > + void array(const std::string &type, Module& m) + { + typedef typename std::remove_extent::type ReturnType; + m.add(user_type(), type); + m.add(fun( + [](T& t, size_t index)->ReturnType &{ + constexpr auto extent = std::extent::value; + if (extent > 0 && index >= extent) { + throw std::range_error("Array index out of range. Received: " + std::to_string(index) + " expected < " + std::to_string(extent)); + } else { + return t[index]; + } + } + ), "[]" + ); + + m.add(fun( + [](const T &t, size_t index)->const ReturnType &{ + constexpr auto extent = std::extent::value; + if (extent > 0 && index >= extent) { + throw std::range_error("Array index out of range. Received: " + std::to_string(index) + " expected < " + std::to_string(extent)); + } else { + return t[index]; + } + } + ), "[]" + ); + + m.add(fun( + [](const T &) { + constexpr auto extent = std::extent::value; + return extent; + }), "size"); + } + + /// \brief Adds a copy constructor for the given type to the given Model + /// \param[in] type The name of the type. The copy constructor will be named "type". + /// \param[in,out] m The Module to add the copy constructor to + /// \tparam T The type to add a copy constructor for + /// \returns The passed in Module + template + void copy_constructor(const std::string &type, Module& m) + { + m.add(constructor(), type); + } + + /// \brief Add all comparison operators for the templated type. Used during bootstrap, also available to users. + /// \tparam T Type to create comparison operators for + /// \param[in,out] m module to add comparison operators to + /// \returns the passed in Module. + template + void opers_comparison(Module& m) + { + operators::equal(m); + operators::greater_than(m); + operators::greater_than_equal(m); + operators::less_than(m); + operators::less_than_equal(m); + operators::not_equal(m); + } + + + + /// \brief Adds default and copy constructors for the given type + /// \param[in] type The name of the type to add the constructors for. + /// \param[in,out] m The Module to add the basic constructors to + /// \tparam T Type to generate basic constructors for + /// \returns The passed in Module + /// \sa copy_constructor + /// \sa constructor + template + void basic_constructors(const std::string &type, Module& m) + { + m.add(constructor(), type); + copy_constructor(type, m); + } + + /// \brief Adds a constructor for a POD type + /// \tparam T The type to add the constructor for + /// \param[in] type The name of the type + /// \param[in,out] m The Module to add the constructor to + template + void construct_pod(const std::string &type, Module& m) + { + m.add(fun([](const Boxed_Number &bn){ return bn.get_as(); }), type); + } + + + /// Internal function for converting from a string to a value + /// uses ostream operator >> to perform the conversion + template + auto parse_string(const std::string &i) + -> typename std::enable_if< + !std::is_same::value + && !std::is_same::value + && !std::is_same::value, + Input>::type + { + std::stringstream ss(i); + Input t; + ss >> t; + return t; + } + + template + auto parse_string(const std::string &) + -> typename std::enable_if< + std::is_same::value + || std::is_same::value + || std::is_same::value, + Input>::type + { + throw std::runtime_error("Parsing of wide characters is not yet supported"); + } + + + /// Add all common functions for a POD type. All operators, and + /// common conversions + template + void bootstrap_pod_type(const std::string &name, Module& m) + { + m.add(user_type(), name); + m.add(constructor(), name); + construct_pod(name, m); + + m.add(fun(&parse_string), "to_" + name); + m.add(fun([](const T t){ return t; }), "to_" + name); + } + + + /// "clone" function for a shared_ptr type. This is used in the case + /// where you do not want to make a deep copy of an object during cloning + /// but want to instead maintain the shared_ptr. It is needed internally + /// for handling of Proxy_Function object (that is, + /// function variables. + template + auto shared_ptr_clone(const std::shared_ptr &p) + { + return p; + } + + /// Specific version of shared_ptr_clone just for Proxy_Functions + template + std::shared_ptr::type> shared_ptr_unconst_clone(const std::shared_ptr::type> &p) + { + return std::const_pointer_cast::type>(p); + } + + + + /// Assignment function for shared_ptr objects, does not perform a copy of the + /// object pointed to, instead maintains the shared_ptr concept. + /// Similar to shared_ptr_clone. Used for Proxy_Function. + template + Boxed_Value ptr_assign(Boxed_Value lhs, const std::shared_ptr &rhs) + { + if (lhs.is_undef() + || (!lhs.get_type_info().is_const() && lhs.get_type_info().bare_equal(chaiscript::detail::Get_Type_Info::get()))) + { + lhs.assign(Boxed_Value(rhs)); + return lhs; + } else { + throw exception::bad_boxed_cast("type mismatch in pointer assignment"); + } + } + + /// Class consisting of only static functions. All default bootstrapping occurs + /// from this class. + class Bootstrap + { + private: + /// Function allowing for assignment of an unknown type to any other value + static Boxed_Value unknown_assign(Boxed_Value lhs, Boxed_Value rhs) + { + if (lhs.is_undef()) + { + return (lhs.assign(rhs)); + } else { + throw exception::bad_boxed_cast("boxed_value has a set type already"); + } + } + + static void print(const std::string &s) + { + //fwrite(s.c_str(), 1, s.size(), stdout); + std::cout << s; + } + + static void println(const std::string &s) + { + //puts(s.c_str()); + std::cout << s << std::endl; + } + + + /// Add all arithmetic operators for PODs + static void opers_arithmetic_pod(Module& m) + { + m.add(fun(&Boxed_Number::equals), "=="); + m.add(fun(&Boxed_Number::less_than), "<"); + m.add(fun(&Boxed_Number::greater_than), ">"); + m.add(fun(&Boxed_Number::greater_than_equal), ">="); + m.add(fun(&Boxed_Number::less_than_equal), "<="); + m.add(fun(&Boxed_Number::not_equal), "!="); + + m.add(fun(&Boxed_Number::pre_decrement), "--"); + m.add(fun(&Boxed_Number::pre_increment), "++"); + m.add(fun(&Boxed_Number::sum), "+"); + m.add(fun(&Boxed_Number::unary_plus), "+"); + m.add(fun(&Boxed_Number::unary_minus), "-"); + m.add(fun(&Boxed_Number::difference), "-"); + m.add(fun(&Boxed_Number::assign_bitwise_and), "&="); + m.add(fun(&Boxed_Number::assign), "="); + m.add(fun(&Boxed_Number::assign_bitwise_or), "|="); + m.add(fun(&Boxed_Number::assign_bitwise_xor), "^="); + m.add(fun(&Boxed_Number::assign_remainder), "%="); + m.add(fun(&Boxed_Number::assign_shift_left), "<<="); + m.add(fun(&Boxed_Number::assign_shift_right), ">>="); + m.add(fun(&Boxed_Number::bitwise_and), "&"); + m.add(fun(&Boxed_Number::bitwise_complement), "~"); + m.add(fun(&Boxed_Number::bitwise_xor), "^"); + m.add(fun(&Boxed_Number::bitwise_or), "|"); + m.add(fun(&Boxed_Number::assign_product), "*="); + m.add(fun(&Boxed_Number::assign_quotient), "/="); + m.add(fun(&Boxed_Number::assign_sum), "+="); + m.add(fun(&Boxed_Number::assign_difference), "-="); + m.add(fun(&Boxed_Number::quotient), "/"); + m.add(fun(&Boxed_Number::shift_left), "<<"); + m.add(fun(&Boxed_Number::product), "*"); + m.add(fun(&Boxed_Number::remainder), "%"); + m.add(fun(&Boxed_Number::shift_right), ">>"); + } + + /// Create a bound function object. The first param is the function to bind + /// the remaining parameters are the args to bind into the result + static Boxed_Value bind_function(const std::vector ¶ms) + { + if (params.empty()) { + throw exception::arity_error(0, 1); + } + + Const_Proxy_Function f = boxed_cast(params[0]); + + if (f->get_arity() != -1 && size_t(f->get_arity()) != params.size() - 1) + { + throw exception::arity_error(static_cast(params.size()), f->get_arity()); + } + + return Boxed_Value(Const_Proxy_Function(std::make_shared(std::move(f), + std::vector(params.begin() + 1, params.end())))); + } + + + static bool has_guard(const Const_Proxy_Function &t_pf) + { + auto pf = std::dynamic_pointer_cast(t_pf); + return pf && pf->get_guard(); + } + + static Const_Proxy_Function get_guard(const Const_Proxy_Function &t_pf) + { + const auto pf = std::dynamic_pointer_cast(t_pf); + if (pf && pf->get_guard()) + { + return pf->get_guard(); + } else { + throw std::runtime_error("Function does not have a guard"); + } + } + + template + static std::vector do_return_boxed_value_vector(FunctionType f, + const dispatch::Proxy_Function_Base *b) + { + auto v = (b->*f)(); + + std::vector vbv; + + for (const auto &o: v) + { + vbv.push_back(const_var(o)); + } + + return vbv; + } + + + static bool has_parse_tree(const chaiscript::Const_Proxy_Function &t_pf) + { + const auto pf = std::dynamic_pointer_cast(t_pf); + return bool(pf); + } + + static const chaiscript::AST_Node &get_parse_tree(const chaiscript::Const_Proxy_Function &t_pf) + { + const auto pf = std::dynamic_pointer_cast(t_pf); + if (pf) + { + return pf->get_parse_tree(); + } else { + throw std::runtime_error("Function does not have a parse tree"); + } + } + + template + static auto return_boxed_value_vector(const Function &f) + { + return [f](const dispatch::Proxy_Function_Base *b) { + return do_return_boxed_value_vector(f, b); + }; + } + + + public: + /// \brief perform all common bootstrap functions for std::string, void and POD types + /// \param[in,out] m Module to add bootstrapped functions to + /// \returns passed in Module + static void bootstrap(Module& m) + { + m.add(user_type(), "void"); + m.add(user_type(), "bool"); + m.add(user_type(), "Object"); + m.add(user_type(), "Number"); + m.add(user_type(), "Function"); + m.add(user_type(), "Assignable_Function"); + m.add(user_type(), "exception"); + + m.add(fun(&dispatch::Proxy_Function_Base::get_arity), "get_arity"); + m.add(fun(&dispatch::Proxy_Function_Base::operator==), "=="); + + + m.add(fun(return_boxed_value_vector(&dispatch::Proxy_Function_Base::get_param_types)), "get_param_types"); + m.add(fun(return_boxed_value_vector(&dispatch::Proxy_Function_Base::get_contained_functions)), "get_contained_functions"); + + m.add(fun([](const std::exception &e){ return std::string(e.what()); }), "what"); + + m.add(user_type(), "out_of_range"); + m.add(user_type(), "logic_error"); + m.add(chaiscript::base_class()); + m.add(chaiscript::base_class()); + m.add(chaiscript::base_class()); + + m.add(user_type(), "runtime_error"); + m.add(chaiscript::base_class()); + + m.add(constructor(), "runtime_error"); + + m.add(user_type(), "Dynamic_Object"); + m.add(constructor(), "Dynamic_Object"); + m.add(constructor(), "Dynamic_Object"); + m.add(fun(&dispatch::Dynamic_Object::get_type_name), "get_type_name"); + m.add(fun(&dispatch::Dynamic_Object::get_attrs), "get_attrs"); + m.add(fun(&dispatch::Dynamic_Object::set_explicit), "set_explicit"); + m.add(fun(&dispatch::Dynamic_Object::is_explicit), "is_explicit"); + m.add(fun(&dispatch::Dynamic_Object::has_attr), "has_attr"); + + m.add(fun(static_cast(&dispatch::Dynamic_Object::get_attr)), "get_attr"); + m.add(fun(static_cast(&dispatch::Dynamic_Object::get_attr)), "get_attr"); + + m.add(fun(static_cast(&dispatch::Dynamic_Object::method_missing)), "method_missing"); + m.add(fun(static_cast(&dispatch::Dynamic_Object::method_missing)), "method_missing"); + + m.add(fun(static_cast(&dispatch::Dynamic_Object::get_attr)), "[]"); + m.add(fun(static_cast(&dispatch::Dynamic_Object::get_attr)), "[]"); + + m.eval(R"chaiscript( + def Dynamic_Object::clone() { + auto &new_o = Dynamic_Object(this.get_type_name()); + for_each(this.get_attrs(), fun[new_o](x) { new_o.get_attr(x.first) = x.second; } ); + new_o; + } + + def `=`(Dynamic_Object lhs, Dynamic_Object rhs) : lhs.get_type_name() == rhs.get_type_name() + { + for_each(rhs.get_attrs(), fun[lhs](x) { lhs.get_attr(x.first) = clone(x.second); } ); + } + + def `!=`(Dynamic_Object lhs, Dynamic_Object rhs) : lhs.get_type_name() == rhs.get_type_name() + { + var rhs_attrs := rhs.get_attrs(); + var lhs_attrs := lhs.get_attrs(); + + if (rhs_attrs.size() != lhs_attrs.size()) { + true; + } else { + return any_of(rhs_attrs, fun[lhs](x) { !lhs.has_attr(x.first) || lhs.get_attr(x.first) != x.second; } ); + } + } + + def `==`(Dynamic_Object lhs, Dynamic_Object rhs) : lhs.get_type_name() == rhs.get_type_name() + { + var rhs_attrs := rhs.get_attrs(); + var lhs_attrs := lhs.get_attrs(); + + if (rhs_attrs.size() != lhs_attrs.size()) { + false; + } else { + return all_of(rhs_attrs, fun[lhs](x) { lhs.has_attr(x.first) && lhs.get_attr(x.first) == x.second; } ); + } + } + )chaiscript"); + + m.add(fun(&has_guard), "has_guard"); + m.add(fun(&get_guard), "get_guard"); + + m.add(fun(&Boxed_Value::is_undef), "is_var_undef"); + m.add(fun(&Boxed_Value::is_null), "is_var_null"); + m.add(fun(&Boxed_Value::is_const), "is_var_const"); + m.add(fun(&Boxed_Value::is_ref), "is_var_reference"); + m.add(fun(&Boxed_Value::is_pointer), "is_var_pointer"); + m.add(fun(&Boxed_Value::is_return_value), "is_var_return_value"); + m.add(fun(&Boxed_Value::reset_return_value), "reset_var_return_value"); + m.add(fun(&Boxed_Value::is_type), "is_type"); + m.add(fun(&Boxed_Value::get_attr), "get_var_attr"); + m.add(fun(&Boxed_Value::copy_attrs), "copy_var_attrs"); + m.add(fun(&Boxed_Value::clone_attrs), "clone_var_attrs"); + + m.add(fun(&Boxed_Value::get_type_info), "get_type_info"); + m.add(user_type(), "Type_Info"); + m.add(constructor(), "Type_Info"); + + + operators::equal(m); + + m.add(fun(&Type_Info::is_const), "is_type_const"); + m.add(fun(&Type_Info::is_reference), "is_type_reference"); + m.add(fun(&Type_Info::is_void), "is_type_void"); + m.add(fun(&Type_Info::is_undef), "is_type_undef"); + m.add(fun(&Type_Info::is_pointer), "is_type_pointer"); + m.add(fun(&Type_Info::is_arithmetic), "is_type_arithmetic"); + m.add(fun(&Type_Info::name), "cpp_name"); + m.add(fun(&Type_Info::bare_name), "cpp_bare_name"); + m.add(fun(&Type_Info::bare_equal), "bare_equal"); + + + basic_constructors("bool", m); + operators::assign(m); + operators::equal(m); + operators::not_equal(m); + + m.add(fun([](const std::string &s) { return s; }), "to_string"); + m.add(fun([](const bool b) { return std::string(b?"true":"false"); }), "to_string"); + m.add(fun(&unknown_assign), "="); + m.add(fun([](const Boxed_Value &bv) { throw bv; }), "throw"); + + m.add(fun([](const char c) { return std::string(1, c); }), "to_string"); + m.add(fun(&Boxed_Number::to_string), "to_string"); + + + bootstrap_pod_type("double", m); + bootstrap_pod_type("long_double", m); + bootstrap_pod_type("float", m); + bootstrap_pod_type("int", m); + bootstrap_pod_type("long", m); + bootstrap_pod_type("unsigned_int", m); + bootstrap_pod_type("unsigned_long", m); + bootstrap_pod_type("long_long", m); + bootstrap_pod_type("unsigned_long_long", m); + bootstrap_pod_type("size_t", m); + bootstrap_pod_type("char", m); + bootstrap_pod_type("wchar_t", m); + bootstrap_pod_type("char16_t", m); + bootstrap_pod_type("char32_t", m); + bootstrap_pod_type("int8_t", m); + bootstrap_pod_type("int16_t", m); + bootstrap_pod_type("int32_t", m); + bootstrap_pod_type("int64_t", m); + bootstrap_pod_type("uint8_t", m); + bootstrap_pod_type("uint16_t", m); + bootstrap_pod_type("uint32_t", m); + bootstrap_pod_type("uint64_t", m); + + + operators::logical_compliment(m); + + opers_arithmetic_pod(m); + + + m.add(fun(&Build_Info::version_major), "version_major"); + m.add(fun(&Build_Info::version_minor), "version_minor"); + m.add(fun(&Build_Info::version_patch), "version_patch"); + m.add(fun(&Build_Info::version), "version"); + m.add(fun(&Build_Info::compiler_version), "compiler_version"); + m.add(fun(&Build_Info::compiler_name), "compiler_name"); + m.add(fun(&Build_Info::compiler_id), "compiler_id"); + m.add(fun(&Build_Info::debug_build), "debug_build"); + + + m.add(fun(&print), "print_string"); + m.add(fun(&println), "println_string"); + + m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind"); + + m.add(fun(&shared_ptr_unconst_clone), "clone"); + m.add(fun(&ptr_assign::type>), "="); + m.add(fun(&ptr_assign::type>), "="); + m.add(chaiscript::base_class()); + m.add(fun( + [](dispatch::Assignable_Proxy_Function &t_lhs, const std::shared_ptr &t_rhs) { + t_lhs.assign(t_rhs); + } + ), "=" + ); + + m.add(fun(&Boxed_Value::type_match), "type_match"); + + + m.add(chaiscript::fun(&has_parse_tree), "has_parse_tree"); + m.add(chaiscript::fun(&get_parse_tree), "get_parse_tree"); + + m.add(chaiscript::base_class()); + m.add(chaiscript::base_class()); + + m.add(chaiscript::user_type(), "arithmetic_error"); + m.add(chaiscript::base_class()); + m.add(chaiscript::base_class()); + + +// chaiscript::bootstrap::standard_library::vector_type > >("AST_NodeVector", m); + + + chaiscript::utility::add_class(m, + "eval_error", + { }, + { {fun(&chaiscript::exception::eval_error::reason), "reason"}, + {fun(&chaiscript::exception::eval_error::pretty_print), "pretty_print"}, + {fun([](const chaiscript::exception::eval_error &t_eval_error) { + std::vector retval; + std::transform(t_eval_error.call_stack.begin(), t_eval_error.call_stack.end(), + std::back_inserter(retval), + &chaiscript::var); + return retval; + }), "call_stack"} } + ); + + + chaiscript::utility::add_class(m, + "File_Position", + { constructor(), + constructor() }, + { {fun(&File_Position::line), "line"}, + {fun(&File_Position::column), "column"} } + ); + + + chaiscript::utility::add_class(m, + "AST_Node", + { }, + { {fun(&AST_Node::text), "text"}, + {fun(&AST_Node::identifier), "identifier"}, + {fun(&AST_Node::filename), "filename"}, + {fun(&AST_Node::start), "start"}, + {fun(&AST_Node::end), "end"}, + {fun(&AST_Node::to_string), "to_string"}, + {fun([](const chaiscript::AST_Node &t_node) -> std::vector { + std::vector retval; + const auto children = t_node.get_children(); + std::transform(children.begin(), children.end(), + std::back_inserter(retval), + &chaiscript::var &>); + return retval; + }), "children"} + } + ); + + } + }; + } +} + +#endif + diff --git a/chaiscript/dispatchkit/bootstrap_stl.hpp b/chaiscript/dispatchkit/bootstrap_stl.hpp new file mode 100644 index 0000000..bc3c086 --- /dev/null +++ b/chaiscript/dispatchkit/bootstrap_stl.hpp @@ -0,0 +1,755 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +/// \file +/// This file contains utility functions for registration of STL container +/// classes. The methodology used is based on the SGI STL concepts. +/// http://www.sgi.com/tech/stl/table_of_contents.html + + +#ifndef CHAISCRIPT_BOOTSTRAP_STL_HPP_ +#define CHAISCRIPT_BOOTSTRAP_STL_HPP_ + +#include +#include +#include +#include +#include + +#include "bootstrap.hpp" +#include "boxed_value.hpp" +#include "dispatchkit.hpp" +#include "operators.hpp" +#include "proxy_constructors.hpp" +#include "register_function.hpp" +#include "type_info.hpp" + +namespace chaiscript +{ + namespace bootstrap + { + namespace standard_library + { + + /// Bidir_Range, based on the D concept of ranges. + /// \todo Update the Range code to base its capabilities on + /// the user_typetraits of the iterator passed in + template + struct Bidir_Range + { + typedef Container container_type; + + Bidir_Range(Container &c) + : m_begin(c.begin()), m_end(c.end()) + { + } + + bool empty() const + { + return m_begin == m_end; + } + + void pop_front() + { + if (empty()) + { + throw std::range_error("Range empty"); + } + ++m_begin; + } + + void pop_back() + { + if (empty()) + { + throw std::range_error("Range empty"); + } + --m_end; + } + + decltype(auto) front() const + { + if (empty()) + { + throw std::range_error("Range empty"); + } + return (*m_begin); + } + + decltype(auto) back() const + { + if (empty()) + { + throw std::range_error("Range empty"); + } + auto pos = m_end; + --pos; + return (*(pos)); + } + + IterType m_begin; + IterType m_end; + }; + + namespace detail { + + template + size_t count(const T &t_target, const typename T::key_type &t_key) + { + return t_target.count(t_key); + } + + template + void insert(T &t_target, const T &t_other) + { + t_target.insert(t_other.begin(), t_other.end()); + } + + template + void insert_ref(T &t_target, const typename T::value_type &t_val) + { + t_target.insert(t_val); + } + + + + /// Add Bidir_Range support for the given ContainerType + template + void input_range_type_impl(const std::string &type, Module& m) + { + m.add(user_type(), type + "_Range"); + + copy_constructor(type + "_Range", m); + + m.add(constructor(), "range_internal"); + + m.add(fun(&Bidir_Type::empty), "empty"); + m.add(fun(&Bidir_Type::pop_front), "pop_front"); + m.add(fun(&Bidir_Type::front), "front"); + m.add(fun(&Bidir_Type::pop_back), "pop_back"); + m.add(fun(&Bidir_Type::back), "back"); + } + + + /// Algorithm for inserting at a specific position into a container + template + void insert_at(Type &container, int pos, const typename Type::value_type &v) + { + auto itr = container.begin(); + auto end = container.end(); + + if (pos < 0 || std::distance(itr, end) < pos) + { + throw std::range_error("Cannot insert past end of range"); + } + + std::advance(itr, pos); + container.insert(itr, v); + } + + + /// Algorithm for erasing a specific position from a container + template + void erase_at(Type &container, int pos) + { + auto itr = container.begin(); + auto end = container.end(); + + if (pos < 0 || std::distance(itr, end) < (pos-1)) + { + throw std::range_error("Cannot erase past end of range"); + } + + std::advance(itr, pos); + container.erase(itr); + } + } + + template + void input_range_type(const std::string &type, Module& m) + { + detail::input_range_type_impl >(type,m); + detail::input_range_type_impl >("Const_" + type,m); + } + template + ModulePtr input_range_type(const std::string &type) + { + auto m = std::make_shared(); + input_range_type(type, *m); + return m; + } + + + /// Add random_access_container concept to the given ContainerType + /// http://www.sgi.com/tech/stl/RandomAccessContainer.html + template + void random_access_container_type(const std::string &/*type*/, Module& m) + { + //In the interest of runtime safety for the m, we prefer the at() method for [] access, + //to throw an exception in an out of bounds condition. + m.add( + fun( + [](ContainerType &c, int index) -> typename ContainerType::reference { + /// \todo we are prefering to keep the key as 'int' to avoid runtime conversions + /// during dispatch. reevaluate + return c.at(static_cast(index)); + }), "[]"); + + m.add( + fun( + [](const ContainerType &c, int index) -> typename ContainerType::const_reference { + /// \todo we are prefering to keep the key as 'int' to avoid runtime conversions + /// during dispatch. reevaluate + return c.at(static_cast(index)); + }), "[]"); + } + template + ModulePtr random_access_container_type(const std::string &type) + { + auto m = std::make_shared(); + random_access_container_type(type, *m); + return m; + } + + + + /// Add assignable concept to the given ContainerType + /// http://www.sgi.com/tech/stl/Assignable.html + template + void assignable_type(const std::string &type, Module& m) + { + copy_constructor(type, m); + operators::assign(m); + } + template + ModulePtr assignable_type(const std::string &type) + { + auto m = std::make_shared(); + assignable_type(type, *m); + return m; + } + + + /// Add container resize concept to the given ContainerType + /// http://www.cplusplus.com/reference/stl/ + template + void resizable_type(const std::string &/*type*/, Module& m) + { + m.add(fun([](ContainerType *a, typename ContainerType::size_type n, const typename ContainerType::value_type& val) { return a->resize(n, val); } ), "resize"); + m.add(fun([](ContainerType *a, typename ContainerType::size_type n) { return a->resize(n); } ), "resize"); + } + template + ModulePtr resizable_type(const std::string &type) + { + auto m = std::make_shared(); + resizable_type(type, *m); + return m; + } + + + /// Add container reserve concept to the given ContainerType + /// http://www.cplusplus.com/reference/stl/ + template + void reservable_type(const std::string &/*type*/, Module& m) + { + m.add(fun([](ContainerType *a, typename ContainerType::size_type n) { return a->reserve(n); } ), "reserve"); + m.add(fun([](const ContainerType *a) { return a->capacity(); } ), "capacity"); + } + template + ModulePtr reservable_type(const std::string &type) + { + auto m = std::make_shared(); + reservable_type(type, *m); + return m; + } + + + /// Add container concept to the given ContainerType + /// http://www.sgi.com/tech/stl/Container.html + template + void container_type(const std::string &/*type*/, Module& m) + { + m.add(fun([](const ContainerType *a) { return a->size(); } ), "size"); + m.add(fun([](const ContainerType *a) { return a->empty(); } ), "empty"); + m.add(fun([](ContainerType *a) { a->clear(); } ), "clear"); + } + template + ModulePtr container_type(const std::string& type) + { + auto m = std::make_shared(); + container_type(type, *m); + return m; + } + + + /// Add default constructable concept to the given Type + /// http://www.sgi.com/tech/stl/DefaultConstructible.html + template + void default_constructible_type(const std::string &type, Module& m) + { + m.add(constructor(), type); + } + template + ModulePtr default_constructible_type(const std::string& type) + { + auto m = std::make_shared(); + default_constructible_type(type, *m); + return m; + } + + + + /// Add sequence concept to the given ContainerType + /// http://www.sgi.com/tech/stl/Sequence.html + template + void sequence_type(const std::string &/*type*/, Module& m) + { + m.add(fun(&detail::insert_at), + []()->std::string{ + if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { + return "insert_ref_at"; + } else { + return "insert_at"; + } + }()); + + m.add(fun(&detail::erase_at), "erase_at"); + } + template + ModulePtr sequence_type(const std::string &type) + { + auto m = std::make_shared(); + sequence_type(type, *m); + return m; + } + + /// Add back insertion sequence concept to the given ContainerType + /// http://www.sgi.com/tech/stl/BackInsertionSequence.html + template + void back_insertion_sequence_type(const std::string &type, Module& m) + { + m.add(fun([](ContainerType &container)->decltype(auto){ + if (container.empty()) { + throw std::range_error("Container empty"); + } else { + return (container.back()); + } + } + ) + , "back"); + m.add(fun([](const ContainerType &container)->decltype(auto){ + if (container.empty()) { + throw std::range_error("Container empty"); + } else { + return (container.back()); + } + } + ) + , "back"); + + + typedef void (ContainerType::*push_back)(const typename ContainerType::value_type &); + m.add(fun(static_cast(&ContainerType::push_back)), + [&]()->std::string{ + if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { + m.eval( + "# Pushes the second value onto the container while making a clone of the value\n" + "def push_back(" + type + " container, x)\n" + "{ \n" + " if (x.is_var_return_value()) {\n" + " x.reset_var_return_value() \n" + " container.push_back_ref(x) \n" + " } else { \n" + " container.push_back_ref(clone(x)); \n" + " }\n" + "} \n" + ); + + return "push_back_ref"; + } else { + return "push_back"; + } + }()); + + m.add(fun(&ContainerType::pop_back), "pop_back"); + } + template + ModulePtr back_insertion_sequence_type(const std::string &type) + { + auto m = std::make_shared(); + back_insertion_sequence_type(type, *m); + return m; + } + + + + /// Front insertion sequence + /// http://www.sgi.com/tech/stl/FrontInsertionSequence.html + template + void front_insertion_sequence_type(const std::string &type, Module& m) + { + typedef void (ContainerType::*push_ptr)(typename ContainerType::const_reference); + typedef void (ContainerType::*pop_ptr)(); + + m.add(fun([](ContainerType &container)->decltype(auto){ + if (container.empty()) { + throw std::range_error("Container empty"); + } else { + return (container.front()); + } + } + ) + , "front"); + + m.add(fun([](const ContainerType &container)->decltype(auto){ + if (container.empty()) { + throw std::range_error("Container empty"); + } else { + return (container.front()); + } + } + ) + , "front"); + + + m.add(fun(static_cast(&ContainerType::push_front)), + [&]()->std::string{ + if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { + m.eval( + "# Pushes the second value onto the front of container while making a clone of the value\n" + "def push_front(" + type + " container, x)\n" + "{ \n" + " if (x.is_var_return_value()) {\n" + " x.reset_var_return_value() \n" + " container.push_front_ref(x) \n" + " } else { \n" + " container.push_front_ref(clone(x)); \n" + " }\n" + "} \n" + ); + return "push_front_ref"; + } else { + return "push_front"; + } + }()); + + m.add(fun(static_cast(&ContainerType::pop_front)), "pop_front"); + } + template + ModulePtr front_insertion_sequence_type(const std::string &type) + { + auto m = std::make_shared(); + front_insertion_sequence_type(type, *m); + return m; + } + + + /// bootstrap a given PairType + /// http://www.sgi.com/tech/stl/pair.html + template + void pair_type(const std::string &type, Module& m) + { + m.add(user_type(), type); + + m.add(fun(&PairType::first), "first"); + m.add(fun(&PairType::second), "second"); + + basic_constructors(type, m); + m.add(constructor(), type); + } + template + ModulePtr pair_type(const std::string &type) + { + auto m = std::make_shared(); + pair_type(type, *m); + return m; + } + + + + /// Add pair associative container concept to the given ContainerType + /// http://www.sgi.com/tech/stl/PairAssociativeContainer.html + + template + void pair_associative_container_type(const std::string &type, Module& m) + { + pair_type(type + "_Pair", m); + } + template + ModulePtr pair_associative_container_type(const std::string &type) + { + auto m = std::make_shared(); + pair_associative_container_type(type, *m); + return m; + } + + + /// Add unique associative container concept to the given ContainerType + /// http://www.sgi.com/tech/stl/UniqueAssociativeContainer.html + template + void unique_associative_container_type(const std::string &/*type*/, Module& m) + { + m.add(fun(detail::count), "count"); + + typedef size_t (ContainerType::*erase_ptr)(const typename ContainerType::key_type &); + + m.add(fun(static_cast(&ContainerType::erase)), "erase"); + + m.add(fun(&detail::insert), "insert"); + + m.add(fun(&detail::insert_ref), + []()->std::string{ + if (typeid(typename ContainerType::mapped_type) == typeid(Boxed_Value)) { + return "insert_ref"; + } else { + return "insert"; + } + }()); + } + template + ModulePtr unique_associative_container_type(const std::string &type) + { + auto m = std::make_shared(); + unique_associative_container_type(type, *m); + return m; + } + + + /// Add a MapType container + /// http://www.sgi.com/tech/stl/Map.html + template + void map_type(const std::string &type, Module& m) + { + m.add(user_type(), type); + + typedef typename MapType::mapped_type &(MapType::*elem_access)(const typename MapType::key_type &); + typedef const typename MapType::mapped_type &(MapType::*const_elem_access)(const typename MapType::key_type &) const; + + m.add(fun(static_cast(&MapType::operator[])), "[]"); + + m.add(fun(static_cast(&MapType::at)), "at"); + m.add(fun(static_cast(&MapType::at)), "at"); + + if (typeid(MapType) == typeid(std::map)) + { + m.eval(R"( + def Map::`==`(Map rhs) { + if ( rhs.size() != this.size() ) { + return false; + } else { + auto r1 = range(this); + auto r2 = range(rhs); + while (!r1.empty()) + { + if (!eq(r1.front().first, r2.front().first) || !eq(r1.front().second, r2.front().second)) + { + return false; + } + r1.pop_front(); + r2.pop_front(); + } + true; + } + } )" + ); + } + + container_type(type, m); + default_constructible_type(type, m); + assignable_type(type, m); + unique_associative_container_type(type, m); + pair_associative_container_type(type, m); + input_range_type(type, m); + } + template + ModulePtr map_type(const std::string &type) + { + auto m = std::make_shared(); + map_type(type, *m); + return m; + } + + + /// http://www.sgi.com/tech/stl/List.html + template + void list_type(const std::string &type, Module& m) + { + m.add(user_type(), type); + + front_insertion_sequence_type(type, m); + back_insertion_sequence_type(type, m); + sequence_type(type, m); + resizable_type(type, m); + container_type(type, m); + default_constructible_type(type, m); + assignable_type(type, m); + input_range_type(type, m); + } + template + ModulePtr list_type(const std::string &type) + { + auto m = std::make_shared(); + list_type(type, m); + return m; + } + + + /// Create a vector type with associated concepts + /// http://www.sgi.com/tech/stl/Vector.html + template + void vector_type(const std::string &type, Module& m) + { + m.add(user_type(), type); + + m.add(fun([](VectorType &container)->decltype(auto){ + if (container.empty()) { + throw std::range_error("Container empty"); + } else { + return (container.front()); + } + } + ) + , "front"); + + m.add(fun([](const VectorType &container)->decltype(auto){ + if (container.empty()) { + throw std::range_error("Container empty"); + } else { + return (container.front()); + } + } + ) + , "front"); + + + + + back_insertion_sequence_type(type, m); + sequence_type(type, m); + random_access_container_type(type, m); + resizable_type(type, m); + reservable_type(type, m); + container_type(type, m); + default_constructible_type(type, m); + assignable_type(type, m); + input_range_type(type, m); + + if (typeid(VectorType) == typeid(std::vector)) + { + m.eval(R"( + def Vector::`==`(Vector rhs) { + if ( rhs.size() != this.size() ) { + return false; + } else { + auto r1 = range(this); + auto r2 = range(rhs); + while (!r1.empty()) + { + if (!eq(r1.front(), r2.front())) + { + return false; + } + r1.pop_front(); + r2.pop_front(); + } + true; + } + } )" + ); + } + } + template + ModulePtr vector_type(const std::string &type) + { + auto m = std::make_shared(); + vector_type(type, *m); + return m; + } + + /// Add a String container + /// http://www.sgi.com/tech/stl/basic_string.html + template + void string_type(const std::string &type, Module& m) + { + m.add(user_type(), type); + operators::addition(m); + operators::assign_sum(m); + opers_comparison(m); + random_access_container_type(type, m); + sequence_type(type, m); + default_constructible_type(type, m); + // container_type(type, m); + assignable_type(type, m); + input_range_type(type, m); + + //Special case: add push_back to string (which doesn't support other back_insertion operations + m.add(fun(&String::push_back), + []()->std::string{ + if (typeid(typename String::value_type) == typeid(Boxed_Value)) { + return "push_back_ref"; + } else { + return "push_back"; + } + }()); + + + m.add(fun([](const String *s, const String &f, size_t pos) { return s->find(f, pos); } ), "find"); + m.add(fun([](const String *s, const String &f, size_t pos) { return s->rfind(f, pos); } ), "rfind"); + m.add(fun([](const String *s, const String &f, size_t pos) { return s->find_first_of(f, pos); } ), "find_first_of"); + m.add(fun([](const String *s, const String &f, size_t pos) { return s->find_last_of(f, pos); } ), "find_last_of"); + m.add(fun([](const String *s, const String &f, size_t pos) { return s->find_last_not_of(f, pos); } ), "find_last_not_of"); + m.add(fun([](const String *s, const String &f, size_t pos) { return s->find_first_not_of(f, pos); } ), "find_first_not_of"); + + m.add(fun([](String *s, typename String::value_type c) -> decltype(auto) { return (*s += c); } ), "+="); + + m.add(fun([](String *s) { s->clear(); } ), "clear"); + m.add(fun([](const String *s) { return s->empty(); } ), "empty"); + m.add(fun([](const String *s) { return s->size(); } ), "size"); + + m.add(fun([](const String *s) { return s->c_str(); } ), "c_str"); + m.add(fun([](const String *s) { return s->data(); } ), "data"); + m.add(fun([](const String *s, size_t pos, size_t len) { return s->substr(pos, len); } ), "substr"); + } + template + ModulePtr string_type(const std::string &type) + { + auto m = std::make_shared(); + string_type(type, *m); + return m; + } + + + + /// Add a MapType container + /// http://www.sgi.com/tech/stl/Map.html + template + void future_type(const std::string &type, Module& m) + { + m.add(user_type(), type); + + m.add(fun([](const FutureType &t) { return t.valid(); }), "valid"); + m.add(fun([](FutureType &t) { return t.get(); }), "get"); + m.add(fun(&FutureType::wait), "wait"); + } + template + ModulePtr future_type(const std::string &type) + { + auto m = std::make_shared(); + future_type(type, *m); + return m; + } + } + } +} + + +#endif + + diff --git a/chaiscript/dispatchkit/boxed_cast.hpp b/chaiscript/dispatchkit/boxed_cast.hpp new file mode 100644 index 0000000..b79d735 --- /dev/null +++ b/chaiscript/dispatchkit/boxed_cast.hpp @@ -0,0 +1,113 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_BOXED_CAST_HPP_ +#define CHAISCRIPT_BOXED_CAST_HPP_ + +#include "../chaiscript_defines.hpp" +#include "bad_boxed_cast.hpp" +#include "boxed_cast_helper.hpp" +#include "boxed_value.hpp" +#include "type_conversions.hpp" +#include "type_info.hpp" + +namespace chaiscript { +class Type_Conversions; +namespace detail { +namespace exception { +class bad_any_cast; +} // namespace exception +} // namespace detail +} // namespace chaiscript + +namespace chaiscript +{ + + /// \brief Function for extracting a value stored in a Boxed_Value object + /// \tparam Type The type to extract from the Boxed_Value + /// \param[in] bv The Boxed_Value to extract a typed value from + /// \returns Type equivalent to the requested type + /// \throws exception::bad_boxed_cast If the requested conversion is not possible + /// + /// boxed_cast will attempt to make conversions between value, &, *, std::shared_ptr, std::reference_wrapper, + /// and std::function (const and non-const) where possible. boxed_cast is used internally during function + /// dispatch. This means that all of these conversions will be attempted automatically for you during + /// ChaiScript function calls. + /// + /// \li non-const values can be extracted as const or non-const + /// \li const values can be extracted only as const + /// \li Boxed_Value constructed from pointer or std::reference_wrapper can be extracted as reference, + /// pointer or value types + /// \li Boxed_Value constructed from std::shared_ptr or value types can be extracted as reference, + /// pointer, value, or std::shared_ptr types + /// + /// Conversions to std::function objects are attempted as well + /// + /// Example: + /// \code + /// // All of the following should succeed + /// chaiscript::Boxed_Value bv(1); + /// std::shared_ptr spi = chaiscript::boxed_cast >(bv); + /// int i = chaiscript::boxed_cast(bv); + /// int *ip = chaiscript::boxed_cast(bv); + /// int &ir = chaiscript::boxed_cast(bv); + /// std::shared_ptr cspi = chaiscript::boxed_cast >(bv); + /// const int ci = chaiscript::boxed_cast(bv); + /// const int *cip = chaiscript::boxed_cast(bv); + /// const int &cir = chaiscript::boxed_cast(bv); + /// \endcode + /// + /// std::function conversion example + /// \code + /// chaiscript::ChaiScript chai; + /// Boxed_Value bv = chai.eval("`+`"); // Get the functor for the + operator which is built in + /// std::function f = chaiscript::boxed_cast >(bv); + /// int i = f(2,3); + /// assert(i == 5); + /// \endcode + template + decltype(auto) boxed_cast(const Boxed_Value &bv, const Type_Conversions_State *t_conversions = nullptr) + { + if (!t_conversions || bv.get_type_info().bare_equal(user_type()) || (t_conversions && !(*t_conversions)->convertable_type())) { + try { + return(detail::Cast_Helper::cast(bv, t_conversions)); + } catch (const chaiscript::detail::exception::bad_any_cast &) { + } + } + + + if (t_conversions && (*t_conversions)->convertable_type()) + { + try { + // We will not catch any bad_boxed_dynamic_cast that is thrown, let the user get it + // either way, we are not responsible if it doesn't work + return(detail::Cast_Helper::cast((*t_conversions)->boxed_type_conversion(t_conversions->saves(), bv), t_conversions)); + } catch (...) { + try { + // try going the other way + return(detail::Cast_Helper::cast((*t_conversions)->boxed_type_down_conversion(t_conversions->saves(), bv), t_conversions)); + } catch (const chaiscript::detail::exception::bad_any_cast &) { + throw exception::bad_boxed_cast(bv.get_type_info(), typeid(Type)); + } + } + } else { + // If it's not convertable, just throw the error, don't waste the time on the + // attempted dynamic_cast + throw exception::bad_boxed_cast(bv.get_type_info(), typeid(Type)); + } + + } + +} + + + +#endif + diff --git a/chaiscript/dispatchkit/boxed_cast_helper.hpp b/chaiscript/dispatchkit/boxed_cast_helper.hpp new file mode 100644 index 0000000..b00dd04 --- /dev/null +++ b/chaiscript/dispatchkit/boxed_cast_helper.hpp @@ -0,0 +1,323 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_BOXED_CAST_HELPER_HPP_ +#define CHAISCRIPT_BOXED_CAST_HELPER_HPP_ + +#include +#include + +#include "boxed_value.hpp" +#include "type_info.hpp" + + +namespace chaiscript +{ + class Type_Conversions_State; + + namespace detail + { + // Cast_Helper_Inner helper classes + + template + T* throw_if_null(T *t) + { + if (t) { return t; } + throw std::runtime_error("Attempted to dereference null Boxed_Value"); + } + + template + static const T *verify_type_no_throw(const Boxed_Value &ob, const std::type_info &ti, const T *ptr) { + if (ob.get_type_info() == ti) { + return ptr; + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static T *verify_type_no_throw(const Boxed_Value &ob, const std::type_info &ti, T *ptr) { + if (!ob.is_const() && ob.get_type_info() == ti) { + return ptr; + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + + template + static const T *verify_type(const Boxed_Value &ob, const std::type_info &ti, const T *ptr) { + if (ob.get_type_info().bare_equal_type_info(ti)) { + return throw_if_null(ptr); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static T *verify_type(const Boxed_Value &ob, const std::type_info &ti, T *ptr) { + if (!ob.is_const() && ob.get_type_info().bare_equal_type_info(ti)) { + return throw_if_null(ptr); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + /// Generic Cast_Helper_Inner, for casting to any type + template + struct Cast_Helper_Inner + { + static Result cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return *static_cast(verify_type(ob, typeid(Result), ob.get_const_ptr())); + } + }; + + template + struct Cast_Helper_Inner : Cast_Helper_Inner + { + }; + + + /// Cast_Helper_Inner for casting to a const * type + template + struct Cast_Helper_Inner + { + static const Result * cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return static_cast(verify_type_no_throw(ob, typeid(Result), ob.get_const_ptr())); + } + }; + + /// Cast_Helper_Inner for casting to a * type + template + struct Cast_Helper_Inner + { + static Result * cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return static_cast(verify_type_no_throw(ob, typeid(Result), ob.get_ptr())); + } + }; + + template + struct Cast_Helper_Inner : public Cast_Helper_Inner + { + }; + + template + struct Cast_Helper_Inner : public Cast_Helper_Inner + { + }; + + + /// Cast_Helper_Inner for casting to a & type + template + struct Cast_Helper_Inner + { + static const Result & cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return *static_cast(verify_type(ob, typeid(Result), ob.get_const_ptr())); + } + }; + + + + /// Cast_Helper_Inner for casting to a & type + template + struct Cast_Helper_Inner + { + static Result& cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return *static_cast(verify_type(ob, typeid(Result), ob.get_ptr())); + } + }; + + /// Cast_Helper_Inner for casting to a && type + template + struct Cast_Helper_Inner + { + static Result&& cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return std::move(*static_cast(verify_type(ob, typeid(Result), ob.get_ptr()))); + } + }; + + /// Cast_Helper_Inner for casting to a std::unique_ptr<> && type + /// \todo Fix the fact that this has to be in a shared_ptr for now + template + struct Cast_Helper_Inner &&> + { + static std::unique_ptr &&cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return std::move(*(ob.get().cast>>())); + } + }; + + /// Cast_Helper_Inner for casting to a std::unique_ptr<> & type + /// \todo Fix the fact that this has to be in a shared_ptr for now + template + struct Cast_Helper_Inner &> + { + static std::unique_ptr &cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return *(ob.get().cast>>()); + } + }; + + /// Cast_Helper_Inner for casting to a std::unique_ptr<> & type + /// \todo Fix the fact that this has to be in a shared_ptr for now + template + struct Cast_Helper_Inner &> + { + static std::unique_ptr &cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return *(ob.get().cast>>()); + } + }; + + + /// Cast_Helper_Inner for casting to a std::shared_ptr<> type + template + struct Cast_Helper_Inner > + { + static auto cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return ob.get().cast >(); + } + }; + + /// Cast_Helper_Inner for casting to a std::shared_ptr type + template + struct Cast_Helper_Inner > + { + static auto cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + if (!ob.get_type_info().is_const()) + { + return std::const_pointer_cast(ob.get().cast >()); + } else { + return ob.get().cast >(); + } + } + }; + + /// Cast_Helper_Inner for casting to a const std::shared_ptr<> & type + template + struct Cast_Helper_Inner > : Cast_Helper_Inner > + { + }; + + template + struct Cast_Helper_Inner &> : Cast_Helper_Inner > + { + }; + + template + struct Cast_Helper_Inner &> + { + static_assert(!std::is_const::value, "Non-const reference to std::shared_ptr is not supported"); + static auto cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + std::shared_ptr &res = ob.get().cast >(); + return ob.pointer_sentinel(res); + } + }; + + + /// Cast_Helper_Inner for casting to a const std::shared_ptr & type + template + struct Cast_Helper_Inner > : Cast_Helper_Inner > + { + }; + + template + struct Cast_Helper_Inner &> : Cast_Helper_Inner > + { + }; + + + /// Cast_Helper_Inner for casting to a Boxed_Value type + template<> + struct Cast_Helper_Inner + { + static Boxed_Value cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return ob; + } + }; + + /// Cast_Helper_Inner for casting to a Boxed_Value & type + template<> + struct Cast_Helper_Inner + { + static std::reference_wrapper cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return std::ref(const_cast(ob)); + } + }; + + + /// Cast_Helper_Inner for casting to a const Boxed_Value & type + template<> + struct Cast_Helper_Inner : Cast_Helper_Inner + { + }; + + template<> + struct Cast_Helper_Inner : Cast_Helper_Inner + { + }; + + + /// Cast_Helper_Inner for casting to a std::reference_wrapper type + template + struct Cast_Helper_Inner > : Cast_Helper_Inner + { + }; + + template + struct Cast_Helper_Inner > : Cast_Helper_Inner + { + }; + + template + struct Cast_Helper_Inner &> : Cast_Helper_Inner + { + }; + + template + struct Cast_Helper_Inner > : Cast_Helper_Inner + { + }; + + template + struct Cast_Helper_Inner > : Cast_Helper_Inner + { + }; + + template + struct Cast_Helper_Inner & > : Cast_Helper_Inner + { + }; + + /// The exposed Cast_Helper object that by default just calls the Cast_Helper_Inner + template + struct Cast_Helper + { + static decltype(auto) cast(const Boxed_Value &ob, const Type_Conversions_State *t_conversions) + { + return(Cast_Helper_Inner::cast(ob, t_conversions)); + } + }; + } + +} + +#endif diff --git a/chaiscript/dispatchkit/boxed_number.hpp b/chaiscript/dispatchkit/boxed_number.hpp new file mode 100644 index 0000000..dafc123 --- /dev/null +++ b/chaiscript/dispatchkit/boxed_number.hpp @@ -0,0 +1,946 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#ifndef CHAISCRIPT_BOXED_NUMERIC_HPP_ +#define CHAISCRIPT_BOXED_NUMERIC_HPP_ + +#include +#include +#include + +#include "../language/chaiscript_algebraic.hpp" +#include "any.hpp" +#include "boxed_cast.hpp" +#include "boxed_cast_helper.hpp" +#include "boxed_value.hpp" +#include "type_info.hpp" + +namespace chaiscript { +class Type_Conversions; +} // namespace chaiscript + +namespace chaiscript +{ + namespace exception + { + struct arithmetic_error : std::runtime_error + { + explicit arithmetic_error(const std::string& reason) : std::runtime_error("Arithmetic error: " + reason) {} + arithmetic_error(const arithmetic_error &) = default; + ~arithmetic_error() noexcept override = default; + }; + } +} + +namespace chaiscript +{ + +// Due to the nature of generating every possible arithmetic operation, there +// are going to be warnings generated on every platform regarding size and sign, +// this is OK, so we're disabling size/and sign type warnings +#ifdef CHAISCRIPT_MSVC +#pragma warning(push) +#pragma warning(disable : 4244 4018 4389 4146 4365 4267 4242) +#endif + + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wfloat-conversion" +#endif + + /// \brief Represents any numeric type, generically. Used internally for generic operations between POD values + class Boxed_Number + { + private: + enum class Common_Types { + t_int32, + t_double, + t_uint8, + t_int8, + t_uint16, + t_int16, + t_uint32, + t_uint64, + t_int64, + t_float, + t_long_double + }; + + template + static inline void check_divide_by_zero(T t, typename std::enable_if::value>::type* = nullptr) + { +#ifndef CHAISCRIPT_NO_PROTECT_DIVIDEBYZERO + if (t == 0) { + throw chaiscript::exception::arithmetic_error("divide by zero"); + } +#endif + } + + template + static inline void check_divide_by_zero(T, typename std::enable_if::value>::type* = nullptr) + { + } + + static constexpr Common_Types get_common_type(size_t t_size, bool t_signed) + { + return (t_size == 1 && t_signed)?(Common_Types::t_int8) + :(t_size == 1)?(Common_Types::t_uint8) + :(t_size == 2 && t_signed)?(Common_Types::t_int16) + :(t_size == 2)?(Common_Types::t_uint16) + :(t_size == 4 && t_signed)?(Common_Types::t_int32) + :(t_size == 4)?(Common_Types::t_uint32) + :(t_size == 8 && t_signed)?(Common_Types::t_int64) + :(Common_Types::t_uint64); + } + + + static Common_Types get_common_type(const Boxed_Value &t_bv) + { + const Type_Info &inp_ = t_bv.get_type_info(); + + if (inp_ == typeid(int)) { + return get_common_type(sizeof(int), true); + } else if (inp_ == typeid(double)) { + return Common_Types::t_double; + } else if (inp_ == typeid(long double)) { + return Common_Types::t_long_double; + } else if (inp_ == typeid(float)) { + return Common_Types::t_float; + } else if (inp_ == typeid(char)) { + return get_common_type(sizeof(char), std::is_signed::value); + } else if (inp_ == typeid(unsigned char)) { + return get_common_type(sizeof(unsigned char), false); + } else if (inp_ == typeid(unsigned int)) { + return get_common_type(sizeof(unsigned int), false); + } else if (inp_ == typeid(long)) { + return get_common_type(sizeof(long), true); + } else if (inp_ == typeid(long long)) { + return get_common_type(sizeof(long long), true); + } else if (inp_ == typeid(unsigned long)) { + return get_common_type(sizeof(unsigned long), false); + } else if (inp_ == typeid(unsigned long long)) { + return get_common_type(sizeof(unsigned long long), false); + } else if (inp_ == typeid(std::int8_t)) { + return Common_Types::t_int8; + } else if (inp_ == typeid(std::int16_t)) { + return Common_Types::t_int16; + } else if (inp_ == typeid(std::int32_t)) { + return Common_Types::t_int32; + } else if (inp_ == typeid(std::int64_t)) { + return Common_Types::t_int64; + } else if (inp_ == typeid(std::uint8_t)) { + return Common_Types::t_uint8; + } else if (inp_ == typeid(std::uint16_t)) { + return Common_Types::t_uint16; + } else if (inp_ == typeid(std::uint32_t)) { + return Common_Types::t_uint32; + } else if (inp_ == typeid(std::uint64_t)) { + return Common_Types::t_uint64; + } else if (inp_ == typeid(wchar_t)) { + return get_common_type(sizeof(wchar_t), std::is_signed::value); + } else if (inp_ == typeid(char16_t)) { + return get_common_type(sizeof(char16_t), std::is_signed::value); + } else if (inp_ == typeid(char32_t)) { + return get_common_type(sizeof(char32_t), std::is_signed::value); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static Boxed_Value boolean_go(Operators::Opers t_oper, const T &t, const T &u) + { + switch (t_oper) + { + case Operators::Opers::equals: + return const_var(t == u); + case Operators::Opers::less_than: + return const_var(t < u); + case Operators::Opers::greater_than: + return const_var(t > u); + case Operators::Opers::less_than_equal: + return const_var(t <= u); + case Operators::Opers::greater_than_equal: + return const_var(t >= u); + case Operators::Opers::not_equal: + return const_var(t != u); + default: + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static Boxed_Value unary_go(Operators::Opers t_oper, T &t, const Boxed_Value &t_lhs) + { + switch (t_oper) + { + case Operators::Opers::pre_increment: + ++t; + break; + case Operators::Opers::pre_decrement: + --t; + break; + default: + throw chaiscript::detail::exception::bad_any_cast(); + } + + return t_lhs; + } + + template + static Boxed_Value binary_go(Operators::Opers t_oper, T &t, const U &u, const Boxed_Value &t_lhs) + { + switch (t_oper) + { + case Operators::Opers::assign: + t = u; + break; + case Operators::Opers::assign_product: + t *= u; + break; + case Operators::Opers::assign_sum: + t += u; + break; + case Operators::Opers::assign_quotient: + check_divide_by_zero(u); + t /= u; + break; + case Operators::Opers::assign_difference: + t -= u; + break; + default: + throw chaiscript::detail::exception::bad_any_cast(); + } + + return t_lhs; + } + + template + static Boxed_Value binary_int_go(Operators::Opers t_oper, T &t, const U &u, const Boxed_Value &t_lhs) + { + switch (t_oper) + { + case Operators::Opers::assign_bitwise_and: + t &= u; + break; + case Operators::Opers::assign_bitwise_or: + t |= u; + break; + case Operators::Opers::assign_shift_left: + t <<= u; + break; + case Operators::Opers::assign_shift_right: + t >>= u; + break; + case Operators::Opers::assign_remainder: + check_divide_by_zero(u); + t %= u; + break; + case Operators::Opers::assign_bitwise_xor: + t ^= u; + break; + default: + throw chaiscript::detail::exception::bad_any_cast(); + } + return t_lhs; + } + + template + static Boxed_Value const_unary_int_go(Operators::Opers t_oper, const T &t) + { + switch (t_oper) + { + case Operators::Opers::bitwise_complement: + return const_var(~t); + default: + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static Boxed_Value const_binary_int_go(Operators::Opers t_oper, const T &t, const T &u) + { + switch (t_oper) + { + case Operators::Opers::shift_left: + return const_var(t << u); + case Operators::Opers::shift_right: + return const_var(t >> u); + case Operators::Opers::remainder: + check_divide_by_zero(u); + return const_var(t % u); + case Operators::Opers::bitwise_and: + return const_var(t & u); + case Operators::Opers::bitwise_or: + return const_var(t | u); + case Operators::Opers::bitwise_xor: + return const_var(t ^ u); + default: + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static Boxed_Value const_unary_go(Operators::Opers t_oper, const T &t) + { + switch (t_oper) + { + case Operators::Opers::unary_minus: + return const_var(-t); + case Operators::Opers::unary_plus: + return const_var(+t); + default: + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static Boxed_Value const_binary_go(Operators::Opers t_oper, const T &t, const T &u) + { + switch (t_oper) + { + case Operators::Opers::sum: + return const_var(t + u); + case Operators::Opers::quotient: + check_divide_by_zero(u); + return const_var(t / u); + case Operators::Opers::product: + return const_var(t * u); + case Operators::Opers::difference: + return const_var(t - u); + default: + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static auto go(Operators::Opers t_oper, const Boxed_Value &t_lhs, const Boxed_Value &t_rhs) + -> typename std::enable_if::value && !std::is_floating_point::value, Boxed_Value>::type + { + typedef typename std::common_type::type common_type; + if (t_oper > Operators::Opers::boolean_flag && t_oper < Operators::Opers::non_const_flag) + { + return boolean_go(t_oper, get_as_aux(t_lhs), get_as_aux(t_rhs)); + } else if (t_oper > Operators::Opers::non_const_flag && t_oper < Operators::Opers::non_const_int_flag && !t_lhs.is_const() && !t_lhs.is_return_value()) { + return binary_go(t_oper, *static_cast(t_lhs.get_ptr()), get_as_aux(t_rhs), t_lhs); + } else if (t_oper > Operators::Opers::non_const_int_flag && t_oper < Operators::Opers::const_int_flag && !t_lhs.is_const() && !t_lhs.is_return_value()) { + return binary_int_go(t_oper, *static_cast(t_lhs.get_ptr()), get_as_aux(t_rhs), t_lhs); + } else if (t_oper > Operators::Opers::const_int_flag && t_oper < Operators::Opers::const_flag) { + return const_binary_int_go(t_oper, get_as_aux(t_lhs), get_as_aux(t_rhs)); + } else if (t_oper > Operators::Opers::const_flag) { + return const_binary_go(t_oper, get_as_aux(t_lhs), get_as_aux(t_rhs)); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static auto go(Operators::Opers t_oper, const Boxed_Value &t_lhs, const Boxed_Value &t_rhs) + -> typename std::enable_if::value || std::is_floating_point::value, Boxed_Value>::type + { + typedef typename std::common_type::type common_type; + if (t_oper > Operators::Opers::boolean_flag && t_oper < Operators::Opers::non_const_flag) + { + return boolean_go(t_oper, get_as_aux(t_lhs), get_as_aux(t_rhs)); + } else if (t_oper > Operators::Opers::non_const_flag && t_oper < Operators::Opers::non_const_int_flag && !t_lhs.is_const() && !t_lhs.is_return_value()) { + return binary_go(t_oper, *static_cast(t_lhs.get_ptr()), get_as_aux(t_rhs), t_lhs); + } else if (t_oper > Operators::Opers::const_flag) { + return const_binary_go(t_oper, get_as_aux(t_lhs), get_as_aux(t_rhs)); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + // Unary + template + static auto go(Operators::Opers t_oper, const Boxed_Value &t_lhs) + -> typename std::enable_if::value, Boxed_Value>::type + { + if (t_oper > Operators::Opers::non_const_flag && t_oper < Operators::Opers::non_const_int_flag && !t_lhs.is_const() && !t_lhs.is_return_value()) { + return unary_go(t_oper, *static_cast(t_lhs.get_ptr()), t_lhs); + } else if (t_oper > Operators::Opers::const_int_flag && t_oper < Operators::Opers::const_flag) { + return const_unary_int_go(t_oper, *static_cast(t_lhs.get_const_ptr())); + } else if (t_oper > Operators::Opers::const_flag) { + return const_unary_go(t_oper, *static_cast(t_lhs.get_const_ptr())); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + static auto go(Operators::Opers t_oper, const Boxed_Value &t_lhs) + -> typename std::enable_if::value, Boxed_Value>::type + { + if (t_oper > Operators::Opers::non_const_flag && t_oper < Operators::Opers::non_const_int_flag && !t_lhs.is_const() && !t_lhs.is_return_value()) { + return unary_go(t_oper, *static_cast(t_lhs.get_ptr()), t_lhs); + } else if (t_oper > Operators::Opers::const_flag) { + return const_unary_go(t_oper, *static_cast(t_lhs.get_const_ptr())); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + template + inline static Boxed_Value oper_rhs(Operators::Opers t_oper, const Boxed_Value &t_lhs, const Boxed_Value &t_rhs) + { + switch (get_common_type(t_rhs)) { + case Common_Types::t_int32: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_uint8: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_int8: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_uint16: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_int16: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_uint32: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_uint64: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_int64: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_double: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_float: + return go(t_oper, t_lhs, t_rhs); + case Common_Types::t_long_double: + return go(t_oper, t_lhs, t_rhs); + } + + throw chaiscript::detail::exception::bad_any_cast(); + } + + inline static Boxed_Value oper(Operators::Opers t_oper, const Boxed_Value &t_lhs) + { + switch (get_common_type(t_lhs)) { + case Common_Types::t_int32: + return go(t_oper, t_lhs); + case Common_Types::t_uint8: + return go(t_oper, t_lhs); + case Common_Types::t_int8: + return go(t_oper, t_lhs); + case Common_Types::t_uint16: + return go(t_oper, t_lhs); + case Common_Types::t_int16: + return go(t_oper, t_lhs); + case Common_Types::t_uint32: + return go(t_oper, t_lhs); + case Common_Types::t_uint64: + return go(t_oper, t_lhs); + case Common_Types::t_int64: + return go(t_oper, t_lhs); + case Common_Types::t_double: + return go(t_oper, t_lhs); + case Common_Types::t_float: + return go(t_oper, t_lhs); + case Common_Types::t_long_double: + return go(t_oper, t_lhs); + } + + throw chaiscript::detail::exception::bad_any_cast(); + } + + + inline static Boxed_Value oper(Operators::Opers t_oper, const Boxed_Value &t_lhs, const Boxed_Value &t_rhs) + { + switch (get_common_type(t_lhs)) { + case Common_Types::t_int32: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_uint8: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_int8: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_uint16: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_int16: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_uint32: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_uint64: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_int64: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_double: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_float: + return oper_rhs(t_oper, t_lhs, t_rhs); + case Common_Types::t_long_double: + return oper_rhs(t_oper, t_lhs, t_rhs); + } + + throw chaiscript::detail::exception::bad_any_cast(); + } + + template + static inline Target get_as_aux(const Boxed_Value &t_bv) + { + return static_cast(*static_cast(t_bv.get_const_ptr())); + } + + template + static std::string to_string_aux(const Boxed_Value &v) + { + std::ostringstream oss; + oss << *static_cast(v.get_const_ptr()); + return oss.str(); + } + + public: + Boxed_Number() + : bv(Boxed_Value(0)) + { + } + + explicit Boxed_Number(Boxed_Value v) + : bv(std::move(v)) + { + validate_boxed_number(bv); + } + + Boxed_Number(const Boxed_Number &) = default; + Boxed_Number(Boxed_Number &&) = default; + Boxed_Number& operator=(Boxed_Number &&) = default; + + template explicit Boxed_Number(T t) + : bv(Boxed_Value(t)) + { + validate_boxed_number(bv); + } + + static Boxed_Value clone(const Boxed_Value &t_bv) { + return Boxed_Number(t_bv).get_as(t_bv.get_type_info()).bv; + } + + static bool is_floating_point(const Boxed_Value &t_bv) + { + const Type_Info &inp_ = t_bv.get_type_info(); + + if (inp_ == typeid(double)) { + return true; + } else if (inp_ == typeid(long double)) { + return true; + } else if (inp_ == typeid(float)) { + return true; + } else { + return false; + } + } + + Boxed_Number get_as(const Type_Info &inp_) const + { + if (inp_.bare_equal_type_info(typeid(int))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(double))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(float))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(long double))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(char))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(unsigned char))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(wchar_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(char16_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(char32_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(unsigned int))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(long))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(long long))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(unsigned long))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(unsigned long long))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(int8_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(int16_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(int32_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(int64_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(uint8_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(uint16_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(uint32_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(uint64_t))) { + return Boxed_Number(get_as()); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + + } + + template + static void check_type() + { +#ifdef CHAISCRIPT_MSVC +// MSVC complains about this being redundant / tautologica l +#pragma warning(push) +#pragma warning(disable : 4127 6287) +#endif + if (sizeof(Source) != sizeof(Target) + || std::is_signed() != std::is_signed() + || std::is_floating_point() != std::is_floating_point()) + { + throw chaiscript::detail::exception::bad_any_cast(); + } +#ifdef CHAISCRIPT_MSVC +#pragma warning(pop) +#endif + } + + template Target get_as_checked() const + { + switch (get_common_type(bv)) { + case Common_Types::t_int32: + check_type(); + return get_as_aux(bv); + case Common_Types::t_uint8: + check_type(); + return get_as_aux(bv); + case Common_Types::t_int8: + check_type(); + return get_as_aux(bv); + case Common_Types::t_uint16: + check_type(); + return get_as_aux(bv); + case Common_Types::t_int16: + check_type(); + return get_as_aux(bv); + case Common_Types::t_uint32: + check_type(); + return get_as_aux(bv); + case Common_Types::t_uint64: + check_type(); + return get_as_aux(bv); + case Common_Types::t_int64: + check_type(); + return get_as_aux(bv); + case Common_Types::t_double: + check_type(); + return get_as_aux(bv); + case Common_Types::t_float: + check_type(); + return get_as_aux(bv); + case Common_Types::t_long_double: + check_type(); + return get_as_aux(bv); + } + + throw chaiscript::detail::exception::bad_any_cast(); + } + + + template Target get_as() const + { + switch (get_common_type(bv)) { + case Common_Types::t_int32: + return get_as_aux(bv); + case Common_Types::t_uint8: + return get_as_aux(bv); + case Common_Types::t_int8: + return get_as_aux(bv); + case Common_Types::t_uint16: + return get_as_aux(bv); + case Common_Types::t_int16: + return get_as_aux(bv); + case Common_Types::t_uint32: + return get_as_aux(bv); + case Common_Types::t_uint64: + return get_as_aux(bv); + case Common_Types::t_int64: + return get_as_aux(bv); + case Common_Types::t_double: + return get_as_aux(bv); + case Common_Types::t_float: + return get_as_aux(bv); + case Common_Types::t_long_double: + return get_as_aux(bv); + } + + throw chaiscript::detail::exception::bad_any_cast(); + } + + std::string to_string() const + { + switch (get_common_type(bv)) { + case Common_Types::t_int32: + return std::to_string(get_as()); + case Common_Types::t_uint8: + return std::to_string(get_as()); + case Common_Types::t_int8: + return std::to_string(get_as()); + case Common_Types::t_uint16: + return std::to_string(get_as()); + case Common_Types::t_int16: + return std::to_string(get_as()); + case Common_Types::t_uint32: + return std::to_string(get_as()); + case Common_Types::t_uint64: + return std::to_string(get_as()); + case Common_Types::t_int64: + return std::to_string(get_as()); + case Common_Types::t_double: + return to_string_aux(bv); + case Common_Types::t_float: + return to_string_aux(bv); + case Common_Types::t_long_double: + return to_string_aux(bv); + } + + throw chaiscript::detail::exception::bad_any_cast(); + } + + static void validate_boxed_number(const Boxed_Value &v) + { + const Type_Info &inp_ = v.get_type_info(); + if (inp_ == typeid(bool)) + { + throw chaiscript::detail::exception::bad_any_cast(); + } + + if (!inp_.is_arithmetic()) + { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + + + + static bool equals(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return boxed_cast(oper(Operators::Opers::equals, t_lhs.bv, t_rhs.bv)); + } + + static bool less_than(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return boxed_cast(oper(Operators::Opers::less_than, t_lhs.bv, t_rhs.bv)); + } + + static bool greater_than(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return boxed_cast(oper(Operators::Opers::greater_than, t_lhs.bv, t_rhs.bv)); + } + + static bool greater_than_equal(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return boxed_cast(oper(Operators::Opers::greater_than_equal, t_lhs.bv, t_rhs.bv)); + } + + static bool less_than_equal(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return boxed_cast(oper(Operators::Opers::less_than_equal, t_lhs.bv, t_rhs.bv)); + } + + static bool not_equal(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return boxed_cast(oper(Operators::Opers::not_equal, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number pre_decrement(Boxed_Number t_lhs) + { + return Boxed_Number(oper(Operators::Opers::pre_decrement, t_lhs.bv)); + } + + static Boxed_Number pre_increment(Boxed_Number t_lhs) + { + return Boxed_Number(oper(Operators::Opers::pre_increment, t_lhs.bv)); + } + + static const Boxed_Number sum(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::sum, t_lhs.bv, t_rhs.bv)); + } + + static const Boxed_Number unary_plus(const Boxed_Number &t_lhs) + { + return Boxed_Number(oper(Operators::Opers::unary_plus, t_lhs.bv)); + } + + static const Boxed_Number unary_minus(const Boxed_Number &t_lhs) + { + return Boxed_Number(oper(Operators::Opers::unary_minus, t_lhs.bv)); + } + + static const Boxed_Number difference(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::difference, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign_bitwise_and(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_bitwise_and, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign_bitwise_or(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_bitwise_or, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign_bitwise_xor(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_bitwise_xor, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign_remainder(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_remainder, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign_shift_left(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_shift_left, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign_shift_right(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_shift_right, t_lhs.bv, t_rhs.bv)); + } + + static const Boxed_Number bitwise_and(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::bitwise_and, t_lhs.bv, t_rhs.bv)); + } + + static const Boxed_Number bitwise_complement(const Boxed_Number &t_lhs) + { + return Boxed_Number(oper(Operators::Opers::bitwise_complement, t_lhs.bv, Boxed_Value(0))); + } + + static const Boxed_Number bitwise_xor(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::bitwise_xor, t_lhs.bv, t_rhs.bv)); + } + + static const Boxed_Number bitwise_or(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::bitwise_or, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign_product(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_product, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign_quotient(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_quotient, t_lhs.bv, t_rhs.bv)); + } + + static Boxed_Number assign_sum(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_sum, t_lhs.bv, t_rhs.bv)); + } + static Boxed_Number assign_difference(Boxed_Number t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::assign_difference, t_lhs.bv, t_rhs.bv)); + } + + static const Boxed_Number quotient(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::quotient, t_lhs.bv, t_rhs.bv)); + } + + static const Boxed_Number shift_left(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::shift_left, t_lhs.bv, t_rhs.bv)); + } + + static const Boxed_Number product(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::product, t_lhs.bv, t_rhs.bv)); + } + + static const Boxed_Number remainder(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::remainder, t_lhs.bv, t_rhs.bv)); + } + + static const Boxed_Number shift_right(const Boxed_Number &t_lhs, const Boxed_Number &t_rhs) + { + return Boxed_Number(oper(Operators::Opers::shift_right, t_lhs.bv, t_rhs.bv)); + } + + + + static Boxed_Value do_oper(Operators::Opers t_oper, const Boxed_Value &t_lhs, const Boxed_Value &t_rhs) + { + return oper(t_oper, t_lhs, t_rhs); + } + + static Boxed_Value do_oper(Operators::Opers t_oper, const Boxed_Value &t_lhs) + { + return oper(t_oper, t_lhs); + } + + + + Boxed_Value bv; + }; + + namespace detail + { + /// Cast_Helper for converting from Boxed_Value to Boxed_Number + template<> + struct Cast_Helper + { + static Boxed_Number cast(const Boxed_Value &ob, const Type_Conversions_State *) + { + return Boxed_Number(ob); + } + }; + + /// Cast_Helper for converting from Boxed_Value to Boxed_Number + template<> + struct Cast_Helper : Cast_Helper + { + }; + + /// Cast_Helper for converting from Boxed_Value to Boxed_Number + template<> + struct Cast_Helper : Cast_Helper + { + }; + } + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef CHAISCRIPT_MSVC +#pragma warning(pop) +#endif + +} + + + +#endif + diff --git a/chaiscript/dispatchkit/boxed_value.hpp b/chaiscript/dispatchkit/boxed_value.hpp new file mode 100644 index 0000000..ce943eb --- /dev/null +++ b/chaiscript/dispatchkit/boxed_value.hpp @@ -0,0 +1,485 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_BOXED_VALUE_HPP_ +#define CHAISCRIPT_BOXED_VALUE_HPP_ + +#include +#include +#include + +#include "../chaiscript_defines.hpp" +#include "any.hpp" +#include "type_info.hpp" + +namespace chaiscript +{ + + /// \brief A wrapper for holding any valid C++ type. All types in ChaiScript are Boxed_Value objects + /// \sa chaiscript::boxed_cast + class Boxed_Value + { + public: + /// used for explicitly creating a "void" object + struct Void_Type + { + }; + + private: + /// structure which holds the internal state of a Boxed_Value + /// \todo Get rid of Any and merge it with this, reducing an allocation in the process + struct Data + { + Data(const Type_Info &ti, + chaiscript::detail::Any to, + bool is_ref, + const void *t_void_ptr, + bool t_return_value) + : m_type_info(ti), m_obj(std::move(to)), m_data_ptr(ti.is_const()?nullptr:const_cast(t_void_ptr)), m_const_data_ptr(t_void_ptr), + m_is_ref(is_ref), m_return_value(t_return_value) + { + } + + Data &operator=(const Data &rhs) + { + m_type_info = rhs.m_type_info; + m_obj = rhs.m_obj; + m_is_ref = rhs.m_is_ref; + m_data_ptr = rhs.m_data_ptr; + m_const_data_ptr = rhs.m_const_data_ptr; + m_return_value = rhs.m_return_value; + + if (rhs.m_attrs) + { + m_attrs = std::make_unique>>(*rhs.m_attrs); + } + + return *this; + } + + Data(const Data &) = delete; + + Data(Data &&) = default; + Data &operator=(Data &&rhs) = default; + + + Type_Info m_type_info; + chaiscript::detail::Any m_obj; + void *m_data_ptr; + const void *m_const_data_ptr; + std::unique_ptr>> m_attrs; + bool m_is_ref; + bool m_return_value; + }; + + struct Object_Data + { + static auto get(Boxed_Value::Void_Type, bool t_return_value) + { + return std::make_shared( + detail::Get_Type_Info::get(), + chaiscript::detail::Any(), + false, + nullptr, + t_return_value) + ; + } + + template + static auto get(const std::shared_ptr *obj, bool t_return_value) + { + return get(*obj, t_return_value); + } + + template + static auto get(const std::shared_ptr &obj, bool t_return_value) + { + return std::make_shared( + detail::Get_Type_Info::get(), + chaiscript::detail::Any(obj), + false, + obj.get(), + t_return_value + ); + } + + template + static auto get(std::shared_ptr &&obj, bool t_return_value) + { + auto ptr = obj.get(); + return std::make_shared( + detail::Get_Type_Info::get(), + chaiscript::detail::Any(std::move(obj)), + false, + ptr, + t_return_value + ); + } + + + + template + static auto get(T *t, bool t_return_value) + { + return get(std::ref(*t), t_return_value); + } + + template + static auto get(const T *t, bool t_return_value) + { + return get(std::cref(*t), t_return_value); + } + + + template + static auto get(std::reference_wrapper obj, bool t_return_value) + { + auto p = &obj.get(); + return std::make_shared( + detail::Get_Type_Info::get(), + chaiscript::detail::Any(std::move(obj)), + true, + p, + t_return_value + ); + } + + template + static auto get(std::unique_ptr &&obj, bool t_return_value) + { + auto ptr = obj.get(); + return std::make_shared( + detail::Get_Type_Info::get(), + chaiscript::detail::Any(std::make_shared>(std::move(obj))), + true, + ptr, + t_return_value + ); + } + + template + static auto get(T t, bool t_return_value) + { + auto p = std::make_shared(std::move(t)); + auto ptr = p.get(); + return std::make_shared( + detail::Get_Type_Info::get(), + chaiscript::detail::Any(std::move(p)), + false, + ptr, + t_return_value + ); + } + + static std::shared_ptr get() + { + return std::make_shared( + Type_Info(), + chaiscript::detail::Any(), + false, + nullptr, + false + ); + } + + }; + + public: + /// Basic Boxed_Value constructor + template::type>::value>::type> + explicit Boxed_Value(T &&t, bool t_return_value = false) + : m_data(Object_Data::get(std::forward(t), t_return_value)) + { + } + + /// Unknown-type constructor + Boxed_Value() = default; + + Boxed_Value(Boxed_Value&&) = default; + Boxed_Value& operator=(Boxed_Value&&) = default; + Boxed_Value(const Boxed_Value&) = default; + Boxed_Value& operator=(const Boxed_Value&) = default; + + void swap(Boxed_Value &rhs) + { + std::swap(m_data, rhs.m_data); + } + + /// Copy the values stored in rhs.m_data to m_data. + /// m_data pointers are not shared in this case + Boxed_Value assign(const Boxed_Value &rhs) + { + (*m_data) = (*rhs.m_data); + return *this; + } + + const Type_Info &get_type_info() const noexcept + { + return m_data->m_type_info; + } + + /// return true if the object is uninitialized + bool is_undef() const noexcept + { + return m_data->m_type_info.is_undef(); + } + + bool is_const() const noexcept + { + return m_data->m_type_info.is_const(); + } + + bool is_type(const Type_Info &ti) const noexcept + { + return m_data->m_type_info.bare_equal(ti); + } + + + template + auto pointer_sentinel(std::shared_ptr &ptr) const + { + struct Sentinel { + Sentinel(std::shared_ptr &t_ptr, Data &data) + : m_ptr(t_ptr), m_data(data) + { + } + + ~Sentinel() + { + // save new pointer data + const auto ptr_ = m_ptr.get().get(); + m_data.get().m_data_ptr = ptr_; + m_data.get().m_const_data_ptr = ptr_; + } + + Sentinel& operator=(Sentinel&&s) = default; + Sentinel(Sentinel &&s) = default; + + operator std::shared_ptr&() const + { + return m_ptr.get(); + } + + Sentinel &operator=(const Sentinel &) = delete; + Sentinel(Sentinel&) = delete; + + std::reference_wrapper> m_ptr; + std::reference_wrapper m_data; + }; + + return Sentinel(ptr, *(m_data.get())); + } + + bool is_null() const noexcept + { + return (m_data->m_data_ptr == nullptr && m_data->m_const_data_ptr == nullptr); + } + + const chaiscript::detail::Any & get() const noexcept + { + return m_data->m_obj; + } + + bool is_ref() const noexcept + { + return m_data->m_is_ref; + } + + bool is_return_value() const noexcept + { + return m_data->m_return_value; + } + + void reset_return_value() const noexcept + { + m_data->m_return_value = false; + } + + bool is_pointer() const noexcept + { + return !is_ref(); + } + + void *get_ptr() const noexcept + { + return m_data->m_data_ptr; + } + + const void *get_const_ptr() const noexcept + { + return m_data->m_const_data_ptr; + } + + Boxed_Value get_attr(const std::string &t_name) + { + if (!m_data->m_attrs) + { + m_data->m_attrs = std::make_unique>>(); + } + + auto &attr = (*m_data->m_attrs)[t_name]; + if (attr) { + return Boxed_Value(attr, Internal_Construction()); + } else { + Boxed_Value bv; //default construct a new one + attr = bv.m_data; + return bv; + } + } + + Boxed_Value ©_attrs(const Boxed_Value &t_obj) + { + if (t_obj.m_data->m_attrs) + { + m_data->m_attrs = std::make_unique>>(*t_obj.m_data->m_attrs); + } + return *this; + } + + Boxed_Value &clone_attrs(const Boxed_Value &t_obj) + { + copy_attrs(t_obj); + reset_return_value(); + return *this; + } + + + /// \returns true if the two Boxed_Values share the same internal type + static bool type_match(const Boxed_Value &l, const Boxed_Value &r) noexcept + { + return l.get_type_info() == r.get_type_info(); + } + + private: + // necessary to avoid hitting the templated && constructor of Boxed_Value + struct Internal_Construction{}; + + Boxed_Value(std::shared_ptr t_data, Internal_Construction) + : m_data(std::move(t_data)) { + } + + std::shared_ptr m_data = Object_Data::get(); + }; + + /// @brief Creates a Boxed_Value. If the object passed in is a value type, it is copied. If it is a pointer, std::shared_ptr, or std::reference_type + /// a copy is not made. + /// @param t The value to box + /// + /// Example: + /// + /// ~~~{.cpp} + /// int i; + /// chaiscript::ChaiScript chai; + /// chai.add(chaiscript::var(i), "i"); + /// chai.add(chaiscript::var(&i), "ip"); + /// ~~~ + /// + /// @sa @ref adding_objects + template + Boxed_Value var(T &&t) + { + return Boxed_Value(std::forward(t)); + } + + namespace detail { + /// \brief Takes a value, copies it and returns a Boxed_Value object that is immutable + /// \param[in] t Value to copy and make const + /// \returns Immutable Boxed_Value + /// \sa Boxed_Value::is_const + template + Boxed_Value const_var_impl(const T &t) + { + return Boxed_Value(std::make_shared::type >(t)); + } + + /// \brief Takes a pointer to a value, adds const to the pointed to type and returns an immutable Boxed_Value. + /// Does not copy the pointed to value. + /// \param[in] t Pointer to make immutable + /// \returns Immutable Boxed_Value + /// \sa Boxed_Value::is_const + template + Boxed_Value const_var_impl(T *t) + { + return Boxed_Value( const_cast::type *>(t) ); + } + + /// \brief Takes a std::shared_ptr to a value, adds const to the pointed to type and returns an immutable Boxed_Value. + /// Does not copy the pointed to value. + /// \param[in] t Pointer to make immutable + /// \returns Immutable Boxed_Value + /// \sa Boxed_Value::is_const + template + Boxed_Value const_var_impl(const std::shared_ptr &t) + { + return Boxed_Value( std::const_pointer_cast::type>(t) ); + } + + /// \brief Takes a std::reference_wrapper value, adds const to the referenced type and returns an immutable Boxed_Value. + /// Does not copy the referenced value. + /// \param[in] t Reference object to make immutable + /// \returns Immutable Boxed_Value + /// \sa Boxed_Value::is_const + template + Boxed_Value const_var_impl(const std::reference_wrapper &t) + { + return Boxed_Value( std::cref(t.get()) ); + } + } + + /// \brief Takes an object and returns an immutable Boxed_Value. If the object is a std::reference or pointer type + /// the value is not copied. If it is an object type, it is copied. + /// \param[in] t Object to make immutable + /// \returns Immutable Boxed_Value + /// \sa chaiscript::Boxed_Value::is_const + /// \sa chaiscript::var + /// + /// Example: + /// \code + /// enum Colors + /// { + /// Blue, + /// Green, + /// Red + /// }; + /// chaiscript::ChaiScript chai + /// chai.add(chaiscript::const_var(Blue), "Blue"); // add immutable constant + /// chai.add(chaiscript::const_var(Red), "Red"); + /// chai.add(chaiscript::const_var(Green), "Green"); + /// \endcode + /// + /// \todo support C++11 strongly typed enums + /// \sa \ref adding_objects + template + Boxed_Value const_var(const T &t) + { + return detail::const_var_impl(t); + } + + inline Boxed_Value void_var() { + static const auto v = Boxed_Value(Boxed_Value::Void_Type()); + return v; + } + + inline Boxed_Value const_var(bool b) { + static const auto t = detail::const_var_impl(true); + static const auto f = detail::const_var_impl(false); + + if (b) { + return t; + } else { + return f; + } + } + +} + +#endif + diff --git a/chaiscript/dispatchkit/callable_traits.hpp b/chaiscript/dispatchkit/callable_traits.hpp new file mode 100644 index 0000000..4b61b08 --- /dev/null +++ b/chaiscript/dispatchkit/callable_traits.hpp @@ -0,0 +1,107 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_CALLABLE_TRAITS_HPP_ +#define CHAISCRIPT_CALLABLE_TRAITS_HPP_ + +#include + +namespace chaiscript { + namespace dispatch { + namespace detail { + + template + struct Constructor + { + template + std::shared_ptr operator()(Inner&& ... inner) const { + return std::make_shared(std::forward(inner)...); + } + }; + + template + struct Const_Caller + { + explicit Const_Caller(Ret (Class::*t_func)(Param...) const) : m_func(t_func) {} + + template + Ret operator()(const Class &o, Inner&& ... inner) const { + return (o.*m_func)(std::forward(inner)...); + } + + Ret (Class::*m_func)(Param...) const; + }; + + template + struct Fun_Caller + { + explicit Fun_Caller(Ret( * t_func)(Param...) ) : m_func(t_func) {} + + template + Ret operator()(Inner&& ... inner) const { + return (m_func)(std::forward(inner)...); + } + + Ret(*m_func)(Param...); + }; + + template + struct Caller + { + explicit Caller(Ret (Class::*t_func)(Param...)) : m_func(t_func) {} + + template + Ret operator()(Class &o, Inner&& ... inner) const { + return (o.*m_func)(std::forward(inner)...); + } + + Ret (Class::*m_func)(Param...); + }; + + template + struct Arity + { + }; + + template + struct Arity + { + static const size_t arity = sizeof...(Params); + }; + + + template + struct Function_Signature + { + }; + + template + struct Function_Signature + { + typedef Ret Return_Type; + typedef Ret (Signature)(Params...); + }; + + template + struct Function_Signature + { + typedef Ret Return_Type; + typedef Ret (Signature)(Params...); + }; + + + template + struct Callable_Traits + { + typedef typename Function_Signature::Signature Signature; + typedef typename Function_Signature::Return_Type Return_Type; + }; + } + } +} + +#endif + diff --git a/chaiscript/dispatchkit/dispatchkit.hpp b/chaiscript/dispatchkit/dispatchkit.hpp new file mode 100644 index 0000000..05f0253 --- /dev/null +++ b/chaiscript/dispatchkit/dispatchkit.hpp @@ -0,0 +1,1564 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_DISPATCHKIT_HPP_ +#define CHAISCRIPT_DISPATCHKIT_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../chaiscript_defines.hpp" +#include "../chaiscript_threading.hpp" +#include "bad_boxed_cast.hpp" +#include "boxed_cast.hpp" +#include "boxed_cast_helper.hpp" +#include "boxed_value.hpp" +#include "type_conversions.hpp" +#include "dynamic_object.hpp" +#include "proxy_constructors.hpp" +#include "proxy_functions.hpp" +#include "type_info.hpp" +#include "short_alloc.hpp" + +namespace chaiscript { +class Boxed_Number; +} // namespace chaiscript + +namespace chaiscript { + namespace parser { + class ChaiScript_Parser_Base; + } +namespace dispatch { +class Dynamic_Proxy_Function; +class Proxy_Function_Base; +struct Placeholder_Object; +} // namespace dispatch +} // namespace chaiscript + + + +/// \namespace chaiscript::dispatch +/// \brief Classes and functions specific to the runtime dispatch side of ChaiScript. Some items may be of use to the end user. + +namespace chaiscript +{ + namespace exception + { + /// Exception thrown in the case that an object name is invalid because it is a reserved word + class reserved_word_error : public std::runtime_error + { + public: + explicit reserved_word_error(const std::string &t_word) noexcept + : std::runtime_error("Reserved word not allowed in object name: " + t_word), m_word(t_word) + { + } + + reserved_word_error(const reserved_word_error &) = default; + + ~reserved_word_error() noexcept override = default; + + std::string word() const + { + return m_word; + } + + private: + std::string m_word; + }; + + /// Exception thrown in the case that an object name is invalid because it contains illegal characters + class illegal_name_error : public std::runtime_error + { + public: + explicit illegal_name_error(const std::string &t_name) noexcept + : std::runtime_error("Reserved name not allowed in object name: " + t_name), m_name(t_name) + { + } + + illegal_name_error(const illegal_name_error &) = default; + + ~illegal_name_error() noexcept override = default; + + std::string name() const + { + return m_name; + } + + private: + std::string m_name; + }; + + + /// Exception thrown in the case that an object name is invalid because it already exists in current context + class name_conflict_error : public std::runtime_error + { + public: + explicit name_conflict_error(const std::string &t_name) noexcept + : std::runtime_error("Name already exists in current context " + t_name), m_name(t_name) + { + } + + name_conflict_error(const name_conflict_error &) = default; + + ~name_conflict_error() noexcept override = default; + + std::string name() const + { + return m_name; + } + + private: + std::string m_name; + + }; + + + /// Exception thrown in the case that a non-const object was added as a shared object + class global_non_const : public std::runtime_error + { + public: + global_non_const() noexcept + : std::runtime_error("a global object must be const") + { + } + + global_non_const(const global_non_const &) = default; + ~global_non_const() noexcept override = default; + }; + } + + + /// \brief Holds a collection of ChaiScript settings which can be applied to the ChaiScript runtime. + /// Used to implement loadable module support. + class Module + { + public: + Module &add(Type_Info ti, std::string name) + { + m_typeinfos.emplace_back(ti, std::move(name)); + return *this; + } + + Module &add(Type_Conversion d) + { + m_conversions.push_back(std::move(d)); + return *this; + } + + Module &add(Proxy_Function f, std::string name) + { + m_funcs.emplace_back(std::move(f), std::move(name)); + return *this; + } + + Module &add_global_const(Boxed_Value t_bv, std::string t_name) + { + if (!t_bv.is_const()) + { + throw chaiscript::exception::global_non_const(); + } + + m_globals.emplace_back(std::move(t_bv), std::move(t_name)); + return *this; + } + + + //Add a bit of ChaiScript to eval during module implementation + Module &eval(const std::string &str) + { + m_evals.push_back(str); + return *this; + } + + template + void apply(Eval &t_eval, Engine &t_engine) const + { + apply(m_typeinfos.begin(), m_typeinfos.end(), t_engine); + apply(m_funcs.begin(), m_funcs.end(), t_engine); + apply_eval(m_evals.begin(), m_evals.end(), t_eval); + apply_single(m_conversions.begin(), m_conversions.end(), t_engine); + apply_globals(m_globals.begin(), m_globals.end(), t_engine); + } + + bool has_function(const Proxy_Function &new_f, const std::string &name) + { + return std::any_of(m_funcs.begin(), m_funcs.end(), + [&](const std::pair &existing_f) { + return existing_f.second == name && *(existing_f.first) == *(new_f); + } + ); + } + + + private: + std::vector> m_typeinfos; + std::vector> m_funcs; + std::vector> m_globals; + std::vector m_evals; + std::vector m_conversions; + + template + static void apply(InItr begin, const InItr end, T &t) + { + for_each(begin, end, + [&t](const auto &obj) { + try { + t.add(obj.first, obj.second); + } catch (const chaiscript::exception::name_conflict_error &) { + /// \todo Should we throw an error if there's a name conflict + /// while applying a module? + } + } + ); + } + + template + static void apply_globals(InItr begin, InItr end, T &t) + { + while (begin != end) + { + t.add_global_const(begin->first, begin->second); + ++begin; + } + } + + template + static void apply_single(InItr begin, InItr end, T &t) + { + while (begin != end) + { + t.add(*begin); + ++begin; + } + } + + template + static void apply_eval(InItr begin, InItr end, T &t) + { + while (begin != end) + { + t.eval(*begin); + ++begin; + } + } + }; + + /// Convenience typedef for Module objects to be added to the ChaiScript runtime + typedef std::shared_ptr ModulePtr; + + namespace detail + { + /// A Proxy_Function implementation that is able to take + /// a vector of Proxy_Functions and perform a dispatch on them. It is + /// used specifically in the case of dealing with Function object variables + class Dispatch_Function final : public dispatch::Proxy_Function_Base + { + public: + explicit Dispatch_Function(std::vector t_funcs) + : Proxy_Function_Base(build_type_infos(t_funcs), calculate_arity(t_funcs)), + m_funcs(std::move(t_funcs)) + { + } + + bool operator==(const dispatch::Proxy_Function_Base &rhs) const override + { + try { + const auto &dispatch_fun = dynamic_cast(rhs); + return m_funcs == dispatch_fun.m_funcs; + } catch (const std::bad_cast &) { + return false; + } + } + + std::vector get_contained_functions() const override + { + return std::vector(m_funcs.begin(), m_funcs.end()); + } + + + static int calculate_arity(const std::vector &t_funcs) + { + if (t_funcs.empty()) { + return -1; + } + + const auto arity = t_funcs.front()->get_arity(); + + for (const auto &func : t_funcs) + { + if (arity != func->get_arity()) + { + // The arities in the list do not match, so it's unspecified + return -1; + } + } + + return arity; + } + + bool call_match(const std::vector &vals, const Type_Conversions_State &t_conversions) const override + { + return std::any_of(std::begin(m_funcs), std::end(m_funcs), + [&vals, &t_conversions](const Proxy_Function &f){ return f->call_match(vals, t_conversions); }); + } + + protected: + Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const override + { + return dispatch::dispatch(m_funcs, params, t_conversions); + } + + private: + std::vector m_funcs; + + static std::vector build_type_infos(const std::vector &t_funcs) + { + auto begin = t_funcs.cbegin(); + const auto &end = t_funcs.cend(); + + if (begin != end) + { + std::vector type_infos = (*begin)->get_param_types(); + + ++begin; + + bool size_mismatch = false; + + while (begin != end) + { + std::vector param_types = (*begin)->get_param_types(); + + if (param_types.size() != type_infos.size()) + { + size_mismatch = true; + } + + for (size_t i = 0; i < type_infos.size() && i < param_types.size(); ++i) + { + if (!(type_infos[i] == param_types[i])) + { + type_infos[i] = detail::Get_Type_Info::get(); + } + } + + ++begin; + } + + assert(!type_infos.empty() && " type_info vector size is < 0, this is only possible if something else is broken"); + + if (size_mismatch) + { + type_infos.resize(1); + } + + return type_infos; + } + + return std::vector(); + } + }; + } + + + namespace detail + { + struct Stack_Holder + { + //template + // using SmallVector = std::vector>; + + template + using SmallVector = std::vector; + + + typedef SmallVector> Scope; + typedef SmallVector StackData; + typedef SmallVector Stacks; + typedef SmallVector Call_Param_List; + typedef SmallVector Call_Params; + + Stack_Holder() + { + push_stack(); + push_call_params(); + } + + void push_stack_data() + { + stacks.back().emplace_back(); +// stacks.back().emplace_back(Scope(scope_allocator)); + } + + void push_stack() + { + stacks.emplace_back(1); +// stacks.emplace_back(StackData(1, Scope(scope_allocator), stack_data_allocator)); + } + + void push_call_params() + { + call_params.emplace_back(); +// call_params.emplace_back(Call_Param_List(call_param_list_allocator)); + } + + //Scope::allocator_type::arena_type scope_allocator; + //StackData::allocator_type::arena_type stack_data_allocator; + //Stacks::allocator_type::arena_type stacks_allocator; + //Call_Param_List::allocator_type::arena_type call_param_list_allocator; + //Call_Params::allocator_type::arena_type call_params_allocator; + +// Stacks stacks = Stacks(stacks_allocator); +// Call_Params call_params = Call_Params(call_params_allocator); + + Stacks stacks; + Call_Params call_params; + + int call_depth = 0; + }; + + /// Main class for the dispatchkit. Handles management + /// of the object stack, functions and registered types. + class Dispatch_Engine + { + + public: + typedef std::map Type_Name_Map; + typedef std::vector> Scope; + typedef Stack_Holder::StackData StackData; + + struct State + { + std::vector>>> m_functions; + std::vector> m_function_objects; + std::vector> m_boxed_functions; + std::map m_global_objects; + Type_Name_Map m_types; + }; + + explicit Dispatch_Engine(chaiscript::parser::ChaiScript_Parser_Base &parser) + : m_stack_holder(), + m_parser(parser) + { + } + + /// \brief casts an object while applying any Dynamic_Conversion available + template + decltype(auto) boxed_cast(const Boxed_Value &bv) const + { + Type_Conversions_State state(m_conversions, m_conversions.conversion_saves()); + return(chaiscript::boxed_cast(bv, &state)); + } + + /// Add a new conversion for upcasting to a base class + void add(const Type_Conversion &d) + { + m_conversions.add_conversion(d); + } + + /// Add a new named Proxy_Function to the system + void add(const Proxy_Function &f, const std::string &name) + { + add_function(f, name); + } + + /// Set the value of an object, by name. If the object + /// is not available in the current scope it is created + void add(Boxed_Value obj, const std::string &name) + { + auto &stack = get_stack_data(); + + for (auto stack_elem = stack.rbegin(); stack_elem != stack.rend(); ++stack_elem) + { + auto itr = std::find_if(stack_elem->begin(), stack_elem->end(), + [&](const std::pair &o) { + return o.first == name; + }); + + if (itr != stack_elem->end()) + { + itr->second = std::move(obj); + return; + } + } + + add_object(name, std::move(obj)); + } + + /// Adds a named object to the current scope + /// \warning This version does not check the validity of the name + /// it is meant for internal use only + Boxed_Value &add_get_object(const std::string &t_name, Boxed_Value obj, Stack_Holder &t_holder) + { + auto &stack_elem = get_stack_data(t_holder).back(); + + if (std::any_of(stack_elem.begin(), stack_elem.end(), + [&](const std::pair &o) { + return o.first == t_name; + })) + { + throw chaiscript::exception::name_conflict_error(t_name); + } + + stack_elem.emplace_back(t_name, std::move(obj)); + return stack_elem.back().second; + } + + + /// Adds a named object to the current scope + /// \warning This version does not check the validity of the name + /// it is meant for internal use only + void add_object(const std::string &t_name, Boxed_Value obj, Stack_Holder &t_holder) + { + auto &stack_elem = get_stack_data(t_holder).back(); + + if (std::any_of(stack_elem.begin(), stack_elem.end(), + [&](const std::pair &o) { + return o.first == t_name; + })) + { + throw chaiscript::exception::name_conflict_error(t_name); + } + + stack_elem.emplace_back(t_name, std::move(obj)); + } + + + /// Adds a named object to the current scope + /// \warning This version does not check the validity of the name + /// it is meant for internal use only + void add_object(const std::string &name, Boxed_Value obj) + { + add_object(name, std::move(obj), get_stack_holder()); + } + + /// Adds a new global shared object, between all the threads + void add_global_const(const Boxed_Value &obj, const std::string &name) + { + if (!obj.is_const()) + { + throw chaiscript::exception::global_non_const(); + } + + chaiscript::detail::threading::unique_lock l(m_mutex); + + if (m_state.m_global_objects.find(name) != m_state.m_global_objects.end()) + { + throw chaiscript::exception::name_conflict_error(name); + } else { + m_state.m_global_objects.insert(std::make_pair(name, obj)); + } + } + + /// Adds a new global (non-const) shared object, between all the threads + Boxed_Value add_global_no_throw(const Boxed_Value &obj, const std::string &name) + { + chaiscript::detail::threading::unique_lock l(m_mutex); + + const auto itr = m_state.m_global_objects.find(name); + if (itr == m_state.m_global_objects.end()) + { + m_state.m_global_objects.insert(std::make_pair(name, obj)); + return obj; + } else { + return itr->second; + } + } + + + /// Adds a new global (non-const) shared object, between all the threads + void add_global(const Boxed_Value &obj, const std::string &name) + { + chaiscript::detail::threading::unique_lock l(m_mutex); + + if (m_state.m_global_objects.find(name) != m_state.m_global_objects.end()) + { + throw chaiscript::exception::name_conflict_error(name); + } else { + m_state.m_global_objects.insert(std::make_pair(name, obj)); + } + } + + /// Updates an existing global shared object or adds a new global shared object if not found + void set_global(const Boxed_Value &obj, const std::string &name) + { + chaiscript::detail::threading::unique_lock l(m_mutex); + + const auto itr = m_state.m_global_objects.find(name); + if (itr != m_state.m_global_objects.end()) + { + itr->second.assign(obj); + } else { + m_state.m_global_objects.insert(std::make_pair(name, obj)); + } + } + + /// Adds a new scope to the stack + void new_scope() + { + new_scope(*m_stack_holder); + } + + /// Pops the current scope from the stack + void pop_scope() + { + pop_scope(*m_stack_holder); + } + + /// Adds a new scope to the stack + static void new_scope(Stack_Holder &t_holder) + { + t_holder.push_stack_data(); + t_holder.push_call_params(); + } + + /// Pops the current scope from the stack + static void pop_scope(Stack_Holder &t_holder) + { + t_holder.call_params.pop_back(); + StackData &stack = get_stack_data(t_holder); + + assert(!stack.empty()); + + stack.pop_back(); + } + + + /// Pushes a new stack on to the list of stacks + static void new_stack(Stack_Holder &t_holder) + { + // add a new Stack with 1 element + t_holder.push_stack(); + } + + static void pop_stack(Stack_Holder &t_holder) + { + t_holder.stacks.pop_back(); + } + + /// Searches the current stack for an object of the given name + /// includes a special overload for the _ place holder object to + /// ensure that it is always in scope. + Boxed_Value get_object(const std::string &name, std::atomic_uint_fast32_t &t_loc, Stack_Holder &t_holder) const + { + enum class Loc : uint_fast32_t { + located = 0x80000000, + is_local = 0x40000000, + stack_mask = 0x0FFF0000, + loc_mask = 0x0000FFFF + }; + + uint_fast32_t loc = t_loc; + + if (loc == 0) + { + auto &stack = get_stack_data(t_holder); + + // Is it in the stack? + for (auto stack_elem = stack.rbegin(); stack_elem != stack.rend(); ++stack_elem) + { + for (auto s = stack_elem->begin(); s != stack_elem->end(); ++s ) + { + if (s->first == name) { + t_loc = static_cast(std::distance(stack.rbegin(), stack_elem) << 16) + | static_cast(std::distance(stack_elem->begin(), s)) + | static_cast(Loc::located) + | static_cast(Loc::is_local); + return s->second; + } + } + } + + t_loc = static_cast(Loc::located); + } else if ((loc & static_cast(Loc::is_local)) != 0u) { + auto &stack = get_stack_data(t_holder); + + return stack[stack.size() - 1 - ((loc & static_cast(Loc::stack_mask)) >> 16)][loc & static_cast(Loc::loc_mask)].second; + } + + // Is the value we are looking for a global or function? + chaiscript::detail::threading::shared_lock l(m_mutex); + + const auto itr = m_state.m_global_objects.find(name); + if (itr != m_state.m_global_objects.end()) + { + return itr->second; + } + + // no? is it a function object? + auto obj = get_function_object_int(name, loc); + if (obj.first != loc) { t_loc = uint_fast32_t(obj.first); } + + return obj.second; + + } + + /// Registers a new named type + void add(const Type_Info &ti, const std::string &name) + { + add_global_const(const_var(ti), name + "_type"); + + chaiscript::detail::threading::unique_lock l(m_mutex); + + m_state.m_types.insert(std::make_pair(name, ti)); + } + + /// Returns the type info for a named type + Type_Info get_type(const std::string &name, bool t_throw = true) const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + const auto itr = m_state.m_types.find(name); + + if (itr != m_state.m_types.end()) + { + return itr->second; + } + + if (t_throw) { + throw std::range_error("Type Not Known: " + name); + } else { + return Type_Info(); + } + } + + /// Returns the registered name of a known type_info object + /// compares the "bare_type_info" for the broadest possible + /// match + std::string get_type_name(const Type_Info &ti) const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + for (const auto & elem : m_state.m_types) + { + if (elem.second.bare_equal(ti)) + { + return elem.first; + } + } + + return ti.bare_name(); + } + + /// Return all registered types + std::vector > get_types() const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + return std::vector >(m_state.m_types.begin(), m_state.m_types.end()); + } + + std::shared_ptr> get_method_missing_functions() const + { + uint_fast32_t method_missing_loc = m_method_missing_loc; + auto method_missing_funs = get_function("method_missing", method_missing_loc); + if (method_missing_funs.first != method_missing_loc) { + m_method_missing_loc = uint_fast32_t(method_missing_funs.first); + } + + return std::move(method_missing_funs.second); + } + + + /// Return a function by name + std::pair>> get_function(const std::string &t_name, const size_t t_hint) const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + const auto &funs = get_functions_int(); + + auto itr = find_keyed_value(funs, t_name, t_hint); + + if (itr != funs.end()) + { + return std::make_pair(std::distance(funs.begin(), itr), itr->second); + } else { + return std::make_pair(size_t(0), std::make_shared>()); + } + } + + /// \returns a function object (Boxed_Value wrapper) if it exists + /// \throws std::range_error if it does not + Boxed_Value get_function_object(const std::string &t_name) const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + return get_function_object_int(t_name, 0).second; + } + + /// \returns a function object (Boxed_Value wrapper) if it exists + /// \throws std::range_error if it does not + /// \warn does not obtain a mutex lock. \sa get_function_object for public version + std::pair get_function_object_int(const std::string &t_name, const size_t t_hint) const + { + const auto &funs = get_boxed_functions_int(); + + auto itr = find_keyed_value(funs, t_name, t_hint); + + if (itr != funs.end()) + { + return std::make_pair(std::distance(funs.begin(), itr), itr->second); + } else { + throw std::range_error("Object not found: " + t_name); + } + } + + + /// Return true if a function exists + bool function_exists(const std::string &name) const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + const auto &functions = get_functions_int(); + return find_keyed_value(functions, name) != functions.end(); + } + + /// \returns All values in the local thread state in the parent scope, or if it doesn't exist, + /// the current scope. + std::map get_parent_locals() const + { + auto &stack = get_stack_data(); + if (stack.size() > 1) + { + return std::map(stack[1].begin(), stack[1].end()); + } else { + return std::map(stack[0].begin(), stack[0].end()); + } + } + + /// \returns All values in the local thread state, added through the add() function + std::map get_locals() const + { + auto &stack = get_stack_data(); + auto &scope = stack.front(); + return std::map(scope.begin(), scope.end()); + } + + /// \brief Sets all of the locals for the current thread state. + /// + /// \param[in] t_locals The map set of variables to replace the current state with + /// + /// Any existing locals are removed and the given set of variables is added + void set_locals(const std::map &t_locals) + { + auto &stack = get_stack_data(); + auto &scope = stack.front(); + scope.assign(t_locals.begin(), t_locals.end()); + } + + + + /// + /// Get a map of all objects that can be seen from the current scope in a scripting context + /// + std::map get_scripting_objects() const + { + const Stack_Holder &s = *m_stack_holder; + + // We don't want the current context, but one up if it exists + const StackData &stack = (s.stacks.size()==1)?(s.stacks.back()):(s.stacks[s.stacks.size()-2]); + + std::map retval; + + // note: map insert doesn't overwrite existing values, which is why this works + for (auto itr = stack.rbegin(); itr != stack.rend(); ++itr) + { + retval.insert(itr->begin(), itr->end()); + } + + // add the global values + chaiscript::detail::threading::shared_lock l(m_mutex); + retval.insert(m_state.m_global_objects.begin(), m_state.m_global_objects.end()); + + return retval; + } + + + /// + /// Get a map of all functions that can be seen from a scripting context + /// + std::map get_function_objects() const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + const auto &funs = get_function_objects_int(); + + std::map objs; + + for (const auto & fun : funs) + { + objs.insert(std::make_pair(fun.first, const_var(fun.second))); + } + + return objs; + } + + + /// Get a vector of all registered functions + std::vector > get_functions() const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + std::vector > rets; + + const auto &functions = get_functions_int(); + + for (const auto & function : functions) + { + for (const auto & internal_func : *function.second) + { + rets.emplace_back(function.first, internal_func); + } + } + + return rets; + } + + + const Type_Conversions &conversions() const + { + return m_conversions; + } + + static bool is_attribute_call(const std::vector &t_funs, const std::vector &t_params, + bool t_has_params, const Type_Conversions_State &t_conversions) + { + if (!t_has_params || t_params.empty()) { + return false; + } + + return std::any_of(std::begin(t_funs), std::end(t_funs), + [&](const auto &fun) { + return fun->is_attribute_function() && fun->compare_first_type(t_params[0], t_conversions); + } + ); + + } + +#ifdef CHAISCRIPT_MSVC +// MSVC is unable to recognize that "rethrow_exception" causes the function to return +// so we must disable it here. +#pragma warning(push) +#pragma warning(disable : 4715) +#endif + Boxed_Value call_member(const std::string &t_name, std::atomic_uint_fast32_t &t_loc, const std::vector ¶ms, bool t_has_params, + const Type_Conversions_State &t_conversions) + { + uint_fast32_t loc = t_loc; + const auto funs = get_function(t_name, loc); + if (funs.first != loc) { t_loc = uint_fast32_t(funs.first); } + + const auto do_attribute_call = + [this](int l_num_params, const std::vector &l_params, const std::vector &l_funs, const Type_Conversions_State &l_conversions)->Boxed_Value + { + std::vector attr_params{l_params.begin(), l_params.begin() + l_num_params}; + Boxed_Value bv = dispatch::dispatch(l_funs, attr_params, l_conversions); + if (l_num_params < int(l_params.size()) || bv.get_type_info().bare_equal(user_type())) { + struct This_Foist { + This_Foist(Dispatch_Engine &e, const Boxed_Value &t_bv) : m_e(e) { + m_e.get().new_scope(); + m_e.get().add_object("__this", t_bv); + } + + ~This_Foist() { + m_e.get().pop_scope(); + } + + std::reference_wrapper m_e; + }; + + This_Foist fi(*this, l_params.front()); + + try { + auto func = boxed_cast(bv); + try { + return (*func)({l_params.begin() + l_num_params, l_params.end()}, l_conversions); + } catch (const chaiscript::exception::bad_boxed_cast &) { + } catch (const chaiscript::exception::arity_error &) { + } catch (const chaiscript::exception::guard_error &) { + } + throw chaiscript::exception::dispatch_error({l_params.begin() + l_num_params, l_params.end()}, + std::vector{boxed_cast(bv)}); + } catch (const chaiscript::exception::bad_boxed_cast &) { + // unable to convert bv into a Proxy_Function_Base + throw chaiscript::exception::dispatch_error({l_params.begin() + l_num_params, l_params.end()}, + std::vector(l_funs.begin(), l_funs.end())); + } + } else { + return bv; + } + }; + + if (is_attribute_call(*funs.second, params, t_has_params, t_conversions)) { + return do_attribute_call(1, params, *funs.second, t_conversions); + } else { + std::exception_ptr except; + + if (!funs.second->empty()) { + try { + return dispatch::dispatch(*funs.second, params, t_conversions); + } catch(chaiscript::exception::dispatch_error&) { + except = std::current_exception(); + } + } + + // If we get here we know that either there was no method with that name, + // or there was no matching method + + const auto functions = [&]()->std::vector { + std::vector fs; + + const auto method_missing_funs = get_method_missing_functions(); + + for (const auto &f : *method_missing_funs) + { + if(f->compare_first_type(params[0], t_conversions)) { + fs.push_back(f); + } + } + + return fs; + }(); + + + + const bool is_no_param = [&]()->bool{ + for (const auto &f : functions) { + if (f->get_arity() != 2) { + return false; + } + } + return true; + }(); + + if (!functions.empty()) { + try { + if (is_no_param) { + std::vector tmp_params(params); + tmp_params.insert(tmp_params.begin() + 1, var(t_name)); + return do_attribute_call(2, tmp_params, functions, t_conversions); + } else { + return dispatch::dispatch(functions, {params[0], var(t_name), var(std::vector(params.begin()+1, params.end()))}, t_conversions); + } + } catch (const dispatch::option_explicit_set &e) { + throw chaiscript::exception::dispatch_error(params, std::vector(funs.second->begin(), funs.second->end()), + e.what()); + } + } + + // If we get all the way down here we know there was no "method_missing" + // method at all. + if (except) { + std::rethrow_exception(except); + } else { + throw chaiscript::exception::dispatch_error(params, std::vector(funs.second->begin(), funs.second->end())); + } + } + } +#ifdef CHAISCRIPT_MSVC +#pragma warning(pop) +#endif + + + + Boxed_Value call_function(const std::string &t_name, std::atomic_uint_fast32_t &t_loc, const std::vector ¶ms, + const Type_Conversions_State &t_conversions) const + { + uint_fast32_t loc = t_loc; + const auto funs = get_function(t_name, loc); + if (funs.first != loc) { t_loc = uint_fast32_t(funs.first); +} + return dispatch::dispatch(*funs.second, params, t_conversions); + } + + + /// Dump object info to stdout + void dump_object(const Boxed_Value &o) const + { + std::cout << (o.is_const()?"const ":"") << type_name(o) << '\n'; + } + + /// Dump type info to stdout + void dump_type(const Type_Info &type) const + { + std::cout << (type.is_const()?"const ":"") << get_type_name(type); + } + + /// Dump function to stdout + void dump_function(const std::pair &f) const + { + std::vector params = f.second->get_param_types(); + + dump_type(params.front()); + std::cout << " " << f.first << "("; + + for (std::vector::const_iterator itr = params.begin() + 1; + itr != params.end(); + ) + { + dump_type(*itr); + ++itr; + + if (itr != params.end()) + { + std::cout << ", "; + } + } + + std::cout << ") \n"; + } + + /// Returns true if a call can be made that consists of the first parameter + /// (the function) with the remaining parameters as its arguments. + Boxed_Value call_exists(const std::vector ¶ms) const + { + if (params.empty()) + { + throw chaiscript::exception::arity_error(static_cast(params.size()), 1); + } + + const Const_Proxy_Function &f = this->boxed_cast(params[0]); + const Type_Conversions_State convs(m_conversions, m_conversions.conversion_saves()); + + return const_var(f->call_match(std::vector(params.begin() + 1, params.end()), convs)); + } + + /// Dump all system info to stdout + void dump_system() const + { + std::cout << "Registered Types: \n"; + for (auto const &type: get_types()) + { + std::cout << type.first << ": " << type.second.bare_name() << '\n'; + } + + std::cout << '\n'; + + std::cout << "Functions: \n"; + for (auto const &func: get_functions()) + { + dump_function(func); + } + std::cout << '\n'; + } + + /// return true if the Boxed_Value matches the registered type by name + bool is_type(const Boxed_Value &r, const std::string &user_typename) const + { + try { + if (get_type(user_typename).bare_equal(r.get_type_info())) + { + return true; + } + } catch (const std::range_error &) { + } + + try { + const dispatch::Dynamic_Object &d = boxed_cast(r); + return d.get_type_name() == user_typename; + } catch (const std::bad_cast &) { + } + + return false; + } + + std::string type_name(const Boxed_Value &obj) const + { + return get_type_name(obj.get_type_info()); + } + + State get_state() const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + return m_state; + } + + void set_state(const State &t_state) + { + chaiscript::detail::threading::unique_lock l(m_mutex); + + m_state = t_state; + } + + static void save_function_params(Stack_Holder &t_s, std::initializer_list t_params) + { + t_s.call_params.back().insert(t_s.call_params.back().begin(), t_params); + } + + static void save_function_params(Stack_Holder &t_s, std::vector &&t_params) + { + for (auto &¶m : t_params) + { + t_s.call_params.back().insert(t_s.call_params.back().begin(), std::move(param)); + } + } + + static void save_function_params(Stack_Holder &t_s, const std::vector &t_params) + { + t_s.call_params.back().insert(t_s.call_params.back().begin(), t_params.begin(), t_params.end()); + } + + void save_function_params(std::initializer_list t_params) + { + save_function_params(*m_stack_holder, t_params); + } + + void save_function_params(std::vector &&t_params) + { + save_function_params(*m_stack_holder, std::move(t_params)); + } + + void save_function_params(const std::vector &t_params) + { + save_function_params(*m_stack_holder, t_params); + } + + void new_function_call(Stack_Holder &t_s, Type_Conversions::Conversion_Saves &t_saves) + { + if (t_s.call_depth == 0) + { + m_conversions.enable_conversion_saves(t_saves, true); + } + + ++t_s.call_depth; + + save_function_params(m_conversions.take_saves(t_saves)); + } + + void pop_function_call(Stack_Holder &t_s, Type_Conversions::Conversion_Saves &t_saves) + { + --t_s.call_depth; + + assert(t_s.call_depth >= 0); + + if (t_s.call_depth == 0) + { + t_s.call_params.back().clear(); + m_conversions.enable_conversion_saves(t_saves, false); + } + } + + void new_function_call() + { + new_function_call(*m_stack_holder, m_conversions.conversion_saves()); + } + + void pop_function_call() + { + pop_function_call(*m_stack_holder, m_conversions.conversion_saves()); + } + + Stack_Holder &get_stack_holder() + { + return *m_stack_holder; + } + + /// Returns the current stack + /// make const/non const versions + const StackData &get_stack_data() const + { + return m_stack_holder->stacks.back(); + } + + static StackData &get_stack_data(Stack_Holder &t_holder) + { + return t_holder.stacks.back(); + } + + StackData &get_stack_data() + { + return m_stack_holder->stacks.back(); + } + + parser::ChaiScript_Parser_Base &get_parser() + { + return m_parser.get(); + } + + private: + + const std::vector> &get_boxed_functions_int() const + { + return m_state.m_boxed_functions; + } + + std::vector> &get_boxed_functions_int() + { + return m_state.m_boxed_functions; + } + + const std::vector> &get_function_objects_int() const + { + return m_state.m_function_objects; + } + + std::vector> &get_function_objects_int() + { + return m_state.m_function_objects; + } + + const std::vector>>> &get_functions_int() const + { + return m_state.m_functions; + } + + std::vector>>> &get_functions_int() + { + return m_state.m_functions; + } + + static bool function_less_than(const Proxy_Function &lhs, const Proxy_Function &rhs) + { + + auto dynamic_lhs(std::dynamic_pointer_cast(lhs)); + auto dynamic_rhs(std::dynamic_pointer_cast(rhs)); + + if (dynamic_lhs && dynamic_rhs) + { + if (dynamic_lhs->get_guard()) + { + return dynamic_rhs->get_guard() ? false : true; + } else { + return false; + } + } + + if (dynamic_lhs && !dynamic_rhs) + { + return false; + } + + if (!dynamic_lhs && dynamic_rhs) + { + return true; + } + + const auto &lhsparamtypes = lhs->get_param_types(); + const auto &rhsparamtypes = rhs->get_param_types(); + + const auto lhssize = lhsparamtypes.size(); + const auto rhssize = rhsparamtypes.size(); + + static const auto boxed_type = user_type(); + static const auto boxed_pod_type = user_type(); + + for (size_t i = 1; i < lhssize && i < rhssize; ++i) + { + const Type_Info < = lhsparamtypes[i]; + const Type_Info &rt = rhsparamtypes[i]; + + if (lt.bare_equal(rt) && lt.is_const() == rt.is_const()) + { + continue; // The first two types are essentially the same, next iteration + } + + // const is after non-const for the same type + if (lt.bare_equal(rt) && lt.is_const() && !rt.is_const()) + { + return false; + } + + if (lt.bare_equal(rt) && !lt.is_const()) + { + return true; + } + + // boxed_values are sorted last + if (lt.bare_equal(boxed_type)) + { + return false; + } + + if (rt.bare_equal(boxed_type)) + { + return true; + } + + if (lt.bare_equal(boxed_pod_type)) + { + return false; + } + + if (rt.bare_equal(boxed_pod_type)) + { + return true; + } + + // otherwise, we want to sort by typeid + return lt < rt; + } + + return false; + } + + + + template + static void add_keyed_value(Container &t_c, const Key &t_key, Value &&t_value) + { + auto itr = find_keyed_value(t_c, t_key); + + if (itr == t_c.end()) { + t_c.reserve(t_c.size() + 1); // tightly control growth of memory usage here + t_c.emplace_back(t_key, std::forward(t_value)); + } else { + typedef typename Container::value_type value_type; + *itr = value_type(t_key, std::forward(t_value)); + } + } + + template + static typename Container::iterator find_keyed_value(Container &t_c, const Key &t_key) + { + return std::find_if(t_c.begin(), t_c.end(), + [&t_key](const typename Container::value_type &o) { + return o.first == t_key; + }); + } + + template + static typename Container::const_iterator find_keyed_value(const Container &t_c, const Key &t_key) + { + return std::find_if(t_c.begin(), t_c.end(), + [&t_key](const typename Container::value_type &o) { + return o.first == t_key; + }); + } + + template + static typename Container::const_iterator find_keyed_value(const Container &t_c, const Key &t_key, const size_t t_hint) + { + if (t_c.size() > t_hint && t_c[t_hint].first == t_key) { + return std::next(t_c.begin(), static_cast::difference_type>(t_hint)); + } else { + return find_keyed_value(t_c, t_key); + } + } + + + /// Implementation detail for adding a function. + /// \throws exception::name_conflict_error if there's a function matching the given one being added + void add_function(const Proxy_Function &t_f, const std::string &t_name) + { + chaiscript::detail::threading::unique_lock l(m_mutex); + + auto &funcs = get_functions_int(); + + auto itr = find_keyed_value(funcs, t_name); + + Proxy_Function new_func = + [&]() -> Proxy_Function { + if (itr != funcs.end()) + { + auto vec = *itr->second; + for (const auto &func : vec) + { + if ((*t_f) == *(func)) + { + throw chaiscript::exception::name_conflict_error(t_name); + } + } + + vec.reserve(vec.size() + 1); // tightly control vec growth + vec.push_back(t_f); + std::stable_sort(vec.begin(), vec.end(), &function_less_than); + itr->second = std::make_shared>(vec); + return std::make_shared(std::move(vec)); + } else if (t_f->has_arithmetic_param()) { + // if the function is the only function but it also contains + // arithmetic operators, we must wrap it in a dispatch function + // to allow for automatic arithmetic type conversions + std::vector vec({t_f}); + funcs.emplace_back(t_name, std::make_shared>(vec)); + return std::make_shared(std::move(vec)); + } else { + funcs.emplace_back(t_name, std::make_shared>(std::initializer_list({t_f}))); + return t_f; + } + }(); + + add_keyed_value(get_boxed_functions_int(), t_name, const_var(new_func)); + add_keyed_value(get_function_objects_int(), t_name, std::move(new_func)); + } + + mutable chaiscript::detail::threading::shared_mutex m_mutex; + + + Type_Conversions m_conversions; + chaiscript::detail::threading::Thread_Storage m_stack_holder; + std::reference_wrapper m_parser; + + mutable std::atomic_uint_fast32_t m_method_missing_loc = {0}; + + State m_state; + }; + + class Dispatch_State + { + public: + explicit Dispatch_State(Dispatch_Engine &t_engine) + : m_engine(t_engine), + m_stack_holder(t_engine.get_stack_holder()), + m_conversions(t_engine.conversions(), t_engine.conversions().conversion_saves()) + { + } + + Dispatch_Engine *operator->() const { + return &m_engine.get(); + } + + Dispatch_Engine &operator*() const { + return m_engine.get(); + } + + Stack_Holder &stack_holder() const { + return m_stack_holder.get(); + } + + const Type_Conversions_State &conversions() const { + return m_conversions; + } + + Type_Conversions::Conversion_Saves &conversion_saves() const { + return m_conversions.saves(); + } + + Boxed_Value &add_get_object(const std::string &t_name, Boxed_Value obj) const { + return m_engine.get().add_get_object(t_name, std::move(obj), m_stack_holder.get()); + } + + void add_object(const std::string &t_name, Boxed_Value obj) const { + return m_engine.get().add_object(t_name, std::move(obj), m_stack_holder.get()); + } + + Boxed_Value get_object(const std::string &t_name, std::atomic_uint_fast32_t &t_loc) const { + return m_engine.get().get_object(t_name, t_loc, m_stack_holder.get()); + } + + private: + std::reference_wrapper m_engine; + std::reference_wrapper m_stack_holder; + Type_Conversions_State m_conversions; + }; + } +} + +#endif + + diff --git a/chaiscript/dispatchkit/dynamic_object.hpp b/chaiscript/dispatchkit/dynamic_object.hpp new file mode 100644 index 0000000..b5afaf1 --- /dev/null +++ b/chaiscript/dispatchkit/dynamic_object.hpp @@ -0,0 +1,131 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_DYNAMIC_OBJECT_HPP_ +#define CHAISCRIPT_DYNAMIC_OBJECT_HPP_ + +#include +#include +#include + +#include "boxed_value.hpp" + +namespace chaiscript { +class Type_Conversions; +namespace dispatch { +class Proxy_Function_Base; +} // namespace dispatch +} // namespace chaiscript + +namespace chaiscript +{ + namespace dispatch + { + struct option_explicit_set : std::runtime_error { + explicit option_explicit_set(const std::string &t_param_name) + : std::runtime_error("option explicit set and parameter '" + t_param_name + "' does not exist") + { + + } + + option_explicit_set(const option_explicit_set &) = default; + + ~option_explicit_set() noexcept override = default; + }; + + class Dynamic_Object + { + public: + explicit Dynamic_Object(std::string t_type_name) + : m_type_name(std::move(t_type_name)), m_option_explicit(false) + { + } + + Dynamic_Object() = default; + + bool is_explicit() const + { + return m_option_explicit; + } + + void set_explicit(const bool t_explicit) + { + m_option_explicit = t_explicit; + } + + std::string get_type_name() const + { + return m_type_name; + } + + const Boxed_Value &operator[](const std::string &t_attr_name) const + { + return get_attr(t_attr_name); + } + + Boxed_Value &operator[](const std::string &t_attr_name) + { + return get_attr(t_attr_name); + } + + const Boxed_Value &get_attr(const std::string &t_attr_name) const + { + auto a = m_attrs.find(t_attr_name); + + if (a != m_attrs.end()) { + return a->second; + } else { + throw std::range_error("Attr not found '" + t_attr_name + "' and cannot be added to const obj"); + } + } + + bool has_attr(const std::string &t_attr_name) const { + return m_attrs.find(t_attr_name) != m_attrs.end(); + } + + Boxed_Value &get_attr(const std::string &t_attr_name) + { + return m_attrs[t_attr_name]; + } + + Boxed_Value &method_missing(const std::string &t_method_name) + { + if (m_option_explicit && m_attrs.find(t_method_name) == m_attrs.end()) { + throw option_explicit_set(t_method_name); + } + + return get_attr(t_method_name); + } + + const Boxed_Value &method_missing(const std::string &t_method_name) const + { + if (m_option_explicit && m_attrs.find(t_method_name) == m_attrs.end()) { + throw option_explicit_set(t_method_name); + } + + return get_attr(t_method_name); + } + + std::map get_attrs() const + { + return m_attrs; + } + + private: + const std::string m_type_name = ""; + bool m_option_explicit = false; + + std::map m_attrs; + }; + + } +} +#endif + diff --git a/chaiscript/dispatchkit/dynamic_object_detail.hpp b/chaiscript/dispatchkit/dynamic_object_detail.hpp new file mode 100644 index 0000000..df90ab6 --- /dev/null +++ b/chaiscript/dispatchkit/dynamic_object_detail.hpp @@ -0,0 +1,237 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_DYNAMIC_OBJECT_DETAIL_HPP_ +#define CHAISCRIPT_DYNAMIC_OBJECT_DETAIL_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include "../chaiscript_defines.hpp" +#include "boxed_cast.hpp" +#include "boxed_cast_helper.hpp" +#include "boxed_value.hpp" +#include "proxy_functions.hpp" +#include "type_info.hpp" +#include "dynamic_object.hpp" + +namespace chaiscript { +class Type_Conversions; +namespace dispatch { +class Proxy_Function_Base; +} // namespace dispatch +} // namespace chaiscript + +namespace chaiscript +{ + namespace dispatch + { + namespace detail + { + /// A Proxy_Function implementation designed for calling a function + /// that is automatically guarded based on the first param based on the + /// param's type name + class Dynamic_Object_Function final : public Proxy_Function_Base + { + public: + Dynamic_Object_Function( + std::string t_type_name, + const Proxy_Function &t_func, + bool t_is_attribute = false) + : Proxy_Function_Base(t_func->get_param_types(), t_func->get_arity()), + m_type_name(std::move(t_type_name)), m_func(t_func), m_doti(user_type()), + m_is_attribute(t_is_attribute) + { + assert( (t_func->get_arity() > 0 || t_func->get_arity() < 0) + && "Programming error, Dynamic_Object_Function must have at least one parameter (this)"); + } + + Dynamic_Object_Function( + std::string t_type_name, + const Proxy_Function &t_func, + const Type_Info &t_ti, + bool t_is_attribute = false) + : Proxy_Function_Base(build_param_types(t_func->get_param_types(), t_ti), t_func->get_arity()), + m_type_name(std::move(t_type_name)), m_func(t_func), m_ti(t_ti.is_undef()?nullptr:new Type_Info(t_ti)), m_doti(user_type()), + m_is_attribute(t_is_attribute) + { + assert( (t_func->get_arity() > 0 || t_func->get_arity() < 0) + && "Programming error, Dynamic_Object_Function must have at least one parameter (this)"); + } + + + Dynamic_Object_Function &operator=(const Dynamic_Object_Function) = delete; + Dynamic_Object_Function(Dynamic_Object_Function &) = delete; + + bool operator==(const Proxy_Function_Base &f) const override + { + if (const auto *df = dynamic_cast(&f)) + { + return df->m_type_name == m_type_name && (*df->m_func) == (*m_func); + } else { + return false; + } + } + + bool is_attribute_function() const override { return m_is_attribute; } + + bool call_match(const std::vector &vals, const Type_Conversions_State &t_conversions) const override + { + if (dynamic_object_typename_match(vals, m_type_name, m_ti, t_conversions)) + { + return m_func->call_match(vals, t_conversions); + } else { + return false; + } + } + + std::vector get_contained_functions() const override + { + return {m_func}; + } + + protected: + Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const override + { + if (dynamic_object_typename_match(params, m_type_name, m_ti, t_conversions)) + { + return (*m_func)(params, t_conversions); + } else { + throw exception::guard_error(); + } + } + + bool compare_first_type(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const override + { + return dynamic_object_typename_match(bv, m_type_name, m_ti, t_conversions); + } + + private: + static std::vector build_param_types( + const std::vector &t_inner_types, const Type_Info& t_objectti) + { + std::vector types(t_inner_types); + + assert(types.size() > 1); + //assert(types[1].bare_equal(user_type())); + types[1] = t_objectti; + return types; + } + + bool dynamic_object_typename_match(const Boxed_Value &bv, const std::string &name, + const std::unique_ptr &ti, const Type_Conversions_State &t_conversions) const + { + if (bv.get_type_info().bare_equal(m_doti)) + { + try { + const Dynamic_Object &d = boxed_cast(bv, &t_conversions); + return name == "Dynamic_Object" || d.get_type_name() == name; + } catch (const std::bad_cast &) { + return false; + } + } else { + if (ti) + { + return bv.get_type_info().bare_equal(*ti); + } else { + return false; + } + } + + } + + bool dynamic_object_typename_match(const std::vector &bvs, const std::string &name, + const std::unique_ptr &ti, const Type_Conversions_State &t_conversions) const + { + if (!bvs.empty()) + { + return dynamic_object_typename_match(bvs[0], name, ti, t_conversions); + } else { + return false; + } + } + + std::string m_type_name; + Proxy_Function m_func; + std::unique_ptr m_ti; + const Type_Info m_doti; + const bool m_is_attribute; + }; + + + /** + * A Proxy_Function implementation designed for creating a new + * Dynamic_Object + * that is automatically guarded based on the first param based on the + * param's type name + */ + class Dynamic_Object_Constructor final : public Proxy_Function_Base + { + public: + Dynamic_Object_Constructor( + std::string t_type_name, + const Proxy_Function &t_func) + : Proxy_Function_Base(build_type_list(t_func->get_param_types()), t_func->get_arity() - 1), + m_type_name(std::move(t_type_name)), m_func(t_func) + { + assert( (t_func->get_arity() > 0 || t_func->get_arity() < 0) + && "Programming error, Dynamic_Object_Function must have at least one parameter (this)"); + } + + static std::vector build_type_list(const std::vector &tl) + { + auto begin = tl.begin(); + auto end = tl.end(); + + if (begin != end) + { + ++begin; + } + + return std::vector(begin, end); + } + + bool operator==(const Proxy_Function_Base &f) const override + { + const Dynamic_Object_Constructor *dc = dynamic_cast(&f); + return (dc != nullptr) && dc->m_type_name == m_type_name && (*dc->m_func) == (*m_func); + } + + bool call_match(const std::vector &vals, const Type_Conversions_State &t_conversions) const override + { + std::vector new_vals{Boxed_Value(Dynamic_Object(m_type_name))}; + new_vals.insert(new_vals.end(), vals.begin(), vals.end()); + + return m_func->call_match(new_vals, t_conversions); + } + + protected: + Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const override + { + auto bv = Boxed_Value(Dynamic_Object(m_type_name), true); + std::vector new_params{bv}; + new_params.insert(new_params.end(), params.begin(), params.end()); + + (*m_func)(new_params, t_conversions); + + return bv; + } + + private: + const std::string m_type_name; + const Proxy_Function m_func; + + }; + } + } +} +#endif + diff --git a/chaiscript/dispatchkit/exception_specification.hpp b/chaiscript/dispatchkit/exception_specification.hpp new file mode 100644 index 0000000..79607fe --- /dev/null +++ b/chaiscript/dispatchkit/exception_specification.hpp @@ -0,0 +1,117 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_EXCEPTION_SPECIFICATION_HPP_ +#define CHAISCRIPT_EXCEPTION_SPECIFICATION_HPP_ + +#include + +#include "../chaiscript_defines.hpp" +#include "boxed_cast.hpp" + +namespace chaiscript { +class Boxed_Value; +namespace exception { +class bad_boxed_cast; +} // namespace exception +} // namespace chaiscript + +namespace chaiscript +{ + namespace detail + { + struct Exception_Handler_Base + { + virtual void handle(const Boxed_Value &bv, const Dispatch_Engine &t_engine) = 0; + + virtual ~Exception_Handler_Base() = default; + + protected: + template + static void throw_type(const Boxed_Value &bv, const Dispatch_Engine &t_engine) + { + try { T t = t_engine.boxed_cast(bv); throw t; } catch (const chaiscript::exception::bad_boxed_cast &) {} + } + }; + + template + struct Exception_Handler_Impl : Exception_Handler_Base + { + void handle(const Boxed_Value &bv, const Dispatch_Engine &t_engine) override + { + (void)std::initializer_list{(throw_type(bv, t_engine), 0)...}; + } + }; + } + + /// \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 std::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 std::make_shared>(); + } +} + + +#endif + diff --git a/chaiscript/dispatchkit/function_call.hpp b/chaiscript/dispatchkit/function_call.hpp new file mode 100644 index 0000000..1980f35 --- /dev/null +++ b/chaiscript/dispatchkit/function_call.hpp @@ -0,0 +1,132 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_FUNCTION_CALL_HPP_ +#define CHAISCRIPT_FUNCTION_CALL_HPP_ + +#include +#include +#include + +#include "boxed_cast.hpp" +#include "function_call_detail.hpp" +#include "proxy_functions.hpp" +#include "callable_traits.hpp" + +namespace chaiscript { +class Boxed_Value; +class Type_Conversions_State; +namespace detail { +template struct Cast_Helper; +} // namespace detail +} // namespace chaiscript + +namespace chaiscript +{ + namespace dispatch + { + /// Build a function caller that knows how to dispatch on a set of functions + /// example: + /// std::function f = + /// build_function_caller(dispatchkit.get_function("print")); + /// \returns A std::function object for dispatching + /// \param[in] funcs the set of functions to dispatch on. + template + std::function functor(const std::vector &funcs, const Type_Conversions_State *t_conversions) + { + const bool has_arity_match = std::any_of(funcs.begin(), funcs.end(), + [](const Const_Proxy_Function &f) { + return f->get_arity() == -1 || size_t(f->get_arity()) == chaiscript::dispatch::detail::Arity::arity; + }); + + if (!has_arity_match) { + throw exception::bad_boxed_cast(user_type(), typeid(std::function)); + } + + FunctionType *p=nullptr; + return detail::build_function_caller_helper(p, funcs, t_conversions); + } + + /// Build a function caller for a particular Proxy_Function object + /// useful in the case that a function is being pass out from scripting back + /// into code + /// example: + /// void my_function(Proxy_Function f) + /// { + /// std::function local_f = + /// build_function_caller(f); + /// } + /// \returns A std::function object for dispatching + /// \param[in] func A function to execute. + template + std::function functor(Const_Proxy_Function func, const Type_Conversions_State *t_conversions) + { + return functor(std::vector({std::move(func)}), t_conversions); + } + + /// Helper for automatically unboxing a Boxed_Value that contains a function object + /// and creating a typesafe C++ function caller from it. + template + std::function functor(const Boxed_Value &bv, const Type_Conversions_State *t_conversions) + { + return functor(boxed_cast(bv, t_conversions), t_conversions); + } + } + + namespace detail{ + /// Cast helper to handle automatic casting to const std::function & + template + struct Cast_Helper &> + { + static std::function cast(const Boxed_Value &ob, const Type_Conversions_State *t_conversions) + { + if (ob.get_type_info().bare_equal(user_type())) + { + return dispatch::functor(ob, t_conversions); + } else { + return Cast_Helper_Inner &>::cast(ob, t_conversions); + } + } + }; + + /// Cast helper to handle automatic casting to std::function + template + struct Cast_Helper > + { + static std::function cast(const Boxed_Value &ob, const Type_Conversions_State *t_conversions) + { + if (ob.get_type_info().bare_equal(user_type())) + { + return dispatch::functor(ob, t_conversions); + } else { + return Cast_Helper_Inner >::cast(ob, t_conversions); + } + } + }; + + /// Cast helper to handle automatic casting to const std::function + template + struct Cast_Helper > + { + static std::function cast(const Boxed_Value &ob, const Type_Conversions_State *t_conversions) + { + if (ob.get_type_info().bare_equal(user_type())) + { + return dispatch::functor(ob, t_conversions); + } else { + return Cast_Helper_Inner >::cast(ob, t_conversions); + } + } + }; + } +} + +#endif + diff --git a/chaiscript/dispatchkit/function_call_detail.hpp b/chaiscript/dispatchkit/function_call_detail.hpp new file mode 100644 index 0000000..d6cb241 --- /dev/null +++ b/chaiscript/dispatchkit/function_call_detail.hpp @@ -0,0 +1,171 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_FUNCTION_CALL_DETAIL_HPP_ +#define CHAISCRIPT_FUNCTION_CALL_DETAIL_HPP_ + +#include +#include +#include +#include +#include + +#include "boxed_cast.hpp" +#include "boxed_number.hpp" +#include "boxed_value.hpp" +#include "type_conversions.hpp" +#include "proxy_functions.hpp" + +namespace chaiscript +{ + namespace dispatch + { + namespace detail + { + /// Internal helper class for handling the return + /// value of a build_function_caller + template + struct Function_Caller_Ret + { + static Ret call(const std::vector &t_funcs, + const std::vector ¶ms, const Type_Conversions_State *t_conversions) + { + if (t_conversions != nullptr) { + return boxed_cast(dispatch::dispatch(t_funcs, params, *t_conversions), t_conversions); + } else { + Type_Conversions conv; + Type_Conversions_State state(conv, conv.conversion_saves()); + return boxed_cast(dispatch::dispatch(t_funcs, params, state), t_conversions); + } + } + }; + + /** + * Specialization for arithmetic return types + */ + template + struct Function_Caller_Ret + { + static Ret call(const std::vector &t_funcs, + const std::vector ¶ms, const Type_Conversions_State *t_conversions) + { + if (t_conversions != nullptr) { + return Boxed_Number(dispatch::dispatch(t_funcs, params, *t_conversions)).get_as(); + } else { + Type_Conversions conv; + Type_Conversions_State state(conv, conv.conversion_saves()); + return Boxed_Number(dispatch::dispatch(t_funcs, params, state)).get_as(); + } + } + }; + + + /** + * Specialization for void return types + */ + template<> + struct Function_Caller_Ret + { + static void call(const std::vector &t_funcs, + const std::vector ¶ms, const Type_Conversions_State *t_conversions) + { + if (t_conversions != nullptr) { + dispatch::dispatch(t_funcs, params, *t_conversions); + } else { + Type_Conversions conv; + Type_Conversions_State state(conv, conv.conversion_saves()); + dispatch::dispatch(t_funcs, params, state); + } + } + }; + + /** + * used internally for unwrapping a function call's types + */ + template + struct Build_Function_Caller_Helper + { + Build_Function_Caller_Helper(std::vector t_funcs, const Type_Conversions *t_conversions) + : m_funcs(std::move(t_funcs)), + m_conversions(t_conversions) + { + } + + template + Ret operator()(P&& ... param) + { + if (m_conversions) { + Type_Conversions_State state(*m_conversions, m_conversions->conversion_saves()); + return Function_Caller_Ret::value && !std::is_same::value>::call(m_funcs, { + box

(std::forward

(param))... + }, &state + ); + } else { + return Function_Caller_Ret::value && !std::is_same::value>::call(m_funcs, { + box

(std::forward

(param))... + }, nullptr + ); + } + + } + + template + static auto box(Q&& q) -> typename std::enable_if::value&&!std::is_same::type>::type>::value, Boxed_Value>::type + { + return Boxed_Value(std::ref(std::forward(q))); + } + + template + static auto box(Q&& q) -> typename std::enable_if::value&&!std::is_same::type>::type>::value, Boxed_Value>::type + { + return Boxed_Value(std::forward(q)); + } + + template + static Boxed_Value box(Boxed_Value bv) + { + return bv; + } + + + std::vector m_funcs; + const Type_Conversions *m_conversions; + }; + + + + /// \todo what happens if t_conversions is deleted out from under us?! + template + std::function build_function_caller_helper(Ret (Params...), const std::vector &funcs, const Type_Conversions_State *t_conversions) + { + /* + if (funcs.size() == 1) + { + std::shared_ptr> pfi = + std::dynamic_pointer_cast > + (funcs[0]); + + if (pfi) + { + return pfi->internal_function(); + } + // looks like this either wasn't a Proxy_Function_Impl or the types didn't match + // we cannot make any other guesses or assumptions really, so continuing + } +*/ + + return std::function(Build_Function_Caller_Helper(funcs, t_conversions?t_conversions->get():nullptr)); + } + } + } +} + +#endif + diff --git a/chaiscript/dispatchkit/handle_return.hpp b/chaiscript/dispatchkit/handle_return.hpp new file mode 100644 index 0000000..8570c8d --- /dev/null +++ b/chaiscript/dispatchkit/handle_return.hpp @@ -0,0 +1,256 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_HANDLE_RETURN_HPP_ +#define CHAISCRIPT_HANDLE_RETURN_HPP_ + +#include +#include +#include + +#include "boxed_number.hpp" +#include "boxed_value.hpp" + +namespace chaiscript { +class Boxed_Number; +} // namespace chaiscript + +namespace chaiscript +{ + namespace dispatch + { + template class Proxy_Function_Callable_Impl; + template class Assignable_Proxy_Function_Impl; + + namespace detail + { + /// Used internally for handling a return value from a Proxy_Function call + template + struct Handle_Return + { + template::type>::value>::type> + static Boxed_Value handle(T r) + { + return Boxed_Value(std::move(r), true); + } + + template::type>::value>::type> + static Boxed_Value handle(T &&r) + { + return Boxed_Value(std::make_shared(std::forward(r)), true); + } + }; + + template + struct Handle_Return &> + { + static Boxed_Value handle(const std::function &f) { + return Boxed_Value( + chaiscript::make_shared>>(f) + ); + } + }; + + template + struct Handle_Return> : Handle_Return &> + { + }; + + template + struct Handle_Return>> + { + static Boxed_Value handle(const std::shared_ptr> &f) { + return Boxed_Value( + chaiscript::make_shared>(std::ref(*f),f) + ); + } + }; + + template + struct Handle_Return> &> : Handle_Return>> + { + }; + + template + struct Handle_Return>> : Handle_Return>> + { + }; + + template + struct Handle_Return &> + { + static Boxed_Value handle(std::function &f) { + return Boxed_Value( + chaiscript::make_shared>(std::ref(f), + std::shared_ptr>()) + ); + } + + static Boxed_Value handle(const std::function &f) { + return Boxed_Value( + chaiscript::make_shared>>(f) + ); + } + }; + + template + struct Handle_Return + { + static Boxed_Value handle(Ret *p) + { + return Boxed_Value(p, true); + } + }; + + template + struct Handle_Return + { + static Boxed_Value handle(const Ret *p) + { + return Boxed_Value(p, true); + } + }; + + template + struct Handle_Return + { + static Boxed_Value handle(Ret *p) + { + return Boxed_Value(p, true); + } + }; + + template + struct Handle_Return + { + static Boxed_Value handle(const Ret *p) + { + return Boxed_Value(p, true); + } + }; + + template + struct Handle_Return &> + { + static Boxed_Value handle(const std::shared_ptr &r) + { + return Boxed_Value(r, true); + } + }; + + template + struct Handle_Return> : Handle_Return &> + { + }; + + template + struct Handle_Return &> : Handle_Return &> + { + }; + + + template + struct Handle_Return> : Handle_Return &> + { + static Boxed_Value handle(std::unique_ptr &&r) + { + return Boxed_Value(std::move(r), true); + } + }; + + + + template + struct Handle_Return + { + static Boxed_Value handle(const Ret &r) + { + return Boxed_Value(std::cref(r), true); + } + }; + + template + struct Handle_Return + { + static Boxed_Value handle(Ret r) + { + return Boxed_Value(std::move(r)); + } + }; + + template + struct Handle_Return + { + static Boxed_Value handle(Ret &r) + { + return Boxed_Value(std::ref(r)); + } + }; + + template<> + struct Handle_Return + { + static Boxed_Value handle(const Boxed_Value &r) + { + return r; + } + }; + + template<> + struct Handle_Return : Handle_Return + { + }; + + template<> + struct Handle_Return : Handle_Return + { + }; + + template<> + struct Handle_Return : Handle_Return + { + }; + + /** + * Used internally for handling a return value from a Proxy_Function call + */ + template<> + struct Handle_Return + { + static Boxed_Value handle(const Boxed_Number &r) + { + return r.bv; + } + }; + + template<> + struct Handle_Return : Handle_Return + { + }; + + + /** + * Used internally for handling a return value from a Proxy_Function call + */ + template<> + struct Handle_Return + { + static Boxed_Value handle() + { + return void_var(); + } + }; + } + } +} + +#endif diff --git a/chaiscript/dispatchkit/operators.hpp b/chaiscript/dispatchkit/operators.hpp new file mode 100644 index 0000000..54c73b9 --- /dev/null +++ b/chaiscript/dispatchkit/operators.hpp @@ -0,0 +1,224 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_OPERATORS_HPP_ +#define CHAISCRIPT_OPERATORS_HPP_ + +#include "../chaiscript_defines.hpp" +#include "register_function.hpp" + +namespace chaiscript +{ + namespace bootstrap + { + namespace operators + { + template + void assign(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs = rhs;}), "="); + } + + template + void assign_bitwise_and(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs &= rhs;}), "&="); + } + + template + void assign_xor(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs ^= rhs;}), "^="); + } + + template + void assign_bitwise_or(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs |= rhs;}), "|="); + } + + template + void assign_difference(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs -= rhs;}), "-="); + } + + template + void assign_left_shift(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs <<= rhs;}), "<<="); + } + + template + void assign_product(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs <<= rhs;}), "*="); + } + + template + void assign_quotient(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs /= rhs;}), "/="); + } + + template + void assign_remainder(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs %= rhs;}), "%="); + } + + template + void assign_right_shift(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs >>= rhs;}), ">>="); + } + + template + void assign_sum(Module& m) + { + m.add(chaiscript::fun([](T &lhs, const T&rhs)->T&{return lhs += rhs;}), "+="); + } + + template + void prefix_decrement(Module& m) + { + m.add(chaiscript::fun([](T &lhs)->T&{return --lhs;}), "--"); + } + + template + void prefix_increment(Module& m) + { + m.add(chaiscript::fun([](T &lhs)->T&{return ++lhs;}), "++"); + } + + template + void equal(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs==rhs;}), "=="); + } + + template + void greater_than(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs>rhs;}), ">"); + } + + template + void greater_than_equal(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs>=rhs;}), ">="); + } + + template + void less_than(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs + void less_than_equal(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs<=rhs;}), "<="); + } + + template + void logical_compliment(Module& m) + { + m.add(chaiscript::fun([](const T &lhs){return !lhs;}), "!"); + } + + template + void not_equal(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs!=rhs;}), "!="); + } + + template + void addition(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs+rhs;}), "+"); + } + + template + void unary_plus(Module& m) + { + m.add(chaiscript::fun([](const T &lhs){return +lhs;}), "+"); + } + + template + void subtraction(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs-rhs;}), "-"); + } + + template + void unary_minus(Module& m) + { + m.add(chaiscript::fun([](const T &lhs){return -lhs;}), "-"); + } + + template + void bitwise_and(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs&rhs;}), "&"); + } + + template + void bitwise_compliment(Module& m) + { + m.add(chaiscript::fun([](const T &lhs){return ~lhs;}), "~"); + } + + template + void bitwise_xor(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs^rhs;}), "^"); + } + + template + void bitwise_or(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs|rhs;}), "|"); + } + + template + void division(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs/rhs;}), "/"); + } + + template + void left_shift(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs< + void multiplication(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs*rhs;}), "*"); + } + + template + void remainder(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs%rhs;}), "%"); + } + + template + void right_shift(Module& m) + { + m.add(chaiscript::fun([](const T &lhs, const T &rhs){return lhs>>rhs;}), ">>"); + } + } + } +} + +#endif diff --git a/chaiscript/dispatchkit/proxy_constructors.hpp b/chaiscript/dispatchkit/proxy_constructors.hpp new file mode 100644 index 0000000..bbe79d7 --- /dev/null +++ b/chaiscript/dispatchkit/proxy_constructors.hpp @@ -0,0 +1,56 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_PROXY_CONSTRUCTORS_HPP_ +#define CHAISCRIPT_PROXY_CONSTRUCTORS_HPP_ + +#include "proxy_functions.hpp" + +namespace chaiscript +{ + namespace dispatch + { + namespace detail + { + + template + Proxy_Function build_constructor_(Class (*)(Params...)) + { + auto call = dispatch::detail::Constructor(); + + return Proxy_Function( + chaiscript::make_shared (Params...), decltype(call)>>(call)); + } + } + } + + + /// \brief Generates a constructor function for use with ChaiScript + /// + /// \tparam T The signature of the constructor to generate. In the form of: ClassType (ParamType1, ParamType2, ...) + /// + /// Example: + /// \code + /// chaiscript::ChaiScript chai; + /// // Create a new function that creates a MyClass object using the (int, float) constructor + /// // and call that function "MyClass" so that it appears as a normal constructor to the user. + /// chai.add(constructor(), "MyClass"); + /// \endcode + template + Proxy_Function constructor() + { + T *f = nullptr; + return (dispatch::detail::build_constructor_(f)); + } + +} + +#endif + diff --git a/chaiscript/dispatchkit/proxy_functions.hpp b/chaiscript/dispatchkit/proxy_functions.hpp new file mode 100644 index 0000000..0c60315 --- /dev/null +++ b/chaiscript/dispatchkit/proxy_functions.hpp @@ -0,0 +1,989 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_PROXY_FUNCTIONS_HPP_ +#define CHAISCRIPT_PROXY_FUNCTIONS_HPP_ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../chaiscript_defines.hpp" +#include "boxed_cast.hpp" +#include "boxed_value.hpp" +#include "proxy_functions_detail.hpp" +#include "type_info.hpp" +#include "dynamic_object.hpp" + +namespace chaiscript { +class Type_Conversions; +namespace exception { +class bad_boxed_cast; +struct arity_error; +} // namespace exception +} // namespace chaiscript + +namespace chaiscript +{ + class Boxed_Number; + struct AST_Node; + + typedef std::unique_ptr AST_NodePtr; + + namespace dispatch + { + template + std::function functor(std::shared_ptr func, const Type_Conversions_State *t_conversions); + + class Param_Types + { + public: + Param_Types() + : m_has_types(false), + m_doti(user_type()) + {} + + explicit Param_Types(std::vector> t_types) + : m_types(std::move(t_types)), + m_has_types(false), + m_doti(user_type()) + { + update_has_types(); + } + + void push_front(std::string t_name, Type_Info t_ti) + { + m_types.emplace(m_types.begin(), std::move(t_name), t_ti); + update_has_types(); + } + + bool operator==(const Param_Types &t_rhs) const + { + return m_types == t_rhs.m_types; + } + + std::vector convert(std::vector vals, const Type_Conversions_State &t_conversions) const + { + for (size_t i = 0; i < vals.size(); ++i) + { + const auto &name = m_types[i].first; + if (!name.empty()) { + const auto &bv = vals[i]; + + if (!bv.get_type_info().bare_equal(m_doti)) + { + const auto &ti = m_types[i].second; + if (!ti.is_undef()) + { + if (!bv.get_type_info().bare_equal(ti)) { + if (t_conversions->converts(ti, bv.get_type_info())) { + try { + // We will not catch any bad_boxed_dynamic_cast that is thrown, let the user get it + // either way, we are not responsible if it doesn't work + vals[i] = t_conversions->boxed_type_conversion(m_types[i].second, t_conversions.saves(), vals[i]); + } catch (...) { + try { + // try going the other way + vals[i] = t_conversions->boxed_type_down_conversion(m_types[i].second, t_conversions.saves(), vals[i]); + } catch (const chaiscript::detail::exception::bad_any_cast &) { + throw exception::bad_boxed_cast(bv.get_type_info(), *m_types[i].second.bare_type_info()); + } + } + } + } + } + } + } + } + + return vals; + } + + // first result: is a match + // second result: needs conversions + std::pair match(const std::vector &vals, const Type_Conversions_State &t_conversions) const + { + bool needs_conversion = false; + + if (!m_has_types) { return std::make_pair(true, needs_conversion); } + if (vals.size() != m_types.size()) { return std::make_pair(false, needs_conversion); } + + for (size_t i = 0; i < vals.size(); ++i) + { + const auto &name = m_types[i].first; + if (!name.empty()) { + const auto &bv = vals[i]; + + if (bv.get_type_info().bare_equal(m_doti)) + { + try { + const Dynamic_Object &d = boxed_cast(bv, &t_conversions); + if (!(name == "Dynamic_Object" || d.get_type_name() == name)) { + return std::make_pair(false, false); + } + } catch (const std::bad_cast &) { + return std::make_pair(false, false); + } + } else { + const auto &ti = m_types[i].second; + if (!ti.is_undef()) + { + if (!bv.get_type_info().bare_equal(ti)) { + if (!t_conversions->converts(ti, bv.get_type_info())) { + return std::make_pair(false, false); + } else { + needs_conversion = true; + } + } + } else { + return std::make_pair(false, false); + } + } + } + } + + return std::make_pair(true, needs_conversion); + } + + const std::vector> &types() const + { + return m_types; + } + + private: + void update_has_types() + { + for (const auto &type : m_types) + { + if (!type.first.empty()) + { + m_has_types = true; + return; + } + } + + m_has_types = false; + } + + std::vector> m_types; + bool m_has_types; + Type_Info m_doti; + + }; + + /** + * Pure virtual base class for all Proxy_Function implementations + * Proxy_Functions are a type erasure of type safe C++ + * function calls. At runtime parameter types are expected to be + * tested against passed in types. + * Dispatch_Engine only knows how to work with Proxy_Function, no other + * function classes. + */ + class Proxy_Function_Base + { + public: + virtual ~Proxy_Function_Base() = default; + + Boxed_Value operator()(const std::vector ¶ms, const chaiscript::Type_Conversions_State &t_conversions) const + { + if (m_arity < 0 || size_t(m_arity) == params.size()) { + return do_call(params, t_conversions); + } else { + throw exception::arity_error(static_cast(params.size()), m_arity); + } + } + + /// Returns a vector containing all of the types of the parameters the function returns/takes + /// if the function is variadic or takes no arguments (arity of 0 or -1), the returned + /// value contains exactly 1 Type_Info object: the return type + /// \returns the types of all parameters. + const std::vector &get_param_types() const { return m_types; } + + virtual bool operator==(const Proxy_Function_Base &) const = 0; + virtual bool call_match(const std::vector &vals, const Type_Conversions_State &t_conversions) const = 0; + + virtual bool is_attribute_function() const { return false; } + + bool has_arithmetic_param() const + { + return m_has_arithmetic_param; + } + + virtual std::vector > get_contained_functions() const + { + return std::vector >(); + } + + //! Return true if the function is a possible match + //! to the passed in values + bool filter(const std::vector &vals, const Type_Conversions_State &t_conversions) const + { + assert(m_arity == -1 || (m_arity > 0 && static_cast(vals.size()) == m_arity)); + + if (m_arity < 0) + { + return true; + } else if (m_arity > 1) { + return compare_type_to_param(m_types[1], vals[0], t_conversions) && compare_type_to_param(m_types[2], vals[1], t_conversions); + } else { + return compare_type_to_param(m_types[1], vals[0], t_conversions); + } + } + + /// \returns the number of arguments the function takes or -1 if it is variadic + int get_arity() const + { + return m_arity; + } + + static bool compare_type_to_param(const Type_Info &ti, const Boxed_Value &bv, const Type_Conversions_State &t_conversions) + { + if (ti.is_undef() + || ti.bare_equal(user_type()) + || (!bv.get_type_info().is_undef() + && ( (ti.bare_equal(user_type()) && bv.get_type_info().is_arithmetic()) + || ti.bare_equal(bv.get_type_info()) + || bv.get_type_info().bare_equal(user_type >()) + || t_conversions->converts(ti, bv.get_type_info()) + ) + ) + ) + { + return true; + } else { + return false; + } + } + + virtual bool compare_first_type(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const + { + return compare_type_to_param(m_types[1], bv, t_conversions); + } + + protected: + virtual Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const = 0; + + Proxy_Function_Base(std::vector t_types, int t_arity) + : m_types(std::move(t_types)), m_arity(t_arity), m_has_arithmetic_param(false) + { + for (size_t i = 1; i < m_types.size(); ++i) + { + if (m_types[i].is_arithmetic()) + { + m_has_arithmetic_param = true; + return; + } + } + + } + + + static bool compare_types(const std::vector &tis, const std::vector &bvs, + const Type_Conversions_State &t_conversions) + { + if (tis.size() - 1 != bvs.size()) + { + return false; + } else { + const size_t size = bvs.size(); + for (size_t i = 0; i < size; ++i) + { + if (!compare_type_to_param(tis[i + 1], bvs[i], t_conversions)) { return false; } + } + } + return true; + } + + std::vector m_types; + int m_arity; + bool m_has_arithmetic_param; + }; + } + + /// \brief Common typedef used for passing of any registered function in ChaiScript + typedef std::shared_ptr Proxy_Function; + + /// \brief Const version of Proxy_Function. Points to a const Proxy_Function. This is how most registered functions + /// are handled internally. + typedef std::shared_ptr Const_Proxy_Function; + + namespace exception + { + /// \brief Exception thrown if a function's guard fails + class guard_error : public std::runtime_error + { + public: + guard_error() noexcept + : std::runtime_error("Guard evaluation failed") + { } + + guard_error(const guard_error &) = default; + + ~guard_error() noexcept override = default; + }; + } + + namespace dispatch + { + /** + * A Proxy_Function implementation that is not type safe, the called function + * is expecting a vector that it works with how it chooses. + */ + class Dynamic_Proxy_Function : public Proxy_Function_Base + { + public: + Dynamic_Proxy_Function( + const int t_arity, + std::shared_ptr t_parsenode, + Param_Types t_param_types = Param_Types(), + Proxy_Function t_guard = Proxy_Function()) + : Proxy_Function_Base(build_param_type_list(t_param_types), t_arity), + m_param_types(std::move(t_param_types)), + m_guard(std::move(t_guard)), m_parsenode(std::move(t_parsenode)) + { + // assert(t_parsenode); + } + + + bool operator==(const Proxy_Function_Base &rhs) const override + { + const Dynamic_Proxy_Function *prhs = dynamic_cast(&rhs); + + return this == &rhs + || ((prhs != nullptr) + && this->m_arity == prhs->m_arity + && !this->m_guard && !prhs->m_guard + && this->m_param_types == prhs->m_param_types); + } + + bool call_match(const std::vector &vals, const Type_Conversions_State &t_conversions) const override + { + return call_match_internal(vals, t_conversions).first; + } + + + Proxy_Function get_guard() const + { + return m_guard; + } + + bool has_parse_tree() const { + return static_cast(m_parsenode); + } + + const AST_Node &get_parse_tree() const + { + if (m_parsenode) { + return *m_parsenode; + } else { + throw std::runtime_error("Dynamic_Proxy_Function does not have parse_tree"); + } + } + + + protected: + bool test_guard(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const + { + if (m_guard) + { + try { + return boxed_cast((*m_guard)(params, t_conversions)); + } catch (const exception::arity_error &) { + return false; + } catch (const exception::bad_boxed_cast &) { + return false; + } + } else { + return true; + } + } + + // first result: is a match + // second result: needs conversions + std::pair call_match_internal(const std::vector &vals, const Type_Conversions_State &t_conversions) const + { + const auto comparison_result = [&](){ + if (m_arity < 0) { + return std::make_pair(true, false); + } else if (vals.size() == size_t(m_arity)) { + return m_param_types.match(vals, t_conversions); + } else { + return std::make_pair(false, false); + } + }(); + + return std::make_pair( + comparison_result.first && test_guard(vals, t_conversions), + comparison_result.second + ); + } + + private: + static std::vector build_param_type_list(const Param_Types &t_types) + { + // For the return type + std::vector types{chaiscript::detail::Get_Type_Info::get()}; + + for (const auto &t : t_types.types()) + { + if (t.second.is_undef()) { + types.push_back(chaiscript::detail::Get_Type_Info::get()); + } else { + types.push_back(t.second); + } + } + + return types; + } + + protected: + Param_Types m_param_types; + + private: + Proxy_Function m_guard; + std::shared_ptr m_parsenode; + }; + + + + template + class Dynamic_Proxy_Function_Impl final : public Dynamic_Proxy_Function + { + public: + Dynamic_Proxy_Function_Impl( + Callable t_f, + int t_arity=-1, + std::shared_ptr t_parsenode = AST_NodePtr(), + Param_Types t_param_types = Param_Types(), + Proxy_Function t_guard = Proxy_Function()) + : Dynamic_Proxy_Function( + t_arity, + std::move(t_parsenode), + std::move(t_param_types), + std::move(t_guard) + ), + m_f(std::move(t_f)) + { + } + + + protected: + Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const override + { + const auto match_results = call_match_internal(params, t_conversions); + if (match_results.first) + { + if (match_results.second) { + return m_f(m_param_types.convert(params, t_conversions)); + } else { + return m_f(params); + } + } else { + throw exception::guard_error(); + } + } + + private: + Callable m_f; + }; + + template + Proxy_Function make_dynamic_proxy_function(Callable &&c, Arg&& ... a) + { + return chaiscript::make_shared>( + std::forward(c), std::forward(a)...); + } + + /// An object used by Bound_Function to represent "_" parameters + /// of a binding. This allows for unbound parameters during bind. + struct Placeholder_Object + { + }; + + /// An implementation of Proxy_Function that takes a Proxy_Function + /// and substitutes bound parameters into the parameter list + /// at runtime, when call() is executed. + /// it is used for bind(function, param1, _, param2) style calls + class Bound_Function final : public Proxy_Function_Base + { + public: + Bound_Function(const Const_Proxy_Function &t_f, + const std::vector &t_args) + : Proxy_Function_Base(build_param_type_info(t_f, t_args), (t_f->get_arity()<0?-1:static_cast(build_param_type_info(t_f, t_args).size())-1)), + m_f(t_f), m_args(t_args) + { + assert(m_f->get_arity() < 0 || m_f->get_arity() == static_cast(m_args.size())); + } + + bool operator==(const Proxy_Function_Base &t_f) const override + { + return &t_f == this; + } + + + bool call_match(const std::vector &vals, const Type_Conversions_State &t_conversions) const override + { + return m_f->call_match(build_param_list(vals), t_conversions); + } + + std::vector get_contained_functions() const override + { + return std::vector{m_f}; + } + + + std::vector build_param_list(const std::vector ¶ms) const + { + auto parg = params.begin(); + auto barg = m_args.begin(); + + std::vector args; + + while (!(parg == params.end() && barg == m_args.end())) + { + while (barg != m_args.end() + && !(barg->get_type_info() == chaiscript::detail::Get_Type_Info::get())) + { + args.push_back(*barg); + ++barg; + } + + if (parg != params.end()) + { + args.push_back(*parg); + ++parg; + } + + if (barg != m_args.end() + && barg->get_type_info() == chaiscript::detail::Get_Type_Info::get()) + { + ++barg; + } + } + return args; + } + + + protected: + static std::vector build_param_type_info(const Const_Proxy_Function &t_f, + const std::vector &t_args) + { + assert(t_f->get_arity() < 0 || t_f->get_arity() == static_cast(t_args.size())); + + if (t_f->get_arity() < 0) { return std::vector(); } + + const auto types = t_f->get_param_types(); + assert(types.size() == t_args.size() + 1); + + // this analysis warning is invalid in MSVC12 and doesn't exist in MSVC14 + std::vector retval{types[0]}; + + for (size_t i = 0; i < types.size() - 1; ++i) + { + if (t_args[i].get_type_info() == chaiscript::detail::Get_Type_Info::get()) + { + retval.push_back(types[i+1]); + } + } + + return retval; + } + + Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const override + { + return (*m_f)(build_param_list(params), t_conversions); + } + + private: + Const_Proxy_Function m_f; + std::vector m_args; + }; + + class Proxy_Function_Impl_Base : public Proxy_Function_Base + { + public: + explicit Proxy_Function_Impl_Base(const std::vector &t_types) + : Proxy_Function_Base(t_types, static_cast(t_types.size()) - 1) + { + } + + bool call_match(const std::vector &vals, const Type_Conversions_State &t_conversions) const override + { + return static_cast(vals.size()) == get_arity() + && (compare_types(m_types, vals, t_conversions) && compare_types_with_cast(vals, t_conversions)); + } + + virtual bool compare_types_with_cast(const std::vector &vals, const Type_Conversions_State &t_conversions) const = 0; + }; + + + + /// For any callable object + template + class Proxy_Function_Callable_Impl final : public Proxy_Function_Impl_Base + { + public: + explicit Proxy_Function_Callable_Impl(Callable f) + : Proxy_Function_Impl_Base(detail::build_param_type_list(static_cast(nullptr))), + m_f(std::move(f)) + { + } + + bool compare_types_with_cast(const std::vector &vals, const Type_Conversions_State &t_conversions) const override + { + return detail::compare_types_cast(static_cast(nullptr), vals, t_conversions); + } + + bool operator==(const Proxy_Function_Base &t_func) const override + { + return dynamic_cast *>(&t_func) != nullptr; + } + + + protected: + Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const override + { + return detail::call_func(detail::Function_Signature(), m_f, params, t_conversions); + } + + private: + Callable m_f; + }; + + + class Assignable_Proxy_Function : public Proxy_Function_Impl_Base + { + public: + explicit Assignable_Proxy_Function(const std::vector &t_types) + : Proxy_Function_Impl_Base(t_types) + { + } + + virtual void assign(const std::shared_ptr &t_rhs) = 0; + }; + + template + class Assignable_Proxy_Function_Impl final : public Assignable_Proxy_Function + { + public: + Assignable_Proxy_Function_Impl(std::reference_wrapper> t_f, std::shared_ptr> t_ptr) + : Assignable_Proxy_Function(detail::build_param_type_list(static_cast(nullptr))), + m_f(std::move(t_f)), m_shared_ptr_holder(std::move(t_ptr)) + { + assert(!m_shared_ptr_holder || m_shared_ptr_holder.get() == &m_f.get()); + } + + bool compare_types_with_cast(const std::vector &vals, const Type_Conversions_State &t_conversions) const override + { + return detail::compare_types_cast(static_cast(nullptr), vals, t_conversions); + } + + bool operator==(const Proxy_Function_Base &t_func) const override + { + return dynamic_cast *>(&t_func) != nullptr; + } + + std::function internal_function() const + { + return m_f.get(); + } + + void assign(const std::shared_ptr &t_rhs) override { + m_f.get() = dispatch::functor(t_rhs, nullptr); + } + + protected: + Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const override + { + return detail::call_func(detail::Function_Signature(), m_f.get(), params, t_conversions); + } + + + private: + std::reference_wrapper> m_f; + std::shared_ptr> m_shared_ptr_holder; + }; + + + /// Attribute getter Proxy_Function implementation + template + class Attribute_Access final : public Proxy_Function_Base + { + public: + explicit Attribute_Access(T Class::* t_attr) + : Proxy_Function_Base(param_types(), 1), + m_attr(t_attr) + { + } + + bool is_attribute_function() const override { return true; } + + bool operator==(const Proxy_Function_Base &t_func) const override + { + const Attribute_Access * aa + = dynamic_cast *>(&t_func); + + if (aa) { + return m_attr == aa->m_attr; + } else { + return false; + } + } + + bool call_match(const std::vector &vals, const Type_Conversions_State &) const override + { + if (vals.size() != 1) + { + return false; + } + + return vals[0].get_type_info().bare_equal(user_type()); + } + + protected: + Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions_State &t_conversions) const override + { + const Boxed_Value &bv = params[0]; + if (bv.is_const()) + { + const Class *o = boxed_cast(bv, &t_conversions); + return do_call_impl(o); + } else { + Class *o = boxed_cast(bv, &t_conversions); + return do_call_impl(o); + } + } + + private: + template + auto do_call_impl(Class *o) const -> std::enable_if_t::value, Boxed_Value> + { + return detail::Handle_Return::handle(o->*m_attr); + } + + template + auto do_call_impl(const Class *o) const -> std::enable_if_t::value, Boxed_Value> + { + return detail::Handle_Return::handle(o->*m_attr); + } + + template + auto do_call_impl(Class *o) const -> std::enable_if_t::value, Boxed_Value> + { + return detail::Handle_Return::type>::handle(o->*m_attr); + } + + template + auto do_call_impl(const Class *o) const -> std::enable_if_t::value, Boxed_Value> + { + return detail::Handle_Return::type>::type>::handle(o->*m_attr); + } + + + + static std::vector param_types() + { + return {user_type(), user_type()}; + } + + T Class::* m_attr; + }; + } + + namespace exception + { + /// \brief Exception thrown in the case that a method dispatch fails + /// because no matching function was found + /// + /// May be thrown due to an arity_error, a guard_error or a bad_boxed_cast + /// exception + class dispatch_error : public std::runtime_error + { + public: + dispatch_error(std::vector t_parameters, + std::vector t_functions) + : std::runtime_error("Error with function dispatch"), parameters(std::move(t_parameters)), functions(std::move(t_functions)) + { + } + + dispatch_error(std::vector t_parameters, + std::vector t_functions, + const std::string &t_desc) + : std::runtime_error(t_desc), parameters(std::move(t_parameters)), functions(std::move(t_functions)) + { + } + + + dispatch_error(const dispatch_error &) = default; + ~dispatch_error() noexcept override = default; + + std::vector parameters; + std::vector functions; + }; + } + + namespace dispatch + { + namespace detail + { + template + bool types_match_except_for_arithmetic(const FuncType &t_func, const std::vector &plist, + const Type_Conversions_State &t_conversions) + { + const std::vector &types = t_func->get_param_types(); + + if (t_func->get_arity() == -1) { return false; } + + assert(plist.size() == types.size() - 1); + + return std::mismatch(plist.begin(), plist.end(), + types.begin()+1, + [&](const Boxed_Value &bv, const Type_Info &ti) { + return Proxy_Function_Base::compare_type_to_param(ti, bv, t_conversions) + || (bv.get_type_info().is_arithmetic() && ti.is_arithmetic()); + } + ) == std::make_pair(plist.end(), types.end()); + } + + template + Boxed_Value dispatch_with_conversions(InItr begin, const InItr &end, const std::vector &plist, + const Type_Conversions_State &t_conversions, const Funcs &t_funcs) + { + InItr matching_func(end); + + while (begin != end) + { + if (types_match_except_for_arithmetic(begin->second, plist, t_conversions)) + { + if (matching_func == end) + { + matching_func = begin; + } else { + // handle const members vs non-const member, which is not really ambiguous + const auto &mat_fun_param_types = matching_func->second->get_param_types(); + const auto &next_fun_param_types = begin->second->get_param_types(); + + if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { + matching_func = begin; // keep the new one, the const/non-const matchup is correct + } else if (!plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { + // keep the old one, it has a better const/non-const matchup + } else { + // ambiguous function call + throw exception::dispatch_error(plist, std::vector(t_funcs.begin(), t_funcs.end())); + } + } + } + + ++begin; + } + + if (matching_func == end) + { + // no appropriate function to attempt arithmetic type conversion on + throw exception::dispatch_error(plist, std::vector(t_funcs.begin(), t_funcs.end())); + } + + + std::vector newplist; + newplist.reserve(plist.size()); + + const std::vector &tis = matching_func->second->get_param_types(); + std::transform(tis.begin() + 1, tis.end(), + plist.begin(), + std::back_inserter(newplist), + [](const Type_Info &ti, const Boxed_Value ¶m) -> Boxed_Value { + if (ti.is_arithmetic() && param.get_type_info().is_arithmetic() + && param.get_type_info() != ti) { + return Boxed_Number(param).get_as(ti).bv; + } else { + return param; + } + } + ); + + try { + return (*(matching_func->second))(newplist, t_conversions); + } catch (const exception::bad_boxed_cast &) { + //parameter failed to cast + } catch (const exception::arity_error &) { + //invalid num params + } catch (const exception::guard_error &) { + //guard failed to allow the function to execute + } + + throw exception::dispatch_error(plist, std::vector(t_funcs.begin(), t_funcs.end())); + + } + } + + /// Take a vector of functions and a vector of parameters. Attempt to execute + /// each function against the set of parameters, in order, until a matching + /// function is found or throw dispatch_error if no matching function is found + template + Boxed_Value dispatch(const Funcs &funcs, + const std::vector &plist, const Type_Conversions_State &t_conversions) + { + std::vector> ordered_funcs; + ordered_funcs.reserve(funcs.size()); + + for (const auto &func : funcs) + { + const auto arity = func->get_arity(); + + if (arity == -1) + { + ordered_funcs.emplace_back(plist.size(), func.get()); + } else if (arity == static_cast(plist.size())) { + size_t numdiffs = 0; + for (size_t i = 0; i < plist.size(); ++i) + { + if (!func->get_param_types()[i+1].bare_equal(plist[i].get_type_info())) + { + ++numdiffs; + } + } + ordered_funcs.emplace_back(numdiffs, func.get()); + } + } + + + for (size_t i = 0; i <= plist.size(); ++i) + { + for (const auto &func : ordered_funcs ) + { + try { + if (func.first == i && (i == 0 || func.second->filter(plist, t_conversions))) + { + return (*(func.second))(plist, t_conversions); + } + } catch (const exception::bad_boxed_cast &) { + //parameter failed to cast, try again + } catch (const exception::arity_error &) { + //invalid num params, try again + } catch (const exception::guard_error &) { + //guard failed to allow the function to execute, + //try again + } + } + } + + return detail::dispatch_with_conversions(ordered_funcs.cbegin(), ordered_funcs.cend(), plist, t_conversions, funcs); + } + } +} + + +#endif diff --git a/chaiscript/dispatchkit/proxy_functions_detail.hpp b/chaiscript/dispatchkit/proxy_functions_detail.hpp new file mode 100644 index 0000000..9ce5846 --- /dev/null +++ b/chaiscript/dispatchkit/proxy_functions_detail.hpp @@ -0,0 +1,139 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_PROXY_FUNCTIONS_DETAIL_HPP_ +#define CHAISCRIPT_PROXY_FUNCTIONS_DETAIL_HPP_ + +#include +#include +#include +#include + +#include "../chaiscript_defines.hpp" +#include "boxed_cast.hpp" +#include "boxed_value.hpp" +#include "handle_return.hpp" +#include "type_info.hpp" +#include "callable_traits.hpp" + +namespace chaiscript { +class Type_Conversions_State; +namespace exception { +class bad_boxed_cast; +} // namespace exception +} // namespace chaiscript + +namespace chaiscript +{ + namespace exception + { + /** + * Exception thrown when there is a mismatch in number of + * parameters during Proxy_Function execution + */ + struct arity_error : std::range_error + { + arity_error(int t_got, int t_expected) + : std::range_error("Function dispatch arity mismatch"), + got(t_got), expected(t_expected) + { + } + + arity_error(const arity_error &) = default; + + ~arity_error() noexcept override = default; + + int got; + int expected; + }; + } + + namespace dispatch + { + namespace detail + { + /** + * Used by Proxy_Function_Impl to return a list of all param types + * it contains. + */ + template + std::vector build_param_type_list(Ret (*)(Params...)) + { + /// \note somehow this is responsible for a large part of the code generation + return { user_type(), user_type()... }; + } + + + /** + * Used by Proxy_Function_Impl to determine if it is equivalent to another + * Proxy_Function_Impl object. This function is primarily used to prevent + * registration of two functions with the exact same signatures + */ + template + bool compare_types_cast(Ret (*)(Params...), + const std::vector ¶ms, const Type_Conversions_State &t_conversions) + { + try { + std::vector::size_type i = 0; + (void)i; + (void)params; (void)t_conversions; + // this is ok because the order of evaluation of initializer lists is well defined + (void)std::initializer_list{(boxed_cast(params[i++], &t_conversions), 0)...}; + return true; + } catch (const exception::bad_boxed_cast &) { + return false; + } + } + + + template + Ret call_func(const chaiscript::dispatch::detail::Function_Signature &, + std::index_sequence, const Callable &f, + const std::vector ¶ms, const Type_Conversions_State &t_conversions) + { + (void)params; (void)t_conversions; + return f(boxed_cast(params[I], &t_conversions)...); + } + + + /// Used by Proxy_Function_Impl to perform typesafe execution of a function. + /// The function attempts to unbox each parameter to the expected type. + /// if any unboxing fails the execution of the function fails and + /// the bad_boxed_cast is passed up to the caller. + template + Boxed_Value call_func(const chaiscript::dispatch::detail::Function_Signature &sig, const Callable &f, + const std::vector ¶ms, const Type_Conversions_State &t_conversions) + { + return Handle_Return::handle(call_func(sig, std::index_sequence_for{}, f, params, t_conversions)); + } + + template + Boxed_Value call_func(const chaiscript::dispatch::detail::Function_Signature &sig, const Callable &f, + const std::vector ¶ms, const Type_Conversions_State &t_conversions) + { + call_func(sig, std::index_sequence_for{}, f, params, t_conversions); +#ifdef CHAISCRIPT_MSVC +#pragma warning(push) +#pragma warning(disable : 4702) +#endif + // MSVC is reporting that this is unreachable code - and it's wrong. + return Handle_Return::handle(); +#ifdef CHAISCRIPT_MSVC +#pragma warning(pop) +#endif + } + + } + } + +} + + +#endif diff --git a/chaiscript/dispatchkit/register_function.hpp b/chaiscript/dispatchkit/register_function.hpp new file mode 100644 index 0000000..2e56fe0 --- /dev/null +++ b/chaiscript/dispatchkit/register_function.hpp @@ -0,0 +1,152 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_REGISTER_FUNCTION_HPP_ +#define CHAISCRIPT_REGISTER_FUNCTION_HPP_ + +#include + +#include "bind_first.hpp" +#include "proxy_functions.hpp" + +namespace chaiscript +{ + + /// \brief Creates a new Proxy_Function object from a free function, member function or data member + /// \param[in] t Function / member to expose + /// + /// \b Example: + /// \code + /// int myfunction(const std::string &); + /// class MyClass + /// { + /// public: + /// void memberfunction(); + /// int memberdata; + /// }; + /// + /// chaiscript::ChaiScript chai; + /// chai.add(fun(&myfunction), "myfunction"); + /// chai.add(fun(&MyClass::memberfunction), "memberfunction"); + /// chai.add(fun(&MyClass::memberdata), "memberdata"); + /// \endcode + /// + /// \sa \ref adding_functions + template + Proxy_Function fun(const T &t) + { + typedef typename dispatch::detail::Callable_Traits::Signature Signature; + + return Proxy_Function( + chaiscript::make_shared>(t)); + } + + + template + Proxy_Function fun(Ret (*func)(Param...)) + { + auto fun_call = dispatch::detail::Fun_Caller(func); + + return Proxy_Function( + chaiscript::make_shared>(fun_call)); + + } + + template + Proxy_Function fun(Ret (Class::*t_func)(Param...) const) + { + auto call = dispatch::detail::Const_Caller(t_func); + + return Proxy_Function( + chaiscript::make_shared>(call)); + } + + template + Proxy_Function fun(Ret (Class::*t_func)(Param...)) + { + auto call = dispatch::detail::Caller(t_func); + + return Proxy_Function( + chaiscript::make_shared>(call)); + + } + + template::value>::type*/> + Proxy_Function fun(T Class::* m /*, typename std::enable_if::value>::type* = 0*/ ) + { + return Proxy_Function(chaiscript::make_shared>(m)); + } + +// only compile this bit if noexcept is part of the type system +// +#if (defined(__cpp_noexcept_function_type) && __cpp_noexcept_function_type >= 201510) || (defined(_NOEXCEPT_TYPES_SUPPORTED) && _MSC_VER >= 1912) + template + Proxy_Function fun(Ret (*func)(Param...) noexcept) + { + auto fun_call = dispatch::detail::Fun_Caller(func); + + return Proxy_Function( + chaiscript::make_shared>(fun_call)); + + } + + template + Proxy_Function fun(Ret (Class::*t_func)(Param...) const noexcept) + { + auto call = dispatch::detail::Const_Caller(t_func); + + return Proxy_Function( + chaiscript::make_shared>(call)); + } + + template + Proxy_Function fun(Ret (Class::*t_func)(Param...) noexcept) + { + auto call = dispatch::detail::Caller(t_func); + + return Proxy_Function( + chaiscript::make_shared>(call)); + + } +#endif + + + + + /// \brief Creates a new Proxy_Function object from a free function, member function or data member and binds the first parameter of it + /// \param[in] t Function / member to expose + /// \param[in] q Value to bind to first parameter + /// + /// \b Example: + /// \code + /// struct MyClass + /// { + /// void memberfunction(int); + /// }; + /// + /// MyClass obj; + /// chaiscript::ChaiScript chai; + /// // Add function taking only one argument, an int, and permanently bound to "obj" + /// chai.add(fun(&MyClass::memberfunction, std::ref(obj)), "memberfunction"); + /// \endcode + /// + /// \sa \ref adding_functions + template + Proxy_Function fun(T &&t, const Q &q) + { + return fun(detail::bind_first(std::forward(t), q)); + } + + +} + + +#endif + diff --git a/chaiscript/dispatchkit/short_alloc.hpp b/chaiscript/dispatchkit/short_alloc.hpp new file mode 100644 index 0000000..e1cc9a9 --- /dev/null +++ b/chaiscript/dispatchkit/short_alloc.hpp @@ -0,0 +1,159 @@ +#ifndef SHORT_ALLOC_H +#define SHORT_ALLOC_H + +// The MIT License (MIT) +// +// Copyright (c) 2015 Howard Hinnant +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include +#include + +template +class arena +{ + alignas(alignment) char buf_[N]; + char* ptr_; + +public: + ~arena() {ptr_ = nullptr;} + arena() noexcept : ptr_(buf_) {} + arena(const arena&) = delete; + arena& operator=(const arena&) = delete; + + template char* allocate(std::size_t n); + void deallocate(char* p, std::size_t n) noexcept; + + static constexpr std::size_t size() noexcept {return N;} + std::size_t used() const noexcept {return static_cast(ptr_ - buf_);} + void reset() noexcept {ptr_ = buf_;} + +private: + static + std::size_t + align_up(std::size_t n) noexcept + {return (n + (alignment-1)) & ~(alignment-1);} + + bool + pointer_in_buffer(char* p) noexcept + {return buf_ <= p && p <= buf_ + N;} +}; + +template +template +char* +arena::allocate(std::size_t n) +{ + static_assert(ReqAlign <= alignment, "alignment is too small for this arena"); + assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena"); + auto const aligned_n = align_up(n); + if (static_cast(buf_ + N - ptr_) >= aligned_n) + { + char* r = ptr_; + ptr_ += aligned_n; + return r; + } + + static_assert(alignment <= alignof(std::max_align_t), "you've chosen an " + "alignment that is larger than alignof(std::max_align_t), and " + "cannot be guaranteed by normal operator new"); + return static_cast(::operator new(n)); +} + +template +void +arena::deallocate(char* p, std::size_t n) noexcept +{ + assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena"); + if (pointer_in_buffer(p)) + { + n = align_up(n); + if (p + n == ptr_) { + ptr_ = p; + } + } + else { + ::operator delete(p); + } +} + +template +class short_alloc +{ +public: + using value_type = T; + static auto constexpr alignment = Align; + static auto constexpr size = N; + using arena_type = arena; + +private: + arena_type& a_; + +public: + short_alloc(const short_alloc&) = default; + short_alloc& operator=(const short_alloc&) = delete; + + explicit short_alloc(arena_type& a) noexcept : a_(a) + { + static_assert(size % alignment == 0, + "size N needs to be a multiple of alignment Align"); + } + template + explicit short_alloc(const short_alloc& a) noexcept + : a_(a.a_) {} + + template struct rebind {using other = short_alloc<_Up, N, alignment>;}; + + T* allocate(std::size_t n) + { + return reinterpret_cast(a_.template allocate(n*sizeof(T))); + } + void deallocate(T* p, std::size_t n) noexcept + { + a_.deallocate(reinterpret_cast(p), n*sizeof(T)); + } + + template + friend + bool + operator==(const short_alloc& x, const short_alloc& y) noexcept; + + template friend class short_alloc; +}; + +template +inline +bool +operator==(const short_alloc& x, const short_alloc& y) noexcept +{ + return N == M && A1 == A2 && &x.a_ == &y.a_; +} + +template +inline +bool +operator!=(const short_alloc& x, const short_alloc& y) noexcept +{ + return !(x == y); +} + +#endif // SHORT_ALLOC_HPP + diff --git a/chaiscript/dispatchkit/type_conversions.hpp b/chaiscript/dispatchkit/type_conversions.hpp new file mode 100644 index 0000000..d9d2f37 --- /dev/null +++ b/chaiscript/dispatchkit/type_conversions.hpp @@ -0,0 +1,649 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_DYNAMIC_CAST_CONVERSION_HPP_ +#define CHAISCRIPT_DYNAMIC_CAST_CONVERSION_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include "../chaiscript_threading.hpp" +#include "bad_boxed_cast.hpp" +#include "boxed_cast_helper.hpp" +#include "boxed_value.hpp" +#include "type_info.hpp" + +namespace chaiscript +{ + namespace exception + { + class bad_boxed_dynamic_cast : public bad_boxed_cast + { + public: + bad_boxed_dynamic_cast(const Type_Info &t_from, const std::type_info &t_to, + const std::string &t_what) noexcept + : bad_boxed_cast(t_from, t_to, t_what) + { + } + + bad_boxed_dynamic_cast(const Type_Info &t_from, const std::type_info &t_to) noexcept + : bad_boxed_cast(t_from, t_to) + { + } + + explicit bad_boxed_dynamic_cast(const std::string &w) noexcept + : bad_boxed_cast(w) + { + } + + bad_boxed_dynamic_cast(const bad_boxed_dynamic_cast &) = default; + + ~bad_boxed_dynamic_cast() noexcept override = default; + }; + + class bad_boxed_type_cast : public bad_boxed_cast + { + public: + bad_boxed_type_cast(const Type_Info &t_from, const std::type_info &t_to, + const std::string &t_what) noexcept + : bad_boxed_cast(t_from, t_to, t_what) + { + } + + bad_boxed_type_cast(const Type_Info &t_from, const std::type_info &t_to) noexcept + : bad_boxed_cast(t_from, t_to) + { + } + + explicit bad_boxed_type_cast(const std::string &w) noexcept + : bad_boxed_cast(w) + { + } + + bad_boxed_type_cast(const bad_boxed_type_cast &) = default; + + ~bad_boxed_type_cast() noexcept override = default; + }; + } + + + namespace detail + { + class Type_Conversion_Base + { + public: + virtual Boxed_Value convert(const Boxed_Value &from) const = 0; + virtual Boxed_Value convert_down(const Boxed_Value &to) const = 0; + + const Type_Info &to() const + { + return m_to; + } + const Type_Info &from() const + { + return m_from; + } + + virtual bool bidir() const + { + return true; + } + + virtual ~Type_Conversion_Base() = default; + + protected: + Type_Conversion_Base(Type_Info t_to, Type_Info t_from) + : m_to(std::move(t_to)), m_from(std::move(t_from)) + { + } + + + private: + const Type_Info m_to; + const Type_Info m_from; + + }; + + template + class Static_Caster + { + public: + static Boxed_Value cast(const Boxed_Value &t_from) + { + if (t_from.get_type_info().bare_equal(chaiscript::user_type())) + { + if (t_from.is_pointer()) + { + // Dynamic cast out the contained boxed value, which we know is the type we want + if (t_from.is_const()) + { + return Boxed_Value( + [&](){ + if (auto data = std::static_pointer_cast(detail::Cast_Helper >::cast(t_from, nullptr))) + { + return data; + } else { + throw std::bad_cast(); + } + }() + ); + } else { + return Boxed_Value( + [&](){ + if (auto data = std::static_pointer_cast(detail::Cast_Helper >::cast(t_from, nullptr))) + { + return data; + } else { + throw std::bad_cast(); + } + }() + ); + } + } else { + // Pull the reference out of the contained boxed value, which we know is the type we want + if (t_from.is_const()) + { + const From &d = detail::Cast_Helper::cast(t_from, nullptr); + const To &data = static_cast(d); + return Boxed_Value(std::cref(data)); + } else { + From &d = detail::Cast_Helper::cast(t_from, nullptr); + To &data = static_cast(d); + return Boxed_Value(std::ref(data)); + } + } + } else { + throw chaiscript::exception::bad_boxed_dynamic_cast(t_from.get_type_info(), typeid(To), "Unknown dynamic_cast_conversion"); + } + } + + }; + + + template + class Dynamic_Caster + { + public: + static Boxed_Value cast(const Boxed_Value &t_from) + { + if (t_from.get_type_info().bare_equal(chaiscript::user_type())) + { + if (t_from.is_pointer()) + { + // Dynamic cast out the contained boxed value, which we know is the type we want + if (t_from.is_const()) + { + return Boxed_Value( + [&](){ + if (auto data = std::dynamic_pointer_cast(detail::Cast_Helper >::cast(t_from, nullptr))) + { + return data; + } else { + throw std::bad_cast(); + } + }() + ); + } else { + return Boxed_Value( + [&](){ + if (auto data = std::dynamic_pointer_cast(detail::Cast_Helper >::cast(t_from, nullptr))) + { + return data; + } else { +#ifdef CHAISCRIPT_LIBCPP + /// \todo fix this someday after libc++ is fixed. + if (std::string(typeid(To).name()).find("Assignable_Proxy_Function") != std::string::npos) { + auto from = detail::Cast_Helper >::cast(t_from, nullptr); + if (std::string(typeid(*from).name()).find("Assignable_Proxy_Function_Impl") != std::string::npos) { + return std::static_pointer_cast(from); + } + } +#endif + throw std::bad_cast(); + } + }() + ); + } + } else { + // Pull the reference out of the contained boxed value, which we know is the type we want + if (t_from.is_const()) + { + const From &d = detail::Cast_Helper::cast(t_from, nullptr); + const To &data = dynamic_cast(d); + return Boxed_Value(std::cref(data)); + } else { + From &d = detail::Cast_Helper::cast(t_from, nullptr); + To &data = dynamic_cast(d); + return Boxed_Value(std::ref(data)); + } + } + } else { + throw chaiscript::exception::bad_boxed_dynamic_cast(t_from.get_type_info(), typeid(To), "Unknown dynamic_cast_conversion"); + } + } + + }; + + + template + class Dynamic_Conversion_Impl : public Type_Conversion_Base + { + public: + Dynamic_Conversion_Impl() + : Type_Conversion_Base(chaiscript::user_type(), chaiscript::user_type()) + { + } + + Boxed_Value convert_down(const Boxed_Value &t_base) const override + { + return Dynamic_Caster::cast(t_base); + } + + Boxed_Value convert(const Boxed_Value &t_derived) const override + { + return Static_Caster::cast(t_derived); + } + }; + + template + class Static_Conversion_Impl : public Type_Conversion_Base + { + public: + Static_Conversion_Impl() + : Type_Conversion_Base(chaiscript::user_type(), chaiscript::user_type()) + { + } + + Boxed_Value convert_down(const Boxed_Value &t_base) const override + { + throw chaiscript::exception::bad_boxed_dynamic_cast(t_base.get_type_info(), typeid(Derived), + "Unable to cast down inheritance hierarchy with non-polymorphic types"); + } + + bool bidir() const override + { + return false; + } + + Boxed_Value convert(const Boxed_Value &t_derived) const override + { + return Static_Caster::cast(t_derived); + } + }; + + + + template + class Type_Conversion_Impl : public Type_Conversion_Base + { + public: + Type_Conversion_Impl(Type_Info t_from, Type_Info t_to, Callable t_func) + : Type_Conversion_Base(t_to, t_from), + m_func(std::move(t_func)) + { + } + + Boxed_Value convert_down(const Boxed_Value &) const override + { + throw chaiscript::exception::bad_boxed_type_cast("No conversion exists"); + } + + Boxed_Value convert(const Boxed_Value &t_from) const override + { + /// \todo better handling of errors from the conversion function + return m_func(t_from); + } + + bool bidir() const override + { + return false; + } + + + private: + Callable m_func; + }; + } + + class Type_Conversions + { + public: + struct Conversion_Saves + { + bool enabled = false; + std::vector saves; + }; + + struct Less_Than + { + bool operator()(const std::type_info *t_lhs, const std::type_info *t_rhs) const + { + return *t_lhs != *t_rhs && t_lhs->before(*t_rhs); + } + }; + + Type_Conversions() + : m_mutex(), + m_conversions(), + m_convertableTypes(), + m_num_types(0) + { + } + + Type_Conversions(const Type_Conversions &t_other) = delete; + Type_Conversions(Type_Conversions &&) = default; + + Type_Conversions &operator=(const Type_Conversions &) = delete; + Type_Conversions &operator=(Type_Conversions &&) = default; + + const std::set &thread_cache() const + { + auto &cache = *m_thread_cache; + if (cache.size() != m_num_types) + { + chaiscript::detail::threading::shared_lock l(m_mutex); + cache = m_convertableTypes; + } + + return cache; + } + + void add_conversion(const std::shared_ptr &conversion) + { + chaiscript::detail::threading::unique_lock l(m_mutex); + /// \todo error if a conversion already exists + m_conversions.insert(conversion); + m_convertableTypes.insert({conversion->to().bare_type_info(), conversion->from().bare_type_info()}); + m_num_types = m_convertableTypes.size(); + } + + template + bool convertable_type() const + { + return thread_cache().count(user_type().bare_type_info()) != 0; + } + + template + bool converts() const + { + return converts(user_type(), user_type()); + } + + bool converts(const Type_Info &to, const Type_Info &from) const + { + const auto &types = thread_cache(); + if (types.count(to.bare_type_info()) != 0 && types.count(from.bare_type_info()) != 0) + { + return has_conversion(to, from); + } else { + return false; + } + } + + template + Boxed_Value boxed_type_conversion(Conversion_Saves &t_saves, const Boxed_Value &from) const + { + return boxed_type_conversion(user_type(), t_saves, from); + } + + template + Boxed_Value boxed_type_down_conversion(Conversion_Saves &t_saves, const Boxed_Value &to) const + { + return boxed_type_down_conversion(user_type(), t_saves, to); + } + + + Boxed_Value boxed_type_conversion(const Type_Info &to, Conversion_Saves &t_saves, const Boxed_Value &from) const + { + try { + Boxed_Value ret = get_conversion(to, from.get_type_info())->convert(from); + if (t_saves.enabled) { t_saves.saves.push_back(ret); } + return ret; + } catch (const std::out_of_range &) { + throw exception::bad_boxed_dynamic_cast(from.get_type_info(), *to.bare_type_info(), "No known conversion"); + } catch (const std::bad_cast &) { + throw exception::bad_boxed_dynamic_cast(from.get_type_info(), *to.bare_type_info(), "Unable to perform dynamic_cast operation"); + } + } + + Boxed_Value boxed_type_down_conversion(const Type_Info &from, Conversion_Saves &t_saves, const Boxed_Value &to) const + { + try { + Boxed_Value ret = get_conversion(to.get_type_info(), from)->convert_down(to); + if (t_saves.enabled) { t_saves.saves.push_back(ret); } + return ret; + } catch (const std::out_of_range &) { + throw exception::bad_boxed_dynamic_cast(to.get_type_info(), *from.bare_type_info(), "No known conversion"); + } catch (const std::bad_cast &) { + throw exception::bad_boxed_dynamic_cast(to.get_type_info(), *from.bare_type_info(), "Unable to perform dynamic_cast operation"); + } + } + + static void enable_conversion_saves(Conversion_Saves &t_saves, bool t_val) + { + t_saves.enabled = t_val; + } + + std::vector take_saves(Conversion_Saves &t_saves) + { + std::vector ret; + std::swap(ret, t_saves.saves); + return ret; + } + + bool has_conversion(const Type_Info &to, const Type_Info &from) const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + return find_bidir(to, from) != m_conversions.end(); + } + + std::shared_ptr get_conversion(const Type_Info &to, const Type_Info &from) const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + const auto itr = find(to, from); + + if (itr != m_conversions.end()) + { + return *itr; + } else { + throw std::out_of_range("No such conversion exists from " + from.bare_name() + " to " + to.bare_name()); + } + } + + Conversion_Saves &conversion_saves() const { + return *m_conversion_saves; + } + + private: + std::set >::const_iterator find_bidir( + const Type_Info &to, const Type_Info &from) const + { + return std::find_if(m_conversions.begin(), m_conversions.end(), + [&to, &from](const std::shared_ptr &conversion) -> bool + { + return (conversion->to().bare_equal(to) && conversion->from().bare_equal(from)) + || (conversion->bidir() && conversion->from().bare_equal(to) && conversion->to().bare_equal(from)); + } + ); + } + + std::set >::const_iterator find( + const Type_Info &to, const Type_Info &from) const + { + return std::find_if(m_conversions.begin(), m_conversions.end(), + [&to, &from](const std::shared_ptr &conversion) + { + return conversion->to().bare_equal(to) && conversion->from().bare_equal(from); + } + ); + } + + std::set> get_conversions() const + { + chaiscript::detail::threading::shared_lock l(m_mutex); + + return m_conversions; + } + + + + mutable chaiscript::detail::threading::shared_mutex m_mutex; + std::set> m_conversions; + std::set m_convertableTypes; + std::atomic_size_t m_num_types; + mutable chaiscript::detail::threading::Thread_Storage> m_thread_cache; + mutable chaiscript::detail::threading::Thread_Storage m_conversion_saves; + }; + + class Type_Conversions_State + { + public: + Type_Conversions_State(const Type_Conversions &t_conversions, + Type_Conversions::Conversion_Saves &t_saves) + : m_conversions(t_conversions), + m_saves(t_saves) + { + } + + const Type_Conversions *operator->() const { + return &m_conversions.get(); + } + + const Type_Conversions *get() const { + return &m_conversions.get(); + } + + Type_Conversions::Conversion_Saves &saves() const { + return m_saves; + } + + private: + std::reference_wrapper m_conversions; + std::reference_wrapper m_saves; + }; + + typedef std::shared_ptr Type_Conversion; + + /// \brief Used to register a to / parent class relationship with ChaiScript. Necessary if you + /// want automatic conversions up your inheritance hierarchy. + /// + /// Create a new to class registration for applying to a module or to the ChaiScript engine + /// Currently, due to limitations in module loading on Windows, and for the sake of portability, + /// if you have a type that is introduced in a loadable module and is used by multiple modules + /// (through a tertiary dll that is shared between the modules, static linking the new type + /// into both loadable modules would not be portable), you need to register the type + /// relationship in all modules that use the newly added type in a polymorphic way. + /// + /// Example: + /// \code + /// class Base + /// {}; + /// class Derived : public Base + /// {}; + /// + /// chaiscript::ChaiScript chai; + /// chai.add(chaiscript::to_class()); + /// \endcode + /// + template + Type_Conversion base_class(typename std::enable_if::value && std::is_polymorphic::value>::type* = nullptr) + { + //Can only be used with related polymorphic types + //may be expanded some day to support conversions other than child -> parent + static_assert(std::is_base_of::value, "Classes are not related by inheritance"); + + return chaiscript::make_shared>(); + } + + template + Type_Conversion base_class(typename std::enable_if::value || !std::is_polymorphic::value>::type* = nullptr) + { + //Can only be used with related polymorphic types + //may be expanded some day to support conversions other than child -> parent + static_assert(std::is_base_of::value, "Classes are not related by inheritance"); + + return chaiscript::make_shared>(); + } + + + template + Type_Conversion type_conversion(const Type_Info &t_from, const Type_Info &t_to, + const Callable &t_func) + { + return chaiscript::make_shared>(t_from, t_to, t_func); + } + + template + Type_Conversion type_conversion(const Callable &t_function) + { + auto func = [t_function](const Boxed_Value &t_bv) -> Boxed_Value { + // not even attempting to call boxed_cast so that we don't get caught in some call recursion + return chaiscript::Boxed_Value(t_function(detail::Cast_Helper::cast(t_bv, nullptr))); + }; + + return chaiscript::make_shared>(user_type(), user_type(), func); + } + + template + Type_Conversion type_conversion() + { + static_assert(std::is_convertible::value, "Types are not automatically convertible"); + auto func = [](const Boxed_Value &t_bv) -> Boxed_Value { + // not even attempting to call boxed_cast so that we don't get caught in some call recursion + return chaiscript::Boxed_Value(To(detail::Cast_Helper::cast(t_bv, nullptr))); + }; + + return chaiscript::make_shared>(user_type(), user_type(), func); + } + + template + Type_Conversion vector_conversion() + { + auto func = [](const Boxed_Value &t_bv) -> Boxed_Value { + const std::vector &from_vec = detail::Cast_Helper &>::cast(t_bv, nullptr); + + To vec; + vec.reserve(from_vec.size()); + for (const Boxed_Value &bv : from_vec) { + vec.push_back(detail::Cast_Helper::cast(bv, nullptr)); + } + + return Boxed_Value(std::move(vec)); + }; + + return chaiscript::make_shared>(user_type>(), user_type(), func); + } + + template + Type_Conversion map_conversion() + { + auto func = [](const Boxed_Value &t_bv) -> Boxed_Value { + const std::map &from_map = detail::Cast_Helper &>::cast(t_bv, nullptr); + + To map; + for (const std::pair &p : from_map) { + map.insert(std::make_pair(p.first, detail::Cast_Helper::cast(p.second, nullptr))); + } + + return Boxed_Value(std::move(map)); + }; + + return chaiscript::make_shared>(user_type>(), user_type(), func); + } +} + + +#endif diff --git a/chaiscript/dispatchkit/type_info.hpp b/chaiscript/dispatchkit/type_info.hpp new file mode 100644 index 0000000..53f007b --- /dev/null +++ b/chaiscript/dispatchkit/type_info.hpp @@ -0,0 +1,244 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_TYPE_INFO_HPP_ +#define CHAISCRIPT_TYPE_INFO_HPP_ + +#include +#include +#include +#include + +namespace chaiscript +{ + + namespace detail + { + template + struct Bare_Type + { + typedef typename std::remove_cv::type>::type>::type type; + }; + } + + + /// \brief Compile time deduced information about a type + class Type_Info + { + public: + constexpr Type_Info(const bool t_is_const, const bool t_is_reference, const bool t_is_pointer, const bool t_is_void, + const bool t_is_arithmetic, const std::type_info *t_ti, const std::type_info *t_bare_ti) + : m_type_info(t_ti), m_bare_type_info(t_bare_ti), + m_flags((static_cast(t_is_const) << is_const_flag) + + (static_cast(t_is_reference) << is_reference_flag) + + (static_cast(t_is_pointer) << is_pointer_flag) + + (static_cast(t_is_void) << is_void_flag) + + (static_cast(t_is_arithmetic) << is_arithmetic_flag)) + { + } + + constexpr Type_Info() = default; + + bool operator<(const Type_Info &ti) const noexcept + { + return m_type_info->before(*ti.m_type_info); + } + + constexpr bool operator!=(const Type_Info &ti) const noexcept + { + return !(operator==(ti)); + } + + constexpr bool operator!=(const std::type_info &ti) const noexcept + { + return !(operator==(ti)); + } + + constexpr bool operator==(const Type_Info &ti) const noexcept + { + return ti.m_type_info == m_type_info + || *ti.m_type_info == *m_type_info; + } + + constexpr bool operator==(const std::type_info &ti) const noexcept + { + return !is_undef() && (*m_type_info) == ti; + } + + constexpr bool bare_equal(const Type_Info &ti) const noexcept + { + return ti.m_bare_type_info == m_bare_type_info + || *ti.m_bare_type_info == *m_bare_type_info; + } + + constexpr bool bare_equal_type_info(const std::type_info &ti) const noexcept + { + return !is_undef() && (*m_bare_type_info) == ti; + } + + constexpr bool is_const() const noexcept { return (m_flags & (1 << is_const_flag)) != 0; } + constexpr bool is_reference() const noexcept { return (m_flags & (1 << is_reference_flag)) != 0; } + constexpr bool is_void() const noexcept { return (m_flags & (1 << is_void_flag)) != 0; } + constexpr bool is_arithmetic() const noexcept { return (m_flags & (1 << is_arithmetic_flag)) != 0; } + constexpr bool is_undef() const noexcept { return (m_flags & (1 << is_undef_flag)) != 0; } + constexpr bool is_pointer() const noexcept { return (m_flags & (1 << is_pointer_flag)) != 0; } + + std::string name() const + { + if (!is_undef()) + { + return m_type_info->name(); + } else { + return ""; + } + } + + std::string bare_name() const + { + if (!is_undef()) + { + return m_bare_type_info->name(); + } else { + return ""; + } + } + + constexpr const std::type_info *bare_type_info() const + { + return m_bare_type_info; + } + + private: + struct Unknown_Type {}; + + const std::type_info *m_type_info = &typeid(Unknown_Type); + const std::type_info *m_bare_type_info = &typeid(Unknown_Type); + static const int is_const_flag = 0; + static const int is_reference_flag = 1; + static const int is_pointer_flag = 2; + static const int is_void_flag = 3; + static const int is_arithmetic_flag = 4; + static const int is_undef_flag = 5; + unsigned int m_flags = (1 << is_undef_flag); + }; + + namespace detail + { + /// Helper used to create a Type_Info object + template + struct Get_Type_Info + { + static constexpr Type_Info get() + { + return Type_Info(std::is_const::type>::type>::value, + std::is_reference::value, std::is_pointer::value, + std::is_void::value, + (std::is_arithmetic::value || std::is_arithmetic::type>::value) + && !std::is_same::type>::type, bool>::value, + &typeid(T), + &typeid(typename Bare_Type::type)); + } + }; + + template + struct Get_Type_Info > + { +// typedef T type; + + static constexpr Type_Info get() + { + return Type_Info(std::is_const::value, std::is_reference::value, std::is_pointer::value, + std::is_void::value, + std::is_arithmetic::value && !std::is_same::type>::type, bool>::value, + &typeid(std::shared_ptr ), + &typeid(typename Bare_Type::type)); + } + }; + + template + struct Get_Type_Info &> : Get_Type_Info> + { + }; + + template + struct Get_Type_Info &> + { + static constexpr Type_Info get() + { + return Type_Info(std::is_const::value, std::is_reference::value, std::is_pointer::value, + std::is_void::value, + std::is_arithmetic::value && !std::is_same::type>::type, bool>::value, + &typeid(const std::shared_ptr &), + &typeid(typename Bare_Type::type)); + } + }; + + template + struct Get_Type_Info > + { + static constexpr Type_Info get() + { + return Type_Info(std::is_const::value, std::is_reference::value, std::is_pointer::value, + std::is_void::value, + std::is_arithmetic::value && !std::is_same::type>::type, bool>::value, + &typeid(std::reference_wrapper ), + &typeid(typename Bare_Type::type)); + } + }; + + template + struct Get_Type_Info &> + { + static constexpr Type_Info get() + { + return Type_Info(std::is_const::value, std::is_reference::value, std::is_pointer::value, + std::is_void::value, + std::is_arithmetic::value && !std::is_same::type>::type, bool>::value, + &typeid(const std::reference_wrapper &), + &typeid(typename Bare_Type::type)); + } + }; + + } + + /// \brief Creates a Type_Info object representing the type passed in + /// \tparam T Type of object to get a Type_Info for, derived from the passed in parameter + /// \return Type_Info for T + /// + /// \b Example: + /// \code + /// int i; + /// chaiscript::Type_Info ti = chaiscript::user_type(i); + /// \endcode + template + constexpr Type_Info user_type(const T &/*t*/) + { + return detail::Get_Type_Info::get(); + } + + + /// \brief Creates a Type_Info object representing the templated type + /// \tparam T Type of object to get a Type_Info for + /// \return Type_Info for T + /// + /// \b Example: + /// \code + /// chaiscript::Type_Info ti = chaiscript::user_type(); + /// \endcode + template + constexpr Type_Info user_type() + { + return detail::Get_Type_Info::get(); + } + +} + +#endif + diff --git a/chaiscript/language/chaiscript_algebraic.hpp b/chaiscript/language/chaiscript_algebraic.hpp new file mode 100644 index 0000000..8a49d2e --- /dev/null +++ b/chaiscript/language/chaiscript_algebraic.hpp @@ -0,0 +1,108 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_ALGEBRAIC_HPP_ +#define CHAISCRIPT_ALGEBRAIC_HPP_ + +#include "../utility/fnv1a.hpp" + +#include + +namespace chaiscript +{ + + struct Operators { + enum class Opers + { + boolean_flag, + equals, less_than, greater_than, less_than_equal, greater_than_equal, not_equal, + non_const_flag, + assign, pre_increment, pre_decrement, assign_product, assign_sum, + assign_quotient, assign_difference, + non_const_int_flag, + assign_bitwise_and, assign_bitwise_or, assign_shift_left, assign_shift_right, + assign_remainder, assign_bitwise_xor, + const_int_flag, + shift_left, shift_right, remainder, bitwise_and, bitwise_or, bitwise_xor, bitwise_complement, + const_flag, + sum, quotient, product, difference, unary_plus, unary_minus, + invalid + }; + + static const char *to_string(Opers t_oper) { + static const char *opers[] = { + "", + "==", "<", ">", "<=", ">=", "!=", + "", + "=", "++", "--", "*=", "+=", + "/=", "-=", + "", + "&=", "|=", "<<=", ">>=", + "%=", "^=", + "", + "<<", ">>", "%", "&", "|", "^", "~", + "", + "+", "/", "*", "-", "+", "-", + "" + }; + return opers[static_cast(t_oper)]; + } + + static Opers to_operator(const std::string &t_str, bool t_is_unary = false) + { +#ifdef CHAISCRIPT_MSVC +#pragma warning(push) +#pragma warning(disable : 4307) +#endif + + const auto op_hash = utility::fnv1a_32(t_str.c_str()); + switch (op_hash) { + case utility::fnv1a_32("=="): { return Opers::equals; } + case utility::fnv1a_32("<"): { return Opers::less_than; } + case utility::fnv1a_32(">"): { return Opers::greater_than; } + case utility::fnv1a_32("<="): { return Opers::less_than_equal; } + case utility::fnv1a_32(">="): { return Opers::greater_than_equal; } + case utility::fnv1a_32("!="): { return Opers::not_equal; } + case utility::fnv1a_32("="): { return Opers::assign; } + case utility::fnv1a_32("++"): { return Opers::pre_increment; } + case utility::fnv1a_32("--"): { return Opers::pre_decrement; } + case utility::fnv1a_32("*="): { return Opers::assign_product; } + case utility::fnv1a_32("+="): { return Opers::assign_sum; } + case utility::fnv1a_32("-="): { return Opers::assign_difference; } + case utility::fnv1a_32("&="): { return Opers::assign_bitwise_and; } + case utility::fnv1a_32("|="): { return Opers::assign_bitwise_or; } + case utility::fnv1a_32("<<="): { return Opers::assign_shift_left; } + case utility::fnv1a_32(">>="): { return Opers::assign_shift_right; } + case utility::fnv1a_32("%="): { return Opers::assign_remainder; } + case utility::fnv1a_32("^="): { return Opers::assign_bitwise_xor; } + case utility::fnv1a_32("<<"): { return Opers::shift_left; } + case utility::fnv1a_32(">>"): { return Opers::shift_right; } + case utility::fnv1a_32("%"): { return Opers::remainder; } + case utility::fnv1a_32("&"): { return Opers::bitwise_and; } + case utility::fnv1a_32("|"): { return Opers::bitwise_or; } + case utility::fnv1a_32("^"): { return Opers::bitwise_xor; } + case utility::fnv1a_32("~"): { return Opers::bitwise_complement; } + case utility::fnv1a_32("+"): { return t_is_unary ? Opers::unary_plus : Opers::sum; } + case utility::fnv1a_32("-"): { return t_is_unary ? Opers::unary_minus : Opers::difference; } + case utility::fnv1a_32("/"): { return Opers::quotient; } + case utility::fnv1a_32("*"): { return Opers::product; } + default: { return Opers::invalid; } + } +#ifdef CHAISCRIPT_MSVC +#pragma warning(pop) +#endif + + } + + }; +} + +#endif /* _CHAISCRIPT_ALGEBRAIC_HPP */ + diff --git a/chaiscript/language/chaiscript_common.hpp b/chaiscript/language/chaiscript_common.hpp new file mode 100644 index 0000000..73e5f02 --- /dev/null +++ b/chaiscript/language/chaiscript_common.hpp @@ -0,0 +1,754 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_COMMON_HPP_ +#define CHAISCRIPT_COMMON_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "../chaiscript_defines.hpp" +#include "../dispatchkit/boxed_value.hpp" +#include "../dispatchkit/dispatchkit.hpp" +#include "../dispatchkit/proxy_functions.hpp" +#include "../dispatchkit/type_info.hpp" + +namespace chaiscript { +struct AST_Node; +} // namespace chaiscript + +namespace chaiscript +{ + struct Name_Validator { + static bool is_reserved_word(const std::string &name) + { + static const std::set m_reserved_words + = {"def", "fun", "while", "for", "if", "else", "&&", "||", ",", "auto", + "return", "break", "true", "false", "class", "attr", "var", "global", "GLOBAL", "_", + "__LINE__", "__FILE__", "__FUNC__", "__CLASS__"}; + return m_reserved_words.count(name) > 0; + } + + static bool valid_object_name(const std::string &name) + { + return name.find("::") == std::string::npos && !is_reserved_word(name); + } + + static void validate_object_name(const std::string &name) + { + if (is_reserved_word(name)) { + throw exception::reserved_word_error(name); + } + + if (name.find("::") != std::string::npos) { + throw exception::illegal_name_error(name); + } + } + }; + + /// Signature of module entry point that all binary loadable modules must implement. + typedef ModulePtr (*Create_Module_Func)(); + + + /// Types of AST nodes available to the parser and eval + enum class AST_Node_Type { Id, Fun_Call, Unused_Return_Fun_Call, Arg_List, Equation, Var_Decl, Assign_Decl, + Array_Call, Dot_Access, + Lambda, Block, Scopeless_Block, Def, While, If, For, Ranged_For, Inline_Array, Inline_Map, Return, File, Prefix, Break, Continue, Map_Pair, Value_Range, + Inline_Range, Try, Catch, Finally, Method, Attr_Decl, + Logical_And, Logical_Or, Reference, Switch, Case, Default, Noop, Class, Binary, Arg, Global_Decl, Constant, Compiled + }; + + enum class Operator_Precidence { Ternary_Cond, Logical_Or, + Logical_And, Bitwise_Or, Bitwise_Xor, Bitwise_And, + Equality, Comparison, Shift, Addition, Multiplication, Prefix }; + + namespace + { + /// Helper lookup to get the name of each node type + inline const char *ast_node_type_to_string(AST_Node_Type ast_node_type) { + static const char * const ast_node_types[] = { "Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Assign_Decl", + "Array_Call", "Dot_Access", + "Lambda", "Block", "Scopeless_Block", "Def", "While", "If", "For", "Ranged_For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", + "Inline_Range", "Try", "Catch", "Finally", "Method", "Attr_Decl", + "Logical_And", "Logical_Or", "Reference", "Switch", "Case", "Default", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled"}; + + return ast_node_types[static_cast(ast_node_type)]; + } + } + + /// \brief Convenience type for file positions + struct File_Position { + int line; + int column; + + File_Position(int t_file_line, int t_file_column) + : line(t_file_line), column(t_file_column) { } + + File_Position() : line(0), column(0) { } + }; + + struct Parse_Location { + Parse_Location(std::string t_fname="", const int t_start_line=0, const int t_start_col=0, + const int t_end_line=0, const int t_end_col=0) + : start(t_start_line, t_start_col), + end(t_end_line, t_end_col), + filename(std::make_shared(std::move(t_fname))) + { + } + + Parse_Location(std::shared_ptr t_fname, const int t_start_line=0, const int t_start_col=0, + const int t_end_line=0, const int t_end_col=0) + : start(t_start_line, t_start_col), + end(t_end_line, t_end_col), + filename(std::move(t_fname)) + { + } + + + + File_Position start; + File_Position end; + std::shared_ptr filename; + }; + + + /// \brief Typedef for pointers to AST_Node objects. Used in building of the AST_Node tree + typedef std::unique_ptr AST_NodePtr; + typedef std::unique_ptr AST_NodePtr_Const; + + struct AST_Node_Trace; + + + /// \brief Classes which may be thrown during error cases when ChaiScript is executing. + namespace exception + { + /// \brief Thrown if an error occurs while attempting to load a binary module + struct load_module_error : std::runtime_error + { + explicit load_module_error(const std::string &t_reason) noexcept + : std::runtime_error(t_reason) + { + } + + load_module_error(const std::string &t_name, const std::vector &t_errors) + : std::runtime_error(format_error(t_name, t_errors)) + { + } + + load_module_error(const load_module_error &) = default; + ~load_module_error() noexcept override = default; + + static std::string format_error(const std::string &t_name, const std::vector &t_errors) + { + std::stringstream ss; + ss << "Error loading module '" << t_name << "'\n" + << " The following locations were searched:\n"; + + for (const auto &err : t_errors) { + ss << " " << err.what() << "\n"; + } + + return ss.str(); + } + }; + + + /// Errors generated during parsing or evaluation + struct eval_error : std::runtime_error { + std::string reason; + File_Position start_position; + std::string filename; + std::string detail; + std::vector call_stack; + + eval_error(const std::string &t_why, const File_Position &t_where, const std::string &t_fname, + const std::vector &t_parameters, const std::vector &t_functions, + bool t_dot_notation, + const chaiscript::detail::Dispatch_Engine &t_ss) noexcept : + std::runtime_error(format(t_why, t_where, t_fname, t_parameters, t_dot_notation, t_ss)), + reason(t_why), start_position(t_where), filename(t_fname), detail(format_detail(t_functions, t_dot_notation, t_ss)) + {} + + eval_error(const std::string &t_why, + const std::vector &t_parameters, const std::vector &t_functions, + bool t_dot_notation, + const chaiscript::detail::Dispatch_Engine &t_ss) noexcept : + std::runtime_error(format(t_why, t_parameters, t_dot_notation, t_ss)), + reason(t_why), detail(format_detail(t_functions, t_dot_notation, t_ss)) + {} + + + eval_error(const std::string &t_why, const File_Position &t_where, const std::string &t_fname) noexcept : + std::runtime_error(format(t_why, t_where, t_fname)), + reason(t_why), start_position(t_where), filename(t_fname) + {} + + explicit eval_error(const std::string &t_why) noexcept + : std::runtime_error("Error: \"" + t_why + "\" "), + reason(t_why) + {} + + eval_error(const eval_error &) = default; + + std::string pretty_print() const + { + std::ostringstream ss; + + ss << what(); + if (!call_stack.empty()) { + ss << "during evaluation at (" << fname(call_stack[0]) << " " << startpos(call_stack[0]) << ")\n"; + ss << '\n' << detail << '\n'; + ss << " " << fname(call_stack[0]) << " (" << startpos(call_stack[0]) << ") '" << pretty(call_stack[0]) << "'"; + for (size_t j = 1; j < call_stack.size(); ++j) { + if (id(call_stack[j]) != chaiscript::AST_Node_Type::Block + && id(call_stack[j]) != chaiscript::AST_Node_Type::File) + { + ss << '\n'; + ss << " from " << fname(call_stack[j]) << " (" << startpos(call_stack[j]) << ") '" << pretty(call_stack[j]) << "'"; + } + } + } + ss << '\n'; + return ss.str(); + } + + ~eval_error() noexcept override = default; + + private: + + template + static AST_Node_Type id(const T& t) + { + return t.identifier; + } + + template + static std::string pretty(const T& t) + { + return t.pretty_print(); + } + + template + static const std::string &fname(const T& t) + { + return t.filename(); + } + + template + static std::string startpos(const T& t) + { + std::ostringstream oss; + oss << t.start().line << ", " << t.start().column; + return oss.str(); + } + + static std::string format_why(const std::string &t_why) + { + return "Error: \"" + t_why + "\""; + } + + static std::string format_types(const Const_Proxy_Function &t_func, + bool t_dot_notation, + const chaiscript::detail::Dispatch_Engine &t_ss) + { + assert(t_func); + int arity = t_func->get_arity(); + std::vector types = t_func->get_param_types(); + + std::string retval; + if (arity == -1) + { + retval = "(...)"; + if (t_dot_notation) + { + retval = "(Object)." + retval; + } + } else if (types.size() <= 1) { + retval = "()"; + } else { + std::stringstream ss; + ss << "("; + + std::string paramstr; + + for (size_t index = 1; + index != types.size(); + ++index) + { + paramstr += (types[index].is_const()?"const ":""); + paramstr += t_ss.get_type_name(types[index]); + + if (index == 1 && t_dot_notation) + { + paramstr += ").("; + if (types.size() == 2) + { + paramstr += ", "; + } + } else { + paramstr += ", "; + } + } + + ss << paramstr.substr(0, paramstr.size() - 2); + + ss << ")"; + retval = ss.str(); + } + + + std::shared_ptr dynfun + = std::dynamic_pointer_cast(t_func); + + if (dynfun && dynfun->has_parse_tree()) + { + Proxy_Function f = dynfun->get_guard(); + + if (f) + { + auto dynfunguard = std::dynamic_pointer_cast(f); + if (dynfunguard && dynfunguard->has_parse_tree()) + { + retval += " : " + format_guard(dynfunguard->get_parse_tree()); + } + } + + retval += "\n Defined at " + format_location(dynfun->get_parse_tree()); + } + + return retval; + } + + template + static std::string format_guard(const T &t) + { + return t.pretty_print(); + } + + template + static std::string format_location(const T &t) + { + std::ostringstream oss; + oss << "(" << t.filename() << " " << t.start().line << ", " << t.start().column << ")"; + return oss.str(); + } + + static std::string format_detail(const std::vector &t_functions, + bool t_dot_notation, + const chaiscript::detail::Dispatch_Engine &t_ss) + { + std::stringstream ss; + if (t_functions.size() == 1) + { + assert(t_functions[0]); + ss << " Expected: " << format_types(t_functions[0], t_dot_notation, t_ss) << '\n'; + } else { + ss << " " << t_functions.size() << " overloads available:\n"; + + for (const auto & t_function : t_functions) + { + ss << " " << format_types((t_function), t_dot_notation, t_ss) << '\n'; + } + + } + + return ss.str(); + + } + + static std::string format_parameters(const std::vector &t_parameters, + bool t_dot_notation, + const chaiscript::detail::Dispatch_Engine &t_ss) + { + std::stringstream ss; + ss << "("; + + if (!t_parameters.empty()) + { + std::string paramstr; + + for (auto itr = t_parameters.begin(); + itr != t_parameters.end(); + ++itr) + { + paramstr += (itr->is_const()?"const ":""); + paramstr += t_ss.type_name(*itr); + + if (itr == t_parameters.begin() && t_dot_notation) + { + paramstr += ").("; + if (t_parameters.size() == 1) + { + paramstr += ", "; + } + } else { + paramstr += ", "; + } + } + + ss << paramstr.substr(0, paramstr.size() - 2); + } + ss << ")"; + + return ss.str(); + } + + static std::string format_filename(const std::string &t_fname) + { + std::stringstream ss; + + if (t_fname != "__EVAL__") + { + ss << "in '" << t_fname << "' "; + } else { + ss << "during evaluation "; + } + + return ss.str(); + } + + static std::string format_location(const File_Position &t_where) + { + std::stringstream ss; + ss << "at (" << t_where.line << ", " << t_where.column << ")"; + return ss.str(); + } + + static std::string format(const std::string &t_why, const File_Position &t_where, const std::string &t_fname, + const std::vector &t_parameters, bool t_dot_notation, const chaiscript::detail::Dispatch_Engine &t_ss) + { + std::stringstream ss; + + ss << format_why(t_why); + ss << " "; + + ss << "With parameters: " << format_parameters(t_parameters, t_dot_notation, t_ss); + ss << " "; + + ss << format_filename(t_fname); + ss << " "; + + ss << format_location(t_where); + + return ss.str(); + } + + static std::string format(const std::string &t_why, + const std::vector &t_parameters, + bool t_dot_notation, + const chaiscript::detail::Dispatch_Engine &t_ss) + { + std::stringstream ss; + + ss << format_why(t_why); + ss << " "; + + ss << "With parameters: " << format_parameters(t_parameters, t_dot_notation, t_ss); + ss << " "; + + return ss.str(); + } + + static std::string format(const std::string &t_why, const File_Position &t_where, const std::string &t_fname) + { + std::stringstream ss; + + ss << format_why(t_why); + ss << " "; + + ss << format_filename(t_fname); + ss << " "; + + ss << format_location(t_where); + + return ss.str(); + } + }; + + + /// Errors generated when loading a file + struct file_not_found_error : std::runtime_error { + explicit file_not_found_error(const std::string &t_filename) noexcept + : std::runtime_error("File Not Found: " + t_filename), + filename(t_filename) + { } + + file_not_found_error(const file_not_found_error &) = default; + ~file_not_found_error() noexcept override = default; + + std::string filename; + }; + + } + + + /// \brief Struct that doubles as both a parser ast_node and an AST node. + struct AST_Node { + public: + const AST_Node_Type identifier; + const std::string text; + Parse_Location location; + + const std::string &filename() const { + return *location.filename; + } + + const File_Position &start() const { + return location.start; + } + + const File_Position &end() const { + return location.end; + } + + std::string pretty_print() const + { + std::ostringstream oss; + + oss << text; + + for (auto & elem : get_children()) { + oss << elem.get().pretty_print() << ' '; + } + + return oss.str(); + } + + virtual std::vector> get_children() const = 0; + virtual Boxed_Value eval(const chaiscript::detail::Dispatch_State &t_e) const = 0; + + + /// Prints the contents of an AST node, including its children, recursively + std::string to_string(const std::string &t_prepend = "") const { + std::ostringstream oss; + + oss << t_prepend << "(" << ast_node_type_to_string(this->identifier) << ") " + << this->text << " : " << this->location.start.line << ", " << this->location.start.column << '\n'; + + for (auto & elem : get_children()) { + oss << elem.get().to_string(t_prepend + " "); + } + return oss.str(); + } + + + static bool get_bool_condition(const Boxed_Value &t_bv, const chaiscript::detail::Dispatch_State &t_ss) { + try { + return t_ss->boxed_cast(t_bv); + } + catch (const exception::bad_boxed_cast &) { + throw exception::eval_error("Condition not boolean"); + } + } + + + virtual ~AST_Node() = default; + AST_Node(AST_Node &&) = default; + AST_Node &operator=(AST_Node &&) = default; + AST_Node(const AST_Node &) = delete; + AST_Node& operator=(const AST_Node &) = delete; + + + protected: + AST_Node(std::string t_ast_node_text, AST_Node_Type t_id, Parse_Location t_loc) + : identifier(t_id), text(std::move(t_ast_node_text)), + location(std::move(t_loc)) + { + } + + + }; + + struct AST_Node_Trace + { + const AST_Node_Type identifier; + const std::string text; + Parse_Location location; + + const std::string &filename() const { + return *location.filename; + } + + const File_Position &start() const { + return location.start; + } + + const File_Position &end() const { + return location.end; + } + + std::string pretty_print() const + { + std::ostringstream oss; + + oss << text; + + for (const auto & elem : children) { + oss << elem.pretty_print() << ' '; + } + + return oss.str(); + } + + std::vector get_children(const AST_Node &node) + { + const auto node_children = node.get_children(); + return std::vector(node_children.begin(), node_children.end()); + } + + AST_Node_Trace(const AST_Node &node) + : identifier(node.identifier), text(node.text), + location(node.location), children(get_children(node)) + { + } + + + std::vector children; + + }; + + namespace parser { + class ChaiScript_Parser_Base + { + public: + virtual AST_NodePtr parse(const std::string &t_input, const std::string &t_fname) = 0; + virtual void debug_print(const AST_Node &t, std::string prepend = "") const = 0; + virtual void *get_tracer_ptr() = 0; + virtual ~ChaiScript_Parser_Base() = default; + ChaiScript_Parser_Base() = default; + ChaiScript_Parser_Base(ChaiScript_Parser_Base &&) = default; + ChaiScript_Parser_Base &operator=(ChaiScript_Parser_Base &&) = delete; + ChaiScript_Parser_Base &operator=(const ChaiScript_Parser_Base &&) = delete; + + template + T &get_tracer() + { + // to do type check this somehow? + return *static_cast(get_tracer_ptr()); + } + + protected: + ChaiScript_Parser_Base(const ChaiScript_Parser_Base &) = default; + }; + } + + namespace eval + { + namespace detail + { + /// Special type for returned values + struct Return_Value { + Boxed_Value retval; + }; + + + /// Special type indicating a call to 'break' + struct Break_Loop { + Break_Loop() = default; + }; + + + /// Special type indicating a call to 'continue' + struct Continue_Loop { + Continue_Loop() = default; + }; + + + /// Creates a new scope then pops it on destruction + struct Scope_Push_Pop + { + Scope_Push_Pop(Scope_Push_Pop &&) = default; + Scope_Push_Pop& operator=(Scope_Push_Pop &&) = default; + Scope_Push_Pop(const Scope_Push_Pop &) = delete; + Scope_Push_Pop& operator=(const Scope_Push_Pop &) = delete; + + explicit Scope_Push_Pop(const chaiscript::detail::Dispatch_State &t_ds) + : m_ds(t_ds) + { + m_ds->new_scope(m_ds.stack_holder()); + } + + ~Scope_Push_Pop() + { + m_ds->pop_scope(m_ds.stack_holder()); + } + + + private: + const chaiscript::detail::Dispatch_State &m_ds; + }; + + /// Creates a new function call and pops it on destruction + struct Function_Push_Pop + { + Function_Push_Pop(Function_Push_Pop &&) = default; + Function_Push_Pop& operator=(Function_Push_Pop &&) = default; + Function_Push_Pop(const Function_Push_Pop &) = delete; + Function_Push_Pop& operator=(const Function_Push_Pop &) = delete; + + explicit Function_Push_Pop(const chaiscript::detail::Dispatch_State &t_ds) + : m_ds(t_ds) + { + m_ds->new_function_call(m_ds.stack_holder(), m_ds.conversion_saves()); + } + + ~Function_Push_Pop() + { + m_ds->pop_function_call(m_ds.stack_holder(), m_ds.conversion_saves()); + } + + void save_params(const std::vector &t_params) + { + m_ds->save_function_params(t_params); + } + + void save_params(std::initializer_list t_params) + { + m_ds->save_function_params(t_params); + } + + + private: + const chaiscript::detail::Dispatch_State &m_ds; + }; + + /// Creates a new scope then pops it on destruction + struct Stack_Push_Pop + { + Stack_Push_Pop(Stack_Push_Pop &&) = default; + Stack_Push_Pop& operator=(Stack_Push_Pop &&) = default; + Stack_Push_Pop(const Stack_Push_Pop &) = delete; + Stack_Push_Pop& operator=(const Stack_Push_Pop &) = delete; + + explicit Stack_Push_Pop(const chaiscript::detail::Dispatch_State &t_ds) + : m_ds(t_ds) + { + m_ds->new_stack(m_ds.stack_holder()); + } + + ~Stack_Push_Pop() + { + m_ds->pop_stack(m_ds.stack_holder()); + } + + + private: + const chaiscript::detail::Dispatch_State &m_ds; + }; + } + } +} + +#endif /* _CHAISCRIPT_COMMON_HPP */ + diff --git a/chaiscript/language/chaiscript_engine.hpp b/chaiscript/language/chaiscript_engine.hpp new file mode 100644 index 0000000..3e6d0d8 --- /dev/null +++ b/chaiscript/language/chaiscript_engine.hpp @@ -0,0 +1,782 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_ENGINE_HPP_ +#define CHAISCRIPT_ENGINE_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../chaiscript_defines.hpp" +#include "../chaiscript_threading.hpp" +#include "../dispatchkit/boxed_cast_helper.hpp" +#include "../dispatchkit/boxed_value.hpp" +#include "../dispatchkit/dispatchkit.hpp" +#include "../dispatchkit/type_conversions.hpp" +#include "../dispatchkit/proxy_functions.hpp" +#include "chaiscript_common.hpp" + +#if defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(__HAIKU__) +#include +#endif + +#if !defined(CHAISCRIPT_NO_DYNLOAD) && defined(_POSIX_VERSION) && !defined(__CYGWIN__) +#include +#endif + +#if defined(CHAISCRIPT_NO_DYNLOAD) +#include "chaiscript_unknown.hpp" +#elif defined(CHAISCRIPT_WINDOWS) +#include "chaiscript_windows.hpp" +#elif _POSIX_VERSION +#include "chaiscript_posix.hpp" +#else +#include "chaiscript_unknown.hpp" +#endif + + +#include "../dispatchkit/exception_specification.hpp" + +namespace chaiscript +{ + /// Namespace alias to provide cleaner and more explicit syntax to users. + using Namespace = dispatch::Dynamic_Object; + + namespace detail + { + typedef std::shared_ptr Loadable_Module_Ptr; + } + + + /// \brief The main object that the ChaiScript user will use. + class ChaiScript_Basic { + + mutable chaiscript::detail::threading::shared_mutex m_mutex; + mutable chaiscript::detail::threading::recursive_mutex m_use_mutex; + + std::set m_used_files; + std::map m_loaded_modules; + std::set m_active_loaded_modules; + + std::vector m_module_paths; + std::vector m_use_paths; + + std::unique_ptr m_parser; + + chaiscript::detail::Dispatch_Engine m_engine; + + std::map> m_namespace_generators; + + /// Evaluates the given string in by parsing it and running the results through the evaluator + Boxed_Value do_eval(const std::string &t_input, const std::string &t_filename = "__EVAL__", bool /* t_internal*/ = false) + { + try { + const auto p = m_parser->parse(t_input, t_filename); + return p->eval(chaiscript::detail::Dispatch_State(m_engine)); + } + catch (chaiscript::eval::detail::Return_Value &rv) { + return rv.retval; + } + } + + + + /// Evaluates the given file and looks in the 'use' paths + const Boxed_Value internal_eval_file(const std::string &t_filename) { + for (const auto &path : m_use_paths) + { + try { + const auto appendedpath = path + t_filename; + return do_eval(load_file(appendedpath), appendedpath, true); + } catch (const exception::file_not_found_error &) { + // failed to load, try the next path + } catch (const exception::eval_error &t_ee) { + throw Boxed_Value(t_ee); + } + } + + // failed to load by any name + throw exception::file_not_found_error(t_filename); + + } + + + + /// Evaluates the given string, used during eval() inside of a script + const Boxed_Value internal_eval(const std::string &t_e) { + try { + return do_eval(t_e, "__EVAL__", true); + } catch (const exception::eval_error &t_ee) { + throw Boxed_Value(t_ee); + } + } + + /// Returns the current evaluation m_engine + chaiscript::detail::Dispatch_Engine &get_eval_engine() { + return m_engine; + } + + /// Builds all the requirements for ChaiScript, including its evaluator and a run of its prelude. + void build_eval_system(const ModulePtr &t_lib, const std::vector &t_opts) { + if (t_lib) + { + add(t_lib); + } + + m_engine.add(fun([this](){ m_engine.dump_system(); }), "dump_system"); + m_engine.add(fun([this](const Boxed_Value &t_bv){ m_engine.dump_object(t_bv); }), "dump_object"); + m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_type){ return m_engine.is_type(t_bv, t_type); }), "is_type"); + m_engine.add(fun([this](const Boxed_Value &t_bv){ return m_engine.type_name(t_bv); }), "type_name"); + m_engine.add(fun([this](const std::string &t_f){ return m_engine.function_exists(t_f); }), "function_exists"); + m_engine.add(fun([this](){ return m_engine.get_function_objects(); }), "get_functions"); + m_engine.add(fun([this](){ return m_engine.get_scripting_objects(); }), "get_objects"); + + m_engine.add( + dispatch::make_dynamic_proxy_function( + [this](const std::vector &t_params) { + return m_engine.call_exists(t_params); + }) + , "call_exists"); + + + m_engine.add(fun( + [=](const dispatch::Proxy_Function_Base &t_fun, const std::vector &t_params) -> Boxed_Value { + Type_Conversions_State s(this->m_engine.conversions(), this->m_engine.conversions().conversion_saves()); + return t_fun(t_params, s); + }), "call"); + + + m_engine.add(fun([this](const Type_Info &t_ti){ return m_engine.get_type_name(t_ti); }), "name"); + + m_engine.add(fun([this](const std::string &t_type_name, bool t_throw){ return m_engine.get_type(t_type_name, t_throw); }), "type"); + m_engine.add(fun([this](const std::string &t_type_name){ return m_engine.get_type(t_type_name, true); }), "type"); + + m_engine.add(fun( + [=](const Type_Info &t_from, const Type_Info &t_to, const std::function &t_func) { + m_engine.add(chaiscript::type_conversion(t_from, t_to, t_func)); + } + ), "add_type_conversion"); + + + + if (std::find(t_opts.begin(), t_opts.end(), Options::No_Load_Modules) == t_opts.end() + && std::find(t_opts.begin(), t_opts.end(), Options::Load_Modules) != t_opts.end()) + { + m_engine.add(fun([this](const std::string &t_module, const std::string &t_file){ return load_module(t_module, t_file); }), "load_module"); + m_engine.add(fun([this](const std::string &t_module){ return load_module(t_module); }), "load_module"); + } + + if (std::find(t_opts.begin(), t_opts.end(), Options::No_External_Scripts) == t_opts.end() + && std::find(t_opts.begin(), t_opts.end(), Options::External_Scripts) != t_opts.end()) + { + m_engine.add(fun([this](const std::string &t_file){ return use(t_file); }), "use"); + m_engine.add(fun([this](const std::string &t_file){ return internal_eval_file(t_file); }), "eval_file"); + } + + m_engine.add(fun([this](const std::string &t_str){ return internal_eval(t_str); }), "eval"); + m_engine.add(fun([this](const AST_Node &t_ast){ return eval(t_ast); }), "eval"); + + m_engine.add(fun([this](const std::string &t_str, const bool t_dump){ return parse(t_str, t_dump); }), "parse"); + m_engine.add(fun([this](const std::string &t_str){ return parse(t_str); }), "parse"); + + + m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name){ add_global_const(t_bv, t_name); }), "add_global_const"); + m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name){ add_global(t_bv, t_name); }), "add_global"); + m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name){ set_global(t_bv, t_name); }), "set_global"); + + m_engine.add(fun([this](const std::string& t_namespace_name) { register_namespace([](Namespace& /*space*/) {}, t_namespace_name); import(t_namespace_name); }), "namespace"); + m_engine.add(fun([this](const std::string& t_namespace_name) { import(t_namespace_name); }), "import"); + } + + /// Skip BOM at the beginning of file + static bool skip_bom(std::ifstream &infile) { + size_t bytes_needed = 3; + char buffer[3]; + + memset(buffer, '\0', bytes_needed); + + infile.read(buffer, static_cast(bytes_needed)); + + if ((buffer[0] == '\xef') + && (buffer[1] == '\xbb') + && (buffer[2] == '\xbf')) { + + infile.seekg(3); + return true; + } + + infile.seekg(0); + + return false; + } + + /// Helper function for loading a file + static std::string load_file(const std::string &t_filename) { + std::ifstream infile(t_filename.c_str(), std::ios::in | std::ios::ate | std::ios::binary ); + + if (!infile.is_open()) { + throw chaiscript::exception::file_not_found_error(t_filename); + } + + auto size = infile.tellg(); + infile.seekg(0, std::ios::beg); + + assert(size >= 0); + + if (skip_bom(infile)) { + size-=3; // decrement the BOM size from file size, otherwise we'll get parsing errors + assert(size >= 0); //and check if there's more text + } + + if (size == std::streampos(0)) + { + return std::string(); + } else { + std::vector v(static_cast(size)); + infile.read(&v[0], static_cast(size)); + return std::string(v.begin(), v.end()); + } + } + + std::vector ensure_minimum_path_vec(std::vector paths) + { + if (paths.empty()) { return {""}; } + else { return paths; } + } + + public: + + /// \brief Constructor for ChaiScript + /// \param[in] t_lib Standard library to apply to this ChaiScript instance + /// \param[in] t_modulepaths Vector of paths to search when attempting to load a binary module + /// \param[in] t_usepaths Vector of paths to search when attempting to "use" an included ChaiScript file + ChaiScript_Basic(const ModulePtr &t_lib, + std::unique_ptr &&parser, + std::vector t_module_paths = {}, + std::vector t_use_paths = {}, + const std::vector &t_opts = chaiscript::default_options()) + : m_module_paths(ensure_minimum_path_vec(std::move(t_module_paths))), + m_use_paths(ensure_minimum_path_vec(std::move(t_use_paths))), + m_parser(std::move(parser)), + m_engine(*m_parser) + { +#if !defined(CHAISCRIPT_NO_DYNLOAD) && defined(_POSIX_VERSION) && !defined(__CYGWIN__) + // If on Unix, add the path of the current executable to the module search path + // as windows would do + + union cast_union + { + Boxed_Value (ChaiScript_Basic::*in_ptr)(const std::string&); + void *out_ptr; + }; + + Dl_info rInfo; + memset( &rInfo, 0, sizeof(rInfo) ); + cast_union u; + u.in_ptr = &ChaiScript_Basic::use; + if ( (dladdr(static_cast(u.out_ptr), &rInfo) != 0) && (rInfo.dli_fname != nullptr) ) { + std::string dllpath(rInfo.dli_fname); + const size_t lastslash = dllpath.rfind('/'); + if (lastslash != std::string::npos) + { + dllpath.erase(lastslash); + } + + // Let's see if this is a link that we should expand + std::vector buf(2048); + const auto pathlen = readlink(dllpath.c_str(), &buf.front(), buf.size()); + if (pathlen > 0 && static_cast(pathlen) < buf.size()) + { + dllpath = std::string(&buf.front(), static_cast(pathlen)); + } + + m_module_paths.insert(m_module_paths.begin(), dllpath+"/"); + } +#endif + build_eval_system(t_lib, t_opts); + } + +#ifndef CHAISCRIPT_NO_DYNLOAD + /// \brief Constructor for ChaiScript. + /// + /// This version of the ChaiScript constructor attempts to find the stdlib module to load + /// at runtime generates an error if it cannot be found. + /// + /// \param[in] t_modulepaths Vector of paths to search when attempting to load a binary module + /// \param[in] t_usepaths Vector of paths to search when attempting to "use" an included ChaiScript file + explicit ChaiScript_Basic(std::unique_ptr &&parser, + std::vector t_module_paths = {}, + std::vector t_use_paths = {}, + const std::vector &t_opts = chaiscript::default_options()) + : ChaiScript_Basic({}, std::move(parser), t_module_paths, t_use_paths, t_opts) + { + try { + // attempt to load the stdlib + load_module("chaiscript_stdlib-" + Build_Info::version()); + } catch (const exception::load_module_error &t_err) { + std::cout << "An error occured while trying to load the chaiscript standard library.\n" + << "\n" + << "You must either provide a standard library, or compile it in.\n" + << "For an example of compiling the standard library in,\n" + << "see: https://gist.github.com/lefticus/9456197\n" + << "Compiling the stdlib in is the recommended and MOST SUPPORTED method.\n" + << "\n" + << "\n" + << t_err.what(); + throw; + } + } +#else // CHAISCRIPT_NO_DYNLOAD +explicit ChaiScript_Basic(std::unique_ptr &&parser, + std::vector t_module_paths = {}, + std::vector t_use_paths = {}, + const std::vector &t_opts = chaiscript::default_options()) = delete; +#endif + + parser::ChaiScript_Parser_Base &get_parser() + { + return *m_parser; + } + + const Boxed_Value eval(const AST_Node &t_ast) + { + try { + return t_ast.eval(chaiscript::detail::Dispatch_State(m_engine)); + } catch (const exception::eval_error &t_ee) { + throw Boxed_Value(t_ee); + } + } + + AST_NodePtr parse(const std::string &t_input, const bool t_debug_print = false) + { + auto ast = m_parser->parse(t_input, "PARSE"); + if (t_debug_print) { + m_parser->debug_print(*ast); + } + return ast; + } + + + std::string get_type_name(const Type_Info &ti) const + { + return m_engine.get_type_name(ti); + } + + template + std::string get_type_name() const + { + return get_type_name(user_type()); + } + + + /// \brief Loads and parses a file. If the file is already, it is not reloaded + /// The use paths specified at ChaiScript construction time are searched for the + /// requested file. + /// + /// \param[in] t_filename Filename to load and evaluate + Boxed_Value use(const std::string &t_filename) + { + for (const auto &path : m_use_paths) + { + const auto appendedpath = path + t_filename; + try { + + chaiscript::detail::threading::unique_lock l(m_use_mutex); + chaiscript::detail::threading::unique_lock l2(m_mutex); + + Boxed_Value retval; + + if (m_used_files.count(appendedpath) == 0) + { + l2.unlock(); + retval = eval_file(appendedpath); + l2.lock(); + m_used_files.insert(appendedpath); + } + + return retval; // return, we loaded it, or it was already loaded + } catch (const exception::file_not_found_error &e) { + if (e.filename != appendedpath) { + // a nested file include failed + throw; + } + // failed to load, try the next path + } + } + + // failed to load by any name + throw exception::file_not_found_error(t_filename); + } + + /// \brief Adds a constant object that is available in all contexts and to all threads + /// \param[in] t_bv Boxed_Value to add as a global + /// \param[in] t_name Name of the value to add + /// \throw chaiscript::exception::global_non_const If t_bv is not a constant object + /// \sa Boxed_Value::is_const + ChaiScript_Basic &add_global_const(const Boxed_Value &t_bv, const std::string &t_name) + { + Name_Validator::validate_object_name(t_name); + m_engine.add_global_const(t_bv, t_name); + return *this; + } + + /// \brief Adds a mutable object that is available in all contexts and to all threads + /// \param[in] t_bv Boxed_Value to add as a global + /// \param[in] t_name Name of the value to add + /// \warning The user is responsible for making sure the object is thread-safe if necessary + /// ChaiScript is thread-safe but provides no threading locking mechanism to the script + ChaiScript_Basic &add_global(const Boxed_Value &t_bv, const std::string &t_name) + { + Name_Validator::validate_object_name(t_name); + m_engine.add_global(t_bv, t_name); + return *this; + } + + ChaiScript_Basic &set_global(const Boxed_Value &t_bv, const std::string &t_name) + { + Name_Validator::validate_object_name(t_name); + m_engine.set_global(t_bv, t_name); + return *this; + } + + /// \brief Represents the current state of the ChaiScript system. State and be saved and restored + /// \warning State object does not contain the user defined type conversions of the engine. They + /// are left out due to performance considerations involved in tracking the state + /// \sa ChaiScript::get_state + /// \sa ChaiScript::set_state + struct State + { + std::set used_files; + chaiscript::detail::Dispatch_Engine::State engine_state; + std::set active_loaded_modules; + }; + + /// \brief Returns a state object that represents the current state of the global system + /// + /// The global system includes the reserved words, global const objects, functions and types. + /// local variables are thread specific and not included. + /// + /// \return Current state of the global system + /// + /// \b Example: + /// + /// \code + /// chaiscript::ChaiScript chai; + /// chaiscript::ChaiScript::State s = chai.get_state(); // represents bootstrapped initial state + /// \endcode + State get_state() const + { + chaiscript::detail::threading::lock_guard l(m_use_mutex); + chaiscript::detail::threading::shared_lock l2(m_mutex); + + State s; + s.used_files = m_used_files; + s.engine_state = m_engine.get_state(); + s.active_loaded_modules = m_active_loaded_modules; + return s; + } + + /// \brief Sets the state of the system + /// + /// The global system includes the reserved words, global objects, functions and types. + /// local variables are thread specific and not included. + /// + /// \param[in] t_state New state to set + /// + /// \b Example: + /// \code + /// chaiscript::ChaiScript chai; + /// chaiscript::ChaiScript::State s = chai.get_state(); // get initial state + /// chai.add(chaiscript::fun(&somefunction), "somefunction"); + /// chai.set_state(s); // restore initial state, which does not have the recently added "somefunction" + /// \endcode + void set_state(const State &t_state) + { + chaiscript::detail::threading::lock_guard l(m_use_mutex); + chaiscript::detail::threading::shared_lock l2(m_mutex); + + m_used_files = t_state.used_files; + m_active_loaded_modules = t_state.active_loaded_modules; + m_engine.set_state(t_state.engine_state); + } + + /// \returns All values in the local thread state, added through the add() function + std::map get_locals() const + { + return m_engine.get_locals(); + } + + /// \brief Sets all of the locals for the current thread state. + /// + /// \param[in] t_locals The map set of variables to replace the current state with + /// + /// Any existing locals are removed and the given set of variables is added + void set_locals(const std::map &t_locals) + { + m_engine.set_locals(t_locals); + } + + /// \brief Adds a type, function or object to ChaiScript. Objects are added to the local thread state. + /// \param[in] t_t Item to add + /// \param[in] t_name Name of item to add + /// \returns Reference to current ChaiScript object + /// + /// \b Examples: + /// \code + /// chaiscript::ChaiScript chai; + /// chai.add(chaiscript::user_type(), "MyClass"); // Add explicit type info (not strictly necessary) + /// chai.add(chaiscript::fun(&MyClass::function), "function"); // Add a class method + /// MyClass obj; + /// chai.add(chaiscript::var(&obj), "obj"); // Add a pointer to a locally defined object + /// \endcode + /// + /// \sa \ref adding_items + template + ChaiScript_Basic &add(const T &t_t, const std::string &t_name) + { + Name_Validator::validate_object_name(t_name); + m_engine.add(t_t, t_name); + return *this; + } + + /// \brief Add a new conversion for upcasting to a base class + /// \sa chaiscript::base_class + /// \param[in] d Base class / parent class + /// + /// \b Example: + /// \code + /// chaiscript::ChaiScript chai; + /// chai.add(chaiscript::base_class()); + /// \endcode + ChaiScript_Basic &add(const Type_Conversion &d) + { + m_engine.add(d); + return *this; + } + + /// \brief Adds all elements of a module to ChaiScript runtime + /// \param[in] t_p The module to add. + /// \sa chaiscript::Module + ChaiScript_Basic &add(const ModulePtr &t_p) + { + t_p->apply(*this, this->get_eval_engine()); + return *this; + } + + /// \brief Load a binary module from a dynamic library. Works on platforms that support + /// dynamic libraries. + /// \param[in] t_module_name Name of the module to load + /// + /// The module is searched for in the registered module path folders (chaiscript::ChaiScript::ChaiScript) + /// and with standard prefixes and postfixes: ("lib"|"")\(".dll"|".so"|".bundle"|""). + /// + /// Once the file is located, the system looks for the symbol "create_chaiscript_module_\". + /// If no file can be found matching the search criteria and containing the appropriate entry point + /// (the symbol mentioned above), an exception is thrown. + /// + /// \throw chaiscript::exception::load_module_error In the event that no matching module can be found. + std::string load_module(const std::string &t_module_name) + { +#ifdef CHAISCRIPT_NO_DYNLOAD + (void)t_module_name; // -Wunused-parameter + throw chaiscript::exception::load_module_error("Loadable module support was disabled (CHAISCRIPT_NO_DYNLOAD)"); +#else + std::vector errors; + std::string version_stripped_name = t_module_name; + size_t version_pos = version_stripped_name.find("-" + Build_Info::version()); + if (version_pos != std::string::npos) + { + version_stripped_name.erase(version_pos); + } + + std::vector prefixes{"lib", "cyg", ""}; + + std::vector postfixes{".dll", ".so", ".bundle", ""}; + + for (auto & elem : m_module_paths) + { + for (auto & prefix : prefixes) + { + for (auto & postfix : postfixes) + { + try { + const auto name = elem + prefix + t_module_name + postfix; + // std::cerr << "trying location: " << name << '\n'; + load_module(version_stripped_name, name); + return name; + } catch (const chaiscript::exception::load_module_error &e) { + // std::cerr << "error: " << e.what() << '\n'; + errors.push_back(e); + // Try next set + } + } + } + } + + throw chaiscript::exception::load_module_error(t_module_name, errors); +#endif + } + + /// \brief Load a binary module from a dynamic library. Works on platforms that support + /// dynamic libraries. + /// + /// \param[in] t_module_name Module name to load + /// \param[in] t_filename Ignore normal filename search process and use specific filename + /// + /// \sa ChaiScript::load_module(const std::string &t_module_name) + void load_module(const std::string &t_module_name, const std::string &t_filename) + { + chaiscript::detail::threading::lock_guard l(m_use_mutex); + + if (m_loaded_modules.count(t_module_name) == 0) + { + detail::Loadable_Module_Ptr lm(new detail::Loadable_Module(t_module_name, t_filename)); + m_loaded_modules[t_module_name] = lm; + m_active_loaded_modules.insert(t_module_name); + add(lm->m_moduleptr); + } else if (m_active_loaded_modules.count(t_module_name) == 0) { + m_active_loaded_modules.insert(t_module_name); + add(m_loaded_modules[t_module_name]->m_moduleptr); + } + } + + + /// \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 chaiscript::exception::eval_error In the case that evaluation fails. + Boxed_Value operator()(const std::string &t_script, const Exception_Handler &t_handler = Exception_Handler()) + { + return eval(t_script, t_handler); + } + + /// \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 + /// \param[in] t_filename Optional filename to report to the user for where the error occured. Useful + /// in special cases where you are loading a file internally instead of using eval_file + /// + /// \return result of the script execution + /// + /// \throw chaiscript::exception::eval_error In the case that evaluation fails. + /// \throw chaiscript::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, const Exception_Handler &t_handler = Exception_Handler(), const std::string &t_filename="__EVAL__") + { + return m_engine.boxed_cast(eval(t_input, t_handler, t_filename)); + } + + /// \brief casts an object while applying any Dynamic_Conversion available + template + decltype(auto) boxed_cast(const Boxed_Value &bv) const + { + return(m_engine.boxed_cast(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 + /// \param[in] t_filename Optional filename to report to the user for where the error occurred. Useful + /// in special cases where you are loading a file internally instead of using eval_file + /// + /// \return result of the script execution + /// + /// \throw exception::eval_error In the case that evaluation fails. + Boxed_Value eval(const std::string &t_input, const Exception_Handler &t_handler = Exception_Handler(), const std::string &t_filename="__EVAL__") + { + try { + return do_eval(t_input, t_filename); + } catch (Boxed_Value &bv) { + if (t_handler) { + t_handler->handle(bv, m_engine); + } + throw; + } + } + + /// \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 chaiscript::exception::eval_error In the case that evaluation fails. + Boxed_Value eval_file(const std::string &t_filename, const Exception_Handler &t_handler = Exception_Handler()) { + return eval(load_file(t_filename), t_handler, t_filename); + } + + /// \brief Loads the file specified by filename, evaluates it, and returns the type safe 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 chaiscript::exception::eval_error In the case that evaluation fails. + /// \throw chaiscript::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, const Exception_Handler &t_handler = Exception_Handler()) { + return m_engine.boxed_cast(eval_file(t_filename, t_handler)); + } + + /// \brief Imports a namespace object into the global scope of this ChaiScript instance. + /// \param[in] t_namespace_name Name of the namespace to import. + /// \throw std::runtime_error In the case that the namespace name was never registered. + void import(const std::string& t_namespace_name) + { + chaiscript::detail::threading::unique_lock l(m_use_mutex); + + if (m_engine.get_scripting_objects().count(t_namespace_name)) { + throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined"); + } + else if (m_namespace_generators.count(t_namespace_name)) { + m_engine.add_global(var(std::ref(m_namespace_generators[t_namespace_name]())), t_namespace_name); + } + else { + throw std::runtime_error("No registered namespace: " + t_namespace_name); + } + } + + /// \brief Registers a namespace generator, which delays generation of the namespace until it is imported, saving memory if it is never used. + /// \param[in] t_namespace_generator Namespace generator function. + /// \param[in] t_namespace_name Name of the Namespace function being registered. + /// \throw std::runtime_error In the case that the namespace name was already registered. + void register_namespace(const std::function& t_namespace_generator, const std::string& t_namespace_name) + { + chaiscript::detail::threading::unique_lock l(m_use_mutex); + + if (!m_namespace_generators.count(t_namespace_name)) { + // contain the namespace object memory within the m_namespace_generators map + m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable -> Namespace& { t_namespace_generator(space); return space; })); + } + else { + throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered."); + } + } + }; + +} +#endif /* CHAISCRIPT_ENGINE_HPP_ */ + diff --git a/chaiscript/language/chaiscript_eval.hpp b/chaiscript/language/chaiscript_eval.hpp new file mode 100644 index 0000000..b6cdfe6 --- /dev/null +++ b/chaiscript/language/chaiscript_eval.hpp @@ -0,0 +1,1562 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_EVAL_HPP_ +#define CHAISCRIPT_EVAL_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../chaiscript_defines.hpp" +#include "../dispatchkit/boxed_cast.hpp" +#include "../dispatchkit/boxed_number.hpp" +#include "../dispatchkit/boxed_value.hpp" +#include "../dispatchkit/dispatchkit.hpp" +#include "../dispatchkit/dynamic_object_detail.hpp" +#include "../dispatchkit/proxy_functions.hpp" +#include "../dispatchkit/proxy_functions_detail.hpp" +#include "../dispatchkit/register_function.hpp" +#include "../dispatchkit/type_info.hpp" +#include "chaiscript_algebraic.hpp" +#include "chaiscript_common.hpp" + +namespace chaiscript { +namespace exception { +class bad_boxed_cast; +} // namespace exception +} // namespace chaiscript + +namespace chaiscript +{ + /// \brief Classes and functions that are part of the runtime eval system + namespace eval + { + template struct AST_Node_Impl; + + template using AST_Node_Impl_Ptr = typename std::unique_ptr>; + + namespace detail + { + /// Helper function that will set up the scope around a function call, including handling the named function parameters + template + static Boxed_Value eval_function(chaiscript::detail::Dispatch_Engine &t_ss, const AST_Node_Impl &t_node, const std::vector &t_param_names, const std::vector &t_vals, const std::map *t_locals=nullptr, bool has_this_capture = false) { + chaiscript::detail::Dispatch_State state(t_ss); + + const Boxed_Value *thisobj = [&]() -> const Boxed_Value *{ + auto &stack = t_ss.get_stack_data(state.stack_holder()).back(); + if (!stack.empty() && stack.back().first == "__this") { + return &stack.back().second; + } else if (!t_vals.empty()) { + return &t_vals[0]; + } else { + return nullptr; + } + }(); + + chaiscript::eval::detail::Stack_Push_Pop tpp(state); + if (thisobj && !has_this_capture) { state.add_object("this", *thisobj); } + + if (t_locals) { + for (const auto &local : *t_locals) { + state.add_object(local.first, local.second); + } + } + + for (size_t i = 0; i < t_param_names.size(); ++i) { + if (t_param_names[i] != "this") { + state.add_object(t_param_names[i], t_vals[i]); + } + } + + try { + return t_node.eval(state); + } catch (detail::Return_Value &rv) { + return std::move(rv.retval); + } + } + + inline Boxed_Value clone_if_necessary(Boxed_Value incoming, std::atomic_uint_fast32_t &t_loc, const chaiscript::detail::Dispatch_State &t_ss) + { + if (!incoming.is_return_value()) + { + if (incoming.get_type_info().is_arithmetic()) { + return Boxed_Number::clone(incoming); + } else if (incoming.get_type_info().bare_equal_type_info(typeid(bool))) { + return Boxed_Value(*static_cast(incoming.get_const_ptr())); + } else { + return t_ss->call_function("clone", t_loc, {incoming}, t_ss.conversions()); + } + } else { + incoming.reset_return_value(); + return incoming; + } + } + } + + template + struct AST_Node_Impl : AST_Node + { + AST_Node_Impl(std::string t_ast_node_text, AST_Node_Type t_id, Parse_Location t_loc, + std::vector> t_children = std::vector>()) + : AST_Node(std::move(t_ast_node_text), t_id, std::move(t_loc)), + children(std::move(t_children)) + { + } + + static bool get_scoped_bool_condition(const AST_Node_Impl &node, const chaiscript::detail::Dispatch_State &t_ss) { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + return get_bool_condition(node.eval(t_ss), t_ss); + } + + + std::vector> get_children() const final { + std::vector> retval; + retval.reserve(children.size()); + for (auto &&child : children) { + retval.emplace_back(*child); + } + + return retval; + } + + Boxed_Value eval(const chaiscript::detail::Dispatch_State &t_e) const final + { + try { + T::trace(t_e, this); + return eval_internal(t_e); + } catch (exception::eval_error &ee) { + ee.call_stack.push_back(*this); + throw; + } + } + + std::vector> children; + + protected: + virtual Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const + { + throw std::runtime_error("Undispatched ast_node (internal error)"); + } + }; + + + template + struct Compiled_AST_Node : AST_Node_Impl { + Compiled_AST_Node(AST_Node_Impl_Ptr t_original_node, std::vector> t_children, + std::function> &, const chaiscript::detail::Dispatch_State &t_ss)> t_func) : + AST_Node_Impl(t_original_node->text, AST_Node_Type::Compiled, t_original_node->location, std::move(t_children)), + m_func(std::move(t_func)), + m_original_node(std::move(t_original_node)) + { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + return m_func(this->children, t_ss); + } + + std::function> &, const chaiscript::detail::Dispatch_State &t_ss)> m_func; + AST_Node_Impl_Ptr m_original_node; + }; + + + template + struct Fold_Right_Binary_Operator_AST_Node : AST_Node_Impl { + Fold_Right_Binary_Operator_AST_Node(const std::string &t_oper, Parse_Location t_loc, std::vector> t_children, Boxed_Value t_rhs) : + AST_Node_Impl(t_oper, AST_Node_Type::Binary, std::move(t_loc), std::move(t_children)), + m_oper(Operators::to_operator(t_oper)), + m_rhs(std::move(t_rhs)) + { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + return do_oper(t_ss, this->text, this->children[0]->eval(t_ss)); + } + + protected: + Boxed_Value do_oper(const chaiscript::detail::Dispatch_State &t_ss, + const std::string &t_oper_string, const Boxed_Value &t_lhs) const + { + try { + if (t_lhs.get_type_info().is_arithmetic()) + { + // If it's an arithmetic operation we want to short circuit dispatch + try{ + return Boxed_Number::do_oper(m_oper, t_lhs, m_rhs); + } catch (const chaiscript::exception::arithmetic_error &) { + throw; + } catch (...) { + throw exception::eval_error("Error with numeric operator calling: " + t_oper_string); + } + } else { + chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); + fpp.save_params({t_lhs, m_rhs}); + return t_ss->call_function(t_oper_string, m_loc, {t_lhs, m_rhs}, t_ss.conversions()); + } + } + catch(const exception::dispatch_error &e){ + throw exception::eval_error("Can not find appropriate '" + t_oper_string + "' operator.", e.parameters, e.functions, false, *t_ss); + } + } + + private: + Operators::Opers m_oper; + Boxed_Value m_rhs; + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + + template + struct Binary_Operator_AST_Node : AST_Node_Impl { + Binary_Operator_AST_Node(const std::string &t_oper, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(t_oper, AST_Node_Type::Binary, std::move(t_loc), std::move(t_children)), + m_oper(Operators::to_operator(t_oper)) + { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + auto lhs = this->children[0]->eval(t_ss); + auto rhs = this->children[1]->eval(t_ss); + return do_oper(t_ss, m_oper, this->text, lhs, rhs); + } + + protected: + Boxed_Value do_oper(const chaiscript::detail::Dispatch_State &t_ss, + Operators::Opers t_oper, const std::string &t_oper_string, const Boxed_Value &t_lhs, const Boxed_Value &t_rhs) const + { + try { + if (t_oper != Operators::Opers::invalid && t_lhs.get_type_info().is_arithmetic() && t_rhs.get_type_info().is_arithmetic()) + { + // If it's an arithmetic operation we want to short circuit dispatch + try{ + return Boxed_Number::do_oper(t_oper, t_lhs, t_rhs); + } catch (const chaiscript::exception::arithmetic_error &) { + throw; + } catch (...) { + throw exception::eval_error("Error with numeric operator calling: " + t_oper_string); + } + } else { + chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); + fpp.save_params({t_lhs, t_rhs}); + return t_ss->call_function(t_oper_string, m_loc, {t_lhs, t_rhs}, t_ss.conversions()); + } + } + catch(const exception::dispatch_error &e){ + throw exception::eval_error("Can not find appropriate '" + t_oper_string + "' operator.", e.parameters, e.functions, false, *t_ss); + } + } + + private: + Operators::Opers m_oper; + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + + template + struct Constant_AST_Node final : AST_Node_Impl { + Constant_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, Boxed_Value t_value) + : AST_Node_Impl(t_ast_node_text, AST_Node_Type::Constant, std::move(t_loc)), + m_value(std::move(t_value)) + { + } + + explicit Constant_AST_Node(Boxed_Value t_value) + : AST_Node_Impl("", AST_Node_Type::Constant, Parse_Location()), + m_value(std::move(t_value)) + { + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override { + return m_value; + } + + Boxed_Value m_value; + }; + + template + struct Id_AST_Node final : AST_Node_Impl { + Id_AST_Node(const std::string &t_ast_node_text, Parse_Location t_loc) : + AST_Node_Impl(t_ast_node_text, AST_Node_Type::Id, std::move(t_loc)) + { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + try { + return t_ss.get_object(this->text, m_loc); + } + catch (std::exception &) { + throw exception::eval_error("Can not find object: " + this->text); + } + } + + private: + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + template + struct Fun_Call_AST_Node : AST_Node_Impl { + Fun_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Fun_Call, std::move(t_loc), std::move(t_children)) { + assert(!this->children.empty()); + } + + template + Boxed_Value do_eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const + { + chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); + + std::vector params; + + params.reserve(this->children[1]->children.size()); + for (const auto &child : this->children[1]->children) { + params.push_back(child->eval(t_ss)); + } + + if (Save_Params) { + fpp.save_params(params); + } + + Boxed_Value fn(this->children[0]->eval(t_ss)); + + using ConstFunctionTypePtr = const dispatch::Proxy_Function_Base *; + try { + return (*t_ss->boxed_cast(fn))(params, t_ss.conversions()); + } + catch(const exception::dispatch_error &e){ + throw exception::eval_error(std::string(e.what()) + " with function '" + this->children[0]->text + "'", e.parameters, e.functions, false, *t_ss); + } + catch(const exception::bad_boxed_cast &){ + try { + using ConstFunctionTypeRef = const Const_Proxy_Function &; + Const_Proxy_Function f = t_ss->boxed_cast(fn); + // handle the case where there is only 1 function to try to call and dispatch fails on it + throw exception::eval_error("Error calling function '" + this->children[0]->text + "'", params, {f}, false, *t_ss); + } catch (const exception::bad_boxed_cast &) { + throw exception::eval_error("'" + this->children[0]->pretty_print() + "' does not evaluate to a function."); + } + } + catch(const exception::arity_error &e){ + throw exception::eval_error(std::string(e.what()) + " with function '" + this->children[0]->text + "'"); + } + catch(const exception::guard_error &e){ + throw exception::eval_error(std::string(e.what()) + " with function '" + this->children[0]->text + "'"); + } + catch(detail::Return_Value &rv) { + return rv.retval; + } + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override + { + return do_eval_internal(t_ss); + } + + }; + + + template + struct Unused_Return_Fun_Call_AST_Node final : Fun_Call_AST_Node { + Unused_Return_Fun_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + Fun_Call_AST_Node(std::move(t_ast_node_text), std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override + { + return this->template do_eval_internal(t_ss); + } + }; + + + + + + template + struct Arg_AST_Node final : AST_Node_Impl { + Arg_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Arg_List, std::move(t_loc), std::move(t_children)) { } + + }; + + template + struct Arg_List_AST_Node final : AST_Node_Impl { + Arg_List_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Arg_List, std::move(t_loc), std::move(t_children)) { } + + + static std::string get_arg_name(const AST_Node_Impl &t_node) { + if (t_node.children.empty()) + { + return t_node.text; + } else if (t_node.children.size() == 1) { + return t_node.children[0]->text; + } else { + return t_node.children[1]->text; + } + } + + static std::vector get_arg_names(const AST_Node_Impl &t_node) { + std::vector retval; + + for (const auto &node : t_node.children) + { + retval.push_back(get_arg_name(*node)); + } + + return retval; + } + + static std::pair get_arg_type(const AST_Node_Impl &t_node, const chaiscript::detail::Dispatch_State &t_ss) + { + if (t_node.children.size() < 2) + { + return {}; + } else { + return {t_node.children[0]->text, t_ss->get_type(t_node.children[0]->text, false)}; + } + } + + static dispatch::Param_Types get_arg_types(const AST_Node_Impl &t_node, const chaiscript::detail::Dispatch_State &t_ss) { + std::vector> retval; + + for (const auto &child : t_node.children) + { + retval.push_back(get_arg_type(*child, t_ss)); + } + + return dispatch::Param_Types(std::move(retval)); + } + }; + + template + struct Equation_AST_Node final : AST_Node_Impl { + Equation_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Equation, std::move(t_loc), std::move(t_children)), + m_oper(Operators::to_operator(this->text)) + { assert(this->children.size() == 2); } + + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); + Boxed_Value rhs = this->children[1]->eval(t_ss); + Boxed_Value lhs = this->children[0]->eval(t_ss); + + if (lhs.is_return_value()) { + throw exception::eval_error("Error, cannot assign to temporary value."); + } else if (lhs.is_const()) { + throw exception::eval_error("Error, cannot assign to constant value."); + } + + + if (m_oper != Operators::Opers::invalid && lhs.get_type_info().is_arithmetic() && + rhs.get_type_info().is_arithmetic()) + { + try { + return Boxed_Number::do_oper(m_oper, lhs, rhs); + } catch (const std::exception &) { + throw exception::eval_error("Error with unsupported arithmetic assignment operation."); + } + } else if (m_oper == Operators::Opers::assign) { + try { + + if (lhs.is_undef()) { + if (!this->children.empty() + && ((this->children[0]->identifier == AST_Node_Type::Reference) + || (!this->children[0]->children.empty() + && this->children[0]->children[0]->identifier == AST_Node_Type::Reference) + ) + ) + + { + /// \todo This does not handle the case of an unassigned reference variable + /// being assigned outside of its declaration + lhs.assign(rhs); + lhs.reset_return_value(); + return rhs; + } else { + rhs = detail::clone_if_necessary(std::move(rhs), m_clone_loc, t_ss); + } + } + + try { + return t_ss->call_function(this->text, m_loc, {std::move(lhs), rhs}, t_ss.conversions()); + } + catch(const exception::dispatch_error &e){ + throw exception::eval_error("Unable to find appropriate'" + this->text + "' operator.", e.parameters, e.functions, false, *t_ss); + } + } + catch(const exception::dispatch_error &e){ + throw exception::eval_error("Missing clone or copy constructor for right hand side of equation", e.parameters, e.functions, false, *t_ss); + } + } + else if (this->text == ":=") { + if (lhs.is_undef() || Boxed_Value::type_match(lhs, rhs)) { + lhs.assign(rhs); + lhs.reset_return_value(); + } else { + throw exception::eval_error("Mismatched types in equation"); + } + } + else { + try { + return t_ss->call_function(this->text, m_loc, {std::move(lhs), rhs}, t_ss.conversions()); + } catch(const exception::dispatch_error &e){ + throw exception::eval_error("Unable to find appropriate'" + this->text + "' operator.", e.parameters, e.functions, false, *t_ss); + } + } + + return rhs; + } + + private: + Operators::Opers m_oper; + mutable std::atomic_uint_fast32_t m_loc = {0}; + mutable std::atomic_uint_fast32_t m_clone_loc = {0}; + }; + + template + struct Global_Decl_AST_Node final : AST_Node_Impl { + Global_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Global_Decl, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + const std::string &idname = + [&]()->const std::string & { + if (this->children[0]->identifier == AST_Node_Type::Reference) { + return this->children[0]->children[0]->text; + } else { + return this->children[0]->text; + } + }(); + + return t_ss->add_global_no_throw(Boxed_Value(), idname); + + } + }; + + + template + struct Var_Decl_AST_Node final : AST_Node_Impl { + Var_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Var_Decl, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + const std::string &idname = this->children[0]->text; + + try { + Boxed_Value bv; + t_ss.add_object(idname, bv); + return bv; + } catch (const exception::name_conflict_error &e) { + throw exception::eval_error("Variable redefined '" + e.name() + "'"); + } + } + }; + + template + struct Assign_Decl_AST_Node final : AST_Node_Impl { + Assign_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Assign_Decl, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + const std::string &idname = this->children[0]->text; + + try { + Boxed_Value bv(detail::clone_if_necessary(this->children[1]->eval(t_ss), m_loc, t_ss)); + bv.reset_return_value(); + t_ss.add_object(idname, bv); + return bv; + } catch (const exception::name_conflict_error &e) { + throw exception::eval_error("Variable redefined '" + e.name() + "'"); + } + } + private: + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + + template + struct Array_Call_AST_Node final : AST_Node_Impl { + Array_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Array_Call, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); + + const std::vector params{this->children[0]->eval(t_ss), this->children[1]->eval(t_ss)}; + + try { + fpp.save_params(params); + return t_ss->call_function("[]", m_loc, params, t_ss.conversions()); + } + catch(const exception::dispatch_error &e){ + throw exception::eval_error("Can not find appropriate array lookup operator '[]'.", e.parameters, e.functions, false, *t_ss ); + } + } + + + private: + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + template + struct Dot_Access_AST_Node final : AST_Node_Impl { + Dot_Access_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Dot_Access, std::move(t_loc), std::move(t_children)), + m_fun_name( + ((this->children[1]->identifier == AST_Node_Type::Fun_Call) || (this->children[1]->identifier == AST_Node_Type::Array_Call))? + this->children[1]->children[0]->text:this->children[1]->text) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); + + + Boxed_Value retval = this->children[0]->eval(t_ss); + std::vector params{retval}; + + bool has_function_params = false; + if (this->children[1]->children.size() > 1) { + has_function_params = true; + for (const auto &child : this->children[1]->children[1]->children) { + params.push_back(child->eval(t_ss)); + } + } + + fpp.save_params(params); + + try { + retval = t_ss->call_member(m_fun_name, m_loc, std::move(params), has_function_params, t_ss.conversions()); + } + catch(const exception::dispatch_error &e){ + if (e.functions.empty()) + { + throw exception::eval_error("'" + m_fun_name + "' is not a function."); + } else { + throw exception::eval_error(std::string(e.what()) + " for function '" + m_fun_name + "'", e.parameters, e.functions, true, *t_ss); + } + } + catch(detail::Return_Value &rv) { + retval = std::move(rv.retval); + } + + if (this->children[1]->identifier == AST_Node_Type::Array_Call) { + try { + retval = t_ss->call_function("[]", m_array_loc, {retval, this->children[1]->children[1]->eval(t_ss)}, t_ss.conversions()); + } + catch(const exception::dispatch_error &e){ + throw exception::eval_error("Can not find appropriate array lookup operator '[]'.", e.parameters, e.functions, true, *t_ss); + } + } + + return retval; + } + + private: + mutable std::atomic_uint_fast32_t m_loc = {0}; + mutable std::atomic_uint_fast32_t m_array_loc = {0}; + const std::string m_fun_name; + }; + + + template + struct Lambda_AST_Node final : AST_Node_Impl { + Lambda_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(t_ast_node_text, + AST_Node_Type::Lambda, + std::move(t_loc), + std::vector>(std::make_move_iterator(t_children.begin()), + std::make_move_iterator(std::prev(t_children.end()))) + ), + m_param_names(Arg_List_AST_Node::get_arg_names(*this->children[1])), + m_this_capture(has_this_capture(this->children[0]->children)), + m_lambda_node(std::move(t_children.back())) + { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + + const auto captures = [&]()->std::map{ + std::map named_captures; + for (const auto &capture : this->children[0]->children) { + named_captures.insert(std::make_pair(capture->children[0]->text, capture->children[0]->eval(t_ss))); + } + return named_captures; + }(); + + const auto numparams = this->children[1]->children.size(); + const auto param_types = Arg_List_AST_Node::get_arg_types(*this->children[1], t_ss); + + std::reference_wrapper engine(*t_ss); + + return Boxed_Value( + dispatch::make_dynamic_proxy_function( + [engine, lambda_node = this->m_lambda_node, param_names = this->m_param_names, captures, + this_capture = this->m_this_capture] (const std::vector &t_params) + { + return detail::eval_function(engine, *lambda_node, param_names, t_params, &captures, this_capture); + }, + static_cast(numparams), m_lambda_node, param_types + ) + ); + } + + static bool has_this_capture(const std::vector> &children) { + return std::any_of(std::begin(children), std::end(children), + [](const auto &child){ + return child->children[0]->text == "this"; + } + ); + } + + private: + const std::vector m_param_names; + const bool m_this_capture = false; + const std::shared_ptr> m_lambda_node; + }; + + template + struct Scopeless_Block_AST_Node final : AST_Node_Impl { + Scopeless_Block_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Scopeless_Block, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + const auto num_children = this->children.size(); + for (size_t i = 0; i < num_children-1; ++i) { + this->children[i]->eval(t_ss); + } + return this->children.back()->eval(t_ss); + } + }; + + template + struct Block_AST_Node final : AST_Node_Impl { + Block_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Block, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + const auto num_children = this->children.size(); + for (size_t i = 0; i < num_children-1; ++i) { + this->children[i]->eval(t_ss); + } + return this->children.back()->eval(t_ss); + } + }; + + template + struct Def_AST_Node final : AST_Node_Impl { + + std::shared_ptr> m_body_node; + std::shared_ptr> m_guard_node; + + Def_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Def, std::move(t_loc), + std::vector>(std::make_move_iterator(t_children.begin()), + std::make_move_iterator(std::prev(t_children.end(), has_guard(t_children, 1)?2:1))) + ), + // This apparent use after move is safe because we are only moving out the specific elements we need + // on each operation. + m_body_node(get_body_node(std::move(t_children))), + m_guard_node(get_guard_node(std::move(t_children), t_children.size()-this->children.size()==2)) + + { } + + static std::shared_ptr> get_guard_node(std::vector> &&vec, bool has_guard) + { + if (has_guard) { + return std::move(*std::prev(vec.end(), 2)); + } else { + return {}; + } + } + + static std::shared_ptr> get_body_node(std::vector> &&vec) + { + return std::move(vec.back()); + } + + static bool has_guard(const std::vector> &t_children, const std::size_t offset) + { + if ((t_children.size() > 2 + offset) && (t_children[1+offset]->identifier == AST_Node_Type::Arg_List)) { + if (t_children.size() > 3 + offset) { + return true; + } + } + else { + if (t_children.size() > 2 + offset) { + return true; + } + } + return false; + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override{ + std::vector t_param_names; + size_t numparams = 0; + + dispatch::Param_Types param_types; + + if ((this->children.size() > 1) && (this->children[1]->identifier == AST_Node_Type::Arg_List)) { + numparams = this->children[1]->children.size(); + t_param_names = Arg_List_AST_Node::get_arg_names(*this->children[1]); + param_types = Arg_List_AST_Node::get_arg_types(*this->children[1], t_ss); + } + + std::reference_wrapper engine(*t_ss); + std::shared_ptr guard; + if (m_guard_node) { + guard = dispatch::make_dynamic_proxy_function( + [engine, guardnode = m_guard_node, t_param_names](const std::vector &t_params) + { + return detail::eval_function(engine, *guardnode, t_param_names, t_params); + }, + static_cast(numparams), m_guard_node); + } + + try { + const std::string & l_function_name = this->children[0]->text; + t_ss->add( + dispatch::make_dynamic_proxy_function( + [engine, func_node = m_body_node, t_param_names](const std::vector &t_params) + { + return detail::eval_function(engine, *func_node, t_param_names, t_params); + }, + static_cast(numparams), m_body_node, + param_types, guard), l_function_name); + } catch (const exception::name_conflict_error &e) { + throw exception::eval_error("Function redefined '" + e.name() + "'"); + } + return void_var(); + } + + }; + + template + struct While_AST_Node final : AST_Node_Impl { + While_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::While, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + try { + while (this->get_scoped_bool_condition(*this->children[0], t_ss)) { + try { + this->children[1]->eval(t_ss); + } catch (detail::Continue_Loop &) { + // we got a continue exception, which means all of the remaining + // loop implementation is skipped and we just need to continue to + // the next condition test + } + } + } catch (detail::Break_Loop &) { + // loop was broken intentionally + } + + return void_var(); + } + }; + + template + struct Class_AST_Node final : AST_Node_Impl { + Class_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Class, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + /// \todo do this better + // put class name in current scope so it can be looked up by the attrs and methods + t_ss.add_object("_current_class_name", const_var(this->children[0]->text)); + + this->children[1]->eval(t_ss); + + return void_var(); + } + }; + + + template + struct If_AST_Node final : AST_Node_Impl { + If_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::If, std::move(t_loc), std::move(t_children)) + { + assert(this->children.size() == 3); + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + if (this->get_bool_condition(this->children[0]->eval(t_ss), t_ss)) { + return this->children[1]->eval(t_ss); + } else { + return this->children[2]->eval(t_ss); + } + } + }; + + template + struct Ranged_For_AST_Node final : AST_Node_Impl { + Ranged_For_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Ranged_For, std::move(t_loc), std::move(t_children)) + { assert(this->children.size() == 3); } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override{ + const auto get_function = [&t_ss](const std::string &t_name, auto &t_hint){ + uint_fast32_t hint = t_hint; + auto funs = t_ss->get_function(t_name, hint); + if (funs.first != hint) { t_hint = uint_fast32_t(funs.first); } + return std::move(funs.second); + }; + + const auto call_function = [&t_ss](const auto &t_funcs, const Boxed_Value &t_param) { + return dispatch::dispatch(*t_funcs, {t_param}, t_ss.conversions()); + }; + + + const std::string &loop_var_name = this->children[0]->text; + Boxed_Value range_expression_result = this->children[1]->eval(t_ss); + + + const auto do_loop = [&loop_var_name, &t_ss, this](const auto &ranged_thing){ + try { + for (auto &&loop_var : ranged_thing) { + // This scope push and pop might not be the best thing for perf + // but we know it's 100% correct + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + /// to-do make this if-constexpr with C++17 branch + if (!std::is_same, Boxed_Value>::value) { + t_ss.add_get_object(loop_var_name, Boxed_Value(std::ref(loop_var))); + } else { + t_ss.add_get_object(loop_var_name, Boxed_Value(loop_var)); + } + try { + this->children[2]->eval(t_ss); + } catch (detail::Continue_Loop &) { + } + } + } catch (detail::Break_Loop &) { + // loop broken + } + return void_var(); + }; + + if (range_expression_result.get_type_info().bare_equal_type_info(typeid(std::vector))) { + return do_loop(boxed_cast &>(range_expression_result)); + } else if (range_expression_result.get_type_info().bare_equal_type_info(typeid(std::map))) { + return do_loop(boxed_cast &>(range_expression_result)); + } else { + const auto range_funcs = get_function("range", m_range_loc); + const auto empty_funcs = get_function("empty", m_empty_loc); + const auto front_funcs = get_function("front", m_front_loc); + const auto pop_front_funcs = get_function("pop_front", m_pop_front_loc); + + try { + const auto range_obj = call_function(range_funcs, range_expression_result); + while (!boxed_cast(call_function(empty_funcs, range_obj))) { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + t_ss.add_get_object(loop_var_name, call_function(front_funcs, range_obj)); + try { + this->children[2]->eval(t_ss); + } catch (detail::Continue_Loop &) { + } + call_function(pop_front_funcs, range_obj); + } + } catch (detail::Break_Loop &) { + // loop broken + } + return void_var(); + } + + } + + private: + mutable std::atomic_uint_fast32_t m_range_loc = {0}; + mutable std::atomic_uint_fast32_t m_empty_loc = {0}; + mutable std::atomic_uint_fast32_t m_front_loc = {0}; + mutable std::atomic_uint_fast32_t m_pop_front_loc = {0}; + }; + + + template + struct For_AST_Node final : AST_Node_Impl { + For_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::For, std::move(t_loc), std::move(t_children)) + { assert(this->children.size() == 4); } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override{ + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + try { + for ( + this->children[0]->eval(t_ss); + this->get_scoped_bool_condition(*this->children[1], t_ss); + this->children[2]->eval(t_ss) + ) { + try { + // Body of Loop + this->children[3]->eval(t_ss); + } catch (detail::Continue_Loop &) { + // we got a continue exception, which means all of the remaining + // loop implementation is skipped and we just need to continue to + // the next iteration step + } + } + } catch (detail::Break_Loop &) { + // loop broken + } + + return void_var(); + } + + }; + + template + struct Switch_AST_Node final : AST_Node_Impl { + Switch_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Switch, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + bool breaking = false; + size_t currentCase = 1; + bool hasMatched = false; + + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + Boxed_Value match_value(this->children[0]->eval(t_ss)); + + while (!breaking && (currentCase < this->children.size())) { + try { + if (this->children[currentCase]->identifier == AST_Node_Type::Case) { + //This is a little odd, but because want to see both the switch and the case simultaneously, I do a downcast here. + try { + if (hasMatched || boxed_cast(t_ss->call_function("==", m_loc, {match_value, this->children[currentCase]->children[0]->eval(t_ss)}, t_ss.conversions()))) { + this->children[currentCase]->eval(t_ss); + hasMatched = true; + } + } + catch (const exception::bad_boxed_cast &) { + throw exception::eval_error("Internal error: case guard evaluation not boolean"); + } + } + else if (this->children[currentCase]->identifier == AST_Node_Type::Default) { + this->children[currentCase]->eval(t_ss); + hasMatched = true; + } + } + catch (detail::Break_Loop &) { + breaking = true; + } + ++currentCase; + } + return void_var(); + } + + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + template + struct Case_AST_Node final : AST_Node_Impl { + Case_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Case, std::move(t_loc), std::move(t_children)) + { assert(this->children.size() == 2); /* how many children does it have? */ } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + this->children[1]->eval(t_ss); + + return void_var(); + } + }; + + template + struct Default_AST_Node final : AST_Node_Impl { + Default_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Default, std::move(t_loc), std::move(t_children)) + { assert(this->children.size() == 1); } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + this->children[0]->eval(t_ss); + + return void_var(); + } + }; + + + template + struct Inline_Array_AST_Node final : AST_Node_Impl { + Inline_Array_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Inline_Array, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + try { + std::vector vec; + if (!this->children.empty()) { + vec.reserve(this->children[0]->children.size()); + for (const auto &child : this->children[0]->children) { + vec.push_back(detail::clone_if_necessary(child->eval(t_ss), m_loc, t_ss)); + } + } + return const_var(std::move(vec)); + } + catch (const exception::dispatch_error &) { + throw exception::eval_error("Can not find appropriate 'clone' or copy constructor for vector elements"); + } + } + + private: + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + template + struct Inline_Map_AST_Node final : AST_Node_Impl { + Inline_Map_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Inline_Map, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override + { + try { + std::map retval; + + for (const auto &child : this->children[0]->children) { + retval.insert(std::make_pair(t_ss->boxed_cast(child->children[0]->eval(t_ss)), + detail::clone_if_necessary(child->children[1]->eval(t_ss), m_loc, t_ss))); + } + + return const_var(std::move(retval)); + } + catch (const exception::dispatch_error &e) { + throw exception::eval_error("Can not find appropriate copy constructor or 'clone' while inserting into Map.", e.parameters, e.functions, false, *t_ss); + } + } + + private: + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + template + struct Return_AST_Node final : AST_Node_Impl { + Return_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Return, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override{ + if (!this->children.empty()) { + throw detail::Return_Value{this->children[0]->eval(t_ss)}; + } + else { + throw detail::Return_Value{void_var()}; + } + } + }; + + template + struct File_AST_Node final : AST_Node_Impl { + File_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::File, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + try { + const auto num_children = this->children.size(); + + if (num_children > 0) { + for (size_t i = 0; i < num_children-1; ++i) { + this->children[i]->eval(t_ss); + } + return this->children.back()->eval(t_ss); + } else { + return void_var(); + } + } catch (const detail::Continue_Loop &) { + throw exception::eval_error("Unexpected `continue` statement outside of a loop"); + } catch (const detail::Break_Loop &) { + throw exception::eval_error("Unexpected `break` statement outside of a loop"); + } + } + }; + + template + struct Reference_AST_Node final : AST_Node_Impl { + Reference_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Reference, std::move(t_loc), std::move(t_children)) + { assert(this->children.size() == 1); } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override{ + Boxed_Value bv; + t_ss.add_object(this->children[0]->text, bv); + return bv; + } + }; + + template + struct Prefix_AST_Node final : AST_Node_Impl { + Prefix_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Prefix, std::move(t_loc), std::move(t_children)), + m_oper(Operators::to_operator(this->text, true)) + { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override{ + Boxed_Value bv(this->children[0]->eval(t_ss)); + + try { + // short circuit arithmetic operations + if (m_oper != Operators::Opers::invalid && m_oper != Operators::Opers::bitwise_and && bv.get_type_info().is_arithmetic()) + { + if ((m_oper == Operators::Opers::pre_increment || m_oper == Operators::Opers::pre_decrement) && bv.is_const()) + { + throw exception::eval_error("Error with prefix operator evaluation: cannot modify constant value."); + } + return Boxed_Number::do_oper(m_oper, bv); + } else { + chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); + fpp.save_params({bv}); + return t_ss->call_function(this->text, m_loc, {std::move(bv)}, t_ss.conversions()); + } + } catch (const exception::dispatch_error &e) { + throw exception::eval_error("Error with prefix operator evaluation: '" + this->text + "'", e.parameters, e.functions, false, *t_ss); + } + } + + private: + Operators::Opers m_oper = Operators::Opers::invalid; + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + template + struct Break_AST_Node final : AST_Node_Impl { + Break_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Break, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override{ + throw detail::Break_Loop(); + } + }; + + template + struct Continue_AST_Node final : AST_Node_Impl { + Continue_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Continue, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override{ + throw detail::Continue_Loop(); + } + }; + + template + struct Noop_AST_Node final : AST_Node_Impl { + Noop_AST_Node() : + AST_Node_Impl("", AST_Node_Type::Noop, Parse_Location()) + { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override{ + // It's a no-op, that evaluates to "void" + return val; + } + + Boxed_Value val = void_var(); + }; + + template + struct Map_Pair_AST_Node final : AST_Node_Impl { + Map_Pair_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Map_Pair, std::move(t_loc), std::move(t_children)) { } + }; + + template + struct Value_Range_AST_Node final : AST_Node_Impl { + Value_Range_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Value_Range, std::move(t_loc), std::move(t_children)) { } + }; + + template + struct Inline_Range_AST_Node final : AST_Node_Impl { + Inline_Range_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Inline_Range, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override{ + try { + auto oper1 = this->children[0]->children[0]->children[0]->eval(t_ss); + auto oper2 = this->children[0]->children[0]->children[1]->eval(t_ss); + return t_ss->call_function("generate_range", m_loc, {oper1, oper2}, t_ss.conversions()); + } + catch (const exception::dispatch_error &e) { + throw exception::eval_error("Unable to generate range vector, while calling 'generate_range'", e.parameters, e.functions, false, *t_ss); + } + } + + private: + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + + template + struct Try_AST_Node final : AST_Node_Impl { + Try_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Try, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value handle_exception(const chaiscript::detail::Dispatch_State &t_ss, const Boxed_Value &t_except) const + { + Boxed_Value retval; + + size_t end_point = this->children.size(); + if (this->children.back()->identifier == AST_Node_Type::Finally) { + assert(end_point > 0); + end_point = this->children.size() - 1; + } + for (size_t i = 1; i < end_point; ++i) { + chaiscript::eval::detail::Scope_Push_Pop catch_scope(t_ss); + auto &catch_block = *this->children[i]; + + if (catch_block.children.size() == 1) { + //No variable capture, no guards + retval = catch_block.children[0]->eval(t_ss); + break; + } else if (catch_block.children.size() == 2 || catch_block.children.size() == 3) { + const auto name = Arg_List_AST_Node::get_arg_name(*catch_block.children[0]); + + if (dispatch::Param_Types( + std::vector>{Arg_List_AST_Node::get_arg_type(*catch_block.children[0], t_ss)} + ).match(std::vector{t_except}, t_ss.conversions()).first) + { + t_ss.add_object(name, t_except); + + if (catch_block.children.size() == 2) { + //Variable capture, no guards + retval = catch_block.children[1]->eval(t_ss); + break; + } + else if (catch_block.children.size() == 3) { + //Variable capture, guards + + bool guard = false; + try { + guard = boxed_cast(catch_block.children[1]->eval(t_ss)); + } catch (const exception::bad_boxed_cast &) { + if (this->children.back()->identifier == AST_Node_Type::Finally) { + this->children.back()->children[0]->eval(t_ss); + } + throw exception::eval_error("Guard condition not boolean"); + } + if (guard) { + retval = catch_block.children[2]->eval(t_ss); + break; + } + } + } + } + else { + if (this->children.back()->identifier == AST_Node_Type::Finally) { + this->children.back()->children[0]->eval(t_ss); + } + throw exception::eval_error("Internal error: catch block size unrecognized"); + } + } + + return retval; + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + Boxed_Value retval; + + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + + try { + retval = this->children[0]->eval(t_ss); + } + catch (const exception::eval_error &e) { + retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); + } + catch (const std::runtime_error &e) { + retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); + } + catch (const std::out_of_range &e) { + retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); + } + catch (const std::exception &e) { + retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); + } + catch (Boxed_Value &e) { + retval = handle_exception(t_ss, e); + } + catch (...) { + if (this->children.back()->identifier == AST_Node_Type::Finally) { + this->children.back()->children[0]->eval(t_ss); + } + throw; + } + + + if (this->children.back()->identifier == AST_Node_Type::Finally) { + retval = this->children.back()->children[0]->eval(t_ss); + } + + return retval; + } + + }; + + template + struct Catch_AST_Node final : AST_Node_Impl { + Catch_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Catch, std::move(t_loc), std::move(t_children)) { } + }; + + template + struct Finally_AST_Node final : AST_Node_Impl { + Finally_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Finally, std::move(t_loc), std::move(t_children)) { } + }; + + template + struct Method_AST_Node final : AST_Node_Impl { + std::shared_ptr> m_body_node; + std::shared_ptr> m_guard_node; + + Method_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Method, std::move(t_loc), + std::vector>(std::make_move_iterator(t_children.begin()), + std::make_move_iterator(std::prev(t_children.end(), Def_AST_Node::has_guard(t_children, 1)?2:1))) + ), + m_body_node(Def_AST_Node::get_body_node(std::move(t_children))), + m_guard_node(Def_AST_Node::get_guard_node(std::move(t_children), t_children.size()-this->children.size()==2)) + { + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override{ + + AST_Node_Impl_Ptr guardnode; + + const std::string & class_name = this->children[0]->text; + + //The first param of a method is always the implied this ptr. + std::vector t_param_names{"this"}; + dispatch::Param_Types param_types; + + if ((this->children.size() > 2) + && (this->children[2]->identifier == AST_Node_Type::Arg_List)) { + auto args = Arg_List_AST_Node::get_arg_names(*this->children[2]); + t_param_names.insert(t_param_names.end(), args.begin(), args.end()); + param_types = Arg_List_AST_Node::get_arg_types(*this->children[2], t_ss); + } + + const size_t numparams = t_param_names.size(); + + std::shared_ptr guard; + std::reference_wrapper engine(*t_ss); + if (m_guard_node) { + guard = dispatch::make_dynamic_proxy_function( + [engine, t_param_names, guardnode = m_guard_node](const std::vector &t_params) { + return chaiscript::eval::detail::eval_function(engine, *guardnode, t_param_names, t_params); + }, + static_cast(numparams), m_guard_node); + } + + try { + const std::string & function_name = this->children[1]->text; + + if (function_name == class_name) { + param_types.push_front(class_name, Type_Info()); + + t_ss->add( + std::make_shared(class_name, + dispatch::make_dynamic_proxy_function( + [engine, t_param_names, node = m_body_node](const std::vector &t_params) { + return chaiscript::eval::detail::eval_function(engine, *node, t_param_names, t_params); + }, + static_cast(numparams), m_body_node, param_types, guard + ) + ), + function_name); + + } else { + // if the type is unknown, then this generates a function that looks up the type + // at runtime. Defining the type first before this is called is better + auto type = t_ss->get_type(class_name, false); + param_types.push_front(class_name, type); + + t_ss->add( + std::make_shared(class_name, + dispatch::make_dynamic_proxy_function( + [engine, t_param_names, node = m_body_node](const std::vector &t_params) { + return chaiscript::eval::detail::eval_function(engine, *node, t_param_names, t_params); + }, + static_cast(numparams), m_body_node, param_types, guard), type), + function_name); + } + } catch (const exception::name_conflict_error &e) { + throw exception::eval_error("Method redefined '" + e.name() + "'"); + } + return void_var(); + } + + }; + + template + struct Attr_Decl_AST_Node final : AST_Node_Impl { + Attr_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Attr_Decl, std::move(t_loc), std::move(t_children)) { } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override + { + std::string class_name = this->children[0]->text; + + try { + std::string attr_name = this->children[1]->text; + + t_ss->add( + std::make_shared( + std::move(class_name), + fun([attr_name](dispatch::Dynamic_Object &t_obj) { + return t_obj.get_attr(attr_name); + }), + true + + ), this->children[1]->text); + } catch (const exception::name_conflict_error &e) { + throw exception::eval_error("Attribute redefined '" + e.name() + "'"); + } + return void_var(); + } + + }; + + + template + struct Logical_And_AST_Node final : AST_Node_Impl { + Logical_And_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Logical_And, std::move(t_loc), std::move(t_children)) + { assert(this->children.size() == 2); } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override + { + return const_var(this->get_bool_condition(this->children[0]->eval(t_ss), t_ss) + && this->get_bool_condition(this->children[1]->eval(t_ss), t_ss)); + } + + }; + + template + struct Logical_Or_AST_Node final : AST_Node_Impl { + Logical_Or_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : + AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Logical_Or, std::move(t_loc), std::move(t_children)) + { assert(this->children.size() == 2); } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override + { + return const_var(this->get_bool_condition(this->children[0]->eval(t_ss), t_ss) + || this->get_bool_condition(this->children[1]->eval(t_ss), t_ss)); + } + }; + } + + +} +#endif /* CHAISCRIPT_EVAL_HPP_ */ + diff --git a/chaiscript/language/chaiscript_optimizer.hpp b/chaiscript/language/chaiscript_optimizer.hpp new file mode 100644 index 0000000..4bcae1a --- /dev/null +++ b/chaiscript/language/chaiscript_optimizer.hpp @@ -0,0 +1,471 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_OPTIMIZER_HPP_ +#define CHAISCRIPT_OPTIMIZER_HPP_ + +#include "chaiscript_eval.hpp" + + +namespace chaiscript { + namespace optimizer { + + template + struct Optimizer : T... + { + Optimizer() = default; + explicit Optimizer(T ... t) + : T(std::move(t))... + { + } + + template + auto optimize(eval::AST_Node_Impl_Ptr p) { + (void)std::initializer_list{ (p = static_cast(*this).optimize(std::move(p)), 0)... }; + return p; + } + }; + + template + eval::AST_Node_Impl &child_at(eval::AST_Node_Impl &node, const size_t offset) { + if (node.children[offset]->identifier == AST_Node_Type::Compiled) { + return *(dynamic_cast &>(*node.children[offset]).m_original_node); + } else { + return *node.children[offset]; + } + } + + template + const eval::AST_Node_Impl &child_at(const eval::AST_Node_Impl &node, const size_t offset) { + if (node.children[offset]->identifier == AST_Node_Type::Compiled) { + return *(dynamic_cast &>(*node.children[offset]).m_original_node); + } else { + return *node.children[offset]; + } + + + /* + if (node->identifier == AST_Node_Type::Compiled) { + return dynamic_cast&>(*node).m_original_node->children[offset]; + } else { + return node->children[offset]; + } + */ + } + + template + auto child_count(const eval::AST_Node_Impl &node) { + if (node.identifier == AST_Node_Type::Compiled) { + return dynamic_cast&>(node).m_original_node->children.size(); + } else { + return node.children.size(); + } + } + + template + auto make_compiled_node(eval::AST_Node_Impl_Ptr original_node, std::vector> children, Callable callable) + { + return chaiscript::make_unique, eval::Compiled_AST_Node>(std::move(original_node), std::move(children), std::move(callable)); + } + + + struct Return { + template + auto optimize(eval::AST_Node_Impl_Ptr p) + { + if ( (p->identifier == AST_Node_Type::Def || p->identifier == AST_Node_Type::Lambda) + && !p->children.empty()) + { + auto &last_child = p->children.back(); + if (last_child->identifier == AST_Node_Type::Block) { + auto &block_last_child = last_child->children.back(); + if (block_last_child->identifier == AST_Node_Type::Return) { + if (block_last_child->children.size() == 1) { + last_child->children.back() = std::move(block_last_child->children[0]); + } + } + } + } + + return p; + } + }; + + template + bool contains_var_decl_in_scope(const eval::AST_Node_Impl &node) + { + if (node.identifier == AST_Node_Type::Var_Decl || node.identifier == AST_Node_Type::Assign_Decl || node.identifier == AST_Node_Type::Reference) { + return true; + } + + const auto num = child_count(node); + + for (size_t i = 0; i < num; ++i) { + const auto &child = child_at(node, i); + if (child.identifier != AST_Node_Type::Block + && child.identifier != AST_Node_Type::For + && child.identifier != AST_Node_Type::Ranged_For + && contains_var_decl_in_scope(child)) { + return true; + } + } + + return false; + } + + struct Block { + template + auto optimize(eval::AST_Node_Impl_Ptr node) { + if (node->identifier == AST_Node_Type::Block) + { + if (!contains_var_decl_in_scope(*node)) + { + if (node->children.size() == 1) { + return std::move(node->children[0]); + } else { + return chaiscript::make_unique, eval::Scopeless_Block_AST_Node>(node->text, node->location, + std::move(node->children)); + } + } + } + + return node; + } + }; + + struct Dead_Code { + template + auto optimize(eval::AST_Node_Impl_Ptr node) { + if (node->identifier == AST_Node_Type::Block) + { + std::vector keepers; + const auto num_children = node->children.size(); + keepers.reserve(num_children); + + for (size_t i = 0; i < num_children; ++i) { + const auto &child = *node->children[i]; + if ( (child.identifier != AST_Node_Type::Id + && child.identifier != AST_Node_Type::Constant + && child.identifier != AST_Node_Type::Noop) + || i == num_children - 1) { + keepers.push_back(i); + } + } + + if (keepers.size() == num_children) { + return node; + } else { + const auto new_children = [&](){ + std::vector> retval; + for (const auto x : keepers) + { + retval.push_back(std::move(node->children[x])); + } + return retval; + }; + + return chaiscript::make_unique, eval::Block_AST_Node>(node->text, node->location, new_children()); + } + } else { + return node; + } + } + }; + + struct Unused_Return { + template + auto optimize(eval::AST_Node_Impl_Ptr node) { + if ((node->identifier == AST_Node_Type::Block + || node->identifier == AST_Node_Type::Scopeless_Block) + && !node->children.empty()) + { + for (size_t i = 0; i < node->children.size()-1; ++i) { + auto child = node->children[i].get(); + if (child->identifier == AST_Node_Type::Fun_Call) { + node->children[i] = chaiscript::make_unique, eval::Unused_Return_Fun_Call_AST_Node>(child->text, child->location, + std::move(child->children)); + } + } + } else if ((node->identifier == AST_Node_Type::For + || node->identifier == AST_Node_Type::While) + && child_count(*node) > 0) { + auto &child = child_at(*node, child_count(*node) - 1); + if (child.identifier == AST_Node_Type::Block + || child.identifier == AST_Node_Type::Scopeless_Block) + { + auto num_sub_children = child_count(child); + for (size_t i = 0; i < num_sub_children; ++i) { + auto &sub_child = child_at(child, i); + if (sub_child.identifier == AST_Node_Type::Fun_Call) { + child.children[i] = chaiscript::make_unique, eval::Unused_Return_Fun_Call_AST_Node>(sub_child.text, sub_child.location, std::move(sub_child.children)); + } + } + } + } + return node; + } + }; + + struct Assign_Decl { + template + auto optimize(eval::AST_Node_Impl_Ptr node) { + if ((node->identifier == AST_Node_Type::Equation) + && node->text == "=" + && node->children.size() == 2 + && node->children[0]->identifier == AST_Node_Type::Var_Decl + ) + { + std::vector> new_children; + new_children.push_back(std::move(node->children[0]->children[0])); + new_children.push_back(std::move(node->children[1])); + return chaiscript::make_unique, eval::Assign_Decl_AST_Node>(node->text, + node->location, std::move(new_children) ); + } + + return node; + } + }; + + + struct If { + template + auto optimize(eval::AST_Node_Impl_Ptr node) { + if ((node->identifier == AST_Node_Type::If) + && node->children.size() >= 2 + && node->children[0]->identifier == AST_Node_Type::Constant) + { + const auto condition = dynamic_cast *>(node->children[0].get())->m_value; + if (condition.get_type_info().bare_equal_type_info(typeid(bool))) { + if (boxed_cast(condition)) { + return std::move(node->children[1]); + } else if (node->children.size() == 3) { + return std::move(node->children[2]); + } + } + } + + return node; + } + }; + + struct Partial_Fold { + template + auto optimize(eval::AST_Node_Impl_Ptr node) { + + // Fold right side + if (node->identifier == AST_Node_Type::Binary + && node->children.size() == 2 + && node->children[0]->identifier != AST_Node_Type::Constant + && node->children[1]->identifier == AST_Node_Type::Constant) + { + try { + const auto &oper = node->text; + const auto parsed = Operators::to_operator(oper); + if (parsed != Operators::Opers::invalid) { + const auto rhs = dynamic_cast *>(node->children[1].get())->m_value; + if (rhs.get_type_info().is_arithmetic()) { + return chaiscript::make_unique, eval::Fold_Right_Binary_Operator_AST_Node>(node->text, node->location, + std::move(node->children), rhs); + } + } + } catch (const std::exception &) { + //failure to fold, that's OK + } + } + + return node; + } + }; + + struct Constant_Fold { + template + auto optimize(eval::AST_Node_Impl_Ptr node) { + + if (node->identifier == AST_Node_Type::Prefix + && node->children.size() == 1 + && node->children[0]->identifier == AST_Node_Type::Constant) + { + try { + const auto &oper = node->text; + const auto parsed = Operators::to_operator(oper, true); + const auto lhs = dynamic_cast *>(node->children[0].get())->m_value; + const auto match = oper + node->children[0]->text; + + if (parsed != Operators::Opers::invalid && parsed != Operators::Opers::bitwise_and && lhs.get_type_info().is_arithmetic()) { + const auto val = Boxed_Number::do_oper(parsed, lhs); + return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, std::move(val)); + } else if (lhs.get_type_info().bare_equal_type_info(typeid(bool)) && oper == "!") { + return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, Boxed_Value(!boxed_cast(lhs))); + } + } catch (const std::exception &) { + //failure to fold, that's OK + } + } else if ((node->identifier == AST_Node_Type::Logical_And || node->identifier == AST_Node_Type::Logical_Or) + && node->children.size() == 2 + && node->children[0]->identifier == AST_Node_Type::Constant + && node->children[1]->identifier == AST_Node_Type::Constant) + { + try { + const auto lhs = dynamic_cast &>(*node->children[0]).m_value; + const auto rhs = dynamic_cast &>(*node->children[1]).m_value; + if (lhs.get_type_info().bare_equal_type_info(typeid(bool)) && rhs.get_type_info().bare_equal_type_info(typeid(bool))) { + const auto match = node->children[0]->text + " " + node->text + " " + node->children[1]->text; + const auto val = [lhs_val = boxed_cast(lhs), rhs_val = boxed_cast(rhs), id = node->identifier] { + if (id == AST_Node_Type::Logical_And) { return Boxed_Value(lhs_val && rhs_val); } + else { return Boxed_Value(lhs_val || rhs_val); } + }(); + + return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, std::move(val)); + } + } catch (const std::exception &) { + //failure to fold, that's OK + } + } else if (node->identifier == AST_Node_Type::Binary + && node->children.size() == 2 + && node->children[0]->identifier == AST_Node_Type::Constant + && node->children[1]->identifier == AST_Node_Type::Constant) + { + try { + const auto &oper = node->text; + const auto parsed = Operators::to_operator(oper); + if (parsed != Operators::Opers::invalid) { + const auto lhs = dynamic_cast &>(*node->children[0]).m_value; + const auto rhs = dynamic_cast &>(*node->children[1]).m_value; + if (lhs.get_type_info().is_arithmetic() && rhs.get_type_info().is_arithmetic()) { + const auto val = Boxed_Number::do_oper(parsed, lhs, rhs); + const auto match = node->children[0]->text + " " + oper + " " + node->children[1]->text; + return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, std::move(val)); + } + } + } catch (const std::exception &) { + //failure to fold, that's OK + } + } else if (node->identifier == AST_Node_Type::Fun_Call + && node->children.size() == 2 + && node->children[0]->identifier == AST_Node_Type::Id + && node->children[1]->identifier == AST_Node_Type::Arg_List + && node->children[1]->children.size() == 1 + && node->children[1]->children[0]->identifier == AST_Node_Type::Constant) { + + const auto arg = dynamic_cast &>(*node->children[1]->children[0]).m_value; + if (arg.get_type_info().is_arithmetic()) { + const auto &fun_name = node->children[0]->text; + + const auto make_constant = [&node, &fun_name](auto val){ + const auto match = fun_name + "(" + node->children[1]->children[0]->text + ")"; + return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, const_var(val)); + }; + + if (fun_name == "double") { + return make_constant(Boxed_Number(arg).get_as()); + } else if (fun_name == "int") { + return make_constant(Boxed_Number(arg).get_as()); + } else if (fun_name == "float") { + return make_constant(Boxed_Number(arg).get_as()); + } else if (fun_name == "long") { + return make_constant(Boxed_Number(arg).get_as()); + } else if (fun_name == "size_t") { + return make_constant(Boxed_Number(arg).get_as()); + } + + + } + + } + + return node; + } + }; + + struct For_Loop { + template + auto optimize(eval::AST_Node_Impl_Ptr for_node) { + + if (for_node->identifier != AST_Node_Type::For) { + return for_node; + } + + const auto &eq_node = child_at(*for_node, 0); + const auto &binary_node = child_at(*for_node, 1); + const auto &prefix_node = child_at(*for_node, 2); + + if (child_count(*for_node) == 4 + && eq_node.identifier == AST_Node_Type::Assign_Decl + && child_count(eq_node) == 2 + && child_at(eq_node, 0).identifier == AST_Node_Type::Id + && child_at(eq_node, 1).identifier == AST_Node_Type::Constant + && binary_node.identifier == AST_Node_Type::Binary + && binary_node.text == "<" + && child_count(binary_node) == 2 + && child_at(binary_node, 0).identifier == AST_Node_Type::Id + && child_at(binary_node, 0).text == child_at(eq_node,0).text + && child_at(binary_node, 1).identifier == AST_Node_Type::Constant + && prefix_node.identifier == AST_Node_Type::Prefix + && prefix_node.text == "++" + && child_count(prefix_node) == 1 + && child_at(prefix_node, 0).identifier == AST_Node_Type::Id + && child_at(prefix_node, 0).text == child_at(eq_node,0).text) + { + const Boxed_Value &begin = dynamic_cast &>(child_at(eq_node, 1)).m_value; + const Boxed_Value &end = dynamic_cast &>(child_at(binary_node, 1)).m_value; + const std::string &id = child_at(prefix_node, 0).text; + + if (begin.get_type_info().bare_equal(user_type()) + && end.get_type_info().bare_equal(user_type())) { + + const auto start_int = boxed_cast(begin); + const auto end_int = boxed_cast(end); + + // note that we are moving the last element out, then popping the empty shared_ptr + // from the vector + std::vector> body_vector; + auto body_child = std::move(for_node->children[3]); + for_node->children.pop_back(); + body_vector.emplace_back(std::move(body_child)); + + return make_compiled_node(std::move(for_node), std::move(body_vector), + [id, start_int, end_int](const std::vector> &children, const chaiscript::detail::Dispatch_State &t_ss) { + assert(children.size() == 1); + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + int i = start_int; + t_ss.add_object(id, var(&i)); + + try { + for (; i < end_int; ++i) { + try { + // Body of Loop + children[0]->eval(t_ss); + } catch (eval::detail::Continue_Loop &) { + // we got a continue exception, which means all of the remaining + // loop implementation is skipped and we just need to continue to + // the next iteration step + } + } + } catch (eval::detail::Break_Loop &) { + // loop broken + } + + return void_var(); + } + ); + } else { + return for_node; + } + } else { + return for_node; + } + } + }; + + typedef Optimizer Optimizer_Default; + + } +} + + +#endif diff --git a/chaiscript/language/chaiscript_parser.hpp b/chaiscript/language/chaiscript_parser.hpp new file mode 100644 index 0000000..848296c --- /dev/null +++ b/chaiscript/language/chaiscript_parser.hpp @@ -0,0 +1,2699 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_PARSER_HPP_ +#define CHAISCRIPT_PARSER_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include + + + + +#include "../dispatchkit/boxed_value.hpp" +#include "chaiscript_common.hpp" +#include "chaiscript_optimizer.hpp" +#include "chaiscript_tracer.hpp" +#include "../utility/fnv1a.hpp" +#include "../utility/static_string.hpp" + +#if defined(CHAISCRIPT_UTF16_UTF32) +#include +#include +#endif + +#if defined(CHAISCRIPT_MSVC) && defined(max) && defined(min) +#define CHAISCRIPT_PUSHED_MIN_MAX +#pragma push_macro("max") // Why Microsoft? why? This is worse than bad +#undef max +#pragma push_macro("min") +#undef min +#endif + + +namespace chaiscript +{ + /// \brief Classes and functions used during the parsing process. + namespace parser + { + /// \brief Classes and functions internal to the parsing process. Not supported for the end user. + namespace detail + { + enum Alphabet + { symbol_alphabet = 0 + , keyword_alphabet + , int_alphabet + , float_alphabet + , x_alphabet + , hex_alphabet + , b_alphabet + , bin_alphabet + , id_alphabet + , white_alphabet + , int_suffix_alphabet + , float_suffix_alphabet + , max_alphabet + , lengthof_alphabet = 256 + }; + + // Generic for u16, u32 and wchar + template + struct Char_Parser_Helper + { + // common for all implementations + static std::string u8str_from_ll(long long val) + { + typedef std::string::value_type char_type; + + char_type c[2]; + c[1] = char_type(val); + c[0] = char_type(val >> 8); + + if (c[0] == 0) + { + return std::string(1, c[1]); // size, character + } + + return std::string(c, 2); // char buffer, size + } + + static string_type str_from_ll(long long val) + { + typedef typename string_type::value_type target_char_type; +#if defined (CHAISCRIPT_UTF16_UTF32) + // prepare converter + std::wstring_convert, target_char_type> converter; + // convert + return converter.from_bytes(u8str_from_ll(val)); +#else + // no conversion available, just put value as character + return string_type(1, target_char_type(val)); // size, character +#endif + } + }; + + // Specialization for char AKA UTF-8 + template<> + struct Char_Parser_Helper + { + static std::string str_from_ll(long long val) + { + // little SFINAE trick to avoid base class + return Char_Parser_Helper::u8str_from_ll(val); + } + }; + } + + + template + class ChaiScript_Parser final : public ChaiScript_Parser_Base { + void *get_tracer_ptr() override { + return &m_tracer; + } + + std::size_t m_current_parse_depth = 0; + + struct Depth_Counter + { + static const auto max_depth = Parse_Depth; + Depth_Counter(ChaiScript_Parser *t_parser) : parser(t_parser) + { + ++parser->m_current_parse_depth; + if (parser->m_current_parse_depth > max_depth) { + throw exception::eval_error("Maximum parse depth exceeded", + File_Position(parser->m_position.line, parser->m_position.col), *(parser->m_filename)); + } + } + + ~Depth_Counter() noexcept + { + --parser->m_current_parse_depth; + } + + ChaiScript_Parser *parser; + }; + + static std::array, detail::max_alphabet> build_alphabet() + { + std::array, detail::max_alphabet> alphabet; + + for (auto &alpha : alphabet) { + alpha.fill(false); + } + + alphabet[detail::symbol_alphabet][static_cast('?')]=true; + alphabet[detail::symbol_alphabet][static_cast('+')]=true; + alphabet[detail::symbol_alphabet][static_cast('-')]=true; + alphabet[detail::symbol_alphabet][static_cast('*')]=true; + alphabet[detail::symbol_alphabet][static_cast('/')]=true; + alphabet[detail::symbol_alphabet][static_cast('|')]=true; + alphabet[detail::symbol_alphabet][static_cast('&')]=true; + alphabet[detail::symbol_alphabet][static_cast('^')]=true; + alphabet[detail::symbol_alphabet][static_cast('=')]=true; + alphabet[detail::symbol_alphabet][static_cast('.')]=true; + alphabet[detail::symbol_alphabet][static_cast('<')]=true; + alphabet[detail::symbol_alphabet][static_cast('>')]=true; + + for ( size_t c = 'a' ; c <= 'z' ; ++c ) { alphabet[detail::keyword_alphabet][c]=true; } + for ( size_t c = 'A' ; c <= 'Z' ; ++c ) { alphabet[detail::keyword_alphabet][c]=true; } + for ( size_t c = '0' ; c <= '9' ; ++c ) { alphabet[detail::keyword_alphabet][c]=true; } + alphabet[detail::keyword_alphabet][static_cast('_')]=true; + + for ( size_t c = '0' ; c <= '9' ; ++c ) { alphabet[detail::int_alphabet][c]=true; } + for ( size_t c = '0' ; c <= '9' ; ++c ) { alphabet[detail::float_alphabet][c]=true; } + alphabet[detail::float_alphabet][static_cast('.')]=true; + + for ( size_t c = '0' ; c <= '9' ; ++c ) { alphabet[detail::hex_alphabet][c]=true; } + for ( size_t c = 'a' ; c <= 'f' ; ++c ) { alphabet[detail::hex_alphabet][c]=true; } + for ( size_t c = 'A' ; c <= 'F' ; ++c ) { alphabet[detail::hex_alphabet][c]=true; } + + alphabet[detail::x_alphabet][static_cast('x')]=true; + alphabet[detail::x_alphabet][static_cast('X')]=true; + + for ( size_t c = '0' ; c <= '1' ; ++c ) { alphabet[detail::bin_alphabet][c]=true; } + alphabet[detail::b_alphabet][static_cast('b')]=true; + alphabet[detail::b_alphabet][static_cast('B')]=true; + + for ( size_t c = 'a' ; c <= 'z' ; ++c ) { alphabet[detail::id_alphabet][c]=true; } + for ( size_t c = 'A' ; c <= 'Z' ; ++c ) { alphabet[detail::id_alphabet][c]=true; } + alphabet[detail::id_alphabet][static_cast('_')] = true; + + alphabet[detail::white_alphabet][static_cast(' ')]=true; + alphabet[detail::white_alphabet][static_cast('\t')]=true; + + alphabet[detail::int_suffix_alphabet][static_cast('l')] = true; + alphabet[detail::int_suffix_alphabet][static_cast('L')] = true; + alphabet[detail::int_suffix_alphabet][static_cast('u')] = true; + alphabet[detail::int_suffix_alphabet][static_cast('U')] = true; + + alphabet[detail::float_suffix_alphabet][static_cast('l')] = true; + alphabet[detail::float_suffix_alphabet][static_cast('L')] = true; + alphabet[detail::float_suffix_alphabet][static_cast('f')] = true; + alphabet[detail::float_suffix_alphabet][static_cast('F')] = true; + + return alphabet; + } + + static const std::array, detail::max_alphabet> &create_alphabet() + { + static const auto alpha = build_alphabet(); + return alpha; + } + + + static const std::vector> &create_operator_matches() { + static const std::vector> operator_matches { + {"?"}, + {"||"}, + {"&&"}, + {"|"}, + {"^"}, + {"&"}, + {"==", "!="}, + {"<", "<=", ">", ">="}, + {"<<", ">>"}, + //We share precedence here but then separate them later + {"+", "-"}, + {"*", "/", "%"}, + {"++", "--", "-", "+", "!", "~"} + }; + + return operator_matches; + } + + + static const std::array &create_operators() { + static const std::array operators = { { + Operator_Precidence::Ternary_Cond, + Operator_Precidence::Logical_Or, + Operator_Precidence::Logical_And, + Operator_Precidence::Bitwise_Or, + Operator_Precidence::Bitwise_Xor, + Operator_Precidence::Bitwise_And, + Operator_Precidence::Equality, + Operator_Precidence::Comparison, + Operator_Precidence::Shift, + Operator_Precidence::Addition, + Operator_Precidence::Multiplication, + Operator_Precidence::Prefix + } }; + return operators; + } + + static const utility::Static_String &multiline_comment_end() + { + static const utility::Static_String s("*/"); + return s; + } + + static const utility::Static_String &multiline_comment_begin() + { + static const utility::Static_String s("/*"); + return s; + } + + static const utility::Static_String &singleline_comment() + { + static const utility::Static_String s("//"); + return s; + } + + static const utility::Static_String &annotation() + { + static const utility::Static_String s("#"); + return s; + } + + static const utility::Static_String &cr_lf() + { + static const utility::Static_String s("\r\n"); + return s; + } + + const std::array, detail::max_alphabet> &m_alphabet = create_alphabet(); + const std::vector> &m_operator_matches = create_operator_matches(); + const std::array &m_operators = create_operators(); + + std::shared_ptr m_filename; + std::vector> m_match_stack; + + + struct Position + { + Position() = default; + + Position(std::string::const_iterator t_pos, std::string::const_iterator t_end) + : line(1), col(1), m_pos(t_pos), m_end(t_end), m_last_col(1) + { + } + + static std::string str(const Position &t_begin, const Position &t_end) { + return std::string(t_begin.m_pos, t_end.m_pos); + } + + Position &operator++() { + if (m_pos != m_end) { + if (*m_pos == '\n') { + ++line; + m_last_col = std::exchange(col, 1); + } else { + ++col; + } + + ++m_pos; + } + return *this; + } + + Position &operator--() { + --m_pos; + if (*m_pos == '\n') { + --line; + col = m_last_col; + } else { + --col; + } + return *this; + } + + Position &operator+=(size_t t_distance) { + *this = (*this) + t_distance; + return *this; + } + + Position operator+(size_t t_distance) const { + Position ret(*this); + for (size_t i = 0; i < t_distance; ++i) { + ++ret; + } + return ret; + } + + Position &operator-=(size_t t_distance) { + *this = (*this) - t_distance; + return *this; + } + + Position operator-(size_t t_distance) const { + Position ret(*this); + for (size_t i = 0; i < t_distance; ++i) { + --ret; + } + return ret; + } + + bool operator==(const Position &t_rhs) const { + return m_pos == t_rhs.m_pos; + } + + bool operator!=(const Position &t_rhs) const { + return m_pos != t_rhs.m_pos; + } + + bool has_more() const { + return m_pos != m_end; + } + + size_t remaining() const { + return static_cast(std::distance(m_pos, m_end)); + } + + const char& operator*() const { + if (m_pos == m_end) { + static const char ktmp ='\0'; + return ktmp; + } else { + return *m_pos; + } + } + + int line = -1; + int col = -1; + + private: + std::string::const_iterator m_pos; + std::string::const_iterator m_end; + int m_last_col = -1; + }; + + Position m_position; + + Tracer m_tracer; + Optimizer m_optimizer; + + void validate_object_name(const std::string &name) const + { + if (!Name_Validator::valid_object_name(name)) { + throw exception::eval_error("Invalid Object Name: " + name, File_Position(m_position.line, m_position.col), *m_filename); + } + } + + public: + explicit ChaiScript_Parser(Tracer tracer = Tracer(), Optimizer optimizer=Optimizer()) + : m_tracer(std::move(tracer)), + m_optimizer(std::move(optimizer)) + { + m_match_stack.reserve(2); + } + + Tracer &get_tracer() + { + return m_tracer; + } + + Optimizer &get_optimizer() + { + return m_optimizer; + } + + ChaiScript_Parser(const ChaiScript_Parser &) = delete; + ChaiScript_Parser &operator=(const ChaiScript_Parser &) = delete; + ChaiScript_Parser(ChaiScript_Parser &&) = default; + ChaiScript_Parser &operator=(ChaiScript_Parser &&) = delete; + + /// test a char in an m_alphabet + bool char_in_alphabet(char c, detail::Alphabet a) const { return m_alphabet[a][static_cast(c)]; } + + /// Prints the parsed ast_nodes as a tree + void debug_print(const AST_Node &t, std::string prepend = "") const override { + std::cout << prepend << "(" << ast_node_type_to_string(t.identifier) << ") " << t.text << " : " << t.start().line << ", " << t.start().column << '\n'; + for (const auto &node : t.get_children()) { + debug_print(node.get(), prepend + " "); + } + } + + + /// Helper function that collects ast_nodes from a starting position to the top of the stack into a new AST node + template + void build_match(size_t t_match_start, std::string t_text = "") { + bool is_deep = false; + + Parse_Location filepos = [&]()->Parse_Location{ + //so we want to take everything to the right of this and make them children + if (t_match_start != m_match_stack.size()) { + is_deep = true; + return Parse_Location( + m_filename, + m_match_stack[t_match_start]->location.start.line, + m_match_stack[t_match_start]->location.start.column, + m_position.line, + m_position.col + ); + } else { + return Parse_Location( + m_filename, + m_position.line, + m_position.col, + m_position.line, + m_position.col + ); + } + }(); + + std::vector> new_children; + + if (is_deep) { + new_children.assign(std::make_move_iterator(m_match_stack.begin() + static_cast(t_match_start)), + std::make_move_iterator(m_match_stack.end())); + m_match_stack.erase(m_match_stack.begin() + static_cast(t_match_start), m_match_stack.end()); + } + + /// \todo fix the fact that a successful match that captured no ast_nodes doesn't have any real start position + m_match_stack.push_back( + m_optimizer.optimize( + chaiscript::make_unique, NodeType>( + std::move(t_text), + std::move(filepos), + std::move(new_children))) + ); + } + + + /// Reads a symbol group from input if it matches the parameter, without skipping initial whitespace + inline auto Symbol_(const utility::Static_String &sym) + { + const auto len = sym.size(); + if (m_position.remaining() >= len) { + const char *file_pos = &(*m_position); + for (size_t pos = 0; pos < len; ++pos) + { + if (sym.c_str()[pos] != file_pos[pos]) { return false; } + } + m_position += len; + return true; + } + return false; + } + + /// Skips any multi-line or single-line comment + bool SkipComment() { + if (Symbol_(multiline_comment_begin())) { + while (m_position.has_more()) { + if (Symbol_(multiline_comment_end())) { + break; + } else if (!Eol_()) { + ++m_position; + } + } + return true; + } else if (Symbol_(singleline_comment())) { + while (m_position.has_more()) { + if (Symbol_(cr_lf())) { + m_position -= 2; + break; + } else if (Char_('\n')) { + --m_position; + break; + } else { + ++m_position; + } + } + return true; + } else if (Symbol_(annotation())) { + while (m_position.has_more()) { + if (Symbol_(cr_lf())) { + m_position -= 2; + break; + } else if (Char_('\n')) { + --m_position; + break; + } else { + ++m_position; + } + } + return true; + } + return false; + } + + + /// Skips ChaiScript whitespace, which means space and tab, but not cr/lf + /// jespada: Modified SkipWS to skip optionally CR ('\n') and/or LF+CR ("\r\n") + /// AlekMosingiewicz: Added exception when illegal character detected + bool SkipWS(bool skip_cr=false) { + bool retval = false; + + while (m_position.has_more()) { + if(static_cast(*m_position) > 0x7e) { + throw exception::eval_error("Illegal character", File_Position(m_position.line, m_position.col), *m_filename); + } + auto end_line = (*m_position != 0) && ((*m_position == '\n') || (*m_position == '\r' && *(m_position+1) == '\n')); + + if ( char_in_alphabet(*m_position,detail::white_alphabet) || (skip_cr && end_line)) { + + if(end_line) { + if(*m_position == '\r') { + // discards lf + ++m_position; + } + } + + ++m_position; + + retval = true; + } + else if (SkipComment()) { + retval = true; + } else { + break; + } + } + return retval; + } + + /// Reads the optional exponent (scientific notation) and suffix for a Float + bool read_exponent_and_suffix() { + // Support a form of scientific notation: 1e-5, 35.5E+8, 0.01e19 + if (m_position.has_more() && (std::tolower(*m_position) == 'e')) { + ++m_position; + if (m_position.has_more() && ((*m_position == '-') || (*m_position == '+'))) { + ++m_position; + } + auto exponent_pos = m_position; + while (m_position.has_more() && char_in_alphabet(*m_position,detail::int_alphabet) ) { + ++m_position; + } + if (m_position == exponent_pos) { + // Require at least one digit after the exponent + return false; + } + } + + // Parse optional float suffix + while (m_position.has_more() && char_in_alphabet(*m_position, detail::float_suffix_alphabet)) + { + ++m_position; + } + + return true; + } + + + /// Reads a floating point value from input, without skipping initial whitespace + bool Float_() { + if (m_position.has_more() && char_in_alphabet(*m_position,detail::float_alphabet) ) { + while (m_position.has_more() && char_in_alphabet(*m_position,detail::int_alphabet) ) { + ++m_position; + } + + if (m_position.has_more() && (std::tolower(*m_position) == 'e')) { + // The exponent is valid even without any decimal in the Float (1e8, 3e-15) + return read_exponent_and_suffix(); + } + else if (m_position.has_more() && (*m_position == '.')) { + ++m_position; + if (m_position.has_more() && char_in_alphabet(*m_position,detail::int_alphabet)) { + while (m_position.has_more() && char_in_alphabet(*m_position,detail::int_alphabet) ) { + ++m_position; + } + + // After any decimal digits, support an optional exponent (3.7e3) + return read_exponent_and_suffix(); + } else { + --m_position; + } + } + } + return false; + } + + /// Reads a hex value from input, without skipping initial whitespace + bool Hex_() { + if (m_position.has_more() && (*m_position == '0')) { + ++m_position; + + if (m_position.has_more() && char_in_alphabet(*m_position, detail::x_alphabet) ) { + ++m_position; + if (m_position.has_more() && char_in_alphabet(*m_position, detail::hex_alphabet)) { + while (m_position.has_more() && char_in_alphabet(*m_position, detail::hex_alphabet) ) { + ++m_position; + } + while (m_position.has_more() && char_in_alphabet(*m_position, detail::int_suffix_alphabet)) + { + ++m_position; + } + + return true; + } + else { + --m_position; + } + } + else { + --m_position; + } + } + + return false; + } + + /// Reads an integer suffix + void IntSuffix_() { + while (m_position.has_more() && char_in_alphabet(*m_position, detail::int_suffix_alphabet)) + { + ++m_position; + } + } + + /// Reads a binary value from input, without skipping initial whitespace + bool Binary_() { + if (m_position.has_more() && (*m_position == '0')) { + ++m_position; + + if (m_position.has_more() && char_in_alphabet(*m_position, detail::b_alphabet) ) { + ++m_position; + if (m_position.has_more() && char_in_alphabet(*m_position, detail::bin_alphabet) ) { + while (m_position.has_more() && char_in_alphabet(*m_position, detail::bin_alphabet) ) { + ++m_position; + } + return true; + } else { + --m_position; + } + } else { + --m_position; + } + } + + return false; + } + + /// Parses a floating point value and returns a Boxed_Value representation of it + static Boxed_Value buildFloat(const std::string &t_val) + { + bool float_ = false; + bool long_ = false; + + auto i = t_val.size(); + + for (; i > 0; --i) + { + char val = t_val[i-1]; + + if (val == 'f' || val == 'F') + { + float_ = true; + } else if (val == 'l' || val == 'L') { + long_ = true; + } else { + break; + } + } + + if (float_) + { + return const_var(parse_num(t_val.substr(0,i))); + } else if (long_) { + return const_var(parse_num(t_val.substr(0,i))); + } else { + return const_var(parse_num(t_val.substr(0,i))); + } + } + + + + static Boxed_Value buildInt(const int base, const std::string &t_val, const bool prefixed) + { + bool unsigned_ = false; + bool long_ = false; + bool longlong_ = false; + + auto i = t_val.size(); + + for (; i > 0; --i) + { + const char val = t_val[i-1]; + + if (val == 'u' || val == 'U') + { + unsigned_ = true; + } else if (val == 'l' || val == 'L') { + if (long_) + { + longlong_ = true; + } + + long_ = true; + } else { + break; + } + } + + const auto val = prefixed?std::string(t_val.begin()+2,t_val.end()):t_val; + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" + +#ifdef CHAISCRIPT_CLANG +#pragma GCC diagnostic ignored "-Wtautological-compare" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#endif + + try { + auto u = std::stoll(val,nullptr,base); + + + if (!unsigned_ && !long_ && u >= std::numeric_limits::min() && u <= std::numeric_limits::max()) { + return const_var(static_cast(u)); + } else if ((unsigned_ || base != 10) && !long_ && u >= std::numeric_limits::min() && u <= std::numeric_limits::max()) { + return const_var(static_cast(u)); + } else if (!unsigned_ && !longlong_ && u >= std::numeric_limits::min() && u <= std::numeric_limits::max()) { + return const_var(static_cast(u)); + } else if ((unsigned_ || base != 10) && !longlong_ + + && u >= std::numeric_limits::min() + && u <= std::numeric_limits::max()) { + return const_var(static_cast(u)); + } else if (!unsigned_ && u >= std::numeric_limits::min() && u <= std::numeric_limits::max()) { + return const_var(static_cast(u)); + } else { + return const_var(static_cast(u)); + } + + } catch (const std::out_of_range &) { + // too big to be signed + try { + auto u = std::stoull(val,nullptr,base); + + if (!longlong_ && u >= std::numeric_limits::min() && u <= std::numeric_limits::max()) { + return const_var(static_cast(u)); + } else { + return const_var(static_cast(u)); + } + } catch (const std::out_of_range &) { + // it's just simply too big + return const_var(std::numeric_limits::max()); + } + } + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + } + + template + std::unique_ptr> make_node(std::string t_match, const int t_prev_line, const int t_prev_col, Param && ...param) + { + return chaiscript::make_unique, T>(std::move(t_match), Parse_Location(m_filename, t_prev_line, t_prev_col, m_position.line, m_position.col), std::forward(param)...); + } + + /// Reads a number from the input, detecting if it's an integer or floating point + bool Num() { + SkipWS(); + + const auto start = m_position; + if (m_position.has_more() && char_in_alphabet(*m_position, detail::float_alphabet) ) { + try { + if (Hex_()) { + auto match = Position::str(start, m_position); + auto bv = buildInt(16, match, true); + m_match_stack.emplace_back(make_node>(std::move(match), start.line, start.col, std::move(bv))); + return true; + } + + if (Binary_()) { + auto match = Position::str(start, m_position); + auto bv = buildInt(2, match, true); + m_match_stack.push_back(make_node>(std::move(match), start.line, start.col, std::move(bv))); + return true; + } + if (Float_()) { + auto match = Position::str(start, m_position); + auto bv = buildFloat(match); + m_match_stack.push_back(make_node>(std::move(match), start.line, start.col, std::move(bv))); + return true; + } + else { + IntSuffix_(); + auto match = Position::str(start, m_position); + if (!match.empty() && (match[0] == '0')) { + auto bv = buildInt(8, match, false); + m_match_stack.push_back(make_node>(std::move(match), start.line, start.col, std::move(bv))); + } + else if (!match.empty()) { + auto bv = buildInt(10, match, false); + m_match_stack.push_back(make_node>(std::move(match), start.line, start.col, std::move(bv))); + } else { + return false; + } + return true; + } + } catch (const std::invalid_argument &) { + // error parsing number passed in to buildFloat/buildInt + return false; + } + } + else { + return false; + } + } + + /// Reads an identifier from input which conforms to C's identifier naming conventions, without skipping initial whitespace + bool Id_() { + if (m_position.has_more() && char_in_alphabet(*m_position, detail::id_alphabet)) { + while (m_position.has_more() && char_in_alphabet(*m_position, detail::keyword_alphabet) ) { + ++m_position; + } + + return true; + } else if (m_position.has_more() && (*m_position == '`')) { + ++m_position; + const auto start = m_position; + + while (m_position.has_more() && (*m_position != '`')) { + if (Eol()) { + throw exception::eval_error("Carriage return in identifier literal", File_Position(m_position.line, m_position.col), *m_filename); + } + else { + ++m_position; + } + } + + if (start == m_position) { + throw exception::eval_error("Missing contents of identifier literal", File_Position(m_position.line, m_position.col), *m_filename); + } + else if (!m_position.has_more()) { + throw exception::eval_error("Incomplete identifier literal", File_Position(m_position.line, m_position.col), *m_filename); + } + + ++m_position; + + return true; + } + return false; + } + + /// Reads (and potentially captures) an identifier from input + bool Id(const bool validate) { + SkipWS(); + + const auto start = m_position; + if (Id_()) { + + auto text = Position::str(start, m_position); + const auto text_hash = utility::fnv1a_32(text.c_str()); + + if (validate) { + validate_object_name(text); + } + +#ifdef CHAISCRIPT_MSVC +#pragma warning(push) +#pragma warning(disable : 4307) +#endif + + switch (text_hash) { + case utility::fnv1a_32("true"): { + m_match_stack.push_back(make_node>(std::move(text), start.line, start.col, const_var(true))); + } break; + case utility::fnv1a_32("false"): { + m_match_stack.push_back(make_node>(std::move(text), start.line, start.col, const_var(false))); + } break; + case utility::fnv1a_32("Infinity"): { + m_match_stack.push_back(make_node>(std::move(text), start.line, start.col, + const_var(std::numeric_limits::infinity()))); + } break; + case utility::fnv1a_32("NaN"): { + m_match_stack.push_back(make_node>(std::move(text), start.line, start.col, + const_var(std::numeric_limits::quiet_NaN()))); + } break; + case utility::fnv1a_32("__LINE__"): { + m_match_stack.push_back(make_node>(std::move(text), start.line, start.col, + const_var(start.line))); + } break; + case utility::fnv1a_32("__FILE__"): { + m_match_stack.push_back(make_node>(std::move(text), start.line, start.col, + const_var(m_filename))); + } break; + case utility::fnv1a_32("__FUNC__"): { + std::string fun_name = "NOT_IN_FUNCTION"; + for (size_t idx = m_match_stack.empty() ? 0 : m_match_stack.size() - 1; idx > 0; --idx) + { + if (m_match_stack[idx-1]->identifier == AST_Node_Type::Id + && m_match_stack[idx-0]->identifier == AST_Node_Type::Arg_List) { + fun_name = m_match_stack[idx-1]->text; + } + } + + m_match_stack.push_back(make_node>(std::move(text), start.line, start.col, + const_var(fun_name))); + } break; + case utility::fnv1a_32("__CLASS__"): { + std::string fun_name = "NOT_IN_CLASS"; + for (size_t idx = m_match_stack.empty() ? 0 : m_match_stack.size() - 1; idx > 1; --idx) + { + if (m_match_stack[idx-2]->identifier == AST_Node_Type::Id + && m_match_stack[idx-1]->identifier == AST_Node_Type::Id + && m_match_stack[idx-0]->identifier == AST_Node_Type::Arg_List) { + fun_name = m_match_stack[idx-2]->text; + } + } + + m_match_stack.push_back(make_node>(std::move(text), start.line, start.col, + const_var(fun_name))); + } break; + case utility::fnv1a_32("_"): { + m_match_stack.push_back(make_node>(std::move(text), start.line, start.col, + Boxed_Value(std::make_shared()))); + } break; + default: { + std::string val = std::move(text); + if (*start == '`') { + // 'escaped' literal, like an operator name + val = Position::str(start+1, m_position-1); + } + m_match_stack.push_back(make_node>(val, start.line, start.col)); + } break; + } + +#ifdef CHAISCRIPT_MSVC +#pragma warning(pop) +#endif + + + return true; + } else { + return false; + } + } + + /// Reads an argument from input + bool Arg(const bool t_type_allowed = true) { + const auto prev_stack_top = m_match_stack.size(); + SkipWS(); + + if (!Id(true)) { + return false; + } + + SkipWS(); + + if (t_type_allowed) { + Id(true); + } + + build_match>(prev_stack_top); + + return true; + } + + + + /// Reads a quoted string from input, without skipping initial whitespace + bool Quoted_String_() { + if (m_position.has_more() && (*m_position == '\"')) { + char prev_char = *m_position; + ++m_position; + + int in_interpolation = 0; + bool in_quote = false; + + while (m_position.has_more() && ((*m_position != '\"') || (in_interpolation > 0) || (prev_char == '\\'))) { + + if (!Eol_()) { + if (prev_char == '$' && *m_position == '{') { + ++in_interpolation; + } else if (prev_char != '\\' && *m_position == '"') { + in_quote = !in_quote; + } else if (*m_position == '}' && !in_quote) { + --in_interpolation; + } + + if (prev_char == '\\') { + prev_char = 0; + } else { + prev_char = *m_position; + } + ++m_position; + } + } + + if (m_position.has_more()) { + ++m_position; + } else { + throw exception::eval_error("Unclosed quoted string", File_Position(m_position.line, m_position.col), *m_filename); + } + + return true; + } + return false; + } + + template + struct Char_Parser + { + string_type &match; + typedef typename string_type::value_type char_type; + bool is_escaped = false; + bool is_interpolated = false; + bool saw_interpolation_marker = false; + bool is_octal = false; + bool is_hex = false; + std::size_t unicode_size = 0; + const bool interpolation_allowed; + + string_type octal_matches; + string_type hex_matches; + + Char_Parser(string_type &t_match, const bool t_interpolation_allowed) + : match(t_match), + interpolation_allowed(t_interpolation_allowed) + { + } + + Char_Parser &operator=(const Char_Parser &) = delete; + + ~Char_Parser(){ + try { + if (is_octal) { + process_octal(); + } + + if (is_hex) { + process_hex(); + } + + if (unicode_size > 0) { + process_unicode(); + } + } catch (const std::invalid_argument &) { + } catch (const exception::eval_error &) { + // Something happened with parsing, we'll catch it later? + } + } + + void process_hex() + { + if (!hex_matches.empty()) { + auto val = stoll(hex_matches, nullptr, 16); + match.push_back(char_type(val)); + } + hex_matches.clear(); + is_escaped = false; + is_hex = false; + } + + + void process_octal() + { + if (!octal_matches.empty()) { + auto val = stoll(octal_matches, nullptr, 8); + match.push_back(char_type(val)); + } + octal_matches.clear(); + is_escaped = false; + is_octal = false; + } + + + void process_unicode() + { + const auto ch = static_cast(std::stoi(hex_matches, nullptr, 16)); + const auto match_size = hex_matches.size(); + hex_matches.clear(); + is_escaped = false; + const auto u_size = unicode_size; + unicode_size = 0; + + char buf[4]; + if (u_size != match_size) { + throw exception::eval_error("Incomplete unicode escape sequence"); + } + if (u_size == 4 && ch >= 0xD800 && ch <= 0xDFFF) { + throw exception::eval_error("Invalid 16 bit universal character"); + } + + + if (ch < 0x80) { + match += static_cast(ch); + } else if (ch < 0x800) { + buf[0] = static_cast(0xC0 | (ch >> 6)); + buf[1] = static_cast(0x80 | (ch & 0x3F)); + match.append(buf, 2); + } else if (ch < 0x10000) { + buf[0] = static_cast(0xE0 | (ch >> 12)); + buf[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + buf[2] = static_cast(0x80 | (ch & 0x3F)); + match.append(buf, 3); + } else if (ch < 0x200000) { + buf[0] = static_cast(0xF0 | (ch >> 18)); + buf[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); + buf[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + buf[3] = static_cast(0x80 | (ch & 0x3F)); + match.append(buf, 4); + } else { + // this must be an invalid escape sequence? + throw exception::eval_error("Invalid 32 bit universal character"); + } + } + + void parse(const char_type t_char, const int line, const int col, const std::string &filename) { + const bool is_octal_char = t_char >= '0' && t_char <= '7'; + + const bool is_hex_char = (t_char >= '0' && t_char <= '9') + || (t_char >= 'a' && t_char <= 'f') + || (t_char >= 'A' && t_char <= 'F'); + + if (is_octal) { + if (is_octal_char) { + octal_matches.push_back(t_char); + + if (octal_matches.size() == 3) { + process_octal(); + } + return; + } else { + process_octal(); + } + } else if (is_hex) { + if (is_hex_char) { + hex_matches.push_back(t_char); + + if (hex_matches.size() == 2*sizeof(char_type)) { + // This rule differs from the C/C++ standard, but ChaiScript + // does not offer the same workaround options, and having + // hexadecimal sequences longer than can fit into the char + // type is undefined behavior anyway. + process_hex(); + } + return; + } else { + process_hex(); + } + } else if (unicode_size > 0) { + if (is_hex_char) { + hex_matches.push_back(t_char); + + if(hex_matches.size() == unicode_size) { + // Format is specified to be 'slash'uABCD + // on collecting from A to D do parsing + process_unicode(); + } + return; + } else { + // Not a unicode anymore, try parsing any way + // May be someone used 'slash'uAA only + process_unicode(); + } + } + + if (t_char == '\\') { + if (is_escaped) { + match.push_back('\\'); + is_escaped = false; + } else { + is_escaped = true; + } + } else { + if (is_escaped) { + if (is_octal_char) { + is_octal = true; + octal_matches.push_back(t_char); + } else if (t_char == 'x') { + is_hex = true; + } else if (t_char == 'u') { + unicode_size = 4; + } else if (t_char == 'U') { + unicode_size = 8; + } else { + switch (t_char) { + case ('\'') : match.push_back('\''); break; + case ('\"') : match.push_back('\"'); break; + case ('?') : match.push_back('?'); break; + case ('a') : match.push_back('\a'); break; + case ('b') : match.push_back('\b'); break; + case ('f') : match.push_back('\f'); break; + case ('n') : match.push_back('\n'); break; + case ('r') : match.push_back('\r'); break; + case ('t') : match.push_back('\t'); break; + case ('v') : match.push_back('\v'); break; + case ('$') : match.push_back('$'); break; + default: throw exception::eval_error("Unknown escaped sequence in string", File_Position(line, col), filename); + } + is_escaped = false; + } + } else if (interpolation_allowed && t_char == '$') { + saw_interpolation_marker = true; + } else { + match.push_back(t_char); + } + } + } + + }; + + + /// Reads (and potentially captures) a quoted string from input. Translates escaped sequences. + bool Quoted_String() { + Depth_Counter dc{this}; + SkipWS(); + + const auto start = m_position; + + if (Quoted_String_()) { + std::string match; + const auto prev_stack_top = m_match_stack.size(); + + bool is_interpolated = [&]()->bool { + Char_Parser cparser(match, true); + + + auto s = start + 1, end = m_position - 1; + + while (s != end) { + if (cparser.saw_interpolation_marker) { + if (*s == '{') { + //We've found an interpolation point + + m_match_stack.push_back(make_node>(match, start.line, start.col, const_var(match))); + + if (cparser.is_interpolated) { + //If we've seen previous interpolation, add on instead of making a new one + build_match>(prev_stack_top, "+"); + } + + //We've finished with the part of the string up to this point, so clear it + match.clear(); + + std::string eval_match; + + ++s; + while ((s != end) && (*s != '}')) { + eval_match.push_back(*s); + ++s; + } + + if (*s == '}') { + cparser.is_interpolated = true; + ++s; + + const auto tostr_stack_top = m_match_stack.size(); + + m_match_stack.push_back(make_node>("to_string", start.line, start.col)); + + const auto ev_stack_top = m_match_stack.size(); + + try { + m_match_stack.push_back(parse_instr_eval(eval_match)); + } catch (const exception::eval_error &e) { + throw exception::eval_error(e.what(), File_Position(start.line, start.col), *m_filename); + } + + build_match>(ev_stack_top); + build_match>(tostr_stack_top); + build_match>(prev_stack_top, "+"); + } else { + throw exception::eval_error("Unclosed in-string eval", File_Position(start.line, start.col), *m_filename); + } + } else { + match.push_back('$'); + } + cparser.saw_interpolation_marker = false; + } else { + cparser.parse(*s, start.line, start.col, *m_filename); + + ++s; + } + } + + return cparser.is_interpolated; + }(); + + m_match_stack.push_back(make_node>(match, start.line, start.col, const_var(match))); + + if (is_interpolated) { + build_match>(prev_stack_top, "+"); + } + + return true; + } else { + return false; + } + } + + /// Reads a character group from input, without skipping initial whitespace + bool Single_Quoted_String_() { + bool retval = false; + if (m_position.has_more() && (*m_position == '\'')) { + retval = true; + char prev_char = *m_position; + ++m_position; + + while (m_position.has_more() && ((*m_position != '\'') || (prev_char == '\\'))) { + if (!Eol_()) { + if (prev_char == '\\') { + prev_char = 0; + } else { + prev_char = *m_position; + } + ++m_position; + } + } + + if (m_position.has_more()) { + ++m_position; + } else { + throw exception::eval_error("Unclosed single-quoted string", File_Position(m_position.line, m_position.col), *m_filename); + } + } + return retval; + } + + /// Reads (and potentially captures) a char group from input. Translates escaped sequences. + bool Single_Quoted_String() { + Depth_Counter dc{this}; + SkipWS(); + + const auto start = m_position; + if (Single_Quoted_String_()) { + std::string match; + + { + // scope for cparser destructor + Char_Parser cparser(match, false); + + for (auto s = start + 1, end = m_position - 1; s != end; ++s) { + cparser.parse(*s, start.line, start.col, *m_filename); + } + } + + if (match.size() != 1) { + throw exception::eval_error("Single-quoted strings must be 1 character long", File_Position(m_position.line, m_position.col), *m_filename); + } + + m_match_stack.push_back(make_node>(match, start.line, start.col, const_var(char(match.at(0))))); + return true; + } + else { + return false; + } + } + + /// Reads a char from input if it matches the parameter, without skipping initial whitespace + bool Char_(const char c) { + if (m_position.has_more() && (*m_position == c)) { + ++m_position; + return true; + } else { + return false; + } + } + + /// Reads (and potentially captures) a char from input if it matches the parameter + bool Char(const char t_c) { + Depth_Counter dc{this}; + SkipWS(); + return Char_(t_c); + } + + /// Reads a string from input if it matches the parameter, without skipping initial whitespace + bool Keyword_(const utility::Static_String &t_s) { + const auto len = t_s.size(); + if (m_position.remaining() >= len) { + auto tmp = m_position; + for (size_t i = 0; tmp.has_more() && i < len; ++i) { + if (*tmp != t_s.c_str()[i]) { + return false; + } + ++tmp; + } + m_position = tmp; + return true; + } + + return false; + } + + /// Reads (and potentially captures) a string from input if it matches the parameter + bool Keyword(const utility::Static_String &t_s) { + Depth_Counter dc{this}; + SkipWS(); + const auto start = m_position; + bool retval = Keyword_(t_s); + // ignore substring matches + if ( retval && m_position.has_more() && char_in_alphabet(*m_position, detail::keyword_alphabet) ) { + m_position = start; + retval = false; + } + + return retval; + } + + bool is_operator(const std::string &t_s) const { + return std::any_of(m_operator_matches.begin(), m_operator_matches.end(), + [t_s](const std::vector &opers) { + return std::any_of(opers.begin(), opers.end(), + [t_s](const utility::Static_String &s) { + return t_s == s.c_str(); + }); + }); + } + + /// Reads (and potentially captures) a symbol group from input if it matches the parameter + bool Symbol(const utility::Static_String &t_s, const bool t_disallow_prevention=false) { + Depth_Counter dc{this}; + SkipWS(); + const auto start = m_position; + bool retval = Symbol_(t_s); + + // ignore substring matches + if (retval && m_position.has_more() && (t_disallow_prevention == false) && char_in_alphabet(*m_position,detail::symbol_alphabet)) { + if (*m_position != '=' && is_operator(Position::str(start, m_position)) && !is_operator(Position::str(start, m_position+1))) { + // don't throw this away, it's a good match and the next is not + } else { + m_position = start; + retval = false; + } + } + + return retval; + } + + /// Reads an end-of-line group from input, without skipping initial whitespace + bool Eol_(const bool t_eos = false) { + bool retval = false; + + if (m_position.has_more() && (Symbol_(cr_lf()) || Char_('\n'))) { + retval = true; + //++m_position.line; + m_position.col = 1; + } else if (m_position.has_more() && !t_eos && Char_(';')) { + retval = true; + } + + return retval; + } + + /// Reads until the end of the current statement + bool Eos() { + Depth_Counter dc{this}; + SkipWS(); + + return Eol_(true); + } + + /// Reads (and potentially captures) an end-of-line group from input + bool Eol() { + Depth_Counter dc{this}; + SkipWS(); + + return Eol_(); + } + + /// Reads a comma-separated list of values from input. Id's only, no types allowed + bool Id_Arg_List() { + Depth_Counter dc{this}; + SkipWS(true); + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Arg(false)) { + retval = true; + while (Eol()) {} + + while (Char(',')) { + while (Eol()) {} + if (!Arg(false)) { + throw exception::eval_error("Unexpected value in parameter list", File_Position(m_position.line, m_position.col), *m_filename); + } + } + } + build_match>(prev_stack_top); + + SkipWS(true); + + return retval; + } + + /// Reads a comma-separated list of values from input, for function declarations + bool Decl_Arg_List() { + Depth_Counter dc{this}; + SkipWS(true); + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Arg()) { + retval = true; + while (Eol()) {} + + while (Char(',')) { + while (Eol()) {} + if (!Arg()) { + throw exception::eval_error("Unexpected value in parameter list", File_Position(m_position.line, m_position.col), *m_filename); + } + } + } + build_match>(prev_stack_top); + + SkipWS(true); + + return retval; + } + + + /// Reads a comma-separated list of values from input + bool Arg_List() { + Depth_Counter dc{this}; + SkipWS(true); + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Equation()) { + retval = true; + while (Eol()) {} + while (Char(',')) { + while (Eol()) {} + if (!Equation()) { + throw exception::eval_error("Unexpected value in parameter list", File_Position(m_position.line, m_position.col), *m_filename); + } + } + } + + build_match>(prev_stack_top); + + SkipWS(true); + + return retval; + } + + /// Reads possible special container values, including ranges and map_pairs + bool Container_Arg_List() { + Depth_Counter dc{this}; + bool retval = false; + SkipWS(true); + + const auto prev_stack_top = m_match_stack.size(); + + if (Value_Range()) { + retval = true; + build_match>(prev_stack_top); + } else if (Map_Pair()) { + retval = true; + while (Eol()) {} + while (Char(',')) { + while (Eol()) {} + if (!Map_Pair()) { + throw exception::eval_error("Unexpected value in container", File_Position(m_position.line, m_position.col), *m_filename); + } + } + build_match>(prev_stack_top); + } else if (Operator()) { + retval = true; + while (Eol()) {} + while (Char(',')) { + while (Eol()) {} + if (!Operator()) { + throw exception::eval_error("Unexpected value in container", File_Position(m_position.line, m_position.col), *m_filename); + } + } + build_match>(prev_stack_top); + } + + SkipWS(true); + + return retval; + } + + /// Reads a lambda (anonymous function) from input + bool Lambda() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("fun")) { + retval = true; + + if (Char('[')) { + Id_Arg_List(); + if (!Char(']')) { + throw exception::eval_error("Incomplete anonymous function bind", File_Position(m_position.line, m_position.col), *m_filename); + } + } else { + // make sure we always have the same number of nodes + build_match>(prev_stack_top); + } + + if (Char('(')) { + Decl_Arg_List(); + if (!Char(')')) { + throw exception::eval_error("Incomplete anonymous function", File_Position(m_position.line, m_position.col), *m_filename); + } + } else { + throw exception::eval_error("Incomplete anonymous function", File_Position(m_position.line, m_position.col), *m_filename); + } + + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete anonymous function", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } + + return retval; + } + + /// Reads a function definition from input + bool Def(const bool t_class_context = false, const std::string &t_class_name = "") { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("def")) { + retval = true; + + if (t_class_context) { + m_match_stack.push_back(make_node>(t_class_name, m_position.line, m_position.col)); + } + + if (!Id(true)) { + throw exception::eval_error("Missing function name in definition", File_Position(m_position.line, m_position.col), *m_filename); + } + + bool is_method = false; + + if (Symbol("::")) { + //We're now a method + is_method = true; + + if (!Id(true)) { + throw exception::eval_error("Missing method name in definition", File_Position(m_position.line, m_position.col), *m_filename); + } + } + + if (Char('(')) { + Decl_Arg_List(); + if (!Char(')')) { + throw exception::eval_error("Incomplete function definition", File_Position(m_position.line, m_position.col), *m_filename); + } + } + + while (Eos()) {} + + if (Char(':')) { + if (!Operator()) { + throw exception::eval_error("Missing guard expression for function", File_Position(m_position.line, m_position.col), *m_filename); + } + } + + while (Eol()) {} + if (!Block()) { + throw exception::eval_error("Incomplete function definition", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (is_method || t_class_context) { + build_match>(prev_stack_top); + } else { + build_match>(prev_stack_top); + } + + } + + return retval; + } + + /// Reads a function definition from input + bool Try() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("try")) { + retval = true; + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'try' block", File_Position(m_position.line, m_position.col), *m_filename); + } + + bool has_matches = true; + while (has_matches) { + while (Eol()) {} + has_matches = false; + if (Keyword("catch")) { + const auto catch_stack_top = m_match_stack.size(); + if (Char('(')) { + if (!(Arg() && Char(')'))) { + throw exception::eval_error("Incomplete 'catch' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + if (Char(':')) { + if (!Operator()) { + throw exception::eval_error("Missing guard expression for catch", File_Position(m_position.line, m_position.col), *m_filename); + } + } + } + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'catch' block", File_Position(m_position.line, m_position.col), *m_filename); + } + build_match>(catch_stack_top); + has_matches = true; + } + } + while (Eol()) {} + if (Keyword("finally")) { + const auto finally_stack_top = m_match_stack.size(); + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'finally' block", File_Position(m_position.line, m_position.col), *m_filename); + } + build_match>(finally_stack_top); + } + + build_match>(prev_stack_top); + } + + return retval; + } + + /// Reads an if/else if/else block from input + bool If() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("if")) { + retval = true; + + if (!Char('(')) { + throw exception::eval_error("Incomplete 'if' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (!Equation()) { + throw exception::eval_error("Incomplete 'if' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + const bool is_if_init = Eol() && Equation(); + + if (!Char(')')) { + throw exception::eval_error("Incomplete 'if' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'if' block", File_Position(m_position.line, m_position.col), *m_filename); + } + + bool has_matches = true; + while (has_matches) { + while (Eol()) {} + has_matches = false; + if (Keyword("else")) { + if (If()) { + has_matches = true; + } else { + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'else' block", File_Position(m_position.line, m_position.col), *m_filename); + } + has_matches = true; + } + } + } + + const auto num_children = m_match_stack.size() - prev_stack_top; + + if ((is_if_init && num_children == 3) + || (!is_if_init && num_children == 2)) { + m_match_stack.push_back(chaiscript::make_unique, eval::Noop_AST_Node>()); + } + + if (!is_if_init) { + build_match>(prev_stack_top); + } else { + build_match>(prev_stack_top+1); + build_match>(prev_stack_top); + } + } + + return retval; + } + + /// Reads a class block from input + bool Class(const bool t_class_allowed) { + Depth_Counter dc{this}; + bool retval = false; + + size_t prev_stack_top = m_match_stack.size(); + + if (Keyword("class")) { + if (!t_class_allowed) { + throw exception::eval_error("Class definitions only allowed at top scope", File_Position(m_position.line, m_position.col), *m_filename); + } + + retval = true; + + if (!Id(true)) { + throw exception::eval_error("Missing class name in definition", File_Position(m_position.line, m_position.col), *m_filename); + } + + const auto class_name = m_match_stack.back()->text; + + while (Eol()) {} + + if (!Class_Block(class_name)) { + throw exception::eval_error("Incomplete 'class' block", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } + + return retval; + } + + + /// Reads a while block from input + bool While() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("while")) { + retval = true; + + if (!Char('(')) { + throw exception::eval_error("Incomplete 'while' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (!(Operator() && Char(')'))) { + throw exception::eval_error("Incomplete 'while' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'while' block", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } + + return retval; + } + + /// Reads the ranged `for` conditions from input + bool Range_Expression() { + Depth_Counter dc{this}; + // the first element will have already been captured by the For_Guards() call that preceeds it + return Char(':') && Equation(); + } + + + /// Reads the C-style `for` conditions from input + bool For_Guards() { + Depth_Counter dc{this}; + if (!(Equation() && Eol())) + { + if (!Eol()) + { + return false; + } else { + m_match_stack.push_back(chaiscript::make_unique, eval::Noop_AST_Node>()); + } + } + + if (!(Equation() && Eol())) + { + if (!Eol()) + { + return false; + } else { + m_match_stack.push_back(chaiscript::make_unique, eval::Constant_AST_Node>(Boxed_Value(true))); + } + } + + if (!Equation()) + { + m_match_stack.push_back(chaiscript::make_unique, eval::Noop_AST_Node>()); + } + + return true; + } + + + /// Reads a for block from input + bool For() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("for")) { + retval = true; + + if (!Char('(')) { + throw exception::eval_error("Incomplete 'for' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + const bool classic_for = For_Guards() && Char(')'); + if (!classic_for && !(Range_Expression() && Char(')'))) { + throw exception::eval_error("Incomplete 'for' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'for' block", File_Position(m_position.line, m_position.col), *m_filename); + } + + const auto num_children = m_match_stack.size() - prev_stack_top; + + if (classic_for) { + if (num_children != 4) { + throw exception::eval_error("Incomplete 'for' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + build_match>(prev_stack_top); + } else { + if (num_children != 3) { + throw exception::eval_error("Incomplete ranged-for expression", File_Position(m_position.line, m_position.col), *m_filename); + } + build_match>(prev_stack_top); + } + } + + return retval; + } + + + /// Reads a case block from input + bool Case() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("case")) { + retval = true; + + if (!Char('(')) { + throw exception::eval_error("Incomplete 'case' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (!(Operator() && Char(')'))) { + throw exception::eval_error("Incomplete 'case' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'case' block", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } else if (Keyword("default")) { + retval = true; + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'default' block", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } + + return retval; + } + + + /// Reads a switch statement from input + bool Switch() { + Depth_Counter dc{this}; + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("switch")) { + + if (!Char('(')) { + throw exception::eval_error("Incomplete 'switch' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (!(Operator() && Char(')'))) { + throw exception::eval_error("Incomplete 'switch' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + while (Eol()) {} + + if (Char('{')) { + while (Eol()) {} + + while (Case()) { + while (Eol()) { } // eat + } + + while (Eol()) { } // eat + + if (!Char('}')) { + throw exception::eval_error("Incomplete block", File_Position(m_position.line, m_position.col), *m_filename); + } + } + else { + throw exception::eval_error("Incomplete block", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + return true; + + } else { + return false; + } + + } + + + /// Reads a curly-brace C-style class block from input + bool Class_Block(const std::string &t_class_name) { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Char('{')) { + retval = true; + + Class_Statements(t_class_name); + if (!Char('}')) { + throw exception::eval_error("Incomplete class block", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (m_match_stack.size() == prev_stack_top) { + m_match_stack.push_back(chaiscript::make_unique, eval::Noop_AST_Node>()); + } + + build_match>(prev_stack_top); + } + + return retval; + } + + /// Reads a curly-brace C-style block from input + bool Block() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Char('{')) { + retval = true; + + Statements(); + if (!Char('}')) { + throw exception::eval_error("Incomplete block", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (m_match_stack.size() == prev_stack_top) { + m_match_stack.push_back(chaiscript::make_unique, eval::Noop_AST_Node>()); + } + + build_match>(prev_stack_top); + } + + return retval; + } + + /// Reads a return statement from input + bool Return() { + Depth_Counter dc{this}; + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("return")) { + Operator(); + build_match>(prev_stack_top); + return true; + } else { + return false; + } + } + + /// Reads a break statement from input + bool Break() { + Depth_Counter dc{this}; + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("break")) { + build_match>(prev_stack_top); + return true; + } else { + return false; + } + } + + /// Reads a continue statement from input + bool Continue() { + Depth_Counter dc{this}; + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("continue")) { + build_match>(prev_stack_top); + return true; + } else { + return false; + } + } + + /// Reads a dot expression(member access), then proceeds to check if it's a function or array call + bool Dot_Fun_Array() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + if (Lambda() || Num() || Quoted_String() || Single_Quoted_String() || + Paren_Expression() || Inline_Container() || Id(false)) + { + retval = true; + bool has_more = true; + + while (has_more) { + has_more = false; + + if (Char('(')) { + has_more = true; + + Arg_List(); + if (!Char(')')) { + throw exception::eval_error("Incomplete function call", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + /// \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) { + if (m_match_stack.empty()) { throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename); +} + if (m_match_stack.back()->children.empty()) { throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename); +} + auto dot_access = std::move(m_match_stack.back()->children[0]); + auto func_call = std::move(m_match_stack.back()); + m_match_stack.pop_back(); + func_call->children.erase(func_call->children.begin()); + if (dot_access->children.empty()) { throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename); +} + func_call->children.insert(func_call->children.begin(), std::move(dot_access->children.back())); + dot_access->children.pop_back(); + dot_access->children.push_back(std::move(func_call)); + if (dot_access->children.size() != 2) { throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename); +} + m_match_stack.push_back(std::move(dot_access)); + } + } + } else if (Char('[')) { + has_more = true; + + if (!(Operator() && Char(']'))) { + throw exception::eval_error("Incomplete array access", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } + else if (Symbol(".")) { + has_more = true; + if (!(Id(true))) { + throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename); + } + + if ( std::distance(m_match_stack.begin() + static_cast(prev_stack_top), m_match_stack.end()) != 2) { + throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename); + } + build_match>(prev_stack_top); + } + } + } + + return retval; + } + + /// Reads a variable declaration from input + bool Var_Decl(const bool t_class_context = false, const std::string &t_class_name = "") { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (t_class_context && (Keyword("attr") || Keyword("auto") || Keyword("var"))) { + retval = true; + + m_match_stack.push_back(make_node>(t_class_name, m_position.line, m_position.col)); + + if (!Id(true)) { + throw exception::eval_error("Incomplete attribute declaration", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } else if (Keyword("auto") || Keyword("var") ) { + retval = true; + + if (Reference()) { + // we built a reference node - continue + } else if (Id(true)) { + build_match>(prev_stack_top); + } else { + throw exception::eval_error("Incomplete variable declaration", File_Position(m_position.line, m_position.col), *m_filename); + } + + } else if (Keyword("global")) { + retval = true; + + if (!(Reference() || Id(true))) { + throw exception::eval_error("Incomplete global declaration", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } else if (Keyword("attr")) { + retval = true; + + if (!Id(true)) { + throw exception::eval_error("Incomplete attribute declaration", File_Position(m_position.line, m_position.col), *m_filename); + } + if (!Symbol("::")) { + throw exception::eval_error("Incomplete attribute declaration", File_Position(m_position.line, m_position.col), *m_filename); + } + if (!Id(true)) { + throw exception::eval_error("Missing attribute name in definition", File_Position(m_position.line, m_position.col), *m_filename); + } + + + build_match>(prev_stack_top); + } + + return retval; + } + + /// Reads an expression surrounded by parentheses from input + bool Paren_Expression() { + Depth_Counter dc{this}; + if (Char('(')) { + if (!Operator()) { + throw exception::eval_error("Incomplete expression", File_Position(m_position.line, m_position.col), *m_filename); + } + if (!Char(')')) { + throw exception::eval_error("Missing closing parenthesis ')'", File_Position(m_position.line, m_position.col), *m_filename); + } + return true; + } else { + return false; + } + } + + /// Reads, and identifies, a short-form container initialization from input + bool Inline_Container() { + Depth_Counter dc{this}; + const auto prev_stack_top = m_match_stack.size(); + + if (Char('[')) { + Container_Arg_List(); + + if (!Char(']')) { + throw exception::eval_error("Missing closing square bracket ']' in container initializer", File_Position(m_position.line, m_position.col), *m_filename); + } + if ((prev_stack_top != m_match_stack.size()) && (!m_match_stack.back()->children.empty())) { + if (m_match_stack.back()->children[0]->identifier == AST_Node_Type::Value_Range) { + build_match>(prev_stack_top); + } + else if (m_match_stack.back()->children[0]->identifier == AST_Node_Type::Map_Pair) { + build_match>(prev_stack_top); + } + else { + build_match>(prev_stack_top); + } + } + else { + build_match>(prev_stack_top); + } + + return true; + } else { + return false; + } + } + + /// Parses a variable specified with a & aka reference + bool Reference() { + Depth_Counter dc{this}; + const auto prev_stack_top = m_match_stack.size(); + + if (Symbol("&")) { + if (!Id(true)) { + throw exception::eval_error("Incomplete '&' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + return true; + } else { + return false; + } + } + + /// Reads a unary prefixed expression from input + bool Prefix() { + Depth_Counter dc{this}; + const auto prev_stack_top = m_match_stack.size(); + using SS = utility::Static_String; + constexpr const std::array prefix_opers{{ + SS{"++"}, + SS{"--"}, + SS{"-"}, + SS{"+"}, + SS{"!"}, + SS{"~"} + }}; + + for (const auto &oper : prefix_opers) + { + const bool is_char = oper.size() == 1; + if ((is_char && Char(oper.c_str()[0])) || (!is_char && Symbol(oper))) + { + if (!Operator(m_operators.size()-1)) { + throw exception::eval_error("Incomplete prefix '" + std::string(oper.c_str()) + "' expression", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top, oper.c_str()); + return true; + } + } + + return false; + } + + /// Parses any of a group of 'value' style ast_node groups from input + bool Value() { + Depth_Counter dc{this}; + return Var_Decl() || Dot_Fun_Array() || Prefix(); + } + + bool Operator_Helper(const size_t t_precedence, std::string &oper) { + Depth_Counter dc{this}; + for (auto & elem : m_operator_matches[t_precedence]) { + if (Symbol(elem)) { + oper = elem.c_str(); + return true; + } + } + return false; + } + + bool Operator(const size_t t_precedence = 0) { + Depth_Counter dc{this}; + bool retval = false; + const auto prev_stack_top = m_match_stack.size(); + + if (m_operators[t_precedence] != Operator_Precidence::Prefix) { + if (Operator(t_precedence+1)) { + retval = true; + std::string oper; + while (Operator_Helper(t_precedence, oper)) { + while (Eol()) {} + if (!Operator(t_precedence+1)) { + throw exception::eval_error("Incomplete '" + oper + "' expression", + File_Position(m_position.line, m_position.col), *m_filename); + } + + switch (m_operators[t_precedence]) { + case(Operator_Precidence::Ternary_Cond) : + if (Symbol(":")) { + if (!Operator(t_precedence+1)) { + throw exception::eval_error("Incomplete '" + oper + "' expression", + File_Position(m_position.line, m_position.col), *m_filename); + } + build_match>(prev_stack_top); + } + else { + throw exception::eval_error("Incomplete '" + oper + "' expression", + File_Position(m_position.line, m_position.col), *m_filename); + } + break; + + case(Operator_Precidence::Addition) : + case(Operator_Precidence::Multiplication) : + case(Operator_Precidence::Shift) : + case(Operator_Precidence::Equality) : + case(Operator_Precidence::Bitwise_And) : + case(Operator_Precidence::Bitwise_Xor) : + case(Operator_Precidence::Bitwise_Or) : + case(Operator_Precidence::Comparison) : + build_match>(prev_stack_top, oper); + break; + + case(Operator_Precidence::Logical_And) : + build_match>(prev_stack_top, oper); + break; + case(Operator_Precidence::Logical_Or) : + build_match>(prev_stack_top, oper); + break; + case(Operator_Precidence::Prefix) : + assert(false); // cannot reach here because of if() statement at the top + break; + +// default: +// throw exception::eval_error("Internal error: unhandled ast_node", File_Position(m_position.line, m_position.col), *m_filename); + } + } + } + } else { + return Value(); + } + + return retval; + } + + /// Reads a pair of values used to create a map initialization from input + bool Map_Pair() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + const auto prev_pos = m_position; + + if (Operator()) { + if (Symbol(":")) { + retval = true; + if (!Operator()) { + throw exception::eval_error("Incomplete map pair", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } + else { + m_position = prev_pos; + while (prev_stack_top != m_match_stack.size()) { + m_match_stack.pop_back(); + } + } + } + + return retval; + } + + /// Reads a pair of values used to create a range initialization from input + bool Value_Range() { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + const auto prev_pos = m_position; + + if (Operator()) { + if (Symbol("..")) { + retval = true; + if (!Operator()) { + throw exception::eval_error("Incomplete value range", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top); + } + else { + m_position = prev_pos; + while (prev_stack_top != m_match_stack.size()) { + m_match_stack.pop_back(); + } + } + } + + return retval; + } + + /// Parses a string of binary equation operators + bool Equation() { + Depth_Counter dc{this}; + const auto prev_stack_top = m_match_stack.size(); + + using SS = utility::Static_String; + + if (Operator()) { + for (const auto &sym : {SS{"="}, SS{":="}, SS{"+="}, SS{"-="}, SS{"*="}, SS{"/="}, SS{"%="}, SS{"<<="}, SS{">>="}, SS{"&="}, SS{"^="}, SS{"|="}}) + { + if (Symbol(sym, true)) { + SkipWS(true); + if (!Equation()) { + throw exception::eval_error("Incomplete equation", File_Position(m_position.line, m_position.col), *m_filename); + } + + build_match>(prev_stack_top, sym.c_str()); + return true; + } + } + return true; + } + + return false; + } + + /// Parses statements allowed inside of a class block + bool Class_Statements(const std::string &t_class_name) { + Depth_Counter dc{this}; + bool retval = false; + + bool has_more = true; + bool saw_eol = true; + + while (has_more) { + const auto start = m_position; + if (Def(true, t_class_name) || Var_Decl(true, t_class_name)) { + if (!saw_eol) { + throw exception::eval_error("Two function definitions missing line separator", File_Position(start.line, start.col), *m_filename); + } + has_more = true; + retval = true; + saw_eol = true; + } else if (Eol()) { + has_more = true; + retval = true; + saw_eol = true; + } else { + has_more = false; + } + } + + return retval; + } + + /// Top level parser, starts parsing of all known parses + bool Statements(const bool t_class_allowed = false) { + Depth_Counter dc{this}; + bool retval = false; + + bool has_more = true; + bool saw_eol = true; + + while (has_more) { + const auto start = m_position; + if (Def() || Try() || If() || While() || Class(t_class_allowed) || For() || Switch()) { + if (!saw_eol) { + throw exception::eval_error("Two function definitions missing line separator", File_Position(start.line, start.col), *m_filename); + } + has_more = true; + retval = true; + saw_eol = true; + } + else if (Return() || Break() || Continue() || Equation()) { + if (!saw_eol) { + throw exception::eval_error("Two expressions missing line separator", File_Position(start.line, start.col), *m_filename); + } + has_more = true; + retval = true; + saw_eol = false; + } + else if (Block() || Eol()) { + has_more = true; + retval = true; + saw_eol = true; + } + else { + has_more = false; + } + } + + return retval; + } + + AST_NodePtr parse(const std::string &t_input, const std::string &t_fname) override + { + ChaiScript_Parser parser(m_tracer, m_optimizer); + return parser.parse_internal(t_input, t_fname); + } + + eval::AST_Node_Impl_Ptr parse_instr_eval(const std::string &t_input) + { + auto last_position = m_position; + auto last_filename = m_filename; + auto last_match_stack = std::exchange(m_match_stack, decltype(m_match_stack){}); + + auto retval = parse_internal(t_input, "instr eval"); + + m_position = std::move(last_position); + m_filename = std::move(last_filename); + m_match_stack = std::move(last_match_stack); + + return eval::AST_Node_Impl_Ptr(dynamic_cast*>(retval.release())); + } + + /// Parses the given input string, tagging parsed ast_nodes with the given m_filename. + AST_NodePtr parse_internal(const std::string &t_input, std::string t_fname) { + m_position = Position(t_input.begin(), t_input.end()); + m_filename = std::make_shared(std::move(t_fname)); + + if ((t_input.size() > 1) && (t_input[0] == '#') && (t_input[1] == '!')) { + while (m_position.has_more() && (!Eol())) { + ++m_position; + } + } + + if (Statements(true)) { + if (m_position.has_more()) { + throw exception::eval_error("Unparsed input", File_Position(m_position.line, m_position.col), *m_filename); + } else { + build_match>(0); + } + } else { + m_match_stack.push_back(chaiscript::make_unique, eval::Noop_AST_Node>()); + } + + AST_NodePtr retval(std::move(m_match_stack.front())); + m_match_stack.clear(); + return retval; + } + }; + } +} + +#if defined(CHAISCRIPT_MSVC) && defined(CHAISCRIPT_PUSHED_MIN_MAX) +#undef CHAISCRIPT_PUSHED_MIN_MAX +#pragma pop_macro("min") +#pragma pop_macro("max") +#endif + + +#endif /* CHAISCRIPT_PARSER_HPP_ */ + diff --git a/chaiscript/language/chaiscript_posix.hpp b/chaiscript/language/chaiscript_posix.hpp new file mode 100644 index 0000000..87b6688 --- /dev/null +++ b/chaiscript/language/chaiscript_posix.hpp @@ -0,0 +1,68 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_POSIX_HPP_ +#define CHAISCRIPT_POSIX_HPP_ + +namespace chaiscript +{ + namespace detail + { + struct Loadable_Module + { + struct DLModule + { + explicit DLModule(const std::string &t_filename) + : m_data(dlopen(t_filename.c_str(), RTLD_NOW)) + { + if (m_data == nullptr) + { + throw chaiscript::exception::load_module_error(dlerror()); + } + } + + DLModule(DLModule &&) = default; + DLModule &operator=(DLModule &&) = default; + DLModule(const DLModule &) = delete; + DLModule &operator=(const DLModule &) = delete; + + ~DLModule() + { + dlclose(m_data); + } + + void *m_data; + }; + + template + struct DLSym + { + DLSym(DLModule &t_mod, const std::string &t_symbol) + : m_symbol(reinterpret_cast(dlsym(t_mod.m_data, t_symbol.c_str()))) + { + if (!m_symbol) + { + throw chaiscript::exception::load_module_error(dlerror()); + } + } + + T m_symbol; + }; + + Loadable_Module(const std::string &t_module_name, const std::string &t_filename) + : m_dlmodule(t_filename), m_func(m_dlmodule, "create_chaiscript_module_" + t_module_name), + m_moduleptr(m_func.m_symbol()) + { + } + + DLModule m_dlmodule; + DLSym m_func; + ModulePtr m_moduleptr; + }; + } +} +#endif + diff --git a/chaiscript/language/chaiscript_prelude.hpp b/chaiscript/language/chaiscript_prelude.hpp new file mode 100644 index 0000000..a4deb87 --- /dev/null +++ b/chaiscript/language/chaiscript_prelude.hpp @@ -0,0 +1,562 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// and 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_PRELUDE_HPP_ +#define CHAISCRIPT_PRELUDE_HPP_ + +namespace chaiscript { +struct ChaiScript_Prelude { + static std::string chaiscript_prelude() { return R"chaiscript( + +def lt(l, r) { + if (call_exists(`<`, l, r)) { + l < r + } else { + type_name(l) < type_name(r) + } +} + + +def gt(l, r) { + if (call_exists(`>`, l, r)) { + l > r + } else { + type_name(l) > type_name(r) + } +} + +def eq(l, r) { + if (call_exists(`==`, l, r)) { + l == r + } else { + false + } +} + +def new(x) { + eval(type_name(x))(); +} + +def clone(double x) { + double(x).clone_var_attrs(x) +} + +def clone(string x) { + string(x).clone_var_attrs(x) +} + +def clone(vector x) { + vector(x).clone_var_attrs(x) +} + + +def clone(int x) { + int(x).clone_var_attrs(x) +} + +def clone(x) : function_exists(type_name(x)) && call_exists(eval(type_name(x)), x) +{ + eval(type_name(x))(x).clone_var_attrs(x); +} + + +# to_string for Pair() +def to_string(x) : call_exists(first, x) && call_exists(second, x) { + "<" + x.first.to_string() + ", " + x.second.to_string() + ">"; +} + +# to_string for containers +def to_string(x) : call_exists(range, x) && !x.is_type("string"){ + "[" + x.join(", ") + "]"; +} + +# Prints to console with no carriage return +def puts(x) { + print_string(x.to_string()); +} + +# Prints to console with carriage return +def print(x) { + println_string(x.to_string()); +} + +# Returns the maximum value of two numbers +def max(a, b) { + if (a>b) { + a + } else { + b + } +} + +# Returns the minimum value of two numbers +def min(a, b) +{ + if (a 0) && (!r.empty())) { + inserter(r.front()); + r.pop_front(); + --i; + } +} + + +# Returns a new container with the given number of elements taken from the container +def take(container, num) { + auto retval := new(container); + take(container, num, back_inserter(retval)); + retval; +} + + +def take_while(container, f, inserter) : call_exists(range, container) { + auto r := range(container); + while ((!r.empty()) && f(r.front())) { + inserter(r.front()); + r.pop_front(); + } +} + + +# Returns a new container with the given elements match the second value function +def take_while(container, f) { + auto retval := new(container); + take_while(container, f, back_inserter(retval)); + retval; +} + + +def drop(container, num, inserter) : call_exists(range, container) { + auto r := range(container); + auto i = num; + while ((i > 0) && (!r.empty())) { + r.pop_front(); + --i; + } + while (!r.empty()) { + inserter(r.front()); + r.pop_front(); + } +} + + +# Returns a new container with the given number of elements dropped from the given container +def drop(container, num) { + auto retval := new(container); + drop(container, num, back_inserter(retval)); + retval; +} + + +def drop_while(container, f, inserter) : call_exists(range, container) { + auto r := range(container); + while ((!r.empty())&& f(r.front())) { + r.pop_front(); + } + while (!r.empty()) { + inserter(r.front()); + r.pop_front(); + } +} + + +# Returns a new container with the given elements dropped that match the second value function +def drop_while(container, f) { + auto retval := new(container); + drop_while(container, f, back_inserter(retval)); + retval; +} + + +# Applies the second value function to the container. Starts with the first two elements. Expects at least 2 elements. +def reduce(container, func) : container.size() >= 2 && call_exists(range, container) { + auto r := range(container); + auto retval = r.front(); + r.pop_front(); + retval = func(retval, r.front()); + r.pop_front(); + while (!r.empty()) { + retval = func(retval, r.front()); + r.pop_front(); + } + retval; +} + + +# Returns a string of the elements in container delimited by the second value string +def join(container, delim) { + auto retval = ""; + auto range := range(container); + if (!range.empty()) { + retval += to_string(range.front()); + range.pop_front(); + while (!range.empty()) { + retval += delim; + retval += to_string(range.front()); + range.pop_front(); + } + } + retval; +} + + +def filter(container, f, inserter) : call_exists(range, container) { + auto r := range(container); + while (!r.empty()) { + if (f(r.front())) { + inserter(r.front()); + } + r.pop_front(); + } +} + + +# Returns a new Vector which match the second value function +def filter(container, f) { + auto retval := new(container); + filter(container, f, back_inserter(retval)); + retval; +} + + +def generate_range(x, y, inserter) { + auto i = x; + while (i <= y) { + inserter(i); + ++i; + } +} + + +# Returns a new Vector which represents the range from the first value to the second value +def generate_range(x, y) { + auto retval := Vector(); + generate_range(x,y,back_inserter(retval)); + retval; +} + + +# Returns a new Vector with the first value to the second value as its elements +def collate(x, y) { + return [x, y]; +} + + +def zip_with(f, x, y, inserter) : call_exists(range, x) && call_exists(range, y) { + auto r_x := range(x); + auto r_y := range(y); + while (!r_x.empty() && !r_y.empty()) { + inserter(f(r_x.front(), r_y.front())); + r_x.pop_front(); + r_y.pop_front(); + } +} + + +# Returns a new Vector which joins matching elements of the second and third value with the first value function +def zip_with(f, x, y) { + auto retval := Vector(); + zip_with(f,x,y,back_inserter(retval)); + retval; +} + + +# Returns a new Vector which joins matching elements of the first and second +def zip(x, y) { + zip_with(collate, x, y); +} + + +# Returns the position of the second value string in the first value string +def string::find(string substr) { + find(this, substr, size_t(0)); +} + + +# Returns the position of last match of the second value string in the first value string +def string::rfind(string substr) { + rfind(this, substr, size_t(-1)); +} + + +# Returns the position of the first match of elements in the second value string in the first value string +def string::find_first_of(string list) { + find_first_of(this, list, size_t(0)); +} + + +# Returns the position of the last match of elements in the second value string in the first value string +def string::find_last_of(string list) { + find_last_of(this, list, size_t(-1)); +} + + +# Returns the position of the first non-matching element in the second value string in the first value string +def string::find_first_not_of(string list) { + find_first_not_of(this, list, size_t(0)); +} + + +# Returns the position of the last non-matching element in the second value string in the first value string +def string::find_last_not_of(string list) { + find_last_not_of(this, list, size_t(-1)); +} + + +def string::ltrim() { + drop_while(this, fun(x) { x == ' ' || x == '\t' || x == '\r' || x == '\n'}); +} + + +def string::rtrim() { + reverse(drop_while(reverse(this), fun(x) { x == ' ' || x == '\t' || x == '\r' || x == '\n'})); +} + + +def string::trim() { + ltrim(rtrim(this)); +} + + +def find(container, value, Function compare_func) : call_exists(range, container) { + auto range := range(container); + while (!range.empty()) { + if (compare_func(range.front(), value)) { + return range; + } else { + range.pop_front(); + } + } + range; +} + + +def find(container, value) { + find(container, value, eq) +} + + +)chaiscript"; +} + +}; +} + +#endif /* CHAISCRIPT_PRELUDE_HPP_ */ diff --git a/chaiscript/language/chaiscript_prelude_docs.hpp b/chaiscript/language/chaiscript_prelude_docs.hpp new file mode 100644 index 0000000..7df5652 --- /dev/null +++ b/chaiscript/language/chaiscript_prelude_docs.hpp @@ -0,0 +1,830 @@ +/// This file is not technically part of the ChaiScript API. It is used solely for generating Doxygen docs +/// regarding the ChaiScript standard runtime library. + +/// \brief Items in this namespace exist in the ChaiScript language runtime. They are not part of the C++ API +namespace ChaiScript_Language +{ + +/// \page LangStandardLibraryRef ChaiScript Language Standard Library Reference +/// +/// ChaiScript, at its core, has some very functional programming-inspired habits. Few places show this off as clearly +/// as the prelude, itself a name taken as a nod to the popular functional language Haskell. This prelude is available +/// to all standard ChaiScript applications, and provides a simple foundation for using numbers, strings, and ranges +/// (the general category of Range cs and their iteration). +/// + + +/// \brief Generic concept of a value in ChaiScript. +/// +/// The Object type exists merely as a concept. All objects in ChaiScript support this concept +/// and have the following methods available to them. All objects are stored internally as chaiscript::Boxed_Value types. +/// +/// \sa chaiscript::Boxed_Value +class Object +{ + public: + /// \brief Returns the Type_Info value for this Object + Type_Info get_type_info() const; + + /// \brief Returns true if the Object is of the named type + bool is_type(string) const; + + /// \brief Returns true if the Object is of the Type_Info passed in + bool is_type(Type_Info) const; + + /// \brief Returns true if the Object is immutable + bool is_var_const() const; + + /// \brief Returns true if the Object is a pointer and the pointer is null + bool is_var_null() const; + + /// \brief Returns true if the Object is stored as a pointer + bool is_var_pointer() const; + + /// \brief Returns true if the Object is stored as a reference + bool is_var_reference() const; + + /// \brief Returns true if the Object does not contain a value is is undefined. + bool is_var_undef() const; + + /// \brief Returns the registered name of the type of the object. + /// + /// \sa Type_Info::name(); + string type_name() const; +}; + +/// \brief Item returned from a Range object from a Map +class Map_Pair +{ + public: + /// \brief Returns the key of the Map entry + const string first(); + + /// \brief Returns the value Object of the Map entry + Object second(); +}; + + +/// \brief Maps strings to Objects +/// +/// ChaiScript has a built in shortcut for generating Map objects: +/// +/// Example: +/// \code +/// eval> var m = ["a":1, "b":2]; +/// [, ] +/// eval> m.count("a"); +/// 1 +/// eval> m.count("c"); +/// 0 +/// eval> m.size(); +/// 2 +/// \endcode +/// +/// Implemented as std::map +/// +/// \sa Map_Pair +/// \sa chaiscript::bootstrap::standard_library::map_type +class Map +{ + public: + /// \brief Returns an object that implements the Range concept for the Map_Pair's in this Map + Range range(); + + /// \brief Returns an object that implements the Const_Range concept for the Map_Pair's in this Map + Const_Range range() const; + + /// \brief Returns the number of elements in the Map + int size() const; + + /// \brief Returns the item at the given key, creating an undefined Object if the key does not yet exist in the map + Object operator[](string); + + /// \brief Clears the map of all items + void clear(); + + /// \brief Returns the number of items in the Map with the given key. Returns 0 or 1 since this is not an std::multimap. + int count(string) const; + + /// \brief Returns true if the map contains no items + bool empty() const; + +}; + + +/// \brief A concept implemented by string, Vector and Map. It is convertible to Range, default constructable and back_insertable +class Container +{ + public: + void push_back(Object); + Range range(); + Const_Range range() const; +}; + + +/// \brief Converts o into a string. +/// +/// \code +/// eval> to_string(3).is_type("string")
+/// true
+/// \endcode +string to_string(Object o); + + +/// \brief Prints o to the terminal, without a trailing carriage return. Applies conversions to string automatically. +/// \code +/// eval> puts("hi, "); puts("there") +/// hi, thereeval> +/// \endcode +/// \sa to_string +/// \sa print +void puts(Object o); + + +/// \brief Prints o to the terminal, with a trailing carriage return. Applies conversions to string automatically +/// \code +/// eval> print("hello") +/// hello +/// eval> +/// \endcode +/// \sa to_string +/// \sa puts +void print(Object o); + +/// \brief ChaiScript representation of std::string. It is an std::string but only some member are exposed to ChaiScript. +/// +/// Because the ChaiScript string object is an std::string, it is directly convertible to and from std::string +/// using the chaiscript::boxed_cast and chaiscript::var functions. +/// +/// With the exception of string::trim, string::rtrim, string::ltrim, all members are direct pass-throughs to the +/// std::string of the same name. +/// +/// \note Object and function notations are equivalent in ChaiScript. This means that +/// \c "bob".find("b") and \c find("bob", "b") are exactly the same. Most examples below follow the +/// second formation of the function calls. +/// \sa \ref keyworddef for extending existing C++ classes in ChaiScript +/// \sa chaiscript::bootstrap::standard_library::string_type +class string +{ + public: + /// \brief Finds the first instance of substr. + /// \code + /// eval> find("abab", "ab") + /// 0 + /// \endcode + int find(string s) const; + + + /// \brief Finds the last instance of substr. + /// \code + /// eval> rfind("abab", "ab") + /// 2 + /// \endcode + int rfind(string s) const; + + /// \brief Finds the first of characters in list in the string. + /// + /// \code + /// eval> find_first_of("abab", "bec") + /// 1 + /// \endcode + int find_first_of(string list) const; + + /// \brief Finds the last of characters in list in the string. + /// + /// \code + /// eval> find_last_of("abab", "bec") + /// 3 + /// \endcode + int find_last_of(string list) const; + + /// \brief Finds the first non-matching character to list in the str string. + /// + /// \code + /// eval> find_first_not_of("abcd", "fec") + /// 0 + /// \endcode + int find_first_not_of(string list) const; + + /// \brief Finds the last non-matching character to list in the list string. + /// + /// \code + /// eval> find_last_not_of("abcd", "fec") + /// 3 + /// \endcode + int find_last_not_of(string list) const; + + /// \brief Removes whitespace from the front of the string, returning a new string + /// + /// \note This function is implemented as a ChaiScript function using the def member function notation. + /// + /// \code + /// eval> ltrim(" bob") + /// bob + /// \endcode + /// + /// \sa \ref keyworddef + string lstrim() const; + + /// \brief Removes whitespace from the back of the string, returning a new string + /// + /// \note This function is implemented as a ChaiScript function using the def member function notation. + /// + /// \code + /// eval> rtrim("bob ") + "|" + /// bob| + /// \endcode + /// + /// \sa \ref keyworddef + string rtrim() const; + + /// \brief Removes whitespace from the front and back of the string, returning a new string + /// + /// \note This function is implemented as a ChaiScript function using the def member function notation. + /// + /// \code + /// eval> trim(" bob ") + "|" + /// bob| + /// \endcode + /// + /// Equivalent to rtrim(ltrim(" bob ")); + /// + /// \sa \ref keyworddef + string trim() const; + + /// \brief Returns the character at the given index in the string, const version + const char &operator[](int t_index) const; + + /// \brief Returns the character at the given index in the string + char &operator[](int t_index); + + /// \brief Returns underlying const char * for C api compatibility + const char *c_str() const; + + /// \brief Returns a pointer to the raw data in the string + const char *data() const; + + /// \brief Resets the string to empty + void clear(); + + /// \brief Returns true if the string is empty + bool empty() const; + + /// \brief Returns the size of the string in bytes. + /// + /// This function normally returns size_t in C++. In ChaiScript the return value is cast to int + /// for ease of use. + int size() const; + + /// \brief Returns an object that implements the Range concept for the characters of this string + Range range(); + + /// \brief Returns an object that implements the Const_Range concept for the characters of this string + Const_Range range() const; +}; + +/// \brief A concept in ChaiScript that is implemented by \ref string, Vector and Map. It provides +/// easy iteration over the elements in a container. +/// +/// Implemented by the template chaiscript::bootstrap::standard_library::Bidir_Range +/// +/// \sa Const_Range +class Range +{ + public: + /// \brief Returns the last item of the range + Object back(); + + /// \brief Returns true if the front and back pointers have passed each other, if no items + /// are left in the Range + bool empty() const; + + /// \brief Returns the first item of the range + Object front(); + + /// \brief Moves the back pointer back one. + /// + /// \post back() returns the element at back() - 1; + void pop_back(); + + /// \brief Moves the front pointer forward one + /// + /// \post front() returns the element at front() + 1; + void pop_front(); + +}; + +/// \brief A concept in ChaiScript that is implemented by \ref string, Vector and Map. It provides +/// easy iteration over the elements in a container. Contained values are const. +/// +/// Implemented by the template chaiscript::bootstrap::standard_library::Const_Bidir_Range +/// +/// \sa Range +class Const_Range +{ + public: + /// \brief Returns the last item of the range + const Object back(); + + /// \brief Returns true if the front and back pointers have passed each other, if no items + /// are left in the Range + bool empty() const; + + /// \brief Returns the first item of the range + const Object front(); + + /// \brief Moves the back pointer back one. + /// + /// \post back() returns the element at back() - 1; + void pop_back(); + + /// \brief Moves the front pointer forward one + /// + /// \post front() returns the element at front() + 1; + void pop_front(); + +}; + +/// \brief A vector of Objects +/// +/// ChaiScript includes a shortcut for creating a Vector of Objects +/// +/// Example: +/// \code +/// eval> var v = [1,2,3,4] +/// [1, 2, 3, 4] +/// eval> v[0]; +/// 1 +/// eval> v.size(); +/// 4 +/// \endcode +/// +/// Implemented with std::vector +/// +/// \sa chaiscript::bootstrap::standard_library::vector_type +class Vector +{ + public: + /// \brief returns the Object at the given index. Throws an exception if the index does not exist + Object operator[](int t_index); + + /// \brief returns a const Object at the given index. Throws an exception if the index does not exist. + const Object operator[](int t_index) const; + + /// \brief returns the last item in the Vector + Object back(); + + /// \brief Clears the Vector of all items + void clear(); + + /// \brief Returns true if the Vector is contains 0 items + bool empty(); + + /// \brief Erases the element at the given index + void erase_at(int t_index); + + /// \brief Returns the first item in the Vector + Object front(); + + /// \brief Inserts a new item in the Vector at the given index. The item is not cloned on insert + /// + /// \sa insert_ref + void insert_ref_at(int, Object); + + /// \brief Inserts a new item in the Vector at the given index. The item is cloned on insert + /// + /// \sa insert_ref + void insert_at(int, Object); + + /// \brief Removes the last item from the Vector + void pop_back(); + + /// \brief Adds an item to the end of the Vector. The item is not cloned. + /// + /// \sa push_back + void push_back_ref(Object); + + /// \brief Adds an item to the end of the Vector. The item is cloned. + /// + /// \sa push_back_ref + void push_back(Object); + + /// \brief Returns a Range object for the entire vector + Range range(); + + /// \brief Returns a Const_Range object for the entire vector + Const_Range range() const; + + /// \brief Returns the number of elements in the Vector + int size() const; + +}; + +class Type_Info +{ + public: + /// \brief Compares this Type_Info object with another one and returns true if the two types are the same + /// after const, pointer, reference are removed. + bool bare_equal(Type_Info t_ti) const; + + /// \brief Returns the mangled C++ name for the type given by the compiler after const, pointer, reference is removed. + string cpp_bare_name() const; + + /// \brief Returns the mangled C++ name for the type given by the compiler. + string cpp_name() const; + + /// \brief Returns true if the type is const + bool is_type_const() const; + + /// \brief Returns true if the type is a pointer + bool is_type_pointer() const; + + /// \brief Returns true if the type is a reference + bool is_type_reference() const; + + /// \brief Returns true if the type is undefined + bool is_type_undef() const; + + /// \brief Returns true if the type is "void" + bool is_type_void() const; + + /// \brief Returns the ChaiScript registered name for the type if one exists. + string name() const; + +}; + + +/// \brief Represents a function object in ChaiScript +/// +/// A function object may be one function, such as: +/// \code +/// var f = fun(x) { return x; } +/// \endcode +/// +/// Or it may represent multiple functions +/// \code +/// var f2 = `-`; // represents the unary - as well as the set of binary - operators +/// \endcode +/// +/// Guarded function example +/// \code +/// def f3(x) : x > 2 { +/// return x; +/// } +/// \endcode +/// +/// Examples in the function definitions below will reference these examples +class Function +{ + public: + /// \brief Returns the annotation description of the function + string get_annotation() const; + + /// \brief Returns the arity of the function, -1 if the function takes a variable number of parameters + /// + /// Example: + /// \code + /// eval> f.get_arity() + /// 1 + /// eval> f2.get_arity() + /// -1 + /// \endcode + int get_arity() const; + + /// \brief Returns a vector of the contained functions + /// + /// Example: + /// \code + /// eval> f.get_contained_functions().size() + /// 0 + /// eval> f2.get_contained_functions().size() + /// 11 + /// eval> var v = f2.get_contained_functions(); + /// v[0].get_arity() + /// 2 + /// \endcode + Vector get_contained_functions() const; + + /// \brief Returns a function guard as function + /// + /// Example: + /// \code + /// eval> f.get_guard() // Throws exception + /// Function does not have a guard + /// eval> f3.get_guard().get_arity() + /// 1 + /// \endcode + Function get_guard() const; + + /// \brief Returns a vector of Type_Info objects that represent the param types for this function. + /// The first value in the list is the return type. + /// + /// If this function is a conglomerate of several functions (get_contained_values().size() > 0) + /// then the function returns as many Type_Info objects as it can. If the functions contained all have + /// the same arity, then it represents the arity. If they have different arities, it returns only + /// one value - the return type. + /// + /// For each parameter that is the same type, the type is returned. If the types are different + /// then a Type_Info for Object is returned. + /// + /// Example: + /// \code + /// eval> f2.get_param_types().size(); // Returns a Type_Info for Object for the return type + /// 1 + /// \endcode + Vector get_param_types() const; + + /// \brief Returns true if the function has a guard to it. Always returns false for a conglomerate function + bool has_guard() const; + + /// \brief Calls the function with the given set of parameters and returns the value; + /// + /// Example: + /// \code + /// eval> `-`.call([2,1]); + /// 1 + /// \endcode + Object call(Vector t_params) const; +} + + + +/// \brief Returns the max of a or b. Requires that operator>(a, b) exists +/// Equivalent to +/// \code +/// return a>b?a:b; +/// \endcode +/// +/// Example: +/// \code +/// eval> max(4, 10) +/// 10 +/// \endcode +Object max(Object a, Object b); + +/// \brief Returns the min of a or b. Requires that operator<(a, b) exists +/// +/// Equivalent to +/// \code +/// return a min(4, 10) +/// 4 +/// \endcode +Object min(Object a, Object b); + +/// \brief Returns true if x is an even integer. +/// +/// Will also work on any non-integer type for which an operator%(x, int) exists +/// +/// Example: +/// \code +/// eval> even(4) +/// true +/// \endcode +bool even(Object x); + +/// \brief Returns true if x is an odd integer. +/// +/// Will also work on any non-integer type for which an operator%(x, int) exists +/// +/// Example: +/// \code +/// eval> odd(4) +/// false +/// \endcode +bool even(Object x); + + +/// \brief Applies the function f over each element in the Range c. +/// +/// Example: +/// \code +/// eval> for_each([1, 2, 3], print) +/// 1 +/// 2 +/// 3 +/// \endcode +void for_each(Range c, Function f); + + +/// \brief Applies f over each element in the Range c, joining all the results. +/// +/// Example: +/// \code +/// eval> map([1, 2, 3], odd) +/// [true, false, true] +/// \endcode +Object map(Range c, Function f); + + +/// \brief Starts with the initial value and applies the function f to it and the first element of the Range c. +/// The result is then applied to the second element, and so on until the elements are exhausted. +/// +/// Example: +/// \code +/// eval> foldl([1, 2, 3, 4], `+`, 0) +/// 10 +/// \endcode +Object foldl(Range c, Function f, Object initial); + + +/// \brief Returns the sum total of the values in the Range c. +/// +/// Example: +/// \code +/// eval> sum([1, 2, 3, 4]) +/// 10 +/// \endcode +/// +/// Equivalent to: +/// \code +/// foldl(c, `+`, 0.0); +/// \endcode +Numeric sum(Range c); + + +/// \brief Returns the product of the value in the Range c. +/// +/// Example: +/// \code +/// eval> product([1, 2, 3, 4]) +/// 24 +/// \endcode +/// +/// Equivalent to: +/// \code +/// foldl(c, `*`, 1.0); +/// \endcode +Numeric product(Range c); + + +/// \brief Takes num elements from the Range c, returning them. +/// +/// Example: +/// \code +/// eval> take([1, 2, 3, 4], 2) +/// [1, 2] +/// \endcode +/// +/// \returns A container of the same type that was passed in +Object take(Range c, int num); + + +/// \brief Takes elements from the Range c that match function f, stopping at the first non-match, returning them as a new Vector. +/// +/// Example: +/// \code +/// eval> take_while([1, 2, 3], odd) +/// [1] +/// \endcode +/// +/// \returns A container of the same type that was passed in +Object take_while(Range c, Function f); + + +/// \brief Drops num elements from the Range c, returning the remainder. +/// +/// Example: +/// \code +/// eval> drop([1, 2, 3, 4], 2) +/// [3, 4] +/// \endcode +/// +/// \returns A container of the same type that was passed in +Object drop(Range c, int num); + + +/// \brief Drops elements from the Range c that match f, stopping at the first non-match, returning the remainder. +/// +/// Example: +/// \code +/// eval> drop_while([1, 2, 3], odd) +/// [2, 3] +/// \endcode +Object drop_while(Range c, Function f); + + +/// \brief Similar to foldl, this takes the first two elements as its starting values for f. This assumes Range c has at least 2 elements. +/// +/// Example: +/// \code +/// eval> reduce([1, 2, 3, 4], `+`) +/// 10 +/// \endcode +Object reduce(Range c, Function f); + + +/// \brief Takes elements from Container c that match function f, return them. +/// +/// Example: +/// \code +/// eval> filter([1, 2, 3, 4], odd) +/// [1, 3] +/// \endcode +Object filter(Container c, Function f); + + +/// \brief Joins the elements of the Range c into a string, delimiting each with the delim string. +/// +/// Example: +/// \code +/// eval> join([1, 2, 3], "*") +/// 1*2*3 +/// \endcode +string join(Range c, string delim); + + +/// \brief Returns the contents of the Container c in reversed order. +/// +/// Example: +/// \code +/// eval> reverse([1, 2, 3, 4, 5, 6, 7]) +/// [7, 6, 5, 4, 3, 2, 1] +/// \endcode +Container reverse(Container c); + + +/// \brief Generates a new Vector filled with values starting at x and ending with y. +/// +/// Works on types supporting operator<=(x, y) and operator++(x) +/// +/// Example: +/// \code +/// eval> generate_range(1, 10) +/// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +/// \endcode +Vector generate_range(Object x, Object y); + + +/// \brief Returns a new Range with x and y concatenated. +/// +/// Example: +/// \code +/// eval> concat([1, 2, 3], [4, 5, 6]) +/// [1, 2, 3, 4, 5, 6] +/// \endcode +Object concat(Range x, Range y); + + +/// \brief Returns a new Vector with x and y as its values. +/// +/// Example: +/// \code +/// eval> collate(1, 2) +/// [1, 2] +/// \endcode +Vector collate(Object x, Object y); + + +/// \brief Applies f to elements of x and y, returning a new Vector with the result of each application. +/// +/// Example: +/// \code +/// eval> zip_with(`+`, [1, 2, 3], [4, 5, 6]) +/// [5, 7, 9] +/// \endcode +Vector zip_with(Function f, Range x, Range y); + + +/// \brief Collates elements of x and y, returning a new Vector with the result. +/// +/// Example: +/// \code +/// eval> zip([1, 2, 3], [4, 5, 6]) +/// [[1, 4], [2, 5], [3, 6]] +/// \endcode +Vector zip(Range x, Range y); + + +/// \brief returns true if there exists a call to the Function f that takes the given parameters +/// +/// Example: +/// \code +/// eval> call_exists(`+`, 1, 2) +/// true +/// \endcode +bool call_exists(Function f, ...); + +/// \brief Reverses a Range object so that the elements are accessed in reverse +Range retro(Range); + +/// \brief Reverses a Const_Range object so that the elements are accessed in reverse +Const_Range retro(Const_Range); + + +/// \brief Raises the given object as an exception. Any type of object can be thrown. +/// +/// Example: +/// \code +/// eval> try { throw(1); } catch (e) { print("Exception caught: " + to_string(e)); } +/// Exception caught: 1 +/// \endcode +/// +/// \sa \ref keywordtry +void throw(Object); +} + diff --git a/chaiscript/language/chaiscript_tracer.hpp b/chaiscript/language/chaiscript_tracer.hpp new file mode 100644 index 0000000..9d11181 --- /dev/null +++ b/chaiscript/language/chaiscript_tracer.hpp @@ -0,0 +1,46 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_TRACER_HPP_ +#define CHAISCRIPT_TRACER_HPP_ + +namespace chaiscript { + namespace eval { + + + struct Noop_Tracer_Detail + { + template + void trace(const chaiscript::detail::Dispatch_State &, const AST_Node_Impl *) + { + } + }; + + template + struct Tracer : T... + { + Tracer() = default; + explicit Tracer(T ... t) + : T(std::move(t))... + { + } + + void do_trace(const chaiscript::detail::Dispatch_State &ds, const AST_Node_Impl> *node) { + (void)std::initializer_list{ (static_cast(*this).trace(ds, node), 0)... }; + } + + static void trace(const chaiscript::detail::Dispatch_State &ds, const AST_Node_Impl> *node) { + ds->get_parser().get_tracer>().do_trace(ds, node); + } + }; + + typedef Tracer Noop_Tracer; + + } +} + +#endif + diff --git a/chaiscript/language/chaiscript_unknown.hpp b/chaiscript/language/chaiscript_unknown.hpp new file mode 100644 index 0000000..8fc1d49 --- /dev/null +++ b/chaiscript/language/chaiscript_unknown.hpp @@ -0,0 +1,31 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_UNKNOWN_HPP_ +#define CHAISCRIPT_UNKNOWN_HPP_ + + +namespace chaiscript +{ + namespace detail + { + struct Loadable_Module + { + Loadable_Module(const std::string &, const std::string &) + { +#ifdef CHAISCRIPT_NO_DYNLOAD + throw chaiscript::exception::load_module_error("Loadable module support was disabled (CHAISCRIPT_NO_DYNLOAD)"); +#else + throw chaiscript::exception::load_module_error("Loadable module support not available for your platform"); +#endif + } + + ModulePtr m_moduleptr; + }; + } +} +#endif + diff --git a/chaiscript/language/chaiscript_windows.hpp b/chaiscript/language/chaiscript_windows.hpp new file mode 100644 index 0000000..493572c --- /dev/null +++ b/chaiscript/language/chaiscript_windows.hpp @@ -0,0 +1,133 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_WINDOWS_HPP_ +#define CHAISCRIPT_WINDOWS_HPP_ + +#include + +#ifdef CHAISCRIPT_WINDOWS +#define VC_EXTRA_LEAN +#if !defined(WIN32_LEAN_AND_MEAN) +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + + +namespace chaiscript +{ + namespace detail + { + struct Loadable_Module + { + template + static std::wstring to_wstring(const T &t_str) + { + return std::wstring(t_str.begin(), t_str.end()); + } + + template + static std::string to_string(const T &t_str) + { + return std::string(t_str.begin(), t_str.end()); + } + +#if defined(_UNICODE) || defined(UNICODE) + template + static std::wstring to_proper_string(const T &t_str) + { + return to_wstring(t_str); + } +#else + template + static std::string to_proper_string(const T &t_str) + { + return to_string(t_str); + } +#endif + + static std::string get_error_message(DWORD t_err) + { + typedef LPTSTR StringType; + +#if defined(_UNICODE) || defined(UNICODE) + std::wstring retval = L"Unknown Error"; +#else + std::string retval = "Unknown Error"; +#endif + StringType lpMsgBuf = nullptr; + + if (FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + t_err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&lpMsgBuf), + 0, nullptr ) != 0 && lpMsgBuf) + { + retval = lpMsgBuf; + LocalFree(lpMsgBuf); + } + + return to_string(retval); + } + + struct DLModule + { + explicit DLModule(const std::string &t_filename) + : m_data(LoadLibrary(to_proper_string(t_filename).c_str())) + { + if (!m_data) + { + throw chaiscript::exception::load_module_error(get_error_message(GetLastError())); + } + } + + DLModule(DLModule &&) = default; + DLModule &operator=(DLModule &&) = default; + DLModule(const DLModule &) = delete; + DLModule &operator=(const DLModule &) = delete; + + ~DLModule() + { + FreeLibrary(m_data); + } + + HMODULE m_data; + }; + + template + struct DLSym + { + DLSym(DLModule &t_mod, const std::string &t_symbol) + : m_symbol(reinterpret_cast(GetProcAddress(t_mod.m_data, t_symbol.c_str()))) + { + if (!m_symbol) + { + throw chaiscript::exception::load_module_error(get_error_message(GetLastError())); + } + } + + T m_symbol; + }; + + Loadable_Module(const std::string &t_module_name, const std::string &t_filename) + : m_dlmodule(t_filename), m_func(m_dlmodule, "create_chaiscript_module_" + t_module_name), + m_moduleptr(m_func.m_symbol()) + { + } + + DLModule m_dlmodule; + DLSym m_func; + ModulePtr m_moduleptr; + }; + } +} +#endif + diff --git a/chaiscript/utility/fnv1a.hpp b/chaiscript/utility/fnv1a.hpp new file mode 100644 index 0000000..9e54992 --- /dev/null +++ b/chaiscript/utility/fnv1a.hpp @@ -0,0 +1,50 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_UTILITY_FNV1A_HPP_ +#define CHAISCRIPT_UTILITY_FNV1A_HPP_ + + +#include +#include "../chaiscript_defines.hpp" + + +namespace chaiscript +{ + + + namespace utility + { + + + static constexpr std::uint32_t fnv1a_32(const char *s, std::uint32_t h = 0x811c9dc5) { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#ifdef CHAISCRIPT_MSVC +#pragma warning(push) +#pragma warning(disable : 4307) +#endif + return (*s == 0) ? h : fnv1a_32(s+1, ((h ^ (*s)) * 0x01000193)); +#ifdef CHAISCRIPT_MSVC +#pragma warning(pop) +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + } + + + } + + +} + +#endif diff --git a/chaiscript/utility/json.hpp b/chaiscript/utility/json.hpp new file mode 100644 index 0000000..c6988d5 --- /dev/null +++ b/chaiscript/utility/json.hpp @@ -0,0 +1,675 @@ +// From github.com/nbsdx/SimpleJSON. +// Released under the DWTFYW PL +// + + +#pragma once + +#ifndef SIMPLEJSON_HPP +#define SIMPLEJSON_HPP + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../chaiscript_defines.hpp" + +namespace json { + +using std::enable_if; +using std::initializer_list; +using std::is_same; +using std::is_convertible; +using std::is_integral; +using std::is_floating_point; + + + + +class JSON +{ + public: + enum class Class { + Null, + Object, + Array, + String, + Floating, + Integral, + Boolean + }; + + private: + + struct QuickFlatMap + { + auto find(const std::string &s) { + return std::find_if(std::begin(data), std::end(data), [&s](const auto &d) { return d.first == s; }); + } + + auto find(const std::string &s) const { + return std::find_if(std::begin(data), std::end(data), [&s](const auto &d) { return d.first == s; }); + } + + auto size() const { + return data.size(); + } + + auto begin() const { + return data.begin(); + } + + auto end() const { + return data.end(); + } + + + auto begin() { + return data.begin(); + } + + auto end() { + return data.end(); + } + + + JSON &operator[](const std::string &s) { + const auto itr = find(s); + if (itr != data.end()) { + return itr->second; + } else { + data.emplace_back(s, JSON()); + return data.back().second; + } + } + + JSON &at(const std::string &s) { + const auto itr = find(s); + if (itr != data.end()) { + return itr->second; + } else { + throw std::out_of_range("Unknown key: " + s); + } + } + + const JSON &at(const std::string &s) const { + const auto itr = find(s); + if (itr != data.end()) { + return itr->second; + } else { + throw std::out_of_range("Unknown key: " + s); + } + } + + size_t count(const std::string &s) const { + return (find(s) != data.end())?1:0; + } + + std::vector> data; + + using iterator = decltype(data)::iterator; + using const_iterator = decltype(data)::const_iterator; + + + }; + + struct Internal { + template + auto clone(const std::unique_ptr &ptr) { + if (ptr != nullptr) { + return std::make_unique(*ptr); + } else { + return std::unique_ptr(nullptr); + } + } + + Internal( double d ) : Float( d ), Type(Class::Floating) {} + Internal( int64_t l ) : Int( l ), Type(Class::Integral) {} + Internal( bool b ) : Bool( b ), Type(Class::Boolean) {} + Internal( std::string s ) : String(std::make_unique(std::move(s))), Type(Class::String) {} + Internal() : Type(Class::Null) {} + + Internal(Class t_type) { + set_type(t_type); + } + + Internal(const Internal &other) + : List(clone(other.List)), + Map(clone(other.Map)), + String(clone(other.String)), + Float(other.Float), + Int(other.Int), + Bool(other.Bool), + Type(other.Type) + { + } + + Internal &operator=(const Internal &other) + { + List = clone(other.List); + Map = clone(other.Map); + String = clone(other.String); + Float = other.Float; + Int = other.Int; + Bool = other.Bool; + Type = other.Type; + return *this; + } + + void set_type( Class type ) { + if( type == Type ) { + return; + } + + Map.reset(); + List.reset(); + String.reset(); + + switch( type ) { + case Class::Object: Map = std::make_unique(); break; + case Class::Array: List = std::make_unique>(); break; + case Class::String: String = std::make_unique(); break; + case Class::Floating: Float = 0.0; break; + case Class::Integral: Int = 0; break; + case Class::Boolean: Bool = false; break; + case Class::Null: break; + } + + Type = type; + } + + Internal(Internal &&) = default; + Internal &operator=(Internal &&) = default; + + std::unique_ptr> List; + std::unique_ptr Map; + std::unique_ptr String; + double Float = 0; + int64_t Int = 0; + bool Bool = false; + + Class Type = Class::Null; + }; + + Internal internal; + + public: + + template + class JSONWrapper { + Container *object = nullptr; + + public: + JSONWrapper( Container *val ) : object( val ) {} + JSONWrapper( std::nullptr_t ) {} + + typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); } + typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); } + typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); } + typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); } + }; + + template + class JSONConstWrapper { + const Container *object = nullptr; + + public: + JSONConstWrapper( const Container *val ) : object( val ) {} + JSONConstWrapper( std::nullptr_t ) {} + + typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::const_iterator(); } + typename Container::const_iterator end() const { return object ? object->end() : typename Container::const_iterator(); } + }; + + JSON() = default; + JSON( std::nullptr_t ) {} + + explicit JSON(Class type) + : internal(type) + { + } + + JSON( initializer_list list ) + : internal(Class::Object) + { + for( auto i = list.begin(), e = list.end(); i != e; ++i, ++i ) { + operator[]( i->to_string() ) = *std::next( i ); + } + } + + template + explicit JSON( T b, typename enable_if::value>::type* = nullptr ) : internal( static_cast(b) ) {} + + template + explicit JSON( T i, typename enable_if::value && !is_same::value>::type* = nullptr ) : internal( static_cast(i) ) {} + + template + explicit JSON( T f, typename enable_if::value>::type* = nullptr ) : internal( static_cast(f) ) {} + + template + explicit JSON( T s, typename enable_if::value>::type* = nullptr ) : internal( static_cast(s) ) {} + + + + static JSON Load( const std::string & ); + + JSON& operator[]( const std::string &key ) { + internal.set_type( Class::Object ); + return internal.Map->operator[]( key ); + } + + JSON& operator[]( const size_t index ) { + internal.set_type( Class::Array ); + if( index >= internal.List->size() ) { + internal.List->resize( index + 1 ); + } + + return internal.List->operator[]( index ); + } + + + JSON &at( const std::string &key ) { + return operator[]( key ); + } + + const JSON &at( const std::string &key ) const { + return internal.Map->at( key ); + } + + JSON &at( size_t index ) { + return operator[]( index ); + } + + const JSON &at( size_t index ) const { + return internal.List->at( index ); + } + + + long length() const { + if( internal.Type == Class::Array ) { + return static_cast(internal.List->size()); + } else { + return -1; + } + } + + bool has_key( const std::string &key ) const { + if( internal.Type == Class::Object ) { + return internal.Map->count(key) != 0; + } + + return false; + } + + int size() const { + if( internal.Type == Class::Object ) { + return static_cast(internal.Map->size()); + } else if( internal.Type == Class::Array ) { + return static_cast(internal.List->size()); + } else { + return -1; + } + } + + Class JSONType() const { return internal.Type; } + + /// Functions for getting primitives from the JSON object. + bool is_null() const { return internal.Type == Class::Null; } + + std::string to_string() const { bool b; return to_string( b ); } + std::string to_string( bool &ok ) const { + ok = (internal.Type == Class::String); + return ok ? *internal.String : std::string(""); + } + + double to_float() const { bool b; return to_float( b ); } + double to_float( bool &ok ) const { + ok = (internal.Type == Class::Floating); + return ok ? internal.Float : 0.0; + } + + int64_t to_int() const { bool b; return to_int( b ); } + int64_t to_int( bool &ok ) const { + ok = (internal.Type == Class::Integral); + return ok ? internal.Int : 0; + } + + bool to_bool() const { bool b; return to_bool( b ); } + bool to_bool( bool &ok ) const { + ok = (internal.Type == Class::Boolean); + return ok ? internal.Bool : false; + } + + JSONWrapper object_range() { + if( internal.Type == Class::Object ) { + return JSONWrapper( internal.Map.get() ); + } else { + return JSONWrapper( nullptr ); + } + } + + JSONWrapper> array_range() { + if( internal.Type == Class::Array ) { + return JSONWrapper>( internal.List.get() ); + } else { + return JSONWrapper>( nullptr ); + } + } + + JSONConstWrapper object_range() const { + if( internal.Type == Class::Object ) { + return JSONConstWrapper( internal.Map.get() ); + } else { + return JSONConstWrapper( nullptr ); + } + } + + + JSONConstWrapper> array_range() const { + if( internal.Type == Class::Array ) { + return JSONConstWrapper>( internal.List.get() ); + } else { + return JSONConstWrapper>( nullptr ); + } + } + + std::string dump( long depth = 1, std::string tab = " ") const { + switch( internal.Type ) { + case Class::Null: + return "null"; + case Class::Object: { + std::string pad = ""; + for( long i = 0; i < depth; ++i, pad += tab ) { } + + std::string s = "{\n"; + bool skip = true; + for( auto &p : *internal.Map ) { + if( !skip ) { s += ",\n"; } + s += ( pad + "\"" + json_escape(p.first) + "\" : " + p.second.dump( depth + 1, tab ) ); + skip = false; + } + s += ( "\n" + pad.erase( 0, 2 ) + "}" ) ; + return s; + } + case Class::Array: { + std::string s = "["; + bool skip = true; + for( auto &p : *internal.List ) { + if( !skip ) { s += ", "; } + s += p.dump( depth + 1, tab ); + skip = false; + } + s += "]"; + return s; + } + case Class::String: + return "\"" + json_escape( *internal.String ) + "\""; + case Class::Floating: + return std::to_string( internal.Float ); + case Class::Integral: + return std::to_string( internal.Int ); + case Class::Boolean: + return internal.Bool ? "true" : "false"; + } + + throw std::runtime_error("Unhandled JSON type"); + } + + + private: + static std::string json_escape( const std::string &str ) { + std::string output; + for(char i : str) { + switch( i ) { + case '\"': output += "\\\""; break; + case '\\': output += "\\\\"; break; + case '\b': output += "\\b"; break; + case '\f': output += "\\f"; break; + case '\n': output += "\\n"; break; + case '\r': output += "\\r"; break; + case '\t': output += "\\t"; break; + default : output += i; break; + } +} + return output; + } + + + private: +}; + + +struct JSONParser { + static bool isspace(const char c) + { +#ifdef CHAISCRIPT_MSVC + // MSVC warns on these line in some circumstances +#pragma warning(push) +#pragma warning(disable : 6330) +#endif + return ::isspace(c) != 0; +#ifdef CHAISCRIPT_MSVC +#pragma warning(pop) +#endif + + + } + + static void consume_ws( const std::string &str, size_t &offset ) { + while( isspace( str.at(offset) ) && offset <= str.size() ) { ++offset; } + } + + static JSON parse_object( const std::string &str, size_t &offset ) { + JSON Object( JSON::Class::Object ); + + ++offset; + consume_ws( str, offset ); + if( str.at(offset) == '}' ) { + ++offset; return Object; + } + + for (;offset= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) { + val += c; + } else { + throw std::runtime_error(std::string("JSON ERROR: String: Expected hex character in unicode escape, found '") + c + "'"); + } + } + offset += 4; + } break; + default : val += '\\'; break; + } + } else { + val += c; + } + } + ++offset; + return JSON(val); + } + + static JSON parse_number( const std::string &str, size_t &offset ) { + std::string val, exp_str; + char c = '\0'; + bool isDouble = false; + bool isNegative = false; + int64_t exp = 0; + bool isExpNegative = false; + if( offset < str.size() && str.at(offset) == '-' ) { + isNegative = true; + ++offset; + } + for (; offset < str.size() ;) { + c = str.at(offset++); + if( c >= '0' && c <= '9' ) { + val += c; + } else if( c == '.' && !isDouble ) { + val += c; + isDouble = true; + } else { + break; + } + } + if( offset < str.size() && (c == 'E' || c == 'e' )) { + c = str.at(offset++); + if( c == '-' ) { + isExpNegative = true; + } else if( c == '+' ) { + // do nothing + } else { + --offset; + } + + for (; offset < str.size() ;) { + c = str.at(offset++); + if( c >= '0' && c <= '9' ) { + exp_str += c; + } else if( !isspace( c ) && c != ',' && c != ']' && c != '}' ) { + throw std::runtime_error(std::string("JSON ERROR: Number: Expected a number for exponent, found '") + c + "'"); + } + else { + break; + } + } + exp = chaiscript::parse_num( exp_str ) * (isExpNegative?-1:1); + } + else if( offset < str.size() && (!isspace( c ) && c != ',' && c != ']' && c != '}' )) { + throw std::runtime_error(std::string("JSON ERROR: Number: unexpected character '") + c + "'"); + } + --offset; + + if( isDouble ) { + return JSON((isNegative?-1:1) * chaiscript::parse_num( val ) * std::pow( 10, exp )); + } else { + if( !exp_str.empty() ) { + return JSON((isNegative?-1:1) * static_cast(chaiscript::parse_num( val )) * std::pow( 10, exp )); + } else { + return JSON((isNegative?-1:1) * chaiscript::parse_num( val )); + } + } + } + + static JSON parse_bool( const std::string &str, size_t &offset ) { + if( str.substr( offset, 4 ) == "true" ) { + offset += 4; + return JSON(true); + } else if( str.substr( offset, 5 ) == "false" ) { + offset += 5; + return JSON(false); + } else { + throw std::runtime_error(std::string("JSON ERROR: Bool: Expected 'true' or 'false', found '") + str.substr( offset, 5 ) + "'"); + } + } + + static JSON parse_null( const std::string &str, size_t &offset ) { + if( str.substr( offset, 4 ) != "null" ) { + throw std::runtime_error(std::string("JSON ERROR: Null: Expected 'null', found '") + str.substr( offset, 4 ) + "'"); + } + offset += 4; + return JSON(); + } + + static JSON parse_next( const std::string &str, size_t &offset ) { + char value; + consume_ws( str, offset ); + value = str.at(offset); + switch( value ) { + case '[' : return parse_array( str, offset ); + case '{' : return parse_object( str, offset ); + case '\"': return parse_string( str, offset ); + case 't' : + case 'f' : return parse_bool( str, offset ); + case 'n' : return parse_null( str, offset ); + default : if( ( value <= '9' && value >= '0' ) || value == '-' ) { + return parse_number( str, offset ); + } + } + throw std::runtime_error(std::string("JSON ERROR: Parse: Unexpected starting character '") + value + "'"); + } + +}; + +inline JSON JSON::Load( const std::string &str ) { + size_t offset = 0; + return JSONParser::parse_next( str, offset ); +} + +} // End Namespace json + + +#endif diff --git a/chaiscript/utility/json_wrap.hpp b/chaiscript/utility/json_wrap.hpp new file mode 100644 index 0000000..656bf21 --- /dev/null +++ b/chaiscript/utility/json_wrap.hpp @@ -0,0 +1,156 @@ +#ifndef CHAISCRIPT_SIMPLEJSON_WRAP_HPP +#define CHAISCRIPT_SIMPLEJSON_WRAP_HPP + +#include "json.hpp" + +namespace chaiscript +{ + class json_wrap + { + public: + + static Module& library(Module& m) + { + + m.add(chaiscript::fun([](const std::string &t_str) { return from_json(t_str); }), "from_json"); + m.add(chaiscript::fun(&json_wrap::to_json), "to_json"); + + return m; + + } + + private: + + static Boxed_Value from_json(const json::JSON &t_json) + { + switch( t_json.JSONType() ) { + case json::JSON::Class::Null: + return Boxed_Value(); + case json::JSON::Class::Object: + { + std::map m; + + for (const auto &p : t_json.object_range()) + { + m.insert(std::make_pair(p.first, from_json(p.second))); + } + + return Boxed_Value(m); + } + case json::JSON::Class::Array: + { + std::vector vec; + + for (const auto &p : t_json.array_range()) + { + vec.emplace_back(from_json(p)); + } + + return Boxed_Value(vec); + } + case json::JSON::Class::String: + return Boxed_Value(t_json.to_string()); + case json::JSON::Class::Floating: + return Boxed_Value(t_json.to_float()); + case json::JSON::Class::Integral: + return Boxed_Value(t_json.to_int()); + case json::JSON::Class::Boolean: + return Boxed_Value(t_json.to_bool()); + } + + throw std::runtime_error("Unknown JSON type"); + } + + static Boxed_Value from_json(const std::string &t_json) + { + try { + return from_json( json::JSON::Load(t_json) ); + } catch (const std::out_of_range& ) { + throw std::runtime_error("Unparsed JSON input"); + } + } + + static std::string to_json(const Boxed_Value &t_bv) + { + return to_json_object(t_bv).dump(); + } + + static json::JSON to_json_object(const Boxed_Value &t_bv) + { + try { + const std::map m = chaiscript::boxed_cast &>(t_bv); + + json::JSON obj(json::JSON::Class::Object); + for (const auto &o : m) + { + obj[o.first] = to_json_object(o.second); + } + return obj; + } catch (const chaiscript::exception::bad_boxed_cast &) { + // not a map + } + + try { + const std::vector v = chaiscript::boxed_cast &>(t_bv); + + json::JSON obj(json::JSON::Class::Array); + for (size_t i = 0; i < v.size(); ++i) + { + obj[i] = to_json_object(v[i]); + } + return obj; + } catch (const chaiscript::exception::bad_boxed_cast &) { + // not a vector + } + + + try { + Boxed_Number bn(t_bv); + if (Boxed_Number::is_floating_point(t_bv)) + { + return json::JSON(bn.get_as()); + } else { + return json::JSON(bn.get_as()); + } + } catch (const chaiscript::detail::exception::bad_any_cast &) { + // not a number + } + + try { + return json::JSON(boxed_cast(t_bv)); + } catch (const chaiscript::exception::bad_boxed_cast &) { + // not a bool + } + + try { + return json::JSON(boxed_cast(t_bv)); + } catch (const chaiscript::exception::bad_boxed_cast &) { + // not a string + } + + + try { + const chaiscript::dispatch::Dynamic_Object &o = boxed_cast(t_bv); + + json::JSON obj(json::JSON::Class::Object); + for (const auto &attr : o.get_attrs()) + { + obj[attr.first] = to_json_object(attr.second); + } + return obj; + } catch (const chaiscript::exception::bad_boxed_cast &) { + // not a dynamic object + } + + if (t_bv.is_null()) return json::JSON(); // a null value + + throw std::runtime_error("Unknown object type to convert to JSON"); + } + + + }; + + +} + +#endif diff --git a/chaiscript/utility/static_string.hpp b/chaiscript/utility/static_string.hpp new file mode 100644 index 0000000..49edfbd --- /dev/null +++ b/chaiscript/utility/static_string.hpp @@ -0,0 +1,37 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +#ifndef CHAISCRIPT_UTILITY_STATIC_STRING_HPP_ +#define CHAISCRIPT_UTILITY_STATIC_STRING_HPP_ + +namespace chaiscript +{ + namespace utility + { + + struct Static_String + { + template + constexpr Static_String(const char (&str)[N]) + : m_size(N-1), data(&str[0]) + { + } + + constexpr size_t size() const { + return m_size; + } + + constexpr const char *c_str() const { + return data; + } + + const size_t m_size; + const char *data = nullptr; + }; + } +} + +#endif diff --git a/chaiscript/utility/utility.hpp b/chaiscript/utility/utility.hpp new file mode 100644 index 0000000..b743c45 --- /dev/null +++ b/chaiscript/utility/utility.hpp @@ -0,0 +1,123 @@ +// This file is distributed under the BSD License. +// See "license.txt" for details. +// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) +// Copyright 2009-2017, Jason Turner (jason@emptycrate.com) +// http://www.chaiscript.com + +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + + +#ifndef CHAISCRIPT_UTILITY_UTILITY_HPP_ +#define CHAISCRIPT_UTILITY_UTILITY_HPP_ + +#include +#include +#include +#include + +#include "../language/chaiscript_common.hpp" +#include "../dispatchkit/register_function.hpp" +#include "../dispatchkit/operators.hpp" + + +namespace chaiscript +{ + namespace utility + { + + /// Single step command for registering a class with ChaiScript + /// + /// \param[in,out] t_module Model to add class to + /// \param[in] t_class_name Name of the class being registered + /// \param[in] t_constructors Vector of constructors to add + /// \param[in] t_funcs Vector of methods to add + /// + /// \example Adding a basic class to ChaiScript in one step + /// + /// \code + /// chaiscript::utility::add_class(*m, + /// "test", + /// { constructor(), + /// constructor() }, + /// { {fun(&test::function), "function"}, + /// {fun(&test::function2), "function2"}, + /// {fun(&test::function3), "function3"}, + /// {fun(static_cast(&test::function_overload)), "function_overload" }, + /// {fun(static_cast(&test::function_overload)), "function_overload" }, + /// {fun(static_cast(&test::operator=)), "=" } + /// } + /// ); + /// + template + void add_class(ModuleType &t_module, + const std::string &t_class_name, + const std::vector &t_constructors, + const std::vector> &t_funcs) + { + t_module.add(chaiscript::user_type(), t_class_name); + + for(const chaiscript::Proxy_Function &ctor: t_constructors) + { + t_module.add(ctor, t_class_name); + } + + for(const auto &fun: t_funcs) + { + t_module.add(fun.first, fun.second); + } + } + + template + typename std::enable_if::value, void>::type + add_class(ModuleType &t_module, + const std::string &t_class_name, + const std::vector::type, std::string>> &t_constants + ) + { + t_module.add(chaiscript::user_type(), t_class_name); + + t_module.add(chaiscript::constructor(), t_class_name); + t_module.add(chaiscript::constructor(), t_class_name); + + using namespace chaiscript::bootstrap::operators; + equal(t_module); + not_equal(t_module); + assign(t_module); + + t_module.add(chaiscript::fun([](const Enum &e, const int &i) { return e == i; }), "=="); + t_module.add(chaiscript::fun([](const int &i, const Enum &e) { return i == e; }), "=="); + + for (const auto &constant : t_constants) + { + t_module.add_global_const(chaiscript::const_var(Enum(constant.first)), constant.second); + } + } + + template + typename std::enable_if::value, void>::type + add_class(ModuleType &t_module, + const std::string &t_class_name, + const std::vector> &t_constants + ) + { + t_module.add(chaiscript::user_type(), t_class_name); + + t_module.add(chaiscript::constructor(), t_class_name); + t_module.add(chaiscript::constructor(), t_class_name); + + using namespace chaiscript::bootstrap::operators; + equal(t_module); + not_equal(t_module); + assign(t_module); + + for (const auto &constant : t_constants) + { + t_module.add_global_const(chaiscript::const_var(EnumClass(constant.first)), constant.second); + } + } + } +} + +#endif +