diff --git a/README.md b/README.md index e3459de..802ed98 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,170 @@ # ChaiScript Extras -User contributed wrappers and modules for ChaiScript. +User contributed wrappers and modules for [ChaiScript](https://github.com/ChaiScript/ChaiScript). + +All modules are header-only and live under `chaiscript::extras`. To use a module, +include its header and add the returned `Module` to your `ChaiScript` engine (or, +for math, register the namespace directly on the engine). ## Modules -- [Math](#math): Adds common math methods to ChaiScript. -- [String ID](#string-id): String hashing with [string_id](https://github.com/foonathan/string_id) -- [String](#string): Adds some extra string methods to ChaiScript strings +| Module | Header | Description | +|---|---|---| +| [Math](#math) | `chaiscript/extras/math.hpp` | Standard C++ `` functions, as flat globals or as a `math.*` namespace. | +| [String Methods](#string-methods) | `chaiscript/extras/string_methods.hpp` | Extra string utilities (`replace`, `trim`, `split`, `toLowerCase`, ...). | +| [String ID](#string-id) | `chaiscript/extras/string_id.hpp` | Bindings for [foonathan/string_id](https://github.com/foonathan/string_id) string hashing. | + +--- ## Math -The Math module adds some standard math functions to ChaiScript. +Wraps the standard C++ `` functions for ChaiScript. Two APIs are provided: + +1. **`bootstrap()`** — registers the functions as free (global) names such as `cos(x)`. + Includes `float`, `double`, and `long double` overloads for every function. +2. **`bootstrap_namespace()`** — registers a real `math` namespace on the engine so + functions can be called as `math.cos(x)`, similar to Lua's `math` library. + Double-precision only. + +Both APIs may be used together; they do not conflict. ### Install -``` cpp + +```cpp #include "chaiscript/extras/math.hpp" ``` -``` cpp + +### Flat / global usage — `bootstrap()` + +```cpp chaiscript::ChaiScript chai; auto mathlib = chaiscript::extras::math::bootstrap(); chai.add(mathlib); ``` -### Usage +```chaiscript +var result = cos(0.5) +``` -``` chaiscript -var result = cos(0.5f) +### Namespace usage — `bootstrap_namespace()` + +```cpp +chaiscript::ChaiScript chai; +chaiscript::extras::math::bootstrap_namespace(chai); ``` -### Options +```chaiscript +import("math") +var result = math.cos(0.5) +``` -Compile with one of the following flags to enable or disable features... -- `CHAISCRIPT_EXTRAS_MATH_SKIP_ADVANCED` When enabled, will skip some of the advanced math functions. +### Available functions -## String ID +All names below are available in both APIs unless noted. Functions marked **(advanced)** +are compiled in only when `CHAISCRIPT_EXTRAS_MATH_SKIP_ADVANCED` is **not** defined. -Adds [String ID](https://github.com/foonathan/string_id) support to ChaiScript. +| Category | Functions | +|---|---| +| Trigonometric | `cos`, `sin`, `tan`, `acos`, `asin`, `atan`, `atan2` | +| Hyperbolic | `cosh`, `sinh`, `tanh`, `acosh` *(advanced)*, `asinh` *(advanced)*, `atanh` *(advanced)* | +| Exponential / logarithmic | `exp`, `log`, `log10`, `exp2` *(advanced)*, `expm1` *(advanced)*, `ilogb` *(advanced)*, `log1p` *(advanced)*, `log2` *(advanced)*, `logb` *(advanced)* | +| Exponential (flat-only) | `frexp`, `ldexp`, `modf`, `scalbn` *(advanced)*, `scalbln` *(advanced)* | +| Power | `pow`, `sqrt`, `cbrt` *(advanced)*, `hypot` *(advanced)* | +| Error / gamma *(advanced)* | `erf`, `erfc`, `tgamma`, `lgamma` | +| Rounding / remainder | `ceil`, `floor`, `fmod`, `trunc` *(advanced)*, `round` *(advanced)*, `lround` *(advanced)*, `llround` *(advanced)*, `rint` *(advanced)*, `lrint` *(advanced)*, `llrint` *(advanced)*, `nearbyint` *(advanced)*, `remainder` *(advanced)* | +| Rounding (flat-only, advanced) | `remquo` | +| Floating-point manipulation *(advanced)* | `copysign`, `nextafter`, `nexttoward`, `nan` *(flat-only)* | +| Min / max / difference *(advanced)* | `fdim`, `fmax`, `fmin` | +| Absolute value | `abs`, `fabs` *(advanced)*, `fma` *(advanced)* | +| Classification | `isfinite`, `isinf`, `isnan`, `isnormal`, `signbit`, `fpclassify` *(advanced)* | +| Comparison | `isgreater`, `isgreaterequal`, `isless`, `islessequal`, `islessgreater`, `isunordered` | -### Install +### Compile options -``` cpp -#include "chaiscript/extras/string_id.hpp" -``` +- `CHAISCRIPT_EXTRAS_MATH_SKIP_ADVANCED` — if defined, skips the functions marked *(advanced)* + above. Useful for reducing compile time and binary size when only the basics are needed. -``` cpp -auto string_idlib = chaiscript::extras::string_id::bootstrap(); -chai.add(string_idlib); -``` +--- -## String +## String Methods -Adds various string methods to extend how strings can be used in ChaiScript: -- `string::replace(string, string)` -- `string::trim()` -- `string::trimStart()` -- `string::trimEnd()` -- `string::split(string)` -- `string::toLowerCase()` -- `string::toUpperCase()` -- `string::includes()` +Adds method-style string utilities to ChaiScript strings. ### Install -``` cpp +```cpp #include "chaiscript/extras/string_methods.hpp" ``` -``` cpp +```cpp +chaiscript::ChaiScript chai; +// split() returns a vector of strings, so register the vector type if you plan to index into it: +chai.add(chaiscript::bootstrap::standard_library::vector_type>("StringVector")); auto stringmethods = chaiscript::extras::string_methods::bootstrap(); chai.add(stringmethods); ``` +### Methods + +| Method | Description | +|---|---| +| `string::replace(string search, string replace)` | Replace all occurrences of `search` with `replace`. | +| `string::replace(char search, char replace)` | Replace all occurrences of character `search` with `replace`. | +| `string::trim()` | Remove leading and trailing whitespace. | +| `string::trimStart()` | Remove leading whitespace. | +| `string::trimEnd()` | Remove trailing whitespace. | +| `string::split(string token)` | Split on `token`, returning a vector of strings. | +| `string::toLowerCase()` | Return a lowercase copy of the string. | +| `string::toUpperCase()` | Return an uppercase copy of the string. | +| `string::includes(string search)` | Return `true` if `search` occurs in the string. | +| `string::includes(char search)` | Return `true` if character `search` occurs in the string. | + ### Usage -``` chaiscript +```chaiscript var input = "Hello, World!" var output = input.replace("Hello", "Goodbye") // => "Goodbye, World!" + +" padded ".trim() // => "padded" +"a,b,c".split(",")[1] // => "b" +"Hello".toUpperCase() // => "HELLO" +"Hello World".includes("orld") // => true ``` + +--- + +## String ID + +Adds [foonathan/string_id](https://github.com/foonathan/string_id) support to +ChaiScript, exposing `string_id`, `string_info`, `default_database`, and +`basic_database` (when compiled with `FOONATHAN_STRING_ID_DATABASE`). + +### Install + +```cpp +#include "chaiscript/extras/string_id.hpp" +``` + +```cpp +chaiscript::ChaiScript chai; +auto string_idlib = chaiscript::extras::string_id::bootstrap(); +chai.add(string_idlib); +``` + +Types registered: `default_database`, `basic_database`, `string_id`, `string_info`. +Operators `==` and `!=` are provided between `string_id` and `hash_type`. + +--- + +## Building and testing + +This is a header-only library, so no build is required to use it. To build and run the tests: + +```sh +cmake -B build -S . +cmake --build build -j +ctest --test-dir build --output-on-failure +``` + +Tests live in `tests/` and use [Catch2](https://github.com/catchorg/Catch2). diff --git a/include/chaiscript/extras/math.hpp b/include/chaiscript/extras/math.hpp index 4fb1bc6..6e259bf 100644 --- a/include/chaiscript/extras/math.hpp +++ b/include/chaiscript/extras/math.hpp @@ -828,6 +828,121 @@ namespace chaiscript { return m; } + + /// \brief Registers a "math" namespace on the given ChaiScript engine, + /// exposing the standard C++ math functions as attributes of + /// the namespace (e.g. \c math.cos(x), \c math.sqrt(x)). + /// + /// Uses ChaiScript's native \c register_namespace API so that the + /// functions live inside a real namespace object rather than being + /// attached to a global variable. The namespace is lazily populated + /// when it is first imported. + inline void bootstrap_namespace(chaiscript::ChaiScript &chai) + { + chai.register_namespace([](chaiscript::Namespace &math) { + // TRIG FUNCTIONS + math["cos"] = chaiscript::var(chaiscript::fun([](double p){ return std::cos(p); })); + math["sin"] = chaiscript::var(chaiscript::fun([](double p){ return std::sin(p); })); + math["tan"] = chaiscript::var(chaiscript::fun([](double p){ return std::tan(p); })); + math["acos"] = chaiscript::var(chaiscript::fun([](double p){ return std::acos(p); })); + math["asin"] = chaiscript::var(chaiscript::fun([](double p){ return std::asin(p); })); + math["atan"] = chaiscript::var(chaiscript::fun([](double p){ return std::atan(p); })); + math["atan2"] = chaiscript::var(chaiscript::fun([](double y, double x){ return std::atan2(y, x); })); + + // HYPERBOLIC FUNCTIONS + math["cosh"] = chaiscript::var(chaiscript::fun([](double p){ return std::cosh(p); })); + math["sinh"] = chaiscript::var(chaiscript::fun([](double p){ return std::sinh(p); })); + math["tanh"] = chaiscript::var(chaiscript::fun([](double p){ return std::tanh(p); })); + +#ifndef CHAISCRIPT_EXTRAS_MATH_SKIP_ADVANCED + math["acosh"] = chaiscript::var(chaiscript::fun([](double p){ return std::acosh(p); })); + math["asinh"] = chaiscript::var(chaiscript::fun([](double p){ return std::asinh(p); })); + math["atanh"] = chaiscript::var(chaiscript::fun([](double p){ return std::atanh(p); })); +#endif + + // EXPONENTIAL AND LOGARITHMIC FUNCTIONS + math["exp"] = chaiscript::var(chaiscript::fun([](double p){ return std::exp(p); })); + math["log"] = chaiscript::var(chaiscript::fun([](double p){ return std::log(p); })); + math["log10"] = chaiscript::var(chaiscript::fun([](double p){ return std::log10(p); })); + +#ifndef CHAISCRIPT_EXTRAS_MATH_SKIP_ADVANCED + math["exp2"] = chaiscript::var(chaiscript::fun([](double p){ return std::exp2(p); })); + math["expm1"] = chaiscript::var(chaiscript::fun([](double p){ return std::expm1(p); })); + math["ilogb"] = chaiscript::var(chaiscript::fun([](double p){ return std::ilogb(p); })); + math["log1p"] = chaiscript::var(chaiscript::fun([](double p){ return std::log1p(p); })); + math["log2"] = chaiscript::var(chaiscript::fun([](double p){ return std::log2(p); })); + math["logb"] = chaiscript::var(chaiscript::fun([](double p){ return std::logb(p); })); +#endif + + // POWER FUNCTIONS + math["pow"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::pow(x, y); })); + math["sqrt"] = chaiscript::var(chaiscript::fun([](double p){ return std::sqrt(p); })); + +#ifndef CHAISCRIPT_EXTRAS_MATH_SKIP_ADVANCED + math["cbrt"] = chaiscript::var(chaiscript::fun([](double p){ return std::cbrt(p); })); + math["hypot"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::hypot(x, y); })); + + // ERROR AND GAMMA FUNCTIONS + math["erf"] = chaiscript::var(chaiscript::fun([](double p){ return std::erf(p); })); + math["erfc"] = chaiscript::var(chaiscript::fun([](double p){ return std::erfc(p); })); + math["tgamma"] = chaiscript::var(chaiscript::fun([](double p){ return std::tgamma(p); })); + math["lgamma"] = chaiscript::var(chaiscript::fun([](double p){ return std::lgamma(p); })); +#endif + + // ROUNDING AND REMAINDER FUNCTIONS + math["ceil"] = chaiscript::var(chaiscript::fun([](double p){ return std::ceil(p); })); + math["floor"] = chaiscript::var(chaiscript::fun([](double p){ return std::floor(p); })); + math["fmod"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::fmod(x, y); })); + +#ifndef CHAISCRIPT_EXTRAS_MATH_SKIP_ADVANCED + math["trunc"] = chaiscript::var(chaiscript::fun([](double p){ return std::trunc(p); })); + math["round"] = chaiscript::var(chaiscript::fun([](double p){ return std::round(p); })); + math["lround"] = chaiscript::var(chaiscript::fun([](double p){ return std::lround(p); })); + math["llround"] = chaiscript::var(chaiscript::fun([](double p){ return std::llround(p); })); + math["rint"] = chaiscript::var(chaiscript::fun([](double p){ return std::rint(p); })); + math["lrint"] = chaiscript::var(chaiscript::fun([](double p){ return std::lrint(p); })); + math["llrint"] = chaiscript::var(chaiscript::fun([](double p){ return std::llrint(p); })); + math["nearbyint"] = chaiscript::var(chaiscript::fun([](double p){ return std::nearbyint(p); })); + math["remainder"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::remainder(x, y); })); + + // FLOATING-POINT MANIPULATION FUNCTIONS + math["copysign"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::copysign(x, y); })); + math["nextafter"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::nextafter(x, y); })); + math["nexttoward"] = chaiscript::var(chaiscript::fun([](double x, long double y){ return std::nexttoward(x, y); })); + + // MINIMUM, MAXIMUM, DIFFERENCE FUNCTIONS + math["fdim"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::fdim(x, y); })); + math["fmax"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::fmax(x, y); })); + math["fmin"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::fmin(x, y); })); + + // OTHER FUNCTIONS + math["fabs"] = chaiscript::var(chaiscript::fun([](double p){ return std::fabs(p); })); +#endif + + math["abs"] = chaiscript::var(chaiscript::fun([](double p){ return std::abs(p); })); + +#ifndef CHAISCRIPT_EXTRAS_MATH_SKIP_ADVANCED + math["fma"] = chaiscript::var(chaiscript::fun([](double x, double y, double z){ return std::fma(x, y, z); })); + + // CLASSIFICATION FUNCTIONS + math["fpclassify"] = chaiscript::var(chaiscript::fun([](double p){ return std::fpclassify(p); })); +#endif + + math["isfinite"] = chaiscript::var(chaiscript::fun([](double p){ return std::isfinite(p); })); + math["isinf"] = chaiscript::var(chaiscript::fun([](double p){ return std::isinf(p); })); + math["isnan"] = chaiscript::var(chaiscript::fun([](double p){ return std::isnan(p); })); + math["isnormal"] = chaiscript::var(chaiscript::fun([](double p){ return std::isnormal(p); })); + math["signbit"] = chaiscript::var(chaiscript::fun([](double p){ return std::signbit(p); })); + + // COMPARISON FUNCTIONS + math["isgreater"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::isgreater(x, y); })); + math["isgreaterequal"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::isgreaterequal(x, y); })); + math["isless"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::isless(x, y); })); + math["islessequal"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::islessequal(x, y); })); + math["islessgreater"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::islessgreater(x, y); })); + math["isunordered"] = chaiscript::var(chaiscript::fun([](double x, double y){ return std::isunordered(x, y); })); + }, "math"); + } } } } diff --git a/tests/math.cpp b/tests/math.cpp index fd24bb5..84d1c06 100644 --- a/tests/math.cpp +++ b/tests/math.cpp @@ -7,6 +7,35 @@ #include +TEST_CASE( "Math namespace works", "[math]" ) { + chaiscript::ChaiScript chai; + chaiscript::extras::math::bootstrap_namespace(chai); + chai.eval(R"(import("math"))"); + + // Trig + CHECK(chai.eval("math.cos(0.5)") == std::cos(0.5)); + CHECK(chai.eval("math.sin(0.5)") == std::sin(0.5)); + CHECK(chai.eval("math.tan(0.5)") == std::tan(0.5)); + CHECK(chai.eval("math.acos(0.5)") == std::acos(0.5)); + CHECK(chai.eval("math.asin(0.5)") == std::asin(0.5)); + CHECK(chai.eval("math.atan(0.5)") == std::atan(0.5)); + CHECK(chai.eval("math.atan2(0.5, 0.5)") == std::atan2(0.5, 0.5)); + + // Power + CHECK(chai.eval("math.pow(0.5, 3.0)") == std::pow(0.5, 3.0)); + CHECK(chai.eval("math.sqrt(0.5)") == std::sqrt(0.5)); + + // Rounding + CHECK(chai.eval("math.ceil(0.5)") == std::ceil(0.5)); + CHECK(chai.eval("math.floor(0.5)") == std::floor(0.5)); + CHECK(chai.eval("math.abs(-0.5)") == std::abs(-0.5)); + + // Exponential + CHECK(chai.eval("math.exp(0.5)") == std::exp(0.5)); + CHECK(chai.eval("math.log(0.5)") == std::log(0.5)); + CHECK(chai.eval("math.log10(0.5)") == std::log10(0.5)); +} + TEST_CASE( "Math functions work", "[math]" ) { auto mathlib = chaiscript::extras::math::bootstrap();