22 KiB
22 KiB
Tasks: Tally - Cross-Platform Calculator
References
Phase 1: Project Scaffolding & Engine Foundation
Task 1.1: Initialize workspace build structure [DONE]
- Create
.mise.tomldeclaring Zig version (pin to specific release) - Create top-level
build.zigwith module declarations for engine, cli, tui - Add Android cross-compilation targets to
build.zig(-Dtarget=aarch64-linux-android,-Dtarget=x86_64-linux-android) - Create
engine/build.zigproducing static lib and shared lib targets - Create
cli/build.ziglinking engine - Create
tui/build.ziglinking engine + libvaxis dependency - Add
.gitignore,README.md(minimal),LICENSE - Verify:
mise installprovisions Zig,zig buildcompiles without errors on empty main files
NOTE: Actual structure diverged from spec - single binary at src/main.zig
(CLI + TUI combined), engine as static lib + shared lib. No separate cli/ or
tui/ build files. kcov-based coverage wired in via build/Coverage.zig.
Task 1.2: Implement core types module [DONE]
- Create
engine/src/types.zig - Define
Valueunion,Integerstruct,BitWidth,Signedness,Endiannessenums - Define
MultiBaseResultstruct - Define
CalcErrorerror set andErrorInfostruct - Define
Modeenum (standard, programmer, financial) - Verify: compiles, types are importable from other engine modules
Task 1.3: Implement tokenizer [DONE]
- Create
engine/src/tokenizer.zig - Token types: numbers (dec, hex
0x, oct0o, bin0b), operators, parens, identifiers, comma, semicolon, EOF - Handle implicit multiplication detection (emit synthetic
*token) - Handle multi-character operators:
**,<<,>>,>>>,rol,ror - Support
_and,as digit separators in number literals (e.g.,1_000_000,1,000,000) - Verify: unit tests for tokenizing expressions in all bases, edge cases (negative numbers, adjacent tokens for implicit mult)
NOTE: Implicit multiplication handled in parser rather than tokenizer. Comma-as-separator only consumed when followed by a digit (avoids eating function arg commas).
Task 1.4: Implement Pratt parser [DONE]
- Create
engine/src/ast.zigwith AST node definitions - Create
engine/src/parser.zig - Implement Pratt parser with precedence table from design doc
- Accept
Modeparameter to resolve^ambiguity (power vs XOR) - Handle function calls:
name(arg1, arg2, ...) - Handle variable assignment:
X = expr - Handle implicit multiplication
- Verify: unit tests parsing complex expressions, operator precedence, all bases, function calls, mode-dependent
^
Task 1.5: Implement standard mode evaluator [DONE]
- Create
engine/src/evaluator.zig - Environment struct with variables, history, config (in evaluator.zig, not separate file)
- Evaluate AST to
f64for standard mode - Implement built-in functions: sin, cos, tan, asin, acos, atan, log, ln, sqrt, cbrt, abs, ceil, floor, round, factorial
- Implement constants: pi, e, tau
- Implement variable storage and
Ans - Implement history recording
- Verify: unit tests for arithmetic, precedence, functions, variables, history recall
Task 1.6: Implement programmer mode evaluator [DONE]
- Create
engine/src/programmer.zig - All operations on
Integer(u64 storage, masked to bit_width) - Implement bitwise operators: AND, OR, XOR, NOT, shifts, rotates
- Implement base conversion output (produce
MultiBaseResult) - Implement bit_pattern array generation
- Handle overflow/truncation per configured bit_width
- Signed vs unsigned interpretation
- Verify: unit tests for bitwise ops at all widths, base conversions, overflow behavior, sign extension
Task 1.7: Implement number display formatter [DONE]
- Create
engine/src/formatter.zig - Define
FormattedValuestruct withdisplay(human-readable) andraw(clipboard) strings - Decimal formatting: comma-separated groups of 3 (
4,294,967,295) - Scientific notation only when value > 10^15 or < 10^-15 or > 15 significant digits
- Hex formatting: underscore per 16-bit word for value view (
0xFFFF_FFFF), space per byte for memory view (FF FF FF FF) - Binary formatting: space per nibble (
1111 1111 1111 1111) - Octal formatting: underscore per 3-digit group (
0o37_777_777_777) - All formatters produce both
displayandrawvariants - Respect bit width (don't show leading zeros beyond the configured width in short-form contexts)
- Verify: unit tests for all bases at all bit widths, edge cases (0, max values, negative signed values), scientific notation threshold
Phase 2: Struct Layout & Financial Engines
Task 2.1: Implement struct DSL tokenizer and parser
- Create
engine/src/struct_layout.zig - Tokenize:
struct,{,},;, type names, identifiers,[,], numbers (for arrays) - Parse into a list of
FieldDefstructs:{ name, type, array_count? } - Support types: u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool, ptr
- Verify: unit tests parsing valid structs, error messages for malformed input
Task 2.2: Implement ABI profile interface and x86-64 System V profile
- Define
AbiProfilestruct with function pointers/vtable (field_layout, struct_alignment, tail_padding, pointer_size) - Implement
x86_64_sysvprofile:- u8/i8/bool -> size 1, align 1
- u16/i16 -> size 2, align 2
- u32/i32/f32 -> size 4, align 4
- u64/i64/f64/ptr -> size 8, align 8
- Struct alignment = max of field alignments
- Tail padding to struct alignment boundary
- Implement
packedprofile (all alignment = 1, no padding) - Verify: unit tests matching
paholeoutput for known structs
Task 2.3: Implement layout computation
- Implement
compute_layout(fields, abi) -> StructLayoutResult - Generate per-field: offset, size, alignment, padding_before
- Generate byte-level map (
[]ByteInfo) for visualization - Compute totals: size, alignment, total padding bytes, padding percentage
- Compute "packed comparison" size
- Verify: unit tests with structs of varying complexity, compare against known C compiler layouts
Task 2.4: Implement endianness display
- Given a
StructLayoutResultand a set of field values (optional), show byte ordering - Little-endian (default): LSB at lowest address
- Big-endian: MSB at lowest address
- This affects the byte-map visualization labels, not the layout computation itself
- Verify: unit test showing a u32 value's bytes in both endiannesses
Task 2.5: Implement financial module
- Create
engine/src/financial.zig - Implement CAGR:
(end/start)^(1/n) - 1 - Implement compound interest (future value):
PV * (1 + r/m)^(m*t) - Implement present value:
FV / (1 + r/m)^(m*t) - Implement TVM solver:
- Closed-form for FV, PV, PMT, N
- Newton-Raphson for I/Y (rate) with convergence check, max 1000 iterations, tolerance 1e-10
- Return step-by-step formula string alongside numeric result
- Verify: unit tests against known financial calculation results (textbook examples)
Task 2.6: Implement unit conversion engine
- Create
engine/src/units.zig - Define
UnitCategoryenum andUnitDefstruct (name, aliases, category, conversion factors) - Implement conversion tables for all categories: length, mass, temperature, time, digital storage, speed, area, volume, energy, pressure, data rate, angle
- Implement
convert(value: f64, from: UnitDef, to: UnitDef) !f64 - Temperature special case: formula-based conversion (C/F/K with offsets)
- Implement unit name resolution: given a string, find the matching
UnitDef(supports aliases) - Verify: unit tests for all conversion categories, round-trip accuracy, unknown unit error handling
Phase 3: Engine Public API & C ABI
Task 3.1: Implement engine public API
- Create
engine/src/engine.zig pub fn evaluate(env: *Environment, input: []const u8) !Valuepub fn computeStructLayout(definition: []const u8, abi: AbiProfile, endian: Endianness) !StructLayoutResultpub fn computeCagr(start: f64, end: f64, periods: f64) !f64pub fn solveTvm(params: TvmParams) !TvmResultpub fn convert(value: f64, from_unit: []const u8, to_unit: []const u8) !ConvertResultpub fn formatMultiBase(integer: Integer) MultiBaseResult- Verify: integration tests exercising the full pipeline (string in -> result out)
Task 3.2: Implement C API
- Create
engine/src/c_api.zig - Define
CalcString(ptr + len) andCalcResult(json ptr/len + error ptr/len) extern structs export fn tally_eval(expr_ptr, expr_len, mode, config_ptr, config_len) CalcResultexport fn tally_struct_layout(def_ptr, def_len, abi_ptr, abi_len, endian) CalcResultexport fn tally_convert(value, from_ptr, from_len, to_ptr, to_len) CalcResultexport fn tally_result_free(result) voidexport fn tally_version(out_len) [*]const u8- No null-terminated strings - all inputs are (ptr, len) pairs
- JSON serialization for all result types
- Input length validation (reject > max expression length)
- Verify: build shared library, write a C test program that calls the API with known inputs and validates JSON output
Task 3.3: Cross-compilation verification
- Verify
zig buildproduces binaries for:x86_64-linux-gnuaarch64-linux-gnux86_64-macosaarch64-macosx86_64-windows-gnu
- Verify
zig buildproduces .so for Android targets:aarch64-linux-androidx86_64-linux-android
- All targets invocable from the same
build.zigvia-Dtarget=<triple> - Verify mise-provisioned Zig handles all targets without additional toolchain installs
- Document any target-specific issues or workarounds needed
Phase 4: CLI Frontend
Task 4.1: Implement CLI argument parsing [DONE]
- Single binary at
src/main.zig(not separate cli/) - Parse args: positional expression (multi-arg joined with spaces),
-p/--programmer,--help,--version - Subcommands (
struct,cagr,tvm,convert) NOT YET IMPLEMENTED - Verify: unit tests for arg parsing;
--helpprints usage
NOTE: Multi-arg joining implemented (tally 2 + 2 works same as tally "2+2").
Subcommands deferred until Phase 2 engine work is done.
Task 4.2: Implement CLI output formatting [DONE]
- Standard mode: print result with comma formatting
- Programmer mode: print multi-base table (dec signed, dec unsigned, hex, bin)
- Struct mode: NOT YET IMPLEMENTED
- Financial mode: NOT YET IMPLEMENTED
--jsonflag: NOT YET IMPLEMENTED- Verify: unit tests for evaluate function output
Task 4.3: CLI integration and error handling [DONE]
- Wire arg parser -> engine calls -> output formatter
- Map
CalcErrorto user-friendly stderr messages - Set exit codes: 0 success, 1 error
- Verify: unit tests for error paths
Phase 5: TUI Frontend
Task 5.1: Set up TUI skeleton [DONE]
- Single binary at
src/main.zig+src/tui.zig(not separate tui/) - libvaxis vxfw.App with custom Widget (eventHandler + drawFn)
- No args launches TUI; with args runs CLI
- Molokai color theme
- Verify: TUI launches, responds to Ctrl-C (quit)
Task 5.2: Implement standard mode TUI view [DONE]
- Expression input via vxfw.TextField (gap buffer)
- Result display in scrollable history panel
- History navigation with up/down arrows (saves/restores current input)
- Ctrl-L clears history
- ? shows help overlay (keybindings, functions, constants, operators)
- Variables persist across expressions (assignment + Ans)
- Verify: can type expressions, see results, browse history
Task 5.3: Implement programmer mode TUI view - expression & base display [PARTIAL]
- Programmer mode accessible via Tab key
- Multi-base result display (dec signed, unsigned, hex, oct, bin)
- Bit grid with navigation (arrows) and toggling (Space)
- Width toggle (Ctrl-W cycles 8->16->32->64)
- Ctrl-B toggles focus between input and bit grid
- Expression evaluation through programmer engine
- DONE: basic passive display + expression input + bit grid toggling
- NOT DONE: editable fields (see Task 5.3.1)
Task 5.3.1: Programmer mode editable fields rework [NOT STARTED]
- Replace passive display + input prompt with navigable editable fields
- Each base representation (DEC signed, DEC unsigned, HEX, OCT, BIN) is an editable text field
- Up/Down arrows move focus between fields (bit grid, dec, hex, oct, bin, expression)
- Typing in any field parses the value in that base and updates all other fields
- Bit grid is one of the navigable fields (not a separate mode toggled with Ctrl-B)
- Expression field at the bottom for full programmer-mode expressions
- Remove Ctrl-B keybinding (field navigation replaces it)
- HEX field accepts/displays space-separated bytes (e.g. "FF 00 AB CD")
- Verify: can arrow between fields, edit hex and see decimal update, toggle bits and see all fields update, evaluate expression and see result in all fields
Task 5.4: Implement programmer mode TUI view - bit grid [DONE - merged into 5.3]
- Bit grid widget: 8 bits per group, groups separated by space
- Arrow key navigation (left/right moves cursor across bits, up/down between rows for 32/64-bit)
- Space/Enter toggles the bit under cursor
- Bit changes immediately reflected in all base displays
- Typing a value in the expression input updates the grid
- Toggling bits updates the expression/result displays
- Verify: can navigate grid, toggle bits, see values change in real-time
Task 5.5: Implement struct visualizer TUI view
- Create
tui/src/views/struct_viz.zig - Text input for struct definition (multi-line mini editor or single-line with parsing)
- Live-updating field table (offset, size, align, padding)
- Memory map visualization (byte grid with color-coded fields and padding)
- ABI toggle (
akey), endian toggle (ekey) - "Packed comparison" shown at bottom
- Transition: from programmer mode, press a key (e.g.,
S) to enter struct sub-view, Esc to return - Verify: can enter struct definition, see layout update, toggle ABI/endian
Task 5.6: Implement financial mode TUI view
- Create
tui/src/views/financial.zig - Form-style interface: labeled input fields for each financial function
- Sub-mode selector: CAGR, Compound Interest, TVM
- For TVM: 5 input fields, leave one empty to solve
- Result display with formula breakdown
- Verify: can input values, compute results, see formula steps
Task 5.7: Implement unit conversion TUI view
- Create
tui/src/views/convert.zig - Category selector (navigable list or hotkeys)
- From-value input field, from-unit selector
- To-unit selector, live-updating result
- Quick swap (from ↔ to) key
- Show conversion factor used
- Verify: can select category, units, enter value, see live result update
Task 5.8: TUI polish and keybindings
- Implement
?for help overlay showing all keybindings - Implement vi-style input mode (optional: Esc for normal mode, i for insert) - configurable
- Mouse support: click on bit grid to toggle, click on tabs to switch modes
- Color theme (sensible defaults, respects terminal capabilities)
- Verify: help overlay works, mouse interactions work, looks reasonable in 80x24 terminal
Phase 6: Android App
Task 6.1: Set up Android project structure
- Create
android/directory with Gradle project (Kotlin DSL) - Configure for Compose, minimum SDK 26 (Android 8.0), Material 3
- Create JNI bridge class
TallyEngine.ktwithexternal fundeclarations - Set up
jniLibs/directory structure for arm64-v8a, x86_64 - Native .so files produced by
zig build -Dtarget=aarch64-linux-android(etc.) - Gradle does not invoke Zig, it consumes prebuilt artifacts - Set up bottom navigation bar (Standard, Programmer, Financial, Convert)
- Verify:
zig build -Dtarget=aarch64-linux-androidproduces libtally.so; Gradle project builds and empty app launches with nav bar
Task 6.2: Implement JNI bridge
- Kotlin side:
TallyEngine.evaluate(expr, mode, config): String(returns JSON) - Kotlin side:
TallyEngine.structLayout(definition, abi, endian): String - Kotlin side:
TallyEngine.convert(value, fromUnit, toUnit): String - JSON deserialization into Kotlin data classes (Value, MultiBaseResult, StructLayoutResult, ConvertResult, etc.)
- Error handling: parse error JSON, surface to UI
- Verify: unit tests calling native functions with known inputs, validating deserialized results
Task 6.3: Implement standard calculator screen
- Compose UI: expression display at top (scrollable, natural format), live result preview
- Button grid: digits, operators, functions (swipeable function row for sin/cos/log/etc.)
- History peek below expression (last 2-3 results), pull-down for full history
- ViewModel: manages expression state, calls engine, stores history
- Long-press
=to copy result, tap history items to recall - Verify: can perform calculations, see results, scroll history
Task 6.4: Implement programmer mode screen
- Compose UI: segmented control for bit width (8/16/32/64)
- Value display cards: DEC, HEX, OCT, BIN, signed/unsigned (tappable to switch primary input base)
- Bit grid: custom Canvas composable, tappable cells with bit index labels, haptic on toggle
- Operator button row: AND, OR, XOR, NOT, <<, >>, ROL, ROR
- Hex digit buttons (A-F) + numeric keypad + base prefix buttons (0x, 0b)
- All displays update instantly on bit toggle or expression entry
- FAB/toolbar button navigating to struct layout sub-screen
- Verify: bit toggling works, base displays update, expressions evaluate
Task 6.5: Implement struct visualizer screen
- Navigation: accessed from programmer mode via toolbar button
- Form-based struct builder: type dropdown + name field per row, add/remove/reorder fields
- Toggle for raw DSL text input (for power users / copy-paste from code)
- Memory map: custom Canvas composable with color-coded byte bar, tappable segments highlighting field info
- Summary card: total size, alignment, padding bytes/percentage, packed comparison
- ABI dropdown selector, endian dropdown selector
- Verify: can add fields visually, see layout update, toggle between form and raw DSL
Task 6.6: Implement financial calculator screen
- Compose UI: scrollable chip row for function selection (CAGR, TVM, Compound Interest, Present Value)
- Card-based input form adapting to selected function
- For TVM: 5 labeled input fields, user leaves one blank to indicate solve target
- Compute button, result card with animated appearance
- Expandable formula breakdown section
- Long-press result to copy
- Verify: all financial calculations produce correct results, formula display works
Task 6.7: Implement unit conversion screen
- Compose UI: scrollable category chip row at top
- Large editable number input (top), from-unit selector dropdown
- Swap button (animated rotation) between from/to
- Live result display (bottom), to-unit selector dropdown
- Numeric keypad (no expression input needed - just digits, decimal, sign)
- Unit selectors open searchable bottom sheets
- Conversion updates live as user types (no "calculate" button)
- Verify: live conversion works, category switching filters units, swap works
Task 6.8: Android polish
- Material 3 theming (dynamic color on Android 12+)
- Tablet layout: two-pane (input left, results/visualization right)
- Landscape orientation: adapted layouts (wider bit grid, side-by-side panels)
- Haptic feedback on bit toggle and button presses
- Screen rotation state preservation
- App icon, splash screen
- Verify: looks correct on phone and tablet, handles rotation, haptics fire
Phase 7: Testing & Release
Task 7.1: Comprehensive engine test suite [IN PROGRESS]
- Target: >= 80% line coverage on engine code -- ACHIEVED: 97.71%
- 187 unit tests passing across engine + CLI modules
- Property-based tests for parser (roundtrip: parse -> format -> parse)
- Known-answer tests for all financial functions (textbook values)
- Unit conversion round-trip tests (convert A->B->A, verify precision)
- Struct layout verification against
paholeoutput for 20+ test structs - Fuzz testing for parser (random input shouldn't crash, should return error)
- Edge cases: max values, min values, NaN, infinity, division by zero
- Formatter tests: all bases, all bit widths, scientific notation threshold boundaries
- Coverage measured via
zig build coverage(kcov-based) - Verify: coverage report shows >= 80% on engine modules
Task 7.2: Integration tests
- CLI end-to-end tests (run binary, check stdout/stderr/exit code)
- TUI smoke tests (script key sequences, verify screen output via libvaxis test mode if available)
- Android instrumented tests for JNI bridge
Task 7.3: CI/CD setup
- Forgejo Actions workflow (at
git.lerch.org/lobo/tally):- Use mise to provision toolchains (same
.mise.tomlas local dev) - Build + test on Linux, macOS, Windows
- Enforce >= 80% engine coverage (fail CI if below)
- Cross-compile all targets
- Build Android .so files
- Use mise to provision toolchains (same
- Release workflow: produce platform binaries as Forgejo release artifacts
- Android: produce signed APK/AAB
Task 7.4: Documentation
README.md: project overview, build instructions, usage examplesCONTRIBUTING.md: dev setup, architecture overview, how to add an ABI profile- CLI
--helptext finalized - Struct DSL syntax reference (in README or separate doc)
Dependency Graph
Phase 1 (foundation) --> Phase 2 (struct + financial) --> Phase 3 (API)
|
+---------------+---------------+
v v v
Phase 4 (CLI) Phase 5 (TUI) Phase 6 (Android)
| | |
+---------------+---------------+
v
Phase 7 (Testing & Release)
Phases 4, 5, and 6 can be worked on in parallel once Phase 3 is complete.
Estimated Effort
| Phase | Effort | Notes |
|---|---|---|
| Phase 1 | 2-3 days | Core parsing + eval is the most algorithmic work |
| Phase 2 | 2-3 days | Struct layout, financial formulas, unit conversion tables |
| Phase 3 | 1 day | Thin wrappers + cross-compile verification |
| Phase 4 | 1 day | Straightforward CLI, most logic in engine |
| Phase 5 | 3-4 days | TUI widgets, bit grid interaction, struct viz, convert view |
| Phase 6 | 4-5 days | Android project setup, Compose UI (5 screens), JNI, polish |
| Phase 7 | 2 days | Testing infra, CI, docs |
| Total | ~15-19 days | For a single developer working focused |