/home/noah/src/ruchy/src/wasm/repl.rs
Line | Count | Source |
1 | | //! WebAssembly REPL implementation for browser-based evaluation |
2 | | //! |
3 | | //! Provides interactive Ruchy evaluation in the browser with progressive enhancement. |
4 | | |
5 | | use std::collections::HashMap; |
6 | | |
7 | | #[cfg(target_arch = "wasm32")] |
8 | | use wasm_bindgen::prelude::*; |
9 | | |
10 | | #[cfg(not(target_arch = "wasm32"))] |
11 | | use serde::{Serialize, Deserialize}; |
12 | | |
13 | | // For non-WASM builds, provide stub types |
14 | | #[cfg(not(target_arch = "wasm32"))] |
15 | | #[derive(Debug, Clone)] |
16 | | pub struct JsValue; |
17 | | |
18 | | // ============================================================================ |
19 | | // REPL Output Types |
20 | | // ============================================================================ |
21 | | |
22 | | #[derive(Debug, Clone)] |
23 | | #[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))] |
24 | | pub struct ReplOutput { |
25 | | pub success: bool, |
26 | | pub display: Option<String>, |
27 | | pub type_info: Option<String>, |
28 | | pub rust_code: Option<String>, |
29 | | pub error: Option<String>, |
30 | | pub timing: TimingInfo, |
31 | | } |
32 | | |
33 | | #[derive(Debug, Clone)] |
34 | | #[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))] |
35 | | pub struct TimingInfo { |
36 | | pub parse_ms: f64, |
37 | | pub typecheck_ms: f64, |
38 | | pub eval_ms: f64, |
39 | | pub total_ms: f64, |
40 | | } |
41 | | |
42 | | // ============================================================================ |
43 | | // WASM REPL Implementation |
44 | | // ============================================================================ |
45 | | |
46 | | #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] |
47 | | pub struct WasmRepl { |
48 | | /// Bindings for variables |
49 | | bindings: HashMap<String, String>, |
50 | | /// Command history |
51 | | history: Vec<String>, |
52 | | /// Session ID for tracking |
53 | | session_id: String, |
54 | | } |
55 | | |
56 | | #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] |
57 | | impl WasmRepl { |
58 | | /// Create a new WASM REPL instance |
59 | | #[cfg_attr(target_arch = "wasm32", wasm_bindgen(constructor))] |
60 | 0 | pub fn new() -> Result<WasmRepl, JsValue> { |
61 | | #[cfg(target_arch = "wasm32")] |
62 | | console_error_panic_hook::set_once(); |
63 | | |
64 | 0 | Ok(WasmRepl { |
65 | 0 | bindings: HashMap::new(), |
66 | 0 | history: Vec::new(), |
67 | 0 | session_id: generate_session_id(), |
68 | 0 | }) |
69 | 0 | } |
70 | | |
71 | | /// Evaluate a Ruchy expression |
72 | | #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] |
73 | 0 | pub fn eval(&mut self, input: &str) -> Result<String, JsValue> { |
74 | 0 | let start = get_timestamp(); |
75 | | |
76 | | // Parse the input |
77 | 0 | let parse_start = get_timestamp(); |
78 | 0 | let mut parser = crate::Parser::new(input); |
79 | 0 | let ast = match parser.parse() { |
80 | 0 | Ok(ast) => ast, |
81 | 0 | Err(e) => { |
82 | 0 | return Ok(serde_json::to_string(&ReplOutput { |
83 | 0 | success: false, |
84 | 0 | display: None, |
85 | 0 | type_info: None, |
86 | 0 | rust_code: None, |
87 | 0 | error: Some(format!("Parse error: {e}")), |
88 | 0 | timing: TimingInfo { |
89 | 0 | parse_ms: get_timestamp() - parse_start, |
90 | 0 | typecheck_ms: 0.0, |
91 | 0 | eval_ms: 0.0, |
92 | 0 | total_ms: get_timestamp() - start, |
93 | 0 | }, |
94 | 0 | }).unwrap_or_else(|_| "Error serializing output".to_string())); |
95 | | } |
96 | | }; |
97 | 0 | let parse_time = get_timestamp() - parse_start; |
98 | | |
99 | | // Type checking would go here |
100 | 0 | let typecheck_start = get_timestamp(); |
101 | | // ... type checking implementation ... |
102 | 0 | let typecheck_time = get_timestamp() - typecheck_start; |
103 | | |
104 | | // Evaluation |
105 | 0 | let eval_start = get_timestamp(); |
106 | | // For now, just return the parsed AST as a string |
107 | 0 | let result = format!("{ast:?}"); |
108 | 0 | let eval_time = get_timestamp() - eval_start; |
109 | | |
110 | | // Add to history |
111 | 0 | self.history.push(input.to_string()); |
112 | | |
113 | | // Return result |
114 | 0 | Ok(serde_json::to_string(&ReplOutput { |
115 | 0 | success: true, |
116 | 0 | display: Some(result), |
117 | 0 | type_info: Some("Any".to_string()), |
118 | 0 | rust_code: None, |
119 | 0 | error: None, |
120 | 0 | timing: TimingInfo { |
121 | 0 | parse_ms: parse_time, |
122 | 0 | typecheck_ms: typecheck_time, |
123 | 0 | eval_ms: eval_time, |
124 | 0 | total_ms: get_timestamp() - start, |
125 | 0 | }, |
126 | 0 | }).unwrap_or_else(|_| "Error serializing output".to_string())) |
127 | 0 | } |
128 | | |
129 | | /// Get command history |
130 | | #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] |
131 | 0 | pub fn get_history(&self) -> Vec<String> { |
132 | 0 | self.history.clone() |
133 | 0 | } |
134 | | |
135 | | /// Clear the REPL state |
136 | | #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] |
137 | 0 | pub fn clear(&mut self) { |
138 | 0 | self.bindings.clear(); |
139 | 0 | self.history.clear(); |
140 | 0 | } |
141 | | |
142 | | /// Get session ID |
143 | | #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] |
144 | 0 | pub fn session_id(&self) -> String { |
145 | 0 | self.session_id.clone() |
146 | 0 | } |
147 | | } |
148 | | |
149 | | impl Default for WasmRepl { |
150 | 0 | fn default() -> Self { |
151 | 0 | Self::new().unwrap() |
152 | 0 | } |
153 | | } |
154 | | |
155 | | // ============================================================================ |
156 | | // Helper Functions |
157 | | // ============================================================================ |
158 | | |
159 | | /// Generate a unique session ID |
160 | 0 | fn generate_session_id() -> String { |
161 | | #[cfg(target_arch = "wasm32")] |
162 | | { |
163 | | // Use browser's crypto API for UUID |
164 | | format!("session-{}", js_sys::Date::now()) |
165 | | } |
166 | | #[cfg(not(target_arch = "wasm32"))] |
167 | | { |
168 | | // Use system time for non-WASM builds |
169 | 0 | format!("session-{}", std::time::SystemTime::now() |
170 | 0 | .duration_since(std::time::UNIX_EPOCH) |
171 | 0 | .unwrap() |
172 | 0 | .as_millis()) |
173 | | } |
174 | 0 | } |
175 | | |
176 | | /// Get current timestamp in milliseconds |
177 | 0 | fn get_timestamp() -> f64 { |
178 | | #[cfg(target_arch = "wasm32")] |
179 | | { |
180 | | js_sys::Date::now() |
181 | | } |
182 | | #[cfg(not(target_arch = "wasm32"))] |
183 | | { |
184 | 0 | std::time::SystemTime::now() |
185 | 0 | .duration_since(std::time::UNIX_EPOCH) |
186 | 0 | .unwrap() |
187 | 0 | .as_millis() as f64 |
188 | | } |
189 | 0 | } |
190 | | |
191 | | // ============================================================================ |
192 | | // Memory Management |
193 | | // ============================================================================ |
194 | | |
195 | | /// Heap allocator for WASM |
196 | | pub struct WasmHeap { |
197 | | /// Young generation for short-lived objects |
198 | | young: Vec<u8>, |
199 | | /// Old generation for long-lived objects |
200 | | old: Vec<u8>, |
201 | | /// GC roots |
202 | | roots: Vec<usize>, |
203 | | } |
204 | | |
205 | | impl WasmHeap { |
206 | 0 | pub fn new() -> Self { |
207 | 0 | Self { |
208 | 0 | young: Vec::with_capacity(256 * 1024), // 256KB |
209 | 0 | old: Vec::with_capacity(2 * 1024 * 1024), // 2MB |
210 | 0 | roots: Vec::new(), |
211 | 0 | } |
212 | 0 | } |
213 | | |
214 | | /// Perform minor garbage collection |
215 | 0 | pub fn minor_gc(&mut self) { |
216 | 0 | self.young.clear(); |
217 | 0 | } |
218 | | |
219 | | /// Perform major garbage collection |
220 | 0 | pub fn major_gc(&mut self) { |
221 | | // Mark phase |
222 | 0 | let mut marked = vec![false; self.old.len()]; |
223 | 0 | for &root in &self.roots { |
224 | 0 | if root < marked.len() { |
225 | 0 | marked[root] = true; |
226 | 0 | } |
227 | | } |
228 | | |
229 | | // Compact phase (simplified) |
230 | 0 | let mut compacted = Vec::new(); |
231 | 0 | for (i, &is_marked) in marked.iter().enumerate() { |
232 | 0 | if is_marked && i < self.old.len() { |
233 | 0 | compacted.push(self.old[i]); |
234 | 0 | } |
235 | | } |
236 | 0 | self.old = compacted; |
237 | 0 | } |
238 | | } |
239 | | |
240 | | impl Default for WasmHeap { |
241 | 0 | fn default() -> Self { |
242 | 0 | Self::new() |
243 | 0 | } |
244 | | } |
245 | | |
246 | | // ============================================================================ |
247 | | // Tests |
248 | | // ============================================================================ |
249 | | |
250 | | #[cfg(test)] |
251 | | mod tests { |
252 | | use super::*; |
253 | | |
254 | | #[test] |
255 | | fn test_wasm_repl_creation() { |
256 | | let repl = WasmRepl::new(); |
257 | | assert!(repl.is_ok()); |
258 | | } |
259 | | |
260 | | #[test] |
261 | | fn test_session_id() { |
262 | | let repl = WasmRepl::new().unwrap(); |
263 | | assert!(repl.session_id().starts_with("session-")); |
264 | | } |
265 | | |
266 | | #[test] |
267 | | fn test_heap() { |
268 | | let mut heap = WasmHeap::new(); |
269 | | heap.minor_gc(); |
270 | | heap.major_gc(); |
271 | | assert!(heap.young.is_empty()); |
272 | | } |
273 | | } |