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