mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
Address review: enum class syntax, constructor, configurable underlying type
- Change syntax from `enum` to `enum class` (only strongly-typed enums)
- Support optional underlying type: `enum class Flags : char { ... }`
(defaults to `int` when omitted)
- Replace `from_int` with a constructor named after the enum type,
accessed as `Color::Color(1)` — the underlying type is no longer
hardcoded to int
- Use Boxed_Number for type-generic value storage and comparison
- to_underlying now returns the actual underlying type
Note: `Color(1)` syntax is not possible because ChaiScript's global
objects shadow functions with the same name; `Color::Color(1)` is the
C++-consistent alternative.
Requested by @lefticus in PR #679 review.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2c807d8c4a
commit
c08447d9c7
@ -899,32 +899,37 @@ namespace chaiscript {
|
||||
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
const auto &enum_name = this->children[0]->text;
|
||||
const auto &underlying_type_name = this->children[1]->text;
|
||||
const auto underlying_ti = t_ss->get_type(underlying_type_name);
|
||||
|
||||
dispatch::Dynamic_Object container(enum_name);
|
||||
std::vector<int> valid_values;
|
||||
std::vector<Boxed_Value> valid_values;
|
||||
|
||||
for (size_t i = 1; i < this->children.size(); i += 2) {
|
||||
for (size_t i = 2; i < this->children.size(); i += 2) {
|
||||
const auto &val_name = this->children[i]->text;
|
||||
const int val_int = Boxed_Number(this->children[i + 1]->eval(t_ss)).get_as<int>();
|
||||
valid_values.push_back(val_int);
|
||||
const auto val_bv = Boxed_Number(this->children[i + 1]->eval(t_ss)).get_as(underlying_ti).bv;
|
||||
valid_values.push_back(val_bv);
|
||||
|
||||
dispatch::Dynamic_Object dobj(enum_name);
|
||||
dobj.get_attr("value") = Boxed_Value(val_int);
|
||||
dobj.get_attr("value") = val_bv;
|
||||
dobj.set_explicit(true);
|
||||
container[val_name] = const_var(dobj);
|
||||
}
|
||||
|
||||
auto shared_valid = std::make_shared<const std::vector<int>>(std::move(valid_values));
|
||||
auto shared_valid = std::make_shared<const std::vector<Boxed_Value>>(std::move(valid_values));
|
||||
|
||||
container["from_int"] = var(
|
||||
fun([shared_valid, enum_name](int t_val) -> Boxed_Value {
|
||||
if (std::find(shared_valid->begin(), shared_valid->end(), t_val) == shared_valid->end()) {
|
||||
throw exception::eval_error("Value " + std::to_string(t_val) + " is not valid for enum '" + enum_name + "'");
|
||||
container[enum_name] = var(
|
||||
fun([shared_valid, enum_name, underlying_ti](const Boxed_Number &t_val) -> Boxed_Value {
|
||||
const auto converted = t_val.get_as(underlying_ti);
|
||||
for (const auto &v : *shared_valid) {
|
||||
if (Boxed_Number::equals(Boxed_Number(v), converted)) {
|
||||
dispatch::Dynamic_Object dobj(enum_name);
|
||||
dobj.get_attr("value") = converted.bv;
|
||||
dobj.set_explicit(true);
|
||||
return const_var(dobj);
|
||||
}
|
||||
}
|
||||
dispatch::Dynamic_Object dobj(enum_name);
|
||||
dobj.get_attr("value") = Boxed_Value(t_val);
|
||||
dobj.set_explicit(true);
|
||||
return const_var(dobj);
|
||||
throw exception::eval_error("Value is not valid for enum '" + enum_name + "'");
|
||||
}));
|
||||
|
||||
t_ss->add_global_const(const_var(container), enum_name);
|
||||
@ -933,7 +938,7 @@ namespace chaiscript {
|
||||
std::make_shared<dispatch::detail::Dynamic_Object_Function>(
|
||||
enum_name,
|
||||
fun([](const dispatch::Dynamic_Object &lhs, const dispatch::Dynamic_Object &rhs) {
|
||||
return Boxed_Number(lhs.get_attr("value")).get_as<int>() == Boxed_Number(rhs.get_attr("value")).get_as<int>();
|
||||
return Boxed_Number::equals(Boxed_Number(lhs.get_attr("value")), Boxed_Number(rhs.get_attr("value")));
|
||||
})),
|
||||
"==");
|
||||
|
||||
@ -941,14 +946,14 @@ namespace chaiscript {
|
||||
std::make_shared<dispatch::detail::Dynamic_Object_Function>(
|
||||
enum_name,
|
||||
fun([](const dispatch::Dynamic_Object &lhs, const dispatch::Dynamic_Object &rhs) {
|
||||
return Boxed_Number(lhs.get_attr("value")).get_as<int>() != Boxed_Number(rhs.get_attr("value")).get_as<int>();
|
||||
return !Boxed_Number::equals(Boxed_Number(lhs.get_attr("value")), Boxed_Number(rhs.get_attr("value")));
|
||||
})),
|
||||
"!=");
|
||||
|
||||
t_ss->add(
|
||||
std::make_shared<dispatch::detail::Dynamic_Object_Function>(
|
||||
enum_name,
|
||||
fun([](const dispatch::Dynamic_Object &obj) { return Boxed_Number(obj.get_attr("value")).get_as<int>(); })),
|
||||
fun([](const dispatch::Dynamic_Object &obj) { return obj.get_attr("value"); })),
|
||||
"to_underlying");
|
||||
|
||||
return void_var();
|
||||
|
||||
@ -2076,6 +2076,12 @@ namespace chaiscript {
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
if (Keyword("enum")) {
|
||||
if (!Keyword("class")) {
|
||||
throw exception::eval_error("Expected 'class' after 'enum' (only 'enum class' is supported)",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
if (!t_allowed) {
|
||||
throw exception::eval_error("Enum definitions only allowed at top scope",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
@ -2085,11 +2091,25 @@ namespace chaiscript {
|
||||
retval = true;
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing enum name in definition", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
throw exception::eval_error("Missing enum class name in definition", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
std::string underlying_type = "int";
|
||||
if (Char(':')) {
|
||||
if (!Id(false)) {
|
||||
throw exception::eval_error("Expected underlying type after ':'",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
underlying_type = m_match_stack.back()->text;
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.push_back(
|
||||
make_node<eval::Constant_AST_Node<Tracer>>(underlying_type, m_position.line, m_position.col, const_var(underlying_type)));
|
||||
|
||||
if (!Char('{')) {
|
||||
throw exception::eval_error("Expected '{' after enum name", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
throw exception::eval_error("Expected '{' after enum class declaration", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
int next_value = 0;
|
||||
@ -2128,7 +2148,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
if (!Char('}')) {
|
||||
throw exception::eval_error("Expected '}' to close enum definition",
|
||||
throw exception::eval_error("Expected '}' to close enum class definition",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// Basic enum definition
|
||||
enum Color { Red, Green, Blue }
|
||||
// Basic enum class definition (default underlying type: int)
|
||||
enum class Color { Red, Green, Blue }
|
||||
|
||||
// Access via :: syntax
|
||||
auto r = Color::Red
|
||||
@ -12,13 +12,13 @@ assert_false(Color::Red == Color::Green)
|
||||
assert_true(Color::Red != Color::Green)
|
||||
assert_false(Color::Red != Color::Red)
|
||||
|
||||
// Construction from valid int
|
||||
auto c = Color::from_int(1)
|
||||
// Constructor from valid underlying value
|
||||
auto c = Color::Color(1)
|
||||
assert_true(c == Color::Green)
|
||||
|
||||
// Construction from invalid int throws
|
||||
// Constructor from invalid value throws
|
||||
try {
|
||||
Color::from_int(52)
|
||||
Color::Color(52)
|
||||
assert_true(false)
|
||||
} catch(e) {
|
||||
// expected
|
||||
@ -28,7 +28,7 @@ try {
|
||||
def takes_color(Color val) { val }
|
||||
takes_color(Color::Red)
|
||||
takes_color(Color::Green)
|
||||
takes_color(Color::from_int(2))
|
||||
takes_color(Color::Color(2))
|
||||
|
||||
// Cannot pass int where Color is expected
|
||||
try {
|
||||
@ -43,17 +43,17 @@ assert_equal(0, Color::Red.to_underlying())
|
||||
assert_equal(1, Color::Green.to_underlying())
|
||||
assert_equal(2, Color::Blue.to_underlying())
|
||||
|
||||
// Enum with explicit values
|
||||
enum Priority { Low = 10, Medium = 20, High = 30 }
|
||||
// Enum class with explicit values
|
||||
enum class Priority { Low = 10, Medium = 20, High = 30 }
|
||||
assert_equal(10, Priority::Low.to_underlying())
|
||||
assert_equal(20, Priority::Medium.to_underlying())
|
||||
assert_equal(30, Priority::High.to_underlying())
|
||||
|
||||
auto p = Priority::from_int(20)
|
||||
auto p = Priority::Priority(20)
|
||||
assert_true(p == Priority::Medium)
|
||||
|
||||
// Mixed auto and explicit values
|
||||
enum Status { Pending, Active = 5, Done }
|
||||
enum class Status { Pending, Active = 5, Done }
|
||||
assert_equal(0, Status::Pending.to_underlying())
|
||||
assert_equal(5, Status::Active.to_underlying())
|
||||
assert_equal(6, Status::Done.to_underlying())
|
||||
@ -93,3 +93,12 @@ switch(Priority::High) {
|
||||
}
|
||||
}
|
||||
assert_equal("high", prio_result)
|
||||
|
||||
// Enum class with explicit underlying type
|
||||
enum class Flags : char { Read = 1, Write = 2, Execute = 4 }
|
||||
assert_equal(1, Flags::Read.to_underlying())
|
||||
assert_equal(2, Flags::Write.to_underlying())
|
||||
assert_equal(4, Flags::Execute.to_underlying())
|
||||
|
||||
auto f = Flags::Flags(2)
|
||||
assert_true(f == Flags::Write)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user