Browse Source

Add a debugger sample inspired by https://github.com/slembcke/debugger.lua

mingodad 9 years ago
parent
commit
36caf7331a
3 changed files with 497 additions and 0 deletions
  1. 269 0
      SquiLu/samples/debug-tutorial.nut
  2. 226 0
      SquiLu/samples/debugger.nut
  3. 2 0
      SquiLu/samples/do-debug.nut

+ 269 - 0
SquiLu/samples/debug-tutorial.nut

@@ -0,0 +1,269 @@
+// Load up the debugger module and assign it to a variable.
+local dbg = dofile("debugger.nut");
+print("");
+
+print([==[
+	Welcome to the interactive debugger.lua tutorial.
+	You'll want to open tutorial.nut in an editor to follow along.
+]==]);
+
+print([==[
+	You are now in the debugger! (Woo! \o/).
+	debugger.nut doesn't support traditional breakpoints.
+	Instead you call the dbg() object to invoke the debugger.
+	Real break points would be better, but this
+	keeps debugger.lua simple and very fast.
+	
+	Notice how it prints out your current file and
+	line as well as which function you are in.
+	Keep a close watch on this as you follow along.
+	It should be stopped a line after the dbg() call.
+	(Line 80 probably?)
+	
+	Sometimes functions don't have global names.
+	It might print the method name, local variable
+	that held the function, or file:line where it starts.
+	
+	Type 's' to step to the next line.
+	(s = Step to the next executable line)
+]==]);
+
+// Multi-line strings are executable statements apparently
+// need to put this in an local to make the tutorial flow nicely.
+local str1 = [==[
+	The 's' command steps to the next executable line.
+	This may step you into a function call.
+	
+	In this case, then next line was a C function that printed this message.
+	You can't step into C functions, so it just steps over them.
+	
+	If you hit <return>, the debugger will rerun your last command.
+	Hit <return> 5 times to step into and through func1().
+	Watch the line numbers.
+]==];
+
+local str2 = [==[
+	Stop!
+	You've now stepped through func1()
+	Notice how entering and exiting a function takes a step.
+	
+	Now try the 'n' command.
+	(n = step to the Next line in the source code)
+]==];
+
+local function func1()
+{
+	print("	Stepping through func1()...");
+	print("	Almost there...");
+}
+
+local function func2()
+{
+	print("	You used the 'n' command.");
+	print("	So it's skipping over the lines in func2().");
+	
+	local function f()
+	{
+		print("	... and anything it might call.");
+	}
+	
+	f();
+	
+	print("");
+	print([==[
+	The 'n' command steps to the next line in the source file.
+	Unlike the 's' command, it steps over function calls, and not into them.
+	
+	Now try the 'c' command to continue on to the next breakpoint.
+	(c =  Continue execution)
+]==]);
+}
+
+dbg();
+print(str1);
+
+func1();
+print(str2);
+
+func2();
+
+local function func3()
+{
+	print([==[
+	You are now sitting at a breakpoint inside of a func3().
+	Let's say you got here by stepping into the function.
+	After poking around for a bit, you just want to step until the
+	function returns, but don't want to
+	run the next command over and over.
+	
+	For this you would use the 'f' command. Try it now.
+	(f = Finish current function)
+]==]);
+	
+	dbg();
+	
+	print([==[
+	Now you are inside func4(), right after where it called func3().
+	func4() has some arguments, local variables and upvalues.
+	Let's assume you want to see them.
+	
+	Try the 'l' command to list all the locally available variables.
+	(l = List local variables)
+	
+	Type 'c' to continue on to the next section.
+]==]);
+}
+
+local my_upvalue1 = "Wee an upvalue";
+local my_upvalue2 = "Awww, can't see this one";
+globalvar <- "Weeee a global";
+
+function func4(a, b, ...)
+{
+	local c = "sea";
+	local varargs_copy = clone vargv;
+	
+	// Functions only get upvalues if you reference them.
+	local d = my_upvalue1 + " ... with stuff appended to it";
+	
+	func3();
+	
+	print([==[
+	Some things to notice about the local variables list.
+	'...'
+	  This is the list of varargs passed to the function.
+	  (This only works with Lua 5.2+ or LuaJIT 2.0+)
+	  Note: varargs are not an array, but debugger.lua stores them that way.
+	'my_upvalue1'
+	  This is a local variable defined outside of, but
+	  referenced by the function. Upvalues show up
+	  *only* when you reference them within your
+	  function. 'my_upvalue2' isn't in the list
+	  because func4() doesn't reference it.
+	
+	Listing the locals is nice, but sometimes there are too many to see at once.
+	Often times it's useful to print just a single variable,
+	evaluate an expression, or call a function to see what it returns.
+	For that you use the 'p' command.
+	(p = Print or evaluate an expression)
+	
+	Try these commands:
+	p my_upvalue1
+	p 1 + 1
+	p print("foo")
+	p math.cos(0)
+	
+	You can also interact with varargs. (Except on Lua 5.1)
+	For example:
+	p ...
+	p select(2, ...)
+	p {...}
+	
+	Type 'c' to continue to the next section.
+]==]);
+	dbg();
+}
+
+func4(1, "two", "vararg1", "vararg2", "vararg3");
+
+local function func5()
+{
+	local my_var = "func5()";
+	print([==[
+	You are now in func5() which was called from func6().
+	func6() was called from func7().
+	
+	Try the 't' command to print out a backtrace and see for yourself.
+	(t = backTrace)
+	
+	Type 'c' to continue to the next section
+]==]);
+	dbg();
+	
+	print([==[
+	Notice that func5(), func6() and func7() all have a
+	'my_var' local. You can print the func5()'s my_var easily enough.
+	What if you wanted to see what local variables were in func6()
+	or func7() to see how you got where you were?
+	
+	For that you use the 'u' and 'd' commands.
+	(u = Move up a stack frame)
+	(d = Move down a stack frame)
+	
+	Try the 'u' and 'd' commands a few times.
+	Print out the value of my_var using the 'p' command each time.
+	
+	Type 'c' to continue.
+]==]);
+	dbg();
+}
+
+local function func6()
+{
+	local my_var = "func6()";
+	func5();
+}
+
+local function func7()
+{
+	local my_var = "func7()";
+	func6();
+}
+
+func7();
+
+print([==[
+	That leaves only one more command.
+	Wouldn't it be nice if there was a way to remember
+	all these one letter debugger commands?
+	
+	Type 'h' to show the command list.
+	(h = Help)
+	
+	Type 'c' to continue.
+]==]);
+dbg();
+
+print([==[
+	The following loop uses an assert-style breakpoint.
+	It will only engage when the conditional fails. (when i == 5)
+	
+	Type 'c' to continue.
+]==]);
+
+for(local i=0; i < 10; ++i)
+{
+	print("i = " + i);
+	
+	if(i == 5) dbg();
+}
+
+print([==[
+	Last but not least, is the dbg.call() function.
+	It works sort of like Lua's xpcall() function,
+	but starts the debugger when an uncaught error occurs.
+	Note that dbg.call() does *not* take a list of varargs though.
+	You must call it on a function that takes no arguments.
+	
+	dbg.call(function()
+	{
+		// Potentially buggy code goes here.
+	});
+	
+	Wrap it around your program's main loop or main entry point.
+	Then when your program crashes, you won't need to go back
+	and add breakpoints.
+	
+	That pretty much wraps ups the basics.
+	Hopefully you find debugger.lua to be simple but useful.
+]==]);
+
+dbg.call(function()
+{
+	local foo = "foo";
+	
+	// Try adding a string and integer
+	local bar = foo + 12;
+	
+	// Program never makes it to here...
+});

