diff --git a/.decent_ci-Linux.yaml b/.decent_ci-Linux.yaml index 24990bd8..246241a7 100644 --- a/.decent_ci-Linux.yaml +++ b/.decent_ci-Linux.yaml @@ -12,7 +12,7 @@ compilers: build_tag: AddressSanitizer version: "3.6" skip_packaging: true - cmake_extra_flags: -DUSE_LIBCXX:BOOL=OFF -DBUILD_SAMPLES:BOOL=ON -DBUILD_PACKAGE:BOOL=ON -DBUILD_TESTING:BOOL=ON -DENABLE_ADDRESS_SANITIZER:BOOL=ON + cmake_extra_flags: -DRUN_FUZZY_TESTS:BOOL=TRUE -DUSE_LIBCXX:BOOL=OFF -DBUILD_SAMPLES:BOOL=ON -DBUILD_PACKAGE:BOOL=ON -DBUILD_TESTING:BOOL=ON -DENABLE_ADDRESS_SANITIZER:BOOL=ON - name: "clang" build_tag: ThreadSanitizer version: "3.6" diff --git a/CMakeLists.txt b/CMakeLists.txt index 59c33a36..00f5658b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,13 @@ if(CMAKE_COMPILER_IS_GNUCC OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(LINKER_FLAGS "${LINKER_FLAGS} -flto") endif() + option(GPROF_OUTPUT "Generate profile data" FALSE) + if (GPROF_OUTPUT) + add_definitions(-pg) + set(LINKER_FLAGS "${LINKER_FLAGS} -pg") + endif() + + option(PROFILE_GENERATE "Generate profile data" FALSE) if (PROFILE_GENERATE) add_definitions(-fprofile-generate) @@ -95,7 +102,7 @@ set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/description.txt" set(CPACK_PACKAGE_VERSION_MAJOR 5) set(CPACK_PACKAGE_VERSION_MINOR 7) -set(CPACK_PACKAGE_VERSION_PATCH 1) +set(CPACK_PACKAGE_VERSION_PATCH 2) set(CPACK_PACKAGE_EXECUTABLES "chai;ChaiScript Eval") set(CPACK_PACKAGE_VENDOR "ChaiScript.com") @@ -174,7 +181,7 @@ else() add_definitions(-Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wcast-qual -Wunused -Woverloaded-virtual -pedantic ${CPP11_FLAG}) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - add_definitions(-Weverything -Wno-c++98-compat -Wno-documentation -Wno-switch-enum -Wno-weak-vtables -Wno-sign-conversion -Wno-missing-prototypes -Wno-padded -Wno-missing-noreturn -Wno-exit-time-destructors) + add_definitions(-Weverything -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-documentation -Wno-switch-enum -Wno-weak-vtables -Wno-sign-conversion -Wno-missing-prototypes -Wno-padded -Wno-missing-noreturn -Wno-exit-time-destructors) else() add_definitions(-Wnoexcept) endif() @@ -253,10 +260,14 @@ add_dependencies(chai chaiscript_stdlib-${CHAI_VERSION}) if(BUILD_SAMPLES) add_executable(example samples/example.cpp) target_link_libraries(example ${LIBS}) + add_executable(test_num_exceptions samples/test_num_exceptions.cpp) + target_link_libraries(test_num_exceptions ${LIBS}) add_executable(memory_leak_test samples/memory_leak_test.cpp) target_link_libraries(memory_leak_test ${LIBS}) add_executable(inheritance samples/inheritance.cpp) target_link_libraries(inheritance ${LIBS}) + add_executable(factory samples/factory.cpp) + target_link_libraries(factory ${LIBS}) add_executable(fun_call_performance samples/fun_call_performance.cpp) target_link_libraries(fun_call_performance ${LIBS}) endif() diff --git a/cheatsheet.md b/cheatsheet.md index 9ff4ad4b..5e785535 100644 --- a/cheatsheet.md +++ b/cheatsheet.md @@ -354,6 +354,19 @@ o.f = fun(y) { print(this.x + y); } o.f(10); // prints 13 ``` +### Option Explicit + +If you want to disable dynamic parameter definitions, you can `set_explicit`. + +``` +class My_Class { + def My_Class() { + this.set_explicit(true); + this.x = 2; // this would fail with explicit set to true + } +}; +``` + ## method_missing A function of the signature `method_missing(object, name, param1, param2, param3)` will be called if an appropriate diff --git a/contrib/codeanalysis/profile_cpp_calls.chai b/contrib/codeanalysis/profile_cpp_calls.chai new file mode 100644 index 00000000..011f9a19 --- /dev/null +++ b/contrib/codeanalysis/profile_cpp_calls.chai @@ -0,0 +1,26 @@ + +var test_str = "bob was a string"; + +for( var i = 0; i < 200000; ++i) +{ + test_str.size(); +// test_str.find("a", i); + test_str.c_str(); + test_str.erase_at(1); + test_str.erase_at(1); + test_str.erase_at(1); + test_str.erase_at(1); + test_str.erase_at(1); + test_str.erase_at(1); + + size(test_str); +// test_str.find("a", i); + c_str(test_str); + erase_at(test_str, 1); + erase_at(test_str, 1); + erase_at(test_str, 1); + erase_at(test_str, 1); + erase_at(test_str, 1); + erase_at(test_str, 1); + test_str = "bob was a string"; +} diff --git a/include/chaiscript/chaiscript_defines.hpp b/include/chaiscript/chaiscript_defines.hpp index 5b3bea5b..c2ba4ae6 100644 --- a/include/chaiscript/chaiscript_defines.hpp +++ b/include/chaiscript/chaiscript_defines.hpp @@ -39,7 +39,11 @@ #define CHAISCRIPT_GCC_4_6 #endif -#if (defined(__GNUC__) && __GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) || defined(CHAISCRIPT_MSVC) || defined(__llvm__) +#if defined(__llvm__) +#define CHAISCRIPT_CLANG +#endif + +#if (defined(__GNUC__) && __GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) || defined(CHAISCRIPT_MSVC) || defined(CHAISCRIPT_CLANG) #define CHAISCRIPT_OVERRIDE override #else #define CHAISCRIPT_OVERRIDE @@ -52,7 +56,7 @@ #define CHAISCRIPT_MODULE_EXPORT extern "C" #endif -#ifdef CHAISCRIPT_MSVC +#ifdef CHAISCRIPT_MSVC_12 #define CHAISCRIPT_NOEXCEPT throw() #define CHAISCRIPT_CONSTEXPR #else @@ -65,7 +69,7 @@ namespace chaiscript { static const int version_major = 5; static const int version_minor = 7; - static const int version_patch = 1; + static const int version_patch = 2; template inline std::shared_ptr make_shared(Arg && ... arg) diff --git a/include/chaiscript/chaiscript_stdlib.hpp b/include/chaiscript/chaiscript_stdlib.hpp index c07e64c6..4b38f2cd 100644 --- a/include/chaiscript/chaiscript_stdlib.hpp +++ b/include/chaiscript/chaiscript_stdlib.hpp @@ -19,6 +19,7 @@ #include "dispatchkit/bootstrap_stl.hpp" #include "dispatchkit/boxed_value.hpp" #include "language/chaiscript_prelude.chai" +#include "utility/json_wrap.hpp" #ifndef CHAISCRIPT_NO_THREADS #include @@ -51,6 +52,8 @@ namespace chaiscript lib->add(chaiscript::fun([](const std::function &t_func){ return std::async(std::launch::async, t_func);}), "async"); #endif + lib->add(json_wrap::library()); + lib->eval(ChaiScript_Prelude::chaiscript_prelude() /*, "standard prelude"*/ ); return lib; diff --git a/include/chaiscript/dispatchkit/bootstrap.hpp b/include/chaiscript/dispatchkit/bootstrap.hpp index 1624e023..7e3a3015 100644 --- a/include/chaiscript/dispatchkit/bootstrap.hpp +++ b/include/chaiscript/dispatchkit/bootstrap.hpp @@ -440,6 +440,8 @@ namespace chaiscript 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(static_cast(&dispatch::Dynamic_Object::get_attr)), "get_attr"); m->add(fun(static_cast(&dispatch::Dynamic_Object::get_attr)), "get_attr"); @@ -450,7 +452,13 @@ namespace chaiscript m->add(fun(static_cast(&dispatch::Dynamic_Object::get_attr)), "[]"); m->add(fun(static_cast(&dispatch::Dynamic_Object::get_attr)), "[]"); - m->eval("def Dynamic_Object::clone() { auto &new_o = Dynamic_Object(this.get_type_name()); for_each(this.get_attrs(), bind(fun(new_o, x) { new_o.get_attr(x.first) = x.second; }, new_o, _) ); return new_o; }"); + m->eval(R""( + 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; + } + )""); m->add(fun(&has_guard), "has_guard"); m->add(fun(&get_guard), "get_guard"); @@ -460,9 +468,12 @@ namespace chaiscript 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"); @@ -485,6 +496,7 @@ namespace chaiscript basic_constructors("bool", m); operators::assign(m); operators::equal(m); + operators::not_equal(m); m->add(fun([](const std::string &s) -> std::string { return s; }), "to_string"); m->add(fun(&Bootstrap::bool_to_string), "to_string"); @@ -502,6 +514,8 @@ namespace chaiscript 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); @@ -556,13 +570,14 @@ namespace chaiscript "eval_error", { }, { {fun(&chaiscript::exception::eval_error::reason), "reason"}, - {fun(std::function (const chaiscript::exception::eval_error &t_eval_error)>([](const chaiscript::exception::eval_error &t_eval_error) -> std::vector { - 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"} } + {fun(&chaiscript::exception::eval_error::pretty_print), "pretty_print"}, + {fun(std::function (const chaiscript::exception::eval_error &t_eval_error)>([](const chaiscript::exception::eval_error &t_eval_error) -> std::vector { + 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"} } ); @@ -588,7 +603,7 @@ namespace chaiscript std::vector retval; std::transform(t_node.children.begin(), t_node.children.end(), std::back_inserter(retval), - &chaiscript::var>); + &chaiscript::var &>); return retval; })), "children"}, {fun(&AST_Node::replace_child), "replace_child"} diff --git a/include/chaiscript/dispatchkit/bootstrap_stl.hpp b/include/chaiscript/dispatchkit/bootstrap_stl.hpp index 1a03888f..8b559811 100644 --- a/include/chaiscript/dispatchkit/bootstrap_stl.hpp +++ b/include/chaiscript/dispatchkit/bootstrap_stl.hpp @@ -334,7 +334,12 @@ namespace chaiscript "# Pushes the second value onto the container while making a clone of the value\n" "def push_back(" + type + " container, x)\n" "{ \n" - " container.push_back_ref(clone(x)) \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" ); @@ -370,7 +375,12 @@ namespace chaiscript "# 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" - " container.push_front_ref(clone(x)) \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"; @@ -460,6 +470,30 @@ namespace chaiscript 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); @@ -515,7 +549,7 @@ namespace chaiscript if (typeid(VectorType) == typeid(std::vector)) { m->eval(R"( - def Vector::`==`(rhs) : type_match(rhs, this) { + def Vector::`==`(Vector rhs) { if ( rhs.size() != this.size() ) { return false; } else { diff --git a/include/chaiscript/dispatchkit/boxed_cast_helper.hpp b/include/chaiscript/dispatchkit/boxed_cast_helper.hpp index 8f478bf4..2e087776 100644 --- a/include/chaiscript/dispatchkit/boxed_cast_helper.hpp +++ b/include/chaiscript/dispatchkit/boxed_cast_helper.hpp @@ -33,14 +33,14 @@ namespace chaiscript template struct Cast_Helper_Inner { - typedef std::reference_wrapper::type > Result_Type; + typedef typename std::add_const::type Result_Type; static Result_Type cast(const Boxed_Value &ob, const Type_Conversions *) { if (ob.get_type_info().bare_equal_type_info(typeid(Result))) { auto p = throw_if_null(ob.get_const_ptr()); - return std::cref(*static_cast(p)); + return *static_cast(p); } else { throw chaiscript::detail::exception::bad_any_cast(); } @@ -52,12 +52,6 @@ namespace chaiscript { }; - /// Cast_Helper_Inner for casting to a const & type - template - struct Cast_Helper_Inner : Cast_Helper_Inner - { - }; - /// Cast_Helper_Inner for casting to a const * type template @@ -68,7 +62,7 @@ namespace chaiscript { if (ob.get_type_info().bare_equal_type_info(typeid(Result))) { - return static_cast(throw_if_null(ob.get_const_ptr())); + return static_cast(ob.get_const_ptr()); } else { throw chaiscript::detail::exception::bad_any_cast(); } @@ -84,7 +78,36 @@ namespace chaiscript { if (!ob.get_type_info().is_const() && ob.get_type_info() == typeid(Result)) { - return static_cast(throw_if_null(ob.get_ptr())); + return static_cast(ob.get_ptr()); + } else { + throw chaiscript::detail::exception::bad_any_cast(); + } + } + }; + + 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 + { + typedef const Result& Result_Type; + + static Result_Type cast(const Boxed_Value &ob, const Type_Conversions *) + { + if (ob.get_type_info().bare_equal_type_info(typeid(Result))) + { + auto p = throw_if_null(ob.get_const_ptr()); + return *static_cast(p); } else { throw chaiscript::detail::exception::bad_any_cast(); } @@ -166,7 +189,7 @@ namespace chaiscript template<> struct Cast_Helper_Inner { - typedef const Boxed_Value & Result_Type; + typedef Boxed_Value Result_Type; static Result_Type cast(const Boxed_Value &ob, const Type_Conversions *) { @@ -178,11 +201,11 @@ namespace chaiscript template<> struct Cast_Helper_Inner { - typedef Boxed_Value& Result_Type; + typedef std::reference_wrapper Result_Type; static Result_Type cast(const Boxed_Value &ob, const Type_Conversions *) { - return const_cast(ob); + return std::ref(const_cast(ob)); } }; diff --git a/include/chaiscript/dispatchkit/boxed_number.hpp b/include/chaiscript/dispatchkit/boxed_number.hpp index c953886e..dd5bf8c9 100644 --- a/include/chaiscript/dispatchkit/boxed_number.hpp +++ b/include/chaiscript/dispatchkit/boxed_number.hpp @@ -100,6 +100,7 @@ namespace chaiscript :(Common_Types::t_uint64); } + static Common_Types get_common_type(const Boxed_Value &t_bv) { const Type_Info &inp_ = t_bv.get_type_info(); @@ -120,8 +121,12 @@ namespace chaiscript 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)) { @@ -513,6 +518,21 @@ namespace chaiscript validate_boxed_number(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))) { @@ -537,8 +557,12 @@ namespace chaiscript 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))) { diff --git a/include/chaiscript/dispatchkit/boxed_value.hpp b/include/chaiscript/dispatchkit/boxed_value.hpp index a6d9c5b1..1c2ebe72 100644 --- a/include/chaiscript/dispatchkit/boxed_value.hpp +++ b/include/chaiscript/dispatchkit/boxed_value.hpp @@ -297,6 +297,13 @@ namespace chaiscript 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) CHAISCRIPT_NOEXCEPT @@ -330,9 +337,9 @@ namespace chaiscript /// /// @sa @ref adding_objects template - Boxed_Value var(T t) + Boxed_Value var(T &&t) { - return Boxed_Value(t); + return Boxed_Value(std::forward(t)); } namespace detail { diff --git a/include/chaiscript/dispatchkit/callable_traits.hpp b/include/chaiscript/dispatchkit/callable_traits.hpp index 9954eeab..cc1b2e70 100644 --- a/include/chaiscript/dispatchkit/callable_traits.hpp +++ b/include/chaiscript/dispatchkit/callable_traits.hpp @@ -61,6 +61,17 @@ namespace chaiscript { Ret (Class::*m_func)(Param...); }; + template + struct Arity + { + }; + + template + struct Arity + { + static const size_t arity = sizeof...(Params); + }; + template struct Function_Signature diff --git a/include/chaiscript/dispatchkit/dispatchkit.hpp b/include/chaiscript/dispatchkit/dispatchkit.hpp index 43cb7b12..aa094867 100644 --- a/include/chaiscript/dispatchkit/dispatchkit.hpp +++ b/include/chaiscript/dispatchkit/dispatchkit.hpp @@ -417,9 +417,9 @@ namespace chaiscript struct State { - std::map > m_functions; - std::map m_function_objects; - std::map m_boxed_functions; + std::vector>>> m_functions; + std::vector> m_function_objects; + std::vector> m_boxed_functions; std::map m_global_objects; Type_Name_Map m_types; std::set m_reserved_words; @@ -482,24 +482,31 @@ namespace chaiscript 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 + 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); + } + + get_stack_data(t_holder).back().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) { - auto &stack_elem = get_stack_data().back(); - - if (std::any_of(stack_elem.begin(), stack_elem.end(), - [&](const std::pair &o) { - return o.first == name; - })) - { - throw chaiscript::exception::name_conflict_error(name); - } - - get_stack_data().back().emplace_back(name, std::move(obj)); - + add_object(name, std::move(obj), get_stack_holder()); } /// Adds a new global shared object, between all the threads @@ -511,7 +518,7 @@ namespace chaiscript throw chaiscript::exception::global_non_const(); } - chaiscript::detail::threading::unique_lock l(m_global_object_mutex); + chaiscript::detail::threading::unique_lock l(m_mutex); if (m_state.m_global_objects.find(name) != m_state.m_global_objects.end()) { @@ -526,7 +533,7 @@ namespace chaiscript { validate_object_name(name); - chaiscript::detail::threading::unique_lock l(m_global_object_mutex); + 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()) @@ -544,7 +551,7 @@ namespace chaiscript { validate_object_name(name); - chaiscript::detail::threading::unique_lock l(m_global_object_mutex); + chaiscript::detail::threading::unique_lock l(m_mutex); if (m_state.m_global_objects.find(name) != m_state.m_global_objects.end()) { @@ -641,19 +648,21 @@ namespace chaiscript 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? - { - chaiscript::detail::threading::shared_lock l(m_global_object_mutex); + // 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; - } + const auto itr = m_state.m_global_objects.find(name); + if (itr != m_state.m_global_objects.end()) + { + return itr->second; } - // If all that failed, then check to see if it's a function - return get_function_object(name); + // no? is it a function object? + auto obj = get_function_object_int(name, loc); + if (obj.first != loc) t_loc.store(uint_fast32_t(obj.first), std::memory_order_relaxed); + return obj.second; + + } /// Registers a new named type @@ -711,20 +720,29 @@ namespace chaiscript 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.load(std::memory_order_relaxed); + auto method_missing_funs = get_function("method_missing", method_missing_loc); + if (method_missing_funs.first != method_missing_loc) m_method_missing_loc.store(uint_fast32_t(method_missing_funs.first), std::memory_order_relaxed); + return std::move(method_missing_funs.second); + } + + /// Return a function by name - std::vector< Proxy_Function > get_function(const std::string &t_name) const + 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 = funs.find(t_name); + auto itr = find_keyed_value(funs, t_name, t_hint); if (itr != funs.end()) { - return itr->second; + return std::make_pair(std::distance(funs.begin(), itr), itr->second); } else { - return std::vector(); + return std::make_pair(size_t(0), std::make_shared>()); } } @@ -732,28 +750,36 @@ namespace chaiscript /// \throws std::range_error if it does not Boxed_Value get_function_object(const std::string &t_name) const { -// std::cout << "Getting function object: " << t_name << '\n'; 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 = funs.find(t_name); + auto itr = find_keyed_value(funs, t_name, t_hint); if (itr != funs.end()) { - return itr->second; + 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 functions.find(name) != functions.end(); + 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, @@ -810,11 +836,8 @@ namespace chaiscript } // add the global values - { - chaiscript::detail::threading::shared_lock l(m_global_object_mutex); - - retval.insert(m_state.m_global_objects.begin(), m_state.m_global_objects.end()); - } + chaiscript::detail::threading::shared_lock l(m_mutex); + retval.insert(m_state.m_global_objects.begin(), m_state.m_global_objects.end()); return retval; } @@ -851,7 +874,7 @@ namespace chaiscript for (const auto & function : functions) { - for (const auto & internal_func : function.second) + for (const auto & internal_func : *function.second) { rets.emplace_back(function.first, internal_func); } @@ -896,38 +919,54 @@ namespace chaiscript #pragma warning(push) #pragma warning(disable : 4715) #endif - Boxed_Value call_member(const std::string &t_name, const std::vector ¶ms, bool t_has_params) const + 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 auto funs = get_function(t_name); + uint_fast32_t loc = t_loc.load(std::memory_order_relaxed); + const auto funs = get_function(t_name, loc); + if (funs.first != loc) t_loc.store(uint_fast32_t(funs.first), std::memory_order_relaxed); const auto do_attribute_call = [this](int l_num_params, const std::vector &l_params, const std::vector &l_funs, const Type_Conversions &l_conversions)->Boxed_Value { std::vector attr_params{l_params.begin(), l_params.begin() + l_num_params}; - std::vector remaining_params{l_params.begin() + l_num_params, l_params.end()}; Boxed_Value bv = dispatch::dispatch(l_funs, attr_params, l_conversions); - if (!remaining_params.empty() || bv.get_type_info().bare_equal(user_type())) { + 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()); + auto func = boxed_cast>(bv); try { - return (*func)(remaining_params, l_conversions); + 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(remaining_params, std::vector{func}); + throw chaiscript::exception::dispatch_error({l_params.begin() + l_num_params, l_params.end()}, std::vector{func}); } else { return bv; } }; - if (is_attribute_call(funs, params, t_has_params)) { - return do_attribute_call(1, params, funs, m_conversions); + if (is_attribute_call(*funs.second, params, t_has_params)) { + return do_attribute_call(1, params, *funs.second, m_conversions); } else { std::exception_ptr except; - if (!funs.empty()) { + if (!funs.second->empty()) { try { - return dispatch::dispatch(funs, params, m_conversions); + return dispatch::dispatch(*funs.second, params, m_conversions); } catch(chaiscript::exception::dispatch_error&) { except = std::current_exception(); } @@ -939,7 +978,9 @@ namespace chaiscript const auto functions = [&]()->std::vector { std::vector fs; - for (const auto &f : get_function("method_missing")) + const auto method_missing_funs = get_method_missing_functions(); + + for (const auto &f : *method_missing_funs) { if(f->compare_first_type(params[0], m_conversions)) { fs.push_back(f); @@ -961,12 +1002,17 @@ namespace chaiscript }(); if (!functions.empty()) { - 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, m_conversions); - } else { - return dispatch::dispatch(functions, {params[0], var(t_name), var(std::vector(params.begin()+1, params.end()))}, m_conversions); + 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, m_conversions); + } else { + return dispatch::dispatch(functions, {params[0], var(t_name), var(std::vector(params.begin()+1, params.end()))}, m_conversions); + } + } catch (const dispatch::option_explicit_set &e) { + throw chaiscript::exception::dispatch_error(params, std::vector(funs.second->begin(), funs.second->end()), + e.what()); } } @@ -975,7 +1021,7 @@ namespace chaiscript if (except) { std::rethrow_exception(except); } else { - throw chaiscript::exception::dispatch_error(params, std::vector(funs.begin(), funs.end())); + throw chaiscript::exception::dispatch_error(params, std::vector(funs.second->begin(), funs.second->end())); } } } @@ -985,30 +1031,14 @@ namespace chaiscript - Boxed_Value call_function(const std::string &t_name, const std::vector ¶ms) const + Boxed_Value call_function(const std::string &t_name, std::atomic_uint_fast32_t &t_loc, const std::vector ¶ms) const { - Boxed_Value bv = dispatch::dispatch(get_function(t_name), params, m_conversions); - // the result of a clone is never to be marked as a return_value - if (t_name == "clone") { - bv.reset_return_value(); - } - return bv; + uint_fast32_t loc = t_loc.load(std::memory_order_relaxed); + const auto funs = get_function(t_name, loc); + if (funs.first != loc) t_loc.store(uint_fast32_t(funs.first), std::memory_order_relaxed); + return dispatch::dispatch(*funs.second, params, m_conversions); } - Boxed_Value call_function(const std::string &t_name) const - { - return call_function(t_name, std::vector()); - } - - Boxed_Value call_function(const std::string &t_name, Boxed_Value p1) const - { - return call_function(t_name, std::vector({std::move(p1)})); - } - - Boxed_Value call_function(const std::string &t_name, Boxed_Value p1, Boxed_Value p2) const - { - return call_function(t_name, std::vector({std::move(p1), std::move(p2)})); - } /// Dump object info to stdout void dump_object(const Boxed_Value &o) const @@ -1119,7 +1149,6 @@ namespace chaiscript State get_state() const { chaiscript::detail::threading::shared_lock l(m_mutex); - chaiscript::detail::threading::shared_lock l2(m_global_object_mutex); return m_state; } @@ -1127,7 +1156,6 @@ namespace chaiscript void set_state(const State &t_state) { chaiscript::detail::threading::unique_lock l(m_mutex); - chaiscript::detail::threading::unique_lock l2(m_global_object_mutex); m_state = t_state; } @@ -1205,7 +1233,6 @@ namespace chaiscript return *m_stack_holder; } - private: /// Returns the current stack /// make const/non const versions const StackData &get_stack_data() const @@ -1223,32 +1250,34 @@ namespace chaiscript return m_stack_holder->stacks.back(); } - const std::map &get_boxed_functions_int() const + private: + + const std::vector> &get_boxed_functions_int() const { return m_state.m_boxed_functions; } - std::map &get_boxed_functions_int() + std::vector> &get_boxed_functions_int() { return m_state.m_boxed_functions; } - const std::map &get_function_objects_int() const + const std::vector> &get_function_objects_int() const { return m_state.m_function_objects; } - std::map &get_function_objects_int() + std::vector> &get_function_objects_int() { return m_state.m_function_objects; } - const std::map > &get_functions_int() const + const std::vector>>> &get_functions_int() const { return m_state.m_functions; } - std::map > &get_functions_int() + std::vector>>> &get_functions_int() { return m_state.m_functions; } @@ -1362,6 +1391,49 @@ namespace chaiscript } } + 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 t_c.begin() + 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) @@ -1370,16 +1442,13 @@ namespace chaiscript auto &funcs = get_functions_int(); - auto itr = funcs.find(t_name); - - auto &func_objs = get_function_objects_int(); - auto &boxed_funcs = get_boxed_functions_int(); + auto itr = find_keyed_value(funcs, t_name); Proxy_Function new_func = [&]() -> Proxy_Function { if (itr != funcs.end()) { - auto &vec = itr->second; + auto vec = *itr->second; for (const auto &func : vec) { if ((*t_f) == *(func)) @@ -1388,33 +1457,35 @@ namespace chaiscript } } + vec.reserve(vec.size() + 1); // tightly control vec growth vec.push_back(t_f); std::stable_sort(vec.begin(), vec.end(), &function_less_than); - return std::make_shared(vec); + 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.insert(std::make_pair(t_name, vec)); + funcs.emplace_back(t_name, std::make_shared>(vec)); return std::make_shared(std::move(vec)); } else { - funcs.insert(std::make_pair(t_name, std::vector{t_f})); + funcs.emplace_back(t_name, std::make_shared>(std::initializer_list({t_f}))); return t_f; } }(); - boxed_funcs[t_name] = const_var(new_func); - func_objs[t_name] = std::move(new_func); + 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; - mutable chaiscript::detail::threading::shared_mutex m_global_object_mutex; Type_Conversions m_conversions; chaiscript::detail::threading::Thread_Storage m_stack_holder; + mutable std::atomic_uint_fast32_t m_method_missing_loc; State m_state; }; @@ -1440,6 +1511,10 @@ namespace chaiscript return m_stack_holder.get(); } + void add_object(const std::string &t_name, Boxed_Value obj) const { + m_engine.get().add_object(t_name, std::move(obj), m_stack_holder.get()); + } + private: std::reference_wrapper m_engine; std::reference_wrapper m_stack_holder; diff --git a/include/chaiscript/dispatchkit/dynamic_object.hpp b/include/chaiscript/dispatchkit/dynamic_object.hpp index 5a9ccaa7..1e79eeab 100644 --- a/include/chaiscript/dispatchkit/dynamic_object.hpp +++ b/include/chaiscript/dispatchkit/dynamic_object.hpp @@ -24,23 +24,55 @@ namespace chaiscript { namespace dispatch { + struct option_explicit_set : std::runtime_error { + 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; + + virtual ~option_explicit_set() CHAISCRIPT_NOEXCEPT {} + }; + class Dynamic_Object { public: Dynamic_Object(std::string t_type_name) - : m_type_name(std::move(t_type_name)) + : m_type_name(std::move(t_type_name)), m_option_explicit(false) { } - Dynamic_Object() : m_type_name("") + Dynamic_Object() : m_type_name(""), m_option_explicit(false) { } + 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); @@ -59,11 +91,19 @@ namespace chaiscript 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); } @@ -75,6 +115,7 @@ namespace chaiscript private: std::string m_type_name; + bool m_option_explicit; std::map m_attrs; }; diff --git a/include/chaiscript/dispatchkit/dynamic_object_detail.hpp b/include/chaiscript/dispatchkit/dynamic_object_detail.hpp index 9d501aed..f1e20a1d 100644 --- a/include/chaiscript/dispatchkit/dynamic_object_detail.hpp +++ b/include/chaiscript/dispatchkit/dynamic_object_detail.hpp @@ -232,7 +232,7 @@ namespace chaiscript protected: virtual Boxed_Value do_call(const std::vector ¶ms, const Type_Conversions &t_conversions) const CHAISCRIPT_OVERRIDE { - auto bv = var(Dynamic_Object(m_type_name)); + 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()); diff --git a/include/chaiscript/dispatchkit/function_call.hpp b/include/chaiscript/dispatchkit/function_call.hpp index 3c8e3c53..c5b950a1 100644 --- a/include/chaiscript/dispatchkit/function_call.hpp +++ b/include/chaiscript/dispatchkit/function_call.hpp @@ -14,6 +14,7 @@ #include "boxed_cast.hpp" #include "function_call_detail.hpp" #include "proxy_functions.hpp" +#include "callable_traits.hpp" namespace chaiscript { class Boxed_Value; @@ -37,6 +38,15 @@ namespace chaiscript std::function functor(const std::vector &funcs, const Type_Conversions *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); } diff --git a/include/chaiscript/dispatchkit/proxy_functions.hpp b/include/chaiscript/dispatchkit/proxy_functions.hpp index 1eee0602..6214b4dd 100644 --- a/include/chaiscript/dispatchkit/proxy_functions.hpp +++ b/include/chaiscript/dispatchkit/proxy_functions.hpp @@ -211,7 +211,7 @@ namespace chaiscript if (ti.is_undef() || ti.bare_equal(user_type()) || (!bv.get_type_info().is_undef() - && (ti.bare_equal(user_type()) + && ( (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()) @@ -248,7 +248,7 @@ namespace chaiscript } - static bool compare_types(const std::vector &tis, const std::vector &bvs) + static bool compare_types(const std::vector &tis, const std::vector &bvs, const Type_Conversions &t_conversions) { if (tis.size() - 1 != bvs.size()) { @@ -257,10 +257,7 @@ namespace chaiscript const size_t size = bvs.size(); for (size_t i = 0; i < size; ++i) { - if (!(tis[i+1].bare_equal(bvs[i].get_type_info()) && tis[i+1].is_const() >= bvs[i].get_type_info().is_const() )) - { - return false; - } + if (!compare_type_to_param(tis[i + 1], bvs[i], t_conversions)) { return false; } } } return true; @@ -578,7 +575,7 @@ namespace chaiscript virtual bool call_match(const std::vector &vals, const Type_Conversions &t_conversions) const CHAISCRIPT_OVERRIDE { - return static_cast(vals.size()) == get_arity() && (compare_types(m_types, vals) || compare_types_with_cast(vals, t_conversions)); + 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 &t_conversions) const = 0; @@ -763,6 +760,14 @@ namespace chaiscript { } + 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; virtual ~dispatch_error() CHAISCRIPT_NOEXCEPT {} diff --git a/include/chaiscript/dispatchkit/type_info.hpp b/include/chaiscript/dispatchkit/type_info.hpp index 0d5c5f38..5be3a2ef 100644 --- a/include/chaiscript/dispatchkit/type_info.hpp +++ b/include/chaiscript/dispatchkit/type_info.hpp @@ -32,17 +32,17 @@ namespace chaiscript CHAISCRIPT_CONSTEXPR Type_Info(bool t_is_const, bool t_is_reference, bool t_is_pointer, bool t_is_void, 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_is_const(t_is_const), m_is_reference(t_is_reference), m_is_pointer(t_is_pointer), - m_is_void(t_is_void), m_is_arithmetic(t_is_arithmetic), - m_is_undef(false) + m_flags((t_is_const << is_const_flag) + + (t_is_reference << is_reference_flag) + + (t_is_pointer << is_pointer_flag) + + (t_is_void << is_void_flag) + + (t_is_arithmetic << is_arithmetic_flag)) { } CHAISCRIPT_CONSTEXPR Type_Info() : m_type_info(nullptr), m_bare_type_info(nullptr), - m_is_const(false), m_is_reference(false), m_is_pointer(false), - m_is_void(false), m_is_arithmetic(false), - m_is_undef(true) + m_flags(1 << is_undef_flag) { } @@ -83,12 +83,12 @@ namespace chaiscript && (*m_bare_type_info) == ti; } - CHAISCRIPT_CONSTEXPR bool is_const() const CHAISCRIPT_NOEXCEPT { return m_is_const; } - CHAISCRIPT_CONSTEXPR bool is_reference() const CHAISCRIPT_NOEXCEPT { return m_is_reference; } - CHAISCRIPT_CONSTEXPR bool is_void() const CHAISCRIPT_NOEXCEPT { return m_is_void; } - CHAISCRIPT_CONSTEXPR bool is_arithmetic() const CHAISCRIPT_NOEXCEPT { return m_is_arithmetic; } - CHAISCRIPT_CONSTEXPR bool is_undef() const CHAISCRIPT_NOEXCEPT { return m_is_undef; } - CHAISCRIPT_CONSTEXPR bool is_pointer() const CHAISCRIPT_NOEXCEPT { return m_is_pointer; } + CHAISCRIPT_CONSTEXPR bool is_const() const CHAISCRIPT_NOEXCEPT { return m_flags & (1 << is_const_flag); } + CHAISCRIPT_CONSTEXPR bool is_reference() const CHAISCRIPT_NOEXCEPT { return m_flags & (1 << is_reference_flag); } + CHAISCRIPT_CONSTEXPR bool is_void() const CHAISCRIPT_NOEXCEPT { return m_flags & (1 << is_void_flag); } + CHAISCRIPT_CONSTEXPR bool is_arithmetic() const CHAISCRIPT_NOEXCEPT { return m_flags & (1 << is_arithmetic_flag); } + CHAISCRIPT_CONSTEXPR bool is_undef() const CHAISCRIPT_NOEXCEPT { return m_flags & (1 << is_undef_flag); } + CHAISCRIPT_CONSTEXPR bool is_pointer() const CHAISCRIPT_NOEXCEPT { return m_flags & (1 << is_pointer_flag); } std::string name() const { @@ -118,12 +118,13 @@ namespace chaiscript private: const std::type_info *m_type_info; const std::type_info *m_bare_type_info; - bool m_is_const; - bool m_is_reference; - bool m_is_pointer; - bool m_is_void; - bool m_is_arithmetic; - bool m_is_undef; + unsigned int m_flags; + 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; }; namespace detail diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index 3a46c96c..259946f1 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -34,7 +34,7 @@ namespace chaiscript /// Types of AST nodes available to the parser and eval class AST_Node_Type { public: - enum Type { Error, Int, Float, Id, Char, Str, Eol, Fun_Call, Inplace_Fun_Call, Arg_List, Variable, Equation, Var_Decl, + enum Type { Error, Int, Float, Id, Char, Str, Eol, Fun_Call, Arg_List, Variable, Equation, Var_Decl, Comparison, Addition, Subtraction, Multiplication, Division, Modulus, Array_Call, Dot_Access, Quoted_String, Single_Quoted_String, Lambda, Block, Def, While, If, For, Inline_Array, Inline_Map, Return, File, Prefix, Break, Continue, Map_Pair, Value_Range, Inline_Range, Annotation, Try, Catch, Finally, Method, Attr_Decl, Shift, Equality, Bitwise_And, Bitwise_Xor, Bitwise_Or, @@ -47,7 +47,7 @@ namespace chaiscript /// Helper lookup to get the name of each node type const char *ast_node_type_to_string(int ast_node_type) { - const char *ast_node_types[] = { "Internal Parser Error", "Int", "Float", "Id", "Char", "Str", "Eol", "Fun_Call", "Inplace_Fun_Call", "Arg_List", "Variable", "Equation", "Var_Decl", + const char *ast_node_types[] = { "Internal Parser Error", "Int", "Float", "Id", "Char", "Str", "Eol", "Fun_Call", "Arg_List", "Variable", "Equation", "Var_Decl", "Comparison", "Addition", "Subtraction", "Multiplication", "Division", "Modulus", "Array_Call", "Dot_Access", "Quoted_String", "Single_Quoted_String", "Lambda", "Block", "Def", "While", "If", "For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", "Inline_Range", "Annotation", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Shift", "Equality", "Bitwise_And", "Bitwise_Xor", "Bitwise_Or", diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index a6a604ab..4764c671 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -36,7 +36,9 @@ #else #ifdef CHAISCRIPT_WINDOWS #define VC_EXTRA_LEAN +#if !defined(WIN32_LEAN_AND_MEAN) #define WIN32_LEAN_AND_MEAN +#endif #include #endif #endif @@ -284,14 +286,7 @@ namespace chaiscript - const Boxed_Value internal_eval_ast(const AST_NodePtr &t_ast) - { - try { - return t_ast->eval(m_engine); - } catch (const exception::eval_error &t_ee) { - throw Boxed_Value(t_ee); - } - } + /// Evaluates the given file and looks in the 'use' paths const Boxed_Value internal_eval_file(const std::string &t_filename) { @@ -396,7 +391,8 @@ namespace chaiscript 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_NodePtr &t_ast){ return internal_eval_ast(t_ast); }), "eval"); + m_engine.add(fun([this](const AST_NodePtr &t_ast){ return eval(t_ast); }), "eval"); + m_engine.add(fun(&parse), "parse"); m_engine.add(fun(&ChaiScript::version_major), "version_major"); m_engine.add(fun(&ChaiScript::version_minor), "version_minor"); @@ -517,6 +513,28 @@ namespace chaiscript build_eval_system(ModulePtr()); } + + const Boxed_Value eval(const AST_NodePtr &t_ast) + { + try { + return t_ast->eval(m_engine); + } catch (const exception::eval_error &t_ee) { + throw Boxed_Value(t_ee); + } + } + + static AST_NodePtr parse(const std::string &t_input) + { + parser::ChaiScript_Parser parser; + if (parser.parse(t_input, "PARSE")) { + //parser.show_match_stack(); + return parser.optimized_ast(); + } else { + throw chaiscript::exception::eval_error("Unknown error while parsing"); + } + } + + static int version_major() { return chaiscript::version_major; diff --git a/include/chaiscript/language/chaiscript_eval.hpp b/include/chaiscript/language/chaiscript_eval.hpp index c14a156a..aa01de96 100644 --- a/include/chaiscript/language/chaiscript_eval.hpp +++ b/include/chaiscript/language/chaiscript_eval.hpp @@ -44,16 +44,33 @@ namespace chaiscript namespace detail { /// Helper function that will set up the scope around a function call, including handling the named function parameters - static Boxed_Value eval_function(chaiscript::detail::Dispatch_Engine &t_ss, const AST_NodePtr &t_node, const std::vector &t_param_names, const std::vector &t_vals, const std::map &t_locals=std::map()) { + static Boxed_Value eval_function(chaiscript::detail::Dispatch_Engine &t_ss, const AST_NodePtr &t_node, const std::vector &t_param_names, const std::vector &t_vals, const std::map *t_locals=nullptr) { chaiscript::detail::Dispatch_State state(t_ss); - chaiscript::eval::detail::Scope_Push_Pop spp(state); - for (const auto &local : t_locals) { - t_ss.add_object(local.first, local.second); + 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) 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) { - t_ss.add_object(t_param_names[i], t_vals[i]); + if (t_param_names[i] != "this") { + state.add_object(t_param_names[i], t_vals[i]); + } } try { @@ -73,9 +90,9 @@ namespace chaiscript virtual ~Binary_Operator_AST_Node() {} virtual Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const CHAISCRIPT_OVERRIDE { - return do_oper(t_ss, m_oper, text, - this->children[0]->eval(t_ss), - this->children[1]->eval(t_ss)); + auto lhs = this->children[0]->eval(t_ss); + auto rhs = this->children[1]->eval(t_ss); + return do_oper(t_ss, m_oper, text, lhs, rhs); } virtual std::string pretty_print() const CHAISCRIPT_OVERRIDE @@ -101,8 +118,7 @@ namespace chaiscript } else { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); fpp.save_params({t_lhs, t_rhs}); - chaiscript::eval::detail::Stack_Push_Pop spp(t_ss); - return t_ss->call_function(t_oper_string, t_lhs, t_rhs); + return t_ss->call_function(t_oper_string, m_loc, {t_lhs, t_rhs}); } } catch(const exception::dispatch_error &e){ @@ -112,13 +128,14 @@ namespace chaiscript private: Operators::Opers m_oper; + mutable std::atomic_uint_fast32_t m_loc; }; struct Int_AST_Node : public AST_Node { public: Int_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, Boxed_Value t_bv) : AST_Node(std::move(t_ast_node_text), AST_Node_Type::Int, std::move(t_loc)), - m_value(std::move(t_bv)) { } + m_value(std::move(t_bv)) { assert(text != ""); } virtual ~Int_AST_Node() {} virtual Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const CHAISCRIPT_OVERRIDE{ return m_value; @@ -236,7 +253,6 @@ namespace chaiscript Boxed_Value fn(this->children[0]->eval(t_ss)); try { - chaiscript::eval::detail::Stack_Push_Pop spp(t_ss); return (*t_ss->boxed_cast(fn))(params, t_ss->conversions()); } catch(const exception::dispatch_error &e){ @@ -286,73 +302,6 @@ namespace chaiscript - /// Used in the context of in-string ${} evals, so that no new scope is created - struct Inplace_Fun_Call_AST_Node : public AST_Node { - public: - Inplace_Fun_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector t_children) : - AST_Node(t_ast_node_text, AST_Node_Type::Inplace_Fun_Call, std::move(t_loc), std::move(t_children)) - { assert(children.size() == 2); } - - virtual ~Inplace_Fun_Call_AST_Node() {} - virtual Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const CHAISCRIPT_OVERRIDE{ - 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)); - } - - - Const_Proxy_Function fn; - try { - Boxed_Value bv = this->children[0]->eval(t_ss); - try { - fn = t_ss->boxed_cast(bv); - } catch (const exception::bad_boxed_cast &) { - throw exception::eval_error("'" + this->children[0]->pretty_print() + "' does not evaluate to a function."); - } - return (*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 &){ - // 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, {fn}, false, *t_ss); - } - 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; - } - } - - virtual std::string pretty_print() const CHAISCRIPT_OVERRIDE - { - std::ostringstream oss; - int count = 0; - for (const auto &child : this->children) { - oss << child->pretty_print(); - - if (count == 0) - { - oss << "("; - } - ++count; - } - - oss << ")"; - - return oss.str(); - } - - }; - struct Arg_AST_Node : public AST_Node { public: Arg_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector t_children) : @@ -448,6 +397,8 @@ namespace chaiscript { assert(children.size() == 3); } Operators::Opers m_oper; + mutable std::atomic_uint_fast32_t m_loc; + mutable std::atomic_uint_fast32_t m_clone_loc; virtual ~Equation_AST_Node() {} virtual Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const CHAISCRIPT_OVERRIDE { @@ -483,14 +434,14 @@ namespace chaiscript } else { if (!rhs.is_return_value()) { - rhs = t_ss->call_function("clone", rhs); + rhs = t_ss->call_function("clone", m_clone_loc, {rhs}); } rhs.reset_return_value(); } } try { - return t_ss->call_function(this->children[1]->text, std::move(lhs), rhs); + return t_ss->call_function(this->children[1]->text, m_loc, {std::move(lhs), rhs}); } catch(const exception::dispatch_error &e){ throw exception::eval_error("Unable to find appropriate'" + this->children[1]->text + "' operator.", e.parameters, e.functions, false, *t_ss); @@ -503,13 +454,14 @@ namespace chaiscript else if (this->children[1]->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->children[1]->text, std::move(lhs), rhs); + return t_ss->call_function(this->children[1]->text, m_loc, {std::move(lhs), rhs}); } catch(const exception::dispatch_error &e){ throw exception::eval_error("Unable to find appropriate'" + this->children[1]->text + "' operator.", e.parameters, e.functions, false, *t_ss); } @@ -560,7 +512,7 @@ namespace chaiscript try { Boxed_Value bv; - t_ss->add_object(idname, bv); + t_ss.add_object(idname, bv); return bv; } catch (const exception::reserved_word_error &) { @@ -591,9 +543,8 @@ namespace chaiscript std::vector params{children[0]->eval(t_ss), children[1]->eval(t_ss)}; try { - chaiscript::eval::detail::Stack_Push_Pop spp(t_ss); fpp.save_params(params); - return t_ss->call_function("[]", params); + return t_ss->call_function("[]", m_loc, params); } catch(const exception::dispatch_error &e){ throw exception::eval_error("Can not find appropriate array lookup operator '[]'.", e.parameters, e.functions, false, *t_ss ); @@ -615,6 +566,8 @@ namespace chaiscript return oss.str(); } + + mutable std::atomic_uint_fast32_t m_loc; }; struct Dot_Access_AST_Node : public AST_Node { @@ -629,6 +582,7 @@ namespace chaiscript virtual Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const CHAISCRIPT_OVERRIDE{ chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); + Boxed_Value retval = children[0]->eval(t_ss); std::vector params{retval}; @@ -643,9 +597,7 @@ namespace chaiscript fpp.save_params(params); try { - chaiscript::eval::detail::Stack_Push_Pop spp(t_ss); - t_ss->add_object("this", retval); - retval = t_ss->call_member(m_fun_name, std::move(params), has_function_params); + retval = t_ss->call_member(m_fun_name, m_loc, std::move(params), has_function_params); } catch(const exception::dispatch_error &e){ if (e.functions.empty()) @@ -661,7 +613,7 @@ namespace chaiscript if (this->children[2]->identifier == AST_Node_Type::Array_Call) { try { - retval = t_ss->call_function("[]", retval, this->children[2]->children[1]->eval(t_ss)); + retval = t_ss->call_function("[]", m_array_loc, {retval, this->children[2]->children[1]->eval(t_ss)}); } catch(const exception::dispatch_error &e){ throw exception::eval_error("Can not find appropriate array lookup operator '[]'.", e.parameters, e.functions, true, *t_ss); @@ -672,6 +624,8 @@ namespace chaiscript } private: + mutable std::atomic_uint_fast32_t m_loc; + mutable std::atomic_uint_fast32_t m_array_loc; std::string m_fun_name; }; @@ -745,7 +699,7 @@ namespace chaiscript dispatch::make_dynamic_proxy_function( [engine, lambda_node, param_names, captures](const std::vector &t_params) { - return detail::eval_function(engine, lambda_node, param_names, t_params, captures); + return detail::eval_function(engine, lambda_node, param_names, t_params, &captures); }, static_cast(numparams), lambda_node, param_types ) @@ -876,7 +830,7 @@ namespace chaiscript /// \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(children[0]->text)); + t_ss.add_object("_current_class_name", const_var(children[0]->text)); children[1]->eval(t_ss); @@ -985,7 +939,7 @@ namespace chaiscript 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("==", match_value, this->children[currentCase]->children[0]->eval(t_ss)))) { + if (hasMatched || boxed_cast(t_ss->call_function("==", m_loc, {match_value, this->children[currentCase]->children[0]->eval(t_ss)}))) { this->children[currentCase]->eval(t_ss); hasMatched = true; } @@ -996,7 +950,7 @@ namespace chaiscript } else if (this->children[currentCase]->identifier == AST_Node_Type::Default) { this->children[currentCase]->eval(t_ss); - breaking = true; + hasMatched = true; } } catch (detail::Break_Loop &) { @@ -1006,6 +960,8 @@ namespace chaiscript } return Boxed_Value(); } + + mutable std::atomic_uint_fast32_t m_loc; }; struct Case_AST_Node : public AST_Node { @@ -1052,7 +1008,7 @@ namespace chaiscript for (const auto &child : children[0]->children) { auto obj = child->eval(t_ss); if (!obj.is_return_value()) { - vec.push_back(t_ss->call_function("clone", obj)); + vec.push_back(t_ss->call_function("clone", m_loc, {obj})); } else { vec.push_back(std::move(obj)); } @@ -1069,6 +1025,8 @@ namespace chaiscript { return "[" + AST_Node::pretty_print() + "]"; } + + mutable std::atomic_uint_fast32_t m_loc; }; struct Inline_Map_AST_Node : public AST_Node { @@ -1083,7 +1041,7 @@ namespace chaiscript for (const auto &child : children[0]->children) { auto obj = child->children[1]->eval(t_ss); if (!obj.is_return_value()) { - obj = t_ss->call_function("clone", obj); + obj = t_ss->call_function("clone", m_loc, {obj}); } retval[t_ss->boxed_cast(child->children[0]->eval(t_ss))] = std::move(obj); @@ -1096,6 +1054,7 @@ namespace chaiscript } } + mutable std::atomic_uint_fast32_t m_loc; }; struct Return_AST_Node : public AST_Node { @@ -1148,7 +1107,7 @@ namespace chaiscript virtual Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const CHAISCRIPT_OVERRIDE{ try { Boxed_Value bv; - t_ss->add_object(this->children[0]->text, bv); + t_ss.add_object(this->children[0]->text, bv); return bv; } catch (const exception::reserved_word_error &) { @@ -1177,9 +1136,8 @@ namespace chaiscript return Boxed_Number::do_oper(m_oper, bv); } else { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); - chaiscript::eval::detail::Stack_Push_Pop spp(t_ss); fpp.save_params({bv}); - return t_ss->call_function(children[0]->text, std::move(bv)); + return t_ss->call_function(children[0]->text, m_loc, {std::move(bv)}); } } catch (const exception::dispatch_error &e) { throw exception::eval_error("Error with prefix operator evaluation: '" + children[0]->text + "'", e.parameters, e.functions, false, *t_ss); @@ -1188,6 +1146,7 @@ namespace chaiscript private: Operators::Opers m_oper; + mutable std::atomic_uint_fast32_t m_loc; }; struct Break_AST_Node : public AST_Node { @@ -1248,15 +1207,16 @@ namespace chaiscript virtual ~Inline_Range_AST_Node() {} virtual Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const CHAISCRIPT_OVERRIDE{ try { - return t_ss->call_function("generate_range", - children[0]->children[0]->children[0]->eval(t_ss), - children[0]->children[0]->children[1]->eval(t_ss)); + auto oper1 = children[0]->children[0]->children[0]->eval(t_ss); + auto oper2 = children[0]->children[0]->children[1]->eval(t_ss); + return t_ss->call_function("generate_range", m_loc, {oper1, oper2}); } 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); } } + mutable std::atomic_uint_fast32_t m_loc; }; struct Annotation_AST_Node : public AST_Node { @@ -1296,7 +1256,7 @@ namespace chaiscript std::vector>{Arg_List_AST_Node::get_arg_type(catch_block->children[0], t_ss)} ).match(std::vector{t_except}, t_ss->conversions())) { - t_ss->add_object(name, t_except); + t_ss.add_object(name, t_except); if (catch_block->children.size() == 2) { //Variable capture, no guards @@ -1433,7 +1393,7 @@ namespace chaiscript if (guardnode) { guard = dispatch::make_dynamic_proxy_function( [engine, t_param_names, guardnode](const std::vector &t_params) { - return chaiscript::eval::detail::eval_function(engine, guardnode, t_param_names, t_params, std::map()); + return chaiscript::eval::detail::eval_function(engine, guardnode, t_param_names, t_params); }, static_cast(numparams), guardnode); } @@ -1450,7 +1410,7 @@ namespace chaiscript std::make_shared(class_name, dispatch::make_dynamic_proxy_function( [engine, t_param_names, node](const std::vector &t_params) { - return chaiscript::eval::detail::eval_function(engine, node, t_param_names, t_params, std::map()); + return chaiscript::eval::detail::eval_function(engine, node, t_param_names, t_params); }, static_cast(numparams), node, param_types, l_annotation, guard ) @@ -1466,7 +1426,7 @@ namespace chaiscript t_ss->add(std::make_shared(class_name, dispatch::make_dynamic_proxy_function( [engine, t_param_names, node](const std::vector &t_params) { - return chaiscript::eval::detail::eval_function(engine, node, t_param_names, t_params, std::map()); + return chaiscript::eval::detail::eval_function(engine, node, t_param_names, t_params); }, static_cast(numparams), node, param_types, l_annotation, guard), type), function_name); diff --git a/include/chaiscript/language/chaiscript_parser.hpp b/include/chaiscript/language/chaiscript_parser.hpp index ddc484fb..6548521d 100644 --- a/include/chaiscript/language/chaiscript_parser.hpp +++ b/include/chaiscript/language/chaiscript_parser.hpp @@ -17,9 +17,19 @@ #include + #include "../dispatchkit/boxed_value.hpp" #include "chaiscript_common.hpp" + +#if defined(CHAISCRIPT_MSVC) && defined(max) && defined(min) +#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. @@ -48,8 +58,6 @@ namespace chaiscript class ChaiScript_Parser { - std::string::const_iterator m_input_pos, m_input_end; - int m_line, m_col; std::string m_multiline_comment_begin; std::string m_multiline_comment_end; std::string m_singleline_comment; @@ -60,10 +68,110 @@ namespace chaiscript std::vector> m_operator_matches; std::vector m_operators; + struct Position + { + Position() + : line(-1), col(-1), m_last_col(-1) {} + + Position(std::string::const_iterator t_pos, std::string::const_iterator t_end) + : line(1), col(1), m_pos(std::move(t_pos)), m_end(std::move(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 = col; + 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 std::distance(m_pos, m_end); + } + + char operator*() const { + if (m_pos == m_end) { + return '\0'; + } else { + return *m_pos; + } + } + + int line; + int col; + + private: + std::string::const_iterator m_pos; + std::string::const_iterator m_end; + int m_last_col; + }; + + Position m_position; + public: ChaiScript_Parser() - : m_line(-1), m_col(-1), - m_multiline_comment_begin("/*"), + : m_multiline_comment_begin("/*"), m_multiline_comment_end("*/"), m_singleline_comment("//") { @@ -167,17 +275,16 @@ namespace chaiscript } /// test a char in an m_alphabet - bool char_in_alphabet(char c, detail::Alphabet a) const { return m_alphabet[a][static_cast(c)]; } + 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(AST_NodePtr t, std::string prepend = "") { - std::cout << prepend << "(" << ast_node_type_to_string(t->identifier) << ") " << t->text << " : " << t->start.line << ", " << t->start.column << '\n'; - for (unsigned int j = 0; j < t->children.size(); ++j) { - debug_print(t->children[j], prepend + " "); - } - } - */ + void debug_print(AST_NodePtr t, std::string prepend = "") { + std::cout << prepend << "(" << ast_node_type_to_string(t->identifier) << ") " << t->text << " : " << t->start().line << ", " << t->start().column << '\n'; + for (unsigned int j = 0; j < t->children.size(); ++j) { + debug_print(t->children[j], prepend + " "); + } + } + /// Shows the current stack of matched ast_nodes void show_match_stack() const { @@ -283,16 +390,16 @@ namespace chaiscript m_filename, m_match_stack[t_match_start]->location.start.line, m_match_stack[t_match_start]->location.start.column, - m_line, - m_col + m_position.line, + m_position.col ); } else { return Parse_Location( m_filename, - m_line, - m_col, - m_line, - m_col + m_position.line, + m_position.col, + m_position.line, + m_position.col ); } }(); @@ -313,34 +420,28 @@ namespace chaiscript std::move(new_children))); } - /// Check to see if there is more text parse - inline bool has_more_input() const { - return (m_input_pos != m_input_end); - } /// Skips any multi-line or single-line comment bool SkipComment() { if (Symbol_(m_multiline_comment_begin.c_str())) { - while (m_input_pos != m_input_end) { + while (m_position.has_more()) { if (Symbol_(m_multiline_comment_end.c_str())) { break; } else if (!Eol_()) { - ++m_col; - ++m_input_pos; + ++m_position; } } return true; } else if (Symbol_(m_singleline_comment.c_str())) { - while (m_input_pos != m_input_end) { + while (m_position.has_more()) { if (Symbol_("\r\n")) { - m_input_pos -= 2; + m_position -= 2; break; } else if (Char_('\n')) { - --m_input_pos; + --m_position; break; } else { - ++m_col; - ++m_input_pos; + ++m_position; } } return true; @@ -354,24 +455,19 @@ namespace chaiscript bool SkipWS(bool skip_cr=false) { bool retval = false; - while (has_more_input()) { - auto end_line = (*m_input_pos != 0) && ((*m_input_pos == '\n') || (*m_input_pos == '\r' && *(m_input_pos+1) == '\n')); + while (m_position.has_more()) { + auto end_line = (*m_position != 0) && ((*m_position == '\n') || (*m_position == '\r' && *(m_position+1) == '\n')); - if ( char_in_alphabet(*m_input_pos,detail::white_alphabet) || (skip_cr && end_line)) { + if ( char_in_alphabet(*m_position,detail::white_alphabet) || (skip_cr && end_line)) { if(end_line) { - m_col = 1; - ++m_line; - - if(*(m_input_pos) == '\r') { + if(*m_position == '\r') { // discards lf - ++m_input_pos; + ++m_position; } } - else { - ++m_col; - } - ++m_input_pos; + + ++m_position; retval = true; } @@ -387,29 +483,25 @@ namespace chaiscript /// 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 (has_more_input() && (std::tolower(*m_input_pos) == 'e')) { - ++m_input_pos; - ++m_col; - if (has_more_input() && ((*m_input_pos == '-') || (*m_input_pos == '+'))) { - ++m_input_pos; - ++m_col; + 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_input_pos; - while (has_more_input() && char_in_alphabet(*m_input_pos,detail::int_alphabet) ) { - ++m_input_pos; - ++m_col; + auto exponent_pos = m_position; + while (m_position.has_more() && char_in_alphabet(*m_position,detail::int_alphabet) ) { + ++m_position; } - if (m_input_pos == exponent_pos) { + if (m_position == exponent_pos) { // Require at least one digit after the exponent return false; } } // Parse optional float suffix - while (has_more_input() && char_in_alphabet(*m_input_pos, detail::float_suffix_alphabet)) + while (m_position.has_more() && char_in_alphabet(*m_position, detail::float_suffix_alphabet)) { - ++m_input_pos; - ++m_col; + ++m_position; } return true; @@ -418,30 +510,26 @@ namespace chaiscript /// Reads a floating point value from input, without skipping initial whitespace bool Float_() { - if (has_more_input() && char_in_alphabet(*m_input_pos,detail::float_alphabet) ) { - while (has_more_input() && char_in_alphabet(*m_input_pos,detail::int_alphabet) ) { - ++m_input_pos; - ++m_col; + 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 (has_more_input() && (std::tolower(*m_input_pos) == 'e')) { + 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 (has_more_input() && (*m_input_pos == '.')) { - ++m_input_pos; - ++m_col; - if (has_more_input() && char_in_alphabet(*m_input_pos,detail::int_alphabet)) { - while (has_more_input() && char_in_alphabet(*m_input_pos,detail::int_alphabet) ) { - ++m_input_pos; - ++m_col; + 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_input_pos; - --m_col; + --m_position; } } } @@ -450,34 +538,28 @@ namespace chaiscript /// Reads a hex value from input, without skipping initial whitespace bool Hex_() { - if (has_more_input() && (*m_input_pos == '0')) { - ++m_input_pos; - ++m_col; + if (m_position.has_more() && (*m_position == '0')) { + ++m_position; - if (has_more_input() && char_in_alphabet(*m_input_pos, detail::x_alphabet) ) { - ++m_input_pos; - ++m_col; - if (has_more_input() && char_in_alphabet(*m_input_pos, detail::hex_alphabet)) { - while (has_more_input() && char_in_alphabet(*m_input_pos, detail::hex_alphabet) ) { - ++m_input_pos; - ++m_col; + 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 (has_more_input() && char_in_alphabet(*m_input_pos, detail::int_suffix_alphabet)) + while (m_position.has_more() && char_in_alphabet(*m_position, detail::int_suffix_alphabet)) { - ++m_input_pos; - ++m_col; + ++m_position; } return true; } else { - --m_input_pos; - --m_col; + --m_position; } } else { - --m_input_pos; - --m_col; + --m_position; } } @@ -486,35 +568,29 @@ namespace chaiscript /// Reads an integer suffix void IntSuffix_() { - while (has_more_input() && char_in_alphabet(*m_input_pos, detail::int_suffix_alphabet)) + while (m_position.has_more() && char_in_alphabet(*m_position, detail::int_suffix_alphabet)) { - ++m_input_pos; - ++m_col; + ++m_position; } } /// Reads a binary value from input, without skipping initial whitespace bool Binary_() { - if (has_more_input() && (*m_input_pos == '0')) { - ++m_input_pos; - ++m_col; + if (m_position.has_more() && (*m_position == '0')) { + ++m_position; - if (has_more_input() && char_in_alphabet(*m_input_pos, detail::b_alphabet) ) { - ++m_input_pos; - ++m_col; - if (has_more_input() && char_in_alphabet(*m_input_pos, detail::bin_alphabet) ) { - while (has_more_input() && char_in_alphabet(*m_input_pos, detail::bin_alphabet) ) { - ++m_input_pos; - ++m_col; + 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_input_pos; - --m_col; + --m_position; } } else { - --m_input_pos; - --m_col; + --m_position; } } @@ -555,8 +631,7 @@ namespace chaiscript - template - static Boxed_Value buildInt(const IntType &t_type, const std::string &t_val) + static Boxed_Value buildInt(const int base, const std::string &t_val, const bool prefixed) { bool unsigned_ = false; bool long_ = false; @@ -583,102 +658,62 @@ namespace chaiscript } } - std::stringstream ss(t_val.substr(0, i)); - ss >> t_type; + const auto val = prefixed?std::string(t_val.begin()+2,t_val.end()):t_val; - std::stringstream testu(t_val.substr(0, i)); - uint64_t u; - testu >> t_type >> u; +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" - bool unsignedrequired = false; +#ifdef CHAISCRIPT_CLANG +#pragma GCC diagnostic ignored "-Wtautological-compare" +#endif - if ((u >> (sizeof(int) * 8)) > 0) - { - //requires something bigger than int - long_ = true; - } +#endif - static_assert(sizeof(long) == sizeof(uint64_t) || sizeof(long) * 2 == sizeof(uint64_t), "Unexpected sizing of integer types"); - - if ((sizeof(long) < sizeof(uint64_t)) - && (u >> ((sizeof(uint64_t) - sizeof(long)) * 8)) > 0) - { - //requires something bigger than long - longlong_ = true; - } + try { + auto u = std::stoll(val,nullptr,base); - const size_t size = [&]()->size_t{ - if (longlong_) - { - return sizeof(int64_t) * 8; - } else if (long_) { - return sizeof(long) * 8; + 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 sizeof(int) * 8; + return const_var(static_cast(u)); } - }(); - if ( (u >> (size - 1)) > 0) - { - unsignedrequired = true; - } + } catch (const std::out_of_range &) { + // too big to be signed + try { + auto u = std::stoull(val,nullptr,base); - if (unsignedrequired && !unsigned_) - { - if (t_type == &std::hex || t_type == &std::oct) - { - // with hex and octal we are happy to just make it unsigned - unsigned_ = true; - } else { - // with decimal we must bump it up to the next size - if (long_) - { - longlong_ = true; - } else if (!long_ && !longlong_) { - long_ = true; + if (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()); } } - if (unsigned_) - { - if (longlong_) - { - uint64_t val; - ss >> val; - return const_var(val); - } else if (long_) { - unsigned long val; - ss >> val; - return const_var(val); - } else { - unsigned int val; - ss >> val; - return const_var(val); - } - } else { - if (longlong_) - { - int64_t val; - ss >> val; - return const_var(val); - } else if (long_) { - long val; - ss >> val; - return const_var(val); - } else { - int val; - ss >> val; - return const_var(val); - } - } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } template std::shared_ptr make_node(std::string t_match, const int t_prev_line, const int t_prev_col, Param && ...param) { - return chaiscript::make_shared(std::move(t_match), Parse_Location(m_filename, t_prev_line, t_prev_col, m_line, m_col), std::forward(param)...); + return chaiscript::make_shared(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 @@ -688,61 +723,46 @@ namespace chaiscript if (!t_capture) { return Hex_() || Float_(); } else { - const auto start = m_input_pos; - const auto prev_col = m_col; - const auto prev_line = m_line; - if (has_more_input() && char_in_alphabet(*m_input_pos, detail::float_alphabet) ) { - if (Hex_()) { - std::string match(start, m_input_pos); - auto bv = buildInt(std::hex, match); - m_match_stack.emplace_back(make_node(std::move(match), prev_line, prev_col, std::move(bv))); - return true; - } - - if (Binary_()) { - std::string match(start, m_input_pos); - int64_t temp_int = 0; - size_t pos = 0; - const auto end = match.length(); - - while ((pos < end) && (pos < (2 + sizeof(int) * 8))) { - temp_int <<= 1; - if (match[pos] == '1') { - temp_int += 1; - } - ++pos; + 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; } - Boxed_Value i = [&]()->Boxed_Value{ - if (match.length() <= sizeof(int) * 8) - { - return const_var(static_cast(temp_int)); - } else { - return const_var(temp_int); - } - }(); - - m_match_stack.push_back(make_node(std::move(match), prev_line, prev_col, std::move(i))); - return true; - } - if (Float_()) { - std::string match(start, m_input_pos); - auto bv = buildFloat(match); - m_match_stack.push_back(make_node(std::move(match), prev_line, prev_col, std::move(bv))); - return true; - } - else { - IntSuffix_(); - std::string match(start, m_input_pos); - if (!match.empty() && (match[0] == '0')) { - auto bv = buildInt(std::oct, match); - m_match_stack.push_back(make_node(std::move(match), prev_line, prev_col, std::move(bv))); + 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 { - auto bv = buildInt(std::dec, match); - m_match_stack.push_back(make_node(std::move(match), prev_line, prev_col, std::move(bv))); + 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; } - return true; + } catch (const std::invalid_argument &) { + // error parsing number passed in to buildFloat/buildInt + return false; } } else { @@ -753,37 +773,33 @@ namespace chaiscript /// Reads an identifier from input which conforms to C's identifier naming conventions, without skipping initial whitespace bool Id_() { - if (has_more_input() && char_in_alphabet(*m_input_pos, detail::id_alphabet)) { - while (has_more_input() && char_in_alphabet(*m_input_pos, detail::keyword_alphabet) ) { - ++m_input_pos; - ++m_col; + 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 (has_more_input() && (*m_input_pos == '`')) { - ++m_col; - ++m_input_pos; - const auto start = m_input_pos; + } else if (m_position.has_more() && (*m_position == '`')) { + ++m_position; + const auto start = m_position; - while (has_more_input() && (*m_input_pos != '`')) { + while (m_position.has_more() && (*m_position != '`')) { if (Eol()) { - throw exception::eval_error("Carriage return in identifier literal", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Carriage return in identifier literal", File_Position(m_position.line, m_position.col), *m_filename); } else { - ++m_input_pos; - ++m_col; + ++m_position; } } - if (start == m_input_pos) { - throw exception::eval_error("Missing contents of identifier literal", File_Position(m_line, m_col), *m_filename); + 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_input_pos == m_input_end) { - throw exception::eval_error("Incomplete identifier literal", File_Position(m_line, m_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_col; - ++m_input_pos; + ++m_position; return true; } @@ -794,20 +810,18 @@ namespace chaiscript bool Id() { SkipWS(); - const auto start = m_input_pos; - const auto prev_col = m_col; - const auto prev_line = m_line; + const auto start = m_position; if (Id_()) { m_match_stack.push_back(make_node( [&]()->std::string{ if (*start == '`') { //Id Literal - return std::string(start+1, m_input_pos-1); + return Position::str(start+1, m_position-1); } else { - return std::string(start, m_input_pos); + return Position::str(start, m_position); } }(), - prev_line, prev_col)); + start.line, start.col)); return true; } else { return false; @@ -839,24 +853,21 @@ namespace chaiscript /// Checks for a node annotation of the form "#" bool Annotation() { SkipWS(); - const auto start = m_input_pos; - const auto prev_col = m_col; - const auto prev_line = m_line; + const auto start = m_position; if (Symbol_("#")) { do { - while (m_input_pos != m_input_end) { + while (m_position.has_more()) { if (Eol_()) { break; } else { - ++m_col; - ++m_input_pos; + ++m_position; } } } while (Symbol("#")); - std::string match(start, m_input_pos); - m_match_stack.push_back(make_node(std::move(match), prev_line, prev_col)); + auto match = Position::str(start, m_position); + m_match_stack.push_back(make_node(std::move(match), start.line, start.col)); return true; } else { @@ -866,28 +877,25 @@ namespace chaiscript /// Reads a quoted string from input, without skipping initial whitespace bool Quoted_String_() { - if (has_more_input() && (*m_input_pos == '\"')) { - char prev_char = *m_input_pos; - ++m_input_pos; - ++m_col; + if (m_position.has_more() && (*m_position == '\"')) { + char prev_char = *m_position; + ++m_position; - while (has_more_input() && ((*m_input_pos != '\"') || ((*m_input_pos == '\"') && (prev_char == '\\')))) { + while (m_position.has_more() && ((*m_position != '\"') || ((*m_position == '\"') && (prev_char == '\\')))) { if (!Eol_()) { if (prev_char == '\\') { prev_char = 0; } else { - prev_char = *m_input_pos; + prev_char = *m_position; } - ++m_input_pos; - ++m_col; + ++m_position; } } - if (has_more_input()) { - ++m_input_pos; - ++m_col; + if (m_position.has_more()) { + ++m_position; } else { - throw exception::eval_error("Unclosed quoted string", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Unclosed quoted string", File_Position(m_position.line, m_position.col), *m_filename); } return true; @@ -895,6 +903,140 @@ namespace chaiscript return false; } + template + struct Char_Parser + { + string_type &match; + typedef typename string_type::value_type char_type; + bool is_escaped; + bool is_interpolated; + bool saw_interpolation_marker; + bool is_octal; + bool is_hex; + 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), + is_escaped(false), + is_interpolated(false), + saw_interpolation_marker(false), + is_octal(false), + is_hex(false), + interpolation_allowed(t_interpolation_allowed) + { + } + + Char_Parser &operator=(const Char_Parser &) = delete; + + ~Char_Parser(){ + if (is_octal) { + process_octal(); + } + + if (is_hex) { + process_hex(); + } + } + + void process_hex() + { + auto val = stoll(hex_matches, 0, 16); + match.push_back(char_type(val)); + hex_matches.clear(); + is_escaped = false; + is_hex = false; + } + + + void process_octal() + { + auto val = stoll(octal_matches, 0, 8); + match.push_back(char_type(val)); + octal_matches.clear(); + is_escaped = false; + is_octal = false; + } + + 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'; + + 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) { + 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_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(); + } + } + + 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 { + 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(const bool t_capture = false) { SkipWS(); @@ -902,111 +1044,86 @@ namespace chaiscript if (!t_capture) { return Quoted_String_(); } else { - const auto start = m_input_pos; - const auto prev_col = m_col; - const auto prev_line = m_line; + const auto start = m_position; if (Quoted_String_()) { std::string match; - bool is_escaped = false; - bool is_interpolated = false; - bool saw_interpolation_marker = false; const auto prev_stack_top = m_match_stack.size(); - std::string::const_iterator s = start + 1, end = m_input_pos - 1; + bool is_interpolated = [&]()->bool { + Char_Parser cparser(match, true); - while (s != end) { - if (saw_interpolation_marker) { - if (*s == '{') { - //We've found an interpolation point - if (is_interpolated) { - //If we've seen previous interpolation, add on instead of making a new one - m_match_stack.push_back(make_node(match, prev_line, prev_col)); + auto s = start + 1, end = m_position - 1; - build_match(prev_stack_top, "+"); - } else { - m_match_stack.push_back(make_node(match, prev_line, prev_col)); - } + while (s != end) { + if (cparser.saw_interpolation_marker) { + if (*s == '{') { + //We've found an interpolation point - //We've finished with the part of the string up to this point, so clear it - match.clear(); + if (cparser.is_interpolated) { + //If we've seen previous interpolation, add on instead of making a new one + m_match_stack.push_back(make_node(match, start.line, start.col)); - std::string eval_match; - - ++s; - while ((s != end) && (*s != '}')) { - eval_match.push_back(*s); - ++s; - } - - if (*s == '}') { - is_interpolated = true; - ++s; - - const auto tostr_stack_top = m_match_stack.size(); - - m_match_stack.push_back(make_node("to_string", prev_line, prev_col)); - - const auto ev_stack_top = m_match_stack.size(); - - /// \todo can we evaluate this in place and save the runtime cost of evaluating with each execution of the node? - m_match_stack.push_back(make_node("eval", prev_line, prev_col)); - - const auto arg_stack_top = m_match_stack.size(); - - m_match_stack.push_back(make_node(eval_match, prev_line, prev_col)); - - build_match(arg_stack_top); - build_match(ev_stack_top); - 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(prev_line, prev_col), *m_filename); - } - } else { - match.push_back('$'); - } - saw_interpolation_marker = false; - } else { - if (*s == '\\') { - if (is_escaped) { - match.push_back('\\'); - is_escaped = false; - } else { - is_escaped = true; - } - } else { - if (is_escaped) { - switch (*s) { - 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 ('\'') : match.push_back('\''); break; - case ('\"') : match.push_back('\"'); break; - case ('$') : match.push_back('$'); break; - default: throw exception::eval_error("Unknown escaped sequence in string", File_Position(prev_line, prev_col), *m_filename); + build_match(prev_stack_top, "+"); + } else { + m_match_stack.push_back(make_node(match, start.line, start.col)); + } + + //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 { + ChaiScript_Parser parser; + parser.parse(eval_match, "instr eval"); + m_match_stack.push_back(parser.ast()); + } 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 if (*s == '$') { - saw_interpolation_marker = true; } else { - match.push_back(*s); + match.push_back('$'); } - is_escaped = false; + cparser.saw_interpolation_marker = false; + } else { + cparser.parse(*s, start.line, start.col, *m_filename); + ++s; } - ++s; } - } + + return cparser.is_interpolated; + }(); if (is_interpolated) { - m_match_stack.push_back(make_node(match, prev_line, prev_col)); + m_match_stack.push_back(make_node(match, start.line, start.col)); build_match(prev_stack_top, "+"); } else { - m_match_stack.push_back(make_node(match, prev_line, prev_col)); + m_match_stack.push_back(make_node(match, start.line, start.col)); } return true; } else { @@ -1018,29 +1135,26 @@ namespace chaiscript /// Reads a character group from input, without skipping initial whitespace bool Single_Quoted_String_() { bool retval = false; - if (has_more_input() && (*m_input_pos == '\'')) { + if (m_position.has_more() && (*m_position == '\'')) { retval = true; - char prev_char = *m_input_pos; - ++m_input_pos; - ++m_col; + char prev_char = *m_position; + ++m_position; - while (has_more_input() && ((*m_input_pos != '\'') || ((*m_input_pos == '\'') && (prev_char == '\\')))) { + while (m_position.has_more() && ((*m_position != '\'') || ((*m_position == '\'') && (prev_char == '\\')))) { if (!Eol_()) { if (prev_char == '\\') { prev_char = 0; } else { - prev_char = *m_input_pos; + prev_char = *m_position; } - ++m_input_pos; - ++m_col; + ++m_position; } } - if (m_input_pos != m_input_end) { - ++m_input_pos; - ++m_col; + if (m_position.has_more()) { + ++m_position; } else { - throw exception::eval_error("Unclosed single-quoted string", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Unclosed single-quoted string", File_Position(m_position.line, m_position.col), *m_filename); } } return retval; @@ -1053,39 +1167,20 @@ namespace chaiscript if (!t_capture) { return Single_Quoted_String_(); } else { - const auto start = m_input_pos; - const auto prev_col = m_col; - const auto prev_line = m_line; + const auto start = m_position; if (Single_Quoted_String_()) { std::string match; - bool is_escaped = false; - for (auto s = start + 1, end = m_input_pos - 1; s != end; ++s) { - if (*s == '\\') { - if (is_escaped) { - match.push_back('\\'); - is_escaped = false; - } else { - is_escaped = true; - } - } else { - if (is_escaped) { - switch (*s) { - 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 ('\'') : match.push_back('\''); break; - case ('\"') : match.push_back('\"'); break; - default: throw exception::eval_error("Unknown escaped sequence in string", File_Position(prev_line, prev_col), *m_filename); - } - } else { - match.push_back(*s); - } - is_escaped = false; + + { + // scope for cparser destrutor + 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); } } - m_match_stack.push_back(make_node(match, prev_line, prev_col)); + + m_match_stack.push_back(make_node(match, start.line, start.col)); return true; } else { @@ -1096,9 +1191,8 @@ namespace chaiscript /// Reads a char from input if it matches the parameter, without skipping initial whitespace bool Char_(const char c) { - if (has_more_input() && (*m_input_pos == c)) { - ++m_input_pos; - ++m_col; + if (m_position.has_more() && (*m_position == c)) { + ++m_position; return true; } else { return false; @@ -1112,11 +1206,9 @@ namespace chaiscript if (!t_capture) { return Char_(t_c); } else { - const auto start = m_input_pos; - const auto prev_col = m_col; - const auto prev_line = m_line; + const auto start = m_position; if (Char_(t_c)) { - m_match_stack.push_back(make_node(std::string(start, m_input_pos), prev_line, prev_col)); + m_match_stack.push_back(make_node(Position::str(start, m_position), start.line, start.col)); return true; } else { return false; @@ -1128,16 +1220,15 @@ namespace chaiscript bool Keyword_(const char *t_s) { const auto len = strlen(t_s); - if ((m_input_end - m_input_pos) >= static_cast::type>(len)) { - auto tmp = m_input_pos; - for (size_t i = 0; i < len; ++i) { + if (m_position.remaining() >= len) { + auto tmp = m_position; + for (size_t i = 0; tmp.has_more() && i < len; ++i) { if (*tmp != t_s[i]) { return false; } ++tmp; } - m_input_pos = tmp; - m_col += static_cast(len); + m_position = tmp; return true; } @@ -1147,20 +1238,16 @@ namespace chaiscript /// Reads (and potentially captures) a string from input if it matches the parameter bool Keyword(const char *t_s, bool t_capture = false) { SkipWS(); - const auto start = m_input_pos; - const auto prev_col = m_col; - const auto prev_line = m_line; + const auto start = m_position; bool retval = Keyword_(t_s); // ignore substring matches - if ( retval && has_more_input() && char_in_alphabet(*m_input_pos, detail::keyword_alphabet) ) { - m_input_pos = start; - m_col = prev_col; - m_line = prev_line; + if ( retval && m_position.has_more() && char_in_alphabet(*m_position, detail::keyword_alphabet) ) { + m_position = start; retval = false; } if ( t_capture && retval ) { - m_match_stack.push_back(make_node(std::string(start, m_input_pos), prev_line, prev_col)); + m_match_stack.push_back(make_node(Position::str(start, m_position), start.line, start.col)); } return retval; } @@ -1169,40 +1256,49 @@ namespace chaiscript bool Symbol_(const char *t_s) { const auto len = strlen(t_s); - if ((m_input_end - m_input_pos) >= static_cast::type>(len)) { - auto tmp = m_input_pos; - for (size_t i = 0; i < len; ++i) { + if (m_position.remaining() >= len) { + auto tmp = m_position; + for (size_t i = 0; m_position.has_more() && i < len; ++i) { if (*tmp != t_s[i]) { return false; } ++tmp; } - m_input_pos = tmp; - m_col += static_cast(len); + m_position = tmp; return true; } return false; } + 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 std::string &s) { + return s == t_s; + }); + }); + } + /// Reads (and potentially captures) a symbol group from input if it matches the parameter bool Symbol(const char *t_s, const bool t_capture = false, const bool t_disallow_prevention=false) { SkipWS(); - const auto start = m_input_pos; - const auto prev_col = m_col; - const auto prev_line = m_line; + const auto start = m_position; bool retval = Symbol_(t_s); // ignore substring matches - if (retval && has_more_input() && (t_disallow_prevention == false) && char_in_alphabet(*m_input_pos,detail::symbol_alphabet)) { - m_input_pos = start; - m_col = prev_col; - m_line = prev_line; - retval = false; + 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; + } } if ( t_capture && retval ) { - m_match_stack.push_back(make_node(std::string(start, m_input_pos), prev_line, prev_col)); + m_match_stack.push_back(make_node(Position::str(start, m_position), start.line, start.col)); } return retval; @@ -1212,11 +1308,11 @@ namespace chaiscript bool Eol_(const bool t_eos = false) { bool retval = false; - if (has_more_input() && (Symbol_("\r\n") || Char_('\n'))) { + if (m_position.has_more() && (Symbol_("\r\n") || Char_('\n'))) { retval = true; - ++m_line; - m_col = 1; - } else if (has_more_input() && !t_eos && Char_(';')) { + //++m_position.line; + m_position.col = 1; + } else if (m_position.has_more() && !t_eos && Char_(';')) { retval = true; } @@ -1251,7 +1347,7 @@ namespace chaiscript do { while (Eol()) {} if (!Arg(false)) { - throw exception::eval_error("Unexpected value in parameter list", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Unexpected value in parameter list", File_Position(m_position.line, m_position.col), *m_filename); } } while (Char(',')); } @@ -1277,7 +1373,7 @@ namespace chaiscript do { while (Eol()) {} if (!Arg()) { - throw exception::eval_error("Unexpected value in parameter list", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Unexpected value in parameter list", File_Position(m_position.line, m_position.col), *m_filename); } } while (Char(',')); } @@ -1304,7 +1400,7 @@ namespace chaiscript do { while (Eol()) {} if (!Equation()) { - throw exception::eval_error("Unexpected value in parameter list", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Unexpected value in parameter list", File_Position(m_position.line, m_position.col), *m_filename); } } while (Char(',')); } @@ -1334,7 +1430,7 @@ namespace chaiscript do { while (Eol()) {} if (!Map_Pair()) { - throw exception::eval_error("Unexpected value in container", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Unexpected value in container", File_Position(m_position.line, m_position.col), *m_filename); } } while (Char(',')); } @@ -1346,7 +1442,7 @@ namespace chaiscript do { while (Eol()) {} if (!Operator()) { - throw exception::eval_error("Unexpected value in container", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Unexpected value in container", File_Position(m_position.line, m_position.col), *m_filename); } } while (Char(',')); } @@ -1370,7 +1466,7 @@ namespace chaiscript if (Char('[')) { Id_Arg_List(); if (!Char(']')) { - throw exception::eval_error("Incomplete anonymous function bind", File_Position(m_line, m_col), *m_filename); + 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 @@ -1380,17 +1476,17 @@ namespace chaiscript if (Char('(')) { Decl_Arg_List(); if (!Char(')')) { - throw exception::eval_error("Incomplete anonymous function", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Incomplete anonymous function", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1416,7 +1512,7 @@ namespace chaiscript retval = true; if (!Id()) { - throw exception::eval_error("Missing function name in definition", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Missing function name in definition", File_Position(m_position.line, m_position.col), *m_filename); } bool is_method = false; @@ -1426,14 +1522,14 @@ namespace chaiscript is_method = true; if (!Id()) { - throw exception::eval_error("Missing method name in definition", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Incomplete function definition", File_Position(m_position.line, m_position.col), *m_filename); } } @@ -1441,13 +1537,13 @@ namespace chaiscript if (Char(':')) { if (!Operator()) { - throw exception::eval_error("Missing guard expression for function", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Incomplete function definition", File_Position(m_position.line, m_position.col), *m_filename); } if (is_method || t_class_context) { @@ -1476,7 +1572,7 @@ namespace chaiscript while (Eol()) {} if (!Block()) { - throw exception::eval_error("Incomplete 'try' block", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'try' block", File_Position(m_position.line, m_position.col), *m_filename); } bool has_matches = true; @@ -1487,11 +1583,11 @@ namespace chaiscript const auto catch_stack_top = m_match_stack.size(); if (Char('(')) { if (!(Arg() && Char(')'))) { - throw exception::eval_error("Incomplete 'catch' expression", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Missing guard expression for catch", File_Position(m_position.line, m_position.col), *m_filename); } } } @@ -1499,7 +1595,7 @@ namespace chaiscript while (Eol()) {} if (!Block()) { - throw exception::eval_error("Incomplete 'catch' block", File_Position(m_line, m_col), *m_filename); + 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; @@ -1512,7 +1608,7 @@ namespace chaiscript while (Eol()) {} if (!Block()) { - throw exception::eval_error("Incomplete 'finally' block", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'finally' block", File_Position(m_position.line, m_position.col), *m_filename); } build_match(finally_stack_top); } @@ -1533,17 +1629,17 @@ namespace chaiscript retval = true; if (!Char('(')) { - throw exception::eval_error("Incomplete 'if' expression", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'if' expression", File_Position(m_position.line, m_position.col), *m_filename); } if (!(Operator() && Char(')'))) { - throw exception::eval_error("Incomplete 'if' expression", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'if' block", File_Position(m_position.line, m_position.col), *m_filename); } bool has_matches = true; @@ -1557,24 +1653,24 @@ namespace chaiscript chaiscript::make_shared("else if", back->location, back->children); m_match_stack.back()->annotation = back->annotation; if (!Char('(')) { - throw exception::eval_error("Incomplete 'else if' expression", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'else if' expression", File_Position(m_position.line, m_position.col), *m_filename); } if (!(Operator() && Char(')'))) { - throw exception::eval_error("Incomplete 'else if' expression", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'else if' expression", File_Position(m_position.line, m_position.col), *m_filename); } while (Eol()) {} if (!Block()) { - throw exception::eval_error("Incomplete 'else if' block", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'else if' block", File_Position(m_position.line, m_position.col), *m_filename); } has_matches = true; } else { while (Eol()) {} if (!Block()) { - throw exception::eval_error("Incomplete 'else' block", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'else' block", File_Position(m_position.line, m_position.col), *m_filename); } has_matches = true; } @@ -1597,14 +1693,14 @@ namespace chaiscript retval = true; if (!Id()) { - throw exception::eval_error("Missing class name in definition", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Missing class name in definition", File_Position(m_position.line, m_position.col), *m_filename); } while (Eol()) {} if (!Class_Block()) { - throw exception::eval_error("Incomplete 'class' block", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'class' block", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1624,17 +1720,17 @@ namespace chaiscript retval = true; if (!Char('(')) { - throw exception::eval_error("Incomplete 'while' expression", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'while' block", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1650,7 +1746,7 @@ namespace chaiscript { if (!Eol()) { - throw exception::eval_error("'for' loop initial statment missing", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("'for' loop initial statment missing", File_Position(m_position.line, m_position.col), *m_filename); } else { m_match_stack.push_back(chaiscript::make_shared()); } @@ -1660,7 +1756,7 @@ namespace chaiscript { if (!Eol()) { - throw exception::eval_error("'for' loop condition missing", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("'for' loop condition missing", File_Position(m_position.line, m_position.col), *m_filename); } else { m_match_stack.push_back(chaiscript::make_shared()); } @@ -1684,17 +1780,17 @@ namespace chaiscript retval = true; if (!Char('(')) { - throw exception::eval_error("Incomplete 'for' expression", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'for' expression", File_Position(m_position.line, m_position.col), *m_filename); } if (!(For_Guards() && Char(')'))) { - throw exception::eval_error("Incomplete 'for' expression", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'for' block", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1713,25 +1809,27 @@ namespace chaiscript retval = true; if (!Char('(')) { - throw exception::eval_error("Incomplete 'case' expression", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'default' block", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1748,11 +1846,11 @@ namespace chaiscript if (Keyword("switch")) { if (!Char('(')) { - throw exception::eval_error("Incomplete 'switch' expression", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Incomplete 'switch' expression", File_Position(m_position.line, m_position.col), *m_filename); } while (Eol()) {} @@ -1767,11 +1865,11 @@ namespace chaiscript while (Eol()) { } // eat if (!Char('}')) { - throw exception::eval_error("Incomplete block", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Incomplete block", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1795,7 +1893,7 @@ namespace chaiscript Class_Statements(); if (!Char('}')) { - throw exception::eval_error("Incomplete class block", File_Position(m_line, m_col), *m_filename); + 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) { @@ -1819,7 +1917,7 @@ namespace chaiscript Statements(); if (!Char('}')) { - throw exception::eval_error("Incomplete block", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete block", File_Position(m_position.line, m_position.col), *m_filename); } if (m_match_stack.size() == prev_stack_top) { @@ -1888,20 +1986,24 @@ namespace chaiscript Arg_List(); if (!Char(')')) { - throw exception::eval_error("Incomplete function call", File_Position(m_line, m_col), *m_filename); + 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); AST_NodePtr dot_access = m_match_stack.back()->children[0]; AST_NodePtr func_call = 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(), dot_access->children.back()); dot_access->children.pop_back(); dot_access->children.push_back(std::move(func_call)); + if (dot_access->children.size() != 3) 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)); } } @@ -1909,7 +2011,7 @@ namespace chaiscript has_more = true; if (!(Operator() && Char(']'))) { - throw exception::eval_error("Incomplete array access", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete array access", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1917,9 +2019,12 @@ namespace chaiscript else if (Symbol(".", true)) { has_more = true; if (!(Id())) { - throw exception::eval_error("Incomplete array access", File_Position(m_line, m_col), *m_filename); + 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()) != 3) { + throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename); + } build_match(prev_stack_top); } } @@ -1938,7 +2043,7 @@ namespace chaiscript retval = true; if (!Id()) { - throw exception::eval_error("Incomplete attribute declaration", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete attribute declaration", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1946,7 +2051,7 @@ namespace chaiscript retval = true; if (!(Reference() || Id())) { - throw exception::eval_error("Incomplete variable declaration", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete variable declaration", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1954,7 +2059,7 @@ namespace chaiscript retval = true; if (!(Reference() || Id())) { - throw exception::eval_error("Incomplete global declaration", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete global declaration", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -1962,13 +2067,13 @@ namespace chaiscript retval = true; if (!Id()) { - throw exception::eval_error("Incomplete attribute declaration", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete attribute declaration", File_Position(m_position.line, m_position.col), *m_filename); } if (!Symbol("::", false)) { - throw exception::eval_error("Incomplete attribute declaration", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete attribute declaration", File_Position(m_position.line, m_position.col), *m_filename); } if (!Id()) { - throw exception::eval_error("Missing attribute name in definition", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Missing attribute name in definition", File_Position(m_position.line, m_position.col), *m_filename); } @@ -1982,10 +2087,10 @@ namespace chaiscript bool Paren_Expression() { if (Char('(')) { if (!Operator()) { - throw exception::eval_error("Incomplete expression", File_Position(m_line, m_col), *m_filename); + 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_line, m_col), *m_filename); + throw exception::eval_error("Missing closing parenthesis ')'", File_Position(m_position.line, m_position.col), *m_filename); } return true; } else { @@ -2001,7 +2106,7 @@ namespace chaiscript Container_Arg_List(); if (!Char(']')) { - throw exception::eval_error("Missing closing square bracket ']' in container initializer", File_Position(m_line, m_col), *m_filename); + 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.size() > 0)) { if (m_match_stack.back()->children[0]->identifier == AST_Node_Type::Value_Range) { @@ -2030,7 +2135,7 @@ namespace chaiscript if (Symbol("&", false)) { if (!Id()) { - throw exception::eval_error("Incomplete '&' expression", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete '&' expression", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -2051,7 +2156,7 @@ namespace chaiscript if ((is_char && Char(oper[0], true)) || (!is_char && Symbol(oper.c_str(), true))) { if (!Operator(m_operators.size()-1)) { - throw exception::eval_error("Incomplete prefix '" + oper + "' expression", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete prefix '" + oper + "' expression", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -2089,7 +2194,7 @@ namespace chaiscript if (!Operator(t_precedence+1)) { throw exception::eval_error("Incomplete " + std::string(ast_node_type_to_string(m_operators[t_precedence])) + " expression", - File_Position(m_line, m_col), *m_filename); + File_Position(m_position.line, m_position.col), *m_filename); } AST_NodePtr oper = m_match_stack.at(m_match_stack.size()-2); @@ -2102,14 +2207,14 @@ namespace chaiscript if (!Operator(t_precedence+1)) { throw exception::eval_error("Incomplete " + std::string(ast_node_type_to_string(m_operators[t_precedence])) + " expression", - File_Position(m_line, m_col), *m_filename); + File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); } else { throw exception::eval_error("Incomplete " + std::string(ast_node_type_to_string(m_operators[t_precedence])) + " expression", - File_Position(m_line, m_col), *m_filename); + File_Position(m_position.line, m_position.col), *m_filename); } break; @@ -2134,7 +2239,7 @@ namespace chaiscript break; default: - throw exception::eval_error("Internal error: unhandled ast_node", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Internal error: unhandled ast_node", File_Position(m_position.line, m_position.col), *m_filename); } } while (Operator_Helper(t_precedence)); } @@ -2152,21 +2257,19 @@ namespace chaiscript bool retval = false; const auto prev_stack_top = m_match_stack.size(); - const auto prev_pos = m_input_pos; - const auto prev_col = m_col; + const auto prev_pos = m_position; if (Operator()) { if (Symbol(":")) { retval = true; if (!Operator()) { - throw exception::eval_error("Incomplete map pair", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete map pair", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); } else { - m_input_pos = prev_pos; - m_col = prev_col; + m_position = prev_pos; while (prev_stack_top != m_match_stack.size()) { m_match_stack.pop_back(); } @@ -2181,21 +2284,19 @@ namespace chaiscript bool retval = false; const auto prev_stack_top = m_match_stack.size(); - const auto prev_pos = m_input_pos; - const auto prev_col = m_col; + const auto prev_pos = m_position; if (Operator()) { if (Symbol("..")) { retval = true; if (!Operator()) { - throw exception::eval_error("Incomplete value range", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete value range", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); } else { - m_input_pos = prev_pos; - m_col = prev_col; + m_position = prev_pos; while (prev_stack_top != m_match_stack.size()) { m_match_stack.pop_back(); } @@ -2219,7 +2320,7 @@ namespace chaiscript Symbol("&=", true, true) || Symbol("^=", true, true) || Symbol("|=", true, true)) { SkipWS(true); if (!Equation()) { - throw exception::eval_error("Incomplete equation", File_Position(m_line, m_col), *m_filename); + throw exception::eval_error("Incomplete equation", File_Position(m_position.line, m_position.col), *m_filename); } build_match(prev_stack_top); @@ -2237,11 +2338,10 @@ namespace chaiscript bool saw_eol = true; while (has_more) { - const auto prev_line = m_line; - const auto prev_col = m_col; + const auto start = m_position; if (Def(true) || Var_Decl(true)) { if (!saw_eol) { - throw exception::eval_error("Two function definitions missing line separator", File_Position(prev_line, prev_col), *m_filename); + throw exception::eval_error("Two function definitions missing line separator", File_Position(start.line, start.col), *m_filename); } has_more = true; retval = true; @@ -2266,11 +2366,10 @@ namespace chaiscript bool saw_eol = true; while (has_more) { - int prev_line = m_line; - int prev_col = m_col; + const auto start = m_position; if (Def() || Try() || If() || While() || Class() || For() || Switch()) { if (!saw_eol) { - throw exception::eval_error("Two function definitions missing line separator", File_Position(prev_line, prev_col), *m_filename); + throw exception::eval_error("Two function definitions missing line separator", File_Position(start.line, start.col), *m_filename); } has_more = true; retval = true; @@ -2278,7 +2377,7 @@ namespace chaiscript } else if (Return() || Break() || Continue() || Equation()) { if (!saw_eol) { - throw exception::eval_error("Two expressions missing line separator", File_Position(prev_line, prev_col), *m_filename); + throw exception::eval_error("Two expressions missing line separator", File_Position(start.line, start.col), *m_filename); } has_more = true; retval = true; @@ -2299,24 +2398,22 @@ namespace chaiscript /// Parses the given input string, tagging parsed ast_nodes with the given m_filename. bool parse(const std::string &t_input, std::string t_fname) { - m_input_pos = t_input.begin(); - m_input_end = t_input.end(); - m_line = 1; - m_col = 1; + 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_input_pos != m_input_end) && (!Eol())) { - ++m_input_pos; + while (m_position.has_more() && (!Eol())) { + ++m_position; } /// \todo respect // -*- coding: utf-8 -*- on line 1 or 2 see: http://evanjones.ca/python-utf8.html) } if (Statements()) { - if (m_input_pos != m_input_end) { - throw exception::eval_error("Unparsed input", File_Position(m_line, m_col), t_fname); + if (m_position.has_more()) { + throw exception::eval_error("Unparsed input", File_Position(m_position.line, m_position.col), t_fname); } else { build_match(0); + //debug_print(ast()); return true; } } else { @@ -2327,5 +2424,12 @@ namespace chaiscript } } + +#ifdef CHAISCRIPT_MSVC +#pragma pop_macro("min") +#pragma pop_macro("max") +#endif + + #endif /* CHAISCRIPT_PARSER_HPP_ */ diff --git a/include/chaiscript/language/chaiscript_prelude.chai b/include/chaiscript/language/chaiscript_prelude.chai index 0fe2a5a6..55514bba 100644 --- a/include/chaiscript/language/chaiscript_prelude.chai +++ b/include/chaiscript/language/chaiscript_prelude.chai @@ -41,25 +41,25 @@ def new(x) { } def clone(double x) { - double(x).copy_var_attrs(x) + double(x).clone_var_attrs(x) } def clone(string x) { - string(x).copy_var_attrs(x) + string(x).clone_var_attrs(x) } def clone(vector x) { - vector(x).copy_var_attrs(x) + vector(x).clone_var_attrs(x) } def clone(int x) { - int(x).copy_var_attrs(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).copy_var_attrs(x); + eval(type_name(x))(x).clone_var_attrs(x); } @@ -145,11 +145,6 @@ def reverse(container) { retval; } -# Return a range from a range -def range(r) : call_exists(empty, r) && call_exists(pop_front, r) && call_exists(pop_back, r) && call_exists(back, r) && call_exists(front, r) -{ - clone(r); -} def range(r) : call_exists(range_internal, r) { @@ -158,6 +153,13 @@ def range(r) : call_exists(range_internal, r) ri; } +# Return a range from a range +def range(r) : call_exists(empty, r) && call_exists(pop_front, r) && call_exists(pop_back, r) && call_exists(back, r) && call_exists(front, r) +{ + clone(r); +} + + # The retro attribute that contains the underlying range attr retro::m_range; diff --git a/include/chaiscript/utility/json.hpp b/include/chaiscript/utility/json.hpp new file mode 100644 index 00000000..c023c2bf --- /dev/null +++ b/include/chaiscript/utility/json.hpp @@ -0,0 +1,647 @@ +// 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 + +namespace json { + +using std::map; +using std::deque; +using std::string; +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; + +namespace { + string json_escape( const string &str ) { + string output; + for( unsigned i = 0; i < str.length(); ++i ) + switch( str[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 += str[i]; break; + } + return output; + } +} + +class JSON +{ + union BackingData { + BackingData( double d ) : Float( d ){} + BackingData( long l ) : Int( l ){} + BackingData( bool b ) : Bool( b ){} + BackingData( string s ) : String( new string( s ) ){} + BackingData() : Int( 0 ){} + + deque *List; + map *Map; + string *String; + double Float; + long Int; + bool Bool; + } Internal; + + public: + enum class Class { + Null, + Object, + Array, + String, + Floating, + Integral, + Boolean + }; + + template + class JSONWrapper { + Container *object; + + public: + JSONWrapper( Container *val ) : object( val ) {} + JSONWrapper( std::nullptr_t ) : object( nullptr ) {} + + 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; + + public: + JSONConstWrapper( const Container *val ) : object( val ) {} + JSONConstWrapper( std::nullptr_t ) : object( nullptr ) {} + + 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() : Internal(), Type( Class::Null ){} + + explicit JSON(Class type) + : Internal(), Type(Class::Null) + { + SetType( type ); + } + + JSON( initializer_list list ) + : Internal(), Type(Class::Null) + { + SetType( Class::Object ); + for( auto i = list.begin(), e = list.end(); i != e; ++i, ++i ) + operator[]( i->ToString() ) = *std::next( i ); + } + + JSON( JSON&& other ) + : Internal( other.Internal ) + , Type( other.Type ) + { other.Type = Class::Null; other.Internal.Map = nullptr; } + + JSON& operator=( JSON&& other ) { + Internal = other.Internal; + Type = other.Type; + other.Internal.Map = nullptr; + other.Type = Class::Null; + return *this; + } + + JSON( const JSON &other ) { + switch( other.Type ) { + case Class::Object: + Internal.Map = + new map( other.Internal.Map->begin(), + other.Internal.Map->end() ); + break; + case Class::Array: + Internal.List = + new deque( other.Internal.List->begin(), + other.Internal.List->end() ); + break; + case Class::String: + Internal.String = + new string( *other.Internal.String ); + break; + default: + Internal = other.Internal; + } + Type = other.Type; + } + + JSON& operator=( const JSON &other ) { + if (&other == this) return *this; + + switch( other.Type ) { + case Class::Object: + Internal.Map = + new map( other.Internal.Map->begin(), + other.Internal.Map->end() ); + break; + case Class::Array: + Internal.List = + new deque( other.Internal.List->begin(), + other.Internal.List->end() ); + break; + case Class::String: + Internal.String = + new string( *other.Internal.String ); + break; + default: + Internal = other.Internal; + } + Type = other.Type; + return *this; + } + + ~JSON() { + switch( Type ) { + case Class::Array: + delete Internal.List; + break; + case Class::Object: + delete Internal.Map; + break; + case Class::String: + delete Internal.String; + break; + default:; + } + } + + template + JSON( T b, typename enable_if::value>::type* = 0 ) : Internal( b ), Type( Class::Boolean ){} + + template + JSON( T i, typename enable_if::value && !is_same::value>::type* = 0 ) : Internal( long(i) ), Type( Class::Integral ){} + + template + JSON( T f, typename enable_if::value>::type* = 0 ) : Internal( double(f) ), Type( Class::Floating ){} + + template + JSON( T s, typename enable_if::value>::type* = 0 ) : Internal( string( s ) ), Type( Class::String ){} + + JSON( std::nullptr_t ) : Internal(), Type( Class::Null ){} + + static JSON Make( Class type ) { + return JSON(type); + } + + static JSON Load( const string & ); + + template + void append( T arg ) { + SetType( Class::Array ); Internal.List->emplace_back( arg ); + } + + template + void append( T arg, U... args ) { + append( arg ); append( args... ); + } + + template + typename enable_if::value, JSON&>::type operator=( T b ) { + SetType( Class::Boolean ); Internal.Bool = b; return *this; + } + + template + typename enable_if::value && !is_same::value, JSON&>::type operator=( T i ) { + SetType( Class::Integral ); Internal.Int = i; return *this; + } + + template + typename enable_if::value, JSON&>::type operator=( T f ) { + SetType( Class::Floating ); Internal.Float = f; return *this; + } + + template + typename enable_if::value, JSON&>::type operator=( T s ) { + SetType( Class::String ); *Internal.String = string( s ); return *this; + } + + JSON& operator[]( const string &key ) { + SetType( Class::Object ); return Internal.Map->operator[]( key ); + } + + JSON& operator[]( const size_t index ) { + SetType( Class::Array ); + if( index >= Internal.List->size() ) Internal.List->resize( index + 1 ); + return Internal.List->operator[]( index ); + } + + JSON &at( const string &key ) { + return operator[]( key ); + } + + const JSON &at( const string &key ) const { + return Internal.Map->at( key ); + } + + JSON &at( unsigned index ) { + return operator[]( index ); + } + + const JSON &at( unsigned index ) const { + return Internal.List->at( index ); + } + + int length() const { + if( Type == Class::Array ) + return static_cast(Internal.List->size()); + else + return -1; + } + + bool hasKey( const string &key ) const { + if( Type == Class::Object ) + return Internal.Map->find( key ) != Internal.Map->end(); + return false; + } + + int size() const { + if( Type == Class::Object ) + return static_cast(Internal.Map->size()); + else if( Type == Class::Array ) + return static_cast(Internal.List->size()); + else + return -1; + } + + Class JSONType() const { return Type; } + + /// Functions for getting primitives from the JSON object. + bool IsNull() const { return Type == Class::Null; } + + string ToString() const { bool b; return ToString( b ); } + string ToString( bool &ok ) const { + ok = (Type == Class::String); + return ok ? *Internal.String : string(""); + } + + double ToFloat() const { bool b; return ToFloat( b ); } + double ToFloat( bool &ok ) const { + ok = (Type == Class::Floating); + return ok ? Internal.Float : 0.0; + } + + long ToInt() const { bool b; return ToInt( b ); } + long ToInt( bool &ok ) const { + ok = (Type == Class::Integral); + return ok ? Internal.Int : 0; + } + + bool ToBool() const { bool b; return ToBool( b ); } + bool ToBool( bool &ok ) const { + ok = (Type == Class::Boolean); + return ok ? Internal.Bool : false; + } + + JSONWrapper> ObjectRange() { + if( Type == Class::Object ) + return JSONWrapper>( Internal.Map ); + return JSONWrapper>( nullptr ); + } + + JSONWrapper> ArrayRange() { + if( Type == Class::Array ) + return JSONWrapper>( Internal.List ); + return JSONWrapper>( nullptr ); + } + + JSONConstWrapper> ObjectRange() const { + if( Type == Class::Object ) + return JSONConstWrapper>( Internal.Map ); + return JSONConstWrapper>( nullptr ); + } + + + JSONConstWrapper> ArrayRange() const { + if( Type == Class::Array ) + return JSONConstWrapper>( Internal.List ); + return JSONConstWrapper>( nullptr ); + } + + string dump( int depth = 1, string tab = " ") const { + switch( Type ) { + case Class::Null: + return "null"; + case Class::Object: { + string pad = ""; + for( int i = 0; i < depth; ++i, pad += tab ); + + string s = "{\n"; + bool skip = true; + for( auto &p : *Internal.Map ) { + if( !skip ) s += ",\n"; + s += ( pad + "\"" + p.first + "\" : " + p.second.dump( depth + 1, tab ) ); + skip = false; + } + s += ( "\n" + pad.erase( 0, 2 ) + "}" ) ; + return s; + } + case Class::Array: { + 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"); + } + + friend std::ostream& operator<<( std::ostream&, const JSON & ); + + private: + void SetType( Class type ) { + if( type == Type ) + return; + + switch( Type ) { + case Class::Object: delete Internal.Map; break; + case Class::Array: delete Internal.List; break; + case Class::String: delete Internal.String; break; + default:; + } + + switch( type ) { + case Class::Null: Internal.Map = nullptr; break; + case Class::Object: Internal.Map = new map(); break; + case Class::Array: Internal.List = new deque(); break; + case Class::String: Internal.String = new string(); break; + case Class::Floating: Internal.Float = 0.0; break; + case Class::Integral: Internal.Int = 0; break; + case Class::Boolean: Internal.Bool = false; break; + } + + Type = type; + } + + private: + + Class Type; +}; + +inline JSON Array() { + return JSON::Make( JSON::Class::Array ); +} + +template +inline JSON Array( T... args ) { + JSON arr = JSON::Make( JSON::Class::Array ); + arr.append( args... ); + return arr; +} + +inline JSON Object() { + return JSON::Make( JSON::Class::Object ); +} + +inline std::ostream& operator<<( std::ostream &os, const JSON &json ) { + os << json.dump(); + return os; +} + +namespace { + JSON parse_next( const string &, size_t & ); + + void consume_ws( const string &str, size_t &offset ) { + while( isspace( str[offset] ) ) ++offset; + } + + JSON parse_object( const string &str, size_t &offset ) { + JSON Object = JSON::Make( JSON::Class::Object ); + + ++offset; + consume_ws( str, offset ); + if( str[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); + } + + JSON parse_number( const string &str, size_t &offset ) { + JSON Number; + string val, exp_str; + char c = '\0'; + bool isDouble = false; + long exp = 0; + for (; offset < str.size() ;) { + c = str[offset++]; + if( (c == '-') || (c >= '0' && c <= '9') ) + val += c; + else if( c == '.' ) { + val += c; + isDouble = true; + } + else + break; + } + if( offset < str.size() && (c == 'E' || c == 'e' )) { + c = str[ offset++ ]; + if( c == '-' ) { exp_str += '-';} + else if( c == '+' ) { } + else --offset; + + for (; offset < str.size() ;) { + c = str[ 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 = std::stol( exp_str ); + } + 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 ) + Number = std::stod( val ) * std::pow( 10, exp ); + else { + if( !exp_str.empty() ) + Number = std::stol( val ) * std::pow( 10, exp ); + else + Number = std::stol( val ); + } + return Number; + } + + JSON parse_bool( const string &str, size_t &offset ) { + JSON Bool; + if( str.substr( offset, 4 ) == "true" ) { + offset += 4; + Bool = true; + } else if( str.substr( offset, 5 ) == "false" ) { + offset += 5; + Bool = false; + } else { + throw std::runtime_error(std::string("JSON ERROR: Bool: Expected 'true' or 'false', found '") + str.substr( offset, 5 ) + "'"); + } + return Bool; + } + + JSON parse_null( const 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(); + } + + JSON parse_next( const string &str, size_t &offset ) { + char value; + consume_ws( str, offset ); + value = str[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 string &str ) { + size_t offset = 0; + return parse_next( str, offset ); +} + +} // End Namespace json + + +#endif diff --git a/include/chaiscript/utility/json_wrap.hpp b/include/chaiscript/utility/json_wrap.hpp new file mode 100644 index 00000000..d15528ff --- /dev/null +++ b/include/chaiscript/utility/json_wrap.hpp @@ -0,0 +1,158 @@ +#ifndef CHAISCRIPT_SIMPLEJSON_WRAP_HPP +#define CHAISCRIPT_SIMPLEJSON_WRAP_HPP + +#include "json.hpp" + +namespace chaiscript +{ + class json_wrap + { + public: + + static ModulePtr library(ModulePtr m = std::make_shared()) + { + + 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.ObjectRange()) + { + 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.ArrayRange()) + { + vec.emplace_back(from_json(p)); + } + + return Boxed_Value(vec); + } + case json::JSON::Class::String: + return Boxed_Value(t_json.ToString()); + case json::JSON::Class::Floating: + return Boxed_Value(t_json.ToFloat()); + case json::JSON::Class::Integral: + return Boxed_Value(t_json.ToInt()); + case json::JSON::Class::Boolean: + return Boxed_Value(t_json.ToBool()); + } + + throw std::runtime_error("Unknown JSON type"); + } + + static Boxed_Value from_json(const std::string &t_json) + { + return from_json( json::JSON::Load(t_json) ); + } + + 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; + 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; + 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); + json::JSON obj; + if (Boxed_Number::is_floating_point(t_bv)) + { + obj = bn.get_as(); + } else { + obj = bn.get_as(); + } + return obj; + } catch (const chaiscript::detail::exception::bad_any_cast &) { + // not a number + } + + try { + bool b = boxed_cast(t_bv); + json::JSON obj; + obj = b; + return obj; + } catch (const chaiscript::exception::bad_boxed_cast &) { + // not a bool + } + + try { + std::string s = boxed_cast(t_bv); + json::JSON obj; + obj = s; + return obj; + } catch (const chaiscript::exception::bad_boxed_cast &) { + // not a string + } + + + try { + const chaiscript::dispatch::Dynamic_Object &o = boxed_cast(t_bv); + + json::JSON obj; + 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 + } + + throw std::runtime_error("Unknown object type to convert to JSON"); + } + + + }; + + +} + +#endif diff --git a/include/chaiscript/utility/utility.hpp b/include/chaiscript/utility/utility.hpp index ecd5a3f0..381afda2 100644 --- a/include/chaiscript/utility/utility.hpp +++ b/include/chaiscript/utility/utility.hpp @@ -8,12 +8,14 @@ #define CHAISCRIPT_UTILITY_UTILITY_HPP_ #include +#include #include #include #include "../chaiscript.hpp" #include "../dispatchkit/proxy_functions.hpp" #include "../dispatchkit/type_info.hpp" +#include "../dispatchkit/operators.hpp" namespace chaiscript @@ -62,6 +64,32 @@ namespace chaiscript 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); + + t_module.add([](){ + // add some comparison and assignment operators + using namespace chaiscript::bootstrap::operators; + return assign(not_equal(equal())); + }()); + + t_module.add(chaiscript::fun([](const Enum &e, const typename std::underlying_type::type &i) { return e == i; }), "=="); + t_module.add(chaiscript::fun([](const typename std::underlying_type::type &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); + } + } } } diff --git a/samples/factory.cpp b/samples/factory.cpp new file mode 100644 index 00000000..ac8eb476 --- /dev/null +++ b/samples/factory.cpp @@ -0,0 +1,103 @@ +#include +#include + +class Entity +{ + public: + int width; + int height; + int x; + int y; + std::string name; + + std::function updater; + + Entity(const int t_width, const int t_height, const int t_x, const int t_y, std::string t_name) + : width(t_width), height(t_height), x(t_x), y(t_y), name(std::move(t_name)) + { + } +}; + +class Factory +{ + public: + // we may as well pass the parameters for the entity to the factory method, this does the initialization + // in one step. + Entity *make_entity(const int width, const int height, const int x, const int y, const std::string &name) + { + auto entity = entities.insert({name, Entity{width, height, x, y, name}}); + return &(entity.first->second); + } + + Entity *get_entity(const std::string &name) + { + return &entities.at(name); + } + + + // loop over all entities and all their updater function (if it exists) + void update_entities() + { + for (auto &entity : entities) + { + if (entity.second.updater) { + entity.second.updater(entity.second); + } + } + } + + + private: + // we cannot store the entities in a std::vector if we want to return a pointer to them, + // because a vector automatically resizing itself can invalidate the pointer that was returned. + // using a map guarantees that the memory assigned to the entity will never change, plus + // lets us easily look up an entity by name + std::map entities; +}; + +int main() +{ + chaiscript::ChaiScript chai(chaiscript::Std_Lib::library()); + + chai.add(chaiscript::fun(&Entity::width), "width"); + chai.add(chaiscript::fun(&Entity::height), "height"); + chai.add(chaiscript::fun(&Entity::x), "x"); + chai.add(chaiscript::fun(&Entity::y), "y"); + chai.add(chaiscript::fun(&Entity::name), "name"); + chai.add(chaiscript::fun(&Entity::updater), "updater"); + chai.add(chaiscript::user_type(), "Entity"); // this isn't strictly necessary but makes error messages nicer + + chai.add(chaiscript::fun(&Factory::make_entity), "make_entity"); + chai.add(chaiscript::fun(&Factory::get_entity), "get_entity"); + chai.add(chaiscript::fun(&Factory::update_entities), "update_entities"); + chai.add(chaiscript::user_type(), "Factory"); // this isn't strictly necessary but makes error messages nicer + + + Factory f; + chai.add(chaiscript::var(&f), "f"); + + std::string script = R""( + f.make_entity(10,10,1,1,"entity1").updater = fun(e){ e.x += 1; e.y += 1 }; + f.make_entity(10,10,10,10,"entity2").updater = fun(e){ e.x += 2; e.y += 2 }; + f.make_entity(10,10,20,20,"entity3"); + + print(f.get_entity("entity1").x == 1) + print(f.get_entity("entity2").x == 10) + print(f.get_entity("entity3").x == 20) + + f.update_entities(); // this runs the function objects we set in the previous lines + // we should now see the updated values + + print(f.get_entity("entity1").x == 2) + print(f.get_entity("entity2").x == 12) + print(f.get_entity("entity3").x == 20) // this one has no updater, so it stays the same + )""; + + + chai.eval(script); + + + +} + + diff --git a/samples/test_num_exceptions.cpp b/samples/test_num_exceptions.cpp new file mode 100644 index 00000000..36bbb756 --- /dev/null +++ b/samples/test_num_exceptions.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +int main( int /*argc*/ , char * /*argv*/[] ) +{ + chaiscript::ChaiScript ch( chaiscript::Std_Lib::library( ) ); + + static const char script[ ] = + R""( + + class Rectangle + { + def Rectangle() { } + } + + var rect = Rectangle( ); + + )""; + + try + { + ch.eval( script ); + } + catch ( const std::exception &e ) + { + printf( " >>> Exception thrown: %s \n" , e.what( ) ); + } + + return 1; +} diff --git a/unittests/bool_comparisons.chai b/unittests/bool_comparisons.chai new file mode 100644 index 00000000..6fdea7a1 --- /dev/null +++ b/unittests/bool_comparisons.chai @@ -0,0 +1,12 @@ + +assert_true(true == true) +assert_false(true == false) +assert_true(true != false) +assert_true(false != true) +assert_true(false || true) +assert_true(true || false) +assert_false(true && false) +assert_false(false && true) +assert_true(!false) +assert_false(!true) + diff --git a/unittests/boxed_cast_test.cpp b/unittests/boxed_cast_test.cpp index e5756492..1e928749 100644 --- a/unittests/boxed_cast_test.cpp +++ b/unittests/boxed_cast_test.cpp @@ -13,9 +13,9 @@ bool run_test_type_conversion(const Boxed_Value &bv, bool expectedpass) try { To ret = chaiscript::boxed_cast(bv); use(ret); - } catch (const chaiscript::exception::bad_boxed_cast &/*e*/) { + } catch (const chaiscript::exception::bad_boxed_cast &e) { if (expectedpass) { -// std::cerr << "Failure in run_test_type_conversion: " << e.what() << '\n'; + std::cerr << "Failure in run_test_type_conversion: " << e.what() << '\n'; return false; } else { return true; diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index c856e9aa..9a9a4a59 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -2,6 +2,7 @@ // caught in other cpp files if chaiscript causes them #include +#include #ifdef CHAISCRIPT_MSVC #pragma warning(push) @@ -518,6 +519,65 @@ TEST_CASE("Utility_Test utility class wrapper") } +enum Utility_Test_Numbers +{ + ONE, + TWO, + THREE +}; + +void do_something_with_enum_vector(const std::vector &v) +{ + CHECK(v.size() == 3); + CHECK(v[0] == ONE); + CHECK(v[1] == THREE); + CHECK(v[2] == TWO); +} + +TEST_CASE("Utility_Test utility class wrapper for enum") +{ + + chaiscript::ModulePtr m = chaiscript::ModulePtr(new chaiscript::Module()); + + using namespace chaiscript; + + chaiscript::utility::add_class(*m, + "Utility_Test_Numbers", + { { ONE, "ONE" }, + { TWO, "TWO" }, + { THREE, "THREE" } + + } + ); + + + chaiscript::ChaiScript chai; + chai.add(m); + + CHECK(chai.eval("ONE ") == 0); + CHECK(chai.eval("TWO ") == 1); + CHECK(chai.eval("THREE ") == 2); + + CHECK(chai.eval("ONE == 0")); + + chai.add(chaiscript::fun(&do_something_with_enum_vector), "do_something_with_enum_vector"); + chai.add(chaiscript::vector_conversion>()); + CHECK_NOTHROW(chai.eval("var a = [ONE, TWO, THREE]")); + CHECK_NOTHROW(chai.eval("do_something_with_enum_vector([ONE, THREE, TWO])")); + CHECK_NOTHROW(chai.eval("[ONE]")); + + const auto v = chai.eval>("a"); + CHECK(v.size() == 3); + CHECK(v.at(1) == TWO); + + CHECK(chai.eval("ONE == ONE")); + CHECK(chai.eval("ONE != TWO")); + CHECK_NOTHROW(chai.eval("var o = ONE; o = TWO")); + + +} + + ////// Object copy count test class Object_Copy_Count_Test @@ -743,3 +803,113 @@ TEST_CASE("Test Derived->Base with non-polymorphic classes") chai.add(chaiscript::fun(&myfunction), "myfunction"); CHECK(chai.eval("myfunction(d)") == 2); } + + +struct TestCppVariableScope +{ + void print() + { + std::cout << "Printed" << std::endl; + } +}; + +TEST_CASE("Variable Scope When Calling From C++") +{ + chaiscript::ChaiScript chai; + chai.add(chaiscript::user_type(), "Test"); + chai.add(chaiscript::constructor(), "Test"); + chai.add(chaiscript::fun(&TestCppVariableScope::print), "print"); + chai.eval(R"(var t := Test(); + + def func() + { + t.print(); + } + + )"); + + CHECK_THROWS(chai.eval("func()")); + + chai.eval("dump_object(t)"); + + auto func = chai.eval>("func"); + CHECK_THROWS(func()); +} + +TEST_CASE("Variable Scope When Calling From C++ 2") +{ + chaiscript::ChaiScript chai; + chai.eval("var obj = 2;"); + auto func = chai.eval>("fun(){ return obj; }"); + CHECK_THROWS(func()); +} + +void ulonglong(unsigned long long i) { + std::cout << i << '\n'; +} + + +void longlong(long long i) { + std::cout << i << '\n'; +} + +TEST_CASE("Test long long dispatch") +{ + chaiscript::ChaiScript chai; + chai.add(chaiscript::fun(&longlong), "longlong"); + chai.add(chaiscript::fun(&ulonglong), "ulonglong"); + chai.eval("longlong(15)"); + chai.eval("ulonglong(15)"); +} + + +struct Returned_Converted_Config +{ + int num_iterations; + int something_else; + std::string a_string; + std::function a_function; +}; + + +TEST_CASE("Return of converted type from script") +{ + chaiscript::ChaiScript chai; + + chai.add(chaiscript::constructor(), "Returned_Converted_Config"); + chai.add(chaiscript::fun(&Returned_Converted_Config::num_iterations), "num_iterations"); + chai.add(chaiscript::fun(&Returned_Converted_Config::something_else), "something_else"); + chai.add(chaiscript::fun(&Returned_Converted_Config::a_string), "a_string"); + chai.add(chaiscript::fun(&Returned_Converted_Config::a_function), "a_function"); + chai.add(chaiscript::vector_conversion>()); + + auto c = chai.eval>(R"( + var c = Returned_Converted_Config(); + + c.num_iterations = 5; + c.something_else = c.num_iterations * 2; + c.a_string = "string"; + c.a_function = fun(s) { s.size(); } + + print("making vector"); + var v = []; + print("adding config item"); + v.push_back_ref(c); + print("returning vector"); + v; + + )"); + + + std::cout << typeid(decltype(c)).name() << std::endl; + + std::cout << "Info: " << c.size() << " " << &c[0] << std::endl; + + std::cout << "num_iterations " << c[0].num_iterations << '\n' + << "something_else " << c[0].something_else << '\n' + << "a_string " << c[0].a_string << '\n' + << "a_function " << c[0].a_function("bob") << '\n'; + + chai.add(chaiscript::user_type(), "Returned_Converted_Config"); +} + diff --git a/unittests/dynamic_object_dynamic_attrs_explicit.chai b/unittests/dynamic_object_dynamic_attrs_explicit.chai new file mode 100644 index 00000000..b37dc0fb --- /dev/null +++ b/unittests/dynamic_object_dynamic_attrs_explicit.chai @@ -0,0 +1,14 @@ + +class MyClass { + def MyClass() + { + this.set_explicit(true); + } +}; + +var o = MyClass(); + +assert_true(o.is_explicit()); + +assert_throws("error", fun[o](){o.x = 2}) + diff --git a/unittests/fuzzy_tests-2015-07-16.tar.bz2 b/unittests/fuzzy_tests-2015-07-16.tar.bz2 index d150daae..9768b8e4 100644 Binary files a/unittests/fuzzy_tests-2015-07-16.tar.bz2 and b/unittests/fuzzy_tests-2015-07-16.tar.bz2 differ diff --git a/unittests/hex_escapes.chai b/unittests/hex_escapes.chai new file mode 100644 index 00000000..fdd0a8a6 --- /dev/null +++ b/unittests/hex_escapes.chai @@ -0,0 +1,7 @@ + +assert_equal("\x39", "9") +assert_equal("\x39ec", "9ec") +assert_equal("\x39g", "9g") +assert_equal("b\x39g", "b9g") +assert_equal("\x39\x38g", "98g") + diff --git a/unittests/integer_literal_test.cpp b/unittests/integer_literal_test.cpp index ecaff8a1..58e954a9 100644 --- a/unittests/integer_literal_test.cpp +++ b/unittests/integer_literal_test.cpp @@ -5,9 +5,10 @@ template bool test_literal(T val, const std::string &str) { + std::cout << "Comparing : " << val; chaiscript::ChaiScript chai; T val2 = chai.eval(str); - std::cout << "Comparing : " << val << " " << val2 << '\n'; + std::cout << " " << val2 << '\n'; return val == val2; } @@ -74,6 +75,39 @@ int main() && TEST_LITERAL(177777777777777777) && TEST_LITERAL(1777777777777777777) + && test_literal(0xF, "0b1111") + && test_literal(0xFF, "0b11111111") + && test_literal(0xFFF, "0b111111111111") + && test_literal(0xFFFF, "0b1111111111111111") + && test_literal(0xFFFFF, "0b11111111111111111111") + && test_literal(0xFFFFFF, "0b111111111111111111111111") + && test_literal(0xFFFFFFF, "0b1111111111111111111111111111") + && test_literal(0xFFFFFFFF, "0b11111111111111111111111111111111") + && test_literal(0xFFFFFFFFF, "0b111111111111111111111111111111111111") + && test_literal(0xFFFFFFFFFF, "0b1111111111111111111111111111111111111111") + && test_literal(0xFFFFFFFFFFF, "0b11111111111111111111111111111111111111111111") + && test_literal(0xFFFFFFFFFFFF, "0b111111111111111111111111111111111111111111111111") + && test_literal(0xFFFFFFFFFFFFF, "0b1111111111111111111111111111111111111111111111111111") + && test_literal(0xFFFFFFFFFFFFFF, "0b11111111111111111111111111111111111111111111111111111111") + && test_literal(0xFFFFFFFFFFFFFFF, "0b111111111111111111111111111111111111111111111111111111111111") + && test_literal(0xFFFFFFFFFFFFFFFF, "0b1111111111111111111111111111111111111111111111111111111111111111") + + && test_literal(0x7, "0b111") + && test_literal(0x7F, "0b1111111") + && test_literal(0x7FF, "0b11111111111") + && test_literal(0x7FFF, "0b111111111111111") + && test_literal(0x7FFFF, "0b1111111111111111111") + && test_literal(0x7FFFFF, "0b11111111111111111111111") + && test_literal(0x7FFFFFF, "0b111111111111111111111111111") + && test_literal(0x7FFFFFFF, "0b1111111111111111111111111111111") + && test_literal(0x7FFFFFFFF, "0b11111111111111111111111111111111111") + && test_literal(0x7FFFFFFFFF, "0b111111111111111111111111111111111111111") + && test_literal(0x7FFFFFFFFFF, "0b1111111111111111111111111111111111111111111") + && test_literal(0x7FFFFFFFFFFF, "0b11111111111111111111111111111111111111111111111") + && test_literal(0x7FFFFFFFFFFFF, "0b111111111111111111111111111111111111111111111111111") + && test_literal(0x7FFFFFFFFFFFFF, "0b1111111111111111111111111111111111111111111111111111111") + && test_literal(0x7FFFFFFFFFFFFFF, "0b11111111111111111111111111111111111111111111111111111111111") + && test_literal(0x7FFFFFFFFFFFFFFF, "0b111111111111111111111111111111111111111111111111111111111111111") ) { return EXIT_SUCCESS; diff --git a/unittests/json_1.chai b/unittests/json_1.chai new file mode 100644 index 00000000..63277b3b --- /dev/null +++ b/unittests/json_1.chai @@ -0,0 +1,3 @@ + + +assert_true(from_json("null").is_var_null()) diff --git a/unittests/json_10.chai b/unittests/json_10.chai new file mode 100644 index 00000000..f19c7cfb --- /dev/null +++ b/unittests/json_10.chai @@ -0,0 +1 @@ +assert_equal(from_json("\"This is a\\n\\nMultiline string\""), "This is a\n\nMultiline string") diff --git a/unittests/json_11.chai b/unittests/json_11.chai new file mode 100644 index 00000000..f11db384 --- /dev/null +++ b/unittests/json_11.chai @@ -0,0 +1,14 @@ +assert_equal(from_json( +"{\n" + +" \"T1\" : \"Value With a Quote : \\\"\",\n" + +" \"T2\" : \"Value With a Rev Solidus : \\/\",\n" + +" \"T3\" : \"Value with a Solidus : \\\\\",\n" + +" \"T4\" : \"Value with a Backspace : \\b\",\n" + +" \"T5\" : \"Value with a Formfeed : \\f\",\n" + +" \"T6\" : \"Value with a Newline : \\n\",\n" + +" \"T7\" : \"Value with a Carriage Return : \\r\",\n" + +" \"T8\" : \"Value with a Horizontal Tab : \\t\"\n" + +"}"), [ "T1" : "Value With a Quote : \"", "T2" : "Value With a Rev Solidus : /", "T3" : "Value with a Solidus : \\", "T4" : "Value with a Backspace : \b", "T5" : "Value with a Formfeed : \f", "T6" : "Value with a Newline : \n", "T7" : "Value with a Carriage Return : \r", "T8" : "Value with a Horizontal Tab : \t" ]); + + + diff --git a/unittests/json_12.chai b/unittests/json_12.chai new file mode 100644 index 00000000..a93211d3 --- /dev/null +++ b/unittests/json_12.chai @@ -0,0 +1 @@ +assert_equal(from_json("\"\""), "") diff --git a/unittests/json_13.chai b/unittests/json_13.chai new file mode 100644 index 00000000..3f9fa009 --- /dev/null +++ b/unittests/json_13.chai @@ -0,0 +1 @@ +assert_equal(from_json("1.20E+2"), 1.20e2) diff --git a/unittests/json_2.chai b/unittests/json_2.chai new file mode 100644 index 00000000..0f779bdd --- /dev/null +++ b/unittests/json_2.chai @@ -0,0 +1,3 @@ + +assert_true(from_json("true")) + diff --git a/unittests/json_3.chai b/unittests/json_3.chai new file mode 100644 index 00000000..d3f222e4 --- /dev/null +++ b/unittests/json_3.chai @@ -0,0 +1 @@ +assert_equal(from_json("100"), 100) diff --git a/unittests/json_4.chai b/unittests/json_4.chai new file mode 100644 index 00000000..22d388c7 --- /dev/null +++ b/unittests/json_4.chai @@ -0,0 +1 @@ +assert_equal(from_json("1.234"), 1.234) diff --git a/unittests/json_5.chai b/unittests/json_5.chai new file mode 100644 index 00000000..9ad2ca21 --- /dev/null +++ b/unittests/json_5.chai @@ -0,0 +1 @@ +assert_equal(from_json("\"StringTest\""), "StringTest") diff --git a/unittests/json_6.chai b/unittests/json_6.chai new file mode 100644 index 00000000..8c33bff3 --- /dev/null +++ b/unittests/json_6.chai @@ -0,0 +1 @@ +assert_equal(from_json("{}"), Map()) diff --git a/unittests/json_7.chai b/unittests/json_7.chai new file mode 100644 index 00000000..b2c6efa8 --- /dev/null +++ b/unittests/json_7.chai @@ -0,0 +1,4 @@ +assert_equal(from_json("\n" + +"{\n" + +" \"Key\" : \"Value\"\n" + +"}\n"), ["Key":"Value"]) diff --git a/unittests/json_8.chai b/unittests/json_8.chai new file mode 100644 index 00000000..104c8bc4 --- /dev/null +++ b/unittests/json_8.chai @@ -0,0 +1 @@ +assert_equal(from_json("[]"), []) diff --git a/unittests/json_9.chai b/unittests/json_9.chai new file mode 100644 index 00000000..15127387 --- /dev/null +++ b/unittests/json_9.chai @@ -0,0 +1,2 @@ +assert_equal(from_json("[1,2,3]"), [1,2,3]) + diff --git a/unittests/json_roundtrip.chai b/unittests/json_roundtrip.chai new file mode 100644 index 00000000..94682e5f --- /dev/null +++ b/unittests/json_roundtrip.chai @@ -0,0 +1,6 @@ + +var m = ["a" : 1, "b" : [ 1, 2, 3 ], "c" : [1, "a string", ["d" : 15.4]]] + +assert_equal(from_json(to_json(m)), m) + + diff --git a/unittests/list_push_back.chai b/unittests/list_push_back.chai index 19d17e13..5a058fc8 100644 --- a/unittests/list_push_back.chai +++ b/unittests/list_push_back.chai @@ -6,3 +6,9 @@ x.push_back("A") assert_equal(3, x.front()); assert_equal("A", x.back()); + + +// push_back newly constructed return value that's non-copyable +x.push_back(async(fun(){})) + + diff --git a/unittests/list_push_front.chai b/unittests/list_push_front.chai index fe318216..b07c2734 100644 --- a/unittests/list_push_front.chai +++ b/unittests/list_push_front.chai @@ -6,3 +6,8 @@ x.push_front("A") assert_equal("A", x.front()); assert_equal(3, x.back()); + +// push_back newly constructed return value that's non-copyable +x.push_front(async(fun(){})) + + diff --git a/unittests/number_suffixes.chai b/unittests/number_suffixes.chai index 99ac03fc..b8fd1f58 100644 --- a/unittests/number_suffixes.chai +++ b/unittests/number_suffixes.chai @@ -2,8 +2,8 @@ assert_equal(true, int_type.bare_equal(1.get_type_info())) assert_equal(true, unsigned_int_type.bare_equal(1u.get_type_info())) assert_equal(true, unsigned_long_type.bare_equal(1lu.get_type_info())) assert_equal(true, long_type.bare_equal(1l.get_type_info())) -assert_equal(true, int64_t_type.bare_equal(1ll.get_type_info())) -assert_equal(true, uint64_t_type.bare_equal(1ull.get_type_info())) +assert_equal(true, long_long_type.bare_equal(1ll.get_type_info())) +assert_equal(true, unsigned_long_long_type.bare_equal(1ull.get_type_info())) assert_equal(true, double_type.bare_equal(1.6.get_type_info())) assert_equal(true, float_type.bare_equal(1.6f.get_type_info())) diff --git a/unittests/octal_escapes.chai b/unittests/octal_escapes.chai new file mode 100644 index 00000000..a220d578 --- /dev/null +++ b/unittests/octal_escapes.chai @@ -0,0 +1,7 @@ + +assert_equal("\71", "9") +assert_equal("\071", "9") +assert_equal("\71a", "9a") +assert_equal("b\71a", "b9a") +assert_equal("\71\70a", "98a") + diff --git a/unittests/operator_overload3.chai b/unittests/operator_overload3.chai new file mode 100644 index 00000000..90b81cdb --- /dev/null +++ b/unittests/operator_overload3.chai @@ -0,0 +1,7 @@ + + +def string::`/=`(double d) { this = "${this}/=${d}"; return this; } + +var s = "Hello World" +s /= 2 + diff --git a/unittests/operator_overload4.chai b/unittests/operator_overload4.chai new file mode 100644 index 00000000..832d984d --- /dev/null +++ b/unittests/operator_overload4.chai @@ -0,0 +1,6 @@ + + +def string::`*`(double d) { return "${this} * ${d}"; } + +"Hello World" * 2 + diff --git a/unittests/operator_parsing.chai b/unittests/operator_parsing.chai new file mode 100644 index 00000000..59d23149 --- /dev/null +++ b/unittests/operator_parsing.chai @@ -0,0 +1,4 @@ +assert_true(2>-1); +assert_false(3<-2); +assert_true(-1==-1); + diff --git a/unittests/order_of_operations.chai b/unittests/order_of_operations.chai new file mode 100644 index 00000000..c07ce798 --- /dev/null +++ b/unittests/order_of_operations.chai @@ -0,0 +1,7 @@ + + +var s = "" + +s += to_string(fun[s](){ s += "3"; 3}() % fun[s](){ s += "2"; 2}()); + +assert_equal(s, "321"); diff --git a/unittests/parser_test.chai b/unittests/parser_test.chai new file mode 100644 index 00000000..05c2014e --- /dev/null +++ b/unittests/parser_test.chai @@ -0,0 +1,10 @@ +auto p = parse("5 + 4"); + +try { + assert_equal(eval(p), 9) +} catch (e) { + print(e.pretty_print()); + assert_true(false); +} + + diff --git a/unittests/switch_default_2.chai b/unittests/switch_default_2.chai new file mode 100644 index 00000000..5082ab61 --- /dev/null +++ b/unittests/switch_default_2.chai @@ -0,0 +1,18 @@ +var total = 0; + +switch(2) { + case (1) { + total += 1; + } + default { + total += 16; + } + case (3) { + total += 4; + } + case (4) { + total += 8; + } +} + +assert_equal(total, 28) diff --git a/unittests/vector_assignment.chai b/unittests/vector_assignment.chai new file mode 100644 index 00000000..8646105b --- /dev/null +++ b/unittests/vector_assignment.chai @@ -0,0 +1,18 @@ +var v = [] +v.push_back(3.4); +v.push_back(1); +v.push_back("bob"); + +assert_true(v[0] == 3.4) +assert_true(v[1] == 1) +assert_true(v[2] == "bob") + +v[0] = 2.9 +v[1] = 3 +v[2] = "tom" + +assert_true(v[0] == 2.9) +assert_true(v[1] == 3) +assert_true(v[2] == "tom") + + diff --git a/unittests/vector_assignment_3.chai b/unittests/vector_assignment_3.chai new file mode 100644 index 00000000..61d4b580 --- /dev/null +++ b/unittests/vector_assignment_3.chai @@ -0,0 +1,5 @@ + +var v = [] +v.push_back(int(1)); +v[0] = 3; + diff --git a/unittests/vector_push_back.chai b/unittests/vector_push_back.chai index eea34b78..176e1cec 100644 --- a/unittests/vector_push_back.chai +++ b/unittests/vector_push_back.chai @@ -11,3 +11,10 @@ auto uint16v = u16vector(); uint16v.push_back(1u); assert_equal(1, uint16v.front()); + +// push_back newly constructed return value that's non-copyable + +var v = [] +v.push_back(async(fun(){})) + +