forked from cuelang/cue
-
Notifications
You must be signed in to change notification settings - Fork 0
/
internal.go
422 lines (379 loc) · 9.69 KB
/
internal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
// Copyright 2018 The CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package internal exposes some cue internals to other packages.
//
// A better name for this package would be technicaldebt.
package internal // import "github.com/solo-io/cue/internal"
// TODO: refactor packages as to make this package unnecessary.
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/cockroachdb/apd/v2"
"github.com/solo-io/cue/cue/ast"
"github.com/solo-io/cue/cue/ast/astutil"
"github.com/solo-io/cue/cue/errors"
"github.com/solo-io/cue/cue/token"
)
// A Decimal is an arbitrary-precision binary-coded decimal number.
//
// Right now Decimal is aliased to apd.Decimal. This may change in the future.
type Decimal = apd.Decimal
// ErrIncomplete can be used by builtins to signal the evaluation was
// incomplete.
var ErrIncomplete = errors.New("incomplete value")
// MakeInstance makes a new instance from a value.
var MakeInstance func(value interface{}) (instance interface{})
// BaseContext is used as CUEs default context for arbitrary-precision decimals
var BaseContext = apd.BaseContext.WithPrecision(24)
// ListEllipsis reports the list type and remaining elements of a list. If we
// ever relax the usage of ellipsis, this function will likely change. Using
// this function will ensure keeping correct behavior or causing a compiler
// failure.
func ListEllipsis(n *ast.ListLit) (elts []ast.Expr, e *ast.Ellipsis) {
elts = n.Elts
if n := len(elts); n > 0 {
var ok bool
if e, ok = elts[n-1].(*ast.Ellipsis); ok {
elts = elts[:n-1]
}
}
return elts, e
}
type PkgInfo struct {
Package *ast.Package
Index int // position in File.Decls
Name string
}
// IsAnonymous reports whether the package is anonymous.
func (p *PkgInfo) IsAnonymous() bool {
return p.Name == "" || p.Name == "_"
}
func GetPackageInfo(f *ast.File) PkgInfo {
for i, d := range f.Decls {
switch x := d.(type) {
case *ast.CommentGroup:
case *ast.Attribute:
case *ast.Package:
if x.Name == nil {
break
}
return PkgInfo{x, i, x.Name.Name}
}
}
return PkgInfo{}
}
// Deprecated: use GetPackageInfo
func PackageInfo(f *ast.File) (p *ast.Package, name string, tok token.Pos) {
x := GetPackageInfo(f)
if p := x.Package; p != nil {
return p, x.Name, p.Name.Pos()
}
return nil, "", f.Pos()
}
func SetPackage(f *ast.File, name string, overwrite bool) {
p, str, _ := PackageInfo(f)
if p != nil {
if !overwrite || str == name {
return
}
ident := ast.NewIdent(name)
astutil.CopyMeta(ident, p.Name)
return
}
decls := make([]ast.Decl, len(f.Decls)+1)
k := 0
for _, d := range f.Decls {
if _, ok := d.(*ast.CommentGroup); ok {
decls[k] = d
k++
continue
}
break
}
decls[k] = &ast.Package{Name: ast.NewIdent(name)}
copy(decls[k+1:], f.Decls[k:])
f.Decls = decls
}
// NewComment creates a new CommentGroup from the given text.
// Each line is prefixed with "//" and the last newline is removed.
// Useful for ASTs generated by code other than the CUE parser.
func NewComment(isDoc bool, s string) *ast.CommentGroup {
if s == "" {
return nil
}
cg := &ast.CommentGroup{Doc: isDoc}
if !isDoc {
cg.Line = true
cg.Position = 10
}
scanner := bufio.NewScanner(strings.NewReader(s))
for scanner.Scan() {
scanner := bufio.NewScanner(strings.NewReader(scanner.Text()))
scanner.Split(bufio.ScanWords)
const maxRunesPerLine = 66
count := 2
buf := strings.Builder{}
buf.WriteString("//")
for scanner.Scan() {
s := scanner.Text()
n := len([]rune(s)) + 1
if count+n > maxRunesPerLine && count > 3 {
cg.List = append(cg.List, &ast.Comment{Text: buf.String()})
count = 3
buf.Reset()
buf.WriteString("//")
}
buf.WriteString(" ")
buf.WriteString(s)
count += n
}
cg.List = append(cg.List, &ast.Comment{Text: buf.String()})
}
if last := len(cg.List) - 1; cg.List[last].Text == "//" {
cg.List = cg.List[:last]
}
return cg
}
func FileComment(f *ast.File) *ast.CommentGroup {
pkg, _, _ := PackageInfo(f)
var cgs []*ast.CommentGroup
if pkg != nil {
cgs = pkg.Comments()
} else if cgs = f.Comments(); len(cgs) > 0 {
// Use file comment.
} else {
// Use first comment before any declaration.
for _, d := range f.Decls {
if cg, ok := d.(*ast.CommentGroup); ok {
return cg
}
if cgs = ast.Comments(d); cgs != nil {
break
}
// TODO: what to do here?
if _, ok := d.(*ast.Attribute); !ok {
break
}
}
}
var cg *ast.CommentGroup
for _, c := range cgs {
if c.Position == 0 {
cg = c
}
}
return cg
}
func NewAttr(name, str string) *ast.Attribute {
buf := &strings.Builder{}
buf.WriteByte('@')
buf.WriteString(name)
buf.WriteByte('(')
fmt.Fprintf(buf, str)
buf.WriteByte(')')
return &ast.Attribute{Text: buf.String()}
}
// ToExpr converts a node to an expression. If it is a file, it will return
// it as a struct. If is an expression, it will return it as is. Otherwise
// it panics.
func ToExpr(n ast.Node) ast.Expr {
switch x := n.(type) {
case nil:
return nil
case ast.Expr:
return x
case *ast.File:
start := 0
outer:
for i, d := range x.Decls {
switch d.(type) {
case *ast.Package, *ast.ImportDecl:
start = i + 1
case *ast.CommentGroup, *ast.Attribute:
default:
break outer
}
}
decls := x.Decls[start:]
if len(decls) == 1 {
if e, ok := decls[0].(*ast.EmbedDecl); ok {
return e.Expr
}
}
return &ast.StructLit{Elts: decls}
default:
panic(fmt.Sprintf("Unsupported node type %T", x))
}
}
// ToFile converts an expression to a file.
//
// Adjusts the spacing of x when needed.
func ToFile(n ast.Node) *ast.File {
switch x := n.(type) {
case nil:
return nil
case *ast.StructLit:
return &ast.File{Decls: x.Elts}
case ast.Expr:
ast.SetRelPos(x, token.NoSpace)
return &ast.File{Decls: []ast.Decl{&ast.EmbedDecl{Expr: x}}}
case *ast.File:
return x
default:
panic(fmt.Sprintf("Unsupported node type %T", x))
}
}
// ToStruct gets the non-preamble declarations of a file and puts them in a
// struct.
func ToStruct(f *ast.File) *ast.StructLit {
start := 0
for i, d := range f.Decls {
switch d.(type) {
case *ast.Package, *ast.ImportDecl:
start = i + 1
case *ast.Attribute, *ast.CommentGroup:
default:
break
}
}
s := ast.NewStruct()
s.Elts = f.Decls[start:]
return s
}
func IsBulkField(d ast.Decl) bool {
if f, ok := d.(*ast.Field); ok {
if _, ok := f.Label.(*ast.ListLit); ok {
return true
}
}
return false
}
func IsDef(s string) bool {
return strings.HasPrefix(s, "#") || strings.HasPrefix(s, "_#")
}
func IsHidden(s string) bool {
return strings.HasPrefix(s, "_")
}
func IsDefOrHidden(s string) bool {
return strings.HasPrefix(s, "#") || strings.HasPrefix(s, "_")
}
func IsDefinition(label ast.Label) bool {
switch x := label.(type) {
case *ast.Alias:
if ident, ok := x.Expr.(*ast.Ident); ok {
return IsDef(ident.Name)
}
case *ast.Ident:
return IsDef(x.Name)
}
return false
}
func IsRegularField(f *ast.Field) bool {
if f.Token == token.ISA {
return false
}
var ident *ast.Ident
switch x := f.Label.(type) {
case *ast.Alias:
ident, _ = x.Expr.(*ast.Ident)
case *ast.Ident:
ident = x
}
if ident == nil {
return true
}
if strings.HasPrefix(ident.Name, "#") || strings.HasPrefix(ident.Name, "_") {
return false
}
return true
}
func EmbedStruct(s *ast.StructLit) *ast.EmbedDecl {
e := &ast.EmbedDecl{Expr: s}
if len(s.Elts) == 1 {
d := s.Elts[0]
astutil.CopyPosition(e, d)
ast.SetRelPos(d, token.NoSpace)
astutil.CopyComments(e, d)
ast.SetComments(d, nil)
if f, ok := d.(*ast.Field); ok {
ast.SetRelPos(f.Label, token.NoSpace)
}
}
s.Lbrace = token.Newline.Pos()
s.Rbrace = token.NoSpace.Pos()
return e
}
// IsEllipsis reports whether the declaration can be represented as an ellipsis.
func IsEllipsis(x ast.Decl) bool {
// ...
if _, ok := x.(*ast.Ellipsis); ok {
return true
}
// [string]: _ or [_]: _
f, ok := x.(*ast.Field)
if !ok {
return false
}
v, ok := f.Value.(*ast.Ident)
if !ok || v.Name != "_" {
return false
}
l, ok := f.Label.(*ast.ListLit)
if !ok || len(l.Elts) != 1 {
return false
}
i, ok := l.Elts[0].(*ast.Ident)
if !ok {
return false
}
return i.Name == "string" || i.Name == "_"
}
// GenPath reports the directory in which to store generated files.
func GenPath(root string) string {
info, err := os.Stat(filepath.Join(root, "cue.mod"))
if os.IsNotExist(err) || !info.IsDir() {
// Try legacy pkgDir mode
pkgDir := filepath.Join(root, "pkg")
if err == nil && !info.IsDir() {
return pkgDir
}
if info, err := os.Stat(pkgDir); err == nil && info.IsDir() {
return pkgDir
}
}
return filepath.Join(root, "cue.mod", "gen")
}
var ErrInexact = errors.New("inexact subsumption")
func DecorateError(info error, err errors.Error) errors.Error {
return &decorated{cueError: err, info: info}
}
type cueError = errors.Error
type decorated struct {
cueError
info error
}
func (e *decorated) Is(err error) bool {
return errors.Is(e.info, err) || errors.Is(e.cueError, err)
}
// MaxDepth indicates the maximum evaluation depth. This is there to break
// cycles in the absence of cycle detection.
//
// It is registered in a central place to make it easy to find all spots where
// cycles are broken in this brute-force manner.
//
// TODO(eval): have cycle detection.
const MaxDepth = 20