# Changelog

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

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- The Hex package `files:` list named a bare `docs` entry, which swept the
  whole `docs/` tree - including `docs/plans/*.md` and `docs/design/*.md`,
  the agent workflow's internal per-bead planning documents. It now names the
  published doc subtrees explicitly (`docs/reference`, `docs/guides`,
  `docs/adr`, `docs/architecture.md`), matching what the `docs()` extras list
  already publishes to hexdocs.

## [3.7.0] - 2026-08-05

### Changed

- **`AND` and `OR` now short-circuit.** Previously the compiler evaluated both
  sides of every `AND`/`OR` unconditionally, so an unbound variable or a
  runtime error on the side that should have been skipped surfaced as an
  error - `false AND score > 5` with `score` unbound raised
  `TypeMismatchError`, and `true OR (1 / 0) > 1` raised a division-by-zero
  error. Both now evaluate to `false` and `true` respectively, matching every
  mainstream language's `AND`/`OR` semantics and this library's own graceful
  undefined-handling documentation. **This is an observable behavior change**:
  expressions that previously returned `{:error, _}` now return `{:ok, _}`. A
  consumer relying on the error was relying on the bug. `:undefined`
  propagation is ECMAScript-aligned rather than symmetric - see
  `docs/architecture.md`'s "Short-Circuit Evaluation" section for the exact
  rule. Old compiled artifacts using `["and"]`/`["or"]` directly are
  unaffected; the evaluator still accepts both opcodes.
- `Predicator.parse/1` now returns positioned AST nodes, so every node has one
  more trailing element than it did in 3.6. Callers that pattern-match on node
  shape either wrap the result in `Predicator.Parser.strip_positions/1` to get
  the old shape back, or add a trailing `_position` to their patterns.
  `Predicator.decompile/2` and `Predicator.Compiler.to_instructions/2` still
  accept a hand-built 3.6-shaped AST unchanged, and the instruction list
  `Predicator.compile/1` produces is byte-identical, so stored compiled
  artifacts and cross-language interchange are unaffected.
- Documentation restructured: the README is now a slim entry point, with the
  language reference, nested data access, custom functions, and location
  expressions moved to `docs/reference/` and `docs/guides/` and published to
  hexdocs. All documentation examples are now executed by the test suite.

### Added

- **ISA v2** (ADR-0001): the instruction set is Predicator's cross-language
  interchange format, and both entries below are new opcodes the Ruby and
  JavaScript siblings need to add for parity with this release.
  - `["make_list", n]` instruction: pops n values from the stack and pushes
    them as a list, in source order.
  - `["jump_if_falsy_or_pop", offset]` and `["jump_if_true_or_pop", offset]`
    instructions: relative, forward-only conditional jumps used to
    short-circuit `AND`/`OR`.
- `Predicator.Context`: a bound evaluation context built once with `new/2`
  (merging builtin and custom functions a single time), rebound cheaply with
  `bind/3` and `assign/3`, and evaluated against many times via
  `Predicator.evaluate/3`, which now accepts either a `%Context{}` or a bare
  map
- `Predicator.Undefined`: the one public module that owns the `:undefined`
  sentinel - `value/0`, `undefined?/1`, and `to_nil/1`/`from_nil/1`
  normalizers for a JSON-shaped boundary. `Predicator.Types.undefined?/1`
  now delegates to it.
- `Predicator.Context.bound?/2`: answers whether a root variable is bound in
  a context's data, checking both string and atom keys.
- `starts_with(s, prefix)`, `ends_with(s, suffix)`, `substring(s, start[, len])`,
  and `index_of(s, sub)` builtin string functions
- `concat(list1, list2)` builtin function: concatenates two lists.
- `+` now concatenates two lists (`[1, 2] + [3]` -> `[1, 2, 3]`), alongside
  its existing numeric and string coercions.
- `Predicator.Evaluator.run_prepared/1` (result plus final evaluator state),
  `Predicator.Evaluator.unbound_loads/1`, and
  `Predicator.Evaluator.resolve_key/2`.
- Source positions on every AST node: each node carries a trailing
  `{line, column}` naming the token that defines it (the operator token for
  binary and unary operators, the opening bracket for lists and objects, the
  name token for function calls).
- `Predicator.Parser.strip_positions/1` and
  `Predicator.Parser.ensure_positions/1`: total, idempotent normalizers between
  the positioned AST and the position-free shape Predicator 3.6 produced.
