Improving Nasal

From FlightGear wiki
Revision as of 15:10, 27 July 2013 by Hooray (talk | contribs)
Jump to navigation Jump to search


Last update: 10/2011

As more and more code in FlightGear is moved to the base package and thus implemented in Nasal space, some Nasal related issues have become increasingly obvious.

On the other hand, Nasal has a proven track record of success in FlightGear, and has shown remarkably few significant issues so far. Most of the more prominent issues are related to a wider adoption in FlightGear, and thus more complex features being implemented in Nasal overall, often developed by people without any formal programming training and/or coding experience, for whom Nasal may be their first programming language.

So, rather than having Nasal flame wars and talking about "alternatives" like Perl, Python, Javascript or Lua, the idea is to document known Nasal issues so that they can hopefully be addressed eventually.

If you are aware of any major Nasal issues that are not yet covered here, please feel free to add them here, however it is also a good idea to use the FlightGear bug tracker in such cases: http://flightgear-bugs.googlecode.com/

Explicit loading order and reset support

See [1].

Better debugging/development support

Besides making a full IDE (which would be really cool), there are several things that can be done by editing the source code of Nasal to enhance debugging support and increase development :

  • Being able to dump the global namespace (see this topic for a possible solution) or at least dump things prettily (an unreleased version of the file discussed in Nasal Meta-Programming has good support for nice formatting).
  • Register a callback for handling errors using call()
  • Register a callback for OP_FCALL et al. to be able to time function calls. Example (relies on the gen.nas module and debug.decompile):
# Debugging:
runindebug = func(fn, arg...) {
    var start = []; var indent = "";
    
    call(func, arg, nil, nil, {
           op_fcall:func(context) {
             var obj = debug.decompile(context.fn);
             obj.end = context.ip+1;
             append(start, [get_fcall_lvalue(obj)]);
             print(indent, "Start function call on function ", start[-1][0]);
             append(start[-1], systime()); #save it now for most accurate results
             indent ~= "  ";
           },
           op_return:func {
             var v = pop(start);
             indent = substr(indent, 0, size(indent)-2);
             print(indent, "Took ", systime()-v[1], " seconds");
           },
           error:func(err...) {
             printf(err[0]);
             for (var i=0; i<size(err); i+=1)
               printf("  called from %s line %d", err[i], err[i+=1]);
             print("Stack info:");
             var namespace = caller(1)[0];
             printf("Local namespace:");
             printf("  "~gen.dump_namespace(namespace, 0, 2, "  "));
             var c = caller(1)[1]; var i = 0;
             while ((var cl = closure(c, i)) != nil) {
               printf("Closure %d:", i+=1);
               printf("  "~gen.dump_namespace(cl, 0, 2, "  "));
             }
             var stack = debug.stackinfo();
             forindex (var i; stack) {
               printf("Caller %d:", i);
               printf("  Local namespace:");
               printf("    "~gen.dump_namespace(stack[i].locals, 0, 4, "    "));
             }
           },
         }, var err=[]);
};

var get_fcall_lvalue = func(obj, level=0, col=0, suffix="") {
    var output = gen.decompile_expr(obj, level, col, suffix);
    var level = 1;
    for (var i=-2; i>=-size(output) and level > 0; i-=1) {
        if (output[i] == `)`) level += 1;
        elsif (output[i] == `(`) level -= 1;
    }
    return substr(output, 0, size(output)-i);
};

# It is really easy to debug code now, just save off every
# expression to a variable and browse them on error.
var possibly_buggy_code = func(arg...) {
    var sum = (var _1 = getprop("/sim/node/not/exists[0]")) +
    (var _2 = getprop("/sim/node/not/exists[1]"));
    return var _3 = sprintf("%8.7f", sum);
};
  • Set breakpoints: register callbacks for values of (struct Frame*)->ip.
  • Time other parts of Nasal (not just VM) with a compile-time flag?
  • Better error messages – in progress.
    • Parsing: Say something other than "parse error", like "null pointer".
    • VM: Indicate type of variable if wrong type.
    • Both: Could we give line and column?
  • Working with bytecode:
    • Expose to Nasal: done
    • Decompile to text: partial (untested)
    • Optimize: not started
    • Working with it: provide Bytecode class: not started
  • Inspect Context: not started, should be easy.
  • Expose Tokens to Nasal: implemented by Hooray as argument to compile(), should be extended to cover output from lex.c and after blocking in addition to the current after-prec-ing (and before freeing!) support.
  • Real function name support via assignment:
    • Option 1: look at the parse tree and check if the right side of the assignment is a function. If so, go ahead and parse the function with the left side as the "name" of the function instead of falling through to more recursions of genExpr().
    • Option 2: recognize assignment in the VM and if there is a bindToContext event, set the name of the function based upon either the last LOCAL/MEMBER/HINSERT or the combination of them (i.e. complex lvalues like local.fn). This presents some obvious issues, however:
      • The right-hand side of an assignment is done before the left-hand side, thus one would have to look ahead to see the assignment, which is clearly illegal for the VM to do.
      • Or one could look behind to see a naCode constant being pushed, and give some indication to its naFunc that it now has a name. This I still somewhat illegal, but not dangerous and thus could be done.
    • Option 3: abandon var foo = func(){} for ECMAscript-like function declaration syntax function foo() {}. This would not affect the use of anonymous func expressions but would instead be applicable in cases where we want to say "this function is static (i.e. permanent) and should have a name" (as opposed the the case of temporary storage variables for functions). Regardless of the method used, a name member will have to be added to naFunc's and the VM and error handling procedures will have to be changed according.
  • Timing parts of VM: use the callbacks and systime/unit.time to time things. Need hooks into the GC as well. Statistics worth tracking:
    • Per function:
      • ncalls per frame/cumulative
      • time per call on avg/cumulative
      • min/max time
      • number of GC invocations & avg/min/max time
      • number of naNew() calls
      • number & list of names of once-use variables; n that are numbers (i.e. non-GC-managed).
    • Per context/global:
      • time spent
      • number of GC invocations:
        • per frame
        • min/max/avg
    • Data per function call (not displayed, dumped to a file):
      • caller line/name