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
Updated by ModularMix over 13 years ago ยท 3 revisions