diff --git a/CMakeLists.txt b/CMakeLists.txt index 47812713..c820b5e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,11 +5,12 @@ enable_testing() # Project setup project(inkcpp VERSION 0.1) - +SET(CMAKE_CXX_STANDARD 17) +SET(CMAKE_CXX_STANDARD_REQUIRED ON) # Add subdirectories add_subdirectory(shared) add_subdirectory(inkcpp) add_subdirectory(inkcpp_compiler) add_subdirectory(inkcpp_cl) add_subdirectory(inkcpp_test) -add_subdirectory(unreal) \ No newline at end of file +add_subdirectory(unreal) diff --git a/inkcpp/CMakeLists.txt b/inkcpp/CMakeLists.txt index 305db42a..41f7207e 100644 --- a/inkcpp/CMakeLists.txt +++ b/inkcpp/CMakeLists.txt @@ -17,6 +17,7 @@ list(APPEND SOURCES system.cpp value.h value.cpp string_table.h string_table.cpp avl_array.h + string_operations.cpp header.cpp ) source_group(Collections REGULAR_EXPRESSION collections/.*) diff --git a/inkcpp/casting.h b/inkcpp/casting.h new file mode 100644 index 00000000..fb940446 --- /dev/null +++ b/inkcpp/casting.h @@ -0,0 +1,87 @@ +#pragma once + +/// Managing casting between value types. +/// The casting is defined by an NxN matrix where N = |value_types|. +/// The entry m,n is the type where we cast to when rh = value_type(m) and +/// lh = value_type(n). +/// for that the matrix is symmetric. +/// `value_type::none` is used to mark an invalid cast +/// +/// The entries are set in the `set_cast` function, which iterates over all +/// value_types combination. For each combination it checks the value of +/// `cast` (with v1 < v2). When not other defined it is none. + +#include "value.h" + +namespace ink::runtime::internal::casting { + + /** + * @brief casting_matrix data and access wrapper. + */ + struct casting_matrix_type { + public: + constexpr casting_matrix_type() : _data{value_type::none}{}; + constexpr value_type get(value_type t1, value_type t2) const { + return _data[static_cast(t1)*N+static_cast(t2)]; + } + static constexpr size_t N = static_cast(value_type::OP_END); + value_type _data[N*N]; + }; + + // iterate through each value_type combination and populate the + // casting_matrix + template + constexpr void set_cast (value_type data[casting_matrix_type::N*casting_matrix_type::N]){ + + if constexpr (t2 == value_type::OP_END) { + // end reached + } else if constexpr (t1 == value_type::OP_END) { + // go to next row + set_cast(data); + } else { + // get entry from cast + constexpr size_t n1 = static_cast(t1); + constexpr size_t n2 = static_cast(t2); + // set matrix entry + if constexpr (n1 < n2) { + data[n1*casting_matrix_type::N + n2] = cast::value; + } else { + data[n1*casting_matrix_type::N + n2] = cast::value; + } + set_cast(data); + } + } + + // function to populate casting_matrix + constexpr casting_matrix_type construct_casting_matrix() { + casting_matrix_type cm; + set_cast(cm._data); + return cm; + } + + /// NxN matrix which contains in cell i,j the common base of value_type(i) + /// and value_type(j). + static constexpr casting_matrix_type casting_matrix = construct_casting_matrix(); + + /** + * @brief returns a type where each value can be casted to. + * Result based on `cast = value_type` + * definitions. + * @tparam N number of values/value array length + * @param vs array which contains the values + * @return value_type::none if there is no common base defined + * @return common base of types in vs else + */ + template + value_type common_base(const value* vs) { + if constexpr (N == 0) { return value_type::none; } + else if constexpr (N == 1) { return vs->type(); } + else { + value_type ty = vs[0].type(); + for(size_t i = 1; i < N; ++i) { + ty = casting_matrix.get(ty, vs[i].type()); + } + return ty; + } + } +} diff --git a/inkcpp/choice.cpp b/inkcpp/choice.cpp index 028e8656..621291ef 100644 --- a/inkcpp/choice.cpp +++ b/inkcpp/choice.cpp @@ -11,12 +11,11 @@ namespace ink if (in.queued() == 2) { // If it's a string, just grab it. Otherwise, use allocation - const internal::data& data = in.peek(); - switch (data.type) + const internal::value& data = in.peek(); + switch (data.type()) { - case internal::data_type::string_table_pointer: - case internal::data_type::allocated_string_pointer: - _text = data.string_val; + case internal::value_type::string: + _text = data.get(); in.discard(2); break; default: @@ -35,4 +34,4 @@ namespace ink _thread = thread; } } -} \ No newline at end of file +} diff --git a/inkcpp/executioner.h b/inkcpp/executioner.h new file mode 100644 index 00000000..7d3a7fbb --- /dev/null +++ b/inkcpp/executioner.h @@ -0,0 +1,168 @@ +#pragma once + +/// Defines the executioner class which initialize the different operations +/// and managed the access to them. +/// +/// The executer creates a array of pointer to the arguments passed, and pass +/// them to each operator, so that each operator can grep the needed arguments. +/// Therefore it is required that each argument has a unique type, so that the +/// order won't matter. +/// +/// When call an operation the executioner iterates through all commands and +/// after find an command match. +/// Then pop arguments from the stack as defined in `command_num_args`. +/// After this iterate through the implementations of that command for different +/// type until it found the correct type, than execute the operation. +/// The search is O(n), but the list is only populated with commands which have +/// at least one implementation, also per command only types listed for which +/// the command is implemented. +/// +/// Improvements: The executioner -> typed_executer could be O(1) when using a +/// look up table. + +#include "system.h" +#include "value.h" +#include "stack.h" +#include "operations.h" + + + +namespace ink::runtime::internal { + + /** + * @brief iterates through value_types until it found a matching operator. + * Matching means a operator which implements the command for the type. + * @tparam cmd Command to search operation for. + * @tparam t value type to start search + * @tparam Offset t + Offset is real start, used because trouble with mscv + * @return value_type::OP_END, if no "next operation" found + * @return type which is greater t + Offset and implement the command + */ + template + constexpr value_type next_operatable_type() { + constexpr value_type ty = t + Offset; + if constexpr (operation::enabled) { + return ty; + } else if constexpr (ty >= value_type::OP_END){ + return value_type::OP_END; + } else { + return next_operatable_type(); + } + } + + /** + * @brief Iterates through all existing operations for this Command. + */ + template()> + class typed_executer { + public: + static constexpr bool enabled = true; + template + typed_executer(const T& t) : _typed_exe{t}, _op{t} {} + + void operator()(value_type t, eval_stack& s, value* v) { + if (t == ty) { _op(s, v); } + else { _typed_exe(t, s, v); } + } + private: + // skip command for not implemented types + typed_executer(ty),1>()> _typed_exe; + operation _op; + }; + + // end of recursion (has no operation attached to it) + template + class typed_executer { + public: + static constexpr bool enabled = false; + template + typed_executer(const T& t) {} + + void operator()(value_type, eval_stack&, value*) { + throw ink_exception("Operation for value not supported!"); + } + }; + + /** + * @brief Find next command which is at least for one type implemented. + * @tparam c command to start search + * @tparam Offset offset to start search, used because of trouble with mscv + * @return Command::OP_END if no next operation is found + * @return next command witch at least of implementation. + */ + template + constexpr Command next_operatable_command() { + constexpr Command cmd = c + Offset; + if constexpr (typed_executer::enabled) { + return cmd; + } else if constexpr (cmd >= Command::OP_END){ + return Command::OP_END; + } else { + return next_operatable_command(); + } + } + + /** + * @brief Iterate through all commands to find correct command. + * Also instantiates all typed_executer and with them the operations. + */ + template()> + class executer_imp { + public: + template + executer_imp(const T& t) : _exe{t}, _typed_exe{t}{} + + void operator()(Command c, eval_stack& s) { + if (c == cmd) { + static constexpr size_t N = command_num_args(cmd); + value args[N]; + for (int i = command_num_args(cmd)-1; i >= 0 ; --i) { + args[i] = s.pop(); + } + value_type ty = casting::common_base(args); + _typed_exe(ty, s, args); + } else { _exe(c, s); } + } + private: + executer_imp()> _exe; + typed_executer _typed_exe; + }; + + /// end of recursion + template<> + class executer_imp { + public: + template + executer_imp(const T& t) {} + void operator()(Command, eval_stack&) { + throw ink_exception("requested command was not found!"); + } + }; + + /** + * @brief Class which instantiates all operations and give access to them. + */ + class executer { + public: + /** + * @brief pass all arguments to operations who need them. + * @attention each type need to be unique for the look up later! + * @tparam Args argument types + * @param args arguments + */ + template + executer(Args& ... args) : _executer{tuple(&args...)} {} + + /** + * @brief execute command on stack. + * @param cmd command to execute + * @param stack stack to operate on + */ + void operator()(Command cmd, eval_stack& stack) { + _executer(cmd, stack); + } + private: + executer_imp _executer; + }; +} + diff --git a/inkcpp/functional.cpp b/inkcpp/functional.cpp index 573bfa20..0fe4cc69 100644 --- a/inkcpp/functional.cpp +++ b/inkcpp/functional.cpp @@ -10,21 +10,23 @@ namespace ink::runtime::internal { - template - T function_base::pop(basic_eval_stack* stack) + template<> + int32_t function_base::pop(basic_eval_stack* stack) { - return stack->pop().get(); + value val = stack->pop(); + inkAssert(val.type() == value_type::int32, "Type missmatch!"); + return val.get(); } - template - void function_base::push(basic_eval_stack* stack, const T& value) + template<> + void function_base::push(basic_eval_stack* stack, const int32_t& v) { - stack->push(value); + stack->push(value{}.set(v)); } void function_base::push_string(basic_eval_stack* stack, const char* dynamic_string) { - stack->push(value(dynamic_string, true)); + stack->push(value{}.set(dynamic_string, true)); } char* function_base::allocate(string_table& strings, size_t len) @@ -34,17 +36,11 @@ namespace ink::runtime::internal // Generate template implementations for all significant types -#define SUPPORT_TYPE(TYPE) template TYPE function_base::pop(basic_eval_stack*); template void function_base::push(basic_eval_stack*, const TYPE&) -#define SUPPORT_TYPE_PARAMETER_ONLY(TYPE) template TYPE function_base::pop(basic_eval_stack*) - - SUPPORT_TYPE(int); - SUPPORT_TYPE(float); - SUPPORT_TYPE(uint32_t); - - // TODO - Support string return values - #ifdef INK_ENABLE_STL - SUPPORT_TYPE_PARAMETER_ONLY(std::string); + template<> + std::string function_base::pop(basic_eval_stack* stack) { + return std::string(pop(stack)); + } #endif #ifdef INK_ENABLE_UNREAL SUPPORT_TYPE_PARAMETER_ONLY(FString); diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 8637eb8a..94369cb7 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -72,53 +72,44 @@ namespace ink::runtime::internal return _variables.get(name); } - template - auto fetch_variable( const value* v, TYPES ... types) { - return v && ((v->get_data_type() == types) || ...) - ? (v->*FN)() - : nullptr; + template + optional fetch_variable(const value* val) { + if (val && val->type() == ty) { + return optional(val->get()); + } + return {nullopt}; } - template - auto fetch_variable(value* v, TYPES ... types) { - return v && ((v->get_data_type() == types) || ...) - ? (v->*FN)() - : nullptr; + + template + bool try_set_value(value* val, T x) { + if (val && val->type() == ty) { + val->set(x); + return true; + } + return false; } - const uint32_t* globals_impl::get_uint(hash_t name) const { - return fetch_variable(get_variable(name), data_type::uint32); + optional globals_impl::get_int(hash_t name) const { + return fetch_variable(get_variable(name)); } - bool globals_impl::set_uint(hash_t name, uint32_t val) { - uint32_t* p = fetch_variable(get_variable(name), data_type::uint32); - if (p == nullptr) { return false; } - *p = val; - return true; + bool globals_impl::set_int(hash_t name, int32_t i) { + return try_set_value(get_variable(name), i); } - - const int32_t* globals_impl::get_int(hash_t name) const { - return fetch_variable(get_variable(name), data_type::int32); + optional globals_impl::get_uint(hash_t name) const { + return fetch_variable(get_variable(name)); } - bool globals_impl::set_int(hash_t name, int32_t val) { - int32_t* p = fetch_variable(get_variable(name), data_type::int32); - if (p == nullptr) { return false; } - *p = val; - return true; + bool globals_impl::set_uint(hash_t name, uint32_t i) { + return try_set_value(get_variable(name), i); } - - const float* globals_impl::get_float(hash_t name) const { - return fetch_variable(get_variable(name), data_type::float32); + optional globals_impl::get_float(hash_t name) const { + return fetch_variable(get_variable(name)); } - bool globals_impl::set_float(hash_t name, float val) { - float* p = fetch_variable(get_variable(name), data_type::float32); - if (p == nullptr) { return false; } - *p = val; - return true; + bool globals_impl::set_float(hash_t name, float i) { + return try_set_value(get_variable(name), i); } - const char * const * globals_impl::get_str(hash_t name) const { - const value* v = get_variable(name); - if (v->type() != value_type::string) { return nullptr; } - return v->as_str_ptr(_strings); + optional globals_impl::get_str(hash_t name) const { + return fetch_variable(get_variable(name)); } bool globals_impl::set_str(hash_t name, const char* val) { value* v = get_variable(name); @@ -134,9 +125,7 @@ namespace ink::runtime::internal *ptr++ = *i; } *ptr = 0; - internal::data d; - d.set_string(new_string, true); - *v = internal::value(d); + *v = value{}.set(static_cast(new_string), true); return true; } return false; diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index bf6ce785..292bd920 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -20,16 +20,16 @@ namespace ink::runtime::internal virtual ~globals_impl() { } protected: - const uint32_t* get_uint(hash_t name) const override; + optional get_uint(hash_t name) const override; bool set_uint(hash_t name, uint32_t value) override; - const int32_t* get_int(hash_t name) const override; + optional get_int(hash_t name) const override; bool set_int(hash_t name, int32_t value) override; - const float* get_float(hash_t name) const override; + optional get_float(hash_t name) const override; bool set_float(hash_t name, float value) override; - const char * const * get_str(hash_t name) const override; + optional get_str(hash_t name) const override; bool set_str(hash_t name, const char* value) override; public: diff --git a/inkcpp/include/globals.h b/inkcpp/include/globals.h index 231fc368..ae6b1a97 100644 --- a/inkcpp/include/globals.h +++ b/inkcpp/include/globals.h @@ -45,21 +45,19 @@ namespace ink::runtime virtual ~globals_interface() = default; protected: - virtual const uint32_t* get_uint(hash_t name) const = 0; + virtual optional get_uint(hash_t name) const = 0; virtual bool set_uint(hash_t name, uint32_t val) = 0; - virtual const int32_t* get_int(hash_t name) const = 0; + virtual optional get_int(hash_t name) const = 0; virtual bool set_int(hash_t name, int32_t val) = 0; - virtual const float* get_float(hash_t name) const = 0; + virtual optional get_float(hash_t name) const = 0; virtual bool set_float(hash_t name, float val) = 0; - virtual const char* const * get_str(hash_t name) const = 0; + virtual optional get_str(hash_t name) const = 0; virtual bool set_str(hash_t name, const char* val) = 0; }; template<> inline optional globals_interface::get(const char* name) const { - const uint32_t* p = get_uint(hash_string(name)); - if (p) { return {*p}; } - return {nullopt}; + return get_uint(hash_string(name)); } template<> inline bool globals_interface::set(const char* name, const uint32_t& val) { @@ -68,9 +66,7 @@ namespace ink::runtime template<> inline optional globals_interface::get(const char* name) const { - const int32_t* p = get_int(hash_string(name)); - if (p) { return {*p}; } - return {nullopt}; + return get_int(hash_string(name)); } template<> inline bool globals_interface::set(const char* name, const int32_t& val) { @@ -79,9 +75,7 @@ namespace ink::runtime template<> inline optional globals_interface::get(const char* name) const { - const float* p = get_float(hash_string(name)); - if (p) { return {*p}; } - return {nullopt}; + return get_float(hash_string(name)); } template<> inline bool globals_interface::set(const char* name, const float& val) { @@ -90,9 +84,7 @@ namespace ink::runtime template<> inline optionalglobals_interface::get(const char* name) const { - const char * const * p = get_str(hash_string(name)); - if (p) { return {*p}; } - return {nullopt}; + return get_str(hash_string(name)); } template<> inline bool globals_interface::set(const char* name, const char * const & val) { diff --git a/inkcpp/include/traits.h b/inkcpp/include/traits.h index 5e3104fa..86ade898 100644 --- a/inkcpp/include/traits.h +++ b/inkcpp/include/traits.h @@ -1,6 +1,7 @@ #pragma once #include "config.h" +#include "system.h" #ifdef INK_ENABLE_STL #include @@ -9,19 +10,15 @@ namespace ink::runtime::internal { template - struct get - { - using type = typename get::type; - }; + struct get_ith_type : get_ith_type {}; template - struct get<0, Arg, Args...> + struct get_ith_type<0, Arg, Args...> { using type = Arg; }; // constant and is_same from http://www.cppreference.com - template struct constant { static constexpr T value = v; @@ -31,16 +28,19 @@ namespace ink::runtime::internal constexpr value_type operator()() const noexcept { return value; } //since c++14 }; + struct false_type : constant {}; + struct true_type : constant{}; + template - struct is_same : constant {}; + struct is_same : false_type {}; template - struct is_same : constant {}; + struct is_same : true_type {}; // == string testing (from me) == template - struct is_string : constant { }; + struct is_string : false_type { }; template struct is_string : is_string { }; @@ -107,7 +107,7 @@ namespace ink::runtime::internal struct argument { static_assert(N < arity, "error: invalid parameter index."); - using type = typename get::type; + using type = typename get_ith_type::type; }; }; diff --git a/inkcpp/numeric_operations.h b/inkcpp/numeric_operations.h new file mode 100644 index 00000000..7654d617 --- /dev/null +++ b/inkcpp/numeric_operations.h @@ -0,0 +1,264 @@ +#pragma once + +/// Define operation for numeric types. +/// use generalized types numeric and integral to keep redundancy minimal. +/// define a cast to support operations like int + float, bool + uint etc. + +namespace ink::runtime::internal { + + /// list of numeric value types + /// produces a SFINAE error if type is not part of list + template + using is_numeric_t = typename enable_if< + ty == value_type::int32 + || ty == value_type::uint32 + || ty == value_type::float32, void>::type; + + /// list of internal value types + /// produces a SFINAE error if type is not part of list + template + using is_integral_t = typename enable_if< + ty == value_type::int32 + || ty == value_type::uint32, void>::type; + + namespace casting { + /// define valid casts + + /// result of operation with int and float is float. + template<> + struct cast + { static constexpr value_type value = value_type::float32; }; + + /// result of operation with uint and bool is uint + template<> + struct cast + { static constexpr value_type value = value_type::uint32; }; + + /// defined numeric cast + /// generic numeric_cast only allow casting to its one type + template + inline typename value::ret::type numeric_cast(const value& v) { + if (to == v.type()) { return v.get(); } + else { + throw ink_exception("invalid numeric_cast!"); + } + } + + /// specialisation for uint32 + template<> + inline typename value::ret::type numeric_cast(const value& v) { + switch(v.type()) { + case value_type::uint32: + return v.get(); + /// bool value can cast to uint32 + case value_type::boolean: + return static_cast(v.get()); + default: + throw ink_exception("invalid cast to uint!"); + } + } + + /// specialisation for float32 + template<> + inline float numeric_cast(const value& v) { + switch(v.type()) { + case value_type::float32: + return v.get(); + // int value can cast to float + case value_type::int32: + return static_cast(v.get()); + default: + throw ink_exception("invalid numeric_cast!"); + } + } + } + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) + + casting::numeric_cast(vals[1]) )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) - + casting::numeric_cast(vals[1]) )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) / + casting::numeric_cast(vals[1]) )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) * + casting::numeric_cast(vals[1]) )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( vals[0].get() % vals[1].get() )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) == + casting::numeric_cast(vals[1]) + )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) > + casting::numeric_cast(vals[1]) + )); + } + }; + + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) < + casting::numeric_cast(vals[1]) + )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) >= + casting::numeric_cast(vals[1]) + )); + } + }; + + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) <= + casting::numeric_cast(vals[1]) + )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( + casting::numeric_cast(vals[0]) != + casting::numeric_cast(vals[1]) + )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( vals[0].get() && vals[1].get() )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set( vals[0].get() || vals[1].get() )); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + typename value::ret::type n[2] = { + casting::numeric_cast(vals[0]), + casting::numeric_cast(vals[1]) + }; + stack.push(value{}.set(n[0] < n[1] ? n[0] : n[1])); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + typename value::ret::type n[2] = { + casting::numeric_cast(vals[0]), + casting::numeric_cast(vals[1]) + }; + stack.push(value{}.set(n[0] > n[1] ? n[0] : n[1])); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set(!vals[0].get())); + } + }; + + template + class operation> : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals) { + stack.push(value{}.set(-vals[0].get())); + } + }; +} diff --git a/inkcpp/operation_bases.h b/inkcpp/operation_bases.h new file mode 100644 index 00000000..64accc7a --- /dev/null +++ b/inkcpp/operation_bases.h @@ -0,0 +1,46 @@ +#pragma once + +/// defines data storage for operations. +/// provide constructor and handle the data member. +/// the data member therefore are protected accessible + +#include "system.h" +#include "./tuple.hpp" + +namespace ink::runtime::internal { + class string_table; + + /// base class for operations to acquire data and provide flags and + /// constructor + template + class operation_base { + public: + static constexpr bool enabled = false; + template + operation_base(const T&) { static_assert(always_false::value, "use undefined base!"); } + }; + + template<> + class operation_base { + public: + static constexpr bool enabled = true; + template + operation_base(const T&) {} + }; + + // base class for operations which needs a string_table + template<> + class operation_base { + public: + static constexpr bool enabled = true; + template + operation_base(const T& t) : _string_table{*get(t)} { + static_assert(has_type::value, "Executioner " + "constructor needs a string table to instantiate " + "some operations!"); + } + + protected: + string_table& _string_table; + }; +} diff --git a/inkcpp/operations.h b/inkcpp/operations.h new file mode 100644 index 00000000..66de710f --- /dev/null +++ b/inkcpp/operations.h @@ -0,0 +1,65 @@ +#pragma once + +/// Define base constructs to specify by operation headers. + +#include "../shared/private/command.h" + +namespace ink::runtime::internal { + + namespace casting { + // default cast to none (invalid cast) + template + struct cast { + static constexpr value_type value = value_type::none; + }; + + // no cast for same type + template + struct cast { + static constexpr value_type value = t; + }; + } + + /** + * @brief Determines the number of arguments needed for an command. + */ + constexpr size_t command_num_args(Command cmd) { + if (cmd >= Command::BINARY_OPERATORS_START && cmd <= Command::BINARY_OPERATORS_END) { + return 2; + } else if (cmd >= Command::UNARY_OPERATORS_START && cmd <= Command::UNARY_OPERATORS_END) { + return 1; + } else { + return 0; + } + } + + /** + * @brief Operation definition. + * A class which contains a call operator to execute the operation needed + * for the command type combination. + * @tparam cmd Command which should be executed + * @tparam ty type on which the command should be executed + */ + template + class operation { + public: + static constexpr bool enabled = false; + template + operation(const T& t) {} + /** + * @brief execute operation. + * @param stack were the result(s) get pushed + * @param vs array of values, first one = first argument etc + */ + void operator()(eval_stack& stack, value* vs) { + throw ink_exception("operation not implemented!"); + } + }; +} + +// include header here to ensure correct order + +#include "operation_bases.h" +#include "numeric_operations.h" +#include "string_operations.h" +#include "casting.h" diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 54742edc..117ef591 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -13,23 +13,23 @@ namespace ink { namespace internal { - basic_stream::basic_stream(data* buffer, size_t len) + basic_stream::basic_stream(value* buffer, size_t len) : _data(buffer), _max(len), _size(0), _save(~0) {} - void basic_stream::append(const data& in) + void basic_stream::append(const value& in) { // SPECIAL: Incoming newline - if (in.type == data_type::newline && _size > 1) + if (in.type() == value_type::newline && _size > 1) { // If the end of the stream is a function start marker, we actually // want to ignore this. Function start trimming. - if (_data[_size - 1].type == data_type::func_start) + if (_data[_size - 1].type() == value_type::func_start) return; } // Ignore leading newlines - if (in.type == data_type::newline && _size == 0) + if (in.type() == value_type::newline && _size == 0) return; // Add to data stream @@ -38,23 +38,23 @@ namespace ink // Special: Incoming glue. Trim whitespace/newlines prior // This also applies when a function ends to trim trailing whitespace. - if ((in.type == data_type::glue || in.type == data_type::func_end) && _size > 1) + if ((in.type() == value_type::glue || in.type() == value_type::func_end) && _size > 1) { // Run backwards size_t i = _size - 2; while(true) { - data& d = _data[i]; + value& d = _data[i]; // Nullify newlines - if (d.type == data_type::newline) - d.type = data_type::none; + if (d.type() == value_type::newline) { + d = value{}; + } // Nullify whitespace - else if ( - (d.type == data_type::string_table_pointer || d.type == data_type::allocated_string_pointer) - && is_whitespace(d.string_val)) - d.type = data_type::none; + else if ( d.type() == value_type::string + && is_whitespace(d.get())) + d = value{}; // If it's not a newline or whitespace, stop else break; @@ -69,7 +69,7 @@ namespace ink } } - void basic_stream::append(const data* in, unsigned int length) + void basic_stream::append(const value* in, unsigned int length) { // TODO: Better way to bulk while still executing glue checks? for (size_t i = 0; i < length; i++) @@ -94,23 +94,13 @@ namespace ink output.put(c); } - inline bool get_next(const data* list, size_t i, size_t size, const data** next) + inline bool get_next(const value* list, size_t i, size_t size, const value** next) { while (i + 1 < size) { *next = &list[i + 1]; - data_type type = (*next)->type; - switch (type) - { - case data_type::int32: - case data_type::float32: - case data_type::uint32: - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: - case data_type::newline: - return true; - } - + value_type type = (*next)->type(); + if ((*next)->printable()) { return true; } i++; } @@ -141,18 +131,17 @@ namespace ink } // check what the next item - const data* next = nullptr; + const value* next = nullptr; if (get_next(_data, dataIter, _size, &next)) { // If it's a newline, ignore all our whitespace - if (next->type == data_type::newline) + if (next->type() == value_type::newline) return; // If it's another string, check if it starts with whitespace - if (next->type == data_type::allocated_string_pointer || - next->type == data_type::string_table_pointer) + if (next->type() == value_type::string ) { - if (is_whitespace(next->string_val[0])) + if (is_whitespace(next->get()[0])) return; } @@ -190,23 +179,10 @@ namespace ink { if (should_skip(i, hasGlue, lastNewline)) continue; - - switch (_data[i].type) - { - case data_type::int32: - str << _data[i].integer_value; - break; - case data_type::float32: - str << std::setprecision(7) << _data[i].float_value; - break; - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: - copy_string(_data[i].string_val, i, str); - break; - case data_type::newline: - str << std::endl; - break; + if (_data[i].printable()){ + str << _data[i]; } + } // Reset stream size to where we last held the marker @@ -234,15 +210,14 @@ namespace ink switch (_data[i].type) { - case data_type::int32: + case value_type::int32: str += FString::Printf(TEXT("%d"), _data[i].integer_value); break; - case data_type::float32: + case value_type::float32: // TODO: Whitespace cleaning str += FString::Printf(TEXT("%f"), _data[i].float_value); break; - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: + case value_type::string: str += _data[i].string_val; break; case data_type::newline: @@ -265,7 +240,7 @@ namespace ink return _size - start; } - const data& basic_stream::peek() const + const value& basic_stream::peek() const { inkAssert(_size > 0, "Attempting to peek empty stream!"); return _data[_size - 1]; @@ -279,12 +254,12 @@ namespace ink _size = 0; } - void basic_stream::get(data* ptr, size_t length) + void basic_stream::get(value* ptr, size_t length) { // Find start size_t start = find_start(); - const data* end = ptr + length; + const value* end = ptr + length; //inkAssert(_size - start < length, "Insufficient space in data array to store stream contents!"); // Move up from marker @@ -298,15 +273,8 @@ namespace ink inkAssert(ptr < end, "Insufficient space in data array to store stream contents!"); // Copy any value elements - switch (_data[i].type) - { - case data_type::int32: - case data_type::float32: - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: - case data_type::newline: + if (_data[i].printable()) { *(ptr++) = _data[i]; - break; } } @@ -319,29 +287,29 @@ namespace ink // TODO: Cache? for (size_t i = 0; i < _size; i++) { - if (_data[i].type == data_type::marker) + if (_data[i].type() == value_type::marker) return true; } return false; } - bool basic_stream::ends_with(data_type type) const + bool basic_stream::ends_with(value_type type) const { if (_size == 0) return false; - return _data[_size - 1].type == type; + return _data[_size - 1].type() == type; } - bool basic_stream::saved_ends_with(data_type type) const + bool basic_stream::saved_ends_with(value_type type) const { inkAssert(_save != ~0, "Stream is not saved!"); if (_save == 0) return false; - return _data[_save - 1].type == type; + return _data[_save - 1].type() == type; } void basic_stream::save() @@ -383,21 +351,8 @@ namespace ink if (should_skip(i, hasGlue, lastNewline)) continue; - switch (_data[i].type) - { - case data_type::int32: - length += decimal_digits(_data[i].integer_value); - break; - case data_type::float32: - length += decimal_digits(_data[i].float_value); - break; - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: - length += strlen(_data[i].string_val); - break; - case data_type::newline: - length += 1; - break; + if (_data[i].printable()) { + length += value_length(_data[i]); } } @@ -410,29 +365,24 @@ namespace ink { if (should_skip(i, hasGlue, lastNewline)) continue; - - switch (_data[i].type) + switch (_data[i].type()) { - case data_type::int32: + case value_type::int32: + case value_type::float32: + case value_type::uint32: // Convert to string and advance - toStr(ptr, end - ptr, _data[i].integer_value); + toStr(ptr, end - ptr, _data[i]); while (*ptr != 0) ptr++; break; - case data_type::float32: - // Convert to string and advance - toStr(ptr, end - ptr, _data[i].float_value); - while (*ptr != 0) ptr++; - - break; - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: + case value_type::string: // Copy string and advance - copy_string(_data[i].string_val, i, ptr); + copy_string(_data[i].get(), i, ptr); break; - case data_type::newline: + case value_type::newline: *ptr = '\n'; ptr++; break; + default: throw ink_exception("cant convert expression to string!"); } } @@ -454,7 +404,7 @@ namespace ink while (start > 0) { start--; - if (_data[start].type == data_type::marker) + if (_data[start].type() == value_type::marker) break; } @@ -467,25 +417,25 @@ namespace ink bool basic_stream::should_skip(size_t iter, bool& hasGlue, bool& lastNewline) const { - switch (_data[iter].type) + switch (_data[iter].type()) { - case data_type::int32: - case data_type::float32: - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: + case value_type::int32: + case value_type::float32: + case value_type::string: hasGlue = false; lastNewline = false; break; - case data_type::newline: + case value_type::newline: if (lastNewline) return true; if (hasGlue) return true; lastNewline = true; break; - case data_type::glue: + case value_type::glue: hasGlue = true; break; + default: break; } return false; @@ -496,11 +446,11 @@ namespace ink // Check if there is text past the save for (size_t i = _save; i < _size; i++) { - const data& d = _data[i]; - if (d.type == data_type::allocated_string_pointer || d.type == data_type::string_table_pointer) + const value& d = _data[i]; + if (d.type() == value_type::string) { // TODO: Cache what counts as whitespace? - if (!is_whitespace(d.string_val, false)) + if (!is_whitespace(d.get(), false)) return true; } } @@ -520,8 +470,12 @@ namespace ink // Find all allocated strings and mark them as used for (int i = 0; i < _size; i++) { - if (_data[i].type == internal::data_type::allocated_string_pointer) - strings.mark_used(_data[i].string_val); + if (_data[i].type() == value_type::string) { + string_type str = _data[i].get(); + if (str.allocated) { + strings.mark_used(str.str); + } + } } } @@ -546,11 +500,6 @@ namespace ink } #endif - basic_stream& operator<<(basic_stream& out, const data& in) - { - out.append(in); - return out; - } } } diff --git a/inkcpp/output.h b/inkcpp/output.h index a2e27dae..d9ecc6e0 100644 --- a/inkcpp/output.h +++ b/inkcpp/output.h @@ -9,20 +9,21 @@ namespace ink { namespace internal { + class string_table; class basic_stream { protected: - basic_stream(data*, size_t); + basic_stream(value*, size_t); public: // Append data to stream - void append(const data&); + void append(const value&); // Append data array to stream - void append(const data*, unsigned int length); + void append(const value*, unsigned int length); // Append fixed sized data array to stream template - void append(const data in[N]) + void append(const value in[N]) { append(&in[0], N); } @@ -31,13 +32,13 @@ namespace ink int queued() const; // Peeks the top entry - const data& peek() const; + const value& peek() const; // discards data void discard(size_t length); // Extract into a data array - void get(data*, size_t length); + void get(value*, size_t length); // Extract to a newly allocated string const char* get_alloc(string_table&); @@ -58,10 +59,10 @@ namespace ink bool has_marker() const; // Checks if the stream ends with a specific type - bool ends_with(data_type) const; + bool ends_with(value_type) const; // Checks if the last element when save()'d was this type - bool saved_ends_with(data_type) const; + bool saved_ends_with(value_type) const; // Checks if there are any elements past the save that // are non-whitespace strings @@ -87,7 +88,7 @@ namespace ink private: // data stream - data* _data; + value* _data; size_t _max; // size @@ -105,14 +106,6 @@ namespace ink basic_stream& operator >>(basic_stream&, FString&); #endif - basic_stream& operator<<(basic_stream&, const data&); - - const data marker = { data_type::marker, 0 }; - const data newline = { data_type::newline, 0 }; - const data glue = { data_type::glue, 0 }; - const data func_start = { data_type::func_start, 0 }; - const data func_end = { data_type::func_end, 0 }; - template class stream : public basic_stream { @@ -120,7 +113,7 @@ namespace ink stream() : basic_stream(&_buffer[0], N) { } private: - data _buffer[N]; + value _buffer[N]; }; } } diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 0dc95e7d..a4865221 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -138,86 +138,6 @@ namespace ink::runtime::internal _ptr = dest; } - void runner_impl::run_binary_operator(unsigned char cmd) - { - // Pop - value rhs = _eval.pop(), lhs = _eval.pop(); - value result; - - switch ((Command)cmd) - { - case Command::ADD: - result = value::add(lhs, rhs, _output, _globals->strings()); - break; - case Command::SUBTRACT: - result = lhs - rhs; - break; - case Command::DIVIDE: - result = lhs / rhs; - break; - case Command::MULTIPLY: - result = lhs * rhs; - break; - case Command::MOD: - result = lhs % rhs; - break; - case Command::IS_EQUAL: - result = lhs == rhs; - break; - case Command::GREATER_THAN: - result = lhs > rhs; - break; - case Command::LESS_THAN: - result = lhs < rhs; - break; - case Command::GREATER_THAN_EQUALS: - result = lhs >= rhs; - break; - case Command::LESS_THAN_EQUALS: - result = lhs <= rhs; - break; - case Command::NOT_EQUAL: - result = lhs != rhs; - break; - case Command::AND: - result = lhs && rhs; - break; - case Command::OR: - result = lhs || rhs; - break; - case Command::MIN: - result = lhs < rhs ? lhs : rhs; - break; - case Command::MAX: - result = lhs > rhs ? lhs : rhs; - break; - } - - // Push result onto the stack - _eval.push(result); - } - - void runner_impl::run_unary_operator(unsigned char cmd) - { - // Pop - value v = _eval.pop(); - value result; - - // Run command - switch ((Command)cmd) - { - case Command::NEGATE: - result = -v; - break; - case Command::NOT: - result = !v; - break; - } - - // Push to the stack - _eval.push(result); - } - frame_type runner_impl::execute_return() { // Pop the callstack @@ -226,7 +146,7 @@ namespace ink::runtime::internal // SPECIAL: On function, do a trim if (type == frame_type::function) - _output << func_end; + _output << values::func_end; // Jump to the old offset inkAssert(_story->instructions() + offset < _story->end(), "Callstack return is outside bounds of story!"); @@ -237,7 +157,9 @@ namespace ink::runtime::internal } runner_impl::runner_impl(const story_impl* data, globals global) - : _story(data), _globals(global.cast()), _container(~0), _threads(~0), + : + _story(data), _globals(global.cast()), _container(~0), _threads(~0), + _operations(global.cast()->strings()), _threadDone(nullptr, (ip_t)~0), _backup(nullptr), _done(nullptr), _choices() { _ptr = _story->instructions(); @@ -432,7 +354,7 @@ namespace ink::runtime::internal { // Check if the old newline is still present (hasn't been glu'd) and // if there is new text (non-whitespace) in the stream since saving - bool stillHasNewline = _output.saved_ends_with(data_type::newline); + bool stillHasNewline = _output.saved_ends_with(value_type::newline); bool hasAddedNewText = _output.text_past_save(); // Newline is still there and there's no new text @@ -475,11 +397,12 @@ namespace ink::runtime::internal // Newline was removed. Proceed as if we never hit it forget(); break; + case change_type::no_change: break; } } // If we're on a newline - if (_output.ends_with(data_type::newline)) + if (_output.ends_with(value_type::newline)) { // TODO: REMOVE // return true; @@ -520,13 +443,9 @@ namespace ink::runtime::internal set_done_ptr(nullptr); } - if (cmd >= Command::BINARY_OPERATORS_START && cmd <= Command::BINARY_OPERATORS_END) - { - run_binary_operator((unsigned char)cmd); - } - else if (cmd >= Command::UNARY_OPERATORS_START && cmd <= Command::UNARY_OPERATORS_END) + if (cmd >= Command::OP_BEGIN && cmd < Command::OP_END) { - run_unary_operator((unsigned char)cmd); + _operations(cmd, _eval); } else switch (cmd) { @@ -535,16 +454,16 @@ namespace ink::runtime::internal { const char* str = read(); if (bEvaluationMode) - _eval.push(str); + _eval.push(value{}.set(str)); else - _output << str; + _output << value{}.set(str); } break; case Command::INT: { int val = read(); if (bEvaluationMode) - _eval.push(val); + _eval.push(value{}.set(val)); // TEST-CASE B006 don't print integers } break; @@ -552,7 +471,7 @@ namespace ink::runtime::internal { float val = read(); if (bEvaluationMode) - _eval.push(val); + _eval.push(value{}.set(val)); // TEST-CASE B006 don't print floats } break; @@ -563,29 +482,29 @@ namespace ink::runtime::internal // Push the divert target onto the stack uint32_t target = read(); - _eval.push(value(target)); + _eval.push(value{}.set(target)); } break; case Command::NEWLINE: { if (bEvaluationMode) - _eval.push(newline); + _eval.push(values::newline); else - _output << newline; + _output << values::newline; } break; case Command::GLUE: { if (bEvaluationMode) - _eval.push(glue); + _eval.push(values::glue); else - _output << glue; + _output << values::glue; } break; case Command::VOID: { if (bEvaluationMode) - _eval.push(0); // TODO: void type? + _eval.push(values::null); // TODO: void type? } break; @@ -596,7 +515,7 @@ namespace ink::runtime::internal uint32_t target = read(); // Check for condition - if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop()) + if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop().get()) break; // SPECIAL: Fallthrough divert. We're starting to fall out of containers @@ -616,7 +535,7 @@ namespace ink::runtime::internal if (_stack.has_frame(&type) && type == frame_type::function) // implicit return is only for functions { // push null and return - _eval.push(value()); + _eval.push(values::null); // HACK _ptr += sizeof(Command) + sizeof(CommandFlag); @@ -640,13 +559,13 @@ namespace ink::runtime::internal hash_t variable = read(); // Check for condition - if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop()) + if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop().get()) break; const value& val = *_stack.get(variable); // Move to location - jump(_story->instructions() + val.as_divert()); + jump(_story->instructions() + val.get()); inkAssert(_ptr < _story->end(), "Diverted past end of story data!"); } break; @@ -666,14 +585,20 @@ namespace ink::runtime::internal { // add a function start marker if (cmd == Command::FUNCTION) - _output << func_start; + _output << values::func_start; // Find divert address uint32_t target = read(); // Push next address onto the callstack - _stack.push_frame(_ptr - _story->instructions(), - cmd == Command::FUNCTION ? frame_type::function : frame_type::tunnel); + { + size_t address = _ptr - _story->instructions(); + if (cmd == Command::FUNCTION) { + _stack.push_frame(address); + } else { + _stack.push_frame(address); + } + } // Do the jump inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!"); @@ -692,7 +617,7 @@ namespace ink::runtime::internal // Push a thread frame so we can return easily // TODO We push ahead of a single divert. Is that correct in all cases....????? auto returnTo = _ptr + CommandSize; - _stack.push_frame(returnTo - _story->instructions(), frame_type::thread); + _stack.push_frame(returnTo - _story->instructions()); // Fork a new thread on the callstack thread_t thread = _stack.fork_thread(); @@ -803,7 +728,7 @@ namespace ink::runtime::internal { inkAssert(bEvaluationMode, "Can not enter string mode while not in evaluation mode!"); bEvaluationMode = false; - _output << marker; + _output << values::marker; } break; case Command::END_STR: { @@ -845,12 +770,12 @@ namespace ink::runtime::internal // Choice is conditional if (flag & CommandFlag::CHOICE_HAS_CONDITION) { // Only show if the top of the eval stack is 'truthy' - if (!_eval.pop()) + if (!_eval.pop().get()) break; } // Use a marker to start compiling the choice text - _output << marker; + _output << values::marker; value stack[2]; int sc = 0; @@ -918,7 +843,7 @@ namespace ink::runtime::internal { // Push the visit count for the current container to the top // is 0-indexed for some reason. idk why but this is what ink expects - _eval.push((int)_globals->visits(_container.top()) - 1); + _eval.push(value{}.set((int)_globals->visits(_container.top()) - 1)); } break; case Command::SEQUENCE: { @@ -926,19 +851,18 @@ namespace ink::runtime::internal // to make sure each element is picked at least once in every // iteration loop. I don't feel like replicating that right now. // So, let's just return a random number and *shrug* - int sequenceLength = _eval.pop(); - int index = _eval.pop(); + int sequenceLength = _eval.pop().get(); + int index = _eval.pop().get(); - _eval.push(_rng.rand(sequenceLength)); + _eval.push(value{}.set(_rng.rand(sequenceLength))); } break; case Command::SEED: { // TODO: Platform independance - int32_t seed = _eval.pop(); + int32_t seed = _eval.pop().get(); _rng.srand(seed); - // push void (TODO) - _eval.push(0); + _eval.push(values::null); } break; case Command::READ_COUNT: { @@ -946,7 +870,7 @@ namespace ink::runtime::internal container_t container = read(); // Push the read count for the requested container index - _eval.push((int)_globals->visits(container)); + _eval.push(value{}.set((int)_globals->visits(container))); } break; default: inkAssert(false, "Unrecognized command!"); diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 3b32dc14..2c33e346 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -16,6 +16,8 @@ #include "runner.h" #include "choice.h" +#include "executioner.h" + namespace ink::runtime::internal { class story_impl; @@ -128,6 +130,7 @@ namespace ink::runtime::internal private: const story_impl* const _story; story_ptr _globals; + executer _operations; // == State == @@ -144,7 +147,7 @@ namespace ink::runtime::internal // Evaluation stack bool bEvaluationMode = false; - internal::eval_stack<20> _eval; + internal::eval_stack _eval; bool bSavedEvaluationMode = false; // Keeps track of what threads we're inside diff --git a/inkcpp/stack.cpp b/inkcpp/stack.cpp index fac96cd5..caf99c9f 100644 --- a/inkcpp/stack.cpp +++ b/inkcpp/stack.cpp @@ -1,4 +1,5 @@ #include "stack.h" +#include "string_table.h" namespace ink::runtime::internal { @@ -33,14 +34,14 @@ namespace ink::runtime::internal } // If this is an end thread marker, skip over it - if (skip == ~0 && e.data.get_data_type() == data_type::thread_end) { - skip = e.data.as_divert(); + if (skip == ~0 && e.data.type() == value_type::thread_end) { + skip = e.data.get(); } // If we're skipping if (skip != ~0) { // Stop if we get to the start of the thread block - if (e.data.get_data_type() == data_type::thread_start && skip == e.data.as_divert()) { + if (e.data.type() == value_type::thread_start && skip == e.data.get().jump) { skip = ~0; } @@ -49,10 +50,10 @@ namespace ink::runtime::internal } // Is it a thread start or a jump marker - if (e.name == InvalidHash && (e.data.get_data_type() == data_type::thread_start || e.data.get_data_type() == data_type::jump_marker)) + if (e.name == InvalidHash && (e.data.type() == value_type::thread_start || e.data.type() == value_type::jump_marker)) { // If this thread start has a jump value - uint32_t jump = e.data.thread_jump(); + uint32_t jump = e.data.get().thread_id; // Then we need to do some jumping. Skip if (jump > 0) { @@ -100,24 +101,20 @@ namespace ink::runtime::internal return nullptr; } - void basic_stack::push_frame(offset_t return_to, frame_type type) + template<> + void basic_stack::push_frame(offset_t return_to) { - data_type frameDataType; - switch (type) - { - case frame_type::tunnel: - frameDataType = data_type::tunnel_frame; - break; - case frame_type::function: - frameDataType = data_type::function_frame; - break; - case frame_type::thread: - frameDataType = data_type::thread_frame; - break; - } - - // Add to top of stack - add(InvalidHash, value(return_to, frameDataType)); + add(InvalidHash, value{}.set(return_to)); + } + template<> + void basic_stack::push_frame(offset_t return_to) + { + add(InvalidHash, value{}.set(return_to)); + } + template<> + void basic_stack::push_frame(offset_t return_to) + { + add(InvalidHash, value{}.set(return_to)); } const entry* basic_stack::pop() @@ -131,27 +128,33 @@ namespace ink::runtime::internal iterator threadIter = jumpStart; // Get a reference to its jump count - uint32_t& jump = threadIter.get()->data.thread_jump(); + value& start = threadIter.get()->data; + value_type vt = start.type(); + auto jump = start.get(); // Move over it threadIter.next(); // Move back over the current jump value - for (uint32_t i = 0; i < jump; i++) + for (uint32_t i = 0; i < jump.thread_id; i++) threadIter.next(); // Now keep iterating back until we get to a frame marker - while (!threadIter.done() && (threadIter.get()->name != InvalidHash || threadIter.get()->data.is_thread_marker())) + // FIXME: meta types or subtypes? + while (!threadIter.done() && (threadIter.get()->name != InvalidHash + || threadIter.get()->data.type() == value_type::thread_start + || threadIter.get()->data.type() == value_type::thread_end)) { // If we've hit an end of thread marker auto e = threadIter.get(); - if (e->data.is_thread_end()) + if (e->data.type() == value_type::thread_end) { // We basically want to skip until we get to the start of this thread (leave the block alone) - thread_t tid = e->data.as_thread_id(); - while (!threadIter.get()->data.is_thread_start() || threadIter.get()->data.as_thread_id() != tid) + thread_t tid = e->data.get(); + while (threadIter.get()->data.type() != value_type::thread_start + || threadIter.get()->data.get().jump != tid) { - jump++; + jump.thread_id++; threadIter.next(); } @@ -159,25 +162,32 @@ namespace ink::runtime::internal } threadIter.next(); - jump++; + jump.thread_id++; } // Move us over the frame marker - jump++; + jump.thread_id++; // Now that thread marker is set to the correct jump value. + if (vt == value_type::jump_marker) { + start.set(jump); + } else if (vt == value_type::thread_start) { + start.set(jump); + } else { + throw ink_exception("unknown jump type"); + } return threadIter.get(); } - frame_type get_frame_type(data_type type) + frame_type get_frame_type(value_type type) { switch (type) { - case data_type::tunnel_frame: + case value_type::tunnel_frame: return frame_type::tunnel; - case data_type::function_frame: + case value_type::function_frame: return frame_type::function; - case data_type::thread_frame: + case value_type::thread_frame: return frame_type::thread; default: inkAssert(false, "Unknown frame type detected"); @@ -206,14 +216,18 @@ namespace ink::runtime::internal // We now have a frame marker. Check if it's a thread // Thread handling - if (frame->data.is_thread_marker() || frame->data.is_jump_marker()) + if ( + // FIXME: is_tghead_marker, is_jump_marker + frame->data.type() == value_type::thread_start + || frame->data.type() == value_type::thread_end + || frame->data.type() == value_type::jump_marker + ) { // End of thread marker, we need to create a jump marker - if (frame->data.get_data_type() == data_type::thread_end) + if (frame->data.type() == value_type::thread_end) { // Push a new jump marker after the thread end - entry& jump = push({ InvalidHash, value(0, data_type::jump_marker) }); - jump.data.thread_jump() = 0; + entry& jump = push({ InvalidHash, value{}.set(0u,0u) }); // Do a pop back returnedFrame = do_thread_jump_pop(base::begin()); @@ -221,7 +235,7 @@ namespace ink::runtime::internal } // If this is a jump marker, we actually want to extend it to the next frame - if (frame->data.is_jump_marker()) + if (frame->data.type() == value_type::jump_marker) { // Use the thread jump pop method using this jump marker returnedFrame = do_thread_jump_pop(iter); @@ -229,7 +243,7 @@ namespace ink::runtime::internal } // Popping past thread start - if (frame->data.get_data_type() == data_type::thread_start) + if (frame->data.type() == value_type::thread_start) { returnedFrame = do_thread_jump_pop(iter); break; @@ -245,17 +259,19 @@ namespace ink::runtime::internal inkAssert(returnedFrame, "Attempting to pop_frame when no frames exist! Stack reset."); // Make sure we're not somehow trying to "return" from a thread - inkAssert(returnedFrame->data.get_data_type() != data_type::thread_start && returnedFrame->data.get_data_type() != data_type::thread_end, + inkAssert(returnedFrame->data.type() != value_type::thread_start + && returnedFrame->data.type() != value_type::thread_end, "Can not return from a thread! How did this happen?"); // Store frame type if (type != nullptr) { - *type = get_frame_type(returnedFrame->data.get_data_type()); + *type = get_frame_type(returnedFrame->data.type()); } // Return the offset stored in the frame record - return returnedFrame->data.as_divert(); + // FIXME: correct type? + return returnedFrame->data.get().jump; } bool basic_stack::has_frame(frame_type* returnType) const @@ -280,21 +296,21 @@ namespace ink::runtime::internal // If we're skipping over a thread, wait until we hit its start before checking if (thread != ~0) { - if (elem.data.is_thread_start() && elem.data.as_thread_id() == thread) + if (elem.data.type() == value_type::thread_start && elem.data.get().jump == thread) thread = ~0; return false; } // If it's a jump marker or a thread start - if (elem.data.is_jump_marker() || elem.data.is_thread_start()) { - jumping = elem.data.thread_jump(); + if (elem.data.type() == value_type::jump_marker || elem.data.type() == value_type::thread_start) { + jumping = elem.data.get().thread_id; return false; } // If it's a thread end, we need to skip to the matching thread start - if (elem.data.is_thread_end()) { - thread = elem.data.as_thread_id(); + if (elem.data.type() == value_type::thread_end) { + thread = elem.data.get(); return false; } @@ -302,7 +318,7 @@ namespace ink::runtime::internal }); if (frame != nullptr && returnType != nullptr) - *returnType = get_frame_type(frame->data.get_data_type()); + *returnType = get_frame_type(frame->data.type()); // Return true if a frame was found return frame != nullptr; @@ -316,7 +332,12 @@ namespace ink::runtime::internal void basic_stack::mark_strings(string_table& strings) const { // Mark all strings - base::for_each_all([&strings](const entry& elem) { elem.data.mark_strings(strings); }); + base::for_each_all( + [&strings](const entry& elem) { + if (elem.data.type() == value_type::string) { + strings.mark_used(elem.data.get()); + } + }); } thread_t basic_stack::fork_thread() @@ -325,11 +346,10 @@ namespace ink::runtime::internal thread_t new_thread = _next_thread++; // Push a thread start marker here - entry& thread_entry = add(InvalidHash, value(new_thread, data_type::thread_start)); + entry& thread_entry = add(InvalidHash, value{}.set(new_thread, 0u)); // Set stack jump counter for thread to 0. This number is used if the thread ever // tries to pop past its origin. It keeps track of how much of the preceeding stack it's popped back - thread_entry.data.thread_jump() = 0; return new_thread; } @@ -337,7 +357,7 @@ namespace ink::runtime::internal void basic_stack::complete_thread(thread_t thread) { // Add a thread complete marker - add(InvalidHash, value(thread, data_type::thread_end)); + add(InvalidHash, value{}.set(thread)); } void basic_stack::collapse_to_thread(thread_t thread) @@ -351,8 +371,8 @@ namespace ink::runtime::internal // Keep popping until we find the requested thread's end marker const entry* top = pop(); while (!( - top->data.get_data_type() == data_type::thread_end && - top->data.as_divert() == thread)) + top->data.type() == value_type::thread_end && + top->data.get() == thread)) { inkAssert(!is_empty(), "Ran out of stack while searching for end of thread marker. Did you call complete_thread?"); top = pop(); @@ -373,14 +393,14 @@ namespace ink::runtime::internal } // Thread end. We just need to delete this whole block - if (nulling == ~0 && elem.data.is_thread_end() && elem.name == InvalidHash) { - nulling = elem.data.as_divert(); + if (nulling == ~0 && elem.data.type() == value_type::thread_end && elem.name == InvalidHash) { + nulling = elem.data.get(); } // If we're deleting a useless thread block if (nulling != ~0) { // If this is the start of the block, stop deleting - if (elem.name == InvalidHash && elem.data.get_data_type() == data_type::thread_start && elem.data.as_divert() == nulling) { + if (elem.name == InvalidHash && elem.data.type() == value_type::thread_start && elem.data.get().jump == nulling) { nulling = ~0; } @@ -390,16 +410,17 @@ namespace ink::runtime::internal else { // Clear thread start markers. We don't need or want them anymore - if (elem.name == InvalidHash && (elem.data.is_thread_start() || elem.data.is_jump_marker())) { + if (elem.name == InvalidHash && + (elem.data.type() == value_type::thread_start || elem.data.type() == value_type::jump_marker)) { // Clear it out elem.name = NulledHashId; // Check if this is a jump, if so we need to ignore even more data - jumping = elem.data.thread_jump(); + jumping = elem.data.get().thread_id; } // Clear thread frame markers. We can't use them anymore - if (elem.name == InvalidHash && elem.data.get_data_type() == data_type::thread_frame) { + if (elem.name == InvalidHash && elem.data.type() == value_type::thread_frame) { elem.name = NulledHashId; } } @@ -449,7 +470,7 @@ namespace ink::runtime::internal value basic_eval_stack::pop() { - return base::pop([](const value& v) { return v.is_none(); }); + return base::pop([](const value& v) { return v.type() == value_type::none; }); } const value& basic_eval_stack::top() const @@ -470,7 +491,14 @@ namespace ink::runtime::internal void basic_eval_stack::mark_strings(string_table& strings) const { // Iterate everything (including what we have saved) and mark strings - base::for_each_all([&strings](const value& elem) { elem.mark_strings(strings); }); + base::for_each_all([&strings](const value& elem) { + if (elem.type() == value_type::string) { + string_type str = elem.get(); + if (str.allocated) { + strings.mark_used(str.str); + } + } + }); } void basic_eval_stack::save() @@ -486,7 +514,7 @@ namespace ink::runtime::internal void basic_eval_stack::forget() { // Clear out - data x; x.set_none(); + value x; x.set(); value none = value(x); base::forget([&none](value& elem) { elem = none; }); } diff --git a/inkcpp/stack.h b/inkcpp/stack.h index 29292bb9..5bb062cc 100644 --- a/inkcpp/stack.h +++ b/inkcpp/stack.h @@ -9,6 +9,7 @@ namespace ink { namespace internal { + class string_table; struct entry { hash_t name; @@ -39,7 +40,8 @@ namespace ink value* get(hash_t name); // pushes a new frame onto the stack - void push_frame(offset_t return_to, frame_type type); + template + void push_frame(offset_t return_to); // Pops a frame (and all temporary variables) from the callstack. offset_t pop_frame(frame_type* type); @@ -125,9 +127,9 @@ namespace ink void forget(); }; - template class eval_stack : public basic_eval_stack { + static constexpr size_t N = 20; public: eval_stack() : basic_eval_stack(&_stack[0], N) { } private: diff --git a/inkcpp/string_operations.cpp b/inkcpp/string_operations.cpp new file mode 100644 index 00000000..2919dbca --- /dev/null +++ b/inkcpp/string_operations.cpp @@ -0,0 +1,70 @@ +/// implementation for commands on strings +/// string_cast is a class which convert an value to a string. +/// if the value is already a string it dose nothing (just serve the pointer), +/// else it convert the value to a string and store it, in it internal storage. + +#include "stack.h" +#include "value.h" +#include "string_utils.h" +#include "operations.h" +#include "string_table.h" + +namespace ink::runtime::internal { + namespace casting { + /** + * @brief Wrapper to cast values to string. + * string representation is stored inside string_cast. + */ + class string_cast { + public: + string_cast(const value& val); + const char* get() const { return _str; } + private: + const value& _val; + const char* _str; + char _data[512]; //TODO define central + }; + + // constructor for string_cast class + string_cast::string_cast(const value& val) : _val{val}, _str{nullptr} { + if (val.type() == value_type::string) { + // reference string if value is already a string + _str = val.get(); + } else { + // convert else + _str = _data; + toStr(_data, 512, val); + } + } + } + void operation::operator()(eval_stack& stack, value* vals) { + // convert values to strings + casting::string_cast lh(vals[0]); + casting::string_cast rh (vals[1]); + + // create new string with needed size + char* str = _string_table.create(c_str_len(lh.get()) + c_str_len(rh.get()) + 1); + + // copy to new string + char* dst = str; + for(const char* src = lh.get(); *src; ++src) { *dst++ = *src; } + for(const char* src = rh.get(); *src; ++src) { *dst++ = *src; } + *dst = 0; + + stack.push(value{}.set(str)); + } + + void operation::operator()(eval_stack& stack, value* vals) { + // convert values to string + casting::string_cast lh (vals[0]); + casting::string_cast rh(vals[1]); + + // compare strings char wise + const char* li = lh.get(); + const char* ri = rh.get(); + while(*li && *ri && *li++ == *ri++); + + stack.push(value{}.set(*li == *ri)); + } + +} diff --git a/inkcpp/string_operations.h b/inkcpp/string_operations.h new file mode 100644 index 00000000..44507eac --- /dev/null +++ b/inkcpp/string_operations.h @@ -0,0 +1,39 @@ +#pragma once + +/// defines operations allowed on strings. + +namespace ink::runtime::internal { + + namespace casting { + // define valid castings + // when operate on float and string, the result is a string + template<> + struct cast + { static constexpr value_type value = value_type::string; }; + template<> + struct cast + { static constexpr value_type value = value_type::string; }; + template<> + struct cast + { static constexpr value_type value = value_type::string; }; + template<> + struct cast + { static constexpr value_type value = value_type::string; }; + } + + // operation declaration add + template<> + class operation : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals); + }; + + // operation declaration equality + template<> + class operation : public operation_base { + public: + using operation_base::operation_base; + void operator()(eval_stack& stack, value* vals); + }; +} diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h index cd6956fc..193c1759 100644 --- a/inkcpp/string_utils.h +++ b/inkcpp/string_utils.h @@ -1,11 +1,16 @@ #pragma once #include "system.h" +#include "traits.h" +#include "value.h" #include -namespace ink::runtime::internal { +#ifndef EINVAL +#define EINVAL -1 +#endif +namespace ink::runtime::internal { // error behavior from: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/itoa-s-itow-s?view=msvc-160 inline int toStr(char * buffer, size_t size, uint32_t value) { #ifdef WIN32 @@ -41,12 +46,32 @@ namespace ink::runtime::internal { return EINVAL; #endif } - inline size_t strlen(const char* str) { - size_t len = 0; - for(const char* c = str; *c; ++c) { - ++len; + + inline int toStr(char* buffer, size_t size, const char* c) { + char* ptr = buffer; + size_t i = 0; + while(*c && i < size) { + *ptr++ = *c; + ++i; + } + if (i >= size) { return EINVAL; } + *ptr = 0; + return 0; + } + + inline int toStr(char * buffer, size_t size, const value& v) { + switch(v.type()) { + case value_type::int32: + return toStr(buffer, size, v.get()); + case value_type::uint32: + return toStr(buffer, size, v.get()); + case value_type::float32: + return toStr(buffer, size, v.get()); + case value_type::newline: + return toStr(buffer, size, "\n"); + default: + throw ink_exception("only support toStr for numeric types"); } - return len; } // return a upper bound for the string representation of the number @@ -65,4 +90,21 @@ namespace ink::runtime::internal { inline constexpr size_t decimal_digits(float number) { return 16; } + + inline constexpr size_t value_length(const value& v) { + switch(v.type()) { + case value_type::int32: + return decimal_digits(v.get()); + case value_type::uint32: + return decimal_digits(v.get()); + case value_type::float32: + return decimal_digits(v.get()); + case value_type::string: + return c_str_len(v.get()); + case value_type::newline: + return 1; + default: + throw ink_exception("Can't determine length of this value type"); + } + } } diff --git a/inkcpp/tuple.hpp b/inkcpp/tuple.hpp new file mode 100644 index 00000000..528c8d47 --- /dev/null +++ b/inkcpp/tuple.hpp @@ -0,0 +1,116 @@ +#pragma once + +/// very basic flat tuple implementation, only use for trivial data types. + +#include "./include/traits.h" + +namespace ink::runtime::internal { + namespace tuple_internal { + /// data member of tuple + template + class tuple_leaf{ + public: + tuple_leaf() : _value() {}; + template + explicit tuple_leaf(U&& u) : _value(std::forward(u)) {} + T& get() { return _value; } + const T& get() const { return _value; } + private: + T _value; + tuple_leaf(const tuple_leaf& tl) = delete; + tuple_leaf& operator=(const tuple_leaf&) = delete; + }; + + // handle indexing + template + struct tuple_indexes {}; + + // create tuple_indexes in [Start,End[ + template + struct make_tuple_indexes { + using type = typename make_tuple_indexes::type; + }; + template + struct make_tuple_indexes { + using type = tuple_indexes; + }; + + /// get the index of first appearance of an type in tuple + template + struct type_index_imp : type_index_imp {}; + template + struct type_index_imp { + static constexpr size_t value = I; + }; + template + constexpr size_t type_index = type_index_imp::value; + + + /// implementation class to extract indices + template + struct tuple_imp; + template + struct tuple_imp, Tys...> + : public tuple_leaf... + { + template + tuple_imp(Us&& ... us) : tuple_leaf(std::forward(us))... { + static_assert(sizeof...(Us) == sizeof...(Tys), + "Tuple must be initialized with same amount of arguments" + ", then types!"); + } + }; + } + + /// minimal tuple class, only for simple data types! + /// flat tuple implementation + template + class tuple + : public tuple_internal::tuple_imp< + typename tuple_internal::make_tuple_indexes::type, + Tys... > + { + using base = tuple_internal::tuple_imp< + typename tuple_internal::make_tuple_indexes::type, + Tys...>; + using this_type = tuple; + public: + template + using element_type = typename get_ith_type::type; + template + static constexpr size_t type_index = tuple_internal::type_index; + + template + tuple(Us&& ... us) : base(std::forward(us)...) {} + }; + + /// access tuple element by index + template + constexpr auto const& + get(const T& t) { + return static_cast>const&>(t).get(); + }; + + /// access tuple element by type. First of this type + template + constexpr const T& + get(const Tuple& t) { + return get, Tuple>(t); + }; + + template<> + class tuple<> { + public: + tuple() {} + }; + + /// check if tuple contains type + template + struct has_type; + template + struct has_type> : false_type {}; + template + struct has_type> : has_type> {}; + template + struct has_type> : true_type {}; +} diff --git a/inkcpp/value.cpp b/inkcpp/value.cpp index 6da899c5..c7a0022e 100644 --- a/inkcpp/value.cpp +++ b/inkcpp/value.cpp @@ -1,537 +1,35 @@ #include "value.h" #include "output.h" -#include "string_table.h" -#include "string_utils.h" -namespace ink +namespace ink::runtime::internal { - namespace runtime - { - namespace internal - { - value::value() - { - _data[0].set_void(); - for (size_t i = 1; i < VALUE_DATA_LENGTH; i++) - _data[i].set_none(); - } - - value::value(int val) : value() - { - _first.set_int(val); - } - - value::value(float val) : value() - { - _first.set_float(val); - } - - value::value(const char* str, bool allocated) : value() - { - _first.set_string(str, allocated); - } - - value::value(uint32_t val) : value() - { - _first.set_uint(val); - } - - value::value(const data& d) : value() - { - _first = d; - } - - value::value(uint32_t val, internal::data_type t) - { - _first.set_uint(val); - _first.type = t; - } - - value_type value::type() const - { - // If we have multiple values set, then we are a string - if (_second.type != data_type::none) - return value_type::string; - - switch (_first.type) - { - case data_type::int32: - return value_type::integer; - case data_type::uint32: - return value_type::divert; - case data_type::float32: - return value_type::decimal; - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: - return value_type::string; - case data_type::null: - return value_type::null; - default: - inkFail("Invalid data in value container!"); - } - } - - void value::cast(value& val, value_type old_type, value_type new_type) - { - if (old_type == new_type) - return; - - inkAssert(old_type < new_type, "Can only cast values upwards!"); - inkAssert(new_type != value_type::null && old_type != value_type::null, "Can not cast void values"); - inkAssert(new_type != value_type::divert && old_type != value_type::divert, "Can not cast divert values"); - - // We do not actually convert floats/ints to strings here. - // Instead, we just pass along the float and it is appended to - // another value's data stream - if (new_type == value_type::string) - return; - - switch (old_type) - { - case value_type::integer: - if (new_type == value_type::decimal) - val = value((float)val._first.integer_value); - return; - case value_type::decimal: - if (new_type == value_type::integer) - val = value((int)val._first.float_value); - return; - } - - inkAssert(false, "Invalid value cast"); - } - - value_type value::maybe_cast(value& left, value& right) - { - // Check the types of the two values. If they're the same, nothing to do. - auto l_type = left.type(), r_type = right.type(); - if (l_type == r_type) - return l_type; - - // Find the "largest" type - value_type type = l_type > r_type ? l_type : r_type; - - // Cast - value::cast(left, l_type, type); - value::cast(right, r_type, type); - - // Return new type - return type; - } - - void value::mark_strings(string_table& strings) const - { - // mark any allocated strings we're using - for (int i = 0; i < VALUE_DATA_LENGTH; i++) - { - switch (_data[i].type) - { - case data_type::allocated_string_pointer: - strings.mark_used(_data[i].string_val); - break; - case data_type::none: - return; - } - } - } - - bool value::is_truthy() const - { - // Concatenated strings are true - if (_second.type != data_type::none) - return true; - - switch (_first.type) - { - case data_type::int32: - return _first.integer_value != 0; - case data_type::float32: - return _first.float_value != 0.0f; - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: - return _first.string_val[0] != '\0'; - } - - inkFail("Invalid type to check for truthy"); - } - - void value::append_to(basic_stream& out) const - { - size_t i = 0; - for (; i < VALUE_DATA_LENGTH; i++) - { - if (_data[i].type == data_type::none) - break; - } - out.append(&_first, i);; - } - - void value::load_from(basic_stream& in) - { - in.get(&_first, VALUE_DATA_LENGTH); - } - - value value::add(value left, value right, basic_stream& stream, string_table& table) - { - // Cast as needed - value_type new_type = maybe_cast(left, right); - - switch (new_type) - { - case value_type::integer: - return left.as_int() + right.as_int(); - case value_type::decimal: - return left.as_float() + right.as_float(); - case value_type::string: - { - // Create empty value - value new_value = value(); - - // Copy left values into new - int i = 0, j = 0; - bool overflow = false; - while (j < VALUE_DATA_LENGTH && left._data[j].type != data_type::none && !overflow) - { - if (i >= VALUE_DATA_LENGTH) - { - overflow = true; - break; - } - new_value._data[i++] = left._data[j++]; - } - - // Copy right values into new - j = 0; - while (j < VALUE_DATA_LENGTH && right._data[j].type != data_type::none && !overflow) - { - if (i >= VALUE_DATA_LENGTH) - { - overflow = true; - break; - } - new_value._data[i++] = right._data[j++]; - } - - // Todo: Use string buffer for dynamic allocation! - if (overflow) - { - // Add a marker - stream << marker; - - // Push everything into the stream - j = 0; - while (j < VALUE_DATA_LENGTH && left._data[j].type != data_type::none) - stream << left._data[j++]; - j = 0; - while (j < VALUE_DATA_LENGTH && right._data[j].type != data_type::none) - stream << right._data[j++]; - - // Pull out into a new string - return value(stream.get_alloc(table), true); - } - - return new_value; - } - } - - inkFail("Invalid type for add"); - } - - // TODO: Macro to make defining these easier when there's no string involvement? - - value value::subtract(value left, value right) - { - // Cast as needed - value_type new_type = maybe_cast(left, right); - - switch (new_type) - { - case value_type::integer: - return left.as_int() - right.as_int(); - case value_type::decimal: - return left.as_float() - right.as_float(); - } - - inkFail("Invalid type for subtract"); - } - - value value::multiply(value left, value right) - { - // Cast as needed - value_type new_type = maybe_cast(left, right); - - switch (new_type) - { - case value_type::integer: - return left.as_int() * right.as_int(); - case value_type::decimal: - return left.as_float() * right.as_float(); - } - - inkFail("Invalid type for multiply"); - } - - value value::divide(value left, value right) - { - // Cast as needed - value_type new_type = maybe_cast(left, right); - - switch (new_type) - { - case value_type::integer: - return left.as_int() / right.as_int(); - case value_type::decimal: - return left.as_float() / right.as_float(); - } - - inkFail("Invalid type for divide"); - } - - value value::mod(value left, value right) - { - // Cast as needed - value_type new_type = maybe_cast(left, right); - - switch (new_type) - { - case value_type::integer: - return left.as_int() % right.as_int(); - } - - inkFail("Invalid type for mod"); - } - - void convert_to_string(const char*& c, const data& d, char* number) { - c = number; - switch(d.type) { - case data_type::int32: - snprintf(number, 32, "%d", d.integer_value); - break; - case data_type::uint32: - snprintf(number, 32, "%d", d.uint_value); - break; - case data_type::float32: - snprintf(number, 32, "%f", d.float_value); - break; - case data_type::newline: - number[0] = '\n'; - number[1] = 0; - break; - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: - c = d.string_val; - break; - default: number[0] = 0; - } - } - - void value::finalize_string(string_table& table) const { - constexpr size_t MaxSize = 256; // max size for no string element - char buffer[VALUE_DATA_LENGTH][MaxSize]; - const char* strs[VALUE_DATA_LENGTH]; - char null = 0; - - size_t len = 0; - for (int i = 0; i < VALUE_DATA_LENGTH; ++i) { - switch(_data[i].type) { - case data_type::float32: - strs[i] = buffer[i]; - toStr(buffer[i], MaxSize, _data[i].float_value); - break; - case data_type::uint32: - strs[i] = buffer[i]; - toStr(buffer[i], MaxSize, _data[i].uint_value); - break; - case data_type::int32: - strs[i] = buffer[i]; - toStr(buffer[i], MaxSize, _data[i].integer_value); - break; - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: - strs[i] = _data[i].string_val; - break; - default: strs[i] = &null; - } - _data[i].set_none(); - len += strlen(strs[i]); - } - char* str = table.create(len+1); - table.mark_used(str); - - char* ptr = str; - for (int i = 0; i < VALUE_DATA_LENGTH; ++i) { - for(const char* c = strs[i]; *c; ++c){ - *ptr++ = *c; - } - } - *ptr = 0; - _data[0].set_string(str, true); - } - - const char* value::as_str(string_table& table) const { - finalize_string(table); - return _first.string_val; - } - - const char * const * value::as_str_ptr(string_table& table) const { - finalize_string(table); - return &_first.string_val; - } - - bool value::compare_string(const value& left, const value& right) { - // convert fields to string representation and start comparison - // when the end of one field is reached, the other still has - // bytes left -> convert the next field and continue comparison - - // iterator for data fields of left and right value - size_t l_i = 0; - size_t r_i = 0; - // buffer to store string representation of numeric fields - char l_number[32]; l_number[0] = 0; - char r_number[32]; r_number[0] = 0; - // current compare position, if *l = 0 -> field compare finish - const char* l_c = l_number; - const char* r_c = r_number; - bool res = true; - // while no different found and fields to check remain - while(res && l_i < VALUE_DATA_LENGTH && r_i < VALUE_DATA_LENGTH) { - // if one field has left overs - if (*l_c || *r_c) - { - // fetch the next field of the value without leftover - if (*l_c) { - convert_to_string(r_c, right._data[r_i], r_number); - } else { - convert_to_string(l_c, left._data[l_i], l_number); - } - } - // if both values are aligned: check if both have the same type - else if (left._data[l_i].type == right._data[r_i].type) - { - bool tmp_res = true; - switch(left._data[l_i].type) { - case data_type::int32: - tmp_res = left._data[l_i].integer_value == right._data[r_i].integer_value; - break; - case data_type::uint32: - tmp_res = left._data[l_i].uint_value == right._data[r_i].uint_value; - break; - case data_type::float32: - tmp_res = left._data[l_i].float_value == right._data[r_i].float_value; - break; - case data_type::string_table_pointer: - case data_type::allocated_string_pointer: - l_c = left._data[l_i].string_val; - r_c = right._data[r_i].string_val; - break; - default: break; - } - // check if maybe the missing part is in next data - if (!tmp_res) { - convert_to_string(r_c, right._data[r_i], r_number); - convert_to_string(l_c, left._data[l_i], l_number); - } - } - // convert both to string and compare - else - { - convert_to_string(r_c, right._data[r_i], r_number); - convert_to_string(l_c, left._data[l_i], l_number); - } - // compare string representation until one reaches the end - while(*l_c && *r_c) { - // if different found: stop and set result to false - if (*l_c != *r_c) { - res = false; break; - } - ++l_c; ++r_c; - } - // if field is finished advance to the next - if (!*l_c){ ++l_i; } - if (!*r_c){ ++r_i; } - } - // if one value not complete compared -> leftover witch not match - if (res && l_i != r_i) { - const value& v = l_i < r_i ? left : right; - int i = l_i < r_i ? l_i : r_i; - // check if leftover fields all empty - while(v._data[i].type != data_type::none) { - if (v._data[i].type != data_type::string_table_pointer - && v._data[i].type != data_type::allocated_string_pointer) { - if (*v._data[i].string_val != 0) { - return false; - } - } else { - return false; - } - ++i; - } - } - return res; - } - - value value::is_equal(value left, value right) - { - // Cast as needed - value_type new_type = maybe_cast(left, right); - - switch (new_type) - { - case value_type::integer: - return left.as_int() == right.as_int(); - case value_type::decimal: - return left.as_float() == right.as_float(); - case value_type::string: - return compare_string(left, right); - case value_type::divert: - return left.as_divert() == right.as_divert(); - } - - inkFail("Invalid type for is_equal"); - } - - value value::less_than(value left, value right) - { - // Cast as needed - value_type new_type = maybe_cast(left, right); - - switch (new_type) - { - case value_type::integer: - return left.as_int() < right.as_int(); - case value_type::decimal: - return left.as_float() < right.as_float(); - } - - inkFail("Invalid type for less_than"); - } - - value value::negate(const value& val) - { - inkAssert(val._second.type == data_type::none, "Can not negate strings"); - - switch (val._first.type) - { - case data_type::int32: - return -val._first.integer_value; - case data_type::float32: - return -val._first.float_value; - } - - inkFail("Invalid type for negate"); +#ifdef INK_ENABLE_STL + template + void append(std::ostream& os, const value& val) { + if constexpr (ty != value_type::PRINT_END) { + if (ty == val.type()) { + os << val.get(); + } else { + append(os, val); } + } + } + std::ostream& operator<<(std::ostream& os, const value& val) { + if (val.type() < value_type::PRINT_BEGIN || val.type() >= value_type::PRINT_END) { + throw ink_exception("printing this type is not supported"); + } + append(os, val); + return os; + } +#endif - basic_stream& operator>>(basic_stream& in, value& out) - { - out.load_from(in); - return in; - } + basic_stream& operator<<(basic_stream& os, const value& val) { + os.append(val); + return os; + } - basic_stream& operator<<(basic_stream& out, const value& in) - { - in.append_to(out); - return out; - } - } + basic_stream& operator>>(basic_stream& is, value& val) { + is.get(&val, 1); + return is; } } diff --git a/inkcpp/value.h b/inkcpp/value.h index 0bc32ae0..73651637 100644 --- a/inkcpp/value.h +++ b/inkcpp/value.h @@ -1,218 +1,325 @@ #pragma once +/// The value class contains the information which type its hold and a small +/// piece of information to access the data. +/// use explicit getter and setter to make access more uniform. +/// define different value_types, and the mapping between type and data. + #include "system.h" +#include "../shared/private/command.h" #ifdef INK_ENABLE_STL -#include +#include #endif -namespace ink -{ - namespace runtime - { - namespace internal - { - class string_table; - - // Data types that can be held internally within the ink runtime - enum class data_type - { - none, // blank. used when data is in a fixed array - int32, // 32-bit integer value - float32, // 32-bit floating point value - uint32, // 32-bit unsigned integer value - string_table_pointer, // Represents an offset within the story's constant string table - allocated_string_pointer, // Represents an offset within the runner's allocated string table - marker, // Special marker (used in output stream) - glue, // Glue. - newline, // \n - func_start, // Start of function marker - func_end, // End of function marker - null, // void/null (used for void function returns) - tunnel_frame, // Return from tunnel - function_frame, // Return from function - thread_frame, // Special tunnel marker for returning from threads - thread_start, // Start of a new thread frame - thread_end, // End of a thread frame - jump_marker, // Used to mark a callstack jump - }; - - // Container for any data used as part of the runtime (variable values, output streams, evaluation stack, etc.) - struct data - { - // Type of data - data_type type; - - union - { - int integer_value; - uint32_t uint_value; - float float_value; - const char* string_val; - // TODO: Do we need a marker type? - }; - - inline void set_none() { type = data_type::none; } - inline void set_void() { type = data_type::null; } - inline void set_int(int val) { type = data_type::int32; integer_value = val; } - inline void set_uint(uint32_t val) { type = data_type::uint32; uint_value = val; } - inline void set_float(float val) { type = data_type::float32; float_value = val; } - inline void set_string(const char* val, bool allocated) { type = allocated ? data_type::allocated_string_pointer : data_type::string_table_pointer; string_val = val; } - }; - - // TODO: Re-implement. Failed on 64-bit builds. - //static_assert(sizeof(data) == sizeof(data_type) + sizeof(offset_t), "No data type should take up more than 32 bits"); - - // Types of values - enum class value_type - { - null, - divert, - integer, - decimal, - string, - }; - - class basic_stream; - - // Used to store values on the evaluation stack or variables. - class value - { - public: - value(); // Creates a value with the "none" type - value(int); // Create a new int value - value(float); // Create a new float value - value(uint32_t); // Create a new divert value - value(const data&); // Create value from data - value(uint32_t, data_type); // Create divert with type - - // Create a new string value (must specify whether or not it's an allocated or story string) - value(const char*, bool allocated = false); - - // Check the value's current type - value_type type() const; - bool is_none() const { return _first.type == data_type::none; } - data_type get_data_type() const { return _first.type; } - - // == Getters == - int as_int() const { return _first.integer_value; } - int* as_int_ptr() { return &_first.integer_value; } - const int* as_int_ptr() const { return &_first.integer_value; } - float as_float() const { return _first.float_value; } - float* as_float_ptr() { return &_first.float_value; } - const float* as_float_ptr() const { return &_first.float_value; } - uint32_t as_divert() const { return _first.uint_value; } - uint32_t as_thread_id() const { return _first.uint_value; } - uint32_t* as_uint_ptr() { return &_first.uint_value; } - const uint32_t* as_uint_ptr() const { return &_first.uint_value; } - const char* as_str(string_table&) const; - const char* const * as_str_ptr(string_table&) const; - - template - T get() const { static_assert(always_false::value, "Type not supported by value class"); } - - // Garbage collection - void mark_strings(string_table&) const; - - inline operator int() const { return as_int(); } - inline operator float() const { return as_float(); } - inline operator uint32_t() const { return as_divert(); } - - // == Threading == - inline bool is_thread_marker() const { return _first.type == data_type::thread_start || _first.type == data_type::thread_end; } - inline bool is_thread_end() const { return _first.type == data_type::thread_end; } - inline bool is_thread_start() const { return _first.type == data_type::thread_start; } - inline bool is_jump_marker() const { return _first.type == data_type::jump_marker; } - inline uint32_t& thread_jump() { return _second.uint_value; } - inline uint32_t thread_jump() const { return _second.uint_value; } - - // Is this value "true" - bool is_truthy() const; - inline operator bool() const { return is_truthy(); } - - void append_to(basic_stream&) const; - void load_from(basic_stream&); - - // == Binary operations == - static value add(value, value, basic_stream&, string_table&); - static value subtract(value, value); - static value multiply(value, value); - static value divide(value, value); - static value mod(value, value); - static value is_equal(value, value); - static value less_than(value, value); - - // == Unary operations - static value negate(const value&); - - private: - static void cast(value&, value_type, value_type); - static value_type maybe_cast(value& left, value& right); - /** - * @brief compare if the string representation of values are equal. - */ - static bool compare_string(const value& left, const value& right); - /** - * @brief compress string values to one data field - */ - void finalize_string(string_table&) const; - - private: - // Maximum sequential data a value can have - static const size_t VALUE_DATA_LENGTH = 4; - - union - { - // Quick access struct - struct - { - data _first; - data _second; - }; - - // Data array - mutable data _data[VALUE_DATA_LENGTH]; - }; - - }; - - // == Binary Operators == - // inline value operator+(const value& lhs, const value& rhs) { return value::add(lhs, rhs); } - inline value operator-(const value& lhs, const value& rhs) { return value::subtract(lhs, rhs); } - inline value operator*(const value& lhs, const value& rhs) { return value::multiply(lhs, rhs); } - inline value operator/(const value& lhs, const value& rhs) { return value::divide(lhs, rhs); } - inline value operator%(const value& lhs, const value& rhs) { return value::mod(lhs, rhs); } - inline value operator-(const value& val) { return value::negate(val); } - - inline value operator==(const value& lhs, const value& rhs) { return value::is_equal(lhs, rhs); } - inline value operator!=(const value& lhs, const value& rhs) { return !value::is_equal(lhs, rhs); } - inline value operator<(const value& lhs, const value& rhs) { return value::less_than(lhs, rhs); } - inline value operator<=(const value& lhs, const value& rhs) { return value::less_than(lhs, rhs) || value::is_equal(lhs, rhs); } - inline value operator>(const value& lhs, const value& rhs) { return !(lhs <= rhs); } - inline value operator>=(const value& lhs, const value& rhs) { return !value::less_than(lhs,rhs); } - - // == Stream Operators == - basic_stream& operator <<(basic_stream&, const value&); - basic_stream& operator >>(basic_stream&, value&); - - // == Specialized get functions == - // TODO: Should these assert? - - template<> - inline int value::get() const { return as_int(); } - template<> - inline float value::get() const { return as_float(); } - template<> - inline uint32_t value::get() const { return as_divert(); } + +namespace ink::runtime::internal { + class basic_stream; + + /// different existing value_types + enum class value_type { + BEGIN, // To find the start of list + none = BEGIN, // no type -> invalid + divert, // divert to different story position + PRINT_BEGIN, // first printable value + boolean = PRINT_BEGIN, // boolean variable + uint32, // 32bit unsigned integer variable + int32, // 32bit integer variable + float32, // 32bit floating point value + string, // Pointer to string + OP_END, // END of types where we can operate on + newline = OP_END, // newline symbol + PRINT_END, // END of printable values + marker = PRINT_END, // special marker (used in output stream) + glue, // glue symbol + func_start, // start of function marker + func_end, // end of function marker + null, // void value, for function returns + tunnel_frame, // return from tunnel + function_frame, // return from function + thread_frame, // return from thread + thread_start, // start of thread frame + thread_end, // end of thread frame + jump_marker // callstack jump + }; + + // add operator for value_type (to simplify usage templates). + constexpr value_type operator+(value_type t, int i) { + return static_cast(static_cast(t)+i); + } + // add operator for Command (to simplify usage in templates). + constexpr Command operator+(Command c, int i) { + return static_cast(static_cast(c)+i); + } + + + struct string_type{ + constexpr string_type(const char* string, bool allocated) + : str{string}, allocated{allocated}{} + constexpr string_type(const char* string) + : str{string}, allocated{true} {} + operator const char*() const { + return str; + } + const char* str; + bool allocated; + }; + /** + * @brief class to wrap stack value to common type. + */ + class value { + public: + /// help struct to determine cpp type which represent the value_type + template struct ret { using type = void; }; + + constexpr value() : _type{value_type::none}, uint32_value{0}{} + + /// get value of the type (if possible) + template + typename ret::type + get() const { static_assert(ty != ty, "No getter for this type defined!"); } + + /// set value of type (if possible) + template + constexpr value& set(Args ...args) { + static_assert(sizeof...(Args)!=sizeof...(Args), "No setter for this type defined!"); + return *this; + } + + /// get type of value + constexpr value_type type() const { return _type; } + + friend basic_stream& operator<<(basic_stream& os, const value&); + friend basic_stream& operator>>(basic_stream& is, value&); + + /// returns if type is printable (see value_type) + constexpr bool printable() const { + return _type >= value_type::PRINT_BEGIN && _type < value_type::PRINT_END; + } + + private: + /// actual storage + union { + bool bool_value; + int32_t int32_value; + string_type string_value; + uint32_t uint32_value; + float float_value; + struct { + uint32_t jump; + uint32_t thread_id; + } jump; + }; + value_type _type; + }; #ifdef INK_ENABLE_STL - template<> - inline std::string value::get() const { return _first.string_val; } // TODO: Missing amalgamate? + std::ostream& operator<<(std::ostream&,const value&); #endif -#ifdef INK_ENABLE_UNREAL - template<> - inline FString value::get() const { return _first.string_val; } // TODO: Missing amalgamate? -#endif - } + + // define get and set for int32 + template<> struct value::ret { using type = int32_t; }; + template<> inline int32_t value::get() const { return int32_value; } + template<> + inline constexpr value& value::set(int32_t v) { + int32_value = v; + _type = value_type::int32; + return *this; + } + + + // define get and set for uint32 + template<> struct value::ret { using type = uint32_t; }; + template<> inline uint32_t value::get() const { return uint32_value; } + template<> + inline constexpr value& value::set(uint32_t v) { + uint32_value = v; + _type = value_type::uint32; + return *this; + } + + // define get and set for divert + template<> struct value::ret { using type = uint32_t; }; + template<> inline uint32_t value::get() const { return uint32_value; } + template<> + inline constexpr value& value::set(uint32_t v) { + uint32_value = v; + _type = value_type::divert; + return *this; + } + + // define get and set for float + template<> struct value::ret { using type = float; }; + template<> inline float value::get() const { return float_value; } + template<> + inline constexpr value& value::set(float v) { + float_value = v; + _type = value_type::float32; + return *this; + } + + // define get and set for boolean + template<> struct value::ret { using type = bool; }; + template<> inline bool value::get() const { return bool_value; } + template<> + inline constexpr value& value::set(bool v) { + bool_value = v; + _type = value_type::boolean; + return *this; + } + + // define get and set for string + template<> struct value::ret { using type = string_type; }; + template<> inline string_type value::get() const { return string_value; } + template<> + inline constexpr value& value::set(const char* v) { + string_value = {v}; + _type = value_type::string; + return *this; + } + template<> + inline constexpr value& value::set(char* v) { + string_value = {v}; + _type = value_type::string; + return *this; + } + template<> + inline constexpr value& value::set(const char* v, bool allocated) { + string_value = {v, allocated}; + _type = value_type::string; + return *this; + } + template<> + inline constexpr value& value::set( char* v, bool allocated) { + string_value = {v, allocated}; + _type = value_type::string; + return *this; + } + + // define getter and setter for jump_marker + template<> struct value::ret { using type = decltype(value::jump); }; + template<> inline value::ret::type value::get() const { return jump; } + template<> + inline constexpr value& value::set(decltype(value::jump) v) { + jump = v; + _type = value_type::jump_marker; + return *this; + } + template<> + inline constexpr value& value::set(uint32_t v, uint32_t j) { + jump.jump = v; + jump.thread_id = j; + _type = value_type::jump_marker; + return *this; + } + + // define getter and setter for thread_start + template<> struct value::ret { using type = decltype(value::jump); }; + template<> inline value::ret::type value::get() const { return jump; } + template<> + inline constexpr value& value::set(decltype(value::jump) v) + { + jump = v; + _type = value_type::thread_start; + return *this; + } + template<> + inline constexpr value& value::set(uint32_t v, uint32_t j) { + jump.jump = v; + jump.thread_id = j; + _type = value_type::thread_start; + return *this; + } + + // define getter and setter for thread_end + template<> struct value::ret { using type = uint32_t; }; + template<> inline uint32_t value::get() const { return uint32_value; } + template<> + inline constexpr value& value::set(uint32_t v) { + uint32_value = v; + _type = value_type::thread_end; + return *this; + } + + // define setter for values without storage + template<> + inline constexpr value& value::set() { + _type = value_type::marker; + return *this; + } + template<> + inline constexpr value& value::set() { + _type = value_type::glue; + return *this; + } + template<> + inline constexpr value& value::set() { + _type = value_type::newline; + return *this; + } + template<> struct value::ret { using type = const char*; }; + template<> + inline const char* value::get() const { + static const char line_break[] = "\n"; + return line_break; + } + template<> + inline constexpr value& value::set() { + _type = value_type::func_start; + return *this; + } + template<> + inline constexpr value& value::set() { + _type = value_type::func_end; + return *this; + } + template<> + inline constexpr value& value::set() { + _type = value_type::null; + return *this; + } + template<> + inline constexpr value& value::set() { + _type = value_type::none; + return *this; + } + + // getter and setter for different frame types + // FIXME: the getter are not used? + /* template<> struct value::ret{ using type = uint32_t; }; */ + /* template<> inline uint32_t value::get() const { return uint32_value; } */ + template<> + inline constexpr value& value::set(uint32_t v) { + uint32_value = v; + _type = value_type::function_frame; + return *this; + } + // FIXME: the getter are not used? + /* template<> struct value::ret{ using type = uint32_t; }; */ + /* template<> inline uint32_t value::get() const { return uint32_value; } */ + template<> + inline constexpr value& value::set(uint32_t v) { + uint32_value = v; + _type = value_type::tunnel_frame; + return *this; + } + // FIXME: the getter are not used? + /* template<> struct value::ret{ using type = uint32_t; }; */ + /* template<> inline uint32_t value::get() const { return uint32_value; } */ + template<> + inline constexpr value& value::set(uint32_t v) { + uint32_value = v; + _type = value_type::thread_frame; + return *this; + } + + // static constexpr instantiations of flag values + namespace values { + static constexpr value marker = value{}.set(); + static constexpr value glue = value{}.set(); + static constexpr value newline = value{}.set(); + static constexpr value func_start = value{}.set(); + static constexpr value func_end = value{}.set(); + static constexpr value null = value{}.set(); } } diff --git a/inkcpp_compiler/json_compiler.cpp b/inkcpp_compiler/json_compiler.cpp index 1bd65808..15548af2 100644 --- a/inkcpp_compiler/json_compiler.cpp +++ b/inkcpp_compiler/json_compiler.cpp @@ -318,5 +318,13 @@ namespace ink::compiler::internal // Encode argument count into command flag and write out the hash of the function name _emitter->write(Command::CALL_EXTERNAL, hash_string(val.c_str()), (CommandFlag)numArgs); } + + // list initialisation + else if (has(command, "list")) + { + for ( const auto& entry : command["list"]) { + + } + } } -} \ No newline at end of file +} diff --git a/inkcpp_test/Callstack.cpp b/inkcpp_test/Callstack.cpp index 39ab64ad..3e1a7f5d 100644 --- a/inkcpp_test/Callstack.cpp +++ b/inkcpp_test/Callstack.cpp @@ -5,12 +5,17 @@ using ink::hash_t; using ink::thread_t; using ink::runtime::internal::value; +using ink::runtime::internal::value_type; using ink::runtime::internal::frame_type; const hash_t X = ink::hash_string("X"); const hash_t Y = ink::hash_string("Y"); const hash_t Z = ink::hash_string("Z"); +value operator "" _v(unsigned long long i) { + return value{}.set(static_cast(i)); +} + SCENARIO("threading with the callstack", "[callstack]") { GIVEN("a callstack with a few temporary variables") @@ -19,8 +24,8 @@ SCENARIO("threading with the callstack", "[callstack]") auto stack = ink::runtime::internal::stack<50>(); // Set X and Y temporary variables - stack.set(X, 100); - stack.set(Y, 200); + stack.set(X, 100_v); + stack.set(Y, 200_v); WHEN("there is a fork and more is pushed") { @@ -33,8 +38,8 @@ SCENARIO("threading with the callstack", "[callstack]") } // Push something onto the thread - stack.set(X, 200); - REQUIRE((int)*stack.get(X) == 200); + stack.set(X, 200_v); + REQUIRE(stack.get(X)->get() == 200); WHEN("when that thread ends") { @@ -46,7 +51,7 @@ SCENARIO("threading with the callstack", "[callstack]") THEN("we should have the value from that thread") { - REQUIRE((int)*stack.get(X) == 200); + REQUIRE(stack.get(X)->get() == 200); } } @@ -56,20 +61,20 @@ SCENARIO("threading with the callstack", "[callstack]") THEN("we should have the value from the original thread") { - REQUIRE((int)*stack.get(X) == 100); - REQUIRE((int)*stack.get(Y) == 200); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->get() == 200); } } THEN("we should be able to access original thread values") { - REQUIRE((int)*stack.get(X) == 100); - REQUIRE((int)*stack.get(Y) == 200); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->get() == 200); } WHEN("we push more on the main thread") { - stack.set(Z, 500); + stack.set(Z, 500_v); THEN("collapsing to the thread shouldn't have the values anymore") { @@ -80,20 +85,20 @@ SCENARIO("threading with the callstack", "[callstack]") WHEN("we start a second thread that closes") { thread_t thread2 = stack.fork_thread(); - stack.set(X, 999); + stack.set(X, 999_v); stack.complete_thread(thread2); THEN("we can still collapse to the main thread") { stack.collapse_to_thread(~0); - REQUIRE((int)*stack.get(X) == 100); - REQUIRE((int)*stack.get(Y) == 200); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->get() == 200); } THEN("we can still collapse to the first thread") { stack.collapse_to_thread(thread); - REQUIRE((int)*stack.get(X) == 200); + REQUIRE(stack.get(Y)->get() == 200); REQUIRE(stack.get(Z) == nullptr); } } @@ -105,7 +110,7 @@ SCENARIO("threading with the callstack", "[callstack]") thread_t thread2 = stack.fork_thread(); // Put something on this thread - stack.set(X, 999); + stack.set(X, 999_v); WHEN("that inner thread and outer thread complete") { @@ -118,7 +123,7 @@ SCENARIO("threading with the callstack", "[callstack]") THEN("we should have the value from that thread") { - REQUIRE((int)*stack.get(X) == 999); + REQUIRE(stack.get(X)->get() == 999); } } @@ -128,7 +133,7 @@ SCENARIO("threading with the callstack", "[callstack]") THEN("we should have the value from that thread") { - REQUIRE((int)*stack.get(X) == 200); + REQUIRE(stack.get(X)->get() == 200); } } @@ -138,8 +143,8 @@ SCENARIO("threading with the callstack", "[callstack]") THEN("we should have the value from the original thread") { - REQUIRE((int)*stack.get(X) == 100); - REQUIRE((int)*stack.get(Y) == 200); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->get() == 200); } } } @@ -149,7 +154,7 @@ SCENARIO("threading with the callstack", "[callstack]") WHEN("there is a fork with a tunnel that finishes") { thread_t thread = stack.fork_thread(); - stack.push_frame(555, frame_type::tunnel); + stack.push_frame(555); stack.complete_thread(thread); THEN("there should be no frames on the stack") @@ -165,15 +170,15 @@ SCENARIO("threading with the callstack", "[callstack]") auto stack = ink::runtime::internal::stack<50>(); // Set X and Y temporary variables - stack.set(X, 100); - stack.set(Y, 200); + stack.set(X, 100_v); + stack.set(Y, 200_v); // Push a tunnel - stack.push_frame(505, frame_type::tunnel); + stack.push_frame(505); // Push some more temps - stack.set(X, 101); - stack.set(Y, 201); + stack.set(X, 101_v); + stack.set(Y, 201_v); WHEN("a thread is forked") { @@ -189,8 +194,8 @@ SCENARIO("threading with the callstack", "[callstack]") REQUIRE(type == frame_type::tunnel); REQUIRE(offset == 505); - REQUIRE(*stack.get(X) == value(100)); - REQUIRE(*stack.get(Y) == value(200)); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->get() == 200); } stack.complete_thread(thread); @@ -200,8 +205,10 @@ SCENARIO("threading with the callstack", "[callstack]") stack.collapse_to_thread(thread); THEN("the stack should be outside the tunnel") { - REQUIRE(*stack.get(X) == value(100)); - REQUIRE(*stack.get(Y) == value(200)); + REQUIRE(stack.get(X)->type() == value_type::int32); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->type() == value_type::int32); + REQUIRE(stack.get(Y)->get() == 200); } } @@ -210,8 +217,10 @@ SCENARIO("threading with the callstack", "[callstack]") stack.collapse_to_thread(~0); THEN("the stack should be inside the tunnel") { - REQUIRE(*stack.get(X) == value(101)); - REQUIRE(*stack.get(Y) == value(201)); + REQUIRE(stack.get(X)->type() == value_type::int32); + REQUIRE(stack.get(X)->get() == 101); + REQUIRE(stack.get(Y)->type() == value_type::int32); + REQUIRE(stack.get(Y)->get() == 201); } WHEN("we do a tunnel return") @@ -223,8 +232,10 @@ SCENARIO("threading with the callstack", "[callstack]") { REQUIRE(type == frame_type::tunnel); REQUIRE(offset == 505); - REQUIRE(*stack.get(X) == value(100)); - REQUIRE(*stack.get(Y) == value(200)); + REQUIRE(stack.get(X)->type() == value_type::int32); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->type() == value_type::int32); + REQUIRE(stack.get(Y)->get() == 200); } } } @@ -238,16 +249,20 @@ SCENARIO("threading with the callstack", "[callstack]") THEN("we should be outside the tunnel") { - REQUIRE(*stack.get(X) == value(100)); - REQUIRE(*stack.get(Y) == value(200)); + REQUIRE(stack.get(X)->type() == value_type::int32); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->type() == value_type::int32); + REQUIRE(stack.get(Y)->get() == 200); } THEN("collapsing to the thread will have the correct callstack") { stack.collapse_to_thread(thread); - REQUIRE(*stack.get(X) == value(101)); - REQUIRE(*stack.get(Y) == value(201)); + REQUIRE(stack.get(X)->type() == value_type::int32); + REQUIRE(stack.get(X)->get() == 101); + REQUIRE(stack.get(Y)->type() == value_type::int32); + REQUIRE(stack.get(Y)->get() == 201); } } } @@ -262,15 +277,15 @@ SCENARIO("threading with the callstack", "[callstack]") thread_t thread = stack.fork_thread(); // Set X and Y temporary variables - stack.set(X, 100); - stack.set(Y, 200); + stack.set(X, 100_v); + stack.set(Y, 200_v); // Push a tunnel - stack.push_frame(505, frame_type::tunnel); + stack.push_frame(505); // Push some more temps - stack.set(X, 101); - stack.set(Y, 201); + stack.set(X, 101_v); + stack.set(Y, 201_v); WHEN("a second thread is forked off the first") { @@ -287,8 +302,10 @@ SCENARIO("threading with the callstack", "[callstack]") THEN("accessing the variable should return the original") { - REQUIRE(*stack.get(X) == value(100)); - REQUIRE(*stack.get(Y) == value(200)); + REQUIRE(stack.get(X)->type() == value_type::int32); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->type() == value_type::int32); + REQUIRE(stack.get(Y)->get() == 200); } WHEN("the first thread ends") @@ -299,8 +316,10 @@ SCENARIO("threading with the callstack", "[callstack]") { stack.collapse_to_thread(thread); - REQUIRE(*stack.get(X) == value(100)); - REQUIRE(*stack.get(Y) == value(200)); + REQUIRE(stack.get(X)->type() == value_type::int32); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->type() == value_type::int32); + REQUIRE(stack.get(Y)->get() == 200); } } } @@ -317,22 +336,22 @@ SCENARIO("threading with the callstack", "[callstack]") thread_t thread = stack.fork_thread(); // Set X and Y temporary variables - stack.set(X, 100); - stack.set(Y, 200); + stack.set(X, 100_v); + stack.set(Y, 200_v); // Push a tunnel - stack.push_frame(505, frame_type::tunnel); + stack.push_frame(505); // Push some more temps - stack.set(X, 101); - stack.set(Y, 201); + stack.set(X, 101_v); + stack.set(Y, 201_v); // Push another tunnel - stack.push_frame(505, frame_type::tunnel); + stack.push_frame(505); // Push some more temps - stack.set(X, 102); - stack.set(Y, 202); + stack.set(X, 102_v); + stack.set(Y, 202_v); WHEN("another thread is started and completed on top of it") { @@ -347,16 +366,20 @@ SCENARIO("threading with the callstack", "[callstack]") THEN("we should have access to the original variables") { - REQUIRE(*stack.get(X) == value(100)); - REQUIRE(*stack.get(Y) == value(200)); + REQUIRE(stack.get(X)->type() == value_type::int32); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->type() == value_type::int32); + REQUIRE(stack.get(Y)->get() == 200); } THEN("collapsing to the thread should also get us the original variables") { stack.complete_thread(thread); stack.collapse_to_thread(thread); - REQUIRE(*stack.get(X) == value(100)); - REQUIRE(*stack.get(Y) == value(200)); + REQUIRE(stack.get(X)->type() == value_type::int32); + REQUIRE(stack.get(X)->get() == 100); + REQUIRE(stack.get(Y)->type() == value_type::int32); + REQUIRE(stack.get(Y)->get() == 200); } } } diff --git a/inkcpp_test/Value.cpp b/inkcpp_test/Value.cpp index e1afb2fd..d6917d8c 100644 --- a/inkcpp_test/Value.cpp +++ b/inkcpp_test/Value.cpp @@ -1,13 +1,17 @@ #include "catch.hpp" -#include "../inkcpp/value.h" #include "../inkcpp/string_table.h" #include "../inkcpp/output.h" +#include "../inkcpp/executioner.h" +#include "../shared/private/command.h" -using value = ink::runtime::internal::value; -using data_type = ink::runtime::internal::data_type; -using string_table = ink::runtime::internal::string_table; +using ink::runtime::internal::value; +using ink::runtime::internal::value_type; +using ink::runtime::internal::string_table; using stream = ink::runtime::internal::stream<128>; +using ink::runtime::internal::executer; +using eval_stack = ink::runtime::internal::eval_stack; +using ink::Command; void cp_str(char* dst, const char* src) { while(*src) { *dst++ = *src++; } @@ -16,6 +20,9 @@ void cp_str(char* dst, const char* src) { SCENARIO("compare concatenated values") { + string_table str_table; + executer ops(str_table); + eval_stack stack; GIVEN("just single strings") { const char str_1[] = "Hello World!"; @@ -23,30 +30,31 @@ SCENARIO("compare concatenated values") const char str_2[] = "Bye World!"; WHEN("equal") { - value v1(str_1); - value v2(str_1_again); - value res = v1 == v2; + stack.push(value{}.set(str_1)); + stack.push(value{}.set(str_1_again)); + ops(Command::IS_EQUAL, stack); + value res = stack.pop(); THEN("== results in true") { - REQUIRE(res.get_data_type() == data_type::int32); - REQUIRE(res.get() == 1); + REQUIRE(res.type() == value_type::boolean); + REQUIRE(res.get() == true); } } WHEN("not equal") { - value v1(str_1); - value v2(str_2); - value res = v1 == v2; + stack.push(value{}.set(str_1)); + stack.push(value{}.set(str_2)); + ops(Command::IS_EQUAL, stack); + value res = stack.pop(); THEN("== results in false") { - REQUIRE(res.get_data_type() == data_type::int32); - REQUIRE(res.get() == 0); + REQUIRE(res.type() == value_type::boolean); + REQUIRE(res.get() == false); } } } GIVEN("string and numbers") { - string_table str_table; stream out{}; char* str_hello = str_table.create(6); cp_str(str_hello, "hello"); @@ -62,38 +70,117 @@ SCENARIO("compare concatenated values") int int_45 = 45; WHEN("concatenated string representation matches (2 fields)") { - value v1 = value::add(value(int_4), value(str_5hello), out, str_table); - value v2 = value::add(value(int_45), value(str_hello), out, str_table); - value res = v1 == v2; + stack.push(value{}.set(int_4)); + stack.push(value{}.set(str_5hello)); + ops(Command::ADD, stack); + stack.push(value{}.set(int_45)); + stack.push(value{}.set(str_hello)); + ops(Command::ADD, stack); + ops(Command::IS_EQUAL, stack); + value res = stack.pop(); THEN("== returns true") { - REQUIRE(res.get_data_type() == data_type::int32); - REQUIRE(res.get() == 1); + REQUIRE(res.type() == value_type::boolean); + REQUIRE(res.get() == true); } } WHEN("concatenated string representation match (many fields)") { - value v1 = value(str_4); + stack.push(value{}.set(str_4)); for (int i = 0; i < 31; ++i) { - v1 = value::add(v1, value(int_4), out, str_table); + stack.push(value{}.set(int_4)); + ops(Command::ADD, stack); } - value v2 = value(str_32_4); - value res = v1 == v2; + stack.push(value{}.set(str_32_4)); + ops(Command::IS_EQUAL, stack); + value res = stack.pop(); THEN("== results true") { - REQUIRE(res.get_data_type() == data_type::int32); - REQUIRE(res.get() == 1); + REQUIRE(res.type() == value_type::boolean); + REQUIRE(res.get() == true); } } WHEN("concatenated string representation won't match") { - value v1 = value::add(value(int_45), value(str_5hello), out, str_table); - value v2 = value::add(value(int_4),value(str_hello), out, str_table); - value res = v1 == v2; + stack.push(value{}.set(int_45)); + stack.push(value{}.set(str_5hello)); + ops(Command::ADD, stack); + stack.push(value{}.set(int_4)); + stack.push(value{}.set(str_hello)); + ops(Command::ADD, stack); + ops(Command::IS_EQUAL, stack); + value res = stack.pop(); THEN("== returns false") { - REQUIRE(res.get_data_type() == data_type::int32); - REQUIRE(res.get() == 0); + REQUIRE(res.type() == value_type::boolean); + REQUIRE(res.get() == false); + } + } + } + GIVEN("numbers") + { + int i5 = 5; + int i8 = 8; + float f5 = 5.f; + WHEN("numbers are same") + { + stack.push(value{}.set(i8)); + stack.push(value{}.set(i8)); + ops(Command::IS_EQUAL, stack); + value res1 = stack.pop(); + stack.push(value{}.set(f5)); + stack.push(value{}.set(f5)); + ops(Command::IS_EQUAL, stack); + value res2 = stack.pop(); + THEN("== returns true") + { + REQUIRE(res1.type() == value_type::boolean); + REQUIRE(res1.get() == true); + REQUIRE(res2.type() == value_type::boolean); + REQUIRE(res2.get() == true); + } + } + WHEN("numbers equal, but different encoding") + { + stack.push(value{}.set(i5)); + stack.push(value{}.set(f5)); + ops(Command::IS_EQUAL, stack); + value res = stack.pop(); + THEN("== returns true") + { + REQUIRE(res.type() == value_type::boolean); + REQUIRE(res.get() == true); + } + } + WHEN("numbers value and encoding differs") + { + stack.push(value{}.set(f5)); + stack.push(value{}.set(i8)); + ops(Command::IS_EQUAL, stack); + value res = stack.pop(); + THEN("== returns false") + { + REQUIRE(res.type() == value_type::boolean); + REQUIRE(res.get() == false); + } + } + WHEN("calculate with float and int (5.,8)") + { + stack.push(value{}.set(f5)); + stack.push(value{}.set(i8)); + THEN("adding results 13.") + { + ops(Command::ADD, stack); + value res = stack.pop(); + REQUIRE(res.type() == value_type::float32); + REQUIRE(res.get() == 13.f); + } + THEN("dividing results in 0.625") + { + ops(Command::DIVIDE, stack); + value res = stack.pop(); + REQUIRE(res.type() == value_type::float32); + REQUIRE(res.get() == 0.625f); } } } diff --git a/notes/OperationNotes.md b/notes/OperationNotes.md new file mode 100644 index 00000000..2966a494 --- /dev/null +++ b/notes/OperationNotes.md @@ -0,0 +1,35 @@ +Operations are everything which works only on the evaluation stack! +(eg. ADD, NOT, EQUAL, etc.) + +To use them include `executioner.h`, this will take care of including the other +needed header in the correct order. + +The `executioner` need to instantiation time all resources needed for all +supported operations (a string_table for now). + +Then you can execute a command with the call operator of the `executioner`. +`void operator()(Command, eval_stack&)` + +The executioner then iterates through a compile time created and optimization +list of all types. To determine how many arguments needed +(with `size_t command_num_args(Command)`) and then finds the matching operator +for the value type. + +Type casting is solved with a cast matrix, where each entry is defined with: +`template<> constexpr value_type cast = value_type` +`template<> constexpr value_type cast = resulting_type` + +! At the moment we must ensure that `(int)(t1) < (int)(t2)`! + +The gain: + +* Operation handling can be separated in different files + (`numeric_operations.h, string_operations.h`). The value class only knows what + data it contains, and not how to handle it => adding new types or operators + is know possible without changing many `switch cases`. +* Values are acquired with `get()` witch allows changing type + without breaking unnoticed code. +* Not one huge file. +* The `executioner` knows the resources for the operations + => no need to pass a string_table to each add. +* everything is without virtualisation => no/negligible runtime overhead. diff --git a/shared/private/command.h b/shared/private/command.h index a164be23..d93a4154 100644 --- a/shared/private/command.h +++ b/shared/private/command.h @@ -53,7 +53,8 @@ namespace ink THREAD, // == Binary operators - BINARY_OPERATORS_START, + OP_BEGIN, + BINARY_OPERATORS_START = OP_BEGIN, ADD = BINARY_OPERATORS_START, SUBTRACT, DIVIDE, @@ -76,9 +77,10 @@ namespace ink NOT = UNARY_OPERATORS_START, NEGATE, UNARY_OPERATORS_END = NEGATE, + OP_END = NEGATE + 1, // == Container tracking - START_CONTAINER_MARKER, + START_CONTAINER_MARKER = OP_END, END_CONTAINER_MARKER, // == Function calls diff --git a/shared/public/system.h b/shared/public/system.h index 0a7d6a5c..6838c4a5 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -117,12 +117,18 @@ namespace ink }; #endif - namespace runtime::internal - { + namespace runtime::internal { + template + struct always_false { + static constexpr bool value = false; + }; + template + struct enable_if {}; template - struct always_false { static constexpr bool value = false; }; + struct enable_if { using type = T; }; } + #ifdef INK_ENABLE_STL template using optional = std::optional;