Browse Source

fix: Proper restarting of interpreter state when debugger starts

Matthias Ladkau 3 years ago
parent
commit
c452e78b74

+ 0 - 6
cli/ecal.go

@@ -19,12 +19,6 @@ import (
 	"devt.de/krotik/ecal/config"
 )
 
-/*
-TODO:
-- pretty printer
-- reload on start for debugger adapter
-*/
-
 func main() {
 
 	tool.RunPackedBinary() // See if we try to run a standalone binary

+ 20 - 7
cli/tool/interpret.go

@@ -184,6 +184,11 @@ func (i *CLIInterpreter) LoadInitialFile(tid uint64) error {
 			if err = ast.Runtime.Validate(); err == nil {
 				_, err = ast.Runtime.Eval(i.GlobalVS, make(map[string]interface{}), tid)
 			}
+			defer func() {
+				if i.RuntimeProvider.Debugger != nil {
+					i.RuntimeProvider.Debugger.RecordThreadFinished(tid)
+				}
+			}()
 		}
 	}
 
@@ -305,6 +310,7 @@ func (i *CLIInterpreter) HandleInput(ot OutputTerminal, line string, tid uint64)
 		// Reload happens in a separate thread as it may be suspended on start
 
 		go i.LoadInitialFile(i.RuntimeProvider.NewThreadID())
