# debug.nas -- debugging helpers #------------------------------------------------------------------------------ # # debug.dump(<variable>) ... dumps contents of variable to terminal; # abbreviation for print(debug.string(v)) # # debug.local([<frame:int>]) ... dump local variables of current # or given frame # # debug.backtrace([<comment:string>]} ... writes backtrace with local variables # (similar to gdb's "bt full) # # debug.proptrace([<property [, <frames>]]) ... trace property write/add/remove # events under the <property> subtree for # a number of frames. Defaults are "/" and # 2 frames (of which the first one is incomplete). # # debug.tree([<property> [, <mode>]) ... dump property tree under property path # or props.Node hash (default: root). If # <mode> is unset or 0, use flat mode # (similar to props.dump()), otherwise # use space indentation # # debug.bt() ... abbreviation for debug.backtrace() # # debug.string(<variable>) ... returns contents of variable as string # # debug.attributes(<property> [, <verb>]) ... returns attribute string for a given property. # <verb>ose is by default 1, and suppressed the # node's refcounter if 0. # # debug.isnan() returns 1 if argument is an invalid number (NaN), # 0 if it's a valid number, and nil in all other cases # # debug.benchmark(<label:string>, <func> [, <repeat:int>]) # ... runs function <repeat> times (default: 1) # and prints execution time in seconds, # prefixed with <label>. # # debug.printerror(<err-vector>) ... prints error vector as set by call() # # debug.warn(<message>, <level>) ... generate debug message followed by caller stack trace # skipping <level> caller levels (default: 0). # # debug.propify(<variable>) ... turn about everything into a props.Node # # CAVE: this file makes extensive use of ANSI color codes. These are # interpreted by UNIX shells and MS Windows with ANSI.SYS extension # installed. If the color codes aren't interpreted correctly, then # set property /sim/startup/terminal-ansi-colors=0 # # ANSI color code wrappers (see $ man console_codes) # var _title = func(s) globals.string.color("33;42;1", s); # backtrace header var _section = func(s) globals.string.color("37;41;1", s); # backtrace frame var _error = func(s) globals.string.color("31;1", s); # internal errors var _bench = func(s) globals.string.color("37;45;1", s); # benchmark info var _nil = func(s) globals.string.color("32", s); # nil var _string = func(s) globals.string.color("31", s); # "foo" var _num = func(s) globals.string.color("31", s); # 0.0 var _bracket = func(s) globals.string.color("", s); # [ ] var _brace = func(s) globals.string.color("", s); # { } var _angle = func(s) globals.string.color("", s); # < > var _vartype = func(s) globals.string.color("33", s); # func ghost var _proptype = func(s) globals.string.color("34", s); # BOOL INT LONG DOUBLE ... var _path = func(s) globals.string.color("36", s); # /some/property/path var _internal = func(s) globals.string.color("35", s); # me parents var _varname = func(s) s; # variable_name ## # Turn p into props.Node (if it isn't yet), or return nil. # var propify = func(p, create = 0) { var type = typeof(p); if (type == "ghost" and ghosttype(p) == "prop") return props.wrapNode(p); if (type == "scalar" and num(p) == nil) return props.globals.getNode(p, create); if (isa(p, props.Node)) return p; return nil; } var tree = func(n = "", graph = 1) { n = propify(n); if (n == nil) return dump(n); _tree(n, graph); } var _tree = func(n, graph = 1, prefix = "", level = 0) { var path = n.getPath(); var children = n.getChildren(); var s = ""; if (graph) { s = prefix ~ n.getName(); var index = n.getIndex(); if (index) s ~= "[" ~ index ~ "]"; } else { s = n.getPath(); } if (size(children)) { s ~= "/"; if (n.getType() != "NONE") s ~= " = " ~ debug.string(n.getValue()) ~ " " ~ attributes(n) ~ " " ~ _section(" PARENT-VALUE "); } else { s ~= " = " ~ debug.string(n.getValue()) ~ " " ~ attributes(n); } if ((var a = n.getAliasTarget()) != nil) s ~= " " ~ _title(" alias to ") ~ " " ~ a.getPath(); print(s); if (n.getType() != "ALIAS") forindex (var i; children) _tree(children[i], graph, prefix ~ ". ", level + 1); } var attributes = func(p, verbose = 1) { var r = p.getAttribute("readable") ? "" : "r"; var w = p.getAttribute("writable") ? "" : "w"; var R = p.getAttribute("trace-read") ? "R" : ""; var W = p.getAttribute("trace-write") ? "W" : ""; var A = p.getAttribute("archive") ? "A" : ""; var U = p.getAttribute("userarchive") ? "U" : ""; var P = p.getAttribute("preserve") ? "P" : ""; var T = p.getAttribute("tied") ? "T" : ""; var attr = r ~ w ~ R ~ W ~ A ~ U ~ P ~ T; var type = "(" ~ p.getType(); if (size(attr)) type ~= ", " ~ attr; if (var l = p.getAttribute("listeners")) type ~= ", L" ~ l; if (verbose and (var c = p.getAttribute("references")) > 2) type ~= ", #" ~ (c - 2); return _proptype(type ~ ")"); } var _dump_prop = func(p) { _path(p.getPath()) ~ " = " ~ debug.string(p.getValue()) ~ " " ~ attributes(p); } var _dump_var = func(v) { if (v == "me" or v == "parents") return _internal(v); else return _varname(v); } var _dump_string = func(str) { var s = "'"; for (var i = 0; i < size(str); i += 1) { var c = str[i]; if (c == `\``) s ~= "\\`"; elsif (c == `\n`) s ~= "\\n"; elsif (c == `\r`) s ~= "\\r"; elsif (c == `\t`) s ~= "\\t"; elsif (globals.string.isprint(c)) s ~= chr(c); else s ~= sprintf("\\x%02x", c); } return _string(s ~ "'"); } # dump hash keys as variables if they are valid variable names, or as string otherwise var _dump_key = func(s) { if (num(s) != nil) return _num(s); if (!size(s)) return _dump_string(s); if (!globals.string.isalpha(s[0]) and s[0] != `_`) return _dump_string(s); for (var i = 1; i < size(s); i += 1) if (!globals.string.isalnum(s[i]) and s[i] != `_`) return _dump_string(s); _dump_var(s); } var string = func(o) { var t = typeof(o); if (t == "nil") { return _nil("nil"); } elsif (t == "scalar") { return num(o) == nil ? _dump_string(o) : _num(o); } elsif (t == "vector") { var s = ""; forindex (var i; o) s ~= (i == 0 ? "" : ", ") ~ debug.string(o[i]); return _bracket("[") ~ s ~ _bracket("]"); } elsif (t == "hash") { if (contains(o, "parents") and typeof(o.parents) == "vector" and size(o.parents) == 1 and o.parents[0] == props.Node) return _angle("<") ~ _dump_prop(o) ~ _angle(">"); var k = keys(o); var s = ""; forindex (var i; k) s ~= (i == 0 ? "" : ", ") ~ _dump_key(k[i]) ~ ": " ~ debug.string(o[k[i]]); return _brace("{") ~ " " ~ s ~ " " ~ _brace("}"); } elsif (t == "ghost") { return _angle("<") ~ _nil(ghosttype(o)) ~ _angle(">"); } else { return _angle("<") ~ _vartype(t) ~ _angle(">"); } } var dump = func(vars...) { if (!size(vars)) return local(1); if (size(vars) == 1) return print(debug.string(vars[0])); forindex (var i; vars) print(globals.string.color("33;40;1", "#" ~ i) ~ " ", debug.string(vars[i])); } var local = func(frame = 0) { var v = caller(frame + 1); print(v == nil ? _error("<no such frame>") : debug.string(v[0])); return v; } var backtrace = func(desc = nil) { var d = desc == nil ? "" : " '" ~ desc ~ "'"; print("\n" ~ _title("\n### backtrace" ~ d ~ " ###")); for (var i = 1; 1; i += 1) { if ((var v = caller(i)) == nil) return; print(_section(sprintf("#%-2d called from %s, line %s:", i - 1, v[2], v[3]))); dump(v[0]); } } var bt = backtrace; var proptrace = func(root = "/", frames = 2) { var events = 0; var trace = setlistener(propify(root), func(this, base, type) { events += 1; if (type > 0) print(_nil("ADD "), this.getPath()); elsif (type < 0) print(_num("DEL "), this.getPath()); else print("SET ", this.getPath(), " = ", debug.string(this.getValue()), " ", attributes(this)); }, 0, 2); var mark = setlistener("/sim/signals/frame", func { print("-------------------- FRAME --------------------"); if (!frames) { removelistener(trace); removelistener(mark); print("proptrace: stop (", events, " calls)"); } frames -= 1; }); } ## # Executes function fn "repeat" times and prints execution time in seconds. Examples: # # var test = func { getprop("/sim/aircraft"); } # debug.benchmark("test()/1", test, 1000); # debug.benchmark("test()/2", func setprop("/sim/aircraft", ""), 1000); # var benchmark = func(label, fn, repeat = 1) { var start = systime(); for (var i = 0; i < repeat; i += 1) fn(); print(_bench(sprintf(" %s --> %.6f s ", label, systime() - start))); } ## # print error vector as set by call(). By using call() one can execute # code that catches "exceptions" (by a die() call or errors). The Nasal # code doesn't abort in this case. Example: # # var possibly_buggy = func { ... } # call(possibly_buggy, nil, var err = []); # debug.printerror(err); # var printerror = func(err) { if (!size(err)) return; printf("%s:\n at %s, line %d", err[0], err[1], err[2]); for (var i = 3; i < size(err); i += 2) printf(" called from: %s, line %d", err[i], err[i + 1]); } # like die(), but code execution continues. The level argument defines # how many caller() levels to omit. One is automatically omitted, as # this would only point to debug.warn(), where the event in question # didn't happen. # var warn = func(msg, level = 0) { var c = caller(level += 1); if (c == nil) die("debug.warn with invalid level argument"); printf("%s:\n at %s, line %d", msg, c[2], c[3]); while ((c = caller(level += 1)) != nil) printf(" called from: %s, line %d", c[2], c[3]); } var isnan = func { call(math.sin, arg, var err = []); return !!size(err); } # --prop:debug=1 enables debug mode with additional warnings # _setlistener("sim/signals/nasal-dir-initialized", func { if (!getprop("debug")) return; var writewarn = func(f, p, r) { if (!r) { var hint = ""; if ((var n = props.globals.getNode(p)) != nil) { if (!n.getAttribute("writable")) hint = " (write protected)"; elsif (n.getAttribute("tied")) hint = " (tied)"; } warn("Warning: " ~ f ~ " -> writing to " ~ p ~ " failed" ~ hint, 2); } return r; } setprop = (func { var _ = setprop; func writewarn("setprop", globals.string.join("", arg[:-2]), call(_, arg)) })(); props.Node.setDoubleValue = func writewarn("setDoubleValue", me.getPath(), props._setDoubleValue(me._g, arg)); props.Node.setBoolValue = func writewarn("setBoolValue", me.getPath(), props._setBoolValue(me._g, arg)); props.Node.setIntValue = func writewarn("setIntValue", me.getPath(), props._setIntValue(me._g, arg)); props.Node.setValue = func writewarn("setValue", me.getPath(), props._setValue(me._g, arg)); });