Changelog

View Source

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[2.2.0] - 2025-08-24

Added

Bracket Access and Property Access Enhancement

  • Complete Bracket Notation Support: Implemented full bracket access functionality (obj['key'], arr[0], obj[variable])
  • Parser Extensions: Added postfix parsing for bracket access with recursive chaining support
  • Grammar Enhancement: Updated grammar with postfix operations: unary → postfix, postfix → primary ( "[" expression "]" )*
  • New AST Node Type: Added {:bracket_access, object, key} AST node for bracket access expressions
  • Evaluator Support: Implemented ["bracket_access"] instruction with comprehensive evaluation logic
  • Mixed Access Patterns: Full support for chained access like data['users'][0]['name']
  • Array Indexing: Complete array access with bounds checking (items[0], scores[index])
  • Dynamic Key Access: Support for variable and expression-based keys (obj[key], items[i + 1])
  • Type Safety: Comprehensive error handling for invalid key types with structured error messages
  • String Visitor Support: Added round-trip string conversion for bracket access expressions
  • Comprehensive Testing: Added 12 new parser tests covering all bracket access scenarios

Property Access Implementation Details

  • Access Value Logic: Robust access_value/2 function supporting:
    • Map access with string, atom, and integer keys
    • Array access with integer indices and bounds checking
    • Graceful fallback to :undefined for missing keys or out-of-bounds access
    • Type coercion between string and atom keys
  • Error Handling: Enhanced error system with bracket access specific errors:
    • Invalid key type validation with structured error messages
    • Proper operation display names ("Bracket access requires a string, integer, or atom key")
    • Integration with existing error architecture
  • Instruction Compilation: Added visit({:bracket_access, object, key}, opts) in InstructionsVisitor
  • Round-trip Support: Perfect string conversion preserving bracket notation syntax
  • Integration Testing: Verified compatibility with existing arithmetic, comparison, and logical operations

Error Handling Architecture Refactoring

  • Modular Error Structure: Refactored monolithic error handling into individual error modules under lib/predicator/errors/
  • Shared Error Utilities: Created Predicator.Errors module with common utility functions for consistent error formatting
  • Individual Error Modules: Split error handling into focused modules:
  • Consistent Error Messages: Unified error message formatting across all error types
  • Code Quality Improvements: Resolved all credo issues with proper module aliasing and organization

Error System Enhancements

  • Shared Utility Functions: Common functions in Predicator.Errors module:
    • expected_type_name/1 - Formats type names with proper articles ("an integer", "a boolean")
    • type_name_with_value/2 - Formats values with type information for error messages
    • operation_display_name/1 - User-friendly operation names ("Arithmetic add", "Logical AND")
  • Factory Functions: Each error module provides convenient factory functions for creating structured errors
  • Direct Error Returns: Eliminated intermediate RuntimeError module for better performance and cleaner architecture
  • Enhanced Type Safety: Comprehensive @type specifications and @spec annotations for all error functions

Technical Implementation Details

  • File Organization: Organized error modules under lib/predicator/errors/ directory structure
  • Module Aliasing: Added proper module aliases in evaluator for cleaner code (alias Predicator.Errors.{EvaluationError, TypeMismatchError})
  • Error Message Consistency: Standardized operation display names across all error types
  • Runtime Performance: Direct error struct returns eliminate conversion overhead
  • Maintainability: Focused error modules with single responsibilities and clear interfaces
  • Test Coverage: Maintained 100% test coverage (847 tests passing) throughout refactoring
  • Documentation: Comprehensive @moduledoc and examples for all error modules

[2.1.0] - 2025-08-24

Added

SCXML Enhancement Phases 1.2-1.4: Arithmetic and Logical Operators

  • Complete Parser Pipeline: Added full parsing support for arithmetic operators (+, -, *, /, %)
  • Enhanced Logical Operators: Added && (logical AND), || (logical OR), ! (logical NOT) with complete parsing
  • Equality Operator: Added == for strict equality comparison with proper precedence handling
  • Grammar Extensions: Implemented proper operator precedence hierarchy in recursive descent parser
  • AST Node Types: Added new AST node types for arithmetic, equality, and unary expressions
  • Visitor Support: Updated InstructionsVisitor and StringVisitor to handle new node types
  • Round-trip Compatibility: Full string ↔ AST ↔ string conversion for all new operators

