# Design Document: Wasot Selector Parser

This document outlines the design for the [selector](./selectors.md) parser. For implementation notes, see [parser notes](./selector_parser_notes.md). For how specificity is calculated, see [specificity](./specificity.md).

## 1\. Overview

This document outlines the design and implementation of a parser for the Wasot selector language. The selector syntax is heavily inspired by CSS Selectors and is designed to query a stream of Wasot events (blocks and text).

The goal is to parse a selector string into a strongly-typed [Abstract Syntax Tree (AST)](./ast.md) in Rust. This AST can then be used by a separate evaluation engine to match elements in a Wasot document. The implementation leverages the `pest` parser generator for its expressive grammar definition and robust parsing capabilities.

The process involves three core components:

1.  A **Pest Grammar** (`.pest` file) that formally defines the selector syntax.
2.  A Rust **Abstract Syntax Tree (AST)** that models the parsed selector in a typed, easy-to-use structure.
3.  An **AST Builder** module in Rust that transforms the raw parse tree from `pest` into our custom AST.

-----

## 2\. Goals & Scope

### 2.1. Goals

  * **Parse the full specified selector syntax:** The parser must correctly handle all basic selectors, argument selectors, child selectors, and combinators defined in the language specification.
  * **Produce a strongly-typed AST:** The output of the parsing process should be a well-defined Rust struct/enum tree, not a generic parse tree. This makes the data easy and safe to work with in downstream code.
  * **Provide clear error handling:** Leverage `pest` to report syntax errors with meaningful messages.
  * **Be extensible:** The design should make it straightforward to add new features in the future, such as class selectors (`.class`) or additional attribute matchers (`^=`, `*=`, etc.).

### 2.2. Non-Goals

  * **Selector Evaluation:** This document covers **parsing only**. The logic for actually *using* the AST to match against a Wasot document is outside the scope of this implementation.
  * **Class Selectors:** As per the specification, class selectors (`.class`) are not included in this initial version.
  * **Advanced `nth-child` Syntax:** The parser will handle numeric arguments like `:nth-child(3)` but not the full `An+B` syntax (e.g., `2n+1`, `odd`, `even`) in this iteration.

-----

## 3\. Component 1: The Pest Grammar

The grammar is the heart of the parser. It defines the valid syntax for a Wasot selector. The following grammar is designed to be unambiguous and efficient, correctly handling details like combinator precedence and recursive selectors.

### 3.1. Grammar File (`selector.pest`)

```rust
// Wasot selector grammar for pest parser - Extended Version

// -----------------------------------------------------------------------------
// Boilerplate & Primitives
// -----------------------------------------------------------------------------

// Main entry point - a selector is the entire input
selector = { SOI ~ compound_selector ~ EOI }

// Whitespace is NOT ignored by default, as it's a combinator.
// We handle it explicitly inside rules where it's allowed but insignificant.
ws = { " " | "\t" | "\n" | "\r" }

// An identifier for types, IDs, and attribute keys.
// Must start with a letter or underscore.
identifier = @{
    (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "-" | "_")*
}

// A string value, used in attribute selectors.
string = @{ "\"" ~ ( !"\"" ~ ANY )* ~ "\"" }

// -----------------------------------------------------------------------------
// Selector Structure
// -----------------------------------------------------------------------------

// A compound selector is one or more simple selectors connected by combinators.
// e.g., `paragraph > #intro:first-child`
compound_selector = {
    simple_selector ~ (combinator ~ simple_selector)*
}

// A simple selector is a chain of basic selectors without a combinator.
// e.g., `quote#special[author="me"]:first-child`
// It can be a type, universal, ID, attribute, or pseudo-class selector.
simple_selector = {
    (type_selector | universal_selector | id_selector | attribute_selector | pseudo_class_selector)+
}

// -----------------------------------------------------------------------------
// Basic Selectors (as parts of a simple_selector)
// -----------------------------------------------------------------------------

