Board index FlightGear Development Nasal

Extensions to Nasal (was: Extension to math.nas)

Nasal is the scripting language of FlightGear.

Extensions to Nasal (was: Extension to math.nas)

Postby Philosopher » Fri Feb 08, 2013 2:25 pm

Hi all, here's an extension to math.nas that I made. Note that I'm not quite sure how the print_base function works at the moment, I have a feeling that it doesn't print decimals (but my memory's rusty since I last tried it).

Code: Select all
##
# CAVE: since it doesn't check the exponents thoroughly, you can "squirrel" away information like this and it will still parse fine:
# 100eHidden Message1
#
# FIXME: I assume a base like the following:
#    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
# Is this correct/useful?
#
var to_base = func(String, base) {
    if (size(split(".", String)) > 2) die("math.to_base(): too many decimals");
    String = string.trim(String);
    if (String[0] == `-`) {
        multiplicand = -1;
        String = String[1:];
    } else {
        multiplicand = 1;
    }
    if (base == 10 and find("e", String) != -1) {
        multiplicand *= pow(10, split("e", String)[-1]);
        String = split("e", String)[0];
    } elsif (base == 10 and find("E", String) != -1) {
        multiplicand *= pow(10, split("E", String)[-1]);
        String = split("E", String)[0];
    } elsif ((base == 16 or base == 2) and find("p", String) != -1) {
        multiplicand *= pow(2, to_base(split("p", String)[-1], base));
        String = split("p", String)[0];
    } elsif ((base == 16 or base == 2) and find("P", String) != -1) {
        multiplicand *= pow(2, to_base(split("P", String)[0], base));
        String = split("P", String)[0];
    }
    var result = 0;
    var tens = split(".", String)[0];
    var tenths = split(".", String)[-1];
    var tens_size = size(tens);
    var tenths_size = size(split(".", String)) <= 1 ? 0 : size(tenths);
    var digits = setsize([], tens_size);
    for (var i=0; i < tens_size; i += 1) {
        digits[i] = tens[tens_size-i-1];
    }
    forindex (i; digits) {
        result += _parse_digit_value(digits[i], base, i);
    }
    digits = setsize([], tenths_size);
    for (var i=0; i < tenths_size; i += 1) {
        digits[i] = tenths[i];
    }
    forindex (i; digits) {
        result += _parse_digit_value(digits[i], base, -i-1);
    }
    return result*multiplicand;
};

var _parse_digit_value = func(digit, base, position) {
    if (string.isalpha(digit)) {
        if (string.isupper(digit)) { var offset = `A` - (base > 36 ? 36 : 10) }
        else { var offset = `a` - 10 }
    } elsif (string.isdigit(digit)) {
        var offset = `0`;
    } else die("math.to_base(): bad digit '" ~ chr(digit) ~ "'");
    if (digit - offset >= base) die("math.to_base(): digit value " ~ digit - offset ~ " too high for base " ~ base);
    return (digit - offset)*pow(base, position);
};

##
# Print a value in the base to a string (that is returned).
# <prec> is the maximum number of decimal digits to print.
#
var print_base = func(value, base, prec=16) {
    var result = "";
    #FIXME: special case that shouldn't exist?
    if ((var x = ln(value) / ln(base)) == int(x)) {
        position = x;
    } else {
        position = 0;
        while (pow(base, position) < value) {
            position += 1;
        }
        position -= 1;
    }
    var remaining = value;
    while (remaining > 0) {
        var digit = _figure_digit_value(remaining, base, position);
        remaining -= digit*int(pow(base, position));
        position -= 1;
        if (digit >= 10 and digit <= 35) result ~= chr(digit + `a` - 10);
        elsif (digit <= 9) result ~= digit;
        else result ~= chr(digit + `A` - 36);
        if (position <= -prec) #avoid repeating decimals like 1/3=1.3333
            break;
    }
    for (position > 0 ? position : 0; position >= 0; position -= 1)
        result ~= "0";
    print(result);
    return result;
};

var _figure_digit_value = func(remaining, base, position) {
    var place_value = int(pow(base, position));
    var digit_value = (remaining - mod(remaining, place_value)) / place_value;
    #debug.dump("remaining: ", remaining, "position: ", position, "mod: ", mod(remaining, place_value), "place_value: ", place_value, "digit_value: ", digit_value);
    return digit_value;
};

##
# Automatically figure out the base and parse the string and return the value
# <default> is the default base to use if we &can't figure it out
#
# Recognized forms:
#   0b{digits}  - binary
#   0x{digits}  - hexadecimal
#
var parse = func(String, default=10) {
    var base = default;
    var start = end = 0;
    var s = size(String);
    if (String[0] == `0`) {
        if (s > 2 and String[1] == `x`) {
            base = 16; start = 2;
        } elsif (s > 2 and String[1] == `b`)
            base = 16; start = 2;
        }
    }
    to_base(String[start:end], base);
};
Thanks,
Philosopher
(inactive but lurking occasionally...)
Philosopher
 
Posts: 1590
Joined: Sun Aug 12, 2012 6:29 pm
Location: Stuck in my head...
Callsign: AFTI
Version: Git
OS: Mac OS X 10.7.5

Re: Extensions to Nasal (was: Extension to math.nas)

Postby Philosopher » Sat Feb 09, 2013 5:20 pm

I would like to disuss a couple things.

