private bool ContainsCircularRefRec(SheetCell cell, HashSet <SheetCell> curChain) { if (curChain.Contains(cell)) { return(true); } if (Refs.ContainsKey(cell) && Refs[cell].Count > 0) { curChain.Add(cell); foreach (var cellRef in Refs[cell]) { // create a duplicate HashSet for every new possible walk on the graph of cells. // this is so that cells that reference multiple values that don't contain // crefs wont cause false positives if (ContainsCircularRefRec(cellRef, new HashSet <SheetCell>(curChain))) { return(true); } } } return(false); }
private void UpdateRefs(SheetCell cell) { if (!Refs.ContainsKey(cell)) { return; } // recompute the values and update Vars dict foreach (var refCell in Refs[cell]) { if (refCell.InvalidExp) { continue; } var res = refCell.ETree.Eval(); Vars[refCell.Name] = res; refCell.SetValue(res.ToString()); CellPropertyChanged(refCell, new PropertyChangedEventArgs("Value")); // recursively update ref ref calls etc UpdateRefs(refCell); } }
// called whenever a cell changes so that we can dispatch another event as well as // check to see if we need to look up the value in another cell private void CellChanged(object sender, PropertyChangedEventArgs e) { var cell = sender as SheetCell; if (e.PropertyName == "Color") { CellPropertyChanged(cell, new PropertyChangedEventArgs("Color")); return; } // reset expression validity when it changes // if its still invalid it will get set back // to true cell.InvalidExp = false; // handle raw numbers double val; if (double.TryParse(cell.Text, out val)) { foreach (var old in cell.Deps) { Refs[Name2Cell(old)].Remove(cell); } cell.Deps.Clear(); Vars[cell.Name] = val; cell.SetValue(val.ToString()); } else if (cell.Text != null && cell.Text.Length > 0 && cell.Text[0] == '=') { cell.ETree = new ExpTree(cell.Text.Substring(1), Vars); double res = cell.ETree.Eval(); var newDeps = cell.ETree.GetExpLocals(); // cell expression variables contain reference to the cell itself if (newDeps.FirstOrDefault(dep => dep == cell.Name) != null) { cell.SetValue("!(self reference)"); cell.InvalidExp = true; } // one of the cell expression variables is invalid else if (newDeps.FirstOrDefault(dep => Name2Cell(dep) == null) != null) { cell.SetValue("!(bad reference)"); cell.InvalidExp = true; } // chain of cell exp vars contains a circular reference else if (newDeps.ToList().FirstOrDefault(p => ContainsCircularRef(cell, Name2Cell(p))) != null) { cell.SetValue("!(circ reference)"); cell.InvalidExp = true; } else { Vars[cell.Name] = res; // remove dependencies that arent needed anymore foreach (var old in cell.Deps.Except(newDeps)) { Refs[Name2Cell(old)].Remove(cell); } cell.Deps = newDeps; // add current cell to each set corresponding to one of the current cell's // dependencies foreach (var dep in cell.Deps) { var rcell = Name2Cell(dep); if (!Refs.ContainsKey(rcell)) { Refs.Add(rcell, new HashSet <SheetCell>()); } Refs[rcell].Add(cell); } cell.SetValue(res.ToString()); } } else { // remove previous dependencies when directly setting text foreach (var old in cell.Deps) { Refs[Name2Cell(old)].Remove(cell); } // changing cells that have dependencies from numerical to be strings // defaults the cells value to 0 so that all dependencies get updated with // the 0 value Vars[cell.Name] = 0; cell.SetValue(cell.Text); } // now update all dependencies UpdateRefs(cell); CellPropertyChanged((sender as SheetCell), new PropertyChangedEventArgs("Value")); }