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