Skip to main content

Natural language math expression calculator

This project has been archived.

The maintainers of this project have marked this project as archived. No new releases are expected.

Project description

nl-clicalc

CLI calculator accepting natural language and unit conversion. Standard library only.

For install as a CLI tool, clone the repo, cd into it, and run the install.py script. It will combine everything into one file and add it to your $path. Then you can run it like calc 2 meters plus 2ft. It ignores spacing and relies on spliting the input by operator.

Basic things work well, but some functionality is lightly tested. There might be edge cases with syntax parsing or conversion I haven't hit yet, though error handling is good. It can be chained into another script and used in webapps. It's passed all tests and it should be handling unexpected inputs well, but I have not tried this in a real environment.

Features

  • Natural Language Input: Write math expressions in plain English
  • Unit Conversions: Seamlessly convert between metric and imperial units
  • Scientific Functions: Support for trigonometric, logarithmic, and other mathematical functions
  • Physical Constants: Built-in scientific constants (Avogadro, Planck, Boltzmann, etc.)
  • Safe Evaluation: Uses AST-based parsing instead of eval() for security
  • Pure Python: No external dependencies - uses only the standard library
  • Webapp Ready: Thread-safe with caching, async support, and optimized performance

Installation

pip install -e .

Or run directly:

python -m nl_clicalc "five plus two"

Usage

Command Line

# Basic arithmetic
calc "five plus two"
# Output: 5+2 -> 7

# Complex expressions
calc "(twenty + five) * 3"
# Output: (20+5)*3 -> 75

# Unit conversions
calc "30m + 100ft"
# Output: 30*m+100*ft -> 60.48 m

calc "(30m + 100ft) / 2"
# Output: (30*m+100*ft)/2 -> 30.24 m

# Trigonometric functions
calc "sin of 3.14159"
# Output: math.sin(3.14159) -> 2.65e-06

# Physical constants
calc "5 times avogadro"
# Output: 5*na -> 3.01e+24

# Piping (quiet mode by default with -e)
echo "5 + 3" | calc -e
# Output: 8

# Interactive REPL mode
calc -i
# >>> five plus two
# 5+2 -> 7
# >>> quit

CLI Options

Option Description
-h, --help Show help and available operators
-v, --version Show version information
-e, --expression Evaluate a single expression (quiet mode by default)
-q, --quiet Suppress expression in output
-s, --show Show expression in output (useful with -e)
--json Output result as JSON
-i, --interactive Start interactive REPL mode

As a Python Module

from nl_clicalc import evaluate_raw, evaluate

# Basic math (use evaluate_raw for expressions with spaces/natural language)
result = evaluate_raw("5 + 3")
print(result)  # 8

# Natural language support
result = evaluate_raw("five plus three")
print(result)  # 8

# Unit conversions
result = evaluate_raw("30m + 100ft")
print(result)  # 60.48 m

# Use evaluate() for pre-normalized expressions (no spaces)
result = evaluate("5+3")  # Must have no spaces
print(result)  # 8

Webapp Usage (Optimized for Long-Running Applications)

from nl_clicalc import PyCalcApp

# Create app instance with caching (recommended for webapps)
app = PyCalcApp(cache_size=1000)

# Calculate (uses cache automatically)
result = app.calculate("5 + 3")           # 8
result = app.calculate("five plus two")   # 7
result = app.calculate("30m + 100ft")     # 60.48 m

# Async support for async web frameworks (FastAPI, aiohttp, etc.)
result = await app.calculate_async("5 + 3")

# Custom constants and functions
app.register_constant("myconst", 42)
result = app.calculate("myconst + 8")      # 50

# Cache management
print(app.cache_size)   # 3
app.clear_cache()

Full API Reference

from nl_clicalc import (
    # Core evaluation
    evaluate,              # Pre-normalized expressions (no spaces)
    evaluate_raw,         # Full pipeline with natural language support
    evaluate_cached,      # Like evaluate_raw, with LRU cache (1024 entries)
    evaluate_async,       # Async version of evaluate_raw
    
    # Configuration
    register_constant,    # Add custom constants (thread-safe)
    register_function,   # Add custom functions (thread-safe)
    load_user_config,    # Load config from nl_clicalc_config.py
    
    # Webapp wrapper
    PyCalcApp,            # Thread-safe wrapper with caching
    
    # Types
    EvaluationError,      # Exception type
    UnitValue,           # For unit-aware results
)

# Cached evaluation (great for repeated queries with natural language)
result = evaluate_cached("5 + 3")
result = evaluate_cached("five plus three")

# Async evaluation
import asyncio
result = await evaluate_async("5 + 3")

