]> git.draconx.ca Git - cdecl99.git/blob - src/scan.l
bd1c5c5b267189f04dd13df3af3366391be5683a
[cdecl99.git] / src / scan.l
1 %top{
2 /*
3  *  Scanner for C declarations.
4  *  Copyright © 2011, 2021, 2023-2024 Nick Bowler
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include "parse.h"
22
23 /* Disable various generated code we don't use */
24 #define YY_INPUT(a, b, c) do {} while (0)
25 #define YY_NO_INPUT 1
26 #define YY_NO_UNPUT 1
27 }
28
29 %option nodefault noyywrap bison-locations reentrant never-interactive
30 %option extra-type="int"
31 %option prefix="cdecl__yy"
32
33 %{
34 #include <ctype.h>
35 #include "cdecl-internal.h"
36 #include "cdecl.h"
37 #include "errmsg.h"
38 #include "intconv.h"
39
40 static char *to_octal(char *dst, unsigned val)
41 {
42         unsigned i;
43
44         for (i = 0; i < 3; i++) {
45                 *dst++ = '0' + ((val >> 6) & 7u);
46                 val <<= 3;
47         }
48
49         return dst;
50 }
51
52 /*
53  * Convert a single character to a C-style character constant, including quote
54  * characters.  At most 7 bytes are written to the buffer for the longest
55  * octal encoding, e.g., '\177'
56  */
57 static void to_readable_ch(char *dst, char c)
58 {
59         unsigned char uc = c;
60         unsigned i;
61         char esc;
62
63         /*
64          * The 7 standard C control characters are contiguous in ASCII,
65          * permitting a simple and compact lookup table; separating their
66          * handling from backslash and quote characters hopefully allows
67          * the compiler to recognize that.
68          */
69         switch (c) {
70         case '\a': i = 0; break;
71         case '\b': i = 1; break;
72         case '\t': i = 2; break;
73         case '\n': i = 3; break;
74         case '\v': i = 4; break;
75         case '\f': i = 5; break;
76         case '\r': i = 6; break;
77         default:   i = 7; break;
78         }
79         esc = "abtnvfr"[i];
80
81         /* Otherwise printable characters that should still be escaped. */
82         switch (c) {
83         case '\\': case '\'': esc = c; break;
84         }
85
86         *dst++ = '\'';
87         if (esc) {
88                 *dst++ = '\\';
89                 *dst++ = esc;
90         } else if (isprint(uc)) {
91                 *dst++ = c;
92         } else {
93                 *dst++ = '\\';
94                 dst = to_octal(dst, uc);
95         }
96         *dst++ = '\'';
97         *dst++ = 0;
98 }
99
100 %}
101
102 IDENT [_[:alpha:]][-_[:alnum:]]*
103
104 %%
105
106 %{
107         int intconv_base;
108         char *c;
109 %}
110
111 "..."|[][;*(),] {
112         unsigned char *match;
113         static const unsigned char tab[2][8] = {
114                 "*[](),.;",
115                 {
116                         PACK_TOKEN(T_ASTERISK),
117                         PACK_TOKEN(T_LBRACKET),
118                         PACK_TOKEN(T_RBRACKET),
119                         PACK_TOKEN(T_LPAREN),
120                         PACK_TOKEN(T_RPAREN),
121                         PACK_TOKEN(T_COMMA),
122                         PACK_TOKEN(T_ELLIPSIS),
123                         PACK_TOKEN(T_SEMICOLON)
124                 }
125         };
126
127         match = memchr(&tab, yytext[0], sizeof tab[0]);
128         return UNPACK_TOKEN(match[sizeof tab[0]]);
129 }
130
131 0[0-7]* { intconv_base = INTCONV_OCTAL; goto int_parse; }
132 [1-9][0-9]* { intconv_base = INTCONV_DECIMAL; goto int_parse; }
133 0[Xx][[:xdigit:]]+ {
134         unsigned char d;
135         uintmax_t v;
136
137         yytext += 2;
138         intconv_base = INTCONV_HEXADECIMAL;
139 int_parse:
140         for (v = 0; (d = *yytext++);) {
141                 if (!intconv_shift(&v, intconv_base, intconv_digit(d))) {
142                         cdecl__errmsg(CDECL__ERANGE);
143                         return T_LEX_ERROR;
144                 }
145         }
146
147         yylval->uintval = v;
148         return T_UINT;
149 }
150 0[Xx]|[0-9]+ {
151         cdecl__errmsg(CDECL__EBADINT);
152         return T_LEX_ERROR;
153 }
154
155 {IDENT} {
156         int len = yyleng, tok;
157         unsigned x;
158
159         x = cdecl__to_keyword(yytext, len, yyextra);
160         yylval->spectype = UNPACK_SPEC(x & 0xff);
161         if ((tok = (x >> 8)) == PACK_TOKEN(T_IDENT)) {
162                 /*
163                  * Our IDENT pattern includes hyphens so we can match
164                  * "variable-length" as a keyword.  In all other cases a
165                  * hyphen is an error.
166                  *
167                  * We could use yyless to re-scan the hyphen and hit the
168                  * error catch-all, but jumping straight to the error code
169                  * seems to produce better results with gcc with no obvious
170                  * downsides.
171                  */
172 #if 1
173                 if ((c = memchr(yytext, '-', len)))
174                         goto invalid_char;
175 #else
176                 yyless(strcspn(yytext, "-"));
177 #endif
178                 if (!(yylval->item = cdecl__alloc_item(len+1)))
179                         return T_LEX_ERROR;
180                 memcpy(yylval->item->s, yytext, len+1);
181         }
182         return UNPACK_TOKEN(tok);
183 }
184
185 [[:space:]]+
186 . {
187         char buf[8];
188
189         c = yytext;
190 invalid_char:
191         to_readable_ch(buf, *c);
192         cdecl__err(CDECL_ENOPARSE, _("syntax error, unexpected %s"), buf);
193         return T_LEX_ERROR;
194 }