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  }