More work on SpiderScript
[tpg/acess2.git] / Usermode / Libraries / libspiderscript.so_src / exec_ast.c
1 /*
2  */
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include "ast.h"
7
8 // === PROTOTYPES ===
9 void    Object_Dereference(tSpiderValue *Object);
10 void    Object_Reference(tSpiderValue *Object);
11 tSpiderValue    *Object_CreateInteger(uint64_t Value);
12 tSpiderValue    *Object_CreateReal(double Value);
13 tSpiderValue    *Object_CreateString(int Length, const char *Data);
14 tSpiderValue    *Object_CastTo(int Type, tSpiderValue *Source);
15  int    Object_IsTrue(tSpiderValue *Value);
16
17 tSpiderValue    *AST_ExecuteNode(tAST_BlockState *Block, tAST_Node *Node);
18
19 tAST_Variable *Variable_Define(tAST_BlockState *Block, int Type, const char *Name);
20 void    Variable_SetValue(tAST_BlockState *Block, const char *Name, tSpiderValue *Value);
21 tSpiderValue    *Variable_GetValue(tAST_BlockState *Block, const char *Name);
22 void    Variable_Destroy(tAST_Variable *Variable);
23
24 // === CODE ===
25 /**
26  * \brief Dereference a created object
27  */
28 void Object_Dereference(tSpiderValue *Object)
29 {
30         if(!Object)     return ;
31         Object->ReferenceCount --;
32         if( Object->ReferenceCount == 0 ) {
33                 switch( (enum eSpiderScript_DataTypes) Object->Type )
34                 {
35                 case SS_DATATYPE_OBJECT:
36                         Object->Object->Type->Destructor( Object->Object );
37                         break;
38                 case SS_DATATYPE_OPAQUE:
39                         Object->Opaque.Destroy( Object->Opaque.Data );
40                         break;
41                 default:
42                         break;
43                 }
44                 free(Object);
45         }
46 }
47
48 void Object_Reference(tSpiderValue *Object)
49 {
50         if(!Object)     return ;
51         Object->ReferenceCount ++;
52 }
53
54 /**
55  * \brief Create an integer object
56  */
57 tSpiderValue *Object_CreateInteger(uint64_t Value)
58 {
59         tSpiderValue    *ret = malloc( sizeof(tSpiderValue) );
60         ret->Type = SS_DATATYPE_INTEGER;
61         ret->ReferenceCount = 1;
62         ret->Integer = Value;
63         return ret;
64 }
65
66 /**
67  * \brief Create an real number object
68  */
69 tSpiderValue *Object_CreateReal(double Value)
70 {
71         tSpiderValue    *ret = malloc( sizeof(tSpiderValue) );
72         ret->Type = SS_DATATYPE_REAL;
73         ret->ReferenceCount = 1;
74         ret->Real = Value;
75         return ret;
76 }
77
78 /**
79  * \brief Create an string object
80  */
81 tSpiderValue *Object_CreateString(int Length, const char *Data)
82 {
83         tSpiderValue    *ret = malloc( sizeof(tSpiderValue) + Length + 1 );
84         ret->Type = SS_DATATYPE_STRING;
85         ret->ReferenceCount = 1;
86         ret->String.Length = Length;
87         memcpy(ret->String.Data, Data, Length);
88         ret->String.Data[Length] = '\0';
89         return ret;
90 }
91
92 /**
93  * \brief Concatenate two strings
94  */
95 tSpiderValue *Object_StringConcat(tSpiderValue *Str1, tSpiderValue *Str2)
96 {
97          int    newLen = 0;
98         tSpiderValue    *ret;
99         if(Str1)        newLen += Str1->String.Length;
100         if(Str2)        newLen += Str2->String.Length;
101         ret = malloc( sizeof(tSpiderValue) + newLen + 1 );
102         ret->Type = SS_DATATYPE_STRING;
103         ret->ReferenceCount = 1;
104         ret->String.Length = newLen;
105         if(Str1)
106                 memcpy(ret->String.Data, Str1->String.Data, Str1->String.Length);
107         if(Str2) {
108                 if(Str1)
109                         memcpy(ret->String.Data+Str1->String.Length, Str2->String.Data, Str2->String.Length);
110                 else
111                         memcpy(ret->String.Data, Str2->String.Data, Str2->String.Length);
112         }
113         ret->String.Data[ newLen ] = '\0';
114         return ret;
115 }
116
117 /**
118  * \brief Cast one object to another
119  * \brief Type  Destination type
120  * \brief Source        Input data
121  */
122 tSpiderValue *Object_CastTo(int Type, tSpiderValue *Source)
123 {
124         tSpiderValue    *ret = ERRPTR;
125         // Check if anything needs to be done
126         if( Source->Type == Type ) {
127                 Object_Reference(Source);
128                 return Source;
129         }
130         
131         switch( (enum eSpiderScript_DataTypes)Type )
132         {
133         case SS_DATATYPE_UNDEF:
134         case SS_DATATYPE_NULL:
135         case SS_DATATYPE_ARRAY:
136         case SS_DATATYPE_OPAQUE:
137                 fprintf(stderr, "Object_CastTo - Invalid cast to %i\n", Type);
138                 return ERRPTR;
139         
140         case SS_DATATYPE_INTEGER:
141                 ret = malloc(sizeof(tSpiderValue));
142                 ret->Type = SS_DATATYPE_INTEGER;
143                 ret->ReferenceCount = 1;
144                 switch(Source->Type)
145                 {
146                 case SS_DATATYPE_INTEGER:       break;  // Handled above
147                 case SS_DATATYPE_STRING:        ret->Integer = atoi(Source->String.Data);       break;
148                 case SS_DATATYPE_REAL:  ret->Integer = Source->Real;    break;
149                 default:
150                         fprintf(stderr, "Object_CastTo - Invalid cast from %i\n", Source->Type);
151                         free(ret);
152                         ret = ERRPTR;
153                         break;
154                 }
155                 break;
156         default:
157                 fprintf(stderr, "BUG REPORT: Unimplemented cast target\n");
158                 break;
159         }
160         
161         return ret;
162 }
163
164 /**
165  * \brief Condenses a value down to a boolean
166  */
167 int Object_IsTrue(tSpiderValue *Value)
168 {
169         switch( (enum eSpiderScript_DataTypes)Value->Type )
170         {
171         case SS_DATATYPE_UNDEF:
172         case SS_DATATYPE_NULL:
173                 return 0;
174         
175         case SS_DATATYPE_INTEGER:
176                 return !!Value->Integer;
177         
178         case SS_DATATYPE_REAL:
179                 return (-.5f < Value->Real && Value->Real < 0.5f);
180         
181         case SS_DATATYPE_STRING:
182                 return Value->String.Length > 0;
183         
184         case SS_DATATYPE_OBJECT:
185                 return Value->Object != NULL;
186         
187         case SS_DATATYPE_OPAQUE:
188                 return Value->Opaque.Data != NULL;
189         
190         case SS_DATATYPE_ARRAY:
191                 return Value->Array.Length > 0;
192         default:
193                 fprintf(stderr, "Spiderscript internal error: Unknown type %i in Object_IsTrue\n", Value->Type);
194                 return 0;
195         }
196         return 0;
197 }
198
199 /**
200  * \brief Execute an AST node and return its value
201  */
202 tSpiderValue *AST_ExecuteNode(tAST_BlockState *Block, tAST_Node *Node)
203 {
204         tAST_Node       *node;
205         tSpiderValue    *ret = NULL, *tmpobj;
206         tSpiderValue    *op1, *op2;     // Binary operations
207          int    cmp;    // Used in comparisons
208         
209         switch(Node->Type)
210         {
211         // No Operation
212         case NODETYPE_NOP:      ret = NULL;     break;
213         
214         // Code block
215         case NODETYPE_BLOCK:
216                 {
217                         tAST_BlockState blockInfo;
218                         blockInfo.FirstVar = NULL;
219                         blockInfo.Parent = Block;
220                         blockInfo.Script = Block->Script;
221                         ret = NULL;
222                         for(node = Node->Block.FirstChild; node; node = node->NextSibling )
223                         {
224                                 if(node->Type == NODETYPE_RETURN) {
225                                         ret = AST_ExecuteNode(&blockInfo, node);
226                                         break ;
227                                 }
228                                 else {
229                                         tmpobj = AST_ExecuteNode(&blockInfo, node);
230                                         if(tmpobj == ERRPTR) {  // Error check
231                                                 ret = ERRPTR;
232                                                 break ;
233                                         }
234                                         if(tmpobj)      Object_Dereference(tmpobj);     // Free unused value
235                                 }
236                         }
237                         // Clean up variables
238                         while(blockInfo.FirstVar)
239                         {
240                                 tAST_Variable   *nextVar = blockInfo.FirstVar->Next;
241                                 Variable_Destroy( blockInfo.FirstVar );
242                                 blockInfo.FirstVar = nextVar;
243                         }
244                 }
245                 
246                 break;
247         
248         // Assignment
249         case NODETYPE_ASSIGN:
250                 if( Node->Assign.Dest->Type != NODETYPE_VARIABLE ) {
251                         fprintf(stderr, "Syntax error: LVALUE of assignment is not a variable\n");
252                         return ERRPTR;
253                 }
254                 ret = AST_ExecuteNode(Block, Node->Assign.Value);
255                 if(ret != ERRPTR)
256                         Variable_SetValue( Block, Node->Assign.Dest->Variable.Name, ret );
257                 break;
258         
259         // Function Call
260         case NODETYPE_FUNCTIONCALL:
261                 {
262                          int    nParams = 0;
263                         for(node = Node->FunctionCall.FirstArg; node; node = node->NextSibling) {
264                                 nParams ++;
265                         }
266                         // Logical block (used to allocate `params`)
267                         {
268                                 tSpiderValue    *params[nParams];
269                                  int    i=0;
270                                 for(node = Node->FunctionCall.FirstArg; node; node = node->NextSibling) {
271                                         params[i] = AST_ExecuteNode(Block, node);
272                                         if( params[i] == ERRPTR ) {
273                                                 while(i--)      Object_Dereference(params[i]);
274                                                 ret = ERRPTR;
275                                                 goto _return;
276                                         }
277                                         i ++;
278                                 }
279                                 
280                                 // Call the function (SpiderScript_ExecuteMethod does the
281                                 // required namespace handling)
282                                 ret = SpiderScript_ExecuteMethod(Block->Script, Node->FunctionCall.Name, nParams, params);
283                                 
284                                 // Dereference parameters
285                                 while(i--)      Object_Dereference(params[i]);
286                                 
287                                 // falls out
288                         }
289                 }
290                 break;
291         
292         // Return's special handling happens elsewhere
293         case NODETYPE_RETURN:
294                 ret = AST_ExecuteNode(Block, Node->UniOp.Value);
295                 break;
296         
297         // Define a variable
298         case NODETYPE_DEFVAR:
299                 ret = NULL;
300                 if( Variable_Define(Block, Node->DefVar.DataType, Node->DefVar.Name) == ERRPTR )
301                         ret = ERRPTR;
302                 break;
303         
304         // Variable
305         case NODETYPE_VARIABLE:
306                 ret = Variable_GetValue( Block, Node->Variable.Name );
307                 break;
308
309         // Cast a value to another
310         case NODETYPE_CAST:
311                 ret = Object_CastTo(
312                         Node->Cast.DataType,
313                         AST_ExecuteNode(Block, Node->Cast.Value)
314                         );
315                 break;
316
317         // Index into an array
318         case NODETYPE_INDEX:
319                 fprintf(stderr, "TODO: Array indexing\n");
320                 ret = ERRPTR;
321                 break;
322
323         // TODO: Implement runtime constants
324         case NODETYPE_CONSTANT:
325                 fprintf(stderr, "TODO: Runtime Constants\n");
326                 ret = ERRPTR;
327                 break;
328         // Constant Values
329         case NODETYPE_STRING:   ret = Object_CreateString( Node->String.Length, Node->String.Data );    break;
330         case NODETYPE_INTEGER:  ret = Object_CreateInteger( Node->Integer );    break;
331         case NODETYPE_REAL:     ret = Object_CreateReal( Node->Real );  break;
332         
333         // --- Operations ---
334         // Boolean Operations
335         case NODETYPE_LOGICALAND:       // Logical AND (&&)
336         case NODETYPE_LOGICALOR:        // Logical OR (||)
337         case NODETYPE_LOGICALXOR:       // Logical XOR (^^)
338                 op1 = AST_ExecuteNode(Block, Node->BinOp.Left);
339                 if(op1 == ERRPTR)       return ERRPTR;
340                 op2 = AST_ExecuteNode(Block, Node->BinOp.Right);
341                 if(op2 == ERRPTR) {
342                         Object_Dereference(op1);
343                         return ERRPTR;
344                 }
345                 
346                 switch( Node->Type )
347                 {
348                 case NODETYPE_LOGICALAND:
349                         ret = Object_CreateInteger( Object_IsTrue(op1) && Object_IsTrue(op2) );
350                         break;
351                 case NODETYPE_LOGICALOR:
352                         ret = Object_CreateInteger( Object_IsTrue(op1) || Object_IsTrue(op2) );
353                         break;
354                 case NODETYPE_LOGICALXOR:
355                         ret = Object_CreateInteger( Object_IsTrue(op1) ^ Object_IsTrue(op2) );
356                         break;
357                 default:        break;
358                 }
359                 
360                 // Free intermediate objects
361                 Object_Dereference(op1);
362                 Object_Dereference(op2);
363                 break;
364         
365         // Comparisons
366         case NODETYPE_EQUALS:
367         case NODETYPE_LESSTHAN:
368         case NODETYPE_GREATERTHAN:
369                 op1 = AST_ExecuteNode(Block, Node->BinOp.Left);
370                 if(op1 == ERRPTR)       return ERRPTR;
371                 op2 = AST_ExecuteNode(Block, Node->BinOp.Right);
372                 if(op2 == ERRPTR) {
373                         Object_Dereference(op1);
374                         return ERRPTR;
375                 }
376                 
377                 // No conversion done for NULL
378                 // TODO: Determine if this will ever be needed
379                 if( op1->Type == SS_DATATYPE_NULL )
380                 {
381                         // NULLs always typecheck
382                         ret = Object_CreateInteger(op2->Type == SS_DATATYPE_NULL);
383                         break;
384                 }
385                 
386                 // Convert types
387                 if( op1->Type != op2->Type ) {
388                         // If dynamically typed, convert op2 to op1's type
389                         if(Block->Script->Variant->bDyamicTyped)
390                         {
391                                 tmpobj = op2;
392                                 op2 = Object_CastTo(op1->Type, op2);
393                                 Object_Dereference(tmpobj);
394                                 if(op2 == ERRPTR) {
395                                         Object_Dereference(op1);
396                                         return ERRPTR;
397                                 }
398                         }
399                         // If statically typed, this should never happen, but catch it anyway
400                         else {
401                                 fprintf(stderr, "PARSER ERROR: Statically typed implicit cast\n");
402                                 ret = ERRPTR;
403                                 break;
404                         }
405                 }
406                 // Do operation
407                 switch(op1->Type)
408                 {
409                 // - NULL
410                 case SS_DATATYPE_NULL:  break;
411                 // - String Compare (does a strcmp, well memcmp)
412                 case SS_DATATYPE_STRING:
413                         // Call memcmp to do most of the work
414                         cmp = memcmp(
415                                 op1->String.Data, op2->String.Data,
416                                 (op1->String.Length < op2->String.Length) ? op1->String.Length : op2->String.Length
417                                 );
418                         // Handle reaching the end of the string
419                         if( cmp == 0 ) {
420                                 if( op1->String.Length == op2->String.Length )
421                                         cmp = 0;
422                                 else if( op1->String.Length < op2->String.Length )
423                                         cmp = 1;
424                                 else
425                                         cmp = -1;
426                         }
427                         break;
428                 default:
429                         fprintf(stderr, "SpiderScript internal error: TODO: Comparison of type %i\n", op1->Type);
430                         ret = ERRPTR;
431                         break;
432                 }
433                 
434                 // Free intermediate objects
435                 Object_Dereference(op1);
436                 Object_Dereference(op2);
437                 
438                 // Error check
439                 if( ret == ERRPTR )
440                         break;
441                 
442                 // Create return
443                 switch(Node->Type)
444                 {
445                 case NODETYPE_EQUALS:   ret = Object_CreateInteger(cmp == 0);   break;
446                 case NODETYPE_LESSTHAN: ret = Object_CreateInteger(cmp < 0);    break;
447                 case NODETYPE_GREATERTHAN:      ret = Object_CreateInteger(cmp > 0);    break;
448                 default:
449                         fprintf(stderr, "SpiderScript internal error: Exec,CmpOp unknown op %i", Node->Type);
450                         ret = ERRPTR;
451                         break;
452                 }
453                 break;
454         
455         // General Binary Operations
456         case NODETYPE_ADD:
457         case NODETYPE_SUBTRACT:
458         case NODETYPE_MULTIPLY:
459         case NODETYPE_DIVIDE:
460         case NODETYPE_MODULO:
461         case NODETYPE_BWAND:
462         case NODETYPE_BWOR:
463         case NODETYPE_BWXOR:
464         case NODETYPE_BITSHIFTLEFT:
465         case NODETYPE_BITSHIFTRIGHT:
466         case NODETYPE_BITROTATELEFT:
467                 // Get operands
468                 op1 = AST_ExecuteNode(Block, Node->BinOp.Left);
469                 if(op1 == ERRPTR)       return ERRPTR;
470                 op2 = AST_ExecuteNode(Block, Node->BinOp.Right);
471                 if(op2 == ERRPTR) {
472                         Object_Dereference(op1);
473                         return ERRPTR;
474                 }
475                 
476                 // Convert types
477                 if( op1 && op2 && op1->Type != op2->Type ) {
478                         // If dynamically typed, convert op2 to op1's type
479                         if(Block->Script->Variant->bDyamicTyped)
480                         {
481                                 tmpobj = op2;
482                                 op2 = Object_CastTo(op1->Type, op2);
483                                 Object_Dereference(tmpobj);
484                                 if(op2 == ERRPTR) {
485                                         Object_Dereference(op1);
486                                         return ERRPTR;
487                                 }
488                         }
489                         // If statically typed, this should never happen, but catch it anyway
490                         else {
491                                 fprintf(stderr, "PARSER ERROR: Statically typed implicit cast\n");
492                                 ret = ERRPTR;
493                                 break;
494                         }
495                 }
496                 
497                 // Do operation
498                 switch(op1->Type)
499                 {
500                 case SS_DATATYPE_NULL:  break;
501                 // String Concatenation
502                 case SS_DATATYPE_STRING:
503                         switch(Node->Type)
504                         {
505                         case NODETYPE_ADD:      // Concatenate
506                                 ret = Object_StringConcat(op1, op2);
507                                 break;
508                         default:
509                                 fprintf(stderr, "SpiderScript internal error: Exec,BinOP,String unknown op %i\n", Node->Type);
510                                 ret = ERRPTR;
511                                 break;
512                         }
513                         break;
514                 // Integer Operations
515                 case SS_DATATYPE_INTEGER:
516                         switch(Node->Type)
517                         {
518                         case NODETYPE_ADD:      ret = Object_CreateInteger( op1->Integer + op2->Integer );      break;
519                         case NODETYPE_SUBTRACT: ret = Object_CreateInteger( op1->Integer - op2->Integer );      break;
520                         case NODETYPE_MULTIPLY: ret = Object_CreateInteger( op1->Integer * op2->Integer );      break;
521                         case NODETYPE_DIVIDE:   ret = Object_CreateInteger( op1->Integer / op2->Integer );      break;
522                         case NODETYPE_MODULO:   ret = Object_CreateInteger( op1->Integer % op2->Integer );      break;
523                         case NODETYPE_BWAND:    ret = Object_CreateInteger( op1->Integer & op2->Integer );      break;
524                         case NODETYPE_BWOR:     ret = Object_CreateInteger( op1->Integer | op2->Integer );      break;
525                         case NODETYPE_BWXOR:    ret = Object_CreateInteger( op1->Integer ^ op2->Integer );      break;
526                         case NODETYPE_BITSHIFTLEFT:     ret = Object_CreateInteger( op1->Integer << op2->Integer );     break;
527                         case NODETYPE_BITSHIFTRIGHT:ret = Object_CreateInteger( op1->Integer >> op2->Integer ); break;
528                         case NODETYPE_BITROTATELEFT:
529                                 ret = Object_CreateInteger( (op1->Integer << op2->Integer) | (op1->Integer >> (64-op2->Integer)) );
530                                 break;
531                         default:
532                                 fprintf(stderr, "SpiderScript internal error: Exec,BinOP,Integer unknown op %i\n", Node->Type);
533                                 ret = ERRPTR;
534                                 break;
535                         }
536                         break;
537                 
538                 // Real Numbers
539                 case SS_DATATYPE_REAL:
540                         switch(Node->Type)
541                         {
542                         default:
543                                 fprintf(stderr, "SpiderScript internal error: Exec,BinOP,Real unknown op %i\n", Node->Type);
544                                 ret = ERRPTR;
545                                 break;
546                         }
547                         break;
548                 
549                 default:
550                         fprintf(stderr, "SpiderScript error: Invalid operation (%i) on type (%i)\n", Node->Type, op1->Type);
551                         ret = ERRPTR;
552                         break;
553                 }
554                 
555                 // Free intermediate objects
556                 Object_Dereference(op1);
557                 Object_Dereference(op2);
558                 break;
559         
560         //default:
561         //      ret = NULL;
562         //      fprintf(stderr, "ERROR: SpiderScript AST_ExecuteNode Unimplemented %i\n", Node->Type);
563         //      break;
564         }
565 _return:
566         return ret;
567 }
568
569 /**
570  * \brief Define a variable
571  * \param Block Current block state
572  * \param Type  Type of the variable
573  * \param Name  Name of the variable
574  * \return Boolean Failure
575  */
576 tAST_Variable *Variable_Define(tAST_BlockState *Block, int Type, const char *Name)
577 {
578         tAST_Variable   *var, *prev = NULL;
579         
580         for( var = Block->FirstVar; var; prev = var, var = var->Next )
581         {
582                 if( strcmp(var->Name, Name) == 0 ) {
583                         fprintf(stderr, "ERROR: Redefinition of variable '%s'\n", Name);
584                         return ERRPTR;
585                 }
586         }
587         
588         var = malloc( sizeof(tAST_Variable) + strlen(Name) + 1 );
589         var->Next = NULL;
590         var->Type = Type;
591         var->Object = NULL;
592         strcpy(var->Name, Name);
593         
594         if(prev)        prev->Next = var;
595         else    Block->FirstVar = var;
596         
597         //printf("Defined variable %s (%i)\n", Name, Type);
598         
599         return var;
600 }
601
602 /**
603  * \brief Set the value of a variable
604  */
605 void Variable_SetValue(tAST_BlockState *Block, const char *Name, tSpiderValue *Value)
606 {
607         tAST_Variable   *var;
608         tAST_BlockState *bs;
609         
610         for( bs = Block; bs; bs = bs->Parent )
611         {
612                 for( var = bs->FirstVar; var; var = var->Next )
613                 {
614                         if( strcmp(var->Name, Name) == 0 ) {
615                                 if( !Block->Script->Variant->bDyamicTyped
616                                  && (Value && var->Type != Value->Type) ) {
617                                         fprintf(stderr, "ERROR: Type mismatch assigning to '%s'\n", Name);
618                                         return ;
619                                 }
620                                 Object_Reference(Value);
621                                 Object_Dereference(var->Object);
622                                 var->Object = Value;
623                                 return ;
624                         }
625                 }
626         }
627         
628         if( Block->Script->Variant->bDyamicTyped )
629         {
630                 // Define variable
631                 var = Variable_Define(Block, Value->Type, Name);
632                 Object_Reference(Value);
633                 var->Object = Value;
634         }
635         else
636         {
637                 fprintf(stderr, "ERROR: Variable '%s' set while undefined\n", Name);
638         }
639 }
640
641 /**
642  * \brief Get the value of a variable
643  */
644 tSpiderValue *Variable_GetValue(tAST_BlockState *Block, const char *Name)
645 {
646         tAST_Variable   *var;
647         tAST_BlockState *bs;
648         
649         for( bs = Block; bs; bs = bs->Parent )
650         {
651                 for( var = bs->FirstVar; var; var = var->Next )
652                 {
653                         if( strcmp(var->Name, Name) == 0 ) {
654                                 Object_Reference(var->Object);
655                                 return var->Object;
656                         }
657                 }
658         }
659         
660         fprintf(stderr, "ERROR: Variable '%s' used undefined\n", Name);
661         
662         return ERRPTR;
663 }
664
665 /**
666  * \brief Destorys a variable
667  */
668 void Variable_Destroy(tAST_Variable *Variable)
669 {
670         Object_Dereference(Variable->Object);
671         free(Variable);
672 }

UCC git Repository :: git.ucc.asn.au