1use std::path::PathBuf;
2
3use bon::bon;
4
5use crate::ast;
6use crate::tokenizer::{Token, TokenEndReason, Tokenizer, TokenizerOptions, Tokens};
7
8pub mod peg;
9#[cfg(feature = "winnow-parser")]
10pub mod winnow_str;
11
12#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)]
14pub enum ParserImpl {
15 #[default]
17 Peg,
18 #[cfg(feature = "winnow-parser")]
20 Winnow,
21}
22
23#[derive(Clone, Eq, Hash, PartialEq)]
25pub struct ParserOptions {
26 pub enable_extended_globbing: bool,
28 pub posix_mode: bool,
30 pub sh_mode: bool,
32 pub tilde_expansion_at_word_start: bool,
35 pub tilde_expansion_after_colon: bool,
37 pub parser_impl: ParserImpl,
39}
40
41impl Default for ParserOptions {
42 fn default() -> Self {
43 Self {
44 enable_extended_globbing: true,
45 posix_mode: false,
46 sh_mode: false,
47 tilde_expansion_at_word_start: true,
48 tilde_expansion_after_colon: false,
49 parser_impl: ParserImpl::default(),
50 }
51 }
52}
53
54impl ParserOptions {
55 pub const fn tokenizer_options(&self) -> TokenizerOptions {
57 TokenizerOptions {
58 enable_extended_globbing: self.enable_extended_globbing,
59 posix_mode: self.posix_mode,
60 sh_mode: self.sh_mode,
61 }
62 }
63}
64
65#[derive(Clone, Debug, Default)]
67#[allow(dead_code)]
68pub struct SourceInfo {
69 pub source: String,
71}
72
73impl From<PathBuf> for SourceInfo {
74 fn from(path: PathBuf) -> Self {
75 Self {
76 source: path.to_string_lossy().to_string(),
77 }
78 }
79}
80
81pub struct Parser<R: std::io::BufRead> {
83 reader: R,
85 options: ParserOptions,
87}
88
89#[bon]
90impl<R: std::io::BufRead> Parser<R> {
91 pub fn new(reader: R, options: &ParserOptions) -> Self {
97 Self {
98 reader,
99 options: options.clone(),
100 }
101 }
102
103 #[builder(
105 finish_fn(doc {
106 })
108 )]
109 pub const fn builder(
110 #[builder(finish_fn)]
112 reader: R,
113
114 #[builder(default = true)]
115 enable_extended_globbing: bool,
117 #[builder(default = false)]
118 posix_mode: bool,
120 #[builder(default = false)]
121 sh_mode: bool,
123 #[builder(default = true)]
124 tilde_expansion_at_word_start: bool,
127 #[builder(default = false)]
128 tilde_expansion_after_colon: bool,
130 #[builder(default)]
131 parser_impl: ParserImpl,
133 ) -> Self {
134 let options = ParserOptions {
135 enable_extended_globbing,
136 posix_mode,
137 sh_mode,
138 tilde_expansion_at_word_start,
139 tilde_expansion_after_colon,
140 parser_impl,
141 };
142 Self { reader, options }
143 }
144
145 pub fn parse_program(&mut self) -> Result<ast::Program, crate::error::ParseError> {
147 match self.options.parser_impl {
155 ParserImpl::Peg => {
156 let tokens = self.tokenize()?;
157 parse_tokens(&tokens, &self.options)
158 }
159 #[cfg(feature = "winnow-parser")]
160 ParserImpl::Winnow => {
161 let mut input_str = String::new();
163 std::io::Read::read_to_string(&mut self.reader, &mut input_str).map_err(|e| {
164 crate::error::ParseError::Tokenizing {
165 inner: crate::tokenizer::TokenizerError::from(e),
166 position: None,
167 }
168 })?;
169
170 winnow_str::parse_program(&input_str, &self.options, &SourceInfo::default())
171 .map_err(|_e| {
172 crate::error::ParseError::ParsingAtEndOfInput
175 })
176 }
177 }
178 }
179
180 pub fn parse_function_parens_and_body(
183 &mut self,
184 ) -> Result<ast::FunctionBody, crate::error::ParseError> {
185 let tokens = self.tokenize()?;
186 let parse_result =
187 peg::token_parser::function_parens_and_body(&Tokens { tokens: &tokens }, &self.options);
188 parse_result_to_error(parse_result, &tokens)
189 }
190
191 fn tokenize(&mut self) -> Result<Vec<Token>, crate::error::ParseError> {
192 let mut tokenizer = Tokenizer::new(&mut self.reader, &self.options.tokenizer_options());
195
196 tracing::debug!(target: "tokenize", "Tokenizing...");
197
198 let mut tokens = vec![];
199 loop {
200 let result = match tokenizer.next_token() {
201 Ok(result) => result,
202 Err(e) => {
203 return Err(crate::error::ParseError::Tokenizing {
204 inner: e,
205 position: tokenizer.current_location(),
206 });
207 }
208 };
209
210 let reason = result.reason;
211 if let Some(token) = result.token {
212 tracing::debug!(target: "tokenize", "TOKEN {}: {:?} {reason:?}", tokens.len(), token);
213 tokens.push(token);
214 }
215
216 if matches!(reason, TokenEndReason::EndOfInput) {
217 break;
218 }
219 }
220
221 tracing::debug!(target: "tokenize", " => {} token(s)", tokens.len());
222
223 Ok(tokens)
224 }
225}
226
227pub fn parse_tokens(
235 tokens: &[Token],
236 options: &ParserOptions,
237) -> Result<ast::Program, crate::error::ParseError> {
238 let parse_result = peg::token_parser::program(&Tokens { tokens }, options);
239 parse_result_to_error(parse_result, tokens)
240}
241
242pub(crate) fn parse_compound_assignment_value(
251 input: &str,
252 options: &ParserOptions,
253) -> Option<Vec<String>> {
254 let mut parser = Parser::new(input.as_bytes(), options);
255 let tokens = parser.tokenize().ok()?;
256 let tokens = Tokens { tokens: &tokens };
257 let elements = peg::token_parser::compound_assignment_value(&tokens, options).ok()?;
258 Some(elements.into_iter().cloned().collect())
259}
260
261fn parse_result_to_error<R>(
262 parse_result: Result<R, ::peg::error::ParseError<usize>>,
263 tokens: &[Token],
264) -> Result<R, crate::error::ParseError>
265where
266 R: std::fmt::Debug,
267{
268 match parse_result {
269 Ok(program) => {
270 tracing::debug!(target: "parse", "PROG: {:?}", program);
271 Ok(program)
272 }
273 Err(parse_error) => {
274 tracing::debug!(target: "parse", "Parse error: {:?}", parse_error);
275 Err(crate::error::convert_peg_parse_error(&parse_error, tokens))
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests;