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

Custom-compiling within a script

For a variety of reasons, we might occasionally want to run a script manually without using the run() function. This involves two steps. The first step is to produce bytecode, easily done using the compile() function. Second, the transform() function gives bytecode a perch on a function’s internal code registry. Here is a simple example:


    myBytecode := compile("myMessage := \"Hello, world!\"; print(myMessage)")
   
    newFunction :: transform(myBytecode)
   
    newFunction()
   

Ordinarily run() does these things for you. If user.cicada wasn’t loaded by start.cicada (probably because Cicada was set to run another script from the command line), then run() isn’t defined, so we need to manually load, compile and transform any script we want to run.

Another compile()-transform() situation arises when we run a script that uses a different syntax from the default Cicada language. For example, our script might want to process commands typed by the user, that are in a completely different format from the Cicada language. In that case we won’t want to change cicada.c since doing so would break our other scripts like start.cicada. Instead we must use the setCompiler() function to process the user’s input using a different syntax from that of our own scripts. Here is an example, relying on language-constant definitions in user.cicada.


    newLanguage :: [] compiledCommandType
    newLanguage[*] = {
       { cat("add ", type1arg, "to", type1arg), 1, "0",
               cat(inbytecode, "8 173 10", anonymousmember, "27 a1 a2 0") },
       { cat("negative", type1arg), 2, "1", cat(inbytecode, "29 54 -1 a1") },
       { int_constant, 0, "1", cat(inbytecode, "54 a1") },
       { double_constant, 0, "1", cat(inbytecode, "55 a1") }
    }
    newLanguageAssociativity :: [] int
    newLanguageAssociativity[*] = { l_to_r, r_to_l }
    newCompilerID := setCompiler(newLanguage, newLanguageAssociativity)
   
    myBytecode := compile(input("Give me math: "))
    doMath :: transform(myBytecode)
   
    doMath()
    printl("The answer is: ", doMath[1])
   

Running this script:


    Give me math: add negative 1 to 3.14
    The answer is: 2.14
   

Notice how the two array arguments to setCompiler() are almost exactly the same as the two arrays that specify the language in cicada.c, the main difference just being the use of cat() to concatenate strings. For consistency, all of the constants used in the C file (such as int_constant and inbytecode) are also defined in user.cicada. Also, make sure to define both arguments as arrays -- setCompiler() will not understand anything inside of curly braces. As always, any script must have an overall type of 0. In this primitive example the only possible valid script is an add command.

Cicada always opens with a single compiler -- compiler ID 1 -- so to switch back just type setCompiler(1). Calling setCompiler() with no arguments returns the ID of the current compiler, without changing the active compiler.

Functions produced by different compilers live in different namespaces, because each compiler which keeps its own running tally of all member names and anonymous members it has encountered. But the use of separate compilers does not prevent collisions of member names between these functions: if anything switching compilers makes collisions more likely. (Any new compiler member will assign member IDs starting from 1 and counting upwards, and that ID is the only thing Cicada sees when the function runs.) To avoid problems, set the search path manually with transform() so that the new bytecode can’t see the workspace.


Prev: Adapters    Next: Reference


Last update: May 8, 2024