blob: 3c62eb64e1a5c14e9c74c26d20812f0e8613ae2a [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
79 // ParseLambdas enables parsing of Lambdas. By default these are disabled.
80 //
81 // NOTE: this option is for internal use only and can be made unavailable at
82 // any time.
83 ParseLambdas Option = parseLambdas
84 parseLambdas = func(p *parser) {
85 p.mode |= parseLambdasMode
86 }
87
88 // Trace causes parsing to print a trace of parsed productions.
89 Trace Option = traceOpt
90 traceOpt = func(p *parser) {
91 p.mode |= traceMode
92 }
93
94 // DeclarationErrors causes parsing to report declaration errors.
95 DeclarationErrors Option = declarationErrors
96 declarationErrors = func(p *parser) {
97 p.mode |= declarationErrorsMode
98 }
99
100 // AllErrors causes all errors to be reported (not just the first 10 on different lines).
101 AllErrors Option = allErrors
102 allErrors = func(p *parser) {
103 p.mode |= allErrorsMode
104 }
105
106 // AllowPartial allows the parser to be used on a prefix buffer.
107 AllowPartial Option = allowPartial
108 allowPartial = func(p *parser) {
109 p.mode |= partialMode
110 }
111)
112
113// A mode value is a set of flags (or 0).
114// They control the amount of source code parsed and other optional
115// parser functionality.
116type mode uint
117
118const (
119 packageClauseOnlyMode mode = 1 << iota // stop parsing after package clause
120 importsOnlyMode // stop parsing after import declarations
121 parseCommentsMode // parse comments and add them to AST
122 parseLambdasMode
123 partialMode
124 traceMode // print a trace of parsed productions
125 declarationErrorsMode // report declaration errors
126 allErrorsMode // report all errors (not just the first 10 on different lines)
127)
128
129// ParseFile parses the source code of a single CUE source file and returns
130// the corresponding File node. The source code may be provided via
131// the filename of the source file, or via the src parameter.
132//
133// If src != nil, ParseFile parses the source from src and the filename is
134// only used when recording position information. The type of the argument
135// for the src parameter must be string, []byte, or io.Reader.
136// If src == nil, ParseFile parses the file specified by filename.
137//
138// The mode parameter controls the amount of source text parsed and other
139// optional parser functionality. Position information is recorded in the
140// file set fset, which must not be nil.
141//
142// If the source couldn't be read, the returned AST is nil and the error
143// indicates the specific failure. If the source was read but syntax
144// errors were found, the result is a partial AST (with Bad* nodes
145// representing the fragments of erroneous source code). Multiple errors
146// are returned via a ErrorList which is sorted by file position.
147func ParseFile(p *token.FileSet, filename string, src interface{}, mode ...Option) (f *ast.File, err error) {
148 if p == nil {
149 panic("ParseFile: no file.FileSet provided (fset == nil)")
150 }
151
152 // get source
153 text, err := readSource(filename, src)
154 if err != nil {
155 return nil, err
156 }
157
158 var pp parser
159 defer func() {
160 if e := recover(); e != nil {
161 // resume same panic if it's not a bailout
162 if _, ok := e.(bailout); !ok {
163 panic(e)
164 }
165 }
166
167 // set result values
168 if f == nil {
169 // source is not a valid Go source file - satisfy
170 // ParseFile API and return a valid (but) empty
171 // *File
172 f = &ast.File{
173 Name: new(ast.Ident),
174 // Scope: NewScope(nil),
175 }
176 }
177
178 pp.errors.Sort()
179 err = pp.errors.Err()
180 }()
181
182 // parse source
183 pp.init(p, filename, text, mode)
184 f = pp.parseFile()
185 if f == nil {
186 return nil, pp.errors
187 }
188 f.Filename = filename
189 resolve(f, pp.error)
190
191 return
192}
193
194// ParseExpr is a convenience function for parsing an expression.
195// The arguments have the same meaning as for Parse, but the source must
196// be a valid CUE (type or value) expression. Specifically, fset must not
197// be nil.
198func ParseExpr(fset *token.FileSet, filename string, src interface{}, mode ...Option) (ast.Expr, error) {
199 if fset == nil {
200 panic("ParseExprFrom: no file.FileSet provided (fset == nil)")
201 }
202
203 // get source
204 text, err := readSource(filename, src)
205 if err != nil {
206 return nil, err
207 }
208
209 var p parser
210 defer func() {
211 if e := recover(); e != nil {
212 // resume same panic if it's not a bailout
213 if _, ok := e.(bailout); !ok {
214 panic(e)
215 }
216 }
217 p.errors.Sort()
218 err = p.errors.Err()
219 }()
220
221 // parse expr
222 p.init(fset, filename, text, mode)
223 // Set up pkg-level scopes to avoid nil-pointer errors.
224 // This is not needed for a correct expression x as the
225 // parser will be ok with a nil topScope, but be cautious
226 // in case of an erroneous x.
227 e := p.parseRHS()
228
229 // If a comma was inserted, consume it;
230 // report an error if there's more tokens.
231 if p.tok == token.COMMA && p.lit == "\n" {
232 p.next()
233 }
234 if p.mode&partialMode == 0 {
235 p.expect(token.EOF)
236 }
237
238 if p.errors.Len() > 0 {
239 p.errors.Sort()
240 return nil, p.errors.Err()
241 }
242
243 return e, nil
244}
245
246// parseExprString is a convenience function for obtaining the AST of an
247// expression x. The position information recorded in the AST is undefined. The
248// filename used in error messages is the empty string.
249func parseExprString(x string) (ast.Expr, error) {
250 return ParseExpr(token.NewFileSet(), "", []byte(x))
251}