# tabnas for Rust
Rust port of the grammar-free Tabnas parser engine. TypeScript is the canonical
implementation; the shared fixtures in `../test/spec` define cross-port
behavior.
The crates.io package is `tabnas-parser`. The Rust library name remains
`tabnas`:
```console
cargo add tabnas-parser --rename tabnas
```
```rust
use tabnas::{Tabnas, Value};
let parser = Tabnas::new();
// Install rules and actions, then parse input with parser.parse(...).
```
Serialized function references are bound through typed Rust registrations
before the grammar is installed:
```rust
use tabnas::Tabnas;
let mut parser = Tabnas::new();
Without it, `tabnas/measure` records this port as **1.10x to 1.15x
slower on every case**. Go's compiler inlines across packages within a
binary by default and V8 inlines across module boundaries at runtime, so
this is the flag that puts a Rust consumer on the same footing rather
than an unusual tuning.
### Pick an allocator
The other two ports of this engine bring their own: Go has the
runtime's and TypeScript has V8's. A Rust binary takes whatever libc
supplies unless it says otherwise, and this engine allocates heavily
enough for that to matter. On glibc, `tabnas/measure` records
**1.09x to 1.56x** between the system allocator and `mimalloc`, the
larger end on the deeply nesting grammars.
```toml
[dependencies]
mimalloc = "0.1"
```
```rust,ignore
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
```
`mimalloc` is what the measurement harness uses; `jemalloc` and others
are reasonable too. The point is the choice, not the crate: leaving it
to the platform is itself a choice, and on glibc it is an expensive
one.
A library cannot set a profile or an allocator for its consumers,
which is why both of these are written down rather than configured.
## Development
```sh
cargo build --all-targets
cargo test --all-targets
cargo test --doc
cargo clippy --all-targets --all-features -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
```
The crate declares Rust 1.85 as its minimum supported toolchain. From the
repository root, `./ci/rust/run.sh` also runs the shared cross-runtime and
compiler-consumer parity gates.
### Formal verification (experiment, not a gate)
`rs/verus/` holds standalone [Verus](https://github.com/verus-lang/verus)
copies of two pieces of `src/`, with specifications and machine-checked
proofs: the `InlineText` length invariant behind the crate's only
`unsafe` block, and the lexer's scalar-index span arithmetic.
Nothing there is compiled by the crate. Verus pins its own Rust
toolchain, which is not the 1.85 above, so no gate runs it and it is not
a build dependency. Run it by hand with `VERUS=/path/to/verus
rs/verus/run.sh`. For the `unsafe` block, the proof covers the length
bound that keeps the slice in range, and not the UTF-8 validity it also
relies on, because Verus has no specification for
`str::from_utf8_unchecked`. The crate itself is not written for Verus:
wrapping code in `verus!{}` spreads to everything that code touches, and
the engine's `Rc`, `RefCell`, `dyn` and `HashMap` sites are outside the
subset Verus verifies.