Member definition (::), assignment (=), and aliasing (=@) are all done by different flavors of the define operator. What makes them different is their ‘flags’: a set of binary properties such as: does this operator define new members? does it copy data? etc. In bytecode, all 8 flags are stored in a single word immediately following the define-operator bytecode word, just before the left- and right-hand arguments to the operator. This section is devoted to that single flags bytecode word.
To calculate a flags word for a set of flags, we treat each flag as a binary digit and read out the number in decimal. For example, the define operator has flags 1, 2, 3 and 5 set, so its flags bytecode word is
flag = (1 << 1 = 2) + (1 << 2 = 4) + (1 << 3 = 8) + (1 << 5 = 32)
= 46
(<< is the bit-shift operator). Table 3 summarizes the flags words for each Cicada operator. Clearly most of the 256 possible operators are not predefined in cicada.c.
Flag 0 -- equate:
copies data from the source variable into the destination variable. This is used by both the assignment = and define-equate := operators, but not the aliasing operators.
Flag 1 -- update-members:
causes the destination member to be updated to the type of the source variable (not the source member unless the source variable is void!). For example, suppose we write:
a :: *, b := 5
a = @b
c :: a
Member a has no type, but the variable it points to is an integer. Thus the member named ‘c’ will be defined and allocate storage for an integer, because the define operator sets the update-member flag.
Flag 2 -- add-members:
creates the looked-for member if it doesn’t already exist inside the current-running function. Set by all define operators, even :: @ which doesn’t assign a type to the new member but will create it.
Flag 3 -- new-target:
does two things. 1) 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. 2) This flag also 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 member or source variable, whichever is more restrictive. Since a member can only point to an as-or-more-restrictively typed variable, this means that the new type will be that of the source variable unless there is none (the source member points into the void), in which case it’s the type specification of the source member.
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 -- relink-target:
instructs Cicada 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, not certain indices of an array (i.e. a[*].b = @c is legal but a[<2, 3>] = @c is not).
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 -- run-constructor:
causes the constructor of the destination variable to be run after it has been created/having its type updated, but before any data has been copied from the source variable if the equate flag was set. 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 will grow the same set of members that comp1 has. Then the final equate should not have any problems, as long as we didn’t modify comp1 after defining it.
Flag 6 -- hidden-member:
sets the ‘hidden’ flag of any newly-defined member. Hidden members, which are created by bytecode adapters, can only be accessed by name (which is usually unwriteable). Array-index operators, assignment operations (=), comparisons (==), and built-in functions like print() all skip over hidden members.
Flag 7 -- unjammable:
makes a member unjammable, i.e. unable to prevent another member from being resized. Ordinarily, if two members alias overlapping indices of an array, 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., inoperable. An unjammed member has to be re-aliased before it can be used again without causing a void-member error.
Last update: May 8, 2024