mirror of
https://github.com/google/go-jsonnet.git
synced 2025-08-07 14:57:24 +02:00
feat: add debugger support (#739)
This commit is contained in:
parent
fed90cd9cd
commit
6838b0a0b8
401
debugger.go
Normal file
401
debugger.go
Normal file
@ -0,0 +1,401 @@
|
||||
package jsonnet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-jsonnet/ast"
|
||||
"github.com/google/go-jsonnet/toolutils"
|
||||
)
|
||||
|
||||
type Debugger struct {
|
||||
// VM evaluating the input
|
||||
vm *VM
|
||||
|
||||
// Interpreter built by the evaluation. Required to look up variables and stack traces
|
||||
interpreter *interpreter
|
||||
|
||||
// breakpoints are stored as the result of the .String function of
|
||||
// *ast.LocationRange to speed up lookup
|
||||
breakpoints map[string]bool
|
||||
|
||||
// The events channel is used to communicate events happening in the VM with the debugger
|
||||
events chan DebugEvent
|
||||
// The cont channel is used to pass continuation events from the frontend to the VM
|
||||
cont chan continuationEvent
|
||||
|
||||
// lastEvaluation stores the result of the last evaluated node
|
||||
lastEvaluation value
|
||||
|
||||
// breakOnNode allows the debugger to request continuation until after a
|
||||
// certain node has been evaluated (step-out)
|
||||
breakOnNode ast.Node
|
||||
|
||||
// singleStep is used to break on every instruction if set to true
|
||||
singleStep bool
|
||||
|
||||
// skip skips all hooks when performing sub-evaluation (to lookup vars)
|
||||
skip bool
|
||||
|
||||
// current keeps track of the node currently being evaluated
|
||||
current ast.Node
|
||||
}
|
||||
|
||||
// ContinuationEvents are sent by the debugger frontend. Specifying `until`
|
||||
// results in continuation until the evaluated node matches the argument
|
||||
type continuationEvent struct {
|
||||
until *ast.Node
|
||||
}
|
||||
|
||||
type DebugStopReason int
|
||||
|
||||
const (
|
||||
StopReasonStep DebugStopReason = iota
|
||||
StopReasonBreakpoint
|
||||
StopReasonException
|
||||
)
|
||||
|
||||
// A DebugEvent is emitted by the hooks to signal certain events happening in the VM. Examples are:
|
||||
// - Hitting a breakpoint
|
||||
// - Catching an exception
|
||||
// - Program termination
|
||||
type DebugEvent interface {
|
||||
anEvent()
|
||||
}
|
||||
|
||||
type DebugEventExit struct {
|
||||
Output string
|
||||
Error error
|
||||
}
|
||||
|
||||
func (d *DebugEventExit) anEvent() {}
|
||||
|
||||
type DebugEventStop struct {
|
||||
Reason DebugStopReason
|
||||
Breakpoint string
|
||||
Current ast.Node
|
||||
LastEvaluation *string
|
||||
Error error
|
||||
|
||||
// efmt is used to format the error (if any). Built by the vm so we need to
|
||||
// keep a reference in the event
|
||||
efmt ErrorFormatter
|
||||
}
|
||||
|
||||
func (d *DebugEventStop) anEvent() {}
|
||||
func (d *DebugEventStop) ErrorFmt() string {
|
||||
return d.efmt.Format(d.Error)
|
||||
}
|
||||
|
||||
func MakeDebugger() *Debugger {
|
||||
d := &Debugger{
|
||||
events: make(chan DebugEvent, 2048),
|
||||
cont: make(chan continuationEvent),
|
||||
}
|
||||
vm := MakeVM()
|
||||
vm.EvalHook = EvalHook{
|
||||
pre: d.preHook,
|
||||
post: d.postHook,
|
||||
}
|
||||
d.vm = vm
|
||||
d.breakpoints = make(map[string]bool)
|
||||
return d
|
||||
}
|
||||
|
||||
func traverse(root ast.Node, f func(node *ast.Node) error) error {
|
||||
if err := f(&root); err != nil {
|
||||
return fmt.Errorf("pre error: %w", err)
|
||||
}
|
||||
|
||||
children := toolutils.Children(root)
|
||||
for _, c := range children {
|
||||
if err := traverse(c, f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Debugger) Continue() {
|
||||
d.cont <- continuationEvent{}
|
||||
}
|
||||
func (d *Debugger) ContinueUntilAfter(n ast.Node) {
|
||||
d.cont <- continuationEvent{
|
||||
until: &n,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Debugger) Step() {
|
||||
d.singleStep = true
|
||||
d.Continue()
|
||||
}
|
||||
|
||||
func (d *Debugger) Terminate() {
|
||||
d.events <- &DebugEventExit{
|
||||
Error: fmt.Errorf("terminated"),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Debugger) postHook(i *interpreter, n ast.Node, v value, err error) {
|
||||
d.lastEvaluation = v
|
||||
if d.skip {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
d.events <- &DebugEventStop{
|
||||
Current: n,
|
||||
Reason: StopReasonException,
|
||||
Error: err,
|
||||
efmt: d.vm.ErrorFormatter,
|
||||
}
|
||||
d.waitForContinuation()
|
||||
}
|
||||
if d.breakOnNode == n {
|
||||
d.breakOnNode = nil
|
||||
d.singleStep = true
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Debugger) waitForContinuation() {
|
||||
c := <-d.cont
|
||||
if c.until != nil {
|
||||
d.breakOnNode = *c.until
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Debugger) preHook(i *interpreter, n ast.Node) {
|
||||
d.interpreter = i
|
||||
d.current = n
|
||||
if d.skip {
|
||||
return
|
||||
}
|
||||
|
||||
switch n.(type) {
|
||||
case *ast.LiteralNull, *ast.LiteralNumber, *ast.LiteralString, *ast.LiteralBoolean:
|
||||
return
|
||||
}
|
||||
l := n.Loc()
|
||||
if l.File == nil {
|
||||
return
|
||||
}
|
||||
vs := valueToString(d.lastEvaluation)
|
||||
if d.singleStep {
|
||||
d.singleStep = false
|
||||
d.events <- &DebugEventStop{
|
||||
Reason: StopReasonStep,
|
||||
Current: n,
|
||||
LastEvaluation: &vs,
|
||||
}
|
||||
d.waitForContinuation()
|
||||
return
|
||||
}
|
||||
loc := n.Loc()
|
||||
if loc == nil || loc.File == nil {
|
||||
// virtual file such as <std>
|
||||
return
|
||||
}
|
||||
if _, ok := d.breakpoints[loc.String()]; ok {
|
||||
d.events <- &DebugEventStop{
|
||||
Reason: StopReasonBreakpoint,
|
||||
Breakpoint: loc.Begin.String(),
|
||||
Current: n,
|
||||
LastEvaluation: &vs,
|
||||
}
|
||||
d.waitForContinuation()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func valueToString(v value) string {
|
||||
switch i := v.(type) {
|
||||
case *valueFlatString:
|
||||
return "\"" + i.getGoString() + "\""
|
||||
case *valueObject:
|
||||
if i == nil {
|
||||
return "{}"
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString("{")
|
||||
firstLine := true
|
||||
for k, v := range i.cache {
|
||||
if k.depth != 0 {
|
||||
continue
|
||||
}
|
||||
if !firstLine {
|
||||
sb.WriteString(", ")
|
||||
firstLine = true
|
||||
}
|
||||
sb.WriteString(k.field)
|
||||
sb.WriteString(": ")
|
||||
sb.WriteString(valueToString(v))
|
||||
}
|
||||
sb.WriteString("}")
|
||||
return sb.String()
|
||||
case *valueArray:
|
||||
var sb strings.Builder
|
||||
sb.WriteString("[")
|
||||
for i, e := range i.elements {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(valueToString(e.content))
|
||||
}
|
||||
sb.WriteString("]")
|
||||
return sb.String()
|
||||
case *valueNumber:
|
||||
return fmt.Sprintf("%f", i.value)
|
||||
case *valueBoolean:
|
||||
return fmt.Sprintf("%t", i.value)
|
||||
case *valueFunction:
|
||||
var sb strings.Builder
|
||||
sb.WriteString("function(")
|
||||
for i, p := range i.parameters() {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(string(p.name))
|
||||
}
|
||||
sb.WriteString(")")
|
||||
return sb.String()
|
||||
}
|
||||
return fmt.Sprintf("%T%+v", v, v)
|
||||
}
|
||||
|
||||
func (d *Debugger) ActiveBreakpoints() []string {
|
||||
bps := []string{}
|
||||
for k := range d.breakpoints {
|
||||
bps = append(bps, k)
|
||||
}
|
||||
return bps
|
||||
}
|
||||
|
||||
func (d *Debugger) BreakpointLocations(file string) ([]*ast.LocationRange, error) {
|
||||
abs, err := filepath.Abs(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, err := os.ReadFile(abs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
a, err := SnippetToAST(file, string(raw))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid source file: %w", err)
|
||||
}
|
||||
bps := []*ast.LocationRange{}
|
||||
traverse(a, func(n *ast.Node) error {
|
||||
if n != nil {
|
||||
l := (*n).Loc()
|
||||
if l.File != nil {
|
||||
bps = append(bps, l)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return bps, nil
|
||||
}
|
||||
|
||||
func (d *Debugger) SetBreakpoint(file string, line int, column int) (string, error) {
|
||||
valid, err := d.BreakpointLocations(file)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting valid breakpoint locations: %w", err)
|
||||
}
|
||||
target := ""
|
||||
for _, b := range valid {
|
||||
if b.Begin.Line == line {
|
||||
if column < 0 {
|
||||
target = b.String()
|
||||
break
|
||||
} else if b.Begin.Column == column {
|
||||
target = b.String()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if target == "" {
|
||||
return "", fmt.Errorf("breakpoint location invalid")
|
||||
}
|
||||
d.breakpoints[target] = true
|
||||
return target, nil
|
||||
}
|
||||
func (d *Debugger) ClearBreakpoints(file string) {
|
||||
abs, _ := filepath.Abs(file)
|
||||
for k := range d.breakpoints {
|
||||
parts := strings.Split(k, ":")
|
||||
full, err := filepath.Abs(parts[0])
|
||||
if err == nil && full == abs {
|
||||
delete(d.breakpoints, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Debugger) LookupValue(val string) (string, error) {
|
||||
switch val {
|
||||
case "self":
|
||||
return valueToString(d.interpreter.stack.getSelfBinding().self), nil
|
||||
case "super":
|
||||
return valueToString(d.interpreter.stack.getSelfBinding().super().self), nil
|
||||
default:
|
||||
v := d.interpreter.stack.lookUpVar(ast.Identifier(val))
|
||||
if v != nil {
|
||||
if v.content == nil {
|
||||
d.skip = true
|
||||
e, err := func() (rv value, err error) { // closure to use defer->recover
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
}()
|
||||
rv, err = d.interpreter.rawevaluate(v.body, 0)
|
||||
return
|
||||
}()
|
||||
d.skip = false
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
v.content = e
|
||||
}
|
||||
return valueToString(v.content), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("invalid identifier %s", val)
|
||||
}
|
||||
|
||||
func (d *Debugger) ListVars() []ast.Identifier {
|
||||
if d.interpreter != nil {
|
||||
return d.interpreter.stack.listVars()
|
||||
}
|
||||
return make([]ast.Identifier, 0)
|
||||
}
|
||||
|
||||
func (d *Debugger) Launch(filename, snippet string, jpaths []string) {
|
||||
jpaths = append(jpaths, filepath.Dir(filename))
|
||||
d.vm.Importer(&FileImporter{
|
||||
JPaths: jpaths,
|
||||
})
|
||||
go func() {
|
||||
out, err := d.vm.EvaluateAnonymousSnippet(filename, snippet)
|
||||
d.events <- &DebugEventExit{
|
||||
Output: out,
|
||||
Error: err,
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *Debugger) Events() chan DebugEvent {
|
||||
return d.events
|
||||
}
|
||||
|
||||
func (d *Debugger) StackTrace() []TraceFrame {
|
||||
if d.interpreter == nil || d.current == nil {
|
||||
return nil
|
||||
}
|
||||
trace := d.interpreter.getCurrentStackTrace()
|
||||
for i, t := range trace {
|
||||
trace[i].Name = t.Loc.FileName // use pseudo file name as name
|
||||
}
|
||||
trace[len(trace)-1].Loc = *d.current.Loc()
|
||||
return trace
|
||||
}
|
@ -110,7 +110,7 @@ func (ef *termErrorFormatter) showCode(buf *bytes.Buffer, loc ast.LocationRange)
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
func (ef *termErrorFormatter) frame(frame *traceFrame, buf *bytes.Buffer) {
|
||||
func (ef *termErrorFormatter) frame(frame *TraceFrame, buf *bytes.Buffer) {
|
||||
// TODO(sbarzowski) tabs are probably a bad idea
|
||||
fmt.Fprintf(buf, "\t%v\t%v\n", frame.Loc.String(), frame.Name)
|
||||
if ef.pretty {
|
||||
@ -118,7 +118,7 @@ func (ef *termErrorFormatter) frame(frame *traceFrame, buf *bytes.Buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ef *termErrorFormatter) buildStackTrace(frames []traceFrame) string {
|
||||
func (ef *termErrorFormatter) buildStackTrace(frames []TraceFrame) string {
|
||||
// https://github.com/google/jsonnet/blob/master/core/libjsonnet.cpp#L594
|
||||
maxAbove := ef.maxStackTraceSize / 2
|
||||
maxBelow := ef.maxStackTraceSize - maxAbove
|
||||
|
@ -49,8 +49,8 @@ func makeEnvironment(upValues bindingFrame, sb selfBinding) environment {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interpreter) getCurrentStackTrace() []traceFrame {
|
||||
var result []traceFrame
|
||||
func (i *interpreter) getCurrentStackTrace() []TraceFrame {
|
||||
var result []TraceFrame
|
||||
for _, f := range i.stack.stack {
|
||||
if f.cleanEnv {
|
||||
result = append(result, traceElementToTraceFrame(f.trace))
|
||||
@ -208,6 +208,20 @@ func (s *callStack) lookUpVar(id ast.Identifier) *cachedThunk {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *callStack) listVars() []ast.Identifier {
|
||||
vars := []ast.Identifier{}
|
||||
for i := len(s.stack) - 1; i >= 0; i-- {
|
||||
for k := range s.stack[i].env.upValues {
|
||||
vars = append(vars, k)
|
||||
}
|
||||
if s.stack[i].cleanEnv {
|
||||
// Nothing beyond the captured environment of the thunk / closure.
|
||||
break
|
||||
}
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
func (s *callStack) lookUpVarOrPanic(id ast.Identifier) *cachedThunk {
|
||||
th := s.lookUpVar(id)
|
||||
if th == nil {
|
||||
@ -239,6 +253,11 @@ func makeCallStack(limit int) callStack {
|
||||
}
|
||||
}
|
||||
|
||||
type EvalHook struct {
|
||||
pre func(i *interpreter, n ast.Node)
|
||||
post func(i *interpreter, n ast.Node, v value, err error)
|
||||
}
|
||||
|
||||
// Keeps current execution context and evaluates things
|
||||
type interpreter struct {
|
||||
// Output stream for trace() for
|
||||
@ -260,6 +279,8 @@ type interpreter struct {
|
||||
// 1) Keeping environment (object we're in, variables)
|
||||
// 2) Diagnostic information in case of failure
|
||||
stack callStack
|
||||
|
||||
evalHook EvalHook
|
||||
}
|
||||
|
||||
// Map union, b takes precedence when keys collide.
|
||||
@ -287,6 +308,13 @@ func (i *interpreter) newCall(env environment, trimmable bool) error {
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
|
||||
i.evalHook.pre(i, a)
|
||||
v, err := i.rawevaluate(a, tc)
|
||||
i.evalHook.post(i, a, v, err)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (i *interpreter) rawevaluate(a ast.Node, tc tailCallStatus) (value, error) {
|
||||
trace := traceElement{
|
||||
loc: a.Loc(),
|
||||
context: a.Context(),
|
||||
@ -1237,12 +1265,13 @@ func buildObject(hide ast.ObjectFieldHide, fields map[string]value) *valueObject
|
||||
return makeValueSimpleObject(bindingFrame{}, fieldMap, nil, nil)
|
||||
}
|
||||
|
||||
func buildInterpreter(ext vmExtMap, nativeFuncs map[string]*NativeFunction, maxStack int, ic *importCache, traceOut io.Writer) (*interpreter, error) {
|
||||
func buildInterpreter(ext vmExtMap, nativeFuncs map[string]*NativeFunction, maxStack int, ic *importCache, traceOut io.Writer, evalHook EvalHook) (*interpreter, error) {
|
||||
i := interpreter{
|
||||
stack: makeCallStack(maxStack),
|
||||
importCache: ic,
|
||||
traceOut: traceOut,
|
||||
nativeFuncs: nativeFuncs,
|
||||
evalHook: evalHook,
|
||||
}
|
||||
|
||||
stdObj, err := buildStdObject(&i)
|
||||
@ -1315,9 +1344,9 @@ func evaluateAux(i *interpreter, node ast.Node, tla vmExtMap) (value, error) {
|
||||
|
||||
// TODO(sbarzowski) this function takes far too many arguments - build interpreter in vm instead
|
||||
func evaluate(node ast.Node, ext vmExtMap, tla vmExtMap, nativeFuncs map[string]*NativeFunction,
|
||||
maxStack int, ic *importCache, traceOut io.Writer, stringOutputMode bool) (string, error) {
|
||||
maxStack int, ic *importCache, traceOut io.Writer, stringOutputMode bool, evalHook EvalHook) (string, error) {
|
||||
|
||||
i, err := buildInterpreter(ext, nativeFuncs, maxStack, ic, traceOut)
|
||||
i, err := buildInterpreter(ext, nativeFuncs, maxStack, ic, traceOut, evalHook)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -1344,9 +1373,9 @@ func evaluate(node ast.Node, ext vmExtMap, tla vmExtMap, nativeFuncs map[string]
|
||||
|
||||
// TODO(sbarzowski) this function takes far too many arguments - build interpreter in vm instead
|
||||
func evaluateMulti(node ast.Node, ext vmExtMap, tla vmExtMap, nativeFuncs map[string]*NativeFunction,
|
||||
maxStack int, ic *importCache, traceOut io.Writer, stringOutputMode bool) (map[string]string, error) {
|
||||
maxStack int, ic *importCache, traceOut io.Writer, stringOutputMode bool, evalHook EvalHook) (map[string]string, error) {
|
||||
|
||||
i, err := buildInterpreter(ext, nativeFuncs, maxStack, ic, traceOut)
|
||||
i, err := buildInterpreter(ext, nativeFuncs, maxStack, ic, traceOut, evalHook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1364,9 +1393,9 @@ func evaluateMulti(node ast.Node, ext vmExtMap, tla vmExtMap, nativeFuncs map[st
|
||||
|
||||
// TODO(sbarzowski) this function takes far too many arguments - build interpreter in vm instead
|
||||
func evaluateStream(node ast.Node, ext vmExtMap, tla vmExtMap, nativeFuncs map[string]*NativeFunction,
|
||||
maxStack int, ic *importCache, traceOut io.Writer) ([]string, error) {
|
||||
maxStack int, ic *importCache, traceOut io.Writer, evalHook EvalHook) ([]string, error) {
|
||||
|
||||
i, err := buildInterpreter(ext, nativeFuncs, maxStack, ic, traceOut)
|
||||
i, err := buildInterpreter(ext, nativeFuncs, maxStack, ic, traceOut, evalHook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -21,10 +21,10 @@ import "github.com/google/go-jsonnet/ast"
|
||||
// RuntimeError is an error discovered during evaluation of the program
|
||||
type RuntimeError struct {
|
||||
Msg string
|
||||
StackTrace []traceFrame
|
||||
StackTrace []TraceFrame
|
||||
}
|
||||
|
||||
func makeRuntimeError(msg string, stackTrace []traceFrame) RuntimeError {
|
||||
func makeRuntimeError(msg string, stackTrace []TraceFrame) RuntimeError {
|
||||
return RuntimeError{
|
||||
Msg: msg,
|
||||
StackTrace: stackTrace,
|
||||
@ -37,15 +37,15 @@ func (err RuntimeError) Error() string {
|
||||
|
||||
// The stack
|
||||
|
||||
// traceFrame is tracing information about a single frame of the call stack.
|
||||
// TraceFrame is tracing information about a single frame of the call stack.
|
||||
// TODO(sbarzowski) the difference from traceElement. Do we even need this?
|
||||
type traceFrame struct {
|
||||
type TraceFrame struct {
|
||||
Name string
|
||||
Loc ast.LocationRange
|
||||
}
|
||||
|
||||
func traceElementToTraceFrame(trace traceElement) traceFrame {
|
||||
tf := traceFrame{Loc: *trace.loc}
|
||||
func traceElementToTraceFrame(trace traceElement) TraceFrame {
|
||||
tf := TraceFrame{Loc: *trace.loc}
|
||||
if trace.context != nil {
|
||||
// TODO(sbarzowski) maybe it should never be nil
|
||||
tf.Name = *trace.context
|
||||
|
35
vm.go
35
vm.go
@ -46,6 +46,7 @@ type VM struct { //nolint:govet
|
||||
StringOutput bool
|
||||
importCache *importCache
|
||||
traceOut io.Writer
|
||||
EvalHook EvalHook
|
||||
}
|
||||
|
||||
// extKind indicates the kind of external variable that is being initialized for the VM
|
||||
@ -81,6 +82,10 @@ func MakeVM() *VM {
|
||||
importer: &FileImporter{},
|
||||
importCache: makeImportCache(defaultImporter),
|
||||
traceOut: os.Stderr,
|
||||
EvalHook: EvalHook{
|
||||
pre: func(i *interpreter, a ast.Node) {},
|
||||
post: func(i *interpreter, a ast.Node, v value, err error) {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +187,7 @@ func (vm *VM) Evaluate(node ast.Node) (val string, err error) {
|
||||
err = fmt.Errorf("(CRASH) %v\n%s", r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
return evaluate(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.StringOutput)
|
||||
return evaluate(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.StringOutput, vm.EvalHook)
|
||||
}
|
||||
|
||||
// EvaluateStream evaluates a Jsonnet program given by an Abstract Syntax Tree
|
||||
@ -193,7 +198,7 @@ func (vm *VM) EvaluateStream(node ast.Node) (output []string, err error) {
|
||||
err = fmt.Errorf("(CRASH) %v\n%s", r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
return evaluateStream(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut)
|
||||
return evaluateStream(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.EvalHook)
|
||||
}
|
||||
|
||||
// EvaluateMulti evaluates a Jsonnet program given by an Abstract Syntax Tree
|
||||
@ -205,7 +210,7 @@ func (vm *VM) EvaluateMulti(node ast.Node) (output map[string]string, err error)
|
||||
err = fmt.Errorf("(CRASH) %v\n%s", r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
return evaluateMulti(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.StringOutput)
|
||||
return evaluateMulti(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.StringOutput, vm.EvalHook)
|
||||
}
|
||||
|
||||
func (vm *VM) evaluateSnippet(diagnosticFileName ast.DiagnosticFileName, filename string, snippet string, kind evalKind) (output interface{}, err error) {
|
||||
@ -220,11 +225,11 @@ func (vm *VM) evaluateSnippet(diagnosticFileName ast.DiagnosticFileName, filenam
|
||||
}
|
||||
switch kind {
|
||||
case evalKindRegular:
|
||||
output, err = evaluate(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.StringOutput)
|
||||
output, err = evaluate(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.StringOutput, vm.EvalHook)
|
||||
case evalKindMulti:
|
||||
output, err = evaluateMulti(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.StringOutput)
|
||||
output, err = evaluateMulti(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.StringOutput, vm.EvalHook)
|
||||
case evalKindStream:
|
||||
output, err = evaluateStream(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut)
|
||||
output, err = evaluateStream(node, vm.ext, vm.tla, vm.nativeFuncs, vm.MaxStack, vm.importCache, vm.traceOut, vm.EvalHook)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -250,20 +255,20 @@ func getAbsPath(path string) (string, error) {
|
||||
return cleanedAbsPath, nil
|
||||
}
|
||||
|
||||
func (vm *VM) findDependencies(filePath string, node *ast.Node, dependencies map[string]struct{}, stackTrace *[]traceFrame) (err error) {
|
||||
func (vm *VM) findDependencies(filePath string, node *ast.Node, dependencies map[string]struct{}, stackTrace *[]TraceFrame) (err error) {
|
||||
var cleanedAbsPath string
|
||||
switch i := (*node).(type) {
|
||||
case *ast.Import:
|
||||
node, foundAt, err := vm.ImportAST(filePath, i.File.Value)
|
||||
if err != nil {
|
||||
*stackTrace = append([]traceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
*stackTrace = append([]TraceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
return err
|
||||
}
|
||||
cleanedAbsPath = foundAt
|
||||
if _, isFileImporter := vm.importer.(*FileImporter); isFileImporter {
|
||||
cleanedAbsPath, err = getAbsPath(foundAt)
|
||||
if err != nil {
|
||||
*stackTrace = append([]traceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
*stackTrace = append([]TraceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -274,20 +279,20 @@ func (vm *VM) findDependencies(filePath string, node *ast.Node, dependencies map
|
||||
dependencies[cleanedAbsPath] = struct{}{}
|
||||
err = vm.findDependencies(foundAt, &node, dependencies, stackTrace)
|
||||
if err != nil {
|
||||
*stackTrace = append([]traceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
*stackTrace = append([]TraceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
return err
|
||||
}
|
||||
case *ast.ImportStr:
|
||||
foundAt, err := vm.ResolveImport(filePath, i.File.Value)
|
||||
if err != nil {
|
||||
*stackTrace = append([]traceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
*stackTrace = append([]TraceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
return err
|
||||
}
|
||||
cleanedAbsPath = foundAt
|
||||
if _, isFileImporter := vm.importer.(*FileImporter); isFileImporter {
|
||||
cleanedAbsPath, err = getAbsPath(foundAt)
|
||||
if err != nil {
|
||||
*stackTrace = append([]traceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
*stackTrace = append([]TraceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -295,14 +300,14 @@ func (vm *VM) findDependencies(filePath string, node *ast.Node, dependencies map
|
||||
case *ast.ImportBin:
|
||||
foundAt, err := vm.ResolveImport(filePath, i.File.Value)
|
||||
if err != nil {
|
||||
*stackTrace = append([]traceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
*stackTrace = append([]TraceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
return err
|
||||
}
|
||||
cleanedAbsPath = foundAt
|
||||
if _, isFileImporter := vm.importer.(*FileImporter); isFileImporter {
|
||||
cleanedAbsPath, err = getAbsPath(foundAt)
|
||||
if err != nil {
|
||||
*stackTrace = append([]traceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
*stackTrace = append([]TraceFrame{{Loc: *i.Loc()}}, *stackTrace...)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -455,7 +460,7 @@ func (vm *VM) EvaluateFileMulti(filename string) (files map[string]string, forma
|
||||
// The `importedPaths` are parsed as if they were imported from a Jsonnet file located at `importedFrom`.
|
||||
func (vm *VM) FindDependencies(importedFrom string, importedPaths []string) ([]string, error) {
|
||||
var nodes []*ast.Node
|
||||
var stackTrace []traceFrame
|
||||
var stackTrace []TraceFrame
|
||||
filePaths := make([]string, len(importedPaths))
|
||||
depsToExclude := make([]string, len(importedPaths))
|
||||
deps := make(map[string]struct{})
|
||||
|
Loading…
Reference in New Issue
Block a user