1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ReplConfig {
9 pub timeout: u64,
11 pub max_history: usize,
13 pub syntax_highlighting: bool,
15 pub auto_completion: bool,
17 pub show_line_numbers: bool,
19}
20
21impl Default for ReplConfig {
22 fn default() -> Self {
23 Self {
24 timeout: 30,
25 max_history: 1000,
26 syntax_highlighting: true,
27 auto_completion: true,
28 show_line_numbers: false,
29 }
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct ReplSessionInfo {
36 pub command_count: usize,
38 pub variable_count: usize,
40 pub start_time: std::time::Instant,
42}
43
44#[derive(Debug, Clone)]
46pub struct CommandResult {
47 pub success: bool,
49 pub output: Option<String>,
51 pub execution_time_ms: u64,
53}
54
55impl CommandResult {
56 pub fn success(output: String, execution_time_ms: u64) -> Self {
58 Self {
59 success: true,
60 output: Some(output),
61 execution_time_ms,
62 }
63 }
64
65 pub fn failure(output: String, execution_time_ms: u64) -> Self {
67 Self {
68 success: false,
69 output: Some(output),
70 execution_time_ms,
71 }
72 }
73
74 pub fn is_success(&self) -> bool {
76 self.success
77 }
78}
79
80pub struct ReplSession {
82 config: ReplConfig,
83 variables: HashMap<String, String>,
84 command_count: usize,
85 start_time: std::time::Instant,
86}
87
88impl ReplSession {
89 pub fn new(config: ReplConfig) -> Self {
91 Self {
92 config,
93 variables: HashMap::new(),
94 command_count: 0,
95 start_time: std::time::Instant::now(),
96 }
97 }
98
99 pub async fn execute(&mut self, command: &str) -> Result<CommandResult, Box<dyn std::error::Error>> {
101 let start_time = std::time::Instant::now();
102 self.command_count += 1;
103
104 let result = match command.trim() {
105 ".help" => {
106 let help_text = r#"Kotoba REPL Commands:
107.help Show this help message
108.vars List all defined variables
109.clear Clear all variables
110.exit Exit the REPL
111.quit Exit the REPL
112
113Examples:
114let x = 42
115let name = "Hello"
1161 + 2
117x * 2
118"#;
119 CommandResult::success(help_text.to_string(), start_time.elapsed().as_millis() as u64)
120 }
121 ".vars" => {
122 let mut output = String::from("Defined variables:\n");
123 if self.variables.is_empty() {
124 output.push_str(" (none)\n");
125 } else {
126 for (name, value) in &self.variables {
127 output.push_str(&format!(" {} = {}\n", name, value));
128 }
129 }
130 CommandResult::success(output, start_time.elapsed().as_millis() as u64)
131 }
132 ".clear" => {
133 self.variables.clear();
134 CommandResult::success("All variables cleared".to_string(), start_time.elapsed().as_millis() as u64)
135 }
136 cmd if cmd.starts_with("let ") => {
137 self.handle_let_command(cmd)
138 .map(|output| CommandResult::success(output, start_time.elapsed().as_millis() as u64))
139 .unwrap_or_else(|err| CommandResult::failure(err, start_time.elapsed().as_millis() as u64))
140 }
141 _ => {
142 let output = format!("Executed: {}\nResult: <evaluation not implemented yet>", command);
144 CommandResult::success(output, start_time.elapsed().as_millis() as u64)
145 }
146 };
147
148 Ok(result)
149 }
150
151 fn handle_let_command(&mut self, command: &str) -> Result<String, String> {
153 let parts: Vec<&str> = command.splitn(2, '=').collect();
154 if parts.len() != 2 {
155 return Err("Invalid variable assignment syntax. Use: let name = value".to_string());
156 }
157
158 let var_name = parts[0].trim().strip_prefix("let ").unwrap_or(parts[0].trim()).trim();
159 let var_value = parts[1].trim();
160
161 if var_name.is_empty() {
162 return Err("Variable name cannot be empty".to_string());
163 }
164
165 let clean_value = if var_value.starts_with('"') && var_value.ends_with('"') {
167 var_value[1..var_value.len()-1].to_string()
168 } else {
169 var_value.to_string()
170 };
171
172 self.variables.insert(var_name.to_string(), clean_value.clone());
173 Ok(format!("Variable '{}' set to '{}'", var_name, clean_value))
174 }
175
176 pub fn get_info(&self) -> ReplSessionInfo {
178 ReplSessionInfo {
179 command_count: self.command_count,
180 variable_count: self.variables.len(),
181 start_time: self.start_time,
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[tokio::test]
191 async fn test_repl_basic_commands() {
192 let config = ReplConfig::default();
193 let mut session = ReplSession::new(config);
194
195 let result = session.execute("let x = 42").await.unwrap();
197 assert!(result.is_success());
198 assert!(result.output.is_some());
199
200 let help_result = session.execute(".help").await.unwrap();
202 assert!(help_result.is_success());
203 assert!(help_result.output.is_some());
204 assert!(help_result.output.as_ref().unwrap().contains("Kotoba REPL Commands"));
205
206 let info = session.get_info();
208 assert_eq!(info.command_count, 2);
209 }
210
211 #[tokio::test]
212 async fn test_variable_operations() {
213 let config = ReplConfig::default();
214 let mut session = ReplSession::new(config);
215
216 let result1 = session.execute("let name = \"Alice\"").await.unwrap();
218 assert!(result1.is_success());
219
220 let result2 = session.execute(".vars").await.unwrap();
222 assert!(result2.is_success());
223 assert!(result2.output.as_ref().unwrap().contains("name"));
224 assert!(result2.output.as_ref().unwrap().contains("Alice"));
225 }
226
227 #[tokio::test]
228 async fn test_expression_evaluation() {
229 let config = ReplConfig::default();
230 let mut session = ReplSession::new(config);
231
232 let result = session.execute("1 + 2").await.unwrap();
234 assert!(result.is_success());
235 }
237
238 #[test]
239 fn test_repl_config_default() {
240 let config = ReplConfig::default();
241 assert_eq!(config.timeout, 30);
242 assert_eq!(config.max_history, 1000);
243 assert!(config.syntax_highlighting);
244 assert!(config.auto_completion);
245 assert!(!config.show_line_numbers);
246 }
247
248 #[test]
249 fn test_command_result() {
250 let success_result = CommandResult::success("output".to_string(), 100);
251 assert!(success_result.is_success());
252 assert_eq!(success_result.output, Some("output".to_string()));
253 assert_eq!(success_result.execution_time_ms, 100);
254
255 let failure_result = CommandResult::failure("error".to_string(), 50);
256 assert!(!failure_result.is_success());
257 assert_eq!(failure_result.output, Some("error".to_string()));
258 assert_eq!(failure_result.execution_time_ms, 50);
259 }
260}