Creation of a new type for Scol

Similarly with Scol functions, it's also possible to extend Scol language by creating new data types using the integration of C/C++ plugins.

Definition of Bloc data type

In this tutorial, we will create a basic data type which will store a name and an integer value. We'll also define the getters and setters for each of the properties.
As we will implement this new data type using a standard C++ class, we'll also define default constructor and destructor.
Now, let's create a new C++ header file named Bloc.h. Copy/paste the following explicit code into it :

 // Include providing MAX_PATH define
 #include <windows.h>

 /*!
  * Bloc class. A simple class that has a name and a value associated with it.
  * It allows simple operations like getting and setting its attributes.
  *
  **/
 class Bloc
 {
 private:
    int value;                //!< The value of the bloc
    char name[MAX_PATH];    //!< The name of the bloc

 public:
    int getValue();
    char* getName();
    void setName(char*);
    void setValue(int);
    Bloc(void);
    ~Bloc(void);
 };

Then, let's create the C++ source file (Bloc.cpp) in which we will implement the different functions.
The source code is still very explicit, our object will act as a property container that we can get or set using getters and setters.

 #include "Bloc.h" 
 #include <string.h>
 #include <stdio.h>

 /*!
 * \brief Bloc constructor
 * 
 */
 Bloc::Bloc(void)
 {
    value=0;
    strcpy_s(name,"SuperBloc");
 }

 /*!
 * \brief Bloc destructor
 * 
 */
 Bloc::~Bloc(void)
 {
 }

 /*!
 * \brief Gives the bloc value
 * \return The bloc value 
 */
 int Bloc::getValue()
 {
    return value;
 }

 /*!
 * \brief Gives the bloc name
 * \return The bloc name
 * 
 */
 char * Bloc::getName()
 {
    return name;
 }

 /*!
 * \brief Set the bloc value
 * \param myValue : The new value 
 */
 void Bloc::setValue(int myValue)
 {
    value = myValue;
 }

 /*!
 * \brief Set the bloc name
 * \param myValue : The new name 
 */
 void Bloc::setName(char* myName)
 {
    strncpy_s(name, myName, MAX_PATH-1);
 }

Binding between C and Scol

Now we'll see how to be able to use Bloc object in Scol language, by integrating it into our plugin Template.

Creation and destruction of Bloc object

The first step is to make available the definition of Bloc object in template.cpp, by including the file Bloc.h :

 #include "Bloc.h" 

OBJBLOCSCOL enables to store the unique ID linked to the new Scol type. This ID is allocated by Scol virtual machine during the registration of this new type (we will explain this later in this document).

 //! Bloc Object in Scol
 int OBJBLOCSCOL;

 //! BlocObj is set here as a type (an int) for doxygen documentation
 typedef int BlocObj;

The first function to be defined is the one which will allow to create the object and add it to the Scol stack.
This one will correspond to the followin Scol prototype : fun [Chn] BlocObj. We have to check that the first element on the Scol stack is a valid channel.
Once the input parameters have been checked, the creation of the Bloc object is done in two steps :
  • The memory allocation ad the creation of the object in C++
  • The allocation of a space in Scol stack, and the call to the function OBJcreate which will create the object in Scol
     /*! @ingroup group1
      * \brief _CRbloc : Open, initialize Bloc object
      *
      * <b>Prototype:</b> fun [Chn] BlocObj
      *
      * \param Chn : current channel
      *
      * \return BlocObj : Bloc object if success, NIL otherwise 
      **/
     int _CRbloc(mmachine m)
     {
          #ifdef _SCOL_DEBUG_
          MMechostr(MSKDEBUG,"_CRbloc\n");
          #endif
    
          // Declare local variables
          int k = 0;
    
          // Get the channel without pulling it (first element in the stack)
          int channel = MMget(m, 0);
    
          // Test the channel
          if (channel == NIL)
          { 
              MMechostr(MSKDEBUG, "Channel NIL\n");
              MMpull(m);                            // Pull the channel                                                    
              MMpush(m, NIL);                       // Push NIL on the stack
              return 0;
          }
    
          // Create bloc instance
          Bloc * bloc = new Bloc();
    
          if (bloc == NULL)
          {
              MMechostr(MSKDEBUG, "_CRbloc ...initialization failed\n");
              SAFE_DELETE(bloc) ;
              MMpull(m);                // Pull the channel
              MMpush(m, NIL);            // Push NIL on the stack
              return 0;
          }
          MMechostr(MSKDEBUG,"_CRbloc ...initialization successful\n");
    
          // Allocate a space in the stack for a table of bloc objects
          int blocTab = MMmalloc(m, 1, TYPETAB);
          if (blocTab == NIL)
          {
              MMechostr(MSKDEBUG,"_CRbloc ...MMmalloc failed\n");
              SAFE_DELETE(bloc); 
              MMpull(m);                // Pull the channel
              return MMpush(m, NIL);        // Push NIL on the stack
          }
          MMechostr(MSKDEBUG,"_CRbloc ...MMmalloc successful\n");
    
          // Push the table of bloc objects into the stack
          MMstore(m, blocTab, 0, (int)bloc);
          MMpush(m, PTOM(blocTab));
    
          // Create a new scol bloc object
          k = OBJcreate(m, OBJBLOCSCOL, (int)bloc, NULL, NULL);
          MMechostr(MSKDEBUG,"_CRbloc ...object creation successful\n");
    
          #ifdef _SCOL_DEBUG_
          MMechostr(MSKDEBUG,"ok\n");
          #endif
    
          // Return bloc object
          return k;
     }
    

