blob: b4512d3160045624fd8660277644d6744e76e128 [file] [log] [blame]
Rob Pikecd7826e2011-06-23 09:27:28 +10001// 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 Pikec66917d2011-08-09 15:42:53 +10005package parse
Rob Pikecd7826e2011-06-23 09:27:28 +10006
7import (
Rob Pike64228e32011-07-06 17:46:36 +10008 "flag"
Rob Pikecd7826e2011-06-23 09:27:28 +10009 "fmt"
Rob Pike7b7a7a52012-09-14 15:25:37 -070010 "strings"
Rob Pikecd7826e2011-06-23 09:27:28 +100011 "testing"
12)
13
Rob Pike7b7a7a52012-09-14 15:25:37 -070014var debug = flag.Bool("debug", false, "show the errors produced by the main tests")
Rob Pikecd7826e2011-06-23 09:27:28 +100015
16type numberTest struct {
17 text string
18 isInt bool
19 isUint bool
20 isFloat bool
Rob Pike13f88972011-07-04 15:15:47 +100021 isComplex bool
Rob Pikecd7826e2011-06-23 09:27:28 +100022 int64
23 uint64
24 float64
Rob Pike13f88972011-07-04 15:15:47 +100025 complex128
Rob Pikecd7826e2011-06-23 09:27:28 +100026}
27
28var numberTests = []numberTest{
29 // basics
Rob Pike13f88972011-07-04 15:15:47 +100030 {"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 Pikebf9531f2011-07-11 11:46:22 +100033 {"073", true, true, true, false, 073, 073, 073, 0},
34 {"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0},
Rob Pike13f88972011-07-04 15:15:47 +100035 {"-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 Pikebf9531f2011-07-11 11:46:22 +100045 {"073i", false, false, false, true, 0, 0, 0, 73i}, // not octal!
Rob Pike13f88972011-07-04 15:15:47 +100046 // 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 Pikecd7826e2011-06-23 09:27:28 +100051 // funny bases
Rob Pike13f88972011-07-04 15:15:47 +100052 {"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 Pikebf9531f2011-07-11 11:46:22 +100055 // 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 Pikecd7826e2011-06-23 09:27:28 +100064 // some broken syntax
65 {text: "+-2"},
66 {text: "0x123."},
67 {text: "1e."},
68 {text: "0xi."},
Rob Pike13f88972011-07-04 15:15:47 +100069 {text: "1+2."},
Rob Pikebf9531f2011-07-11 11:46:22 +100070 {text: "'x"},
71 {text: "'xx'"},
Rob Pike409420c2015-05-01 15:33:08 -070072 {text: "'433937734937734969526500969526500'"}, // Integer too large - issue 10634.
Rob Pike55fa76592014-09-03 15:57:03 -070073 // Issue 8622 - 0xe parsed as floating point. Very embarrassing.
74 {"0xef", true, true, true, false, 0xef, 0xef, 0xef, 0},
Rob Pikecd7826e2011-06-23 09:27:28 +100075}
76
77func TestNumberParse(t *testing.T) {
78 for _, test := range numberTests {
Rob Pike13f88972011-07-04 15:15:47 +100079 // 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 Pikebf9531f2011-07-11 11:46:22 +100082 typ := itemNumber
Rob Pike1ad1c0b2014-08-29 09:54:00 -070083 var tree *Tree
Rob Pikebf9531f2011-07-11 11:46:22 +100084 if test.text[0] == '\'' {
Rob Pikefc1f0bd2011-07-14 13:15:55 +100085 typ = itemCharConstant
Rob Pikebf9531f2011-07-11 11:46:22 +100086 } else {
87 _, err := fmt.Sscan(test.text, &c)
88 if err == nil {
89 typ = itemComplex
90 }
91 }
Rob Pike1ad1c0b2014-08-29 09:54:00 -070092 n, err := tree.newNumber(0, test.text, typ)
Rob Pike13f88972011-07-04 15:15:47 +100093 ok := test.isInt || test.isUint || test.isFloat || test.isComplex
Rob Pikecd7826e2011-06-23 09:27:28 +100094 if ok && err != nil {
Rob Pikebf9531f2011-07-11 11:46:22 +100095 t.Errorf("unexpected error for %q: %s", test.text, err)
Rob Pikecd7826e2011-06-23 09:27:28 +100096 continue
97 }
98 if !ok && err == nil {
99 t.Errorf("expected error for %q", test.text)
100 continue
101 }
102 if !ok {
Rob Pikebf9531f2011-07-11 11:46:22 +1000103 if *debug {
104 fmt.Printf("%s\n\t%s\n", test.text, err)
105 }
Rob Pikecd7826e2011-06-23 09:27:28 +1000106 continue
107 }
Rob Pikec66917d2011-08-09 15:42:53 +1000108 if n.IsComplex != test.isComplex {
Rob Pike13f88972011-07-04 15:15:47 +1000109 t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex)
Rob Pikecd7826e2011-06-23 09:27:28 +1000110 }
111 if test.isInt {
Rob Pikec66917d2011-08-09 15:42:53 +1000112 if !n.IsInt {
Rob Pikecd7826e2011-06-23 09:27:28 +1000113 t.Errorf("expected integer for %q", test.text)
114 }
Rob Pikec66917d2011-08-09 15:42:53 +1000115 if n.Int64 != test.int64 {
116 t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64)
Rob Pikecd7826e2011-06-23 09:27:28 +1000117 }
Rob Pikec66917d2011-08-09 15:42:53 +1000118 } else if n.IsInt {
Rob Pikecd7826e2011-06-23 09:27:28 +1000119 t.Errorf("did not expect integer for %q", test.text)
120 }
121 if test.isUint {
Rob Pikec66917d2011-08-09 15:42:53 +1000122 if !n.IsUint {
Rob Pikecd7826e2011-06-23 09:27:28 +1000123 t.Errorf("expected unsigned integer for %q", test.text)
124 }
Rob Pikec66917d2011-08-09 15:42:53 +1000125 if n.Uint64 != test.uint64 {
126 t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64)
Rob Pikecd7826e2011-06-23 09:27:28 +1000127 }
Rob Pikec66917d2011-08-09 15:42:53 +1000128 } else if n.IsUint {
Rob Pikecd7826e2011-06-23 09:27:28 +1000129 t.Errorf("did not expect unsigned integer for %q", test.text)
130 }
131 if test.isFloat {
Rob Pikec66917d2011-08-09 15:42:53 +1000132 if !n.IsFloat {
Rob Pikecd7826e2011-06-23 09:27:28 +1000133 t.Errorf("expected float for %q", test.text)
134 }
Rob Pikec66917d2011-08-09 15:42:53 +1000135 if n.Float64 != test.float64 {
136 t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64)
Rob Pikecd7826e2011-06-23 09:27:28 +1000137 }
Rob Pikec66917d2011-08-09 15:42:53 +1000138 } else if n.IsFloat {
Rob Pikecd7826e2011-06-23 09:27:28 +1000139 t.Errorf("did not expect float for %q", test.text)
140 }
Rob Pike13f88972011-07-04 15:15:47 +1000141 if test.isComplex {
Rob Pikec66917d2011-08-09 15:42:53 +1000142 if !n.IsComplex {
Rob Pike13f88972011-07-04 15:15:47 +1000143 t.Errorf("expected complex for %q", test.text)
144 }
Rob Pikec66917d2011-08-09 15:42:53 +1000145 if n.Complex128 != test.complex128 {
146 t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128)
Rob Pike13f88972011-07-04 15:15:47 +1000147 }
Rob Pikec66917d2011-08-09 15:42:53 +1000148 } else if n.IsComplex {
Rob Pike13f88972011-07-04 15:15:47 +1000149 t.Errorf("did not expect complex for %q", test.text)
150 }
Rob Pikecd7826e2011-06-23 09:27:28 +1000151 }
152}
153
Rob Pikecd7826e2011-06-23 09:27:28 +1000154type parseTest struct {
155 name string
156 input string
157 ok bool
Rob Pikec837e612012-01-19 13:51:37 -0800158 result string // what the user would see in an error message.
Rob Pikecd7826e2011-06-23 09:27:28 +1000159}
160
161const (
162 noError = true
163 hasError = false
164)
165
166var parseTests = []parseTest{
167 {"empty", "", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800168 ``},
Rob Pikec66917d2011-08-09 15:42:53 +1000169 {"comment", "{{/*\n\n\n*/}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800170 ``},
Rob Pikecd7826e2011-06-23 09:27:28 +1000171 {"spaces", " \t\n", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800172 `" \t\n"`},
Rob Pikecd7826e2011-06-23 09:27:28 +1000173 {"text", "some text", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800174 `"some text"`},
Rob Pike8d538c62011-07-07 10:56:33 +1000175 {"emptyAction", "{{}}", hasError,
Rob Pikec837e612012-01-19 13:51:37 -0800176 `{{}}`},
Rob Pike81592c22011-06-28 23:04:08 +1000177 {"field", "{{.X}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800178 `{{.X}}`},
Rob Pike5b165822011-07-05 16:02:34 +1000179 {"simple command", "{{printf}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800180 `{{printf}}`},
Rob Pikee7030e72011-07-11 10:01:15 +1000181 {"$ invocation", "{{$}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800182 "{{$}}"},
Rob Pikee7030e72011-07-11 10:01:15 +1000183 {"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800184 "{{with $x := 3}}{{$x 23}}{{end}}"},
Rob Pike7b79b3b2011-07-11 15:23:38 +1000185 {"variable with fields", "{{$.I}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800186 "{{$.I}}"},
Rob Pike5b165822011-07-05 16:02:34 +1000187 {"multi-word command", "{{printf `%d` 23}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800188 "{{printf `%d` 23}}"},
Rob Pike5b165822011-07-05 16:02:34 +1000189 {"pipeline", "{{.X|.Y}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800190 `{{.X | .Y}}`},
Rob Pikec7057012011-07-17 13:31:59 +1000191 {"pipeline with decl", "{{$x := .X|.Y}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800192 `{{$x := .X | .Y}}`},
Rob Pikecc842c72012-08-24 12:37:23 -0700193 {"nested pipeline", "{{.X (.Y .Z) (.A | .B .C) (.E)}}", noError,
194 `{{.X (.Y .Z) (.A | .B .C) (.E)}}`},
Rob Pike90505502012-09-24 13:23:15 +1000195 {"field applied to parentheses", "{{(.Y .Z).Field}}", noError,
196 `{{(.Y .Z).Field}}`},
Rob Pike13f88972011-07-04 15:15:47 +1000197 {"simple if", "{{if .X}}hello{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800198 `{{if .X}}"hello"{{end}}`},
Rob Pike13f88972011-07-04 15:15:47 +1000199 {"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800200 `{{if .X}}"true"{{else}}"false"{{end}}`},
Rob Pike37cee772013-08-28 14:43:56 +1000201 {"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 Pike81592c22011-06-28 23:04:08 +1000205 {"simple range", "{{range .X}}hello{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800206 `{{range .X}}"hello"{{end}}`},
Rob Pike81592c22011-06-28 23:04:08 +1000207 {"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800208 `{{range .X.Y.Z}}"hello"{{end}}`},
Rob Pike81592c22011-06-28 23:04:08 +1000209 {"nested range", "{{range .X}}hello{{range .Y}}goodbye{{end}}{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800210 `{{range .X}}"hello"{{range .Y}}"goodbye"{{end}}{{end}}`},
Rob Pike81592c22011-06-28 23:04:08 +1000211 {"range with else", "{{range .X}}true{{else}}false{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800212 `{{range .X}}"true"{{else}}"false"{{end}}`},
Rob Pike81592c22011-06-28 23:04:08 +1000213 {"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800214 `{{range .X | .M}}"true"{{else}}"false"{{end}}`},
Rob Pike81592c22011-06-28 23:04:08 +1000215 {"range []int", "{{range .SI}}{{.}}{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800216 `{{range .SI}}{{.}}{{end}}`},
Rob Pike8170d812012-03-14 07:03:11 +1100217 {"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 Pike18c378c2012-08-08 20:02:19 -0700221 {"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
222 `{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
Rob Pike3987b912011-07-10 07:32:01 +1000223 {"template", "{{template `x`}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800224 `{{template "x"}}`},
Rob Pike7aa1a1a2011-07-13 15:58:31 +1000225 {"template with arg", "{{template `x` .Y}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800226 `{{template "x" .Y}}`},
Rob Pike13f88972011-07-04 15:15:47 +1000227 {"with", "{{with .X}}hello{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800228 `{{with .X}}"hello"{{end}}`},
Rob Pike13f88972011-07-04 15:15:47 +1000229 {"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
Rob Pikec837e612012-01-19 13:51:37 -0800230 `{{with .X}}"hello"{{else}}"goodbye"{{end}}`},
Rob Pikee6ee26a2015-09-08 14:58:12 -0700231 // 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 Gerrand12dfc3b2015-08-28 15:31:51 +1000238 {"block definition", `{{block "foo" .}}hello{{end}}`, noError,
239 `{{template "foo" .}}`},
Rob Pikecd7826e2011-06-23 09:27:28 +1000240 // Errors.
241 {"unclosed action", "hello{{range", hasError, ""},
Rob Pikee7030e72011-07-11 10:01:15 +1000242 {"unmatched end", "{{end}}", hasError, ""},
Didier Spezia80cedf32015-05-01 18:20:31 +0000243 {"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 Pikecd7826e2011-06-23 09:27:28 +1000246 {"missing end", "hello{{range .x}}", hasError, ""},
247 {"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
Rob Pike5b165822011-07-05 16:02:34 +1000248 {"undefined function", "hello{{undefined}}", hasError, ""},
Rob Pikee7030e72011-07-11 10:01:15 +1000249 {"undefined variable", "{{$x}}", hasError, ""},
250 {"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""},
Rob Pike41efecf2011-07-13 13:21:18 +1000251 {"variable undefined in template", "{{template $v}}", hasError, ""},
Rob Pike7b79b3b2011-07-11 15:23:38 +1000252 {"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""},
Rob Pike7aa1a1a2011-07-13 15:58:31 +1000253 {"template with field ref", "{{template .X}}", hasError, ""},
254 {"template with var", "{{template $v}}", hasError, ""},
Rob Pikefc1f0bd2011-07-14 13:15:55 +1000255 {"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 Pike8b230662012-08-24 13:00:24 -0700258 {"dot applied to parentheses", "{{printf (printf .).}}", hasError, ""},
Rob Pikede13e8d2012-08-29 21:42:53 -0700259 {"adjacent args", "{{printf 3`x`}}", hasError, ""},
260 {"adjacent args with .", "{{printf `x`.}}", hasError, ""},
Rob Pike37cee772013-08-28 14:43:56 +1000261 {"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""},
Rob Pike8170d812012-03-14 07:03:11 +1100262 // 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 Spezia76ace942015-05-02 11:03:35 +0000273 // 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 Spezia62fb4722015-05-05 06:45:49 +0000281 // Wrong pipeline
282 {"wrong pipeline dot", "{{12|.}}", hasError, ""},
283 {"wrong pipeline number", "{{.|12|printf}}", hasError, ""},
Didier Speziaf68533692015-05-21 21:35:49 +0000284 {"wrong pipeline string", "{{.|printf|\"error\"}}", hasError, ""},
285 {"wrong pipeline char", "{{12|printf|'e'}}", hasError, ""},
Didier Spezia62fb4722015-05-05 06:45:49 +0000286 {"wrong pipeline boolean", "{{.|true}}", hasError, ""},
287 {"wrong pipeline nil", "{{'c'|nil}}", hasError, ""},
288 {"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""},
Andrew Gerrand12dfc3b2015-08-28 15:31:51 +1000289 // Missing pipeline in block
290 {"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
Rob Pikecd7826e2011-06-23 09:27:28 +1000291}
292
Rob Pike7506ee72011-08-09 16:49:36 +1000293var builtins = map[string]interface{}{
294 "printf": fmt.Sprintf,
Rob Pikec66917d2011-08-09 15:42:53 +1000295}
296
Rob Pikeb027a0f2012-02-11 14:21:16 +1100297func testParse(doCopy bool, t *testing.T) {
Rob Pikedf4de942013-07-31 15:09:13 +1000298 textFormat = "%q"
299 defer func() { textFormat = "%s" }()
Rob Pikecd7826e2011-06-23 09:27:28 +1000300 for _, test := range parseTests {
Rob Pikef56db6f2011-11-23 20:17:22 -0800301 tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins)
Rob Pikecd7826e2011-06-23 09:27:28 +1000302 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 Pike64228e32011-07-06 17:46:36 +1000311 if *debug {
Rob Pikecd7826e2011-06-23 09:27:28 +1000312 fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
313 }
314 continue
315 }
Rob Pikeb027a0f2012-02-11 14:21:16 +1100316 var result string
317 if doCopy {
318 result = tmpl.Root.Copy().String()
319 } else {
320 result = tmpl.Root.String()
321 }
Rob Pikecd7826e2011-06-23 09:27:28 +1000322 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 Pikee6b33712011-12-01 17:24:54 -0800327
Rob Pikeb027a0f2012-02-11 14:21:16 +1100328func TestParse(t *testing.T) {
329 testParse(false, t)
330}
331
332// Same as TestParse, but we copy the node first
333func TestParseCopy(t *testing.T) {
334 testParse(true, t)
335}
336
Rob Pikee6b33712011-12-01 17:24:54 -0800337type isEmptyTest struct {
338 name string
339 input string
340 empty bool
341}
342
343var 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 Pikecaa46212013-09-12 13:22:56 +1000349 {"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n", false},
Rob Pikee6b33712011-12-01 17:24:54 -0800350 {"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false},
351}
352
353func TestIsEmpty(t *testing.T) {
Rob Pike180541b2012-02-28 14:23:57 +1100354 if !IsEmptyTree(nil) {
355 t.Errorf("nil tree is not empty")
356 }
Rob Pikee6b33712011-12-01 17:24:54 -0800357 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 Pike7b7a7a52012-09-14 15:25:37 -0700368
Josh Bleecher Snydereeb75852013-09-17 14:19:44 +1000369func 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 Pike7b7a7a52012-09-14 15:25:37 -0700385// All failures, and the result is a string that must appear in the error message.
386var 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 Spezia76ace942015-05-02 11:03:35 +0000409 hasError, `in operand`},
Rob Pike7b7a7a52012-09-14 15:25:37 -0700410 {"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 Pike90505502012-09-24 13:23:15 +1000432 // 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 Pike7b7a7a52012-09-14 15:25:37 -0700435 {"multidecl",
436 "{{$a,$b,$c := 23}}",
437 hasError, `too many declarations`},
438 {"undefvar",
439 "{{$a}}",
440 hasError, `undefined variable`},
Didier Spezia76ace942015-05-02 11:03:35 +0000441 {"wrongdot",
442 "{{true.any}}",
443 hasError, `unexpected . after term`},
Didier Spezia62fb4722015-05-05 06:45:49 +0000444 {"wrongpipeline",
445 "{{12|false}}",
446 hasError, `non executable command in pipeline`},
447 {"emptypipeline",
448 `{{ ( ) }}`,
449 hasError, `missing value for parenthesized pipeline`},
Rob Pike7b7a7a52012-09-14 15:25:37 -0700450}
451
452func 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 Gerrand12dfc3b2015-08-28 15:31:51 +1000464
465func 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}