Browse Source

feat: Adding break on error to debugger

Matthias Ladkau 3 years ago
parent
commit
9cca323af6
8 changed files with 651 additions and 8 deletions
  1. 0 1
      cli/ecal.go
  2. 5 1
      cli/tool/debug.go
  3. 47 3
      interpreter/debug.go
  4. 148 0
      interpreter/debug_test.go
  5. 1 1
      interpreter/rt_identifier.go
  6. 46 0
      util/error.go
  7. 397 0
      util/error_test.go
  8. 7 2
      util/types.go

+ 0 - 1
cli/ecal.go

@@ -22,7 +22,6 @@ import (
 /*
 TODO:
 - create executable binary (pack into single binary)
-- debug support errors
 - pretty printer
 - local variable definition (let)
 - reload on start for debugger adapter

+ 5 - 1
cli/tool/debug.go

@@ -40,13 +40,14 @@ type CLIDebugInterpreter struct {
 	EchoDebugServer *bool   // Echo all input and output of the debug server
 	Interactive     *bool   // Flag if the interpreter should open a console in the current tty.
 	BreakOnStart    *bool   // Flag if the debugger should stop the execution on start
+	BreakOnError    *bool   // Flag if the debugger should stop when encountering an error
 }
 
 /*
 NewCLIDebugInterpreter wraps an existing CLIInterpreter object and adds capabilities.
 */
 func NewCLIDebugInterpreter(i *CLIInterpreter) *CLIDebugInterpreter {
-	return &CLIDebugInterpreter{i, nil, nil, nil, nil, nil}
+	return &CLIDebugInterpreter{i, nil, nil, nil, nil, nil, nil}
 }
 
 /*
@@ -63,6 +64,7 @@ func (i *CLIDebugInterpreter) ParseArgs() bool {
 	i.EchoDebugServer = flag.Bool("echo", false, "Echo all i/o of the debug server")
 	i.Interactive = flag.Bool("interactive", true, "Run interactive console")
 	i.BreakOnStart = flag.Bool("breakonstart", false, "Stop the execution on start")
+	i.BreakOnError = flag.Bool("breakonerror", false, "Stop the execution when encountering an error")
 
 	return i.CLIInterpreter.ParseArgs()
 }
@@ -93,6 +95,7 @@ func (i *CLIDebugInterpreter) Interpret() error {
 
 		i.RuntimeProvider.Debugger = interpreter.NewECALDebugger(i.GlobalVS)
 		i.RuntimeProvider.Debugger.BreakOnStart(*i.BreakOnStart)
+		i.RuntimeProvider.Debugger.BreakOnError(*i.BreakOnError)
 
 		// Set this object as a custom handler to deal with input.
 
@@ -126,6 +129,7 @@ LoadInitialFile clears the global scope and reloads the initial file.
 func (i *CLIDebugInterpreter) LoadInitialFile(tid uint64) error {
 	i.RuntimeProvider.Debugger.StopThreads(500 * time.Millisecond)
 	i.RuntimeProvider.Debugger.BreakOnStart(*i.BreakOnStart)
+	i.RuntimeProvider.Debugger.BreakOnError(*i.BreakOnError)
 	return nil
 }
 

+ 47 - 3
interpreter/debug.go

@@ -38,6 +38,7 @@ type ecalDebugger struct {
 	callStackGlobalVsSnapshots map[uint64][]map[string]interface{} // Call stack global variable scope snapshots of threads
 	sources                    map[string]bool                     // All known sources
 	breakOnStart               bool                                // Flag to stop at the start of the next execution
+	breakOnError               bool                                // Flag to stop if an error occurs
 	globalScope                parser.Scope                        // Global variable scope which can be used to transfer data
 	lock                       *sync.RWMutex                       // Lock for this debugger
 	lastVisit                  int64                               // Last time the debugger had a state visit
@@ -53,6 +54,7 @@ type interrogationState struct {
 	stepOutStack []*parser.ASTNode // Target stack when doing a step out
 	node         *parser.ASTNode   // Node on which the thread was last stopped
 	vs           parser.Scope      // Variable scope of the thread when it was last stopped
+	err          error             // Error which was returned by a function call
 }
 
 /*
@@ -83,6 +85,7 @@ func newInterrogationState(node *parser.ASTNode, vs parser.Scope) *interrogation
 		nil,
 		node,
 		vs,
+		nil,
 	}
 }
 
@@ -98,6 +101,7 @@ func NewECALDebugger(globalVS parser.Scope) util.ECALDebugger {
 		callStackGlobalVsSnapshots: make(map[uint64][]map[string]interface{}),
 		sources:                    make(map[string]bool),
 		breakOnStart:               false,
+		breakOnError:               true,
 		globalScope:                globalVS,
 		lock:                       &sync.RWMutex{},
 		lastVisit:                  0,
@@ -159,7 +163,7 @@ func (ed *ecalDebugger) StopThreads(d time.Duration) bool {
 }
 
 /*
-Break on the start of the next execution.
+BreakOnStart breaks on the start of the next execution.
 */
 func (ed *ecalDebugger) BreakOnStart(flag bool) {
 	ed.lock.Lock()
@@ -167,6 +171,15 @@ func (ed *ecalDebugger) BreakOnStart(flag bool) {
 	ed.breakOnStart = flag
 }
 
+/*
+BreakOnError breaks if an error occurs.
+*/
+func (ed *ecalDebugger) BreakOnError(flag bool) {
+	ed.lock.Lock()
+	defer ed.lock.Unlock()
+	ed.breakOnError = flag
+}
+
 /*
 VisitState is called for every state during the execution of a program.
 */
@@ -304,7 +317,7 @@ func (ed *ecalDebugger) VisitStepInState(node *parser.ASTNode, vs parser.Scope,
 /*
 VisitStepOutState is called after returning from a function call.
 */
-func (ed *ecalDebugger) VisitStepOutState(node *parser.ASTNode, vs parser.Scope, tid uint64) util.TraceableRuntimeError {
+func (ed *ecalDebugger) VisitStepOutState(node *parser.ASTNode, vs parser.Scope, tid uint64, soErr error) util.TraceableRuntimeError {
 	ed.lock.Lock()
 	defer ed.lock.Unlock()
 
@@ -324,7 +337,36 @@ func (ed *ecalDebugger) VisitStepOutState(node *parser.ASTNode, vs parser.Scope,
 
 	is, ok := ed.interrogationStates[tid]
 
-	if ok {
+	if ed.breakOnError && soErr != nil {
+
+		if !ok {
+			is = newInterrogationState(node, vs)
+
+			ed.breakOnStart = false
+			ed.interrogationStates[tid] = is
+
+		} else {
+			is.node = node
+			is.vs = vs
+			is.running = false
+		}
+
+		if is.err == nil {
+
+			// Only stop if the error is being set
+
+			is.err = soErr
+
+			ed.lock.Unlock()
+			is.cond.L.Lock()
+			is.cond.Wait()
+			is.cond.L.Unlock()
+			ed.lock.Lock()
+		}
+
+	} else if ok {
+
+		is.err = soErr
 
 		// The thread is being interrogated
 
@@ -514,6 +556,7 @@ func (ed *ecalDebugger) Status() interface{} {
 
 		if is, ok := ed.interrogationStates[k]; ok {
 			s["threadRunning"] = is.running
+			s["error"] = is.err
 		}
 
 		threadStates[fmt.Sprint(k)] = s
@@ -542,6 +585,7 @@ func (ed *ecalDebugger) Describe(threadId uint64) interface{} {
 
 		res = map[string]interface{}{
 			"threadRunning":             is.running,
+			"error":                     is.err,
 			"callStack":                 ed.prettyPrintCallStack(threadCallStack),
 			"callStackNode":             callStackNode,
 			"callStackVsSnapshot":       ed.callStackVsSnapshots[threadId],

+ 148 - 0
interpreter/debug_test.go

@@ -80,6 +80,7 @@ log("test3")
   "threads": {
     "1": {
       "callStack": [],
+      "error": null,
       "threadRunning": false
     }
   }
@@ -99,6 +100,7 @@ log("test3")
   "callStackVsSnapshot": [],
   "callStackVsSnapshotGlobal": [],
   "code": "log(\"test2\")",
+  "error": null,
   "node": {
     "allowescapes": false,
     "children": [
@@ -261,6 +263,7 @@ log("test3")
   "threads": {
     "1": {
       "callStack": [],
+      "error": null,
       "threadRunning": false
     }
   }
@@ -281,6 +284,130 @@ test2`[1:] {
 	}
 }
 
+func TestErrorStop(t *testing.T) {
+	var err, evalError error
+
+	defer func() {
+		testDebugger = nil
+	}()
+
+	testDebugger = NewECALDebugger(nil)
+	testDebugger.BreakOnError(true)
+
+	if _, err = testDebugger.HandleInput("break ECALEvalTest:8"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+
+	go func() {
+		defer wg.Done()
+
+		_, evalError = UnitTestEval(`
+func err () {
+	raise("foo")
+}
+log("test1")
+log("test2")
+err()
+log("test3")
+`, nil)
+	}()
+
+	waitForThreadSuspension(t)
+
+	out, err := testDebugger.HandleInput(fmt.Sprintf("status"))
+
+	outBytes, _ := json.MarshalIndent(out, "", "  ")
+	outString := string(outBytes)
+
+	if err != nil || outString != `{
+  "breakonstart": false,
+  "breakpoints": {
+    "ECALEvalTest:8": true
+  },
+  "sources": [
+    "ECALEvalTest"
+  ],
+  "threads": {
+    "1": {
+      "callStack": [
+        "err() (ECALEvalTest:7)"
+      ],
+      "error": {
+        "Data": null,
+        "Detail": "",
+        "Environment": {},
+        "Node": {
+          "Name": "identifier",
+          "Token": {
+            "ID": 7,
+            "Pos": 16,
+            "Val": "raise",
+            "Identifier": true,
+            "AllowEscapes": false,
+            "Lsource": "ECALEvalTest",
+            "Lline": 3,
+            "Lpos": 2
+          },
+          "Meta": null,
+          "Children": [
+            {
+              "Name": "funccall",
+              "Token": null,
+              "Meta": null,
+              "Children": [
+                {
+                  "Name": "string",
+                  "Token": {
+                    "ID": 5,
+                    "Pos": 22,
+                    "Val": "foo",
+                    "Identifier": false,
+                    "AllowEscapes": true,
+                    "Lsource": "ECALEvalTest",
+                    "Lline": 3,
+                    "Lpos": 8
+                  },
+                  "Meta": null,
+                  "Children": [],
+                  "Runtime": {}
+                }
+              ],
+              "Runtime": {}
+            }
+          ],
+          "Runtime": {}
+        },
+        "Source": "ECALTestRuntime",
+        "Trace": null,
+        "Type": "foo"
+      },
+      "threadRunning": false
+    }
+  }
+}` {
+		t.Error("Unexpected result:", outString, err)
+		return
+	}
+
+	if _, err = testDebugger.HandleInput(fmt.Sprintf("cont 1 Resume")); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	wg.Wait()
+
+	if evalError == nil || testlogger.String() != `
+test1
+test2`[1:] || evalError.Error() != "ECAL error in ECALTestRuntime: foo () (Line:3 Pos:2)" {
+		t.Error("Unexpected result:", testlogger.String(), err)
+		return
+	}
+}
+
 func TestConcurrentDebugging(t *testing.T) {
 	var err error
 
@@ -359,12 +486,14 @@ log("test4")
       "callStack": [
         "test1() (ECALEvalTest:10)"
       ],
+      "error": null,
       "threadRunning": false
     },
     "2": {
       "callStack": [
         "test2() (ECALEvalTest:10)"
       ],
+      "error": null,
       "threadRunning": false
     }
   }
@@ -381,12 +510,14 @@ log("test4")
       "callStack": [
         "test2() (ECALEvalTest:10)"
       ],
+      "error": null,
       "threadRunning": false
     },
     "2": {
       "callStack": [
         "test1() (ECALEvalTest:10)"
       ],
+      "error": null,
       "threadRunning": false
     }
   }
@@ -543,6 +674,7 @@ log("finish")
   "threads": {
     "1": {
       "callStack": [],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -570,6 +702,7 @@ log("finish")
         "fa(1) (ECALEvalTest:21)",
         "fb(x) (ECALEvalTest:6)"
       ],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -601,6 +734,7 @@ log("finish")
         "fa(1) (ECALEvalTest:21)",
         "fb(x) (ECALEvalTest:6)"
       ],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -633,6 +767,7 @@ log("finish")
         "fa(1) (ECALEvalTest:21)",
         "fb(x) (ECALEvalTest:6)"
       ],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -666,6 +801,7 @@ log("finish")
         "fb(x) (ECALEvalTest:6)",
         "fc() (ECALEvalTest:12)"
       ],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -695,6 +831,7 @@ log("finish")
         "fa(1) (ECALEvalTest:21)",
         "fb(x) (ECALEvalTest:6)"
       ],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -728,6 +865,7 @@ log("finish")
         "fb(x) (ECALEvalTest:6)",
         "fc() (ECALEvalTest:13)"
       ],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -754,6 +892,7 @@ log("finish")
         "fa(1) (ECALEvalTest:21)",
         "fb(x) (ECALEvalTest:6)"
       ],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -784,6 +923,7 @@ log("finish")
       "callStack": [
         "fa(1) (ECALEvalTest:21)"
       ],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -819,6 +959,7 @@ log("finish")
   "threads": {
     "1": {
       "callStack": [],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -851,6 +992,7 @@ log("finish")
   "threads": {
     "1": {
       "callStack": [],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -881,6 +1023,7 @@ log("finish")
   "threads": {
     "1": {
       "callStack": [],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -983,6 +1126,7 @@ log("finish: ", a)
   "threads": {
     "1": {
       "callStack": [],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -1016,6 +1160,7 @@ log("finish: ", a)
       "callStack": [
         "myfunc(a) (ECALEvalTest:5)"
       ],
+      "error": null,
       "threadRunning": false
     }
   },
@@ -1127,6 +1272,7 @@ log("test3 b=", b)
       "callStack": [
         "myfunc() (ECALEvalTest:8)"
       ],
+      "error": null,
       "threadRunning": false
     }
   }
@@ -1175,6 +1321,7 @@ log("test3 b=", b)
     }
   ],
   "code": "log(\"test2 a=\", a)",
+  "error": null,
   "node": {
     "allowescapes": false,
     "children": [
@@ -1394,6 +1541,7 @@ log("test3")
   "threads": {
     "1": {
       "callStack": [],
+      "error": null,
       "threadRunning": false
     }
   }

+ 1 - 1
interpreter/rt_identifier.go

@@ -205,7 +205,7 @@ func (rt *identifierRuntime) resolveFunction(astring string, vs parser.Scope, is
 						result, err = funcObj.Run(rt.instanceID, vs, is, tid, args)
 
 						if rt.erp.Debugger != nil {
-							rt.erp.Debugger.VisitStepOutState(node, vs, tid)
+							rt.erp.Debugger.VisitStepOutState(node, vs, tid, err)
 						}
 
 						_, ok1 := err.(*util.RuntimeError)

+ 46 - 0
util/error.go

@@ -14,6 +14,7 @@ Package util contains utility definitions and functions for the event condition
 package util
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
 
@@ -132,6 +133,30 @@ func (re *RuntimeError) GetTraceString() []string {
 	return res
 }
 
+/*
+ToJSONObject returns this RuntimeError and all its children as a JSON object.
+*/
+func (re *RuntimeError) ToJSONObject() map[string]interface{} {
+	t := ""
+	if re.Type != nil {
+		t = re.Type.Error()
+	}
+	return map[string]interface{}{
+		"Source": re.Source,
+		"Type":   t,
+		"Detail": re.Detail,
+		"Node":   re.Node,
+		"Trace":  re.Trace,
+	}
+}
+
+/*
+MarshalJSON serializes this RuntimeError into a JSON string.
+*/
+func (re *RuntimeError) MarshalJSON() ([]byte, error) {
+	return json.Marshal(re.ToJSONObject())
+}
+
 /*
 RuntimeErrorWithDetail is a runtime error with additional environment information.
 */
@@ -140,3 +165,24 @@ type RuntimeErrorWithDetail struct {
 	Environment parser.Scope
 	Data        interface{}
 }
+
+/*
+ToJSONObject returns this RuntimeErrorWithDetail and all its children as a JSON object.
+*/
+func (re *RuntimeErrorWithDetail) ToJSONObject() map[string]interface{} {
+	res := re.RuntimeError.ToJSONObject()
+	e := map[string]interface{}{}
+	if re.Environment != nil {
+		e = re.Environment.ToJSONObject()
+	}
+	res["Environment"] = e
+	res["Data"] = re.Data
+	return res
+}
+
+/*
+MarshalJSON serializes this RuntimeErrorWithDetail into a JSON string.
+*/
+func (re *RuntimeErrorWithDetail) MarshalJSON() ([]byte, error) {
+	return json.Marshal(re.ToJSONObject())
+}

+ 397 - 0
util/error_test.go

@@ -11,11 +11,14 @@
 package util
 
 import (
+	"encoding/json"
 	"fmt"
 	"strings"
+	"sync"
 	"testing"
 
 	"devt.de/krotik/ecal/parser"
+	"devt.de/krotik/ecal/scope"
 )
 
 func TestRuntimeError(t *testing.T) {
@@ -56,4 +59,398 @@ raise(c) (bar2:1)
 		t.Error("Unexpected result:", trace)
 		return
 	}
+
+	err4 := &RuntimeErrorWithDetail{err3.(*RuntimeError), nil, nil}
+
+	res, _ := json.MarshalIndent(err4.RuntimeError, "", "  ")
+	if string(res) != `{
+  "Detail": "bar",
+  "Node": {
+    "Name": ":=",
+    "Token": {
+      "ID": 39,
+      "Pos": 1,
+      "Val": ":=",
+      "Identifier": false,
+      "AllowEscapes": false,
+      "Lsource": "foo",
+      "Lline": 1,
+      "Lpos": 2
+    },
+    "Meta": null,
+    "Children": [
+      {
+        "Name": "identifier",
+        "Token": {
+          "ID": 7,
+          "Pos": 0,
+          "Val": "a",
+          "Identifier": true,
+          "AllowEscapes": false,
+          "Lsource": "foo",
+          "Lline": 1,
+          "Lpos": 1
+        },
+        "Meta": null,
+        "Children": [],
+        "Runtime": null
+      },
+      {
+        "Name": "number",
+        "Token": {
+          "ID": 6,
+          "Pos": 3,
+          "Val": "1",
+          "Identifier": false,
+          "AllowEscapes": false,
+          "Lsource": "foo",
+          "Lline": 1,
+          "Lpos": 4
+        },
+        "Meta": null,
+        "Children": [],
+        "Runtime": null
+      }
+    ],
+    "Runtime": null
+  },
+  "Source": "foo",
+  "Trace": [
+    {
+      "Name": "identifier",
+      "Token": {
+        "ID": 7,
+        "Pos": 0,
+        "Val": "print",
+        "Identifier": true,
+        "AllowEscapes": false,
+        "Lsource": "bar1",
+        "Lline": 1,
+        "Lpos": 1
+      },
+      "Meta": null,
+      "Children": [
+        {
+          "Name": "funccall",
+          "Token": null,
+          "Meta": null,
+          "Children": [
+            {
+              "Name": "identifier",
+              "Token": {
+                "ID": 7,
+                "Pos": 6,
+                "Val": "b",
+                "Identifier": true,
+                "AllowEscapes": false,
+                "Lsource": "bar1",
+                "Lline": 1,
+                "Lpos": 7
+              },
+              "Meta": null,
+              "Children": [],
+              "Runtime": null
+            }
+          ],
+          "Runtime": null
+        }
+      ],
+      "Runtime": null
+    },
+    {
+      "Name": "identifier",
+      "Token": {
+        "ID": 7,
+        "Pos": 0,
+        "Val": "raise",
+        "Identifier": true,
+        "AllowEscapes": false,
+        "Lsource": "bar2",
+        "Lline": 1,
+        "Lpos": 1
+      },
+      "Meta": null,
+      "Children": [
+        {
+          "Name": "funccall",
+          "Token": null,
+          "Meta": null,
+          "Children": [
+            {
+              "Name": "identifier",
+              "Token": {
+                "ID": 7,
+                "Pos": 6,
+                "Val": "c",
+                "Identifier": true,
+                "AllowEscapes": false,
+                "Lsource": "bar2",
+                "Lline": 1,
+                "Lpos": 7
+              },
+              "Meta": null,
+              "Children": [],
+              "Runtime": null
+            }
+          ],
+          "Runtime": null
+        }
+      ],
+      "Runtime": null
+    },
+    {
+      "Name": "plus",
+      "Token": {
+        "ID": 33,
+        "Pos": 2,
+        "Val": "+",
+        "Identifier": false,
+        "AllowEscapes": false,
+        "Lsource": "bar3",
+        "Lline": 1,
+        "Lpos": 3
+      },
+      "Meta": null,
+      "Children": [
+        {
+          "Name": "number",
+          "Token": {
+            "ID": 6,
+            "Pos": 0,
+            "Val": "1",
+            "Identifier": false,
+            "AllowEscapes": false,
+            "Lsource": "bar3",
+            "Lline": 1,
+            "Lpos": 1
+          },
+          "Meta": null,
+          "Children": [],
+          "Runtime": null
+        },
+        {
+          "Name": "identifier",
+          "Token": {
+            "ID": 7,
+            "Pos": 4,
+            "Val": "d",
+            "Identifier": true,
+            "AllowEscapes": false,
+            "Lsource": "bar3",
+            "Lline": 1,
+            "Lpos": 5
+          },
+          "Meta": null,
+          "Children": [],
+          "Runtime": null
+        }
+      ],
+      "Runtime": null
+    }
+  ],
+  "Type": "foo"
+}` {
+		t.Error("Unexpected result:", string(res))
+		return
+	}
+
+	s := scope.NewScope("aa")
+	s.SetValue("xx", 123)
+	err4 = &RuntimeErrorWithDetail{err3.(*RuntimeError), s, sync.Mutex{}}
+
+	res, _ = json.MarshalIndent(err4, "", "  ")
+	if string(res) != `{
+  "Data": {},
+  "Detail": "bar",
+  "Environment": {
+    "xx": 123
+  },
+  "Node": {
+    "Name": ":=",
+    "Token": {
+      "ID": 39,
+      "Pos": 1,
+      "Val": ":=",
+      "Identifier": false,
+      "AllowEscapes": false,
+      "Lsource": "foo",
+      "Lline": 1,
+      "Lpos": 2
+    },
+    "Meta": null,
+    "Children": [
+      {
+        "Name": "identifier",
+        "Token": {
+          "ID": 7,
+          "Pos": 0,
+          "Val": "a",
+          "Identifier": true,
+          "AllowEscapes": false,
+          "Lsource": "foo",
+          "Lline": 1,
+          "Lpos": 1
+        },
+        "Meta": null,
+        "Children": [],
+        "Runtime": null
+      },
+      {
+        "Name": "number",
+        "Token": {
+          "ID": 6,
+          "Pos": 3,
+          "Val": "1",
+          "Identifier": false,
+          "AllowEscapes": false,
+          "Lsource": "foo",
+          "Lline": 1,
+          "Lpos": 4
+        },
+        "Meta": null,
+        "Children": [],
+        "Runtime": null
+      }
+    ],
+    "Runtime": null
+  },
+  "Source": "foo",
+  "Trace": [
+    {
+      "Name": "identifier",
+      "Token": {
+        "ID": 7,
+        "Pos": 0,
+        "Val": "print",
+        "Identifier": true,
+        "AllowEscapes": false,
+        "Lsource": "bar1",
+        "Lline": 1,
+        "Lpos": 1
+      },
+      "Meta": null,
+      "Children": [
+        {
+          "Name": "funccall",
+          "Token": null,
+          "Meta": null,
+          "Children": [
+            {
+              "Name": "identifier",
+              "Token": {
+                "ID": 7,
+                "Pos": 6,
+                "Val": "b",
+                "Identifier": true,
+                "AllowEscapes": false,
+                "Lsource": "bar1",
+                "Lline": 1,
+                "Lpos": 7
+              },
+              "Meta": null,
+              "Children": [],
+              "Runtime": null
+            }
+          ],
+          "Runtime": null
+        }
+      ],
+      "Runtime": null
+    },
+    {
+      "Name": "identifier",
+      "Token": {
+        "ID": 7,
+        "Pos": 0,
+        "Val": "raise",
+        "Identifier": true,
+        "AllowEscapes": false,
+        "Lsource": "bar2",
+        "Lline": 1,
+        "Lpos": 1
+      },
+      "Meta": null,
+      "Children": [
+        {
+          "Name": "funccall",
+          "Token": null,
+          "Meta": null,
+          "Children": [
+            {
+              "Name": "identifier",
+              "Token": {
+                "ID": 7,
+                "Pos": 6,
+                "Val": "c",
+                "Identifier": true,
+                "AllowEscapes": false,
+                "Lsource": "bar2",
+                "Lline": 1,
+                "Lpos": 7
+              },
+              "Meta": null,
+              "Children": [],
+              "Runtime": null
+            }
+          ],
+          "Runtime": null
+        }
+      ],
+      "Runtime": null
+    },
+    {
+      "Name": "plus",
+      "Token": {
+        "ID": 33,
+        "Pos": 2,
+        "Val": "+",
+        "Identifier": false,
+        "AllowEscapes": false,
+        "Lsource": "bar3",
+        "Lline": 1,
+        "Lpos": 3
+      },
+      "Meta": null,
+      "Children": [
+        {
+          "Name": "number",
+          "Token": {
+            "ID": 6,
+            "Pos": 0,
+            "Val": "1",
+            "Identifier": false,
+            "AllowEscapes": false,
+            "Lsource": "bar3",
+            "Lline": 1,
+            "Lpos": 1
+          },
+          "Meta": null,
+          "Children": [],
+          "Runtime": null
+        },
+        {
+          "Name": "identifier",
+          "Token": {
+            "ID": 7,
+            "Pos": 4,
+            "Val": "d",
+            "Identifier": true,
+            "AllowEscapes": false,
+            "Lsource": "bar3",
+            "Lline": 1,
+            "Lpos": 5
+          },
+          "Meta": null,
+          "Children": [],
+          "Runtime": null
+        }
+      ],
+      "Runtime": null
+    }
+  ],
+  "Type": "foo"
+}` {
+		t.Error("Unexpected result:", string(res))
+		return
+	}
 }

+ 7 - 2
util/types.go

@@ -109,10 +109,15 @@ type ECALDebugger interface {
 	StopThreads(d time.Duration) bool
 
 	/*
-	   Break on the start of the next execution.
+	   BreakOnStart breaks on the start of the next execution.
 	*/
 	BreakOnStart(flag bool)
 
+	/*
+	   BreakOnError breaks if an error occurs.
+	*/
+	BreakOnError(flag bool)
+
 	/*
 	   VisitState is called for every state during the execution of a program.
 	*/
@@ -126,7 +131,7 @@ type ECALDebugger interface {
 	/*
 	   VisitStepOutState is called after returning from a function call.
 	*/
-	VisitStepOutState(node *parser.ASTNode, vs parser.Scope, tid uint64) TraceableRuntimeError
+	VisitStepOutState(node *parser.ASTNode, vs parser.Scope, tid uint64, soErr error) TraceableRuntimeError
 
 	/*
 	   SetBreakPoint sets a break point.