Python Runtime
Package: xpr-lang · GitHub · v0.5.0
Install
bash
pip install xpr-langRequires Python 3.10+. Zero runtime dependencies.
API
Xpr()
Creates a new XPR engine instance.
python
from xpr import Xpr
engine = Xpr()engine.evaluate(expression, context=None)
Evaluates an XPR expression string against an optional context dict. Returns the result value.
Raises XprError if the expression is invalid or a runtime error occurs.
python
engine.evaluate('1 + 2')
# → 3
engine.evaluate('user["name"] ?? "anonymous"', {'user': {'name': None}})
# → "anonymous"
engine.evaluate('[1,2,3].map(x => x * 2)')
# → [2, 4, 6]
engine.evaluate(
'items.filter(x => x.active).map(x => x.name)',
{'items': [{'name': 'a', 'active': True}, {'name': 'b', 'active': False}]}
)
# → ['a']engine.add_function(name, fn)
Registers a custom function that can be called from expressions.
python
engine.add_function('slugify', lambda s: str(s).lower().replace(' ', '-'))
engine.evaluate('slugify(product.name)', {'product': {'name': 'Hello World'}})
# → 'hello-world'XprError
Raised on parse errors and runtime errors. Has a position attribute (character offset).
python
from xpr import Xpr, XprError
try:
engine.evaluate('1 / 0')
except XprError as e:
print(e) # "Division by zero"
print(e.position) # character offsetType Notes
- Numbers are always
floatinternally (e.g.1 + 2returns3.0) nullin XPR maps toNonein Python- Arrays map to
list, objects map todict - Arrow functions become Python callables
v0.2 Features
Let Bindings
python
engine.evaluate('let x = 1; let y = x + 1; y')
# → 2.0
engine.evaluate('let f = (x) => x * 2; f(5)')
# → 10.0
engine.evaluate('let items = [1,2,3,4,5]; items.filter(x => x > 2).map(x => x * 10)')
# → [30.0, 40.0, 50.0]Spread Operator
python
engine.evaluate('[...[1,2], ...[3,4]]')
# → [1.0, 2.0, 3.0, 4.0]
engine.evaluate('{...defaults, ...overrides}', {
'defaults': {'color': 'blue', 'size': 10},
'overrides': {'color': 'red'},
})
# → {'color': 'red', 'size': 10.0}New Array Methods
python
engine.evaluate('[1,2,3].includes(2)') # → True
engine.evaluate('[1,2,3].indexOf(2)') # → 1.0
engine.evaluate('[1,2,3,4,5].slice(1, 3)') # → [2.0, 3.0]
engine.evaluate('[1,2,3].join(", ")') # → "1, 2, 3"
engine.evaluate('[1,2].concat([3,4])') # → [1.0, 2.0, 3.0, 4.0]
engine.evaluate('[[1,2],[3,4]].flat()') # → [1.0, 2.0, 3.0, 4.0]
engine.evaluate('[1,2,1,3].unique()') # → [1.0, 2.0, 3.0]
engine.evaluate('[1,2,3].zip([4,5,6])') # → [[1.0,4.0],[2.0,5.0],[3.0,6.0]]
engine.evaluate('[1,2,3,4,5].chunk(2)') # → [[1.0,2.0],[3.0,4.0],[5.0]]
engine.evaluate('[1,2,3].groupBy(x => x > 1 ? "big" : "small")')
# → {'big': [2.0, 3.0], 'small': [1.0]}New String Methods
python
engine.evaluate('"hello".indexOf("ll")') # → 2.0
engine.evaluate('"ab".repeat(3)') # → "ababab"
engine.evaluate('" hi ".trimStart()') # → "hi "
engine.evaluate('" hi ".trimEnd()') # → " hi"
engine.evaluate('"hello".charAt(1)') # → "e"
engine.evaluate('"42".padStart(5, "0")') # → "00042"
engine.evaluate('"hi".padEnd(5, ".")') # → "hi..."New Object Methods
python
engine.evaluate('{"b": 2, "a": 1}.entries()') # → [["a", 1.0], ["b", 2.0]]
engine.evaluate('{"a": 1}.has("a")') # → True
engine.evaluate('{"a": 1}.has("b")') # → Falserange() Function
python
engine.evaluate('range(5)') # → [0.0, 1.0, 2.0, 3.0, 4.0]
engine.evaluate('range(1, 5)') # → [1.0, 2.0, 3.0, 4.0]
engine.evaluate('range(0, 10, 2)') # → [0.0, 2.0, 4.0, 6.0, 8.0]
engine.evaluate('range(5, 0, -1)') # → [5.0, 4.0, 3.0, 2.0, 1.0]v0.3 Features
Date/Time
Dates are epoch milliseconds (UTC only). Numbers return as float.
python
engine.evaluate('formatDate(now(), "yyyy-MM-dd")')
# → "2026-03-15"
engine.evaluate('dateDiff(parseDate("2024-01-01T00:00:00Z"), now(), "days")')
# → 439.0
engine.evaluate('dateAdd(parseDate("2024-01-31T00:00:00Z"), 1, "months")')
# → 1709337600000.0Regex Functions
python
engine.evaluate('matches("hello 42", "\\\\d+")') # → True
engine.evaluate('match("order-123", "\\\\d+")') # → "123"
engine.evaluate('matchAll("a1b2c3", "\\\\d")') # → ["1", "2", "3"]
engine.evaluate('replacePattern("2024-01-15","(\\\\d{4})-(\\\\d{2})-(\\\\d{2})","$3/$2/$1")')
# → "15/01/2024"Negative Indexing and Spread in Calls
python
engine.evaluate('[1,2,3][-1]') # → 3.0
engine.evaluate('max(...[1, 5, 3, 2])') # → 5.0v0.4 Features
Destructuring
python
engine.evaluate('let {name, age} = user; name', {'user': {'name': 'Alice', 'age': 30}})
# → 'Alice'
engine.evaluate('users.map(({name, age}) => `${name}: ${age}`)',
{'users': [{'name': 'Alice', 'age': 30}]})
# → ['Alice: 30']
engine.evaluate('let [head, ...tail] = items; tail', {'items': [1, 2, 3]})
# → [2.0, 3.0]Regex Literals
python
engine.evaluate('/\\\\d+/.test("order-123")') # → True
engine.evaluate('"2024-01-15".match(/\\\\d{4}/)') # → "2024"
engine.evaluate('"hello world".replace(/o/, "0")') # → "hell0 w0rld"v0.5 Features
Math Functions and Constants
python
engine.evaluate('sqrt(16)') # → 4.0
engine.evaluate('log(E)') # → 1.0
engine.evaluate('pow(2, 10)') # → 1024.0
engine.evaluate('PI * pow(5, 2)') # → 78.53981633974483
engine.evaluate('sign(-7)') # → -1.0
engine.evaluate('trunc(3.9)') # → 3.0Type Predicates
python
engine.evaluate('isNumber(42)') # → True
engine.evaluate('isString("x")') # → True
engine.evaluate('isArray([1,2])') # → True
engine.evaluate('isObject([1,2])') # → False (arrays are "array" type)
engine.evaluate('isNull(null)') # → TrueNew Array Methods
python
engine.evaluate('[3,null,1,null,5].compact().sortBy(x => x)') # → [1.0, 3.0, 5.0]
engine.evaluate('[1,2,3,4,5].take(3)') # → [1.0, 2.0, 3.0]
engine.evaluate('[1,2,3,4].sum()') # → 10.0
engine.evaluate('[1,2,3,4].avg()') # → 2.5
engine.evaluate('[1,2,3,4].partition(x => x > 2)') # → [[3.0, 4.0], [1.0, 2.0]]
engine.evaluate('[3,1,2].first()') # → 3.0
engine.evaluate('[1,2,3].count(x => x > 1)') # → 2.0fromEntries, str.split(/regex/), Rest Parameters
python
engine.evaluate('fromEntries([["a", 1], ["b", 2]])') # → {'a': 1.0, 'b': 2.0}
engine.evaluate('"a1b2c3".split(/\\\\d+/)') # → ['a', 'b', 'c']