finish syntax checks

This commit is contained in:
Schrottkatze 2023-11-18 21:11:43 +01:00
parent 2db2ef2ea1
commit a07a031e0c
4 changed files with 182 additions and 19 deletions

View file

@ -16,7 +16,7 @@ mod namespace;
mod syntax; mod syntax;
fn main() { fn main() {
let args = dbg!(Args::parse()); let args = Args::parse();
let syntax = parse_syntax(&args.text); let syntax = parse_syntax(&args.text);
@ -50,7 +50,7 @@ fn main() {
let diags = errs.into_iter().map(|err| err.to_diagnostic(input_id)); let diags = errs.into_iter().map(|err| err.to_diagnostic(input_id));
for diag in diags { for diag in diags {
term::emit(writer, &config, &files, &diag); term::emit(writer, &config, &files, &diag).unwrap();
} }
} }
} }

View file

@ -1,7 +1,9 @@
#[cfg(test)] #[cfg(test)]
mod 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<SyntaxError>> { pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec<SyntaxError>> {
let mut errs = Vec::new(); let mut errs = Vec::new();
@ -11,13 +13,25 @@ pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec<SyntaxError>> {
} }
if let Err(err_locs) = check_missing_filters(syntax) { 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) { if let Err(e_span) = check_missing_sink(syntax) {
errs.push(SyntaxError::MissingSink(vec![e_span])); 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() { if errs.is_empty() {
Ok(()) Ok(())
} else { } else {
@ -74,3 +88,105 @@ fn check_missing_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> {
Ok(()) 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<logos::Span>> {
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::<Vec<logos::Span>>();
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(())
}
}

View file

@ -1,5 +1,8 @@
use crate::syntax::{ 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, parse_syntax,
}; };
@ -26,3 +29,30 @@ fn test_check_missing_sink() {
assert_eq!(check_missing_sink(&syntax), Err(14..15)) 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))
}

View file

@ -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 /// This indicates a missing filter somewhere in the pipeline, meaning that there's 2 pipes after one another
MissingFilter(Vec<logos::Span>), MissingFilter(Vec<logos::Span>),
/// A literal cannot be a sink, TODO /// A literal cannot be a sink, TODO
LiteralAsSink, LiteralAsSink(Vec<logos::Span>),
/// A literal can't be a filter either, TODO /// A literal can't be a filter either, TODO
LiteralAsFilter, LiteralAsFilter(Vec<logos::Span>),
/// A literal acting as streamer cannot take arguments, TODO /// A literal acting as streamer cannot take arguments, TODO
LiteralWithArgs, LiteralWithArgs(Vec<logos::Span>),
} }
// TODO: much better and more complex errors, with suggestions for fixes
impl SyntaxError { impl SyntaxError {
pub fn to_diagnostic(&self, file_id: usize) -> Diagnostic<usize> { pub fn to_diagnostic(&self, file_id: usize) -> Diagnostic<usize> {
match self { match self {
@ -32,31 +33,47 @@ impl SyntaxError {
.collect(), .collect(),
), ),
Self::MissingStreamer(locs) => Diagnostic::error() 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( .with_labels(
locs.iter() locs.iter()
.map(|span| { .map(|span| Label::primary(file_id, span.clone()))
Label::primary(file_id, span.clone()).with_message("missing streamer")
})
.collect(), .collect(),
), ),
Self::MissingFilter(locs) => Diagnostic::error() Self::MissingFilter(locs) => Diagnostic::error()
.with_message("missing filters in pipelines") .with_message("missing filters in pipeline")
.with_labels( .with_labels(
locs.iter() locs.iter()
.map(|span| { .map(|span| Label::primary(file_id, span.clone()))
Label::primary(file_id, span.clone()).with_message("no filter here")
})
.collect(), .collect(),
), ),
Self::MissingSink(locs) => Diagnostic::error() Self::MissingSink(locs) => Diagnostic::error()
.with_message("pipelines cannot end on a pipe") .with_message("pipelines need to end in a sink")
.with_labels( .with_labels(
locs.iter() 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(), .collect(),
), ),
_ => unimplemented!(),
} }
} }
} }