In the previous tutorial I showed the usage of experimental VM script engine. The main purpose of the script is to create a dynamic code without recompiling the application. But there is a disadvantage: the script is imperative rather than object-oriented. Now I will show how could we create a new code by combining different group objects with each other - actually assembling the object.
But before we start I will shortly describe the group / role types we will use:
As you may remember a message consists of a subject and a verb. The subject should be declared explicitly. Every time a new subject is declared the appropriate symbol is declared as well (it is used to invoke this subject). On the other hand the verb is built-in. Starting from 1.6.13 for every verb there is a special role (e.g. for get - __get) which can be used to convert this verb to "invoke" one and vice versa.
Wrap is a special group which uses the first member of the collection as a wrapper around the second one (content). It means that only the wrapper methods are invoked but "self" variable is assigned to the wrap content (so it could be called as a temporal mutation). We will use wraps with subject symbols and verb roles.
Let's practice with wraps. First of all we will need to start VM terminal. As I told in the previous tutorial VM should be start so we could use a simple script to do so - newconsole. Let's go to BIN folder and type the following command:
Now I will show how to use wrap to invoke subject - for example a string length (std'dictionary'length):
-Ninline &nil 'program'output &std'dictionary'length "abc" &wrap &nil ^get ^write
This is similar to the following "traditional" code:
'program'output << __wrap(std'dictionary'length, "abc") get.
Let's try to understand how the code is working.
The wrap group object redirects the message (GET in our case) to its first member (std'dictionary'length) while assigning SELF variable with its second member ("abc") - actually wraps a literal constant in a subject role. The subject role in its turn appends the appropriate subject (std'dictionary'length) to the sent message (GET) and redirects it to SELF ("abc"). As a result
__wrap(std'dictionary'length, "abc") get
is translated into
Now let's use a verb role to make our code completely dynamic
-Ninline &nil 'program'output &get &std'dictionary'length "abc" &wrap &wrap &nil ^invoke ^write
which is similar to
'program'output << __wrap(__get , __wrap(std'dictionary'length, "abc")) invoke.
In this code we add an additional wrap with GET verb role and use a generic verb - invoke. When a verb role receives INVOKE message it converts it to GET and redirects to SELF - the second wrap. As a result we have a previous case.
Now let's look at another group object - BATCH. Batch a is collection which redirects sent message to every its member, passing the result of the previous operation as a parameter of the next one.
Let's once again modify our sample:
-i &nil sys'dynamics'batch &get &std'dictionary'length "abc" &wrap &wrap ~sys'dynamics'group_member ^append &write &nil 'program'output &wrap ~sys'dynamics'group_member ^append &nil ^invoke
which is similar to
__batch(__wrap(__get , __wrap(std'dictionary'length, "abc"), __wrap(__write, 'program'output) ) invoke
Unlike the wrap, a batch does not have a script engine built-in identifier so we have to use sys'dynamics'Batch class to create a collection. ~sys'dynamics'group_member ^append is used to append the created wrap to the batch.
Our batch collection consisting of two wraps: the one we use to get the literal length and a new one, which is used to send the parameter to the console output. So let's look into our code once again. We send INVOKE message with NIL as a parameter to the batch. The batch redirects the message to its first member which returns the literal length and then sends INVOKE again to the next member but this time the parameter is a literal length. The wrap converts INVOKE into WRITE and redirects it to the console output as a result we prints the literal length.