# debug.nas -- debugging helpers #------------------------------------------------------------------------------ # # debug.color(<enabled>); ... turns terminal colors on (1) or off (0) # # 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.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.exit() ... exits fgfs # # debug.bt ... abbreviation for debug.backtrace # # debug.string(<variable>) ... returns contents of variable as string # # debug.load_nasal(<file> [, <module>])) ... load and run Nasal file (under namespace # <module> if defined, or the basename # otherwise) # # debug.benchmark(<label:string>, <func> [<args>]) # ... calls function with optional args and # prints execution time in seconds, # prefixed with <label>. # # debug.printerror(<err-vector>) ... prints error vector as set by call() # # # 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 var _c = func {} var color = func(enabled) { if (enabled) { _c = func(color, s) { "\x1b[" ~ color ~ "m" ~ s ~ "\x1b[m" } } else { _c = func(dummy, s) { s } } } # for color codes see $ man console_codes # var _title = func(s) { _c("33;42;1", s) } # backtrace header var _section = func(s) { _c("37;41;1", s) } # backtrace frame var _error = func(s) { _c("31;1", s) } # internal errors var _bench = func(s) { _c("37;45;1", s) } # benchmark info var _nil = func(s) { _c("32", s) } # nil var _string = func(s) { _c("31", s) } # "foo" var _num = func(s) { _c("31", s) } # 0.0 var _bracket = func(s) { _c("", s) } # [ ] var _brace = func(s) { _c("", s) } # { } var _angle = func(s) { _c("", s) } # < > var _vartype = func(s) { _c("33", s) } # func ghost var _proptype = func(s) { _c("34", s) } # BOOL INT LONG DOUBLE ... var _path = func(s) { _c("36", s) } # /some/property/path var _internal = func(s) { _c("35", s) } # me parents var _varname = func(s) { _c("1", s) } # variable_name var tree = func(p = "", graph = 1) { var n = isa(p, props.Node) ? p : props.globals.getNode(p, 0); 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") s ~= " = " ~ string(n.getValue()) ~ " " ~ _attrib(n) ~ " " ~ _section(" PARENT-VALUE "); } else { s ~= " = " ~ string(n.getValue()) ~ " " ~ _attrib(n); } print(s); if (n.getType() != "ALIAS") forindex (var i; children) _tree(children[i], graph, prefix ~ ". ", level + 1); } var _attrib = func(p) { var r = p.getAttribute("read") ? "" : "r"; var w = p.getAttribute("write") ? "" : "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 = "(" ~ _proptype(p.getType()); if (size(attr)) type ~= ", " ~ attr; if (var l = p.getAttribute("listeners")) type ~= ", L" ~ l; return type ~ ")"; } var _dump_prop = func(p) { _path(p.getPath()) ~ " = " ~ string(p.getValue()) ~ " " ~ _attrib(p); } var _dump_var = func(v) { if (v == "me" or v == "parents") return _internal(v); else return _varname(v); } var string = func(o) { var t = typeof(o); if (t == "nil") { return _nil("nil"); } elsif (t == "scalar") { return num(o) == nil ? _string('"' ~ o ~ '"') : _num(o); } elsif (t == "vector") { var s = ""; forindex (var i; o) s ~= (i == 0 ? "" : ", ") ~ 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_var(k[i]) ~ " : " ~ 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 { size(arg) ? print(string(arg[0])) : local(1) } var local = func(frame = 0) { var v = caller(frame + 1); print(v == nil ? _error("<no such frame>") : 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; ## # Executes function f with optional arguments and prints execution # time in seconds. Examples: # # var test = func(n) { for (var i = 0; i < n; i +=1) { print(i) } # debug.benchmark("test()/1", test, 10); # debug.benchmark("test()/2", func { test(10) }); # var benchmark = func(label, f, arg...) { var start = systime(); call(f, arg); print(_bench(sprintf(" %s --> %.6f s ", label, systime() - start))); } var exit = func { fgcommand("exit") } # 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: # # call(func { possibly_buggy() }, nil, var err = []); # debug.printerror(err); # var printerror = func(err) { if (!size(err)) return; print(sprintf("%s at %s line %d", err[0], err[1], err[2])); for (var i = 3; i < size(err); i += 2) print(sprintf(" called from %s line %d", err[i], err[i + 1])); } if (getprop("/sim/logging/priority") != "alert") { _setlistener("/sim/signals/nasal-dir-initialized", func { print(_c("32", "** NASAL initialized **")) }); _setlistener("/sim/signals/fdm-initialized", func { print(_c("36", "** FDM initialized **")) }); } ## # Loads Nasal file into namespace and executes it. The namespace # (module name) is taken from the optional second argument, or # derived from the Nasal file's name. # # Usage: debug.load_nasal(<filename> [, <modulename>]); # # Example: # # debug.load_nasal(getprop("/sim/fg-root") ~ "/Local/test.nas"); # debug.load_nasal("/tmp/foo.nas", "test"); # var load_nasal = func(file, module = nil) { if (module == nil) module = split(".", split("/", file)[-1])[0]; if (!contains(globals, module)) globals[module] = {}; printlog("info", "loading ", file, " into namespace ", module); call(compile(io.readfile(file), file), nil, nil, globals[module]); } _setlistener("/sim/signals/nasal-dir-initialized", func { setlistener("/sim/startup/terminal-ansi-colors", func { color(cmdarg().getBoolValue()); }, 1); });