if (typeof(visualize) == "undefined") throw new Error("stackDump() requires visualize()");
function stackDump(iLevel, bArguments) {
	var asStack = [];
	var iCalls = 0;
	// Walk the stack:
	for (var oCaller = arguments.callee.caller; oCaller != null; oCaller = oCaller.caller) {
		// Skip the first X calls on the stack
		if (typeof(iLevel) == "number" && iLevel-- > 0) continue;
		iCalls++;
		var bLastCall = !(oCaller.caller);
		// Convert caller function to a string and cut the function
		// body. For simplicity, I'm not taking into account inline
		// comments in the function declaration, otherwise the regular
		// expressions would become quite complex.
		sCaller = (oCaller+"").replace(/function\s*|\s*{[\s\S]*/g, "");
		// sCaller is now something like "stackDump()", let's seperate
		// the function name from the argument list:
		var asCaller = sCaller.match(/(\w*\s*)\(([^\)]*)\)/);
		// If this failed, we'll use what we have so far and not try to be
		// smart about the arguments:
		var sCallHeadHeader = " " + (bLastCall ? "\u255A":"\u2560") + "\u2550 ";
		var sCallBodyHeader = " " + (bLastCall ? " " : "\u2551") + "  ";
		if (asCaller == null) {
			asStack.push(sCallHeadHeader + visualize(oCaller, 1, false));
			if (bArguments) asStack.push(sCallBodyHeader + " \u2559\u2501 Argument info unavailable.");
		} else {
			// We've split the function name from the arguments:
			var sName = asCaller[1];
			// Now let's split the individual arguments, if any:
			var asArgumentNames = asCaller[2].match(/^\s*$/) ?
				[]: asCaller[2].split(",");
			// Add the function name + argument names to the call stack:
			asStack.push(sCallHeadHeader + "function " + sName + " (" + asArgumentNames.join(", ") + ")");
			// Next we'll walk the argument names and the argument values
			// to process them and see if there are any arguments in the
			// function definition that were not supplied in the call or
			// extra arguments supplied in the call that are not in the
			// definition:
			if (bArguments) {
				for (var i = 0; i < asArgumentNames.length || i < oCaller.arguments.length; i++) {
					var sArgumentName = "arguments[" + i + "]: ";
					if (i < asArgumentNames.length) {
						// This argument exists in the function definition, use
						// the name rather than the number:
						asArgumentNames[i] = asArgumentNames[i].replace(/^\s*|\s*$/g, "");
						sArgumentName = asArgumentNames[i] + ": ";
					}
					// We'll display each named argument from the definition,
					// even if it is not defined in the call. We'll also
					// display any arguments defined in the call that are not
					// in the function definition:
					var bLastArgument = (i + 1 >= asArgumentNames.length && i + 1 >= oCaller.arguments.length);
					var sArgumentHeadHeader = " " + (bLastArgument ? "\u2514":"\u251C") + "\u2500 ";
					var sArgumentBodyHeader = " " + (bLastArgument ? " " : "\u2502") + "  ";
					while (sArgumentBodyHeader.length < sArgumentName.length + 4) sArgumentBodyHeader += " ";
					asStack.push(
						sCallBodyHeader +
						sArgumentHeadHeader +
						sArgumentName + 
						visualize(oCaller.arguments[i], sCallBodyHeader + sArgumentBodyHeader)
					);
				}
			}
		}
	}
	if (iCalls == 0) return "Call stack info unavailable.";
	// Combine all information found and return it:
	return "Call stack (" + iCalls + " calls):\r\n" + asStack.join("\r\n");
}
