debugger.nut 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /*
  2. With inpiration and ideas from https://github.com/slembcke/debugger.lua
  3. */
  4. local globals = getroottable();
  5. local tget = table_get;
  6. local trget = table_rawget;
  7. local trset = table_rawset;
  8. local last_cmd;
  9. local stack_depth = 0;
  10. local stack_depth_offset = 0;
  11. local min_stack_depth;
  12. local function table_merge(t1, t2)
  13. {
  14. local tbl = {};
  15. foreach( k, v in t1) tbl[k] <- v;
  16. foreach( k, v in t2) tbl[k] <- v;
  17. return tbl;
  18. }
  19. // Create a table of all the locally accessible variables.
  20. // Globals are not included when running the locals command, but are when running the print command.
  21. local function local_bindings(offset, include_globals=false)
  22. {
  23. local level = stack_depth_offset + offset;
  24. local stack_info = getstackinfos(level);
  25. local func = stack_info.func;
  26. local bindings = {};
  27. // Retrieve the locals (overwriting any upvalues)
  28. foreach(k,v in stack_info.locals)
  29. {
  30. if(k == "this") continue;
  31. bindings[k] <- v;
  32. }
  33. if(include_globals)
  34. {
  35. trset(bindings, "_index", globals);
  36. }
  37. return bindings;
  38. }
  39. local function pretty(obj, recurse=false)
  40. {
  41. // Returns true if a table has a __tostring metamethod.
  42. local function coerceable(tbl)
  43. {
  44. local meta = table_getdelegate(tbl)
  45. return (meta && tget(meta, "_tostring", false));
  46. }
  47. if( type(obj) == "string")
  48. {
  49. // Dump the string so that escape sequences are printed.
  50. return format("%q", obj);
  51. }
  52. else if( (type(obj) == "table") && !coerceable(obj) && !recurse )
  53. {
  54. local str = "{";
  55. foreach(k, v in obj)
  56. {
  57. local pair = pretty(k, true) + " = " + pretty(v, true);
  58. str = str + ((str == "{") && (pair || (", " + pair)));
  59. }
  60. return str + "}";
  61. }
  62. else
  63. {
  64. // tostring() can fail if there is an error in a __tostring metamethod.
  65. local value;
  66. try {value = obj.tostring();} catch(e){print("tostring error", e);}
  67. return (value || "<!!error in _tostring metamethod!!>");
  68. }
  69. }
  70. local help_message = [==[
  71. [return] - re-run last command
  72. c(ontinue) - contiue execution
  73. s(tep) - step forward by one line (into functions)
  74. n(ext) - step forward by one line (skipping over functions)
  75. p(rint) [expression] - execute the expression and print the result
  76. f(inish) - step forward until exiting the current function
  77. u(p) - move up the stack by one frame
  78. d(own) - move down the stack by one frame
  79. t(race) - print the stack trace
  80. l(ocals) [stack frame] - print the function arguments, locals and upvalues.
  81. q(uit) - terminates the program.
  82. h(elp) - print this message
  83. ]==];
  84. local function myDebugHook(event_type,sourcefile,line,funcname)
  85. {
  86. if(sourcefile == __FILE__) return;
  87. if(event_type == 'c')
  88. {
  89. ++stack_depth;
  90. return;
  91. }
  92. if(event_type == 'r')
  93. {
  94. --stack_depth;
  95. return;
  96. }
  97. if(event_type == 'l')
  98. {
  99. if(min_stack_depth != null)
  100. {
  101. if(stack_depth > min_stack_depth) return;
  102. }
  103. min_stack_depth = null;
  104. local done = false;
  105. do
  106. {
  107. local stack_info = getstackinfos(2);
  108. local fname = (stack_info.func || format("<%s:%d>", stack_info.src, stack_info.line));
  109. stdout.write(format("%s:%d in '%s' $> ", stack_info.src, stack_info.line, fname));
  110. local cmd = stdin.read_line();
  111. if( cmd == "")
  112. {
  113. if(last_cmd)
  114. {
  115. cmd = last_cmd;
  116. }
  117. }
  118. if(cmd == "c")
  119. {
  120. setdebughook(null);
  121. done = true;
  122. }
  123. else if(cmd == "s")
  124. {
  125. done = true;
  126. }
  127. else if(cmd == "n")
  128. {
  129. done = true;
  130. min_stack_depth = stack_depth;
  131. }
  132. else if(cmd == "f")
  133. {
  134. done = true;
  135. min_stack_depth = stack_depth-1;
  136. }
  137. else if(cmd.len() && cmd[0] == 'l')
  138. {
  139. local depth = cmd.match("l%s+(%d+)");
  140. if(depth)
  141. {
  142. depth = depth.tointeger() + 2;
  143. }
  144. else depth = 3;
  145. local bindings = local_bindings(depth, false);
  146. // Get all the variable binding names and sort them
  147. local keys = [];
  148. foreach( k, _ in bindings) keys.append(k);
  149. keys.sort();
  150. foreach(k in keys)
  151. {
  152. local v = bindings[k];
  153. print(format("\t%s => %s", k, pretty(v)));
  154. }
  155. }
  156. else if(cmd.len() && cmd[0] == 'p')
  157. {
  158. auto exp = cmd.match("p%s?(.*)");
  159. //print("p=", exp);
  160. try
  161. {
  162. local env = local_bindings(3, true);
  163. local chunk = compilestring("return " + exp, "debugger.nut repl");
  164. // Call the chunk and collect the results.
  165. local result = chunk.pcall(env);
  166. print(pretty(result));
  167. }
  168. catch(e)
  169. {
  170. print("p cmd error", e);
  171. }
  172. }
  173. else if(cmd == "q")
  174. {
  175. os.exit(0);
  176. }
  177. else if(cmd == "h")
  178. {
  179. print(help_message);
  180. }
  181. if(last_cmd != cmd) last_cmd = cmd;
  182. } while (!done);
  183. }
  184. }
  185. local function dbg()
  186. {
  187. setdebughook(myDebugHook);
  188. }
  189. return dbg;