github.com/haraldrudell/parl@v0.4.176/pruntime/code-location.go (about)

     1  /*
     2  © 2021–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package pruntime
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strconv"
    14  
    15  	"github.com/haraldrudell/parl/pruntime/pruntimelib"
    16  )
    17  
    18  const (
    19  	// counts [pruntime.NewCodeLocation]
    20  	newCodeLocationStackFrames = 1
    21  )
    22  
    23  // CodeLocation represents an executing code location, ie. a code line in source code
    24  //   - CodeLocation is similar to the location in [runtime.Frame], but
    25  //     contains only basic types string and int
    26  type CodeLocation struct {
    27  	// File is absolute path to the Go source file
    28  	//	- “/opt/foxyboy/sw/privates/parl/mains/executable.go”
    29  	//	- may be “<autogenerated>” for tests
    30  	File string
    31  	// Line is the line number in the source file, eg. 35
    32  	Line int
    33  	// FuncName is the fully qualified Go package path,
    34  	// a possible value or pointer receiver struct name,
    35  	// and the function name
    36  	//	- “github.com/haraldrudell/parl/mains.(*Executable).AddErr”
    37  	//	- anonymous functions have names like “func1”
    38  	FuncName string
    39  }
    40  
    41  // NewCodeLocation gets data for a single stack frame
    42  //   - for stackFramesToSkip 0, NewCodeLocation returns data for
    43  //     its immediate caller
    44  func NewCodeLocation(stackFramesToSkip int) (cl *CodeLocation) {
    45  	if stackFramesToSkip < 0 {
    46  		stackFramesToSkip = 0
    47  	}
    48  	var c = CodeLocation{}
    49  
    50  	// obtain code-location ifnrormation using [runtime.Caller]
    51  	var pc uintptr
    52  	var ok bool
    53  	// pc: opaque
    54  	// file: basename.go
    55  	// line: int 25
    56  	if pc, c.File, c.Line, ok = runtime.Caller(newCodeLocationStackFrames + stackFramesToSkip); !ok {
    57  		panic(errors.New("runtime.Caller failed"))
    58  	}
    59  
    60  	// obtain the fully qualified function name using [runtime.FuncForPC]
    61  	// rFunc: runtime.Func is all opaque fields. methods:
    62  	// Entry() (uintptr)
    63  	// FileLine(uintptr) (line string, line int) "/opt/foxyboy/sw/privates/parl/mains/executable.go"
    64  	// Name(): github.com/haraldrudell/parl/mains.(*Executable).AddErr
    65  	if rFunc := runtime.FuncForPC(pc); rFunc != nil {
    66  		c.FuncName = rFunc.Name()
    67  	}
    68  
    69  	return &c
    70  }
    71  
    72  // CodeLocationFromFunc returns a code location value from a
    73  // non-nil [runtime.Func] value
    74  //   - Func values are return by [runtime.FuncForPC]
    75  func CodeLocationFromFunc(runtimeFunc *runtime.Func) (cl *CodeLocation) {
    76  	cl = &CodeLocation{FuncName: runtimeFunc.Name()}
    77  	cl.File, cl.Line = runtimeFunc.FileLine(runtimeFunc.Entry())
    78  	return
    79  }
    80  
    81  // FuncName returns function name, characters no space:
    82  // “AddErr”
    83  func (cl *CodeLocation) Name() (funcName string) {
    84  	_, _, _, funcName = pruntimelib.SplitAbsoluteFunctionName(cl.FuncName)
    85  	return
    86  }
    87  
    88  // Package return base package name, a single word of characters with no space:
    89  // “mains”
    90  func (cl *CodeLocation) Package() (packageName string) {
    91  	_, packageName, _, _ = pruntimelib.SplitAbsoluteFunctionName(cl.FuncName)
    92  	return
    93  }
    94  
    95  // PackFunc return base package name and function
    96  // “mains.AddErr”
    97  func (cl *CodeLocation) PackFunc() (packageDotFunction string) {
    98  	_, packageName, _, funcName := pruntimelib.SplitAbsoluteFunctionName(cl.FuncName)
    99  	return packageName + "." + funcName
   100  }
   101  
   102  // FuncIdentifier return the function name identifier
   103  // “AddErr”
   104  //   - anonymous function like “SomeFunc.func1”
   105  func (cl *CodeLocation) FuncIdentifier() (funcIdentifier string) {
   106  	_, _, _, funcIdentifier = pruntimelib.SplitAbsoluteFunctionName(cl.FuncName)
   107  	return
   108  }
   109  
   110  // Base returns base package name, an optional type name and the function name:
   111  // “mains.(*Executable).AddErr”
   112  func (cl *CodeLocation) Base() (baseName string) {
   113  	return filepath.Base(cl.FuncName)
   114  }
   115  
   116  // FuncLine retuns the fully qualified function name and its line number:
   117  // “github.com/haraldrudell/parl/mains.(*Executable).AddErr:43”
   118  func (cl *CodeLocation) FuncLine() (funcLine string) {
   119  	return cl.FuncName + ":" + strconv.Itoa(cl.Line)
   120  }
   121  
   122  // Short returns base package name, an optional type name and
   123  // the function name, base filename and line number:
   124  // “mains.(*Executable).AddErr-executable.go:25”
   125  func (cl *CodeLocation) Short() (funcName string) {
   126  	return fmt.Sprintf("%s()-%s:%d", filepath.Base(cl.FuncName), filepath.Base(cl.File), cl.Line)
   127  }
   128  
   129  // Long returns full package path, an optional type name and
   130  // the function name, base filename and line number:
   131  // “github.com/haraldrudell/parl/mains.(*Executable).AddErr-executable.go:25”
   132  func (cl *CodeLocation) Long() (funcName string) {
   133  	return fmt.Sprintf("%s-%s:%d", cl.FuncName, cl.File, cl.Line)
   134  }
   135  
   136  // IsSet returns true if this CodeLocation has a value, ie. is not zero-value
   137  func (cl *CodeLocation) IsSet() (isSet bool) { return cl.File != "" || cl.FuncName != "" }
   138  
   139  // Dump outputs all values quoted for debug purposes:
   140  //   - File: "/opt/homebrew/Cellar/go/1.20.4/libexec/src/testing/testing.go"
   141  //   - Line: 1576
   142  //   - FuncName: "github.com/haraldrudell/parl/mains.(*Executable).AddErr"
   143  func (cl *CodeLocation) Dump() (s string) {
   144  	return fmt.Sprintf("File: %q Line: %d FuncName: %q", cl.File, cl.Line, cl.FuncName)
   145  }
   146  
   147  // String returns a two-line string representation suitable for a multi-line stack trace.
   148  // Typical output:
   149  //   - “github.com/haraldrudell/parl/error116.(*TypeName).FuncName␤
   150  //     ␠␠/opt/sw/privates/parl/error116/codelocation_test.go:20”
   151  //   - indentation is two spaces, not tab characters
   152  func (cl CodeLocation) String() string {
   153  	return fmt.Sprintf("%s\n\x20\x20%s:%d", cl.FuncName, cl.File, cl.Line)
   154  }