Macro arguments and the !length function work.
[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
27 #include "gl/xalloc.h"
28
29 #include "gettext.h"
30 #define _(msgid) gettext (msgid)
31
32 static bool
33 force_macro_id (struct lexer *lexer)
34 {
35   return lex_token (lexer) == T_MACRO_ID || lex_force_id (lexer);
36 }
37
38 static bool
39 match_macro_id (struct lexer *lexer, const char *id)
40 {
41   if (id[0] != '!')
42     return lex_match_id (lexer, id);
43   else if (lex_token (lexer) == T_MACRO_ID
44            && ss_equals_case (lex_tokss (lexer), ss_cstr (id)))
45     {
46       lex_get (lexer);
47       return true;
48     }
49   else
50     return false;
51 }
52
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);
62   struct token another_token;
63   if (!string_lexer_next (&slex, token)
64       || string_lexer_next (&slex, &another_token))
65     {
66       token_destroy (token);
67       token_destroy (&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 void
76 macro_tokenize (struct macro *m, struct lexer *lexer)
77 {
78   struct state
79     {
80       struct segmenter segmenter;
81       struct substring body;
82     };
83
84   struct state state = {
85     .segmenter = SEGMENTER_INIT (lex_get_syntax_mode (lexer)),
86     .body = m->body,
87   };
88   struct state saved = state;
89
90   struct token token = { .type = T_STOP };
91
92   while (state.body.length > 0)
93     {
94       struct scanner scanner;
95       scanner_init (&scanner, &token);
96
97       for (;;)
98         {
99           enum segment_type type;
100           int seg_len = segmenter_push (&state.segmenter, state.body.string,
101                                         state.body.length, true, &type);
102           assert (seg_len >= 0);
103
104           struct substring segment = ss_head (state.body, seg_len);
105           ss_advance (&state.body, seg_len);
106
107           enum scan_result result = scanner_push (&scanner, type, segment, &token);
108           if (result == SCAN_SAVE)
109             saved = state;
110           else if (result == SCAN_BACK)
111             {
112               state = saved;
113               break;
114             }
115           else if (result == SCAN_DONE)
116             break;
117         }
118
119       /* We have a token in 'token'. */
120       if (is_scan_type (token.type))
121         {
122           if (token.type != SCAN_SKIP)
123             {
124               /* XXX report error */
125             }
126         }
127       else
128         tokens_add (&m->body_tokens, &token);
129       token_destroy (&token);
130     }
131 }
132
133 int
134 cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
135 {
136   if (!force_macro_id (lexer))
137     return CMD_FAILURE;
138
139   /* Parse macro name. */
140   struct macro *m = xmalloc (sizeof *m);
141   *m = (struct macro) { .name = ss_xstrdup (lex_tokss (lexer)) };
142   lex_get (lexer);
143
144   if (!lex_force_match (lexer, T_LPAREN))
145     goto error;
146
147   size_t allocated_params = 0;
148   while (!lex_match (lexer, T_RPAREN))
149     {
150       if (m->n_params >= allocated_params)
151         m->params = x2nrealloc (m->params, &allocated_params,
152                                 sizeof *m->params);
153
154       size_t param_index = m->n_params++;
155       struct macro_param *p = &m->params[param_index];
156       *p = (struct macro_param) { .expand_arg = true };
157
158       /* Parse parameter name. */
159       if (match_macro_id (lexer, "!POSITIONAL"))
160         {
161           if (param_index > 0 && !m->params[param_index - 1].positional)
162             {
163               lex_error (lexer, _("Positional parameters must precede "
164                                   "keyword parameters."));
165               goto error;
166             }
167
168           p->positional = true;
169           p->name = xasprintf ("!%zu", param_index + 1);
170         }
171       else
172         {
173           if (!lex_force_id (lexer))
174             goto error;
175
176           p->positional = false;
177           p->name = xasprintf ("!%s", lex_tokcstr (lexer));
178           lex_get (lexer);
179
180           if (!lex_force_match (lexer, T_EQUALS))
181             goto error;
182         }
183
184       /* Parse default value. */
185       if (match_macro_id (lexer, "!DEFAULT"))
186         {
187           if (!lex_force_match (lexer, T_LPAREN))
188             goto error;
189
190           /* XXX Should this handle balanced inner parentheses? */
191           while (!lex_match (lexer, T_RPAREN))
192             {
193               if (lex_token (lexer) == T_ENDCMD)
194                 {
195                   lex_error_expecting (lexer, ")");
196                   goto error;
197                 }
198               tokens_add (&p->def, lex_next (lexer, 0));
199               lex_get (lexer);
200             }
201         }
202
203       if (match_macro_id (lexer, "!NOEXPAND"))
204         p->expand_arg = false;
205
206       if (match_macro_id (lexer, "!TOKENS"))
207         {
208           if (!lex_force_match (lexer, T_LPAREN)
209               || !lex_force_int_range (lexer, "!TOKENS", 1, INT_MAX))
210             goto error;
211           p->arg_type = ARG_N_TOKENS;
212           p->n_tokens = lex_integer (lexer);
213           lex_get (lexer);
214           if (!lex_force_match (lexer, T_RPAREN))
215             goto error;
216         }
217       else if (match_macro_id (lexer, "!CHAREND"))
218         {
219           p->arg_type = ARG_CHAREND;
220           p->charend = (struct token) { .type = T_STOP };
221
222           if (!lex_force_match (lexer, T_LPAREN)
223               || !parse_quoted_token (lexer, &p->charend)
224               || !lex_force_match (lexer, T_RPAREN))
225             goto error;
226         }
227       else if (match_macro_id (lexer, "!ENCLOSE"))
228         {
229           p->arg_type = ARG_ENCLOSE;
230           p->enclose[0] = p->enclose[1] = (struct token) { .type = T_STOP };
231
232           if (!lex_force_match (lexer, T_LPAREN)
233               || !parse_quoted_token (lexer, &p->enclose[0])
234               || !lex_force_match (lexer, T_COMMA)
235               || !parse_quoted_token (lexer, &p->enclose[1])
236               || !lex_force_match (lexer, T_RPAREN))
237             goto error;
238         }
239       else if (match_macro_id (lexer, "!CMDEND"))
240         p->arg_type = ARG_CMDEND;
241       else
242         {
243           lex_error_expecting (lexer, "!TOKENS", "!CHAREND",
244                                "!ENCLOSE", "!CMDEND");
245           goto error;
246         }
247
248       if (lex_token (lexer) != T_RPAREN && !lex_force_match (lexer, T_SLASH))
249         goto error;
250     }
251
252   struct string body = DS_EMPTY_INITIALIZER;
253   while (!match_macro_id (lexer, "!ENDDEFINE"))
254     {
255       if (lex_token (lexer) != T_STRING)
256         {
257           lex_error (lexer, _("Expecting macro body or !ENDDEFINE"));
258           ds_destroy (&body);
259           goto error;
260         }
261
262       ds_put_substring (&body, lex_tokss (lexer));
263       ds_put_byte (&body, '\n');
264       lex_get (lexer);
265     }
266   m->body = ds_ss (&body);
267
268   macro_tokenize (m, lexer);
269
270   lex_define_macro (lexer, m);
271
272   return CMD_SUCCESS;
273
274 error:
275   macro_destroy (m);
276   return CMD_FAILURE;
277 }