this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue/parser: add package

also exports DebugStr in internal package

Change-Id: I61b4099d8cd0aa9a471bd8343f6b369feb2ba736

+3739
+23
cue/parser/doc.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // Package parser implements a parser for CUE source files. Input may be 16 + // provided in a variety of forms (see the various Parse* functions); the output 17 + // is an abstract syntax tree (AST) representing the CUE source. The parser is 18 + // invoked through one of the Parse* functions. 19 + // 20 + // The parser accepts a larger language than is syntactically permitted by the 21 + // CUE spec, for simplicity, and for improved robustness in the presence of 22 + // syntax errors. 23 + package parser // import "cuelang.org/go/cue/parser"
+198
cue/parser/error_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // This file implements a parser test harness. The files in the testdata 16 + // directory are parsed and the errors reported are compared against the 17 + // error messages expected in the test files. The test files must end in 18 + // .src rather than .go so that they are not disturbed by gofmt runs. 19 + // 20 + // Expected errors are indicated in the test files by putting a comment 21 + // of the form /* ERROR "rx" */ immediately following an offending 22 + // The harness will verify that an error matching the regular expression 23 + // rx is reported at that source position. 24 + // 25 + // For instance, the following test file indicates that a "not declared" 26 + // error should be reported for the undeclared variable x: 27 + // 28 + // package p 29 + // { 30 + // a = x /* ERROR "not declared" */ + 1 31 + // } 32 + 33 + package parser 34 + 35 + import ( 36 + "io/ioutil" 37 + "path/filepath" 38 + "regexp" 39 + "strings" 40 + "testing" 41 + 42 + "cuelang.org/go/cue/errors" 43 + "cuelang.org/go/cue/scanner" 44 + "cuelang.org/go/cue/token" 45 + ) 46 + 47 + const testdata = "testdata" 48 + 49 + // getFile assumes that each filename occurs at most once 50 + func getFile(fset *token.FileSet, filename string) (info *token.File) { 51 + fset.Iterate(func(f *token.File) bool { 52 + if f.Name() == filename { 53 + if info != nil { 54 + panic(filename + " used multiple times") 55 + } 56 + info = f 57 + } 58 + return true 59 + }) 60 + return info 61 + } 62 + 63 + func getPos(fset *token.FileSet, filename string, offset int) token.Pos { 64 + if f := getFile(fset, filename); f != nil { 65 + return f.Pos(offset, 0) 66 + } 67 + return token.NoPos 68 + } 69 + 70 + // ERROR comments must be of the form /* ERROR "rx" */ and rx is 71 + // a regular expression that matches the expected error message. 72 + // The special form /* ERROR HERE "rx" */ must be used for error 73 + // messages that appear immediately after a token, rather than at 74 + // a token's position. 75 + // 76 + var errRx = regexp.MustCompile(`^/\* *ERROR *(HERE)? *"([^"]*)" *\*/$`) 77 + 78 + // expectedErrors collects the regular expressions of ERROR comments found 79 + // in files and returns them as a map of error positions to error messages. 80 + // 81 + func expectedErrors(t *testing.T, fset *token.FileSet, filename string, src []byte) map[token.Pos]string { 82 + errors := make(map[token.Pos]string) 83 + 84 + var s scanner.Scanner 85 + // file was parsed already - do not add it again to the file 86 + // set otherwise the position information returned here will 87 + // not match the position information collected by the parser 88 + s.Init(getFile(fset, filename), src, nil, scanner.ScanComments) 89 + var prev token.Pos // position of last non-comment, non-semicolon token 90 + var here token.Pos // position immediately after the token at position prev 91 + 92 + for { 93 + pos, tok, lit := s.Scan() 94 + pos = pos.WithRel(0) 95 + switch tok { 96 + case token.EOF: 97 + return errors 98 + case token.COMMENT: 99 + s := errRx.FindStringSubmatch(lit) 100 + if len(s) == 3 { 101 + pos := prev 102 + if s[1] == "HERE" { 103 + pos = here 104 + } 105 + errors[pos] = string(s[2]) 106 + } 107 + default: 108 + prev = pos 109 + var l int // token length 110 + if tok.IsLiteral() { 111 + l = len(lit) 112 + } else { 113 + l = len(tok.String()) 114 + } 115 + here = prev + token.Pos(l) 116 + } 117 + } 118 + } 119 + 120 + // compareErrors compares the map of expected error messages with the list 121 + // of found errors and reports discrepancies. 122 + // 123 + func compareErrors(t *testing.T, fset *token.FileSet, expected map[token.Pos]string, found errors.List) { 124 + t.Helper() 125 + for _, error := range found { 126 + // error.Pos is a Position, but we want 127 + // a Pos so we can do a map lookup 128 + ePos := error.Position() 129 + eMsg := error.Error() 130 + pos := getPos(fset, ePos.Filename, ePos.Offset).WithRel(0) 131 + if msg, found := expected[pos]; found { 132 + // we expect a message at pos; check if it matches 133 + rx, err := regexp.Compile(msg) 134 + if err != nil { 135 + t.Errorf("%s: %v", ePos, err) 136 + continue 137 + } 138 + if match := rx.MatchString(eMsg); !match { 139 + t.Errorf("%s: %q does not match %q", ePos, eMsg, msg) 140 + continue 141 + } 142 + // we have a match - eliminate this error 143 + delete(expected, pos) 144 + } else { 145 + // To keep in mind when analyzing failed test output: 146 + // If the same error position occurs multiple times in errors, 147 + // this message will be triggered (because the first error at 148 + // the position removes this position from the expected errors). 149 + t.Errorf("%s: unexpected error: -%q-", ePos, eMsg) 150 + } 151 + } 152 + 153 + // there should be no expected errors left 154 + if len(expected) > 0 { 155 + t.Errorf("%d errors not reported:", len(expected)) 156 + for pos, msg := range expected { 157 + t.Errorf("%s: -%q-\n", fset.Position(pos), msg) 158 + } 159 + } 160 + } 161 + 162 + func checkErrors(t *testing.T, filename string, input interface{}) { 163 + t.Helper() 164 + src, err := readSource(filename, input) 165 + if err != nil { 166 + t.Error(err) 167 + return 168 + } 169 + 170 + fset := token.NewFileSet() 171 + _, err = ParseFile(fset, filename, src, DeclarationErrors, AllErrors, ParseLambdas) 172 + found, ok := err.(errors.List) 173 + if err != nil && !ok { 174 + t.Error(err) 175 + return 176 + } 177 + found.RemoveMultiples() 178 + 179 + // we are expecting the following errors 180 + // (collect these after parsing a file so that it is found in the file set) 181 + expected := expectedErrors(t, fset, filename, src) 182 + 183 + // verify errors returned by the parser 184 + compareErrors(t, fset, expected, found) 185 + } 186 + 187 + func TestErrors(t *testing.T) { 188 + list, err := ioutil.ReadDir(testdata) 189 + if err != nil { 190 + t.Fatal(err) 191 + } 192 + for _, fi := range list { 193 + name := fi.Name() 194 + if !fi.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".src") { 195 + checkErrors(t, filepath.Join(testdata, name), nil) 196 + } 197 + } 198 + }
+41
cue/parser/example_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package parser_test 16 + 17 + import ( 18 + "fmt" 19 + 20 + "cuelang.org/go/cue/parser" 21 + "cuelang.org/go/cue/token" 22 + ) 23 + 24 + func ExampleParseFile() { 25 + fset := token.NewFileSet() // positions are relative to fset 26 + 27 + // Parse the file containing this very example 28 + // but stop after processing the imports. 29 + f, err := parser.ParseFile(fset, "testdata/test.cue", nil) 30 + if err != nil { 31 + fmt.Println(err) 32 + return 33 + } 34 + 35 + // Print the imports from the file's AST. 36 + for _, s := range f.Imports { 37 + fmt.Println(s.Path.Value) 38 + } 39 + // Output: 40 + // "math" 41 + }
+169
cue/parser/import.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package parser 16 + 17 + import ( 18 + "sort" 19 + "strconv" 20 + 21 + "cuelang.org/go/cue/ast" 22 + "cuelang.org/go/cue/token" 23 + ) 24 + 25 + // sortImports sorts runs of consecutive import lines in import blocks in f. 26 + // It also removes duplicate imports when it is possible to do so without data loss. 27 + func sortImports(fset *token.FileSet, f *ast.File) { 28 + for _, d := range f.Decls { 29 + d, ok := d.(*ast.ImportDecl) 30 + if !ok { 31 + // Not an import declaration, so we're done. 32 + // Imports are always first. 33 + break 34 + } 35 + 36 + if !d.Lparen.IsValid() { 37 + // Not a block: sorted by default. 38 + continue 39 + } 40 + 41 + // Identify and sort runs of specs on successive lines. 42 + i := 0 43 + specs := d.Specs[:0] 44 + for j, s := range d.Specs { 45 + if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line { 46 + // j begins a new run. End this one. 47 + specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) 48 + i = j 49 + } 50 + } 51 + specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) 52 + d.Specs = specs 53 + 54 + // Deduping can leave a blank line before the rparen; clean that up. 55 + if len(d.Specs) > 0 { 56 + lastSpec := d.Specs[len(d.Specs)-1] 57 + lastLine := fset.Position(lastSpec.Pos()).Line 58 + rParenLine := fset.Position(d.Rparen).Line 59 + for rParenLine > lastLine+1 { 60 + rParenLine-- 61 + fset.File(d.Rparen).MergeLine(rParenLine) 62 + } 63 + } 64 + } 65 + } 66 + 67 + func importPath(s *ast.ImportSpec) string { 68 + t, err := strconv.Unquote(s.Path.Value) 69 + if err == nil { 70 + return t 71 + } 72 + return "" 73 + } 74 + 75 + func importName(s *ast.ImportSpec) string { 76 + n := s.Name 77 + if n == nil { 78 + return "" 79 + } 80 + return n.Name 81 + } 82 + 83 + func importComment(s *ast.ImportSpec) string { 84 + for _, c := range s.Comments() { 85 + if c.Line { 86 + return c.Text() 87 + } 88 + } 89 + return "" 90 + } 91 + 92 + // collapse indicates whether prev may be removed, leaving only next. 93 + func collapse(prev, next *ast.ImportSpec) bool { 94 + if importPath(next) != importPath(prev) || importName(next) != importName(prev) { 95 + return false 96 + } 97 + for _, c := range prev.Comments() { 98 + if !c.Doc { 99 + return false 100 + } 101 + } 102 + return true 103 + } 104 + 105 + type posSpan struct { 106 + Start token.Pos 107 + End token.Pos 108 + } 109 + 110 + func sortSpecs(fset *token.FileSet, f *ast.File, specs []*ast.ImportSpec) []*ast.ImportSpec { 111 + // Can't short-circuit here even if specs are already sorted, 112 + // since they might yet need deduplication. 113 + // A lone import, however, may be safely ignored. 114 + if len(specs) <= 1 { 115 + return specs 116 + } 117 + 118 + // Record positions for specs. 119 + pos := make([]posSpan, len(specs)) 120 + for i, s := range specs { 121 + pos[i] = posSpan{s.Pos(), s.End()} 122 + } 123 + 124 + // Sort the import specs by import path. 125 + // Remove duplicates, when possible without data loss. 126 + // Reassign the import paths to have the same position sequence. 127 + // Reassign each comment to abut the end of its spec. 128 + // Sort the comments by new position. 129 + sort.Sort(byImportSpec(specs)) 130 + 131 + // Dedup. Thanks to our sorting, we can just consider 132 + // adjacent pairs of imports. 133 + deduped := specs[:0] 134 + for i, s := range specs { 135 + if i == len(specs)-1 || !collapse(s, specs[i+1]) { 136 + deduped = append(deduped, s) 137 + } else { 138 + p := s.Pos() 139 + fset.File(p).MergeLine(fset.Position(p).Line) 140 + } 141 + } 142 + specs = deduped 143 + 144 + return specs 145 + } 146 + 147 + type byImportSpec []*ast.ImportSpec 148 + 149 + func (x byImportSpec) Len() int { return len(x) } 150 + func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 151 + func (x byImportSpec) Less(i, j int) bool { 152 + ipath := importPath(x[i]) 153 + jpath := importPath(x[j]) 154 + if ipath != jpath { 155 + return ipath < jpath 156 + } 157 + iname := importName(x[i]) 158 + jname := importName(x[j]) 159 + if iname != jname { 160 + return iname < jname 161 + } 162 + return importComment(x[i]) < importComment(x[j]) 163 + } 164 + 165 + type byCommentPos []*ast.CommentGroup 166 + 167 + func (x byCommentPos) Len() int { return len(x) } 168 + func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 169 + func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() }
+250
cue/parser/interface.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // This file contains the exported entry points for invoking the 16 + 17 + package parser 18 + 19 + import ( 20 + "bytes" 21 + "fmt" 22 + "io" 23 + "io/ioutil" 24 + 25 + "cuelang.org/go/cue/ast" 26 + "cuelang.org/go/cue/token" 27 + ) 28 + 29 + // If src != nil, readSource converts src to a []byte if possible; 30 + // otherwise it returns an error. If src == nil, readSource returns 31 + // the result of reading the file specified by filename. 32 + // 33 + func readSource(filename string, src interface{}) ([]byte, error) { 34 + if src != nil { 35 + switch s := src.(type) { 36 + case string: 37 + return []byte(s), nil 38 + case []byte: 39 + return s, nil 40 + case *bytes.Buffer: 41 + // is io.Reader, but src is already available in []byte form 42 + if s != nil { 43 + return s.Bytes(), nil 44 + } 45 + case io.Reader: 46 + var buf bytes.Buffer 47 + if _, err := io.Copy(&buf, s); err != nil { 48 + return nil, err 49 + } 50 + return buf.Bytes(), nil 51 + } 52 + return nil, fmt.Errorf("invalid source type %T", src) 53 + } 54 + return ioutil.ReadFile(filename) 55 + } 56 + 57 + type Option func(p *parser) 58 + 59 + var ( 60 + // PackageClauseOnly causes parsing to stop after the package clause. 61 + PackageClauseOnly Option = packageClauseOnly 62 + packageClauseOnly = func(p *parser) { 63 + p.mode |= packageClauseOnlyMode 64 + } 65 + 66 + // ImportsOnly causes parsing to stop parsing after the import declarations. 67 + ImportsOnly Option = importsOnly 68 + importsOnly = func(p *parser) { 69 + p.mode |= importsOnlyMode 70 + } 71 + 72 + // ParseComments causes comments to be parsed. 73 + ParseComments Option = parseComments 74 + parseComments = func(p *parser) { 75 + p.mode |= parseCommentsMode 76 + } 77 + 78 + // ParseLambdas enables parsing of Lambdas. By default these are disabled. 79 + // 80 + // NOTE: this option is for internal use only and can be made unavailable at 81 + // any time. 82 + ParseLambdas Option = parseLambdas 83 + parseLambdas = func(p *parser) { 84 + p.mode |= parseLambdasMode 85 + } 86 + 87 + // Trace causes parsing to print a trace of parsed productions. 88 + Trace Option = traceOpt 89 + traceOpt = func(p *parser) { 90 + p.mode |= traceMode 91 + } 92 + 93 + // DeclarationErrors causes parsing to report declaration errors. 94 + DeclarationErrors Option = declarationErrors 95 + declarationErrors = func(p *parser) { 96 + p.mode |= declarationErrorsMode 97 + } 98 + 99 + // AllErrors causes all errors to be reported (not just the first 10 on different lines). 100 + AllErrors Option = allErrors 101 + allErrors = func(p *parser) { 102 + p.mode |= allErrorsMode 103 + } 104 + 105 + // AllowPartial allows the parser to be used on a prefix buffer. 106 + AllowPartial Option = allowPartial 107 + allowPartial = func(p *parser) { 108 + p.mode |= partialMode 109 + } 110 + ) 111 + 112 + // A mode value is a set of flags (or 0). 113 + // They control the amount of source code parsed and other optional 114 + // parser functionality. 115 + type mode uint 116 + 117 + const ( 118 + packageClauseOnlyMode mode = 1 << iota // stop parsing after package clause 119 + importsOnlyMode // stop parsing after import declarations 120 + parseCommentsMode // parse comments and add them to AST 121 + parseLambdasMode 122 + partialMode 123 + traceMode // print a trace of parsed productions 124 + declarationErrorsMode // report declaration errors 125 + allErrorsMode // report all errors (not just the first 10 on different lines) 126 + ) 127 + 128 + // ParseFile parses the source code of a single CUE source file and returns 129 + // the corresponding File node. The source code may be provided via 130 + // the filename of the source file, or via the src parameter. 131 + // 132 + // If src != nil, ParseFile parses the source from src and the filename is 133 + // only used when recording position information. The type of the argument 134 + // for the src parameter must be string, []byte, or io.Reader. 135 + // If src == nil, ParseFile parses the file specified by filename. 136 + // 137 + // The mode parameter controls the amount of source text parsed and other 138 + // optional parser functionality. Position information is recorded in the 139 + // file set fset, which must not be nil. 140 + // 141 + // If the source couldn't be read, the returned AST is nil and the error 142 + // indicates the specific failure. If the source was read but syntax 143 + // errors were found, the result is a partial AST (with Bad* nodes 144 + // representing the fragments of erroneous source code). Multiple errors 145 + // are returned via a ErrorList which is sorted by file position. 146 + func ParseFile(p *token.FileSet, filename string, src interface{}, mode ...Option) (f *ast.File, err error) { 147 + if p == nil { 148 + panic("ParseFile: no file.FileSet provided (fset == nil)") 149 + } 150 + 151 + // get source 152 + text, err := readSource(filename, src) 153 + if err != nil { 154 + return nil, err 155 + } 156 + 157 + var pp parser 158 + defer func() { 159 + if e := recover(); e != nil { 160 + // resume same panic if it's not a bailout 161 + if _, ok := e.(bailout); !ok { 162 + panic(e) 163 + } 164 + } 165 + 166 + // set result values 167 + if f == nil { 168 + // source is not a valid Go source file - satisfy 169 + // ParseFile API and return a valid (but) empty 170 + // *File 171 + f = &ast.File{ 172 + Name: new(ast.Ident), 173 + // Scope: NewScope(nil), 174 + } 175 + } 176 + 177 + pp.errors.Sort() 178 + err = pp.errors.Err() 179 + }() 180 + 181 + // parse source 182 + pp.init(p, filename, text, mode) 183 + f = pp.parseFile() 184 + if f == nil { 185 + return nil, pp.errors 186 + } 187 + f.Filename = filename 188 + resolve(f, pp.error) 189 + 190 + return 191 + } 192 + 193 + // ParseExpr is a convenience function for parsing an expression. 194 + // The arguments have the same meaning as for Parse, but the source must 195 + // be a valid CUE (type or value) expression. Specifically, fset must not 196 + // be nil. 197 + func ParseExpr(fset *token.FileSet, filename string, src interface{}, mode ...Option) (ast.Expr, error) { 198 + if fset == nil { 199 + panic("ParseExprFrom: no file.FileSet provided (fset == nil)") 200 + } 201 + 202 + // get source 203 + text, err := readSource(filename, src) 204 + if err != nil { 205 + return nil, err 206 + } 207 + 208 + var p parser 209 + defer func() { 210 + if e := recover(); e != nil { 211 + // resume same panic if it's not a bailout 212 + if _, ok := e.(bailout); !ok { 213 + panic(e) 214 + } 215 + } 216 + p.errors.Sort() 217 + err = p.errors.Err() 218 + }() 219 + 220 + // parse expr 221 + p.init(fset, filename, text, mode) 222 + // Set up pkg-level scopes to avoid nil-pointer errors. 223 + // This is not needed for a correct expression x as the 224 + // parser will be ok with a nil topScope, but be cautious 225 + // in case of an erroneous x. 226 + e := p.parseRHS() 227 + 228 + // If a comma was inserted, consume it; 229 + // report an error if there's more tokens. 230 + if p.tok == token.COMMA && p.lit == "\n" { 231 + p.next() 232 + } 233 + if p.mode&partialMode == 0 { 234 + p.expect(token.EOF) 235 + } 236 + 237 + if p.errors.Len() > 0 { 238 + p.errors.Sort() 239 + return nil, p.errors.Err() 240 + } 241 + 242 + return e, nil 243 + } 244 + 245 + // parseExprString is a convenience function for obtaining the AST of an 246 + // expression x. The position information recorded in the AST is undefined. The 247 + // filename used in error messages is the empty string. 248 + func parseExprString(x string) (ast.Expr, error) { 249 + return ParseExpr(token.NewFileSet(), "", []byte(x)) 250 + }
+126
cue/parser/interface_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package parser 16 + 17 + import ( 18 + "reflect" 19 + "testing" 20 + 21 + "cuelang.org/go/cue/ast" 22 + "cuelang.org/go/cue/token" 23 + ) 24 + 25 + func Test_readSource(t *testing.T) { 26 + type args struct { 27 + filename string 28 + src interface{} 29 + } 30 + tests := []struct { 31 + name string 32 + args args 33 + want []byte 34 + wantErr bool 35 + }{ 36 + // TODO: Add test cases. 37 + } 38 + for _, tt := range tests { 39 + got, err := readSource(tt.args.filename, tt.args.src) 40 + if (err != nil) != tt.wantErr { 41 + t.Errorf("%q. readSource() error = %v, wantErr %v", tt.name, err, tt.wantErr) 42 + continue 43 + } 44 + if !reflect.DeepEqual(got, tt.want) { 45 + t.Errorf("%q. readSource() = %v, want %v", tt.name, got, tt.want) 46 + } 47 + } 48 + } 49 + 50 + func TestParseFile(t *testing.T) { 51 + type args struct { 52 + fset *token.FileSet 53 + filename string 54 + src interface{} 55 + options []Option 56 + } 57 + tests := []struct { 58 + name string 59 + args args 60 + wantF *ast.File 61 + wantErr bool 62 + }{ 63 + // TODO: Add test cases. 64 + } 65 + for _, tt := range tests { 66 + gotF, err := ParseFile(tt.args.fset, tt.args.filename, tt.args.src, tt.args.options...) 67 + if (err != nil) != tt.wantErr { 68 + t.Errorf("%q. ParseFile() error = %v, wantErr %v", tt.name, err, tt.wantErr) 69 + continue 70 + } 71 + if !reflect.DeepEqual(gotF, tt.wantF) { 72 + t.Errorf("%q. ParseFile() = %v, want %v", tt.name, gotF, tt.wantF) 73 + } 74 + } 75 + } 76 + 77 + func TestParseExprFrom(t *testing.T) { 78 + type args struct { 79 + fset *token.FileSet 80 + filename string 81 + src interface{} 82 + mode Option 83 + } 84 + tests := []struct { 85 + name string 86 + args args 87 + want ast.Expr 88 + wantErr bool 89 + }{ 90 + // TODO: Add test cases. 91 + } 92 + for _, tt := range tests { 93 + got, err := ParseExpr(tt.args.fset, tt.args.filename, tt.args.src, tt.args.mode) 94 + if (err != nil) != tt.wantErr { 95 + t.Errorf("%q. ParseExprFrom() error = %v, wantErr %v", tt.name, err, tt.wantErr) 96 + continue 97 + } 98 + if !reflect.DeepEqual(got, tt.want) { 99 + t.Errorf("%q. ParseExprFrom() = %v, want %v", tt.name, got, tt.want) 100 + } 101 + } 102 + } 103 + 104 + func TestParseExprString(t *testing.T) { 105 + type args struct { 106 + x string 107 + } 108 + tests := []struct { 109 + name string 110 + args args 111 + want ast.Expr 112 + wantErr bool 113 + }{ 114 + // TODO: Add test cases. 115 + } 116 + for _, tt := range tests { 117 + got, err := parseExprString(tt.args.x) 118 + if (err != nil) != tt.wantErr { 119 + t.Errorf("%q. ParseExpr() error = %v, wantErr %v", tt.name, err, tt.wantErr) 120 + continue 121 + } 122 + if !reflect.DeepEqual(got, tt.want) { 123 + t.Errorf("%q. ParseExpr() = %v, want %v", tt.name, got, tt.want) 124 + } 125 + } 126 + }
+1446
cue/parser/parser.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package parser 16 + 17 + import ( 18 + "fmt" 19 + "strconv" 20 + "strings" 21 + "unicode" 22 + 23 + "cuelang.org/go/cue/ast" 24 + "cuelang.org/go/cue/errors" 25 + "cuelang.org/go/cue/scanner" 26 + "cuelang.org/go/cue/token" 27 + ) 28 + 29 + // The parser structure holds the parser's internal state. 30 + type parser struct { 31 + file *token.File 32 + errors errors.List 33 + scanner scanner.Scanner 34 + 35 + // Tracing/debugging 36 + mode mode // parsing mode 37 + trace bool // == (mode & Trace != 0) 38 + indent int // indentation used for tracing output 39 + 40 + // Comments 41 + leadComment *ast.CommentGroup 42 + comments *commentState 43 + 44 + // Next token 45 + pos token.Pos // token position 46 + tok token.Token // one token look-ahead 47 + lit string // token literal 48 + 49 + // Error recovery 50 + // (used to limit the number of calls to syncXXX functions 51 + // w/o making scanning progress - avoids potential endless 52 + // loops across multiple parser functions during error recovery) 53 + syncPos token.Pos // last synchronization position 54 + syncCnt int // number of calls to syncXXX without progress 55 + 56 + // Non-syntactic parser control 57 + exprLev int // < 0: in control clause, >= 0: in expression 58 + 59 + imports []*ast.ImportSpec // list of imports 60 + 61 + } 62 + 63 + func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode []Option) { 64 + p.file = fset.AddFile(filename, -1, len(src)) 65 + for _, f := range mode { 66 + f(p) 67 + } 68 + var m scanner.Mode 69 + if p.mode&parseCommentsMode != 0 { 70 + m = scanner.ScanComments 71 + } 72 + eh := func(pos token.Position, msg string) { p.errors.AddNew(pos, msg) } 73 + p.scanner.Init(p.file, src, eh, m) 74 + 75 + p.trace = p.mode&traceMode != 0 // for convenience (p.trace is used frequently) 76 + 77 + p.comments = &commentState{pos: -1} 78 + 79 + p.next() 80 + } 81 + 82 + type commentList struct { 83 + taken bool // for validation 84 + attachTail bool 85 + head *ast.CommentGroup 86 + last *ast.CommentGroup 87 + } 88 + 89 + type commentState struct { 90 + parent *commentState 91 + pos int8 92 + groups []*ast.CommentGroup 93 + 94 + // lists are not attached to nodes themselves. Enclosed expressions may 95 + // miss a comment due to commas and line termination. closeLists ensures 96 + // that comments will be passed to someone. 97 + isList int 98 + lastChild ast.Node 99 + lastPos int8 100 + } 101 + 102 + // openComments reserves the next doc comment for the caller and flushes 103 + func (p *parser) openComments() *commentState { 104 + if c := p.comments; c != nil && c.isList > 0 { 105 + if c.lastChild != nil { 106 + for _, cg := range c.groups { 107 + cg.Position = c.lastPos 108 + c.lastChild.AddComment(cg) 109 + } 110 + c.groups = nil 111 + } 112 + c.lastChild = nil 113 + } 114 + c := &commentState{ 115 + parent: p.comments, 116 + groups: []*ast.CommentGroup{p.leadComment}, 117 + } 118 + p.comments = c 119 + p.leadComment = nil 120 + return c 121 + } 122 + 123 + // openList is used to treat a list of comments as a single comment 124 + // position in a production. 125 + func (p *parser) openList() { 126 + if p.comments.isList > 0 { 127 + p.comments.isList++ 128 + return 129 + } 130 + c := &commentState{ 131 + parent: p.comments, 132 + isList: 1, 133 + } 134 + p.comments = c 135 + } 136 + 137 + func (c *commentState) add(g *ast.CommentGroup) { 138 + g.Position = c.pos 139 + c.groups = append(c.groups, g) 140 + } 141 + 142 + func (p *parser) closeList() { 143 + c := p.comments 144 + if c.lastChild != nil { 145 + for _, cg := range c.groups { 146 + cg.Position = c.lastPos 147 + c.lastChild.AddComment(cg) 148 + } 149 + c.groups = nil 150 + } 151 + switch c.isList--; { 152 + case c.isList < 0: 153 + panic("unmatched close list") 154 + case c.isList == 0: 155 + parent := c.parent 156 + parent.groups = append(parent.groups, c.groups...) 157 + parent.pos++ 158 + p.comments = parent 159 + } 160 + } 161 + 162 + func (c *commentState) closeNode(p *parser, n ast.Node) ast.Node { 163 + if p.comments != c { 164 + panic("unmatched comments") 165 + } 166 + p.comments = c.parent 167 + if c.parent != nil { 168 + c.parent.lastChild = n 169 + c.parent.lastPos = c.pos 170 + c.parent.pos++ 171 + } 172 + for _, cg := range c.groups { 173 + if n != nil { 174 + n.AddComment(cg) 175 + } 176 + } 177 + c.groups = nil 178 + return n 179 + } 180 + 181 + func (c *commentState) closeExpr(p *parser, n ast.Expr) ast.Expr { 182 + c.closeNode(p, n) 183 + return n 184 + } 185 + 186 + func (c *commentState) closeClause(p *parser, n ast.Clause) ast.Clause { 187 + c.closeNode(p, n) 188 + return n 189 + } 190 + 191 + // ---------------------------------------------------------------------------- 192 + // Parsing support 193 + 194 + func (p *parser) printTrace(a ...interface{}) { 195 + const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " 196 + const n = len(dots) 197 + pos := p.file.Position(p.pos) 198 + fmt.Printf("%5d:%3d: ", pos.Line, pos.Column) 199 + i := 2 * p.indent 200 + for i > n { 201 + fmt.Print(dots) 202 + i -= n 203 + } 204 + // i <= n 205 + fmt.Print(dots[0:i]) 206 + fmt.Println(a...) 207 + } 208 + 209 + func trace(p *parser, msg string) *parser { 210 + p.printTrace(msg, "(") 211 + p.indent++ 212 + return p 213 + } 214 + 215 + // Usage pattern: defer un(trace(p, "...")) 216 + func un(p *parser) { 217 + p.indent-- 218 + p.printTrace(")") 219 + } 220 + 221 + // Advance to the next 222 + func (p *parser) next0() { 223 + // Because of one-token look-ahead, print the previous token 224 + // when tracing as it provides a more readable output. The 225 + // very first token (!p.pos.IsValid()) is not initialized 226 + // (it is ILLEGAL), so don't print it . 227 + if p.trace && p.pos.IsValid() { 228 + s := p.tok.String() 229 + switch { 230 + case p.tok.IsLiteral(): 231 + p.printTrace(s, p.lit) 232 + case p.tok.IsOperator(), p.tok.IsKeyword(): 233 + p.printTrace("\"" + s + "\"") 234 + default: 235 + p.printTrace(s) 236 + } 237 + } 238 + 239 + p.pos, p.tok, p.lit = p.scanner.Scan() 240 + } 241 + 242 + // Consume a comment and return it and the line on which it ends. 243 + func (p *parser) consumeComment() (comment *ast.Comment, endline int) { 244 + // /*-style comments may end on a different line than where they start. 245 + // Scan the comment for '\n' chars and adjust endline accordingly. 246 + endline = p.file.Line(p.pos) 247 + if p.lit[1] == '*' { 248 + // don't use range here - no need to decode Unicode code points 249 + for i := 0; i < len(p.lit); i++ { 250 + if p.lit[i] == '\n' { 251 + endline++ 252 + } 253 + } 254 + } 255 + 256 + comment = &ast.Comment{Slash: p.pos, Text: p.lit} 257 + p.next0() 258 + 259 + return 260 + } 261 + 262 + // Consume a group of adjacent comments, add it to the parser's 263 + // comments list, and return it together with the line at which 264 + // the last comment in the group ends. A non-comment token or n 265 + // empty lines terminate a comment group. 266 + func (p *parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) { 267 + var list []*ast.Comment 268 + endline = p.file.Line(p.pos) 269 + for p.tok == token.COMMENT && p.file.Line(p.pos) <= endline+n { 270 + var comment *ast.Comment 271 + comment, endline = p.consumeComment() 272 + list = append(list, comment) 273 + } 274 + 275 + cg := &ast.CommentGroup{List: list} 276 + comments = cg 277 + return 278 + } 279 + 280 + // Advance to the next non-comment In the process, collect 281 + // any comment groups encountered, and refield the last lead and 282 + // and line comments. 283 + // 284 + // A lead comment is a comment group that starts and ends in a 285 + // line without any other tokens and that is followed by a non-comment 286 + // token on the line immediately after the comment group. 287 + // 288 + // A line comment is a comment group that follows a non-comment 289 + // token on the same line, and that has no tokens after it on the line 290 + // where it ends. 291 + // 292 + // Lead and line comments may be considered documentation that is 293 + // stored in the AST. 294 + func (p *parser) next() { 295 + // A leadComment may not be consumed if it leads an inner token of a node. 296 + if p.leadComment != nil { 297 + p.comments.add(p.leadComment) 298 + } 299 + p.leadComment = nil 300 + prev := p.pos 301 + p.next0() 302 + p.comments.pos++ 303 + 304 + if p.tok == token.COMMENT { 305 + var comment *ast.CommentGroup 306 + var endline int 307 + 308 + if p.file.Line(p.pos) == p.file.Line(prev) { 309 + // The comment is on same line as the previous token; it 310 + // cannot be a lead comment but may be a line comment. 311 + comment, endline = p.consumeCommentGroup(0) 312 + if p.file.Line(p.pos) != endline { 313 + // The next token is on a different line, thus 314 + // the last comment group is a line comment. 315 + comment.Line = true 316 + } 317 + } 318 + 319 + // consume successor comments, if any 320 + endline = -1 321 + for p.tok == token.COMMENT { 322 + if comment != nil { 323 + p.comments.add(comment) 324 + } 325 + comment, endline = p.consumeCommentGroup(1) 326 + } 327 + 328 + if endline+1 == p.file.Line(p.pos) && p.tok != token.EOF { 329 + // The next token is following on the line immediately after the 330 + // comment group, thus the last comment group is a lead comment. 331 + comment.Doc = true 332 + p.leadComment = comment 333 + } else { 334 + p.comments.add(comment) 335 + } 336 + } 337 + } 338 + 339 + // A bailout panic is raised to indicate early termination. 340 + type bailout struct{} 341 + 342 + func (p *parser) error(pos token.Pos, msg string) { 343 + ePos := p.file.Position(pos) 344 + 345 + // If AllErrors is not set, discard errors reported on the same line 346 + // as the last recorded error and stop parsing if there are more than 347 + // 10 errors. 348 + if p.mode&allErrorsMode == 0 { 349 + n := len(p.errors) 350 + if n > 0 && p.errors[n-1].Position().Line == ePos.Line { 351 + return // discard - likely a spurious error 352 + } 353 + if n > 10 { 354 + panic(bailout{}) 355 + } 356 + } 357 + 358 + p.errors.AddNew(ePos, msg) 359 + } 360 + 361 + func (p *parser) errorExpected(pos token.Pos, msg string) { 362 + msg = "expected " + msg 363 + if pos == p.pos { 364 + // the error happened at the current position; 365 + // make the error message more specific 366 + if p.tok == token.COMMA && p.lit == "\n" { 367 + msg += ", found newline" 368 + } else { 369 + msg += ", found '" + p.tok.String() + "'" 370 + if p.tok.IsLiteral() { 371 + msg += " " + p.lit 372 + } 373 + } 374 + } 375 + p.error(pos, msg) 376 + } 377 + 378 + func (p *parser) expect(tok token.Token) token.Pos { 379 + pos := p.pos 380 + if p.tok != tok { 381 + p.errorExpected(pos, "'"+tok.String()+"'") 382 + } 383 + p.next() // make progress 384 + return pos 385 + } 386 + 387 + // expectClosing is like expect but provides a better error message 388 + // for the common case of a missing comma before a newline. 389 + func (p *parser) expectClosing(tok token.Token, context string) token.Pos { 390 + if p.tok != tok && p.tok == token.COMMA && p.lit == "\n" { 391 + p.error(p.pos, "missing ',' before newline in "+context) 392 + p.next() 393 + } 394 + return p.expect(tok) 395 + } 396 + 397 + func (p *parser) expectComma() { 398 + // semicolon is optional before a closing ')', ']', '}', or newline 399 + if p.tok != token.RPAREN && p.tok != token.RBRACE && p.tok != token.EOF { 400 + switch p.tok { 401 + case token.COMMA: 402 + p.next() 403 + default: 404 + p.errorExpected(p.pos, "','") 405 + syncExpr(p) 406 + } 407 + } 408 + } 409 + 410 + func (p *parser) atComma(context string, follow ...token.Token) bool { 411 + if p.tok == token.COMMA { 412 + return true 413 + } 414 + for _, t := range follow { 415 + if p.tok == t { 416 + return false 417 + } 418 + } 419 + msg := "missing ','" 420 + // TODO: find a way to detect crossing lines now we don't have a semi. 421 + if p.lit == "\n" { 422 + msg += " before newline" 423 + } 424 + p.error(p.pos, msg+" in "+context) 425 + return true // "insert" comma and continue 426 + } 427 + 428 + func assert(cond bool, msg string) { 429 + if !cond { 430 + panic("lacelang/parser internal error: " + msg) 431 + } 432 + } 433 + 434 + // syncExpr advances to the next field in a field list. 435 + // Used for synchronization after an error. 436 + func syncExpr(p *parser) { 437 + for { 438 + switch p.tok { 439 + case token.COMMA: 440 + // Return only if parser made some progress since last 441 + // sync or if it has not reached 10 sync calls without 442 + // progress. Otherwise consume at least one token to 443 + // avoid an endless parser loop (it is possible that 444 + // both parseOperand and parseStmt call syncStmt and 445 + // correctly do not advance, thus the need for the 446 + // invocation limit p.syncCnt). 447 + if p.pos == p.syncPos && p.syncCnt < 10 { 448 + p.syncCnt++ 449 + return 450 + } 451 + if p.pos > p.syncPos { 452 + p.syncPos = p.pos 453 + p.syncCnt = 0 454 + return 455 + } 456 + // Reaching here indicates a parser bug, likely an 457 + // incorrect token list in this function, but it only 458 + // leads to skipping of possibly correct code if a 459 + // previous error is present, and thus is preferred 460 + // over a non-terminating parse. 461 + case token.EOF: 462 + return 463 + } 464 + p.next() 465 + } 466 + } 467 + 468 + // safePos returns a valid file position for a given position: If pos 469 + // is valid to begin with, safePos returns pos. If pos is out-of-range, 470 + // safePos returns the EOF position. 471 + // 472 + // This is hack to work around "artificial" end positions in the AST which 473 + // are computed by adding 1 to (presumably valid) token positions. If the 474 + // token positions are invalid due to parse errors, the resulting end position 475 + // may be past the file's EOF position, which would lead to panics if used 476 + // later on. 477 + func (p *parser) safePos(pos token.Pos) (res token.Pos) { 478 + defer func() { 479 + if recover() != nil { 480 + res = token.Pos(p.file.Base() + p.file.Size()) // EOF position 481 + } 482 + }() 483 + _ = p.file.Offset(pos) // trigger a panic if position is out-of-range 484 + return pos 485 + } 486 + 487 + // ---------------------------------------------------------------------------- 488 + // Identifiers 489 + 490 + func (p *parser) parseIdent() *ast.Ident { 491 + c := p.openComments() 492 + pos := p.pos 493 + name := "_" 494 + if p.tok == token.IDENT { 495 + name = p.lit 496 + p.next() 497 + } else { 498 + p.expect(token.IDENT) // use expect() error handling 499 + } 500 + ident := &ast.Ident{NamePos: pos, Name: name} 501 + c.closeNode(p, ident) 502 + return ident 503 + } 504 + 505 + // ---------------------------------------------------------------------------- 506 + // Expressions 507 + 508 + // parseOperand returns an expression. 509 + // Callers must verify the result. 510 + func (p *parser) parseOperand() (expr ast.Expr) { 511 + if p.trace { 512 + defer un(trace(p, "Operand")) 513 + } 514 + 515 + switch p.tok { 516 + case token.IDENT: 517 + return p.parseIdent() 518 + 519 + case token.LBRACE: 520 + return p.parseStruct() 521 + 522 + case token.LBRACK: 523 + return p.parseList() 524 + 525 + case token.BOTTOM: 526 + c := p.openComments() 527 + x := &ast.BottomLit{Bottom: p.pos} 528 + p.next() 529 + return c.closeExpr(p, x) 530 + 531 + case token.NULL, token.TRUE, token.FALSE, token.INT, token.FLOAT, token.STRING: 532 + c := p.openComments() 533 + x := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} 534 + p.next() 535 + return c.closeExpr(p, x) 536 + 537 + case token.INTERPOLATION: 538 + return p.parseInterpolation() 539 + 540 + case token.LPAREN: 541 + c := p.openComments() 542 + defer func() { c.closeNode(p, expr) }() 543 + lparen := p.pos 544 + p.next() 545 + if p.tok == token.RPAREN && p.mode&parseLambdasMode != 0 { 546 + c.pos = 2 547 + rparen := p.expect(token.RPAREN) 548 + p.expect(token.LAMBDA) 549 + return &ast.LambdaExpr{ 550 + Lparen: lparen, 551 + Rparen: rparen, 552 + Expr: p.parseRHS(), 553 + } 554 + } 555 + p.exprLev++ 556 + p.openList() 557 + x := p.parseRHS() // types may be parenthesized: (some type) 558 + var params []*ast.Field 559 + ident, ok := x.(*ast.Ident) 560 + if ok && (p.tok == token.COLON || p.tok == token.COMMA) && p.mode&parseLambdasMode != 0 { 561 + params = p.parseParams(ident, token.RPAREN) 562 + } 563 + p.closeList() 564 + p.exprLev-- 565 + rparen := p.expect(token.RPAREN) 566 + if p.tok == token.LAMBDA || params != nil && p.mode&parseLambdasMode != 0 { 567 + p.expect(token.LAMBDA) 568 + if params == nil { 569 + m := &ast.Field{Label: ident} 570 + params = append(params, m) 571 + } 572 + return &ast.LambdaExpr{ 573 + Lparen: lparen, 574 + Params: params, 575 + Rparen: rparen, 576 + Expr: p.parseRHS(), 577 + } 578 + } 579 + return &ast.ParenExpr{ 580 + Lparen: lparen, 581 + X: x, 582 + Rparen: rparen} 583 + } 584 + 585 + // we have an error 586 + c := p.openComments() 587 + pos := p.pos 588 + p.errorExpected(pos, "operand") 589 + syncExpr(p) 590 + return c.closeExpr(p, &ast.BadExpr{From: pos, To: p.pos}) 591 + } 592 + 593 + func (p *parser) parseParams(ident *ast.Ident, follow token.Token) (params []*ast.Field) { 594 + for { 595 + c := p.openComments() 596 + if ident == nil { 597 + ident = p.parseIdent() 598 + } 599 + m := &ast.Field{Label: ident} 600 + if p.tok == token.COLON { 601 + m.Colon = p.expect(token.COLON) 602 + m.Value = p.parseRHS() 603 + } 604 + hasComma := p.tok == token.COMMA 605 + if hasComma { 606 + p.expect(token.COMMA) 607 + } 608 + c.closeNode(p, m) 609 + params = append(params, m) 610 + if !hasComma || p.tok == follow || p.tok == token.EOF { 611 + break 612 + } 613 + ident = nil 614 + } 615 + return params 616 + } 617 + 618 + func (p *parser) parseIndexOrSlice(x ast.Expr) (expr ast.Expr) { 619 + if p.trace { 620 + defer un(trace(p, "IndexOrSlice")) 621 + } 622 + 623 + c := p.openComments() 624 + defer func() { c.closeNode(p, expr) }() 625 + c.pos = 1 626 + 627 + const N = 2 628 + lbrack := p.expect(token.LBRACK) 629 + 630 + p.exprLev++ 631 + var index [N]ast.Expr 632 + var colons [N - 1]token.Pos 633 + if p.tok != token.COLON { 634 + index[0] = p.parseRHS() 635 + } 636 + nColons := 0 637 + for p.tok == token.COLON && nColons < len(colons) { 638 + colons[nColons] = p.pos 639 + nColons++ 640 + p.next() 641 + if p.tok != token.COLON && p.tok != token.RBRACK && p.tok != token.EOF { 642 + index[nColons] = p.parseRHS() 643 + } 644 + } 645 + p.exprLev-- 646 + rbrack := p.expect(token.RBRACK) 647 + 648 + if nColons > 0 { 649 + return &ast.SliceExpr{ 650 + X: x, 651 + Lbrack: lbrack, 652 + Low: index[0], 653 + High: index[1], 654 + Rbrack: rbrack} 655 + } 656 + 657 + return &ast.IndexExpr{ 658 + X: x, 659 + Lbrack: lbrack, 660 + Index: index[0], 661 + Rbrack: rbrack} 662 + } 663 + 664 + func (p *parser) parseCallOrConversion(fun ast.Expr) (expr *ast.CallExpr) { 665 + if p.trace { 666 + defer un(trace(p, "CallOrConversion")) 667 + } 668 + c := p.openComments() 669 + defer func() { c.closeNode(p, expr) }() 670 + 671 + lparen := p.expect(token.LPAREN) 672 + p.exprLev++ 673 + var list []ast.Expr 674 + for p.tok != token.RPAREN && p.tok != token.EOF { 675 + list = append(list, p.parseRHS()) // builtins may expect a type: make(some type, ...) 676 + if !p.atComma("argument list", token.RPAREN) { 677 + break 678 + } 679 + p.next() 680 + } 681 + p.exprLev-- 682 + rparen := p.expectClosing(token.RPAREN, "argument list") 683 + 684 + return &ast.CallExpr{ 685 + Fun: fun, 686 + Lparen: lparen, 687 + Args: list, 688 + Rparen: rparen} 689 + } 690 + 691 + func (p *parser) parseFieldList(allowEmit bool) (list []ast.Decl) { 692 + if p.trace { 693 + defer un(trace(p, "FieldList")) 694 + } 695 + origEmit := allowEmit 696 + p.openList() 697 + defer p.closeList() 698 + 699 + for p.tok != token.RBRACE && p.tok != token.EOF { 700 + d := p.parseField(allowEmit) 701 + if e, ok := d.(*ast.EmitDecl); ok { 702 + if origEmit && !allowEmit { 703 + p.error(p.pos, "only one emit allowed at top level") 704 + } 705 + if !origEmit || !allowEmit { 706 + d = &ast.BadDecl{From: e.Pos(), To: e.End()} 707 + for _, cg := range e.Comments() { 708 + d.AddComment(cg) 709 + } 710 + } 711 + // uncomment to only allow one emit per top-level 712 + // allowEmit = false 713 + } 714 + list = append(list, d) 715 + } 716 + return 717 + } 718 + func (p *parser) parseField(allowEmit bool) (decl ast.Decl) { 719 + if p.trace { 720 + defer un(trace(p, "Field")) 721 + } 722 + 723 + c := p.openComments() 724 + defer func() { c.closeNode(p, decl) }() 725 + 726 + pos := p.pos 727 + 728 + this := &ast.Field{Label: nil} 729 + m := this 730 + 731 + for i := 0; ; i++ { 732 + tok := p.tok 733 + 734 + expr, ok := p.parseLabel(m) 735 + 736 + if !ok { 737 + if !allowEmit { 738 + p.error(pos, "expected label, found "+tok.String()) 739 + } 740 + if expr == nil { 741 + expr = p.parseExpr() 742 + } 743 + e := &ast.EmitDecl{Expr: expr} 744 + if p.atComma("file", token.RBRACE) { 745 + p.next() 746 + } 747 + return e 748 + } 749 + 750 + if i == 0 && tok == token.IDENT { 751 + ident := expr.(*ast.Ident) 752 + switch p.tok { 753 + case token.BIND: 754 + pos := p.pos 755 + p.expect(token.BIND) 756 + ref := p.parseRHS() 757 + if p.atComma("struct literal", token.RBRACE) { // TODO: may be EOF 758 + p.next() 759 + } 760 + return &ast.Alias{Ident: ident, Equal: pos, Expr: ref} 761 + 762 + case token.LPAREN: 763 + var value ast.Expr 764 + if p.mode&parseLambdasMode != 0 { 765 + c.pos = 2 766 + // TODO: Only allow LambdaExpr after non-quoted identifier. 767 + value = p.parseOperand() 768 + if _, ok := unparen(value).(*ast.LambdaExpr); !ok { 769 + p.error(value.Pos(), "expected lambda expression") 770 + } 771 + } 772 + if p.atComma("struct literal", token.RBRACE) { // TODO: may be EOF 773 + p.next() 774 + } 775 + return &ast.Field{Label: ident, Value: value} 776 + } 777 + } 778 + 779 + if p.tok == token.COLON { 780 + break 781 + } 782 + 783 + switch p.tok { 784 + default: 785 + if !allowEmit || p.tok != token.COMMA { 786 + p.errorExpected(p.pos, "label or ':'") 787 + } 788 + switch tok { 789 + case token.IDENT, token.LBRACK, token.STRING, token.INTERPOLATION, token.NULL, token.TRUE, token.FALSE: 790 + if p.tok == token.COMMA { 791 + p.expectComma() 792 + return &ast.EmitDecl{Expr: expr} 793 + } 794 + } 795 + return &ast.BadDecl{From: pos, To: p.pos} 796 + 797 + case token.IDENT, token.STRING, token.LSS, token.INTERPOLATION, token.LBRACK: 798 + field := &ast.Field{} 799 + m.Value = &ast.StructLit{Elts: []ast.Decl{field}} 800 + m = field 801 + } 802 + 803 + allowEmit = false 804 + } 805 + 806 + this.Colon = p.pos 807 + p.expect(token.COLON) 808 + m.Value = p.parseRHS() 809 + 810 + decl = this 811 + var arrow token.Pos 812 + switch p.tok { 813 + case token.ARROW: 814 + arrow = p.expect(token.ARROW) 815 + fallthrough 816 + 817 + case token.FOR, token.IF: 818 + clauses := p.parseComprehensionClauses() 819 + return &ast.ComprehensionDecl{ 820 + Field: this, 821 + Select: arrow, 822 + Clauses: clauses, 823 + } 824 + } 825 + 826 + if p.atComma("struct literal", token.RBRACE) { // TODO: may be EOF 827 + p.next() 828 + } 829 + 830 + return decl 831 + } 832 + 833 + func (p *parser) parseLabel(f *ast.Field) (expr ast.Expr, ok bool) { 834 + switch p.tok { 835 + case token.IDENT: 836 + ident := p.parseIdent() 837 + f.Label = ident 838 + expr = ident 839 + 840 + case token.STRING: 841 + // JSON compatibility. 842 + 843 + expr = p.parseOperand() 844 + f.Label = expr.(ast.Label) 845 + 846 + case token.INTERPOLATION: 847 + expr = p.parseInterpolation() 848 + f.Label = expr.(ast.Label) 849 + 850 + case token.NULL, token.TRUE, token.FALSE: 851 + // Keywords that represent operands. 852 + 853 + // Allowing keywords to be used as a labels should not interfere with 854 + // generating good errors: any keyword can only appear on the RHS of a 855 + // field (after a ':'), whereas labels always appear on the LHS. 856 + ident := &ast.BasicLit{ 857 + Kind: p.tok, 858 + ValuePos: p.pos, 859 + Value: p.lit, 860 + } 861 + p.next() 862 + f.Label = ident 863 + expr = ident 864 + 865 + case token.IF, token.FOR, token.IN, token.LET: 866 + // Keywords representing clauses. 867 + f.Label = &ast.Ident{ 868 + NamePos: p.pos, 869 + Name: p.lit, 870 + } 871 + p.next() 872 + 873 + case token.LSS: // element templates 874 + pos := p.pos 875 + c := p.openComments() 876 + p.next() 877 + ident := p.parseIdent() 878 + gtr := p.pos 879 + if p.tok != token.GTR { 880 + p.expect(token.GTR) 881 + } 882 + p.next() 883 + label := &ast.TemplateLabel{Langle: pos, Ident: ident, Rangle: gtr} 884 + c.closeNode(p, label) 885 + f.Label = label 886 + 887 + case token.LBRACK: 888 + expr = p.parseList() 889 + list, ok := expr.(*ast.ListLit) 890 + if ok && len(list.Elts) == 1 && list.Ellipsis == token.NoPos { 891 + f.Label = &ast.ExprLabel{ 892 + Lbrack: list.Lbrack, 893 + Label: list.Elts[0], 894 + Rbrack: list.Rbrack, 895 + } 896 + break 897 + } 898 + 899 + fallthrough 900 + default: 901 + return expr, false 902 + } 903 + return expr, true 904 + } 905 + 906 + func (p *parser) parseStruct() (expr ast.Expr) { 907 + c := p.openComments() 908 + defer func() { c.closeNode(p, expr) }() 909 + 910 + lbrace := p.expect(token.LBRACE) 911 + 912 + if p.trace { 913 + defer un(trace(p, "StructLit")) 914 + } 915 + 916 + elts := p.parseStructBody() 917 + rbrace := p.expectClosing(token.RBRACE, "struct literal") 918 + return &ast.StructLit{ 919 + Lbrace: lbrace, 920 + Elts: elts, 921 + Rbrace: rbrace, 922 + } 923 + } 924 + 925 + func (p *parser) parseStructBody() []ast.Decl { 926 + if p.trace { 927 + defer un(trace(p, "StructBody")) 928 + } 929 + 930 + p.exprLev++ 931 + var elts []ast.Decl 932 + if p.tok != token.RBRACE { 933 + elts = p.parseFieldList(false) 934 + } 935 + p.exprLev-- 936 + 937 + return elts 938 + } 939 + 940 + func isClauseStart(tok token.Token) bool { 941 + return tok == token.FOR || tok == token.IF // || tok == LET 942 + } 943 + 944 + func (p *parser) parseComprehensionClauses() (clauses []ast.Clause) { 945 + // TODO: reuse Template spec, which is possible if it doesn't check the 946 + // first is an identifier. 947 + for { 948 + if p.tok == token.COMMA { 949 + p.next() 950 + } 951 + switch p.tok { 952 + case token.FOR: 953 + c := p.openComments() 954 + forPos := p.expect(token.FOR) 955 + var key, value *ast.Ident 956 + var colon token.Pos 957 + value = p.parseIdent() 958 + if p.tok == token.COMMA { 959 + colon = p.expect(token.COMMA) 960 + key = value 961 + value = p.parseIdent() 962 + } 963 + c.pos = 4 964 + // params := p.parseParams(nil, ARROW) 965 + clauses = append(clauses, c.closeClause(p, &ast.ForClause{ 966 + For: forPos, 967 + Key: key, 968 + Colon: colon, 969 + Value: value, 970 + In: p.expect(token.IN), 971 + Source: p.parseExpr(), 972 + })) 973 + 974 + case token.IF: 975 + c := p.openComments() 976 + clauses = append(clauses, c.closeClause(p, &ast.IfClause{ 977 + If: p.expect(token.IF), 978 + Condition: p.parseExpr(), 979 + })) 980 + 981 + // TODO: case LET: 982 + default: 983 + return clauses 984 + } 985 + } 986 + } 987 + 988 + func (p *parser) parseList() (expr ast.Expr) { 989 + c := p.openComments() 990 + defer func() { c.closeNode(p, expr) }() 991 + 992 + lbrack := p.expect(token.LBRACK) 993 + 994 + if p.trace { 995 + defer un(trace(p, "ListLiteral")) 996 + } 997 + 998 + elts := p.parseListElements() 999 + 1000 + if clauses := p.parseComprehensionClauses(); clauses != nil { 1001 + var expr ast.Expr 1002 + if len(elts) != 1 { 1003 + p.error(lbrack+1, "list comprehension must have exactly one element") 1004 + } 1005 + if len(elts) > 0 { 1006 + expr = elts[0] 1007 + } 1008 + rbrack := p.expectClosing(token.RBRACK, "list comprehension") 1009 + 1010 + return &ast.ListComprehension{ 1011 + Lbrack: lbrack, 1012 + Expr: expr, 1013 + Clauses: clauses, 1014 + Rbrack: rbrack, 1015 + } 1016 + } 1017 + 1018 + ellipsis := token.NoPos 1019 + typ := ast.Expr(nil) 1020 + if p.tok == token.ELLIPSIS { 1021 + ellipsis = p.pos 1022 + p.next() 1023 + if p.tok != token.COMMA && p.tok != token.RBRACK { 1024 + typ = p.parseRHS() 1025 + } 1026 + if p.atComma("list literal", token.RBRACK) { 1027 + p.next() 1028 + } 1029 + } 1030 + 1031 + rbrack := p.expectClosing(token.RBRACK, "list literal") 1032 + return &ast.ListLit{ 1033 + Lbrack: lbrack, 1034 + Elts: elts, 1035 + Ellipsis: ellipsis, 1036 + Type: typ, 1037 + Rbrack: rbrack} 1038 + } 1039 + 1040 + func (p *parser) parseListElements() (list []ast.Expr) { 1041 + if p.trace { 1042 + defer un(trace(p, "ListElements")) 1043 + } 1044 + p.openList() 1045 + defer p.closeList() 1046 + 1047 + for p.tok != token.RBRACK && p.tok != token.ELLIPSIS && p.tok != token.EOF { 1048 + list = append(list, p.parseListElement()) 1049 + // Enforce there is an explicit comma. We could also allow the 1050 + // omission of commas in lists, but this gives rise to some ambiguities 1051 + // with list comprehensions. 1052 + if p.tok == token.COMMA && p.lit != "," { 1053 + p.next() 1054 + // Allow missing comma for last element, though, to be compliant 1055 + // with JSON. 1056 + if p.tok == token.RBRACK || p.tok == token.FOR || p.tok == token.IF { 1057 + break 1058 + } 1059 + p.error(p.pos, "missing ',' before newline in list literal") 1060 + } else if !p.atComma("list literal", token.RBRACK, token.FOR, token.IF) { 1061 + break 1062 + } 1063 + p.next() 1064 + } 1065 + 1066 + return 1067 + } 1068 + 1069 + func (p *parser) parseListElement() (expr ast.Expr) { 1070 + if p.trace { 1071 + defer un(trace(p, "ListElement")) 1072 + } 1073 + c := p.openComments() 1074 + defer func() { c.closeNode(p, expr) }() 1075 + 1076 + e := p.parseRHS() 1077 + switch p.tok { 1078 + case token.ELLIPSIS: 1079 + return &ast.Ellipsis{Ellipsis: p.expect(token.ELLIPSIS), Elt: e} 1080 + } 1081 + return e 1082 + } 1083 + 1084 + // checkExpr checks that x is an expression (and not a type). 1085 + func (p *parser) checkExpr(x ast.Expr) ast.Expr { 1086 + switch unparen(x).(type) { 1087 + case *ast.BadExpr: 1088 + case *ast.BottomLit: 1089 + case *ast.Ident: 1090 + case *ast.BasicLit: 1091 + case *ast.Interpolation: 1092 + case *ast.StructLit: 1093 + case *ast.ListLit: 1094 + case *ast.LambdaExpr: 1095 + case *ast.ListComprehension: 1096 + case *ast.ParenExpr: 1097 + panic("unreachable") 1098 + case *ast.SelectorExpr: 1099 + case *ast.IndexExpr: 1100 + case *ast.SliceExpr: 1101 + case *ast.CallExpr: 1102 + case *ast.UnaryExpr: 1103 + case *ast.BinaryExpr: 1104 + default: 1105 + // all other nodes are not proper expressions 1106 + p.errorExpected(x.Pos(), "expression") 1107 + x = &ast.BadExpr{ 1108 + From: x.Pos(), To: p.safePos(x.End()), 1109 + } 1110 + } 1111 + return x 1112 + } 1113 + 1114 + // If x is of the form (T), unparen returns unparen(T), otherwise it returns x. 1115 + func unparen(x ast.Expr) ast.Expr { 1116 + if p, isParen := x.(*ast.ParenExpr); isParen { 1117 + x = unparen(p.X) 1118 + } 1119 + return x 1120 + } 1121 + 1122 + // If lhs is set and the result is an identifier, it is not resolved. 1123 + func (p *parser) parsePrimaryExpr() ast.Expr { 1124 + if p.trace { 1125 + defer un(trace(p, "PrimaryExpr")) 1126 + } 1127 + 1128 + x := p.parseOperand() 1129 + 1130 + L: 1131 + for { 1132 + switch p.tok { 1133 + case token.PERIOD: 1134 + c := p.openComments() 1135 + c.pos = 1 1136 + p.next() 1137 + switch p.tok { 1138 + case token.IDENT: 1139 + x = &ast.SelectorExpr{ 1140 + X: p.checkExpr(x), 1141 + Sel: p.parseIdent(), 1142 + } 1143 + default: 1144 + pos := p.pos 1145 + p.errorExpected(pos, "selector") 1146 + p.next() // make progress 1147 + x = &ast.SelectorExpr{X: x, Sel: &ast.Ident{NamePos: pos, Name: "_"}} 1148 + } 1149 + c.closeNode(p, x) 1150 + case token.LBRACK: 1151 + x = p.parseIndexOrSlice(p.checkExpr(x)) 1152 + case token.LPAREN: 1153 + x = p.parseCallOrConversion(p.checkExpr(x)) 1154 + default: 1155 + break L 1156 + } 1157 + } 1158 + 1159 + return x 1160 + } 1161 + 1162 + // If lhs is set and the result is an identifier, it is not resolved. 1163 + func (p *parser) parseUnaryExpr() ast.Expr { 1164 + if p.trace { 1165 + defer un(trace(p, "UnaryExpr")) 1166 + } 1167 + 1168 + switch p.tok { 1169 + case token.ADD, token.SUB, token.NOT: 1170 + pos, op := p.pos, p.tok 1171 + c := p.openComments() 1172 + p.next() 1173 + return c.closeExpr(p, &ast.UnaryExpr{ 1174 + OpPos: pos, 1175 + Op: op, 1176 + X: p.checkExpr(p.parseUnaryExpr()), 1177 + }) 1178 + } 1179 + 1180 + return p.parsePrimaryExpr() 1181 + } 1182 + 1183 + func (p *parser) tokPrec() (token.Token, int) { 1184 + tok := p.tok 1185 + if tok == token.IDENT { 1186 + switch p.lit { 1187 + case "quo": 1188 + return token.IQUO, 7 1189 + case "rem": 1190 + return token.IREM, 7 1191 + case "div": 1192 + return token.IDIV, 7 1193 + case "mod": 1194 + return token.IMOD, 7 1195 + default: 1196 + return tok, 0 1197 + } 1198 + } 1199 + return tok, tok.Precedence() 1200 + } 1201 + 1202 + // If lhs is set and the result is an identifier, it is not resolved. 1203 + func (p *parser) parseBinaryExpr(prec1 int) ast.Expr { 1204 + if p.trace { 1205 + defer un(trace(p, "BinaryExpr")) 1206 + } 1207 + p.openList() 1208 + defer p.closeList() 1209 + 1210 + x := p.parseUnaryExpr() 1211 + 1212 + for { 1213 + op, prec := p.tokPrec() 1214 + if prec < prec1 { 1215 + return x 1216 + } 1217 + c := p.openComments() 1218 + c.pos = 1 1219 + pos := p.expect(p.tok) 1220 + x = c.closeExpr(p, &ast.BinaryExpr{ 1221 + X: p.checkExpr(x), 1222 + OpPos: pos, 1223 + Op: op, 1224 + Y: p.checkExpr(p.parseBinaryExpr(prec + 1))}) 1225 + } 1226 + } 1227 + 1228 + func (p *parser) parseInterpolation() (expr ast.Expr) { 1229 + c := p.openComments() 1230 + defer func() { c.closeNode(p, expr) }() 1231 + 1232 + p.openList() 1233 + defer p.closeList() 1234 + 1235 + cc := p.openComments() 1236 + 1237 + lit := p.lit 1238 + p.next() 1239 + last := &ast.BasicLit{ValuePos: p.pos, Kind: token.STRING, Value: lit} 1240 + exprs := []ast.Expr{last} 1241 + 1242 + quote := rune(lit[0]) 1243 + numQuotes := 1 1244 + if len(lit) > 2 && lit[0] == lit[1] { 1245 + numQuotes = 3 1246 + } 1247 + 1248 + for p.tok == token.LPAREN { 1249 + c.pos = 1 1250 + p.expect(token.LPAREN) 1251 + cc.closeExpr(p, last) 1252 + 1253 + exprs = append(exprs, p.parseExpr()) 1254 + 1255 + cc = p.openComments() 1256 + if p.tok != token.RPAREN { 1257 + p.error(p.pos, "expected ')' for string interpolation") 1258 + } 1259 + lit = p.scanner.ResumeInterpolation(quote, numQuotes) 1260 + p.next() 1261 + last = &ast.BasicLit{ 1262 + ValuePos: p.pos, 1263 + Kind: token.STRING, 1264 + Value: lit, 1265 + } 1266 + exprs = append(exprs, last) 1267 + } 1268 + cc.closeExpr(p, last) 1269 + return &ast.Interpolation{Elts: exprs} 1270 + } 1271 + 1272 + // Callers must check the result (using checkExpr), depending on context. 1273 + func (p *parser) parseExpr() ast.Expr { 1274 + if p.trace { 1275 + defer un(trace(p, "Expression")) 1276 + } 1277 + 1278 + return p.parseBinaryExpr(token.LowestPrec + 1) 1279 + } 1280 + 1281 + func (p *parser) parseRHS() ast.Expr { 1282 + x := p.checkExpr(p.parseExpr()) 1283 + return x 1284 + } 1285 + 1286 + func (p *parser) parseCallExpr(callType string) *ast.CallExpr { 1287 + x := p.parseRHS() // could be a conversion: (some type)(x) 1288 + if call, isCall := x.(*ast.CallExpr); isCall { 1289 + return call 1290 + } 1291 + if _, isBad := x.(*ast.BadExpr); !isBad { 1292 + // only report error if it's a new one 1293 + p.error(p.safePos(x.End()), fmt.Sprintf("function must be invoked in %s statement", callType)) 1294 + } 1295 + return nil 1296 + } 1297 + 1298 + // ---------------------------------------------------------------------------- 1299 + // Declarations 1300 + 1301 + type parseSpecFunction func(iota int) *ast.ImportSpec 1302 + 1303 + func isValidImport(lit string) bool { 1304 + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" 1305 + s, _ := strconv.Unquote(lit) // go/scanner returns a legal string literal 1306 + for _, r := range s { 1307 + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { 1308 + return false 1309 + } 1310 + } 1311 + return s != "" 1312 + } 1313 + 1314 + func (p *parser) parseImportSpec(_ int) *ast.ImportSpec { 1315 + if p.trace { 1316 + defer un(trace(p, "ImportSpec")) 1317 + } 1318 + 1319 + c := p.openComments() 1320 + 1321 + var ident *ast.Ident 1322 + switch p.tok { 1323 + case token.PERIOD: 1324 + ident = &ast.Ident{NamePos: p.pos, Name: "."} 1325 + p.next() 1326 + case token.IDENT: 1327 + ident = p.parseIdent() 1328 + } 1329 + 1330 + pos := p.pos 1331 + var path string 1332 + if p.tok == token.STRING { 1333 + path = p.lit 1334 + if !isValidImport(path) { 1335 + p.error(pos, "invalid import path: "+path) 1336 + } 1337 + p.next() 1338 + p.expectComma() // call before accessing p.linecomment 1339 + } else { 1340 + p.expect(token.STRING) // use expect() error handling 1341 + if p.tok == token.COMMA { 1342 + p.expectComma() // call before accessing p.linecomment 1343 + } 1344 + } 1345 + // collect imports 1346 + spec := &ast.ImportSpec{ 1347 + Name: ident, 1348 + Path: &ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: path}, 1349 + } 1350 + c.closeNode(p, spec) 1351 + p.imports = append(p.imports, spec) 1352 + 1353 + return spec 1354 + } 1355 + 1356 + func (p *parser) parseImports() *ast.ImportDecl { 1357 + if p.trace { 1358 + defer un(trace(p, "Imports")) 1359 + } 1360 + c := p.openComments() 1361 + 1362 + ident := p.parseIdent() 1363 + var lparen, rparen token.Pos 1364 + var list []*ast.ImportSpec 1365 + if p.tok == token.LPAREN { 1366 + lparen = p.pos 1367 + p.next() 1368 + p.openList() 1369 + for iota := 0; p.tok != token.RPAREN && p.tok != token.EOF; iota++ { 1370 + list = append(list, p.parseImportSpec(iota)) 1371 + } 1372 + p.closeList() 1373 + rparen = p.expect(token.RPAREN) 1374 + p.expectComma() 1375 + } else { 1376 + list = append(list, p.parseImportSpec(0)) 1377 + } 1378 + 1379 + d := &ast.ImportDecl{ 1380 + Import: ident.Pos(), 1381 + Lparen: lparen, 1382 + Specs: list, 1383 + Rparen: rparen, 1384 + } 1385 + c.closeNode(p, d) 1386 + return d 1387 + } 1388 + 1389 + // ---------------------------------------------------------------------------- 1390 + // Source files 1391 + 1392 + func (p *parser) parseFile() *ast.File { 1393 + if p.trace { 1394 + defer un(trace(p, "File")) 1395 + } 1396 + 1397 + c := p.comments 1398 + 1399 + // Don't bother parsing the rest if we had errors scanning the first 1400 + // Likely not a Go source file at all. 1401 + if p.errors.Len() != 0 { 1402 + return nil 1403 + } 1404 + 1405 + // The package clause is not a declaration: it does not appear in any 1406 + // scope. 1407 + pos := p.pos 1408 + var name *ast.Ident 1409 + if p.tok == token.IDENT && p.lit == "package" { 1410 + p.expect(token.IDENT) 1411 + name = p.parseIdent() 1412 + if name.Name == "_" && p.mode&declarationErrorsMode != 0 { 1413 + p.error(p.pos, "invalid package name _") 1414 + } 1415 + p.expectComma() 1416 + } else { 1417 + pos = token.NoPos 1418 + } 1419 + c.pos = 3 1420 + 1421 + p.openList() 1422 + var decls []ast.Decl 1423 + if p.mode&packageClauseOnlyMode == 0 { 1424 + // import decls 1425 + for p.tok == token.IDENT && p.lit == "import" { 1426 + decls = append(decls, p.parseImports()) 1427 + } 1428 + 1429 + if p.mode&importsOnlyMode == 0 { 1430 + // rest of package decls 1431 + // TODO: loop and allow multiple expressions. 1432 + decls = append(decls, p.parseFieldList(true)...) 1433 + p.expect(token.EOF) 1434 + } 1435 + } 1436 + p.closeList() 1437 + 1438 + f := &ast.File{ 1439 + Package: pos, 1440 + Name: name, 1441 + Imports: p.imports, 1442 + Decls: decls, 1443 + } 1444 + c.closeNode(p, f) 1445 + return f 1446 + }
+543
cue/parser/parser_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package parser 16 + 17 + import ( 18 + "bytes" 19 + "fmt" 20 + "strings" 21 + "testing" 22 + 23 + "cuelang.org/go/cue/ast" 24 + "cuelang.org/go/cue/token" 25 + ) 26 + 27 + func TestParse(t *testing.T) { 28 + testCases := []struct{ desc, in, out string }{{ 29 + "empty file", "", "", 30 + }, { 31 + "empty struct", "{}", "{}", 32 + }, { 33 + "empty structs", "{},{},", "{}, {}", 34 + }, { 35 + "empty structs; elided comma", "{}\n{}", "{}, {}", 36 + }, { 37 + "basic lits", `"a","b", 3,3.4,5,2_3`, `"a", "b", 3, 3.4, 5, 2_3`, 38 + }, { 39 + "keyword basic lits", `true,false,null`, `true, false, null`, 40 + }, { 41 + "keywords as labels", 42 + `if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5`, 43 + `if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5`, 44 + }, { 45 + "json", 46 + `{ 47 + "a": 1, 48 + "b": "2", 49 + "c": 3 50 + }`, 51 + `{"a": 1, "b": "2", "c": 3}`, 52 + }, { 53 + "json:extra comma", 54 + `{ 55 + "a": 1, 56 + "b": "2", 57 + "c": 3, 58 + }`, 59 + `{"a": 1, "b": "2", "c": 3}`, 60 + }, { 61 + "json:simplified", 62 + `{ 63 + a: 1 64 + b: "2" 65 + c: 3 66 + }`, 67 + `{a: 1, b: "2", c: 3}`, 68 + }, { 69 + "not emitted", 70 + `a: true 71 + b: "2" 72 + c: 3 73 + `, 74 + `a: true, b: "2", c: 3`, 75 + }, { 76 + "emitted refrencing non-emitted", 77 + `a: 1 78 + b: "2" 79 + c: 3 80 + { name: b, total: a + b }`, 81 + `a: 1, b: "2", c: 3, {name: b, total: a+b}`, 82 + }, { 83 + "package file", 84 + `package k8s 85 + {} 86 + `, 87 + `package k8s, {}`, 88 + }, { 89 + "imports group", 90 + `package k8s 91 + 92 + import ( 93 + a "foo" 94 + "bar/baz" 95 + . "model" 96 + ) 97 + `, 98 + `package k8s, import ( a "foo", "bar/baz", . "model" )`, 99 + }, { 100 + "imports single", 101 + `package k8s 102 + 103 + import a "foo" 104 + import "bar/baz" 105 + import . "model" 106 + `, 107 + `package k8s, import a "foo", import "bar/baz", import . "model"`, 108 + }, { 109 + "collapsed fields", 110 + `a b c: 1 111 + // job foo { bar: 1 } // TODO error after foo 112 + job "foo": { bar: 1 } 113 + `, 114 + `a: {b: {c: 1}}, job: {"foo": {bar: 1}}`, 115 + }, { 116 + "identifiers", 117 + `// $_: 1, 118 + a: {b: {c: d}} 119 + c: a 120 + d: a.b 121 + // e: a."b" // TODO: is an error 122 + e: a.b.c 123 + "f": f, 124 + <X>: X 125 + `, 126 + "a: {b: {c: d}}, c: a, d: a.b, e: a.b.c, \"f\": f, <X>: X", 127 + }, { 128 + "expressions", 129 + ` a: (2 + 3) * 5 130 + b: (2 + 3) + 4 131 + c: 2 + 3 + 4 132 + d: -1 133 + e: !foo 134 + f: _|_ 135 + `, 136 + "a: (2+3)*5, b: (2+3)+4, c: 2+3+4, d: -1, e: !foo, f: _|_", 137 + }, { 138 + "pseudo keyword expressions", 139 + ` a: (2 div 3) mod 5 140 + b: (2 quo 3) rem 4 141 + c: 2 div 3 div 4 142 + `, 143 + "a: (2 div 3) mod 5, b: (2 quo 3) rem 4, c: 2 div 3 div 4", 144 + }, { 145 + "ranges", 146 + ` a: 1..2 147 + b: 2.0 .. 40.0 148 + c: "a".."b" 149 + v: (1..2)..(5..10) 150 + w: 1..2..3 151 + d: 3T..5M 152 + `, 153 + "a: 1..2, b: 2.0..40.0, c: \"a\"..\"b\", v: (1..2)..(5..10), w: 1..2..3, d: 3T..5M", 154 + }, { 155 + "indices", 156 + `{ 157 + a: b[2] 158 + b: c[1:2] 159 + c: "asdf" 160 + d: c ["a"] 161 + }`, 162 + `{a: b[2], b: c[1:2], c: "asdf", d: c["a"]}`, 163 + }, { 164 + "lambdas", 165 + `{ 166 + a(P, Q, r: R) -> { p: P, q: Q } 167 + b: a(4002, "s") 168 + }`, 169 + `{a: (P: _,Q: _,r: R,) -> {p: P, q: Q}, b: a(4002, "s")}`, // c(C): {d(D): {}}}`, 170 + }, { 171 + "calls", 172 + `{ 173 + a: b(a.b, c.d) 174 + b: a.b(c) 175 + }`, 176 + `{a: b(a.b, c.d), b: a.b(c)}`, 177 + }, { 178 + "lists", 179 + `{ 180 + a: [ 1, 2, 3, b..., c... ] 181 + b: [ 1, 2, 3, ], 182 + c: [ 1, 183 + 2, 184 + 3 185 + ], 186 + d: [ 1+2, 2, 4,] 187 + }`, 188 + `{a: [1, 2, 3, b..., c...], b: [1, 2, 3], c: [1, 2, 3], d: [1+2, 2, 4]}`, 189 + }, { 190 + "list types", 191 + `{ 192 + a: 4*[int] 193 + b: 0..5*[ {a: 5} ] 194 + c1: [...int] 195 + c2: [...] 196 + c3: [1, 2, ...int,] 197 + }`, 198 + `{a: 4*[int], b: 0..5*[{a: 5}], c1: [...int], c2: [...], c3: [1, 2, ...int]}`, 199 + }, { 200 + "list comprehensions", 201 + `{ 202 + y: [1,2,3] 203 + b: [ x for x in y if x == 1 ], 204 + }`, 205 + `{y: [1, 2, 3], b: [x for x in y if x==1 ]}`, 206 + }, { 207 + "field comprehensions", 208 + `{ 209 + y: { a: 1, b: 2} 210 + a: { "\(k)": v for k, v in y if v > 2 } 211 + }`, 212 + `{y: {a: 1, b: 2}, a: {"\(k)": v for k: v in y if v>2 }}`, 213 + }, { 214 + "duplicates allowed", 215 + `{ 216 + a b: 3 217 + a: { b: 3 } 218 + }`, 219 + "{a: {b: 3}, a: {b: 3}}", 220 + }, { 221 + "templates", 222 + `{ 223 + <foo>: { a: int } 224 + a: { a: 1 } 225 + }`, 226 + "{<foo>: {a: int}, a: {a: 1}}", 227 + }, { 228 + "foo", 229 + `[ 230 + [1], 231 + [1, 2], 232 + [1, 2, 3], 233 + ]`, 234 + "[[1], [1, 2], [1, 2, 3]]", 235 + }, { 236 + "interpolation", 237 + `a: "foo \(ident)" 238 + b: "bar \(bar) $$$ " 239 + c: "nest \( { a: "\( nest ) "}.a ) \(5)" 240 + m1: """ 241 + multi \(bar) 242 + """ 243 + m2: ''' 244 + \(bar) multi 245 + '''`, 246 + `a: "foo \(ident)", b: "bar \(bar) $$$ ", c: "nest \({a: "\(nest) "}.a) \(5)", ` + "m1: \"\"\"\n\t\t\t multi \\(bar)\n\t\t\t \"\"\", m2: '''\n\t\t\t \\(bar) multi\n\t\t\t '''", 247 + }, { 248 + "file comments", 249 + `// foo 250 + 251 + // uni 252 + package foo // uniline 253 + 254 + // file.1 255 + // file.2 256 + 257 + `, 258 + "<[0// foo] [d0// uni] [l3// uniline] [3// file.1 // file.2] package foo, >", 259 + }, { 260 + "line comments", 261 + `// doc 262 + a: 5 // line 263 + b: 6 // lineb 264 + // next 265 + `, // next is followed by EOF. Ensure it doesn't move to file. 266 + "<[d0// doc] [l4// line] a: 5>, " + 267 + "<[l4// lineb] [4// next] b: 6>", 268 + }, { 269 + "alt comments", 270 + `// a ... 271 + a: 5 // line a 272 + 273 + // about a 274 + 275 + // b ... 276 + b: // lineb 277 + 6 278 + 279 + // about b 280 + 281 + c: 7 282 + 283 + // about c 284 + 285 + `, 286 + "<[d0// a ...] [l4// line a] [4// about a] a: 5>, " + 287 + "<[d0// b ...] [l2// lineb] [4// about b] b: 6>, " + 288 + "<[4// about c] c: 7>", 289 + }, { 290 + "expr comments", 291 + ` 292 + a: 2 + // 2 + 293 + 3 + // 3 + 294 + 4 // 4 295 + l1( // sig 296 + ) -> // arrow 297 + 4 // expr 298 + l2(a // la 299 + ) -> // arrow 300 + a // l2 301 + l3( 302 + // param a 303 + a : // la 304 + 305 + // int 306 + int // lint 307 + ) -> // larrow 308 + a + 1 309 + `, 310 + "<[l4// 4] a: <[l2// 3 +] <[l2// 2 +] 2+3>+4>>, " + 311 + "<[l4// expr] l1: <[l1// sig] [l4// arrow] () -> 4>>, " + 312 + "<[l4// l2] l2: <[l4// arrow] (<[l1// la] a: _>,) -> a>>, " + 313 + "l3: <[l4// larrow] (<[l1// la] [l3// lint] <[d0// param a] a>: <[d0// int] int>>,) -> a+1>", 314 + }, { 315 + "composit comments", 316 + `a : { 317 + a: 1, b: 2, c: 3, d: 4 318 + // end 319 + } 320 + b: [ 321 + 1, 2, 3, 4, 5, 322 + // end 323 + ] 324 + c: [ 1, 2, 3, 4, // here 325 + 5, 6, 7, 8 // and here 326 + ] 327 + d: { 328 + a: /* 8 */ 1 // Hello 329 + // Doc 330 + b: 2 331 + } 332 + e1: [ 333 + // comment in list body 334 + ] 335 + e2: { 336 + // comment in struct body 337 + } 338 + `, 339 + "a: <[d2// end] {a: 1, b: 2, c: 3, d: 4}>, " + 340 + "b: <[d2// end] [1, 2, 3, 4, 5]>, " + 341 + "c: [1, 2, 3, <[l1// here] 4>, 5, 6, 7, <[l1// and here] 8>], " + 342 + "d: {<[2/* 8 */] [l4// Hello] a: 1>, <[d0// Doc] b: 2>}, " + 343 + "e1: <[d2// comment in list body] []>, " + 344 + "e2: <[d1// comment in struct body] {}>", 345 + }, { 346 + "emit comments", 347 + `// a comment at the beginning of the file 348 + 349 + // a second comment 350 + 351 + // comment 352 + a: 5 353 + 354 + {} 355 + 356 + // a comment at the end of the file 357 + `, 358 + "<[0// a comment at the beginning of the file] [0// a second comment] <[d0// comment] a: 5>, <[2// a comment at the end of the file] {}>>", 359 + }} 360 + for _, tc := range testCases { 361 + t.Run(tc.desc, func(t *testing.T) { 362 + fset := token.NewFileSet() 363 + mode := []Option{AllErrors, ParseLambdas} 364 + if strings.Contains(tc.desc, "comments") { 365 + mode = append(mode, ParseComments) 366 + } 367 + f, err := ParseFile(fset, "input", tc.in, mode...) 368 + if err != nil { 369 + t.Errorf("unexpected error: %v", err) 370 + } 371 + if got := debugStr(f); got != tc.out { 372 + t.Errorf("\ngot %q;\nwant %q", got, tc.out) 373 + } 374 + }) 375 + } 376 + } 377 + 378 + func TestParseExpr(t *testing.T) { 379 + // just kicking the tires: 380 + // a valid arithmetic expression 381 + src := "a + b" 382 + x, err := parseExprString(src) 383 + if err != nil { 384 + t.Errorf("ParseExpr(%q): %v", src, err) 385 + } 386 + // sanity check 387 + if _, ok := x.(*ast.BinaryExpr); !ok { 388 + t.Errorf("ParseExpr(%q): got %T, want *BinaryExpr", src, x) 389 + } 390 + 391 + // an invalid expression 392 + src = "a + *" 393 + if _, err := parseExprString(src); err == nil { 394 + t.Errorf("ParseExpr(%q): got no error", src) 395 + } 396 + 397 + // a comma is not permitted unless automatically inserted 398 + src = "a + b\n" 399 + if _, err := parseExprString(src); err != nil { 400 + t.Errorf("ParseExpr(%q): got error %s", src, err) 401 + } 402 + src = "a + b;" 403 + if _, err := parseExprString(src); err == nil { 404 + t.Errorf("ParseExpr(%q): got no error", src) 405 + } 406 + 407 + // various other stuff following a valid expression 408 + const validExpr = "a + b" 409 + const anything = "dh3*#D)#_" 410 + for _, c := range "!)]};," { 411 + src := validExpr + string(c) + anything 412 + if _, err := parseExprString(src); err == nil { 413 + t.Errorf("ParseExpr(%q): got no error", src) 414 + } 415 + } 416 + 417 + // ParseExpr must not crash 418 + for _, src := range valids { 419 + parseExprString(src) 420 + } 421 + } 422 + 423 + func TestImports(t *testing.T) { 424 + var imports = map[string]bool{ 425 + `"a"`: true, 426 + `"a/b"`: true, 427 + `"a.b"`: true, 428 + `"m\x61th"`: true, 429 + `"greek/αβ"`: true, 430 + `""`: false, 431 + 432 + // Each of these pairs tests both `` vs "" strings 433 + // and also use of invalid characters spelled out as 434 + // escape sequences and written directly. 435 + // For example `"\x00"` tests import "\x00" 436 + // while "`\x00`" tests import `<actual-NUL-byte>`. 437 + "`a`": true, 438 + `"\x00"`: false, 439 + "`\x00`": false, 440 + `"\x7f"`: false, 441 + "`\x7f`": false, 442 + `"a!"`: false, 443 + "`a!`": false, 444 + `"a b"`: false, 445 + "`a b`": false, 446 + `"a\\b"`: false, 447 + "`a\\b`": false, 448 + "\"`a`\"": false, 449 + "`\"a\"`": false, 450 + `"\x80\x80"`: false, 451 + "`\x80\x80`": false, 452 + `"\xFFFD"`: false, 453 + "`\xFFFD`": false, 454 + } 455 + for path, isValid := range imports { 456 + t.Run(path, func(t *testing.T) { 457 + src := fmt.Sprintf("package p, import %s", path) 458 + _, err := ParseFile(token.NewFileSet(), "", src) 459 + switch { 460 + case err != nil && isValid: 461 + t.Errorf("ParseFile(%s): got %v; expected no error", src, err) 462 + case err == nil && !isValid: 463 + t.Errorf("ParseFile(%s): got no error; expected one", src) 464 + } 465 + }) 466 + } 467 + } 468 + 469 + func labelName(l ast.Label) string { 470 + name, _ := ast.LabelName(l) 471 + return name 472 + } 473 + 474 + func getField(file *ast.File, fieldname string) *ast.Field { 475 + get := func(elts []ast.Decl, name string) *ast.Field { 476 + for _, s := range elts { 477 + if s, ok := s.(*ast.Field); ok && labelName(s.Label) == name { 478 + return s 479 + } 480 + } 481 + return nil 482 + } 483 + elts := file.Decls 484 + var m *ast.Field 485 + for _, p := range strings.Split(fieldname, ".") { 486 + m = get(elts, p) 487 + if v, ok := m.Value.(*ast.StructLit); ok { 488 + elts = v.Elts 489 + } else { 490 + break 491 + } 492 + } 493 + return m 494 + } 495 + 496 + // Don't use CommentGroup.Text() - we want to see exact comment text. 497 + func commentText(c *ast.CommentGroup) string { 498 + var buf bytes.Buffer 499 + if c != nil { 500 + for _, c := range c.List { 501 + buf.WriteString(c.Text) 502 + } 503 + } 504 + return buf.String() 505 + } 506 + 507 + // TestIncompleteSelection ensures that an incomplete selector 508 + // expression is parsed as a (blank) *SelectorExpr, not a 509 + // *BadExpr. 510 + func TestIncompleteSelection(t *testing.T) { 511 + for _, src := range []string{ 512 + "{ a: fmt. }", // at end of object 513 + "{ a: fmt.\n\"a\": x }", // not at end of struct 514 + } { 515 + t.Run("", func(t *testing.T) { 516 + fset := token.NewFileSet() 517 + f, err := ParseFile(fset, "", src) 518 + if err == nil { 519 + t.Fatalf("ParseFile(%s) succeeded unexpectedly", src) 520 + } 521 + 522 + const wantErr = "expected selector" 523 + if !strings.Contains(err.Error(), wantErr) { 524 + t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr) 525 + } 526 + 527 + var sel *ast.SelectorExpr 528 + ast.Walk(f, func(n ast.Node) bool { 529 + if n, ok := n.(*ast.SelectorExpr); ok { 530 + sel = n 531 + } 532 + return true 533 + }, nil) 534 + if sel == nil { 535 + t.Fatalf("found no *SelectorExpr: %#v %s", f.Decls[0], debugStr(f)) 536 + } 537 + const wantSel = "&{{<nil>} fmt _}" 538 + if fmt.Sprint(sel) != wantSel { 539 + t.Fatalf("found selector %v, want %s", sel, wantSel) 540 + } 541 + }) 542 + } 543 + }
+41
cue/parser/performance_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package parser 16 + 17 + import ( 18 + "io/ioutil" 19 + "testing" 20 + 21 + "cuelang.org/go/cue/token" 22 + ) 23 + 24 + var src = readFile("testdata/commas.src") 25 + 26 + func readFile(filename string) []byte { 27 + data, err := ioutil.ReadFile(filename) 28 + if err != nil { 29 + panic(err) 30 + } 31 + return data 32 + } 33 + 34 + func BenchmarkParse(b *testing.B) { 35 + b.SetBytes(int64(len(src))) 36 + for i := 0; i < b.N; i++ { 37 + if _, err := ParseFile(token.NewFileSet(), "", src, ParseComments); err != nil { 38 + b.Fatalf("benchmark failed due to parse error: %s", err) 39 + } 40 + } 41 + }
+302
cue/parser/print.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package parser 16 + 17 + import ( 18 + "fmt" 19 + "strconv" 20 + "strings" 21 + 22 + "cuelang.org/go/cue/ast" 23 + "cuelang.org/go/cue/token" 24 + "cuelang.org/go/internal" 25 + ) 26 + 27 + func init() { 28 + internal.DebugStr = debugStr 29 + } 30 + 31 + func debugStr(x interface{}) (out string) { 32 + if n, ok := x.(ast.Node); ok { 33 + comments := "" 34 + for _, g := range n.Comments() { 35 + comments += debugStr(g) 36 + } 37 + if comments != "" { 38 + defer func() { out = "<" + comments + out + ">" }() 39 + } 40 + } 41 + switch v := x.(type) { 42 + case *ast.File: 43 + out := "" 44 + if v.Name != nil { 45 + out += "package " 46 + out += debugStr(v.Name) 47 + out += ", " 48 + } 49 + out += debugStr(v.Decls) 50 + return out 51 + 52 + case *ast.Alias: 53 + out := debugStr(v.Ident) 54 + out += " = " 55 + out += debugStr(v.Expr) 56 + return out 57 + 58 + case *ast.BottomLit: 59 + return "_|_" 60 + 61 + case *ast.BasicLit: 62 + return v.Value 63 + 64 + case *ast.Interpolation: 65 + for _, e := range v.Elts { 66 + out += debugStr(e) 67 + } 68 + return out 69 + 70 + case *ast.EmitDecl: 71 + // out := "<" 72 + out += debugStr(v.Expr) 73 + // out += ">" 74 + return out 75 + 76 + case *ast.ImportDecl: 77 + out := "import " 78 + if v.Lparen != token.NoPos { 79 + out += "( " 80 + out += debugStr(v.Specs) 81 + out += " )" 82 + } else { 83 + out += debugStr(v.Specs) 84 + } 85 + return out 86 + 87 + case *ast.ComprehensionDecl: 88 + out := debugStr(v.Field) 89 + out += " " 90 + out += debugStr(v.Clauses) 91 + return out 92 + 93 + case *ast.StructLit: 94 + out := "{" 95 + out += debugStr(v.Elts) 96 + out += "}" 97 + return out 98 + 99 + case *ast.ListLit: 100 + out := "[" 101 + out += debugStr(v.Elts) 102 + if v.Ellipsis != token.NoPos || v.Type != nil { 103 + if out != "[" { 104 + out += ", " 105 + } 106 + out += "..." 107 + if v.Type != nil { 108 + out += debugStr(v.Type) 109 + } 110 + } 111 + out += "]" 112 + return out 113 + 114 + case *ast.ListComprehension: 115 + out := "[" 116 + out += debugStr(v.Expr) 117 + out += " " 118 + out += debugStr(v.Clauses) 119 + out += "]" 120 + return out 121 + 122 + case *ast.ForClause: 123 + out := "for " 124 + if v.Key != nil { 125 + out += debugStr(v.Key) 126 + out += ": " 127 + } 128 + out += debugStr(v.Value) 129 + out += " in " 130 + out += debugStr(v.Source) 131 + return out 132 + 133 + case *ast.IfClause: 134 + out := "if " 135 + out += debugStr(v.Condition) 136 + return out 137 + 138 + case *ast.Field: 139 + out := debugStr(v.Label) 140 + if v.Value != nil { 141 + out += ": " 142 + out += debugStr(v.Value) 143 + } 144 + return out 145 + 146 + case *ast.LambdaExpr: 147 + out := "(" 148 + for _, m := range v.Params { 149 + out += debugStr(m) 150 + out += "," 151 + } 152 + out += ") -> " 153 + out += debugStr(v.Expr) 154 + return out 155 + 156 + case *ast.Ident: 157 + return v.Name 158 + 159 + case *ast.ExprLabel: 160 + out := "[" 161 + out += debugStr(v.Label) 162 + out += "]" 163 + return out 164 + 165 + case *ast.TemplateLabel: 166 + out := "<" 167 + out += debugStr(v.Ident) 168 + out += ">" 169 + return out 170 + 171 + case *ast.SelectorExpr: 172 + return debugStr(v.X) + "." + debugStr(v.Sel) 173 + 174 + case *ast.CallExpr: 175 + out := debugStr(v.Fun) 176 + out += "(" 177 + out += debugStr(v.Args) 178 + out += ")" 179 + return out 180 + 181 + case *ast.Ellipsis: 182 + return debugStr(v.Elt) + "..." 183 + 184 + case *ast.ParenExpr: 185 + out := "(" 186 + out += debugStr(v.X) 187 + out += ")" 188 + return out 189 + 190 + case *ast.UnaryExpr: 191 + return v.Op.String() + debugStr(v.X) 192 + 193 + case *ast.BinaryExpr: 194 + out := debugStr(v.X) 195 + op := v.Op.String() 196 + if 'a' <= op[0] && op[0] <= 'z' { 197 + op = fmt.Sprintf(" %s ", op) 198 + } 199 + out += op 200 + out += debugStr(v.Y) 201 + return out 202 + 203 + case []*ast.CommentGroup: 204 + var a []string 205 + for _, c := range v { 206 + a = append(a, debugStr(c)) 207 + } 208 + return strings.Join(a, "\n") 209 + 210 + case *ast.CommentGroup: 211 + str := "[" 212 + if v.Doc { 213 + str += "d" 214 + } 215 + if v.Line { 216 + str += "l" 217 + } 218 + str += strconv.Itoa(int(v.Position)) 219 + var a = []string{} 220 + for _, c := range v.List { 221 + a = append(a, c.Text) 222 + } 223 + return str + strings.Join(a, " ") + "] " 224 + 225 + case *ast.IndexExpr: 226 + out := debugStr(v.X) 227 + out += "[" 228 + out += debugStr(v.Index) 229 + out += "]" 230 + return out 231 + 232 + case *ast.SliceExpr: 233 + out := debugStr(v.X) 234 + out += "[" 235 + out += debugStr(v.Low) 236 + out += ":" 237 + out += debugStr(v.High) 238 + out += "]" 239 + return out 240 + 241 + case *ast.ImportSpec: 242 + out := "" 243 + if v.Name != nil { 244 + out += debugStr(v.Name) 245 + out += " " 246 + } 247 + out += debugStr(v.Path) 248 + return out 249 + 250 + case []ast.Decl: 251 + if len(v) == 0 { 252 + return "" 253 + } 254 + out := "" 255 + for _, d := range v { 256 + out += debugStr(d) 257 + out += sep 258 + } 259 + return out[:len(out)-len(sep)] 260 + 261 + case []ast.Clause: 262 + if len(v) == 0 { 263 + return "" 264 + } 265 + out := "" 266 + for _, c := range v { 267 + out += debugStr(c) 268 + out += " " 269 + } 270 + return out 271 + 272 + case []ast.Expr: 273 + if len(v) == 0 { 274 + return "" 275 + } 276 + out := "" 277 + for _, d := range v { 278 + out += debugStr(d) 279 + out += sep 280 + } 281 + return out[:len(out)-len(sep)] 282 + 283 + case []*ast.ImportSpec: 284 + if len(v) == 0 { 285 + return "" 286 + } 287 + out := "" 288 + for _, d := range v { 289 + out += debugStr(d) 290 + out += sep 291 + } 292 + return out[:len(out)-len(sep)] 293 + 294 + default: 295 + if v == nil { 296 + return "" 297 + } 298 + return fmt.Sprintf("<%T>", x) 299 + } 300 + } 301 + 302 + const sep = ", "
+209
cue/parser/resolve.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // This file implements scopes and the objects they contain. 16 + 17 + package parser 18 + 19 + import ( 20 + "bytes" 21 + "fmt" 22 + 23 + "cuelang.org/go/cue/ast" 24 + "cuelang.org/go/cue/token" 25 + ) 26 + 27 + // resolve resolves all identifiers in a file. Unresolved identifiers are 28 + // recorded in Unresolved. 29 + func resolve(f *ast.File, errFn func(pos token.Pos, msg string)) { 30 + walk(&scope{errFn: errFn}, f) 31 + } 32 + 33 + // A Scope maintains the set of named language entities declared 34 + // in the scope and a link to the immediately surrounding (outer) 35 + // scope. 36 + // 37 + type scope struct { 38 + file *ast.File 39 + outer *scope 40 + node ast.Node 41 + index map[string]ast.Node 42 + 43 + errFn func(p token.Pos, msg string) 44 + } 45 + 46 + func newScope(f *ast.File, outer *scope, node ast.Node, decls []ast.Decl) *scope { 47 + const n = 4 // initial scope capacity 48 + s := &scope{ 49 + file: f, 50 + outer: outer, 51 + node: node, 52 + index: make(map[string]ast.Node, n), 53 + errFn: outer.errFn, 54 + } 55 + for _, d := range decls { 56 + switch x := d.(type) { 57 + case *ast.Field: 58 + if name, ok := ast.LabelName(x.Label); ok { 59 + s.insert(name, x.Value) 60 + } 61 + case *ast.Alias: 62 + name := x.Ident.Name 63 + s.insert(name, x) 64 + // Handle imports 65 + } 66 + } 67 + return s 68 + } 69 + 70 + func (s *scope) insert(name string, n ast.Node) { 71 + if _, existing := s.lookup(name); existing != nil { 72 + _, isAlias1 := n.(*ast.Alias) 73 + _, isAlias2 := existing.(*ast.Alias) 74 + if isAlias1 != isAlias2 { 75 + s.errFn(n.Pos(), "cannot have alias and non-alias with the same name") 76 + return 77 + } else if isAlias1 || isAlias2 { 78 + s.errFn(n.Pos(), "cannot have two aliases with the same name in the same scope") 79 + return 80 + } 81 + } 82 + s.index[name] = n 83 + } 84 + 85 + func (s *scope) lookup(name string) (obj, node ast.Node) { 86 + last := s 87 + for s != nil { 88 + if n, ok := s.index[name]; ok { 89 + if last.node == n { 90 + return nil, n 91 + } 92 + return s.node, n 93 + } 94 + s, last = s.outer, s 95 + } 96 + return nil, nil 97 + } 98 + 99 + func (s *scope) After(n ast.Node) {} 100 + func (s *scope) Before(n ast.Node) (w visitor) { 101 + switch x := n.(type) { 102 + case *ast.File: 103 + s := newScope(x, s, x, x.Decls) 104 + // Support imports. 105 + for _, d := range x.Decls { 106 + walk(s, d) 107 + } 108 + return nil 109 + 110 + case *ast.StructLit: 111 + return newScope(s.file, s, x, x.Elts) 112 + 113 + case *ast.ComprehensionDecl: 114 + s = scopeClauses(s, x.Clauses) 115 + 116 + case *ast.ListComprehension: 117 + s = scopeClauses(s, x.Clauses) 118 + 119 + case *ast.Field: 120 + switch label := x.Label.(type) { 121 + case *ast.Interpolation: 122 + walk(s, label) 123 + case *ast.ExprLabel: 124 + walk(s, x.Label) 125 + case *ast.TemplateLabel: 126 + s := newScope(s.file, s, x, nil) 127 + name, _ := ast.LabelName(label) 128 + s.insert(name, x.Label) // Field used for entire lambda. 129 + walk(s, x.Value) 130 + return nil 131 + } 132 + // Disallow referring to the current LHS name (this applies recursively) 133 + if x.Value != nil { 134 + walk(s, x.Value) 135 + } 136 + return nil 137 + 138 + case *ast.Alias: 139 + // Disallow referring to the current LHS name. 140 + name := x.Ident.Name 141 + saved := s.index[name] 142 + delete(s.index, name) // The same name may still appear in another scope 143 + 144 + if x.Expr != nil { 145 + walk(s, x.Expr) 146 + } 147 + s.index[name] = saved 148 + return nil 149 + 150 + case *ast.ImportSpec: 151 + return nil 152 + 153 + case *ast.SelectorExpr: 154 + walk(s, x.X) 155 + return nil 156 + 157 + case *ast.LambdaExpr: 158 + s = newScope(s.file, s, x, nil) 159 + for _, p := range x.Params { 160 + name, _ := ast.LabelName(p.Label) 161 + s.insert(name, p) 162 + if p.Value == nil { 163 + // TODO: make this optional 164 + p.Value = ast.NewIdent("_") 165 + s.insert(name, p) 166 + } 167 + } 168 + 169 + case *ast.Ident: 170 + if obj, node := s.lookup(x.Name); node != nil { 171 + x.Node = node 172 + x.Scope = obj 173 + } else { 174 + s.file.Unresolved = append(s.file.Unresolved, x) 175 + } 176 + return nil 177 + } 178 + return s 179 + } 180 + 181 + func scopeClauses(s *scope, clauses []ast.Clause) *scope { 182 + for _, c := range clauses { 183 + if f, ok := c.(*ast.ForClause); ok { // TODO(let): support let clause 184 + walk(s, f.Source) 185 + s = newScope(s.file, s, f, nil) 186 + if f.Key != nil { 187 + s.insert(f.Key.Name, f.Key) 188 + } 189 + s.insert(f.Value.Name, f.Value) 190 + } else { 191 + walk(s, c) 192 + } 193 + } 194 + return s 195 + } 196 + 197 + // Debugging support 198 + func (s *scope) String() string { 199 + var buf bytes.Buffer 200 + fmt.Fprintf(&buf, "scope %p {", s) 201 + if s != nil && len(s.index) > 0 { 202 + fmt.Fprintln(&buf) 203 + for name := range s.index { 204 + fmt.Fprintf(&buf, "\t%v\n", name) 205 + } 206 + } 207 + fmt.Fprintf(&buf, "}\n") 208 + return buf.String() 209 + }
+50
cue/parser/short_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // This file contains test cases for short valid and invalid programs. 16 + 17 + package parser 18 + 19 + import "testing" 20 + 21 + var valids = []string{ 22 + "\n", 23 + `{}`, 24 + `{ foo: "fmt", bar: () -> { baz: fmt.Service("Hello, World!") }, }`, 25 + `{ <Name>: foo }`, 26 + `{ a: 3 }`, 27 + } 28 + 29 + func TestValid(t *testing.T) { 30 + for _, src := range valids { 31 + t.Run(src, func(t *testing.T) { 32 + checkErrors(t, src, src) 33 + }) 34 + } 35 + } 36 + 37 + func TestInvalid(t *testing.T) { 38 + invalids := []string{ 39 + `foo !/* ERROR "expected label or ':', found '!'" */`, 40 + // `foo: /* ERROR "expected operand, found '}'" */}`, // TODO: wrong position 41 + `{ <Name 42 + /* ERROR "expected '>', found newline" */ >: foo }`, 43 + // TODO: 44 + // `{ </* ERROR "expected identifier, found newline" */ 45 + // Name>: foo }`, 46 + } 47 + for _, src := range invalids { 48 + checkErrors(t, src, src) 49 + } 50 + }
+35
cue/parser/testdata/commas.src
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // Test case for error messages/parser synchronization 16 + // after missing commas. 17 + package foo 18 + 19 + import "path/to/pkg" 20 + import name "path/to/pkg" 21 + import . "path/to/pkg" 22 + import /* ERROR "expected 'STRING', found newline" */ 23 + import err /* ERROR "expected 'STRING', found newline" */ 24 + 25 + foo: [ 26 + 0 // legal JSON 27 + ] 28 + 29 + bar: [ 30 + 0, 31 + 1, 32 + 2, 33 + 3 34 + ] 35 +
+5
cue/parser/testdata/test.cue
··· 1 + 2 + import "math" 3 + 4 + foo: 1 5 + bar: "baz"
+281
cue/parser/walk.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package parser 16 + 17 + import ( 18 + "fmt" 19 + 20 + "cuelang.org/go/cue/ast" 21 + "cuelang.org/go/cue/token" 22 + ) 23 + 24 + // TODO: use ast.Walk or adopt that version to allow visitors. 25 + 26 + // A visitor's before method is invoked for each node encountered by Walk. 27 + // If the result visitor w is not nil, Walk visits each of the children 28 + // of node with the visitor w, followed by a call of w.After. 29 + type visitor interface { 30 + Before(node ast.Node) (w visitor) 31 + After(node ast.Node) 32 + } 33 + 34 + // Helper functions for common node lists. They may be empty. 35 + 36 + func walkIdentList(v visitor, list []*ast.Ident) { 37 + for _, x := range list { 38 + walk(v, x) 39 + } 40 + } 41 + 42 + func walkExprList(v visitor, list []ast.Expr) { 43 + for _, x := range list { 44 + walk(v, x) 45 + } 46 + } 47 + 48 + func walkDeclList(v visitor, list []ast.Decl) { 49 + for _, x := range list { 50 + walk(v, x) 51 + } 52 + } 53 + 54 + // walk traverses an AST in depth-first order: It starts by calling 55 + // v.Visit(node); node must not be nil. If the visitor w returned by 56 + // v.Visit(node) is not nil, walk is invoked recursively with visitor 57 + // w for each of the non-nil children of node, followed by a call of 58 + // w.Visit(nil). 59 + // 60 + func walk(v visitor, node ast.Node) { 61 + if v = v.Before(node); v == nil { 62 + return 63 + } 64 + 65 + // TODO: record the comment groups and interleave with the values like for 66 + // parsing and printing? 67 + for _, c := range node.Comments() { 68 + walk(v, c) 69 + } 70 + 71 + // walk children 72 + // (the order of the cases matches the order 73 + // of the corresponding node types in go) 74 + switch n := node.(type) { 75 + // Comments and fields 76 + case *ast.Comment: 77 + // nothing to do 78 + 79 + case *ast.CommentGroup: 80 + for _, c := range n.List { 81 + walk(v, c) 82 + } 83 + 84 + case *ast.Field: 85 + walk(v, n.Label) 86 + if n.Value != nil { 87 + walk(v, n.Value) 88 + } 89 + 90 + case *ast.LambdaExpr: 91 + for _, p := range n.Params { 92 + walk(v, p) 93 + } 94 + walk(v, n.Expr) 95 + 96 + case *ast.StructLit: 97 + for _, f := range n.Elts { 98 + walk(v, f) 99 + } 100 + 101 + // Expressions 102 + case *ast.BottomLit, *ast.BadExpr, *ast.Ident, *ast.BasicLit: 103 + // nothing to do 104 + 105 + case *ast.ExprLabel: 106 + walk(v, n.Label) 107 + 108 + case *ast.TemplateLabel: 109 + walk(v, n.Ident) 110 + 111 + case *ast.Interpolation: 112 + for _, e := range n.Elts { 113 + walk(v, e) 114 + } 115 + 116 + case *ast.Ellipsis: 117 + if n.Elt != nil { 118 + walk(v, n.Elt) 119 + } 120 + 121 + case *ast.ListLit: 122 + walkExprList(v, n.Elts) 123 + if n.Type != nil { 124 + walk(v, n.Type) 125 + } 126 + 127 + case *ast.ParenExpr: 128 + walk(v, n.X) 129 + 130 + case *ast.SelectorExpr: 131 + walk(v, n.X) 132 + walk(v, n.Sel) 133 + 134 + case *ast.IndexExpr: 135 + walk(v, n.X) 136 + walk(v, n.Index) 137 + 138 + case *ast.SliceExpr: 139 + walk(v, n.X) 140 + if n.Low != nil { 141 + walk(v, n.Low) 142 + } 143 + if n.High != nil { 144 + walk(v, n.High) 145 + } 146 + 147 + case *ast.CallExpr: 148 + walk(v, n.Fun) 149 + walkExprList(v, n.Args) 150 + 151 + case *ast.UnaryExpr: 152 + walk(v, n.X) 153 + 154 + case *ast.BinaryExpr: 155 + walk(v, n.X) 156 + walk(v, n.Y) 157 + 158 + // Declarations 159 + case *ast.ImportSpec: 160 + if n.Name != nil { 161 + walk(v, n.Name) 162 + } 163 + walk(v, n.Path) 164 + 165 + case *ast.BadDecl: 166 + // nothing to do 167 + 168 + case *ast.ImportDecl: 169 + for _, s := range n.Specs { 170 + walk(v, s) 171 + } 172 + 173 + case *ast.EmitDecl: 174 + walk(v, n.Expr) 175 + 176 + case *ast.Alias: 177 + walk(v, n.Ident) 178 + walk(v, n.Expr) 179 + 180 + case *ast.ComprehensionDecl: 181 + walk(v, n.Field) 182 + for _, c := range n.Clauses { 183 + walk(v, c) 184 + } 185 + 186 + // Files and packages 187 + case *ast.File: 188 + if n.Name != nil { 189 + walk(v, n.Name) 190 + } 191 + walkDeclList(v, n.Decls) 192 + // don't walk n.Comments - they have been 193 + // visited already through the individual 194 + // nodes 195 + 196 + case *ast.ListComprehension: 197 + walk(v, n.Expr) 198 + for _, c := range n.Clauses { 199 + walk(v, c) 200 + } 201 + 202 + case *ast.ForClause: 203 + if n.Key != nil { 204 + walk(v, n.Key) 205 + } 206 + walk(v, n.Value) 207 + walk(v, n.Source) 208 + 209 + case *ast.IfClause: 210 + walk(v, n.Condition) 211 + 212 + default: 213 + panic(fmt.Sprintf("Walk: unexpected node type %T", n)) 214 + } 215 + 216 + v.After(node) 217 + } 218 + 219 + type inspector struct { 220 + before func(ast.Node) bool 221 + after func(ast.Node) 222 + 223 + commentStack []commentFrame 224 + current commentFrame 225 + } 226 + 227 + type commentFrame struct { 228 + cg []*ast.CommentGroup 229 + pos int8 230 + } 231 + 232 + func (f *inspector) Before(node ast.Node) visitor { 233 + if f.before == nil || f.before(node) { 234 + f.commentStack = append(f.commentStack, f.current) 235 + f.current = commentFrame{cg: node.Comments()} 236 + f.visitComments(f.current.pos) 237 + return f 238 + } 239 + return nil 240 + } 241 + 242 + func (f *inspector) After(node ast.Node) { 243 + f.visitComments(127) 244 + p := len(f.commentStack) - 1 245 + f.current = f.commentStack[p] 246 + f.commentStack = f.commentStack[:p] 247 + f.current.pos++ 248 + if f.after != nil { 249 + f.after(node) 250 + } 251 + } 252 + 253 + func (f *inspector) Token(t token.Token) { 254 + f.current.pos++ 255 + } 256 + 257 + func (f *inspector) setPos(i int8) { 258 + f.current.pos = i 259 + } 260 + 261 + func (f *inspector) visitComments(pos int8) { 262 + c := &f.current 263 + for ; len(c.cg) > 0; c.cg = c.cg[1:] { 264 + cg := c.cg[0] 265 + if cg.Position == pos { 266 + continue 267 + } 268 + if f.before == nil || f.before(cg) { 269 + for _, c := range cg.List { 270 + if f.before == nil || f.before(c) { 271 + if f.after != nil { 272 + f.after(c) 273 + } 274 + } 275 + } 276 + if f.after != nil { 277 + f.after(cg) 278 + } 279 + } 280 + } 281 + }
+20
internal/internal.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package internal // import "cuelang.org/go/internal" 16 + 17 + // TODO: refactor packages as to make this package unnecessary. 18 + 19 + // DebugStr prints a syntax node. 20 + var DebugStr func(x interface{}) string