Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 126 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -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++ `<cmath>` 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++ `<cmath>` 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<std::vector<std::string>>("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).
115 changes: 115 additions & 0 deletions include/chaiscript/extras/math.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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); }));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these now live in the math namespace, would it make sense to remove the original ones.

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");
}
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions tests/math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,35 @@

#include <iostream>

TEST_CASE( "Math namespace works", "[math]" ) {
chaiscript::ChaiScript chai;
chaiscript::extras::math::bootstrap_namespace(chai);
chai.eval(R"(import("math"))");

// Trig
CHECK(chai.eval<double>("math.cos(0.5)") == std::cos(0.5));
CHECK(chai.eval<double>("math.sin(0.5)") == std::sin(0.5));
CHECK(chai.eval<double>("math.tan(0.5)") == std::tan(0.5));
CHECK(chai.eval<double>("math.acos(0.5)") == std::acos(0.5));
CHECK(chai.eval<double>("math.asin(0.5)") == std::asin(0.5));
CHECK(chai.eval<double>("math.atan(0.5)") == std::atan(0.5));
CHECK(chai.eval<double>("math.atan2(0.5, 0.5)") == std::atan2(0.5, 0.5));

// Power
CHECK(chai.eval<double>("math.pow(0.5, 3.0)") == std::pow(0.5, 3.0));
CHECK(chai.eval<double>("math.sqrt(0.5)") == std::sqrt(0.5));

// Rounding
CHECK(chai.eval<double>("math.ceil(0.5)") == std::ceil(0.5));
CHECK(chai.eval<double>("math.floor(0.5)") == std::floor(0.5));
CHECK(chai.eval<double>("math.abs(-0.5)") == std::abs(-0.5));

// Exponential
CHECK(chai.eval<double>("math.exp(0.5)") == std::exp(0.5));
CHECK(chai.eval<double>("math.log(0.5)") == std::log(0.5));
CHECK(chai.eval<double>("math.log10(0.5)") == std::log10(0.5));
}

TEST_CASE( "Math functions work", "[math]" ) {
auto mathlib = chaiscript::extras::math::bootstrap();

Expand Down