| | |
| | |
| | |
| |
|
| | package parse |
| |
|
| | import ( |
| | "fmt" |
| | "testing" |
| | ) |
| |
|
| | |
| | var itemName = map[itemType]string{ |
| | itemError: "error", |
| | itemBool: "bool", |
| | itemChar: "char", |
| | itemCharConstant: "charconst", |
| | itemComment: "comment", |
| | itemComplex: "complex", |
| | itemDeclare: ":=", |
| | itemEOF: "EOF", |
| | itemField: "field", |
| | itemIdentifier: "identifier", |
| | itemLeftDelim: "left delim", |
| | itemLeftParen: "(", |
| | itemNumber: "number", |
| | itemPipe: "pipe", |
| | itemRawString: "raw string", |
| | itemRightDelim: "right delim", |
| | itemRightParen: ")", |
| | itemSpace: "space", |
| | itemString: "string", |
| | itemVariable: "variable", |
| |
|
| | |
| | itemDot: ".", |
| | itemBlock: "block", |
| | itemBreak: "break", |
| | itemContinue: "continue", |
| | itemDefine: "define", |
| | itemElse: "else", |
| | itemIf: "if", |
| | itemEnd: "end", |
| | itemNil: "nil", |
| | itemRange: "range", |
| | itemTemplate: "template", |
| | itemWith: "with", |
| | } |
| |
|
| | func (i itemType) String() string { |
| | s := itemName[i] |
| | if s == "" { |
| | return fmt.Sprintf("item%d", int(i)) |
| | } |
| | return s |
| | } |
| |
|
| | type lexTest struct { |
| | name string |
| | input string |
| | items []item |
| | } |
| |
|
| | func mkItem(typ itemType, text string) item { |
| | return item{ |
| | typ: typ, |
| | val: text, |
| | } |
| | } |
| |
|
| | var ( |
| | tDot = mkItem(itemDot, ".") |
| | tBlock = mkItem(itemBlock, "block") |
| | tEOF = mkItem(itemEOF, "") |
| | tFor = mkItem(itemIdentifier, "for") |
| | tLeft = mkItem(itemLeftDelim, "{{") |
| | tLpar = mkItem(itemLeftParen, "(") |
| | tPipe = mkItem(itemPipe, "|") |
| | tQuote = mkItem(itemString, `"abc \n\t\" "`) |
| | tRange = mkItem(itemRange, "range") |
| | tRight = mkItem(itemRightDelim, "}}") |
| | tRpar = mkItem(itemRightParen, ")") |
| | tSpace = mkItem(itemSpace, " ") |
| | raw = "`" + `abc\n\t\" ` + "`" |
| | rawNL = "`now is{{\n}}the time`" |
| | tRawQuote = mkItem(itemRawString, raw) |
| | tRawQuoteNL = mkItem(itemRawString, rawNL) |
| | ) |
| |
|
| | var lexTests = []lexTest{ |
| | {"empty", "", []item{tEOF}}, |
| | {"spaces", " \t\n", []item{mkItem(itemText, " \t\n"), tEOF}}, |
| | {"text", `now is the time`, []item{mkItem(itemText, "now is the time"), tEOF}}, |
| | {"text with comment", "hello-{{/* this is a comment */}}-world", []item{ |
| | mkItem(itemText, "hello-"), |
| | mkItem(itemComment, "/* this is a comment */"), |
| | mkItem(itemText, "-world"), |
| | tEOF, |
| | }}, |
| | {"punctuation", "{{,@% }}", []item{ |
| | tLeft, |
| | mkItem(itemChar, ","), |
| | mkItem(itemChar, "@"), |
| | mkItem(itemChar, "%"), |
| | tSpace, |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"parens", "{{((3))}}", []item{ |
| | tLeft, |
| | tLpar, |
| | tLpar, |
| | mkItem(itemNumber, "3"), |
| | tRpar, |
| | tRpar, |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"empty action", `{{}}`, []item{tLeft, tRight, tEOF}}, |
| | {"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}}, |
| | {"block", `{{block "foo" .}}`, []item{ |
| | tLeft, tBlock, tSpace, mkItem(itemString, `"foo"`), tSpace, tDot, tRight, tEOF, |
| | }}, |
| | {"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}}, |
| | {"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}}, |
| | {"raw quote with newline", "{{" + rawNL + "}}", []item{tLeft, tRawQuoteNL, tRight, tEOF}}, |
| | {"numbers", "{{1 02 0x14 0X14 -7.2i 1e3 1E3 +1.2e-4 4.2i 1+2i 1_2 0x1.e_fp4 0X1.E_FP4}}", []item{ |
| | tLeft, |
| | mkItem(itemNumber, "1"), |
| | tSpace, |
| | mkItem(itemNumber, "02"), |
| | tSpace, |
| | mkItem(itemNumber, "0x14"), |
| | tSpace, |
| | mkItem(itemNumber, "0X14"), |
| | tSpace, |
| | mkItem(itemNumber, "-7.2i"), |
| | tSpace, |
| | mkItem(itemNumber, "1e3"), |
| | tSpace, |
| | mkItem(itemNumber, "1E3"), |
| | tSpace, |
| | mkItem(itemNumber, "+1.2e-4"), |
| | tSpace, |
| | mkItem(itemNumber, "4.2i"), |
| | tSpace, |
| | mkItem(itemComplex, "1+2i"), |
| | tSpace, |
| | mkItem(itemNumber, "1_2"), |
| | tSpace, |
| | mkItem(itemNumber, "0x1.e_fp4"), |
| | tSpace, |
| | mkItem(itemNumber, "0X1.E_FP4"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{ |
| | tLeft, |
| | mkItem(itemCharConstant, `'a'`), |
| | tSpace, |
| | mkItem(itemCharConstant, `'\n'`), |
| | tSpace, |
| | mkItem(itemCharConstant, `'\''`), |
| | tSpace, |
| | mkItem(itemCharConstant, `'\\'`), |
| | tSpace, |
| | mkItem(itemCharConstant, `'\u00FF'`), |
| | tSpace, |
| | mkItem(itemCharConstant, `'\xFF'`), |
| | tSpace, |
| | mkItem(itemCharConstant, `'本'`), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"bools", "{{true false}}", []item{ |
| | tLeft, |
| | mkItem(itemBool, "true"), |
| | tSpace, |
| | mkItem(itemBool, "false"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"dot", "{{.}}", []item{ |
| | tLeft, |
| | tDot, |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"nil", "{{nil}}", []item{ |
| | tLeft, |
| | mkItem(itemNil, "nil"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"dots", "{{.x . .2 .x.y.z}}", []item{ |
| | tLeft, |
| | mkItem(itemField, ".x"), |
| | tSpace, |
| | tDot, |
| | tSpace, |
| | mkItem(itemNumber, ".2"), |
| | tSpace, |
| | mkItem(itemField, ".x"), |
| | mkItem(itemField, ".y"), |
| | mkItem(itemField, ".z"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"keywords", "{{range if else end with}}", []item{ |
| | tLeft, |
| | mkItem(itemRange, "range"), |
| | tSpace, |
| | mkItem(itemIf, "if"), |
| | tSpace, |
| | mkItem(itemElse, "else"), |
| | tSpace, |
| | mkItem(itemEnd, "end"), |
| | tSpace, |
| | mkItem(itemWith, "with"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{ |
| | tLeft, |
| | mkItem(itemVariable, "$c"), |
| | tSpace, |
| | mkItem(itemDeclare, ":="), |
| | tSpace, |
| | mkItem(itemIdentifier, "printf"), |
| | tSpace, |
| | mkItem(itemVariable, "$"), |
| | tSpace, |
| | mkItem(itemVariable, "$hello"), |
| | tSpace, |
| | mkItem(itemVariable, "$23"), |
| | tSpace, |
| | mkItem(itemVariable, "$"), |
| | tSpace, |
| | mkItem(itemVariable, "$var"), |
| | mkItem(itemField, ".Field"), |
| | tSpace, |
| | mkItem(itemField, ".Method"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"variable invocation", "{{$x 23}}", []item{ |
| | tLeft, |
| | mkItem(itemVariable, "$x"), |
| | tSpace, |
| | mkItem(itemNumber, "23"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{ |
| | mkItem(itemText, "intro "), |
| | tLeft, |
| | mkItem(itemIdentifier, "echo"), |
| | tSpace, |
| | mkItem(itemIdentifier, "hi"), |
| | tSpace, |
| | mkItem(itemNumber, "1.2"), |
| | tSpace, |
| | tPipe, |
| | mkItem(itemIdentifier, "noargs"), |
| | tPipe, |
| | mkItem(itemIdentifier, "args"), |
| | tSpace, |
| | mkItem(itemNumber, "1"), |
| | tSpace, |
| | mkItem(itemString, `"hi"`), |
| | tRight, |
| | mkItem(itemText, " outro"), |
| | tEOF, |
| | }}, |
| | {"declaration", "{{$v := 3}}", []item{ |
| | tLeft, |
| | mkItem(itemVariable, "$v"), |
| | tSpace, |
| | mkItem(itemDeclare, ":="), |
| | tSpace, |
| | mkItem(itemNumber, "3"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"2 declarations", "{{$v , $w := 3}}", []item{ |
| | tLeft, |
| | mkItem(itemVariable, "$v"), |
| | tSpace, |
| | mkItem(itemChar, ","), |
| | tSpace, |
| | mkItem(itemVariable, "$w"), |
| | tSpace, |
| | mkItem(itemDeclare, ":="), |
| | tSpace, |
| | mkItem(itemNumber, "3"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"field of parenthesized expression", "{{(.X).Y}}", []item{ |
| | tLeft, |
| | tLpar, |
| | mkItem(itemField, ".X"), |
| | tRpar, |
| | mkItem(itemField, ".Y"), |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"trimming spaces before and after", "hello- {{- 3 -}} -world", []item{ |
| | mkItem(itemText, "hello-"), |
| | tLeft, |
| | mkItem(itemNumber, "3"), |
| | tRight, |
| | mkItem(itemText, "-world"), |
| | tEOF, |
| | }}, |
| | {"trimming spaces before and after comment", "hello- {{- /* hello */ -}} -world", []item{ |
| | mkItem(itemText, "hello-"), |
| | mkItem(itemComment, "/* hello */"), |
| | mkItem(itemText, "-world"), |
| | tEOF, |
| | }}, |
| | |
| | {"badchar", "#{{\x01}}", []item{ |
| | mkItem(itemText, "#"), |
| | tLeft, |
| | mkItem(itemError, "unrecognized character in action: U+0001"), |
| | }}, |
| | {"unclosed action", "{{", []item{ |
| | tLeft, |
| | mkItem(itemError, "unclosed action"), |
| | }}, |
| | {"EOF in action", "{{range", []item{ |
| | tLeft, |
| | tRange, |
| | mkItem(itemError, "unclosed action"), |
| | }}, |
| | {"unclosed quote", "{{\"\n\"}}", []item{ |
| | tLeft, |
| | mkItem(itemError, "unterminated quoted string"), |
| | }}, |
| | {"unclosed raw quote", "{{`xx}}", []item{ |
| | tLeft, |
| | mkItem(itemError, "unterminated raw quoted string"), |
| | }}, |
| | {"unclosed char constant", "{{'\n}}", []item{ |
| | tLeft, |
| | mkItem(itemError, "unterminated character constant"), |
| | }}, |
| | {"bad number", "{{3k}}", []item{ |
| | tLeft, |
| | mkItem(itemError, `bad number syntax: "3k"`), |
| | }}, |
| | {"unclosed paren", "{{(3}}", []item{ |
| | tLeft, |
| | tLpar, |
| | mkItem(itemNumber, "3"), |
| | mkItem(itemError, `unclosed left paren`), |
| | }}, |
| | {"extra right paren", "{{3)}}", []item{ |
| | tLeft, |
| | mkItem(itemNumber, "3"), |
| | mkItem(itemError, "unexpected right paren"), |
| | }}, |
| |
|
| | |
| | |
| | |
| | {"long pipeline deadlock", "{{|||||}}", []item{ |
| | tLeft, |
| | tPipe, |
| | tPipe, |
| | tPipe, |
| | tPipe, |
| | tPipe, |
| | tRight, |
| | tEOF, |
| | }}, |
| | {"text with bad comment", "hello-{{/*/}}-world", []item{ |
| | mkItem(itemText, "hello-"), |
| | mkItem(itemError, `unclosed comment`), |
| | }}, |
| | {"text with comment close separated from delim", "hello-{{/* */ }}-world", []item{ |
| | mkItem(itemText, "hello-"), |
| | mkItem(itemError, `comment ends before closing delimiter`), |
| | }}, |
| | |
| | |
| | {"unmatched right delimiter", "hello-{.}}-world", []item{ |
| | mkItem(itemText, "hello-{.}}-world"), |
| | tEOF, |
| | }}, |
| | } |
| |
|
| | |
| | func collect(t *lexTest, left, right string) (items []item) { |
| | l := lex(t.name, t.input, left, right) |
| | l.options = lexOptions{ |
| | emitComment: true, |
| | breakOK: true, |
| | continueOK: true, |
| | } |
| | for { |
| | item := l.nextItem() |
| | items = append(items, item) |
| | if item.typ == itemEOF || item.typ == itemError { |
| | break |
| | } |
| | } |
| | return |
| | } |
| |
|
| | func equal(i1, i2 []item, checkPos bool) bool { |
| | if len(i1) != len(i2) { |
| | return false |
| | } |
| | for k := range i1 { |
| | if i1[k].typ != i2[k].typ { |
| | return false |
| | } |
| | if i1[k].val != i2[k].val { |
| | return false |
| | } |
| | if checkPos && i1[k].pos != i2[k].pos { |
| | return false |
| | } |
| | if checkPos && i1[k].line != i2[k].line { |
| | return false |
| | } |
| | } |
| | return true |
| | } |
| |
|
| | func TestLex(t *testing.T) { |
| | for _, test := range lexTests { |
| | items := collect(&test, "", "") |
| | if !equal(items, test.items, false) { |
| | t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items) |
| | return |
| | } |
| | t.Log(test.name, "OK") |
| | } |
| | } |
| |
|
| | |
| | var lexDelimTests = []lexTest{ |
| | {"punctuation", "$$,@%{{}}@@", []item{ |
| | tLeftDelim, |
| | mkItem(itemChar, ","), |
| | mkItem(itemChar, "@"), |
| | mkItem(itemChar, "%"), |
| | mkItem(itemChar, "{"), |
| | mkItem(itemChar, "{"), |
| | mkItem(itemChar, "}"), |
| | mkItem(itemChar, "}"), |
| | tRightDelim, |
| | tEOF, |
| | }}, |
| | {"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}}, |
| | {"for", `$$for@@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}}, |
| | {"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}}, |
| | {"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}}, |
| | } |
| |
|
| | var ( |
| | tLeftDelim = mkItem(itemLeftDelim, "$$") |
| | tRightDelim = mkItem(itemRightDelim, "@@") |
| | ) |
| |
|
| | func TestDelims(t *testing.T) { |
| | for _, test := range lexDelimTests { |
| | items := collect(&test, "$$", "@@") |
| | if !equal(items, test.items, false) { |
| | t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) |
| | } |
| | } |
| | } |
| |
|
| | func TestDelimsAlphaNumeric(t *testing.T) { |
| | test := lexTest{"right delimiter with alphanumeric start", "{{hub .host hub}}", []item{ |
| | mkItem(itemLeftDelim, "{{hub"), |
| | mkItem(itemSpace, " "), |
| | mkItem(itemField, ".host"), |
| | mkItem(itemSpace, " "), |
| | mkItem(itemRightDelim, "hub}}"), |
| | tEOF, |
| | }} |
| | items := collect(&test, "{{hub", "hub}}") |
| |
|
| | if !equal(items, test.items, false) { |
| | t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) |
| | } |
| | } |
| |
|
| | func TestDelimsAndMarkers(t *testing.T) { |
| | test := lexTest{"delims that look like markers", "{{- .x -}} {{- - .x - -}}", []item{ |
| | mkItem(itemLeftDelim, "{{- "), |
| | mkItem(itemField, ".x"), |
| | mkItem(itemRightDelim, " -}}"), |
| | mkItem(itemLeftDelim, "{{- "), |
| | mkItem(itemField, ".x"), |
| | mkItem(itemRightDelim, " -}}"), |
| | tEOF, |
| | }} |
| | items := collect(&test, "{{- ", " -}}") |
| |
|
| | if !equal(items, test.items, false) { |
| | t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) |
| | } |
| | } |
| |
|
| | var lexPosTests = []lexTest{ |
| | {"empty", "", []item{{itemEOF, 0, "", 1}}}, |
| | {"punctuation", "{{,@%#}}", []item{ |
| | {itemLeftDelim, 0, "{{", 1}, |
| | {itemChar, 2, ",", 1}, |
| | {itemChar, 3, "@", 1}, |
| | {itemChar, 4, "%", 1}, |
| | {itemChar, 5, "#", 1}, |
| | {itemRightDelim, 6, "}}", 1}, |
| | {itemEOF, 8, "", 1}, |
| | }}, |
| | {"sample", "0123{{hello}}xyz", []item{ |
| | {itemText, 0, "0123", 1}, |
| | {itemLeftDelim, 4, "{{", 1}, |
| | {itemIdentifier, 6, "hello", 1}, |
| | {itemRightDelim, 11, "}}", 1}, |
| | {itemText, 13, "xyz", 1}, |
| | {itemEOF, 16, "", 1}, |
| | }}, |
| | {"trimafter", "{{x -}}\n{{y}}", []item{ |
| | {itemLeftDelim, 0, "{{", 1}, |
| | {itemIdentifier, 2, "x", 1}, |
| | {itemRightDelim, 5, "}}", 1}, |
| | {itemLeftDelim, 8, "{{", 2}, |
| | {itemIdentifier, 10, "y", 2}, |
| | {itemRightDelim, 11, "}}", 2}, |
| | {itemEOF, 13, "", 2}, |
| | }}, |
| | {"trimbefore", "{{x}}\n{{- y}}", []item{ |
| | {itemLeftDelim, 0, "{{", 1}, |
| | {itemIdentifier, 2, "x", 1}, |
| | {itemRightDelim, 3, "}}", 1}, |
| | {itemLeftDelim, 6, "{{", 2}, |
| | {itemIdentifier, 10, "y", 2}, |
| | {itemRightDelim, 11, "}}", 2}, |
| | {itemEOF, 13, "", 2}, |
| | }}, |
| | {"longcomment", "{{/*\n*/}}\n{{undefinedFunction \"test\"}}", []item{ |
| | {itemComment, 2, "/*\n*/", 1}, |
| | {itemText, 9, "\n", 2}, |
| | {itemLeftDelim, 10, "{{", 3}, |
| | {itemIdentifier, 12, "undefinedFunction", 3}, |
| | {itemSpace, 29, " ", 3}, |
| | {itemString, 30, "\"test\"", 3}, |
| | {itemRightDelim, 36, "}}", 3}, |
| | {itemEOF, 38, "", 3}, |
| | }}, |
| | } |
| |
|
| | |
| | |
| | func TestPos(t *testing.T) { |
| | for _, test := range lexPosTests { |
| | items := collect(&test, "", "") |
| | if !equal(items, test.items, true) { |
| | t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) |
| | if len(items) == len(test.items) { |
| | |
| | for i := range items { |
| | if !equal(items[i:i+1], test.items[i:i+1], true) { |
| | i1 := items[i] |
| | i2 := test.items[i] |
| | t.Errorf("\t#%d: got {%v %d %q %d} expected {%v %d %q %d}", |
| | i, i1.typ, i1.pos, i1.val, i1.line, i2.typ, i2.pos, i2.val, i2.line) |
| | } |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|