hypen-parser 0.1.2

A Rust implementation of the Hypen DSL parser using Chumsky
Documentation
# Error Handling in Hypen Parser

## Overview

The Hypen parser uses **Chumsky** for parsing with **Ariadne** for error reporting, providing:
- ✅ Precise error locations (line, column)
- ✅ Context-aware error messages
- ✅ Beautiful colored output
- ✅ Multiple error reporting

## How Errors Look

### Basic Error Output

When using raw Chumsky errors:

```rust
use hypen_parser::parse_component;

let input = r#"Text("Hello"#;
match parse_component(input) {
    Err(errors) => {
        for error in errors {
            println!("{}", error);
        }
    }
    Ok(_) => {}
}
```

**Output:**
```
found '"' expected identifier, or ')'
```

### Pretty Error Output (with Ariadne)

When using Ariadne's pretty printing:

```rust
use hypen_parser::{parse_component, print_parse_errors};

let input = r#"Text("Hello"#;
if let Err(errors) = parse_component(input) {
    print_parse_errors("myfile.hypen", input, &errors);
}
```

**Output:**
```
Error: Parse error
   ╭─[myfile.hypen:1:6]
   │
 1 │ Text("Hello
   │      ┬
   │      ╰── found '"' expected identifier, or ')'
───╯
```

## Error Types & Examples

### 1. Unclosed String

**Input:**
```hypen
Text("Hello
```

**Error:**
```
Error: Parse error
   ╭─[example.hypen:1:6]
   │
 1 │ Text("Hello
   │      ┬
   │      ╰── found '"' expected identifier, or ')'
───╯
```

### 2. Missing Closing Parenthesis

**Input:**
```hypen
Text("Hello"
```

**Error:**
```
Error: Parse error
   ╭─[example.hypen:1:13]
   │
 1 │ Text("Hello"
   │             │
   │             ╰─ found end of input expected ',', or ')'
───╯
```

### 3. Missing Closing Brace

**Input:**
```hypen
Column {
    Text("Hello")
```

**Error:**
```
Error: Parse error
   ╭─[example.hypen:2:19]
   │
 2 │     Text("Hello")
   │                   │
   │                   ╰─ found end of input expected '{', '.', any, or '}'
───╯
```

### 4. Invalid Syntax in Arguments

**Input:**
```hypen
Text(key: , value: 123)
```

**Error:**
```
Error: Parse error
   ╭─[example.hypen:1:9]
   │
 1 │ Text(key: , value: 123)
   │         ┬
   │         ╰── found ':' expected identifier, ',', or ')'
───╯
```

### 5. Complex Nested Error

**Input:**
```hypen
Column {
    Row {
        Button("Sign In")
            .padding(16
        Text("Footer")
    }
}
```

**Error:**
```
Error: Parse error
   ╭─[example.hypen:4:24]
   │
 4 │             .padding(16
   │                        │
   │                        ╰─ found end of input expected ',', or ')'
───╯
```

## Error API

### Basic API

```rust
use hypen_parser::parse_component;
use chumsky::error::Rich;

let result: Result<ComponentSpecification, Vec<Rich<char>>>
    = parse_component(input);
```

The error type `Rich<char>` provides:
- `span()` - Location in source (start, end)
- `found()` - What token was found
- `expected()` - What tokens were expected
- `reason()` - Error reason/context

### Pretty Printing API

```rust
use hypen_parser::print_parse_errors;

// Print to stdout with colors
print_parse_errors("filename.hypen", source_code, &errors);
```

```rust
use hypen_parser::format_parse_errors;

// Get as string for logging
let error_text = format_parse_errors("filename.hypen", source_code, &errors);
log::error!("{}", error_text);
```

## Best Practices

### 1. Production Error Handling

