#![allow(clippy::approx_constant)]
use super::{bail, parse_expr_recursive, Expr, ExprKind, ParserState, Result, Span, Token};
use crate::frontend::ast::Literal;
pub fn parse_dataframe_macro(state: &mut ParserState) -> Result<Option<Expr>> {
state.tokens.advance();
if !matches!(state.tokens.peek(), Some((Token::LeftBracket, _))) {
return Ok(None); }
state.tokens.advance();
if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
state.tokens.advance(); return Ok(Some(Expr::new(
ExprKind::DataFrame {
columns: Vec::new(),
},
Span::default(),
)));
}
let columns = super::collections::parse_dataframe_column_definitions(state)?;
state.tokens.expect(&Token::RightBracket)?;
Ok(Some(Expr::new(
ExprKind::DataFrame { columns },
Span::default(),
)))
}
pub fn parse_sql_macro(state: &mut ParserState, name: &str) -> Result<Expr> {
state.tokens.advance();
let sql_content = collect_sql_content(state)?;
Ok(Expr::new(
ExprKind::Macro {
name: name.to_string(),
args: vec![Expr::new(
ExprKind::Literal(Literal::String(sql_content)),
Span::default(),
)],
},
Span::default(),
))
}
fn collect_sql_content(state: &mut ParserState) -> Result<String> {
let mut sql_content = String::new();
let mut depth = 1;
while depth > 0 {
if let Some((token, _)) = state.tokens.peek() {
match token {
Token::LeftBrace => depth += 1,
Token::RightBrace => {
depth -= 1;
if depth == 0 {
state.tokens.advance(); break;
}
}
_ => {}
}
if depth > 0 {
let token_text = convert_token_to_sql(token);
if !sql_content.is_empty() {
sql_content.push(' ');
}
sql_content.push_str(&token_text);
state.tokens.advance();
}
} else {
bail!("Unclosed SQL macro");
}
}
Ok(sql_content)
}
fn convert_token_to_sql(token: &Token) -> String {
match token {
Token::Identifier(s) => s.clone(),
Token::Integer(n) => n.clone(),
Token::Float(f) => f.to_string(),
Token::String(s) => format!("'{s}'"),
Token::Star => "*".to_string(),
Token::Greater => ">".to_string(),
Token::Less => "<".to_string(),
Token::GreaterEqual => ">=".to_string(),
Token::LessEqual => "<=".to_string(),
Token::EqualEqual => "=".to_string(),
Token::NotEqual => "!=".to_string(),
Token::Comma => ",".to_string(),
Token::LeftParen => "(".to_string(),
Token::RightParen => ")".to_string(),
Token::Dot => ".".to_string(),
Token::Plus => "+".to_string(),
Token::Minus => "-".to_string(),
Token::Slash => "/".to_string(),
_ => format!("{token:?}"), }
}
pub fn get_macro_delimiters(state: &mut ParserState) -> Option<(&'static str, Token)> {
match state.tokens.peek() {
Some((Token::LeftParen, _)) => {
state.tokens.advance(); Some(("paren", Token::RightParen))
}
Some((Token::LeftBracket, _)) => {
state.tokens.advance(); Some(("bracket", Token::RightBracket))
}
Some((Token::LeftBrace, _)) => {
state.tokens.advance(); Some(("brace", Token::RightBrace))
}
_ => None,
}
}
pub fn parse_macro_arguments(state: &mut ParserState, closing_token: Token) -> Result<Vec<Expr>> {
let mut args = Vec::new();
while !matches!(state.tokens.peek(), Some((token, _)) if token == &closing_token) {
args.push(parse_expr_recursive(state)?);
if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
state.tokens.advance(); } else {
break;
}
}
state.tokens.expect(&closing_token)?;
Ok(args)
}
fn parse_remaining_elements(state: &mut ParserState, first: Expr) -> Result<Vec<Expr>> {
let mut args = vec![first];
state.tokens.advance();
while !matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
args.push(parse_expr_recursive(state)?);
if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
state.tokens.advance();
} else {
break;
}
}
state.tokens.expect(&Token::RightBracket)?;
Ok(args)
}
pub fn parse_vec_macro(state: &mut ParserState) -> Result<Option<Expr>> {
state.tokens.advance();
if !matches!(state.tokens.peek(), Some((Token::LeftBracket, _))) {
return Ok(None); }
state.tokens.advance();
if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
state.tokens.advance(); return Ok(Some(create_macro_expr("vec".to_string(), Vec::new())));
}
let first_expr = parse_expr_recursive(state)?;
match state.tokens.peek() {
Some((Token::Semicolon, _)) => {
state.tokens.advance(); let size_expr = parse_expr_recursive(state)?;
state.tokens.expect(&Token::RightBracket)?;
Ok(Some(Expr::new(
ExprKind::VecRepeat {
value: Box::new(first_expr),
count: Box::new(size_expr),
},
Span::default(),
)))
}
Some((Token::Comma, _)) => {
let args = parse_remaining_elements(state, first_expr)?;
Ok(Some(create_macro_expr("vec".to_string(), args)))
}
Some((Token::RightBracket, _)) => {
state.tokens.advance(); Ok(Some(create_macro_expr("vec".to_string(), vec![first_expr])))
}
_ => bail!("Unexpected token in vec![] macro"),
}
}
pub fn create_macro_expr(name: String, args: Vec<Expr>) -> Expr {
Expr::new(ExprKind::MacroInvocation { name, args }, Span::default())
}
#[cfg(test)]
mod tests {
use super::super::Parser;
use super::*;
#[test]
fn test_convert_token_to_sql_all_match_arms() {
assert_eq!(
convert_token_to_sql(&Token::Identifier("SELECT".to_string())),
"SELECT"
);
assert_eq!(
convert_token_to_sql(&Token::Integer("42".to_string())),
"42"
);
assert_eq!(convert_token_to_sql(&Token::Float(3.15)), "3.15");
assert_eq!(
convert_token_to_sql(&Token::String("test".to_string())),
"'test'"
);
assert_eq!(convert_token_to_sql(&Token::Star), "*");
assert_eq!(convert_token_to_sql(&Token::Greater), ">");
assert_eq!(convert_token_to_sql(&Token::Less), "<");
assert_eq!(convert_token_to_sql(&Token::GreaterEqual), ">=");
assert_eq!(
convert_token_to_sql(&Token::LessEqual),
"<=",
"LessEqual token (line 104)"
);
assert_eq!(
convert_token_to_sql(&Token::EqualEqual),
"=",
"EqualEqual token (line 105)"
);
assert_eq!(convert_token_to_sql(&Token::NotEqual), "!=");
assert_eq!(
convert_token_to_sql(&Token::Comma),
",",
"Comma token (line 107)"
);
assert_eq!(
convert_token_to_sql(&Token::LeftParen),
"(",
"LeftParen token (line 108)"
);
assert_eq!(
convert_token_to_sql(&Token::RightParen),
")",
"RightParen token (line 109)"
);
assert_eq!(
convert_token_to_sql(&Token::Dot),
".",
"Dot token (line 110)"
);
assert_eq!(convert_token_to_sql(&Token::Plus), "+");
assert_eq!(convert_token_to_sql(&Token::Minus), "-");
assert_eq!(
convert_token_to_sql(&Token::Slash),
"/",
"Slash token (line 113)"
);
}
#[test]
fn test_parse_dataframe_macro_returns_some() {
let mut parser = Parser::new("df![]");
let result = parser.parse();
assert!(result.is_ok(), "Should parse empty df![] macro");
}
#[test]
fn test_parse_dataframe_macro_with_columns() {
let mut parser = Parser::new("df![]");
let result = parser.parse();
assert!(result.is_ok(), "Should parse df![] macro (empty dataframe)");
}
#[test]
fn test_parse_dataframe_macro_returns_none_for_non_bracket() {
let mut parser = Parser::new("df!(args)");
let result = parser.parse();
assert!(result.is_ok(), "Should parse df!() as regular macro");
}
#[test]
fn test_sql_macro_parsing() {
let mut parser = Parser::new("sql!{ SELECT * FROM users }");
let result = parser.parse();
assert!(result.is_ok(), "Should parse sql! macro with braces");
}
#[test]
fn test_collect_sql_content_with_nested_braces() {
let mut parser = Parser::new("sql!{ SELECT CASE WHEN {nested} END }");
let result = parser.parse();
assert!(result.is_ok(), "Should handle nested braces in SQL content");
}
#[test]
fn test_collect_sql_content_with_left_brace() {
let mut parser = Parser::new("sql!{ SELECT { }");
let result = parser.parse();
assert!(
result.is_ok() || result.is_err(),
"Should process LeftBrace token"
);
}
#[test]
fn test_get_macro_delimiters_returns_some() {
let mut parser = Parser::new("vec![1, 2, 3]");
let result = parser.parse();
assert!(
result.is_ok(),
"Should parse vec![] with bracket delimiters"
);
}
#[test]
fn test_get_macro_delimiters_paren() {
let mut parser = Parser::new("println!(\"hello\")");
let result = parser.parse();
assert!(result.is_ok(), "Should parse macro with paren delimiters");
}
#[test]
fn test_get_macro_delimiters_brace() {
let mut parser = Parser::new("macro_name!{ arg }");
let result = parser.parse();
assert!(result.is_ok(), "Should parse macro with brace delimiters");
}
#[test]
fn test_sql_content_with_comparison_operators() {
let mut parser = Parser::new("sql!{ SELECT * WHERE age > 18 }");
let result = parser.parse();
assert!(result.is_ok(), "Should parse SQL with comparison operators");
}
#[test]
fn test_parse_vec_macro_empty() {
let mut parser = Parser::new("vec![]");
let result = parser.parse();
assert!(result.is_ok(), "Should parse empty vec![]");
}
#[test]
fn test_parse_vec_macro_single_element() {
let mut parser = Parser::new("vec![42]");
let result = parser.parse();
assert!(result.is_ok(), "Should parse vec! with single element");
}
#[test]
fn test_parse_vec_macro_multiple_elements() {
let mut parser = Parser::new("vec![1, 2, 3, 4, 5]");
let result = parser.parse();
assert!(result.is_ok(), "Should parse vec! with multiple elements");
}
#[test]
fn test_parse_vec_macro_repeat_pattern() {
let mut parser = Parser::new("vec![0; 10]");
let result = parser.parse();
assert!(result.is_ok(), "Should parse vec! repeat pattern");
if let Ok(expr) = result {
assert!(
matches!(expr.kind, ExprKind::VecRepeat { .. }),
"Should produce VecRepeat variant"
);
}
}
#[test]
fn test_parse_vec_macro_repeat_expression() {
let mut parser = Parser::new("vec![1 + 1; 5]");
let result = parser.parse();
assert!(result.is_ok(), "Should parse vec! repeat with expression");
}
#[test]
fn test_create_macro_expr() {
let args = vec![Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
)];
let expr = create_macro_expr("test_macro".to_string(), args);
match &expr.kind {
ExprKind::MacroInvocation { name, args } => {
assert_eq!(name, "test_macro");
assert_eq!(args.len(), 1);
}
_ => panic!("Expected MacroInvocation"),
}
}
#[test]
fn test_create_macro_expr_empty_args() {
let expr = create_macro_expr("empty_macro".to_string(), Vec::new());
match &expr.kind {
ExprKind::MacroInvocation { name, args } => {
assert_eq!(name, "empty_macro");
assert!(args.is_empty());
}
_ => panic!("Expected MacroInvocation"),
}
}
#[test]
fn test_convert_token_to_sql_fallback() {
let result = convert_token_to_sql(&Token::Colon);
assert!(
!result.is_empty(),
"Fallback should produce non-empty string"
);
}
#[test]
fn test_convert_token_to_sql_special_float() {
let result = convert_token_to_sql(&Token::Float(3.14159));
assert_eq!(result, "3.14159");
}
#[test]
fn test_parse_vec_macro_nested() {
let mut parser = Parser::new("vec![vec![1, 2], vec![3, 4]]");
let result = parser.parse();
assert!(result.is_ok(), "Should parse nested vec! macros");
}
#[test]
fn test_sql_macro_with_operators() {
let mut parser = Parser::new("sql!{ SELECT a + b - c * d / e FROM t }");
let result = parser.parse();
assert!(result.is_ok(), "Should parse SQL with arithmetic operators");
}
#[test]
fn test_dataframe_macro_non_bracket() {
let mut parser = Parser::new("df!{column}");
let result = parser.parse();
assert!(
result.is_ok() || result.is_err(),
"Should handle df! with braces"
);
}
#[test]
fn test_sql_macro_with_functions() {
let mut parser = Parser::new("sql!{ SELECT COUNT(id), MAX(score) FROM users }");
let result = parser.parse();
assert!(result.is_ok(), "Should parse SQL with function calls");
}
#[test]
fn test_vec_macro_trailing_comma() {
let mut parser = Parser::new("vec![1, 2, 3,]");
let result = parser.parse();
assert!(
result.is_ok() || result.is_err(),
"Should handle trailing comma"
);
}
#[test]
fn test_macro_with_string_literal() {
let mut parser = Parser::new("format!(\"Hello, {}\", name)");
let result = parser.parse();
assert!(result.is_ok(), "Should parse macro with string argument");
}
#[test]
fn test_convert_token_to_sql_empty_string() {
let result = convert_token_to_sql(&Token::String("".to_string()));
assert_eq!(
result, "''",
"Empty string should produce single-quoted empty"
);
}
}