Major commit for preparing 1.1.1 release
[spandex:spandex.git] / sct / common / core / sct_tokenizer.c
1 /*
2  * Spandex benchmark and test framework.
3  *
4  * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
5  *
6  * Contact: Kari J. Kangas <kari.j.kangas@nokia.com>
7  *
8  *   This framework is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by the
10  * Free Software Foundation, version 2.1 of the License.
11  *
12  *   This framework is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
15  * for more details.
16  *
17  *   You should have received a copy of the GNU Lesser General Public License
18  * along with this framework; if not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21
22 #include "sct_tokenizer.h"
23 #include "sct_tokenizer_internal.h"
24 #include "sct_utils.h"
25 #include "sct_version.h"
26
27 #include <stdio.h>
28 #include <string.h>
29
30 /*!
31  * Initializes Tokenizer.
32  *
33  * \return Tokenizer context.
34  *
35  */
36 SCTTokenizerContext* sctTokenizerInit( void )
37 {
38         SCTTokenizerContext*    context;
39
40         context = ( SCTTokenizerContext* )( siCommonMemoryAlloc( 0, sizeof( SCTTokenizerContext ) ) );
41         if( context == NULL )
42         {
43         SCT_LOG_ERROR( "Tokenizer init failed: out of memory" );
44                 return NULL;
45         }
46
47     /* Initial token buffer size of 4 Kb should be enough for most cases. */
48     context->tokenBufferSize = 4096;
49     context->tokenBuffer     = ( char* )( siCommonMemoryAlloc( NULL, context->tokenBufferSize ) );
50     
51     if( context->tokenBuffer == NULL )
52     {
53         SCT_LOG_ERROR( "Tokenizer init failed: could not allocate token buffer" );
54         siCommonMemoryFree( NULL, context );
55         return NULL;
56     }
57     
58     context->currentLine             = 1;
59     context->currentColumn           = 1;
60     context->currentTokenStartColumn = 1;
61     context->lastChar                = '\0';
62     context->characterWasPutBack     = SCT_FALSE;
63     context->lastTokenType           = TOK_ERROR;
64     context->tokenWasPutBack         = SCT_FALSE;
65     context->release[ 0 ]            = '\0';
66     
67         return context;
68 }
69
70 /*!
71  * Release tokenizer context after it is no longer used.
72  *
73  * \param       context         Tokenizer context
74  *
75  */
76 void sctTokenizerTerminate( SCTTokenizerContext* context )
77 {
78         SCT_ASSERT( context != NULL );
79     siCommonMemoryFree( NULL, context->tokenBuffer );
80         siCommonMemoryFree( NULL, context );
81 }
82
83 /*!
84  * Get next token. The contents of the output buffer will be changed even if
85  * there is an error. The output buffer will have NULL at the first character
86  * location to make it a zero-lenght string.
87  *
88  * \param       ctx         Tokenizer context
89  * \param       token       Output variable for returning a pointer to the token 
90  *
91  * \return      Token type. In error case TOK_ERROR is returned 
92  */
93 SCTTokenType sctGetToken( SCTTokenizerContext* ctx, char** token )
94 {
95         SCTTokenizerContext*    context;
96
97     context = ( SCTTokenizerContext* )( ctx );
98     
99         SCT_ASSERT( context != NULL );
100         SCT_ASSERT( token != NULL );
101
102     /* Check if last token was put back */
103     if( context->tokenWasPutBack == SCT_TRUE )
104     {
105         context->tokenWasPutBack = SCT_FALSE;
106     }
107     else
108     {
109         /* Record the current column as the start of the current token. Its nice
110          * to know for error reporting. */
111         context->currentTokenStartColumn = context->currentColumn;
112
113         /* Save token type and value to context so they can be retrieved if the
114          * token is put back. */
115         context->lastTokenType = sctiGetTokenFromInput( context );
116         if( context->lastTokenType == TOK_END || context->lastTokenType == TOK_ERROR )
117         {
118             /* End-of-input does not have a token value and its safer to leave the
119              * output buffer to indicate a zero-length string in case of an
120              * error. */
121             context->tokenBuffer[ 0 ] = '\0';
122         }
123     }
124     
125     *token = context->tokenBuffer;
126     
127     return context->lastTokenType;
128 }
129
130 /*!
131  * Puts back the last read token so that next call to sctGetToken reports it
132  * again.
133  *
134  * \param context   Tokenizer context pointer.
135  */
136 void sctPutBackToken( SCTTokenizerContext* context )
137 {
138     SCT_ASSERT( context != NULL );
139     SCT_ASSERT( context->tokenWasPutBack == SCT_FALSE );
140     context->tokenWasPutBack = SCT_TRUE;
141 }
142
143 /*!
144  * Adds an error to the system error log information of the current input line
145  * and column.
146  *
147  * \param context   Tokenizer context pointer.
148  * \param error     Additional error message.
149  */
150 void sctLogInputError( SCTTokenizerContext* context, char* error )
151 {
152     sprintf( context->tokenBuffer, "Error at input line %d, column %d: %s", context->currentLine, context->currentTokenStartColumn, error );
153     sctLogError( context->tokenBuffer );
154 }
155
156 /*!
157  * Check that the core release matches the input file release. Generate a
158  * warning to the system log if the releases do not match. Call this function
159  * after the whole input file has been parsed.
160  *
161  * \param context   Tokenizer context pointer.
162  */
163 void sctTokernizerCheckRelease( SCTTokenizerContext* context )
164 {
165     char buf[ 256 ];
166             
167 #if defined( SCT_DEBUG )            
168     siCommonDebugPrintf( NULL, "SPANDEX: core release %s, input file release %s", SCT_VERSION, context->release );
169 #endif  /* defined( SCT_DEBUG ) */
170
171     if( strlen( context->release ) == 0 )
172     {
173         sprintf( buf, "unspecified input file release, might be incompatible with the core release %s", SCT_VERSION );
174
175 #if defined( SCT_DEBUG )            
176         siCommonDebugPrintf( NULL, "SPANDEX: warning %s", buf );
177 #endif  /* defined( SCT_DEBUG ) */
178         
179         sctLogWarning( buf );
180     }
181     else if( strcmp( SCT_VERSION, context->release ) != 0 )
182     {
183         sprintf( buf, "core release %s does not match input file release %s", SCT_VERSION, context->release );
184         
185 #if defined( SCT_DEBUG )            
186         siCommonDebugPrintf( NULL, "SPANDEX: warning %s", buf );
187 #endif  /* defined( SCT_DEBUG ) */
188                             
189         sctLogWarning( buf );
190     }
191 }
192
193 /*******************************************************************************
194  * Internal functions.
195  *******************************************************************************/
196
197 /*!
198  * Reads of a token from the input and stores it to context->tokenBuffer.
199  *
200  * \param context   Tokenizer context pointer
201  *
202  * \return Type of the token read from the input or TOK_ERROR.
203  */
204 SCTTokenType sctiGetTokenFromInput( SCTTokenizerContext* context )
205 {
206     char    nextChar;
207     int     bufferIndex;
208
209     sctiSkipWhitespaceAndComments( context );
210
211     nextChar = sctiGetNextChar( context );
212     
213     if( sctiIsDelimiter( nextChar ) )
214     {
215         context->tokenBuffer[ 0 ] = nextChar;
216         context->tokenBuffer[ 1 ] = '\0';
217         
218         return TOK_DELIMITER;
219     }
220     else if( nextChar == SCT_END_OF_INPUT )
221     {
222         context->tokenBuffer[ 0 ] = '\0';
223         
224         return TOK_END;
225     }
226     else if( nextChar == quoteChar )
227     {
228         return sctiReadQuotedString( context );
229     }
230     
231     /* This must be a string token, start reading more characters. */
232     context->tokenBuffer[ 0 ] = nextChar;
233     bufferIndex = 1;
234     
235     do
236     {
237         /* Is there still room in the token buffer? */
238         if( bufferIndex >= context->tokenBufferSize - 2 &&
239             sctiIncreaseTokenBufferSize( context ) == SCT_FALSE )
240         {
241             sctLogInputError( context, "could not increase token buffer length" );
242             
243             return TOK_ERROR;
244         }
245         
246         nextChar = sctiGetNextChar( context );
247         
248         /* At this point any delimiter, white space or the comment character
249          * simply means that the token we are reading ends. */
250         if( sctiIsDelimiter( nextChar )  ||
251             sctiIsWhitespace( nextChar ) ||
252             nextChar == commentChar )
253         {
254             /* Character has be put back to ensure that the next token is read
255              * correctly. */
256             sctiPutBackChar( context );
257             
258             break;
259         }
260         
261         context->tokenBuffer[bufferIndex++] = nextChar;
262         
263     } while( nextChar != SCT_END_OF_INPUT );
264     
265     /* Terminate the buffer with NULL. */
266     context->tokenBuffer[ bufferIndex ] = '\0';
267     
268     return TOK_STRING;
269 }
270
271 /*!
272  * Checks if the given character is one of the delimiters.
273  *
274  * \param c Character to check.
275  *
276  * \return  SCT_TRUE if c is a delimiter, SCT_FALSE otherwise.
277  */
278 SCTBoolean sctiIsDelimiter( char c )
279 {
280     int i;
281
282     for( i = 0; i < SCT_ARRAY_LENGTH( delimiters ); i++ )
283     {
284         if( c == delimiters[ i ] )
285         {
286             return SCT_TRUE;
287         }
288     }
289     
290     return SCT_FALSE;
291 }
292
293 /*!
294  * Checks if the given character is one of the white space characteres.
295  *
296  * \param c Character to check.
297  *
298  * \return  SCT_TRUE if c is a white space character, SCT_FALSE otherwise.
299  */
300 SCTBoolean sctiIsWhitespace( char c )
301 {
302     switch( c )
303     {
304     case ' ':
305     case '\t':
306     case '\n':
307     case '\r': /* Carriage return (appears in DOS files when read in binary mode) */
308         return SCT_TRUE;
309         
310     default:
311         return SCT_FALSE;
312     }
313 }
314
315 /*!
316  * Reads all consecutive white space characters and comment strings from the
317  * input.
318  *
319  * \param context   Tokenizer context
320  * */
321 void sctiSkipWhitespaceAndComments( SCTTokenizerContext* context )
322 {
323     char        nextChar;
324     int         i;
325     int         j;
326     int         t;
327     
328     for(;;)
329     {
330         nextChar = sctiGetNextChar( context );
331            
332         if( nextChar == commentChar )
333         {
334             t = 1;
335             
336             /* Skip to the end-of-line or end-of-input. */
337             for( i = 0, j = 0; ; ++i )
338             {
339                 nextChar = sctiGetNextChar( context );
340                 if( nextChar == '\n' || nextChar == SCT_END_OF_INPUT )
341                 {
342                     break;
343                 }
344
345                 if( t == 2 )    /* Collect release info into a buffer. */
346                 {
347                     /* Skip potential spaces, tabs, carriage returns, etc. */
348                     if( sctiIsWhitespace( nextChar ) == SCT_FALSE && j < SCT_MAX_RELEASE_LENGTH )
349                     {
350                         context->release[ j++ ] = nextChar;
351                         context->release[ j ]   = '\0';
352                     }
353                 }
354                 
355                 /* Look for the comment for release info. */
356                 if( t == 1 && nextChar != SCT_RELEASE_PREAMPLE[ i ] )
357                 {
358                     t = 0;  /* Comment line does not contain release info. */
359                 }
360                 
361                 if( t == 1 && SCT_RELEASE_PREAMPLE[ i + 1 ] == '\0' )
362                 {
363                     t = 2;  /* Comment line contains release info, collect it. */
364                 }
365             }
366         }
367         
368         if( sctiIsWhitespace( nextChar ) == SCT_FALSE )
369         {
370             sctiPutBackChar( context );
371             return;
372         }
373     }
374 }
375
376 /*!
377  * If sctiPutBackChar was called, returns the character read from the input last
378  * time, otherwise returns the next character from the input stream.
379  *
380  * \param context   Tokenizer context pointer.
381  *
382  * \return The put back character or the next character from the input.
383  * */
384 char sctiGetNextChar( SCTTokenizerContext* context )
385 {
386     char    c;
387     
388     SCT_ASSERT( context != NULL );
389
390     if( context->characterWasPutBack == SCT_TRUE )
391     {
392         c = context->lastChar;
393         context->characterWasPutBack = SCT_FALSE;
394     }
395     else
396     {
397         c = siCommonReadChar( NULL );
398     }
399     if( c == '\n' )
400     {
401         context->currentLine            += 1;
402         context->currentColumn           = 1;
403         context->currentTokenStartColumn = 1;
404     }
405     else
406     {
407         context->currentColumn += 1;
408     }
409     
410     context->lastChar = c;
411     
412     return c;
413 }
414
415 /*!
416  * Makes sctiGetNextChar return the last character again, i.e., makes it look
417  * like the the last character was "put back" to the input stream.
418  *
419  * \param context   Tokenizer context pointer.
420  * */
421 void sctiPutBackChar( SCTTokenizerContext* context )
422 {
423     SCT_ASSERT( context != NULL );
424     SCT_ASSERT( context->characterWasPutBack == SCT_FALSE );
425
426     if( context->lastChar == '\n' )
427     {
428         SCT_ASSERT( context->currentLine > 1 );
429         context->currentLine  -= 1;
430         context->currentColumn = 1;
431     }
432     else
433     {
434         SCT_ASSERT( context->currentColumn > 1 );
435         context->currentColumn -= 1;
436     }
437     
438     context->characterWasPutBack = SCT_TRUE;
439 }
440
441 /*!
442  * Reads a quoted string from the input stream and stores it (without the
443  * quotes) to the context->tokenBuffer.
444  *
445  * \param context   Tokenizer context pointer.
446  *
447  * \return  TOK_STRING if succesfull, TOK_ERROR otherwise.
448  * */
449 SCTTokenType sctiReadQuotedString( SCTTokenizerContext* context )
450 {
451     int     bufferIndex     = 0;
452     char    nextChar;
453
454     /* The opening quote has already been read from the stream. Simply read the
455     input until there is another quote. If we hit a newline or the input ends,
456     indicate an error. */
457     do
458     {
459         if( bufferIndex >= context->tokenBufferSize - 1 &&
460             sctiIncreaseTokenBufferSize( context ) == SCT_FALSE )
461         {
462             sctLogInputError( context, "Could not increase token buffer size" );
463             return TOK_ERROR;
464         }
465         
466         nextChar = sctiGetNextChar( context );
467         
468         if( nextChar == '\n' || nextChar == SCT_END_OF_INPUT )
469         {
470             sctLogInputError( context, "Unterminated quoted string." );
471             
472             return TOK_ERROR;
473         }
474         
475         context->tokenBuffer[bufferIndex++] = nextChar;
476         
477     } while( nextChar != quoteChar );
478     
479     /* Quote character was written to position bufferIndex-1 but it has to
480      * removed so lets just write NULL over it (which also terminates the
481      * string). */
482     context->tokenBuffer[ bufferIndex - 1 ] = '\0';
483     
484     return TOK_STRING;
485 }
486
487 /*!
488  * Tries to increase (currently, double) the token buffer size. The contents of
489  * the buffer are copied to the new buffer and the old buffers memory is
490  * released, if the call is succesfull.
491  *
492  * \param context   Tokenizer context pointer.
493  *
494  * \return SCT_TRUE if increasing succeeded, SCT_FALSE otherwise.
495  */
496 SCTBoolean sctiIncreaseTokenBufferSize( SCTTokenizerContext* context )
497 {
498     int     newSize;
499     int     i;
500     char*   newBuffer;
501     
502     SCT_ASSERT( context != NULL );
503     SCT_ASSERT( context->tokenBufferSize > 0 );
504     /* Kind of crazy to assert this but in this case doubling the
505      * tokenBufferSize would cause an integer overflow. On the other hand at
506      * this point the memory requirement is 1GB, which should be unlikely to
507      * occur in practice... */
508     SCT_ASSERT( context->tokenBufferSize <= 1024 * 1024 * 1024 );
509
510     /* Try to double the amount of memory */
511     newSize   = context->tokenBufferSize * 2;
512     newBuffer = ( char* )( siCommonMemoryAlloc( NULL, newSize ) );
513     
514     if( newBuffer == NULL )
515     {
516         return SCT_FALSE;
517     }
518
519     /* Copy the contents of the old buffer */
520     for( i = 0; i < context->tokenBufferSize; i++ )
521     {
522         newBuffer[ i ] = context->tokenBuffer[ i ];
523     }
524     
525     /* Free the old buffer */
526     siCommonMemoryFree( NULL, context->tokenBuffer );
527     
528     context->tokenBuffer     = newBuffer;
529     context->tokenBufferSize = newSize;
530
531     return SCT_TRUE;
532 }