cuelang.org/go@v0.13.0/internal/core/adt/log.go (about) 1 // Copyright 2025 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 // https://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 adt 16 17 import ( 18 "fmt" 19 "io" 20 "log" 21 "path/filepath" 22 "runtime" 23 "strings" 24 25 "cuelang.org/go/cue/token" 26 ) 27 28 // Assert panics if the condition is false. Assert can be used to check for 29 // conditions that are considers to break an internal variant or unexpected 30 // condition, but that nonetheless probably will be handled correctly down the 31 // line. For instance, a faulty condition could lead to error being caught 32 // down the road, but resulting in an inaccurate error message. In production 33 // code it is better to deal with the bad error message than to panic. 34 // 35 // It is advisable for each use of Assert to document how the error is expected 36 // to be handled down the line. 37 func Assertf(c *OpContext, b bool, format string, args ...interface{}) { 38 if c.Strict && !b { 39 panic(fmt.Sprintf("assertion failed: "+format, args...)) 40 } 41 } 42 43 // Assertf either panics or reports an error to c if the condition is not met. 44 func (c *OpContext) Assertf(pos token.Pos, b bool, format string, args ...interface{}) { 45 if !b { 46 if c.Strict { 47 panic(fmt.Sprintf("assertion failed: "+format, args...)) 48 } 49 c.addErrf(0, pos, format, args...) 50 } 51 } 52 53 func init() { 54 log.SetFlags(0) 55 } 56 57 var pMap = map[*Vertex]int{} 58 59 type nestString string 60 61 func (c *OpContext) Un(s nestString, id int) { 62 c.nest-- 63 if id != c.logID { 64 c.Logf(nil, "END %s", string(s)) 65 } 66 } 67 68 // Indentf logs a function call and increases the nesting level. 69 // The first argument must be the function name. 70 func (c *OpContext) Indentf(v *Vertex, format string, args ...any) (s nestString, id int) { 71 if c.LogEval == 0 { 72 // The Go compiler as of 1.24 is not very clever with no-op function calls; 73 // any arguments passed to ...args above escape to the heap and allocate. 74 panic("avoid calling OpContext.Indentf when logging is disabled to prevent overhead") 75 } 76 name := strings.Split(format, "(")[0] 77 if name == "" { 78 name, _ = getCallerFunctionName(1) 79 format = name + format 80 } 81 82 caller, line := getCallerFunctionName(2) 83 args = append(args, caller, line) 84 85 format += " %s:%d" 86 87 c.Logf(v, format, args...) 88 c.nest++ 89 90 return nestString(name), c.logID 91 } 92 93 func (c *OpContext) RewriteArgs(args ...interface{}) { 94 for i, a := range args { 95 switch x := a.(type) { 96 case Node: 97 args[i] = c.Str(x) 98 case Feature: 99 args[i] = x.SelectorString(c) 100 } 101 } 102 } 103 104 func (c *OpContext) Logf(v *Vertex, format string, args ...interface{}) { 105 if c.LogEval == 0 { 106 // The Go compiler as of 1.24 is not very clever with no-op function calls; 107 // any arguments passed to ...args above escape to the heap and allocate. 108 panic("avoid calling OpContext.Logf when logging is disabled to prevent overhead") 109 } 110 w := &strings.Builder{} 111 112 c.logID++ 113 fmt.Fprintf(w, "%3d ", c.logID) 114 115 if c.nest > 0 { 116 for i := 0; i < c.nest; i++ { 117 w.WriteString("... ") 118 } 119 } 120 121 if v == nil { 122 fmt.Fprintf(w, format, args...) 123 _ = log.Output(2, w.String()) 124 return 125 } 126 127 c.RewriteArgs(args...) 128 n, _ := fmt.Fprintf(w, format, args...) 129 if n < 60 { 130 w.WriteString(strings.Repeat(" ", 60-n)) 131 } 132 133 p := pMap[v] 134 if p == 0 { 135 p = len(pMap) + 1 136 pMap[v] = p 137 } 138 disjunctInfo := c.disjunctInfo() 139 fmt.Fprintf(w, "; n:%d %v %v%s ", 140 p, c.PathToString(v.Path()), v.Path(), disjunctInfo) 141 142 _ = log.Output(2, w.String()) 143 } 144 145 // PathToString creates a pretty-printed path of the given list of features. 146 func (c *OpContext) PathToString(path []Feature) string { 147 var b strings.Builder 148 for i, f := range path { 149 if i > 0 { 150 b.WriteByte('.') 151 } 152 b.WriteString(f.SelectorString(c)) 153 } 154 return b.String() 155 } 156 157 type disjunctInfo struct { 158 node *nodeContext 159 disjunctionID int // unique ID for sequence 160 disjunctionSeq int // index into node.disjunctions 161 numDisjunctions int // number of disjunctions 162 crossProductSeq int // index into node.disjuncts (previous results) 163 numPrevious int // index into node.disjuncts (previous results) 164 numDisjuncts int // index into node.disjuncts (previous results) 165 disjunctID int // unique ID for disjunct 166 disjunctSeq int // index into node.disjunctions[disjunctionSeq].disjuncts 167 holeID int // unique ID for hole 168 lhs Node // current LHS expression 169 rhs Node // current RHS expression 170 } 171 172 func (c *OpContext) currentDisjunct() *disjunctInfo { 173 if len(c.disjunctStack) == 0 { 174 panic("no disjunct") 175 } 176 return &c.disjunctStack[len(c.disjunctStack)-1] 177 } 178 179 func (n *nodeContext) pushDisjunctionTask() *disjunctInfo { 180 c := n.ctx 181 c.currentDisjunctionID++ 182 id := disjunctInfo{ 183 node: n, 184 disjunctionID: c.currentDisjunctionID, 185 } 186 c.disjunctStack = append(c.disjunctStack, id) 187 188 return c.currentDisjunct() 189 } 190 191 func (n *nodeContext) nextDisjunction(index, num, hole int) { 192 d := n.ctx.currentDisjunct() 193 194 d.disjunctionSeq = index + 1 195 d.numDisjunctions = num 196 d.holeID = hole 197 } 198 199 func (n *nodeContext) nextCrossProduct(index, num int, v *nodeContext) *disjunctInfo { 200 d := n.ctx.currentDisjunct() 201 202 d.crossProductSeq = index + 1 203 d.numPrevious = num 204 d.lhs = v.node.Value() 205 206 return d 207 } 208 209 func (n *nodeContext) nextDisjunct(index, num int, expr Node) { 210 d := n.ctx.currentDisjunct() 211 212 d.disjunctSeq = index + 1 213 d.numDisjuncts = num 214 d.rhs = expr 215 } 216 217 func (n *nodeContext) logDoDisjunct() *disjunctInfo { 218 c := n.ctx 219 c.stats.Disjuncts++ 220 221 d := c.currentDisjunct() 222 223 d.disjunctID = int(c.stats.Disjuncts) 224 225 if n.ctx.LogEval > 0 { 226 n.Logf("====== Do DISJUNCT %v & %v ======", d.lhs, d.rhs) 227 } 228 229 return d 230 } 231 232 func (d disjunctInfo) pop() { 233 c := d.node.ctx 234 c.disjunctStack = c.disjunctStack[:len(c.disjunctStack)-1] 235 } 236 237 // Format implements the fmt.Formatter interface for disjunctInfo. 238 func (d *disjunctInfo) Format(f fmt.State, c rune) { 239 d.Write(f) 240 } 241 242 func (d *disjunctInfo) Write(w io.Writer) { 243 // which disjunct 244 fmt.Fprintf(w, " D%d:H%d:%d/%d", 245 d.disjunctionID, d.holeID, d.disjunctionSeq, d.numDisjunctions) 246 if d.crossProductSeq != 0 { 247 fmt.Fprintf(w, " P%d/%d", d.crossProductSeq, d.numPrevious) 248 } 249 if d.disjunctID != 0 { 250 fmt.Fprintf(w, " d%d:%d/%d", 251 d.disjunctID, d.disjunctSeq, d.numDisjuncts, 252 ) 253 } 254 } 255 256 // disjunctInfo prints a header for log to indicate the current disjunct. 257 func (c *OpContext) disjunctInfo() string { 258 if len(c.disjunctStack) == 0 { 259 return "" 260 } 261 var b strings.Builder 262 for i, d := range c.disjunctStack { 263 if i != len(c.disjunctStack)-1 && d.disjunctID == 0 { 264 continue 265 } 266 if i != 0 { 267 b.WriteString(" =>") 268 } 269 d.Write(&b) 270 } 271 return b.String() 272 } 273 274 func getCallerFunctionName(i int) (caller string, line int) { 275 pc, _, line, ok := runtime.Caller(1 + i) 276 if !ok { 277 return "unknown", 0 278 } 279 fn := runtime.FuncForPC(pc) 280 if fn == nil { 281 return "unknown", 0 282 } 283 fullName := fn.Name() 284 name := filepath.Base(fullName) 285 if idx := strings.LastIndex(name, "."); idx != -1 { 286 name = name[idx+1:] 287 } 288 return name, line 289 }