| // 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 "cuelang.org/go/internal" |
| |
| // TODO: refactor packages as to make this package unnecessary. |
| |
| import ( |
| "bufio" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "github.com/cockroachdb/apd/v2" |
| "golang.org/x/xerrors" |
| |
| "cuelang.org/go/cue/ast" |
| "cuelang.org/go/cue/ast/astutil" |
| "cuelang.org/go/cue/errors" |
| "cuelang.org/go/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 |
| |
| // DebugStr prints a syntax node. |
| var DebugStr func(x interface{}) string |
| |
| // ErrIncomplete can be used by builtins to signal the evaluation was |
| // incomplete. |
| var ErrIncomplete = errors.New("incomplete value") |
| |
| // EvalExpr evaluates an expression within an existing struct value. |
| // Identifiers only resolve to values defined within the struct. |
| // |
| // Expressions may refer to builtin packages if they can be uniquely identified |
| // |
| // Both value and result are of type cue.Value, but are an interface to prevent |
| // cyclic dependencies. |
| // |
| // TODO: extract interface |
| var EvalExpr func(value, expr interface{}) (result interface{}) |
| |
| // FromGoValue converts an arbitrary Go value to the corresponding CUE value. |
| // instance must be of type *cue.Instance. |
| // The returned value is a cue.Value, which the caller must cast to. |
| var FromGoValue func(instance, x interface{}, allowDefault bool) interface{} |
| |
| // FromGoType converts an arbitrary Go type to the corresponding CUE value. |
| // instance must be of type *cue.Instance. |
| // The returned value is a cue.Value, which the caller must cast to. |
| var FromGoType func(instance, x interface{}) interface{} |
| |
| // UnifyBuiltin returns the given Value unified with the given builtin template. |
| var UnifyBuiltin func(v interface{}, kind string) interface{} |
| |
| // GetRuntime reports the runtime for an Instance or Value. |
| var GetRuntime func(instance interface{}) interface{} |
| |
| // MakeInstance makes a new instance from a value. |
| var MakeInstance func(value interface{}) (instance interface{}) |
| |
| // CheckAndForkRuntime checks that value is created using runtime, panicking |
| // if it does not, and returns a forked runtime that will discard additional |
| // keys. |
| var CheckAndForkRuntime func(runtime, value interface{}) 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 |
| } |
| |
| func Imports(f *ast.File) (a []ast.Decl) { |
| for _, d := range f.Decls { |
| switch x := d.(type) { |
| case *ast.CommentGroup: |
| case *ast.Package: |
| case *ast.Attribute: |
| case *ast.ImportDecl: |
| a = append(a, x) |
| default: |
| return a |
| } |
| } |
| return a |
| } |
| |
| func PackageInfo(f *ast.File) (p *ast.Package, name string, tok token.Pos) { |
| for _, d := range f.Decls { |
| switch x := d.(type) { |
| case *ast.CommentGroup: |
| case *ast.Package: |
| if x.Name == nil { |
| break |
| } |
| return x, x.Name.Name, x.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 |
| } |
| 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 |
| } |
| } |
| return &ast.StructLit{Elts: x.Decls[start:]} |
| |
| 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 xerrors.Is(e.info, err) || xerrors.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 |