diff --git a/src/main.rs b/src/main.rs index 99bd744..ffb618c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,41 @@ -use lexer::Token; -use logos::Lexer; -use logos::Logos; +use codespan_reporting::files::SimpleFiles; +use codespan_reporting::term; +use codespan_reporting::term::termcolor::ColorChoice; +use codespan_reporting::term::termcolor::StandardStream; use syntax::parse_syntax; -use utils::ws; -use winnow::prelude::*; -use winnow::Parser; -use crate::syntax::check_syntax; +use crate::syntax::check::check; mod lexer; mod syntax; mod utils; fn main() { - // valid - let input = "load \"./image.png\" | invert | save \"./image_processed.jpg\""; - dbg!(parse_syntax(input)); + let mut files = SimpleFiles::new(); + let mut out_errs = Vec::new(); + + let invalid_toks = "meow | gay $ error!\\"; + let invalid_toks_id = files.add("invalid_toks", invalid_toks); + if let Err(err) = parse_syntax(invalid_toks, invalid_toks_id) { + out_errs.push(err) + } + + let invalid_no_streamer = "| invert | save \"./image_processed.jpg\""; + let invalid_no_streamer_id = files.add("invalid_no_streamer", invalid_no_streamer); + if let Err(mut errs) = check( + parse_syntax(invalid_no_streamer, invalid_no_streamer_id).unwrap(), + invalid_no_streamer, + invalid_no_streamer_id, + ) { + out_errs.append(&mut errs) + } // invalid - let invalid_no_streamer = "| invert | save \"./image_processed.jpg\""; - check_syntax(parse_syntax(invalid_no_streamer), invalid_no_streamer) + let writer = StandardStream::stderr(ColorChoice::Always); + let config = term::Config::default(); + + for err in out_errs { + let writer = &mut writer.lock(); + term::emit(writer, &config, &files, &err.to_diagnostic()).unwrap(); + } } diff --git a/src/syntax/check.rs b/src/syntax/check.rs new file mode 100644 index 0000000..483655a --- /dev/null +++ b/src/syntax/check.rs @@ -0,0 +1,34 @@ +use super::{ + error::{FileId, SyntaxError}, + PipelineElement, PipelineElementKind, +}; + +pub fn check( + syntax: Vec, + raw_source: &str, + file_id: FileId, +) -> Result, Vec> { + let mut errs = Vec::new(); + + if let Err(e_span) = check_missing_streamer(&syntax) { + errs.push(SyntaxError::MissingStreamer(vec![(file_id, e_span)])); + } + + if errs.is_empty() { + Ok(syntax) + } else { + Err(errs) + } +} + +fn check_missing_streamer(syntax: &Vec) -> Result<(), logos::Span> { + if let Some(&PipelineElement { + kind: PipelineElementKind::Pipe, + ref span, + }) = syntax.first() + { + Err(span.clone()) + } else { + Ok(()) + } +} diff --git a/src/syntax/error.rs b/src/syntax/error.rs new file mode 100644 index 0000000..9e572d0 --- /dev/null +++ b/src/syntax/error.rs @@ -0,0 +1,48 @@ +use codespan_reporting::diagnostic::{Diagnostic, Label}; + +pub type FileId = usize; + +/// The enum representing a syntax error, used for error reporting +#[derive(Debug, Clone)] +pub enum SyntaxError { + /// This variant indicates a token that the Lexer didn't recognize + InvalidToken(Vec<(FileId, logos::Span)>), + /// `MissingStreamer` means, that the pipeline starts with a Pipe (`|`), so it has no streamer as input in front of it. + MissingStreamer(Vec<(FileId, logos::Span)>), + /// `MissingSink` means, that the pipeline ends with a Pipe (`|`), meaning that the output can't go anywhere + MissingSink, + /// This indicates a missing filter somewhere in the pipeline, meaning that there's 2 pipes after one another + MissingFilter, + /// A literal cannot be a sink + LiteralAsSink, + /// A literal can't be a filter either + LiteralAsFilter, + /// A literal acting as streamer cannot take arguments + LiteralWithArgs, +} + +impl SyntaxError { + pub fn to_diagnostic(&self) -> Diagnostic { + match self { + Self::InvalidToken(errs) => Diagnostic::error() + .with_message("failed to parse invalid tokens") + .with_labels( + errs.into_iter() + .map(|(file_id, span)| { + Label::primary(*file_id, span.clone()).with_message("invalid token") + }) + .collect(), + ), + Self::MissingStreamer(locs) => Diagnostic::error() + .with_message("pipelines must always start with a streamer") + .with_labels( + locs.into_iter() + .map(|(file_id, span)| { + Label::primary(*file_id, span.clone()).with_message("missing streamer") + }) + .collect(), + ), + _ => todo!(), + } + } +} diff --git a/src/syntax.rs b/src/syntax/mod.rs similarity index 65% rename from src/syntax.rs rename to src/syntax/mod.rs index 1ea4c8b..df3c971 100644 --- a/src/syntax.rs +++ b/src/syntax/mod.rs @@ -1,19 +1,16 @@ -use core::panic; use std::mem; -use codespan_reporting::diagnostic::Diagnostic; -use codespan_reporting::diagnostic::Label; -use codespan_reporting::files::Files; -use codespan_reporting::files::SimpleFile; -use codespan_reporting::files::SimpleFiles; -use codespan_reporting::term; -use codespan_reporting::term::termcolor::ColorChoice; -use codespan_reporting::term::termcolor::StandardStream; use logos::Logos; use logos::Span; use crate::lexer::Token; +use self::error::FileId; +use self::error::SyntaxError; + +pub mod check; +mod error; + #[derive(Debug, Clone, PartialEq)] pub struct PipelineElement { kind: PipelineElementKind, @@ -40,8 +37,9 @@ pub enum CommandPartKind { String(String), } -pub fn parse_syntax(input: &str) -> Vec { +pub fn parse_syntax(input: &str, file_id: FileId) -> Result, SyntaxError> { let lexer = Token::lexer(input); + let mut errs = Vec::new(); let mut r = Vec::new(); @@ -85,7 +83,7 @@ pub fn parse_syntax(input: &str) -> Vec { _ => {} } } else { - panic!("Error at {span:?}") + errs.push((file_id, span)) } } @@ -98,34 +96,9 @@ pub fn parse_syntax(input: &str) -> Vec { }); } - r -} - -pub fn check_syntax(syntax: Vec, raw_source: &str) { - let mut files = SimpleFiles::new(); - let file_id = files.add("input", raw_source); - - if let Some(PipelineElement { - kind: PipelineElementKind::Pipe, - span, - }) = syntax.first() - { - let err = Diagnostic::error() - .with_message("Missing streamer for pipeline") - .with_labels(vec![ - Label::primary(file_id, 0..span.end).with_message("expected streamer") - ]) - .with_notes(vec!["a pipeline can't start with a pipe".to_string()]); - - let writer = StandardStream::stderr(ColorChoice::Always); - let config = codespan_reporting::term::Config::default(); - - term::emit(&mut writer.lock(), &config, &files, &err).unwrap(); + if errs.is_empty() { + Ok(r) + } else { + Err(SyntaxError::InvalidToken(errs)) } } - -#[test] -fn test_simple_parse_pipeline() { - let test_pipeline = "load ./image.png | invert | save ./image_processed.jpg"; - parse_syntax(test_pipeline); -}