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.
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))
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
;
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
96 printf(__FILE__
": Macros defined while parsing \"%s\"\n", parser
->file_name
);
99 for (macro
= parser
->macros
; macro
!= NULL
; macro
= mnext
) {
101 printf(" %s", macro
->name
);
102 if (macro
->arg_count
>= 0)
103 printf("(args=%d)", macro
->arg_count
);
106 if (macro
->function
!= NULL
) {
107 macro
->function(macro
, parser
);
108 printf("function:\"%s\"", macro
->value
);
115 while (size
-- > 0) putchar(*rd
++);
117 if (*rd
== 0xFF) break;
118 printf(" #%d ", (*rd
++) + 1);
121 printf(", used %d times\n", macro
->usage_count
);
128 printf(__FILE__
": %d macros\n", count
);
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,
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
)
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") );
169 macro
= wmalloc(sizeof(*macro
));
171 /* Isolate name of macro */
173 while (isnamechr(*parser
->rd
)) {
174 if (idx
< sizeof(macro
->name
) - 1)
175 macro
->name
[idx
++] = *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
== '(') {
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
);
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
);
196 *parser
->rd
= '\0'; // fake end-of-line to avoid warnings from remaining line content
199 if (isnamechr(*parser
->rd
)) {
200 arg_name
[macro
->arg_count
++] = arg_names_buf
+ idx
;
202 if (idx
< sizeof(arg_names_buf
) - 1)
203 arg_names_buf
[idx
++] = *parser
->rd
;
205 } while (isnamechr(*parser
->rd
));
206 arg_names_buf
[idx
] = '\0';
207 if (idx
< sizeof(arg_names_buf
) - 1) idx
++;
209 WMenuParserError(parser
, _("invalid characted '%c' in arg-list for macro \"%s\" while expecting parameter name"),
210 *parser
->rd
, macro
->name
);
212 *parser
->rd
= '\0'; // fake end-of-line to avoid warnings from remaining line content
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
);
223 *parser
->rd
= '\0'; // fake end-of-line to avoid warnings from remaining line content
228 parser
->rd
++; // skip the closing ')'
230 macro
->arg_count
= -1; // Means no parenthesis at all to expect
232 /* If we're inside a #if sequence, we abort now, but not sooner in
233 order to keep the syntax check */
234 if (parser
->cond
.stack
[0].skip
) {
236 *parser
->rd
= '\0'; // Ignore macro content
240 /* Get the macro's definition */
241 menu_parser_skip_spaces_and_comments(parser
);
242 if (!menu_parser_read_macro_def(parser
, macro
, arg_name
)) {
247 /* Create the macro in the Root parser */
248 while (parser
->parent_file
!= NULL
)
249 parser
= parser
->parent_file
;
251 /* Check that the macro was not already defined */
252 if (menu_parser_find_macro(parser
, macro
->name
) != NULL
) {
253 WMenuParserError(parser
, _("macro \"%s\" already defined, ignoring redefinition"),
259 /* Append at beginning of list */
260 macro
->next
= parser
->macros
;
261 parser
->macros
= macro
;
264 /* Check if the current word in the parser matches a macro */
265 WParserMacro
*menu_parser_find_macro(WMenuParser parser
, const char *name
)
267 const char *ref
, *cmp
;
270 while (parser
->parent_file
!= NULL
)
271 parser
= parser
->parent_file
;
272 for (macro
= parser
->macros
; macro
!= NULL
; macro
= macro
->next
) {
276 if (*ref
++ != *cmp
++)
277 goto check_next_macro
;
287 /* look to see if the next word matches the name of one of the parameter
288 names for a macro definition
289 This function is internal to the macro definition function as this is
290 where the analysis is done */
291 static inline char *mp_is_parameter(char *parse
, const char *param
) {
293 if (*parse
++ != *param
++)
295 if (isnamechr(*parse
))
300 /* Read the content definition part of a #define construct (the part after the optional
301 argument list) and store it in the prepared format for quick expansion
303 There is no need to keep track of the names of the parameters, so they are stored in
304 a temporary storage for the time of the macro parsing. */
305 static Bool
menu_parser_read_macro_def(WMenuParser parser
, WParserMacro
*macro
, char **arg
)
307 unsigned char *wr_size
;
309 unsigned int size_data
;
310 unsigned int size_max
;
313 wr_size
= macro
->value
;
316 size_max
= sizeof(macro
->value
) - (wr
- macro
->value
) - 3;
317 while (menu_parser_skip_spaces_and_comments(parser
)) {
318 if (isnamechr(*parser
->rd
)) {
321 /* Is the current word a parameter to replace? */
322 for (i
= 0; i
< macro
->arg_count
; i
++) {
323 next_rd
= mp_is_parameter(parser
->rd
, arg
[i
]);
324 if (next_rd
!= NULL
) {
325 if (wr
+ 4 >= macro
->value
+ sizeof(macro
->value
))
326 goto error_too_much_data
;
327 wr_size
[0] = (size_data
>> 8) & 0xFF;
328 wr_size
[1] = size_data
& 0xFF;
332 parser
->rd
= next_rd
;
335 size_max
= sizeof(macro
->value
) - (wr
- macro
->value
) - 3;
336 goto next_loop
; // Because we can't 'break' this loop and 'continue'
337 // the outer one in a clean and easy way
341 /* Not parameter name -> copy as-is */
343 *wr
++ = *parser
->rd
++;
344 if (++size_data
>= size_max
) {
346 WMenuParserError(parser
, _("more content than supported for the macro \"%s\""),
350 } while (isnamechr(*parser
->rd
));
351 if (isspace(*parser
->rd
)) {
353 if (++size_data
>= size_max
)
354 goto error_too_much_data
;
357 /* Some uninterresting characters, copy as-is */
358 while (*parser
->rd
!= '\0') {
359 if (isnamechr(*parser
->rd
)) break; // handle in next loop
360 if (parser
->rd
[0] == '/')
361 if ((parser
->rd
[1] == '*') || (parser
->rd
[1] == '/'))
362 break; // Comments are handled by std function
363 if ((parser
->rd
[0] == '\\') &&
364 (parser
->rd
[1] == '\n') &&
365 (parser
->rd
[2] == '\0'))
366 break; // Long-lines are handled by std function
367 *wr
++ = *parser
->rd
++;
368 if (++size_data
>= size_max
)
369 goto error_too_much_data
;
375 wr_size
[0] = (size_data
>> 8) & 0xFF;
376 wr_size
[1] = size_data
& 0xFF;
381 /* When a macro is being used in the file, this function will generate the
382 expanded value for the macro in the parser's work line.
383 It blindly supposes that the data generated in macro->value is valid */
384 void menu_parser_expand_macro(WMenuParser parser
, WParserMacro
*macro
,
385 char *write_buf
, int write_buf_size
)
387 char save_buf
[sizeof(parser
->line_buffer
)];
388 char arg_values_buf
[MAXLINE
];
389 char *arg_value
[MAX_MACRO_ARG_COUNT
];
395 if (macro
->arg_count
>= 0) {
396 menu_parser_skip_spaces_and_comments(parser
);
397 if (!menu_parser_read_macro_args(parser
, macro
, arg_value
, arg_values_buf
, sizeof(arg_values_buf
)))
402 macro
->usage_count
++;
405 /* Save the remaining data from current line as we will overwrite the
406 current line's workspace with the expanded macro, so we can re-append
409 while ((*dst
++ = *parser
->rd
++) != '\0') ;
411 /* Generate expanded macro */
413 space_left
= write_buf_size
- 1;
414 if (macro
->function
!= NULL
) {
415 /* Parser's pre-defined macros actually proposes a function call to
416 generate dynamic value for the expansion of the macro. In this case
417 it is generated as a C string in the macro->value and used directly */
418 macro
->function(macro
, parser
);
420 while (--space_left
> 0)
421 if ((*dst
= *rd
++) == '\0')
432 if (--space_left
> 0) dst
++;
434 if (*rd
== 0xFF) break;
435 src
= arg_value
[*rd
++];
438 if (--space_left
> 0) dst
++;
443 /* Copy finished -> Re-append the text that was following the macro */
445 while (--space_left
> 0)
446 if ((*dst
++ = *src
++) == '\0')
451 WMenuParserError(parser
, _("expansion for macro \"%s\" too big, line truncated"),
455 /* When reading a macro to be expanded (not being defined), that takes arguments,
456 this function parses the arguments being provided */
457 static Bool
menu_parser_read_macro_args(WMenuParser parser
, WParserMacro
*macro
,
458 char *array
[], char *buffer
, ssize_t buffer_size
)
462 if (*parser
->rd
!= '(') {
463 WMenuParserError(parser
, _("macro \"%s\" needs parenthesis for arguments"),
469 buffer_size
--; // Room for final '\0'
470 menu_parser_skip_spaces_and_comments(parser
);
477 while (*parser
->rd
!= '\0') {
479 if (*parser
->rd
== '(')
482 if (paren_count
<= 0)
483 if ((*parser
->rd
== ',') ||
484 (*parser
->rd
== ')') ) break;
486 if ((*parser
->rd
== '"') || (*parser
->rd
== '\'')) {
487 char eot
= *parser
->rd
++;
488 if (buffer_size
-- > 0) *buffer
++ = eot
;
489 while (*parser
->rd
) {
490 if ((*buffer
= *parser
->rd
++) == eot
)
491 goto found_end_of_string
;
492 if (buffer_size
-- > 0) buffer
++;
494 WMenuParserError(parser
, _("missing closing quote or double-quote before end-of-line") );
500 if (isspace(*parser
->rd
)) {
501 if (buffer_size
-- > 0) *buffer
++ = ' ';
502 menu_parser_skip_spaces_and_comments(parser
);
506 *buffer
= *parser
->rd
++;
507 if (buffer_size
-- > 0) buffer
++;
510 if (buffer_size
-- > 0) buffer
++;
514 if (*parser
->rd
== ',') {
516 if (arg
>= macro
->arg_count
) {
517 WMenuParserError(parser
, _("too many arguments for macro \"%s\", expected only %d"),
518 macro
->name
, macro
->arg_count
);
525 if (*parser
->rd
!= ')') {
526 WMenuParserError(parser
, _("premature end of line while searching for arguments to macro \"%s\""),
531 if (arg
< macro
->arg_count
) {
532 WMenuParserError(parser
, _("not enough arguments for macro \"%s\", expected %d but got only %d"),
533 macro
->name
, macro
->arg_count
, arg
);
537 WMenuParserError(parser
, _("too much data in parameter list of macro \"%s\", truncated"),
542 /******************************************************************************/
543 /* Definition of pre-defined macros */
544 /******************************************************************************/
546 void WMenuParserRegisterSimpleMacro(WMenuParser parser
, const char *name
, const char *value
)
552 macro
= wmalloc(sizeof(*macro
));
553 strncpy(macro
->name
, name
, sizeof(macro
->name
)-1);
554 macro
->arg_count
= -1;
556 if (len
> sizeof(macro
->value
) - 3) {
557 wwarning(_("size of value for macro '%s' is too big, truncated"), name
);
558 len
= sizeof(macro
->value
) - 3;
560 macro
->value
[0] = (len
>> 8) & 0xFF;
561 macro
->value
[1] = len
& 0xFF;
562 wr
= ¯o
->value
[2];
566 macro
->next
= parser
->macros
;
567 parser
->macros
= macro
;
570 /* Name of the originally loaded file (before #includes) */
571 static void mpm_base_file(WParserMacro
*this, WMenuParser parser
)
573 unsigned char *src
, *dst
;
575 if (this->value
[0] != '\0') return; // Value already evaluated, re-use previous
577 while (parser
->parent_file
!= NULL
)
578 parser
= parser
->parent_file
;
581 src
= (unsigned char *) parser
->file_name
;
584 if (dst
< this->value
+ sizeof(this->value
) - 2)
592 /* Number of #include currently nested */
593 static void mpm_include_level(WParserMacro
*this, WMenuParser parser
)
596 while (parser
->parent_file
!= NULL
) {
597 parser
= parser
->parent_file
;
600 snprintf((char *) this->value
, sizeof(this->value
), "%d", level
);
603 /* Name of current file */
604 static void mpm_current_file(WParserMacro
*this, WMenuParser parser
)
606 unsigned char *src
, *dst
;
609 src
= (unsigned char *) parser
->file_name
;
612 if (dst
< this->value
+ sizeof(this->value
) - 2)
620 /* Number of current line */
621 static void mpm_current_line(WParserMacro
*this, WMenuParser parser
)
623 snprintf((char *) this->value
, sizeof(this->value
), "%d", parser
->line_number
);
626 /* Name of host on which we are running, not necessarily displaying */
627 static void mpm_get_hostname(WParserMacro
*this, WMenuParser parser
)
631 if (this->value
[0] != '\0') return; // Value already evaluated, re-use previous
633 h
= getenv("HOSTNAME");
637 if (gethostname((char *) this->value
, sizeof(this->value
) ) != 0) {
638 WMenuParserError(parser
, _("could not determine %s"), "HOSTNAME");
639 this->value
[0] = '?';
640 this->value
[1] = '?';
641 this->value
[2] = '?';
642 this->value
[3] = '\0';
647 wstrlcpy((char *) this->value
, h
, sizeof(this->value
) );
650 /* Name of the current user */
651 static void mpm_get_user_name(WParserMacro
*this, WMenuParser parser
)
655 if (this->value
[0] != '\0') return; // Value already evaluated, re-use previous
659 struct passwd
*pw_user
;
661 pw_user
= getpwuid(getuid());
662 if (pw_user
== NULL
) {
664 WMenuParserError(parser
, _("could not determine %s"), "USER" );
665 /* Fall back on numeric id - better than nothing */
666 snprintf((char *) this->value
, sizeof(this->value
), "%d", getuid() );
669 user
= pw_user
->pw_name
;
670 if (user
== NULL
) goto error_no_username
;
672 wstrlcpy((char *) this->value
, user
, sizeof(this->value
) );
675 /* Number id of the user under which we are running */
676 static void mpm_get_user_id(WParserMacro
*this, WMenuParser parser
)
678 if (this->value
[0] != '\0') return; // Already evaluated, re-use previous
679 snprintf((char *) this->value
, sizeof(this->value
), "%d", getuid() );
682 /* Small helper to automate creation of one pre-defined macro in the parser */
683 static void w_create_macro(WMenuParser parser
, const char *name
, WParserMacroFunction
*handler
)
687 macro
= wmalloc(sizeof(*macro
));
688 strcpy(macro
->name
, name
);
689 macro
->function
= handler
;
690 macro
->arg_count
= -1;
691 macro
->next
= parser
->macros
;
692 parser
->macros
= macro
;
695 /***** Register all the pre-defined macros in the parser *****/
696 void menu_parser_register_preset_macros(WMenuParser parser
)
698 /* Defined by CPP: common predefined macros (GNU C extension) */
699 w_create_macro(parser
, "__BASE_FILE__", mpm_base_file
);
700 w_create_macro(parser
, "__INCLUDE_LEVEL__", mpm_include_level
);
702 /* Defined by CPP: standard predefined macros */
703 w_create_macro(parser
, "__FILE__", mpm_current_file
);
704 w_create_macro(parser
, "__LINE__", mpm_current_line
);
705 // w_create_macro(parser, "__DATE__", NULL); [will be implemented only per user request]
706 // w_create_macro(parser, "__TIME__", NULL); [will be implemented only per user request]
708 /* Historically defined by WindowMaker */
709 w_create_macro(parser
, "HOST", mpm_get_hostname
);
710 w_create_macro(parser
, "UID", mpm_get_user_id
);
711 w_create_macro(parser
, "USER", mpm_get_user_name
);