Compare commits

..

No commits in common. "develop" and "wasm-latest" have entirely different histories.

83 changed files with 18946 additions and 31901 deletions

View File

@ -1,14 +1,10 @@
# clang-format: 19 # clang-format: 11
AccessModifierOffset: -2 AccessModifierOffset: -2
AlignAfterOpenBracket: Align AlignAfterOpenBracket: Align
AlignConsecutiveBitFields: false AlignConsecutiveBitFields: false
AllowShortBlocksOnASingleLine: Always AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLambdasOnASingleLine: All AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakTemplateDeclarations: true AlwaysBreakTemplateDeclarations: true
BasedOnStyle: WebKit BasedOnStyle: WebKit
BinPackArguments: true BinPackArguments: true
@ -17,14 +13,12 @@ BreakBeforeBraces: Attach
ColumnLimit: 0 ColumnLimit: 0
Cpp11BracedListStyle: true Cpp11BracedListStyle: true
FixNamespaceComments: true FixNamespaceComments: true
IfMacros: ['SECTION']
IncludeBlocks: Preserve IncludeBlocks: Preserve
IndentCaseLabels: true IndentCaseLabels: true
IndentPPDirectives: None IndentPPDirectives: None
IndentWidth: 2 IndentWidth: 2
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
NamespaceIndentation: All NamespaceIndentation: All
PackConstructorInitializers: CurrentLine
PenaltyBreakBeforeFirstCallParameter: 200 PenaltyBreakBeforeFirstCallParameter: 200
PenaltyBreakComment: 5 PenaltyBreakComment: 5
PenaltyBreakFirstLessLess: 50 PenaltyBreakFirstLessLess: 50
@ -33,10 +27,6 @@ PointerAlignment: Right
SortIncludes: true SortIncludes: true
SpaceAfterTemplateKeyword: false SpaceAfterTemplateKeyword: false
SpaceBeforeCpp11BracedList: false SpaceBeforeCpp11BracedList: false
SpaceBeforeParens: Custom
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterIfMacros: false
SpaceInEmptyBlock: false SpaceInEmptyBlock: false
Standard: Latest Standard: Latest
TabWidth: 2 TabWidth: 2

View File

