# Requirements: Tally - Cross-Platform Calculator ## Overview A calculator application with three frontends (CLI, TUI, Android) sharing a common Zig engine. Supports standard arithmetic, a programmer mode with struct layout visualization, and financial calculations. --- ## Functional Requirements ### FR-1: Standard Calculation Mode - **FR-1.1**: Parse and evaluate infix mathematical expressions with correct operator precedence (PEMDAS). - **FR-1.2**: Support operators: `+`, `-`, `*`, `/`, `%` (modulo), `^` (power), unary `-`. - **FR-1.3**: Support parentheses for grouping. - **FR-1.4**: Support built-in functions: `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `log` (base-10), `ln` (natural), `sqrt`, `cbrt`, `abs`, `ceil`, `floor`, `round`, `factorial`. - **FR-1.5**: Support constants: `pi`, `e`, `tau`. - **FR-1.6**: Support variable storage (Ans for last result, named variables A-F, X, Y, Z). - **FR-1.7**: Maintain calculation history with replay capability. - **FR-1.8**: Commas accepted as digit separators in input (e.g., `1,000 * 2` = 2000). Spaces in hex literals treated as byte separators (e.g., `0xFF FF` = 0xFFFF). - **FR-1.9**: When a standard-mode expression contains any non-decimal literal (hex `0x`, octal `0o`, or binary `0b`) and the result is a non-negative integer, enrich the result display with hex/octal/binary representations inline (without leaving standard mode). Uses the smallest standard bit width (8/16/32/64/128) that holds the value. This does not change the evaluation semantics (still f64 arithmetic, `^` is still power); it only augments the display. Fractional or negative results show decimal only. ### FR-2: Programmer Mode - **FR-2.1**: Accept input in decimal, hexadecimal (`0x`), octal (`0o`), and binary (`0b`) formats. - **FR-2.2**: Simultaneously display results in all four bases (dec, hex, oct, bin). - **FR-2.3**: Support configurable bit widths: 8, 16, 32, 64, 128-bit. - **FR-2.4**: Support bitwise operators: AND (`&` or `and`), OR (`|` or `or`), XOR (`xor` keyword), NOT (`~` or `not`), left shift (`<<`), right shift (logical `>>>`), arithmetic right shift (`>>`), rotate left (`rol`), rotate right (`ror`). Note: `^` is always exponentiation (never XOR) - see FR-2.12. - **FR-2.12**: The `^` operator means exponentiation in all modes (never XOR). This avoids mode-dependent operator overloading. XOR is available only via the `xor` keyword. Power is also available via `**`. This keeps every operator's meaning identical across standard and programmer modes. - **FR-2.5**: Display both signed (two's complement) and unsigned interpretations of the current value. - **FR-2.6**: Visualize the bit pattern as a grid (integer.exposed style) - bits individually addressable/toggleable in TUI and Android. - **FR-2.7**: Quick toggle between base display formats via dedicated keyboard shortcuts (TUI). - **FR-2.8**: Configurable endianness display (little-endian default, big-endian available). Affects byte order in HEX display and byte-level visualizations. Does not change the underlying value - purely a display/interpretation toggle. - **FR-2.9**: ASCII/text literal input via single-quoted strings: `'hello'` packs ASCII bytes into the integer value. First character occupies the most significant used byte (big-endian packing when display is BE) or least significant byte (when display is LE). Useful for examining magic numbers, file signatures, protocol headers. - **FR-2.10**: ASCII interpretation display - when the current value contains printable ASCII bytes, show the text representation alongside the numeric bases (e.g., `ASCII: "ELF."` or `ASCII: ..lf` with dots for non-printable bytes). - **FR-2.11**: IEEE 754 floating-point interpretation (float.exposed style): - Support float32 (single) and float64 (double) formats, selectable - Decompose bit pattern into sign bit, exponent field, and significand/mantissa field with visual color-coding in the bit grid - Display the evaluation formula: (-1)^sign * 2^(exponent - bias) * 1.mantissa - Display the exact decimal value represented by the bit pattern - Display delta to next and previous representable float (ULP - unit in last place) - Handle special values: +/-infinity, NaN, denormals/subnormals, +/-zero - Allow toggling individual bits and seeing the float value update in real-time - Allow entering a decimal float value and seeing the closest IEEE 754 representation - Show when a value is not exactly representable (rounding occurred) ### FR-3: Struct Layout Visualizer (Programmer Mode Extension) - **FR-3.1**: Accept struct definitions in a mini-DSL: ``` struct { u32 x; u8 flags; u16 id; u64 timestamp; } ``` - **FR-3.2**: Compute and display total struct size in bytes. - **FR-3.3**: Display per-field information: offset, size, alignment requirement. - **FR-3.4**: Identify and highlight padding bytes between fields and at the end (tail padding). - **FR-3.5**: Render a byte-level memory map showing which bytes belong to which field and which are padding. - **FR-3.6**: Default to x86-64 System V ABI alignment rules. - **FR-3.7**: Support switching ABI/alignment profiles (extensible design - future: Windows x64, ARM AAPCS, packed). - **FR-3.8**: Support switching endianness display (little-endian default, big-endian available). - **FR-3.9**: Supported field types (initial set): `u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`, `f32`, `f64`, `bool`, `ptr` (pointer-sized, arch-dependent). - **FR-3.10**: Show a "packed" comparison - display what the struct would look like with no padding vs. natural alignment. ### FR-4: Unit Conversions - **FR-4.1**: Support inline unit conversion syntax: ` to|in ` (e.g., `100 km to mi`, `32F in C`). IMPLEMENTED as ordinary expression input, so no subcommand is needed: `tally 32F in C` works, as does typing it in the TUI standard mode. The value may itself be an expression (`2*3 kg to lb`). - **FR-4.2**: Support unit conversions within expressions: `5 kg + 3 lb` evaluates in the left-hand unit. NOT YET IMPLEMENTED (needs unit-aware tokenizing and evaluation, unlike FR-4.1 which only needs the `to` keyword as a trigger). - **FR-4.3**: Length: nm, um, mm, cm, m, km, in, ft, yd, mi, nmi (nautical mile), ly, au, pc. Base unit: m. - **FR-4.4**: Mass/Weight: mg, g, kg, t (tonne), oz, lb, st (stone), ton (US short), lt (long ton). Base unit: kg. - **FR-4.5**: Temperature: C (Celsius), F (Fahrenheit), K (Kelvin), R (Rankine). Base unit: C. These are the only affine conversions (they need an offset, not just a factor). - **FR-4.6**: Time: ns, us, ms, s, min, h, d, wk, yr. Base unit: s. A year is the Julian year (365.25 days), the usual unit-conversion convention. - **FR-4.7**: Digital storage: bit, B, and both decimal (kB, MB, GB, TB, PB) and binary (KiB, MiB, GiB, TiB, PiB) prefixes, kept distinct. Base unit: B. - **FR-4.8**: Speed: m/s, km/h, mph, ft/s, kn (knots), c (speed of light). Base unit: m/s. - **FR-4.9**: Area: mm2, cm2, m2, km2, in2, ft2, yd2, mi2, ha, acre. Base unit: m2. - **FR-4.10**: Volume: mL, L, m3, cm3, in3, ft3, gal, qt, pt, cup, floz, tbsp, tsp, bbl. Base unit: L. - **FR-4.11**: Energy: J, kJ, cal, kcal, Wh, kWh, BTU, eV, erg. Base unit: J. - **FR-4.12**: Pressure: Pa, kPa, MPa, bar, mbar, atm, psi, torr, mmHg, inHg. Base unit: Pa. - **FR-4.13**: Data rate: bps, kbps, Mbps, Gbps, Tbps, Bps, kBps, MBps, GBps. Base unit: bps. - **FR-4.14**: Angle: rad, mrad, deg, grad, turn, arcmin, arcsec. Base unit: rad. - **FR-4.15**: In CLI, unit conversion is plain expression input (FR-4.1): `tally 100 km to mi`, `tally 32F to C`. A `convert` subcommand is also accepted for explicitness and scripting (`tally convert 100 km mi`, with or without the `to`), but it is a convenience, not the only route. - **FR-4.16**: In TUI, a dedicated convert mode reachable from the mode tabs. - **FR-4.17**: Unit system extensible - adding a unit or category requires only a table entry, no parser changes. - **FR-4.18**: Unit names resolve by canonical name or alias (long forms and plurals). An exact case-sensitive match is tried first so case-distinguished units keep their meaning (`K` is Kelvin, `B` is byte, `kB` is not `KiB`); a case-insensitive pass runs only as a fallback, so forgiving input like `KM` or `Celsius` still works. - **FR-4.19**: Converting between units of different categories is an error (`IncompatibleUnits`); an unrecognized unit name is an error (`UnknownUnit`). - **FR-4.20**: Conversion detection must not change the meaning of ordinary expressions. Only a standalone whitespace-delimited `to` or `in` triggers it, and a unit name is only recognized at a token boundary (so the trailing `s` of `smoots` is not read as seconds, and the `in` inside `min` is not read as a separator). Without a separator, `e` remains Euler's number rather than becoming a unit. - **FR-4.21**: Because `in` is both a separator and the canonical name for inches, candidate separators are tried last-to-first and the first reading that yields a valid conversion wins. This must resolve all of: `5 in in cm` (5 inches to cm), `100 mm in in` (mm to inches), `1 in in in` (identity), and `60 min in h` (minutes to hours). ### FR-5: Financial Mode - **FR-5.1**: Compute CAGR given start value, end value, and number of periods. - **FR-5.2**: Compute compound interest (future value given principal, rate, periods, compounding frequency). - **FR-5.3**: Compute present value (discount future cash flow). - **FR-5.4**: Support TVM (Time Value of Money) solver - given any 4 of: N, I/Y, PV, PMT, FV, solve for the 5th. - **FR-5.5**: Display intermediate calculation steps/formula used (transparency). ### FR-6: CLI Frontend - **FR-6.1**: Invoke as `tally ""` for standard mode evaluation. - **FR-6.2**: Flag `--programmer` or `-p` for programmer mode: `tally -p "0xFF & 0x0F"`. - **FR-6.3**: Flag `--bits <8|16|32|64>` to set bit width in programmer mode (default: 64). - **FR-6.4**: Flag `--base ` to control primary output format (default: show all). - **FR-6.5**: Subcommand `tally struct ` for struct layout - accepts inline DSL string or path to a file containing the definition. - **FR-6.6**: Subcommand `tally cagr ` for quick CAGR. - **FR-6.7**: Subcommand `tally tvm` with named flags (`--pv`, `--fv`, `--n`, `--rate`, `--pmt`) - omit one to solve for it. - **FR-6.8**: Subcommand `tally convert ` for unit conversion. - **FR-6.9**: Output format flags: `--json` for machine-readable output, plain text default. - **FR-6.10**: Exit code 0 on success, non-zero on parse/evaluation errors with stderr message. ### FR-7: TUI Frontend - **FR-7.1**: Full-screen terminal UI with mode tabs (Standard, Programmer, Financial, Convert). - **FR-7.2**: Standard mode: expression input line, result display, scrollable history. - **FR-7.3**: Programmer mode: bit grid (navigable with arrow keys, toggle with Space/Enter), simultaneous base displays, expression input. - **FR-7.4**: Programmer mode struct sub-view: field list editor, live-updating memory map visualization. - **FR-7.5**: Financial mode: form-style input for parameters, result display with formula breakdown. - **FR-7.6**: Convert mode: select category, input value, select from/to units, live result. - **FR-7.7**: Every action must be reachable from the keyboard alone; the TUI is fully usable without a mouse. - **FR-7.8**: Quick-switch keys for base display in programmer mode (e.g., `d`=dec, `h`=hex, `o`=oct, `b`=bin to highlight primary). - **FR-7.9**: Support terminal resize gracefully. - **FR-7.10**: Vi-style and Emacs-style keybinding options for expression input. #### FR-7.11: Mouse Support Mouse input is a first-class way to drive the TUI, not an optional extra. Anything visibly interactive must respond to a left click. Keyboard parity is still required (FR-7.7): the mouse never becomes the only way to do something. - **FR-7.11.1**: Clicking a mode tab switches to that mode. - **FR-7.11.2**: Clicking a bit in the programmer bit grid flips that bit and moves the cursor to it. - **FR-7.11.3**: Clicking a digit in the HEX or OCT row focuses that field and places the cursor on the exact digit clicked, ready for typing. A click does not alter the value, because a hex nibble or octal digit has no single unambiguous "toggle". - **FR-7.11.4**: Clicking a digit in the BIN row flips that bit, since one binary digit is exactly one bit. - **FR-7.11.5**: Clicking a DEC(s) or DEC(u) row focuses that field for editing. - **FR-7.11.6**: Clicking the `Bits:`, `Signed:`, or `Endian:` label in the programmer header toggles or cycles that setting. - **FR-7.11.7**: In the float view, clicking a bit flips it; clicking the format or exit hint in the header toggles f32/f64 or leaves the overlay. - **FR-7.11.8**: In convert mode, clicking a category chip selects that category (resetting the unit pair to that category's defaults); clicking a unit in the From or To column selects it. - **FR-7.11.9**: Clicking the input line returns keyboard focus to the expression prompt. - **FR-7.11.10**: Clicking anywhere dismisses the help overlay, matching its "any key dismisses" keyboard behavior. - **FR-7.11.11**: Only a left button press acts. Release, motion, and drag events are ignored so a single click cannot fire an action twice. - **FR-7.11.12**: Clickable regions are rebuilt every frame from what was actually drawn, so hit targets can never drift out of sync with the display. ### FR-8: Android Frontend - **FR-8.1**: Native Android app using Kotlin and Jetpack Compose. - **FR-8.2**: Call into the Zig engine via JNI (C ABI shared library). - **FR-8.3**: Bottom navigation bar for mode switching (Standard, Programmer, Financial, Convert). - **FR-8.4**: Standard mode: traditional calculator layout with button grid, expression display at top, result below. - **FR-8.5**: Programmer mode: tappable bit grid, segmented control for bit width, base display cards, operator button row for bitwise ops. - **FR-8.6**: Programmer mode struct sub-screen: text field for struct input, scrollable field table, color-coded memory map rendered as a custom Canvas composable. - **FR-8.7**: Financial mode: card-based UI with input fields, dropdown for function selection (CAGR, TVM, etc.), compute button, result card with formula. - **FR-8.8**: Convert mode: category picker (horizontal chips), from/to unit selectors, live conversion as user types. - **FR-8.9**: Responsive layout for phone and tablet (multi-pane on tablet). - **FR-8.10**: Follow Material Design 3 guidelines with dynamic color (Android 12+). - **FR-8.11**: Haptic feedback on bit toggle and button presses. - **FR-8.12**: Support landscape orientation with adapted layouts (wider bit grid, side-by-side panels). --- ## Non-Functional Requirements ### NFR-1: Performance - Expression parsing and evaluation must complete in < 1ms for typical expressions. - TUI must render at >= 30fps during interaction (bit toggling, navigation). - Struct layout computation must handle structs with up to 256 fields without perceptible delay. ### NFR-2: Portability - Engine, CLI, and TUI must compile and run on: Linux (x86-64, aarch64), macOS (aarch64, x86-64), Windows (x86-64). - Android engine .so must target: arm64-v8a, armeabi-v7a, x86_64. - Single `zig build` invocation to produce all desktop targets (cross-compilation). ### NFR-3: Correctness - Floating-point arithmetic uses IEEE 754 double precision (64-bit). - Programmer mode integer operations must be exact (no floating-point). - Struct layout computation must match the actual layout produced by a C compiler under the specified ABI. ### NFR-4: Extensibility - Adding a new ABI profile for struct layout requires implementing a single interface (alignment rules + pointer size). - Adding new financial functions requires no changes to the parser - use a function-call syntax. - Adding new unit categories/units requires only table entries, no parser changes. - Engine exposes a stable C ABI for future frontends (e.g., WASM, iOS). ### NFR-5: Testability - Engine must be testable in isolation (no I/O dependencies). - Struct layout results must be verifiable against `pahole` or compiler output for reference structs. - Engine test coverage target: >= 80% line coverage. The engine is pure computation with no I/O, so this is achievable and expected. - Frontend coverage: best-effort via integration and smoke tests (TUI key-sequence tests, Android instrumented tests). No hard coverage target on frontend code. - All public engine API functions must have corresponding unit tests covering happy path, error cases, and edge cases. ### NFR-6: Build & Distribution - **Zig build system for everything.** A single `zig build` invocation at the workspace root handles all targets: engine, CLI, TUI, and Android .so cross-compilation. No Gradle required for the native library build - Gradle only wraps the prebuilt .so into the APK. - **Mise for toolchain management.** All required toolchains (Zig version, Android NDK if needed for validation) are declared in `.mise.toml` at the project root. A developer needs only `mise` installed - running `mise install` provisions everything else. - **No other system-level dependencies.** Outside of mise, a developer should not need to manually install Zig, Android SDK/NDK, or any other toolchain. The `.mise.toml` is the single source of truth for tool versions. - **libvaxis for TUI.** The TUI frontend uses libvaxis (pulled as a Zig build dependency). No other TUI framework. - Produce statically-linked binaries for CLI and TUI (no shared library dependencies on end-user system). - Android: produce .so files for arm64-v8a, x86_64 via `zig build -Dtarget=aarch64-linux-android` (or similar). The Gradle project in `android/` consumes these prebuilt artifacts. - Minimal runtime dependencies: Zig stdlib + libvaxis (TUI only). No other native deps. ### NFR-7: Number Display & Formatting - Decimal numbers must use comma grouping for display (e.g., `4,294,967,295`). - Scientific notation only when value exceeds 15 significant digits or absolute value > 10^15 / < 10^-15. Never jump to scientific notation for values that fit in a readable decimal. - Programmer mode hex values display with space-separated bytes (e.g., `FF FF FF FF`). - Programmer mode binary values display grouped by nibble with spaces (e.g., `1111 1111`). - All frontends must distinguish between "display format" (with separators) and "clipboard format" (raw, no separators). - Android: long-press any result to copy the raw (no-separator) value to clipboard. - TUI: yank command copies raw value to system clipboard. - CLI: stdout output uses display format by default; `--raw` flag or `--json` gives unformatted values. ### NFR-8: Security - C API must not use null-terminated strings. All string inputs take `(pointer, length)` pairs to prevent buffer overread. - Engine must validate all input lengths before processing. - No unbounded allocations from untrusted input (enforce maximum expression length, maximum struct field count).