123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- use crate::data::{AccountName, UnitName, Span, Spanned, spec::SpecRoot, DataError, SourceFile};
- use super::{LedgerEntry, Balance};
- use chumsky::{prelude::*, text::inline_whitespace};
- type InputWithContext<'a> = chumsky::input::WithContext<Span, &'a str>;
- /*
- #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
- struct InputWrap<'a>(SourceFile, &'a str);
- impl<'a> chumsky::input::Input<'a> for InputWrap<'a> {
- type Token = char;
- type Span = Span;
- type Cache = ();
- type Cursor = usize;
- type MaybeToken = char;
- fn begin(self) -> (Self::Cursor, Self::Cache) {
- (0, ())
- }
- fn cursor_location(cursor: &Self::Cursor) -> usize {
- *cursor
- }
- unsafe fn span(_cache: &mut Self::Cache, range: std::ops::Range<&Self::Cursor>) -> Self::Span {
- let e : &[u8] = &[];
- Span::new(stringstore::StoredOsString::from(e), (*range.start..*range.end))
- }
- unsafe fn next_maybe(
- cache: &mut Self::Cache,
- cursor: &mut Self::Cursor,
- ) -> Option<Self::MaybeToken> {
- None
- }
- }
- */
- fn ledger_parser<'a>() -> impl Parser<
- 'a,
- InputWithContext<'a>,
- Vec<Spanned<LedgerEntry>>,
- chumsky::extra::Full<
- chumsky::error::Rich<'a, char, Span>,
- chumsky::extra::SimpleState<&'a SpecRoot>,
- (),
- >,
- > {
- let int = chumsky::text::digits(10)
- .to_slice()
- .map(|v: &str| v.parse::<usize>().unwrap());
- let datestamp = group((int, just('-').ignored(), int, just('-').ignored(), int))
- .map(|(y, _, m, _, d)| (y as u16, m as u8, d as u8));
- let mark = |m| just(m).padded_by(inline_whitespace());
- let decimal_digit = one_of("0123456789.,");
- let decimal_digits = decimal_digit
- .or(just(' ').repeated().ignore_then(decimal_digit))
- .repeated();
- let decimal = choice((just('-').ignored(), just('+').ignored(), empty()))
- .then(decimal_digits)
- .to_slice()
- .try_map(|s: &str, span| {
- Ok(Spanned::new(
- rust_decimal::Decimal::from_str_exact(s.trim()).map_err(|e| {
- Rich::custom(span, format!("Failed to parse '{s}' as a decimal number"))
- })?,
- span
- ))
- });
- let balance = group((
- mark('-'),
- none_of(": \n\t").repeated().to_slice().map_with(|v, e| Spanned(stringstore::StoredString::new(v), e.span())),
- mark(':'),
- decimal,
- choice((
- inline_whitespace()
- .at_least(1)
- .ignore_then(chumsky::text::ident())
- .then_ignore(inline_whitespace())
- .map_with(|u, e| Some(Spanned(UnitName::new(u), e.span()))),
- inline_whitespace().map(|_| None),
- ))
- .then_ignore(chumsky::text::newline()),
- ))
- .try_map_with(|(_, acc, _, amount, unit, ), e| {
- let span = e.span();
- let spec: &mut chumsky::extra::SimpleState<&SpecRoot> = e.state();
- let Some(acc_spec) = spec.accounts.get(acc.as_ref()) else {
- return Err(chumsky::error::Rich::custom(acc.span(), "no such account"));
- };
- let unit = match unit {
- Some(sunit) => sunit,
- None => acc_spec.default_unit.map(|u| Spanned(u, span)).ok_or_else(||
- chumsky::error::Rich::custom(span, format!("No unit specified and no default unit specified for account '{}'", acc.as_ref())))?
- };
- if !spec.units.contains_key(&unit) {
- return Err(chumsky::error::Rich::custom(unit.span(), format!("no such unit '{unit}' found")))
- }
- Ok(Spanned::new(Balance {
- account: acc,
- amount,
- unit,
- }, span))
- });
- let entry = group((
- chumsky::text::whitespace(),
- datestamp,
- mark(':'),
- inline_whitespace(),
- chumsky::primitive::none_of("\n")
- .repeated()
- .collect::<String>(),
- chumsky::text::newline(),
- balance.repeated().at_least(1).collect(),
- chumsky::text::whitespace(),
- ))
- .map_with(|(_, datestamp, _, _, title, _, balances, _), e| Spanned::new(LedgerEntry {
- datestamp,
- title: (!title.is_empty()).then_some(title),
- balances,
- }, e.span()));
- entry.repeated().collect()
- }
- pub fn parse_ledger(
- source: SourceFile,
- spec: &SpecRoot,
- data: &str,
- ) -> Result<Vec<Spanned<LedgerEntry>>, DataError> {
- let parser = ledger_parser();
- let (presult, errors) = parser
- .parse_with_state(data.with_context(source), &mut chumsky::extra::SimpleState(spec))
- .into_output_errors();
- if let Some(e) = errors.first() {
- let span = e.span().start()..e.span().end();
- let report = ariadne::Report::build(ariadne::ReportKind::Error, (source, span.clone()))
- .with_label(ariadne::Label::new((source, span)).with_message(e.reason()))
- .finish();
- Err(report.into())
- } else {
- Ok(presult.unwrap())
- }
- }
|