├── go.mod ├── testdata ├── x.tmpl └── y.tmpl ├── README.md ├── LICENSE ├── tmplfunc_test.go ├── func.go ├── tmpl.go └── internal └── parse ├── lex_test.go ├── lex.go ├── parse.go ├── parse_test.go └── node.go /go.mod: -------------------------------------------------------------------------------- 1 | module rsc.io/tmplfunc 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /testdata/x.tmpl: -------------------------------------------------------------------------------- 1 | {{define "x"}} 2 | {{if len .}}{{y (slice . 1 (len .))}}{{else}}x{{end}} 3 | {{end}} 4 | -------------------------------------------------------------------------------- /testdata/y.tmpl: -------------------------------------------------------------------------------- 1 | {{define "y"}} 2 | {{if len .}}{{x (slice . 1 (len .))}}{{else}}y{{end}} 3 | {{end}} 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation](https://pkg.go.dev/badge/rsc.io/tmplfunc.svg)](https://pkg.go.dev/rsc.io/tmplfunc) 2 | 3 | Package tmplfunc provides an extension of Go templates 4 | in which templates can be invoked as if they were functions. 5 | 6 | See the [package documentation](https://pkg.go.dev/rsc.io/tmplfunc) for details. 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /tmplfunc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 tmplfunc 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "strings" 11 | "testing" 12 | 13 | htmltemplate "html/template" 14 | texttemplate "text/template" 15 | ) 16 | 17 | var tests = []struct { 18 | in string 19 | out string 20 | }{ 21 | {`{{define "hello"}}hello {{.}}{{end}}{{template "hello" "world"}}`, "hello world"}, 22 | {`{{define "hello"}}hello {{.}}{{end}}{{hello "world"}}`, "hello world"}, 23 | {`{{define "hello who"}}hello {{.who}}{{end}}{{hello "world"}}`, "hello world"}, 24 | {`{{define "hello who"}}hello {{.who}}{{end}}{{hello}}`, 25 | "EXEC: template: :1:45: executing \"\" at : error calling hello: too few arguments in call to template hello", 26 | }, 27 | {`{{define "hello who?"}}hello {{.who}}{{end}}{{hello}}`, "hello"}, 28 | {`{{define "hello who?"}}hello {{.who}}{{end}}{{hello "world"}}`, "hello world"}, 29 | {`{{define "hello who..."}}hello {{.who}}{{end}}{{hello}}`, "hello []"}, 30 | {`{{define "hello who..."}}hello {{.who}}{{end}}{{hello "world"}}`, "hello [world]"}, 31 | } 32 | 33 | func TestText(t *testing.T) { 34 | for i, tt := range tests { 35 | t.Run(fmt.Sprint(i), func(t *testing.T) { 36 | tmpl := texttemplate.New("") 37 | err := Parse(tmpl, tt.in) 38 | var out string 39 | if err != nil { 40 | out = "PARSE: " + err.Error() 41 | } else { 42 | var buf bytes.Buffer 43 | err := tmpl.Execute(&buf, nil) 44 | if err != nil { 45 | out = "EXEC: " + err.Error() 46 | } else { 47 | out = strings.ReplaceAll(buf.String(), "", "") // text generates these but html does not 48 | out = strings.TrimSpace(out) 49 | } 50 | } 51 | if out != tt.out { 52 | t.Errorf("have: %s\nwant: %s", out, tt.out) 53 | } 54 | }) 55 | } 56 | } 57 | 58 | func TestHTML(t *testing.T) { 59 | for i, tt := range tests { 60 | t.Run(fmt.Sprint(i), func(t *testing.T) { 61 | tmpl := htmltemplate.New("") 62 | err := Parse(tmpl, tt.in) 63 | var out string 64 | if err != nil { 65 | out = "PARSE: " + err.Error() 66 | } else { 67 | var buf bytes.Buffer 68 | err := tmpl.Execute(&buf, nil) 69 | if err != nil { 70 | out = "EXEC: " + err.Error() 71 | } else { 72 | out = strings.TrimSpace(buf.String()) 73 | } 74 | } 75 | if out != tt.out { 76 | t.Errorf("have: %s\nwant: %s", out, tt.out) 77 | } 78 | }) 79 | } 80 | } 81 | 82 | func TestGlob(t *testing.T) { 83 | tmpl := texttemplate.New("") 84 | MustParseGlob(tmpl, "testdata/*.tmpl") 85 | texttemplate.Must(tmpl.Parse("{{x .}}")) 86 | 87 | var buf bytes.Buffer 88 | must(tmpl.Execute(&buf, []int{1, 2, 3})) 89 | out := strings.TrimSpace(buf.String()) 90 | if out != "y" { 91 | t.Fatalf("out = %q, want %q", out, "y") 92 | } 93 | } 94 | 95 | func TestFuncs(t *testing.T) { 96 | tmpl := htmltemplate.New("") 97 | MustParseGlob(tmpl, "testdata/*.tmpl") 98 | htmltemplate.Must(tmpl.Parse("{{x .}}")) 99 | 100 | tmpl2 := htmltemplate.Must(tmpl.Clone()) 101 | if err := Funcs(tmpl2); err != nil { 102 | t.Fatal(err) 103 | } 104 | tmpl2.Execute(new(bytes.Buffer), nil) 105 | 106 | if _, err := tmpl.Clone(); err != nil { 107 | // Happens if you forget to call Funcs above: 108 | // cannot Clone "" after it has executed 109 | t.Fatal(err) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /func.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 tmplfunc 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "reflect" 11 | "regexp" 12 | "strings" 13 | 14 | "rsc.io/tmplfunc/internal/parse" 15 | 16 | htmltemplate "html/template" 17 | texttemplate "text/template" 18 | ) 19 | 20 | var validNameRE = regexp.MustCompile(`\A[_\pL][_\pL\p{Nd}]*\z`) 21 | var validArgNameRE = regexp.MustCompile(`\A[_\pL][_\pL\p{Nd}]*(\.\.\.|\?)?\z`) 22 | 23 | func funcs(t Template, names, texts []string) error { 24 | var leftDelim, rightDelim string 25 | switch t := t.(type) { 26 | case nil: 27 | return fmt.Errorf("tmplfunc: nil Template") 28 | default: 29 | return fmt.Errorf("tmplfunc: non-template type %T", t) 30 | case *texttemplate.Template: 31 | leftDelim = reflect.ValueOf(t).Elem().FieldByName("leftDelim").String() 32 | rightDelim = reflect.ValueOf(t).Elem().FieldByName("rightDelim").String() 33 | case *htmltemplate.Template: 34 | leftDelim = reflect.ValueOf(t).Elem().FieldByName("text").Elem().FieldByName("leftDelim").String() 35 | rightDelim = reflect.ValueOf(t).Elem().FieldByName("text").Elem().FieldByName("rightDelim").String() 36 | } 37 | 38 | trees := make(map[string]*parse.Tree) 39 | for i, text := range texts { 40 | t := parse.New(names[i], nil) 41 | t.Mode = parse.SkipFuncCheck 42 | _, err := t.Parse(text, leftDelim, rightDelim, trees) 43 | if err != nil { 44 | return err 45 | } 46 | } 47 | 48 | // Install functions for named templates as appropriate. 49 | funcs := make(map[string]interface{}) 50 | for name := range trees { 51 | if err := addFunc(t, name, funcs); err != nil { 52 | return err 53 | } 54 | } 55 | 56 | switch t := t.(type) { 57 | case *texttemplate.Template: 58 | t.Funcs(funcs) 59 | case *htmltemplate.Template: 60 | t.Funcs(funcs) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // Funcs installs functions for all the templates in the set containing t. 67 | // After using t.Clone it is necessary to call Funcs on the result to arrange 68 | // for the functions to invoke the cloned templates and not the originals. 69 | func Funcs(t Template) error { 70 | funcs := make(map[string]interface{}) 71 | switch t := t.(type) { 72 | case *texttemplate.Template: 73 | for _, t1 := range t.Templates() { 74 | if err := addFunc(t, t1.Name(), funcs); err != nil { 75 | return err 76 | } 77 | } 78 | t.Funcs(funcs) 79 | case *htmltemplate.Template: 80 | for _, t1 := range t.Templates() { 81 | if err := addFunc(t, t1.Name(), funcs); err != nil { 82 | return err 83 | } 84 | } 85 | t.Funcs(funcs) 86 | } 87 | return nil 88 | } 89 | 90 | func addFunc(t Template, name string, funcs map[string]interface{}) error { 91 | fn, bundle, err := bundler(name) 92 | if err != nil { 93 | return err 94 | } 95 | if fn == "" { 96 | return nil 97 | } 98 | switch t := t.(type) { 99 | case *texttemplate.Template: 100 | funcs[fn] = func(args ...interface{}) (string, error) { 101 | t := t.Lookup(name) 102 | if t == nil { 103 | return "", fmt.Errorf("lost template %q", name) 104 | } 105 | arg, err := bundle(args) 106 | if err != nil { 107 | return "", err 108 | } 109 | var buf bytes.Buffer 110 | err = t.Execute(&buf, arg) 111 | if err != nil { 112 | return "", err 113 | } 114 | return buf.String(), nil 115 | } 116 | case *htmltemplate.Template: 117 | funcs[fn] = func(args ...interface{}) (htmltemplate.HTML, error) { 118 | t := t.Lookup(name) 119 | if t == nil { 120 | return "", fmt.Errorf("lost template %q", name) 121 | } 122 | arg, err := bundle(args) 123 | if err != nil { 124 | return "", err 125 | } 126 | var buf bytes.Buffer 127 | err = t.Execute(&buf, arg) 128 | if err != nil { 129 | return "", err 130 | } 131 | return htmltemplate.HTML(buf.String()), nil 132 | } 133 | } 134 | return nil 135 | } 136 | 137 | func bundler(name string) (fn string, bundle func(args []interface{}) (interface{}, error), err error) { 138 | f := strings.Fields(name) 139 | if len(f) == 0 || !validNameRE.MatchString(f[0]) { 140 | return "", nil, nil 141 | } 142 | 143 | fn = f[0] 144 | if len(f) == 1 { 145 | bundle = func(args []interface{}) (interface{}, error) { 146 | if len(args) == 0 { 147 | return nil, nil 148 | } 149 | if len(args) == 1 { 150 | return args[0], nil 151 | } 152 | return nil, fmt.Errorf("too many arguments in call to template %s", fn) 153 | } 154 | } else { 155 | sawQ := false 156 | for i, argName := range f[1:] { 157 | if !validArgNameRE.MatchString(argName) { 158 | return "", nil, fmt.Errorf("invalid template name %q: invalid argument name %s", name, argName) 159 | } 160 | if strings.HasSuffix(argName, "...") { 161 | if i != len(f)-2 { 162 | return "", nil, fmt.Errorf("invalid template name %q: %s is not last argument", name, argName) 163 | } 164 | break 165 | } 166 | if strings.HasSuffix(argName, "?") { 167 | sawQ = true 168 | continue 169 | } 170 | if sawQ { 171 | return "", nil, fmt.Errorf("invalid template name %q: required %s after optional %s", name, argName, f[i]) 172 | } 173 | } 174 | 175 | bundle = func(args []interface{}) (interface{}, error) { 176 | m := make(map[string]interface{}) 177 | for _, argName := range f[1:] { 178 | if strings.HasSuffix(argName, "...") { 179 | m[strings.TrimSuffix(argName, "...")] = args 180 | args = nil 181 | break 182 | } 183 | if strings.HasSuffix(argName, "?") { 184 | prefix := strings.TrimSuffix(argName, "?") 185 | if len(args) == 0 { 186 | m[prefix] = nil 187 | } else { 188 | m[prefix], args = args[0], args[1:] 189 | } 190 | continue 191 | } 192 | if len(args) == 0 { 193 | return nil, fmt.Errorf("too few arguments in call to template %s", fn) 194 | } 195 | m[argName], args = args[0], args[1:] 196 | } 197 | if len(args) > 0 { 198 | return nil, fmt.Errorf("too many arguments in call to template %s", fn) 199 | } 200 | return m, nil 201 | } 202 | } 203 | 204 | return fn, bundle, nil 205 | } 206 | -------------------------------------------------------------------------------- /tmpl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 tmplfunc provides an extension of Go templates 6 | // in which templates can be invoked as if they were functions. 7 | // 8 | // For example, after parsing 9 | // 10 | // {{define "link url text"}}{{.text}}{{end}} 11 | // 12 | // this package installs a function named link allowing the template 13 | // to be invoked as 14 | // 15 | // {{link "https://golang.org" "the Go language"}} 16 | // 17 | // instead of the longer-form (assuming an appropriate function named dict) 18 | // 19 | // {{template "link" (dict "url" "https://golang.org" "text" "the Go language")}} 20 | // 21 | // Function Definitions 22 | // 23 | // The function installed for a given template depends on the name of the 24 | // defined template, which can include not just a function name but also 25 | // a list of parameter names. The function name and parameter names must 26 | // consist only of letters, digits, and underscores, with a leading non-digit. 27 | // 28 | // If there is no parameter list, then the function is expected to take 29 | // at most one argument, made available in the template body as “.” (dot). 30 | // If such a function is called with no arguments, dot will be a nil interface value. 31 | // 32 | // If there is a parameter list, then the function requires an argument for 33 | // each parameter, except for optional and variadic parameters, explained below. 34 | // Inside the template, the top-level value “.” is a map[string]interface{} in which 35 | // each parameter name is mapped to the corresponding argument value. 36 | // A parameter x can therefore be accessed as {{(index . "x")}} or, more concisely, {{.x}}. 37 | // 38 | // The first special case in parameter handling is that 39 | // a parameter can be made optional by adding a “?” suffix after its name. 40 | // If the argument list ends before that parameter, the corresponding map entry 41 | // will be present and set to a nil value. 42 | // The second special case is that a parameter can be made variadic 43 | // by adding a “...” suffix after its name. 44 | // The corresponding map entry contains a []interface{} holding the 45 | // zero or more arguments corresponding to that parameter. 46 | // 47 | // In the parameter list, required parameters must precede optional parameters, 48 | // which must in turn precede any variadic parameter. 49 | // 50 | // For example, we can revise the link template given earlier to make the 51 | // link text optional, substituting the URL when the text is omitted: 52 | // 53 | // {{define "link url text?"}}{{or .text .url}}{{end}} 54 | // 55 | // The Go home page is {{link "https://golang.org"}}. 56 | // 57 | // Usage 58 | // 59 | // This package is meant to be used with templates from either the 60 | // text/template or html/template packages. Given a *template.Template 61 | // variable t, substitute: 62 | // 63 | // t.Parse(text) -> tmplfunc.Parse(t, text) 64 | // t.ParseFiles(list) -> tmplfunc.ParseFiles(t, list) 65 | // t.ParseGlob(pattern) -> tmplfunc.ParseGlob(t, pattern) 66 | // 67 | // Parse, ParseFiles, and ParseGlob parse the new templates but also add 68 | // functions that invoke them, named according to the function signatures. 69 | // Templates can only invoke functions for templates that have already been 70 | // defined or that are being defined in the same Parse, ParseFiles, or ParseGlob call. 71 | // For example, templates in two files x.tmpl and y.tmpl can call each other 72 | // only if ParseFiles or ParseGlob is used to parse both files in a single call. 73 | // Otherwise, the parsing of the first file will report that calls to templates in 74 | // the second file are calling unknown functions. 75 | // 76 | // When used with the html/template package, all function-invoked template 77 | // calls are treated as invoking templates producing HTML. In order to use a 78 | // template that produces some other kind of text fragment, the template must 79 | // be invoked directly using the {{template "name"}} form, not as a function call. 80 | package tmplfunc 81 | 82 | import ( 83 | "fmt" 84 | "io/ioutil" 85 | "path/filepath" 86 | 87 | htmltemplate "html/template" 88 | texttemplate "text/template" 89 | ) 90 | 91 | // A Template is a *template.Template, where template refers to either 92 | // the html/template or text/template package. 93 | type Template interface { 94 | // Method here only to make most types that are not a *template.Template 95 | // not implement the interface. The requirement here is to be one of the two 96 | // template types, not just to have this single method. 97 | DefinedTemplates() string 98 | Name() string 99 | } 100 | 101 | // Parse is like t.Parse(text), adding functions for the templates defined in text. 102 | func Parse(t Template, text string) error { 103 | if err := funcs(t, []string{t.Name()}, []string{text}); err != nil { 104 | return err 105 | } 106 | var err error 107 | switch t := t.(type) { 108 | case *texttemplate.Template: 109 | _, err = t.Parse(text) 110 | case *htmltemplate.Template: 111 | _, err = t.Parse(text) 112 | } 113 | return err 114 | } 115 | 116 | // ParseFiles is like t.ParseFiles(filenames...), adding functions for the parsed templates. 117 | func ParseFiles(t Template, filenames ...string) error { 118 | return parseFiles(t, readFileOS, filenames...) 119 | } 120 | 121 | // parseFiles is the helper for the method and function. If the argument 122 | // template is nil, it is created from the first file. 123 | func parseFiles(t Template, readFile func(string) (string, []byte, error), filenames ...string) error { 124 | if len(filenames) == 0 { 125 | // Not really a problem, but be consistent. 126 | return fmt.Errorf("tmplfunc: no files named in call to ParseFiles") 127 | } 128 | 129 | var names []string 130 | var texts []string 131 | for _, filename := range filenames { 132 | name, b, err := readFile(filename) 133 | if err != nil { 134 | return err 135 | } 136 | names = append(names, name) 137 | texts = append(texts, string(b)) 138 | } 139 | 140 | err := funcs(t, names, texts) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | switch t := t.(type) { 146 | case *texttemplate.Template: 147 | for i, name := range names { 148 | var tmpl *texttemplate.Template 149 | if name == t.Name() { 150 | tmpl = t 151 | } else { 152 | tmpl = t.New(name) 153 | } 154 | if _, err := tmpl.Parse(texts[i]); err != nil { 155 | return err 156 | } 157 | } 158 | 159 | case *htmltemplate.Template: 160 | for i, name := range names { 161 | var tmpl *htmltemplate.Template 162 | if name == t.Name() { 163 | tmpl = t 164 | } else { 165 | tmpl = t.New(name) 166 | } 167 | if _, err := tmpl.Parse(texts[i]); err != nil { 168 | return err 169 | } 170 | } 171 | } 172 | 173 | return nil 174 | } 175 | 176 | // ParseGlob is like t.ParseGlob(pattern), adding functions for the parsed templates. 177 | func ParseGlob(t Template, pattern string) error { 178 | filenames, err := filepath.Glob(pattern) 179 | if err != nil { 180 | return err 181 | } 182 | if len(filenames) == 0 { 183 | return fmt.Errorf("tmplfunc: pattern matches no files: %#q", pattern) 184 | } 185 | return parseFiles(t, readFileOS, filenames...) 186 | } 187 | 188 | func must(err error) { 189 | if err != nil { 190 | panic(err) 191 | } 192 | } 193 | 194 | // MustParse is like Parse but panics on error. 195 | func MustParse(t Template, text string) { 196 | must(Parse(t, text)) 197 | } 198 | 199 | // MustParseFiles is like ParseFiles but panics on error. 200 | func MustParseFiles(t Template, filenames ...string) { 201 | must(ParseFiles(t, filenames...)) 202 | } 203 | 204 | // MustParseGlob is like ParseGlob but panics on error. 205 | func MustParseGlob(t Template, pattern string) { 206 | must(ParseGlob(t, pattern)) 207 | } 208 | 209 | func readFileOS(file string) (name string, b []byte, err error) { 210 | name = filepath.Base(file) 211 | b, err = ioutil.ReadFile(file) 212 | return 213 | } 214 | -------------------------------------------------------------------------------- /internal/parse/lex_test.go: -------------------------------------------------------------------------------- 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 | itemComment: "comment", 19 | itemComplex: "complex", 20 | itemDeclare: ":=", 21 | itemEOF: "EOF", 22 | itemField: "field", 23 | itemIdentifier: "identifier", 24 | itemLeftDelim: "left delim", 25 | itemLeftParen: "(", 26 | itemNumber: "number", 27 | itemPipe: "pipe", 28 | itemRawString: "raw string", 29 | itemRightDelim: "right delim", 30 | itemRightParen: ")", 31 | itemSpace: "space", 32 | itemString: "string", 33 | itemVariable: "variable", 34 | 35 | // keywords 36 | itemDot: ".", 37 | itemBlock: "block", 38 | itemDefine: "define", 39 | itemElse: "else", 40 | itemIf: "if", 41 | itemEnd: "end", 42 | itemNil: "nil", 43 | itemRange: "range", 44 | itemTemplate: "template", 45 | itemWith: "with", 46 | } 47 | 48 | func (i itemType) String() string { 49 | s := itemName[i] 50 | if s == "" { 51 | return fmt.Sprintf("item%d", int(i)) 52 | } 53 | return s 54 | } 55 | 56 | type lexTest struct { 57 | name string 58 | input string 59 | items []item 60 | } 61 | 62 | func mkItem(typ itemType, text string) item { 63 | return item{ 64 | typ: typ, 65 | val: text, 66 | } 67 | } 68 | 69 | var ( 70 | tDot = mkItem(itemDot, ".") 71 | tBlock = mkItem(itemBlock, "block") 72 | tEOF = mkItem(itemEOF, "") 73 | tFor = mkItem(itemIdentifier, "for") 74 | tLeft = mkItem(itemLeftDelim, "{{") 75 | tLpar = mkItem(itemLeftParen, "(") 76 | tPipe = mkItem(itemPipe, "|") 77 | tQuote = mkItem(itemString, `"abc \n\t\" "`) 78 | tRange = mkItem(itemRange, "range") 79 | tRight = mkItem(itemRightDelim, "}}") 80 | tRpar = mkItem(itemRightParen, ")") 81 | tSpace = mkItem(itemSpace, " ") 82 | raw = "`" + `abc\n\t\" ` + "`" 83 | rawNL = "`now is{{\n}}the time`" // Contains newline inside raw quote. 84 | tRawQuote = mkItem(itemRawString, raw) 85 | tRawQuoteNL = mkItem(itemRawString, rawNL) 86 | ) 87 | 88 | var lexTests = []lexTest{ 89 | {"empty", "", []item{tEOF}}, 90 | {"spaces", " \t\n", []item{mkItem(itemText, " \t\n"), tEOF}}, 91 | {"text", `now is the time`, []item{mkItem(itemText, "now is the time"), tEOF}}, 92 | {"text with comment", "hello-{{/* this is a comment */}}-world", []item{ 93 | mkItem(itemText, "hello-"), 94 | mkItem(itemComment, "/* this is a comment */"), 95 | mkItem(itemText, "-world"), 96 | tEOF, 97 | }}, 98 | {"punctuation", "{{,@% }}", []item{ 99 | tLeft, 100 | mkItem(itemChar, ","), 101 | mkItem(itemChar, "@"), 102 | mkItem(itemChar, "%"), 103 | tSpace, 104 | tRight, 105 | tEOF, 106 | }}, 107 | {"parens", "{{((3))}}", []item{ 108 | tLeft, 109 | tLpar, 110 | tLpar, 111 | mkItem(itemNumber, "3"), 112 | tRpar, 113 | tRpar, 114 | tRight, 115 | tEOF, 116 | }}, 117 | {"empty action", `{{}}`, []item{tLeft, tRight, tEOF}}, 118 | {"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}}, 119 | {"block", `{{block "foo" .}}`, []item{ 120 | tLeft, tBlock, tSpace, mkItem(itemString, `"foo"`), tSpace, tDot, tRight, tEOF, 121 | }}, 122 | {"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}}, 123 | {"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}}, 124 | {"raw quote with newline", "{{" + rawNL + "}}", []item{tLeft, tRawQuoteNL, tRight, tEOF}}, 125 | {"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{ 126 | tLeft, 127 | mkItem(itemNumber, "1"), 128 | tSpace, 129 | mkItem(itemNumber, "02"), 130 | tSpace, 131 | mkItem(itemNumber, "0x14"), 132 | tSpace, 133 | mkItem(itemNumber, "0X14"), 134 | tSpace, 135 | mkItem(itemNumber, "-7.2i"), 136 | tSpace, 137 | mkItem(itemNumber, "1e3"), 138 | tSpace, 139 | mkItem(itemNumber, "1E3"), 140 | tSpace, 141 | mkItem(itemNumber, "+1.2e-4"), 142 | tSpace, 143 | mkItem(itemNumber, "4.2i"), 144 | tSpace, 145 | mkItem(itemComplex, "1+2i"), 146 | tSpace, 147 | mkItem(itemNumber, "1_2"), 148 | tSpace, 149 | mkItem(itemNumber, "0x1.e_fp4"), 150 | tSpace, 151 | mkItem(itemNumber, "0X1.E_FP4"), 152 | tRight, 153 | tEOF, 154 | }}, 155 | {"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{ 156 | tLeft, 157 | mkItem(itemCharConstant, `'a'`), 158 | tSpace, 159 | mkItem(itemCharConstant, `'\n'`), 160 | tSpace, 161 | mkItem(itemCharConstant, `'\''`), 162 | tSpace, 163 | mkItem(itemCharConstant, `'\\'`), 164 | tSpace, 165 | mkItem(itemCharConstant, `'\u00FF'`), 166 | tSpace, 167 | mkItem(itemCharConstant, `'\xFF'`), 168 | tSpace, 169 | mkItem(itemCharConstant, `'本'`), 170 | tRight, 171 | tEOF, 172 | }}, 173 | {"bools", "{{true false}}", []item{ 174 | tLeft, 175 | mkItem(itemBool, "true"), 176 | tSpace, 177 | mkItem(itemBool, "false"), 178 | tRight, 179 | tEOF, 180 | }}, 181 | {"dot", "{{.}}", []item{ 182 | tLeft, 183 | tDot, 184 | tRight, 185 | tEOF, 186 | }}, 187 | {"nil", "{{nil}}", []item{ 188 | tLeft, 189 | mkItem(itemNil, "nil"), 190 | tRight, 191 | tEOF, 192 | }}, 193 | {"dots", "{{.x . .2 .x.y.z}}", []item{ 194 | tLeft, 195 | mkItem(itemField, ".x"), 196 | tSpace, 197 | tDot, 198 | tSpace, 199 | mkItem(itemNumber, ".2"), 200 | tSpace, 201 | mkItem(itemField, ".x"), 202 | mkItem(itemField, ".y"), 203 | mkItem(itemField, ".z"), 204 | tRight, 205 | tEOF, 206 | }}, 207 | {"keywords", "{{range if else end with}}", []item{ 208 | tLeft, 209 | mkItem(itemRange, "range"), 210 | tSpace, 211 | mkItem(itemIf, "if"), 212 | tSpace, 213 | mkItem(itemElse, "else"), 214 | tSpace, 215 | mkItem(itemEnd, "end"), 216 | tSpace, 217 | mkItem(itemWith, "with"), 218 | tRight, 219 | tEOF, 220 | }}, 221 | {"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{ 222 | tLeft, 223 | mkItem(itemVariable, "$c"), 224 | tSpace, 225 | mkItem(itemDeclare, ":="), 226 | tSpace, 227 | mkItem(itemIdentifier, "printf"), 228 | tSpace, 229 | mkItem(itemVariable, "$"), 230 | tSpace, 231 | mkItem(itemVariable, "$hello"), 232 | tSpace, 233 | mkItem(itemVariable, "$23"), 234 | tSpace, 235 | mkItem(itemVariable, "$"), 236 | tSpace, 237 | mkItem(itemVariable, "$var"), 238 | mkItem(itemField, ".Field"), 239 | tSpace, 240 | mkItem(itemField, ".Method"), 241 | tRight, 242 | tEOF, 243 | }}, 244 | {"variable invocation", "{{$x 23}}", []item{ 245 | tLeft, 246 | mkItem(itemVariable, "$x"), 247 | tSpace, 248 | mkItem(itemNumber, "23"), 249 | tRight, 250 | tEOF, 251 | }}, 252 | {"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{ 253 | mkItem(itemText, "intro "), 254 | tLeft, 255 | mkItem(itemIdentifier, "echo"), 256 | tSpace, 257 | mkItem(itemIdentifier, "hi"), 258 | tSpace, 259 | mkItem(itemNumber, "1.2"), 260 | tSpace, 261 | tPipe, 262 | mkItem(itemIdentifier, "noargs"), 263 | tPipe, 264 | mkItem(itemIdentifier, "args"), 265 | tSpace, 266 | mkItem(itemNumber, "1"), 267 | tSpace, 268 | mkItem(itemString, `"hi"`), 269 | tRight, 270 | mkItem(itemText, " outro"), 271 | tEOF, 272 | }}, 273 | {"declaration", "{{$v := 3}}", []item{ 274 | tLeft, 275 | mkItem(itemVariable, "$v"), 276 | tSpace, 277 | mkItem(itemDeclare, ":="), 278 | tSpace, 279 | mkItem(itemNumber, "3"), 280 | tRight, 281 | tEOF, 282 | }}, 283 | {"2 declarations", "{{$v , $w := 3}}", []item{ 284 | tLeft, 285 | mkItem(itemVariable, "$v"), 286 | tSpace, 287 | mkItem(itemChar, ","), 288 | tSpace, 289 | mkItem(itemVariable, "$w"), 290 | tSpace, 291 | mkItem(itemDeclare, ":="), 292 | tSpace, 293 | mkItem(itemNumber, "3"), 294 | tRight, 295 | tEOF, 296 | }}, 297 | {"field of parenthesized expression", "{{(.X).Y}}", []item{ 298 | tLeft, 299 | tLpar, 300 | mkItem(itemField, ".X"), 301 | tRpar, 302 | mkItem(itemField, ".Y"), 303 | tRight, 304 | tEOF, 305 | }}, 306 | {"trimming spaces before and after", "hello- {{- 3 -}} -world", []item{ 307 | mkItem(itemText, "hello-"), 308 | tLeft, 309 | mkItem(itemNumber, "3"), 310 | tRight, 311 | mkItem(itemText, "-world"), 312 | tEOF, 313 | }}, 314 | {"trimming spaces before and after comment", "hello- {{- /* hello */ -}} -world", []item{ 315 | mkItem(itemText, "hello-"), 316 | mkItem(itemComment, "/* hello */"), 317 | mkItem(itemText, "-world"), 318 | tEOF, 319 | }}, 320 | // errors 321 | {"badchar", "#{{\x01}}", []item{ 322 | mkItem(itemText, "#"), 323 | tLeft, 324 | mkItem(itemError, "unrecognized character in action: U+0001"), 325 | }}, 326 | {"unclosed action", "{{", []item{ 327 | tLeft, 328 | mkItem(itemError, "unclosed action"), 329 | }}, 330 | {"EOF in action", "{{range", []item{ 331 | tLeft, 332 | tRange, 333 | mkItem(itemError, "unclosed action"), 334 | }}, 335 | {"unclosed quote", "{{\"\n\"}}", []item{ 336 | tLeft, 337 | mkItem(itemError, "unterminated quoted string"), 338 | }}, 339 | {"unclosed raw quote", "{{`xx}}", []item{ 340 | tLeft, 341 | mkItem(itemError, "unterminated raw quoted string"), 342 | }}, 343 | {"unclosed char constant", "{{'\n}}", []item{ 344 | tLeft, 345 | mkItem(itemError, "unterminated character constant"), 346 | }}, 347 | {"bad number", "{{3k}}", []item{ 348 | tLeft, 349 | mkItem(itemError, `bad number syntax: "3k"`), 350 | }}, 351 | {"unclosed paren", "{{(3}}", []item{ 352 | tLeft, 353 | tLpar, 354 | mkItem(itemNumber, "3"), 355 | mkItem(itemError, `unclosed left paren`), 356 | }}, 357 | {"extra right paren", "{{3)}}", []item{ 358 | tLeft, 359 | mkItem(itemNumber, "3"), 360 | tRpar, 361 | mkItem(itemError, `unexpected right paren U+0029 ')'`), 362 | }}, 363 | 364 | // Fixed bugs 365 | // Many elements in an action blew the lookahead until 366 | // we made lexInsideAction not loop. 367 | {"long pipeline deadlock", "{{|||||}}", []item{ 368 | tLeft, 369 | tPipe, 370 | tPipe, 371 | tPipe, 372 | tPipe, 373 | tPipe, 374 | tRight, 375 | tEOF, 376 | }}, 377 | {"text with bad comment", "hello-{{/*/}}-world", []item{ 378 | mkItem(itemText, "hello-"), 379 | mkItem(itemError, `unclosed comment`), 380 | }}, 381 | {"text with comment close separated from delim", "hello-{{/* */ }}-world", []item{ 382 | mkItem(itemText, "hello-"), 383 | mkItem(itemError, `comment ends before closing delimiter`), 384 | }}, 385 | // This one is an error that we can't catch because it breaks templates with 386 | // minimized JavaScript. Should have fixed it before Go 1.1. 387 | {"unmatched right delimiter", "hello-{.}}-world", []item{ 388 | mkItem(itemText, "hello-{.}}-world"), 389 | tEOF, 390 | }}, 391 | } 392 | 393 | // collect gathers the emitted items into a slice. 394 | func collect(t *lexTest, left, right string) (items []item) { 395 | l := lex(t.name, t.input, left, right, true) 396 | for { 397 | item := l.nextItem() 398 | items = append(items, item) 399 | if item.typ == itemEOF || item.typ == itemError { 400 | break 401 | } 402 | } 403 | return 404 | } 405 | 406 | func equal(i1, i2 []item, checkPos bool) bool { 407 | if len(i1) != len(i2) { 408 | return false 409 | } 410 | for k := range i1 { 411 | if i1[k].typ != i2[k].typ { 412 | return false 413 | } 414 | if i1[k].val != i2[k].val { 415 | return false 416 | } 417 | if checkPos && i1[k].pos != i2[k].pos { 418 | return false 419 | } 420 | if checkPos && i1[k].line != i2[k].line { 421 | return false 422 | } 423 | } 424 | return true 425 | } 426 | 427 | func TestLex(t *testing.T) { 428 | for _, test := range lexTests { 429 | items := collect(&test, "", "") 430 | if !equal(items, test.items, false) { 431 | t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items) 432 | } 433 | } 434 | } 435 | 436 | // Some easy cases from above, but with delimiters $$ and @@ 437 | var lexDelimTests = []lexTest{ 438 | {"punctuation", "$$,@%{{}}@@", []item{ 439 | tLeftDelim, 440 | mkItem(itemChar, ","), 441 | mkItem(itemChar, "@"), 442 | mkItem(itemChar, "%"), 443 | mkItem(itemChar, "{"), 444 | mkItem(itemChar, "{"), 445 | mkItem(itemChar, "}"), 446 | mkItem(itemChar, "}"), 447 | tRightDelim, 448 | tEOF, 449 | }}, 450 | {"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}}, 451 | {"for", `$$for@@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}}, 452 | {"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}}, 453 | {"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}}, 454 | } 455 | 456 | var ( 457 | tLeftDelim = mkItem(itemLeftDelim, "$$") 458 | tRightDelim = mkItem(itemRightDelim, "@@") 459 | ) 460 | 461 | func TestDelims(t *testing.T) { 462 | for _, test := range lexDelimTests { 463 | items := collect(&test, "$$", "@@") 464 | if !equal(items, test.items, false) { 465 | t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) 466 | } 467 | } 468 | } 469 | 470 | var lexPosTests = []lexTest{ 471 | {"empty", "", []item{{itemEOF, 0, "", 1}}}, 472 | {"punctuation", "{{,@%#}}", []item{ 473 | {itemLeftDelim, 0, "{{", 1}, 474 | {itemChar, 2, ",", 1}, 475 | {itemChar, 3, "@", 1}, 476 | {itemChar, 4, "%", 1}, 477 | {itemChar, 5, "#", 1}, 478 | {itemRightDelim, 6, "}}", 1}, 479 | {itemEOF, 8, "", 1}, 480 | }}, 481 | {"sample", "0123{{hello}}xyz", []item{ 482 | {itemText, 0, "0123", 1}, 483 | {itemLeftDelim, 4, "{{", 1}, 484 | {itemIdentifier, 6, "hello", 1}, 485 | {itemRightDelim, 11, "}}", 1}, 486 | {itemText, 13, "xyz", 1}, 487 | {itemEOF, 16, "", 1}, 488 | }}, 489 | {"trimafter", "{{x -}}\n{{y}}", []item{ 490 | {itemLeftDelim, 0, "{{", 1}, 491 | {itemIdentifier, 2, "x", 1}, 492 | {itemRightDelim, 5, "}}", 1}, 493 | {itemLeftDelim, 8, "{{", 2}, 494 | {itemIdentifier, 10, "y", 2}, 495 | {itemRightDelim, 11, "}}", 2}, 496 | {itemEOF, 13, "", 2}, 497 | }}, 498 | {"trimbefore", "{{x}}\n{{- y}}", []item{ 499 | {itemLeftDelim, 0, "{{", 1}, 500 | {itemIdentifier, 2, "x", 1}, 501 | {itemRightDelim, 3, "}}", 1}, 502 | {itemLeftDelim, 6, "{{", 2}, 503 | {itemIdentifier, 10, "y", 2}, 504 | {itemRightDelim, 11, "}}", 2}, 505 | {itemEOF, 13, "", 2}, 506 | }}, 507 | } 508 | 509 | // The other tests don't check position, to make the test cases easier to construct. 510 | // This one does. 511 | func TestPos(t *testing.T) { 512 | for _, test := range lexPosTests { 513 | items := collect(&test, "", "") 514 | if !equal(items, test.items, true) { 515 | t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) 516 | if len(items) == len(test.items) { 517 | // Detailed print; avoid item.String() to expose the position value. 518 | for i := range items { 519 | if !equal(items[i:i+1], test.items[i:i+1], true) { 520 | i1 := items[i] 521 | i2 := test.items[i] 522 | t.Errorf("\t#%d: got {%v %d %q %d} expected {%v %d %q %d}", 523 | i, i1.typ, i1.pos, i1.val, i1.line, i2.typ, i2.pos, i2.val, i2.line) 524 | } 525 | } 526 | } 527 | } 528 | } 529 | } 530 | 531 | // Test that an error shuts down the lexing goroutine. 532 | func TestShutdown(t *testing.T) { 533 | // We need to duplicate template.Parse here to hold on to the lexer. 534 | const text = "erroneous{{define}}{{else}}1234" 535 | lexer := lex("foo", text, "{{", "}}", false) 536 | _, err := New("root").parseLexer(lexer) 537 | if err == nil { 538 | t.Fatalf("expected error") 539 | } 540 | // The error should have drained the input. Therefore, the lexer should be shut down. 541 | token, ok := <-lexer.items 542 | if ok { 543 | t.Fatalf("input was not drained; got %v", token) 544 | } 545 | } 546 | 547 | // parseLexer is a local version of parse that lets us pass in the lexer instead of building it. 548 | // We expect an error, so the tree set and funcs list are explicitly nil. 549 | func (t *Tree) parseLexer(lex *lexer) (tree *Tree, err error) { 550 | defer t.recover(&err) 551 | t.ParseName = t.Name 552 | t.startParse(nil, lex, map[string]*Tree{}) 553 | t.parse() 554 | t.add() 555 | t.stopParse() 556 | return t, nil 557 | } 558 | -------------------------------------------------------------------------------- /internal/parse/lex.go: -------------------------------------------------------------------------------- 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 | "strings" 10 | "unicode" 11 | "unicode/utf8" 12 | ) 13 | 14 | // item represents a token or text string returned from the scanner. 15 | type item struct { 16 | typ itemType // The type of this item. 17 | pos Pos // The starting position, in bytes, of this item in the input string. 18 | val string // The value of this item. 19 | line int // The line number at the start of this item. 20 | } 21 | 22 | func (i item) String() string { 23 | switch { 24 | case i.typ == itemEOF: 25 | return "EOF" 26 | case i.typ == itemError: 27 | return i.val 28 | case i.typ > itemKeyword: 29 | return fmt.Sprintf("<%s>", i.val) 30 | case len(i.val) > 10: 31 | return fmt.Sprintf("%.10q...", i.val) 32 | } 33 | return fmt.Sprintf("%q", i.val) 34 | } 35 | 36 | // itemType identifies the type of lex items. 37 | type itemType int 38 | 39 | const ( 40 | itemError itemType = iota // error occurred; value is text of error 41 | itemBool // boolean constant 42 | itemChar // printable ASCII character; grab bag for comma etc. 43 | itemCharConstant // character constant 44 | itemComment // comment text 45 | itemComplex // complex constant (1+2i); imaginary is just a number 46 | itemAssign // equals ('=') introducing an assignment 47 | itemDeclare // colon-equals (':=') introducing a declaration 48 | itemEOF 49 | itemField // alphanumeric identifier starting with '.' 50 | itemIdentifier // alphanumeric identifier not starting with '.' 51 | itemLeftDelim // left action delimiter 52 | itemLeftParen // '(' inside action 53 | itemNumber // simple number, including imaginary 54 | itemPipe // pipe symbol 55 | itemRawString // raw quoted string (includes quotes) 56 | itemRightDelim // right action delimiter 57 | itemRightParen // ')' inside action 58 | itemSpace // run of spaces separating arguments 59 | itemString // quoted string (includes quotes) 60 | itemText // plain text 61 | itemVariable // variable starting with '$', such as '$' or '$1' or '$hello' 62 | // Keywords appear after all the rest. 63 | itemKeyword // used only to delimit the keywords 64 | itemBlock // block keyword 65 | itemDot // the cursor, spelled '.' 66 | itemDefine // define keyword 67 | itemElse // else keyword 68 | itemEnd // end keyword 69 | itemIf // if keyword 70 | itemNil // the untyped nil constant, easiest to treat as a keyword 71 | itemRange // range keyword 72 | itemTemplate // template keyword 73 | itemWith // with keyword 74 | ) 75 | 76 | var key = map[string]itemType{ 77 | ".": itemDot, 78 | "block": itemBlock, 79 | "define": itemDefine, 80 | "else": itemElse, 81 | "end": itemEnd, 82 | "if": itemIf, 83 | "range": itemRange, 84 | "nil": itemNil, 85 | "template": itemTemplate, 86 | "with": itemWith, 87 | } 88 | 89 | const eof = -1 90 | 91 | // Trimming spaces. 92 | // If the action begins "{{- " rather than "{{", then all space/tab/newlines 93 | // preceding the action are trimmed; conversely if it ends " -}}" the 94 | // leading spaces are trimmed. This is done entirely in the lexer; the 95 | // parser never sees it happen. We require an ASCII space (' ', \t, \r, \n) 96 | // to be present to avoid ambiguity with things like "{{-3}}". It reads 97 | // better with the space present anyway. For simplicity, only ASCII 98 | // does the job. 99 | const ( 100 | spaceChars = " \t\r\n" // These are the space characters defined by Go itself. 101 | trimMarker = '-' // Attached to left/right delimiter, trims trailing spaces from preceding/following text. 102 | trimMarkerLen = Pos(1 + 1) // marker plus space before or after 103 | ) 104 | 105 | // stateFn represents the state of the scanner as a function that returns the next state. 106 | type stateFn func(*lexer) stateFn 107 | 108 | // lexer holds the state of the scanner. 109 | type lexer struct { 110 | name string // the name of the input; used only for error reports 111 | input string // the string being scanned 112 | leftDelim string // start of action 113 | rightDelim string // end of action 114 | emitComment bool // emit itemComment tokens. 115 | pos Pos // current position in the input 116 | start Pos // start position of this item 117 | width Pos // width of last rune read from input 118 | items chan item // channel of scanned items 119 | parenDepth int // nesting depth of ( ) exprs 120 | line int // 1+number of newlines seen 121 | startLine int // start line of this item 122 | } 123 | 124 | // next returns the next rune in the input. 125 | func (l *lexer) next() rune { 126 | if int(l.pos) >= len(l.input) { 127 | l.width = 0 128 | return eof 129 | } 130 | r, w := utf8.DecodeRuneInString(l.input[l.pos:]) 131 | l.width = Pos(w) 132 | l.pos += l.width 133 | if r == '\n' { 134 | l.line++ 135 | } 136 | return r 137 | } 138 | 139 | // peek returns but does not consume the next rune in the input. 140 | func (l *lexer) peek() rune { 141 | r := l.next() 142 | l.backup() 143 | return r 144 | } 145 | 146 | // backup steps back one rune. Can only be called once per call of next. 147 | func (l *lexer) backup() { 148 | l.pos -= l.width 149 | // Correct newline count. 150 | if l.width == 1 && l.input[l.pos] == '\n' { 151 | l.line-- 152 | } 153 | } 154 | 155 | // emit passes an item back to the client. 156 | func (l *lexer) emit(t itemType) { 157 | l.items <- item{t, l.start, l.input[l.start:l.pos], l.startLine} 158 | l.start = l.pos 159 | l.startLine = l.line 160 | } 161 | 162 | // ignore skips over the pending input before this point. 163 | func (l *lexer) ignore() { 164 | l.line += strings.Count(l.input[l.start:l.pos], "\n") 165 | l.start = l.pos 166 | l.startLine = l.line 167 | } 168 | 169 | // accept consumes the next rune if it's from the valid set. 170 | func (l *lexer) accept(valid string) bool { 171 | if strings.ContainsRune(valid, l.next()) { 172 | return true 173 | } 174 | l.backup() 175 | return false 176 | } 177 | 178 | // acceptRun consumes a run of runes from the valid set. 179 | func (l *lexer) acceptRun(valid string) { 180 | for strings.ContainsRune(valid, l.next()) { 181 | } 182 | l.backup() 183 | } 184 | 185 | // errorf returns an error token and terminates the scan by passing 186 | // back a nil pointer that will be the next state, terminating l.nextItem. 187 | func (l *lexer) errorf(format string, args ...interface{}) stateFn { 188 | l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine} 189 | return nil 190 | } 191 | 192 | // nextItem returns the next item from the input. 193 | // Called by the parser, not in the lexing goroutine. 194 | func (l *lexer) nextItem() item { 195 | return <-l.items 196 | } 197 | 198 | // drain drains the output so the lexing goroutine will exit. 199 | // Called by the parser, not in the lexing goroutine. 200 | func (l *lexer) drain() { 201 | for range l.items { 202 | } 203 | } 204 | 205 | // lex creates a new scanner for the input string. 206 | func lex(name, input, left, right string, emitComment bool) *lexer { 207 | if left == "" { 208 | left = leftDelim 209 | } 210 | if right == "" { 211 | right = rightDelim 212 | } 213 | l := &lexer{ 214 | name: name, 215 | input: input, 216 | leftDelim: left, 217 | rightDelim: right, 218 | emitComment: emitComment, 219 | items: make(chan item), 220 | line: 1, 221 | startLine: 1, 222 | } 223 | go l.run() 224 | return l 225 | } 226 | 227 | // run runs the state machine for the lexer. 228 | func (l *lexer) run() { 229 | for state := lexText; state != nil; { 230 | state = state(l) 231 | } 232 | close(l.items) 233 | } 234 | 235 | // state functions 236 | 237 | const ( 238 | leftDelim = "{{" 239 | rightDelim = "}}" 240 | leftComment = "/*" 241 | rightComment = "*/" 242 | ) 243 | 244 | // lexText scans until an opening action delimiter, "{{". 245 | func lexText(l *lexer) stateFn { 246 | l.width = 0 247 | if x := strings.Index(l.input[l.pos:], l.leftDelim); x >= 0 { 248 | ldn := Pos(len(l.leftDelim)) 249 | l.pos += Pos(x) 250 | trimLength := Pos(0) 251 | if hasLeftTrimMarker(l.input[l.pos+ldn:]) { 252 | trimLength = rightTrimLength(l.input[l.start:l.pos]) 253 | } 254 | l.pos -= trimLength 255 | if l.pos > l.start { 256 | l.line += strings.Count(l.input[l.start:l.pos], "\n") 257 | l.emit(itemText) 258 | } 259 | l.pos += trimLength 260 | l.ignore() 261 | return lexLeftDelim 262 | } 263 | l.pos = Pos(len(l.input)) 264 | // Correctly reached EOF. 265 | if l.pos > l.start { 266 | l.line += strings.Count(l.input[l.start:l.pos], "\n") 267 | l.emit(itemText) 268 | } 269 | l.emit(itemEOF) 270 | return nil 271 | } 272 | 273 | // rightTrimLength returns the length of the spaces at the end of the string. 274 | func rightTrimLength(s string) Pos { 275 | return Pos(len(s) - len(strings.TrimRight(s, spaceChars))) 276 | } 277 | 278 | // atRightDelim reports whether the lexer is at a right delimiter, possibly preceded by a trim marker. 279 | func (l *lexer) atRightDelim() (delim, trimSpaces bool) { 280 | if hasRightTrimMarker(l.input[l.pos:]) && strings.HasPrefix(l.input[l.pos+trimMarkerLen:], l.rightDelim) { // With trim marker. 281 | return true, true 282 | } 283 | if strings.HasPrefix(l.input[l.pos:], l.rightDelim) { // Without trim marker. 284 | return true, false 285 | } 286 | return false, false 287 | } 288 | 289 | // leftTrimLength returns the length of the spaces at the beginning of the string. 290 | func leftTrimLength(s string) Pos { 291 | return Pos(len(s) - len(strings.TrimLeft(s, spaceChars))) 292 | } 293 | 294 | // lexLeftDelim scans the left delimiter, which is known to be present, possibly with a trim marker. 295 | func lexLeftDelim(l *lexer) stateFn { 296 | l.pos += Pos(len(l.leftDelim)) 297 | trimSpace := hasLeftTrimMarker(l.input[l.pos:]) 298 | afterMarker := Pos(0) 299 | if trimSpace { 300 | afterMarker = trimMarkerLen 301 | } 302 | if strings.HasPrefix(l.input[l.pos+afterMarker:], leftComment) { 303 | l.pos += afterMarker 304 | l.ignore() 305 | return lexComment 306 | } 307 | l.emit(itemLeftDelim) 308 | l.pos += afterMarker 309 | l.ignore() 310 | l.parenDepth = 0 311 | return lexInsideAction 312 | } 313 | 314 | // lexComment scans a comment. The left comment marker is known to be present. 315 | func lexComment(l *lexer) stateFn { 316 | l.pos += Pos(len(leftComment)) 317 | i := strings.Index(l.input[l.pos:], rightComment) 318 | if i < 0 { 319 | return l.errorf("unclosed comment") 320 | } 321 | l.pos += Pos(i + len(rightComment)) 322 | delim, trimSpace := l.atRightDelim() 323 | if !delim { 324 | return l.errorf("comment ends before closing delimiter") 325 | } 326 | if l.emitComment { 327 | l.emit(itemComment) 328 | } 329 | if trimSpace { 330 | l.pos += trimMarkerLen 331 | } 332 | l.pos += Pos(len(l.rightDelim)) 333 | if trimSpace { 334 | l.pos += leftTrimLength(l.input[l.pos:]) 335 | } 336 | l.ignore() 337 | return lexText 338 | } 339 | 340 | // lexRightDelim scans the right delimiter, which is known to be present, possibly with a trim marker. 341 | func lexRightDelim(l *lexer) stateFn { 342 | trimSpace := hasRightTrimMarker(l.input[l.pos:]) 343 | if trimSpace { 344 | l.pos += trimMarkerLen 345 | l.ignore() 346 | } 347 | l.pos += Pos(len(l.rightDelim)) 348 | l.emit(itemRightDelim) 349 | if trimSpace { 350 | l.pos += leftTrimLength(l.input[l.pos:]) 351 | l.ignore() 352 | } 353 | return lexText 354 | } 355 | 356 | // lexInsideAction scans the elements inside action delimiters. 357 | func lexInsideAction(l *lexer) stateFn { 358 | // Either number, quoted string, or identifier. 359 | // Spaces separate arguments; runs of spaces turn into itemSpace. 360 | // Pipe symbols separate and are emitted. 361 | delim, _ := l.atRightDelim() 362 | if delim { 363 | if l.parenDepth == 0 { 364 | return lexRightDelim 365 | } 366 | return l.errorf("unclosed left paren") 367 | } 368 | switch r := l.next(); { 369 | case r == eof: 370 | return l.errorf("unclosed action") 371 | case isSpace(r): 372 | l.backup() // Put space back in case we have " -}}". 373 | return lexSpace 374 | case r == '=': 375 | l.emit(itemAssign) 376 | case r == ':': 377 | if l.next() != '=' { 378 | return l.errorf("expected :=") 379 | } 380 | l.emit(itemDeclare) 381 | case r == '|': 382 | l.emit(itemPipe) 383 | case r == '"': 384 | return lexQuote 385 | case r == '`': 386 | return lexRawQuote 387 | case r == '$': 388 | return lexVariable 389 | case r == '\'': 390 | return lexChar 391 | case r == '.': 392 | // special look-ahead for ".field" so we don't break l.backup(). 393 | if l.pos < Pos(len(l.input)) { 394 | r := l.input[l.pos] 395 | if r < '0' || '9' < r { 396 | return lexField 397 | } 398 | } 399 | fallthrough // '.' can start a number. 400 | case r == '+' || r == '-' || ('0' <= r && r <= '9'): 401 | l.backup() 402 | return lexNumber 403 | case isAlphaNumeric(r): 404 | l.backup() 405 | return lexIdentifier 406 | case r == '(': 407 | l.emit(itemLeftParen) 408 | l.parenDepth++ 409 | case r == ')': 410 | l.emit(itemRightParen) 411 | l.parenDepth-- 412 | if l.parenDepth < 0 { 413 | return l.errorf("unexpected right paren %#U", r) 414 | } 415 | case r <= unicode.MaxASCII && unicode.IsPrint(r): 416 | l.emit(itemChar) 417 | default: 418 | return l.errorf("unrecognized character in action: %#U", r) 419 | } 420 | return lexInsideAction 421 | } 422 | 423 | // lexSpace scans a run of space characters. 424 | // We have not consumed the first space, which is known to be present. 425 | // Take care if there is a trim-marked right delimiter, which starts with a space. 426 | func lexSpace(l *lexer) stateFn { 427 | var r rune 428 | var numSpaces int 429 | for { 430 | r = l.peek() 431 | if !isSpace(r) { 432 | break 433 | } 434 | l.next() 435 | numSpaces++ 436 | } 437 | // Be careful about a trim-marked closing delimiter, which has a minus 438 | // after a space. We know there is a space, so check for the '-' that might follow. 439 | if hasRightTrimMarker(l.input[l.pos-1:]) && strings.HasPrefix(l.input[l.pos-1+trimMarkerLen:], l.rightDelim) { 440 | l.backup() // Before the space. 441 | if numSpaces == 1 { 442 | return lexRightDelim // On the delim, so go right to that. 443 | } 444 | } 445 | l.emit(itemSpace) 446 | return lexInsideAction 447 | } 448 | 449 | // lexIdentifier scans an alphanumeric. 450 | func lexIdentifier(l *lexer) stateFn { 451 | Loop: 452 | for { 453 | switch r := l.next(); { 454 | case isAlphaNumeric(r): 455 | // absorb. 456 | default: 457 | l.backup() 458 | word := l.input[l.start:l.pos] 459 | if !l.atTerminator() { 460 | return l.errorf("bad character %#U", r) 461 | } 462 | switch { 463 | case key[word] > itemKeyword: 464 | l.emit(key[word]) 465 | case word[0] == '.': 466 | l.emit(itemField) 467 | case word == "true", word == "false": 468 | l.emit(itemBool) 469 | default: 470 | l.emit(itemIdentifier) 471 | } 472 | break Loop 473 | } 474 | } 475 | return lexInsideAction 476 | } 477 | 478 | // lexField scans a field: .Alphanumeric. 479 | // The . has been scanned. 480 | func lexField(l *lexer) stateFn { 481 | return lexFieldOrVariable(l, itemField) 482 | } 483 | 484 | // lexVariable scans a Variable: $Alphanumeric. 485 | // The $ has been scanned. 486 | func lexVariable(l *lexer) stateFn { 487 | if l.atTerminator() { // Nothing interesting follows -> "$". 488 | l.emit(itemVariable) 489 | return lexInsideAction 490 | } 491 | return lexFieldOrVariable(l, itemVariable) 492 | } 493 | 494 | // lexVariable scans a field or variable: [.$]Alphanumeric. 495 | // The . or $ has been scanned. 496 | func lexFieldOrVariable(l *lexer, typ itemType) stateFn { 497 | if l.atTerminator() { // Nothing interesting follows -> "." or "$". 498 | if typ == itemVariable { 499 | l.emit(itemVariable) 500 | } else { 501 | l.emit(itemDot) 502 | } 503 | return lexInsideAction 504 | } 505 | var r rune 506 | for { 507 | r = l.next() 508 | if !isAlphaNumeric(r) { 509 | l.backup() 510 | break 511 | } 512 | } 513 | if !l.atTerminator() { 514 | return l.errorf("bad character %#U", r) 515 | } 516 | l.emit(typ) 517 | return lexInsideAction 518 | } 519 | 520 | // atTerminator reports whether the input is at valid termination character to 521 | // appear after an identifier. Breaks .X.Y into two pieces. Also catches cases 522 | // like "$x+2" not being acceptable without a space, in case we decide one 523 | // day to implement arithmetic. 524 | func (l *lexer) atTerminator() bool { 525 | r := l.peek() 526 | if isSpace(r) { 527 | return true 528 | } 529 | switch r { 530 | case eof, '.', ',', '|', ':', ')', '(': 531 | return true 532 | } 533 | // Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will 534 | // succeed but should fail) but only in extremely rare cases caused by willfully 535 | // bad choice of delimiter. 536 | if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r { 537 | return true 538 | } 539 | return false 540 | } 541 | 542 | // lexChar scans a character constant. The initial quote is already 543 | // scanned. Syntax checking is done by the parser. 544 | func lexChar(l *lexer) stateFn { 545 | Loop: 546 | for { 547 | switch l.next() { 548 | case '\\': 549 | if r := l.next(); r != eof && r != '\n' { 550 | break 551 | } 552 | fallthrough 553 | case eof, '\n': 554 | return l.errorf("unterminated character constant") 555 | case '\'': 556 | break Loop 557 | } 558 | } 559 | l.emit(itemCharConstant) 560 | return lexInsideAction 561 | } 562 | 563 | // lexNumber scans a number: decimal, octal, hex, float, or imaginary. This 564 | // isn't a perfect number scanner - for instance it accepts "." and "0x0.2" 565 | // and "089" - but when it's wrong the input is invalid and the parser (via 566 | // strconv) will notice. 567 | func lexNumber(l *lexer) stateFn { 568 | if !l.scanNumber() { 569 | return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) 570 | } 571 | if sign := l.peek(); sign == '+' || sign == '-' { 572 | // Complex: 1+2i. No spaces, must end in 'i'. 573 | if !l.scanNumber() || l.input[l.pos-1] != 'i' { 574 | return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) 575 | } 576 | l.emit(itemComplex) 577 | } else { 578 | l.emit(itemNumber) 579 | } 580 | return lexInsideAction 581 | } 582 | 583 | func (l *lexer) scanNumber() bool { 584 | // Optional leading sign. 585 | l.accept("+-") 586 | // Is it hex? 587 | digits := "0123456789_" 588 | if l.accept("0") { 589 | // Note: Leading 0 does not mean octal in floats. 590 | if l.accept("xX") { 591 | digits = "0123456789abcdefABCDEF_" 592 | } else if l.accept("oO") { 593 | digits = "01234567_" 594 | } else if l.accept("bB") { 595 | digits = "01_" 596 | } 597 | } 598 | l.acceptRun(digits) 599 | if l.accept(".") { 600 | l.acceptRun(digits) 601 | } 602 | if len(digits) == 10+1 && l.accept("eE") { 603 | l.accept("+-") 604 | l.acceptRun("0123456789_") 605 | } 606 | if len(digits) == 16+6+1 && l.accept("pP") { 607 | l.accept("+-") 608 | l.acceptRun("0123456789_") 609 | } 610 | // Is it imaginary? 611 | l.accept("i") 612 | // Next thing mustn't be alphanumeric. 613 | if isAlphaNumeric(l.peek()) { 614 | l.next() 615 | return false 616 | } 617 | return true 618 | } 619 | 620 | // lexQuote scans a quoted string. 621 | func lexQuote(l *lexer) stateFn { 622 | Loop: 623 | for { 624 | switch l.next() { 625 | case '\\': 626 | if r := l.next(); r != eof && r != '\n' { 627 | break 628 | } 629 | fallthrough 630 | case eof, '\n': 631 | return l.errorf("unterminated quoted string") 632 | case '"': 633 | break Loop 634 | } 635 | } 636 | l.emit(itemString) 637 | return lexInsideAction 638 | } 639 | 640 | // lexRawQuote scans a raw quoted string. 641 | func lexRawQuote(l *lexer) stateFn { 642 | Loop: 643 | for { 644 | switch l.next() { 645 | case eof: 646 | return l.errorf("unterminated raw quoted string") 647 | case '`': 648 | break Loop 649 | } 650 | } 651 | l.emit(itemRawString) 652 | return lexInsideAction 653 | } 654 | 655 | // isSpace reports whether r is a space character. 656 | func isSpace(r rune) bool { 657 | return r == ' ' || r == '\t' || r == '\r' || r == '\n' 658 | } 659 | 660 | // isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. 661 | func isAlphaNumeric(r rune) bool { 662 | return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) 663 | } 664 | 665 | func hasLeftTrimMarker(s string) bool { 666 | return len(s) >= 2 && s[0] == trimMarker && isSpace(rune(s[1])) 667 | } 668 | 669 | func hasRightTrimMarker(s string) bool { 670 | return len(s) >= 2 && isSpace(rune(s[0])) && s[1] == trimMarker 671 | } 672 | -------------------------------------------------------------------------------- /internal/parse/parse.go: -------------------------------------------------------------------------------- 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 builds parse trees for templates as defined by text/template 6 | // and html/template. Clients should use those packages to construct templates 7 | // rather than this one, which provides shared internal data structures not 8 | // intended for general use. 9 | package parse 10 | 11 | import ( 12 | "bytes" 13 | "fmt" 14 | "runtime" 15 | "strconv" 16 | "strings" 17 | ) 18 | 19 | // Tree is the representation of a single parsed template. 20 | type Tree struct { 21 | Name string // name of the template represented by the tree. 22 | ParseName string // name of the top-level template during parsing, for error messages. 23 | Root *ListNode // top-level root of the tree. 24 | Mode Mode // parsing mode. 25 | text string // text parsed to create the template (or its parent) 26 | // Parsing only; cleared after parse. 27 | funcs []map[string]interface{} 28 | lex *lexer 29 | token [3]item // three-token lookahead for parser. 30 | peekCount int 31 | vars []string // variables defined at the moment. 32 | treeSet map[string]*Tree 33 | actionLine int // line of left delim starting action 34 | mode Mode 35 | } 36 | 37 | // A mode value is a set of flags (or 0). Modes control parser behavior. 38 | type Mode uint 39 | 40 | const ( 41 | ParseComments Mode = 1 << iota // parse comments and add them to AST 42 | SkipFuncCheck // do not check that functions are defined 43 | ) 44 | 45 | // Copy returns a copy of the Tree. Any parsing state is discarded. 46 | func (t *Tree) Copy() *Tree { 47 | if t == nil { 48 | return nil 49 | } 50 | return &Tree{ 51 | Name: t.Name, 52 | ParseName: t.ParseName, 53 | Root: t.Root.CopyList(), 54 | text: t.text, 55 | } 56 | } 57 | 58 | // Parse returns a map from template name to parse.Tree, created by parsing the 59 | // templates described in the argument string. The top-level template will be 60 | // given the specified name. If an error is encountered, parsing stops and an 61 | // empty map is returned with the error. 62 | func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (map[string]*Tree, error) { 63 | treeSet := make(map[string]*Tree) 64 | t := New(name) 65 | t.text = text 66 | _, err := t.Parse(text, leftDelim, rightDelim, treeSet, funcs...) 67 | return treeSet, err 68 | } 69 | 70 | // next returns the next token. 71 | func (t *Tree) next() item { 72 | if t.peekCount > 0 { 73 | t.peekCount-- 74 | } else { 75 | t.token[0] = t.lex.nextItem() 76 | } 77 | return t.token[t.peekCount] 78 | } 79 | 80 | // backup backs the input stream up one token. 81 | func (t *Tree) backup() { 82 | t.peekCount++ 83 | } 84 | 85 | // backup2 backs the input stream up two tokens. 86 | // The zeroth token is already there. 87 | func (t *Tree) backup2(t1 item) { 88 | t.token[1] = t1 89 | t.peekCount = 2 90 | } 91 | 92 | // backup3 backs the input stream up three tokens 93 | // The zeroth token is already there. 94 | func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back. 95 | t.token[1] = t1 96 | t.token[2] = t2 97 | t.peekCount = 3 98 | } 99 | 100 | // peek returns but does not consume the next token. 101 | func (t *Tree) peek() item { 102 | if t.peekCount > 0 { 103 | return t.token[t.peekCount-1] 104 | } 105 | t.peekCount = 1 106 | t.token[0] = t.lex.nextItem() 107 | return t.token[0] 108 | } 109 | 110 | // nextNonSpace returns the next non-space token. 111 | func (t *Tree) nextNonSpace() (token item) { 112 | for { 113 | token = t.next() 114 | if token.typ != itemSpace { 115 | break 116 | } 117 | } 118 | return token 119 | } 120 | 121 | // peekNonSpace returns but does not consume the next non-space token. 122 | func (t *Tree) peekNonSpace() item { 123 | token := t.nextNonSpace() 124 | t.backup() 125 | return token 126 | } 127 | 128 | // Parsing. 129 | 130 | // New allocates a new parse tree with the given name. 131 | func New(name string, funcs ...map[string]interface{}) *Tree { 132 | return &Tree{ 133 | Name: name, 134 | funcs: funcs, 135 | } 136 | } 137 | 138 | // ErrorContext returns a textual representation of the location of the node in the input text. 139 | // The receiver is only used when the node does not have a pointer to the tree inside, 140 | // which can occur in old code. 141 | func (t *Tree) ErrorContext(n Node) (location, context string) { 142 | pos := int(n.Position()) 143 | tree := n.tree() 144 | if tree == nil { 145 | tree = t 146 | } 147 | text := tree.text[:pos] 148 | byteNum := strings.LastIndex(text, "\n") 149 | if byteNum == -1 { 150 | byteNum = pos // On first line. 151 | } else { 152 | byteNum++ // After the newline. 153 | byteNum = pos - byteNum 154 | } 155 | lineNum := 1 + strings.Count(text, "\n") 156 | context = n.String() 157 | return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context 158 | } 159 | 160 | // errorf formats the error and terminates processing. 161 | func (t *Tree) errorf(format string, args ...interface{}) { 162 | t.Root = nil 163 | format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format) 164 | panic(fmt.Errorf(format, args...)) 165 | } 166 | 167 | // error terminates processing. 168 | func (t *Tree) error(err error) { 169 | t.errorf("%s", err) 170 | } 171 | 172 | // expect consumes the next token and guarantees it has the required type. 173 | func (t *Tree) expect(expected itemType, context string) item { 174 | token := t.nextNonSpace() 175 | if token.typ != expected { 176 | t.unexpected(token, context) 177 | } 178 | return token 179 | } 180 | 181 | // expectOneOf consumes the next token and guarantees it has one of the required types. 182 | func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { 183 | token := t.nextNonSpace() 184 | if token.typ != expected1 && token.typ != expected2 { 185 | t.unexpected(token, context) 186 | } 187 | return token 188 | } 189 | 190 | // unexpected complains about the token and terminates processing. 191 | func (t *Tree) unexpected(token item, context string) { 192 | if token.typ == itemError { 193 | extra := "" 194 | if t.actionLine != 0 && t.actionLine != token.line { 195 | extra = fmt.Sprintf(" in action started at %s:%d", t.ParseName, t.actionLine) 196 | if strings.HasSuffix(token.val, " action") { 197 | extra = extra[len(" in action"):] // avoid "action in action" 198 | } 199 | } 200 | t.errorf("%s%s", token, extra) 201 | } 202 | t.errorf("unexpected %s in %s", token, context) 203 | } 204 | 205 | // recover is the handler that turns panics into returns from the top level of Parse. 206 | func (t *Tree) recover(errp *error) { 207 | e := recover() 208 | if e != nil { 209 | if _, ok := e.(runtime.Error); ok { 210 | panic(e) 211 | } 212 | if t != nil { 213 | t.lex.drain() 214 | t.stopParse() 215 | } 216 | *errp = e.(error) 217 | } 218 | } 219 | 220 | // startParse initializes the parser, using the lexer. 221 | func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer, treeSet map[string]*Tree) { 222 | t.Root = nil 223 | t.lex = lex 224 | t.vars = []string{"$"} 225 | t.funcs = funcs 226 | t.treeSet = treeSet 227 | } 228 | 229 | // stopParse terminates parsing. 230 | func (t *Tree) stopParse() { 231 | t.lex = nil 232 | t.vars = nil 233 | t.funcs = nil 234 | t.treeSet = nil 235 | } 236 | 237 | // Parse parses the template definition string to construct a representation of 238 | // the template for execution. If either action delimiter string is empty, the 239 | // default ("{{" or "}}") is used. Embedded template definitions are added to 240 | // the treeSet map. 241 | func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) { 242 | defer t.recover(&err) 243 | t.ParseName = t.Name 244 | emitComment := t.Mode&ParseComments != 0 245 | t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim, emitComment), treeSet) 246 | t.text = text 247 | t.parse() 248 | t.add() 249 | t.stopParse() 250 | return t, nil 251 | } 252 | 253 | // add adds tree to t.treeSet. 254 | func (t *Tree) add() { 255 | tree := t.treeSet[t.Name] 256 | if tree == nil || IsEmptyTree(tree.Root) { 257 | t.treeSet[t.Name] = t 258 | return 259 | } 260 | if !IsEmptyTree(t.Root) { 261 | t.errorf("template: multiple definition of template %q", t.Name) 262 | } 263 | } 264 | 265 | // IsEmptyTree reports whether this tree (node) is empty of everything but space or comments. 266 | func IsEmptyTree(n Node) bool { 267 | switch n := n.(type) { 268 | case nil: 269 | return true 270 | case *ActionNode: 271 | case *CommentNode: 272 | return true 273 | case *IfNode: 274 | case *ListNode: 275 | for _, node := range n.Nodes { 276 | if !IsEmptyTree(node) { 277 | return false 278 | } 279 | } 280 | return true 281 | case *RangeNode: 282 | case *TemplateNode: 283 | case *TextNode: 284 | return len(bytes.TrimSpace(n.Text)) == 0 285 | case *WithNode: 286 | default: 287 | panic("unknown node: " + n.String()) 288 | } 289 | return false 290 | } 291 | 292 | // parse is the top-level parser for a template, essentially the same 293 | // as itemList except it also parses {{define}} actions. 294 | // It runs to EOF. 295 | func (t *Tree) parse() { 296 | t.Root = t.newList(t.peek().pos) 297 | for t.peek().typ != itemEOF { 298 | if t.peek().typ == itemLeftDelim { 299 | delim := t.next() 300 | if t.nextNonSpace().typ == itemDefine { 301 | newT := New("definition") // name will be updated once we know it. 302 | newT.text = t.text 303 | newT.Mode = t.Mode 304 | newT.ParseName = t.ParseName 305 | newT.startParse(t.funcs, t.lex, t.treeSet) 306 | newT.parseDefinition() 307 | continue 308 | } 309 | t.backup2(delim) 310 | } 311 | switch n := t.textOrAction(); n.Type() { 312 | case nodeEnd, nodeElse: 313 | t.errorf("unexpected %s", n) 314 | default: 315 | t.Root.append(n) 316 | } 317 | } 318 | } 319 | 320 | // parseDefinition parses a {{define}} ... {{end}} template definition and 321 | // installs the definition in t.treeSet. The "define" keyword has already 322 | // been scanned. 323 | func (t *Tree) parseDefinition() { 324 | const context = "define clause" 325 | name := t.expectOneOf(itemString, itemRawString, context) 326 | var err error 327 | t.Name, err = strconv.Unquote(name.val) 328 | if err != nil { 329 | t.error(err) 330 | } 331 | t.expect(itemRightDelim, context) 332 | var end Node 333 | t.Root, end = t.itemList() 334 | if end.Type() != nodeEnd { 335 | t.errorf("unexpected %s in %s", end, context) 336 | } 337 | t.add() 338 | t.stopParse() 339 | } 340 | 341 | // itemList: 342 | // textOrAction* 343 | // Terminates at {{end}} or {{else}}, returned separately. 344 | func (t *Tree) itemList() (list *ListNode, next Node) { 345 | list = t.newList(t.peekNonSpace().pos) 346 | for t.peekNonSpace().typ != itemEOF { 347 | n := t.textOrAction() 348 | switch n.Type() { 349 | case nodeEnd, nodeElse: 350 | return list, n 351 | } 352 | list.append(n) 353 | } 354 | t.errorf("unexpected EOF") 355 | return 356 | } 357 | 358 | // textOrAction: 359 | // text | comment | action 360 | func (t *Tree) textOrAction() Node { 361 | switch token := t.nextNonSpace(); token.typ { 362 | case itemText: 363 | return t.newText(token.pos, token.val) 364 | case itemLeftDelim: 365 | t.actionLine = token.line 366 | defer t.clearActionLine() 367 | return t.action() 368 | case itemComment: 369 | return t.newComment(token.pos, token.val) 370 | default: 371 | t.unexpected(token, "input") 372 | } 373 | return nil 374 | } 375 | 376 | func (t *Tree) clearActionLine() { 377 | t.actionLine = 0 378 | } 379 | 380 | // Action: 381 | // control 382 | // command ("|" command)* 383 | // Left delim is past. Now get actions. 384 | // First word could be a keyword such as range. 385 | func (t *Tree) action() (n Node) { 386 | switch token := t.nextNonSpace(); token.typ { 387 | case itemBlock: 388 | return t.blockControl() 389 | case itemElse: 390 | return t.elseControl() 391 | case itemEnd: 392 | return t.endControl() 393 | case itemIf: 394 | return t.ifControl() 395 | case itemRange: 396 | return t.rangeControl() 397 | case itemTemplate: 398 | return t.templateControl() 399 | case itemWith: 400 | return t.withControl() 401 | } 402 | t.backup() 403 | token := t.peek() 404 | // Do not pop variables; they persist until "end". 405 | return t.newAction(token.pos, token.line, t.pipeline("command", itemRightDelim)) 406 | } 407 | 408 | // Pipeline: 409 | // declarations? command ('|' command)* 410 | func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) { 411 | token := t.peekNonSpace() 412 | pipe = t.newPipeline(token.pos, token.line, nil) 413 | // Are there declarations or assignments? 414 | decls: 415 | if v := t.peekNonSpace(); v.typ == itemVariable { 416 | t.next() 417 | // Since space is a token, we need 3-token look-ahead here in the worst case: 418 | // in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an 419 | // argument variable rather than a declaration. So remember the token 420 | // adjacent to the variable so we can push it back if necessary. 421 | tokenAfterVariable := t.peek() 422 | next := t.peekNonSpace() 423 | switch { 424 | case next.typ == itemAssign, next.typ == itemDeclare: 425 | pipe.IsAssign = next.typ == itemAssign 426 | t.nextNonSpace() 427 | pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) 428 | t.vars = append(t.vars, v.val) 429 | case next.typ == itemChar && next.val == ",": 430 | t.nextNonSpace() 431 | pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) 432 | t.vars = append(t.vars, v.val) 433 | if context == "range" && len(pipe.Decl) < 2 { 434 | switch t.peekNonSpace().typ { 435 | case itemVariable, itemRightDelim, itemRightParen: 436 | // second initialized variable in a range pipeline 437 | goto decls 438 | default: 439 | t.errorf("range can only initialize variables") 440 | } 441 | } 442 | t.errorf("too many declarations in %s", context) 443 | case tokenAfterVariable.typ == itemSpace: 444 | t.backup3(v, tokenAfterVariable) 445 | default: 446 | t.backup2(v) 447 | } 448 | } 449 | for { 450 | switch token := t.nextNonSpace(); token.typ { 451 | case end: 452 | // At this point, the pipeline is complete 453 | t.checkPipeline(pipe, context) 454 | return 455 | case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, 456 | itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen: 457 | t.backup() 458 | pipe.append(t.command()) 459 | default: 460 | t.unexpected(token, context) 461 | } 462 | } 463 | } 464 | 465 | func (t *Tree) checkPipeline(pipe *PipeNode, context string) { 466 | // Reject empty pipelines 467 | if len(pipe.Cmds) == 0 { 468 | t.errorf("missing value for %s", context) 469 | } 470 | // Only the first command of a pipeline can start with a non executable operand 471 | for i, c := range pipe.Cmds[1:] { 472 | switch c.Args[0].Type() { 473 | case NodeBool, NodeDot, NodeNil, NodeNumber, NodeString: 474 | // With A|B|C, pipeline stage 2 is B 475 | t.errorf("non executable command in pipeline stage %d", i+2) 476 | } 477 | } 478 | } 479 | 480 | func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) { 481 | defer t.popVars(len(t.vars)) 482 | pipe = t.pipeline(context, itemRightDelim) 483 | var next Node 484 | list, next = t.itemList() 485 | switch next.Type() { 486 | case nodeEnd: //done 487 | case nodeElse: 488 | if allowElseIf { 489 | // Special case for "else if". If the "else" is followed immediately by an "if", 490 | // the elseControl will have left the "if" token pending. Treat 491 | // {{if a}}_{{else if b}}_{{end}} 492 | // as 493 | // {{if a}}_{{else}}{{if b}}_{{end}}{{end}}. 494 | // To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}} 495 | // is assumed. This technique works even for long if-else-if chains. 496 | // TODO: Should we allow else-if in with and range? 497 | if t.peek().typ == itemIf { 498 | t.next() // Consume the "if" token. 499 | elseList = t.newList(next.Position()) 500 | elseList.append(t.ifControl()) 501 | // Do not consume the next item - only one {{end}} required. 502 | break 503 | } 504 | } 505 | elseList, next = t.itemList() 506 | if next.Type() != nodeEnd { 507 | t.errorf("expected end; found %s", next) 508 | } 509 | } 510 | return pipe.Position(), pipe.Line, pipe, list, elseList 511 | } 512 | 513 | // If: 514 | // {{if pipeline}} itemList {{end}} 515 | // {{if pipeline}} itemList {{else}} itemList {{end}} 516 | // If keyword is past. 517 | func (t *Tree) ifControl() Node { 518 | return t.newIf(t.parseControl(true, "if")) 519 | } 520 | 521 | // Range: 522 | // {{range pipeline}} itemList {{end}} 523 | // {{range pipeline}} itemList {{else}} itemList {{end}} 524 | // Range keyword is past. 525 | func (t *Tree) rangeControl() Node { 526 | return t.newRange(t.parseControl(false, "range")) 527 | } 528 | 529 | // With: 530 | // {{with pipeline}} itemList {{end}} 531 | // {{with pipeline}} itemList {{else}} itemList {{end}} 532 | // If keyword is past. 533 | func (t *Tree) withControl() Node { 534 | return t.newWith(t.parseControl(false, "with")) 535 | } 536 | 537 | // End: 538 | // {{end}} 539 | // End keyword is past. 540 | func (t *Tree) endControl() Node { 541 | return t.newEnd(t.expect(itemRightDelim, "end").pos) 542 | } 543 | 544 | // Else: 545 | // {{else}} 546 | // Else keyword is past. 547 | func (t *Tree) elseControl() Node { 548 | // Special case for "else if". 549 | peek := t.peekNonSpace() 550 | if peek.typ == itemIf { 551 | // We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ". 552 | return t.newElse(peek.pos, peek.line) 553 | } 554 | token := t.expect(itemRightDelim, "else") 555 | return t.newElse(token.pos, token.line) 556 | } 557 | 558 | // Block: 559 | // {{block stringValue pipeline}} 560 | // Block keyword is past. 561 | // The name must be something that can evaluate to a string. 562 | // The pipeline is mandatory. 563 | func (t *Tree) blockControl() Node { 564 | const context = "block clause" 565 | 566 | token := t.nextNonSpace() 567 | name := t.parseTemplateName(token, context) 568 | pipe := t.pipeline(context, itemRightDelim) 569 | 570 | block := New(name) // name will be updated once we know it. 571 | block.text = t.text 572 | block.Mode = t.Mode 573 | block.ParseName = t.ParseName 574 | block.startParse(t.funcs, t.lex, t.treeSet) 575 | var end Node 576 | block.Root, end = block.itemList() 577 | if end.Type() != nodeEnd { 578 | t.errorf("unexpected %s in %s", end, context) 579 | } 580 | block.add() 581 | block.stopParse() 582 | 583 | return t.newTemplate(token.pos, token.line, name, pipe) 584 | } 585 | 586 | // Template: 587 | // {{template stringValue pipeline}} 588 | // Template keyword is past. The name must be something that can evaluate 589 | // to a string. 590 | func (t *Tree) templateControl() Node { 591 | const context = "template clause" 592 | token := t.nextNonSpace() 593 | name := t.parseTemplateName(token, context) 594 | var pipe *PipeNode 595 | if t.nextNonSpace().typ != itemRightDelim { 596 | t.backup() 597 | // Do not pop variables; they persist until "end". 598 | pipe = t.pipeline(context, itemRightDelim) 599 | } 600 | return t.newTemplate(token.pos, token.line, name, pipe) 601 | } 602 | 603 | func (t *Tree) parseTemplateName(token item, context string) (name string) { 604 | switch token.typ { 605 | case itemString, itemRawString: 606 | s, err := strconv.Unquote(token.val) 607 | if err != nil { 608 | t.error(err) 609 | } 610 | name = s 611 | default: 612 | t.unexpected(token, context) 613 | } 614 | return 615 | } 616 | 617 | // command: 618 | // operand (space operand)* 619 | // space-separated arguments up to a pipeline character or right delimiter. 620 | // we consume the pipe character but leave the right delim to terminate the action. 621 | func (t *Tree) command() *CommandNode { 622 | cmd := t.newCommand(t.peekNonSpace().pos) 623 | for { 624 | t.peekNonSpace() // skip leading spaces. 625 | operand := t.operand() 626 | if operand != nil { 627 | cmd.append(operand) 628 | } 629 | switch token := t.next(); token.typ { 630 | case itemSpace: 631 | continue 632 | case itemRightDelim, itemRightParen: 633 | t.backup() 634 | case itemPipe: 635 | // nothing here; break loop below 636 | default: 637 | t.unexpected(token, "operand") 638 | } 639 | break 640 | } 641 | if len(cmd.Args) == 0 { 642 | t.errorf("empty command") 643 | } 644 | return cmd 645 | } 646 | 647 | // operand: 648 | // term .Field* 649 | // An operand is a space-separated component of a command, 650 | // a term possibly followed by field accesses. 651 | // A nil return means the next item is not an operand. 652 | func (t *Tree) operand() Node { 653 | node := t.term() 654 | if node == nil { 655 | return nil 656 | } 657 | if t.peek().typ == itemField { 658 | chain := t.newChain(t.peek().pos, node) 659 | for t.peek().typ == itemField { 660 | chain.Add(t.next().val) 661 | } 662 | // Compatibility with original API: If the term is of type NodeField 663 | // or NodeVariable, just put more fields on the original. 664 | // Otherwise, keep the Chain node. 665 | // Obvious parsing errors involving literal values are detected here. 666 | // More complex error cases will have to be handled at execution time. 667 | switch node.Type() { 668 | case NodeField: 669 | node = t.newField(chain.Position(), chain.String()) 670 | case NodeVariable: 671 | node = t.newVariable(chain.Position(), chain.String()) 672 | case NodeBool, NodeString, NodeNumber, NodeNil, NodeDot: 673 | t.errorf("unexpected . after term %q", node.String()) 674 | default: 675 | node = chain 676 | } 677 | } 678 | return node 679 | } 680 | 681 | // term: 682 | // literal (number, string, nil, boolean) 683 | // function (identifier) 684 | // . 685 | // .Field 686 | // $ 687 | // '(' pipeline ')' 688 | // A term is a simple "expression". 689 | // A nil return means the next item is not a term. 690 | func (t *Tree) term() Node { 691 | switch token := t.nextNonSpace(); token.typ { 692 | case itemIdentifier: 693 | checkFunc := t.Mode&SkipFuncCheck == 0 694 | if checkFunc && !t.hasFunction(token.val) { 695 | t.errorf("function %q not defined", token.val) 696 | } 697 | return NewIdentifier(token.val).SetTree(t).SetPos(token.pos) 698 | case itemDot: 699 | return t.newDot(token.pos) 700 | case itemNil: 701 | return t.newNil(token.pos) 702 | case itemVariable: 703 | return t.useVar(token.pos, token.val) 704 | case itemField: 705 | return t.newField(token.pos, token.val) 706 | case itemBool: 707 | return t.newBool(token.pos, token.val == "true") 708 | case itemCharConstant, itemComplex, itemNumber: 709 | number, err := t.newNumber(token.pos, token.val, token.typ) 710 | if err != nil { 711 | t.error(err) 712 | } 713 | return number 714 | case itemLeftParen: 715 | return t.pipeline("parenthesized pipeline", itemRightParen) 716 | case itemString, itemRawString: 717 | s, err := strconv.Unquote(token.val) 718 | if err != nil { 719 | t.error(err) 720 | } 721 | return t.newString(token.pos, token.val, s) 722 | } 723 | t.backup() 724 | return nil 725 | } 726 | 727 | // hasFunction reports if a function name exists in the Tree's maps. 728 | func (t *Tree) hasFunction(name string) bool { 729 | for _, funcMap := range t.funcs { 730 | if funcMap == nil { 731 | continue 732 | } 733 | if funcMap[name] != nil { 734 | return true 735 | } 736 | } 737 | return false 738 | } 739 | 740 | // popVars trims the variable list to the specified length 741 | func (t *Tree) popVars(n int) { 742 | t.vars = t.vars[:n] 743 | } 744 | 745 | // useVar returns a node for a variable reference. It errors if the 746 | // variable is not defined. 747 | func (t *Tree) useVar(pos Pos, name string) Node { 748 | v := t.newVariable(pos, name) 749 | for _, varName := range t.vars { 750 | if varName == v.Ident[0] { 751 | return v 752 | } 753 | } 754 | t.errorf("undefined variable %q", v.Ident[0]) 755 | return nil 756 | } 757 | -------------------------------------------------------------------------------- /internal/parse/parse_test.go: -------------------------------------------------------------------------------- 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 | "flag" 9 | "fmt" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | var debug = flag.Bool("debug", false, "show the errors produced by the main tests") 15 | 16 | type numberTest struct { 17 | text string 18 | isInt bool 19 | isUint bool 20 | isFloat bool 21 | isComplex bool 22 | int64 23 | uint64 24 | float64 25 | complex128 26 | } 27 | 28 | var numberTests = []numberTest{ 29 | // basics 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}, 33 | {"7_3", true, true, true, false, 73, 73, 73, 0}, 34 | {"0b10_010_01", true, true, true, false, 73, 73, 73, 0}, 35 | {"0B10_010_01", true, true, true, false, 73, 73, 73, 0}, 36 | {"073", true, true, true, false, 073, 073, 073, 0}, 37 | {"0o73", true, true, true, false, 073, 073, 073, 0}, 38 | {"0O73", true, true, true, false, 073, 073, 073, 0}, 39 | {"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0}, 40 | {"0X73", true, true, true, false, 0x73, 0x73, 0x73, 0}, 41 | {"0x7_3", true, true, true, false, 0x73, 0x73, 0x73, 0}, 42 | {"-73", true, false, true, false, -73, 0, -73, 0}, 43 | {"+73", true, false, true, false, 73, 0, 73, 0}, 44 | {"100", true, true, true, false, 100, 100, 100, 0}, 45 | {"1e9", true, true, true, false, 1e9, 1e9, 1e9, 0}, 46 | {"-1e9", true, false, true, false, -1e9, 0, -1e9, 0}, 47 | {"-1.2", false, false, true, false, 0, 0, -1.2, 0}, 48 | {"1e19", false, true, true, false, 0, 1e19, 1e19, 0}, 49 | {"1e1_9", false, true, true, false, 0, 1e19, 1e19, 0}, 50 | {"1E19", false, true, true, false, 0, 1e19, 1e19, 0}, 51 | {"-1e19", false, false, true, false, 0, 0, -1e19, 0}, 52 | {"0x_1p4", true, true, true, false, 16, 16, 16, 0}, 53 | {"0X_1P4", true, true, true, false, 16, 16, 16, 0}, 54 | {"0x_1p-4", false, false, true, false, 0, 0, 1 / 16., 0}, 55 | {"4i", false, false, false, true, 0, 0, 0, 4i}, 56 | {"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i}, 57 | {"073i", false, false, false, true, 0, 0, 0, 73i}, // not octal! 58 | // complex with 0 imaginary are float (and maybe integer) 59 | {"0i", true, true, true, true, 0, 0, 0, 0}, 60 | {"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2}, 61 | {"-12+0i", true, false, true, true, -12, 0, -12, -12}, 62 | {"13+0i", true, true, true, true, 13, 13, 13, 13}, 63 | // funny bases 64 | {"0123", true, true, true, false, 0123, 0123, 0123, 0}, 65 | {"-0x0", true, true, true, false, 0, 0, 0, 0}, 66 | {"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0}, 67 | // character constants 68 | {`'a'`, true, true, true, false, 'a', 'a', 'a', 0}, 69 | {`'\n'`, true, true, true, false, '\n', '\n', '\n', 0}, 70 | {`'\\'`, true, true, true, false, '\\', '\\', '\\', 0}, 71 | {`'\''`, true, true, true, false, '\'', '\'', '\'', 0}, 72 | {`'\xFF'`, true, true, true, false, 0xFF, 0xFF, 0xFF, 0}, 73 | {`'パ'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, 74 | {`'\u30d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, 75 | {`'\U000030d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, 76 | // some broken syntax 77 | {text: "+-2"}, 78 | {text: "0x123."}, 79 | {text: "1e."}, 80 | {text: "0xi."}, 81 | {text: "1+2."}, 82 | {text: "'x"}, 83 | {text: "'xx'"}, 84 | {text: "'433937734937734969526500969526500'"}, // Integer too large - issue 10634. 85 | // Issue 8622 - 0xe parsed as floating point. Very embarrassing. 86 | {"0xef", true, true, true, false, 0xef, 0xef, 0xef, 0}, 87 | } 88 | 89 | func TestNumberParse(t *testing.T) { 90 | for _, test := range numberTests { 91 | // If fmt.Sscan thinks it's complex, it's complex. We can't trust the output 92 | // because imaginary comes out as a number. 93 | var c complex128 94 | typ := itemNumber 95 | var tree *Tree 96 | if test.text[0] == '\'' { 97 | typ = itemCharConstant 98 | } else { 99 | _, err := fmt.Sscan(test.text, &c) 100 | if err == nil { 101 | typ = itemComplex 102 | } 103 | } 104 | n, err := tree.newNumber(0, test.text, typ) 105 | ok := test.isInt || test.isUint || test.isFloat || test.isComplex 106 | if ok && err != nil { 107 | t.Errorf("unexpected error for %q: %s", test.text, err) 108 | continue 109 | } 110 | if !ok && err == nil { 111 | t.Errorf("expected error for %q", test.text) 112 | continue 113 | } 114 | if !ok { 115 | if *debug { 116 | fmt.Printf("%s\n\t%s\n", test.text, err) 117 | } 118 | continue 119 | } 120 | if n.IsComplex != test.isComplex { 121 | t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex) 122 | } 123 | if test.isInt { 124 | if !n.IsInt { 125 | t.Errorf("expected integer for %q", test.text) 126 | } 127 | if n.Int64 != test.int64 { 128 | t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64) 129 | } 130 | } else if n.IsInt { 131 | t.Errorf("did not expect integer for %q", test.text) 132 | } 133 | if test.isUint { 134 | if !n.IsUint { 135 | t.Errorf("expected unsigned integer for %q", test.text) 136 | } 137 | if n.Uint64 != test.uint64 { 138 | t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64) 139 | } 140 | } else if n.IsUint { 141 | t.Errorf("did not expect unsigned integer for %q", test.text) 142 | } 143 | if test.isFloat { 144 | if !n.IsFloat { 145 | t.Errorf("expected float for %q", test.text) 146 | } 147 | if n.Float64 != test.float64 { 148 | t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64) 149 | } 150 | } else if n.IsFloat { 151 | t.Errorf("did not expect float for %q", test.text) 152 | } 153 | if test.isComplex { 154 | if !n.IsComplex { 155 | t.Errorf("expected complex for %q", test.text) 156 | } 157 | if n.Complex128 != test.complex128 { 158 | t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128) 159 | } 160 | } else if n.IsComplex { 161 | t.Errorf("did not expect complex for %q", test.text) 162 | } 163 | } 164 | } 165 | 166 | type parseTest struct { 167 | name string 168 | input string 169 | ok bool 170 | result string // what the user would see in an error message. 171 | } 172 | 173 | const ( 174 | noError = true 175 | hasError = false 176 | ) 177 | 178 | var parseTests = []parseTest{ 179 | {"empty", "", noError, 180 | ``}, 181 | {"comment", "{{/*\n\n\n*/}}", noError, 182 | ``}, 183 | {"spaces", " \t\n", noError, 184 | `" \t\n"`}, 185 | {"text", "some text", noError, 186 | `"some text"`}, 187 | {"emptyAction", "{{}}", hasError, 188 | `{{}}`}, 189 | {"field", "{{.X}}", noError, 190 | `{{.X}}`}, 191 | {"simple command", "{{printf}}", noError, 192 | `{{printf}}`}, 193 | {"$ invocation", "{{$}}", noError, 194 | "{{$}}"}, 195 | {"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError, 196 | "{{with $x := 3}}{{$x 23}}{{end}}"}, 197 | {"variable with fields", "{{$.I}}", noError, 198 | "{{$.I}}"}, 199 | {"multi-word command", "{{printf `%d` 23}}", noError, 200 | "{{printf `%d` 23}}"}, 201 | {"pipeline", "{{.X|.Y}}", noError, 202 | `{{.X | .Y}}`}, 203 | {"pipeline with decl", "{{$x := .X|.Y}}", noError, 204 | `{{$x := .X | .Y}}`}, 205 | {"nested pipeline", "{{.X (.Y .Z) (.A | .B .C) (.E)}}", noError, 206 | `{{.X (.Y .Z) (.A | .B .C) (.E)}}`}, 207 | {"field applied to parentheses", "{{(.Y .Z).Field}}", noError, 208 | `{{(.Y .Z).Field}}`}, 209 | {"simple if", "{{if .X}}hello{{end}}", noError, 210 | `{{if .X}}"hello"{{end}}`}, 211 | {"if with else", "{{if .X}}true{{else}}false{{end}}", noError, 212 | `{{if .X}}"true"{{else}}"false"{{end}}`}, 213 | {"if with else if", "{{if .X}}true{{else if .Y}}false{{end}}", noError, 214 | `{{if .X}}"true"{{else}}{{if .Y}}"false"{{end}}{{end}}`}, 215 | {"if else chain", "+{{if .X}}X{{else if .Y}}Y{{else if .Z}}Z{{end}}+", noError, 216 | `"+"{{if .X}}"X"{{else}}{{if .Y}}"Y"{{else}}{{if .Z}}"Z"{{end}}{{end}}{{end}}"+"`}, 217 | {"simple range", "{{range .X}}hello{{end}}", noError, 218 | `{{range .X}}"hello"{{end}}`}, 219 | {"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError, 220 | `{{range .X.Y.Z}}"hello"{{end}}`}, 221 | {"nested range", "{{range .X}}hello{{range .Y}}goodbye{{end}}{{end}}", noError, 222 | `{{range .X}}"hello"{{range .Y}}"goodbye"{{end}}{{end}}`}, 223 | {"range with else", "{{range .X}}true{{else}}false{{end}}", noError, 224 | `{{range .X}}"true"{{else}}"false"{{end}}`}, 225 | {"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError, 226 | `{{range .X | .M}}"true"{{else}}"false"{{end}}`}, 227 | {"range []int", "{{range .SI}}{{.}}{{end}}", noError, 228 | `{{range .SI}}{{.}}{{end}}`}, 229 | {"range 1 var", "{{range $x := .SI}}{{.}}{{end}}", noError, 230 | `{{range $x := .SI}}{{.}}{{end}}`}, 231 | {"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError, 232 | `{{range $x, $y := .SI}}{{.}}{{end}}`}, 233 | {"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError, 234 | `{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`}, 235 | {"template", "{{template `x`}}", noError, 236 | `{{template "x"}}`}, 237 | {"template with arg", "{{template `x` .Y}}", noError, 238 | `{{template "x" .Y}}`}, 239 | {"with", "{{with .X}}hello{{end}}", noError, 240 | `{{with .X}}"hello"{{end}}`}, 241 | {"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError, 242 | `{{with .X}}"hello"{{else}}"goodbye"{{end}}`}, 243 | // Trimming spaces. 244 | {"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`}, 245 | {"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`}, 246 | {"trim left and right", "x \r\n\t{{- 3 -}}\n\n\ty", noError, `"x"{{3}}"y"`}, 247 | {"trim with extra spaces", "x\n{{- 3 -}}\ny", noError, `"x"{{3}}"y"`}, 248 | {"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`}, 249 | {"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`}, 250 | {"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`}, 251 | {"block definition", `{{block "foo" .}}hello{{end}}`, noError, 252 | `{{template "foo" .}}`}, 253 | 254 | {"newline in assignment", "{{ $x \n := \n 1 \n }}", noError, "{{$x := 1}}"}, 255 | {"newline in empty action", "{{\n}}", hasError, "{{\n}}"}, 256 | {"newline in pipeline", "{{\n\"x\"\n|\nprintf\n}}", noError, `{{"x" | printf}}`}, 257 | {"newline in comment", "{{/*\nhello\n*/}}", noError, ""}, 258 | {"newline in comment", "{{-\n/*\nhello\n*/\n-}}", noError, ""}, 259 | 260 | // Errors. 261 | {"unclosed action", "hello{{range", hasError, ""}, 262 | {"unmatched end", "{{end}}", hasError, ""}, 263 | {"unmatched else", "{{else}}", hasError, ""}, 264 | {"unmatched else after if", "{{if .X}}hello{{end}}{{else}}", hasError, ""}, 265 | {"multiple else", "{{if .X}}1{{else}}2{{else}}3{{end}}", hasError, ""}, 266 | {"missing end", "hello{{range .x}}", hasError, ""}, 267 | {"missing end after else", "hello{{range .x}}{{else}}", hasError, ""}, 268 | {"undefined function", "hello{{undefined}}", hasError, ""}, 269 | {"undefined variable", "{{$x}}", hasError, ""}, 270 | {"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""}, 271 | {"variable undefined in template", "{{template $v}}", hasError, ""}, 272 | {"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""}, 273 | {"template with field ref", "{{template .X}}", hasError, ""}, 274 | {"template with var", "{{template $v}}", hasError, ""}, 275 | {"invalid punctuation", "{{printf 3, 4}}", hasError, ""}, 276 | {"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""}, 277 | {"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""}, 278 | {"dot applied to parentheses", "{{printf (printf .).}}", hasError, ""}, 279 | {"adjacent args", "{{printf 3`x`}}", hasError, ""}, 280 | {"adjacent args with .", "{{printf `x`.}}", hasError, ""}, 281 | {"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""}, 282 | // Other kinds of assignments and operators aren't available yet. 283 | {"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"}, 284 | {"bug0b", "{{$x += 1}}{{$x}}", hasError, ""}, 285 | {"bug0c", "{{$x ! 2}}{{$x}}", hasError, ""}, 286 | {"bug0d", "{{$x % 3}}{{$x}}", hasError, ""}, 287 | // Check the parse fails for := rather than comma. 288 | {"bug0e", "{{range $x := $y := 3}}{{end}}", hasError, ""}, 289 | // Another bug: variable read must ignore following punctuation. 290 | {"bug1a", "{{$x:=.}}{{$x!2}}", hasError, ""}, // ! is just illegal here. 291 | {"bug1b", "{{$x:=.}}{{$x+2}}", hasError, ""}, // $x+2 should not parse as ($x) (+2). 292 | {"bug1c", "{{$x:=.}}{{$x +2}}", noError, "{{$x := .}}{{$x +2}}"}, // It's OK with a space. 293 | // dot following a literal value 294 | {"dot after integer", "{{1.E}}", hasError, ""}, 295 | {"dot after float", "{{0.1.E}}", hasError, ""}, 296 | {"dot after boolean", "{{true.E}}", hasError, ""}, 297 | {"dot after char", "{{'a'.any}}", hasError, ""}, 298 | {"dot after string", `{{"hello".guys}}`, hasError, ""}, 299 | {"dot after dot", "{{..E}}", hasError, ""}, 300 | {"dot after nil", "{{nil.E}}", hasError, ""}, 301 | // Wrong pipeline 302 | {"wrong pipeline dot", "{{12|.}}", hasError, ""}, 303 | {"wrong pipeline number", "{{.|12|printf}}", hasError, ""}, 304 | {"wrong pipeline string", "{{.|printf|\"error\"}}", hasError, ""}, 305 | {"wrong pipeline char", "{{12|printf|'e'}}", hasError, ""}, 306 | {"wrong pipeline boolean", "{{.|true}}", hasError, ""}, 307 | {"wrong pipeline nil", "{{'c'|nil}}", hasError, ""}, 308 | {"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""}, 309 | // Missing pipeline in block 310 | {"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""}, 311 | } 312 | 313 | var builtins = map[string]interface{}{ 314 | "printf": fmt.Sprintf, 315 | "contains": strings.Contains, 316 | } 317 | 318 | func testParse(doCopy bool, t *testing.T) { 319 | textFormat = "%q" 320 | defer func() { textFormat = "%s" }() 321 | for _, test := range parseTests { 322 | tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins) 323 | switch { 324 | case err == nil && !test.ok: 325 | t.Errorf("%q: expected error; got none", test.name) 326 | continue 327 | case err != nil && test.ok: 328 | t.Errorf("%q: unexpected error: %v", test.name, err) 329 | continue 330 | case err != nil && !test.ok: 331 | // expected error, got one 332 | if *debug { 333 | fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) 334 | } 335 | continue 336 | } 337 | var result string 338 | if doCopy { 339 | result = tmpl.Root.Copy().String() 340 | } else { 341 | result = tmpl.Root.String() 342 | } 343 | if result != test.result { 344 | t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result) 345 | } 346 | } 347 | } 348 | 349 | func TestParse(t *testing.T) { 350 | testParse(false, t) 351 | } 352 | 353 | // Same as TestParse, but we copy the node first 354 | func TestParseCopy(t *testing.T) { 355 | testParse(true, t) 356 | } 357 | 358 | func TestParseWithComments(t *testing.T) { 359 | textFormat = "%q" 360 | defer func() { textFormat = "%s" }() 361 | tests := [...]parseTest{ 362 | {"comment", "{{/*\n\n\n*/}}", noError, "{{/*\n\n\n*/}}"}, 363 | {"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"{{/* hi */}}`}, 364 | {"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `{{/* hi */}}"y"`}, 365 | {"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x"{{/* */}}"y"`}, 366 | } 367 | for _, test := range tests { 368 | t.Run(test.name, func(t *testing.T) { 369 | tr := New(test.name) 370 | tr.Mode = ParseComments 371 | tmpl, err := tr.Parse(test.input, "", "", make(map[string]*Tree)) 372 | if err != nil { 373 | t.Errorf("%q: expected error; got none", test.name) 374 | } 375 | if result := tmpl.Root.String(); result != test.result { 376 | t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result) 377 | } 378 | }) 379 | } 380 | } 381 | 382 | func TestSkipFuncCheck(t *testing.T) { 383 | oldTextFormat := textFormat 384 | textFormat = "%q" 385 | defer func() { textFormat = oldTextFormat }() 386 | tr := New("skip func check") 387 | tr.Mode = SkipFuncCheck 388 | tmpl, err := tr.Parse("{{fn 1 2}}", "", "", make(map[string]*Tree)) 389 | if err != nil { 390 | t.Fatalf("unexpected error: %v", err) 391 | } 392 | expected := "{{fn 1 2}}" 393 | if result := tmpl.Root.String(); result != expected { 394 | t.Errorf("got\n\t%v\nexpected\n\t%v", result, expected) 395 | } 396 | } 397 | 398 | type isEmptyTest struct { 399 | name string 400 | input string 401 | empty bool 402 | } 403 | 404 | var isEmptyTests = []isEmptyTest{ 405 | {"empty", ``, true}, 406 | {"nonempty", `hello`, false}, 407 | {"spaces only", " \t\n \t\n", true}, 408 | {"comment only", "{{/* comment */}}", true}, 409 | {"definition", `{{define "x"}}something{{end}}`, true}, 410 | {"definitions and space", "{{define `x`}}something{{end}}\n\n{{define `y`}}something{{end}}\n\n", true}, 411 | {"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n", false}, 412 | {"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false}, 413 | } 414 | 415 | func TestIsEmpty(t *testing.T) { 416 | if !IsEmptyTree(nil) { 417 | t.Errorf("nil tree is not empty") 418 | } 419 | for _, test := range isEmptyTests { 420 | tree, err := New("root").Parse(test.input, "", "", make(map[string]*Tree), nil) 421 | if err != nil { 422 | t.Errorf("%q: unexpected error: %v", test.name, err) 423 | continue 424 | } 425 | if empty := IsEmptyTree(tree.Root); empty != test.empty { 426 | t.Errorf("%q: expected %t got %t", test.name, test.empty, empty) 427 | } 428 | } 429 | } 430 | 431 | func TestErrorContextWithTreeCopy(t *testing.T) { 432 | tree, err := New("root").Parse("{{if true}}{{end}}", "", "", make(map[string]*Tree), nil) 433 | if err != nil { 434 | t.Fatalf("unexpected tree parse failure: %v", err) 435 | } 436 | treeCopy := tree.Copy() 437 | wantLocation, wantContext := tree.ErrorContext(tree.Root.Nodes[0]) 438 | gotLocation, gotContext := treeCopy.ErrorContext(treeCopy.Root.Nodes[0]) 439 | if wantLocation != gotLocation { 440 | t.Errorf("wrong error location want %q got %q", wantLocation, gotLocation) 441 | } 442 | if wantContext != gotContext { 443 | t.Errorf("wrong error location want %q got %q", wantContext, gotContext) 444 | } 445 | } 446 | 447 | // All failures, and the result is a string that must appear in the error message. 448 | var errorTests = []parseTest{ 449 | // Check line numbers are accurate. 450 | {"unclosed1", 451 | "line1\n{{", 452 | hasError, `unclosed1:2: unclosed action`}, 453 | {"unclosed2", 454 | "line1\n{{define `x`}}line2\n{{", 455 | hasError, `unclosed2:3: unclosed action`}, 456 | {"unclosed3", 457 | "line1\n{{\"x\"\n\"y\"\n", 458 | hasError, `unclosed3:4: unclosed action started at unclosed3:2`}, 459 | {"unclosed4", 460 | "{{\n\n\n\n\n", 461 | hasError, `unclosed4:6: unclosed action started at unclosed4:1`}, 462 | {"var1", 463 | "line1\n{{\nx\n}}", 464 | hasError, `var1:3: function "x" not defined`}, 465 | // Specific errors. 466 | {"function", 467 | "{{foo}}", 468 | hasError, `function "foo" not defined`}, 469 | {"comment1", 470 | "{{/*}}", 471 | hasError, `comment1:1: unclosed comment`}, 472 | {"comment2", 473 | "{{/*\nhello\n}}", 474 | hasError, `comment2:1: unclosed comment`}, 475 | {"lparen", 476 | "{{.X (1 2 3}}", 477 | hasError, `unclosed left paren`}, 478 | {"rparen", 479 | "{{.X 1 2 3 ) }}", 480 | hasError, `unexpected ")" in command`}, 481 | {"rparen2", 482 | "{{(.X 1 2 3", 483 | hasError, `unclosed action`}, 484 | {"space", 485 | "{{`x`3}}", 486 | hasError, `in operand`}, 487 | {"idchar", 488 | "{{a#}}", 489 | hasError, `'#'`}, 490 | {"charconst", 491 | "{{'a}}", 492 | hasError, `unterminated character constant`}, 493 | {"stringconst", 494 | `{{"a}}`, 495 | hasError, `unterminated quoted string`}, 496 | {"rawstringconst", 497 | "{{`a}}", 498 | hasError, `unterminated raw quoted string`}, 499 | {"number", 500 | "{{0xi}}", 501 | hasError, `number syntax`}, 502 | {"multidefine", 503 | "{{define `a`}}a{{end}}{{define `a`}}b{{end}}", 504 | hasError, `multiple definition of template`}, 505 | {"eof", 506 | "{{range .X}}", 507 | hasError, `unexpected EOF`}, 508 | {"variable", 509 | // Declare $x so it's defined, to avoid that error, and then check we don't parse a declaration. 510 | "{{$x := 23}}{{with $x.y := 3}}{{$x 23}}{{end}}", 511 | hasError, `unexpected ":="`}, 512 | {"multidecl", 513 | "{{$a,$b,$c := 23}}", 514 | hasError, `too many declarations`}, 515 | {"undefvar", 516 | "{{$a}}", 517 | hasError, `undefined variable`}, 518 | {"wrongdot", 519 | "{{true.any}}", 520 | hasError, `unexpected . after term`}, 521 | {"wrongpipeline", 522 | "{{12|false}}", 523 | hasError, `non executable command in pipeline`}, 524 | {"emptypipeline", 525 | `{{ ( ) }}`, 526 | hasError, `missing value for parenthesized pipeline`}, 527 | {"multilinerawstring", 528 | "{{ $v := `\n` }} {{", 529 | hasError, `multilinerawstring:2: unclosed action`}, 530 | {"rangeundefvar", 531 | "{{range $k}}{{end}}", 532 | hasError, `undefined variable`}, 533 | {"rangeundefvars", 534 | "{{range $k, $v}}{{end}}", 535 | hasError, `undefined variable`}, 536 | {"rangemissingvalue1", 537 | "{{range $k,}}{{end}}", 538 | hasError, `missing value for range`}, 539 | {"rangemissingvalue2", 540 | "{{range $k, $v := }}{{end}}", 541 | hasError, `missing value for range`}, 542 | {"rangenotvariable1", 543 | "{{range $k, .}}{{end}}", 544 | hasError, `range can only initialize variables`}, 545 | {"rangenotvariable2", 546 | "{{range $k, 123 := .}}{{end}}", 547 | hasError, `range can only initialize variables`}, 548 | } 549 | 550 | func TestErrors(t *testing.T) { 551 | for _, test := range errorTests { 552 | t.Run(test.name, func(t *testing.T) { 553 | _, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree)) 554 | if err == nil { 555 | t.Fatalf("expected error %q, got nil", test.result) 556 | } 557 | if !strings.Contains(err.Error(), test.result) { 558 | t.Fatalf("error %q does not contain %q", err, test.result) 559 | } 560 | }) 561 | } 562 | } 563 | 564 | func TestBlock(t *testing.T) { 565 | const ( 566 | input = `a{{block "inner" .}}bar{{.}}baz{{end}}b` 567 | outer = `a{{template "inner" .}}b` 568 | inner = `bar{{.}}baz` 569 | ) 570 | treeSet := make(map[string]*Tree) 571 | tmpl, err := New("outer").Parse(input, "", "", treeSet, nil) 572 | if err != nil { 573 | t.Fatal(err) 574 | } 575 | if g, w := tmpl.Root.String(), outer; g != w { 576 | t.Errorf("outer template = %q, want %q", g, w) 577 | } 578 | inTmpl := treeSet["inner"] 579 | if inTmpl == nil { 580 | t.Fatal("block did not define template") 581 | } 582 | if g, w := inTmpl.Root.String(), inner; g != w { 583 | t.Errorf("inner template = %q, want %q", g, w) 584 | } 585 | } 586 | 587 | func TestLineNum(t *testing.T) { 588 | const count = 100 589 | text := strings.Repeat("{{printf 1234}}\n", count) 590 | tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins) 591 | if err != nil { 592 | t.Fatal(err) 593 | } 594 | // Check the line numbers. Each line is an action containing a template, followed by text. 595 | // That's two nodes per line. 596 | nodes := tree.Root.Nodes 597 | for i := 0; i < len(nodes); i += 2 { 598 | line := 1 + i/2 599 | // Action first. 600 | action := nodes[i].(*ActionNode) 601 | if action.Line != line { 602 | t.Fatalf("line %d: action is line %d", line, action.Line) 603 | } 604 | pipe := action.Pipe 605 | if pipe.Line != line { 606 | t.Fatalf("line %d: pipe is line %d", line, pipe.Line) 607 | } 608 | } 609 | } 610 | 611 | func BenchmarkParseLarge(b *testing.B) { 612 | text := strings.Repeat("{{1234}}\n", 10000) 613 | for i := 0; i < b.N; i++ { 614 | _, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins) 615 | if err != nil { 616 | b.Fatal(err) 617 | } 618 | } 619 | } 620 | 621 | var sinkv, sinkl string 622 | 623 | func BenchmarkVariableString(b *testing.B) { 624 | v := &VariableNode{ 625 | Ident: []string{"$", "A", "BB", "CCC", "THIS_IS_THE_VARIABLE_BEING_PROCESSED"}, 626 | } 627 | b.ResetTimer() 628 | b.ReportAllocs() 629 | for i := 0; i < b.N; i++ { 630 | sinkv = v.String() 631 | } 632 | if sinkv == "" { 633 | b.Fatal("Benchmark was not run") 634 | } 635 | } 636 | 637 | func BenchmarkListString(b *testing.B) { 638 | text := ` 639 | {{(printf .Field1.Field2.Field3).Value}} 640 | {{$x := (printf .Field1.Field2.Field3).Value}} 641 | {{$y := (printf $x.Field1.Field2.Field3).Value}} 642 | {{$z := $y.Field1.Field2.Field3}} 643 | {{if contains $y $z}} 644 | {{printf "%q" $y}} 645 | {{else}} 646 | {{printf "%q" $x}} 647 | {{end}} 648 | {{with $z.Field1 | contains "boring"}} 649 | {{printf "%q" . | printf "%s"}} 650 | {{else}} 651 | {{printf "%d %d %d" 11 11 11}} 652 | {{printf "%d %d %s" 22 22 $x.Field1.Field2.Field3 | printf "%s"}} 653 | {{printf "%v" (contains $z.Field1.Field2 $y)}} 654 | {{end}} 655 | ` 656 | tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins) 657 | if err != nil { 658 | b.Fatal(err) 659 | } 660 | b.ResetTimer() 661 | b.ReportAllocs() 662 | for i := 0; i < b.N; i++ { 663 | sinkl = tree.Root.String() 664 | } 665 | if sinkl == "" { 666 | b.Fatal("Benchmark was not run") 667 | } 668 | } 669 | -------------------------------------------------------------------------------- /internal/parse/node.go: -------------------------------------------------------------------------------- 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 | // Parse nodes. 6 | 7 | package parse 8 | 9 | import ( 10 | "fmt" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | var textFormat = "%s" // Changed to "%q" in tests for better error messages. 16 | 17 | // A Node is an element in the parse tree. The interface is trivial. 18 | // The interface contains an unexported method so that only 19 | // types local to this package can satisfy it. 20 | type Node interface { 21 | Type() NodeType 22 | String() string 23 | // Copy does a deep copy of the Node and all its components. 24 | // To avoid type assertions, some XxxNodes also have specialized 25 | // CopyXxx methods that return *XxxNode. 26 | Copy() Node 27 | Position() Pos // byte position of start of node in full original input string 28 | // tree returns the containing *Tree. 29 | // It is unexported so all implementations of Node are in this package. 30 | tree() *Tree 31 | // writeTo writes the String output to the builder. 32 | writeTo(*strings.Builder) 33 | } 34 | 35 | // NodeType identifies the type of a parse tree node. 36 | type NodeType int 37 | 38 | // Pos represents a byte position in the original input text from which 39 | // this template was parsed. 40 | type Pos int 41 | 42 | func (p Pos) Position() Pos { 43 | return p 44 | } 45 | 46 | // Type returns itself and provides an easy default implementation 47 | // for embedding in a Node. Embedded in all non-trivial Nodes. 48 | func (t NodeType) Type() NodeType { 49 | return t 50 | } 51 | 52 | const ( 53 | NodeText NodeType = iota // Plain text. 54 | NodeAction // A non-control action such as a field evaluation. 55 | NodeBool // A boolean constant. 56 | NodeChain // A sequence of field accesses. 57 | NodeCommand // An element of a pipeline. 58 | NodeDot // The cursor, dot. 59 | nodeElse // An else action. Not added to tree. 60 | nodeEnd // An end action. Not added to tree. 61 | NodeField // A field or method name. 62 | NodeIdentifier // An identifier; always a function name. 63 | NodeIf // An if action. 64 | NodeList // A list of Nodes. 65 | NodeNil // An untyped nil constant. 66 | NodeNumber // A numerical constant. 67 | NodePipe // A pipeline of commands. 68 | NodeRange // A range action. 69 | NodeString // A string constant. 70 | NodeTemplate // A template invocation action. 71 | NodeVariable // A $ variable. 72 | NodeWith // A with action. 73 | NodeComment // A comment. 74 | ) 75 | 76 | // Nodes. 77 | 78 | // ListNode holds a sequence of nodes. 79 | type ListNode struct { 80 | NodeType 81 | Pos 82 | tr *Tree 83 | Nodes []Node // The element nodes in lexical order. 84 | } 85 | 86 | func (t *Tree) newList(pos Pos) *ListNode { 87 | return &ListNode{tr: t, NodeType: NodeList, Pos: pos} 88 | } 89 | 90 | func (l *ListNode) append(n Node) { 91 | l.Nodes = append(l.Nodes, n) 92 | } 93 | 94 | func (l *ListNode) tree() *Tree { 95 | return l.tr 96 | } 97 | 98 | func (l *ListNode) String() string { 99 | var sb strings.Builder 100 | l.writeTo(&sb) 101 | return sb.String() 102 | } 103 | 104 | func (l *ListNode) writeTo(sb *strings.Builder) { 105 | for _, n := range l.Nodes { 106 | n.writeTo(sb) 107 | } 108 | } 109 | 110 | func (l *ListNode) CopyList() *ListNode { 111 | if l == nil { 112 | return l 113 | } 114 | n := l.tr.newList(l.Pos) 115 | for _, elem := range l.Nodes { 116 | n.append(elem.Copy()) 117 | } 118 | return n 119 | } 120 | 121 | func (l *ListNode) Copy() Node { 122 | return l.CopyList() 123 | } 124 | 125 | // TextNode holds plain text. 126 | type TextNode struct { 127 | NodeType 128 | Pos 129 | tr *Tree 130 | Text []byte // The text; may span newlines. 131 | } 132 | 133 | func (t *Tree) newText(pos Pos, text string) *TextNode { 134 | return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)} 135 | } 136 | 137 | func (t *TextNode) String() string { 138 | return fmt.Sprintf(textFormat, t.Text) 139 | } 140 | 141 | func (t *TextNode) writeTo(sb *strings.Builder) { 142 | sb.WriteString(t.String()) 143 | } 144 | 145 | func (t *TextNode) tree() *Tree { 146 | return t.tr 147 | } 148 | 149 | func (t *TextNode) Copy() Node { 150 | return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)} 151 | } 152 | 153 | // CommentNode holds a comment. 154 | type CommentNode struct { 155 | NodeType 156 | Pos 157 | tr *Tree 158 | Text string // Comment text. 159 | } 160 | 161 | func (t *Tree) newComment(pos Pos, text string) *CommentNode { 162 | return &CommentNode{tr: t, NodeType: NodeComment, Pos: pos, Text: text} 163 | } 164 | 165 | func (c *CommentNode) String() string { 166 | var sb strings.Builder 167 | c.writeTo(&sb) 168 | return sb.String() 169 | } 170 | 171 | func (c *CommentNode) writeTo(sb *strings.Builder) { 172 | sb.WriteString("{{") 173 | sb.WriteString(c.Text) 174 | sb.WriteString("}}") 175 | } 176 | 177 | func (c *CommentNode) tree() *Tree { 178 | return c.tr 179 | } 180 | 181 | func (c *CommentNode) Copy() Node { 182 | return &CommentNode{tr: c.tr, NodeType: NodeComment, Pos: c.Pos, Text: c.Text} 183 | } 184 | 185 | // PipeNode holds a pipeline with optional declaration 186 | type PipeNode struct { 187 | NodeType 188 | Pos 189 | tr *Tree 190 | Line int // The line number in the input. Deprecated: Kept for compatibility. 191 | IsAssign bool // The variables are being assigned, not declared. 192 | Decl []*VariableNode // Variables in lexical order. 193 | Cmds []*CommandNode // The commands in lexical order. 194 | } 195 | 196 | func (t *Tree) newPipeline(pos Pos, line int, vars []*VariableNode) *PipeNode { 197 | return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: vars} 198 | } 199 | 200 | func (p *PipeNode) append(command *CommandNode) { 201 | p.Cmds = append(p.Cmds, command) 202 | } 203 | 204 | func (p *PipeNode) String() string { 205 | var sb strings.Builder 206 | p.writeTo(&sb) 207 | return sb.String() 208 | } 209 | 210 | func (p *PipeNode) writeTo(sb *strings.Builder) { 211 | if len(p.Decl) > 0 { 212 | for i, v := range p.Decl { 213 | if i > 0 { 214 | sb.WriteString(", ") 215 | } 216 | v.writeTo(sb) 217 | } 218 | sb.WriteString(" := ") 219 | } 220 | for i, c := range p.Cmds { 221 | if i > 0 { 222 | sb.WriteString(" | ") 223 | } 224 | c.writeTo(sb) 225 | } 226 | } 227 | 228 | func (p *PipeNode) tree() *Tree { 229 | return p.tr 230 | } 231 | 232 | func (p *PipeNode) CopyPipe() *PipeNode { 233 | if p == nil { 234 | return p 235 | } 236 | vars := make([]*VariableNode, len(p.Decl)) 237 | for i, d := range p.Decl { 238 | vars[i] = d.Copy().(*VariableNode) 239 | } 240 | n := p.tr.newPipeline(p.Pos, p.Line, vars) 241 | n.IsAssign = p.IsAssign 242 | for _, c := range p.Cmds { 243 | n.append(c.Copy().(*CommandNode)) 244 | } 245 | return n 246 | } 247 | 248 | func (p *PipeNode) Copy() Node { 249 | return p.CopyPipe() 250 | } 251 | 252 | // ActionNode holds an action (something bounded by delimiters). 253 | // Control actions have their own nodes; ActionNode represents simple 254 | // ones such as field evaluations and parenthesized pipelines. 255 | type ActionNode struct { 256 | NodeType 257 | Pos 258 | tr *Tree 259 | Line int // The line number in the input. Deprecated: Kept for compatibility. 260 | Pipe *PipeNode // The pipeline in the action. 261 | } 262 | 263 | func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode { 264 | return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe} 265 | } 266 | 267 | func (a *ActionNode) String() string { 268 | var sb strings.Builder 269 | a.writeTo(&sb) 270 | return sb.String() 271 | } 272 | 273 | func (a *ActionNode) writeTo(sb *strings.Builder) { 274 | sb.WriteString("{{") 275 | a.Pipe.writeTo(sb) 276 | sb.WriteString("}}") 277 | } 278 | 279 | func (a *ActionNode) tree() *Tree { 280 | return a.tr 281 | } 282 | 283 | func (a *ActionNode) Copy() Node { 284 | return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe()) 285 | 286 | } 287 | 288 | // CommandNode holds a command (a pipeline inside an evaluating action). 289 | type CommandNode struct { 290 | NodeType 291 | Pos 292 | tr *Tree 293 | Args []Node // Arguments in lexical order: Identifier, field, or constant. 294 | } 295 | 296 | func (t *Tree) newCommand(pos Pos) *CommandNode { 297 | return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos} 298 | } 299 | 300 | func (c *CommandNode) append(arg Node) { 301 | c.Args = append(c.Args, arg) 302 | } 303 | 304 | func (c *CommandNode) String() string { 305 | var sb strings.Builder 306 | c.writeTo(&sb) 307 | return sb.String() 308 | } 309 | 310 | func (c *CommandNode) writeTo(sb *strings.Builder) { 311 | for i, arg := range c.Args { 312 | if i > 0 { 313 | sb.WriteByte(' ') 314 | } 315 | if arg, ok := arg.(*PipeNode); ok { 316 | sb.WriteByte('(') 317 | arg.writeTo(sb) 318 | sb.WriteByte(')') 319 | continue 320 | } 321 | arg.writeTo(sb) 322 | } 323 | } 324 | 325 | func (c *CommandNode) tree() *Tree { 326 | return c.tr 327 | } 328 | 329 | func (c *CommandNode) Copy() Node { 330 | if c == nil { 331 | return c 332 | } 333 | n := c.tr.newCommand(c.Pos) 334 | for _, c := range c.Args { 335 | n.append(c.Copy()) 336 | } 337 | return n 338 | } 339 | 340 | // IdentifierNode holds an identifier. 341 | type IdentifierNode struct { 342 | NodeType 343 | Pos 344 | tr *Tree 345 | Ident string // The identifier's name. 346 | } 347 | 348 | // NewIdentifier returns a new IdentifierNode with the given identifier name. 349 | func NewIdentifier(ident string) *IdentifierNode { 350 | return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident} 351 | } 352 | 353 | // SetPos sets the position. NewIdentifier is a public method so we can't modify its signature. 354 | // Chained for convenience. 355 | // TODO: fix one day? 356 | func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode { 357 | i.Pos = pos 358 | return i 359 | } 360 | 361 | // SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature. 362 | // Chained for convenience. 363 | // TODO: fix one day? 364 | func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode { 365 | i.tr = t 366 | return i 367 | } 368 | 369 | func (i *IdentifierNode) String() string { 370 | return i.Ident 371 | } 372 | 373 | func (i *IdentifierNode) writeTo(sb *strings.Builder) { 374 | sb.WriteString(i.String()) 375 | } 376 | 377 | func (i *IdentifierNode) tree() *Tree { 378 | return i.tr 379 | } 380 | 381 | func (i *IdentifierNode) Copy() Node { 382 | return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos) 383 | } 384 | 385 | // VariableNode holds a list of variable names, possibly with chained field 386 | // accesses. The dollar sign is part of the (first) name. 387 | type VariableNode struct { 388 | NodeType 389 | Pos 390 | tr *Tree 391 | Ident []string // Variable name and fields in lexical order. 392 | } 393 | 394 | func (t *Tree) newVariable(pos Pos, ident string) *VariableNode { 395 | return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")} 396 | } 397 | 398 | func (v *VariableNode) String() string { 399 | var sb strings.Builder 400 | v.writeTo(&sb) 401 | return sb.String() 402 | } 403 | 404 | func (v *VariableNode) writeTo(sb *strings.Builder) { 405 | for i, id := range v.Ident { 406 | if i > 0 { 407 | sb.WriteByte('.') 408 | } 409 | sb.WriteString(id) 410 | } 411 | } 412 | 413 | func (v *VariableNode) tree() *Tree { 414 | return v.tr 415 | } 416 | 417 | func (v *VariableNode) Copy() Node { 418 | return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)} 419 | } 420 | 421 | // DotNode holds the special identifier '.'. 422 | type DotNode struct { 423 | NodeType 424 | Pos 425 | tr *Tree 426 | } 427 | 428 | func (t *Tree) newDot(pos Pos) *DotNode { 429 | return &DotNode{tr: t, NodeType: NodeDot, Pos: pos} 430 | } 431 | 432 | func (d *DotNode) Type() NodeType { 433 | // Override method on embedded NodeType for API compatibility. 434 | // TODO: Not really a problem; could change API without effect but 435 | // api tool complains. 436 | return NodeDot 437 | } 438 | 439 | func (d *DotNode) String() string { 440 | return "." 441 | } 442 | 443 | func (d *DotNode) writeTo(sb *strings.Builder) { 444 | sb.WriteString(d.String()) 445 | } 446 | 447 | func (d *DotNode) tree() *Tree { 448 | return d.tr 449 | } 450 | 451 | func (d *DotNode) Copy() Node { 452 | return d.tr.newDot(d.Pos) 453 | } 454 | 455 | // NilNode holds the special identifier 'nil' representing an untyped nil constant. 456 | type NilNode struct { 457 | NodeType 458 | Pos 459 | tr *Tree 460 | } 461 | 462 | func (t *Tree) newNil(pos Pos) *NilNode { 463 | return &NilNode{tr: t, NodeType: NodeNil, Pos: pos} 464 | } 465 | 466 | func (n *NilNode) Type() NodeType { 467 | // Override method on embedded NodeType for API compatibility. 468 | // TODO: Not really a problem; could change API without effect but 469 | // api tool complains. 470 | return NodeNil 471 | } 472 | 473 | func (n *NilNode) String() string { 474 | return "nil" 475 | } 476 | 477 | func (n *NilNode) writeTo(sb *strings.Builder) { 478 | sb.WriteString(n.String()) 479 | } 480 | 481 | func (n *NilNode) tree() *Tree { 482 | return n.tr 483 | } 484 | 485 | func (n *NilNode) Copy() Node { 486 | return n.tr.newNil(n.Pos) 487 | } 488 | 489 | // FieldNode holds a field (identifier starting with '.'). 490 | // The names may be chained ('.x.y'). 491 | // The period is dropped from each ident. 492 | type FieldNode struct { 493 | NodeType 494 | Pos 495 | tr *Tree 496 | Ident []string // The identifiers in lexical order. 497 | } 498 | 499 | func (t *Tree) newField(pos Pos, ident string) *FieldNode { 500 | return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period 501 | } 502 | 503 | func (f *FieldNode) String() string { 504 | var sb strings.Builder 505 | f.writeTo(&sb) 506 | return sb.String() 507 | } 508 | 509 | func (f *FieldNode) writeTo(sb *strings.Builder) { 510 | for _, id := range f.Ident { 511 | sb.WriteByte('.') 512 | sb.WriteString(id) 513 | } 514 | } 515 | 516 | func (f *FieldNode) tree() *Tree { 517 | return f.tr 518 | } 519 | 520 | func (f *FieldNode) Copy() Node { 521 | return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)} 522 | } 523 | 524 | // ChainNode holds a term followed by a chain of field accesses (identifier starting with '.'). 525 | // The names may be chained ('.x.y'). 526 | // The periods are dropped from each ident. 527 | type ChainNode struct { 528 | NodeType 529 | Pos 530 | tr *Tree 531 | Node Node 532 | Field []string // The identifiers in lexical order. 533 | } 534 | 535 | func (t *Tree) newChain(pos Pos, node Node) *ChainNode { 536 | return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node} 537 | } 538 | 539 | // Add adds the named field (which should start with a period) to the end of the chain. 540 | func (c *ChainNode) Add(field string) { 541 | if len(field) == 0 || field[0] != '.' { 542 | panic("no dot in field") 543 | } 544 | field = field[1:] // Remove leading dot. 545 | if field == "" { 546 | panic("empty field") 547 | } 548 | c.Field = append(c.Field, field) 549 | } 550 | 551 | func (c *ChainNode) String() string { 552 | var sb strings.Builder 553 | c.writeTo(&sb) 554 | return sb.String() 555 | } 556 | 557 | func (c *ChainNode) writeTo(sb *strings.Builder) { 558 | if _, ok := c.Node.(*PipeNode); ok { 559 | sb.WriteByte('(') 560 | c.Node.writeTo(sb) 561 | sb.WriteByte(')') 562 | } else { 563 | c.Node.writeTo(sb) 564 | } 565 | for _, field := range c.Field { 566 | sb.WriteByte('.') 567 | sb.WriteString(field) 568 | } 569 | } 570 | 571 | func (c *ChainNode) tree() *Tree { 572 | return c.tr 573 | } 574 | 575 | func (c *ChainNode) Copy() Node { 576 | return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)} 577 | } 578 | 579 | // BoolNode holds a boolean constant. 580 | type BoolNode struct { 581 | NodeType 582 | Pos 583 | tr *Tree 584 | True bool // The value of the boolean constant. 585 | } 586 | 587 | func (t *Tree) newBool(pos Pos, true bool) *BoolNode { 588 | return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true} 589 | } 590 | 591 | func (b *BoolNode) String() string { 592 | if b.True { 593 | return "true" 594 | } 595 | return "false" 596 | } 597 | 598 | func (b *BoolNode) writeTo(sb *strings.Builder) { 599 | sb.WriteString(b.String()) 600 | } 601 | 602 | func (b *BoolNode) tree() *Tree { 603 | return b.tr 604 | } 605 | 606 | func (b *BoolNode) Copy() Node { 607 | return b.tr.newBool(b.Pos, b.True) 608 | } 609 | 610 | // NumberNode holds a number: signed or unsigned integer, float, or complex. 611 | // The value is parsed and stored under all the types that can represent the value. 612 | // This simulates in a small amount of code the behavior of Go's ideal constants. 613 | type NumberNode struct { 614 | NodeType 615 | Pos 616 | tr *Tree 617 | IsInt bool // Number has an integral value. 618 | IsUint bool // Number has an unsigned integral value. 619 | IsFloat bool // Number has a floating-point value. 620 | IsComplex bool // Number is complex. 621 | Int64 int64 // The signed integer value. 622 | Uint64 uint64 // The unsigned integer value. 623 | Float64 float64 // The floating-point value. 624 | Complex128 complex128 // The complex value. 625 | Text string // The original textual representation from the input. 626 | } 627 | 628 | func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) { 629 | n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text} 630 | switch typ { 631 | case itemCharConstant: 632 | rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0]) 633 | if err != nil { 634 | return nil, err 635 | } 636 | if tail != "'" { 637 | return nil, fmt.Errorf("malformed character constant: %s", text) 638 | } 639 | n.Int64 = int64(rune) 640 | n.IsInt = true 641 | n.Uint64 = uint64(rune) 642 | n.IsUint = true 643 | n.Float64 = float64(rune) // odd but those are the rules. 644 | n.IsFloat = true 645 | return n, nil 646 | case itemComplex: 647 | // fmt.Sscan can parse the pair, so let it do the work. 648 | if _, err := fmt.Sscan(text, &n.Complex128); err != nil { 649 | return nil, err 650 | } 651 | n.IsComplex = true 652 | n.simplifyComplex() 653 | return n, nil 654 | } 655 | // Imaginary constants can only be complex unless they are zero. 656 | if len(text) > 0 && text[len(text)-1] == 'i' { 657 | f, err := strconv.ParseFloat(text[:len(text)-1], 64) 658 | if err == nil { 659 | n.IsComplex = true 660 | n.Complex128 = complex(0, f) 661 | n.simplifyComplex() 662 | return n, nil 663 | } 664 | } 665 | // Do integer test first so we get 0x123 etc. 666 | u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below. 667 | if err == nil { 668 | n.IsUint = true 669 | n.Uint64 = u 670 | } 671 | i, err := strconv.ParseInt(text, 0, 64) 672 | if err == nil { 673 | n.IsInt = true 674 | n.Int64 = i 675 | if i == 0 { 676 | n.IsUint = true // in case of -0. 677 | n.Uint64 = u 678 | } 679 | } 680 | // If an integer extraction succeeded, promote the float. 681 | if n.IsInt { 682 | n.IsFloat = true 683 | n.Float64 = float64(n.Int64) 684 | } else if n.IsUint { 685 | n.IsFloat = true 686 | n.Float64 = float64(n.Uint64) 687 | } else { 688 | f, err := strconv.ParseFloat(text, 64) 689 | if err == nil { 690 | // If we parsed it as a float but it looks like an integer, 691 | // it's a huge number too large to fit in an int. Reject it. 692 | if !strings.ContainsAny(text, ".eEpP") { 693 | return nil, fmt.Errorf("integer overflow: %q", text) 694 | } 695 | n.IsFloat = true 696 | n.Float64 = f 697 | // If a floating-point extraction succeeded, extract the int if needed. 698 | if !n.IsInt && float64(int64(f)) == f { 699 | n.IsInt = true 700 | n.Int64 = int64(f) 701 | } 702 | if !n.IsUint && float64(uint64(f)) == f { 703 | n.IsUint = true 704 | n.Uint64 = uint64(f) 705 | } 706 | } 707 | } 708 | if !n.IsInt && !n.IsUint && !n.IsFloat { 709 | return nil, fmt.Errorf("illegal number syntax: %q", text) 710 | } 711 | return n, nil 712 | } 713 | 714 | // simplifyComplex pulls out any other types that are represented by the complex number. 715 | // These all require that the imaginary part be zero. 716 | func (n *NumberNode) simplifyComplex() { 717 | n.IsFloat = imag(n.Complex128) == 0 718 | if n.IsFloat { 719 | n.Float64 = real(n.Complex128) 720 | n.IsInt = float64(int64(n.Float64)) == n.Float64 721 | if n.IsInt { 722 | n.Int64 = int64(n.Float64) 723 | } 724 | n.IsUint = float64(uint64(n.Float64)) == n.Float64 725 | if n.IsUint { 726 | n.Uint64 = uint64(n.Float64) 727 | } 728 | } 729 | } 730 | 731 | func (n *NumberNode) String() string { 732 | return n.Text 733 | } 734 | 735 | func (n *NumberNode) writeTo(sb *strings.Builder) { 736 | sb.WriteString(n.String()) 737 | } 738 | 739 | func (n *NumberNode) tree() *Tree { 740 | return n.tr 741 | } 742 | 743 | func (n *NumberNode) Copy() Node { 744 | nn := new(NumberNode) 745 | *nn = *n // Easy, fast, correct. 746 | return nn 747 | } 748 | 749 | // StringNode holds a string constant. The value has been "unquoted". 750 | type StringNode struct { 751 | NodeType 752 | Pos 753 | tr *Tree 754 | Quoted string // The original text of the string, with quotes. 755 | Text string // The string, after quote processing. 756 | } 757 | 758 | func (t *Tree) newString(pos Pos, orig, text string) *StringNode { 759 | return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text} 760 | } 761 | 762 | func (s *StringNode) String() string { 763 | return s.Quoted 764 | } 765 | 766 | func (s *StringNode) writeTo(sb *strings.Builder) { 767 | sb.WriteString(s.String()) 768 | } 769 | 770 | func (s *StringNode) tree() *Tree { 771 | return s.tr 772 | } 773 | 774 | func (s *StringNode) Copy() Node { 775 | return s.tr.newString(s.Pos, s.Quoted, s.Text) 776 | } 777 | 778 | // endNode represents an {{end}} action. 779 | // It does not appear in the final parse tree. 780 | type endNode struct { 781 | NodeType 782 | Pos 783 | tr *Tree 784 | } 785 | 786 | func (t *Tree) newEnd(pos Pos) *endNode { 787 | return &endNode{tr: t, NodeType: nodeEnd, Pos: pos} 788 | } 789 | 790 | func (e *endNode) String() string { 791 | return "{{end}}" 792 | } 793 | 794 | func (e *endNode) writeTo(sb *strings.Builder) { 795 | sb.WriteString(e.String()) 796 | } 797 | 798 | func (e *endNode) tree() *Tree { 799 | return e.tr 800 | } 801 | 802 | func (e *endNode) Copy() Node { 803 | return e.tr.newEnd(e.Pos) 804 | } 805 | 806 | // elseNode represents an {{else}} action. Does not appear in the final tree. 807 | type elseNode struct { 808 | NodeType 809 | Pos 810 | tr *Tree 811 | Line int // The line number in the input. Deprecated: Kept for compatibility. 812 | } 813 | 814 | func (t *Tree) newElse(pos Pos, line int) *elseNode { 815 | return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line} 816 | } 817 | 818 | func (e *elseNode) Type() NodeType { 819 | return nodeElse 820 | } 821 | 822 | func (e *elseNode) String() string { 823 | return "{{else}}" 824 | } 825 | 826 | func (e *elseNode) writeTo(sb *strings.Builder) { 827 | sb.WriteString(e.String()) 828 | } 829 | 830 | func (e *elseNode) tree() *Tree { 831 | return e.tr 832 | } 833 | 834 | func (e *elseNode) Copy() Node { 835 | return e.tr.newElse(e.Pos, e.Line) 836 | } 837 | 838 | // BranchNode is the common representation of if, range, and with. 839 | type BranchNode struct { 840 | NodeType 841 | Pos 842 | tr *Tree 843 | Line int // The line number in the input. Deprecated: Kept for compatibility. 844 | Pipe *PipeNode // The pipeline to be evaluated. 845 | List *ListNode // What to execute if the value is non-empty. 846 | ElseList *ListNode // What to execute if the value is empty (nil if absent). 847 | } 848 | 849 | func (b *BranchNode) String() string { 850 | var sb strings.Builder 851 | b.writeTo(&sb) 852 | return sb.String() 853 | } 854 | 855 | func (b *BranchNode) writeTo(sb *strings.Builder) { 856 | name := "" 857 | switch b.NodeType { 858 | case NodeIf: 859 | name = "if" 860 | case NodeRange: 861 | name = "range" 862 | case NodeWith: 863 | name = "with" 864 | default: 865 | panic("unknown branch type") 866 | } 867 | sb.WriteString("{{") 868 | sb.WriteString(name) 869 | sb.WriteByte(' ') 870 | b.Pipe.writeTo(sb) 871 | sb.WriteString("}}") 872 | b.List.writeTo(sb) 873 | if b.ElseList != nil { 874 | sb.WriteString("{{else}}") 875 | b.ElseList.writeTo(sb) 876 | } 877 | sb.WriteString("{{end}}") 878 | } 879 | 880 | func (b *BranchNode) tree() *Tree { 881 | return b.tr 882 | } 883 | 884 | func (b *BranchNode) Copy() Node { 885 | switch b.NodeType { 886 | case NodeIf: 887 | return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) 888 | case NodeRange: 889 | return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) 890 | case NodeWith: 891 | return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) 892 | default: 893 | panic("unknown branch type") 894 | } 895 | } 896 | 897 | // IfNode represents an {{if}} action and its commands. 898 | type IfNode struct { 899 | BranchNode 900 | } 901 | 902 | func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode { 903 | return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} 904 | } 905 | 906 | func (i *IfNode) Copy() Node { 907 | return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList()) 908 | } 909 | 910 | // RangeNode represents a {{range}} action and its commands. 911 | type RangeNode struct { 912 | BranchNode 913 | } 914 | 915 | func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode { 916 | return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} 917 | } 918 | 919 | func (r *RangeNode) Copy() Node { 920 | return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList()) 921 | } 922 | 923 | // WithNode represents a {{with}} action and its commands. 924 | type WithNode struct { 925 | BranchNode 926 | } 927 | 928 | func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode { 929 | return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} 930 | } 931 | 932 | func (w *WithNode) Copy() Node { 933 | return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList()) 934 | } 935 | 936 | // TemplateNode represents a {{template}} action. 937 | type TemplateNode struct { 938 | NodeType 939 | Pos 940 | tr *Tree 941 | Line int // The line number in the input. Deprecated: Kept for compatibility. 942 | Name string // The name of the template (unquoted). 943 | Pipe *PipeNode // The command to evaluate as dot for the template. 944 | } 945 | 946 | func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode { 947 | return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe} 948 | } 949 | 950 | func (t *TemplateNode) String() string { 951 | var sb strings.Builder 952 | t.writeTo(&sb) 953 | return sb.String() 954 | } 955 | 956 | func (t *TemplateNode) writeTo(sb *strings.Builder) { 957 | sb.WriteString("{{template ") 958 | sb.WriteString(strconv.Quote(t.Name)) 959 | if t.Pipe != nil { 960 | sb.WriteByte(' ') 961 | t.Pipe.writeTo(sb) 962 | } 963 | sb.WriteString("}}") 964 | } 965 | 966 | func (t *TemplateNode) tree() *Tree { 967 | return t.tr 968 | } 969 | 970 | func (t *TemplateNode) Copy() Node { 971 | return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe()) 972 | } 973 | --------------------------------------------------------------------------------