Crate beancount_parser_lima
source ·Expand description
§beancount-parser-lima
A zero-copy parser for Beancount in Rust.
It is intended to be a complete implementation of the Beancount file format, except for those parts which are deprecated and other features as documented here (in a list which may not be comprehensive).
Currently under active development. APIs are subject to change, but I hope not majorly.
The slightly strange name is because of a somewhat careless failure on my part to notice the existing beancount-parser when starting this project, for which apologies.
§Features
-
beautiful error messages, thanks to Ariadne
-
interface for applications to also report beautiful errors in their original context, as in the example below
-
focus on conceptual clarity of application domain objects mapped to Rust types
-
Python bindings (work-in-progress)
§Roadmap and Status
-
create Python bindings, so that this could be a drop-in replacement for the existing Beancount parser (which is not to say it will necessarily become that!)
-
improve API in the light of experience, i.e. when it gets some use 😅
-
address mistakes, misunderstandings, and edge-cases in the initial implementation as they are discovered
beancount-parser-lima v0.2.1
is able to parse the example.beancount file from the official Beancount repo.
§Examples
§dump
This simply parses a Beancount file and outputs the results of parsing, using the Display implementations for the parser output types.
The special filename STDIN
causes it to read instead from standard input and parse the resulting inline string.
cargo run --example dump -- ./examples/data/full.beancount
§check
This is an example of reporting errors against source locations by the application rather than the parser. This is important as semantic errors are not the business of the core parser to detect and report.
cargo run --example check -- ./examples/data/full.beancount
§Uncertainties / TODOs
Yeah, Beancount is complicated, and I may have made some mistakes here. Current list of uncertainties, which is certainly not comprehensive.
- metadata tags/links for a directive get folded in with those in the directive header line
§Unsupported
This is an incomplete list of what is currently unsupported.
- custom directive
§Unsupported Options
allow_pipe_separator
allow_deprecated_none_for_tags_and_links
default_tolerance
experiment_explicit_tolerances
insert_pythonpath
plugin
tolerance
use_legacy_fixed_tolerances
Also, unary options are not supported.
§Parser Tests
The parser test cases are based on the parser tests from Beancount itself, extracted into a language independent format. That is, all the original tests have been replicated here, with some additions.
Each test comprises a Beancount file and expected parse output formatted as Protobuf Text Format Language, using the Beancount Protobuf schema from the Beancount repo.
Error cases in this repo have been converted to match the expected error message output of this parser.
Behaviour which differs from original Beancount parser has been annotated in the test with ANOMALY
.
Tests for features unsupported in the Lima parser are left in test-cases-unsupported.
§Alpha Status Dependencies
-
Chumsky
1.0.0.alpha.*
releases are required for zero-copy support -
smallvec
2.0.0-alpha.*
releases are required for correct lifetime inference
§Alternatives
beancount-parser is another parser for Beancount which predates this one, using nom instead of Chumsky.
§License
Licensed under either of
- Apache License, Version 2.0 LICENSE-APACHE
- MIT license LICENSE-MIT
at your option.
§Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
§Examples
This example generates the output as shown above.
The supporting function parse
is required in order to avoid lifetime problems.
use beancount_parser_lima::{
BeancountParser, BeancountSources, DirectiveVariant, ParseError, ParseSuccess,
};
fn main() {
let sources = BeancountSources::try_from(PathBuf::from("examples/data/error-post-balancing.beancount")).unwrap();
let parser = BeancountParser::new(&sources);
parse(&sources, &parser, &io::stderr());
}
fn parse<W>(sources: &BeancountSources, parser: &BeancountParser, error_w: W)
where
W: Write + Copy,
{
match parser.parse() {
Ok(ParseSuccess {
directives,
options: _,
plugins: _,
mut warnings,
}) => {
let mut errors = Vec::new();
for directive in directives {
if let DirectiveVariant::Transaction(transaction) = directive.variant() {
let mut postings = transaction.postings().collect::<Vec<_>>();
let n_postings = postings.len();
let n_amounts = itertools::partition(&mut postings, |p| p.amount().is_some());
if postings.is_empty() {
warnings.push(directive.warning("no postings"));
} else if n_amounts + 1 < n_postings {
errors.push(
directive
.error("multiple postings without amount specified")
.related_to_all(postings[n_amounts..].iter().copied()),
);
} else if n_amounts == n_postings {
let total: Decimal =
postings.iter().map(|p| p.amount().unwrap().value()).sum();
if total != Decimal::ZERO {
let last_amount = postings.pop().unwrap().amount().unwrap();
let other_amounts = postings.iter().map(|p| p.amount().unwrap());
errors.push(
last_amount
.error(format!("sum is {}, expected zero", total))
.related_to_all(other_amounts)
.in_context(&directive),
)
}
}
}
}
sources.write(error_w, errors).unwrap();
sources.write(error_w, warnings).unwrap();
}
Err(ParseError { errors, warnings }) => {
sources.write(error_w, errors).unwrap();
sources.write(error_w, warnings).unwrap();
}
}
}
Re-exports§
pub use types::*;
Modules§
Structs§
- The Beancount parser itself, which tokenizes and parses the source files contained in
BeancountSources
. - Contains the content of the Beancount source file, and the content of the transitive closure of all the include’d source files.
- All options read in from
option
pragmas, excluding those for internal processing only. - The value returned when parsing fails.
- A successful parsing all the files, containing date-ordered
Directive
s,Options
,Plugin
s, and anyWarning
s.