use std::path::PathBuf;
use bon::bon;
use crate::ast;
use crate::tokenizer::{Token, TokenEndReason, Tokenizer, TokenizerOptions, Tokens};
pub mod peg;
#[cfg(feature = "winnow-parser")]
pub mod winnow_str;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)]
pub enum ParserImpl {
#[default]
Peg,
#[cfg(feature = "winnow-parser")]
Winnow,
}
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct ParserOptions {
pub enable_extended_globbing: bool,
pub posix_mode: bool,
pub sh_mode: bool,
pub tilde_expansion_at_word_start: bool,
pub tilde_expansion_after_colon: bool,
pub parser_impl: ParserImpl,
}
impl Default for ParserOptions {
fn default() -> Self {
Self {
enable_extended_globbing: true,
posix_mode: false,
sh_mode: false,
tilde_expansion_at_word_start: true,
tilde_expansion_after_colon: false,
parser_impl: ParserImpl::default(),
}
}
}
impl ParserOptions {
pub const fn tokenizer_options(&self) -> TokenizerOptions {
TokenizerOptions {
enable_extended_globbing: self.enable_extended_globbing,
posix_mode: self.posix_mode,
sh_mode: self.sh_mode,
}
}
}
#[derive(Clone, Debug, Default)]
#[allow(dead_code)]
pub struct SourceInfo {
pub source: String,
}
impl From<PathBuf> for SourceInfo {
fn from(path: PathBuf) -> Self {
Self {
source: path.to_string_lossy().to_string(),
}
}
}
pub struct Parser<R: std::io::BufRead> {
reader: R,
options: ParserOptions,
}
#[bon]
impl<R: std::io::BufRead> Parser<R> {
pub fn new(reader: R, options: &ParserOptions) -> Self {
Self {
reader,
options: options.clone(),
}
}
#[builder(
finish_fn(doc {
/// Instantiate a parser with the provided reader as input
})
)]
pub const fn builder(
#[builder(finish_fn)]
reader: R,
#[builder(default = true)]
enable_extended_globbing: bool,
#[builder(default = false)]
posix_mode: bool,
#[builder(default = false)]
sh_mode: bool,
#[builder(default = true)]
tilde_expansion_at_word_start: bool,
#[builder(default = false)]
tilde_expansion_after_colon: bool,
#[builder(default)]
parser_impl: ParserImpl,
) -> Self {
let options = ParserOptions {
enable_extended_globbing,
posix_mode,
sh_mode,
tilde_expansion_at_word_start,
tilde_expansion_after_colon,
parser_impl,
};
Self { reader, options }
}
pub fn parse_program(&mut self) -> Result<ast::Program, crate::error::ParseError> {
match self.options.parser_impl {
ParserImpl::Peg => {
let tokens = self.tokenize()?;
parse_tokens(&tokens, &self.options)
}
#[cfg(feature = "winnow-parser")]
ParserImpl::Winnow => {
let mut input_str = String::new();
std::io::Read::read_to_string(&mut self.reader, &mut input_str).map_err(|e| {
crate::error::ParseError::Tokenizing {
inner: crate::tokenizer::TokenizerError::from(e),
position: None,
}
})?;
winnow_str::parse_program(&input_str, &self.options, &SourceInfo::default())
.map_err(|_e| {
crate::error::ParseError::ParsingAtEndOfInput
})
}
}
}
pub fn parse_function_parens_and_body(
&mut self,
) -> Result<ast::FunctionBody, crate::error::ParseError> {
let tokens = self.tokenize()?;
let parse_result =
peg::token_parser::function_parens_and_body(&Tokens { tokens: &tokens }, &self.options);
parse_result_to_error(parse_result, &tokens)
}
fn tokenize(&mut self) -> Result<Vec<Token>, crate::error::ParseError> {
let mut tokenizer = Tokenizer::new(&mut self.reader, &self.options.tokenizer_options());
tracing::debug!(target: "tokenize", "Tokenizing...");
let mut tokens = vec![];
loop {
let result = match tokenizer.next_token() {
Ok(result) => result,
Err(e) => {
return Err(crate::error::ParseError::Tokenizing {
inner: e,
position: tokenizer.current_location(),
});
}
};
let reason = result.reason;
if let Some(token) = result.token {
tracing::debug!(target: "tokenize", "TOKEN {}: {:?} {reason:?}", tokens.len(), token);
tokens.push(token);
}
if matches!(reason, TokenEndReason::EndOfInput) {
break;
}
}
tracing::debug!(target: "tokenize", " => {} token(s)", tokens.len());
Ok(tokens)
}
}
pub fn parse_tokens(
tokens: &[Token],
options: &ParserOptions,
) -> Result<ast::Program, crate::error::ParseError> {
let parse_result = peg::token_parser::program(&Tokens { tokens }, options);
parse_result_to_error(parse_result, tokens)
}
fn parse_result_to_error<R>(
parse_result: Result<R, ::peg::error::ParseError<usize>>,
tokens: &[Token],
) -> Result<R, crate::error::ParseError>
where
R: std::fmt::Debug,
{
match parse_result {
Ok(program) => {
tracing::debug!(target: "parse", "PROG: {:?}", program);
Ok(program)
}
Err(parse_error) => {
tracing::debug!(target: "parse", "Parse error: {:?}", parse_error);
Err(crate::error::convert_peg_parse_error(&parse_error, tokens))
}
}
}
#[cfg(test)]
mod tests;