Project

General

Profile

Creation of a new type for Scol » History » Version 2

ModularMix, 10/18/2011 10:21 AM

1 2 ModularMix
h1. Creation of a new data type for Scol
2 1 ModularMix
3 2 ModularMix
Similarly with Scol functions, it's also possible to extend Scol language by creating new data types using the integration of C/C++ plugins.
4 1 ModularMix
5 2 ModularMix
h2. Definition of *Bloc* data type
6 1 ModularMix
7 2 ModularMix
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.
8
As we will implement this new data type using a standard C++ class, we'll also define default constructor and destructor.
9
Now, let's create a new C++ header file named *Bloc.h*. Copy/paste the following explicit code into it :
10 1 ModularMix
<pre>
11
 // Include providing MAX_PATH define
12
 #include <windows.h>
13
 
14
 /*!
15
  * Bloc class. A simple class that has a name and a value associated with it.
16
  * It allows simple operations like getting and setting its attributes.
17
  *
18
  **/
19
 class Bloc
20
 {
21
 private:
22
    int value;	            //!< The value of the bloc
23
    char name[MAX_PATH];    //!< The name of the bloc
24
 
25
 public:
26
    int getValue();
27
    char* getName();
28
    void setName(char*);
29
    void setValue(int);
30
    Bloc(void);
31
    ~Bloc(void);
32
 };
33
</pre>
34
35 2 ModularMix
36
Then, let's create the C++ source file (*Bloc.cpp*) in which we will implement the different functions.
37
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.
38 1 ModularMix
<pre>
39
 #include "Bloc.h"
40
 #include <string.h>
41
 #include <stdio.h>
42
 
43
 /*!
44
 * \brief Bloc constructor
45
 * 
46
 */
47
 Bloc::Bloc(void)
48
 {
49
    value=0;
50
    strcpy_s(name,"SuperBloc");
51
 }
52
 
53
 /*!
54
 * \brief Bloc destructor
55
 * 
56
 */
57
 Bloc::~Bloc(void)
58
 {
59
 }
60
 
61
 /*!
62
 * \brief Gives the bloc value
63
 * \return The bloc value 
64
 */
65
 int Bloc::getValue()
66
 {
67
    return value;
68
 }
69
 
70
 /*!
71
 * \brief Gives the bloc name
72
 * \return The bloc name
73
 * 
74
 */
75
 char * Bloc::getName()
76
 {
77
    return name;
78
 }
79
 
80
 /*!
81
 * \brief Set the bloc value
82
 * \param myValue : The new value 
83
 */
84
 void Bloc::setValue(int myValue)
85
 {
86
    value = myValue;
87
 }
88
 
89
 /*!
90
 * \brief Set the bloc name
91
 * \param myValue : The new name 
92
 */
93
 void Bloc::setName(char* myName)
94
 {
95
    strncpy_s(name, myName, MAX_PATH-1);
96
 }
97 2 ModularMix
</pre>
98
99
100
h2. Binding between C and Scol
101
102
Now we'll see how to be able to use *Bloc* object in Scol language, by integrating it into our plugin Template.
103
104
h3. Creation and destruction of *Bloc* object
105
106
The first step is to make available the definition of *Bloc* object in *template.cpp*, by including the file *Bloc.h* :
107
<pre>
108
 #include "Bloc.h"
109
</pre>
110
111
112
*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). 
113
<pre>
114
 //! Bloc Object in Scol
115
 int OBJBLOCSCOL;
116
 
117
 //! BlocObj is set here as a type (an int) for doxygen documentation
118
 typedef int BlocObj;
119
</pre>
120
121
The first function to be defined is the one which will allow to create the object and add it to the Scol stack.
122
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.
123
Once the input parameters have been checked, the creation of the *Bloc* object is done in two steps :
124
* The memory allocation ad the creation of the object in C++
125
* The allocation of a space in Scol stack, and the call to the function *OBJcreate* which will create the object in Scol 
126
<pre>
127
 /*! @ingroup group1
128
  * \brief _CRbloc : Open, initialize Bloc object
129
  *
130
  * <b>Prototype:</b> fun [Chn] BlocObj
131
  *
132
  * \param Chn : current channel
133
  *
134
  * \return BlocObj : Bloc object if success, NIL otherwise 
135
  **/