# Register custom constants globally
register_constant("pi_approx", 3.14)
register_constant("earth_radius", 6371)

# Register custom functions
def my_func(x, y):
    return x ** 2 + y ** 2

register_function("mysquare", my_func)
result = evaluate_raw("mysquare(3, 4)")  # 25

Performance

For webapps requiring high throughput:

Method Input Type Performance
evaluate() Pre-normalized (e.g., 5+3) Fastest, ~29 μs/eval
evaluate_raw() Natural language, spaces Full pipeline, ~50 μs/eval
evaluate_cached() Natural language, with cache O(1) after first call
PyCalcApp.calculate() Natural language, auto-caching O(1) after first call
PyCalcApp.calculate_async() Async, non-blocking For async frameworks

The library includes optimizations:

  • Pre-computed unit lookups
  • LRU caching for parsed expressions
  • Combined regex patterns for normalization
  • Thread-safe constant/function registration

API Reference

Core Functions

evaluate(expression: str) -> Any

Evaluate a pre-normalized expression (no spaces, no natural language). Use this for maximum performance when you control the input format.

from nl_clicalc import evaluate
result = evaluate("5+3")  # 8 (note: no spaces)

evaluate_raw(expression: str) -> Any

Evaluate a raw expression with spaces and/or natural language. This is the main function for user input.

from nl_clicalc import evaluate_raw
result = evaluate_raw("5 + 3")        # 8
result = evaluate_raw("five plus 3")  # 8

evaluate_cached(expression: str) -> Any

Like evaluate_raw() but with LRU caching. Best for repeated identical expressions.

from nl_clicalc import evaluate_cached
result = evaluate_cached("5 + 3")  # Cached after first call

evaluate_async(expression: str) -> Awaitable[Any]

Async version of evaluate_raw(). For async web frameworks.

from nl_clicalc import evaluate_async
result = await evaluate_async("5 + 3")

evaluate_with_timeout(expression: str, timeout: float = 5.0) -> Any

Evaluate with timeout protection. Recommended for untrusted input.

from nl_clicalc import evaluate_with_timeout, TimeoutError

try:
    result = evaluate_with_timeout("2 ** 1000000", timeout=1.0)
except TimeoutError:
    print("Evaluation timed out")

PyCalcApp Class

Thread-safe wrapper optimized for webapps with caching and instance isolation.

from nl_clicalc import PyCalcApp

app = PyCalcApp(cache_size=1000)  # LRU cache with 1000 entries

# Evaluate expressions
result = app.calculate("5 + 3")

# Async support
result = await app.calculate_async("5 + 3")

# Register instance-specific constants/functions
app.register_constant("myconst", 42)
app.register_function("double", lambda x: x * 2)

# Cache management
app.clear_cache()
print(app.cache_size)

Configuration Functions

register_constant(name: str, value: float) -> None

Register a custom constant globally. Thread-safe.

from nl_clicalc import register_constant
register_constant("earth_radius", 6371)
result = evaluate_raw("earth_radius")  # 6371

register_function(name: str, func: Callable) -> None

Register a custom function globally. Thread-safe. Only call during initialization.

from nl_clicalc import register_function
register_function("square", lambda x: x ** 2)
result = evaluate_raw("square(5)")  # 25

Exceptions

EvaluationError

Raised when an expression is invalid or contains unsupported operations.

from nl_clicalc import evaluate_raw, EvaluationError

try:
    result = evaluate_raw("import os")
except EvaluationError as e:
    print(f"Error: {e}")

TimeoutError

Raised when evaluation exceeds the timeout in evaluate_with_timeout().

from nl_clicalc import evaluate_with_timeout, TimeoutError

try:
    result = evaluate_with_timeout("slow_expression", timeout=1.0)
except TimeoutError:
    print("Timed out")

Types

UnitValue

Represents a numeric value with optional units.

from nl_clicalc import UnitValue

uv = UnitValue(5, "m")
print(uv)        # "5 m"
print(uv.value)  # 5.0
print(uv.unit)   # "m"

Utility Functions

normalize_unit(unit: str) -> str

Normalize a unit to its canonical form.

from nl_clicalc import normalize_unit
normalize_unit("meters")  # "m"
normalize_unit("ft")      # "ft"

get_conversion_factor(from_unit: str, to_unit: str) -> float

Get conversion factor between two units.

from nl_clicalc import get_conversion_factor
get_conversion_factor("ft", "m")  # 0.3048

get_all_units() -> list[str]

Get list of all supported units.

from nl_clicalc import get_all_units
units = get_all_units()  # ['A', 'B', 'BTU', 'C', 'F', 'GB', ...]

