f33e885925a807b67071d1832b2de8442f3c7845
[pspp] / src / language / control / define.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2021 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 #include <config.h>
18
19 #include <limits.h>
20
21 #include "language/command.h"
22 #include "language/lexer/lexer.h"
23 #include "language/lexer/macro.h"
24 #include "language/lexer/scan.h"
25 #include "language/lexer/token.h"
26 #include "libpspp/intern.h"
27 #include "libpspp/message.h"
28
29 #include "gl/xalloc.h"
30
31 #include "gettext.h"
32 #define _(msgid) gettext (msgid)
33
34 static bool
35 match_macro_id (struct lexer *lexer, const char *keyword)
36 {
37   if (keyword[0] != '!')
38     return lex_match_id (lexer, keyword);
39   else if (lex_token (lexer) == T_MACRO_ID
40            && lex_id_match_n (ss_cstr (keyword), lex_tokss (lexer), 4))
41     {
42       lex_get (lexer);
43       return true;
44     }
45   else
46     return false;
47 }
48
49 /* Obtains a quoted string from LEXER and then tokenizes the quoted string's
50    content to obtain a single TOKEN.  Returns true if successful, false
51    otherwise.  The caller takes ownership of TOKEN on success, otherwise TOKEN
52    is indeterminate. */
53 static bool
54 parse_quoted_token (struct lexer *lexer, struct token *token)
55 {
56   if (!lex_force_string (lexer))
57     return false;
58
59   struct substring s = lex_tokss (lexer);
60   struct string_lexer slex;
61   string_lexer_init (&slex, s.string, s.length, SEG_MODE_INTERACTIVE, true);
62   struct token another_token = { .type = T_STOP };
63   if (string_lexer_next (&slex, token) != SLR_TOKEN
64       || string_lexer_next (&slex, &another_token) != SLR_END)
65     {
66       token_uninit (token);
67       token_uninit (&another_token);
68       lex_error (lexer, _("String must contain exactly one token."));
69       return false;
70     }
71   lex_get (lexer);
72   return true;
73 }
74
75 static bool
76 dup_arg_type (struct lexer *lexer, bool *saw_arg_type)
77 {
78   if (*saw_arg_type)
79     {
80       lex_error (lexer, _("Only one of !TOKENS, !CHAREND, !ENCLOSE, or "
81                           "!CMDEND is allowed."));
82       return false;
83     }
84   else
85     {
86       *saw_arg_type = true;
87       return true;
88     }
89 }
90
91 int
92 cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
93 {
94   /* Parse macro name.
95
96      The macro name is a T_STRING token, even though it's an identifier,
97      because that's the way that the segmenter prevents it from getting
98      macro-expanded. */
99   if (lex_token (lexer) != T_STRING)
100     {
101       lex_error (lexer, _("expecting identifier"));
102       return CMD_FAILURE;
103     }
104   const char *name = lex_tokcstr (lexer);
105   if (!id_is_plausible (name + (name[0] == '!'), false))
106     {
107       lex_error (lexer, _("expecting identifier"));
108       return CMD_FAILURE;
109     }
110
111   struct macro *m = xmalloc (sizeof *m);
112   *m = (struct macro) {
113     .name = xstrdup (name),
114     .location = xmalloc (sizeof *m->location),
115   };
116   *m->location = (struct msg_location) {
117     .file_name = intern_new_if_nonnull (lex_get_file_name (lexer)),
118     .first_line = lex_get_first_line_number (lexer, 0),
119   };
120   lex_get (lexer);
121
122   if (!lex_force_match (lexer, T_LPAREN))
123     goto error;
124
125   size_t allocated_params = 0;
126   while (!lex_match (lexer, T_RPAREN))
127     {
128       if (m->n_params >= allocated_params)
129         m->params = x2nrealloc (m->params, &allocated_params,
130                                 sizeof *m->params);
131
132       size_t param_index = m->n_params++;
133       struct macro_param *p = &m->params[param_index];
134       *p = (struct macro_param) { .expand_arg = true };
135
136       /* Parse parameter name. */
137       if (match_macro_id (lexer, "!POSITIONAL"))
138         {
139           if (param_index > 0 && !m->params[param_index - 1].positional)
140             {
141               lex_error (lexer, _("Positional parameters must precede "
142                                   "keyword parameters."));
143               goto error;
144             }
145
146           p->positional = true;
147           p->name = xasprintf ("!%zu", param_index + 1);
148         }
149       else
150         {
151           if (lex_token (lexer) == T_MACRO_ID)
152             {
153               lex_error (lexer, _("Keyword macro parameter must be named in "
154                                   "definition without \"!\" prefix."));
155               goto error;
156             }
157           if (!lex_force_id (lexer))
158             goto error;
159
160           if (is_macro_keyword (lex_tokss (lexer)))
161             {
162               lex_error (lexer, _("Cannot use macro keyword \"%s\" "
163                                   "as an argument name."),
164                          lex_tokcstr (lexer));
165               goto error;
166             }
167
168           p->positional = false;
169           p->name = xasprintf ("!%s", lex_tokcstr (lexer));
170           lex_get (lexer);
171         }
172       lex_match (lexer, T_EQUALS);
173
174       bool saw_default = false;
175       bool saw_arg_type = false;
176       for (;;)
177         {
178           if (match_macro_id (lexer, "!DEFAULT"))
179             {
180               if (saw_default)
181                 {
182                   lex_error (lexer,
183                              _("!DEFAULT is allowed only once per argument."));
184                   goto error;
185                 }
186               saw_default = true;
187
188               if (!lex_force_match (lexer, T_LPAREN))
189                 goto error;
190
191               /* XXX Should this handle balanced inner parentheses? */
192               while (!lex_match (lexer, T_RPAREN))
193                 {
194                   if (lex_token (lexer) == T_ENDCMD)
195                     {
196                       lex_error_expecting (lexer, ")");
197                       goto error;
198                     }
199                   char *syntax = lex_next_representation (lexer, 0, 0);
200                   const struct macro_token mt = {
201                     .token = *lex_next (lexer, 0),
202                     .syntax = ss_cstr (syntax),
203                   };
204                   macro_tokens_add (&p->def, &mt);
205                   free (syntax);
206
207                   lex_get (lexer);
208                 }
209             }
210           else if (match_macro_id (lexer, "!NOEXPAND"))
211             p->expand_arg = false;
212           else if (match_macro_id (lexer, "!TOKENS"))
213             {
214               if (!dup_arg_type (lexer, &saw_arg_type)
215                   || !lex_force_match (lexer, T_LPAREN)
216                   || !lex_force_int_range (lexer, "!TOKENS", 1, INT_MAX))
217                 goto error;
218               p->arg_type = ARG_N_TOKENS;
219               p->n_tokens = lex_integer (lexer);
220               lex_get (lexer);
221               if (!lex_force_match (lexer, T_RPAREN))
222                 goto error;
223             }
224           else if (match_macro_id (lexer, "!CHAREND"))
225             {
226               if (!dup_arg_type (lexer, &saw_arg_type))
227                 goto error;
228
229               p->arg_type = ARG_CHAREND;
230
231               if (!lex_force_match (lexer, T_LPAREN)
232                   || !parse_quoted_token (lexer, &p->end)
233                   || !lex_force_match (lexer, T_RPAREN))
234                 goto error;
235             }
236           else if (match_macro_id (lexer, "!ENCLOSE"))
237             {
238               if (!dup_arg_type (lexer, &saw_arg_type))
239                 goto error;
240
241               p->arg_type = ARG_ENCLOSE;
242
243               if (!lex_force_match (lexer, T_LPAREN)
244                   || !parse_quoted_token (lexer, &p->start)
245                   || !lex_force_match (lexer, T_COMMA)
246                   || !parse_quoted_token (lexer, &p->end)
247                   || !lex_force_match (lexer, T_RPAREN))
248                 goto error;
249             }
250           else if (match_macro_id (lexer, "!CMDEND"))
251             {
252               if (!dup_arg_type (lexer, &saw_arg_type))
253                 goto error;
254
255               p->arg_type = ARG_CMDEND;
256             }
257           else
258             break;
259         }
260       if (!saw_arg_type)
261         {
262           lex_error_expecting (lexer, "!TOKENS", "!CHAREND", "!ENCLOSE",
263                                "!CMDEND");
264           goto error;
265         }
266
267       if (lex_token (lexer) != T_RPAREN && !lex_force_match (lexer, T_SLASH))
268         goto error;
269     }
270
271   struct string body = DS_EMPTY_INITIALIZER;
272   while (!match_macro_id (lexer, "!ENDDEFINE"))
273     {
274       if (lex_token (lexer) != T_STRING)
275         {
276           lex_error (lexer, _("Expecting macro body or !ENDDEFINE"));
277           ds_destroy (&body);
278           goto error;
279         }
280
281       ds_put_substring (&body, lex_tokss (lexer));
282       ds_put_byte (&body, '\n');
283       lex_get (lexer);
284     }
285   m->location->last_line = lex_get_last_line_number (lexer, 0);
286
287   macro_tokens_from_string (&m->body, body.ss, lex_get_syntax_mode (lexer));
288   ds_destroy (&body);
289
290   lex_define_macro (lexer, m);
291
292   return CMD_SUCCESS;
293
294 error:
295   macro_destroy (m);
296   return CMD_FAILURE;
297 }
298
299 int
300 cmd_debug_expand (struct lexer *lexer, struct dataset *ds UNUSED)
301 {
302   settings_set_mprint (true);
303
304   while (lex_token (lexer) != T_STOP)
305     {
306       if (!lex_next_is_from_macro (lexer, 0) && lex_token (lexer) != T_ENDCMD)
307         {
308           char *rep = lex_next_representation (lexer, 0, 0);
309           msg (MN, "unexpanded token \"%s\"", rep);
310           free (rep);
311         }
312       lex_get (lexer);
313     }
314   return CMD_SUCCESS;
315 }