Skip to content

Commit

Permalink
Handle named ranges when inlining formula
Browse files Browse the repository at this point in the history
  • Loading branch information
dhoepelman committed Feb 12, 2015
1 parent c33e294 commit c1036a1
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 43 deletions.
39 changes: 22 additions & 17 deletions BumbleBee/BBAddIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ public void inlineFormula()
Range toInline = Application.Selection;
var toInlineFormula = toInline.HasFormula ? toInline.Formula.Substring(1) : toInline.Formula;
var toInlineAST = FSharpFormulaHelper.createFSharpTree(toInlineFormula);
var toInlineAddress = FSharpFormulaHelper.createFSharpTree(toInline.Address[false, false, XlReferenceStyle.xlA1]);
var toInlineAddress = FSharpFormulaHelper.createFSharpTree(toInline.Address[false, false]);

var dependencies = RefactoringHelper.getAllDirectDependents(toInline);

Expand All @@ -613,6 +613,12 @@ public void inlineFormula()
return;
}

if (toInlineAST == null)
{
MessageBox.Show("Couldn't parse to-inline cell");
return;
}

var errors = new Dictionary<Range, string>();
foreach (Range dependent in dependencies)
{
Expand All @@ -631,33 +637,32 @@ public void inlineFormula()
continue;
}
// Check if the dependent has the cell in a named range
// TODO: Do a proper check for named ranges
// HACK: I haven't fully figured out how to best traverse the named ranges, this propably needs additional AST.
// As such this check if the cell contains the formula adress and thus fails
// if a cell contains a reference to the cell both by adress and in a named range
// This check however would always be wise to have
// Check if the AST contains the address to inline
if (!dependentAST.Contains(toInlineAddress))
string range;
if (RefactoringHelper.isInNamedRanges(toInline, dependentAST.NamedRanges, out range))
{
errors.Add(dependent, "Could not find reference to cell in this dependent cell (named range?)");
errors.Add(dependent, String.Format("Cannot handle named ranges, refers to cell in named range '{0}'.", range));
continue;
}

var newFormula = dependentAST.ReplaceSubTree(toInlineAddress, toInlineAST);
dependent.Formula = "=" + FSharpFormulaHelper.Print(newFormula);
}

string message = String.Format("Inlined formula '{0}' into cells:\r\n{1}",
toInlineFormula,
String.Join("\r\n",
dependencies
.Where(d => !errors.ContainsKey(d))
.Select(d => d.Address[false, false, XlReferenceStyle.xlA1, true]))
);
string message = "";
if (!dependencies.All(d => errors.ContainsKey(d)))
{
message += String.Format("Inlined formula '{0}' into cells:\r\n{1}",
toInlineFormula,
String.Join("\r\n",
dependencies
.Where(d => !errors.ContainsKey(d))
.Select(d => d.Address[false, false, XlReferenceStyle.xlA1, true]))
);
}
if (errors.Count > 0)
{
message += String.Format("\r\n\r\nCouldn't inline into:\r\n{0}",
String.Join("\r\n", from d in errors select d.Value + ": " + d.Value)
String.Join("\r\n", from d in errors select d.Key.Address[false, false, XlReferenceStyle.xlA1, true] + ": " + d.Value)
);
}
else
Expand Down
28 changes: 28 additions & 0 deletions BumbleBee/RefactoringHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,34 @@ public static ICollection<Range> getAllDirectDependents(Range cell)

return dependents;
}

/// <summary>
/// Check if a cell is in the supplied named ranges
/// </summary>
public static bool isInNamedRanges(Range cell, IEnumerable<String> ranges, out string which)
{
foreach (string range in ranges)
{
var excelRange = cell.Worksheet.Range[range];
var intersect = cell.Application.Intersect(cell, excelRange);
if (intersect != null && intersect.Count > 0)
{
which = range;
return true;
}
}
which = "";
return false;
}

/// <summary>
/// Check if a cell is in the supplied named ranges
/// </summary>
public static bool isInNamedRanges(Range cell, IEnumerable<String> ranges)
{
string v;
return isInNamedRanges(cell, ranges, out v);
}

}
}
67 changes: 42 additions & 25 deletions FSharpEngine/Transformation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ type Formula =
| S of SuperCell
/// Concrete or dynamic cell range
| Range of SuperCell * SuperCell
/// Named range
| NamedRange of string
/// Concrete function call with argument list
| Function of string * list<Formula>
/// Dynamic Range
Expand Down Expand Up @@ -82,6 +84,7 @@ let makeDCell i j = D(i,j)
let makeDRange r = DRange r
let makeDArgument c = DArgument c
let makeRange (x,y) = Range (x,y)
let makeNamedRange name = NamedRange (name)
let makeFormula (s:string) (x:list<Formula>) = Function (s,x)

