Compare commits

..

20 commits

Author SHA1 Message Date
abf4d5a502 Silence remaining warnings 2024-11-21 02:14:35 +01:00
22eec53c5a Move warnings to Cargo.toml 2024-11-21 02:14:12 +01:00
4001d0653b Satisfy clippy 2024-08-03 03:31:37 +02:00
cb470d201e Update dependencies 2023-02-11 22:24:15 +01:00
4529f383fe Update chrono 2023-02-11 22:12:58 +01:00
64c41b1073 Satisfy clippy 2023-02-11 21:50:38 +01:00
f3792fae64 Add marks to show which span a reminder belongs to 2022-11-01 16:41:40 +01:00
23b0a5e5fc Use pratt parser for prefix logic 2022-11-01 15:14:30 +01:00
11d9a2f1c7 Switch to pratt parser 2022-11-01 14:55:08 +01:00
f01c3818c0 Ignore some false positives 2022-10-31 18:06:49 +01:00
c5a2e5dccb Fix some warnings 2022-10-31 17:16:27 +01:00
57da1026c2 Use cove lints 2022-10-31 17:07:10 +01:00
36d8082c9d Update dependencies 2022-10-31 17:03:00 +01:00
aaa3537e90 Update dependencies 2022-05-19 14:00:33 +02:00
bc0d2481c8 Switch from structopt to clap 2022-05-19 13:56:09 +02:00
a63529b972 Increase default --range by one day 2022-05-04 11:17:25 +02:00
972a590ba9 Satisfy clippy 2022-05-02 12:57:21 +02:00
f42cf01a15 Fix cli arg anchoring 2022-05-02 12:49:03 +02:00
74433eccbe Highlight current birthdays 2022-05-02 11:50:59 +02:00
f231fee508 Document Done fields 2022-05-02 11:37:39 +02:00
26 changed files with 1373 additions and 919 deletions

View file

@ -4,6 +4,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
### Added
- Marks to show which span a reminder belongs to
### Changed
- Birthdays for current day are now highlighted
- Default value for `--range` argument
### Fixed
- `--date` accepting incomplete expressions
## 0.2.0 - 2022-03-18
### Added

742
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,16 +4,32 @@ version = "0.2.0"
edition = "2021"
[dependencies]
chrono = "0.4.19"
chrono = "0.4.23"
clap = { version = "4.1.4", features = ["derive"] }
codespan-reporting = "0.11.1"
colored = "2.0.0"
computus = "1.0.0"
directories = "4.0.1"
edit = "0.1.3"
pest = "2.1.3"
pest_derive = "2.1.0"
promptly = "0.3.0"
structopt = "0.3.26"
termcolor = "1.1.3"
thiserror = "1.0.30"
edit = "0.1.4"
pest = "2.5.5"
pest_derive = "2.5.5"
promptly = "0.3.1"
termcolor = "1.2.0"
thiserror = "1.0.38"
tzfile = { git = "https://github.com/Garmelon/tzfile.git", branch = "tzdir" }
[lints]
rust.unsafe_code = { level = "forbid", priority = 1 }
rust.future_incompatible = "warn"
rust.rust_2018_idioms = "warn"
rust.noop_method_call = "warn"
rust.single_use_lifetimes = "warn"
rust.trivial_numeric_casts = "warn"
rust.unused = "warn"
rust.unused_crate_dependencies = "warn"
rust.unused_extern_crates = "warn"
rust.unused_import_braces = "warn"
rust.unused_lifetimes = "warn"
rust.unused_qualifications = "warn"
clippy.all = "warn"
clippy.use_self = "warn"

View file

