diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml new file mode 100644 index 00000000..4be8931f --- /dev/null +++ b/.github/workflows/emscripten.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index ad3e79a0..029f33da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,6 +423,10 @@ 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) + install(TARGETS test_module RUNTIME DESTINATION bin LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/chaiscript") endif() endif() diff --git a/emscripten/CMakeLists.txt b/emscripten/CMakeLists.txt new file mode 100644 index 00000000..7751631f --- /dev/null +++ b/emscripten/CMakeLists.txt @@ -0,0 +1,35 @@ +# 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) + +# Emscripten link flags: enable embind, allow memory growth, export as ES module-compatible +target_link_options(chaiscript PRIVATE + --bind + -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" +) diff --git a/emscripten/chaiscript.html b/emscripten/chaiscript.html new file mode 100644 index 00000000..29e32252 --- /dev/null +++ b/emscripten/chaiscript.html @@ -0,0 +1,237 @@ + + + + + + + ChaiScript + + + +
+

ChaiScript

+ Interactive Playground +
Loading...
+
+
+
+
Input
+ +
+
+
+
Output
+
+
+
+ + + + + + diff --git a/emscripten/chaiscript_em.cpp b/emscripten/chaiscript_em.cpp new file mode 100644 index 00000000..06806797 --- /dev/null +++ b/emscripten/chaiscript_em.cpp @@ -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_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 diff --git a/emscripten/chaiscript_eval.hpp b/emscripten/chaiscript_eval.hpp new file mode 100644 index 00000000..52791ab3 --- /dev/null +++ b/emscripten/chaiscript_eval.hpp @@ -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 +#include + +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(input); +} + +inline bool chaiscript_eval_bool(const std::string &input) { + return detail::get_chai_instance().eval(input); +} + +inline int chaiscript_eval_int(const std::string &input) { + return detail::get_chai_instance().eval(input); +} + +inline float chaiscript_eval_float(const std::string &input) { + return detail::get_chai_instance().eval(input); +} + +inline double chaiscript_eval_double(const std::string &input) { + return detail::get_chai_instance().eval(input); +} + +#endif /* CHAISCRIPT_EMSCRIPTEN_EVAL_HPP_ */ diff --git a/unittests/emscripten_eval_test.cpp b/unittests/emscripten_eval_test.cpp new file mode 100644 index 00000000..6084c9d3 --- /dev/null +++ b/unittests/emscripten_eval_test.cpp @@ -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 +#include "../emscripten/chaiscript_eval.hpp" +#include +#include +#include + +int main() { + // Test eval (void return) - same as Emscripten eval() + chaiscript_eval("var x = 42"); + + // Test evalString - same as Emscripten evalString() + std::string s = chaiscript_eval_string("to_string(x)"); + assert(s == "42"); + + // Test evalInt - same as Emscripten evalInt() + int i = chaiscript_eval_int("1 + 2"); + assert(i == 3); + + // Test evalBool - same as Emscripten evalBool() + bool b = chaiscript_eval_bool("true"); + assert(b == true); + + b = chaiscript_eval_bool("false"); + assert(b == false); + + // Test evalFloat - same as Emscripten evalFloat() + float f = chaiscript_eval_float("1.5f"); + assert(std::abs(f - 1.5f) < 0.001f); + + // Test evalDouble - same as Emscripten evalDouble() + 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; }"); + int sq = chaiscript_eval_int("square(7)"); + assert(sq == 49); + + return 0; +}