Adding New Patterns
This guide explains how to add new patterns to the Ori compiler.
Overview
To add a new pattern:
- Create pattern struct implementing
PatternDefinition - Register pattern in
PatternRegistry - Add tests
- Update documentation
Step 1: Create Pattern Struct
Create a new file in compiler/oric/src/patterns/:
// patterns/take.rs
use crate::patterns::{PatternDefinition, PatternArg, ArgType, TypedArg, EvalArg};
use crate::types::Type;
use crate::eval::Value;
/// take(over: items, count: n) - Take first n items
pub struct TakePattern;
impl PatternDefinition for TakePattern {
fn name(&self) -> &str {
"take"
}
fn arguments(&self) -> &[PatternArg] {
&[
PatternArg {
name: "over",
ty: ArgType::Iterable,
required: true,
default: None,
},
PatternArg {
name: "count",
ty: ArgType::Exact(Type::Int),
required: true,
default: None,
},
]
}
fn type_check(
&self,
args: &[TypedArg],
checker: &mut TypeChecker,
) -> Result<Type, TypeError> {
let over_arg = args.iter().find(|a| a.name.as_str() == "over")
.ok_or(TypeError::MissingArg("over"))?;
let count_arg = args.iter().find(|a| a.name.as_str() == "count")
.ok_or(TypeError::MissingArg("count"))?;
// Verify count is int
checker.unify(&count_arg.ty, &Type::Int)?;
// Result has same type as input
Ok(over_arg.ty.clone())
}
fn evaluate(
&self,
args: &[EvalArg],
_evaluator: &mut Evaluator,
) -> Result<Value, EvalError> {
let over = args.iter().find(|a| a.name.as_str() == "over")
.ok_or(EvalError::MissingArg("over"))?
.value.as_list()?;
let count = args.iter().find(|a| a.name.as_str() == "count")
.ok_or(EvalError::MissingArg("count"))?
.value.as_int()? as usize;
let result: Vec<Value> = over.iter()
.take(count)
.cloned()
.collect();
Ok(Value::List(Arc::new(result)))
}
}
Step 2: Add to Module
Update patterns/mod.rs:
mod take;
pub use take::TakePattern;
Step 3: Register Pattern
Update patterns/registry.rs:
impl PatternRegistry {
pub fn with_builtins(interner: Arc<Interner>) -> Self {
let mut registry = Self::new(interner);
// ... existing patterns ...
// Add new pattern
registry.register(TakePattern);
registry
}
}
Step 4: Add Tests
Create patterns/take_test.rs:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_take_basic() {
let result = eval("take(over: [1, 2, 3, 4, 5], count: 3)");
assert_eq!(result, Value::List(vec![1, 2, 3].into()));
}
#[test]
fn test_take_empty() {
let result = eval("take(over: [], count: 3)");
assert_eq!(result, Value::List(vec![].into()));
}
#[test]
fn test_take_more_than_available() {
let result = eval("take(over: [1, 2], count: 5)");
assert_eq!(result, Value::List(vec![1, 2].into()));
}
#[test]
fn test_take_type_error() {
let err = eval_err("take(over: [1, 2], count: \"not a number\")");
assert!(matches!(err, TypeError::Mismatch { .. }));
}
}
Step 5: Add Documentation
Update docs/ori_lang/0.1-alpha/spec/10-patterns.md:
### take
Takes the first n elements from a collection.
**Signature:**
take(over: [T], count: int) -> [T]
**Arguments:**
- `.over` - Collection to take from
- `.count` - Number of elements to take
**Example:**
```ori
take(over: [1, 2, 3, 4, 5], count: 3) // [1, 2, 3]
## Optional: Add Fusion Support
If your pattern can fuse with others:
```rust
impl PatternDefinition for TakePattern {
// ... other methods ...
fn can_fuse(&self, next: &dyn PatternDefinition) -> bool {
// take can fuse with map
next.name() == "map"
}
fn fuse_with(
&self,
next: &dyn PatternDefinition,
) -> Option<Box<dyn PatternDefinition>> {
if next.name() == "map" {
Some(Box::new(TakeMapPattern::new(self, next)))
} else {
None
}
}
}
Pattern Categories
Data Patterns (function_exp)
Return transformed data:
// map, filter, fold, take, skip, etc.
fn evaluate(&self, args: &[EvalArg], eval: &mut Evaluator) -> Result<Value, EvalError> {
// Transform input and return result
Ok(transformed_value)
}
Control Patterns (function_seq)
Control execution flow:
// run, try, match
fn evaluate(&self, args: &[EvalArg], eval: &mut Evaluator) -> Result<Value, EvalError> {
// Execute in sequence, handle control flow
for expr in sequence {
eval.eval_expr(expr)?;
}
Ok(final_value)
}
Effect Patterns
Require capabilities:
impl PatternDefinition for HttpGetPattern {
fn capabilities(&self) -> Vec<Capability> {
vec![Capability::Http]
}
fn evaluate(&self, args: &[EvalArg], eval: &mut Evaluator) -> Result<Value, EvalError> {
// Verify capability is available
eval.require_capability(Capability::Http)?;
// Perform HTTP request
let url = args.get("url")?.as_string()?;
let response = eval.http_client()?.get(&url)?;
Ok(Value::String(response))
}
}
Checklist
Before submitting:
- Pattern struct implements
PatternDefinition - Registered in
PatternRegistry::with_builtins - Type checking validates all arguments
- Evaluation handles edge cases
- Unit tests cover basic usage
- Unit tests cover error cases
- Documentation added to spec
- Example added to guide (optional)
- Fusion rules defined (if applicable)
- Capabilities declared (if applicable)
Common Mistakes
- Forgetting required arguments - Always check required args in type_check and evaluate
- Wrong return type - Ensure type_check returns same type as evaluate produces
- Not handling empty input - Test with empty lists/collections
- Missing capability check - Declare and verify capabilities for effects
- Thread safety - Ensure pattern is
Send + Sync