#![allow(clippy::missing_errors_doc)]
#![allow(clippy::wildcard_imports)]
use super::*;
use crate::frontend::ast::{ActorHandler, StructField};
use anyhow::Result;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
impl Transpiler {
pub fn transpile_actor(
&self,
name: &str,
state: &[StructField],
handlers: &[ActorHandler],
) -> Result<TokenStream> {
let actor_name = format_ident!("{}", name);
let message_enum_name = format_ident!("{}Message", name);
let state_fields: Vec<TokenStream> = state
.iter()
.map(|field| {
let field_name = format_ident!("{}", field.name);
let field_type = self
.transpile_type(&field.ty)
.unwrap_or_else(|_| quote! { _ });
quote! { #field_name: #field_type }
})
.collect();
let field_names: Vec<_> = state
.iter()
.map(|field| format_ident!("{}", field.name))
.collect();
let mut message_variants = Vec::new();
let mut handler_arms = Vec::new();
for handler in handlers {
let variant_name = format_ident!("{}", handler.message_type);
if handler.params.is_empty() {
message_variants.push(quote! { #variant_name });
let body_tokens = self.transpile_expr(&handler.body)?;
handler_arms.push(quote! {
#message_enum_name::#variant_name => {
#body_tokens
}
});
} else {
let param_types: Vec<TokenStream> = handler
.params
.iter()
.map(|p| {
self.transpile_type(&p.ty)
.unwrap_or_else(|_| quote! { String })
})
.collect();
if param_types.len() == 1 {
message_variants.push(quote! { #variant_name(#(#param_types),*) });
} else {
message_variants.push(quote! { #variant_name { #(#param_types),* } });
}
let param_names: Vec<_> = handler
.params
.iter()
.map(|p| format_ident!("{}", p.name()))
.collect();
let body_tokens = self.transpile_expr(&handler.body)?;
if param_names.len() == 1 {
let param = ¶m_names[0];
handler_arms.push(quote! {
#message_enum_name::#variant_name(#param) => {
#body_tokens
}
});
} else {
handler_arms.push(quote! {
#message_enum_name::#variant_name { #(#param_names),* } => {
#body_tokens
}
});
}
}
}
Ok(quote! {
#[derive(Debug, Clone)]
enum #message_enum_name {
#(#message_variants,)*
}
#[derive(Debug, Clone)]
struct #actor_name {
#(#state_fields,)*
}
impl #actor_name {
fn new() -> Self {
Self {
#(#field_names: Default::default(),)*
}
}
fn handle_message(&mut self, msg: #message_enum_name) {
match msg {
#(#handler_arms)*
}
}
}
})
}
pub fn transpile_send(&self, actor: &Expr, message: &Expr) -> Result<TokenStream> {
let actor_tokens = self.transpile_expr(actor)?;
let message_tokens = self.transpile_expr(message)?;
Ok(quote! {
#actor_tokens.lock().expect("operation should succeed in test").handle_message(#message_tokens)
})
}
pub fn transpile_ask(
&self,
actor: &Expr,
message: &Expr,
timeout: Option<&Expr>,
) -> Result<TokenStream> {
let actor_tokens = self.transpile_expr(actor)?;
let message_tokens = self.transpile_expr(message)?;
if let Some(timeout_expr) = timeout {
let _timeout_tokens = self.transpile_expr(timeout_expr)?;
Ok(quote! {
#actor_tokens.lock().expect("operation should succeed in test").handle_message(#message_tokens)
})
} else {
Ok(quote! {
#actor_tokens.lock().expect("operation should succeed in test").handle_message(#message_tokens)
})
}
}
pub fn transpile_command(
&self,
program: &str,
args: &[String],
_env: &[(String, String)],
_working_dir: &Option<String>,
) -> Result<TokenStream> {
let prog_str = program;
let args_tokens: Vec<_> = args.iter().map(|arg| quote! { #arg }).collect();
Ok(quote! {
std::process::Command::new(#prog_str)
.args(&[#(#args_tokens),*])
.output()
.expect("Failed to execute command")
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::ast::{Expr, ExprKind, Literal, Param, Pattern, Span, Type, TypeKind};
fn make_transpiler() -> Transpiler {
Transpiler::new()
}
fn make_literal(lit: Literal) -> Expr {
Expr::new(ExprKind::Literal(lit), Span::new(0, 1))
}
fn make_ident(name: &str) -> Expr {
Expr::new(ExprKind::Identifier(name.to_string()), Span::new(0, 1))
}
fn make_type(name: &str) -> Type {
Type {
kind: TypeKind::Named(name.to_string()),
span: Span::new(0, 1),
}
}
#[test]
fn test_transpile_simple_actor() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![];
let result = transpiler.transpile_actor("Counter", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("enum CounterMessage"));
assert!(tokens.contains("struct Counter"));
}
#[test]
fn test_actor_with_state() {
let transpiler = make_transpiler();
use crate::frontend::ast::Visibility;
let state = vec![StructField {
name: "count".to_string(),
ty: make_type("i32"),
visibility: Visibility::Private,
is_mut: false,
default_value: None,
decorators: vec![],
}];
let handlers = vec![];
let result = transpiler.transpile_actor("Counter", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("count :"));
}
#[test]
fn test_actor_with_simple_handler() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![ActorHandler {
message_type: "Reset".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Unit)),
}];
let result = transpiler.transpile_actor("Counter", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Reset"));
assert!(tokens.contains("CounterMessage :: Reset"));
}
#[test]
fn test_actor_with_parameterized_handler() {
let transpiler = make_transpiler();
let state = vec![];
let params = vec![Param {
pattern: Pattern::Identifier("value".to_string()),
ty: make_type("i32"),
span: Span::new(0, 1),
is_mutable: false,
default_value: None,
}];
let handlers = vec![ActorHandler {
message_type: "Add".to_string(),
params,
body: Box::new(make_ident("value")),
}];
let result = transpiler.transpile_actor("Counter", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Add"));
}
#[test]
fn test_actor_with_multiple_handlers() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![
ActorHandler {
message_type: "Increment".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Unit)),
},
ActorHandler {
message_type: "Decrement".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Unit)),
},
ActorHandler {
message_type: "Reset".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Unit)),
},
];
let result = transpiler.transpile_actor("Counter", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Increment"));
assert!(tokens.contains("Decrement"));
assert!(tokens.contains("Reset"));
}
#[test]
fn test_transpile_send() {
let transpiler = make_transpiler();
let actor = make_ident("my_actor");
let message = make_ident("Message");
let result = transpiler.transpile_send(&actor, &message);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("lock"));
assert!(tokens.contains("handle_message"));
}
#[test]
#[ignore = "SPEC-001-F: Ask operation requires async actors - not implemented in simplified version"]
fn test_transpile_ask_with_timeout() {
let transpiler = make_transpiler();
let actor = make_ident("my_actor");
let message = make_ident("Query");
let timeout = Some(make_literal(Literal::Integer(10, None)));
let result = transpiler.transpile_ask(&actor, &message, timeout.as_ref());
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("my_actor . ask"));
assert!(tokens.contains("await"));
}
#[test]
#[ignore = "SPEC-001-F: Ask operation requires async actors - not implemented in simplified version"]
fn test_transpile_ask_without_timeout() {
let transpiler = make_transpiler();
let actor = make_ident("my_actor");
let message = make_ident("Query");
let result = transpiler.transpile_ask(&actor, &message, None);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("my_actor . ask"));
assert!(tokens.contains("Duration :: from_secs"));
assert!(tokens.contains("await"));
}
#[test]
fn test_transpile_command() {
let transpiler = make_transpiler();
let program = "echo";
let args = vec!["hello".to_string(), "world".to_string()];
let env = vec![];
let working_dir = None;
let result = transpiler.transpile_command(program, &args, &env, &working_dir);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Command :: new"));
assert!(tokens.contains("echo"));
assert!(tokens.contains("hello"));
assert!(tokens.contains("world"));
assert!(tokens.contains("output"));
}
#[test]
fn test_transpile_command_no_args() {
let transpiler = make_transpiler();
let program = "ls";
let args = vec![];
let env = vec![];
let working_dir = None;
let result = transpiler.transpile_command(program, &args, &env, &working_dir);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Command :: new"));
assert!(tokens.contains("ls"));
}
#[test]
fn test_actor_with_multiple_params() {
let transpiler = make_transpiler();
let state = vec![];
let params = vec![
Param {
pattern: Pattern::Identifier("x".to_string()),
ty: make_type("i32"),
span: Span::new(0, 1),
is_mutable: false,
default_value: None,
},
Param {
pattern: Pattern::Identifier("y".to_string()),
ty: make_type("i32"),
span: Span::new(0, 1),
is_mutable: false,
default_value: None,
},
];
let handlers = vec![ActorHandler {
message_type: "Compute".to_string(),
params,
body: Box::new(make_ident("x")),
}];
let result = transpiler.transpile_actor("Calculator", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Compute"));
}
#[test]
fn test_actor_struct_generation() {
let transpiler = make_transpiler();
let state = vec![
StructField {
name: "value".to_string(),
ty: make_type("String"),
visibility: crate::frontend::ast::Visibility::Private,
is_mut: false,
default_value: None,
decorators: vec![],
},
StructField {
name: "count".to_string(),
ty: make_type("usize"),
visibility: crate::frontend::ast::Visibility::Private,
is_mut: false,
default_value: None,
decorators: vec![],
},
];
let handlers = vec![];
let result = transpiler.transpile_actor("Storage", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("struct Storage"));
assert!(tokens.contains("value :"));
assert!(tokens.contains("count :"));
}
#[test]
#[ignore = "SPEC-001-F: Async methods require tokio runtime - not in simplified version"]
fn test_actor_async_methods() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![];
let result = transpiler.transpile_actor("Worker", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("async fn run"));
assert!(tokens.contains("async fn handle_message"));
assert!(tokens.contains("self . receiver . recv"));
}
#[test]
#[ignore = "SPEC-001-F: Channel creation requires tokio - not in simplified version"]
fn test_actor_channel_creation() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![];
let result = transpiler.transpile_actor("Service", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("fn new"));
assert!(tokens.contains("tokio :: sync :: mpsc :: channel"));
assert!(tokens.contains("fn sender"));
}
#[test]
fn test_actor_single_param_handler_variant() {
let transpiler = make_transpiler();
let state = vec![];
let params = vec![Param {
pattern: Pattern::Identifier("value".to_string()),
ty: make_type("String"),
span: Span::new(0, 1),
is_mutable: false,
default_value: None,
}];
let handlers = vec![ActorHandler {
message_type: "Update".to_string(),
params,
body: Box::new(make_literal(Literal::Unit)),
}];
let result = transpiler.transpile_actor("Store", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Update"));
assert!(tokens.contains("String"));
}
#[test]
fn test_actor_two_param_handler_variant() {
let transpiler = make_transpiler();
let state = vec![];
let params = vec![
Param {
pattern: Pattern::Identifier("x".to_string()),
ty: make_type("f64"),
span: Span::new(0, 1),
is_mutable: false,
default_value: None,
},
Param {
pattern: Pattern::Identifier("y".to_string()),
ty: make_type("f64"),
span: Span::new(0, 1),
is_mutable: false,
default_value: None,
},
];
let handlers = vec![ActorHandler {
message_type: "Move".to_string(),
params,
body: Box::new(make_literal(Literal::Unit)),
}];
let result = transpiler.transpile_actor("Robot", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Move"));
assert!(tokens.contains("f64"));
}
#[test]
fn test_actor_message_enum_variants() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![
ActorHandler {
message_type: "Start".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Unit)),
},
ActorHandler {
message_type: "Stop".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Unit)),
},
];
let result = transpiler.transpile_actor("Worker", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("enum WorkerMessage"));
assert!(tokens.contains("Start"));
assert!(tokens.contains("Stop"));
}
#[test]
fn test_actor_handler_body_transpilation() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![ActorHandler {
message_type: "Echo".to_string(),
params: vec![Param {
pattern: Pattern::Identifier("msg".to_string()),
ty: make_type("String"),
span: Span::new(0, 1),
is_mutable: false,
default_value: None,
}],
body: Box::new(make_ident("msg")),
}];
let result = transpiler.transpile_actor("Echo", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("msg"));
assert!(tokens.contains("handle_message"));
}
#[test]
fn test_actor_new_method_with_state() {
let transpiler = make_transpiler();
use crate::frontend::ast::Visibility;
let state = vec![
StructField {
name: "counter".to_string(),
ty: make_type("usize"),
visibility: Visibility::Private,
is_mut: false,
default_value: None,
decorators: vec![],
},
StructField {
name: "name".to_string(),
ty: make_type("String"),
visibility: Visibility::Private,
is_mut: false,
default_value: None,
decorators: vec![],
},
];
let handlers = vec![];
let result = transpiler.transpile_actor("Service", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("fn new"));
assert!(tokens.contains("counter : Default :: default"));
assert!(tokens.contains("name : Default :: default"));
}
#[test]
fn test_transpile_send_literal() {
let transpiler = make_transpiler();
let actor = make_ident("actor");
let message = make_literal(Literal::String("Hello".to_string()));
let result = transpiler.transpile_send(&actor, &message);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("lock"));
assert!(tokens.contains("handle_message"));
assert!(tokens.contains("Hello"));
}
#[test]
fn test_transpile_send_qualified_name() {
let transpiler = make_transpiler();
let actor = make_ident("system_actor");
let message = Expr::new(
ExprKind::QualifiedName {
module: "Message".to_string(),
name: "Reset".to_string(),
},
Span::new(0, 1),
);
let result = transpiler.transpile_send(&actor, &message);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("lock"));
assert!(tokens.contains("handle_message"));
assert!(tokens.contains("Message"));
assert!(tokens.contains("Reset"));
}
#[test]
fn test_transpile_ask_no_timeout_simplified() {
let transpiler = make_transpiler();
let actor = make_ident("query_actor");
let message = make_ident("GetData");
let result = transpiler.transpile_ask(&actor, &message, None);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("lock"));
assert!(tokens.contains("handle_message"));
}
#[test]
fn test_transpile_ask_with_timeout_simplified() {
let transpiler = make_transpiler();
let actor = make_ident("query_actor");
let message = make_ident("GetData");
let timeout = Some(make_literal(Literal::Integer(100, None)));
let result = transpiler.transpile_ask(&actor, &message, timeout.as_ref());
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("lock"));
assert!(tokens.contains("handle_message"));
}
#[test]
fn test_transpile_ask_literal_message() {
let transpiler = make_transpiler();
let actor = make_ident("actor");
let message = make_literal(Literal::String("Query".to_string()));
let result = transpiler.transpile_ask(&actor, &message, None);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Query"));
assert!(tokens.contains("lock"));
}
#[test]
fn test_transpile_command_with_env() {
let transpiler = make_transpiler();
let program = "node";
let args = vec!["script.js".to_string()];
let env = vec![
("NODE_ENV".to_string(), "production".to_string()),
("PATH".to_string(), "/usr/bin".to_string()),
];
let working_dir = None;
let result = transpiler.transpile_command(program, &args, &env, &working_dir);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Command :: new"));
assert!(tokens.contains("node"));
assert!(tokens.contains("script.js"));
}
#[test]
fn test_transpile_command_with_working_dir() {
let transpiler = make_transpiler();
let program = "make";
let args = vec!["build".to_string()];
let env = vec![];
let working_dir = Some("/home/user/project".to_string());
let result = transpiler.transpile_command(program, &args, &env, &working_dir);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("Command :: new"));
assert!(tokens.contains("make"));
assert!(tokens.contains("build"));
}
#[test]
fn test_transpile_command_complex() {
let transpiler = make_transpiler();
let program = "git";
let args = vec![
"commit".to_string(),
"-m".to_string(),
"test message".to_string(),
"--author".to_string(),
"Test <test@example.com>".to_string(),
];
let env = vec![];
let working_dir = None;
let result = transpiler.transpile_command(program, &args, &env, &working_dir);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("git"));
assert!(tokens.contains("commit"));
assert!(tokens.contains("test message"));
assert!(tokens.contains("--author"));
}
#[test]
fn test_actor_empty_state_with_handlers() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![
ActorHandler {
message_type: "Ping".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Unit)),
},
ActorHandler {
message_type: "Pong".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Unit)),
},
];
let result = transpiler.transpile_actor("PingPong", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("struct PingPong"));
assert!(tokens.contains("Ping"));
assert!(tokens.contains("Pong"));
assert!(tokens.contains("fn new"));
}
#[test]
fn test_actor_derive_attributes() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![];
let result = transpiler.transpile_actor("Simple", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("# [derive (Debug , Clone)]"));
assert!(tokens.contains("enum SimpleMessage"));
assert!(tokens.contains("struct Simple"));
}
#[test]
fn test_transpile_send_complex_actor() {
let transpiler = make_transpiler();
let actor = Expr::new(
ExprKind::FieldAccess {
object: Box::new(make_ident("system")),
field: "worker".to_string(),
},
Span::new(0, 1),
);
let message = make_ident("Task");
let result = transpiler.transpile_send(&actor, &message);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("lock"));
assert!(tokens.contains("handle_message"));
}
#[test]
fn test_transpile_command_special_chars() {
let transpiler = make_transpiler();
let program = "echo";
let args = vec![
"Hello, World!".to_string(),
"Test@#$%".to_string(),
"path/to/file".to_string(),
];
let env = vec![];
let working_dir = None;
let result = transpiler.transpile_command(program, &args, &env, &working_dir);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("echo"));
assert!(tokens.contains("Hello, World!"));
assert!(tokens.contains("Test@#$%"));
assert!(tokens.contains("path/to/file"));
}
#[test]
fn test_actor_handler_match_arms() {
let transpiler = make_transpiler();
let state = vec![];
let handlers = vec![
ActorHandler {
message_type: "Action1".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Integer(1, None))),
},
ActorHandler {
message_type: "Action2".to_string(),
params: vec![],
body: Box::new(make_literal(Literal::Integer(2, None))),
},
];
let result = transpiler.transpile_actor("MultiAction", &state, &handlers);
assert!(result.is_ok());
let tokens = result
.expect("operation should succeed in test")
.to_string();
assert!(tokens.contains("match msg"));
assert!(tokens.contains("MultiActionMessage :: Action1"));
assert!(tokens.contains("MultiActionMessage :: Action2"));
}
}
#[cfg(test)]
mod property_tests_actors {
use proptest::proptest;
proptest! {
#[test]
fn test_transpile_actor_never_panics(input: String) {
let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
let _ = std::panic::catch_unwind(|| {
});
}
}
}