mysqlshdk/scripting/types_cpp.cc (955 lines of code) (raw):
/*
* Copyright (c) 2014, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "mysqlshdk/include/scripting/types_cpp.h"
#include <algorithm>
#include <cctype>
#include <cstdarg>
#include <limits>
#include <tuple>
#include "mysqlshdk/include/scripting/common.h"
#include "mysqlshdk/include/scripting/type_info/generic.h"
#include "mysqlshdk/include/shellcore/utils_help.h"
#include "mysqlshdk/libs/utils/log_sql.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#ifdef WIN32
#ifdef max
#undef max
#endif
using ssize_t = __int64;
#endif
// TODO(alfredo) - remove all the linear lookups on the member names
// TODO(alfredo) - clarify property mechanism
using namespace std::placeholders;
namespace shcore {
namespace {
std::tuple<bool, std::vector<std::string>> process_keyword_args(
const shcore::Cpp_function::Raw_signature &cand,
const shcore::Dictionary_t &kwds) {
std::set<std::string> kwd_names;
std::set<std::string> params;
// First of all, we ensure all the provided keywords are valid parameters
for (const auto ¶m : cand) {
params.insert(param->name);
}
for (const auto &kwd : *kwds) {
kwd_names.insert(kwd.first);
}
std::vector<std::string> diff;
std::set_difference(kwd_names.begin(), kwd_names.end(), params.begin(),
params.end(), std::inserter(diff, diff.begin()));
// There are keywords that are NOT parameter names.
// If the last parameter is a map, and there's no keyword param for it these
// options will be wrapped into a dictionary; otherwise they are considered
// invalid named parameters.
bool remaining_as_kwargs = !diff.empty() && !cand.empty() &&
cand.back()->type() == shcore::Value_type::Map &&
!kwds->has_key(cand.back()->name);
return std::make_tuple(remaining_as_kwargs, std::move(diff));
}
} // namespace
Cpp_function::Raw_signature Cpp_function::gen_signature(
const std::vector<std::pair<std::string, Value_type>> &args) {
Raw_signature sig;
for (auto &i : args) {
Param_flag flag = Param_flag::Mandatory;
std::string name = i.first;
if (i.first[0] == '?') {
name = i.first.substr(1);
flag = Param_flag::Optional;
}
sig.push_back(std::make_shared<Parameter>(name, i.second, flag));
}
return sig;
}
std::tuple<bool, int, shcore::Exception> Cpp_function::match_signatures(
const Raw_signature &cand, const std::vector<Value_type> &wanted,
const shcore::Dictionary_t &kwds) {
bool match = true;
shcore::Exception error("", 0);
bool have_object_params = false;
size_t exact_matches = 0;
// If keyword parameters are provided, function matching evaluation is done
// reviewing parameter by parameter from the function signature
if (kwds && !kwds->empty()) {
std::vector<std::string> real_kwargs;
bool support_kwargs = false;
std::tie(support_kwargs, real_kwargs) = process_keyword_args(cand, kwds);
if (!real_kwargs.empty() && !support_kwargs) {
match = false;
error = shcore::Exception::argument_error(shcore::str_format(
"Invalid keyword argument%s: %s", real_kwargs.size() == 1 ? "" : "s",
shcore::str_join(real_kwargs, ", ", [](const std::string &item) {
return "'" + item + "'";
}).c_str()));
} else {
size_t param_count = 0;
// We make sure the data for each parameter is correctly defined
for (const auto ¶m : cand) {
// For each parameter ensures:
// A positional parameter is given, or
// A named parameter is given, or
// Verify wether the parameter is optional
// If a value was given ensures it is of a compatible type
if (wanted.size() > param_count) {
// A positional parameter was given, we must ensure:
// - The given type is compatible with the expected one
// - That NO keyword has been defined for the parameter
if (param->valid_type(wanted[param_count])) {
if (kwds->find(param->name) != kwds->end()) {
match = false;
error = shcore::Exception::argument_error(
shcore::str_format("Got multiple values for argument '%s'",
param->name.c_str()));
} else if (param->type() == wanted[param_count]) {
exact_matches++;
}
have_object_params =
have_object_params || (wanted[param_count] == Object);
} else {
match = false;
error = shcore::Exception::type_error(shcore::str_format(
"Argument #%zu is expected to be %s", param_count + 1,
type_description(param->type()).c_str()));
}
} else if (kwds->has_key(param->name)) {
// A keyword parameter is given, we must ensure
// - The given type is compatible with the expected one
if (param->valid_type(kwds->get_type(param->name))) {
if (param->type() == kwds->get_type(param->name)) {
exact_matches++;
}
have_object_params =
have_object_params || (kwds->at(param->name).type == Object);
} else {
match = false;
error = shcore::Exception::type_error(shcore::str_format(
"Argument '%s' is expected to be %s", param->name.c_str(),
type_description(param->type()).c_str()));
}
// This is the last parameter, it's a map and there are dangling named
// arguments, they will be used to construct the options dictionary
} else if (param == cand.back() && support_kwargs) {
exact_matches++;
} else if (param->flag != Param_flag::Optional) {
match = false;
error = shcore::Exception::argument_error(shcore::str_format(
"Missing value for argument '%s'", param->name.c_str()));
}
param_count++;
if (!match) break;
}
}
} else {
// this function follows mostly the same rules for finding viable
// functions as c++ for function overload resolution
size_t m = wanted.size();
size_t c = cand.size();
if (m <= c) {
for (size_t index = m; index < c; index++) {
if (cand[index]->flag != Param_flag::Optional) {
match = false;
break;
}
}
} else {
match = false;
}
// In case of parameter count mistmatch, creates the appropiate error
// message
if (!match) {
size_t max = cand.size();
size_t min = 0;
for (const auto ¶m : cand) {
if (param->flag == Param_flag::Optional) {
break;
} else {
min++;
}
}
if (min == max)
error = shcore::Exception::argument_error(shcore::str_format(
"Invalid number of arguments, expected %zu but got %zu", min, m));
else
error = shcore::Exception::argument_error(shcore::str_format(
"Invalid number of arguments, expected %zu to %zu but got %zu", min,
max, m));
} else {
exact_matches = m;
// NOTE: m is 1 based (not 0 based)
for (ssize_t index = m - 1; index >= 0; index--) {
if (!cand[index]->valid_type(wanted[index])) {
match = false;
error = shcore::Exception::type_error(shcore::str_format(
"Argument #%zu is expected to be %s", index + 1,
type_description(cand[index]->type()).c_str()));
}
exact_matches -= (wanted[index] != cand[index]->type());
have_object_params = have_object_params || (wanted[index] == Object);
}
}
}
if (exact_matches == cand.size() && !have_object_params) {
return std::make_tuple(match, std::numeric_limits<int>::max(), error);
} else {
return std::make_tuple(match, have_object_params ? 0 : exact_matches,
error);
}
}
using FunctionEntry = std::pair<std::string, std::shared_ptr<Cpp_function>>;
std::map<std::string, Cpp_function::Metadata> Cpp_object_bridge::mdtable;
std::mutex Cpp_object_bridge::s_mtx;
Cpp_function::Metadata &Cpp_object_bridge::get_metadata(
const std::string &name) {
return mdtable[name];
}
void Cpp_object_bridge::set_metadata(
Cpp_function::Metadata &meta, const std::string &name, Value_type rtype,
const std::vector<std::pair<std::string, Value_type>> &ptypes,
const std::string &pcodes) {
bool found_optional = false;
// validate optional parameter sanity
// func(p1, p2=opt, p3=opt) is ok
// func(p1=opt, p2=opt, p3) is not
for (const auto &ptype : ptypes) {
if (ptype.first[0] == '?')
found_optional = true;
else if (found_optional)
throw std::logic_error(
"optional parameters have to be at the end of param list");
}
meta.set(name, rtype, ptypes, pcodes);
}
void Cpp_function::Metadata::set_name(const std::string &name_) {
auto index = name_.find("|");
if (index == std::string::npos) {
name[LowerCamelCase] = get_member_name(name_, LowerCamelCase);
name[LowerCaseUnderscores] = get_member_name(name_, LowerCaseUnderscores);
} else {
name[LowerCamelCase] = name_.substr(0, index);
name[LowerCaseUnderscores] = name_.substr(index + 1);
}
}
void Cpp_function::Metadata::set(
const std::string &name_, Value_type rtype,
const std::vector<std::pair<std::string, Value_type>> &ptypes,
const std::string &pcodes) {
set_name(name_);
signature = Cpp_function::gen_signature(ptypes);
param_types = ptypes;
param_codes = pcodes;
return_type = rtype;
}
void Cpp_function::Metadata::set(
const std::string &name_, Value_type rtype, const Raw_signature ¶ms,
const std::vector<std::pair<std::string, Value_type>> &ptypes,
const std::string &pcodes) {
set_name(name_);
signature = params;
for (const auto ¶m : signature) {
std::string param_name = param->name;
if (param->flag == Param_flag::Optional) param_name = "?" + param_name;
param_types.push_back(std::make_pair(param_name, param->type()));
}
param_types = ptypes;
param_codes = pcodes;
return_type = rtype;
}
void Cpp_function::Metadata::cli(bool enable) { cli_enabled = enable; }
Cpp_object_bridge::Cpp_object_bridge() {
expose("help", &Cpp_object_bridge::help, "?item")->cli(false);
}
std::string &Cpp_object_bridge::append_descr(std::string &s_out, int,
int) const {
s_out.append("<" + class_name() + ">");
return s_out;
}
std::string &Cpp_object_bridge::append_repr(std::string &s_out) const {
return append_descr(s_out, 0, '"');
}
std::vector<std::string> Cpp_object_bridge::get_members() const {
std::vector<std::string> members;
members.reserve(_properties.size() + _funcs.size());
for (const auto &prop : _properties)
members.push_back(prop.name(current_naming_style()));
for (const auto &func : _funcs)
members.push_back(func.second->name(current_naming_style()));
return members;
}
std::vector<std::string> Cpp_object_bridge::get_cli_members() const {
std::vector<std::string> members;
for (const auto &func : _funcs) {
if (func.second->get_metadata()->cli_enabled.get_safe(false)) {
members.push_back(func.second->name(current_naming_style()));
}
}
return members;
}
std::string Cpp_object_bridge::get_base_name(const std::string &member) const {
if (auto func = lookup_function(member); func)
return func->name(NamingStyle::LowerCamelCase);
auto prop = std::find_if(_properties.begin(), _properties.end(),
[&member](const Cpp_property_name &p) {
return p.name(current_naming_style()) == member;
});
if (prop != _properties.end())
return (*prop).name(NamingStyle::LowerCamelCase);
return {};
}
std::string Cpp_object_bridge::get_function_name(const std::string &member,
bool fully_specified) const {
auto m = lookup_function(member);
std::string name;
if (!m) {
name = get_member_name(member, current_naming_style());
} else {
name = m->name(current_naming_style());
}
if (fully_specified) {
return class_name() + "." + name;
} else {
return name;
}
}
shcore::Value Cpp_object_bridge::get_member_method(
const shcore::Argument_list &args, const std::string &method,
const std::string &prop) {
args.ensure_count(0, get_function_name(method).c_str());
return get_member_advanced(get_member_name(prop, current_naming_style()));
}
Value Cpp_object_bridge::get_member_advanced(const std::string &prop) const {
auto func = std::find_if(
_funcs.begin(), _funcs.end(), [&prop](const FunctionEntry &f) {
return f.second->name(current_naming_style()) == prop;
});
if (func != _funcs.end())
return Value(std::shared_ptr<Function_base>(func->second));
auto prop_index =
std::find_if(_properties.begin(), _properties.end(),
[&prop](const Cpp_property_name &p) {
return p.name(current_naming_style()) == prop;
});
if (prop_index != _properties.end())
return get_member((*prop_index).base_name());
throw Exception::attrib_error("Invalid object member " + prop);
}
Value Cpp_object_bridge::get_member(const std::string &prop) const {
if (auto i = _funcs.find(prop); i != _funcs.end())
return Value(std::shared_ptr<Function_base>(i->second));
throw Exception::attrib_error("Invalid object member " + prop);
}
bool Cpp_object_bridge::has_member_advanced(const std::string &prop) const {
if (lookup_function(prop)) return true;
auto prop_index =
std::find_if(_properties.begin(), _properties.end(),
[&prop](const Cpp_property_name &p) {
return p.name(current_naming_style()) == prop;
});
return (prop_index != _properties.end());
}
bool Cpp_object_bridge::has_member(const std::string &prop) const {
{
Scoped_naming_style lower(NamingStyle::LowerCamelCase);
if (lookup_function(prop)) return true;
}
auto prop_index = std::find_if(
_properties.begin(), _properties.end(),
[&prop](const Cpp_property_name &p) { return p.base_name() == prop; });
return (prop_index != _properties.end());
}
void Cpp_object_bridge::set_member_advanced(const std::string &prop,
Value value) {
auto prop_index =
std::find_if(_properties.begin(), _properties.end(),
[&prop](const Cpp_property_name &p) {
return p.name(current_naming_style()) == prop;
});
if (prop_index != _properties.end()) {
set_member((*prop_index).base_name(), value);
} else {
throw Exception::attrib_error("Can't set object member " + prop);
}
}
void Cpp_object_bridge::set_member(const std::string &prop, Value) {
throw Exception::attrib_error("Can't set object member " + prop);
}
bool Cpp_object_bridge::is_indexed() const { return false; }
Value Cpp_object_bridge::get_member(size_t) const {
throw Exception::attrib_error("Can't access object members using an index");
}
void Cpp_object_bridge::set_member(size_t, Value) {
throw Exception::attrib_error("Can't set object member using an index");
}
size_t Cpp_object_bridge::length() const {
throw Exception::attrib_error("Can't get count of indexed members");
}
bool Cpp_object_bridge::has_method(const std::string &name) const {
auto method_index = _funcs.find(name);
return method_index != _funcs.end();
}
Cpp_function::Metadata *Cpp_object_bridge::expose(
const std::string &name, const shcore::Function_base_ref &func,
const Cpp_function::Raw_signature ¶meters) {
assert(func);
assert(!name.empty());
std::string pcodes;
std::vector<std::pair<std::string, Value_type>> ptypes;
for (const auto ¶m : parameters) {
switch (param->type()) {
case shcore::Value_type::Object:
pcodes.append("O");
break;
case shcore::Value_type::Map:
pcodes.append("D");
break;
case shcore::Value_type::Array:
pcodes.append("A");
break;
case shcore::Value_type::String:
pcodes.append("s");
break;
case shcore::Value_type::Integer:
pcodes.append("i");
break;
case shcore::Value_type::Bool:
pcodes.append("b");
break;
case shcore::Value_type::Float:
pcodes.append("f");
break;
case shcore::Value_type::Undefined:
pcodes.append("V");
break;
default:
throw std::runtime_error("Unsupported parameter type for '" +
param->name + "'");
}
ptypes.push_back({param->name, param->type()});
}
std::string mangled_name = class_name() + "::" + name + ":" + pcodes;
Cpp_function::Metadata &md = get_metadata(mangled_name);
if (md.name[0].empty()) {
md.set(name, func->return_type(), parameters, ptypes, pcodes);
}
std::string registered_name = name.substr(0, name.find("|"));
detect_overload_conflicts(registered_name, md);
_funcs.emplace(std::make_pair(
registered_name,
std::shared_ptr<Cpp_function>(new Cpp_function(
&md, [&md, func](const shcore::Argument_list &args) -> shcore::Value {
// Executes parameter validators
for (size_t index = 0; index < args.size(); index++) {
Parameter_context context{
"", {{"Argument", static_cast<int>(index + 1)}}};
md.signature[index]->validate(args[index], &context);
}
return func->invoke(args);
}))));
return &md;
}
const Cpp_function::Metadata *Cpp_object_bridge::get_function_metadata(
const std::string &name, bool cli_enabled) {
auto range = _funcs.equal_range(name);
for (auto it = range.first; it != range.second; it++) {
auto md = it->second->get_metadata();
if (!cli_enabled || md->cli_enabled.get_safe(false)) {
return md;
}
}
return nullptr;
}
bool Cpp_object_bridge::has_method_advanced(const std::string &name) const {
if (lookup_function(name)) return true;
return false;
}
void Cpp_object_bridge::add_method_(
const std::string &name, Cpp_function::Function func,
std::vector<std::pair<std::string, Value_type>> *signature) {
auto f = _funcs.find(name);
if (f != _funcs.end()) {
#ifndef NDEBUG
log_warning("Attempt to register a duplicate method: %s", name.c_str());
#endif
// overloading not supported in old API, erase the previous one
_funcs.erase(f);
}
auto function =
std::shared_ptr<Cpp_function>(new Cpp_function(name, func, *signature));
function->is_legacy = true;
_funcs.emplace(name.substr(0, name.find("|")), function);
}
void Cpp_object_bridge::add_constant(const std::string &name) {
_properties.push_back(Cpp_property_name(name, true));
}
void Cpp_object_bridge::add_property(const std::string &name,
const std::string &getter) {
_properties.push_back(Cpp_property_name(name));
if (!getter.empty())
add_method(getter, std::bind(&Cpp_object_bridge::get_member_method, this,
_1, getter, name));
}
void Cpp_object_bridge::delete_property(const std::string &name,
const std::string &getter) {
auto prop_index = std::find_if(
_properties.begin(), _properties.end(),
[name](const Cpp_property_name &p) { return p.base_name() == name; });
if (prop_index != _properties.end()) {
_properties.erase(prop_index);
if (!getter.empty()) _funcs.erase(getter);
}
}
Value Cpp_object_bridge::call_advanced(const std::string &name,
const Argument_list &args,
const shcore::Dictionary_t &kwargs) {
auto func = lookup_function_overload(name, args, kwargs);
if (func) {
// At this point func is the best function valid with the given arguments
// and keyword arguments.
auto scope = get_function_name(name, true);
auto signature = func->function_signature();
// If all the arguments are already in place the function is called
if (args.size() == signature.size()) {
return call_function(scope, func, args);
} else {
// If missing arguments, they are taken from the keywords or the default
// values
Argument_list new_args;
for (const auto &arg : args) {
new_args.push_back(arg);
}
bool support_kwargs = false;
std::vector<std::string> final_kwargs;
if (kwargs && !kwargs->empty()) {
std::tie(support_kwargs, final_kwargs) =
process_keyword_args(signature, kwargs);
}
for (size_t index = new_args.size(); index < signature.size(); index++) {
if (kwargs && kwargs->has_key(signature.at(index)->name)) {
new_args.push_back(kwargs->at(signature.at(index)->name));
} else if (index == signature.size() - 1 && support_kwargs) {
auto kwargs_dict = shcore::make_dict();
for (const auto &key : final_kwargs) {
kwargs_dict->set(key, kwargs->at(key));
}
new_args.push_back(shcore::Value(kwargs_dict));
} else if (signature.at(index)->flag == Param_flag::Optional) {
// An undefined Value is used as a trigger to use the defined
// default for optional paraeters the parameter when the function is
// called
new_args.push_back(signature.at(index)->def_value);
}
}
return call_function(scope, func, new_args);
}
} else {
throw Exception::attrib_error("Invalid object function " + name);
}
}
/**
* Utility function to handle function calls using both legacy and new
* export framework.
*
* On new framework, any error will be prepended with the given scope
* which usually is <class>.<function>
*/
Value Cpp_object_bridge::call_function(
const std::string &scope, const std::shared_ptr<Cpp_function> &func,
const Argument_list &args) {
shcore::Log_sql_guard guard(scope.c_str());
if (func->is_legacy) {
return func->invoke(args);
} else {
try {
return func->invoke(args);
} catch (const shcore::Exception &e) {
throw shcore::Exception(e.type(), scope + ": " + e.what(), e.code());
} catch (const shcore::Error &e) {
throw shcore::Exception(scope + ": " + e.what(), e.code());
} catch (const std::runtime_error &e) {
throw shcore::Exception::runtime_error(scope + ": " + e.what());
} catch (const std::invalid_argument &e) {
throw shcore::Exception::argument_error(scope + ": " + e.what());
} catch (const std::logic_error &e) {
throw shcore::Exception::logic_error(scope + ": " + e.what());
} catch (...) {
throw;
}
}
}
std::shared_ptr<Cpp_function> Cpp_object_bridge::lookup_function(
const std::string &method) const {
// NOTE this linear lookup is no good, but needed until the naming style
// mechanism is improved
std::multimap<std::string, std::shared_ptr<Cpp_function>>::const_iterator i;
for (i = _funcs.begin(); i != _funcs.end(); ++i) {
if (i->second->name(current_naming_style()) == method) break;
}
if (i == _funcs.end()) {
return std::shared_ptr<Cpp_function>(nullptr);
}
// ignore the overloads and just return first match...
return i->second;
}
std::shared_ptr<Cpp_function> Cpp_object_bridge::lookup_function_overload(
const std::string &method, const shcore::Argument_list &args,
const shcore::Dictionary_t &kwds) const {
// NOTE this linear lookup is no good, but needed until the naming style
// mechanism is improved
std::multimap<std::string, std::shared_ptr<Cpp_function>>::const_iterator i;
for (i = _funcs.begin(); i != _funcs.end(); ++i) {
if (i->second->name(current_naming_style()) == method) break;
}
if (i == _funcs.end()) {
throw Exception::attrib_error("Invalid object function " + method);
}
std::vector<Value_type> arg_types;
for (const auto &arg : args) {
arg_types.push_back(arg.type);
}
// find best matching function taking param types into account
// by using rules similar to those from C++
// we don't bother trying to optimize much, since overloads are rare
// and won't have more than 2 or 3 candidates
std::vector<std::pair<int, std::shared_ptr<Cpp_function>>> candidates;
int max_error_score = -1;
shcore::Exception match_error("", 0);
shcore::Exception error("", 0);
while (i != _funcs.end() &&
i->second->name(current_naming_style()) == method) {
if (i->second->is_legacy) return i->second;
bool match;
int score;
std::tie(match, score, error) = Cpp_function::match_signatures(
i->second->function_signature(), arg_types, kwds);
if (match) {
candidates.push_back({score, i->second});
} else {
if (score > max_error_score) {
max_error_score = score;
match_error = error;
}
}
++i;
}
if (candidates.size() == 1) {
return candidates[0].second;
} else if (candidates.size() > 1) {
#ifndef NDEBUG
for (const auto &cand : candidates) {
log_info("Candidates: %s(%i)", cand.second->name().c_str(),
static_cast<int>(cand.second->signature().size()));
}
#endif
// multiple matches, find best one by the closest implicit conversion
std::sort(
candidates.begin(), candidates.end(),
[](const std::pair<int, std::shared_ptr<Cpp_function>> &a,
const std::pair<int, std::shared_ptr<Cpp_function>> &b) -> bool {
return a.first > b.first;
});
// TODO(alfredo) object matching not supported yet...
throw Exception::attrib_error("Call to overloaded function " +
get_function_name(method, true) +
" has ambiguous candidates.");
}
if (max_error_score != -1) {
throw Exception(match_error.type(),
get_function_name(method, true) + ": " + match_error.what(),
match_error.code());
} else {
throw Exception::argument_error("Call to " +
get_function_name(method, true) +
" has wrong parameter count/types.");
}
}
Value Cpp_object_bridge::call(const std::string &name,
const Argument_list &args) {
Scoped_naming_style lower(LowerCamelCase);
return call_advanced(name, args);
}
std::string Cpp_object_bridge::help(const std::string &item) {
IShell_core::Mode mode;
if (current_naming_style() == NamingStyle::LowerCamelCase)
mode = IShell_core::Mode::JavaScript;
else
mode = IShell_core::Mode::Python;
Help_manager help;
help.set_mode(mode);
shcore::Topic_mask mask;
std::string pattern = get_help_id();
if (!item.empty()) {
// This group represents the API topics that can be children of another
// API topic
pattern += "." + item;
mask.set(shcore::Topic_type::FUNCTION);
mask.set(shcore::Topic_type::PROPERTY);
mask.set(shcore::Topic_type::OBJECT);
mask.set(shcore::Topic_type::CLASS);
mask.set(shcore::Topic_type::CONSTANTS);
} else {
// This group represents the API topics that can contain children
mask.set(shcore::Topic_type::MODULE);
mask.set(shcore::Topic_type::OBJECT);
mask.set(shcore::Topic_type::GLOBAL_OBJECT);
mask.set(shcore::Topic_type::CONSTANTS);
mask.set(shcore::Topic_type::CLASS);
}
return help.get_help(pattern, mask);
}
void Cpp_object_bridge::detect_overload_conflicts(
const std::string &name, const Cpp_function::Metadata &md) {
const auto &function_sig = md.signature;
auto range = _funcs.equal_range(name);
for (auto it = range.first; it != range.second; ++it) {
if (it->second->is_legacy)
throw Exception::attrib_error("Attempt to overload legacy function: " +
name);
const auto &overload_sig = it->second->function_signature();
int diff = static_cast<int>(overload_sig.size()) -
static_cast<int>(function_sig.size());
if (diff < 0) {
size_t i = overload_sig.size();
for (; i < function_sig.size(); i++)
if (function_sig[i]->flag != Param_flag::Optional) break;
if (i < function_sig.size()) continue;
} else if (diff > 0) {
size_t i = function_sig.size();
for (; i < overload_sig.size(); i++)
if (overload_sig[i]->flag != Param_flag::Optional) break;
if (i < overload_sig.size()) continue;
}
size_t i = 0;
size_t args_num = std::min(overload_sig.size(), function_sig.size());
for (; i < args_num; i++)
if (!overload_sig[i]->valid_type(function_sig[i]->type()) &&
!function_sig[i]->valid_type(overload_sig[i]->type()))
break;
if (i == args_num)
throw Exception::attrib_error(
"Ambiguous overload detected for funtion: " + name);
}
}
//-------
Cpp_function::Cpp_function(const Metadata *meta, const Function &func)
: _func(func), _meta(meta) {}
// TODO(rennox) legacy, delme once add_method is no longer needed
std::string get_param_codes(
const std::vector<std::pair<std::string, Value_type>> &args) {
std::string codes;
for (const auto &arg : args) {
switch (arg.second) {
case Value_type::Array:
codes.append("A");
break;
case Value_type::Bool:
codes.append("b");
break;
case Value_type::Float:
codes.append("f");
break;
case Value_type::Function:
codes.append("F");
break;
case Value_type::Integer:
codes.append("i");
break;
case Value_type::Map:
codes.append("D");
break;
case Value_type::MapRef:
codes.append("D");
break;
case Value_type::Null:
codes.append("n");
break;
case Value_type::Object:
codes.append("O");
break;
case Value_type::String:
codes.append("s");
break;
case Value_type::UInteger:
codes.append("u");
break;
case Value_type::Undefined:
codes.append("V");
break;
case Value_type::Binary:
codes.append("B");
break;
}
}
return codes;
}
// TODO(alfredo) legacy, delme
Cpp_function::Cpp_function(
const std::string &name_, const Function &func,
const std::vector<std::pair<std::string, Value_type>> &args)
: _func(func) {
// The | separator is used when specific names are given for a function
// Otherwise the function name is retrieved based on the style
auto index = name_.find("|");
if (index == std::string::npos) {
_meta_tmp.name[LowerCamelCase] = get_member_name(name_, LowerCamelCase);
_meta_tmp.name[LowerCaseUnderscores] =
get_member_name(name_, LowerCaseUnderscores);
} else {
_meta_tmp.name[LowerCamelCase] = name_.substr(0, index);
_meta_tmp.name[LowerCaseUnderscores] = name_.substr(index + 1);
}
_meta_tmp.param_types = args;
_meta_tmp.param_codes = get_param_codes(args);
_meta_tmp.signature = Cpp_function::gen_signature(args);
_meta = &_meta_tmp;
}
const std::string &Cpp_function::name() const {
return _meta->name[current_naming_style()];
}
const std::string &Cpp_function::name(const NamingStyle &style) const {
return _meta->name[style];
}
const std::vector<std::pair<std::string, Value_type>> &Cpp_function::signature()
const {
return _meta->param_types;
}
Value_type Cpp_function::return_type() const { return _meta->return_type; }
bool Cpp_function::operator==(const Function_base &UNUSED(other)) const {
throw Exception::logic_error("Cannot compare function objects");
return false;
}
static std::string num_args_expected(
const std::vector<std::pair<std::string, Value_type>> &argt) {
size_t min_args = argt.size();
for (auto i = argt.rbegin(); i != argt.rend(); ++i) {
if (i->first[0] == '?' || i->first[0] == '*') --min_args;
}
if (min_args != argt.size()) {
return std::to_string(min_args) + " to " + std::to_string(argt.size());
}
return std::to_string(min_args);
}
Value Cpp_function::invoke(const Argument_list &args) {
// Check that the list of arguments is correct
if (!_meta->signature.empty() && !is_legacy) {
auto a = args.begin();
const auto &sig = signature();
auto s = _meta->signature.begin();
const auto s_end = _meta->signature.end();
int n = 0;
if (args.size() > sig.size() ||
(args.size() < sig.size() &&
_meta->signature[args.size()]->flag != Param_flag::Optional)) {
throw Exception::argument_error(
name() + ": Invalid number of arguments, expected " +
num_args_expected(sig) + " but got " + std::to_string(args.size()));
}
while (s != s_end) {
if (a == args.end()) {
if ((*s)->flag == Param_flag::Optional) {
// param is optional... remaining expected params can only be
// optional too
break;
}
}
// NOTE: Undefined Optional parameters will skip this validation as they
// will be taking the default value
if ((a->type != Value_type::Undefined ||
(*s)->flag != Param_flag::Optional) &&
!(*s)->valid_type(a->type)) {
throw Exception::type_error(
name() + ": Argument #" + std::to_string(n + 1) +
" is expected to be " + type_description((*s)->type()));
}
++n;
++a;
++s;
}
}
// Note: exceptions caught here should all be self-descriptive and be
// enough for the caller to figure out what's wrong. Other specific
// exception types should have been caught earlier, in the bridges
try {
return _func(args);
} catch (const shcore::Error &) {
// shcore::Error can be thrown by bridges
throw;
} catch (const std::invalid_argument &e) {
throw Exception::argument_error(e.what());
} catch (const std::logic_error &e) {
throw Exception::logic_error(e.what());
} catch (const std::runtime_error &e) {
throw Exception::runtime_error(e.what());
} catch (const std::exception &e) {
throw Exception::logic_error(std::string("Uncaught exception: ") +
e.what());
}
}
std::shared_ptr<Function_base> Cpp_function::create(
const std::string &name, const Function &func,
const std::vector<std::pair<std::string, Value_type>> &args) {
return std::shared_ptr<Function_base>(new Cpp_function(name, func, args));
}
Cpp_property_name::Cpp_property_name(const std::string &name, bool constant) {
// The | separator is used when specific names are given for a function
// Otherwise the function name is retrieved based on the style
auto index = name.find("|");
if (index == std::string::npos) {
_name[LowerCamelCase] =
get_member_name(name, constant ? Constants : LowerCamelCase);
_name[LowerCaseUnderscores] =
get_member_name(name, constant ? Constants : LowerCaseUnderscores);
} else {
_name[LowerCamelCase] = name.substr(0, index);
_name[LowerCaseUnderscores] = name.substr(index + 1);
}
}
std::string Cpp_property_name::name(NamingStyle style) const {
assert((style >= 0) && (style < _name.size()));
return _name[style];
}
std::string Cpp_property_name::base_name() const {
return _name[LowerCamelCase];
}
std::string Parameter_context::str() const {
std::vector<std::string> ctx_data;
for (auto it = levels.begin(); it != levels.end(); it++) {
if (it->position.is_null()) {
ctx_data.push_back(it->name);
} else {
ctx_data.push_back(
shcore::str_format("%s #%i", it->name.c_str(), *it->position));
}
}
return (title.empty() ? "" : title + " ") + shcore::str_join(ctx_data, ", ");
}
bool Parameter_validator::valid_type(const Parameter ¶m,
Value_type type) const {
return is_compatible_type(type, param.type());
}
void Parameter_validator::validate(const Parameter ¶m, const Value &data,
Parameter_context *context) const {
if (!m_enabled) return;
try {
// NOTE: Skipping validation for Undefined parameters that are optional as
// they will use the default value
if (param.flag == Param_flag::Optional &&
data.type == Value_type::Undefined)
return;
if (!valid_type(param, data.type)) {
throw std::invalid_argument("Invalid type");
}
// The call to check_type only verifies the kConvertible matrix there:
// - A string is convertible to integer, bool and float
// - A Null us convertible to object, array and dictionary
// We do this validation to make sure the conversion is really valid
if (data.type == shcore::String) {
if (param.type() == shcore::Integer)
data.as_int();
else if (param.type() == shcore::Bool)
data.as_bool();
else if (param.type() == shcore::Float)
data.as_double();
}
} catch (...) {
auto error =
shcore::str_format("%s is expected to be %s", context->str().c_str(),
shcore::type_description(param.type()).c_str());
throw shcore::Exception::type_error(error);
}
}
void Object_validator::validate(const Parameter ¶m, const Value &data,
Parameter_context *context) const {
if (!m_enabled) return;
// NOTE: Skipping validation for Undefined parameters that are optional as
// they will use the default value
if (param.flag == Param_flag::Optional && data.type == Value_type::Undefined)
return;
Parameter_validator::validate(param, data, context);
if (!m_allowed_ref->empty()) {
const auto object = data.as_object();
if (!object) {
throw shcore::Exception::type_error(shcore::str_format(
"%s is expected to be an object", context->str().c_str()));
}
if (std::find(std::begin(m_allowed), std::end(m_allowed),
object->class_name()) == std::end(m_allowed)) {
auto allowed_str = shcore::str_join(m_allowed, ", ");
if (m_allowed.size() == 1) {
throw shcore::Exception::type_error(
shcore::str_format("%s is expected to be a '%s' object",
context->str().c_str(), allowed_str.c_str()));
} else {
throw shcore::Exception::argument_error(
shcore::str_format("%s is expected to be one of '%s'",
context->str().c_str(), allowed_str.c_str()));
}
}
}
}
void String_validator::validate(const Parameter ¶m, const Value &data,
Parameter_context *context) const {
if (!m_enabled) return;
// NOTE: Skipping validation for Undefined parameters that are optional as
// they will use the default value
if (param.flag == Param_flag::Optional && data.type == Value_type::Undefined)
return;
Parameter_validator::validate(param, data, context);
if (!m_allowed_ref->empty()) {
if (std::find(std::begin(*m_allowed_ref), std::end(*m_allowed_ref),
data.as_string()) == std::end(*m_allowed_ref)) {
auto error = shcore::str_format(
"%s only accepts the following values: %s.", context->str().c_str(),
shcore::str_join(*m_allowed_ref, ", ").c_str());
throw shcore::Exception::argument_error(error);
}
}
}
void Option_validator::validate(const Parameter ¶m, const Value &data,
Parameter_context *context) const {
if (!m_enabled) return;
// NOTE: Skipping validation for Undefined parameters that are optional as
// they will use the default value
if (param.flag == Param_flag::Optional && data.type == Value_type::Undefined)
return;
Parameter_validator::validate(param, data, context);
if (!m_allowed_ref->empty()) {
Option_unpacker unpacker(data.as_map());
for (const auto &item : *m_allowed_ref) {
shcore::Value value;
if (item->flag == Param_flag::Mandatory) {
unpacker.required(item->name.c_str(), &value);
} else {
unpacker.optional(item->name.c_str(), &value);
}
if (value) {
context->levels.push_back({"option '" + item->name + "'", {}});
item->validate(value, context);
context->levels.pop_back();
}
}
unpacker.end("at " + context->str());
}
}
void Parameter::validate(const Value &data, Parameter_context *context) const {
if (m_validator) {
m_validator->validate(*this, data, context);
} else {
// If no validator was set, uses the default validator.
Parameter_validator{}.validate(*this, data, context);
}
}
bool Parameter::valid_type(Value_type type) const {
if (m_validator) {
return m_validator->valid_type(*this, type);
} else {
// If no validator was set, uses the default validator.
return Parameter_validator{}.valid_type(*this, type);
}
}
} // namespace shcore