6b7e038ac1508417589071cad0c5ffd56d12ecc6
[tpg/acess2.git] / Usermode / Libraries / libspiderscript.so_src / ast_to_bytecode.c
1 /*
2  * SpiderScript Library
3  *
4  * AST to Bytecode Conversion
5  */
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <stdarg.h>
9 #include <string.h>
10 #include "common.h"
11 #include "ast.h"
12 #include "bytecode_gen.h"
13 #include "bytecode_ops.h"
14
15 #define TRACE_VAR_LOOKUPS       0
16 #define TRACE_NODE_RETURNS      0
17 #define MAX_NAMESPACE_DEPTH     10
18
19 // === IMPORTS ===
20 extern tSpiderFunction  *gpExports_First;
21
22 // === TYPES ===
23 typedef struct sAST_BlockInfo
24 {
25         struct sAST_BlockInfo   *Parent;
26         void    *Handle;
27         const char      *Tag;
28
29          int    BreakTarget;
30          int    ContinueTarget;
31         
32         const char      *CurNamespaceStack[MAX_NAMESPACE_DEPTH];
33 } tAST_BlockInfo;
34
35 // === PROTOTYPES ===
36 // Node Traversal
37  int    AST_ConvertNode(tAST_BlockInfo *Block, tAST_Node *Node, int bKeepValue);
38 // Variables
39  int    BC_Variable_Define(tAST_BlockInfo *Block, int Type, const char *Name);
40  int    BC_Variable_SetValue(tAST_BlockInfo *Block, tAST_Node *VarNode);
41  int    BC_Variable_GetValue(tAST_BlockInfo *Block, tAST_Node *VarNode);
42 // - Errors
43 void    AST_RuntimeMessage(tAST_Node *Node, const char *Type, const char *Format, ...);
44 void    AST_RuntimeError(tAST_Node *Node, const char *Format, ...);
45
46 // === GLOBALS ===
47 // int  giNextBlockIdent = 1;
48
49 // === CODE ===
50 int SpiderScript_BytecodeScript(tSpiderScript *Script)
51 {
52         tScript_Function        *fcn;
53         for(fcn = Script->Functions; fcn; fcn = fcn->Next)
54         {
55                 if( Bytecode_ConvertFunction(fcn) == 0 )
56                         return -1;
57         }
58         return 0;
59 }
60
61 /**
62  * \brief Convert a function into bytecode
63  */
64 tBC_Function *Bytecode_ConvertFunction(tScript_Function *Fcn)
65 {
66         tBC_Function    *ret;
67         tAST_BlockInfo bi = {0};
68
69         // TODO: Return BCFcn instead?
70         if(Fcn->BCFcn)  return Fcn->BCFcn;
71         
72         ret = Bytecode_CreateFunction(Fcn);
73         if(!ret)        return NULL;
74         
75         bi.Handle = ret;
76         if( AST_ConvertNode(&bi, Fcn->ASTFcn, 0) )
77         {
78                 Bytecode_DeleteFunction(ret);
79                 return NULL;
80         }
81
82         Fcn->BCFcn = ret;
83
84         return ret;
85 }
86
87 /**
88  * \brief Convert a node into bytecode
89  * \param Block Execution context
90  * \param Node  Node to execute
91  */
92 int AST_ConvertNode(tAST_BlockInfo *Block, tAST_Node *Node, int bKeepValue)
93 {
94         tAST_Node       *node;
95          int    ret = 0;
96          int    i, op = 0;
97         
98         switch(Node->Type)
99         {
100         // No Operation
101         case NODETYPE_NOP:
102                 break;
103         
104         // Code block
105         case NODETYPE_BLOCK:
106                 Bytecode_AppendEnterContext(Block->Handle);     // Create a new block
107                 {
108                         tAST_BlockInfo  blockInfo = {0};
109                         blockInfo.Parent = Block;
110                         blockInfo.Handle = Block->Handle;
111                         // Loop over all nodes, or until the return value is set
112                         for(node = Node->Block.FirstChild;
113                                 node;
114                                 node = node->NextSibling )
115                         {
116                                 AST_ConvertNode(Block, node, 0);
117                         }
118                 }
119                 Bytecode_AppendLeaveContext(Block->Handle);     // Leave this context
120                 break;
121         
122         // Assignment
123         case NODETYPE_ASSIGN:
124                 // TODO: Support assigning to object attributes
125                 if( Node->Assign.Dest->Type != NODETYPE_VARIABLE ) {
126                         AST_RuntimeError(Node, "LVALUE of assignment is not a variable");
127                         return -1;
128                 }
129                 ret = AST_ConvertNode(Block, Node->Assign.Value, 1);
130                 if(ret) return ret;
131                 
132                 // Perform assignment operation
133                 if( Node->Assign.Operation != NODETYPE_NOP )
134                 {
135                         
136                         ret = BC_Variable_GetValue(Block, Node->Assign.Dest);
137                         if(ret) return ret;
138                         switch(Node->Assign.Operation)
139                         {
140                         // General Binary Operations
141                         case NODETYPE_ADD:      op = BC_OP_ADD; break;
142                         case NODETYPE_SUBTRACT: op = BC_OP_SUBTRACT;    break;
143                         case NODETYPE_MULTIPLY: op = BC_OP_MULTIPLY;    break;
144                         case NODETYPE_DIVIDE:   op = BC_OP_DIVIDE;      break;
145                         case NODETYPE_MODULO:   op = BC_OP_MODULO;      break;
146                         case NODETYPE_BWAND:    op = BC_OP_BITAND;      break;
147                         case NODETYPE_BWOR:     op = BC_OP_BITOR;       break;
148                         case NODETYPE_BWXOR:    op = BC_OP_BITXOR;      break;
149                         case NODETYPE_BITSHIFTLEFT:     op = BC_OP_BITSHIFTLEFT;        break;
150                         case NODETYPE_BITSHIFTRIGHT:    op = BC_OP_BITSHIFTRIGHT;       break;
151                         case NODETYPE_BITROTATELEFT:    op = BC_OP_BITROTATELEFT;       break;
152
153                         default:
154                                 AST_RuntimeError(Node, "Unknown operation in ASSIGN %i", Node->Assign.Operation);
155                                 break;
156                         }
157                         printf("assign, op = %i\n", op);
158                         Bytecode_AppendBinOp(Block->Handle, op);
159                 }
160                 
161                 if( bKeepValue )
162                         Bytecode_AppendDuplicate(Block->Handle);
163                 // Set the variable value
164                 ret = BC_Variable_SetValue( Block, Node->Assign.Dest );
165                 break;
166         
167         // Post increment/decrement
168         case NODETYPE_POSTINC:
169         case NODETYPE_POSTDEC:
170                 Bytecode_AppendConstInt(Block->Handle, 1);
171                 
172                 // TODO: Support assigning to object attributes
173                 if( Node->UniOp.Value->Type != NODETYPE_VARIABLE ) {
174                         AST_RuntimeError(Node, "LVALUE of assignment is not a variable");
175                         return -1;
176                 }
177
178                 ret = BC_Variable_GetValue(Block, Node->UniOp.Value);
179                 if(ret) return ret;
180
181                 if( Node->Type == NODETYPE_POSTDEC )
182                         Bytecode_AppendBinOp(Block->Handle, BC_OP_SUBTRACT);
183                 else
184                         Bytecode_AppendBinOp(Block->Handle, BC_OP_ADD);
185                 if(ret) return ret;
186
187                 ret = BC_Variable_SetValue(Block, Node->UniOp.Value);
188                 if(ret) return ret;
189                 break;
190         
191         // Function Call
192         case NODETYPE_METHODCALL:
193         case NODETYPE_FUNCTIONCALL:
194         case NODETYPE_CREATEOBJECT: {
195                  int    nargs = 0;
196                 for(node = Node->FunctionCall.FirstArg; node; node = node->NextSibling)
197                 {
198                         ret = AST_ConvertNode(Block, node, 1);
199                         if(ret) return ret;
200                         nargs ++;
201                 }
202                 
203                 // Call the function
204                 if( Node->Type == NODETYPE_METHODCALL )
205                 {
206                         // TODO: Sanity check stack top
207                         ret = AST_ConvertNode(Block, Node->FunctionCall.Object, 1);
208                         if(ret) return ret;
209                         Bytecode_AppendMethodCall(Block->Handle, Node->FunctionCall.Name, nargs);
210                 }
211                 else
212                 {
213                          int    newnamelen = 0;
214                         char    *manglename;
215                         for( i = 0; i < MAX_NAMESPACE_DEPTH && Block->CurNamespaceStack[i]; i ++ )
216                                 newnamelen = strlen(Block->CurNamespaceStack[i]) + 1;
217                         newnamelen += strlen(Node->FunctionCall.Name) + 1;
218                         manglename = alloca(newnamelen);
219                         manglename[0] = 0;
220                         for( i = 0; i < MAX_NAMESPACE_DEPTH && Block->CurNamespaceStack[i]; i ++ ) {
221                                  int    pos;
222                                 strcat(manglename, Block->CurNamespaceStack[i]);
223                                 pos = strlen(manglename);
224                                 manglename[pos] = BC_NS_SEPARATOR;
225                                 manglename[pos+1] = '\0';
226                         }
227                         strcat(manglename, Node->FunctionCall.Name);
228                                 
229                         if( Node->Type == NODETYPE_CREATEOBJECT )
230                         {
231                                 // TODO: Sanity check stack top
232                                 Bytecode_AppendCreateObj(Block->Handle, manglename, nargs);
233                         }
234                         else
235                         {
236                                 Bytecode_AppendFunctionCall(Block->Handle, manglename, nargs);
237                         }
238                 }
239                 } break;
240         
241         // Conditional
242         case NODETYPE_IF: {
243                  int    if_end;
244                 ret = AST_ConvertNode(Block, Node->If.Condition, 1);
245                 if(ret) return ret;
246                 
247                 if_end = Bytecode_AllocateLabel(Block->Handle);
248
249                 if( Node->If.False->Type != NODETYPE_NOP )
250                 {
251                          int    if_true = Bytecode_AllocateLabel(Block->Handle);
252                         
253                         Bytecode_AppendCondJump(Block->Handle, if_true);
254         
255                         // False
256                         ret = AST_ConvertNode(Block, Node->If.False, 0);
257                         if(ret) return ret;
258                         Bytecode_AppendJump(Block->Handle, if_end);
259                         Bytecode_SetLabel(Block->Handle, if_true);
260                 }
261                 else
262                 {
263                         Bytecode_AppendCondJumpNot(Block->Handle, if_end);
264                 }
265                 
266                 // True
267                 ret = AST_ConvertNode(Block, Node->If.True, 0);
268                 if(ret) return ret;
269
270                 // End
271                 Bytecode_SetLabel(Block->Handle, if_end);
272                 } break;
273         
274         // Loop
275         case NODETYPE_LOOP: {
276                  int    loop_start, loop_end;
277                  int    saved_break, saved_continue;
278                 const char      *saved_tag;
279
280                 // Initialise
281                 ret = AST_ConvertNode(Block, Node->For.Init, 0);
282                 if(ret) return ret;
283                 
284                 loop_start = Bytecode_AllocateLabel(Block->Handle);
285                 loop_end = Bytecode_AllocateLabel(Block->Handle);
286
287                 saved_break = Block->BreakTarget;
288                 saved_continue = Block->ContinueTarget;
289                 saved_tag = Block->Tag;
290                 Block->BreakTarget = loop_end;
291                 Block->ContinueTarget = loop_end;
292                 Block->Tag = Node->For.Tag;
293
294                 Bytecode_SetLabel(Block->Handle, loop_start);
295
296                 // Check initial condition
297                 if( !Node->For.bCheckAfter )
298                 {
299                         ret = AST_ConvertNode(Block, Node->For.Condition, 1);
300                         if(ret) return ret;
301                         Bytecode_AppendUniOp(Block->Handle, BC_OP_LOGICNOT);
302                         Bytecode_AppendCondJump(Block->Handle, loop_end);
303                 }
304         
305                 // Code
306                 ret = AST_ConvertNode(Block, Node->For.Code, 0);
307                 if(ret) return ret;
308                 
309                 // Increment
310                 ret = AST_ConvertNode(Block, Node->For.Increment, 0);
311                 if(ret) return ret;
312
313                 // Tail check
314                 if( Node->For.bCheckAfter )
315                 {
316                         ret = AST_ConvertNode(Block, Node->For.Condition, 1);
317                         if(ret) return ret;
318                         Bytecode_AppendCondJump(Block->Handle, loop_start);
319                 }
320                 else
321                 {
322                         Bytecode_AppendJump(Block->Handle, loop_start);
323                 }
324
325                 Bytecode_SetLabel(Block->Handle, loop_end);
326
327                 Block->BreakTarget = saved_break;
328                 Block->ContinueTarget = saved_continue;
329                 Block->Tag = saved_tag;
330                 } break;
331         
332         // Return
333         case NODETYPE_RETURN:
334                 ret = AST_ConvertNode(Block, Node->UniOp.Value, 1);
335                 if(ret) return ret;
336                 Bytecode_AppendReturn(Block->Handle);
337                 break;
338         
339         case NODETYPE_BREAK:
340         case NODETYPE_CONTINUE: {
341                 tAST_BlockInfo  *bi = Block;
342                 if( Node->Variable.Name[0] ) {
343                         while(bi && strcmp(bi->Tag, Node->Variable.Name) == 0)  bi = bi->Parent;
344                 }
345                 if( !bi )       return 1;
346                 // TODO: Check if BreakTarget/ContinueTarget are valid
347                 if( Node->Type == NODETYPE_BREAK )
348                         Bytecode_AppendJump(Block->Handle, bi->BreakTarget);
349                 else
350                         Bytecode_AppendJump(Block->Handle, bi->ContinueTarget);
351                 } break;
352         
353         // Define a variable
354         case NODETYPE_DEFVAR:
355                 ret = BC_Variable_Define(Block, Node->DefVar.DataType, Node->DefVar.Name);
356                 if(ret) return ret;
357                 
358                 if( Node->DefVar.InitialValue )
359                 {
360                         ret = AST_ConvertNode(Block, Node->DefVar.InitialValue, 1);
361                         if(ret) return ret;
362                         Bytecode_AppendSaveVar(Block->Handle, Node->DefVar.Name);
363                 }
364                 break;
365         
366         // Scope
367         case NODETYPE_SCOPE:
368                 for( i = 0; i < MAX_NAMESPACE_DEPTH && Block->CurNamespaceStack[i]; i ++ );
369                 if( i == MAX_NAMESPACE_DEPTH ) {
370                         AST_RuntimeError(Node, "Exceeded max explicit namespace depth (%i)", i);
371                         return 2;
372                 }
373                 Block->CurNamespaceStack[i] = Node->Scope.Name;
374                 ret = AST_ConvertNode(Block, Node->Scope.Element, 2);
375                 Block->CurNamespaceStack[i] = NULL;
376                 break;
377         
378         // Variable
379         case NODETYPE_VARIABLE:
380                 ret = BC_Variable_GetValue( Block, Node );
381                 break;
382         
383         // Element of an Object
384         case NODETYPE_ELEMENT:
385                 ret = AST_ConvertNode( Block, Node->Scope.Element, 1 );
386                 if(ret) return ret;
387
388                 Bytecode_AppendElement(Block->Handle, Node->Scope.Name);
389                 break;
390
391         // Cast a value to another
392         case NODETYPE_CAST:
393                 ret = AST_ConvertNode(Block, Node->Cast.Value, 1);
394                 if(ret) return ret;
395                 Bytecode_AppendCast(Block->Handle, Node->Cast.DataType);
396                 break;
397
398         // Index into an array
399         case NODETYPE_INDEX:
400                 ret = AST_ConvertNode(Block, Node->BinOp.Left, 1);      // Array
401                 if(ret) return ret;
402                 ret = AST_ConvertNode(Block, Node->BinOp.Right, 1);     // Offset
403                 if(ret) return ret;
404                 
405                 Bytecode_AppendIndex(Block->Handle);
406                 break;
407
408         // TODO: Implement runtime constants
409         case NODETYPE_CONSTANT:
410                 // TODO: Scan namespace for constant name
411                 AST_RuntimeError(Node, "TODO - Runtime Constants");
412                 ret = -1;
413                 break;
414         
415         // Constant Values
416         case NODETYPE_STRING:
417                 Bytecode_AppendConstString(Block->Handle, Node->Constant.String.Data, Node->Constant.String.Length);
418                 break;
419         case NODETYPE_INTEGER:
420                 Bytecode_AppendConstInt(Block->Handle, Node->Constant.Integer);
421                 break;
422         case NODETYPE_REAL:
423                 Bytecode_AppendConstReal(Block->Handle, Node->Constant.Real);
424                 break;
425         
426         // --- Operations ---
427         // Boolean Operations
428         case NODETYPE_LOGICALNOT:       // Logical NOT (!)
429                 if(!op) op = BC_OP_LOGICNOT;
430         case NODETYPE_BWNOT:    // Bitwise NOT (~)
431                 if(!op) op = BC_OP_BITNOT;
432         case NODETYPE_NEGATE:   // Negation (-)
433                 if(!op) op = BC_OP_NEG;
434                 ret = AST_ConvertNode(Block, Node->UniOp.Value, 1);
435                 if(ret) return ret;
436                 Bytecode_AppendUniOp(Block->Handle, op);
437                 break;
438
439         // Logic
440         case NODETYPE_LOGICALAND:       if(!op) op = BC_OP_LOGICAND;
441         case NODETYPE_LOGICALOR:        if(!op) op = BC_OP_LOGICOR;
442         case NODETYPE_LOGICALXOR:       if(!op) op = BC_OP_LOGICXOR;
443         // Comparisons
444         case NODETYPE_EQUALS:   if(!op) op = BC_OP_EQUALS;
445         case NODETYPE_LESSTHAN: if(!op) op = BC_OP_LESSTHAN;
446         case NODETYPE_GREATERTHAN:      if(!op) op = BC_OP_GREATERTHAN;
447         case NODETYPE_LESSTHANEQUAL:    if(!op) op = BC_OP_LESSTHANOREQUAL;
448         case NODETYPE_GREATERTHANEQUAL: if(!op) op = BC_OP_GREATERTHANOREQUAL;
449         // General Binary Operations
450         case NODETYPE_ADD:      if(!op) op = BC_OP_ADD;
451         case NODETYPE_SUBTRACT: if(!op) op = BC_OP_SUBTRACT;
452         case NODETYPE_MULTIPLY: if(!op) op = BC_OP_MULTIPLY;
453         case NODETYPE_DIVIDE:   if(!op) op = BC_OP_DIVIDE;
454         case NODETYPE_MODULO:   if(!op) op = BC_OP_MODULO;
455         case NODETYPE_BWAND:    if(!op) op = BC_OP_BITAND;
456         case NODETYPE_BWOR:     if(!op) op = BC_OP_BITOR;
457         case NODETYPE_BWXOR:    if(!op) op = BC_OP_BITXOR;
458         case NODETYPE_BITSHIFTLEFT:     if(!op) op = BC_OP_BITSHIFTLEFT;
459         case NODETYPE_BITSHIFTRIGHT:    if(!op) op = BC_OP_BITSHIFTRIGHT;
460         case NODETYPE_BITROTATELEFT:    if(!op) op = BC_OP_BITROTATELEFT;
461                 ret = AST_ConvertNode(Block, Node->BinOp.Left, 1);
462                 if(ret) return ret;
463                 ret = AST_ConvertNode(Block, Node->BinOp.Right, 1);
464                 if(ret) return ret;
465         
466                 Bytecode_AppendBinOp(Block->Handle, op);
467                 break;
468         
469         //default:
470         //      ret = NULL;
471         //      AST_RuntimeError(Node, "BUG - SpiderScript AST_ConvertNode Unimplemented %i", Node->Type);
472         //      break;
473         }
474
475         #if TRACE_NODE_RETURNS
476         if(ret && ret != ERRPTR) {
477                 AST_RuntimeError(Node, "Ret type of %p %i is %i", Node, Node->Type, ret->Type);
478         }
479         else {
480                 AST_RuntimeError(Node, "Ret type of %p %i is %p", Node, Node->Type, ret);
481         }
482         #endif
483
484         return ret;
485 }
486
487 /**
488  * \brief Define a variable
489  * \param Block Current block state
490  * \param Type  Type of the variable
491  * \param Name  Name of the variable
492  * \return Boolean Failure
493  */
494 int BC_Variable_Define(tAST_BlockInfo *Block, int Type, const char *Name)
495 {
496         #if 0
497         tAST_Variable   *var, *prev = NULL;
498         
499         for( var = Block->FirstVar; var; prev = var, var = var->Next )
500         {
501                 if( strcmp(var->Name, Name) == 0 ) {
502                         AST_RuntimeError(NULL, "Redefinition of variable '%s'", Name);
503                         return ERRPTR;
504                 }
505         }
506         
507         var = malloc( sizeof(tAST_Variable) + strlen(Name) + 1 );
508         var->Next = NULL;
509         var->Type = Type;
510         strcpy(var->Name, Name);
511         
512         if(prev)        prev->Next = var;
513         else    Block->FirstVar = var;
514         
515         return var;
516         #else
517         Bytecode_AppendDefineVar(Block->Handle, Name, Type);
518         return 0;
519         #endif
520 }
521
522 tAST_Variable *BC_Variable_Lookup(tAST_BlockInfo *Block, tAST_Node *VarNode, int CreateType)
523 {
524         #if 0
525         tAST_Variable   *var = NULL;
526         
527         // Speed hack
528         if( VarNode->BlockState == Block && VarNode->BlockIdent == Block->Ident ) {
529                 var = VarNode->ValueCache;
530                 #if TRACE_VAR_LOOKUPS
531                 AST_RuntimeMessage(VarNode, "debug", "Fast var fetch on '%s' %p (%p:%i)",
532                         VarNode->Variable.Name, var,
533                         VarNode->BlockState, VarNode->BlockIdent
534                         );
535                 #endif
536         }
537         else
538         {
539                 tAST_BlockInfo  *bs;
540                 for( bs = Block; bs; bs = bs->Parent )
541                 {
542                         for( var = bs->FirstVar; var; var = var->Next )
543                         {
544                                 if( strcmp(var->Name, VarNode->Variable.Name) == 0 )
545                                         break;
546                         }
547                         if(var) break;
548                 }
549                 
550                 if( !var )
551                 {
552                         if( Block->Script->Variant->bDyamicTyped && CreateType != SS_DATATYPE_UNDEF ) {
553                                 // Define variable
554                                 var = BC_Variable_Define(Block, CreateType, VarNode->Variable.Name, NULL);
555                         }
556                         else
557                         {
558                                 AST_RuntimeError(VarNode, "Variable '%s' is undefined", VarNode->Variable.Name);
559                                 return NULL;
560                         }
561                 }
562                 
563                 #if TRACE_VAR_LOOKUPS
564                 AST_RuntimeMessage(VarNode, "debug", "Saved variable lookup of '%s' %p (%p:%i)",
565                         VarNode->Variable.Name, var,
566                         Block, Block->Ident);
567                 #endif
568                 
569                 VarNode->ValueCache = var;
570                 VarNode->BlockState = Block;
571                 VarNode->BlockIdent = Block->Ident;
572         }
573         
574         return var;
575         #else
576         return (void*)1;
577         #endif
578 }
579
580 /**
581  * \brief Set the value of a variable
582  * \return Boolean Failure
583  */
584 int BC_Variable_SetValue(tAST_BlockInfo *Block, tAST_Node *VarNode)
585 {
586         tAST_Variable   *var;
587         
588         var = BC_Variable_Lookup(Block, VarNode, SS_DATATYPE_UNDEF);
589         if(!var)        return -1;
590
591         // TODO: Check types
592
593         Bytecode_AppendSaveVar(Block->Handle, VarNode->Variable.Name);
594         return 0;
595 }
596
597 /**
598  * \brief Get the value of a variable
599  */
600 int BC_Variable_GetValue(tAST_BlockInfo *Block, tAST_Node *VarNode)
601 {
602         tAST_Variable   *var;
603
604         var = BC_Variable_Lookup(Block, VarNode, 0);    
605         if(!var)        return -1;
606         
607         Bytecode_AppendLoadVar(Block->Handle, VarNode->Variable.Name);
608         return 0;
609 }
610
611 #if 0
612 void AST_RuntimeMessage(tAST_Node *Node, const char *Type, const char *Format, ...)
613 {
614         va_list args;
615         
616         if(Node) {
617                 fprintf(stderr, "%s:%i: ", Node->File, Node->Line);
618         }
619         fprintf(stderr, "%s: ", Type);
620         va_start(args, Format);
621         vfprintf(stderr, Format, args);
622         va_end(args);
623         fprintf(stderr, "\n");
624 }
625 void AST_RuntimeError(tAST_Node *Node, const char *Format, ...)
626 {
627         va_list args;
628         
629         if(Node) {
630                 fprintf(stderr, "%s:%i: ", Node->File, Node->Line);
631         }
632         fprintf(stderr, "error: ");
633         va_start(args, Format);
634         vfprintf(stderr, Format, args);
635         va_end(args);
636         fprintf(stderr, "\n");
637 }
638 #endif

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