# Statifier - SCXML State Machines for Elixir
[](https://github.com/riddler/statifier/actions)
[](https://codecov.io/gh/riddler/statifier)
An Elixir implementation of SCXML (State Chart XML) state charts with a focus on W3C compliance.
## Features
- ✅ **Complete SCXML Parser** - Converts XML documents to structured data with precise location tracking
- ✅ **State Chart Interpreter** - Runtime engine for executing SCXML state charts
- ✅ **Modular Validation** - Document validation with focused sub-validators for maintainability
- ✅ **Compound States** - Support for hierarchical states with automatic initial child entry
- ✅ **Initial State Elements** - Full support for `<initial>` elements with transitions (W3C compliant)
- ✅ **Parallel States** - Support for concurrent state regions with simultaneous execution
- ✅ **Eventless Transitions** - Automatic transitions without event attributes (W3C compliant)
- ✅ **Conditional Transitions** - Full support for `cond` attributes with expression evaluation
- ✅ **Assign Elements** - Complete `<assign>` element support with location-based assignment and nested property access
- ✅ **Value Evaluation** - Non-boolean expression evaluation using Predicator v3.0 for actual data values
- ✅ **Data Model Integration** - StateChart data model with dynamic variable assignment and persistence
- ✅ **O(1) Performance** - Optimized state and transition lookups via Maps
- ✅ **Event Processing** - Internal and external event queues per SCXML specification
- ✅ **Parse → Validate → Optimize Architecture** - Clean separation of concerns
- ✅ **Feature Detection** - Automatic SCXML feature detection for test validation
- ✅ **Regression Testing** - Automated tracking of passing tests to prevent regressions
- ✅ **Git Hooks** - Pre-push validation workflow to catch issues early
- ✅ **Logging Infrastructure** - Protocol-based logging system with TestAdapter for clean test environments
- ✅ **Test Infrastructure** - Compatible with SCION and W3C test suites with integrated logging
- ✅ **Code Quality** - Full Credo compliance with proper module aliasing
## Current Status
### Working Features
- ✅ **Basic state transitions** and event-driven changes
- ✅ **Hierarchical states** with optimized O(1) state lookup and automatic initial child entry
- ✅ **Initial state elements** - Full `<initial>` element support with transitions and comprehensive validation
- ✅ **Parallel states** with concurrent execution of multiple regions and proper cross-boundary exit semantics
- ✅ **Eventless transitions** - Automatic transitions without event attributes (also called NULL transitions in SCXML spec), with cycle detection and microstep processing
- ✅ **Conditional transitions** - Full `cond` attribute support with Predicator v3.0 expression evaluation and SCXML `In()` function
- ✅ **Assign elements** - Complete `<assign>` element support with location-based assignment, nested property access, and mixed notation
- ✅ **Value evaluation system** - Statifier.ValueEvaluator module for non-boolean expression evaluation and data model operations
- ✅ **Enhanced expression evaluation** - Predicator v3.0 integration with deep property access and type-safe operations
- ✅ **Transition conflict resolution** - Child state transitions take priority over ancestor transitions per W3C specification
- ✅ **SCXML-compliant processing** - Proper microstep/macrostep execution model with exit set computation and LCCA algorithms
- ✅ **Modular validation** - Refactored from 386-line monolith into focused sub-validators
- ✅ **Feature detection** - Automatic SCXML feature detection prevents false positive test results
- ✅ **SAX-based XML parsing** with accurate location tracking for error reporting
- ✅ **Performance optimizations** - O(1) state/transition lookups, optimized active configuration
- ✅ **Source field optimization** - Transitions include source state for faster event processing
- ✅ **Comprehensive logging** - Protocol-based logging system with structured metadata and test environment integration
### Planned Features
- History states (`<history>`)
- Internal and targetless transitions
- More executable content (`<script>`, `<send>`, etc.) - `<assign>` now supported!
- Enhanced datamodel support with more expression functions
- Enhanced validation for complex SCXML constructs
## Recent Completions
### **✅ SCXML-Compliant Processing Engine**
- **`Microstep/Macrostep Execution`** - Implements SCXML event processing model with microstep (single transition set execution) and macrostep (series of microsteps until stable)
- **`Eventless Transitions`** - Transitions without event attributes (called NULL transitions in SCXML spec) that fire automatically upon state entry
- **`Exit Set Computation`** - Implements W3C SCXML exit set calculation algorithm for determining which states to exit during transitions
- **`LCCA Algorithm`** - Full Least Common Compound Ancestor computation for accurate transition conflict resolution and exit set calculation
- **`Cycle Detection`** - Prevents infinite loops with configurable iteration limits (100 iterations default)
- **`Parallel Region Preservation`** - Proper SCXML exit semantics for transitions within and across parallel regions
- **`Optimal Transition Set`** - SCXML-compliant transition conflict resolution where child state transitions take priority over ancestors
- **`Test Coverage`** - 18+ comprehensive test scenarios covering all eventless transition patterns, LCCA edge cases, and complex hierarchies
### **✅ Enhanced Parallel State Support**
- **`Cross-Parallel Boundaries`** - Proper exit semantics when transitions leave parallel regions
- **`Sibling State Management`** - Automatic exit of parallel siblings when transitions exit their shared parent
- **`Self-Transitions`** - Transitions within parallel regions preserve unaffected parallel regions
- **`SCION Compatibility`** - All 4 `cond_js` tests now pass, 6 parallel interrupt tests fixed
- **`Regression Prevention`** - 62 regression tests now validate all critical functionality
### **✅ Feature-Based Test Validation System**
- **`Statifier.FeatureDetector`** - Analyzes SCXML documents to detect used features
- **Feature validation** - Tests fail when they depend on unsupported features
- **False positive prevention** - No more "passing" tests that silently ignore unsupported features
- **Capability tracking** - Clear visibility into which SCXML features are supported
### **✅ Modular Validator Architecture**
- **`Statifier.Validator`** - Main orchestrator (from 386-line monolith)
- **`Statifier.Validator.StateValidator`** - State ID validation
- **`Statifier.Validator.TransitionValidator`** - Transition target validation
- **`Statifier.Validator.InitialStateValidator`** - All initial state constraints
- **`Statifier.Validator.ReachabilityAnalyzer`** - State reachability analysis
- **`Statifier.Validator.Utils`** - Shared utilities
### **✅ Initial State Elements**
- **Parser support** - `<initial>` elements with `<transition>` children
- **Interpreter logic** - Proper initial state entry via initial elements
- **Comprehensive validation** - Conflict detection, target validation, structure validation
- **Feature detection** - Automatic detection of initial element usage
## Future Extensions
The next major areas for development focus on expanding SCXML feature support:
### **High Priority Features**
- **Executable Content** - `<script>` elements (`<onentry>`, `<onexit>`, `<assign>` now supported!)
- **History States** - Shallow and deep history state support
### **Medium Priority Features**
- **Internal Transitions** - `type="internal"` transition support
- **Targetless Transitions** - Transitions without target for pure actions
- **Enhanced Error Handling** - Better error messages with source locations
- **Performance Benchmarking** - Establish performance baselines and optimize hot paths
## Installation
Add `statifier` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:statifier, "~> 1.4"}
]
end
```
## Usage
### Basic Example
```elixir
# Parse SCXML document
xml = """
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
<state id="start">
<transition event="go" target="end"/>
</state>
<state id="end"/>
</scxml>
"""
{:ok, document} = Statifier.parse(xml)
# Initialize state chart
{:ok, state_chart} = Statifier.interpret(document)
# Check active states
active_states = Statifier.Interpreter.active_states(state_chart)
# Returns: MapSet.new(["start"])
# Send event
event = Statifier.Event.new("go")
{:ok, new_state_chart} = Statifier.Interpreter.send_event(state_chart, event)
# Check new active states
active_states = Statifier.Interpreter.active_states(new_state_chart)
# Returns: MapSet.new(["end"])
```
### Eventless Transitions Example
```elixir
# Automatic transitions without events fire immediately
xml = """
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
<state id="start">
<transition target="processing"/> <!-- No event - fires automatically -->
</state>
<state id="processing">
<transition target="done" cond="ready == true"/> <!-- Conditional eventless -->
</state>
<state id="done"/>
</scxml>
"""
{:ok, document} = Statifier.parse(xml)
{:ok, state_chart} = Statifier.interpret(document)
# Eventless transitions processed automatically during initialization
active_states = Statifier.Interpreter.active_states(state_chart)
# Returns: MapSet.new(["processing"]) - automatically moved from start
```
### Parallel States Example
```elixir
xml = """
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0">
<parallel id="app">
<state id="ui" initial="idle">
<state id="idle">
<transition event="click" target="busy"/>
</state>
<state id="busy">
<transition event="done" target="idle"/>
</state>
</state>
<state id="network" initial="offline">
<state id="offline">
<transition event="connect" target="online"/>
</state>
<state id="online"/>
</state>
</parallel>
</scxml>
"""
{:ok, document} = Statifier.parse(xml)
{:ok, state_chart} = Statifier.interpret(document)
# Both parallel regions active simultaneously
active_states = Statifier.Interpreter.active_states(state_chart)
# Returns: MapSet.new(["idle", "offline"])
```
### Document Validation
```elixir
{:ok, document} = Statifier.parse(xml)
case Statifier.validate(document) do
{:ok, optimized_document, warnings} ->
# Document is valid and optimized, warnings are non-fatal
IO.puts("Valid document with #{length(warnings)} warnings")
# optimized_document now has O(1) lookup maps built
{:error, errors, warnings} ->
# Document has validation errors
IO.puts("Validation failed with #{length(errors)} errors")
end
```
### Assign Elements Example
```elixir
# SCXML with assign elements for dynamic data manipulation
xml = """
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
<state id="start">
<onentry>
<assign location="userName" expr="'John Doe'"/>
<assign location="counter" expr="42"/>
<assign location="user.profile.name" expr="'Jane Smith'"/>
<assign location="users['admin'].active" expr="true"/>
</onentry>
<transition target="working"/>
</state>
<state id="working">
<onentry>
<assign location="counter" expr="counter + 1"/>
<assign location="status" expr="'processing'"/>
</onentry>
<onexit>
<assign location="status" expr="'completed'"/>
</onexit>
<transition event="finish" target="done"/>
</state>
<final id="done"/>
</scxml>
"""
{:ok, document} = Statifier.parse(xml)
{:ok, state_chart} = Statifier.interpret(document)
# Check the data model after onentry execution
datamodel = state_chart.datamodel
# Returns: %{
# "userName" => "John Doe",
# "counter" => 43, # incremented to 43 in working state
# "user" => %{"profile" => %{"name" => "Jane Smith"}},
# "users" => %{"admin" => %{"active" => true}},
# "status" => "processing"
# }
```
### Logging and Test Environment
Statifier includes a comprehensive logging system designed for both production use and clean test environments:
```elixir
# Production logging with Elixir Logger integration
{:ok, document} = Statifier.parse(xml)
{:ok, state_chart} = Statifier.interpret(document, [
log_adapter: {Statifier.Logging.ElixirLoggerAdapter, []},
log_level: :info
])
# Test environment automatically uses TestAdapter (configured in test/test_helper.exs)
# for clean output and log inspection
# Using log helpers in tests
defmodule MyStateMachineTest do
use Statifier.Case # Provides logging test helpers
test "action execution with logging" do
xml = """
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
<state id="start">
<onentry>
<log expr="'Starting process'"/>
<assign location="status" expr="'active'"/>
</onentry>
<transition event="go" target="done"/>
</state>
<state id="done"/>
</scxml>
"""
{:ok, state_chart} = test_scxml(xml, "logging test", ["start"], [
{%{"name" => "go"}, ["done"]}
])
# Assert specific log entries were created
assert_log_entry(state_chart, message_contains: "Starting process")
assert_log_entry(state_chart, level: :debug, action_type: "assign_action")
# Verify logs appear in chronological order
assert_log_order(state_chart, [
[message_contains: "Starting process"],
[action_type: "assign_action"]
])
end
end
```
**Key logging features:**
- **Clean test output**: No log pollution in test console
- **Structured metadata**: All logs include contextual information (state_id, action_type, phase)
- **Chronological storage**: Logs stored oldest-first for intuitive debugging
- **Test helpers**: `assert_log_entry()` and `assert_log_order()` for easy log verification
- **Production integration**: ElixirLoggerAdapter integrates seamlessly with existing Logger setup
## Development
### Requirements
- Elixir 1.17+
- Erlang/OTP 26+
### Setup
```bash
mix deps.get
mix compile
```
### Code Quality Workflow
The project maintains high code quality through automated checks:
```bash
# Local validation workflow (also runs via pre-push hook)
mix format # Auto-fix formatting
mix test.regression # Run critical regression tests (22 tests)
mix credo --strict # Static code analysis
mix dialyzer # Type checking
```
### Regression Testing
The project uses automated regression testing to prevent breaking existing functionality:
```bash
# Run only tests that should always pass (63 tests)
mix test.regression
# Check which tests are currently passing to update regression suite
mix test.baseline
# Install git hooks for automated validation
./scripts/setup-git-hooks.sh
```
The regression suite tracks:
- **Internal tests**: All `test/statifier/**/*_test.exs` files (supports wildcards) - includes edge case coverage tests
- **SCION tests**: 8 known passing tests (basic + hierarchy + parallel + conditional)
- **W3C tests**: Currently none passing
### Running Tests
```bash
# All internal tests (excludes SCION/W3C by default)
mix test
# All tests including SCION and W3C test suites
mix test --include scion --include scxml_w3
# Only regression tests (63 critical tests)
mix test.regression
# With coverage reporting
mix coveralls
# Specific test categories
mix test --include scion test/scion_tests/basic/
mix test test/statifier/parser/scxml_test.exs
```
## Architecture
### Core Components
- **`Statifier.Parser.SCXML`** - SAX-based XML parser with location tracking (parse phase)
- **`Statifier.Validator`** - Modular validation orchestrator with focused sub-validators (validate + optimize phases)
- **`Statifier.FeatureDetector`** - SCXML feature detection for test validation and capability tracking
- **`Statifier.Interpreter`** - Synchronous state chart interpreter with compound state support
- **`Statifier.StateChart`** - Runtime container with event queues
- **`Statifier.Configuration`** - Active state management (leaf states only)
- **`Statifier.Event`** - Event representation with origin tracking
### Data Structures
- **`Statifier.Document`** - Root SCXML document with states, metadata, and O(1) lookup maps
- **`Statifier.State`** - Individual states with transitions and hierarchical nesting support
- **`Statifier.Transition`** - State transitions with events and targets
- **`Statifier.Data`** - Datamodel elements with expressions
### Architecture Flow
```elixir
# 1. Parse: XML → Document structure
{:ok, document} = Statifier.parse(xml)
# 2. Validate: Check semantics + optimize with lookup maps
{:ok, optimized_document, warnings} = Statifier.validate(document)
# 3. Interpret: Run state chart with optimized lookups
{:ok, state_chart} = Statifier.interpret(optimized_document)
```
## Performance Optimizations
The implementation includes several key optimizations for production use:
### **O(1) State and Transition Lookups**
- **State Lookup Map**: `%{state_id => state}` for instant state access
- **Transition Lookup Map**: `%{state_id => [transitions]}` for fast transition queries
- **Built During Validation**: Lookup maps only created for valid documents
- **Memory Efficient**: Uses existing document structure, no duplication
### **Compound and Parallel State Entry**
```elixir
# Automatic hierarchical entry
{:ok, state_chart} = Statifier.interpret(document)
active_states = Statifier.Interpreter.active_states(state_chart)
# Returns only leaf states (compound/parallel states entered automatically)
# Fast ancestor computation when needed
ancestors = Statifier.Interpreter.active_ancestors(state_chart)
# O(1) state lookups + O(d) ancestor traversal
# Parallel states enter ALL child regions simultaneously
# Compound states enter initial child recursively
```
### **Parse → Validate → Optimize Flow**
- **Separation of Concerns**: Parser focuses on structure, validator on semantics
- **Conditional Optimization**: Only builds lookup maps for valid documents
- **Future-Proof**: Supports additional parsers (JSON, YAML) with same validation
**Performance Impact:**
- O(1) vs O(n) state lookups during interpretation
- O(1) vs O(n) transition queries for event processing
- Source field optimization eliminates expensive lookups during event processing
- Critical for responsive event processing in complex state charts
## Regression Testing System
The project includes a sophisticated regression testing system to ensure stability:
### **Test Registry** (`test/passing_tests.json`)
```json
{
"internal_tests": ["test/statifier_test.exs", "test/statifier/**/*_test.exs"],
"scion_tests": ["test/scion_tests/basic/basic0_test.exs", ...],
"w3c_tests": []
}
```
### **Wildcard Support**
- Supports glob patterns like `test/statifier/**/*_test.exs`
- Automatically expands to all matching test files
- Maintains clean, maintainable test registry
### **CI Integration**
- Regression tests run before full test suite in CI
- Prevents merging code that breaks core functionality
- Fast feedback loop (63 tests vs 444 total tests)
### **Local Development**
```bash
# Check current regression status
mix test.regression
# Update regression baseline after adding features
mix test.baseline
# Manually add newly passing tests to test/passing_tests.json
# Pre-push hook automatically runs regression tests
git push origin feature-branch
```
## Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Install git hooks: `./scripts/setup-git-hooks.sh`
4. Make your changes following the code quality workflow:
- `mix format` (auto-fix formatting)
- Add tests for new functionality
- `mix test.regression` (ensure no regressions)
- `mix credo --strict` (static analysis)
- `mix dialyzer` (type checking)
5. Update regression tests if you fix failing SCION/W3C tests:
- Run `mix test.baseline` to see current status
- Add newly passing tests to `test/passing_tests.json`
6. Ensure all CI checks pass
7. Commit your changes (`git commit -m 'Add amazing feature'`)
8. Push to the branch (pre-push hook will run automatically)
9. Open a Pull Request
### Code Style
- All code is formatted with `mix format`
- Static analysis with Credo (strict mode)
- Type checking with Dialyzer
- Comprehensive test coverage (90%+ maintained)
- Detailed documentation with `@moduledoc` and `@doc`
- Pattern matching preferred over multiple assertions in tests
- Git pre-push hook enforces validation workflow automatically
- Regression tests ensure core functionality never breaks
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- [W3C SCXML Specification](https://www.w3.org/TR/scxml/) - Official specification
- [SCION Test Suite](https://github.com/jbeard4/SCION) - Comprehensive test cases