+		ot.WriteString(fmt.Sprintln(fmt.Sprintln("Reloading interpreter state")))
 
 	} else if strings.HasPrefix(line, "@sym") {
 		i.displaySymbols(ot, strings.Split(line, " ")[1:])
@@ -320,18 +326,25 @@ func (i *CLIInterpreter) HandleInput(ot OutputTerminal, line string, tid uint64)
 		var ast *parser.ASTNode
 		var res interface{}
 
-		if ast, ierr = parser.ParseWithRuntime("console input", line, i.RuntimeProvider); ierr == nil {
+		if line != "" {
+			if ast, ierr = parser.ParseWithRuntime("console input", line, i.RuntimeProvider); ierr == nil {
 
-			if ierr = ast.Runtime.Validate(); ierr == nil {
+				if ierr = ast.Runtime.Validate(); ierr == nil {
 
-				if res, ierr = ast.Runtime.Eval(i.GlobalVS, make(map[string]interface{}), tid); ierr == nil && res != nil {
-					ot.WriteString(fmt.Sprintln(stringutil.ConvertToString(res)))
+					if res, ierr = ast.Runtime.Eval(i.GlobalVS, make(map[string]interface{}), tid); ierr == nil && res != nil {
+						ot.WriteString(fmt.Sprintln(stringutil.ConvertToString(res)))
+					}
+					defer func() {
+						if i.RuntimeProvider.Debugger != nil {
+							i.RuntimeProvider.Debugger.RecordThreadFinished(tid)
+						}
+					}()
 				}
 			}
-		}
 
-		if ierr != nil {
-			ot.WriteString(fmt.Sprintln(ierr.Error()))
+			if ierr != nil {
+				ot.WriteString(fmt.Sprintln(ierr.Error()))
+			}
 		}
 	}
 }

BIN
ecal-support/Screenshot_2020-11-16_22-53-34.png


+ 6 - 2
ecal-support/src/ecalDebugAdapter.ts

@@ -1,7 +1,7 @@
 /**
  * Debug Adapter for VS Code to support the ECAL debugger.
  *
- * See the debugger extension guide:
+ * Based on the debugger extension guide:
  * https://code.visualstudio.com/api/extension-guides/debugger-extension
  */
 
@@ -190,7 +190,11 @@ export class ECALDebugSession extends LoggingDebugSession {
 
     this.extout.appendLine(`Configuration loaded: ${JSON.stringify(args)}`);
 
-    await this.client.conect(args.host, args.port);
+    await this.client.connect(args.host, args.port);
+
+    if (args.executeOnEntry) {
+      this.client.reload();
+    }
 
     console.log("##### launchRequest result:", response.body);
 

+ 18 - 2
ecal-support/src/ecalDebugClient.ts

@@ -26,6 +26,7 @@ export class ECALDebugClient extends EventEmitter {
   private connected: boolean = false;
   private backlog: BacklogCommand[] = [];
   private threadInspection: Record<number, ThreadInspection> = {};
+  private doReload: boolean = false;
 
   /**
    * Create a new debug client.
@@ -38,11 +39,10 @@ export class ECALDebugClient extends EventEmitter {
     this.socketLock = new AsyncLock();
   }
 
-  public async conect(host: string, port: number) {
+  public async connect(host: string, port: number) {
     try {
       this.out.log(`Connecting to: ${host}:${port}`);
       await this.socket.connect({ port, host });
-      // this.socket.setTimeout(2000);
       this.connected = true;
       this.pollEvents(); // Start emitting events
     } catch (e) {
@@ -59,6 +59,10 @@ export class ECALDebugClient extends EventEmitter {
     }
   }
 
+  public reload() {
+    this.doReload = true;
+  }
+
   public async describe(tid: number): Promise<ThreadInspection | null> {
     try {
       return (await this.sendCommand("describe", [
@@ -136,6 +140,16 @@ export class ECALDebugClient extends EventEmitter {
           this.emit("pauseOnBreakpoint", { tid, inspection });
         }
       }
+
+      if (this.doReload) {
+        this.doReload = false;
+        this.out.log(`Reloading interpreter state`);
+        try {
+          await this.sendCommandString("@reload\r\n");
+        } catch (e) {
+          this.out.error(`Could not reload the interpreter state: ${e}`);
+        }
+      }
     } catch (e) {
       this.out.error(`Error during event loop: ${e}`);
       nextLoop = 5000;
@@ -180,11 +194,13 @@ export class ECALDebugClient extends EventEmitter {
       await this.socket.write(cmdString, "utf8");
 
       let text = "";
+
       while (!text.endsWith("\n\n")) {
         text += await this.socket.read(1);
       }
 
       let res: any = {};
+
       try {
         res = JSON.parse(text);
       } catch (e) {

+ 1 - 1
examples/fib/debug.sh

@@ -1,2 +1,2 @@
 #!/bin/sh
-../../ecal debug -server -echo fib.ecal
+../../ecal debug -server fib.ecal

+ 0 - 18
examples/game_of_life/.vscode/launch.json

@@ -1,18 +0,0 @@
-{
-    // Use IntelliSense to learn about possible attributes.
-    // Hover to view descriptions of existing attributes.
-    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
-    "version": "0.2.0",
-    "configurations": [
-        {
-            "type": "ecaldebug",
-            "request": "launch",
-            "name": "Debug ECAL script with ECAL Debug Server",
-            "host": "localhost",
-            "port": 33274,
-            "dir": "${workspaceFolder}",
-            "executeOnEntry": true,
-            "trace": false
-        }
-    ]
-}

+ 0 - 2
examples/game_of_life/debug.sh

@@ -1,2 +0,0 @@
-#!/bin/sh
-../../ecal debug -server game_of_life.ecal

+ 15 - 0
interpreter/debug.go

@@ -391,6 +391,21 @@ func (ed *ecalDebugger) RecordSource(source string) {
 	ed.sources[source] = true
 }
 
+/*
+RecordThreadFinished lets the debugger know that a thread has finished.
+*/
+func (ed *ecalDebugger) RecordThreadFinished(tid uint64) {
+	ed.lock.Lock()
+	defer ed.lock.Unlock()
+
+	if is, ok := ed.interrogationStates[tid]; !ok || !is.running {
+		delete(ed.interrogationStates, tid)
+		delete(ed.callStacks, tid)
+		delete(ed.callStackVsSnapshots, tid)
+		delete(ed.callStackGlobalVsSnapshots, tid)
+	}
+}
+
 /*
 SetBreakPoint sets a break point.
 */

+ 8 - 11
interpreter/debug_test.go

@@ -49,6 +49,8 @@ func TestSimpleDebugging(t *testing.T) {
 	wg := &sync.WaitGroup{}
 	wg.Add(1)
 
+	var tid uint64
+
 	go func() {
 		_, err = UnitTestEval(`
 log("test1")
@@ -58,10 +60,13 @@ log("test3")
 		if err != nil {
 			t.Error(err)
 		}
+
+		testDebugger.RecordThreadFinished(tid)
+
 		wg.Done()
 	}()
 
-	tid := waitForThreadSuspension(t)
+	tid = waitForThreadSuspension(t)
 
 	out, err := testDebugger.HandleInput(fmt.Sprintf("status"))
 
@@ -173,11 +178,7 @@ test3`[1:] {
   "sources": [
     "ECALEvalTest"
   ],
-  "threads": {
-    "1": {
-      "callStack": []
-    }
-  }
+  "threads": {}
 }` {
 		t.Error("Unexpected result:", outString, err)
 		return
@@ -204,11 +205,7 @@ test3`[1:] {
   "sources": [
     "ECALEvalTest"
   ],
-  "threads": {
-    "1": {
-      "callStack": []
-    }
-  }
+  "threads": {}
 }` {
 		t.Error("Unexpected result:", outString, err)
 		return

+ 5 - 0
util/types.go

@@ -133,6 +133,11 @@ type ECALDebugger interface {
 	*/
 	VisitStepOutState(node *parser.ASTNode, vs parser.Scope, tid uint64, soErr error) TraceableRuntimeError
 
+	/*
+	   RecordThreadFinished lets the debugger know that a thread has finished.
+	*/
+	RecordThreadFinished(tid uint64)
+
 	/*
 	   SetBreakPoint sets a break point.
 	*/