@ -1,27 +0,0 @@
name: auto-clang-format
on:
push:
branches:
- develop
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: DoozyX/clang-format-lint-action@v0.20
with:
source: '.'
exclude: './third_party ./external ./unittests/catch.hpp'
extensions: 'h,cpp,hpp'
clangFormatVersion: 19
inplace: True
- uses: EndBug/add-and-commit@v9
with:
author_name: Clang Robot
author_email: robot@example.com
message: ':art: Committing clang-format changes'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -9,47 +9,39 @@ on:
jobs: jobs:
linux: linux:
name: Linux GCC ${{ matrix.build_type }} threads=${{ matrix.multithread }} name: Linux GCC ${{ matrix.build_type }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
build_type: [Debug, Release] build_type: [Debug, Release]
multithread: [ON, OFF]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Ninja
run: sudo apt-get install -y ninja-build
- name: Configure - name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMULTITHREAD_SUPPORT_ENABLED=${{ matrix.multithread }} run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
- name: Build - name: Build
run: cmake --build build run: cmake --build build -j
- name: Test - name: Test
run: ctest --test-dir build --output-on-failure run: ctest --test-dir build --output-on-failure
macos: macos:
name: macOS AppleClang ${{ matrix.build_type }} threads=${{ matrix.multithread }} name: macOS AppleClang ${{ matrix.build_type }}
runs-on: macos-latest runs-on: macos-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
build_type: [Debug, Release] build_type: [Debug, Release]
multithread: [ON, OFF]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Ninja
run: brew install ninja
- name: Configure - name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMULTITHREAD_SUPPORT_ENABLED=${{ matrix.multithread }} run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
- name: Build - name: Build
run: cmake --build build run: cmake --build build -j
- name: Test - name: Test
run: ctest --test-dir build --output-on-failure run: ctest --test-dir build --output-on-failure
@ -64,14 +56,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Ninja
run: sudo apt-get install -y ninja-build
- name: Configure - name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON
- name: Build - name: Build
run: cmake --build build run: cmake --build build -j
- name: Test - name: Test
run: ctest --test-dir build --output-on-failure run: ctest --test-dir build --output-on-failure
@ -86,31 +75,27 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Ninja
run: brew install ninja
- name: Configure - name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON
- name: Build - name: Build
run: cmake --build build run: cmake --build build -j
- name: Test - name: Test
run: ctest --test-dir build --output-on-failure run: ctest --test-dir build --output-on-failure
windows: windows:
name: Windows MSVC ${{ matrix.build_type }} threads=${{ matrix.multithread }} name: Windows MSVC ${{ matrix.build_type }}
runs-on: windows-latest runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
build_type: [Debug, Release] build_type: [Debug, Release]
multithread: [ON, OFF]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Configure - name: Configure
run: cmake -B build -DMULTITHREAD_SUPPORT_ENABLED=${{ matrix.multithread }} run: cmake -B build
- name: Build - name: Build
run: cmake --build build --config ${{ matrix.build_type }} -j run: cmake --build build --config ${{ matrix.build_type }} -j
@ -128,14 +113,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Ninja
run: sudo apt-get install -y ninja-build
- name: Configure - name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_THREAD_SANITIZER=ON -DMULTITHREAD_SUPPORT_ENABLED=ON run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_THREAD_SANITIZER=ON
- name: Build - name: Build
run: cmake --build build run: cmake --build build -j
- name: Test - name: Test
run: ctest --test-dir build --output-on-failure run: ctest --test-dir build --output-on-failure
@ -150,14 +132,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Ninja
run: brew install ninja
- name: Configure - name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_THREAD_SANITIZER=ON -DMULTITHREAD_SUPPORT_ENABLED=ON run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_THREAD_SANITIZER=ON
- name: Build - name: Build
run: cmake --build build run: cmake --build build -j
- name: Test - name: Test
run: ctest --test-dir build --output-on-failure run: ctest --test-dir build --output-on-failure

View File

@ -155,7 +155,7 @@ else()
endif() endif()
if(MSVC) if(MSVC)
add_definitions(/WX /W4 /w14545 /w34242 /w34254 /w34287 /w44263 /w44265 /w44296 /w44311 /w44826 /we4289 /w14546 /w14547 /w14549 /w14555 /w14619 /w14905 /w14906 /w14928) add_definitions(/W4 /w14545 /w34242 /w34254 /w34287 /w44263 /w44265 /w44296 /w44311 /w44826 /we4289 /w14546 /w14547 /w14549 /w14555 /w14619 /w14905 /w14906 /w14928)
if(MSVC_VERSION STREQUAL "1800") if(MSVC_VERSION STREQUAL "1800")
# VS2013 doesn't have magic statics # VS2013 doesn't have magic statics
@ -175,10 +175,10 @@ if(MSVC)
# how to workaround or fix the error. So I'm disabling it globally. # how to workaround or fix the error. So I'm disabling it globally.
add_definitions(/wd4503) add_definitions(/wd4503)
else() else()
add_definitions(-Werror -Wall -Wextra -Wconversion -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wcast-qual -Wunused -Woverloaded-virtual -Wno-noexcept-type -Wpedantic -Werror=return-type) add_definitions(-Wall -Wextra -Wconversion -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wcast-qual -Wunused -Woverloaded-virtual -Wno-noexcept-type -Wpedantic -Werror=return-type)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
add_definitions(-Weverything -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-documentation -Wno-switch-enum -Wno-weak-vtables -Wno-missing-prototypes -Wno-padded -Wno-missing-noreturn -Wno-exit-time-destructors -Wno-documentation-unknown-command -Wno-unused-template -Wno-undef -Wno-double-promotion -Wno-switch-default -Wno-nrvo -Wno-shadow-uncaptured-local -Wno-unsafe-buffer-usage-in-libc-call -Wno-c++20-extensions -Wno-unknown-warning-option -Wno-poison-system-directories -Wno-c++20-compat -Wno-c++17-compat) add_definitions(-Weverything -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-documentation -Wno-switch-enum -Wno-weak-vtables -Wno-missing-prototypes -Wno-padded -Wno-missing-noreturn -Wno-exit-time-destructors -Wno-documentation-unknown-command -Wno-unused-template -Wno-undef -Wno-double-promotion)
else() else()
add_definitions(-Wnoexcept) add_definitions(-Wnoexcept)
endif() endif()
@ -292,16 +292,6 @@ endif()
file(GLOB UNIT_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/unittests/ ${CMAKE_CURRENT_SOURCE_DIR}/unittests/*.chai ${CMAKE_CURRENT_SOURCE_DIR}/unittests/3.x/*.chai) file(GLOB UNIT_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/unittests/ ${CMAKE_CURRENT_SOURCE_DIR}/unittests/*.chai ${CMAKE_CURRENT_SOURCE_DIR}/unittests/3.x/*.chai)
list(SORT UNIT_TESTS) list(SORT UNIT_TESTS)
if(NOT MULTITHREAD_SUPPORT_ENABLED)
list(REMOVE_ITEM UNIT_TESTS
async_engine_lifetime.chai
async_return_value.chai
future.chai
list_push_front.chai
move_async.chai
)
endif()
file(GLOB PERFORMANCE_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/performance_tests/ ${CMAKE_CURRENT_SOURCE_DIR}/performance_tests/*.chai) file(GLOB PERFORMANCE_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/performance_tests/ ${CMAKE_CURRENT_SOURCE_DIR}/performance_tests/*.chai)
list(SORT PERFORMANCE_TESTS) list(SORT PERFORMANCE_TESTS)
@ -390,16 +380,8 @@ if(BUILD_TESTING)
) )
if(NOT UNIT_TEST_LIGHT) if(NOT UNIT_TEST_LIGHT)
add_library(catch2 STATIC unittests/catch_amalgamated.cpp)
if(NOT MSVC)
target_compile_options(catch2 PRIVATE -Wno-conversion -Wno-noexcept -Wno-maybe-uninitialized)
endif()
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
target_compile_options(catch2 PUBLIC -Wno-unknown-warning-option -Wno-covered-switch-default -Wno-disabled-macro-expansion -Wno-unsafe-buffer-usage -Wno-unused-macros)
endif()
add_executable(compiled_tests unittests/compiled_tests.cpp) add_executable(compiled_tests unittests/compiled_tests.cpp)
target_link_libraries(compiled_tests catch2 ${LIBS} ${CHAISCRIPT_LIBS}) target_link_libraries(compiled_tests ${LIBS} ${CHAISCRIPT_LIBS})
catch_discover_tests(compiled_tests TEST_PREFIX "compiled.") catch_discover_tests(compiled_tests TEST_PREFIX "compiled.")
add_executable(static_chaiscript_test unittests/static_chaiscript.cpp) add_executable(static_chaiscript_test unittests/static_chaiscript.cpp)
@ -411,7 +393,7 @@ if(BUILD_TESTING)
add_test(NAME Boxed_Cast_Test COMMAND boxed_cast_test) add_test(NAME Boxed_Cast_Test COMMAND boxed_cast_test)
add_executable(type_info_test unittests/type_info_test.cpp) add_executable(type_info_test unittests/type_info_test.cpp)
target_link_libraries(type_info_test catch2 ${LIBS}) target_link_libraries(type_info_test ${LIBS})
add_test(NAME Type_Info_Test COMMAND type_info_test) add_test(NAME Type_Info_Test COMMAND type_info_test)
add_executable(c_linkage_test unittests/c_linkage_test.cpp) add_executable(c_linkage_test unittests/c_linkage_test.cpp)
@ -431,9 +413,6 @@ if(BUILD_TESTING)
"CHAI_USE_PATH=${CMAKE_CURRENT_SOURCE_DIR}/unittests/" "CHAI_USE_PATH=${CMAKE_CURRENT_SOURCE_DIR}/unittests/"
"CHAI_MODULE_PATH=${CMAKE_CURRENT_BINARY_DIR}/" "CHAI_MODULE_PATH=${CMAKE_CURRENT_BINARY_DIR}/"
) )
add_executable(async_engine_lifetime_test unittests/async_engine_lifetime_test.cpp)
target_link_libraries(async_engine_lifetime_test ${LIBS})
add_test(NAME Async_Engine_Lifetime_Test COMMAND async_engine_lifetime_test)
endif() endif()
add_executable(multifile_test add_executable(multifile_test
@ -448,18 +427,6 @@ if(BUILD_TESTING)
target_link_libraries(emscripten_eval_test ${LIBS}) target_link_libraries(emscripten_eval_test ${LIBS})
add_test(NAME Emscripten_Eval_Test COMMAND emscripten_eval_test) 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(emscripten_state_test unittests/emscripten_state_test.cpp)
target_link_libraries(emscripten_state_test ${LIBS})
add_test(NAME Emscripten_State_Test COMMAND emscripten_state_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)
install(TARGETS test_module RUNTIME DESTINATION bin LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/chaiscript") install(TARGETS test_module RUNTIME DESTINATION bin LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/chaiscript")
endif() endif()
endif() endif()

View File

@ -16,67 +16,6 @@ chaiscript::ChaiScript chai; // initializes ChaiScript, adding the standard Chai
Note that ChaiScript cannot be used as a global / static object unless it is being compiled with `CHAISCRIPT_NO_THREADS`. Note that ChaiScript cannot be used as a global / static object unless it is being compiled with `CHAISCRIPT_NO_THREADS`.
## Engine Options (`Options`)
Engine-level options control which scripting capabilities are exposed. These are passed as a `std::vector<Options>` to the `ChaiScript` or `ChaiScript_Basic` constructor.
| Option | Effect |
|--------|--------|
| `Options::Load_Modules` | Enables `load_module()` in scripts (default) |
| `Options::No_Load_Modules` | Disables `load_module()` |
| `Options::External_Scripts` | Enables `use()` and `eval_file()` in scripts (default) |
| `Options::No_External_Scripts` | Disables `use()` and `eval_file()` |
```cpp
// Sandboxed engine: no dynamic module loading, no external script evaluation
chaiscript::ChaiScript chai({}, {},
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts});
```
## Library Options (`Library_Options`)
Library-level options control which parts of the standard library are registered. These are passed as a `std::vector<Library_Options>`.
| Option | Effect |
|--------|--------|
| `Library_Options::No_Stdlib` | Disables the entire standard library (types, I/O, prelude, JSON — everything) |
| `Library_Options::No_IO` | Disables `print_string` and `println_string` (and the prelude's `print`/`puts` wrappers) |
| `Library_Options::No_Prelude` | Disables the ChaiScript prelude (`print`, `puts`, `filter`, `map`, `foldl`, `join`, etc.) |
| `Library_Options::No_JSON` | Disables `from_json` and `to_json` |
With the `ChaiScript` convenience class, pass library options as the fourth constructor parameter:
```cpp
// No I/O functions
chaiscript::ChaiScript chai({}, {}, chaiscript::default_options(),
{chaiscript::Library_Options::No_IO});
// No JSON support
chaiscript::ChaiScript chai({}, {}, chaiscript::default_options(),
{chaiscript::Library_Options::No_JSON});
// Completely bare engine — no stdlib at all
chaiscript::ChaiScript chai({}, {}, chaiscript::default_options(),
{chaiscript::Library_Options::No_Stdlib});
// Combine both: no external scripts and no I/O
chaiscript::ChaiScript chai({}, {},
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts},
{chaiscript::Library_Options::No_IO});
```
With `ChaiScript_Basic`, pass library options directly to `Std_Lib::library()`:
```cpp
chaiscript::ChaiScript_Basic chai(
chaiscript::Std_Lib::library({chaiscript::Library_Options::No_IO}),
create_chaiscript_parser(),
{}, {},
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts});
```
Note: `No_Prelude` disables the prelude script which defines convenience functions like `print` (which wraps `print_string`). If you disable the prelude but not I/O, `print_string` and `println_string` are still available.
# Adding Things To The Engine # Adding Things To The Engine
## Adding a Function / Method / Member ## Adding a Function / Method / Member
@ -665,124 +604,6 @@ copy.width = 99
print(original.width) // still 10 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 ## Dynamic Objects
All ChaiScript defined types and generic Dynamic_Object support dynamic parameters All ChaiScript defined types and generic Dynamic_Object support dynamic parameters
@ -829,94 +650,6 @@ class My_Class {
}; };
``` ```
## Strong Typedefs
Strong typedefs create distinct types that are not interchangeable with their underlying type
or with other typedefs of the same underlying type. They use `Dynamic_Object` internally and
automatically expose operators that the underlying type supports.
### Basic Usage
```
using Meters = int
using Seconds = int
var d = Meters(100)
var t = Seconds(10)
// d and t are distinct types — you cannot accidentally mix them
// Meters + Seconds would require an explicit conversion
```
### Arithmetic and Comparison
Operators from the underlying type are forwarded and remain strongly typed:
```
using Meters = int
var a = Meters(10)
var b = Meters(20)
var c = a + b // Meters(30) — result is still Meters
var bigger = b > a // true — comparisons return bool
// Compound assignment operators work too
a += b // a is now Meters(30)
```
### String-Based Strong Typedefs
Strong typedefs work with any type, not just numeric types:
```
using Name = string
var n = Name("Alice")
var greeting = Name("Hello, ") + Name("world") // Name — string concatenation is forwarded
```
### Accessing the Underlying Value
Use `to_underlying` to extract the wrapped value:
```
using Meters = int
var d = Meters(42)
var raw = to_underlying(d) // 42, plain int
```
### Extending Strong Typedefs
You can add custom operations to strong typedefs just like any other ChaiScript type:
```
using Meters = int
using Seconds = int
using MetersPerSecond = int
def speed(Meters d, Seconds t) {
MetersPerSecond(to_underlying(d) / to_underlying(t))
}
var s = speed(Meters(100), Seconds(10)) // MetersPerSecond(10)
```
You can also overload operators between different strong typedefs:
```
using Meters = int
using Feet = int
def to_feet(Meters m) {
Feet((to_underlying(m) * 328) / 100)
}
var m = Meters(10)
var f = to_feet(m) // Feet(32)
```
## method_missing ## method_missing
A function of the signature `method_missing(object, name, param1, param2, param3)` will be called if an appropriate A function of the signature `method_missing(object, name, param1, param2, param3)` will be called if an appropriate
@ -1082,88 +815,5 @@ m.is_type("MyClass") // true (checks the ChaiScript class name)
* `from_json` converts a JSON string into its strongly typed (map, vector, int, double, string) representations * `from_json` converts a JSON string into its strongly typed (map, vector, int, double, string) representations
* `to_json` converts a ChaiScript object (either a `Object` or one of map, vector, int, double, string) tree into its JSON string representation * `to_json` converts a ChaiScript object (either a `Object` or one of map, vector, int, double, string) tree into its JSON string representation
## IO Redirection
By default, ChaiScript's `print()` and `puts()` functions write to stdout. You can redirect
output on a per-instance basis by setting a single print handler. Both `println_string`
(used by `print()`) and `print_string` (used by `puts()`) dispatch through the same handler —
`println_string` simply appends a newline before calling it.
```cpp
chaiscript::ChaiScript chai;
// Redirect all output (print_string and println_string both use this handler)
chai.set_print_handler([](const std::string &s) {
my_log_window.append(s);
});
```
This is useful for embedding ChaiScript in GUI applications, logging frameworks, or any
context where stdout is not the desired output destination.
```cpp
// Example: capture all output to a string
std::string captured;
chai.set_print_handler([&captured](const std::string &s) {
captured += s;
});
chai.eval("print(42)"); // captured == "42\n"
chai.eval("puts(\"hi\")"); // captured == "42\nhi"
```
The print handler can also be set from within ChaiScript itself via `set_print_handler`:
```chaiscript
// Redirect output from within a script
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 ## 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.) 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**.

View File

@ -33,13 +33,6 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
[TEST_SUFFIX suffix] [TEST_SUFFIX suffix]
[PROPERTIES name1 value1...] [PROPERTIES name1 value1...]
[TEST_LIST var] [TEST_LIST var]
[REPORTER reporter]
[OUTPUT_DIR dir]
[OUTPUT_PREFIX prefix]
[OUTPUT_SUFFIX suffix]
[DISCOVERY_MODE <POST_BUILD|PRE_TEST>]
[SKIP_IS_FAILURE]
[ADD_TAGS_AS_LABELS]
) )
``catch_discover_tests`` sets up a post-build command on the test executable ``catch_discover_tests`` sets up a post-build command on the test executable
@ -97,118 +90,36 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
executable is being used in multiple calls to ``catch_discover_tests()``. executable is being used in multiple calls to ``catch_discover_tests()``.
Note that this variable is only available in CTest. Note that this variable is only available in CTest.
``REPORTER reporter``
Use the specified reporter when running the test case. The reporter will
be passed to the Catch executable as ``--reporter reporter``.
``OUTPUT_DIR dir``
If specified, the parameter is passed along as
``--out dir/<test_name>`` to Catch executable. The actual file name is the
same as the test name. This should be used instead of
``EXTRA_ARGS --out foo`` to avoid race conditions writing the result output
when using parallel test execution.
``OUTPUT_PREFIX prefix``
May be used in conjunction with ``OUTPUT_DIR``.
If specified, ``prefix`` is added to each output file name, like so
``--out dir/prefix<test_name>``.
``OUTPUT_SUFFIX suffix``
May be used in conjunction with ``OUTPUT_DIR``.
If specified, ``suffix`` is added to each output file name, like so
``--out dir/<test_name>suffix``. This can be used to add a file extension to
the output e.g. ".xml".
``DL_PATHS path...``
Specifies paths that need to be set for the dynamic linker to find shared
libraries/DLLs when running the test executable (PATH/LD_LIBRARY_PATH respectively).
These paths will both be set when retrieving the list of test cases from the
test executable and when the tests are executed themselves. This requires
cmake/ctest >= 3.22.
``DL_FRAMEWORK_PATHS path...``
Specifies paths that need to be set for the dynamic linker to find libraries
packaged as frameworks on Apple platforms when running the test executable
(DYLD_FRAMEWORK_PATH). These paths will both be set when retrieving the list
of test cases from the test executable and when the tests are executed themselves.
This requires cmake/ctest >= 3.22.
``DISCOVERY_MODE mode``
Provides control over when ``catch_discover_tests`` performs test discovery.
By default, ``POST_BUILD`` sets up a post-build command to perform test discovery
at build time. In certain scenarios, like cross-compiling, this ``POST_BUILD``
behavior is not desirable. By contrast, ``PRE_TEST`` delays test discovery until
just prior to test execution. This way test discovery occurs in the target environment
where the test has a better chance at finding appropriate runtime dependencies.
``DISCOVERY_MODE`` defaults to the value of the
``CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE`` variable if it is not passed when
calling ``catch_discover_tests``. This provides a mechanism for globally selecting
a preferred test discovery behavior without having to modify each call site.
``SKIP_IS_FAILURE``
Disables skipped test detection.
``ADD_TAGS_AS_LABELS``
Adds all test tags as CTest labels.
#]=======================================================================] #]=======================================================================]
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
function(catch_discover_tests TARGET) function(catch_discover_tests TARGET)
cmake_parse_arguments( cmake_parse_arguments(
"" ""
"SKIP_IS_FAILURE;ADD_TAGS_AS_LABELS" ""
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;REPORTER;OUTPUT_DIR;OUTPUT_PREFIX;OUTPUT_SUFFIX;DISCOVERY_MODE" "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST"
"TEST_SPEC;EXTRA_ARGS;PROPERTIES;DL_PATHS;DL_FRAMEWORK_PATHS" "TEST_SPEC;EXTRA_ARGS;PROPERTIES"
${ARGN} ${ARGN}
) )
if(${CMAKE_VERSION} VERSION_LESS "3.19")
message(FATAL_ERROR "This script requires JSON support from CMake version 3.19 or greater.")
endif()
if(NOT _WORKING_DIRECTORY) if(NOT _WORKING_DIRECTORY)
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif() endif()
if(NOT _TEST_LIST) if(NOT _TEST_LIST)
set(_TEST_LIST ${TARGET}_TESTS) set(_TEST_LIST ${TARGET}_TESTS)
endif() endif()
if(_DL_PATHS AND ${CMAKE_VERSION} VERSION_LESS "3.22.0")
message(FATAL_ERROR "The DL_PATHS option requires at least cmake 3.22")
endif()
if(_DL_FRAMEWORK_PATHS AND ${CMAKE_VERSION} VERSION_LESS "3.22.0")
message(FATAL_ERROR "The DL_FRAMEWORK_PATHS option requires at least cmake 3.22")
endif()
if(NOT _DISCOVERY_MODE)
if(NOT CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE)
set(CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE "POST_BUILD")
endif()
set(_DISCOVERY_MODE ${CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE})
endif()
if(NOT _DISCOVERY_MODE MATCHES "^(POST_BUILD|PRE_TEST)$")
message(FATAL_ERROR "Unknown DISCOVERY_MODE: ${_DISCOVERY_MODE}")
endif()
## Generate a unique name based on the extra arguments ## Generate a unique name based on the extra arguments
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}") string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}")
string(SUBSTRING ${args_hash} 0 7 args_hash) string(SUBSTRING ${args_hash} 0 7 args_hash)
# Define rule to generate test list for aforementioned test executable # Define rule to generate test list for aforementioned test executable
set(ctest_file_base "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-${args_hash}") set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
set(ctest_include_file "${ctest_file_base}_include.cmake") set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
set(ctest_tests_file "${ctest_file_base}_tests.cmake")
get_property(crosscompiling_emulator get_property(crosscompiling_emulator
TARGET ${TARGET} TARGET ${TARGET}
PROPERTY CROSSCOMPILING_EMULATOR PROPERTY CROSSCOMPILING_EMULATOR
) )
if(NOT _SKIP_IS_FAILURE)
set(_PROPERTIES ${_PROPERTIES} SKIP_RETURN_CODE 4)
endif()
if(_DISCOVERY_MODE STREQUAL "POST_BUILD")
add_custom_command( add_custom_command(
TARGET ${TARGET} POST_BUILD TARGET ${TARGET} POST_BUILD
BYPRODUCTS "${ctest_tests_file}" BYPRODUCTS "${ctest_tests_file}"
@ -223,14 +134,7 @@ function(catch_discover_tests TARGET)
-D "TEST_PREFIX=${_TEST_PREFIX}" -D "TEST_PREFIX=${_TEST_PREFIX}"
-D "TEST_SUFFIX=${_TEST_SUFFIX}" -D "TEST_SUFFIX=${_TEST_SUFFIX}"
-D "TEST_LIST=${_TEST_LIST}" -D "TEST_LIST=${_TEST_LIST}"
-D "TEST_REPORTER=${_REPORTER}"
-D "TEST_OUTPUT_DIR=${_OUTPUT_DIR}"
-D "TEST_OUTPUT_PREFIX=${_OUTPUT_PREFIX}"
-D "TEST_OUTPUT_SUFFIX=${_OUTPUT_SUFFIX}"
-D "TEST_DL_PATHS=${_DL_PATHS}"
-D "TEST_DL_FRAMEWORK_PATHS=${_DL_FRAMEWORK_PATHS}"
-D "CTEST_FILE=${ctest_tests_file}" -D "CTEST_FILE=${ctest_tests_file}"
-D "ADD_TAGS_AS_LABELS=${_ADD_TAGS_AS_LABELS}"
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}" -P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
VERBATIM VERBATIM
) )
@ -243,70 +147,24 @@ function(catch_discover_tests TARGET)
"endif()\n" "endif()\n"
) )
elseif(_DISCOVERY_MODE STREQUAL "PRE_TEST") if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL
PROPERTY GENERATOR_IS_MULTI_CONFIG
)
if(GENERATOR_IS_MULTI_CONFIG)
set(ctest_tests_file "${ctest_file_base}_tests-$<CONFIG>.cmake")
endif()
string(CONCAT ctest_include_content
"if(EXISTS \"$<TARGET_FILE:${TARGET}>\")" "\n"
" if(NOT EXISTS \"${ctest_tests_file}\" OR" "\n"
" NOT \"${ctest_tests_file}\" IS_NEWER_THAN \"$<TARGET_FILE:${TARGET}>\" OR\n"
" NOT \"${ctest_tests_file}\" IS_NEWER_THAN \"\${CMAKE_CURRENT_LIST_FILE}\")\n"
" include(\"${_CATCH_DISCOVER_TESTS_SCRIPT}\")" "\n"
" catch_discover_tests_impl(" "\n"
" TEST_EXECUTABLE" " [==[" "$<TARGET_FILE:${TARGET}>" "]==]" "\n"
" TEST_EXECUTOR" " [==[" "${crosscompiling_emulator}" "]==]" "\n"
" TEST_WORKING_DIR" " [==[" "${_WORKING_DIRECTORY}" "]==]" "\n"
" TEST_SPEC" " [==[" "${_TEST_SPEC}" "]==]" "\n"
" TEST_EXTRA_ARGS" " [==[" "${_EXTRA_ARGS}" "]==]" "\n"
" TEST_PROPERTIES" " [==[" "${_PROPERTIES}" "]==]" "\n"
" TEST_PREFIX" " [==[" "${_TEST_PREFIX}" "]==]" "\n"
" TEST_SUFFIX" " [==[" "${_TEST_SUFFIX}" "]==]" "\n"
" TEST_LIST" " [==[" "${_TEST_LIST}" "]==]" "\n"
" TEST_REPORTER" " [==[" "${_REPORTER}" "]==]" "\n"
" TEST_OUTPUT_DIR" " [==[" "${_OUTPUT_DIR}" "]==]" "\n"
" TEST_OUTPUT_PREFIX" " [==[" "${_OUTPUT_PREFIX}" "]==]" "\n"
" TEST_OUTPUT_SUFFIX" " [==[" "${_OUTPUT_SUFFIX}" "]==]" "\n"
" CTEST_FILE" " [==[" "${ctest_tests_file}" "]==]" "\n"
" TEST_DL_PATHS" " [==[" "${_DL_PATHS}" "]==]" "\n"
" TEST_DL_FRAMEWORK_PATHS" " [==[" "${_DL_FRAMEWORK_PATHS}" "]==]" "\n"
" ADD_TAGS_AS_LABELS" " [==[" "${_ADD_TAGS_AS_LABELS}" "]==]" "\n"
" )" "\n"
" endif()" "\n"
" include(\"${ctest_tests_file}\")" "\n"
"else()" "\n"
" add_test(${TARGET}_NOT_BUILT ${TARGET}_NOT_BUILT)" "\n"
"endif()" "\n"
)
if(GENERATOR_IS_MULTI_CONFIG)
foreach(_config ${CMAKE_CONFIGURATION_TYPES})
file(GENERATE OUTPUT "${ctest_file_base}_include-${_config}.cmake" CONTENT "${ctest_include_content}" CONDITION $<CONFIG:${_config}>)
endforeach()
string(CONCAT ctest_include_multi_content
"if(NOT CTEST_CONFIGURATION_TYPE)" "\n"
" message(\"No configuration for testing specified, use '-C <cfg>'.\")" "\n"
"else()" "\n"
" include(\"${ctest_file_base}_include-\${CTEST_CONFIGURATION_TYPE}.cmake\")" "\n"
"endif()" "\n"
)
file(GENERATE OUTPUT "${ctest_include_file}" CONTENT "${ctest_include_multi_content}")
else()
file(GENERATE OUTPUT "${ctest_file_base}_include.cmake" CONTENT "${ctest_include_content}")
file(WRITE "${ctest_include_file}" "include(\"${ctest_file_base}_include.cmake\")")
endif()
endif()
# Add discovered tests to directory TEST_INCLUDE_FILES # Add discovered tests to directory TEST_INCLUDE_FILES
set_property(DIRECTORY set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
) )
else()
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
if (NOT ${test_include_file_set})
set_property(DIRECTORY
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
)
else()
message(FATAL_ERROR
"Cannot set more than one TEST_INCLUDE_FILE"
)
endif()
endif()
endfunction() endfunction()
@ -314,5 +172,4 @@ endfunction()
set(_CATCH_DISCOVER_TESTS_SCRIPT set(_CATCH_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
) )

View File

@ -1,12 +1,18 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying # Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details. # file Copyright.txt or https://cmake.org/licensing for details.
set(prefix "${TEST_PREFIX}")
set(suffix "${TEST_SUFFIX}")
set(spec ${TEST_SPEC})
set(extra_args ${TEST_EXTRA_ARGS})
set(properties ${TEST_PROPERTIES})
set(script)
set(suite)
set(tests)
function(add_command NAME) function(add_command NAME)
set(_args "") set(_args "")
# use ARGV* instead of ARGN, because ARGN splits arrays into multiple arguments foreach(_arg ${ARGN})
math(EXPR _last_arg ${ARGC}-1)
foreach(_n RANGE 1 ${_last_arg})
set(_arg "${ARGV${_n}}")
if(_arg MATCHES "[^-./:a-zA-Z0-9_]") if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
else() else()
@ -16,239 +22,55 @@ function(add_command NAME)
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
endfunction() endfunction()
function(catch_discover_tests_impl)
cmake_parse_arguments(
""
""
"TEST_EXECUTABLE;TEST_WORKING_DIR;TEST_OUTPUT_DIR;TEST_OUTPUT_PREFIX;TEST_OUTPUT_SUFFIX;TEST_PREFIX;TEST_REPORTER;TEST_SPEC;TEST_SUFFIX;TEST_LIST;CTEST_FILE"
"TEST_EXTRA_ARGS;TEST_PROPERTIES;TEST_EXECUTOR;TEST_DL_PATHS;TEST_DL_FRAMEWORK_PATHS;ADD_TAGS_AS_LABELS"
${ARGN}
)
set(add_tags "${_ADD_TAGS_AS_LABELS}")
set(prefix "${_TEST_PREFIX}")
set(suffix "${_TEST_SUFFIX}")
set(spec ${_TEST_SPEC})
set(extra_args ${_TEST_EXTRA_ARGS})
set(properties ${_TEST_PROPERTIES})
set(reporter ${_TEST_REPORTER})
set(output_dir ${_TEST_OUTPUT_DIR})
set(output_prefix ${_TEST_OUTPUT_PREFIX})
set(output_suffix ${_TEST_OUTPUT_SUFFIX})
set(dl_paths ${_TEST_DL_PATHS})
set(dl_framework_paths ${_TEST_DL_FRAMEWORK_PATHS})
set(environment_modifications "")
set(script)
set(suite)
set(tests)
if(WIN32)
set(dl_paths_variable_name PATH)
elseif(APPLE)
set(dl_paths_variable_name DYLD_LIBRARY_PATH)
else()
set(dl_paths_variable_name LD_LIBRARY_PATH)
endif()
# Run test executable to get list of available tests # Run test executable to get list of available tests
if(NOT EXISTS "${_TEST_EXECUTABLE}") if(NOT EXISTS "${TEST_EXECUTABLE}")
message(FATAL_ERROR message(FATAL_ERROR
"Specified test executable '${_TEST_EXECUTABLE}' does not exist" "Specified test executable '${TEST_EXECUTABLE}' does not exist"
) )
endif() endif()
if(dl_paths)
cmake_path(CONVERT "$ENV{${dl_paths_variable_name}}" TO_NATIVE_PATH_LIST env_dl_paths)
list(PREPEND env_dl_paths "${dl_paths}")
cmake_path(CONVERT "${env_dl_paths}" TO_NATIVE_PATH_LIST paths)
set(ENV{${dl_paths_variable_name}} "${paths}")
endif()
if(APPLE AND dl_framework_paths)
cmake_path(CONVERT "$ENV{DYLD_FRAMEWORK_PATH}" TO_NATIVE_PATH_LIST env_dl_framework_paths)
list(PREPEND env_dl_framework_paths "${dl_framework_paths}")
cmake_path(CONVERT "${env_dl_framework_paths}" TO_NATIVE_PATH_LIST paths)
set(ENV{DYLD_FRAMEWORK_PATH} "${paths}")
endif()
execute_process( execute_process(
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec} --list-tests --reporter json COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only
OUTPUT_VARIABLE listing_output OUTPUT_VARIABLE output
RESULT_VARIABLE result RESULT_VARIABLE result
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
) )
if(NOT ${result} EQUAL 0) # Catch --list-test-names-only reports the number of tests, so 0 is... surprising
if(${result} EQUAL 0)
message(WARNING
"Test executable '${TEST_EXECUTABLE}' contains no tests!\n"
)
elseif(${result} LESS 0)
message(FATAL_ERROR message(FATAL_ERROR
"Error listing tests from executable '${_TEST_EXECUTABLE}':\n" "Error running test executable '${TEST_EXECUTABLE}':\n"
" Result: ${result}\n" " Result: ${result}\n"
" Output: ${listing_output}\n" " Output: ${output}\n"
) )
endif() endif()
# Prepare reporter string(REPLACE "\n" ";" output "${output}")
if(reporter)
set(reporter_arg "--reporter ${reporter}")
# Run test executable to check whether reporter is available
# note that the use of --list-reporters is not the important part,
# we only want to check whether the execution succeeds with ${reporter_arg}
execute_process(
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec} ${reporter_arg} --list-reporters
OUTPUT_VARIABLE reporter_check_output
RESULT_VARIABLE reporter_check_result
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
)
if(${reporter_check_result} EQUAL 255)
message(FATAL_ERROR
"\"${reporter}\" is not a valid reporter!\n"
)
elseif(NOT ${reporter_check_result} EQUAL 0)
message(FATAL_ERROR
"Error checking for reporter in test executable '${_TEST_EXECUTABLE}':\n"
" Result: ${reporter_check_result}\n"
" Output: ${reporter_check_output}\n"
)
endif()
endif()
# Prepare output dir
if(output_dir AND NOT IS_ABSOLUTE ${output_dir})
set(output_dir "${_TEST_WORKING_DIR}/${output_dir}")
if(NOT EXISTS ${output_dir})
file(MAKE_DIRECTORY ${output_dir})
endif()
endif()
if(dl_paths)
foreach(path ${dl_paths})
cmake_path(NATIVE_PATH path native_path)
list(PREPEND environment_modifications "${dl_paths_variable_name}=path_list_prepend:${native_path}")
endforeach()
endif()
if(APPLE AND dl_framework_paths)
foreach(path ${dl_framework_paths})
cmake_path(NATIVE_PATH path native_path)
list(PREPEND environment_modifications "DYLD_FRAMEWORK_PATH=path_list_prepend:${native_path}")
endforeach()
endif()
# Parse JSON output for list of tests/class names/tags
string(JSON version GET "${listing_output}" "version")
if(NOT version STREQUAL "1")
message(FATAL_ERROR "Unsupported catch output version: '${version}'")
endif()
# Speed-up reparsing by cutting away unneeded parts of JSON.
string(JSON test_listing GET "${listing_output}" "listings" "tests")
string(JSON num_tests LENGTH "${test_listing}")
# Exit early if no tests are detected
if(num_tests STREQUAL "0")
file(WRITE "${_CTEST_FILE}" "")
return()
endif()
# CMake's foreach-RANGE is inclusive, so we have to subtract 1
math(EXPR num_tests "${num_tests} - 1")
foreach(idx RANGE ${num_tests})
string(JSON single_test GET ${test_listing} ${idx})
string(JSON test_tags GET "${single_test}" "tags")
string(JSON plain_name GET "${single_test}" "name")
# Escape characters in test case names that would be parsed by Catch2
# Note that the \ escaping must happen FIRST! Do not change the order.
set(escaped_name "${plain_name}")
foreach(char \\ , [ ] ;)
string(REPLACE ${char} "\\${char}" escaped_name "${escaped_name}")
endforeach(char)
# ...add output dir
if(output_dir)
string(REGEX REPLACE "[^A-Za-z0-9_]" "_" escaped_name_clean "${escaped_name}")
set(output_dir_arg "--out ${output_dir}/${output_prefix}${escaped_name_clean}${output_suffix}")
endif()
# Parse output
foreach(line ${output})
set(test ${line})
# ...and add to script # ...and add to script
add_command(add_test add_command(add_test
"${prefix}${plain_name}${suffix}" "${prefix}${test}${suffix}"
${_TEST_EXECUTOR} ${TEST_EXECUTOR}
"${_TEST_EXECUTABLE}" "${TEST_EXECUTABLE}"
"${escaped_name}" ${test}
${extra_args} ${extra_args}
"${reporter_arg}"
"${output_dir_arg}"
) )
add_command(set_tests_properties add_command(set_tests_properties
"${prefix}${plain_name}${suffix}" "${prefix}${test}${suffix}"
PROPERTIES PROPERTIES
WORKING_DIRECTORY "${_TEST_WORKING_DIR}" WORKING_DIRECTORY "${TEST_WORKING_DIR}"
${properties} ${properties}
) )
list(APPEND tests "${prefix}${test}${suffix}")
if(add_tags)
string(JSON num_tags LENGTH "${test_tags}")
math(EXPR num_tags "${num_tags} - 1")
set(tag_list "")
if(num_tags GREATER_EQUAL "0")
foreach(tag_idx RANGE ${num_tags})
string(JSON a_tag GET "${test_tags}" "${tag_idx}")
# Catch2's tags can contain semicolons, which are list element separators
# in CMake, so we have to escape them. Ideally we could use the [=[...]=]
# syntax for this, but CTest currently keeps the square quotes in the label
# name. So we add 2 backslashes to escape it instead.
# **IMPORTANT**: The number of backslashes depends on how many layers
# of CMake the tag goes. If this script is changed, the
# number of backslashes to escape may change as well.
string(REPLACE ";" "\\;" a_tag "${a_tag}")
list(APPEND tag_list "${a_tag}")
endforeach()
add_command(set_tests_properties
"${prefix}${plain_name}${suffix}"
PROPERTIES
LABELS "${tag_list}"
)
endif()
endif(add_tags)
if(environment_modifications)
add_command(set_tests_properties
"${prefix}${plain_name}${suffix}"
PROPERTIES
ENVIRONMENT_MODIFICATION "${environment_modifications}")
endif()
list(APPEND tests "${prefix}${plain_name}${suffix}")
endforeach() endforeach()
# Create a list of all discovered tests, which users may use to e.g. set # Create a list of all discovered tests, which users may use to e.g. set
# properties on the tests # properties on the tests
add_command(set ${_TEST_LIST} ${tests}) add_command(set ${TEST_LIST} ${tests})
# Write CTest script # Write CTest script
file(WRITE "${_CTEST_FILE}" "${script}") file(WRITE "${CTEST_FILE}" "${script}")
endfunction()
if(CMAKE_SCRIPT_MODE_FILE)
catch_discover_tests_impl(
TEST_EXECUTABLE ${TEST_EXECUTABLE}
TEST_EXECUTOR ${TEST_EXECUTOR}
TEST_WORKING_DIR ${TEST_WORKING_DIR}
TEST_SPEC ${TEST_SPEC}
TEST_EXTRA_ARGS ${TEST_EXTRA_ARGS}
TEST_PROPERTIES ${TEST_PROPERTIES}
TEST_PREFIX ${TEST_PREFIX}
TEST_SUFFIX ${TEST_SUFFIX}
TEST_LIST ${TEST_LIST}
TEST_REPORTER ${TEST_REPORTER}
TEST_OUTPUT_DIR ${TEST_OUTPUT_DIR}
TEST_OUTPUT_PREFIX ${TEST_OUTPUT_PREFIX}
TEST_OUTPUT_SUFFIX ${TEST_OUTPUT_SUFFIX}
TEST_DL_PATHS ${TEST_DL_PATHS}
TEST_DL_FRAMEWORK_PATHS ${TEST_DL_FRAMEWORK_PATHS}
CTEST_FILE ${CTEST_FILE}
ADD_TAGS_AS_LABELS ${ADD_TAGS_AS_LABELS}
)
endif()

View File

@ -1,72 +0,0 @@
# Copyright Catch2 Authors
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at
# https://www.boost.org/LICENSE_1_0.txt)
# SPDX-License-Identifier: BSL-1.0
# Supported optional args:
# * SHARD_COUNT - number of shards to split target's tests into
# * REPORTER - reporter spec to use for tests
# * TEST_SPEC - test spec used for filtering tests
function(catch_add_sharded_tests TARGET)
if(${CMAKE_VERSION} VERSION_LESS "3.10.0")
message(FATAL_ERROR "add_sharded_catch_tests only supports CMake versions 3.10.0 and up")
endif()
cmake_parse_arguments(
""
""
"SHARD_COUNT;REPORTER;TEST_SPEC"
""
${ARGN}
)
if(NOT DEFINED _SHARD_COUNT)
set(_SHARD_COUNT 2)
endif()
# Generate a unique name based on the extra arguments
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX} ${_SHARD_COUNT}")
string(SUBSTRING ${args_hash} 0 7 args_hash)
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-sharded-tests-include-${args_hash}.cmake")
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-sharded-tests-impl-${args_hash}.cmake")
file(WRITE "${ctest_include_file}"
"if(EXISTS \"${ctest_tests_file}\")\n"
" include(\"${ctest_tests_file}\")\n"
"else()\n"
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
"endif()\n"
)
set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
)
set(shard_impl_script_file "${_CATCH_DISCOVER_SHARD_TESTS_IMPL_SCRIPT}")
add_custom_command(
TARGET ${TARGET} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}"
-D "TARGET_NAME=${TARGET}"
-D "TEST_BINARY=$<TARGET_FILE:${TARGET}>"
-D "CTEST_FILE=${ctest_tests_file}"
-D "SHARD_COUNT=${_SHARD_COUNT}"
-D "REPORTER_SPEC=${_REPORTER}"
-D "TEST_SPEC=${_TEST_SPEC}"
-P "${shard_impl_script_file}"
VERBATIM
)
endfunction()
###############################################################################
set(_CATCH_DISCOVER_SHARD_TESTS_IMPL_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/CatchShardTestsImpl.cmake
CACHE INTERNAL "Catch2 full path to CatchShardTestsImpl.cmake helper file"
)

View File

@ -1,52 +0,0 @@
# Copyright Catch2 Authors
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at
# https://www.boost.org/LICENSE_1_0.txt)
# SPDX-License-Identifier: BSL-1.0
# Indirection for CatchShardTests that allows us to delay the script
# file generation until build time.
# Expected args:
# * TEST_BINARY - full path to the test binary to run sharded
# * CTEST_FILE - full path to ctest script file to write to
# * TARGET_NAME - name of the target to shard (used for test names)
# * SHARD_COUNT - number of shards to split the binary into
# Optional args:
# * REPORTER_SPEC - reporter specs to be passed down to the binary
# * TEST_SPEC - test spec to pass down to the test binary
if(NOT EXISTS "${TEST_BINARY}")
message(FATAL_ERROR
"Specified test binary '${TEST_BINARY}' does not exist"
)
endif()
set(other_args "")
if(TEST_SPEC)
set(other_args "${other_args} ${TEST_SPEC}")
endif()
if(REPORTER_SPEC)
set(other_args "${other_args} --reporter ${REPORTER_SPEC}")
endif()
# foreach RANGE in cmake is inclusive of the end, so we have to adjust it
math(EXPR adjusted_shard_count "${SHARD_COUNT} - 1")
file(WRITE "${CTEST_FILE}"
"string(RANDOM LENGTH 8 ALPHABET \"0123456789abcdef\" rng_seed)\n"
"\n"
"foreach(shard_idx RANGE ${adjusted_shard_count})\n"
" add_test(${TARGET_NAME}-shard-" [[${shard_idx}]] "/${adjusted_shard_count}\n"
" ${TEST_BINARY}"
" --shard-index " [[${shard_idx}]]
" --shard-count ${SHARD_COUNT}"
" --rng-seed " [[0x${rng_seed}]]
" --order rand"
"${other_args}"
"\n"
" )\n"
"endforeach()\n"
)

View File

@ -1,11 +1,9 @@
#==================================================================================================# #==================================================================================================#
# supported macros # # supported macros #
# - TEST_CASE, # # - TEST_CASE, #
# - TEMPLATE_TEST_CASE #
# - SCENARIO, # # - SCENARIO, #
# - TEST_CASE_METHOD, # # - TEST_CASE_METHOD, #
# - CATCH_TEST_CASE, # # - CATCH_TEST_CASE, #
# - CATCH_TEMPLATE_TEST_CASE #
# - CATCH_SCENARIO, # # - CATCH_SCENARIO, #
# - CATCH_TEST_CASE_METHOD. # # - CATCH_TEST_CASE_METHOD. #
# # # #
@ -41,24 +39,9 @@
# PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) # # PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) #
# -- causes CMake to rerun when file with tests changes so that new tests will be discovered # # -- causes CMake to rerun when file with tests changes so that new tests will be discovered #
# # # #
# One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way #
# a test should be run. For instance to use test MPI, one can write #
# set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC}) #
# just before calling this ParseAndAddCatchTests function #
# #
# The AdditionalCatchParameters optional variable can be used to pass extra argument to the test #
# command. For example, to include successful tests in the output, one can write #
# set(AdditionalCatchParameters --success) #
# #
# After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source #
# file in the target is set, and contains the list of the tests extracted from that target, or #
# from that file. This is useful, for example to add further labels or properties to the tests. #
# #
#==================================================================================================# #==================================================================================================#
if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8) cmake_minimum_required(VERSION 2.8.8)
message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer")
endif()
option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF) option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF)
option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF) option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF)
@ -66,7 +49,7 @@ option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the
option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON) option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON)
option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF) option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF)
function(ParseAndAddCatchTests_PrintDebugMessage) function(PrintDebugMessage)
if(PARSE_CATCH_TESTS_VERBOSE) if(PARSE_CATCH_TESTS_VERBOSE)
message(STATUS "ParseAndAddCatchTests: ${ARGV}") message(STATUS "ParseAndAddCatchTests: ${ARGV}")
endif() endif()
@ -77,7 +60,7 @@ endfunction()
# - full line comments (i.e. // ... ) # - full line comments (i.e. // ... )
# contents have been read into '${CppCode}'. # contents have been read into '${CppCode}'.
# !keep partial line comments # !keep partial line comments
function(ParseAndAddCatchTests_RemoveComments CppCode) function(RemoveComments CppCode)
string(ASCII 2 CMakeBeginBlockComment) string(ASCII 2 CMakeBeginBlockComment)
string(ASCII 3 CMakeEndBlockComment) string(ASCII 3 CMakeEndBlockComment)
string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}") string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}")
@ -89,30 +72,24 @@ function(ParseAndAddCatchTests_RemoveComments CppCode)
endfunction() endfunction()
# Worker function # Worker function
function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget) function(ParseFile SourceFile TestTarget)
# If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file.
if(SourceFile MATCHES "\\\$<TARGET_OBJECTS:.+>")
ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.")
return()
endif()
# According to CMake docs EXISTS behavior is well-defined only for full paths. # According to CMake docs EXISTS behavior is well-defined only for full paths.
get_filename_component(SourceFile ${SourceFile} ABSOLUTE) get_filename_component(SourceFile ${SourceFile} ABSOLUTE)
if(NOT EXISTS ${SourceFile}) if(NOT EXISTS ${SourceFile})
message(WARNING "Cannot find source file: ${SourceFile}") message(WARNING "Cannot find source file: ${SourceFile}")
return() return()
endif() endif()
ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}") PrintDebugMessage("parsing ${SourceFile}")
file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)
# Remove block and fullline comments # Remove block and fullline comments
ParseAndAddCatchTests_RemoveComments(Contents) RemoveComments(Contents)
# Find definition of test names # Find definition of test names
# https://regex101.com/r/JygOND/1 string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}")
string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([ \t\n]*\"[^\"]*\"[ \t\n]*(,[ \t\n]*\"[^\"]*\")?(,[ \t\n]*[^\,\)]*)*\\)[ \t\n]*\{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}")
if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)
ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
set_property( set_property(
DIRECTORY DIRECTORY
APPEND APPEND
@ -120,22 +97,14 @@ function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget)
) )
endif() endif()
# check CMP0110 policy for new add_test() behavior
if(POLICY CMP0110)
cmake_policy(GET CMP0110 _cmp0110_value) # new add_test() behavior
else()
# just to be thorough explicitly set the variable
set(_cmp0110_value)
endif()
foreach(TestName ${Tests}) foreach(TestName ${Tests})
# Strip newlines # Strip newlines
string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}")
# Get test type and fixture if applicable # Get test type and fixture if applicable
string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}")
string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}")
string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}") string(REPLACE "${TestType}(" "" TestFixture "${TestTypeAndFixture}")
# Get string parts of test definition # Get string parts of test definition
string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}")
@ -155,7 +124,7 @@ function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget)
if("${TestType}" STREQUAL "SCENARIO") if("${TestType}" STREQUAL "SCENARIO")
set(Name "Scenario: ${Name}") set(Name "Scenario: ${Name}")
endif() endif()
if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND "${TestType}" MATCHES "(CATCH_)?TEST_CASE_METHOD" AND TestFixture) if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture)
set(CTestName "${TestFixture}:${Name}") set(CTestName "${TestFixture}:${Name}")
else() else()
set(CTestName "${Name}") set(CTestName "${Name}")
@ -174,77 +143,43 @@ function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget)
endif() endif()
string(REPLACE "]" ";" Tags "${Tags}") string(REPLACE "]" ";" Tags "${Tags}")
string(REPLACE "[" "" Tags "${Tags}") string(REPLACE "[" "" Tags "${Tags}")
else()
# unset tags variable from previous loop
unset(Tags)
endif() endif()
list(APPEND Labels ${Tags}) list(APPEND Labels ${Tags})
list(FIND Labels "!hide" IndexOfHideLabel)
set(HiddenTagFound OFF) set(HiddenTagFound OFF)
foreach(label ${Labels}) foreach(label ${Labels})
string(REGEX MATCH "^!hide|^\\." result ${label}) string(REGEX MATCH "^!hide|^\\." result ${label})
if(result) if(result)
set(HiddenTagFound ON) set(HiddenTagFound ON)
break() break()
endif() endif(result)
endforeach(label) endforeach(label)
if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9") if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound})
ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label")
else() else()
ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"") PrintDebugMessage("Adding test \"${CTestName}\"")
if(Labels) if(Labels)
ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}") PrintDebugMessage("Setting labels to ${Labels}")
endif()
# Escape commas in the test spec
string(REPLACE "," "\\," Name ${Name})
# Work around CMake 3.18.0 change in `add_test()`, before the escaped quotes were necessary,
# only with CMake 3.18.0 the escaped double quotes confuse the call. This change is reverted in 3.18.1
# And properly introduced in 3.19 with the CMP0110 policy
if(_cmp0110_value STREQUAL "NEW" OR ${CMAKE_VERSION} VERSION_EQUAL "3.18")
ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to NEW, no need for add_test(\"\") workaround")
else()
ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to OLD adding \"\" for add_test() workaround")
set(CTestName "\"${CTestName}\"")
endif()
# Handle template test cases
if("${TestTypeAndFixture}" MATCHES ".*TEMPLATE_.*")
set(Name "${Name} - *")
endif() endif()
# Add the test and set its properties # Add the test and set its properties
add_test(NAME "${CTestName}" COMMAND ${OptionalCatchTestLauncher} $<TARGET_FILE:${TestTarget}> ${Name} ${AdditionalCatchParameters}) add_test(NAME "\"${CTestName}\"" COMMAND ${TestTarget} ${Name} ${AdditionalCatchParameters})
# Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran"
if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8")
ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property")
set_tests_properties("${CTestName}" PROPERTIES DISABLED ON)
else()
set_tests_properties("${CTestName}" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran"
LABELS "${Labels}") LABELS "${Labels}")
endif() endif()
set_property(
TARGET ${TestTarget}
APPEND
PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}")
set_property(
SOURCE ${SourceFile}
APPEND
PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}")
endif()
endforeach() endforeach()
endfunction() endfunction()
# entry point # entry point
function(ParseAndAddCatchTests TestTarget) function(ParseAndAddCatchTests TestTarget)
message(DEPRECATION "ParseAndAddCatchTest: function deprecated because of possibility of missed test cases. Consider using 'catch_discover_tests' from 'Catch.cmake'") PrintDebugMessage("Started parsing ${TestTarget}")
ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}")
get_target_property(SourceFiles ${TestTarget} SOURCES) get_target_property(SourceFiles ${TestTarget} SOURCES)
ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}") PrintDebugMessage("Found the following sources: ${SourceFiles}")
foreach(SourceFile ${SourceFiles}) foreach(SourceFile ${SourceFiles})
ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget}) ParseFile(${SourceFile} ${TestTarget})
endforeach() endforeach()
ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}") PrintDebugMessage("Finished parsing ${TestTarget}")
endfunction() endfunction()

View File

@ -17,14 +17,9 @@ add_definitions(-DCHAISCRIPT_NO_THREADS -DCHAISCRIPT_NO_DYNLOAD)
add_executable(chaiscript chaiscript_em.cpp) add_executable(chaiscript chaiscript_em.cpp)
target_include_directories(chaiscript PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include) 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 # Emscripten link flags: enable embind, allow memory growth, export as ES module-compatible
target_link_options(chaiscript PRIVATE target_link_options(chaiscript PRIVATE
--bind --bind
-fwasm-exceptions
-sALLOW_MEMORY_GROWTH=1 -sALLOW_MEMORY_GROWTH=1
-sEXPORT_ES6=0 -sEXPORT_ES6=0
-sMODULARIZE=0 -sMODULARIZE=0

View File

@ -185,8 +185,6 @@ print("10! = " + to_string(factorial(10)))
outputEl.scrollTop = outputEl.scrollHeight; outputEl.scrollTop = outputEl.scrollHeight;
} }
var chaiHandle = 0;
var Module = { var Module = {
print: function(text) { print: function(text) {
appendOutput(text); appendOutput(text);
@ -195,7 +193,6 @@ print("10! = " + to_string(factorial(10)))
appendOutput(text, 'output-error'); appendOutput(text, 'output-error');
}, },
onRuntimeInitialized: function() { onRuntimeInitialized: function() {
chaiHandle = Module.create();
statusEl.textContent = 'Ready'; statusEl.textContent = 'Ready';
statusEl.className = 'ready'; statusEl.className = 'ready';
btnRun.disabled = false; btnRun.disabled = false;
@ -208,7 +205,7 @@ print("10! = " + to_string(factorial(10)))
appendOutput('> Running...', 'output-line'); appendOutput('> Running...', 'output-line');
try { try {
Module.eval(chaiHandle, code); Module.eval(code);
} catch (e) { } catch (e) {
appendOutput('Error: ' + e.message, 'output-error'); appendOutput('Error: ' + e.message, 'output-error');
} }

View File

@ -13,16 +13,11 @@
#include <emscripten/bind.h> #include <emscripten/bind.h>
EMSCRIPTEN_BINDINGS(chaiscript) { EMSCRIPTEN_BINDINGS(chaiscript) {
emscripten::function("create", &chaiscript_create);
emscripten::function("destroy", &chaiscript_destroy);
emscripten::function("eval", &chaiscript_eval); emscripten::function("eval", &chaiscript_eval);
emscripten::function("evalString", &chaiscript_eval_string); emscripten::function("evalString", &chaiscript_eval_string);
emscripten::function("evalBool", &chaiscript_eval_bool); emscripten::function("evalBool", &chaiscript_eval_bool);
emscripten::function("evalInt", &chaiscript_eval_int); emscripten::function("evalInt", &chaiscript_eval_int);
emscripten::function("evalFloat", &chaiscript_eval_float); emscripten::function("evalFloat", &chaiscript_eval_float);
emscripten::function("evalDouble", &chaiscript_eval_double); emscripten::function("evalDouble", &chaiscript_eval_double);
emscripten::function("saveState", &chaiscript_save_state);
emscripten::function("restoreState", &chaiscript_restore_state);
emscripten::function("releaseState", &chaiscript_release_state);
} }
#endif #endif

View File

@ -7,118 +7,42 @@
// Shared eval helper functions for the ChaiScript Emscripten wrapper. // Shared eval helper functions for the ChaiScript Emscripten wrapper.
// These functions provide typed evaluation of ChaiScript expressions, // These functions provide typed evaluation of ChaiScript expressions,
// used by both the Emscripten/WebAssembly build and native tests. // used by both the Emscripten/WebAssembly build and native tests.
//
// The interface is opaque and handle-based: JS callers create one or more
// engines via chaiscript_create(), pass the resulting handle to the eval and
// state helpers, and release the engine with chaiscript_destroy() when done.
// State snapshots are themselves opaque handles produced by
// chaiscript_save_state() and consumed by chaiscript_restore_state() /
// chaiscript_release_state(). Hiding ChaiScript and ChaiScript::State behind
// integer handles keeps embind from having to manage their lifetimes and
// avoids forcing a static singleton on the C++ side.
#ifndef CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ #ifndef CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_
#define CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ #define CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_
#include <chaiscript/chaiscript.hpp>
#include <map>
#include <memory>
#include <string> #include <string>
#include <unordered_map> #include <chaiscript/chaiscript.hpp>
namespace detail { namespace detail {
// ChaiScript::State captures globals/functions/types but not the top-level inline chaiscript::ChaiScript &get_chai_instance() {
// scripting locals (variables created by `var x = ...` at the script's static chaiscript::ChaiScript chai;
// outermost scope). The playground's reset-between-runs use case needs both, return chai;
// so the snapshot pairs the engine state with the locals map.
struct Snapshot {
chaiscript::ChaiScript::State engine_state;
std::map<std::string, chaiscript::Boxed_Value> locals;
};
inline std::unordered_map<int, std::unique_ptr<chaiscript::ChaiScript>> &chai_registry() {
static std::unordered_map<int, std::unique_ptr<chaiscript::ChaiScript>> registry;
return registry;
}
inline std::unordered_map<int, Snapshot> &state_registry() {
static std::unordered_map<int, Snapshot> registry;
return registry;
}
inline int next_handle() {
static int handle = 0;
return ++handle;
}
inline chaiscript::ChaiScript &get_chai(const int handle) {
return *chai_registry().at(handle);
} }
} // namespace detail } // namespace detail
// Construct a fresh ChaiScript engine and return an opaque handle. The handle inline void chaiscript_eval(const std::string &input) {
// is owned by the caller and must be released with chaiscript_destroy(). detail::get_chai_instance().eval(input);
inline int chaiscript_create() {
const int handle = detail::next_handle();
detail::chai_registry().emplace(handle, std::make_unique<chaiscript::ChaiScript>());
return handle;
} }
// Destroy an engine handle. Unknown handles are silently ignored so JS callers inline std::string chaiscript_eval_string(const std::string &input) {
// do not need to track validity defensively. return detail::get_chai_instance().eval<std::string>(input);
inline void chaiscript_destroy(const int handle) {
detail::chai_registry().erase(handle);
} }
inline void chaiscript_eval(const int handle, const std::string &input) { inline bool chaiscript_eval_bool(const std::string &input) {
detail::get_chai(handle).eval(input); return detail::get_chai_instance().eval<bool>(input);
} }
inline std::string chaiscript_eval_string(const int handle, const std::string &input) { inline int chaiscript_eval_int(const std::string &input) {
return detail::get_chai(handle).eval<std::string>(input); return detail::get_chai_instance().eval<int>(input);
} }
inline bool chaiscript_eval_bool(const int handle, const std::string &input) { inline float chaiscript_eval_float(const std::string &input) {
return detail::get_chai(handle).eval<bool>(input); return detail::get_chai_instance().eval<float>(input);
} }
inline int chaiscript_eval_int(const int handle, const std::string &input) { inline double chaiscript_eval_double(const std::string &input) {
return detail::get_chai(handle).eval<int>(input); return detail::get_chai_instance().eval<double>(input);
}
inline float chaiscript_eval_float(const int handle, const std::string &input) {
return detail::get_chai(handle).eval<float>(input);
}
inline double chaiscript_eval_double(const int handle, const std::string &input) {
return detail::get_chai(handle).eval<double>(input);
}
// Snapshot the engine identified by chai_handle and return a fresh opaque
// state handle. Release it with chaiscript_release_state() when no longer
// needed.
inline int chaiscript_save_state(const int chai_handle) {
const int state_handle = detail::next_handle();
auto &chai = detail::get_chai(chai_handle);
detail::state_registry().emplace(state_handle, detail::Snapshot{chai.get_state(), chai.get_locals()});
return state_handle;
}
// Restore a previously snapshotted state onto the engine identified by
// chai_handle. Unknown state handles are silently ignored.
inline void chaiscript_restore_state(const int chai_handle, const int state_handle) {
const auto it = detail::state_registry().find(state_handle);
if (it != detail::state_registry().end()) {
auto &chai = detail::get_chai(chai_handle);
chai.set_state(it->second.engine_state);
chai.set_locals(it->second.locals);
}
}
// Release a state handle returned by chaiscript_save_state. Releasing an
// unknown handle is a no-op.
inline void chaiscript_release_state(const int state_handle) {
detail::state_registry().erase(state_handle);
} }
#endif /* CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ */ #endif /* CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ */

View File

@ -1,189 +0,0 @@
/*
* 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 ::= [^"\]

View File

@ -824,14 +824,12 @@ namespace chaiscript {
public: public:
ChaiScript(std::vector<std::string> t_modulepaths = {}, ChaiScript(std::vector<std::string> t_modulepaths = {},
std::vector<std::string> t_usepaths = {}, std::vector<std::string> t_usepaths = {},
std::vector<Options> t_opts = chaiscript::default_options(), std::vector<Options> t_opts = chaiscript::default_options())
std::vector<Library_Options> t_lib_opts = {}) : ChaiScript_Basic(chaiscript::Std_Lib::library(),
: ChaiScript_Basic(chaiscript::Std_Lib::library(t_lib_opts),
std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(), std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(),
std::move(t_modulepaths), std::move(t_modulepaths),
std::move(t_usepaths), std::move(t_usepaths),
std::move(t_opts), std::move(t_opts)) {
std::find(t_lib_opts.begin(), t_lib_opts.end(), Library_Options::No_IO) != t_lib_opts.end()) {
} }
}; };
} // namespace chaiscript } // namespace chaiscript

View File

@ -7,16 +7,6 @@
#ifndef CHAISCRIPT_DEFINES_HPP_ #ifndef CHAISCRIPT_DEFINES_HPP_
#define CHAISCRIPT_DEFINES_HPP_ #define CHAISCRIPT_DEFINES_HPP_
// MacOSX Fix: std::get/std::get_if/std::visit for std::variant are annotated
// __attribute__((availability(macos,strict,introduced=10.14))) in Apple libc++.
// Defining this macro before any libc++ header strips those annotations so
// ChaiScript can be built against older MacOSX deployment targets.
#if defined(__APPLE__) && defined(__clang__)
#ifndef _LIBCPP_DISABLE_AVAILABILITY
#define _LIBCPP_DISABLE_AVAILABILITY
#endif
#endif
#ifdef _MSC_VER #ifdef _MSC_VER
#define CHAISCRIPT_STRINGIZE(x) "" #x #define CHAISCRIPT_STRINGIZE(x) "" #x
#define CHAISCRIPT_STRINGIZE_EXPANDED(x) CHAISCRIPT_STRINGIZE(x) #define CHAISCRIPT_STRINGIZE_EXPANDED(x) CHAISCRIPT_STRINGIZE(x)
@ -69,30 +59,16 @@ static_assert(_MSC_FULL_VER >= 190024210, "Visual C++ 2015 Update 3 or later req
#define CHAISCRIPT_MODULE_EXPORT extern "C" #define CHAISCRIPT_MODULE_EXPORT extern "C"
#endif #endif
#if defined(CHAISCRIPT_MSVC) || (defined(__GNUC__) && __GNUC__ >= 5) || defined(CHAISCRIPT_CLANG)
#define CHAISCRIPT_UTF16_UTF32
#endif
#ifdef _DEBUG #ifdef _DEBUG
#define CHAISCRIPT_DEBUG true #define CHAISCRIPT_DEBUG true
#else #else
#define CHAISCRIPT_DEBUG false #define CHAISCRIPT_DEBUG false
#endif #endif
// Upper bound on the depth of nested ChaiScript function calls. Hitting it
// causes the dispatcher to throw chaiscript::exception::stack_overflow_error
// instead of letting the native call stack overflow. Defining the macro on
// the command line overrides the default.
//
// MSVC Debug builds emit very large per-frame native stack usage (no inlining,
// /RTC, buffer security checks) and Windows defaults to a 1 MiB thread stack,
// so the same ChaiScript depth that fits comfortably on Linux/macOS or in an
// MSVC Release build overflows the native stack before the depth check fires.
// We pick a tighter default in that configuration to keep the throw reachable.
#ifndef CHAISCRIPT_MAX_CALL_DEPTH
#if defined(CHAISCRIPT_MSVC) && CHAISCRIPT_DEBUG
#define CHAISCRIPT_MAX_CALL_DEPTH 32
#else
#define CHAISCRIPT_MAX_CALL_DEPTH 256
#endif
#endif
#include <cmath> #include <cmath>
#include <memory> #include <memory>
#include <string> #include <string>
@ -106,9 +82,6 @@ namespace chaiscript {
constexpr static const char *compiler_name = CHAISCRIPT_COMPILER_NAME; constexpr static const char *compiler_name = CHAISCRIPT_COMPILER_NAME;
constexpr static const bool debug_build = CHAISCRIPT_DEBUG; constexpr static const bool debug_build = CHAISCRIPT_DEBUG;
constexpr static const int max_call_depth = CHAISCRIPT_MAX_CALL_DEPTH;
static_assert(max_call_depth > 0, "CHAISCRIPT_MAX_CALL_DEPTH must be a positive integer");
template<typename B, typename D, typename... Arg> template<typename B, typename D, typename... Arg>
inline std::shared_ptr<B> make_shared(Arg &&...arg) { inline std::shared_ptr<B> make_shared(Arg &&...arg) {
#ifdef CHAISCRIPT_USE_STD_MAKE_SHARED #ifdef CHAISCRIPT_USE_STD_MAKE_SHARED
@ -238,13 +211,6 @@ namespace chaiscript {
External_Scripts External_Scripts
}; };
enum class Library_Options {
No_Stdlib,
No_IO,
No_Prelude,
No_JSON
};
template<typename From, typename To> template<typename From, typename To>
struct is_nothrow_forward_constructible : std::bool_constant<noexcept(To{std::declval<From>()})> { struct is_nothrow_forward_constructible : std::bool_constant<noexcept(To{std::declval<From>()})> {
}; };

View File

@ -10,7 +10,6 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <string_view>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -39,41 +38,25 @@
namespace chaiscript { namespace chaiscript {
class Std_Lib { class Std_Lib {
public: public:
[[nodiscard]] static ModulePtr library(const std::vector<Library_Options> &t_opts = {}) { [[nodiscard]] static ModulePtr library() {
if (std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Stdlib) != t_opts.end()) {
return std::make_shared<Module>();
}
auto lib = std::make_shared<Module>(); auto lib = std::make_shared<Module>();
const bool no_prelude = std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Prelude) != t_opts.end();
const bool no_json = std::find(t_opts.begin(), t_opts.end(), Library_Options::No_JSON) != t_opts.end();
bootstrap::Bootstrap::bootstrap(*lib); bootstrap::Bootstrap::bootstrap(*lib);
bootstrap::standard_library::vector_type<std::vector<Boxed_Value>>("Vector", *lib); bootstrap::standard_library::vector_type<std::vector<Boxed_Value>>("Vector", *lib);
bootstrap::standard_library::string_type<std::string>("string", *lib); bootstrap::standard_library::string_type<std::string>("string", *lib);
bootstrap::standard_library::string_view_type<std::string_view, std::string>("string_view", *lib);
bootstrap::standard_library::map_type<std::map<std::string, Boxed_Value>>("Map", *lib); bootstrap::standard_library::map_type<std::map<std::string, Boxed_Value>>("Map", *lib);
bootstrap::standard_library::pair_type<std::pair<Boxed_Value, Boxed_Value>>("Pair", *lib); bootstrap::standard_library::pair_type<std::pair<Boxed_Value, Boxed_Value>>("Pair", *lib);
// Allow explicit conversion from std::string_view back to std::string,
// e.g. `string(sv)` in ChaiScript.
lib->add(fun([](const std::string_view sv) { return std::string{sv}; }), "string");
#ifndef CHAISCRIPT_NO_THREADS #ifndef CHAISCRIPT_NO_THREADS
bootstrap::standard_library::future_type<std::future<chaiscript::Boxed_Value>>("future", *lib); bootstrap::standard_library::future_type<std::future<chaiscript::Boxed_Value>>("future", *lib);
// Note: async() is registered in ChaiScript_Basic::build_eval_system() lib->add(chaiscript::fun(
// with thread tracking to prevent heap-use-after-free on engine destruction. [](const std::function<chaiscript::Boxed_Value()> &t_func) { return std::async(std::launch::async, t_func); }),
"async");
#endif #endif
if (!no_json) {
json_wrap::library(*lib); json_wrap::library(*lib);
}
if (!no_prelude) {
lib->eval(ChaiScript_Prelude::chaiscript_prelude() /*, "standard prelude"*/); lib->eval(ChaiScript_Prelude::chaiscript_prelude() /*, "standard prelude"*/);
}
return lib; return lib;
} }

