From 3bdbd4c048c1b672fed127b295df2187bbda05ff Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Tue, 7 Apr 2026 23:18:49 +0200 Subject: [PATCH 1/3] Update to argmojo v.5.0 --- .github/workflows/run_tests.yaml | 2 - pixi.lock | 18 ++++ pixi.toml | 15 ++- src/cli/main.mojo | 170 +++++++++++++------------------ 4 files changed, 99 insertions(+), 106 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 91094c5..c5d5326 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -195,8 +195,6 @@ jobs: echo "$HOME/.pixi/bin" >> $GITHUB_PATH - name: pixi install run: pixi install - - name: Fetch argmojo - run: pixi run fetch - name: Build CLI binary run: pixi run buildcli - name: Run tests diff --git a/pixi.lock b/pixi.lock index b79dca3..5843ec3 100644 --- a/pixi.lock +++ b/pixi.lock @@ -12,6 +12,7 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://repo.prefix.dev/modular-community/linux-64/argmojo-0.5.0-hb0f4dca_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.2.0-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py313h18e8e13_0.conda @@ -107,6 +108,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://repo.prefix.dev/modular-community/osx-arm64/argmojo-0.5.0-h60d57d3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.2.0-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py313h48bb75e_0.conda @@ -212,6 +214,22 @@ packages: license_family: MIT size: 8191 timestamp: 1744137672556 +- conda: https://repo.prefix.dev/modular-community/linux-64/argmojo-0.5.0-hb0f4dca_0.conda + sha256: c1342a434a1150358ffbf150cb05eee5600f4df1755d81a7386b3c891229d7a8 + md5: bbeb104e87b1eeec44a39eda4d1f22ef + depends: + - mojo-compiler 0.26.2.* + license: Apache-2.0 + size: 2163429 + timestamp: 1775323629145 +- conda: https://repo.prefix.dev/modular-community/osx-arm64/argmojo-0.5.0-h60d57d3_0.conda + sha256: 8a6f43cc045c97ef335dca504cffe0b4b91d199d41f8f8e4deae79a801b4b53d + md5: f0a0c5c25cc6bb8b692176544b9b4000 + depends: + - mojo-compiler 0.26.2.* + license: Apache-2.0 + size: 2161961 + timestamp: 1775323591456 - conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_5.conda sha256: e1c3dc8b5aa6e12145423fed262b4754d70fec601339896b9ccf483178f690a6 md5: 767d508c1a67e02ae8f50e44cacfadb2 diff --git a/pixi.toml b/pixi.toml index 2c3cc2c..27a087e 100644 --- a/pixi.toml +++ b/pixi.toml @@ -10,6 +10,7 @@ version = "0.9.0" [dependencies] # argmojo = ">=0.4.0" # CLI argument parsing for the Decimo calculator (TODO: waiting for argmojo 0.4.0 compatible with mojo 0.26.2) +argmojo = ">=0.5.0,<0.6.0" # CLI argument parsing for the Decimo calculator mojo = ">=0.26.2.0,<0.26.3" # Mojo language compiler and runtime python = ">=3.13" # For Python bindings and tests python-build = ">=0.2.0" # Build PyPI wheel (`pixi run wheel`) @@ -77,13 +78,18 @@ dec_debug = """clear && pixi run package && cd benches/decimal128 \ && pixi run clean""" # Fetch argmojo source code for CLI calculator -fetch = """git clone https://github.com/forfudan/argmojo.git temp/argmojo 2>/dev/null || true \ -&& cd temp/argmojo && git checkout 24ca712fa291ec6a82dc9317f6ba5d0484d1fb47 2>/dev/null""" +# Only do this when necessary +# (argmojo is not compatible with the latest mojo version) +# fetch = """git clone https://github.com/forfudan/argmojo.git temp/argmojo 2>/dev/null || true \ +# && cd temp/argmojo && git checkout 29b6f54545f850e19d9a9ccfd1185d87f54e92b2 2>/dev/null""" # cli calculator bcli = "clear && pixi run buildcli" -buildcli = """pixi run mojo package temp/argmojo/src/argmojo -o temp/argmojo.mojopkg \ -&& pixi run mojo build -I src -I src/cli -I temp -o decimo src/cli/main.mojo""" +# Uncomment the following lines if we build the CLI pacakge with +# local clone of argmojo +# buildcli = """pixi run mojo package temp/argmojo/src/argmojo -o temp/argmojo.mojopkg \ +# && pixi run mojo build -I src -I src/cli -I temp -o decimo src/cli/main.mojo""" +buildcli = """pixi run mojo build -I src -I src/cli -o decimo src/cli/main.mojo""" tcli = "clear && pixi run testcli" testcli = "bash tests/test_cli.sh" @@ -101,7 +107,6 @@ all = """pixi run format \ &&pixi run package \ &&pixi run doc \ &&pixi run test \ -&&pixi run fetch \ &&pixi run buildcli \ &&pixi run testcli \ &&pixi run buildpy \ diff --git a/src/cli/main.mojo b/src/cli/main.mojo index c49bf4c..5704be9 100644 --- a/src/cli/main.mojo +++ b/src/cli/main.mojo @@ -11,7 +11,7 @@ from std.sys import exit -from argmojo import Arg, Command +from argmojo import Parsable, Option, Flag, Positional, Command from decimo.rounding_mode import RoundingMode from calculator.tokenizer import tokenize from calculator.parser import parse_to_rpn @@ -19,6 +19,63 @@ from calculator.evaluator import evaluate_rpn, final_round from calculator.display import print_error +struct DecimoArgs(Parsable): + var expr: Positional[ + String, + help="Math expression to evaluate (e.g. 'sqrt(abs(1.1*-12-23/17))')", + required=True, + ] + var precision: Option[ + Int, + long="precision", + short="p", + help="Number of significant digits", + default="50", + ] + var scientific: Flag[ + long="scientific", + short="s", + help="Output in scientific notation (e.g. 1.23E+10)", + ] + var engineering: Flag[ + long="engineering", + short="e", + help="Output in engineering notation (exponent multiple of 3)", + ] + var pad: Flag[ + long="pad", + short="P", + help="Pad trailing zeros to the specified precision", + ] + var delimiter: Option[ + String, + long="delimiter", + short="d", + help="Digit-group separator inserted every 3 digits (e.g. '_' gives 1_234.567_89)", + default="", + ] + var rounding_mode: Option[ + String, + long="rounding-mode", + short="r", + help="Rounding mode for the final result", + default="half-even", + choices="half-even,half-up,half-down,up,down,ceiling,floor", + ] + + @staticmethod + def description() -> String: + return "Arbitrary-precision CLI calculator powered by Decimo." + + @staticmethod + def version() -> String: + return "0.1.0" + + @staticmethod + def name() -> String: + return "decimo" + + def main(): try: _run() @@ -31,106 +88,21 @@ def main(): def _run() raises: - var cmd = Command( - "decimo", - ( - "Arbitrary-precision CLI calculator powered by Decimo.\n" - "\n" - "Note: if your expression contains *, ( or ), your shell may\n" - "intercept them before decimo runs. Use quotes or noglob:\n" - ' decimo "2 * (3 + 4)" # with quotes\n' - " noglob decimo 2*(3+4) # with noglob\n" - " alias decimo='noglob decimo' # add to ~/.zshrc" - ), - version="0.1.0", - ) - - # Positional: the math expression - cmd.add_argument( - Arg( - "expr", - help=( - "Math expression to evaluate (e.g. 'sqrt(abs(1.1*-12-23/17))')" - ), - ) - .positional() - .required() - ) - - # Named option: number of significant digits - cmd.add_argument( - Arg("precision", help="Number of significant digits (default: 50)") - .long["precision"]() - .short["p"]() - .default["50"]() - ) - - # Output formatting flags - # Mutually exclusive: scientific, engineering - cmd.add_argument( - Arg("scientific", help="Output in scientific notation (e.g. 1.23E+10)") - .long["scientific"]() - .short["s"]() - .flag() - ) - cmd.add_argument( - Arg( - "engineering", - help="Output in engineering notation (exponent multiple of 3)", - ) - .long["engineering"]() - .short["e"]() - .flag() - ) + var cmd = DecimoArgs.to_command() cmd.mutually_exclusive(["scientific", "engineering"]) - cmd.add_argument( - Arg( - "pad", - help="Pad trailing zeros to the specified precision", - ) - .long["pad"]() - .short["P"]() - .flag() - ) - cmd.add_argument( - Arg( - "delimiter", - help=( - "Digit-group separator inserted every 3 digits" - " (e.g. '_' gives 1_234.567_89)" - ), - ) - .long["delimiter"]() - .short["d"]() - .default[""]() + cmd.add_tip( + 'If your expression contains *, ( or ), quote it: decimo "2 * (3 + 4)"' ) - - # Rounding mode for the final result - cmd.add_argument( - Arg( - "rounding-mode", - help="Rounding mode for the final result (default: half-even)", - ) - .long["rounding-mode"]() - .short["r"]() - .choice["half-even"]() - .choice["half-up"]() - .choice["half-down"]() - .choice["up"]() - .choice["down"]() - .choice["ceiling"]() - .choice["floor"]() - .default["half-even"]() - ) - - var result = cmd.parse() - var expr = result.get_string("expr") - var precision = result.get_int("precision") - var scientific = result.get_flag("scientific") - var engineering = result.get_flag("engineering") - var pad = result.get_flag("pad") - var delimiter = result.get_string("delimiter") - var rounding_mode = _parse_rounding_mode(result.get_string("rounding-mode")) + cmd.add_tip("Or use noglob: alias decimo='noglob decimo' (add to ~/.zshrc)") + var args = DecimoArgs.parse_from_command(cmd^) + + var expr = args.expr.value + var precision = args.precision.value + var scientific = args.scientific.value + var engineering = args.engineering.value + var pad = args.pad.value + var delimiter = args.delimiter.value + var rounding_mode = _parse_rounding_mode(args.rounding_mode.value) # ── Phase 1: Tokenize & parse ────────────────────────────────────────── try: From 5e054bbad3ab36e375985a2a4a1d62ec2cb7c8c4 Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Tue, 7 Apr 2026 23:37:08 +0200 Subject: [PATCH 2/3] debug-level option: - > -- --- .github/workflows/run_tests.yaml | 2 ++ pixi.toml | 30 +++++++++++++++--------------- tests/test_bigdecimal.sh | 2 +- tests/test_bigfloat.sh | 2 +- tests/test_bigint.sh | 2 +- tests/test_bigint10.sh | 2 +- tests/test_biguint.sh | 2 +- tests/test_cli.sh | 2 +- tests/test_decimal128.sh | 2 +- tests/test_toml.sh | 2 +- 10 files changed, 25 insertions(+), 23 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index c5d5326..cad7a1a 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -1,5 +1,7 @@ name: CI on: + push: + branches: [main, dev] pull_request: workflow_dispatch: diff --git a/pixi.toml b/pixi.toml index 27a087e..95315a5 100644 --- a/pixi.toml +++ b/pixi.toml @@ -27,17 +27,17 @@ format = """pixi run mojo format ./src \ &&pixi run ruff format ./python""" # doc -doc = "pixi run mojo doc --diagnose-missing-doc-strings src/decimo" +doc = "pixi run mojo doc --diagnose-missing-doc-strings src/decimo > /dev/null" # compile the package p = "clear && pixi run package" package = """pixi run format \ -&& pixi run doc \ -&& pixi run package_decimo""" +&&pixi run doc \ +&&pixi run package_decimo""" package_decimo = """pixi run mojo package src/decimo \ -&& cp decimo.mojopkg tests/ \ -&& cp decimo.mojopkg benches/ \ -&& rm decimo.mojopkg""" +&&cp decimo.mojopkg tests/ \ +&&cp decimo.mojopkg benches/ \ +&&rm decimo.mojopkg""" # clean the package files in tests folder c = "clear && pixi run clean" @@ -65,17 +65,17 @@ bench = "pixi run package && bash benches/run_bench.sh" # bench with debug assertions enabled bdec_debug = """clear && pixi run package && cd benches/bigdecimal \ -&& pixi run mojo run -I ../ -D ASSERT=all bench.mojo && cd ../.. \ -&& pixi run clean""" +&&pixi run mojo run -I ../ -D ASSERT=all bench.mojo && cd ../.. \ +&&pixi run clean""" bint_debug = """clear && pixi run package && cd benches/bigint \ -&& pixi run mojo run -I ../ -D ASSERT=all bench.mojo && cd ../.. \ -&& pixi run clean""" +&&pixi run mojo run -I ../ -D ASSERT=all bench.mojo && cd ../.. \ +&&pixi run clean""" buint_debug = """clear && pixi run package && cd benches/biguint \ -&& pixi run mojo run -I ../ -D ASSERT=all bench.mojo && cd ../.. \ -&& pixi run clean""" +&&pixi run mojo run -I ../ -D ASSERT=all bench.mojo && cd ../.. \ +&&pixi run clean""" dec_debug = """clear && pixi run package && cd benches/decimal128 \ -&& pixi run mojo run -I ../ -D ASSERT=all bench.mojo && cd ../.. \ -&& pixi run clean""" +&&pixi run mojo run -I ../ -D ASSERT=all bench.mojo && cd ../.. \ +&&pixi run clean""" # Fetch argmojo source code for CLI calculator # Only do this when necessary @@ -85,7 +85,7 @@ dec_debug = """clear && pixi run package && cd benches/decimal128 \ # cli calculator bcli = "clear && pixi run buildcli" -# Uncomment the following lines if we build the CLI pacakge with +# Uncomment the following lines if we build the CLI package with # local clone of argmojo # buildcli = """pixi run mojo package temp/argmojo/src/argmojo -o temp/argmojo.mojopkg \ # && pixi run mojo build -I src -I src/cli -I temp -o decimo src/cli/main.mojo""" diff --git a/tests/test_bigdecimal.sh b/tests/test_bigdecimal.sh index 756927f..b445169 100755 --- a/tests/test_bigdecimal.sh +++ b/tests/test_bigdecimal.sh @@ -2,5 +2,5 @@ set -e for f in tests/bigdecimal/*.mojo; do - pixi run mojo run -I src -D ASSERT=all -debug-level=line-tables "$f" + pixi run mojo run -I src -D ASSERT=all --debug-level=line-tables "$f" done diff --git a/tests/test_bigfloat.sh b/tests/test_bigfloat.sh index 9249a64..f9f5081 100644 --- a/tests/test_bigfloat.sh +++ b/tests/test_bigfloat.sh @@ -37,7 +37,7 @@ trap cleanup EXIT for f in tests/bigfloat/*.mojo; do echo "=== $f ===" TMPBIN=$(mktemp /tmp/decimo_test_bigfloat_XXXXXX) - pixi run mojo build -I src -debug-level=line-tables \ + pixi run mojo build -I src --debug-level=line-tables \ -Xlinker -L./"$WRAPPER_DIR" -Xlinker -ldecimo_gmp_wrapper \ -o "$TMPBIN" "$f" DYLD_LIBRARY_PATH="./$WRAPPER_DIR" LD_LIBRARY_PATH="./$WRAPPER_DIR" "$TMPBIN" diff --git a/tests/test_bigint.sh b/tests/test_bigint.sh index a1011fa..94a45c5 100755 --- a/tests/test_bigint.sh +++ b/tests/test_bigint.sh @@ -2,5 +2,5 @@ set -e for f in tests/bigint/*.mojo; do - pixi run mojo run -I src -D ASSERT=all -debug-level=line-tables "$f" + pixi run mojo run -I src -D ASSERT=all --debug-level=line-tables "$f" done diff --git a/tests/test_bigint10.sh b/tests/test_bigint10.sh index 2ac7278..db746be 100755 --- a/tests/test_bigint10.sh +++ b/tests/test_bigint10.sh @@ -2,5 +2,5 @@ set -e for f in tests/bigint10/*.mojo; do - pixi run mojo run -I src -D ASSERT=all -debug-level=line-tables "$f" + pixi run mojo run -I src -D ASSERT=all --debug-level=line-tables "$f" done diff --git a/tests/test_biguint.sh b/tests/test_biguint.sh index 9d6694f..7e09a61 100755 --- a/tests/test_biguint.sh +++ b/tests/test_biguint.sh @@ -2,5 +2,5 @@ set -e for f in tests/biguint/*.mojo; do - pixi run mojo run -I src -D ASSERT=all -debug-level=line-tables "$f" + pixi run mojo run -I src -D ASSERT=all --debug-level=line-tables "$f" done diff --git a/tests/test_cli.sh b/tests/test_cli.sh index 123b57f..34c21ed 100644 --- a/tests/test_cli.sh +++ b/tests/test_cli.sh @@ -2,5 +2,5 @@ set -e # Exit immediately if any command fails for f in tests/cli/*.mojo; do - pixi run mojo run -I src -I src/cli -D ASSERT=all -debug-level=line-tables "$f" + pixi run mojo run -I src -I src/cli -D ASSERT=all --debug-level=line-tables "$f" done diff --git a/tests/test_decimal128.sh b/tests/test_decimal128.sh index 86eaf1f..e845e8b 100755 --- a/tests/test_decimal128.sh +++ b/tests/test_decimal128.sh @@ -2,5 +2,5 @@ set -e for f in tests/decimal128/*.mojo; do - pixi run mojo run -I src -D ASSERT=all -debug-level=line-tables "$f" + pixi run mojo run -I src -D ASSERT=all --debug-level=line-tables "$f" done diff --git a/tests/test_toml.sh b/tests/test_toml.sh index 82348e7..78efb43 100755 --- a/tests/test_toml.sh +++ b/tests/test_toml.sh @@ -2,5 +2,5 @@ set -e for f in tests/toml/*.mojo; do - pixi run mojo run -I src -D ASSERT=all -debug-level=line-tables "$f" + pixi run mojo run -I src -D ASSERT=all --debug-level=line-tables "$f" done From 219ee1ee7d73d92e8d9f11f916b52e7de9eac827 Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Tue, 7 Apr 2026 23:42:49 +0200 Subject: [PATCH 3/3] Fix comments --- tests/test_cli.sh | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/test_cli.sh b/tests/test_cli.sh index 34c21ed..79f78ee 100644 --- a/tests/test_cli.sh +++ b/tests/test_cli.sh @@ -1,6 +1,63 @@ #!/bin/bash set -e # Exit immediately if any command fails +# ── Unit tests ───────────────────────────────────────────────────────────── for f in tests/cli/*.mojo; do pixi run mojo run -I src -I src/cli -D ASSERT=all --debug-level=line-tables "$f" done + +# ── Integration tests (exercise the compiled binary) ─────────────────────── +BINARY="./decimo" + +if [[ ! -x "$BINARY" ]]; then + echo "SKIP: CLI integration tests ($BINARY not found)" + exit 0 +fi + +PASS=0 +FAIL=0 + +assert_output() { + local description="$1" + shift + local expected="$1" + shift + local actual + actual=$("$@" 2>&1) + if [[ "$actual" == "$expected" ]]; then + PASS=$((PASS + 1)) + else + echo "FAIL: $description" + echo " expected: $expected" + echo " actual: $actual" + FAIL=$((FAIL + 1)) + fi +} + +# Basic expression +assert_output "basic addition" "5" "$BINARY" "2+3" + +# Precision flag (-p) +assert_output "precision -p 10" "0.3333333333" "$BINARY" "1/3" -p 10 + +# Scientific notation (--scientific / -s) +assert_output "scientific notation" "1.2345678E+4" "$BINARY" "12345.678" --scientific + +# Engineering notation (--engineering / -e) +assert_output "engineering notation" "12.345678E+3" "$BINARY" "12345.678" --engineering + +# Delimiter flag (-d) +assert_output "delimiter underscore" "1_234_567.89" "$BINARY" "1234567.89" -d "_" + +# Rounding mode (--rounding-mode / -r) +assert_output "rounding mode ceiling" "0.33334" "$BINARY" "1/3" -p 5 -r ceiling + +# Pad flag (--pad / -P) +assert_output "pad trailing zeros" "0.33333" "$BINARY" "1/3" -p 5 --pad + +echo "" +echo "CLI integration tests: $PASS passed, $FAIL failed" + +if [[ "$FAIL" -gt 0 ]]; then + exit 1 +fi