Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 69 additions & 3 deletions docs/plans/cli_calculator.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,10 @@ Format the final `BigDecimal` result based on CLI flags:

1. Error messages: clear diagnostics for malformed expressions (e.g., "Unexpected token '*' at position 5").
2. Edge cases: division by zero, negative sqrt, overflow, empty expression.
3. Performance: ensure the tokenizer/parser overhead is negligible compared to BigDecimal computation.
4. Documentation and examples in README.
5. Build and distribute as a single binary.
3. Upgrade to ArgMojo v0.2.0 (once available in pixi). See [ArgMojo v0.2.0 Upgrade Tasks](#argmojo-v020-upgrade-tasks) below.
4. Performance: ensure the tokenizer/parser overhead is negligible compared to BigDecimal computation.
5. Documentation and examples in README.
6. Build and distribute as a single binary.

### Phase 4: Interactive REPL

Expand Down Expand Up @@ -303,6 +304,71 @@ decimo> exit

1. Detect full-width digits/operators for CJK users while parsing.

---

### ArgMojo v0.2.0 Upgrade Tasks

> **Prerequisite:** ArgMojo ≥ v0.2.0 is available as a pixi package.
>
> Reference: <https://github.com/forfudan/argmojo/releases/tag/v0.2.0>

Once ArgMojo v0.2.0 lands in pixi, apply the following changes to `decimo`:

#### 1. Auto-show help when no positional arg is given

ArgMojo v0.2.0 automatically displays help when a required positional argument is missing — no code change needed on our side. Remove the `.required()` guard if it interferes, or verify the behaviour works out of the box.

**Current (v0.1.x):** missing `expr` prints a raw error.
**After:** missing `expr` prints the full help text.

#### 2. Shell-quoting tips via `add_tip()`

Replace the inline description workaround with ArgMojo's dedicated `add_tip()` API. Tips render as a separate section at the bottom of `--help` output.

```mojo
cmd.add_tip('If your expression contains *, ( or ), wrap it in quotes:')
cmd.add_tip(' decimo "2 * (3 + 4)"')
cmd.add_tip('Or use noglob: noglob decimo 2*(3+4)')
cmd.add_tip("Or add to ~/.zshrc: alias decimo='noglob decimo'")
```

Remove the corresponding note that is currently embedded in the `Command` description string.

#### 3. Negative number passthrough

Enable `allow_negative_numbers()` so that expressions like `decimo -3+4` or `decimo -3.14` are treated as math, not as unknown CLI flags.

```mojo
cmd.allow_negative_numbers()
```

#### 4. Rename `Arg` → `Argument`

`Arg` is kept as an alias in v0.2.0, so this is optional but recommended for consistency with the new API naming.

```mojo
# Before
from argmojo import Arg, Command
# After
from argmojo import Argument, Command
```

#### 5. Colored error messages from ArgMojo

ArgMojo v0.2.0 produces ANSI-colored stderr errors for its own parse errors (e.g., unknown flags). Our custom `display.mojo` colors still handle calculator-level errors. Verify that both layers look consistent (same RED styling).

#### 6. Subcommands (Phase 4 REPL prep)

Although not needed immediately, the new `add_subcommand()` API could later support:

- `decimo repl` — launch interactive REPL
- `decimo eval "expr"` — explicit one-shot evaluation (current default)
- `decimo help <topic>` — extended help on functions, constants, etc.

This is deferred to Phase 4 planning.

---

## Design Decisions

### All Numbers Are `BigDecimal`
Expand Down
1 change: 1 addition & 0 deletions src/cli/calculator/__init__.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ from .tokenizer import (
)
from .parser import parse_to_rpn
from .evaluator import evaluate_rpn, evaluate
from .display import print_error, print_warning, print_hint
151 changes: 151 additions & 0 deletions src/cli/calculator/display.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# ===----------------------------------------------------------------------=== #
# Copyright 2025 Yuhao Zhu
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===----------------------------------------------------------------------=== #

"""
Display utilities for the Decimo CLI calculator.

Provides coloured error and warning output to stderr, and a
visual caret indicator that points at the offending position
in an expression. Modelled after ArgMojo's colour system.

```text
decimo "1 + @ * 2"
Error: unexpected character '@'
1 + @ * 2
^
```
"""

from sys import stderr

# ── ANSI colour codes ────────────────────────────────────────────────────────

comptime RESET = "\x1b[0m"
comptime BOLD = "\x1b[1m"

# Bright foreground colours.
comptime RED = "\x1b[91m"
comptime GREEN = "\x1b[92m"
comptime YELLOW = "\x1b[93m"
comptime BLUE = "\x1b[94m"
comptime MAGENTA = "\x1b[95m"
comptime CYAN = "\x1b[96m"
comptime WHITE = "\x1b[97m"
comptime ORANGE = "\x1b[33m" # dark yellow — renders as orange on most terminals

# Semantic aliases.
comptime ERROR_COLOR = RED
comptime WARNING_COLOR = ORANGE
comptime HINT_COLOR = YELLOW
comptime CARET_COLOR = GREEN


# ── Public API ───────────────────────────────────────────────────────────────


fn print_error(message: String):
"""Print a coloured error message to stderr.

Format: ``Error: <message>``

The label ``Error`` is displayed in bold red. The message text
follows in the default terminal colour.
"""
_write_stderr(
BOLD + ERROR_COLOR + "Error" + RESET + BOLD + ": " + RESET + message
)


fn print_error(message: String, expr: String, position: Int):
"""Print a coloured error message with a caret pointing at
the offending position in `expr`.

Example output (colours omitted for docstring):

```text
Error: unexpected character '@'
1 + @ * 2
^
```

Args:
message: Human-readable error description.
expr: The original expression string.
position: 0-based column index to place the caret indicator.
"""
_write_stderr(
BOLD + ERROR_COLOR + "Error" + RESET + BOLD + ": " + RESET + message
)
_write_caret(expr, position)


fn print_warning(message: String):
"""Print a coloured warning message to stderr.

Format: ``Warning: <message>``

The label ``Warning`` is displayed in bold orange/yellow.
"""
_write_stderr(
BOLD + WARNING_COLOR + "Warning" + RESET + BOLD + ": " + RESET + message
)


fn print_warning(message: String, expr: String, position: Int):
"""Print a coloured warning message with a caret indicator."""
_write_stderr(
BOLD + WARNING_COLOR + "Warning" + RESET + BOLD + ": " + RESET + message
)
_write_caret(expr, position)


fn print_hint(message: String):
"""Print a coloured hint message to stderr.

Format: ``Hint: <message>``

The label ``Hint`` is displayed in bold cyan.
"""
_write_stderr(
BOLD + HINT_COLOR + "Hint" + RESET + BOLD + ": " + RESET + message
)


# ── Internal helpers ─────────────────────────────────────────────────────────


fn _write_stderr(msg: String):
"""Write a line to stderr."""
print(msg, file=stderr)


fn _write_caret(expr: String, position: Int):
"""Print the expression line and a green caret (^) under the
given column position to stderr.

```text
1 + @ * 2
^
```
"""
# Expression line — indented by 2 spaces.
_write_stderr(" " + expr)

# Caret line — spaces + coloured '^'.
var caret_col = position if position >= 0 else 0
if caret_col > len(expr):
caret_col = len(expr)
_write_stderr(" " + " " * caret_col + CARET_COLOR + "^" + RESET)
Loading