]> git.xonotic.org Git - xonotic/gmqcc.git/blob - lex.c
digraph and trigraph tests
[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 || !fp)
43                 return NULL;
44                 
45         lex->file = fp;
46         fseek(lex->file, 0, SEEK_END);
47         lex->length = ftell(lex->file);
48         lex->size   = lex->length; /* copy, this is never changed */
49         fseek(lex->file, 0, SEEK_SET);
50         lex->last = 0;
51         
52         memset(lex->peek, 0, sizeof(lex->peek));
53         return lex;
54 }
55
56 void lex_close(struct lex_file *file) {
57         if (!file) return;
58         
59         fclose(file->file); /* may already be closed */
60         mem_d(file);
61 }
62
63 static void lex_addch(int ch, struct lex_file *file) {
64         if (file->current <  sizeof(file->lastok)-1)
65                 file->lastok[file->current++] = (char)ch;
66         if (file->current == sizeof(file->lastok)-1)
67                 file->lastok[file->current]   = (char)'\0';
68 }
69 static inline void lex_clear(struct lex_file *file) {
70         file->current = 0;
71 }
72
73 /*
74  * read in inget/unget character from a lexer stream.
75  * This doesn't play with file streams, the lexer has
76  * it's own internal state for this.
77  */
78 static int lex_inget(struct lex_file *file) {
79         file->length --;
80         if (file->last > 0)
81                 return file->peek[--file->last];
82         return fgetc(file->file);
83 }
84 static void lex_unget(int ch, struct lex_file *file) {
85         if (file->last < sizeof(file->peek))
86                 file->peek[file->last++] = ch;
87         file->length ++;
88 }
89
90 /*
91  * This is trigraph and digraph support, a feature not qc compiler
92  * supports.  Moving up in this world!
93  */
94 static int lex_trigraph(struct lex_file *file) {
95         int  ch;
96         if ((ch = lex_inget(file)) != '?') {
97                 lex_unget(ch, file);
98                 return '?';
99         }
100         
101         ch = lex_inget(file);
102         switch (ch) {
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                 case '=' : return '#' ;
112                 default:
113                         lex_unget('?', file);
114                         lex_unget(ch , file);
115                         return '?';
116         }
117         return '?';
118 }
119 static int lex_digraph(struct lex_file *file, int first) {
120         int ch = lex_inget(file);
121         switch (first) {
122                 case '<':
123                         if (ch == '%') return '{';
124                         if (ch == ':') return '[';
125                         break;
126                 case '%':
127                         if (ch == '>') return '}';
128                         if (ch == ':') return '#';
129                         break;
130                 case ':':
131                         if (ch == '>') return ']';
132                         break;
133         }
134         
135         lex_unget(ch, file);
136         return first;
137 }
138
139 static int lex_getch(struct lex_file *file) {
140         int ch = lex_inget(file);
141         if (ch == '?')
142                 return lex_trigraph(file);
143         if (ch == '<' || ch == ':' || ch == '%')
144                 return lex_digraph (file, ch);
145                 
146         return ch;
147 }
148
149 static int lex_get(struct lex_file *file) {
150         int ch;
151         if (!isspace(ch = lex_getch(file)))
152                 return ch;
153         
154         /* skip over all spaces */
155         while (isspace(ch) && ch != '\n')
156                 ch = lex_getch(file);
157                 
158         if (ch == '\n')
159                 return ch;
160         lex_unget(ch, file);
161         return ' ';
162 }
163
164 static int lex_skipchr(struct lex_file *file) {
165         int ch;
166         int it;
167         
168         lex_clear(file);
169         lex_addch('\'', file);
170         
171         for (it = 0; it < 2 && ((ch = lex_inget(file)) != '\''); it++) {
172                 lex_addch(ch, file);
173                 
174                 if (ch == '\n')
175                         return ERROR_LEX;
176                 if (ch == '\\')
177                         lex_addch(lex_getch(file), file);
178         }
179         lex_addch('\'', file);
180         lex_addch('\0', file);
181         
182         if (it > 2)
183                 return ERROR_LEX;
184                 
185         return LEX_CHRLIT;
186 }
187
188 static int lex_skipstr(struct lex_file *file) {
189         int ch;
190         lex_clear(file);
191         lex_addch('"', file);
192         
193         while ((ch = lex_getch(file)) != '"') {
194                 if (ch == '\n' || ch == EOF)
195                         return ERROR_LEX;
196                         
197                 lex_addch(ch, file);
198                 if (ch == '\\')
199                         lex_addch(lex_inget(file), file);
200         }
201         
202         lex_addch('"', file);
203         lex_addch('\0', file);
204         
205         return LEX_STRLIT;
206 }
207 static int lex_skipcmt(struct lex_file *file) {
208         int ch;
209         lex_clear(file);
210         ch = lex_getch(file);
211         
212         if (ch == '/') {
213                 lex_addch('/', file);
214                 lex_addch('/', file);
215                 
216                 while ((ch = lex_getch(file)) != '\n') {
217                         if (ch == '\\') {
218                                 lex_addch(ch, file);
219                                 lex_addch(lex_getch(file), file);
220                         } else {
221                                 lex_addch(ch, file);
222                         }
223                 }
224                 lex_addch('\0', file);
225                 return LEX_COMMENT;
226         }
227         
228         if (ch != '*') {
229                 lex_unget(ch, file);
230                 return '/';
231         }
232         
233         lex_addch('/', file);
234         
235         /* hate this */
236         do {
237                 lex_addch(ch, file);
238                 while ((ch = lex_getch(file)) != '*') {
239                         if (ch == EOF)
240                                 return error(ERROR_LEX, "malformatted comment at line", "");
241                         else
242                                 lex_addch(ch, file);
243                 }
244                 lex_addch(ch, file);
245         } while ((ch = lex_getch(file)) != '/');
246         
247         lex_addch('/',  file);
248         lex_addch('\0', file);
249         
250         return LEX_COMMENT;
251 }
252
253 static int lex_getsource(struct lex_file *file) {
254         int ch = lex_get(file);
255         
256         /* skip char/string/comment */
257         switch (ch) {
258                 case '\'': return lex_skipchr(file);
259                 case '"':  return lex_skipstr(file);
260                 case '/':  return lex_skipcmt(file);
261                 default:   return ch;
262         }
263 }
264
265 int lex_token(struct lex_file *file) {
266         int ch = lex_getsource(file);
267         int it;
268         
269         /* valid identifier */
270         if (ch > 0 && (ch == '_' || isalpha(ch))) {
271                 lex_clear(file);
272                 while (ch > 0 && (isalpha(ch) || ch == '_')) {
273                         lex_addch(ch, file);
274                         ch = lex_getsource(file);
275                 }
276                 lex_unget(ch,   file);
277                 lex_addch('\0', file);
278                 
279                 /* look inside the table for a keyword .. */
280                 for (it = 0; it < sizeof(lex_keywords)/sizeof(*lex_keywords); it++)
281                         if (!strncmp(file->lastok, lex_keywords[it], sizeof(lex_keywords[it])))
282                                 return it;
283                                 
284                 /* try a type? */
285                 #define TEST_TYPE(X)                                 \
286                     do {                                             \
287                         if (!strncmp(X, "float",  sizeof("float")))  \
288                             return TOKEN_FLOAT;                      \
289                         if (!strncmp(X, "vector", sizeof("vector"))) \
290                             return TOKEN_VECTOR;                     \
291                         if (!strncmp(X, "string", sizeof("string"))) \
292                             return TOKEN_STRING;                     \
293                         if (!strncmp(X, "entity", sizeof("entity"))) \
294                             return TOKEN_ENTITY;                     \
295                         if (!strncmp(X, "void"  , sizeof("void")))   \
296                                 return TOKEN_VOID;                       \
297                     } while(0)
298                 
299                 TEST_TYPE(file->lastok);
300                 
301                 /* try the hashtable for typedefs? */
302                 if (typedef_find(file->lastok))
303                         TEST_TYPE(typedef_find(file->lastok)->name);
304                         
305                 return LEX_IDENT;
306         }
307         return ch;
308 }
309
310 void lex_reset(struct lex_file *file) {
311         file->current = 0;
312         file->last    = 0;
313         file->length  = file->size;
314         fseek(file->file, 0, SEEK_SET);
315         
316         memset(file->peek,   0, sizeof(file->peek  ));
317         memset(file->lastok, 0, sizeof(file->lastok));
318 }