```rust
use hypen_parser::{parse_component, print_parse_errors};
use std::fs;

fn parse_file(path: &str) -> Result<ComponentSpecification, String> {
    let source = fs::read_to_string(path)
        .map_err(|e| format!("Failed to read file: {}", e))?;

    parse_component(&source).map_err(|errors| {
        // Log errors with full context
        let mut buffer = Vec::new();
        for error in &errors {
            use ariadne::*;
            Report::build(ReportKind::Error, path, error.span().start)
                .with_label(
                    Label::new((path, error.span().into_range()))
                        .with_message(format!("{}", error))
                        .with_color(Color::Red),
                )
                .finish()
                .write((path, Source::from(&source)), &mut buffer)
                .unwrap();
        }
        String::from_utf8(buffer).unwrap()
    })
}
```

### 2. Multiple File Parsing

```rust
fn parse_multiple_files(files: &[&str]) -> Vec<Result<ComponentSpecification, String>> {
    files.iter().map(|&file| {
        let source = std::fs::read_to_string(file).unwrap();
        parse_component(&source).map_err(|errors| {
            format_parse_errors(file, &source, &errors)
        })
    }).collect()
}
```

### 3. User-Friendly Error Messages

```rust
match parse_component(input) {
    Ok(component) => {
        println!("✓ Successfully parsed {}", component.name);
    }
    Err(errors) => {
        eprintln!("✗ Failed to parse component:");
        eprintln!();
        print_parse_errors("input", input, &errors);
        eprintln!();
        eprintln!("Hint: Check for unclosed strings, parentheses, or braces");
    }
}
```

### 4. Error Recovery

For parsing multiple components, continue on errors:

```rust
use hypen_parser::parse_components;

fn parse_all_or_partial(input: &str) -> Vec<ComponentSpecification> {
    match parse_components(input) {
        Ok(components) => components,
        Err(errors) => {
            // Log errors but return what we could parse
            eprintln!("Partial parse - some components may be missing");
            print_parse_errors("input", input, &errors);
            vec![] // or try to recover partial results
        }
    }
}
```

## Error Categories

Chumsky errors fall into these categories:

### Unexpected Token
- Found something the parser didn't expect
- Most common error type
- Usually indicates syntax error

### Unexpected End of Input
- Parser expected more tokens but file ended
- Usually means missing closing delimiter

### Expected One Of
- Parser expected specific tokens but found something else
- Helpful for understanding valid syntax at that position

## Improving Error Messages

Chumsky's error messages can be customized:

```rust
// In parser.rs - add labels to parsers
component_parser()
    .labelled("component")
    .then(/* ... */)
```

This makes errors say "expected component" instead of lower-level token names.

## Testing Error Handling

Always test error cases:

```rust
#[test]
fn test_unclosed_string_error() {
    let input = r#"Text("Hello"#;
    let result = parse_component(input);

    assert!(result.is_err());
    let errors = result.unwrap_err();
    assert!(!errors.is_empty());

    // Check error mentions the problem
    let error_msg = format!("{}", errors[0]);
    assert!(error_msg.contains("expected") || error_msg.contains("found"));
}
```

## Error Recovery Strategies

For IDE/LSP integration, consider:

1. **Partial parsing** - Parse what you can, skip invalid sections
2. **Error tokens** - Insert placeholder nodes for invalid syntax
3. **Multiple passes** - Try to recover and continue parsing
4. **Incremental parsing** - Only reparse changed sections

Chumsky supports error recovery through:
- `.recover_with()` combinators
- Custom error recovery strategies
- Fallback parsers

## Performance Considerations

- Error reporting is fast (microseconds)
- Pretty printing with Ariadne adds minimal overhead
- For bulk parsing, consider batching error reports
- Cache parsed results to avoid re-parsing on every error

## Summary

✅ **Chumsky provides**: Precise error locations and context
✅ **Ariadne provides**: Beautiful, colored error reports
✅ **Best practice**: Always use `print_parse_errors` for user-facing errors
✅ **Testing**: Include both success and error test cases
✅ **Production**: Log errors with full context and source location