mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
Merge upstream/develop into fix/issue-677-add-strong-typedefs
Carefully resolved merge conflicts to preserve both the strong typedef (using) feature from this branch and the enum support (#679), nested namespaces (#552), grammar diagram (#628), and WASM exception (#678) features from upstream develop. All 376 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
ebcbd23bf8
118
cheatsheet.md
118
cheatsheet.md
@ -665,6 +665,124 @@ copy.width = 99
|
||||
print(original.width) // still 10
|
||||
```
|
||||
|
||||
## Enums
|
||||
|
||||
ChaiScript supports strongly-typed enums using `enum class` (or equivalently `enum struct`),
|
||||
matching C++ scoped-enum semantics. Values are accessed via `::` syntax and are type-safe —
|
||||
a plain integer cannot be passed where an enum type is expected.
|
||||
|
||||
### Basic Definition
|
||||
|
||||
```
|
||||
enum class Color { Red, Green, Blue }
|
||||
```
|
||||
|
||||
Values are auto-numbered starting from 0. Access them with `Color::Red`, `Color::Green`, etc.
|
||||
|
||||
### Explicit Values
|
||||
|
||||
```
|
||||
enum class Priority { Low = 10, Medium = 20, High = 30 }
|
||||
```
|
||||
|
||||
Auto-numbering continues from the last explicit value:
|
||||
|
||||
```
|
||||
enum class Status { Pending, Active = 5, Done }
|
||||
// Pending = 0, Active = 5, Done = 6
|
||||
```
|
||||
|
||||
### Specifying an Underlying Type
|
||||
|
||||
By default the underlying type is `int`. Use `: type` to choose a different numeric type:
|
||||
|
||||
```
|
||||
enum class Flags : char { Read = 1, Write = 2, Execute = 4 }
|
||||
```
|
||||
|
||||
The underlying type must be a numeric type registered in ChaiScript. `string` and other
|
||||
non-numeric types cannot be used. The available underlying types are:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `int` | (default) signed integer |
|
||||
| `unsigned_int` | unsigned integer |
|
||||
| `long` | signed long |
|
||||
| `unsigned_long` | unsigned long |
|
||||
| `long_long` | signed long long |
|
||||
| `unsigned_long_long` | unsigned long long |
|
||||
| `char` | character (8-bit) |
|
||||
| `wchar_t` | wide character |
|
||||
| `char16_t` | 16-bit character |
|
||||
| `char32_t` | 32-bit character |
|
||||
| `float` | single-precision float |
|
||||
| `double` | double-precision float |
|
||||
| `long_double` | extended-precision float |
|
||||
| `size_t` | unsigned size type |
|
||||
| `int8_t` | signed 8-bit |
|
||||
| `int16_t` | signed 16-bit |
|
||||
| `int32_t` | signed 32-bit |
|
||||
| `int64_t` | signed 64-bit |
|
||||
| `uint8_t` | unsigned 8-bit |
|
||||
| `uint16_t` | unsigned 16-bit |
|
||||
| `uint32_t` | unsigned 32-bit |
|
||||
|
||||
### `enum struct` Syntax
|
||||
|
||||
`enum struct` is accepted as a synonym for `enum class`, just like in C++:
|
||||
|
||||
```
|
||||
enum struct Direction { North, East, South, West }
|
||||
```
|
||||
|
||||
### Constructing from a Value
|
||||
|
||||
Each enum type has a constructor that accepts the underlying type. It validates that the
|
||||
value matches one of the defined enumerators:
|
||||
|
||||
```
|
||||
auto c = Color::Color(1) // creates Color::Green
|
||||
Color::Color(52) // throws: invalid value
|
||||
```
|
||||
|
||||
### `to_underlying`
|
||||
|
||||
Convert an enum value back to its underlying numeric type:
|
||||
|
||||
```
|
||||
Color::Red.to_underlying() // 0
|
||||
Priority::High.to_underlying() // 30
|
||||
```
|
||||
|
||||
### Comparison
|
||||
|
||||
`==` and `!=` are defined for values of the same enum type:
|
||||
|
||||
```
|
||||
assert_true(Color::Red == Color::Red)
|
||||
assert_true(Color::Red != Color::Green)
|
||||
```
|
||||
|
||||
### Type-Safe Dispatch
|
||||
|
||||
Functions declared with an enum parameter type reject plain integers:
|
||||
|
||||
```
|
||||
def handle(Color c) { /* ... */ }
|
||||
handle(Color::Red) // ok
|
||||
handle(42) // throws: dispatch error
|
||||
```
|
||||
|
||||
### Using with `switch`
|
||||
|
||||
```
|
||||
switch(Color::Green) {
|
||||
case (Color::Red) { print("red"); break }
|
||||
case (Color::Green) { print("green"); break }
|
||||
case (Color::Blue) { print("blue"); break }
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic Objects
|
||||
|
||||
All ChaiScript defined types and generic Dynamic_Object support dynamic parameters
|
||||
|
||||
@ -18,8 +18,8 @@
|
||||
|
||||
/* ---- Top-level ---- */
|
||||
|
||||
statements ::= ( def | try | if | while | class | for
|
||||
| switch | return | break | continue
|
||||
statements ::= ( def | try | if | while | class | enum
|
||||
| for | switch | return | break | continue
|
||||
| equation | block | eol )+
|
||||
|
||||
/* ---- Functions ---- */
|
||||
@ -57,6 +57,18 @@ class ::= "class" id ( ":" id )? eol* class_block
|
||||
class_block ::= "{" class_statements* "}"
|
||||
class_statements ::= def | var_decl | eol
|
||||
|
||||
/* ---- Enums ---- */
|
||||
|
||||
enum ::= "enum" ( "class" | "struct" ) id ( ":" underlying_type )?
|
||||
"{" enum_entries? "}"
|
||||
|
||||
enum_entries ::= enum_entry ( "," enum_entry )*
|
||||
|
||||
enum_entry ::= id ( "=" integer )?
|
||||
|
||||
underlying_type ::= id
|
||||
|
||||
|
||||
/* ---- Blocks & flow keywords ---- */
|
||||
|
||||
block ::= "{" statements* "}"
|
||||
|
||||
@ -33,7 +33,7 @@ namespace chaiscript {
|
||||
template<typename T>
|
||||
static bool is_reserved_word(const T &s) noexcept {
|
||||
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("using")};
|
||||
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;
|
||||
}
|
||||
@ -108,6 +108,7 @@ namespace chaiscript {
|
||||
Const_Var_Decl,
|
||||
Const_Assign_Decl,
|
||||
Using,
|
||||
Enum,
|
||||
Namespace_Block
|
||||
};
|
||||
|
||||
@ -129,7 +130,7 @@ namespace chaiscript {
|
||||
namespace {
|
||||
/// 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 *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", "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)];
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -1166,6 +1167,75 @@ namespace chaiscript {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename 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)
|
||||
: AST_Node_Impl<T>(std::move(t_ast_node_text), AST_Node_Type::Enum, std::move(t_loc), std::move(t_children)) {
|
||||
}
|
||||
|
||||
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<Boxed_Value> valid_values;
|
||||
|
||||
for (size_t i = 2; i < this->children.size(); i += 2) {
|
||||
const auto &val_name = this->children[i]->text;
|
||||
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") = val_bv;
|
||||
dobj.set_explicit(true);
|
||||
container[val_name] = const_var(dobj);
|
||||
}
|
||||
|
||||
auto shared_valid = std::make_shared<const std::vector<Boxed_Value>>(std::move(valid_values));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
throw exception::eval_error("Value is not valid for enum '" + enum_name + "'");
|
||||
}));
|
||||
|
||||
t_ss->add_global_const(const_var(container), enum_name);
|
||||
|
||||
t_ss->add(
|
||||
std::make_shared<dispatch::detail::Dynamic_Object_Function>(
|
||||
enum_name,
|
||||
fun([](const dispatch::Dynamic_Object &lhs, const dispatch::Dynamic_Object &rhs) {
|
||||
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 &lhs, const dispatch::Dynamic_Object &rhs) {
|
||||
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 obj.get_attr("value"); })),
|
||||
"to_underlying");
|
||||
|
||||
return void_var();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Namespace_Block_AST_Node final : AST_Node_Impl<T> {
|
||||
Namespace_Block_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
|
||||
|
||||
@ -2106,6 +2106,97 @@ namespace chaiscript {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Enum(const bool t_allowed) {
|
||||
Depth_Counter dc{this};
|
||||
bool retval = false;
|
||||
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
if (Keyword("enum")) {
|
||||
if (!Keyword("class") && !Keyword("struct")) {
|
||||
throw exception::eval_error("Expected 'class' or 'struct' after 'enum' (only 'enum class'/'enum struct' 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),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
retval = true;
|
||||
|
||||
if (!Id(true)) {
|
||||
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 class declaration", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
int next_value = 0;
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (!Char('}')) {
|
||||
do {
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Expected enum value name", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
if (Symbol("=")) {
|
||||
if (!Num()) {
|
||||
throw exception::eval_error("Expected integer after '=' in enum definition",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
next_value = static_cast<int>(std::stoi(m_match_stack.back()->text));
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.push_back(
|
||||
make_node<eval::Constant_AST_Node<Tracer>>(std::to_string(next_value), m_position.line, m_position.col, const_var(next_value)));
|
||||
++next_value;
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
} while (Char(',') && !Char('}'));
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (!Char('}')) {
|
||||
throw exception::eval_error("Expected '}' to close enum class definition",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
}
|
||||
|
||||
build_match<eval::Enum_AST_Node<Tracer>>(prev_stack_top);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// Reads a while block from input
|
||||
bool While() {
|
||||
Depth_Counter dc{this};
|
||||
@ -2851,7 +2942,7 @@ namespace chaiscript {
|
||||
|
||||
while (has_more) {
|
||||
const auto start = m_position;
|
||||
if (Def() || Try() || If() || While() || Namespace_Block() || Class(t_class_allowed) || Using(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) {
|
||||
throw exception::eval_error("Two function definitions missing line separator",
|
||||
File_Position(start.line, start.col),
|
||||
|
||||
110
unittests/enum.chai
Normal file
110
unittests/enum.chai
Normal file
@ -0,0 +1,110 @@
|
||||
// Basic enum class definition (default underlying type: int)
|
||||
enum class Color { Red, Green, Blue }
|
||||
|
||||
// Access via :: syntax
|
||||
auto r = Color::Red
|
||||
auto g = Color::Green
|
||||
auto b = Color::Blue
|
||||
|
||||
// Equality and inequality
|
||||
assert_true(Color::Red == Color::Red)
|
||||
assert_false(Color::Red == Color::Green)
|
||||
assert_true(Color::Red != Color::Green)
|
||||
assert_false(Color::Red != Color::Red)
|
||||
|
||||
// Constructor from valid underlying value
|
||||
auto c = Color::Color(1)
|
||||
assert_true(c == Color::Green)
|
||||
|
||||
// Constructor from invalid value throws
|
||||
try {
|
||||
Color::Color(52)
|
||||
assert_true(false)
|
||||
} catch(e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
// Strong typing: function with typed parameter
|
||||
def takes_color(Color val) { val }
|
||||
takes_color(Color::Red)
|
||||
takes_color(Color::Green)
|
||||
takes_color(Color::Color(2))
|
||||
|
||||
// Cannot pass int where Color is expected
|
||||
try {
|
||||
takes_color(52)
|
||||
assert_true(false)
|
||||
} catch(e) {
|
||||
// expected: dispatch error
|
||||
}
|
||||
|
||||
// to_underlying accessor
|
||||
assert_equal(0, Color::Red.to_underlying())
|
||||
assert_equal(1, Color::Green.to_underlying())
|
||||
assert_equal(2, Color::Blue.to_underlying())
|
||||
|
||||
// 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::Priority(20)
|
||||
assert_true(p == Priority::Medium)
|
||||
|
||||
// Mixed auto and explicit values
|
||||
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())
|
||||
|
||||
// Switch on enum values
|
||||
var result = ""
|
||||
switch(Color::Green) {
|
||||
case (Color::Red) {
|
||||
result = "red"
|
||||
break
|
||||
}
|
||||
case (Color::Green) {
|
||||
result = "green"
|
||||
break
|
||||
}
|
||||
case (Color::Blue) {
|
||||
result = "blue"
|
||||
break
|
||||
}
|
||||
}
|
||||
assert_equal("green", result)
|
||||
|
||||
// Switch on enum with explicit values
|
||||
var prio_result = ""
|
||||
switch(Priority::High) {
|
||||
case (Priority::Low) {
|
||||
prio_result = "low"
|
||||
break
|
||||
}
|
||||
case (Priority::Medium) {
|
||||
prio_result = "medium"
|
||||
break
|
||||
}
|
||||
case (Priority::High) {
|
||||
prio_result = "high"
|
||||
break
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
// enum struct syntax (equivalent to enum class, like C++)
|
||||
enum struct Direction { North, East, South, West }
|
||||
assert_equal(0, Direction::North.to_underlying())
|
||||
assert_equal(3, Direction::West.to_underlying())
|
||||
assert_true(Direction::East != Direction::South)
|
||||
Loading…
x
Reference in New Issue
Block a user