1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19 
20 //
21 // parse.c
22 // FIXME TODO:
23 // - currentCol is broken in certain cases
24 //
25 
26 #include "common.h"
27 
28 /*
29 ==============================================================================
30 
31 	SESSION CREATION
32 
33 ==============================================================================
34 */
35 
36 static parse_t	ps_sessionList[MAX_PARSE_SESSIONS];
37 static byte		ps_numSessions;
38 
39 static char		ps_scratchToken[MAX_PS_TOKCHARS];	// Used for temporary storage during data-type/post parsing
40 static const char *ps_dataTypeStr[] = {
41 	"char",
42 	"bool",
43 	"byte",
44 	"double",
45 	"float",
46 	"int",
47 	"uint"
48 };
49 
50 /*
51 ============
52 PS_StartSession
53 ============
54 */
PS_StartSession(char * dataPtr,uint32 properties)55 parse_t *PS_StartSession (char *dataPtr, uint32 properties)
56 {
57 	parse_t	*ps = NULL;
58 	int		i;
59 
60 	// Make sure incoming data is valid
61 	if (!dataPtr)
62 		return NULL;
63 
64 	// Find an available session
65 	for (i=0 ; i<ps_numSessions ; i++) {
66 		if (!ps_sessionList[i].inUse) {
67 			ps = &ps_sessionList[i];
68 			break;
69 		}
70 	}
71 
72 	if (i == ps_numSessions) {
73 		if (ps_numSessions+1 >= MAX_PARSE_SESSIONS)
74 			Com_Error (ERR_FATAL, "PS_StartSession: MAX_PARSE_SESSIONS\n");
75 
76 		ps = &ps_sessionList[ps_numSessions++];
77 	}
78 
79 	// Fill in the values
80 	ps->currentCol = 1;
81 	ps->currentLine = 1;
82 	ps->currentToken[0] = '\0';
83 
84 	ps->dataPtr = dataPtr;
85 	ps->dataPtrLast = dataPtr;
86 	ps->inUse = qTrue;
87 
88 	ps->numErrors = 0;
89 	ps->numWarnings = 0;
90 
91 	ps->properties = properties;
92 	return ps;
93 }
94 
95 
96 /*
97 ============
98 PS_EndSession
99 ============
100 */
PS_EndSession(parse_t * ps)101 void PS_EndSession (parse_t *ps)
102 {
103 	if (!ps)
104 		return;
105 
106 	ps->currentCol = 1;
107 	ps->currentLine = 1;
108 	ps->currentToken[0] = '\0';
109 
110 	ps->dataPtr = NULL;
111 	ps->dataPtrLast = NULL;
112 	ps->inUse = qFalse;
113 
114 	ps->numErrors = 0;
115 	ps->numWarnings = 0;
116 
117 	ps->properties = 0;
118 }
119 
120 /*
121 ==============================================================================
122 
123 	ERROR HANDLING
124 
125 ==============================================================================
126 */
127 
128 /*
129 ============
130 PS_AddErrorCount
131 ============
132 */
PS_AddErrorCount(parse_t * ps,uint32 * errors,uint32 * warnings)133 void PS_AddErrorCount (parse_t *ps, uint32 *errors, uint32 *warnings)
134 {
135 	if (ps) {
136 		if (errors)
137 			*errors += ps->numErrors;
138 		if (warnings)
139 			*warnings += ps->numWarnings;
140 	}
141 }
142 
143 
144 /*
145 ============
146 PS_GetErrorCount
147 ============
148 */
PS_GetErrorCount(parse_t * ps,uint32 * errors,uint32 * warnings)149 void PS_GetErrorCount (parse_t *ps, uint32 *errors, uint32 *warnings)
150 {
151 	if (ps) {
152 		if (errors)
153 			*errors = ps->numErrors;
154 		if (warnings)
155 			*warnings = ps->numWarnings;
156 	}
157 }
158 
159 
160 /*
161 ============
162 PS_GetPosition
163 ============
164 */
PS_GetPosition(parse_t * ps,uint32 * line,uint32 * col)165 void PS_GetPosition (parse_t *ps, uint32 *line, uint32 *col)
166 {
167 	if (ps) {
168 		if (line)
169 			*line = ps->currentLine;
170 		if (col)
171 			*col = ps->currentCol;
172 	}
173 }
174 
175 
176 /*
177 ============
178 PS_PrintError
179 ============
180 */
PS_PrintError(parse_t * ps,char * fmt,...)181 static void PS_PrintError (parse_t *ps, char *fmt, ...)
182 {
183 	va_list		argptr;
184 	char		msg[MAX_COMPRINT];
185 
186 	// Evaluate args
187 	va_start (argptr, fmt);
188 	vsnprintf (msg, sizeof (msg), fmt, argptr);
189 	va_end (argptr);
190 
191 	// Print
192 	ps->numErrors++;
193 	Com_ConPrint (PRNT_ERROR, msg);
194 }
195 
196 
197 /*
198 ============
199 PS_PrintWarning
200 ============
201 */
PS_PrintWarning(parse_t * ps,char * fmt,...)202 static void PS_PrintWarning (parse_t *ps, char *fmt, ...)
203 {
204 	va_list		argptr;
205 	char		msg[MAX_COMPRINT];
206 
207 	// Evaluate args
208 	va_start (argptr, fmt);
209 	vsnprintf (msg, sizeof (msg), fmt, argptr);
210 	va_end (argptr);
211 
212 	// Print
213 	ps->numWarnings++;
214 	Com_ConPrint (PRNT_WARNING, msg);
215 }
216 
217 /*
218 ==============================================================================
219 
220 	PARSING
221 
222 ==============================================================================
223 */
224 
225 /*
226 ============
227 PS_ParseToken
228 
229 Sets data to NULL when a '\0' is hit, or when a broken comment is detected.
230 Returns qTrue only when a comment block was handled.
231 ============
232 */
PS_SkipComments(parse_t * ps,char ** data,uint32 flags)233 static qBool PS_SkipComments (parse_t *ps, char **data, uint32 flags)
234 {
235 	char	*p;
236 
237 	// See if any comment types are allowed
238 	if (!(ps->properties & PSP_COMMENT_MASK))
239 		return qFalse;
240 
241 	p = *data;
242 
243 	switch (*p) {
244 	case '#':
245 		// Skip "# comments"
246 		if (ps->properties & PSP_COMMENT_POUND) {
247 			while (*p != '\n') {
248 				if (*p == '\0') {
249 					*data = NULL;
250 					return qTrue;
251 				}
252 
253 				p++;
254 				ps->currentCol++;
255 			}
256 
257 			*data = p;
258 			return qTrue;
259 		}
260 		break;
261 
262 	case '*':
263 		// This shouldn't happen with proper commenting
264 		if (p[1] == '/' && ps->properties & PSP_COMMENT_BLOCK) {
265 			p += 2;
266 			PS_PrintError (ps, "PARSE ERROR: end-comment '*/' with no opening\n");
267 			*data = NULL;
268 			return qTrue;
269 		}
270 		break;
271 
272 	case '/':
273 		// Skip "// comments"
274 		if (p[1] == '/' && ps->properties & PSP_COMMENT_LINE) {
275 			while (*p != '\n') {
276 				if (*p == '\0') {
277 					*data = NULL;
278 					return qTrue;
279 				}
280 
281 				p++;
282 				ps->currentCol++;
283 			}
284 
285 			*data = p;
286 			return qTrue;
287 		}
288 
289 		// Skip "/* comments */"
290 		if (p[1] == '*' && ps->properties & PSP_COMMENT_BLOCK) {
291 			// Skip initial "/*"
292 			p += 2;
293 			ps->currentCol += 2;
294 
295 			// Skip everything until "*/"
296 			while (*p && (*p != '*' || p[1] != '/')) {
297 				if (*p == '\n') {
298 					ps->currentCol = 0;
299 					ps->currentLine++;
300 				}
301 				else {
302 					ps->currentCol++;
303 				}
304 
305 				p++;
306 			}
307 
308 			// Skip the final "*/"
309 			if (*p == '*' && p[1] == '/') {
310 				p += 2;
311 				ps->currentCol += 2;
312 				*data = p;
313 				return qTrue;
314 			}
315 
316 			// Didn't find final "*/" (hit EOF)
317 			PS_PrintError (ps, "PARSE ERROR: unclosed comment and hit EOF\n");
318 			*data = NULL;
319 			return qTrue;
320 		}
321 		break;
322 	}
323 
324 	// No comment block handled
325 	return qFalse;
326 }
327 
328 
329 /*
330 ============
331 PS_ConvertEscape
332 ============
333 */
PS_ConvertEscape(parse_t * ps,uint32 flags)334 static qBool PS_ConvertEscape (parse_t *ps, uint32 flags)
335 {
336 	uint32	len, i;
337 	char	*source, *scratch;
338 
339 	// If it's blank then why even try?
340 	len = strlen (ps->currentToken);
341 	if (!len)
342 		return qTrue;
343 
344 	// Convert escape characters
345 	source = &ps->currentToken[0];
346 	scratch = &ps_scratchToken[0];
347 	for (i=0 ; i<len ; i++) {
348 		if (source[0] != '\\') {
349 			*scratch++ = *source++;
350 			continue;
351 		}
352 
353 		// Hit a '\'
354 		switch (source[1]) {
355 		case 'n':
356 			if (flags & PSF_CONVERT_NEWLINE) {
357 				*scratch++ = '\n';
358 				source += 2;
359 				continue;
360 			}
361 			break;
362 
363 		default:
364 			PS_PrintWarning (ps, "PARSE WARNING: unknown escape character '%c%c', ignoring\n", source[0], source[1]);
365 			*scratch++ = *source++;
366 			*scratch++ = *source++;
367 			break;
368 		}
369 	}
370 	*scratch = '\0';
371 
372 	// Copy scratch back to the current token
373 	Q_strncpyz (ps->currentToken, ps_scratchToken, sizeof (ps->currentToken));
374 	return qTrue;
375 }
376 
377 
378 /*
379 ============
380 PS_ParseToken
381 - Skip whitespace and skip comments. If a comment gets skipped start over at
382   skip whitespace to make sure we're right up at the tail of the next token.
383 - If it's a quoted string, copy the string into the currentToken until an
384   end-quote is hit.
385 - If it's not a quoted string, store off characters into currentToken until
386   a quotation, comment, or blank space is hit.
387 ============
388 */
PS_ParseToken(parse_t * ps,uint32 flags,char ** target)389 qBool PS_ParseToken (parse_t *ps, uint32 flags, char **target)
390 {
391 	int		c, len;
392 	char	*data;
393 
394 	if (!ps)
395 		return qFalse;
396 
397 	// Check if the incoming data offset is valid (see if we hit EOF last the last run)
398 	data = ps->dataPtr;
399 	if (!data) {
400 		PS_PrintError (ps, "PARSE ERROR: called PS_ParseToken and already hit EOF\n");
401 		return qFalse;
402 	}
403 	ps->dataPtrLast = ps->dataPtr;
404 
405 	// Clear the current token
406 	ps->currentToken[0] = '\0';
407 	len = 0;
408 
409 	for ( ; ; ) {
410 		// Skip whitespace
411 		while ((c = *data) <= ' ') {
412 			switch (c) {
413 			case '\0':
414 				ps->dataPtr = NULL;
415 				return qFalse;
416 
417 			case '\n':
418 				if (!(flags & PSF_ALLOW_NEWLINES)) {
419 					ps->dataPtr = data;
420 					if (!ps->currentToken[0])
421 						return qFalse;
422 
423 					*target = ps->currentToken;
424 					return qTrue;
425 				}
426 
427 				ps->currentCol = 0;
428 				ps->currentLine++;
429 				break;
430 
431 			default:
432 				ps->currentCol++;
433 				break;
434 			}
435 
436 			data++;
437 		}
438 
439 		// Skip comments
440 		if (PS_SkipComments (ps, &data, flags)) {
441 			if (!data) {
442 				ps->dataPtr = NULL;
443 				return qFalse;
444 			}
445 		}
446 		else {
447 			// No comment, don't skip anymore whitespace
448 			break;
449 		}
450 	}
451 
452 	// Handle quoted strings specially
453 	// FIXME: PSP_QUOTES_TOKENED
454 	if (c == '\"') {
455 		ps->currentCol++;
456 		data++;
457 
458 		for ( ; ; ) {
459 			c = *data++;
460 			switch (c) {
461 			case '\0':
462 				ps->currentCol++;
463 				ps->dataPtr = data;
464 				PS_PrintError (ps, "PARSE ERROR: hit EOF while inside quotation\n");
465 				return qFalse;
466 
467 			case '\"':
468 				ps->currentCol++;
469 				ps->dataPtr = data;
470 				ps->currentToken[len] = '\0';
471 
472 				// Empty token
473 				if (!ps->currentToken[0])
474 					return qFalse;
475 
476 				// Lower-case if desired
477 				if (flags & PSF_TO_LOWER)
478 					Q_strlwr (ps->currentToken);
479 				if (flags & PSF_CONVERT_NEWLINE) {
480 					if (!PS_ConvertEscape (ps, flags))
481 						return qFalse;
482 				}
483 
484 				*target = ps->currentToken;
485 				return qTrue;
486 
487 			case '\n':
488 				if (!(flags & PSF_ALLOW_NEWLINES)) {
489 					ps->dataPtr = data;
490 					if (!ps->currentToken[0])
491 						return qFalse;
492 
493 					*target = ps->currentToken;
494 					return qTrue;
495 				}
496 
497 				ps->currentCol = 0;
498 				ps->currentLine++;
499 				break;
500 
501 			default:
502 				ps->currentCol++;
503 				break;
504 			}
505 
506 			if (len < MAX_PS_TOKCHARS)
507 				ps->currentToken[len++] = c;
508 		}
509 	}
510 
511 	// Parse a regular word
512 	for ( ; ; ) {
513 		if (c <= ' ' || c == '\"')	// FIXME: PSP_QUOTES_TOKENED
514 			break;	// Stop at spaces and quotation marks
515 
516 		// Stop at opening comments
517 		if (ps->properties & PSP_COMMENT_MASK) {
518 			if (c == '#' && ps->properties & PSP_COMMENT_POUND)
519 				break;
520 			if (c == '/') {
521 				if (data[1] == '/' && ps->properties & PSP_COMMENT_LINE)
522 					break;
523 				if (data[1] == '*' && ps->properties & PSP_COMMENT_BLOCK)
524 					break;
525 			}
526 			if (c == '*' && data[1] == '/' && ps->properties & PSP_COMMENT_BLOCK) {
527 				ps->dataPtr = data;
528 				PS_PrintError (ps, "PARSE ERROR: end-comment '*/' with no opening\n");
529 				return qFalse;
530 			}
531 		}
532 
533 		// Store character
534 		if (len < MAX_PS_TOKCHARS)
535 			ps->currentToken[len++] = c;
536 
537 		ps->currentCol++;
538 		c = *++data;
539 	}
540 
541 	// Check length
542 	if (len >= MAX_PS_TOKCHARS-1) {
543 		PS_PrintError (ps, "PARSE ERROR: token too long!\n");
544 		ps->dataPtr = data;
545 		return qFalse;
546 	}
547 
548 	// Done
549 	ps->currentToken[len] = '\0';
550 	ps->dataPtr = data;
551 
552 	// Empty token
553 	if (!ps->currentToken[0])
554 		return qFalse;
555 
556 	// Lower-case if desired
557 	if (flags & PSF_TO_LOWER)
558 		Q_strlwr (ps->currentToken);
559 	if (flags & PSF_CONVERT_NEWLINE) {
560 		if (!PS_ConvertEscape (ps, flags))
561 			return qFalse;
562 	}
563 	*target = ps->currentToken;
564 	return qTrue;
565 }
566 
567 
568 /*
569 ============
570 PS_UndoParse
571 
572 Simply move back to the point parsing was at before the last token parse.
573 Has issues with datatype parsing and "1" "1" "1" etc token values.
574 ============
575 */
PS_UndoParse(parse_t * ps)576 void PS_UndoParse (parse_t *ps)
577 {
578 	if (!ps)
579 		return;
580 
581 	ps->dataPtr = ps->dataPtrLast;
582 }
583 
584 
585 /*
586 ============
587 PS_SkipLine
588 
589 Unlike other parsing functions in this file, this does not route through
590 PS_ParseToken simply for the small performance gain.
591 
592 Simply skip from the current position to the first new-line character.
593 ============
594 */
PS_SkipLine(parse_t * ps)595 void PS_SkipLine (parse_t *ps)
596 {
597 	char	*data;
598 
599 	if (!ps)
600 		return;
601 
602 	// Check if the incoming data offset is valid (see if we hit EOF last the last run)
603 	data = ps->dataPtr;
604 	if (!data) {
605 		PS_PrintError (ps, "PARSE ERROR: called PS_SkipLine and already hit EOF\n");
606 		return;
607 	}
608 	ps->dataPtrLast = ps->dataPtr;
609 
610 	// Skip to the end of the line
611 	while (*data && *data != '\n') {
612 		data++;
613 		ps->currentCol++;
614 	}
615 	ps->dataPtr = data;
616 }
617 
618 /*
619 ==============================================================================
620 
621 	DATA-TYPE PARSING
622 
623 ==============================================================================
624 */
625 
626 /*
627 ============
628 PS_VerifyCharVec
629 ============
630 */
PS_VerifyCharVec(char * token,char * target)631 static qBool PS_VerifyCharVec (char *token, char *target)
632 {
633 	uint32	len, i;
634 	int		temp;
635 
636 	len = strlen (token);
637 	for (i=0 ; i<len ; i++) {
638 		if (token[i] >= '0' || token[i] <= '9')
639 			continue;
640 		if (token[i] == '-' && i == 0)
641 			continue;
642 		break;
643 	}
644 	if (i != len)
645 		return qFalse;
646 
647 	temp = atoi (token);
648 	if (temp < -128 || temp > 127)
649 		return qFalse;
650 
651 	*target = temp;
652 	return qTrue;
653 }
654 
655 
656 /*
657 ============
658 PS_VerifyBooleanVec
659 ============
660 */
PS_VerifyBooleanVec(char * token,qBool * target)661 static qBool PS_VerifyBooleanVec (char *token, qBool *target)
662 {
663 	if (!strcmp (token, "1") || !strcmp (token, "true")) {
664 		*target = qTrue;
665 		return qTrue;
666 	}
667 	if (!strcmp (token, "0") || !strcmp (token, "false")) {
668 		*target = qFalse;
669 		return qTrue;
670 	}
671 
672 	return qFalse;
673 }
674 
675 
676 /*
677 ============
678 PS_VerifyByteVec
679 ============
680 */
PS_VerifyByteVec(char * token,byte * target)681 static qBool PS_VerifyByteVec (char *token, byte *target)
682 {
683 	uint32	len, i;
684 	int		temp;
685 
686 	len = strlen (token);
687 	for (i=0 ; i<len ; i++) {
688 		if (token[i] >= '0' || token[i] <= '9')
689 			continue;
690 		if (token[i] == '-' && i == 0)
691 			continue;
692 		break;
693 	}
694 	if (i != len)
695 		return qFalse;
696 
697 	temp = atoi (token);
698 	if (temp < 0 || temp > 255)
699 		return qFalse;
700 
701 	*target = temp;
702 	return qTrue;
703 }
704 
705 
706 /*
707 ============
708 PS_VerifyDoubleVec
709 ============
710 */
PS_VerifyDoubleVec(char * token,double * target)711 static qBool PS_VerifyDoubleVec (char *token, double *target)
712 {
713 	uint32	len, i;
714 	qBool	dot;
715 
716 	dot = qFalse;
717 	len = strlen (token);
718 	for (i=0 ; i<len ; i++) {
719 		if (token[i] >= '0' || token[i] <= '9')
720 			continue;
721 		if (token[i] == '-' && i == 0)
722 			continue;
723 		if (token[i] == '.' && !dot) {
724 			dot = qTrue;
725 			continue;
726 		}
727 		break;
728 	}
729 	if (i != len)
730 		return qFalse;
731 
732 	*target = atof (token);
733 	return qTrue;
734 }
735 
736 
737 /*
738 ============
739 PS_VerifyFloatVec
740 ============
741 */
PS_VerifyFloatVec(char * token,float * target)742 static qBool PS_VerifyFloatVec (char *token, float *target)
743 {
744 	uint32	len, i;
745 	qBool	dot;
746 
747 	dot = qFalse;
748 	len = strlen (token);
749 	for (i=0 ; i<len ; i++) {
750 		if (token[i] >= '0' || token[i] <= '9')
751 			continue;
752 		if (token[i] == '.' && !dot) {
753 			dot = qTrue;
754 			continue;
755 		}
756 		if (i == 0) {
757 			if (token[i] == '-')
758 				continue;
759 		}
760 		else if (i == len-1 && (token[i] == 'f' || token[i] == 'F'))
761 			continue;
762 		break;
763 	}
764 	if (i != len)
765 		return qFalse;
766 
767 	*target = (float)atof (token);
768 	return qTrue;
769 }
770 
771 
772 /*
773 ============
774 PS_VerifyIntegerVec
775 ============
776 */
PS_VerifyIntegerVec(char * token,int * target)777 static qBool PS_VerifyIntegerVec (char *token, int *target)
778 {
779 	uint32	len, i;
780 
781 	len = strlen (token);
782 	for (i=0 ; i<len ; i++) {
783 		if (token[i] >= '0' || token[i] <= '9')
784 			continue;
785 		if (token[i] == '-' && i == 0)
786 			continue;
787 		break;
788 	}
789 	if (i != len)
790 		return qFalse;
791 
792 	*target = atoi (token);
793 	return qTrue;
794 }
795 
796 
797 /*
798 ============
799 PS_VerifyUIntegerVec
800 ============
801 */
PS_VerifyUIntegerVec(char * token,uint32 * target)802 static qBool PS_VerifyUIntegerVec (char *token, uint32 *target)
803 {
804 	uint32	len, i;
805 
806 	len = strlen (token);
807 	for (i=0 ; i<len ; i++) {
808 		if (token[i] >= '0' || token[i] <= '9')
809 			continue;
810 		break;
811 	}
812 	if (i != len)
813 		return qFalse;
814 
815 	*target = atoi (token);
816 	return qTrue;
817 }
818 
819 
820 /*
821 ============
822 PS_ParseDataVector
823 ============
824 */
PS_ParseDataVector(char * token,uint32 dataType,void * target)825 static qBool PS_ParseDataVector (char *token, uint32 dataType, void *target)
826 {
827 	switch (dataType) {
828 	case PSDT_CHAR:
829 		return PS_VerifyCharVec (token, (char *)target);
830 	case PSDT_BOOLEAN:
831 		return PS_VerifyBooleanVec (token, (qBool *)target);
832 	case PSDT_BYTE:
833 		return PS_VerifyByteVec (token, (byte *)target);
834 	case PSDT_DOUBLE:
835 		return PS_VerifyDoubleVec (token, (double *)target);
836 	case PSDT_FLOAT:
837 		return PS_VerifyFloatVec (token, (float *)target);
838 	case PSDT_INTEGER:
839 		return PS_VerifyIntegerVec (token, (int *)target);
840 	case PSDT_UINTEGER:
841 		return PS_VerifyUIntegerVec (token, (uint32 *)target);
842 	}
843 
844 	return qFalse;
845 }
846 
847 
848 /*
849 ============
850 PS_ParseDataType
851 ============
852 */
PS_ParseDataType(parse_t * ps,uint32 flags,uint32 dataType,void * target,uint32 numVecs)853 qBool PS_ParseDataType (parse_t *ps, uint32 flags, uint32 dataType, void *target, uint32 numVecs)
854 {
855 	char	*token, *data;
856 	uint32	len, i;
857 	int		c;
858 
859 	// Parse the next token
860 	if (!PS_ParseToken (ps, flags, &token))
861 		return qFalse;
862 
863 	// Individual tokens
864 	// FIXME: support commas and () [] {} brackets
865 	if (!strchr (token, ' ') && !strchr (token, ',')) {
866 		for (i=0 ; i<numVecs ; i++) {
867 			if (i) {
868 				// Parse the next token
869 				if (!PS_ParseToken (ps, flags, &token)) {
870 					PS_PrintError (ps, "PARSE ERROR: vector missing parameters!\n");
871 					return qFalse;
872 				}
873 
874 				// Storage position
875 				switch (dataType) {
876 				case PSDT_CHAR:		target = (char *)target+1;		break;
877 				case PSDT_BOOLEAN:	target = (qBool *)target+1;		break;
878 				case PSDT_BYTE:		target = (byte *)target+1;		break;
879 				case PSDT_DOUBLE:	target = (double *)target+1;	break;
880 				case PSDT_FLOAT:	target = (float *)target+1;		break;
881 				case PSDT_INTEGER:	target = (int *)target+1;		break;
882 				case PSDT_UINTEGER:	target = (uint32 *)target+1;	break;
883 				}
884 			}
885 
886 			// Check the data type
887 			if (!PS_ParseDataVector (token, dataType, target)) {
888 				PS_PrintError (ps, "PARSE ERROR: does not evaluate to desired data type!\n");
889 				return qFalse;
890 			}
891 		}
892 
893 		return qTrue;
894 	}
895 
896 	// Single token with all vectors
897 	// FIXME: support () [] {} brackets
898 	data = token;
899 	for (i=0 ; i<numVecs ; i++) {
900 		ps_scratchToken[0] = '\0';
901 		len = 0;
902 
903 		// Skip white-space
904 		for ( ; ; ) {
905 			c = *data++;
906 			if (c <= ' ' || c == ',')
907 				continue;
908 			if (c == '\0')
909 				break;
910 			break;
911 		}
912 
913 		// Parse this token into a sub-token stored in ps_scratchToken
914 		for ( ; ; ) {
915 			if (c <= ' ' || c == ',') {
916 				data--;
917 				break;	// Stop at white space and commas
918 			}
919 
920 			ps_scratchToken[len++] = c;
921 			c = *data++;
922 		}
923 		ps_scratchToken[len++] = '\0';
924 
925 		// Too few vecs
926 		if (!ps_scratchToken[0]) {
927 			PS_PrintError (ps, "PARSE ERROR: missing vector parameters!\n");
928 			return qFalse;
929 		}
930 
931 		// Check the data type and set the target
932 		if (!PS_ParseDataVector (ps_scratchToken, dataType, target)) {
933 			PS_PrintError (ps, "PARSE ERROR: '%s' does not evaluate to desired data type %s!\n", ps_scratchToken, ps_dataTypeStr[dataType]);
934 			return qFalse;
935 		}
936 
937 		// Next storage position
938 		switch (dataType) {
939 		case PSDT_CHAR:		target = (char *)target+1;		break;
940 		case PSDT_BOOLEAN:	target = (qBool *)target+1;		break;
941 		case PSDT_BYTE:		target = (byte *)target+1;		break;
942 		case PSDT_DOUBLE:	target = (double *)target+1;	break;
943 		case PSDT_FLOAT:	target = (float *)target+1;		break;
944 		case PSDT_INTEGER:	target = (int *)target+1;		break;
945 		case PSDT_UINTEGER:	target = (uint32 *)target+1;	break;
946 		}
947 	}
948 
949 	// Check for too much data
950 	for ( ; ; ) {
951 		c = *data++;
952 		if (c == '\0')
953 			break;
954 		if (c > ' ') {
955 			PS_PrintError (ps, "PARSE ERROR: too many vector parameters!\n");
956 			return qFalse;
957 		}
958 	}
959 
960 	return qTrue;
961 }
962