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; #[derive(Debug, Clone, PartialEq)] pub struct PipelineElement { kind: PipelineElementKind, span: Span, } #[derive(Debug, Clone, PartialEq)] pub enum PipelineElementKind { Pipe, Command(Vec), } #[derive(Debug, Clone, PartialEq)] pub struct CommandPart { kind: CommandPartKind, span: Span, } #[derive(Debug, Clone, PartialEq)] pub enum CommandPartKind { Word(String), Integer(i64), Float(f64), String(String), } pub fn parse_syntax(input: &str) -> Vec { let lexer = Token::lexer(input); let mut r = Vec::new(); let mut partial_command: Vec = Vec::new(); for (tok, span) in lexer.spanned().into_iter() { if let Ok(tok) = tok { match tok { Token::Pipe => { if !partial_command.is_empty() { let span = partial_command.first().unwrap().span.start ..partial_command.last().unwrap().span.end; r.push(PipelineElement { kind: PipelineElementKind::Command(mem::replace( &mut partial_command, Vec::new(), )), span, }); } r.push(PipelineElement { kind: PipelineElementKind::Pipe, span, }); } Token::Word(word) => partial_command.push(CommandPart { kind: CommandPartKind::Word(word.to_owned()), span, }), Token::IntLiteral(int) => partial_command.push(CommandPart { kind: CommandPartKind::Integer(int), span, }), Token::FloatLiteral(float) => partial_command.push(CommandPart { kind: CommandPartKind::Float(float), span, }), Token::StringLiteral(string) => partial_command.push(CommandPart { kind: CommandPartKind::String(string), span, }), _ => {} } } else { panic!("Error at {span:?}") } } if !partial_command.is_empty() { let span = partial_command.first().unwrap().span.start..partial_command.last().unwrap().span.end; r.push(PipelineElement { kind: PipelineElementKind::Command(mem::replace(&mut partial_command, Vec::new())), span, }); } 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(); } } #[test] fn test_simple_parse_pipeline() { let test_pipeline = "load ./image.png | invert | save ./image_processed.jpg"; parse_syntax(test_pipeline); }