Metadata-Version: 2.4
Name: expression-py
Version: 0.1.0
Summary: Mathematical expression evaluation library with PEG parser
Author-email: "Jakub T. Jankiewicz" <jcubic.jj@gmail.com>
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, https://github.com/jcubic/expression.py
Project-URL: Repository, https://github.com/jcubic/expression.py
Project-URL: Issues, https://github.com/jcubic/expression.py/issues
Keywords: expression,evaluator,parser,peg,math
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pegen>=0.3.0
Dynamic: license-file

# expression.py

[![pip](https://img.shields.io/badge/pip-0.1.0-blue.svg)](https://pypi.org/project/expression-py/)
[![CI](https://github.com/jcubic/expression.py/actions/workflows/ci.yml/badge.svg)](https://github.com/jcubic/expression.py/actions/workflows/ci.yml)
[![PyPI Downloads](https://static.pepy.tech/personalized-badge/expression-py?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/expression-py)
[![expression.py GitHub repo](https://img.shields.io/badge/github-expression.py-orange?logo=github)](https://github.com/jcubic/expression.py)
[![Coverage Status](https://coveralls.io/repos/github/jcubic/expression.py/badge.svg?branch=master)](https://coveralls.io/github/jcubic/expression.py?branch=master)
[![LICENSE GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue.svg)](https://github.com/jcubic/expression.py/blob/master/LICENSE)

Mathematical expression evaluation library for Python, built with a PEG parser generated by
[pegen](https://github.com/we-like-parsers/pegen). An alternative to
[safeeval](https://pypi.org/project/safeeval/) that supports more Ruby operators like `=~` and array
operators.

This is a Python port of [jcubic/expression.php](https://github.com/jcubic/expression.php).

## Installation

```bash
pip install expression-py
```

## Usage

```python
from expression import Expression

expr = Expression()

# Basic arithmetic
expr.evaluate("2 + 3 * 4")       # 14
expr.evaluate("(2 + 3) * 4")     # 20
expr.evaluate("2 ** 10")         # 1024
expr.evaluate("10 % 3")          # 1

# Floating point
expr.evaluate("0.1 + 0.2")       # 0.30000000000000004
expr.evaluate("1.5e2")           # 150.0

# Hex and binary literals
expr.evaluate("0xFF")            # 255
expr.evaluate("0b1010")          # 10

# Bitshift operators
expr.evaluate("100 >> 2")        # 25
expr.evaluate("100 << 2")        # 400
```

### Variables

```python
expr = Expression()

# Assignment
expr.evaluate("x = 10")
expr.evaluate("x + 2")           # 12

# Pre-set variables
expr.variables = {"width": 100, "height": 50}
expr.evaluate("width * height")  # 5000
```

### Built-in Constants and Functions

```python
expr = Expression()

# Constants
expr.evaluate("pi")              # 3.141592653589793
expr.evaluate("e")               # 2.718281828459045

# Math functions
expr.evaluate("sqrt(16)")        # 4.0
expr.evaluate("sin(pi / 2)")     # 1.0
expr.evaluate("abs(-42)")        # 42
expr.evaluate("log(e)")          # 1.0
```

Available built-in functions: `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `sqrt`, `abs`, `log`, `ln`.

Aliases: `arcsin`/`arccos`/`arctan`/`arcsinh`/`arccosh`/`arctanh` map to their `a`-prefixed equivalents. `ln` is an alias for `log`.

### Custom Functions

```python
expr = Expression()

# Define functions in the expression language
expr.evaluate("f(x, y) = x + y")
expr.evaluate("f(10, 20)")       # 30

expr.evaluate("square(x) = x * x")
expr.evaluate("square(5)")       # 25

# Register Python functions
expr.functions["even"] = lambda x: x % 2 == 0
expr.evaluate("even(4)")         # True

# Access defined functions from Python
expr.evaluate("add(a, b) = a + b")
expr.functions["add"](3, 4)      # 7
```

### Implicit Multiplication

```python
expr = Expression()

expr.evaluate("f(x) = 2x")
expr.evaluate("f(5)")            # 10
expr.evaluate("x = 3")
expr.evaluate("2x")              # 6
expr.evaluate("2(3 + 4)")        # 14
```

### Boolean and Comparison Operators

```python
expr = Expression()

# Comparisons
expr.evaluate("10 == 10")        # True
expr.evaluate("10 != 20")        # True
expr.evaluate("10 > 5")          # True
expr.evaluate("10 <= 10")        # True

# Strict equality (type-aware)
expr.evaluate("2 === 2")         # True
expr.evaluate("'2' !== 2")       # True

# Boolean operators
expr.evaluate("10 > 5 && 20 > 10")  # True
expr.evaluate("10 > 50 || 20 > 10") # True
expr.evaluate("!0")                  # True

# Keywords
expr.evaluate("true == true")    # True
expr.evaluate("null == null")    # True
```

### Strings

```python
expr = Expression()

# String literals (single or double quotes)
expr.evaluate('"hello" + " " + "world"')  # "hello world"
expr.evaluate("'foo' == 'foo'")            # True

# Ruby-style string operators
expr.evaluate('"ab" * 3')                  # "ababab"  (repeat)
expr.evaluate('"a" << "b"')                # "ab"      (append; mutates a variable)
expr.evaluate('"a" <=> "b"')               # -1        (lexicographic compare)
```

### Regular Expressions

```python
expr = Expression()

# Match operator
expr.evaluate('"foobar" =~ /([fo]+)/i')  # True
expr.evaluate("$1")                       # "Foo" (capture group)

expr.evaluate('"hello" =~ /([0-9]+)/')   # False
```

### JSON Literals

```python
expr = Expression()

# Objects and arrays
expr.evaluate('{"name": "Alice"}')              # {"name": "Alice"}
expr.evaluate('[10, 20, 30]')                    # [10, 20, 30]

# Property access
expr.evaluate('{"foo": "bar"}["foo"]')           # "bar"
expr.evaluate('[10, 20][0]')                     # 10

# Comparison
expr.evaluate('{"a": 1} == {"a": 1}')           # True
expr.evaluate('[1, 2] != [3, 4]')                # True
```

### Array Operators

Ruby-inspired operators for concise list manipulation. When one operand is an
array and the other is a scalar, the scalar is coerced to a single-element array.

```python
expr = Expression()

# Intersection (&) — common elements, deduped, left order preserved
expr.evaluate("[1, 1, 2, 3] & [3, 4]")     # [3]

# Union (|) — combined elements, deduped
expr.evaluate("[1, 2] | [2, 3]")           # [1, 2, 3]

# Difference (-) — left elements not in right
expr.evaluate("[1, 2, 2, 3] - [2]")        # [1, 3]

# Concatenation (+) — keeps duplicates
expr.evaluate("[1, 2] + [2, 3]")           # [1, 2, 2, 3]

# Append (<<) — mutates the left operand when it is a variable
expr.evaluate("[1, 2] << 3")               # [1, 2, 3]

# Multiplication (*) — repeat with an integer
expr.evaluate("[1, 2] * 3")                # [1, 2, 1, 2, 1, 2]
# Join (*) — with a string separator
expr.evaluate('["a", "b"] * "-"')          # "a-b"

# Deep equality (==) and spaceship (<=>)
expr.evaluate("[1, 2] == [1, 2]")          # True
expr.evaluate("[1, 2] <=> [1, 3]")         # -1

# Membership (in) — array element, or substring of a string
expr.evaluate("2 in [1, 2, 3]")            # True
expr.evaluate('"py" in "python"')          # True

# Scalar coercion
expr.evaluate("[1, 2, 3] & 2")             # [2]
expr.evaluate("1 + [2, 3]")                # [1, 2, 3]
```

When neither operand is an array, these operators fall back to scalar
semantics: `&` and `|` are bitwise AND/OR, `<<`/`>>` are bitshifts, `<=>`
compares numbers or strings, and `+`/`-`/`*` are arithmetic.

```python
expr.evaluate("6 & 3")                      # 2  (bitwise AND)
expr.evaluate("6 | 1")                      # 7  (bitwise OR)
expr.evaluate("5 <=> 3")                    # 1
```

Empty arrays are falsy (unlike JavaScript), so they work directly in boolean
contexts:

```python
expr.evaluate("!![]")                       # False
expr.evaluate('[] || "default"')            # "default"
expr.evaluate("[] ? 'yes' : 'no'")          # "no"

# Validator pattern: true only when at least one skill matches
expr.variables = {"skills": ["Python", "AI"]}
bool(expr.evaluate('skills & ["AI", "ML"]'))   # True
```

### Conditional (Ternary) Operator

```python
expr = Expression()

expr.evaluate("1 > 0 ? 'yes' : 'no'")       # "yes"
expr.evaluate("[] ? 'yes' : 'no'")          # "no"
```

### Error Handling

```python
expr = Expression()

# Errors raise exceptions by default
try:
    expr.evaluate("10 / 0")
except ZeroDivisionError:
    print("Division by zero!")

# Suppress errors (returns None on error)
expr.suppress_errors = True
expr.evaluate("unknown_var")     # None
print(expr.last_error)           # error message
```

### Semicolons

Trailing semicolons are optional and ignored:

```python
expr.evaluate("10 + 10;")        # 20
```

## Operator Precedence (lowest to highest)

| Precedence | Operators |
|-----------|-----------|
| 0 | `? :` (ternary) |
| 1 | `\|\|` |
| 2 | `&&` |
| 3 | `==`, `!=`, `===`, `!==`, `=~`, `<=>`, `>`, `<`, `>=`, `<=`, `in` |
| 4 | `<<`, `>>` |
| 5 | `+`, `-`, `\|` |
| 6 | `*`, `/`, `%`, `&`, `[]`, implicit multiplication |
| 7 | `!`, unary `-`, unary `+` |
| 8 | `**`, `^` |

Operators `&` (intersection), `\|` (union), `-` (difference), `+`
(concatenation), `<<` (append), `*` (repeat/join), `==` (deep equality), `<=>`
(spaceship), and `in` (membership) are type-dispatched: they apply array
semantics when either operand is an array, otherwise scalar semantics.

## Development

```bash
# Install in development mode
pip install -e ".[dev]"

# Run tests
pytest

# Regenerate parser from grammar
python -m pegen expression/grammar.peg -o expression/parser.py
```

## License

Copyright (c) 2026 [Jakub T. Jankiewicz](https://jakub.jankiewicz.org/)

This program is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

See [LICENSE](https://github.com/jcubic/expression.py/blob/master/LICENSE) for the full text.