- `Predicator.Compiler.to_instructions_with_positions/2` and
  `Predicator.Visitors.InstructionsVisitor.visit_with_positions/2`: compile to
  the usual instruction list plus a side table mapping each instruction's
  0-based index to the `{line, column}` of the AST node that emitted it. The
  table is an Elixir-side companion value and never enters the instruction
  format itself.
- `Predicator.compile_with_positions/1`: compiles a string expression to the
  instruction list `compile/1` returns plus that side table.
- An optional `:position` field on `Predicator.Errors.EvaluationError`,
  `Predicator.Errors.TypeMismatchError`, and
  `Predicator.Errors.UndefinedVariableError`, holding the `{line, column}` of
  the source token behind the failing instruction, or `nil` when no side table
  was available.
- `Predicator.Errors.put_position/2`: attaches a position to any error value,
  returning it unchanged when the position is `nil` or the value has no
  `:position` field.
- A `:positions` option on `Predicator.evaluate/3` and
  `Predicator.Evaluator.evaluate/3`, seeding the side table used to populate
  `:position` on runtime errors. Evaluating a string expression threads its own
  table automatically; an instruction-list caller who omits the option sees
  `position: nil` and no other change.

### Fixed

- `Predicator.decompile/2` and `Predicator.Compiler.to_string/2` no longer
  raise `FunctionClauseError` on ASTs containing dotted property access
  (`user.name`). `Predicator.Visitors.StringVisitor` was missing the
  `:property_access` clause; it now renders `object.property`, including
  chains (`user.profile.email`) and mixes with bracket access.
- Duration units now parse in source order: `3d8h` produces
  `[{3, "d"}, {8, "h"}]` instead of the reversed `[{8, "h"}, {3, "d"}]`, and
  multi-unit durations round-trip through the string visitor unchanged
- Comparing a `Date` against a `DateTime` now returns a boolean instead of
  silently evaluating to `:undefined`. The `Date` is coerced to `00:00:00`
  UTC of that day, matching the coercion mixed date subtraction already
  performs. This covers ordering, `==`/`!=`, and `in`/`contains`, and it
  makes every relative date (`3d ago`, `2w from now`, `next 1mo`, `last 1y`,
  all of which produce a `DateTime`) usable against a `Date` context value.
  Strict equality (`===`/`!==`) stays type-strict and never crosses the
  boundary.
- `Date` and `DateTime` ordering (`<`, `>`, `<=`, `>=`) is now chronological.
  The evaluator previously dispatched ordering comparisons to Erlang's `<`/`>`
  after confirming both sides were the same struct type, but Erlang orders
  structs by sorted map key, not by field meaning - `Date`'s keys sort
  `day, month, year`, so `#2026-08-14# < #2030-01-01#` compared day 14 against
  day 1 and returned `false`. `DateTime` was worse, sorting `microsecond`
  ahead of `month`. Ordering now goes through `Date.compare/2` and
  `DateTime.compare/2`, and `EQ`/`NE`/list-membership on `DateTime` now agree
  with `DateTime.compare/2` rather than structural equality, so two `DateTime`
  values denoting the same instant in different time zones compare equal.
- `Predicator.evaluate/3` now correctly reports `UndefinedVariableError` for
  any unbound root variable, not just a bare `variable_name` expression. The
  old check only matched a single-instruction `[["load", _]]` program, so an
  unbound variable inside a larger expression (`"missing > 5"`) silently
  returned `{:ok, :undefined}` instead of an error.
- Unbound-variable reporting now reflects the loads a run actually executed
  rather than the loads the compiled program contains. With short-circuiting
  `AND`/`OR`, a load inside a skipped branch is never read, but the previous
  check scanned the whole instruction list and could name it -
  `(false AND missing) OR unbound_b` reported `missing` instead of
  `unbound_b`.
- List literals with non-literal elements (`[x + 1, y]`) now compile and
  evaluate. Previously the compiler raised
  `"Non-literal list elements are not yet supported"`, the one place the
  errors-are-values convention was broken; errors from such expressions are now
  returned as `{:error, _}` values like every other failure.

### Deprecated

#### `=` as an equality operator

- Parsing an expression that uses `=` as an equality operator now emits a
  deprecation warning naming `==` as the replacement
- Behavior is otherwise unchanged: `=` still parses and still compiles to
  `["compare", "EQ"]`
