package cpp.vm; import haxe.Stack; enum DebugToken { IDENT(name:String); CONST(value:Dynamic); DOT; LPAREN; RPAREN; COMMA; EQUALS; LARRAY; RARRAY; } enum DebugExpr { EXPR_VALUE(value:Dynamic); EXPR_FIELD_REF(obj:Dynamic,member:String); EXPR_ARRAY_REF(obj:Dynamic,index:Int); EXPR_STACK_REF(name:String); } class DebugBase { var threadStopped:Bool; var stillDebugging:Bool; var inputThread:Thread; var debugQueue:Deque; var files:Array; var frame:Int; var stack:Array; var vars:Array; public function new() { frame = -1; files = Debugger.getFiles(); stillDebugging = true; threadStopped = false; Debugger.setThread(); Debugger.setHandler(onDebug); debugQueue= new Deque(); inputThread = Thread.create(inputLoop); } function onDebug() { threadStopped = true; onStopped(); while(threadStopped && stillDebugging) { var job = debugQueue.pop(true); job(); } } function onHelp() { } function waitDebugger(inSendResult:Bool=true) { debugQueue.add( function() inputThread.sendMessage("ok") ); var result = Thread.readMessage(true); if (inSendResult) { if (result!="ok") onResult("Debugger out of sync"); else onResult("ok"); } } function onStopped() { } function onRunning() { } function showWhere() { } function showFiles() { } function showBreakpoints() { } function onPrint(result:Dynamic) { } function getNextCommand() : String { return "bye"; } function onResult(inResult:String) { } function addBreakpoint(inFile:String, inLine:String) { var id = Std.parseInt(inFile); if (id==null) { for(idx in 0...files.length) if (files[idx]==inFile) { id = idx; break; } } if (id==null) onResult("Could not find file for " + inFile ); else { Debugger.addBreakpoint(id,Std.parseInt(inLine)); onResult("ok"); } } static var dot:Int = ".".charCodeAt(0); static var quote:Int = "\"".charCodeAt(0); static var comma:Int = ",".charCodeAt(0); static var equals:Int = "=".charCodeAt(0); static var minus:Int = "-".charCodeAt(0); static var a_code:Int = "a".charCodeAt(0); static var z_code:Int = "z".charCodeAt(0); static var A_code:Int = "A".charCodeAt(0); static var Z_code:Int = "Z".charCodeAt(0); static var __code:Int = "_".charCodeAt(0); static var num0_code:Int = "0".charCodeAt(0); static var num9_code:Int = "9".charCodeAt(0); static var space_code:Int = " ".charCodeAt(0); static var lparent:Int = "(".charCodeAt(0); static var rparent:Int = ")".charCodeAt(0); static var larray:Int = "[".charCodeAt(0); static var rarray:Int = "]".charCodeAt(0); function tokenize(inString:String) : Array { var len = inString.length; var result = new Array(); var idx = 0; while(idx=a_code && code<=z_code) || (code>=A_code && code<=Z_code) || code==__code ) { var start = idx++; while(idx=a_code && code<=z_code) || (code>=A_code && code<=Z_code) || code==__code || (code>=num0_code && code=num0_code && code<=num9_code) ) { var start = idx++; while(idx=num0_code && code<=num9_code) ) idx++; else break; } var val = inString.substr(start, idx-start); var num = Std.parseFloat(val); if (!Math.isFinite(num)) throw ("Bad constant '" + val + "'"); result.push( CONST(num) ); } else if (code==quote) { var start = ++idx; while(idx) : DebugExpr { var classPath = ""; var expr:Dynamic = null; var tok = 0; var len = inTokens.length; while(tok < len) { switch(inTokens[tok]) { case IDENT(name): if (expr!=null) throw "Misplaced '" + name + "'"; expr = resolve(name); if (expr==null) classPath = name; tok++; case CONST(value): if (expr!=null || classPath!="") throw "Misplaced '" + value + "'"; expr = EXPR_VALUE(value); tok++; case DOT: if (expr==null && classPath=="") throw "Bad '.' after null value"; tok++; switch(inTokens[tok]) { case IDENT(name): if (expr!=null) expr = EXPR_FIELD_REF(exprToDynamic(expr),name); else { var qname = classPath + "." + name; expr = resolve(qname); classPath = (expr==null) ? qname : ""; } tok++; default: throw "Expected field after '.'"; } case LPAREN: var args = new Array(); var lastComma = tok; var start = ++tok; var parenOpen = 1; var arrayOpen = 0; while(tok) : Dynamic { return exprToDynamic(getExpression(inTokens)); } function print(inString:String) { var tokens:Array = null; try { tokens = tokenize(inString); var result = getValue(tokens); onPrint(result); onResult("ok"); } catch (e:Dynamic) { onResult("Error while printing : " + e);//+ ( tokens==null ? "" : " : " + tokens) ); } } function set(inString:String) { var tokens:Array = null; try { tokens = tokenize(inString); var equals_pos = -1; for(i in 0...tokens.length) { if (tokens[i]==EQUALS) { if (equals_pos>=0) throw "more than one '='"; equals_pos = i; } } if (equals_pos<0) throw "use a = b syntax"; if (equals_pos==0 || equals_pos==tokens.length-1) throw "Misplaced '='"; var lhs = getExpression( tokens.slice(0,equals_pos) ); var rhs = getValue( tokens.slice(equals_pos+1, tokens.length) ); switch(lhs) { case EXPR_VALUE(value): throw "left hand side can't be set"; case EXPR_FIELD_REF(obj,member): Reflect.setProperty(obj,member,rhs); case EXPR_ARRAY_REF(obj,index): obj[index] = rhs; case EXPR_STACK_REF(name): Debugger.setStackVar(frame,name,rhs); } } catch (e:Dynamic) { onResult("Error while setting : " + e);//+ ( tokens==null ? "" : " : " + tokens) ); return; } onResult("ok"); } function setFrame(inFrame:Int) { if (stack!=null && inFrame>0 && inFrame <= stack.length ) { frame = inFrame; vars = Debugger.getStackVars(frame); } } function getStack() { stack = haxe.Stack.callStack(); setFrame(1); } function checkStack() { if (threadStopped && stack==null) { debugQueue.add( getStack ); waitDebugger(false); } } function run() { stack = null; vars = null; debugQueue.add( function() { threadStopped = false; inputThread.sendMessage("running"); } ); var result = Thread.readMessage(true); onRunning(); onResult("ok"); } function inputLoop() { while(stillDebugging) { checkStack(); var command = getNextCommand(); var words = command.split(" "); switch(words[0]) { case "": onResult(""); // Do nothing case "bye": stillDebugging = false; debugQueue.add( function() { trace("bye"); } ); onResult("ok"); case "exit","quit": onResult("ok"); Debugger.exit(); case "break","b": if (words.length==1) { if (threadStopped) onResult("already stopped."); else { Debugger.setBreak(Debugger.BRK_ASAP); waitDebugger(); } } else if (words.length==3) { addBreakpoint(words[1],words[2]); } else onResult("Usage: break [file line] - pause execution of one thread [when at certain point]"); case "cont","c": if (!threadStopped) onResult("Already running."); else run(); case "vars","v": if (!threadStopped || vars==null) onResult("Must break first."); else { onPrint(vars); onResult("ok"); } case "frame","f": if (!threadStopped || stack==null ) onResult("Must break first."); else { var f = Std.parseInt(words[1]); if (f<1 || f>stack.length ) onResult("Stack out of range."); else { debugQueue.add( function() setFrame(f) ); waitDebugger(); } } case "where","w": if (!threadStopped || stack==null) onResult("Must break first."); else { showWhere(); onResult("ok"); } case "print","p": words.shift(); print(words.join(" ")); case "set","s": words.shift(); set(words.join(" ")); case "files","fi": { showFiles(); onResult("ok"); } case "breakpoints","bp": { showBreakpoints(); onResult("ok"); } case "delete","d": if (words[1]==null) { onResult("Usage : delete N"); } else { var i = Std.parseInt(words[1]); Debugger.deleteBreakpoint(i); onResult("ok"); } case "help","h","?": { onHelp(); onResult("ok"); } default: onResult("Unknown command:" + command); } } } }