## Error handling. ##
## ---------------- ##
+# The following error handling macros print error directives that should not
+# become arguments of other macro invocations since they would likely then be
+# mangled. Thus, they print to stdout directly.
+
+# b4_cat(TEXT)
+# ------------
+# Write TEXT to stdout. Precede the final newline with an @ so that it's
+# escaped. For example:
+#
+# b4_cat([[@complain(invalid input@)]])
+m4_define([b4_cat],
+[m4_syscmd([cat <<'_m4eof'
+]m4_bpatsubst(m4_dquote($1), [_m4eof], [_m4@`eof])[@
+_m4eof])dnl
+m4_if(m4_sysval, [0], [], [m4_fatal([$0: cannot write to stdout])])])
+
# b4_error(KIND, FORMAT, [ARG1], [ARG2], ...)
# -------------------------------------------
-# Write @KIND(FORMAT@,ARG1@,ARG2@,...@) to diversion 0.
+# Write @KIND(FORMAT@,ARG1@,ARG2@,...@) to stdout.
+#
+# For example:
+#
+# b4_error([[warn]], [[invalid value for `%s': %s]], [[foo]], [[3]])
m4_define([b4_error],
-[m4_divert_push(0)[@]$1[(]$2[]m4_if([$#], [2], [],
-[m4_foreach([b4_arg],
- m4_dquote(m4_shift(m4_shift($@))),
- [[@,]b4_arg])])[@)]m4_divert_pop(0)])
+[b4_cat([[@]$1[(]$2[]]dnl
+[m4_if([$#], [2], [],
+ [m4_foreach([b4_arg],
+ m4_dquote(m4_shift(m4_shift($@))),
+ [[@,]b4_arg])])[@)]])])
# b4_error_at(KIND, START, END, FORMAT, [ARG1], [ARG2], ...)
# ----------------------------------------------------------
-# Write @KIND(START@,END@,FORMAT@,ARG1@,ARG2@,...@) to diversion 0.
+# Write @KIND_at(START@,END@,FORMAT@,ARG1@,ARG2@,...@) to stdout.
+#
+# For example:
+#
+# b4_error_at([[complain]], [[input.y:2.3]], [[input.y:5.4]],
+# [[invalid %s]], [[foo]])
m4_define([b4_error_at],
-[m4_divert_push(0)[@]$1[_at(]$2[@,]$3[@,]$4[]m4_if([$#], [4], [],
-[m4_foreach([b4_arg],
- m4_dquote(m4_shift(m4_shift(m4_shift(m4_shift($@))))),
- [[@,]b4_arg])])[@)]m4_divert_pop(0)])
+[b4_cat([[@]$1[_at(]$2[@,]$3[@,]$4[]]dnl
+[m4_if([$#], [4], [],
+ [m4_foreach([b4_arg],
+ m4_dquote(m4_shift(m4_shift(m4_shift(m4_shift($@))))),
+ [[@,]b4_arg])])[@)]])])
# b4_warn(FORMAT, [ARG1], [ARG2], ...)
# ------------------------------------
-# Write @warn(FORMAT@,ARG1@,ARG2@,...@) to diversion 0.
+# Write @warn(FORMAT@,ARG1@,ARG2@,...@) to stdout.
+#
+# For example:
+#
+# b4_warn([[invalid value for `%s': %s]], [[foo]], [[3]])
#
# As a simple test suite, this:
#
+# m4_divert(-1)
# m4_define([asdf], [ASDF])
# m4_define([fsa], [FSA])
# m4_define([fdsa], [FDSA])
# b4_warn([[[asdf), asdf]]], [[[fsa), fsa]]], [[[fdsa), fdsa]]])
-# m4_divert(0)
# b4_warn([[asdf), asdf]], [[fsa), fsa]], [[fdsa), fdsa]])
-# m4_divert(0)
-# b4_warn([asdf), asdf], [fsa), fsa], [fdsa), fdsa])
-# m4_divert(0)
# b4_warn()
-# m4_divert(0)
# b4_warn(1)
-# m4_divert(0)
# b4_warn(1, 2)
#
-# Should produce this:
+# Should produce this without newlines:
#
# @warn([asdf), asdf]@,[fsa), fsa]@,[fdsa), fdsa]@)
# @warn(asdf), asdf@,fsa), fsa@,fdsa), fdsa@)
-# @warn(ASDF), ASDF@,FSA), FSA@,FDSA), FDSA@)
# @warn(@)
# @warn(1@)
# @warn(1@,2@)
# b4_warn_at(START, END, FORMAT, [ARG1], [ARG2], ...)
# ---------------------------------------------------
-# Write @warn(START@,END@,FORMAT@,ARG1@,ARG2@,...@) to diversion 0.
+# Write @warn(START@,END@,FORMAT@,ARG1@,ARG2@,...@) to stdout.
+#
+# For example:
+#
+# b4_warn_at([[input.y:2.3]], [[input.y:5.4]], [[invalid %s]], [[foo]])
m4_define([b4_warn_at],
[b4_error_at([[warn]], $@)])
# b4_complain(FORMAT, [ARG1], [ARG2], ...)
# ----------------------------------------
-# Write @complain(FORMAT@,ARG1@,ARG2@,...@) to diversion 0.
+# Write @complain(FORMAT@,ARG1@,ARG2@,...@) to stdout.
#
-# See the test suite for b4_warn above.
+# See b4_warn example.
m4_define([b4_complain],
[b4_error([[complain]], $@)])
# b4_complain_at(START, END, FORMAT, [ARG1], [ARG2], ...)
# -------------------------------------------------------
-# Write @complain(START@,END@,FORMAT@,ARG1@,ARG2@,...@) to diversion 0.
+# Write @complain(START@,END@,FORMAT@,ARG1@,ARG2@,...@) to stdout.
+#
+# See b4_warn_at example.
m4_define([b4_complain_at],
[b4_error_at([[complain]], $@)])
# b4_fatal(FORMAT, [ARG1], [ARG2], ...)
# -------------------------------------
-# Write @fatal(FORMAT@,ARG1@,ARG2@,...@) to diversion 0.
+# Write @fatal(FORMAT@,ARG1@,ARG2@,...@) to stdout and exit.
#
-# See the test suite for b4_warn above.
+# See b4_warn example.
m4_define([b4_fatal],
-[b4_error([[fatal]], $@)])
+[b4_error([[fatal]], $@)dnl
+m4_exit(1)])
# b4_fatal_at(START, END, FORMAT, [ARG1], [ARG2], ...)
# ----------------------------------------------------
-# Write @fatal(START@,END@,FORMAT@,ARG1@,ARG2@,...@) to diversion 0.
+# Write @fatal(START@,END@,FORMAT@,ARG1@,ARG2@,...@) to stdout and exit.
+#
+# See b4_warn_at example.
m4_define([b4_fatal_at],
-[b4_error_at([[fatal]], $@)])
+[b4_error_at([[fatal]], $@)dnl
+m4_exit(1)])
## ---------------- ##
AT_SETUP([[%define Boolean variables: invalid skeleton defaults]])
-AT_CHECK([[mkdir tmp]])
-
AT_DATA([[skel.c]],
[[b4_percent_define_default([[foo]], [[bogus value]])
b4_percent_define_flag_if([[foo]])
AT_CLEANUP
+## --------------------------------------------- ##
+## Complaining during macro argument expansion. ##
+## --------------------------------------------- ##
+
+AT_SETUP([[Complaining during macro argument expansion]])
+
+AT_DATA([[skel1.c]],
+[[m4@&t@_define([foow], [b4_warn([[foow fubar]])])
+m4@&t@_define([foowat], [b4_warn_at([[foow.y:2.3]],
+ [[foow.y:5.4]], [[foowat fubar]])])
+m4@&t@_define([fooc], [b4_complain([[fooc fubar]])])
+m4@&t@_define([foocat], [b4_complain_at([[fooc.y:1.1]],
+ [[fooc.y:10.6]], [[foocat fubar]])])
+m4@&t@_define([foof], [b4_fatal([[foof fubar]])])
+m4@&t@_if(foow, [1], [yes])
+m4@&t@_if(foowat, [1], [yes])
+m4@&t@_if(fooc, [1], [yes])
+m4@&t@_if(foocat, [1], [yes])
+m4@&t@_if(foof, [1], [yes])
+]])
+
+AT_DATA([[input1.y]],
+[[%skeleton "./skel1.c"
+%%
+start: ;
+]])
+
+AT_CHECK([[bison input1.y]], [[1]], [[]],
+[[input1.y: warning: foow fubar
+foow.y:2.3-5.3: warning: foowat fubar
+input1.y: fooc fubar
+fooc.y:1.1-10.5: foocat fubar
+input1.y: fatal error: foof fubar
+]])
+
+AT_DATA([[skel2.c]],
+[[m4@&t@_define([foofat], [b4_fatal_at([[foof.y:12.11]],
+ [[foof.y:100.123]], [[foofat fubar]])])
+m4@&t@_if(foofat, [1], [yes])
+]])
+
+AT_DATA([[input2.y]],
+[[%skeleton "./skel2.c"
+%%
+start: ;
+]])
+
+AT_CHECK([[bison input2.y]], [[1]], [[]],
+[[foof.y:12.11-100.122: fatal error: foofat fubar
+]])
+
+AT_CLEANUP
+
+
+## --------------------------------------- ##
+## Fatal errors make M4 exit immediately. ##
+## --------------------------------------- ##
+
+AT_SETUP([[Fatal errors make M4 exit immediately]])
+
+AT_DATA([[skel1.c]],
+[[b4_complain([[non-fatal error]])
+b4_fatal([[M4 should exit immediately here]])
+m4@&t@_fatal([this should never be evaluated])
+]])
+
+AT_DATA([[input1.y]],
+[[%skeleton "./skel1.c"
+%%
+start: ;
+]])
+
+AT_CHECK([[bison input1.y]], [[1]], [[]],
+[[input1.y: non-fatal error
+input1.y: fatal error: M4 should exit immediately here
+]])
+
+AT_DATA([[skel2.c]],
+[[b4_warn([[morning]])
+b4_fatal_at([[foo.y:1.5]], [[foo.y:1.7]], [[M4 should exit immediately here]])
+m4@&t@_fatal([this should never be evaluated])
+]])
+
+AT_DATA([[input2.y]],
+[[%skeleton "./skel2.c"
+%%
+start: ;
+]])
+
+AT_CHECK([[bison input2.y]], [[1]], [[]],
+[[input2.y: warning: morning
+foo.y:1.5-6: fatal error: M4 should exit immediately here
+]])
+
+AT_CLEANUP