2007-01-28 12:20:18 +00:00
|
|
|
# debug.nas -- debugging helpers
|
2007-05-09 17:31:45 +00:00
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
#
|
2007-02-07 17:06:05 +00:00
|
|
|
# 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
|
|
|
|
#
|
2007-01-28 12:20:18 +00:00
|
|
|
# debug.backtrace([<comment:string>]} ... writes backtrace with local variables
|
|
|
|
# (similar to gdb's "bt full)
|
2007-02-07 17:06:05 +00:00
|
|
|
#
|
2007-10-16 17:23:25 +00:00
|
|
|
# debug.proptrace([<property [, <frames>]]) ... trace property write/add/remove
|
|
|
|
# events under the <property> subtree for
|
|
|
|
# a number of frames. Defaults are "/" and
|
2008-08-17 07:35:27 +00:00
|
|
|
# 2 frames (of which the first one is incomplete).
|
2007-10-16 15:24:30 +00:00
|
|
|
#
|
2007-05-09 17:31:45 +00:00
|
|
|
# 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
|
|
|
|
#
|
2008-06-08 11:14:57 +00:00
|
|
|
# debug.bt() ... abbreviation for debug.backtrace()
|
2007-02-07 17:06:05 +00:00
|
|
|
#
|
|
|
|
# debug.string(<variable>) ... returns contents of variable as string
|
2007-11-15 06:46:14 +00:00
|
|
|
# debug.attributes(<property> [, <verb>]) ... returns attribute string for a given property.
|
2008-06-08 11:14:57 +00:00
|
|
|
# <verb>ose is by default 1, and suppressed the
|
|
|
|
# node's refcounter if 0.
|
2007-02-07 17:06:05 +00:00
|
|
|
#
|
2009-02-06 14:41:13 +00:00
|
|
|
# debug.isnan() returns 1 if argument is an invalid number (NaN),
|
|
|
|
# 0 if it's a valid number, and nil in all other cases
|
|
|
|
#
|
2008-06-21 07:10:32 +00:00
|
|
|
# debug.benchmark(<label:string>, <func> [, <repeat:int>])
|
|
|
|
# ... runs function <repeat> times (default: 1)
|
|
|
|
# and prints execution time in seconds,
|
2007-05-14 16:28:53 +00:00
|
|
|
# prefixed with <label>.
|
2007-06-10 20:31:33 +00:00
|
|
|
#
|
2007-06-16 08:21:39 +00:00
|
|
|
# debug.printerror(<err-vector>) ... prints error vector as set by call()
|
2009-02-08 21:16:28 +00:00
|
|
|
# debug.error(<message>, <level>) ... generate error message followed by stack trace
|
|
|
|
# beginning with <level> (default: 2).
|
2007-06-16 08:21:39 +00:00
|
|
|
#
|
2007-10-16 15:24:30 +00:00
|
|
|
# debug.propify(<variable>) ... turn about everything into a props.Node
|
2007-06-16 08:21:39 +00:00
|
|
|
#
|
2007-06-10 20:31:33 +00:00
|
|
|
# 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
|
2007-11-25 20:36:57 +00:00
|
|
|
#
|
2007-01-28 12:20:18 +00:00
|
|
|
|
2009-02-08 20:46:55 +00:00
|
|
|
# ANSI color code wrappers (see $ man console_codes)
|
2007-01-29 16:24:58 +00:00
|
|
|
#
|
2008-10-03 15:20:12 +00:00
|
|
|
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
|
2007-02-07 17:06:05 +00:00
|
|
|
|
|
|
|
|
2007-10-16 15:24:30 +00:00
|
|
|
##
|
|
|
|
# Turn p into props.Node (if it isn't yet), or return nil.
|
|
|
|
#
|
|
|
|
var propify = func(p, create = 0) {
|
|
|
|
var type = typeof(p);
|
2008-11-13 12:00:23 +00:00
|
|
|
if (type == "ghost" and ghosttype(p) == "prop")
|
2007-10-16 15:24:30 +00:00
|
|
|
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);
|
2007-05-09 17:31:45 +00:00
|
|
|
if (n == nil)
|
|
|
|
dump(n);
|
|
|
|
else
|
|
|
|
_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")
|
2008-07-30 19:22:36 +00:00
|
|
|
s ~= " = " ~ debug.string(n.getValue()) ~ " " ~ attributes(n)
|
2007-05-09 17:31:45 +00:00
|
|
|
~ " " ~ _section(" PARENT-VALUE ");
|
|
|
|
} else {
|
2008-07-30 19:22:36 +00:00
|
|
|
s ~= " = " ~ debug.string(n.getValue()) ~ " " ~ attributes(n);
|
2007-05-09 17:31:45 +00:00
|
|
|
}
|
2008-11-26 16:07:32 +00:00
|
|
|
|
|
|
|
if ((var a = n.getAliasTarget()) != nil)
|
|
|
|
s ~= " " ~ _title(" alias to ") ~ " " ~ a.getPath();
|
|
|
|
|
2007-05-09 17:31:45 +00:00
|
|
|
print(s);
|
|
|
|
|
|
|
|
if (n.getType() != "ALIAS")
|
|
|
|
forindex (var i; children)
|
|
|
|
_tree(children[i], graph, prefix ~ ". ", level + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-11-15 21:27:42 +00:00
|
|
|
var attributes = func(p, verbose = 1) {
|
2008-07-07 10:25:08 +00:00
|
|
|
var r = p.getAttribute("readable") ? "" : "r";
|
|
|
|
var w = p.getAttribute("writable") ? "" : "w";
|
2007-01-30 23:22:36 +00:00
|
|
|
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" : "";
|
2007-05-09 17:31:45 +00:00
|
|
|
var attr = r ~ w ~ R ~ W ~ A ~ U ~ T;
|
2008-05-15 15:23:17 +00:00
|
|
|
var type = "(" ~ p.getType();
|
2007-05-09 17:31:45 +00:00
|
|
|
if (size(attr))
|
|
|
|
type ~= ", " ~ attr;
|
|
|
|
if (var l = p.getAttribute("listeners"))
|
|
|
|
type ~= ", L" ~ l;
|
2007-11-15 06:46:14 +00:00
|
|
|
if (verbose and (var c = p.getAttribute("references")) > 2)
|
|
|
|
type ~= ", #" ~ (c - 2);
|
2008-05-15 15:23:17 +00:00
|
|
|
return _proptype(type ~ ")");
|
2007-05-09 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var _dump_prop = func(p) {
|
2008-07-30 19:22:36 +00:00
|
|
|
_path(p.getPath()) ~ " = " ~ debug.string(p.getValue()) ~ " " ~ attributes(p);
|
2007-01-28 12:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-02-07 17:06:05 +00:00
|
|
|
var _dump_var = func(v) {
|
2007-05-09 17:31:45 +00:00
|
|
|
if (v == "me" or v == "parents")
|
2007-01-29 16:24:58 +00:00
|
|
|
return _internal(v);
|
2007-05-09 17:31:45 +00:00
|
|
|
else
|
2007-01-28 12:20:18 +00:00
|
|
|
return _varname(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-11-19 12:36:26 +00:00
|
|
|
var _dump_string = func(str) {
|
2007-11-25 20:36:57 +00:00
|
|
|
var s = "'";
|
2007-11-19 12:36:26 +00:00
|
|
|
for (var i = 0; i < size(str); i += 1) {
|
2007-11-25 20:36:57 +00:00
|
|
|
var c = str[i];
|
|
|
|
if (c == `\``)
|
|
|
|
s ~= "\\`";
|
|
|
|
elsif (c == `\n`)
|
|
|
|
s ~= "\\n";
|
|
|
|
elsif (c == `\r`)
|
|
|
|
s ~= "\\r";
|
|
|
|
elsif (c == `\t`)
|
|
|
|
s ~= "\\t";
|
2008-07-30 19:22:36 +00:00
|
|
|
elsif (globals.string.isprint(c))
|
2007-11-25 20:36:57 +00:00
|
|
|
s ~= chr(c);
|
|
|
|
else
|
|
|
|
s ~= sprintf("\\x%02x", c);
|
2007-11-19 12:36:26 +00:00
|
|
|
}
|
2007-11-25 20:36:57 +00:00
|
|
|
return _string(s ~ "'");
|
2007-11-19 12:36:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# dump hash keys as variables if they are valid variable names, or as string otherwise
|
|
|
|
var _dump_key = func(s) {
|
2007-11-19 13:31:18 +00:00
|
|
|
if (num(s) != nil)
|
|
|
|
return _num(s);
|
2008-10-25 14:24:26 +00:00
|
|
|
if (!size(s))
|
|
|
|
return _dump_string(s);
|
2008-07-30 19:22:36 +00:00
|
|
|
if (!globals.string.isalpha(s[0]) and s[0] != `_`)
|
2007-11-19 12:36:26 +00:00
|
|
|
return _dump_string(s);
|
2009-02-06 14:41:13 +00:00
|
|
|
for (var i = 1; i < size(s); i += 1)
|
2008-07-30 19:22:36 +00:00
|
|
|
if (!globals.string.isalnum(s[i]) and s[i] != `_`)
|
2007-11-19 12:36:26 +00:00
|
|
|
return _dump_string(s);
|
|
|
|
_dump_var(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-02-07 17:06:05 +00:00
|
|
|
var string = func(o) {
|
2007-01-28 12:20:18 +00:00
|
|
|
var t = typeof(o);
|
|
|
|
if (t == "nil") {
|
|
|
|
return _nil("nil");
|
2007-11-19 12:36:26 +00:00
|
|
|
|
2007-01-28 12:20:18 +00:00
|
|
|
} elsif (t == "scalar") {
|
2007-11-19 12:36:26 +00:00
|
|
|
return num(o) == nil ? _dump_string(o) : _num(o);
|
|
|
|
|
2007-01-28 12:20:18 +00:00
|
|
|
} elsif (t == "vector") {
|
2007-01-30 23:22:36 +00:00
|
|
|
var s = "";
|
2007-05-09 17:31:45 +00:00
|
|
|
forindex (var i; o)
|
2008-07-30 19:22:36 +00:00
|
|
|
s ~= (i == 0 ? "" : ", ") ~ debug.string(o[i]);
|
2008-12-03 20:49:33 +00:00
|
|
|
return _bracket("[") ~ s ~ _bracket("]");
|
2007-11-19 12:36:26 +00:00
|
|
|
|
2007-01-28 12:20:18 +00:00
|
|
|
} elsif (t == "hash") {
|
|
|
|
if (contains(o, "parents") and typeof(o.parents) == "vector"
|
2007-06-12 21:27:48 +00:00
|
|
|
and size(o.parents) == 1 and o.parents[0] == props.Node)
|
2007-01-28 12:20:18 +00:00
|
|
|
return _angle("<") ~ _dump_prop(o) ~ _angle(">");
|
2007-06-12 21:27:48 +00:00
|
|
|
|
2007-01-28 12:20:18 +00:00
|
|
|
var k = keys(o);
|
2007-01-30 23:22:36 +00:00
|
|
|
var s = "";
|
2007-05-09 17:31:45 +00:00
|
|
|
forindex (var i; k)
|
2008-08-14 22:13:25 +00:00
|
|
|
s ~= (i == 0 ? "" : ", ") ~ _dump_key(k[i]) ~ ": " ~ debug.string(o[k[i]]);
|
2007-01-30 23:22:36 +00:00
|
|
|
return _brace("{") ~ " " ~ s ~ " " ~ _brace("}");
|
2007-11-19 12:36:26 +00:00
|
|
|
|
2007-06-21 20:00:21 +00:00
|
|
|
} elsif (t == "ghost") {
|
2008-11-13 12:00:23 +00:00
|
|
|
return _angle("<") ~ _nil(ghosttype(o)) ~ _angle(">");
|
2007-11-19 12:36:26 +00:00
|
|
|
|
2007-01-28 12:20:18 +00:00
|
|
|
} else {
|
2007-01-29 16:24:58 +00:00
|
|
|
return _angle("<") ~ _vartype(t) ~ _angle(">");
|
2007-01-28 12:20:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-05-15 15:23:17 +00:00
|
|
|
var dump = func(vars...) {
|
|
|
|
if (!size(vars))
|
|
|
|
return local(1);
|
|
|
|
if (size(vars) == 1)
|
2008-07-30 19:22:36 +00:00
|
|
|
return print(debug.string(vars[0]));
|
2008-05-15 15:23:17 +00:00
|
|
|
forindex (var i; vars)
|
2008-10-03 15:20:12 +00:00
|
|
|
print(globals.string.color("33;40;1", "#" ~ i) ~ " ", debug.string(vars[i]));
|
2008-05-15 15:23:17 +00:00
|
|
|
}
|
2007-02-07 17:06:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
var local = func(frame = 0) {
|
|
|
|
var v = caller(frame + 1);
|
2008-07-30 19:22:36 +00:00
|
|
|
print(v == nil ? _error("<no such frame>") : debug.string(v[0]));
|
2007-02-07 17:06:05 +00:00
|
|
|
return v;
|
|
|
|
}
|
2007-01-28 12:20:18 +00:00
|
|
|
|
|
|
|
|
2007-06-10 20:31:33 +00:00
|
|
|
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])));
|
2007-01-28 12:20:18 +00:00
|
|
|
dump(v[0]);
|
|
|
|
}
|
|
|
|
}
|
2007-02-07 17:06:05 +00:00
|
|
|
var bt = backtrace;
|
2007-01-28 12:20:18 +00:00
|
|
|
|
|
|
|
|
2008-08-17 07:35:27 +00:00
|
|
|
var proptrace = func(root = "/", frames = 2) {
|
2007-10-16 17:23:25 +00:00
|
|
|
var events = 0;
|
2007-10-16 15:24:30 +00:00
|
|
|
var trace = setlistener(propify(root), func(this, base, type) {
|
2007-10-16 17:23:25 +00:00
|
|
|
events += 1;
|
2007-10-16 15:24:30 +00:00
|
|
|
if (type > 0)
|
2007-10-16 18:54:25 +00:00
|
|
|
print(_nil("ADD "), this.getPath());
|
2007-10-16 15:24:30 +00:00
|
|
|
elsif (type < 0)
|
2007-10-16 18:54:25 +00:00
|
|
|
print(_num("DEL "), this.getPath());
|
2007-10-16 15:24:30 +00:00
|
|
|
else
|
2008-07-30 19:22:36 +00:00
|
|
|
print("SET ", this.getPath(), " = ", debug.string(this.getValue()), " ", attributes(this));
|
2007-10-16 15:24:30 +00:00
|
|
|
}, 0, 2);
|
2007-10-16 17:23:25 +00:00
|
|
|
var mark = setlistener("/sim/signals/frame", func {
|
|
|
|
print("-------------------- FRAME --------------------");
|
|
|
|
if (!frames) {
|
|
|
|
removelistener(trace);
|
|
|
|
removelistener(mark);
|
|
|
|
print("proptrace: stop (", events, " calls)");
|
|
|
|
}
|
|
|
|
frames -= 1;
|
|
|
|
});
|
2007-10-16 15:24:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-05-14 16:28:53 +00:00
|
|
|
##
|
2008-10-09 17:20:27 +00:00
|
|
|
# Executes function fn "repeat" times and prints execution time in seconds. Examples:
|
2007-05-14 16:28:53 +00:00
|
|
|
#
|
2008-06-21 07:10:32 +00:00
|
|
|
# var test = func { getprop("/sim/aircraft"); }
|
2008-08-10 17:53:41 +00:00
|
|
|
# debug.benchmark("test()/2", test, 1000);
|
|
|
|
# debug.benchmark("test()/2", func setprop("/sim/aircraft", ""), 1000);
|
2007-05-14 16:28:53 +00:00
|
|
|
#
|
2008-08-14 22:13:25 +00:00
|
|
|
var benchmark = func(label, fn, repeat = 1) {
|
2007-05-14 16:28:53 +00:00
|
|
|
var start = systime();
|
2008-06-21 07:10:32 +00:00
|
|
|
for (var i = 0; i < repeat; i += 1)
|
2008-08-14 22:13:25 +00:00
|
|
|
fn();
|
2007-05-14 16:28:53 +00:00
|
|
|
print(_bench(sprintf(" %s --> %.6f s ", label, systime() - start)));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-11-15 06:46:14 +00:00
|
|
|
##
|
2007-06-16 08:21:39 +00:00
|
|
|
# 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:
|
|
|
|
#
|
2008-08-14 22:13:25 +00:00
|
|
|
# var possibly_buggy = func { ... }
|
|
|
|
# call(possibly_buggy, nil, var err = []);
|
2007-06-16 08:21:39 +00:00
|
|
|
# debug.printerror(err);
|
|
|
|
#
|
|
|
|
var printerror = func(err) {
|
|
|
|
if (!size(err))
|
|
|
|
return;
|
|
|
|
|
2008-07-23 15:43:45 +00:00
|
|
|
printf("%s at %s line %d", err[0], err[1], err[2]);
|
2007-06-16 08:21:39 +00:00
|
|
|
for (var i = 3; i < size(err); i += 2)
|
2008-07-23 15:43:45 +00:00
|
|
|
printf(" called from %s line %d", err[i], err[i + 1]);
|
2007-06-16 08:21:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-02-08 20:46:55 +00:00
|
|
|
var error = func(msg, level = 2) {
|
|
|
|
var c = caller(level);
|
|
|
|
print(msg, ":\n at ", c[2], ", line ", c[3]);
|
|
|
|
while ((c = caller(level += 1)) != nil)
|
|
|
|
print(" called from: ", c[2], ", line ", c[3]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-02-06 14:41:13 +00:00
|
|
|
var isnan = (func { var nan = 1 / 0; func(d) num(d) == nil ? nil : d == nan; })();
|
|
|
|
|
|
|
|
|
2009-02-08 20:46:55 +00:00
|
|
|
|
|
|
|
# --prop:debug=1 enables debug mode with additional warnings
|
|
|
|
#
|
|
|
|
_setlistener("sim/signals/nasal-dir-initialized", func {
|
|
|
|
if (!getprop("debug"))
|
|
|
|
return;
|
|
|
|
var warn = func(f, p, r) {
|
2009-02-08 21:16:28 +00:00
|
|
|
if (!r) {
|
|
|
|
var hint = "";
|
|
|
|
if ((n = props.globals.getNode(p)) != nil) {
|
|
|
|
if (!n.getAttribute("writable"))
|
|
|
|
hint = " (write protected)";
|
|
|
|
elsif (n.getAttribute("tied"))
|
|
|
|
hint = " (tied)";
|
|
|
|
}
|
|
|
|
error("Warning: " ~ f ~ " -> writing to " ~ p ~ " failed" ~ hint, 3);
|
|
|
|
}
|
2009-02-08 20:46:55 +00:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
setprop = (func { var _ = setprop; func warn("setprop",
|
|
|
|
globals.string.join("", arg[:-1]), call(_, arg)) })();
|
2009-02-08 21:16:28 +00:00
|
|
|
props.Node.setDoubleValue = func warn("setDoubleValue", me.getPath(),
|
|
|
|
props._setDoubleValue(me._g, arg));
|
|
|
|
props.Node.setBoolValue = func warn("setBoolValue", me.getPath(),
|
|
|
|
props._setBoolValue(me._g, arg));
|
|
|
|
props.Node.setIntValue = func warn("setIntValue", me.getPath(),
|
|
|
|
props._setIntValue(me._g, arg));
|
|
|
|
props.Node.setValue = func warn("setValue", me.getPath(),
|
|
|
|
props._setValue(me._g, arg));
|
2009-02-08 20:46:55 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|