View File

@ -26,7 +26,7 @@ namespace chaiscript::bootstrap {
throw std::range_error("Array index out of range. Received: " + std::to_string(index) + " expected < " throw std::range_error("Array index out of range. Received: " + std::to_string(index) + " expected < "
+ std::to_string(extent)); + std::to_string(extent));
} else { } else {
return *std::next(t, static_cast<std::ptrdiff_t>(index)); return t[index];
} }
}), }),
"[]"); "[]");
@ -37,7 +37,7 @@ namespace chaiscript::bootstrap {
throw std::range_error("Array index out of range. Received: " + std::to_string(index) + " expected < " throw std::range_error("Array index out of range. Received: " + std::to_string(index) + " expected < "
+ std::to_string(extent)); + std::to_string(extent));
} else { } else {
return *std::next(t, static_cast<std::ptrdiff_t>(index)); return t[index];
} }
}), }),
"[]"); "[]");
@ -115,8 +115,6 @@ namespace chaiscript::bootstrap {
m.add(fun(&parse_string<T>), "to_" + name); m.add(fun(&parse_string<T>), "to_" + name);
m.add(fun([](const T t) { return t; }), "to_" + name); m.add(fun([](const T t) { return t; }), "to_" + name);
m.add(fun([](const Boxed_Number &bn) { return bn.get_as<T>(); }), "to_" + name);
m.add(fun(&parse_string<T>), name);
} }
/// "clone" function for a shared_ptr type. This is used in the case /// "clone" function for a shared_ptr type. This is used in the case
@ -268,7 +266,7 @@ namespace chaiscript::bootstrap {
public: public:
/// \brief perform all common bootstrap functions for std::string, void and POD types /// \brief perform all common bootstrap functions for std::string, void and POD types
/// \param[in,out] m Module to add bootstrapped functions to /// \param[in,out] m Module to add bootstrapped functions to
/// \param[in] t_no_io If true, skip registering print_string and println_string /// \returns passed in Module
static void bootstrap(Module &m) { static void bootstrap(Module &m) {
m.add(user_type<void>(), "void"); m.add(user_type<void>(), "void");
m.add(user_type<bool>(), "bool"); m.add(user_type<bool>(), "bool");
@ -437,6 +435,9 @@ namespace chaiscript::bootstrap {
m.add(fun(&Build_Info::compiler_id), "compiler_id"); m.add(fun(&Build_Info::compiler_id), "compiler_id");
m.add(fun(&Build_Info::debug_build), "debug_build"); m.add(fun(&Build_Info::debug_build), "debug_build");
m.add(fun(&print), "print_string");
m.add(fun(&println), "println_string");
m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind"); m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind");
m.add(fun(&shared_ptr_unconst_clone<dispatch::Proxy_Function_Base>), "clone"); m.add(fun(&shared_ptr_unconst_clone<dispatch::Proxy_Function_Base>), "clone");

View File

@ -18,7 +18,6 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#include <type_traits>
#include <typeinfo> #include <typeinfo>
#include <vector> #include <vector>
@ -85,15 +84,6 @@ namespace chaiscript::bootstrap::standard_library {
return t_target.count(t_key); return t_target.count(t_key);
} }
template<typename T>
Boxed_Value find(const T &t_target, const typename T::key_type &t_key) {
const auto itr = t_target.find(t_key);
if (itr != t_target.end()) {
return Boxed_Value(itr->second);
}
return Boxed_Value();
}
template<typename T> template<typename T>
void insert(T &t_target, const T &t_other) { void insert(T &t_target, const T &t_other) {
t_target.insert(t_other.begin(), t_other.end()); t_target.insert(t_other.begin(), t_other.end());
@ -180,22 +170,18 @@ namespace chaiscript::bootstrap::standard_library {
/// http://www.sgi.com/tech/stl/Assignable.html /// http://www.sgi.com/tech/stl/Assignable.html
template<typename ContainerType> template<typename ContainerType>
void assignable_type(const std::string &type, Module &m) { void assignable_type(const std::string &type, Module &m) {
if constexpr (std::is_copy_constructible_v<typename ContainerType::value_type>) {
copy_constructor<ContainerType>(type, m); copy_constructor<ContainerType>(type, m);
operators::assign<ContainerType>(m); operators::assign<ContainerType>(m);
} }
}
/// Add container resize concept to the given ContainerType /// Add container resize concept to the given ContainerType
/// http://www.cplusplus.com/reference/stl/ /// http://www.cplusplus.com/reference/stl/
template<typename ContainerType> template<typename ContainerType>
void resizable_type(const std::string & /*type*/, Module &m) { void resizable_type(const std::string & /*type*/, Module &m) {
if constexpr (std::is_copy_constructible_v<typename ContainerType::value_type>) {
m.add(fun([](ContainerType *a, typename ContainerType::size_type n, const typename ContainerType::value_type &val) { m.add(fun([](ContainerType *a, typename ContainerType::size_type n, const typename ContainerType::value_type &val) {
return a->resize(n, val); return a->resize(n, val);
}), }),
"resize"); "resize");
}
m.add(fun([](ContainerType *a, typename ContainerType::size_type n) { return a->resize(n); }), "resize"); m.add(fun([](ContainerType *a, typename ContainerType::size_type n) { return a->resize(n); }), "resize");
} }
@ -227,7 +213,6 @@ namespace chaiscript::bootstrap::standard_library {
/// http://www.sgi.com/tech/stl/Sequence.html /// http://www.sgi.com/tech/stl/Sequence.html
template<typename ContainerType> template<typename ContainerType>
void sequence_type(const std::string & /*type*/, Module &m) { void sequence_type(const std::string & /*type*/, Module &m) {
if constexpr (std::is_copy_constructible_v<typename ContainerType::value_type>) {
m.add(fun(&detail::insert_at<ContainerType>), []() -> std::string { m.add(fun(&detail::insert_at<ContainerType>), []() -> std::string {
if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) {
return "insert_ref_at"; return "insert_ref_at";
@ -235,7 +220,6 @@ namespace chaiscript::bootstrap::standard_library {
return "insert_at"; return "insert_at";
} }
}()); }());
}
m.add(fun(&detail::erase_at<ContainerType>), "erase_at"); m.add(fun(&detail::erase_at<ContainerType>), "erase_at");
} }
@ -261,7 +245,6 @@ namespace chaiscript::bootstrap::standard_library {
}), }),
"back"); "back");
if constexpr (std::is_copy_constructible_v<typename ContainerType::value_type>) {
using push_back = void (ContainerType::*)(const typename ContainerType::value_type &); using push_back = void (ContainerType::*)(const typename ContainerType::value_type &);
m.add(fun(static_cast<push_back>(&ContainerType::push_back)), [&]() -> std::string { m.add(fun(static_cast<push_back>(&ContainerType::push_back)), [&]() -> std::string {
if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) {
@ -283,7 +266,6 @@ namespace chaiscript::bootstrap::standard_library {
return "push_back"; return "push_back";
} }
}()); }());
}
m.add(fun(&ContainerType::pop_back), "pop_back"); m.add(fun(&ContainerType::pop_back), "pop_back");
} }
@ -313,7 +295,6 @@ namespace chaiscript::bootstrap::standard_library {
}), }),
"front"); "front");
if constexpr (std::is_copy_constructible_v<typename ContainerType::value_type>) {
m.add(fun(static_cast<push_ptr>(&ContainerType::push_front)), [&]() -> std::string { m.add(fun(static_cast<push_ptr>(&ContainerType::push_front)), [&]() -> std::string {
if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) {
m.eval("# Pushes the second value onto the front of container while making a clone of the value\n" m.eval("# Pushes the second value onto the front of container while making a clone of the value\n"
@ -333,7 +314,6 @@ namespace chaiscript::bootstrap::standard_library {
return "push_front"; return "push_front";
} }
}()); }());
}
m.add(fun(static_cast<pop_ptr>(&ContainerType::pop_front)), "pop_front"); m.add(fun(static_cast<pop_ptr>(&ContainerType::pop_front)), "pop_front");
} }
@ -364,7 +344,6 @@ namespace chaiscript::bootstrap::standard_library {
template<typename ContainerType> template<typename ContainerType>
void unique_associative_container_type(const std::string & /*type*/, Module &m) { void unique_associative_container_type(const std::string & /*type*/, Module &m) {
m.add(fun(detail::count<ContainerType>), "count"); m.add(fun(detail::count<ContainerType>), "count");
m.add(fun(detail::find<ContainerType>), "find");
using erase_ptr = size_t (ContainerType::*)(const typename ContainerType::key_type &); using erase_ptr = size_t (ContainerType::*)(const typename ContainerType::key_type &);
@ -539,61 +518,6 @@ namespace chaiscript::bootstrap::standard_library {
m.add(fun([](const String *s, size_t pos, size_t len) { return s->substr(pos, len); }), "substr"); m.add(fun([](const String *s, size_t pos, size_t len) { return s->substr(pos, len); }), "substr");
} }
/// Add a String_View type (e.g. std::string_view), with conversions to and from
/// the matching owning String type (e.g. std::string).
///
/// Mirrors the search/substring surface of string_type so that scripts can
/// traverse a buffer through StringView without allocating. Sharing method
/// names with the owning String type is safe: dispatch deprioritizes any
/// candidate whose first/receiver argument requires a type conversion (see
/// dispatch() in proxy_functions.hpp), so myString.substr(1, 2) still
/// resolves to String::substr while mySV.substr(1, 2) resolves to
/// StringView::substr (returning a StringView).
///
/// \note A String_View is a non-owning reference. Constructing one from a
/// temporary owning String yields a dangling reference once that temporary
/// is destroyed; the same lifetime caveats as in C++ apply here.
template<typename StringView, typename String>
void string_view_type(const std::string &type, Module &m) {
m.add(user_type<StringView>(), type);
m.add(constructor<StringView()>(), type);
m.add(constructor<StringView(const StringView &)>(), type);
m.add(fun([](const String &s) { return StringView{s}; }), type);
opers_comparison<StringView>(m);
m.add(fun([](const StringView *s) { return s->size(); }), "size");
m.add(fun([](const StringView *s) { return s->length(); }), "length");
m.add(fun([](const StringView *s) { return s->empty(); }), "empty");
m.add(fun([](const StringView *s) { return s->data(); }), "data");
// Random-access via [] / at, returning const_reference; StringView is read-only.
m.add(fun([](const StringView &sv, int index) -> typename StringView::const_reference {
return sv.at(static_cast<typename StringView::size_type>(index));
}),
"[]");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find(f, pos); }), "find");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->rfind(f, pos); }), "rfind");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_first_of(f, pos); }), "find_first_of");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_last_of(f, pos); }), "find_last_of");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_last_not_of(f, pos); }), "find_last_not_of");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_first_not_of(f, pos); }), "find_first_not_of");
m.add(fun([](const StringView *s, const StringView &f) { return s->starts_with(f); }), "starts_with");
m.add(fun([](const StringView *s, const StringView &f) { return s->ends_with(f); }), "ends_with");
m.add(fun([](const StringView *s, size_t pos, size_t len) { return s->substr(pos, len); }), "substr");
// Built-in implicit conversion from owning String to non-owning StringView.
m.add(type_conversion<const String &, StringView>());
// Explicit conversion from StringView back to owning String, registered as
// to_string(sv); the call site can also register it under the owning type's
// name (e.g. string(sv)) when desired.
m.add(fun([](const StringView sv) { return String{sv}; }), "to_string");
}
/// Add a MapType container /// Add a MapType container
/// http://www.sgi.com/tech/stl/Map.html /// http://www.sgi.com/tech/stl/Map.html
template<typename FutureType> template<typename FutureType>

