Packages
predicator
4.0.0
9.4.2
9.4.1
9.4.0
9.3.0
9.2.0
9.1.0
9.0.2
9.0.1
9.0.0
8.0.0
7.0.0
6.0.0
5.0.0
4.0.0
3.8.0
3.7.0
3.6.0
3.5.0
3.4.0
3.3.0
3.2.0
3.1.0
3.0.0
2.2.0
2.1.0
2.0.0
1.1.0
1.0.1
1.0.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.1
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.5.1
0.5.0
0.4.0
0.3.0
0.2.1
0.2.0
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A secure, non-evaling condition (boolean predicate) engine for end users
Current section
Files
Jump to
Current section
Files
predicator
README.md
README.md
# Predicator
[](https://github.com/riddler/predicator-ex/actions/workflows/ci.yml)
[](https://codecov.io/gh/riddler/predicator-ex)
[](https://hex.pm/packages/predicator)
[](https://hex.pm/packages/predicator)
[](https://hexdocs.pm/predicator/)
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](docs/guides/nested-data-access.md),
and both [builtin and custom functions](docs/reference/language.md).
## Installation
Add `predicator` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:predicator, "~> 4.0"}
]
end
```
Predicator requires Elixir 1.18 or later and has no runtime dependencies.
## Quick Start
```elixir
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](docs/guides/embedding.md) for the full
store/check/run lifecycle, including what to do when a stored artifact
predates a retired opcode.
## Documentation
- [Language reference](docs/reference/language.md) - operators, builtin
functions, data types, and error shapes
- [ISA reference](docs/isa.md) - the instruction set specification: opcodes,
stack effects, error semantics, and versioning
- [Nested data access](docs/guides/nested-data-access.md) - dot and bracket
notation over deep contexts
- [Custom functions](docs/guides/custom-functions.md) - extending the function
set per evaluation
- [Location expressions](docs/guides/location-expressions.md) - SCXML
assignment targets and writing into a context
- [Embedding compiled programs](docs/guides/embedding.md) - storing an
instruction list and checking its ISA version before running it
- [Porting Predicator](docs/guides/porting.md) - implementing the instruction
set in another language and verifying it against the conformance corpus
- [Architecture and language reference](docs/architecture.md) - the grammar
with precedence, the compilation pipeline, and the component map
- [Architecture decision records](docs/adr/README.md) - the reasoning behind
the design
## 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](docs/adr/0002-the-equals-grammar-break.md) 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](https://github.com/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](docs/adr/0003-the-elixir-implementation-leads-the-isa.md) for the
reasoning and [docs/architecture.md](docs/architecture.md) for what each
sibling currently supports. [docs/isa.md](docs/isa.md) is the specification a
sibling implements against, and [conformance/](conformance/README.md) 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](docs/guides/porting.md)
walks the path from picking a version to recording a conformance claim.
## Development
See `CLAUDE.md` for the contributor workflow and
[docs/contributing.md](docs/contributing.md) for the quality-check commands
and the checklists for adding operators and data types.
## License
MIT - see [LICENSE](LICENSE).