use miette::{Diagnostic, SourceSpan};
use thiserror::Error;
pub type ParseResult<T> = Result<T, ParseError>;
#[derive(Error, Debug, Diagnostic)]
#[error("{message}")]
pub struct ParseError {
pub message: String,
pub kind: ErrorKind,
#[source_code]
pub source_code: Option<String>,
#[label("{}", self.label_text())]
pub span: Option<SourceSpan>,
#[help]
pub suggestions: Option<String>,
pub example: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
SyntaxError,
IndentationError,
InvalidToken,
MissingProgramDecorator,
InvalidImport,
UnsupportedFeature,
TypeError,
InvalidIntrinsic,
MissingCapability,
InvalidPattern,
ValidationError,
}
impl ParseError {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
message: message.into(),
kind,
source_code: None,
span: None,
suggestions: None,
example: None,
}
}
pub fn with_source(mut self, source: String) -> Self {
self.source_code = Some(source);
self
}
pub fn with_span(mut self, start: usize, len: usize) -> Self {
self.span = Some((start, len).into());
self
}
pub fn with_suggestions(mut self, suggestions: Vec<String>) -> Self {
if !suggestions.is_empty() {
self.suggestions = Some(suggestions.join("\n"));
}
self
}
pub fn with_example(mut self, example: String) -> Self {
self.example = Some(example);
self
}
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(),
}
}
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
))
}
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(),
])
}
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(),
)
}
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()])
}
pub fn invalid_intrinsic(intrinsic: &str, reason: &str) -> Self {
Self::new(
ErrorKind::InvalidIntrinsic,
format!("Invalid call to {}: {}", intrinsic, reason),
)
}
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
))
}
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())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Span {
pub start: usize,
pub length: usize,
pub line: usize,
pub column: usize,
}
impl Span {
pub fn line(line: usize) -> Self {
Self {
start: 0,
length: 0,
line,
column: 1,
}
}
pub fn new(start: usize, length: usize) -> Self {
Self {
start,
length,
line: 0,
column: 0,
}
}
}