// Universal selector matches any element. e.g., `*`
universal_selector = { "*" }

// Type selector matches elements by their type name. e.g., `paragraph`
type_selector = { identifier }

// ID selector matches an element by its ID. e.g., `#main-content`
id_selector = { "#" ~ identifier }

// -----------------------------------------------------------------------------
// Argument (Attribute) Selectors
// -----------------------------------------------------------------------------

// Matches an element based on its arguments (attributes). e.g., `[key="value"]`
attribute_selector = {
    "[" ~ ws* ~ (
        attribute_exact_match |
        attribute_negated_presence |
        attribute_presence
    ) ~ ws* ~ "]"
}

// e.g., `[argument]` - checks for presence of a positional or keyword argument.
attribute_presence = { identifier }

// e.g., `[!argument]` - checks for the absence of an argument.
attribute_negated_presence = { "!" ~ identifier }

// e.g., `[key="value"]` or `[key=value]` - checks for an exact value match.
attribute_exact_match = {
    identifier ~ ws* ~ "=" ~ ws* ~ (string | identifier)
}

// -----------------------------------------------------------------------------
// Child (Pseudo-Class) Selectors
// -----------------------------------------------------------------------------

// Matches an element based on information that isn't in the element itself,
// but is relative to its siblings or parents. Starts with a colon.
pseudo_class_selector = {
    ":" ~ (
        not_pseudo |
        nth_functional_pseudo |
        simple_pseudo
    )
}

// Simple keyword-based pseudo-classes.
// e.g., `:first-child`, `:last-block`
simple_pseudo = @{
    "first-child" | "first-block" | "first-text" |
    "last-child"  | "last-block"  | "last-text"
}

// Functional pseudo-classes that take a numeric argument `n`.
// e.g., `:nth-child(3)`
nth_functional_pseudo = {
    ("nth-child" | "nth-block" | "nth-text") ~
    ws* ~ "(" ~ ws* ~ argument_n ~ ws* ~ ")"
}
// For now, `n` is just a number. This can be expanded later for `2n+1`, etc.
argument_n = @{ ASCII_DIGIT+ }

// The negation pseudo-class, which recursively takes another selector.
// e.g., `:not(#main)`
not_pseudo = {
    "not" ~ ws* ~ "(" ~ ws* ~ compound_selector ~ ws* ~ ")"
}

// -----------------------------------------------------------------------------
// Combinators
// -----------------------------------------------------------------------------

// Connects two simple selectors.
// IMPORTANT: Order matters. We check for specific symbols first,
// then fall back to the whitespace descendant combinator.
combinator = {
    child |
    adjacent_sibling |
    general_sibling |
    descendant
}

// Descendant combinator (one or more spaces). e.g., `div span`
descendant = { ws+ }

// Child combinator. e.g., `ul > li`
child = { ws* ~ ">" ~ ws* }

// Adjacent Sibling combinator. e.g., `h1 + p`
adjacent_sibling = { ws* ~ "+" ~ ws* }

// General Sibling combinator. e.g., `h1 ~ p`
general_sibling = { ws* ~ "~" ~ ws* }
```

-----

## 4\. Component 2: The Abstract Syntax Tree (AST)

The AST translates the parsed syntax into a native Rust data structure. This is the primary interface for any code that uses the parser's output. Key design choices include using `enum`s to represent choices in the grammar (like different combinators) and `Box<T>` to handle recursive data structures (`:not(selector)`) without causing an infinite type size.

### 4.1. AST Module (`ast.rs`)

```rust
// The top-level structure of a full selector
#[derive(Debug)]
pub struct CompoundSelector {
    pub base: SimpleSelector,
    pub parts: Vec<(Combinator, SimpleSelector)>,
}

// A single selector part without a combinator, e.g., `div#id[attr]`
#[derive(Debug, Default)]
pub struct SimpleSelector {
    pub tag: Option<TagSelector>,
    pub id: Option<String>,
    pub attributes: Vec<AttributeSelector>,
    pub pseudo_classes: Vec<PseudoClassSelector>,
}