@ -3,9 +3,9 @@ use std::str::FromStr;
use std::{process, result};
use chrono::{NaiveDate, NaiveDateTime};
use clap::Parser;
use codespan_reporting::files::SimpleFile;
use directories::ProjectDirs;
use structopt::StructOpt;
use crate::eval::{self, DateRange, Entry, EntryMode};
use crate::files::cli::{CliDate, CliIdent, CliRange};
@ -24,77 +24,76 @@ mod print;
mod show;
mod util;
#[derive(Debug, StructOpt)]
#[derive(Debug, clap::Parser)]
pub struct Opt {
/// File to load
#[structopt(short, long, parse(from_os_str))]
#[clap(short, long)]
file: Option<PathBuf>,
/// Overwrite the current date
#[structopt(short, long, default_value = "t")]
#[clap(short, long, default_value = "t")]
date: String,
/// Range of days to focus on
#[structopt(short, long, default_value = "t-2d--t+13d")]
#[clap(short, long, default_value = "t-2d--t+2w")]
range: String,
#[structopt(subcommand)]
#[clap(subcommand)]
command: Option<Command>,
}
#[derive(Debug, StructOpt)]
#[derive(Debug, clap::Subcommand)]
pub enum Command {
/// Shows individual entries in detail
#[structopt(alias = "s")]
#[clap(alias = "s")]
Show {
/// Entries and days to show
#[structopt(required = true)]
#[clap(required = true)]
identifiers: Vec<String>,
},
/// Create a new entry based on a template
#[structopt(alias = "n")]
#[clap(alias = "n")]
New {
#[structopt(subcommand)]
#[clap(subcommand)]
template: Template,
},
/// Marks one or more entries as done
#[structopt(alias = "d")]
#[clap(alias = "d")]
Done {
/// Entries to mark as done
#[structopt(required = true)]
#[clap(required = true)]
entries: Vec<usize>,
},
/// Marks one or more entries as canceled
#[structopt(alias = "c")]
#[clap(alias = "c")]
Cancel {
/// Entries to mark as done
#[structopt(required = true)]
#[clap(required = true)]
entries: Vec<usize>,
},
/// Edits or creates a log entry
#[structopt(alias = "l")]
#[clap(alias = "l")]
Log {
#[structopt(default_value = "t")]
#[clap(default_value = "t")]
date: String,
},
/// Reformats all loaded files
Fmt,
}
// TODO Add templates for tasks and notes
#[derive(Debug, StructOpt)]
#[derive(Debug, clap::Subcommand)]
pub enum Template {
/// Adds a task
#[structopt(alias = "t")]
#[clap(alias = "t")]
Task {
/// If specified, the task is dated to this date
date: Option<String>,
},
/// Adds a note
#[structopt(alias = "n")]
#[clap(alias = "n")]
Note {
/// If specified, the note is dated to this date
date: Option<String>,
},
/// Adds an undated task marked as done today
#[structopt(alias = "d")]
#[clap(alias = "d")]
Done,
}
@ -222,7 +221,7 @@ fn run_with_files(opt: Opt, files: &mut Files) -> Result<()> {
}
pub fn run() {
let opt = Opt::from_args();
let opt = Opt::parse();
let mut files = Files::new();
if let Err(e) = load_files(&opt, &mut files) {

View file

@ -40,14 +40,15 @@ impl<'a, F> Eprint<'a, F> for Error
where
F: Files<'a, FileId = FileSource>,
{
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) {
match self {
Error::Eval(e) => e.eprint(files, config),
Error::ArgumentParse { file, error } => error.eprint(file, config),
Error::ArgumentEval { file, error } => error.eprint(file, config),
Error::NoSuchEntry(n) => eprintln!("No entry with number {n}"),
Error::NoSuchLog(date) => eprintln!("No log for {date}"),
Error::NotATask(ns) => {
Self::Eval(e) => e.eprint(files, config),
Self::ArgumentParse { file, error } => error.eprint(file, config),
Self::ArgumentEval { file, error } => error.eprint(file, config),
Self::NoSuchEntry(n) => eprintln!("No entry with number {n}"),
Self::NoSuchLog(date) => eprintln!("No log for {date}"),
Self::NotATask(ns) => {
if ns.is_empty() {
eprintln!("Not a task.");
} else if ns.len() == 1 {
@ -57,8 +58,8 @@ where
eprintln!("{} are not tasks.", ns.join(", "));
}
}
Error::NoCaptureFile => eprintln!("No capture file found"),
Error::EditingIo(error) => {
Self::NoCaptureFile => eprintln!("No capture file found"),
Self::EditingIo(error) => {
eprintln!("Error while editing:");
eprintln!(" {error}");
}

View file

@ -189,7 +189,7 @@ impl DayLayout {
}
}
fn sort_entries(entries: &mut Vec<(usize, &Entry)>) {
fn sort_entries(entries: &mut [(usize, &Entry)]) {
// Entries should be sorted by these factors, in descending order of
// significance:
// 1. Their start date, if any
@ -219,7 +219,7 @@ impl DayLayout {
entries.sort_by_key(|(_, e)| e.dates.map(|d| d.sorted().root_with_time()));
}
fn sort_day(day: &mut Vec<DayEntry>) {
fn sort_day(day: &mut [DayEntry]) {
// In a day, entries should be sorted into these categories:
// 1. Untimed entries that end at the current day
// 2. Timed entries, based on

View file

@ -36,15 +36,17 @@ impl SpanStyle {
pub enum SpanSegment {
Start(SpanStyle),
Middle(SpanStyle),
Mark(SpanStyle),
End(SpanStyle),
}
impl SpanSegment {
fn style(&self) -> SpanStyle {
match self {
SpanSegment::Start(s) => *s,
SpanSegment::Middle(s) => *s,
SpanSegment::End(s) => *s,
Self::Start(s) => *s,
Self::Middle(s) => *s,
Self::Mark(s) => *s,
Self::End(s) => *s,
}
}
}
@ -56,7 +58,7 @@ pub enum Times {
FromTo(Time, Time),
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineKind {
Task,
Done,
@ -79,6 +81,7 @@ pub enum LineEntry {
Entry {
number: Option<usize>,
spans: Vec<Option<SpanSegment>>,
today: bool,
time: Times,
kind: LineKind,
text: String,
@ -121,17 +124,18 @@ impl LineLayout {
self.step_spans();
for day in layout.range.days() {
let today = day == layout.today;
let spans = self.spans_for_line();
self.line(LineEntry::Day {
spans,
date: day,
today: day == layout.today,
today,
has_log: files.log(day).is_some(),
});
let layout_entries = layout.days.get(&day).expect("got nonexisting day");
for layout_entry in layout_entries {
self.render_layout_entry(entries, layout_entry);
self.render_layout_entry(entries, layout_entry, today);
}
}
}
@ -157,11 +161,11 @@ impl LineLayout {
.ok_or(Error::NoSuchEntry(number))
}
fn render_layout_entry(&mut self, entries: &[Entry], l_entry: &DayEntry) {
fn render_layout_entry(&mut self, entries: &[Entry], l_entry: &DayEntry, today: bool) {
match l_entry {
DayEntry::End(i) => {
self.stop_span(*i);
self.line_entry(entries, *i, Times::Untimed, None);
self.line_entry(entries, *i, today, Times::Untimed, None);
}
DayEntry::Now(t) => self.line(LineEntry::Now {
spans: self.spans_for_line(),
@ -169,17 +173,15 @@ impl LineLayout {
}),
DayEntry::TimedEnd(i, t) => {
self.stop_span(*i);
self.line_entry(entries, *i, Times::At(*t), None);
self.line_entry(entries, *i, today, Times::At(*t), None);
}
DayEntry::TimedAt(i, t, t2) => {
let time = t2
.map(|t2| Times::FromTo(*t, t2))
.unwrap_or_else(|| Times::At(*t));
self.line_entry(entries, *i, time, None);
let time = t2.map(|t2| Times::FromTo(*t, t2)).unwrap_or(Times::At(*t));
self.line_entry(entries, *i, today, time, None);
}
DayEntry::TimedStart(i, t) => {
self.start_span(*i);
self.line_entry(entries, *i, Times::At(*t), None);
self.line_entry(entries, *i, today, Times::At(*t), None);
}
DayEntry::ReminderSince(i, d) => {
let extra = if *d == 1 {
@ -187,22 +189,23 @@ impl LineLayout {
} else {
format!("{d} days ago")
};
self.line_entry(entries, *i, Times::Untimed, Some(extra));
self.line_entry(entries, *i, today, Times::Untimed, Some(extra));
}
DayEntry::At(i) => {
self.line_entry(entries, *i, Times::Untimed, None);
self.line_entry(entries, *i, today, Times::Untimed, None);
}
DayEntry::ReminderWhile(i, d) => {
let plural = if *d == 1 { "" } else { "s" };
let extra = format!("{d} day{plural} left");
self.line_entry(entries, *i, Times::Untimed, Some(extra));
self.mark_span(*i);
self.line_entry(entries, *i, today, Times::Untimed, Some(extra));
}
DayEntry::Undated(i) => {
self.line_entry(entries, *i, Times::Untimed, None);
self.line_entry(entries, *i, today, Times::Untimed, None);
}
DayEntry::Start(i) => {
self.start_span(*i);
self.line_entry(entries, *i, Times::Untimed, None);
self.line_entry(entries, *i, today, Times::Untimed, None);
}
DayEntry::ReminderUntil(i, d) => {
let extra = if *d == 1 {
@ -210,7 +213,7 @@ impl LineLayout {
} else {
format!("in {d} days")
};
self.line_entry(entries, *i, Times::Untimed, Some(extra));
self.line_entry(entries, *i, today, Times::Untimed, Some(extra));
}
}
}
@ -246,6 +249,15 @@ impl LineLayout {
self.spans.push(Some((index, SpanSegment::Start(style))));
}
fn mark_span(&mut self, index: usize) {
for span in self.spans.iter_mut() {
match span {
Some((i, s)) if *i == index => *s = SpanSegment::Mark(s.style()),
_ => {}
}
}
}
fn stop_span(&mut self, index: usize) {
for span in self.spans.iter_mut() {
match span {
@ -258,7 +270,9 @@ impl LineLayout {
fn step_spans(&mut self) {
for span in self.spans.iter_mut() {
match span {
Some((_, s @ SpanSegment::Start(_))) => *s = SpanSegment::Middle(s.style()),
Some((_, s @ (SpanSegment::Start(_) | SpanSegment::Mark(_)))) => {
*s = SpanSegment::Middle(s.style())
}
Some((_, SpanSegment::End(_))) => *span = None,
_ => {}
}
@ -277,7 +291,14 @@ impl LineLayout {
self.step_spans();
}
fn line_entry(&mut self, entries: &[Entry], index: usize, time: Times, extra: Option<String>) {
fn line_entry(
&mut self,
entries: &[Entry],
index: usize,
today: bool,
time: Times,
extra: Option<String>,
) {
let entry = &entries[index];
let number = match self.numbers.get(&index) {
@ -292,6 +313,7 @@ impl LineLayout {
self.line(LineEntry::Entry {
number: Some(number),
spans: self.spans_for_line(),
today,
time,
kind: Self::entry_kind(entry),
text: Self::entry_title(entry),

View file

@ -35,12 +35,14 @@ impl ShowLines {
LineEntry::Entry {
number,
spans,
today,
time,
kind,
text,
has_desc,
extra,
} => self.display_line_entry(*number, spans, *time, *kind, text, *has_desc, extra),
} => self
.display_line_entry(*number, spans, *today, *time, *kind, text, *has_desc, extra),
}
}
@ -99,6 +101,7 @@ impl ShowLines {
&mut self,
number: Option<usize>,
spans: &[Option<SpanSegment>],
today: bool,
time: Times,
kind: LineKind,
text: &str,
@ -110,6 +113,12 @@ impl ShowLines {
None => "".to_string(),
};
let text = if kind == LineKind::Birthday && today {
util::display_current_birthday_text(text)
} else {
text.into()
};
self.push(&format!(
"{:>nw$} {} {}{} {}{}{}\n",
num.bright_black(),
@ -132,6 +141,7 @@ impl ShowLines {
SpanSegment::Middle(SpanStyle::Solid) => "".bright_black(),
SpanSegment::Middle(SpanStyle::Dashed) => "".bright_black(),
SpanSegment::Middle(SpanStyle::Dotted) => "".bright_black(),
SpanSegment::Mark(_) => "".bright_black(),
SpanSegment::End(_) => "".bright_black(),
};
result.push_str(&format!("{colored_str}"));

View file

@ -13,6 +13,10 @@ pub fn display_kind(kind: LineKind) -> ColoredString {
}
}
pub fn display_current_birthday_text(text: &str) -> ColoredString {
text.yellow()
}
pub fn edit(input: &str) -> Result<String> {
edit::edit(input).map_err(Error::EditingIo)
}

View file

@ -4,6 +4,7 @@ use codespan_reporting::term::{self, Config};
use termcolor::StandardStream;
pub trait Eprint<'a, F: Files<'a>> {
#[allow(single_use_lifetimes)]
fn eprint_diagnostic<'f: 'a>(
files: &'f F,
config: &Config,
@ -15,9 +16,11 @@ pub trait Eprint<'a, F: Files<'a>> {
}
}
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config);
}
#[allow(single_use_lifetimes)]
pub fn eprint_error<'a, 'f: 'a, F, E>(files: &'f F, e: &E)
where
F: Files<'a>,

View file

@ -8,7 +8,7 @@ use super::super::date::Dates;
use super::super::error::Error;
use super::super::EntryKind;
impl<'a> CommandState<'a> {
impl CommandState<'_> {
pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<(), Error<FileSource>> {
let range = match self.limit_from_until(self.range_with_remind()) {
Some(range) => range,
@ -35,8 +35,8 @@ impl<'a> CommandState<'a> {
assert_eq!(spec.date.month(), 2);
assert_eq!(spec.date.day(), 29);
let first = NaiveDate::from_ymd(year, 2, 28);
let second = NaiveDate::from_ymd(year, 3, 1);
let first = NaiveDate::from_ymd_opt(year, 2, 28).unwrap();
let second = NaiveDate::from_ymd_opt(year, 3, 1).unwrap();
self.add(self.entry_with_remind(kind, Some(Dates::new(first, second)))?);
}
}

View file

@ -85,7 +85,7 @@ impl DateSpec {
let range_from = s
.command
.last_done_root()
.map(|date| date.succ())
.map(|date| date.succ_opt().unwrap())
.unwrap_or(self.start);
let range = s
.range_with_remind()
@ -137,7 +137,7 @@ impl DateSpec {
}
}
impl<'a> CommandState<'a> {
impl CommandState<'_> {
pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<(), Error<FileSource>> {
let index = self.source.file();
if let Some(repeat) = &spec.repeat {

File diff suppressed because it is too large Load diff

View file

@ -43,84 +43,84 @@ impl DeltaStep {
/// A lower bound on days
fn lower_bound(&self) -> i32 {
match self {
DeltaStep::Year(n) => {
Self::Year(n) => {
if *n < 0 {
*n * 366
} else {
*n * 365
}
}
DeltaStep::Month(n) | DeltaStep::MonthReverse(n) => {
Self::Month(n) | Self::MonthReverse(n) => {
if *n < 0 {
*n * 31
} else {
*n * 28
}
}
DeltaStep::Day(n) => *n,
DeltaStep::Week(n) => *n * 7,
DeltaStep::Hour(n) => {
Self::Day(n) => *n,
Self::Week(n) => *n * 7,
Self::Hour(n) => {
if *n < 0 {
*n / 24 + (*n % 24).signum()
} else {
*n / 24
}
}
DeltaStep::Minute(n) => {
Self::Minute(n) => {
if *n < 0 {
*n / (24 * 60) + (*n % (24 * 60)).signum()
} else {
*n / (24 * 60)
}
}
DeltaStep::Weekday(n, _) => match n.cmp(&0) {
Self::Weekday(n, _) => match n.cmp(&0) {
Ordering::Less => *n * 7 - 1,
Ordering::Equal => 0,
Ordering::Greater => *n * 7 - 7,
},
DeltaStep::Time(_) => 0,
Self::Time(_) => 0,
}
}
/// An upper bound on days
fn upper_bound(&self) -> i32 {
match self {
DeltaStep::Year(n) => {
Self::Year(n) => {
if *n > 0 {
*n * 366
} else {
*n * 365
}
}
DeltaStep::Month(n) | DeltaStep::MonthReverse(n) => {
Self::Month(n) | Self::MonthReverse(n) => {
if *n > 0 {
*n * 31
} else {
*n * 28
}
}
DeltaStep::Day(n) => *n,
DeltaStep::Week(n) => *n * 7,
DeltaStep::Hour(n) => {
Self::Day(n) => *n,
Self::Week(n) => *n * 7,
Self::Hour(n) => {
if *n > 0 {
*n / 24 + (*n % 24).signum()
} else {
*n / 24
}
}
DeltaStep::Minute(n) => {
Self::Minute(n) => {
if *n > 0 {
*n / (24 * 60) + (*n % (24 * 60)).signum()
} else {
*n / (24 * 60)
}
}
DeltaStep::Weekday(n, _) => match n.cmp(&0) {
Self::Weekday(n, _) => match n.cmp(&0) {
Ordering::Less => *n * 7 - 7,
Ordering::Equal => 0,
Ordering::Greater => *n * 7 - 1,
},
DeltaStep::Time(_) => 1,
Self::Time(_) => 1,
}
}
}
@ -298,7 +298,7 @@ impl<S: Copy> DeltaEval<S> {
};
if time < curr_time {
self.curr = self.curr.succ();
self.curr = self.curr.succ_opt().unwrap();
}
self.curr_time = Some(time);
Ok(())
@ -359,13 +359,13 @@ mod tests {
}
fn apply_d(step: Step, from: (i32, u32, u32)) -> Result<NaiveDate, Error<()>> {
delta(step).apply_date((), NaiveDate::from_ymd(from.0, from.1, from.2))
delta(step).apply_date((), NaiveDate::from_ymd_opt(from.0, from.1, from.2).unwrap())
}
fn test_d(step: Step, from: (i32, u32, u32), expected: (i32, u32, u32)) {
assert_eq!(
apply_d(step, from).unwrap(),
NaiveDate::from_ymd(expected.0, expected.1, expected.2)
NaiveDate::from_ymd_opt(expected.0, expected.1, expected.2).unwrap()
);
}
@ -375,7 +375,7 @@ mod tests {
) -> Result<(NaiveDate, Time), Error<()>> {
delta(step).apply_date_time(
(),
NaiveDate::from_ymd(from.0, from.1, from.2),
NaiveDate::from_ymd_opt(from.0, from.1, from.2).unwrap(),
Time::new(from.3, from.4),
)
}
@ -385,7 +385,7 @@ mod tests {
assert_eq!(
apply_dt(step, from).unwrap(),
(
NaiveDate::from_ymd(expected.0, expected.1, expected.2),
NaiveDate::from_ymd_opt(expected.0, expected.1, expected.2).unwrap(),
Time::new(expected.3, expected.4)
)
);

View file

@ -61,6 +61,7 @@ impl Entry {
/// Mode that determines how entries are filtered when they are added to
/// an [`Entries`].
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryMode {
/// The entry's root date must be contained in the range.

View file

@ -88,6 +88,7 @@ impl<S> Error<S> {
}
impl<'a, F: Files<'a>> Eprint<'a, F> for Error<F::FileId> {
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) {
let diagnostic = match self {
Error::DeltaInvalidStep {

View file

@ -37,6 +37,7 @@ impl DateRange {
/// Return a new range with its [`Self::until`] set to a new value.
///
/// Returns [`None`] if the new value is earlier than [`Self::from`].
#[allow(dead_code)]
pub fn with_until(&self, until: NaiveDate) -> Option<Self> {
if self.from <= until {
Some(Self::new(self.from, until))
@ -75,7 +76,7 @@ impl DateRange {
pub fn days(&self) -> impl Iterator<Item = NaiveDate> {
(self.from.num_days_from_ce()..=self.until.num_days_from_ce())
.map(NaiveDate::from_num_days_from_ce)
.map(|days| NaiveDate::from_num_days_from_ce_opt(days).unwrap())
}
pub fn years(&self) -> RangeInclusive<i32> {

View file

@ -9,13 +9,14 @@ pub fn is_iso_leap_year(year: i32) -> bool {
}
pub fn year_length(year: i32) -> u32 {
NaiveDate::from_ymd(year, 12, 31).ordinal()
NaiveDate::from_ymd_opt(year, 12, 31).unwrap().ordinal()
}
pub fn month_length(year: i32, month: u32) -> u32 {
NaiveDate::from_ymd_opt(year, month + 1, 1)
.unwrap_or_else(|| NaiveDate::from_ymd(year + 1, 1, 1))
.pred()
.unwrap_or_else(|| NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap())
.pred_opt()
.unwrap()
.day()
}

View file

@ -13,7 +13,8 @@ fn from_str_via_parse<P, R>(s: &str, rule: Rule, parse: P) -> result::Result<R,
where
P: FnOnce(Pair<'_, Rule>) -> Result<R>,
{
let mut pairs = TodayfileParser::parse(rule, s).map_err(|e| ParseError::new((), e))?;
let mut pairs =
TodayfileParser::parse(rule, s).map_err(|e| ParseError::new((), Box::new(e)))?;
let p = pairs.next().unwrap();
assert_eq!(pairs.next(), None);
@ -57,11 +58,17 @@ fn parse_cli_date(p: Pair<'_, Rule>) -> Result<CliDate> {
Ok(CliDate { datum, delta })
}
fn parse_cli_date_arg(p: Pair<'_, Rule>) -> Result<CliDate> {
assert_eq!(p.as_rule(), Rule::cli_date_arg);
let p = p.into_inner().next().unwrap();
parse_cli_date(p)
}
impl FromStr for CliDate {
type Err = ParseError<()>;
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
from_str_via_parse(s, Rule::cli_date, parse_cli_date)
from_str_via_parse(s, Rule::cli_date_arg, parse_cli_date_arg)
}
}
@ -81,11 +88,17 @@ fn parse_cli_ident(p: Pair<'_, Rule>) -> Result<CliIdent> {
})
}
fn parse_cli_ident_arg(p: Pair<'_, Rule>) -> Result<CliIdent> {
assert_eq!(p.as_rule(), Rule::cli_ident_arg);
let p = p.into_inner().next().unwrap();
parse_cli_ident(p)
}
impl FromStr for CliIdent {
type Err = ParseError<()>;
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
from_str_via_parse(s, Rule::cli_ident, parse_cli_ident)
from_str_via_parse(s, Rule::cli_ident_arg, parse_cli_ident_arg)
}
}
@ -135,11 +148,12 @@ fn parse_cli_range(p: Pair<'_, Rule>) -> Result<CliRange> {
let (start, start_delta) = parse_cli_range_start(p.next().unwrap())?;
let (end, end_delta) = match p.next() {
// For some reason, the EOI gets captured but the SOI doesn't.
Some(p) if p.as_rule() != Rule::EOI => parse_cli_range_end(p)?,
_ => (None, None),
Some(p) => parse_cli_range_end(p)?,
None => (None, None),
};
assert_eq!(p.next(), None);
Ok(CliRange {
start,
start_delta,
@ -148,11 +162,17 @@ fn parse_cli_range(p: Pair<'_, Rule>) -> Result<CliRange> {
})
}
fn parse_cli_range_arg(p: Pair<'_, Rule>) -> Result<CliRange> {
assert_eq!(p.as_rule(), Rule::cli_range_arg);
let p = p.into_inner().next().unwrap();
parse_cli_range(p)
}
impl FromStr for CliRange {
type Err = ParseError<()>;
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
from_str_via_parse(s, Rule::cli_range, parse_cli_range)
from_str_via_parse(s, Rule::cli_range_arg, parse_cli_range_arg)
}
}

View file

@ -27,27 +27,27 @@ pub enum DeltaStep {
impl DeltaStep {
pub fn amount(&self) -> i32 {
match self {
DeltaStep::Year(i) => *i,
DeltaStep::Month(i) => *i,
DeltaStep::MonthReverse(i) => *i,
DeltaStep::Day(i) => *i,
DeltaStep::Week(i) => *i,
DeltaStep::Hour(i) => *i,
DeltaStep::Minute(i) => *i,
DeltaStep::Weekday(i, _) => *i,
Self::Year(i) => *i,
Self::Month(i) => *i,
Self::MonthReverse(i) => *i,
Self::Day(i) => *i,
Self::Week(i) => *i,
Self::Hour(i) => *i,
Self::Minute(i) => *i,
Self::Weekday(i, _) => *i,
}
}
pub fn name(&self) -> &'static str {
match self {
DeltaStep::Year(_) => "y",
DeltaStep::Month(_) => "m",
DeltaStep::MonthReverse(_) => "M",
DeltaStep::Day(_) => "d",
DeltaStep::Week(_) => "w",
DeltaStep::Hour(_) => "h",
DeltaStep::Minute(_) => "min",
DeltaStep::Weekday(_, wd) => wd.name(),
Self::Year(_) => "y",
Self::Month(_) => "m",
Self::MonthReverse(_) => "M",
Self::Day(_) => "d",
Self::Week(_) => "w",
Self::Hour(_) => "h",
Self::Minute(_) => "min",
Self::Weekday(_, wd) => wd.name(),
}
}
}
@ -168,39 +168,39 @@ impl Var {
pub fn name(&self) -> &'static str {
match self {
// Constants
Var::True => "true",
Var::False => "false",
Var::Monday => "mon",
Var::Tuesday => "tue",
Var::Wednesday => "wed",
Var::Thursday => "thu",
Var::Friday => "fri",
Var::Saturday => "sat",
Var::Sunday => "sun",
Self::True => "true",
Self::False => "false",
Self::Monday => "mon",
Self::Tuesday => "tue",
Self::Wednesday => "wed",
Self::Thursday => "thu",
Self::Friday => "fri",
Self::Saturday => "sat",
Self::Sunday => "sun",
// Variables
Var::JulianDay => "j",
Var::Year => "y",
Var::YearLength => "yl",
Var::YearDay => "yd",
Var::YearDayReverse => "yD",
Var::YearWeek => "yw",
Var::YearWeekReverse => "yW",
Var::Month => "m",
Var::MonthLength => "ml",
Var::MonthWeek => "mw",
Var::MonthWeekReverse => "mW",
Var::Day => "d",
Var::DayReverse => "D",
Var::IsoYear => "iy",
Var::IsoYearLength => "iyl",
Var::IsoWeek => "iw",
Var::Weekday => "wd",
Var::Easter => "e",
Self::JulianDay => "j",
Self::Year => "y",
Self::YearLength => "yl",
Self::YearDay => "yd",
Self::YearDayReverse => "yD",
Self::YearWeek => "yw",
Self::YearWeekReverse => "yW",
Self::Month => "m",
Self::MonthLength => "ml",
Self::MonthWeek => "mw",
Self::MonthWeekReverse => "mW",
Self::Day => "d",
Self::DayReverse => "D",
Self::IsoYear => "iy",
Self::IsoYearLength => "iyl",
Self::IsoWeek => "iw",
Self::Weekday => "wd",
Self::Easter => "e",
// Variables with "boolean" values
Var::IsWeekday => "isWeekday",
Var::IsWeekend => "isWeekend",
Var::IsLeapYear => "isLeapYear",
Var::IsIsoLeapYear => "isIsoLeapYear",
Self::IsWeekday => "isWeekday",
Self::IsWeekend => "isWeekend",
Self::IsLeapYear => "isLeapYear",
Self::IsIsoLeapYear => "isIsoLeapYear",
}
}
}
@ -301,11 +301,11 @@ pub enum DoneDate {
impl DoneDate {
pub fn root(self) -> NaiveDate {
match self {
DoneDate::Date { root } => root,
DoneDate::DateTime { root, .. } => root,
DoneDate::DateToDate { root, .. } => root,
DoneDate::DateTimeToTime { root, .. } => root,
DoneDate::DateTimeToDateTime { root, .. } => root,
Self::Date { root } => root,
Self::DateTime { root, .. } => root,
Self::DateToDate { root, .. } => root,
Self::DateTimeToTime { root, .. } => root,
Self::DateTimeToDateTime { root, .. } => root,
}
}
@ -346,7 +346,9 @@ pub enum DoneKind {
#[derive(Debug)]
pub struct Done {
pub kind: DoneKind,
/// The date of the task the DONE refers to.
pub date: Option<DoneDate>,
/// When the task was actually completed.
pub done_at: NaiveDate,
}

View file

@ -15,11 +15,11 @@ use super::{parse, FileSource, Files};
#[error("{error}")]
pub struct ParseError<S> {
file: S,
error: parse::Error,
error: Box<parse::Error>,
}
impl<S> ParseError<S> {
pub fn new(file: S, error: parse::Error) -> Self {
pub fn new(file: S, error: Box<parse::Error>) -> Self {
Self { file, error }
}
@ -69,6 +69,7 @@ impl<'a, F> Eprint<'a, F> for ParseError<F::FileId>
where
F: codespan_reporting::files::Files<'a>,
{
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) {
let range = match self.error.location {
InputLocation::Pos(at) => at..at,
@ -103,7 +104,7 @@ pub enum Error {
#[error("{error}")]
Parse {
file: FileSource,
error: parse::Error,
error: Box<parse::Error>,
},
#[error("Conflicting time zones {tz1} and {tz2}")]
TzConflict {
@ -132,21 +133,22 @@ pub enum Error {
}
impl<'a> Eprint<'a, Files> for Error {
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f Files, config: &Config) {
match self {
Error::ResolvePath { path, error } => {
Self::ResolvePath { path, error } => {
eprintln!("Could not resolve path {path:?}:");
eprintln!(" {error}");
}
Error::ReadFile { file, error } => {
Self::ReadFile { file, error } => {
eprintln!("Could not read file {file:?}:");
eprintln!(" {error}");
}
Error::WriteFile { file, error } => {
Self::WriteFile { file, error } => {
eprintln!("Could not write file {file:?}:");
eprintln!(" {error}");
}
Error::ResolveTz {
Self::ResolveTz {
file,
span,
tz,
@ -158,14 +160,14 @@ impl<'a> Eprint<'a, Files> for Error {
.with_notes(vec![format!("{error}")]);
Self::eprint_diagnostic(files, config, &diagnostic);
}
Error::LocalTz { error } => {
Self::LocalTz { error } => {
eprintln!("Could not determine local timezone:");
eprintln!(" {error}");
}
Error::Parse { file, error } => {
Self::Parse { file, error } => {
ParseError::new(*file, error.clone()).eprint(files, config)
}
Error::TzConflict {
Self::TzConflict {
file1,
span1,
tz1,
@ -184,7 +186,7 @@ impl<'a> Eprint<'a, Files> for Error {
]);
Self::eprint_diagnostic(files, config, &diagnostic);
}
Error::MultipleCapture {
Self::MultipleCapture {
file1,
span1,
file2,
@ -201,7 +203,7 @@ impl<'a> Eprint<'a, Files> for Error {
]);
Self::eprint_diagnostic(files, config, &diagnostic);
}
Error::LogConflict {
Self::LogConflict {
file1,
span1,
file2,

View file

@ -75,10 +75,10 @@ impl fmt::Display for DateSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Start
write!(f, "{}", self.start)?;
for delta in &self.start_delta {
if let Some(delta) = &self.start_delta {
write!(f, " {delta}")?;
}
for time in &self.start_time {
if let Some(time) = &self.start_time {
write!(f, " {time}")?;
}
@ -109,7 +109,7 @@ impl fmt::Display for WeekdaySpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Start
write!(f, "{}", self.start)?;
for time in &self.start_time {
if let Some(time) = &self.start_time {
write!(f, " {time}")?;
}
@ -140,25 +140,25 @@ impl fmt::Display for Var {
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expr::Lit(i) => write!(f, "{i}"),
Expr::Var(v) => write!(f, "{v}"),
Expr::Paren(e) => write!(f, "({e})"),
Expr::Neg(e) => write!(f, "-{e}"),
Expr::Add(a, b) => write!(f, "{a} + {b}"),
Expr::Sub(a, b) => write!(f, "{a} - {b}"),
Expr::Mul(a, b) => write!(f, "{a} * {b}"),
Expr::Div(a, b) => write!(f, "{a} / {b}"),
Expr::Mod(a, b) => write!(f, "{a} % {b}"),
Expr::Eq(a, b) => write!(f, "{a} = {b}"),
Expr::Neq(a, b) => write!(f, "{a} != {b}"),
Expr::Lt(a, b) => write!(f, "{a} < {b}"),
Expr::Lte(a, b) => write!(f, "{a} <= {b}"),
Expr::Gt(a, b) => write!(f, "{a} > {b}"),
Expr::Gte(a, b) => write!(f, "{a} >= {b}"),
Expr::Not(e) => write!(f, "!{e}"),
Expr::And(a, b) => write!(f, "{a} & {b}"),
Expr::Or(a, b) => write!(f, "{a} | {b}"),
Expr::Xor(a, b) => write!(f, "{a} ^ {b}"),
Self::Lit(i) => write!(f, "{i}"),
Self::Var(v) => write!(f, "{v}"),
Self::Paren(e) => write!(f, "({e})"),
Self::Neg(e) => write!(f, "-{e}"),
Self::Add(a, b) => write!(f, "{a} + {b}"),
Self::Sub(a, b) => write!(f, "{a} - {b}"),
Self::Mul(a, b) => write!(f, "{a} * {b}"),
Self::Div(a, b) => write!(f, "{a} / {b}"),
Self::Mod(a, b) => write!(f, "{a} % {b}"),
Self::Eq(a, b) => write!(f, "{a} = {b}"),
Self::Neq(a, b) => write!(f, "{a} != {b}"),
Self::Lt(a, b) => write!(f, "{a} < {b}"),
Self::Lte(a, b) => write!(f, "{a} <= {b}"),
Self::Gt(a, b) => write!(f, "{a} > {b}"),
Self::Gte(a, b) => write!(f, "{a} >= {b}"),
Self::Not(e) => write!(f, "!{e}"),
Self::And(a, b) => write!(f, "{a} & {b}"),
Self::Or(a, b) => write!(f, "{a} | {b}"),
Self::Xor(a, b) => write!(f, "{a} ^ {b}"),
}
}
}
@ -171,10 +171,10 @@ impl fmt::Display for FormulaSpec {
} else {
write!(f, "*")?;
}
for delta in &self.start_delta {
if let Some(delta) = &self.start_delta {
write!(f, " {delta}")?;
}
for time in &self.start_time {
if let Some(time) = &self.start_time {
write!(f, " {time}")?;
}
@ -196,9 +196,9 @@ impl fmt::Display for FormulaSpec {
impl fmt::Display for Spec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Spec::Date(spec) => write!(f, "{spec}"),
Spec::Weekday(spec) => write!(f, "{spec}"),
Spec::Formula(spec) => write!(f, "{spec}"),
Self::Date(spec) => write!(f, "{spec}"),
Self::Weekday(spec) => write!(f, "{spec}"),
Self::Formula(spec) => write!(f, "{spec}"),
}
}
}
@ -216,14 +216,14 @@ impl fmt::Display for BirthdaySpec {
impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Statement::Date(spec) => writeln!(f, "DATE {spec}"),
Statement::BDate(spec) => writeln!(f, "BDATE {spec}"),
Statement::From(Some(date)) => writeln!(f, "FROM {date}"),
Statement::From(None) => writeln!(f, "FROM *"),
Statement::Until(Some(date)) => writeln!(f, "UNTIL {date}"),
Statement::Until(None) => writeln!(f, "UNTIL *"),
Statement::Except(date) => writeln!(f, "EXCEPT {date}"),
Statement::Move {
Self::Date(spec) => writeln!(f, "DATE {spec}"),
Self::BDate(spec) => writeln!(f, "BDATE {spec}"),
Self::From(Some(date)) => writeln!(f, "FROM {date}"),
Self::From(None) => writeln!(f, "FROM *"),
Self::Until(Some(date)) => writeln!(f, "UNTIL {date}"),
Self::Until(None) => writeln!(f, "UNTIL *"),
Self::Except(date) => writeln!(f, "EXCEPT {date}"),
Self::Move {
from, to, to_time, ..
} => match (to, to_time) {
(None, None) => unreachable!(),
@ -231,8 +231,8 @@ impl fmt::Display for Statement {
(None, Some(to_time)) => writeln!(f, "MOVE {from} TO {to_time}"),
(Some(to), Some(to_time)) => writeln!(f, "MOVE {from} TO {to} {to_time}"),
},
Statement::Remind(Some(delta)) => writeln!(f, "REMIND {delta}"),
Statement::Remind(None) => writeln!(f, "REMIND *"),
Self::Remind(Some(delta)) => writeln!(f, "REMIND {delta}"),
Self::Remind(None) => writeln!(f, "REMIND *"),
}
}
}
@ -240,15 +240,15 @@ impl fmt::Display for Statement {
impl fmt::Display for DoneDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.simplified() {
DoneDate::Date { root } => write!(f, "{root}"),
DoneDate::DateTime { root, root_time } => write!(f, "{root} {root_time}"),
DoneDate::DateToDate { root, other } => write!(f, "{root} -- {other}"),
DoneDate::DateTimeToTime {
Self::Date { root } => write!(f, "{root}"),
Self::DateTime { root, root_time } => write!(f, "{root} {root_time}"),
Self::DateToDate { root, other } => write!(f, "{root} -- {other}"),
Self::DateTimeToTime {
root,
root_time,
other_time,
} => write!(f, "{root} {root_time} -- {other_time}"),
DoneDate::DateTimeToDateTime {
Self::DateTimeToDateTime {
root,
root_time,
other,
@ -308,18 +308,18 @@ impl fmt::Display for Log {
impl fmt::Display for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Command::Include(name) => writeln!(f, "INCLUDE {name}"),
Command::Timezone(name) => writeln!(f, "TIMEZONE {name}"),
Command::Capture => writeln!(f, "CAPTURE"),
Command::Task(task) => write!(f, "{task}"),
Command::Note(note) => write!(f, "{note}"),
Command::Log(log) => write!(f, "{log}"),
Self::Include(name) => writeln!(f, "INCLUDE {name}"),
Self::Timezone(name) => writeln!(f, "TIMEZONE {name}"),
Self::Capture => writeln!(f, "CAPTURE"),
Self::Task(task) => write!(f, "{task}"),
Self::Note(note) => write!(f, "{note}"),
Self::Log(log) => write!(f, "{log}"),
}
}
}
impl File {
fn sort(commands: &mut Vec<&Command>) {
fn sort(commands: &mut [&Command]) {
// Order of commands in a file:
// 1. Imports, sorted alphabetically
// 2. Time zone(s)

View file

@ -59,34 +59,33 @@ variable = {
| "e"
}
unop_neg = { "-" }
unop_not = { "!" }
unop = _{ unop_neg | unop_not }
prefix_neg = { "-" }
prefix_not = { "!" }
prefix = _{ prefix_neg | prefix_not }
op_add = { "+" }
op_sub = { "-" }
op_mul = { "*" }
op_div = { "/" }
op_mod = { "%" }
op_eq = { "=" }
op_neq = { "!=" }
op_lt = { "<" }
op_lte = { "<=" }
op_gt = { ">" }
op_gte = { ">=" }
op_and = { "&" }
op_or = { "|" }
op_xor = { "^" }
op = _{
op_add | op_sub | op_mul | op_div | op_mod
| op_eq | op_neq | op_lt | op_lte | op_gt | op_gte
| op_and | op_or | op_xor
infix_add = { "+" }
infix_sub = { "-" }
infix_mul = { "*" }
infix_div = { "/" }
infix_mod = { "%" }
infix_eq = { "=" }
infix_neq = { "!=" }
infix_lt = { "<" }
infix_lte = { "<=" }
infix_gt = { ">" }
infix_gte = { ">=" }
infix_and = { "&" }
infix_or = { "|" }
infix_xor = { "^" }
infix = _{
infix_add | infix_sub | infix_mul | infix_div | infix_mod
| infix_eq | infix_neq | infix_lt | infix_lte | infix_gt | infix_gte
| infix_and | infix_or | infix_xor
}
paren_expr = { "(" ~ expr ~ ")" }
unop_expr = { unop ~ expr }
term = { number | boolean | variable | paren_expr | unop_expr }
expr = { term ~ (op ~ term)* }
term = { number | boolean | variable | paren_expr }
expr = { prefix* ~ term ~ (infix ~ prefix* ~ term)* }
date_fixed_start = { datum ~ delta? ~ time? }
date_fixed_end = { datum ~ delta? ~ time? | delta ~ time? | time }
@ -152,8 +151,13 @@ file = ${ SOI ~ (empty_line* ~ command)* ~ empty_line* ~ WHITESPACE* ~ EOI }
today = { "today" | "t" }
cli_datum = { datum | today }
cli_date = { cli_datum ~ delta? }
cli_ident = { SOI ~ (cli_date | number) ~ EOI }
cli_ident = { cli_date | number }
cli_range_start = { cli_datum ~ delta? }
cli_range_end = { cli_datum ~ delta? | delta }
cli_range = { SOI ~ cli_range_start ~ ("--" ~ cli_range_end)? ~ EOI }
cli_range = { cli_range_start ~ ("--" ~ cli_range_end)? }
cli_date_arg = { SOI ~ cli_date ~ EOI }
cli_ident_arg = { SOI ~ cli_ident ~ EOI }
cli_range_arg = { SOI ~ cli_range ~ EOI }
cli_command = ${ SOI ~ empty_line* ~ command ~ empty_line* ~ WHITESPACE* ~ EOI }

View file

@ -4,7 +4,7 @@ use std::result;
use chrono::NaiveDate;
use pest::error::ErrorVariant;
use pest::iterators::Pair;
use pest::prec_climber::{Assoc, Operator, PrecClimber};
use pest::pratt_parser::{Assoc, Op, PrattParser};
use pest::{Parser, Span};
use super::commands::{
@ -18,7 +18,7 @@ use super::primitives::{Spanned, Time, Weekday};
pub struct TodayfileParser;
pub type Error = pest::error::Error<Rule>;
pub type Result<T> = result::Result<T, Error>;
pub type Result<T> = result::Result<T, Box<Error>>;
fn error<S: Into<String>>(span: Span<'_>, message: S) -> Error {
Error::new_from_span(
@ -30,7 +30,7 @@ fn error<S: Into<String>>(span: Span<'_>, message: S) -> Error {
}
fn fail<S: Into<String>, T>(span: Span<'_>, message: S) -> Result<T> {
Err(error(span, message))
Err(Box::new(error(span, message)))
}
fn parse_include(p: Pair<'_, Rule>) -> Spanned<String> {
@ -293,7 +293,7 @@ fn parse_date_fixed(p: Pair<'_, Rule>) -> Result<DateSpec> {
assert_eq!(p.as_rule(), Rule::date_fixed);
let mut spec = DateSpec {
start: NaiveDate::from_ymd(0, 1, 1),
start: NaiveDate::from_ymd_opt(0, 1, 1).unwrap(),
start_delta: None,
start_time: None,
end: None,
@ -359,24 +359,6 @@ fn parse_variable(p: Pair<'_, Rule>) -> Var {
}
}
fn parse_unop_expr(p: Pair<'_, Rule>) -> Spanned<Expr> {
assert_eq!(p.as_rule(), Rule::unop_expr);
let span = (&p.as_span()).into();
let mut p = p.into_inner();
let p_op = p.next().unwrap();
let p_expr = p.next().unwrap();
assert_eq!(p.next(), None);
let inner = parse_expr(p_expr);
let expr = match p_op.as_rule() {
Rule::unop_neg => Expr::Neg(Box::new(inner)),
Rule::unop_not => Expr::Not(Box::new(inner)),
_ => unreachable!(),
};
Spanned::new(span, expr)
}
fn parse_paren_expr(p: Pair<'_, Rule>) -> Spanned<Expr> {
assert_eq!(p.as_rule(), Rule::paren_expr);
let span = (&p.as_span()).into();
@ -392,34 +374,43 @@ fn parse_term(p: Pair<'_, Rule>) -> Spanned<Expr> {
Rule::number => Spanned::new(span, Expr::Lit(parse_number(p).into())),
Rule::boolean => Spanned::new(span, Expr::Var(parse_boolean(p))),
Rule::variable => Spanned::new(span, Expr::Var(parse_variable(p))),
Rule::unop_expr => parse_unop_expr(p),
Rule::paren_expr => parse_paren_expr(p),
_ => unreachable!(),
}
}
fn parse_op(l: Spanned<Expr>, p: Pair<'_, Rule>, r: Spanned<Expr>) -> Spanned<Expr> {
fn parse_prefix(p: Pair<'_, Rule>, s: Spanned<Expr>) -> Spanned<Expr> {
let span = s.span.join((&p.as_span()).into());
let expr = match p.as_rule() {
Rule::prefix_neg => Expr::Neg(Box::new(s)),
Rule::prefix_not => Expr::Not(Box::new(s)),
_ => unreachable!(),
};
Spanned::new(span, expr)
}
fn parse_infix(l: Spanned<Expr>, p: Pair<'_, Rule>, r: Spanned<Expr>) -> Spanned<Expr> {
let span = l.span.join(r.span);
let expr = match p.as_rule() {
// Integer-y operations
Rule::op_add => Expr::Add(Box::new(l), Box::new(r)),
Rule::op_sub => Expr::Sub(Box::new(l), Box::new(r)),
Rule::op_mul => Expr::Mul(Box::new(l), Box::new(r)),
Rule::op_div => Expr::Div(Box::new(l), Box::new(r)),
Rule::op_mod => Expr::Mod(Box::new(l), Box::new(r)),
Rule::infix_add => Expr::Add(Box::new(l), Box::new(r)),
Rule::infix_sub => Expr::Sub(Box::new(l), Box::new(r)),
Rule::infix_mul => Expr::Mul(Box::new(l), Box::new(r)),
Rule::infix_div => Expr::Div(Box::new(l), Box::new(r)),
Rule::infix_mod => Expr::Mod(Box::new(l), Box::new(r)),
// Comparisons
Rule::op_eq => Expr::Eq(Box::new(l), Box::new(r)),
Rule::op_neq => Expr::Neq(Box::new(l), Box::new(r)),
Rule::op_lt => Expr::Lt(Box::new(l), Box::new(r)),
Rule::op_lte => Expr::Lte(Box::new(l), Box::new(r)),
Rule::op_gt => Expr::Gt(Box::new(l), Box::new(r)),
Rule::op_gte => Expr::Gte(Box::new(l), Box::new(r)),
Rule::infix_eq => Expr::Eq(Box::new(l), Box::new(r)),
Rule::infix_neq => Expr::Neq(Box::new(l), Box::new(r)),
Rule::infix_lt => Expr::Lt(Box::new(l), Box::new(r)),
Rule::infix_lte => Expr::Lte(Box::new(l), Box::new(r)),
Rule::infix_gt => Expr::Gt(Box::new(l), Box::new(r)),
Rule::infix_gte => Expr::Gte(Box::new(l), Box::new(r)),
// Boolean-y operations
Rule::op_and => Expr::And(Box::new(l), Box::new(r)),
Rule::op_or => Expr::Or(Box::new(l), Box::new(r)),
Rule::op_xor => Expr::Xor(Box::new(l), Box::new(r)),
Rule::infix_and => Expr::And(Box::new(l), Box::new(r)),
Rule::infix_or => Expr::Or(Box::new(l), Box::new(r)),
Rule::infix_xor => Expr::Xor(Box::new(l), Box::new(r)),
_ => unreachable!(),
};
@ -429,21 +420,23 @@ fn parse_op(l: Spanned<Expr>, p: Pair<'_, Rule>, r: Spanned<Expr>) -> Spanned<Ex
fn parse_expr(p: Pair<'_, Rule>) -> Spanned<Expr> {
assert_eq!(p.as_rule(), Rule::expr);
fn op(rule: Rule) -> Operator<Rule> {
Operator::new(rule, Assoc::Left)
}
let climber = PrecClimber::new(vec![
// Precedence from low to high
op(Rule::op_or) | op(Rule::op_xor),
op(Rule::op_and),
op(Rule::op_eq) | op(Rule::op_neq),
op(Rule::op_lt) | op(Rule::op_lte) | op(Rule::op_gt) | op(Rule::op_gte),
op(Rule::op_mul) | op(Rule::op_div) | op(Rule::op_mod),
op(Rule::op_add) | op(Rule::op_sub),
]);
climber.climb(p.into_inner(), parse_term, parse_op)
PrattParser::new()
.op(Op::infix(Rule::infix_or, Assoc::Left) | Op::infix(Rule::infix_xor, Assoc::Left))
.op(Op::infix(Rule::infix_and, Assoc::Left))
.op(Op::infix(Rule::infix_eq, Assoc::Left) | Op::infix(Rule::infix_neq, Assoc::Left))
.op(Op::infix(Rule::infix_lt, Assoc::Left)
| Op::infix(Rule::infix_lte, Assoc::Left)
| Op::infix(Rule::infix_gt, Assoc::Left)
| Op::infix(Rule::infix_gte, Assoc::Left))
.op(Op::infix(Rule::infix_mul, Assoc::Left)
| Op::infix(Rule::infix_div, Assoc::Left)
| Op::infix(Rule::infix_mod, Assoc::Left))
.op(Op::infix(Rule::infix_add, Assoc::Left) | Op::infix(Rule::infix_sub, Assoc::Left))
.op(Op::prefix(Rule::prefix_neg) | Op::prefix(Rule::prefix_not))
.map_primary(parse_term)
.map_prefix(parse_prefix)
.map_infix(parse_infix)
.parse(p.into_inner())
}
fn parse_date_expr_start(p: Pair<'_, Rule>, spec: &mut FormulaSpec) -> Result<()> {
@ -858,5 +851,5 @@ pub fn parse(path: &Path, input: &str) -> Result<File> {
let file_pair = pairs.next().unwrap();
assert_eq!(pairs.next(), None);
parse_file(file_pair).map_err(|e| e.with_path(&pathstr))
parse_file(file_pair).map_err(|e| Box::new(e.with_path(&pathstr)))
}

View file

@ -206,13 +206,13 @@ impl Weekday {
/// `Saturday`, `Sunday`).
pub fn full_name(self) -> &'static str {
match self {
Weekday::Monday => "Monday",
Weekday::Tuesday => "Tuesday",
Weekday::Wednesday => "Wednesday",
Weekday::Thursday => "Thursday",
Weekday::Friday => "Friday",
Weekday::Saturday => "Saturday",
Weekday::Sunday => "Sunday",
Self::Monday => "Monday",
Self::Tuesday => "Tuesday",
Self::Wednesday => "Wednesday",
Self::Thursday => "Thursday",
Self::Friday => "Friday",
Self::Saturday => "Saturday",
Self::Sunday => "Sunday",
}
}

View file

@ -1,8 +1,3 @@
#![warn(future_incompatible)]
#![warn(rust_2018_idioms)]
#![warn(clippy::all)]
#![warn(clippy::use_self)]
mod cli;
mod error;
mod eval;