use super::super::Transpiler;
use crate::frontend::ast::Expr;
use anyhow::Result;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
impl Transpiler {
fn is_module_path(&self, expr: &Expr) -> bool {
use crate::frontend::ast::ExprKind;
match &expr.kind {
ExprKind::Identifier(name) => {
name == "std" || self.module_names.contains(name) || Self::is_module_like_identifier(name) || name.chars().next().is_some_and(char::is_uppercase) }
ExprKind::FieldAccess { object, .. } => self.is_module_path(object),
_ => false,
}
}
fn get_root_identifier(expr: &Expr) -> Option<&str> {
use crate::frontend::ast::ExprKind;
match &expr.kind {
ExprKind::Identifier(name) => Some(name.as_str()),
ExprKind::FieldAccess { object, .. } => Self::get_root_identifier(object),
_ => None,
}
}
fn is_variable_chain(&self, expr: &Expr) -> bool {
if let Some(root) = Self::get_root_identifier(expr) {
let is_not_module = !self.module_names.contains(root)
&& root != "std"
&& !Self::is_module_like_identifier(root);
let is_not_type = root.chars().next().is_some_and(char::is_lowercase);
is_not_module && is_not_type
} else {
false
}
}
fn is_module_like_identifier(name: &str) -> bool {
if name.is_empty() || name == "self" || name == "this" {
return false;
}
let known_module_prefixes = ["std_", "ruchy_", "aws_", "tokio_"];
known_module_prefixes
.iter()
.any(|prefix| name.starts_with(prefix))
}
pub fn transpile_field_access(&self, object: &Expr, field: &str) -> Result<TokenStream> {
use crate::frontend::ast::ExprKind;
let obj_tokens = self.transpile_expr(object)?;
if field.chars().all(|c| c.is_ascii_digit()) {
let index: usize = field
.parse()
.expect("field string should parse to usize after is_ascii_digit check");
let index = syn::Index::from(index);
return Ok(quote! { #obj_tokens.#index });
}
match &object.kind {
ExprKind::ObjectLiteral { .. } => {
Ok(quote! {
#obj_tokens.get(#field)
.cloned()
.unwrap_or_else(|| panic!("Field '{}' not found", #field))
})
}
ExprKind::FieldAccess { .. } => {
if field.chars().all(|c| c.is_ascii_digit()) {
let index: usize = field
.parse()
.expect("field string should parse to usize after is_ascii_digit check");
let index = syn::Index::from(index);
Ok(quote! { #obj_tokens.#index })
} else if field.is_empty()
|| field.chars().next().is_some_and(|c| c.is_ascii_digit())
{
anyhow::bail!("Invalid field name '{field}': field names cannot be empty or start with a digit")
} else {
let known_methods = [
"success", "exists", "is_empty", "is_some", "is_none", "is_ok", "is_err",
];
let field_ident = format_ident!("{}", field);
if known_methods.contains(&field) {
Ok(quote! { #obj_tokens.#field_ident() })
} else if self.is_variable_chain(object) {
Ok(quote! { #obj_tokens.#field_ident })
} else if self.is_module_path(object) {
Ok(quote! { #obj_tokens::#field_ident })
} else {
Ok(quote! { #obj_tokens::#field_ident })
}
}
}
ExprKind::Identifier(name) if name.contains("::") => {
let field_ident = format_ident!("{}", field);
Ok(quote! { #obj_tokens::#field_ident })
}
ExprKind::Identifier(name) if name == "std" => {
let field_ident = format_ident!("{}", field);
Ok(quote! { #obj_tokens::#field_ident })
}
ExprKind::Identifier(name) if self.module_names.contains(name) => {
let field_ident = format_ident!("{}", field);
Ok(quote! { #obj_tokens::#field_ident })
}
ExprKind::Identifier(name) if name.chars().next().is_some_and(char::is_uppercase) => {
let field_ident = format_ident!("{}", field);
Ok(quote! { #obj_tokens::#field_ident })
}
ExprKind::Identifier(name) if Self::is_module_like_identifier(name) => {
let field_ident = format_ident!("{}", field);
Ok(quote! { #obj_tokens::#field_ident })
}
_ => {
if field.chars().all(|c| c.is_ascii_digit()) {
let index: usize = field
.parse()
.expect("field string should parse to usize after is_ascii_digit check");
let index = syn::Index::from(index);
Ok(quote! { #obj_tokens.#index })
} else {
let known_methods = [
"success", "exists", "is_empty", "is_some", "is_none", "is_ok", "is_err",
];
let field_ident = format_ident!("{}", field);
if known_methods.contains(&field) {
Ok(quote! { #obj_tokens.#field_ident() })
} else {
Ok(quote! { #obj_tokens.#field_ident })
}
}
}
}
}
pub fn transpile_index_access(&self, object: &Expr, index: &Expr) -> Result<TokenStream> {
use crate::frontend::ast::{ExprKind, Literal};
let obj_tokens = self.transpile_expr(object)?;
let index_tokens = self.transpile_expr(index)?;
match &index.kind {
ExprKind::Literal(Literal::String(_)) => Ok(quote! {
#obj_tokens.get(#index_tokens)
.cloned()
.unwrap_or_else(|| panic!("Key not found"))
}),
_ => Ok(quote! { #obj_tokens[#index_tokens as usize].clone() }),
}
}
pub fn transpile_slice(
&self,
object: &Expr,
start: Option<&Expr>,
end: Option<&Expr>,
) -> Result<TokenStream> {
let obj_tokens = self.transpile_expr(object)?;
match (start, end) {
(None, None) => {
Ok(quote! { &#obj_tokens[..] })
}
(None, Some(end)) => {
let end_tokens = self.transpile_expr(end)?;
Ok(quote! { &#obj_tokens[..#end_tokens as usize] })
}
(Some(start), None) => {
let start_tokens = self.transpile_expr(start)?;
Ok(quote! { &#obj_tokens[#start_tokens as usize..] })
}
(Some(start), Some(end)) => {
let start_tokens = self.transpile_expr(start)?;
let end_tokens = self.transpile_expr(end)?;
Ok(quote! { &#obj_tokens[#start_tokens as usize..#end_tokens as usize] })
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
fn test_transpiler() -> Transpiler {
Transpiler::new()
}
fn test_transpiler_with_modules(modules: Vec<&str>) -> Transpiler {
let mut transpiler = Transpiler::new();
transpiler.module_names = modules
.iter()
.map(std::string::ToString::to_string)
.collect();
transpiler
}
fn ident_expr(name: &str) -> Expr {
Expr {
kind: ExprKind::Identifier(name.to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
fn field_access_expr(object: Expr, field: &str) -> Expr {
Expr {
kind: ExprKind::FieldAccess {
object: Box::new(object),
field: field.to_string(),
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
fn int_expr(value: i64) -> Expr {
Expr {
kind: ExprKind::Literal(Literal::Integer(value, None)),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
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,
}
}
#[test]
fn test_is_module_like_identifier_valid() {
assert!(Transpiler::is_module_like_identifier("std_env"));
assert!(Transpiler::is_module_like_identifier("ruchy_core"));
assert!(Transpiler::is_module_like_identifier("aws_lambda"));
assert!(Transpiler::is_module_like_identifier("tokio_runtime"));
}
#[test]
fn test_is_module_like_identifier_no_underscore() {
assert!(!Transpiler::is_module_like_identifier("obj"));
assert!(!Transpiler::is_module_like_identifier("myvar"));
assert!(!Transpiler::is_module_like_identifier("x"));
}
#[test]
fn test_is_module_like_identifier_special() {
assert!(!Transpiler::is_module_like_identifier("self"));
assert!(!Transpiler::is_module_like_identifier("this"));
assert!(!Transpiler::is_module_like_identifier(""));
}
#[test]
fn test_is_module_path_std() {
let transpiler = test_transpiler();
let expr = ident_expr("std");
assert!(transpiler.is_module_path(&expr));
}
#[test]
fn test_is_module_path_user_module() {
let transpiler = test_transpiler_with_modules(vec!["helper"]);
let expr = ident_expr("helper");
assert!(transpiler.is_module_path(&expr));
}
#[test]
fn test_is_module_path_module_like() {
let transpiler = test_transpiler();
let expr = ident_expr("std_env");
assert!(transpiler.is_module_path(&expr));
}
#[test]
fn test_is_module_path_type_name() {
let transpiler = test_transpiler();
let expr = ident_expr("String");
assert!(transpiler.is_module_path(&expr));
}
#[test]
fn test_is_module_path_nested() {
let transpiler = test_transpiler();
let std_expr = ident_expr("std");
let nested = field_access_expr(std_expr, "time");
assert!(transpiler.is_module_path(&nested));
}
#[test]
fn test_get_root_identifier_simple() {
let expr = ident_expr("obj");
assert_eq!(Transpiler::get_root_identifier(&expr), Some("obj"));
}
#[test]
fn test_get_root_identifier_nested() {
let obj = ident_expr("event");
let access = field_access_expr(obj, "requestContext");
assert_eq!(Transpiler::get_root_identifier(&access), Some("event"));
}
#[test]
fn test_is_variable_chain_simple() {
let transpiler = test_transpiler();
let expr = ident_expr("event");
assert!(transpiler.is_variable_chain(&expr));
}
#[test]
fn test_is_variable_chain_not_module() {
let transpiler = test_transpiler();
let expr = ident_expr("std_env");
assert!(!transpiler.is_variable_chain(&expr)); }
#[test]
fn test_transpile_field_access_tuple() {
let transpiler = test_transpiler();
let obj = ident_expr("tuple");
let result = transpiler
.transpile_field_access(&obj, "0")
.expect("operation should succeed in test");
let result_str = result.to_string();
assert!(result_str.contains("tuple") && result_str.contains(". 0"));
}
#[test]
fn test_transpile_field_access_std_module() {
let transpiler = test_transpiler();
let std = ident_expr("std");
let result = transpiler
.transpile_field_access(&std, "time")
.expect("operation should succeed in test");
assert_eq!(result.to_string(), "std :: time");
}
#[test]
fn test_transpile_field_access_type_associated() {
let transpiler = test_transpiler();
let string_type = ident_expr("String");
let result = transpiler
.transpile_field_access(&string_type, "from")
.expect("operation should succeed in test");
assert_eq!(result.to_string(), "String :: from");
}
#[test]
fn test_transpile_field_access_known_method() {
let transpiler = test_transpiler();
let obj = ident_expr("result");
let result = transpiler
.transpile_field_access(&obj, "is_ok")
.expect("operation should succeed in test");
let result_str = result.to_string();
assert!(
result_str.contains("result")
&& result_str.contains("is_ok")
&& result_str.contains("()")
);
}
#[test]
fn test_transpile_field_access_variable_chain() {
let transpiler = test_transpiler();
let event = ident_expr("event");
let result = transpiler
.transpile_field_access(&event, "field")
.expect("operation should succeed in test");
assert_eq!(result.to_string(), "event . field");
}
#[test]
fn test_transpile_field_access_module_like_identifier() {
let transpiler = test_transpiler();
let std_env = ident_expr("std_env");
let result = transpiler
.transpile_field_access(&std_env, "var")
.expect("operation should succeed in test");
assert_eq!(result.to_string(), "std_env :: var");
}
#[test]
fn test_transpile_field_access_invalid_field_starts_digit() {
let transpiler = test_transpiler();
let nested = field_access_expr(ident_expr("obj"), "field");
let result = transpiler.transpile_field_access(&nested, "9field");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid field name"));
}
#[test]
fn test_transpile_index_access_string_key() {
let transpiler = test_transpiler();
let map = ident_expr("map");
let key = string_expr("key");
let result = transpiler
.transpile_index_access(&map, &key)
.expect("operation should succeed in test");
let result_str = result.to_string();
assert!(result_str.contains("map . get") && result_str.contains("cloned"));
}
#[test]
fn test_transpile_index_access_numeric() {
let transpiler = test_transpiler();
let array = ident_expr("arr");
let index = int_expr(0);
let result = transpiler
.transpile_index_access(&array, &index)
.expect("operation should succeed in test");
let result_str = result.to_string();
assert!(
result_str.contains("arr") && result_str.contains('[') && result_str.contains("clone")
);
}
#[test]
fn test_transpile_slice_full() {
let transpiler = test_transpiler();
let array = ident_expr("arr");
let result = transpiler
.transpile_slice(&array, None, None)
.expect("operation should succeed in test");
let result_str = result.to_string();
assert!(
result_str.contains("& arr") && result_str.contains('[') && result_str.contains("..")
);
}
#[test]
fn test_transpile_slice_to_end() {
let transpiler = test_transpiler();
let array = ident_expr("arr");
let end = int_expr(5);
let result = transpiler
.transpile_slice(&array, None, Some(&end))
.expect("operation should succeed in test");
let result_str = result.to_string();
assert!(
result_str.contains("arr") && result_str.contains("..") && result_str.contains('5')
);
}
#[test]
fn test_transpile_slice_from_start() {
let transpiler = test_transpiler();
let array = ident_expr("arr");
let start = int_expr(2);
let result = transpiler
.transpile_slice(&array, Some(&start), None)
.expect("operation should succeed in test");
let result_str = result.to_string();
assert!(
result_str.contains("arr") && result_str.contains('2') && result_str.contains("..")
);
}
#[test]
fn test_transpile_slice_range() {
let transpiler = test_transpiler();
let array = ident_expr("arr");
let start = int_expr(1);
let end = int_expr(4);
let result = transpiler
.transpile_slice(&array, Some(&start), Some(&end))
.expect("operation should succeed in test");
let result_str = result.to_string();
assert!(
result_str.contains("arr")
&& result_str.contains('1')
&& result_str.contains('4')
&& result_str.contains("..")
);
}
#[test]
fn test_transpile_field_access_numeric_field() {
let transpiler = test_transpiler();
let obj = ident_expr("tup");
let result = transpiler.transpile_field_access(&obj, "0");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("tup") && code.contains('0'));
}
#[test]
fn test_transpile_field_access_object_literal() {
let transpiler = test_transpiler();
let obj = Expr {
kind: ExprKind::ObjectLiteral {
fields: vec![crate::frontend::ast::ObjectField::KeyValue {
key: "name".to_string(),
value: string_expr("Alice"),
}],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let result = transpiler.transpile_field_access(&obj, "name");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("get"), "ObjectLiteral access should use .get()");
}
#[test]
fn test_transpile_field_access_nested_numeric() {
let transpiler = test_transpiler();
let inner = ident_expr("nested");
let nested = field_access_expr(inner, "data");
let result = transpiler.transpile_field_access(&nested, "0");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains('0'), "Should contain numeric index");
}
#[test]
fn test_transpile_field_access_nested_digit_start() {
let transpiler = test_transpiler();
let inner = ident_expr("obj");
let nested = field_access_expr(inner, "field");
let result = transpiler.transpile_field_access(&nested, "3abc");
assert!(result.is_err(), "Field starting with digit should be an error");
}
#[test]
fn test_transpile_field_access_nested_known_method() {
let transpiler = test_transpiler();
let inner = ident_expr("status");
let nested = field_access_expr(inner, "exit");
let result = transpiler.transpile_field_access(&nested, "success");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("success"), "Should contain method name");
assert!(code.contains("()"), "Known methods should have ()");
}
#[test]
fn test_transpile_field_access_nested_variable_chain() {
let transpiler = test_transpiler();
let inner = ident_expr("event");
let nested = field_access_expr(inner, "request");
let result = transpiler.transpile_field_access(&nested, "id");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(
code.contains("id"),
"Should contain field name"
);
}
#[test]
fn test_transpile_field_access_nested_module_path() {
let transpiler = test_transpiler();
let inner = ident_expr("std");
let nested = field_access_expr(inner, "time");
let result = transpiler.transpile_field_access(&nested, "Duration");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("Duration"));
}
#[test]
fn test_transpile_field_access_identifier_with_colons() {
let transpiler = test_transpiler();
let obj = Expr {
kind: ExprKind::Identifier("std::collections".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let result = transpiler.transpile_field_access(&obj, "HashMap");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("HashMap"), "Should contain the field");
}
#[test]
fn test_transpile_field_access_std_fs_module() {
let transpiler = test_transpiler();
let obj = ident_expr("std");
let result = transpiler.transpile_field_access(&obj, "fs");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("std") && code.contains("fs"));
}
#[test]
fn test_transpile_field_access_known_module() {
let transpiler = test_transpiler_with_modules(vec!["helper"]);
let obj = ident_expr("helper");
let result = transpiler.transpile_field_access(&obj, "get_message");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("helper") && code.contains("get_message"));
}
#[test]
fn test_transpile_field_access_pascal_case_type() {
let transpiler = test_transpiler();
let obj = ident_expr("String");
let result = transpiler.transpile_field_access(&obj, "from");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("String") && code.contains("from"));
}
#[test]
fn test_transpile_field_access_module_like() {
let transpiler = test_transpiler();
let obj = ident_expr("aws_lambda");
let result = transpiler.transpile_field_access(&obj, "handler");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("aws_lambda") && code.contains("handler"));
}
#[test]
fn test_transpile_field_access_fallback_numeric() {
let transpiler = test_transpiler();
let obj = Expr {
kind: ExprKind::Call {
func: Box::new(ident_expr("get_tuple")),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let result = transpiler.transpile_field_access(&obj, "0");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains('0'), "Should have numeric index");
}
#[test]
fn test_transpile_field_access_fallback_known_method() {
let transpiler = test_transpiler();
let obj = Expr {
kind: ExprKind::Call {
func: Box::new(ident_expr("get_vec")),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let result = transpiler.transpile_field_access(&obj, "is_empty");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("is_empty"), "Should contain method name");
assert!(code.contains("()"), "Known method should have ()");
}
#[test]
fn test_transpile_field_access_fallback_regular_field() {
let transpiler = test_transpiler();
let obj = Expr {
kind: ExprKind::Call {
func: Box::new(ident_expr("get_person")),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let result = transpiler.transpile_field_access(&obj, "name");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("name"), "Should contain field name");
}
#[test]
fn test_transpile_field_access_variable() {
let transpiler = test_transpiler();
let obj = ident_expr("person");
let result = transpiler.transpile_field_access(&obj, "name");
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("person") && code.contains("name"));
}
}