Operator Support Details

# Arithmetic operators (FULLY IMPLEMENTED - parsing and evaluation complete)
2 + 3       # Addition - evaluates to 5
5 - 2       # Subtraction - evaluates to 3  
3 * 4       # Multiplication - evaluates to 12
8 / 2       # Division - evaluates to 4 (integer division)
7 % 3       # Modulo - evaluates to 1
-5          # Unary minus - evaluates to -5

# Logical operators (fully functional)
true && false   # Logical AND - works completely
true || false   # Logical OR - works completely
!active         # Logical NOT - works completely

# Equality operator (fully functional)  
x == y          # Strict equality - works completely

Arithmetic Evaluation Implementation

  • Complete Pipeline: Full arithmetic evaluation now implemented in stack machine evaluator
  • Instruction Handlers: Added execution support for ["add"], ["subtract"], ["multiply"], ["divide"], ["modulo"] instructions
  • Unary Operations: Implemented ["unary_minus"] and ["unary_bang"] instruction evaluation
  • Error Handling: Comprehensive type checking and division-by-zero protection
  • Pattern Matching: Idiomatic Elixir implementation using pattern matching for each operation
  • Integration Testing: Full pipeline testing from expression strings to computed results

Foundation for SCXML Value Expressions

  • Complete Implementation: Finished lexer, parser, AST, and evaluation phases (1.2-1.4) of SCXML datamodel support
  • Full Expression Support: Arithmetic expressions now work end-to-end from parsing to evaluation
  • Production Ready: Complete arithmetic expression evaluation ready for SCXML integration
  • Backward Compatibility: All existing functionality remains unchanged

Technical Implementation Details

  • File Organization: Organized error modules under lib/predicator/errors/ directory structure
  • Module Aliasing: Added proper module aliases in evaluator for cleaner code (alias Predicator.Errors.{EvaluationError, TypeMismatchError})
  • Error Message Consistency: Standardized operation display names across all error types
  • Runtime Performance: Direct error struct returns eliminate conversion overhead
  • Maintainability: Focused error modules with single responsibilities and clear interfaces
  • Test Coverage: Maintained 100% test coverage (847 tests passing) throughout refactoring
  • Documentation: Comprehensive @moduledoc and examples for all error modules

[2.1.0] - 2025-08-24

Added

SCXML Enhancement Phases 1.2-1.4: Arithmetic and Logical Operators

  • Complete Parser Pipeline: Added full parsing support for arithmetic operators (+, -, *, /, %)
  • Enhanced Logical Operators: Added && (logical AND), || (logical OR), ! (logical NOT) with complete parsing
  • Equality Operator: Added == for strict equality comparison with proper precedence handling
  • Grammar Extensions: Implemented proper operator precedence hierarchy in recursive descent parser
  • AST Node Types: Added new AST node types for arithmetic, equality, and unary expressions
  • Visitor Support: Updated InstructionsVisitor and StringVisitor to handle new node types
  • Round-trip Compatibility: Full string ↔ AST ↔ string conversion for all new operators

Operator Support Details

# Arithmetic operators (FULLY IMPLEMENTED - parsing and evaluation complete)
2 + 3       # Addition - evaluates to 5
5 - 2       # Subtraction - evaluates to 3  
3 * 4       # Multiplication - evaluates to 12
8 / 2       # Division - evaluates to 4 (integer division)
7 % 3       # Modulo - evaluates to 1
-5          # Unary minus - evaluates to -5

# Logical operators (fully functional)
true && false   # Logical AND - works completely
true || false   # Logical OR - works completely
!active         # Logical NOT - works completely

# Equality operator (fully functional)  
x == y          # Strict equality - works completely

Arithmetic Evaluation Implementation

  • Complete Pipeline: Full arithmetic evaluation now implemented in stack machine evaluator
  • Instruction Handlers: Added execution support for ["add"], ["subtract"], ["multiply"], ["divide"], ["modulo"] instructions
  • Unary Operations: Implemented ["unary_minus"] and ["unary_bang"] instruction evaluation
  • Error Handling: Comprehensive type checking and division-by-zero protection
  • Pattern Matching: Idiomatic Elixir implementation using pattern matching for each operation
  • Integration Testing: Full pipeline testing from expression strings to computed results

