This section is devoted to a single word of bytecode: the `flags' immediately following the def-general operator. Given that such diverse scripting functions as define (::), equate (=) and equate-at (=@)---among others---are all def-generals, distinguished only by their flags, this flag-word must pack a punch. The unifying theme is that all these operators copy something, or some things, which are determined by these flags --- data for an equate, or type/code in the case of define, or a reference with the equate-at. In all cases the copying is done from some source variable, constant or type on the right-hand of the operator in the original script, into some destination member and/or variable which is on the left.
The def-general flags consists of nine Boolean flags, stored in the lowest nine bits in the 32-bit flag word. For each flag that is `on' the corresponding bit is set to 1. The flags are numbered from the lowest bit to the highest bit starting from zero. To find the value of the flags-word corresponding to given flags, shift binary 1 to the left N times for each flag N, and sum the resulting values. For example, define-equate-at (:=@) has flags 1, 2 and 4 set. Thus the value of the flags-word is
fw = (1 << 1 = 2) + (1 << 2 = 4) + (1 << 4 = 16)
= 22
The def-general flags are listed in the reference section, along with the flags corresponding to each scripted def-general operator. Here we will describe each flag in turn, starting from flag 0 in the lowest-order bit.
Flag 0 is the equate flag. When set, the data stored in the source variable is copied into the destination variable. This requires the structures of these two variables to be similar if they are composite. However, their types, as defined by their codes, may be different. For example, the following is allowed:
{ a :: ubyte, b :: string } = { c :: ulong, "" }
whereas the following
a[2] :: double
a = { 5.5, 3.9 }
is disallowed because the source contains two members, while the destination has only a single width-2 member. (The copy() routine handles the latter case in interactive mode.)
Flag 1 is the update-members flag. It causes the destination member to be updated to the type of the source variable (not the source member!). For example, suppose we write:
a :: *, b := 5
a = @b
c :: a
Member a has no type, but the variable it points to is a signed long. Thus the member named `c' will be defined as a signed long, because the define operator sets the update-member flag. Thus trying something later on like
d := "some string"
c = @d
would cause a type-mismatch error.
Flag 2 is called the add-members flag. If the destination member can't be found and this flag is set, the destination member is created inside the currently-running function. This works even for members without a name, notably members created implicitly while defining arrays. For multi-step paths where several members have to be created in a row, typically only the last member earns a type; the others will default to a void type which can be aliased to any variable later on. For example, suppose that array had not been defined until we wrote:
array[10].title :: string
Since define sets the add-members flag, Yazoo will create an array member (without a type) pointing to a composite variable; a width-10 unnamed member within (again lacking a type) pointing to a composite variable; and finally a member entitled `title' which will have a string type and point to some string variable.
Flag 3, the new-target flag, performs two tasks. First, it creates a new destination variable if none existed already (i.e. if the destination member was void), but it will not overwrite an existing variable. Second, if necessary it updates (specializes) the type of the destination variable, regardless of whether or not it had just created that variable. The new type is the type of the source variable (not member), so it requires that the source member not be void. New-target is thus complementary to both the update-members and add-members flags.
The member-define operator (*::) sets the update-members flag, but not the new-target flag, so it operates only on members. On the other hand, the variable-define operator (@::) has its new-target flag set but the update-members flag clear, so it will specialize variable but not member types. It can however create new members since it sets the add-members flag. Plain old define (::) sets all three flags.
Flag 4 is the relink-target flag; it instructs Yazoo to make the destination member an alias of the source variable. This flag is set by the equate-at and define-equate-at operators. The destination (left-hand) must be an entire variable, since all indices of a given member must have the same target. An example of an illegal maneuver involving a relink is the following:
a[10] :: b[5] :: ulong
a[1, 5] = @b[1, 5]
In a sense, relink-target is the third of three pillars of the def-equate flags. Whereas post-equate copies data, and update-members and new-target copy code, the relink-target flag copies the target reference (which is something like a pointer) of the source member.
Flag 5 is named run-constructor; it causes the constructor of the destination variable to be run after it has been created but before any data has been copied. The constructor is the part of a script before the first code marker or semicolon. If the variable has several concatenated codes, the constructors of each code are run in order from first code to last. Primitive variables have no code, so they are unaffected by this flag.
The constructor-run is the 2nd-to-last operation performed by the def-equate operator, with the actual equate being the last. This is why we are able to copy composite variables in one step:
comp1 :: { ... }
comp2 := comp1
The new variable comp2 is defined to have the same code as comp1, so when its constructor runs it should grow the same set of members that comp1 has. So the final equate should not have any problems, as long as we didn't modify comp1 after defining it. A common case where the constructor does not recreate the variable is in implicit variable definitions, such as:
array[12] :: ulong
arr2 := array | causes an error
where array and therefore arr2 have no constructor, so the latter is created empty even though the run-constructor flag was set.
Flag 6 is called the hidden-member flag. We have not encountered hidden members yet; suffice it to say that members whose indices were defined with this flag set simply do not show up under the user's radar, so they have to be accessed by name. For example, suppose we have a variable, call it three_members because it has three members, and the members have 3, 4 and 5 indices respectively. Suppose that the second member is hidden. Then
three_members[1, 3]
spans the first member, and
three_members[4, 8]
spans the third member. The second member is there -- but its index is hidden.
The equate (=) and compare (==) operators skip hidden members of both (left-hand and right-hand) arguments. In addition, print() does not print the contents of hidden members.
Flag 7 is the unjammable flag. A jamb is an alias that prevents a member from being resized: for example, if two members alias the indices of an array, ordinarily neither one can resize the array since doing so would also affect the other member. However, if one member is defined as unjammable, then it cannot jam the other member: the second member can be resized and the first member, which now has the wrong number of indices, becomes `unjammed' -- i.e., dead. An unjammed member has to be re-aliased before it can be used again without causing an error.
proxies
Flag 8, the final and left-most flag, is the proxy flag. Until recently the author considered proxies to be so obscure that he did not even bother writing an operator for defining them -- they had to be byte-coded in by hand. That has changed: the operator `#::' now defines proxies, as in: a[10] #:: double.
Proxies distinguish themselves only when they are defined as arrays. Ordinarily, the indices of a member must point to a contiguous block of memory, so we can't do things like re-alias some of the indices of an array if they do not span one entire member. (The distinction between members and indices is explained in the discussion on arrays.) However, each index of a proxy-member may point to an entirely different variable, as long as its type matches. So if we follow up the definition of our array with the lines below, we get:
> b := 3.14
> a[4] =@ b, a[10] :: double
> sprint(a)
{ *, *, *, 3.14, *, *, *, *, *, 0 }
As we can see, when the proxy array is defined each element is initially void. We can create variables for them by subsequently defining the array elements one by one, using just the normal define operator `::'.
The advantage of proxies is that they bring the power of array manipulation to bear on aliases. There is also a disadvantage, which is that only one proxy can be accessed at a time. Any multiple-indices operation such as
array[2, 5]
is illegal when dealing with proxies, even if only one member is involved.
Last update: July 28, 2013