]> git.ipfire.org Git - thirdparty/autoconf.git/commitdiff
WIP: replace AS_ESCAPE and _AS_QUOTE with AS_QUOTE_D.
authorZack Weinberg <zack@owlfolio.org>
Wed, 26 Jun 2024 18:42:44 +0000 (14:42 -0400)
committerZack Weinberg <zack@owlfolio.org>
Wed, 26 Jun 2024 20:03:57 +0000 (16:03 -0400)
AS_ESCAPE is difficult or even impossible to use correctly,
depending on what you’re trying to do with it:

+ AS_ESCAPE([text], [`\"]) is intended to permit variable interpolation
  but prevent command substitution.  This does not work anymore,
  because we have $(...) command substitution nowadays, Solaris 10
  notwithstanding.
+ It is incorrect to leave \ or " out of the [chars] argument, but people
  do this all the dang time both in our own code and in third-party code.
+ It is incorrect to put anything in [chars] besides $ ` \ ", but people
  also do this.  In particular, 'AS_ESCAPE([text], [\'])' *does not*
  produce a correct single-quoted string, but at least one third-party
  m4 macro does this anyway.
+ In most cases you need to write AS_ESCAPE(m4_dquote(m4_expand([text])))
  or else M4 macros inside [text] will be expanded *after* the quotation
  process is complete, and the text they expand to won’t get escaped.
  *Our* code using AS_ESCAPE was diligent about this, but almost no
  no third-party uses bothered.
+ Almost all uses of AS_ESCAPE are constructing double-quoted strings,
  so it would be more ergonomic if it added the outermost quotes for you.

(All assertions about third-party code courtesy of
<http://codesearch.debian.net/search?q=%5CbAS_ESCAPE%5Cb+-pkg%3Aautoconf+-pkg%3Aautoconf2.69&literal=0>.)

The internal _AS_QUOTE macro is also almost impossible to use
correctly, for the above reasons plus the fact that it includes some
20-year-old *internal* backward compatibility logic that doesn’t
distinguish ${variable} from $(command) either.

Replace with a safer API:

+ New macro AS_QUOTE_S([text]) turns TEXT into a single-quoted string,
  *correctly* escaping embedded single quotes, and supplying an outer
  pair of quotes. Macros in TEXT are expanded once before quotation.

+ New macro AS_QUOTE_D([text], [interpolation]) turns TEXT into a
  double-quoted string, supplying an outer pair of quotes. Macros in
  TEXT are expanded once before quotation.

  The INTERPOLATION argument is a comma-separated list of keywords
  specifying what types of interpolation will be permitted:

    - ‘allow-vars’: Allow variable and arithmetic interpolation.
    - ‘allow-commands’: Allow command substitution.

  If the argument is empty or omitted, no interpolation is allowed.

  This is intentionally verbose and there is no ‘allow-all’, because
  I want people to think hard about if it’s really a good idea before
  enabling command substitution, and I want it to be easy to find all
  the place’s it’s being used.  (Almost all uses of AS_ESCAPE, both
  internally and externally, want either no interpolation or only
  variable interpolation.)  Both `...` and $(...) are handled correctly
  in all modes.

+ AS_ESCAPE is changed to map each of the valid possibilities for CHARS
  to the AS_QUOTE_D interpolation mode that was probably intended,
  papering over the bugs in third party macros.  (For example, both
  [`\"] and [`] by itself will be mapped to allow-vars mode, because
  we presume that someone who wrote [‘] didn’t think it through and
  there’s a latent bug.)  Invalid CHARS arguments now raise an error
  instead of silently doing something nonsensical.  All uses trigger
  a -Wobsolete warning.  AS_ESCAPE cannot be autoupdated to AS_QUOTE_D
  (because it’s an m4sh macro, and because autoupdate can’t edit out
  the surrounding quote marks) so instead I wrote a whole bunch of
  advice for manual conversion in the manual.

WIP: buggy (partially but not entirely because of the bugs in
m4_join_uniq); one internal use of AS_ESCAPE remains; need to
write tests and NEWS; unresolved design decisions including

+ should ‘allow-arithmetic’ be a third interpolation keyword, separate
  from ‘allow-vars’?
+ do we want a public API for escaping *without* adding surrounding
  quotes? Autotest was doing this but it didn’t particularly need to.
+ should the result of AS_QUOTE_[DS] be M4-quoted? Currently it isn’t.
+ is expand-once-then-quote [i.e. shellquote(m4_dquote(m4_expand([text])))]
  the most ergonomic behavior?

doc/autoconf.texi
lib/autoconf/autoheader.m4
lib/autoconf/general.m4
lib/autoconf/headers.m4
lib/autoconf/types.m4
lib/autotest/general.m4
lib/m4sugar/m4sh.m4

index a500de356a82d0786996f8d936927d0d12dbe15e..c806286657207e7456875dd2bddb66239fc9b44f 100644 (file)
@@ -13959,6 +13959,7 @@ namespaces.
 
 @menu
 * Common Shell Constructs::     Portability layer for common shell constructs
+* Shell Quotation::             Turning text into quoted strings
 * Polymorphic Variables::       Support for indirect variable names
 * Initialization Macros::       Macros to establish a sane shell environment
 * File Descriptor Macros::      File descriptor macros for input and output
@@ -14041,55 +14042,6 @@ for portability, should not include more than one newline.  The bytes of
 Redirections can be placed outside the macro invocation.
 @end defmac
 
-@c We cannot use @dvar because the macro expansion mistreats backslashes.
-@defmac AS_ESCAPE (@var{string}, @r{[}@var{chars} = @samp{`\"$}@r{]})
-@asindex{ESCAPE}
-Expands to @var{string}, with any characters in @var{chars} escaped with
-a backslash (@samp{\}).  @var{chars} should be at most four bytes long,
-and only contain characters from the set @samp{`\"$}; however,
-characters may be safely listed more than once in @var{chars} for the
-sake of syntax highlighting editors.  The current implementation expands
-@var{string} after adding escapes; if @var{string} contains macro calls
-that in turn expand to text needing shell quoting, you can use
-@code{AS_ESCAPE(m4_dquote(m4_expand([string])))}.
-
-The default for @var{chars} (@samp{\"$`}) is the set of characters
-needing escapes when @var{string} will be used literally within double
-quotes.  One common variant is the set of characters to protect when
-@var{string} will be used literally within back-ticks or an unquoted
-here-document (@samp{\$`}).  Another common variant is @samp{""}, which can
-be used to form a double-quoted string containing the same expansions
-that would have occurred if @var{string} were expanded in an unquoted
-here-document; however, when using this variant, care must be taken that
-@var{string} does not use double quotes within complex variable
-expansions (such as @samp{$@{foo-`echo "hi"`@}}) that would be broken
-with improper escapes.
-
-This macro is often used with @code{AS_ECHO}.  For an example, observe
-the output generated by the shell code generated from this snippet:
-
-@example
-foo=bar
-AS_ECHO(["AS_ESCAPE(["$foo" = ])AS_ESCAPE(["$foo"], [""])"])
-@result{}"$foo" = "bar"
-m4_define([macro], [a, [\b]])
-AS_ECHO(["AS_ESCAPE([[macro]])"])
-@result{}macro
-AS_ECHO(["AS_ESCAPE([macro])"])
-@result{}a, b
-AS_ECHO(["AS_ESCAPE(m4_dquote(m4_expand([macro])))"])
-@result{}a, \b
-@end example
-
-@comment Should we add AS_ESCAPE_SINGLE? If we do, we can optimize in
-@comment the case of @var{string} that does not contain '.
-To escape a string that will be placed within single quotes, use:
-
-@example
-m4_bpatsubst([[@var{string}]], ['], ['\\''])
-@end example
-@end defmac
-
 @defmac AS_EXECUTABLE_P (@var{file})
 @asindex{EXECUTABLE_P}
 Emit code to probe whether @var{file} is a regular file with executable
@@ -14202,6 +14154,79 @@ glibc (@pxref{String/Array Comparison, , String/Array Comparison, libc,
 The GNU C Library}).
 @end defmac
 
+@node Shell Quotation
+@section Shell Quotation
+
+M4sh provides two macros for fabricating shell quoted strings from text.
+
+@defmac AS_QUOTE_S (@var{string})
+@asindex{QUOTE_S}
+Convert @var{string} into a single-quoted shell string.
+@samp{'} within @var{string} will be mapped to @samp{'\''}.
+
+@var{string} is taken literally---it cannot be the contents of a shell
+variable.  M4 macros within @var{string} will be expanded, once, before
+quotation.
+@end defmac
+
+
+@defmac AS_QUOTE_D (@var{string}, @ovar{interpolation})
+@asindex{QUOTE_D}
+
+Convert @var{string} into a double-quoted shell string, escaping
+special characters with @samp{\} as necessary.  @var{string} is taken
+literally---it cannot be the contents of a shell variable.  M4 macros
+within @var{string} will be expanded, once, before quotation.
+
+The @var{interpolation} argument controls whether anything can be
+interpolated into the string when the shell script runs.  By default, or
+when this argument is empty, nothing will be interpolated.  The possible
+non-empty values for @var{interpolation} are:
+
+@table @code
+@item allow-vars
+Allow variables and arithmetic expressions to be interpolated.
+For example:
+@smallexample
+@group
+AWK=gawk
+echo AS_QUOTE_D([checking whether $AWK supports 'asorti'],
+                [allow-vars])
+  @expansion{} AWK=gawk
+  @expansion{} echo "checking whether $AWK supports 'asorti'"
+    @result{} checking whether gawk supports 'asorti'
+@end group
+@end smallexample
+@noindent
+Without @samp{allow-vars}, the output would instead be @samp{checking
+whether $AWK supports 'asorti'}.
+
+@strong{Compatibility note:} Arithmetic expressions are part of the
+POSIX shell standard, but are not universally implemented; see
+@ref{shell-arithmetic,, the discussion of @samp{$((@var{expression}))}}
+in @ref{Shell Substitutions}.  In Autoconf scripts it is better to use
+@code{AS_VAR_ARITH} and then interpolate the variable holding the
+result.
+
+@item allow-commands
+Allow execution of embedded commands, written either with
+@samp{`@var{command}`} or with @samp{$(@var{command})}.  (Even today,
+not all shells implement @samp{$(@var{command})}, so truly portable
+scripts still need to avoid it; but @emph{defensive} quotation requires
+us to assume that it @emph{does} work.)
+
+@strong{Caution:} Embedded commands that contain nested double-quoted
+strings will be mangled (handling them correctly is beyond M4's parsing
+capabilities).  Instead, assign the result of the command to a shell
+variable and use variable interpolation.
+
+@item allow-vars,allow-commands
+@itemx allow-commands,allow-vars
+Allow both of the above kinds of interpolation.
+@end table
+@end defmac
+
+
 @node Polymorphic Variables
 @section Support for indirect variable names
 @cindex variable name indirection
@@ -16961,6 +16986,7 @@ echo $(case x in x) echo hello;; esac)
 @end example
 
 
+@anchor{shell-arithmetic}
 @item $((@var{expression}))
 @cindex @code{$((@var{expression}))}
 Arithmetic expansion is not portable as some shells (most
@@ -24817,6 +24843,90 @@ This macro was renamed @code{AC_DECL_YYTEXT}, which in turn was
 integrated into @code{AC_PROG_LEX} (@pxref{AC_PROG_LEX}).
 @end defmac
 
+
+@defmac AS_ESCAPE (@var{string}, @ovar{chars})
+@asindex{ESCAPE}
+This macro has been superseded by @code{AS_QUOTE_D} and @code{AS_QUOTE_S}
+(@pxref{Shell Quotation}). @command{autoupdate} does not attempt to
+update uses of @code{AS_ESCAPE}, because the update will usually require
+modifying surrounding code, which @command{autoupdate} cannot do.
+
+Like @code{AS_QUOTE_D}, @code{AS_ESCAPE} escapes special characters for
+use inside shell double-quoted strings.  However, it is harder to use
+correctly: it does not put quotation marks around @var{string}, it does
+not expand M4 macros in @var{string} before escaping special characters,
+and many of the values of the @var{chars} argument will produce
+improperly quoted results for some strings.
+
+@samp{"AS_ESCAPE(m4_dquote(m4_expand([string])))"} is completely
+equivalent to @samp{AS_QUOTE_D([string])}.  Watch out for the
+surrounding quotation marks!  They were necessary with @code{AS_ESCAPE}
+but become incorrect with @code{AS_QUOTE_D}.
+
+In most cases, it is also correct to replace
+@samp{"AS_ESCAPE([string])"}, without the pre-expansion, with
+@samp{AS_QUOTE_D([string])}.  If @var{string} should not be expanded
+at all, use either @samp{AS_QUOTE_D([[string]])} or
+@samp{AS_QUOTE_D(m4_dquote([string]))}
+as appropriate.
+
+If you actually need M4 macros within @var{string} to be expanded
+and any special characters from the expansion @emph{not} to be escaped,
+split up the string like this:
+@samp{AS_QUOTE_D([prefix])"macro"AS_QUOTE_D([suffix])}.  This will
+produce @samp{"prefix""expanded text""suffix"} in the output; the
+doubled quote marks will be removed by the shell.
+
+Uses of @code{AS_ESCAPE} with the @var{chars} argument fall into
+four classes, as shown in the table below:
+
+@c Without the trailing hard spaces on the first line,
+@c the HTML render has no whitespace between the columns!
+@multitable {@code{`$"\@ @ `$"@ @ `$\@ @ `$@ @ @ }} {@samp{AS_QUOTE_D([...], [allow-commands,allow-vars])}}
+@headitem @var{chars} arg @tab Replace with@dots{}
+@item
+@t{`$"\@ @ `$"@ @ `$\@ @ `$@ @ @ }
+@tab
+@samp{AS_QUOTE_D([...])}
+@item
+@t{@ `"\@ @ @ `"@ @ @ `\@ @ @ `}
+@tab
+@samp{AS_QUOTE_D([...], [allow-vars])}
+@item
+@t{@ $"\@ @ @ $"@ @ @ $\@ @ @ $}
+@tab
+@samp{AS_QUOTE_D([...], [allow-commands])}
+@item
+@t{@ @ "\@ @ @ @ "@ @ @ @ \}
+@tab
+@samp{AS_QUOTE_D([...], [allow-commands,allow-vars])}
+@end multitable
+
+The order of characters within @var{chars} is not significant, and
+duplicate characters are ignored: @samp{"`} and @samp{`""} are both the
+same as @samp{`"} and should be mapped to @code{allow-vars}.
+
+Uses of @code{AS_ESCAPE} with any characters in @var{chars} besides
+@samp{"}, @samp{`}, @samp{$}, and/or @samp{\} are incorrect, and have no
+@code{AS_QUOTE_D} equivalent.  In particular, @code{AS_ESCAPE} cannot
+generate @emph{single-quoted} strings (but @code{AS_QUOTE_S} can).
+
+@strong{Compatibility Note:} Prior to Autoconf 2.73, the various
+possibilities for @var{chars} on each row of the table above were not
+equivalent.  The behavior of older @code{AS_ESCAPE} only matches the
+behavior of @code{AS_QUOTE_D} for the first possibility listed in each
+row.
+
+When @code{AS_ESCAPE} is used for its intended purpose of generating
+double-quoted shell strings, it is a @emph{bug} to use any of the other
+possibilities: they would cause it not to quote all @var{string}s
+correctly.  Therefore, in Autoconf 2.73 and later, @code{AS_ESCAPE}
+treats all the possibilities on each row as equivalent to the first.
+If you were using @code{AS_ESCAPE} off-label and really wanted the
+old behavior of @samp{AS_ESCAPE([@var{string}], [`$])} (for example),
+change it to @samp{m4_bpatsubst([@var{string}], [[`$]], [\\\&])}.
+@end defmac
+
 @node Autoconf 1
 @section Upgrading From Version 1
 @cindex Upgrading autoconf
index 99c0cf5e6db26d99dd3278c88df0d4d1864fe230..ba7d3f8ec460d0822da4a3327146375415605e16 100644 (file)
@@ -46,7 +46,7 @@ m4_define([AH_OUTPUT], [])
 # Quote for Perl '' strings, which are those used by Autoheader.
 m4_define([AH_VERBATIM],
 [AS_LITERAL_WORD_IF([$1],
-              [AH_OUTPUT(_m4_expand([$1]), AS_ESCAPE([[$2]], [\']))])])
+  [AH_OUTPUT(_m4_expand([$1]), m4_bpatsubst([[$2]], [[\']], [\\\&]))])])
 
 
 # AH_TEMPLATE(KEY, DESCRIPTION)
index f5ad64baf832a7a4b90200fc716094cb501edcb8..2953191b91c9238cdfd43d95373f9ee13f6b32e4 100644 (file)
@@ -2359,7 +2359,7 @@ m4_define([AC_DEFINE], [_AC_DEFINE_Q([_$0], $@)])
 # Append the pre-expanded STRING and a newline to confdefs.h, as if by
 # a quoted here-doc.
 m4_define([_AC_DEFINE],
-[AS_ECHO(["AS_ESCAPE([[$1]])"]) >>confdefs.h])
+[AS_ECHO([AS_QUOTE_D([[$1]])]) >>confdefs.h])
 
 
 # AC_DEFINE_UNQUOTED(VARIABLE, [VALUE], [DESCRIPTION])
@@ -2377,11 +2377,11 @@ m4_define([AC_DEFINE_UNQUOTED], [_AC_DEFINE_Q([_$0], $@)])
 # avoid AS_ECHO if "#" is present to avoid confusing m4 with comments,
 # but quadrigraphs are fine in that case.
 m4_define([_AC_DEFINE_UNQUOTED],
-[m4_if(m4_bregexp([$1], [#\|\\\|`\|\(\$\|@S|@\)\((|{|@{:@\)]), [-1],
-       [AS_ECHO(["AS_ESCAPE([$1], [""])"]) >>confdefs.h],
+[m4_bmatch([$1], [#\|\\\|`\|\(\$\|@S|@\)\((|{|@{:@\)],
        [cat >>confdefs.h <<_ACEOF
 [$1]
-_ACEOF])])
+_ACEOF],
+       [AS_ECHO([AS_QUOTE_D([$1])]) >>confdefs.h])])
 
 
 # _AC_DEFINE_Q(MACRO, VARIABLE, [VALUE], [DESCRIPTION])
@@ -2738,7 +2738,7 @@ AC_DEFUN([AC_TRY_COMMAND],
 # -------------------
 AC_DEFUN([AC_RUN_LOG],
 [_AC_RUN_LOG([$1],
-            [AS_ECHO(["$as_me:${as_lineno-$LINENO}: AS_ESCAPE([$1])"])])])
+            [AS_ECHO(["$as_me:${as_lineno-$LINENO}: "AS_QUOTE_D([$1])])])])
 
 
 
@@ -3187,7 +3187,8 @@ dnl Initialize each $ac_[]_AC_LANG_ABBREV[]_undeclared_builtin_options once.
 [AC_REQUIRE([_AC_UNDECLARED_BUILTIN_]_AC_LANG_ABBREV)]dnl
 [AS_VAR_PUSHDEF([ac_Symbol], [ac_cv_have_decl_$1])]dnl
 [ac_fn_check_decl ]dnl
-["$LINENO" "$1" "ac_Symbol" "AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])" ]dnl
+["$LINENO" "$1" "ac_Symbol" ]dnl
+[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars]) ]dnl
 ["$ac_[]_AC_LANG_ABBREV[]_undeclared_builtin_options" "_AC_LANG_PREFIX[]FLAGS"]
 [AS_VAR_IF([ac_Symbol], [yes], [$2], [$3])]dnl
 [AS_VAR_POPDEF([ac_Symbol])]dnl
@@ -3401,7 +3402,7 @@ AC_DEFUN([AC_COMPUTE_INT],
      be computed])],
     [_$0_BODY])]dnl
 [AS_IF([ac_fn_[]_AC_LANG_ABBREV[]_compute_int "$LINENO" "$2" "$1" ]dnl
-       ["AS_ESCAPE([$3], [""])"],
+       [AS_QUOTE_D([$3], [allow-commands,allow-vars])],
        [], [$4])
 ])# AC_COMPUTE_INT
 
index dec8538b3dfd9edcc1c984226a023805af56d1fa..15fc643f8b4c3a54c0c190597f5b52d0c7ac228d 100644 (file)
@@ -90,7 +90,8 @@ AC_DEFUN([_AC_CHECK_HEADER_COMPILE],
 [_AC_CHECK_HEADER_COMPILE_FN()]dnl
 [AS_VAR_PUSHDEF([ac_Header], [ac_cv_header_$1])]dnl
 [ac_fn_[]_AC_LANG_ABBREV[]_check_header_compile ]dnl
-["$LINENO" "$1" "ac_Header" "AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])"
+["$LINENO" "$1" "ac_Header" ]dnl
+[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars])
 AS_VAR_IF([ac_Header], [yes], [$2], [$3])
 AS_VAR_POPDEF([ac_Header])])# _AC_CHECK_HEADER_COMPILE
 
index f231fcfcd9b06fa9f995aede601015224d4ad163..ae66afffe89afea0516f98babcb3333894e785f8 100644 (file)
@@ -155,7 +155,7 @@ AC_DEFUN([_AC_CHECK_TYPE_NEW],
     [$0_BODY])]dnl
 [AS_VAR_PUSHDEF([ac_Type], [ac_cv_type_$1])]dnl
 [ac_fn_[]_AC_LANG_ABBREV[]_check_type "$LINENO" "$1" "ac_Type" ]dnl
-["AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])"
+[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars])
 AS_VAR_IF([ac_Type], [yes], [$2], [$3])
 AS_VAR_POPDEF([ac_Type])dnl
 ])# _AC_CHECK_TYPE_NEW
@@ -944,7 +944,7 @@ AC_DEFUN([AC_CHECK_MEMBER],
 [AS_VAR_PUSHDEF([ac_Member], [ac_cv_member_$1])]dnl
 [ac_fn_[]_AC_LANG_ABBREV[]_check_member "$LINENO" ]dnl
 [m4_bpatsubst([$1], [^\([^.]*\)\.\(.*\)], ["\1" "\2"]) "ac_Member" ]dnl
-["AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])"
+[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars])
 AS_VAR_IF([ac_Member], [yes], [$2], [$3])
 AS_VAR_POPDEF([ac_Member])dnl
 ])# AC_CHECK_MEMBER
index a0668dde22abe5c37fd9e7728c99ded332412b81..4825bc60f6296171001293f7aa4d91a4fe0cb4d1 100644 (file)
@@ -153,7 +153,7 @@ m4_defn([_AT_LINE_base]):__line__])
 # _AT_LINE_ESCAPED
 # ----------------
 # Same as AT_LINE, but already escaped for the shell.
-m4_define([_AT_LINE_ESCAPED], ["AS_ESCAPE(m4_dquote(AT_LINE))"])
+m4_define([_AT_LINE_ESCAPED], [AS_QUOTE_D([AT_LINE])])
 
 
 # _AT_NORMALIZE_TEST_GROUP_NUMBER(SHELL-VAR)
@@ -449,7 +449,7 @@ at_color=m4_ifdef([AT_color], [AT_color], [no])
 # numerical order.
 at_format='m4_bpatsubst(m4_defn([AT_ordinal]), [.], [?])'
 # Description of all the test groups.
-at_help_all="AS_ESCAPE(m4_dquote(m4_defn([AT_help_all])))"
+at_help_all=AS_QUOTE_D(m4_defn([AT_help_all]))
 # List of the all the test groups.
 at_groups_all=`AS_ECHO(["$at_help_all"]) | sed 's/;.*//'`
 
@@ -1891,7 +1891,7 @@ m4_define([AT_ordinal], m4_incr(AT_ordinal))
 m4_divert_push([TEST_GROUPS])dnl
 [#AT_START_]AT_ordinal
 at_fn_group_banner AT_ordinal 'm4_defn([AT_line])' \
-  "AS_ESCAPE(m4_dquote(m4_defn([AT_description])))" m4_format(["%*s"],
+  AS_QUOTE_D(m4_dquote(m4_defn([AT_description]))) m4_format(["%*s"],
   m4_max(0, m4_eval(47 - m4_qlen(m4_defn([AT_description])))), [])m4_if(
   AT_banner_ordinal, [0], [], [ AT_banner_ordinal])
 m4_ifset([AT_prepare_each_test], [AT_prepare_each_test
@@ -2001,7 +2001,7 @@ m4_define([AT_banner_ordinal], m4_incr(AT_banner_ordinal))
 m4_divert_text([BANNERS],
 [@%:@ Banner AT_banner_ordinal. AT_LINE
 @%:@ Category starts at test group m4_incr(AT_ordinal).
-at_banner_text_[]AT_banner_ordinal="AS_ESCAPE([$1])"])dnl
+at_banner_text_[]AT_banner_ordinal=AS_QUOTE_D([$1])])dnl
 ])# AT_BANNER
 
 
@@ -2076,17 +2076,21 @@ $2[]_ATEOF
 # This may cause spurious failures when the test suite is run with '-x'.
 #
 _AT_DEFINE_SETUP([AT_CHECK],
-[_AT_CHECK(m4_expand([$1]), [$2], AS_ESCAPE(m4_dquote(m4_expand([$3]))),
-  AS_ESCAPE(m4_dquote(m4_expand([$4]))), [$5], [$6])])
+[_AT_CHECK(m4_expand([$1]), [$2],
+  AS_QUOTE_D([$3]),
+  AS_QUOTE_D([$4]),
+  [$5], [$6])])
 
 # AT_CHECK_UNQUOTED(COMMANDS, [STATUS = 0], STDOUT, STDERR,
 #                   [RUN-IF-FAIL], [RUN-IF-PASS])
 # ---------------------------------------------------------
-# Like AT_CHECK, but do not AS_ESCAPE shell metacharacters in the STDOUT
+# Like AT_CHECK, but perform string interpolations in the STDOUT
 # and STDERR arguments before running the comparison.
 _AT_DEFINE_SETUP([AT_CHECK_UNQUOTED],
-[_AT_CHECK(m4_expand([$1]), [$2], AS_ESCAPE(m4_dquote(m4_expand([$3])), [""]),
-  AS_ESCAPE(m4_dquote(m4_expand([$4])), [""]), [$5], [$6])])
+[_AT_CHECK(m4_expand([$1]), [$2],
+  AS_QUOTE_D([$3], [allow-commands,allow-vars]),
+  AS_QUOTE_D([$4], [allow-commands,allow-vars]),
+  [$5], [$6])])
 
 # AT_CHECK_NOESCAPE(COMMANDS, [STATUS = 0], STDOUT, STDERR,
 #                   [RUN-IF-FAIL], [RUN-IF-PASS])
@@ -2194,7 +2198,7 @@ dnl We know at build time that tracing COMMANDS is never safe.
 dnl We know at build time that tracing COMMANDS is always safe.
 [[at_fn_check_prepare_trace],]dnl
 dnl COMMANDS may contain parameter expansions; expand them at runtime.
-[[at_fn_check_prepare_dynamic "AS_ESCAPE([[$1]], [`\"])"])[]]dnl
+[[at_fn_check_prepare_dynamic AS_QUOTE_D([[$1]], [allow-vars])])[]]dnl
 [_m4_popdef([at_reason])])
 
 
@@ -2202,28 +2206,28 @@ dnl COMMANDS may contain parameter expansions; expand them at runtime.
 # -----------------------------
 # These are subroutines of AT_CHECK.  Using indirect dispatch is a tad
 # faster than using m4_case, and these are called very frequently.
-m4_define([AT_DIFF_STDERR(stderr)],
+m4_define([AT_DIFF_STDERR("stderr")],
          [echo stderr:; tee stderr <"$at_stderr"])
-m4_define([AT_DIFF_STDERR(stderr-nolog)],
+m4_define([AT_DIFF_STDERR("stderr-nolog")],
          [echo stderr captured; cp "$at_stderr" stderr])
-m4_define([AT_DIFF_STDERR(ignore)],
+m4_define([AT_DIFF_STDERR("ignore")],
          [echo stderr:; cat "$at_stderr"])
-m4_define([AT_DIFF_STDERR(ignore-nolog)])
-m4_define([AT_DIFF_STDERR(experr)],
+m4_define([AT_DIFF_STDERR("ignore-nolog")])
+m4_define([AT_DIFF_STDERR("experr")],
          [$at_diff experr "$at_stderr" || at_failed=:])
-m4_define([AT_DIFF_STDERR()],
+m4_define([AT_DIFF_STDERR("")],
          [at_fn_diff_devnull "$at_stderr" || at_failed=:])
 
-m4_define([AT_DIFF_STDOUT(stdout)],
+m4_define([AT_DIFF_STDOUT("stdout")],
          [echo stdout:; tee stdout <"$at_stdout"])
-m4_define([AT_DIFF_STDOUT(stdout-nolog)],
+m4_define([AT_DIFF_STDOUT("stdout-nolog")],
          [echo stdout captured; cp "$at_stdout" stdout])
-m4_define([AT_DIFF_STDOUT(ignore)],
+m4_define([AT_DIFF_STDOUT("ignore")],
          [echo stdout:; cat "$at_stdout"])
-m4_define([AT_DIFF_STDOUT(ignore-nolog)])
-m4_define([AT_DIFF_STDOUT(expout)],
+m4_define([AT_DIFF_STDOUT("ignore-nolog")])
+m4_define([AT_DIFF_STDOUT("expout")],
          [$at_diff expout "$at_stdout" || at_failed=:])
-m4_define([AT_DIFF_STDOUT()],
+m4_define([AT_DIFF_STDOUT("")],
          [at_fn_diff_devnull "$at_stdout" || at_failed=:])
 
 # _AT_CHECK(COMMANDS, [STATUS = 0], STDOUT, STDERR,
@@ -2263,17 +2267,17 @@ m4_define([AT_DIFF_STDOUT()],
 m4_define([_AT_CHECK],
 [m4_define([AT_ingroup])]dnl
 [{ set +x
-AS_ECHO(["$at_srcdir/AT_LINE: AS_ESCAPE([[$1]])"])
+AS_ECHO(["$at_srcdir/AT_LINE: "AS_QUOTE_D([[$1]])])
 _AT_DECIDE_TRACEABLE([$1]) _AT_LINE_ESCAPED
 ( $at_check_trace; [$1]
 ) >>"$at_stdout" 2>>"$at_stderr" AS_MESSAGE_LOG_FD>&-
 at_status=$? at_failed=false
 $at_check_filter
 m4_ifdef([AT_DIFF_STDERR($4)], [m4_indir([AT_DIFF_STDERR($4)])],
-  [echo >>"$at_stderr"; AS_ECHO([["$4"]]) | \
+  [echo >>"$at_stderr"; AS_ECHO([[$4]]) | \
   $at_diff - "$at_stderr" || at_failed=:])
 m4_ifdef([AT_DIFF_STDOUT($3)], [m4_indir([AT_DIFF_STDOUT($3)])],
-  [echo >>"$at_stdout"; AS_ECHO([["$3"]]) | \
+  [echo >>"$at_stdout"; AS_ECHO([[$3]]) | \
   $at_diff - "$at_stdout" || at_failed=:])
 m4_if([$2], [ignore], [at_fn_check_skip],
   [at_fn_check_status m4_default([$2], [0])]) $at_status "$at_srcdir/AT_LINE"
index ba6d54098acd1afef9ff4724e945ec12513a4e0e..4538a3d4b0ea825a6b247bb0bbac36cd3d01c790 100644 (file)
@@ -222,7 +222,7 @@ dnl Remove any tests from suggested that are also required
 [m4_set_map([_AS_DETECT_SUGGESTED_BODY], [_AS_DETECT_SUGGESTED_PRUNE])]dnl
 [m4_pushdef([AS_EXIT], [exit m4_default(]m4_dquote([$][1])[, 1)])]dnl
 [if test "x$CONFIG_SHELL" = x; then
-  as_bourne_compatible="AS_ESCAPE(_m4_expand([_AS_BOURNE_COMPATIBLE]))"
+  as_bourne_compatible=AS_QUOTE_D([_AS_BOURNE_COMPATIBLE])
   _AS_DETECT_EXPAND([as_required], [_AS_DETECT_REQUIRED_BODY])
   _AS_DETECT_EXPAND([as_suggested], [_AS_DETECT_SUGGESTED_BODY])
   AS_IF([_AS_RUN(["$as_required"])],
@@ -763,69 +763,109 @@ m4_define([AS_MESSAGE_LOG_FD])
 # shuffle fd's.
 m4_define([AS_ORIGINAL_STDIN_FD], [0])
 
-
-# AS_ESCAPE(STRING, [CHARS = `\"$])
+# AS_QUOTE_S(TEXT)
+# ----------------
+# Quote TEXT for the shell, using single quotes.  Expand macros within
+# the text prior to quotation.
+#
+# With GNU m4 1.4.19, avoiding a call to m4_bpatsubst when it would
+# have nothing to do is good for a ~2.5x speedup.
+m4_define([AS_QUOTE_S], ['_$0(m4_dquote(m4_expand([$1])))'])
+m4_define([_AS_QUOTE_S],
+[m4_if([m4_index([$1], ['])], [-1],
+  [$1],
+  [m4_bpatsubst([$1], ['], ['\''])])])
+
+# AS_QUOTE_D(TEXT, [INTERPOLATION])
 # ---------------------------------
-# Add backslash escaping to the CHARS in STRING.  In an effort to
-# optimize use of this macro inside double-quoted shell constructs,
-# the behavior is intentionally undefined if CHARS is longer than 4
-# bytes, or contains bytes outside of the set [`\"$].  However,
-# repeated bytes within the set are permissible (AS_ESCAPE([$1], [""])
-# being a common way to be nice to syntax highlighting).
-#
-# Avoid the m4_bpatsubst if there are no interesting characters to escape.
-# _AS_ESCAPE bypasses argument defaulting.
+# Quote TEXT for the shell, using double quotes.  Expand macros within
+# the text prior to quotation.  INTERPOLATION says what kinds of
+# interpolations to allow:
+#
+#  - blank/absent:   no interpolation
+#  - allow-vars:     allow interpolation of $var ${var} $((expr))
+#  - allow-commands: allow interpolation of `command` $(command)
+#  - allow-commands,allow-vars
+#    allow-vars,allow-commands
+#      allow both the above kinds of interpolation
+#      (there is intentionally no 'allow-all' because we want
+#      people to be able to find all cases where command interpolation
+#      is enabled by grepping for 'allow-commands').
+m4_define([AS_QUOTE_D],
+["_$0_QUOTE(m4_dquote(m4_expand([$1])), _$0_IARGCHECK([$2]))"])
+
+# _AS_QUOTE_D_IARGCHECK(INTERPOLATION)
+# ------------------------------------
+# Evaluate the INTERPOLATION argument to AS_QUOTE_D.  If it is valid,
+# return a list of the second, third, fourth, and fifth arguments to
+# _AS_QUOTE_D_QUOTE, otherwise issue an error.
+m4_define([_AS_QUOTE_D_ARGCHECK],
+[m4_case(m4_translit([[$1]], [ ][ ][
+]),
+  [],                          [[$], [\"`], [$$$], [[\"$`]]],
+  [allow-vars],                [[$], [\"`], [$$$], [[\"`]|\$(\($|[^(]\)]],
+  [allow-commands],            [[$], [\"], [$$], [[\"]\|\$[a-zA-Z_{]\|\$((]],
+  [allow-commands,allow-vars], [["], [\], ["], [[\"]]],
+  [allow-vars,allow-commands], [["], [\], ["], [[\"]]],
+  [m4_fatal([invalid INTERPOLATION argument '$1' to AS_QUOTE_D])])])
+
+# _AS_QUOTE_D_QUOTE(STRING, C, HARS, CCCC, REGEX)
+# -----------------------------------------------
+# Insert a backslash in STRING before every match for REGEX.
+# C, HARS, and CCCC are hints for a performance hack: if C is not
+# present in STRING after transliterating HARS to CCCC, then
+# REGEX is assumed not to match anywhere within STRING.
+# (As noted for AS_QUOTE_S, avoiding calls to m4_bpatsubst is good
+# for a ~2.5x speedup even when the overhead of the translit and index
+# are accounted for.
+m4_define([_AS_QUOTE_D_QUOTE],
+[m4_if(m4_index(m4_if([$3], [], [$1], [m4_translit([[$1]], [$3], [$4])]), [$2]),
+       [-1],
+       [$1],
+       [m4_bpatsubst([$1], [$5], [\\\&])])])
+
+# AS_ESCAPE(STRING, [CHARS])
+# --------------------------
+# Superseded by AS_QUOTE_D.  The differences are:
+#  - Does not wrap " ... " around the result.
+#  - Does not pre-expand the string.
+#  - CHARS is a much clumsier way to specify what interpolation to allow.
+#    Almost all existing uses of AS_ESCAPE with a non-default CHARS
+#    argument are incorrect per the original implementation.
+#    This compat stub attempts to map each possible value of CHARS
+#    (plus one more that wasn't supposed to happen at all) to an
+#    AS_QUOTE_D/S invocation that will do what was intended.
 m4_define([AS_ESCAPE],
-[_$0([$1], m4_if([$2], [], [[`], [\"$]], [m4_substr([$2], [0], [1]), [$2]]))])
-
-# _AS_ESCAPE(STRING, KEY, SET)
-# ----------------------------
-# Backslash-escape all instances of the single byte KEY or up to four
-# bytes in SET occurring in STRING.  Although a character can occur
-# multiple times, optimum efficiency occurs when KEY and SET are
-# distinct, and when SET does not exceed two bytes.  These particular
-# semantics allow for the fewest number of parses of STRING, as well
-# as taking advantage of the optimizations in m4 1.4.13+ when
-# m4_translit is passed SET of size 2 or smaller.
-m4_define([_AS_ESCAPE],
-[m4_if(m4_index(m4_translit([[$1]], [$3], [$2$2$2$2]), [$2]), [-1],
-       [$0_], [m4_bpatsubst])([$1], [[$2$3]], [\\\&])])
-m4_define([_AS_ESCAPE_], [$1])
-
-
-# _AS_QUOTE(STRING)
-# -----------------
-# If there are quoted (via backslash) backquotes, output STRING
-# literally and warn; otherwise, output STRING with ` and " quoted.
-#
-# Compatibility glue between the old AS_MSG suite which did not
-# quote anything, and the modern suite which quotes the quotes.
-# If STRING contains '\\' or '\$', it's modern.
-# If STRING contains '\"' or '\`', it's old.
-# Otherwise it's modern.
-#
-# Profiling shows that m4_index is 5 to 8x faster than m4_bregexp.  The
-# slower implementation used:
-# m4_bmatch([$1],
-#          [\\[\\$]], [$2],
-#          [\\[`"]], [$3],
-#          [$2])
-# The current implementation caters to the common case of no backslashes,
-# to minimize m4_index expansions (hence the nested if).
-m4_define([_AS_QUOTE],
-[m4_cond([m4_index([$1], [\])], [-1], [_AS_QUOTE_MODERN],
-        [m4_eval(m4_index(m4_translit([[$1]], [$], [\]), [\\]) >= 0)],
-[1], [_AS_QUOTE_MODERN],
-        [m4_eval(m4_index(m4_translit([[$1]], ["], [`]), [\`]) >= 0)],dnl"
-[1], [_AS_QUOTE_OLD],
-        [_AS_QUOTE_MODERN])([$1])])
-
-m4_define([_AS_QUOTE_MODERN],
-[_AS_ESCAPE([$1], [`], [""])])
-
-m4_define([_AS_QUOTE_OLD],
-[m4_warn([obsolete],
-   [back quotes and double quotes must not be escaped in: $1])$1])
+[m4_warn([obsolete], [AS_ESCAPE is deprecated, change to AS_QUOTE_D or _S])]dnl
+dnl Handle the most common case without going through _AS_ESCAPE_MAPCHARS,
+dnl which is expensive.
+dnl AS_ESCAPE was never intended to produce single-quoted strings but
+dnl someone tried to use it that way anyway!
+dnl https://sources.debian.org/src/fakechroot/2.20.1+ds-17/m4/check_func_argtypes.m4/?hl=111#L111
+[m4_case([$2],
+  [],   [_AS_QUOTE_D_QUOTE([$1], _AS_QUOTE_D_ARGCHECK([]))],
+  ['],  [_AS_QUOTE_S([$1])],
+  [''], [_AS_QUOTE_S([$1])],
+  [_AS_QUOTE_D_QUOTE([$1], _AS_QUOTE_D_ARGCHECK([]))])])
+#  [_AS_QUOTE_D_QUOTE([$1], _AS_QUOTE_D_IARGCHECK(_AS_ESCAPE_MAPCHARS([$2])))])])
+
+# The characters could be in any order and they could also be
+# duplicated.  M4 + M4sugar does have a "remove duplicate from list"
+# mechanism, but it doesn't have "sort list" at all.
+# m4_define([_AS_ESCAPE_MAPCHARS],
+# [__AS_ESCAPE_MAPCHARS(m4_join_uniq([], m4_bpatsubst([$1], [.], [[\&],])))])
+# m4_define([__AS_ESCAPE_MAPCHARS],
+# [[[$1]] m4_bmatch([$1],
+#   [^\([$`][$`]\|[$`"][$`"][$`"]\|[$\`][$\`][$\`]\|[$`\"][$`\"][$`\"][$`\"]\)$],
+#     [],
+#   [^\([$]\|[\$][\$]\|["$]["$]\|[\"$][\"$][\"$]\)$],
+#     [allow-commands],
+#   [^\([`]\|[\`][\`]\|["`]["`]\|[\"`][\"`][\"`]\)$],
+#     [allow-vars],
+#   [^\([\\]|["]|[\"][\"]\)$],
+#     [allow-commands,allow-vars],
+#     [invalid])])
+#  [m4_fatal([invalid CHARS argument '$1' to AS_ESCAPE])])])
 
 
 # _AS_ECHO_UNQUOTED(STRING, [FD = AS_MESSAGE_FD])
@@ -837,9 +877,17 @@ m4_define([_AS_ECHO_UNQUOTED],
 
 # _AS_ECHO(STRING, [FD = AS_MESSAGE_FD])
 # --------------------------------------
-# Protect STRING from backquote expansion, echo the result to FD.
+# Protect STRING from backquote expansion but not variable expansion,
+# echo the result to FD.
 m4_define([_AS_ECHO],
-[_AS_ECHO_UNQUOTED([_AS_QUOTE([$1])], [$2])])
+[AS_ECHO([AS_QUOTE_D([$1], [allow-vars])]) >&m4_default([$2], [AS_MESSAGE_FD])])
+
+
+# _AS_ECHO_N(STRING, [FD = AS_MESSAGE_FD])
+# ----------------------------------------
+# Same as _AS_ECHO, but echo doesn't return to a new line.
+m4_define([_AS_ECHO_N],
+[AS_ECHO_N([AS_QUOTE_D([$1], [allow-vars])]) >&m4_default([$2], [AS_MESSAGE_FD])])
 
 
 # _AS_ECHO_LOG(STRING)
@@ -850,13 +898,6 @@ m4_defun_init([_AS_ECHO_LOG],
 [_AS_ECHO([$as_me:${as_lineno-$LINENO}: $1], AS_MESSAGE_LOG_FD)])
 
 
-# _AS_ECHO_N(STRING, [FD = AS_MESSAGE_FD])
-# ----------------------------------------
-# Same as _AS_ECHO, but echo doesn't return to a new line.
-m4_define([_AS_ECHO_N],
-[AS_ECHO_N(["_AS_QUOTE([$1])"]) >&m4_default([$2], [AS_MESSAGE_FD])])
-
-
 # AS_MESSAGE(STRING, [FD = AS_MESSAGE_FD])
 # ----------------------------------------
 # Output "`basename $0`: STRING" to the open file FD, and if logging
@@ -913,7 +954,7 @@ _m4_popdef([AS_MESSAGE_LOG_FD])dnl
 m4_defun_init([AS_ERROR],
 [m4_append_uniq([_AS_CLEANUP],
   [m4_divert_text([M4SH-INIT-FN], [_AS_ERROR_PREPARE[]])])],
-[as_fn_error m4_default([$2], [$?]) "_AS_QUOTE([$1])"m4_ifval(AS_MESSAGE_LOG_FD,
+[as_fn_error m4_default([$2], [$?]) AS_QUOTE_D([$1], [allow-vars])m4_ifval(AS_MESSAGE_LOG_FD,
   [ "$LINENO" AS_MESSAGE_LOG_FD])])
 
 
@@ -1462,8 +1503,7 @@ m4_define([_AS_BOX],
 # _AS_BOX_LITERAL(MESSAGE, [FRAME-CHARACTER = '-'])
 # -------------------------------------------------
 m4_define([_AS_BOX_LITERAL],
-[AS_ECHO(["_AS_ESCAPE(m4_dquote(m4_expand([m4_text_box($@)])), [`], [\"$])"])])
-
+[AS_ECHO([AS_QUOTE_D([m4_text_box($@)])])])
 
 # _AS_BOX_INDIR(MESSAGE, [FRAME-CHARACTER = '-'])
 # -----------------------------------------------
@@ -1886,7 +1926,7 @@ m4_define([_AS_TR_SH_LITERAL],
   [pp[]]]m4_dquote(m4_for(,1,255,,[[_]]))[)])
 
 m4_define([_AS_TR_SH_INDIR],
-[`AS_ECHO(["_AS_ESCAPE([[$1]], [`], [\])"]) | sed "$as_sed_sh"`])
+[`AS_ECHO([AS_QUOTE_D([$1], [allow-vars])]) | sed "$as_sed_sh"`])
 
 
 # _AS_TR_CPP_PREPARE
@@ -1920,7 +1960,7 @@ m4_define([_AS_TR_CPP_LITERAL],
   [P[]]]m4_dquote(m4_defn([m4_cr_LETTERS])m4_for(,1,255,,[[_]]))[)])
 
 m4_define([_AS_TR_CPP_INDIR],
-[`AS_ECHO(["_AS_ESCAPE([[$1]], [`], [\])"]) | sed "$as_sed_cpp"`])
+[`AS_ECHO([AS_QUOTE_D([$1], [allow-vars])]) | sed "$as_sed_cpp"`])
 
 
 # _AS_TR_PREPARE
@@ -1959,7 +1999,7 @@ m4_defun([_AS_VAR_APPEND_PREPARE],
 VAR.  Take advantage of any shell optimizations that allow amortized
 linear growth over repeated appends, instead of the typical quadratic
 growth present in naive implementations.])
-AS_IF([_AS_RUN(["AS_ESCAPE(m4_quote(_AS_VAR_APPEND_WORKS))"])],
+AS_IF([_AS_RUN([AS_QUOTE_D([_AS_VAR_APPEND_WORKS])])],
 [eval 'as_fn_append ()
   {
     eval $[]1+=\$[]2
@@ -2000,7 +2040,7 @@ m4_defun([_AS_VAR_ARITH_PREPARE],
 [Perform arithmetic evaluation on the ARGs, and store the result in
 the global $as_val.  Take advantage of shells that can avoid forks.
 The arguments must be portable across $(()) and expr.])
-AS_IF([_AS_RUN(["AS_ESCAPE(m4_quote(_AS_VAR_ARITH_WORKS))"])],
+AS_IF([_AS_RUN([AS_QUOTE_D([_AS_VAR_ARITH_WORKS])])],
 [eval 'as_fn_arith ()
   {
     as_val=$(( $[]* ))
@@ -2048,7 +2088,7 @@ m4_define([AS_VAR_COPY],
 m4_define([AS_VAR_GET],
 [AS_LITERAL_WORD_IF([$1],
               [$$1],
-  [`eval 'as_val=${'_AS_ESCAPE([[$1]], [`], [\])'};AS_ECHO(["$as_val"])'`])])
+  [`eval 'as_val=\$$2;AS_ECHO(["$as_val"])'`])])
 
 
 # AS_VAR_IF(VARIABLE, VALUE, IF-TRUE, IF-FALSE)
@@ -2061,7 +2101,7 @@ m4_define([AS_VAR_IF],
   [AS_VAR_COPY([as_val], [$1])
    AS_IF(m4_ifval([$2], [[test "x$as_val" = x[]$2]], [[${as_val:+false} :]])],
   [AS_IF(m4_ifval([$2],
-    [[eval test \"x\$"$1"\" = x"_AS_ESCAPE([$2], [`], [\"$])"]],
+    [[eval test \"x\$"$1"\" = x[]AS_QUOTE_D([$2])]],
     [[eval \${$1:+false} :]])]),
 [$3], [$4])])
 
@@ -2128,7 +2168,7 @@ m4_pushdef([$1], [$as_[$1]])],
 m4_define([AS_VAR_SET],
 [AS_LITERAL_WORD_IF([$1],
               [$1=$2],
-              [eval "$1=_AS_ESCAPE([$2], [`], [\"$])"])])
+              [eval "$1="AS_QUOTE_D([$2])])])
 
 
 # AS_VAR_SET_IF(VARIABLE, IF-TRUE, IF-FALSE)