use super::ast::*;
use super::lexer::Token;
use super::parser::{BashParser, ParseResult};
impl BashParser {
pub(crate) fn parse_variable_expansion(&self, var_content: &str) -> ParseResult<BashExpr> {
if var_content.starts_with('#') && var_content.len() > 1 && !var_content.contains(':') {
let variable = var_content[1..].to_string();
return Ok(BashExpr::StringLength { variable });
}
if let Some(pos) = var_content.find(":-") {
let variable = var_content[..pos].to_string();
let default = var_content[pos + 2..].to_string();
return Ok(BashExpr::DefaultValue {
variable,
default: Box::new(BashExpr::Literal(default)),
});
}
if let Some(pos) = var_content.find(":=") {
let variable = var_content[..pos].to_string();
let default = var_content[pos + 2..].to_string();
return Ok(BashExpr::AssignDefault {
variable,
default: Box::new(BashExpr::Literal(default)),
});
}
if let Some(pos) = var_content.find(":+") {
let variable = var_content[..pos].to_string();
let alternative = var_content[pos + 2..].to_string();
return Ok(BashExpr::AlternativeValue {
variable,
alternative: Box::new(BashExpr::Literal(alternative)),
});
}
if let Some(pos) = var_content.find(":?") {
let variable = var_content[..pos].to_string();
let message = var_content[pos + 2..].to_string();
return Ok(BashExpr::ErrorIfUnset {
variable,
message: Box::new(BashExpr::Literal(message)),
});
}
if let Some(pos) = var_content.find("##") {
let variable = var_content[..pos].to_string();
let pattern = var_content[pos + 2..].to_string();
return Ok(BashExpr::RemoveLongestPrefix {
variable,
pattern: Box::new(BashExpr::Literal(pattern)),
});
}
if let Some(pos) = var_content.find('#') {
if pos > 0 {
let variable = var_content[..pos].to_string();
let pattern = var_content[pos + 1..].to_string();
return Ok(BashExpr::RemovePrefix {
variable,
pattern: Box::new(BashExpr::Literal(pattern)),
});
}
}
if let Some(pos) = var_content.find("%%") {
let variable = var_content[..pos].to_string();
let pattern = var_content[pos + 2..].to_string();
return Ok(BashExpr::RemoveLongestSuffix {
variable,
pattern: Box::new(BashExpr::Literal(pattern)),
});
}
if let Some(pos) = var_content.find('%') {
let variable = var_content[..pos].to_string();
let pattern = var_content[pos + 1..].to_string();
return Ok(BashExpr::RemoveSuffix {
variable,
pattern: Box::new(BashExpr::Literal(pattern)),
});
}
Ok(BashExpr::Variable(var_content.to_string()))
}
pub(crate) fn parse_expression(&mut self) -> ParseResult<BashExpr> {
match self.peek() {
Some(Token::String(s)) => {
let str = s.clone();
self.advance();
Ok(BashExpr::Literal(str))
}
Some(Token::Number(n)) => {
let num = *n;
self.advance();
Ok(BashExpr::Literal(num.to_string()))
}
Some(Token::Variable(v)) => {
let var = v.clone();
self.advance();
self.parse_variable_expansion(&var)
}
Some(Token::Identifier(s)) => {
let ident = s.clone();
self.advance();
if ident.contains('*') || ident.contains('?') {
Ok(BashExpr::Glob(ident))
} else {
Ok(BashExpr::Literal(ident))
}
}
Some(Token::LeftParen) => self.parse_array_literal(),
Some(Token::ArithmeticExpansion(expr)) => {
let expr_str = expr.clone();
self.advance();
let arith_expr = self.parse_arithmetic_expr(&expr_str)?;
Ok(BashExpr::Arithmetic(Box::new(arith_expr)))
}
Some(Token::CommandSubstitution(cmd)) => {
let cmd_str = cmd.clone();
self.advance();
Ok(BashExpr::CommandSubst(Box::new(BashStmt::Command {
name: cmd_str.clone(),
args: vec![],
redirects: vec![],
span: Span {
start_line: 0,
start_col: 0,
end_line: 0,
end_col: 0,
},
})))
}
Some(Token::Heredoc {
delimiter: _,
content,
}) => {
let content_str = content.clone();
self.advance();
Ok(BashExpr::Literal(content_str))
}
Some(Token::LeftBracket) => self.parse_glob_bracket_pattern(),
Some(Token::LeftBrace) if self.peek_ahead(1) == Some(&Token::RightBrace) => {
self.advance(); self.advance(); Ok(BashExpr::Literal("{}".to_string()))
}
Some(t) if Self::keyword_as_str(t).is_some() => {
#[allow(clippy::expect_used)]
let kw = Self::keyword_as_str(t).expect("checked is_some");
self.advance();
Ok(BashExpr::Literal(kw.to_string()))
}
_ => Err(self.syntax_error("expression")),
}
}
fn parse_array_literal(&mut self) -> ParseResult<BashExpr> {
self.advance(); let mut elements = Vec::new();
while !self.is_at_end() && !self.check(&Token::RightParen) {
if self.check(&Token::LeftBracket) {
elements.push(self.parse_sparse_array_element()?);
} else if self.check(&Token::Newline) {
self.advance();
} else {
elements.push(self.parse_expression()?);
}
}
self.expect(Token::RightParen)?;
Ok(BashExpr::Array(elements))
}
fn parse_sparse_array_element(&mut self) -> ParseResult<BashExpr> {
self.advance(); let index = self.collect_bracket_index();
if self.check(&Token::RightBracket) {
self.advance(); }
if self.check(&Token::Assign) {
self.advance(); }
if self.is_at_end() || self.check(&Token::RightParen) {
return Ok(BashExpr::Literal(format!("[{index}]=")));
}
let value = self.parse_expression()?;
let value_str = match &value {
BashExpr::Literal(s) => s.clone(),
BashExpr::Variable(v) => format!("${v}"),
_ => "?".to_string(),
};
Ok(BashExpr::Literal(format!("[{index}]={value_str}")))
}
fn collect_bracket_index(&mut self) -> String {
let mut index = String::new();
while !self.is_at_end() && !self.check(&Token::RightBracket) {
match self.peek() {
Some(Token::Identifier(s) | Token::String(s)) => {
index.push_str(s);
self.advance();
}
Some(Token::Number(n)) => {
index.push_str(&n.to_string());
self.advance();
}
_ => break,
}
}
index
}
fn parse_glob_bracket_pattern(&mut self) -> ParseResult<BashExpr> {
let mut pattern = String::from("[");
self.advance(); while !self.is_at_end() && !self.check(&Token::RightBracket) {
match self.peek() {
Some(Token::Identifier(s)) => {
pattern.push_str(s);
self.advance();
}
Some(Token::Number(n)) => {
pattern.push_str(&n.to_string());
self.advance();
}
Some(Token::Not) => {
pattern.push('!');
self.advance();
}
_ => break,
}
}
if self.check(&Token::RightBracket) {
pattern.push(']');
self.advance();
}
while let Some(Token::Identifier(s)) = self.peek() {
pattern.push_str(s);
self.advance();
}
Ok(BashExpr::Glob(pattern))
}
pub(crate) fn keyword_as_str(token: &Token) -> Option<&'static str> {
match token {
Token::If => Some("if"),
Token::Then => Some("then"),
Token::Elif => Some("elif"),
Token::Else => Some("else"),
Token::Fi => Some("fi"),
Token::For => Some("for"),
Token::While => Some("while"),
Token::Until => Some("until"),
Token::Do => Some("do"),
Token::Done => Some("done"),
Token::Case => Some("case"),
Token::Esac => Some("esac"),
Token::In => Some("in"),
Token::Function => Some("function"),
Token::Return => Some("return"),
Token::Export => Some("export"),
Token::Local => Some("local"),
Token::Coproc => Some("coproc"),
Token::Select => Some("select"),
_ => None,
}
}
}
include!("parser_expr_methods.rs");