+ 226 - 0
SquiLu/samples/debugger.nut

@@ -0,0 +1,226 @@
+/*
+With inpiration and ideas from https://github.com/slembcke/debugger.lua
+*/
+
+local globals = getroottable();
+
+local tget = table_get;
+local trget = table_rawget;
+local trset = table_rawset;
+
+local last_cmd;
+local stack_depth = 0;
+local stack_depth_offset = 0;
+local min_stack_depth;
+
+local function table_merge(t1, t2)
+{
+	local tbl = {};
+	foreach( k, v in t1) tbl[k] <- v;
+	foreach( k, v in t2) tbl[k] <- v;
+
+	return tbl;
+}
+
+// Create a table of all the locally accessible variables.
+// Globals are not included when running the locals command, but are when running the print command.
+local function local_bindings(offset, include_globals=false)
+{
+	local level = stack_depth_offset + offset;
+	local stack_info = getstackinfos(level);
+	local func = stack_info.func;
+	local bindings = {};
+		
+	// Retrieve the locals (overwriting any upvalues)
+	foreach(k,v in stack_info.locals)
+	{
+		if(k == "this") continue;
+		bindings[k] <- v;
+	}
+	
+	if(include_globals)
+	{
+		trset(bindings, "_index", globals);
+	}
+	
+	return bindings;
+}
+
+local function pretty(obj, recurse=false)
+{
+	// Returns true if a table has a __tostring metamethod.
+	local function coerceable(tbl)
+	{
+		local meta = table_getdelegate(tbl)
+		return (meta && tget(meta, "_tostring", false));
+	}
+	
+	if( type(obj) == "string")
+	{
+		// Dump the string so that escape sequences are printed.
+		return format("%q", obj);
+	}
+	else if( (type(obj) == "table") && !coerceable(obj) && !recurse )
+	{
+		local str = "{";
+		
+		foreach(k, v in obj)
+		{
+			local pair = pretty(k, true) + " = " + pretty(v, true);
+			str = str + ((str == "{") && (pair || (", " + pair)));
+		}
+		
+		return str + "}";
+	}
+	else
+	{
+		// tostring() can fail if there is an error in a __tostring metamethod.
+		local value;
+		try {value = obj.tostring();} catch(e){print("tostring error", e);}
+		return (value || "<!!error in _tostring metamethod!!>");
+	}
+}
+
+local help_message = [==[
+[return] - re-run last command
+c(ontinue) - contiue execution
+s(tep) - step forward by one line (into functions)
+n(ext) - step forward by one line (skipping over functions)
+p(rint) [expression] - execute the expression and print the result
+f(inish) - step forward until exiting the current function
+u(p) - move up the stack by one frame
+d(own) - move down the stack by one frame
+t(race) - print the stack trace
+l(ocals) [stack frame] - print the function arguments, locals and upvalues.
+q(uit) - terminates the program.
+h(elp) - print this message
+]==];
+
+
+local function myDebugHook(event_type,sourcefile,line,funcname)
+{
+	if(sourcefile == __FILE__) return;
+	
+	if(event_type == 'c')
+	{
+		++stack_depth;
+		return;
+	}
+	
+	if(event_type == 'r')
+	{
+		--stack_depth;
+		return;
+	}
+	
+	if(event_type == 'l')
+	{		
+		if(min_stack_depth != null)
+		{
+			if(stack_depth > min_stack_depth) return;
+		}
+		min_stack_depth = null;
+		
+		local done = false;
+		do
+		{
+			local stack_info = getstackinfos(2);
+			local fname = (stack_info.func || format("<%s:%d>", stack_info.src, stack_info.line));
+			stdout.write(format("%s:%d in '%s' $> ", stack_info.src, stack_info.line, fname));
+			
+			local cmd = stdin.read_line();
+			if( cmd == "")
+			{
+				if(last_cmd)
+				{
+					cmd = last_cmd;
+				}
+			}
+			
+			if(cmd == "c")
+			{
+				setdebughook(null);
+				done = true;
+			}
+			
+			else if(cmd == "s")
+			{
+				done = true;
+			}
+			
+			else if(cmd == "n")
+			{
+				done = true;
+				min_stack_depth = stack_depth;
+			}
+
+			else if(cmd == "f")
+			{
+				done = true;
+				min_stack_depth = stack_depth-1;
+			}
+
+			else if(cmd.len() && cmd[0] == 'l')
+			{
+				local depth = cmd.match("l%s+(%d+)");
+				if(depth)
+				{
+					depth = depth.tointeger() + 2;
+				}
+				else depth = 3;
+				
+				local bindings = local_bindings(depth, false);
+				
+				// Get all the variable binding names and sort them
+				local keys = [];
+				foreach( k, _ in bindings) keys.append(k);
+				keys.sort();
+				
+				foreach(k in keys)
+				{
+					local v = bindings[k];
+					print(format("\t%s => %s", k, pretty(v)));
+				}
+			}
+
+			else if(cmd.len() && cmd[0] == 'p')
+			{
+				auto exp = cmd.match("p%s?(.*)");
+				//print("p=", exp);
+				try
+				{
+					local env = local_bindings(3, true);
+					local chunk = compilestring("return " + exp, "debugger.nut repl");
+					
+					// Call the chunk and collect the results.
+					local result = chunk.pcall(env);
+					print(pretty(result));
+				}
+				catch(e)
+				{
+					print("p cmd error", e);
+				}
+			}
+
+			else if(cmd == "q")
+			{
+				os.exit(0);
+			}
+
+			else if(cmd == "h")
+			{
+				print(help_message);
+			}
+
+			if(last_cmd != cmd) last_cmd = cmd;
+
+		} while (!done);
+	}
+}
+
+local function dbg()
+{
+	setdebughook(myDebugHook);
+}
+
+return dbg;

+ 2 - 0
SquiLu/samples/do-debug.nut

@@ -0,0 +1,2 @@
+enabledebuginfo(true);
+dofile("tutorial.nut");