mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
Address review: merge develop to resolve merge conflicts
Merged origin/develop into the branch. Git auto-merged all files (chaiscript_common.hpp, chaiscript_engine.hpp, chaiscript_eval.hpp, compiled_tests.cpp) without conflicts. Build succeeds and all 391 tests pass. Requested by @lefticus in PR #682 review.
This commit is contained in:
commit
e46ccb7d33
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMULTITHREAD_SUPPORT_ENABLED=${{ matrix.multithread }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
@ -49,7 +49,7 @@ jobs:
|
||||
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMULTITHREAD_SUPPORT_ENABLED=${{ matrix.multithread }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
@ -71,7 +71,7 @@ jobs:
|
||||
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
@ -93,7 +93,7 @@ jobs:
|
||||
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
@ -135,7 +135,7 @@ jobs:
|
||||
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_THREAD_SANITIZER=ON -DMULTITHREAD_SUPPORT_ENABLED=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
@ -157,7 +157,7 @@ jobs:
|
||||
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_THREAD_SANITIZER=ON -DMULTITHREAD_SUPPORT_ENABLED=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
@ -440,6 +440,10 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(emscripten_eval_test ${LIBS})
|
||||
add_test(NAME Emscripten_Eval_Test COMMAND emscripten_eval_test)
|
||||
|
||||
add_executable(emscripten_exception_test unittests/emscripten_exception_test.cpp)
|
||||
target_link_libraries(emscripten_exception_test ${LIBS})
|
||||
add_test(NAME Emscripten_Exception_Test COMMAND emscripten_exception_test)
|
||||
|
||||
add_executable(threading_config_test unittests/threading_config_test.cpp)
|
||||
target_link_libraries(threading_config_test ${LIBS})
|
||||
add_test(NAME Threading_Config_Test COMMAND threading_config_test)
|
||||
|
||||
252
cheatsheet.md
252
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
|
||||
@ -711,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
|
||||
|
||||
A function of the signature `method_missing(object, name, param1, param2, param3)` will be called if an appropriate
|
||||
@ -913,5 +1119,51 @@ The print handler can also be set from within ChaiScript itself via `set_print_h
|
||||
set_print_handler(fun(s) { my_custom_log(s) })
|
||||
```
|
||||
|
||||
## Custom File Loading
|
||||
|
||||
By default, ChaiScript reads files from the filesystem when `eval_file()` or `use()` is called.
|
||||
You can override this behavior on a per-instance basis by setting a custom file reader callback.
|
||||
This follows the same pattern as `set_print_handler` and enables use cases such as encrypted
|
||||
script files, in-memory virtual filesystems, or platform-specific file access (e.g., Android assets).
|
||||
|
||||
```cpp
|
||||
chaiscript::ChaiScript chai;
|
||||
|
||||
// Provide scripts from an in-memory map instead of the filesystem
|
||||
std::map<std::string, std::string> virtual_fs = {
|
||||
{"init.chai", "var x = 42"},
|
||||
{"utils.chai", "def add(a, b) { a + b }"}
|
||||
};
|
||||
|
||||
chai.set_file_reader([&virtual_fs](const std::string &filename) -> std::string {
|
||||
const auto it = virtual_fs.find(filename);
|
||||
if (it != virtual_fs.end()) {
|
||||
return it->second;
|
||||
}
|
||||
throw chaiscript::exception::file_not_found_error(filename);
|
||||
});
|
||||
|
||||
chai.eval_file("init.chai"); // evaluates "var x = 42"
|
||||
chai.use("utils.chai"); // evaluates "def add(a, b) { a + b }"
|
||||
```
|
||||
|
||||
The file reader can also be set from within ChaiScript itself via `set_file_reader`:
|
||||
|
||||
```chaiscript
|
||||
// Override file loading from within a script
|
||||
set_file_reader(fun(filename) { return my_custom_read(filename) })
|
||||
```
|
||||
|
||||
When no custom file reader is set, ChaiScript uses its built-in filesystem reader.
|
||||
|
||||
## Extras
|
||||
ChaiScript itself does not provide a link to the math functions defined in `<cmath>`. You can either add them yourself, or use the [ChaiScript_Extras](https://github.com/ChaiScript/ChaiScript_Extras) helper library. (Which also provides some additional string functions.)
|
||||
|
||||
## Grammar Railroad Diagrams
|
||||
|
||||
A formal EBNF grammar for ChaiScript is available in [`grammar/chaiscript.ebnf`](grammar/chaiscript.ebnf). You can visualize it as navigable railroad diagrams by pasting its contents into one of these tools:
|
||||
|
||||
* [rr — Railroad Diagram Generator (IPv6)](https://www.bottlecaps.de/rr/ui)
|
||||
* [rr — Railroad Diagram Generator (IPv4)](https://rr.red-dove.com/ui)
|
||||
|
||||
Open either link, switch to the **Edit Grammar** tab, paste the file contents, then click **View Diagram**.
|
||||
|
||||
@ -17,9 +17,14 @@ add_definitions(-DCHAISCRIPT_NO_THREADS -DCHAISCRIPT_NO_DYNLOAD)
|
||||
add_executable(chaiscript chaiscript_em.cpp)
|
||||
target_include_directories(chaiscript PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
|
||||
|
||||
# Enable WASM exception handling — ChaiScript relies on C++ exceptions for
|
||||
# error propagation; without this flag exceptions cause an abort in WASM.
|
||||
target_compile_options(chaiscript PRIVATE -fwasm-exceptions)
|
||||
|
||||
# Emscripten link flags: enable embind, allow memory growth, export as ES module-compatible
|
||||
target_link_options(chaiscript PRIVATE
|
||||
--bind
|
||||
-fwasm-exceptions
|
||||
-sALLOW_MEMORY_GROWTH=1
|
||||
-sEXPORT_ES6=0
|
||||
-sMODULARIZE=0
|
||||
|
||||
189
grammar/chaiscript.ebnf
Normal file
189
grammar/chaiscript.ebnf
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* ChaiScript Grammar — EBNF for Railroad Diagram Generation
|
||||
*
|
||||
* View as navigable railroad diagrams at:
|
||||
* https://www.bottlecaps.de/rr/ui (IPv6)
|
||||
* https://rr.red-dove.com/ui (IPv4)
|
||||
*
|
||||
* Copy and paste this file into the 'Edit Grammar' tab, then
|
||||
* click 'View Diagram'.
|
||||
*
|
||||
* This grammar uses the notation accepted by
|
||||
* https://github.com/GuntherRademacher/rr :
|
||||
* - "::=" as rule separator
|
||||
* - no semicolon at end of rule
|
||||
* - "?" "+" "*" for repetition
|
||||
* - C comments
|
||||
*/
|
||||
|
||||
/* ---- Top-level ---- */
|
||||
|
||||
statements ::= ( def | try | if | while | class | enum
|
||||
| for | switch | return | break | continue
|
||||
| equation | block | eol )+
|
||||
|
||||
/* ---- Functions ---- */
|
||||
|
||||
def ::= "def" id ( "::" id )? "(" decl_arg_list ")" eol*
|
||||
( ":" guard )? eol* block
|
||||
|
||||
lambda ::= "fun" ( "[" id_arg_list "]" )? "(" decl_arg_list ")" eol* block
|
||||
|
||||
guard ::= operator
|
||||
|
||||
/* ---- Exception handling ---- */
|
||||
|
||||
try ::= "try" eol* block catch* finally?
|
||||
catch ::= "catch" ( "(" arg ")" )? eol* block
|
||||
finally ::= "finally" eol* block
|
||||
|
||||
/* ---- Control flow ---- */
|
||||
|
||||
if ::= "if" "(" equation ( eol equation )? ")" eol* block
|
||||
( "else" ( if | eol* block ) )*
|
||||
|
||||
while ::= "while" "(" operator ")" eol* block
|
||||
|
||||
for ::= "for" "(" ( for_guards | equation ":" equation ) ")" eol* block
|
||||
for_guards ::= equation eol equation eol equation
|
||||
|
||||
switch ::= "switch" "(" operator ")" eol* "{" ( case | default )+ "}"
|
||||
case ::= "case" "(" operator ")" eol* block
|
||||
default ::= "default" eol* block
|
||||
|
||||
/* ---- Classes ---- */
|
||||
|
||||
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* "}"
|
||||
return ::= "return" operator?
|
||||
break ::= "break"
|
||||
continue ::= "continue"
|
||||
|
||||
/* ---- Line termination ---- */
|
||||
|
||||
eol ::= "\n" | "\r\n" | ";"
|
||||
|
||||
/* ---- Equations & operators ---- */
|
||||
|
||||
equation ::= operator ( ( "=" | ":=" | "+=" | "-=" | "*=" | "/="
|
||||
| "%=" | "<<=" | ">>=" | "&=" | "^=" | "|=" )
|
||||
equation )?
|
||||
|
||||
operator ::= prefix
|
||||
| value
|
||||
| operator binary_operator operator
|
||||
| operator "?" operator ":" operator
|
||||
|
||||
prefix ::= ( "++" | "--" | "-" | "+" | "!" | "~" ) operator
|
||||
|
||||
binary_operator ::= "||" | "&&"
|
||||
| "|" | "^" | "&"
|
||||
| "==" | "!="
|
||||
| "<" | "<=" | ">" | ">="
|
||||
| "<<" | ">>"
|
||||
| "+" | "-"
|
||||
| "*" | "/" | "%"
|
||||
|
||||
/* ---- Values & access ---- */
|
||||
|
||||
value ::= var_decl | dot_fun_array | prefix
|
||||
|
||||
dot_fun_array ::= ( lambda | num | quoted_string
|
||||
| single_quoted_string | raw_string
|
||||
| paren_expression | inline_container
|
||||
| id )
|
||||
( fun_call | array_call | dot_access )*
|
||||
|
||||
fun_call ::= "(" arg_list ")"
|
||||
array_call ::= "[" operator "]"
|
||||
dot_access ::= "." id
|
||||
|
||||
/* ---- Variable declarations ---- */
|
||||
|
||||
var_decl ::= ( "auto" | "var" | "const" ) ( reference | id )
|
||||
| "global" ( reference | id )
|
||||
| "attr" id ( "::" id )?
|
||||
|
||||
reference ::= "&" id
|
||||
|
||||
/* ---- Parenthesised & inline containers ---- */
|
||||
|
||||
paren_expression ::= "(" operator ")"
|
||||
|
||||
inline_container ::= "[" container_arg_list "]"
|
||||
container_arg_list ::= value_range
|
||||
| map_pair ( "," map_pair )*
|
||||
| operator ( "," operator )*
|
||||
|
||||
value_range ::= operator ".." operator
|
||||
map_pair ::= operator ":" operator
|
||||
|
||||
/* ---- String literals ---- */
|
||||
|
||||
quoted_string ::= '"' ( char | escape | interpolation )* '"'
|
||||
single_quoted_string ::= "'" ( char | escape ) "'"
|
||||
raw_string ::= 'R"' delimiter? "(" char* ")" delimiter? '"'
|
||||
delimiter ::= [a-zA-Z0-9_]+
|
||||
interpolation ::= "${" equation "}"
|
||||
|
||||
/* ---- Escape sequences ---- */
|
||||
|
||||
escape ::= "\" ( "'" | '"' | "?" | "\" | "a" | "b"
|
||||
| "f" | "n" | "r" | "t" | "v" | "$"
|
||||
| "0"
|
||||
| "x" hex_digit+
|
||||
| "u" hex_digit hex_digit hex_digit hex_digit
|
||||
| "U" hex_digit hex_digit hex_digit hex_digit
|
||||
hex_digit hex_digit hex_digit hex_digit
|
||||
| octal_digit+ )
|
||||
|
||||
/* ---- Argument lists ---- */
|
||||
|
||||
id_arg_list ::= id ( "," id )*
|
||||
decl_arg_list ::= ( arg ( "," arg )* )?
|
||||
arg_list ::= ( equation ( "," equation )* )?
|
||||
arg ::= id id?
|
||||
|
||||
/* ---- Identifiers ---- */
|
||||
|
||||
id ::= ( [a-zA-Z_] [a-zA-Z0-9_]* )
|
||||
| ( "`" [^`]+ "`" )
|
||||
| "true" | "false"
|
||||
| "Infinity" | "NaN"
|
||||
| "_"
|
||||
| "__LINE__" | "__FILE__" | "__FUNC__" | "__CLASS__"
|
||||
|
||||
/* ---- Numeric literals ---- */
|
||||
|
||||
num ::= hex | binary | float | integer
|
||||
|
||||
hex ::= "0" ( "x" | "X" ) [0-9a-fA-F]+ int_suffix*
|
||||
binary ::= "0" ( "b" | "B" ) [01]+ int_suffix*
|
||||
float ::= [0-9]+ "." [0-9]+ ( ( "e" | "E" ) ( "+" | "-" )? [0-9]+ )? float_suffix?
|
||||
integer ::= [0-9]+ int_suffix*
|
||||
|
||||
int_suffix ::= "l" | "L" | "ll" | "LL" | "u" | "U"
|
||||
float_suffix ::= "l" | "L" | "f" | "F"
|
||||
|
||||
/* ---- Character classes ---- */
|
||||
|
||||
octal_digit ::= [0-7]
|
||||
hex_digit ::= [0-9a-fA-F]
|
||||
char ::= [^"\]
|
||||
@ -34,7 +34,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("using"), utility::hash("enum")};
|
||||
|
||||
return words.count(utility::hash(s)) == 1;
|
||||
}
|
||||
@ -107,7 +107,10 @@ namespace chaiscript {
|
||||
Constant,
|
||||
Compiled,
|
||||
Const_Var_Decl,
|
||||
Const_Assign_Decl
|
||||
Const_Assign_Decl,
|
||||
Using,
|
||||
Enum,
|
||||
Namespace_Block
|
||||
};
|
||||
|
||||
enum class Operator_Precedence {
|
||||
@ -128,7 +131,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"};
|
||||
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)];
|
||||
}
|
||||
|
||||
@ -80,6 +80,7 @@ namespace chaiscript {
|
||||
std::map<std::string, std::function<Namespace &()>> m_namespace_generators;
|
||||
|
||||
std::function<void(const std::string &)> m_print_handler = [](const std::string &) noexcept {};
|
||||
std::function<std::string(const std::string &)> m_file_reader;
|
||||
|
||||
/// Evaluates the given string in by parsing it and running the results through the evaluator
|
||||
Boxed_Value do_eval(const std::string &t_input, const std::string &t_filename = "__EVAL__", bool /* t_internal*/ = false) {
|
||||
@ -145,6 +146,10 @@ namespace chaiscript {
|
||||
m_print_handler = t_handler;
|
||||
}), "set_print_handler");
|
||||
|
||||
m_engine.add(fun([this](const std::function<std::string(const std::string &)> &t_reader) {
|
||||
m_file_reader = t_reader;
|
||||
}), "set_file_reader");
|
||||
|
||||
m_engine.add(fun([this]() { m_engine.dump_system(); }), "dump_system");
|
||||
m_engine.add(fun([this](const Boxed_Value &t_bv) { m_engine.dump_object(t_bv); }), "dump_object");
|
||||
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_type) { return m_engine.is_type(t_bv, t_type); }), "is_type");
|
||||
@ -194,10 +199,17 @@ namespace chaiscript {
|
||||
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { add_global(t_bv, t_name); }), "add_global");
|
||||
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { set_global(t_bv, t_name); }), "set_global");
|
||||
|
||||
// why this unused parameter to Namespace?
|
||||
m_engine.add(fun([this](const std::string &t_namespace_name) {
|
||||
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
||||
import(t_namespace_name);
|
||||
if (!m_namespace_generators.count(t_namespace_name)) {
|
||||
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
||||
}
|
||||
const auto sep_pos = t_namespace_name.find("::");
|
||||
const std::string root_name = (sep_pos != std::string::npos) ? t_namespace_name.substr(0, sep_pos) : t_namespace_name;
|
||||
if (!m_engine.get_scripting_objects().count(root_name)) {
|
||||
import(root_name);
|
||||
} else if (m_namespace_generators.count(root_name)) {
|
||||
nest_children(root_name, m_namespace_generators[root_name]());
|
||||
}
|
||||
}),
|
||||
"namespace");
|
||||
m_engine.add(fun([this](const std::string &t_namespace_name) { import(t_namespace_name); }), "import");
|
||||
@ -242,7 +254,15 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
/// Helper function for loading a file
|
||||
static std::string load_file(const std::string &t_filename) {
|
||||
std::string load_file(const std::string &t_filename) const {
|
||||
if (m_file_reader) {
|
||||
return m_file_reader(t_filename);
|
||||
}
|
||||
|
||||
return load_file_default(t_filename);
|
||||
}
|
||||
|
||||
static std::string load_file_default(const std::string &t_filename) {
|
||||
std::ifstream infile(t_filename.c_str(), std::ios::in | std::ios::ate | std::ios::binary);
|
||||
|
||||
if (!infile.is_open()) {
|
||||
@ -284,6 +304,12 @@ namespace chaiscript {
|
||||
m_print_handler = std::move(t_handler);
|
||||
}
|
||||
|
||||
/// \brief Set a custom handler for reading files, used by eval_file, use, and internal_eval_file
|
||||
/// \param[in] t_reader Function to call with the filename, returning the file contents as a string
|
||||
void set_file_reader(std::function<std::string(const std::string &)> t_reader) {
|
||||
m_file_reader = std::move(t_reader);
|
||||
}
|
||||
|
||||
/// \brief Virtual destructor for ChaiScript
|
||||
virtual ~ChaiScript_Basic() = default;
|
||||
|
||||
@ -746,28 +772,59 @@ namespace chaiscript {
|
||||
if (m_engine.get_scripting_objects().count(t_namespace_name)) {
|
||||
throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined");
|
||||
} else if (m_namespace_generators.count(t_namespace_name)) {
|
||||
m_engine.add_global(var(std::ref(m_namespace_generators[t_namespace_name]())), t_namespace_name);
|
||||
auto &ns = m_namespace_generators[t_namespace_name]();
|
||||
nest_children(t_namespace_name, ns);
|
||||
m_engine.add_global(var(std::ref(ns)), t_namespace_name);
|
||||
} else {
|
||||
throw std::runtime_error("No registered namespace: " + t_namespace_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Registers a namespace generator, which delays generation of the namespace until it is imported, saving memory if it is never
|
||||
/// used. \param[in] t_namespace_generator Namespace generator function. \param[in] t_namespace_name Name of the Namespace function
|
||||
/// being registered. \throw std::runtime_error In the case that the namespace name was already registered.
|
||||
/// used. Supports C++-style nested names (e.g. "constants::si") for nested namespaces; parent namespaces are auto-registered if absent.
|
||||
/// \param[in] t_namespace_generator Namespace generator function.
|
||||
/// \param[in] t_namespace_name Name of the Namespace function being registered (may contain :: for nesting).
|
||||
/// \throw std::runtime_error In the case that the namespace name was already registered.
|
||||
void register_namespace(const std::function<void(Namespace &)> &t_namespace_generator, const std::string &t_namespace_name) {
|
||||
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
|
||||
|
||||
if (!m_namespace_generators.count(t_namespace_name)) {
|
||||
// contain the namespace object memory within the m_namespace_generators map
|
||||
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable -> Namespace & {
|
||||
t_namespace_generator(space);
|
||||
return space;
|
||||
}));
|
||||
} else {
|
||||
if (m_namespace_generators.count(t_namespace_name)) {
|
||||
throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered.");
|
||||
}
|
||||
|
||||
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable -> Namespace & {
|
||||
t_namespace_generator(space);
|
||||
return space;
|
||||
}));
|
||||
|
||||
auto pos = t_namespace_name.rfind("::");
|
||||
while (pos != std::string::npos) {
|
||||
const std::string parent = t_namespace_name.substr(0, pos);
|
||||
if (!m_namespace_generators.count(parent)) {
|
||||
m_namespace_generators.emplace(std::make_pair(parent, [space = Namespace()]() mutable -> Namespace & {
|
||||
return space;
|
||||
}));
|
||||
}
|
||||
pos = parent.rfind("::");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void nest_children(const std::string &t_parent_name, Namespace &t_parent) {
|
||||
const std::string prefix = t_parent_name + "::";
|
||||
for (auto &[name, generator] : m_namespace_generators) {
|
||||
if (name.size() > prefix.size() && name.compare(0, prefix.size(), prefix) == 0) {
|
||||
const std::string remainder = name.substr(prefix.size());
|
||||
if (remainder.find("::") == std::string::npos) {
|
||||
auto &child_ns = generator();
|
||||
nest_children(name, child_ns);
|
||||
t_parent[remainder] = var(std::ref(child_ns));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
};
|
||||
|
||||
} // namespace chaiscript
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -108,6 +109,169 @@ 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};
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
template<typename T>
|
||||
@ -792,40 +956,43 @@ namespace chaiscript {
|
||||
return false;
|
||||
}
|
||||
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
static std::shared_ptr<dispatch::Proxy_Function_Base> make_proxy_function(
|
||||
const Def_AST_Node<T> &t_node, const chaiscript::detail::Dispatch_State &t_ss) {
|
||||
std::vector<std::string> t_param_names;
|
||||
size_t numparams = 0;
|
||||
|
||||
dispatch::Param_Types param_types;
|
||||
|
||||
if ((this->children.size() > 1) && (this->children[1]->identifier == AST_Node_Type::Arg_List)) {
|
||||
numparams = this->children[1]->children.size();
|
||||
t_param_names = Arg_List_AST_Node<T>::get_arg_names(*this->children[1]);
|
||||
param_types = Arg_List_AST_Node<T>::get_arg_types(*this->children[1], t_ss);
|
||||
if ((t_node.children.size() > 1) && (t_node.children[1]->identifier == AST_Node_Type::Arg_List)) {
|
||||
numparams = t_node.children[1]->children.size();
|
||||
t_param_names = Arg_List_AST_Node<T>::get_arg_names(*t_node.children[1]);
|
||||
param_types = Arg_List_AST_Node<T>::get_arg_types(*t_node.children[1], t_ss);
|
||||
}
|
||||
|
||||
std::reference_wrapper<chaiscript::detail::Dispatch_Engine> engine(*t_ss);
|
||||
std::shared_ptr<dispatch::Proxy_Function_Base> guard;
|
||||
if (m_guard_node) {
|
||||
if (t_node.m_guard_node) {
|
||||
guard = dispatch::make_dynamic_proxy_function(
|
||||
[engine, guardnode = m_guard_node, t_param_names](const Function_Params &t_params) {
|
||||
[engine, guardnode = t_node.m_guard_node, t_param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *guardnode, t_param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
m_guard_node);
|
||||
t_node.m_guard_node);
|
||||
}
|
||||
|
||||
return dispatch::make_dynamic_proxy_function(
|
||||
[engine, func_node = t_node.m_body_node, t_param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *func_node, t_param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
t_node.m_body_node,
|
||||
param_types,
|
||||
guard);
|
||||
}
|
||||
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
try {
|
||||
const std::string &l_function_name = this->children[0]->text;
|
||||
t_ss->add(dispatch::make_dynamic_proxy_function(
|
||||
[engine, func_node = m_body_node, t_param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *func_node, t_param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
m_body_node,
|
||||
param_types,
|
||||
guard),
|
||||
l_function_name);
|
||||
t_ss->add(make_proxy_function(*this, t_ss), this->children[0]->text);
|
||||
} catch (const exception::name_conflict_error &e) {
|
||||
throw exception::eval_error("Function redefined '" + e.name() + "'");
|
||||
}
|
||||
@ -891,6 +1058,269 @@ 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>
|
||||
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)
|
||||
: AST_Node_Impl<T>(std::move(t_ast_node_text), AST_Node_Type::Namespace_Block, std::move(t_loc), std::move(t_children)) {
|
||||
}
|
||||
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
const auto &ns_name = this->children[0]->text;
|
||||
|
||||
auto ns_name_bv = const_var(ns_name);
|
||||
t_ss->call_function("namespace", m_ns_loc, Function_Params{ns_name_bv}, t_ss.conversions());
|
||||
|
||||
std::vector<std::string> parts;
|
||||
{
|
||||
std::string::size_type start = 0;
|
||||
std::string::size_type pos = 0;
|
||||
while ((pos = ns_name.find("::", start)) != std::string::npos) {
|
||||
parts.push_back(ns_name.substr(start, pos - start));
|
||||
start = pos + 2;
|
||||
}
|
||||
parts.push_back(ns_name.substr(start));
|
||||
}
|
||||
|
||||
Boxed_Value ns_bv = t_ss.get_object(parts[0], m_root_loc);
|
||||
|
||||
for (size_t i = 1; i < parts.size(); ++i) {
|
||||
auto &parent_ns = boxed_cast<dispatch::Dynamic_Object &>(ns_bv);
|
||||
ns_bv = parent_ns.get_attr(parts[i]);
|
||||
}
|
||||
|
||||
auto &target_ns = boxed_cast<dispatch::Dynamic_Object &>(ns_bv);
|
||||
|
||||
const auto process_statement = [&](const AST_Node_Impl<T> &stmt) {
|
||||
if (stmt.identifier == AST_Node_Type::Def) {
|
||||
const auto &def_node = static_cast<const Def_AST_Node<T> &>(stmt);
|
||||
target_ns[def_node.children[0]->text] =
|
||||
Boxed_Value(Def_AST_Node<T>::make_proxy_function(def_node, t_ss));
|
||||
} else if (stmt.identifier == AST_Node_Type::Assign_Decl
|
||||
|| stmt.identifier == AST_Node_Type::Const_Assign_Decl) {
|
||||
const auto &var_name = stmt.children[0]->text;
|
||||
auto value = detail::clone_if_necessary(stmt.children[1]->eval(t_ss), m_clone_loc, t_ss);
|
||||
value.reset_return_value();
|
||||
if (stmt.identifier == AST_Node_Type::Const_Assign_Decl) {
|
||||
value.make_const();
|
||||
}
|
||||
target_ns[var_name] = std::move(value);
|
||||
} else if (stmt.identifier == AST_Node_Type::Equation
|
||||
&& !stmt.children.empty()
|
||||
&& (stmt.children[0]->identifier == AST_Node_Type::Var_Decl
|
||||
|| stmt.children[0]->identifier == AST_Node_Type::Const_Var_Decl)) {
|
||||
const auto &var_name = stmt.children[0]->children[0]->text;
|
||||
auto value = detail::clone_if_necessary(stmt.children[1]->eval(t_ss), m_clone_loc, t_ss);
|
||||
value.reset_return_value();
|
||||
target_ns[var_name] = std::move(value);
|
||||
} else if (stmt.identifier == AST_Node_Type::Var_Decl) {
|
||||
const auto &var_name = stmt.children[0]->text;
|
||||
target_ns[var_name] = Boxed_Value();
|
||||
} else {
|
||||
throw exception::eval_error("Only declarations (def, var, auto, global) are allowed inside namespace blocks");
|
||||
}
|
||||
};
|
||||
|
||||
const auto &body = this->children[1];
|
||||
if (body->identifier == AST_Node_Type::Block
|
||||
|| body->identifier == AST_Node_Type::Scopeless_Block) {
|
||||
for (const auto &child : body->children) {
|
||||
process_statement(*child);
|
||||
}
|
||||
} else {
|
||||
process_statement(*body);
|
||||
}
|
||||
|
||||
return void_var();
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::atomic_uint_fast32_t m_ns_loc = {0};
|
||||
mutable std::atomic_uint_fast32_t m_root_loc = {0};
|
||||
mutable std::atomic_uint_fast32_t m_clone_loc = {0};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct If_AST_Node final : AST_Node_Impl<T> {
|
||||
If_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
|
||||
|
||||
@ -1990,6 +1990,44 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
/// Reads a class block from input
|
||||
bool Namespace_Block() {
|
||||
Depth_Counter dc{this};
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
const auto prev_pos = m_position;
|
||||
|
||||
if (Keyword("namespace")) {
|
||||
if (Id(true)) {
|
||||
std::string ns_name = m_match_stack.back()->text;
|
||||
|
||||
while (Symbol("::")) {
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Incomplete namespace name after '::'",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
ns_name += "::" + m_match_stack.back()->text;
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.back() = make_node<eval::Id_AST_Node<Tracer>>(ns_name, prev_pos.line, prev_pos.col);
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (Block()) {
|
||||
build_match<eval::Namespace_Block_AST_Node<Tracer>>(prev_stack_top);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_position = prev_pos;
|
||||
while (prev_stack_top != m_match_stack.size()) {
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Class(const bool t_class_allowed) {
|
||||
Depth_Counter dc{this};
|
||||
bool retval = false;
|
||||
@ -2031,6 +2069,134 @@ namespace chaiscript {
|
||||
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) {
|
||||
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};
|
||||
@ -2379,7 +2545,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
build_match<eval::Array_Call_AST_Node<Tracer>>(prev_stack_top);
|
||||
} else if (Symbol(".")) {
|
||||
} else if (Symbol(".") || Symbol("::")) {
|
||||
has_more = true;
|
||||
if (!(Id(true))) {
|
||||
throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
@ -2776,7 +2942,7 @@ namespace chaiscript {
|
||||
|
||||
while (has_more) {
|
||||
const auto start = m_position;
|
||||
if (Def() || Try() || If() || While() || Class(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),
|
||||
|
||||
10
readme.md
10
readme.md
@ -91,6 +91,16 @@ the doxygen documentation in the build folder or see the website
|
||||
http://www.chaiscript.com.
|
||||
|
||||
|
||||
Grammar
|
||||
=======
|
||||
|
||||
A formal EBNF grammar for ChaiScript is available in
|
||||
[grammar/chaiscript.ebnf](grammar/chaiscript.ebnf). To view it as a railroad
|
||||
diagram, paste the grammar into
|
||||
[mingodad's railroad diagram generator](https://mingodad.github.io/plgh/json2ebnf.html)
|
||||
or [bottlecaps.de/rr](https://www.bottlecaps.de/rr/ui).
|
||||
|
||||
|
||||
The shortest complete example possible follows:
|
||||
|
||||
```C++
|
||||
|
||||
@ -4,6 +4,7 @@ Current Version: 6.1.1
|
||||
|
||||
### Changes since 6.1.0
|
||||
|
||||
* Add `set_file_reader` callback for custom file loading #116 — allows overriding how `eval_file` and `use` read files
|
||||
* Handle the returning of `&` to `*` types. This specifically comes up with `std::vector<int *>` and similar containers
|
||||
* Update CMake to use `LIBDIR` instead of `lib` #502 by @guoyunhe
|
||||
* Add documentation for installing ChaiScript with vcpkg #500 by @grdowns
|
||||
|
||||
@ -1983,6 +1983,162 @@ TEST_CASE("eval_error with AST_Node_Trace call stack compiles in C++20") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test set_file_reader from C++ land") {
|
||||
chaiscript::ChaiScript chai;
|
||||
chai.set_file_reader([](const std::string &) {
|
||||
return std::string("var file_reader_test_val = 42");
|
||||
});
|
||||
chai.eval_file("nonexistent_file.chai");
|
||||
CHECK(chai.eval<int>("file_reader_test_val") == 42);
|
||||
}
|
||||
|
||||
TEST_CASE("Test set_file_reader from ChaiScript land") {
|
||||
chaiscript::ChaiScript chai;
|
||||
chai.set_file_reader([](const std::string &) {
|
||||
return std::string("var from_custom_reader = true");
|
||||
});
|
||||
chai.eval("set_file_reader(fun(filename) { return \"var from_chai_reader = true\"; })");
|
||||
chai.eval_file("any_file.chai");
|
||||
CHECK(chai.eval<bool>("from_chai_reader") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("Test set_file_reader receives correct filename") {
|
||||
chaiscript::ChaiScript chai;
|
||||
std::string captured_filename;
|
||||
chai.set_file_reader([&captured_filename](const std::string &t_filename) {
|
||||
captured_filename = t_filename;
|
||||
return std::string("var dummy = 1");
|
||||
});
|
||||
chai.eval_file("my_special_file.chai");
|
||||
CHECK(captured_filename == "my_special_file.chai");
|
||||
}
|
||||
|
||||
TEST_CASE("Test use with set_file_reader") {
|
||||
chaiscript::ChaiScript chai;
|
||||
chai.set_file_reader([](const std::string &) {
|
||||
return std::string("var use_reader_val = 99");
|
||||
});
|
||||
chai.use("virtual_file.chai");
|
||||
CHECK(chai.eval<int>("use_reader_val") == 99);
|
||||
}
|
||||
TEST_CASE("Nested namespaces via register_namespace with :: separator") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.register_namespace(
|
||||
[](chaiscript::Namespace &si) {
|
||||
si["mu_B"] = chaiscript::const_var(9.274);
|
||||
},
|
||||
"constants::si");
|
||||
|
||||
chai.register_namespace(
|
||||
[](chaiscript::Namespace &mm) {
|
||||
mm["mu_B"] = chaiscript::const_var(0.05788);
|
||||
},
|
||||
"constants::mm");
|
||||
|
||||
chai.import("constants");
|
||||
|
||||
CHECK(chai.eval<double>("constants.si.mu_B") == Approx(9.274));
|
||||
CHECK(chai.eval<double>("constants.mm.mu_B") == Approx(0.05788));
|
||||
|
||||
// Scope resolution via :: works the same as . for access
|
||||
CHECK(chai.eval<double>("constants::si::mu_B") == Approx(9.274));
|
||||
CHECK(chai.eval<double>("constants::mm::mu_B") == Approx(0.05788));
|
||||
}
|
||||
|
||||
TEST_CASE("Deeply nested namespaces via register_namespace") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.register_namespace(
|
||||
[](chaiscript::Namespace &leaf) {
|
||||
leaf["val"] = chaiscript::const_var(42);
|
||||
},
|
||||
"a::b::c");
|
||||
|
||||
chai.import("a");
|
||||
|
||||
CHECK(chai.eval<int>("a.b.c.val") == 42);
|
||||
CHECK(chai.eval<int>("a::b::c::val") == 42);
|
||||
}
|
||||
|
||||
TEST_CASE("Block namespace declaration with ::") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace math {
|
||||
def square(x) { x * x }
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<int>("math::square(5)") == 25);
|
||||
CHECK(chai.eval<int>("math.square(5)") == 25);
|
||||
}
|
||||
|
||||
TEST_CASE("Nested block namespace declaration") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace physics::constants {
|
||||
def speed_of_light() { return 299792458 }
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<int>("physics::constants::speed_of_light()") == 299792458);
|
||||
}
|
||||
|
||||
TEST_CASE("Namespace block reopening") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace ns {
|
||||
def foo() { return 1 }
|
||||
}
|
||||
namespace ns {
|
||||
def bar() { return 2 }
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<int>("ns::foo()") == 1);
|
||||
CHECK(chai.eval<int>("ns::bar()") == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Namespace block with var declarations") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace config {
|
||||
var pi = 3.14
|
||||
var name = "hello"
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<double>("config::pi") == Approx(3.14));
|
||||
CHECK(chai.eval<std::string>("config::name") == "hello");
|
||||
}
|
||||
|
||||
TEST_CASE("Namespace block rejects non-declaration statements") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
namespace bad {
|
||||
1 + 2
|
||||
}
|
||||
)"), chaiscript::exception::eval_error);
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
namespace bad {
|
||||
print("hello")
|
||||
}
|
||||
)"), chaiscript::exception::eval_error);
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
var x = 5
|
||||
namespace bad {
|
||||
x = 10
|
||||
}
|
||||
)"), chaiscript::exception::eval_error);
|
||||
}
|
||||
|
||||
TEST_CASE("C++ runtime_error thrown from registered function is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
|
||||
72
unittests/emscripten_exception_test.cpp
Normal file
72
unittests/emscripten_exception_test.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
// Test that validates exception propagation through the Emscripten eval wrapper.
|
||||
// Without proper exception support flags (-fwasm-exceptions) in the WASM build,
|
||||
// C++ exceptions would cause an abort instead of being catchable.
|
||||
|
||||
#ifndef CHAISCRIPT_NO_THREADS
|
||||
#define CHAISCRIPT_NO_THREADS
|
||||
#endif
|
||||
|
||||
#ifndef CHAISCRIPT_NO_DYNLOAD
|
||||
#define CHAISCRIPT_NO_DYNLOAD
|
||||
#endif
|
||||
|
||||
#include <chaiscript/chaiscript.hpp>
|
||||
#include "../emscripten/chaiscript_eval.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
int main() {
|
||||
// Verify that ChaiScript evaluation errors propagate as exceptions
|
||||
// through the eval wrapper functions. In WASM builds without exception
|
||||
// support, these would abort instead of throwing.
|
||||
|
||||
bool caught = false;
|
||||
|
||||
// Test 1: eval with undefined variable should throw
|
||||
caught = false;
|
||||
try {
|
||||
chaiscript_eval("this_variable_does_not_exist");
|
||||
} catch (const chaiscript::exception::eval_error &) {
|
||||
caught = true;
|
||||
}
|
||||
assert(caught && "eval of undefined variable must throw eval_error");
|
||||
|
||||
// Test 2: evalString with a type mismatch should throw
|
||||
caught = false;
|
||||
try {
|
||||
chaiscript_eval_string("1 + 2");
|
||||
} catch (const chaiscript::exception::bad_boxed_cast &) {
|
||||
caught = true;
|
||||
}
|
||||
assert(caught && "evalString with non-string result must throw bad_boxed_cast");
|
||||
|
||||
// Test 3: evalInt with invalid syntax should throw
|
||||
caught = false;
|
||||
try {
|
||||
chaiscript_eval_int("def {}");
|
||||
} catch (const chaiscript::exception::eval_error &) {
|
||||
caught = true;
|
||||
}
|
||||
assert(caught && "evalInt with syntax error must throw eval_error");
|
||||
|
||||
// Test 4: eval with throw statement should propagate exception
|
||||
caught = false;
|
||||
try {
|
||||
chaiscript_eval("throw(\"user exception\")");
|
||||
} catch (const chaiscript::Boxed_Value &) {
|
||||
caught = true;
|
||||
} catch (...) {
|
||||
caught = true;
|
||||
}
|
||||
assert(caught && "ChaiScript throw must propagate as an exception");
|
||||
|
||||
// Test 5: Verify normal operation still works after caught exceptions
|
||||
chaiscript_eval("var post_exception_test = 100");
|
||||
const int result = chaiscript_eval_int("post_exception_test");
|
||||
assert(result == 100 && "normal eval must work after caught exceptions");
|
||||
|
||||
std::cout << "All emscripten exception tests passed.\n";
|
||||
return 0;
|
||||
}
|
||||
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)
|
||||
219
unittests/grammar_constructs.chai
Normal file
219
unittests/grammar_constructs.chai
Normal file
@ -0,0 +1,219 @@
|
||||
// Regression test: exercises grammar constructs documented in grammar/chaiscript.ebnf
|
||||
|
||||
// --- Variable declarations ---
|
||||
var a = 1
|
||||
auto b = 2
|
||||
global c = 3
|
||||
const d = 42
|
||||
|
||||
assert_equal(1, a)
|
||||
assert_equal(2, b)
|
||||
assert_equal(3, c)
|
||||
assert_equal(42, d)
|
||||
|
||||
// --- Reference variables ---
|
||||
var orig = 10
|
||||
var &ref = orig
|
||||
ref = 20
|
||||
assert_equal(20, orig)
|
||||
|
||||
// --- Numeric literals ---
|
||||
assert_equal(255, 0xFF)
|
||||
assert_equal(255, 0xff)
|
||||
assert_equal(5, 0b101)
|
||||
assert_equal(42, 42)
|
||||
assert_equal(3.14, 3.14)
|
||||
|
||||
// --- String interpolation ---
|
||||
var name = "world"
|
||||
assert_equal("hello world", "hello ${name}")
|
||||
|
||||
// --- Escape sequences ---
|
||||
assert_equal("\n", "\n")
|
||||
assert_equal("\t", "\t")
|
||||
|
||||
// --- Single-quoted char ---
|
||||
assert_equal('A', 'A')
|
||||
|
||||
// --- Operators and precedence ---
|
||||
assert_equal(7, 1 + 2 * 3)
|
||||
assert_equal(true, 5 > 3 && 2 < 4)
|
||||
assert_equal(true, false || true)
|
||||
assert_equal(6, 3 << 1)
|
||||
assert_equal(1, 3 >> 1)
|
||||
assert_equal(5, 7 & 5)
|
||||
assert_equal(7, 5 | 3)
|
||||
assert_equal(6, 5 ^ 3)
|
||||
assert_equal(-1, ~0)
|
||||
|
||||
// --- Ternary operator ---
|
||||
assert_equal("yes", true ? "yes" : "no")
|
||||
assert_equal("no", false ? "yes" : "no")
|
||||
|
||||
// --- Prefix operators ---
|
||||
var x = 5
|
||||
++x
|
||||
assert_equal(6, x)
|
||||
--x
|
||||
assert_equal(5, x)
|
||||
assert_equal(true, !false)
|
||||
|
||||
// --- Assignment operators ---
|
||||
var v = 10
|
||||
v += 5; assert_equal(15, v)
|
||||
v -= 3; assert_equal(12, v)
|
||||
v *= 2; assert_equal(24, v)
|
||||
v /= 4; assert_equal(6, v)
|
||||
v %= 4; assert_equal(2, v)
|
||||
v <<= 2; assert_equal(8, v)
|
||||
v >>= 1; assert_equal(4, v)
|
||||
v |= 3; assert_equal(7, v)
|
||||
v &= 5; assert_equal(5, v)
|
||||
v ^= 3; assert_equal(6, v)
|
||||
|
||||
// --- Lambda ---
|
||||
var add = fun(a, b) { a + b }
|
||||
assert_equal(5, add(2, 3))
|
||||
|
||||
// --- Lambda with capture ---
|
||||
var captured = 100
|
||||
var get_captured = fun[captured]() { captured }
|
||||
assert_equal(100, get_captured())
|
||||
|
||||
// --- Function definition ---
|
||||
def multiply(a, b) { a * b }
|
||||
assert_equal(12, multiply(3, 4))
|
||||
|
||||
// --- Guard condition on function ---
|
||||
def abs_val(x) : x >= 0 { x }
|
||||
def abs_val(x) : x < 0 { -x }
|
||||
assert_equal(5, abs_val(5))
|
||||
assert_equal(5, abs_val(-5))
|
||||
|
||||
// --- Class definition ---
|
||||
class Animal
|
||||
{
|
||||
attr sound
|
||||
def Animal(s) { this.sound = s }
|
||||
def speak() { this.sound }
|
||||
}
|
||||
|
||||
var dog = Animal("woof")
|
||||
assert_equal("woof", dog.speak())
|
||||
|
||||
// --- Class with inheritance ---
|
||||
class Puppy : Animal
|
||||
{
|
||||
attr name
|
||||
def Puppy(n, s) { this.name = n; this.sound = s }
|
||||
def greet() { to_string(this.name) + " says " + to_string(this.speak()) }
|
||||
}
|
||||
|
||||
var p = Puppy("Rex", "yip")
|
||||
assert_equal("Rex says yip", p.greet())
|
||||
|
||||
// --- Control flow: if/else ---
|
||||
var result = ""
|
||||
if (true) { result = "yes" } else { result = "no" }
|
||||
assert_equal("yes", result)
|
||||
|
||||
// --- Control flow: while ---
|
||||
var counter = 0
|
||||
while (counter < 3) { ++counter }
|
||||
assert_equal(3, counter)
|
||||
|
||||
// --- Control flow: for ---
|
||||
var sum = 0
|
||||
for (var i = 0; i < 5; ++i) { sum += i }
|
||||
assert_equal(10, sum)
|
||||
|
||||
// --- Control flow: ranged for ---
|
||||
var items = [10, 20, 30]
|
||||
var total = 0
|
||||
for (item : items) { total += item }
|
||||
assert_equal(60, total)
|
||||
|
||||
// --- Switch/case ---
|
||||
def classify(n) {
|
||||
var label = ""
|
||||
switch (n) {
|
||||
case (1) { label = "one"; break }
|
||||
case (2) { label = "two"; break }
|
||||
default { label = "other" }
|
||||
}
|
||||
return label
|
||||
}
|
||||
assert_equal("one", classify(1))
|
||||
assert_equal("two", classify(2))
|
||||
assert_equal("other", classify(99))
|
||||
|
||||
// --- Try/catch/finally ---
|
||||
var caught = false
|
||||
var finalized = false
|
||||
try {
|
||||
throw("oops")
|
||||
} catch (e) {
|
||||
caught = true
|
||||
} finally {
|
||||
finalized = true
|
||||
}
|
||||
assert_true(caught)
|
||||
assert_true(finalized)
|
||||
|
||||
// --- Inline containers ---
|
||||
var vec = [1, 2, 3]
|
||||
assert_equal(3, vec.size())
|
||||
|
||||
var m = ["a": 1, "b": 2]
|
||||
assert_equal(1, m["a"])
|
||||
|
||||
var r = [1, 2, 3, 4, 5]
|
||||
assert_equal(5, r.size())
|
||||
|
||||
// --- Dot access chaining ---
|
||||
assert_equal(3, [1, 2, 3].size())
|
||||
|
||||
// --- Array access ---
|
||||
var arr = [10, 20, 30]
|
||||
assert_equal(20, arr[1])
|
||||
|
||||
// --- Backtick identifier ---
|
||||
var `my var` = 42
|
||||
assert_equal(42, `my var`)
|
||||
|
||||
// --- Special identifiers ---
|
||||
assert_equal(true, true)
|
||||
assert_equal(false, false)
|
||||
|
||||
// --- Nested block ---
|
||||
var block_result = 0
|
||||
{ block_result = 42 }
|
||||
assert_equal(42, block_result)
|
||||
|
||||
// --- Break and continue ---
|
||||
var break_sum = 0
|
||||
for (var i = 0; i < 10; ++i) {
|
||||
if (i == 5) { break }
|
||||
break_sum += i
|
||||
}
|
||||
assert_equal(10, break_sum)
|
||||
|
||||
var cont_sum = 0
|
||||
for (var i = 0; i < 5; ++i) {
|
||||
if (i == 2) { continue }
|
||||
cont_sum += i
|
||||
}
|
||||
assert_equal(8, cont_sum)
|
||||
|
||||
// --- Return from function ---
|
||||
def early_return(n) {
|
||||
if (n > 0) { return "positive" }
|
||||
return "non-positive"
|
||||
}
|
||||
assert_equal("positive", early_return(1))
|
||||
assert_equal("non-positive", early_return(-1))
|
||||
|
||||
// --- Colon assignment ---
|
||||
var ca = 0
|
||||
ca := 99
|
||||
assert_equal(99, ca)
|
||||
55
unittests/nested_namespaces.chai
Normal file
55
unittests/nested_namespaces.chai
Normal file
@ -0,0 +1,55 @@
|
||||
// Test C++-style block namespace declarations
|
||||
namespace constants::si {
|
||||
def mu_B() { return 1.0 }
|
||||
}
|
||||
|
||||
namespace constants::mm {
|
||||
def mu_B() { return 2.0 }
|
||||
}
|
||||
|
||||
assert_equal(1.0, constants::si::mu_B())
|
||||
assert_equal(2.0, constants::mm::mu_B())
|
||||
|
||||
// Test deeper nesting with block syntax
|
||||
namespace a::b::c {
|
||||
def val() { return 42 }
|
||||
}
|
||||
|
||||
assert_equal(42, a::b::c::val())
|
||||
|
||||
// Test reopening a namespace to add more members
|
||||
namespace math {
|
||||
def square(x) { x * x }
|
||||
}
|
||||
|
||||
namespace math::trig {
|
||||
def double_angle(x) { 2.0 * x }
|
||||
}
|
||||
|
||||
assert_equal(16, math::square(4))
|
||||
assert_equal(6.0, math::trig::double_angle(3.0))
|
||||
|
||||
// Test reopening a namespace (C++ allows this)
|
||||
namespace math {
|
||||
def cube(x) { x * x * x }
|
||||
}
|
||||
|
||||
assert_equal(27, math::cube(3))
|
||||
|
||||
// Test that :: scope resolution works the same as . for access
|
||||
assert_equal(16, math.square(4))
|
||||
assert_equal(6.0, math.trig.double_angle(3.0))
|
||||
|
||||
// Test namespace with var declarations
|
||||
namespace config {
|
||||
var pi = 3.14159
|
||||
var name = "test"
|
||||
}
|
||||
|
||||
assert_equal(3.14159, config::pi)
|
||||
assert_equal("test", config::name)
|
||||
|
||||
// Test function-call style still works
|
||||
namespace("compat")
|
||||
compat.legacy = 99
|
||||
assert_equal(99, compat::legacy)
|
||||
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