The second function to define is the one to destroy the object on a query made by the Scol developer.
The singularity of this function is that we don't pop from the Scol stack the object passed as a parameter, and we don't delete the associated C++ object either.
Instead of this, we use the Scol Garbage Collector (GC) using the function OBJdelTM from the Scol API. This function needs to know the Scol type of the object we want to delete.

 /*! @ingroup group1
  * \brief _DSbloc : Destroy bloc object
  *
  * <b>Prototype:</b> fun [BlocObj] I
  *
  * \param BlocObj : Bloc Object to destroy
  *
  * \return I : 0 if success, NIL otherwise 
  **/
 int _DSbloc(mmachine m)
 {
     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"_DSbloc\n");
     #endif

     int bloc = MTOP( MMget(m,0) );
     if ( bloc == NIL ) { MMset(m,0,NIL); return 0; }

     OBJdelTM( m, OBJBLOCSCOL, PTOM(bloc) );
     MMset(m,0,0);    

     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"ok\n");
     #endif
     return 0;
 }

As previously stated, we have to define a callback function for the object destruction which will then be called by the GC.
This function will pop the object from the Scol stack and delete the C++ object from the memory.

 /*!
  * \brief Scol object destroy callback
  *
  * \param mmachine : scol machine structure
  * \param int : scol object system handle
  * \param int : scol object stack handle
  *
  * \return int : 0
  **/
 int destroyBlocObj(mmachine m, int handsys, int blocTab)
 {
     // Read the first element of a TAB element (table of objects)
     Bloc* bloc = (Bloc*) MMfetch(m, MTOP(blocTab), 0);
     if (bloc == NULL)
     {
         // Write the first element in the stack, without pulling it
         MMset(m, 0, NIL); 
         return 0;
     }

     // Safely dispose of "Bloc" pointer
     SAFE_DELETE(bloc);

     // Write the first element of a TAB element
     MMstore(m, MTOP(blocTab), 0, NULL);

     // Display debug message
     MMechostr(MSKDEBUG,"Bloc object destroyed.\n");
     return 0;
 }

Given that all our functions are defined, we will register the new data type for Scol.
It's important to notice :
  • the binding to the callback function for the object destruction (destroyBlocObj),
  • the parameter "OBJBLOCSCOL" which corresponds to the type name as it will be used in a Scol program.

Finally, the function returns an ID for the new data type, which is stored in the global variable OBJBLOCSCOL (we remind that this variable is used in the function _DSBloc to notice to the GC the object type to delete).

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

The binding of the C++ functions enabling to create and delete a Bloc object in Scol is registered using PKhardpak function, so we have to modify the parameters of this function.
We notice the specific values used to bind the Scol data type, which name will be ObjBloc.

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

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

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

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

 /*!
  * Prototypes of the scol functions
  **/
 char* TplType[NbTplPKG]=
 {
     "fun [] I",                // _HelloWorld
     NULL,
     "fun [Chn] ObjBloc",    // _CRbloc
     "fun [ObjBloc] I"          // _DSbloc
 };

Access to the properties of Bloc object

