/// <summary> /// Change a C# parse tree to a F# AST /// </summary> public static FSharpTransform.Formula CreateFSharpTree(this ParseTreeNode input) { if (input.IsParentheses()) { return(FSharpTransform.Formula.NewFunction("", ListModule.OfSeq(new [] { CreateFSharpTree(input.ChildNodes[0]) }))); } input = input.SkipToRelevant(); switch (input.Type()) { case GrammarNames.FunctionCall: case GrammarNames.ReferenceFunctionCall: case GrammarNames.UDFunctionCall: var fname = input.GetFunction() + (input.IsNamedFunction()?"(":""); var args = ListModule.OfSeq(input.GetFunctionArguments().Select(CreateFSharpTree)); // Check for range if (fname == ":") { return(makeFSharpRange(input)); } return(FSharpTransform.makeFormula(fname, args)); case GrammarNames.Reference: // ignore prefix return(CreateFSharpTree(input.ChildNodes.Count == 1 ? input.ChildNodes[0] : input.ChildNodes[1])); case GrammarNames.Cell: var L = new Location(input.Print()); return(FSharpTransform.makeSuperCell(FSharpTransform.makeCell(L.Column, L.Row))); case GrammarNames.NamedRange: return(FSharpTransform.makeNamedRange(input.Print())); case TransformationRuleGrammar.Names.DynamicCell: //get variables from dynamic cell return(FSharpTransform.makeSuperCell(GetDynamicCell(input))); case TransformationRuleGrammar.Names.DynamicRange: var letter = input // DynamicRange .ChildNodes[0] // LowLetter .Token.ValueString[0]; return(FSharpTransform.makeDRange(letter)); case GrammarNames.Constant: case GrammarNames.Number: case GrammarNames.Text: case GrammarNames.Bool: case GrammarNames.Error: case GrammarNames.RefError: return(FSharpTransform.makeConstant(input.Print())); case TransformationRuleGrammar.Names.DynamicConstant: return(FSharpTransform.makeDArgument(input.ChildNodes[0].Token.ValueString[1])); default: throw new ArgumentException($"Can't convert node type {input.Type()}", nameof(input)); } }
/// <summary> /// Go to the first "relevant" child node, i.e. skips wrapper nodes /// </summary> /// <param name="input">The input parse tree node</param> /// <param name="skipReferencesWithoutPrefix">If true, skip all reference nodes without a prefix instead of only parentheses</param> /// <remarks> /// Skips: /// * FormulaWithEq and ArrayFormula nodes /// * Formula nodes /// * Parentheses /// * Reference nodes which are just wrappers /// </remarks> public static ParseTreeNode SkipToRelevant(this ParseTreeNode input, bool skipReferencesWithoutPrefix = false) { while (true) { switch (input.Type()) { case GrammarNames.FormulaWithEq: case GrammarNames.ArrayFormula: input = input.ChildNodes[1]; break; case GrammarNames.Argument: case GrammarNames.Formula: if (input.ChildNodes.Count == 1) { input = input.ChildNodes[0]; } else { return(input); } break; case GrammarNames.Reference: // Skip references which are parentheses // Skip references without a prefix (=> they only have one child node) if the option is set if ((skipReferencesWithoutPrefix && input.ChildNodes.Count == 1) || input.IsParentheses()) { input = input.ChildNodes[0]; } else { return(input); } break; default: return(input); } } }
/// <summary> /// Print transformation rule grammar /// </summary> public static string Print(this ParseTreeNode input) { // For terminals, just print the token text if (input.Term is Terminal) { return(input.Token.Text); } // (Lazy) enumerable for printed childs var childs = input.ChildNodes.Select(Print); // Concrete list when needed List <string> childsL; string ret; // Switch on nonterminals switch (input.Term.Name) { case TransformationRuleGrammar.Names.VarExpression: case TransformationRuleGrammar.Names.DynamicCell: case TransformationRuleGrammar.Names.DynamicConstant: case TransformationRuleGrammar.Names.DynamicRange: return(string.Join("", input.ChildNodes)); case GrammarNames.Formula: // Check if these are brackets, otherwise print first child return(input.IsParentheses() ? $"({childs.First()})" : childs.First()); case GrammarNames.FunctionCall: case GrammarNames.ReferenceFunctionCall: case GrammarNames.UDFunctionCall: childsL = childs.ToList(); if (input.IsNamedFunction()) { return(string.Join("", childsL) + ")"); } if (input.IsBinaryOperation()) { // format string for "normal" binary operation string format = "{0} {1} {2}"; if (input.IsIntersection()) { format = "{0} {2}"; } else if (input.IsBinaryReferenceOperation()) { format = "{0}{1}{2}"; } return(string.Format(format, childsL[0], childsL[1], childsL[2])); } if (input.IsUnion()) { return($"({string.Join(",", childsL)})"); } if (input.IsUnaryOperation()) { return(string.Join("", childsL)); } throw new ArgumentException("Unknown function type."); case GrammarNames.Reference: if (input.IsParentheses()) { return($"({childs.First()})"); } return(string.Join("", childs)); case GrammarNames.Prefix: ret = string.Join("", childs); // The exclamation mark token is not included in the parse tree, so we have to add that if it's a single file if (input.ChildNodes.Count == 1 && input.ChildNodes[0].Is(GrammarNames.File)) { ret += "!"; } return(ret); case GrammarNames.ArrayFormula: return("{=" + childs.ElementAt(1) + "}"); case GrammarNames.StructureReference: ret = ""; var hastable = input.ChildNodes.Count == 2; var contentsNode = hastable ? 1 : 0; childsL = childs.ToList(); if (hastable) { ret += childsL[0]; } if (input.ChildNodes[contentsNode].Is(GrammarNames.StructureReferenceColumnOrKeyword)) { ret += childsL[contentsNode]; } else { ret += $"[{childsL[contentsNode]}]"; } return(ret); // Terms for which to print all child nodes concatenated case GrammarNames.ArrayConstant: case GrammarNames.DynamicDataExchange: case GrammarNames.FormulaWithEq: case GrammarNames.File: case GrammarNames.StructureReferenceExpression: return(string.Join("", childs)); // Terms for which we print the childs comma-separated case GrammarNames.Arguments: case GrammarNames.ArrayRows: case GrammarNames.Union: return(string.Join(",", childs)); case GrammarNames.ArrayColumns: return(string.Join(";", childs)); case GrammarNames.ConstantArray: return($"{{{childs.First()}}}"); default: // If it is not defined above and the number of childs is exactly one, we want to just print the first child if (input.ChildNodes.Count == 1) { return(childs.First()); } throw new ArgumentException($"Could not print node of type '{input.Term.Name}'.\nThis probably means the excel grammar was modified without the print function being modified"); } }