API Reference¶
RuleEngine¶
The main class for loading and applying rules.
Construction¶
from rerum import RuleEngine, ARITHMETIC_PRELUDE
# Empty engine
engine = RuleEngine()
# With prelude
engine = RuleEngine(fold_funcs=ARITHMETIC_PRELUDE)
# From DSL
engine = RuleEngine.from_dsl("@add-zero: (+ ?x 0) => :x")
# From file
engine = RuleEngine.from_file("rules.rules")
# From Python list
engine = RuleEngine.from_rules([[["+", ["?", "x"], 0], [":", "x"]]])
Fluent API¶
engine = (RuleEngine()
.with_prelude(ARITHMETIC_PRELUDE)
.load_dsl("@add-zero: (+ ?x 0) => :x")
.load_file("more.rules")
.disable_group("experimental"))
Simplification¶
# Basic
result = engine(expr)
result = engine.simplify(expr)
# With options
result = engine(expr, strategy="bottomup", groups=["algebra"])
# With tracing
result, trace = engine(expr, trace=True)
Pattern Matching¶
from rerum import Bindings, NoMatch
# Match returns Bindings or NoMatch
if bindings := engine.match("(+ ?a ?b)", expr):
print(bindings["a"], bindings["b"])
Rule Application¶
# Apply one rule
result, metadata = engine.apply_once(expr)
# Find matching rules
for metadata, bindings in engine.rules_matching(expr):
print(f"{metadata.name}: {bindings.to_dict()}")
Inspection¶
len(engine) # Number of rules
"add-zero" in engine # Check if rule exists
rule, meta = engine["add-zero"] # Get by name
engine.list_rules() # DSL format strings
engine.groups() # All group names
for rule, meta in engine:
print(meta.name)
Groups¶
engine.groups() # Get all group names
engine.disable_group("name") # Disable a group
engine.enable_group("name") # Enable a group
# Use specific groups
engine(expr, groups=["algebra", "folding"])
Combining Engines¶
# Union
combined = engine1 | engine2
engine1 |= engine2
# Sequencing (phase1 to fixpoint, then phase2)
pipeline = engine1 >> engine2
result = pipeline(expr)
Expression Builder (E)¶
from rerum import E
# Parse s-expression
expr = E("(+ x (* 2 y))")
# Build programmatically
expr = E.op("+", "x", E.op("*", 2, "y"))
# Variables
x, y = E.vars("x", "y")
expr = E.op("+", x, y)
# Constants
n = E.const(42)
Bindings¶
from rerum import Bindings, NoMatch
# Dict-like access
bindings["x"]
bindings.get("x", default)
"x" in bindings
bindings.to_dict()
# NoMatch is falsy
if bindings := engine.match(pattern, expr):
# matched
RewriteTrace¶
result, trace = engine(expr, trace=True)
# Properties
trace.initial # Initial expression
trace.final # Final expression
trace.steps # List of RewriteStep
# Formatting
str(trace) # Verbose
trace.format("compact") # Single line
trace.format("rules") # Just rule names
trace.format("chain") # Step-by-step
# Statistics
len(trace) # Number of steps
trace.summary() # Brief summary
trace.rule_counts() # Dict of rule -> count
trace.rules_applied() # List of rule names
# Iteration
for step in trace:
print(step.metadata.name)
# Serialization
trace.to_dict() # JSON-serializable dict
# Boolean
if trace: # True if any rules applied
...
Preludes¶
from rerum import (
ARITHMETIC_PRELUDE, # +, -, *, /, ^
MATH_PRELUDE, # arithmetic + sin, cos, exp, log, sqrt, abs
PREDICATE_PRELUDE, # >, <, =, const?, var?, list?, and, or, not
FULL_PRELUDE, # arithmetic + predicates
MINIMAL_PRELUDE, # +, * only
NO_PRELUDE, # empty
)
# Helpers for custom preludes
from rerum import nary_fold, binary_only, unary_only
my_prelude = {
"+": nary_fold(0, lambda a, b: a + b),
"max": binary_only(max),
"neg": unary_only(lambda x: -x),
}
Low-Level Functions¶
from rerum import (
match, # Pattern matching
instantiate, # Skeleton instantiation
rewriter, # Create simplifier function
parse_sexpr, # Parse s-expression string
format_sexpr, # Format to s-expression string
)
# Direct pattern matching
bindings = match(pattern, expr, [])
if bindings != "failed":
result = instantiate(skeleton, bindings, fold_funcs)
# Create rewriter function
simplify = rewriter(rules, fold_funcs=ARITHMETIC_PRELUDE)
result = simplify(expr)
# S-expression I/O
expr = parse_sexpr("(+ x 1)")
text = format_sexpr(expr)