Elex (Elex v0.3.0)
View SourceElex is an expression language library for parsing, validating, and evaluating expressions.
It supports:
- Arithmetic operations (
+,-,*,/,%) and unary minus - Comparison operators (
<,>,<=,>=,==,!=) for decimals, booleans, strings,null, and same-dimension quantities - Boolean operations (
and,or,not) with short-circuit evaluation - Literals: decimals, booleans (
true/false,yes/no), strings,null - Variables and built-in functions (
abs,add_unit,between,ceil,clamp,coalesce,concat,contains,convert,ends_with,floor,if,length,lower,match,max,min,mod,pi,pow,rem,remove_unit,round,sqrt,starts_with,trim,upper) - Variadic
min,max, andcoalesce(two or more arguments) andconcat(zero or more arguments) - Type checking and validation
- Optional caller-registered units (Elex ships no catalog); evaluate
returns
%Elex.Quantity{}when the result has a unit. Non-additive categories (additive: false) reject binary+ − * /; useadd_unit/remove_unitfor magnitude arithmetic. Formula strings use|for division (m | s,km | h), not/.evaluate/3acceptsunit:andcategory:;validate/3acceptscategory:only
Quick start
context =
Elex.new_context()
|> Elex.add_variable!("x", 10)
|> Elex.add_variable!("y", 5)
Elex.evaluate("x + y * 2", context)
#=> {:ok, #Decimal<20>}
Elex.validate("x > 0", context)
#=> {:ok, :boolean}
Elex.extract_variables("x + y", context)
#=> {:ok, ["x", "y"]}Guides
See Elex.Parser for parsing, Elex.Evaluator for
direct AST evaluation, and Elex.Context for custom variables and
functions.
Summary
Functions
Add a single variable to a context.
Same as add_variable/3, but returns the context or raises ArgumentError.
Add multiple variables to a context at once.
Same as add_variables/2, but returns the context or raises ArgumentError.
Parses, validates, and evaluates an expression string.
Extracts variable names referenced in an expression string.
Returns the list of built-in function modules registered by new_context/0.
Creates a new evaluation context with standard functions and optional variables.
Parses and validates an expression string without evaluating it.
Functions
@spec add_variable(Elex.Context.t(), String.t(), any(), keyword()) :: {:ok, Elex.Context.t()} | {:error, String.t()}
Add a single variable to a context.
Always returns {:ok, context} or {:error, reason}. A {number, unit}
or %Elex.Quantity{} value must be paired with category:. Use
add_variable!/3 when piping.
Examples
{:ok, context} = add_variable(new_context(), "setting_a", 42.5)
context = new_context() |> add_variable!("setting_a", 42.5)
{:ok, context} =
add_variable(context, "width", {10, "cm"}, category: :length)
{:ok, context} =
add_variable(context, "width", %Elex.Quantity{value: Decimal.new("10"), unit: "cm"},
category: :length)
@spec add_variable!(Elex.Context.t(), String.t(), any(), keyword()) :: Elex.Context.t()
Same as add_variable/3, but returns the context or raises ArgumentError.
@spec add_variables(Elex.Context.t(), map()) :: {:ok, Elex.Context.t()} | {:error, String.t()}
Add multiple variables to a context at once.
Returns {:ok, context} or {:error, reason}. Unitful {number, unit}
or %Elex.Quantity{} values are rejected here — they require
category: on add_variable/4. On error, later entries are not applied.
Use add_variables!/2 when piping.
Examples
{:ok, context} =
add_variables(new_context(), %{"setting_a" => 10, "setting_b" => 20})
context =
new_context()
|> add_variables!(%{"setting_a" => 10, "setting_b" => 20})
@spec add_variables!(Elex.Context.t(), map()) :: Elex.Context.t()
Same as add_variables/2, but returns the context or raises ArgumentError.
@spec evaluate(String.t(), Elex.Context.t(), keyword()) :: {:ok, Decimal.t() | boolean() | String.t() | nil | Elex.Quantity.t()} | {:error, String.t()}
Parses, validates, and evaluates an expression string.
Returns {:ok, result} on success or {:error, reason} on parse, validation, or
evaluation failure (including arithmetic errors such as division by zero).
Parameters
expression_string- The expression to evaluatecontext- AElex.Contextwith variables and functionsopts- Optional keywords.unit:converts a quantity result into a registered symbol or a formula over registered symbols (for example"mm","km | h", or"m | s^2"). You do not need to register the formula as its own unit. Inside an expression,convert(value, "mm")is the same conversion asunit: "mm", but a dim mismatch fromconvert/2iscannot convert length to masswhile rootunit:isexpression should return a valid length result(the target's category).category:is the same compatibility check asvalidate/3(for examplecategory: :speedrequireslength | time).unit:orcategory:with no catalog raisesArgumentError. Unknown option keys raiseArgumentError.
Returns
{:ok, result}- The evaluated result (Decimal.t(),boolean(),String.t(),nil, orElex.Quantity.t()){:error, reason}- A human-readable error message
Examples
context = Elex.new_context() |> Elex.add_variable!("x", 10)
Elex.evaluate("x + 5", context)
#=> {:ok, #Decimal<15>}
# Quantity results and `unit:` need a catalog on the context
# (`unit:` raises without one). See the Units guide.
@spec extract_variables(String.t(), Elex.Context.t()) :: {:ok, [String.t()]} | {:error, String.t()}
Extracts variable names referenced in an expression string.
Parsing uses context (including a units catalog when attached) without
validation, so variables need not exist.
Parameters
expression_string- The expression to analysecontext- AElex.Context(catalog, functions)
Returns
{:ok, names}- A deduplicated list of variable name strings{:error, reason}- A parse error message
Examples
Elex.extract_variables("x + y * 2", Elex.new_context())
#=> {:ok, ["x", "y"]}
@spec list_standard_function_modules() :: [module()]
Returns the list of built-in function modules registered by new_context/0.
Use this to distinguish standard functions from custom ones without relying on module-name heuristics.
Examples
Elex.list_standard_function_modules()
#=> [Elex.Functions.Abs, Elex.Functions.AddUnit, ...]
@spec new_context(%{optional(String.t()) => Elex.Variable.t()}) :: Elex.Context.t()
Creates a new evaluation context with standard functions and optional variables.
Parameters
variables- Map of variable names toElex.Variablestructs. Defaults to an empty map.
Returns
A Elex.Context struct ready for parsing and evaluation.
Examples
Elex.new_context()
Elex.new_context(%{
"x" => %Elex.Variable{value: Decimal.new(1), type: :decimal}
})
@spec validate(String.t(), Elex.Context.t(), keyword()) :: {:ok, atom() | Elex.Dimension.t()} | {:error, String.t()}
Parses and validates an expression string without evaluating it.
Parameters
expression_string- The expression to validatecontext- AElex.Contextwith variables and functionsopts- Optional keywords.category:checks that a unitful result matches that catalog category's formula (for examplecategory: :speedrequireslength | time). On success, validate still returns the inferred dimension, not the category atom.category:with no catalog, or an unknown category, raisesArgumentError. Unknown option keys (includingunit:) raiseArgumentError— convert withevaluate/3.
Returns
{:ok, type}- The expression's result type (:decimal,:boolean,:string, orElex.Dimension.t()for unitful results){:error, reason}- A human-readable error message
Examples
context = Elex.new_context() |> Elex.add_variable!("x", 10)
Elex.validate("x > 0", context)
#=> {:ok, :boolean}
# Unitful results and `category:` need a catalog on the context
# (`category:` raises without one). See the Units guide.