boxes/src/parsing.c
2024-02-16 22:01:58 +01:00

316 lines
9.8 KiB
C

/*
* boxes - Command line filter to draw/remove ASCII boxes around text
* Copyright (c) 1999-2024 Thomas Jensen and the boxes contributors
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License, version 3, as published by the Free Software Foundation.
* 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, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
/*
* Invocation of the configuration file parser
*/
#include "config.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "boxes.h"
#include "bxstring.h"
#include "parsing.h"
#include "tools.h"
#include "parser.h"
#include "lex.yy.h"
/** file handle of the config file currently being parsed */
static FILE *current_config_handle = NULL;
/** the arguments passed to our current invocation of the parser */
static pass_to_bison *current_bison_args = NULL;
/** the name of the initially specified config file */
static bxstr_t *first_config_file = NULL;
/** all parent configs encountered across all parsed config files */
static bxstr_t **parent_configs = NULL;
/** total number of parent configs (the size of the `parent_configs` array) */
static size_t num_parent_configs = 0;
/**
* Set yyin to the config file to be used.
*
* @param bison_args the bison args that we set up in the calling function (contains config file path)
* @return 0 if successful (yyin is set)
* != 0 on error (yyin is unmodified)
*/
static int open_yy_config_file(pass_to_bison *bison_args)
{
current_config_handle = bx_fopens(bison_args->config_file, "r");
if (current_config_handle == NULL) {
fprintf(stderr, "%s: Couldn't open config file '%s' for input\n", PROJECT,
bxs_to_output(bison_args->config_file));
return 1;
}
yyset_in(current_config_handle, bison_args->lexer_state);
return 0;
}
void print_design_list_header()
{
char buf[42];
sprintf(buf, "%d", num_designs);
fprintf(opt.outfile, "%d Available Style%s", num_designs, num_designs == 1 ? "" : "s");
if (num_parent_configs > 0) {
fprintf(opt.outfile, ":%s", opt.eol);
fprintf(opt.outfile, "-----------------%s", num_designs == 1 ? "" : "-");
for (int i = strlen(buf); i > 0; --i) {
fprintf(opt.outfile, "-");
}
fprintf(opt.outfile, "%s%s", opt.eol, opt.eol);
fprintf(opt.outfile, "Configuration Files:%s", opt.eol);
fprintf(opt.outfile, " - %s%s", bxs_to_output(first_config_file), opt.eol);
for (size_t i = 0; i < num_parent_configs; i++) {
fprintf(opt.outfile, " - %s (parent)%s", bxs_to_output(parent_configs[i]), opt.eol);
}
}
else {
fprintf(opt.outfile, " in \"%s\":%s", bxs_to_output(first_config_file), opt.eol);
fprintf(opt.outfile, "-----------------------%s", num_designs == 1 ? "" : "-");
for (int i = first_config_file->num_columns + strlen(buf); i > 0; --i) {
fprintf(opt.outfile, "-");
}
}
fprintf(opt.outfile, "%s%s", opt.eol, opt.eol);
}
/**
* Print configuration file parser errors.
* @param bison_args pointer to the parser arguments
* @param fmt a format string for `vfprintf()`, followed by the arguments
* @return 0
*/
int yyerror(pass_to_bison *bison_args, const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
pass_to_bison *bargs = bison_args ? bison_args : current_bison_args;
fprintf(stderr, "%s: %s: line %d: ", PROJECT, bxs_to_output(bargs->config_file), yyget_lineno(bargs->lexer_state));
vfprintf(stderr, fmt, ap);
fputc('\n', stderr);
va_end (ap);
return 0;
}
static pass_to_bison new_bison_args(bxstr_t *config_file)
{
pass_to_bison bison_args;
bison_args.designs = NULL;
bison_args.num_designs = 0;
bison_args.design_idx = 0;
bison_args.config_file = config_file;
bison_args.num_mandatory = 0;
bison_args.time_for_se_check = 0;
bison_args.num_shapespec = 0;
bison_args.skipping = 0;
bison_args.speeding = 0;
bison_args.parent_configs = NULL;
bison_args.num_parent_configs = 0;
bison_args.lexer_state = NULL;
return bison_args;
}
static pass_to_flex new_flex_extra_data()
{
pass_to_flex flex_extra_data;
flex_extra_data.yyerrcnt = 0;
flex_extra_data.sdel = '\"';
flex_extra_data.sesc = '\\';
return flex_extra_data;
}
static pass_to_bison parse_config_file(bxstr_t *config_file, design_t *child_configs, size_t num_child_configs)
{
#ifdef DEBUG
fprintf (stderr, "Parsing Config File %s ...\n", bxs_to_output(config_file));
#endif
pass_to_bison bison_args = new_bison_args(config_file);
bison_args.child_configs = child_configs;
bison_args.num_child_configs = num_child_configs;
pass_to_flex flex_extra_data = new_flex_extra_data();
current_bison_args = &bison_args;
yylex_init (&(bison_args.lexer_state));
yylex_init_extra(&flex_extra_data, &(bison_args.lexer_state));
int rc = open_yy_config_file(&bison_args);
if (rc) {
return new_bison_args(config_file);
}
inflate_inbuf(bison_args.lexer_state, config_file);
rc = yyparse(&bison_args);
yylex_destroy(bison_args.lexer_state);
if (current_config_handle != NULL) {
fclose(current_config_handle);
current_config_handle = NULL;
}
if (rc) {
#ifdef DEBUG
fprintf (stderr, "yyparse() returned %d\n", rc);
#endif
return new_bison_args(config_file);
}
return bison_args;
}
static int designs_contain(design_t *designs, size_t num_designs, design_t adesign)
{
int result = adesign.name == NULL; /* broken records count as "present", so we don't copy them */
if (designs != NULL && num_designs > 0 && adesign.name != NULL) {
for (size_t d = 0; d < num_designs; d++) {
if (strcasecmp(designs[d].name, adesign.name) == 0) {
result = 1;
break;
}
}
}
return result;
}
static int record_parent_config_files(pass_to_bison *bison_args)
{
if (bison_args->num_parent_configs > 0) {
for (int parent_idx = bison_args->num_parent_configs - 1; parent_idx >= 0; parent_idx--) {
bxstr_t *parent_file = bison_args->parent_configs[parent_idx];
int is_new = !array_contains_bxs(parent_configs, num_parent_configs, parent_file);
if (is_new) {
parent_configs = (bxstr_t **) realloc(parent_configs, (num_parent_configs + 1) * sizeof(bxstr_t *));
if (parent_configs == NULL) {
return 1;
}
parent_configs[num_parent_configs] = parent_file;
++num_parent_configs;
}
}
}
BFREE(bison_args->parent_configs);
return 0;
}
static int copy_designs(pass_to_bison *bison_args, design_t **r_result, size_t *r_num_designs)
{
if (bison_args->num_designs > 0) {
if (*r_result == NULL) {
*r_result = bison_args->designs;
*r_num_designs = bison_args->num_designs;
}
else {
for (size_t d = 0; d < bison_args->num_designs; d++) {
if (!designs_contain(*r_result, *r_num_designs, bison_args->designs[d])) {
*r_result = (design_t *) realloc(*r_result, (*r_num_designs + 1) * sizeof(design_t));
if (*r_result == NULL) {
perror(PROJECT);
return 1;
}
memcpy(*r_result + *r_num_designs, bison_args->designs + d, sizeof(design_t));
(*r_num_designs)++;
}
}
BFREE(bison_args->designs);
}
}
return 0;
}
design_t *parse_config_files(bxstr_t *p_first_config_file, size_t *r_num_designs)
{
size_t parents_parsed = -1; /** how many parent config files have already been parsed */
design_t *result = NULL;
*r_num_designs = 0;
first_config_file = p_first_config_file;
bxstr_t *config_file = p_first_config_file;
do {
pass_to_bison bison_args = parse_config_file(config_file, result, *r_num_designs);
++parents_parsed;
#ifdef DEBUG
fprintf (stderr, "bison_args returned: "
".num_parent_configs=%d, .parent_configs=%p, .num_designs=%d, .designs=%p\n",
(int) bison_args.num_parent_configs, bison_args.parent_configs,
(int) bison_args.num_designs, bison_args.designs);
#endif
if (record_parent_config_files(&bison_args) != 0) {
perror(PROJECT);
return NULL;
}
if (copy_designs(&bison_args, &result, r_num_designs) != 0) {
return NULL;
}
#ifdef DEBUG
fprintf (stderr, "design count raised to: %d\n", (int) *r_num_designs);
#endif
if (parents_parsed < num_parent_configs) {
config_file = parent_configs[parents_parsed];
}
} while (parents_parsed < num_parent_configs);
if (*r_num_designs == 0) {
if (opt.design_choice_by_user) {
fprintf (stderr, "%s: unknown box design -- %s\n", PROJECT, (char *) opt.design);
}
else {
fprintf (stderr, "%s: no valid designs found\n", PROJECT);
}
return NULL;
}
fflush(stderr); /* ensure order of error msgs and normal output for test cases */
return result;
}
/*EOF*/ /* vim: set sw=4: */