- **Predicator 4.0 will make expression-position `=` a parse error.** Migrate
  to `==` before upgrading
- The warning is emitted once per parse and can be silenced with
  `config :predicator, deprecation_warnings: false`

```elixir
# Deprecated - warns, still works in 3.x
Predicator.evaluate("status = 'active'", context)

# Preferred
Predicator.evaluate("status == 'active'", context)
```

## [3.6.0] - 2026-08-04

### Added

#### Auto-vivifying path assignment for SCXML location expressions

- `Predicator.ContextLocation.put/3` writes a value at a resolved location path,
  creating missing intermediate maps and lists
- `Predicator.context_assign/4` resolves a location expression and writes in one call
- Integer path segments index lists and pad gaps with `:undefined`
- Assigning through an existing scalar returns a `:not_a_container` error rather than
  destroying data; negative indices return `:invalid_index`

#### Examples

```elixir
Predicator.context_assign(%{}, "user.profile.name", "Ada")
# {:ok, %{"user" => %{"profile" => %{"name" => "Ada"}}}}

Predicator.context_assign(%{"items" => [1]}, "items[2]", "x")
# {:ok, %{"items" => [1, :undefined, "x"]}}

Predicator.ContextLocation.put(%{}, ["data", "users", 0, "name"], "Ada")
# {:ok, %{"data" => %{"users" => [%{"name" => "Ada"}]}}}
```

### Changed

#### Replaces the hand-rolled quality gate with ex_quality

