1 # debug.nas -- debugging helpers
2 #------------------------------------------------------------------------------
4 # debug.color(<enabled>); ... turns terminal colors on (1) or off (0)
6 # debug.dump(<variable>) ... dumps contents of variable to terminal;
7 # abbreviation for print(debug.string(v))
9 # debug.local([<frame:int>]) ... dump local variables of current
12 # debug.backtrace([<comment:string>]} ... writes backtrace with local variables
13 # (similar to gdb's "bt full)
15 # debug.tree([<property> [, <mode>]) ... dump property tree under property path
16 # or props.Node hash (default: root). If
17 # <mode> is unset or 0, use flat mode
18 # (similar to props.dump()), otherwise
19 # use space indentation
21 # debug.exit() ... exits fgfs
23 # debug.bt ... abbreviation for debug.backtrace
25 # debug.string(<variable>) ... returns contents of variable as string
27 # debug.load_nasal(<file> [, <module>])) ... load and run Nasal file (under namespace
28 # <module> if defined, or the basename
31 # debug.benchmark(<label:string>, <func> [<args>])
32 # ... calls function with optional args and
33 # prints execution time in seconds,
34 # prefixed with <label>.
36 # debug.printerror(<err-vector>) ... prints error vector as set by call()
39 # CAVE: this file makes extensive use of ANSI color codes. These are
40 # interpreted by UNIX shells and MS Windows with ANSI.SYS extension
41 # installed. If the color codes aren't interpreted correctly, then
42 # set property /sim/startup/terminal-ansi-colors=0
46 var color = func(enabled) {
48 _c = func(color, s) { "\x1b[" ~ color ~ "m" ~ s ~ "\x1b[m" }
50 _c = func(dummy, s) { s }
55 # for color codes see $ man console_codes
57 var _title = func(s) { _c("33;42;1", s) } # backtrace header
58 var _section = func(s) { _c("37;41;1", s) } # backtrace frame
59 var _error = func(s) { _c("31;1", s) } # internal errors
60 var _bench = func(s) { _c("37;45;1", s) } # benchmark info
62 var _nil = func(s) { _c("32", s) } # nil
63 var _string = func(s) { _c("31", s) } # "foo"
64 var _num = func(s) { _c("31", s) } # 0.0
65 var _bracket = func(s) { _c("", s) } # [ ]
66 var _brace = func(s) { _c("", s) } # { }
67 var _angle = func(s) { _c("", s) } # < >
68 var _vartype = func(s) { _c("33", s) } # func ghost
69 var _proptype = func(s) { _c("34", s) } # BOOL INT LONG DOUBLE ...
70 var _path = func(s) { _c("36", s) } # /some/property/path
71 var _internal = func(s) { _c("35", s) } # me parents
72 var _varname = func(s) { _c("1", s) } # variable_name
77 var tree = func(p = "", graph = 1) {
78 var n = isa(p, props.Node) ? p : props.globals.getNode(p, 0);
86 var _tree = func(n, graph = 1, prefix = "", level = 0) {
87 var path = n.getPath();
88 var children = n.getChildren();
92 s = prefix ~ n.getName();
93 var index = n.getIndex();
95 s ~= "[" ~ index ~ "]";
100 if (size(children)) {
102 if (n.getType() != "NONE")
103 s ~= " = " ~ string(n.getValue()) ~ " " ~ _attrib(n)
104 ~ " " ~ _section(" PARENT-VALUE ");
106 s ~= " = " ~ string(n.getValue()) ~ " " ~ _attrib(n);
110 if (n.getType() != "ALIAS")
111 forindex (var i; children)
112 _tree(children[i], graph, prefix ~ ". ", level + 1);
116 var _attrib = func(p) {
117 var r = p.getAttribute("read") ? "" : "r";
118 var w = p.getAttribute("write") ? "" : "w";
119 var R = p.getAttribute("trace-read") ? "R" : "";
120 var W = p.getAttribute("trace-write") ? "W" : "";
121 var A = p.getAttribute("archive") ? "A" : "";
122 var U = p.getAttribute("userarchive") ? "U" : "";
123 var T = p.getAttribute("tied") ? "T" : "";
124 var attr = r ~ w ~ R ~ W ~ A ~ U ~ T;
125 var type = "(" ~ _proptype(p.getType());
128 if (var l = p.getAttribute("listeners"))
134 var _dump_prop = func(p) {
135 _path(p.getPath()) ~ " = " ~ string(p.getValue()) ~ " " ~ _attrib(p);
139 var _dump_var = func(v) {
140 if (v == "me" or v == "parents")
147 var string = func(o) {
151 } elsif (t == "scalar") {
152 return num(o) == nil ? _string('"' ~ o ~ '"') : _num(o);
153 } elsif (t == "vector") {
156 s ~= (i == 0 ? "" : ", ") ~ string(o[i]);
158 return _bracket("[") ~ " " ~ s ~ " " ~ _bracket("]");
159 } elsif (t == "hash") {
160 if (contains(o, "parents") and typeof(o.parents) == "vector"
161 and size(o.parents) == 1 and o.parents[0] == props.Node)
162 return _angle("<") ~ _dump_prop(o) ~ _angle(">");
167 s ~= (i == 0 ? "" : ", ") ~ _dump_var(k[i]) ~ " : " ~ string(o[k[i]]);
169 return _brace("{") ~ " " ~ s ~ " " ~ _brace("}");
170 } elsif (t == "ghost") {
171 var gt = ghosttype(o);
172 if (contains(ghosttypes, gt))
173 return _angle("<") ~ _nil(ghosttypes[gt]) ~ _angle(">");
175 return _angle("<") ~ _nil(gt) ~ _angle(">");
177 return _angle("<") ~ _vartype(t) ~ _angle(">");
182 var dump = func { size(arg) ? print(string(arg[0])) : local(1) }
185 var local = func(frame = 0) {
186 var v = caller(frame + 1);
187 print(v == nil ? _error("<no such frame>") : string(v[0]));
192 var backtrace = func(desc = nil) {
193 var d = desc == nil ? "" : " '" ~ desc ~ "'";
194 print("\n" ~ _title("\n### backtrace" ~ d ~ " ###"));
195 for (var i = 1; 1; i += 1) {
199 print(_section(sprintf("#%-2d called from %s, line %s:", i - 1, v[2], v[3])));
207 # Executes function f with optional arguments and prints execution
208 # time in seconds. Examples:
210 # var test = func(n) { for (var i = 0; i < n; i +=1) { print(i) }
211 # debug.benchmark("test()/1", test, 10);
212 # debug.benchmark("test()/2", func { test(10) });
214 var benchmark = func(label, f, arg...) {
215 var start = systime();
217 print(_bench(sprintf(" %s --> %.6f s ", label, systime() - start)));
221 var exit = func { fgcommand("exit") }
224 # print error vector as set by call(). By using call() one can execute
225 # code that catches "exceptions" (by a die() call or errors). The Nasal
226 # code doesn't abort in this case. Example:
228 # call(func { possibly_buggy() }, nil, var err = []);
229 # debug.printerror(err);
231 var printerror = func(err) {
235 print(sprintf("%s at %s line %d", err[0], err[1], err[2]));
236 for (var i = 3; i < size(err); i += 2)
237 print(sprintf(" called from %s line %d", err[i], err[i + 1]));
241 if (getprop("/sim/logging/priority") != "alert") {
242 _setlistener("/sim/signals/nasal-dir-initialized", func { print(_c("32", "** NASAL initialized **")) });
243 _setlistener("/sim/signals/fdm-initialized", func { print(_c("36", "** FDM initialized **")) });
249 # Loads Nasal file into namespace and executes it. The namespace
250 # (module name) is taken from the optional second argument, or
251 # derived from the Nasal file's name.
253 # Usage: debug.load_nasal(<filename> [, <modulename>]);
257 # debug.load_nasal(getprop("/sim/fg-root") ~ "/Local/test.nas");
258 # debug.load_nasal("/tmp/foo.nas", "test");
260 var load_nasal = func(file, module = nil) {
262 module = split(".", split("/", file)[-1])[0];
264 if (!contains(globals, module))
265 globals[module] = {};
267 printlog("info", "loading ", file, " into namespace ", module);
268 call(compile(io.readfile(file), file), nil, nil, globals[module]);
272 _setlistener("/sim/signals/nasal-dir-initialized", func {
273 ghosttypes[ghosttype(props._globals())] = "PropertyNode";
274 ghosttypes[ghosttype(io.stderr)] = "FileHandle";
276 setlistener("/sim/startup/terminal-ansi-colors", func {
277 color(cmdarg().getBoolValue());