Files
bison/src/muscle-tab.c
Akim Demaille 5855da4722 parser: keep string aliases as the user wrote it
Currently our scanner decodes all the escapes in the strings, and we
later reescape the strings when we emit them.

This is troublesome, as we do not respect the user input.  For
instance, when the user writes in UTF-8, we destroy her string when we
write it back.  And this shows everywhere: in the reports we show the
escaped string instead of the actual alias:

    0 $accept: . exp $end
    1 exp: . exp "\342\212\225" exp
    2    | . exp "+" exp
    3    | . exp "+" exp
    4    | . "number"
    5    | . "\303\221\303\271\341\271\203\303\251\342\204\235\303\264"

    "number"                                                    shift, and go to state 1
    "\303\221\303\271\341\271\203\303\251\342\204\235\303\264"  shift, and go to state 2

This commit preserves the user's exact spelling of the string aliases,
instead of interpreting the escapes and then reescaping.  The report
now shows:

    0 $accept: . exp $end
    1 exp: . exp "⊕" exp
    2    | . exp "+" exp
    3    | . exp "+" exp
    4    | . "number"
    5    | . "Ñùṃéℝô"

    "number"          shift, and go to state 1
    "Ñùṃéℝô"  shift, and go to state 2

Likewise, the XML (and therefore HTML) outputs are fixed.

* src/scan-gram.l (STRING, TSTRING): Do not interpret the escapes in
the resulting string.
* src/parse-gram.y (unquote, parser_init, parser_free, unquote_free)
(handle_defines, handle_language, obstack_for_unquote): New.
Use them to unquote where needed.
* tests/regression.at, tests/report.at: Update.
2020-06-13 16:56:40 +02:00

791 lines
24 KiB
C