Foundation for SCXML Value Expressions

  • Complete Implementation: Finished lexer, parser, AST, and evaluation phases (1.2-1.4) of SCXML datamodel support
  • Full Expression Support: Arithmetic expressions now work end-to-end from parsing to evaluation
  • Production Ready: Complete arithmetic expression evaluation ready for SCXML integration
  • Backward Compatibility: All existing functionality remains unchanged

Technical Implementation

  • Lexer Enhancement: Extended tokenization with 9 new token types
  • Parser Grammar: Implemented arithmetic precedence hierarchy (unary → multiplication → addition → equality → comparison)
  • AST Extensions: Added 4 new AST node types (:arithmetic, :equality, :unary, plus enhanced visitor support)
  • Instruction Generation: Arithmetic expressions compile to proper stack machine instructions
  • Evaluator Enhancement: Added 7 new instruction handlers with pattern matching for type safety
  • Error Recovery: Comprehensive error messages for parsing and evaluation phases
  • Test Coverage: 847 tests passing (92.6% coverage) with comprehensive arithmetic evaluation testing
  • Code Quality: Idiomatic Elixir pattern matching implementation, all quality checks passing

[2.0.0] - 2025-08-21

Changed

Custom Function Architecture Overhaul

  • Breaking Change: Removed global function registry system in favor of evaluation-time function parameters
  • New API: Custom functions now passed via functions: option in Predicator.evaluate/3 calls
  • Function Format: Custom functions use %{name => {arity, function}} format where function takes [args], context and returns {:ok, result} or {:error, message}
  • Thread Safety: Eliminated global state for improved concurrency and thread safety
  • Function Merging: SystemFunctions always available with custom functions merged in, allowing overrides
  • Simplified Startup: No application-level function registry initialization required

Examples

# Old registry-based approach (removed)
Predicator.register_function("double", 1, fn [n], _context -> {:ok, n * 2} end)
Predicator.evaluate("double(21)", %{})

# New evaluation-time approach
custom_functions = %{"double" => {1, fn [n], _context -> {:ok, n * 2} end}}
Predicator.evaluate("double(21)", %{}, functions: custom_functions)

# Custom functions can override built-ins
custom_len = %{"len" => {1, fn [_], _context -> {:ok, "custom_result"} end}}
Predicator.evaluate("len('anything')", %{}, functions: custom_len)  # {:ok, "custom_result"}

Removed APIs

  • Predicator.register_function/3 - Use functions: option instead
  • Predicator.clear_custom_functions/0 - No longer needed
  • Predicator.list_custom_functions/0 - No longer needed
  • Predicator.Functions.Registry module - Entire registry system removed

Migration Guide

  1. Replace registry calls: Convert register_function calls to function maps passed to evaluate/3
  2. Update function definitions: Ensure functions return {:ok, result} or {:error, message}
  3. Remove initialization code: Delete any registry setup from application startup
  4. Update tests: Replace registry-based setup with evaluation-time function passing

Technical Implementation

  • Evaluator Enhancement: Modified to accept :functions option and merge with system functions
  • SystemFunctions Refactor: Added all_functions/0 to provide system functions in evaluator format
  • Clean Architecture: Removed ETS-based global registry and associated complexity
  • Backward Compatibility: evaluate/2 functions continue to work unchanged for expressions without custom functions

Security

  • Improved Isolation: Custom functions scoped to individual evaluation calls
  • No Global State: Eliminates potential race conditions and global state mutations

Performance

  • Reduced Overhead: No ETS lookups or global registry management
  • Better Concurrency: Thread-safe by design with no shared state

[1.1.0] - 2025-08-20

Added

Nested Data Structure Access

  • Dot Notation Support: Access deeply nested data structures using dot notation syntax
  • Enhanced Lexer: Extended identifier tokenization to include dots (.) as valid characters
  • Recursive Context Loading: Added load_nested_value/2 function for traversing nested maps
  • Mixed Key Type Support: Works seamlessly with string keys, atom keys, or mixed key types
  • Graceful Error Handling: Returns :undefined for missing paths or non-map intermediate values
  • Unlimited Nesting Depth: Support for arbitrarily deep nested structures

