rustledger_parser/lib.rs
1//! Beancount parser using chumsky parser combinators.
2//!
3//! This crate provides a parser for the Beancount file format. It produces
4//! a stream of [`Directive`]s from source text, along with any parse errors.
5//!
6//! # Features
7//!
8//! - Full Beancount syntax support (all 12 directive types)
9//! - Error recovery (continues parsing after errors)
10//! - Precise source locations for error reporting
11//! - Support for includes, options, plugins
12//!
13//! # Example
14//!
15//! ```ignore
16//! use rustledger_parser::parse;
17//!
18//! let source = r#"
19//! 2024-01-15 * "Coffee Shop" "Morning coffee"
20//! Expenses:Food:Coffee 5.00 USD
21//! Assets:Cash
22//! "#;
23//!
24//! let (directives, errors) = parse(source);
25//! assert!(errors.is_empty());
26//! assert_eq!(directives.len(), 1);
27//! ```
28
29#![forbid(unsafe_code)]
30#![warn(missing_docs)]
31
32mod error;
33pub mod logos_lexer;
34mod span;
35mod token_parser;
36mod winnow_parser;
37
38pub use error::{ParseError, ParseErrorKind};
39pub use span::{Span, Spanned};
40
41use rustledger_core::Directive;
42
43/// Result of parsing a beancount file.
44#[derive(Debug)]
45pub struct ParseResult {
46 /// Successfully parsed directives.
47 pub directives: Vec<Spanned<Directive>>,
48 /// Options found in the file.
49 pub options: Vec<(String, String, Span)>,
50 /// Include directives found.
51 pub includes: Vec<(String, Span)>,
52 /// Plugin directives found.
53 pub plugins: Vec<(String, Option<String>, Span)>,
54 /// Standalone comments found in the file.
55 pub comments: Vec<Spanned<String>>,
56 /// Parse errors encountered.
57 pub errors: Vec<ParseError>,
58 /// Deprecation warnings.
59 pub warnings: Vec<ParseWarning>,
60}
61
62/// A warning from the parser (non-fatal).
63#[derive(Debug, Clone)]
64pub struct ParseWarning {
65 /// The warning message.
66 pub message: String,
67 /// Location in source.
68 pub span: Span,
69}
70
71impl ParseWarning {
72 /// Create a new warning.
73 pub fn new(message: impl Into<String>, span: Span) -> Self {
74 Self {
75 message: message.into(),
76 span,
77 }
78 }
79}
80
81/// Parse beancount source code.
82///
83/// Uses a fast token-based parser (Logos lexer + Winnow combinators).
84///
85/// # Arguments
86///
87/// * `source` - The beancount source code to parse
88///
89/// # Returns
90///
91/// A `ParseResult` containing directives, options, includes, plugins, and errors.
92pub fn parse(source: &str) -> ParseResult {
93 winnow_parser::parse(source)
94}
95
96/// Parse beancount source code using chumsky parser (legacy).
97///
98/// This is kept for comparison/benchmarking purposes.
99#[doc(hidden)]
100pub fn parse_chumsky(source: &str) -> ParseResult {
101 token_parser::parse(source)
102}
103
104/// Parse beancount source code, returning only directives and errors.
105///
106/// This is a simpler interface when you don't need options/includes/plugins.
107pub fn parse_directives(source: &str) -> (Vec<Spanned<Directive>>, Vec<ParseError>) {
108 let result = parse(source);
109 (result.directives, result.errors)
110}