is_unit(text: str) -> bool

Check if text represents a unit.

from nl_clicalc import is_unit
is_unit("m")   # True
is_unit("xyz") # False

Supported Operations

Arithmetic

+, -, *, /, **

Number Words

  • 0-9: zero, one, two, three, four, five, six, seven, eight, nine
  • Teens: ten, eleven, twelve... nineteen
  • Tens: twenty, thirty, forty... ninety
  • Scales: hundred, thousand, million, billion, trillion

Functions

  • Trig: sin, cos, tan, asin, acos, atan, atan2
  • Hyperbolic: sinh, cosh, tanh, asinh, acosh, atanh
  • Math: sqrt, cbrt, log, log10, log2, log1p, exp, abs, floor, ceil, trunc, round, sign
  • Factorial & Combinatorics: factorial, gcd, lcm, perm, comb, nPr, nCr
  • Complex: real, imag, conj, phase, polar, rect
  • Bitwise: bitand, bitor, bitxor, bitnot, bin, hex, oct
  • Statistics: mean, median, mode, std, variance, sum, max, min
  • Prime: isprime, primefactors, nextprime, prevprime
  • Random: random, randint, uniform, randn, gauss, seed
  • Memory: store, recall, Mplus, Mminus, MR, MC
  • Variables: setvar, getvar, delvar, listvars, clearvars
  • Utility: clamp, hypot, percentof, aspercent
  • Temperature: temp

Bitwise Operators

& (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift)

Base Prefixes

0x (hex), 0b (binary), 0o (octal)

Complex Numbers

i or j for imaginary unit, e.g., 3+4i, 5j

Percentage

% suffix, e.g., 50% = 0.5

Units

Length

meters (m), kilometers (km), centimeters (cm), millimeters (mm), micrometers (μm), nanometers (nm), picometers (pm), inches (in), feet (ft), yards (yd), miles (mi), lightyears (ly), astronomical units (au), parsecs (pc), angstroms, fermis

Time

seconds (s), milliseconds (ms), microseconds (μs), nanoseconds (ns), picoseconds (ps), minutes (min), hours (h), days (d), weeks (wk), years (yr)

Data

bytes (B), kilobytes (KB), megabytes (MB), gigabytes (GB), terabytes (TB), petabytes (PB)

Mass

kilograms (kg), grams (g), milligrams (mg), micrograms (μg), nanograms (ng), pounds (lb), ounces (oz), tons

Volume

liters (L), milliliters (mL), gallons (gal), quarts (qt), pints (pt), cups

Pressure

Pascals (Pa), kilopascals (kPa), bar, atmospheres (atm), psi

Energy

Joules (J), kilojoules (kJ), calories (cal), kilocalories (kcal), watt-hours (Wh), kilowatt-hours (kWh), BTU, electronvolts (eV)

Power

Watts (W), kilowatts (kW), megawatts (MW), gigawatts (GW), horsepower (hp)

Constants

  • Mathematical: pi, e, tau, i (imaginary unit)
  • Physical: avogadro, gas constant, planck, boltzmann, speed of light (c), echarge (elementary charge), faraday, atomic mass unit (amu), vacuum permittivity

Advanced Features

Complex Numbers

Support for complex number arithmetic using i or j notation:

from nl_clicalc import evaluate_raw

# Complex literals
evaluate_raw("3 + 4i")      # (3+4j)
evaluate_raw("(3+4j)")      # (3+4j)

# Complex functions
evaluate_raw("sqrt(-1)")    # 1j
evaluate_raw("log(-1)")     # 3.14159265j (πi)
evaluate_raw("abs(3+4i)")   # 5.0
evaluate_raw("conj(3+4i)")  # (3-4j)
evaluate_raw("real(3+4j)")  # 3.0
evaluate_raw("imag(3+4j)")  # 4.0

# Euler's identity
evaluate_raw("e^(i*pi)")    # (-1+0j)

Bitwise Operations

Full support for bitwise operations:

from nl_clicalc import evaluate_raw

# Bitwise operators
evaluate_raw("5 AND 3")     # 1 (0b101 & 0b011)
evaluate_raw("5 OR 3")      # 7 (0b101 | 0b011)
evaluate_raw("5 XOR 3")     # 6 (0b101 ^ 0b011)
evaluate_raw("~5")          # -6 (bitwise NOT)
evaluate_raw("5 << 2")      # 20 (left shift)
evaluate_raw("5 >> 1")      # 2 (right shift)

# Base prefixes
evaluate_raw("0xFF")        # 255 (hexadecimal)
evaluate_raw("0b1010")      # 10 (binary)
evaluate_raw("0o777")       # 511 (octal)

