Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 1 | // Copyright 2011 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 5 | package parse |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 6 | |
| 7 | import ( |
Rob Pike | 64228e3 | 2011-07-06 17:46:36 +1000 | [diff] [blame] | 8 | "flag" |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 9 | "fmt" |
Rob Pike | 7b7a7a5 | 2012-09-14 15:25:37 -0700 | [diff] [blame] | 10 | "strings" |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 11 | "testing" |
| 12 | ) |
| 13 | |
Rob Pike | 7b7a7a5 | 2012-09-14 15:25:37 -0700 | [diff] [blame] | 14 | var debug = flag.Bool("debug", false, "show the errors produced by the main tests") |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 15 | |
| 16 | type numberTest struct { |
| 17 | text string |
| 18 | isInt bool |
| 19 | isUint bool |
| 20 | isFloat bool |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 21 | isComplex bool |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 22 | int64 |
| 23 | uint64 |
| 24 | float64 |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 25 | complex128 |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 26 | } |
| 27 | |
| 28 | var numberTests = []numberTest{ |
| 29 | // basics |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 30 | {"0", true, true, true, false, 0, 0, 0, 0}, |
| 31 | {"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint. |
| 32 | {"73", true, true, true, false, 73, 73, 73, 0}, |
Rob Pike | bf9531f | 2011-07-11 11:46:22 +1000 | [diff] [blame] | 33 | {"073", true, true, true, false, 073, 073, 073, 0}, |
| 34 | {"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0}, |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 35 | {"-73", true, false, true, false, -73, 0, -73, 0}, |
| 36 | {"+73", true, false, true, false, 73, 0, 73, 0}, |
| 37 | {"100", true, true, true, false, 100, 100, 100, 0}, |
| 38 | {"1e9", true, true, true, false, 1e9, 1e9, 1e9, 0}, |
| 39 | {"-1e9", true, false, true, false, -1e9, 0, -1e9, 0}, |
| 40 | {"-1.2", false, false, true, false, 0, 0, -1.2, 0}, |
| 41 | {"1e19", false, true, true, false, 0, 1e19, 1e19, 0}, |
| 42 | {"-1e19", false, false, true, false, 0, 0, -1e19, 0}, |
| 43 | {"4i", false, false, false, true, 0, 0, 0, 4i}, |
| 44 | {"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i}, |
Rob Pike | bf9531f | 2011-07-11 11:46:22 +1000 | [diff] [blame] | 45 | {"073i", false, false, false, true, 0, 0, 0, 73i}, // not octal! |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 46 | // complex with 0 imaginary are float (and maybe integer) |
| 47 | {"0i", true, true, true, true, 0, 0, 0, 0}, |
| 48 | {"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2}, |
| 49 | {"-12+0i", true, false, true, true, -12, 0, -12, -12}, |
| 50 | {"13+0i", true, true, true, true, 13, 13, 13, 13}, |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 51 | // funny bases |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 52 | {"0123", true, true, true, false, 0123, 0123, 0123, 0}, |
| 53 | {"-0x0", true, true, true, false, 0, 0, 0, 0}, |
| 54 | {"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0}, |
Rob Pike | bf9531f | 2011-07-11 11:46:22 +1000 | [diff] [blame] | 55 | // character constants |
| 56 | {`'a'`, true, true, true, false, 'a', 'a', 'a', 0}, |
| 57 | {`'\n'`, true, true, true, false, '\n', '\n', '\n', 0}, |
| 58 | {`'\\'`, true, true, true, false, '\\', '\\', '\\', 0}, |
| 59 | {`'\''`, true, true, true, false, '\'', '\'', '\'', 0}, |
| 60 | {`'\xFF'`, true, true, true, false, 0xFF, 0xFF, 0xFF, 0}, |
| 61 | {`'パ'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, |
| 62 | {`'\u30d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, |
| 63 | {`'\U000030d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 64 | // some broken syntax |
| 65 | {text: "+-2"}, |
| 66 | {text: "0x123."}, |
| 67 | {text: "1e."}, |
| 68 | {text: "0xi."}, |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 69 | {text: "1+2."}, |
Rob Pike | bf9531f | 2011-07-11 11:46:22 +1000 | [diff] [blame] | 70 | {text: "'x"}, |
| 71 | {text: "'xx'"}, |
Rob Pike | 409420c | 2015-05-01 15:33:08 -0700 | [diff] [blame] | 72 | {text: "'433937734937734969526500969526500'"}, // Integer too large - issue 10634. |
Rob Pike | 55fa7659 | 2014-09-03 15:57:03 -0700 | [diff] [blame] | 73 | // Issue 8622 - 0xe parsed as floating point. Very embarrassing. |
| 74 | {"0xef", true, true, true, false, 0xef, 0xef, 0xef, 0}, |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 75 | } |
| 76 | |
| 77 | func TestNumberParse(t *testing.T) { |
| 78 | for _, test := range numberTests { |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 79 | // If fmt.Sscan thinks it's complex, it's complex. We can't trust the output |
| 80 | // because imaginary comes out as a number. |
| 81 | var c complex128 |
Rob Pike | bf9531f | 2011-07-11 11:46:22 +1000 | [diff] [blame] | 82 | typ := itemNumber |
Rob Pike | 1ad1c0b | 2014-08-29 09:54:00 -0700 | [diff] [blame] | 83 | var tree *Tree |
Rob Pike | bf9531f | 2011-07-11 11:46:22 +1000 | [diff] [blame] | 84 | if test.text[0] == '\'' { |
Rob Pike | fc1f0bd | 2011-07-14 13:15:55 +1000 | [diff] [blame] | 85 | typ = itemCharConstant |
Rob Pike | bf9531f | 2011-07-11 11:46:22 +1000 | [diff] [blame] | 86 | } else { |
| 87 | _, err := fmt.Sscan(test.text, &c) |
| 88 | if err == nil { |
| 89 | typ = itemComplex |
| 90 | } |
| 91 | } |
Rob Pike | 1ad1c0b | 2014-08-29 09:54:00 -0700 | [diff] [blame] | 92 | n, err := tree.newNumber(0, test.text, typ) |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 93 | ok := test.isInt || test.isUint || test.isFloat || test.isComplex |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 94 | if ok && err != nil { |
Rob Pike | bf9531f | 2011-07-11 11:46:22 +1000 | [diff] [blame] | 95 | t.Errorf("unexpected error for %q: %s", test.text, err) |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 96 | continue |
| 97 | } |
| 98 | if !ok && err == nil { |
| 99 | t.Errorf("expected error for %q", test.text) |
| 100 | continue |
| 101 | } |
| 102 | if !ok { |
Rob Pike | bf9531f | 2011-07-11 11:46:22 +1000 | [diff] [blame] | 103 | if *debug { |
| 104 | fmt.Printf("%s\n\t%s\n", test.text, err) |
| 105 | } |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 106 | continue |
| 107 | } |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 108 | if n.IsComplex != test.isComplex { |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 109 | t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex) |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 110 | } |
| 111 | if test.isInt { |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 112 | if !n.IsInt { |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 113 | t.Errorf("expected integer for %q", test.text) |
| 114 | } |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 115 | if n.Int64 != test.int64 { |
| 116 | t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64) |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 117 | } |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 118 | } else if n.IsInt { |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 119 | t.Errorf("did not expect integer for %q", test.text) |
| 120 | } |
| 121 | if test.isUint { |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 122 | if !n.IsUint { |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 123 | t.Errorf("expected unsigned integer for %q", test.text) |
| 124 | } |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 125 | if n.Uint64 != test.uint64 { |
| 126 | t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64) |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 127 | } |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 128 | } else if n.IsUint { |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 129 | t.Errorf("did not expect unsigned integer for %q", test.text) |
| 130 | } |
| 131 | if test.isFloat { |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 132 | if !n.IsFloat { |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 133 | t.Errorf("expected float for %q", test.text) |
| 134 | } |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 135 | if n.Float64 != test.float64 { |
| 136 | t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64) |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 137 | } |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 138 | } else if n.IsFloat { |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 139 | t.Errorf("did not expect float for %q", test.text) |
| 140 | } |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 141 | if test.isComplex { |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 142 | if !n.IsComplex { |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 143 | t.Errorf("expected complex for %q", test.text) |
| 144 | } |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 145 | if n.Complex128 != test.complex128 { |
| 146 | t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128) |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 147 | } |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 148 | } else if n.IsComplex { |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 149 | t.Errorf("did not expect complex for %q", test.text) |
| 150 | } |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 151 | } |
| 152 | } |
| 153 | |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 154 | type parseTest struct { |
| 155 | name string |
| 156 | input string |
| 157 | ok bool |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 158 | result string // what the user would see in an error message. |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 159 | } |
| 160 | |
| 161 | const ( |
| 162 | noError = true |
| 163 | hasError = false |
| 164 | ) |
| 165 | |
| 166 | var parseTests = []parseTest{ |
| 167 | {"empty", "", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 168 | ``}, |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 169 | {"comment", "{{/*\n\n\n*/}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 170 | ``}, |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 171 | {"spaces", " \t\n", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 172 | `" \t\n"`}, |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 173 | {"text", "some text", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 174 | `"some text"`}, |
Rob Pike | 8d538c6 | 2011-07-07 10:56:33 +1000 | [diff] [blame] | 175 | {"emptyAction", "{{}}", hasError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 176 | `{{}}`}, |
Rob Pike | 81592c2 | 2011-06-28 23:04:08 +1000 | [diff] [blame] | 177 | {"field", "{{.X}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 178 | `{{.X}}`}, |
Rob Pike | 5b16582 | 2011-07-05 16:02:34 +1000 | [diff] [blame] | 179 | {"simple command", "{{printf}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 180 | `{{printf}}`}, |
Rob Pike | e7030e7 | 2011-07-11 10:01:15 +1000 | [diff] [blame] | 181 | {"$ invocation", "{{$}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 182 | "{{$}}"}, |
Rob Pike | e7030e7 | 2011-07-11 10:01:15 +1000 | [diff] [blame] | 183 | {"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 184 | "{{with $x := 3}}{{$x 23}}{{end}}"}, |
Rob Pike | 7b79b3b | 2011-07-11 15:23:38 +1000 | [diff] [blame] | 185 | {"variable with fields", "{{$.I}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 186 | "{{$.I}}"}, |
Rob Pike | 5b16582 | 2011-07-05 16:02:34 +1000 | [diff] [blame] | 187 | {"multi-word command", "{{printf `%d` 23}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 188 | "{{printf `%d` 23}}"}, |
Rob Pike | 5b16582 | 2011-07-05 16:02:34 +1000 | [diff] [blame] | 189 | {"pipeline", "{{.X|.Y}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 190 | `{{.X | .Y}}`}, |
Rob Pike | c705701 | 2011-07-17 13:31:59 +1000 | [diff] [blame] | 191 | {"pipeline with decl", "{{$x := .X|.Y}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 192 | `{{$x := .X | .Y}}`}, |
Rob Pike | cc842c7 | 2012-08-24 12:37:23 -0700 | [diff] [blame] | 193 | {"nested pipeline", "{{.X (.Y .Z) (.A | .B .C) (.E)}}", noError, |
| 194 | `{{.X (.Y .Z) (.A | .B .C) (.E)}}`}, |
Rob Pike | 9050550 | 2012-09-24 13:23:15 +1000 | [diff] [blame] | 195 | {"field applied to parentheses", "{{(.Y .Z).Field}}", noError, |
| 196 | `{{(.Y .Z).Field}}`}, |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 197 | {"simple if", "{{if .X}}hello{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 198 | `{{if .X}}"hello"{{end}}`}, |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 199 | {"if with else", "{{if .X}}true{{else}}false{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 200 | `{{if .X}}"true"{{else}}"false"{{end}}`}, |
Rob Pike | 37cee77 | 2013-08-28 14:43:56 +1000 | [diff] [blame] | 201 | {"if with else if", "{{if .X}}true{{else if .Y}}false{{end}}", noError, |
| 202 | `{{if .X}}"true"{{else}}{{if .Y}}"false"{{end}}{{end}}`}, |
| 203 | {"if else chain", "+{{if .X}}X{{else if .Y}}Y{{else if .Z}}Z{{end}}+", noError, |
| 204 | `"+"{{if .X}}"X"{{else}}{{if .Y}}"Y"{{else}}{{if .Z}}"Z"{{end}}{{end}}{{end}}"+"`}, |
Rob Pike | 81592c2 | 2011-06-28 23:04:08 +1000 | [diff] [blame] | 205 | {"simple range", "{{range .X}}hello{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 206 | `{{range .X}}"hello"{{end}}`}, |
Rob Pike | 81592c2 | 2011-06-28 23:04:08 +1000 | [diff] [blame] | 207 | {"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 208 | `{{range .X.Y.Z}}"hello"{{end}}`}, |
Rob Pike | 81592c2 | 2011-06-28 23:04:08 +1000 | [diff] [blame] | 209 | {"nested range", "{{range .X}}hello{{range .Y}}goodbye{{end}}{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 210 | `{{range .X}}"hello"{{range .Y}}"goodbye"{{end}}{{end}}`}, |
Rob Pike | 81592c2 | 2011-06-28 23:04:08 +1000 | [diff] [blame] | 211 | {"range with else", "{{range .X}}true{{else}}false{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 212 | `{{range .X}}"true"{{else}}"false"{{end}}`}, |
Rob Pike | 81592c2 | 2011-06-28 23:04:08 +1000 | [diff] [blame] | 213 | {"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 214 | `{{range .X | .M}}"true"{{else}}"false"{{end}}`}, |
Rob Pike | 81592c2 | 2011-06-28 23:04:08 +1000 | [diff] [blame] | 215 | {"range []int", "{{range .SI}}{{.}}{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 216 | `{{range .SI}}{{.}}{{end}}`}, |
Rob Pike | 8170d81 | 2012-03-14 07:03:11 +1100 | [diff] [blame] | 217 | {"range 1 var", "{{range $x := .SI}}{{.}}{{end}}", noError, |
| 218 | `{{range $x := .SI}}{{.}}{{end}}`}, |
| 219 | {"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError, |
| 220 | `{{range $x, $y := .SI}}{{.}}{{end}}`}, |
Rob Pike | 18c378c | 2012-08-08 20:02:19 -0700 | [diff] [blame] | 221 | {"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError, |
| 222 | `{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`}, |
Rob Pike | 3987b91 | 2011-07-10 07:32:01 +1000 | [diff] [blame] | 223 | {"template", "{{template `x`}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 224 | `{{template "x"}}`}, |
Rob Pike | 7aa1a1a | 2011-07-13 15:58:31 +1000 | [diff] [blame] | 225 | {"template with arg", "{{template `x` .Y}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 226 | `{{template "x" .Y}}`}, |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 227 | {"with", "{{with .X}}hello{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 228 | `{{with .X}}"hello"{{end}}`}, |
Rob Pike | 13f8897 | 2011-07-04 15:15:47 +1000 | [diff] [blame] | 229 | {"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError, |
Rob Pike | c837e61 | 2012-01-19 13:51:37 -0800 | [diff] [blame] | 230 | `{{with .X}}"hello"{{else}}"goodbye"{{end}}`}, |
Rob Pike | e6ee26a | 2015-09-08 14:58:12 -0700 | [diff] [blame] | 231 | // Trimming spaces. |
| 232 | {"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`}, |
| 233 | {"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`}, |
| 234 | {"trim left and right", "x \r\n\t{{- 3 -}}\n\n\ty", noError, `"x"{{3}}"y"`}, |
| 235 | {"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`}, |
| 236 | {"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`}, |
| 237 | {"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`}, |
Andrew Gerrand | 12dfc3b | 2015-08-28 15:31:51 +1000 | [diff] [blame^] | 238 | {"block definition", `{{block "foo" .}}hello{{end}}`, noError, |
| 239 | `{{template "foo" .}}`}, |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 240 | // Errors. |
| 241 | {"unclosed action", "hello{{range", hasError, ""}, |
Rob Pike | e7030e7 | 2011-07-11 10:01:15 +1000 | [diff] [blame] | 242 | {"unmatched end", "{{end}}", hasError, ""}, |
Didier Spezia | 80cedf3 | 2015-05-01 18:20:31 +0000 | [diff] [blame] | 243 | {"unmatched else", "{{else}}", hasError, ""}, |
| 244 | {"unmatched else after if", "{{if .X}}hello{{end}}{{else}}", hasError, ""}, |
| 245 | {"multiple else", "{{if .X}}1{{else}}2{{else}}3{{end}}", hasError, ""}, |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 246 | {"missing end", "hello{{range .x}}", hasError, ""}, |
| 247 | {"missing end after else", "hello{{range .x}}{{else}}", hasError, ""}, |
Rob Pike | 5b16582 | 2011-07-05 16:02:34 +1000 | [diff] [blame] | 248 | {"undefined function", "hello{{undefined}}", hasError, ""}, |
Rob Pike | e7030e7 | 2011-07-11 10:01:15 +1000 | [diff] [blame] | 249 | {"undefined variable", "{{$x}}", hasError, ""}, |
| 250 | {"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""}, |
Rob Pike | 41efecf | 2011-07-13 13:21:18 +1000 | [diff] [blame] | 251 | {"variable undefined in template", "{{template $v}}", hasError, ""}, |
Rob Pike | 7b79b3b | 2011-07-11 15:23:38 +1000 | [diff] [blame] | 252 | {"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""}, |
Rob Pike | 7aa1a1a | 2011-07-13 15:58:31 +1000 | [diff] [blame] | 253 | {"template with field ref", "{{template .X}}", hasError, ""}, |
| 254 | {"template with var", "{{template $v}}", hasError, ""}, |
Rob Pike | fc1f0bd | 2011-07-14 13:15:55 +1000 | [diff] [blame] | 255 | {"invalid punctuation", "{{printf 3, 4}}", hasError, ""}, |
| 256 | {"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""}, |
| 257 | {"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""}, |
Rob Pike | 8b23066 | 2012-08-24 13:00:24 -0700 | [diff] [blame] | 258 | {"dot applied to parentheses", "{{printf (printf .).}}", hasError, ""}, |
Rob Pike | de13e8d | 2012-08-29 21:42:53 -0700 | [diff] [blame] | 259 | {"adjacent args", "{{printf 3`x`}}", hasError, ""}, |
| 260 | {"adjacent args with .", "{{printf `x`.}}", hasError, ""}, |
Rob Pike | 37cee77 | 2013-08-28 14:43:56 +1000 | [diff] [blame] | 261 | {"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""}, |
Rob Pike | 8170d81 | 2012-03-14 07:03:11 +1100 | [diff] [blame] | 262 | // Equals (and other chars) do not assignments make (yet). |
| 263 | {"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"}, |
| 264 | {"bug0b", "{{$x = 1}}{{$x}}", hasError, ""}, |
| 265 | {"bug0c", "{{$x ! 2}}{{$x}}", hasError, ""}, |
| 266 | {"bug0d", "{{$x % 3}}{{$x}}", hasError, ""}, |
| 267 | // Check the parse fails for := rather than comma. |
| 268 | {"bug0e", "{{range $x := $y := 3}}{{end}}", hasError, ""}, |
| 269 | // Another bug: variable read must ignore following punctuation. |
| 270 | {"bug1a", "{{$x:=.}}{{$x!2}}", hasError, ""}, // ! is just illegal here. |
| 271 | {"bug1b", "{{$x:=.}}{{$x+2}}", hasError, ""}, // $x+2 should not parse as ($x) (+2). |
| 272 | {"bug1c", "{{$x:=.}}{{$x +2}}", noError, "{{$x := .}}{{$x +2}}"}, // It's OK with a space. |
Didier Spezia | 76ace94 | 2015-05-02 11:03:35 +0000 | [diff] [blame] | 273 | // dot following a literal value |
| 274 | {"dot after integer", "{{1.E}}", hasError, ""}, |
| 275 | {"dot after float", "{{0.1.E}}", hasError, ""}, |
| 276 | {"dot after boolean", "{{true.E}}", hasError, ""}, |
| 277 | {"dot after char", "{{'a'.any}}", hasError, ""}, |
| 278 | {"dot after string", `{{"hello".guys}}`, hasError, ""}, |
| 279 | {"dot after dot", "{{..E}}", hasError, ""}, |
| 280 | {"dot after nil", "{{nil.E}}", hasError, ""}, |
Didier Spezia | 62fb472 | 2015-05-05 06:45:49 +0000 | [diff] [blame] | 281 | // Wrong pipeline |
| 282 | {"wrong pipeline dot", "{{12|.}}", hasError, ""}, |
| 283 | {"wrong pipeline number", "{{.|12|printf}}", hasError, ""}, |
Didier Spezia | f6853369 | 2015-05-21 21:35:49 +0000 | [diff] [blame] | 284 | {"wrong pipeline string", "{{.|printf|\"error\"}}", hasError, ""}, |
| 285 | {"wrong pipeline char", "{{12|printf|'e'}}", hasError, ""}, |
Didier Spezia | 62fb472 | 2015-05-05 06:45:49 +0000 | [diff] [blame] | 286 | {"wrong pipeline boolean", "{{.|true}}", hasError, ""}, |
| 287 | {"wrong pipeline nil", "{{'c'|nil}}", hasError, ""}, |
| 288 | {"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""}, |
Andrew Gerrand | 12dfc3b | 2015-08-28 15:31:51 +1000 | [diff] [blame^] | 289 | // Missing pipeline in block |
| 290 | {"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""}, |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 291 | } |
| 292 | |
Rob Pike | 7506ee7 | 2011-08-09 16:49:36 +1000 | [diff] [blame] | 293 | var builtins = map[string]interface{}{ |
| 294 | "printf": fmt.Sprintf, |
Rob Pike | c66917d | 2011-08-09 15:42:53 +1000 | [diff] [blame] | 295 | } |
| 296 | |
Rob Pike | b027a0f | 2012-02-11 14:21:16 +1100 | [diff] [blame] | 297 | func testParse(doCopy bool, t *testing.T) { |
Rob Pike | df4de94 | 2013-07-31 15:09:13 +1000 | [diff] [blame] | 298 | textFormat = "%q" |
| 299 | defer func() { textFormat = "%s" }() |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 300 | for _, test := range parseTests { |
Rob Pike | f56db6f | 2011-11-23 20:17:22 -0800 | [diff] [blame] | 301 | tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins) |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 302 | switch { |
| 303 | case err == nil && !test.ok: |
| 304 | t.Errorf("%q: expected error; got none", test.name) |
| 305 | continue |
| 306 | case err != nil && test.ok: |
| 307 | t.Errorf("%q: unexpected error: %v", test.name, err) |
| 308 | continue |
| 309 | case err != nil && !test.ok: |
| 310 | // expected error, got one |
Rob Pike | 64228e3 | 2011-07-06 17:46:36 +1000 | [diff] [blame] | 311 | if *debug { |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 312 | fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) |
| 313 | } |
| 314 | continue |
| 315 | } |
Rob Pike | b027a0f | 2012-02-11 14:21:16 +1100 | [diff] [blame] | 316 | var result string |
| 317 | if doCopy { |
| 318 | result = tmpl.Root.Copy().String() |
| 319 | } else { |
| 320 | result = tmpl.Root.String() |
| 321 | } |
Rob Pike | cd7826e | 2011-06-23 09:27:28 +1000 | [diff] [blame] | 322 | if result != test.result { |
| 323 | t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result) |
| 324 | } |
| 325 | } |
| 326 | } |
Rob Pike | e6b3371 | 2011-12-01 17:24:54 -0800 | [diff] [blame] | 327 | |
Rob Pike | b027a0f | 2012-02-11 14:21:16 +1100 | [diff] [blame] | 328 | func TestParse(t *testing.T) { |
| 329 | testParse(false, t) |
| 330 | } |
| 331 | |
| 332 | // Same as TestParse, but we copy the node first |
| 333 | func TestParseCopy(t *testing.T) { |
| 334 | testParse(true, t) |
| 335 | } |
| 336 | |
Rob Pike | e6b3371 | 2011-12-01 17:24:54 -0800 | [diff] [blame] | 337 | type isEmptyTest struct { |
| 338 | name string |
| 339 | input string |
| 340 | empty bool |
| 341 | } |
| 342 | |
| 343 | var isEmptyTests = []isEmptyTest{ |
| 344 | {"empty", ``, true}, |
| 345 | {"nonempty", `hello`, false}, |
| 346 | {"spaces only", " \t\n \t\n", true}, |
| 347 | {"definition", `{{define "x"}}something{{end}}`, true}, |
| 348 | {"definitions and space", "{{define `x`}}something{{end}}\n\n{{define `y`}}something{{end}}\n\n", true}, |
Rob Pike | caa4621 | 2013-09-12 13:22:56 +1000 | [diff] [blame] | 349 | {"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n", false}, |
Rob Pike | e6b3371 | 2011-12-01 17:24:54 -0800 | [diff] [blame] | 350 | {"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false}, |
| 351 | } |
| 352 | |
| 353 | func TestIsEmpty(t *testing.T) { |
Rob Pike | 180541b | 2012-02-28 14:23:57 +1100 | [diff] [blame] | 354 | if !IsEmptyTree(nil) { |
| 355 | t.Errorf("nil tree is not empty") |
| 356 | } |
Rob Pike | e6b3371 | 2011-12-01 17:24:54 -0800 | [diff] [blame] | 357 | for _, test := range isEmptyTests { |
| 358 | tree, err := New("root").Parse(test.input, "", "", make(map[string]*Tree), nil) |
| 359 | if err != nil { |
| 360 | t.Errorf("%q: unexpected error: %v", test.name, err) |
| 361 | continue |
| 362 | } |
| 363 | if empty := IsEmptyTree(tree.Root); empty != test.empty { |
| 364 | t.Errorf("%q: expected %t got %t", test.name, test.empty, empty) |
| 365 | } |
| 366 | } |
| 367 | } |
Rob Pike | 7b7a7a5 | 2012-09-14 15:25:37 -0700 | [diff] [blame] | 368 | |
Josh Bleecher Snyder | eeb7585 | 2013-09-17 14:19:44 +1000 | [diff] [blame] | 369 | func TestErrorContextWithTreeCopy(t *testing.T) { |
| 370 | tree, err := New("root").Parse("{{if true}}{{end}}", "", "", make(map[string]*Tree), nil) |
| 371 | if err != nil { |
| 372 | t.Fatalf("unexpected tree parse failure: %v", err) |
| 373 | } |
| 374 | treeCopy := tree.Copy() |
| 375 | wantLocation, wantContext := tree.ErrorContext(tree.Root.Nodes[0]) |
| 376 | gotLocation, gotContext := treeCopy.ErrorContext(treeCopy.Root.Nodes[0]) |
| 377 | if wantLocation != gotLocation { |
| 378 | t.Errorf("wrong error location want %q got %q", wantLocation, gotLocation) |
| 379 | } |
| 380 | if wantContext != gotContext { |
| 381 | t.Errorf("wrong error location want %q got %q", wantContext, gotContext) |
| 382 | } |
| 383 | } |
| 384 | |
Rob Pike | 7b7a7a5 | 2012-09-14 15:25:37 -0700 | [diff] [blame] | 385 | // All failures, and the result is a string that must appear in the error message. |
| 386 | var errorTests = []parseTest{ |
| 387 | // Check line numbers are accurate. |
| 388 | {"unclosed1", |
| 389 | "line1\n{{", |
| 390 | hasError, `unclosed1:2: unexpected unclosed action in command`}, |
| 391 | {"unclosed2", |
| 392 | "line1\n{{define `x`}}line2\n{{", |
| 393 | hasError, `unclosed2:3: unexpected unclosed action in command`}, |
| 394 | // Specific errors. |
| 395 | {"function", |
| 396 | "{{foo}}", |
| 397 | hasError, `function "foo" not defined`}, |
| 398 | {"comment", |
| 399 | "{{/*}}", |
| 400 | hasError, `unclosed comment`}, |
| 401 | {"lparen", |
| 402 | "{{.X (1 2 3}}", |
| 403 | hasError, `unclosed left paren`}, |
| 404 | {"rparen", |
| 405 | "{{.X 1 2 3)}}", |
| 406 | hasError, `unexpected ")"`}, |
| 407 | {"space", |
| 408 | "{{`x`3}}", |
Didier Spezia | 76ace94 | 2015-05-02 11:03:35 +0000 | [diff] [blame] | 409 | hasError, `in operand`}, |
Rob Pike | 7b7a7a5 | 2012-09-14 15:25:37 -0700 | [diff] [blame] | 410 | {"idchar", |
| 411 | "{{a#}}", |
| 412 | hasError, `'#'`}, |
| 413 | {"charconst", |
| 414 | "{{'a}}", |
| 415 | hasError, `unterminated character constant`}, |
| 416 | {"stringconst", |
| 417 | `{{"a}}`, |
| 418 | hasError, `unterminated quoted string`}, |
| 419 | {"rawstringconst", |
| 420 | "{{`a}}", |
| 421 | hasError, `unterminated raw quoted string`}, |
| 422 | {"number", |
| 423 | "{{0xi}}", |
| 424 | hasError, `number syntax`}, |
| 425 | {"multidefine", |
| 426 | "{{define `a`}}a{{end}}{{define `a`}}b{{end}}", |
| 427 | hasError, `multiple definition of template`}, |
| 428 | {"eof", |
| 429 | "{{range .X}}", |
| 430 | hasError, `unexpected EOF`}, |
| 431 | {"variable", |
Rob Pike | 9050550 | 2012-09-24 13:23:15 +1000 | [diff] [blame] | 432 | // Declare $x so it's defined, to avoid that error, and then check we don't parse a declaration. |
| 433 | "{{$x := 23}}{{with $x.y := 3}}{{$x 23}}{{end}}", |
| 434 | hasError, `unexpected ":="`}, |
Rob Pike | 7b7a7a5 | 2012-09-14 15:25:37 -0700 | [diff] [blame] | 435 | {"multidecl", |
| 436 | "{{$a,$b,$c := 23}}", |
| 437 | hasError, `too many declarations`}, |
| 438 | {"undefvar", |
| 439 | "{{$a}}", |
| 440 | hasError, `undefined variable`}, |
Didier Spezia | 76ace94 | 2015-05-02 11:03:35 +0000 | [diff] [blame] | 441 | {"wrongdot", |
| 442 | "{{true.any}}", |
| 443 | hasError, `unexpected . after term`}, |
Didier Spezia | 62fb472 | 2015-05-05 06:45:49 +0000 | [diff] [blame] | 444 | {"wrongpipeline", |
| 445 | "{{12|false}}", |
| 446 | hasError, `non executable command in pipeline`}, |
| 447 | {"emptypipeline", |
| 448 | `{{ ( ) }}`, |
| 449 | hasError, `missing value for parenthesized pipeline`}, |
Rob Pike | 7b7a7a5 | 2012-09-14 15:25:37 -0700 | [diff] [blame] | 450 | } |
| 451 | |
| 452 | func TestErrors(t *testing.T) { |
| 453 | for _, test := range errorTests { |
| 454 | _, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree)) |
| 455 | if err == nil { |
| 456 | t.Errorf("%q: expected error", test.name) |
| 457 | continue |
| 458 | } |
| 459 | if !strings.Contains(err.Error(), test.result) { |
| 460 | t.Errorf("%q: error %q does not contain %q", test.name, err, test.result) |
| 461 | } |
| 462 | } |
| 463 | } |
Andrew Gerrand | 12dfc3b | 2015-08-28 15:31:51 +1000 | [diff] [blame^] | 464 | |
| 465 | func TestBlock(t *testing.T) { |
| 466 | const ( |
| 467 | input = `a{{block "inner" .}}bar{{.}}baz{{end}}b` |
| 468 | outer = `a{{template "inner" .}}b` |
| 469 | inner = `bar{{.}}baz` |
| 470 | ) |
| 471 | treeSet := make(map[string]*Tree) |
| 472 | tmpl, err := New("outer").Parse(input, "", "", treeSet, nil) |
| 473 | if err != nil { |
| 474 | t.Fatal(err) |
| 475 | } |
| 476 | if g, w := tmpl.Root.String(), outer; g != w { |
| 477 | t.Errorf("outer template = %q, want %q", g, w) |
| 478 | } |
| 479 | inTmpl := treeSet["inner"] |
| 480 | if inTmpl == nil { |
| 481 | t.Fatal("block did not define template") |
| 482 | } |
| 483 | if g, w := inTmpl.Root.String(), inner; g != w { |
| 484 | t.Errorf("inner template = %q, want %q", g, w) |
| 485 | } |
| 486 | } |