let rec IsDynamic (f:Formula) =
Expand All @@ -94,6 +97,7 @@ let rec IsDynamic (f:Formula) =
| DArgument c -> true
// This seems wrong, wouldn't a formula be dynamic if any of the arguments were dynamic? Instead of all arguments?
| Function (_, arguments) | ArgumentList(arguments) -> List.forall(IsDynamic) arguments
| NamedRange (_) -> false

let HasMap f:bool =
match f with
Expand Down Expand Up @@ -256,32 +260,27 @@ let rec ApplyOn to' from source:Formula =
type Formula with
member this.ApplyOn to' from = this |> ApplyOn to' from

let rec Contains (search:Formula) (subject:Formula) : bool =
if search = subject then
true
else
match subject with
| Function (_, arguments) | ArgumentList (arguments) -> Seq.exists (Contains search) arguments
| _ -> false
let rec Contains (search:Formula) = function
| subject when search = subject -> true
| Function (_, arguments) | ArgumentList (arguments) -> Seq.exists (Contains search) arguments
| _ -> false

type Formula with
/// Check if the AST contains a certain subtree
member this.Contains search = this |> Contains search

// You'd think this would be better done by defining `map f ast`
// but how do you decide whether to go deeper into the tree at a Function or apply f to the function?
let rec ReplaceSubTree search replace subject : Formula =
// Found it, so replace
if subject = search then
replace
else
let doArgs arguments = arguments |> List.map (ReplaceSubTree search replace)
match subject with
// Look deeper into the AST
| Function (s, arguments) -> Function(s, doArgs arguments)
| ArgumentList (arguments) -> ArgumentList(doArgs arguments)
// No match, do nothing
| _ -> subject
let rec ReplaceSubTree search replace =
let doArgs arguments = arguments |> List.map (ReplaceSubTree search replace)
function
// Found it, so replace
| subject when subject = search -> replace
// Look deeper into the AST
| Function (s, arguments) -> Function(s, doArgs arguments)
| ArgumentList (arguments) -> ArgumentList(doArgs arguments)
// No match, do nothing
| subject -> subject

type Formula with
/// Replace every occurence of an expression in an AST with another expression
Expand All @@ -296,12 +295,30 @@ let IsCellInRange cell range =
| _ -> invalidArg "cell" "cell must be a concrete cell")
| _ -> invalidArg "range" "range must be a concrete range"

let rec RangesInFormula = function
| Range (_,_) as r -> [r]
| Function (_, arguments) | ArgumentList(arguments) -> arguments |> List.collect RangesInFormula
| _ -> []
let rec Leaves = function
| Function (_, arguments) | ArgumentList (arguments) -> arguments |> List.collect Leaves
| l -> [l]

type Formula with
member this.Ranges = this |> RangesInFormula
member this.Leaves = this |> Leaves

(*
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Formula =
let private Leaves = lazy(this |> Leaves)
*)

let Ranges (formula:Formula) =
formula.Leaves |>
List.filter (function | Range(_,_) -> true | _ -> false)
let NamedRanges (formula:Formula) =
formula.Leaves
|> List.map (function | NamedRange(r) -> r | _ -> "")
|> List.filter (fun x -> x <> "")

type Formula with
member this.Ranges = this |> Ranges
member this.NamedRanges = this |> NamedRanges

let ContainsCellInRanges cell formula = formula |> RangesInFormula |> List.exists (IsCellInRange cell)
/// Test if a cell is contained in any concrete range
let ContainsCellInRanges cell (formula:Formula) = formula.Ranges |> List.exists (IsCellInRange cell)
6 changes: 5 additions & 1 deletion FormulaTransformation/FSharpFormulaHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ public static FSharpTransform.Formula CreateFSharpTree(this ParseTreeNode input)
FSharpList<FSharpTransform.Formula> Farguments = ListModule.OfSeq(arguments);
return FSharpTransform.makeFormula(FunctionName, Farguments);
}
else if (termName == GrammarNames.NamedRange)
{
return FSharpTransform.makeNamedRange(input.ChildNodes.First().Token.Text);
}
else if (termName == GrammarNames.Range)
{
if (input.ChildNodes.First().Term.ToString() == GrammarNames.DynamicRange)
Expand Down Expand Up @@ -155,7 +159,7 @@ public static FSharpTransform.Formula CreateFSharpTree(this ParseTreeNode input)
return FSharpTransform.makeDArgument(y);
}

return null;
throw new ArgumentException("Can't convert this node type", "input");
}

private static FSharpTransform.SuperCell GetDynamicCell(ParseTreeNode input)
Expand Down

0 comments on commit c1036a1

Please sign in to comment.