github.com/tcnksm/go@v0.0.0-20141208075154-439b32936367/src/text/template/parse/lex_test.go (about) 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 5 package parse 6 7 import ( 8 "fmt" 9 "testing" 10 ) 11 12 // Make the types prettyprint. 13 var itemName = map[itemType]string{ 14 itemError: "error", 15 itemBool: "bool", 16 itemChar: "char", 17 itemCharConstant: "charconst", 18 itemComplex: "complex", 19 itemColonEquals: ":=", 20 itemEOF: "EOF", 21 itemField: "field", 22 itemIdentifier: "identifier", 23 itemLeftDelim: "left delim", 24 itemLeftParen: "(", 25 itemNumber: "number", 26 itemPipe: "pipe", 27 itemRawString: "raw string", 28 itemRightDelim: "right delim", 29 itemRightParen: ")", 30 itemSpace: "space", 31 itemString: "string", 32 itemVariable: "variable", 33 34 // keywords 35 itemDot: ".", 36 itemDefine: "define", 37 itemElse: "else", 38 itemIf: "if", 39 itemEnd: "end", 40 itemNil: "nil", 41 itemRange: "range", 42 itemTemplate: "template", 43 itemWith: "with", 44 } 45 46 func (i itemType) String() string { 47 s := itemName[i] 48 if s == "" { 49 return fmt.Sprintf("item%d", int(i)) 50 } 51 return s 52 } 53 54 type lexTest struct { 55 name string 56 input string 57 items []item 58 } 59 60 var ( 61 tEOF = item{itemEOF, 0, ""} 62 tFor = item{itemIdentifier, 0, "for"} 63 tLeft = item{itemLeftDelim, 0, "{{"} 64 tLpar = item{itemLeftParen, 0, "("} 65 tPipe = item{itemPipe, 0, "|"} 66 tQuote = item{itemString, 0, `"abc \n\t\" "`} 67 tRange = item{itemRange, 0, "range"} 68 tRight = item{itemRightDelim, 0, "}}"} 69 tRpar = item{itemRightParen, 0, ")"} 70 tSpace = item{itemSpace, 0, " "} 71 raw = "`" + `abc\n\t\" ` + "`" 72 tRawQuote = item{itemRawString, 0, raw} 73 ) 74 75 var lexTests = []lexTest{ 76 {"empty", "", []item{tEOF}}, 77 {"spaces", " \t\n", []item{{itemText, 0, " \t\n"}, tEOF}}, 78 {"text", `now is the time`, []item{{itemText, 0, "now is the time"}, tEOF}}, 79 {"text with comment", "hello-{{/* this is a comment */}}-world", []item{ 80 {itemText, 0, "hello-"}, 81 {itemText, 0, "-world"}, 82 tEOF, 83 }}, 84 {"punctuation", "{{,@% }}", []item{ 85 tLeft, 86 {itemChar, 0, ","}, 87 {itemChar, 0, "@"}, 88 {itemChar, 0, "%"}, 89 tSpace, 90 tRight, 91 tEOF, 92 }}, 93 {"parens", "{{((3))}}", []item{ 94 tLeft, 95 tLpar, 96 tLpar, 97 {itemNumber, 0, "3"}, 98 tRpar, 99 tRpar, 100 tRight, 101 tEOF, 102 }}, 103 {"empty action", `{{}}`, []item{tLeft, tRight, tEOF}}, 104 {"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}}, 105 {"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}}, 106 {"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}}, 107 {"numbers", "{{1 02 0x14 -7.2i 1e3 +1.2e-4 4.2i 1+2i}}", []item{ 108 tLeft, 109 {itemNumber, 0, "1"}, 110 tSpace, 111 {itemNumber, 0, "02"}, 112 tSpace, 113 {itemNumber, 0, "0x14"}, 114 tSpace, 115 {itemNumber, 0, "-7.2i"}, 116 tSpace, 117 {itemNumber, 0, "1e3"}, 118 tSpace, 119 {itemNumber, 0, "+1.2e-4"}, 120 tSpace, 121 {itemNumber, 0, "4.2i"}, 122 tSpace, 123 {itemComplex, 0, "1+2i"}, 124 tRight, 125 tEOF, 126 }}, 127 {"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{ 128 tLeft, 129 {itemCharConstant, 0, `'a'`}, 130 tSpace, 131 {itemCharConstant, 0, `'\n'`}, 132 tSpace, 133 {itemCharConstant, 0, `'\''`}, 134 tSpace, 135 {itemCharConstant, 0, `'\\'`}, 136 tSpace, 137 {itemCharConstant, 0, `'\u00FF'`}, 138 tSpace, 139 {itemCharConstant, 0, `'\xFF'`}, 140 tSpace, 141 {itemCharConstant, 0, `'本'`}, 142 tRight, 143 tEOF, 144 }}, 145 {"bools", "{{true false}}", []item{ 146 tLeft, 147 {itemBool, 0, "true"}, 148 tSpace, 149 {itemBool, 0, "false"}, 150 tRight, 151 tEOF, 152 }}, 153 {"dot", "{{.}}", []item{ 154 tLeft, 155 {itemDot, 0, "."}, 156 tRight, 157 tEOF, 158 }}, 159 {"nil", "{{nil}}", []item{ 160 tLeft, 161 {itemNil, 0, "nil"}, 162 tRight, 163 tEOF, 164 }}, 165 {"dots", "{{.x . .2 .x.y.z}}", []item{ 166 tLeft, 167 {itemField, 0, ".x"}, 168 tSpace, 169 {itemDot, 0, "."}, 170 tSpace, 171 {itemNumber, 0, ".2"}, 172 tSpace, 173 {itemField, 0, ".x"}, 174 {itemField, 0, ".y"}, 175 {itemField, 0, ".z"}, 176 tRight, 177 tEOF, 178 }}, 179 {"keywords", "{{range if else end with}}", []item{ 180 tLeft, 181 {itemRange, 0, "range"}, 182 tSpace, 183 {itemIf, 0, "if"}, 184 tSpace, 185 {itemElse, 0, "else"}, 186 tSpace, 187 {itemEnd, 0, "end"}, 188 tSpace, 189 {itemWith, 0, "with"}, 190 tRight, 191 tEOF, 192 }}, 193 {"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{ 194 tLeft, 195 {itemVariable, 0, "$c"}, 196 tSpace, 197 {itemColonEquals, 0, ":="}, 198 tSpace, 199 {itemIdentifier, 0, "printf"}, 200 tSpace, 201 {itemVariable, 0, "$"}, 202 tSpace, 203 {itemVariable, 0, "$hello"}, 204 tSpace, 205 {itemVariable, 0, "$23"}, 206 tSpace, 207 {itemVariable, 0, "$"}, 208 tSpace, 209 {itemVariable, 0, "$var"}, 210 {itemField, 0, ".Field"}, 211 tSpace, 212 {itemField, 0, ".Method"}, 213 tRight, 214 tEOF, 215 }}, 216 {"variable invocation", "{{$x 23}}", []item{ 217 tLeft, 218 {itemVariable, 0, "$x"}, 219 tSpace, 220 {itemNumber, 0, "23"}, 221 tRight, 222 tEOF, 223 }}, 224 {"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{ 225 {itemText, 0, "intro "}, 226 tLeft, 227 {itemIdentifier, 0, "echo"}, 228 tSpace, 229 {itemIdentifier, 0, "hi"}, 230 tSpace, 231 {itemNumber, 0, "1.2"}, 232 tSpace, 233 tPipe, 234 {itemIdentifier, 0, "noargs"}, 235 tPipe, 236 {itemIdentifier, 0, "args"}, 237 tSpace, 238 {itemNumber, 0, "1"}, 239 tSpace, 240 {itemString, 0, `"hi"`}, 241 tRight, 242 {itemText, 0, " outro"}, 243 tEOF, 244 }}, 245 {"declaration", "{{$v := 3}}", []item{ 246 tLeft, 247 {itemVariable, 0, "$v"}, 248 tSpace, 249 {itemColonEquals, 0, ":="}, 250 tSpace, 251 {itemNumber, 0, "3"}, 252 tRight, 253 tEOF, 254 }}, 255 {"2 declarations", "{{$v , $w := 3}}", []item{ 256 tLeft, 257 {itemVariable, 0, "$v"}, 258 tSpace, 259 {itemChar, 0, ","}, 260 tSpace, 261 {itemVariable, 0, "$w"}, 262 tSpace, 263 {itemColonEquals, 0, ":="}, 264 tSpace, 265 {itemNumber, 0, "3"}, 266 tRight, 267 tEOF, 268 }}, 269 {"field of parenthesized expression", "{{(.X).Y}}", []item{ 270 tLeft, 271 tLpar, 272 {itemField, 0, ".X"}, 273 tRpar, 274 {itemField, 0, ".Y"}, 275 tRight, 276 tEOF, 277 }}, 278 // errors 279 {"badchar", "#{{\x01}}", []item{ 280 {itemText, 0, "#"}, 281 tLeft, 282 {itemError, 0, "unrecognized character in action: U+0001"}, 283 }}, 284 {"unclosed action", "{{\n}}", []item{ 285 tLeft, 286 {itemError, 0, "unclosed action"}, 287 }}, 288 {"EOF in action", "{{range", []item{ 289 tLeft, 290 tRange, 291 {itemError, 0, "unclosed action"}, 292 }}, 293 {"unclosed quote", "{{\"\n\"}}", []item{ 294 tLeft, 295 {itemError, 0, "unterminated quoted string"}, 296 }}, 297 {"unclosed raw quote", "{{`xx\n`}}", []item{ 298 tLeft, 299 {itemError, 0, "unterminated raw quoted string"}, 300 }}, 301 {"unclosed char constant", "{{'\n}}", []item{ 302 tLeft, 303 {itemError, 0, "unterminated character constant"}, 304 }}, 305 {"bad number", "{{3k}}", []item{ 306 tLeft, 307 {itemError, 0, `bad number syntax: "3k"`}, 308 }}, 309 {"unclosed paren", "{{(3}}", []item{ 310 tLeft, 311 tLpar, 312 {itemNumber, 0, "3"}, 313 {itemError, 0, `unclosed left paren`}, 314 }}, 315 {"extra right paren", "{{3)}}", []item{ 316 tLeft, 317 {itemNumber, 0, "3"}, 318 tRpar, 319 {itemError, 0, `unexpected right paren U+0029 ')'`}, 320 }}, 321 322 // Fixed bugs 323 // Many elements in an action blew the lookahead until 324 // we made lexInsideAction not loop. 325 {"long pipeline deadlock", "{{|||||}}", []item{ 326 tLeft, 327 tPipe, 328 tPipe, 329 tPipe, 330 tPipe, 331 tPipe, 332 tRight, 333 tEOF, 334 }}, 335 {"text with bad comment", "hello-{{/*/}}-world", []item{ 336 {itemText, 0, "hello-"}, 337 {itemError, 0, `unclosed comment`}, 338 }}, 339 {"text with comment close separted from delim", "hello-{{/* */ }}-world", []item{ 340 {itemText, 0, "hello-"}, 341 {itemError, 0, `comment ends before closing delimiter`}, 342 }}, 343 // This one is an error that we can't catch because it breaks templates with 344 // minimized JavaScript. Should have fixed it before Go 1.1. 345 {"unmatched right delimiter", "hello-{.}}-world", []item{ 346 {itemText, 0, "hello-{.}}-world"}, 347 tEOF, 348 }}, 349 } 350 351 // collect gathers the emitted items into a slice. 352 func collect(t *lexTest, left, right string) (items []item) { 353 l := lex(t.name, t.input, left, right) 354 for { 355 item := l.nextItem() 356 items = append(items, item) 357 if item.typ == itemEOF || item.typ == itemError { 358 break 359 } 360 } 361 return 362 } 363 364 func equal(i1, i2 []item, checkPos bool) bool { 365 if len(i1) != len(i2) { 366 return false 367 } 368 for k := range i1 { 369 if i1[k].typ != i2[k].typ { 370 return false 371 } 372 if i1[k].val != i2[k].val { 373 return false 374 } 375 if checkPos && i1[k].pos != i2[k].pos { 376 return false 377 } 378 } 379 return true 380 } 381 382 func TestLex(t *testing.T) { 383 for _, test := range lexTests { 384 items := collect(&test, "", "") 385 if !equal(items, test.items, false) { 386 t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items) 387 } 388 } 389 } 390 391 // Some easy cases from above, but with delimiters $$ and @@ 392 var lexDelimTests = []lexTest{ 393 {"punctuation", "$$,@%{{}}@@", []item{ 394 tLeftDelim, 395 {itemChar, 0, ","}, 396 {itemChar, 0, "@"}, 397 {itemChar, 0, "%"}, 398 {itemChar, 0, "{"}, 399 {itemChar, 0, "{"}, 400 {itemChar, 0, "}"}, 401 {itemChar, 0, "}"}, 402 tRightDelim, 403 tEOF, 404 }}, 405 {"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}}, 406 {"for", `$$for@@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}}, 407 {"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}}, 408 {"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}}, 409 } 410 411 var ( 412 tLeftDelim = item{itemLeftDelim, 0, "$$"} 413 tRightDelim = item{itemRightDelim, 0, "@@"} 414 ) 415 416 func TestDelims(t *testing.T) { 417 for _, test := range lexDelimTests { 418 items := collect(&test, "$$", "@@") 419 if !equal(items, test.items, false) { 420 t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) 421 } 422 } 423 } 424 425 var lexPosTests = []lexTest{ 426 {"empty", "", []item{tEOF}}, 427 {"punctuation", "{{,@%#}}", []item{ 428 {itemLeftDelim, 0, "{{"}, 429 {itemChar, 2, ","}, 430 {itemChar, 3, "@"}, 431 {itemChar, 4, "%"}, 432 {itemChar, 5, "#"}, 433 {itemRightDelim, 6, "}}"}, 434 {itemEOF, 8, ""}, 435 }}, 436 {"sample", "0123{{hello}}xyz", []item{ 437 {itemText, 0, "0123"}, 438 {itemLeftDelim, 4, "{{"}, 439 {itemIdentifier, 6, "hello"}, 440 {itemRightDelim, 11, "}}"}, 441 {itemText, 13, "xyz"}, 442 {itemEOF, 16, ""}, 443 }}, 444 } 445 446 // The other tests don't check position, to make the test cases easier to construct. 447 // This one does. 448 func TestPos(t *testing.T) { 449 for _, test := range lexPosTests { 450 items := collect(&test, "", "") 451 if !equal(items, test.items, true) { 452 t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) 453 if len(items) == len(test.items) { 454 // Detailed print; avoid item.String() to expose the position value. 455 for i := range items { 456 if !equal(items[i:i+1], test.items[i:i+1], true) { 457 i1 := items[i] 458 i2 := test.items[i] 459 t.Errorf("\t#%d: got {%v %d %q} expected {%v %d %q}", i, i1.typ, i1.pos, i1.val, i2.typ, i2.pos, i2.val) 460 } 461 } 462 } 463 } 464 } 465 }