Nasal Namespaces in-depth: Difference between revisions

Jump to navigation Jump to search
m
Use Nasal highlighter
m (not a draft)
m (Use Nasal highlighter)
Line 14: Line 14:
== Hands-On Example ==
== Hands-On Example ==
Let's consider a common operation to perform. As I'm sure you all know, to put the gear down from a joystick button (which is in the namespace __js0 for example), you call a script like this:
Let's consider a common operation to perform. As I'm sure you all know, to put the gear down from a joystick button (which is in the namespace __js0 for example), you call a script like this:
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
controls.gearDown(1);
controls.gearDown(1);
</syntaxhighlight>
</syntaxhighlight>
Line 23: Line 23:


Namespaces also resemble hashes in another way – they in fact are hashes! Nasal thinks of namespaces as just another hash, and this can challenge a common notion of programmers, that of "lvalues". Typically, programmers can only declare named variables that are legal lvalues – that is, they have to fit a certain pattern. The pattern is usually something like this: a name can start with an underscore or alpha character and all remaining characters must be an underscore or an alphanumeric character. The lvalue ends at the first character that doesn't fit that pattern, e.g. a punctuation mark or bit of white space. But in Nasal hashes, one can use arbitrary scalars (strings or numbers) as hash indexes:
Namespaces also resemble hashes in another way – they in fact are hashes! Nasal thinks of namespaces as just another hash, and this can challenge a common notion of programmers, that of "lvalues". Typically, programmers can only declare named variables that are legal lvalues – that is, they have to fit a certain pattern. The pattern is usually something like this: a name can start with an underscore or alpha character and all remaining characters must be an underscore or an alphanumeric character. The lvalue ends at the first character that doesn't fit that pattern, e.g. a punctuation mark or bit of white space. But in Nasal hashes, one can use arbitrary scalars (strings or numbers) as hash indexes:
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
a_hash["(illegal lvalue)"] = 78;
a_hash["(illegal lvalue)"] = 78;
</syntaxhighlight>
</syntaxhighlight>
This means that namespaces can contain ‘variables’ that aren't valid lvalues and thus can't be used in typical code. Also notice that the dialog's namespaces are not lvalues, as they have punctuation in them, and thus can't be used like the controls namespace can. Instead, one must use various methods to access non-lvalues, like indexing the globals namespace (though this is ''not'' recommended as they are supposed to be "private"):
This means that namespaces can contain ‘variables’ that aren't valid lvalues and thus can't be used in typical code. Also notice that the dialog's namespaces are not lvalues, as they have punctuation in them, and thus can't be used like the controls namespace can. Instead, one must use various methods to access non-lvalues, like indexing the globals namespace (though this is ''not'' recommended as they are supposed to be "private"):
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
globals["__dlg:foo"].setName("foo");
globals["__dlg:foo"].setName("foo");
</syntaxhighlight>
</syntaxhighlight>
Line 38: Line 38:
== Dealing with Different Objects that have the Same Name ==
== Dealing with Different Objects that have the Same Name ==
Back to the graph: each portion of the graph has its own variables, some of which might be functions with their own namespaces, and some are hashes that are used as objects or namespaces. We learned that the Nasal code executor looks first at the current namespace for a symbol, then the next higher namespace, until it reaches the top. But what if we define a variable in this scope and we want to access another variable that has the same name but lives in a different namespace? This is what happens in debug.nas; it defines a string function which obscures the string namespace (in computer science this is called [en.wikipedia.org/Variable_Shadowing shadowing]). To use the string namespace, there are two options: use caller() to manually "hop" up the namespace tree or prefix "globals<nowiki>[dot]</nowiki>" to the symbol like this:
Back to the graph: each portion of the graph has its own variables, some of which might be functions with their own namespaces, and some are hashes that are used as objects or namespaces. We learned that the Nasal code executor looks first at the current namespace for a symbol, then the next higher namespace, until it reaches the top. But what if we define a variable in this scope and we want to access another variable that has the same name but lives in a different namespace? This is what happens in debug.nas; it defines a string function which obscures the string namespace (in computer science this is called [en.wikipedia.org/Variable_Shadowing shadowing]). To use the string namespace, there are two options: use caller() to manually "hop" up the namespace tree or prefix "globals<nowiki>[dot]</nowiki>" to the symbol like this:
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
globals.string.isascii(n);
globals.string.isascii(n);
</syntaxhighlight>
</syntaxhighlight>
Line 44: Line 44:


