Skip to main content

rable/
lib.rs

1//! # Rable — A complete GNU Bash 5.3-compatible parser
2//!
3//! Rable parses bash source code into an abstract syntax tree (AST) of [`Node`]
4//! values. Each node can be formatted as an S-expression via its [`Display`]
5//! implementation, producing output identical to [Parable](https://github.com/ldayton/Parable).
6//!
7//! # Quick Start
8//!
9//! ```
10//! use rable::{parse, NodeKind};
11//!
12//! let nodes = parse("echo hello | grep h", false).unwrap();
13//! assert_eq!(nodes.len(), 1);
14//!
15//! // S-expression output via Display
16//! let sexp = nodes[0].to_string();
17//! assert!(sexp.contains("pipe"));
18//! ```
19//!
20//! # Parsing Options
21//!
22//! The `extglob` parameter enables extended glob patterns (`@()`, `?()`, `*()`,
23//! `+()`, `!()`). Set to `false` for standard bash parsing.
24//!
25//! ```
26//! // Standard parsing
27//! let nodes = rable::parse("echo hello", false).unwrap();
28//!
29//! // With extended globs
30//! let nodes = rable::parse("echo @(foo|bar)", true).unwrap();
31//! ```
32//!
33//! # Error Handling
34//!
35//! Parse errors include line and position information:
36//!
37//! ```
38//! match rable::parse("if", false) {
39//!     Ok(_) => unreachable!(),
40//!     Err(e) => {
41//!         assert_eq!(e.line(), 1);
42//!         println!("Error: {e}");
43//!     }
44//! }
45//! ```
46//!
47//! # Working with the AST
48//!
49//! The AST uses a [`Node`] struct wrapping a [`NodeKind`] enum with a [`Span`].
50//! Pattern matching on `node.kind` is the primary way to inspect nodes:
51//!
52//! ```
53//! use rable::{parse, NodeKind};
54//!
55//! let nodes = parse("echo hello world", false).unwrap();
56//! match &nodes[0].kind {
57//!     NodeKind::Command { words, redirects, .. } => {
58//!         assert_eq!(words.len(), 3); // echo, hello, world
59//!         assert!(redirects.is_empty());
60//!     }
61//!     _ => panic!("expected Command"),
62//! }
63//! ```
64
65pub mod ast;
66pub mod error;
67pub mod token;
68
69// Public for advanced use (S-expression formatting)
70pub mod sexp;
71
72// Implementation details — not part of the stable API
73pub(crate) mod context;
74pub(crate) mod format;
75pub(crate) mod lexer;
76pub(crate) mod parser;
77
78#[cfg(feature = "python")]
79mod python;
80
81// Convenient re-exports
82pub use ast::{CasePattern, ListItem, ListOperator, Node, NodeKind, PipeSep, Span};
83pub use error::{RableError, Result};
84pub use token::{Token, TokenType};
85
86/// Parses a bash source string into a list of top-level AST nodes.
87///
88/// Each top-level command separated by newlines becomes a separate node.
89/// Commands separated by `;` on the same line are grouped into a single
90/// [`NodeKind::List`].
91///
92/// Set `extglob` to `true` to enable extended glob patterns (`@()`, `?()`,
93/// `*()`, `+()`, `!()`).
94///
95/// # Examples
96///
97/// ```
98/// let nodes = rable::parse("echo hello", false).unwrap();
99/// assert_eq!(nodes[0].to_string(), "(command (word \"echo\") (word \"hello\"))");
100/// ```
101///
102/// ```
103/// // Multiple top-level commands
104/// let nodes = rable::parse("echo a\necho b", false).unwrap();
105/// assert_eq!(nodes.len(), 2);
106/// ```
107///
108/// # Errors
109///
110/// Returns [`RableError::Parse`] for syntax errors and
111/// [`RableError::MatchedPair`] for unclosed delimiters.
112pub fn parse(source: &str, extglob: bool) -> Result<Vec<Node>> {
113    let lexer = lexer::Lexer::new(source, extglob);
114    let mut parser = parser::Parser::new(lexer);
115    parser.parse_all()
116}