...

Source file src/cuelang.org/go/internal/internal.go

Documentation: cuelang.org/go/internal

     1  // 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  // Package internal exposes some cue internals to other packages.
    16  //
    17  // A better name for this package would be technicaldebt.
    18  package internal // import "cuelang.org/go/internal"
    19  
    20  // TODO: refactor packages as to make this package unnecessary.
    21  
    22  import (
    23  	"bufio"
    24  	"fmt"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	"github.com/cockroachdb/apd/v3"
    29  
    30  	"cuelang.org/go/cue/ast"
    31  	"cuelang.org/go/cue/ast/astutil"
    32  	"cuelang.org/go/cue/errors"
    33  	"cuelang.org/go/cue/token"
    34  )
    35  
    36  // A Decimal is an arbitrary-precision binary-coded decimal number.
    37  //
    38  // Right now Decimal is aliased to apd.Decimal. This may change in the future.
    39  type Decimal = apd.Decimal
    40  
    41  // Context wraps apd.Context for CUE's custom logic.
    42  //
    43  // Note that it avoids pointers to make it easier to make copies.
    44  type Context struct {
    45  	apd.Context
    46  }
    47  
    48  // WithPrecision mirrors upstream, but returning our type without a pointer.
    49  func (c Context) WithPrecision(p uint32) Context {
    50  	c.Context = *c.Context.WithPrecision(p)
    51  	return c
    52  }
    53  
    54  // apd/v2 used to call Reduce on the result of Quo and Rem,
    55  // so that the operations always trimmed all but one trailing zeros.
    56  // apd/v3 does not do that at all.
    57  // For now, get the old behavior back by calling Reduce ourselves.
    58  // Note that v3's Reduce also removes all trailing zeros,
    59  // whereas v2's Reduce would leave ".0" behind.
    60  // Get that detail back as well, to consistently show floats with decimal points.
    61  //
    62  // TODO: Rather than reducing all trailing zeros,
    63  // we should keep a number of zeros that makes sense given the operation.
    64  
    65  func reduceKeepingFloats(d *apd.Decimal) {
    66  	oldExponent := d.Exponent
    67  	d.Reduce(d)
    68  	// If the decimal had decimal places, like "3.000" and "5.000E+5",
    69  	// Reduce gives us "3" and "5E+5", but we want "3.0" and "5.0E+5".
    70  	if oldExponent < 0 && d.Exponent >= 0 {
    71  		d.Exponent--
    72  		// TODO: we can likely make the NewBigInt(10) a static global to reduce allocs
    73  		d.Coeff.Mul(&d.Coeff, apd.NewBigInt(10))
    74  	}
    75  }
    76  
    77  func (c Context) Quo(d, x, y *apd.Decimal) (apd.Condition, error) {
    78  	res, err := c.Context.Quo(d, x, y)
    79  	reduceKeepingFloats(d)
    80  	return res, err
    81  }
    82  
    83  func (c Context) Sqrt(d, x *apd.Decimal) (apd.Condition, error) {
    84  	res, err := c.Context.Sqrt(d, x)
    85  	reduceKeepingFloats(d)
    86  	return res, err
    87  }
    88  
    89  // ErrIncomplete can be used by builtins to signal the evaluation was
    90  // incomplete.
    91  var ErrIncomplete = errors.New("incomplete value")
    92  
    93  // MakeInstance makes a new instance from a value.
    94  var MakeInstance func(value interface{}) (instance interface{})
    95  
    96  // BaseContext is used as CUE's default context for arbitrary-precision decimals.
    97  var BaseContext = Context{*apd.BaseContext.WithPrecision(34)}
    98  
    99  // APIVersionSupported is the back version until which deprecated features
   100  // are still supported.
   101  var APIVersionSupported = Version(MinorSupported, PatchSupported)
   102  
   103  const (
   104  	MinorCurrent   = 5
   105  	MinorSupported = 4
   106  	PatchSupported = 0
   107  )
   108  
   109  func Version(minor, patch int) int {
   110  	return -1000 + 100*minor + patch
   111  }
   112  
   113  type EvaluatorVersion int
   114  
   115  const (
   116  	DefaultVersion EvaluatorVersion = iota
   117  
   118  	// The DevVersion is used for new implementations of the evaluator that
   119  	// do not cover all features of the CUE language yet.
   120  	DevVersion
   121  )
   122  
   123  // ListEllipsis reports the list type and remaining elements of a list. If we
   124  // ever relax the usage of ellipsis, this function will likely change. Using
   125  // this function will ensure keeping correct behavior or causing a compiler
   126  // failure.
   127  func ListEllipsis(n *ast.ListLit) (elts []ast.Expr, e *ast.Ellipsis) {
   128  	elts = n.Elts
   129  	if n := len(elts); n > 0 {
   130  		var ok bool
   131  		if e, ok = elts[n-1].(*ast.Ellipsis); ok {
   132  			elts = elts[:n-1]
   133  		}
   134  	}
   135  	return elts, e
   136  }
   137  
   138  type PkgInfo struct {
   139  	Package *ast.Package
   140  	Index   int // position in File.Decls
   141  	Name    string
   142  }
   143  
   144  // IsAnonymous reports whether the package is anonymous.
   145  func (p *PkgInfo) IsAnonymous() bool {
   146  	return p.Name == "" || p.Name == "_"
   147  }
   148  
   149  func GetPackageInfo(f *ast.File) PkgInfo {
   150  	for i, d := range f.Decls {
   151  		switch x := d.(type) {
   152  		case *ast.CommentGroup:
   153  		case *ast.Attribute:
   154  		case *ast.Package:
   155  			if x.Name == nil {
   156  				break
   157  			}
   158  			return PkgInfo{x, i, x.Name.Name}
   159  		}
   160  	}
   161  	return PkgInfo{}
   162  }
   163  
   164  // Deprecated: use GetPackageInfo
   165  func PackageInfo(f *ast.File) (p *ast.Package, name string, tok token.Pos) {
   166  	x := GetPackageInfo(f)
   167  	if p := x.Package; p != nil {
   168  		return p, x.Name, p.Name.Pos()
   169  	}
   170  	return nil, "", f.Pos()
   171  }
   172  
   173  func SetPackage(f *ast.File, name string, overwrite bool) {
   174  	p, str, _ := PackageInfo(f)
   175  	if p != nil {
   176  		if !overwrite || str == name {
   177  			return
   178  		}
   179  		ident := ast.NewIdent(name)
   180  		astutil.CopyMeta(ident, p.Name)
   181  		return
   182  	}
   183  
   184  	decls := make([]ast.Decl, len(f.Decls)+1)
   185  	k := 0
   186  	for _, d := range f.Decls {
   187  		if _, ok := d.(*ast.CommentGroup); ok {
   188  			decls[k] = d
   189  			k++
   190  			continue
   191  		}
   192  		break
   193  	}
   194  	decls[k] = &ast.Package{Name: ast.NewIdent(name)}
   195  	copy(decls[k+1:], f.Decls[k:])
   196  	f.Decls = decls
   197  }
   198  
   199  // NewComment creates a new CommentGroup from the given text.
   200  // Each line is prefixed with "//" and the last newline is removed.
   201  // Useful for ASTs generated by code other than the CUE parser.
   202  func NewComment(isDoc bool, s string) *ast.CommentGroup {
   203  	if s == "" {
   204  		return nil
   205  	}
   206  	cg := &ast.CommentGroup{Doc: isDoc}
   207  	if !isDoc {
   208  		cg.Line = true
   209  		cg.Position = 10
   210  	}
   211  	scanner := bufio.NewScanner(strings.NewReader(s))
   212  	for scanner.Scan() {
   213  		scanner := bufio.NewScanner(strings.NewReader(scanner.Text()))
   214  		scanner.Split(bufio.ScanWords)
   215  		const maxRunesPerLine = 66
   216  		count := 2
   217  		buf := strings.Builder{}
   218  		buf.WriteString("//")
   219  		for scanner.Scan() {
   220  			s := scanner.Text()
   221  			n := len([]rune(s)) + 1
   222  			if count+n > maxRunesPerLine && count > 3 {
   223  				cg.List = append(cg.List, &ast.Comment{Text: buf.String()})
   224  				count = 3
   225  				buf.Reset()
   226  				buf.WriteString("//")
   227  			}
   228  			buf.WriteString(" ")
   229  			buf.WriteString(s)
   230  			count += n
   231  		}
   232  		cg.List = append(cg.List, &ast.Comment{Text: buf.String()})
   233  	}
   234  	if last := len(cg.List) - 1; cg.List[last].Text == "//" {
   235  		cg.List = cg.List[:last]
   236  	}
   237  	return cg
   238  }
   239  
   240  func FileComment(f *ast.File) *ast.CommentGroup {
   241  	pkg, _, _ := PackageInfo(f)
   242  	var cgs []*ast.CommentGroup
   243  	if pkg != nil {
   244  		cgs = pkg.Comments()
   245  	} else if cgs = f.Comments(); len(cgs) > 0 {
   246  		// Use file comment.
   247  	} else {
   248  		// Use first comment before any declaration.
   249  		for _, d := range f.Decls {
   250  			if cg, ok := d.(*ast.CommentGroup); ok {
   251  				return cg
   252  			}
   253  			if cgs = ast.Comments(d); cgs != nil {
   254  				break
   255  			}
   256  			// TODO: what to do here?
   257  			if _, ok := d.(*ast.Attribute); !ok {
   258  				break
   259  			}
   260  		}
   261  	}
   262  	var cg *ast.CommentGroup
   263  	for _, c := range cgs {
   264  		if c.Position == 0 {
   265  			cg = c
   266  		}
   267  	}
   268  	return cg
   269  }
   270  
   271  func NewAttr(name, str string) *ast.Attribute {
   272  	buf := &strings.Builder{}
   273  	buf.WriteByte('@')
   274  	buf.WriteString(name)
   275  	buf.WriteByte('(')
   276  	buf.WriteString(str)
   277  	buf.WriteByte(')')
   278  	return &ast.Attribute{Text: buf.String()}
   279  }
   280  
   281  // ToExpr converts a node to an expression. If it is a file, it will return
   282  // it as a struct. If is an expression, it will return it as is. Otherwise
   283  // it panics.
   284  func ToExpr(n ast.Node) ast.Expr {
   285  	switch x := n.(type) {
   286  	case nil:
   287  		return nil
   288  
   289  	case ast.Expr:
   290  		return x
   291  
   292  	case *ast.File:
   293  		start := 0
   294  	outer:
   295  		for i, d := range x.Decls {
   296  			switch d.(type) {
   297  			case *ast.Package, *ast.ImportDecl:
   298  				start = i + 1
   299  			case *ast.CommentGroup, *ast.Attribute:
   300  			default:
   301  				break outer
   302  			}
   303  		}
   304  		decls := x.Decls[start:]
   305  		if len(decls) == 1 {
   306  			if e, ok := decls[0].(*ast.EmbedDecl); ok {
   307  				return e.Expr
   308  			}
   309  		}
   310  		return &ast.StructLit{Elts: decls}
   311  
   312  	default:
   313  		panic(fmt.Sprintf("Unsupported node type %T", x))
   314  	}
   315  }
   316  
   317  // ToFile converts an expression to a file.
   318  //
   319  // Adjusts the spacing of x when needed.
   320  func ToFile(n ast.Node) *ast.File {
   321  	switch x := n.(type) {
   322  	case nil:
   323  		return nil
   324  	case *ast.StructLit:
   325  		return &ast.File{Decls: x.Elts}
   326  	case ast.Expr:
   327  		ast.SetRelPos(x, token.NoSpace)
   328  		return &ast.File{Decls: []ast.Decl{&ast.EmbedDecl{Expr: x}}}
   329  	case *ast.File:
   330  		return x
   331  	default:
   332  		panic(fmt.Sprintf("Unsupported node type %T", x))
   333  	}
   334  }
   335  
   336  func IsDef(s string) bool {
   337  	return strings.HasPrefix(s, "#") || strings.HasPrefix(s, "_#")
   338  }
   339  
   340  func IsHidden(s string) bool {
   341  	return strings.HasPrefix(s, "_")
   342  }
   343  
   344  func IsDefOrHidden(s string) bool {
   345  	return strings.HasPrefix(s, "#") || strings.HasPrefix(s, "_")
   346  }
   347  
   348  func IsDefinition(label ast.Label) bool {
   349  	switch x := label.(type) {
   350  	case *ast.Alias:
   351  		if ident, ok := x.Expr.(*ast.Ident); ok {
   352  			return IsDef(ident.Name)
   353  		}
   354  	case *ast.Ident:
   355  		return IsDef(x.Name)
   356  	}
   357  	return false
   358  }
   359  
   360  func IsRegularField(f *ast.Field) bool {
   361  	var ident *ast.Ident
   362  	switch x := f.Label.(type) {
   363  	case *ast.Alias:
   364  		ident, _ = x.Expr.(*ast.Ident)
   365  	case *ast.Ident:
   366  		ident = x
   367  	}
   368  	if ident == nil {
   369  		return true
   370  	}
   371  	if strings.HasPrefix(ident.Name, "#") || strings.HasPrefix(ident.Name, "_") {
   372  		return false
   373  	}
   374  	return true
   375  }
   376  
   377  // ConstraintToken reports which constraint token (? or !) is associated
   378  // with a field (if any), taking into account compatibility of deprecated
   379  // fields.
   380  func ConstraintToken(f *ast.Field) (t token.Token, ok bool) {
   381  	if f.Constraint != token.ILLEGAL {
   382  		return f.Constraint, true
   383  	}
   384  	if f.Optional != token.NoPos {
   385  		return token.OPTION, true
   386  	}
   387  	return f.Constraint, false
   388  }
   389  
   390  // SetConstraints sets both the main and deprecated fields of f according to the
   391  // given constraint token.
   392  func SetConstraint(f *ast.Field, t token.Token) {
   393  	f.Constraint = t
   394  	if t == token.ILLEGAL {
   395  		f.Optional = token.NoPos
   396  	} else {
   397  		f.Optional = token.Blank.Pos()
   398  	}
   399  }
   400  
   401  func EmbedStruct(s *ast.StructLit) *ast.EmbedDecl {
   402  	e := &ast.EmbedDecl{Expr: s}
   403  	if len(s.Elts) == 1 {
   404  		d := s.Elts[0]
   405  		astutil.CopyPosition(e, d)
   406  		ast.SetRelPos(d, token.NoSpace)
   407  		astutil.CopyComments(e, d)
   408  		ast.SetComments(d, nil)
   409  		if f, ok := d.(*ast.Field); ok {
   410  			ast.SetRelPos(f.Label, token.NoSpace)
   411  		}
   412  	}
   413  	s.Lbrace = token.Newline.Pos()
   414  	s.Rbrace = token.NoSpace.Pos()
   415  	return e
   416  }
   417  
   418  // IsEllipsis reports whether the declaration can be represented as an ellipsis.
   419  func IsEllipsis(x ast.Decl) bool {
   420  	// ...
   421  	if _, ok := x.(*ast.Ellipsis); ok {
   422  		return true
   423  	}
   424  
   425  	// [string]: _ or [_]: _
   426  	f, ok := x.(*ast.Field)
   427  	if !ok {
   428  		return false
   429  	}
   430  	v, ok := f.Value.(*ast.Ident)
   431  	if !ok || v.Name != "_" {
   432  		return false
   433  	}
   434  	l, ok := f.Label.(*ast.ListLit)
   435  	if !ok || len(l.Elts) != 1 {
   436  		return false
   437  	}
   438  	i, ok := l.Elts[0].(*ast.Ident)
   439  	if !ok {
   440  		return false
   441  	}
   442  	return i.Name == "string" || i.Name == "_"
   443  }
   444  
   445  // GenPath reports the directory in which to store generated files.
   446  func GenPath(root string) string {
   447  	return filepath.Join(root, "cue.mod", "gen")
   448  }
   449  
   450  var ErrInexact = errors.New("inexact subsumption")
   451  
   452  func DecorateError(info error, err errors.Error) errors.Error {
   453  	return &decorated{cueError: err, info: info}
   454  }
   455  
   456  type cueError = errors.Error
   457  
   458  type decorated struct {
   459  	cueError
   460  
   461  	info error
   462  }
   463  
   464  func (e *decorated) Is(err error) bool {
   465  	return errors.Is(e.info, err) || errors.Is(e.cueError, err)
   466  }
   467  
   468  // MaxDepth indicates the maximum evaluation depth. This is there to break
   469  // cycles in the absence of cycle detection.
   470  //
   471  // It is registered in a central place to make it easy to find all spots where
   472  // cycles are broken in this brute-force manner.
   473  //
   474  // TODO(eval): have cycle detection.
   475  const MaxDepth = 20
   476  

View as plain text