mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
Compare commits
54 Commits
2eb3279c39
...
d3c94e4451
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3c94e4451 | ||
|
|
56506bc111 | ||
|
|
0b75a8be7d | ||
|
|
cd8dacccc6 | ||
|
|
d2ec2a82a9 | ||
|
|
b64c930b16 | ||
|
|
9680c93bd1 | ||
|
|
1cbbba38f9 | ||
|
|
bb06919061 | ||
|
|
1df1b4ad92 | ||
|
|
9ff56426e0 | ||
|
|
092ec417d2 | ||
|
|
0fd9cab654 | ||
|
|
e61be76531 | ||
|
|
d4c5bdb3e4 | ||
|
|
362e93fb29 | ||
|
|
07d62aae99 | ||
|
|
5a6050210d | ||
|
|
9237acbbb9 | ||
|
|
4804fb6e03 | ||
|
|
726169acc8 | ||
|
|
bcd07e05cd | ||
|
|
dcdab50efd | ||
|
|
0d1ceed05d | ||
|
|
08281a9d69 | ||
|
|
005f18feb2 | ||
|
|
11fec25112 | ||
|
|
7b95ff5126 | ||
|
|
1b27d0dd15 | ||
|
|
340e7d4b16 | ||
|
|
255ff87f37 | ||
|
|
92abd226a9 | ||
|
|
1619d846da | ||
|
|
bc771ab8ba | ||
|
|
fa4b8277f5 | ||
|
|
7b45fe19fe | ||
|
|
e42497a8b3 | ||
|
|
fc574c320b | ||
|
|
7d3c29085d | ||
|
|
f1ab992d0a | ||
|
|
91e50bc80f | ||
|
|
bcf2fdbf50 | ||
|
|
f59eff9b2f | ||
|
|
22092656fd | ||
|
|
a329be16ad | ||
|
|
78d2de0ccf | ||
|
|
da0322a8a2 | ||
|
|
cb48b7cc7e | ||
|
|
2e2740f90d | ||
|
|
25c520ed81 | ||
|
|
28894373f7 | ||
|
|
34fea05bc2 | ||
|
|
84507f59ef | ||
|
|
69476967ae |
@ -1,10 +1,14 @@
|
||||
# clang-format: 11
|
||||
# clang-format: 19
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveBitFields: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BasedOnStyle: WebKit
|
||||
BinPackArguments: true
|
||||
@ -13,12 +17,14 @@ 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
|
||||
@ -27,7 +33,11 @@ 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
Normal file
27
.github/workflows/auto-clang-format.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
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 }}
|
||||
163
.github/workflows/ci.yml
vendored
Normal file
163
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,163 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [develop, main]
|
||||
pull_request:
|
||||
branches: [develop, main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux GCC ${{ matrix.build_type }} threads=${{ matrix.multithread }}
|
||||
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 }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
macos:
|
||||
name: macOS AppleClang ${{ matrix.build_type }} threads=${{ matrix.multithread }}
|
||||
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 }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
linux-sanitizers:
|
||||
name: Linux GCC ASAN+UBSAN ${{ matrix.build_type }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
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
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
macos-sanitizers:
|
||||
name: macOS AppleClang ASAN+UBSAN ${{ matrix.build_type }}
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
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
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
windows:
|
||||
name: Windows MSVC ${{ matrix.build_type }} threads=${{ matrix.multithread }}
|
||||
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 }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config ${{ matrix.build_type }} -j
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure -C ${{ matrix.build_type }}
|
||||
|
||||
linux-tsan:
|
||||
name: Linux GCC TSAN ${{ matrix.build_type }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
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
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
|
||||
macos-tsan:
|
||||
name: macOS AppleClang TSAN ${{ matrix.build_type }}
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
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
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build --output-on-failure
|
||||
71
.github/workflows/emscripten.yml
vendored
Normal file
71
.github/workflows/emscripten.yml
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
name: Emscripten
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [develop, main]
|
||||
pull_request:
|
||||
branches: [develop, main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
emscripten:
|
||||
name: Emscripten WebAssembly Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Emscripten
|
||||
uses: mymindstorm/setup-emsdk@v14
|
||||
|
||||
- name: Configure
|
||||
run: emcmake cmake -B build-em -S emscripten -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build-em -j
|
||||
|
||||
- name: Verify artifacts
|
||||
run: |
|
||||
test -f build-em/chaiscript.js
|
||||
test -f build-em/chaiscript.wasm
|
||||
test -f build-em/chaiscript.html
|
||||
echo "All expected artifacts present"
|
||||
ls -lh build-em/chaiscript.*
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: chaiscript-web
|
||||
path: |
|
||||
build-em/chaiscript.js
|
||||
build-em/chaiscript.wasm
|
||||
build-em/chaiscript.html
|
||||
retention-days: 90
|
||||
|
||||
publish:
|
||||
name: Publish WASM Release
|
||||
needs: emscripten
|
||||
if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: chaiscript-web
|
||||
path: artifacts
|
||||
|
||||
- name: Publish to wasm-latest release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Flatten: artifacts may contain build-em/ subdirectory
|
||||
find artifacts -type f \( -name 'chaiscript.js' -o -name 'chaiscript.wasm' -o -name 'chaiscript.html' \) -exec cp {} . \;
|
||||
|
||||
# Delete existing release if present, then recreate
|
||||
gh release delete wasm-latest --repo "$GITHUB_REPOSITORY" -y 2>/dev/null || true
|
||||
gh release create wasm-latest \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "ChaiScript WASM Build (latest)" \
|
||||
--notes "Auto-published from develop branch. Built with Emscripten for browser use." \
|
||||
--prerelease \
|
||||
chaiscript.js chaiscript.wasm chaiscript.html
|
||||
@ -34,7 +34,7 @@ if(CMAKE_COMPILER_IS_GNUCC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
|
||||
option(ENABLE_THREAD_SANITIZER "Enable thread sanitizer testing in gcc/clang" FALSE)
|
||||
if(ENABLE_THREAD_SANITIZER)
|
||||
add_definitions(-fsanitize=thread -g)
|
||||
@ -87,6 +87,16 @@ if(CMAKE_COMPILER_IS_GNUCC OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
endif()
|
||||
|
||||
|
||||
elseif(MSVC)
|
||||
option(ENABLE_ADDRESS_SANITIZER "Enable address sanitizer testing in MSVC" FALSE)
|
||||
if(ENABLE_ADDRESS_SANITIZER)
|
||||
add_compile_options(/fsanitize=address)
|
||||
# ASAN is incompatible with /RTC (runtime error checks) and incremental linking
|
||||
string(REGEX REPLACE "/RTC[^ ]*" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
|
||||
string(REGEX REPLACE "/RTC[^ ]*" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
|
||||
add_link_options(/INCREMENTAL:NO)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
@ -145,7 +155,7 @@ else()
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_definitions(/W4 /w14545 /w34242 /w34254 /w34287 /w44263 /w44265 /w44296 /w44311 /w44826 /we4289 /w14546 /w14547 /w14549 /w14555 /w14619 /w14905 /w14906 /w14928)
|
||||
add_definitions(/WX /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
|
||||
@ -165,10 +175,10 @@ if(MSVC)
|
||||
# how to workaround or fix the error. So I'm disabling it globally.
|
||||
add_definitions(/wd4503)
|
||||
else()
|
||||
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)
|
||||
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)
|
||||
|
||||
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)
|
||||
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)
|
||||
else()
|
||||
add_definitions(-Wnoexcept)
|
||||
endif()
|
||||
@ -282,6 +292,16 @@ 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)
|
||||
|
||||
@ -370,8 +390,16 @@ 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 ${LIBS} ${CHAISCRIPT_LIBS})
|
||||
target_link_libraries(compiled_tests catch2 ${LIBS} ${CHAISCRIPT_LIBS})
|
||||
catch_discover_tests(compiled_tests TEST_PREFIX "compiled.")
|
||||
|
||||
add_executable(static_chaiscript_test unittests/static_chaiscript.cpp)
|
||||
@ -383,7 +411,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 ${LIBS})
|
||||
target_link_libraries(type_info_test catch2 ${LIBS})
|
||||
add_test(NAME Type_Info_Test COMMAND type_info_test)
|
||||
|
||||
add_executable(c_linkage_test unittests/c_linkage_test.cpp)
|
||||
@ -403,6 +431,9 @@ 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
|
||||
@ -413,6 +444,18 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(multifile_test ${LIBS})
|
||||
add_test(NAME MultiFile_Test COMMAND multifile_test)
|
||||
|
||||
add_executable(emscripten_eval_test unittests/emscripten_eval_test.cpp)
|
||||
target_link_libraries(emscripten_eval_test ${LIBS})
|
||||
add_test(NAME Emscripten_Eval_Test COMMAND emscripten_eval_test)
|
||||
|
||||
add_executable(emscripten_exception_test unittests/emscripten_exception_test.cpp)
|
||||
target_link_libraries(emscripten_exception_test ${LIBS})
|
||||
add_test(NAME Emscripten_Exception_Test COMMAND emscripten_exception_test)
|
||||
|
||||
add_executable(threading_config_test unittests/threading_config_test.cpp)
|
||||
target_link_libraries(threading_config_test ${LIBS})
|
||||
add_test(NAME Threading_Config_Test COMMAND threading_config_test)
|
||||
|
||||
install(TARGETS test_module RUNTIME DESTINATION bin LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/chaiscript")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
23
appveyor.yml
23
appveyor.yml
@ -1,23 +0,0 @@
|
||||
version: 6.1.x.{build}
|
||||
image:
|
||||
- Visual Studio 2019
|
||||
environment:
|
||||
matrix:
|
||||
- VS_VERSION: "Visual Studio 16"
|
||||
build_script:
|
||||
- cmd: >-
|
||||
mkdir build
|
||||
|
||||
cd build
|
||||
|
||||
cmake c:\Projects\chaiscript -G "%VS_VERSION%" -DBUILD_TESTING:BOOL=ON -DBUILD_MODULES:BOOL=ON
|
||||
|
||||
cmake --build . --config Debug
|
||||
test_script:
|
||||
- cmd: ctest -C Debug
|
||||
notifications:
|
||||
- provider: Webhook
|
||||
url: https://webhooks.gitter.im/e/9ff725a985b5679d1d5d
|
||||
on_build_success: true
|
||||
on_build_failure: true
|
||||
on_build_status_changed: false
|
||||
613
cheatsheet.md
613
cheatsheet.md
@ -16,6 +16,67 @@ 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
|
||||
@ -153,17 +214,39 @@ This allows you to pass a ChaiScript function to a function requiring `std::vect
|
||||
|
||||
## Adding Objects
|
||||
|
||||
```
|
||||
chai.add(chaiscript::var(somevar), "somevar"); // copied in
|
||||
chai.add(chaiscript::var(std::ref(somevar)), "somevar"); // by reference, shared between C++ and chai
|
||||
### `add` — Thread-Local Scoped Variables
|
||||
|
||||
`add` adds an object to the current thread's local scope. The variable is only visible in the
|
||||
thread that added it. If the variable already exists in the current scope, it is overwritten.
|
||||
|
||||
```cpp
|
||||
chai.add(chaiscript::var(somevar), "somevar"); // copied in
|
||||
chai.add(chaiscript::var(std::ref(somevar)), "somevar"); // by reference, shared between C++ and chai
|
||||
auto shareddouble = std::make_shared<double>(4.3);
|
||||
chai.add(chaiscript::var(shareddouble), "shareddouble"); // by shared_ptr, shared between c++ and chai
|
||||
chai.add(chaiscript::const_var(somevar), "somevar"); // copied in and made const
|
||||
chai.add_global_const(chaiscript::const_var(somevar), "somevar"); // global const. Throws if value is non-const, throws if object exists
|
||||
chai.add_global(chaiscript::var(somevar), "somevar"); // global non-const, throws if object exists
|
||||
chai.set_global(chaiscript::var(somevar), "somevar"); // global non-const, overwrites existing object
|
||||
chai.add(chaiscript::var(shareddouble), "shareddouble"); // by shared_ptr, shared between C++ and chai
|
||||
chai.add(chaiscript::const_var(somevar), "somevar"); // copied in and made const
|
||||
```
|
||||
|
||||
### `add_global` / `add_global_const` / `set_global` — Global Shared Variables
|
||||
|
||||
Global variables are shared between all threads and are visible from any scope (including inside
|
||||
functions). Use these when you need a variable accessible everywhere.
|
||||
|
||||
```cpp
|
||||
chai.add_global_const(chaiscript::const_var(somevar), "somevar"); // global const, throws if value is non-const or object already exists
|
||||
chai.add_global(chaiscript::var(somevar), "somevar"); // global non-const, throws if object already exists
|
||||
chai.set_global(chaiscript::var(somevar), "somevar"); // global non-const, overwrites existing or creates new
|
||||
```
|
||||
|
||||
### Summary of Differences
|
||||
|
||||
| Method | Scope | Thread Safety | If Name Exists |
|
||||
|--------|-------|---------------|----------------|
|
||||
| `add` | Thread-local, current scope | Not shared between threads | Overwrites |
|
||||
| `add_global` | Global, all scopes and threads | Mutex-protected, shared between threads | Throws exception |
|
||||
| `add_global_const` | Global, all scopes and threads | Mutex-protected, shared between threads | Throws exception |
|
||||
| `set_global` | Global, all scopes and threads | Mutex-protected, shared between threads | Overwrites |
|
||||
|
||||
## Adding Namespaces
|
||||
|
||||
Namespaces will not be populated until `import` is called.
|
||||
@ -477,34 +560,227 @@ n(2); // returns 20
|
||||
|
||||
|
||||
|
||||
## ChaiScript Defined Types
|
||||
## ChaiScript Defined Types (Classes)
|
||||
|
||||
Define a type called "MyType" with one member value "a" and a getter
|
||||
ChaiScript supports user-defined types using the `class` keyword. Classes can have attributes,
|
||||
constructors, methods, guards, and operator overloads. There is no inheritance between
|
||||
ChaiScript-defined types, but C++ class hierarchies can be exposed (see *Class Hierarchies* above).
|
||||
|
||||
### Preferred
|
||||
### Class Definition (Block Syntax)
|
||||
|
||||
Define a type with attributes, a constructor, and methods inside a `class` block.
|
||||
The keywords `var`, `attr`, and `auto` are interchangeable for declaring attributes.
|
||||
|
||||
```
|
||||
class MyType {
|
||||
var value;
|
||||
def MyType() { this.value = "a"; }
|
||||
def get_value() { "Value Is: " + this.value; }
|
||||
};
|
||||
class Rectangle {
|
||||
var width
|
||||
var height
|
||||
def Rectangle(w, h) { this.width = w; this.height = h; }
|
||||
def Rectangle() { this.width = 0; this.height = 0; }
|
||||
def area() { this.width * this.height; }
|
||||
}
|
||||
|
||||
var r = Rectangle(3, 4)
|
||||
print(r.area()) // prints 12
|
||||
```
|
||||
|
||||
### Alternative
|
||||
### Class Definition (Open Syntax)
|
||||
|
||||
Equivalently, attributes and methods can be defined outside a block using the `TypeName::` prefix.
|
||||
|
||||
```
|
||||
attr MyType::value;
|
||||
def MyType::MyType() { this.value = "a"; }
|
||||
def MyType::get_value() { "Value Is: " + this.value; }
|
||||
attr Circle::radius
|
||||
def Circle::Circle(r) { this.radius = r; }
|
||||
def Circle::circumference() { 2.0 * 3.14159 * this.radius; }
|
||||
```
|
||||
|
||||
Methods can also be added to an existing class after its initial definition:
|
||||
|
||||
```
|
||||
def Rectangle::perimeter() { 2 * (this.width + this.height); }
|
||||
```
|
||||
|
||||
### Using
|
||||
|
||||
```
|
||||
var m = MyType(); // calls constructor
|
||||
print(m.get_value()); // prints "Value Is: a"
|
||||
print(get_value(m)); // prints "Value Is: a"
|
||||
var m = Rectangle(5, 10)
|
||||
print(m.area()) // prints 50 — method call syntax
|
||||
print(area(m)) // prints 50 — function call syntax (equivalent)
|
||||
```
|
||||
|
||||
### Constructor and Method Guards
|
||||
|
||||
Constructors and methods can have guard expressions (after `:`) that control which
|
||||
overload is selected at call time.
|
||||
|
||||
```
|
||||
class Clamped {
|
||||
var value
|
||||
def Clamped(x) : x >= 0 { this.value = x; }
|
||||
def Clamped(x) { this.value = 0; } // fallback when guard fails
|
||||
}
|
||||
|
||||
Clamped(5).value // 5
|
||||
Clamped(-3).value // 0
|
||||
|
||||
class Abs {
|
||||
var x
|
||||
def Abs(v) { this.x = v; }
|
||||
def get() : this.x >= 0 { this.x; }
|
||||
def get() { -this.x; }
|
||||
}
|
||||
```
|
||||
|
||||
### Operator Overloading
|
||||
|
||||
Operators can be overloaded on user-defined types using backtick-quoted operator names.
|
||||
|
||||
```
|
||||
class Vec2 {
|
||||
var x
|
||||
var y
|
||||
def Vec2(x, y) { this.x = x; this.y = y; }
|
||||
def `+`(other) { Vec2(this.x + other.x, this.y + other.y); }
|
||||
}
|
||||
|
||||
var v = Vec2(1, 2) + Vec2(3, 4) // v.x == 4, v.y == 6
|
||||
```
|
||||
|
||||
Operators can also be overloaded as free functions with guards:
|
||||
|
||||
```
|
||||
def `-`(a, b) : is_type(a, "Vec2") && is_type(b, "Vec2") {
|
||||
Vec2(a.x - b.x, a.y - b.y)
|
||||
}
|
||||
```
|
||||
|
||||
### Cloning Objects
|
||||
|
||||
Use `clone()` to create a deep copy of a ChaiScript-defined object.
|
||||
|
||||
```
|
||||
var original = Rectangle(10, 20)
|
||||
var copy = clone(original)
|
||||
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
|
||||
@ -553,6 +829,94 @@ class My_Class {
|
||||
};
|
||||
```
|
||||
|
||||
## Strong Typedefs
|
||||
|
||||
Strong typedefs create distinct types that are not interchangeable with their underlying type
|
||||
or with other typedefs of the same underlying type. They use `Dynamic_Object` internally and
|
||||
automatically expose operators that the underlying type supports.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```
|
||||
using Meters = int
|
||||
using Seconds = int
|
||||
|
||||
var d = Meters(100)
|
||||
var t = Seconds(10)
|
||||
|
||||
// d and t are distinct types — you cannot accidentally mix them
|
||||
// Meters + Seconds would require an explicit conversion
|
||||
```
|
||||
|
||||
### Arithmetic and Comparison
|
||||
|
||||
Operators from the underlying type are forwarded and remain strongly typed:
|
||||
|
||||
```
|
||||
using Meters = int
|
||||
|
||||
var a = Meters(10)
|
||||
var b = Meters(20)
|
||||
|
||||
var c = a + b // Meters(30) — result is still Meters
|
||||
var bigger = b > a // true — comparisons return bool
|
||||
|
||||
// Compound assignment operators work too
|
||||
a += b // a is now Meters(30)
|
||||
```
|
||||
|
||||
### String-Based Strong Typedefs
|
||||
|
||||
Strong typedefs work with any type, not just numeric types:
|
||||
|
||||
```
|
||||
using Name = string
|
||||
|
||||
var n = Name("Alice")
|
||||
var greeting = Name("Hello, ") + Name("world") // Name — string concatenation is forwarded
|
||||
```
|
||||
|
||||
### Accessing the Underlying Value
|
||||
|
||||
Use `to_underlying` to extract the wrapped value:
|
||||
|
||||
```
|
||||
using Meters = int
|
||||
|
||||
var d = Meters(42)
|
||||
var raw = to_underlying(d) // 42, plain int
|
||||
```
|
||||
|
||||
### Extending Strong Typedefs
|
||||
|
||||
You can add custom operations to strong typedefs just like any other ChaiScript type:
|
||||
|
||||
```
|
||||
using Meters = int
|
||||
using Seconds = int
|
||||
using MetersPerSecond = int
|
||||
|
||||
def speed(Meters d, Seconds t) {
|
||||
MetersPerSecond(to_underlying(d) / to_underlying(t))
|
||||
}
|
||||
|
||||
var s = speed(Meters(100), Seconds(10)) // MetersPerSecond(10)
|
||||
```
|
||||
|
||||
You can also overload operators between different strong typedefs:
|
||||
|
||||
```
|
||||
using Meters = int
|
||||
using Feet = int
|
||||
|
||||
def to_feet(Meters m) {
|
||||
Feet((to_underlying(m) * 328) / 100)
|
||||
}
|
||||
|
||||
var m = Meters(10)
|
||||
var f = to_feet(m) // Feet(32)
|
||||
```
|
||||
|
||||
## method_missing
|
||||
|
||||
A function of the signature `method_missing(object, name, param1, param2, param3)` will be called if an appropriate
|
||||
@ -593,10 +957,213 @@ use("filename") // evals file exactly once and returns value of last statement
|
||||
|
||||
Both `use` and `eval_file` search the 'usepaths' passed to the ChaiScript constructor
|
||||
|
||||
## Reflection and Introspection
|
||||
|
||||
ChaiScript provides built-in reflection capabilities for inspecting types, functions, and objects at runtime.
|
||||
|
||||
### Type Inspection
|
||||
|
||||
```
|
||||
type_name(x) // returns the type name of a value as a string
|
||||
is_type(x, "typename") // returns true if x is of the named type
|
||||
type("typename") // returns a Type_Info object for the named type
|
||||
|
||||
// Examples
|
||||
type_name(1) // "int"
|
||||
type_name("hello") // "string"
|
||||
is_type(1, "int") // true
|
||||
is_type(1, "string") // false
|
||||
```
|
||||
|
||||
### Object Inspection Methods
|
||||
|
||||
Every object in ChaiScript supports these methods:
|
||||
|
||||
```
|
||||
x.get_type_info() // returns a Type_Info object for the value
|
||||
x.is_type("string") // returns true if x is of the named type
|
||||
x.is_type(string_type) // returns true if x matches the Type_Info
|
||||
x.is_var_const() // returns true if x is immutable
|
||||
x.is_var_null() // returns true if x is a null pointer
|
||||
x.is_var_pointer() // returns true if x is stored as a pointer
|
||||
x.is_var_reference() // returns true if x is stored as a reference
|
||||
x.is_var_undef() // returns true if x is undefined
|
||||
```
|
||||
|
||||
### Type_Info
|
||||
|
||||
`Type_Info` objects describe a type. You can get them via `type("typename")` or `x.get_type_info()`.
|
||||
|
||||
```
|
||||
var ti = type("int")
|
||||
ti.name() // ChaiScript registered name, e.g. "int"
|
||||
ti.cpp_name() // mangled C++ type name
|
||||
ti.cpp_bare_name() // C++ name without const/pointer/reference
|
||||
ti.bare_equal(other) // true if types match ignoring const/ptr/ref
|
||||
ti.is_type_const() // true if type is const
|
||||
ti.is_type_reference() // true if type is a reference
|
||||
ti.is_type_void() // true if type is void
|
||||
ti.is_type_undef() // true if type is undefined
|
||||
ti.is_type_pointer() // true if type is a pointer
|
||||
ti.is_type_arithmetic() // true if type is arithmetic (int, double, etc.)
|
||||
```
|
||||
|
||||
Built-in type constants are available: `int_type`, `double_type`, `string_type`, `bool_type`, `Object_type`, `Function_type`, `vector_type`, `map_type`.
|
||||
|
||||
### Function Introspection
|
||||
|
||||
Function objects support these introspection methods:
|
||||
|
||||
```
|
||||
f.get_arity() // number of parameters (-1 for variadic)
|
||||
f.get_param_types() // Vector of Type_Info (first element is return type)
|
||||
f.get_contained_functions() // Vector of overloaded functions (empty if not a conglomerate)
|
||||
f.has_guard() // true if the function has a guard condition
|
||||
f.get_guard() // returns the guard function (throws if none)
|
||||
f.get_annotation() // returns the annotation description
|
||||
f.call([param1, param2]) // call the function with a vector of parameters
|
||||
|
||||
// Examples
|
||||
def my_func(a, b) { return a + b; }
|
||||
my_func.get_arity() // 2
|
||||
my_func.has_guard() // false
|
||||
|
||||
def guarded(x) : x > 0 { return x; }
|
||||
guarded.has_guard() // true
|
||||
guarded.get_guard().get_arity() // 1
|
||||
|
||||
// Calling functions dynamically
|
||||
`+`.call([1, 2]) // 3
|
||||
```
|
||||
|
||||
### System Introspection
|
||||
|
||||
```
|
||||
get_functions() // returns a Map of all registered functions (name -> function)
|
||||
get_objects() // returns a Map of all scripting objects (name -> value)
|
||||
function_exists("f") // returns true if a function named "f" is registered
|
||||
call_exists(`f`, args) // returns true if f can be called with the given args
|
||||
dump_system() // prints all registered functions to stdout
|
||||
dump_object(x) // prints information about a value to stdout
|
||||
|
||||
// Examples
|
||||
var funcs = get_functions()
|
||||
funcs["print"] // the print function object
|
||||
function_exists("print") // true
|
||||
call_exists(`+`, 1, 2) // true
|
||||
```
|
||||
|
||||
### Dynamic_Object Reflection
|
||||
|
||||
ChaiScript-defined classes are Dynamic_Objects internally. They support:
|
||||
|
||||
```
|
||||
obj.get_type_name() // returns the ChaiScript class name (e.g. "MyClass")
|
||||
obj.get_attrs() // returns a Map of all attributes
|
||||
obj.has_attr("name") // returns true if the attribute exists
|
||||
obj.get_attr("name") // returns the value of the attribute
|
||||
obj.set_explicit(true) // disables dynamic attribute creation
|
||||
obj.is_explicit() // returns true if explicit mode is enabled
|
||||
|
||||
// Example
|
||||
class MyClass {
|
||||
var x
|
||||
def MyClass() { this.x = 10; }
|
||||
}
|
||||
var m = MyClass()
|
||||
m.get_type_name() // "MyClass"
|
||||
m.get_attrs() // map containing "x" -> 10
|
||||
type_name(m) // "Dynamic_Object" (the underlying C++ type)
|
||||
m.is_type("MyClass") // true (checks the ChaiScript class name)
|
||||
```
|
||||
|
||||
## JSON
|
||||
|
||||
* `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,6 +33,13 @@ 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
|
||||
@ -90,86 +97,222 @@ 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(
|
||||
""
|
||||
""
|
||||
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST"
|
||||
"TEST_SPEC;EXTRA_ARGS;PROPERTIES"
|
||||
"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"
|
||||
${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}")
|
||||
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}")
|
||||
string(SUBSTRING ${args_hash} 0 7 args_hash)
|
||||
|
||||
# Define rule to generate test list for aforementioned test executable
|
||||
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")
|
||||
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")
|
||||
|
||||
get_property(crosscompiling_emulator
|
||||
TARGET ${TARGET}
|
||||
PROPERTY CROSSCOMPILING_EMULATOR
|
||||
)
|
||||
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
|
||||
)
|
||||
if(NOT _SKIP_IS_FAILURE)
|
||||
set(_PROPERTIES ${_PROPERTIES} SKIP_RETURN_CODE 4)
|
||||
endif()
|
||||
|
||||
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}"
|
||||
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
|
||||
)
|
||||
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}"
|
||||
|
||||
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()
|
||||
message(FATAL_ERROR
|
||||
"Cannot set more than one TEST_INCLUDE_FILE"
|
||||
)
|
||||
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}"
|
||||
)
|
||||
|
||||
endfunction()
|
||||
|
||||
###############################################################################
|
||||
|
||||
set(_CATCH_DISCOVER_TESTS_SCRIPT
|
||||
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
|
||||
CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
|
||||
)
|
||||
|
||||
@ -1,18 +1,12 @@
|
||||
# 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 "")
|
||||
foreach(_arg ${ARGN})
|
||||
# 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}}")
|
||||
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
|
||||
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
|
||||
else()
|
||||
@ -22,55 +16,239 @@ function(add_command NAME)
|
||||
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# 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"
|
||||
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}
|
||||
)
|
||||
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}")
|
||||
|
||||
72
cmake/CatchShardTests.cmake
Normal file
72
cmake/CatchShardTests.cmake
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
# 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"
|
||||
)
|
||||
52
cmake/CatchShardTestsImpl.cmake
Normal file
52
cmake/CatchShardTestsImpl.cmake
Normal file
@ -0,0 +1,52 @@
|
||||
|
||||
# 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,9 +1,11 @@
|
||||
#==================================================================================================#
|
||||
# supported macros #
|
||||
# - TEST_CASE, #
|
||||
# - TEMPLATE_TEST_CASE #
|
||||
# - SCENARIO, #
|
||||
# - TEST_CASE_METHOD, #
|
||||
# - CATCH_TEST_CASE, #
|
||||
# - CATCH_TEMPLATE_TEST_CASE #
|
||||
# - CATCH_SCENARIO, #
|
||||
# - CATCH_TEST_CASE_METHOD. #
|
||||
# #
|
||||
@ -39,9 +41,24 @@
|
||||
# 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. #
|
||||
# #
|
||||
#==================================================================================================#
|
||||
|
||||
cmake_minimum_required(VERSION 2.8.8)
|
||||
if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8)
|
||||
message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer")
|
||||
endif()
|
||||
|
||||
option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF)
|
||||
option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF)
|
||||
@ -49,10 +66,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(PrintDebugMessage)
|
||||
if(PARSE_CATCH_TESTS_VERBOSE)
|
||||
message(STATUS "ParseAndAddCatchTests: ${ARGV}")
|
||||
endif()
|
||||
function(ParseAndAddCatchTests_PrintDebugMessage)
|
||||
if(PARSE_CATCH_TESTS_VERBOSE)
|
||||
message(STATUS "ParseAndAddCatchTests: ${ARGV}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# This removes the contents between
|
||||
@ -60,7 +77,7 @@ endfunction()
|
||||
# - full line comments (i.e. // ... )
|
||||
# contents have been read into '${CppCode}'.
|
||||
# !keep partial line comments
|
||||
function(RemoveComments CppCode)
|
||||
function(ParseAndAddCatchTests_RemoveComments CppCode)
|
||||
string(ASCII 2 CMakeBeginBlockComment)
|
||||
string(ASCII 3 CMakeEndBlockComment)
|
||||
string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}")
|
||||
@ -72,114 +89,162 @@ function(RemoveComments CppCode)
|
||||
endfunction()
|
||||
|
||||
# Worker function
|
||||
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()
|
||||
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}")
|
||||
endif()
|
||||
PrintDebugMessage("parsing ${SourceFile}")
|
||||
file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)
|
||||
|
||||
# Remove block and fullline comments
|
||||
RemoveComments(Contents)
|
||||
# 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()
|
||||
|
||||
# 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}")
|
||||
list(APPEND Labels ${Tags})
|
||||
|
||||
if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)
|
||||
PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
|
||||
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()
|
||||
set_property(
|
||||
DIRECTORY
|
||||
TARGET ${TestTarget}
|
||||
APPEND
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile}
|
||||
)
|
||||
PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}")
|
||||
set_property(
|
||||
SOURCE ${SourceFile}
|
||||
APPEND
|
||||
PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}")
|
||||
endif()
|
||||
|
||||
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()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# entry point
|
||||
function(ParseAndAddCatchTests 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}")
|
||||
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}")
|
||||
endfunction()
|
||||
|
||||
40
emscripten/CMakeLists.txt
Normal file
40
emscripten/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
||||
# Emscripten/WebAssembly build for ChaiScript
|
||||
# Based on work by Rob Loach: https://github.com/RobLoach/ChaiScript.js
|
||||
#
|
||||
# Usage:
|
||||
# emcmake cmake -B build-em -S emscripten
|
||||
# cmake --build build-em
|
||||
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
project(chaiscript_em)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Emscripten-specific compiler/linker flags
|
||||
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
|
||||
-sINVOKE_RUN=0
|
||||
)
|
||||
|
||||
# Copy the HTML shell to the build output directory
|
||||
add_custom_command(TARGET chaiscript POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/chaiscript.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/chaiscript.html
|
||||
COMMENT "Copying HTML frontend to build directory"
|
||||
)
|
||||
237
emscripten/chaiscript.html
Normal file
237
emscripten/chaiscript.html
Normal file
@ -0,0 +1,237 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
ChaiScript Emscripten Frontend
|
||||
Based on work by Rob Loach: https://github.com/RobLoach/ChaiScript.js
|
||||
Copyright 2019, Rob Loach
|
||||
Copyright 2009-2018, Jason Turner
|
||||
Licensed under the BSD License. See "license.txt" for details.
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ChaiScript</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #e0e0e0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
header {
|
||||
background: #16213e;
|
||||
padding: 1rem 2rem;
|
||||
border-bottom: 2px solid #0f3460;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
header h1 {
|
||||
font-size: 1.5rem;
|
||||
color: #e94560;
|
||||
font-weight: 700;
|
||||
}
|
||||
header span {
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
}
|
||||
#status {
|
||||
margin-left: auto;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.3rem 0.8rem;
|
||||
border-radius: 4px;
|
||||
background: #0f3460;
|
||||
}
|
||||
#status.ready { color: #4ecca3; }
|
||||
#status.loading { color: #e9c46a; }
|
||||
#status.error { color: #e94560; }
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 0;
|
||||
}
|
||||
.panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.panel-header {
|
||||
background: #16213e;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: #888;
|
||||
border-bottom: 1px solid #0f3460;
|
||||
}
|
||||
#input {
|
||||
flex: 1;
|
||||
background: #0d1117;
|
||||
color: #c9d1d9;
|
||||
border: none;
|
||||
padding: 1rem;
|
||||
font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
resize: none;
|
||||
outline: none;
|
||||
tab-size: 2;
|
||||
}
|
||||
.divider {
|
||||
width: 2px;
|
||||
background: #0f3460;
|
||||
}
|
||||
#output {
|
||||
flex: 1;
|
||||
background: #0d1117;
|
||||
padding: 1rem;
|
||||
font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.output-line { color: #c9d1d9; }
|
||||
.output-error { color: #e94560; }
|
||||
footer {
|
||||
background: #16213e;
|
||||
padding: 0.5rem 1rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
border-top: 2px solid #0f3460;
|
||||
}
|
||||
button {
|
||||
background: #e94560;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 0.4rem 1.2rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
button:hover { background: #c73652; }
|
||||
button:disabled { background: #555; cursor: not-allowed; }
|
||||
#btn-clear {
|
||||
background: #0f3460;
|
||||
}
|
||||
#btn-clear:hover { background: #1a4a8a; }
|
||||
.hint {
|
||||
margin-left: auto;
|
||||
font-size: 0.75rem;
|
||||
color: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>ChaiScript</h1>
|
||||
<span>Interactive Playground</span>
|
||||
<div id="status" class="loading">Loading...</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="panel">
|
||||
<div class="panel-header">Input</div>
|
||||
<textarea id="input" spellcheck="false">// Welcome to ChaiScript!
|
||||
// Write your code here and click Run (or press Ctrl+Enter).
|
||||
|
||||
def greet(name) {
|
||||
return "Hello, " + name + "!"
|
||||
}
|
||||
|
||||
print(greet("World"))
|
||||
print(greet("ChaiScript"))
|
||||
|
||||
// Math example
|
||||
def factorial(n) {
|
||||
if (n <= 1) { return 1 }
|
||||
return n * factorial(n - 1)
|
||||
}
|
||||
|
||||
print("5! = " + to_string(factorial(5)))
|
||||
print("10! = " + to_string(factorial(10)))
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="panel">
|
||||
<div class="panel-header">Output</div>
|
||||
<div id="output"></div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<button id="btn-run" disabled>Run</button>
|
||||
<button id="btn-clear">Clear</button>
|
||||
<span class="hint">Ctrl+Enter to run</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
var outputEl = document.getElementById('output');
|
||||
var inputEl = document.getElementById('input');
|
||||
var btnRun = document.getElementById('btn-run');
|
||||
var btnClear = document.getElementById('btn-clear');
|
||||
var statusEl = document.getElementById('status');
|
||||
|
||||
function appendOutput(text, className) {
|
||||
var line = document.createElement('div');
|
||||
line.className = className || 'output-line';
|
||||
line.textContent = text;
|
||||
outputEl.appendChild(line);
|
||||
outputEl.scrollTop = outputEl.scrollHeight;
|
||||
}
|
||||
|
||||
var Module = {
|
||||
print: function(text) {
|
||||
appendOutput(text);
|
||||
},
|
||||
printErr: function(text) {
|
||||
appendOutput(text, 'output-error');
|
||||
},
|
||||
onRuntimeInitialized: function() {
|
||||
statusEl.textContent = 'Ready';
|
||||
statusEl.className = 'ready';
|
||||
btnRun.disabled = false;
|
||||
}
|
||||
};
|
||||
|
||||
function runCode() {
|
||||
var code = inputEl.value;
|
||||
if (!code.trim()) return;
|
||||
|
||||
appendOutput('> Running...', 'output-line');
|
||||
try {
|
||||
Module.eval(code);
|
||||
} catch (e) {
|
||||
appendOutput('Error: ' + e.message, 'output-error');
|
||||
}
|
||||
appendOutput('', 'output-line');
|
||||
}
|
||||
|
||||
btnRun.addEventListener('click', runCode);
|
||||
btnClear.addEventListener('click', function() {
|
||||
outputEl.innerHTML = '';
|
||||
});
|
||||
|
||||
inputEl.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
runCode();
|
||||
}
|
||||
// Tab key inserts spaces
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
var start = this.selectionStart;
|
||||
var end = this.selectionEnd;
|
||||
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
|
||||
this.selectionStart = this.selectionEnd = start + 2;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="chaiscript.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
23
emscripten/chaiscript_em.cpp
Normal file
23
emscripten/chaiscript_em.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
// This file is distributed under the BSD License.
|
||||
// See "license.txt" for details.
|
||||
// Copyright 2019, Rob Loach (https://github.com/RobLoach/ChaiScript.js)
|
||||
// Copyright 2009-2018, Jason Turner (jason@emptycrate.com)
|
||||
// http://www.chaiscript.com
|
||||
//
|
||||
// Emscripten/WebAssembly wrapper for ChaiScript.
|
||||
// Based on work by Rob Loach: https://github.com/RobLoach/ChaiScript.js
|
||||
|
||||
#include "chaiscript_eval.hpp"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten/bind.h>
|
||||
|
||||
EMSCRIPTEN_BINDINGS(chaiscript) {
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
48
emscripten/chaiscript_eval.hpp
Normal file
48
emscripten/chaiscript_eval.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
// This file is distributed under the BSD License.
|
||||
// See "license.txt" for details.
|
||||
// Copyright 2019, Rob Loach (https://github.com/RobLoach/ChaiScript.js)
|
||||
// Copyright 2009-2018, Jason Turner (jason@emptycrate.com)
|
||||
// http://www.chaiscript.com
|
||||
|
||||
// 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.
|
||||
|
||||
#ifndef CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_
|
||||
#define CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_
|
||||
|
||||
#include <chaiscript/chaiscript.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace detail {
|
||||
inline chaiscript::ChaiScript &get_chai_instance() {
|
||||
static chaiscript::ChaiScript chai;
|
||||
return chai;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
inline void chaiscript_eval(const std::string &input) {
|
||||
detail::get_chai_instance().eval(input);
|
||||
}
|
||||
|
||||
inline std::string chaiscript_eval_string(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<std::string>(input);
|
||||
}
|
||||
|
||||
inline bool chaiscript_eval_bool(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<bool>(input);
|
||||
}
|
||||
|
||||
inline int chaiscript_eval_int(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<int>(input);
|
||||
}
|
||||
|
||||
inline float chaiscript_eval_float(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<float>(input);
|
||||
}
|
||||
|
||||
inline double chaiscript_eval_double(const std::string &input) {
|
||||
return detail::get_chai_instance().eval<double>(input);
|
||||
}
|
||||
|
||||
#endif /* CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ */
|
||||
189
grammar/chaiscript.ebnf
Normal file
189
grammar/chaiscript.ebnf
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* ChaiScript Grammar — EBNF for Railroad Diagram Generation
|
||||
*
|
||||
* View as navigable railroad diagrams at:
|
||||
* https://www.bottlecaps.de/rr/ui (IPv6)
|
||||
* https://rr.red-dove.com/ui (IPv4)
|
||||
*
|
||||
* Copy and paste this file into the 'Edit Grammar' tab, then
|
||||
* click 'View Diagram'.
|
||||
*
|
||||
* This grammar uses the notation accepted by
|
||||
* https://github.com/GuntherRademacher/rr :
|
||||
* - "::=" as rule separator
|
||||
* - no semicolon at end of rule
|
||||
* - "?" "+" "*" for repetition
|
||||
* - C comments
|
||||
*/
|
||||
|
||||
/* ---- Top-level ---- */
|
||||
|
||||
statements ::= ( def | try | if | while | class | enum
|
||||
| for | switch | return | break | continue
|
||||
| equation | block | eol )+
|
||||
|
||||
/* ---- Functions ---- */
|
||||
|
||||
def ::= "def" id ( "::" id )? "(" decl_arg_list ")" eol*
|
||||
( ":" guard )? eol* block
|
||||
|
||||
lambda ::= "fun" ( "[" id_arg_list "]" )? "(" decl_arg_list ")" eol* block
|
||||
|
||||
guard ::= operator
|
||||
|
||||
/* ---- Exception handling ---- */
|
||||
|
||||
try ::= "try" eol* block catch* finally?
|
||||
catch ::= "catch" ( "(" arg ")" )? eol* block
|
||||
finally ::= "finally" eol* block
|
||||
|
||||
/* ---- Control flow ---- */
|
||||
|
||||
if ::= "if" "(" equation ( eol equation )? ")" eol* block
|
||||
( "else" ( if | eol* block ) )*
|
||||
|
||||
while ::= "while" "(" operator ")" eol* block
|
||||
|
||||
for ::= "for" "(" ( for_guards | equation ":" equation ) ")" eol* block
|
||||
for_guards ::= equation eol equation eol equation
|
||||
|
||||
switch ::= "switch" "(" operator ")" eol* "{" ( case | default )+ "}"
|
||||
case ::= "case" "(" operator ")" eol* block
|
||||
default ::= "default" eol* block
|
||||
|
||||
/* ---- Classes ---- */
|
||||
|
||||
class ::= "class" id ( ":" id )? eol* class_block
|
||||
class_block ::= "{" class_statements* "}"
|
||||
class_statements ::= def | var_decl | eol
|
||||
|
||||
/* ---- Enums ---- */
|
||||
|
||||
enum ::= "enum" ( "class" | "struct" ) id ( ":" underlying_type )?
|
||||
"{" enum_entries? "}"
|
||||
|
||||
enum_entries ::= enum_entry ( "," enum_entry )*
|
||||
|
||||
enum_entry ::= id ( "=" integer )?
|
||||
|
||||
underlying_type ::= id
|
||||
|
||||
|
||||
/* ---- Blocks & flow keywords ---- */
|
||||
|
||||
block ::= "{" statements* "}"
|
||||
return ::= "return" operator?
|
||||
break ::= "break"
|
||||
continue ::= "continue"
|
||||
|
||||
/* ---- Line termination ---- */
|
||||
|
||||
eol ::= "\n" | "\r\n" | ";"
|
||||
|
||||
/* ---- Equations & operators ---- */
|
||||
|
||||
equation ::= operator ( ( "=" | ":=" | "+=" | "-=" | "*=" | "/="
|
||||
| "%=" | "<<=" | ">>=" | "&=" | "^=" | "|=" )
|
||||
equation )?
|
||||
|
||||
operator ::= prefix
|
||||
| value
|
||||
| operator binary_operator operator
|
||||
| operator "?" operator ":" operator
|
||||
|
||||
prefix ::= ( "++" | "--" | "-" | "+" | "!" | "~" ) operator
|
||||
|
||||
binary_operator ::= "||" | "&&"
|
||||
| "|" | "^" | "&"
|
||||
| "==" | "!="
|
||||
| "<" | "<=" | ">" | ">="
|
||||
| "<<" | ">>"
|
||||
| "+" | "-"
|
||||
| "*" | "/" | "%"
|
||||
|
||||
/* ---- Values & access ---- */
|
||||
|
||||
value ::= var_decl | dot_fun_array | prefix
|
||||
|
||||
dot_fun_array ::= ( lambda | num | quoted_string
|
||||
| single_quoted_string | raw_string
|
||||
| paren_expression | inline_container
|
||||
| id )
|
||||
( fun_call | array_call | dot_access )*
|
||||
|
||||
fun_call ::= "(" arg_list ")"
|
||||
array_call ::= "[" operator "]"
|
||||
dot_access ::= "." id
|
||||
|
||||
/* ---- Variable declarations ---- */
|
||||
|
||||
var_decl ::= ( "auto" | "var" | "const" ) ( reference | id )
|
||||
| "global" ( reference | id )
|
||||
| "attr" id ( "::" id )?
|
||||
|
||||
reference ::= "&" id
|
||||
|
||||
/* ---- Parenthesised & inline containers ---- */
|
||||
|
||||
paren_expression ::= "(" operator ")"
|
||||
|
||||
inline_container ::= "[" container_arg_list "]"
|
||||
container_arg_list ::= value_range
|
||||
| map_pair ( "," map_pair )*
|
||||
| operator ( "," operator )*
|
||||
|
||||
value_range ::= operator ".." operator
|
||||
map_pair ::= operator ":" operator
|
||||
|
||||
/* ---- String literals ---- */
|
||||
|
||||
quoted_string ::= '"' ( char | escape | interpolation )* '"'
|
||||
single_quoted_string ::= "'" ( char | escape ) "'"
|
||||
raw_string ::= 'R"' delimiter? "(" char* ")" delimiter? '"'
|
||||
delimiter ::= [a-zA-Z0-9_]+
|
||||
interpolation ::= "${" equation "}"
|
||||
|
||||
/* ---- Escape sequences ---- */
|
||||
|
||||
escape ::= "\" ( "'" | '"' | "?" | "\" | "a" | "b"
|
||||
| "f" | "n" | "r" | "t" | "v" | "$"
|
||||
| "0"
|
||||
| "x" hex_digit+
|
||||
| "u" hex_digit hex_digit hex_digit hex_digit
|
||||
| "U" hex_digit hex_digit hex_digit hex_digit
|
||||
hex_digit hex_digit hex_digit hex_digit
|
||||
| octal_digit+ )
|
||||
|
||||
/* ---- Argument lists ---- */
|
||||
|
||||
id_arg_list ::= id ( "," id )*
|
||||
decl_arg_list ::= ( arg ( "," arg )* )?
|
||||
arg_list ::= ( equation ( "," equation )* )?
|
||||
arg ::= id id?
|
||||
|
||||
/* ---- Identifiers ---- */
|
||||
|
||||
id ::= ( [a-zA-Z_] [a-zA-Z0-9_]* )
|
||||
| ( "`" [^`]+ "`" )
|
||||
| "true" | "false"
|
||||
| "Infinity" | "NaN"
|
||||
| "_"
|
||||
| "__LINE__" | "__FILE__" | "__FUNC__" | "__CLASS__"
|
||||
|
||||
/* ---- Numeric literals ---- */
|
||||
|
||||
num ::= hex | binary | float | integer
|
||||
|
||||
hex ::= "0" ( "x" | "X" ) [0-9a-fA-F]+ int_suffix*
|
||||
binary ::= "0" ( "b" | "B" ) [01]+ int_suffix*
|
||||
float ::= [0-9]+ "." [0-9]+ ( ( "e" | "E" ) ( "+" | "-" )? [0-9]+ )? float_suffix?
|
||||
integer ::= [0-9]+ int_suffix*
|
||||
|
||||
int_suffix ::= "l" | "L" | "ll" | "LL" | "u" | "U"
|
||||
float_suffix ::= "l" | "L" | "f" | "F"
|
||||
|
||||
/* ---- Character classes ---- */
|
||||
|
||||
octal_digit ::= [0-7]
|
||||
hex_digit ::= [0-9a-fA-F]
|
||||
char ::= [^"\]
|
||||
@ -824,12 +824,14 @@ namespace chaiscript {
|
||||
public:
|
||||
ChaiScript(std::vector<std::string> t_modulepaths = {},
|
||||
std::vector<std::string> t_usepaths = {},
|
||||
std::vector<Options> t_opts = chaiscript::default_options())
|
||||
: ChaiScript_Basic(chaiscript::Std_Lib::library(),
|
||||
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::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(),
|
||||
std::move(t_modulepaths),
|
||||
std::move(t_usepaths),
|
||||
std::move(t_opts)) {
|
||||
std::move(t_opts),
|
||||
std::find(t_lib_opts.begin(), t_lib_opts.end(), Library_Options::No_IO) != t_lib_opts.end()) {
|
||||
}
|
||||
};
|
||||
} // namespace chaiscript
|
||||
|
||||
@ -7,6 +7,16 @@
|
||||
#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)
|
||||
@ -59,10 +69,6 @@ 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
|
||||
@ -211,6 +217,13 @@ 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>()})> {
|
||||
};
|
||||
|
||||
@ -18,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"
|
||||
@ -38,8 +38,16 @@
|
||||
namespace chaiscript {
|
||||
class Std_Lib {
|
||||
public:
|
||||
[[nodiscard]] static ModulePtr library() {
|
||||
[[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>();
|
||||
}
|
||||
|
||||
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);
|
||||
@ -49,14 +57,17 @@ namespace chaiscript {
|
||||
|
||||
#ifndef CHAISCRIPT_NO_THREADS
|
||||
bootstrap::standard_library::future_type<std::future<chaiscript::Boxed_Value>>("future", *lib);
|
||||
lib->add(chaiscript::fun(
|
||||
[](const std::function<chaiscript::Boxed_Value()> &t_func) { return std::async(std::launch::async, t_func); }),
|
||||
"async");
|
||||
// Note: async() is registered in ChaiScript_Basic::build_eval_system()
|
||||
// with thread tracking to prevent heap-use-after-free on engine destruction.
|
||||
#endif
|
||||
|
||||
json_wrap::library(*lib);
|
||||
if (!no_json) {
|
||||
json_wrap::library(*lib);
|
||||
}
|
||||
|
||||
lib->eval(ChaiScript_Prelude::chaiscript_prelude() /*, "standard prelude"*/);
|
||||
if (!no_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 t[index];
|
||||
return *std::next(t, static_cast<std::ptrdiff_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 t[index];
|
||||
return *std::next(t, static_cast<std::ptrdiff_t>(index));
|
||||
}
|
||||
}),
|
||||
"[]");
|
||||
@ -115,6 +115,8 @@ 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
|
||||
@ -266,7 +268,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
|
||||
/// \returns passed in Module
|
||||
/// \param[in] t_no_io If true, skip registering print_string and println_string
|
||||
static void bootstrap(Module &m) {
|
||||
m.add(user_type<void>(), "void");
|
||||
m.add(user_type<bool>(), "bool");
|
||||
@ -435,9 +437,6 @@ 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,6 +18,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
||||
@ -84,6 +85,15 @@ 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());
|
||||
@ -170,18 +180,22 @@ 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) {
|
||||
copy_constructor<ContainerType>(type, m);
|
||||
operators::assign<ContainerType>(m);
|
||||
if constexpr (std::is_copy_constructible_v<typename ContainerType::value_type>) {
|
||||
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) {
|
||||
m.add(fun([](ContainerType *a, typename ContainerType::size_type n, const typename ContainerType::value_type &val) {
|
||||
return a->resize(n, val);
|
||||
}),
|
||||
"resize");
|
||||
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) { return a->resize(n); }), "resize");
|
||||
}
|
||||
|
||||
@ -213,13 +227,15 @@ 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) {
|
||||
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";
|
||||
}
|
||||
}());
|
||||
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::erase_at<ContainerType>), "erase_at");
|
||||
}
|
||||
@ -245,27 +261,29 @@ namespace chaiscript::bootstrap::standard_library {
|
||||
}),
|
||||
"back");
|
||||
|
||||
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");
|
||||
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");
|
||||
|
||||
return "push_back_ref";
|
||||
} else {
|
||||
return "push_back";
|
||||
}
|
||||
}());
|
||||
return "push_back_ref";
|
||||
} else {
|
||||
return "push_back";
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
m.add(fun(&ContainerType::pop_back), "pop_back");
|
||||
}
|
||||
@ -295,25 +313,27 @@ namespace chaiscript::bootstrap::standard_library {
|
||||
}),
|
||||
"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";
|
||||
}
|
||||
}());
|
||||
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<pop_ptr>(&ContainerType::pop_front)), "pop_front");
|
||||
}
|
||||
@ -344,6 +364,7 @@ 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 &);
|
||||
|
||||
|
||||
@ -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)
|
||||
#pragma warning(disable : 4244 4018 4389 4146 4365 4267 4242 4702) // 4702 is for broken unreachable warning
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
@ -220,7 +220,6 @@ 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:
|
||||
@ -233,6 +232,7 @@ 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:
|
||||
|
||||
@ -157,6 +157,12 @@ namespace chaiscript {
|
||||
|
||||
bool is_const() const noexcept { return m_data->m_type_info.is_const(); }
|
||||
|
||||
/// Mark this Boxed_Value as const (used for script-level const declarations)
|
||||
void make_const() noexcept {
|
||||
m_data->m_type_info.make_const();
|
||||
m_data->m_data_ptr = nullptr;
|
||||
}
|
||||
|
||||
bool is_type(const Type_Info &ti) const noexcept { return m_data->m_type_info.bare_equal(ti); }
|
||||
|
||||
template<typename T>
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -33,7 +34,6 @@
|
||||
#include "dynamic_object.hpp"
|
||||
#include "proxy_constructors.hpp"
|
||||
#include "proxy_functions.hpp"
|
||||
#include "short_alloc.hpp"
|
||||
#include "type_conversions.hpp"
|
||||
#include "type_info.hpp"
|
||||
|
||||
@ -317,9 +317,6 @@ 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>;
|
||||
|
||||
@ -370,6 +367,28 @@ 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 {
|
||||
@ -767,13 +786,13 @@ namespace chaiscript {
|
||||
t_loc = uint_fast32_t(funs.first);
|
||||
}
|
||||
|
||||
const auto do_attribute_call = [this](int l_num_params,
|
||||
const auto do_attribute_call = [this](std::size_t 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.begin(), l_params.begin() + l_num_params);
|
||||
Function_Params attr_params(l_params.first(l_num_params));
|
||||
Boxed_Value bv = dispatch::dispatch(l_funs, attr_params, l_conversions);
|
||||
if (l_num_params < int(l_params.size()) || bv.get_type_info().bare_equal(user_type<dispatch::Proxy_Function_Base>())) {
|
||||
if (l_num_params < 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) {
|
||||
@ -791,16 +810,16 @@ namespace chaiscript {
|
||||
try {
|
||||
auto func = boxed_cast<const dispatch::Proxy_Function_Base *>(bv);
|
||||
try {
|
||||
return (*func)({l_params.begin() + l_num_params, l_params.end()}, l_conversions);
|
||||
return (*func)(l_params.subspan(l_num_params), 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.begin() + l_num_params, l_params.end()},
|
||||
throw chaiscript::exception::dispatch_error(l_params.subspan(l_num_params),
|
||||
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.begin() + l_num_params, l_params.end()},
|
||||
throw chaiscript::exception::dispatch_error(l_params.subspan(l_num_params),
|
||||
std::vector<Const_Proxy_Function>(l_funs.begin(), l_funs.end()));
|
||||
}
|
||||
} else {
|
||||
@ -850,7 +869,7 @@ namespace chaiscript {
|
||||
if (!functions.empty()) {
|
||||
try {
|
||||
if (is_no_param) {
|
||||
auto tmp_params = params.to_vector();
|
||||
auto tmp_params = std::vector<Boxed_Value>(params.begin(), params.end());
|
||||
tmp_params.insert(tmp_params.begin() + 1, var(t_name));
|
||||
return do_attribute_call(2, Function_Params(tmp_params), functions, t_conversions);
|
||||
} else {
|
||||
@ -925,7 +944,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.begin() + 1, params.end()), convs));
|
||||
return const_var(f->call_match(Function_Params(params.subspan(1)), convs));
|
||||
}
|
||||
|
||||
/// Dump all system info to stdout
|
||||
@ -1060,6 +1079,27 @@ namespace chaiscript {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sort more-derived Dynamic_Object types before base types so that
|
||||
// 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
|
||||
}
|
||||
}
|
||||
// Impose a total order on type names to maintain strict-weak ordering:
|
||||
// non-empty names sort before empty names, then lexicographically
|
||||
if (lhs_dotn.empty() != rhs_dotn.empty()) {
|
||||
return !lhs_dotn.empty();
|
||||
}
|
||||
return lhs_dotn < rhs_dotn;
|
||||
}
|
||||
|
||||
const auto &lhsparamtypes = lhs->get_param_types();
|
||||
const auto &rhsparamtypes = rhs->get_param_types();
|
||||
|
||||
@ -1107,7 +1147,9 @@ namespace chaiscript {
|
||||
return lt < rt;
|
||||
}
|
||||
|
||||
return false;
|
||||
// When all overlapping parameters match, order by arity to maintain
|
||||
// strict-weak ordering (transitivity of equivalence)
|
||||
return lhssize < rhssize;
|
||||
}
|
||||
|
||||
/// Implementation detail for adding a function.
|
||||
@ -1152,6 +1194,21 @@ 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;
|
||||
@ -1161,6 +1218,11 @@ 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 {
|
||||
|
||||
@ -44,6 +44,27 @@ namespace chaiscript {
|
||||
|
||||
Dynamic_Object() = default;
|
||||
|
||||
/// Register that derived_name inherits from base_name
|
||||
static void register_inheritance(const std::string &derived_name, const std::string &base_name) {
|
||||
inheritance_map()[derived_name] = base_name;
|
||||
}
|
||||
|
||||
/// Check if type_name is, or inherits from, base_name
|
||||
static bool type_matches(const std::string &type_name, const std::string &base_name) noexcept {
|
||||
if (type_name == base_name) {
|
||||
return true;
|
||||
}
|
||||
const auto &m = inheritance_map();
|
||||
auto it = m.find(type_name);
|
||||
while (it != m.end()) {
|
||||
if (it->second == base_name) {
|
||||
return true;
|
||||
}
|
||||
it = m.find(it->second);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_explicit() const noexcept { return m_option_explicit; }
|
||||
|
||||
void set_explicit(const bool t_explicit) noexcept { m_option_explicit = t_explicit; }
|
||||
@ -87,6 +108,11 @@ namespace chaiscript {
|
||||
std::map<std::string, Boxed_Value> get_attrs() const { return m_attrs; }
|
||||
|
||||
private:
|
||||
static std::map<std::string, std::string> &inheritance_map() {
|
||||
static std::map<std::string, std::string> s_map;
|
||||
return s_map;
|
||||
}
|
||||
|
||||
const std::string m_type_name = "";
|
||||
bool m_option_explicit = false;
|
||||
|
||||
|
||||
@ -72,6 +72,8 @@ namespace chaiscript {
|
||||
|
||||
bool is_attribute_function() const noexcept override { return m_is_attribute; }
|
||||
|
||||
const std::string &dynamic_object_type_name() const noexcept override { return m_type_name; }
|
||||
|
||||
bool call_match(const chaiscript::Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override {
|
||||
if (dynamic_object_typename_match(vals, m_type_name, m_ti, t_conversions)) {
|
||||
return m_func->call_match(vals, t_conversions);
|
||||
@ -101,7 +103,9 @@ namespace chaiscript {
|
||||
|
||||
assert(types.size() > 1);
|
||||
// assert(types[1].bare_equal(user_type<Boxed_Value>()));
|
||||
types[1] = t_objectti;
|
||||
// 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;
|
||||
return types;
|
||||
}
|
||||
|
||||
@ -112,7 +116,7 @@ namespace chaiscript {
|
||||
if (bv.get_type_info().bare_equal(m_doti)) {
|
||||
try {
|
||||
const Dynamic_Object &d = boxed_cast<const Dynamic_Object &>(bv, &t_conversions);
|
||||
return name == "Dynamic_Object" || d.get_type_name() == name;
|
||||
return name == "Dynamic_Object" || Dynamic_Object::type_matches(d.get_type_name(), name);
|
||||
} catch (const std::bad_cast &) {
|
||||
return false;
|
||||
}
|
||||
@ -144,11 +148,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)
|
||||
|
||||
@ -12,55 +12,11 @@
|
||||
|
||||
#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) {
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
using Function_Params = std::span<const Boxed_Value>;
|
||||
|
||||
} // 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), true);
|
||||
return Boxed_Value(std::cref(r), false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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>
|
||||
void assign(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs = rhs; }), "=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_bitwise_and(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_bitwise_and(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs &= rhs; }), "&=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_xor(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_xor(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs ^= rhs; }), "^=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_bitwise_or(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_bitwise_or(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs |= rhs; }), "|=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_difference(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_difference(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs -= rhs; }), "-=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_left_shift(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_left_shift(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "<<=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_product(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_product(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "*=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_quotient(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_quotient(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs /= rhs; }), "/=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_remainder(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_remainder(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs %= rhs; }), "%=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_right_shift(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_right_shift(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs >>= rhs; }), ">>=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void assign_sum(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void assign_sum(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs += rhs; }), "+=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void prefix_decrement(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void prefix_decrement(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs) -> T & { return --lhs; }), "--");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void prefix_increment(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void prefix_increment(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](T &lhs) -> T & { return ++lhs; }), "++");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void equal(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void equal(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs == rhs; }), "==");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void greater_than(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void greater_than(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs > rhs; }), ">");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void greater_than_equal(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void greater_than_equal(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >= rhs; }), ">=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void less_than(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void less_than(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs < rhs; }), "<");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void less_than_equal(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void less_than_equal(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs <= rhs; }), "<=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void logical_compliment(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void logical_compliment(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs) { return !lhs; }), "!");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void not_equal(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void not_equal(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs != rhs; }), "!=");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void addition(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void addition(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs + rhs; }), "+");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void unary_plus(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void unary_plus(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs) { return +lhs; }), "+");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void subtraction(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void subtraction(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs - rhs; }), "-");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void unary_minus(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void unary_minus(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs) { return -lhs; }), "-");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void bitwise_and(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void bitwise_and(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs & rhs; }), "&");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void bitwise_compliment(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void bitwise_compliment(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs) { return ~lhs; }), "~");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void bitwise_xor(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void bitwise_xor(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs ^ rhs; }), "^");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void bitwise_or(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void bitwise_or(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs | rhs; }), "|");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void division(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void division(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs / rhs; }), "/");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void left_shift(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void left_shift(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs << rhs; }), "<<");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void multiplication(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void multiplication(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs * rhs; }), "*");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void remainder(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void remainder(ModuleType &m) {
|
||||
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs % rhs; }), "%");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void right_shift(Module &m) {
|
||||
template<typename T, typename ModuleType>
|
||||
void right_shift(ModuleType &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 = t_params.to_vector();
|
||||
auto vals = std::vector<Boxed_Value>{t_params.begin(), t_params.end()};
|
||||
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;
|
||||
@ -120,7 +120,7 @@ namespace chaiscript {
|
||||
if (bv.get_type_info().bare_equal(dynamic_object_type_info)) {
|
||||
try {
|
||||
const Dynamic_Object &d = boxed_cast<const Dynamic_Object &>(bv, &t_conversions);
|
||||
if (!(name == "Dynamic_Object" || d.get_type_name() == name)) {
|
||||
if (!(name == "Dynamic_Object" || Dynamic_Object::type_matches(d.get_type_name(), name))) {
|
||||
return std::make_pair(false, false);
|
||||
}
|
||||
} catch (const std::bad_cast &) {
|
||||
@ -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;
|
||||
@ -233,6 +233,12 @@ namespace chaiscript {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Dynamic_Object type name this function is bound to, or empty string if not a Dynamic_Object function
|
||||
virtual const std::string &dynamic_object_type_name() const noexcept {
|
||||
static const std::string empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
virtual bool compare_first_type(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept {
|
||||
/// TODO is m_types guaranteed to be at least 2??
|
||||
return compare_type_to_param(m_types[1], bv, t_conversions);
|
||||
@ -374,7 +380,15 @@ namespace chaiscript {
|
||||
|
||||
for (const auto &t : t_types.types()) {
|
||||
if (t.second.is_undef()) {
|
||||
types.push_back(chaiscript::detail::Get_Type_Info<Boxed_Value>::get());
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
types.push_back(t.second);
|
||||
}
|
||||
@ -668,13 +682,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.to_vector())
|
||||
, parameters(t_parameters.begin(), t_parameters.end())
|
||||
, 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.to_vector())
|
||||
, parameters(t_parameters.begin(), t_parameters.end())
|
||||
, functions(std::move(t_functions)) {
|
||||
}
|
||||
|
||||
@ -792,6 +806,38 @@ namespace chaiscript {
|
||||
++numdiffs;
|
||||
}
|
||||
}
|
||||
|
||||
// Deprioritize functions whose first parameter (object/receiver) requires
|
||||
// type conversion: conversions create temporaries, so mutations on the
|
||||
// converted object are silently lost.
|
||||
if (plist.size() > 1 && !func->get_param_types()[1].bare_equal(plist[0].get_type_info())) {
|
||||
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(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_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) {
|
||||
return f(boxed_cast<Params>(params[I], &t_conversions)...);
|
||||
}
|
||||
|
||||
@ -95,14 +95,24 @@ 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) {
|
||||
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));
|
||||
}
|
||||
return Handle_Return<Ret>::handle(call_func_impl(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
|
||||
|
||||
|
||||
@ -1,137 +0,0 @@
|
||||
#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,6 +498,29 @@ 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 {
|
||||
@ -506,7 +529,7 @@ namespace chaiscript {
|
||||
To vec;
|
||||
vec.reserve(from_vec.size());
|
||||
for (const Boxed_Value &bv : from_vec) {
|
||||
vec.push_back(detail::Cast_Helper<typename To::value_type>::cast(bv, nullptr));
|
||||
vec.push_back(detail::convert_vector_element<typename To::value_type>(bv));
|
||||
}
|
||||
|
||||
return Boxed_Value(std::move(vec));
|
||||
@ -534,6 +557,23 @@ namespace chaiscript {
|
||||
return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Type_Conversion_Impl<decltype(func)>>(
|
||||
user_type<std::map<std::string, Boxed_Value>>(), user_type<To>(), func);
|
||||
}
|
||||
|
||||
template<typename Left, typename Right>
|
||||
Type_Conversion pair_conversion() {
|
||||
auto func = [](const Boxed_Value &t_bv) -> Boxed_Value {
|
||||
const std::pair<Boxed_Value, Boxed_Value> &from_pair
|
||||
= 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));
|
||||
|
||||
return Boxed_Value(std::move(pair));
|
||||
};
|
||||
|
||||
return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Type_Conversion_Impl<decltype(func)>>(
|
||||
user_type<std::pair<Boxed_Value, Boxed_Value>>(), user_type<std::pair<Left, Right>>(), func);
|
||||
}
|
||||
} // namespace chaiscript
|
||||
|
||||
#endif
|
||||
|
||||
@ -85,6 +85,8 @@ namespace chaiscript {
|
||||
|
||||
constexpr const std::type_info *bare_type_info() const noexcept { return m_bare_type_info; }
|
||||
|
||||
void make_const() noexcept { m_flags |= (1 << is_const_flag); }
|
||||
|
||||
private:
|
||||
struct Unknown_Type {
|
||||
};
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
|
||||
#include "../utility/hash.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace chaiscript {
|
||||
@ -52,10 +53,10 @@ namespace chaiscript {
|
||||
invalid
|
||||
};
|
||||
|
||||
constexpr static const char *to_string(Opers t_oper) noexcept {
|
||||
constexpr const char *opers[]
|
||||
constexpr static std::string_view to_string(Opers t_oper) noexcept {
|
||||
constexpr const std::array opers
|
||||
= {"", "==", "<", ">", "<=", ">=", "!=", "", "=", "++", "--", "*=", "+=", "/=", "-=", "", "&=", "|=", "<<=", ">>=", "%=", "^=", "", "<<", ">>", "%", "&", "|", "^", "~", "", "+", "/", "*", "-", "+", "-", ""};
|
||||
return opers[static_cast<int>(t_oper)];
|
||||
return opers[static_cast<std::size_t>(t_oper)];
|
||||
}
|
||||
|
||||
constexpr static Opers to_operator(std::string_view t_str, bool t_is_unary = false) noexcept {
|
||||
|
||||
@ -26,10 +26,6 @@
|
||||
|
||||
namespace chaiscript {
|
||||
struct AST_Node;
|
||||
struct AST_Node_Trace;
|
||||
namespace exception {
|
||||
struct eval_error;
|
||||
}
|
||||
} // namespace chaiscript
|
||||
|
||||
namespace chaiscript {
|
||||
@ -37,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__")};
|
||||
words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const"), utility::hash("using"), utility::hash("enum")};
|
||||
|
||||
return words.count(utility::hash(s)) == 1;
|
||||
}
|
||||
@ -108,7 +104,12 @@ namespace chaiscript {
|
||||
Arg,
|
||||
Global_Decl,
|
||||
Constant,
|
||||
Compiled
|
||||
Compiled,
|
||||
Const_Var_Decl,
|
||||
Const_Assign_Decl,
|
||||
Using,
|
||||
Enum,
|
||||
Namespace_Block
|
||||
};
|
||||
|
||||
enum class Operator_Precedence {
|
||||
@ -129,9 +130,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 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"};
|
||||
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"};
|
||||
|
||||
return ast_node_types[static_cast<int>(ast_node_type)];
|
||||
return ast_node_types[static_cast<std::size_t>(ast_node_type)];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@ -592,13 +593,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,6 +15,7 @@
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@ -78,6 +79,9 @@ 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 {
|
||||
@ -118,11 +122,30 @@ 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) {
|
||||
void build_eval_system(const ModulePtr &t_lib, const std::vector<Options> &t_opts, const bool t_no_io) {
|
||||
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");
|
||||
@ -172,13 +195,39 @@ namespace chaiscript {
|
||||
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { add_global(t_bv, t_name); }), "add_global");
|
||||
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { set_global(t_bv, t_name); }), "set_global");
|
||||
|
||||
// why this unused parameter to Namespace?
|
||||
m_engine.add(fun([this](const std::string &t_namespace_name) {
|
||||
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
||||
import(t_namespace_name);
|
||||
if (!m_namespace_generators.count(t_namespace_name)) {
|
||||
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
||||
}
|
||||
const auto sep_pos = t_namespace_name.find("::");
|
||||
const std::string root_name = (sep_pos != std::string::npos) ? t_namespace_name.substr(0, sep_pos) : t_namespace_name;
|
||||
if (!m_engine.get_scripting_objects().count(root_name)) {
|
||||
import(root_name);
|
||||
} else if (m_namespace_generators.count(root_name)) {
|
||||
nest_children(root_name, m_namespace_generators[root_name]());
|
||||
}
|
||||
}),
|
||||
"namespace");
|
||||
m_engine.add(fun([this](const std::string &t_namespace_name) { import(t_namespace_name); }), "import");
|
||||
|
||||
#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
|
||||
@ -201,7 +250,15 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
/// Helper function for loading a file
|
||||
static std::string load_file(const std::string &t_filename) {
|
||||
std::string load_file(const std::string &t_filename) const {
|
||||
if (m_file_reader) {
|
||||
return m_file_reader(t_filename);
|
||||
}
|
||||
|
||||
return load_file_default(t_filename);
|
||||
}
|
||||
|
||||
static std::string load_file_default(const std::string &t_filename) {
|
||||
std::ifstream infile(t_filename.c_str(), std::ios::in | std::ios::ate | std::ios::binary);
|
||||
|
||||
if (!infile.is_open()) {
|
||||
@ -236,10 +293,21 @@ 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
|
||||
@ -248,7 +316,8 @@ 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 std::vector<chaiscript::Options> &t_opts = chaiscript::default_options(),
|
||||
const bool t_no_io = false)
|
||||
: m_module_paths(ensure_minimum_path_vec(std::move(t_module_paths)))
|
||||
, m_use_paths(ensure_minimum_path_vec(std::move(t_use_paths)))
|
||||
, m_parser(std::move(parser))
|
||||
@ -283,7 +352,7 @@ namespace chaiscript {
|
||||
m_module_paths.insert(m_module_paths.begin(), dllpath + "/");
|
||||
}
|
||||
#endif
|
||||
build_eval_system(t_lib, t_opts);
|
||||
build_eval_system(t_lib, t_opts, t_no_io);
|
||||
}
|
||||
|
||||
#ifndef CHAISCRIPT_NO_DYNLOAD
|
||||
@ -473,6 +542,12 @@ namespace chaiscript {
|
||||
/// \returns All values in the local thread state, added through the add() function
|
||||
std::map<std::string, Boxed_Value> get_locals() const { return m_engine.get_locals(); }
|
||||
|
||||
/// \returns All accessible function objects, as a map of name to Boxed_Value
|
||||
std::map<std::string, Boxed_Value> get_function_objects() const { return m_engine.get_function_objects(); }
|
||||
|
||||
/// \returns All accessible scripting objects (locals + globals), as a map of name to Boxed_Value
|
||||
std::map<std::string, Boxed_Value> get_scripting_objects() const { return m_engine.get_scripting_objects(); }
|
||||
|
||||
/// \brief Sets all of the locals for the current thread state.
|
||||
///
|
||||
/// \param[in] t_locals The map<name, value> set of variables to replace the current state with
|
||||
@ -536,7 +611,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(const std::string &t_module_name) {
|
||||
std::string load_module([[maybe_unused]] 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
|
||||
@ -682,28 +757,59 @@ namespace chaiscript {
|
||||
if (m_engine.get_scripting_objects().count(t_namespace_name)) {
|
||||
throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined");
|
||||
} else if (m_namespace_generators.count(t_namespace_name)) {
|
||||
m_engine.add_global(var(std::ref(m_namespace_generators[t_namespace_name]())), t_namespace_name);
|
||||
auto &ns = m_namespace_generators[t_namespace_name]();
|
||||
nest_children(t_namespace_name, ns);
|
||||
m_engine.add_global(var(std::ref(ns)), t_namespace_name);
|
||||
} else {
|
||||
throw std::runtime_error("No registered namespace: " + t_namespace_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Registers a namespace generator, which delays generation of the namespace until it is imported, saving memory if it is never
|
||||
/// used. \param[in] t_namespace_generator Namespace generator function. \param[in] t_namespace_name Name of the Namespace function
|
||||
/// being registered. \throw std::runtime_error In the case that the namespace name was already registered.
|
||||
/// used. Supports C++-style nested names (e.g. "constants::si") for nested namespaces; parent namespaces are auto-registered if absent.
|
||||
/// \param[in] t_namespace_generator Namespace generator function.
|
||||
/// \param[in] t_namespace_name Name of the Namespace function being registered (may contain :: for nesting).
|
||||
/// \throw std::runtime_error In the case that the namespace name was already registered.
|
||||
void register_namespace(const std::function<void(Namespace &)> &t_namespace_generator, const std::string &t_namespace_name) {
|
||||
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
|
||||
|
||||
if (!m_namespace_generators.count(t_namespace_name)) {
|
||||
// contain the namespace object memory within the m_namespace_generators map
|
||||
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable -> Namespace & {
|
||||
t_namespace_generator(space);
|
||||
return space;
|
||||
}));
|
||||
} else {
|
||||
if (m_namespace_generators.count(t_namespace_name)) {
|
||||
throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered.");
|
||||
}
|
||||
|
||||
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable 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,6 +10,7 @@
|
||||
#ifndef CHAISCRIPT_EVAL_HPP_
|
||||
#define CHAISCRIPT_EVAL_HPP_
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
@ -108,6 +109,169 @@ namespace chaiscript {
|
||||
return incoming;
|
||||
}
|
||||
}
|
||||
class Strong_Typedef_Binary_Op final : public dispatch::Proxy_Function_Base {
|
||||
public:
|
||||
Strong_Typedef_Binary_Op(
|
||||
std::string t_type_name,
|
||||
std::string t_op_name,
|
||||
Operators::Opers t_oper,
|
||||
bool t_rewrap,
|
||||
chaiscript::detail::Dispatch_Engine &t_engine)
|
||||
: Proxy_Function_Base(
|
||||
{chaiscript::detail::Get_Type_Info<Boxed_Value>::get(),
|
||||
user_type<dispatch::Dynamic_Object>(),
|
||||
user_type<dispatch::Dynamic_Object>()},
|
||||
2)
|
||||
, m_type_name(std::move(t_type_name))
|
||||
, m_op_name(std::move(t_op_name))
|
||||
, m_oper(t_oper)
|
||||
, m_rewrap(t_rewrap)
|
||||
, m_engine(t_engine) {
|
||||
}
|
||||
|
||||
bool operator==(const Proxy_Function_Base &f) const noexcept override {
|
||||
if (const auto *other = dynamic_cast<const Strong_Typedef_Binary_Op *>(&f)) {
|
||||
return m_type_name == other->m_type_name && m_op_name == other->m_op_name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override {
|
||||
return vals.size() == 2
|
||||
&& type_matches(vals[0], t_conversions)
|
||||
&& type_matches(vals[1], t_conversions);
|
||||
}
|
||||
|
||||
protected:
|
||||
Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override {
|
||||
if (!call_match(params, t_conversions)) {
|
||||
throw chaiscript::exception::guard_error();
|
||||
}
|
||||
|
||||
const auto &lhs = boxed_cast<const dispatch::Dynamic_Object &>(params[0], &t_conversions);
|
||||
const auto &rhs = boxed_cast<const dispatch::Dynamic_Object &>(params[1], &t_conversions);
|
||||
const auto lhs_val = lhs.get_attr("__value");
|
||||
const auto rhs_val = rhs.get_attr("__value");
|
||||
|
||||
Boxed_Value result;
|
||||
if (m_oper != Operators::Opers::invalid
|
||||
&& lhs_val.get_type_info().is_arithmetic()
|
||||
&& rhs_val.get_type_info().is_arithmetic()) {
|
||||
result = Boxed_Number::do_oper(m_oper, lhs_val, rhs_val);
|
||||
} else {
|
||||
std::array<Boxed_Value, 2> underlying_params{lhs_val, rhs_val};
|
||||
result = m_engine.call_function(m_op_name, m_loc, Function_Params(underlying_params), t_conversions);
|
||||
}
|
||||
|
||||
if (m_rewrap) {
|
||||
auto bv = Boxed_Value(dispatch::Dynamic_Object(m_type_name), true);
|
||||
auto *obj = static_cast<dispatch::Dynamic_Object *>(bv.get_ptr());
|
||||
obj->get_attr("__value") = result;
|
||||
return bv;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept {
|
||||
if (!bv.get_type_info().bare_equal(user_type<dispatch::Dynamic_Object>())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const auto &d = boxed_cast<const dispatch::Dynamic_Object &>(bv, &t_conversions);
|
||||
return d.get_type_name() == m_type_name;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_type_name;
|
||||
std::string m_op_name;
|
||||
Operators::Opers m_oper;
|
||||
bool m_rewrap;
|
||||
chaiscript::detail::Dispatch_Engine &m_engine;
|
||||
mutable std::atomic_uint_fast32_t m_loc{0};
|
||||
};
|
||||
|
||||
class Strong_Typedef_Compound_Assign_Op final : public dispatch::Proxy_Function_Base {
|
||||
public:
|
||||
Strong_Typedef_Compound_Assign_Op(
|
||||
std::string t_type_name,
|
||||
std::string t_op_name,
|
||||
Operators::Opers t_base_oper,
|
||||
std::string t_base_op_name,
|
||||
chaiscript::detail::Dispatch_Engine &t_engine)
|
||||
: Proxy_Function_Base(
|
||||
{user_type<dispatch::Dynamic_Object>(),
|
||||
user_type<dispatch::Dynamic_Object>(),
|
||||
user_type<dispatch::Dynamic_Object>()},
|
||||
2)
|
||||
, m_type_name(std::move(t_type_name))
|
||||
, m_op_name(std::move(t_op_name))
|
||||
, m_base_oper(t_base_oper)
|
||||
, m_base_op_name(std::move(t_base_op_name))
|
||||
, m_engine(t_engine) {
|
||||
}
|
||||
|
||||
bool operator==(const Proxy_Function_Base &f) const noexcept override {
|
||||
if (const auto *other = dynamic_cast<const Strong_Typedef_Compound_Assign_Op *>(&f)) {
|
||||
return m_type_name == other->m_type_name && m_op_name == other->m_op_name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override {
|
||||
return vals.size() == 2
|
||||
&& type_matches(vals[0], t_conversions)
|
||||
&& type_matches(vals[1], t_conversions);
|
||||
}
|
||||
|
||||
protected:
|
||||
Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override {
|
||||
if (!call_match(params, t_conversions)) {
|
||||
throw chaiscript::exception::guard_error();
|
||||
}
|
||||
|
||||
auto &lhs = boxed_cast<dispatch::Dynamic_Object &>(params[0], &t_conversions);
|
||||
const auto &rhs = boxed_cast<const dispatch::Dynamic_Object &>(params[1], &t_conversions);
|
||||
const auto lhs_val = lhs.get_attr("__value");
|
||||
const auto rhs_val = rhs.get_attr("__value");
|
||||
|
||||
Boxed_Value result;
|
||||
if (m_base_oper != Operators::Opers::invalid
|
||||
&& lhs_val.get_type_info().is_arithmetic()
|
||||
&& rhs_val.get_type_info().is_arithmetic()) {
|
||||
result = Boxed_Number::do_oper(m_base_oper, lhs_val, rhs_val);
|
||||
} else {
|
||||
std::array<Boxed_Value, 2> underlying_params{lhs_val, rhs_val};
|
||||
result = m_engine.call_function(m_base_op_name, m_loc, Function_Params(underlying_params), t_conversions);
|
||||
}
|
||||
|
||||
lhs.get_attr("__value") = result;
|
||||
return params[0];
|
||||
}
|
||||
|
||||
private:
|
||||
bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept {
|
||||
if (!bv.get_type_info().bare_equal(user_type<dispatch::Dynamic_Object>())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const auto &d = boxed_cast<const dispatch::Dynamic_Object &>(bv, &t_conversions);
|
||||
return d.get_type_name() == m_type_name;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_type_name;
|
||||
std::string m_op_name;
|
||||
Operators::Opers m_base_oper;
|
||||
std::string m_base_op_name;
|
||||
chaiscript::detail::Dispatch_Engine &m_engine;
|
||||
mutable std::atomic_uint_fast32_t m_loc{0};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<typename T>
|
||||
@ -220,15 +384,16 @@ namespace chaiscript {
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
auto lhs = this->children[0]->eval(t_ss);
|
||||
auto rhs = this->children[1]->eval(t_ss);
|
||||
return do_oper(t_ss, m_oper, this->text, lhs, rhs);
|
||||
return do_oper(t_ss, m_oper, this->text, lhs, rhs, m_loc);
|
||||
}
|
||||
|
||||
protected:
|
||||
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) const {
|
||||
// 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) {
|
||||
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
|
||||
@ -243,7 +408,7 @@ namespace chaiscript {
|
||||
chaiscript::eval::detail::Function_Push_Pop fpp(t_ss);
|
||||
std::array<Boxed_Value, 2> params{t_lhs, t_rhs};
|
||||
fpp.save_params(Function_Params(params));
|
||||
return t_ss->call_function(t_oper_string, m_loc, Function_Params(params), t_ss.conversions());
|
||||
return t_ss->call_function(t_oper_string, t_loc, Function_Params(params), t_ss.conversions());
|
||||
}
|
||||
} catch (const exception::dispatch_error &e) {
|
||||
throw exception::eval_error("Can not find appropriate '" + t_oper_string + "' operator.", e.parameters, e.functions, false, *t_ss);
|
||||
@ -437,6 +602,8 @@ 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.");
|
||||
}
|
||||
@ -549,6 +716,41 @@ namespace chaiscript {
|
||||
mutable std::atomic_uint_fast32_t m_loc = {0};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Const_Var_Decl_AST_Node final : AST_Node_Impl<T> {
|
||||
Const_Var_Decl_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::Const_Var_Decl, std::move(t_loc), std::move(t_children)) {
|
||||
}
|
||||
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override {
|
||||
throw exception::eval_error("const variables must be initialized at declaration");
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Const_Assign_Decl_AST_Node final : AST_Node_Impl<T> {
|
||||
Const_Assign_Decl_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::Const_Assign_Decl, std::move(t_loc), std::move(t_children)) {
|
||||
}
|
||||
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
const std::string &idname = this->children[0]->text;
|
||||
|
||||
try {
|
||||
Boxed_Value bv(detail::clone_if_necessary(this->children[1]->eval(t_ss), m_loc, t_ss));
|
||||
bv.reset_return_value();
|
||||
bv.make_const();
|
||||
t_ss.add_object(idname, bv);
|
||||
return bv;
|
||||
} catch (const exception::name_conflict_error &e) {
|
||||
throw exception::eval_error("Variable redefined '" + e.name() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::atomic_uint_fast32_t m_loc = {0};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Array_Call_AST_Node final : AST_Node_Impl<T> {
|
||||
Array_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
|
||||
@ -750,40 +952,43 @@ namespace chaiscript {
|
||||
return false;
|
||||
}
|
||||
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
static std::shared_ptr<dispatch::Proxy_Function_Base> make_proxy_function(
|
||||
const Def_AST_Node<T> &t_node, const chaiscript::detail::Dispatch_State &t_ss) {
|
||||
std::vector<std::string> t_param_names;
|
||||
size_t numparams = 0;
|
||||
|
||||
dispatch::Param_Types param_types;
|
||||
|
||||
if ((this->children.size() > 1) && (this->children[1]->identifier == AST_Node_Type::Arg_List)) {
|
||||
numparams = this->children[1]->children.size();
|
||||
t_param_names = Arg_List_AST_Node<T>::get_arg_names(*this->children[1]);
|
||||
param_types = Arg_List_AST_Node<T>::get_arg_types(*this->children[1], t_ss);
|
||||
if ((t_node.children.size() > 1) && (t_node.children[1]->identifier == AST_Node_Type::Arg_List)) {
|
||||
numparams = t_node.children[1]->children.size();
|
||||
t_param_names = Arg_List_AST_Node<T>::get_arg_names(*t_node.children[1]);
|
||||
param_types = Arg_List_AST_Node<T>::get_arg_types(*t_node.children[1], t_ss);
|
||||
}
|
||||
|
||||
std::reference_wrapper<chaiscript::detail::Dispatch_Engine> engine(*t_ss);
|
||||
std::shared_ptr<dispatch::Proxy_Function_Base> guard;
|
||||
if (m_guard_node) {
|
||||
if (t_node.m_guard_node) {
|
||||
guard = dispatch::make_dynamic_proxy_function(
|
||||
[engine, guardnode = m_guard_node, t_param_names](const Function_Params &t_params) {
|
||||
[engine, guardnode = t_node.m_guard_node, t_param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *guardnode, t_param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
m_guard_node);
|
||||
t_node.m_guard_node);
|
||||
}
|
||||
|
||||
return dispatch::make_dynamic_proxy_function(
|
||||
[engine, func_node = t_node.m_body_node, t_param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *func_node, t_param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
t_node.m_body_node,
|
||||
param_types,
|
||||
guard);
|
||||
}
|
||||
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
try {
|
||||
const std::string &l_function_name = this->children[0]->text;
|
||||
t_ss->add(dispatch::make_dynamic_proxy_function(
|
||||
[engine, func_node = m_body_node, t_param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *func_node, t_param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
m_body_node,
|
||||
param_types,
|
||||
guard),
|
||||
l_function_name);
|
||||
t_ss->add(make_proxy_function(*this, t_ss), this->children[0]->text);
|
||||
} catch (const exception::name_conflict_error &e) {
|
||||
throw exception::eval_error("Function redefined '" + e.name() + "'");
|
||||
}
|
||||
@ -827,16 +1032,290 @@ namespace chaiscript {
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
|
||||
|
||||
const auto &class_name = this->children[0]->text;
|
||||
|
||||
/// \todo do this better
|
||||
// put class name in current scope so it can be looked up by the attrs and methods
|
||||
t_ss.add_object("_current_class_name", const_var(this->children[0]->text));
|
||||
t_ss.add_object("_current_class_name", const_var(class_name));
|
||||
|
||||
this->children[1]->eval(t_ss);
|
||||
const bool has_base_class = (this->children.size() == 3);
|
||||
const auto &block = has_base_class ? this->children[2] : this->children[1];
|
||||
|
||||
// Register inheritance before evaluating the class body so that
|
||||
// function dispatch ordering can account for the relationship
|
||||
if (has_base_class) {
|
||||
const auto &base_name = this->children[1]->text;
|
||||
dispatch::Dynamic_Object::register_inheritance(class_name, base_name);
|
||||
}
|
||||
|
||||
block->eval(t_ss);
|
||||
|
||||
return void_var();
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
@ -871,7 +1350,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}, t_ss.conversions());
|
||||
return dispatch::dispatch(*t_funcs, Function_Params{&t_param, 1}, t_ss.conversions());
|
||||
};
|
||||
|
||||
const std::string &loop_var_name = this->children[0]->text;
|
||||
@ -985,8 +1464,7 @@ namespace chaiscript {
|
||||
if (this->children[currentCase]->identifier == AST_Node_Type::Case) {
|
||||
// This is a little odd, but because want to see both the switch and the case simultaneously, I do a downcast here.
|
||||
try {
|
||||
std::array<Boxed_Value, 2> p{match_value, this->children[currentCase]->children[0]->eval(t_ss)};
|
||||
if (hasMatched || boxed_cast<bool>(t_ss->call_function("==", m_loc, Function_Params{p}, t_ss.conversions()))) {
|
||||
if (hasMatched || boxed_cast<bool>(Binary_Operator_AST_Node<T>::do_oper(t_ss, Operators::Opers::equals, "==", match_value, this->children[currentCase]->children[0]->eval(t_ss), m_loc))) {
|
||||
this->children[currentCase]->eval(t_ss);
|
||||
hasMatched = true;
|
||||
}
|
||||
@ -1168,8 +1646,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});
|
||||
return t_ss->call_function(this->text, m_loc, Function_Params{bv}, t_ss.conversions());
|
||||
fpp.save_params(Function_Params{&bv, 1});
|
||||
return t_ss->call_function(this->text, m_loc, Function_Params{&bv, 1}, 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);
|
||||
@ -1256,6 +1734,7 @@ 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) {
|
||||
@ -1269,30 +1748,33 @@ 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}, t_ss.conversions())
|
||||
.match(Function_Params{&t_except, 1}, 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;
|
||||
}
|
||||
|
||||
@ -1302,17 +1784,19 @@ namespace chaiscript {
|
||||
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
} catch (...) {
|
||||
if (this->children.back()->identifier == AST_Node_Type::Finally) {
|
||||
this->children.back()->children[0]->eval(t_ss);
|
||||
|
||||
@ -89,6 +89,7 @@ namespace chaiscript {
|
||||
template<typename T>
|
||||
bool contains_var_decl_in_scope(const eval::AST_Node_Impl<T> &node) noexcept {
|
||||
if (node.identifier == AST_Node_Type::Var_Decl || node.identifier == AST_Node_Type::Assign_Decl
|
||||
|| node.identifier == AST_Node_Type::Const_Var_Decl || node.identifier == AST_Node_Type::Const_Assign_Decl
|
||||
|| node.identifier == AST_Node_Type::Reference) {
|
||||
return true;
|
||||
}
|
||||
@ -202,6 +203,14 @@ namespace chaiscript {
|
||||
return chaiscript::make_unique<eval::AST_Node_Impl<T>, eval::Assign_Decl_AST_Node<T>>(node->text,
|
||||
node->location,
|
||||
std::move(new_children));
|
||||
} else if ((node->identifier == AST_Node_Type::Equation) && node->text == "=" && node->children.size() == 2
|
||||
&& node->children[0]->identifier == AST_Node_Type::Const_Var_Decl) {
|
||||
std::vector<eval::AST_Node_Impl_Ptr<T>> new_children;
|
||||
new_children.push_back(std::move(node->children[0]->children[0]));
|
||||
new_children.push_back(std::move(node->children[1]));
|
||||
return chaiscript::make_unique<eval::AST_Node_Impl<T>, eval::Const_Assign_Decl_AST_Node<T>>(node->text,
|
||||
node->location,
|
||||
std::move(new_children));
|
||||
}
|
||||
|
||||
return node;
|
||||
@ -388,8 +397,9 @@ namespace chaiscript {
|
||||
assert(children.size() == 1);
|
||||
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
|
||||
|
||||
int i = start_int;
|
||||
t_ss.add_object(id, var(&i));
|
||||
Boxed_Value bv_i(start_int);
|
||||
auto &i = *static_cast<int *>(bv_i.get_ptr());
|
||||
t_ss.add_object(id, bv_i);
|
||||
|
||||
try {
|
||||
for (; i < end_int; ++i) {
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@ -22,15 +23,11 @@
|
||||
#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
|
||||
@ -64,41 +61,26 @@ namespace chaiscript {
|
||||
// Generic for u16, u32 and wchar
|
||||
template<typename string_type>
|
||||
struct Char_Parser_Helper {
|
||||
// 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]); // size, character
|
||||
}
|
||||
|
||||
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
|
||||
string_type out;
|
||||
utility::unicode::append_codepoint(out, static_cast<std::uint32_t>(val));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization for char AKA UTF-8
|
||||
// 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) {
|
||||
// little SFINAE trick to avoid base class
|
||||
return Char_Parser_Helper<std::true_type>::u8str_from_ll(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(c, 2);
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
@ -128,9 +110,7 @@ namespace chaiscript {
|
||||
|
||||
template<typename Array2D, typename First, typename Second>
|
||||
constexpr static void set_alphabet(Array2D &array, const First first, const Second second) noexcept {
|
||||
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;
|
||||
array[static_cast<std::size_t>(first)][static_cast<std::size_t>(second)] = true;
|
||||
}
|
||||
|
||||
constexpr static std::array<std::array<bool, detail::lengthof_alphabet>, detail::max_alphabet> build_alphabet() noexcept {
|
||||
@ -360,13 +340,13 @@ namespace chaiscript {
|
||||
++col;
|
||||
}
|
||||
|
||||
++m_pos;
|
||||
std::advance(m_pos, 1);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Position &operator--() noexcept {
|
||||
--m_pos;
|
||||
std::advance(m_pos, -1);
|
||||
if (*m_pos == '\n') {
|
||||
--line;
|
||||
col = m_last_col;
|
||||
@ -377,7 +357,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
constexpr Position &operator+=(size_t t_distance) noexcept {
|
||||
*this = (*this) + t_distance;
|
||||
*this = *this + t_distance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -406,9 +386,9 @@ namespace chaiscript {
|
||||
|
||||
constexpr bool operator!=(const Position &t_rhs) const noexcept { return m_pos != t_rhs.m_pos; }
|
||||
|
||||
constexpr bool has_more() const noexcept { return m_pos != m_end; }
|
||||
[[nodiscard]] constexpr bool has_more() const noexcept { return m_pos != m_end; }
|
||||
|
||||
constexpr size_t remaining() const noexcept { return static_cast<size_t>(m_end - m_pos); }
|
||||
[[nodiscard]] 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) {
|
||||
@ -458,7 +438,7 @@ namespace chaiscript {
|
||||
constexpr static Operator_Matches m_operator_matches{};
|
||||
|
||||
/// test a char in an m_alphabet
|
||||
constexpr bool char_in_alphabet(char c, detail::Alphabet a) const noexcept { return m_alphabet[a][static_cast<uint8_t>(c)]; }
|
||||
[[nodiscard]] 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 {
|
||||
@ -509,7 +489,7 @@ namespace chaiscript {
|
||||
if (m_position.remaining() >= len) {
|
||||
const char *file_pos = &(*m_position);
|
||||
for (size_t pos = 0; pos < len; ++pos) {
|
||||
if (sym.c_str()[pos] != file_pos[pos]) {
|
||||
if (sym[pos] != *std::next(file_pos, static_cast<std::ptrdiff_t>(pos))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -530,20 +510,9 @@ namespace chaiscript {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (Symbol_(m_singleline_comment)) {
|
||||
while (m_position.has_more()) {
|
||||
if (Symbol_(m_cr_lf)) {
|
||||
m_position -= 2;
|
||||
break;
|
||||
} else if (Char_('\n')) {
|
||||
--m_position;
|
||||
break;
|
||||
} else {
|
||||
++m_position;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (Symbol_(m_annotation)) {
|
||||
}
|
||||
|
||||
if (Symbol_(m_singleline_comment) || Symbol_(m_annotation)) {
|
||||
while (m_position.has_more()) {
|
||||
if (Symbol_(m_cr_lf)) {
|
||||
m_position -= 2;
|
||||
@ -1060,7 +1029,7 @@ namespace chaiscript {
|
||||
template<typename string_type>
|
||||
struct Char_Parser {
|
||||
string_type &match;
|
||||
using char_type = typename string_type::value_type;
|
||||
using char_type = string_type::value_type;
|
||||
bool is_escaped = false;
|
||||
bool is_interpolated = false;
|
||||
bool saw_interpolation_marker = false;
|
||||
@ -1119,40 +1088,20 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
void process_unicode() {
|
||||
const auto ch = static_cast<uint32_t>(std::stoi(hex_matches, nullptr, 16));
|
||||
const auto ch = static_cast<std::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 && ch >= 0xD800 && ch <= 0xDFFF) {
|
||||
if (u_size == 4 && utility::unicode::is_surrogate(ch)) {
|
||||
throw exception::eval_error("Invalid 16 bit universal character");
|
||||
}
|
||||
|
||||
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?
|
||||
if (utility::unicode::append_utf8(match, ch) == 0) {
|
||||
throw exception::eval_error("Invalid 32 bit universal character");
|
||||
}
|
||||
}
|
||||
@ -1363,6 +1312,109 @@ namespace chaiscript {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a raw string from input R"delimiter(content)delimiter", without skipping initial whitespace
|
||||
bool Raw_String_() {
|
||||
if (m_position.has_more() && (*m_position == 'R') && m_position.remaining() > 1 && *(m_position + 1) == '"') {
|
||||
auto s = m_position + 2;
|
||||
|
||||
// Read the delimiter (may be empty)
|
||||
std::string delimiter;
|
||||
while (s.has_more() && *s != '(' && *s != '\n') {
|
||||
delimiter.push_back(*s);
|
||||
++s;
|
||||
}
|
||||
|
||||
if (!s.has_more() || *s != '(') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the opening '('
|
||||
++s;
|
||||
|
||||
// Build the closing sequence: )delimiter"
|
||||
std::string close_seq = ")";
|
||||
close_seq += delimiter;
|
||||
close_seq += '"';
|
||||
|
||||
// Search for the closing sequence
|
||||
while (s.has_more()) {
|
||||
if (*s == close_seq[0]) {
|
||||
// Check if we match the full closing sequence
|
||||
auto t = s;
|
||||
std::size_t i = 0;
|
||||
while (t.has_more() && i < close_seq.size() && *t == close_seq[i]) {
|
||||
++t;
|
||||
++i;
|
||||
}
|
||||
if (i == close_seq.size()) {
|
||||
// Found the closing sequence
|
||||
m_position = t;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (*s == '\n') {
|
||||
m_position.col = 1;
|
||||
}
|
||||
++s;
|
||||
}
|
||||
|
||||
throw exception::eval_error("Unclosed raw string", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Reads and captures a raw string from input. No escape processing or interpolation.
|
||||
bool Raw_String() {
|
||||
Depth_Counter dc{this};
|
||||
SkipWS();
|
||||
|
||||
const auto start = m_position;
|
||||
|
||||
if (Raw_String_()) {
|
||||
// Extract content between R"delimiter( and )delimiter"
|
||||
auto s = start + 2; // skip R"
|
||||
|
||||
// Skip delimiter
|
||||
std::string delimiter;
|
||||
while (*s != '(') {
|
||||
delimiter.push_back(*s);
|
||||
++s;
|
||||
}
|
||||
++s; // skip '('
|
||||
|
||||
// Build closing sequence
|
||||
std::string close_seq = ")";
|
||||
close_seq += delimiter;
|
||||
close_seq += '"';
|
||||
|
||||
// Extract raw content up to closing sequence
|
||||
std::string match;
|
||||
auto end = m_position; // m_position is already past the closing sequence
|
||||
|
||||
// Content is from s up to (end - close_seq.size())
|
||||
// We need to find the closing sequence from s
|
||||
while (s.has_more()) {
|
||||
if (*s == close_seq[0]) {
|
||||
auto t = s;
|
||||
std::size_t i = 0;
|
||||
while (t.has_more() && i < close_seq.size() && *t == close_seq[i]) {
|
||||
++t;
|
||||
++i;
|
||||
}
|
||||
if (i == close_seq.size() && t == end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
match.push_back(*s);
|
||||
++s;
|
||||
}
|
||||
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(match)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Reads a character group from input, without skipping initial whitespace
|
||||
bool Single_Quoted_String_() {
|
||||
bool retval = false;
|
||||
@ -1445,7 +1497,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.c_str()[i]) {
|
||||
if (*tmp != t_s[i]) {
|
||||
return false;
|
||||
}
|
||||
++tmp;
|
||||
@ -1472,7 +1524,7 @@ namespace chaiscript {
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool is_operator(std::string_view t_s) const noexcept { return m_operator_matches.is_match(t_s); }
|
||||
[[nodiscard]] 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) {
|
||||
@ -1887,6 +1939,44 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
/// Reads a class block from input
|
||||
bool Namespace_Block() {
|
||||
Depth_Counter dc{this};
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
const auto prev_pos = m_position;
|
||||
|
||||
if (Keyword("namespace")) {
|
||||
if (Id(true)) {
|
||||
std::string ns_name = m_match_stack.back()->text;
|
||||
|
||||
while (Symbol("::")) {
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Incomplete namespace name after '::'",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
ns_name += "::" + m_match_stack.back()->text;
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.back() = make_node<eval::Id_AST_Node<Tracer>>(ns_name, prev_pos.line, prev_pos.col);
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (Block()) {
|
||||
build_match<eval::Namespace_Block_AST_Node<Tracer>>(prev_stack_top);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_position = prev_pos;
|
||||
while (prev_stack_top != m_match_stack.size()) {
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Class(const bool t_class_allowed) {
|
||||
Depth_Counter dc{this};
|
||||
bool retval = false;
|
||||
@ -1908,6 +1998,13 @@ namespace chaiscript {
|
||||
|
||||
const auto class_name = m_match_stack.back()->text;
|
||||
|
||||
// Optionally parse ': BaseClassName' for inheritance
|
||||
if (Char(':')) {
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing base class name in definition", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
}
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
@ -1921,6 +2018,134 @@ namespace chaiscript {
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool Using(const bool t_class_allowed) {
|
||||
Depth_Counter dc{this};
|
||||
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
if (Keyword("using")) {
|
||||
if (!t_class_allowed) {
|
||||
throw exception::eval_error("Type alias definitions only allowed at top scope",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing type name in 'using' declaration",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
if (!Symbol("=", true)) {
|
||||
throw exception::eval_error("Missing '=' in 'using' declaration",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing base type name in 'using' declaration",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
build_match<eval::Using_AST_Node<Tracer>>(prev_stack_top);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Enum(const bool t_allowed) {
|
||||
Depth_Counter dc{this};
|
||||
bool retval = false;
|
||||
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
if (Keyword("enum")) {
|
||||
if (!Keyword("class") && !Keyword("struct")) {
|
||||
throw exception::eval_error("Expected 'class' or 'struct' after 'enum' (only 'enum class'/'enum struct' is supported)",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
if (!t_allowed) {
|
||||
throw exception::eval_error("Enum definitions only allowed at top scope",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
retval = true;
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing enum class name in definition", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
std::string underlying_type = "int";
|
||||
if (Char(':')) {
|
||||
if (!Id(false)) {
|
||||
throw exception::eval_error("Expected underlying type after ':'",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
underlying_type = m_match_stack.back()->text;
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.push_back(
|
||||
make_node<eval::Constant_AST_Node<Tracer>>(underlying_type, m_position.line, m_position.col, const_var(underlying_type)));
|
||||
|
||||
if (!Char('{')) {
|
||||
throw exception::eval_error("Expected '{' after enum class declaration", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
int next_value = 0;
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (!Char('}')) {
|
||||
do {
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Expected enum value name", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
if (Symbol("=")) {
|
||||
if (!Num()) {
|
||||
throw exception::eval_error("Expected integer after '=' in enum definition",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
next_value = static_cast<int>(std::stoi(m_match_stack.back()->text));
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.push_back(
|
||||
make_node<eval::Constant_AST_Node<Tracer>>(std::to_string(next_value), m_position.line, m_position.col, const_var(next_value)));
|
||||
++next_value;
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
} while (Char(',') && !Char('}'));
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (!Char('}')) {
|
||||
throw exception::eval_error("Expected '}' to close enum class definition",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
}
|
||||
|
||||
build_match<eval::Enum_AST_Node<Tracer>>(prev_stack_top);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// Reads a while block from input
|
||||
bool While() {
|
||||
Depth_Counter dc{this};
|
||||
@ -2172,7 +2397,7 @@ namespace chaiscript {
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
if (Keyword("return")) {
|
||||
Operator();
|
||||
Equation();
|
||||
build_match<eval::Return_AST_Node<Tracer>>(prev_stack_top);
|
||||
return true;
|
||||
} else {
|
||||
@ -2212,7 +2437,7 @@ namespace chaiscript {
|
||||
bool retval = false;
|
||||
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
if (Lambda() || Num() || Quoted_String() || Single_Quoted_String() || Paren_Expression() || Inline_Container() || Id(false)) {
|
||||
if (Lambda() || Num() || Raw_String() || Quoted_String() || Single_Quoted_String() || Paren_Expression() || Inline_Container() || Id(false)) {
|
||||
retval = true;
|
||||
bool has_more = true;
|
||||
|
||||
@ -2269,7 +2494,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
build_match<eval::Array_Call_AST_Node<Tracer>>(prev_stack_top);
|
||||
} else if (Symbol(".")) {
|
||||
} else if (Symbol(".") || Symbol("::")) {
|
||||
has_more = true;
|
||||
if (!(Id(true))) {
|
||||
throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
@ -2324,6 +2549,18 @@ namespace chaiscript {
|
||||
throw exception::eval_error("Incomplete variable declaration", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
} else if (Keyword("const")) {
|
||||
retval = true;
|
||||
|
||||
// consume optional 'var' or 'auto' after 'const'
|
||||
Keyword("var") || Keyword("auto");
|
||||
|
||||
if (Id(true)) {
|
||||
build_match<eval::Const_Var_Decl_AST_Node<Tracer>>(prev_stack_top);
|
||||
} else {
|
||||
throw exception::eval_error("Incomplete const variable declaration", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
|
||||
} else if (Keyword("global")) {
|
||||
retval = true;
|
||||
|
||||
@ -2461,7 +2698,7 @@ namespace chaiscript {
|
||||
bool retval = false;
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
if (m_operators[t_precedence] != Operator_Precedence::Prefix) {
|
||||
if (t_precedence < m_operators.size() && m_operators[t_precedence] < Operator_Precedence::Prefix) {
|
||||
if (Operator(t_precedence + 1)) {
|
||||
retval = true;
|
||||
std::string oper;
|
||||
@ -2475,7 +2712,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",
|
||||
@ -2490,24 +2727,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;
|
||||
|
||||
@ -2614,7 +2851,7 @@ namespace chaiscript {
|
||||
|
||||
while (has_more) {
|
||||
const auto start = m_position;
|
||||
if (Def(true, t_class_name) || Var_Decl(true, t_class_name)) {
|
||||
if (Def(true, t_class_name)) {
|
||||
if (!saw_eol) {
|
||||
throw exception::eval_error("Two function definitions missing line separator",
|
||||
File_Position(start.line, start.col),
|
||||
@ -2623,6 +2860,15 @@ namespace chaiscript {
|
||||
has_more = true;
|
||||
retval = true;
|
||||
saw_eol = true;
|
||||
} else if (Var_Decl(true, t_class_name)) {
|
||||
if (!saw_eol) {
|
||||
throw exception::eval_error("Two expressions missing line separator",
|
||||
File_Position(start.line, start.col),
|
||||
*m_filename);
|
||||
}
|
||||
has_more = true;
|
||||
retval = true;
|
||||
saw_eol = false;
|
||||
} else if (Eol()) {
|
||||
has_more = true;
|
||||
retval = true;
|
||||
@ -2645,7 +2891,7 @@ namespace chaiscript {
|
||||
|
||||
while (has_more) {
|
||||
const auto start = m_position;
|
||||
if (Def() || Try() || If() || While() || Class(t_class_allowed) || For() || Switch()) {
|
||||
if (Def() || Try() || If() || While() || Namespace_Block() || Class(t_class_allowed) || Using(t_class_allowed) || Enum(t_class_allowed) || For() || Switch()) {
|
||||
if (!saw_eol) {
|
||||
throw exception::eval_error("Two function definitions missing line separator",
|
||||
File_Position(start.line, start.col),
|
||||
@ -2695,7 +2941,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 : begin + t_input.size();
|
||||
const auto end = begin == nullptr ? nullptr : std::next(begin, std::ssize(t_input));
|
||||
m_position = Position(begin, end);
|
||||
m_filename = std::make_shared<std::string>(std::move(t_fname));
|
||||
|
||||
|
||||
@ -426,6 +426,9 @@ namespace ChaiScript_Language {
|
||||
/// \brief Returns true if the type is "void"
|
||||
bool is_type_void() const;
|
||||
|
||||
/// \brief Returns true if the type is an arithmetic type (int, double, etc.)
|
||||
bool is_type_arithmetic() const;
|
||||
|
||||
/// \brief Returns the ChaiScript registered name for the type if one exists.
|
||||
string name() const;
|
||||
};
|
||||
@ -764,6 +767,90 @@ namespace ChaiScript_Language {
|
||||
/// \endcode
|
||||
bool call_exists(Function f, ...);
|
||||
|
||||
/// \brief Returns the type name of the given object as a string
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> type_name(1)
|
||||
/// int
|
||||
/// eval> type_name("hello")
|
||||
/// string
|
||||
/// \endcode
|
||||
///
|
||||
/// \sa Type_Info::name()
|
||||
/// \sa Object::get_type_info()
|
||||
string type_name(Object o);
|
||||
|
||||
/// \brief Returns true if the object is of the named type
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> is_type(1, "int")
|
||||
/// true
|
||||
/// eval> is_type(1, "string")
|
||||
/// false
|
||||
/// \endcode
|
||||
///
|
||||
/// \sa Object::is_type()
|
||||
bool is_type(Object o, string type_name);
|
||||
|
||||
/// \brief Returns true if a function with the given name is registered
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> function_exists("print")
|
||||
/// true
|
||||
/// eval> function_exists("nonexistent")
|
||||
/// false
|
||||
/// \endcode
|
||||
bool function_exists(string name);
|
||||
|
||||
/// \brief Returns a Map of all registered functions, keyed by function name
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> var funcs = get_functions()
|
||||
/// eval> funcs["print"].get_arity()
|
||||
/// 1
|
||||
/// \endcode
|
||||
///
|
||||
/// \sa Function
|
||||
Map get_functions();
|
||||
|
||||
/// \brief Returns a Map of all scripting objects (variables), keyed by name
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> var x = 42
|
||||
/// eval> var objs = get_objects()
|
||||
/// eval> objs.count("x")
|
||||
/// 1
|
||||
/// \endcode
|
||||
Map get_objects();
|
||||
|
||||
/// \brief Returns a Type_Info for the named type
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> type("int").name()
|
||||
/// int
|
||||
/// \endcode
|
||||
///
|
||||
/// \param type_name The name of the type to look up
|
||||
/// \param throw_on_fail If true (default), throws if the type is not found
|
||||
/// \sa Type_Info
|
||||
Type_Info type(string type_name, bool throw_on_fail = true);
|
||||
|
||||
/// \brief Prints all registered functions to stdout
|
||||
///
|
||||
/// Useful for debugging. Outputs a list of all functions registered in the system.
|
||||
void dump_system();
|
||||
|
||||
/// \brief Prints information about the given object to stdout
|
||||
///
|
||||
/// Useful for debugging. Outputs the type and value of the object.
|
||||
void dump_object(Object o);
|
||||
|
||||
/// \brief Reverses a Range object so that the elements are accessed in reverse
|
||||
Range retro(Range);
|
||||
|
||||
|
||||
@ -8,7 +8,9 @@
|
||||
#define CHAISCRIPT_UTILITY_FNV1A_HPP_
|
||||
|
||||
#include "../chaiscript_defines.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
|
||||
namespace chaiscript {
|
||||
namespace utility {
|
||||
@ -28,7 +30,7 @@ namespace chaiscript {
|
||||
|
||||
while (begin != end) {
|
||||
h = (h ^ (*begin)) * 0x01000193;
|
||||
++begin;
|
||||
std::advance(begin, 1);
|
||||
}
|
||||
return h;
|
||||
|
||||
@ -43,7 +45,7 @@ namespace chaiscript {
|
||||
|
||||
template<size_t N>
|
||||
static constexpr std::uint32_t hash(const char (&str)[N]) noexcept {
|
||||
return hash(std::begin(str), std::end(str) - 1);
|
||||
return hash(std::begin(str), std::prev(std::end(str)));
|
||||
}
|
||||
|
||||
static constexpr std::uint32_t hash(std::string_view sv) noexcept {
|
||||
@ -64,7 +66,7 @@ namespace chaiscript {
|
||||
hash += std::uint32_t(*begin);
|
||||
hash += hash << 10;
|
||||
hash ^= hash >> 6;
|
||||
++begin;
|
||||
std::advance(begin, 1);
|
||||
}
|
||||
|
||||
hash += hash << 3;
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
#include "../chaiscript_defines.hpp"
|
||||
#include "quick_flat_map.hpp"
|
||||
#include "unicode.hpp"
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
@ -44,21 +45,17 @@ 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 {
|
||||
Internal(std::nullptr_t)
|
||||
: d(nullptr) {
|
||||
}
|
||||
Internal()
|
||||
: d(nullptr) {
|
||||
}
|
||||
Internal(Class c)
|
||||
explicit Internal(std::nullptr_t) {}
|
||||
Internal() = default;
|
||||
Internal(const Class c)
|
||||
: d(make_type(c)) {
|
||||
}
|
||||
template<typename T>
|
||||
Internal(T t)
|
||||
explicit Internal(T t)
|
||||
: d(std::move(t)) {
|
||||
}
|
||||
|
||||
static Data make_type(Class c) {
|
||||
static Data make_type(const Class c) {
|
||||
switch (c) {
|
||||
case Class::Null:
|
||||
return nullptr;
|
||||
@ -84,7 +81,7 @@ namespace chaiscript::json {
|
||||
}
|
||||
}
|
||||
|
||||
Class type() const noexcept { return Class(d.index()); }
|
||||
[[nodiscard]] Class type() const noexcept { return Class(d.index()); }
|
||||
|
||||
template<auto ClassValue, typename Visitor, typename Or>
|
||||
decltype(auto) visit_or(Visitor &&visitor, Or &&other) const {
|
||||
@ -108,14 +105,14 @@ namespace chaiscript::json {
|
||||
auto &Float() { return get_set_type<Class::Floating>(); }
|
||||
auto &Bool() { return get_set_type<Class::Boolean>(); }
|
||||
|
||||
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); }
|
||||
[[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); }
|
||||
|
||||
Data d;
|
||||
Data d{nullptr};
|
||||
};
|
||||
|
||||
Internal internal;
|
||||
@ -131,10 +128,10 @@ namespace chaiscript::json {
|
||||
}
|
||||
JSONWrapper(std::nullptr_t) {}
|
||||
|
||||
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(); }
|
||||
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(); }
|
||||
};
|
||||
|
||||
template<typename Container>
|
||||
@ -147,10 +144,10 @@ namespace chaiscript::json {
|
||||
}
|
||||
JSONConstWrapper(std::nullptr_t) {}
|
||||
|
||||
typename Container::const_iterator begin() const noexcept {
|
||||
[[nodiscard]] Container::const_iterator begin() const noexcept {
|
||||
return object ? object->begin() : typename Container::const_iterator();
|
||||
}
|
||||
typename Container::const_iterator end() const noexcept { return object ? object->end() : typename Container::const_iterator(); }
|
||||
[[nodiscard]] Container::const_iterator end() const noexcept { return object ? object->end() : typename Container::const_iterator(); }
|
||||
};
|
||||
|
||||
JSON() = default;
|
||||
@ -162,7 +159,9 @@ namespace chaiscript::json {
|
||||
|
||||
JSON(initializer_list<JSON> list)
|
||||
: internal(Class::Object) {
|
||||
for (auto i = list.begin(), e = list.end(); i != e; ++i, ++i) {
|
||||
for (auto i = list.begin(), e = list.end();
|
||||
i != e;
|
||||
std::advance(i, 2)) {
|
||||
operator[](i->to_string()) = *std::next(i);
|
||||
}
|
||||
}
|
||||
@ -460,17 +459,21 @@ namespace chaiscript::json {
|
||||
val += '\t';
|
||||
break;
|
||||
case 'u': {
|
||||
val += "\\u";
|
||||
std::string hex_matches;
|
||||
for (size_t i = 1; i <= 4; ++i) {
|
||||
c = str.at(offset + i);
|
||||
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
|
||||
val += c;
|
||||
hex_matches += c;
|
||||
} else {
|
||||
throw std::runtime_error(
|
||||
std::string("JSON ERROR: String: Expected hex character in unicode escape, found '") + c + "'");
|
||||
}
|
||||
}
|
||||
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) {
|
||||
throw std::runtime_error(std::string("JSON ERROR: String: Invalid 32 bit universal character"));
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
val += '\\';
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#ifndef CHAISCRIPT_SIMPLEJSON_WRAP_HPP
|
||||
#define CHAISCRIPT_SIMPLEJSON_WRAP_HPP
|
||||
|
||||
#include "../dispatchkit/dynamic_object.hpp"
|
||||
#include "json.hpp"
|
||||
|
||||
namespace chaiscript {
|
||||
@ -8,7 +9,10 @@ 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;
|
||||
}
|
||||
@ -57,6 +61,63 @@ 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,6 +7,8 @@
|
||||
#ifndef CHAISCRIPT_UTILITY_STATIC_STRING_HPP_
|
||||
#define CHAISCRIPT_UTILITY_STATIC_STRING_HPP_
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace chaiscript::utility {
|
||||
struct Static_String {
|
||||
template<size_t N>
|
||||
@ -21,30 +23,17 @@ namespace chaiscript::utility {
|
||||
|
||||
constexpr auto begin() const noexcept { return data; }
|
||||
|
||||
constexpr auto end() const noexcept { return data + m_size; }
|
||||
constexpr auto end() const noexcept { return std::next(data, static_cast<std::ptrdiff_t>(m_size)); }
|
||||
|
||||
constexpr bool operator==(std::string_view other) const noexcept {
|
||||
// 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 auto operator[](const std::size_t idx) const noexcept {
|
||||
return *std::next(data, static_cast<std::ptrdiff_t>(idx));
|
||||
}
|
||||
|
||||
bool operator==(const std::string &t_str) const noexcept { return std::equal(begin(), end(), std::cbegin(t_str), std::cend(t_str)); }
|
||||
constexpr bool operator==(std::string_view other) const noexcept {
|
||||
return std::string_view(data, m_size) == other;
|
||||
}
|
||||
|
||||
constexpr 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;
|
||||
|
||||
93
include/chaiscript/utility/unicode.hpp
Normal file
93
include/chaiscript/utility/unicode.hpp
Normal file
@ -0,0 +1,93 @@
|
||||
// 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,6 +91,16 @@ the doxygen documentation in the build folder or see the website
|
||||
http://www.chaiscript.com.
|
||||
|
||||
|
||||
Grammar
|
||||
=======
|
||||
|
||||
A formal EBNF grammar for ChaiScript is available in
|
||||
[grammar/chaiscript.ebnf](grammar/chaiscript.ebnf). To view it as a railroad
|
||||
diagram, paste the grammar into
|
||||
[mingodad's railroad diagram generator](https://mingodad.github.io/plgh/json2ebnf.html)
|
||||
or [bottlecaps.de/rr](https://www.bottlecaps.de/rr/ui).
|
||||
|
||||
|
||||
The shortest complete example possible follows:
|
||||
|
||||
```C++
|
||||
|
||||
@ -4,6 +4,7 @@ Current Version: 6.1.1
|
||||
|
||||
### Changes since 6.1.0
|
||||
|
||||
* Add `set_file_reader` callback for custom file loading #116 — allows overriding how `eval_file` and `use` read files
|
||||
* Handle the returning of `&` to `*` types. This specifically comes up with `std::vector<int *>` and similar containers
|
||||
* Update CMake to use `LIBDIR` instead of `lib` #502 by @guoyunhe
|
||||
* Add documentation for installing ChaiScript with vcpkg #500 by @grdowns
|
||||
|
||||
@ -30,9 +30,8 @@ char *mystrdup(const char *s) {
|
||||
#ifdef CHAISCRIPT_MSVC
|
||||
strcpy_s(d, len + 1, s); // Copy the characters
|
||||
#else
|
||||
strncpy(d, s, len); // Copy the characters
|
||||
strncpy(d, s, len + 1); // Copy the characters
|
||||
#endif
|
||||
d[len] = '\0';
|
||||
return d; // Return the new string
|
||||
}
|
||||
|
||||
@ -297,7 +296,7 @@ int main(int argc, char *argv[]) {
|
||||
++i;
|
||||
}
|
||||
|
||||
std::string arg(i ? argv[i] : "--interactive");
|
||||
std::string arg(i ? *std::next(argv, i) : "--interactive");
|
||||
|
||||
enum {
|
||||
eInteractive,
|
||||
@ -311,7 +310,7 @@ int main(int argc, char *argv[]) {
|
||||
std::cout << "insufficient input following " << arg << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
arg = argv[++i];
|
||||
arg = *std::next(argv, ++i);
|
||||
}
|
||||
} else if (arg == "-" || arg == "--stdin") {
|
||||
arg = "";
|
||||
|
||||
@ -35,9 +35,8 @@ char *mystrdup(const char *s) {
|
||||
#ifdef CHAISCRIPT_MSVC
|
||||
strcpy_s(d, len + 1, s); // Copy the characters
|
||||
#else
|
||||
strncpy(d, s, len); // Copy the characters
|
||||
strncpy(d, s, len + 1); // Copy the characters
|
||||
#endif
|
||||
d[len] = '\0';
|
||||
return d; // Return the new string
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <regex>
|
||||
@ -33,9 +35,8 @@ char *mystrdup(const char *s) {
|
||||
#ifdef CHAISCRIPT_MSVC
|
||||
strcpy_s(d, len + 1, s); // Copy the characters
|
||||
#else
|
||||
strncpy(d, s, len); // Copy the characters
|
||||
strncpy(d, s, len + 1); // Copy the characters
|
||||
#endif
|
||||
d[len] = '\0';
|
||||
return d; // Return the new string
|
||||
}
|
||||
|
||||
@ -282,7 +283,7 @@ int main(int argc, char *argv[]) {
|
||||
++i;
|
||||
}
|
||||
|
||||
std::string arg(i != 0 ? argv[i] : "--interactive");
|
||||
std::string arg(i != 0 ? *std::next(argv, i) : "--interactive");
|
||||
|
||||
enum {
|
||||
eInteractive,
|
||||
@ -296,7 +297,7 @@ int main(int argc, char *argv[]) {
|
||||
std::cout << "insufficient input following " << arg << '\n';
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
arg = argv[++i];
|
||||
arg = *std::next(argv, ++i);
|
||||
|
||||
} else if (arg == "-" || arg == "--stdin") {
|
||||
arg = "";
|
||||
|
||||
30
src/sha3.h
30
src/sha3.h
@ -6,20 +6,19 @@
|
||||
|
||||
#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;
|
||||
@ -37,18 +36,21 @@ 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();
|
||||
@ -58,24 +60,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;
|
||||
};
|
||||
|
||||
43
unittests/add_vs_add_global.chai
Normal file
43
unittests/add_vs_add_global.chai
Normal file
@ -0,0 +1,43 @@
|
||||
// Test demonstrating the difference between add (local scope) and add_global/set_global (global scope)
|
||||
// See issue #554
|
||||
|
||||
// --- Local variables (var / add) are scoped ---
|
||||
// A local variable defined in a block is not visible outside that block
|
||||
var local_val = 10
|
||||
assert_equal(local_val, 10)
|
||||
|
||||
// Local variables can be reassigned in scope
|
||||
local_val = 20
|
||||
assert_equal(local_val, 20)
|
||||
|
||||
// Local variables inside a function are not visible outside
|
||||
def set_local() {
|
||||
var func_local = 99
|
||||
assert_equal(func_local, 99)
|
||||
}
|
||||
set_local()
|
||||
|
||||
// --- Globals (add_global) are visible everywhere, including inside functions ---
|
||||
var g_val = 42
|
||||
add_global(g_val, "my_global")
|
||||
|
||||
def check_global() {
|
||||
assert_equal(my_global, 42)
|
||||
}
|
||||
check_global()
|
||||
|
||||
// add_global throws if the global already exists
|
||||
assert_throws("Name already exists in current context my_global", fun() { add_global(1, "my_global") })
|
||||
|
||||
// --- set_global overwrites an existing global ---
|
||||
set_global(100, "my_global")
|
||||
assert_equal(my_global, 100)
|
||||
|
||||
def check_global_updated() {
|
||||
assert_equal(my_global, 100)
|
||||
}
|
||||
check_global_updated()
|
||||
|
||||
// set_global can also create a new global if it doesn't exist
|
||||
set_global(77, "new_global")
|
||||
assert_equal(new_global, 77)
|
||||
41
unittests/assign_no_aliasing.chai
Normal file
41
unittests/assign_no_aliasing.chai
Normal file
@ -0,0 +1,41 @@
|
||||
// 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)
|
||||
17
unittests/async_engine_lifetime.chai
Normal file
17
unittests/async_engine_lifetime.chai
Normal file
@ -0,0 +1,17 @@
|
||||
// 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);
|
||||
46
unittests/async_engine_lifetime_test.cpp
Normal file
46
unittests/async_engine_lifetime_test.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
// 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;
|
||||
}
|
||||
25
unittests/async_return_value.chai
Normal file
25
unittests/async_return_value.chai
Normal file
@ -0,0 +1,25 @@
|
||||
// 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
17969
unittests/catch.hpp
File diff suppressed because it is too large
Load Diff
11844
unittests/catch_amalgamated.cpp
Normal file
11844
unittests/catch_amalgamated.cpp
Normal file
File diff suppressed because it is too large
Load Diff
14059
unittests/catch_amalgamated.hpp
Normal file
14059
unittests/catch_amalgamated.hpp
Normal file
File diff suppressed because it is too large
Load Diff
103
unittests/class_cheatsheet.chai
Normal file
103
unittests/class_cheatsheet.chai
Normal file
@ -0,0 +1,103 @@
|
||||
// Test for class features documented in the cheatsheet
|
||||
|
||||
// --- Class definition (preferred block syntax) ---
|
||||
class Rectangle {
|
||||
var width
|
||||
var height
|
||||
def Rectangle(w, h) { this.width = w; this.height = h; }
|
||||
def Rectangle() { this.width = 0; this.height = 0; }
|
||||
def area() { this.width * this.height; }
|
||||
}
|
||||
|
||||
auto r = Rectangle(3, 4)
|
||||
assert_equal(12, r.area())
|
||||
|
||||
// --- Default constructor ---
|
||||
auto r2 = Rectangle()
|
||||
assert_equal(0, r2.area())
|
||||
|
||||
// --- Alternative open syntax ---
|
||||
attr Circle::radius
|
||||
def Circle::Circle(r) { this.radius = r; }
|
||||
def Circle::circumference() { 2.0 * 3.14159 * this.radius; }
|
||||
|
||||
auto c = Circle(5.0)
|
||||
assert_equal(5.0, c.radius)
|
||||
|
||||
// --- attr, auto, var all work for attributes ---
|
||||
class AttrTest {
|
||||
attr a
|
||||
auto b
|
||||
var c
|
||||
def AttrTest() { this.a = 1; this.b = 2; this.c = 3; }
|
||||
}
|
||||
|
||||
auto at = AttrTest()
|
||||
assert_equal(1, at.a)
|
||||
assert_equal(2, at.b)
|
||||
assert_equal(3, at.c)
|
||||
|
||||
// --- Constructor guards ---
|
||||
class Clamped {
|
||||
var value
|
||||
def Clamped(x) : x >= 0 { this.value = x; }
|
||||
def Clamped(x) { this.value = 0; }
|
||||
}
|
||||
|
||||
assert_equal(5, Clamped(5).value)
|
||||
assert_equal(0, Clamped(-3).value)
|
||||
|
||||
// --- Method guards ---
|
||||
class Abs {
|
||||
var x
|
||||
def Abs(v) { this.x = v; }
|
||||
def get() : this.x >= 0 { this.x; }
|
||||
def get() { -this.x; }
|
||||
}
|
||||
|
||||
assert_equal(5, Abs(5).get())
|
||||
assert_equal(3, Abs(-3).get())
|
||||
|
||||
// --- Operator overloading ---
|
||||
class Vec2 {
|
||||
var x
|
||||
var y
|
||||
def Vec2(x, y) { this.x = x; this.y = y; }
|
||||
def `+`(other) { Vec2(this.x + other.x, this.y + other.y); }
|
||||
}
|
||||
|
||||
auto v = Vec2(1, 2) + Vec2(3, 4)
|
||||
assert_equal(4, v.x)
|
||||
assert_equal(6, v.y)
|
||||
|
||||
// --- Cloning objects ---
|
||||
auto r3 = Rectangle(10, 20)
|
||||
auto r4 = clone(r3)
|
||||
r4.width = 99
|
||||
assert_equal(10, r3.width)
|
||||
assert_equal(99, r4.width)
|
||||
|
||||
// --- Dynamic attributes ---
|
||||
auto r5 = Rectangle(1, 1)
|
||||
r5.color = "red"
|
||||
assert_equal("red", r5.color)
|
||||
|
||||
// --- Explicit mode ---
|
||||
class Strict {
|
||||
var x
|
||||
def Strict() {
|
||||
this.x = 0
|
||||
this.set_explicit(true)
|
||||
}
|
||||
}
|
||||
|
||||
auto s = Strict()
|
||||
assert_equal(0, s.x)
|
||||
assert_equal(true, s.is_explicit())
|
||||
|
||||
try {
|
||||
s.y = 10
|
||||
assert_equal(true, false) // should not reach here
|
||||
} catch(e) {
|
||||
// expected: cannot add dynamic attribute in explicit mode
|
||||
}
|
||||
137
unittests/class_inheritance.chai
Normal file
137
unittests/class_inheritance.chai
Normal file
@ -0,0 +1,137 @@
|
||||
|
||||
class Base
|
||||
{
|
||||
attr x
|
||||
|
||||
def Base()
|
||||
{
|
||||
this.x = 10
|
||||
}
|
||||
|
||||
def do_something()
|
||||
{
|
||||
return this.x * 2
|
||||
}
|
||||
}
|
||||
|
||||
class Derived : Base
|
||||
{
|
||||
attr y
|
||||
|
||||
def Derived()
|
||||
{
|
||||
this.x = 10
|
||||
this.y = 20
|
||||
}
|
||||
|
||||
def do_other()
|
||||
{
|
||||
return this.y * 3
|
||||
}
|
||||
}
|
||||
|
||||
// Test basic inheritance - derived can call base methods
|
||||
auto d = Derived()
|
||||
assert_equal(10, d.x)
|
||||
assert_equal(20, d.y)
|
||||
assert_equal(20, d.do_something())
|
||||
assert_equal(60, d.do_other())
|
||||
|
||||
// Test base class still works independently
|
||||
auto b = Base()
|
||||
assert_equal(10, b.x)
|
||||
assert_equal(20, b.do_something())
|
||||
|
||||
// Test method override
|
||||
class Derived2 : Base
|
||||
{
|
||||
def Derived2()
|
||||
{
|
||||
this.x = 5
|
||||
}
|
||||
|
||||
def do_something()
|
||||
{
|
||||
return this.x * 100
|
||||
}
|
||||
}
|
||||
|
||||
auto d2 = Derived2()
|
||||
assert_equal(500, d2.do_something())
|
||||
|
||||
// Test passing a derived object to an untyped free function
|
||||
def call_do_something_untyped(obj) {
|
||||
return obj.do_something()
|
||||
}
|
||||
|
||||
auto d3 = Derived()
|
||||
assert_equal(20, call_do_something_untyped(d3))
|
||||
assert_equal(20, call_do_something_untyped(Base()))
|
||||
|
||||
// Test typed functions: parameter declared as Base, accepts derived objects
|
||||
def call_do_something(Base obj) {
|
||||
return obj.do_something()
|
||||
}
|
||||
|
||||
assert_equal(20, call_do_something(Base()))
|
||||
assert_equal(20, call_do_something(d3))
|
||||
|
||||
// Test typed function accessing base attributes on a derived object
|
||||
def get_x(Base obj) {
|
||||
return obj.x
|
||||
}
|
||||
|
||||
assert_equal(10, get_x(d3))
|
||||
assert_equal(10, get_x(Base()))
|
||||
|
||||
// Test polymorphic dispatch through typed function: derived override is called
|
||||
auto d4 = Derived2()
|
||||
assert_equal(500, call_do_something(d4))
|
||||
|
||||
// Test mixing base and derived in a container, calling base methods
|
||||
var objects = [Base(), Derived(), Derived2()]
|
||||
assert_equal(20, objects[0].do_something())
|
||||
assert_equal(20, objects[1].do_something())
|
||||
assert_equal(500, objects[2].do_something())
|
||||
|
||||
// Test that derived objects still report correct type
|
||||
auto d5 = Derived()
|
||||
assert_true(d5.is_type("Derived"))
|
||||
|
||||
// Test multi-level inheritance
|
||||
class GrandChild : Derived
|
||||
{
|
||||
attr z
|
||||
|
||||
def GrandChild()
|
||||
{
|
||||
this.x = 1
|
||||
this.y = 2
|
||||
this.z = 3
|
||||
}
|
||||
|
||||
def do_grandchild()
|
||||
{
|
||||
return this.z * 4
|
||||
}
|
||||
}
|
||||
|
||||
auto gc = GrandChild()
|
||||
assert_equal(1, gc.x)
|
||||
assert_equal(2, gc.y)
|
||||
assert_equal(3, gc.z)
|
||||
assert_equal(2, gc.do_something()) // Base method
|
||||
assert_equal(6, gc.do_other()) // Derived method
|
||||
assert_equal(12, gc.do_grandchild()) // Own method
|
||||
|
||||
// Test passing grandchild to typed Base function (multi-level inheritance)
|
||||
assert_equal(2, call_do_something(gc))
|
||||
assert_equal(1, get_x(gc))
|
||||
|
||||
// Test typed function expecting mid-level type
|
||||
def call_do_other(Derived obj) {
|
||||
return obj.do_other()
|
||||
}
|
||||
|
||||
assert_equal(6, call_do_other(gc))
|
||||
assert_equal(60, call_do_other(Derived()))
|
||||
23
unittests/class_missing_line_separator.chai
Normal file
23
unittests/class_missing_line_separator.chai
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
// Test that two var declarations on the same line in a class body
|
||||
// are rejected with a "missing line separator" error (issue #592)
|
||||
|
||||
try {
|
||||
eval("class Foo { var x var y }")
|
||||
assert_true(false)
|
||||
} catch (eval_error) {
|
||||
}
|
||||
|
||||
// Also verify that properly separated declarations still work
|
||||
class Bar {
|
||||
var x
|
||||
var y
|
||||
def Bar() {
|
||||
this.x = 1
|
||||
this.y = 2
|
||||
}
|
||||
}
|
||||
|
||||
auto b = Bar()
|
||||
assert_equal(1, b.x)
|
||||
assert_equal(2, b.y)
|
||||
@ -22,12 +22,10 @@
|
||||
#include "../static_libs/chaiscript_parser.hpp"
|
||||
#include "../static_libs/chaiscript_stdlib.hpp"
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch_amalgamated.hpp"
|
||||
|
||||
#include <clocale>
|
||||
|
||||
#include "catch.hpp"
|
||||
|
||||
// lambda_tests
|
||||
TEST_CASE("C++11 Lambdas Can Be Registered") {
|
||||
// We cannot deduce the type of a lambda expression, you must either wrap it
|
||||
@ -190,7 +188,7 @@ TEST_CASE("Throw int or double") {
|
||||
chai.eval("throw(1.0)", chaiscript::exception_specification<int, double>());
|
||||
REQUIRE(false);
|
||||
} catch (const double e) {
|
||||
CHECK(e == Approx(1.0));
|
||||
CHECK(e == Catch::Approx(1.0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -412,6 +410,44 @@ TEST_CASE("Set and restore chai state") {
|
||||
CHECK_THROWS_AS(chai.eval<int>("i"), chaiscript::exception::eval_error);
|
||||
}
|
||||
|
||||
TEST_CASE("Get function objects from public API") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
// Add a custom function
|
||||
chai.add(chaiscript::fun(&set_state_test_myfun), "myfun");
|
||||
|
||||
// get_function_objects should be accessible from the public API
|
||||
auto funcs = chai.get_function_objects();
|
||||
|
||||
// Our custom function should be in the map
|
||||
CHECK(funcs.count("myfun") == 1);
|
||||
|
||||
// Built-in functions should also be present
|
||||
CHECK(funcs.count("to_string") == 1);
|
||||
|
||||
// The function should be callable
|
||||
CHECK(chai.eval<int>("myfun()") == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Get scripting objects from public API") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
// Define some variables in script
|
||||
chai.eval("var x = 42");
|
||||
chai.eval("var name = \"hello\"");
|
||||
|
||||
// get_scripting_objects should be accessible from the public API
|
||||
auto objects = chai.get_scripting_objects();
|
||||
|
||||
// Our scripting variables should be in the map
|
||||
CHECK(objects.count("x") == 1);
|
||||
CHECK(objects.count("name") == 1);
|
||||
|
||||
// Verify the values are correct
|
||||
CHECK(chaiscript::boxed_cast<int>(objects["x"]) == 42);
|
||||
CHECK(chaiscript::boxed_cast<std::string>(objects["name"]) == "hello");
|
||||
}
|
||||
|
||||
//// Short comparisons
|
||||
|
||||
class Short_Comparison_Test {
|
||||
@ -566,6 +602,49 @@ TEST_CASE("Utility_Test utility class wrapper for enum") {
|
||||
CHECK_NOTHROW(chai.eval("var o = ONE; o = TWO"));
|
||||
}
|
||||
|
||||
// Issue #601: add_class for enums should work directly with ChaiScript reference
|
||||
enum class Issue601_EnumClass { Apple,
|
||||
Banana,
|
||||
Pear };
|
||||
|
||||
TEST_CASE("Issue 601: add_class enum with ChaiScript reference directly") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
// This should compile and work — previously it failed because the operator
|
||||
// functions in chaiscript::bootstrap::operators hardcoded Module& as their
|
||||
// first parameter instead of using a template parameter.
|
||||
chaiscript::utility::add_class<Issue601_EnumClass>(chai,
|
||||
"Issue601_EnumClass",
|
||||
{{Issue601_EnumClass::Apple, "Apple"},
|
||||
{Issue601_EnumClass::Banana, "Banana"},
|
||||
{Issue601_EnumClass::Pear, "Pear"}});
|
||||
|
||||
CHECK(chai.eval<bool>("Apple == Apple"));
|
||||
CHECK(chai.eval<bool>("Apple != Banana"));
|
||||
CHECK_NOTHROW(chai.eval("var e = Apple; e = Pear"));
|
||||
CHECK(chai.eval<Issue601_EnumClass>("Banana") == Issue601_EnumClass::Banana);
|
||||
}
|
||||
|
||||
// Also test non-scoped enum directly with ChaiScript reference
|
||||
enum Issue601_PlainEnum { Issue601_Red = 0,
|
||||
Issue601_Green = 1,
|
||||
Issue601_Blue = 2 };
|
||||
|
||||
TEST_CASE("Issue 601: add_class plain enum with ChaiScript reference directly") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chaiscript::utility::add_class<Issue601_PlainEnum>(chai,
|
||||
"Issue601_PlainEnum",
|
||||
{{Issue601_Red, "Red"},
|
||||
{Issue601_Green, "Green"},
|
||||
{Issue601_Blue, "Blue"}});
|
||||
|
||||
CHECK(chai.eval<bool>("Red == Red"));
|
||||
CHECK(chai.eval<bool>("Red == 0"));
|
||||
CHECK(chai.eval<bool>("Red != Green"));
|
||||
CHECK_NOTHROW(chai.eval("var c = Red; c = Blue"));
|
||||
}
|
||||
|
||||
////// Object copy count test
|
||||
|
||||
class Object_Copy_Count_Test {
|
||||
@ -866,6 +945,27 @@ TEST_CASE("Map conversions") {
|
||||
CHECK(c == 42);
|
||||
}
|
||||
|
||||
TEST_CASE("Pair conversions") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
chai.add(chaiscript::pair_conversion<std::string, std::string>());
|
||||
chai.add(chaiscript::pair_conversion<int, double>());
|
||||
|
||||
{
|
||||
const auto p = chai.eval<std::pair<std::string, std::string>>(R"cs(
|
||||
Pair("chai", "script");
|
||||
)cs");
|
||||
CHECK(p.first == std::string{"chai"});
|
||||
CHECK(p.second == "script");
|
||||
}
|
||||
{
|
||||
const auto p = chai.eval<std::pair<int, double>>(R"cs(
|
||||
Pair(5, 3.14);
|
||||
)cs");
|
||||
CHECK(p.first == 5);
|
||||
CHECK(p.second == Catch::Approx(3.14));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Parse floats with non-posix locale") {
|
||||
#ifdef CHAISCRIPT_MSVC
|
||||
std::setlocale(LC_ALL, "en-ZA");
|
||||
@ -874,7 +974,7 @@ TEST_CASE("Parse floats with non-posix locale") {
|
||||
#endif
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
const double parsed = chai.eval<double>("print(1.3); 1.3");
|
||||
CHECK(parsed == Approx(1.3));
|
||||
CHECK(parsed == Catch::Approx(1.3));
|
||||
const std::string str = chai.eval<std::string>("to_string(1.3)");
|
||||
CHECK(str == "1.3");
|
||||
}
|
||||
@ -1168,7 +1268,7 @@ TEST_CASE("Test reference member being registered") {
|
||||
double d;
|
||||
chai.add(chaiscript::var(Reference_MyClass(d)), "ref");
|
||||
chai.eval("ref.x = 2.3");
|
||||
CHECK(d == Approx(2.3));
|
||||
CHECK(d == Catch::Approx(2.3));
|
||||
}
|
||||
|
||||
// starting with C++20 u8"" strings cannot be compared with std::string
|
||||
@ -1283,3 +1383,824 @@ TEST_CASE("Test if non copyable/movable types can be registered") {
|
||||
chai.add(chaiscript::user_type<Nothing>(), "Nothing");
|
||||
chai.add(chaiscript::constructor<Nothing()>(), "Nothing");
|
||||
}
|
||||
|
||||
// Tests for issue #146: configuration to bypass registering built-in functions
|
||||
// Tests through ChaiScript_Basic (library options passed explicitly to Std_Lib::library)
|
||||
|
||||
TEST_CASE("ChaiScript_Basic No_Stdlib option disables all standard library functions") {
|
||||
chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_Stdlib}),
|
||||
create_chaiscript_parser(),
|
||||
{},
|
||||
{},
|
||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts});
|
||||
|
||||
CHECK_NOTHROW(chai.eval("var x = 5"));
|
||||
CHECK_THROWS(chai.eval("print(\"hello\")"));
|
||||
CHECK_THROWS(chai.eval("var v = Vector()"));
|
||||
CHECK_THROWS(chai.eval("\"hello\".trim()"));
|
||||
CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")"));
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript_Basic No_IO option uses null handler by default") {
|
||||
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},
|
||||
true);
|
||||
|
||||
// print_string and println_string should still be available via the handler mechanism
|
||||
// but the default handler is a no-op (no stdout output)
|
||||
CHECK_NOTHROW(chai.eval("print_string(\"hello\")"));
|
||||
CHECK_NOTHROW(chai.eval("println_string(\"hello\")"));
|
||||
CHECK(chai.eval<int>("5 + 3") == 8);
|
||||
CHECK_NOTHROW(chai.eval("var v = Vector()"));
|
||||
|
||||
// Users can set their own print handler even with No_IO
|
||||
std::string captured;
|
||||
chai.set_print_handler([&captured](const std::string &s) { captured += s; });
|
||||
chai.eval("print_string(\"redirected\")");
|
||||
CHECK(captured == "redirected");
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript_Basic No_Prelude option disables prelude functions") {
|
||||
chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_Prelude}),
|
||||
create_chaiscript_parser(),
|
||||
{},
|
||||
{},
|
||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts});
|
||||
|
||||
CHECK_THROWS(chai.eval("print(\"hello\")"));
|
||||
CHECK_NOTHROW(chai.eval("print_string(\"hello\")"));
|
||||
CHECK_THROWS(chai.eval("filter([1,2,3], fun(x) { x > 1 })"));
|
||||
CHECK(chai.eval<int>("5 + 3") == 8);
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript_Basic No_JSON option disables JSON support") {
|
||||
chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_JSON}),
|
||||
create_chaiscript_parser(),
|
||||
{},
|
||||
{},
|
||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts});
|
||||
|
||||
CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")"));
|
||||
CHECK(chai.eval<int>("5 + 3") == 8);
|
||||
CHECK_NOTHROW(chai.eval("print(\"hello\")"));
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript_Basic default library has all functions") {
|
||||
chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library(),
|
||||
create_chaiscript_parser(),
|
||||
{},
|
||||
{},
|
||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts});
|
||||
|
||||
CHECK_NOTHROW(chai.eval("print(\"hello\")"));
|
||||
CHECK_NOTHROW(chai.eval("print_string(\"hello\")"));
|
||||
CHECK_NOTHROW(chai.eval("var v = Vector()"));
|
||||
CHECK(chai.eval<int>("5 + 3") == 8);
|
||||
}
|
||||
|
||||
// Tests through ChaiScript (library options passed as constructor parameter)
|
||||
|
||||
TEST_CASE("ChaiScript No_Stdlib option via library options parameter") {
|
||||
chaiscript::ChaiScript chai({},
|
||||
{},
|
||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts},
|
||||
{chaiscript::Library_Options::No_Stdlib});
|
||||
|
||||
CHECK_NOTHROW(chai.eval("var x = 5"));
|
||||
CHECK_THROWS(chai.eval("print(\"hello\")"));
|
||||
CHECK_THROWS(chai.eval("var v = Vector()"));
|
||||
CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")"));
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript No_IO option via library options parameter") {
|
||||
chaiscript::ChaiScript chai({},
|
||||
{},
|
||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts},
|
||||
{chaiscript::Library_Options::No_IO});
|
||||
|
||||
// print_string and println_string remain available via the handler mechanism
|
||||
// but the default handler is a no-op (no stdout output)
|
||||
CHECK_NOTHROW(chai.eval("print_string(\"hello\")"));
|
||||
CHECK_NOTHROW(chai.eval("println_string(\"hello\")"));
|
||||
CHECK(chai.eval<int>("5 + 3") == 8);
|
||||
CHECK_NOTHROW(chai.eval("var v = Vector()"));
|
||||
|
||||
// Users can override the null handler with their own
|
||||
std::string captured;
|
||||
chai.set_print_handler([&captured](const std::string &s) { captured += s; });
|
||||
chai.eval("print_string(\"redirected\")");
|
||||
CHECK(captured == "redirected");
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript No_Prelude option via library options parameter") {
|
||||
chaiscript::ChaiScript chai({},
|
||||
{},
|
||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts},
|
||||
{chaiscript::Library_Options::No_Prelude});
|
||||
|
||||
CHECK_THROWS(chai.eval("print(\"hello\")"));
|
||||
CHECK_NOTHROW(chai.eval("print_string(\"hello\")"));
|
||||
CHECK_THROWS(chai.eval("filter([1,2,3], fun(x) { x > 1 })"));
|
||||
CHECK(chai.eval<int>("5 + 3") == 8);
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript No_JSON option via library options parameter") {
|
||||
chaiscript::ChaiScript chai({},
|
||||
{},
|
||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts},
|
||||
{chaiscript::Library_Options::No_JSON});
|
||||
|
||||
CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")"));
|
||||
CHECK(chai.eval<int>("5 + 3") == 8);
|
||||
CHECK_NOTHROW(chai.eval("print(\"hello\")"));
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript default has all functions") {
|
||||
chaiscript::ChaiScript chai;
|
||||
|
||||
CHECK_NOTHROW(chai.eval("print(\"hello\")"));
|
||||
CHECK_NOTHROW(chai.eval("print_string(\"hello\")"));
|
||||
CHECK_NOTHROW(chai.eval("var v = Vector()"));
|
||||
CHECK(chai.eval<int>("5 + 3") == 8);
|
||||
}
|
||||
|
||||
// Issue #421: Class with type_conversion from int and "==" operator
|
||||
// causes switch statement to compare destroyed objects.
|
||||
// The switch case comparison must use Function_Push_Pop to properly
|
||||
// manage the lifetime of temporaries created by type conversions.
|
||||
TEST_CASE("Issue #421 - Switch with type_conversion does not compare destroyed objects") {
|
||||
struct MyType {
|
||||
int value;
|
||||
explicit MyType(int v)
|
||||
: value(v) {}
|
||||
};
|
||||
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
chai.add(chaiscript::user_type<MyType>(), "MyType");
|
||||
chai.add(chaiscript::constructor<MyType(int)>(), "MyType");
|
||||
chai.add(chaiscript::fun(&MyType::value), "value");
|
||||
chai.add(chaiscript::fun([](const MyType &a, const MyType &b) { return a.value == b.value; }), "==");
|
||||
chai.add(chaiscript::type_conversion<int, MyType>([](const int &i) { return MyType(i); }));
|
||||
|
||||
// Test switch with type conversion - the case integer literals must be
|
||||
// converted to MyType via the registered conversion before comparison.
|
||||
// Without Function_Push_Pop, the converted temporaries may be destroyed
|
||||
// before the == operator can compare them.
|
||||
CHECK(chai.eval<int>(R"({
|
||||
var result = 0
|
||||
var obj = MyType(2)
|
||||
switch(obj) {
|
||||
case (1) {
|
||||
result = 1
|
||||
break
|
||||
}
|
||||
case (2) {
|
||||
result = 2
|
||||
break
|
||||
}
|
||||
case (3) {
|
||||
result = 3
|
||||
break
|
||||
}
|
||||
}
|
||||
result
|
||||
})") == 2);
|
||||
|
||||
// Test fall-through with type conversion
|
||||
CHECK(chai.eval<int>(R"({
|
||||
var total = 0
|
||||
var obj = MyType(2)
|
||||
switch(obj) {
|
||||
case (1) {
|
||||
total += 1
|
||||
}
|
||||
case (2) {
|
||||
total += 2
|
||||
}
|
||||
case (3) {
|
||||
total += 4
|
||||
}
|
||||
}
|
||||
total
|
||||
})") == 6);
|
||||
|
||||
// Test matching the first case
|
||||
CHECK(chai.eval<int>(R"({
|
||||
var result = 0
|
||||
var obj = MyType(1)
|
||||
switch(obj) {
|
||||
case (1) {
|
||||
result = 10
|
||||
break
|
||||
}
|
||||
case (2) {
|
||||
result = 20
|
||||
break
|
||||
}
|
||||
}
|
||||
result
|
||||
})") == 10);
|
||||
|
||||
// Test no match
|
||||
CHECK(chai.eval<int>(R"({
|
||||
var result = 0
|
||||
var obj = MyType(5)
|
||||
switch(obj) {
|
||||
case (1) {
|
||||
result = 1
|
||||
break
|
||||
}
|
||||
case (2) {
|
||||
result = 2
|
||||
break
|
||||
}
|
||||
}
|
||||
result
|
||||
})") == 0);
|
||||
}
|
||||
|
||||
// Issue #524: A std::vector of std::unique_ptrs can't be added
|
||||
// vector_type should compile with non-copyable value types by
|
||||
// skipping copy-dependent operations via if constexpr.
|
||||
struct Issue524_Foo {
|
||||
int value = 42;
|
||||
};
|
||||
|
||||
TEST_CASE("Issue #524 - vector of unique_ptr can be registered") {
|
||||
using VecType = std::vector<std::unique_ptr<Issue524_Foo>>;
|
||||
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
// This should compile and not throw - previously failed to compile
|
||||
// because vector_type tried to instantiate copy constructor, assignment,
|
||||
// push_back(const T&), insert_at(const T&), and resize(n, const T&)
|
||||
// for the non-copyable std::unique_ptr<Issue524_Foo>.
|
||||
chaiscript::ModulePtr m = std::make_shared<chaiscript::Module>();
|
||||
chaiscript::bootstrap::standard_library::vector_type<VecType>("UniqueVec", *m);
|
||||
CHECK_NOTHROW(chai.add(m));
|
||||
|
||||
// Verify basic operations still work
|
||||
CHECK(chai.eval<size_t>("var v = UniqueVec(); v.size()") == 0);
|
||||
CHECK(chai.eval<bool>("var v2 = UniqueVec(); v2.empty()") == true);
|
||||
}
|
||||
|
||||
// Issue #625: function_less_than comparator must satisfy strict-weak ordering.
|
||||
// Registering overloaded functions with different arities triggered a
|
||||
// std::stable_sort assertion on macOS 15.2 (hardened libc++) because the
|
||||
// comparator violated transitivity of equivalence.
|
||||
TEST_CASE("Issue 625: function_less_than strict-weak ordering with different arities") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
// Register overloaded functions with varying arities under the same name.
|
||||
// If the comparator doesn't order by arity when overlapping params match,
|
||||
// std::stable_sort may exhibit undefined behavior.
|
||||
CHECK_NOTHROW(chai.add(chaiscript::fun([](int x, const chaiscript::Boxed_Value &) { return x; }), "overloaded"));
|
||||
CHECK_NOTHROW(chai.add(chaiscript::fun([](int x, double y) { return x + static_cast<int>(y); }), "overloaded"));
|
||||
CHECK_NOTHROW(chai.add(chaiscript::fun([](int x) { return x; }), "overloaded"));
|
||||
|
||||
// Verify dispatch still works correctly
|
||||
CHECK(chai.eval<int>("overloaded(5)") == 5);
|
||||
CHECK(chai.eval<int>("overloaded(3, 2.0)") == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("IO redirection with set_print_handler") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
std::string captured_output;
|
||||
|
||||
// Set custom print handler — both print_string and println_string dispatch through it
|
||||
chai.set_print_handler([&captured_output](const std::string &s) {
|
||||
captured_output += s;
|
||||
});
|
||||
|
||||
// Test that puts() uses the custom handler
|
||||
captured_output.clear();
|
||||
chai.eval("puts(\"hello\")");
|
||||
CHECK(captured_output == "hello");
|
||||
|
||||
// Test that print() uses the custom handler (println_string appends newline before calling handler)
|
||||
captured_output.clear();
|
||||
chai.eval("print(\"world\")");
|
||||
CHECK(captured_output == "world\n");
|
||||
|
||||
// Test that print_string() directly uses the custom handler
|
||||
captured_output.clear();
|
||||
chai.eval("print_string(\"direct\")");
|
||||
CHECK(captured_output == "direct");
|
||||
|
||||
// Test that println_string() directly uses the custom handler with newline
|
||||
captured_output.clear();
|
||||
chai.eval("println_string(\"direct_ln\")");
|
||||
CHECK(captured_output == "direct_ln\n");
|
||||
}
|
||||
|
||||
TEST_CASE("IO redirection captures numeric output") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
std::string captured_output;
|
||||
chai.set_print_handler([&captured_output](const std::string &s) {
|
||||
captured_output += s;
|
||||
});
|
||||
|
||||
chai.eval("print(42)");
|
||||
CHECK(captured_output == "42\n");
|
||||
}
|
||||
|
||||
TEST_CASE("IO redirection different instances are independent") {
|
||||
chaiscript::ChaiScript_Basic chai1(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
chaiscript::ChaiScript_Basic chai2(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
std::string output1;
|
||||
std::string output2;
|
||||
|
||||
chai1.set_print_handler([&output1](const std::string &s) { output1 += s; });
|
||||
chai2.set_print_handler([&output2](const std::string &s) { output2 += s; });
|
||||
|
||||
chai1.eval("print(\"from1\")");
|
||||
chai2.eval("print(\"from2\")");
|
||||
|
||||
CHECK(output1 == "from1\n");
|
||||
CHECK(output2 == "from2\n");
|
||||
}
|
||||
|
||||
TEST_CASE("set_print_handler accessible from ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
auto captured = std::make_shared<std::string>();
|
||||
chai.add(chaiscript::fun([captured](const std::string &s) { *captured += s; }), "test_output_sink");
|
||||
|
||||
// Set the print handler from within ChaiScript
|
||||
chai.eval("set_print_handler(fun(s) { test_output_sink(s) })");
|
||||
|
||||
chai.eval("print(\"from_script\")");
|
||||
CHECK(*captured == "from_script\n");
|
||||
|
||||
captured->clear();
|
||||
chai.eval("puts(\"no_newline\")");
|
||||
CHECK(*captured == "no_newline");
|
||||
}
|
||||
|
||||
// Regression test: push_back() on script-created vector has no effect when
|
||||
// vector_conversion is in effect. The bug occurs because dispatch selects
|
||||
// the C++ push_back for the converted type over the built-in one, operating
|
||||
// on a temporary copy of the vector.
|
||||
TEST_CASE("push_back on script vector with vector_conversion") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
auto m = std::make_shared<chaiscript::Module>();
|
||||
chaiscript::bootstrap::standard_library::vector_type<std::vector<std::string>>("VectorString", *m);
|
||||
m->add(chaiscript::vector_conversion<std::vector<std::string>>());
|
||||
chai.add(m);
|
||||
|
||||
// Register a C++ function that accepts the converted type, so we can
|
||||
// verify that vector_conversion actually works for passing vectors
|
||||
chai.add(chaiscript::fun([](const std::vector<std::string> &v) -> std::string {
|
||||
std::string result;
|
||||
for (const auto &s : v) {
|
||||
if (!result.empty()) { result += ","; }
|
||||
result += s;
|
||||
}
|
||||
return result;
|
||||
}),
|
||||
"join_strings");
|
||||
|
||||
// push_back on an empty script-created vector must be visible
|
||||
CHECK(chai.eval<bool>(
|
||||
"auto x = [];"
|
||||
"x.push_back(\"Hello\");"
|
||||
"x.size() == 1"));
|
||||
|
||||
// push_back on a vector with initial elements must grow correctly
|
||||
CHECK(chai.eval<bool>(
|
||||
"auto y = [\"a\", \"b\"];"
|
||||
"y.push_back(\"c\");"
|
||||
"y.push_back(\"d\");"
|
||||
"y.size() == 4"));
|
||||
|
||||
// Verify the actual content is preserved after push_back
|
||||
CHECK(chai.eval<std::string>(
|
||||
"auto z = [];"
|
||||
"z.push_back(\"World\");"
|
||||
"z[0]")
|
||||
== "World");
|
||||
|
||||
// Round-trip: build a vector in script, push_back elements, then pass it
|
||||
// to a C++ function via vector_conversion and verify the contents
|
||||
CHECK(chai.eval<std::string>(
|
||||
"auto v = [\"one\", \"two\"];"
|
||||
"v.push_back(\"three\");"
|
||||
"join_strings(v)")
|
||||
== "one,two,three");
|
||||
|
||||
// Verify conversion works on a freshly created vector too
|
||||
CHECK(chai.eval<std::string>(
|
||||
"auto w = [];"
|
||||
"w.push_back(\"hello\");"
|
||||
"w.push_back(\"world\");"
|
||||
"join_strings(w)")
|
||||
== "hello,world");
|
||||
}
|
||||
|
||||
TEST_CASE("vector of vectors conversion (issue #374)") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
auto m = std::make_shared<chaiscript::Module>();
|
||||
chaiscript::bootstrap::standard_library::vector_type<std::vector<double>>("VectorDouble", *m);
|
||||
chaiscript::bootstrap::standard_library::vector_type<std::vector<std::vector<double>>>("VectorVectorDouble", *m);
|
||||
m->add(chaiscript::vector_conversion<std::vector<double>>());
|
||||
m->add(chaiscript::vector_conversion<std::vector<std::vector<double>>>());
|
||||
chai.add(m);
|
||||
|
||||
chai.add(chaiscript::fun([](const std::vector<std::vector<double>> &v) -> double {
|
||||
double sum = 0;
|
||||
for (const auto &inner : v) {
|
||||
for (const auto d : inner) {
|
||||
sum += d;
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}),
|
||||
"sum_nested");
|
||||
|
||||
CHECK(chai.eval<double>("sum_nested([[1.0, 2.0], [3.0, 4.0]])") == Catch::Approx(10.0));
|
||||
|
||||
CHECK(chai.eval<bool>(
|
||||
"auto v = VectorVectorDouble();"
|
||||
"v = [[1.0, 2.0], [3.0, 4.0]];"
|
||||
"v.size() == 2"));
|
||||
}
|
||||
|
||||
// Regression test for issue #607: AST_Node_Trace must be a complete type
|
||||
// when used in eval_error's std::vector<AST_Node_Trace> call_stack member.
|
||||
// This failed to compile with C++20 on clang/libc++ when AST_Node_Trace
|
||||
// was only forward-declared before eval_error's definition.
|
||||
TEST_CASE("eval_error with AST_Node_Trace call stack compiles in C++20") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
// Trigger an eval_error by calling a non-existent function
|
||||
try {
|
||||
chai.eval("nonexistent_function()");
|
||||
REQUIRE(false);
|
||||
} catch (const chaiscript::exception::eval_error &e) {
|
||||
// Verify that eval_error's call_stack member (std::vector<AST_Node_Trace>)
|
||||
// is usable - this would fail to compile if AST_Node_Trace were incomplete
|
||||
const auto &stack = e.call_stack;
|
||||
CHECK(e.pretty_print().size() > 0);
|
||||
(void)stack;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test set_file_reader from C++ land") {
|
||||
chaiscript::ChaiScript chai;
|
||||
chai.set_file_reader([](const std::string &) {
|
||||
return std::string("var file_reader_test_val = 42");
|
||||
});
|
||||
chai.eval_file("nonexistent_file.chai");
|
||||
CHECK(chai.eval<int>("file_reader_test_val") == 42);
|
||||
}
|
||||
|
||||
TEST_CASE("Test set_file_reader from ChaiScript land") {
|
||||
chaiscript::ChaiScript chai;
|
||||
chai.set_file_reader([](const std::string &) {
|
||||
return std::string("var from_custom_reader = true");
|
||||
});
|
||||
chai.eval("set_file_reader(fun(filename) { return \"var from_chai_reader = true\"; })");
|
||||
chai.eval_file("any_file.chai");
|
||||
CHECK(chai.eval<bool>("from_chai_reader") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("Test set_file_reader receives correct filename") {
|
||||
chaiscript::ChaiScript chai;
|
||||
std::string captured_filename;
|
||||
chai.set_file_reader([&captured_filename](const std::string &t_filename) {
|
||||
captured_filename = t_filename;
|
||||
return std::string("var dummy = 1");
|
||||
});
|
||||
chai.eval_file("my_special_file.chai");
|
||||
CHECK(captured_filename == "my_special_file.chai");
|
||||
}
|
||||
|
||||
TEST_CASE("Test use with set_file_reader") {
|
||||
chaiscript::ChaiScript chai;
|
||||
chai.set_file_reader([](const std::string &) {
|
||||
return std::string("var use_reader_val = 99");
|
||||
});
|
||||
chai.use("virtual_file.chai");
|
||||
CHECK(chai.eval<int>("use_reader_val") == 99);
|
||||
}
|
||||
TEST_CASE("Nested namespaces via register_namespace with :: separator") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.register_namespace(
|
||||
[](chaiscript::Namespace &si) {
|
||||
si["mu_B"] = chaiscript::const_var(9.274);
|
||||
},
|
||||
"constants::si");
|
||||
|
||||
chai.register_namespace(
|
||||
[](chaiscript::Namespace &mm) {
|
||||
mm["mu_B"] = chaiscript::const_var(0.05788);
|
||||
},
|
||||
"constants::mm");
|
||||
|
||||
chai.import("constants");
|
||||
|
||||
CHECK(chai.eval<double>("constants.si.mu_B") == Catch::Approx(9.274));
|
||||
CHECK(chai.eval<double>("constants.mm.mu_B") == Catch::Approx(0.05788));
|
||||
|
||||
// Scope resolution via :: works the same as . for access
|
||||
CHECK(chai.eval<double>("constants::si::mu_B") == Catch::Approx(9.274));
|
||||
CHECK(chai.eval<double>("constants::mm::mu_B") == Catch::Approx(0.05788));
|
||||
}
|
||||
|
||||
TEST_CASE("Deeply nested namespaces via register_namespace") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.register_namespace(
|
||||
[](chaiscript::Namespace &leaf) {
|
||||
leaf["val"] = chaiscript::const_var(42);
|
||||
},
|
||||
"a::b::c");
|
||||
|
||||
chai.import("a");
|
||||
|
||||
CHECK(chai.eval<int>("a.b.c.val") == 42);
|
||||
CHECK(chai.eval<int>("a::b::c::val") == 42);
|
||||
}
|
||||
|
||||
TEST_CASE("Block namespace declaration with ::") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace math {
|
||||
def square(x) { x * x }
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<int>("math::square(5)") == 25);
|
||||
CHECK(chai.eval<int>("math.square(5)") == 25);
|
||||
}
|
||||
|
||||
TEST_CASE("Nested block namespace declaration") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace physics::constants {
|
||||
def speed_of_light() { return 299792458 }
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<int>("physics::constants::speed_of_light()") == 299792458);
|
||||
}
|
||||
|
||||
TEST_CASE("Namespace block reopening") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace ns {
|
||||
def foo() { return 1 }
|
||||
}
|
||||
namespace ns {
|
||||
def bar() { return 2 }
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<int>("ns::foo()") == 1);
|
||||
CHECK(chai.eval<int>("ns::bar()") == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Namespace block with var declarations") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace config {
|
||||
var pi = 3.14
|
||||
var name = "hello"
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<double>("config::pi") == Catch::Approx(3.14));
|
||||
CHECK(chai.eval<std::string>("config::name") == "hello");
|
||||
}
|
||||
|
||||
TEST_CASE("Namespace block rejects non-declaration statements") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
namespace bad {
|
||||
1 + 2
|
||||
}
|
||||
)"),
|
||||
chaiscript::exception::eval_error);
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
namespace bad {
|
||||
print("hello")
|
||||
}
|
||||
)"),
|
||||
chaiscript::exception::eval_error);
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
var x = 5
|
||||
namespace bad {
|
||||
x = 10
|
||||
}
|
||||
)"),
|
||||
chaiscript::exception::eval_error);
|
||||
}
|
||||
|
||||
TEST_CASE("C++ runtime_error thrown from registered function is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.add(chaiscript::fun([]() -> int { throw std::runtime_error("cpp_runtime_error"); }), "cpp_throw_runtime");
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
cpp_throw_runtime()
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("C++ out_of_range thrown from registered function is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.add(chaiscript::fun([]() -> int { throw std::out_of_range("cpp_out_of_range"); }), "cpp_throw_oor");
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
cpp_throw_oor()
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("C++ logic_error thrown from registered function is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.add(chaiscript::fun([]() -> int { throw std::logic_error("cpp_logic_error"); }), "cpp_throw_logic");
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
cpp_throw_logic()
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript throw(int) propagates as Boxed_Value to C++") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
try {
|
||||
chai.eval("throw(42)");
|
||||
REQUIRE(false);
|
||||
} catch (chaiscript::Boxed_Value &bv) {
|
||||
CHECK(chaiscript::boxed_cast<int>(bv) == 42);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript throw(string) propagates as Boxed_Value to C++") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
try {
|
||||
chai.eval(R"(throw("error msg"))");
|
||||
REQUIRE(false);
|
||||
} catch (chaiscript::Boxed_Value &bv) {
|
||||
CHECK(chaiscript::boxed_cast<std::string>(bv) == "error msg");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Typed catch with no match propagates exception") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
try {
|
||||
throw(42)
|
||||
}
|
||||
catch(string e) {
|
||||
// wrong type, should not match
|
||||
}
|
||||
)"),
|
||||
chaiscript::Boxed_Value);
|
||||
}
|
||||
|
||||
TEST_CASE("Typed catch with no match still runs finally block") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
var finally_ran = false
|
||||
try {
|
||||
throw(42)
|
||||
}
|
||||
catch(string e) {
|
||||
// wrong type
|
||||
}
|
||||
finally {
|
||||
finally_ran = true
|
||||
}
|
||||
)"),
|
||||
chaiscript::Boxed_Value);
|
||||
|
||||
CHECK(chai.eval<bool>("finally_ran") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple C++ exception types from registered functions") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.add(chaiscript::fun([](int which) -> int {
|
||||
switch (which) {
|
||||
case 0: throw std::runtime_error("runtime");
|
||||
case 1: throw std::out_of_range("range");
|
||||
case 2: throw std::logic_error("logic");
|
||||
default: return which;
|
||||
}
|
||||
}),
|
||||
"cpp_multi_throw");
|
||||
|
||||
CHECK(chai.eval<int>(R"(
|
||||
var catch_count = 0
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
try {
|
||||
cpp_multi_throw(i)
|
||||
}
|
||||
catch(e) {
|
||||
catch_count = catch_count + 1
|
||||
}
|
||||
}
|
||||
catch_count
|
||||
)") == 3);
|
||||
|
||||
CHECK(chai.eval<int>("cpp_multi_throw(5)") == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("Exception from C++ binary operator is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
struct ThrowingType {
|
||||
int value;
|
||||
};
|
||||
|
||||
chai.add(chaiscript::user_type<ThrowingType>(), "ThrowingType");
|
||||
chai.add(chaiscript::constructor<ThrowingType(int)>(), "ThrowingType");
|
||||
chai.add(chaiscript::fun([](const ThrowingType &, const ThrowingType &) -> ThrowingType {
|
||||
throw std::runtime_error("cpp operator+ threw");
|
||||
}),
|
||||
"+");
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
var a = ThrowingType(1)
|
||||
var b = ThrowingType(2)
|
||||
var c = a + b
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("Exception from C++ [] operator is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
struct IndexableType {
|
||||
int value;
|
||||
};
|
||||
|
||||
chai.add(chaiscript::user_type<IndexableType>(), "IndexableType");
|
||||
chai.add(chaiscript::constructor<IndexableType(int)>(), "IndexableType");
|
||||
chai.add(chaiscript::fun([](const IndexableType &, int idx) -> int {
|
||||
if (idx < 0) { throw std::out_of_range("negative index"); }
|
||||
return idx;
|
||||
}),
|
||||
"[]");
|
||||
|
||||
CHECK(chai.eval<int>("var obj = IndexableType(0); obj[5]") == 5);
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
var x = obj[-1]
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
21
unittests/const_var.chai
Normal file
21
unittests/const_var.chai
Normal file
@ -0,0 +1,21 @@
|
||||
// Test const variable declarations
|
||||
const var x = 5
|
||||
assert_equal(5, x)
|
||||
|
||||
// Test that const variables cannot be reassigned
|
||||
assert_throws("Error: \"Error, cannot assign to constant value.\"", fun() { const var y = 10; y = 20 })
|
||||
|
||||
// Test const with auto keyword
|
||||
const auto z = "hello"
|
||||
assert_equal("hello", z)
|
||||
|
||||
// Test that const auto variables cannot be reassigned
|
||||
assert_throws("Error: \"Error, cannot assign to constant value.\"", fun() { const auto w = 3; w = 4 })
|
||||
|
||||
// Test is_var_const on const var
|
||||
const var c = 42
|
||||
assert_true(c.is_var_const())
|
||||
|
||||
// Test that non-const var is not const
|
||||
var nc = 42
|
||||
assert_false(nc.is_var_const())
|
||||
33
unittests/conversion_functions.chai
Normal file
33
unittests/conversion_functions.chai
Normal file
@ -0,0 +1,33 @@
|
||||
// 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)
|
||||
51
unittests/emscripten_eval_test.cpp
Normal file
51
unittests/emscripten_eval_test.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
// Test that validates the eval patterns used by the Emscripten wrapper.
|
||||
// Based on work by Rob Loach (https://github.com/RobLoach/ChaiScript.js)
|
||||
|
||||
#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 <cmath>
|
||||
#include <string>
|
||||
|
||||
int main() {
|
||||
// Test eval (void return) - same as Emscripten eval()
|
||||
chaiscript_eval("var x = 42");
|
||||
|
||||
// Test evalString - same as Emscripten evalString()
|
||||
[[maybe_unused]] std::string s = chaiscript_eval_string("to_string(x)");
|
||||
assert(s == "42");
|
||||
|
||||
// Test evalInt - same as Emscripten evalInt()
|
||||
[[maybe_unused]] int i = chaiscript_eval_int("1 + 2");
|
||||
assert(i == 3);
|
||||
|
||||
// Test evalBool - same as Emscripten evalBool()
|
||||
[[maybe_unused]] bool b = chaiscript_eval_bool("true");
|
||||
assert(b == true);
|
||||
|
||||
b = chaiscript_eval_bool("false");
|
||||
assert(b == false);
|
||||
|
||||
// Test evalFloat - same as Emscripten evalFloat()
|
||||
[[maybe_unused]] float f = chaiscript_eval_float("1.5f");
|
||||
assert(std::abs(f - 1.5f) < 0.001f);
|
||||
|
||||
// Test evalDouble - same as Emscripten evalDouble()
|
||||
[[maybe_unused]] double d = chaiscript_eval_double("3.14");
|
||||
assert(std::abs(d - 3.14) < 0.001);
|
||||
|
||||
// Test a more complex expression
|
||||
chaiscript_eval("def square(n) { return n * n; }");
|
||||
[[maybe_unused]] int sq = chaiscript_eval_int("square(7)");
|
||||
assert(sq == 49);
|
||||
|
||||
return 0;
|
||||
}
|
||||
73
unittests/emscripten_exception_test.cpp
Normal file
73
unittests/emscripten_exception_test.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
// 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.
|
||||
|
||||
[[maybe_unused]] bool caught = false;
|
||||
|
||||
// Test 1: eval with undefined variable should throw
|
||||
caught = false;
|
||||
try {
|
||||
chaiscript_eval("this_variable_does_not_exist");
|
||||
} catch (const chaiscript::exception::eval_error &) {
|
||||
caught = true;
|
||||
}
|
||||
assert(caught && "eval of undefined variable must throw eval_error");
|
||||
|
||||
// Test 2: evalString with a type mismatch should throw
|
||||
caught = false;
|
||||
try {
|
||||
chaiscript_eval_string("1 + 2");
|
||||
} catch (const chaiscript::exception::bad_boxed_cast &) {
|
||||
caught = true;
|
||||
}
|
||||
assert(caught && "evalString with non-string result must throw bad_boxed_cast");
|
||||
|
||||
// Test 3: evalInt with invalid syntax should throw
|
||||
caught = false;
|
||||
try {
|
||||
chaiscript_eval_int("def {}");
|
||||
} catch (const chaiscript::exception::eval_error &) {
|
||||
caught = true;
|
||||
}
|
||||
assert(caught && "evalInt with syntax error must throw eval_error");
|
||||
|
||||
// Test 4: eval with throw statement should propagate exception
|
||||
caught = false;
|
||||
try {
|
||||
chaiscript_eval("throw(\"user exception\")");
|
||||
} catch (const chaiscript::Boxed_Value &) {
|
||||
caught = true;
|
||||
} catch (...) {
|
||||
caught = true;
|
||||
}
|
||||
assert(caught && "ChaiScript throw must propagate as an exception");
|
||||
|
||||
// Test 5: Verify normal operation still works after caught exceptions
|
||||
chaiscript_eval("var post_exception_test = 100");
|
||||
|
||||
[[maybe_unused]] const int result = chaiscript_eval_int("post_exception_test");
|
||||
assert(result == 100 && "normal eval must work after caught exceptions");
|
||||
|
||||
std::cout << "All emscripten exception tests passed.\n";
|
||||
return 0;
|
||||
}
|
||||
110
unittests/enum.chai
Normal file
110
unittests/enum.chai
Normal file
@ -0,0 +1,110 @@
|
||||
// Basic enum class definition (default underlying type: int)
|
||||
enum class Color { Red, Green, Blue }
|
||||
|
||||
// Access via :: syntax
|
||||
auto r = Color::Red
|
||||
auto g = Color::Green
|
||||
auto b = Color::Blue
|
||||
|
||||
// Equality and inequality
|
||||
assert_true(Color::Red == Color::Red)
|
||||
assert_false(Color::Red == Color::Green)
|
||||
assert_true(Color::Red != Color::Green)
|
||||
assert_false(Color::Red != Color::Red)
|
||||
|
||||
// Constructor from valid underlying value
|
||||
auto c = Color::Color(1)
|
||||
assert_true(c == Color::Green)
|
||||
|
||||
// Constructor from invalid value throws
|
||||
try {
|
||||
Color::Color(52)
|
||||
assert_true(false)
|
||||
} catch(e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
// Strong typing: function with typed parameter
|
||||
def takes_color(Color val) { val }
|
||||
takes_color(Color::Red)
|
||||
takes_color(Color::Green)
|
||||
takes_color(Color::Color(2))
|
||||
|
||||
// Cannot pass int where Color is expected
|
||||
try {
|
||||
takes_color(52)
|
||||
assert_true(false)
|
||||
} catch(e) {
|
||||
// expected: dispatch error
|
||||
}
|
||||
|
||||
// to_underlying accessor
|
||||
assert_equal(0, Color::Red.to_underlying())
|
||||
assert_equal(1, Color::Green.to_underlying())
|
||||
assert_equal(2, Color::Blue.to_underlying())
|
||||
|
||||
// Enum class with explicit values
|
||||
enum class Priority { Low = 10, Medium = 20, High = 30 }
|
||||
assert_equal(10, Priority::Low.to_underlying())
|
||||
assert_equal(20, Priority::Medium.to_underlying())
|
||||
assert_equal(30, Priority::High.to_underlying())
|
||||
|
||||
auto p = Priority::Priority(20)
|
||||
assert_true(p == Priority::Medium)
|
||||
|
||||
// Mixed auto and explicit values
|
||||
enum class Status { Pending, Active = 5, Done }
|
||||
assert_equal(0, Status::Pending.to_underlying())
|
||||
assert_equal(5, Status::Active.to_underlying())
|
||||
assert_equal(6, Status::Done.to_underlying())
|
||||
|
||||
// Switch on enum values
|
||||
var result = ""
|
||||
switch(Color::Green) {
|
||||
case (Color::Red) {
|
||||
result = "red"
|
||||
break
|
||||
}
|
||||
case (Color::Green) {
|
||||
result = "green"
|
||||
break
|
||||
}
|
||||
case (Color::Blue) {
|
||||
result = "blue"
|
||||
break
|
||||
}
|
||||
}
|
||||
assert_equal("green", result)
|
||||
|
||||
// Switch on enum with explicit values
|
||||
var prio_result = ""
|
||||
switch(Priority::High) {
|
||||
case (Priority::Low) {
|
||||
prio_result = "low"
|
||||
break
|
||||
}
|
||||
case (Priority::Medium) {
|
||||
prio_result = "medium"
|
||||
break
|
||||
}
|
||||
case (Priority::High) {
|
||||
prio_result = "high"
|
||||
break
|
||||
}
|
||||
}
|
||||
assert_equal("high", prio_result)
|
||||
|
||||
// Enum class with explicit underlying type
|
||||
enum class Flags : char { Read = 1, Write = 2, Execute = 4 }
|
||||
assert_equal(1, Flags::Read.to_underlying())
|
||||
assert_equal(2, Flags::Write.to_underlying())
|
||||
assert_equal(4, Flags::Execute.to_underlying())
|
||||
|
||||
auto f = Flags::Flags(2)
|
||||
assert_true(f == Flags::Write)
|
||||
|
||||
// enum struct syntax (equivalent to enum class, like C++)
|
||||
enum struct Direction { North, East, South, West }
|
||||
assert_equal(0, Direction::North.to_underlying())
|
||||
assert_equal(3, Direction::West.to_underlying())
|
||||
assert_true(Direction::East != Direction::South)
|
||||
862
unittests/exception_comprehensive.chai
Normal file
862
unittests/exception_comprehensive.chai
Normal file
@ -0,0 +1,862 @@
|
||||
// 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)
|
||||
219
unittests/grammar_constructs.chai
Normal file
219
unittests/grammar_constructs.chai
Normal file
@ -0,0 +1,219 @@
|
||||
// Regression test: exercises grammar constructs documented in grammar/chaiscript.ebnf
|
||||
|
||||
// --- Variable declarations ---
|
||||
var a = 1
|
||||
auto b = 2
|
||||
global c = 3
|
||||
const d = 42
|
||||
|
||||
assert_equal(1, a)
|
||||
assert_equal(2, b)
|
||||
assert_equal(3, c)
|
||||
assert_equal(42, d)
|
||||
|
||||
// --- Reference variables ---
|
||||
var orig = 10
|
||||
var &ref = orig
|
||||
ref = 20
|
||||
assert_equal(20, orig)
|
||||
|
||||
// --- Numeric literals ---
|
||||
assert_equal(255, 0xFF)
|
||||
assert_equal(255, 0xff)
|
||||
assert_equal(5, 0b101)
|
||||
assert_equal(42, 42)
|
||||
assert_equal(3.14, 3.14)
|
||||
|
||||
// --- String interpolation ---
|
||||
var name = "world"
|
||||
assert_equal("hello world", "hello ${name}")
|
||||
|
||||
// --- Escape sequences ---
|
||||
assert_equal("\n", "\n")
|
||||
assert_equal("\t", "\t")
|
||||
|
||||
// --- Single-quoted char ---
|
||||
assert_equal('A', 'A')
|
||||
|
||||
// --- Operators and precedence ---
|
||||
assert_equal(7, 1 + 2 * 3)
|
||||
assert_equal(true, 5 > 3 && 2 < 4)
|
||||
assert_equal(true, false || true)
|
||||
assert_equal(6, 3 << 1)
|
||||
assert_equal(1, 3 >> 1)
|
||||
assert_equal(5, 7 & 5)
|
||||
assert_equal(7, 5 | 3)
|
||||
assert_equal(6, 5 ^ 3)
|
||||
assert_equal(-1, ~0)
|
||||
|
||||
// --- Ternary operator ---
|
||||
assert_equal("yes", true ? "yes" : "no")
|
||||
assert_equal("no", false ? "yes" : "no")
|
||||
|
||||
// --- Prefix operators ---
|
||||
var x = 5
|
||||
++x
|
||||
assert_equal(6, x)
|
||||
--x
|
||||
assert_equal(5, x)
|
||||
assert_equal(true, !false)
|
||||
|
||||
// --- Assignment operators ---
|
||||
var v = 10
|
||||
v += 5; assert_equal(15, v)
|
||||
v -= 3; assert_equal(12, v)
|
||||
v *= 2; assert_equal(24, v)
|
||||
v /= 4; assert_equal(6, v)
|
||||
v %= 4; assert_equal(2, v)
|
||||
v <<= 2; assert_equal(8, v)
|
||||
v >>= 1; assert_equal(4, v)
|
||||
v |= 3; assert_equal(7, v)
|
||||
v &= 5; assert_equal(5, v)
|
||||
v ^= 3; assert_equal(6, v)
|
||||
|
||||
// --- Lambda ---
|
||||
var add = fun(a, b) { a + b }
|
||||
assert_equal(5, add(2, 3))
|
||||
|
||||
// --- Lambda with capture ---
|
||||
var captured = 100
|
||||
var get_captured = fun[captured]() { captured }
|
||||
assert_equal(100, get_captured())
|
||||
|
||||
// --- Function definition ---
|
||||
def multiply(a, b) { a * b }
|
||||
assert_equal(12, multiply(3, 4))
|
||||
|
||||
// --- Guard condition on function ---
|
||||
def abs_val(x) : x >= 0 { x }
|
||||
def abs_val(x) : x < 0 { -x }
|
||||
assert_equal(5, abs_val(5))
|
||||
assert_equal(5, abs_val(-5))
|
||||
|
||||
// --- Class definition ---
|
||||
class Animal
|
||||
{
|
||||
attr sound
|
||||
def Animal(s) { this.sound = s }
|
||||
def speak() { this.sound }
|
||||
}
|
||||
|
||||
var dog = Animal("woof")
|
||||
assert_equal("woof", dog.speak())
|
||||
|
||||
// --- Class with inheritance ---
|
||||
class Puppy : Animal
|
||||
{
|
||||
attr name
|
||||
def Puppy(n, s) { this.name = n; this.sound = s }
|
||||
def greet() { to_string(this.name) + " says " + to_string(this.speak()) }
|
||||
}
|
||||
|
||||
var p = Puppy("Rex", "yip")
|
||||
assert_equal("Rex says yip", p.greet())
|
||||
|
||||
// --- Control flow: if/else ---
|
||||
var result = ""
|
||||
if (true) { result = "yes" } else { result = "no" }
|
||||
assert_equal("yes", result)
|
||||
|
||||
// --- Control flow: while ---
|
||||
var counter = 0
|
||||
while (counter < 3) { ++counter }
|
||||
assert_equal(3, counter)
|
||||
|
||||
// --- Control flow: for ---
|
||||
var sum = 0
|
||||
for (var i = 0; i < 5; ++i) { sum += i }
|
||||
assert_equal(10, sum)
|
||||
|
||||
// --- Control flow: ranged for ---
|
||||
var items = [10, 20, 30]
|
||||
var total = 0
|
||||
for (item : items) { total += item }
|
||||
assert_equal(60, total)
|
||||
|
||||
// --- Switch/case ---
|
||||
def classify(n) {
|
||||
var label = ""
|
||||
switch (n) {
|
||||
case (1) { label = "one"; break }
|
||||
case (2) { label = "two"; break }
|
||||
default { label = "other" }
|
||||
}
|
||||
return label
|
||||
}
|
||||
assert_equal("one", classify(1))
|
||||
assert_equal("two", classify(2))
|
||||
assert_equal("other", classify(99))
|
||||
|
||||
// --- Try/catch/finally ---
|
||||
var caught = false
|
||||
var finalized = false
|
||||
try {
|
||||
throw("oops")
|
||||
} catch (e) {
|
||||
caught = true
|
||||
} finally {
|
||||
finalized = true
|
||||
}
|
||||
assert_true(caught)
|
||||
assert_true(finalized)
|
||||
|
||||
// --- Inline containers ---
|
||||
var vec = [1, 2, 3]
|
||||
assert_equal(3, vec.size())
|
||||
|
||||
var m = ["a": 1, "b": 2]
|
||||
assert_equal(1, m["a"])
|
||||
|
||||
var r = [1, 2, 3, 4, 5]
|
||||
assert_equal(5, r.size())
|
||||
|
||||
// --- Dot access chaining ---
|
||||
assert_equal(3, [1, 2, 3].size())
|
||||
|
||||
// --- Array access ---
|
||||
var arr = [10, 20, 30]
|
||||
assert_equal(20, arr[1])
|
||||
|
||||
// --- Backtick identifier ---
|
||||
var `my var` = 42
|
||||
assert_equal(42, `my var`)
|
||||
|
||||
// --- Special identifiers ---
|
||||
assert_equal(true, true)
|
||||
assert_equal(false, false)
|
||||
|
||||
// --- Nested block ---
|
||||
var block_result = 0
|
||||
{ block_result = 42 }
|
||||
assert_equal(42, block_result)
|
||||
|
||||
// --- Break and continue ---
|
||||
var break_sum = 0
|
||||
for (var i = 0; i < 10; ++i) {
|
||||
if (i == 5) { break }
|
||||
break_sum += i
|
||||
}
|
||||
assert_equal(10, break_sum)
|
||||
|
||||
var cont_sum = 0
|
||||
for (var i = 0; i < 5; ++i) {
|
||||
if (i == 2) { continue }
|
||||
cont_sum += i
|
||||
}
|
||||
assert_equal(8, cont_sum)
|
||||
|
||||
// --- Return from function ---
|
||||
def early_return(n) {
|
||||
if (n > 0) { return "positive" }
|
||||
return "non-positive"
|
||||
}
|
||||
assert_equal("positive", early_return(1))
|
||||
assert_equal("non-positive", early_return(-1))
|
||||
|
||||
// --- Colon assignment ---
|
||||
var ca = 0
|
||||
ca := 99
|
||||
assert_equal(99, ca)
|
||||
45
unittests/index_operator_override.chai
Normal file
45
unittests/index_operator_override.chai
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
// 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]);
|
||||
2
unittests/json_unicode_1.chai
Normal file
2
unittests/json_unicode_1.chai
Normal file
@ -0,0 +1,2 @@
|
||||
// Test JSON \u escape: ASCII range (U+0041 = 'A')
|
||||
assert_equal(from_json("\"\\u0041\""), "A")
|
||||
3
unittests/json_unicode_2.chai
Normal file
3
unittests/json_unicode_2.chai
Normal file
@ -0,0 +1,3 @@
|
||||
// Test JSON \u escape: 2-byte UTF-8 (U+00C4 = 'Ä')
|
||||
// This is the example from issue #477
|
||||
assert_equal(from_json("\"\\u00c4\""), "\u00C4")
|
||||
2
unittests/json_unicode_3.chai
Normal file
2
unittests/json_unicode_3.chai
Normal file
@ -0,0 +1,2 @@
|
||||
// Test JSON \u escape: 3-byte UTF-8 (U+20AC = '€')
|
||||
assert_equal(from_json("\"\\u20AC\""), "\u20AC")
|
||||
2
unittests/json_unicode_4.chai
Normal file
2
unittests/json_unicode_4.chai
Normal file
@ -0,0 +1,2 @@
|
||||
// Test JSON \u escape: mixed with regular text
|
||||
assert_equal(from_json("\"Hello \\u0057orld\""), "Hello World")
|
||||
2
unittests/json_unicode_5.chai
Normal file
2
unittests/json_unicode_5.chai
Normal file
@ -0,0 +1,2 @@
|
||||
// Test JSON \u escape: multiple unicode escapes in one string
|
||||
assert_equal(from_json("\"\\u0048\\u0065\\u006C\\u006C\\u006F\""), "Hello")
|
||||
2
unittests/json_unicode_6.chai
Normal file
2
unittests/json_unicode_6.chai
Normal file
@ -0,0 +1,2 @@
|
||||
// Test JSON \u escape: uppercase hex digits
|
||||
assert_equal(from_json("\"\\u00C4\""), "\u00C4")
|
||||
2
unittests/json_unicode_7.chai
Normal file
2
unittests/json_unicode_7.chai
Normal file
@ -0,0 +1,2 @@
|
||||
// Test JSON \u escape: null character (U+0000) - edge case
|
||||
assert_equal(from_json("\"before\\u0041after\""), "beforeAafter")
|
||||
3
unittests/json_unicode_8.chai
Normal file
3
unittests/json_unicode_8.chai
Normal file
@ -0,0 +1,3 @@
|
||||
// Test JSON \u escape inside an object value
|
||||
var m = from_json("{\"key\": \"\\u00C4\\u00D6\\u00DC\"}")
|
||||
assert_equal(m["key"], "\u00C4\u00D6\u00DC")
|
||||
15
unittests/map_find.chai
Normal file
15
unittests/map_find.chai
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
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"))
|
||||
29
unittests/map_keys_vector_push_back.chai
Normal file
29
unittests/map_keys_vector_push_back.chai
Normal file
@ -0,0 +1,29 @@
|
||||
// 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)
|
||||
}
|
||||
16
unittests/modulo_by_zero_protection.chai
Normal file
16
unittests/modulo_by_zero_protection.chai
Normal file
@ -0,0 +1,16 @@
|
||||
// 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() {
|
||||
|
||||
55
unittests/nested_namespaces.chai
Normal file
55
unittests/nested_namespaces.chai
Normal file
@ -0,0 +1,55 @@
|
||||
// Test C++-style block namespace declarations
|
||||
namespace constants::si {
|
||||
def mu_B() { return 1.0 }
|
||||
}
|
||||
|
||||
namespace constants::mm {
|
||||
def mu_B() { return 2.0 }
|
||||
}
|
||||
|
||||
assert_equal(1.0, constants::si::mu_B())
|
||||
assert_equal(2.0, constants::mm::mu_B())
|
||||
|
||||
// Test deeper nesting with block syntax
|
||||
namespace a::b::c {
|
||||
def val() { return 42 }
|
||||
}
|
||||
|
||||
assert_equal(42, a::b::c::val())
|
||||
|
||||
// Test reopening a namespace to add more members
|
||||
namespace math {
|
||||
def square(x) { x * x }
|
||||
}
|
||||
|
||||
namespace math::trig {
|
||||
def double_angle(x) { 2.0 * x }
|
||||
}
|
||||
|
||||
assert_equal(16, math::square(4))
|
||||
assert_equal(6.0, math::trig::double_angle(3.0))
|
||||
|
||||
// Test reopening a namespace (C++ allows this)
|
||||
namespace math {
|
||||
def cube(x) { x * x * x }
|
||||
}
|
||||
|
||||
assert_equal(27, math::cube(3))
|
||||
|
||||
// Test that :: scope resolution works the same as . for access
|
||||
assert_equal(16, math.square(4))
|
||||
assert_equal(6.0, math.trig.double_angle(3.0))
|
||||
|
||||
// Test namespace with var declarations
|
||||
namespace config {
|
||||
var pi = 3.14159
|
||||
var name = "test"
|
||||
}
|
||||
|
||||
assert_equal(3.14159, config::pi)
|
||||
assert_equal("test", config::name)
|
||||
|
||||
// Test function-call style still works
|
||||
namespace("compat")
|
||||
compat.legacy = 99
|
||||
assert_equal(99, compat::legacy)
|
||||
3
unittests/now_function.chai
Normal file
3
unittests/now_function.chai
Normal file
@ -0,0 +1,3 @@
|
||||
// Regression test for issue #660: now() requires <chrono> include
|
||||
var t = now()
|
||||
assert_true(t > 0)
|
||||
6
unittests/object_from_json_1.chai
Normal file
6
unittests/object_from_json_1.chai
Normal file
@ -0,0 +1,6 @@
|
||||
// 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")
|
||||
3
unittests/object_from_json_2.chai
Normal file
3
unittests/object_from_json_2.chai
Normal file
@ -0,0 +1,3 @@
|
||||
// object_from_json: nested objects become nested Dynamic_Objects
|
||||
var obj = object_from_json("{\"outer\":{\"inner\":42}}")
|
||||
assert_equal(obj.outer.inner, 42)
|
||||
3
unittests/object_from_json_3.chai
Normal file
3
unittests/object_from_json_3.chai
Normal file
@ -0,0 +1,3 @@
|
||||
// object_from_json: arrays remain as vectors
|
||||
var obj = object_from_json("{\"items\":[1,2,3]}")
|
||||
assert_equal(obj.items, [1,2,3])
|
||||
9
unittests/object_from_json_4.chai
Normal file
9
unittests/object_from_json_4.chai
Normal file
@ -0,0 +1,9 @@
|
||||
// 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")
|
||||
6
unittests/object_from_json_5.chai
Normal file
6
unittests/object_from_json_5.chai
Normal file
@ -0,0 +1,6 @@
|
||||
// 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")
|
||||
23
unittests/raw_string.chai
Normal file
23
unittests/raw_string.chai
Normal file
@ -0,0 +1,23 @@
|
||||
// Basic raw string
|
||||
assert_equal("hello", R"(hello)")
|
||||
|
||||
// Raw string with no interpolation
|
||||
assert_equal("\${5+5}", R"(${5+5})")
|
||||
|
||||
// Raw string preserves backslashes
|
||||
assert_equal("hello\\nworld", R"(hello\nworld)")
|
||||
|
||||
// Raw string with custom delimiter
|
||||
assert_equal("hello)world", R"foo(hello)world)foo")
|
||||
|
||||
// Raw string with quotes inside
|
||||
assert_equal("he said \"hi\"", R"(he said "hi")")
|
||||
|
||||
// Raw string with dollar signs
|
||||
assert_equal("cost is \$100", R"(cost is $100)")
|
||||
|
||||
// Empty raw string
|
||||
assert_equal("", R"()")
|
||||
|
||||
// Raw string with backslash-dollar
|
||||
assert_equal("\\\${foo}", R"(\${foo})")
|
||||
123
unittests/reflection_documentation.chai
Normal file
123
unittests/reflection_documentation.chai
Normal file
@ -0,0 +1,123 @@
|
||||
|
||||
// Tests for ChaiScript reflection / introspection capabilities
|
||||
// Ensures all documented reflection functions work correctly
|
||||
|
||||
// --- Global reflection functions ---
|
||||
|
||||
// type_name: returns the type name of a value
|
||||
assert_equal("int", type_name(1))
|
||||
assert_equal("string", type_name("hello"))
|
||||
assert_equal("bool", type_name(true))
|
||||
assert_equal("double", type_name(1.0))
|
||||
|
||||
// is_type: checks if a value is of the given type
|
||||
assert_true(is_type(1, "int"))
|
||||
assert_true(is_type("hello", "string"))
|
||||
assert_false(is_type(1, "string"))
|
||||
|
||||
// function_exists: checks if a named function is registered
|
||||
assert_true(function_exists("print"))
|
||||
assert_true(function_exists("type_name"))
|
||||
assert_true(function_exists("function_exists"))
|
||||
assert_false(function_exists("this_function_does_not_exist_xyz"))
|
||||
|
||||
// get_functions: returns a Map of all registered functions
|
||||
var funcs = get_functions()
|
||||
assert_true(funcs.size() > 0)
|
||||
assert_true(funcs.count("print") > 0)
|
||||
assert_true(funcs.count("type_name") > 0)
|
||||
|
||||
// get_objects: returns a Map of all scripting objects
|
||||
var my_test_var = 42
|
||||
var objs = get_objects()
|
||||
assert_true(objs.size() > 0)
|
||||
assert_true(objs.count("my_test_var") > 0)
|
||||
|
||||
// type: returns a Type_Info for a named type
|
||||
var ti = type("int")
|
||||
assert_equal("int", ti.name())
|
||||
|
||||
// call_exists: checks if a function call with given params exists
|
||||
assert_true(call_exists(`+`, 1, 2))
|
||||
assert_true(call_exists(`+`, "a", "b"))
|
||||
|
||||
// --- Object methods ---
|
||||
|
||||
// get_type_info: returns Type_Info for a value
|
||||
var s = "hello"
|
||||
assert_equal("string", s.get_type_info().name())
|
||||
assert_equal("int", 1.get_type_info().name())
|
||||
|
||||
// is_type on objects
|
||||
assert_true("hello".is_type("string"))
|
||||
assert_true(1.is_type("int"))
|
||||
assert_false(1.is_type("string"))
|
||||
|
||||
// is_type with Type_Info
|
||||
assert_true("hello".is_type(string_type))
|
||||
assert_true(1.is_type(int_type))
|
||||
|
||||
// is_var_* methods
|
||||
var x = 5
|
||||
assert_false(x.is_var_const())
|
||||
assert_false(x.is_var_null())
|
||||
assert_false(x.is_var_undef())
|
||||
|
||||
// --- Type_Info methods ---
|
||||
var int_ti = type("int")
|
||||
assert_equal("int", int_ti.name())
|
||||
assert_false(int_ti.is_type_const())
|
||||
assert_false(int_ti.is_type_void())
|
||||
assert_false(int_ti.is_type_undef())
|
||||
assert_false(int_ti.is_type_reference())
|
||||
assert_false(int_ti.is_type_pointer())
|
||||
|
||||
// bare_equal: compares types ignoring const/pointer/reference
|
||||
assert_true(int_ti.bare_equal(1.get_type_info()))
|
||||
|
||||
// --- Function introspection ---
|
||||
|
||||
def my_reflection_test_func(a, b) { return a + b; }
|
||||
|
||||
// get_arity
|
||||
assert_equal(2, my_reflection_test_func.get_arity())
|
||||
|
||||
// get_param_types
|
||||
var param_types = my_reflection_test_func.get_param_types()
|
||||
assert_true(param_types.size() > 0)
|
||||
|
||||
// get_contained_functions
|
||||
assert_equal(0, my_reflection_test_func.get_contained_functions().size())
|
||||
|
||||
// has_guard
|
||||
assert_false(my_reflection_test_func.has_guard())
|
||||
|
||||
// Guarded function
|
||||
def my_guarded_func(x) : x > 0 { return x; }
|
||||
assert_true(my_guarded_func.has_guard())
|
||||
var g = my_guarded_func.get_guard()
|
||||
assert_equal(1, g.get_arity())
|
||||
|
||||
// call: invoke a function with a vector of parameters
|
||||
assert_equal(3, `+`.call([1, 2]))
|
||||
|
||||
// --- Dynamic_Object reflection ---
|
||||
var obj = Dynamic_Object()
|
||||
obj.name = "test"
|
||||
obj.value = 42
|
||||
var attrs = obj.get_attrs()
|
||||
assert_true(attrs.count("name") > 0)
|
||||
assert_true(attrs.count("value") > 0)
|
||||
|
||||
// --- Class reflection ---
|
||||
class ReflectionTestClass {
|
||||
var x
|
||||
def ReflectionTestClass() { this.x = 10; }
|
||||
def get_x() { return this.x; }
|
||||
}
|
||||
|
||||
var rtc = ReflectionTestClass()
|
||||
assert_equal("ReflectionTestClass", rtc.get_type_name())
|
||||
assert_true(rtc.is_type("ReflectionTestClass"))
|
||||
assert_equal("Dynamic_Object", type_name(rtc))
|
||||
assert_equal("ReflectionTestClass", rtc.get_type_name())
|
||||
16
unittests/return_assignment.chai
Normal file
16
unittests/return_assignment.chai
Normal file
@ -0,0 +1,16 @@
|
||||
// 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())
|
||||
200
unittests/strong_typedef.chai
Normal file
200
unittests/strong_typedef.chai
Normal file
@ -0,0 +1,200 @@
|
||||
// Strong typedef: using Type = int creates a distinct type
|
||||
using Meters = int
|
||||
|
||||
def measure(Meters m) {
|
||||
return m
|
||||
}
|
||||
|
||||
// Constructing a strong typedef value should work
|
||||
var m = Meters(42)
|
||||
|
||||
// Calling with the typedef'd value should succeed
|
||||
measure(m)
|
||||
|
||||
// Calling with a plain int should fail (strong typedef)
|
||||
try {
|
||||
measure(42)
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: type mismatch because int is not Meters
|
||||
}
|
||||
|
||||
// Multiple strong typedefs from the same base type should be distinct
|
||||
using Seconds = int
|
||||
|
||||
def wait(Seconds s) {
|
||||
return s
|
||||
}
|
||||
|
||||
var s = Seconds(10)
|
||||
wait(s)
|
||||
|
||||
// Meters and Seconds should not be interchangeable
|
||||
try {
|
||||
wait(m)
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: Meters is not Seconds
|
||||
}
|
||||
|
||||
try {
|
||||
measure(s)
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: Seconds is not Meters
|
||||
}
|
||||
|
||||
// to_underlying should return the base value
|
||||
assert_equal(to_underlying(m), 42)
|
||||
assert_equal(to_underlying(s), 10)
|
||||
|
||||
// to_underlying result should be a plain value, not a strong typedef
|
||||
def takes_int(int i) {
|
||||
return i
|
||||
}
|
||||
assert_equal(takes_int(to_underlying(m)), 42)
|
||||
|
||||
// --- Arithmetic operators: strongly typed ---
|
||||
var m2 = Meters(8)
|
||||
var m_sum = m + m2
|
||||
assert_equal(to_underlying(m_sum), 50)
|
||||
measure(m_sum)
|
||||
|
||||
var m_diff = m - m2
|
||||
assert_equal(to_underlying(m_diff), 34)
|
||||
|
||||
var m_prod = Meters(3) * Meters(4)
|
||||
assert_equal(to_underlying(m_prod), 12)
|
||||
|
||||
var m_quot = Meters(20) / Meters(5)
|
||||
assert_equal(to_underlying(m_quot), 4)
|
||||
|
||||
var m_rem = Meters(17) % Meters(5)
|
||||
assert_equal(to_underlying(m_rem), 2)
|
||||
|
||||
// Arithmetic result is strongly typed, not plain int
|
||||
try {
|
||||
takes_int(m_sum)
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: m_sum is Meters, not int
|
||||
}
|
||||
|
||||
// --- Comparison operators ---
|
||||
assert_equal(Meters(5) == Meters(5), true)
|
||||
assert_equal(Meters(5) != Meters(3), true)
|
||||
assert_equal(Meters(3) < Meters(5), true)
|
||||
assert_equal(Meters(5) > Meters(3), true)
|
||||
assert_equal(Meters(5) <= Meters(5), true)
|
||||
assert_equal(Meters(3) >= Meters(3), true)
|
||||
assert_equal(Meters(3) >= Meters(5), false)
|
||||
|
||||
// --- Bitwise and shift operators ---
|
||||
assert_equal(to_underlying(Meters(6) & Meters(3)), 2)
|
||||
assert_equal(to_underlying(Meters(6) | Meters(3)), 7)
|
||||
assert_equal(to_underlying(Meters(6) ^ Meters(3)), 5)
|
||||
assert_equal(to_underlying(Meters(5) << Meters(2)), 20)
|
||||
assert_equal(to_underlying(Meters(12) >> Meters(1)), 6)
|
||||
|
||||
// Bitwise results are strongly typed
|
||||
try {
|
||||
takes_int(Meters(6) & Meters(3))
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: result is Meters, not int
|
||||
}
|
||||
|
||||
// --- Strong typedef over string ---
|
||||
using StrongString = string
|
||||
|
||||
var ss1 = StrongString("hello")
|
||||
var ss2 = StrongString(" world")
|
||||
var ss_cat = ss1 + ss2
|
||||
assert_equal(to_underlying(ss_cat), "hello world")
|
||||
|
||||
// StrongString + StrongString -> StrongString (strongly typed)
|
||||
def takes_strong_string(StrongString ss) {
|
||||
return ss
|
||||
}
|
||||
takes_strong_string(ss_cat)
|
||||
|
||||
// Operators not supported by the underlying type error at call time
|
||||
try {
|
||||
var bad = ss1 * ss2
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: underlying string has no * operator
|
||||
}
|
||||
try {
|
||||
var bad = ss1 - ss2
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: underlying string has no - operator
|
||||
}
|
||||
try {
|
||||
var bad = ss1 / ss2
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: underlying string has no / operator
|
||||
}
|
||||
try {
|
||||
var bad = ss1 % ss2
|
||||
assert_equal(true, false)
|
||||
} catch(e) {
|
||||
// Expected: underlying string has no % operator
|
||||
}
|
||||
|
||||
// Comparison on StrongString
|
||||
assert_equal(StrongString("abc") < StrongString("def"), true)
|
||||
assert_equal(StrongString("abc") == StrongString("abc"), true)
|
||||
assert_equal(StrongString("abc") != StrongString("def"), true)
|
||||
assert_equal(StrongString("def") > StrongString("abc"), true)
|
||||
assert_equal(StrongString("abc") <= StrongString("abc"), true)
|
||||
assert_equal(StrongString("def") >= StrongString("abc"), true)
|
||||
|
||||
// --- User-defined extensions on strong typedefs ---
|
||||
def first_char(StrongString ss) {
|
||||
return to_string(to_underlying(ss)[0])
|
||||
}
|
||||
assert_equal(first_char(StrongString("hello")), "h")
|
||||
|
||||
def double_meters(Meters m) {
|
||||
return Meters(to_underlying(m) * 2)
|
||||
}
|
||||
assert_equal(to_underlying(double_meters(Meters(21))), 42)
|
||||
|
||||
// User-defined operator extension
|
||||
def `[]`(StrongString ss, int offset) {
|
||||
return to_string(to_underlying(ss)[offset])
|
||||
}
|
||||
assert_equal(StrongString("hello")[1], "e")
|
||||
|
||||
// --- Compound assignment operators ---
|
||||
var m3 = Meters(10)
|
||||
m3 += Meters(5)
|
||||
assert_equal(to_underlying(m3), 15)
|
||||
measure(m3)
|
||||
|
||||
m3 -= Meters(3)
|
||||
assert_equal(to_underlying(m3), 12)
|
||||
|
||||
m3 *= Meters(2)
|
||||
assert_equal(to_underlying(m3), 24)
|
||||
|
||||
m3 /= Meters(4)
|
||||
assert_equal(to_underlying(m3), 6)
|
||||
|
||||
m3 %= Meters(4)
|
||||
assert_equal(to_underlying(m3), 2)
|
||||
|
||||
// Compound assignment result is still the strong typedef
|
||||
var m4 = Meters(10)
|
||||
m4 += Meters(5)
|
||||
assert_equal(to_underlying(m4), 15)
|
||||
measure(m4)
|
||||
|
||||
// Compound assignment on StrongString
|
||||
var ss3 = StrongString("hello")
|
||||
ss3 += StrongString(" world")
|
||||
assert_equal(to_underlying(ss3), "hello world")
|
||||
takes_strong_string(ss3)
|
||||
50
unittests/switch_type_conversion.chai
Normal file
50
unittests/switch_type_conversion.chai
Normal file
@ -0,0 +1,50 @@
|
||||
// Test for issue #421: switch statement with custom == operator on
|
||||
// dynamic objects must properly manage object lifetimes during
|
||||
// case comparisons.
|
||||
|
||||
class MyType {
|
||||
var value
|
||||
def MyType(v) { this.value = v }
|
||||
}
|
||||
|
||||
def `==`(a, b) : a.is_type("MyType") && b.is_type("MyType") {
|
||||
return a.value == b.value
|
||||
}
|
||||
|
||||
var result = 0
|
||||
var obj = MyType(2)
|
||||
|
||||
switch(obj) {
|
||||
case (MyType(1)) {
|
||||
result = 1
|
||||
break
|
||||
}
|
||||
case (MyType(2)) {
|
||||
result = 2
|
||||
break
|
||||
}
|
||||
case (MyType(3)) {
|
||||
result = 3
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert_equal(result, 2)
|
||||
|
||||
// Also test fall-through with custom == operator
|
||||
var total = 0
|
||||
var obj2 = MyType(2)
|
||||
|
||||
switch(obj2) {
|
||||
case (MyType(1)) {
|
||||
total += 1
|
||||
}
|
||||
case (MyType(2)) {
|
||||
total += 2
|
||||
}
|
||||
case (MyType(3)) {
|
||||
total += 4
|
||||
}
|
||||
}
|
||||
|
||||
assert_equal(total, 6)
|
||||
26
unittests/threading_config_test.cpp
Normal file
26
unittests/threading_config_test.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#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,9 +16,7 @@
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch.hpp"
|
||||
#include "catch_amalgamated.hpp"
|
||||
|
||||
TEST_CASE("Type_Info objects generate expected results") {
|
||||
const auto test_type
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user