# Base conversion functions
evaluate_raw("hex(255)")    # '0xff'
evaluate_raw("bin(10)")     # '0b1010'
evaluate_raw("oct(511)")    # '0o777'

Combinatorics

Permutations and combinations:

from nl_clicalc import evaluate_raw

# Permutations P(n,r) = n!/(n-r)!
evaluate_raw("perm(5, 3)")  # 60
evaluate_raw("nPr(5, 3)")   # 60 (alias)

# Combinations C(n,r) = n!/(r!(n-r)!)
evaluate_raw("comb(5, 3)")  # 10
evaluate_raw("nCr(5, 3)")   # 10 (alias)

# LCM and GCD
evaluate_raw("lcm(12, 18)")     # 36
evaluate_raw("lcm(12, 18, 24)") # 72
evaluate_raw("gcd(12, 18)")     # 6

Prime Functions

Prime number utilities:

from nl_clicalc import evaluate_raw

# Check if prime
evaluate_raw("isprime(17)")       # True
evaluate_raw("isprime(18)")       # False

# Prime factorization
evaluate_raw("primefactors(84)")  # "2^2 × 3 × 7"

# Find nearby primes
evaluate_raw("nextprime(17)")     # 19
evaluate_raw("prevprime(20)")     # 19

Statistical Functions

Extended statistical operations:

from nl_clicalc import evaluate_raw

# Basic statistics
evaluate_raw("mean(1, 2, 3, 4, 5)")        # 3.0
evaluate_raw("median(1, 2, 3, 4, 5)")      # 3
evaluate_raw("median(1, 2, 3, 4)")         # 2.5
evaluate_raw("mode(1, 2, 2, 3)")           # 2
evaluate_raw("variance(1, 2, 3, 4, 5)")    # 2.0
evaluate_raw("std(1, 2, 3, 4, 5)")         # 1.414...

# Aggregate functions
evaluate_raw("sum(1, 2, 3, 4, 5)")         # 15
evaluate_raw("min(3, 1, 4, 1, 5)")         # 1
evaluate_raw("max(3, 1, 4, 1, 5)")         # 5

Random Functions

Random number generation with seeding:

from nl_clicalc import evaluate_raw

# Seed for reproducibility
evaluate_raw("seed(42)")

# Random numbers
evaluate_raw("random()")         # 0.0-1.0
evaluate_raw("randint(1, 100)")  # Integer 1-100
evaluate_raw("uniform(0, 10)")   # Float 0-10

# Normal distribution
evaluate_raw("randn()")          # Standard normal (μ=0, σ=1)
evaluate_raw("gauss(100, 15)")   # Normal (μ=100, σ=15)

Percentage

Percentage calculations:

from nl_clicalc import evaluate_raw

# Percentage literals
evaluate_raw("50%")              # 0.5
evaluate_raw("25%")              # 0.25

# Percentage functions
evaluate_raw("percentof(20, 100)")   # 20.0 (20% of 100)
evaluate_raw("aspercent(25, 100)")   # 25.0 (25 as % of 100)

Memory Registers

Calculator-style memory operations:

from nl_clicalc import evaluate_raw

# Store and recall
evaluate_raw("store(42)")        # Store 42 in memory
evaluate_raw("recall()")         # 42
evaluate_raw("MR")               # 42 (alias)

# Memory add/subtract
evaluate_raw("Mplus(8)")         # 50 (adds 8 to memory)
evaluate_raw("Mminus(5)")        # 45 (subtracts 5)

# Clear memory
evaluate_raw("MC")               # Clears memory

Variables

User-defined variables:

from nl_clicalc import evaluate_raw

# Set and use variables
evaluate_raw('setvar("x", 10)')  # 10
evaluate_raw("x + 5")            # 15
evaluate_raw('setvar("y", 20)')  # 20
evaluate_raw("x * y")            # 200

# Variable management
evaluate_raw("getvar('x')")      # 10
evaluate_raw("listvars()")       # {'x': 10, 'y': 20}
evaluate_raw("delvar('x')")      # Deletes x
evaluate_raw("clearvars()")      # Deletes all variables

Utility Functions

from nl_clicalc import evaluate_raw

# Rounding and signs
evaluate_raw("round(3.14159, 2)")    # 3.14
evaluate_raw("sign(-5)")              # -1
evaluate_raw("sign(5)")               # 1

# Clamping
evaluate_raw("clamp(15, 0, 10)")     # 10
evaluate_raw("clamp(-5, 0, 10)")     # 0

# Hypotenuse
evaluate_raw("hypot(3, 4)")          # 5.0

