There’s something missing in our scripts, and the easiest place to see this is in an ordinary function call:
f(2, pi, { 1, int })
Realizing that commas are just ends-of-lines, we could write this
f(
2
pi
{ 1, int }
)
which shows that 2, pi, and { 1, int } are somehow all valid commands. How can this be?
Let’s draw an analogy. Back in the cave man days, there were probably a lot of sentences like
rock
which in modern English would be
[That which I want to draw your attention to] [is] the rock.
In other words, if a sentence only contains an object, the cave man’s brain fills it out by adding some stock subject and verb. Cicada works exactly the same way: the stock subjects and verbs to use in different situations are the so-called adapters defined in cclang.c. Each adapter allows the compiler to convert some bare expression (e.g. the object of a sentence) to another type (a full sentence) by throwing in a few extra bytecode words.
When the user enters the command ‘2’, the compiler rolls its eyes and reaches for the type-mismatch error button, because it a complete command is a type 1 expression whereas an int_constant can only be construed as types 5, 6, or 7 based on its return-types string in the cicadaLanguage[] array. But then Cicada notices an adapter that works on type-6 objects (named type6arg_adapter in cclang.c), and moreover that adapter’s return-types string includes a “1” which is what we want. So the adapter adds its code and the error never happens. Here is the adapter’s bytecode:
inbytecode bc_define(deqxFlags) bc(search_member) anonymousmember bcArg(1)
The expression to adapt is considered the argument and goes in place of bcArg(1). It’s clear that this adapter turns our expression into something looking like
var1 := 2
except that the define-equate operator has slightly different define flags (deqxFlags instead of deqFlags).
Other types of objects use different adapters when they appear by themselves. Variables use an adapter that creates an alias: for example the expression ‘pi’ becomes something like
var2 := @pi
though again using slightly different flags from a normal aliasing operator. Finally, type-objects are turned into full commands using a third adapter that adds a define-like operator. Thus ‘{ 1, int }’ turns into a modified version of
var3 :: { 1, int }
or more precisely:
var3 :: { var3a := 1, var3b :: int }
The anonymousmember keyword produces a unique member ‘name’ that is inexpressible by the user. (Names become ID numbers in the bytecode: user-typed names become positive ID numbers, whereas anonymous members get assigned sequential negative ID numbers as they are encountered. The namespace consists of both the name-ID list and the negative ID counter.) Thus var1, var2, etc. in the last paragraph don’t really don’t have those names or any other, so function f() will access those members using the bracket operators (args[1], args[2], etc.). It’s technically possible to hand-write bytecode that uses negative IDs to access anonymous members, but that’s a heroic measure that’s probably only useful for anonymous members that are also ‘hidden’.
Hidden members are invisible to the array-index operators. In bytecode-speak, these are produced using define operators whose hidden-member flags are set (flag 6 in Table 3). A typical workspace has a sprinkle of hidden members, notably around function calls where they briefly shine as args while the function is running, then live almost invisibly until the function is rerun. (Unless the function was called using braces, in which case the args variable is not hidden). The rarest bird of all is the hidden-define-minus-constructor operator (def-c** in the table), living exclusively in trap() function calls, where it defines an args variable without running its constructor so that trap() can do so in a controlled way.
The other define-operator flag used by (all) adapters is the unjammable flag (flag 7; see Table 3), which prevents members from jamming arrays. Consider the function call f(myArray[<2, 4>]), which produces a hidden args variable consisting of { myArray[<2, 4>] }, and which compiles to something like { anon1 := @myArray[<2, 4>] }. Ordinarily anon1[] would jam myArray[], and the actual array could not be resized from either member since doing so would also force a resize of the other. But in this case anon1 was defined as unjammable---as in unjam-able (can be unjammed)---so future array resizes like myArray[+3] are allowed because anon1 will de-alias rather than cause a jammed-member error. That’s OK because the alias will be restored the next time that same function call happens, since the argument constructor will be rerun. (However there can be a problem in other contexts where the constructor is not rerun each time it is used, for example in sets containing aliases to array subsets. Use hard-coded aliases for these cases.) As an aside, these adapters also clear their update-member-type flags (flag 1), so that their anonymous members can be re-assigned to variables of different type (in case, for example, between two iterations of the command f(a) member ‘a’ gets removed and redefined).
One last type of adapter called noarg_adapter replaces missing expressions altogether. These adapters are necessary to allow blank scripts, or situations like two consecutive end-of-lines (command-conjoining operators) which lack a command between them to conjoin. Another no-argument adapter allows a script to contain a return command without a variable. The final set of no-argument adapters in cclang.c is used to convert a sentences-type expression (type 1) to a script expression (type 0), by adding a null bytecode word at the end.
Last update: November 12, 2025