Scol reflexive functions

The purpose of this document is to explain how a reflexive Scol function works, that is to say a callback function which can be called from Scol language.
In this tutorial, we will use the term callback function for the C++ function which be called when the event will occur, and the term reflexive function for the Scol function called by the callback function (so when the event occurs). In other terms, a reflexive function is comparable to a callback function which can be called in Scol language.
Our example will show how to call a reflexive function as soon as one the values of Bloc instance is updated.

Creation of a callback for Bloc type

The constant SCOL_BLOC_NEW_DATA_CB correspond to the ID of the reflexive function in the data type Bloc. We can explain this principle as an ID of a slot in the data type Bloc, each slot corresponding to a specific reflexive function which can be defined (if a reflexive function is not defined in the slot of an instance of this object, the Scol Virtual machine will ignore this call).

The variable named BLOC_NEW_DATA_CB enables to store the ID of the Windows event which will trigger the call to the C++ callback.

 //! New data event callback number
 int SCOL_BLOC_NEW_DATA_CB=0;

 //! New data event number
 int BLOC_NEW_DATA_CB;

We will define our first callback function. Its purpose is to push the parameters used by the reflexive function (we'll define them later) into the Scol stack, an then call the reflexive function using a function from the Scol API named OBJcallreflex.
This function takes as parameters :
  • 2 mandatory parameters for the reflexive function
    • the object which has triggered the call (first parameter)
    • a user parameter which can be of any type (second parameter, unused in our case)
  • as many optional parameters as we need to use

Our custom parameters will respectively be the new integer value stored in Bloc object, et the new name of this object.
Consequently, we can already notice that the prototype of our Scol reflexive function will be : fun [ObjBloc u0 I S] u1.

The other important point concerns the function OBJbeginreflex from the Scol API. It can notice the Scol VM that we're preparing a call to a reflexive function.
Given that several of these functions can be defined in the VM for each data type, even for each object instance of this data type, it's used to specify the target of the call.

In our case, the reflexive function called which be the one from the slot SCOL_BLOC_NEW_DATA_CB of the object instance bloc, which Scol type is OBJBLOCSCOL.

 /*!
 * \brief Callback function for calling scol reflex defined for BLOC_NEW_DATA_CB
 *
 * \param mmachine : scol machine structure
 * \param HWND : target window handle
 * \param unsigned msg : window message
 * \param UINT : callback param
 * \param LONG : callback param
 * \param int : return value
 */
 int getBlocNewData(mmachine m, UINT id, LONG param)
 {
     int k = 0;

     // Cast id parameter to BlocObj type
     Bloc * bloc = (Bloc*) id;

     // Use : OBJbeginreflex(mmachine, type of object, ptr object, callback type)
     if (OBJbeginreflex(m, OBJBLOCSCOL, (int)bloc, SCOL_BLOC_NEW_DATA_CB))
     {
         MMechostr(MSKDEBUG,"Bloc not found\n");
         return 0;
     }

     // Retrieve current bloc value, and set it as 3rd parameter of the callback
     MMpush(m, ITOM(bloc->getValue()));
     // Retrieve current bloc name, and set it as 4th parameter of the callback
     Mpushstrbloc(m, bloc->getName());

     // Call reflex previously defined
     k = OBJcallreflex(m, 2 /*nb param after obj and u0*/);
     return k;
 }

When the plugin is loading, we have to notify to the Scol VM that we want our reflexive function to be available on our object Bloc.

The call of the function enabling to register the new data type must be updated, because the first parameter of OBJregister corresponds to the number of callbacks which will be defined on the object. Then, we have to update this parameter to '1'.

In addition, we have to define a new event and associate to it the callback function. These 2 operations are done by calling the following functions from the Scol API :
  • OBJgetUserEvent : returns an event ID which is available into the Scol VM,
  • OBJdefEvent : associates this ID to a pointer on the callback function.
 /*!
  * \brief Load the template functions
  *
  * \param mmachine : scol machine structure
  *
  * \return int : 0 if succes, error code otherwise
  **/
 int LoadTemplate(mmachine m)
 {
     int k;

     // Declare a new type of object ("OBJBLOCSCOL")
     OBJBLOCSCOL = OBJregister('''1 /*nb of callback*/''', 1/* deleted from parent */, destroyBlocObj, "OBJBLOCSCOL");

     // ----- Define callbacks
     // Get a new user event
     BLOC_NEW_DATA_CB = OBJgetUserEvent();

     // ----- Define callbacks for the call of the reflexive function.
     OBJdefEvent(BLOC_NEW_DATA_CB, (int (__cdecl *)(struct Mmachine*, UINT, LONG))getBlocNewData);

     // Load package
     k = PKhardpak(m, "TemplateEngine", NbTplPKG, TplName, TplFunc, TplNArg, TplType);
     return k;
 }

Then, we have to add the following code in the functions _SETBlocValue and _SETBlocName to trigger the new event.

 // send the change data callback message to scol, use OBJpostEvent instead if you need to poll the message
 OBJsendEvent(BLOC_NEW_DATA_CB,(int)bloc,(LONG)NULL);

Registration of a new reflexive function

In order to be able to call a reflexive function of a Bloc object, we first have to associate it to this instance.
The next function enables to register in the slot SCOL_BLOC_NEW_DATA_CB an object OBJBLOCSCOL in the Scol function which prototype is fun [ObjBloc u0 I S] u1, which will be called when OBJcallreflex will be run.

 /*! @ingroup group1
 * \brief _CBblocChangeValue : This function sets the reflexive function to be executed when Bloc value change event happens
 *
 * <b>Prototype:</b> fun [BlocObj fun [ObjBloc u0 I S] u1 u0] BlocObj
 *
 * \param BlocObj : Bloc Object whose value has changed
 * \param fun [ObjBloc u0 I S] u1 : The reflexive function to call when the event occurs.
 * - I : int value of the object
 * - S   : name value of the object
 * \param u0 : User parameter
 *
 * \return BlocObj : The Bloc object whose value has changed
 */
 int _CBblocChangeValue(mmachine m)
 {
     // Add a reflex
     MMechostr(MSKDEBUG, "_CBblocChangeValue ...adding reflex\n");
     return OBJaddreflex(m, OBJBLOCSCOL, SCOL_BLOC_NEW_DATA_CB);
 }

To allow the Scol developer to associate a reflexive function, we have to bind the function _CBblocChangeValue using the same method described above in the document.
Consequently, we will modify the variables used by PKhardpak. The prototype for the Scol function named _CBblocChangeValue is : _fun [ObjBloc fun [ObjBloc u0 I S] u1 u0] ObjBloc.
Note that the prototype of the reflexive function is the second parameter.

 //! Nb of Scol functions or types
 #define NbTplPKG    9

 /*!
  * Scol function names
  **/
 char* TplName[NbTplPKG] =
 {
     "_HelloWorld",
     "ObjBloc",
     "_CRbloc",
     "_DSbloc",
     "_GETblocValue",
     "_GETblocName",
     "_SETblocName",
     "_SETblocValue",
     "_CBblocChangeValue" 
 };

 /*!
  * Pointers to C functions that manipulate the VM for each scol function previously defined
  **/
 int (*TplFunc[NbTplPKG])(mmachine m)=
 {
     _HelloWorld,
     NULL,
     _CRbloc,
     _DSbloc,
     _GETblocValue,
     _GETblocName,
     _SETblocName,
     _SETblocValue,
     _CBblocChangeValue
 };

 /*!
  * Nb of arguments of each scol function
  **/
 int TplNArg[NbTplPKG]=
 {
     0,
     TYPTYPE,
     1,
     1,
     1,
     1,
     2,
     2,
     3
 };

 /*!
  * Prototypes of the scol functions
  **/
 char* TplType[NbTplPKG]=
 {
     "fun [] I",                                        // _HelloWorld
     NULL,
     "fun [Chn] ObjBloc",                // _CRbloc
     "fun [ObjBloc] I",                    // _DSbloc
     "fun [ObjBloc] I",                                 // _GETblocValue
     "fun [ObjBloc] S",                                 // _GETblocName
     "fun [ObjBloc S] I",                               // _SETblocName
     "fun [ObjBloc I] I",                               // _SETblocValue
     "fun [ObjBloc fun [ObjBloc u0 I S] u1 u0] ObjBloc" //_CBblocChangeValue
 };

Use of a reflexive function in Scol

The purpose of the reflexive function is to log into the Scol console the updated values for the object ObjBloc. Let's add the following function into template.pkg file :

 /*! \brief Callback that show values of an ObjBloc whenever a property he owned is changed.
  *
  *  Values are save to console log.
  *
  *  <b>Prototype:</b> fun [u0 u1 u1 u2] I
  *
  *  \param u0: not used, but the bloc instance where a value has changed is passed.
  *  \param u1: not used
  *  \param I : bloc value
  *  \param S : bloc name
  *   
  *  \return I : 0
  **/
 fun blocHasChanged(blocInstance, userParam, newValue, newName)=
   // Log new values
   _fooS strcatn "Bloc name:  "::newName::"\nBloc value: "::(itoa newValue)::nil;
   0;;

We'll add a second function into the file template.pkg.
It's important to understand how this function works. Once the instance bloc which type is ObjBloc is created, we allocate the reflexive function using _CBblocChangeValue value.
Notice the '@' character just before the parameter, it represents the reflexive function that we want to call when the event is triggered. The character indicates that it's actually a pointer to the function. The parameter named u0 is not used, so in our case we will set its value to nil.

 /*! \brief Sample main function that show how to use a callback.
  *
  *  <b>Prototype:</b> fun [S I] I
  *
  *  \param S : bloc name
  *  \param I : bloc value
  *   
  *  \return I : 0
  **/
 fun objBlocTestEvent(nameValue, integerValue)=
   let _CRbloc _channel -> blocInstance in 
   {
     // Setting callback
     _CBblocChangeValue blocInstance @blocHasChanged nil; 

     // Set ObjBloc properties values
     _SETblocName blocInstance nameValue;
     _SETblocValue blocInstance integerValue;

     // Manually destroying blocInstance
     _DSbloc blocInstance;
   };
   0;;

Now, we'll create another file into our user partition : testObjBlocEvent.scol. This Scol program will load the package template.pkg that we have just updated, and run the function objBlocTestEvent using 2 parameters :
  • the name of the bloc (string value),
  • its value (integer).
 _load "template.pkg" 
 objBlocTestEvent "newBloc" ff

Finally, we can run the program testObjBlocEvent.scol. The log file should contain the following lines :

 Loading C:\Users\Jeff\Documents\Scol Voyager\Partition_LocalUsr\template.pkg ...
 typechecking
 fun main : fun [] I
 fun objBlocTest : fun [S I] I
 fun blocHasChanged : fun [u0 u1 I S] I
 fun objBlocTestEvent : fun [S I] I
 Generating bytecodes for 'main'...
 3 bytes generated (for a total of 375 bytes)
 Generating bytecodes for 'objBlocTest'...
 79 bytes generated (for a total of 454 bytes)
 Generating bytecodes for 'blocHasChanged'...
 51 bytes generated (for a total of 505 bytes)
 Generating bytecodes for 'objBlocTestEvent'...
 28 bytes generated (for a total of 533 bytes)
 Loading complete

 > exec: objBlocTestEvent "newBloc" ff

 _CRbloc
 _CRbloc ...initialization successful
 _CRbloc ...MMmalloc successful
 _CRbloc ...object creation successful
 ok
 _CBblocChangeValue ...adding reflex
 _SETblocName
 Bloc name:  newBloc
 Bloc value: 0
 ok
 _SETblocValue
 Bloc name:  newBloc
 Bloc value: 255
 ok
 _DSbloc
 Bloc object destroyed.
 ok