glyph-parser 0.0.1

Python-like parser for the Glyph programming language
Documentation
//! Error types and error handling for the Glyph parser

use miette::{Diagnostic, SourceSpan};
use thiserror::Error;

/// Result type for parser operations
pub type ParseResult<T> = Result<T, ParseError>;

/// Main error type for parsing errors
#[derive(Error, Debug, Diagnostic)]
#[error("{message}")]
pub struct ParseError {
    /// Error message
    pub message: String,
    /// Error kind
    pub kind: ErrorKind,
    /// Source location
    #[source_code]
    pub source_code: Option<String>,
    /// Span in source
    #[label("{}", self.label_text())]
    pub span: Option<SourceSpan>,
    /// Suggestions for fixing the error
    #[help]
    pub suggestions: Option<String>,
    /// Example of correct syntax
    pub example: Option<String>,
}

/// Different kinds of parse errors
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
    /// Syntax error in source
    SyntaxError,
    /// Indentation error
    IndentationError,
    /// Invalid token
    InvalidToken,
    /// Missing @program decorator
    MissingProgramDecorator,
    /// Invalid import statement
    InvalidImport,
    /// Unsupported Python feature
    UnsupportedFeature,
    /// Type error
    TypeError,
    /// Invalid intrinsic call
    InvalidIntrinsic,
    /// Missing required capability
    MissingCapability,
    /// Invalid pattern in match
    InvalidPattern,
    /// Other validation error
    ValidationError,
}

impl ParseError {
    /// Create a new parse error
    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
            kind,
            source_code: None,
            span: None,
            suggestions: None,
            example: None,
        }
    }

    /// Add source code context
    pub fn with_source(mut self, source: String) -> Self {
        self.source_code = Some(source);
        self
    }

    /// Add span information
    pub fn with_span(mut self, start: usize, len: usize) -> Self {
        self.span = Some((start, len).into());
        self
    }

    /// Add suggestions
    pub fn with_suggestions(mut self, suggestions: Vec<String>) -> Self {
        if !suggestions.is_empty() {
            self.suggestions = Some(suggestions.join("\n"));
        }
        self
    }

    /// Add an example
    pub fn with_example(mut self, example: String) -> Self {
        self.example = Some(example);
        self
    }

    /// Get label text for the error
    fn label_text(&self) -> String {
        match &self.kind {
            ErrorKind::IndentationError => "incorrect indentation here".to_string(),
            ErrorKind::SyntaxError => "syntax error".to_string(),
            ErrorKind::InvalidToken => "invalid token".to_string(),
            ErrorKind::MissingProgramDecorator => "missing @program decorator".to_string(),
            _ => "error here".to_string(),
        }
    }

    // Common error constructors

    /// Create an indentation error
    pub fn indentation_error(line: usize, expected: usize, actual: usize) -> Self {
        Self::new(
            ErrorKind::IndentationError,
            format!(
                "Indentation error on line {}: expected {} spaces, found {}",
                line, expected, actual
            ),
        )
        .with_suggestions(vec![
            format!("Use exactly {} spaces for this indentation level", expected),
            "Ensure you're not mixing tabs and spaces".to_string(),
        ])
        .with_example(format!(
            "def function():\n{}# This line should have {} spaces",
            " ".repeat(expected),
            expected
        ))
    }

    /// Create a tab error
    pub fn tab_error(line: usize) -> Self {
        Self::new(
            ErrorKind::IndentationError,
            format!("Tab character found on line {}. Glyph requires spaces for indentation", line),
        )
        .with_suggestions(vec![
            "Replace all tabs with 4 spaces".to_string(),
            "Configure your editor to use spaces instead of tabs".to_string(),
        ])
    }

    /// Create a missing program decorator error
    pub fn missing_program_decorator() -> Self {
        Self::new(
            ErrorKind::MissingProgramDecorator,
            "Every Glyph program must start with an @program decorator",
        )
        .with_suggestions(vec![
            "Add @program decorator at the beginning of your file".to_string(),
        ])
        .with_example(
            r#"@program(
    name="my-program",
    version="0.1",
    requires=["voice-out"]
)

def main():
    pass"#.to_string(),
        )
    }

    /// Create an unsupported feature error
    pub fn unsupported_feature(feature: &str, alternative: &str) -> Self {
        Self::new(
            ErrorKind::UnsupportedFeature,
            format!("{} is not supported in Glyph", feature),
        )
        .with_suggestions(vec![alternative.to_string()])
    }

    /// Create an invalid intrinsic error
    pub fn invalid_intrinsic(intrinsic: &str, reason: &str) -> Self {
        Self::new(
            ErrorKind::InvalidIntrinsic,
            format!("Invalid call to {}: {}", intrinsic, reason),
        )
    }

    /// Create a missing capability error
    pub fn missing_capability(intrinsic: &str, capability: &str) -> Self {
        Self::new(
            ErrorKind::MissingCapability,
            format!(
                "Cannot use {} without declaring '{}' capability in @program",
                intrinsic, capability
            ),
        )
        .with_suggestions(vec![
            format!("Add '{}' to the requires list in @program", capability),
        ])
        .with_example(format!(
            r#"@program(
    name="my-program",
    version="0.1",
    requires=["{}"]  # Add this capability
)"#,
            capability
        ))
    }

    /// Create an invalid import error
    pub fn invalid_import(import: &str) -> Self {
        Self::new(
            ErrorKind::InvalidImport,
            format!("Invalid import statement: '{}'", import),
        )
        .with_suggestions(vec![
            "Use: import module_name".to_string(),
            "Or: from module_name import function1, function2".to_string(),
        ])
        .with_example("import json\nfrom math import sqrt, pow".to_string())
    }
}

/// Span information for error locations
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Span {
    /// Start position in source
    pub start: usize,
    /// Length of the span
    pub length: usize,
    /// Line number (1-indexed)
    pub line: usize,
    /// Column number (1-indexed)
    pub column: usize,
}

impl Span {
    /// Create a span for a single line
    pub fn line(line: usize) -> Self {
        Self {
            start: 0,
            length: 0,
            line,
            column: 1,
        }
    }

    /// Create a span from start and length
    pub fn new(start: usize, length: usize) -> Self {
        Self {
            start,
            length,
            line: 0,
            column: 0,
        }
    }
}