this repo has no description
at master 307 lines 8.6 kB view raw
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. 18package internal 19 20// TODO: refactor packages as to make this package unnecessary. 21 22import ( 23 "bufio" 24 "fmt" 25 "path/filepath" 26 "strings" 27 "unicode/utf8" 28 29 "github.com/cockroachdb/apd/v3" 30 31 "cuelang.org/go/cue/ast" 32 "cuelang.org/go/cue/token" 33) 34 35// A Decimal is an arbitrary-precision binary-coded decimal number. 36// 37// Right now Decimal is aliased to apd.Decimal. This may change in the future. 38type Decimal = apd.Decimal 39 40// Context wraps apd.Context for CUE's custom logic. 41// 42// Note that it avoids pointers to make it easier to make copies. 43type Context struct { 44 apd.Context 45} 46 47// WithPrecision mirrors upstream, but returning our type without a pointer. 48func (c Context) WithPrecision(p uint32) Context { 49 c.Context = *c.Context.WithPrecision(p) 50 return c 51} 52 53// apd/v2 used to call Reduce on the result of Quo and Rem, 54// so that the operations always trimmed all but one trailing zeros. 55// apd/v3 does not do that at all. 56// For now, get the old behavior back by calling Reduce ourselves. 57// Note that v3's Reduce also removes all trailing zeros, 58// whereas v2's Reduce would leave ".0" behind. 59// Get that detail back as well, to consistently show floats with decimal points. 60// 61// TODO: Rather than reducing all trailing zeros, 62// we should keep a number of zeros that makes sense given the operation. 63 64func reduceKeepingFloats(d *apd.Decimal) { 65 oldExponent := d.Exponent 66 d.Reduce(d) 67 // If the decimal had decimal places, like "3.000" and "5.000E+5", 68 // Reduce gives us "3" and "5E+5", but we want "3.0" and "5.0E+5". 69 if oldExponent < 0 && d.Exponent >= 0 { 70 d.Exponent-- 71 // TODO: we can likely make the NewBigInt(10) a static global to reduce allocs 72 d.Coeff.Mul(&d.Coeff, apd.NewBigInt(10)) 73 } 74} 75 76func (c Context) Quo(d, x, y *apd.Decimal) (apd.Condition, error) { 77 res, err := c.Context.Quo(d, x, y) 78 reduceKeepingFloats(d) 79 return res, err 80} 81 82func (c Context) Sqrt(d, x *apd.Decimal) (apd.Condition, error) { 83 res, err := c.Context.Sqrt(d, x) 84 reduceKeepingFloats(d) 85 return res, err 86} 87 88// BaseContext is used as CUE's default context for arbitrary-precision decimals. 89var BaseContext = Context{*apd.BaseContext.WithPrecision(34)} 90 91// EvaluatorVersion is declared here so it can be used everywhere without import cycles, 92// but the canonical documentation lives at [cuelang.org/go/cue/cuecontext.EvalVersion]. 93// 94// TODO(mvdan): rename to EvalVersion for consistency with cuecontext. 95type EvaluatorVersion int 96 97const ( 98 // EvalVersionUnset is the zero value, which signals that no evaluator version is provided. 99 EvalVersionUnset EvaluatorVersion = 0 100 101 // DefaultVersion is a special value as it selects a version depending on the current 102 // value of CUE_EXPERIMENT. It exists separately to [EvalVersionUnset], even though both 103 // implement the same version selection logic, so that we can distinguish between 104 // a user explicitly asking for the default version versus an entirely unset version. 105 DefaultVersion EvaluatorVersion = -1 // TODO(mvdan): rename to EvalDefault for consistency with cuecontext 106 107 // The values below are documented under [cuelang.org/go/cue/cuecontext.EvalVersion]. 108 // We should never change or delete the values below, as they describe all known past versions 109 // which is useful for understanding old debug output. 110 111 EvalV2 EvaluatorVersion = 2 112 EvalV3 EvaluatorVersion = 3 113 114 // The current default, stable, and experimental versions. 115 116 StableVersion = EvalV3 // TODO(mvdan): rename to EvalStable for consistency with cuecontext 117 DevVersion = EvalV3 // TODO(mvdan): rename to EvalExperiment for consistency with cuecontext 118) 119 120// Package finds the package declaration from the preamble of a file, 121// returning it, and its index within the file's Decls. 122func Package(f *ast.File) (*ast.Package, int) { 123 for i, d := range f.Decls { 124 switch d := d.(type) { 125 case *ast.CommentGroup: 126 case *ast.Attribute: 127 case *ast.Package: 128 if d.Name == nil { // malformed package declaration 129 return nil, -1 130 } 131 return d, i 132 default: 133 return nil, -1 134 } 135 } 136 return nil, -1 137} 138 139// NewComment creates a new CommentGroup from the given text. 140// Each line is prefixed with "//" and the last newline is removed. 141// Useful for ASTs generated by code other than the CUE parser. 142func NewComment(isDoc bool, s string) *ast.CommentGroup { 143 if s == "" { 144 return nil 145 } 146 cg := &ast.CommentGroup{Doc: isDoc} 147 if !isDoc { 148 cg.Line = true 149 cg.Position = 10 150 } 151 scanner := bufio.NewScanner(strings.NewReader(s)) 152 for scanner.Scan() { 153 scanner := bufio.NewScanner(strings.NewReader(scanner.Text())) 154 scanner.Split(bufio.ScanWords) 155 const maxRunesPerLine = 66 156 count := 2 157 buf := strings.Builder{} 158 buf.WriteString("//") 159 for scanner.Scan() { 160 s := scanner.Text() 161 n := utf8.RuneCountInString(s) + 1 162 if count+n > maxRunesPerLine && count > 3 { 163 cg.List = append(cg.List, &ast.Comment{Text: buf.String()}) 164 count = 3 165 buf.Reset() 166 buf.WriteString("//") 167 } 168 buf.WriteString(" ") 169 buf.WriteString(s) 170 count += n 171 } 172 cg.List = append(cg.List, &ast.Comment{Text: buf.String()}) 173 } 174 if last := len(cg.List) - 1; cg.List[last].Text == "//" { 175 cg.List = cg.List[:last] 176 } 177 return cg 178} 179 180func FileComments(f *ast.File) (docs, rest []*ast.CommentGroup) { 181 hasPkg := false 182 if pkg, _ := Package(f); pkg != nil { 183 hasPkg = true 184 docs = ast.Comments(pkg) 185 } 186 187 for _, c := range ast.Comments(f) { 188 if c.Doc { 189 docs = append(docs, c) 190 } else { 191 rest = append(rest, c) 192 } 193 } 194 195 if !hasPkg && len(docs) == 0 && len(rest) > 0 { 196 // use the first file comment group as as doc comment. 197 docs, rest = rest[:1], rest[1:] 198 docs[0].Doc = true 199 } 200 201 return 202} 203 204// ToExpr converts a node to an expression. If it is a file, it will return 205// it as a struct. If is an expression, it will return it as is. Otherwise 206// it panics. 207func ToExpr(n ast.Node) ast.Expr { 208 switch x := n.(type) { 209 case nil: 210 return nil 211 212 case ast.Expr: 213 return x 214 215 case *ast.File: 216 start := 0 217 outer: 218 for i, d := range x.Decls { 219 switch d.(type) { 220 case *ast.Package, *ast.ImportDecl: 221 start = i + 1 222 case *ast.CommentGroup, *ast.Attribute: 223 default: 224 break outer 225 } 226 } 227 decls := x.Decls[start:] 228 if len(decls) == 1 { 229 if e, ok := decls[0].(*ast.EmbedDecl); ok { 230 return e.Expr 231 } 232 } 233 return &ast.StructLit{Elts: decls} 234 235 default: 236 panic(fmt.Sprintf("Unsupported node type %T", x)) 237 } 238} 239 240// ToFile converts an expression to a file. 241// 242// Adjusts the spacing of x when needed. 243func ToFile(n ast.Node) *ast.File { 244 if n == nil { 245 return nil 246 } 247 switch n := n.(type) { 248 case *ast.StructLit: 249 f := &ast.File{Decls: n.Elts} 250 // Ensure that the comments attached to the struct literal are not lost. 251 ast.SetComments(f, ast.Comments(n)) 252 return f 253 case ast.Expr: 254 ast.SetRelPos(n, token.NoSpace) 255 return &ast.File{Decls: []ast.Decl{&ast.EmbedDecl{Expr: n}}} 256 case *ast.File: 257 return n 258 default: 259 panic(fmt.Sprintf("Unsupported node type %T", n)) 260 } 261} 262 263func IsDef(s string) bool { 264 return strings.HasPrefix(s, "#") || strings.HasPrefix(s, "_#") 265} 266 267func IsHidden(s string) bool { 268 return strings.HasPrefix(s, "_") 269} 270 271func IsDefOrHidden(s string) bool { 272 return strings.HasPrefix(s, "#") || strings.HasPrefix(s, "_") 273} 274 275func IsDefinition(label ast.Label) bool { 276 switch x := label.(type) { 277 case *ast.Alias: 278 if ident, ok := x.Expr.(*ast.Ident); ok { 279 return IsDef(ident.Name) 280 } 281 case *ast.Ident: 282 return IsDef(x.Name) 283 } 284 return false 285} 286 287func IsRegularField(f *ast.Field) bool { 288 var ident *ast.Ident 289 switch x := f.Label.(type) { 290 case *ast.Alias: 291 ident, _ = x.Expr.(*ast.Ident) 292 case *ast.Ident: 293 ident = x 294 } 295 if ident == nil { 296 return true 297 } 298 if strings.HasPrefix(ident.Name, "#") || strings.HasPrefix(ident.Name, "_") { 299 return false 300 } 301 return true 302} 303 304// GenPath reports the directory in which to store generated files. 305func GenPath(root string) string { 306 return filepath.Join(root, "cue.mod", "gen") 307}