Skip to content

Repository files navigation

Predicator

CI codecov Hex.pm Version Hex Downloads Hex Docs

Predicator is a secure, non-evaluative condition engine for end-user boolean predicates. A user-authored expression like score > 85 AND active compiles to a flat instruction list run by a small stack VM - there is no eval, no Code.eval_string, and no dynamic code execution anywhere in the pipeline, so untrusted input can never become code.

The language covers comparisons, arithmetic, logical operators, dates and durations, lists and objects, nested data access, and both builtin and custom functions.

Installation

Add predicator to your list of dependencies in mix.exs:

def deps do
  [
    {:predicator, "~> 3.8"}
  ]
end

Predicator requires Elixir 1.18 or later and has no runtime dependencies.

Quick Start

iex> Predicator.evaluate!("score > 85 AND active", %{"score" => 92, "active" => true})
true

iex> {:ok, instructions} = Predicator.compile("score > threshold")
iex> Predicator.evaluate!(instructions, %{"score" => 95, "threshold" => 80})
true

iex> Predicator.evaluate("score > 85", %{"score" => 92})
{:ok, true}

iex> {:ok, compiled} = Predicator.compile_with_positions("score > threshold")
iex> Predicator.evaluate(compiled, %{"score" => 95, "threshold" => 80})
{:ok, true}

compile_with_positions/1 and compile_with_spans/1 return a %Predicator.Compiled{} carrying the instructions and their source-location table as one value, so runtime errors keep their positions without the caller re-attaching anything.

Persist compiled.instructions, not the struct - the instruction list is the portable artifact; the table holds offsets into the source string and is meaningless without it. Want positions back after a round trip? Persist the source too and recompile with compile_with_positions/1 on load, rather than storing the table - a table compiled from one source silently mismatches a different source's instructions. See Embedding compiled programs for the full store/check/run lifecycle, including what to do when a stored artifact predates a retired opcode.

Documentation

Migrating from =

= is no longer an equality operator. It is assignment, valid only at the start of a statement (Predicator.parse_program/2) and only with an assignable left side; a bare = in expression position - through Predicator.parse/2 or Predicator.evaluate/3 - is a parse error naming == as the fix, never a silent reinterpretation. == and === are the only equality operators. See ADR-0002 for the reasoning.

Cross-Language Siblings

Predicator's Elixir implementation is the reference implementation of the instruction set (the ISA), which is versioned. Ruby and JavaScript siblings, in the riddler/predicator monorepo, adopt each ISA version on their own schedule; a sibling running behind the current version is an expected, documented state, not a defect. See ADR-0003 for the reasoning and docs/architecture.md for what each sibling currently supports. docs/isa.md is the specification a sibling implements against, and conformance/ is how a sibling verifies a claim of support against it: a checked-in, language-neutral JSON corpus, tiered so a v1-only implementation runs a smaller, complete slice rather than skipping its way through the whole thing. This is the versioned-contract framing ADR-0003 asks for - a sibling behind the current ISA version is expected and documented, not a parity deficit to apologize for. If you are that implementer, Porting Predicator walks the path from picking a version to recording a conformance claim.

Development

See CLAUDE.md for the contributor workflow and docs/contributing.md for the quality-check commands and the checklists for adding operators and data types.

License

MIT - see LICENSE.

About

Predicator in elixir

Resources

Code of conduct

Contributing

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages