I wrote:
> Finally, there's now a slight esoteric bind() function

Which sounds weird, I know.  But here's an application:

Languages like Perl and Ruby have a really useful syntax (which they
got from the shell) for "interpolating" variables into strings, so
that for simple output you don't have to bother with stuff like
sprintf() or concatenating strings.  You just do something like:

  "My field value is currently: $fieldVal"

Perl can do that because the support is built into the syntax of the
language.  While it would be nice to have an "interpolate()" function
that you could write in the script itself, this generally isn't
possible because the $fieldVal variable is in the calling function,
not the interpolate() function.  Really, that string expression is a
bit of code that would have be executed dynamically when it appears in
the source file.  Wouldn't it be great if we could dynamically
generate code like that using the caller's scope?

Well, you can.  The implementation is attached.  It supports
interpolation of "$variable" expressions, and even 100% arbitrary
Nasal expressions (something even perl can't do) with the use of curly
brackets: "${ want_number ? 12345 : 'not a number!' }".

And it's amazingly easy.  Almost all of the complexity is in the
parser.  The magic required to handle the calling function's variables
and compile the appropriate function is about 10 lines at the top of
the file.  And the result, being a compiled function that
automatically evaluates the right string, is about as efficient as it
possibly can be (modulo the O(N) copying involved in all the ~
operators for long strings).

You can do stuff like interpolate("string to interpolate") for
simplicity, or use interpolater("string to interpolate") to generate a
function that you can call iteratively whenever you want:

  ip = interpolater("the value of i is $i\n");
  for(var i=0; i<10; i+=1) { print(ip()); } # prints 1, 2, ... 10

Anyway, like I said, this isn't completely appropos to stuff people
will want to do in FlightGear (the original idea was to use this as
the engine for the right-hand-side of a regex substitution).  But it
was awfully cool.

For fun, I wrote up a very similar thing that generates a PHP-like web
page template engine and it's similarly tiny.  Nasal has no I/O
library yet, so I can't use this for an actual CGI script, but the
exercise was promising.

Anyway, have fun with it.  I just kinda wanted to show it off. :)

Andy




##
# Generates a callable function object from an interpolation string.
# Takes a function object as the lexical environment in which the
# interpolated expressions should be evaluated, in almost all cases
# (except interpolate() below) this will be the caller's environment
# and can/should be ignored.
#
interpolater = func(s, lexenv=nil) {
    var elems = interparse(s);
    var syms = {};
    var expr = "";
    if(lexenv==nil) { var f = caller(); lexenv = bind(func{}, f[0], f[1]); }
    for(var i=0; i<size(elems); i+=1) {
        var sym = "__intersym" ~ i;
        syms[sym] = elems[i];
        expr ~= (i == 0 ? "" : "~") ~ sym;
        if((i+=1) < size(elems)) {
            expr ~= "~(" ~ elems[i] ~ ")";
        }
    }
    return bind(compile(expr), syms, lexenv);
}

##
# Uses an interpolater to evaluate a string in the lexical environment of
# the calling function.
#
interpolate = func(s) {
    var frame = caller();
    var ip = interpolater(s, bind(func{}, frame[0], frame[1]));
    return ip();
}

# Returns true if the character is legal in a symbol name.  Note that
# this allows symbols to start with numbers, which is not a legal
# symbol name in Nasal.  However, other idioms (regex substitutions)
# typically allow symbols like $1, so we do here, too.
symchr = func(c) {
    if(c >= 48 and c <= 57)  { return 1; } # 0-9
    if(c >= 65 and c <= 90)  { return 1; } # A-Z
    if(c >= 97 and c <= 122) { return 1; } # a-z
    if(c == 95) { return 1; }              # _
    return 0;
}
    
# Splits a string into a list of literal strings interleaved with nasal
# expressions.  Even indices (0, 2, ...) are always (possibly zero-length)
# strings.  Odd indices are always nasal expressions.  Does not handle
# escaping of $ characters, so you currently need to do ${'$'} if the $ is
# followed by a '{' or a symbol character ([a-zA-Z0-9_]).
interparse = func(s) {
    var str0 = 0;
    var list = [];
    for(var i=0; i<size(s)-1; i+=1) {
        if(strc(s, i) == strc("$")) {
            if(strc(s, i+1) == strc("{")) {
                var count = 0;
                var open = i+1;
                var close = -1;
                for(var j=i+2; j<size(s); j+=1) {
                    if(strc(s, j) == strc("{")) {
                        count += 1;
                    } elsif(strc(s, j) == strc("}")) {
                        if(count == 0) { close = j; break; }
                        count -= 1;
                    }
                }
                if(close > 0) {
                    append(list, substr(s, str0, i-str0));
                    append(list, substr(s, open+1, close-open-1));
                    str0 = close+1;
                    i = close;
                }
            } else {
                for(var j=i+1; j<size(s) and symchr(strc(s, j)); j+=1) {}
                if(j - i > 1) {
                    append(list, substr(s, str0, i-str0));
                    append(list, substr(s, i+1, j-i-1));
                    str0 = j;
                    i = j-1;
                }
            }
        }
    }
    if(str0 < size(s)) { append(list, substr(s, str0)); }
    return list;
}
_______________________________________________
Flightgear-devel mailing list
Flightgear-devel@flightgear.org
http://mail.flightgear.org/mailman/listinfo/flightgear-devel
2f585eeea02e2c79d7b1d8c4963bae2d

Reply via email to