use super::Transpiler;
use crate::frontend::ast::{Expr, ExprKind};
use anyhow::Result;
use proc_macro2::TokenStream;
#[cfg(test)]
#[allow(unused_imports)]
use proptest::prelude::*;
use quote::quote;
impl Transpiler {
pub fn transpile_dataframe_builder(&self, expr: &Expr) -> Result<Option<TokenStream>> {
if let Some((columns, _base)) = self.extract_dataframe_builder_chain(expr) {
let mut series_tokens = Vec::new();
for (name, data) in columns {
let name_tokens = self.transpile_expr(&name)?;
let data_tokens = self.transpile_expr(&data)?;
series_tokens.push(quote! {
polars::prelude::Series::new(#name_tokens, &#data_tokens)
});
}
if series_tokens.is_empty() {
Ok(Some(quote! { polars::prelude::DataFrame::empty() }))
} else {
Ok(Some(quote! {
polars::prelude::DataFrame::new(vec![#(#series_tokens),*])
.expect("DataFrame::new should succeed with valid Series (incompatible lengths would be a Ruchy type error)")
}))
}
} else {
Ok(None)
}
}
fn extract_dataframe_builder_chain(&self, expr: &Expr) -> Option<(Vec<(Expr, Expr)>, Expr)> {
match &expr.kind {
ExprKind::MethodCall {
receiver,
method,
args,
} if method == "build" && args.is_empty() => Self::extract_column_chain(receiver),
ExprKind::MethodCall {
receiver,
method,
args,
} if method == "column" && args.len() == 2 => {
if let Some((mut cols, base)) = Self::extract_column_chain(receiver) {
cols.push((args[0].clone(), args[1].clone()));
Some((cols, base))
} else {
Some((
vec![(args[0].clone(), args[1].clone())],
receiver.as_ref().clone(),
))
}
}
_ => None,
}
}
fn extract_column_chain(expr: &Expr) -> Option<(Vec<(Expr, Expr)>, Expr)> {
match &expr.kind {
ExprKind::MethodCall {
receiver,
method,
args,
} if method == "column" && args.len() == 2 => {
if let Some((mut cols, base)) = Self::extract_column_chain(receiver) {
cols.push((args[0].clone(), args[1].clone()));
Some((cols, base))
} else {
Some((
vec![(args[0].clone(), args[1].clone())],
receiver.as_ref().clone(),
))
}
}
ExprKind::Call { func, args } => {
if let ExprKind::QualifiedName { module, name } = &func.kind {
if module == "DataFrame" && name == "new" && args.is_empty() {
return Some((Vec::new(), expr.clone()));
}
}
None
}
_ => None,
}
}
pub fn is_dataframe_builder(&self, expr: &Expr) -> bool {
match &expr.kind {
ExprKind::MethodCall { method, .. } => {
matches!(method.as_str(), "column" | "build")
}
ExprKind::Call { func, .. } => {
if let ExprKind::QualifiedName { module, name } = &func.kind {
module == "DataFrame" && name == "new"
} else {
false
}
}
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
fn test_transpiler() -> Transpiler {
Transpiler::new()
}
fn string_expr(value: &str) -> Expr {
Expr {
kind: ExprKind::Literal(Literal::String(value.to_string())),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
fn list_expr(items: Vec<i64>) -> Expr {
Expr {
kind: ExprKind::List(
items
.into_iter()
.map(|n| Expr {
kind: ExprKind::Literal(Literal::Integer(n, None)),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
})
.collect(),
),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
fn dataframe_new_call() -> Expr {
Expr {
kind: ExprKind::Call {
func: Box::new(Expr {
kind: ExprKind::QualifiedName {
module: "DataFrame".to_string(),
name: "new".to_string(),
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
fn column_method_call(receiver: Expr, name: Expr, data: Expr) -> Expr {
Expr {
kind: ExprKind::MethodCall {
receiver: Box::new(receiver),
method: "column".to_string(),
args: vec![name, data],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
fn build_method_call(receiver: Expr) -> Expr {
Expr {
kind: ExprKind::MethodCall {
receiver: Box::new(receiver),
method: "build".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
#[test]
fn test_transpile_dataframe_builder_empty() {
let transpiler = test_transpiler();
let expr = build_method_call(dataframe_new_call());
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
let tokens = result.expect("operation should succeed in test");
assert!(tokens.is_some());
let output = tokens
.expect("operation should succeed in test")
.to_string();
assert!(output.contains("DataFrame"));
assert!(output.contains("empty"));
}
#[test]
fn test_transpile_dataframe_builder_single_column() {
let transpiler = test_transpiler();
let expr = build_method_call(column_method_call(
dataframe_new_call(),
string_expr("a"),
list_expr(vec![1, 2, 3]),
));
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
let tokens = result.expect("operation should succeed in test");
assert!(tokens.is_some());
let output = tokens
.expect("operation should succeed in test")
.to_string();
assert!(output.contains("DataFrame"));
assert!(output.contains("Series"));
}
#[test]
fn test_transpile_dataframe_builder_multiple_columns() {
let transpiler = test_transpiler();
let base = dataframe_new_call();
let with_col1 = column_method_call(base, string_expr("a"), list_expr(vec![1, 2]));
let with_col2 = column_method_call(with_col1, string_expr("b"), list_expr(vec![3, 4]));
let expr = build_method_call(with_col2);
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
let tokens = result.expect("operation should succeed in test");
assert!(tokens.is_some());
let output = tokens
.expect("operation should succeed in test")
.to_string();
assert!(output.contains("DataFrame"));
assert!(output.contains("Series"));
assert!(output.contains("vec"));
}
#[test]
fn test_transpile_dataframe_builder_no_build() {
let transpiler = test_transpiler();
let expr = column_method_call(
dataframe_new_call(),
string_expr("x"),
list_expr(vec![10, 20]),
);
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
let tokens = result.expect("operation should succeed in test");
assert!(tokens.is_some());
}
#[test]
fn test_transpile_dataframe_builder_non_builder() {
let transpiler = test_transpiler();
let expr = string_expr("not a builder");
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
assert!(result.expect("operation should succeed in test").is_none());
}
#[test]
fn test_is_dataframe_builder_new_call() {
let transpiler = test_transpiler();
let expr = dataframe_new_call();
assert!(transpiler.is_dataframe_builder(&expr));
}
#[test]
fn test_is_dataframe_builder_column_method() {
let transpiler = test_transpiler();
let expr = column_method_call(dataframe_new_call(), string_expr("a"), list_expr(vec![1]));
assert!(transpiler.is_dataframe_builder(&expr));
}
#[test]
fn test_is_dataframe_builder_build_method() {
let transpiler = test_transpiler();
let expr = build_method_call(dataframe_new_call());
assert!(transpiler.is_dataframe_builder(&expr));
}
#[test]
fn test_is_dataframe_builder_false() {
let transpiler = test_transpiler();
let expr = string_expr("not a builder");
assert!(!transpiler.is_dataframe_builder(&expr));
}
#[test]
fn test_extract_dataframe_builder_chain_with_build() {
let transpiler = test_transpiler();
let expr = build_method_call(column_method_call(
dataframe_new_call(),
string_expr("col"),
list_expr(vec![1]),
));
let result = transpiler.extract_dataframe_builder_chain(&expr);
assert!(result.is_some());
let (columns, _base) = result.expect("operation should succeed in test");
assert_eq!(columns.len(), 1);
}
#[test]
fn test_extract_dataframe_builder_chain_no_build() {
let transpiler = test_transpiler();
let expr = column_method_call(
dataframe_new_call(),
string_expr("data"),
list_expr(vec![5, 6]),
);
let result = transpiler.extract_dataframe_builder_chain(&expr);
assert!(result.is_some());
}
#[test]
fn test_extract_dataframe_builder_chain_none() {
let transpiler = test_transpiler();
let expr = string_expr("not builder");
let result = transpiler.extract_dataframe_builder_chain(&expr);
assert!(result.is_none());
}
#[test]
fn test_extract_column_chain_single() {
let expr = column_method_call(dataframe_new_call(), string_expr("x"), list_expr(vec![1]));
let result = Transpiler::extract_column_chain(&expr);
assert!(result.is_some());
let (columns, _base) = result.expect("operation should succeed in test");
assert_eq!(columns.len(), 1);
}
#[test]
fn test_extract_column_chain_chained() {
let base = dataframe_new_call();
let col1 = column_method_call(base, string_expr("a"), list_expr(vec![1]));
let col2 = column_method_call(col1, string_expr("b"), list_expr(vec![2]));
let result = Transpiler::extract_column_chain(&col2);
assert!(result.is_some());
let (columns, _base) = result.expect("operation should succeed in test");
assert_eq!(columns.len(), 2);
}
#[test]
fn test_extract_column_chain_none() {
let expr = string_expr("not a column");
let result = Transpiler::extract_column_chain(&expr);
assert!(result.is_none());
}
#[test]
fn test_transpile_dataframe_builder_three_columns() {
let transpiler = test_transpiler();
let base = dataframe_new_call();
let col1 = column_method_call(base, string_expr("a"), list_expr(vec![1]));
let col2 = column_method_call(col1, string_expr("b"), list_expr(vec![2]));
let col3 = column_method_call(col2, string_expr("c"), list_expr(vec![3]));
let expr = build_method_call(col3);
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
let tokens = result.expect("operation should succeed in test");
assert!(tokens.is_some());
let output = tokens
.expect("operation should succeed in test")
.to_string();
assert!(output.contains("Series"));
assert!(output.contains("vec"));
}
#[test]
fn test_transpile_dataframe_builder_expression_data() {
let transpiler = test_transpiler();
let expr = column_method_call(
dataframe_new_call(),
string_expr("values"),
list_expr(vec![10, 20, 30]),
);
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
assert!(result.expect("operation should succeed in test").is_some());
}
#[test]
fn test_transpile_dataframe_builder_series_output() {
let transpiler = test_transpiler();
let expr = build_method_call(column_method_call(
dataframe_new_call(),
string_expr("col"),
list_expr(vec![1, 2]),
));
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
let output = result
.expect("operation should succeed in test")
.expect("operation should succeed in test")
.to_string();
assert!(output.contains("polars"));
assert!(output.contains("Series"));
assert!(output.contains("new"));
}
#[test]
fn test_extract_dataframe_builder_chain_three_cols() {
let transpiler = test_transpiler();
let base = dataframe_new_call();
let col1 = column_method_call(base, string_expr("x"), list_expr(vec![1]));
let col2 = column_method_call(col1, string_expr("y"), list_expr(vec![2]));
let col3 = column_method_call(col2, string_expr("z"), list_expr(vec![3]));
let expr = build_method_call(col3);
let result = transpiler.extract_dataframe_builder_chain(&expr);
assert!(result.is_some());
let (columns, _base) = result.expect("operation should succeed in test");
assert_eq!(columns.len(), 3);
}
#[test]
fn test_extract_dataframe_builder_chain_build_after_cols() {
let transpiler = test_transpiler();
let expr = build_method_call(column_method_call(
column_method_call(
dataframe_new_call(),
string_expr("a"),
list_expr(vec![1, 2]),
),
string_expr("b"),
list_expr(vec![3, 4]),
));
let result = transpiler.extract_dataframe_builder_chain(&expr);
assert!(result.is_some());
let (columns, _) = result.expect("operation should succeed in test");
assert_eq!(columns.len(), 2);
}
#[test]
fn test_extract_column_chain_three_cols() {
let base = dataframe_new_call();
let col1 = column_method_call(base, string_expr("a"), list_expr(vec![1]));
let col2 = column_method_call(col1, string_expr("b"), list_expr(vec![2]));
let col3 = column_method_call(col2, string_expr("c"), list_expr(vec![3]));
let result = Transpiler::extract_column_chain(&col3);
assert!(result.is_some());
let (columns, _base) = result.expect("operation should succeed in test");
assert_eq!(columns.len(), 3);
}
#[test]
fn test_extract_column_chain_dataframe_new_base() {
let base = dataframe_new_call();
let col = column_method_call(base, string_expr("data"), list_expr(vec![5]));
let result = Transpiler::extract_column_chain(&col);
assert!(result.is_some());
let (columns, base_expr) = result.expect("operation should succeed in test");
assert_eq!(columns.len(), 1);
assert!(matches!(base_expr.kind, ExprKind::Call { .. }));
}
#[test]
fn test_is_dataframe_builder_build_no_args() {
let transpiler = test_transpiler();
let expr = Expr {
kind: ExprKind::MethodCall {
receiver: Box::new(dataframe_new_call()),
method: "build".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(transpiler.is_dataframe_builder(&expr));
}
#[test]
fn test_is_dataframe_builder_column_two_args() {
let transpiler = test_transpiler();
let expr = column_method_call(
dataframe_new_call(),
string_expr("name"),
list_expr(vec![1]),
);
assert!(transpiler.is_dataframe_builder(&expr));
}
#[test]
fn test_is_dataframe_builder_qualified_name() {
let transpiler = test_transpiler();
let expr = dataframe_new_call();
assert!(transpiler.is_dataframe_builder(&expr));
}
#[test]
fn test_transpile_dataframe_builder_expect_present() {
let transpiler = test_transpiler();
let expr = build_method_call(column_method_call(
dataframe_new_call(),
string_expr("test"),
list_expr(vec![99]),
));
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
let output = result
.expect("operation should succeed in test")
.expect("operation should succeed in test")
.to_string();
assert!(output.contains("expect"));
assert!(output.contains("DataFrame::new should succeed"));
}
#[test]
fn test_transpile_dataframe_builder_empty_for_no_cols() {
let transpiler = test_transpiler();
let expr = build_method_call(dataframe_new_call());
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
let output = result
.expect("operation should succeed in test")
.expect("operation should succeed in test")
.to_string();
assert!(output.contains("empty"));
}
#[test]
fn test_extract_dataframe_builder_chain_single_col() {
let transpiler = test_transpiler();
let expr = column_method_call(
dataframe_new_call(),
string_expr("single"),
list_expr(vec![42]),
);
let result = transpiler.extract_dataframe_builder_chain(&expr);
assert!(result.is_some());
let (columns, _) = result.expect("operation should succeed in test");
assert_eq!(columns.len(), 1);
}
#[test]
fn test_is_dataframe_builder_literal_false() {
let transpiler = test_transpiler();
let expr = Expr {
kind: ExprKind::Literal(Literal::Integer(42, None)),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(!transpiler.is_dataframe_builder(&expr));
}
#[test]
fn test_transpile_dataframe_builder_four_columns() {
let transpiler = test_transpiler();
let base = dataframe_new_call();
let col1 = column_method_call(base, string_expr("a"), list_expr(vec![1]));
let col2 = column_method_call(col1, string_expr("b"), list_expr(vec![2]));
let col3 = column_method_call(col2, string_expr("c"), list_expr(vec![3]));
let col4 = column_method_call(col3, string_expr("d"), list_expr(vec![4]));
let expr = build_method_call(col4);
let result = transpiler.transpile_dataframe_builder(&expr);
assert!(result.is_ok());
let tokens = result.expect("operation should succeed in test");
assert!(tokens.is_some());
let output = tokens
.expect("operation should succeed in test")
.to_string();
assert!(output.contains("DataFrame"));
assert!(output.contains("new"));
assert!(output.contains("vec"));
}
}
#[cfg(test)]
mod property_tests_dataframe_builder {
#[allow(unused_imports)]
use super::*;
#[allow(unused_imports)]
use proptest::prelude::*;
use proptest::proptest;
proptest! {
#[test]
fn test_transpile_dataframe_builder_never_panics(input: String) {
let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
let _ = std::panic::catch_unwind(|| {
});
}
}
}