diff --git a/example.today b/example.today new file mode 100644 index 0000000..f098ab8 --- /dev/null +++ b/example.today @@ -0,0 +1,44 @@ +TASK foo + +NOTE Spielerunde +DATE sun 22:00 -- 24:00 +DATE sun 22:00 -- 00:00 +DATE sun 22:00 -- +2h +DATE (wd = sun) 22:00 -- 24:00 +DATE 2021-11-07 22:00 -- 24:00; +w +DATE 2021-11-07 22:00 -- +2h; +w + +NOTE daily +DATE * +DATE (true) +DATE 2021-11-07; +d + +NOTE on weekends +DATE (wd = sat | wd = sun) + +NOTE weekends +DATE sat -- sun +DATE 2021-11-06 -- 2021-11-07; +w +DATE 2021-11-06 -- +d; +w +DATE (wd = sat) -- +d + +NOTE last of each month +DATE (m = 1) -d + +BIRTHDAY Max +BDATE 1987-05-14 + +BIRTHDAY Martha +BDATE ?-09-21 + +NOTE Physics lecture +DATE wed 14:00 -- 15:30 +DATE 2021-05-07 14:00 -- 15:30 +FROM 2021-04-14 +UNTIL 2021-07-28 +EXCEPT 2021-05-06 + This is a description of the event. It might mention further information + that doesn't fit into the title. + + It may even contain multiple paragraphs, separated by an arbitrary amount + of empty lines, as long as every non-empty line is indented. diff --git a/src/main.rs b/src/main.rs index 48a0c19..9143364 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,11 @@ use std::path::PathBuf; +use std::process; use structopt::StructOpt; +use crate::parser::Parser; +use crate::source::SourceFiles; + mod commands; mod parser; mod source; @@ -14,5 +18,26 @@ pub struct Opt { fn main() { let opt = Opt::from_args(); - println!("{:#?}", opt); + + let mut files = SourceFiles::new(); + + let (file, content) = match files.load(&opt.file) { + Ok(result) => result, + Err(e) => { + eprintln!("Failed to load file: {}", e); + process::exit(1); + } + }; + + let mut parser = Parser::new(file, content); + + let commands = match parser.parse() { + Ok(result) => result, + Err(es) => { + files.emit_all(&es); + process::exit(1); + } + }; + + println!("{:#?}", commands); } diff --git a/src/parser.rs b/src/parser.rs index f3da2ce..0fc1366 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,11 +1,23 @@ use std::cmp::min; use std::process::Command; +use codespan_reporting::diagnostic::{Diagnostic, Label}; + use crate::source::{SourceFile, SourceSpan}; +// TODO Add warnings for things like trailing whitespace + #[derive(Debug)] pub struct ParseError(SourceSpan, String); +impl From<&ParseError> for Diagnostic { + fn from(e: &ParseError) -> Self { + Self::error() + .with_message(&e.1) + .with_labels(vec![Label::primary(e.0.file_id(), e.0.range())]) + } +} + type ParseResult = Result; #[derive(Debug)] @@ -102,6 +114,7 @@ impl<'a> Parser<'a> { } fn parse_command(&mut self) -> ParseResult { + self.critical(self.offset, "idk, ded")?; todo!() } } diff --git a/src/source.rs b/src/source.rs index c7fbc6c..4674a9b 100644 --- a/src/source.rs +++ b/src/source.rs @@ -3,16 +3,12 @@ use std::ops::Range; use std::path::{Path, PathBuf}; use std::{fs, io}; +use codespan_reporting::diagnostic::Diagnostic; use codespan_reporting::files::SimpleFiles; +use codespan_reporting::term; +use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; -#[derive(Debug, thiserror::Error)] -pub enum LoadFileError { - #[error("file already loaded")] - FileAlreadyLoaded, - #[error("error loading file: {0}")] - IoError(#[from] io::Error), -} - +#[derive(Debug)] pub struct SourceFiles { files: SimpleFiles, files_by_path: HashMap, @@ -26,20 +22,31 @@ impl SourceFiles { } } - pub fn load(&mut self, path: &Path) -> Result { + pub fn load(&mut self, path: &Path) -> io::Result<(SourceFile, &str)> { let canonical_path = path.canonicalize()?; - if self.files_by_path.contains_key(&canonical_path) { - return Err(LoadFileError::FileAlreadyLoaded); - } + let file_id = if let Some(&file_id) = self.files_by_path.get(&canonical_path) { + file_id + } else { + let name = path.as_os_str().to_string_lossy().into_owned(); + let content = fs::read_to_string(path)?; + self.files.add(name, content) + }; - let name = path.as_os_str().to_string_lossy().into_owned(); - let content = fs::read_to_string(path)?; - let file_id = self.files.add(name, content); - Ok(SourceFile(file_id)) + let content = self.files.get(file_id).unwrap().source() as &str; + Ok((SourceFile(file_id), content)) } - pub fn content_of(&self, file: SourceFile) -> Option<&str> { - self.files.get(file.0).ok().map(|sf| sf.source() as &str) + pub fn emit_all<'a, T>(&self, diagnostics: &'a [T]) + where + &'a T: Into>, + { + let stderr = StandardStream::stderr(ColorChoice::Auto); + let config = term::Config::default(); + + for diagnostic in diagnostics { + let diagnostic: Diagnostic = diagnostic.into(); + term::emit(&mut stderr.lock(), &config, &self.files, &diagnostic); + } } } @@ -49,7 +56,7 @@ pub struct SourceFile(usize); impl SourceFile { pub fn span(&self, range: Range) -> SourceSpan { SourceSpan { - file: self.0, + file_id: self.0, start: range.start, end: range.end, } @@ -58,7 +65,17 @@ impl SourceFile { #[derive(Clone, Copy, Debug)] pub struct SourceSpan { - file: usize, + file_id: usize, start: usize, end: usize, } + +impl SourceSpan { + pub fn file_id(&self) -> usize { + self.file_id + } + + pub fn range(&self) -> Range { + self.start..self.end + } +}