Skip to content

Commit 211309c

Browse files
committed
cue/parser: add Config type
This makes the parser configuration public, which makes it possible for `ParseFile` middleware such as `cue/load.Config.ParseFile` to see what options have been passed and modify them as desired. Since `Config` implements `Option`, it will be straightforward to call `parser.ParseFile` with a `Config` value. This API change should be fully backwardly compatible. Signed-off-by: Roger Peppe <[email protected]> Change-Id: I50fe0de41f9d2f1ff1107ea3bbae510860aae9f8 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1217123 TryBot-Result: CUEcueckoo <[email protected]> Reviewed-by: Daniel Martí <[email protected]> Reviewed-by: Marcel van Lohuizen <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent ab9ac54 commit 211309c

File tree

2 files changed

+119
-83
lines changed

2 files changed

+119
-83
lines changed

cue/parser/interface.go

Lines changed: 108 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -17,80 +17,140 @@
1717
package parser
1818

1919
import (
20+
"fmt"
21+
2022
"cuelang.org/go/cue/ast"
2123
"cuelang.org/go/cue/ast/astutil"
2224
"cuelang.org/go/cue/errors"
2325
"cuelang.org/go/cue/token"
24-
"cuelang.org/go/internal"
26+
"cuelang.org/go/internal/cueversion"
27+
"cuelang.org/go/internal/mod/semver"
2528
"cuelang.org/go/internal/source"
2629
)
2730

2831
// Option specifies a parse option.
29-
type Option func(p *parser)
32+
type Option interface {
33+
apply(cfg *Config)
34+
}
3035

