1package formula23import (4 "math"5 "strconv"6 "strings"7)89// Text prints a parsed formula back to text, with a leading "=".10// Rewritten formulas (after a paste or an inserted row) are stored this11// way, in Sheets' spelling: ranges as A1:B3, functions without @.12func Text(n Node) string {13 var b strings.Builder14 b.WriteByte('=')15 printNode(&b, n)16 return b.String()17}1819// Expr prints an expression without the leading "=", e.g. to quote part20// of a formula.21func Expr(n Node) string {22 var b strings.Builder23 printNode(&b, n)24 return b.String()25}2627// atomPower is the binding power of nodes that never need parentheses.28const atomPower = 1002930// power returns how tightly n binds, using the parser's binding powers, so31// the printer adds parentheses exactly where the parser needs them.32func power(n Node) int {33 switch n := n.(type) {34 case Binary:35 return infixPower[n.Op]36 case Unary:37 switch n.Op {38 case "%":39 return percentPower40 case "#NOT#":41 return notPower42 }43 return unaryPower44 }45 return atomPower46}4748func printNode(b *strings.Builder, n Node) {49 switch n := n.(type) {50 case Num:51 b.WriteString(numText(n.V))52 case Str:53 b.WriteString(`"` + strings.ReplaceAll(n.V, `"`, `""`) + `"`)54 case Bool:55 b.WriteString(strings.ToUpper(strconv.FormatBool(n.V)))56 case Ref:57 writeSheet(b, n.Sheet)58 b.WriteString(RefString(n.Addr, n.Abs))59 case Range:60 writeSheet(b, n.Sheet)61 b.WriteString(RangeString(n.Rect, n.Abs))62 case RefErr:63 b.WriteString(refErrorText)64 case Name:65 b.WriteString(n.Name)66 case TableRef:67 b.WriteString(n.String())68 case Unary:69 printUnary(b, n)70 case Binary:71 p := infixPower[n.Op]72 // Operators associate to the left, so a right operand of equal73 // power needs parentheses: 1-(2-3).74 printChild(b, n.L, power(n.L) < p)75 b.WriteString(n.Op)76 printChild(b, n.R, power(n.R) <= p)77 case Call:78 b.WriteString(n.Fn.Signature().Name)79 printArgs(b, n.Args)80 case Local:81 b.WriteString(n.Name)82 case Invoke:83 printNode(b, n.Fn)84 printArgs(b, n.Args)85 case Array:86 b.WriteByte('{')87 for i, row := range n.Rows {88 if i > 0 {89 b.WriteByte(';')90 }91 for j, e := range row {92 if j > 0 {93 b.WriteByte(',')94 }95 printNode(b, e)96 }97 }98 b.WriteByte('}')99 }100}101102// printArgs writes a call's arguments in parentheses.103func printArgs(b *strings.Builder, args []Node) {104 b.WriteByte('(')105 for i, a := range args {106 if i > 0 {107 b.WriteByte(',')108 }109 printNode(b, a)110 }111 b.WriteByte(')')112}113114func printUnary(b *strings.Builder, n Unary) {115 if n.Op == "%" {116 printChild(b, n.X, power(n.X) < percentPower)117 b.WriteByte('%')118 return119 }120 b.WriteString(n.Op)121 printChild(b, n.X, power(n.X) < power(n))122}123124// writeSheet writes a reference's sheet and its "!", quoted as needed.125func writeSheet(b *strings.Builder, sheet string) {126 if sheet != "" {127 b.WriteString(QuoteSheet(sheet) + "!")128 }129}130131func printChild(b *strings.Builder, n Node, paren bool) {132 if paren {133 b.WriteByte('(')134 }135 printNode(b, n)136 if paren {137 b.WriteByte(')')138 }139}140141// numText writes a number literal so it parses back to the same float:142// plainly in everyday ranges, in E notation for very large or small ones.143func numText(v float64) string {144 if a := math.Abs(v); a == 0 || (a >= 1e-6 && a < 1e21) {145 return strconv.FormatFloat(v, 'f', -1, 64)146 }147 return strconv.FormatFloat(v, 'E', -1, 64)148}