lexer: Reimplement for better testability and internationalization.
[pspp-builds.git] / src / data / identifier2.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 /* This file implements parts of identifier.h that call the msg() function.
18    This allows test programs that do not use those functions to avoid linking
19    additional object files. */
20
21 #include <config.h>
22
23 #include "data/identifier.h"
24
25 #include <string.h>
26 #include <unistr.h>
27
28 #include "libpspp/cast.h"
29 #include "libpspp/i18n.h"
30 #include "libpspp/message.h"
31
32 #include "gl/c-ctype.h"
33
34 #include "gettext.h"
35 #define _(msgid) gettext (msgid)
36
37 /* Returns true if UTF-8 string ID is an acceptable identifier in encoding
38    DICT_ENCODING (UTF-8 if null), false otherwise.  If ISSUE_ERROR is true,
39    issues an explanatory error message on failure. */
40 bool
41 id_is_valid (const char *id, const char *dict_encoding, bool issue_error)
42 {
43   size_t dict_len;
44
45   if (!id_is_plausible (id, issue_error))
46     return false;
47
48   if (dict_encoding != NULL)
49     {
50       /* XXX need to reject recoded strings that contain the fallback
51          character. */
52       dict_len = recode_string_len (dict_encoding, "UTF-8", id, -1);
53     }
54   else
55     dict_len = strlen (id);
56
57   if (dict_len > ID_MAX_LEN)
58     {
59       if (issue_error)
60         msg (SE, _("Identifier `%s' exceeds %d-byte limit."),
61              id, ID_MAX_LEN);
62       return false;
63     }
64
65   return true;
66 }
67
68 /* Returns true if UTF-8 string ID is an plausible identifier, false
69    otherwise.  If ISSUE_ERROR is true, issues an explanatory error message on
70    failure.  */
71 bool
72 id_is_plausible (const char *id, bool issue_error)
73 {
74   const uint8_t *bad_unit;
75   const uint8_t *s;
76   char ucname[16];
77   int mblen;
78   ucs4_t uc;
79
80   /* ID cannot be the empty string. */
81   if (id[0] == '\0')
82     {
83       if (issue_error)
84         msg (SE, _("Identifier cannot be empty string."));
85       return false;
86     }
87
88   /* ID cannot be a reserved word. */
89   if (lex_id_to_token (ss_cstr (id)) != T_ID)
90     {
91       if (issue_error)
92         msg (SE, _("`%s' may not be used as an identifier because it "
93                    "is a reserved word."), id);
94       return false;
95     }
96
97   bad_unit = u8_check (CHAR_CAST (const uint8_t *, id), strlen (id));
98   if (bad_unit != NULL)
99     {
100       /* If this message ever appears, it probably indicates a PSPP bug since
101          it shouldn't be possible to get invalid UTF-8 this far. */
102       if (issue_error)
103         msg (SE, _("`%s' may not be used as an identifier because it "
104                    "contains ill-formed UTF-8 at byte offset %tu."),
105              id, CHAR_CAST (const char *, bad_unit) - id);
106       return false;
107     }
108
109   /* Check that it is a valid identifier. */
110   mblen = u8_strmbtouc (&uc, CHAR_CAST (uint8_t *, id));
111   if (!lex_uc_is_id1 (uc))
112     {
113       if (issue_error)
114         msg (SE, _("Character %s (in `%s') may not appear "
115                    "as the first character in a identifier."),
116              uc_name (uc, ucname), id);
117       return false;
118     }
119
120   for (s = CHAR_CAST (uint8_t *, id + mblen);
121        (mblen = u8_strmbtouc (&uc, s)) != 0;
122         s += mblen)
123     if (!lex_uc_is_idn (uc))
124       {
125         if (issue_error)
126           msg (SE, _("Character %s (in `%s') may not appear in an "
127                      "identifier."),
128                uc_name (uc, ucname), id);
129         return false;
130       }
131
132   return true;
133 }