mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
* Fix #677: Add strong typedefs via 'using Type = BaseType' syntax Strong typedefs create distinct types backed by Dynamic_Object, so 'using Meters = int' makes Meters a type that is not interchangeable with int or other typedefs of int. The constructor Meters(val) wraps the base value, and function dispatch enforces the type distinction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review: add to_underlying function for strong typedefs Registers a to_underlying() function for each strong typedef that returns the wrapped base value from the Dynamic_Object's __value attr. Requested by @lefticus in PR #680 review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 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> * Address review: conditionally register operators based on underlying type support Only register strong typedef operators that actually exist for the underlying type. Previously all operators were added unconditionally, causing confusing reflection entries (e.g. * for StrongString) that would fail at runtime. Now each operator is probed via call_match against default-constructed base type values before registration. Also adds bitwise/shift operators (&, |, ^, <<, >>) for types that support them, and expands test coverage for unsupported operator rejection. Requested by @lefticus in PR #680 review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review: register all operators unconditionally and add compound assignment operators Remove conditional operator registration (op_exists_for_base_type check) since users could add underlying operators later, and the runtime check was expensive. Operators that fail on the underlying type now error at call time instead of being absent. Add compound assignment operators (*=, +=, -=, /=, %=, <<=, >>=, &=, |=, ^=) via Strong_Typedef_Compound_Assign_Op which computes the base operation and stores the result back in __value. Requested by @lefticus in PR #680 review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Merge upstream/develop into fix/issue-677-add-strong-typedefs Resolve merge conflicts with ChaiScript:develop. Upstream added nested namespace support (#675), grammar railroad diagrams (#673), and WASM exception support (#689). Conflicts in chaiscript_common.hpp, chaiscript_eval.hpp, and chaiscript_parser.hpp resolved by keeping both Using and Namespace_Block AST node types. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review: add strong typedef documentation to cheatsheet Add a new "Strong Typedefs" section to the cheatsheet covering: - Basic usage with `using Type = BaseType` syntax - Arithmetic and comparison operator forwarding - String-based strong typedefs - Accessing the underlying value via to_underlying - Extending strong typedefs with custom operations Requested by @lefticus in PR #680 review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: leftibot <leftibot@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1df1b4ad92
commit
bb06919061
@ -829,6 +829,94 @@ class My_Class {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Strong Typedefs
|
||||||
|
|
||||||
|
Strong typedefs create distinct types that are not interchangeable with their underlying type
|
||||||
|
or with other typedefs of the same underlying type. They use `Dynamic_Object` internally and
|
||||||
|
automatically expose operators that the underlying type supports.
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
using Meters = int
|
||||||
|
using Seconds = int
|
||||||
|
|
||||||
|
var d = Meters(100)
|
||||||
|
var t = Seconds(10)
|
||||||
|
|
||||||
|
// d and t are distinct types — you cannot accidentally mix them
|
||||||
|
// Meters + Seconds would require an explicit conversion
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arithmetic and Comparison
|
||||||
|
|
||||||
|
Operators from the underlying type are forwarded and remain strongly typed:
|
||||||
|
|
||||||
|
```
|
||||||
|
using Meters = int
|
||||||
|
|
||||||
|
var a = Meters(10)
|
||||||
|
var b = Meters(20)
|
||||||
|
|
||||||
|
var c = a + b // Meters(30) — result is still Meters
|
||||||
|
var bigger = b > a // true — comparisons return bool
|
||||||
|
|
||||||
|
// Compound assignment operators work too
|
||||||
|
a += b // a is now Meters(30)
|
||||||
|
```
|
||||||
|
|
||||||
|
### String-Based Strong Typedefs
|
||||||
|
|
||||||
|
Strong typedefs work with any type, not just numeric types:
|
||||||
|
|
||||||
|
```
|
||||||
|
using Name = string
|
||||||
|
|
||||||
|
var n = Name("Alice")
|
||||||
|
var greeting = Name("Hello, ") + Name("world") // Name — string concatenation is forwarded
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing the Underlying Value
|
||||||
|
|
||||||
|
Use `to_underlying` to extract the wrapped value:
|
||||||
|
|
||||||
|
```
|
||||||
|
using Meters = int
|
||||||
|
|
||||||
|
var d = Meters(42)
|
||||||
|
var raw = to_underlying(d) // 42, plain int
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extending Strong Typedefs
|
||||||
|
|
||||||
|
You can add custom operations to strong typedefs just like any other ChaiScript type:
|
||||||
|
|
||||||
|
```
|
||||||
|
using Meters = int
|
||||||
|
using Seconds = int
|
||||||
|
using MetersPerSecond = int
|
||||||
|
|
||||||
|
def speed(Meters d, Seconds t) {
|
||||||
|
MetersPerSecond(to_underlying(d) / to_underlying(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = speed(Meters(100), Seconds(10)) // MetersPerSecond(10)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also overload operators between different strong typedefs:
|
||||||
|
|
||||||
|
```
|
||||||
|
using Meters = int
|
||||||
|
using Feet = int
|
||||||
|
|
||||||
|
def to_feet(Meters m) {
|
||||||
|
Feet((to_underlying(m) * 328) / 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = Meters(10)
|
||||||
|
var f = to_feet(m) // Feet(32)
|
||||||
|
```
|
||||||
|
|
||||||
## method_missing
|
## method_missing
|
||||||
|
|
||||||
A function of the signature `method_missing(object, name, param1, param2, param3)` will be called if an appropriate
|
A function of the signature `method_missing(object, name, param1, param2, param3)` will be called if an appropriate
|
||||||
|
|||||||
@ -68,6 +68,7 @@ enum_entry ::= id ( "=" integer )?
|
|||||||
|
|
||||||
underlying_type ::= id
|
underlying_type ::= id
|
||||||
|
|
||||||
|
|
||||||
/* ---- Blocks & flow keywords ---- */
|
/* ---- Blocks & flow keywords ---- */
|
||||||
|
|
||||||
block ::= "{" statements* "}"
|
block ::= "{" statements* "}"
|
||||||
|
|||||||
@ -33,7 +33,7 @@ namespace chaiscript {
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
static bool is_reserved_word(const T &s) noexcept {
|
static bool is_reserved_word(const T &s) noexcept {
|
||||||
const static std::unordered_set<std::uint32_t>
|
const static std::unordered_set<std::uint32_t>
|
||||||
words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const"), utility::hash("enum")};
|
words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const"), utility::hash("using"), utility::hash("enum")};
|
||||||
|
|
||||||
return words.count(utility::hash(s)) == 1;
|
return words.count(utility::hash(s)) == 1;
|
||||||
}
|
}
|
||||||
@ -107,6 +107,7 @@ namespace chaiscript {
|
|||||||
Compiled,
|
Compiled,
|
||||||
Const_Var_Decl,
|
Const_Var_Decl,
|
||||||
Const_Assign_Decl,
|
Const_Assign_Decl,
|
||||||
|
Using,
|
||||||
Enum,
|
Enum,
|
||||||
Namespace_Block
|
Namespace_Block
|
||||||
};
|
};
|
||||||
@ -129,7 +130,7 @@ namespace chaiscript {
|
|||||||
namespace {
|
namespace {
|
||||||
/// Helper lookup to get the name of each node type
|
/// Helper lookup to get the name of each node type
|
||||||
constexpr const char *ast_node_type_to_string(AST_Node_Type ast_node_type) noexcept {
|
constexpr const char *ast_node_type_to_string(AST_Node_Type ast_node_type) noexcept {
|
||||||
constexpr const char *const ast_node_types[] = {"Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Assign_Decl", "Array_Call", "Dot_Access", "Lambda", "Block", "Scopeless_Block", "Def", "While", "If", "For", "Ranged_For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", "Inline_Range", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Logical_And", "Logical_Or", "Reference", "Switch", "Case", "Default", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled", "Const_Var_Decl", "Const_Assign_Decl", "Enum", "Namespace_Block"};
|
constexpr const char *const ast_node_types[] = {"Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Assign_Decl", "Array_Call", "Dot_Access", "Lambda", "Block", "Scopeless_Block", "Def", "While", "If", "For", "Ranged_For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", "Inline_Range", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Logical_And", "Logical_Or", "Reference", "Switch", "Case", "Default", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled", "Const_Var_Decl", "Const_Assign_Decl", "Using", "Enum", "Namespace_Block"};
|
||||||
|
|
||||||
return ast_node_types[static_cast<int>(ast_node_type)];
|
return ast_node_types[static_cast<int>(ast_node_type)];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,6 +109,169 @@ namespace chaiscript {
|
|||||||
return incoming;
|
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};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Strong_Typedef_Compound_Assign_Op final : public dispatch::Proxy_Function_Base {
|
||||||
|
public:
|
||||||
|
Strong_Typedef_Compound_Assign_Op(
|
||||||
|
std::string t_type_name,
|
||||||
|
std::string t_op_name,
|
||||||
|
Operators::Opers t_base_oper,
|
||||||
|
std::string t_base_op_name,
|
||||||
|
chaiscript::detail::Dispatch_Engine &t_engine)
|
||||||
|
: Proxy_Function_Base(
|
||||||
|
{user_type<dispatch::Dynamic_Object>(),
|
||||||
|
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_base_oper(t_base_oper)
|
||||||
|
, m_base_op_name(std::move(t_base_op_name))
|
||||||
|
, m_engine(t_engine) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Proxy_Function_Base &f) const noexcept override {
|
||||||
|
if (const auto *other = dynamic_cast<const Strong_Typedef_Compound_Assign_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();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &lhs = boxed_cast<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_base_oper != Operators::Opers::invalid
|
||||||
|
&& lhs_val.get_type_info().is_arithmetic()
|
||||||
|
&& rhs_val.get_type_info().is_arithmetic()) {
|
||||||
|
result = Boxed_Number::do_oper(m_base_oper, lhs_val, rhs_val);
|
||||||
|
} else {
|
||||||
|
std::array<Boxed_Value, 2> underlying_params{lhs_val, rhs_val};
|
||||||
|
result = m_engine.call_function(m_base_op_name, m_loc, Function_Params(underlying_params), t_conversions);
|
||||||
|
}
|
||||||
|
|
||||||
|
lhs.get_attr("__value") = result;
|
||||||
|
return params[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
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_base_oper;
|
||||||
|
std::string m_base_op_name;
|
||||||
|
chaiscript::detail::Dispatch_Engine &m_engine;
|
||||||
|
mutable std::atomic_uint_fast32_t m_loc{0};
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@ -891,6 +1054,119 @@ namespace chaiscript {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct Using_AST_Node final : AST_Node_Impl<T> {
|
||||||
|
Using_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
|
||||||
|
: AST_Node_Impl<T>(std::move(t_ast_node_text), AST_Node_Type::Using, std::move(t_loc), std::move(t_children)) {
|
||||||
|
assert(this->children.size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||||
|
const auto &new_type_name = this->children[0]->text;
|
||||||
|
const auto &base_type_name = this->children[1]->text;
|
||||||
|
|
||||||
|
const auto base_type = t_ss->get_type(base_type_name, true);
|
||||||
|
|
||||||
|
t_ss->add(user_type<dispatch::Dynamic_Object>(), new_type_name);
|
||||||
|
|
||||||
|
dispatch::Param_Types param_types(std::vector<std::pair<std::string, Type_Info>>{
|
||||||
|
{new_type_name, Type_Info()},
|
||||||
|
{base_type_name, base_type}});
|
||||||
|
|
||||||
|
auto ctor_body = dispatch::make_dynamic_proxy_function(
|
||||||
|
[](const Function_Params &t_params) -> Boxed_Value {
|
||||||
|
auto *obj = static_cast<dispatch::Dynamic_Object *>(t_params[0].get_ptr());
|
||||||
|
obj->get_attr("__value") = t_params[1];
|
||||||
|
return void_var();
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
std::shared_ptr<AST_Node>(),
|
||||||
|
param_types);
|
||||||
|
|
||||||
|
try {
|
||||||
|
t_ss->add(std::make_shared<dispatch::detail::Dynamic_Object_Constructor>(new_type_name, ctor_body), new_type_name);
|
||||||
|
} catch (const exception::name_conflict_error &e) {
|
||||||
|
throw exception::eval_error("Type alias redefined '" + e.name() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch::Param_Types to_underlying_param_types(std::vector<std::pair<std::string, Type_Info>>{
|
||||||
|
{new_type_name, user_type<dispatch::Dynamic_Object>()}});
|
||||||
|
|
||||||
|
auto to_underlying_body = dispatch::make_dynamic_proxy_function(
|
||||||
|
[](const Function_Params &t_params) -> Boxed_Value {
|
||||||
|
const auto *obj = static_cast<const dispatch::Dynamic_Object *>(t_params[0].get_const_ptr());
|
||||||
|
return obj->get_attr("__value");
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
std::shared_ptr<AST_Node>(),
|
||||||
|
to_underlying_param_types);
|
||||||
|
|
||||||
|
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::shift_left, true},
|
||||||
|
{">>", Operators::Opers::shift_right, true},
|
||||||
|
{"&", Operators::Opers::bitwise_and, true},
|
||||||
|
{"|", Operators::Opers::bitwise_or, true},
|
||||||
|
{"^", Operators::Opers::bitwise_xor, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Compound_Op_Entry {
|
||||||
|
const char *name;
|
||||||
|
Operators::Opers base_oper;
|
||||||
|
const char *base_op_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr Compound_Op_Entry compound_ops[] = {
|
||||||
|
{"+=", Operators::Opers::sum, "+"},
|
||||||
|
{"-=", Operators::Opers::difference, "-"},
|
||||||
|
{"*=", Operators::Opers::product, "*"},
|
||||||
|
{"/=", Operators::Opers::quotient, "/"},
|
||||||
|
{"%=", Operators::Opers::remainder, "%"},
|
||||||
|
{"<<=", Operators::Opers::shift_left, "<<"},
|
||||||
|
{">>=", Operators::Opers::shift_right, ">>"},
|
||||||
|
{"&=", Operators::Opers::bitwise_and, "&"},
|
||||||
|
{"|=", Operators::Opers::bitwise_or, "|"},
|
||||||
|
{"^=", Operators::Opers::bitwise_xor, "^"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &op : compound_ops) {
|
||||||
|
t_ss->add(
|
||||||
|
chaiscript::make_shared<dispatch::Proxy_Function_Base, detail::Strong_Typedef_Compound_Assign_Op>(
|
||||||
|
new_type_name, std::string(op.name), op.base_oper, std::string(op.base_op_name), engine),
|
||||||
|
op.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return void_var();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct Enum_AST_Node final : AST_Node_Impl<T> {
|
struct Enum_AST_Node final : AST_Node_Impl<T> {
|
||||||
Enum_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
|
Enum_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
|
||||||
|
|||||||
@ -2069,6 +2069,43 @@ namespace chaiscript {
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Using(const bool t_class_allowed) {
|
||||||
|
Depth_Counter dc{this};
|
||||||
|
|
||||||
|
const auto prev_stack_top = m_match_stack.size();
|
||||||
|
|
||||||
|
if (Keyword("using")) {
|
||||||
|
if (!t_class_allowed) {
|
||||||
|
throw exception::eval_error("Type alias definitions only allowed at top scope",
|
||||||
|
File_Position(m_position.line, m_position.col),
|
||||||
|
*m_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Id(true)) {
|
||||||
|
throw exception::eval_error("Missing type name in 'using' declaration",
|
||||||
|
File_Position(m_position.line, m_position.col),
|
||||||
|
*m_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Symbol("=", true)) {
|
||||||
|
throw exception::eval_error("Missing '=' in 'using' declaration",
|
||||||
|
File_Position(m_position.line, m_position.col),
|
||||||
|
*m_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Id(true)) {
|
||||||
|
throw exception::eval_error("Missing base type name in 'using' declaration",
|
||||||
|
File_Position(m_position.line, m_position.col),
|
||||||
|
*m_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
build_match<eval::Using_AST_Node<Tracer>>(prev_stack_top);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool Enum(const bool t_allowed) {
|
bool Enum(const bool t_allowed) {
|
||||||
Depth_Counter dc{this};
|
Depth_Counter dc{this};
|
||||||
bool retval = false;
|
bool retval = false;
|
||||||
@ -2905,7 +2942,7 @@ namespace chaiscript {
|
|||||||
|
|
||||||
while (has_more) {
|
while (has_more) {
|
||||||
const auto start = m_position;
|
const auto start = m_position;
|
||||||
if (Def() || Try() || If() || While() || Namespace_Block() || Class(t_class_allowed) || Enum(t_class_allowed) || For() || Switch()) {
|
if (Def() || Try() || If() || While() || Namespace_Block() || Class(t_class_allowed) || Using(t_class_allowed) || Enum(t_class_allowed) || For() || Switch()) {
|
||||||
if (!saw_eol) {
|
if (!saw_eol) {
|
||||||
throw exception::eval_error("Two function definitions missing line separator",
|
throw exception::eval_error("Two function definitions missing line separator",
|
||||||
File_Position(start.line, start.col),
|
File_Position(start.line, start.col),
|
||||||
|
|||||||
200
unittests/strong_typedef.chai
Normal file
200
unittests/strong_typedef.chai
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// Strong typedef: using Type = int creates a distinct type
|
||||||
|
using Meters = int
|
||||||
|
|
||||||
|
def measure(Meters m) {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructing a strong typedef value should work
|
||||||
|
var m = Meters(42)
|
||||||
|
|
||||||
|
// Calling with the typedef'd value should succeed
|
||||||
|
measure(m)
|
||||||
|
|
||||||
|
// Calling with a plain int should fail (strong typedef)
|
||||||
|
try {
|
||||||
|
measure(42)
|
||||||
|
assert_equal(true, false)
|
||||||
|
} catch(e) {
|
||||||
|
// Expected: type mismatch because int is not Meters
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiple strong typedefs from the same base type should be distinct
|
||||||
|
using Seconds = int
|
||||||
|
|
||||||
|
def wait(Seconds s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = Seconds(10)
|
||||||
|
wait(s)
|
||||||
|
|
||||||
|
// Meters and Seconds should not be interchangeable
|
||||||
|
try {
|
||||||
|
wait(m)
|
||||||
|
assert_equal(true, false)
|
||||||
|
} catch(e) {
|
||||||
|
// Expected: Meters is not Seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
measure(s)
|
||||||
|
assert_equal(true, false)
|
||||||
|
} catch(e) {
|
||||||
|
// Expected: Seconds is not Meters
|
||||||
|
}
|
||||||
|
|
||||||
|
// to_underlying should return the base value
|
||||||
|
assert_equal(to_underlying(m), 42)
|
||||||
|
assert_equal(to_underlying(s), 10)
|
||||||
|
|
||||||
|
// to_underlying result should be a plain value, not a strong typedef
|
||||||
|
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)
|
||||||
|
|
||||||
|
// --- Bitwise and shift operators ---
|
||||||
|
assert_equal(to_underlying(Meters(6) & Meters(3)), 2)
|
||||||
|
assert_equal(to_underlying(Meters(6) | Meters(3)), 7)
|
||||||
|
assert_equal(to_underlying(Meters(6) ^ Meters(3)), 5)
|
||||||
|
assert_equal(to_underlying(Meters(5) << Meters(2)), 20)
|
||||||
|
assert_equal(to_underlying(Meters(12) >> Meters(1)), 6)
|
||||||
|
|
||||||
|
// Bitwise results are strongly typed
|
||||||
|
try {
|
||||||
|
takes_int(Meters(6) & Meters(3))
|
||||||
|
assert_equal(true, false)
|
||||||
|
} catch(e) {
|
||||||
|
// Expected: result is Meters, not int
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 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)
|
||||||
|
|
||||||
|
// Operators not supported by the underlying type error at call time
|
||||||
|
try {
|
||||||
|
var bad = ss1 * ss2
|
||||||
|
assert_equal(true, false)
|
||||||
|
} catch(e) {
|
||||||
|
// Expected: underlying string has no * operator
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var bad = ss1 - ss2
|
||||||
|
assert_equal(true, false)
|
||||||
|
} catch(e) {
|
||||||
|
// Expected: underlying string has no - operator
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var bad = ss1 / ss2
|
||||||
|
assert_equal(true, false)
|
||||||
|
} catch(e) {
|
||||||
|
// Expected: underlying string has no / operator
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var bad = ss1 % ss2
|
||||||
|
assert_equal(true, false)
|
||||||
|
} catch(e) {
|
||||||
|
// Expected: underlying string has no % operator
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
// --- Compound assignment operators ---
|
||||||
|
var m3 = Meters(10)
|
||||||
|
m3 += Meters(5)
|
||||||
|
assert_equal(to_underlying(m3), 15)
|
||||||
|
measure(m3)
|
||||||
|
|
||||||
|
m3 -= Meters(3)
|
||||||
|
assert_equal(to_underlying(m3), 12)
|
||||||
|
|
||||||
|
m3 *= Meters(2)
|
||||||
|
assert_equal(to_underlying(m3), 24)
|
||||||
|
|
||||||
|
m3 /= Meters(4)
|
||||||
|
assert_equal(to_underlying(m3), 6)
|
||||||
|
|
||||||
|
m3 %= Meters(4)
|
||||||
|
assert_equal(to_underlying(m3), 2)
|
||||||
|
|
||||||
|
// Compound assignment result is still the strong typedef
|
||||||
|
var m4 = Meters(10)
|
||||||
|
m4 += Meters(5)
|
||||||
|
assert_equal(to_underlying(m4), 15)
|
||||||
|
measure(m4)
|
||||||
|
|
||||||
|
// Compound assignment on StrongString
|
||||||
|
var ss3 = StrongString("hello")
|
||||||
|
ss3 += StrongString(" world")
|
||||||
|
assert_equal(to_underlying(ss3), "hello world")
|
||||||
|
takes_strong_string(ss3)
|
||||||
Loading…
x
Reference in New Issue
Block a user