136
 int _CRbloc(mmachine m)
137
 {
138
      #ifdef _SCOL_DEBUG_
139
      MMechostr(MSKDEBUG,"_CRbloc\n");
140
      #endif
141
 
142
      // Declare local variables
143
      int k = 0;
144
 
145
      // Get the channel without pulling it (first element in the stack)
146
      int channel = MMget(m, 0);
147
 
148
      // Test the channel
149
      if (channel == NIL)
150
      { 
151
          MMechostr(MSKDEBUG, "Channel NIL\n");
152
          MMpull(m);                            // Pull the channel													
153
          MMpush(m, NIL);                       // Push NIL on the stack
154
          return 0;
155
      }
156
 
157
      // Create bloc instance
158
      Bloc * bloc = new Bloc();
159
 
160
      if (bloc == NULL)
161
      {
162
          MMechostr(MSKDEBUG, "_CRbloc ...initialization failed\n");
163
          SAFE_DELETE(bloc) ;
164
          MMpull(m);				// Pull the channel
165
          MMpush(m, NIL);			// Push NIL on the stack
166
          return 0;
167
      }
168
      MMechostr(MSKDEBUG,"_CRbloc ...initialization successful\n");
169
 
170
      // Allocate a space in the stack for a table of bloc objects
171
      int blocTab = MMmalloc(m, 1, TYPETAB);
172
      if (blocTab == NIL)
173
      {
174
          MMechostr(MSKDEBUG,"_CRbloc ...MMmalloc failed\n");
175
          SAFE_DELETE(bloc); 
176
          MMpull(m);				// Pull the channel
177
          return MMpush(m, NIL);		// Push NIL on the stack
178
      }
179
      MMechostr(MSKDEBUG,"_CRbloc ...MMmalloc successful\n");
180
 
181
      // Push the table of bloc objects into the stack
182
      MMstore(m, blocTab, 0, (int)bloc);
183
      MMpush(m, PTOM(blocTab));
184
 
185
      // Create a new scol bloc object
186
      k = OBJcreate(m, OBJBLOCSCOL, (int)bloc, NULL, NULL);
187
      MMechostr(MSKDEBUG,"_CRbloc ...object creation successful\n");
188
 
189
      #ifdef _SCOL_DEBUG_
190
      MMechostr(MSKDEBUG,"ok\n");
191
      #endif
192
 
193
      // Return bloc object
194
      return k;
195
 }
196
</pre>
197
198
The second function to define is the one to destroy the object on a query made by the Scol developer.
199
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.
200
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.
201
<pre>
202
 /*! @ingroup group1
203
  * \brief _DSbloc : Destroy bloc object
204
  *
205
  * <b>Prototype:</b> fun [BlocObj] I
206
  *
207
  * \param BlocObj : Bloc Object to destroy
208
  *
209
  * \return I : 0 if success, NIL otherwise 
210
  **/
211
 int _DSbloc(mmachine m)
212
 {
213
     #ifdef _SCOL_DEBUG_
214
     MMechostr(MSKDEBUG,"_DSbloc\n");
215
     #endif
216
 
217
     int bloc = MTOP( MMget(m,0) );
218
     if ( bloc == NIL ) { MMset(m,0,NIL); return 0; }
219
 
220
     OBJdelTM( m, OBJBLOCSCOL, PTOM(bloc) );
221
     MMset(m,0,0);	
222
 
223
     #ifdef _SCOL_DEBUG_
224
     MMechostr(MSKDEBUG,"ok\n");
225
     #endif
226
     return 0;
227
 }
228
</pre>
229
230
231
As previously stated, we have to define a callback function for the object destruction which will then be called by the GC.
232
This function will pop the object from the Scol stack and delete the C++ object from the memory.
233
<pre>
234
 /*!
235
  * \brief Scol object destroy callback
236
  *
237
  * \param mmachine : scol machine structure
238
  * \param int : scol object system handle
239
  * \param int : scol object stack handle
240
  *
241
  * \return int : 0
242
  **/
243
 int destroyBlocObj(mmachine m, int handsys, int blocTab)