View File

@ -41,7 +41,7 @@ namespace chaiscript {
// this is OK, so we're disabling size/and sign type warnings // this is OK, so we're disabling size/and sign type warnings
#ifdef CHAISCRIPT_MSVC #ifdef CHAISCRIPT_MSVC
#pragma warning(push) #pragma warning(push)
#pragma warning(disable : 4244 4018 4389 4146 4365 4267 4242 4702) // 4702 is for broken unreachable warning #pragma warning(disable : 4244 4018 4389 4146 4365 4267 4242)
#endif #endif
#ifdef __GNUC__ #ifdef __GNUC__
@ -220,6 +220,7 @@ namespace chaiscript {
if constexpr (!std::is_floating_point<LHS>::value && !std::is_floating_point<RHS>::value) { if constexpr (!std::is_floating_point<LHS>::value && !std::is_floating_point<RHS>::value) {
switch (t_oper) { switch (t_oper) {
case Operators::Opers::assign_bitwise_and: case Operators::Opers::assign_bitwise_and:
check_divide_by_zero(c_rhs);
*t_lhs &= c_rhs; *t_lhs &= c_rhs;
return t_bv; return t_bv;
case Operators::Opers::assign_bitwise_or: case Operators::Opers::assign_bitwise_or:
@ -232,7 +233,6 @@ namespace chaiscript {
*t_lhs >>= c_rhs; *t_lhs >>= c_rhs;
return t_bv; return t_bv;
case Operators::Opers::assign_remainder: case Operators::Opers::assign_remainder:
check_divide_by_zero(c_rhs);
*t_lhs %= c_rhs; *t_lhs %= c_rhs;
return t_bv; return t_bv;
case Operators::Opers::assign_bitwise_xor: case Operators::Opers::assign_bitwise_xor:

View File

@ -19,7 +19,6 @@
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <thread>
#include <typeinfo> #include <typeinfo>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -34,6 +33,7 @@
#include "dynamic_object.hpp" #include "dynamic_object.hpp"
#include "proxy_constructors.hpp" #include "proxy_constructors.hpp"
#include "proxy_functions.hpp" #include "proxy_functions.hpp"
#include "short_alloc.hpp"
#include "type_conversions.hpp" #include "type_conversions.hpp"
#include "type_info.hpp" #include "type_info.hpp"
@ -121,19 +121,6 @@ namespace chaiscript {
global_non_const(const global_non_const &) = default; global_non_const(const global_non_const &) = default;
~global_non_const() noexcept override = default; ~global_non_const() noexcept override = default;
}; };
/// Exception thrown when the ChaiScript call stack exceeds
/// chaiscript::max_call_depth, signalling runaway recursion before the
/// native stack can overflow.
class stack_overflow_error : public std::runtime_error {
public:
stack_overflow_error() noexcept
: std::runtime_error("Maximum call stack depth exceeded") {
}
stack_overflow_error(const stack_overflow_error &) = default;
~stack_overflow_error() noexcept override = default;
};
} // namespace exception } // namespace exception
/// \brief Holds a collection of ChaiScript settings which can be applied to the ChaiScript runtime. /// \brief Holds a collection of ChaiScript settings which can be applied to the ChaiScript runtime.
@ -330,6 +317,9 @@ namespace chaiscript {
namespace detail { namespace detail {
struct Stack_Holder { struct Stack_Holder {
// template <class T, std::size_t BufSize = sizeof(T)*20000>
// using SmallVector = std::vector<T, short_alloc<T, BufSize>>;
template<class T> template<class T>
using SmallVector = std::vector<T>; using SmallVector = std::vector<T>;
@ -380,28 +370,6 @@ namespace chaiscript {
, m_parser(parser) { , m_parser(parser) {
} }
~Dispatch_Engine() {
join_async_threads();
}
Dispatch_Engine(const Dispatch_Engine &) = delete;
Dispatch_Engine &operator=(const Dispatch_Engine &) = delete;
Dispatch_Engine(Dispatch_Engine &&) = delete;
Dispatch_Engine &operator=(Dispatch_Engine &&) = delete;
#ifndef CHAISCRIPT_NO_THREADS
/// Track an async thread so it can be joined during destruction
void track_async_thread(std::thread t_thread) {
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::shared_mutex> l(m_async_mutex);
// Clean up already-finished threads to avoid unbounded growth
m_async_threads.erase(
std::remove_if(m_async_threads.begin(), m_async_threads.end(),
[](std::thread &t) { return !t.joinable(); }),
m_async_threads.end());
m_async_threads.push_back(std::move(t_thread));
}
#endif
/// \brief casts an object while applying any Dynamic_Conversion available /// \brief casts an object while applying any Dynamic_Conversion available
template<typename Type> template<typename Type>
decltype(auto) boxed_cast(const Boxed_Value &bv) const { decltype(auto) boxed_cast(const Boxed_Value &bv) const {
@ -799,13 +767,13 @@ namespace chaiscript {
t_loc = uint_fast32_t(funs.first); t_loc = uint_fast32_t(funs.first);
} }
const auto do_attribute_call = [this](std::size_t l_num_params, const auto do_attribute_call = [this](int l_num_params,
Function_Params l_params, Function_Params l_params,
const std::vector<Proxy_Function> &l_funs, const std::vector<Proxy_Function> &l_funs,
const Type_Conversions_State &l_conversions) -> Boxed_Value { const Type_Conversions_State &l_conversions) -> Boxed_Value {
Function_Params attr_params(l_params.first(l_num_params)); Function_Params attr_params(l_params.begin(), l_params.begin() + l_num_params);
Boxed_Value bv = dispatch::dispatch(l_funs, attr_params, l_conversions); Boxed_Value bv = dispatch::dispatch(l_funs, attr_params, l_conversions);
if (l_num_params < l_params.size() || bv.get_type_info().bare_equal(user_type<dispatch::Proxy_Function_Base>())) { if (l_num_params < int(l_params.size()) || bv.get_type_info().bare_equal(user_type<dispatch::Proxy_Function_Base>())) {
struct This_Foist { struct This_Foist {
This_Foist(Dispatch_Engine &e, const Boxed_Value &t_bv) This_Foist(Dispatch_Engine &e, const Boxed_Value &t_bv)
: m_e(e) { : m_e(e) {
@ -823,16 +791,16 @@ namespace chaiscript {
try { try {
auto func = boxed_cast<const dispatch::Proxy_Function_Base *>(bv); auto func = boxed_cast<const dispatch::Proxy_Function_Base *>(bv);
try { try {
return (*func)(l_params.subspan(l_num_params), l_conversions); return (*func)({l_params.begin() + l_num_params, l_params.end()}, l_conversions);
} catch (const chaiscript::exception::bad_boxed_cast &) { } catch (const chaiscript::exception::bad_boxed_cast &) {
} catch (const chaiscript::exception::arity_error &) { } catch (const chaiscript::exception::arity_error &) {
} catch (const chaiscript::exception::guard_error &) { } catch (const chaiscript::exception::guard_error &) {
} }
throw chaiscript::exception::dispatch_error(l_params.subspan(l_num_params), throw chaiscript::exception::dispatch_error({l_params.begin() + l_num_params, l_params.end()},
std::vector<Const_Proxy_Function>{boxed_cast<Const_Proxy_Function>(bv)}); std::vector<Const_Proxy_Function>{boxed_cast<Const_Proxy_Function>(bv)});
} catch (const chaiscript::exception::bad_boxed_cast &) { } catch (const chaiscript::exception::bad_boxed_cast &) {
// unable to convert bv into a Proxy_Function_Base // unable to convert bv into a Proxy_Function_Base
throw chaiscript::exception::dispatch_error(l_params.subspan(l_num_params), throw chaiscript::exception::dispatch_error({l_params.begin() + l_num_params, l_params.end()},
std::vector<Const_Proxy_Function>(l_funs.begin(), l_funs.end())); std::vector<Const_Proxy_Function>(l_funs.begin(), l_funs.end()));
} }
} else { } else {
@ -882,7 +850,7 @@ namespace chaiscript {
if (!functions.empty()) { if (!functions.empty()) {
try { try {
if (is_no_param) { if (is_no_param) {
auto tmp_params = std::vector<Boxed_Value>(params.begin(), params.end()); auto tmp_params = params.to_vector();
tmp_params.insert(tmp_params.begin() + 1, var(t_name)); tmp_params.insert(tmp_params.begin() + 1, var(t_name));
return do_attribute_call(2, Function_Params(tmp_params), functions, t_conversions); return do_attribute_call(2, Function_Params(tmp_params), functions, t_conversions);
} else { } else {
@ -957,7 +925,7 @@ namespace chaiscript {
const auto &f = this->boxed_cast<Const_Proxy_Function>(params[0]); const auto &f = this->boxed_cast<Const_Proxy_Function>(params[0]);
const Type_Conversions_State convs(m_conversions, m_conversions.conversion_saves()); const Type_Conversions_State convs(m_conversions, m_conversions.conversion_saves());
return const_var(f->call_match(Function_Params(params.subspan(1)), convs)); return const_var(f->call_match(Function_Params(params.begin() + 1, params.end()), convs));
} }
/// Dump all system info to stdout /// Dump all system info to stdout
@ -1023,10 +991,6 @@ namespace chaiscript {
void save_function_params(const Function_Params &t_params) { save_function_params(*m_stack_holder, t_params); } void save_function_params(const Function_Params &t_params) { save_function_params(*m_stack_holder, t_params); }
void new_function_call(Stack_Holder &t_s, Type_Conversions::Conversion_Saves &t_saves) { void new_function_call(Stack_Holder &t_s, Type_Conversions::Conversion_Saves &t_saves) {
if (t_s.call_depth >= max_call_depth) {
throw chaiscript::exception::stack_overflow_error{};
}
if (t_s.call_depth == 0) { if (t_s.call_depth == 0) {
m_conversions.enable_conversion_saves(t_saves, true); m_conversions.enable_conversion_saves(t_saves, true);
} }
@ -1100,8 +1064,7 @@ namespace chaiscript {
// overridden methods in derived classes are tried first during dispatch // overridden methods in derived classes are tried first during dispatch
const auto &lhs_dotn = lhs->dynamic_object_type_name(); const auto &lhs_dotn = lhs->dynamic_object_type_name();
const auto &rhs_dotn = rhs->dynamic_object_type_name(); const auto &rhs_dotn = rhs->dynamic_object_type_name();
if (lhs_dotn != rhs_dotn) { if (!lhs_dotn.empty() && !rhs_dotn.empty() && lhs_dotn != rhs_dotn) {
if (!lhs_dotn.empty() && !rhs_dotn.empty()) {
if (dispatch::Dynamic_Object::type_matches(lhs_dotn, rhs_dotn)) { if (dispatch::Dynamic_Object::type_matches(lhs_dotn, rhs_dotn)) {
return true; // lhs is derived from rhs, so lhs is more specific return true; // lhs is derived from rhs, so lhs is more specific
} }
@ -1109,13 +1072,6 @@ namespace chaiscript {
return false; // rhs is derived from lhs, so rhs is more specific return false; // rhs is derived from lhs, so rhs is more specific
} }
} }
// Impose a total order on type names to maintain strict-weak ordering:
// non-empty names sort before empty names, then lexicographically
if (lhs_dotn.empty() != rhs_dotn.empty()) {
return !lhs_dotn.empty();
}
return lhs_dotn < rhs_dotn;
}
const auto &lhsparamtypes = lhs->get_param_types(); const auto &lhsparamtypes = lhs->get_param_types();
const auto &rhsparamtypes = rhs->get_param_types(); const auto &rhsparamtypes = rhs->get_param_types();
@ -1164,9 +1120,7 @@ namespace chaiscript {
return lt < rt; return lt < rt;
} }
// When all overlapping parameters match, order by arity to maintain return false;
// strict-weak ordering (transitivity of equivalence)
return lhssize < rhssize;
} }
/// Implementation detail for adding a function. /// Implementation detail for adding a function.
@ -1211,21 +1165,6 @@ namespace chaiscript {
get_function_objects_int().insert_or_assign(t_name, std::move(new_func)); get_function_objects_int().insert_or_assign(t_name, std::move(new_func));
} }
void join_async_threads() {
#ifndef CHAISCRIPT_NO_THREADS
std::vector<std::thread> threads;
{
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::shared_mutex> l(m_async_mutex);
threads = std::move(m_async_threads);
}
for (auto &t : threads) {
if (t.joinable()) {
t.join();
}
}
#endif
}
mutable chaiscript::detail::threading::shared_mutex m_mutex; mutable chaiscript::detail::threading::shared_mutex m_mutex;
Type_Conversions m_conversions; Type_Conversions m_conversions;
@ -1235,11 +1174,6 @@ namespace chaiscript {
mutable std::atomic_uint_fast32_t m_method_missing_loc = {0}; mutable std::atomic_uint_fast32_t m_method_missing_loc = {0};
State m_state; State m_state;
#ifndef CHAISCRIPT_NO_THREADS
mutable chaiscript::detail::threading::shared_mutex m_async_mutex;
std::vector<std::thread> m_async_threads;
#endif
}; };
class Dispatch_State { class Dispatch_State {

View File

@ -65,6 +65,7 @@ namespace chaiscript {
return false; return false;
} }
bool is_explicit() const noexcept { return m_option_explicit; } bool is_explicit() const noexcept { return m_option_explicit; }
void set_explicit(const bool t_explicit) noexcept { m_option_explicit = t_explicit; } void set_explicit(const bool t_explicit) noexcept { m_option_explicit = t_explicit; }

View File

@ -103,9 +103,7 @@ namespace chaiscript {
assert(types.size() > 1); assert(types.size() > 1);
// assert(types[1].bare_equal(user_type<Boxed_Value>())); // assert(types[1].bare_equal(user_type<Boxed_Value>()));
// When the object type_info is undefined (ChaiScript-defined class), use types[1] = t_objectti;
// Dynamic_Object so that dispatch priority scoring works correctly.
types[1] = t_objectti.is_undef() ? user_type<Dynamic_Object>() : t_objectti;
return types; return types;
} }

View File

@ -55,23 +55,10 @@ namespace chaiscript::dispatch::detail {
} }
} }
template<typename T>
struct is_shared_ptr : std::false_type {};
template<typename T>
struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
template<typename P, typename Q> template<typename P, typename Q>
static Boxed_Value box(Q &&q) { static Boxed_Value box(Q &&q) {
using bare_p = std::remove_cv_t<std::remove_reference_t<P>>;
if constexpr (std::is_same_v<chaiscript::Boxed_Value, std::decay_t<Q>>) { if constexpr (std::is_same_v<chaiscript::Boxed_Value, std::decay_t<Q>>) {
return std::forward<Q>(q); return std::forward<Q>(q);
} else if constexpr (is_shared_ptr<bare_p>::value) {
// Pass shared_ptr arguments through by value rather than wrapping a
// reference. Boxed_Value's shared_ptr-aware overloads then record the
// bare type as the pointee, allowing dispatch to a C++ function that
// takes the same shared_ptr<T> on the other side.
return Boxed_Value(bare_p(std::forward<Q>(q)));
} else if constexpr (std::is_reference_v<P>) { } else if constexpr (std::is_reference_v<P>) {
return Boxed_Value(std::ref(std::forward<Q>(q))); return Boxed_Value(std::ref(std::forward<Q>(q)));
} else { } else {

View File

@ -12,11 +12,55 @@
#include "boxed_value.hpp" #include "boxed_value.hpp"
#include <span>
namespace chaiscript { namespace chaiscript {
class Function_Params {
public:
constexpr Function_Params(const Boxed_Value *const t_begin, const Boxed_Value *const t_end)
: m_begin(t_begin)
, m_end(t_end) {
}
using Function_Params = std::span<const Boxed_Value>; explicit Function_Params(const Boxed_Value &bv)
: m_begin(&bv)
, m_end(m_begin + 1) {
}
explicit Function_Params(const std::vector<Boxed_Value> &vec)
: m_begin(vec.empty() ? nullptr : &vec.front())
, m_end(vec.empty() ? nullptr : &vec.front() + vec.size()) {
}
template<size_t Size>
constexpr explicit Function_Params(const std::array<Boxed_Value, Size> &a)
: m_begin(&a.front())
, m_end(&a.front() + Size) {
}
[[nodiscard]] constexpr const Boxed_Value &operator[](const std::size_t t_i) const noexcept { return m_begin[t_i]; }
[[nodiscard]] constexpr const Boxed_Value *begin() const noexcept { return m_begin; }
[[nodiscard]] constexpr const Boxed_Value &front() const noexcept { return *m_begin; }
[[nodiscard]] constexpr const Boxed_Value *end() const noexcept { return m_end; }
[[nodiscard]] constexpr std::size_t size() const noexcept { return std::size_t(m_end - m_begin); }
[[nodiscard]] std::vector<Boxed_Value> to_vector() const { return std::vector<Boxed_Value>{m_begin, m_end}; }
[[nodiscard]] constexpr bool empty() const noexcept { return m_begin == m_end; }
private:
const Boxed_Value *m_begin = nullptr;
const Boxed_Value *m_end = nullptr;
};
// Constructor specialization for array of size 0
template<>
constexpr Function_Params::Function_Params(const std::array<Boxed_Value, size_t{0}> & /* a */)
: m_begin(nullptr)
, m_end(nullptr) {
}
} // namespace chaiscript } // namespace chaiscript

View File

@ -126,7 +126,7 @@ namespace chaiscript {
struct Handle_Return_Ref { struct Handle_Return_Ref {
template<typename T> template<typename T>
static Boxed_Value handle(T &&r) { static Boxed_Value handle(T &&r) {
return Boxed_Value(std::cref(r), false); return Boxed_Value(std::cref(r), true);
} }
}; };

View File

@ -15,168 +15,168 @@
#include "register_function.hpp" #include "register_function.hpp"
namespace chaiscript::bootstrap::operators { namespace chaiscript::bootstrap::operators {
template<typename T, typename ModuleType> template<typename T>
void assign(ModuleType &m) { void assign(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs = rhs; }), "="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs = rhs; }), "=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_bitwise_and(ModuleType &m) { void assign_bitwise_and(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs &= rhs; }), "&="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs &= rhs; }), "&=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_xor(ModuleType &m) { void assign_xor(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs ^= rhs; }), "^="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs ^= rhs; }), "^=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_bitwise_or(ModuleType &m) { void assign_bitwise_or(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs |= rhs; }), "|="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs |= rhs; }), "|=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_difference(ModuleType &m) { void assign_difference(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs -= rhs; }), "-="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs -= rhs; }), "-=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_left_shift(ModuleType &m) { void assign_left_shift(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "<<="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "<<=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_product(ModuleType &m) { void assign_product(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "*="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "*=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_quotient(ModuleType &m) { void assign_quotient(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs /= rhs; }), "/="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs /= rhs; }), "/=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_remainder(ModuleType &m) { void assign_remainder(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs %= rhs; }), "%="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs %= rhs; }), "%=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_right_shift(ModuleType &m) { void assign_right_shift(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs >>= rhs; }), ">>="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs >>= rhs; }), ">>=");
} }
template<typename T, typename ModuleType> template<typename T>
void assign_sum(ModuleType &m) { void assign_sum(Module &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs += rhs; }), "+="); m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs += rhs; }), "+=");
} }
template<typename T, typename ModuleType> template<typename T>
void prefix_decrement(ModuleType &m) { void prefix_decrement(Module &m) {
m.add(chaiscript::fun([](T &lhs) -> T & { return --lhs; }), "--"); m.add(chaiscript::fun([](T &lhs) -> T & { return --lhs; }), "--");
} }
template<typename T, typename ModuleType> template<typename T>
void prefix_increment(ModuleType &m) { void prefix_increment(Module &m) {
m.add(chaiscript::fun([](T &lhs) -> T & { return ++lhs; }), "++"); m.add(chaiscript::fun([](T &lhs) -> T & { return ++lhs; }), "++");
} }
template<typename T, typename ModuleType> template<typename T>
void equal(ModuleType &m) { void equal(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs == rhs; }), "=="); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs == rhs; }), "==");
} }
template<typename T, typename ModuleType> template<typename T>
void greater_than(ModuleType &m) { void greater_than(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs > rhs; }), ">"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs > rhs; }), ">");
} }
template<typename T, typename ModuleType> template<typename T>
void greater_than_equal(ModuleType &m) { void greater_than_equal(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >= rhs; }), ">="); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >= rhs; }), ">=");
} }
template<typename T, typename ModuleType> template<typename T>
void less_than(ModuleType &m) { void less_than(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs < rhs; }), "<"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs < rhs; }), "<");
} }
template<typename T, typename ModuleType> template<typename T>
void less_than_equal(ModuleType &m) { void less_than_equal(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs <= rhs; }), "<="); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs <= rhs; }), "<=");
} }
template<typename T, typename ModuleType> template<typename T>
void logical_compliment(ModuleType &m) { void logical_compliment(Module &m) {
m.add(chaiscript::fun([](const T &lhs) { return !lhs; }), "!"); m.add(chaiscript::fun([](const T &lhs) { return !lhs; }), "!");
} }
template<typename T, typename ModuleType> template<typename T>
void not_equal(ModuleType &m) { void not_equal(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs != rhs; }), "!="); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs != rhs; }), "!=");
} }
template<typename T, typename ModuleType> template<typename T>
void addition(ModuleType &m) { void addition(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs + rhs; }), "+"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs + rhs; }), "+");
} }
template<typename T, typename ModuleType> template<typename T>
void unary_plus(ModuleType &m) { void unary_plus(Module &m) {
m.add(chaiscript::fun([](const T &lhs) { return +lhs; }), "+"); m.add(chaiscript::fun([](const T &lhs) { return +lhs; }), "+");
} }
template<typename T, typename ModuleType> template<typename T>
void subtraction(ModuleType &m) { void subtraction(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs - rhs; }), "-"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs - rhs; }), "-");
} }
template<typename T, typename ModuleType> template<typename T>
void unary_minus(ModuleType &m) { void unary_minus(Module &m) {
m.add(chaiscript::fun([](const T &lhs) { return -lhs; }), "-"); m.add(chaiscript::fun([](const T &lhs) { return -lhs; }), "-");
} }
template<typename T, typename ModuleType> template<typename T>
void bitwise_and(ModuleType &m) { void bitwise_and(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs & rhs; }), "&"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs & rhs; }), "&");
} }
template<typename T, typename ModuleType> template<typename T>
void bitwise_compliment(ModuleType &m) { void bitwise_compliment(Module &m) {
m.add(chaiscript::fun([](const T &lhs) { return ~lhs; }), "~"); m.add(chaiscript::fun([](const T &lhs) { return ~lhs; }), "~");
} }
template<typename T, typename ModuleType> template<typename T>
void bitwise_xor(ModuleType &m) { void bitwise_xor(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs ^ rhs; }), "^"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs ^ rhs; }), "^");
} }
template<typename T, typename ModuleType> template<typename T>
void bitwise_or(ModuleType &m) { void bitwise_or(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs | rhs; }), "|"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs | rhs; }), "|");
} }
template<typename T, typename ModuleType> template<typename T>
void division(ModuleType &m) { void division(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs / rhs; }), "/"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs / rhs; }), "/");
} }
template<typename T, typename ModuleType> template<typename T>
void left_shift(ModuleType &m) { void left_shift(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs << rhs; }), "<<"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs << rhs; }), "<<");
} }
template<typename T, typename ModuleType> template<typename T>
void multiplication(ModuleType &m) { void multiplication(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs * rhs; }), "*"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs * rhs; }), "*");
} }
template<typename T, typename ModuleType> template<typename T>
void remainder(ModuleType &m) { void remainder(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs % rhs; }), "%"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs % rhs; }), "%");
} }
template<typename T, typename ModuleType> template<typename T>
void right_shift(ModuleType &m) { void right_shift(Module &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >> rhs; }), ">>"); m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >> rhs; }), ">>");
} }
} // namespace chaiscript::bootstrap::operators } // namespace chaiscript::bootstrap::operators

View File

