Yazoo ---> Online Help Docs ---> Yazoo bytecode ---> Inventions of the compiler

Hidden members

We saw in the last section how Yazoo surreptitiously creates nameless members -- tokens -- that we can only access with the index operators. The user might be spooked to learn that his scripts are probably crawling with another kind of member that is even harder to detect: which not only have ineffable names, but can't even be reached by index because they have their `hidden' flags set. These little elves may be invisible to the user, but they nevertheless perform important services for sets and function calls.

To begin with, consider a set, or more generally any sort of code block. An expression like


    my_set :: { 5, 9 }
   

is straightforward, because a define statement looks for some type-defining right-hand argument, and a block of code fits that bill. On the other hand:


    my_set = { 5, 9 }
   

is more problematic, because equate expects its right-hard argument to be some sort of data: either an inlined constant or a variable. The stuff in braces is one step removed from that: it is only code -- constructor code. The constructor codes for tokens bearing the data that we want to copy, but we will only have that data in hand after the constructor has been run in some variable space.

In other words, that second command---which is of course legal---seems to imply that either equate does something complicated when it sees a set, or else that Yazoo's compiler cooks the expression in a way that makes it more digestible. Option 2 turns out to be correct. So how could Yazoo explicitly run the set's constructor -- or at least, how would we do it? Probably by writing something like


    my_set = ( temp :: { 5, 9 } )
   

That would make things simple because equate would just be copying data from (by the time it got around to it) a fully-formed variable. This is (almost) exactly what Yazoo's compiler will do with our original expression.

To see exactly how Yazoo fleshes out our set equation, we of course have to disassemble it. We get:


    equ ( sm $my_set , def** ( sm $3 , scr { deq* ( sm $1 , csl 5 ) , deq* ( sm $2 , csl 9 ) } ) )
   

and we find one a new ingredient: the def** operator defining invisible variable number 3 (corresponding to temp above). Unlike tokens which pop up in a single-starred def*, dqa* or deq*, this new thing has two asterisks to symbolize that it is doubly invisible, because its hidden flag is set. The hidden flag is the only difference between singly- and doubly-starred operators.

What is true for the right-hand side of an equate is also true for the left. Our first two lines of example could have been replaced with


    { a, b } = { 5, 9 }
   

in which case Yazoo would insert two hidden members:


    equ ( def** ( sm $3 , scr { dqa* ( sm $1 , sm $a ) , dqa* ( sm $2 , sm $b ) } ) ,
            def** ( sm $6 , scr { deq* ( sm $4 , csl 5 ) , deq* ( sm $5 , csl 9 ) } ) )
   

Furthermore, this trick works for other operators besides equate as well. Any time Yazoo needs to convert a script, or concatenation of scripts ({} : {}), into data, it will do so by slipping in a hidden variable. For example, our disassemblies would look much the same if we were to use forced equates instead.

Besides the def** operator, there is also a deq** whose sole habitat is the right side of a return statement. A function always returns some whole variable, not stand-alone data -- we know this has to be true because expressions like


    f().a :: double
   

are legal. (That adds or modifies member a within the return variable.) So for statements like


    return 5
   

a variable has to be created to hold the return data. A scripted analog of Yazoo's fix would look like


    return ( temp := 5 )
   

and the disassembly of the real thing is:


    ret ( deq** ( sm $1 , csl 5 ) )
   

Finally, to return to a nagging question from earlier: what about the args variables? Nobody has ever seen one explicitly outside of its function, and not surprisingly, the reason is that they all lie behind hidden members. It's helpful to take a simple example, say,


    sprint("Hello")
   

and look at its disassembly:


    dqa* ( sm $3 , f ( sm $sprint , def** ( sm $1 , scr { deq* ( sm $2 , cst "Hello" ) } ) ) )
   

The entire expression is of course embedded inside a dqa* because Yazoo tokenizes all user-defined function calls. Concentrate on the bytecode function call which begins with f (.... It takes two arguments: the first is the function variable, and the second is the argument variable. The argument list -- args -- needs to be a variable, not code. By now we know exactly how to turn the code in parentheses into a variable: we need to hidden-define a member with the argument code. And we see Yazoo doing exactly that with the def**.

It may seem trivial, but just to make sure we've touched home plate let's revisit the last taunting example from the beginning of this section. It was:


    print("Hello")
   

where we are back to print() (i.e. not sprint()). (So it is now a built-in, rather than user-defined, function.) The disassembly looks like:


    [print] ( def** ( sm $1 , scr { deq* ( sm $2 , cst "Hello" ) } ) )
   

The first thing to notice is that there is no preceding dqa*. Yazoo knows which of its own internal functions are worth making tokens out of, and print() is not one of those. Secondly, there is no function variable, obviously, since print() is hard-wired in. But the rest should be straightforward: the arguments to print() are constructed inside of a hidden variable before the print() function even begins. And if there is a problem in the arguments, then fine: Yazoo will halt execution and give the appropriate error before print() has a chance to run.

Which leads us to one last, microscopic little detail regarding the hidden arguments of the built-in trap() function. Unlike the other built-in functions, trap() needs tight control over its arguments when they run, carefully logging the type and location of any errors or warnings and allowing execution outside of trap() to proceed normally afterwards if an error does happen. In other words, trap() wants to run its arguments by itself and in its own way, rather than have them constructed automatically before it can control the situation. It follows that the hidden argument definition in a trap() call should not have the run-constructor flag set.

Thus we have one final species of the def-general clade to catalogue. Take the script


    trap( exit )
   

(which, by the way, will not quit the program). Its disassembly looks like


    [trap] ( def-c** ( sm $1 , scr { exit } ) )
   

It's the def-c** --- in the ponderous taxonomy of the disassembler that means `hidden-define-minus-constructor'. This last and rarest of birds, found exclusively in the function call of a trap() function, differs from def** only in that it lacks the constructor flag. The flags of this operator, and those of the other def-general operators that the compiler creates, are all tabulated in the reference section.


Prev: Tokens   Next: Reference


Last update: July 28, 2013

Get Yazoo scripting language at SourceForge.net. Fast, secure and Free Open Source software downloads