244
 {
245
     // Read the first element of a TAB element (table of objects)
246
     Bloc* bloc = (Bloc*) MMfetch(m, MTOP(blocTab), 0);
247
     if (bloc == NULL)
248
     {
249
         // Write the first element in the stack, without pulling it
250
         MMset(m, 0, NIL); 
251
         return 0;
252
     }
253
 
254
     // Safely dispose of "Bloc" pointer
255
     SAFE_DELETE(bloc);
256
 
257
     // Write the first element of a TAB element
258
     MMstore(m, MTOP(blocTab), 0, NULL);
259
 
260
     // Display debug message
261
     MMechostr(MSKDEBUG,"Bloc object destroyed.\n");
262
     return 0;
263
 }
264
</pre>
265
266
267
Given that all our functions are defined, we will register the new data type for Scol.
268
It's important to notice :
269
* the binding to the callback function for the object destruction (*destroyBlocObj*),
270
* the parameter "OBJBLOCSCOL" which corresponds to the type name as it will be used in a Scol program.
271
272
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).
273
<pre>
274
 // Declare a new type of object ("OBJBLOCSCOL")
275
 OBJBLOCSCOL = OBJregister(0 /*nb of callback*/, 1/* deleted from parent */, destroyBlocObj, "OBJBLOCSCOL");
276
</pre>
277
278
279
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.
280
We notice the specific values used to bind the Scol data type, which name will be *ObjBloc*.
281
<pre>
282
 //! Nb of Scol functions or types
283
 #define NbTplPKG	4
284
 
285
 /*!
286
  * Scol function names
287
  **/
288
 char* TplName[NbTplPKG] =
289
 {
290
     "_HelloWorld",
291
     "ObjBloc",
292
     "_CRbloc",
293
     "_DSbloc"
294
 };
295
 
296
 /*!
297
  * Pointers to C functions that manipulate the VM for each scol function previously defined
298
  **/
299
 int (*TplFunc[NbTplPKG])(mmachine m)=
300
 {
301
     _HelloWorld,
302
     NULL,
303
     _CRbloc,
304
     _DSbloc
305
 };
306
 
307
 /*!
308
  * Nb of arguments of each scol function
309
  **/
310
 int TplNArg[NbTplPKG]=
311
 {
312
     0,
313
     TYPTYPE,
314
     1,
315
     1
316
 };
317
 
318
 /*!
319
  * Prototypes of the scol functions
320
  **/
321
 char* TplType[NbTplPKG]=
322
 {
323
     "fun [] I",                // _HelloWorld
324
     NULL,
325
     "fun [Chn] ObjBloc",	// _CRbloc
326
     "fun [ObjBloc] I"          // _DSbloc
327
 };
328
</pre>
329
330
h3. Access to the properties of Bloc object
331
332
To access the properties of *Bloc* object, we will define the getters and setters for each property of the object.
333
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).
334
<pre>
335
 /*! @ingroup group1
336
  * \brief _GETblocValue : Get the value of the Bloc object
337
  *
338
  * <b>Prototype:</b> fun [BlocObj] I
339
  *
340
  * \param BlocObj : bloc object
341
  *
342
  * \return I : return bloc value, nil otherwise
343
  **/
344
 int _GETblocValue(mmachine m)
345
 {
346
     #ifdef _SCOL_DEBUG_
347
     MMechostr(MSKDEBUG,"_GETblocValue\n");
348
     #endif
349
     // Get bloc table from the stack, then transform it
350
     int blocTable = MTOP( MMget(m,0) ) ;
351
     if ( blocTable == NIL ) { MMset(m,0,NIL) ; return 0 ; }
352
 
353
     // cast this bloc in a Bloc object
354
     Bloc* bloc = (Bloc*)MMfetch(m, blocTable, 0) ;
355
     if( !bloc ) { MMset(m,0,NIL) ; return 0 ; }
356
 
357
     // put the return of the function call in the stack 
358
     MMset(m,0,ITOM(bloc->getValue()));
359
 
360
     #ifdef _SCOL_DEBUG_
361
     MMechostr(MSKDEBUG,"ok\n");
362
     #endif
363
 
364
     return 0;
365
 }
366
 
