Elex (Elex v0.3.0)

View Source

Elex 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, and coalesce (two or more arguments) and concat (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 + − * /; use add_unit / remove_unit for magnitude arithmetic. Formula strings use | for division (m | s, km | h), not /. evaluate/3 accepts unit: and category:; validate/3 accepts category: 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.

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

add_variable(context, name, value, opts \\ [])

@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)

add_variable!(context, name, value, opts \\ [])

@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.

add_variables(context, variables_map)

@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})

add_variables!(context, variables_map)

@spec add_variables!(Elex.Context.t(), map()) :: Elex.Context.t()

Same as add_variables/2, but returns the context or raises ArgumentError.

evaluate(expression_string, context, opts \\ [])

@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 evaluate
  • context - A Elex.Context with variables and functions
  • opts - 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 as unit: "mm", but a dim mismatch from convert/2 is cannot convert length to mass while root unit: is expression should return a valid length result (the target's category). category: is the same compatibility check as validate/3 (for example category: :speed requires length | time). unit: or category: with no catalog raises ArgumentError. Unknown option keys raise ArgumentError.

Returns

  • {:ok, result} - The evaluated result (Decimal.t(), boolean(), String.t(), nil, or Elex.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.

extract_variables(expression_string, context)

@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 analyse
  • context - A Elex.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"]}

list_standard_function_modules()

@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, ...]

new_context(variables \\ %{})

@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 to Elex.Variable structs. 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}
})

validate(expression_string, context, opts \\ [])

@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 validate
  • context - A Elex.Context with variables and functions
  • opts - Optional keywords. category: checks that a unitful result matches that catalog category's formula (for example category: :speed requires length | time). On success, validate still returns the inferred dimension, not the category atom. category: with no catalog, or an unknown category, raises ArgumentError. Unknown option keys (including unit:) raise ArgumentError — convert with evaluate/3.

Returns

  • {:ok, type} - The expression's result type (:decimal, :boolean, :string, or Elex.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.