To access the properties of Bloc object, we will define the getters and setters for each property of the object.
The important point here is to well observe and understand for each function the different operations updating the Scol stack, regarding the Scol prototype (retrieve Scol parameters, check all of them and return the appropriate Scol value).

 /*! @ingroup group1
  * \brief _GETblocValue : Get the value of the Bloc object
  *
  * <b>Prototype:</b> fun [BlocObj] I
  *
  * \param BlocObj : bloc object
  *
  * \return I : return bloc value, nil otherwise
  **/
 int _GETblocValue(mmachine m)
 {
     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"_GETblocValue\n");
     #endif
     // Get bloc table from the stack, then transform it
     int blocTable = MTOP( MMget(m,0) ) ;
     if ( blocTable == NIL ) { MMset(m,0,NIL) ; return 0 ; }

     // cast this bloc in a Bloc object
     Bloc* bloc = (Bloc*)MMfetch(m, blocTable, 0) ;
     if( !bloc ) { MMset(m,0,NIL) ; return 0 ; }

     // put the return of the function call in the stack 
     MMset(m,0,ITOM(bloc->getValue()));

     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"ok\n");
     #endif

     return 0;
 }

 /*! @ingroup group1
  * \brief _SETblocValue : Set the value of the bloc
  *
  * <b>Prototype:</b> fun [BlocObj I] I
  *
  * \param BlocObj : bloc object
  * \param I : New value
  *
  * \return I : 0 if success, -1 otherwise 
  **/
 int _SETblocValue(mmachine m)
 {
     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"_SETblocValue\n");
     #endif
     // Get param
     int value = MTOI( MMpull(m) ) ;

     // Get Bloc table
     int blocTable = MTOP( MMget(m,0) ) ;
     if ( blocTable == NIL ) { MMset(m,0,-1) ; return 0 ; }

     // Cast the content of bloc table to Bloc*
     Bloc* bloc = (Bloc*)MMfetch(m, blocTable, 0) ;
     bloc->setValue(value);
     MMset(m,0,0);

     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"ok\n");
     #endif

     return 0;
 }

 /*! @ingroup group1
  * \brief _GETblocName : Get the name of the bloc
  *
  * <b>Prototype:</b> fun [BlocObj] S
  *
  * \param BlocObj : bloc object
  *
  * \return S : The name of the bloc if success, NIL otherwise 
  **/
 int _GETblocName(mmachine m)
 {
     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"_GETblocName\n");
     #endif
     // Get bloc table in the stack, then transform it in system pointer
     int blocTable = MTOP( MMget(m,0) ) ;
     if ( blocTable == NIL ) { MMset(m,0,NIL) ; return 0 ; }

     // cast this bloc in a Bloc object
     Bloc* bloc = (Bloc*)MMfetch(m, blocTable, 0) ;
     if( !bloc ) { MMset(m,0,NIL) ; return 0 ; }

     // remove param from stack
     MMpull(m);

     // put in the stack the return of the function call  
     Mpushstrbloc(m, bloc->getName());

     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"ok\n");
     #endif

     return 0;
 }

 /*! @ingroup group1
  * \brief _SETblocName : Set the name of the bloc
  *
  * <b>Prototype:</b> fun [BlocObj S] I
  *
  * \param BlocObj : bloc object
  * \param S : New name
  *
  * \return I : 0 if success, -1 otherwise 
  **/
 int _SETblocName(mmachine m)
 {
     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"_SETblocName\n");
     #endif
     // Get param
     int name = MTOP( MMpull(m) ) ;

     // Get Bloc table
     int blocTable = MTOP( MMget(m,0) ) ;
     if ( blocTable == NIL ) { MMset(m,0,-1) ; return 0 ; }

     // Cast the content of bloc table to Bloc*
     Bloc* bloc = (Bloc*)MMfetch(m, blocTable, 0) ;

     char * sname = MMstartstr(m, name);
     bloc->setName(sname);

     MMset(m,0,0);

     #ifdef _SCOL_DEBUG_
     MMechostr(MSKDEBUG,"ok\n");
     #endif

     return 0;
 }

As explained earlier, we will add the binding of our new functions to the variables used during the registration of the package (when calling PKhardpak).

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

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

 /*!
  * 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
 };

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

 /*!
  * 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
 };

We can now recompile the project and move the generated DLL file to the plugins folder of Scol Voyager.

Use of the new data type in Scol

In this section, we will explain how to create a Scol program which will be using the ObjBloc data type that we have just created.
The first thing to do is to update the template.pkg file, which must still be located in the user partition of Scol Voyager, by adding the following function :

 /*! \brief Sample main function that show how to use a custom C++ type in Scol.
  *
  *  The custom type is defined within a C++ plugin.
  *  We checks creation of a new instance of the object, setting values in,
  *  reading the values stored in, and then we manually deleting the instance.
  *
  *  <b>Prototype:</b> fun [S I] I
  *
  *  \param S : bloc name
  *  \param I : bloc value
  *   
  *  \return I : 0
  **/
 fun ObjBlocTest(nameValue, integerValue)=
     let _CRbloc _channel -> blocInstance in 
     {
         // Set ObjBloc properties values
         _SETblocName blocInstance nameValue;
         _SETblocValue blocInstance integerValue;    

         // Check if values where correctly registered and log them on the console
         _fooS strcatn "Bloc name:  "::(_GETblocName blocInstance)::"\nBloc value: "::(itoa (_GETblocValue blocInstance))::nil;

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

Now, let's create a new file in our user partition, named TestObjBloc.scol. This program will load the template.pkg package that we have just updated, and run the function ObjBlocTest by passing to it 2 parameters.
It's important to notice that in Scol language, to pass an integer as a parameter in a .scol file, we must use its hexadecimal representation (in our case, 'ff' stands for '255').

 _load "template.pkg" 
 ObjBlocTest "newBloc" ff

Now, we can run the Scol program TestObjBloc.scol. The log file (usually located in C:\Users\MyUser\AppData\Local\Scol Voyager\Logs) should be close to 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
 Generating bytecodes for 'main'...
 3 bytes generated (for a total of 375 bytes)
 Generating bytecodes for 'ObjBlocTest'...
 79 bytes generated (for a total of 453 bytes)
 Loading complete

 > exec: ObjBlocTest "newBloc" ff

 _CRbloc
 _CRbloc ...initialization successful
 _CRbloc ...MMmalloc successful
 _CRbloc ...object creation successful
 ok
 _SETblocName
 ok
 _SETblocValue
 ok
 _GETblocName
 ok
 _GETblocValue
 ok
 Bloc name:  newBloc
 Bloc value: 255
 _DSbloc
 Bloc object destroyed.
 ok