91e05e3c26
## What
Introduces `wickra-c` — a `cdylib` + `staticlib` that exposes the Rust core over a **C ABI**. This is the hub every C-capable language (C, C++, Go, C#, Java, R) links against, instead of re-wiring each indicator natively. The native Python/Node/WASM bindings are untouched; this is purely additive, for ecosystems without first-class Rust tooling.
## Scope (foundation slice)
This PR deliberately validates the **whole pipeline end to end with one indicator (SMA)** before scaling to all 514, so the CI / cross-OS / header-drift mechanics are proven green first.
- Opaque `*mut T` handles; `wickra_<ind>_{new,update,batch,reset,free}`.
- NaN sentinel for warmup / NULL handles; caller-owned batch buffers; every function NULL-safe.
- cbindgen generates and commits `bindings/c/include/wickra.h` with opaque handle typedefs.
- A C smoke example (`examples/c/`) links the header + compiled library and runs (CMake + ctest).
- A `c-abi` CI job builds the library and runs the smoke test on **Linux, macOS and Windows**, plus a header drift check on Linux.
## Notes
- The per-indicator FFI blocks are plain `#[no_mangle]` functions, **not** a macro: cbindgen cannot see macro-generated functions on stable Rust (macro expansion needs nightly), so the blocks are written literally and will be generated mechanically by the ScriptHelpers `capi` wrapper in a follow-up (same model as the committed-but-generated Node `index.js`).
- `bindings/c` cannot inherit the workspace `forbid(unsafe_code)` lint (the C boundary needs raw pointers), so it mirrors every workspace lint and only relaxes `unsafe_code`. The Rust core stays `unsafe`-forbidden.
## Follow-ups (separate PRs)
- ScriptHelpers `capi` generator + wire the scalar family (~235).
- Hand-written blocks for multi-output / custom-input / bars (~279).
- Docs consistency wave (README / docs / webpage: Python·Node·WASM·Rust → +C).
- Release wiring (native-lib matrix + header/lib GH-release assets) — gated.
67 lines
1.7 KiB
C++
67 lines
1.7 KiB
C++
// Optional C++ convenience layer over the Wickra C ABI (`wickra.h`).
|
|
//
|
|
// The C ABI hands out raw handles that must be released exactly once with the
|
|
// matching `wickra_<ind>_free`. `wickra::Handle` wraps that in a move-only RAII
|
|
// owner so the free happens automatically at scope exit:
|
|
//
|
|
// #include "wickra.hpp"
|
|
//
|
|
// wickra::Handle<Sma, wickra_sma_free> sma(wickra_sma_new(14));
|
|
// if (sma) {
|
|
// double v = wickra_sma_update(sma.get(), 42.0); // NaN during warmup
|
|
// }
|
|
// // sma is freed here
|
|
//
|
|
// This is header-only and adds no runtime cost beyond the C calls themselves.
|
|
|
|
#ifndef WICKRA_HPP
|
|
#define WICKRA_HPP
|
|
|
|
#include "wickra.h"
|
|
|
|
#include <utility>
|
|
|
|
namespace wickra {
|
|
|
|
/// Move-only RAII owner of a Wickra handle. `T` is the opaque indicator type and
|
|
/// `Free` its `wickra_<ind>_free` function.
|
|
template <typename T, void (*Free)(T *)>
|
|
class Handle {
|
|
public:
|
|
explicit Handle(T *ptr) noexcept : ptr_(ptr) {}
|
|
|
|
~Handle() {
|
|
if (ptr_ != nullptr) {
|
|
Free(ptr_);
|
|
}
|
|
}
|
|
|
|
Handle(const Handle &) = delete;
|
|
Handle &operator=(const Handle &) = delete;
|
|
|
|
Handle(Handle &&other) noexcept : ptr_(std::exchange(other.ptr_, nullptr)) {}
|
|
|
|
Handle &operator=(Handle &&other) noexcept {
|
|
if (this != &other) {
|
|
if (ptr_ != nullptr) {
|
|
Free(ptr_);
|
|
}
|
|
ptr_ = std::exchange(other.ptr_, nullptr);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
/// The raw handle, for passing to the `wickra_<ind>_*` functions.
|
|
T *get() const noexcept { return ptr_; }
|
|
|
|
/// True if the handle is non-null (construction succeeded).
|
|
explicit operator bool() const noexcept { return ptr_ != nullptr; }
|
|
|
|
private:
|
|
T *ptr_;
|
|
};
|
|
|
|
} // namespace wickra
|
|
|
|
#endif // WICKRA_HPP
|