// Represents either a specific tag name or the universal selector `*`
#[derive(Debug)]
pub enum TagSelector {
    TypeName(String),
    Universal,
}

// All possible combinators
#[derive(Debug)]
pub enum Combinator {
    Descendant,
    Child,
    AdjacentSibling,
    GeneralSibling,
}

// Argument/Attribute selectors, e.g., `[key="value"]`
#[derive(Debug)]
pub enum AttributeSelector {
    Presence(String),
    NegatedPresence(String),
    ExactMatch { key: String, value: String },
}

// Pseudo-class selectors, e.g., `:first-child` or `:not(...)`
#[derive(Debug)]
pub enum PseudoClassSelector {
    Simple(String),
    Nth(String, u32),
    Not(Box<CompoundSelector>), // Boxed to handle recursion without infinite size
}
```

-----

## 5\. Component 3: The AST Builder

The builder is the bridge between `pest`'s raw parse tree (`Pair`s) and our strongly-typed AST. It's a set of recursive functions that walk the `Pair` tree and construct the corresponding AST nodes.

### 5.1. Builder Module (`builder.rs`)

```rust
use pest::iterators::Pair;
use crate::ast::*; // Import the AST definitions

// Assuming your grammar file is loaded with this derive macro
#[derive(pest_derive::Parser)]
#[grammar = "selector.pest"]
pub struct WasotParser;

// Main error type for parsing
type PestError = pest::error::Error<Rule>;

pub fn build_ast_from_pair(pair: Pair<Rule>) -> Result<CompoundSelector, PestError> {
    match pair.as_rule() {
        Rule::compound_selector => {
            let mut inner = pair.into_inner();

            // The first part is always a simple_selector
            let base = build_simple_selector(inner.next().unwrap())?;

            // The rest are (combinator, simple_selector) pairs
            let mut parts = Vec::new();
            while let Some(combinator_pair) = inner.next() {
                let combinator = build_combinator(combinator_pair)?;
                let simple_selector = build_simple_selector(inner.next().unwrap())?;
                parts.push((combinator, simple_selector));
            }

            Ok(CompoundSelector { base, parts })
        }
        _ => Err(PestError::new_from_span(
            pest::error::ErrorVariant::CustomError {
                message: format!("Expected a compound_selector, found {:?}", pair.as_rule()),
            },
            pair.as_span(),
        )),
    }
}

fn build_simple_selector(pair: Pair<Rule>) -> Result<SimpleSelector, PestError> {
    let mut selector = SimpleSelector::default();
    for part in pair.into_inner() {
        match part.as_rule() {
            Rule::type_selector => selector.tag = Some(TagSelector::TypeName(part.as_str().to_string())),
            Rule::universal_selector => selector.tag = Some(TagSelector::Universal),
            Rule::id_selector => selector.id = Some(part.into_inner().next().unwrap().as_str().to_string()),
            Rule::attribute_selector => selector.attributes.push(build_attribute_selector(part)?),
            Rule::pseudo_class_selector => selector.pseudo_classes.push(build_pseudo_class_selector(part)?),
            _ => unreachable!(), // simple_selector should only contain these parts
        }
    }
    Ok(selector)
}

fn build_combinator(pair: Pair<Rule>) -> Result<Combinator, PestError> {
    // The combinator rule has the actual combinator type as its inner rule
    let inner_rule = pair.into_inner().next().unwrap().as_rule();
    Ok(match inner_rule {
        Rule::descendant => Combinator::Descendant,
        Rule::child => Combinator::Child,
        Rule::adjacent_sibling => Combinator::AdjacentSibling,
        Rule::general_sibling => Combinator::GeneralSibling,
        _ => unreachable!(),
    })
}

