Nasal Meta-Programming: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
(contributed by Philosopher)
 
m (Use Nasal highlighter)
 
(3 intermediate revisions by 2 users not shown)
Line 5: Line 5:
I called my module "gen.nas" and though it started with "generators", it really covers anything I thought to create and the name is now more of a convenience (I don't like typing) and meant to imply a fair amount of ambiguity. I based it off of two different methods to load modules: the driver.nas '''import''' function and FlightGear's security-free '''io.load_nasal''' function. For those who don't know, Andy Ross (the creator of Nasal) made a repository on GitHub (see [https://github.com/andyross/nasal/tree/master/] that has some helpful Nasal libraries, one of which (driver.nas) provides an import() function which duplicates the global namespace to prevent modules loaded by import() write access to it, though they can still use extension functions like find(). I knew that I wanted to have access to the global namespace, which would preclude use of import(), but I also liked the idea of an EXPORTS vector to control (or at least pretend to control) what could be used outside of my module, as well as allowing for some good example functions to make use of it. So in the end, it needs to be loaded using a mixture of the two ;).
I called my module "gen.nas" and though it started with "generators", it really covers anything I thought to create and the name is now more of a convenience (I don't like typing) and meant to imply a fair amount of ambiguity. I based it off of two different methods to load modules: the driver.nas '''import''' function and FlightGear's security-free '''io.load_nasal''' function. For those who don't know, Andy Ross (the creator of Nasal) made a repository on GitHub (see [https://github.com/andyross/nasal/tree/master/] that has some helpful Nasal libraries, one of which (driver.nas) provides an import() function which duplicates the global namespace to prevent modules loaded by import() write access to it, though they can still use extension functions like find(). I knew that I wanted to have access to the global namespace, which would preclude use of import(), but I also liked the idea of an EXPORTS vector to control (or at least pretend to control) what could be used outside of my module, as well as allowing for some good example functions to make use of it. So in the end, it needs to be loaded using a mixture of the two ;).


The whole file can be viewed [[here|here]], but I will copy each section over to here when I explain it. At the top there are some comments which I covered already:
The whole file can be viewed [http://codepad.org/DUNWJfX9 here] (updated 05/2013), but I will copy each section over to here when I explain it. At the top there are some comments which I covered already:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
# gen.nas -- namespace "gen"
# gen.nas -- namespace "gen"


Line 17: Line 17:
# (driver.nas's import would not work, but FlightGear's io.load_nasal
# (driver.nas's import would not work, but FlightGear's io.load_nasal
# would work; which is funny, given that I am using EXPORTS :D).
# would work; which is funny, given that I am using EXPORTS :D).
</suntaxhighlight>
</syntaxhighlight>


Just a disclaimer here: I was writing this module for fun, and much of it is untested, but I have caught a few errors or improvements that I will point out as we are touring it.
Just a disclaimer here: I was writing this module for fun, and much of it is untested, but I have caught a few errors or improvements that I will point out as we are touring it.
Line 23: Line 23:
If you look at the top of the file, I define a minimal EXPORTS vector. What happened to my other functions??
If you look at the top of the file, I define a minimal EXPORTS vector. What happened to my other functions??


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var EXPORTS = ["_the_globals", "_global_func",
var EXPORTS = ["_the_globals", "_global_func",
               "public", "namespace", "global",
               "public", "namespace", "global",
Line 32: Line 32:
Well I decided to make a fun hack so I did not have to manually enter every function I wanted to make public. So I made a public() function!
Well I decided to make a fun hack so I did not have to manually enter every function I wanted to make public. So I made a public() function!


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
# For each symbol created by the function <fn> or
# For each symbol created by the function <fn> or
# for each symbol in <fn> (if it is either a hash
# for each symbol in <fn> (if it is either a hash
Line 73: Line 73:
</syntaxhighlight>
</syntaxhighlight>


So I try and split whatever variable we receive up into '''names''' and '''hash''', where hash holds both variable_name and value whereas names only holds the names. It's fairly condensed code, but it should be understandable to the reader knowing Nasal. Note the funny return of a foreach loop, though! It turns out that this loop (and forindex, whichbis equivalent) leaves the vector on the stack, aka it "returns" that value. If this wacky behavior changes (I think a comment in the code mentioned taking the vector off of the stack), then uncomment the alternative code. As an excercise for the reader: Given a manual return of the names vector (i.e. no return foreach(){} hack), what is a really easy optimization to make instead of a foreach/append() loop?
So I try and split whatever variable we receive up into '''names''' and '''hash''', where hash holds both variable_name and value whereas names only holds the names in a vector. It's fairly condensed code, but it should be understandable to the reader knowing Nasal. Note the funny return of a foreach loop, though! It turns out that this loop (and forindex, which is equivalent) leaves the vector on the stack, aka it "returns" that value. If this wacky behavior changes (I think a comment in the code mentioned taking the vector off of the stack), then uncomment the alternative code. As an excercise for the reader: Given a manual return of the names vector (i.e. no return foreach(){} hack), what is a really easy optimization to make instead of a foreach/append() loop?


Next I have two more functions that really are not that well thought through, but should work for simple use cases:
Next I have two more functions that really are not that well thought through, but should work for simple use cases:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
# Basically the same. FIXME: should we use bind() instead?
# Basically the same. FIXME: should we use bind() instead?
var global = func(fn) {
var global = func(fn) {
Line 117: Line 117:
Note that these rely on the assumption that we can access and modify the global namespace. Let me backtrack and cover a different part of the file, where we try and capture the global namespace and put it under a variable called '''_the_globals'''.
Note that these rely on the assumption that we can access and modify the global namespace. Let me backtrack and cover a different part of the file, where we try and capture the global namespace and put it under a variable called '''_the_globals'''.


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var _level = 0;
var _level = 0;
while (closure(caller(0)[1], _level)) != nil) _level += 1;
while (closure(caller(0)[1], _level)) != nil) _level += 1;
var _the_globals = caller(_level-=1);
var _the_globals = closure(caller(0)[1], _level-=1);
var _global_func = bind(func{}, _the_globals);
var _global_func = bind(func{}, _the_globals);
bind = (func{
var _bind = bind;
func(fn, namespace, enclosure=nil) {
if (fn != _global_func)
return _bind(fn, namespace, enclosure);
#protect it from getting rebound by returning an equivalent but duplicate function:
return _bind(_bind(func{}, _the_globals), namespace, enclosure);
}
})();
</syntaxhighlight>
</syntaxhighlight>


Line 128: Line 137:
One bothersome thing about namespaces in Nasal is that they are fundamentally tied to functions, not the hashes that make up the namespace's variables. It turns out that it is easy to give a function both an outer namespace and a namespace to run in (using '''bind''' and '''call''' respectively), but it is ''really'' hard to give it an outer-outer namespace, due to the chain of functions that needs to be created and the fact that you don't know where you would find the correct function. I got really frustrated one day with this, but the next week I hit upon the solution: '''make''' the function! What the cautious programmer has to do is manually create a chain of functions that link to the next one and represent the correct namespace. The '''bind''' function takes two arguments: a namespace and an enclosure. It must be emphasized that there are three namespace-related parts of a function. The first namespace is stored with the function and is the outer namespace or the first level of recursion for looking up variables. This is the second argument to bind(). The next namespace is '''not''' stored with the function and is only set when the function is called. This is the namespace that the function runs in and is the fourth argument to call, or a new hash otherwise. This is where variables set using the '''var''' keyword go. The last attribute, which is also stored with the function, is a function from which to retrieve both another namespace and another function in the chain (or nil if there is none). It turns out that we can manually create a new function to put into this chain, which is what I do with these functions:
One bothersome thing about namespaces in Nasal is that they are fundamentally tied to functions, not the hashes that make up the namespace's variables. It turns out that it is easy to give a function both an outer namespace and a namespace to run in (using '''bind''' and '''call''' respectively), but it is ''really'' hard to give it an outer-outer namespace, due to the chain of functions that needs to be created and the fact that you don't know where you would find the correct function. I got really frustrated one day with this, but the next week I hit upon the solution: '''make''' the function! What the cautious programmer has to do is manually create a chain of functions that link to the next one and represent the correct namespace. The '''bind''' function takes two arguments: a namespace and an enclosure. It must be emphasized that there are three namespace-related parts of a function. The first namespace is stored with the function and is the outer namespace or the first level of recursion for looking up variables. This is the second argument to bind(). The next namespace is '''not''' stored with the function and is only set when the function is called. This is the namespace that the function runs in and is the fourth argument to call, or a new hash otherwise. This is where variables set using the '''var''' keyword go. The last attribute, which is also stored with the function, is a function from which to retrieve both another namespace and another function in the chain (or nil if there is none). It turns out that we can manually create a new function to put into this chain, which is what I do with these functions:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
# Lexically bind the function to the caller
# Lexically bind the function to the caller
var bind_to_caller = func(fn, level=1) {
var bind_to_caller = func(fn, level=1) {
Line 143: Line 152:
# first is the top-level one, after globals). Each
# first is the top-level one, after globals). Each
# item can be a scalar (name of the sub-namespace)
# item can be a scalar (name of the sub-namespace)
# or a hash (the namespace itself).
# or a hash (the namespace itself). If create is
var bind_to_namespaces = func(fn, namespaces) {
# true, then any names that are not present in a
  if (typeof(namespace) == 'scalar')
# namespace are created as a new hash; else this
      var namespaces = split(".", namespaces);
# returns nil.
  var save = pop(namespaces);
var bind_to_namespaces = func(fn, namespaces, create=1) {
  var namespace = _the_globals;
if (typeof(namespace) == 'scalar')
  var _fn = _global_func;
var namespaces = split(".", namespaces);
  foreach (var i; namespaces) {
var namespace = _the_globals;
      if (typeof(i) == 'scalar') {
var save = pop(namespaces);
        if (!contains(namespace, i))
var _fn = _global_func;
            namespace[i] = {};
foreach (var i; namespaces) {
        var i = namespace[i];
if (typeof(i) == 'scalar') {
      }
if (!contains(namespace, i))
      var _fn = bind(func{}, namespace = i, _fn);
if (create)
  }
namespace[i] = {};
  if (typeof(save) == 'scalar') {
else return;
      if (!contains(namespace, save))
var i = namespace[i];
        namespace[save] = {};
}
      var save = namespace[save];
var _fn = bind(func{}, var namespace = i, _fn);
  }
}
  bind(fn, save, _fn);
if (typeof(save) == 'scalar') {
if (!contains(namespace, save))
if (create)
namespace[save] = {};
else return;
var save = namespace[save];
}
bind(fn, save, _fn);
};
};
</syntaxhighlight>
</syntaxhighlight>


Line 173: Line 190:
Onto some other utilities:
Onto some other utilities:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var _defined = func(sym) {
var _defined = func(sym) {
   # We must first check the frame->locals hash/namespace
   # We must first check the frame->locals hash/namespace
Line 189: Line 206:
var _ldefined = func(sym) {
var _ldefined = func(sym) {
   return contains(caller(1)[0], sym);
   return contains(caller(1)[0], sym);
};
var _fix_rest = func(sym) {
    var val = caller(1)[0][sym];
    if (typeof(val) == 'vector' and
        size(val) == 1 and
        typeof(val[0]) == 'vector')
        caller(1)[0][val] = val[0];
};
};
</syntaxhighlight>
</syntaxhighlight>


Here I have "defined" and "locally defined" functions, I just made them private because they will probably be defined by a library like globals.nas does for FlightGear and/or are one-liners than can be embedded. Note, however, that the defined function in globals.nas is not correct! It purely checks caller entries instead of closure entries, the latter of which are what actually represent the inheritance of namespaces. This is the correct version, note the use of '''caller(1)[ 1]''' to get the function that is currently running and from which we can access the chain of namespaces via closure(). Also note the check of '''caller(1)[0]''' to make sure that it wasn't defined in the local namespace (aka using the var keyword).
Here I have "defined", "locally defined", and "fixup the rest vector" functions. I just made them private because they will probably be defined by a library like globals.nas does for FlightGear and/or are one-liners than can be embedded. Note, however, that the defined function in globals.nas is not correct! It purely checks caller entries instead of closure entries, the latter of which are what actually represent the inheritance of namespaces. This is the correct version, note the use of '''<nowiki>caller(1)[1]</nowiki>''' to get the function that is currently running and from which we can access the chain of namespaces via closure(). Also note the check of '''<nowiki>caller(1)[0]</nowiki>''' to check if it was defined in the local namespace (like by using the var keyword). The last function takes the name of the rest vector (usually arg...) and checks if it consists of a single vector. If so, it replaces the whole vector with just the first element. This allows for functions to call other functions and specify the rest argument as a vector, instead of having to use call() to get the correct arguments.


Now I will move on to higher-level hacking and lesser utilities, all of which are declared using the public() helper so that they automatically get added to the namespace and the EXPORT vector. Note that I won't cover all of them here! Let's start with four of the fun ones:
Now I will move on to higher-level hacking and lesser utilities, all of which are declared using the public() helper so that they automatically get added to the namespace and the EXPORT vector. Note that I won't cover all of them here! Let's start with four of the fun ones:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
   # Create a new hash from the symbools of the caller
   # Create a new hash from the symbools of the caller
   # if they are not listed in the ignore vector.
   # if they are not listed in the ignore vector.
Line 259: Line 283:
These basically allow working with members of an object as local variables. This is particularly useful when you have a gazillion arguments to a constructor function (indeed, they probably all have defaults values!) and they are all named according to their name as the member, so there would be a lot of lines of the form '''m.foo = foo;'''. These helper functions make constructors like that into one-liners. If you have a temporary variable that should not be copied as a member, just include it as an argument:
These basically allow working with members of an object as local variables. This is particularly useful when you have a gazillion arguments to a constructor function (indeed, they probably all have defaults values!) and they are all named according to their name as the member, so there would be a lot of lines of the form '''m.foo = foo;'''. These helper functions make constructors like that into one-liners. If you have a temporary variable that should not be copied as a member, just include it as an argument:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var Warper = {
var Warper = {
     # Create a class to warp an input, giving
     # Create a class to warp an input, giving
Line 271: Line 295:
</syntaxhighlight>
</syntaxhighlight>


Please note that new_obj bases its parents vector off of the "me" variable of the caller! For most use cases, this works perfectly well (e.g. calling Warper.new() would base it off of Warper), but it also allows instances of objects based of instances (e.g. Warper.new().new() would have a parents vector of the first Warper.new()) and will not work using brackets (e.g. Warper[ "new" ]() would result in a error, "undefined symbol "new" on line 188 of gen.nas").
Please note that new_obj bases its parents vector off of the "me" variable of the caller! For most use cases, this works perfectly well (e.g. calling Warper.new() would base it off of Warper), but it also allows instances of objects based of instances (e.g. Warper.new().new() would have a parents vector of the first Warper.new()) and will not work using brackets (e.g. Warper["new"]() would result in a error, "undefined symbol "new" on line 188 of gen.nas").


This is a function that automatically associates a list of keys with a list of values:
This is a function that automatically associates a list of keys with a list of values:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
   # Associate respective keys with values stored in the second vector
   # Associate respective keys with values stored in the second vector
   # and return the resulting hash. It is recursive, so something like
   # and return the resulting hash. It is recursive, so something like
Line 297: Line 321:
</syntaxhighlight>
</syntaxhighlight>


It was inspired by the way C extension functions in Nasal are initialized: where a dimple list is specified (like a vector in Nasal) though each receives a name to be accessed by. This extension also allows hashes within hashes, if the list of keys has a vector in itself in which case the first index specifies where to put the sub-hash inside the outer hash, while the other items specify the keys inside of the sub-hash.
It was inspired by the way C extension functions in Nasal are initialized: a simple list is specified (like a vector in Nasal) though each receives a name to be accessed by depending on its index in the list. This extension also allows hashes within hashes, if the list of keys has a vector in itself in which case the first index specifies where to put the sub-hash inside the outer hash, while the other items specify the keys inside of the sub-hash.


Here's another one:
Here's another one:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
   # Make an extension in the namespace, inside any objects
   # Make an extension in the namespace, inside any objects
   # or sub-namespaces specified in objs, with the name
   # or sub-namespaces specified in objs, with the name
Line 323: Line 347:
Like the comment says, it makes a function inside a namespace and any objects only if the namespace exists but the variable doesn't. Notice that I only bind the function to the namespace and globals, why is that?? To take an example, lets consider props.nas which defines a Node class:
Like the comment says, it makes a function inside a namespace and any objects only if the namespace exists but the variable doesn't. Notice that I only bind the function to the namespace and globals, why is that?? To take an example, lets consider props.nas which defines a Node class:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
# $FG_ROOT/Nasal/props.nas
# $FG_ROOT/Nasal/props.nas
var Node = {
var Node = {
Line 338: Line 362:
Consider where Node.getNode and Node.getValues are bound to. Are they bound to the same namespace? Or different ones? Is one bound "inside" the class? Well it turns out that the Nasal VM binds them both to the same place, which is simply inside the props namespace. This is why I don't have to use bind_to_namespaces but simply have to follow the objects and bind it to one namespace. (Note that I originally came up with this function for the purpose of adding a custom extension to props.nas from outside of it!) Also note that this means that bind_to_namespaces is only intended for namespaces within namespaces, not objects/classes within namespaces. This is where a line must be drawn between the two, even though they are all internally hashes, they do have a different use cases and I draw a distinction with my jargon.
Consider where Node.getNode and Node.getValues are bound to. Are they bound to the same namespace? Or different ones? Is one bound "inside" the class? Well it turns out that the Nasal VM binds them both to the same place, which is simply inside the props namespace. This is why I don't have to use bind_to_namespaces but simply have to follow the objects and bind it to one namespace. (Note that I originally came up with this function for the purpose of adding a custom extension to props.nas from outside of it!) Also note that this means that bind_to_namespaces is only intended for namespaces within namespaces, not objects/classes within namespaces. This is where a line must be drawn between the two, even though they are all internally hashes, they do have a different use cases and I draw a distinction with my jargon.


The complete file:
Other features available in the module but not tutorialized yet:
 
* mutable functions
<syntaxhighlight lang="php">
* "macro" functions
# gen.nas -- namespace "gen"
* consolidation of namespaces (actually named accumulate right now): make a one-time expense to reduce timing of hash lookups. Probably not recommended, though.
 
* two harebrained schemes for overloading that are rather inflexible and actually only satisfy two use-cases per function I made.
# Generators and mostly utilities using namespace hacking &c
* duplicate, (recursive) equals, and econtains (extended contains) utilities
# Quickly grew overboard ;)
* a couple classes at the end: Hash, Func, and Class.
 
# Note: the fundamental assertion that _the_globals is *the* globals
# could potentially cause problems depending on the loading method
# (driver.nas's import would not work, but FlightGear's io.load_nasal
# would work; which is funny, given that I am using EXPORTS :D).
 
var EXPORTS = ["_the_globals", "_global_func",
              "public", "namespace", "global",
              "bind_to_caller", "bind_to_namespace",
              "bind_to_namespaces"];
 
var _level = 0;
while (closure(caller(0)[1], _level)) != nil) _level += 1;
var _the_globals = caller(_level-=1);
var _global_func = bind(func{}, _the_globals);
bind = (func{
  var _bind = bind;
  func(fn, namespace, enclosure=nil) {
      if (fn != _global_func)
        return _bind(fn, namespace, enclosure);
      #protect it from getting rebound by returning an equivalent but duplicate function:
      return _bind(_bind(func{}, _the_globals), namespace, enclosure);
  }
})();
var _defined = func(sym) {
  # We must first check the frame->locals hash/namespace
  # (since closure(fn, 0) returns the namespace/closure
  # above it, i.e. PTR(frame->func).func->namespace vs
  # PTR(PTR(frame->func).func->next).func->namespace).
  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;
};
var _ldefined = func(sym) {
  return contains(caller(1)[0], sym);
};
 
# Lexically bind the function to the caller
var bind_to_caller = func(fn, level=1) {
  if (level < 1) return;
  bind(fn, caller(level)[0], caller(level)[1]);
};
# Bind the function to the namespace and then globals
var bind_to_namespace = func(fn, namespace) {
  if (typeof(namespace) == 'scalar')
      var namespace = _the_globals[namespace];
  bind(fn, namespace, _global_func));
};
# Bind the function to each namespace in turn (the
# first is the top-level one, after globals). Each
# item can be a scalar (name of the sub-namespace)
# or a hash (the namespace itself).
var bind_to_namespaces = func(fn, namespaces) {
  if (typeof(namespace) == 'scalar')
      var namespaces = split(".", namespaces);
  var namespace = _the_globals;
  var save = pop(namespaces);
  var _fn = _global_func;
  foreach (var i; namespaces) {
      if (typeof(i) == 'scalar') {
        if (!contains(namespace, i))
            namespace[i] = {};
        var i = namespace[i];
      }
      var _fn = bind(func{}, namespace = i, _fn);
  }
  if (typeof(save) == 'scalar') {
      if (!contains(namespace, save))
        namespace[save] = {};
      var save = namespace[save];
  }
  bind(fn, save, _fn);
};
 
# For each symbol created by the function <fn> or
# for each symbol in <fn> (if it is either a hash
# or vector), add the name of the symbol to the
# caller's EXPORT vector. Returns a vector of the
# added symbols and adds the symbols to the caller's
# local namespace if possible (i.e. when <fn> is not
# a vector).
#
# The anonymous function argument is so that you can
# use exactly the same syntax, versus having to
# convert it to or write in hash-style syntax (after
# all, Nasal just splits off another codegen to handle
# func{}s...)
var public = func(fn) {
  var c = caller(1)[0];
  var names = []; var hash = {};
  if (typeof(fn) == 'func') {
      call(fn, nil, nil, hash);
      var names = keys(hash);
  } elsif (typeof(fn) == 'hash') {
      var names = keys(hash = fn);
  } elsif (typeof(fn) == 'vector') {
      var names = fn;
  } else die("invalid/unrecognized argument to public()");
  foreach(var sym; keys(hash)) {
      c[sym] = hash[sym];
  }
  if (!contains(c, "EXPORTS"))
      c["EXPORTS"] = [];
  return foreach (var sym; names) {
      append(c["EXPORTS"], sym);
  };
  # In case the behavior changes (they are equivalent):
  #foreach (var sym; names) {
  #  append(c["EXPORTS"], sym);
  #};
  #return names;
};
 
# Basically the same. FIXME: should we use bind() instead?
var global = func(fn) {
  var c = _the_globals;
  var names = []; var hash = {};
  if (typeof(fn) == 'func') {
      call(fn, nil, nil, hash);
      var names = keys(hash);
  } elsif (typeof(fn) == 'hash') {
      var names = keys(hash = fn);
  } else die("invalid/unrecognized argument to global()");
  foreach(var sym; keys(hash)) {
      c[sym] = hash[sym];
  }
  return names;
};
 
# Runs the function in the namespace, like public().
# Essentially says that the function "describes"
# that namespace (after it runs, of course).
# I could write down a dozen analogies for this...
# Usage:
#  gen.namespace("foo", func {
#      ... #your code here, just write normally
#  });
# Which roughly translates into C++ as:
#  namespace foo
#  {
#      ... //your code here
#  }
var namespace = func(namespc, fn) {
  if (typeof(namespc) == 'scalar')
      var namespc = _the_globals[namespc];
  bind(fn, _the_globals);
  call(fn, nil, nil, namespc);
};
 
public(func {
  # Turns a scalar bit of code into a function and binds it
  # to the namespace inside the enclosure.
  var make_code = func(code, namespace, enclosure=nil) {
      if (typeof(code) == 'func') return bind(code, namespace, enclosure);
      if (typeof(code) != 'scalar') die("invalid argument to make_code()");
      code = compile(code);
      code = bind(code, namespace, enclosure);
      return code;
  };
 
  # Create a new hash from the symbools of the caller
  # if they are not listed in the ignore vector.
  var new_hash = func(ignore...) {
      var c = caller(1)[0];
      var m = {};
      foreach (SYM; var sym; keys(c)) {
        foreach (var s; ignore) {
            if (sym == s) continue SYM;
        }
        m[sym] == c[sym];
      }
      return m;
  };
 
  # Create a new object instance (similar to above,
  # but uses the 'me' symbol for the parents vector
  # and ignores the arg and me symbols)
  var new_obj = func(ignore...) {
      var c = caller(1)[0];
      var m = { parents: [c.me] };
      foreach (SYM; var sym; keys(c)) {
        if (sym == "me" or sym == "arg") continue SYM;
        foreach (var s; ignore) {
            if (sym == s) continue SYM;
        }
        m[sym] == c[sym];
      }
      return m;
  };
 
  #ifdef globals.props.Node:
  if (contains(_the_globals, "props") and contains(_the_globals.props, "Node")) {
  # Same as new_hash but returns a props.Node object using setValues()
  var new_prop = func(ignore...) {
      var c = caller(1)[0];
      var m = {};
      foreach (SYM; var sym; keys(c)) {
        foreach (var s; ignore) {
            if (sym == s) continue SYM;
        }
        m[sym] == c[sym];
      }
      return props.Node.new(m);
  };
  } #endif
 
  # The opposite of new_hash, this takes a hash and expands the key/values
  # contained in it into the caller (overwriting any possible duplicates)
  var expand_hash = func(hash, ingore...) {
      var c = caller(1)[0];
      foreach (SYM; var sym; keys(hash)) {
        foreach (var s; ignore) {
            if (sym == s) continue SYM;
        }
        c[sym] == hash[sym];
      }
      return c;
  };
 
  # Make an extension in the namespace, inside any objects
  # or sub-namespaces specified in objs, with the name
  # of fname, and where fn is written like it was in the file
  # (i.e. no prefixing of the namespace before every variable).
  # It only defines it if the namespace exists and a variable
  # with the name does not exist or is nil.
  var provide_extension = func(namespc, fname, fn, objs...) {
      if (typeof(namespc) == 'scalar')
        var _n = _the_globals[namespc];
      foreach (var name; objs) {
        if (_n == nil) return;
        _n = _n[name];
      }
      if (_n[fname] != nil) return; #only define it if it does not exist
      if (typeof(fn) == 'scalar') fn = compile(fn);
      _n[fname] = bind(fn, _the_globals[namespc], _global_func);
  };
 
  # Associate respective keys with values stored in the second vector
  # and return the resulting hash. It is recursive, so something like
  # this works as syntactic sugar (the first index specifies the name):
  #  var clamp_template = ["property", ["range", "min", "max"]];
  #  var aileron = ["/controls/flight/aileron", [-1, 1]];
  #  vec2hash(clamp_template, aileron) == {
  #      "property": "/controls/flight/aileron",
  #      range: { "min": -1, "max": 1 } }
  var vec2hash = func(_keys, list) {
      var result = {};
      forindex (var i; _keys) {
        if (typeof(_keys[i]) == 'vector') {
            result[_keys[i][0]] = list2hash(_keys[i][1:], list[i]);
        } elsif (typeof(_keys[i]) == 'scalar') {
            result[_keys[i]] = list[i];
        }
      };
      return result;
  };
 
  # Self explanatory:
  var has_module = func(name)
      return (contains(_the_globals, name)
              and typeof(_the_globals[name] == 'hash'));
  var has_extension = func(name)
      return (contains(_the_globals, name)
              and typeof(_the_globals[name] == 'func'));
  var has_name = func(name)
      return contains(_the_globals, name);
 
  # Returns the variable if it matches the type or
  # <default> if not. Useful for optional arguments.
  var value = func(variable, default=nil)
      return typeof(variable) == typeof(default) ? variable : default;
 
  # "Wrap" a builtin function (Func) for OOP purposes
  var wrap = func (Func, cond, fn) {
      if (typeof(fn) == 'scalar')
        fn = compile('func(n) {'
            'return n.'~fn~ #make sure it is a method call
        '}')(); #the () is to make sure we get the new func we made!
      if (typeof(cond) == 'func') {
        return func {
            if (call(cond, arg))
              return call(fn, arg);
            return call(CFunc, arg);
        }
      } elsif (typeof(cond) == 'hash') {
        return func {
            if (isa(arg[0], cond))
              return call(fn, arg);
            return call(CFunc, arg);
        }
      } else die();
  };
 
  # @brief Returns a deep-copy (duplicate) of the object. Scalars, functions, and ghosts are left as-is in leu of a) a way to decompile functions and b) a good test to determine numbers versus strings.
  # @param obj The object to duplicate
  # @param depth If not nil, the number of levels to keep copying
  #              (0 return the current object as-is).
  # @param parents Whether or not to duplicate out the parents vector of a hash
  var dup = func(obj, depth=nil, parents=0) {
      var t = typeof(obj);
      if (depth == 0 or obj == nil) return obj;
      if (t == 'func' or t == 'scalar' or t == 'ghost')
        return obj;
      if (t == 'vector')
        if (depth == 1)
            return obj~[]; #simple enough ;-)
        else {
            var m = [];
            foreach (var i; obj)
              append(m, dup(i, depth == nil ? nil : depth-1, parents));
            return m;
        }
      if (t == 'hash') {
        var m = {};
        foreach (var i; keys(obj)) {
            if (i == "parents" and !parents) continue;
            m[i] = dup(obj[i], depth == nil ? nil : depth-1);
        }
        return m;
      }
      else die("unknown type: "~t);
  };
 
  # Overload a function. Takes a list of entries with two
  # fields per item:
  #  * list of argument types in order (vector)
  #  * function to call (func)
  var overload = func(list...) {
      return func {
        foreach (DESC; var desc; list) {
            if (size(desc[0]) != size(arg)) continue;
            forindex (TYPE; var i; desc[0]) {
              if (typeof(desc[0][i]) == 'scalar'
                  ? typeof(arg[i]) != desc[0][i]
                  : !isa(arg[i], desc[0][i]))
                  continue DESC;
            }
            return call(desc[1], arg);
        }
      };
  };
 
  # A small wrapper to create 'macros', functions that are run in the caller
  # an thus have direct access to any local variables (they cannot use
  # break/continue/return!)
  var new_macro = func(fn) {
      if (typeof(fn) == 'scalar')
        fn = compile(fn, "<macro>");
      if (typeof(fn) != 'func')
        die("invalid argument to gen.new_macro()");
      return func {
        # First we "transport" the function to the right enclosure/outer namespace
        bind(fn, {}, caller(1)[1]);
        # An then we call it in the namespace of the caller
        # (i.e. call it "inline", without changing namespaces)
        return call(fn, arg, nil, caller(1)[0]);
      };
  };
 
  # Another closure() hack to generate a "mutatable"
  # function. Redefine the function using gen.mutate_func().
  # Note that closure gets redefined to return the
  # closure of the current function that currently
  # serves as the base for the mutable object, but
  # using closure(mutable, -1) will fetch the closure
  # of this. This does NOT preserve named arguments
  # (as it relies of the arg vector)!
  var mutable_func = func(fn=nil) {
      var fn = value(fn, func{});
      var ret = func {
        # Comment out this wrapping of caller
        # if you do not want the performance
        # impact, etc.
        var _caller = caller;
        caller = func(level=1) {
            var _c = [];
            var l = 0;
            while ((var c = _caller(l+=1)) != nil and l<level)
              append(_c, c);
            foreach (var l; _c)
              if (l[1] == ret) return _caller(level+1);
            return _caller(level);
        }
        call(fn, arg);
        caller = _caller; #undo our changes
      };
      closure = (func{
        var _closure = closure;
        func(fn, level=0) {
            if (fn == ret)
              return _closure(fn, level+1);
            return _closure(fn, level);
        };
      })();
      return ret;
  };
  var mutate_func = func(mutable, new_fn=nil) {
      var new_fn = value(new_fn, func{});
      closure(mutable, -1).fn = new_fn;
  };
 
  # Captures a "frame" of caller and closure entries starting from
  # the first entry (1 or 0) and going until it returns nil
  var capture_frame = func {
      var result = [[], []]; var level = 0;
      while ((var c = caller(level+=1)) != nil) {
        append(result[0], c);
      }
      var level = -1; var fn=caller()[1];
      while ((var c = closure(fn, level+=1)) != nil) {
        append(result[1], c);
      }
      return result;
  };
 
  # Now dump it :-)
  var dump_frame = func(frame) {
      var result = "";
      forindex(var level; frame[0]) {
        for (var i=0; i<level; i+=1)
            result ~= ". ";
        result ~= "caller '"~frame[0][level][2]~"':  " ~ dump(frame[0][level][1], level, level*2) ~ "\n"
                    ~ dump(frame[0][level], level, level*2) ~ "\n";
      };
      forindex(var level; frame[1]) {
        for (var i=0; i<level; i+=1)
            result ~= ". ";
        result ~= "closure: " ~ dump(frame[1][level], level, level*2) ~ "\n";
      };
  };
 
  var i_str = "  ", #indentation string
  var wrap = 50, #how many columns before we wrap our result
 
  # Our own "pretty dump", tries to be valid Nasal syntax.
  # The returned result does not include any prefix (e.g.
  # indentation) or suffix (e.g. newline, semi-colon).
  # If the line needs to be split, then i_str is appended
  # after the newline for each level. The col argument
  # specifies an offset to start the wrapping at. Any
  # function gets dumped with its id for tracking purposes
  var dump = func(obj, level=0, col=0, prefix="") {
      var t = typeof(obj); var s = size(obj);
      var str = "";
      # A "macro" to do all the work for us. If wrapping is needed,
      # then _joiner_~_str_ is appended, else _sep_~_str_.
      var APPEND = func(_str_, _sep_, _joiner_) {
        var _col_ = size(split("\n", str)[-1]); #current column
        _col_ += (find("\n", str) != -1)*col;  #plus the starting column
        var _s_ = size(split("\n", _str_~_sep_)[-1]);
        if ( (find("\n", _str_) != -1 or #if we have something that needs wrapping
              _col_ + size(split("\n", _str_~_sep_)[-1]) >= wrap)
            and _col_ != 0) { #and we will gain something by it
            str ~= _joiner_~_str_;
        } else {
            str ~= _sep_~_str_;
        }
      }
      var this_indent = prefix; var next_indent = prefix~i_str;
      for (var j=0; j<indent; j+=1) {this_indent~=me.i_str; next_indent~=me.i_str}
      var t_str_joiner = "\"\n"~this_indent~"\"";
      var n_str_joiner = "\"\n"~next_indent~"\"";
      if (t == 'func') {
        # Unfortunately, no decompile, even though that is partly
        # what this is designed for (namely the col argument).
        APPEND("func {", "", "\n");
        APPEND("\"<func", "", "\n");
        APPEND("ptr: "~substr(id(obj), 5)~">", " ", t_str_joiner);
        APPEND("}", "", "\n");
      } elsif (t == 'scalar') {
        if (num(obj) != nil) APPEND(obj~"", "", "\n");
        else {
            var escapes = {
              "\n":'\n',
              "\r":'\r',
              "\\":"\\\\",
              "\"":'\"',
            };
            APPEND("\"", "", "\n");
            foreach (var i; split("", obj)) {
              if (contains(escapes, i))
                  APPEND(escapes[i], "", t_str_joiner);
              else
                  APPEND(i, "", t_str_joiner);
            }
            APPEND("\"", "", "");
        }
      } elsif (t == 'hash') {
        APPEND("{", "", "");
        foreach (var sym; keys(obj)) {
            APPEND(sym~":", " ", "\n"~this_indent);
            var _col_ = size(split("\n", str)[-1]); #current column
            _col_ += (find("\n", str) != -1)*col;  #plus the starting column
            APPEND("", dump(obj[sym], level+1, _col_, prefix)~", ",
                  dump(obj[sym], level+1, size(next_indent))~"\n"~next_indent);
        }
        APPEND("}", " ", "\n"~this_indent);
      } elsif (t == 'vector') {
        APPEND("[", "", "");
        foreach (var sym; obj) {
            var _col_ = size(split("\n", str)[-1]); #current column
            _col_ += (find("\n", str) != -1)*col;  #plus the starting column
            APPEND("", dump(obj[sym], level+1, _col_, prefix)~", ",
                  dump(obj[sym], level+1, size(next_indent))~"\n"~next_indent);
        }
        APPEND("]", " ", "\n"~this_indent);
      } else APPEND("nil", "", "");
      return str;
  },
 
  var find_name = func(obj, fn) {
      var level = -1;
      while ((var ns = closure(fn, level+=1)) != nil) {
        foreach (var sym; keys(ns)) {
            if (id(ns[sym]) == id(obj)) return " name: "~sym;
        }
      }
      return "";
  };
}); #end of public functions
</syntaxhighlight>

Latest revision as of 21:09, 10 November 2013


Recently I created a module full of different hacks, since I was a hobby-ish programmer who had nothing else left to invent in my spare time. In interest as a tutorial, I would like to explain the workings of it, so that maybe you can become a Nasal hacker too!

I called my module "gen.nas" and though it started with "generators", it really covers anything I thought to create and the name is now more of a convenience (I don't like typing) and meant to imply a fair amount of ambiguity. I based it off of two different methods to load modules: the driver.nas import function and FlightGear's security-free io.load_nasal function. For those who don't know, Andy Ross (the creator of Nasal) made a repository on GitHub (see [1] that has some helpful Nasal libraries, one of which (driver.nas) provides an import() function which duplicates the global namespace to prevent modules loaded by import() write access to it, though they can still use extension functions like find(). I knew that I wanted to have access to the global namespace, which would preclude use of import(), but I also liked the idea of an EXPORTS vector to control (or at least pretend to control) what could be used outside of my module, as well as allowing for some good example functions to make use of it. So in the end, it needs to be loaded using a mixture of the two ;).

The whole file can be viewed here (updated 05/2013), but I will copy each section over to here when I explain it. At the top there are some comments which I covered already:

# gen.nas -- namespace "gen"

# Generators and mostly utilities using namespace hacking &c
# Quickly grew overboard ;)

# Note: the fundamental assertion that _the_globals is *the* globals
# could potentially cause problems depending on the loading method
# (driver.nas's import would not work, but FlightGear's io.load_nasal
# would work; which is funny, given that I am using EXPORTS :D).

Just a disclaimer here: I was writing this module for fun, and much of it is untested, but I have caught a few errors or improvements that I will point out as we are touring it.

If you look at the top of the file, I define a minimal EXPORTS vector. What happened to my other functions??

var EXPORTS = ["_the_globals", "_global_func",
               "public", "namespace", "global",
               "bind_to_caller", "bind_to_namespace",
               "bind_to_namespaces"];

Well I decided to make a fun hack so I did not have to manually enter every function I wanted to make public. So I made a public() function!

# For each symbol created by the function <fn> or
# for each symbol in <fn> (if it is either a hash
# or vector), add the name of the symbol to the
# caller's EXPORT vector. Returns a vector of the
# added symbols and adds the symbols to the caller's
# local namespace if possible (i.e. when <fn> is not
# a vector).
#
# The anonymous function argument is so that you can
# use exactly the same syntax, versus having to
# convert it to or write in hash-style syntax (after
# all, Nasal just splits off another codegen to handle
# func{}s...)
var public = func(fn) {
   var c = caller(1)[0];
   var names = []; var hash = {};
   if (typeof(fn) == 'func') {
      call(fn, nil, nil, hash);
      var names = keys(hash);
   } elsif (typeof(fn) == 'hash') {
      var names = keys(hash = fn);
   } elsif (typeof(fn) == 'vector') {
      var names = fn;
   } else die("invalid/unrecognized argument to public()");
   foreach(var sym; keys(hash)) {
      c[sym] = hash[sym];
   }
   if (!contains(c, "EXPORTS"))
      c["EXPORTS"] = [];
   return foreach (var sym; names) {
      append(c["EXPORTS"], sym);
   };
   # In case the behavior changes (they are equivalent):
   #foreach (var sym; names) {
   #   append(c["EXPORTS"], sym);
   #};
   #return names;
};

So I try and split whatever variable we receive up into names and hash, where hash holds both variable_name and value whereas names only holds the names in a vector. It's fairly condensed code, but it should be understandable to the reader knowing Nasal. Note the funny return of a foreach loop, though! It turns out that this loop (and forindex, which is equivalent) leaves the vector on the stack, aka it "returns" that value. If this wacky behavior changes (I think a comment in the code mentioned taking the vector off of the stack), then uncomment the alternative code. As an excercise for the reader: Given a manual return of the names vector (i.e. no return foreach(){} hack), what is a really easy optimization to make instead of a foreach/append() loop?

Next I have two more functions that really are not that well thought through, but should work for simple use cases:

# Basically the same. FIXME: should we use bind() instead?
var global = func(fn) {
   var c = _the_globals;
   var names = []; var hash = {};
   if (typeof(fn) == 'func') {
      call(fn, nil, nil, hash);
      var names = keys(hash);
   } elsif (typeof(fn) == 'hash') {
      var names = keys(hash = fn);
   } else die("invalid/unrecognized argument to global()");
   foreach(var sym; keys(hash)) {
      c[sym] = hash[sym];
   }
   return names;
};

# Runs the function in the namespace, like public().
# Essentially says that the function "describes"
# that namespace (after it runs, of course).
# I could write down a dozen analogies for this...
# Usage:
#   gen.namespace("foo", func {
#       ... #your code here, just write normally 
#   });
# Which roughly translates into C++ as:
#   namespace foo
#   {
#       ... //your code here
#   }
var namespace = func(namespc, fn) {
   if (typeof(namespc) == 'scalar')
      var namespc = _the_globals[namespc];
   bind(fn, _the_globals);
   call(fn, nil, nil, namespc);
};

Note that these rely on the assumption that we can access and modify the global namespace. Let me backtrack and cover a different part of the file, where we try and capture the global namespace and put it under a variable called _the_globals.

var _level = 0;
while (closure(caller(0)[1], _level)) != nil) _level += 1;
var _the_globals = closure(caller(0)[1], _level-=1);
var _global_func = bind(func{}, _the_globals);
bind = (func{
	var _bind = bind;
	func(fn, namespace, enclosure=nil) {
		if (fn != _global_func)
			return _bind(fn, namespace, enclosure);
		#protect it from getting rebound by returning an equivalent but duplicate function:
		return _bind(_bind(func{}, _the_globals), namespace, enclosure);
	}
})();

Very short in length, it checks all the namespaces above this namespace (the caller(0)[1] returns the function that is currently running, aka the one that is creating this namespace, and using closure() on that returns the namespace above it (level=0) or above that (level=1), etc.) until it returns nil, at which case it goes back down one level and caches the assumed "global" namespace. Then we declare an empty function that is bound to the global namespace. This turns out to be very useful later on, with advanced namespace assignment.

One bothersome thing about namespaces in Nasal is that they are fundamentally tied to functions, not the hashes that make up the namespace's variables. It turns out that it is easy to give a function both an outer namespace and a namespace to run in (using bind and call respectively), but it is really hard to give it an outer-outer namespace, due to the chain of functions that needs to be created and the fact that you don't know where you would find the correct function. I got really frustrated one day with this, but the next week I hit upon the solution: make the function! What the cautious programmer has to do is manually create a chain of functions that link to the next one and represent the correct namespace. The bind function takes two arguments: a namespace and an enclosure. It must be emphasized that there are three namespace-related parts of a function. The first namespace is stored with the function and is the outer namespace or the first level of recursion for looking up variables. This is the second argument to bind(). The next namespace is not stored with the function and is only set when the function is called. This is the namespace that the function runs in and is the fourth argument to call, or a new hash otherwise. This is where variables set using the var keyword go. The last attribute, which is also stored with the function, is a function from which to retrieve both another namespace and another function in the chain (or nil if there is none). It turns out that we can manually create a new function to put into this chain, which is what I do with these functions:

# Lexically bind the function to the caller
var bind_to_caller = func(fn, level=1) {
   if (level < 1) return;
   bind(fn, caller(level)[0], caller(level)[1]);
};
# Bind the function to the namespace and then globals
var bind_to_namespace = func(fn, namespace) {
   if (typeof(namespace) == 'scalar')
      var namespace = _the_globals[namespace];
   bind(fn, namespace, _global_func));
};
# Bind the function to each namespace in turn (the
# first is the top-level one, after globals). Each
# item can be a scalar (name of the sub-namespace)
# or a hash (the namespace itself). If create is
# true, then any names that are not present in a
# namespace are created as a new hash; else this
# returns nil.
var bind_to_namespaces = func(fn, namespaces, create=1) {
	if (typeof(namespace) == 'scalar')
		var namespaces = split(".", namespaces);
	var namespace = _the_globals;
	var save = pop(namespaces);
	var _fn = _global_func;
	foreach (var i; namespaces) {
		if (typeof(i) == 'scalar') {
			if (!contains(namespace, i))
				if (create)
					namespace[i] = {};
				else return;
			var i = namespace[i];
		}
		var _fn = bind(func{}, var namespace = i, _fn);
	}
	if (typeof(save) == 'scalar') {
		if (!contains(namespace, save))
			if (create)
				namespace[save] = {};
			else return;
		var save = namespace[save];
	}
	bind(fn, save, _fn);
};

The first one is really easy, we actually do what happens when the Nasal VM sees a func{} expression, though in this case we a recieving a naFunc instead of an naCode. The next one is not complicated either, we bind it to a namespace and then globals (or the assumed globals). The third one is the most tricky. The namespaces argument is either a list of names or hashes or a single string to be split at each dot character. We then save a namespace from the end of the list and process the rest.

We define a temporary variable called _fn that starts out as the _global_func, since we of course want our function to ultimately recurse into the global namespace. Then we reassign it to be a new function (new functions are created by each func{} expression) that is bound to the previous function. This builds the chain of namespaces that we want. One very important thing to note is that using bind(_fn, namespace, _fn) would be wrong, wrong, wrong! It would not create a new function but instead bind _fn to itself, which creates an infinite recursion onto itself which would throw Nasal into an infinite loop as soon as the function tried to use a non-local variable. Big mistake! (that I actually made – oops) Notice how we have to start at the top level and work our way down; this is because each function chains "upward" in its namespaces, so that the "upward" must exist at the time of binding. Also note how we have to save one namespace, this is because the last step requires actually binding the function that we were ultimately trying to bind.

Onto some other utilities:

var _defined = func(sym) {
   # We must first check the frame->locals hash/namespace
   # (since closure(fn, 0) returns the namespace/closure
   # above it, i.e. PTR(frame->func).func->namespace vs
   # PTR(PTR(frame->func).func->next).func->namespace).
   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;
};
var _ldefined = func(sym) {
   return contains(caller(1)[0], sym);
};
var _fix_rest = func(sym) {
    var val = caller(1)[0][sym];
    if (typeof(val) == 'vector' and
        size(val) == 1 and
        typeof(val[0]) == 'vector')
        caller(1)[0][val] = val[0];
};

Here I have "defined", "locally defined", and "fixup the rest vector" functions. I just made them private because they will probably be defined by a library like globals.nas does for FlightGear and/or are one-liners than can be embedded. Note, however, that the defined function in globals.nas is not correct! It purely checks caller entries instead of closure entries, the latter of which are what actually represent the inheritance of namespaces. This is the correct version, note the use of caller(1)[1] to get the function that is currently running and from which we can access the chain of namespaces via closure(). Also note the check of caller(1)[0] to check if it was defined in the local namespace (like by using the var keyword). The last function takes the name of the rest vector (usually arg...) and checks if it consists of a single vector. If so, it replaces the whole vector with just the first element. This allows for functions to call other functions and specify the rest argument as a vector, instead of having to use call() to get the correct arguments.

Now I will move on to higher-level hacking and lesser utilities, all of which are declared using the public() helper so that they automatically get added to the namespace and the EXPORT vector. Note that I won't cover all of them here! Let's start with four of the fun ones:

   # Create a new hash from the symbools of the caller
   # if they are not listed in the ignore vector.
   var new_hash = func(ignore...) {
      var c = caller(1)[0];
      var m = {};
      foreach (SYM; var sym; keys(c)) {
         foreach (var s; ignore) {
            if (sym == s) continue SYM;
         }
         m[sym] == c[sym];
      }
      return m;
   };

   # Create a new object instance (similar to above,
   # but uses the 'me' symbol for the parents vector
   # and ignores the arg and me symbols)
   var new_obj = func(ignore...) {
      var c = caller(1)[0];
      var m = { parents: [c.me] };
      foreach (SYM; var sym; keys(c)) {
         if (sym == "me" or sym == "arg") continue SYM;
         foreach (var s; ignore) {
            if (sym == s) continue SYM;
         }
         m[sym] == c[sym];
      }
      return m;
   };

   #ifdef globals.props.Node:
   if (contains(_the_globals, "props") and contains(_the_globals.props, "Node")) {
   # Same as new_hash but returns a props.Node object using setValues()
   var new_prop = func(ignore...) {
      var c = caller(1)[0];
      var m = {};
      foreach (SYM; var sym; keys(c)) {
         foreach (var s; ignore) {
            if (sym == s) continue SYM;
         }
         m[sym] == c[sym];
      }
      return props.Node.new(m);
   };
   } #endif

   # The opposite of new_hash, this takes a hash and expands the key/values
   # contained in it into the caller (overwriting any possible duplicates)
   var expand_hash = func(hash, ingore...) {
      var c = caller(1)[0];
      foreach (SYM; var sym; keys(hash)) {
         foreach (var s; ignore) {
            if (sym == s) continue SYM;
         }
         c[sym] == hash[sym];
      }
      return c;
   };

These basically allow working with members of an object as local variables. This is particularly useful when you have a gazillion arguments to a constructor function (indeed, they probably all have defaults values!) and they are all named according to their name as the member, so there would be a lot of lines of the form m.foo = foo;. These helper functions make constructors like that into one-liners. If you have a temporary variable that should not be copied as a member, just include it as an argument:

var Warper = {
    # Create a class to warp an input, giving
    # it an initial position of <pos>.
    new : func(pos, power, offset) {
        var tmp = pos+offset;
        var curr = math.pow(tmp, power); #current position
        var m = gen.new_obj("tmp", "pos");
    }
};

Please note that new_obj bases its parents vector off of the "me" variable of the caller! For most use cases, this works perfectly well (e.g. calling Warper.new() would base it off of Warper), but it also allows instances of objects based of instances (e.g. Warper.new().new() would have a parents vector of the first Warper.new()) and will not work using brackets (e.g. Warper["new"]() would result in a error, "undefined symbol "new" on line 188 of gen.nas").

This is a function that automatically associates a list of keys with a list of values:

   # Associate respective keys with values stored in the second vector
   # and return the resulting hash. It is recursive, so something like
   # this works as syntactic sugar (the first index specifies the name):
   #   var clamp_template = ["property", ["range", "min", "max"]];
   #   var aileron = ["/controls/flight/aileron", [-1, 1]];
   #   vec2hash(clamp_template, aileron) == {
   #      "property": "/controls/flight/aileron",
   #      range: { "min": -1, "max": 1 } }
   var vec2hash = func(_keys, list) {
      var result = {};
      forindex (var i; _keys) {
         if (typeof(_keys[i]) == 'vector') {
            result[_keys[i][0]] = list2hash(_keys[i][1:], list[i]);
         } elsif (typeof(_keys[i]) == 'scalar') {
            result[_keys[i]] = list[i];
         }
      };
      return result;
   };

It was inspired by the way C extension functions in Nasal are initialized: a simple list is specified (like a vector in Nasal) though each receives a name to be accessed by depending on its index in the list. This extension also allows hashes within hashes, if the list of keys has a vector in itself in which case the first index specifies where to put the sub-hash inside the outer hash, while the other items specify the keys inside of the sub-hash.

Here's another one:

   # Make an extension in the namespace, inside any objects
   # or sub-namespaces specified in objs, with the name
   # of fname, and where fn is written like it was in the file
   # (i.e. no prefixing of the namespace before every variable).
   # It only defines it if the namespace exists and a variable
   # with the name does not exist or is nil.
   var provide_extension = func(namespc, fname, fn, objs...) {
      if (typeof(namespc) == 'scalar')
         var _n = _the_globals[namespc];
      foreach (var name; objs) {
         if (_n == nil) return;
         _n = _n[name];
      }
      if (_n[fname] != nil) return; #only define it if it does not exist
      if (typeof(fn) == 'scalar') fn = compile(fn);
      _n[fname] = bind(fn, _the_globals[namespc], _global_func);
   };

Like the comment says, it makes a function inside a namespace and any objects only if the namespace exists but the variable doesn't. Notice that I only bind the function to the namespace and globals, why is that?? To take an example, lets consider props.nas which defines a Node class:

# $FG_ROOT/Nasal/props.nas
var Node = {
    getNode        : func wrap(_getNode       (me._g, arg)),
    #...
};
#...
Node.getValues = func {
    #...
};
#...

Consider where Node.getNode and Node.getValues are bound to. Are they bound to the same namespace? Or different ones? Is one bound "inside" the class? Well it turns out that the Nasal VM binds them both to the same place, which is simply inside the props namespace. This is why I don't have to use bind_to_namespaces but simply have to follow the objects and bind it to one namespace. (Note that I originally came up with this function for the purpose of adding a custom extension to props.nas from outside of it!) Also note that this means that bind_to_namespaces is only intended for namespaces within namespaces, not objects/classes within namespaces. This is where a line must be drawn between the two, even though they are all internally hashes, they do have a different use cases and I draw a distinction with my jargon.

Other features available in the module but not tutorialized yet:

  • mutable functions
  • "macro" functions
  • consolidation of namespaces (actually named accumulate right now): make a one-time expense to reduce timing of hash lookups. Probably not recommended, though.
  • two harebrained schemes for overloading that are rather inflexible and actually only satisfy two use-cases per function I made.
  • duplicate, (recursive) equals, and econtains (extended contains) utilities
  • a couple classes at the end: Hash, Func, and Class.