Skip to main content

rustledger_parser/
lib.rs

1//! Beancount parser using Logos lexer and Winnow manual parser.
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 winnow_parser;
36
37pub use error::{ParseError, ParseErrorKind};
38pub use span::{SYNTHESIZED_FILE_ID, Span, Spanned};
39
40use rustledger_core::Directive;
41
42/// Result of parsing a beancount file.
43#[derive(Debug)]
44pub struct ParseResult {
45    /// Successfully parsed directives.
46    pub directives: Vec<Spanned<Directive>>,
47    /// Options found in the file.
48    pub options: Vec<(String, String, Span)>,
49    /// Include directives found.
50    pub includes: Vec<(String, Span)>,
51    /// Plugin directives found.
52    pub plugins: Vec<(String, Option<String>, Span)>,
53    /// Standalone comments found in the file.
54    pub comments: Vec<Spanned<String>>,
55    /// Parse errors encountered.
56    pub errors: Vec<ParseError>,
57    /// Deprecation warnings.
58    pub warnings: Vec<ParseWarning>,
59}
60
61/// A warning from the parser (non-fatal).
62#[derive(Debug, Clone)]
63pub struct ParseWarning {
64    /// The warning message.
65    pub message: String,
66    /// Location in source.
67    pub span: Span,
68}
69
70impl ParseWarning {
71    /// Create a new warning.
72    pub fn new(message: impl Into<String>, span: Span) -> Self {
73        Self {
74            message: message.into(),
75            span,
76        }
77    }
78}
79
80/// Parse beancount source code.
81///
82/// Uses a fast token-based parser (Logos lexer + Winnow combinators).
83///
84/// # Arguments
85///
86/// * `source` - The beancount source code to parse
87///
88/// # Returns
89///
90/// A `ParseResult` containing directives, options, includes, plugins, and errors.
91pub fn parse(source: &str) -> ParseResult {
92    winnow_parser::parse(source)
93}
94
95/// Parse beancount source code, returning only directives and errors.
96///
97/// This is a simpler interface when you don't need options/includes/plugins.
98pub fn parse_directives(source: &str) -> (Vec<Spanned<Directive>>, Vec<ParseError>) {
99    let result = parse(source);
100    (result.directives, result.errors)
101}