tally/.kiro/specs/calculator/requirements.md

19 KiB

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: <value> <from_unit> to|in <to_unit> (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 "<expression>" 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 <dec|hex|oct|bin> to control primary output format (default: show all).
  • FR-6.5: Subcommand tally struct <definition_or_file> for struct layout - accepts inline DSL string or path to a file containing the definition.
  • FR-6.6: Subcommand tally cagr <start> <end> <periods> 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 <value> <from_unit> <to_unit> 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).