remove README.Protocol and add a README that refers to the "real"
[fg:toms-fgdata.git] / Nasal / debug.nas
1 # debug.nas -- debugging helpers
2 #------------------------------------------------------------------------------
3 #
4 # debug.color(<enabled>);              ... turns terminal colors on (1) or off (0)
5 #
6 # debug.dump(<variable>)               ... dumps contents of variable to terminal;
7 #                                          abbreviation for print(debug.string(v))
8 #
9 # debug.local([<frame:int>])           ... dump local variables of current
10 #                                          or given frame
11 #
12 # debug.backtrace([<comment:string>]}  ... writes backtrace with local variables
13 #                                          (similar to gdb's "bt full)
14 #
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
20 #
21 # debug.exit()                         ... exits fgfs
22 #
23 # debug.bt                             ... abbreviation for debug.backtrace
24 #
25 # debug.string(<variable>)             ... returns contents of variable as string
26 #
27 # debug.load_nasal(<file> [, <module>])) ... load and run Nasal file (under namespace
28 #                                          <module> if defined, or the basename
29 #                                          otherwise)
30 #
31 # debug.benchmark(<label:string>, <func> [<args>])
32 #                                      ... calls function with optional args and
33 #                                          prints execution time in seconds,
34 #                                          prefixed with <label>.
35 #
36 # debug.printerror(<err-vector>)       ... prints error vector as set by call()
37 #
38 #
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
43
44 var _c = func {}
45
46 var color = func(enabled) {
47         if (enabled) {
48                 _c = func(color, s) { "\x1b[" ~ color ~ "m" ~ s ~ "\x1b[m" }
49         } else {
50                 _c = func(dummy, s) { s }
51         }
52 }
53
54
55 # for color codes see  $ man console_codes
56 #
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
61
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
73
74 var ghosttypes = {};
75
76
77 var tree = func(p = "", graph = 1) {
78         var n = isa(p, props.Node) ? p : props.globals.getNode(p, 0);
79         if (n == nil)
80                 dump(n);
81         else
82                 _tree(n, graph);
83 }
84
85
86 var _tree = func(n, graph = 1, prefix = "", level = 0) {
87         var path = n.getPath();
88         var children = n.getChildren();
89         var s = "";
90
91         if (graph) {
92                 s = prefix ~ n.getName();
93                 var index = n.getIndex();
94                 if (index)
95                         s ~= "[" ~ index ~ "]";
96         } else {
97                 s = n.getPath();
98         }
99
100         if (size(children)) {
101                 s ~= "/";
102                 if (n.getType() != "NONE")
103                         s ~= " = " ~ string(n.getValue()) ~ " " ~ _attrib(n)
104                                         ~ "    " ~ _section(" PARENT-VALUE ");
105         } else {
106                 s ~= " = " ~ string(n.getValue()) ~ " " ~ _attrib(n);
107         }
108         print(s);
109
110         if (n.getType() != "ALIAS")
111                 forindex (var i; children)
112                         _tree(children[i], graph, prefix ~ ".   ", level + 1);
113 }
114
115
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());
126         if (size(attr))
127                 type ~= ", " ~ attr;
128         if (var l = p.getAttribute("listeners"))
129                 type ~= ", L" ~ l;
130         return type ~ ")";
131 }
132
133
134 var _dump_prop = func(p) {
135         _path(p.getPath()) ~ " = " ~ string(p.getValue()) ~ " " ~ _attrib(p);
136 }
137
138
139 var _dump_var = func(v) {
140         if (v == "me" or v == "parents")
141                 return _internal(v);
142         else
143                 return _varname(v);
144 }
145
146
147 var string = func(o) {
148         var t = typeof(o);
149         if (t == "nil") {
150                 return _nil("nil");
151         } elsif (t == "scalar") {
152                 return num(o) == nil ? _string('"' ~ o ~ '"') : _num(o);
153         } elsif (t == "vector") {
154                 var s = "";
155                 forindex (var i; o)
156                         s ~= (i == 0 ? "" : ", ") ~ string(o[i]);
157
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(">");
163
164                 var k = keys(o);
165                 var s = "";
166                 forindex (var i; k)
167                         s ~= (i == 0 ? "" : ", ") ~ _dump_var(k[i]) ~ " : " ~ string(o[k[i]]);
168
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(">");
174                 else
175                         return _angle("<") ~ _nil(gt) ~ _angle(">");
176         } else {
177                 return _angle("<") ~ _vartype(t) ~ _angle(">");
178         }
179 }
180
181
182 var dump = func { size(arg) ? print(string(arg[0])) : local(1) }
183
184
185 var local = func(frame = 0) {
186         var v = caller(frame + 1);
187         print(v == nil ? _error("<no such frame>") : string(v[0]));
188         return v;
189 }
190
191
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) {
196                 v = caller(i);
197                 if (v == nil)
198                         return;
199                 print(_section(sprintf("#%-2d called from %s, line %s:", i - 1, v[2], v[3])));
200                 dump(v[0]);
201         }
202 }
203 var bt = backtrace;
204
205
206 ##
207 # Executes function f with optional arguments and prints execution
208 # time in seconds. Examples:
209 #
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) });
213 #
214 var benchmark = func(label, f, arg...) {
215         var start = systime();
216         call(f, arg);
217         print(_bench(sprintf(" %s --> %.6f s ", label, systime() - start)));
218 }
219
220
221 var exit = func { fgcommand("exit") }
222
223
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:
227 #
228 #     call(func { possibly_buggy() }, nil, var err = []);
229 #     debug.printerror(err);
230 #
231 var printerror = func(err) {
232         if (!size(err))
233                 return;
234
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]));
238 }
239
240
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 **")) });
244 }
245
246
247
248 ##
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.
252 #
253 # Usage:   debug.load_nasal(<filename> [, <modulename>]);
254 #
255 # Example:
256 #
257 #     debug.load_nasal(getprop("/sim/fg-root") ~ "/Local/test.nas");
258 #     debug.load_nasal("/tmp/foo.nas", "test");
259 #
260 var load_nasal = func(file, module = nil) {
261         if (module == nil)
262                 module = split(".", split("/", file)[-1])[0];
263
264         if (!contains(globals, module))
265                 globals[module] = {};
266
267         printlog("info", "loading ", file, " into namespace ", module);
268         call(compile(io.readfile(file), file), nil, nil, globals[module]);
269 }
270
271
272 _setlistener("/sim/signals/nasal-dir-initialized", func {
273         ghosttypes[ghosttype(props._globals())] = "PropertyNode";
274         ghosttypes[ghosttype(io.stderr)] = "FileHandle";
275
276         setlistener("/sim/startup/terminal-ansi-colors", func {
277                 color(cmdarg().getBoolValue());
278         }, 1);
279 });
280
281