iowo/src/syntax/check.rs

214 lines
5.4 KiB
Rust

#[cfg(test)]
#[allow(clippy::unwrap_used, reason = "these are tests. they may unwrap.")]
mod test;
use crate::{
error::{ErrorKind, Errors, SyntaxErrorKind},
syntax::CommandPart,
};
use super::{CommandPartKind, PipelineElement, PipelineElementKind};
pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec<Errors>> {
let mut errs = Vec::new();
if let Err(e_span) = check_missing_streamer(syntax) {
errs.push(Errors::new_single(
ErrorKind::SyntaxError(SyntaxErrorKind::MissingStreamer),
e_span,
));
}
if let Err(err_locs) = check_missing_filters(syntax) {
errs.push(Errors::new(
ErrorKind::SyntaxError(SyntaxErrorKind::MissingFilter),
err_locs,
));
}
if let Err(e_span) = check_missing_sink(syntax) {
errs.push(Errors::new_single(
ErrorKind::SyntaxError(SyntaxErrorKind::MissingSink),
e_span,
));
}
if let Err(e_span) = check_literal_as_sink(syntax) {
errs.push(Errors::new_single(
ErrorKind::SyntaxError(SyntaxErrorKind::LiteralAsSink),
e_span,
));
}
if let Err(err_locs) = check_literal_as_filter(syntax) {
errs.push(Errors::new(
ErrorKind::SyntaxError(SyntaxErrorKind::LiteralAsFilter),
err_locs,
));
}
if let Err(e_span) = check_literal_with_args(syntax) {
errs.push(Errors::new_single(
ErrorKind::SyntaxError(SyntaxErrorKind::LiteralWithArgs),
e_span,
));
}
if errs.is_empty() {
Ok(())
} else {
Err(errs)
}
}
fn check_missing_streamer(syntax: &[PipelineElement]) -> Result<(), logos::Span> {
if let Some(&PipelineElement {
kind: PipelineElementKind::Pipe,
ref span,
}) = syntax.first()
{
Err(span.clone())
} else {
Ok(())
}
}
fn check_missing_filters(syntax: &[PipelineElement]) -> Result<(), Vec<logos::Span>> {
let mut missing_filter_locs = Vec::new();
for i in 0..syntax.len() {
if let (
Some(&PipelineElement {
kind: PipelineElementKind::Pipe,
ref span,
}),
Some(&PipelineElement {
kind: PipelineElementKind::Pipe,
span: ref span1,
}),
) = (syntax.get(i), syntax.get(i + 1))
{
missing_filter_locs.push(span.start..span1.end);
}
}
if missing_filter_locs.is_empty() {
Ok(())
} else {
Err(missing_filter_locs)
}
}
fn check_missing_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> {
if let Some(&PipelineElement {
kind: PipelineElementKind::Pipe,
ref span,
}) = syntax.last()
{
Err(span.clone())
} else {
Ok(())
}
}
fn check_literal_as_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> {
let last_block: Option<&[PipelineElement]> = syntax
.split(|PipelineElement { kind, .. }| 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(_),
..
}
) {
Ok(())
} else {
Err(span.clone())
}
} else {
Ok(())
}
} else {
Ok(())
}
} else {
Ok(())
}
}
fn check_literal_as_filter(syntax: &[PipelineElement]) -> Result<(), Vec<logos::Span>> {
let errs = syntax
.iter()
.take(syntax.len() - 1)
.skip(1)
.filter(|element| {
!matches!(
element,
PipelineElement {
kind: PipelineElementKind::Pipe,
..
}
)
})
.map(|item| {
let PipelineElement {
kind: PipelineElementKind::Command(c),
span,
} = item
else {
return None;
};
let Some(CommandPart { kind, .. }) = c.first() else {
return None;
};
if matches!(kind, CommandPartKind::Word(_)) {
None
} else {
Some(span)
}
})
.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(_),
..
})
)
{
Err(span.clone())
} else {
Ok(())
}
} else {
Ok(())
}
}