1
0
Fork 0
fgdata/Nasal/debug.nas
mfranz 824cff9192 comment out isnan()
It conflicts with Jester's debugging setup, and Andy says there should be
no NaN in the first place. This is a bug that needs to get fixed. Just
enable isnan() if you want to detect NaNs in the meantime. (The MP system
creates them ATM. The property getters shall turn them into nil.)
2009-02-12 16:59:03 +00:00

369 lines
12 KiB
Text

# 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 T = p.getAttribute("tied") ? "T" : "";
var attr = r ~ w ~ R ~ W ~ A ~ U ~ 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) {
v = caller(i);
if (v == 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()/2", 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 { var nan = 1 / 0; func(d) num(d) == nil ? nil : d == nan; })();
# --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));
});