Coverage Report

Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}