Here is the reason why prefixing "globals" even works (be warned that it doesn't hold for other Nasal implementations!):
Here is the reason why prefixing "globals" even works (be warned that it doesn't hold for other Nasal implementations!):
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
-+- [flightgear]/src/Scripting/NasalSys.cxx, line 634 -+-
-+- [flightgear]/src/Scripting/NasalSys.cxx, line 634 -+-
     // Start with globals.  Add it to itself as a recursive
     // Start with globals.  Add it to itself as a recursive
Line 60: Line 60:


Consider what we learned: functions are anonymous and they create new namespaces. Now consider this: anonymous namespaces. Nasal can create these by making anonymous functions and immediately evaluating it. Let's look at an example from io.nas to create a layer of security over removelistener():
Consider what we learned: functions are anonymous and they create new namespaces. Now consider this: anonymous namespaces. Nasal can create these by making anonymous functions and immediately evaluating it. Let's look at an example from io.nas to create a layer of security over removelistener():
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
     # wrap removelistener
     # wrap removelistener
     globals.removelistener = (func {
     globals.removelistener = (func {
Line 82: Line 82:
* bind()
* bind()
These all deal with namespaces is some way, shape, or form: closure() gives the namespace (closure) of the function at the specified level – more on this later; bind() can modify those closure (somewhat indirectly); caller() gives info about the caller: locals namespace, function, name of source file, currently executing line; and call() gives enormous control over how a function is executed, from the 'me' variable and the arguments to the namespace it will be executed in. The ‘official’ prototype given for the latter is this:
These all deal with namespaces is some way, shape, or form: closure() gives the namespace (closure) of the function at the specified level – more on this later; bind() can modify those closure (somewhat indirectly); caller() gives info about the caller: locals namespace, function, name of source file, currently executing line; and call() gives enormous control over how a function is executed, from the 'me' variable and the arguments to the namespace it will be executed in. The ‘official’ prototype given for the latter is this:
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
call(fn, args=[], me=nil, namespace=nil, error=nil)
call(fn, args=[], me=nil, namespace=nil, error=nil)
</syntaxhighlight>
</syntaxhighlight>
Line 98: Line 98:
=== Example: Class Constructor ===
=== Example: Class Constructor ===
Let's say you are making your own class in Nasal and you have a gazillion members you new() method needs to populate. Each member is passed in as an argument which gets tiring for you (also limits you to 16 members, as functions can't have more arguments than that). This is what a first attempt might look like:
Let's say you are making your own class in Nasal and you have a gazillion members you new() method needs to populate. Each member is passed in as an argument which gets tiring for you (also limits you to 16 members, as functions can't have more arguments than that). This is what a first attempt might look like:
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var class = {
var class = {
     new: func(a, b, c, d, e) {
     new: func(a, b, c, d, e) {
Line 112: Line 112:
</syntaxhighlight>
</syntaxhighlight>
And it goes on and on. What if you could just do it automatically? Your first attempt might go like this:
And it goes on and on. What if you could just do it automatically? Your first attempt might go like this:
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var class = {
var class = {
     new: func(a, b, c, d, e) {
     new: func(a, b, c, d, e) {
Line 124: Line 124:
</syntaxhighlight>
</syntaxhighlight>
But this is still a little inefficient as you have to manually add each symbol. There are two ways around this. Firstly, you could do a foreach on the symbols in the namespace and ignore the symbols if it is "m" – that is, our hash we are populating. Or you could wrap that in a function which generates the hash itself and doesn't need to skip it. Here they are in order:
But this is still a little inefficient as you have to manually add each symbol. There are two ways around this. Firstly, you could do a foreach on the symbols in the namespace and ignore the symbols if it is "m" – that is, our hash we are populating. Or you could wrap that in a function which generates the hash itself and doesn't need to skip it. Here they are in order:
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var class = {
var class = {
     new: func(a, b, c, d, e) {
     new: func(a, b, c, d, e) {
Line 136: Line 136:
};
};
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var _gen_new = func() {
var _gen_new = func() {
     var symbols = caller(1)[0];
     var symbols = caller(1)[0];

Navigation menu