This commit is contained in:
2025-09-13 17:14:15 +10:00
parent 4df702fb79
commit b81f6d01cd
5 changed files with 94 additions and 89 deletions

1
.gitignore vendored
View File

@@ -1 +0,0 @@
backend/target

1
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

View File

@@ -20,6 +20,7 @@ pub enum LeadErrCode {
Server, Server,
Unsupported, Unsupported,
Invalid, Invalid,
Ref,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@@ -4,7 +4,7 @@ use log::info;
use crate::{ use crate::{
cell::{Cell, CellRef}, cell::{Cell, CellRef},
common::Literal, common::{LeadErr, LeadErrCode, Literal},
evaluator::{Eval, evaluate}, evaluator::{Eval, evaluate},
}; };
@@ -43,7 +43,7 @@ impl Grid {
if self.cells.contains_key(&cell_ref) { if self.cells.contains_key(&cell_ref) {
updated_cells = self updated_cells = self
.update_exisiting_cell(raw_val, eval, precs, cell_ref)? .update_exisiting_cell(raw_val, eval, precs, cell_ref)
.into_iter() .into_iter()
.chain(updated_cells) .chain(updated_cells)
.collect(); .collect();
@@ -85,39 +85,22 @@ impl Grid {
// This is a topological order on the precedents graph // This is a topological order on the precedents graph
// i.e. if a requires b (e.g. a = 1 + b) then a -> b // i.e. if a requires b (e.g. a = 1 + b) then a -> b
// so a comes before b in the topo order // so a comes before b in the topo order
fn topo_order(&self, from: CellRef) -> Result<Vec<CellRef>, String> { fn topo_order(&self, from: CellRef) -> (Vec<CellRef>, bool) {
let mut res: Vec<CellRef> = Vec::new(); let mut res = Vec::new();
let mut search_set = Vec::new();
let mut temp = HashSet::new(); let mut temp = HashSet::new();
let mut perm = HashSet::new(); let mut perm = HashSet::new();
let mut cycle_detected = false;
search_set.push(from);
let cell_data = &self.cells[&from];
search_set.extend(cell_data.deps().iter());
temp.insert(from);
perm.insert(from); // Make this inside the inner topo_visit
let mut searched = 1;
while searched != search_set.len() {
if perm.contains(&search_set[searched]) {
searched += 1;
continue;
}
self.topo_visit( self.topo_visit(
search_set[searched], from,
&mut temp, &mut temp,
&mut perm, &mut perm,
&mut search_set,
&mut res, &mut res,
)?; &mut cycle_detected,
searched += 1; from,
} );
Ok(res) (res, cycle_detected)
} }
fn topo_visit( fn topo_visit(
@@ -125,38 +108,38 @@ impl Grid {
cell: CellRef, cell: CellRef,
temp: &mut HashSet<CellRef>, temp: &mut HashSet<CellRef>,
perm: &mut HashSet<CellRef>, perm: &mut HashSet<CellRef>,
search_set: &mut Vec<CellRef>,
res: &mut Vec<CellRef>, res: &mut Vec<CellRef>,
) -> Result<(), String> { cycle_detected: &mut bool,
caller: CellRef,
) {
if perm.contains(&cell) { if perm.contains(&cell) {
return Ok(()); return;
}
if temp.contains(&cell) {
return Err("Evalutation error: Cycle detected in cell refs.".into());
} }
temp.insert(cell); if !temp.insert(cell) {
*cycle_detected = true;
return;
}
if !self.cells.contains_key(&cell) { if !self.cells.contains_key(&cell) {
perm.insert(cell); perm.insert(cell);
res.push(cell); res.push(cell);
return Ok(()); return;
} }
let cell_data = &self.cells[&cell]; // Walk dependencies if this cell exists; otherwise treat as leaf/external ref.
if let Some(cell_data) = self.cells.get(&cell) {
search_set.extend(cell_data.deps().iter()); for &dep in cell_data.deps().iter() {
// search_set.extend(cell_data.precedents.iter().cloned()); self.topo_visit(dep, temp, perm, res, cycle_detected, caller);
}
for prec in cell_data.precs().iter() {
self.topo_visit(*prec, temp, perm, search_set, res)?;
} }
// Done exploring this node
temp.remove(&cell);
perm.insert(cell); perm.insert(cell);
if cell != caller {
res.push(cell); res.push(cell);
}
Ok(())
} }
fn update_exisiting_cell( fn update_exisiting_cell(
@@ -165,13 +148,13 @@ impl Grid {
new_eval: Eval, new_eval: Eval,
new_precs: HashSet<CellRef>, new_precs: HashSet<CellRef>,
cell_ref: CellRef, cell_ref: CellRef,
) -> Result<Vec<CellRef>, String> { ) -> Vec<CellRef> {
let (old_precs, old_eval) = match self.cells.get_mut(&cell_ref) { let (old_precs, old_eval) = match self.cells.get_mut(&cell_ref) {
Some(cell) => { Some(cell) => {
cell.set_raw(raw); cell.set_raw(raw);
(cell.precs().clone(), cell.eval().clone()) (cell.precs().clone(), cell.eval().clone())
} }
None => return Ok(Vec::new()), None => return Vec::new(),
}; };
// diffs (outside any borrow) // diffs (outside any borrow)
@@ -204,12 +187,24 @@ impl Grid {
let cell = self.cells.get_mut(&cell_ref).unwrap(); // Should be impossible to crash let cell = self.cells.get_mut(&cell_ref).unwrap(); // Should be impossible to crash
cell.set_precs(new_precs); cell.set_precs(new_precs);
cell.set_eval(new_eval);
if eval_changed { if eval_changed {
self.propagate(cell_ref) cell.set_eval(new_eval);
match self.propagate(cell_ref) {
Ok(affected_cells) => affected_cells,
Err(affected_cells) => {
let cell = self.cells.get_mut(&cell_ref).unwrap();
cell.set_eval(Eval::Err(LeadErr {
title: "Propagation error.".into(),
desc: "Circular dependencies detected.".into(),
code: LeadErrCode::Ref,
}));
affected_cells
}
}
} else { } else {
Ok(Vec::new()) Vec::new()
} }
} }
@@ -239,13 +234,11 @@ impl Grid {
); );
} }
fn propagate(&mut self, from: CellRef) -> Result<Vec<CellRef>, String> { fn propagate(&mut self, from: CellRef) -> Result<Vec<CellRef>, Vec<CellRef>> {
let mut res = Vec::new(); let (topo, cycle_detected) = self.topo_order(from);
let topo = self.topo_order(from)?;
for cell_ref in topo {
res.push(cell_ref);
if !cycle_detected {
for cell_ref in topo.to_owned() {
let raw = if let Some(cell) = self.cells.get(&cell_ref) { let raw = if let Some(cell) = self.cells.get(&cell_ref) {
let s = cell.raw(); let s = cell.raw();
if let Some(rest) = s.strip_prefix('=') { if let Some(rest) = s.strip_prefix('=') {
@@ -257,14 +250,25 @@ impl Grid {
continue; continue;
}; };
// Now we dropped the borrow of self.cells before this point
let (e, _) = evaluate(raw, Some(self)); let (e, _) = evaluate(raw, Some(self));
if let Some(cell) = self.cells.get_mut(&cell_ref) { if let Some(cell) = self.cells.get_mut(&cell_ref) {
cell.set_eval(e); cell.set_eval(e);
} }
} }
Ok(topo)
Ok(res) } else {
let err = LeadErr {
title: "Propagation error.".into(),
desc: "Circular dependencies detected.".into(),
code: LeadErrCode::Ref,
};
topo.iter().for_each(|cell_ref| {
if let Some(cell) = self.cells.get_mut(&cell_ref) {
cell.set_eval(Eval::Err(err.to_owned()));
}
});
Err(topo)
}
} }
} }

View File

@@ -51,18 +51,18 @@
</Sidebar.GroupLabel> </Sidebar.GroupLabel>
<Sidebar.GroupContent> <Sidebar.GroupContent>
<Sidebar.Menu> <Sidebar.Menu>
{#each items as item (item.title)} <!-- {#each items as item (item.title)} -->
<Sidebar.MenuItem> <!-- <Sidebar.MenuItem> -->
<Sidebar.MenuButton> <!-- <Sidebar.MenuButton> -->
{#snippet child({ props })} <!-- {#snippet child({ props })} -->
<a href={item.url} {...props}> <!-- <a href={item.url} {...props}> -->
<item.icon /> <!-- <item.icon /> -->
<span>{item.title}</span> <!-- <span>{item.title}</span> -->
</a> <!-- </a> -->
{/snippet} <!-- {/snippet} -->
</Sidebar.MenuButton> <!-- </Sidebar.MenuButton> -->
</Sidebar.MenuItem> <!-- </Sidebar.MenuItem> -->
{/each} <!-- {/each} -->
</Sidebar.Menu> </Sidebar.Menu>
</Sidebar.GroupContent> </Sidebar.GroupContent>
</Sidebar.Group> </Sidebar.Group>