- `mix quality` is now [ex_quality](https://hex.pm/packages/ex_quality), configured
  in `.quality.exs`; the vendored `lib/mix/tasks/quality.ex` has been removed
- The gate runs format, compile (warnings as errors), Credo `--strict`, Dialyzer,
  an unused-dependency and security audit, and the full suite with the existing
  90% coverage minimum - stages run in parallel and report `file:line` findings
- `mix quality --profile loop` replaces `--skip-dialyzer`: it skips Dialyzer and
  coverage and runs only the tests covering changed code
- `mix quality --format json` emits a machine-readable report
- `mix quality.check` and `mix test --watch` are gone; the former no longer
  existed as a task and the latter's `mix_test_watch` dependency was undeclared

## [3.5.0] - 2025-09-09

### Added

#### Adds milliseconds support to duration system

- New 'ms' unit support in lexer, parser, and evaluator
- Duration.to_milliseconds/1 function for high-precision calculations
- Pattern matching guards for automatic precision selection
- Smart DateTime arithmetic (millisecond vs second precision)
- Comprehensive test coverage with 89 new tests
- Refactors evaluator to use Duration module functions (DRY)

#### Examples

- 500ms ago, 2s750ms from now
- #2024-01-15T10:30:00.000Z# + 1s500ms
- Automatic precision: ms > 0 triggers millisecond precision

## [3.4.0] - 2025-09-09

### Added

#### Durations and relative date/time arithmetic

- New duration literals and relative date expressions (e.g., `3d ago`, `2w from now`, `next 1mo`, `last 1y`)
- Date and DateTime arithmetic using durations (e.g., `#2024-01-10# + 5d`, `#2024-01-15T10:30:00Z# - 2h`)
- Grammar additions: `duration` and `relative_date` productions
- Full pipeline support (lexer, parser, compiler, evaluator, string visitor) with tests

#### Examples

```elixir
Predicator.evaluate("created_at > 3d ago", %{"created_at" => ~U[2024-01-20 00:00:00Z]})
Predicator.evaluate("due_at < 2w from now", %{"due_at" => Date.add(Date.utc_today(), 10)})
Predicator.evaluate("#2024-01-10# + 5d = #2024-01-15#", %{})
Predicator.evaluate("#2024-01-15T10:30:00Z# - 2h < #2024-01-15T10:30:00Z#", %{})
```

### Documentation

- Updated EBNF grammar in docs
- Added AGENTS.md with model-agnostic agent guidance; `CLAUDE.md` now references the same content

## [3.3.0] - 2025-08-31

### Added

- Depends on Jason library

## [3.2.0] - 2025-08-31

### Added

#### Strict Equality Operators

- **New Operators**: Added `===` (strict equality) and `!==` (strict inequality) operators
- **Type-Safe Comparisons**: Strict operators compare both value and type, unlike loose equality
- **Round-Trip Preservation**: Operators maintain their exact form during parse/decompile cycles
- **Complete Pipeline Support**: Full lexer, parser, evaluator, and visitor implementation
- **Comprehensive Testing**: 23 tests covering all aspects of strict equality functionality

#### Examples

```elixir
# Strict equality - same type and value required
Predicator.evaluate("5 === 5", %{})      # {:ok, true}
Predicator.evaluate("5 === '5'", %{})    # {:ok, false} - different types

# Strict inequality - true when type or value differs
Predicator.evaluate("5 !== '5'", %{})    # {:ok, true} - different types
Predicator.evaluate("1 !== true", %{})   # {:ok, true} - different types

# Operator distinction preserved
Predicator.parse("x = y") |> elem(1) |> Predicator.decompile()   # "x = y"
Predicator.parse("x == y") |> elem(1) |> Predicator.decompile()  # "x == y"  
Predicator.parse("x === y") |> elem(1) |> Predicator.decompile() # "x === y"
```

#### Technical Implementation

- **Lexer**: Added `:strict_equal` and `:strict_ne` token types with proper precedence
- **Parser**: Extended comparison grammar to support strict operators
- **Evaluator**: Added `STRICT_EQ` and `STRICT_NE` instruction handlers
- **StringVisitor**: Added decompilation support for round-trip accuracy
- **Type Safety**: Works with all data types including `:undefined` values

## [3.1.0] - 2025-08-30

### Added

#### JavaScript-Style Object Literals (Complete Implementation)

- **Object Literal Syntax**: Full support for JavaScript-style object notation with `{key: value}` syntax
- **Multiple Key Types**: Both identifier keys (`name: "John"`) and string keys (`"first name": "John"`)
- **Nested Objects**: Unlimited nesting depth for complex data structures
- **All Value Types**: Objects support all Predicator value types (strings, numbers, booleans, dates, lists, expressions)
- **Object Comparisons**: Full equality and inequality operations between objects
- **Integration**: Seamless compatibility with all existing features (functions, operators, property access)

#### Object Literal Examples

```elixir
# Basic object creation
Predicator.evaluate("{}", %{})                                    # {:ok, %{}}
Predicator.evaluate("{name: \"John\", age: 30}", %{})            # {:ok, %{"name" => "John", "age" => 30}}

# Variable references and expressions
Predicator.evaluate("{user: name, total: price + tax}", %{"name" => "Alice", "price" => 100, "tax" => 10})
# {:ok, %{"user" => "Alice", "total" => 110}}

# Nested objects
Predicator.evaluate("{user: {name: \"Bob\", role: \"admin\"}, active: true}", %{})
# {:ok, %{"user" => %{"name" => "Bob", "role" => "admin"}, "active" => true}}

# String keys for complex property names
Predicator.evaluate("{\"first name\": \"John\", \"last-name\": \"Doe\"}", %{})
# {:ok, %{"first name" => "John", "last-name" => "Doe"}}

# Object comparisons
Predicator.evaluate("{score: 85} == user_data", %{"user_data" => %{"score" => 85}})
# {:ok, true}
```

#### Complete Pipeline Support

- **Lexer**: Added `{`, `}`, `:` token recognition
- **Parser**: Full object grammar with proper precedence and error handling
- **Instructions**: Stack-based `object_new` and `object_set` instruction execution
- **Evaluator**: Efficient object construction and comparison operations
- **String Visitor**: Bidirectional transformation support (AST ↔ string representation)
- **Type System**: Enhanced type matching for object equality comparisons

#### Integration Features

- **Function Integration**: Objects work as function parameters and return values
- **Property Access**: Objects integrate with dot notation (`obj.property`) and bracket access (`obj["key"]`)
- **Boolean Logic**: Objects support all logical operations (AND, OR, NOT)
- **Arithmetic**: Object properties can contain arithmetic expressions and results
- **Date Support**: Objects can contain date/datetime literals and date function results
- **Custom Functions**: Objects work seamlessly with user-defined functions

#### Quality and Testing

- **886 Total Tests**: Comprehensive test coverage including edge cases and integration scenarios
- **91.8% Coverage**: High test coverage across all components
- **Parser Error Handling**: Robust error recovery for malformed object syntax
- **Performance Tested**: Validated with large objects and repeated evaluations
- **Production Ready**: Full quality assurance (formatting, linting, type checking)

## [3.0.0] - 2025-08-25

### Added

#### Location Expressions for SCXML Assignment Operations (Phase 2 Complete)

- **SCXML Location Expressions**: Complete implementation of location path resolution for SCXML `<assign>` operations
- **New API Function**: `Predicator.context_location/3` - resolves assignable location paths from expressions
- **Location Path Resolution**: Returns navigation paths like `["user", "name"]`, `["items", 0, "property"]` for SCXML assignment targets
- **Assignment Validation**: Distinguishes valid assignment targets (l-values) from computed expressions (r-values)
- **Core Module**: `Predicator.ContextLocation` with comprehensive location resolution logic and error handling
- **Structured Error Handling**: `Predicator.Errors.LocationError` with detailed error types and context information

#### Location Expression Examples

```elixir
# Valid assignment targets resolve to location paths
Predicator.context_location("user.profile.name", %{})                    # {:ok, ["user", "profile", "name"]}
Predicator.context_location("items[0]", %{})                             # {:ok, ["items", 0]}
Predicator.context_location("data['users'][index]['profile']", %{"index" => 2})  # {:ok, ["data", "users", 2, "profile"]}

# Invalid assignment targets return structured errors
Predicator.context_location("len(name)", %{})                            # {:error, %LocationError{type: :not_assignable}}
Predicator.context_location("42", %{})                                   # {:error, %LocationError{type: :not_assignable}}
Predicator.context_location("score + 1", %{})                            # {:error, %LocationError{type: :not_assignable}}
```

#### Error Types and Validation

- **`:not_assignable`**: Expression cannot be used as assignment target (literals, functions, computed expressions)
- **`:invalid_node`**: Unknown or unsupported AST node type encountered during resolution
- **`:undefined_variable`**: Variable referenced in bracket key is not defined in evaluation context
- **`:invalid_key`**: Bracket key is not a valid string or integer type
- **`:computed_key`**: Computed expressions cannot be used as assignment target keys

#### Assignable vs Non-Assignable Classifications

- **✅ Valid Assignment Targets**: Simple identifiers, property access, bracket access, mixed notation
  - `user`, `score`, `config.database.host`
  - `items[0]`, `user['profile']`, `data["settings"]`
  - `user.settings['theme']`, `data['users'][0].profile`
- **❌ Invalid Assignment Targets**: Literals, function calls, computed expressions
  - `42`, `"hello"`, `true`, `#2024-01-15#`
  - `len(name)`, `upper(role)`, `max(a, b)`
  - `score + 1`, `items[i + 1]`, `score > 85`

#### Technical Implementation

- **Full Location Resolution**: Recursive resolution of nested property access and bracket access
- **Mixed Notation Support**: Complete support for expressions like `user.settings['theme']` and `data['users'][0].name`
- **Variable Key Resolution**: Bracket keys can reference context variables for dynamic access patterns
- **Context Integration**: Uses existing evaluation context for variable key resolution
- **Comprehensive Testing**: 49 comprehensive tests covering all location resolution scenarios and error cases

#### Type Coercion and Float Support

- **Float Literal Support**: Extended lexer to parse floating-point numbers (e.g., `3.14`, `0.5`)
- **Float Token Type**: Added `:float` token type to distinguish from integers
- **Parser Float Handling**: Updated parser to handle float tokens and create appropriate AST nodes
- **Arithmetic with Floats**: All arithmetic operations now support both integers and floats
  - Addition, subtraction, multiplication work seamlessly with mixed numeric types
  - Division returns float when needed, integer when evenly divisible
  - Modulo remains integer-only as per mathematical conventions
- **String Concatenation with `+` Operator**: Implemented JavaScript-like type coercion
  - `"Hello" + "World"` → `"HelloWorld"` (string concatenation)
  - `"Count: " + 5` → `"Count: 5"` (string + number coercion)
  - `42 + " items"` → `"42 items"` (number + string coercion)
- **Type Coercion Rules**:
  - Number + Number → Numeric addition (supports mixed int/float)
  - String + String → String concatenation
  - String + Number → String concatenation (number converted to string)
  - Number + String → String concatenation (number converted to string)
- **Comparison Enhancements**: Numbers of different types (int/float) can be compared
- **Unary Minus for Floats**: Unary minus operator now works with floating-point numbers
- **Error Message Updates**: Updated error messages from "integer" to "number" where appropriate
- **Comprehensive Testing**: Added 28 new tests covering all type coercion scenarios

### Changed

#### Property Access Parsing Architecture Overhaul (Breaking Changes)

- **Complete Dot Notation Reimplementation**: Transformed from dotted identifiers to proper property access AST nodes
- **Lexer Breaking Change**: Dots removed from valid identifier characters, now parsed as separate tokens
- **Parser Grammar Enhancement**: Added property access grammar `postfix → primary ( "[" expression "]" | "." IDENTIFIER )*`
- **New AST Structure**: Expressions like `user.email` now parsed as `{:property_access, {:identifier, "user"}, "email"}`
- **Instruction Pipeline**: Evaluation generates separate `load` and `access` instructions instead of single `load` with dotted name
- **Mixed Notation Support**: Enables complex expressions like `user.settings['theme']` and `data['users'][0].profile`

### Breaking Changes

#### v3.0.0 - Property Access Parsing Overhaul

This is a **major breaking change** affecting how dot notation is parsed and evaluated:

**⚠️ Context Key Impact**: Context keys containing dots (e.g., `"user.email"`) will no longer match dot notation expressions (`user.email`). The expression `user.email` is now parsed as property access requiring nested structure `%{"user" => %{"email" => "..."}}`

**Migration Required**:

```elixir
# BEFORE (v2.2.0 and earlier) - WILL NO LONGER WORK
context = %{"user.email" => "john@example.com"}
Predicator.evaluate("user.email = 'john@example.com'", context)  # No longer matches

# AFTER (v3.0.0+) - Use proper nested structures
context = %{"user" => %{"email" => "john@example.com"}}
Predicator.evaluate("user.email = 'john@example.com'", context)  # Works correctly
```

**Technical Changes**:

- **Lexer**: Dots no longer valid in identifier characters, parsed as separate `:dot` tokens
- **Parser**: New property access AST nodes `{:property_access, left_node, property}`
- **Evaluator**: New `access` instruction handler, removed dotted identifier support from `load_from_context`
- **Instructions**: `user.email` generates `[["load", "user"], ["access", "email"]]` instead of `[["load", "user.email"]]`

**Benefits**:

- Enables mixed notation: `user.settings['theme']`, `data['users'][0].name`
- Supports SCXML location expressions for assignment operations
- Proper property access semantics for complex data structures
- Foundation for advanced SCXML datamodel integration

## [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

#### 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:
  - `Predicator.Errors.TypeMismatchError` - Type validation and mismatch errors
  - `Predicator.Errors.EvaluationError` - Runtime evaluation errors (division by zero, insufficient operands)
  - `Predicator.Errors.UndefinedVariableError` - Variable access errors
  - `Predicator.Errors.ParseError` - Expression parsing and syntax errors
- **Consistent Error Messages**: Unified error message formatting across all error types
- **Code Quality Improvements**: Resolved all credo issues with proper module aliasing and organization

## [2.1.0] - 2025-08-24

### Added

#### Arithmetic and Unary Operations (Complete Implementation)

- **Full Arithmetic Support**: Complete parsing and evaluation pipeline for arithmetic expressions
  - **Binary operations**: `+` (addition), `-` (subtraction), `*` (multiplication), `/` (division), `%` (modulo)
  - **Unary operations**: `-` (unary minus), `!` (unary bang/logical NOT)
- **Proper Precedence**: Mathematical precedence handling (unary → multiplication → addition → equality → comparison)
- **Instruction Execution**: Stack-based evaluator with 7 new instruction handlers
- **Error Handling**: Division by zero protection, type checking, comprehensive error messages
- **Pattern Matching**: Idiomatic Elixir implementation using pattern matching for clean code

## [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

```elixir
# 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

### Breaking Changes

#### v2.0.0 - Custom Function Architecture Overhaul

- **Removed**: Global function registry system (`Predicator.Functions.Registry` module)
- **Removed**: `Predicator.register_function/3`, `Predicator.clear_custom_functions/0`, `Predicator.list_custom_functions/0`
- **Changed**: Custom functions now passed via `functions:` option in `evaluate/3` calls instead of global registration
- **Benefit**: Thread-safe, no global state, per-evaluation function scoping
- **Migration**: Replace registry calls with function maps passed to `evaluate/3`

## [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.)

### Breaking Changes

#### v1.1.0 - Nested Access Parsing

- **Changed**: Variables containing dots (e.g., `"user.email"`) now parsed as nested access paths
- **Impact**: Context keys like `"user.profile.name"` will no longer match identifier `user.profile.name`
- **Solution**: Use proper nested data structures instead of flat keys with dots

## [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`)

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

---

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