@ -65,7 +65,7 @@ namespace chaiscript {
bool operator==(const Param_Types &t_rhs) const noexcept { return m_types == t_rhs.m_types; } bool operator==(const Param_Types &t_rhs) const noexcept { return m_types == t_rhs.m_types; }
std::vector<Boxed_Value> convert(Function_Params t_params, const Type_Conversions_State &t_conversions) const { std::vector<Boxed_Value> convert(Function_Params t_params, const Type_Conversions_State &t_conversions) const {
auto vals = std::vector<Boxed_Value>{t_params.begin(), t_params.end()}; auto vals = t_params.to_vector();
const auto dynamic_object_type_info = user_type<Dynamic_Object>(); const auto dynamic_object_type_info = user_type<Dynamic_Object>();
for (size_t i = 0; i < vals.size(); ++i) { for (size_t i = 0; i < vals.size(); ++i) {
const auto &name = m_types[i].first; const auto &name = m_types[i].first;
@ -380,15 +380,7 @@ namespace chaiscript {
for (const auto &t : t_types.types()) { for (const auto &t : t_types.types()) {
if (t.second.is_undef()) { if (t.second.is_undef()) {
if (!t.first.empty()) {
// Named type without C++ type_info — assumed to be a Dynamic_Object subtype.
// Using Dynamic_Object type_info ensures correct dispatch priority so that
// user-defined overrides on specific subtypes are tried before generic
// Dynamic_Object built-in functions.
types.push_back(user_type<Dynamic_Object>());
} else {
types.push_back(chaiscript::detail::Get_Type_Info<Boxed_Value>::get()); types.push_back(chaiscript::detail::Get_Type_Info<Boxed_Value>::get());
}
} else { } else {
types.push_back(t.second); types.push_back(t.second);
} }
@ -682,13 +674,13 @@ namespace chaiscript {
public: public:
dispatch_error(const Function_Params &t_parameters, std::vector<Const_Proxy_Function> t_functions) dispatch_error(const Function_Params &t_parameters, std::vector<Const_Proxy_Function> t_functions)
: std::runtime_error("Error with function dispatch") : std::runtime_error("Error with function dispatch")
, parameters(t_parameters.begin(), t_parameters.end()) , parameters(t_parameters.to_vector())
, functions(std::move(t_functions)) { , functions(std::move(t_functions)) {
} }
dispatch_error(const Function_Params &t_parameters, std::vector<Const_Proxy_Function> t_functions, const std::string &t_desc) dispatch_error(const Function_Params &t_parameters, std::vector<Const_Proxy_Function> t_functions, const std::string &t_desc)
: std::runtime_error(t_desc) : std::runtime_error(t_desc)
, parameters(t_parameters.begin(), t_parameters.end()) , parameters(t_parameters.to_vector())
, functions(std::move(t_functions)) { , functions(std::move(t_functions)) {
} }
@ -737,24 +729,11 @@ namespace chaiscript {
if (matching_func == end) { if (matching_func == end) {
matching_func = begin; matching_func = begin;
} else { } else {
// handle const members vs non-const member, which is not really ambiguous
const auto &mat_fun_param_types = matching_func->second->get_param_types(); const auto &mat_fun_param_types = matching_func->second->get_param_types();
const auto &next_fun_param_types = begin->second->get_param_types(); const auto &next_fun_param_types = begin->second->get_param_types();
// Prefer the candidate whose first parameter (receiver) matches the if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) {
// actual receiver type exactly over one that needs a type conversion.
// Conversions on the receiver create temporaries, so any mutation
// would be silently lost; this mirrors the deprioritization in
// dispatch() and resolves cases like myString.substr(int, int) when
// both string::substr and string_view::substr are registered.
const bool plist_empty = plist.empty();
const bool mat_receiver_exact = !plist_empty && mat_fun_param_types[1].bare_equal(plist[0].get_type_info());
const bool next_receiver_exact = !plist_empty && next_fun_param_types[1].bare_equal(plist[0].get_type_info());
if (mat_receiver_exact && !next_receiver_exact) {
// keep the old one, it has the better receiver match
} else if (!mat_receiver_exact && next_receiver_exact) {
matching_func = begin; // keep the new one, it has the better receiver match
} else if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) {
matching_func = begin; // keep the new one, the const/non-const matchup is correct matching_func = begin; // keep the new one, the const/non-const matchup is correct
} else if (!plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { } else if (!plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) {
// keep the old one, it has a better const/non-const matchup // keep the old one, it has a better const/non-const matchup
@ -827,30 +806,6 @@ namespace chaiscript {
numdiffs = plist.size(); numdiffs = plist.size();
} }
// Deprioritize C++ registered generic Dynamic_Object functions when the
// actual first argument is a specific Dynamic_Object subtype. This allows
// user-defined operator overrides (e.g. `[]`) on a subtype to take precedence
// over the built-in Dynamic_Object operations such as get_attr.
// Only deprioritize non-dynamic (C++ registered) functions — ChaiScript-defined
// functions (Dynamic_Proxy_Function) have their own type matching via Param_Types.
if (!plist.empty()
&& dynamic_cast<const Dynamic_Proxy_Function *>(func.get()) == nullptr) {
static const auto dynamic_object_ti = user_type<Dynamic_Object>();
if (func->get_param_types().size() > 1
&& func->get_param_types()[1].bare_equal(dynamic_object_ti)
&& plist[0].get_type_info().bare_equal(dynamic_object_ti)
&& func->dynamic_object_type_name().empty()) {
try {
const auto &d = boxed_cast<const Dynamic_Object &>(plist[0], &t_conversions);
if (d.get_type_name() != "Dynamic_Object") {
numdiffs = plist.size();
}
} catch (const std::bad_cast &) {
// not a Dynamic_Object, ignore
}
}
}
ordered_funcs.emplace_back(numdiffs, func.get()); ordered_funcs.emplace_back(numdiffs, func.get());
} }
} }

View File

@ -80,7 +80,7 @@ namespace chaiscript {
} }
template<typename Callable, typename Ret, typename... Params, size_t... I> template<typename Callable, typename Ret, typename... Params, size_t... I>
Ret call_func_impl(Ret (*)(Params...), Ret call_func(Ret (*)(Params...),
std::index_sequence<I...>, std::index_sequence<I...>,
const Callable &f, const Callable &f,
[[maybe_unused]] const chaiscript::Function_Params &params, [[maybe_unused]] const chaiscript::Function_Params &params,
@ -95,23 +95,13 @@ namespace chaiscript {
template<typename Callable, typename Ret, typename... Params> template<typename Callable, typename Ret, typename... Params>
Boxed_Value Boxed_Value
call_func(Ret (*sig)(Params...), const Callable &f, const chaiscript::Function_Params &params, const Type_Conversions_State &t_conversions) { call_func(Ret (*sig)(Params...), const Callable &f, const chaiscript::Function_Params &params, const Type_Conversions_State &t_conversions) {
return Handle_Return<Ret>::handle(call_func_impl(sig, std::index_sequence_for<Params...>{}, f, params, t_conversions)); if constexpr (std::is_same_v<Ret, void>) {
} call_func(sig, std::index_sequence_for<Params...>{}, f, params, t_conversions);
// MSVC has a broken warning for unreachable code in this block
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4702)
#endif
template<typename Callable, typename... Params>
Boxed_Value
call_func(void (*sig)(Params...), const Callable &f, const chaiscript::Function_Params &params, const Type_Conversions_State &t_conversions) {
call_func_impl(sig, std::index_sequence_for<Params...>{}, f, params, t_conversions);
return Handle_Return<void>::handle(); return Handle_Return<void>::handle();
} else {
return Handle_Return<Ret>::handle(call_func(sig, std::index_sequence_for<Params...>{}, f, params, t_conversions));
}
} }
#ifdef _MSC_VER
#pragma warning(pop)
#endif
} // namespace detail } // namespace detail
} // namespace dispatch } // namespace dispatch

View File

@ -82,43 +82,6 @@ namespace chaiscript {
return dispatch::detail::make_callable(std::forward<T>(t), dispatch::detail::function_signature(t)); return dispatch::detail::make_callable(std::forward<T>(t), dispatch::detail::function_signature(t));
} }
/// \brief Creates a new Proxy_Function object from an overloaded free function, with the
/// signature specified explicitly to disambiguate the overload.
///
/// \b Example:
/// \code
/// int overloaded(int);
/// double overloaded(double);
///
/// chai.add(chaiscript::fun<int(int)>(&overloaded), "overloaded");
/// chai.add(chaiscript::fun<double(double)>(&overloaded), "overloaded");
/// \endcode
template<typename Sig, std::enable_if_t<std::is_function_v<Sig>, int> = 0>
Proxy_Function fun(std::type_identity_t<Sig> *f) {
return dispatch::detail::make_callable(f, dispatch::detail::function_signature(f));
}
/// \brief Creates a new Proxy_Function object from an overloaded member function, with the
/// signature specified explicitly to disambiguate the overload.
///
/// \b Example:
/// \code
/// class MyClass {
/// public:
/// int overloaded(int);
/// double overloaded(double);
/// int const_overloaded() const;
/// };
///
/// chai.add(chaiscript::fun<int(int)>(&MyClass::overloaded), "overloaded");
/// chai.add(chaiscript::fun<double(double)>(&MyClass::overloaded), "overloaded");
/// chai.add(chaiscript::fun<int() const>(&MyClass::const_overloaded), "const_overloaded");
/// \endcode
template<typename Sig, typename Class, std::enable_if_t<std::is_function_v<Sig>, int> = 0>
Proxy_Function fun(std::type_identity_t<Sig> Class::*f) {
return dispatch::detail::make_callable(f, dispatch::detail::function_signature(f));
}
/// \brief Creates a new Proxy_Function object from a free function, member function or data member and binds the first parameter of it /// \brief Creates a new Proxy_Function object from a free function, member function or data member and binds the first parameter of it
/// \param[in] t Function / member to expose /// \param[in] t Function / member to expose
/// \param[in] q Value to bind to first parameter /// \param[in] q Value to bind to first parameter

View File

@ -0,0 +1,137 @@
#ifndef SHORT_ALLOC_H
#define SHORT_ALLOC_H
// The MIT License (MIT)
//
// Copyright (c) 2015 Howard Hinnant
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <cassert>
#include <cstddef>
template<std::size_t N, std::size_t alignment = alignof(std::max_align_t)>
class arena {
alignas(alignment) char buf_[N];
char *ptr_;
public:
~arena() { ptr_ = nullptr; }
arena() noexcept
: ptr_(buf_) {
}
arena(const arena &) = delete;
arena &operator=(const arena &) = delete;
template<std::size_t ReqAlign>
char *allocate(std::size_t n);
void deallocate(char *p, std::size_t n) noexcept;
static constexpr std::size_t size() noexcept { return N; }
std::size_t used() const noexcept { return static_cast<std::size_t>(ptr_ - buf_); }
void reset() noexcept { ptr_ = buf_; }
private:
static std::size_t align_up(std::size_t n) noexcept { return (n + (alignment - 1)) & ~(alignment - 1); }
bool pointer_in_buffer(char *p) noexcept { return buf_ <= p && p <= buf_ + N; }
};
template<std::size_t N, std::size_t alignment>
template<std::size_t ReqAlign>
char *arena<N, alignment>::allocate(std::size_t n) {
static_assert(ReqAlign <= alignment, "alignment is too small for this arena");
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
auto const aligned_n = align_up(n);
if (static_cast<decltype(aligned_n)>(buf_ + N - ptr_) >= aligned_n) {
char *r = ptr_;
ptr_ += aligned_n;
return r;
}
static_assert(alignment <= alignof(std::max_align_t),
"you've chosen an "
"alignment that is larger than alignof(std::max_align_t), and "
"cannot be guaranteed by normal operator new");
return static_cast<char *>(::operator new(n));
}
template<std::size_t N, std::size_t alignment>
void arena<N, alignment>::deallocate(char *p, std::size_t n) noexcept {
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
if (pointer_in_buffer(p)) {
n = align_up(n);
if (p + n == ptr_) {
ptr_ = p;
}
} else {
::operator delete(p);
}
}
template<class T, std::size_t N, std::size_t Align = alignof(std::max_align_t)>
class short_alloc {
public:
using value_type = T;
static auto constexpr alignment = Align;
static auto constexpr size = N;
using arena_type = arena<size, alignment>;
private:
arena_type &a_;
public:
short_alloc(const short_alloc &) = default;
short_alloc &operator=(const short_alloc &) = delete;
explicit short_alloc(arena_type &a) noexcept
: a_(a) {
static_assert(size % alignment == 0, "size N needs to be a multiple of alignment Align");
}
template<class U>
explicit short_alloc(const short_alloc<U, N, alignment> &a) noexcept
: a_(a.a_) {
}
template<class _Up>
struct rebind {
using other = short_alloc<_Up, N, alignment>;
};
T *allocate(std::size_t n) { return reinterpret_cast<T *>(a_.template allocate<alignof(T)>(n * sizeof(T))); }
void deallocate(T *p, std::size_t n) noexcept { a_.deallocate(reinterpret_cast<char *>(p), n * sizeof(T)); }
template<class T1, std::size_t N1, std::size_t A1, class U, std::size_t M, std::size_t A2>
friend bool operator==(const short_alloc<T1, N1, A1> &x, const short_alloc<U, M, A2> &y) noexcept;
template<class U, std::size_t M, std::size_t A>
friend class short_alloc;
};
template<class T, std::size_t N, std::size_t A1, class U, std::size_t M, std::size_t A2>
inline bool operator==(const short_alloc<T, N, A1> &x, const short_alloc<U, M, A2> &y) noexcept {
return N == M && A1 == A2 && &x.a_ == &y.a_;
}
template<class T, std::size_t N, std::size_t A1, class U, std::size_t M, std::size_t A2>
inline bool operator!=(const short_alloc<T, N, A1> &x, const short_alloc<U, M, A2> &y) noexcept {
return !(x == y);
}
#endif // SHORT_ALLOC_HPP

View File

@ -498,29 +498,6 @@ namespace chaiscript {
func); func);
} }
namespace detail {
template<typename T>
struct is_std_vector : std::false_type {};
template<typename T, typename A>
struct is_std_vector<std::vector<T, A>> : std::true_type {};
template<typename T>
T convert_vector_element(const Boxed_Value &bv) {
if constexpr (is_std_vector<T>::value) {
const auto &inner = Cast_Helper<const std::vector<Boxed_Value> &>::cast(bv, nullptr);
T result;
result.reserve(inner.size());
for (const Boxed_Value &elem : inner) {
result.push_back(convert_vector_element<typename T::value_type>(elem));
}
return result;
} else {
return Cast_Helper<T>::cast(bv, nullptr);
}
}
} // namespace detail
template<typename To> template<typename To>
Type_Conversion vector_conversion() { Type_Conversion vector_conversion() {
auto func = [](const Boxed_Value &t_bv) -> Boxed_Value { auto func = [](const Boxed_Value &t_bv) -> Boxed_Value {
@ -529,7 +506,7 @@ namespace chaiscript {
To vec; To vec;
vec.reserve(from_vec.size()); vec.reserve(from_vec.size());
for (const Boxed_Value &bv : from_vec) { for (const Boxed_Value &bv : from_vec) {
vec.push_back(detail::convert_vector_element<typename To::value_type>(bv)); vec.push_back(detail::Cast_Helper<typename To::value_type>::cast(bv, nullptr));
} }
return Boxed_Value(std::move(vec)); return Boxed_Value(std::move(vec));
@ -566,7 +543,8 @@ namespace chaiscript {
auto pair = std::make_pair( auto pair = std::make_pair(
detail::Cast_Helper<Left>::cast(from_pair.first, nullptr), detail::Cast_Helper<Left>::cast(from_pair.first, nullptr),
detail::Cast_Helper<Right>::cast(from_pair.second, nullptr)); detail::Cast_Helper<Right>::cast(from_pair.second, nullptr)
);
return Boxed_Value(std::move(pair)); return Boxed_Value(std::move(pair));
}; };

View File

@ -12,7 +12,6 @@
#include "../utility/hash.hpp" #include "../utility/hash.hpp"
#include <array>
#include <string> #include <string>
namespace chaiscript { namespace chaiscript {
@ -53,10 +52,10 @@ namespace chaiscript {
invalid invalid
}; };
constexpr static std::string_view to_string(Opers t_oper) noexcept { constexpr static const char *to_string(Opers t_oper) noexcept {
constexpr const std::array opers constexpr const char *opers[]
= {"", "==", "<", ">", "<=", ">=", "!=", "", "=", "++", "--", "*=", "+=", "/=", "-=", "", "&=", "|=", "<<=", ">>=", "%=", "^=", "", "<<", ">>", "%", "&", "|", "^", "~", "", "+", "/", "*", "-", "+", "-", ""}; = {"", "==", "<", ">", "<=", ">=", "!=", "", "=", "++", "--", "*=", "+=", "/=", "-=", "", "&=", "|=", "<<=", ">>=", "%=", "^=", "", "<<", ">>", "%", "&", "|", "^", "~", "", "+", "/", "*", "-", "+", "-", ""};
return opers[static_cast<std::size_t>(t_oper)]; return opers[static_cast<int>(t_oper)];
} }
constexpr static Opers to_operator(std::string_view t_str, bool t_is_unary = false) noexcept { constexpr static Opers to_operator(std::string_view t_str, bool t_is_unary = false) noexcept {

View File

@ -33,7 +33,7 @@ namespace chaiscript {
template<typename T> template<typename T>
static bool is_reserved_word(const T &s) noexcept { static bool is_reserved_word(const T &s) noexcept {
const static std::unordered_set<std::uint32_t> const static std::unordered_set<std::uint32_t>
words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const"), utility::hash("using"), utility::hash("enum")}; words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const")};
return words.count(utility::hash(s)) == 1; return words.count(utility::hash(s)) == 1;
} }
@ -106,10 +106,7 @@ namespace chaiscript {
Constant, Constant,
Compiled, Compiled,
Const_Var_Decl, Const_Var_Decl,
Const_Assign_Decl, Const_Assign_Decl
Using,
Enum,
Namespace_Block
}; };
enum class Operator_Precedence { enum class Operator_Precedence {
@ -130,9 +127,9 @@ namespace chaiscript {
namespace { namespace {
/// Helper lookup to get the name of each node type /// Helper lookup to get the name of each node type
constexpr const char *ast_node_type_to_string(AST_Node_Type ast_node_type) noexcept { constexpr const char *ast_node_type_to_string(AST_Node_Type ast_node_type) noexcept {
constexpr std::array 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"}; 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"};
return ast_node_types[static_cast<std::size_t>(ast_node_type)]; return ast_node_types[static_cast<int>(ast_node_type)];
} }
} // namespace } // namespace

View File

@ -15,7 +15,6 @@
#include <exception> #include <exception>
#include <fstream> #include <fstream>
#include <functional> #include <functional>
#include <future>
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -79,9 +78,6 @@ namespace chaiscript {
std::map<std::string, std::function<Namespace &()>> m_namespace_generators; 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 /// 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) { Boxed_Value do_eval(const std::string &t_input, const std::string &t_filename = "__EVAL__", bool /* t_internal*/ = false) {
try { try {
@ -122,30 +118,11 @@ namespace chaiscript {
chaiscript::detail::Dispatch_Engine &get_eval_engine() noexcept { return m_engine; } chaiscript::detail::Dispatch_Engine &get_eval_engine() noexcept { return m_engine; }
/// Builds all the requirements for ChaiScript, including its evaluator and a run of its prelude. /// Builds all the requirements for ChaiScript, including its evaluator and a run of its prelude.
void build_eval_system(const ModulePtr &t_lib, const std::vector<Options> &t_opts, const bool t_no_io) { void build_eval_system(const ModulePtr &t_lib, const std::vector<Options> &t_opts) {
if (t_lib) { if (t_lib) {
add(t_lib); add(t_lib);
} }
if (!t_no_io) {
m_print_handler = [](const std::string &s) noexcept {
fwrite(s.c_str(), 1, s.size(), stdout);
};
}
m_engine.add(fun([this](const std::string &s) { m_print_handler(s); }), "print_string");
m_engine.add(fun([this](const std::string &s) { m_print_handler(s + "\n"); }), "println_string");
m_engine.add(fun([this](const std::function<void(const std::string &)> &t_handler) {
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]() { 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) { 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"); 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");
@ -195,39 +172,13 @@ 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) { 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"); 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) { m_engine.add(fun([this](const std::string &t_namespace_name) {
if (!m_namespace_generators.count(t_namespace_name)) {
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name); register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
} import(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"); "namespace");
m_engine.add(fun([this](const std::string &t_namespace_name) { import(t_namespace_name); }), "import"); m_engine.add(fun([this](const std::string &t_namespace_name) { import(t_namespace_name); }), "import");
#ifndef CHAISCRIPT_NO_THREADS
// Register async() with thread tracking so the engine can join all
// async threads before destroying shared state (issues #632, #636).
m_engine.add(chaiscript::fun(
[this](const std::function<chaiscript::Boxed_Value()> &t_func) {
auto promise_ptr = std::make_shared<std::promise<chaiscript::Boxed_Value>>();
auto future = promise_ptr->get_future();
m_engine.track_async_thread(std::thread([promise_ptr, t_func]() {
try {
promise_ptr->set_value(t_func());
} catch (...) {
promise_ptr->set_exception(std::current_exception());
}
}));
return future;
}),
"async");
#endif
} }
/// Skip BOM at the beginning of file /// Skip BOM at the beginning of file
@ -250,15 +201,7 @@ namespace chaiscript {
} }
/// Helper function for loading a file /// Helper function for loading a file
std::string load_file(const std::string &t_filename) const { static std::string load_file(const std::string &t_filename) {
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); std::ifstream infile(t_filename.c_str(), std::ios::in | std::ios::ate | std::ios::binary);
if (!infile.is_open()) { if (!infile.is_open()) {
@ -293,17 +236,6 @@ namespace chaiscript {
} }
public: public:
/// \brief Set a custom handler for print output, used by both print_string and println_string
/// \param[in] t_handler Function to call with the string to print
void set_print_handler(std::function<void(const std::string &)> t_handler) {
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 /// \brief Virtual destructor for ChaiScript
virtual ~ChaiScript_Basic() = default; virtual ~ChaiScript_Basic() = default;
@ -316,8 +248,7 @@ namespace chaiscript {
std::unique_ptr<parser::ChaiScript_Parser_Base> &&parser, std::unique_ptr<parser::ChaiScript_Parser_Base> &&parser,
std::vector<std::string> t_module_paths = {}, std::vector<std::string> t_module_paths = {},
std::vector<std::string> t_use_paths = {}, std::vector<std::string> t_use_paths = {},
const std::vector<chaiscript::Options> &t_opts = chaiscript::default_options(), const std::vector<chaiscript::Options> &t_opts = chaiscript::default_options())
const bool t_no_io = false)
: m_module_paths(ensure_minimum_path_vec(std::move(t_module_paths))) : m_module_paths(ensure_minimum_path_vec(std::move(t_module_paths)))
, m_use_paths(ensure_minimum_path_vec(std::move(t_use_paths))) , m_use_paths(ensure_minimum_path_vec(std::move(t_use_paths)))
, m_parser(std::move(parser)) , m_parser(std::move(parser))
@ -352,7 +283,7 @@ namespace chaiscript {
m_module_paths.insert(m_module_paths.begin(), dllpath + "/"); m_module_paths.insert(m_module_paths.begin(), dllpath + "/");
} }
#endif #endif
build_eval_system(t_lib, t_opts, t_no_io); build_eval_system(t_lib, t_opts);
} }
#ifndef CHAISCRIPT_NO_DYNLOAD #ifndef CHAISCRIPT_NO_DYNLOAD
@ -611,7 +542,7 @@ namespace chaiscript {
/// (the symbol mentioned above), an exception is thrown. /// (the symbol mentioned above), an exception is thrown.
/// ///
/// \throw chaiscript::exception::load_module_error In the event that no matching module can be found. /// \throw chaiscript::exception::load_module_error In the event that no matching module can be found.
std::string load_module([[maybe_unused]] const std::string &t_module_name) { std::string load_module(const std::string &t_module_name) {
#ifdef CHAISCRIPT_NO_DYNLOAD #ifdef CHAISCRIPT_NO_DYNLOAD
throw chaiscript::exception::load_module_error("Loadable module support was disabled (CHAISCRIPT_NO_DYNLOAD)"); throw chaiscript::exception::load_module_error("Loadable module support was disabled (CHAISCRIPT_NO_DYNLOAD)");
#else #else
@ -757,59 +688,28 @@ namespace chaiscript {
if (m_engine.get_scripting_objects().count(t_namespace_name)) { if (m_engine.get_scripting_objects().count(t_namespace_name)) {
throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined"); throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined");
} else if (m_namespace_generators.count(t_namespace_name)) { } else if (m_namespace_generators.count(t_namespace_name)) {
auto &ns = m_namespace_generators[t_namespace_name](); m_engine.add_global(var(std::ref(m_namespace_generators[t_namespace_name]())), t_namespace_name);
nest_children(t_namespace_name, ns);
m_engine.add_global(var(std::ref(ns)), t_namespace_name);
} else { } else {
throw std::runtime_error("No registered namespace: " + t_namespace_name); 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 /// \brief Registers a namespace generator, which delays generation of the namespace until it is imported, saving memory if it is never
/// used. Supports C++-style nested names (e.g. "constants::si") for nested namespaces; parent namespaces are auto-registered if absent. /// used. \param[in] t_namespace_generator Namespace generator function. \param[in] t_namespace_name Name of the Namespace function
/// \param[in] t_namespace_generator Namespace generator function. /// being registered. \throw std::runtime_error In the case that the namespace name was already registered.
/// \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) { 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); chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
if (m_namespace_generators.count(t_namespace_name)) { if (!m_namespace_generators.count(t_namespace_name)) {
throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered."); // 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 & {
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable noexcept -> Namespace & {
t_namespace_generator(space); t_namespace_generator(space);
return space; return space;
})); }));
} else {
auto pos = t_namespace_name.rfind("::"); throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered.");
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 noexcept -> 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 } // namespace chaiscript

View File

@ -10,7 +10,6 @@
#ifndef CHAISCRIPT_EVAL_HPP_ #ifndef CHAISCRIPT_EVAL_HPP_
#define CHAISCRIPT_EVAL_HPP_ #define CHAISCRIPT_EVAL_HPP_
#include <algorithm>
#include <exception> #include <exception>
#include <functional> #include <functional>
#include <limits> #include <limits>
@ -109,169 +108,6 @@ namespace chaiscript {
return incoming; return incoming;
} }
} }
class Strong_Typedef_Binary_Op final : public dispatch::Proxy_Function_Base {
public:
Strong_Typedef_Binary_Op(
std::string t_type_name,
std::string t_op_name,
Operators::Opers t_oper,
bool t_rewrap,
chaiscript::detail::Dispatch_Engine &t_engine)
: Proxy_Function_Base(
{chaiscript::detail::Get_Type_Info<Boxed_Value>::get(),
user_type<dispatch::Dynamic_Object>(),
user_type<dispatch::Dynamic_Object>()},
2)
, m_type_name(std::move(t_type_name))
, m_op_name(std::move(t_op_name))
, m_oper(t_oper)
, m_rewrap(t_rewrap)
, m_engine(t_engine) {
}
bool operator==(const Proxy_Function_Base &f) const noexcept override {
if (const auto *other = dynamic_cast<const Strong_Typedef_Binary_Op *>(&f)) {
return m_type_name == other->m_type_name && m_op_name == other->m_op_name;
}
return false;
}
bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override {
return vals.size() == 2
&& type_matches(vals[0], t_conversions)
&& type_matches(vals[1], t_conversions);
}
protected:
Boxed_Value do_call(const Function_Params &params, 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 &params, const Type_Conversions_State &t_conversions) const override {
if (!call_match(params, t_conversions)) {
throw chaiscript::exception::guard_error();
}
auto &lhs = boxed_cast<dispatch::Dynamic_Object &>(params[0], &t_conversions);
const auto &rhs = boxed_cast<const dispatch::Dynamic_Object &>(params[1], &t_conversions);
const auto lhs_val = lhs.get_attr("__value");
const auto rhs_val = rhs.get_attr("__value");
Boxed_Value result;
if (m_base_oper != Operators::Opers::invalid
&& lhs_val.get_type_info().is_arithmetic()
&& rhs_val.get_type_info().is_arithmetic()) {
result = Boxed_Number::do_oper(m_base_oper, lhs_val, rhs_val);
} else {
std::array<Boxed_Value, 2> underlying_params{lhs_val, rhs_val};
result = m_engine.call_function(m_base_op_name, m_loc, Function_Params(underlying_params), t_conversions);
}
lhs.get_attr("__value") = result;
return params[0];
}
private:
bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept {
if (!bv.get_type_info().bare_equal(user_type<dispatch::Dynamic_Object>())) {
return false;
}
try {
const auto &d = boxed_cast<const dispatch::Dynamic_Object &>(bv, &t_conversions);
return d.get_type_name() == m_type_name;
} catch (...) {
return false;
}
}
std::string m_type_name;
std::string m_op_name;
Operators::Opers m_base_oper;
std::string m_base_op_name;
chaiscript::detail::Dispatch_Engine &m_engine;
mutable std::atomic_uint_fast32_t m_loc{0};
};
} // namespace detail } // namespace detail
template<typename T> template<typename T>
@ -602,8 +438,6 @@ namespace chaiscript {
if (m_oper != Operators::Opers::invalid && params[0].get_type_info().is_arithmetic() && params[1].get_type_info().is_arithmetic()) { if (m_oper != Operators::Opers::invalid && params[0].get_type_info().is_arithmetic() && params[1].get_type_info().is_arithmetic()) {
try { try {
return Boxed_Number::do_oper(m_oper, params[0], params[1]); return Boxed_Number::do_oper(m_oper, params[0], params[1]);
} catch (const chaiscript::exception::arithmetic_error &) {
throw;
} catch (const std::exception &) { } catch (const std::exception &) {
throw exception::eval_error("Error with unsupported arithmetic assignment operation."); throw exception::eval_error("Error with unsupported arithmetic assignment operation.");
} }
@ -952,43 +786,40 @@ namespace chaiscript {
return false; return false;
} }
static std::shared_ptr<dispatch::Proxy_Function_Base> make_proxy_function( Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
const Def_AST_Node<T> &t_node, const chaiscript::detail::Dispatch_State &t_ss) {
std::vector<std::string> t_param_names; std::vector<std::string> t_param_names;
size_t numparams = 0; size_t numparams = 0;
dispatch::Param_Types param_types; dispatch::Param_Types param_types;
if ((t_node.children.size() > 1) && (t_node.children[1]->identifier == AST_Node_Type::Arg_List)) { if ((this->children.size() > 1) && (this->children[1]->identifier == AST_Node_Type::Arg_List)) {
numparams = t_node.children[1]->children.size(); numparams = this->children[1]->children.size();
t_param_names = Arg_List_AST_Node<T>::get_arg_names(*t_node.children[1]); t_param_names = Arg_List_AST_Node<T>::get_arg_names(*this->children[1]);
param_types = Arg_List_AST_Node<T>::get_arg_types(*t_node.children[1], t_ss); param_types = Arg_List_AST_Node<T>::get_arg_types(*this->children[1], t_ss);
} }
std::reference_wrapper<chaiscript::detail::Dispatch_Engine> engine(*t_ss); std::reference_wrapper<chaiscript::detail::Dispatch_Engine> engine(*t_ss);
std::shared_ptr<dispatch::Proxy_Function_Base> guard; std::shared_ptr<dispatch::Proxy_Function_Base> guard;
if (t_node.m_guard_node) { if (m_guard_node) {
guard = dispatch::make_dynamic_proxy_function( guard = dispatch::make_dynamic_proxy_function(
[engine, guardnode = t_node.m_guard_node, t_param_names](const Function_Params &t_params) { [engine, guardnode = m_guard_node, t_param_names](const Function_Params &t_params) {
return detail::eval_function(engine, *guardnode, t_param_names, t_params); return detail::eval_function(engine, *guardnode, t_param_names, t_params);
}, },
static_cast<int>(numparams), static_cast<int>(numparams),
t_node.m_guard_node); m_guard_node);
} }
return dispatch::make_dynamic_proxy_function( try {
[engine, func_node = t_node.m_body_node, t_param_names](const Function_Params &t_params) { 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); return detail::eval_function(engine, *func_node, t_param_names, t_params);
}, },
static_cast<int>(numparams), static_cast<int>(numparams),
t_node.m_body_node, m_body_node,
param_types, param_types,
guard); guard),
} l_function_name);
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
try {
t_ss->add(make_proxy_function(*this, t_ss), this->children[0]->text);
} catch (const exception::name_conflict_error &e) { } catch (const exception::name_conflict_error &e) {
throw exception::eval_error("Function redefined '" + e.name() + "'"); throw exception::eval_error("Function redefined '" + e.name() + "'");
} }
@ -1054,268 +885,6 @@ 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, 1}, 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> template<typename T>
struct If_AST_Node final : AST_Node_Impl<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) If_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
@ -1350,7 +919,7 @@ namespace chaiscript {
}; };
const auto call_function = [&t_ss](const auto &t_funcs, const Boxed_Value &t_param) { const auto call_function = [&t_ss](const auto &t_funcs, const Boxed_Value &t_param) {
return dispatch::dispatch(*t_funcs, Function_Params{&t_param, 1}, t_ss.conversions()); return dispatch::dispatch(*t_funcs, Function_Params{t_param}, t_ss.conversions());
}; };
const std::string &loop_var_name = this->children[0]->text; const std::string &loop_var_name = this->children[0]->text;
@ -1646,8 +1215,8 @@ namespace chaiscript {
return Boxed_Number::do_oper(m_oper, bv); return Boxed_Number::do_oper(m_oper, bv);
} else { } else {
chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); chaiscript::eval::detail::Function_Push_Pop fpp(t_ss);
fpp.save_params(Function_Params{&bv, 1}); fpp.save_params(Function_Params{bv});
return t_ss->call_function(this->text, m_loc, Function_Params{&bv, 1}, t_ss.conversions()); return t_ss->call_function(this->text, m_loc, Function_Params{bv}, t_ss.conversions());
} }
} catch (const exception::dispatch_error &e) { } catch (const exception::dispatch_error &e) {
throw exception::eval_error("Error with prefix operator evaluation: '" + this->text + "'", e.parameters, e.functions, false, *t_ss); throw exception::eval_error("Error with prefix operator evaluation: '" + this->text + "'", e.parameters, e.functions, false, *t_ss);
@ -1734,7 +1303,6 @@ namespace chaiscript {
Boxed_Value handle_exception(const chaiscript::detail::Dispatch_State &t_ss, const Boxed_Value &t_except) const { Boxed_Value handle_exception(const chaiscript::detail::Dispatch_State &t_ss, const Boxed_Value &t_except) const {
Boxed_Value retval; Boxed_Value retval;
bool handled = false;
size_t end_point = this->children.size(); size_t end_point = this->children.size();
if (this->children.back()->identifier == AST_Node_Type::Finally) { if (this->children.back()->identifier == AST_Node_Type::Finally) {
@ -1748,33 +1316,30 @@ namespace chaiscript {
if (catch_block.children.size() == 1) { if (catch_block.children.size() == 1) {
// No variable capture // No variable capture
retval = catch_block.children[0]->eval(t_ss); retval = catch_block.children[0]->eval(t_ss);
handled = true;
break; break;
} else if (catch_block.children.size() == 2 || catch_block.children.size() == 3) { } else if (catch_block.children.size() == 2 || catch_block.children.size() == 3) {
const auto name = Arg_List_AST_Node<T>::get_arg_name(*catch_block.children[0]); const auto name = Arg_List_AST_Node<T>::get_arg_name(*catch_block.children[0]);
if (dispatch::Param_Types( if (dispatch::Param_Types(
std::vector<std::pair<std::string, Type_Info>>{Arg_List_AST_Node<T>::get_arg_type(*catch_block.children[0], t_ss)}) std::vector<std::pair<std::string, Type_Info>>{Arg_List_AST_Node<T>::get_arg_type(*catch_block.children[0], t_ss)})
.match(Function_Params{&t_except, 1}, t_ss.conversions()) .match(Function_Params{t_except}, t_ss.conversions())
.first) { .first) {
t_ss.add_object(name, t_except); t_ss.add_object(name, t_except);
if (catch_block.children.size() == 2) { if (catch_block.children.size() == 2) {
// Variable capture // Variable capture
retval = catch_block.children[1]->eval(t_ss); retval = catch_block.children[1]->eval(t_ss);
handled = true;
break; break;
} }
} }
} else { } else {
if (this->children.back()->identifier == AST_Node_Type::Finally) {
this->children.back()->children[0]->eval(t_ss);
}
throw exception::eval_error("Internal error: catch block size unrecognized"); throw exception::eval_error("Internal error: catch block size unrecognized");
} }
} }
if (!handled) {
throw;
}
return retval; return retval;
} }
@ -1783,7 +1348,6 @@ namespace chaiscript {
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
try {
try { try {
retval = this->children[0]->eval(t_ss); retval = this->children[0]->eval(t_ss);
} catch (const exception::eval_error &e) { } catch (const exception::eval_error &e) {
@ -1796,7 +1360,6 @@ namespace chaiscript {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
} catch (Boxed_Value &e) { } catch (Boxed_Value &e) {
retval = handle_exception(t_ss, e); retval = handle_exception(t_ss, e);
}
} catch (...) { } catch (...) {
if (this->children.back()->identifier == AST_Node_Type::Finally) { if (this->children.back()->identifier == AST_Node_Type::Finally) {
this->children.back()->children[0]->eval(t_ss); this->children.back()->children[0]->eval(t_ss);

View File

@ -397,9 +397,8 @@ namespace chaiscript {
assert(children.size() == 1); assert(children.size() == 1);
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
Boxed_Value bv_i(start_int); int i = start_int;
auto &i = *static_cast<int *>(bv_i.get_ptr()); t_ss.add_object(id, var(&i));
t_ss.add_object(id, bv_i);
try { try {
for (; i < end_int; ++i) { for (; i < end_int; ++i) {

View File

@ -14,7 +14,6 @@
#include <cstring> #include <cstring>
#include <exception> #include <exception>
#include <iostream> #include <iostream>
#include <iterator>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <string> #include <string>
@ -23,11 +22,15 @@
#include "../dispatchkit/boxed_value.hpp" #include "../dispatchkit/boxed_value.hpp"
#include "../utility/hash.hpp" #include "../utility/hash.hpp"
#include "../utility/static_string.hpp" #include "../utility/static_string.hpp"
#include "../utility/unicode.hpp"
#include "chaiscript_common.hpp" #include "chaiscript_common.hpp"
#include "chaiscript_optimizer.hpp" #include "chaiscript_optimizer.hpp"
#include "chaiscript_tracer.hpp" #include "chaiscript_tracer.hpp"
#if defined(CHAISCRIPT_UTF16_UTF32)
#include <codecvt>
#include <locale>
#endif
#if defined(CHAISCRIPT_MSVC) && defined(max) && defined(min) #if defined(CHAISCRIPT_MSVC) && defined(max) && defined(min)
#define CHAISCRIPT_PUSHED_MIN_MAX #define CHAISCRIPT_PUSHED_MIN_MAX
#pragma push_macro("max") // Why Microsoft? why? This is worse than bad #pragma push_macro("max") // Why Microsoft? why? This is worse than bad
@ -61,26 +64,41 @@ namespace chaiscript {
// Generic for u16, u32 and wchar // Generic for u16, u32 and wchar
template<typename string_type> template<typename string_type>
struct Char_Parser_Helper { struct Char_Parser_Helper {
static string_type str_from_ll(long long val) { // common for all implementations
string_type out; static std::string u8str_from_ll(long long val) {
utility::unicode::append_codepoint(out, static_cast<std::uint32_t>(val));
return out;
}
};
// Specialization for char AKA UTF-8: preserve raw two-byte packing
// of multi-character literals.
template<>
struct Char_Parser_Helper<std::string> {
static std::string str_from_ll(long long val) {
using char_type = std::string::value_type; using char_type = std::string::value_type;
char_type c[2]; char_type c[2];
c[1] = char_type(val); c[1] = char_type(val);
c[0] = char_type(val >> 8); c[0] = char_type(val >> 8);
if (c[0] == 0) { if (c[0] == 0) {
return std::string(1, c[1]); return std::string(1, c[1]); // size, character
} }
return std::string(c, 2);
return std::string(c, 2); // char buffer, size
}
static string_type str_from_ll(long long val) {
using target_char_type = typename string_type::value_type;
#if defined(CHAISCRIPT_UTF16_UTF32)
// prepare converter
std::wstring_convert<std::codecvt_utf8<target_char_type>, target_char_type> converter;
// convert
return converter.from_bytes(u8str_from_ll(val));
#else
// no conversion available, just put value as character
return string_type(1, target_char_type(val)); // size, character
#endif
}
};
// Specialization for char AKA UTF-8
template<>
struct Char_Parser_Helper<std::string> {
static std::string str_from_ll(long long val) {
// little SFINAE trick to avoid base class
return Char_Parser_Helper<std::true_type>::u8str_from_ll(val);
} }
}; };
} // namespace detail } // namespace detail
@ -110,7 +128,9 @@ namespace chaiscript {
template<typename Array2D, typename First, typename Second> template<typename Array2D, typename First, typename Second>
constexpr static void set_alphabet(Array2D &array, const First first, const Second second) noexcept { constexpr static void set_alphabet(Array2D &array, const First first, const Second second) noexcept {
array[static_cast<std::size_t>(first)][static_cast<std::size_t>(second)] = true; auto *first_ptr = &std::get<0>(array) + static_cast<std::size_t>(first);
auto *second_ptr = &std::get<0>(*first_ptr) + static_cast<std::size_t>(second);
*second_ptr = true;
} }
constexpr static std::array<std::array<bool, detail::lengthof_alphabet>, detail::max_alphabet> build_alphabet() noexcept { constexpr static std::array<std::array<bool, detail::lengthof_alphabet>, detail::max_alphabet> build_alphabet() noexcept {
@ -340,13 +360,13 @@ namespace chaiscript {
++col; ++col;
} }
std::advance(m_pos, 1); ++m_pos;
} }
return *this; return *this;
} }
constexpr Position &operator--() noexcept { constexpr Position &operator--() noexcept {
std::advance(m_pos, -1); --m_pos;
if (*m_pos == '\n') { if (*m_pos == '\n') {
--line; --line;
col = m_last_col; col = m_last_col;
@ -357,7 +377,7 @@ namespace chaiscript {
} }
constexpr Position &operator+=(size_t t_distance) noexcept { constexpr Position &operator+=(size_t t_distance) noexcept {
*this = *this + t_distance; *this = (*this) + t_distance;
return *this; return *this;
} }
@ -386,9 +406,9 @@ namespace chaiscript {
constexpr bool operator!=(const Position &t_rhs) const noexcept { return m_pos != t_rhs.m_pos; } constexpr bool operator!=(const Position &t_rhs) const noexcept { return m_pos != t_rhs.m_pos; }
[[nodiscard]] constexpr bool has_more() const noexcept { return m_pos != m_end; } constexpr bool has_more() const noexcept { return m_pos != m_end; }
[[nodiscard]] constexpr size_t remaining() const noexcept { return static_cast<size_t>(m_end - m_pos); } constexpr size_t remaining() const noexcept { return static_cast<size_t>(m_end - m_pos); }
constexpr const char &operator*() const noexcept { constexpr const char &operator*() const noexcept {
if (m_pos == m_end) { if (m_pos == m_end) {
@ -438,7 +458,7 @@ namespace chaiscript {
constexpr static Operator_Matches m_operator_matches{}; constexpr static Operator_Matches m_operator_matches{};
/// test a char in an m_alphabet /// test a char in an m_alphabet
[[nodiscard]] constexpr bool char_in_alphabet(char c, detail::Alphabet a) const noexcept { return m_alphabet[a][static_cast<uint8_t>(c)]; } constexpr bool char_in_alphabet(char c, detail::Alphabet a) const noexcept { return m_alphabet[a][static_cast<uint8_t>(c)]; }
/// Prints the parsed ast_nodes as a tree /// Prints the parsed ast_nodes as a tree
void debug_print(const AST_Node &t, std::string prepend = "") const override { void debug_print(const AST_Node &t, std::string prepend = "") const override {
@ -489,7 +509,7 @@ namespace chaiscript {
if (m_position.remaining() >= len) { if (m_position.remaining() >= len) {
const char *file_pos = &(*m_position); const char *file_pos = &(*m_position);
for (size_t pos = 0; pos < len; ++pos) { for (size_t pos = 0; pos < len; ++pos) {
if (sym[pos] != *std::next(file_pos, static_cast<std::ptrdiff_t>(pos))) { if (sym.c_str()[pos] != file_pos[pos]) {
return false; return false;
} }
} }
@ -510,9 +530,20 @@ namespace chaiscript {
} }
} }
return true; return true;
} else if (Symbol_(m_singleline_comment)) {
while (m_position.has_more()) {
if (Symbol_(m_cr_lf)) {
m_position -= 2;
break;
} else if (Char_('\n')) {
--m_position;
break;
} else {
++m_position;
} }
}
if (Symbol_(m_singleline_comment) || Symbol_(m_annotation)) { return true;
} else if (Symbol_(m_annotation)) {
while (m_position.has_more()) { while (m_position.has_more()) {
if (Symbol_(m_cr_lf)) { if (Symbol_(m_cr_lf)) {
m_position -= 2; m_position -= 2;
@ -1029,7 +1060,7 @@ namespace chaiscript {
template<typename string_type> template<typename string_type>
struct Char_Parser { struct Char_Parser {
string_type &match; string_type &match;
using char_type = string_type::value_type; using char_type = typename string_type::value_type;
bool is_escaped = false; bool is_escaped = false;
bool is_interpolated = false; bool is_interpolated = false;
bool saw_interpolation_marker = false; bool saw_interpolation_marker = false;
@ -1088,20 +1119,40 @@ namespace chaiscript {
} }
void process_unicode() { void process_unicode() {
const auto ch = static_cast<std::uint32_t>(std::stoi(hex_matches, nullptr, 16)); const auto ch = static_cast<uint32_t>(std::stoi(hex_matches, nullptr, 16));
const auto match_size = hex_matches.size(); const auto match_size = hex_matches.size();
hex_matches.clear(); hex_matches.clear();
is_escaped = false; is_escaped = false;
const auto u_size = unicode_size; const auto u_size = unicode_size;
unicode_size = 0; unicode_size = 0;
char buf[4];
if (u_size != match_size) { if (u_size != match_size) {
throw exception::eval_error("Incomplete unicode escape sequence"); throw exception::eval_error("Incomplete unicode escape sequence");
} }
if (u_size == 4 && utility::unicode::is_surrogate(ch)) { if (u_size == 4 && ch >= 0xD800 && ch <= 0xDFFF) {
throw exception::eval_error("Invalid 16 bit universal character"); throw exception::eval_error("Invalid 16 bit universal character");
} }
if (utility::unicode::append_utf8(match, ch) == 0) {
if (ch < 0x80) {
match += static_cast<char>(ch);
} else if (ch < 0x800) {
buf[0] = static_cast<char>(0xC0 | (ch >> 6));
buf[1] = static_cast<char>(0x80 | (ch & 0x3F));
match.append(buf, 2);
} else if (ch < 0x10000) {
buf[0] = static_cast<char>(0xE0 | (ch >> 12));
buf[1] = static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
buf[2] = static_cast<char>(0x80 | (ch & 0x3F));
match.append(buf, 3);
} else if (ch < 0x200000) {
buf[0] = static_cast<char>(0xF0 | (ch >> 18));
buf[1] = static_cast<char>(0x80 | ((ch >> 12) & 0x3F));
buf[2] = static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
buf[3] = static_cast<char>(0x80 | (ch & 0x3F));
match.append(buf, 4);
} else {
// this must be an invalid escape sequence?
throw exception::eval_error("Invalid 32 bit universal character"); throw exception::eval_error("Invalid 32 bit universal character");
} }
} }
@ -1497,7 +1548,7 @@ namespace chaiscript {
if (m_position.remaining() >= len) { if (m_position.remaining() >= len) {
auto tmp = m_position; auto tmp = m_position;
for (size_t i = 0; tmp.has_more() && i < len; ++i) { for (size_t i = 0; tmp.has_more() && i < len; ++i) {
if (*tmp != t_s[i]) { if (*tmp != t_s.c_str()[i]) {
return false; return false;
} }
++tmp; ++tmp;
@ -1524,7 +1575,7 @@ namespace chaiscript {
return retval; return retval;
} }
[[nodiscard]] bool is_operator(std::string_view t_s) const noexcept { return m_operator_matches.is_match(t_s); } bool is_operator(std::string_view t_s) const noexcept { return m_operator_matches.is_match(t_s); }
/// Reads (and potentially captures) a symbol group from input if it matches the parameter /// Reads (and potentially captures) a symbol group from input if it matches the parameter
bool Symbol(const utility::Static_String &t_s, const bool t_disallow_prevention = false) { bool Symbol(const utility::Static_String &t_s, const bool t_disallow_prevention = false) {
@ -1939,44 +1990,6 @@ namespace chaiscript {
} }
/// Reads a class block from input /// 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) { bool Class(const bool t_class_allowed) {
Depth_Counter dc{this}; Depth_Counter dc{this};
bool retval = false; bool retval = false;
@ -2018,134 +2031,6 @@ namespace chaiscript {
return retval; return retval;
} }
bool Using(const bool t_class_allowed) {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
if (Keyword("using")) {
if (!t_class_allowed) {
throw exception::eval_error("Type alias definitions only allowed at top scope",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!Id(true)) {
throw exception::eval_error("Missing type name in 'using' declaration",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!Symbol("=", true)) {
throw exception::eval_error("Missing '=' in 'using' declaration",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!Id(true)) {
throw exception::eval_error("Missing base type name in 'using' declaration",
File_Position(m_position.line, m_position.col),
*m_filename);
}
build_match<eval::Using_AST_Node<Tracer>>(prev_stack_top);
return true;
}
return false;
}
bool Enum(const bool t_allowed) {
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 /// Reads a while block from input
bool While() { bool While() {
Depth_Counter dc{this}; Depth_Counter dc{this};
@ -2397,7 +2282,7 @@ namespace chaiscript {
const auto prev_stack_top = m_match_stack.size(); const auto prev_stack_top = m_match_stack.size();
if (Keyword("return")) { if (Keyword("return")) {
Equation(); Operator();
build_match<eval::Return_AST_Node<Tracer>>(prev_stack_top); build_match<eval::Return_AST_Node<Tracer>>(prev_stack_top);
return true; return true;
} else { } else {
@ -2494,7 +2379,7 @@ namespace chaiscript {
} }
build_match<eval::Array_Call_AST_Node<Tracer>>(prev_stack_top); build_match<eval::Array_Call_AST_Node<Tracer>>(prev_stack_top);
} else if (Symbol(".") || Symbol("::")) { } else if (Symbol(".")) {
has_more = true; has_more = true;
if (!(Id(true))) { if (!(Id(true))) {
throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename); throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename);
@ -2698,7 +2583,7 @@ namespace chaiscript {
bool retval = false; bool retval = false;
const auto prev_stack_top = m_match_stack.size(); const auto prev_stack_top = m_match_stack.size();
if (t_precedence < m_operators.size() && m_operators[t_precedence] < Operator_Precedence::Prefix) { if (m_operators[t_precedence] != Operator_Precedence::Prefix) {
if (Operator(t_precedence + 1)) { if (Operator(t_precedence + 1)) {
retval = true; retval = true;
std::string oper; std::string oper;
@ -2712,7 +2597,7 @@ namespace chaiscript {
} }
switch (m_operators[t_precedence]) { switch (m_operators[t_precedence]) {
case Operator_Precedence::Ternary_Cond: case (Operator_Precedence::Ternary_Cond):
if (Symbol(":")) { if (Symbol(":")) {
if (!Operator(t_precedence + 1)) { if (!Operator(t_precedence + 1)) {
throw exception::eval_error("Incomplete '" + oper + "' expression", throw exception::eval_error("Incomplete '" + oper + "' expression",
@ -2727,24 +2612,24 @@ namespace chaiscript {
} }
break; break;
case Operator_Precedence::Addition: case (Operator_Precedence::Addition):
case Operator_Precedence::Multiplication: case (Operator_Precedence::Multiplication):
case Operator_Precedence::Shift: case (Operator_Precedence::Shift):
case Operator_Precedence::Equality: case (Operator_Precedence::Equality):
case Operator_Precedence::Bitwise_And: case (Operator_Precedence::Bitwise_And):
case Operator_Precedence::Bitwise_Xor: case (Operator_Precedence::Bitwise_Xor):
case Operator_Precedence::Bitwise_Or: case (Operator_Precedence::Bitwise_Or):
case Operator_Precedence::Comparison: case (Operator_Precedence::Comparison):
build_match<eval::Binary_Operator_AST_Node<Tracer>>(prev_stack_top, oper); build_match<eval::Binary_Operator_AST_Node<Tracer>>(prev_stack_top, oper);
break; break;
case Operator_Precedence::Logical_And: case (Operator_Precedence::Logical_And):
build_match<eval::Logical_And_AST_Node<Tracer>>(prev_stack_top, oper); build_match<eval::Logical_And_AST_Node<Tracer>>(prev_stack_top, oper);
break; break;
case Operator_Precedence::Logical_Or: case (Operator_Precedence::Logical_Or):
build_match<eval::Logical_Or_AST_Node<Tracer>>(prev_stack_top, oper); build_match<eval::Logical_Or_AST_Node<Tracer>>(prev_stack_top, oper);
break; break;
case Operator_Precedence::Prefix: case (Operator_Precedence::Prefix):
assert(false); // cannot reach here because of if() statement at the top assert(false); // cannot reach here because of if() statement at the top
break; break;
@ -2891,7 +2776,7 @@ namespace chaiscript {
while (has_more) { while (has_more) {
const auto start = m_position; const auto start = m_position;
if (Def() || Try() || If() || While() || Namespace_Block() || Class(t_class_allowed) || Using(t_class_allowed) || Enum(t_class_allowed) || For() || Switch()) { if (Def() || Try() || If() || While() || Class(t_class_allowed) || For() || Switch()) {
if (!saw_eol) { if (!saw_eol) {
throw exception::eval_error("Two function definitions missing line separator", throw exception::eval_error("Two function definitions missing line separator",
File_Position(start.line, start.col), File_Position(start.line, start.col),
@ -2941,7 +2826,7 @@ namespace chaiscript {
/// Parses the given input string, tagging parsed ast_nodes with the given m_filename. /// Parses the given input string, tagging parsed ast_nodes with the given m_filename.
AST_NodePtr parse_internal(const std::string &t_input, std::string t_fname) { AST_NodePtr parse_internal(const std::string &t_input, std::string t_fname) {
const auto begin = t_input.empty() ? nullptr : &t_input.front(); const auto begin = t_input.empty() ? nullptr : &t_input.front();
const auto end = begin == nullptr ? nullptr : std::next(begin, std::ssize(t_input)); const auto end = begin == nullptr ? nullptr : begin + t_input.size();
m_position = Position(begin, end); m_position = Position(begin, end);
m_filename = std::make_shared<std::string>(std::move(t_fname)); m_filename = std::make_shared<std::string>(std::move(t_fname));

View File

@ -8,9 +8,7 @@
#define CHAISCRIPT_UTILITY_FNV1A_HPP_ #define CHAISCRIPT_UTILITY_FNV1A_HPP_
#include "../chaiscript_defines.hpp" #include "../chaiscript_defines.hpp"
#include <cstdint> #include <cstdint>
#include <iterator>
namespace chaiscript { namespace chaiscript {
namespace utility { namespace utility {
@ -30,7 +28,7 @@ namespace chaiscript {
while (begin != end) { while (begin != end) {
h = (h ^ (*begin)) * 0x01000193; h = (h ^ (*begin)) * 0x01000193;
std::advance(begin, 1); ++begin;
} }
return h; return h;
@ -45,7 +43,7 @@ namespace chaiscript {
template<size_t N> template<size_t N>
static constexpr std::uint32_t hash(const char (&str)[N]) noexcept { static constexpr std::uint32_t hash(const char (&str)[N]) noexcept {
return hash(std::begin(str), std::prev(std::end(str))); return hash(std::begin(str), std::end(str) - 1);
} }
static constexpr std::uint32_t hash(std::string_view sv) noexcept { static constexpr std::uint32_t hash(std::string_view sv) noexcept {
@ -66,7 +64,7 @@ namespace chaiscript {
hash += std::uint32_t(*begin); hash += std::uint32_t(*begin);
hash += hash << 10; hash += hash << 10;
hash ^= hash >> 6; hash ^= hash >> 6;
std::advance(begin, 1); ++begin;
} }
hash += hash << 3; hash += hash << 3;

View File

@ -7,7 +7,6 @@
#include "../chaiscript_defines.hpp" #include "../chaiscript_defines.hpp"
#include "quick_flat_map.hpp" #include "quick_flat_map.hpp"
#include "unicode.hpp"
#include <cctype> #include <cctype>
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>
@ -45,17 +44,21 @@ namespace chaiscript::json {
= std::variant<std::nullptr_t, chaiscript::utility::QuickFlatMap<std::string, JSON>, std::vector<JSON>, std::string, double, std::int64_t, bool>; = std::variant<std::nullptr_t, chaiscript::utility::QuickFlatMap<std::string, JSON>, std::vector<JSON>, std::string, double, std::int64_t, bool>;
struct Internal { struct Internal {
explicit Internal(std::nullptr_t) {} Internal(std::nullptr_t)
Internal() = default; : d(nullptr) {
Internal(const Class c) }
Internal()
: d(nullptr) {
}
Internal(Class c)
: d(make_type(c)) { : d(make_type(c)) {
} }
template<typename T> template<typename T>
explicit Internal(T t) Internal(T t)
: d(std::move(t)) { : d(std::move(t)) {
} }
static Data make_type(const Class c) { static Data make_type(Class c) {
switch (c) { switch (c) {
case Class::Null: case Class::Null:
return nullptr; return nullptr;
@ -81,7 +84,7 @@ namespace chaiscript::json {
} }
} }
[[nodiscard]] Class type() const noexcept { return Class(d.index()); } Class type() const noexcept { return Class(d.index()); }
template<auto ClassValue, typename Visitor, typename Or> template<auto ClassValue, typename Visitor, typename Or>
decltype(auto) visit_or(Visitor &&visitor, Or &&other) const { decltype(auto) visit_or(Visitor &&visitor, Or &&other) const {
@ -105,14 +108,14 @@ namespace chaiscript::json {
auto &Float() { return get_set_type<Class::Floating>(); } auto &Float() { return get_set_type<Class::Floating>(); }
auto &Bool() { return get_set_type<Class::Boolean>(); } auto &Bool() { return get_set_type<Class::Boolean>(); }
[[nodiscard]] auto Map() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Object)>(&d); } auto Map() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Object)>(&d); }
[[nodiscard]] auto Vector() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Array)>(&d); } auto Vector() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Array)>(&d); }
[[nodiscard]] auto String() const noexcept { return std::get_if<static_cast<std::size_t>(Class::String)>(&d); } auto String() const noexcept { return std::get_if<static_cast<std::size_t>(Class::String)>(&d); }
[[nodiscard]] auto Int() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Integral)>(&d); } auto Int() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Integral)>(&d); }
[[nodiscard]] auto Float() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Floating)>(&d); } auto Float() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Floating)>(&d); }
[[nodiscard]] auto Bool() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Boolean)>(&d); } auto Bool() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Boolean)>(&d); }
Data d{nullptr}; Data d;
}; };
Internal internal; Internal internal;
@ -128,10 +131,10 @@ namespace chaiscript::json {
} }
JSONWrapper(std::nullptr_t) {} JSONWrapper(std::nullptr_t) {}
Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); } typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); }
Container::iterator end() { return object ? object->end() : typename Container::iterator(); } typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); }
[[nodiscard]] Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); } typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); }
[[nodiscard]] Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); } typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); }
}; };
template<typename Container> template<typename Container>
@ -144,10 +147,10 @@ namespace chaiscript::json {
} }
JSONConstWrapper(std::nullptr_t) {} JSONConstWrapper(std::nullptr_t) {}
[[nodiscard]] Container::const_iterator begin() const noexcept { typename Container::const_iterator begin() const noexcept {
return object ? object->begin() : typename Container::const_iterator(); return object ? object->begin() : typename Container::const_iterator();
} }
[[nodiscard]] Container::const_iterator end() const noexcept { return object ? object->end() : typename Container::const_iterator(); } typename Container::const_iterator end() const noexcept { return object ? object->end() : typename Container::const_iterator(); }
}; };
JSON() = default; JSON() = default;
@ -159,9 +162,7 @@ namespace chaiscript::json {
JSON(initializer_list<JSON> list) JSON(initializer_list<JSON> list)
: internal(Class::Object) { : internal(Class::Object) {
for (auto i = list.begin(), e = list.end(); for (auto i = list.begin(), e = list.end(); i != e; ++i, ++i) {
i != e;
std::advance(i, 2)) {
operator[](i->to_string()) = *std::next(i); operator[](i->to_string()) = *std::next(i);
} }
} }
@ -470,8 +471,22 @@ namespace chaiscript::json {
} }
} }
offset += 4; offset += 4;
const auto ch = static_cast<std::uint32_t>(std::stoi(hex_matches, nullptr, 16)); const auto ch = static_cast<uint32_t>(std::stoi(hex_matches, nullptr, 16));
if (chaiscript::utility::unicode::append_utf8(val, ch) == 0) { if (ch < 0x80) {
val += static_cast<char>(ch);
} else if (ch < 0x800) {
val += static_cast<char>(0xC0 | (ch >> 6));
val += static_cast<char>(0x80 | (ch & 0x3F));
} else if (ch < 0x10000) {
val += static_cast<char>(0xE0 | (ch >> 12));
val += static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
val += static_cast<char>(0x80 | (ch & 0x3F));
} else if (ch < 0x200000) {
val += static_cast<char>(0xF0 | (ch >> 18));
val += static_cast<char>(0x80 | ((ch >> 12) & 0x3F));
val += static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
val += static_cast<char>(0x80 | (ch & 0x3F));
} else {
throw std::runtime_error(std::string("JSON ERROR: String: Invalid 32 bit universal character")); throw std::runtime_error(std::string("JSON ERROR: String: Invalid 32 bit universal character"));
} }
} break; } break;

View File

@ -1,7 +1,6 @@
#ifndef CHAISCRIPT_SIMPLEJSON_WRAP_HPP #ifndef CHAISCRIPT_SIMPLEJSON_WRAP_HPP
#define CHAISCRIPT_SIMPLEJSON_WRAP_HPP #define CHAISCRIPT_SIMPLEJSON_WRAP_HPP
#include "../dispatchkit/dynamic_object.hpp"
#include "json.hpp" #include "json.hpp"
namespace chaiscript { namespace chaiscript {
@ -9,10 +8,7 @@ namespace chaiscript {
public: public:
static Module &library(Module &m) { static Module &library(Module &m) {
m.add(chaiscript::fun([](const std::string &t_str) { return from_json(t_str); }), "from_json"); m.add(chaiscript::fun([](const std::string &t_str) { return from_json(t_str); }), "from_json");
m.add(chaiscript::fun([](const std::string &t_str) { return object_from_json(t_str); }), "object_from_json");
m.add(chaiscript::fun(&json_wrap::to_json), "to_json"); m.add(chaiscript::fun(&json_wrap::to_json), "to_json");
m.add(chaiscript::fun(&json_wrap::map_to_object), "map_to_object");
m.add(chaiscript::fun(&json_wrap::object_to_map), "object_to_map");
return m; return m;
} }
@ -61,63 +57,6 @@ namespace chaiscript {
} }
} }
static Boxed_Value object_from_json(const json::JSON &t_json) {
switch (t_json.JSONType()) {
case json::JSON::Class::Null:
return Boxed_Value();
case json::JSON::Class::Object: {
auto obj = dispatch::Dynamic_Object("JSON_Object");
for (const auto &p : t_json.object_range()) {
obj.get_attr(p.first) = object_from_json(p.second);
}
return Boxed_Value(std::move(obj));
}
case json::JSON::Class::Array: {
std::vector<Boxed_Value> vec;
for (const auto &p : t_json.array_range()) {
vec.emplace_back(object_from_json(p));
}
return Boxed_Value(vec);
}
case json::JSON::Class::String:
return Boxed_Value(t_json.to_string());
case json::JSON::Class::Floating:
return Boxed_Value(t_json.to_float());
case json::JSON::Class::Integral:
return Boxed_Value(t_json.to_int());
case json::JSON::Class::Boolean:
return Boxed_Value(t_json.to_bool());
}
throw std::runtime_error("Unknown JSON type");
}
static Boxed_Value object_from_json(const std::string &t_json) {
try {
return object_from_json(json::JSON::Load(t_json));
} catch (const std::out_of_range &) {
throw std::runtime_error("Unparsed JSON input");
}
}
static Boxed_Value map_to_object(const std::map<std::string, Boxed_Value> &t_map) {
auto obj = dispatch::Dynamic_Object("JSON_Object");
for (const auto &p : t_map) {
obj.get_attr(p.first) = p.second;
}
return Boxed_Value(std::move(obj));
}
static std::map<std::string, Boxed_Value> object_to_map(const dispatch::Dynamic_Object &t_obj) {
return t_obj.get_attrs();
}
static std::string to_json(const Boxed_Value &t_bv) { return to_json_object(t_bv).dump(); } static std::string to_json(const Boxed_Value &t_bv) { return to_json_object(t_bv).dump(); }
static json::JSON to_json_object(const Boxed_Value &t_bv) { static json::JSON to_json_object(const Boxed_Value &t_bv) {

View File

@ -7,8 +7,6 @@
#ifndef CHAISCRIPT_UTILITY_STATIC_STRING_HPP_ #ifndef CHAISCRIPT_UTILITY_STATIC_STRING_HPP_
#define CHAISCRIPT_UTILITY_STATIC_STRING_HPP_ #define CHAISCRIPT_UTILITY_STATIC_STRING_HPP_
#include <iterator>
namespace chaiscript::utility { namespace chaiscript::utility {
struct Static_String { struct Static_String {
template<size_t N> template<size_t N>
@ -23,17 +21,30 @@ namespace chaiscript::utility {
constexpr auto begin() const noexcept { return data; } constexpr auto begin() const noexcept { return data; }
constexpr auto end() const noexcept { return std::next(data, static_cast<std::ptrdiff_t>(m_size)); } constexpr auto end() const noexcept { return data + m_size; }
constexpr auto operator[](const std::size_t idx) const noexcept {
return *std::next(data, static_cast<std::ptrdiff_t>(idx));
}
constexpr bool operator==(std::string_view other) const noexcept { constexpr bool operator==(std::string_view other) const noexcept {
return std::string_view(data, m_size) == other; // return std::string_view(data, m_size) == other;
auto b1 = begin();
const auto e1 = end();
auto b2 = other.begin();
const auto e2 = other.end();
if (e1 - b1 != e2 - b2) {
return false;
} }
constexpr bool operator==(const std::string &t_str) const noexcept { return std::equal(begin(), end(), std::cbegin(t_str), std::cend(t_str)); } while (b1 != e1) {
if (*b1 != *b2) {
return false;
}
++b1;
++b2;
}
return true;
}
bool operator==(const std::string &t_str) const noexcept { return std::equal(begin(), end(), std::cbegin(t_str), std::cend(t_str)); }
const size_t m_size; const size_t m_size;
const char *data = nullptr; const char *data = nullptr;

View File

@ -1,93 +0,0 @@
// This file is distributed under the BSD License.
// See "license.txt" for details.
// http://www.chaiscript.com
#ifndef CHAISCRIPT_UTILITY_UNICODE_HPP_
#define CHAISCRIPT_UTILITY_UNICODE_HPP_
#include <cstddef>
#include <cstdint>
#include <string>
namespace chaiscript {
namespace utility {
namespace unicode {
inline constexpr std::uint32_t max_codepoint = 0x10FFFF;
constexpr bool is_surrogate(std::uint32_t cp) noexcept {
return cp >= 0xD800 && cp <= 0xDFFF;
}
// Append cp to out as UTF-8. Returns bytes written, or 0 if cp >= 0x200000.
// Surrogates are not rejected here; callers that care check is_surrogate() first.
inline std::size_t append_utf8(std::string &out, std::uint32_t cp) {
if (cp < 0x80) {
out += static_cast<char>(cp);
return 1;
}
if (cp < 0x800) {
out += static_cast<char>(0xC0 | (cp >> 6));
out += static_cast<char>(0x80 | (cp & 0x3F));
return 2;
}
if (cp < 0x10000) {
out += static_cast<char>(0xE0 | (cp >> 12));
out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
out += static_cast<char>(0x80 | (cp & 0x3F));
return 3;
}
if (cp < 0x200000) {
out += static_cast<char>(0xF0 | (cp >> 18));
out += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
out += static_cast<char>(0x80 | (cp & 0x3F));
return 4;
}
return 0;
}
// Append cp to out as UTF-16. Returns code units written, or 0 if cp is
// a surrogate or > max_codepoint.
template<typename CharT>
inline std::size_t append_utf16(std::basic_string<CharT> &out, std::uint32_t cp) {
if (is_surrogate(cp) || cp > max_codepoint) {
return 0;
}
if (cp < 0x10000) {
out += static_cast<CharT>(cp);
return 1;
}
const std::uint32_t v = cp - 0x10000;
out += static_cast<CharT>(0xD800 | (v >> 10));
out += static_cast<CharT>(0xDC00 | (v & 0x3FF));
return 2;
}
// Append cp to a basic_string<CharT>. Dispatches on sizeof(CharT):
// 1 byte -> UTF-8, 2 bytes -> UTF-16, 4 bytes -> UTF-32.
// Returns code units written, or 0 if the codepoint is invalid.
template<typename CharT>
inline std::size_t append_codepoint(std::basic_string<CharT> &out, std::uint32_t cp) {
if constexpr (sizeof(CharT) == 1) {
std::string tmp;
const auto n = append_utf8(tmp, cp);
out.append(tmp.begin(), tmp.end());
return n;
} else if constexpr (sizeof(CharT) == 2) {
return append_utf16(out, cp);
} else {
static_assert(sizeof(CharT) == 4, "append_codepoint: unsupported CharT size");
if (is_surrogate(cp) || cp > max_codepoint) {
return 0;
}
out += static_cast<CharT>(cp);
return 1;
}
}
} // namespace unicode
} // namespace utility
} // namespace chaiscript
#endif

View File

@ -91,16 +91,6 @@ the doxygen documentation in the build folder or see the website
http://www.chaiscript.com. 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: The shortest complete example possible follows:
```C++ ```C++

View File

@ -4,7 +4,6 @@ Current Version: 6.1.1
### Changes since 6.1.0 ### 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 * 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 * Update CMake to use `LIBDIR` instead of `lib` #502 by @guoyunhe
* Add documentation for installing ChaiScript with vcpkg #500 by @grdowns * Add documentation for installing ChaiScript with vcpkg #500 by @grdowns

View File

@ -30,8 +30,9 @@ char *mystrdup(const char *s) {
#ifdef CHAISCRIPT_MSVC #ifdef CHAISCRIPT_MSVC
strcpy_s(d, len + 1, s); // Copy the characters strcpy_s(d, len + 1, s); // Copy the characters
#else #else
strncpy(d, s, len + 1); // Copy the characters strncpy(d, s, len); // Copy the characters
#endif #endif
d[len] = '\0';
return d; // Return the new string return d; // Return the new string
} }
@ -296,7 +297,7 @@ int main(int argc, char *argv[]) {
++i; ++i;
} }
std::string arg(i ? *std::next(argv, i) : "--interactive"); std::string arg(i ? argv[i] : "--interactive");
enum { enum {
eInteractive, eInteractive,
@ -310,7 +311,7 @@ int main(int argc, char *argv[]) {
std::cout << "insufficient input following " << arg << std::endl; std::cout << "insufficient input following " << arg << std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} else { } else {
arg = *std::next(argv, ++i); arg = argv[++i];
} }
} else if (arg == "-" || arg == "--stdin") { } else if (arg == "-" || arg == "--stdin") {
arg = ""; arg = "";

View File

@ -35,8 +35,9 @@ char *mystrdup(const char *s) {
#ifdef CHAISCRIPT_MSVC #ifdef CHAISCRIPT_MSVC
strcpy_s(d, len + 1, s); // Copy the characters strcpy_s(d, len + 1, s); // Copy the characters
#else #else
strncpy(d, s, len + 1); // Copy the characters strncpy(d, s, len); // Copy the characters
#endif #endif
d[len] = '\0';
return d; // Return the new string return d; // Return the new string
} }

View File

@ -8,7 +8,6 @@
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <chrono> #include <chrono>
#include <cstddef>
#include <iostream> #include <iostream>
#include <list> #include <list>
#include <regex> #include <regex>
@ -35,8 +34,9 @@ char *mystrdup(const char *s) {
#ifdef CHAISCRIPT_MSVC #ifdef CHAISCRIPT_MSVC
strcpy_s(d, len + 1, s); // Copy the characters strcpy_s(d, len + 1, s); // Copy the characters
#else #else
strncpy(d, s, len + 1); // Copy the characters strncpy(d, s, len); // Copy the characters
#endif #endif
d[len] = '\0';
return d; // Return the new string return d; // Return the new string
} }
@ -283,7 +283,7 @@ int main(int argc, char *argv[]) {
++i; ++i;
} }
std::string arg(i != 0 ? *std::next(argv, i) : "--interactive"); std::string arg(i != 0 ? argv[i] : "--interactive");
enum { enum {
eInteractive, eInteractive,
@ -297,7 +297,7 @@ int main(int argc, char *argv[]) {
std::cout << "insufficient input following " << arg << '\n'; std::cout << "insufficient input following " << arg << '\n';
return EXIT_FAILURE; return EXIT_FAILURE;
} }
arg = *std::next(argv, ++i); arg = argv[++i];
} else if (arg == "-" || arg == "--stdin") { } else if (arg == "-" || arg == "--stdin") {
arg = ""; arg = "";

View File

@ -19,6 +19,7 @@ typedef unsigned __int64 uint64_t;
#include <stdint.h> #include <stdint.h>
#endif #endif
/// compute SHA3 hash /// compute SHA3 hash
/** Usage: /** Usage:
SHA3 sha3; SHA3 sha3;
@ -36,10 +37,7 @@ class SHA3 //: public Hash
{ {
public: public:
/// algorithm variants /// algorithm variants
enum Bits { Bits224 = 224, enum Bits { Bits224 = 224, Bits256 = 256, Bits384 = 384, Bits512 = 512 };
Bits256 = 256,
Bits384 = 384,
Bits512 = 512 };
/// same as reset() /// same as reset()
explicit SHA3(Bits bits = Bits256); explicit SHA3(Bits bits = Bits256);

View File

@ -1,41 +0,0 @@
// Issue #635: optimized for loop with := must not produce dangling values
// := is reference-assign, so ret aliases i and sees the loop exit value
// Basic: capture loop variable via :=
var ret = 0
for (var i = 0; i < 100; ++i) {
ret := i
}
assert_equal(100, ret)
// Return from function containing optimized for loop
def loop_result() {
var ret = 0
for (var i = 0; i < 200; ++i) {
ret := i
}
return ret
}
assert_equal(200, loop_result())
// Multiple calls return consistent results
assert_equal(loop_result(), loop_result())
// Nested optimized for loops
var outer_val = 0
var inner_val = 0
for (var i = 0; i < 10; ++i) {
for (var j = 0; j < 10; ++j) {
inner_val := j
}
outer_val := i
}
assert_equal(10, outer_val)
assert_equal(10, inner_val)
// Value-assign (=) captures last body-iteration value, not exit value
var ret2 = 0
for (var i = 0; i < 100; ++i) {
ret2 = i
}
assert_equal(99, ret2)

View File

@ -1,17 +0,0 @@
// Regression test for #632 and #636: Heap-use-after-free in async threads
// Async threads must complete before the engine is destroyed.
var func = fun(){
var ret = 0;
for (var i = 0; i < 1000; ++i) {
ret += i;
}
return ret;
}
var fut1 = async(func);
var fut2 = async(func);
// Wait for results to verify correctness
assert_equal(fut1.get(), 499500);
assert_equal(fut2.get(), 499500);

View File

@ -1,46 +0,0 @@
// Regression test for #632 and #636:
// Heap-use-after-free when async threads outlive the ChaiScript engine.
// The engine must join all outstanding async threads before destroying shared state.
#include <chaiscript/chaiscript.hpp>
int main() {
// Test 1: Async threads still running when engine is destroyed.
// Without the fix, this triggers heap-use-after-free under ASAN/TSAN.
for (int iter = 0; iter < 3; ++iter) {
{
chaiscript::ChaiScript chai;
try {
chai.eval(R"(
var func = fun(){
var ret = 0;
for (var i = 0; i < 5000; ++i) {
ret += i;
}
return ret;
}
var fut1 = async(func);
var fut2 = async(func);
)");
// Engine destroyed here without waiting for futures.
} catch (const std::exception &) {
// Exceptions are fine
}
}
}
// Test 2: Verify async still works correctly (results are accessible).
{
chaiscript::ChaiScript chai;
auto result = chai.eval<int>(R"(
var f = async(fun() { return 42; });
f.get();
)");
if (result != 42) {
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}

View File

@ -1,25 +0,0 @@
// Issue #635: async + optimized for loop must not segfault from dangling pointer
// := is reference-assign, so ret aliases i and sees the loop exit value
var func = fun(){
var ret = 0;
for (var i = 0; i < 1000; ++i) {
ret := i;
}
return ret;
}
var&fut1 = async(func);
var fut2 = async(func);
assert_equal(1000, fut1.get())
assert_equal(1000, fut2.get())
// Multiple concurrent async calls to stress the scenario
var results = []
for (var n = 0; n < 5; ++n) {
results.push_back(async(func))
}
for (var n = 0; n < 5; ++n) {
assert_equal(1000, results[n].get())
}

17969
unittests/catch.hpp Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
// Test that all conversion functions work across char, int, and string types
// char() constructor
assert_equal('A', char('A')) // char from char (identity via Boxed_Number)
assert_equal('A', char(65)) // char from int (via Boxed_Number)
assert_equal('A', char("A")) // char from string (new)
// to_char() conversions
assert_equal('A', to_char('A')) // to_char from char (identity)
assert_equal('A', to_char(65)) // to_char from int (new, via Boxed_Number)
assert_equal('A', to_char("A")) // to_char from string (existing)
// int() constructor
assert_equal(65, int('A')) // int from char (via Boxed_Number)
assert_equal(65, int(65)) // int from int (identity via Boxed_Number)
assert_equal(65, int("65")) // int from string (new)
// to_int() conversions
assert_equal(65, to_int('A')) // to_int from char (new, via Boxed_Number)
assert_equal(65, to_int(65)) // to_int from int (identity)
assert_equal(65, to_int("65")) // to_int from string (existing)
// to_string() conversions
assert_equal("A", to_string('A'))
assert_equal("65", to_string(65))
assert_equal("A", to_string("A"))
// double conversions
assert_equal(3.5, double("3.5")) // double from string (new)
assert_equal(3.5, to_double("3.5")) // to_double from string (existing)
assert_equal(65.0, to_double('A')) // to_double from char (new, via Boxed_Number)
assert_equal(65.0, to_double(65)) // to_double from int (new, via Boxed_Number)
assert_equal("3.5", to_string(3.5)) // to_string from double (existing)

View File

@ -9,64 +9,43 @@
#define CHAISCRIPT_NO_DYNLOAD #define CHAISCRIPT_NO_DYNLOAD
#endif #endif
#include <chaiscript/chaiscript.hpp>
#include "../emscripten/chaiscript_eval.hpp" #include "../emscripten/chaiscript_eval.hpp"
#include <cassert> #include <cassert>
#include <chaiscript/chaiscript.hpp>
#include <cmath> #include <cmath>
#include <string> #include <string>
int main() { int main() {
const int chai = chaiscript_create();
assert(chai > 0 && "create must return a positive handle");
// Test eval (void return) - same as Emscripten eval() // Test eval (void return) - same as Emscripten eval()
chaiscript_eval(chai, "var x = 42"); chaiscript_eval("var x = 42");
// Test evalString - same as Emscripten evalString() // Test evalString - same as Emscripten evalString()
[[maybe_unused]] const std::string s = chaiscript_eval_string(chai, "to_string(x)"); std::string s = chaiscript_eval_string("to_string(x)");
assert(s == "42"); assert(s == "42");
// Test evalInt - same as Emscripten evalInt() // Test evalInt - same as Emscripten evalInt()
[[maybe_unused]] const int i = chaiscript_eval_int(chai, "1 + 2"); int i = chaiscript_eval_int("1 + 2");
assert(i == 3); assert(i == 3);
// Test evalBool - same as Emscripten evalBool() // Test evalBool - same as Emscripten evalBool()
[[maybe_unused]] bool b = chaiscript_eval_bool(chai, "true"); bool b = chaiscript_eval_bool("true");
assert(b == true); assert(b == true);
b = chaiscript_eval_bool(chai, "false"); b = chaiscript_eval_bool("false");
assert(b == false); assert(b == false);
// Test evalFloat - same as Emscripten evalFloat() // Test evalFloat - same as Emscripten evalFloat()
[[maybe_unused]] const float f = chaiscript_eval_float(chai, "1.5f"); float f = chaiscript_eval_float("1.5f");
assert(std::abs(f - 1.5f) < 0.001f); assert(std::abs(f - 1.5f) < 0.001f);
// Test evalDouble - same as Emscripten evalDouble() // Test evalDouble - same as Emscripten evalDouble()
[[maybe_unused]] const double d = chaiscript_eval_double(chai, "3.14"); double d = chaiscript_eval_double("3.14");
assert(std::abs(d - 3.14) < 0.001); assert(std::abs(d - 3.14) < 0.001);
// Test a more complex expression // Test a more complex expression
chaiscript_eval(chai, "def square(n) { return n * n; }"); chaiscript_eval("def square(n) { return n * n; }");
[[maybe_unused]] const int sq = chaiscript_eval_int(chai, "square(7)"); int sq = chaiscript_eval_int("square(7)");
assert(sq == 49); assert(sq == 49);
// A second engine is fully independent of the first.
const int chai2 = chaiscript_create();
assert(chai2 != chai && "create must mint a fresh handle each call");
[[maybe_unused]] bool caught = false;
try {
chaiscript_eval(chai2, "x");
} catch (const chaiscript::exception::eval_error &) {
caught = true;
}
assert(caught && "engines must not share globals");
chaiscript_destroy(chai2);
chaiscript_destroy(chai);
// Destroying an unknown handle is a no-op.
chaiscript_destroy(chai);
chaiscript_destroy(99999);
return 0; return 0;
} }

View File

@ -1,77 +0,0 @@
// 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 "../emscripten/chaiscript_eval.hpp"
#include <cassert>
#include <chaiscript/chaiscript.hpp>
#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.
const int chai = chaiscript_create();
[[maybe_unused]] bool caught = false;
// Test 1: eval with undefined variable should throw
caught = false;
try {
chaiscript_eval(chai, "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(chai, "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(chai, "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(chai, "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(chai, "var post_exception_test = 100");
[[maybe_unused]] const int result = chaiscript_eval_int(chai, "post_exception_test");
assert(result == 100 && "normal eval must work after caught exceptions");
chaiscript_destroy(chai);
std::cout << "All emscripten exception tests passed.\n";
return 0;
}

View File

@ -1,76 +0,0 @@
// Test that validates the save/restore/release state helpers used by the
// Emscripten wrapper. A handle-based registry is exposed so the JS playground
// can capture a pristine engine once and restore it between runs without
// reloading the WASM module.
#ifndef CHAISCRIPT_NO_THREADS
#define CHAISCRIPT_NO_THREADS
#endif
#ifndef CHAISCRIPT_NO_DYNLOAD
#define CHAISCRIPT_NO_DYNLOAD
#endif
#include "../emscripten/chaiscript_eval.hpp"
#include <cassert>
#include <chaiscript/chaiscript.hpp>
#include <string>
int main() {
const int chai = chaiscript_create();
assert(chai > 0 && "create must return a positive handle");
// Capture a baseline before any user code has been evaluated.
const int baseline = chaiscript_save_state(chai);
assert(baseline > 0 && "saveState must return a positive handle");
// Distinct calls produce distinct handles.
const int second = chaiscript_save_state(chai);
assert(second != baseline && "saveState must mint a fresh handle each call");
chaiscript_release_state(second);
// Define a global that did not exist in the baseline.
chaiscript_eval(chai, "var post_baseline_marker = 123");
assert(chaiscript_eval_int(chai, "post_baseline_marker") == 123);
// Restoring the baseline must drop the post-baseline definition: evaluating
// it should now fail with eval_error because the variable is undefined.
chaiscript_restore_state(chai, baseline);
[[maybe_unused]] bool caught = false;
try {
chaiscript_eval(chai, "post_baseline_marker");
} catch (const chaiscript::exception::eval_error &) {
caught = true;
}
assert(caught && "restoreState must drop globals defined after the snapshot");
// The engine remains usable after a restore.
chaiscript_eval(chai, "var after_restore = 7");
assert(chaiscript_eval_int(chai, "after_restore * 6") == 42);
// Releasing the handle is required so the registry does not grow unbounded.
chaiscript_release_state(baseline);
// Releasing a handle that was never minted (or has already been released) is
// a no-op rather than an error.
chaiscript_release_state(baseline);
chaiscript_release_state(99999);
// Restoring an unknown handle is a no-op: state must be unchanged.
chaiscript_restore_state(chai, 99999);
assert(chaiscript_eval_int(chai, "after_restore") == 7);
// A snapshot taken on one engine can be restored onto a different engine,
// confirming that state is decoupled from any particular instance.
const int snapshot_with_after_restore = chaiscript_save_state(chai);
const int chai2 = chaiscript_create();
chaiscript_restore_state(chai2, snapshot_with_after_restore);
assert(chaiscript_eval_int(chai2, "after_restore") == 7);
chaiscript_release_state(snapshot_with_after_restore);
chaiscript_destroy(chai2);
chaiscript_destroy(chai);
return 0;
}

View File

@ -1,110 +0,0 @@
// 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)

View File

@ -1,862 +0,0 @@
// Comprehensive exception throwing and catching tests
// Tests throw/catch from various contexts: operators, functions, lambdas,
// methods, [] operator, nested scopes, and with various value types.
// ============================================================
// Section 1: Throwing and catching different value types
// ============================================================
// Throw int
try {
throw(42)
}
catch(e) {
assert_equal(42, e)
}
// Throw string
try {
throw("error message")
}
catch(e) {
assert_equal("error message", e)
}
// Throw double
try {
throw(3.14)
}
catch(e) {
assert_equal(3.14, e)
}
// Throw bool
try {
throw(true)
}
catch(e) {
assert_equal(true, e)
}
// Throw a Vector (via named variable to preserve mutability)
auto thrown_vec = [1, 2, 3]
try {
throw(thrown_vec)
}
catch(e) {
assert_equal(3, e.size())
assert_equal(1, e[0])
assert_equal(2, e[1])
assert_equal(3, e[2])
}
// Throw a Map (via named variable to preserve mutability)
auto thrown_map = ["key": 42]
try {
throw(thrown_map)
}
catch(e) {
assert_equal(42, e["key"])
}
// ============================================================
// Section 2: Typed catch blocks
// ============================================================
// Typed catch matching int
auto typed_result = 0
try {
throw(10)
}
catch(int e) {
typed_result = e + 1
}
assert_equal(11, typed_result)
// Typed catch matching string
typed_result = 0
try {
throw("hello")
}
catch(string e) {
typed_result = 1
}
assert_equal(1, typed_result)
// Typed catch mismatch falls through to untyped
typed_result = 0
try {
throw(42)
}
catch(string e) {
typed_result = 1
}
catch(e) {
typed_result = 2
}
assert_equal(2, typed_result)
// Multiple typed catches - first match wins
typed_result = 0
try {
throw("test")
}
catch(int e) {
typed_result = 1
}
catch(string e) {
typed_result = 2
}
catch(e) {
typed_result = 3
}
assert_equal(2, typed_result)
// Typed catch with int, multiple typed blocks, none match except untyped
typed_result = 0
try {
throw(3.14)
}
catch(int e) {
typed_result = 1
}
catch(string e) {
typed_result = 2
}
catch(e) {
typed_result = 3
}
assert_equal(3, typed_result)
// ============================================================
// Section 3: Catch-all (no variable) catch block
// ============================================================
auto catch_all_reached = false
try {
throw(99)
}
catch {
catch_all_reached = true
}
assert_true(catch_all_reached)
// ============================================================
// Section 4: Finally block semantics
// ============================================================
// Finally runs after exception
auto finally_ran = false
try {
throw(1)
}
catch(e) {
// caught
}
finally {
finally_ran = true
}
assert_true(finally_ran)
// Finally runs without exception
finally_ran = false
try {
auto x = 1
}
catch(e) {
// not reached
}
finally {
finally_ran = true
}
assert_true(finally_ran)
// Finally runs even with typed catch that matches
finally_ran = false
try {
throw(42)
}
catch(int e) {
assert_equal(42, e)
}
finally {
finally_ran = true
}
assert_true(finally_ran)
// Finally runs when typed catch does NOT match (exception not caught)
finally_ran = false
auto outer_caught = false
try {
try {
throw(42)
}
catch(string e) {
// wrong type, won't match
}
finally {
finally_ran = true
}
}
catch(e) {
outer_caught = true
}
assert_true(finally_ran)
assert_true(outer_caught)
// ============================================================
// Section 5: Throwing from functions
// ============================================================
def throwing_function() {
throw("from function")
}
try {
throwing_function()
}
catch(e) {
assert_equal("from function", e)
}
// Throwing from nested function calls
def inner_throw() {
throw("inner")
}
def outer_call() {
inner_throw()
}
try {
outer_call()
}
catch(e) {
assert_equal("inner", e)
}
// Function that throws conditionally
def conditional_throw(should_throw) {
if (should_throw) {
throw("conditional")
}
return "no throw"
}
assert_equal("no throw", conditional_throw(false))
try {
conditional_throw(true)
}
catch(e) {
assert_equal("conditional", e)
}
// ============================================================
// Section 6: Throwing from lambdas
// ============================================================
auto throwing_lambda = fun() { throw("from lambda") }
try {
throwing_lambda()
}
catch(e) {
assert_equal("from lambda", e)
}
// Lambda that captures and throws
auto captured_val = "captured"
auto capture_lambda = fun[captured_val]() { throw(captured_val) }
try {
capture_lambda()
}
catch(e) {
assert_equal("captured", e)
}
// ============================================================
// Section 7: Throwing from binary operators
// ============================================================
// Define a type and an operator that throws
attr ThrowOnAdd::val
def ThrowOnAdd::ThrowOnAdd(v) { this.val = v }
def `+`(ThrowOnAdd x, ThrowOnAdd y) {
throw("add not supported")
}
try {
auto a = ThrowOnAdd(1)
auto b = ThrowOnAdd(2)
auto c = a + b
assert_true(false) // should not reach here
}
catch(e) {
assert_equal("add not supported", e)
}
// Operator that throws for specific values
def `-`(ThrowOnAdd x, ThrowOnAdd y) {
if (x.val == y.val) {
throw("cannot subtract equal values")
}
return ThrowOnAdd(x.val - y.val)
}
try {
auto a = ThrowOnAdd(5)
auto b = ThrowOnAdd(5)
auto c = a - b
assert_true(false)
}
catch(e) {
assert_equal("cannot subtract equal values", e)
}
// Multiplication operator that throws (not pre-defined for Dynamic_Object)
def `*`(ThrowOnAdd x, ThrowOnAdd y) {
throw("multiply not supported")
}
try {
auto a = ThrowOnAdd(1)
auto b = ThrowOnAdd(2)
auto c = a * b
assert_true(false)
}
catch(e) {
assert_equal("multiply not supported", e)
}
// Subtraction works for non-equal values
auto sub_result = ThrowOnAdd(10) - ThrowOnAdd(3)
assert_equal(7, sub_result.val)
// ============================================================
// Section 8: Throwing from unary/prefix operators
// ============================================================
def `++`(ThrowOnAdd x) {
throw("increment not supported")
}
try {
auto a = ThrowOnAdd(1)
++a
assert_true(false)
}
catch(e) {
assert_equal("increment not supported", e)
}
// ============================================================
// Section 9: Throwing from [] operator
// ============================================================
attr ThrowOnIndex::data
def ThrowOnIndex::ThrowOnIndex() { this.data = [1, 2, 3] }
def `[]`(ThrowOnIndex obj, int idx) {
if (idx < 0) {
throw("negative index not allowed")
}
return obj.data[idx]
}
auto toi = ThrowOnIndex()
assert_equal(1, toi[0])
assert_equal(2, toi[1])
try {
auto val = toi[-1]
assert_true(false)
}
catch(e) {
assert_equal("negative index not allowed", e)
}
// ============================================================
// Section 10: Throwing from member functions
// ============================================================
attr Validatable::value
def Validatable::Validatable(v) { this.value = v }
def Validatable::validate() {
if (this.value < 0) {
throw("validation failed: negative value")
}
return true
}
auto valid_obj = Validatable(10)
assert_true(valid_obj.validate())
auto invalid_obj = Validatable(-1)
try {
invalid_obj.validate()
assert_true(false)
}
catch(e) {
assert_equal("validation failed: negative value", e)
}
// ============================================================
// Section 11: Nested try/catch
// ============================================================
auto inner_caught = false
auto outer_caught_val = 0
try {
try {
throw(1)
}
catch(e) {
inner_caught = true
throw(e + 10)
}
}
catch(e) {
outer_caught_val = e
}
assert_true(inner_caught)
assert_equal(11, outer_caught_val)
// Deeply nested try/catch
auto depth = 0
try {
try {
try {
throw("deep")
}
catch(e) {
depth = 1
throw(e + "er")
}
}
catch(e) {
depth = 2
throw(e + "est")
}
}
catch(e) {
depth = 3
assert_equal("deeperest", e)
}
assert_equal(3, depth)
// ============================================================
// Section 12: Rethrow from catch block
// ============================================================
auto rethrow_caught = false
try {
try {
throw("rethrown")
}
catch(e) {
throw(e)
}
}
catch(e) {
rethrow_caught = true
assert_equal("rethrown", e)
}
assert_true(rethrow_caught)
// ============================================================
// Section 13: Exception in for loop
// ============================================================
auto loop_exception_val = 0
try {
for (auto i = 0; i < 10; ++i) {
if (i == 5) {
throw(i)
}
}
}
catch(e) {
loop_exception_val = e
}
assert_equal(5, loop_exception_val)
// ============================================================
// Section 14: Exception in while loop
// ============================================================
auto while_exc_val = 0
auto counter = 0
try {
while (true) {
++counter
if (counter == 3) {
throw(counter)
}
}
}
catch(e) {
while_exc_val = e
}
assert_equal(3, while_exc_val)
// ============================================================
// Section 15: Exception preserves value through nested calls
// ============================================================
def deep_throw(val) {
throw(val)
}
def middle_call(val) {
deep_throw(val)
}
def top_call(val) {
middle_call(val)
}
auto nested_map = ["key": "value"]
try {
top_call(nested_map)
}
catch(e) {
assert_equal("value", e["key"])
}
auto nested_vec = [10, 20, 30]
try {
top_call(nested_vec)
}
catch(e) {
assert_equal(3, e.size())
assert_equal(20, e[1])
}
// ============================================================
// Section 16: Code after throw is not executed
// ============================================================
auto after_throw = false
try {
throw(1)
after_throw = true
}
catch(e) {
// caught
}
assert_false(after_throw)
// ============================================================
// Section 17: Exception value is usable in catch block arithmetic
// ============================================================
auto catch_computed = 0
try {
throw(1)
}
catch(e) {
catch_computed = e + 100
}
assert_equal(101, catch_computed)
// ============================================================
// Section 18: No exception means catch is skipped
// ============================================================
auto catch_skipped = true
try {
auto x = 42
}
catch(e) {
catch_skipped = false
}
assert_true(catch_skipped)
// ============================================================
// Section 19: Exception from dynamic object method chaining
// ============================================================
attr Chain::val
def Chain::Chain(v) { this.val = v }
def Chain::add(n) {
if (this.val + n > 100) {
throw("overflow: " + to_string(this.val + n))
}
this.val = this.val + n
return this
}
auto chain = Chain(50)
try {
chain.add(30).add(30)
assert_true(false)
}
catch(e) {
assert_equal("overflow: 110", e)
}
assert_equal(80, chain.val)
// ============================================================
// Section 20: Exception thrown during map construction
// ============================================================
def exploding_value() {
throw("boom during construction")
}
try {
auto m = ["ok": 1, "bad": exploding_value()]
assert_true(false)
}
catch(e) {
assert_equal("boom during construction", e)
}
// ============================================================
// Section 21: Exception thrown during vector construction
// ============================================================
try {
auto v = [1, 2, exploding_value(), 4]
assert_true(false)
}
catch(e) {
assert_equal("boom during construction", e)
}
// ============================================================
// Section 22: Exception in if-condition
// ============================================================
def exploding_condition() {
throw("condition exploded")
}
try {
if (exploding_condition()) {
assert_true(false)
}
}
catch(e) {
assert_equal("condition exploded", e)
}
// ============================================================
// Section 23: Multiple catch blocks - only first matching runs
// ============================================================
auto catch_count = 0
try {
throw(42)
}
catch(int e) {
++catch_count
}
catch(e) {
++catch_count
}
assert_equal(1, catch_count)
// ============================================================
// Section 24: Throwing from within catch, with finally
// ============================================================
auto s24_finally = false
auto s24_outer = false
try {
try {
throw("original")
}
catch(e) {
throw("replaced: " + e)
}
finally {
s24_finally = true
}
}
catch(e) {
s24_outer = true
assert_equal("replaced: original", e)
}
assert_true(s24_finally)
assert_true(s24_outer)
// ============================================================
// Section 25: Unhandled typed catch propagates exception
// ============================================================
auto s25_caught = false
try {
try {
throw(3.14)
}
catch(int e) {
assert_true(false) // should not match double
}
catch(string e) {
assert_true(false) // should not match double
}
}
catch(e) {
s25_caught = true
assert_equal(3.14, e)
}
assert_true(s25_caught)
// ============================================================
// Section 26: Throw from range-based for
// ============================================================
auto s26_val = 0
try {
for (x : [10, 20, 30, 40]) {
if (x == 30) {
throw(x)
}
}
}
catch(e) {
s26_val = e
}
assert_equal(30, s26_val)
// ============================================================
// Section 27: Throw from eval
// ============================================================
try {
eval("throw(\"from eval\")")
}
catch(e) {
assert_equal("from eval", e)
}
// ============================================================
// Section 28: Exception from built-in operations (out of range)
// ============================================================
auto s28_caught = false
try {
auto v = [1, 2, 3]
auto x = v[10]
}
catch(e) {
s28_caught = true
}
assert_true(s28_caught)
// ============================================================
// Section 29: Throw zero and empty string (falsy values)
// ============================================================
try {
throw(0)
}
catch(e) {
assert_equal(0, e)
}
try {
throw("")
}
catch(e) {
assert_equal("", e)
}
// ============================================================
// Section 30: Throw from ternary-style inline_if
// ============================================================
def maybe_throw(do_it) {
if (do_it) { throw("inline threw") } else { "ok" }
}
try {
maybe_throw(true)
}
catch(e) {
assert_equal("inline threw", e)
}
assert_equal("ok", maybe_throw(false))
// ============================================================
// Section 31: Verify catch variable scope isolation
// ============================================================
auto outer_e = "untouched"
try {
throw("caught_value")
}
catch(e) {
assert_equal("caught_value", e)
}
assert_equal("untouched", outer_e)
// ============================================================
// Section 32: Exception from recursive function
// ============================================================
def recursive_throw(n) {
if (n == 0) {
throw("bottom")
}
recursive_throw(n - 1)
}
try {
recursive_throw(5)
}
catch(e) {
assert_equal("bottom", e)
}
// ============================================================
// Section 33: Try/catch in a function body
// ============================================================
def safe_divide(a, b) {
try {
if (b == 0) {
throw("division by zero")
}
return a / b
}
catch(e) {
return e
}
}
assert_equal(5, safe_divide(10, 2))
assert_equal("division by zero", safe_divide(10, 0))
// ============================================================
// Section 34: Throw from [] on a Map with missing key
// ============================================================
auto s34_caught = false
try {
auto m = ["a": 1]
auto x = m["nonexistent"]
}
catch(e) {
s34_caught = true
}
assert_true(s34_caught)
// ============================================================
// Section 35: Arithmetic exception (divide by zero)
// ============================================================
auto s35_caught = false
try {
auto x = 1 / 0
}
catch(e) {
s35_caught = true
}
assert_true(s35_caught)

View File

@ -39,15 +39,14 @@ assert_equal(true, test_fun_types[1].bare_equal(int_type));
assert_equal(2, `==`.get_arity()); assert_equal(2, `==`.get_arity());
// < should be the merging of three functions bool <(PODObject, PODObject), // < should be the merging of two functions bool <(PODObject, PODObject) and bool <(string, string)
// bool <(string, string), and bool <(string_view, string_view)
// we want to peel it apart and make sure that's true // we want to peel it apart and make sure that's true
auto types = `<`.get_param_types(); auto types = `<`.get_param_types();
assert_equal(3, types.size()); assert_equal(3, types.size());
assert_equal(true, types[0].bare_equal(bool_type)); assert_equal(true, types[0].bare_equal(bool_type));
assert_equal(true, types[1].bare_equal(Object_type)); assert_equal(true, types[1].bare_equal(Object_type));
assert_equal(true, types[2].bare_equal(Object_type)); assert_equal(true, types[2].bare_equal(Object_type));
assert_equal(3, `<`.get_contained_functions().size()); assert_equal(2, `<`.get_contained_functions().size());
// guard existence tests // guard existence tests

View File

@ -1,219 +0,0 @@
// 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)

View File

@ -1,45 +0,0 @@
// Test overriding [] operator with various index types (issue #398)
class my_class
{
def my_class()
{
}
};
def `[]`(my_class o, idx)
{
return "Hello World!";
}
var o = my_class();
// Integer index should work
assert_equal("Hello World!", o[3]);
// String index should work
assert_equal("Hello World!", o["3"]);
// String variable as index should work
var s = "abc";
assert_equal("Hello World!", o[s]);
// Typed string parameter override
class my_class2
{
def my_class2()
{
}
};
def `[]`(my_class2 o, string key)
{
return "key=" + key;
}
var o2 = my_class2();
assert_equal("key=foo", o2["foo"]);
var key = "bar";
assert_equal("key=bar", o2[key]);

View File

@ -1,15 +0,0 @@
auto m = ["k":"v"]
// find existing key returns the value
assert_equal("v", m.find("k"))
// find missing key returns undef
assert_true(m.find("missing").is_var_undef())
// find does not mutate the map
m.find("other")
assert_equal(1, m.size())
// find works with in-place map
assert_equal("v", ["k":"v"].find("k"))

View File

@ -1,29 +0,0 @@
// Regression test for issue #594
// Map keys pushed into a vector via .first should remain valid
// after the map goes out of scope.
def keys(Map map)
{
var v = Vector();
for( i : map )
{
v.push_back(i.first);
}
return v;
}
var k = Vector();
if ( true )
{
var m = ["a":"x", "b":"y", "c":"z"];
k = keys(m);
}
// After the map is out of scope, the keys should still be valid strings
assert_equal(3, k.size())
// Verify each element is a non-empty string
for (elem : k)
{
assert_true(elem.size() > 0)
}

View File

@ -1,16 +0,0 @@
// modulo by zero should throw, not crash
try {
3 % 0
assert_true(false)
} catch (e) {
assert_equal("Arithmetic error: divide by zero", e.what())
}
// assign-modulo by zero should also throw
var x = 3;
try {
x %= 0
assert_true(false)
} catch (e) {
assert_equal("Arithmetic error: divide by zero", e.what())
}

View File

@ -1,55 +0,0 @@
// 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)

View File

@ -1,6 +0,0 @@
// object_from_json: returns Dynamic_Object with dot-access on JSON fields
var obj = object_from_json("{\"name\":\"ChaiScript\",\"version\":6,\"active\":true}")
assert_equal(obj.name, "ChaiScript")
assert_equal(obj.version, 6)
assert_equal(obj.active, true)
assert_equal(obj.get_type_name(), "JSON_Object")

View File

@ -1,3 +0,0 @@
// object_from_json: nested objects become nested Dynamic_Objects
var obj = object_from_json("{\"outer\":{\"inner\":42}}")
assert_equal(obj.outer.inner, 42)

View File

@ -1,3 +0,0 @@
// object_from_json: arrays remain as vectors
var obj = object_from_json("{\"items\":[1,2,3]}")
assert_equal(obj.items, [1,2,3])

View File

@ -1,9 +0,0 @@
// object_to_map and map_to_object conversions
var m = ["a": 1, "b": "hello"]
var obj = map_to_object(m)
assert_equal(obj.a, 1)
assert_equal(obj.b, "hello")
var m2 = object_to_map(obj)
assert_equal(m2["a"], 1)
assert_equal(m2["b"], "hello")

View File

@ -1,6 +0,0 @@
// object_from_json roundtrip through to_json
var json_str = "{\"key\":\"value\"}"
var obj = object_from_json(json_str)
var result = to_json(obj)
var obj2 = object_from_json(result)
assert_equal(obj2.key, "value")

View File

@ -1,41 +0,0 @@
// Regression test for issue #633: Stack-overflow due to infinite recursion
// in user-defined operator (string interpolation).
//
// Before the fix the recursive `/=` invocation triggered unbounded native
// recursion in the evaluator and crashed the host process with a SIGSEGV.
// The engine now bounds call_depth and throws a catchable exception
// instead of letting the native stack overflow.
def string::`/=`(double d) {
this = "${this/= 2}/=${d}";
return this;
}
var s = "o World"
var caught = false
var message = ""
try {
s /= 2
// unreachable: the recursive operator must abort with an exception
assert_true(false)
} catch (e) {
caught = true
message = e.what()
}
assert_true(caught)
// The reported error must mention the call-stack overflow so users can
// distinguish it from an arbitrary script-level error.
assert_true(find(message, "call stack") != -1)
// A bounded recursion that stays well below the limit must keep working.
// Use a small depth so the test passes on every platform default - notably
// the MSVC Debug build where the native stack budget forces a tighter cap.
def count_down(n) {
if (n <= 0) { return 0 }
return count_down(n - 1) + 1
}
assert_equal(10, count_down(10))

View File

@ -1,16 +0,0 @@
// Test that assignment expressions work inside return statements
// Issue #473: `return foo = 5` should work
def return_assign() {
var x = 0
return x = 5
}
assert_equal(5, return_assign())
def return_member_assign() {
var o = Dynamic_Object()
return o.value = 42
}
assert_equal(42, return_member_assign())

View File

@ -1,200 +0,0 @@
// 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)

View File

@ -1,26 +0,0 @@
#include <chaiscript/chaiscript.hpp>
int main() {
#ifndef CHAISCRIPT_NO_THREADS
// When threading is enabled, verify that async() is registered and functional
chaiscript::ChaiScript chai;
const auto result = chai.eval<int>(R"(
var f = async(fun() { return 42; });
f.get();
)");
if (result != 42) {
return EXIT_FAILURE;
}
#else
// When threading is disabled, verify that async() is NOT registered
chaiscript::ChaiScript chai;
try {
chai.eval("async(fun() { return 0; })");
return EXIT_FAILURE;
} catch (const std::exception &) {
// Expected: async should not exist
}
#endif
return EXIT_SUCCESS;
}

View File

@ -16,7 +16,9 @@
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include "catch_amalgamated.hpp" #define CATCH_CONFIG_MAIN
#include "catch.hpp"
TEST_CASE("Type_Info objects generate expected results") { TEST_CASE("Type_Info objects generate expected results") {
const auto test_type const auto test_type