blob: d921adc8c4ee7121006b956467e87f2c7443ef3f [file] [log] [blame]
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +01001// 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
17package parser
18
19import (
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//
33func 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
Marcel van Lohuizen76b92b52018-12-16 10:47:03 +010057// Option specifies a parse option.
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +010058type Option func(p *parser)
59
60var (
61 // PackageClauseOnly causes parsing to stop after the package clause.
62 PackageClauseOnly Option = packageClauseOnly
63 packageClauseOnly = func(p *parser) {
64 p.mode |= packageClauseOnlyMode
65 }
66
67 // ImportsOnly causes parsing to stop parsing after the import declarations.
68 ImportsOnly Option = importsOnly
69 importsOnly = func(p *parser) {
70 p.mode |= importsOnlyMode
71 }
72
73 // ParseComments causes comments to be parsed.
74 ParseComments Option = parseComments
75 parseComments = func(p *parser) {
76 p.mode |= parseCommentsMode
77 }
78
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +010079 // Trace causes parsing to print a trace of parsed productions.
80 Trace Option = traceOpt
81 traceOpt = func(p *parser) {
82 p.mode |= traceMode
83 }
84
85 // DeclarationErrors causes parsing to report declaration errors.
86 DeclarationErrors Option = declarationErrors
87 declarationErrors = func(p *parser) {
88 p.mode |= declarationErrorsMode
89 }
90
91 // AllErrors causes all errors to be reported (not just the first 10 on different lines).
92 AllErrors Option = allErrors
93 allErrors = func(p *parser) {
94 p.mode |= allErrorsMode
95 }
96
97 // AllowPartial allows the parser to be used on a prefix buffer.
98 AllowPartial Option = allowPartial
99 allowPartial = func(p *parser) {
100 p.mode |= partialMode
101 }
102)
103
104// A mode value is a set of flags (or 0).
105// They control the amount of source code parsed and other optional
106// parser functionality.
107type mode uint
108
109const (
110 packageClauseOnlyMode mode = 1 << iota // stop parsing after package clause
111 importsOnlyMode // stop parsing after import declarations
112 parseCommentsMode // parse comments and add them to AST
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100113 partialMode
114 traceMode // print a trace of parsed productions
115 declarationErrorsMode // report declaration errors
116 allErrorsMode // report all errors (not just the first 10 on different lines)
117)
118
119// ParseFile parses the source code of a single CUE source file and returns
120// the corresponding File node. The source code may be provided via
121// the filename of the source file, or via the src parameter.
122//
123// If src != nil, ParseFile parses the source from src and the filename is
124// only used when recording position information. The type of the argument
125// for the src parameter must be string, []byte, or io.Reader.
126// If src == nil, ParseFile parses the file specified by filename.
127//
128// The mode parameter controls the amount of source text parsed and other
129// optional parser functionality. Position information is recorded in the
130// file set fset, which must not be nil.
131//
132// If the source couldn't be read, the returned AST is nil and the error
133// indicates the specific failure. If the source was read but syntax
134// errors were found, the result is a partial AST (with Bad* nodes
135// representing the fragments of erroneous source code). Multiple errors
136// are returned via a ErrorList which is sorted by file position.
137func ParseFile(p *token.FileSet, filename string, src interface{}, mode ...Option) (f *ast.File, err error) {
138 if p == nil {
139 panic("ParseFile: no file.FileSet provided (fset == nil)")
140 }
141
142 // get source
143 text, err := readSource(filename, src)
144 if err != nil {
145 return nil, err
146 }
147
148 var pp parser
149 defer func() {
Marcel van Lohuizendfd47282019-03-14 12:01:33 +0100150 if pp.panicking {
151 recover()
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100152 }
153
154 // set result values
155 if f == nil {
156 // source is not a valid Go source file - satisfy
157 // ParseFile API and return a valid (but) empty
158 // *File
159 f = &ast.File{
160 Name: new(ast.Ident),
161 // Scope: NewScope(nil),
162 }
163 }
164
165 pp.errors.Sort()
166 err = pp.errors.Err()
167 }()
168
169 // parse source
170 pp.init(p, filename, text, mode)
171 f = pp.parseFile()
172 if f == nil {
173 return nil, pp.errors
174 }
175 f.Filename = filename
176 resolve(f, pp.error)
177
178 return
179}
180
181// ParseExpr is a convenience function for parsing an expression.
182// The arguments have the same meaning as for Parse, but the source must
183// be a valid CUE (type or value) expression. Specifically, fset must not
184// be nil.
185func ParseExpr(fset *token.FileSet, filename string, src interface{}, mode ...Option) (ast.Expr, error) {
186 if fset == nil {
187 panic("ParseExprFrom: no file.FileSet provided (fset == nil)")
188 }
189
190 // get source
191 text, err := readSource(filename, src)
192 if err != nil {
193 return nil, err
194 }
195
196 var p parser
197 defer func() {
Marcel van Lohuizendfd47282019-03-14 12:01:33 +0100198 if p.panicking {
199 recover()
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100200 }
201 p.errors.Sort()
202 err = p.errors.Err()
203 }()
204
205 // parse expr
206 p.init(fset, filename, text, mode)
207 // Set up pkg-level scopes to avoid nil-pointer errors.
208 // This is not needed for a correct expression x as the
209 // parser will be ok with a nil topScope, but be cautious
210 // in case of an erroneous x.
211 e := p.parseRHS()
212
213 // If a comma was inserted, consume it;
214 // report an error if there's more tokens.
215 if p.tok == token.COMMA && p.lit == "\n" {
216 p.next()
217 }
218 if p.mode&partialMode == 0 {
219 p.expect(token.EOF)
220 }
221
222 if p.errors.Len() > 0 {
223 p.errors.Sort()
224 return nil, p.errors.Err()
225 }
Marcel van Lohuizenea2de892019-04-03 22:34:44 +0200226 resolveExpr(e, p.error)
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100227
228 return e, nil
229}
230
231// parseExprString is a convenience function for obtaining the AST of an
232// expression x. The position information recorded in the AST is undefined. The
233// filename used in error messages is the empty string.
234func parseExprString(x string) (ast.Expr, error) {
235 return ParseExpr(token.NewFileSet(), "", []byte(x))
236}