4,400
edits
No edit summary |
m (Use Nasal highlighter) |
||
(2 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 [ | 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=" | <syntaxhighlight lang="nasal"> | ||
# gen.nas -- namespace "gen" | # gen.nas -- namespace "gen" | ||
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=" | <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=" | <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, | 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=" | <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=" | <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=" | <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 | ||
# 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); | |||
}; | }; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 173: | Line 190: | ||
Onto some other utilities: | Onto some other utilities: | ||
<syntaxhighlight lang=" | <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" | 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=" | <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=" | <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=" | <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: | 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=" | <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=" | <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. | ||
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. | |||