Metadata-Version: 2.4
Name: calc-rs-lang
Version: 0.2.0
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries
Requires-Dist: marimo>=0.24.0 ; extra == 'notebook'
Requires-Dist: anywidget>=0.11.0 ; extra == 'notebook'
Requires-Dist: traitlets>=5.16.1 ; extra == 'notebook'
Requires-Dist: maturin>=1.15.0 ; extra == 'notebook'
Provides-Extra: notebook
License-File: LICENSE
Summary: A strongly typed, spreadsheet-style expression engine
Keywords: calculator,expression,spreadsheet,currency,rust
Author: Attica-oss
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/Attica-oss/calc_rs

# calc_rs

A strongly typed, spreadsheet-style expression engine — currency, tonnage,
percentages, dates, durations, and tables are distinct first-class types,
not just decorated numbers. A Rust rewrite of
[Attica-oss/calc](https://github.com/Attica-oss/calc), usable as a CLI, a
Rust library, or a Python extension module.

## Install

**CLI** — a prebuilt binary, no Rust toolchain needed:

```sh
uv tool install calc-rust          # then run: calc_rs
uvx --from calc-rust calc_rs '$5.00 * 3'
pipx install calc-rust
```

or from source:

```sh
cargo install --path . --bin calc_rs
```

**Python library** (`import calc_rs`) — also ships the CLI as
`python -m calc_rs`, for locked-down environments that block standalone
executables but allow pip packages:

```sh
pip install calc-rs-lang
python -m calc_rs '$5.00 * 3'
python -m calc_rs                 # REPL
```

(The pure-Python front end has no line editing or syntax highlighting,
but the same value formatting — tables and all — via `calc_rs.render`.)

## CLI usage

```sh
calc_rs '$5.00 * 3'            # $15.00  (currency)
calc_rs                        # start the REPL
calc_rs --bare '1 + 2 * 3'     # 7        (value only, for scripts)
```

Statements are separated by `;` **or a newline**, so scripts read
naturally:

```sh
calc_rs $'let price = $12.50\nlet qty = 3\nprice * qty'   # $37.50
```

A newline *inside* an expression is insignificant (`1 +⏎2` is `3`), and
the REPL keeps taking input while a line has an open `(`/`[` or ends on
an operator — so multi-line `table(...)` / `sort(filter(...))` just
work. Press Enter on a blank line twice to force-submit anyway and let
the parser point at whatever's unbalanced.

Tab completes function names, plus the variables and `fn`s you've
defined this session (`sel⇥` → `select`).

### Editor support

`calc_rs_lsp` is a Language Server (diagnostics, completion, hover) —
`cargo install --path . --bin calc_rs_lsp --features lsp`, then see
[docs/LSP.md](docs/LSP.md) for editor wiring.

### Tables

Tables are ordinary values — build, edit, query, and persist them with
functions, in the REPL or one-shot:

```
table("item:text, qty:int, price:currency")   # empty, typed
column("day", "Mon", "Tue")                    # a named column
table(column("x", 1, 2), column("y", 3, 4))    # from columns

append(t, "Bolt", 250, $0.08)     # add a row      (type-checked)
extend(t, "total", [qty] * [price])   # add a computed column
setcell(t, 0, "qty", 5)           # replace one cell
droprow(t, 2)  ·  rename(t, "price", "unit_price")

select(t, "item", "total")  ·  filter(t, [qty] > 100)
sort(t, [total], "desc")  ·  groupby(t, "item", "total", "sum")

open("sales.json")                # read a JSON workbench file
save(sort(open("sales.json"), [total], "desc"), "ranked.json")
```

Each verb returns a new table, so they compose. `[col]` inside
`filter` / `sort` / `extend` refers to that column in the current row.

## Python usage

```python
import calc_rs

calc_rs.evaluate("$5.00 * 3")          # Currency($15.00)
calc_rs.evaluate("1 + 2 * 3")          # 7
calc_rs.check("$5.00 * 3")             # 'currency'  (no evaluation)

calc = calc_rs.Calculator()
calc.set("price", calc_rs.Currency("12.50"))
calc.eval("let qty = 3")
calc.eval("price * qty")               # Currency($37.50)
calc.get("qty")                        # 3
```

Currency, tonnage, percent, duration, and complex results come back as
dedicated classes — `Currency`/`Tonnage`/`Percent` expose `.amount`,
`Duration` exposes `.months`/`.days`/`.seconds`, `Complex` exposes
`.real`/`.imag`. Numbers, text, booleans, and dates/times come back as
native Python types. The wheel ships a type stub and `py.typed`.

## Rust library

```toml
[dependencies]
calc_rs = { git = "https://github.com/Attica-oss/calc_rs" }
```

Everything the common cases need is re-exported at the crate root (or
grab it in one line with `use calc_rs::prelude::*`):

```rust
use calc_rs::prelude::*;

// one-shot: an expression or a full `let x = 5; x + 1` script
let out = evaluate("$5.00 * 3")?;
assert_eq!(out.to_string(), "$15.00");
assert_eq!(out.ty.to_string(), "currency");

// a session that remembers variables and `ans`, like the REPL
let mut calc = Calculator::new();
calc.eval("let price = $12.50")?;
calc.eval("let qty = 3")?;
assert_eq!(calc.eval("price * qty")?.to_string(), "$37.50");

// type-check without evaluating
assert_eq!(calc.check("price * qty")?.to_string(), "currency");
```

`evaluate` and `Calculator::eval` return an `Evaluated` — `value`
(a `Value`), `ty` (the inferred `Type`), `bindings` (every `let` the
script made), `assigned`, and `used_vars`. For a custom function
registry, drop to `calc_rs::run_script(source, vars, functions)`.

## Notebook

A [marimo](https://marimo.io) notebook backed by this engine lives in
`notebook/`, replicating the expression-editor and table-builder workflow of
the original Python project's notebook:

```sh
pip install -e '.[notebook]'
maturin develop --release
marimo edit notebook/calc_notebook.py
```

## Development

```sh
cargo test                     # engine tests
maturin develop --release      # build + install the Python extension
```

## License

MIT — see [LICENSE](LICENSE).

