07c7675bd5a3000cf3742c85505bf68b2174ebd3
[pspp] / src / data / identifier.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2005, 2009, 2010, 2011, 2012, 2013 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 <unistr.h>
28 #include <unictype.h>
29
30 #include "libpspp/assertion.h"
31 #include "libpspp/cast.h"
32
33 #include "gl/c-ctype.h"
34
35 #include "gettext.h"
36 #define _(msgid) gettext (msgid)
37
38 /* Tokens. */
39
40 /* Returns TYPE as a string, e.g. "ID" for T_ID. */
41 const char *
42 token_type_to_name (enum token_type type)
43 {
44   switch (type)
45     {
46 #define TOKEN_TYPE(TYPE) case T_##TYPE: return #TYPE;
47       TOKEN_TYPES
48 #undef TOKEN_TYPE
49     default:
50       return "unknown token type";
51     }
52 }
53
54 /* Returns an ASCII string that yields TOKEN if it appeared in a syntax file,
55    as a statically allocated constant string.  This function returns NULL for
56    tokens that don't have any fixed string representation, such as identifier
57    and number tokens. */
58 const char *
59 token_type_to_string (enum token_type token)
60 {
61   switch (token)
62     {
63     case T_ID:
64     case T_POS_NUM:
65     case T_NEG_NUM:
66     case T_STRING:
67     case T_MACRO_ID:
68     case T_MACRO_PUNCT:
69     case T_STOP:
70       return NULL;
71
72     case T_ENDCMD:
73       return ".";
74
75     case T_PLUS:
76       return "+";
77
78     case T_DASH:
79       return "-";
80
81     case T_ASTERISK:
82       return "*";
83
84     case T_SLASH:
85       return "/";
86
87     case T_EQUALS:
88       return "=";
89
90     case T_LPAREN:
91       return "(";
92
93     case T_RPAREN:
94       return ")";
95
96     case T_LBRACK:
97       return "[";
98
99     case T_RBRACK:
100       return "]";
101
102     case T_COMMA:
103       return ",";
104
105     case T_AND:
106       return "AND";
107
108     case T_OR:
109       return "OR";
110
111     case T_NOT:
112       return "NOT";
113
114     case T_EQ:
115       return "EQ";
116
117     case T_GE:
118       return ">=";
119
120     case T_GT:
121       return ">";
122
123     case T_LE:
124       return "<=";
125
126     case T_LT:
127       return "<";
128
129     case T_NE:
130       return "~=";
131
132     case T_ALL:
133       return "ALL";
134
135     case T_BY:
136       return "BY";
137
138     case T_TO:
139       return "TO";
140
141     case T_WITH:
142       return "WITH";
143
144     case T_EXP:
145       return "**";
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 (uc < 0x80
192           ? is_ascii_id1 (uc)
193           : (uc_is_general_category_withtable (uc,
194                                                UC_CATEGORY_MASK_L |
195                                                UC_CATEGORY_MASK_M |
196                                                UC_CATEGORY_MASK_S)
197              && uc != 0xfffc && uc != 0xfffd));
198 }
199
200 /* Returns true if Unicode code point UC may be a character in an identifier
201    other than the first. */
202 bool
203 lex_uc_is_idn (ucs4_t uc)
204 {
205   return (uc < 0x80
206           ? is_ascii_id1 (uc) || isdigit (uc) || uc == '.' || uc == '_'
207           : (uc_is_general_category_withtable (uc,
208                                                UC_CATEGORY_MASK_L |
209                                                UC_CATEGORY_MASK_M |
210                                                UC_CATEGORY_MASK_S |
211                                                UC_CATEGORY_MASK_N)
212              && uc != 0xfffc && uc != 0xfffd));
213 }
214
215 /* Returns true if Unicode code point UC is a space that separates tokens. */
216 bool
217 lex_uc_is_space (ucs4_t uc)
218 {
219   /* These are all of the Unicode characters in category Zs, Zl, or Zp.  */
220   return (uc == ' ' || (uc <= 0x000d && uc >= 0x0009)
221           || (uc >= 0x80
222               && (uc == 0xa0 || uc == 0x85 || uc == 0x1680 || uc == 0x180e
223                   || (uc >= 0x2000 && uc <= 0x200a)
224                   || uc == 0x2028 || uc == 0x2029 || uc == 0x202f
225                   || uc == 0x205f || uc == 0x3000)));
226 }
227
228
229 /* Returns the length of the longest prefix of STRING that forms
230    a valid identifier.  Returns zero if STRING does not begin
231    with a valid identifier.  */
232 size_t
233 lex_id_get_length (struct substring string)
234 {
235   const uint8_t *s = CHAR_CAST (const uint8_t *, string.string);
236   size_t len = string.length;
237   size_t ofs;
238   int mblen;
239
240   for (ofs = 0; ofs < string.length; ofs += mblen)
241     {
242       ucs4_t uc;
243
244       mblen = u8_mbtouc (&uc, s + ofs, len - ofs);
245       if (!(ofs == 0 ? lex_uc_is_id1 (uc) : lex_uc_is_idn (uc)))
246         break;
247     }
248
249   return ofs;
250 }
251 \f
252 /* Comparing identifiers. */
253
254 /* Returns true if TOKEN is a case-insensitive match for KEYWORD.
255
256    Keywords match if one of the following is true: KEYWORD and
257    TOKEN are identical, or TOKEN is at least 3 characters long
258    and those characters are identical to KEYWORD.  (Letters that
259    differ only in case are considered identical.)
260
261    KEYWORD must be ASCII, but TOKEN may be ASCII or UTF-8. */
262 bool
263 lex_id_match (struct substring keyword, struct substring token)
264 {
265   return lex_id_match_n (keyword, token, 3);
266 }
267
268 /* Returns true if TOKEN is a case-insensitive match for at least
269    the first N characters of KEYWORD.
270
271    KEYWORD must be ASCII, but TOKEN may be ASCII or UTF-8. */
272 bool
273 lex_id_match_n (struct substring keyword, struct substring token, size_t n)
274 {
275   size_t token_len = ss_length (token);
276   size_t keyword_len = ss_length (keyword);
277
278   if (token_len >= n && token_len < keyword_len)
279     return ss_equals_case (ss_head (keyword, token_len), token);
280   else
281     return ss_equals_case (keyword, token);
282 }
283 \f
284 /* Table of keywords. */
285 struct keyword
286   {
287     int token;
288     const struct substring identifier;
289   };
290
291 static const struct keyword keywords[] =
292   {
293     { T_AND,  SS_LITERAL_INITIALIZER ("AND") },
294     { T_OR,   SS_LITERAL_INITIALIZER ("OR") },
295     { T_NOT,  SS_LITERAL_INITIALIZER ("NOT") },
296     { T_EQ,   SS_LITERAL_INITIALIZER ("EQ") },
297     { T_GE,   SS_LITERAL_INITIALIZER ("GE") },
298     { T_GT,   SS_LITERAL_INITIALIZER ("GT") },
299     { T_LE,   SS_LITERAL_INITIALIZER ("LE") },
300     { T_LT,   SS_LITERAL_INITIALIZER ("LT") },
301     { T_NE,   SS_LITERAL_INITIALIZER ("NE") },
302     { T_ALL,  SS_LITERAL_INITIALIZER ("ALL") },
303     { T_BY,   SS_LITERAL_INITIALIZER ("BY") },
304     { T_TO,   SS_LITERAL_INITIALIZER ("TO") },
305     { T_WITH, SS_LITERAL_INITIALIZER ("WITH") },
306   };
307 static const size_t keyword_cnt = sizeof keywords / sizeof *keywords;
308
309 /* Returns true if TOKEN is representable as a keyword. */
310 bool
311 lex_is_keyword (enum token_type token)
312 {
313   const struct keyword *kw;
314   for (kw = keywords; kw < &keywords[keyword_cnt]; kw++)
315     if (kw->token == token)
316       return true;
317   return false;
318 }
319
320 /* Returns the proper token type, either T_ID or a reserved
321    keyword enum, for ID. */
322 int
323 lex_id_to_token (struct substring id)
324 {
325   if (ss_length (id) >= 2 && ss_length (id) <= 4)
326     {
327       const struct keyword *kw;
328       for (kw = keywords; kw < &keywords[keyword_cnt]; kw++)
329         if (ss_equals_case (kw->identifier, id))
330           return kw->token;
331     }
332
333   return T_ID;
334 }