Skip to content

Commit 7f51854

Browse files
authored
[cli] Enable negative expressions as positional arguments (#202)
This PR updates the Decimo CLI argument parsing so expressions that start with `-` (e.g., negative numbers / negative expressions) can be passed as the positional `<EXPR>` more naturally, and documents/tests the behavior. **Changes:** - Enable hyphen-prefixed values for the `expr` positional argument via ArgMojo (`allow_hyphen=True`) and remove the older command-level negative-number allowance. - Expand CLI integration tests to cover negative expressions, mixed option/positional ordering, and `--` separator cases. - Add user-manual documentation for “Negative Expressions” and update the CLI calculator plan checklist accordingly.
1 parent 8ee0b4c commit 7f51854

File tree

4 files changed

+102
-18
lines changed

4 files changed

+102
-18
lines changed

docs/plans/cli_calculator.md

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -311,23 +311,25 @@ Format the final `BigDecimal` result based on CLI flags:
311311
6. Documentation and examples in README (include shell completion setup).
312312
7. Build and distribute as a single binary.
313313

314-
| # | Task | Status | Notes |
315-
| ---- | --------------------------------------------------------------- | :----: | ------------------------------------------------------------------------------------------------------------------ |
316-
| 3.1 | Error messages with position info + caret display || Colored stderr: red `Error:`, green `^` caret |
317-
| 3.2 | Edge cases (div-by-zero, negative sqrt, empty expression, etc.) || 27 error-handling tests |
318-
| 3.3 | ArgMojo v0.5.0 declarative API migration || `Parsable` struct, `add_tip()`, `mutually_exclusive()`, `choices`, `--version` all working |
319-
| 3.4 | Built-in features (free with ArgMojo v0.5.0) || Typo suggestions, `NO_COLOR`, CJK full-width correction, prefix matching, `--` stop marker |
320-
| 3.5 | Shell completion (`--completions bash\|zsh\|fish`) || Built-in — zero code; needs documentation in user manual and README |
321-
| 3.6 | `allow_negative_numbers()` to allow pure negative numbers || Explicit opt-in in hybrid bridge; `decimo "-3"` works, expressions need quoting or `--` |
322-
| 3.7 | Numeric range on `precision` || `has_range=True, range_min=1, range_max=1000000000`; rejects `--precision 0` or `-5` |
323-
| 3.8 | Value names for help readability || `--precision <N>`, `--delimiter <CHAR>`, `--rounding-mode <MODE>` |
324-
| 3.9 | Argument groups in help output || `Computation` and `Formatting` groups in `--help` |
325-
| 3.10 | Custom usage line || `Usage: decimo [OPTIONS] <EXPR>` |
326-
| 3.11 | `Parsable.run()` override || Move eval logic into `DecimoArgs.run()` for cleaner separation |
327-
| 3.12 | Performance validation || No CLI-level benchmarks yet |
328-
| 3.13 | Documentation (user manual for CLI) || `docs/user_manual_cli.md`; include shell completion setup |
329-
| 3.14 | Build and distribute as single binary || |
330-
| 3.15 | Allow negative expressions || This needs ArgMojo to regard arguments with a hyphen and followed by more than one letter as a positional argument |
314+
| # | Task | Status | Notes |
315+
| ---- | --------------------------------------------------------------- | :----: | ------------------------------------------------------------------------------------------ |
316+
| 3.1 | Error messages with position info + caret display || Colored stderr: red `Error:`, green `^` caret |
317+
| 3.2 | Edge cases (div-by-zero, negative sqrt, empty expression, etc.) || 27 error-handling tests |
318+
| 3.3 | ArgMojo v0.5.0 declarative API migration || `Parsable` struct, `add_tip()`, `mutually_exclusive()`, `choices`, `--version` all working |
319+
| 3.4 | Built-in features (free with ArgMojo v0.5.0) || Typo suggestions, `NO_COLOR`, CJK full-width correction, prefix matching, `--` stop marker |
320+
| 3.5 | Shell completion (`--completions bash\|zsh\|fish`) || Built-in — zero code; needs documentation in user manual and README |
321+
| 3.6 | `allow_negative_numbers()` to allow pure negative numbers || Superseded by 3.15 `allow_hyphen=True`; removed in favour of the more general approach |
322+
| 3.7 | Numeric range on `precision` || `has_range=True, range_min=1, range_max=1000000000`; rejects `--precision 0` or `-5` |
323+
| 3.8 | Value names for help readability || `--precision <N>`, `--delimiter <CHAR>`, `--rounding-mode <MODE>` |
324+
| 3.9 | Argument groups in help output || `Computation` and `Formatting` groups in `--help` |
325+
| 3.10 | Custom usage line || `Usage: decimo [OPTIONS] <EXPR>` |
326+
| 3.11 | `Parsable.run()` override || Move eval logic into `DecimoArgs.run()` for cleaner separation |
327+
| 3.12 | Performance validation || No CLI-level benchmarks yet |
328+
| 3.13 | Documentation (user manual for CLI) || `docs/user_manual_cli.md`; include shell completion setup |
329+
| 3.14 | Build and distribute as single binary || |
330+
| 3.15 | Allow negative expressions || `allow_hyphen=True` on `Positional`; `decimo "-3*pi*(sin(1))"` works |
331+
| 3.16 | Make short names upper cases to avoid expression collisions || `-sin(1)` clashes with `-s` (scientific), `-e` clashes with `--engineering` |
332+
| 3.17 | Define `allow_hyphen_values` in declarative API || When argmojo supports it |
331333

332334
### Phase 4: Interactive REPL & Subcommands
333335

docs/user_manual_cli.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- [Rounding Mode (`--rounding-mode`, `-r`)](#rounding-mode---rounding-mode--r)
2121
- [Shell Integration](#shell-integration)
2222
- [Quoting Expressions](#quoting-expressions)
23+
- [Negative Expressions](#negative-expressions)
2324
- [Using noglob](#using-noglob)
2425
- [Examples](#examples)
2526
- [Basic Arithmetic](#basic-arithmetic)
@@ -87,8 +88,11 @@ decimo "2^256"
8788
- **Integers:** `42`, `-7`, `1000000`
8889
- **Decimals:** `3.14`, `0.001`, `.5`
8990
- **Negative numbers:** `-3`, `-3.14`, `(-5 + 2)`
91+
- **Negative expressions:** `-3*pi`, `-3*pi*(sin(1))`
9092
- **Unary minus:** `2 * -3`, `sqrt(-1 + 2)`
9193

94+
Negative numbers and many expressions starting with `-` can be passed directly as the positional argument. However, if the expression token looks like a CLI flag (e.g., `-e`), use `--` to force positional parsing. See [Negative Expressions](#negative-expressions) for details.
95+
9296
### Operators
9397

9498
| Operator | Description | Example | Result |
@@ -255,6 +259,38 @@ decimo "2 * (3 + 4)"
255259
decimo 2 * (3 + 4)
256260
```
257261

262+
### Negative Expressions
263+
264+
Most expressions starting with a hyphen (`-`) are treated as positional arguments, not as option flags:
265+
266+
```bash
267+
# Negative number
268+
decimo "-3.14"
269+
# → -3.14
270+
271+
# Negative expression
272+
decimo "-3*2"
273+
# → -6
274+
275+
# Complex negative expression
276+
decimo "-3*pi*(sin(1))"
277+
# → -7.930677192244368536658197969…
278+
279+
# Options can appear before or after the expression
280+
decimo -p 10 "-3*pi"
281+
decimo "-3*pi" -p 10
282+
```
283+
284+
**Caveat:** If the expression looks like an existing CLI flag (e.g., `-e` matches `--engineering`), ArgMojo will consume it as an option. Use `--` to force positional parsing in those cases:
285+
286+
```bash
287+
# -e is the engineering flag, so use -- to treat it as an expression
288+
decimo -- "-e"
289+
# → -2.71828…
290+
```
291+
292+
> **Note:** A future release will rename short flags to uppercase (`-S`, `-E`) to eliminate these collisions entirely.
293+
258294
### Using noglob
259295

260296
On zsh, you can use `noglob` to prevent shell interpretation:

src/cli/main.mojo

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,13 @@ def main():
102102
def _run() raises:
103103
var cmd = DecimoArgs.to_command()
104104
cmd.usage("decimo [OPTIONS] <EXPR>")
105-
cmd.allow_negative_numbers()
106105
cmd.mutually_exclusive(["scientific", "engineering"])
106+
# Allow expressions starting with '-' (e.g. "-3*pi*(sin(1))") to be
107+
# treated as positional values rather than option flags.
108+
for i in range(len(cmd.args)):
109+
if cmd.args[i].name == "expr":
110+
cmd.args[i]._allow_hyphen_values = True
111+
break
107112
cmd.add_tip(
108113
'If your expression contains *, ( or ), quote it: decimo "2 * (3 + 4)"'
109114
)

tests/test_cli.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,47 @@ assert_output "rounding mode ceiling" "0.33334" "$BINARY" "1/3" -p 5 -r ceiling
5555
# Pad flag (--pad / -P)
5656
assert_output "pad trailing zeros" "0.33333" "$BINARY" "1/3" -p 5 --pad
5757

58+
# ── Negative expressions (allow_hyphen) ───────────────────────────────────
59+
assert_output "negative number" "-3.14" "$BINARY" "-3.14"
60+
assert_output "negative integer" "-42" "$BINARY" "-42"
61+
assert_output "negative expression" "-6" "$BINARY" "-3*2"
62+
assert_output "negative expression with pi" "-9.424777961" "$BINARY" "-3*pi" -p 10
63+
assert_output "negative expr with abs()" "-3" "$BINARY" "-abs(3)" -p 10
64+
assert_output "negative expression complex" "17.32428719" "$BINARY" "-3*pi/sin(10)" -p 10
65+
assert_output "negative expression with parens" "-7.9306771922443685366581979690091558499739419154171" "$BINARY" "-3*pi*(sin(1))" -p 50
66+
assert_output "negative zero" "-0" "$BINARY" "-0"
67+
assert_output "negative minus negative" "1" "$BINARY" "-1*-1"
68+
assert_output "negative power" "-8" "$BINARY" "-2^3"
69+
assert_output "negative cancel out" "0" "$BINARY" "-1+1"
70+
assert_output "negative subtraction" "-50" "$BINARY" "-100+50"
71+
72+
# ── Option/positional ordering ────────────────────────────────────────────
73+
assert_output "options before expr" "0.3333333333" "$BINARY" -p 10 "1/3"
74+
assert_output "options after expr" "0.3333333333" "$BINARY" "1/3" -p 10
75+
assert_output "multiple options before expr" "1.732428719E+1" "$BINARY" -s -p 10 "-3*pi/sin(10)"
76+
assert_output "mixed order: flag expr option" "1.732428719E+1" "$BINARY" -s "-3*pi/sin(10)" -p 10
77+
assert_output "mixed order: option expr flag" "1.732428719E+1" "$BINARY" -p 10 "-3*pi/sin(10)" -s
78+
assert_output "engineering before expr" "-12.345678E+3" "$BINARY" -e "-12345.678"
79+
assert_output "engineering after expr" "-12.345678E+3" "$BINARY" "-12345.678" -e
80+
assert_output "delimiter before expr" "3.141_592_654" "$BINARY" -d _ -p 10 "pi"
81+
assert_output "delimiter after expr" "3.141_592_654" "$BINARY" "pi" -p 10 -d _
82+
assert_output "rounding before expr" "0.33334" "$BINARY" -p 5 -r ceiling "1/3"
83+
assert_output "all options before expr" "0.33334" "$BINARY" -p 5 -r ceiling --pad "1/3"
84+
assert_output "all options after expr" "0.33334" "$BINARY" "1/3" -p 5 -r ceiling --pad
85+
86+
# ── Double-dash separator ─────────────────────────────────────────────────
87+
assert_output "-- with negative expr" "-6" "$BINARY" -- "-3*2"
88+
assert_output "-- with negative number" "-3.14" "$BINARY" -- "-3.14"
89+
assert_output "-- with -e as expr" "-2.7182818284590452353602874713526624977572470937000" "$BINARY" -- "-e"
90+
91+
# ── Bare hyphen rejection ─────────────────────────────────────────────────
92+
if "$BINARY" -- - >/dev/null 2>&1; then
93+
echo "FAIL: bare hyphen should be rejected"
94+
FAIL=$((FAIL + 1))
95+
else
96+
PASS=$((PASS + 1))
97+
fi
98+
5899
echo ""
59100
echo "CLI integration tests: $PASS passed, $FAIL failed"
60101

0 commit comments

Comments
 (0)