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