1mod component;
6mod directive;
7mod state;
8mod utils;
9
10use chumsky::error::RichReason;
11use chumsky::prelude::*;
12
13use crate::directive::directives;
14use crate::state::State;
15
16pub use crate::component::amount::Amount;
17pub use crate::component::period::interval::Interval;
18pub use crate::component::period::Period;
19pub use crate::component::price::AmountPrice;
20pub use crate::directive::{
21 Account, Assertion, AutoPosting, AutosPostingRule, Commodity, DecimalMark, Directive, Format,
22 Include, Payee, PeriodicTransaction, Posting, Price, Query, Status, Tag, Term, Transaction,
23 Year,
24};
25
26pub fn parse<I: AsRef<str>>(contents: I) -> Result<Vec<Directive>, Vec<ParseError>> {
32 directives()
33 .then_ignore(end())
34 .parse_with_state(contents.as_ref(), &mut State::default())
35 .into_result()
36 .map_err(|errors| errors.into_iter().map(ParseError::from).collect())
37}
38
39#[derive(Debug)]
41pub struct ParseError {
42 pub span: std::ops::Range<usize>,
44 pub message: String,
46}
47
48impl<'a, T: std::fmt::Debug> From<Rich<'a, T>> for ParseError {
49 fn from(rich_error: Rich<'a, T>) -> Self {
50 let span = rich_error.span().start..rich_error.span().end;
51
52 let message = match rich_error.into_reason() {
53 RichReason::Custom(msg) => msg,
54 RichReason::ExpectedFound { expected, found } => {
55 let expected_items: Vec<_> = expected
56 .into_iter()
57 .map(|pattern| format!("{pattern:?}"))
58 .collect();
59
60 format!(
61 "Expected {}{}",
62 if expected_items.is_empty() {
63 String::from("something else")
64 } else {
65 format!("one of: [{}]", expected_items.join(", "))
66 },
67 if let Some(found) = found {
68 format!(", found: {found:?}")
69 } else {
70 String::new()
71 }
72 )
73 }
74 RichReason::Many(reasons) => reasons
75 .into_iter()
76 .map(|reason| format!("{reason:?}"))
77 .collect::<Vec<_>>()
78 .join("; "),
79 };
80
81 ParseError { span, message }
82 }
83}