mirror of
https://github.com/gryf/wmaker.git
synced 2025-12-18 20:10:29 +01:00
Update all the callers in our code to deal with the const qualifier where the compiler reports an issue.
653 lines
17 KiB
C
653 lines
17 KiB
C
/*
|
|
* Window Maker window manager
|
|
*
|
|
* Copyright (c) 2012 Christophe Curis
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
#include "wconfig.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
|
|
#include <WINGs/WUtil.h>
|
|
|
|
#include "menuparser.h"
|
|
|
|
static WMenuParser menu_parser_create_new(const char *file_name, void *file,
|
|
const char *include_default_paths);
|
|
static char *menu_parser_isolate_token(WMenuParser parser);
|
|
static void menu_parser_get_directive(WMenuParser parser);
|
|
static Bool menu_parser_include_file(WMenuParser parser);
|
|
static void menu_parser_condition_ifmacro(WMenuParser parser, Bool check_exists);
|
|
static void menu_parser_condition_else(WMenuParser parser);
|
|
static void menu_parser_condition_end(WMenuParser parser);
|
|
|
|
|
|
/* Constructor and Destructor for the Menu Parser object */
|
|
WMenuParser WMenuParserCreate(const char *file_name, void *file,
|
|
const char *include_default_paths)
|
|
{
|
|
WMenuParser parser;
|
|
|
|
parser = menu_parser_create_new(file_name, file, include_default_paths);
|
|
menu_parser_register_preset_macros(parser);
|
|
return parser;
|
|
}
|
|
|
|
void WMenuParserDelete(WMenuParser parser)
|
|
{
|
|
if (parser->include_file) {
|
|
/* Trick: the top parser's data are not wmalloc'd, we point on the
|
|
* provided data so we do not wfree it; however for include files
|
|
* we did wmalloc them.
|
|
* This code should not be used as the wfree is done when we reach
|
|
* the end of an include file; however this may not happen when an
|
|
* early exit occurs (typically when 'readMenuFile' does not find
|
|
* its expected command). */
|
|
fclose(parser->include_file->file_handle);
|
|
wfree((char *) parser->include_file->file_name);
|
|
WMenuParserDelete(parser->include_file);
|
|
}
|
|
|
|
if (parser->macros)
|
|
menu_parser_free_macros(parser);
|
|
|
|
wfree(parser);
|
|
}
|
|
|
|
static WMenuParser menu_parser_create_new(const char *file_name, void *file,
|
|
const char *include_default_paths)
|
|
{
|
|
WMenuParser parser;
|
|
|
|
parser = wmalloc(sizeof(*parser));
|
|
parser->include_default_paths = include_default_paths;
|
|
parser->file_name = file_name;
|
|
parser->file_handle = file;
|
|
parser->rd = parser->line_buffer;
|
|
|
|
return parser;
|
|
}
|
|
|
|
/* To report helpfull messages to user */
|
|
const char *WMenuParserGetFilename(WMenuParser parser)
|
|
{
|
|
return parser->file_name;
|
|
}
|
|
|
|
void WMenuParserError(WMenuParser parser, const char *msg, ...)
|
|
{
|
|
char buf[MAXLINE];
|
|
va_list args;
|
|
WMenuParser parent;
|
|
|
|
while (parser->include_file)
|
|
parser = parser->include_file;
|
|
|
|
va_start(args, msg);
|
|
vsnprintf(buf, sizeof(buf), msg, args);
|
|
va_end(args);
|
|
__wmessage("WMenuParser", parser->file_name, parser->line_number,
|
|
WMESSAGE_TYPE_WARNING, "%s", buf);
|
|
|
|
for (parent = parser->parent_file; parent != NULL; parent = parent->parent_file)
|
|
__wmessage("WMenuParser", parser->file_name, parser->line_number,
|
|
WMESSAGE_TYPE_WARNING, _(" included from file \"%s\" at line %d"),
|
|
parent->file_name, parent->line_number);
|
|
}
|
|
|
|
/* Read one line from file and split content
|
|
* The function returns False when the end of file is reached */
|
|
Bool WMenuParserGetLine(WMenuParser top_parser, char **title, char **command,
|
|
char **parameter, char **shortcut)
|
|
{
|
|
WMenuParser cur_parser;
|
|
enum { GET_TITLE, GET_COMMAND, GET_PARAMETERS, GET_SHORTCUT } scanmode;
|
|
char *token, *params = NULL;
|
|
char lineparam[MAXLINE];
|
|
|
|
lineparam[0] = '\0';
|
|
*title = NULL;
|
|
*command = NULL;
|
|
*parameter = NULL;
|
|
*shortcut = NULL;
|
|
scanmode = GET_TITLE;
|
|
|
|
read_next_line_with_filechange:
|
|
cur_parser = top_parser;
|
|
while (cur_parser->include_file)
|
|
cur_parser = cur_parser->include_file;
|
|
|
|
read_next_line:
|
|
if (fgets(cur_parser->line_buffer, sizeof(cur_parser->line_buffer), cur_parser->file_handle) == NULL) {
|
|
if (cur_parser->cond.depth > 0) {
|
|
int i;
|
|
|
|
for (i = 0; i < cur_parser->cond.depth; i++)
|
|
WMenuParserError(cur_parser, _("missing #endif to match #%s at line %d"),
|
|
cur_parser->cond.stack[i].name, cur_parser->cond.stack[i].line);
|
|
}
|
|
|
|
if (cur_parser->parent_file == NULL)
|
|
/* Not inside an included file -> we have reached the end */
|
|
return False;
|
|
|
|
/* We have only reached the end of an included file -> go back to calling file */
|
|
fclose(cur_parser->file_handle);
|
|
wfree((char *) cur_parser->file_name);
|
|
cur_parser = cur_parser->parent_file;
|
|
wfree(cur_parser->include_file);
|
|
cur_parser->include_file = NULL;
|
|
goto read_next_line_with_filechange;
|
|
}
|
|
|
|
cur_parser->line_number++;
|
|
cur_parser->rd = cur_parser->line_buffer;
|
|
|
|
for (;;) {
|
|
if (!menu_parser_skip_spaces_and_comments(cur_parser)) {
|
|
/* We reached the end of line */
|
|
if (scanmode == GET_TITLE)
|
|
goto read_next_line; /* Empty line -> skip */
|
|
else
|
|
break; /* Finished reading current line -> return it to caller */
|
|
}
|
|
|
|
if ((scanmode == GET_TITLE) && (*cur_parser->rd == '#')) {
|
|
cur_parser->rd++;
|
|
menu_parser_get_directive(cur_parser);
|
|
goto read_next_line_with_filechange;
|
|
}
|
|
|
|
if (cur_parser->cond.stack[0].skip)
|
|
goto read_next_line;
|
|
|
|
/* Found a word */
|
|
token = menu_parser_isolate_token(cur_parser);
|
|
switch (scanmode) {
|
|
case GET_TITLE:
|
|
*title = token;
|
|
scanmode = GET_COMMAND;
|
|
break;
|
|
|
|
case GET_COMMAND:
|
|
if (strcmp(token, "SHORTCUT") == 0) {
|
|
scanmode = GET_SHORTCUT;
|
|
wfree(token);
|
|
} else {
|
|
*command = token;
|
|
scanmode = GET_PARAMETERS;
|
|
}
|
|
break;
|
|
|
|
case GET_SHORTCUT:
|
|
if (*shortcut != NULL) {
|
|
WMenuParserError(top_parser, _("multiple SHORTCUT definition not valid") );
|
|
wfree(*shortcut);
|
|
}
|
|
*shortcut = token;
|
|
scanmode = GET_COMMAND;
|
|
break;
|
|
|
|
case GET_PARAMETERS:
|
|
{
|
|
char *src;
|
|
|
|
if (params == NULL) {
|
|
params = lineparam;
|
|
} else {
|
|
if ((params - lineparam) < sizeof(lineparam) - 1)
|
|
*params++ = ' ';
|
|
}
|
|
|
|
src = token;
|
|
while ((params - lineparam) < sizeof(lineparam) - 1)
|
|
if ( (*params = *src++) == '\0')
|
|
break;
|
|
else
|
|
params++;
|
|
wfree(token);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (params != NULL) {
|
|
lineparam[sizeof(lineparam) - 1] = '\0';
|
|
*parameter = wstrdup(lineparam);
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
/* Return False when there's nothing left on the line,
|
|
otherwise increment parser's pointer to next token */
|
|
Bool menu_parser_skip_spaces_and_comments(WMenuParser parser)
|
|
{
|
|
for (;;) {
|
|
while (isspace(*parser->rd))
|
|
parser->rd++;
|
|
|
|
if (*parser->rd == '\0') {
|
|
return False; /* Found the end of current line */
|
|
} else if ((parser->rd[0] == '\\') &&
|
|
(parser->rd[1] == '\n') &&
|
|
(parser->rd[2] == '\0')) {
|
|
/* Means that the current line is expected to be continued on next line */
|
|
if (fgets(parser->line_buffer, sizeof(parser->line_buffer), parser->file_handle) == NULL) {
|
|
WMenuParserError(parser, _("premature end of file while expecting a new line after '\\'") );
|
|
return False;
|
|
}
|
|
parser->line_number++;
|
|
parser->rd = parser->line_buffer;
|
|
|
|
} else if (parser->rd[0] == '/') {
|
|
if (parser->rd[1] == '/') /* Single line C comment */
|
|
return False; /* Won't find anything more on this line */
|
|
|
|
if (parser->rd[1] == '*') {
|
|
int start_line;
|
|
|
|
start_line = parser->line_number;
|
|
parser->rd += 2;
|
|
for (;;) {
|
|
/* Search end-of-comment marker */
|
|
while (*parser->rd != '\0') {
|
|
if ((parser->rd[0] == '*') && (parser->rd[1] == '/'))
|
|
goto found_end_of_comment;
|
|
|
|
parser->rd++;
|
|
}
|
|
|
|
/* Marker not found -> load next line */
|
|
if (fgets(parser->line_buffer, sizeof(parser->line_buffer), parser->file_handle) == NULL) {
|
|
WMenuParserError(parser, _("reached end of file while searching '*/' for comment started at line %d"), start_line);
|
|
return False;
|
|
}
|
|
|
|
parser->line_number++;
|
|
parser->rd = parser->line_buffer;
|
|
}
|
|
|
|
found_end_of_comment:
|
|
parser->rd += 2; /* Skip closing mark */
|
|
continue; /* Because there may be spaces after the comment */
|
|
}
|
|
|
|
return True; /* the '/' was not a comment, treat it as user data */
|
|
} else {
|
|
return True; /* Found some data */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* read a token (non-spaces suite of characters)
|
|
* the result is wmalloc's, so it needs to be free'd */
|
|
static char *menu_parser_isolate_token(WMenuParser parser)
|
|
{
|
|
char buffer_token[sizeof(parser->line_buffer)];
|
|
char *token;
|
|
int limit = MAX_NESTED_MACROS;
|
|
|
|
token = buffer_token;
|
|
|
|
restart_token_split:
|
|
|
|
while (*parser->rd != '\0')
|
|
if (isspace(*parser->rd)) {
|
|
break;
|
|
|
|
} else if ((parser->rd[0] == '/') &&
|
|
((parser->rd[1] == '*') || (parser->rd[1] == '/'))) {
|
|
break;
|
|
|
|
} else if (parser->rd[0] == '\\') {
|
|
if ((parser->rd[1] == '\n') || (parser->rd[1] == '\0'))
|
|
break;
|
|
|
|
parser->rd++;
|
|
*token++ = *parser->rd++;
|
|
|
|
} else if (*parser->rd == '"' ) {
|
|
char ch;
|
|
|
|
/* Double-quoted string deserve special processing because macros are not expanded
|
|
inside. We also remove the double quotes. */
|
|
parser->rd++;
|
|
while ((*parser->rd != '\0') && (*parser->rd != '\n')) {
|
|
ch = *parser->rd++;
|
|
if (ch == '\\') {
|
|
if ((*parser->rd == '\0') || (*parser->rd == '\n'))
|
|
break;
|
|
*token++ = *parser->rd++;
|
|
} else if (ch == '"')
|
|
goto found_end_dquote;
|
|
else
|
|
*token++ = ch;
|
|
}
|
|
|
|
WMenuParserError(parser, _("missing closing double-quote before end-of-line") );
|
|
|
|
found_end_dquote:
|
|
;
|
|
|
|
} else if (*parser->rd == '\'') {
|
|
char ch;
|
|
|
|
/* Simple-quoted string deserve special processing because we keep their content
|
|
as-is, including the quotes and the \-escaped text */
|
|
*token++ = *parser->rd++;
|
|
while ((*parser->rd != '\0') && (*parser->rd != '\n')) {
|
|
ch = *parser->rd++;
|
|
*token++ = ch;
|
|
if (ch == '\'')
|
|
goto found_end_squote;
|
|
}
|
|
|
|
WMenuParserError(parser, _("missing closing simple-quote before end-of-line") );
|
|
|
|
found_end_squote:
|
|
;
|
|
|
|
} else if (isnamechr(*parser->rd)) {
|
|
WParserMacro *macro;
|
|
|
|
macro = menu_parser_find_macro(parser, parser->rd);
|
|
if (macro != NULL) {
|
|
/* The expansion is done inside the parser's buffer
|
|
this is needed to allow sub macro calls */
|
|
menu_parser_expand_macro(parser, macro);
|
|
|
|
/* Restart parsing to allow expansion of sub macro calls */
|
|
if (limit-- > 0)
|
|
goto restart_token_split;
|
|
|
|
WMenuParserError(parser, _("too many nested macro expansions, breaking loop"));
|
|
|
|
while (isnamechr(*parser->rd))
|
|
parser->rd++;
|
|
|
|
break;
|
|
} else {
|
|
while (isnamechr(*parser->rd))
|
|
*token++ = *parser->rd++;
|
|
}
|
|
} else {
|
|
*token++ = *parser->rd++;
|
|
}
|
|
|
|
*token++ = '\0';
|
|
token = wmalloc(token - buffer_token);
|
|
strcpy(token, buffer_token);
|
|
|
|
return token;
|
|
}
|
|
|
|
/* Processing of special # directives */
|
|
static void menu_parser_get_directive(WMenuParser parser)
|
|
{
|
|
char *command;
|
|
|
|
/* Isolate the command */
|
|
while (isspace(*parser->rd))
|
|
parser->rd++;
|
|
|
|
command = parser->rd;
|
|
while (*parser->rd)
|
|
if (isspace(*parser->rd)) {
|
|
*parser->rd++ = '\0';
|
|
break;
|
|
} else {
|
|
parser->rd++;
|
|
}
|
|
|
|
if (strcmp(command, "include") == 0) {
|
|
if (!menu_parser_include_file(parser))
|
|
return;
|
|
|
|
} else if (strcmp(command, "define") == 0) {
|
|
menu_parser_define_macro(parser);
|
|
|
|
} else if (strcmp(command, "ifdef") == 0) {
|
|
menu_parser_condition_ifmacro(parser, 1);
|
|
|
|
} else if (strcmp(command, "ifndef") == 0) {
|
|
menu_parser_condition_ifmacro(parser, 0);
|
|
|
|
} else if (strcmp(command, "else") == 0) {
|
|
menu_parser_condition_else(parser);
|
|
|
|
} else if (strcmp(command, "endif") == 0) {
|
|
menu_parser_condition_end(parser);
|
|
|
|
} else {
|
|
WMenuParserError(parser, _("unknown directive '#%s'"), command);
|
|
return;
|
|
}
|
|
|
|
if (menu_parser_skip_spaces_and_comments(parser))
|
|
WMenuParserError(parser, _("extra text after '#' command is ignored: \"%.16s...\""),
|
|
parser->rd);
|
|
}
|
|
|
|
/* Extract the file name, search for it in known directories
|
|
* and create a sub-parser to handle it.
|
|
* Returns False if the file could not be found */
|
|
static Bool menu_parser_include_file(WMenuParser parser)
|
|
{
|
|
char buffer[MAXLINE];
|
|
char *req_filename, *fullfilename, *p;
|
|
char eot;
|
|
FILE *fh;
|
|
|
|
if (!menu_parser_skip_spaces_and_comments(parser)) {
|
|
WMenuParserError(parser, _("no file name found for #include") );
|
|
return False;
|
|
}
|
|
|
|
switch (*parser->rd++) {
|
|
case '<':
|
|
eot = '>';
|
|
break;
|
|
case '"':
|
|
eot = '"';
|
|
break;
|
|
default:
|
|
WMenuParserError(parser, _("file name must be enclosed in brackets or double-quotes for #define") );
|
|
return False;
|
|
}
|
|
|
|
req_filename = parser->rd;
|
|
while (*parser->rd) {
|
|
if (*parser->rd == eot) {
|
|
*parser->rd++ = '\0';
|
|
goto found_end_define_fname;
|
|
} else {
|
|
parser->rd++;
|
|
}
|
|
}
|
|
|
|
WMenuParserError(parser, _("missing closing '%c' in filename specification"), eot);
|
|
return False;
|
|
|
|
found_end_define_fname:
|
|
/* If we're inside a #if sequence, we abort now, but not sooner in
|
|
* order to keep the syntax check */
|
|
if (parser->cond.stack[0].skip)
|
|
return False;
|
|
|
|
{ /* Check we are not nesting too many includes */
|
|
WMenuParser p;
|
|
int count;
|
|
|
|
count = 0;
|
|
for (p = parser; p->parent_file; p = p->parent_file)
|
|
count++;
|
|
|
|
if (count > MAX_NESTED_INCLUDES) {
|
|
WMenuParserError(parser, _("too many nested #include's"));
|
|
return False;
|
|
}
|
|
}
|
|
|
|
/* Absolute paths */
|
|
fullfilename = req_filename;
|
|
if (req_filename[0] != '/') {
|
|
/* Search first in the same directory as the current file */
|
|
p = strrchr(parser->file_name, '/');
|
|
if (p != NULL) {
|
|
int len;
|
|
|
|
len = p - parser->file_name + 1;
|
|
if (len > sizeof(buffer) - 1)
|
|
len = sizeof(buffer) - 1;
|
|
|
|
strncpy(buffer, parser->file_name, len);
|
|
strncpy(buffer+len, req_filename, sizeof(buffer) - len - 1);
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
fullfilename = buffer;
|
|
}
|
|
}
|
|
fh = fopen(fullfilename, "rb");
|
|
|
|
/* Not found? Search in wmaker's known places */
|
|
if (fh == NULL) {
|
|
if (req_filename[0] != '/') {
|
|
const char *src;
|
|
int idx;
|
|
|
|
fullfilename = buffer;
|
|
src = parser->include_default_paths;
|
|
while (*src != '\0') {
|
|
idx = 0;
|
|
if (*src == '~') {
|
|
const char *home = wgethomedir();
|
|
while (*home != '\0') {
|
|
if (idx < sizeof(buffer) - 2)
|
|
buffer[idx++] = *home;
|
|
home++;
|
|
}
|
|
src++;
|
|
}
|
|
|
|
while ((*src != '\0') && (*src != ':')) {
|
|
if (idx < sizeof(buffer) - 2)
|
|
buffer[idx++] = *src;
|
|
src++;
|
|
}
|
|
|
|
buffer[idx++] = '/';
|
|
for (p = req_filename; *p != '\0'; p++)
|
|
if (idx < sizeof(buffer) - 1)
|
|
buffer[idx++] = *p;
|
|
buffer[idx] = '\0';
|
|
|
|
fh = fopen(fullfilename, "rb");
|
|
if (fh != NULL)
|
|
goto found_valid_file;
|
|
|
|
if (*src == ':')
|
|
src++;
|
|
}
|
|
}
|
|
WMenuParserError(parser, _("could not find file \"%s\" for #include"), req_filename);
|
|
return False;
|
|
}
|
|
|
|
/* Found the file, make it our new source */
|
|
found_valid_file:
|
|
parser->include_file = menu_parser_create_new(wstrdup(req_filename), fh, parser->include_default_paths);
|
|
parser->include_file->parent_file = parser;
|
|
return True;
|
|
}
|
|
|
|
/* Check wether a macro exists or not, and marks the parser to ignore the
|
|
* following data accordingly */
|
|
static void menu_parser_condition_ifmacro(WMenuParser parser, Bool check_exists)
|
|
{
|
|
WParserMacro *macro;
|
|
int idx;
|
|
const char *cmd_name, *macro_name;
|
|
|
|
cmd_name = check_exists?"ifdef":"ifndef";
|
|
if (!menu_parser_skip_spaces_and_comments(parser)) {
|
|
WMenuParserError(parser, _("missing macro name argument to #%s"), cmd_name);
|
|
return;
|
|
}
|
|
|
|
/* jump to end of provided name for later checks that no extra stuff is following */
|
|
macro_name = parser->rd;
|
|
while (isnamechr(*parser->rd))
|
|
parser->rd++;
|
|
|
|
/* Add this condition to the stack of conditions */
|
|
if (parser->cond.depth >= wlengthof(parser->cond.stack)) {
|
|
WMenuParserError(parser, _("too many nested #if sequences") );
|
|
return;
|
|
}
|
|
|
|
for (idx = parser->cond.depth - 1; idx >= 0; idx--)
|
|
parser->cond.stack[idx + 1] = parser->cond.stack[idx];
|
|
|
|
parser->cond.depth++;
|
|
|
|
if (parser->cond.stack[1].skip) {
|
|
parser->cond.stack[0].skip = True;
|
|
} else {
|
|
macro = menu_parser_find_macro(parser, macro_name);
|
|
parser->cond.stack[0].skip =
|
|
((check_exists) && (macro == NULL)) ||
|
|
((!check_exists) && (macro != NULL)) ;
|
|
}
|
|
|
|
strcpy(parser->cond.stack[0].name, cmd_name);
|
|
parser->cond.stack[0].line = parser->line_number;
|
|
}
|
|
|
|
/* Swap the 'data ignore' flag because a #else condition was found */
|
|
static void menu_parser_condition_else(WMenuParser parser)
|
|
{
|
|
if (parser->cond.depth <= 0) {
|
|
WMenuParserError(parser, _("found #%s but has no matching #if"), "else");
|
|
return;
|
|
}
|
|
|
|
if ((parser->cond.depth > 1) && (parser->cond.stack[1].skip))
|
|
/* The containing #if is false, so we continue skipping anyway */
|
|
parser->cond.stack[0].skip = True;
|
|
else
|
|
parser->cond.stack[0].skip = !parser->cond.stack[0].skip;
|
|
}
|
|
|
|
/* Closes the current conditional, removing it from the stack */
|
|
static void menu_parser_condition_end(WMenuParser parser)
|
|
{
|
|
int idx;
|
|
|
|
if (parser->cond.depth <= 0) {
|
|
WMenuParserError(parser, _("found #%s but has no matching #if"), "endif");
|
|
return;
|
|
}
|
|
|
|
if (--parser->cond.depth > 0)
|
|
for (idx = 0; idx < parser->cond.depth; idx++)
|
|
parser->cond.stack[idx] = parser->cond.stack[idx + 1];
|
|
else
|
|
parser->cond.stack[0].skip = False;
|
|
}
|