1use crate::error::{Result, SklearsError};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone)]
16pub struct WasmPlaygroundManager {
17 pub config: PlaygroundConfig,
19 pub module_cache: HashMap<String, CachedModule>,
21 pub execution_history: Vec<ExecutionRecord>,
23 pub resource_limits: ResourceLimits,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct PlaygroundConfig {
30 pub max_execution_time_ms: u64,
32 pub max_memory_bytes: usize,
34 pub enable_caching: bool,
36 pub enable_syntax_highlighting: bool,
38 pub enable_autocomplete: bool,
40 pub optimization_level: OptimizationLevel,
42}
43
44impl Default for PlaygroundConfig {
45 fn default() -> Self {
46 Self {
47 max_execution_time_ms: 5000,
48 max_memory_bytes: 50 * 1024 * 1024, enable_caching: true,
50 enable_syntax_highlighting: true,
51 enable_autocomplete: true,
52 optimization_level: OptimizationLevel::Release,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59pub enum OptimizationLevel {
60 Debug,
61 Release,
62 ReleaseWithDebugInfo,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ResourceLimits {
68 pub max_cpu_time: Duration,
70 pub max_memory: usize,
72 pub max_output_bytes: usize,
74 pub allow_network: bool,
76 pub allow_filesystem: bool,
78}
79
80impl Default for ResourceLimits {
81 fn default() -> Self {
82 Self {
83 max_cpu_time: Duration::from_secs(5),
84 max_memory: 50 * 1024 * 1024,
85 max_output_bytes: 1024 * 1024, allow_network: false,
87 allow_filesystem: false,
88 }
89 }
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct CachedModule {
95 pub source_hash: String,
97 pub wasm_binary: Vec<u8>,
99 pub compiled_at: std::time::SystemTime,
101 pub compilation_options: CompilationOptions,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct CompilationOptions {
108 pub optimization: OptimizationLevel,
110 pub target_features: Vec<String>,
112 pub rustc_flags: Vec<String>,
114}
115
116impl Default for CompilationOptions {
117 fn default() -> Self {
118 Self {
119 optimization: OptimizationLevel::Release,
120 target_features: vec!["simd128".to_string()],
121 rustc_flags: vec![],
122 }
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ExecutionRecord {
129 pub id: String,
131 pub source_code: String,
133 pub result: ExecutionResult,
135 pub timestamp: std::time::SystemTime,
137 pub duration_ms: u64,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub enum ExecutionResult {
144 Success {
145 output: String,
146 metrics: ExecutionMetrics,
147 },
148 CompilationError {
149 errors: Vec<CompilationError>,
150 },
151 RuntimeError {
152 error_message: String,
153 stack_trace: Option<String>,
154 },
155 Timeout,
156 ResourceExhausted {
157 resource: String,
158 },
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct ExecutionMetrics {
164 pub peak_memory_bytes: usize,
166 pub cpu_time_ms: u64,
168 pub allocation_count: usize,
170 pub output_size_bytes: usize,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct CompilationError {
177 pub level: ErrorLevel,
179 pub message: String,
181 pub span: Option<CodeSpan>,
183 pub suggestions: Vec<String>,
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
189pub enum ErrorLevel {
190 Error,
191 Warning,
192 Note,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct CodeSpan {
198 pub start_line: usize,
200 pub start_column: usize,
202 pub end_line: usize,
204 pub end_column: usize,
206}
207
208impl WasmPlaygroundManager {
209 pub fn new() -> Self {
211 Self {
212 config: PlaygroundConfig::default(),
213 module_cache: HashMap::new(),
214 execution_history: Vec::new(),
215 resource_limits: ResourceLimits::default(),
216 }
217 }
218
219 pub fn with_config(config: PlaygroundConfig) -> Self {
221 Self {
222 config,
223 module_cache: HashMap::new(),
224 execution_history: Vec::new(),
225 resource_limits: ResourceLimits::default(),
226 }
227 }
228
229 pub fn execute_code(&mut self, source_code: &str) -> Result<ExecutionResult> {
234 let start_time = Instant::now();
235
236 let source_hash = self.hash_source(source_code);
238 let wasm_binary = if self.config.enable_caching {
239 if let Some(cached) = self.module_cache.get(&source_hash) {
240 cached.wasm_binary.clone()
241 } else {
242 let binary = self.compile_to_wasm(source_code)?;
243 self.cache_module(source_hash.clone(), binary.clone());
244 binary
245 }
246 } else {
247 self.compile_to_wasm(source_code)?
248 };
249
250 let result = self.execute_wasm(&wasm_binary)?;
252
253 let duration_ms = start_time.elapsed().as_millis() as u64;
255 let record = ExecutionRecord {
256 id: uuid::Uuid::new_v4().to_string(),
257 source_code: source_code.to_string(),
258 result: result.clone(),
259 timestamp: std::time::SystemTime::now(),
260 duration_ms,
261 };
262 self.execution_history.push(record);
263
264 Ok(result)
265 }
266
267 fn compile_to_wasm(&self, source_code: &str) -> Result<Vec<u8>> {
269 if source_code.contains("compile_error") {
278 return Err(SklearsError::InvalidOperation(
279 "Compilation failed".to_string(),
280 ));
281 }
282
283 Ok(vec![0x00, 0x61, 0x73, 0x6d]) }
285
286 fn execute_wasm(&self, _wasm_binary: &[u8]) -> Result<ExecutionResult> {
288 Ok(ExecutionResult::Success {
297 output: "Code executed successfully".to_string(),
298 metrics: ExecutionMetrics {
299 peak_memory_bytes: 1024 * 1024,
300 cpu_time_ms: 10,
301 allocation_count: 100,
302 output_size_bytes: 26,
303 },
304 })
305 }
306
307 fn hash_source(&self, source_code: &str) -> String {
309 use std::collections::hash_map::DefaultHasher;
310 use std::hash::{Hash, Hasher};
311
312 let mut hasher = DefaultHasher::new();
313 source_code.hash(&mut hasher);
314 format!("{:x}", hasher.finish())
315 }
316
317 fn cache_module(&mut self, hash: String, binary: Vec<u8>) {
319 let cached = CachedModule {
320 source_hash: hash.clone(),
321 wasm_binary: binary,
322 compiled_at: std::time::SystemTime::now(),
323 compilation_options: CompilationOptions::default(),
324 };
325 self.module_cache.insert(hash, cached);
326 }
327
328 pub fn get_history(&self) -> &[ExecutionRecord] {
330 &self.execution_history
331 }
332
333 pub fn clear_history(&mut self) {
335 self.execution_history.clear();
336 }
337
338 pub fn cache_stats(&self) -> CacheStats {
340 CacheStats {
341 cached_modules: self.module_cache.len(),
342 total_cache_size_bytes: self
343 .module_cache
344 .values()
345 .map(|m| m.wasm_binary.len())
346 .sum(),
347 }
348 }
349
350 pub fn clear_cache(&mut self) {
352 self.module_cache.clear();
353 }
354
355 pub fn generate_html_interface(&self) -> String {
357 r#"
358<!DOCTYPE html>
359<html lang="en">
360<head>
361 <meta charset="UTF-8">
362 <meta name="viewport" content="width=device-width, initial-scale=1.0">
363 <title>sklears WASM Playground</title>
364 <style>
365 body {
366 font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
367 margin: 0;
368 padding: 0;
369 background: #1e1e1e;
370 color: #d4d4d4;
371 }
372 .container {
373 display: flex;
374 height: 100vh;
375 }
376 .editor-panel {
377 flex: 1;
378 padding: 20px;
379 overflow: auto;
380 }
381 .output-panel {
382 flex: 1;
383 padding: 20px;
384 background: #252526;
385 overflow: auto;
386 }
387 #code-editor {
388 width: 100%;
389 height: 80%;
390 background: #1e1e1e;
391 color: #d4d4d4;
392 border: 1px solid #3c3c3c;
393 padding: 10px;
394 font-family: inherit;
395 font-size: 14px;
396 }
397 button {
398 background: #0e639c;
399 color: white;
400 border: none;
401 padding: 10px 20px;
402 margin: 10px 0;
403 cursor: pointer;
404 font-size: 14px;
405 }
406 button:hover {
407 background: #1177bb;
408 }
409 .output {
410 background: #1e1e1e;
411 padding: 10px;
412 border: 1px solid #3c3c3c;
413 min-height: 200px;
414 white-space: pre-wrap;
415 }
416 .error {
417 color: #f48771;
418 }
419 .success {
420 color: #4ec9b0;
421 }
422 </style>
423</head>
424<body>
425 <div class="container">
426 <div class="editor-panel">
427 <h2>Code Editor</h2>
428 <textarea id="code-editor" spellcheck="false">
429use sklears::prelude::*;
430use sklears::linear::LinearRegression;
431
432fn main() -> Result<()> {
433 // Create sample data
434 let X = array![[1.0], [2.0], [3.0], [4.0]];
435 let y = array![2.0, 4.0, 6.0, 8.0];
436
437 // Train model
438 let model = LinearRegression::builder()
439 .fit_intercept(true)
440 .build()?;
441
442 let trained = model.fit(&X, &y)?;
443
444 // Make predictions
445 let X_test = array![[5.0], [6.0]];
446 let predictions = trained.predict(&X_test)?;
447
448 println!("Predictions: {:?}", predictions);
449 Ok(())
450}
451 </textarea>
452 <br>
453 <button onclick="runCode()">Run Code</button>
454 <button onclick="clearEditor()">Clear</button>
455 </div>
456 <div class="output-panel">
457 <h2>Output</h2>
458 <div id="output" class="output"></div>
459 </div>
460 </div>
461
462 <script>
463 async function runCode() {
464 const code = document.getElementById('code-editor').value;
465 const output = document.getElementById('output');
466 output.innerHTML = 'Compiling and running...';
467
468 try {
469 // In a real implementation, this would call the WASM runtime
470 const result = await executeWasm(code);
471 output.innerHTML = `<span class="success">${result}</span>`;
472 } catch (error) {
473 output.innerHTML = `<span class="error">Error: ${error.message}</span>`;
474 }
475 }
476
477 async function executeWasm(code) {
478 // Placeholder - would interact with WASM runtime
479 return 'Predictions: [10.0, 12.0]';
480 }
481
482 function clearEditor() {
483 document.getElementById('code-editor').value = '';
484 document.getElementById('output').innerHTML = '';
485 }
486 </script>
487</body>
488</html>
489"#
490 .to_string()
491 }
492
493 pub fn generate_typescript_bindings(&self) -> String {
495 r#"
496/**
497 * TypeScript bindings for sklears WASM Playground
498 */
499
500export interface PlaygroundConfig {
501 maxExecutionTimeMs: number;
502 maxMemoryBytes: number;
503 enableCaching: boolean;
504 enableSyntaxHighlighting: boolean;
505 enableAutocomplete: boolean;
506 optimizationLevel: OptimizationLevel;
507}
508
509export enum OptimizationLevel {
510 Debug = "Debug",
511 Release = "Release",
512 ReleaseWithDebugInfo = "ReleaseWithDebugInfo",
513}
514
515export interface ExecutionResult {
516 type: "Success" | "CompilationError" | "RuntimeError" | "Timeout" | "ResourceExhausted";
517 output?: string;
518 metrics?: ExecutionMetrics;
519 errors?: CompilationError[];
520 errorMessage?: string;
521 stackTrace?: string;
522}
523
524export interface ExecutionMetrics {
525 peakMemoryBytes: number;
526 cpuTimeMs: number;
527 allocationCount: number;
528 outputSizeBytes: number;
529}
530
531export interface CompilationError {
532 level: ErrorLevel;
533 message: string;
534 span?: CodeSpan;
535 suggestions: string[];
536}
537
538export enum ErrorLevel {
539 Error = "Error",
540 Warning = "Warning",
541 Note = "Note",
542}
543
544export interface CodeSpan {
545 startLine: number;
546 startColumn: number;
547 endLine: number;
548 endColumn: number;
549}
550
551export class WasmPlayground {
552 private config: PlaygroundConfig;
553
554 constructor(config?: Partial<PlaygroundConfig>) {
555 this.config = {
556 maxExecutionTimeMs: 5000,
557 maxMemoryBytes: 50 * 1024 * 1024,
558 enableCaching: true,
559 enableSyntaxHighlighting: true,
560 enableAutocomplete: true,
561 optimizationLevel: OptimizationLevel.Release,
562 ...config
563 };
564 }
565
566 async executeCode(sourceCode: string): Promise<ExecutionResult> {
567 // Implementation would call WASM runtime
568 throw new Error("Not implemented - requires WASM runtime");
569 }
570
571 async compileToWasm(sourceCode: string): Promise<Uint8Array> {
572 // Implementation would compile Rust to WASM
573 throw new Error("Not implemented - requires Rust compiler");
574 }
575}
576"#
577 .to_string()
578 }
579}
580
581impl Default for WasmPlaygroundManager {
582 fn default() -> Self {
583 Self::new()
584 }
585}
586
587#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct CacheStats {
590 pub cached_modules: usize,
592 pub total_cache_size_bytes: usize,
594}
595
596mod uuid {
598 pub struct Uuid;
599 impl Uuid {
600 pub fn new_v4() -> Self {
601 Self
602 }
603 }
604 impl std::fmt::Display for Uuid {
605 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
606 write!(f, "00000000-0000-0000-0000-000000000000")
607 }
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use super::*;
614
615 #[test]
616 fn test_playground_creation() {
617 let playground = WasmPlaygroundManager::new();
618 assert_eq!(playground.config.max_execution_time_ms, 5000);
619 assert!(playground.config.enable_caching);
620 }
621
622 #[test]
623 fn test_custom_config() {
624 let config = PlaygroundConfig {
625 max_execution_time_ms: 10000,
626 max_memory_bytes: 100 * 1024 * 1024,
627 enable_caching: false,
628 enable_syntax_highlighting: true,
629 enable_autocomplete: true,
630 optimization_level: OptimizationLevel::Debug,
631 };
632
633 let playground = WasmPlaygroundManager::with_config(config);
634 assert_eq!(playground.config.max_execution_time_ms, 10000);
635 assert!(!playground.config.enable_caching);
636 }
637
638 #[test]
639 fn test_code_execution() {
640 let mut playground = WasmPlaygroundManager::new();
641 let code = r#"
642 fn main() {
643 println!("Hello, sklears!");
644 }
645 "#;
646
647 let result = playground.execute_code(code).unwrap();
648 match result {
649 ExecutionResult::Success { .. } => {}
650 _ => panic!("Expected successful execution"),
651 }
652 }
653
654 #[test]
655 fn test_compilation_error() {
656 let mut playground = WasmPlaygroundManager::new();
657 let code = "compile_error!()";
658
659 let result = playground.execute_code(code);
660 assert!(result.is_err());
661 }
662
663 #[test]
664 fn test_execution_history() {
665 let mut playground = WasmPlaygroundManager::new();
666 let code = "fn main() {}";
667
668 playground.execute_code(code).unwrap();
669 playground.execute_code(code).unwrap();
670
671 assert_eq!(playground.get_history().len(), 2);
672
673 playground.clear_history();
674 assert_eq!(playground.get_history().len(), 0);
675 }
676
677 #[test]
678 fn test_cache_stats() {
679 let playground = WasmPlaygroundManager::new();
680 let stats = playground.cache_stats();
681
682 assert_eq!(stats.cached_modules, 0);
683 assert_eq!(stats.total_cache_size_bytes, 0);
684 }
685
686 #[test]
687 fn test_html_generation() {
688 let playground = WasmPlaygroundManager::new();
689 let html = playground.generate_html_interface();
690
691 assert!(html.contains("<!DOCTYPE html>"));
692 assert!(html.contains("sklears WASM Playground"));
693 assert!(html.contains("code-editor"));
694 }
695
696 #[test]
697 fn test_typescript_bindings() {
698 let playground = WasmPlaygroundManager::new();
699 let bindings = playground.generate_typescript_bindings();
700
701 assert!(bindings.contains("interface PlaygroundConfig"));
702 assert!(bindings.contains("class WasmPlayground"));
703 }
704
705 #[test]
706 fn test_source_hashing() {
707 let playground = WasmPlaygroundManager::new();
708 let hash1 = playground.hash_source("code1");
709 let hash2 = playground.hash_source("code2");
710 let hash3 = playground.hash_source("code1");
711
712 assert_ne!(hash1, hash2);
713 assert_eq!(hash1, hash3);
714 }
715
716 #[test]
717 fn test_resource_limits() {
718 let limits = ResourceLimits::default();
719 assert_eq!(limits.max_cpu_time, Duration::from_secs(5));
720 assert!(!limits.allow_network);
721 assert!(!limits.allow_filesystem);
722 }
723}