367
 /*! @ingroup group1
368
  * \brief _SETblocValue : Set the value of the bloc
369
  *
370
  * <b>Prototype:</b> fun [BlocObj I] I
371
  *
372
  * \param BlocObj : bloc object
373
  * \param I : New value
374
  *
375
  * \return I : 0 if success, -1 otherwise 
376
  **/
377
 int _SETblocValue(mmachine m)
378
 {
379
     #ifdef _SCOL_DEBUG_
380
     MMechostr(MSKDEBUG,"_SETblocValue\n");
381
     #endif
382
     // Get param
383
     int value = MTOI( MMpull(m) ) ;
384
 
385
     // Get Bloc table
386
     int blocTable = MTOP( MMget(m,0) ) ;
387
     if ( blocTable == NIL ) { MMset(m,0,-1) ; return 0 ; }
388
 
389
     // Cast the content of bloc table to Bloc*
390
     Bloc* bloc = (Bloc*)MMfetch(m, blocTable, 0) ;
391
     bloc->setValue(value);
392
     MMset(m,0,0);
393
 
394
     #ifdef _SCOL_DEBUG_
395
     MMechostr(MSKDEBUG,"ok\n");
396
     #endif
397
 
398
     return 0;
399
 }
400
 
401
 /*! @ingroup group1
402
  * \brief _GETblocName : Get the name of the bloc
403
  *
404
  * <b>Prototype:</b> fun [BlocObj] S
405
  *
406
  * \param BlocObj : bloc object
407
  *
408
  * \return S : The name of the bloc if success, NIL otherwise 
409
  **/
410
 int _GETblocName(mmachine m)
411
 {
412
     #ifdef _SCOL_DEBUG_
413
     MMechostr(MSKDEBUG,"_GETblocName\n");
414
     #endif
415
     // Get bloc table in the stack, then transform it in system pointer
416
     int blocTable = MTOP( MMget(m,0) ) ;
417
     if ( blocTable == NIL ) { MMset(m,0,NIL) ; return 0 ; }
418
 
419
     // cast this bloc in a Bloc object
420
     Bloc* bloc = (Bloc*)MMfetch(m, blocTable, 0) ;
421
     if( !bloc ) { MMset(m,0,NIL) ; return 0 ; }
422
 
423
     // remove param from stack
424
     MMpull(m);
425
 
426
     // put in the stack the return of the function call  
427
     Mpushstrbloc(m, bloc->getName());
428
 
429
     #ifdef _SCOL_DEBUG_
430
     MMechostr(MSKDEBUG,"ok\n");
431
     #endif
432
 
433
     return 0;
434
 }
435
 
436
 /*! @ingroup group1
437
  * \brief _SETblocName : Set the name of the bloc
438
  *
439
  * <b>Prototype:</b> fun [BlocObj S] I
440
  *
441
  * \param BlocObj : bloc object
442
  * \param S : New name
443
  *
444
  * \return I : 0 if success, -1 otherwise 
445
  **/
446
 int _SETblocName(mmachine m)
447
 {
448
     #ifdef _SCOL_DEBUG_
449
     MMechostr(MSKDEBUG,"_SETblocName\n");
450
     #endif
451
     // Get param
452
     int name = MTOP( MMpull(m) ) ;
453
 
454
     // Get Bloc table
455
     int blocTable = MTOP( MMget(m,0) ) ;
456
     if ( blocTable == NIL ) { MMset(m,0,-1) ; return 0 ; }
457
 
458
     // Cast the content of bloc table to Bloc*
459
     Bloc* bloc = (Bloc*)MMfetch(m, blocTable, 0) ;
460
 
461
     char * sname = MMstartstr(m, name);
462
     bloc->setName(sname);
463
 
464
     MMset(m,0,0);
465
     
466
     #ifdef _SCOL_DEBUG_
467
     MMechostr(MSKDEBUG,"ok\n");
468
     #endif
469
 
470
     return 0;
471
 }
472
</pre>
473
474
475
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*).
476
<pre>
477
 //! Nb of Scol functions or types
478
 #define NbTplPKG	8
479
 
480
 /*!
481
  * Scol function names
482
  **/
483
 char* TplName[NbTplPKG] =
