mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-10-28 18:07:39 +08:00
Address review: expose strongly-typed operators for strong typedefs
Register forwarding binary operators at typedef creation time via a custom Proxy_Function_Base subclass (Strong_Typedef_Binary_Op). Each operator unwraps __value from both operands, dispatches on the underlying types, and re-wraps arithmetic results in the typedef. Comparison operators return the raw bool. - Arithmetic: +, -, *, /, % → Meters + Meters -> Meters - Comparison: <, >, <=, >=, ==, != → Meters < Meters -> bool - Operators that don't exist on the base type error at call time (e.g. StrongString * StrongString -> error) - Users can extend typedefs with their own operations using to_underlying() for unwrapping Tests cover int-based arithmetic, string-based concatenation, string multiplication error, comparison ops, type safety of results, and user-defined operator extensions. Requested by @lefticus in PR #680 review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
61cbf1cda1
commit
64a823c630
@ -108,6 +108,90 @@ namespace chaiscript {
|
||||
return incoming;
|
||||
}
|
||||
}
|
||||
class Strong_Typedef_Binary_Op final : public dispatch::Proxy_Function_Base {
|
||||
public:
|
||||
Strong_Typedef_Binary_Op(
|
||||
std::string t_type_name,
|
||||
std::string t_op_name,
|
||||
Operators::Opers t_oper,
|
||||
bool t_rewrap,
|
||||
chaiscript::detail::Dispatch_Engine &t_engine)
|
||||
: Proxy_Function_Base(
|
||||
{chaiscript::detail::Get_Type_Info<Boxed_Value>::get(),
|
||||
user_type<dispatch::Dynamic_Object>(),
|
||||
user_type<dispatch::Dynamic_Object>()},
|
||||
2)
|
||||
, m_type_name(std::move(t_type_name))
|
||||
, m_op_name(std::move(t_op_name))
|
||||
, m_oper(t_oper)
|
||||
, m_rewrap(t_rewrap)
|
||||
, m_engine(t_engine) {
|
||||
}
|
||||
|
||||
bool operator==(const Proxy_Function_Base &f) const noexcept override {
|
||||
if (const auto *other = dynamic_cast<const Strong_Typedef_Binary_Op *>(&f)) {
|
||||
return m_type_name == other->m_type_name && m_op_name == other->m_op_name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override {
|
||||
return vals.size() == 2
|
||||
&& type_matches(vals[0], t_conversions)
|
||||
&& type_matches(vals[1], t_conversions);
|
||||
}
|
||||
|
||||
protected:
|
||||
Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override {
|
||||
if (!call_match(params, t_conversions)) {
|
||||
throw chaiscript::exception::guard_error();
|
||||
}
|
||||
|
||||
const auto &lhs = boxed_cast<const dispatch::Dynamic_Object &>(params[0], &t_conversions);
|
||||
const auto &rhs = boxed_cast<const dispatch::Dynamic_Object &>(params[1], &t_conversions);
|
||||
const auto lhs_val = lhs.get_attr("__value");
|
||||
const auto rhs_val = rhs.get_attr("__value");
|
||||
|
||||
Boxed_Value result;
|
||||
if (m_oper != Operators::Opers::invalid
|
||||
&& lhs_val.get_type_info().is_arithmetic()
|
||||
&& rhs_val.get_type_info().is_arithmetic()) {
|
||||
result = Boxed_Number::do_oper(m_oper, lhs_val, rhs_val);
|
||||
} else {
|
||||
std::array<Boxed_Value, 2> underlying_params{lhs_val, rhs_val};
|
||||
result = m_engine.call_function(m_op_name, m_loc, Function_Params(underlying_params), t_conversions);
|
||||
}
|
||||
|
||||
if (m_rewrap) {
|
||||
auto bv = Boxed_Value(dispatch::Dynamic_Object(m_type_name), true);
|
||||
auto *obj = static_cast<dispatch::Dynamic_Object *>(bv.get_ptr());
|
||||
obj->get_attr("__value") = result;
|
||||
return bv;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept {
|
||||
if (!bv.get_type_info().bare_equal(user_type<dispatch::Dynamic_Object>())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const auto &d = boxed_cast<const dispatch::Dynamic_Object &>(bv, &t_conversions);
|
||||
return d.get_type_name() == m_type_name;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_type_name;
|
||||
std::string m_op_name;
|
||||
Operators::Opers m_oper;
|
||||
bool m_rewrap;
|
||||
chaiscript::detail::Dispatch_Engine &m_engine;
|
||||
mutable std::atomic_uint_fast32_t m_loc{0};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<typename T>
|
||||
@ -936,6 +1020,35 @@ namespace chaiscript {
|
||||
|
||||
t_ss->add(to_underlying_body, "to_underlying");
|
||||
|
||||
auto &engine = *t_ss;
|
||||
|
||||
struct Op_Entry {
|
||||
const char *name;
|
||||
Operators::Opers oper;
|
||||
bool rewrap;
|
||||
};
|
||||
|
||||
static constexpr Op_Entry ops[] = {
|
||||
{"+", Operators::Opers::sum, true},
|
||||
{"-", Operators::Opers::difference, true},
|
||||
{"*", Operators::Opers::product, true},
|
||||
{"/", Operators::Opers::quotient, true},
|
||||
{"%", Operators::Opers::remainder, true},
|
||||
{"<", Operators::Opers::less_than, false},
|
||||
{">", Operators::Opers::greater_than, false},
|
||||
{"<=", Operators::Opers::less_than_equal, false},
|
||||
{">=", Operators::Opers::greater_than_equal, false},
|
||||
{"==", Operators::Opers::equals, false},
|
||||
{"!=", Operators::Opers::not_equal, false},
|
||||
};
|
||||
|
||||
for (const auto &op : ops) {
|
||||
t_ss->add(
|
||||
chaiscript::make_shared<dispatch::Proxy_Function_Base, detail::Strong_Typedef_Binary_Op>(
|
||||
new_type_name, std::string(op.name), op.oper, op.rewrap, engine),
|
||||
op.name);
|
||||
}
|
||||
|
||||
return void_var();
|
||||
}
|
||||
};
|
||||
|
||||
@ -53,3 +53,85 @@ def takes_int(int i) {
|
||||
return i
|
||||
}
|
||||
assert_equal(takes_int(to_underlying(m)), 42)
|
||||
|
||||
// --- Arithmetic operators: strongly typed ---
|
||||
var m2 = Meters(8)
|
||||
var m_sum = m + m2
|
||||
assert_equal(to_underlying(m_sum), 50)
|
||||
measure(m_sum)
|
||||
|
||||
var m_diff = m - m2
|
||||
assert_equal(to_underlying(m_diff), 34)
|
||||
|
||||
var m_prod = Meters(3) * Meters(4)
|
||||
assert_equal(to_underlying(m_prod), 12)
|
||||
|
||||
var m_quot = Meters(20) / Meters(5)
|
||||
assert_equal(to_underlying(m_quot), 4)
|
||||
|
||||
var m_rem = Meters(17) % Meters(5)
|
||||
assert_equal(to_underlying(m_rem), 2)
|
||||
|
||||
// Arithmetic result is strongly typed, not plain int
|
||||
try {
|
||||
takes_int(m_sum)
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: m_sum is Meters, not int
|
||||
}
|
||||
|
||||
// --- Comparison operators ---
|
||||
assert_equal(Meters(5) == Meters(5), true)
|
||||
assert_equal(Meters(5) != Meters(3), true)
|
||||
assert_equal(Meters(3) < Meters(5), true)
|
||||
assert_equal(Meters(5) > Meters(3), true)
|
||||
assert_equal(Meters(5) <= Meters(5), true)
|
||||
assert_equal(Meters(3) >= Meters(3), true)
|
||||
assert_equal(Meters(3) >= Meters(5), false)
|
||||
|
||||
// --- Strong typedef over string ---
|
||||
using StrongString = string
|
||||
|
||||
var ss1 = StrongString("hello")
|
||||
var ss2 = StrongString(" world")
|
||||
var ss_cat = ss1 + ss2
|
||||
assert_equal(to_underlying(ss_cat), "hello world")
|
||||
|
||||
// StrongString + StrongString -> StrongString (strongly typed)
|
||||
def takes_strong_string(StrongString ss) {
|
||||
return ss
|
||||
}
|
||||
takes_strong_string(ss_cat)
|
||||
|
||||
// StrongString * StrongString -> error (no * for strings)
|
||||
try {
|
||||
var bad = ss1 * ss2
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: no * operator for strings
|
||||
}
|
||||
|
||||
// Comparison on StrongString
|
||||
assert_equal(StrongString("abc") < StrongString("def"), true)
|
||||
assert_equal(StrongString("abc") == StrongString("abc"), true)
|
||||
assert_equal(StrongString("abc") != StrongString("def"), true)
|
||||
assert_equal(StrongString("def") > StrongString("abc"), true)
|
||||
assert_equal(StrongString("abc") <= StrongString("abc"), true)
|
||||
assert_equal(StrongString("def") >= StrongString("abc"), true)
|
||||
|
||||
// --- User-defined extensions on strong typedefs ---
|
||||
def first_char(StrongString ss) {
|
||||
return to_string(to_underlying(ss)[0])
|
||||
}
|
||||
assert_equal(first_char(StrongString("hello")), "h")
|
||||
|
||||
def double_meters(Meters m) {
|
||||
return Meters(to_underlying(m) * 2)
|
||||
}
|
||||
assert_equal(to_underlying(double_meters(Meters(21))), 42)
|
||||
|
||||
// User-defined operator extension
|
||||
def `[]`(StrongString ss, int offset) {
|
||||
return to_string(to_underlying(ss)[offset])
|
||||
}
|
||||
assert_equal(StrongString("hello")[1], "e")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user