fn build_attribute_selector(pair: Pair<Rule>) -> Result<AttributeSelector, PestError> {
    let inner = pair.into_inner().next().unwrap(); // e.g., attribute_presence
    Ok(match inner.as_rule() {
        Rule::attribute_presence => AttributeSelector::Presence(inner.as_str().to_string()),
        Rule::attribute_negated_presence => {
            let key = inner.into_inner().next().unwrap().as_str().to_string();
            AttributeSelector::NegatedPresence(key)
        },
        Rule::attribute_exact_match => {
            let mut parts = inner.into_inner();
            let key = parts.next().unwrap().as_str().to_string();
            let value = parts.next().unwrap().as_str().trim_matches('"').to_string();
            AttributeSelector::ExactMatch { key, value }
        }
        _ => unreachable!(),
    })
}

fn build_pseudo_class_selector(pair: Pair<Rule>) -> Result<PseudoClassSelector, PestError> {
    let mut inner = pair.into_inner().next().unwrap();
    Ok(match inner.as_rule() {
        Rule::simple_pseudo => PseudoClassSelector::Simple(inner.as_str().to_string()),
        Rule::nth_functional_pseudo => {
            let mut parts = inner.into_inner();
            let name = parts.next().unwrap().as_str().to_string();
            let arg = parts.next().unwrap().as_str().parse::<u32>().unwrap();
            PseudoClassSelector::Nth(name, arg)
        }
        // This is the recursive part!
        Rule::not_pseudo => {
            let inner_selector_pair = inner.into_inner().find(|p| p.as_rule() == Rule::compound_selector).unwrap();
            let inner_ast = build_ast_from_pair(inner_selector_pair)?;
            PseudoClassSelector::Not(Box::new(inner_ast))
        }
        _ => unreachable!(),
    })
}
```

-----

## 6\. Example Usage

This section provides a complete `main.rs` file demonstrating how to use the components to parse a selector string and print the resulting AST.

### 6.1. Main Application (`main.rs`)

```rust
use pest::Parser;

mod ast;
mod builder;

use builder::{WasotParser, Rule, build_ast_from_pair};

fn main() {
    let selector_str = r#"doc > section#intro[lang="en"] :first-child:not(p + span)"#;

    println!("Parsing selector: '{}'", selector_str);

    match WasotParser::parse(Rule::selector, selector_str) {
        Ok(mut pairs) => {
            // Get the top-level compound_selector rule
            let selector_pair = pairs.next().unwrap().into_inner().next().unwrap();
            
            // Build the AST
            let ast = build_ast_from_pair(selector_pair).expect("AST building failed");

            // Print the resulting AST
            println!("\nSuccessfully parsed AST:");
            dbg!(ast);
        }
        Err(e) => {
            eprintln!("\nParse failed: {}", e);
        }
    }
}
```

-----

## 7\. Future Work & Extensibility

The current design provides a solid foundation for future enhancements. The modular nature of the grammar and the AST builder makes adding new features straightforward.

  * **Class Selectors (`.class`):**

    1.  Add `class_selector = { "." ~ identifier }` to the grammar.
    2.  Add `class_selector` as an option in the `simple_selector` rule.
    3.  Add a `classes: Vec<String>` field to the `SimpleSelector` AST struct.
    4.  Update the `build_simple_selector` function to handle the new `class_selector` rule.

  * **Extended Attribute Selectors:**

    1.  Add new rules like `attribute_contains = { identifier ~ ws* ~ "*=" ~ ws* ~ value }` to the grammar.
    2.  Add a corresponding variant to the `AttributeSelector` AST enum.
    3.  Update the `build_attribute_selector` function.

  * **Advanced `nth-child` Syntax:**

    1.  Modify the `argument_n` rule in the grammar to parse `An+B` syntax.
    2.  Update the `PseudoClassSelector::Nth` enum variant to store the `A` and `B` components.
    3.  Update the `build_pseudo_class_selector` function to parse this new structure.