1/*
2 * gomacro - A Go interpreter with Lisp-like macros
3 *
4 * Copyright (C) 2018-2019 Massimiliano Ghilardi
5 *
6 *     This Source Code Form is subject to the terms of the Mozilla Public
7 *     License, v. 2.0. If a copy of the MPL was not distributed with this
8 *     file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 *
11 * debug.go
12 *
13 *  Created on Apr 20, 2018
14 *      Author Massimiliano Ghilardi
15 */
16
17package fast
18
19import (
20	"go/ast"
21	"go/token"
22
23	. "github.com/cosmos72/gomacro/base"
24)
25
26type stubDebugger struct{}
27
28func (s stubDebugger) Breakpoint(ir *Interp, env *Env) DebugOp {
29	return DebugOpContinue
30}
31
32func (s stubDebugger) At(ir *Interp, env *Env) DebugOp {
33	return DebugOpContinue
34}
35
36// return true if statement is either "break" or _ = "break"
37func isBreakpoint(stmt ast.Stmt) bool {
38	switch node := stmt.(type) {
39	case *ast.ExprStmt:
40		return isBreakLiteral(node.X)
41	case *ast.AssignStmt:
42		if node.Tok == token.ASSIGN && len(node.Lhs) == 1 && len(node.Rhs) == 1 {
43			return isUnderscore(node.Lhs[0]) && isBreakLiteral(node.Rhs[0])
44		}
45	}
46	return false
47}
48
49func isUnderscore(node ast.Expr) bool {
50	switch node := node.(type) {
51	case *ast.Ident:
52		return node.Name == "_"
53	}
54	return false
55}
56
57func isBreakLiteral(node ast.Expr) bool {
58	switch node := node.(type) {
59	case *ast.BasicLit:
60		return node.Kind == token.STRING && node.Value == `"break"`
61	}
62	return false
63}
64
65func (c *Comp) breakpoint() Stmt {
66	return func(env *Env) (Stmt, *Env) {
67		ir := Interp{c, env}
68		sig := ir.debug(true)
69		env.IP++
70		stmt := env.Code[env.IP]
71		if sig != SigNone {
72			run := env.Run
73			stmt = run.Interrupt
74			if run.Options&OptDebugDebugger != 0 {
75				run.Debugf("after breakpoint: single-stepping with stmt = %p, env = %p, IP = %v, execFlags = %v, signals = %#v", stmt, env, env.IP, run.ExecFlags, run.Signals)
76			}
77		}
78		return stmt, env
79	}
80}
81
82func singleStep(env *Env) (Stmt, *Env) {
83	stmt := env.Code[env.IP]
84	run := env.Run
85	if run.Signals.Debug == SigNone {
86		return stmt, env // resume normal execution
87	}
88
89	if env.CallDepth < run.DebugDepth {
90		if run.Options&OptDebugDebugger != 0 {
91			run.Debugf("single-stepping: stmt = %p, env = %p, IP = %v, env.CallDepth = %d, g.DebugDepth = %d", stmt, env, env.IP, env.CallDepth, run.DebugDepth)
92		}
93		c := env.DebugComp
94		if c != nil {
95			ir := Interp{c, env}
96			sig := ir.debug(false) // not a breakpoint
97			if sig != SigNone {
98				run := env.Run
99				run.Signals.Debug = sig
100			}
101		}
102	}
103
104	// single step
105	stmt, env = stmt(env)
106	if run.Signals.Debug != SigNone {
107		stmt = run.Interrupt
108	}
109	return stmt, env
110}
111
112func (ir *Interp) debug(breakpoint bool) Signal {
113	run := ir.env.Run
114	if run.Debugger == nil {
115		ir.Comp.Warnf("// breakpoint: no debugger set with Interp.SetDebugger(), resuming execution (warned only once)")
116		run.Debugger = stubDebugger{}
117	}
118	var op DebugOp
119	if breakpoint {
120		op = run.Debugger.Breakpoint(ir, ir.env)
121	} else {
122		op = run.Debugger.At(ir, ir.env)
123	}
124	if run.Options&OptDebugDebugger != 0 {
125		run.Debugf("Debugger returned op = %v", op)
126	}
127	return run.applyDebugOp(op)
128}
129
130func (run *Run) applyDebugOp(op DebugOp) Signal {
131	if op.Panic != nil {
132		if run.Options&OptDebugDebugger != 0 {
133			run.Debugf("applyDebugOp: op = %v, signaling panic(%v)", op, *op.Panic)
134		}
135		panic(*op.Panic)
136	}
137	saveOp := op
138	var sig Signal
139	if op.Depth > 0 {
140		sig = SigDebug
141	} else {
142		sig = SigNone
143		op.Depth = 0
144	}
145	if run.Options&OptDebugDebugger != 0 {
146		if op == saveOp {
147			run.Debugf("applyDebugOp: op = %v, updated run.DebugDepth from %v to %v", op, run.DebugDepth, op.Depth)
148		} else {
149			run.Debugf("applyDebugOp: op = %v, replaced with %v and updated run.DebugDepth from %v to %v", saveOp, op, run.DebugDepth, op.Depth)
150		}
151	}
152	run.DebugDepth = op.Depth
153	run.ExecFlags.SetDebug(sig != SigNone)
154	run.Signals.Debug = sig
155	return sig
156}
157