I want to make fully functional get/setlocalprop functions for my joystick (fully functional as in they accept the same type of arguments). I do not have my previous attempt on me (which worked but was a bad hack), but here is my current attempt which doesn't seem to work!
Code: Select all
var Joystick = [cmdarg().getParent().getPath()~"/"];
var makepath = func(prefix, arg) string.normpath(Joystick[0], string.join("/", arg));
getlocalprop = func {return getprop(makepath(Joystick[0], arg)) };
setlocalprop = func {return setprop(makepath(Joystick[0], arg[0:-2]), arg[-1]) };

I only had a brief period to test it, but I think my last argument (the part that is not the path, but the value to set) got integrated into the path somehow.

Next, I have a proposal. Would it be possible to adapt foreach/forindex to work with hashes? I assume it would require adaption to the VM mainly, but I have not researched it enough.
For example, forindex of a hash would iterate through its keys and foreach through its values. Also I thought that I read in the literature that one could mutate a vector by assigning to its foreach variable, but when I tried this nothing changed:
Code: Select all
var vec = [1,2,3,4,5];
foreach (var v; vec)
    v -= 1;

I could be wrong about it though, it isn't mentioned in sample.nas. EDIT: perhaps I was led astray by this: http://www.mail-archive.com/flightgear- ... 07569.html (which is also on the wiki). It would be cool though...

Also, I think these would be useful (in the right place ;)) and might deprecate the comment at the top ;):
Code: Select all
    getNodeValue : func {
        if (!size(arg)) return me.getValue();
        var _node = _getNode(me._g, arg);
        return _node==nil ? nil : wrap(_getValue(_node, []));
    },

    getSibling     : func wrap(_getChild(_getParent(me._g, []), arg);
    getSilbings    : func wrap(_getChilder(_getParent(me._g, []), arg);


And could someone point me to FGNasalSys, specifically the setlistener and removelistener? I was thinking it would be awfully convenient to be able to do this (though it might violate Nasal's sense of "me"):
Code: Select all
setlistener("/foo", func {
    removelistener(me);
});

EDIT: didn't look far enough, just need to look in the 1000s!
Last edited by Philosopher on Wed Feb 27, 2013 8:20 pm, edited 1 time in total.
Thanks,
Philosopher
(inactive but lurking occasionally...)
Philosopher
 
Posts: 1590
Joined: Sun Aug 12, 2012 6:29 pm
Location: Stuck in my head...
Callsign: AFTI
Version: Git
OS: Mac OS X 10.7.5

Re: Extensions to Nasal (was: Extension to math.nas)

Postby Philosopher » Sun Feb 10, 2013 4:25 pm

This will (probably) avoid a stack overflow when dumping the global namespace (excerpt from debug.string(), around line 214):
Code: Select all
   } 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 level = 0;
                while(caller(level) != nil) level += 1;
                level -= 1; var the_globals = caller(level)[0];

      var k = keys(o);
      var s = "";
      forindex (var i; k) {
         if (k[i] == the_globals) continue; #avoid the recursive global namespace
         s ~= (i == 0 ? "" : ", ") ~ _dump_key(k[i]) ~ ": " ~ debug.string(o[k[i]]);
      }
      return _brace("{") ~ " " ~ s ~ " " ~ _brace("}");
Thanks,
Philosopher
(inactive but lurking occasionally...)
Philosopher
 
Posts: 1590
Joined: Sun Aug 12, 2012 6:29 pm
Location: Stuck in my head...
Callsign: AFTI
Version: Git
OS: Mac OS X 10.7.5

Re: Extensions to Nasal (was: Extension to math.nas)

Postby Philosopher » Mon May 06, 2013 6:01 pm

Ah scratch that, try this instead:
Code: Select all
   } 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_key(k[i]) ~ ": " ~ o[k[i]] == globals ? "globals" : debug.string(o[k[i]]);
      }
      return _brace("{") ~ " " ~ s ~ " " ~ _brace("}");


Here's a couple fixed functions in globals.nas (for the record, the defined implementation was really screwed up, like my debug.string was above):
Code: Select all
var interpolate = func(node, val...) {
    if(isa(node, props.Node)) var node = node._g;
    elsif(typeof(node) != "scalar" and typeof(node) != "ghost")
        die("bad argument to interpolate()");
    _interpolate(node, val);
}

var setlistener = func(node, fn, init = 0, runtime = 1) {
    if(isa(node, props.Node)) var node = node._g;
    elsif(typeof(node) != "scalar" and typeof(node) != "ghost")
        die("bad argument to setlistener()");
    var id = _setlistener(node, func(chg, lst, mode, is_child) {
        fn(props.wrapNode(chg), props.wrapNode(lst), mode, is_child);
    }, init, runtime);
    if(__.log_level <= 2) {
        var c = caller(1);
        printf("setting listener #%d in %s, line %s", id, c[2], c[3]);
    }
    return id;
}

var fgcommand = func(cmd, node=nil) {
    if(isa(node, props.Node)) var node = node._g;
    elsif (typeof(node) == 'hash')
        var node = props.Node.new(node);
    _fgcommand(cmd, node);
}

var defined = func(sym) {
    if (contains(caller(1)[0], sym)) return 1;
    var fn = caller(1)[1]; var l = 0;
    while((var frame = closure(fn, l)) != nil) {
        if(contains(frame, sym)) return 1;
        l += 1;
    }
    return 0;
}
Thanks,
Philosopher
(inactive but lurking occasionally...)
Philosopher
 
Posts: 1590
Joined: Sun Aug 12, 2012 6:29 pm
Location: Stuck in my head...
Callsign: AFTI
Version: Git
OS: Mac OS X 10.7.5


Return to Nasal

Who is online

Users browsing this forum: No registered users and 4 guests