1
0
mirror of https://github.com/gryf/wmaker.git synced 2025-12-23 14:42:29 +01:00
Files
wmaker/WINGs/menuparser.c
Christophe CURIS aaa4517df7 Remove dependency to CPP: support for #define macros
This adds support for defining new macros, with or without parameters, which
when found afterwards in the text are replaced by their definition.
The complex analysis for arguments replacement is done at macro definition
time, so it is done only once and the macro expansion will be fast.
The macro-related functions have been placed in their own file because it is
quite a complex task and we do not want filesize to explode, it is always
better to keep things human-sized.
2012-07-14 20:22:20 +02:00

477 lines
14 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, WParserMacro *list_macros);
static void menu_parser_get_directive(WMenuParser parser);
static Bool menu_parser_include_file(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);
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, 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;
char lineparam[MAXLINE];
char *params = NULL;
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->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;
}
/* Found a word */
token = menu_parser_isolate_token(cur_parser, top_parser->macros);
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] == '*')
if (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 os wmalloc's, so it needs to be free'd */
static char *menu_parser_isolate_token(WMenuParser parser, WParserMacro *list_macros)
{
char *start;
char *token;
int limit = MAX_NESTED_MACROS;
start = parser->rd;
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] == '\\') && (parser->rd[1] == '\n'))
break;
else if ((*parser->rd == '"' ) || (*parser->rd == '\'')) {
char eot = *parser->rd++;
while ((*parser->rd != '\0') && (*parser->rd != '\n'))
if (*parser->rd++ == eot)
goto found_end_quote;
WMenuParserError(parser, _("missing closing quote or double-quote before end-of-line") );
found_end_quote:
;
} else if (isnamechr(*parser->rd)) {
WParserMacro *macro;
char *start_macro;
start_macro = parser->rd;
while (isnamechr(*parser->rd))
parser->rd++;
macro = menu_parser_find_macro(parser, start_macro);
if (macro != NULL) {
char *expand_there;
/* Copy the chars before the macro to the beginning of the buffer to
leave as much room as possible for expansion */
expand_there = parser->line_buffer;
if (start != parser->line_buffer)
while (start < start_macro)
*expand_there++ = *start++;
start = parser->line_buffer;
/* Macro expansion will take care to keep the rest of the line after
the macro to the end of the line for future token extraction */
menu_parser_expand_macro(parser, macro, expand_there,
sizeof(parser->line_buffer) - (start - parser->line_buffer) );
/* Restart parsing to allow expansion of sub macro calls */
parser->rd = expand_there;
if (limit-- > 0)
goto restart_token_split;
WMenuParserError(parser, _("too many nested macro expansion, breaking loop") );
while (isnamechr(*parser->rd))
parser->rd++;
break;
}
// else: the text was passed over so it will be counted in the token from 'start'
} else
parser->rd++;
token = wmalloc(parser->rd - start + 1);
strncpy(token, start, parser->rd - start);
token[parser->rd - start] = '\0';
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 {
WMenuParserError(parser, _("unknow 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:
{ /* 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 includes") );
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;
fullfilename = buffer;
src = parser->include_default_paths;
while (*src != '\0') {
p = buffer;
if (*src == '~') {
char *home = wgethomedir();
while (*home != '\0')
*p++ = *home++;
src++;
}
while ((*src != '\0') && (*src != ':'))
*p++ = *src++;
*p++ = '/';
strncpy(p, req_filename, sizeof(buffer) - (p - buffer - 1));
buffer[sizeof(buffer) - 1] = '\0';
fh = fopen(fullfilename, "rb");
if (fh != NULL) goto found_valid_file;
}
}
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;
}