1 #include <allegro5/allegro.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include "token.h"
5 
6 /* declarations of these variables - see token.h for comments on their meaning */
7 int Error, Lines;
8 char ErrorText[1024];
9 struct Tok Token;
10 ALLEGRO_FILE *input = NULL;
11 
12 static int my_ungetc_c = -1;
13 
14 /*
15 
16 	OVERARCHING NOTES ON THE LEVEL PARSER
17 	-------------------------------------
18 
19 	We're using something a lot like a recursive descent parser here except that
20 	it never actually needs to recurse. It is typified by knowing a bunch of
21 	tokens (which are not necessarily individual characters but may be lumps of
22 	characters that make sense together - e.g. 'a number' might be a recognised
23 	token and 2345 is a number) and two functions:
24 
25 	GetToken - reads a new token from the input.
26 
27 	ExpectToken - calls GetToken, checks if the token is the one expected and
28 	if not flags up an error.
29 
30 	A bunch of other functions call these two to read the file input, according
31 	to what it was decided it should look like. Suppose we want a C-style list
32 	of numbers that looks like this:
33 
34 	{ 1, 2, 3, ... }
35 
36 	Then we might write the function:
37 
38 		ExpectToken( '{' );
39 		while(1)
40 		{
41 			ExpectToken( number );
42 			GetToken();
43 			switch(token)
44 			{
45 				default: flag an error; return;
46 				case '}': return;
47 				case ',': break;
48 			}
49 		}
50 
51 */
52 
53 /*
54 
55 	Two macros -
56 
57 		whitespace(v) evaluates to non-zero if 'v' is a character considered to
58 		be whitespace (i.e. a space, newline, carriage return or tab)
59 
60 		breaker(v) evaluates to non-zero if 'v' is a character that indicates
61 		one of the longer tokens (e.g. a number) is over
62 
63 */
64 #define whitespace(v) ((v == ' ') || (v == '\r') || (v == '\n') || (v == '\t'))
65 #define breaker(v)	(whitespace(v) || (v == '{') || (v == '}') || (v == ','))
66 
67 
68 /*
69 
70 	my_fgetc is a direct replacement for fgetc that takes the same parameters
71 	and returns the same result but counts lines while it goes.
72 
73 	There is a slight complication here because of the different line endings
74 	used by different operating systems. The code will accept a newline or a
75 	carriage return or both in either order. But if a series of alternating
76 	new lines and carriage returns is found then they are bundled together
77 	in pairs for line counting.
78 
79 	E.g.
80 
81 		\r					- 1 line ending
82 		\n					- 1 line ending
83 		\n\r				- 1 line ending
84 		\r\n\r\n			- 2 line endings
85 		\r\r				- 2 line endings
86 
87 */
my_fgetc(ALLEGRO_FILE * f)88 static int my_fgetc(ALLEGRO_FILE * f)
89 {
90    static int LastChar = '\0';
91    int r;
92    char TestChar;
93 
94    if (my_ungetc_c != -1) {
95       r = my_ungetc_c;
96       my_ungetc_c = -1;
97    }
98    else
99       r = al_fgetc(f);
100 
101    if (r == '\n' || r == '\r') {
102       TestChar = (r == '\n') ? '\r' : '\n';
103 
104       if (LastChar != TestChar) {
105          Lines++;
106          LastChar = r;
107       } else
108          LastChar = '\0';
109    } else
110       LastChar = r;
111 
112    return r;
113 }
114 
115 /*
116    Hackish way to ungetc a single character.
117 */
my_ungetc(int c)118 static void my_ungetc(int c)
119 {
120    my_ungetc_c = c;
121 }
122 
123 /*
124 
125 	GetTokenInner is the guts of GetToken - it reads characters from the input
126 	file and tokenises them
127 
128 */
129 
GetTokenInner(void)130 static void GetTokenInner(void)
131 {
132    char *Ptr = Token.Text;
133 
134    /* filter leading whitespace */
135    do {
136       *Ptr = my_fgetc(input);
137    } while (whitespace(*Ptr));
138 
139    /* check whether the token can be recognised from the first character alone.
140       This is possible if the token is any of the single character tokens (i.e.
141       { } and ,) a comment or a string (i.e. begins with a quote) */
142    if (*Ptr == '{') {
143       Token.Type = TK_OPENBRACE;
144       return;
145    }
146 
147    if (*Ptr == '}') {
148       Token.Type = TK_CLOSEBRACE;
149       return;
150    }
151 
152    if (*Ptr == ',') {
153       Token.Type = TK_COMMA;
154       return;
155    }
156 
157    if (*Ptr == '\"') {
158       Token.Type = TK_STRING;
159       /* if this is a string then read until EOF or the next quote. In
160          ensuring that we don't overrun the available string storage in the
161          Tok struct an extra potential error condition is invoked */
162       do {
163          *Ptr++ = my_fgetc(input);
164       } while (Ptr[-1] != '\"' && !al_feof(input)
165                && (Ptr - Token.Text) < 256);
166       Ptr[-1] = '\0';
167       if (al_feof(input) || (strlen(Token.Text) == 255))
168          Error = 1;
169       return;
170    }
171 
172    if (*Ptr == '#') {
173       Token.Type = TK_COMMENT;
174       /* comments run to end of line, so find the next \r or \n */
175       do {
176          *Ptr++ = my_fgetc(input);
177       } while (Ptr[-1] != '\r' && Ptr[-1] != '\n');
178       Ptr[-1] = '\0';
179       return;
180    }
181 
182    Ptr++;
183    *Ptr = '\0';
184 
185    /* if we're here, the token was not recognisable from the first character
186       alone, meaning it is either a number or ill formed */
187    while (1) {
188       char newc = my_fgetc(input);  /* read new character */
189 
190       /* check if this is a terminator or we have hit end of file as in either
191          circumstance we should check if what we have makes a valid number */
192       if (breaker(newc) || al_feof(input)) {
193          /* check first if we have a valid integer quantity. If so fill
194             IQuantity with that and cast to double for FQuantity */
195          char *eptr;
196 
197          my_ungetc(newc);
198          Token.IQuantity = strtol(Token.Text, &eptr, 0);
199          if (!*eptr) {
200             Token.Type = TK_NUMBER;
201             Token.FQuantity = (float)Token.IQuantity;
202             return;
203          }
204 
205          /* if not, check if we have a valid floating point quantity. If
206             so, fill FQuantity with that and cast to int for IQuantity */
207          Token.FQuantity = (float)strtod(Token.Text, &eptr);
208          if (!*eptr) {
209             Token.Type = TK_NUMBER;
210             Token.IQuantity = (int)Token.FQuantity;
211             return;
212          }
213 
214          /* if what we have doesn't make integer or floating point sense
215             then this section of the file appears not to be a valid token */
216          Token.Type = TK_UNKNOWN;
217          return;
218       }
219 
220       *Ptr++ = newc;
221       *Ptr = '\0';
222    }
223 }
224 
225 /*
226 
227 	GetToken is a wrapper for GetTokenInner that discards comments
228 
229 */
GetToken(void)230 void GetToken(void)
231 {
232    while (1) {
233       GetTokenInner();
234       if (Token.Type != TK_COMMENT)
235          return;
236    }
237 }
238 
239 /*
240 
241 	ExpectToken calls GetToken and then compares to the specified type, setting
242 	an error if the found token does not match the expected type
243 
244 */
ExpectToken(enum TokenTypes Type)245 void ExpectToken(enum TokenTypes Type)
246 {
247    GetToken();
248    if (Token.Type != Type)
249       Error = 1;
250 }
251