mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-07-31 08:46:30 +08:00
Compare commits
No commits in common. "develop" and "wasm-latest" have entirely different histories.
develop
...
wasm-lates
@ -1,14 +1,10 @@
|
||||
# clang-format: 19
|
||||
# clang-format: 11
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveBitFields: false
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BasedOnStyle: WebKit
|
||||
BinPackArguments: true
|
||||
@ -17,14 +13,12 @@ BreakBeforeBraces: Attach
|
||||
ColumnLimit: 0
|
||||
Cpp11BracedListStyle: true
|
||||
FixNamespaceComments: true
|
||||
IfMacros: ['SECTION']
|
||||
IncludeBlocks: Preserve
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 2
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
NamespaceIndentation: All
|
||||
PackConstructorInitializers: CurrentLine
|
||||
PenaltyBreakBeforeFirstCallParameter: 200
|
||||
PenaltyBreakComment: 5
|
||||
PenaltyBreakFirstLessLess: 50
|
||||
@ -33,11 +27,7 @@ PointerAlignment: Right
|
||||
SortIncludes: true
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeParens: Custom
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterIfMacros: false
|
||||
SpaceInEmptyBlock: false
|
||||
Standard: Latest
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
||||
UseTab: Never
|
||||
27
.github/workflows/auto-clang-format.yml
vendored
27
.github/workflows/auto-clang-format.yml
vendored
@ -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 }}
|
||||
53
.github/workflows/ci.yml
vendored
53
.github/workflows/ci.yml
vendored
@ -9,47 +9,39 @@ on:
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux GCC ${{ matrix.build_type }} threads=${{ matrix.multithread }}
|
||||
name: Linux GCC ${{ matrix.build_type }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
multithread: [ON, OFF]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ninja
|
||||
run: sudo apt-get install -y ninja-build
|
||||
|
||||
- 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
|
||||
run: cmake --build build
|
||||
run: cmake --build build -j
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
macos:
|
||||
name: macOS AppleClang ${{ matrix.build_type }} threads=${{ matrix.multithread }}
|
||||
name: macOS AppleClang ${{ matrix.build_type }}
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
multithread: [ON, OFF]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ninja
|
||||
run: brew install ninja
|
||||
|
||||
- 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
|
||||
run: cmake --build build
|
||||
run: cmake --build build -j
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
@ -64,14 +56,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ninja
|
||||
run: sudo apt-get install -y ninja-build
|
||||
|
||||
- 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
|
||||
run: cmake --build build
|
||||
run: cmake --build build -j
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
@ -86,31 +75,27 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ninja
|
||||
run: brew install ninja
|
||||
|
||||
- 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
|
||||
run: cmake --build build
|
||||
run: cmake --build build -j
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
windows:
|
||||
name: Windows MSVC ${{ matrix.build_type }} threads=${{ matrix.multithread }}
|
||||
name: Windows MSVC ${{ matrix.build_type }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
multithread: [ON, OFF]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Configure
|
||||
run: cmake -B build -DMULTITHREAD_SUPPORT_ENABLED=${{ matrix.multithread }}
|
||||
run: cmake -B build
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config ${{ matrix.build_type }} -j
|
||||
@ -128,14 +113,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ninja
|
||||
run: sudo apt-get install -y ninja-build
|
||||
|
||||
- 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
|
||||
run: cmake --build build
|
||||
run: cmake --build build -j
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
@ -150,14 +132,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ninja
|
||||
run: brew install ninja
|
||||
|
||||
- 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
|
||||
run: cmake --build build
|
||||
run: cmake --build build -j
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
@ -155,7 +155,7 @@ else()
|
||||
endif()
|
||||
|
||||
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")
|
||||
# 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.
|
||||
add_definitions(/wd4503)
|
||||
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")
|
||||
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()
|
||||
add_definitions(-Wnoexcept)
|
||||
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)
|
||||
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)
|
||||
list(SORT PERFORMANCE_TESTS)
|
||||
|
||||
@ -390,16 +380,8 @@ if(BUILD_TESTING)
|
||||
)
|
||||
|
||||
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)
|
||||
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.")
|
||||
|
||||
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_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_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_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()
|
||||
|
||||
add_executable(multifile_test
|
||||
@ -448,18 +427,6 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(emscripten_eval_test ${LIBS})
|
||||
add_test(NAME Emscripten_Eval_Test COMMAND emscripten_eval_test)
|
||||
|
||||
add_executable(emscripten_exception_test unittests/emscripten_exception_test.cpp)
|
||||
target_link_libraries(emscripten_exception_test ${LIBS})
|
||||
add_test(NAME Emscripten_Exception_Test COMMAND emscripten_exception_test)
|
||||
|
||||
add_executable(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")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
350
cheatsheet.md
350
cheatsheet.md
@ -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`.
|
||||
|
||||
## 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 a Function / Method / Member
|
||||
@ -665,124 +604,6 @@ copy.width = 99
|
||||
print(original.width) // still 10
|
||||
```
|
||||
|
||||
## Enums
|
||||
|
||||
ChaiScript supports strongly-typed enums using `enum class` (or equivalently `enum struct`),
|
||||
matching C++ scoped-enum semantics. Values are accessed via `::` syntax and are type-safe —
|
||||
a plain integer cannot be passed where an enum type is expected.
|
||||
|
||||
### Basic Definition
|
||||
|
||||
```
|
||||
enum class Color { Red, Green, Blue }
|
||||
```
|
||||
|
||||
Values are auto-numbered starting from 0. Access them with `Color::Red`, `Color::Green`, etc.
|
||||
|
||||
### Explicit Values
|
||||
|
||||
```
|
||||
enum class Priority { Low = 10, Medium = 20, High = 30 }
|
||||
```
|
||||
|
||||
Auto-numbering continues from the last explicit value:
|
||||
|
||||
```
|
||||
enum class Status { Pending, Active = 5, Done }
|
||||
// Pending = 0, Active = 5, Done = 6
|
||||
```
|
||||
|
||||
### Specifying an Underlying Type
|
||||
|
||||
By default the underlying type is `int`. Use `: type` to choose a different numeric type:
|
||||
|
||||
```
|
||||
enum class Flags : char { Read = 1, Write = 2, Execute = 4 }
|
||||
```
|
||||
|
||||
The underlying type must be a numeric type registered in ChaiScript. `string` and other
|
||||
non-numeric types cannot be used. The available underlying types are:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `int` | (default) signed integer |
|
||||
| `unsigned_int` | unsigned integer |
|
||||
| `long` | signed long |
|
||||
| `unsigned_long` | unsigned long |
|
||||
| `long_long` | signed long long |
|
||||
| `unsigned_long_long` | unsigned long long |
|
||||
| `char` | character (8-bit) |
|
||||
| `wchar_t` | wide character |
|
||||
| `char16_t` | 16-bit character |
|
||||
| `char32_t` | 32-bit character |
|
||||
| `float` | single-precision float |
|
||||
| `double` | double-precision float |
|
||||
| `long_double` | extended-precision float |
|
||||
| `size_t` | unsigned size type |
|
||||
| `int8_t` | signed 8-bit |
|
||||
| `int16_t` | signed 16-bit |
|
||||
| `int32_t` | signed 32-bit |
|
||||
| `int64_t` | signed 64-bit |
|
||||
| `uint8_t` | unsigned 8-bit |
|
||||
| `uint16_t` | unsigned 16-bit |
|
||||
| `uint32_t` | unsigned 32-bit |
|
||||
|
||||
### `enum struct` Syntax
|
||||
|
||||
`enum struct` is accepted as a synonym for `enum class`, just like in C++:
|
||||
|
||||
```
|
||||
enum struct Direction { North, East, South, West }
|
||||
```
|
||||
|
||||
### Constructing from a Value
|
||||
|
||||
Each enum type has a constructor that accepts the underlying type. It validates that the
|
||||
value matches one of the defined enumerators:
|
||||
|
||||
```
|
||||
auto c = Color::Color(1) // creates Color::Green
|
||||
Color::Color(52) // throws: invalid value
|
||||
```
|
||||
|
||||
### `to_underlying`
|
||||
|
||||
Convert an enum value back to its underlying numeric type:
|
||||
|
||||
```
|
||||
Color::Red.to_underlying() // 0
|
||||
Priority::High.to_underlying() // 30
|
||||
```
|
||||
|
||||
### Comparison
|
||||
|
||||
`==` and `!=` are defined for values of the same enum type:
|
||||
|
||||
```
|
||||
assert_true(Color::Red == Color::Red)
|
||||
assert_true(Color::Red != Color::Green)
|
||||
```
|
||||
|
||||
### Type-Safe Dispatch
|
||||
|
||||
Functions declared with an enum parameter type reject plain integers:
|
||||
|
||||
```
|
||||
def handle(Color c) { /* ... */ }
|
||||
handle(Color::Red) // ok
|
||||
handle(42) // throws: dispatch error
|
||||
```
|
||||
|
||||
### Using with `switch`
|
||||
|
||||
```
|
||||
switch(Color::Green) {
|
||||
case (Color::Red) { print("red"); break }
|
||||
case (Color::Green) { print("green"); break }
|
||||
case (Color::Blue) { print("blue"); break }
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic Objects
|
||||
|
||||
All ChaiScript defined types and generic Dynamic_Object support dynamic parameters
|
||||
@ -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
|
||||
|
||||
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
|
||||
* `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
|
||||
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**.
|
||||
|
||||
@ -33,13 +33,6 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
|
||||
[TEST_SUFFIX suffix]
|
||||
[PROPERTIES name1 value1...]
|
||||
[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
|
||||
@ -97,222 +90,86 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
|
||||
executable is being used in multiple calls to ``catch_discover_tests()``.
|
||||
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)
|
||||
|
||||
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_SPEC;EXTRA_ARGS;PROPERTIES;DL_PATHS;DL_FRAMEWORK_PATHS"
|
||||
""
|
||||
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST"
|
||||
"TEST_SPEC;EXTRA_ARGS;PROPERTIES"
|
||||
${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)
|
||||
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
if(NOT _TEST_LIST)
|
||||
set(_TEST_LIST ${TARGET}_TESTS)
|
||||
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
|
||||
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)
|
||||
|
||||
# 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 "${ctest_file_base}_include.cmake")
|
||||
set(ctest_tests_file "${ctest_file_base}_tests.cmake")
|
||||
|
||||
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
|
||||
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
|
||||
get_property(crosscompiling_emulator
|
||||
TARGET ${TARGET}
|
||||
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(
|
||||
TARGET ${TARGET} POST_BUILD
|
||||
BYPRODUCTS "${ctest_tests_file}"
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-D "TEST_TARGET=${TARGET}"
|
||||
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
|
||||
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
|
||||
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
|
||||
-D "TEST_SPEC=${_TEST_SPEC}"
|
||||
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
|
||||
-D "TEST_PROPERTIES=${_PROPERTIES}"
|
||||
-D "TEST_PREFIX=${_TEST_PREFIX}"
|
||||
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
|
||||
-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 "ADD_TAGS_AS_LABELS=${_ADD_TAGS_AS_LABELS}"
|
||||
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
elseif(_DISCOVERY_MODE STREQUAL "PRE_TEST")
|
||||
|
||||
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
|
||||
set_property(DIRECTORY
|
||||
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
|
||||
add_custom_command(
|
||||
TARGET ${TARGET} POST_BUILD
|
||||
BYPRODUCTS "${ctest_tests_file}"
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-D "TEST_TARGET=${TARGET}"
|
||||
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
|
||||
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
|
||||
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
|
||||
-D "TEST_SPEC=${_TEST_SPEC}"
|
||||
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
|
||||
-D "TEST_PROPERTIES=${_PROPERTIES}"
|
||||
-D "TEST_PREFIX=${_TEST_PREFIX}"
|
||||
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
|
||||
-D "TEST_LIST=${_TEST_LIST}"
|
||||
-D "CTEST_FILE=${ctest_tests_file}"
|
||||
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
|
||||
# Add discovered tests to directory TEST_INCLUDE_FILES
|
||||
set_property(DIRECTORY
|
||||
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()
|
||||
|
||||
###############################################################################
|
||||
|
||||
set(_CATCH_DISCOVER_TESTS_SCRIPT
|
||||
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
|
||||
CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
|
||||
)
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# 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)
|
||||
set(_args "")
|
||||
# use ARGV* instead of ARGN, because ARGN splits arrays into multiple arguments
|
||||
math(EXPR _last_arg ${ARGC}-1)
|
||||
foreach(_n RANGE 1 ${_last_arg})
|
||||
set(_arg "${ARGV${_n}}")
|
||||
foreach(_arg ${ARGN})
|
||||
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
|
||||
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
|
||||
else()
|
||||
@ -16,239 +22,55 @@ function(add_command NAME)
|
||||
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
|
||||
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
|
||||
if(NOT EXISTS "${_TEST_EXECUTABLE}")
|
||||
message(FATAL_ERROR
|
||||
"Specified test executable '${_TEST_EXECUTABLE}' does not exist"
|
||||
)
|
||||
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(
|
||||
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec} --list-tests --reporter json
|
||||
OUTPUT_VARIABLE listing_output
|
||||
RESULT_VARIABLE result
|
||||
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
|
||||
)
|
||||
if(NOT ${result} EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"Error listing tests from executable '${_TEST_EXECUTABLE}':\n"
|
||||
" Result: ${result}\n"
|
||||
" Output: ${listing_output}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Prepare reporter
|
||||
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()
|
||||
|
||||
# ...and add to script
|
||||
add_command(add_test
|
||||
"${prefix}${plain_name}${suffix}"
|
||||
${_TEST_EXECUTOR}
|
||||
"${_TEST_EXECUTABLE}"
|
||||
"${escaped_name}"
|
||||
${extra_args}
|
||||
"${reporter_arg}"
|
||||
"${output_dir_arg}"
|
||||
)
|
||||
add_command(set_tests_properties
|
||||
"${prefix}${plain_name}${suffix}"
|
||||
PROPERTIES
|
||||
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
|
||||
${properties}
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
# Create a list of all discovered tests, which users may use to e.g. set
|
||||
# properties on the tests
|
||||
add_command(set ${_TEST_LIST} ${tests})
|
||||
|
||||
# Write CTest 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}
|
||||
# Run test executable to get list of available tests
|
||||
if(NOT EXISTS "${TEST_EXECUTABLE}")
|
||||
message(FATAL_ERROR
|
||||
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
|
||||
)
|
||||
endif()
|
||||
execute_process(
|
||||
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only
|
||||
OUTPUT_VARIABLE output
|
||||
RESULT_VARIABLE result
|
||||
)
|
||||
# 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
|
||||
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||
" Result: ${result}\n"
|
||||
" Output: ${output}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
string(REPLACE "\n" ";" output "${output}")
|
||||
|
||||
# Parse output
|
||||
foreach(line ${output})
|
||||
set(test ${line})
|
||||
# ...and add to script
|
||||
add_command(add_test
|
||||
"${prefix}${test}${suffix}"
|
||||
${TEST_EXECUTOR}
|
||||
"${TEST_EXECUTABLE}"
|
||||
${test}
|
||||
${extra_args}
|
||||
)
|
||||
add_command(set_tests_properties
|
||||
"${prefix}${test}${suffix}"
|
||||
PROPERTIES
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
${properties}
|
||||
)
|
||||
list(APPEND tests "${prefix}${test}${suffix}")
|
||||
endforeach()
|
||||
|
||||
# Create a list of all discovered tests, which users may use to e.g. set
|
||||
# properties on the tests
|
||||
add_command(set ${TEST_LIST} ${tests})
|
||||
|
||||
# Write CTest script
|
||||
file(WRITE "${CTEST_FILE}" "${script}")
|
||||
|
||||
@ -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"
|
||||
)
|
||||
@ -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"
|
||||
)
|
||||
@ -1,11 +1,9 @@
|
||||
#==================================================================================================#
|
||||
# supported macros #
|
||||
# - TEST_CASE, #
|
||||
# - TEMPLATE_TEST_CASE #
|
||||
# - SCENARIO, #
|
||||
# - TEST_CASE_METHOD, #
|
||||
# - CATCH_TEST_CASE, #
|
||||
# - CATCH_TEMPLATE_TEST_CASE #
|
||||
# - CATCH_SCENARIO, #
|
||||
# - CATCH_TEST_CASE_METHOD. #
|
||||
# #
|
||||
@ -41,24 +39,9 @@
|
||||
# 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 #
|
||||
# #
|
||||
# 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)
|
||||
message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer")
|
||||
endif()
|
||||
cmake_minimum_required(VERSION 2.8.8)
|
||||
|
||||
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)
|
||||
@ -66,10 +49,10 @@ 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_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF)
|
||||
|
||||
function(ParseAndAddCatchTests_PrintDebugMessage)
|
||||
if(PARSE_CATCH_TESTS_VERBOSE)
|
||||
message(STATUS "ParseAndAddCatchTests: ${ARGV}")
|
||||
endif()
|
||||
function(PrintDebugMessage)
|
||||
if(PARSE_CATCH_TESTS_VERBOSE)
|
||||
message(STATUS "ParseAndAddCatchTests: ${ARGV}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# This removes the contents between
|
||||
@ -77,7 +60,7 @@ endfunction()
|
||||
# - full line comments (i.e. // ... )
|
||||
# contents have been read into '${CppCode}'.
|
||||
# !keep partial line comments
|
||||
function(ParseAndAddCatchTests_RemoveComments CppCode)
|
||||
function(RemoveComments CppCode)
|
||||
string(ASCII 2 CMakeBeginBlockComment)
|
||||
string(ASCII 3 CMakeEndBlockComment)
|
||||
string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}")
|
||||
@ -89,162 +72,114 @@ function(ParseAndAddCatchTests_RemoveComments CppCode)
|
||||
endfunction()
|
||||
|
||||
# Worker function
|
||||
function(ParseAndAddCatchTests_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.
|
||||
get_filename_component(SourceFile ${SourceFile} ABSOLUTE)
|
||||
if(NOT EXISTS ${SourceFile})
|
||||
message(WARNING "Cannot find source file: ${SourceFile}")
|
||||
return()
|
||||
endif()
|
||||
ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}")
|
||||
file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)
|
||||
|
||||
# Remove block and fullline comments
|
||||
ParseAndAddCatchTests_RemoveComments(Contents)
|
||||
|
||||
# Find definition of test names
|
||||
# https://regex101.com/r/JygOND/1
|
||||
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)
|
||||
ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
|
||||
set_property(
|
||||
DIRECTORY
|
||||
APPEND
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile}
|
||||
)
|
||||
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})
|
||||
# Strip newlines
|
||||
string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}")
|
||||
|
||||
# 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_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}")
|
||||
string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}")
|
||||
|
||||
# Get string parts of test definition
|
||||
string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}")
|
||||
|
||||
# Strip wrapping quotation marks
|
||||
string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}")
|
||||
string(REPLACE "\";\"" ";" TestStrings "${TestStrings}")
|
||||
|
||||
# Validate that a test name and tags have been provided
|
||||
list(LENGTH TestStrings TestStringsLength)
|
||||
if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1)
|
||||
message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}")
|
||||
function(ParseFile SourceFile TestTarget)
|
||||
# According to CMake docs EXISTS behavior is well-defined only for full paths.
|
||||
get_filename_component(SourceFile ${SourceFile} ABSOLUTE)
|
||||
if(NOT EXISTS ${SourceFile})
|
||||
message(WARNING "Cannot find source file: ${SourceFile}")
|
||||
return()
|
||||
endif()
|
||||
PrintDebugMessage("parsing ${SourceFile}")
|
||||
file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)
|
||||
|
||||
# Assign name and tags
|
||||
list(GET TestStrings 0 Name)
|
||||
if("${TestType}" STREQUAL "SCENARIO")
|
||||
set(Name "Scenario: ${Name}")
|
||||
endif()
|
||||
if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND "${TestType}" MATCHES "(CATCH_)?TEST_CASE_METHOD" AND TestFixture)
|
||||
set(CTestName "${TestFixture}:${Name}")
|
||||
else()
|
||||
set(CTestName "${Name}")
|
||||
endif()
|
||||
if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME)
|
||||
set(CTestName "${TestTarget}:${CTestName}")
|
||||
endif()
|
||||
# add target to labels to enable running all tests added from this target
|
||||
set(Labels ${TestTarget})
|
||||
if(TestStringsLength EQUAL 2)
|
||||
list(GET TestStrings 1 Tags)
|
||||
string(TOLOWER "${Tags}" Tags)
|
||||
# remove target from labels if the test is hidden
|
||||
if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*")
|
||||
list(REMOVE_ITEM Labels ${TestTarget})
|
||||
endif()
|
||||
string(REPLACE "]" ";" Tags "${Tags}")
|
||||
string(REPLACE "[" "" Tags "${Tags}")
|
||||
else()
|
||||
# unset tags variable from previous loop
|
||||
unset(Tags)
|
||||
endif()
|
||||
# Remove block and fullline comments
|
||||
RemoveComments(Contents)
|
||||
|
||||
list(APPEND Labels ${Tags})
|
||||
# Find definition of test names
|
||||
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}")
|
||||
|
||||
set(HiddenTagFound OFF)
|
||||
foreach(label ${Labels})
|
||||
string(REGEX MATCH "^!hide|^\\." result ${label})
|
||||
if(result)
|
||||
set(HiddenTagFound ON)
|
||||
break()
|
||||
endif()
|
||||
endforeach(label)
|
||||
if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9")
|
||||
ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label")
|
||||
else()
|
||||
ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"")
|
||||
if(Labels)
|
||||
ParseAndAddCatchTests_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()
|
||||
|
||||
# Add the test and set its properties
|
||||
add_test(NAME "${CTestName}" COMMAND ${OptionalCatchTestLauncher} $<TARGET_FILE:${TestTarget}> ${Name} ${AdditionalCatchParameters})
|
||||
# Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead
|
||||
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}")
|
||||
endif()
|
||||
if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)
|
||||
PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
|
||||
set_property(
|
||||
TARGET ${TestTarget}
|
||||
DIRECTORY
|
||||
APPEND
|
||||
PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}")
|
||||
set_property(
|
||||
SOURCE ${SourceFile}
|
||||
APPEND
|
||||
PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}")
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile}
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(TestName ${Tests})
|
||||
# Strip newlines
|
||||
string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}")
|
||||
|
||||
# Get test type and fixture if applicable
|
||||
string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}")
|
||||
string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}")
|
||||
string(REPLACE "${TestType}(" "" TestFixture "${TestTypeAndFixture}")
|
||||
|
||||
# Get string parts of test definition
|
||||
string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}")
|
||||
|
||||
# Strip wrapping quotation marks
|
||||
string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}")
|
||||
string(REPLACE "\";\"" ";" TestStrings "${TestStrings}")
|
||||
|
||||
# Validate that a test name and tags have been provided
|
||||
list(LENGTH TestStrings TestStringsLength)
|
||||
if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1)
|
||||
message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}")
|
||||
endif()
|
||||
|
||||
# Assign name and tags
|
||||
list(GET TestStrings 0 Name)
|
||||
if("${TestType}" STREQUAL "SCENARIO")
|
||||
set(Name "Scenario: ${Name}")
|
||||
endif()
|
||||
if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture)
|
||||
set(CTestName "${TestFixture}:${Name}")
|
||||
else()
|
||||
set(CTestName "${Name}")
|
||||
endif()
|
||||
if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME)
|
||||
set(CTestName "${TestTarget}:${CTestName}")
|
||||
endif()
|
||||
# add target to labels to enable running all tests added from this target
|
||||
set(Labels ${TestTarget})
|
||||
if(TestStringsLength EQUAL 2)
|
||||
list(GET TestStrings 1 Tags)
|
||||
string(TOLOWER "${Tags}" Tags)
|
||||
# remove target from labels if the test is hidden
|
||||
if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*")
|
||||
list(REMOVE_ITEM Labels ${TestTarget})
|
||||
endif()
|
||||
string(REPLACE "]" ";" Tags "${Tags}")
|
||||
string(REPLACE "[" "" Tags "${Tags}")
|
||||
endif()
|
||||
|
||||
list(APPEND Labels ${Tags})
|
||||
|
||||
list(FIND Labels "!hide" IndexOfHideLabel)
|
||||
set(HiddenTagFound OFF)
|
||||
foreach(label ${Labels})
|
||||
string(REGEX MATCH "^!hide|^\\." result ${label})
|
||||
if(result)
|
||||
set(HiddenTagFound ON)
|
||||
break()
|
||||
endif(result)
|
||||
endforeach(label)
|
||||
if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound})
|
||||
PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label")
|
||||
else()
|
||||
PrintDebugMessage("Adding test \"${CTestName}\"")
|
||||
if(Labels)
|
||||
PrintDebugMessage("Setting labels to ${Labels}")
|
||||
endif()
|
||||
|
||||
# Add the test and set its properties
|
||||
add_test(NAME "\"${CTestName}\"" COMMAND ${TestTarget} ${Name} ${AdditionalCatchParameters})
|
||||
set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran"
|
||||
LABELS "${Labels}")
|
||||
endif()
|
||||
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# entry point
|
||||
function(ParseAndAddCatchTests TestTarget)
|
||||
message(DEPRECATION "ParseAndAddCatchTest: function deprecated because of possibility of missed test cases. Consider using 'catch_discover_tests' from 'Catch.cmake'")
|
||||
ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}")
|
||||
get_target_property(SourceFiles ${TestTarget} SOURCES)
|
||||
ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}")
|
||||
foreach(SourceFile ${SourceFiles})
|
||||
ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget})
|
||||
endforeach()
|
||||
ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}")
|
||||
PrintDebugMessage("Started parsing ${TestTarget}")
|
||||
get_target_property(SourceFiles ${TestTarget} SOURCES)
|
||||
PrintDebugMessage("Found the following sources: ${SourceFiles}")
|
||||
foreach(SourceFile ${SourceFiles})
|
||||
ParseFile(${SourceFile} ${TestTarget})
|
||||
endforeach()
|
||||
PrintDebugMessage("Finished parsing ${TestTarget}")
|
||||
endfunction()
|
||||
|
||||
@ -17,14 +17,9 @@ add_definitions(-DCHAISCRIPT_NO_THREADS -DCHAISCRIPT_NO_DYNLOAD)
|
||||
add_executable(chaiscript chaiscript_em.cpp)
|
||||
target_include_directories(chaiscript PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
|
||||
|
||||
# Enable WASM exception handling — ChaiScript relies on C++ exceptions for
|
||||
# error propagation; without this flag exceptions cause an abort in WASM.
|
||||
target_compile_options(chaiscript PRIVATE -fwasm-exceptions)
|
||||
|
||||
# Emscripten link flags: enable embind, allow memory growth, export as ES module-compatible
|
||||
target_link_options(chaiscript PRIVATE
|
||||
--bind
|
||||
-fwasm-exceptions
|
||||
-sALLOW_MEMORY_GROWTH=1
|
||||
-sEXPORT_ES6=0
|
||||
-sMODULARIZE=0
|
||||
|
||||
@ -185,8 +185,6 @@ print("10! = " + to_string(factorial(10)))
|
||||
outputEl.scrollTop = outputEl.scrollHeight;
|
||||
}
|
||||
|
||||
var chaiHandle = 0;
|
||||
|
||||
var Module = {
|
||||
print: function(text) {
|
||||
appendOutput(text);
|
||||
@ -195,7 +193,6 @@ print("10! = " + to_string(factorial(10)))
|
||||
appendOutput(text, 'output-error');
|
||||
},
|
||||
onRuntimeInitialized: function() {
|
||||
chaiHandle = Module.create();
|
||||
statusEl.textContent = 'Ready';
|
||||
statusEl.className = 'ready';
|
||||
btnRun.disabled = false;
|
||||
@ -208,7 +205,7 @@ print("10! = " + to_string(factorial(10)))
|
||||
|
||||
appendOutput('> Running...', 'output-line');
|
||||
try {
|
||||
Module.eval(chaiHandle, code);
|
||||
Module.eval(code);
|
||||
} catch (e) {
|
||||
appendOutput('Error: ' + e.message, 'output-error');
|
||||
}
|
||||
|
||||
@ -13,16 +13,11 @@
|
||||
#include <emscripten/bind.h>
|
||||
|
||||
EMSCRIPTEN_BINDINGS(chaiscript) {
|
||||
emscripten::function("create", &chaiscript_create);
|
||||
emscripten::function("destroy", &chaiscript_destroy);
|
||||
emscripten::function("eval", &chaiscript_eval);
|
||||
emscripten::function("evalString", &chaiscript_eval_string);
|
||||
emscripten::function("evalBool", &chaiscript_eval_bool);
|
||||
emscripten::function("evalInt", &chaiscript_eval_int);
|
||||
emscripten::function("evalFloat", &chaiscript_eval_float);
|
||||
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
|
||||
|
||||
@ -7,118 +7,42 @@
|
||||
// Shared eval helper functions for the ChaiScript Emscripten wrapper.
|
||||
// These functions provide typed evaluation of ChaiScript expressions,
|
||||
// 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_
|
||||
#define CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_
|
||||
|
||||
#include <chaiscript/chaiscript.hpp>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <chaiscript/chaiscript.hpp>
|
||||
|
||||
namespace detail {
|
||||
// ChaiScript::State captures globals/functions/types but not the top-level
|
||||
// scripting locals (variables created by `var x = ...` at the script's
|
||||
// outermost scope). The playground's reset-between-runs use case needs both,
|
||||
// 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);
|
||||
}
|
||||
inline chaiscript::ChaiScript &get_chai_instance() {
|
||||
static chaiscript::ChaiScript chai;
|
||||
return chai;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// Construct a fresh ChaiScript engine and return an opaque handle. The handle
|
||||
// is owned by the caller and must be released with chaiscript_destroy().
|
||||
inline int chaiscript_create() {
|
||||
const int handle = detail::next_handle();
|
||||
detail::chai_registry().emplace(handle, std::make_unique<chaiscript::ChaiScript>());
|
||||
return handle;
|
||||
inline void chaiscript_eval(const std::string &input) {
|
||||
detail::get_chai_instance().eval(input);
|
||||
}
|
||||
|
||||
// Destroy an engine handle. Unknown handles are silently ignored so JS callers
|
||||
// do not need to track validity defensively.
|
||||
inline void chaiscript_destroy(const int handle) {
|
||||
detail::chai_registry().erase(handle);
|
||||
inline std::string chaiscript_eval_string(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<std::string>(input);
|
||||
}
|
||||
|
||||
inline void chaiscript_eval(const int handle, const std::string &input) {
|
||||
detail::get_chai(handle).eval(input);
|
||||
inline bool chaiscript_eval_bool(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<bool>(input);
|
||||
}
|
||||
|
||||
inline std::string chaiscript_eval_string(const int handle, const std::string &input) {
|
||||
return detail::get_chai(handle).eval<std::string>(input);
|
||||
inline int chaiscript_eval_int(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<int>(input);
|
||||
}
|
||||
|
||||
inline bool chaiscript_eval_bool(const int handle, const std::string &input) {
|
||||
return detail::get_chai(handle).eval<bool>(input);
|
||||
inline float chaiscript_eval_float(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<float>(input);
|
||||
}
|
||||
|
||||
inline int chaiscript_eval_int(const int handle, const std::string &input) {
|
||||
return detail::get_chai(handle).eval<int>(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);
|
||||
inline double chaiscript_eval_double(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<double>(input);
|
||||
}
|
||||
|
||||
#endif /* CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ */
|
||||
|
||||
@ -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 ::= [^"\]
|
||||
@ -824,14 +824,12 @@ namespace chaiscript {
|
||||
public:
|
||||
ChaiScript(std::vector<std::string> t_modulepaths = {},
|
||||
std::vector<std::string> t_usepaths = {},
|
||||
std::vector<Options> t_opts = chaiscript::default_options(),
|
||||
std::vector<Library_Options> t_lib_opts = {})
|
||||
: ChaiScript_Basic(chaiscript::Std_Lib::library(t_lib_opts),
|
||||
std::vector<Options> t_opts = chaiscript::default_options())
|
||||
: ChaiScript_Basic(chaiscript::Std_Lib::library(),
|
||||
std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(),
|
||||
std::move(t_modulepaths),
|
||||
std::move(t_usepaths),
|
||||
std::move(t_opts),
|
||||
std::find(t_lib_opts.begin(), t_lib_opts.end(), Library_Options::No_IO) != t_lib_opts.end()) {
|
||||
std::move(t_opts)) {
|
||||
}
|
||||
};
|
||||
} // namespace chaiscript
|
||||
|
||||
@ -7,16 +7,6 @@
|
||||
#ifndef 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
|
||||
#define CHAISCRIPT_STRINGIZE(x) "" #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"
|
||||
#endif
|
||||
|
||||
#if defined(CHAISCRIPT_MSVC) || (defined(__GNUC__) && __GNUC__ >= 5) || defined(CHAISCRIPT_CLANG)
|
||||
#define CHAISCRIPT_UTF16_UTF32
|
||||
#endif
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define CHAISCRIPT_DEBUG true
|
||||
#else
|
||||
#define CHAISCRIPT_DEBUG false
|
||||
#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 <memory>
|
||||
#include <string>
|
||||
@ -106,9 +82,6 @@ namespace chaiscript {
|
||||
constexpr static const char *compiler_name = CHAISCRIPT_COMPILER_NAME;
|
||||
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>
|
||||
inline std::shared_ptr<B> make_shared(Arg &&...arg) {
|
||||
#ifdef CHAISCRIPT_USE_STD_MAKE_SHARED
|
||||
@ -238,13 +211,6 @@ namespace chaiscript {
|
||||
External_Scripts
|
||||
};
|
||||
|
||||
enum class Library_Options {
|
||||
No_Stdlib,
|
||||
No_IO,
|
||||
No_Prelude,
|
||||
No_JSON
|
||||
};
|
||||
|
||||
template<typename From, typename To>
|
||||
struct is_nothrow_forward_constructible : std::bool_constant<noexcept(To{std::declval<From>()})> {
|
||||
};
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@ -19,11 +18,11 @@
|
||||
|
||||
#include "dispatchkit/function_call.hpp"
|
||||
|
||||
// #include "dispatchkit/dispatchkit.hpp"
|
||||
//#include "dispatchkit/dispatchkit.hpp"
|
||||
#include "dispatchkit/bootstrap.hpp"
|
||||
#include "dispatchkit/bootstrap_stl.hpp"
|
||||
#include "dispatchkit/operators.hpp"
|
||||
// #include "dispatchkit/boxed_value.hpp"
|
||||
//#include "dispatchkit/boxed_value.hpp"
|
||||
#include "dispatchkit/register_function.hpp"
|
||||
#include "language/chaiscript_prelude.hpp"
|
||||
#include "utility/json_wrap.hpp"
|
||||
@ -39,41 +38,25 @@
|
||||
namespace chaiscript {
|
||||
class Std_Lib {
|
||||
public:
|
||||
[[nodiscard]] static ModulePtr library(const std::vector<Library_Options> &t_opts = {}) {
|
||||
if (std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Stdlib) != t_opts.end()) {
|
||||
return std::make_shared<Module>();
|
||||
}
|
||||
|
||||
[[nodiscard]] static ModulePtr library() {
|
||||
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::standard_library::vector_type<std::vector<Boxed_Value>>("Vector", *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::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
|
||||
bootstrap::standard_library::future_type<std::future<chaiscript::Boxed_Value>>("future", *lib);
|
||||
// Note: async() is registered in ChaiScript_Basic::build_eval_system()
|
||||
// with thread tracking to prevent heap-use-after-free on engine destruction.
|
||||
lib->add(chaiscript::fun(
|
||||
[](const std::function<chaiscript::Boxed_Value()> &t_func) { return std::async(std::launch::async, t_func); }),
|
||||
"async");
|
||||
#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;
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ namespace chaiscript::bootstrap {
|
||||
throw std::range_error("Array index out of range. Received: " + std::to_string(index) + " expected < "
|
||||
+ std::to_string(extent));
|
||||
} 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 < "
|
||||
+ std::to_string(extent));
|
||||
} 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([](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
|
||||
@ -268,7 +266,7 @@ namespace chaiscript::bootstrap {
|
||||
public:
|
||||
/// \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] t_no_io If true, skip registering print_string and println_string
|
||||
/// \returns passed in Module
|
||||
static void bootstrap(Module &m) {
|
||||
m.add(user_type<void>(), "void");
|
||||
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::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(fun(&shared_ptr_unconst_clone<dispatch::Proxy_Function_Base>), "clone");
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
||||
@ -85,15 +84,6 @@ namespace chaiscript::bootstrap::standard_library {
|
||||
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>
|
||||
void insert(T &t_target, const T &t_other) {
|
||||
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
|
||||
template<typename ContainerType>
|
||||
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);
|
||||
operators::assign<ContainerType>(m);
|
||||
}
|
||||
copy_constructor<ContainerType>(type, m);
|
||||
operators::assign<ContainerType>(m);
|
||||
}
|
||||
|
||||
/// Add container resize concept to the given ContainerType
|
||||
/// http://www.cplusplus.com/reference/stl/
|
||||
template<typename ContainerType>
|
||||
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) {
|
||||
return a->resize(n, val);
|
||||
}),
|
||||
"resize");
|
||||
}
|
||||
m.add(fun([](ContainerType *a, typename ContainerType::size_type n, const typename ContainerType::value_type &val) {
|
||||
return a->resize(n, val);
|
||||
}),
|
||||
"resize");
|
||||
m.add(fun([](ContainerType *a, typename ContainerType::size_type n) { return a->resize(n); }), "resize");
|
||||
}
|
||||
|
||||
@ -227,15 +213,13 @@ namespace chaiscript::bootstrap::standard_library {
|
||||
/// http://www.sgi.com/tech/stl/Sequence.html
|
||||
template<typename ContainerType>
|
||||
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 {
|
||||
if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) {
|
||||
return "insert_ref_at";
|
||||
} else {
|
||||
return "insert_at";
|
||||
}
|
||||
}());
|
||||
}
|
||||
m.add(fun(&detail::insert_at<ContainerType>), []() -> std::string {
|
||||
if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) {
|
||||
return "insert_ref_at";
|
||||
} else {
|
||||
return "insert_at";
|
||||
}
|
||||
}());
|
||||
|
||||
m.add(fun(&detail::erase_at<ContainerType>), "erase_at");
|
||||
}
|
||||
@ -261,29 +245,27 @@ namespace chaiscript::bootstrap::standard_library {
|
||||
}),
|
||||
"back");
|
||||
|
||||
if constexpr (std::is_copy_constructible_v<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 {
|
||||
if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) {
|
||||
m.eval("# Pushes the second value onto the container while making a clone of the value\n"
|
||||
"def push_back("
|
||||
+ type
|
||||
+ " container, x)\n"
|
||||
"{ \n"
|
||||
" if (x.is_var_return_value()) {\n"
|
||||
" x.reset_var_return_value() \n"
|
||||
" container.push_back_ref(x) \n"
|
||||
" } else { \n"
|
||||
" container.push_back_ref(clone(x)); \n"
|
||||
" }\n"
|
||||
"} \n");
|
||||
using push_back = void (ContainerType::*)(const typename ContainerType::value_type &);
|
||||
m.add(fun(static_cast<push_back>(&ContainerType::push_back)), [&]() -> std::string {
|
||||
if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) {
|
||||
m.eval("# Pushes the second value onto the container while making a clone of the value\n"
|
||||
"def push_back("
|
||||
+ type
|
||||
+ " container, x)\n"
|
||||
"{ \n"
|
||||
" if (x.is_var_return_value()) {\n"
|
||||
" x.reset_var_return_value() \n"
|
||||
" container.push_back_ref(x) \n"
|
||||
" } else { \n"
|
||||
" container.push_back_ref(clone(x)); \n"
|
||||
" }\n"
|
||||
"} \n");
|
||||
|
||||
return "push_back_ref";
|
||||
} else {
|
||||
return "push_back";
|
||||
}
|
||||
}());
|
||||
}
|
||||
return "push_back_ref";
|
||||
} else {
|
||||
return "push_back";
|
||||
}
|
||||
}());
|
||||
|
||||
m.add(fun(&ContainerType::pop_back), "pop_back");
|
||||
}
|
||||
@ -313,27 +295,25 @@ namespace chaiscript::bootstrap::standard_library {
|
||||
}),
|
||||
"front");
|
||||
|
||||
if constexpr (std::is_copy_constructible_v<typename ContainerType::value_type>) {
|
||||
m.add(fun(static_cast<push_ptr>(&ContainerType::push_front)), [&]() -> std::string {
|
||||
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"
|
||||
"def push_front("
|
||||
+ type
|
||||
+ " container, x)\n"
|
||||
"{ \n"
|
||||
" if (x.is_var_return_value()) {\n"
|
||||
" x.reset_var_return_value() \n"
|
||||
" container.push_front_ref(x) \n"
|
||||
" } else { \n"
|
||||
" container.push_front_ref(clone(x)); \n"
|
||||
" }\n"
|
||||
"} \n");
|
||||
return "push_front_ref";
|
||||
} else {
|
||||
return "push_front";
|
||||
}
|
||||
}());
|
||||
}
|
||||
m.add(fun(static_cast<push_ptr>(&ContainerType::push_front)), [&]() -> std::string {
|
||||
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"
|
||||
"def push_front("
|
||||
+ type
|
||||
+ " container, x)\n"
|
||||
"{ \n"
|
||||
" if (x.is_var_return_value()) {\n"
|
||||
" x.reset_var_return_value() \n"
|
||||
" container.push_front_ref(x) \n"
|
||||
" } else { \n"
|
||||
" container.push_front_ref(clone(x)); \n"
|
||||
" }\n"
|
||||
"} \n");
|
||||
return "push_front_ref";
|
||||
} else {
|
||||
return "push_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>
|
||||
void unique_associative_container_type(const std::string & /*type*/, Module &m) {
|
||||
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 &);
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// http://www.sgi.com/tech/stl/Map.html
|
||||
template<typename FutureType>
|
||||
|
||||
@ -41,7 +41,7 @@ namespace chaiscript {
|
||||
// this is OK, so we're disabling size/and sign type warnings
|
||||
#ifdef CHAISCRIPT_MSVC
|
||||
#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
|
||||
|
||||
#ifdef __GNUC__
|
||||
@ -220,6 +220,7 @@ namespace chaiscript {
|
||||
if constexpr (!std::is_floating_point<LHS>::value && !std::is_floating_point<RHS>::value) {
|
||||
switch (t_oper) {
|
||||
case Operators::Opers::assign_bitwise_and:
|
||||
check_divide_by_zero(c_rhs);
|
||||
*t_lhs &= c_rhs;
|
||||
return t_bv;
|
||||
case Operators::Opers::assign_bitwise_or:
|
||||
@ -232,7 +233,6 @@ namespace chaiscript {
|
||||
*t_lhs >>= c_rhs;
|
||||
return t_bv;
|
||||
case Operators::Opers::assign_remainder:
|
||||
check_divide_by_zero(c_rhs);
|
||||
*t_lhs %= c_rhs;
|
||||
return t_bv;
|
||||
case Operators::Opers::assign_bitwise_xor:
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -34,6 +33,7 @@
|
||||
#include "dynamic_object.hpp"
|
||||
#include "proxy_constructors.hpp"
|
||||
#include "proxy_functions.hpp"
|
||||
#include "short_alloc.hpp"
|
||||
#include "type_conversions.hpp"
|
||||
#include "type_info.hpp"
|
||||
|
||||
@ -121,19 +121,6 @@ namespace chaiscript {
|
||||
global_non_const(const global_non_const &) = 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
|
||||
|
||||
/// \brief Holds a collection of ChaiScript settings which can be applied to the ChaiScript runtime.
|
||||
@ -330,6 +317,9 @@ namespace chaiscript {
|
||||
|
||||
namespace detail {
|
||||
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>
|
||||
using SmallVector = std::vector<T>;
|
||||
|
||||
@ -380,28 +370,6 @@ namespace chaiscript {
|
||||
, 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
|
||||
template<typename Type>
|
||||
decltype(auto) boxed_cast(const Boxed_Value &bv) const {
|
||||
@ -799,13 +767,13 @@ namespace chaiscript {
|
||||
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,
|
||||
const std::vector<Proxy_Function> &l_funs,
|
||||
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);
|
||||
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 {
|
||||
This_Foist(Dispatch_Engine &e, const Boxed_Value &t_bv)
|
||||
: m_e(e) {
|
||||
@ -823,16 +791,16 @@ namespace chaiscript {
|
||||
try {
|
||||
auto func = boxed_cast<const dispatch::Proxy_Function_Base *>(bv);
|
||||
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::arity_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)});
|
||||
} catch (const chaiscript::exception::bad_boxed_cast &) {
|
||||
// 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()));
|
||||
}
|
||||
} else {
|
||||
@ -882,7 +850,7 @@ namespace chaiscript {
|
||||
if (!functions.empty()) {
|
||||
try {
|
||||
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));
|
||||
return do_attribute_call(2, Function_Params(tmp_params), functions, t_conversions);
|
||||
} else {
|
||||
@ -957,7 +925,7 @@ namespace chaiscript {
|
||||
const auto &f = this->boxed_cast<Const_Proxy_Function>(params[0]);
|
||||
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
|
||||
@ -1023,10 +991,6 @@ namespace chaiscript {
|
||||
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) {
|
||||
if (t_s.call_depth >= max_call_depth) {
|
||||
throw chaiscript::exception::stack_overflow_error{};
|
||||
}
|
||||
|
||||
if (t_s.call_depth == 0) {
|
||||
m_conversions.enable_conversion_saves(t_saves, true);
|
||||
}
|
||||
@ -1100,21 +1064,13 @@ namespace chaiscript {
|
||||
// overridden methods in derived classes are tried first during dispatch
|
||||
const auto &lhs_dotn = lhs->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()) {
|
||||
if (dispatch::Dynamic_Object::type_matches(lhs_dotn, rhs_dotn)) {
|
||||
return true; // lhs is derived from rhs, so lhs is more specific
|
||||
}
|
||||
if (dispatch::Dynamic_Object::type_matches(rhs_dotn, lhs_dotn)) {
|
||||
return false; // rhs is derived from lhs, so rhs is more specific
|
||||
}
|
||||
if (!lhs_dotn.empty() && !rhs_dotn.empty() && 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
|
||||
}
|
||||
// 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();
|
||||
if (dispatch::Dynamic_Object::type_matches(rhs_dotn, lhs_dotn)) {
|
||||
return false; // rhs is derived from lhs, so rhs is more specific
|
||||
}
|
||||
return lhs_dotn < rhs_dotn;
|
||||
}
|
||||
|
||||
const auto &lhsparamtypes = lhs->get_param_types();
|
||||
@ -1164,9 +1120,7 @@ namespace chaiscript {
|
||||
return lt < rt;
|
||||
}
|
||||
|
||||
// When all overlapping parameters match, order by arity to maintain
|
||||
// strict-weak ordering (transitivity of equivalence)
|
||||
return lhssize < rhssize;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 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));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Type_Conversions m_conversions;
|
||||
@ -1235,11 +1174,6 @@ namespace chaiscript {
|
||||
mutable std::atomic_uint_fast32_t m_method_missing_loc = {0};
|
||||
|
||||
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 {
|
||||
|
||||
@ -65,6 +65,7 @@ namespace chaiscript {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool is_explicit() const noexcept { return m_option_explicit; }
|
||||
|
||||
void set_explicit(const bool t_explicit) noexcept { m_option_explicit = t_explicit; }
|
||||
|
||||
@ -103,9 +103,7 @@ namespace chaiscript {
|
||||
|
||||
assert(types.size() > 1);
|
||||
// assert(types[1].bare_equal(user_type<Boxed_Value>()));
|
||||
// When the object type_info is undefined (ChaiScript-defined class), use
|
||||
// Dynamic_Object so that dispatch priority scoring works correctly.
|
||||
types[1] = t_objectti.is_undef() ? user_type<Dynamic_Object>() : t_objectti;
|
||||
types[1] = t_objectti;
|
||||
return types;
|
||||
}
|
||||
|
||||
@ -148,11 +146,11 @@ namespace chaiscript {
|
||||
};
|
||||
|
||||
/**
|
||||
* A Proxy_Function implementation designed for creating a new
|
||||
* Dynamic_Object
|
||||
* that is automatically guarded based on the first param based on the
|
||||
* param's type name
|
||||
*/
|
||||
* A Proxy_Function implementation designed for creating a new
|
||||
* Dynamic_Object
|
||||
* that is automatically guarded based on the first param based on the
|
||||
* param's type name
|
||||
*/
|
||||
class Dynamic_Object_Constructor final : public Proxy_Function_Base {
|
||||
public:
|
||||
Dynamic_Object_Constructor(std::string t_type_name, const Proxy_Function &t_func)
|
||||
|
||||
@ -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>
|
||||
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>>) {
|
||||
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>) {
|
||||
return Boxed_Value(std::ref(std::forward<Q>(q)));
|
||||
} else {
|
||||
|
||||
@ -12,11 +12,55 @@
|
||||
|
||||
#include "boxed_value.hpp"
|
||||
|
||||
#include <span>
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ namespace chaiscript::dispatch::detail {
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile &) -> Function_Signature<Ret, Function_Params<volatile Class &, Param...>, false, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile & noexcept)
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile &noexcept)
|
||||
-> Function_Signature<Ret, Function_Params<volatile Class &, Param...>, true, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
@ -72,20 +72,20 @@ namespace chaiscript::dispatch::detail {
|
||||
-> Function_Signature<Ret, Function_Params<volatile const Class &, Param...>, false, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile const & noexcept)
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile const &noexcept)
|
||||
-> Function_Signature<Ret, Function_Params<volatile const Class &, Param...>, true, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) &) -> Function_Signature<Ret, Function_Params<Class &, Param...>, false, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) & noexcept) -> Function_Signature<Ret, Function_Params<Class &, Param...>, true, true>;
|
||||
Function_Signature(Ret (Class::*f)(Param...) &noexcept) -> Function_Signature<Ret, Function_Params<Class &, Param...>, true, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) const &) -> Function_Signature<Ret, Function_Params<const Class &, Param...>, false, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) const & noexcept) -> Function_Signature<Ret, Function_Params<const Class &, Param...>, true, true>;
|
||||
Function_Signature(Ret (Class::*f)(Param...) const &noexcept) -> Function_Signature<Ret, Function_Params<const Class &, Param...>, true, true>;
|
||||
|
||||
// && reference specifier
|
||||
|
||||
@ -93,7 +93,7 @@ namespace chaiscript::dispatch::detail {
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile &&) -> Function_Signature<Ret, Function_Params<volatile Class &&, Param...>, false, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile && noexcept)
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile &&noexcept)
|
||||
-> Function_Signature<Ret, Function_Params<volatile Class &&, Param...>, true, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
@ -101,20 +101,20 @@ namespace chaiscript::dispatch::detail {
|
||||
-> Function_Signature<Ret, Function_Params<volatile const Class &&, Param...>, false, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile const && noexcept)
|
||||
Function_Signature(Ret (Class::*f)(Param...) volatile const &&noexcept)
|
||||
-> Function_Signature<Ret, Function_Params<volatile const Class &&, Param...>, true, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) &&) -> Function_Signature<Ret, Function_Params<Class &&, Param...>, false, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) && noexcept) -> Function_Signature<Ret, Function_Params<Class &&, Param...>, true, true>;
|
||||
Function_Signature(Ret (Class::*f)(Param...) &&noexcept) -> Function_Signature<Ret, Function_Params<Class &&, Param...>, true, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) const &&) -> Function_Signature<Ret, Function_Params<const Class &&, Param...>, false, true>;
|
||||
|
||||
template<typename Ret, typename Class, typename... Param>
|
||||
Function_Signature(Ret (Class::*f)(Param...) const && noexcept)
|
||||
Function_Signature(Ret (Class::*f)(Param...) const &&noexcept)
|
||||
-> Function_Signature<Ret, Function_Params<const Class &&, Param...>, true, true>;
|
||||
|
||||
template<typename Ret, typename Class>
|
||||
|
||||
@ -126,7 +126,7 @@ namespace chaiscript {
|
||||
struct Handle_Return_Ref {
|
||||
template<typename T>
|
||||
static Boxed_Value handle(T &&r) {
|
||||
return Boxed_Value(std::cref(r), false);
|
||||
return Boxed_Value(std::cref(r), true);
|
||||
}
|
||||
};
|
||||
|
||||
@ -170,8 +170,8 @@ namespace chaiscript {
|
||||
};
|
||||
|
||||
/**
|
||||
* Used internally for handling a return value from a Proxy_Function call
|
||||
*/
|
||||
* Used internally for handling a return value from a Proxy_Function call
|
||||
*/
|
||||
template<>
|
||||
struct Handle_Return<Boxed_Number> {
|
||||
static Boxed_Value handle(const Boxed_Number &r) noexcept { return r.bv; }
|
||||
@ -182,8 +182,8 @@ namespace chaiscript {
|
||||
};
|
||||
|
||||
/**
|
||||
* Used internally for handling a return value from a Proxy_Function call
|
||||
*/
|
||||
* Used internally for handling a return value from a Proxy_Function call
|
||||
*/
|
||||
template<>
|
||||
struct Handle_Return<void> {
|
||||
static Boxed_Value handle() { return void_var(); }
|
||||
|
||||
@ -15,168 +15,168 @@
|
||||
#include "register_function.hpp"
|
||||
|
||||
namespace chaiscript::bootstrap::operators {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs = rhs; }), "=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_bitwise_and(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_bitwise_and(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs &= rhs; }), "&=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_xor(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_xor(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs ^= rhs; }), "^=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_bitwise_or(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_bitwise_or(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs |= rhs; }), "|=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_difference(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_difference(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs -= rhs; }), "-=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_left_shift(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_left_shift(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "<<=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_product(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_product(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "*=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_quotient(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_quotient(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs /= rhs; }), "/=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_remainder(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_remainder(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs %= rhs; }), "%=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_right_shift(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_right_shift(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs >>= rhs; }), ">>=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_sum(ModuleType &m) {
|
||||
template<typename T>
|
||||
void assign_sum(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs += rhs; }), "+=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void prefix_decrement(ModuleType &m) {
|
||||
template<typename T>
|
||||
void prefix_decrement(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs) -> T & { return --lhs; }), "--");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void prefix_increment(ModuleType &m) {
|
||||
template<typename T>
|
||||
void prefix_increment(Module &m) {
|
||||
m.add(chaiscript::fun([](T &lhs) -> T & { return ++lhs; }), "++");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void equal(ModuleType &m) {
|
||||
template<typename T>
|
||||
void equal(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs == rhs; }), "==");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void greater_than(ModuleType &m) {
|
||||
template<typename T>
|
||||
void greater_than(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs > rhs; }), ">");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void greater_than_equal(ModuleType &m) {
|
||||
template<typename T>
|
||||
void greater_than_equal(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >= rhs; }), ">=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void less_than(ModuleType &m) {
|
||||
template<typename T>
|
||||
void less_than(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs < rhs; }), "<");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void less_than_equal(ModuleType &m) {
|
||||
template<typename T>
|
||||
void less_than_equal(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs <= rhs; }), "<=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void logical_compliment(ModuleType &m) {
|
||||
template<typename T>
|
||||
void logical_compliment(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs) { return !lhs; }), "!");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void not_equal(ModuleType &m) {
|
||||
template<typename T>
|
||||
void not_equal(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs != rhs; }), "!=");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void addition(ModuleType &m) {
|
||||
template<typename T>
|
||||
void addition(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs + rhs; }), "+");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void unary_plus(ModuleType &m) {
|
||||
template<typename T>
|
||||
void unary_plus(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs) { return +lhs; }), "+");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void subtraction(ModuleType &m) {
|
||||
template<typename T>
|
||||
void subtraction(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs - rhs; }), "-");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void unary_minus(ModuleType &m) {
|
||||
template<typename T>
|
||||
void unary_minus(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs) { return -lhs; }), "-");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void bitwise_and(ModuleType &m) {
|
||||
template<typename T>
|
||||
void bitwise_and(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs & rhs; }), "&");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void bitwise_compliment(ModuleType &m) {
|
||||
template<typename T>
|
||||
void bitwise_compliment(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs) { return ~lhs; }), "~");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void bitwise_xor(ModuleType &m) {
|
||||
template<typename T>
|
||||
void bitwise_xor(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs ^ rhs; }), "^");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void bitwise_or(ModuleType &m) {
|
||||
template<typename T>
|
||||
void bitwise_or(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs | rhs; }), "|");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void division(ModuleType &m) {
|
||||
template<typename T>
|
||||
void division(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs / rhs; }), "/");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void left_shift(ModuleType &m) {
|
||||
template<typename T>
|
||||
void left_shift(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs << rhs; }), "<<");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void multiplication(ModuleType &m) {
|
||||
template<typename T>
|
||||
void multiplication(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs * rhs; }), "*");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void remainder(ModuleType &m) {
|
||||
template<typename T>
|
||||
void remainder(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs % rhs; }), "%");
|
||||
}
|
||||
|
||||
template<typename T, typename ModuleType>
|
||||
void right_shift(ModuleType &m) {
|
||||
template<typename T>
|
||||
void right_shift(Module &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >> rhs; }), ">>");
|
||||
}
|
||||
} // namespace chaiscript::bootstrap::operators
|
||||
|
||||
@ -65,7 +65,7 @@ namespace chaiscript {
|
||||
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 {
|
||||
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>();
|
||||
for (size_t i = 0; i < vals.size(); ++i) {
|
||||
const auto &name = m_types[i].first;
|
||||
@ -165,13 +165,13 @@ namespace chaiscript {
|
||||
};
|
||||
|
||||
/**
|
||||
* Pure virtual base class for all Proxy_Function implementations
|
||||
* Proxy_Functions are a type erasure of type safe C++
|
||||
* function calls. At runtime parameter types are expected to be
|
||||
* tested against passed in types.
|
||||
* Dispatch_Engine only knows how to work with Proxy_Function, no other
|
||||
* function classes.
|
||||
*/
|
||||
* Pure virtual base class for all Proxy_Function implementations
|
||||
* Proxy_Functions are a type erasure of type safe C++
|
||||
* function calls. At runtime parameter types are expected to be
|
||||
* tested against passed in types.
|
||||
* Dispatch_Engine only knows how to work with Proxy_Function, no other
|
||||
* function classes.
|
||||
*/
|
||||
class Proxy_Function_Base {
|
||||
public:
|
||||
virtual ~Proxy_Function_Base() = default;
|
||||
@ -380,15 +380,7 @@ namespace chaiscript {
|
||||
|
||||
for (const auto &t : t_types.types()) {
|
||||
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 {
|
||||
types.push_back(t.second);
|
||||
}
|
||||
@ -682,13 +674,13 @@ namespace chaiscript {
|
||||
public:
|
||||
dispatch_error(const Function_Params &t_parameters, std::vector<Const_Proxy_Function> t_functions)
|
||||
: std::runtime_error("Error with function dispatch")
|
||||
, parameters(t_parameters.begin(), t_parameters.end())
|
||||
, parameters(t_parameters.to_vector())
|
||||
, functions(std::move(t_functions)) {
|
||||
}
|
||||
|
||||
dispatch_error(const Function_Params &t_parameters, std::vector<Const_Proxy_Function> t_functions, const std::string &t_desc)
|
||||
: std::runtime_error(t_desc)
|
||||
, parameters(t_parameters.begin(), t_parameters.end())
|
||||
, parameters(t_parameters.to_vector())
|
||||
, functions(std::move(t_functions)) {
|
||||
}
|
||||
|
||||
@ -737,24 +729,11 @@ namespace chaiscript {
|
||||
if (matching_func == end) {
|
||||
matching_func = begin;
|
||||
} 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 &next_fun_param_types = begin->second->get_param_types();
|
||||
|
||||
// Prefer the candidate whose first parameter (receiver) matches the
|
||||
// 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()) {
|
||||
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
|
||||
} 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
|
||||
@ -827,30 +806,6 @@ namespace chaiscript {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,9 +32,9 @@ namespace chaiscript {
|
||||
namespace chaiscript {
|
||||
namespace exception {
|
||||
/**
|
||||
* Exception thrown when there is a mismatch in number of
|
||||
* parameters during Proxy_Function execution
|
||||
*/
|
||||
* Exception thrown when there is a mismatch in number of
|
||||
* parameters during Proxy_Function execution
|
||||
*/
|
||||
struct arity_error : std::range_error {
|
||||
arity_error(int t_got, int t_expected)
|
||||
: std::range_error("Function dispatch arity mismatch")
|
||||
@ -54,9 +54,9 @@ namespace chaiscript {
|
||||
namespace dispatch {
|
||||
namespace detail {
|
||||
/**
|
||||
* Used by Proxy_Function_Impl to return a list of all param types
|
||||
* it contains.
|
||||
*/
|
||||
* Used by Proxy_Function_Impl to return a list of all param types
|
||||
* it contains.
|
||||
*/
|
||||
template<typename Ret, typename... Params>
|
||||
std::vector<Type_Info> build_param_type_list(Ret (*)(Params...)) {
|
||||
/// \note somehow this is responsible for a large part of the code generation
|
||||
@ -64,10 +64,10 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by Proxy_Function_Impl to determine if it is equivalent to another
|
||||
* Proxy_Function_Impl object. This function is primarily used to prevent
|
||||
* registration of two functions with the exact same signatures
|
||||
*/
|
||||
* Used by Proxy_Function_Impl to determine if it is equivalent to another
|
||||
* Proxy_Function_Impl object. This function is primarily used to prevent
|
||||
* registration of two functions with the exact same signatures
|
||||
*/
|
||||
template<typename Ret, typename... Params>
|
||||
bool compare_types_cast(Ret (*)(Params...), const chaiscript::Function_Params ¶ms, const Type_Conversions_State &t_conversions) noexcept {
|
||||
try {
|
||||
@ -80,11 +80,11 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
template<typename Callable, typename Ret, typename... Params, size_t... I>
|
||||
Ret call_func_impl(Ret (*)(Params...),
|
||||
std::index_sequence<I...>,
|
||||
const Callable &f,
|
||||
[[maybe_unused]] const chaiscript::Function_Params ¶ms,
|
||||
[[maybe_unused]] const Type_Conversions_State &t_conversions) {
|
||||
Ret call_func(Ret (*)(Params...),
|
||||
std::index_sequence<I...>,
|
||||
const Callable &f,
|
||||
[[maybe_unused]] const chaiscript::Function_Params ¶ms,
|
||||
[[maybe_unused]] const Type_Conversions_State &t_conversions) {
|
||||
return f(boxed_cast<Params>(params[I], &t_conversions)...);
|
||||
}
|
||||
|
||||
@ -95,24 +95,14 @@ namespace chaiscript {
|
||||
template<typename Callable, typename Ret, typename... Params>
|
||||
Boxed_Value
|
||||
call_func(Ret (*sig)(Params...), const Callable &f, const chaiscript::Function_Params ¶ms, 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);
|
||||
return Handle_Return<void>::handle();
|
||||
} else {
|
||||
return Handle_Return<Ret>::handle(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 ¶ms, const Type_Conversions_State &t_conversions) {
|
||||
call_func_impl(sig, std::index_sequence_for<Params...>{}, f, params, t_conversions);
|
||||
return Handle_Return<void>::handle();
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
} // namespace dispatch
|
||||
|
||||
|
||||
@ -82,43 +82,6 @@ namespace chaiscript {
|
||||
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
|
||||
/// \param[in] t Function / member to expose
|
||||
/// \param[in] q Value to bind to first parameter
|
||||
|
||||
137
include/chaiscript/dispatchkit/short_alloc.hpp
Normal file
137
include/chaiscript/dispatchkit/short_alloc.hpp
Normal 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
|
||||
@ -498,29 +498,6 @@ namespace chaiscript {
|
||||
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>
|
||||
Type_Conversion vector_conversion() {
|
||||
auto func = [](const Boxed_Value &t_bv) -> Boxed_Value {
|
||||
@ -529,7 +506,7 @@ namespace chaiscript {
|
||||
To vec;
|
||||
vec.reserve(from_vec.size());
|
||||
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));
|
||||
@ -565,8 +542,9 @@ namespace chaiscript {
|
||||
= detail::Cast_Helper<const std::pair<Boxed_Value, Boxed_Value> &>::cast(t_bv, nullptr);
|
||||
|
||||
auto pair = std::make_pair(
|
||||
detail::Cast_Helper<Left>::cast(from_pair.first, nullptr),
|
||||
detail::Cast_Helper<Right>::cast(from_pair.second, nullptr));
|
||||
detail::Cast_Helper<Left>::cast(from_pair.first, nullptr),
|
||||
detail::Cast_Helper<Right>::cast(from_pair.second, nullptr)
|
||||
);
|
||||
|
||||
return Boxed_Value(std::move(pair));
|
||||
};
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
|
||||
#include "../utility/hash.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace chaiscript {
|
||||
@ -53,10 +52,10 @@ namespace chaiscript {
|
||||
invalid
|
||||
};
|
||||
|
||||
constexpr static std::string_view to_string(Opers t_oper) noexcept {
|
||||
constexpr const std::array opers
|
||||
constexpr static const char *to_string(Opers t_oper) noexcept {
|
||||
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 {
|
||||
|
||||
@ -33,7 +33,7 @@ namespace chaiscript {
|
||||
template<typename T>
|
||||
static bool is_reserved_word(const T &s) noexcept {
|
||||
const static std::unordered_set<std::uint32_t>
|
||||
words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const"), utility::hash("using"), 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;
|
||||
}
|
||||
@ -106,10 +106,7 @@ namespace chaiscript {
|
||||
Constant,
|
||||
Compiled,
|
||||
Const_Var_Decl,
|
||||
Const_Assign_Decl,
|
||||
Using,
|
||||
Enum,
|
||||
Namespace_Block
|
||||
Const_Assign_Decl
|
||||
};
|
||||
|
||||
enum class Operator_Precedence {
|
||||
@ -130,9 +127,9 @@ namespace chaiscript {
|
||||
namespace {
|
||||
/// Helper lookup to get the name of each node type
|
||||
constexpr const char *ast_node_type_to_string(AST_Node_Type ast_node_type) noexcept {
|
||||
constexpr 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
|
||||
|
||||
@ -593,13 +590,13 @@ namespace chaiscript {
|
||||
|
||||
} // namespace exception
|
||||
|
||||
// static
|
||||
//static
|
||||
bool AST_Node::get_bool_condition(const Boxed_Value &t_bv, const chaiscript::detail::Dispatch_State &t_ss) {
|
||||
try {
|
||||
return t_ss->boxed_cast<bool>(t_bv);
|
||||
} catch (const exception::bad_boxed_cast &) {
|
||||
throw exception::eval_error("Condition not boolean");
|
||||
}
|
||||
try {
|
||||
return t_ss->boxed_cast<bool>(t_bv);
|
||||
} catch (const exception::bad_boxed_cast &) {
|
||||
throw exception::eval_error("Condition not boolean");
|
||||
}
|
||||
}
|
||||
|
||||
namespace parser {
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@ -79,9 +78,6 @@ namespace chaiscript {
|
||||
|
||||
std::map<std::string, std::function<Namespace &()>> m_namespace_generators;
|
||||
|
||||
std::function<void(const std::string &)> m_print_handler = [](const std::string &) noexcept {};
|
||||
std::function<std::string(const std::string &)> m_file_reader;
|
||||
|
||||
/// Evaluates the given string in by parsing it and running the results through the evaluator
|
||||
Boxed_Value do_eval(const std::string &t_input, const std::string &t_filename = "__EVAL__", bool /* t_internal*/ = false) {
|
||||
try {
|
||||
@ -122,30 +118,11 @@ namespace chaiscript {
|
||||
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.
|
||||
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) {
|
||||
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](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");
|
||||
@ -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) { 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) {
|
||||
if (!m_namespace_generators.count(t_namespace_name)) {
|
||||
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
||||
}
|
||||
const auto sep_pos = t_namespace_name.find("::");
|
||||
const std::string root_name = (sep_pos != std::string::npos) ? t_namespace_name.substr(0, sep_pos) : t_namespace_name;
|
||||
if (!m_engine.get_scripting_objects().count(root_name)) {
|
||||
import(root_name);
|
||||
} else if (m_namespace_generators.count(root_name)) {
|
||||
nest_children(root_name, m_namespace_generators[root_name]());
|
||||
}
|
||||
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
||||
import(t_namespace_name);
|
||||
}),
|
||||
"namespace");
|
||||
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
|
||||
@ -250,15 +201,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
/// Helper function for loading a file
|
||||
std::string load_file(const std::string &t_filename) const {
|
||||
if (m_file_reader) {
|
||||
return m_file_reader(t_filename);
|
||||
}
|
||||
|
||||
return load_file_default(t_filename);
|
||||
}
|
||||
|
||||
static std::string load_file_default(const std::string &t_filename) {
|
||||
static std::string load_file(const std::string &t_filename) {
|
||||
std::ifstream infile(t_filename.c_str(), std::ios::in | std::ios::ate | std::ios::binary);
|
||||
|
||||
if (!infile.is_open()) {
|
||||
@ -293,21 +236,10 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
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
|
||||
virtual ~ChaiScript_Basic() = default;
|
||||
|
||||
|
||||
/// \brief Constructor for ChaiScript
|
||||
/// \param[in] t_lib Standard library to apply to this ChaiScript instance
|
||||
/// \param[in] t_modulepaths Vector of paths to search when attempting to load a binary module
|
||||
@ -316,8 +248,7 @@ namespace chaiscript {
|
||||
std::unique_ptr<parser::ChaiScript_Parser_Base> &&parser,
|
||||
std::vector<std::string> t_module_paths = {},
|
||||
std::vector<std::string> t_use_paths = {},
|
||||
const std::vector<chaiscript::Options> &t_opts = chaiscript::default_options(),
|
||||
const bool t_no_io = false)
|
||||
const std::vector<chaiscript::Options> &t_opts = chaiscript::default_options())
|
||||
: 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_parser(std::move(parser))
|
||||
@ -352,7 +283,7 @@ namespace chaiscript {
|
||||
m_module_paths.insert(m_module_paths.begin(), dllpath + "/");
|
||||
}
|
||||
#endif
|
||||
build_eval_system(t_lib, t_opts, t_no_io);
|
||||
build_eval_system(t_lib, t_opts);
|
||||
}
|
||||
|
||||
#ifndef CHAISCRIPT_NO_DYNLOAD
|
||||
@ -611,7 +542,7 @@ namespace chaiscript {
|
||||
/// (the symbol mentioned above), an exception is thrown.
|
||||
///
|
||||
/// \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
|
||||
throw chaiscript::exception::load_module_error("Loadable module support was disabled (CHAISCRIPT_NO_DYNLOAD)");
|
||||
#else
|
||||
@ -757,59 +688,28 @@ namespace chaiscript {
|
||||
if (m_engine.get_scripting_objects().count(t_namespace_name)) {
|
||||
throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined");
|
||||
} else if (m_namespace_generators.count(t_namespace_name)) {
|
||||
auto &ns = m_namespace_generators[t_namespace_name]();
|
||||
nest_children(t_namespace_name, ns);
|
||||
m_engine.add_global(var(std::ref(ns)), t_namespace_name);
|
||||
m_engine.add_global(var(std::ref(m_namespace_generators[t_namespace_name]())), t_namespace_name);
|
||||
} else {
|
||||
throw std::runtime_error("No registered namespace: " + t_namespace_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Registers a namespace generator, which delays generation of the namespace until it is imported, saving memory if it is never
|
||||
/// used. Supports C++-style nested names (e.g. "constants::si") for nested namespaces; parent namespaces are auto-registered if absent.
|
||||
/// \param[in] t_namespace_generator Namespace generator function.
|
||||
/// \param[in] t_namespace_name Name of the Namespace function being registered (may contain :: for nesting).
|
||||
/// \throw std::runtime_error In the case that the namespace name was already registered.
|
||||
/// used. \param[in] t_namespace_generator Namespace generator function. \param[in] t_namespace_name Name of the Namespace function
|
||||
/// being registered. \throw std::runtime_error In the case that the namespace name was already registered.
|
||||
void register_namespace(const std::function<void(Namespace &)> &t_namespace_generator, const std::string &t_namespace_name) {
|
||||
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
|
||||
|
||||
if (m_namespace_generators.count(t_namespace_name)) {
|
||||
if (!m_namespace_generators.count(t_namespace_name)) {
|
||||
// contain the namespace object memory within the m_namespace_generators map
|
||||
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable -> Namespace & {
|
||||
t_namespace_generator(space);
|
||||
return space;
|
||||
}));
|
||||
} else {
|
||||
throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered.");
|
||||
}
|
||||
|
||||
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable noexcept -> Namespace & {
|
||||
t_namespace_generator(space);
|
||||
return space;
|
||||
}));
|
||||
|
||||
auto pos = t_namespace_name.rfind("::");
|
||||
while (pos != std::string::npos) {
|
||||
const std::string parent = t_namespace_name.substr(0, pos);
|
||||
if (!m_namespace_generators.count(parent)) {
|
||||
m_namespace_generators.emplace(std::make_pair(parent, [space = Namespace()]() mutable 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
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#ifndef CHAISCRIPT_EVAL_HPP_
|
||||
#define CHAISCRIPT_EVAL_HPP_
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
@ -109,169 +108,6 @@ namespace chaiscript {
|
||||
return incoming;
|
||||
}
|
||||
}
|
||||
class Strong_Typedef_Binary_Op final : public dispatch::Proxy_Function_Base {
|
||||
public:
|
||||
Strong_Typedef_Binary_Op(
|
||||
std::string t_type_name,
|
||||
std::string t_op_name,
|
||||
Operators::Opers t_oper,
|
||||
bool t_rewrap,
|
||||
chaiscript::detail::Dispatch_Engine &t_engine)
|
||||
: Proxy_Function_Base(
|
||||
{chaiscript::detail::Get_Type_Info<Boxed_Value>::get(),
|
||||
user_type<dispatch::Dynamic_Object>(),
|
||||
user_type<dispatch::Dynamic_Object>()},
|
||||
2)
|
||||
, m_type_name(std::move(t_type_name))
|
||||
, m_op_name(std::move(t_op_name))
|
||||
, m_oper(t_oper)
|
||||
, m_rewrap(t_rewrap)
|
||||
, m_engine(t_engine) {
|
||||
}
|
||||
|
||||
bool operator==(const Proxy_Function_Base &f) const noexcept override {
|
||||
if (const auto *other = dynamic_cast<const Strong_Typedef_Binary_Op *>(&f)) {
|
||||
return m_type_name == other->m_type_name && m_op_name == other->m_op_name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override {
|
||||
return vals.size() == 2
|
||||
&& type_matches(vals[0], t_conversions)
|
||||
&& type_matches(vals[1], t_conversions);
|
||||
}
|
||||
|
||||
protected:
|
||||
Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override {
|
||||
if (!call_match(params, t_conversions)) {
|
||||
throw chaiscript::exception::guard_error();
|
||||
}
|
||||
|
||||
const auto &lhs = boxed_cast<const dispatch::Dynamic_Object &>(params[0], &t_conversions);
|
||||
const auto &rhs = boxed_cast<const dispatch::Dynamic_Object &>(params[1], &t_conversions);
|
||||
const auto lhs_val = lhs.get_attr("__value");
|
||||
const auto rhs_val = rhs.get_attr("__value");
|
||||
|
||||
Boxed_Value result;
|
||||
if (m_oper != Operators::Opers::invalid
|
||||
&& lhs_val.get_type_info().is_arithmetic()
|
||||
&& rhs_val.get_type_info().is_arithmetic()) {
|
||||
result = Boxed_Number::do_oper(m_oper, lhs_val, rhs_val);
|
||||
} else {
|
||||
std::array<Boxed_Value, 2> underlying_params{lhs_val, rhs_val};
|
||||
result = m_engine.call_function(m_op_name, m_loc, Function_Params(underlying_params), t_conversions);
|
||||
}
|
||||
|
||||
if (m_rewrap) {
|
||||
auto bv = Boxed_Value(dispatch::Dynamic_Object(m_type_name), true);
|
||||
auto *obj = static_cast<dispatch::Dynamic_Object *>(bv.get_ptr());
|
||||
obj->get_attr("__value") = result;
|
||||
return bv;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept {
|
||||
if (!bv.get_type_info().bare_equal(user_type<dispatch::Dynamic_Object>())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const auto &d = boxed_cast<const dispatch::Dynamic_Object &>(bv, &t_conversions);
|
||||
return d.get_type_name() == m_type_name;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_type_name;
|
||||
std::string m_op_name;
|
||||
Operators::Opers m_oper;
|
||||
bool m_rewrap;
|
||||
chaiscript::detail::Dispatch_Engine &m_engine;
|
||||
mutable std::atomic_uint_fast32_t m_loc{0};
|
||||
};
|
||||
|
||||
class Strong_Typedef_Compound_Assign_Op final : public dispatch::Proxy_Function_Base {
|
||||
public:
|
||||
Strong_Typedef_Compound_Assign_Op(
|
||||
std::string t_type_name,
|
||||
std::string t_op_name,
|
||||
Operators::Opers t_base_oper,
|
||||
std::string t_base_op_name,
|
||||
chaiscript::detail::Dispatch_Engine &t_engine)
|
||||
: Proxy_Function_Base(
|
||||
{user_type<dispatch::Dynamic_Object>(),
|
||||
user_type<dispatch::Dynamic_Object>(),
|
||||
user_type<dispatch::Dynamic_Object>()},
|
||||
2)
|
||||
, m_type_name(std::move(t_type_name))
|
||||
, m_op_name(std::move(t_op_name))
|
||||
, m_base_oper(t_base_oper)
|
||||
, m_base_op_name(std::move(t_base_op_name))
|
||||
, m_engine(t_engine) {
|
||||
}
|
||||
|
||||
bool operator==(const Proxy_Function_Base &f) const noexcept override {
|
||||
if (const auto *other = dynamic_cast<const Strong_Typedef_Compound_Assign_Op *>(&f)) {
|
||||
return m_type_name == other->m_type_name && m_op_name == other->m_op_name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override {
|
||||
return vals.size() == 2
|
||||
&& type_matches(vals[0], t_conversions)
|
||||
&& type_matches(vals[1], t_conversions);
|
||||
}
|
||||
|
||||
protected:
|
||||
Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override {
|
||||
if (!call_match(params, t_conversions)) {
|
||||
throw chaiscript::exception::guard_error();
|
||||
}
|
||||
|
||||
auto &lhs = boxed_cast<dispatch::Dynamic_Object &>(params[0], &t_conversions);
|
||||
const auto &rhs = boxed_cast<const dispatch::Dynamic_Object &>(params[1], &t_conversions);
|
||||
const auto lhs_val = lhs.get_attr("__value");
|
||||
const auto rhs_val = rhs.get_attr("__value");
|
||||
|
||||
Boxed_Value result;
|
||||
if (m_base_oper != Operators::Opers::invalid
|
||||
&& lhs_val.get_type_info().is_arithmetic()
|
||||
&& rhs_val.get_type_info().is_arithmetic()) {
|
||||
result = Boxed_Number::do_oper(m_base_oper, lhs_val, rhs_val);
|
||||
} else {
|
||||
std::array<Boxed_Value, 2> underlying_params{lhs_val, rhs_val};
|
||||
result = m_engine.call_function(m_base_op_name, m_loc, Function_Params(underlying_params), t_conversions);
|
||||
}
|
||||
|
||||
lhs.get_attr("__value") = result;
|
||||
return params[0];
|
||||
}
|
||||
|
||||
private:
|
||||
bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept {
|
||||
if (!bv.get_type_info().bare_equal(user_type<dispatch::Dynamic_Object>())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const auto &d = boxed_cast<const dispatch::Dynamic_Object &>(bv, &t_conversions);
|
||||
return d.get_type_name() == m_type_name;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_type_name;
|
||||
std::string m_op_name;
|
||||
Operators::Opers m_base_oper;
|
||||
std::string m_base_op_name;
|
||||
chaiscript::detail::Dispatch_Engine &m_engine;
|
||||
mutable std::atomic_uint_fast32_t m_loc{0};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<typename T>
|
||||
@ -389,11 +225,11 @@ namespace chaiscript {
|
||||
|
||||
// static and public so we can use this to process Switch_AST_Node case equality
|
||||
static Boxed_Value do_oper(const chaiscript::detail::Dispatch_State &t_ss,
|
||||
Operators::Opers t_oper,
|
||||
const std::string &t_oper_string,
|
||||
const Boxed_Value &t_lhs,
|
||||
const Boxed_Value &t_rhs,
|
||||
std::atomic_uint_fast32_t &t_loc) {
|
||||
Operators::Opers t_oper,
|
||||
const std::string &t_oper_string,
|
||||
const Boxed_Value &t_lhs,
|
||||
const Boxed_Value &t_rhs,
|
||||
std::atomic_uint_fast32_t &t_loc) {
|
||||
try {
|
||||
if (t_oper != Operators::Opers::invalid && t_lhs.get_type_info().is_arithmetic() && t_rhs.get_type_info().is_arithmetic()) {
|
||||
// If it's an arithmetic operation we want to short circuit dispatch
|
||||
@ -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()) {
|
||||
try {
|
||||
return Boxed_Number::do_oper(m_oper, params[0], params[1]);
|
||||
} catch (const chaiscript::exception::arithmetic_error &) {
|
||||
throw;
|
||||
} catch (const std::exception &) {
|
||||
throw exception::eval_error("Error with unsupported arithmetic assignment operation.");
|
||||
}
|
||||
@ -952,43 +786,40 @@ namespace chaiscript {
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::shared_ptr<dispatch::Proxy_Function_Base> make_proxy_function(
|
||||
const Def_AST_Node<T> &t_node, const chaiscript::detail::Dispatch_State &t_ss) {
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
std::vector<std::string> t_param_names;
|
||||
size_t numparams = 0;
|
||||
|
||||
dispatch::Param_Types param_types;
|
||||
|
||||
if ((t_node.children.size() > 1) && (t_node.children[1]->identifier == AST_Node_Type::Arg_List)) {
|
||||
numparams = t_node.children[1]->children.size();
|
||||
t_param_names = Arg_List_AST_Node<T>::get_arg_names(*t_node.children[1]);
|
||||
param_types = Arg_List_AST_Node<T>::get_arg_types(*t_node.children[1], t_ss);
|
||||
if ((this->children.size() > 1) && (this->children[1]->identifier == AST_Node_Type::Arg_List)) {
|
||||
numparams = this->children[1]->children.size();
|
||||
t_param_names = Arg_List_AST_Node<T>::get_arg_names(*this->children[1]);
|
||||
param_types = Arg_List_AST_Node<T>::get_arg_types(*this->children[1], t_ss);
|
||||
}
|
||||
|
||||
std::reference_wrapper<chaiscript::detail::Dispatch_Engine> engine(*t_ss);
|
||||
std::shared_ptr<dispatch::Proxy_Function_Base> guard;
|
||||
if (t_node.m_guard_node) {
|
||||
if (m_guard_node) {
|
||||
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);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
t_node.m_guard_node);
|
||||
m_guard_node);
|
||||
}
|
||||
|
||||
return dispatch::make_dynamic_proxy_function(
|
||||
[engine, func_node = t_node.m_body_node, t_param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *func_node, t_param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
t_node.m_body_node,
|
||||
param_types,
|
||||
guard);
|
||||
}
|
||||
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
try {
|
||||
t_ss->add(make_proxy_function(*this, t_ss), this->children[0]->text);
|
||||
const std::string &l_function_name = this->children[0]->text;
|
||||
t_ss->add(dispatch::make_dynamic_proxy_function(
|
||||
[engine, func_node = m_body_node, t_param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *func_node, t_param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
m_body_node,
|
||||
param_types,
|
||||
guard),
|
||||
l_function_name);
|
||||
} catch (const exception::name_conflict_error &e) {
|
||||
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>
|
||||
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)
|
||||
@ -1350,7 +919,7 @@ namespace chaiscript {
|
||||
};
|
||||
|
||||
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;
|
||||
@ -1646,8 +1215,8 @@ namespace chaiscript {
|
||||
return Boxed_Number::do_oper(m_oper, bv);
|
||||
} else {
|
||||
chaiscript::eval::detail::Function_Push_Pop fpp(t_ss);
|
||||
fpp.save_params(Function_Params{&bv, 1});
|
||||
return t_ss->call_function(this->text, m_loc, Function_Params{&bv, 1}, t_ss.conversions());
|
||||
fpp.save_params(Function_Params{bv});
|
||||
return t_ss->call_function(this->text, m_loc, Function_Params{bv}, t_ss.conversions());
|
||||
}
|
||||
} catch (const exception::dispatch_error &e) {
|
||||
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 retval;
|
||||
bool handled = false;
|
||||
|
||||
size_t end_point = this->children.size();
|
||||
if (this->children.back()->identifier == AST_Node_Type::Finally) {
|
||||
@ -1748,33 +1316,30 @@ namespace chaiscript {
|
||||
if (catch_block.children.size() == 1) {
|
||||
// No variable capture
|
||||
retval = catch_block.children[0]->eval(t_ss);
|
||||
handled = true;
|
||||
break;
|
||||
} 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]);
|
||||
|
||||
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)})
|
||||
.match(Function_Params{&t_except, 1}, t_ss.conversions())
|
||||
.match(Function_Params{t_except}, t_ss.conversions())
|
||||
.first) {
|
||||
t_ss.add_object(name, t_except);
|
||||
|
||||
if (catch_block.children.size() == 2) {
|
||||
// Variable capture
|
||||
retval = catch_block.children[1]->eval(t_ss);
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
throw;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -1784,19 +1349,17 @@ namespace chaiscript {
|
||||
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
|
||||
|
||||
try {
|
||||
try {
|
||||
retval = this->children[0]->eval(t_ss);
|
||||
} catch (const exception::eval_error &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (const std::runtime_error &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (const std::out_of_range &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (const std::exception &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (Boxed_Value &e) {
|
||||
retval = handle_exception(t_ss, e);
|
||||
}
|
||||
retval = this->children[0]->eval(t_ss);
|
||||
} catch (const exception::eval_error &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (const std::runtime_error &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (const std::out_of_range &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (const std::exception &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (Boxed_Value &e) {
|
||||
retval = handle_exception(t_ss, e);
|
||||
} catch (...) {
|
||||
if (this->children.back()->identifier == AST_Node_Type::Finally) {
|
||||
this->children.back()->children[0]->eval(t_ss);
|
||||
|
||||
@ -397,9 +397,8 @@ namespace chaiscript {
|
||||
assert(children.size() == 1);
|
||||
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
|
||||
|
||||
Boxed_Value bv_i(start_int);
|
||||
auto &i = *static_cast<int *>(bv_i.get_ptr());
|
||||
t_ss.add_object(id, bv_i);
|
||||
int i = start_int;
|
||||
t_ss.add_object(id, var(&i));
|
||||
|
||||
try {
|
||||
for (; i < end_int; ++i) {
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@ -23,11 +22,15 @@
|
||||
#include "../dispatchkit/boxed_value.hpp"
|
||||
#include "../utility/hash.hpp"
|
||||
#include "../utility/static_string.hpp"
|
||||
#include "../utility/unicode.hpp"
|
||||
#include "chaiscript_common.hpp"
|
||||
#include "chaiscript_optimizer.hpp"
|
||||
#include "chaiscript_tracer.hpp"
|
||||
|
||||
#if defined(CHAISCRIPT_UTF16_UTF32)
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#endif
|
||||
|
||||
#if defined(CHAISCRIPT_MSVC) && defined(max) && defined(min)
|
||||
#define CHAISCRIPT_PUSHED_MIN_MAX
|
||||
#pragma push_macro("max") // Why Microsoft? why? This is worse than bad
|
||||
@ -61,26 +64,41 @@ namespace chaiscript {
|
||||
// Generic for u16, u32 and wchar
|
||||
template<typename string_type>
|
||||
struct Char_Parser_Helper {
|
||||
static string_type str_from_ll(long long val) {
|
||||
string_type out;
|
||||
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) {
|
||||
// common for all implementations
|
||||
static std::string u8str_from_ll(long long val) {
|
||||
using char_type = std::string::value_type;
|
||||
|
||||
char_type c[2];
|
||||
c[1] = char_type(val);
|
||||
c[0] = char_type(val >> 8);
|
||||
|
||||
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
|
||||
@ -110,7 +128,9 @@ namespace chaiscript {
|
||||
|
||||
template<typename Array2D, typename First, typename Second>
|
||||
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 {
|
||||
@ -340,13 +360,13 @@ namespace chaiscript {
|
||||
++col;
|
||||
}
|
||||
|
||||
std::advance(m_pos, 1);
|
||||
++m_pos;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Position &operator--() noexcept {
|
||||
std::advance(m_pos, -1);
|
||||
--m_pos;
|
||||
if (*m_pos == '\n') {
|
||||
--line;
|
||||
col = m_last_col;
|
||||
@ -357,7 +377,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
constexpr Position &operator+=(size_t t_distance) noexcept {
|
||||
*this = *this + t_distance;
|
||||
*this = (*this) + t_distance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -386,9 +406,9 @@ namespace chaiscript {
|
||||
|
||||
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 {
|
||||
if (m_pos == m_end) {
|
||||
@ -438,7 +458,7 @@ namespace chaiscript {
|
||||
constexpr static Operator_Matches m_operator_matches{};
|
||||
|
||||
/// 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
|
||||
void debug_print(const AST_Node &t, std::string prepend = "") const override {
|
||||
@ -489,7 +509,7 @@ namespace chaiscript {
|
||||
if (m_position.remaining() >= len) {
|
||||
const char *file_pos = &(*m_position);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -510,9 +530,20 @@ namespace chaiscript {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Symbol_(m_singleline_comment) || Symbol_(m_annotation)) {
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (Symbol_(m_annotation)) {
|
||||
while (m_position.has_more()) {
|
||||
if (Symbol_(m_cr_lf)) {
|
||||
m_position -= 2;
|
||||
@ -1029,7 +1060,7 @@ namespace chaiscript {
|
||||
template<typename string_type>
|
||||
struct Char_Parser {
|
||||
string_type &match;
|
||||
using char_type = string_type::value_type;
|
||||
using char_type = typename string_type::value_type;
|
||||
bool is_escaped = false;
|
||||
bool is_interpolated = false;
|
||||
bool saw_interpolation_marker = false;
|
||||
@ -1088,20 +1119,40 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
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();
|
||||
hex_matches.clear();
|
||||
is_escaped = false;
|
||||
const auto u_size = unicode_size;
|
||||
unicode_size = 0;
|
||||
|
||||
char buf[4];
|
||||
if (u_size != match_size) {
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -1497,7 +1548,7 @@ namespace chaiscript {
|
||||
if (m_position.remaining() >= len) {
|
||||
auto tmp = m_position;
|
||||
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;
|
||||
}
|
||||
++tmp;
|
||||
@ -1524,7 +1575,7 @@ namespace chaiscript {
|
||||
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
|
||||
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
|
||||
bool Namespace_Block() {
|
||||
Depth_Counter dc{this};
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
const auto prev_pos = m_position;
|
||||
|
||||
if (Keyword("namespace")) {
|
||||
if (Id(true)) {
|
||||
std::string ns_name = m_match_stack.back()->text;
|
||||
|
||||
while (Symbol("::")) {
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Incomplete namespace name after '::'",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
ns_name += "::" + m_match_stack.back()->text;
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.back() = make_node<eval::Id_AST_Node<Tracer>>(ns_name, prev_pos.line, prev_pos.col);
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (Block()) {
|
||||
build_match<eval::Namespace_Block_AST_Node<Tracer>>(prev_stack_top);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_position = prev_pos;
|
||||
while (prev_stack_top != m_match_stack.size()) {
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Class(const bool t_class_allowed) {
|
||||
Depth_Counter dc{this};
|
||||
bool retval = false;
|
||||
@ -2018,134 +2031,6 @@ namespace chaiscript {
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool Using(const bool t_class_allowed) {
|
||||
Depth_Counter dc{this};
|
||||
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
if (Keyword("using")) {
|
||||
if (!t_class_allowed) {
|
||||
throw exception::eval_error("Type alias definitions only allowed at top scope",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing type name in 'using' declaration",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
if (!Symbol("=", true)) {
|
||||
throw exception::eval_error("Missing '=' in 'using' declaration",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing base type name in 'using' declaration",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
build_match<eval::Using_AST_Node<Tracer>>(prev_stack_top);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Enum(const bool t_allowed) {
|
||||
Depth_Counter dc{this};
|
||||
bool retval = false;
|
||||
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
if (Keyword("enum")) {
|
||||
if (!Keyword("class") && !Keyword("struct")) {
|
||||
throw exception::eval_error("Expected 'class' or 'struct' after 'enum' (only 'enum class'/'enum struct' is supported)",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
if (!t_allowed) {
|
||||
throw exception::eval_error("Enum definitions only allowed at top scope",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
retval = true;
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing enum class name in definition", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
std::string underlying_type = "int";
|
||||
if (Char(':')) {
|
||||
if (!Id(false)) {
|
||||
throw exception::eval_error("Expected underlying type after ':'",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
underlying_type = m_match_stack.back()->text;
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.push_back(
|
||||
make_node<eval::Constant_AST_Node<Tracer>>(underlying_type, m_position.line, m_position.col, const_var(underlying_type)));
|
||||
|
||||
if (!Char('{')) {
|
||||
throw exception::eval_error("Expected '{' after enum class declaration", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
int next_value = 0;
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (!Char('}')) {
|
||||
do {
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Expected enum value name", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
if (Symbol("=")) {
|
||||
if (!Num()) {
|
||||
throw exception::eval_error("Expected integer after '=' in enum definition",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
next_value = static_cast<int>(std::stoi(m_match_stack.back()->text));
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.push_back(
|
||||
make_node<eval::Constant_AST_Node<Tracer>>(std::to_string(next_value), m_position.line, m_position.col, const_var(next_value)));
|
||||
++next_value;
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
} while (Char(',') && !Char('}'));
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (!Char('}')) {
|
||||
throw exception::eval_error("Expected '}' to close enum class definition",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
}
|
||||
|
||||
build_match<eval::Enum_AST_Node<Tracer>>(prev_stack_top);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// Reads a while block from input
|
||||
bool While() {
|
||||
Depth_Counter dc{this};
|
||||
@ -2397,7 +2282,7 @@ namespace chaiscript {
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
if (Keyword("return")) {
|
||||
Equation();
|
||||
Operator();
|
||||
build_match<eval::Return_AST_Node<Tracer>>(prev_stack_top);
|
||||
return true;
|
||||
} else {
|
||||
@ -2494,7 +2379,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
build_match<eval::Array_Call_AST_Node<Tracer>>(prev_stack_top);
|
||||
} else if (Symbol(".") || Symbol("::")) {
|
||||
} else if (Symbol(".")) {
|
||||
has_more = true;
|
||||
if (!(Id(true))) {
|
||||
throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
@ -2698,7 +2583,7 @@ namespace chaiscript {
|
||||
bool retval = false;
|
||||
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)) {
|
||||
retval = true;
|
||||
std::string oper;
|
||||
@ -2712,7 +2597,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
switch (m_operators[t_precedence]) {
|
||||
case Operator_Precedence::Ternary_Cond:
|
||||
case (Operator_Precedence::Ternary_Cond):
|
||||
if (Symbol(":")) {
|
||||
if (!Operator(t_precedence + 1)) {
|
||||
throw exception::eval_error("Incomplete '" + oper + "' expression",
|
||||
@ -2727,24 +2612,24 @@ namespace chaiscript {
|
||||
}
|
||||
break;
|
||||
|
||||
case Operator_Precedence::Addition:
|
||||
case Operator_Precedence::Multiplication:
|
||||
case Operator_Precedence::Shift:
|
||||
case Operator_Precedence::Equality:
|
||||
case Operator_Precedence::Bitwise_And:
|
||||
case Operator_Precedence::Bitwise_Xor:
|
||||
case Operator_Precedence::Bitwise_Or:
|
||||
case Operator_Precedence::Comparison:
|
||||
case (Operator_Precedence::Addition):
|
||||
case (Operator_Precedence::Multiplication):
|
||||
case (Operator_Precedence::Shift):
|
||||
case (Operator_Precedence::Equality):
|
||||
case (Operator_Precedence::Bitwise_And):
|
||||
case (Operator_Precedence::Bitwise_Xor):
|
||||
case (Operator_Precedence::Bitwise_Or):
|
||||
case (Operator_Precedence::Comparison):
|
||||
build_match<eval::Binary_Operator_AST_Node<Tracer>>(prev_stack_top, oper);
|
||||
break;
|
||||
|
||||
case Operator_Precedence::Logical_And:
|
||||
case (Operator_Precedence::Logical_And):
|
||||
build_match<eval::Logical_And_AST_Node<Tracer>>(prev_stack_top, oper);
|
||||
break;
|
||||
case Operator_Precedence::Logical_Or:
|
||||
case (Operator_Precedence::Logical_Or):
|
||||
build_match<eval::Logical_Or_AST_Node<Tracer>>(prev_stack_top, oper);
|
||||
break;
|
||||
case Operator_Precedence::Prefix:
|
||||
case (Operator_Precedence::Prefix):
|
||||
assert(false); // cannot reach here because of if() statement at the top
|
||||
break;
|
||||
|
||||
@ -2891,7 +2776,7 @@ namespace chaiscript {
|
||||
|
||||
while (has_more) {
|
||||
const auto start = m_position;
|
||||
if (Def() || Try() || If() || While() || Namespace_Block() || Class(t_class_allowed) || Using(t_class_allowed) || Enum(t_class_allowed) || For() || Switch()) {
|
||||
if (Def() || Try() || If() || While() || Class(t_class_allowed) || For() || Switch()) {
|
||||
if (!saw_eol) {
|
||||
throw exception::eval_error("Two function definitions missing line separator",
|
||||
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.
|
||||
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 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_filename = std::make_shared<std::string>(std::move(t_fname));
|
||||
|
||||
|
||||
@ -8,9 +8,7 @@
|
||||
#define CHAISCRIPT_UTILITY_FNV1A_HPP_
|
||||
|
||||
#include "../chaiscript_defines.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
|
||||
namespace chaiscript {
|
||||
namespace utility {
|
||||
@ -30,7 +28,7 @@ namespace chaiscript {
|
||||
|
||||
while (begin != end) {
|
||||
h = (h ^ (*begin)) * 0x01000193;
|
||||
std::advance(begin, 1);
|
||||
++begin;
|
||||
}
|
||||
return h;
|
||||
|
||||
@ -45,7 +43,7 @@ namespace chaiscript {
|
||||
|
||||
template<size_t N>
|
||||
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 {
|
||||
@ -66,7 +64,7 @@ namespace chaiscript {
|
||||
hash += std::uint32_t(*begin);
|
||||
hash += hash << 10;
|
||||
hash ^= hash >> 6;
|
||||
std::advance(begin, 1);
|
||||
++begin;
|
||||
}
|
||||
|
||||
hash += hash << 3;
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
#include "../chaiscript_defines.hpp"
|
||||
#include "quick_flat_map.hpp"
|
||||
#include "unicode.hpp"
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#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>;
|
||||
|
||||
struct Internal {
|
||||
explicit Internal(std::nullptr_t) {}
|
||||
Internal() = default;
|
||||
Internal(const Class c)
|
||||
Internal(std::nullptr_t)
|
||||
: d(nullptr) {
|
||||
}
|
||||
Internal()
|
||||
: d(nullptr) {
|
||||
}
|
||||
Internal(Class c)
|
||||
: d(make_type(c)) {
|
||||
}
|
||||
template<typename T>
|
||||
explicit Internal(T t)
|
||||
Internal(T t)
|
||||
: d(std::move(t)) {
|
||||
}
|
||||
|
||||
static Data make_type(const Class c) {
|
||||
static Data make_type(Class c) {
|
||||
switch (c) {
|
||||
case Class::Null:
|
||||
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>
|
||||
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 &Bool() { return get_set_type<Class::Boolean>(); }
|
||||
|
||||
[[nodiscard]] 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); }
|
||||
[[nodiscard]] 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); }
|
||||
[[nodiscard]] 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 Map() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Object)>(&d); }
|
||||
auto Vector() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Array)>(&d); }
|
||||
auto String() const noexcept { return std::get_if<static_cast<std::size_t>(Class::String)>(&d); }
|
||||
auto Int() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Integral)>(&d); }
|
||||
auto Float() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Floating)>(&d); }
|
||||
auto Bool() const noexcept { return std::get_if<static_cast<std::size_t>(Class::Boolean)>(&d); }
|
||||
|
||||
Data d{nullptr};
|
||||
Data d;
|
||||
};
|
||||
|
||||
Internal internal;
|
||||
@ -128,10 +131,10 @@ namespace chaiscript::json {
|
||||
}
|
||||
JSONWrapper(std::nullptr_t) {}
|
||||
|
||||
Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); }
|
||||
Container::iterator end() { return object ? object->end() : typename Container::iterator(); }
|
||||
[[nodiscard]] 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::iterator begin() { return object ? object->begin() : typename Container::iterator(); }
|
||||
typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); }
|
||||
typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); }
|
||||
typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); }
|
||||
};
|
||||
|
||||
template<typename Container>
|
||||
@ -144,10 +147,10 @@ namespace chaiscript::json {
|
||||
}
|
||||
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();
|
||||
}
|
||||
[[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;
|
||||
@ -159,9 +162,7 @@ namespace chaiscript::json {
|
||||
|
||||
JSON(initializer_list<JSON> list)
|
||||
: internal(Class::Object) {
|
||||
for (auto i = list.begin(), e = list.end();
|
||||
i != e;
|
||||
std::advance(i, 2)) {
|
||||
for (auto i = list.begin(), e = list.end(); i != e; ++i, ++i) {
|
||||
operator[](i->to_string()) = *std::next(i);
|
||||
}
|
||||
}
|
||||
@ -470,8 +471,22 @@ namespace chaiscript::json {
|
||||
}
|
||||
}
|
||||
offset += 4;
|
||||
const auto ch = static_cast<std::uint32_t>(std::stoi(hex_matches, nullptr, 16));
|
||||
if (chaiscript::utility::unicode::append_utf8(val, ch) == 0) {
|
||||
const auto ch = static_cast<uint32_t>(std::stoi(hex_matches, nullptr, 16));
|
||||
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"));
|
||||
}
|
||||
} break;
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#ifndef CHAISCRIPT_SIMPLEJSON_WRAP_HPP
|
||||
#define CHAISCRIPT_SIMPLEJSON_WRAP_HPP
|
||||
|
||||
#include "../dispatchkit/dynamic_object.hpp"
|
||||
#include "json.hpp"
|
||||
|
||||
namespace chaiscript {
|
||||
@ -9,10 +8,7 @@ namespace chaiscript {
|
||||
public:
|
||||
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 object_from_json(t_str); }), "object_from_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;
|
||||
}
|
||||
@ -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 json::JSON to_json_object(const Boxed_Value &t_bv) {
|
||||
|
||||
@ -7,8 +7,6 @@
|
||||
#ifndef CHAISCRIPT_UTILITY_STATIC_STRING_HPP_
|
||||
#define CHAISCRIPT_UTILITY_STATIC_STRING_HPP_
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace chaiscript::utility {
|
||||
struct Static_String {
|
||||
template<size_t N>
|
||||
@ -23,17 +21,30 @@ namespace chaiscript::utility {
|
||||
|
||||
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 operator[](const std::size_t idx) const noexcept {
|
||||
return *std::next(data, static_cast<std::ptrdiff_t>(idx));
|
||||
}
|
||||
constexpr auto end() const noexcept { return data + m_size; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
while (b1 != e1) {
|
||||
if (*b1 != *b2) {
|
||||
return false;
|
||||
}
|
||||
++b1;
|
||||
++b2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool operator==(const std::string &t_str) const noexcept { return std::equal(begin(), end(), std::cbegin(t_str), std::cend(t_str)); }
|
||||
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 char *data = nullptr;
|
||||
|
||||
@ -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
|
||||
10
readme.md
10
readme.md
@ -91,16 +91,6 @@ the doxygen documentation in the build folder or see the website
|
||||
http://www.chaiscript.com.
|
||||
|
||||
|
||||
Grammar
|
||||
=======
|
||||
|
||||
A formal EBNF grammar for ChaiScript is available in
|
||||
[grammar/chaiscript.ebnf](grammar/chaiscript.ebnf). To view it as a railroad
|
||||
diagram, paste the grammar into
|
||||
[mingodad's railroad diagram generator](https://mingodad.github.io/plgh/json2ebnf.html)
|
||||
or [bottlecaps.de/rr](https://www.bottlecaps.de/rr/ui).
|
||||
|
||||
|
||||
The shortest complete example possible follows:
|
||||
|
||||
```C++
|
||||
|
||||
@ -4,7 +4,6 @@ Current Version: 6.1.1
|
||||
|
||||
### Changes since 6.1.0
|
||||
|
||||
* Add `set_file_reader` callback for custom file loading #116 — allows overriding how `eval_file` and `use` read files
|
||||
* Handle the returning of `&` to `*` types. This specifically comes up with `std::vector<int *>` and similar containers
|
||||
* Update CMake to use `LIBDIR` instead of `lib` #502 by @guoyunhe
|
||||
* Add documentation for installing ChaiScript with vcpkg #500 by @grdowns
|
||||
|
||||
@ -30,8 +30,9 @@ char *mystrdup(const char *s) {
|
||||
#ifdef CHAISCRIPT_MSVC
|
||||
strcpy_s(d, len + 1, s); // Copy the characters
|
||||
#else
|
||||
strncpy(d, s, len + 1); // Copy the characters
|
||||
strncpy(d, s, len); // Copy the characters
|
||||
#endif
|
||||
d[len] = '\0';
|
||||
return d; // Return the new string
|
||||
}
|
||||
|
||||
@ -296,7 +297,7 @@ int main(int argc, char *argv[]) {
|
||||
++i;
|
||||
}
|
||||
|
||||
std::string arg(i ? *std::next(argv, i) : "--interactive");
|
||||
std::string arg(i ? argv[i] : "--interactive");
|
||||
|
||||
enum {
|
||||
eInteractive,
|
||||
@ -310,7 +311,7 @@ int main(int argc, char *argv[]) {
|
||||
std::cout << "insufficient input following " << arg << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
arg = *std::next(argv, ++i);
|
||||
arg = argv[++i];
|
||||
}
|
||||
} else if (arg == "-" || arg == "--stdin") {
|
||||
arg = "";
|
||||
|
||||
@ -35,8 +35,9 @@ char *mystrdup(const char *s) {
|
||||
#ifdef CHAISCRIPT_MSVC
|
||||
strcpy_s(d, len + 1, s); // Copy the characters
|
||||
#else
|
||||
strncpy(d, s, len + 1); // Copy the characters
|
||||
strncpy(d, s, len); // Copy the characters
|
||||
#endif
|
||||
d[len] = '\0';
|
||||
return d; // Return the new string
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <regex>
|
||||
@ -35,8 +34,9 @@ char *mystrdup(const char *s) {
|
||||
#ifdef CHAISCRIPT_MSVC
|
||||
strcpy_s(d, len + 1, s); // Copy the characters
|
||||
#else
|
||||
strncpy(d, s, len + 1); // Copy the characters
|
||||
strncpy(d, s, len); // Copy the characters
|
||||
#endif
|
||||
d[len] = '\0';
|
||||
return d; // Return the new string
|
||||
}
|
||||
|
||||
@ -283,7 +283,7 @@ int main(int argc, char *argv[]) {
|
||||
++i;
|
||||
}
|
||||
|
||||
std::string arg(i != 0 ? *std::next(argv, i) : "--interactive");
|
||||
std::string arg(i != 0 ? argv[i] : "--interactive");
|
||||
|
||||
enum {
|
||||
eInteractive,
|
||||
@ -297,7 +297,7 @@ int main(int argc, char *argv[]) {
|
||||
std::cout << "insufficient input following " << arg << '\n';
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
arg = *std::next(argv, ++i);
|
||||
arg = argv[++i];
|
||||
|
||||
} else if (arg == "-" || arg == "--stdin") {
|
||||
arg = "";
|
||||
|
||||
30
src/sha3.h
30
src/sha3.h
@ -6,19 +6,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// #include "hash.h"
|
||||
//#include "hash.h"
|
||||
#include <string>
|
||||
|
||||
// define fixed size integer types
|
||||
#ifdef _MSC_VER
|
||||
// Windows
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
// GCC
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
|
||||
/// compute SHA3 hash
|
||||
/** Usage:
|
||||
SHA3 sha3;
|
||||
@ -36,21 +37,18 @@ class SHA3 //: public Hash
|
||||
{
|
||||
public:
|
||||
/// algorithm variants
|
||||
enum Bits { Bits224 = 224,
|
||||
Bits256 = 256,
|
||||
Bits384 = 384,
|
||||
Bits512 = 512 };
|
||||
enum Bits { Bits224 = 224, Bits256 = 256, Bits384 = 384, Bits512 = 512 };
|
||||
|
||||
/// same as reset()
|
||||
explicit SHA3(Bits bits = Bits256);
|
||||
|
||||
/// compute hash of a memory block
|
||||
std::string operator()(const void *data, size_t numBytes);
|
||||
std::string operator()(const void* data, size_t numBytes);
|
||||
/// compute hash of a string, excluding final zero
|
||||
std::string operator()(const std::string &text);
|
||||
std::string operator()(const std::string& text);
|
||||
|
||||
/// add arbitrary number of bytes
|
||||
void add(const void *data, size_t numBytes);
|
||||
void add(const void* data, size_t numBytes);
|
||||
|
||||
/// return latest hash as hex characters
|
||||
std::string getHash();
|
||||
@ -60,24 +58,24 @@ public:
|
||||
|
||||
private:
|
||||
/// process a full block
|
||||
void processBlock(const void *data);
|
||||
void processBlock(const void* data);
|
||||
/// process everything left in the internal buffer
|
||||
void processBuffer();
|
||||
|
||||
/// 1600 bits, stored as 25x64 bit, BlockSize is no more than 1152 bits (Keccak224)
|
||||
enum { StateSize = 1600 / (8 * 8),
|
||||
MaxBlockSize = 200 - 2 * (224 / 8) };
|
||||
enum { StateSize = 1600 / (8 * 8),
|
||||
MaxBlockSize = 200 - 2 * (224 / 8) };
|
||||
|
||||
/// hash
|
||||
uint64_t m_hash[StateSize];
|
||||
/// size of processed data in bytes
|
||||
uint64_t m_numBytes;
|
||||
/// block size (less or equal to MaxBlockSize)
|
||||
size_t m_blockSize;
|
||||
size_t m_blockSize;
|
||||
/// valid bytes in m_buffer
|
||||
size_t m_bufferSize;
|
||||
size_t m_bufferSize;
|
||||
/// bytes not processed yet
|
||||
uint8_t m_buffer[MaxBlockSize];
|
||||
uint8_t m_buffer[MaxBlockSize];
|
||||
/// variant
|
||||
Bits m_bits;
|
||||
Bits m_bits;
|
||||
};
|
||||
|
||||
@ -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)
|
||||
@ -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);
|
||||
@ -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;
|
||||
}
|
||||
@ -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
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
@ -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)
|
||||
@ -9,64 +9,43 @@
|
||||
#define CHAISCRIPT_NO_DYNLOAD
|
||||
#endif
|
||||
|
||||
#include <chaiscript/chaiscript.hpp>
|
||||
#include "../emscripten/chaiscript_eval.hpp"
|
||||
#include <cassert>
|
||||
#include <chaiscript/chaiscript.hpp>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
int main() {
|
||||
const int chai = chaiscript_create();
|
||||
assert(chai > 0 && "create must return a positive handle");
|
||||
|
||||
// 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()
|
||||
[[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");
|
||||
|
||||
// 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);
|
||||
|
||||
// Test evalBool - same as Emscripten evalBool()
|
||||
[[maybe_unused]] bool b = chaiscript_eval_bool(chai, "true");
|
||||
bool b = chaiscript_eval_bool("true");
|
||||
assert(b == true);
|
||||
|
||||
b = chaiscript_eval_bool(chai, "false");
|
||||
b = chaiscript_eval_bool("false");
|
||||
assert(b == false);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Test a more complex expression
|
||||
chaiscript_eval(chai, "def square(n) { return n * n; }");
|
||||
[[maybe_unused]] const int sq = chaiscript_eval_int(chai, "square(7)");
|
||||
chaiscript_eval("def square(n) { return n * n; }");
|
||||
int sq = chaiscript_eval_int("square(7)");
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -39,15 +39,14 @@ assert_equal(true, test_fun_types[1].bare_equal(int_type));
|
||||
|
||||
assert_equal(2, `==`.get_arity());
|
||||
|
||||
// < should be the merging of three functions bool <(PODObject, PODObject),
|
||||
// bool <(string, string), and bool <(string_view, string_view)
|
||||
// < should be the merging of two functions bool <(PODObject, PODObject) and bool <(string, string)
|
||||
// we want to peel it apart and make sure that's true
|
||||
auto types = `<`.get_param_types();
|
||||
assert_equal(3, types.size());
|
||||
assert_equal(true, types[0].bare_equal(bool_type));
|
||||
assert_equal(true, types[1].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
|
||||
|
||||
@ -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)
|
||||
@ -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]);
|
||||
@ -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"))
|
||||
@ -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)
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
@ -5,8 +5,8 @@
|
||||
|
||||
Multi_Test_Chai::Multi_Test_Chai()
|
||||
: m_chai(new chaiscript::ChaiScript_Basic(
|
||||
chaiscript::Std_Lib::library(),
|
||||
std::make_unique<chaiscript::parser::ChaiScript_Parser<chaiscript::eval::Noop_Tracer, chaiscript::optimizer::Optimizer_Default>>())) {
|
||||
chaiscript::Std_Lib::library(),
|
||||
std::make_unique<chaiscript::parser::ChaiScript_Parser<chaiscript::eval::Noop_Tracer, chaiscript::optimizer::Optimizer_Default>>())) {
|
||||
}
|
||||
|
||||
std::shared_ptr<chaiscript::ChaiScript_Basic> Multi_Test_Chai::get_chai() {
|
||||
|
||||
@ -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)
|
||||
@ -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")
|
||||
@ -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)
|
||||
@ -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])
|
||||
@ -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")
|
||||
@ -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")
|
||||
@ -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))
|
||||
@ -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())
|
||||
@ -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)
|
||||
@ -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;
|
||||
}
|
||||
@ -16,7 +16,9 @@
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#include "catch_amalgamated.hpp"
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch.hpp"
|
||||
|
||||
TEST_CASE("Type_Info objects generate expected results") {
|
||||
const auto test_type
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user