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  }