484
 {
485
     "_HelloWorld",
486
     "ObjBloc",
487
     "_CRbloc",
488
     "_DSbloc",
489
     "_GETblocValue",
490
     "_GETblocName",
491
     "_SETblocName",
492
     "_SETblocValue"
493
 };
494
 
495
 /*!
496
  * Pointers to C functions that manipulate the VM for each scol function previously defined
497
  **/
498
 int (*TplFunc[NbTplPKG])(mmachine m)=
499
 {
500
     _HelloWorld,
501
     NULL,
502
     _CRbloc,
503
     _DSbloc,
504
     _GETblocValue,
505
     _GETblocName,
506
     _SETblocName,
507
     _SETblocValue
508
 };
509
 
510
 /*!
511
  * Nb of arguments of each scol function
512
  **/
513
 int TplNArg[NbTplPKG]=
514
 {
515
     0,
516
     TYPTYPE,
517
     1,
518
     1,
519
     1,
520
     1,
521
     2,
522
     2
523
 };
524
 
525
 /*!
526
  * Prototypes of the scol functions
527
  **/
528
 char* TplType[NbTplPKG]=
529
 {
530
     "fun [] I",                                        // _HelloWorld
531
     NULL,
532
     "fun [Chn] ObjBloc",				// _CRbloc
533
     "fun [ObjBloc] I",					// _DSbloc
534
     "fun [ObjBloc] I",                                 // _GETblocValue
535
     "fun [ObjBloc] S",                                 // _GETblocName
536
     "fun [ObjBloc S] I",                               // _SETblocName
537
     "fun [ObjBloc I] I"                                // _SETblocValue
538
 };
539
</pre>
540
541
542
We can now recompile the project and move the generated DLL file to the *plugins* folder of Scol Voyager.
543
544
545
h2. Use of the new data type in Scol
546
547
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.
548
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 :
549
<pre>
550
 /*! \brief Sample main function that show how to use a custom C++ type in Scol.
551
  *
552
  *  The custom type is defined within a C++ plugin.
553
  *  We checks creation of a new instance of the object, setting values in,
554
  *  reading the values stored in, and then we manually deleting the instance.
555
  *
556
  *  <b>Prototype:</b> fun [S I] I
557
  *
558
  *  \param S : bloc name
559
  *  \param I : bloc value
560
  *   
561
  *  \return I : 0
562
  **/
563
 fun ObjBlocTest(nameValue, integerValue)=
564
     let _CRbloc _channel -> blocInstance in 
565
     {
566
         // Set ObjBloc properties values
567
         _SETblocName blocInstance nameValue;
568
         _SETblocValue blocInstance integerValue;    
569
 
570
         // Check if values where correctly registered and log them on the console
571
         _fooS strcatn "Bloc name:  "::(_GETblocName blocInstance)::"\nBloc value: "::(itoa (_GETblocValue blocInstance))::nil;
572
 
573
         // Manually destroying blocInstance
574
         _DSbloc blocInstance;
575
     };
576
     0;;
577
</pre>
578
579
580
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.
581
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').
582
<pre>
583
 _load "template.pkg"
584
 ObjBlocTest "newBloc" ff
585
</pre>
586
587
588
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 :
589
<pre>
590
 Loading C:\Users\Jeff\Documents\Scol Voyager\Partition_LocalUsr\template.pkg ...
591
 typechecking
592
 fun main : fun [] I
593
 fun ObjBlocTest : fun [S I] I
594
 Generating bytecodes for 'main'...
595
 3 bytes generated (for a total of 375 bytes)
596
 Generating bytecodes for 'ObjBlocTest'...
597
 79 bytes generated (for a total of 453 bytes)
598
 Loading complete
599
 
600
 
601
 > exec: ObjBlocTest "newBloc" ff
602
 
603
 _CRbloc
604
 _CRbloc ...initialization successful
605
 _CRbloc ...MMmalloc successful
606
 _CRbloc ...object creation successful
607
 ok
608
 _SETblocName
609
 ok
610
 _SETblocValue
611
 ok
612
 _GETblocName
613
 ok
614
 _GETblocValue
615
 ok
616
 Bloc name:  newBloc
617
 Bloc value: 255
618
 _DSbloc
619
 Bloc object destroyed.
620
 ok
621 1 ModularMix
</pre>