/* Muscle table manager for Bison.
Copyright (C) 2001-2015, 2018-2020 Free Software Foundation, Inc.
This file is part of Bison, the GNU Compiler Compiler.
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 3 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, see <http://www.gnu.org/licenses/>. */
#include <config.h>
#include "system.h"
#include <hash.h>
#include <quote.h>
#include "complain.h"
#include "files.h"
#include "fixits.h"
#include "getargs.h"
#include "muscle-tab.h"
muscle_kind
muscle_kind_new (char const *k)
{
if (STREQ (k, "code"))
return muscle_code;
else if (STREQ (k, "keyword"))
return muscle_keyword;
else if (STREQ (k, "string"))
return muscle_string;
abort ();
}
char const *
muscle_kind_string (muscle_kind k)
{
switch (k)
{
case muscle_code: return "code";
case muscle_keyword: return "keyword";
case muscle_string: return "string";
}
abort ();
}
/* A key-value pair, along with storage that can be reclaimed when
this pair is no longer needed. */
typedef struct
{
char const *key;
char const *value;
char *storage;
muscle_kind kind;
} muscle_entry;
/* The name of muscle for the %define variable VAR (corresponding to
FIELD, if defined). */
static uniqstr
muscle_name (char const *var, char const *field)
{
if (field)
return UNIQSTR_CONCAT ("percent_define_", field, "(", var, ")");
else
return UNIQSTR_CONCAT ("percent_define(", var, ")");
}
/* An obstack used to create some entries. */
struct obstack muscle_obstack;
/* Initial capacity of muscles hash table. */
#define HT_INITIAL_CAPACITY 257
static struct hash_table *muscle_table = NULL;
static bool
hash_compare_muscles (void const *x, void const *y)
{
muscle_entry const *m1 = x;
muscle_entry const *m2 = y;
return STREQ (m1->key, m2->key);
}
static size_t
hash_muscle (const void *x, size_t tablesize)
{
muscle_entry const *m = x;
return hash_string (m->key, tablesize);
}
/* Create a fresh muscle name KEY, and insert in the hash table. */
static void *
muscle_entry_new (char const *key)
{
muscle_entry *res = xmalloc (sizeof *res);
res->key = key;
res->value = NULL;
res->storage = NULL;
hash_xinsert (muscle_table, res);
return res;
}
static void
muscle_entry_free (void *entry)
{
muscle_entry *mentry = entry;
free (mentry->storage);
free (mentry);
}
void
muscle_init (void)
{
/* Initialize the muscle obstack. */
obstack_init (&muscle_obstack);
muscle_table = hash_xinitialize (HT_INITIAL_CAPACITY, NULL, hash_muscle,
hash_compare_muscles, muscle_entry_free);
/* Version and input file. */
MUSCLE_INSERT_STRING ("version", VERSION);
}
void
muscle_free (void)
{
hash_free (muscle_table);
obstack_free (&muscle_obstack, NULL);
}
/* Look for the muscle named KEY. Return NULL if does not exist. */
static muscle_entry *
muscle_lookup (char const *key)
{
muscle_entry probe;
probe.key = key;
return hash_lookup (muscle_table, &probe);
}
void
muscle_insert (char const *key, char const *value)
{
muscle_entry *entry = muscle_lookup (key);
if (entry)
free (entry->storage);
else
/* First insertion in the hash. */
entry = muscle_entry_new (key);
entry->value = value;
entry->storage = NULL;
}
/* Append VALUE to the current value of KEY. If KEY did not already
exist, create it. Use MUSCLE_OBSTACK. De-allocate the previously
associated value. Copy VALUE and SEPARATOR. If VALUE does not end
with TERMINATOR, append one. */
static void
muscle_grow (const char *key, const char *val,
const char *separator, const char *terminator)
{
muscle_entry *entry = muscle_lookup (key);
if (entry)
{
obstack_sgrow (&muscle_obstack, entry->value);
obstack_sgrow (&muscle_obstack, separator);
free (entry->storage);
}
else
entry = muscle_entry_new (key);
obstack_sgrow (&muscle_obstack, val);
size_t vals = strlen (val);
size_t terms = strlen (terminator);
if (terms <= vals
&& STRNEQ (val + vals - terms, terminator))
obstack_sgrow (&muscle_obstack, terminator);
{
char const *new_val = obstack_finish0 (&muscle_obstack);
entry->value = entry->storage = xstrdup (new_val);
obstack_free (&muscle_obstack, new_val);
}
}
/*------------------------------------------------------------------.
| Using muscle_grow, append a synchronization line for the location |
| LOC to the current value of KEY. |
`------------------------------------------------------------------*/
static void
muscle_syncline_grow (char const *key, location loc)
{
obstack_printf (&muscle_obstack, "]b4_syncline(%d, ", loc.start.line);
obstack_quote (&muscle_obstack,
quotearg_style (c_quoting_style, loc.start.file));
obstack_sgrow (&muscle_obstack, ")dnl\n[");
char const *extension = obstack_finish0 (&muscle_obstack);
muscle_grow (key, extension, "", "");
obstack_free (&muscle_obstack, extension);
}
/*------------------------------------------------------------------.
| Append VALUE to the current value of KEY, using muscle_grow. But |
| in addition, issue a synchronization line for the location LOC |
| using muscle_syncline_grow. |
`------------------------------------------------------------------*/
void
muscle_code_grow (const char *key, const char *val, location loc)
{
muscle_syncline_grow (key, loc);
muscle_grow (key, val, "", "\n");
}
void
muscle_pair_list_grow (const char *muscle,
const char *a1, const char *a2)
{
obstack_sgrow (&muscle_obstack, "[");
obstack_quote (&muscle_obstack, a1);
obstack_sgrow (&muscle_obstack, ", ");
obstack_quote (&muscle_obstack, a2);
obstack_sgrow (&muscle_obstack, "]");
char const *pair = obstack_finish0 (&muscle_obstack);
muscle_grow (muscle, pair, ",\n", "");
obstack_free (&muscle_obstack, pair);
}
char const *
muscle_find_const (char const *key)
{
muscle_entry *entry = muscle_lookup (key);
return entry ? entry->value : NULL;
}
char *
muscle_find (char const *key)
{
muscle_entry *entry = muscle_lookup (key);
if (entry)
{
aver (entry->value == entry->storage);
return entry->storage;
}
return NULL;
}
/* In the format 'file_name:line.column', append BOUND to MUSCLE. Use
digraphs for special characters in the file name. */
static void
muscle_boundary_grow (char const *key, boundary bound)
{
obstack_sgrow (&muscle_obstack, "[[");
obstack_escape (&muscle_obstack, bound.file);
obstack_printf (&muscle_obstack, ":%d.%d@@%d]]", bound.line, bound.column, bound.byte);
char const *extension = obstack_finish0 (&muscle_obstack);
muscle_grow (key, extension, "", "");
obstack_free (&muscle_obstack, extension);
}
void
muscle_location_grow (char const *key, location loc)
{
muscle_boundary_grow (key, loc.start);
muscle_grow (key, "", ", ", "");
muscle_boundary_grow (key, loc.end);
}
#define COMMON_DECODE(Value) \
case '$': \
++(Value); aver (*(Value) == ']'); \
++(Value); aver (*(Value) == '['); \
obstack_sgrow (&muscle_obstack, "$"); \
break; \
case '@': \
switch (*++(Value)) \
{ \
case '@': obstack_sgrow (&muscle_obstack, "@" ); break; \
case '{': obstack_sgrow (&muscle_obstack, "[" ); break; \
case '}': obstack_sgrow (&muscle_obstack, "]" ); break; \
default: aver (false); break; \
} \
break; \
default: \
obstack_1grow (&muscle_obstack, *(Value)); \
break;
/* Reverse of obstack_escape. */
static char *
string_decode (char const *key)
{
char const *value = muscle_find_const (key);
if (!value)
return NULL;
do {
switch (*value)
{
COMMON_DECODE (value)
case '[':
case ']':
aver (false);
break;
}
} while (*value++);
char const *value_decoded = obstack_finish (&muscle_obstack);
char *res = xstrdup (value_decoded);
obstack_free (&muscle_obstack, value_decoded);
return res;
}
/* Reverse of muscle_location_grow. */
static location
location_decode (char const *value)
{
aver (value);
aver (*value == '[');
++value; aver (*value == '[');
location loc;
while (*++value)
switch (*value)
{
COMMON_DECODE (value)
case '[':
aver (false);
break;
case ']':
++value; aver (*value == ']');
char *boundary_str = obstack_finish0 (&muscle_obstack);
switch (*++value)
{
case ',':
boundary_set_from_string (&loc.start, boundary_str);
obstack_free (&muscle_obstack, boundary_str);
++value; aver (*value == ' ');
++value; aver (*value == '[');
++value; aver (*value == '[');
break;
case '\0':
boundary_set_from_string (&loc.end, boundary_str);
obstack_free (&muscle_obstack, boundary_str);
return loc;
break;
default:
aver (false);
break;
}
break;
}
aver (false);
return loc;
}
void
muscle_user_name_list_grow (char const *key, char const *user_name,
location loc)
{
muscle_grow (key, "[[[[", ",", "");
muscle_grow (key, user_name, "", "");
muscle_grow (key, "]], ", "", "");
muscle_location_grow (key, loc);
muscle_grow (key, "]]", "", "");
}
/** Return an allocated string that represents the %define directive
that performs the assignment.
@param assignment "VAR", or "VAR=VAL".
@param value default value if VAL \a assignment has no '='.
For instance:
"foo", NULL => "%define foo"
"foo", "baz" => "%define foo baz"
"foo=bar", NULL => "%define foo bar"
"foo=bar", "baz" => "%define foo bar"
"foo=", NULL => "%define foo"
"foo=", "baz" => "%define foo"
*/
static
char *
define_directive (char const *assignment,
muscle_kind kind,
char const *value)
{
char *eq = strchr (assignment, '=');
char const *fmt
= eq || !value || !*value ? "%%define %s"
: kind == muscle_code ? "%%define %s {%s}"
: kind == muscle_string ? "%%define %s \"%s\""
: "%%define %s %s";
char *res = xmalloc (strlen (fmt) + strlen (assignment)
+ (value ? strlen (value) : 0));
sprintf (res, fmt, assignment, value);
eq = strchr (res, '=');
if (eq)
*eq = eq[1] ? ' ' : '\0';
return res;
}
/** If the \a variable name is obsolete, return the name to use,
* otherwise \a variable. If the \a value is obsolete, update it too.
*
* Allocates the returned value if needed, otherwise the returned
* value is exactly \a variable. */
static
char const *
muscle_percent_variable_update (char const *variable,
muscle_kind kind,
char const **value,
char **old, char **upd)
{
typedef struct
{
const char *obsolete;
const char *updated;
muscle_kind kind;
} conversion_type;
const conversion_type conversion[] =
{
{ "%error-verbose", "parse.error=verbose", muscle_keyword },
{ "%error_verbose", "parse.error=verbose", muscle_keyword },
{ "abstract", "api.parser.abstract", muscle_keyword },
{ "annotations", "api.parser.annotations", muscle_code },
{ "api.push_pull", "api.push-pull", muscle_keyword },
{ "api.tokens.prefix", "api.token.prefix", muscle_code },
{ "extends", "api.parser.extends", muscle_keyword },
{ "final", "api.parser.final", muscle_keyword },
{ "implements", "api.parser.implements", muscle_keyword },
{ "lex_symbol", "api.token.constructor", -1 },
{ "location_type", "api.location.type", muscle_code },
{ "lr.default-reductions", "lr.default-reduction", muscle_keyword },
{ "lr.keep-unreachable-states", "lr.keep-unreachable-state", muscle_keyword },
{ "lr.keep_unreachable_states", "lr.keep-unreachable-state", muscle_keyword },
{ "namespace", "api.namespace", muscle_code },
{ "parser_class_name", "api.parser.class", muscle_code },
{ "public", "api.parser.public", muscle_keyword },
{ "strictfp", "api.parser.strictfp", muscle_keyword },
{ "stype", "api.value.type", -1 },
{ "variant=", "api.value.type=variant", -1 },
{ "variant=true", "api.value.type=variant", -1 },
{ NULL, NULL, -1, }
};
for (conversion_type const *c = conversion; c->obsolete; ++c)
{
char const *eq = strchr (c->obsolete, '=');
if (eq
? (!strncmp (c->obsolete, variable, eq - c->obsolete)
&& STREQ (eq + 1, *value))
: STREQ (c->obsolete, variable))
{
/* Generate the deprecation warning. */
*old = c->obsolete[0] == '%'
? xstrdup (c->obsolete)
: define_directive (c->obsolete, kind, *value);
*upd = define_directive (c->updated, c->kind, *value);
/* Update the variable and its value. */
{
char *res = xstrdup (c->updated);
char *eq2 = strchr (res, '=');
if (eq2)
{
*eq2 = '\0';
*value = eq2 + 1;
}
return res;
}
}
}
return variable;
}
void
muscle_percent_define_insert (char const *var, location variable_loc,
muscle_kind kind,
char const *value,
muscle_percent_define_how how)
{
/* Backward compatibility. */
char *old = NULL;
char *upd = NULL;
char const *variable
= muscle_percent_variable_update (var, kind,
&value, &old, &upd);
uniqstr name = muscle_name (variable, NULL);
uniqstr loc_name = muscle_name (variable, "loc");
uniqstr syncline_name = muscle_name (variable, "syncline");
uniqstr how_name = muscle_name (variable, "how");
uniqstr kind_name = muscle_name (variable, "kind");
/* Command-line options are processed before the grammar file. */
bool warned = false;
if (how == MUSCLE_PERCENT_DEFINE_GRAMMAR_FILE)
{
char const *current_value = muscle_find_const (name);
if (current_value)
{
muscle_percent_define_how how_old
= atoi (muscle_find_const (how_name));
if (how_old == MUSCLE_PERCENT_DEFINE_F)
goto end;
/* If assigning the same value, make it a warning. */
warnings warn = STREQ (value, current_value) ? Wother : complaint;
complain (&variable_loc, warn,
_("%%define variable %s redefined"),
quote (variable));
location loc = muscle_percent_define_get_loc (variable);
subcomplain (&loc, warn, _("previous definition"));
fixits_register (&variable_loc, "");
warned = true;
}
}
if (!warned && old && upd)
deprecated_directive (&variable_loc, old, upd);
MUSCLE_INSERT_STRING (name, value);
muscle_insert (loc_name, "");
muscle_location_grow (loc_name, variable_loc);
muscle_insert (syncline_name, "");
muscle_syncline_grow (syncline_name, variable_loc);
muscle_user_name_list_grow ("percent_define_user_variables", variable,
variable_loc);
MUSCLE_INSERT_INT (how_name, how);
MUSCLE_INSERT_STRING (kind_name, muscle_kind_string (kind));
end:
free (old);
free (upd);
if (variable != var)
free ((char *) variable);
}
/* This is used for backward compatibility, e.g., "%define api.pure"
supersedes "%pure-parser". */
void
muscle_percent_define_ensure (char const *variable, location loc,
bool value)
{
uniqstr name = muscle_name (variable, NULL);
char const *val = value ? "" : "false";
/* Don't complain is VARIABLE is already defined, but be sure to set
its value to VAL. */
if (!muscle_find_const (name)
|| muscle_percent_define_flag_if (variable) != value)
muscle_percent_define_insert (variable, loc, muscle_keyword, val,
MUSCLE_PERCENT_DEFINE_GRAMMAR_FILE);
}
/* Mark %define VARIABLE as used. */
static void
muscle_percent_define_use (char const *variable)
{
muscle_insert (muscle_name (variable, "bison_variables"), "");
}
/* The value of %define variable VARIABLE (corresponding to FIELD, if
defined). Do not register as used, but diagnose unset variables. */
static
char const *
muscle_percent_define_get_raw (char const *variable, char const *field)
{
uniqstr name = muscle_name (variable, field);
char const *res = muscle_find_const (name);
if (!res)
complain (NULL, fatal, _("%s: undefined %%define variable %s"),
"muscle_percent_define_get_raw", quote (variable));
return res;
}
char *
muscle_percent_define_get (char const *variable)
{
uniqstr name = muscle_name (variable, NULL);
char *value = string_decode (name);
if (!value)
value = xstrdup ("");
muscle_percent_define_use (variable);
return value;
}
/* The kind of VARIABLE. An error if undefined. */
static muscle_kind
muscle_percent_define_get_kind (char const *variable)
{
return muscle_kind_new (muscle_percent_define_get_raw (variable, "kind"));
}
/* Check the kind of VARIABLE. An error if undefined. */
static void
muscle_percent_define_check_kind (char const *variable, muscle_kind kind)
{
if (muscle_percent_define_get_kind (variable) != kind)
{
location loc = muscle_percent_define_get_loc (variable);
switch (kind)
{
case muscle_code:
complain (&loc, Wdeprecated,
_("%%define variable '%s' requires '{...}' values"),
variable);
break;
case muscle_keyword:
complain (&loc, Wdeprecated,
_("%%define variable '%s' requires keyword values"),
variable);
break;
case muscle_string:
complain (&loc, Wdeprecated,
_("%%define variable '%s' requires '\"...\"' values"),
variable);
break;
}
}
}
location
muscle_percent_define_get_loc (char const *variable)
{
return location_decode (muscle_percent_define_get_raw (variable, "loc"));
}
char const *
muscle_percent_define_get_syncline (char const *variable)
{
return muscle_percent_define_get_raw (variable, "syncline");
}
bool
muscle_percent_define_ifdef (char const *variable)
{
if (muscle_find_const (muscle_name (variable, NULL)))
{
muscle_percent_define_use (variable);
return true;
}
else
return false;
}
bool
muscle_percent_define_flag_if (char const *variable)
{
uniqstr invalid_boolean_name = muscle_name (variable, "invalid_boolean");
bool res = false;
if (muscle_percent_define_ifdef (variable))
{
char *value = muscle_percent_define_get (variable);
muscle_percent_define_check_kind (variable, muscle_keyword);
if (value[0] == '\0' || STREQ (value, "true"))
res = true;
else if (STREQ (value, "false"))
res = false;
else if (!muscle_find_const (invalid_boolean_name))
{
muscle_insert (invalid_boolean_name, "");
location loc = muscle_percent_define_get_loc (variable);
complain (&loc, complaint,
_("invalid value for %%define Boolean variable %s"),
quote (variable));
}
free (value);
}
else
complain (NULL, fatal, _("%s: undefined %%define variable %s"),
"muscle_percent_define_flag", quote (variable));
return res;
}
void
muscle_percent_define_default (char const *variable, char const *value)
{
uniqstr name = muscle_name (variable, NULL);
if (!muscle_find_const (name))
{
MUSCLE_INSERT_STRING (name, value);
MUSCLE_INSERT_STRING (muscle_name (variable, "kind"), "keyword");
{
uniqstr loc_name = muscle_name (variable, "loc");
location loc;
loc.start.file = "<default value>";
loc.start.line = -1;
loc.start.column = -1;
loc.start.byte = -1;
loc.end = loc.start;
muscle_insert (loc_name, "");
muscle_location_grow (loc_name, loc);
}
muscle_insert (muscle_name (variable, "syncline"), "");
}
}
void
muscle_percent_define_check_values (char const * const *values)
{
for (; *values; ++values)
{
char const * const *variablep = values;
uniqstr name = muscle_name (*variablep, NULL);
char *value = string_decode (name);
muscle_percent_define_check_kind (*variablep, muscle_keyword);
if (value)
{
for (++values; *values; ++values)
if (STREQ (value, *values))
break;
if (!*values)
{
location loc = muscle_percent_define_get_loc (*variablep);
complain (&loc, complaint,
_("invalid value for %%define variable %s: %s"),
quote (*variablep), quote_n (1, value));
for (values = variablep + 1; *values; ++values)
subcomplain (&loc, complaint | no_caret | silent,
_("accepted value: %s"), quote (*values));
}
else
while (*values)
++values;
free (value);
}
else
complain (NULL, fatal, _("%s: undefined %%define variable %s"),
"muscle_percent_define_check_values", quote (*variablep));
}
}
void
muscle_percent_code_grow (char const *qualifier, location qualifier_loc,
char const *code, location code_loc)
{
char const *name = UNIQSTR_CONCAT ("percent_code(", qualifier, ")");
muscle_code_grow (name, code, code_loc);
muscle_user_name_list_grow ("percent_code_user_qualifiers", qualifier,
qualifier_loc);
}
/*------------------------------------------------.
| Output the definition of ENTRY as a m4_define. |
`------------------------------------------------*/
static inline bool
muscle_m4_output (muscle_entry *entry, FILE *out)
{
fprintf (out,
"m4_define([b4_%s],\n"
"[[%s]])\n\n\n", entry->key, entry->value);
return true;
}
static bool
muscle_m4_output_processor (void *entry, void *out)
{
return muscle_m4_output (entry, out);
}
void
muscles_m4_output (FILE *out)
{
hash_do_for_each (muscle_table, muscle_m4_output_processor, out);
}