| 1 | package calc |
| 2 | |
| 3 | import "testing" |
| 4 | |
| 5 | func TestParseArithmeticPrecedence(t *testing.T) { |
| 6 | n, err := Parse("1+2*3") |
| 7 | if err != nil { |
| 8 | t.Fatal(err) |
| 9 | } |
| 10 | b, ok := n.(*Binary) |
| 11 | if !ok || b.Op != "+" { |
| 12 | t.Fatalf("root = %T %v, want Binary +", n, n) |
| 13 | } |
| 14 | r, ok := b.Right.(*Binary) |
| 15 | if !ok || r.Op != "*" { |
| 16 | t.Fatal("multiplication did not bind tighter than addition") |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | func TestParseAbsoluteAndRelativeRefs(t *testing.T) { |
| 21 | n, err := Parse("$A$1") |
| 22 | if err != nil { |
| 23 | t.Fatal(err) |
| 24 | } |
| 25 | r := n.(*Ref) |
| 26 | if !r.AbsCol || !r.AbsRow { |
| 27 | t.Fatal("$A$1 did not parse as fully absolute") |
| 28 | } |
| 29 | n2, _ := Parse("A1") |
| 30 | r2 := n2.(*Ref) |
| 31 | if r2.AbsCol || r2.AbsRow { |
| 32 | t.Fatal("A1 parsed as absolute") |
| 33 | } |
| 34 | if r2.ColPos != 0 || r2.RowPos != 0 { |
| 35 | t.Fatalf("A1 = col %d row %d, want 0,0", r2.ColPos, r2.RowPos) |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | func TestParseRangeAndCall(t *testing.T) { |
| 40 | n, err := Parse("SUM(A1:B10)") |
| 41 | if err != nil { |
| 42 | t.Fatal(err) |
| 43 | } |
| 44 | c, ok := n.(*Call) |
| 45 | if !ok || c.Name != "SUM" { |
| 46 | t.Fatalf("root = %T, want Call SUM", n) |
| 47 | } |
| 48 | if _, ok := c.Args[0].(*Range); !ok { |
| 49 | t.Fatalf("arg 0 = %T, want Range", c.Args[0]) |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | func TestParseRejectsGarbage(t *testing.T) { |
| 54 | for _, src := range []string{"1+", "(1", "SUM(", "A1:", "*"} { |
| 55 | if _, err := Parse(src); err == nil { |
| 56 | t.Errorf("Parse(%q) succeeded, want error", src) |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | func TestParsePowerIsRightAssociative(t *testing.T) { |
| 62 | // 2^3^2 must parse as 2^(3^2) = 2^9 = 512, not (2^3)^2 = 64. |
| 63 | n, err := Parse("2^3^2") |
| 64 | if err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | b, ok := n.(*Binary) |
| 68 | if !ok || b.Op != "^" { |
| 69 | t.Fatalf("root = %T %v, want Binary ^", n, n) |
| 70 | } |
| 71 | if _, ok := b.Left.(*Num); !ok { |
| 72 | t.Fatalf("left = %T, want Num (right-associative grouping)", b.Left) |
| 73 | } |
| 74 | r, ok := b.Right.(*Binary) |
| 75 | if !ok || r.Op != "^" { |
| 76 | t.Fatal("right operand should itself be a ^ binary (right-associative)") |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | func TestParseFunctionNameCaseInsensitive(t *testing.T) { |
| 81 | n, err := Parse("sum(A1)") |
| 82 | if err != nil { |
| 83 | t.Fatal(err) |
| 84 | } |
| 85 | c, ok := n.(*Call) |
| 86 | if !ok || c.Name != "SUM" { |
| 87 | t.Fatalf("root = %T %v, want Call SUM (normalised to upper case)", n, n) |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | func TestParseUnaryBindsTighterThanPower(t *testing.T) { |
| 92 | // -2^2 must parse as (-2)^2 = 4, not -(2^2) = -4: unary minus binds |
| 93 | // tighter than ^, matching Excel rather than most programming |
| 94 | // languages. |
| 95 | n, err := Parse("-2^2") |
| 96 | if err != nil { |
| 97 | t.Fatal(err) |
| 98 | } |
| 99 | b, ok := n.(*Binary) |
| 100 | if !ok || b.Op != "^" { |
| 101 | t.Fatalf("root = %T %v, want Binary ^", n, n) |
| 102 | } |
| 103 | u, ok := b.Left.(*Unary) |
| 104 | if !ok || u.Op != "-" { |
| 105 | t.Fatalf("left = %T, want Unary - (unary binds tighter than ^)", b.Left) |
| 106 | } |
| 107 | if _, ok := u.X.(*Num); !ok { |
| 108 | t.Fatalf("unary operand = %T, want Num", u.X) |
| 109 | } |
| 110 | if _, ok := b.Right.(*Num); !ok { |
| 111 | t.Fatalf("right = %T, want Num", b.Right) |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | func TestParseFunctionNameShapedLikeReference(t *testing.T) { |
| 116 | // LOG10 matches the reference shape (<=3 letters followed by digits) |
| 117 | // but is followed immediately by '(', so it must lex as a function |
| 118 | // call, not a reference. |
| 119 | n, err := Parse("LOG10(100)") |
| 120 | if err != nil { |
| 121 | t.Fatal(err) |
| 122 | } |
| 123 | c, ok := n.(*Call) |
| 124 | if !ok || c.Name != "LOG10" { |
| 125 | t.Fatalf("root = %T %v, want Call LOG10", n, n) |
| 126 | } |
| 127 | if len(c.Args) != 1 { |
| 128 | t.Fatalf("args = %d, want 1", len(c.Args)) |
| 129 | } |
| 130 | if _, ok := c.Args[0].(*Num); !ok { |
| 131 | t.Fatalf("arg 0 = %T, want Num", c.Args[0]) |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | func TestParseRefStillLexesWhenNotFollowedByCall(t *testing.T) { |
| 136 | // A1 is not followed by '(', so it must still lex as a reference even |
| 137 | // though the call-parenthesis lookahead exists for words like LOG10. |
| 138 | n, err := Parse("A1") |
| 139 | if err != nil { |
| 140 | t.Fatal(err) |
| 141 | } |
| 142 | if _, ok := n.(*Ref); !ok { |
| 143 | t.Fatalf("root = %T, want Ref", n) |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | func TestParseRejectsZeroRow(t *testing.T) { |
| 148 | // A1 notation is one-based; "A0" is malformed input, not row -1. |
| 149 | if _, err := Parse("A0"); err == nil { |
| 150 | t.Error(`Parse("A0") succeeded, want error`) |
| 151 | } |
| 152 | } |
| 153 | |