[b4_fatal([[$0: undefined %%define variable '%s']], [$1])])])
 
 
-# b4_percent_define_default(VARIABLE, DEFAULT)
-# --------------------------------------------
+# b4_percent_define_default(VARIABLE, DEFAULT, [KIND = keyword])
+# --------------------------------------------------------------
 # Mimic muscle_percent_define_default in ../src/muscle-tab.h exactly.  That is,
 # if the %define variable VARIABLE is undefined, set its value to DEFAULT.
 # Don't record this as a Bison usage of VARIABLE as there's no reason to
 m4_define([b4_percent_define_default],
 [b4_percent_define_ifdef_([$1], [],
            [m4_define([b4_percent_define(]$1[)], [$2])dnl
+            m4_define([b4_percent_define_kind(]$1[)],
+                      [m4_default([$3], [keyword])])dnl
             m4_define([b4_percent_define_loc(]$1[)],
                       [[[[<skeleton default value>:-1.-1]],
                         [[<skeleton default value>:-1.-1]]]])dnl
               b4_percent_define_get_loc([$1]),
               [m4_case([$2],
                  [code], [[%%define variable '%s' requires '{...}' values]],
-                 [keyword], [[%%define variable '%s' requires '...' values]],
+                 [keyword], [[%%define variable '%s' requires keyword values]],
                  [string], [[%%define variable '%s' requires '"..."' values]])],
               [$1])])])dnl
 ])
 
 m4_define([_b4_percent_define_check_values],
 [b4_percent_define_ifdef_([$1],
-  [m4_pushdef([b4_good_value], [0])dnl
+  [b4_percent_define_check_kind(]$1[, [keyword], [deprecated])dnl
+   m4_pushdef([b4_good_value], [0])dnl
    m4_if($#, 1, [],
          [m4_foreach([b4_value], m4_dquote(m4_shift($@)),
                      [m4_if(m4_indir([b4_percent_define(]$1[)]), b4_value,
 
 @item Default Value:
 @code{false}
 @item History:
-introduced in Bison 2.8
+introduced in Bison 3.0
 @end itemize
 @end deffn
 @c api.token.constructor
 @end itemize
 
 @item History:
-introduced in Bison 2.8.  Was introduced for Java only in 2.3b as
+introduced in Bison 3.0.  Was introduced for Java only in 2.3b as
 @code{stype}.
 @end itemize
 @end deffn
 @item @code{most} otherwise.
 @end itemize
 @item History:
-introduced as @code{lr.default-reduction} in 2.5, renamed as
-@code{lr.default-reduction} in 2.8.
+introduced as @code{lr.default-reductions} in 2.5, renamed as
+@code{lr.default-reduction} in 3.0.
 @end itemize
 @end deffn
 
 @item History:
 introduced as @code{lr.keep_unreachable_states} in 2.3b, renamed as
 @code{lr.keep-unreachable-states} in 2.5, and as
-@code{lr.keep-unreachable-state} in 2.8.
+@code{lr.keep-unreachable-state} in 3.0.
 @end itemize
 @end deffn
 @c lr.keep-unreachable-state
 
   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)
 {
   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"))
         result = true;
       else if (STREQ (value, "false"))
   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;
       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)
 
 AT_CLEANUP
 
 
+## --------------------------- ##
+## %define keyword variables.  ##
+## --------------------------- ##
+
+AT_SETUP([["%define" keyword variables]])
+
+m4_pushdef([AT_TEST],
+[AT_DATA([input.y],
+[[%define api.pure                ]$1[true]$2[
+%define api.push-pull             ]$1[both]$2[
+%define lr.default-reduction      ]$1[most]$2[
+%define lr.keep-unreachable-state ]$1[true]$2[
+%define lr.type                   ]$1[lalr]$2[
+%%
+exp: %empty
+]])
+
+AT_BISON_CHECK([[input.y]], [0], [],
+[[input.y:5.9-15: warning: %define variable 'lr.type' requires keyword values [-Wdeprecated]
+input.y:3.9-28: warning: %define variable 'lr.default-reduction' requires keyword values [-Wdeprecated]
+input.y:4.9-33: warning: %define variable 'lr.keep-unreachable-state' requires keyword values [-Wdeprecated]
+input.y:2.9-21: warning: %define variable 'api.push-pull' requires keyword values [-Wdeprecated]
+input.y:1.9-16: warning: %define variable 'api.pure' requires keyword values [-Wdeprecated]
+]])
+])
+
+AT_TEST(["], ["])
+AT_TEST([{], [}])
+m4_popdef([AT_TEST])
+
+AT_CLEANUP
+
+
 ## ------------------------ ##
 ## %define enum variables.  ##
 ## ------------------------ ##