#include #include #include #include #include #include #include #include #include #include #include #include using namespace Rice; using runner = ink::runtime::runner_interface; using runner_ptr = ink::runtime::runner; using globals = ink::runtime::globals_interface; using globals_ptr = ink::runtime::globals; using story = ink::runtime::story; using story_ptr = ink::runtime::story_ptr; using choice = ink::runtime::choice; using value = ink::runtime::value; using list = ink::runtime::list_interface; using snapshot = ink::runtime::snapshot; //TODO: probably should be a shared pointer / smart pointer using bound_func = std::function&)>; namespace Rice::detail { template<> struct Type { static bool verify() { return true; } }; template<> class From_Ruby { public: From_Ruby() = default; explicit From_Ruby(Arg* arg) : arg_(arg) { } bool is_convertible(VALUE value) { return rb_obj_is_proc(value) == RUBY_Qtrue; } bound_func* convert(VALUE lambda) { VALUE check = detail::protect(rb_obj_is_proc, lambda); if(check != RUBY_Qtrue){ throw std::runtime_error("Object is not convertible"); } bound_func * result = new bound_func([lambda](std::vector & args) { detail::To_Ruby to_convertor; VALUE ary = rb_ary_new2(args.size()); for(value v: args) { rb_ary_push(ary, to_convertor.convert(v)); } VALUE v = rb_proc_call(lambda, ary); detail::From_Ruby from_convertor; switch(TYPE(v)) { case T_FIXNUM: return value((int32_t)NUM2INT(v)); case T_FLOAT: return value((float)RFLOAT_VALUE(v)); case T_STRING: return value(StringValueCStr(v)); case T_OBJECT: if(from_convertor.is_convertible(v)) { value result = from_convertor.convert(v); return result; } else { const char * str = rb_string_value_cstr(&v); if(str != NULL) { return value(str); } else { return value(RB_TEST(v)); } } } throw std::runtime_error("Result must be a valid ink value type"); }); return result; } private: Arg* arg_ = nullptr; }; } std::string list_to_str(const list& list) { std::stringstream out; out << "["; bool first = true; for (const auto& flag : list) { if (first) { first = false; } else { out << ", "; } out << flag; } out << "]"; return out.str(); } extern "C" void Init_inkcpp_rb() { Rice::Module rb_mInk = define_module("Ink"); rb_mInk.const_set(Rice::Identifier("VERSION"), Rice::String("0.1.0")); Data_Type rb_cCompilationResults = define_class_under(rb_mInk, "CompilationResults") .define_method("warnings", [](ink::compiler::compilation_results &results) { return results.warnings; }, Return().takeOwnership()) .define_method("errors", [](ink::compiler::compilation_results &results) { return results.errors; }, Return().takeOwnership()); rb_mInk.define_singleton_function("compile_file", [](std::string &in_file, std::string &out_file) { ink::compiler::compilation_results * results = new ink::compiler::compilation_results(); ink::compiler::run(in_file.c_str(), out_file.c_str(), results); return results; }, Return().takeOwnership()) .define_singleton_function("compile_json", [](std::string &in_stream) { std::istringstream istream(in_stream); std::ostringstream ostream; ink::compiler::compilation_results * results = new ink::compiler::compilation_results(); ink::compiler::run(istream, ostream, results); delete results; return ostream.str(); }, Return().takeOwnership()); Data_Type rb_cChoice = define_class_under(rb_mInk, "Choice") .define_method("index", &choice::index) .define_method("text", &choice::text) .define_method("has_tags", &choice::has_tags) .define_method("num_tags", &choice::num_tags) .define_method("get_tag", &choice::get_tag); Data_Type rb_cSnapshot = define_class_under(rb_mInk, "Snapshot") .define_singleton_method("from_binary", [](const std::string & data){ return snapshot::from_binary((const unsigned char *)data.c_str(), data.length(), false); }, Return().takeOwnership()) .define_singleton_method("from_file", &snapshot::from_file, Return().takeOwnership()) .define_method("get_data", &snapshot::get_data) .define_method("get_data_len", &snapshot::get_data_len) .define_method("num_runners", &snapshot::num_runners) .define_method("write_to_file", &snapshot::write_to_file); Data_Type rb_cRunner = define_class_under(rb_mInk, "Runner") .define_method("set_rng_seed", &runner::set_rng_seed) .define_method("can_continue", &runner::can_continue) .define_method("choose", &runner::choose) .define_method("has_tags", &runner::has_tags) .define_method("num_tags", &runner::num_tags) .define_method("num_choices", &runner::num_choices) .define_method("get_tag", &runner::get_tag) .define_method("move_to", [](runner &self, std::string &path) { return self.move_to(ink::hash_string(path.c_str())); }) .define_method("getline", [](runner &self) { return self.getline(); }, Return().takeOwnership()) .define_method("create_snapshot", &runner::create_snapshot, Return().takeOwnership()) .define_method("getall", [](runner &self){ return self.getall(); }, Return().takeOwnership()) .define_iterator(&runner::begin, &runner::end, "each_choice") .define_method("get_choice", &runner::get_choice) .define_method("bind_void", [](runner &self, std::string &function_name, bound_func * callback, bool lookahead_safe){ self.bind(function_name.c_str(), [callback](size_t len, const value* vals) { std::vector args(vals, vals + len); return (*callback)(args); }, lookahead_safe); }) .define_method("bind", [](runner &self, std::string &function_name, bound_func * callback, bool lookahead_safe){ self.bind(function_name.c_str(), [callback](size_t len, const value* vals) { std::vector args(vals, vals + len); return (*callback)(args); }, lookahead_safe); }, Arg("function_name"), Arg("callback"), Arg("lookahead_safe") = false); Data_Type rb_cRunnerImpl = define_class_under(rb_mInk, "RunnerImpl") .define_method("get", [](runner_ptr &self) { return self.get(); }); Data_Type rb_cFlag = define_class_under(rb_mInk, "Flag") .define_attr("name", &list::iterator::Flag::flag_name, AttrAccess::Read) .define_attr("list_name", &list::iterator::Flag::list_name, AttrAccess::Read); list::iterator (list::*begin_ptr)() const = &list::begin; list::iterator (list::*end_ptr)() const = &list::end; Data_Type rb_cList = define_class_under(rb_mInk, "List") .define_method("add", &list::add) .define_method("remove", &list::remove) .define_method("contains", &list::contains) .define_method("to_s", &list_to_str, Return().takeOwnership()) .define_method("flags_from", [](list &self, std::string & list_name) { std::vector result; for (list::iterator it = self.begin(list_name.c_str()); it != self.end(); ++it) { result.push_back(*it); } return result; }, Return().takeOwnership()) .define_method("to_vec", [](list &self) { std::vector result; for (const auto& flag : self) { result.push_back(flag); } return result; }, Return().takeOwnership()); // Include enumerable support Enum rb_cValueType = define_enum("ValueType", rb_mInk) .define_value("INT32", value::Type::Int32) .define_value("FLOAT", value::Type::Float) .define_value("UINT32", value::Type::Uint32) .define_value("STRING", value::Type::String) .define_value("LIST", value::Type::List) .define_value("BOOL", value::Type::Bool); Data_Type rb_cValue = define_class_under(rb_mInk, "Value") .define_singleton_function("from_int", [](int32_t num){ return value(num); }) .define_singleton_function("from_bool", [](bool b){ return value(b); }) .define_singleton_function("from_float", [](float f) { return value(f); }) .define_singleton_function("from_string", [](std::string & s) { return value(s.c_str()); }) .define_singleton_function("from_uint", [](uint32_t num) { return value(num); }) .define_singleton_function("from_list", [](list & l) { return value(&l); }) .define_singleton_function("from_value", [](value & v) { return value(v); }) .define_attr("type", &value::type, Rice::AttrAccess::Read) .define_method("to_s", [](value &self) { switch(self.type) { case value::Type::Bool: return std::string(self.get() ? "true" : "false"); case value::Type::Uint32: return std::to_string(self.get()); case value::Type::Int32: return std::to_string(self.get()); case value::Type::String: return std::string(self.get()); case value::Type::Float: return std::to_string(self.get()); case value::Type::List: { return list_to_str(*self.get()); } } throw std::runtime_error("value is in an invalid state"); }) .define_method("to_i", [](value &self) { switch(self.type) { case value::Type::Bool: return int64_t(self.get() ? 1 : 0); case value::Type::Uint32: return int64_t(self.get()); case value::Type::Int32: return int64_t(self.get()); case value::Type::String: return (int64_t) 0; case value::Type::Float: return int64_t (round(self.get())); case value::Type::List: return (int64_t) 0; } throw std::runtime_error("value is in an invalid state"); }) .define_method("to_f", [](value &self) { switch(self.type) { case value::Type::Bool: return double(self.get() ? 1 : 0); case value::Type::Uint32: return double(self.get()); case value::Type::Int32: return double(self.get()); case value::Type::String: return (double) 0; case value::Type::Float: return double (self.get()); case value::Type::List: return (double) 0; } throw std::runtime_error("value is in an invalid state"); }) .define_method("to_list", [](value &self) { if(self.type != value::Type::List) { throw std::runtime_error("Not a list"); } return self.get(); }); Data_Type rb_cGlobals = define_class_under(rb_mInk, "Globals") .define_method("[]", [](globals &self, std::string &key) { const char * k = key.c_str(); auto res = self.get(k); if(!res.has_value()) { throw std::runtime_error("No value"); } return res.value(); }) .define_method("[]=", [](globals &self, std::string &key, value &value) { const char * k = key.c_str(); if(!self.set(k, value)) { throw std::runtime_error("Could not set"); } }); Data_Type rb_cStoryImpl = define_class_under(rb_mInk, "StoryImpl") .define_method("get", [](story_ptr &self) { return self.get(); }); Data_Type rb_cStory = define_class_under(rb_mInk, "Story") .define_singleton_function("from_file", &story::from_file, Return().takeOwnership()) .define_singleton_function("from_binary", [](std::string &data) { return story::from_binary((unsigned char *) data.c_str(), data.length(), false); }, Return().takeOwnership()) .define_method("new_globals", &story::new_globals, Return().takeOwnership()) .define_method("new_runner", &story::new_runner, Arg("globals") = (story_ptr) nullptr, Return().takeOwnership()) .define_method("new_globals_from_snapshot", &story::new_globals_from_snapshot, Return().takeOwnership()) .define_method("new_runner_from_snapshot", &story::new_runner_from_snapshot, Arg("snapshot"), Arg("globals") = (story_ptr) nullptr, Arg("idx"), Return().takeOwnership()); }