initial commit, including Kiro specs
This commit is contained in:
parent
7a27b24c7a
commit
8253766e0f
4 changed files with 1607 additions and 0 deletions
982
.kiro/specs/calculator/design.md
Normal file
982
.kiro/specs/calculator/design.md
Normal file
|
|
@ -0,0 +1,982 @@
|
|||
# Design: Tally - Cross-Platform Calculator
|
||||
|
||||
## References
|
||||
|
||||
- #[[file:.kiro/specs/calculator/requirements.md]]
|
||||
|
||||
---
|
||||
|
||||
## 1. System Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Frontends │
|
||||
├─────────────┬──────────────────┬────────────────────────────┤
|
||||
│ CLI │ TUI │ Android │
|
||||
│ (Zig) │ (Zig+libvaxis) │ (Kotlin/Compose) │
|
||||
└──────┬──────┴────────┬─────────┴──────────────┬─────────────┘
|
||||
│ │ │
|
||||
│ Zig import │ Zig import │ JNI (C ABI)
|
||||
│ │ │
|
||||
┌──────▼───────────────▼────────────────────────▼─────────────┐
|
||||
│ Engine (Zig Library) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ parser │ evaluator │ programmer │ struct_layout │
|
||||
│ │ │ │ │
|
||||
│ tokens │ standard │ bitwise ops │ abi_profiles │
|
||||
│ ast │ financial │ base conv │ layout_compute │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
The engine is a pure Zig library with no I/O. Frontends depend on the engine; the engine depends on nothing outside `std`.
|
||||
|
||||
### Build Graph
|
||||
|
||||
```
|
||||
build.zig (workspace root)
|
||||
|- engine/build.zig -> produces: static lib (.a), C header, shared lib (.so/.dylib/.dll)
|
||||
|- cli/build.zig -> produces: statically-linked binary
|
||||
|- tui/build.zig -> produces: statically-linked binary (links libvaxis)
|
||||
|- android targets -> produces: libtally.so for arm64-v8a, x86_64
|
||||
(consumed by android/ Gradle project for APK packaging)
|
||||
```
|
||||
|
||||
**Build system philosophy:**
|
||||
- `zig build` is the single entry point for all compilation. No Makefile, no CMake, no shell scripts.
|
||||
- `zig build` (no args) -> builds engine + CLI + TUI for the host platform.
|
||||
- `zig build -Dtarget=aarch64-linux-android` -> cross-compiles engine .so for Android arm64.
|
||||
- `zig build test` -> runs all engine unit tests with coverage reporting.
|
||||
- The `android/` Gradle project handles only APK packaging and Compose UI compilation - it does not invoke Zig. Prebuilt .so files are committed or produced by a prior `zig build` step.
|
||||
|
||||
**Toolchain management (mise):**
|
||||
- `.mise.toml` at project root declares: Zig version, any other needed tools.
|
||||
- `mise install` provisions the full dev environment. No other manual setup required.
|
||||
- CI uses the same `.mise.toml` for reproducibility.
|
||||
|
||||
---
|
||||
|
||||
## 2. Engine Design
|
||||
|
||||
### 2.1 Module Breakdown
|
||||
|
||||
| Module | Responsibility |
|
||||
|--------|---------------|
|
||||
| `parser.zig` | Tokenizer + Pratt parser -> AST |
|
||||
| `ast.zig` | AST node definitions |
|
||||
| `evaluator.zig` | Walk AST, produce results |
|
||||
| `programmer.zig` | Integer operations, base conversion, bit manipulation |
|
||||
| `struct_layout.zig` | Struct DSL parser, layout computation, ABI profiles |
|
||||
| `units.zig` | Unit conversion tables and resolver |
|
||||
| `financial.zig` | CAGR, TVM, compound interest |
|
||||
| `types.zig` | Shared types (Value, Error, etc.) |
|
||||
| `engine.zig` | Public API surface (Zig-native) |
|
||||
| `c_api.zig` | `extern "C"` wrappers for JNI/FFI consumers |
|
||||
|
||||
### 2.2 Core Data Types
|
||||
|
||||
```zig
|
||||
/// Result of any calculation
|
||||
pub const Value = union(enum) {
|
||||
integer: Integer,
|
||||
float: f64,
|
||||
boolean: bool,
|
||||
struct_layout: StructLayoutResult,
|
||||
multi_base: MultiBaseResult, // programmer mode result
|
||||
};
|
||||
|
||||
pub const Integer = struct {
|
||||
/// Raw bits stored in u64, interpretation depends on context
|
||||
raw: u64,
|
||||
bit_width: BitWidth,
|
||||
signedness: Signedness,
|
||||
};
|
||||
|
||||
pub const BitWidth = enum { bits8, bits16, bits32, bits64 };
|
||||
pub const Signedness = enum { signed, unsigned };
|
||||
|
||||
pub const MultiBaseResult = struct {
|
||||
value: Integer,
|
||||
decimal_signed: []const u8, // formatted string
|
||||
decimal_unsigned: []const u8,
|
||||
hex: []const u8,
|
||||
octal: []const u8,
|
||||
binary: []const u8,
|
||||
bit_pattern: [64]u1, // individual bits, index 0 = LSB
|
||||
};
|
||||
```
|
||||
|
||||
### 2.3 AST Nodes
|
||||
|
||||
```zig
|
||||
pub const Expr = union(enum) {
|
||||
number_literal: NumberLiteral,
|
||||
unary_op: UnaryOp,
|
||||
binary_op: BinaryOp,
|
||||
function_call: FunctionCall,
|
||||
variable: []const u8,
|
||||
assignment: Assignment,
|
||||
};
|
||||
|
||||
pub const NumberLiteral = struct {
|
||||
value: f64,
|
||||
/// For programmer mode: preserve original base and integer value
|
||||
int_value: ?u64,
|
||||
base: Base,
|
||||
};
|
||||
|
||||
pub const Base = enum { decimal, hex, octal, binary };
|
||||
|
||||
pub const BinaryOp = struct {
|
||||
op: Operator,
|
||||
left: *Expr,
|
||||
right: *Expr,
|
||||
};
|
||||
|
||||
pub const Operator = enum {
|
||||
add, sub, mul, div, mod, pow,
|
||||
bit_and, bit_or, bit_xor, shift_left,
|
||||
shift_right_logical, shift_right_arithmetic,
|
||||
rotate_left, rotate_right,
|
||||
};
|
||||
```
|
||||
|
||||
### 2.4 Parser Design
|
||||
|
||||
**Approach: Pratt parser (top-down operator precedence)**
|
||||
|
||||
Precedence table (low -> high):
|
||||
|
||||
| Level | Operators |
|
||||
|-------|-----------|
|
||||
| 1 | `\|` (bitwise OR) |
|
||||
| 2 | `^` (bitwise XOR) - context-dependent, see below |
|
||||
| 3 | `&` (bitwise AND) |
|
||||
| 4 | `<<`, `>>`, `>>>`, `rol`, `ror` |
|
||||
| 5 | `+`, `-` |
|
||||
| 6 | `*`, `/`, `%` |
|
||||
| 7 | `**` or `^` (exponentiation - standard mode) |
|
||||
| 8 | Unary `-`, `~` (bitwise NOT) |
|
||||
| 9 | Function calls, parentheses |
|
||||
|
||||
**Context-dependent `^`**: In standard mode, `^` means exponentiation. In programmer mode, `^` means XOR. The parser accepts a mode parameter to resolve this. Programmer mode uses `**` for exponentiation if needed.
|
||||
|
||||
**Implicit multiplication**: The tokenizer detects adjacency patterns (number-identifier, number-paren, paren-paren) and inserts a synthetic `*` token.
|
||||
|
||||
### 2.5 Evaluation
|
||||
|
||||
The evaluator maintains an `Environment`:
|
||||
|
||||
```zig
|
||||
pub const Environment = struct {
|
||||
mode: Mode,
|
||||
variables: std.StringHashMap(Value),
|
||||
history: std.ArrayList(HistoryEntry),
|
||||
programmer_config: ProgrammerConfig,
|
||||
|
||||
pub const Mode = enum { standard, programmer, financial };
|
||||
|
||||
pub const ProgrammerConfig = struct {
|
||||
bit_width: BitWidth = .bits64,
|
||||
signedness: Signedness = .signed,
|
||||
display_endian: Endianness = .little,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Standard mode evaluates to `f64`. Programmer mode evaluates to `Integer` (exact, truncated to bit width). Financial functions return `f64` with optional step-by-step breakdown.
|
||||
|
||||
### 2.6 Number Display Formatting
|
||||
|
||||
The engine provides raw values; frontends apply display formatting. The engine includes a formatting module that produces display strings and raw (clipboard-friendly) strings separately.
|
||||
|
||||
#### Decimal Formatting
|
||||
|
||||
| Context | Display | Clipboard |
|
||||
|---------|---------|-----------|
|
||||
| Standard result | `4,294,967,295` | `4294967295` |
|
||||
| Financial result | `$1,234,567.89` | `1234567.89` |
|
||||
| Large decimals | `4,294,967,295` (never scientific unless > 15 digits of precision) | `4294967295` |
|
||||
|
||||
**Scientific notation threshold**: Only use scientific notation when the number exceeds what can be meaningfully displayed in decimal (> 15 significant digits, or absolute value > 10^15 or < 10^-15). Otherwise, always show full decimal with commas. This is a deliberate departure from calculators that jump to scientific notation at 10+ digits.
|
||||
|
||||
#### Programmer Mode Formatting
|
||||
|
||||
Values are displayed with visual grouping separators to aid readability, but clipboard copies the raw value without separators.
|
||||
|
||||
| Base | Display (value view) | Display (memory view) | Clipboard |
|
||||
|------|---------------------|----------------------|-----------|
|
||||
| Hex | `0xFFFF_FFFF` | `FF FF FF FF` | `0xFFFFFFFF` |
|
||||
| Bin | `1111 1111 1111 1111 1111 1111 1111 1111` | same | `0b11111111111111111111111111111111` |
|
||||
| Oct | `0o37_777_777_777` | - | `0o37777777777` |
|
||||
| Dec | `4,294,967,295` | - | `4294967295` |
|
||||
|
||||
Rationale for hex grouping:
|
||||
- **Value view uses underscore per 16-bit word** (`0xFFFF_FFFF`): matches Zig/Rust literal syntax - this is a programmer tool and values should look like code you'd actually type. Compact, familiar.
|
||||
- **Memory/byte view uses spaces per byte** (`FF FF FF FF`): matches hex editors, debuggers, `xxd` output. Used in struct visualizer and bit grid context.
|
||||
- These are two different views of the same data: the *value* (what you'd write in source code) vs. the *memory* (how it sits in RAM).
|
||||
|
||||
Binary always groups by nibble (4 bits) with spaces - universally expected by programmers.
|
||||
|
||||
#### Formatting Data Structure
|
||||
|
||||
```zig
|
||||
pub const FormattedValue = struct {
|
||||
/// Human-readable display string (with commas, underscores, spaces)
|
||||
display: []const u8,
|
||||
/// Machine/clipboard string (raw, no separators, includes prefix)
|
||||
raw: []const u8,
|
||||
};
|
||||
```
|
||||
|
||||
All frontends:
|
||||
- Show `display` in the UI
|
||||
- Copy `raw` to clipboard on long-press (Android) or yank (TUI)
|
||||
|
||||
---
|
||||
|
||||
## 3. Struct Layout Engine
|
||||
|
||||
### 3.1 Mini-DSL Grammar
|
||||
|
||||
```
|
||||
struct_def := "struct" "{" field_list "}"
|
||||
field_list := (field ";")*
|
||||
field := type_name identifier
|
||||
type_name := "u8" | "u16" | "u32" | "u64"
|
||||
| "i8" | "i16" | "i32" | "i64"
|
||||
| "f32" | "f64" | "bool" | "ptr"
|
||||
| "[" number "]" type_name // arrays
|
||||
identifier := [a-zA-Z_][a-zA-Z0-9_]*
|
||||
```
|
||||
|
||||
### 3.2 ABI Profile Interface
|
||||
|
||||
```zig
|
||||
pub const AbiProfile = struct {
|
||||
name: []const u8,
|
||||
pointer_size: u8, // bytes
|
||||
max_alignment: u8, // maximum alignment enforced
|
||||
|
||||
/// Given a field type, return its (size, alignment) in bytes
|
||||
field_layout: *const fn (field_type: FieldType) -> struct { size: u8, alignment: u8 },
|
||||
|
||||
/// Struct-level alignment (usually max of all field alignments)
|
||||
struct_alignment: *const fn (fields: []const FieldInfo) -> u8,
|
||||
|
||||
/// Whether tail padding is added to round up to struct alignment
|
||||
tail_padding: bool,
|
||||
};
|
||||
|
||||
// Built-in profiles
|
||||
pub const x86_64_sysv = AbiProfile{ ... }; // default
|
||||
pub const x86_64_win64 = AbiProfile{ ... }; // future
|
||||
pub const packed = AbiProfile{ ... }; // no padding
|
||||
```
|
||||
|
||||
### 3.3 Layout Computation Algorithm
|
||||
|
||||
```
|
||||
function compute_layout(fields, abi):
|
||||
offset = 0
|
||||
struct_align = 1
|
||||
result_fields = []
|
||||
|
||||
for each field in fields:
|
||||
(size, align) = abi.field_layout(field.type)
|
||||
struct_align = max(struct_align, align)
|
||||
|
||||
# Insert padding for alignment
|
||||
padding = (align - (offset % align)) % align
|
||||
if padding > 0:
|
||||
emit_padding(offset, padding)
|
||||
offset += padding
|
||||
|
||||
# Place field
|
||||
emit_field(field.name, field.type, offset, size)
|
||||
offset += size
|
||||
|
||||
# Tail padding
|
||||
if abi.tail_padding:
|
||||
tail = (struct_align - (offset % struct_align)) % struct_align
|
||||
if tail > 0:
|
||||
emit_padding(offset, tail)
|
||||
offset += tail
|
||||
|
||||
return StructLayout {
|
||||
total_size: offset,
|
||||
alignment: struct_align,
|
||||
fields: result_fields,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Layout Result Structure
|
||||
|
||||
```zig
|
||||
pub const StructLayoutResult = struct {
|
||||
total_size: usize,
|
||||
alignment: u8,
|
||||
fields: []const LayoutField,
|
||||
bytes: []const ByteInfo, // byte-by-byte map for visualization
|
||||
abi_name: []const u8,
|
||||
};
|
||||
|
||||
pub const LayoutField = struct {
|
||||
name: []const u8,
|
||||
type_name: []const u8,
|
||||
offset: usize,
|
||||
size: usize,
|
||||
alignment: u8,
|
||||
padding_before: usize,
|
||||
};
|
||||
|
||||
pub const ByteInfo = union(enum) {
|
||||
field: struct { field_index: usize, byte_within_field: usize },
|
||||
padding: struct { before_field_index: usize },
|
||||
tail_padding: void,
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Unit Conversion Engine
|
||||
|
||||
### 4.1 Architecture
|
||||
|
||||
Units are organized by category. Each category has a canonical base unit. All conversions go through the base unit (from -> base -> to), except temperature which uses formula-based conversion.
|
||||
|
||||
```zig
|
||||
pub const UnitCategory = enum {
|
||||
length, mass, temperature, time, digital_storage,
|
||||
speed, area, volume, energy, pressure, data_rate, angle,
|
||||
};
|
||||
|
||||
pub const UnitDef = struct {
|
||||
name: []const u8, // canonical name (e.g., "km")
|
||||
aliases: []const []const u8, // alternatives (e.g., "kilometer", "kilometers")
|
||||
category: UnitCategory,
|
||||
/// Conversion to base unit: value_in_base = value * factor + offset
|
||||
/// For most units, offset = 0. Temperature uses both.
|
||||
to_base_factor: f64,
|
||||
to_base_offset: f64,
|
||||
from_base_factor: f64,
|
||||
from_base_offset: f64,
|
||||
};
|
||||
```
|
||||
|
||||
### 4.2 Conversion Resolution
|
||||
|
||||
The parser recognizes the pattern `<expr> <unit> to <unit>`:
|
||||
- Tokenizer identifies known unit names after a numeric expression
|
||||
- `to` keyword triggers conversion mode
|
||||
- Both units must be in the same category (error otherwise)
|
||||
|
||||
For expressions like `5 kg + 3 lb`, the parser:
|
||||
1. Identifies `5 kg` as a unit-qualified value
|
||||
2. Sees `+ 3 lb` - converts `3 lb` to `kg` (left-hand unit wins)
|
||||
3. Evaluates as `5 + 1.36078 = 6.36078 kg`
|
||||
|
||||
### 4.3 Adding New Units
|
||||
|
||||
New units require only a table entry - no parser changes:
|
||||
|
||||
```zig
|
||||
// In units/length.zig
|
||||
pub const length_units = [_]UnitDef{
|
||||
.{ .name = "m", .aliases = &.{"meter", "meters"}, .to_base_factor = 1.0, ... },
|
||||
.{ .name = "km", .aliases = &.{"kilometer", "kilometers"}, .to_base_factor = 1000.0, ... },
|
||||
.{ .name = "mi", .aliases = &.{"mile", "miles"}, .to_base_factor = 1609.344, ... },
|
||||
// ... add new entries here
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Financial Module
|
||||
|
||||
### 5.1 CAGR
|
||||
|
||||
```
|
||||
CAGR = (end_value / start_value) ^ (1 / periods) - 1
|
||||
```
|
||||
|
||||
### 5.2 TVM (Time Value of Money)
|
||||
|
||||
The five TVM variables: N (periods), I/Y (interest rate per period), PV (present value), PMT (payment), FV (future value).
|
||||
|
||||
Relationship:
|
||||
```
|
||||
PV * (1 + r)^N + PMT * [((1 + r)^N - 1) / r] + FV = 0
|
||||
```
|
||||
where `r = I/Y / 100`.
|
||||
|
||||
Solving for the unknown variable:
|
||||
- **FV, PV, PMT**: algebraic (closed-form)
|
||||
- **N**: logarithmic solution
|
||||
- **I/Y**: Newton-Raphson iterative solver (no closed form)
|
||||
|
||||
---
|
||||
|
||||
## 6. C API (for Android/FFI)
|
||||
|
||||
```zig
|
||||
// c_api.zig - extern "C" exports
|
||||
//
|
||||
// SAFETY: All string inputs use pointer + length (not null-terminated).
|
||||
// This avoids buffer overread vulnerabilities and allows embedded nulls.
|
||||
// All returned strings include a length - caller must free with tally_result_free().
|
||||
|
||||
pub const CalcString = extern struct {
|
||||
ptr: [*]const u8,
|
||||
len: usize,
|
||||
};
|
||||
|
||||
pub const CalcResult = extern struct {
|
||||
/// JSON-encoded result. Null if error.
|
||||
json_ptr: ?[*]u8,
|
||||
json_len: usize,
|
||||
/// Error message if json_ptr is null.
|
||||
error_ptr: ?[*]u8,
|
||||
error_len: usize,
|
||||
};
|
||||
|
||||
/// Evaluate an expression, return JSON result.
|
||||
/// Caller must free the result with tally_result_free().
|
||||
export fn tally_eval(
|
||||
expr_ptr: [*]const u8,
|
||||
expr_len: usize,
|
||||
mode: c_int, // 0=standard, 1=programmer, 2=financial
|
||||
config_ptr: ?[*]const u8, // optional JSON config (null if none)
|
||||
config_len: usize, // 0 if no config
|
||||
) callconv(.c) CalcResult;
|
||||
|
||||
/// Compute struct layout, return JSON result.
|
||||
export fn tally_struct_layout(
|
||||
def_ptr: [*]const u8,
|
||||
def_len: usize,
|
||||
abi_ptr: [*]const u8, // "sysv", "win64", "packed"
|
||||
abi_len: usize,
|
||||
endian: c_int, // 0=little, 1=big
|
||||
) callconv(.c) CalcResult;
|
||||
|
||||
/// Convert a value between units, return JSON result.
|
||||
export fn tally_convert(
|
||||
value: f64,
|
||||
from_ptr: [*]const u8,
|
||||
from_len: usize,
|
||||
to_ptr: [*]const u8,
|
||||
to_len: usize,
|
||||
) callconv(.c) CalcResult;
|
||||
|
||||
/// Free a CalcResult's buffers.
|
||||
export fn tally_result_free(result: *CalcResult) callconv(.c) void;
|
||||
|
||||
/// Get version string (static lifetime, do not free).
|
||||
export fn tally_version(out_len: *usize) callconv(.c) [*]const u8;
|
||||
```
|
||||
|
||||
Design notes:
|
||||
- **No null-terminated strings.** All inputs take `(ptr, len)` pairs. This prevents buffer overread, allows binary data in expressions if ever needed, and is the idiomatic C pattern for length-known strings.
|
||||
- **`CalcResult` struct** bundles success/error in a single return. Caller checks `json_ptr != null` for success.
|
||||
- **JSON result format** keeps the Android/FFI boundary simple - Kotlin parses JSON natively. On the JNI side, the Kotlin bridge reads `(ptr, len)` into a `ByteArray` and decodes UTF-8.
|
||||
|
||||
---
|
||||
|
||||
## 7. CLI Design
|
||||
|
||||
```
|
||||
tally [OPTIONS] <expression>
|
||||
tally struct [OPTIONS] <definition | --file path>
|
||||
tally convert <value> <from_unit> <to_unit>
|
||||
tally cagr <start_value> <end_value> <periods>
|
||||
tally tvm [--n N] [--rate R] [--pv PV] [--pmt PMT] [--fv FV]
|
||||
|
||||
Options:
|
||||
-p, --programmer Programmer mode
|
||||
-b, --bits <WIDTH> Bit width: 8, 16, 32, 64 (default: 64)
|
||||
--base <BASE> Output base: dec, hex, oct, bin, all (default: all)
|
||||
--json JSON output
|
||||
--abi <ABI> Struct ABI: sysv (default), packed
|
||||
--endian <E> Endianness: little (default), big
|
||||
-h, --help Show help
|
||||
--version Show version
|
||||
```
|
||||
|
||||
### CLI Output Examples
|
||||
|
||||
Standard:
|
||||
```
|
||||
$ tally "2^32 - 1"
|
||||
4,294,967,295
|
||||
```
|
||||
|
||||
Programmer:
|
||||
```
|
||||
$ tally -p "0xFF & 0x0F"
|
||||
dec(signed): 15
|
||||
dec(unsigned): 15
|
||||
hex: 0x0000_0000_0000_000F
|
||||
oct: 0o17
|
||||
bin: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111
|
||||
bits: [64]
|
||||
```
|
||||
|
||||
Struct:
|
||||
```
|
||||
$ tally struct "struct { u32 x; u8 flags; u16 id; u64 ts; }"
|
||||
ABI: x86-64 System V | Endian: little
|
||||
|
||||
Offset Size Field Type Align Padding
|
||||
────── ──── ────────── ───── ───── ───────
|
||||
0 4 x u32 4
|
||||
4 1 flags u8 1
|
||||
5 1 (padding) 1 byte
|
||||
6 2 id u16 2
|
||||
8 8 ts u64 8
|
||||
|
||||
Total: 16 bytes (alignment: 8, padding: 1 byte, tail: 0)
|
||||
|
||||
Memory map:
|
||||
00: [xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx] x
|
||||
04: [ffff ffff] [········] [iiii iiii iiii iiii] flags, pad, id
|
||||
08: [tttt tttt tttt tttt tttt tttt tttt tttt ts
|
||||
0C: tttt tttt tttt tttt tttt tttt tttt tttt]
|
||||
```
|
||||
|
||||
Unit conversion:
|
||||
```
|
||||
$ tally convert 100 km miles
|
||||
62.1371 miles
|
||||
|
||||
$ tally "72 F to C"
|
||||
22.2222 °C
|
||||
|
||||
$ tally "5 kg + 3 lb"
|
||||
6.36078 kg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. TUI Layout
|
||||
|
||||
### 8.1 Standard Mode
|
||||
```
|
||||
┌─ Tally ─────────────────────── [Standard] [Programmer] [Financial] ─┐
|
||||
│ │
|
||||
│ History: │
|
||||
│ 2^10 = 1024 │
|
||||
│ sqrt(144) = 12 │
|
||||
│ Ans * 2 = 24 │
|
||||
│ │
|
||||
│ ───────────────────────────────────────────────────────────────── │
|
||||
│ > 3 * pi + 1_ │
|
||||
│ │
|
||||
│ = 10.42477796... │
|
||||
│ │
|
||||
└───────────────────────────── [d]ec [h]ex [o]ct [b]in ── q:quit ── ?:help┘
|
||||
```
|
||||
|
||||
### 8.2 Programmer Mode
|
||||
```
|
||||
┌─ Tally ─────────────────────── [Standard] [Programmer] [Financial] [Convert] ─┐
|
||||
│ Bit Width: [64] Signed: [yes] Endian: [LE] │
|
||||
│ │
|
||||
│ ┌─ Bit Pattern (63 -> 0) ─────────────────────────────────────────────────────┐ │
|
||||
│ │ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │
|
||||
│ │ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ cursor: bit 3 ▲ │
|
||||
│ │
|
||||
│ Signed: 255 │
|
||||
│ Unsigned: 255 │
|
||||
│ Hex: 0x0000_0000_0000_00FF │
|
||||
│ Oct: 0o377 │
|
||||
│ Bin: 0000 0000 0000 0000 0000 0000 1111 1111 │
|
||||
│ │
|
||||
│ > 0xFF & 0x0F_ │
|
||||
│ = 15 │
|
||||
│ │
|
||||
└──── ←→:nav Space:toggle w:width s:sign e:endian y:yank ── Tab:mode ────────┘
|
||||
```
|
||||
|
||||
### 8.3 Struct Visualizer (sub-view of Programmer Mode)
|
||||
```
|
||||
┌─ Tally ────────────────── [Programmer > Struct Layout] ─────────────┐
|
||||
│ ABI: x86-64 SysV Endian: LE │
|
||||
│ │
|
||||
│ Definition: │
|
||||
│ struct { u32 x; u8 flags; u16 id; u64 ts; } │
|
||||
│ │
|
||||
│ ┌─ Memory Map ─────────────────────────────────────────────────────┐ │
|
||||
│ │ 00 01 02 03 │ 04 │ 05 │ 06 07 │ 08 09 0A 0B 0C 0D 0E 0F │ │ │
|
||||
│ │ ████████████ │ ▓▓ │ ░░ │ ▒▒▒▒▒ │ ░░░░░░░░░░░░░░░░░░░░░░░ │ │ │
|
||||
│ │ x │flag│pad │ id │ ts │ │ │
|
||||
│ └───────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Field Type Offset Size Align Pad Before │
|
||||
│ x u32 0 4 4 0 │
|
||||
│ flags u8 4 1 1 0 │
|
||||
│ id u16 6 2 2 1 │
|
||||
│ ts u64 8 8 8 0 │
|
||||
│ │
|
||||
│ Total: 16 bytes | Alignment: 8 | Padding: 1 byte (6.25%) │
|
||||
│ Packed would be: 15 bytes │
|
||||
└──── a:abi e:endian Enter:edit struct Esc:back ─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Android UI Design
|
||||
|
||||
The Android app uses a fundamentally different interaction model from the TUI. Where the TUI is keyboard-driven with a prompt, Android is **touch-first with purpose-built input surfaces** for each mode. No text-cursor-in-a-prompt paradigm - instead, tappable buttons, interactive grids, and form fields.
|
||||
|
||||
### 9.1 Navigation & Shell
|
||||
|
||||
Bottom navigation bar with 4 destinations: **Standard**, **Programmer**, **Financial**, **Convert**.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ Mode Content Area │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
|
||||
│ 🔢 Standard 💻 Programmer 💰 Finance 🔄 Convert │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 9.2 Standard Calculator Screen
|
||||
|
||||
Classic calculator layout. Expression builds at top, result previews live below it, button grid fills the bottom half.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Expression area (scrollable) │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ 2 × (3 + 4) │ │ ← expression in natural format
|
||||
│ │ = 14 │ │ ← live result preview (dimmed)
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ History peek: last 2-3 results │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ 5^2 = 25 sqrt(144) = 12│ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ sin │ cos │ tan │ ( │ ) │ ⌫ │ │ ← function row (swipeable for more)
|
||||
│ ├─────┼─────┼─────┼─────┼─────┼───┤ │
|
||||
│ │ 7 │ 8 │ 9 │ ÷ │ ^ │ │ │
|
||||
│ ├─────┼─────┼─────┼─────┼─────┤ │ │
|
||||
│ │ 4 │ 5 │ 6 │ × │ % │ │ │
|
||||
│ ├─────┼─────┼─────┼─────┼─────┤ │ │
|
||||
│ │ 1 │ 2 │ 3 │ − │ √ │ │ │
|
||||
│ ├─────┼─────┼─────┼─────┼─────┤ │ │
|
||||
│ │ 0 │ . │ Ans │ + │ = │ │ │
|
||||
│ └─────┴─────┴─────┴─────┴─────┘ │ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key interactions:**
|
||||
- Tap buttons to build expression (no keyboard typing required)
|
||||
- Long-press `=` to copy result
|
||||
- Swipe function row for more functions (log, ln, abs, etc.)
|
||||
- Tap history items to recall into expression
|
||||
- Pull-down to expand full history view
|
||||
|
||||
### 9.3 Programmer Mode Screen
|
||||
|
||||
Split into three vertical zones: **value display**, **bit grid**, and **input buttons**.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ┌─── Bit Width ────────────────────┐ │
|
||||
│ │ [ 8 ] [ 16 ] [ 32 ] [●64] │ │ ← segmented control
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─── Value Display ────────────────┐ │
|
||||
│ │ DEC 4,294,967,295 │ │ ← tappable to switch primary
|
||||
│ │ HEX 0xFFFF_FFFF │ │
|
||||
│ │ OCT 0o37_777_777_777 │ │
|
||||
│ │ BIN 1111 1111 1111 1111 111… │ │
|
||||
│ │ SIGN -1 (signed) │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─── Bit Grid ─────────────────────┐ │
|
||||
│ │ 31 30 29 28 │ 27 26 25 24 │ │ ← bit index labels
|
||||
│ │ 1 1 1 1 │ 1 1 1 1 │ │ ← tappable cells
|
||||
│ │ 23 22 21 20 │ 19 18 17 16 │ │
|
||||
│ │ 1 1 1 1 │ 1 1 1 1 │ │
|
||||
│ │ 15 14 13 12 │ 11 10 9 8 │ │
|
||||
│ │ 1 1 1 1 │ 1 1 1 1 │ │
|
||||
│ │ 7 6 5 4 │ 3 2 1 0 │ │
|
||||
│ │ 1 1 1 1 │ 1 1 1 1 │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─── Operators ────────────────────┐ │
|
||||
│ │ AND│ OR │XOR │NOT │ << │ >> │ROL │ │ ← bitwise op buttons
|
||||
│ ├────┼────┼────┼────┼────┼────┼────┤ │
|
||||
│ │ A │ B │ C │ D │ E │ F │ │ │ ← hex input
|
||||
│ ├────┼────┼────┼────┼────┼────┤ │ │
|
||||
│ │ 7 │ 8 │ 9 │ 0x│ 0b│CLR │ │ │ ← digits + base prefix
|
||||
│ ├────┼────┼────┼────┼────┼────┤ │ │
|
||||
│ │ 4 │ 5 │ 6 │ + │ - │ = │ │ │
|
||||
│ ├────┼────┼────┼────┼────┼────┤ │ │
|
||||
│ │ 1 │ 2 │ 3 │ × │ ÷ │ ⌫ │ │ │
|
||||
│ └────┴────┴────┴────┴────┴────┘ │ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key interactions:**
|
||||
- Tap any bit in the grid to toggle it - all displays update instantly
|
||||
- Haptic pulse on bit toggle
|
||||
- Tap DEC/HEX/OCT/BIN label to make that the primary input base
|
||||
- Bit grid scrolls horizontally for 64-bit (or collapses to 4 rows of 16)
|
||||
- FAB or toolbar button for "Struct Layout" sub-screen
|
||||
|
||||
### 9.4 Struct Layout Screen (Programmer sub-screen)
|
||||
|
||||
Accessed via a button within Programmer mode. Uses a **form-based struct builder** rather than raw text DSL (though raw text input is available via a toggle).
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ← Back to Programmer │
|
||||
│ │
|
||||
│ ┌─── ABI / Settings ──────────────┐ │
|
||||
│ │ ABI: [x86-64 SysV ▾] Endian: [LE ▾]│
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─── Fields ───────────────────────┐ │
|
||||
│ │ ┌────────┬──────────┬─────────┐ │ │
|
||||
│ │ │ Type ▾ │ Name │ ⊖ │ │ │ ← each row is a field
|
||||
│ │ ├────────┼──────────┼─────────┤ │ │
|
||||
│ │ │ u32 │ x │ ⊖ │ │ │
|
||||
│ │ │ u8 │ flags │ ⊖ │ │ │
|
||||
│ │ │ u16 │ id │ ⊖ │ │ │
|
||||
│ │ │ u64 │ ts │ ⊖ │ │ │
|
||||
│ │ └────────┴──────────┴─────────┘ │ │
|
||||
│ │ [ + Add Field ] │ │
|
||||
│ │ [ ✏️ Raw DSL input toggle ] │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─── Memory Map ───────────────────┐ │
|
||||
│ │ ██████████ ▓▓ ░░ ▒▒▒▒ ░░░░░░░░░ │ │ ← color-coded byte bar
|
||||
│ │ x(4) fl p id ts(8) │ │ ← labels below
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─── Summary ──────────────────────┐ │
|
||||
│ │ Total: 16 bytes Align: 8 │ │
|
||||
│ │ Padding: 1 byte (6.25%) │ │
|
||||
│ │ Packed: 15 bytes │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key interactions:**
|
||||
- Type dropdown for each field (Material exposed dropdown menu)
|
||||
- Swipe-to-delete or tap ⊖ to remove a field
|
||||
- Drag handles to reorder fields (see padding change in real-time)
|
||||
- Memory map is a custom Canvas composable with tappable segments (tap a segment -> highlights field info)
|
||||
- Toggle for raw DSL text input (for copy-paste from code)
|
||||
- Share button: export struct layout as text or image
|
||||
|
||||
### 9.5 Financial Mode Screen
|
||||
|
||||
Card-based interface with function selector at top.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ┌─── Function ────────────────────┐ │
|
||||
│ │ [●CAGR] [TVM] [Compound] [PV] │ │ ← scrollable chip row
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─── CAGR Inputs ────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ Start Value │ │
|
||||
│ │ ┌─────────────────────────┐ │ │
|
||||
│ │ │ 10,000 │ │ │ ← text field
|
||||
│ │ └─────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ End Value │ │
|
||||
│ │ ┌─────────────────────────┐ │ │
|
||||
│ │ │ 25,000 │ │ │
|
||||
│ │ └─────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ Periods (years) │ │
|
||||
│ │ ┌─────────────────────────┐ │ │
|
||||
│ │ │ 5 │ │ │
|
||||
│ │ └─────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ [ Calculate ] │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─── Result ─────────────────────┐ │
|
||||
│ │ CAGR = 20.11% │ │
|
||||
│ │ │ │
|
||||
│ │ Formula: │ │
|
||||
│ │ (25000/10000)^(1/5) − 1 │ │
|
||||
│ │ = 2.5^0.2 − 1 │ │
|
||||
│ │ = 0.2011... │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key interactions:**
|
||||
- Chip row selects function type, input form adapts
|
||||
- For TVM: 5 input fields, user leaves one blank -> that's the solve target
|
||||
- Compute triggers engine call, result animates in
|
||||
- Result card shows formula breakdown (expandable)
|
||||
- Long-press result to copy
|
||||
|
||||
### 9.6 Convert Mode Screen
|
||||
|
||||
Designed for fast, one-handed unit conversion.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ┌─── Category ────────────────────┐ │
|
||||
│ │ [Length] [Mass] [Temp] [Time] ▸ │ │ ← scrollable chip row
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 100 │ │ ← large editable number
|
||||
│ │ kilometers [▾] │ │ ← unit selector dropdown
|
||||
│ │ │ │
|
||||
│ │ ⇅ (swap) │ │ ← tap to swap from/to
|
||||
│ │ │ │
|
||||
│ │ 62.1371 │ │ ← live result (updates as you type)
|
||||
│ │ miles [▾] │ │ ← unit selector dropdown
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─── Keypad ─────────────────────┐ │
|
||||
│ │ 7 │ 8 │ 9 │ │ │
|
||||
│ ├─────┼─────┼─────┤ ⌫ │ │
|
||||
│ │ 4 │ 5 │ 6 │ │ │
|
||||
│ ├─────┼─────┼─────┼─────────────┤ │
|
||||
│ │ 1 │ 2 │ 3 │ │ │
|
||||
│ ├─────┼─────┼─────┤ C │ │
|
||||
│ │ 0 │ . │ ± │ │ │
|
||||
│ └─────┴─────┴─────┴─────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key interactions:**
|
||||
- Conversion updates live as user types (no "calculate" button needed)
|
||||
- Swap button rotates from/to units with an animation
|
||||
- Unit selectors open a searchable bottom sheet (for categories with many units)
|
||||
- Category chips filter which units appear in the selectors
|
||||
- Recent conversions remembered
|
||||
|
||||
### 9.7 Tablet Layout (landscape / large screen)
|
||||
|
||||
On tablets, modes use a two-pane layout:
|
||||
|
||||
```
|
||||
┌─────────────────────┬──────────────────────────────┐
|
||||
│ │ │
|
||||
│ Input / Controls │ Results / Visualization │
|
||||
│ │ │
|
||||
│ (button grid, │ (history, bit grid, │
|
||||
│ form fields, │ memory map, formula │
|
||||
│ struct editor) │ breakdown) │
|
||||
│ │ │
|
||||
└─────────────────────┴──────────────────────────────┘
|
||||
│ 🔢 Standard 💻 Programmer 💰 Finance 🔄 Convert │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 9.8 Android Architecture (Technical)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ Compose UI Layer │
|
||||
│ ├── StandardScreen │
|
||||
│ ├── ProgrammerScreen │
|
||||
│ │ ├── BitGridComposable │
|
||||
│ │ └── StructVisualizerScreen │
|
||||
│ ├── FinancialScreen │
|
||||
│ └── ConvertScreen │
|
||||
├──────────────────────────────────────┤
|
||||
│ ViewModel Layer │
|
||||
│ ├── CalculatorViewModel │
|
||||
│ ├── ProgrammerViewModel │
|
||||
│ ├── FinancialViewModel │
|
||||
│ ├── ConvertViewModel │
|
||||
│ └── manages state, calls engine │
|
||||
├──────────────────────────────────────┤
|
||||
│ JNI Bridge (TallyEngine.kt) │
|
||||
│ ├── external fun evaluate(...) │
|
||||
│ ├── external fun structLayout(...) │
|
||||
│ ├── external fun convert(...) │
|
||||
│ └── JSON serialization/deser │
|
||||
├──────────────────────────────────────┤
|
||||
│ Native .so (libtally.so) │
|
||||
│ └── C ABI from engine/c_api.zig │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
The JNI bridge sends expression strings down and receives JSON results back. This keeps the boundary thin and debuggable.
|
||||
|
||||
---
|
||||
|
||||
## 10. Error Handling Strategy
|
||||
|
||||
```zig
|
||||
pub const CalcError = error{
|
||||
// Parser errors
|
||||
UnexpectedToken,
|
||||
UnmatchedParen,
|
||||
InvalidNumber,
|
||||
UnknownFunction,
|
||||
UnknownVariable,
|
||||
|
||||
// Evaluation errors
|
||||
DivisionByZero,
|
||||
Overflow,
|
||||
InvalidOperandType,
|
||||
DomainError, // e.g., sqrt(-1)
|
||||
|
||||
// Struct layout errors
|
||||
InvalidType,
|
||||
InvalidFieldName,
|
||||
DuplicateFieldName,
|
||||
StructTooLarge,
|
||||
|
||||
// Financial errors
|
||||
InsufficientParameters,
|
||||
ConvergenceFailure, // TVM Newton-Raphson didn't converge
|
||||
|
||||
// System
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
pub const ErrorInfo = struct {
|
||||
err: CalcError,
|
||||
message: []const u8,
|
||||
position: ?usize, // character position in input where error occurred
|
||||
context: []const u8, // snippet of input around error
|
||||
};
|
||||
```
|
||||
|
||||
All engine functions return `CalcError!Result`. Frontends translate these into user-facing messages with position highlighting.
|
||||
|
||||
---
|
||||
|
||||
## 11. Key Design Decisions
|
||||
|
||||
| # | Decision | Rationale |
|
||||
|---|----------|-----------|
|
||||
| 1 | Pratt parser over recursive descent | Easier to extend precedence levels; cleaner for infix with mixed prefix operators |
|
||||
| 2 | `u64` storage for all programmer integers | Simplest representation; bit_width used for masking/interpretation, not storage |
|
||||
| 3 | JSON over C ABI boundary | Avoids complex struct marshaling; Kotlin/Swift handle JSON natively; small perf cost acceptable for calculator |
|
||||
| 4 | Separate parser for struct DSL | Struct grammar is different enough from expressions; keeps both parsers simple |
|
||||
| 5 | ABI as interface/vtable | Adding Windows x64 or ARM is implementing one struct; no changes to layout algorithm |
|
||||
| 6 | libvaxis for TUI | Most mature Zig TUI; supports Windows/Mac/Linux; active development; used by Ghostty |
|
||||
| 7 | Mode as parser parameter | Resolves `^` ambiguity (power vs XOR) at parse time rather than eval time |
|
||||
| 8 | Statically-linked desktop binaries | Zero dependencies for end user; Zig makes this trivial |
|
||||
| 9 | Unit conversion via base-unit normalization | Simple to implement, easy to extend; only temperature needs special case (offset) |
|
||||
| 10 | Android: button grids, not text prompts | Touch-first UX; no keyboard needed for basic use; purpose-built surfaces per mode beat a generic expression prompt |
|
||||
| 11 | Android: form-based struct builder + raw toggle | Most users won't want to type DSL syntax on a phone; visual builder is more natural; power users can toggle to raw text |
|
||||
| 12 | Android: live conversion without "Calculate" button | Conversions are instant; eliminating a tap makes the UX feel responsive and direct |
|
||||
184
.kiro/specs/calculator/requirements.md
Normal file
184
.kiro/specs/calculator/requirements.md
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
# 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**: Support implicit multiplication (e.g., `2pi`, `3(4+5)`).
|
||||
|
||||
### 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-bit.
|
||||
- **FR-2.4**: Support bitwise operators: AND (`&`), OR (`|`), XOR (`^`), NOT (`~`), left shift (`<<`), right shift (logical `>>>`), arithmetic right shift (`>>`), rotate left (`rol`), rotate right (`ror`).
|
||||
- **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-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 <to_unit>` (e.g., `100 km to miles`, `72 F to C`).
|
||||
- **FR-4.2**: Support unit conversions within expressions: `5 kg + 3 lb` evaluates in the left-hand unit.
|
||||
- **FR-4.3**: Length: mm, cm, m, km, in, ft, yd, mi, nm (nautical mile).
|
||||
- **FR-4.4**: Mass/Weight: mg, g, kg, oz, lb, ton, tonne.
|
||||
- **FR-4.5**: Temperature: C (Celsius), F (Fahrenheit), K (Kelvin).
|
||||
- **FR-4.6**: Time: ms, s, min, hr, day, week, year.
|
||||
- **FR-4.7**: Digital storage: bit, byte, KB, MB, GB, TB, PB (both SI and binary: KiB, MiB, GiB, TiB).
|
||||
- **FR-4.8**: Speed: m/s, km/h, mph, knots.
|
||||
- **FR-4.9**: Area: mm2, cm2, m2, km2, in2, ft2, acre, hectare.
|
||||
- **FR-4.10**: Volume: ml, L, gal, qt, pt, fl_oz, cm3, m3.
|
||||
- **FR-4.11**: Energy: J, kJ, cal, kcal, Wh, kWh, BTU.
|
||||
- **FR-4.12**: Pressure: Pa, kPa, bar, atm, psi, mmHg.
|
||||
- **FR-4.13**: Data rate: bps, Kbps, Mbps, Gbps, B/s, KB/s, MB/s, GB/s.
|
||||
- **FR-4.14**: Angle: deg, rad, grad, turn.
|
||||
- **FR-4.15**: In CLI, support `tally "100 km to miles"` and `tally convert 100 km miles`.
|
||||
- **FR-4.16**: In TUI, dedicated conversion panel accessible from any mode.
|
||||
- **FR-4.17**: Unit system extensible - adding a new category requires only a conversion table, no parser changes.
|
||||
|
||||
### 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**: Keyboard-driven navigation; no mouse required (mouse optional enhancement).
|
||||
- **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-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 underscore grouping per 16-bit word (e.g., `0xFFFF_FFFF`).
|
||||
- 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).
|
||||
420
.kiro/specs/calculator/tasks.md
Normal file
420
.kiro/specs/calculator/tasks.md
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
# Tasks: Tally - Cross-Platform Calculator
|
||||
|
||||
## References
|
||||
|
||||
- #[[file:.kiro/specs/calculator/requirements.md]]
|
||||
- #[[file:.kiro/specs/calculator/design.md]]
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Project Scaffolding & Engine Foundation
|
||||
|
||||
### Task 1.1: Initialize workspace build structure
|
||||
- Create `.mise.toml` declaring Zig version (pin to specific release)
|
||||
- Create top-level `build.zig` with 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.zig` producing static lib and shared lib targets
|
||||
- Create `cli/build.zig` linking engine
|
||||
- Create `tui/build.zig` linking engine + libvaxis dependency
|
||||
- Add `.gitignore`, `README.md` (minimal), `LICENSE`
|
||||
- Verify: `mise install` provisions Zig, `zig build` compiles without errors on empty main files
|
||||
|
||||
### Task 1.2: Implement core types module
|
||||
- Create `engine/src/types.zig`
|
||||
- Define `Value` union, `Integer` struct, `BitWidth`, `Signedness`, `Endianness` enums
|
||||
- Define `MultiBaseResult` struct
|
||||
- Define `CalcError` error set and `ErrorInfo` struct
|
||||
- Define `Mode` enum (standard, programmer, financial)
|
||||
- Verify: compiles, types are importable from other engine modules
|
||||
|
||||
### Task 1.3: Implement tokenizer
|
||||
- Create `engine/src/tokenizer.zig`
|
||||
- Token types: numbers (dec, hex `0x`, oct `0o`, bin `0b`), operators, parens, identifiers, comma, semicolon, EOF
|
||||
- Handle implicit multiplication detection (emit synthetic `*` token)
|
||||
- Handle multi-character operators: `**`, `<<`, `>>`, `>>>`, `rol`, `ror`
|
||||
- Support `_` as digit separator in number literals (e.g., `1_000_000`, `0xFF_FF`)
|
||||
- Verify: unit tests for tokenizing expressions in all bases, edge cases (negative numbers, adjacent tokens for implicit mult)
|
||||
|
||||
### Task 1.4: Implement Pratt parser
|
||||
- Create `engine/src/ast.zig` with AST node definitions
|
||||
- Create `engine/src/parser.zig`
|
||||
- Implement Pratt parser with precedence table from design doc
|
||||
- Accept `Mode` parameter 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
|
||||
- Create `engine/src/evaluator.zig`
|
||||
- Create `engine/src/environment.zig` with `Environment` struct (variables, history, config)
|
||||
- Evaluate AST to `f64` for 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
|
||||
- Extend evaluator for `Mode.programmer`
|
||||
- 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
|
||||
- Create `engine/src/formatter.zig`
|
||||
- Define `FormattedValue` struct with `display` (human-readable) and `raw` (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 `display` and `raw` variants
|
||||
- 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 `FieldDef` structs: `{ 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 `AbiProfile` struct with function pointers/vtable (field_layout, struct_alignment, tail_padding, pointer_size)
|
||||
- Implement `x86_64_sysv` profile:
|
||||
- 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 `packed` profile (all alignment = 1, no padding)
|
||||
- Verify: unit tests matching `pahole` output 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 `StructLayoutResult` and 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 `UnitCategory` enum and `UnitDef` struct (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) !Value`
|
||||
- `pub fn computeStructLayout(definition: []const u8, abi: AbiProfile, endian: Endianness) !StructLayoutResult`
|
||||
- `pub fn computeCagr(start: f64, end: f64, periods: f64) !f64`
|
||||
- `pub fn solveTvm(params: TvmParams) !TvmResult`
|
||||
- `pub fn convert(value: f64, from_unit: []const u8, to_unit: []const u8) !ConvertResult`
|
||||
- `pub 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) and `CalcResult` (json ptr/len + error ptr/len) extern structs
|
||||
- `export fn tally_eval(expr_ptr, expr_len, mode, config_ptr, config_len) CalcResult`
|
||||
- `export fn tally_struct_layout(def_ptr, def_len, abi_ptr, abi_len, endian) CalcResult`
|
||||
- `export fn tally_convert(value, from_ptr, from_len, to_ptr, to_len) CalcResult`
|
||||
- `export fn tally_result_free(result) void`
|
||||
- `export 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 build` produces binaries for:
|
||||
- `x86_64-linux-gnu`
|
||||
- `aarch64-linux-gnu`
|
||||
- `x86_64-macos`
|
||||
- `aarch64-macos`
|
||||
- `x86_64-windows-gnu`
|
||||
- Verify `zig build` produces .so for Android targets:
|
||||
- `aarch64-linux-android`
|
||||
- `x86_64-linux-android`
|
||||
- All targets invocable from the same `build.zig` via `-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
|
||||
- Create `cli/src/main.zig`
|
||||
- Parse args: positional expression, `-p`/`--programmer`, `--bits`, `--base`, `--json`, `--help`, `--version`
|
||||
- Parse subcommands: `struct`, `cagr`, `tvm`, `convert`
|
||||
- For `tvm`: parse `--n`, `--rate`, `--pv`, `--pmt`, `--fv` (detect which is missing -> solve for it)
|
||||
- For `convert`: parse `tally convert <value> <from_unit> <to_unit>`
|
||||
- Also support inline conversion in expressions: `tally "100 km to miles"`
|
||||
- Verify: unit tests for arg parsing edge cases; `--help` prints usage
|
||||
|
||||
### Task 4.2: Implement CLI output formatting
|
||||
- Standard mode: print result (plain number or formatted float)
|
||||
- Programmer mode: print multi-base table (dec signed, dec unsigned, hex, oct, bin, bit pattern)
|
||||
- Struct mode: print table with offsets/sizes/padding + memory map ASCII art
|
||||
- Financial mode: print result + formula used
|
||||
- `--json` flag: output structured JSON for all modes
|
||||
- Verify: golden-file tests comparing output against expected strings
|
||||
|
||||
### Task 4.3: CLI integration and error handling
|
||||
- Wire arg parser -> engine calls -> output formatter
|
||||
- Map `CalcError` to user-friendly stderr messages with position indicators
|
||||
- Set exit codes: 0 success, 1 parse error, 2 eval error
|
||||
- Verify: end-to-end tests with both valid and invalid inputs
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: TUI Frontend
|
||||
|
||||
### Task 5.1: Set up TUI skeleton
|
||||
- Create `tui/src/main.zig` with app initialization, event loop, graceful shutdown (libvaxis already wired in Task 1.1)
|
||||
- Implement mode tab bar (Standard / Programmer / Financial / Convert) with Tab key switching
|
||||
- Implement basic window frame and resize handling
|
||||
- Verify: TUI launches, displays tabs, responds to Tab and q (quit)
|
||||
|
||||
### Task 5.2: Implement standard mode TUI view
|
||||
- Create `tui/src/views/standard.zig`
|
||||
- Expression input line with cursor (basic line editing: insert, delete, left/right, home/end)
|
||||
- Result display below input
|
||||
- Scrollable history panel above input
|
||||
- Wire input -> engine.evaluate() -> display result + push to history
|
||||
- Verify: can type expressions, see results, scroll history
|
||||
|
||||
### Task 5.3: Implement programmer mode TUI view - expression & base display
|
||||
- Create `tui/src/views/programmer.zig`
|
||||
- Expression input (same as standard but in programmer mode)
|
||||
- Multi-base result display (dec signed, unsigned, hex, oct, bin)
|
||||
- Quick-toggle keys: `d` highlights dec, `h` highlights hex, `o` oct, `b` bin
|
||||
- Status bar showing current bit width, signedness, endianness
|
||||
- Width toggle (`w` key cycles 8->16->32->64), sign toggle (`s`), endian toggle (`e`)
|
||||
- Verify: expressions evaluate in programmer mode, base display updates, toggles work
|
||||
|
||||
### Task 5.4: Implement programmer mode TUI view - bit grid
|
||||
- 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 (`a` key), endian toggle (`e` key)
|
||||
- "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.kt` with `external fun` declarations
|
||||
- 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-android` produces 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
|
||||
- **Target: >= 80% line coverage on engine code**
|
||||
- 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 `pahole` output 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 test` with coverage reporting enabled
|
||||
- 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.toml` as local dev)
|
||||
- Build + test on Linux, macOS, Windows
|
||||
- Enforce >= 80% engine coverage (fail CI if below)
|
||||
- Cross-compile all targets
|
||||
- Build Android .so files
|
||||
- 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 examples
|
||||
- `CONTRIBUTING.md`: dev setup, architecture overview, how to add an ABI profile
|
||||
- CLI `--help` text 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 |
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Emil Lerch
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Loading…
Add table
Reference in a new issue