From 90f74c82bcbb74081afae76be84c4fb1ddfde565 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 18 Nov 2021 23:46:41 +0100 Subject: [PATCH] Continue parsing the pest output --- src/commands.rs | 6 ++ src/parse.rs | 217 ++++++++++++++++++++++++++++++++++++--- src/parse/todayfile.pest | 8 +- 3 files changed, 216 insertions(+), 15 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index ee6a793..66d56e7 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -209,6 +209,9 @@ pub struct Done { pub struct Task { pub title: String, pub when: Vec, + pub from: Option, + pub until: Option, + pub except: Vec, pub done: Vec, pub desc: Option, } @@ -217,6 +220,9 @@ pub struct Task { pub struct Note { pub title: String, pub when: Vec, // Should not be empty? + pub from: Option, + pub until: Option, + pub except: Vec, pub desc: Option, } diff --git a/src/parse.rs b/src/parse.rs index 3b44cb5..ee1b550 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,10 +1,11 @@ use std::result; -use pest::error::Error; +use chrono::NaiveDate; +use pest::error::{Error, ErrorVariant}; use pest::iterators::Pair; -use pest::Parser; +use pest::{Parser, Span}; -use crate::commands::{Command, Task}; +use crate::commands::{Birthday, BirthdaySpec, Command, Done, Note, Spec, Task}; #[derive(pest_derive::Parser)] #[grammar = "parse/todayfile.pest"] @@ -12,23 +13,213 @@ struct TodayfileParser; type Result = result::Result>; -pub fn parse(input: &str) -> Result> { - let mut pairs = TodayfileParser::parse(Rule::file, input)?; - let file = pairs.next().unwrap(); - let commands = file.into_inner(); - commands.map(parse_command).collect() +fn fail, T>(span: Span, message: S) -> Result { + Err(Error::new_from_span( + ErrorVariant::CustomError { + message: message.into(), + }, + span, + )) +} + +fn parse_title(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::title); + Ok(p.into_inner().next().unwrap().as_str().to_string()) +} + +fn parse_datum(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::datum); + let date_span = p.as_span(); + let mut p = p.into_inner(); + + let year = p.next().unwrap().as_str().parse().unwrap(); + let month = p.next().unwrap().as_str().parse().unwrap(); + let day = p.next().unwrap().as_str().parse().unwrap(); + + assert_eq!(p.next(), None); + + match NaiveDate::from_ymd_opt(year, month, day) { + Some(date) => Ok(date), + None => fail(date_span, "invalid date"), + } +} + +#[derive(Default)] +struct Options { + when: Vec, + from: Option, + until: Option, + except: Vec, + done: Vec, +} + +fn parse_date(p: Pair, opts: &mut Options) -> Result<()> { + todo!() +} + +fn parse_from(p: Pair, opts: &mut Options) -> Result<()> { + todo!() +} + +fn parse_until(p: Pair, opts: &mut Options) -> Result<()> { + todo!() +} + +fn parse_except(p: Pair, opts: &mut Options) -> Result<()> { + todo!() +} + +fn parse_done(p: Pair, opts: &mut Options) -> Result<()> { + todo!() +} + +fn parse_options(p: Pair) -> Result { + assert!(matches!( + p.as_rule(), + Rule::task_options | Rule::note_options + )); + + let mut opts = Options::default(); + for opt in p.into_inner() { + match opt.as_rule() { + Rule::date => parse_date(opt, &mut opts)?, + Rule::from => parse_from(opt, &mut opts)?, + Rule::until => parse_until(opt, &mut opts)?, + Rule::except => parse_except(opt, &mut opts)?, + Rule::done => parse_done(opt, &mut opts)?, + _ => unreachable!(), + } + } + + Ok(opts) +} + +fn parse_indented_line(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::indented_line); + Ok(match p.into_inner().next() { + Some(rest) => { + assert_eq!(rest.as_rule(), Rule::rest); + rest.as_str().to_string() + } + None => "".to_string(), + }) +} + +fn parse_description(p: Pair) -> Result> { + assert_eq!(p.as_rule(), Rule::description); + + let lines = p + .into_inner() + .map(parse_indented_line) + .collect::>>()?; + + // TODO Strip whitespace prefix + + let desc = lines.join("\n"); + Ok(Some(desc).filter(|s| !s.is_empty())) +} + +fn parse_task(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::task); + let mut p = p.into_inner(); + + let title = parse_title(p.next().unwrap())?; + let opts = parse_options(p.next().unwrap())?; + let desc = parse_description(p.next().unwrap())?; + + assert_eq!(p.next(), None); + + Ok(Task { + title, + when: opts.when, + from: opts.from, + until: opts.until, + except: opts.except, + done: opts.done, + desc, + }) +} + +fn parse_note(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::task); + let mut p = p.into_inner(); + + let title = parse_title(p.next().unwrap())?; + let opts = parse_options(p.next().unwrap())?; + let desc = parse_description(p.next().unwrap())?; + + assert_eq!(p.next(), None); + assert!(opts.done.is_empty()); + + Ok(Note { + title, + when: opts.when, + from: opts.from, + until: opts.until, + except: opts.except, + desc, + }) +} + +fn parse_bdatum(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::bdatum); + let span = p.as_span(); + let p = p.into_inner().collect::>(); + assert!(p.len() == 2 || p.len() == 3); + + let (y, m, d, year_known) = if p.len() == 3 { + let y = p[0].as_str().parse().unwrap(); + let m = p[1].as_str().parse().unwrap(); + let d = p[2].as_str().parse().unwrap(); + (y, m, d, true) + } else { + let m = p[0].as_str().parse().unwrap(); + let d = p[1].as_str().parse().unwrap(); + (0, m, d, false) + }; + + let date = match NaiveDate::from_ymd_opt(y, m, d) { + Some(date) => Ok(date), + None => fail(span, "invalid date"), + }?; + + Ok(BirthdaySpec { date, year_known }) +} + +fn parse_bdate(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::bdate); + parse_bdatum(p.into_inner().next().unwrap()) +} + +fn parse_birthday(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::birthday); + let mut p = p.into_inner(); + + let title = parse_title(p.next().unwrap())?; + let when = parse_bdate(p.next().unwrap())?; + let desc = parse_description(p.next().unwrap())?; + + Ok(Birthday { title, when, desc }) } fn parse_command(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::command); + + let p = p.into_inner().next().unwrap(); match p.as_rule() { Rule::task => parse_task(p).map(Command::Task), - Rule::note => todo!(), - Rule::birthday => todo!(), + Rule::note => parse_note(p).map(Command::Note), + Rule::birthday => parse_birthday(p).map(Command::Birthday), _ => unreachable!(), } } -fn parse_task(p: Pair) -> Result { - dbg!(p); - todo!() +pub fn parse(input: &str) -> Result> { + let mut pairs = TodayfileParser::parse(Rule::file, input)?; + let file = pairs.next().unwrap(); + file.into_inner() + // For some reason, the EOI in `file` always gets captured + .take_while(|p| p.as_rule() == Rule::command) + .map(parse_command) + .collect() } diff --git a/src/parse/todayfile.pest b/src/parse/todayfile.pest index 61e4e00..79a44a9 100644 --- a/src/parse/todayfile.pest +++ b/src/parse/todayfile.pest @@ -79,17 +79,21 @@ indented_line = { NEWLINE | WHITESPACE ~ rest ~ eol } description = { indented_line* } +task_options = { (date | from | until | except | done)* } + task = { "TASK" ~ title - ~ (date | from | until | except | done)* + ~ task_options ~ description } +note_options = { (date | from | until | except)* } + note = { "NOTE" ~ title - ~ (date | from | until | except | done)* + ~ note_options ~ description }