kotoba_repl/
lib.rs

1//! Kotoba REPL - Interactive shell for Kotoba programming language
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// REPL configuration
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ReplConfig {
9    /// Command timeout in seconds
10    pub timeout: u64,
11    /// Maximum command history size
12    pub max_history: usize,
13    /// Enable syntax highlighting
14    pub syntax_highlighting: bool,
15    /// Enable auto-completion
16    pub auto_completion: bool,
17    /// Show line numbers
18    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/// REPL session information
34#[derive(Debug, Clone)]
35pub struct ReplSessionInfo {
36    /// Number of commands executed
37    pub command_count: usize,
38    /// Number of variables defined
39    pub variable_count: usize,
40    /// Session start time
41    pub start_time: std::time::Instant,
42}
43
44/// Command execution result
45#[derive(Debug, Clone)]
46pub struct CommandResult {
47    /// Whether the command executed successfully
48    pub success: bool,
49    /// Command output
50    pub output: Option<String>,
51    /// Execution time in milliseconds
52    pub execution_time_ms: u64,
53}
54
55impl CommandResult {
56    /// Create a successful result
57    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    /// Create a failed result
66    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    /// Check if the command was successful
75    pub fn is_success(&self) -> bool {
76        self.success
77    }
78}
79
80/// REPL session
81pub 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    /// Create a new REPL session
90    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    /// Execute a command
100    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                // For now, just echo the command as if it were evaluated
143                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    /// Handle let command for variable assignment
152    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        // Remove quotes if present
166        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    /// Get session information
177    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        // 変数宣言のテスト
196        let result = session.execute("let x = 42").await.unwrap();
197        assert!(result.is_success());
198        assert!(result.output.is_some());
199
200        // ヘルプコマンドのテスト
201        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        // セッション情報のテスト
207        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        // 変数宣言
217        let result1 = session.execute("let name = \"Alice\"").await.unwrap();
218        assert!(result1.is_success());
219
220        // 変数一覧表示
221        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        // 簡単な式の評価
233        let result = session.execute("1 + 2").await.unwrap();
234        assert!(result.is_success());
235        // 簡易的な評価なので、結果は実行されたことを示すメッセージになる
236    }
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}