5-Layer Architecture

The Ori formatter uses a layered architecture inspired by modern formatters (rustfmt, Gleam, TypeScript). Each layer has a single responsibility and well-defined interfaces.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│  Layer 5: Orchestration (formatter/)                            │
│  • Coordinates all layers                                       │
│  • Renders inline/broken/stacked based on width                 │
│  • Main Formatter struct and public API                         │
├─────────────────────────────────────────────────────────────────┤
│  Layer 4: Breaking Rules (rules/)                               │
│  • 8 Ori-specific rules (1 integrated, 7 infrastructure-only)   │
│  • Only ParenthesesRule is invoked by the formatter              │
├─────────────────────────────────────────────────────────────────┤
│  Layer 3: Shape Tracking (shape/)                               │
│  • Width tracking through recursion                             │
│  • Independent breaking for nested constructs                   │
├─────────────────────────────────────────────────────────────────┤
│  Layer 2: Container Packing (packing/)                          │
│  • When to inline vs break containers                           │
│  • Packing strategies: FitOrOnePerLine, FitOrPackMultiple, etc. │
├─────────────────────────────────────────────────────────────────┤
│  Layer 1: Token Spacing (spacing/)                              │
│  • O(1) declarative spacing rules                               │
│  • TokenCategory → SpaceAction lookup                           │
└─────────────────────────────────────────────────────────────────┘

Layer Responsibilities

LayerModuleResponsibilityKey Types
1spacing/Token spacing (space, none, newline)SpaceAction, TokenCategory, RulesMap
2packing/Container packing decisionsPacking, ConstructKind, Separator
3shape/Width tracking through recursionShape
4rules/Ori-specific breaking rules8 rule structs (1 integrated: ParenthesesRule)
5formatter/Orchestration and renderingFormatter

Data Flow

Source → Parse → AST


         ┌──────────────────┐
         │ Width Calculator │ (bottom-up traversal, uses shape/)
         └────────┬─────────┘


         ┌──────────────────┐
         │    Formatter     │ (top-down rendering)
         │                  │
         │  ┌─────────────┐ │
         │  │ spacing/    │─┼─→ token spacing
         │  │ packing/    │─┼─→ container decisions
         │  │ rules/      │─┼─→ special construct handling
         │  └─────────────┘ │
         └────────┬─────────┘


           Formatted Output

Design Principles

1. Separation of Concerns

Each layer handles exactly one aspect of formatting:

  • Spacing: Only token-to-token spacing decisions
  • Packing: Only inline vs break decisions for containers
  • Shape: Only width tracking and fit checks
  • Rules: Only special-case breaking logic
  • Orchestration: Only coordination and rendering

2. Information Flow

Information flows down the layers:

  • Orchestration queries rules for special cases
  • Rules query shape for fit checks
  • Shape queries packing for strategy
  • All layers may use spacing for token output

No layer calls upward, preventing circular dependencies.

3. Declarative Where Possible

  • Layer 1 (Spacing): Pure declarative rules (token pair → action)
  • Layer 2 (Packing): Declarative strategies (construct → packing)
  • Layers 3-5: Imperative but with clear decision boundaries

Adding or Modifying Rules

Token Spacing (Layer 1)

Add new spacing rules in spacing/rules.rs:

// Example: Add spacing after new keyword
(TokenCategory::MyKeyword, TokenCategory::Ident) => SpaceAction::Space,

Packing Strategies (Layer 2)

Add new constructs in packing/construct.rs:

// Add new construct kind
pub enum ConstructKind {
    // ...existing...
    MyNewConstruct,
}

Special Breaking Rules (Layer 4)

Create a new rule module:

  1. Create rules/my_rule.rs
  2. Define the rule struct with decision logic
  3. Export from rules/mod.rs
  4. Integrate with orchestration

See Breaking Rules for details on each rule.

Layer Documentation

Module Structure

ori_fmt/src/
├── spacing/           # Layer 1: Token spacing
│   ├── action.rs      # SpaceAction enum
│   ├── category.rs    # TokenCategory (117 variants)
│   ├── matcher.rs     # TokenMatcher patterns
│   ├── rules.rs       # Declarative rules (SPACE_RULES)
│   ├── lookup.rs      # Hybrid RulesMap (O(1) exact + fallback)
│   └── tests.rs
├── packing/           # Layer 2: Container packing
│   ├── strategy.rs    # Packing enum, determine_packing()
│   ├── construct.rs   # ConstructKind
│   ├── separator.rs   # Separator enum
│   ├── simple.rs      # Simple item detection
│   └── tests.rs
├── shape/             # Layer 3: Shape tracking
│   ├── core.rs        # Shape struct
│   └── tests.rs
├── rules/             # Layer 4: Breaking rules
│   ├── method_chain.rs   # MethodChainRule (infrastructure)
│   ├── short_body.rs     # ShortBodyRule
│   ├── boolean_break.rs  # BooleanBreakRule
│   ├── chained_else_if.rs# ChainedElseIfRule
│   ├── nested_for.rs     # NestedForRule
│   ├── parentheses.rs    # ParenthesesRule
│   ├── loop_rule.rs      # LoopRule
│   └── tests.rs
├── formatter/         # Layer 5: Orchestration
│   ├── mod.rs         # Main Formatter<'a, I> struct
│   ├── inline.rs      # Single-line rendering
│   ├── broken.rs      # Multi-line rendering
│   ├── stacked.rs     # Always-stacked constructs
│   ├── helpers.rs     # Collection helpers
│   ├── patterns.rs    # Pattern rendering
│   ├── literals.rs    # Literal rendering
│   └── tests.rs
├── declarations/      # Module-level formatting
│   ├── functions.rs   # Function declarations
│   ├── types.rs       # Type definitions
│   ├── traits.rs      # Trait definitions
│   ├── impls.rs       # Impl blocks
│   ├── imports.rs     # Use statements
│   ├── configs.rs     # Config variables
│   ├── comments.rs    # Comment handling
│   ├── tests_fmt.rs   # Test formatting
│   └── parsed_types/mod.rs # Parsed type formatting
├── comments/mod.rs    # Comment preservation
├── context/mod.rs     # FormatContext<E> (column/indent tracking)
├── emitter/mod.rs     # StringEmitter output abstraction
├── width/             # Width calculation (WidthCalculator)
│   ├── mod.rs         # WidthCalculator with LRU cache
│   ├── calls.rs       # Function/method call widths
│   ├── collections.rs # List/map/tuple widths
│   ├── compounds/mod.rs # Compound expression widths
│   ├── control.rs     # Control flow widths
│   ├── helpers/mod.rs # Width helper functions
│   ├── literals/mod.rs # Literal widths
│   ├── operators/mod.rs # Operator widths
│   ├── patterns/mod.rs # Pattern widths
│   ├── wrappers.rs    # Wrapper type widths
│   └── tests.rs
└── incremental/mod.rs # Incremental formatting

Relationship to Spec

The formatting spec (docs/ori_lang/v2026/spec/16-formatting.md) defines what the canonical format is. This layer architecture explains how the implementation achieves that format.

Spec SectionImplementation Layer
Spacing table (lines 25-47)Layer 1 (spacing/)
Width-based rules (lines 58-92)Layer 2 (packing/), Layer 3 (shape/)
Construct-specific rulesLayer 4 (rules/)
Overall formattingLayer 5 (formatter/)