lexer: Reimplement for better testability and internationalization.
[pspp-builds.git] / src / data / identifier.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2005, 2009, 2010, 2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 /*
18    This file is concerned with the definition of the PSPP syntax, NOT the
19    action of scanning/parsing code .
20 */
21
22 #include <config.h>
23
24 #include "data/identifier.h"
25
26 #include <string.h>
27 #include <unictype.h>
28
29 #include "libpspp/assertion.h"
30
31 #include "gl/c-ctype.h"
32
33 #include "gettext.h"
34 #define _(msgid) gettext (msgid)
35
36 /* Tokens. */
37
38 /* Returns TYPE as a string, e.g. "ID" for T_ID. */
39 const char *
40 token_type_to_name (enum token_type type)
41 {
42   switch (type)
43     {
44 #define TOKEN_TYPE(TYPE) case T_##TYPE: return #TYPE;
45       TOKEN_TYPES
46 #undef TOKEN_TYPE
47     case TOKEN_N_TYPES:
48     default:
49       return "unknown token type";
50     }
51 }
52
53 /* Returns an ASCII string that yields TOKEN if it appeared in a syntax file,
54    as a statically allocated constant string.  This function returns NULL for
55    tokens that don't have any fixed string representation, such as identifier
56    and number tokens. */
57 const char *
58 token_type_to_string (enum token_type token)
59 {
60   switch (token)
61     {
62     case T_ID:
63     case T_POS_NUM:
64     case T_NEG_NUM:
65     case T_STRING:
66     case T_STOP:
67       return NULL;
68
69     case T_ENDCMD:
70       return ".";
71
72     case T_PLUS:
73       return "+";
74
75     case T_DASH:
76       return "-";
77
78     case T_ASTERISK:
79       return "*";
80
81     case T_SLASH:
82       return "/";
83
84     case T_EQUALS:
85       return "=";
86
87     case T_LPAREN:
88       return "(";
89
90     case T_RPAREN:
91       return ")";
92
93     case T_LBRACK:
94       return "[";
95
96     case T_RBRACK:
97       return "]";
98
99     case T_COMMA:
100       return ",";
101
102     case T_AND:
103       return "AND";
104
105     case T_OR:
106       return "OR";
107
108     case T_NOT:
109       return "NOT";
110
111     case T_EQ:
112       return "EQ";
113
114     case T_GE:
115       return ">=";
116
117     case T_GT:
118       return ">";
119
120     case T_LE:
121       return "<=";
122
123     case T_LT:
124       return "<";
125
126     case T_NE:
127       return "~=";
128
129     case T_ALL:
130       return "ALL";
131
132     case T_BY:
133       return "BY";
134
135     case T_TO:
136       return "TO";
137
138     case T_WITH:
139       return "WITH";
140
141     case T_EXP:
142       return "**";
143
144     case TOKEN_N_TYPES:
145       NOT_REACHED ();
146     }
147
148   NOT_REACHED ();
149 }
150
151 /* Recognizing identifiers. */
152
153 static bool
154 is_ascii_id1 (unsigned char c)
155 {
156   return c_isalpha (c) || c == '@' || c == '#' || c == '$';
157 }
158
159 static bool
160 is_ascii_idn (unsigned char c)
161 {
162   return is_ascii_id1 (c) || isdigit (c) || c == '.' || c == '_';
163 }
164
165 /* Returns true if C may be the first byte in an identifier in the current
166    locale.
167
168    (PSPP is transitioning to using Unicode internally for syntax, so please
169    use lex_uc_is_id1() instead, if possible.) */
170 bool
171 lex_is_id1 (char c)
172 {
173   return is_ascii_id1 (c) || (unsigned char) c >= 128;
174 }
175
176 /* Returns true if C may be a byte in an identifier other than the first.
177
178    (PSPP is transitioning to using Unicode internally for syntax, so please
179    use lex_uc_is_idn() instead, if possible.) */
180 bool
181 lex_is_idn (char c)
182 {
183   return is_ascii_idn (c) || (unsigned char) c >= 128;
184 }
185
186 /* Returns true if Unicode code point UC may be the first character in an
187    identifier in the current locale. */
188 bool
189 lex_uc_is_id1 (ucs4_t uc)
190 {
191   return is_ascii_id1 (uc) || (uc >= 0x80 && uc_is_property_id_start (uc));
192 }
193
194 /* Returns true if Unicode code point UC may be a character in an identifier
195    other than the first. */
196 bool
197 lex_uc_is_idn (ucs4_t uc)
198 {
199   return (is_ascii_id1 (uc) || isdigit (uc) || uc == '.' || uc == '_'
200           || (uc >= 0x80 && uc_is_property_id_continue (uc)));
201 }
202
203 /* Returns true if Unicode code point UC is a space that separates tokens. */
204 bool
205 lex_uc_is_space (ucs4_t uc)
206 {
207   /* These are all of the Unicode characters in category Zs, Zl, or Zp.  */
208   return (uc == ' ' || (uc <= 0x000d && uc >= 0x0009)
209           || (uc >= 0x80
210               && (uc == 0xa0 || uc == 0x85 || uc == 0x1680 || uc == 0x180e
211                   || (uc >= 0x2000 && uc <= 0x200a)
212                   || uc == 0x2028 || uc == 0x2029 || uc == 0x202f
213                   || uc == 0x205f || uc == 0x3000)));
214 }
215
216
217 /* Returns the length of the longest prefix of STRING that forms
218    a valid identifier.  Returns zero if STRING does not begin
219    with a valid identifier.  */
220 size_t
221 lex_id_get_length (struct substring string)
222 {
223   size_t length = 0;
224   if (!ss_is_empty (string) && lex_is_id1 (ss_first (string)))
225     {
226       length = 1;
227       while (length < ss_length (string)
228              && lex_is_idn (ss_at (string, length)))
229         length++;
230     }
231   return length;
232 }
233 \f
234 /* Comparing identifiers. */
235
236 /* Returns true if TOKEN is a case-insensitive match for KEYWORD.
237
238    Keywords match if one of the following is true: KEYWORD and
239    TOKEN are identical, or TOKEN is at least 3 characters long
240    and those characters are identical to KEYWORD.  (Letters that
241    differ only in case are considered identical.)
242
243    KEYWORD must be ASCII, but TOKEN may be ASCII or UTF-8. */
244 bool
245 lex_id_match (struct substring keyword, struct substring token)
246 {
247   return lex_id_match_n (keyword, token, 3);
248 }
249
250 /* Returns true if TOKEN is a case-insensitive match for at least
251    the first N characters of KEYWORD.
252
253    KEYWORD must be ASCII, but TOKEN may be ASCII or UTF-8. */
254 bool
255 lex_id_match_n (struct substring keyword, struct substring token, size_t n)
256 {
257   size_t token_len = ss_length (token);
258   size_t keyword_len = ss_length (keyword);
259
260   if (token_len >= n && token_len < keyword_len)
261     return ss_equals_case (ss_head (keyword, token_len), token);
262   else
263     return ss_equals_case (keyword, token);
264 }
265 \f
266 /* Table of keywords. */
267 struct keyword
268   {
269     int token;
270     const struct substring identifier;
271   };
272
273 static const struct keyword keywords[] =
274   {
275     { T_AND,  SS_LITERAL_INITIALIZER ("AND") },
276     { T_OR,   SS_LITERAL_INITIALIZER ("OR") },
277     { T_NOT,  SS_LITERAL_INITIALIZER ("NOT") },
278     { T_EQ,   SS_LITERAL_INITIALIZER ("EQ") },
279     { T_GE,   SS_LITERAL_INITIALIZER ("GE") },
280     { T_GT,   SS_LITERAL_INITIALIZER ("GT") },
281     { T_LE,   SS_LITERAL_INITIALIZER ("LE") },
282     { T_LT,   SS_LITERAL_INITIALIZER ("LT") },
283     { T_NE,   SS_LITERAL_INITIALIZER ("NE") },
284     { T_ALL,  SS_LITERAL_INITIALIZER ("ALL") },
285     { T_BY,   SS_LITERAL_INITIALIZER ("BY") },
286     { T_TO,   SS_LITERAL_INITIALIZER ("TO") },
287     { T_WITH, SS_LITERAL_INITIALIZER ("WITH") },
288   };
289 static const size_t keyword_cnt = sizeof keywords / sizeof *keywords;
290
291 /* Returns true if TOKEN is representable as a keyword. */
292 bool
293 lex_is_keyword (enum token_type token)
294 {
295   const struct keyword *kw;
296   for (kw = keywords; kw < &keywords[keyword_cnt]; kw++)
297     if (kw->token == token)
298       return true;
299   return false;
300 }
301
302 /* Returns the proper token type, either T_ID or a reserved
303    keyword enum, for ID. */
304 int
305 lex_id_to_token (struct substring id)
306 {
307   if (ss_length (id) >= 2 && ss_length (id) <= 4)
308     {
309       const struct keyword *kw;
310       for (kw = keywords; kw < &keywords[keyword_cnt]; kw++)
311         if (ss_equals_case (kw->identifier, id))
312           return kw->token;
313     }
314
315   return T_ID;
316 }