Single Quote String Support

  • Dual Quote Types: Added support for single-quoted strings ('hello') alongside double-quoted strings ("hello")
  • Quote Type Preservation: Round-trip parsing and decompilation preserves original quote type
  • Enhanced Lexer: Extended string tokenization to handle both quote types with proper escaping
  • AST Enhancement: New {:string_literal, value, quote_type} AST node for quote-aware string handling
  • Escape Sequences: Full escape sequence support in both quote types (\', \", \n, \t, etc.)

Examples

# Basic nested access
context = %{"user" => %{"name" => %{"first" => "John"}}}
Predicator.evaluate("user.name.first = \"John\"", context)  # {:ok, true}

# Complex expressions with nested access  
Predicator.evaluate("user.profile.age > 18 AND config.enabled", context)

# Mixed key types
mixed_context = %{"user" => %{profile: %{"active" => true}}}
Predicator.evaluate("user.profile.active", mixed_context)  # {:ok, true}

# Single quoted strings with nested access
Predicator.evaluate("user.name.first = 'John'", context)  # {:ok, true}

# Quote type preservation in round-trip
{:ok, ast} = Predicator.parse("user.role = 'admin'")
Predicator.decompile(ast)  # "user.role = 'admin'"

Technical Implementation

  • Lexer Enhancement: Modified take_identifier/3 to include dots in valid identifier characters
  • Evaluator Enhancement: Enhanced load_from_context/2 with nested path detection and delegation
  • Backwards Compatibility: Simple variable names continue to work exactly as before
  • Comprehensive Testing: Added 100+ new tests covering nested access scenarios

Breaking Changes

  • Dotted Variable Names: Variables containing dots (e.g., "user.email") are now parsed as nested access paths rather than literal key names
  • Flat Key Behavior: Context keys like "user.profile.name" will no longer match the identifier user.profile.name - use proper nested structures instead

Security

  • No security implications - nested access maintains the same safe evaluation model
  • All nested paths are validated and type-checked during traversal

Performance

  • Minimal performance impact - dot notation detection adds only a string contains check
  • Recursive traversal is efficient and stops early for missing paths

[1.0.1] - 2025-08-20

Documentation

  • Fixes main page for Hex docs

[1.0.0] - 2025-08-19

Added

Core Language Features

  • Comparison Operators: Full support for >, <, >=, <=, =, != with proper type handling
  • Logical Operators: Case-insensitive AND/and, OR/or, NOT/not with correct precedence
  • Data Types:
    • Numbers (integers): 42, -17
    • Strings (double-quoted): "hello", "world"
    • Booleans: true, false
    • Date literals: #2024-01-15# (ISO 8601 format)
    • DateTime literals: #2024-01-15T10:30:00Z# (ISO 8601 with timezone)
    • List literals: [1, 2, 3], ["admin", "manager"]
    • Identifiers: score, user_name, is_active

Advanced Operations

  • Membership Operators:
    • in for element-in-collection testing (role in ["admin", "manager"])
    • contains for collection-contains-element testing ([1, 2, 3] contains 2)
  • Parenthesized Expressions: Full support with proper precedence handling
  • Plain Boolean Expressions: Support for bare identifiers (active, expired) without explicit = true

Function System

  • Built-in System Functions:
    • String functions: len(string), upper(string), lower(string), trim(string)
    • Numeric functions: abs(number), max(a, b), min(a, b)
    • Date functions: year(date), month(date), day(date)
  • Custom Function Registration: Register anonymous functions with Predicator.register_function/3
  • Function Registry: ETS-based registry with automatic arity validation and error handling
  • Context-Aware Functions: Functions receive evaluation context for dynamic behavior

Architecture & Performance

  • Multi-Stage Compilation Pipeline: Expression → Lexer → Parser → Compiler → Instructions → Evaluator
  • Compile-Once, Evaluate-Many: Pre-compile expressions for repeated evaluation
  • Stack-Based Evaluator: Efficient instruction execution with minimal overhead
  • Comprehensive Error Handling: Detailed error messages with line/column positioning

Developer Experience

  • String Decompilation: Convert AST back to readable expressions with formatting options
  • Multiple Evaluation APIs:
    • evaluate/2 - Returns {:ok, result} or {:error, message}
    • evaluate!/2 - Returns result directly or raises exception
    • compile/1 - Pre-compile expressions to instructions
    • parse/1 - Parse expressions to AST for inspection
  • Formatting Options: Configurable spacing (:normal, :compact, :verbose) and parentheses (:minimal, :explicit, :none)

Code Organization

  • Modular Architecture: Clean separation of concerns across lexer, parser, compiler, evaluator
  • Organized File Structure:
    • lib/predicator/functions/ - Function system components
    • lib/predicator/visitors/ - AST transformation modules
  • Comprehensive Testing: 616 tests with 66 doctests, achieving >90% code coverage

Technical Details

Grammar

The language supports a complete expression grammar with proper operator precedence:

expression   → logical_or
logical_or   → logical_and ( ("OR" | "or") logical_and )*
logical_and  → logical_not ( ("AND" | "and") logical_not )*
logical_not  → ("NOT" | "not") logical_not | comparison
comparison   → primary ( ( ">" | "<" | ">=" | "<=" | "=" | "!=" | "in" | "contains" ) primary )?
primary      → NUMBER | STRING | BOOLEAN | DATE | DATETIME | IDENTIFIER | list | function_call | "(" expression ")"
function_call → IDENTIFIER "(" ( expression ( "," expression )* )? ")"
list         → "[" ( expression ( "," expression )* )? "]"

Security

  • No Dynamic Code Execution: All expressions compiled to safe instruction sequences
  • Input Validation: Comprehensive validation at lexer and parser levels
  • Type Safety: Strong typing throughout compilation and evaluation pipeline
  • Sandboxed Evaluation: No access to system functions or arbitrary code execution

Performance

  • Efficient Tokenization: Single-pass lexer with position tracking
  • Recursive Descent Parser: Clean, maintainable parsing with excellent error recovery
  • Optimized Instruction Set: Minimal instruction overhead for fast evaluation
  • Memory Efficient: Low allocation during expression evaluation

Dependencies

  • Runtime: Zero external dependencies for core functionality
  • Development: Credo, Dialyzer, ExCoveralls for code quality and testing
  • Minimum Elixir: ~> 1.11

Breaking Changes

⚠️ COMPLETE LIBRARY REWRITE ⚠️

Version 1.0.0 is a complete rewrite of the Predicator library with entirely new:

  • API design and function signatures
  • Expression syntax and grammar
  • Internal architecture and data structures
  • Feature set and capabilities

Migration Guide

Migration from versions < 1.0.0 has NOT been tested and is NOT guaranteed to work.

If you are upgrading from a pre-1.0.0 version:

  1. Treat this as a new library adoption, not an upgrade
  2. Review all documentation - APIs have completely changed
  3. Test thoroughly in development environments
  4. Expect to rewrite all integration code
  5. Plan for significant refactoring of existing expressions

Future 1.x.x versions will maintain backwards compatibility and include proper migration guides.


Legacy Versions (Pre-1.0.0)

The following versions are part of the original Predicator implementation, which has been completely rewritten for 1.0.0:

[0.9.2]

Documentation

  • Adds additional information to README
  • Adds documentation to functions in Predicator

Enhancements

  • Adds compile!, evaluate, evaluate!, evaluate_instructions, evaluate_instructions! functions to Predicator
  • Adds Ecto.PredicatorInstructions Ecto type

[0.9.1]

Documentation

[0.9.0]

Breaking Changes

  • Evaluates compare instead of comparator to be compatible with ruby predicator lib

[0.8.1]

Enhancements

  • Adds leex and parsing for and and or
  • Adds leex and parsing for ! and boolean

[0.8.0]

Added

  • Predicator.matches?/3 accepts evaluator options

Enhancements

  • Adds leex and parsing for isblank and ispresent
  • Supports escaped double quote strings

Fixed

  • in and notin accept list of strings

[0.7.3]

Enhancements

  • Adds leex and parsing for in, notin, between, startswith, endswith instructions

[0.7.1]

Added

  • Adds between instruction for eval on dates

[0.7.0]

Added

  • Adds 2 new comparison predicates for starts_with & ends_with

[0.6.0]

Added

  • Adds 3 new evaluatable predicates for to_date, date_ago, and date_from_now

[0.5.0]

Changed

  • Evaluator now reads new coercion instructions to_int, to_str, & to_bool

[0.4.0]

Added

  • Adds 4 new functions to the Predicator module: eval/3, leex_string/1, parsed_lexed/1, & leex_and_parse/1

[0.3.0]

Enhancements

  • 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]

For detailed information about upcoming features and development roadmap, see the project README.