]> git.xonotic.org Git - xonotic/gmqcc.git/blob - lex.c
Cleanups
[xonotic/gmqcc.git] / lex.c
1 /*
2  * Copyright (C) 2012 
3  *      Dale Weiler
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy of
6  * this software and associated documentation files (the "Software"), to deal in
7  * the Software without restriction, including without limitation the rights to
8  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is furnished to do
10  * so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in all
13  * copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 #include <stdio.h>
24 #include <limits.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27 #include <string.h>
28 #include "gmqcc.h"
29
30 /*
31  * Keywords are multichar, punctuation lexing is a bit more complicated
32  * than keyword lexing.
33  */
34 static const char *const lex_keywords[] = {
35         "do",    "else",     "if",     "while",
36         "break", "continue", "return", "goto",
37         "for",   "typedef"
38 };
39
40 struct lex_file *lex_open(FILE *fp) {
41         struct lex_file *lex = mem_a(sizeof(struct lex_file));
42         if (lex) {
43                 lex->file = fp;
44                 fseek(lex->file, 0, SEEK_END);
45                 lex->length = ftell(lex->file);
46                 lex->size   = lex->length; /* copy, this is never changed */
47                 fseek(lex->file, 0, SEEK_SET);
48                 lex->last = 0;
49                 
50                 memset(lex->peek, 0, sizeof(lex->peek));
51         }
52         return lex;
53 }
54
55 void lex_close(struct lex_file *file) {
56         if (!file) return;
57         
58         fclose(file->file); /* may already be closed */
59         mem_d(file);
60 }
61
62 static void lex_addch(int ch, struct lex_file *file) {
63         if (file->current <  sizeof(file->lastok)-1)
64                 file->lastok[file->current++] = (char)ch;
65         if (file->current == sizeof(file->lastok)-1)
66                 file->lastok[file->current]   = (char)'\0';
67 }
68 static inline void lex_clear(struct lex_file *file) {
69         file->current = 0;
70 }
71
72 /*
73  * read in inget/unget character from a lexer stream.
74  * This doesn't play with file streams, the lexer has
75  * it's own internal state for this.
76  */
77 static int lex_inget(struct lex_file *file) {
78         file->length --;
79         if (file->last > 0)
80                 return file->peek[--file->last];
81         return fgetc(file->file);
82 }
83 static void lex_unget(int ch, struct lex_file *file) {
84         if (file->last < sizeof(file->peek))
85                 file->peek[file->last++] = ch;
86         file->length ++;
87 }
88
89 /*
90  * This is trigraph and digraph support, a feature not qc compiler
91  * supports.  Moving up in this world!
92  */
93 static int lex_trigraph(struct lex_file *file) {
94         int  ch;
95         if ((ch = lex_inget(file)) != '?') {
96                 lex_unget(ch, file);
97                 return '?';
98         }
99         
100         ch = lex_inget(file);
101         switch (ch) {
102                 case '(' : return '[' ;
103                 case ')' : return ']' ;
104                 case '/' : return '\\';
105                 case '\'': return '^' ;
106                 case '<' : return '{' ;
107                 case '>' : return '}' ;
108                 case '!' : return '|' ;
109                 case '-' : return '~' ;
110                 case '=' : return '#' ;
111                 default:
112                         lex_unget('?', file);
113                         lex_unget(ch , file);
114                         return '?';
115         }
116         return '?';
117 }
118 static int lex_digraph(struct lex_file *file, int first) {
119         int ch = lex_inget(file);
120         switch (first) {
121                 case '<':
122                         if (ch == '%') return '{';
123                         if (ch == ':') return '[';
124                         break;
125                 case '%':
126                         if (ch == '>') return '}';
127                         if (ch == ':') return '#';
128                         break;
129                 case ':':
130                         if (ch == '>') return ']';
131                         break;
132         }
133         
134         lex_unget(ch, file);
135         return first;
136 }
137
138 static int lex_getch(struct lex_file *file) {
139         int ch = lex_inget(file);
140         if (ch == '?')
141                 return lex_trigraph(file);
142         if (ch == '<' || ch == ':' || ch == '%')
143                 return lex_digraph (file, ch);
144                 
145         return ch;
146 }
147
148 static int lex_get(struct lex_file *file) {
149         int ch;
150         if (!isspace(ch = lex_getch(file)))
151                 return ch;
152         
153         /* skip over all spaces */
154         while (isspace(ch) && ch != '\n')
155                 ch = lex_getch(file);
156                 
157         if (ch == '\n')
158                 return ch;
159         lex_unget(ch, file);
160         return ' ';
161 }
162
163 static int lex_skipchr(struct lex_file *file) {
164         int ch;
165         int it;
166         
167         lex_clear(file);
168         lex_addch('\'', file);
169         
170         for (it = 0; it < 2 && ((ch = lex_inget(file)) != '\''); it++) {
171                 lex_addch(ch, file);
172                 
173                 if (ch == '\n')
174                         return ERROR_LEX;
175                 if (ch == '\\')
176                         lex_addch(lex_getch(file), file);
177         }
178         lex_addch('\'', file);
179         lex_addch('\0', file);
180         
181         if (it > 2)
182                 return ERROR_LEX;
183                 
184         return LEX_CHRLIT;
185 }
186
187 static int lex_skipstr(struct lex_file *file) {
188         int ch;
189         lex_clear(file);
190         lex_addch('"', file);
191         
192         while ((ch = lex_getch(file)) != '"') {
193                 if (ch == '\n' || ch == EOF)
194                         return ERROR_LEX;
195                         
196                 lex_addch(ch, file);
197                 if (ch == '\\')
198                         lex_addch(lex_inget(file), file);
199         }
200         
201         lex_addch('"', file);
202         lex_addch('\0', file);
203         
204         return LEX_STRLIT;
205 }
206 static int lex_skipcmt(struct lex_file *file) {
207         int ch;
208         lex_clear(file);
209         ch = lex_getch(file);
210         
211         if (ch == '/') {
212                 lex_addch('/', file);
213                 lex_addch('/', file);
214                 
215                 while ((ch = lex_getch(file)) != '\n') {
216                         if (ch == '\\') {
217                                 lex_addch(ch, file);
218                                 lex_addch(lex_getch(file), file);
219                         } else {
220                                 lex_addch(ch, file);
221                         }
222                 }
223                 lex_addch('\0', file);
224                 return LEX_COMMENT;
225         }
226         
227         if (ch != '*') {
228                 lex_unget(ch, file);
229                 return '/';
230         }
231         
232         lex_addch('/', file);
233         
234         /* hate this */
235         do {
236                 lex_addch(ch, file);
237                 while ((ch = lex_getch(file)) != '*') {
238                         if (ch == EOF)
239                                 return error(ERROR_LEX, "malformatted comment at line %d", file->line);
240                         else
241                                 lex_addch(ch, file);
242                 }
243                 lex_addch(ch, file);
244         } while ((ch = lex_getch(file)) != '/');
245         
246         lex_addch('/',  file);
247         lex_addch('\0', file);
248         
249         return LEX_COMMENT;
250 }
251
252 static int lex_getsource(struct lex_file *file) {
253         int ch = lex_get(file);
254         
255         /* skip char/string/comment */
256         switch (ch) {
257                 case '\'': return lex_skipchr(file);
258                 case '"':  return lex_skipstr(file);
259                 case '/':  return lex_skipcmt(file);
260                 default:   return ch;
261         }
262 }
263
264 int lex_token(struct lex_file *file) {
265         int ch = lex_getsource(file);
266         int it;
267         
268         /* valid identifier */
269         if (ch > 0 && (ch == '_' || isalpha(ch))) {
270                 lex_clear(file);
271                 while (ch > 0 && (isalpha(ch) || ch == '_')) {
272                         lex_addch(ch, file);
273                         ch = lex_getsource(file);
274                 }
275                 lex_unget(ch,   file);
276                 lex_addch('\0', file);
277                 
278                 /* look inside the table for a keyword .. */
279                 for (it = 0; it < sizeof(lex_keywords)/sizeof(*lex_keywords); it++)
280                         if (!strncmp(file->lastok, lex_keywords[it], sizeof(lex_keywords[it])))
281                                 return it;
282                                 
283                 /* try a type? */
284                 #define TEST_TYPE(X)                                 \
285                     do {                                             \
286                         if (!strncmp(X, "float",  sizeof("float")))  \
287                             return TOKEN_FLOAT;                      \
288                         if (!strncmp(X, "vector", sizeof("vector"))) \
289                             return TOKEN_VECTOR;                     \
290                         if (!strncmp(X, "string", sizeof("string"))) \
291                             return TOKEN_STRING;                     \
292                         if (!strncmp(X, "entity", sizeof("entity"))) \
293                             return TOKEN_ENTITY;                     \
294                         if (!strncmp(X, "void"  , sizeof("void")))   \
295                                 return TOKEN_VOID;                       \
296                     } while(0)
297                 
298                 TEST_TYPE(file->lastok);
299                 
300                 /* try the hashtable for typedefs? */
301                 if (typedef_find(file->lastok))
302                         TEST_TYPE(typedef_find(file->lastok)->name);
303                         
304                 return LEX_IDENT;
305         }
306         return ch;
307 }
308
309 void lex_reset(struct lex_file *file) {
310         file->current = 0;
311         file->last    = 0;
312         file->length  = file->size;
313         fseek(file->file, 0, SEEK_SET);
314         
315         memset(file->peek,   0, sizeof(file->peek  ));
316         memset(file->lastok, 0, sizeof(file->lastok));
317 }