Skip to content
118 changes: 118 additions & 0 deletions cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 13 additions & 2 deletions grammar/chaiscript.ebnf
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---- */
Expand Down Expand Up @@ -57,6 +57,17 @@ 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* "}"
Expand Down
5 changes: 3 additions & 2 deletions include/chaiscript/language/chaiscript_common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")};
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")};

return words.count(utility::hash(s)) == 1;
}
Expand Down Expand Up @@ -107,6 +107,7 @@ namespace chaiscript {
Compiled,
Const_Var_Decl,
Const_Assign_Decl,
Enum,
Namespace_Block
};

Expand All @@ -128,7 +129,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", "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", "Enum", "Namespace_Block"};

return ast_node_types[static_cast<int>(ast_node_type)];
}
Expand Down
70 changes: 70 additions & 0 deletions include/chaiscript/language/chaiscript_eval.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <map>
#include <memory>
#include <ostream>
#include <algorithm>
#include <stdexcept>
#include <string>
#include <vector>
Expand Down Expand Up @@ -890,6 +891,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)
Expand Down
93 changes: 92 additions & 1 deletion include/chaiscript/language/chaiscript_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,97 @@ namespace chaiscript {
return retval;
}

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};
Expand Down Expand Up @@ -2814,7 +2905,7 @@ namespace chaiscript {

while (has_more) {
const auto start = m_position;
if (Def() || Try() || If() || While() || Namespace_Block() || Class(t_class_allowed) || For() || Switch()) {
if (Def() || Try() || If() || While() || Namespace_Block() || Class(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),
Expand Down
Loading
Loading