aprender_present_lib/browser/
notebook.rs1use std::collections::{HashMap, HashSet, VecDeque};
29
30pub type CellId = u64;
32
33#[derive(Debug, Clone, Default)]
35pub struct CellOutput {
36 pub stdout: String,
38 pub stderr: String,
40 pub value: Option<String>,
42 pub exec_time_ms: u64,
44}
45
46#[derive(Debug, Clone)]
48pub struct Cell {
49 pub id: CellId,
51 pub source: String,
53 pub output: CellOutput,
55 pub dependencies: HashSet<String>,
57 pub definitions: HashSet<String>,
59 pub dirty: bool,
61 pub execution_order: u64,
63}
64
65impl Cell {
66 #[must_use]
68 pub fn new(id: CellId, source: impl Into<String>) -> Self {
69 let source = source.into();
70 let (dependencies, definitions) = Self::analyze_dependencies(&source);
71
72 Self {
73 id,
74 source,
75 output: CellOutput::default(),
76 dependencies,
77 definitions,
78 dirty: true,
79 execution_order: 0,
80 }
81 }
82
83 pub fn update_source(&mut self, source: impl Into<String>) {
85 self.source = source.into();
86 let (deps, defs) = Self::analyze_dependencies(&self.source);
87 self.dependencies = deps;
88 self.definitions = defs;
89 self.dirty = true;
90 }
91
92 fn analyze_dependencies(source: &str) -> (HashSet<String>, HashSet<String>) {
96 let mut dependencies = HashSet::new();
97 let mut definitions = HashSet::new();
98
99 for line in source.lines() {
100 let trimmed = line.trim();
101
102 if let Some(rest) = trimmed.strip_prefix("let ") {
104 if let Some(eq_pos) = rest.find('=') {
105 let name = rest[..eq_pos].trim().to_string();
106 definitions.insert(name);
107
108 let rhs = &rest[eq_pos + 1..];
110 for word in rhs.split(|c: char| !c.is_alphanumeric() && c != '_') {
111 let word = word.trim();
112 if !word.is_empty() && word.chars().next().is_some_and(char::is_alphabetic)
113 {
114 dependencies.insert(word.to_string());
115 }
116 }
117 }
118 } else {
119 for word in trimmed.split(|c: char| !c.is_alphanumeric() && c != '_') {
121 let word = word.trim();
122 if !word.is_empty() && word.chars().next().is_some_and(char::is_alphabetic) {
123 dependencies.insert(word.to_string());
124 }
125 }
126 }
127 }
128
129 for def in &definitions {
131 dependencies.remove(def);
132 }
133
134 for kw in &[
136 "let", "if", "else", "for", "while", "fun", "return", "true", "false",
137 ] {
138 dependencies.remove(*kw);
139 }
140
141 (dependencies, definitions)
142 }
143}
144
145#[derive(Debug, Default)]
147pub struct CellGraph {
148 cells: HashMap<CellId, Cell>,
150 var_to_cell: HashMap<String, CellId>,
152 next_id: CellId,
154}
155
156impl CellGraph {
157 #[must_use]
159 pub fn new() -> Self {
160 Self::default()
161 }
162
163 #[must_use]
165 pub fn add_cell(&mut self, source: impl Into<String>) -> CellId {
166 let id = self.next_id;
167 self.next_id += 1;
168
169 let cell = Cell::new(id, source);
170
171 for var in &cell.definitions {
173 self.var_to_cell.insert(var.clone(), id);
174 }
175
176 self.cells.insert(id, cell);
177 id
178 }
179
180 pub fn update_cell(&mut self, id: CellId, source: impl Into<String>) -> HashSet<CellId> {
184 let mut dirty = HashSet::new();
185
186 if let Some(cell) = self.cells.get_mut(&id) {
187 for var in &cell.definitions {
189 self.var_to_cell.remove(var);
190 }
191
192 cell.update_source(source);
194
195 for var in &cell.definitions {
197 self.var_to_cell.insert(var.clone(), id);
198 }
199
200 dirty.insert(id);
201 }
202
203 self.propagate_dirty(id, &mut dirty);
205
206 dirty
207 }
208
209 pub fn remove_cell(&mut self, id: CellId) -> Option<Cell> {
211 if let Some(cell) = self.cells.remove(&id) {
212 for var in &cell.definitions {
214 if self.var_to_cell.get(var) == Some(&id) {
215 self.var_to_cell.remove(var);
216 }
217 }
218 Some(cell)
219 } else {
220 None
221 }
222 }
223
224 #[must_use]
226 pub fn get_cell(&self, id: CellId) -> Option<&Cell> {
227 self.cells.get(&id)
228 }
229
230 pub fn get_cell_mut(&mut self, id: CellId) -> Option<&mut Cell> {
232 self.cells.get_mut(&id)
233 }
234
235 pub fn cells(&self) -> impl Iterator<Item = &Cell> {
237 self.cells.values()
238 }
239
240 #[must_use]
242 pub fn len(&self) -> usize {
243 self.cells.len()
244 }
245
246 #[must_use]
248 pub fn is_empty(&self) -> bool {
249 self.cells.is_empty()
250 }
251
252 fn propagate_dirty(&mut self, from: CellId, dirty: &mut HashSet<CellId>) {
254 let definitions: HashSet<String> = self
256 .cells
257 .get(&from)
258 .map(|c| c.definitions.clone())
259 .unwrap_or_default();
260
261 for (id, cell) in &mut self.cells {
263 if *id == from {
264 continue;
265 }
266
267 if cell.dependencies.iter().any(|d| definitions.contains(d)) {
269 cell.dirty = true;
270 dirty.insert(*id);
271 }
272 }
273 }
274
275 #[must_use]
279 pub fn topological_order(&self) -> Option<Vec<CellId>> {
280 let mut in_degree: HashMap<CellId, usize> = HashMap::new();
281 let mut edges: HashMap<CellId, Vec<CellId>> = HashMap::new();
282
283 for id in self.cells.keys() {
285 in_degree.insert(*id, 0);
286 edges.insert(*id, Vec::new());
287 }
288
289 for (id, cell) in &self.cells {
291 for dep in &cell.dependencies {
292 if let Some(&def_cell) = self.var_to_cell.get(dep) {
293 if def_cell != *id {
294 edges.entry(def_cell).or_default().push(*id);
295 *in_degree.entry(*id).or_default() += 1;
296 }
297 }
298 }
299 }
300
301 let mut queue: VecDeque<CellId> = in_degree
303 .iter()
304 .filter(|(_, °)| deg == 0)
305 .map(|(&id, _)| id)
306 .collect();
307
308 let mut result = Vec::with_capacity(self.cells.len());
309
310 while let Some(id) = queue.pop_front() {
311 result.push(id);
312
313 if let Some(neighbors) = edges.get(&id) {
314 for &neighbor in neighbors {
315 if let Some(deg) = in_degree.get_mut(&neighbor) {
316 *deg -= 1;
317 if *deg == 0 {
318 queue.push_back(neighbor);
319 }
320 }
321 }
322 }
323 }
324
325 if result.len() == self.cells.len() {
326 Some(result)
327 } else {
328 None }
330 }
331
332 #[must_use]
334 pub fn cells_to_execute(&self) -> Vec<CellId> {
335 self.topological_order()
336 .unwrap_or_default()
337 .into_iter()
338 .filter(|id| self.cells.get(id).is_some_and(|c| c.dirty))
339 .collect()
340 }
341}
342
343#[derive(Debug, Default)]
345pub struct NotebookRuntime {
346 graph: CellGraph,
348 execution_counter: u64,
350 namespace: HashMap<String, String>,
352}
353
354impl NotebookRuntime {
355 #[must_use]
357 pub fn new() -> Self {
358 Self::default()
359 }
360
361 #[must_use]
363 pub fn add_cell(&mut self, source: impl Into<String>) -> CellId {
364 self.graph.add_cell(source)
365 }
366
367 pub fn update_cell(&mut self, id: CellId, source: impl Into<String>) -> HashSet<CellId> {
371 self.graph.update_cell(id, source)
372 }
373
374 pub fn remove_cell(&mut self, id: CellId) -> Option<Cell> {
376 self.graph.remove_cell(id)
377 }
378
379 #[must_use]
381 pub fn get_cell(&self, id: CellId) -> Option<&Cell> {
382 self.graph.get_cell(id)
383 }
384
385 pub fn cells(&self) -> impl Iterator<Item = &Cell> {
387 self.graph.cells()
388 }
389
390 #[must_use]
392 pub fn cells_in_order(&self) -> Vec<CellId> {
393 self.graph.topological_order().unwrap_or_default()
394 }
395
396 #[must_use]
398 pub fn dirty_cells(&self) -> Vec<CellId> {
399 self.graph.cells_to_execute()
400 }
401
402 pub fn execute_cell(&mut self, id: CellId) -> Option<&CellOutput> {
406 self.execution_counter += 1;
407 let counter = self.execution_counter;
408
409 if let Some(cell) = self.graph.get_cell_mut(id) {
410 cell.dirty = false;
413 cell.execution_order = counter;
414 cell.output = CellOutput {
415 stdout: String::new(),
416 stderr: String::new(),
417 value: Some(format!("/* executed cell {} */", id)),
418 exec_time_ms: 0,
419 };
420
421 for var in &cell.definitions {
423 self.namespace.insert(var.clone(), "null".to_string());
424 }
425
426 return Some(&cell.output);
427 }
428 None
429 }
430
431 pub fn execute_dirty(&mut self) -> Vec<(CellId, CellOutput)> {
433 let to_execute = self.dirty_cells();
434 let mut results = Vec::with_capacity(to_execute.len());
435
436 for id in to_execute {
437 if let Some(output) = self.execute_cell(id) {
438 results.push((id, output.clone()));
439 }
440 }
441
442 results
443 }
444
445 pub fn execute_all(&mut self) -> Vec<(CellId, CellOutput)> {
447 for cell in self.graph.cells.values_mut() {
449 cell.dirty = true;
450 }
451
452 self.execute_dirty()
453 }
454
455 #[must_use]
457 pub fn namespace(&self) -> &HashMap<String, String> {
458 &self.namespace
459 }
460
461 pub fn clear_namespace(&mut self) {
463 self.namespace.clear();
464 }
465
466 #[must_use]
468 pub fn len(&self) -> usize {
469 self.graph.len()
470 }
471
472 #[must_use]
474 pub fn is_empty(&self) -> bool {
475 self.graph.is_empty()
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 fn test_cell_creation() {
485 let cell = Cell::new(1, "let x = 10");
486 assert_eq!(cell.id, 1);
487 assert!(cell.definitions.contains("x"));
488 assert!(!cell.dependencies.contains("x")); }
490
491 #[test]
492 fn test_cell_dependencies() {
493 let cell = Cell::new(1, "let y = x * 2");
494 assert!(cell.definitions.contains("y"));
495 assert!(cell.dependencies.contains("x"));
496 }
497
498 #[test]
499 fn test_cell_graph_add() {
500 let mut graph = CellGraph::new();
501 let id = graph.add_cell("let x = 10");
502 assert_eq!(graph.len(), 1);
503 assert!(graph.get_cell(id).is_some());
504 }
505
506 #[test]
507 fn test_cell_graph_topological_order() {
508 let mut graph = CellGraph::new();
509 let a = graph.add_cell("let x = 10");
510 let b = graph.add_cell("let y = x * 2");
511 let c = graph.add_cell("let z = x + y");
512
513 let order = graph.topological_order().unwrap();
514
515 let pos_a = order.iter().position(|&id| id == a).unwrap();
517 let pos_b = order.iter().position(|&id| id == b).unwrap();
518 let pos_c = order.iter().position(|&id| id == c).unwrap();
519
520 assert!(pos_a < pos_b);
521 assert!(pos_a < pos_c);
522 assert!(pos_b < pos_c); }
524
525 #[test]
526 fn test_cell_graph_update_propagates_dirty() {
527 let mut graph = CellGraph::new();
528 let a = graph.add_cell("let x = 10");
529 let b = graph.add_cell("let y = x * 2");
530
531 graph.get_cell_mut(a).unwrap().dirty = false;
533 graph.get_cell_mut(b).unwrap().dirty = false;
534
535 let dirty = graph.update_cell(a, "let x = 20");
537
538 assert!(dirty.contains(&a));
539 assert!(dirty.contains(&b)); }
541
542 #[test]
543 fn test_notebook_runtime_basic() {
544 let mut notebook = NotebookRuntime::new();
545 let a = notebook.add_cell("let x = 10");
546 let b = notebook.add_cell("let y = x * 2");
547
548 assert_eq!(notebook.len(), 2);
549
550 let results = notebook.execute_all();
552 assert_eq!(results.len(), 2);
553
554 assert!(!notebook.get_cell(a).unwrap().dirty);
556 assert!(!notebook.get_cell(b).unwrap().dirty);
557 }
558
559 #[test]
560 fn test_notebook_runtime_reactive_update() {
561 let mut notebook = NotebookRuntime::new();
562 let a = notebook.add_cell("let x = 10");
563 let b = notebook.add_cell("let y = x * 2");
564
565 notebook.execute_all();
567
568 let dirty = notebook.update_cell(a, "let x = 20");
570
571 assert!(dirty.contains(&a));
573 assert!(dirty.contains(&b));
574
575 let results = notebook.execute_dirty();
577 assert_eq!(results.len(), 2);
578 }
579
580 #[test]
581 fn test_notebook_remove_cell() {
582 let mut notebook = NotebookRuntime::new();
583 let a = notebook.add_cell("let x = 10");
584
585 assert_eq!(notebook.len(), 1);
586
587 let removed = notebook.remove_cell(a);
588 assert!(removed.is_some());
589 assert_eq!(notebook.len(), 0);
590 }
591
592 #[test]
593 fn test_cell_graph_cycle_detection() {
594 let mut graph = CellGraph::new();
595
596 let _ = graph.add_cell("let x = 10");
598 let _ = graph.add_cell("let y = x * 2");
599
600 assert!(graph.topological_order().is_some());
602 }
603
604 #[test]
605 fn test_cell_expression_dependencies() {
606 let cell = Cell::new(1, "x + y + z");
607 assert!(cell.dependencies.contains("x"));
608 assert!(cell.dependencies.contains("y"));
609 assert!(cell.dependencies.contains("z"));
610 assert!(cell.definitions.is_empty());
611 }
612}