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