Remove dependency to CPP: added pre-defined macros
[wmaker-crm.git] / WINGs / menuparser_macros.c
blob12e75480c61e6781e017ef683923db03acca1b0c
1 /*
2 * Window Maker window manager
4 * Copyright (c) 2012 Christophe Curis
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "wconfig.h"
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include <unistd.h>
27 #include <pwd.h>
29 #include <WINGs/WUtil.h>
31 #include "menuparser.h"
34 This file contains the functions related to macros:
35 - parse a macro being defined
36 - handle single macro expansion
37 - pre-defined parser's macros
39 Some design notes for macro internal storage:
41 arg_count is -1 when the macro does not take arguments
42 but 0 when no args but still using parenthesis. The
43 difference is explained in GNU cpp's documentation:
44 http://gcc.gnu.org/onlinedocs/cpp/Function_002dlike-Macros.html
46 the value is stored for fast expansion; here is an
47 example of the storage format used:
48 #define EXAMPLE(a, b) "text:" a and b!
49 will be stored in macro->value[] as:
50 0x0000: 0x00 0x07 (strlen(part 1))
51 0x0002: '"', 't', 'e', 'x', 't', ':', '"' (first part)
52 0x0009: 0x00 (part 2, id=0 for replacement by 1st parameter 'a')
53 0x000A: 0x00 0x03 (strlen(part 3))
54 0x000C: 'a', 'n', 'd' (part 3)
55 0x000F: 0x01 (part 4, id=1 for replacement by 2nd parameter 'b')
56 0x0010: 0x00 0x01 (strlen(part 5))
57 0x0012: '!' (part 5)
58 0x0013: 0xFF (end of macro)
59 This structure allows to store any number and combination
60 of text/parameter and still provide very fast generation
61 at macro replacement time.
63 Predefined macros are using a call-back function mechanism
64 to generate the value on-demand. This value is generated
65 in the 'value' buffer of the structure.
66 Most of these call-backs will actually cache the value:
67 they generate it on the first use (inside a parser,
68 not globally) and reuse that value on next call(s).
70 Because none of these macros take parameters, the call-back
71 mechanism does not include passing of user arguments; the
72 complex storage mechanism for argument replacement being
73 not necessary the macro->value parameter is used as a
74 plain C string to be copied, this fact being recognised
75 by macro->function being non-null. It was chosen that the
76 call-back function would not have the possibility to fail.
79 static Bool menu_parser_read_macro_def(WMenuParser parser, WParserMacro *macro, char **argname);
81 static Bool menu_parser_read_macro_args(WMenuParser parser, WParserMacro *macro,
82 char *array[], char *buffer, ssize_t buffer_size);
84 /* Free all used memory associated with parser's macros */
85 void menu_parser_free_macros(WMenuParser parser)
87 WParserMacro *macro, *mnext;
88 #ifdef DEBUG
89 unsigned char *rd;
90 unsigned int size;
91 unsigned int count;
93 /* if we were compiled with debugging, we take the opportunity that we
94 parse the list of macros, for memory release, to print all the
95 definitions */
96 printf(__FILE__ ": Macros defined while parsing \"%s\"\n", parser->file_name);
97 count = 0;
98 #endif
99 for (macro = parser->macros; macro != NULL; macro = mnext) {
100 #ifdef DEBUG
101 printf(" %s", macro->name);
102 if (macro->arg_count >= 0)
103 printf("(args=%d)", macro->arg_count);
104 printf(" = ");
106 if (macro->function != NULL) {
107 macro->function(macro, parser);
108 printf("function:\"%s\"", macro->value);
109 } else {
110 rd = macro->value;
111 for (;;) {
112 putchar('"');
113 size = (*rd++) << 8;
114 size |= *rd++;
115 while (size-- > 0) putchar(*rd++);
116 putchar('"');
117 if (*rd == 0xFF) break;
118 printf(" #%d ", (*rd++) + 1);
121 printf(", used %d times\n", macro->usage_count);
122 count++;
123 #endif
124 mnext = macro->next;
125 wfree(macro);
127 #ifdef DEBUG
128 printf(__FILE__ ": %d macros\n", count);
129 #endif
130 parser->macros = NULL; // Security
133 /* Check wether the specified character is valid for a name (macro, parameter) or not */
134 int isnamechr(char ch)
136 static const int table[256] = {
137 [0] = 0, // In case we'd fall on buggy compiler, to avoid crash
138 // C99: 6.7.8.21 -> non specified values are initialised to 0
139 ['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1, ['4'] = 1,
140 ['5'] = 1, ['6'] = 1, ['7'] = 1, ['8'] = 1, ['9'] = 1,
141 ['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1, ['F'] = 1,
142 ['G'] = 1, ['H'] = 1, ['I'] = 1, ['J'] = 1, ['K'] = 1, ['L'] = 1,
143 ['M'] = 1, ['N'] = 1, ['O'] = 1, ['P'] = 1, ['Q'] = 1, ['R'] = 1,
144 ['S'] = 1, ['T'] = 1, ['U'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1,
145 ['Y'] = 1, ['Z'] = 1,
146 ['a'] = 1, ['b'] = 1, ['c'] = 1, ['d'] = 1, ['e'] = 1, ['f'] = 1,
147 ['g'] = 1, ['h'] = 1, ['i'] = 1, ['j'] = 1, ['k'] = 1, ['l'] = 1,
148 ['m'] = 1, ['n'] = 1, ['o'] = 1, ['p'] = 1, ['q'] = 1, ['r'] = 1,
149 ['s'] = 1, ['t'] = 1, ['u'] = 1, ['v'] = 1, ['w'] = 1, ['x'] = 1,
150 ['y'] = 1, ['z'] = 1,
151 ['_'] = 1
152 // We refuse any UTF-8 coded character, or accents in ISO-xxx codepages
154 return table[0x00FF & (unsigned)ch ];
157 /* Parse the definition of the macro and add it to the top-most parser's list */
158 void menu_parser_define_macro(WMenuParser parser)
160 WParserMacro *macro;
161 int idx;
162 char arg_names_buf[MAXLINE];
163 char *arg_name[MAX_MACRO_ARG_COUNT];
165 if (!menu_parser_skip_spaces_and_comments(parser)) {
166 WMenuParserError(parser, _("no macro name found for #define") );
167 return;
169 macro = wmalloc(sizeof(*macro));
171 /* Isolate name of macro */
172 idx = 0;
173 while (isnamechr(*parser->rd)) {
174 if (idx < sizeof(macro->name) - 1)
175 macro->name[idx++] = *parser->rd;
176 parser->rd++;
178 // macro->name[idx] = '\0'; -> Already present because wmalloc filled struct with 0s
180 /* Build list of expected arguments */
181 if (*parser->rd == '(') {
182 parser->rd++;
183 idx = 0;
184 for (;;) {
185 if (!menu_parser_skip_spaces_and_comments(parser)) {
186 arglist_error_premature_eol:
187 WMenuParserError(parser, _("premature end of file while reading arg-list for macro \"%s\""), macro->name);
188 wfree(macro);
189 return;
191 if (*parser->rd == ')') break;
193 if (macro->arg_count >= sizeof(arg_name) / sizeof(arg_name[0])) {
194 WMenuParserError(parser, _("too many parameters for macro \"%s\" definition"), macro->name);
195 wfree(macro);
196 *parser->rd = '\0'; // fake end-of-line to avoid warnings from remaining line content
197 return;
199 if (isnamechr(*parser->rd)) {
200 arg_name[macro->arg_count++] = arg_names_buf + idx;
201 do {
202 if (idx < sizeof(arg_names_buf) - 1)
203 arg_names_buf[idx++] = *parser->rd;
204 parser->rd++;
205 } while (isnamechr(*parser->rd));
206 arg_names_buf[idx] = '\0';
207 if (idx < sizeof(arg_names_buf) - 1) idx++;
208 } else {
209 WMenuParserError(parser, _("invalid characted '%c' in arg-list for macro \"%s\" while expecting parameter name"),
210 *parser->rd, macro->name);
211 wfree(macro);
212 *parser->rd = '\0'; // fake end-of-line to avoid warnings from remaining line content
213 return;
215 if (!menu_parser_skip_spaces_and_comments(parser))
216 goto arglist_error_premature_eol;
217 if (*parser->rd == ')') break;
219 if (*parser->rd != ',') {
220 WMenuParserError(parser, _("invalid characted '%c' in arg-list for macro \"%s\" while expecting ',' or ')'"),
221 *parser->rd, macro->name);
222 wfree(macro);
223 *parser->rd = '\0'; // fake end-of-line to avoid warnings from remaining line content
224 return;
226 parser->rd++;
228 parser->rd++; // skip the closing ')'
229 } else
230 macro->arg_count = -1; // Means no parenthesis at all to expect
232 /* Get the macro's definition */
233 menu_parser_skip_spaces_and_comments(parser);
234 if (!menu_parser_read_macro_def(parser, macro, arg_name)) {
235 wfree(macro);
236 return;
239 /* Create the macro in the Root parser */
240 while (parser->parent_file != NULL)
241 parser = parser->parent_file;
243 /* Check that the macro was not already defined */
244 if (menu_parser_find_macro(parser, macro->name) != NULL) {
245 WMenuParserError(parser, _("macro \"%s\" already defined, ignoring redefinition"),
246 macro->name);
247 wfree(macro);
248 return;
251 /* Append at beginning of list */
252 macro->next = parser->macros;
253 parser->macros = macro;
256 /* Check if the current word in the parser matches a macro */
257 WParserMacro *menu_parser_find_macro(WMenuParser parser, const char *name)
259 const char *ref, *cmp;
260 WParserMacro *macro;
262 while (parser->parent_file != NULL)
263 parser = parser->parent_file;
264 for (macro = parser->macros; macro != NULL; macro = macro->next) {
265 ref = macro->name;
266 cmp = name;
267 while (*ref != '\0')
268 if (*ref++ != *cmp++)
269 goto check_next_macro;
270 if (isnamechr(*cmp))
271 continue;
273 return macro;
274 check_next_macro: ;
276 return NULL;
279 /* look to see if the next word matches the name of one of the parameter
280 names for a macro definition
281 This function is internal to the macro definition function as this is
282 where the analysis is done */
283 static inline char *mp_is_parameter(char *parse, const char *param) {
284 while (*param)
285 if (*parse++ != *param++)
286 return NULL;
287 if (isnamechr(*parse))
288 return NULL;
289 return parse;
292 /* Read the content definition part of a #define construct (the part after the optional
293 argument list) and store it in the prepared format for quick expansion
295 There is no need to keep track of the names of the parameters, so they are stored in
296 a temporary storage for the time of the macro parsing. */
297 static Bool menu_parser_read_macro_def(WMenuParser parser, WParserMacro *macro, char **arg)
299 unsigned char *wr_size;
300 unsigned char *wr;
301 unsigned int size_data;
302 unsigned int size_max;
303 int i;
305 wr_size = macro->value;
306 size_data = 0;
307 wr = wr_size + 2;
308 size_max = sizeof(macro->value) - (wr - macro->value) - 3;
309 while (menu_parser_skip_spaces_and_comments(parser)) {
310 if (isnamechr(*parser->rd)) {
311 char *next_rd;
313 /* Is the current word a parameter to replace? */
314 for (i = 0; i < macro->arg_count; i++) {
315 next_rd = mp_is_parameter(parser->rd, arg[i]);
316 if (next_rd != NULL) {
317 if (wr + 4 >= macro->value + sizeof(macro->value))
318 goto error_too_much_data;
319 wr_size[0] = (size_data >> 8) & 0xFF;
320 wr_size[1] = size_data & 0xFF;
321 *wr++ = i;
322 wr_size = wr;
323 wr += 2;
324 parser->rd = next_rd;
325 *wr++ = ' ';
326 size_data = 1;
327 size_max = sizeof(macro->value) - (wr - macro->value) - 3;
328 goto next_loop; // Because we can't 'break' this loop and 'continue'
329 // the outer one in a clean and easy way
333 /* Not parameter name -> copy as-is */
334 do {
335 *wr++ = *parser->rd++;
336 if (++size_data >= size_max) {
337 error_too_much_data:
338 WMenuParserError(parser, _("more content than supported for the macro \"%s\""),
339 macro->name);
340 return False;
342 } while (isnamechr(*parser->rd));
343 if (isspace(*parser->rd)) {
344 *wr++ = ' ';
345 if (++size_data >= size_max)
346 goto error_too_much_data;
348 } else {
349 /* Some uninterresting characters, copy as-is */
350 while (*parser->rd != '\0') {
351 if (isnamechr(*parser->rd)) break; // handle in next loop
352 if (parser->rd[0] == '/')
353 if ((parser->rd[1] == '*') || (parser->rd[1] == '/'))
354 break; // Comments are handled by std function
355 if ((parser->rd[0] == '\\') &&
356 (parser->rd[1] == '\n') &&
357 (parser->rd[2] == '\0'))
358 break; // Long-lines are handled by std function
359 *wr++ = *parser->rd++;
360 if (++size_data >= size_max)
361 goto error_too_much_data;
364 next_loop:
367 wr_size[0] = (size_data >> 8) & 0xFF;
368 wr_size[1] = size_data & 0xFF;
369 *wr = 0xFF;
370 return True;
373 /* When a macro is being used in the file, this function will generate the
374 expanded value for the macro in the parser's work line.
375 It blindly supposes that the data generated in macro->value is valid */
376 void menu_parser_expand_macro(WMenuParser parser, WParserMacro *macro,
377 char *write_buf, int write_buf_size)
379 char save_buf[sizeof(parser->line_buffer)];
380 char arg_values_buf[MAXLINE];
381 char *arg_value[MAX_MACRO_ARG_COUNT];
382 char *src, *dst;
383 unsigned char *rd;
384 unsigned int size;
385 int space_left;
387 if (macro->arg_count >= 0) {
388 menu_parser_skip_spaces_and_comments(parser);
389 if (!menu_parser_read_macro_args(parser, macro, arg_value, arg_values_buf, sizeof(arg_values_buf)))
390 return;
393 #ifdef DEBUG
394 macro->usage_count++;
395 #endif
397 /* Save the remaining data from current line as we will overwrite the
398 current line's workspace with the expanded macro, so we can re-append
399 it afterwards */
400 dst = save_buf;
401 while ((*dst++ = *parser->rd++) != '\0') ;
403 /* Generate expanded macro */
404 dst = write_buf;
405 space_left = write_buf_size - 1;
406 if (macro->function != NULL) {
407 /* Parser's pre-defined macros actually proposes a function call to
408 generate dynamic value for the expansion of the macro. In this case
409 it is generated as a C string in the macro->value and used directly */
410 macro->function(macro, parser);
411 rd = macro->value;
412 while (--space_left > 0)
413 if ((*dst = *rd++) == '\0')
414 break;
415 else
416 dst++;
417 } else {
418 rd = macro->value;
419 for (;;) {
420 size = (*rd++) << 8;
421 size |= *rd++;
422 while (size-- > 0) {
423 *dst = *rd++;
424 if (--space_left > 0) dst++;
426 if (*rd == 0xFF) break;
427 src = arg_value[*rd++];
428 while (*src) {
429 *dst = *src++;
430 if (--space_left > 0) dst++;
435 /* Copy finished -> Re-append the text that was following the macro */
436 src = save_buf;
437 while (--space_left > 0)
438 if ((*dst++ = *src++) == '\0')
439 break;
440 *dst = '\0';
442 if (space_left <= 0)
443 WMenuParserError(parser, _("expansion for macro \"%s\" too big, line truncated"),
444 macro->name);
447 /* When reading a macro to be expanded (not being defined), that takes arguments,
448 this function parses the arguments being provided */
449 static Bool menu_parser_read_macro_args(WMenuParser parser, WParserMacro *macro,
450 char *array[], char *buffer, ssize_t buffer_size)
452 int arg;
454 if (*parser->rd != '(') {
455 WMenuParserError(parser, _("macro \"%s\" needs parenthesis for arguments"),
456 macro->name);
457 return False;
459 parser->rd++;
461 buffer_size--; // Room for final '\0'
462 menu_parser_skip_spaces_and_comments(parser);
463 arg = 0;
464 for (;;) {
465 int paren_count;
467 array[arg] = buffer;
468 paren_count = 0;
469 while (*parser->rd != '\0') {
471 if (*parser->rd == '(')
472 paren_count++;
474 if (paren_count <= 0)
475 if ((*parser->rd == ',') ||
476 (*parser->rd == ')') ) break;
478 if ((*parser->rd == '"') || (*parser->rd == '\'')) {
479 char eot = *parser->rd++;
480 if (buffer_size-- > 0) *buffer++ = eot;
481 while (*parser->rd) {
482 if ((*buffer = *parser->rd++) == eot)
483 goto found_end_of_string;
484 if (buffer_size-- > 0) buffer++;
486 WMenuParserError(parser, _("missing closing quote or double-quote before end-of-line") );
487 return False;
488 found_end_of_string:
489 continue;
492 if (isspace(*parser->rd)) {
493 if (buffer_size-- > 0) *buffer++ = ' ';
494 menu_parser_skip_spaces_and_comments(parser);
495 continue;
498 *buffer = *parser->rd++;
499 if (buffer_size-- > 0) buffer++;
501 *buffer = '\0';
502 if (buffer_size-- > 0) buffer++;
504 arg++;
506 if (*parser->rd == ',') {
507 parser->rd++;
508 if (arg >= macro->arg_count) {
509 WMenuParserError(parser, _("too many arguments for macro \"%s\", expected only %d"),
510 macro->name, macro->arg_count);
511 return False;
513 continue;
515 break;
517 if (*parser->rd != ')') {
518 WMenuParserError(parser, _("premature end of line while searching for arguments to macro \"%s\""),
519 macro->name);
520 return False;
522 parser->rd++;
523 if (arg < macro->arg_count) {
524 WMenuParserError(parser, _("not enough arguments for macro \"%s\", expected %d but got only %d"),
525 macro->name, macro->arg_count, arg);
526 return False;
528 if (buffer_size < 0)
529 WMenuParserError(parser, _("too much data in parameter list of macro \"%s\", truncated"),
530 macro->name);
531 return True;
534 /******************************************************************************/
535 /* Definition of pre-defined macros */
536 /******************************************************************************/
538 void WMenuParserRegisterSimpleMacro(WMenuParser parser, const char *name, const char *value)
540 WParserMacro *macro;
541 size_t len;
542 unsigned char *wr;
544 macro = wmalloc(sizeof(*macro));
545 strncpy(macro->name, name, sizeof(macro->name)-1);
546 macro->arg_count = -1;
547 len = strlen(value);
548 if (len > sizeof(macro->value) - 3) {
549 wwarning(_("size of value for macro '%s' is too big, truncated"), name);
550 len = sizeof(macro->value) - 3;
552 macro->value[0] = (len >> 8) & 0xFF;
553 macro->value[1] = len & 0xFF;
554 wr = &macro->value[2];
555 while (len-- > 0)
556 *wr++ = *value++;
557 *wr = 0xFF;
558 macro->next = parser->macros;
559 parser->macros = macro;
562 /* Name of the originally loaded file (before #includes) */
563 static void mpm_base_file(WParserMacro *this, WMenuParser parser)
565 unsigned char *src, *dst;
567 if (this->value[0] != '\0') return; // Value already evaluated, re-use previous
569 while (parser->parent_file != NULL)
570 parser = parser->parent_file;
572 dst = this->value;
573 src = (unsigned char *) parser->file_name;
574 *dst++ = '\"';
575 while (*src != '\0')
576 if (dst < this->value + sizeof(this->value) - 2)
577 *dst++ = *src++;
578 else
579 break;
580 *dst++ = '\"';
581 *dst = '\0';
584 /* Number of #include currently nested */
585 static void mpm_include_level(WParserMacro *this, WMenuParser parser)
587 int level = 0;
588 while (parser->parent_file != NULL) {
589 parser = parser->parent_file;
590 level++;
592 snprintf((char *) this->value, sizeof(this->value), "%d", level);
595 /* Name of current file */
596 static void mpm_current_file(WParserMacro *this, WMenuParser parser)
598 unsigned char *src, *dst;
600 dst = this->value;
601 src = (unsigned char *) parser->file_name;
602 *dst++ = '\"';
603 while (*src != '\0')
604 if (dst < this->value + sizeof(this->value) - 2)
605 *dst++ = *src++;
606 else
607 break;
608 *dst++ = '\"';
609 *dst = '\0';
612 /* Number of current line */
613 static void mpm_current_line(WParserMacro *this, WMenuParser parser)
615 snprintf((char *) this->value, sizeof(this->value), "%d", parser->line_number);
618 /* Name of host on which we are running, not necessarily displaying */
619 static void mpm_get_hostname(WParserMacro *this, WMenuParser parser)
621 char *h;
623 if (this->value[0] != '\0') return; // Value already evaluated, re-use previous
625 h = getenv("HOSTNAME");
626 if (h == NULL) {
627 h = getenv("HOST");
628 if (h == NULL) {
629 if (gethostname((char *) this->value, sizeof(this->value) ) != 0) {
630 WMenuParserError(parser, _("could not determine %s"), "HOSTNAME");
631 this->value[0] = '?';
632 this->value[1] = '?';
633 this->value[2] = '?';
634 this->value[3] = '\0';
636 return;
639 wstrlcpy((char *) this->value, h, sizeof(this->value) );
642 /* Name of the current user */
643 static void mpm_get_user_name(WParserMacro *this, WMenuParser parser)
645 char *user;
647 if (this->value[0] != '\0') return; // Value already evaluated, re-use previous
649 user = getlogin();
650 if (user == NULL) {
651 struct passwd *pw_user;
653 pw_user = getpwuid(getuid());
654 if (pw_user == NULL) {
655 error_no_username:
656 WMenuParserError(parser, _("could not determine %s"), "USER" );
657 /* Fall back on numeric id - better than nothing */
658 snprintf((char *) this->value, sizeof(this->value), "%d", getuid() );
659 return;
661 user = pw_user->pw_name;
662 if (user == NULL) goto error_no_username;
664 wstrlcpy((char *) this->value, user, sizeof(this->value) );
667 /* Number id of the user under which we are running */
668 static void mpm_get_user_id(WParserMacro *this, WMenuParser parser)
670 if (this->value[0] != '\0') return; // Already evaluated, re-use previous
671 snprintf((char *) this->value, sizeof(this->value), "%d", getuid() );
674 /* Small helper to automate creation of one pre-defined macro in the parser */
675 static void w_create_macro(WMenuParser parser, const char *name, WParserMacroFunction *handler)
677 WParserMacro *macro;
679 macro = wmalloc(sizeof(*macro));
680 strcpy(macro->name, name);
681 macro->function = handler;
682 macro->arg_count = -1;
683 macro->next = parser->macros;
684 parser->macros = macro;
687 /***** Register all the pre-defined macros in the parser *****/
688 void menu_parser_register_preset_macros(WMenuParser parser)
690 /* Defined by CPP: common predefined macros (GNU C extension) */
691 w_create_macro(parser, "__BASE_FILE__", mpm_base_file);
692 w_create_macro(parser, "__INCLUDE_LEVEL__", mpm_include_level);
694 /* Defined by CPP: standard predefined macros */
695 w_create_macro(parser, "__FILE__", mpm_current_file);
696 w_create_macro(parser, "__LINE__", mpm_current_line);
697 // w_create_macro(parser, "__DATE__", NULL); [will be implemented only per user request]
698 // w_create_macro(parser, "__TIME__", NULL); [will be implemented only per user request]
700 /* Historically defined by WindowMaker */
701 w_create_macro(parser, "HOST", mpm_get_hostname);
702 w_create_macro(parser, "UID", mpm_get_user_id);
703 w_create_macro(parser, "USER", mpm_get_user_name);