Skip to content

Commit e94c759

Browse files
authored
Prepares for release (#10)
* Adds codecov * Adds CHANGELOG and updates LICENSE * Adds history to CHANGELOG; Fixes flaky tests
1 parent a93efd7 commit e94c759

File tree

6 files changed

+217
-36
lines changed

6 files changed

+217
-36
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,9 @@ jobs:
2727
runs-on: ubuntu-latest
2828
strategy:
2929
matrix:
30-
elixir: ['1.14', '1.15', '1.16', '1.17']
31-
otp: ['25', '26', '27']
32-
exclude:
33-
# OTP 27 requires Elixir 1.17+
34-
- elixir: '1.14'
35-
otp: '27'
36-
- elixir: '1.15'
37-
otp: '27'
38-
- elixir: '1.16'
39-
otp: '27'
30+
elixir: ['1.17', '1.18']
31+
otp: ['26', '27']
32+
4033
steps:
4134
- name: Checkout code
4235
uses: actions/checkout@v4
@@ -125,13 +118,13 @@ jobs:
125118
- name: Run tests with coverage
126119
run: mix coveralls.json
127120

128-
- name: Upload coverage to Codecov
129-
if: matrix.elixir == '1.17' && matrix.otp == '27'
130-
uses: codecov/codecov-action@v3
121+
- name: Upload coverage reports to Codecov
122+
if: matrix.elixir == '1.18' && matrix.otp == '27'
123+
uses: codecov/codecov-action@v5
131124
with:
132-
files: ./cover/excoveralls.json
133-
fail_ci_if_error: false
134125
token: ${{ secrets.CODECOV_TOKEN }}
126+
slug: riddler/predicator-ex
127+
files: ./cover/excoveralls.json
135128

136129
credo:
137130
name: Static Analysis (Credo)

CHANGELOG.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [1.0.0] - 2025-08-XX
9+
10+
### Added
11+
12+
#### Core Language Features
13+
- **Comparison Operators**: Full support for `>`, `<`, `>=`, `<=`, `=`, `!=` with proper type handling
14+
- **Logical Operators**: Case-insensitive `AND`/`and`, `OR`/`or`, `NOT`/`not` with correct precedence
15+
- **Data Types**:
16+
- Numbers (integers): `42`, `-17`
17+
- Strings (double-quoted): `"hello"`, `"world"`
18+
- Booleans: `true`, `false`
19+
- Date literals: `#2024-01-15#` (ISO 8601 format)
20+
- DateTime literals: `#2024-01-15T10:30:00Z#` (ISO 8601 with timezone)
21+
- List literals: `[1, 2, 3]`, `["admin", "manager"]`
22+
- Identifiers: `score`, `user_name`, `is_active`
23+
24+
#### Advanced Operations
25+
- **Membership Operators**:
26+
- `in` for element-in-collection testing (`role in ["admin", "manager"]`)
27+
- `contains` for collection-contains-element testing (`[1, 2, 3] contains 2`)
28+
- **Parenthesized Expressions**: Full support with proper precedence handling
29+
- **Plain Boolean Expressions**: Support for bare identifiers (`active`, `expired`) without explicit `= true`
30+
31+
#### Function System
32+
- **Built-in System Functions**:
33+
- **String functions**: `len(string)`, `upper(string)`, `lower(string)`, `trim(string)`
34+
- **Numeric functions**: `abs(number)`, `max(a, b)`, `min(a, b)`
35+
- **Date functions**: `year(date)`, `month(date)`, `day(date)`
36+
- **Custom Function Registration**: Register anonymous functions with `Predicator.register_function/3`
37+
- **Function Registry**: ETS-based registry with automatic arity validation and error handling
38+
- **Context-Aware Functions**: Functions receive evaluation context for dynamic behavior
39+
40+
#### Architecture & Performance
41+
- **Multi-Stage Compilation Pipeline**: Expression → Lexer → Parser → Compiler → Instructions → Evaluator
42+
- **Compile-Once, Evaluate-Many**: Pre-compile expressions for repeated evaluation
43+
- **Stack-Based Evaluator**: Efficient instruction execution with minimal overhead
44+
- **Comprehensive Error Handling**: Detailed error messages with line/column positioning
45+
46+
#### Developer Experience
47+
- **String Decompilation**: Convert AST back to readable expressions with formatting options
48+
- **Multiple Evaluation APIs**:
49+
- `evaluate/2` - Returns `{:ok, result}` or `{:error, message}`
50+
- `evaluate!/2` - Returns result directly or raises exception
51+
- `compile/1` - Pre-compile expressions to instructions
52+
- `parse/1` - Parse expressions to AST for inspection
53+
- **Formatting Options**: Configurable spacing (`:normal`, `:compact`, `:verbose`) and parentheses (`:minimal`, `:explicit`, `:none`)
54+
55+
#### Code Organization
56+
- **Modular Architecture**: Clean separation of concerns across lexer, parser, compiler, evaluator
57+
- **Organized File Structure**:
58+
- `lib/predicator/functions/` - Function system components
59+
- `lib/predicator/visitors/` - AST transformation modules
60+
- **Comprehensive Testing**: 616 tests with 66 doctests, achieving >90% code coverage
61+
62+
### Technical Details
63+
64+
#### Grammar
65+
The language supports a complete expression grammar with proper operator precedence:
66+
```ebnf
67+
expression → logical_or
68+
logical_or → logical_and ( ("OR" | "or") logical_and )*
69+
logical_and → logical_not ( ("AND" | "and") logical_not )*
70+
logical_not → ("NOT" | "not") logical_not | comparison
71+
comparison → primary ( ( ">" | "<" | ">=" | "<=" | "=" | "!=" | "in" | "contains" ) primary )?
72+
primary → NUMBER | STRING | BOOLEAN | DATE | DATETIME | IDENTIFIER | list | function_call | "(" expression ")"
73+
function_call → IDENTIFIER "(" ( expression ( "," expression )* )? ")"
74+
list → "[" ( expression ( "," expression )* )? "]"
75+
```
76+
77+
#### Security
78+
- **No Dynamic Code Execution**: All expressions compiled to safe instruction sequences
79+
- **Input Validation**: Comprehensive validation at lexer and parser levels
80+
- **Type Safety**: Strong typing throughout compilation and evaluation pipeline
81+
- **Sandboxed Evaluation**: No access to system functions or arbitrary code execution
82+
83+
#### Performance
84+
- **Efficient Tokenization**: Single-pass lexer with position tracking
85+
- **Recursive Descent Parser**: Clean, maintainable parsing with excellent error recovery
86+
- **Optimized Instruction Set**: Minimal instruction overhead for fast evaluation
87+
- **Memory Efficient**: Low allocation during expression evaluation
88+
89+
### Dependencies
90+
- **Runtime**: Zero external dependencies for core functionality
91+
- **Development**: Credo, Dialyzer, ExCoveralls for code quality and testing
92+
- **Minimum Elixir**: ~> 1.11
93+
94+
### Breaking Changes
95+
96+
**⚠️ COMPLETE LIBRARY REWRITE ⚠️**
97+
98+
Version 1.0.0 is a **complete rewrite** of the Predicator library with entirely new:
99+
- API design and function signatures
100+
- Expression syntax and grammar
101+
- Internal architecture and data structures
102+
- Feature set and capabilities
103+
104+
### Migration Guide
105+
106+
**Migration from versions < 1.0.0 has NOT been tested and is NOT guaranteed to work.**
107+
108+
If you are upgrading from a pre-1.0.0 version:
109+
1. **Treat this as a new library adoption**, not an upgrade
110+
2. **Review all documentation** - APIs have completely changed
111+
3. **Test thoroughly** in development environments
112+
4. **Expect to rewrite** all integration code
113+
5. **Plan for significant refactoring** of existing expressions
114+
115+
Future 1.x.x versions will maintain backwards compatibility and include proper migration guides.
116+
117+
---
118+
119+
## Legacy Versions (Pre-1.0.0)
120+
121+
The following versions are part of the original Predicator implementation, which has been completely rewritten for 1.0.0:
122+
123+
## [0.9.2]
124+
125+
### Documentation
126+
- Adds additional information to README
127+
- Adds documentation to functions in Predicator
128+
129+
### Enhancements
130+
- Adds `compile!`, `evaluate`, `evaluate!`, `evaluate_instructions`, `evaluate_instructions!` functions to Predicator
131+
- Adds `Ecto.PredicatorInstructions` Ecto type
132+
133+
## [0.9.1]
134+
135+
### Documentation
136+
- Moves project from [predicator/predicator_elixir](https://github.com/predicator/predicator_elixir) to [riddler/predicator](https://github.com/riddler/predicator/tree/master/impl/ex)
137+
138+
## [0.9.0]
139+
140+
### Breaking Changes
141+
- Evaluates `compare` instead of `comparator` to be compatible with ruby predicator lib
142+
143+
## [0.8.1]
144+
145+
### Enhancements
146+
- Adds leex and parsing for `and` and `or`
147+
- Adds leex and parsing for `!` and boolean
148+
149+
## [0.8.0]
150+
151+
### Added
152+
- **Predicator.matches?/3** accepts evaluator options
153+
154+
### Enhancements
155+
- Adds leex and parsing for `isblank` and `ispresent`
156+
- Supports escaped double quote strings
157+
158+
### Fixed
159+
- `in` and `notin` accept list of strings
160+
161+
## [0.7.3]
162+
163+
### Enhancements
164+
- Adds leex and parsing for `in`, `notin`, `between`, `startswith`, `endswith` instructions
165+
166+
## [0.7.1]
167+
168+
### Added
169+
- Adds `between` instruction for eval on dates
170+
171+
## [0.7.0]
172+
173+
### Added
174+
- Adds 2 new comparison predicates for `starts_with` & `ends_with`
175+
176+
## [0.6.0]
177+
178+
### Added
179+
- Adds 3 new evaluatable predicates for `to_date`, `date_ago`, and `date_from_now`
180+
181+
## [0.5.0]
182+
183+
### Changed
184+
- Evaluator now reads new coercion instructions `to_int`, `to_str`, & `to_bool`
185+
186+
## [0.4.0]
187+
188+
### Added
189+
- Adds 4 new functions to the `Predicator` module: `eval/3`, `leex_string/1`, `parsed_lexed/1`, & `leex_and_parse/1`
190+
191+
## [0.3.0]
192+
193+
### Enhancements
194+
- Adds options to **Predicator.Evaluator.execute/3** as a keyword list to define if the context map is a string keyed list `[map_type: :string]` or atom keyed for the default `[map_type: :atom]`
195+
196+
---
197+
198+
For detailed information about upcoming features and development roadmap, see the project README.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2017 Joshua Richardson
1+
Copyright (c) 2025 John Thornton
22

33
Permission is hereby granted, free of charge, to any person obtaining
44
a copy of this software and associated documentation files (the

README.md

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
[![Hex.pm Version](https://img.shields.io/hexpm/v/predicator.svg)](https://hex.pm/packages/predicator)
66
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/predicator/)
77

8-
A secure, non-evaluative condition engine for processing end-user boolean predicates in Elixir. Predicator allows you to safely evaluate user-defined expressions without the security risks of dynamic code execution.
8+
A secure, non-evaluative condition engine for processing end-user boolean predicates in Elixir.
9+
Predicator allows you to safely evaluate user-defined expressions without the security risks of dynamic code execution.
910

1011
## Features
1112

@@ -159,9 +160,9 @@ iex> Predicator.decompile(ast)
159160
Predicator uses a multi-stage compilation pipeline:
160161

161162
```
162-
Expression String → Lexer → Parser → Compiler → Instructions → Evaluator
163-
164-
"score > 85 AND #2024-01-15# in dates" → Tokens → AST → Instructions → Result
163+
Expression String → Lexer → Parser → Compiler → Evaluator
164+
↓ ↓
165+
"score > 85 OR admin" → Tokens → AST → Instructions → Result
165166
```
166167

167168
### Grammar
@@ -254,21 +255,6 @@ iex> Predicator.decompile(ast, parentheses: :explicit)
254255
"(score > 85)"
255256
```
256257

257-
### Performance
258-
259-
For repeated evaluations with the same expression:
260-
261-
```elixir
262-
# Compile once
263-
{:ok, instructions} = Predicator.compile("score > threshold")
264-
265-
# Evaluate many times
266-
results =
267-
data_list
268-
|> Enum.map(&Predicator.evaluate(instructions, &1))
269-
|> Enum.map(fn {:ok, result} -> result end)
270-
```
271-
272258
## Development
273259

274260
### Setup

test/custom_functions_integration_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ defmodule CustomFunctionsIntegrationTest do
66
# CompanyFunctions module removed - using manual registration instead
77

88
setup do
9-
# Clear custom functions before each test
9+
# Clear custom functions before each test but preserve system functions
1010
clear_custom_functions()
1111
:ok
1212
end

test/test_helper.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1+
# Ensure the Predicator application is started before tests run
2+
# This ensures system functions are registered and available
3+
Application.ensure_all_started(:predicator)
4+
15
ExUnit.start()

0 commit comments

Comments
 (0)