1 /*
2      PLIB - A Suite of Portable Game Libraries
3      Copyright (C) 1998,2002  Steve Baker
4 
5      This library is free software; you can redistribute it and/or
6      modify it under the terms of the GNU Library General Public
7      License as published by the Free Software Foundation; either
8      version 2 of the License, or (at your option) any later version.
9 
10      This library is distributed in the hope that it will be useful,
11      but WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Library General Public License for more details.
14 
15      You should have received a copy of the GNU Library General Public
16      License along with this library; if not, write to the Free Software
17      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 
19      For further information visit http://plib.sourceforge.net
20 
21      $Id: ssgParser.cxx 2117 2007-09-13 23:21:09Z fayjf $
22 */
23 
24 //
25 // File parser for SSG/PLIB
26 // Written by Dave McClurg (dpm@efn.org) in Feb-2000
27 // extended by Wolfram Kuss (w_kuss@rz-online.de) in Nov-2000
28 
29 // This is mainly an lexical analyzer that extracts tokens from ascii-files
30 
31 // Be sure to read the ssg-documentation, especially the chapter
32 // on loaders/writers
33 
34 
35 #define AM_IN_SSGPARSER_CXX 1
36 
37 #include "ssgLocal.h"
38 #include "ssgParser.h"
39 
40 
41 static _ssgParserSpec default_spec =
42 {
43    "\r\n\t ",  // delim_chars_skipable
44    0,          // delim_chars_non_skipable
45    NULL,      // pre_processor
46    0,          // open_brace_chars
47    0,          // close_brace_chars
48    '"',        // quote_char
49    0,          // comment_char
50 	 "//"        // comment_string
51 } ;
52 
53 
54 // Output an error
error(const char * format,...)55 void _ssgParser::error( const char *format, ... )
56 {
57   char msgbuff[ 255 ];
58   va_list argp;
59 
60   va_start( argp, format );
61   vsnprintf( msgbuff, sizeof(msgbuff)-1, format, argp );
62   va_end( argp );
63 
64   msgbuff[sizeof(msgbuff)-1] = '\0';
65 
66   if (linenum)
67   {
68     ulSetError ( UL_WARNING, "%s, line %d: %s", path, linenum, msgbuff ) ;
69   } else {
70     ulSetError ( UL_WARNING, "%s", msgbuff ) ;
71   }
72 }
73 
74 
75 // Output a message
message(const char * format,...)76 void _ssgParser::message( const char *format, ... )
77 {
78   char msgbuff[ 255 ];
79   va_list argp;
80 
81   va_start( argp, format );
82   vsnprintf( msgbuff, sizeof(msgbuff)-1, format, argp );
83   va_end( argp );
84 
85   msgbuff[sizeof(msgbuff)-1] = '\0';
86 
87   if (linenum)
88   {
89     ulSetError ( UL_DEBUG, "%s, line %d: %s", path, linenum, msgbuff ) ;
90   } else {
91     ulSetError ( UL_DEBUG, "%s", msgbuff ) ;
92   }
93 }
94 
95 // Opens the file and does a few internal calculations based on the spec.
openFile(const char * fname,const _ssgParserSpec * _spec)96 int _ssgParser::openFile( const char* fname, const _ssgParserSpec* _spec )
97 // returns TRUE on success
98 {
99   if ( !_spec ) _spec = &default_spec ;
100 
101 	if ( _spec->comment_string != NULL )
102 	{ assert ( _spec->comment_string [0] != 0 );
103 	}
104 
105   memset(this,0,sizeof(_ssgParser));
106   memcpy( &spec, _spec, sizeof(spec) );
107   ssgGetCurrentOptions () -> makeModelPath ( path, fname ) ;
108   fileptr = fopen( path, "rb" );
109   if ( ! fileptr )
110 	{
111     error("cannot open file: %s",path);
112 		return FALSE;
113 	}
114 	eof = FALSE;
115 	// Calculate anyDelimiter and return.
116 	anyDelimiter[0] = 0;
117 	int length = 0;
118 	if ( spec.delim_chars_skipable != NULL )
119 	{ length +=strlen ( spec.delim_chars_skipable);
120 	  strcat(anyDelimiter, spec.delim_chars_skipable);
121 	}
122 	if ( spec.delim_chars_non_skipable  != NULL )
123 	{ length += strlen ( spec.delim_chars_non_skipable ) ;
124 	  strcat ( anyDelimiter, spec.delim_chars_non_skipable ) ;
125 	}
126 	if ( spec.open_brace_chars  != NULL )
127 	{ length +=strlen ( spec.open_brace_chars );
128 	  strcat ( anyDelimiter, spec.open_brace_chars );
129 	}
130 	if ( spec.close_brace_chars  != NULL )
131 	{ length +=strlen ( spec.close_brace_chars ) ;
132 	  strcat ( anyDelimiter, spec.close_brace_chars ) ;
133 	}
134 	assert ( length < MAX_DELIMITER_CHARS );
135 	return TRUE;
136 }
137 
138 
closeFile()139 void _ssgParser::closeFile()
140 {
141   fclose( fileptr ) ;
142   fileptr = 0 ;
143 }
144 
145 static char *EOF_string = "EOF reached";
146 static char *EOL_string = "EOL reached";
147 
getNextToken(const char * name)148 char* _ssgParser::getNextToken( const char* name )
149 // Fetches next token, even if it has to read over some empty or comment-only lines to get to it.
150 // Never returns NULL. Returns EOF_string on EOF.
151 {
152 	while(!( curtok < numtok ))
153 	{	//int startLevel = level;
154 	  //ulSetError(UL_DEBUG, "Forcing!");
155 		if(getLine( -999 ) == NULL) // -999
156 		{	if ( name )
157 				error("missing %s",name) ;
158 			return EOF_string;
159 		}
160 		assert(curtok==1);
161 		curtok=0; // redo the get one token that getLine does
162 	}
163   char* token = 0 ;
164   assert ( curtok < numtok );
165   token = tokptr [ curtok++ ] ;
166 	return(token) ;
167 }
168 
peekAtNextToken(const char * name)169 char *_ssgParser::peekAtNextToken( const char* name )
170 // Like getNextToken, but doesn't remove the token from the input stream
171 {
172 	while(!( curtok < numtok ))
173 	{	//int startLevel = level;
174 	  //ulSetError(UL_DEBUG, "Forcing!");
175 		if(getLine( -999 ) == NULL) // -999
176 		{	if ( name )
177 				error("missing %s",name) ;
178 			return EOF_string;
179 		}
180 		assert(curtok==1);
181 		curtok=0; // redo the get one token that getLine does
182 	}
183   char* token = 0 ;
184   assert ( curtok < numtok );
185   token = tokptr [ curtok ] ;
186 	return(token) ;
187 }
188 
189 
190 
getNextFloat(SGfloat & retVal,const char * name)191 int _ssgParser::getNextFloat( SGfloat &retVal, const char* name )
192 // returns TRUE on success
193 {
194   char *endptr, *token = getNextToken(name);
195   retVal = SGfloat(strtod( token, &endptr));
196 	if ( (endptr == NULL) || (*endptr == 0))
197     return TRUE;
198 	else
199 	{ error("The field %s should contain a floating point number but contains %s",name, token) ;
200 		return FALSE;
201 	}
202 }
203 
getNextInt(int & retVal,const char * name)204 int _ssgParser::getNextInt( int & retVal, const char* name )
205 // returns TRUE on success
206 {
207   char *endptr, *token = getNextToken(name);
208   retVal = int(strtol( token, &endptr, 10));
209 	if ( (endptr == NULL) || (*endptr == 0))
210     return TRUE;
211 	else
212 	{ error("The field %s should contain an integer number but contains %s",name, token) ;
213 		return FALSE;
214 	}
215 }
216 
getNextString(char * & retVal,const char * name)217 int _ssgParser::getNextString(char *&retVal, const char* name ) // returns TRUE on success
218 // wk: This is only for strings where we know they are inside spec.quote_chars, correct?
219 {
220    char *token = getNextToken( NULL );
221 
222    if ( spec.quote_char && *token == spec.quote_char )
223      {
224 	//knock off the quotes
225 	token++ ;
226 	int len = strlen( token ) ;
227 	if (len > 0 && token[len-1] == spec.quote_char)
228 	  token[len-1] = 0;
229      }
230 
231    if( name != NULL && strcmp( token, name  ) )
232      {
233 	error("Expected %s but got %s instead", name, token) ;
234 	return FALSE;
235      }
236 
237    retVal = token;
238    return TRUE;
239 }
240 
getNextUInt(unsigned int & retVal,const char * name)241 int _ssgParser::getNextUInt( unsigned int & retVal, const char* name )
242 // returns TRUE on success
243 { char *endptr, *token = getNextToken(name);
244   retVal = (unsigned int)(strtol( token, &endptr, 10));
245 	if ( (endptr == NULL) || (*endptr == 0))
246     return TRUE;
247 	else
248 	{ error("The field %s should contain an integer number but contains %s",name, token) ;
249 		return FALSE;
250 	}
251 }
252 
253 
expectNextToken(const char * name)254 void _ssgParser::expectNextToken( const char* name )
255 // Swallows the next token. If it is not name, then there is an error message
256 {
257   char* token = getNextToken(name);
258   if (strcmp(token,name))
259     error("missing %s",name) ;
260 }
261 
262 // internal function. A token consisting of a single char has been found.
263 // This is copied to a new buffer, so that I have the space to add the 0.
addOneCharToken(char * ptr)264 void _ssgParser::addOneCharToken ( char *ptr )
265 {
266 	assert( (long)onechartokenbuf_ptr- (long)onechartokenbuf < 4096 ) ; // Buffer overflow
267 
268 	onechartokenbuf_ptr [ 0 ] = *ptr;
269 	onechartokenbuf_ptr [ 1 ] = 0;
270 	tokptr [ numtok++ ] = onechartokenbuf_ptr;
271 	onechartokenbuf_ptr += 2; // prepare for nect onechartoken
272 }
273 
mystrchr(const char * string,int c)274 static const char *mystrchr( const char *string, int c )
275 // like strchr, but string may be NULL
276 {
277 	if (string == NULL )
278 		return NULL;
279 	else
280 		return strchr( string, c );
281 }
282 
283 
284 // gets the next line (no matter where it is), without tokenizing it
285 // useful for parsing text-formatted files which are identified by
286 // comments at the very beginning
getRawLine()287 char* _ssgParser::getRawLine()
288 // return NULL on eof
289 {
290    tokbuf[0]=0;
291 
292    //get the next line with something on it
293    if ( fgets ( linebuf, sizeof(linebuf), fileptr ) == NULL )
294      {
295 	eol = TRUE;
296 	eof = TRUE;
297 	return(0) ;
298      }
299 
300    memcpy( tokbuf, linebuf, sizeof(linebuf) ) ;
301 
302    return tokbuf;
303 }
304 
305 // wk: This works and is IMHO robust.
306 // However, I feel it could be smaller, more elegant and readable.
getLine(int startLevel)307 char* _ssgParser::getLine( int startLevel )
308 // return NULL on eof or if (level < startLevel)
309 {
310 	// throw away old tokens
311   tokbuf [ 0 ] = 0 ;
312   numtok = 0 ;
313   curtok = 0 ;
314 	eol = FALSE;
315 	onechartokenbuf_ptr = onechartokenbuf ;
316 
317   //get the next line with something on it
318   char* ptr = tokbuf , *tptr;
319   while ( *ptr == 0 )
320   {
321 		linenum++ ;
322 		if ( fgets ( linebuf, sizeof(linebuf), fileptr ) == NULL )
323 		{ eol = TRUE;
324 			eof = TRUE;
325 			return(0) ;
326 		}
327     if(spec.pre_processor != NULL)
328       spec.pre_processor (linebuf);
329 		memcpy( tokbuf, linebuf, sizeof(linebuf) ) ;
330 		ptr = tokbuf ;
331 
332 		// check for comments
333 		tptr=strchr(tokbuf, spec.comment_char);
334 		if ( tptr != NULL )
335 			*tptr = 0;
336 		if ( spec.comment_string != NULL )
337 		{
338 			tptr=strstr(tokbuf, spec.comment_string);
339 			if ( tptr != NULL )
340 				*tptr = 0;
341 		}
342 
343 		//skip delim_chars
344 		if ( spec.delim_chars_skipable != NULL )
345 			while ( *ptr && strchr(spec.delim_chars_skipable,*ptr) )
346 				ptr++ ;
347   }
348 
349   //tokenize the line
350   numtok = 0 ;
351   while ( *ptr )
352   {
353      //skip delim_chars
354 		if ( spec.delim_chars_skipable != NULL )
355 			while ( *ptr && strchr(spec.delim_chars_skipable,*ptr) )
356 				ptr++ ;
357 
358 		if ( *ptr == 0 )
359 			break; // only skipable stuff left, dont create another token.
360 
361 		// now unnessary?:
362 		if ( *ptr == spec.comment_char )
363     {
364       *ptr = 0 ;
365       break;
366     }
367 
368     //count the token
369     tokptr [ numtok++ ] = ptr ;
370 
371     //handle quoted string
372     if ( spec.quote_char && *ptr == spec.quote_char )
373     {
374       ptr++ ;
375       while ( *ptr && *ptr != spec.quote_char )
376         ptr++ ;
377     }
378 
379     //adjust level
380     if ( spec.open_brace_chars && *ptr && mystrchr(spec.open_brace_chars,*ptr) )
381       level++ ;
382     else if ( spec.close_brace_chars && *ptr && mystrchr(spec.close_brace_chars,*ptr) )
383       level-- ;
384 		else
385 			//find end of token
386 			while ( *ptr && !strchr(anyDelimiter,*ptr) )
387 				ptr++ ;
388 
389 		if ( *ptr != 0 )
390 			if ( ptr == tokptr [ numtok-1 ] )
391 			{ // we dont want tokens of length zero
392 				assert(NULL==mystrchr(spec.delim_chars_skipable,*ptr));
393 				// ptr is non-skipable, return it as token of length one
394 				numtok--;                  // remove zero-length token
395 				addOneCharToken ( ptr ) ;  // and add new token instead
396 				*ptr++ = 0;
397 				continue;
398 			}
399 
400     //mark end of token
401 		if( *ptr && ( mystrchr(spec.delim_chars_non_skipable,*ptr)
402 			        || mystrchr(spec.open_brace_chars,*ptr)
403 							|| mystrchr(spec.close_brace_chars,*ptr) ) )
404 		{
405 			// ptr is non-skipable, return it as token of length one
406 			// additional to the one already in tokptr [ numtok-1 ].
407 			addOneCharToken ( ptr ) ;
408 			*ptr++ = 0;
409 		}
410 		if ( spec.delim_chars_skipable != NULL )
411 			while ( *ptr && strchr(spec.delim_chars_skipable,*ptr) )
412 				*ptr++ = 0 ;
413   }
414   if (level >= startLevel)
415     return parseToken (0) ;
416   return 0 ;
417 }
418 
419 
parseToken(const char * name)420 char* _ssgParser::parseToken( const char* name )
421 // returns the next token from the current line.
422 // Never returns NULL, but may return EOL_string
423 {
424   char* token = EOL_string ;
425   if ( curtok < numtok )
426     token = tokptr [ curtok++ ] ;
427   else
428 	{ eol = TRUE;
429 		if ( name )
430 			error("missing %s",name) ;
431 	}
432   return(token) ;
433 }
434 
435 
parseString(char * & retVal,const char * name)436 int _ssgParser::parseString(char *&retVal, const char* name ) // returns TRUE on success
437 // wk: This is only for strings where we know they are inside spec.quote_chars, correct?
438 {
439   char* token = EOL_string ;
440 	retVal = EOL_string ;
441 
442   if ( curtok >= numtok )
443   { eol = TRUE;
444 		if ( name )
445 	    error("missing %s",name) ;
446 		return FALSE;
447 	}
448 
449 	if ( numtok > 0 && spec.quote_char && *tokptr [ curtok ] == spec.quote_char )
450   {
451     token = tokptr [ curtok++ ] ;
452 
453     //knock off the quotes
454     token++ ;
455     int len = strlen (token) ;
456     if (len > 0 && token[len-1] == spec.quote_char)
457        token[len-1] = 0 ;
458   }
459   else
460   { if ( name )
461 	    error("missing %s",name) ;
462 		return FALSE;
463 	}
464    retVal = token;
465   return TRUE;
466 }
467 
parseDouble(double & retVal,const char * name)468 int _ssgParser::parseDouble( double &retVal, const char* name )
469 // returns TRUE on success
470 {
471   char *endptr, *token = parseToken(name);
472   retVal = strtod( token, &endptr);
473 	if ( (endptr == NULL) || (*endptr == 0))
474     return TRUE;
475 	else
476 	{ error("The field %s should contain a floating point number but contains %s",name, token) ;
477 		return FALSE;
478 	}
479 }
480 
parseFloat(SGfloat & retVal,const char * name)481 int _ssgParser::parseFloat( SGfloat &retVal, const char* name )
482 // returns TRUE on success
483 {
484   char *endptr, *token = parseToken(name);
485   retVal = SGfloat(strtod( token, &endptr));
486 	if ( (endptr == NULL) || (*endptr == 0))
487     return TRUE;
488 	else
489 	{ error("The field %s should contain a floating point number but contains %s",name, token) ;
490 		return FALSE;
491 	}
492 }
493 
parseInt(int & retVal,const char * name)494 int _ssgParser::parseInt(int &retVal, const char* name )
495 // returns TRUE on success
496 {
497   char *endptr, *token = parseToken(name);
498   retVal = int(strtol( token, &endptr, 10));
499 	if ( (endptr == NULL) || (*endptr == 0))
500     return TRUE;
501 	else
502 	{ error("The field %s should contain an integer number but contains %s",name, token) ;
503 		return FALSE;
504 	}
505 }
506 
parseUInt(unsigned int & retVal,const char * name)507 int _ssgParser::parseUInt(unsigned int &retVal, const char* name )
508 // returns TRUE on success
509 {
510   char *endptr, *token = parseToken(name);
511 	long l = strtol( token, &endptr, 10);
512 	if (l<0)
513 		message("The field %s should contain an UNSIGNED integer number but contains %s",name, token) ;
514   retVal = (unsigned int)(l);
515 	if ( (endptr == NULL) || (*endptr == 0))
516     return TRUE;
517 	else
518 	{ error("The field %s should contain an integer number but contains %s",name, token) ;
519 		return FALSE;
520 	}
521 }
522 
523 
524 
expect(const char * name)525 void _ssgParser::expect( const char* name )
526 // Swallows the next token. If it is not name, then there is an error message
527 {
528   char* token = parseToken(name);
529   if (strcmp(token,name))
530     error("missing %s",name) ;
531 }
532