Cicada ---> Online Help Docs ---> Customizing the Cicada language ---> Cicada bytecode

Adapters

There’s something missing in our scripts, and the easiest place to see this is in an ordinary function call:


    f(2, pi)
   

Realizing that commas are just ends-of-lines, we could write this


    f(
       2
       pi
    )
   

which shows that 2 and pi are somehow 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 written


    [That which I want to draw your attention to] [is] the rock.
   

In other words if an utterance only consists of a noun, we fill out the rest of the sentence using 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 cicada.c. Each adapter allows the compiler to convert an expression from one type (say, something resembling a bare noun) to another type (perhaps a ‘sentence’ type), at the price of adding a few bytecode words. This prevents a type-mismatch error from being thrown by incomplete expressions.

For example, when the compiler sees the command ‘2’ it rolls its eyes and reaches for the type-mismatch error button, because it expected a complete command (a type 1 expression) whereas an int_constant can only be construed as types 4, 5 or 6 based on its return-types string. But then Cicada notices an adapter that works on type-5 objects (called type5arg_adapter in cicada.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 "8 173 10" anonymousmember "a1"
   

The expression to adapt is considered the ‘argument’ and goes in place of a1. Looking at cicada.h or the reference section, we could figure out that this adapter turns our expression into something looking like


    var1 := 2
   

except that the define-equate operator has slightly different define flags (173 instead of 47). Likewise the expression ‘pi’ requires a different adapter that looks like


    var2 := @pi
   

though again with slightly different flags on the aliasing operator. A third adapter with a define-like operator creates composite objects, and allows statements like myVar = { 1, false }.

The anonymousmember keyword is a unique member ‘name’, which is inexpressible by any user. (The reason is that members the user types are translated into positive member ID numbers in bytecode, whereas anonymous members are assigned sequential negative ID numbers.) Thus the members we represented as var1 and var2 in the last paragraph really don’t have those names or any other, so they can only be accessed by index using the bracket operators [...]. (Or by ‘name’/ID number if we are willing to write our own bytecode.)

Incidentally, anonymous members are also used in ordinary function calls, and in this case those members are also ‘hidden’ because their define operators have the hidden-member flag set. A hidden member is invisible to the array-index operators: one can only access them by name (which is difficult when the members are also anonymous). The variables behind these members become the args variables when functions are run. Remember, think of a function call f(2, pi) as if the syntax were: f{2, pi}. The exotic define operators used by function calls are listed in Table 3. The rarest bird in this collection, abbreviated def-c** (‘hidden-define-minus-constructor’ in the ponderous taxonomy of the disassembler), lives exclusively in the function call of a trap() function, defining the args variable without running its constructor so that trap() can do this in a controlled way.

The adapters consistently set the unjammable flag of their define/equate-at operators, preventing them from jamming arrays. For example, if we call f(myArray[<2, 4>]) we will still be able to resize myArray in the future, and the broken link will be refreshed next time that call to f() is made because that reruns the argument constructor. (In sets this can cause a problem, because the set’s constructor is not usually rerun after an array resize. We can get around this by manually aliasing a member to certain array indices.) Also, the update-members flag is cleared by all of the adapters, so that the anonymous member can be re-assigned to a variable of a different type (for example if 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 expressions like ‘,,’ where no command appeared between the commas, since each comma expects a sentence-type argument. A final no-argument-adapter allows a script to contain a return command without a variable.

Notice that the final set of adapters in cicada.c/cpp 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.


Prev: define flags   Next: Custom-compiling within a script


Last update: May 11, 2017

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