31-
var (
32-
// PackageClauseOnly causes parsing to stop after the package clause.
33-
PackageClauseOnly Option = packageClauseOnly
34-
packageClauseOnly = func(p *parser) {
35-
p.mode |= packageClauseOnlyMode
36+
var _ Option = Config{}
37+
38+
// Config represents the end result of applying a set of options.
39+
// The zero value is not OK to use: use [NewConfig] to construct
40+
// a Config value before using it.
41+
//
42+
// Config itself implements [Option] by overwriting the
43+
// entire configuration.
44+
//
45+
// Config is comparable.
46+
type Config struct {
47+
// valid is set by NewConfig and is used to check
48+
// that a Config has been created correctly.
49+
valid bool
50+
51+
// Mode holds a bitmask of boolean parser options.
52+
Mode Mode
53+
54+
// Version holds the language version to use when
55+
// parsing the CUE syntax.
56+
Version string
57+
}
58+
59+
// Ensure that Config is comparable.
60+
var _ = Config{} == Config{}
61+
62+
// apply implements [Option]
63+
func (cfg Config) apply(cfg1 *Config) {
64+
if !cfg.valid {
65+
panic("zero parser.Config value used; use parser.NewConfig!")
3666
}
67+
*cfg1 = cfg
68+
}
3769

38-
// ImportsOnly causes parsing to stop parsing after the import declarations.
39-
ImportsOnly Option = importsOnly
40-
importsOnly = func(p *parser) {
41-
p.mode |= importsOnlyMode
70+
// NewConfig returns the configuration containing all default values
71+
// with the given options applied.
72+
func NewConfig(opts ...Option) Config {
73+
return Config{
74+
valid: true,
75+
Version: cueversion.LanguageVersion(),
76+
}.Apply(opts...)
77+
}
78+
79+
// Apply applies all the given options to cfg and
80+
// returns the resulting configuration.
81+
func (cfg Config) Apply(opts ...Option) Config {
82+
for _, opt := range opts {
83+
opt.apply(&cfg)
4284
}
85+
return cfg
86+
}
87+
88+
// optionFunc implements [Option] for a function.
89+
type optionFunc func(cfg *Config)
90+
91+
func (f optionFunc) apply(cfg *Config) {
92+
f(cfg)
93+
}
94+
95+
// A Mode value is a set of flags (or 0).
96+
// It controls the amount of source code parsed and other optional
97+
// parser functionality.
98+
//
99+
// Mode implements [Option] by or-ing all its bits
100+
// with [Config.Mode].
101+
type Mode uint
102+
103+
const (
104+
// PackageClauseOnly causes parsing to stop after the package clause.
105+
PackageClauseOnly Mode = 1 << iota
106+
107+
// ImportsOnly causes parsing to stop parsing after the import declarations.
108+
ImportsOnly
43109

44110
// ParseComments causes comments to be parsed.
45-
ParseComments Option = parseComments
46-
parseComments = func(p *parser) {
47-
p.mode |= parseCommentsMode
48-
}
111+
ParseComments
49112

50113
// ParseFuncs causes function declarations to be parsed.
51114
//
52115
// This is an experimental function and the API is likely to
53116
// change or dissapear.
54-
ParseFuncs Option = parseFuncs
55-
parseFuncs = func(p *parser) {
56-
p.mode |= parseFuncsMode
57-
}
117+
ParseFuncs
58118

59119
// Trace causes parsing to print a trace of parsed productions.
60-
Trace Option = traceOpt
61-
traceOpt = func(p *parser) {
62-
p.mode |= traceMode
63-
}
120+
Trace
64121

65122
// DeclarationErrors causes parsing to report declaration errors.
66-
DeclarationErrors Option = declarationErrors
67-
declarationErrors = func(p *parser) {
68-
p.mode |= declarationErrorsMode
69-
}
123+
DeclarationErrors
70124

71125
// AllErrors causes all errors to be reported (not just the first 10 on different lines).
72-
AllErrors Option = allErrors
73-
allErrors = func(p *parser) {
74-
p.mode |= allErrorsMode
75-
}
126+
AllErrors
76127

77128
// AllowPartial allows the parser to be used on a prefix buffer.
78-
AllowPartial Option = allowPartial
79-
allowPartial = func(p *parser) {
80-
p.mode |= partialMode
81-
}
129+
AllowPartial
82130
)
83131

132+
// apply implements [Option].
133+
func (m Mode) apply(c *Config) {
134+
c.Mode |= m
135+
}
136+
137+
// Version specifies the language version to use when parsing
138+
// the CUE. The argument must be a valid semantic version, as
139+
// checked by [semver.IsValid].
140+
func Version(v string) Option {
141+
if !semver.IsValid(v) {
142+
panic(fmt.Errorf("invalid language version %q", v))
143+
}
144+
return optionFunc(func(c *Config) {
145+
c.Version = v
146+
})
147+
}
148+
84149
// FromVersion specifies until which legacy version the parser should provide
85150
// backwards compatibility.
151+
// Deprecated: use [Version] instead.
86152
func FromVersion(version int) Option {
87-
if version >= 0 {
88-
version++
89-
}
90-
// Versions:
91-
// <0: major version 0 (counting -1000 + x, where x = 100*m+p in 0.m.p
92-
// >=0: x+1 in 1.x.y
93-
return func(p *parser) { p.version = version }
153+
return optionFunc(func(cfg *Config) {})
94154
}
95155

96156
// DeprecationError is a sentinel error to indicate that an error is
@@ -104,42 +164,20 @@ func (e *DeprecationError) Error() string {
104164
}
105165

106166
const (
107-
// Latest specifies the latest version of the parser, effectively setting
108-
// the strictest implementation.
109-
Latest = latest
110-
111-
latest = -1000 + (100 * internal.MinorCurrent) + 0
112-
113-
// FullBackwardCompatibility enables all deprecated features that are
114-
// currently still supported by the parser.
115-
FullBackwardCompatibility = fullCompatibility
167+
// Deprecated: see [Version].
168+
Latest = 0
116169

117-
fullCompatibility = -1000
170+
// Deprecated: see [Version].
171+
FullBackwardCompatibility = 0
118172
)
119173

120174
// FileOffset specifies the File position info to use.
121175
//
122176
// Deprecated: this has no effect.
123177
func FileOffset(pos int) Option {
124-
return func(p *parser) {}
178+
return optionFunc(func(*Config) {})
125179
}
126180

127-
// A mode value is a set of flags (or 0).
128-
// They control the amount of source code parsed and other optional
129-
// parser functionality.
130-
type mode uint
131-
132-
const (
133-
packageClauseOnlyMode mode = 1 << iota // stop parsing after package clause
134-
importsOnlyMode // stop parsing after import declarations
135-
parseCommentsMode // parse comments and add them to AST
136-
parseFuncsMode // parse function declarations (experimental)
137-
partialMode
138-
traceMode // print a trace of parsed productions
139-
declarationErrorsMode // report declaration errors
140-
allErrorsMode // report all errors (not just the first 10 on different lines)
141-
)
142-
143181
// ParseFile parses the source code of a single CUE source file and returns
144182
// the corresponding File node. The source code may be provided via
145183
// the filename of the source file, or via the src parameter.
@@ -228,7 +266,7 @@ func ParseExpr(filename string, src interface{}, mode ...Option) (ast.Expr, erro
228266
if p.tok == token.COMMA && p.lit == "\n" {
229267
p.next()
230268
}
231-
if p.mode&partialMode == 0 {
269+
if p.cfg.Mode&AllowPartial == 0 {
232270
p.expect(token.EOF)
233271
}
234272

cue/parser/parser.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ type parser struct {
3434
scanner scanner.Scanner
3535

3636
// Tracing/debugging
37-
mode mode // parsing mode
38-
trace bool // == (mode & Trace != 0)
37+
cfg Config
38+
trace bool // == (cfg.Mode & Trace != 0)
3939
panicking bool // set if we are bailing out due to too many errors.
4040
indent int // indentation used for tracing output
4141

@@ -72,22 +72,20 @@ type parser struct {
7272
version int
7373
}
7474

75-
func (p *parser) init(filename string, src []byte, mode []Option) {
76-
for _, f := range mode {
77-
f(p)
78-
}
75+
func (p *parser) init(filename string, src []byte, opts []Option) {
76+
p.cfg = NewConfig().Apply(opts...)
7977
p.file = token.NewFile(filename, -1, len(src))
8078

8179
var m scanner.Mode
82-
if p.mode&parseCommentsMode != 0 {
80+
if p.cfg.Mode&ParseComments != 0 {
8381
m = scanner.ScanComments
8482
}
8583
eh := func(pos token.Pos, msg string, args []interface{}) {
8684
p.errors = errors.Append(p.errors, errors.Newf(pos, msg, args...))
8785
}
8886
p.scanner.Init(p.file, src, eh, m)
8987

90-
p.trace = p.mode&traceMode != 0 // for convenience (p.trace is used frequently)
88+
p.trace = p.cfg.Mode&Trace != 0 // for convenience (p.trace is used frequently)
9189

9290
p.comments = &commentState{pos: -1}
9391

@@ -422,7 +420,7 @@ func (p *parser) errf(pos token.Pos, msg string, args ...interface{}) {
422420
// If AllErrors is not set, discard errors reported on the same line
423421
// as the last recorded error and stop parsing if there are more than
424422
// 10 errors.
425-
if p.mode&allErrorsMode == 0 {
423+
if p.cfg.Mode&AllErrors == 0 {
426424
errors := errors.Errors(p.errors)
427425
n := len(errors)
428426
if n > 0 && errors[n-1].Position().Line() == ePos.Line() {
@@ -608,7 +606,7 @@ func (p *parser) parseOperand() (expr ast.Expr) {
608606
return p.parseList()
609607

610608
case token.FUNC:
611-
if p.mode&parseFuncsMode != 0 {
609+
if p.cfg.Mode&ParseFuncs != 0 {
612610
return p.parseFunc()
613611
} else {
614612
return p.parseKeyIdent()
@@ -1719,7 +1717,7 @@ func (p *parser) parseFile() *ast.File {
17191717
var name *ast.Ident
17201718
p.expect(token.IDENT)
17211719
name = p.parseIdent()
1722-
if name.Name == "_" && p.mode&declarationErrorsMode != 0 {
1720+
if name.Name == "_" && p.cfg.Mode&DeclarationErrors != 0 {
17231721
p.errf(p.pos, "invalid package name _")
17241722
}
17251723
if isDefinition(name) {
@@ -1739,13 +1737,13 @@ func (p *parser) parseFile() *ast.File {
17391737
p.consumeDeclComma()
17401738
}
17411739

1742-
if p.mode&packageClauseOnlyMode == 0 {
1740+
if p.cfg.Mode&PackageClauseOnly == 0 {
17431741
// import decls
17441742
for p.tok == token.IDENT && p.lit == "import" {
17451743
decls = append(decls, p.parseImports())
17461744
}
17471745

1748-
if p.mode&importsOnlyMode == 0 {
1746+
if p.cfg.Mode&ImportsOnly == 0 {
17491747
// rest of package decls
17501748
// TODO: loop and allow multiple expressions.
17511749
decls = append(decls, p.parseFieldList()...)

0 commit comments

Comments
 (0)