From a07a031e0ceab721488a2a839ca436803754eb6a Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Sat, 18 Nov 2023 21:11:43 +0100 Subject: [PATCH] finish syntax checks --- src/main.rs | 4 +- src/syntax/check.rs | 120 ++++++++++++++++++++++++++++++++++++++- src/syntax/check/test.rs | 32 ++++++++++- src/syntax/error.rs | 45 ++++++++++----- 4 files changed, 182 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3be6cd6..744fe30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ mod namespace; mod syntax; fn main() { - let args = dbg!(Args::parse()); + let args = Args::parse(); let syntax = parse_syntax(&args.text); @@ -50,7 +50,7 @@ fn main() { let diags = errs.into_iter().map(|err| err.to_diagnostic(input_id)); for diag in diags { - term::emit(writer, &config, &files, &diag); + term::emit(writer, &config, &files, &diag).unwrap(); } } } diff --git a/src/syntax/check.rs b/src/syntax/check.rs index dd074cb..ec4dc22 100644 --- a/src/syntax/check.rs +++ b/src/syntax/check.rs @@ -1,7 +1,9 @@ #[cfg(test)] mod test; -use super::{error::SyntaxError, PipelineElement, PipelineElementKind}; +use crate::syntax::CommandPart; + +use super::{error::SyntaxError, CommandPartKind, PipelineElement, PipelineElementKind}; pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec> { let mut errs = Vec::new(); @@ -11,13 +13,25 @@ pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec> { } if let Err(err_locs) = check_missing_filters(syntax) { - errs.push(SyntaxError::MissingFilter(err_locs)) + errs.push(SyntaxError::MissingFilter(err_locs)); } if let Err(e_span) = check_missing_sink(syntax) { errs.push(SyntaxError::MissingSink(vec![e_span])); } + if let Err(e_span) = check_literal_as_sink(syntax) { + errs.push(SyntaxError::LiteralAsSink(vec![e_span])); + } + + if let Err(err_locs) = check_literal_as_filter(syntax) { + errs.push(SyntaxError::LiteralAsFilter(err_locs)); + } + + if let Err(e_span) = check_literal_with_args(syntax) { + errs.push(SyntaxError::LiteralWithArgs(vec![e_span])); + } + if errs.is_empty() { Ok(()) } else { @@ -74,3 +88,105 @@ fn check_missing_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> { Ok(()) } } + +fn check_literal_as_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> { + let last_block: Option<&[PipelineElement]> = syntax + .split(|PipelineElement { kind, span: _ }| kind == &PipelineElementKind::Pipe) + .last(); + + // there HAS to be a better way to do this... this is HORRIBLE + if let Some(last_block) = last_block { + if let Some(PipelineElement { + kind: PipelineElementKind::Command(parts), + span, + }) = last_block.first() + { + if let Some(first_part) = parts.first() { + if !matches!( + first_part, + CommandPart { + kind: CommandPartKind::Word(_), + span: _ + } + ) { + Err(span.clone()) + } else { + Ok(()) + } + } else { + Ok(()) + } + } else { + Ok(()) + } + } else { + Ok(()) + } +} + +fn check_literal_as_filter(syntax: &[PipelineElement]) -> Result<(), Vec> { + let errs = syntax + .iter() + .filter(|element| { + !matches!( + element, + PipelineElement { + kind: PipelineElementKind::Pipe, + span: _ + } + ) + }) + .map(|item| { + let PipelineElement { + kind: PipelineElementKind::Command(c), + span, + } = item + else { + return None; + }; + + let Some(CommandPart { kind, span: _ }) = c.first() else { + return None; + }; + + if !matches!(kind, CommandPartKind::Word(_)) { + Some(span) + } else { + None + } + }) + .skip(1) + .take(syntax.len() - 1) + .filter_map(|err| err.map(Clone::clone)) + .collect::>(); + + if errs.is_empty() { + Ok(()) + } else { + Err(errs) + } +} + +fn check_literal_with_args(syntax: &[PipelineElement]) -> Result<(), logos::Span> { + if let Some(PipelineElement { + kind: PipelineElementKind::Command(c), + span, + }) = syntax.first() + { + if c.len() > 1 + && !matches!( + c.first(), + Some(CommandPart { + kind: CommandPartKind::Word(_), + span: _ + }) + ) + { + Err(span.clone()) + } else { + Ok(()) + } + } else { + Ok(()) + } +} diff --git a/src/syntax/check/test.rs b/src/syntax/check/test.rs index c885303..44a3523 100644 --- a/src/syntax/check/test.rs +++ b/src/syntax/check/test.rs @@ -1,5 +1,8 @@ use crate::syntax::{ - check::{check_missing_filters, check_missing_sink, check_missing_streamer}, + check::{ + check_literal_as_filter, check_literal_as_sink, check_literal_with_args, + check_missing_filters, check_missing_sink, check_missing_streamer, + }, parse_syntax, }; @@ -26,3 +29,30 @@ fn test_check_missing_sink() { assert_eq!(check_missing_sink(&syntax), Err(14..15)) } + +#[test] +fn test_check_literal_as_sink() { + let test_data = "meow | test | 3"; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!(check_literal_as_sink(&syntax), Err(14..15)) +} + +#[test] +fn test_check_literal_as_filter() { + let test_data = "meow | \"gay\" | 42 | 3.14 | uwu"; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!( + check_literal_as_filter(&syntax), + Err(vec![7..12, 15..17, 20..24]) + ) +} + +#[test] +fn test_check_literal_with_args() { + let test_data = "14 12 | sink"; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!(check_literal_with_args(&syntax), Err(0..5)) +} diff --git a/src/syntax/error.rs b/src/syntax/error.rs index e4cdaff..7d5ee56 100644 --- a/src/syntax/error.rs +++ b/src/syntax/error.rs @@ -12,13 +12,14 @@ pub enum SyntaxError { /// This indicates a missing filter somewhere in the pipeline, meaning that there's 2 pipes after one another MissingFilter(Vec), /// A literal cannot be a sink, TODO - LiteralAsSink, + LiteralAsSink(Vec), /// A literal can't be a filter either, TODO - LiteralAsFilter, + LiteralAsFilter(Vec), /// A literal acting as streamer cannot take arguments, TODO - LiteralWithArgs, + LiteralWithArgs(Vec), } +// TODO: much better and more complex errors, with suggestions for fixes impl SyntaxError { pub fn to_diagnostic(&self, file_id: usize) -> Diagnostic { match self { @@ -32,31 +33,47 @@ impl SyntaxError { .collect(), ), Self::MissingStreamer(locs) => Diagnostic::error() - .with_message("pipelines must always start with a streamer") + .with_message("pipelines must always start with a provider") .with_labels( locs.iter() - .map(|span| { - Label::primary(file_id, span.clone()).with_message("missing streamer") - }) + .map(|span| Label::primary(file_id, span.clone())) .collect(), ), Self::MissingFilter(locs) => Diagnostic::error() - .with_message("missing filters in pipelines") + .with_message("missing filters in pipeline") .with_labels( locs.iter() - .map(|span| { - Label::primary(file_id, span.clone()).with_message("no filter here") - }) + .map(|span| Label::primary(file_id, span.clone())) .collect(), ), Self::MissingSink(locs) => Diagnostic::error() - .with_message("pipelines cannot end on a pipe") + .with_message("pipelines need to end in a sink") .with_labels( locs.iter() - .map(|span| Label::primary(file_id, span.clone()).with_message("no sink")) + .map(|span| Label::primary(file_id, span.clone())) + .collect(), + ), + Self::LiteralAsSink(locs) => Diagnostic::error() + .with_message("literals cannot be sinks") + .with_labels( + locs.iter() + .map(|span| Label::primary(file_id, span.clone())) + .collect(), + ), + Self::LiteralAsFilter(locs) => Diagnostic::error() + .with_message("literals cannot be filters") + .with_labels( + locs.iter() + .map(|span| Label::primary(file_id, span.clone())) + .collect(), + ), + Self::LiteralWithArgs(locs) => Diagnostic::error() + .with_message("literals cannot take arguments") + .with_labels( + locs.iter() + .map(|span| Label::primary(file_id, span.clone())) .collect(), ), - _ => unimplemented!(), } } }