Custom Configuration

Create a clicalc_config.py file to add custom constants, functions, and units:

# clicalc_config.py

# Custom constants
CUSTOM_CONSTANTS = {
    "myconst": 42,
    "earth_radius_km": 6371,
}

# Custom functions
CUSTOM_FUNCTIONS = {
    "mysquare": lambda x, y: x**2 + y**2,
}

# Custom units (add to existing category or create new)
CUSTOM_UNITS = {
    "m": {
        "nm": 1e-9,  # nanometers (already exists, but shows pattern)
    },
}

# Custom unit aliases
CUSTOM_ALIASES = {
    "meter": "m",
    "meters": "m",
}

# Custom temperature conversions
CUSTOM_TEMP_CONVERSIONS = {
    ("C", "R"): (1.0, 491.67),  # Celsius to Rankine
}

# Custom number words
CUSTOM_NUMBER_WORDS = {
    "1000000000000000": ["quadrillion"],
}

# Custom operator words
CUSTOM_OPERATOR_WORDS = {
    "+": ["plus", "add"],
}

Development

Running Tests

pip install pytest
pytest tests/

Project Structure

nl-clicalc/
├── nl-clicalc/
│   ├── __init__.py      # Package init
│   ├── __main__.py      # CLI entry point
│   ├── units.py         # Unit definitions and conversions
│   ├── evaluator.py     # AST-based expression evaluator
│   └── normalize.py     # Main parsing and normalization
├── tests/
│   ├── test_nl_clicalc.py  # Test suite
│   └── test_security_fuzz.py  # Security fuzz tests
├── pyproject.toml       # Package configuration
└── README.md            # This file

Security

nl-clicalc uses AST-based parsing instead of eval(), which provides:

  • No arbitrary code execution
  • Controlled function access
  • Safe constant evaluation
  • No access to system resources

Input Limits

Built-in protections against DoS attacks:

Constant Default Description
MAX_INPUT_LENGTH 10,000 Maximum input character length
MAX_NESTING_DEPTH 100 Maximum parentheses nesting depth
MAX_EXPONENT 10,000 Maximum exponent value
MAX_FACTORIAL 1,000 Maximum factorial input
MAX_RESULT_VALUE 1e308 Maximum result value
DEFAULT_CACHE_SIZE 1,024 LRU cache size

These can be imported and modified:

from nl_clicalc import MAX_INPUT_LENGTH, MAX_NESTING_DEPTH, MAX_EXPONENT

# Increase limits (use with caution for security)
MAX_EXPONENT = 100000

Security Considerations for Webapps

Safe for untrusted input:

  • evaluate(), evaluate_raw(), evaluate_cached(), evaluate_async()
  • PyCalcApp.calculate(), PyCalcApp.calculate_async()

Register with caution:

  • register_function() - Only register during initialization, never from user input
  • register_constant() - Safe to use, values are validated

Config file warning:

  • clicalc_config.py is imported from the working directory
  • For production, ensure this file is not user-writable
  • Consider removing config loading in high-security environments

Example: Secure Webapp Usage

from nl_clicalc import PyCalcApp, EvaluationError

app = PyCalcApp(cache_size=1000)

def handle_user_input(expression: str) -> dict:
    """Safely evaluate user-provided expression."""
    try:
        result = app.calculate(expression)
        return {"success": True, "result": str(result)}
    except EvaluationError as e:
        return {"success": False, "error": str(e)}

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nl_clicalc-1.1.1.tar.gz (53.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nl_clicalc-1.1.1-py3-none-any.whl (40.0 kB view details)

Uploaded Python 3

File details

Details for the file nl_clicalc-1.1.1.tar.gz.

File metadata

  • Download URL: nl_clicalc-1.1.1.tar.gz
  • Upload date:
  • Size: 53.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for nl_clicalc-1.1.1.tar.gz
Algorithm Hash digest
SHA256 30d427eea52417066d1a30798ebb0e5c36e3d0888ea463a4ad5a0fed873396df
MD5 f555b640e9800fc84f9ee1329d763944
BLAKE2b-256 d5661fbae691998f0c2e07714d301571943994d5ea52d02770a3ac53711a3799

See more details on using hashes here.

File details

Details for the file nl_clicalc-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: nl_clicalc-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 40.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for nl_clicalc-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 02ef9dbf2aca50c63c17f2b871b51a6fe0b16d34374705b161f43ebed7dcaaa2
MD5 938d16e63793d04683bb823593244c1b
BLAKE2b-256 f70fa90d09ed2082db8c2d911eefedbf24dee0dedcdd6145c3adf666f79add1d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page