/* Copyright 2019-2022 René Ferdinand Rivera Morell Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt) */ #include "bindjam.h" #include "class.h" #include "function.h" #include "hash.h" #include "lists.h" #include "modules.h" #include "mp.h" #include "native.h" #include "optval.h" #include "output.h" #include "parse.h" #include "rules.h" #include "types.h" #include "value.h" #include "variable.h" #include "mod_args.h" #include "mod_command_db.h" #include "mod_db.h" #include "mod_jam_builtin.h" #include "mod_jam_class.h" #include "mod_jam_errors.h" #include "mod_jam_modules.h" #include "mod_path.h" #include "mod_regex.h" #include "mod_set.h" #include "mod_string.h" #include "mod_sysinfo.h" #include "mod_version.h" #include #include #include #include #include #include namespace b2 { namespace jam { namespace { inline const char * mod_cstr(const std::string & n) { return n.empty() ? nullptr : n.c_str(); } inline std::string mod_str(const char * n) { return (n == nullptr) ? "" : n; } inline module_ptr mod(const char * name) { if (name == nullptr || name[0] == '\0') return root_module(); return bindmodule(value_ref(name)); } inline module_ptr mod(const std::string & name) { return mod(mod_cstr(name)); } } // namespace /* Basic core types to marshal.. */ template <> string_t from_jam(value_ptr jv) { return object_str(jv); } template <> value_ptr to_jam(string_t v) { return object_new(v.c_str()); } template <> uint_t from_jam(value_ptr jv) { return std::stoul(from_jam(jv)); } template <> value_ptr to_jam(uint_t v) { return to_jam(std::to_string(v)); } template <> int_t from_jam(value_ptr jv) { return std::stoi(from_jam(jv)); } template <> value_ptr to_jam(int_t v) { return to_jam(std::to_string(v)); } template <> bool from_jam(value_ptr jv) { return object_str(jv) != nullptr && object_str(jv)[0] != '\0'; } template <> value_ptr to_jam(bool v) { return object_new(v ? "1" : ""); } template <> value_ref from_jam(value_ptr v) { return value_ref(v); } // General marshaling of one jam value list. Default converts the first item // to/from the list. template struct converter { static list_ref to_bind_value(const CxxValue & cxx_value) { return list_ref(to_jam(cxx_value)); } static CxxValue from_bind_value( list_cref::iterator & i, list_cref::iterator e) { return (i == e) ? CxxValue() : from_jam(*(i++)); } }; // Marshaling specialization for vector container. // TODO: Generalize to more containers. template struct converter> { using CxxValue = std::vector; static list_ref to_bind_value(const CxxValue & cxx_value) { list_ref result; for (auto & v : cxx_value) result.push_back(to_jam(v)); return result; } static CxxValue from_bind_value( list_cref::iterator & i, list_cref::iterator e) { CxxValue result; for (; i != e; ++i) result.emplace_back(from_jam(*i)); return result; } }; // Marshaling for optval container. template struct converter> { using CxxValue = optval; static list_ref to_bind_value(const CxxValue & cxx_value) { list_ref result; if (cxx_value.has_value()) result.push_back(to_jam(static_cast(cxx_value))); return result; } static CxxValue from_bind_value( list_cref::iterator & i, list_cref::iterator e) { return (i == e) ? CxxValue() : from_jam(*(i++)); } }; // template <> struct converter { static list_ref to_bind_value(const list_ref & cxx_value) { return cxx_value; } static list_ref from_bind_value( list_cref::iterator & i, list_cref::iterator e) { list_ref result(i, e); i = e; return result; } }; // Marshaling for value_ref container. template <> struct converter { static list_ref to_bind_value(const value_ref & cxx_value) { list_ref result; if (cxx_value.has_value()) result.push_back(cxx_value); return result; } static value_ref from_bind_value( list_cref::iterator & i, list_cref::iterator e) { return (i == e) ? value_ref() : value_ref(*(i++)); } }; // Marshaling of tuples as multi-param arguments. template struct converter> { using CxxValue = std::tuple; static list_ref to_bind_value(const CxxValue & cxx_value) { return to_bind_value(cxx_value, mp::make_index_sequence::value> {}); } template static list_ref to_bind_value( const CxxValue & cxx_value, mp::index_sequence) { using std::get; value_ptr jam_val[] { nullptr, to_jam(get(cxx_value))... }; list_ref result; for (int i = 1; i <= std::tuple_size::value; ++i) result.push_back(jam_val[i]); return result; } static CxxValue from_bind_value( list_cref::iterator & i, list_cref::iterator e) { return from_bind_value( i, e, mp::make_index_sequence::value> {}); } template static CxxValue from_bind_value(list_cref::iterator & i, list_cref::iterator e, mp::index_sequence) { CxxValue result; int _[] { 0, ((void)(std::get(result) = converter::type>::from_bind_value(i, e)), 0)... }; (void)_; return result; } }; }} // namespace b2::jam namespace b2 { namespace bind { using namespace b2::jam; // Direct, marshalling for list_cref container. template <> struct converter_ { static LIST * to_bind_value(const list_cref & cpp_value) { return *cpp_value; } static list_cref from_bind_value(LIST * jam_value) { return list_cref(jam_value); } }; // Direct, marshalling for list_ref container. template <> struct converter_ { static LIST * to_bind_value(const list_ref & cpp_value) { return list_ref(cpp_value).release(); } static list_ref from_bind_value(LIST * jam_value) { // TODO: Can we optimize this to not do a list copy? return list_ref(jam_value); } }; template struct converter_ { static LIST * to_bind_value(const CxxValue & cxx_value) { return b2::jam::converter::to_bind_value(cxx_value).release(); } static CxxValue from_bind_value(LIST * jam_value) { list_cref v(jam_value); auto i = v.begin(); auto e = v.end(); return b2::jam::converter::from_bind_value(i, e); } }; }} // namespace b2::bind namespace b2 { namespace jam { struct jam_cxx_self { // Get the hidden C++ self instance for the jam class instance. template static Class * get(FRAME * frame) { value_ref cxx_self_name(value::make("__cxx_self__")); value_ref cxx_self_value( list_front(var_get(frame->module, cxx_self_name))); Class * self = static_cast(cxx_self_value->as_object()); return self; } // Set the hidden C++ self instance for the jam class instance. template static void set(FRAME * frame, Class * self) { value_ref cxx_self_name(value::make("__cxx_self__")); value_ref cxx_self_value(value::make(self)); var_set( frame->module, cxx_self_name, list_new(cxx_self_value), VAR_SET); } }; // Marshals arguments from Jam function FRAME to C++ tuple.. template struct jam_marshal_arg { template static void convert(ArgsTuple & to_args, jam_context & context) { LIST * arg_value = lol_get(context.frame->args, N); if (arg_value != L0) std::get(to_args) = jam_binder::convert_from_bind_value(arg_value); } }; template struct jam_marshal_arg { template static void convert(ArgsTuple & to_args, jam_context & context) { std::get(to_args) = bind::context_ref_(context); } }; template struct jam_marshal_arg { template static void convert(ArgsTuple & to_args, jam_context & context) { lists & to_arg = std::get(to_args); for (int32_t i = N; i < context.frame->args->count; ++i) to_arg | lol_get(context.frame->args, i); } }; template struct jam_marshal_args { static void convert(ArgsTuple & to_args, jam_context & context) {} }; template struct jam_marshal_args::value)>::type> { static void convert(ArgsTuple & to_args, jam_context & context) { using arg_type = typename std::tuple_element::type; jam_marshal_arg::convert(to_args, context); if (N < std::tuple_size::value) { jam_marshal_args::convert(to_args, context); } } }; // Bound init/constructor function that forwards from jam __init__ to C++. template static LIST * jam_call_init( FRAME * frame, int flags, Call cxx_call, Class * (*)(Args...)) { using namespace mp; try { // Marshal arguments from frame->args. using ArgsTuple = std::tuple::type...>; ArgsTuple args; jam_context context { frame }; jam_marshal_args::convert(args, context); // Construct the class instance, and set the hidden jam instance var // to keep track of it. Class * self = invoke(cxx_call, args, make_index_sequence::value> {}); jam_cxx_self::set(frame, self); // Nothing to return from an init. return L0; } catch (const std::exception &) { return L0; } } // Bound plain function that forwards from jam to C++ with a return // converted back to jam. template ::value, int>::type = 0> static LIST * jam_call_method( FRAME * frame, int flags, Call cxx_call, Return (*)(Class *, Args...)) { using namespace mp; try { // Marshal arguments from frame->args. using ArgsTuple = std::tuple::type...>; ArgsTuple args; jam_context context { frame }; jam_marshal_args::convert(args, context); // Invoke call, and return marshaled result. return jam_binder::convert_to_bind_value( invoke(cxx_call, std::tuple_cat( std::make_tuple(jam_cxx_self::get(frame)), args), make_index_sequence<1 + std::tuple_size::value> {})); } catch (const std::exception &) { return L0; } } // Bound plain function that forwards from jam to C++ with a void return. template ::value, int>::type = 0> static LIST * jam_call_method( FRAME * frame, int flags, Call cxx_call, Return (*)(Class *, Args...)) { using namespace mp; try { // Marshal arguments from frame->args. using ArgsTuple = std::tuple::type...>; ArgsTuple args; jam_context context { frame }; jam_marshal_args::convert(args, context); // Invoke call, and return marshaled result. invoke(cxx_call, std::tuple_cat( std::make_tuple(jam_cxx_self::get(frame)), args), make_index_sequence<1 + std::tuple_size::value> {}); return L0; } catch (const std::exception &) { return L0; } } // Bound plain function... template ::value, int>::type = 0> static LIST * jam_call_function( FRAME * frame, int flags, Call cxx_call, Return (*)(Args...)) { using namespace mp; try { // Marshal arguments from frame->args. using ArgsTuple = std::tuple::type...>; ArgsTuple args; jam_context context { frame }; jam_marshal_args::convert(args, context); // Invoke call, and return marshaled result. return jam_binder::convert_to_bind_value( invoke(cxx_call, args, make_index_sequence::value> {})); } catch (const std::exception &) { return L0; } } template ::value, int>::type = 0> static LIST * jam_call_function( FRAME * frame, int flags, Call cxx_call, Return (*)(Args...)) { using namespace mp; try { // Marshal arguments from frame->args. using ArgsTuple = std::tuple::type...>; ArgsTuple args; jam_context context { frame }; jam_marshal_args::convert(args, context); // Invoke call, and return marshaled result. invoke(cxx_call, args, make_index_sequence::value> {}); return L0; } catch (const std::exception &) { return L0; } } template struct jam_arg_spec_count_sum {}; template <> struct jam_arg_spec_count_sum<> { // static constexpr std::size_t value = 0; enum : std::size_t { value = 0 }; }; template struct jam_arg_spec_count_sum { // static constexpr std::size_t value // = X::count + jam_arg_spec_count_sum::value; enum : std::size_t { value = X::count + jam_arg_spec_count_sum::value }; }; template struct jam_arg_spec_max_size { // static constexpr std::size_t value // = ::b2::bind::args_::count // + jam_arg_spec_count_sum::value*2 // + 2; enum : std::size_t { value = ::b2::bind::args_::count + jam_arg_spec_count_sum::value * 2 + 2 }; }; // Builds the jam argument specifier for a rule from the bind arguments // specification. template struct jam_arg_spec { using args_t = ::b2::bind::args_; // The jam specification, terminated by a `nullptr`. Note, the `spec` will // be larger than the actual specification. Hence use the `count` if one // needs to know the range. const char * spec[jam_arg_spec_max_size::value]; // The number of strings in the specification, exclusive of the terminating // n`nullptr`. int count; jam_arg_spec(const args_t & args) : jam_arg_spec(args, mp::make_index_sequence {}) {} template jam_arg_spec(const args_t & args, mp::index_sequence) { count = 0; using std::get; #if 0 bool _[]{ false, append_arg_list_to_spec(get(args.arg))... }; #endif if (count == 0) spec[count++] = "*"; spec[count] = nullptr; } template bool append_arg_list_to_spec(const ::b2::bind::arg_ & arg) { if (count > 0) spec[count++] = ":"; for (int i = 0; i < C; ++i) { if (arg.args[i].count == ::b2::bind::param_::rest) { spec[count++] = "*"; break; } spec[count++] = arg.args[i].name; switch (arg.args[i].count) { case ::b2::bind::param_::any: spec[count++] = "*"; break; case ::b2::bind::param_::many: spec[count++] = "+"; break; case ::b2::bind::param_::optional: spec[count++] = "?"; break; } } return true; } }; template void jam_native_bind(const string_t & module_name, const string_t & rule_name, ArgSpec & arg_spec, function_builtin_t native_rule, Return (*)(Args...)) { value_ptr rule_name_obj = object_new(rule_name.c_str()); module_t * module = mod(module_name); int found = 0; RULE * const rule = (RULE *)hash_insert(demand_rules(module), rule_name_obj, &found); rule->name = object_copy(rule_name_obj); rule->procedure = 0; rule->module = module; rule->actions = 0; rule->exported = 0; // Register the function as a native jam rule. declare_native_rule(mod_cstr(module_name), rule_name.c_str(), arg_spec.spec, native_rule, 0); // Note, we don't check results of not finding the existing native rule, // for the obvious reason that we just created it a few lines above. native_rule_t * np = (native_rule_t *)hash_find(module->native_rules, rule_name_obj); // Set a global name for the native rule. function_set_rulename( np->procedure, object_new((module_name + "." + rule_name).c_str())); // Define the native rule in the class module. new_rule_body(module, np->name, np->procedure, 1); object_free(rule_name_obj); } template void jam_bind(const string_t & module_name, const string_t & rule_name, ArgSpec & arg_spec, Return (Class::*method)(Args...)) { // out_printf( "jam_bind: %s.%s.\n", module_name.c_str(), rule_name.c_str() // ); jam_native_bind( module_name, rule_name, arg_spec, [method](FRAME * frame, int flags) -> LIST * { return jam_call_method( frame, flags, [method](Class * self, Args... args) -> Return { return (self->*method)(args...); }, static_cast(nullptr)); }, static_cast(nullptr)); } template void jam_bind(const string_t & module_name, const string_t & rule_name, ArgSpec & arg_spec, Return (Class::*method)(Args...) const) { // out_printf( "jam_bind: %s.%s.\n", module_name.c_str(), rule_name.c_str() // ); jam_native_bind( module_name, rule_name, arg_spec, [method](FRAME * frame, int flags) -> LIST * { return jam_call_method( frame, flags, [method](const Class * self, Args... args) -> Return { return (self->*method)(args...); }, static_cast(nullptr)); }, static_cast(nullptr)); } template void jam_bind(const string_t & module_name, const string_t & rule_name, ArgSpec & arg_spec, Return (*function)(Args...)) { jam_native_bind( module_name, rule_name, arg_spec, [function](FRAME * frame, int flags) -> LIST * { return jam_call_function( frame, flags, [function]( Args... args) -> Return { return (*function)(args...); }, static_cast(nullptr)); }, static_cast(nullptr)); } template void jam_bind(const string_t & module_name, const string_t & rule_name, ArgSpec & arg_spec, Class *, ::b2::bind::init_) { // out_printf( "jam_bind: %s.%s.\n", module_name.c_str(), rule_name.c_str() // ); jam_native_bind( module_name, rule_name, arg_spec, [module_name, rule_name](FRAME * frame, int flags) -> LIST * { return jam_call_init( frame, flags, [](Args... args) -> Class * { return new Class(args...); }, static_cast(nullptr)); }, (void (*)(Args...)) nullptr); } void jam_binder::bind_module(const char * module_name) { mod(module_name); } template void jam_binder::bind_class(const char * module_name, const char * class_name, ::b2::bind::type_, ::b2::bind::type_...) { value_ptr class_name_obj = object_new(class_name); LIST * class_name_list = list_new(class_name_obj); value_ptr bases_name_obj[] = { object_new(::b2::bind::class_::name())..., nullptr }; LIST * bases_name_list = L0; for (value_ptr base_name_obj : bases_name_obj) { if (base_name_obj) bases_name_list = list_push_back(bases_name_list, base_name_obj); } /* value_ptr class_module = */ make_class_module( class_name_list, bases_name_list); object_free(class_name_obj); for (value_ptr base_name_obj : bases_name_obj) { if (base_name_obj) object_free(base_name_obj); } list_free(bases_name_list); } template void jam_binder::bind_method(const char * module_name, const char * class_name, const char * method_name, ::b2::bind::args_ args, Function f) { jam_arg_spec arg_spec { args }; jam_bind(string_t("class@") + class_name, method_name, arg_spec, f); } template void jam_binder::bind_init(const char * module_name, const char * class_name, Class * c, Init i, ::b2::bind::args_ args) { jam_arg_spec arg_spec { args }; jam_bind(string_t("class@") + class_name, "__init__", arg_spec, c, i); } template void jam_binder::bind_function(const char * module_name, const char * function_name, ::b2::bind::args_ args, Function f) { jam_arg_spec arg_spec { args }; jam_bind(mod_str(module_name), function_name, arg_spec, f); } void jam_binder::eval_data(const char * module_name, const char * data) { b2::jam::module_scope scope( context_ref.get().frame, module_name); parse_buffer(scope.name(), data, scope.frame()); } void jam_binder::set_loaded(const char * module_name) { b2::jam::variable modules_loaded("modules", ".loaded"); modules_loaded += module_name; } template <> void bind_jam(FRAME * f) { jam_context context { f }; jam_binder binder; binder.context_ref = context; binder // These have to be done in dependency order so that the init code // each executes is valid. .bind(jam::modules::modules_module()) .bind(jam::builtin::root_module()) .bind(jam::klass::class_module()) .bind(jam::errors::errors_module()) .bind(paths::paths_module()) .bind(regex_module()) .bind(set_module()) .bind(string_module()) .bind(sysinfo_module()) .bind(version_module()) .bind(db_module()) .bind(command_db_module()) .bind(b2::args::args_module()); } }} // namespace b2::jam