#!/bin/sh -
#@ mdocmx.sh - mdocmx(7) preprocessor for single-pass troff.
#@ mdocmx(7) extends the mdoc(7) semantic markup language by references,
#@ allowing mdoc(7) to create anchors and table of contents.
#@ Synopsis: mdocmx[.sh] [:-v:] [-t | -T Sh|sh|Ss|ss] [FILE]
#@ -v: increase verbosity
#@ -t: wether -toc lines shall be expanded to a flat .Sh TOC
#@ -T: wether -toc lines shall be expanded as specified: only .Sh / .Sh + .Ss
#@ -c: only with -t or -T: wether compact TOC display shall be generated
#@ Set $AWK environment to force a special awk(1) interpreter.
#
# Written 2014 - 2015 by Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
# Public Domain

: ${TMPDIR:=/tmp}
: ${ENV_TMP="${TMPDIR}:${TMP}:${TEMP}"}

#  --  >8  --  8<  --  #

#AWK=
EX_OK=0
EX_USAGE=64
EX_DATAERR=65
EX_TEMPFAIL=75

V=0 T= TT= F=

find_awk() {
  [ -n "${AWK}" ] && return 0
  i=${IFS}
  IFS=:
  set -- ${PATH}:/bin
  IFS=${i}
  # for i; do -- new in POSIX Issue 7 + TC1
  for i
  do
    for j in n m '' g; do
      AWK="${i}/${j}awk"
      [ -x "${AWK}" ] && return 0
    done
  done
  return 1
}

synopsis() {
  ex=${1} msg=${2}
  [ -n "${msg}" ] && echo >&2 ${msg}
  [ ${ex} -eq 0 ] && f=1 || f=2
  ( echo "${0##*/}" ) >/dev/null 2>&1 && eval 'p="${0##*/}"' || p="${0}"
  echo >&${f} "Synopsis: ${p} [-h]"
  echo >&${f} "          ${p} [:-v:] [-t | -T Sh|sh|Ss|ss  [-c]] [FILE]"
  exit ${ex}
}

##

find_awk || synopsis 1 'Cannot find a usable awk(1) implementation'

while getopts hvtT:c i; do
  case ${i} in
  h)
    synopsis ${EX_OK};;
  v)
    V=`expr ${V} + 1`;;
  t)
    [ x != x"${T}" ] && synopsis ${EX_USAGE} '-toc line expansion yet defined'
    T=Sh;;
  T)
    [ x != x"${T}" ] && synopsis ${EX_USAGE} '-toc line expansion yet defined'
    case "${OPTARG}" in
    [Ss]h)  T=Sh;;
    [Ss]s)  T=Ss;;
    *)      synopsis ${EX_USAGE} "Invalid -T argument: -- ${OPTARG}";;
    esac;;
  c)
    TT=-compact;;
  ?)
    synopsis ${EX_USAGE} '';;
  esac
done
[ -n "${TT}" ] && [ -z "${T}" ] && synopsis ${EX_USAGE} '-c requires -t or -T'
OPTIND=`expr ${OPTIND} - 1`
shift ${OPTIND}

[ ${#} -gt 1 ] && synopsis ${EX_USAGE} 'Excess arguments given'
[ ${#} -eq 0 ] && F=- || F=${1}

##

# awk(1) doesn't support signal handlers, which means that, when we're part of
# a pipe which the user terminates, we are not capable to deal with the broken
# pipe case that our END{} handler will generate when we had to perform any
# preprocessing, and that in turn would result in a dangling temporary file!
# Thus the only sane option seems to be to always create the temporary file,
# wether we need it or not, not to exec(1) awk(1) but keep on running the shell
# in order to remove the temporary after awk(1) has finished, whichever way.

find_tmpdir() {
  i=${IFS}
  IFS=:
  set -- ${ENV_TMP}
  IFS=${i}
  # for i; do -- new in POSIX Issue 7 + TC1
  for tmpdir
  do
    [ -d "${tmpdir}" ] && return 0
  done
  tmpdir=${TMPDIR}
  [ -d "${tmpdir}" ] && return 0
  echo >&2 'Cannot find a usable temporary directory, please set $TMPDIR'
  exit ${EX_TEMPFAIL}
}
find_tmpdir

max=421
[ ${V} -gt 1 ] && max=2
i=1
# RW by user only, avoid overwriting of existing files
old_umask=`umask`
umask 077
set -C
while [ 1 ]; do
  tmpfile="${tmpdir}/mdocmx-${i}.mx"
  { : > "${tmpfile}"; } >/dev/null 2>/dev/null && break
  i=`expr ${i} + 1`
  if [ ${i} -gt ${max} ]; then
    echo >&2 'Cannot create a temporary file within '"${tmpdir}"
    exit ${EX_TEMPFAIL}
  fi
done

trap "rm -f ${tmpfile}; exit ${EX_TEMPFAIL}" HUP INT QUIT PIPE TERM
trap "rm -f ${tmpfile}" EXIT
umask ${old_umask}

# Let's go awk(1) {{{
APOSTROPHE=\'
${AWK} -v VERBOSE=${V} -v TOC="${T}" -v TOCTYPE="${TT}" -v MX_FO="${tmpfile}" \
  -v EX_USAGE="${EX_USAGE}" -v EX_DATAERR="${EX_DATAERR}" \
'BEGIN {
  # The mdoc macros that support referenceable anchors.
  # .Sh and .Ss also create anchors, but since they do not require .Mx they are
  # treated special and handled directly -- update manual on change!
  UMACS = "Ar Cm Dv Er Ev Fl Fn Fo Ic In Pa Va Vt"

  # Some of those impose special rules for their arguments; mdocmx(1) solves
  # this by outsourcing such desires in argument parse preparation hooks
  UMACS_KEYHOOKS = "Fl Fn"

  # A list of all mdoc commands; taken from mdocml, "mdoc.c,v 1.226 2014/10/16"
  UCOMMS = \
      "%A %B %C %D %I %J %N %O %P %Q %R %T %U %V " \
      "Ac Ad An Ao Ap Aq Ar At Bc Bd " \
        "Bf Bk Bl Bo Bq Brc Bro Brq Bsx Bt Bx " \
      "Cd Cm D1 Db Dc Dd Dl Do Dq Dt Dv Dx " \
      "Ec Ed Ef Ek El Em En Eo Er Es Ev Ex Fa Fc Fd Fl Fn Fo Fr Ft Fx " \
      "Hf " \
      "Ic In It " \
      "Lb Li Lk Lp " \
      "Ms Mt Nd Nm No Ns Nx " \
      "Oc Oo Op Os Ot Ox Pa Pc Pf Po Pp Pq " \
      "Qc Ql Qo Qq Re Rs Rv " \
      "Sc Sh Sm So Sq Ss St Sx Sy Ta Tn " \
      "Ud Ux Va Vt Xc Xo Xr " \
      "br ll sp"

  # Punctuation to be ignored (without changing current mode)
  UPUNCTS = ". , : ; ( ) [ ] ? !"

  #  --  >8  --  8<  --  #

  i = split(UMACS, savea)
  for (j = 1; j <= i; ++j) {
    k = savea[j]
    MACS[k] = k
  }

  i = split(UMACS_KEYHOOKS, savea)
  for (j = 1; j <= i; ++j) {
    k = savea[j]
    MACS_KEYHOOKS[k] = k
  }

  i = split(UCOMMS, savea)
  for (j = 1; j <= i; ++j) {
    k = savea[j]
    COMMS[k] = k
  }

  i = split(UPUNCTS, savea)
  for (j = 1; j <= i; ++j) {
    k = savea[j]
    PUNCTS[k] = k
  }

  mx_bypass = 0   # Avoid preprocessing if parsing preprocessed file!

  mx_nlcont = ""  # Line continuation in progress?  (Then: data so far)
  mx_nlcontfun = 0 # Which function to call after line complete
  NLCONT_SH_SS_COMM = 1
  NLCONT_MX_COMM = 2
  NLCONT_MX_CHECK_LINE = 3

  #mx_sh[]        # Arrays which store headlines, and their sizes
  #mx_sh_cnt
  #mx_sh_toc      # Special avoidance of multiple TOC anchors needed, ++
  #mx_ss[]
  #mx_ss_cnt
  #mx_sh_ss[]     # With TOC we need relation of .Ss with its .Sh
  #mx_fo = ""     # Our temporary output fork (cleaned of .Mx)
  #mx_macros[]    # Readily prepared anchors: macros..
  #mx_keys[]      # ..: keys
  #mx_anchors_cnt # ..number of anchors
  #mx_stack[]     # Stack of future anchors to be parsed off..
  #mx_stack_cnt   # ..number thereof
  #mx_keystack[]  # User specified ".Mx MACRO KEY": store KEY somewhere
  #ARG, [..]      # Next parsed argument (from arg_parse() helper)
}

END {
  # If we were forced to create referenceable anchors, dump the temporary file
  # after writing our table-of-anchors (TAO :)
  if (mx_fo) {
    close(mx_fo)

    if (mx_stack_cnt > 0)
      warn("At end of file: \".Mx\" stack not empty (" mx_stack_cnt " levels)")

    for (i = 1; i <= mx_sh_cnt; ++i)
      printf ".Mx -anchor-spass Sh \"%s\" %d\n", arg_quote(mx_sh[i]), i
    for (i = 1; i <= mx_ss_cnt; ++i)
      printf ".Mx -anchor-spass Ss \"%s\" %d\n",
        arg_quote(mx_ss[i]), mx_sh_ss[i]
    for (i = 1; i <= mx_anchors_cnt; ++i)
      printf ".Mx -anchor-spass %s \"%s\"\n",
        mx_macros[i], arg_quote(mx_keys[i])

    # If we are about to produce a TOC, intercept ".Mx -toc" lines and replace
    # them with the desired TOC content
    if (!TOC) {
      while (getline < mx_fo)
        print
    } else {
      while (getline < mx_fo) {
        if ($0 ~ /^[[:space:]]*\.[[:space:]]*Mx[[:space:]]+-toc[[:space:]]*/) {
          print ".Sh \"\\*[mx-toc-name]\""
          if (mx_sh_cnt > 0) {
            print ".Bl -inset", TOCTYPE
            for (i = 1; i <= mx_sh_cnt; ++i) {
              printf ".It Sx \"%s\"\n", arg_quote(mx_sh[i])
              if (TOC == "Ss")
                toc_print_ss(i)
            }
            print ".El"
          }
          # Rather illegal, but it maybe we have .Ss yet no .Sh
          else if (TOC == "Ss" && mx_ss_cnt > 0) {
            print ".Bl -tag -compact"
            for (i = 1; i <= mx_ss_cnt; ++i)
              print ".It Sx \"%s\"\n", arg_quote(mx_ss[i])
            print ".El"
          }
        } else
          print
      }
    }
  }
}

function f_a_l() {
  if (!fal) {
    fal = FILENAME
    if (!fal || fal == "-")
      fal = "<stdin>"
  }
  return fal ":" NR
}

function dbg(s) {
  if (VERBOSE > 1)
    print "DBG@" f_a_l() ": " s >> "/dev/stderr"
}

function warn(s) {
  if (VERBOSE > 0)
    print "WARN@" f_a_l() ": mdocmx(7): " s "." >> "/dev/stderr"
}

function fatal(e, s) {
  print "FATAL@" f_a_l() ": mdocmx(7): " s "." >> "/dev/stderr"
  exit e
}

# Dump all .Ss which belong to the .Sh with the index sh_idx, if any
function toc_print_ss(sh_idx)
{
  tps_any = 0
  for (tps_i = 1; tps_i <= mx_ss_cnt; ++tps_i) {
    tps_j = mx_sh_ss[tps_i]
    if (tps_j < sh_idx)
      continue
    if (tps_j > sh_idx)
      break

    if (!tps_any) {
      tps_any = 1
      print ".Bl -tag -offset indent -compact"
    }
    printf ".It Sx \"%s\"\n", arg_quote(mx_ss[tps_i])
  }
  if (tps_any)
    print ".El"
}

# Parse the next _roff_ argument from the awk(1) line (in $0).
# If "no" < 0, reset the parser and return wether the former state would
# have parsed another argument from the line.
# If "no" is >0 we start at $(no); if it is 0, iterate to the next argument.
# Returns ARG.  Only used when "hot"
function arg_pushback(arg) { ap_pushback = arg }
function arg_parse(no) {
  if (no < 0) {
    no = ap_no
    ap_no = 0
    ap_pushback = ""
    return no < NF
  }
  if (no == 0) {
    if (ap_pushback) {
      ARG = ap_pushback
      ap_pushback = ""
      return ARG
    }
    no = ap_no + 1
  }
  ap_pushback = ""

  ARG = ""
  for (ap_i = 0; no <= NF; ++no) {
    ap_j = $(no)

    # The good news about quotation mode is that entering it requires
    # a preceeding space: we get it almost for free with awk(1)!
    if (!ap_i) {
      if (ap_j ~ /^"/) {
        ap_i = 1
        ap_j = substr(ap_j, 2)
      } else {
        ARG = ap_j
        break
      }
    } else
      ARG = ARG " "

    if ((ap_k = index(ap_j, "\"")) != 0) {
      do {
        # The bad news on quotation mode are:
        # - "" inside it resolves to a single "
        # - " need not mark EOS, but any " that is not followed by "
        #   ends quotation mode and marks the beginning of the next arg
        # - awk(1) has no goto;
        if (ap_k == length(ap_j)) {
          ARG = ARG substr(ap_j, 1, ap_k - 1)
          ap_no = no
          ap_i = 0
          return ARG
        } else if (substr(ap_j, ap_k + 1, 1) == "\"") {
          ARG = ARG substr(ap_j, 1, ap_k)
          ap_j = substr(ap_j, ap_k + 2)
        } else {
          ARG = ARG substr(ap_j, 1, ap_k)
          ap_j = substr(ap_j, ap_k + 1)
          $(no) = ap_j
          ap_no = no
          ap_i = 0
          return ARG
        }
      } while ((ap_k = index(ap_j, "\"")) > 0)
    } else
      ARG = ARG ap_j
  }
  ap_no = no
  return ARG
}

function arg_cleanup(arg) {
  # Deal with common special glyphs etc.
  # Note: must be in sync with mdocmx(7) macros (mx:cleanup-string)!
  ac_i = match(arg, /([ \t]|\\&|\\%|\\\/|\\c)+$/)
  if (ac_i)
    arg = substr(arg, 1, ac_i - 1)
  while (arg ~ /^[ \t]/)
    arg = substr(arg, 1)
  while (arg ~ /^(\\&|\\%)/ && arg !~ /^\\&\\&/)
    arg = substr(arg, 3)
  return arg
}

function arg_quote(arg) {
  aq_a = arg
  gsub("\"", "\"\"", aq_a)
  return aq_a
}

# ".Mx -enable" seen
function mx_enable() {
  # However, are we running on an already preprocessed document?  Bypass!
  if (NF > 2) {
    if (NF > 3 && $3 == "-preprocessed") {
      $1 = $2 = $3 = ""
      $0 = substr($0, 3)
      print ".Mx -enable -preprocessed" $0
      mx_bypass = 1
      return
    }
  }

  # If we generate the TOC ourselfs better ensure the string mx-toc-name!
  # mdocml.bsd.lv (mandoc(1)) does not offer any ".if !d NAME" way, so..
  # But even otherwise we need it, since mandoc(1) complains about unknown
  # \*[] strings in quoted strings, and we *may* have a ".Mx -toc" anyway!
  if (!TOC)
    printf ".\\\" Uncomment for mandoc(1) compat.:\n.\\\""
  print ".ds mx-toc-name TABLE OF CONTENTS"
  mx_fo = MX_FO
  $1 = $2 = ""
  $0 = substr($0, 2)
  print ".Mx -enable -preprocessed" $0
}

# Deal with a non-"-enable" ".Mx" request
function mx_comm() {
  # No argument: plain push
  if (NF == 1) {
    ++mx_stack_cnt
    dbg(".Mx: [noarg] -> +1, stack size=" mx_stack_cnt)
    return
  }

  # ".Mx -disable"
  if ($2 == "-disable") {
    # Nothing to do here (and do not check device arguments)
    return
  }

  # ".Mx -toc"
  if ($2 == "-toc") {
    # With TOC creation we surely want the TOC to have an anchor, too!
    if (!mx_sh_toc++)
      mx_sh[++mx_sh_cnt] = "\\*[mx-toc-name]"
    else
      warn("\".Mx -toc\": multiple TOCs?  Duplicate anchor avoided")
    return
  }

  # This explicitly specifies the macro to create an anchor for next
  mxc_i = $2
  if (mxc_i ~ /^\./) {
    warn("\".Mx\": stripping dot prefix from \"" mxc_i "\"")
    mxc_i = substr(mxc_i, 2)
  }

  mxc_j = MACS[mxc_i]
  if (!mxc_j)
    fatal(EX_DATAERR, "\".Mx\": macro \"" mxc_i "\" not supported")
  mx_stack[++mx_stack_cnt] = mxc_i
  dbg(".Mx: for next \"." mxc_i "\", stack size=" mx_stack_cnt)

  # Do we also have a fixed key?
  if (NF == 2)
    return
  mx_keystack[mx_stack_cnt] = arg_parse(3)
  dbg("  ... USER KEY given: <" ARG ">");
  if (arg_parse(-1))
    fatal(EX_DATAERR, "\".Mx\": data after USER KEY is faulty syntax")
}

# mx_stack_cnt is >0, check wether this line will pop the stack
function mx_check_line() {
  # May be line continuation in the middle of nowhere
  if (!mx_stack_cnt)
    return
  # Must be a non-comment, non-escaped macro line
  if ($0 !~ /^[[:space:]]*[.'${APOSTROPHE}'][[:space:]]*[^"#]/)
    return

  # Iterate over all arguments and try to classify them, comparing them against
  # stack content as applicable
  mcl_mac = ""
  mcl_cont = 0
  mcl_firstmac = 1
  for (arg_parse(-1); arg_parse(0);) {
    # Solely ignore punctuation (xxx are we too stupid here?)
    if (PUNCTS[ARG])
      continue

    # (xxx Do this at the end of the loop instead, after decrement?)
    if (mx_stack_cnt == 0) {
      dbg("stack empty, stopping arg processing before <" ARG ">")
      break
    }

    mcl_j = mx_stack[mx_stack_cnt]

    # Is this something we consider a macro?  For convenience and documentation
    # of roff stuff do auto-ignore a leading dot of the name in question
    mcl_cont = 0
    if (mcl_firstmac && ARG ~ /^\./)
      ARG = substr(ARG, 2)
    mcl_firstmac = 0
    mcl_i = MACS[ARG]
    if (mcl_i)
      mcl_mac = mcl_i
    else {
      if (!mcl_mac)
        continue
      # It may be some mdoc command nonetheless, ensure it does not fool our
      # simpleminded processing, and end possible mcl_mac savings
      if (COMMS[ARG]) {
        if (mcl_j)
          dbg("NO POP due macro (got<" ARG "> want<" mcl_j ">)")
        mcl_mac = ""
        continue
      }
      mcl_i = mcl_mac
      mcl_cont = 1
    }

    # Current command matches the one on the stack, if there is any
    if (mcl_j) {
      if (mcl_i != mcl_j) {
        dbg("NO POP due macro (got<" mcl_i "> want<" mcl_j ">)")
        mcl_mac = ""
        continue
      }
    }

    # We need the KEY
    if (!mcl_cont && !arg_parse(0))
      fatal(EX_DATAERR, "\".Mx\": expected KEY after \"" mcl_mac "\"")
    ARG = arg_cleanup(ARG)
    if (ARG ~ /^\\&\\&/)
      warn("\".Mx\": KEY starting with \"\\&\\&\" will never match: " ARG)
    if (MACS_KEYHOOKS[mcl_mac])
      _mx_check_line_keyhook()

    if (mx_keystack[mx_stack_cnt]) {
      mcl_i = mx_keystack[mx_stack_cnt]
      if (mcl_i != ARG) {
        dbg("NO POP mac<" mcl_mac "> due key (got<" ARG "> want <" mcl_i ">)")
        continue
      }
      delete mx_keystack[mx_stack_cnt]
      mcl_i = "STACK"
    } else
      mcl_i = "USER"

    delete mx_stack[mx_stack_cnt--]
    dbg("POP mac<" mcl_mac "> " mcl_i " key <" ARG \
      "> stack size=" mx_stack_cnt)

    # Warn about and ignore duplicate anchors
    for (mcl_i = 1; mcl_i <= mx_anchors_cnt; ++mcl_i)
      if (mx_macros[mcl_i] == mcl_mac && mx_keys[mcl_i] == ARG)
        break
    if (mcl_i > mx_anchors_cnt) {
      ++mx_anchors_cnt
      mx_macros[mx_anchors_cnt] = mcl_mac
      mx_keys[mx_anchors_cnt] = ARG
    } else
      warn("\".Mx\": duplicate anchor avoided: " ARG)
  }
}

function _mx_check_line_keyhook() {
  # .Fl: arguments may be continued via |, as in ".Fl a | b | c"
  if (mcl_mac == "Fl") {
    mclpkh_i = ARG
    for (mclpkh_j = 0;; ++mclpkh_j) {
      if (!arg_parse(0))
        break
      if (ARG != "|") {
        arg_pushback(ARG)
        break
      }
      if (!arg_parse(0)) {
        warn("Premature end of \".Fl\" continuation via \"|\"")
        break
      }
      # Be aware that this argument may indeed be a macro
      # XXX However, only support another Fl as in
      # XXX  .Op Fl T | Fl t Ar \&Sh | sh | \&Ss | ss
      # XXX We are too stupid to recursively process any possible thing,
      # XXX more complicated recursions are simply not supported
      if (ARG == "Fl") {
        arg_pushback(ARG)
        break
      }
      ARG = arg_cleanup(ARG)
      mclpkh_i = mclpkh_i " | " ARG
    }
    ARG = mclpkh_i
  }
  # .Fn: in ".Fn const char *funcname" all we want is "funcname"
  else if (mcl_mac == "Fn") {
    if (ARG ~ /[*&[:space:]]/) {
      mclpkh_i = match(ARG, /[^*&[:space:]]+$/)
      ARG = arg_cleanup(substr(ARG, mclpkh_i))
    }
  }
}

# Handle a .Sh or .Ss
function sh_ss_comm() {
  ssc_s = ""
  ssc_i = 0
  for (arg_parse(-1); arg_parse(0); ++ssc_i) {
    if (ssc_i < 1)
      continue
    if (ssc_i > 1)
      ssc_s = ssc_s " "
    ssc_s = ssc_s ARG
  }
  ssc_s = arg_cleanup(ssc_s)
  if ($1 ~ /Sh/)
    mx_sh[++mx_sh_cnt] = ssc_s
  else {
    mx_ss[++mx_ss_cnt] = ssc_s
    mx_sh_ss[mx_ss_cnt] = mx_sh_cnt
  }
}

# This is our *very* primitive way of dealing with line continuation
function line_nlcont_add(fun) {
  mx_nlcont = mx_nlcont $0
  mx_nlcont = substr(mx_nlcont, 1, length(mx_nlcont) - 1)
  if (!mx_nlcontfun)
    mx_nlcontfun = fun
}

function line_nlcont_done() {
  lnd_save = $0
  $0 = mx_nlcont $0
  mx_nlcont = ""
  if (mx_nlcontfun == NLCONT_SH_SS_COMM)
    sh_ss_comm()
  else if (mx_nlcontfun == NLCONT_MX_COMM)
    mx_comm()
  else if (mx_nlcontfun == NLCONT_MX_CHECK_LINE)
    mx_check_line()
  else
    fatal(EX_DATAERR, "mdocmx(1) implementation error: line_nlcont_done()")
  mx_nlcontfun = 0
  $0 = lnd_save # simplify callees life
}

# .Mx is a line that we care about
/^[[:space:]]*[.'${APOSTROPHE}'][[:space:]]*M[Xx][[:space:]]*/ {
  if (mx_bypass)
    print
  else if (mx_fo) {
    if (NF > 1 && $2 == "-enable")
      fatal(EX_USAGE, "\".Mx -enable\" may be used only once")
    if (mx_nlcont)
      fatal(EX_DATAERR, "Line continuation too complicated for mdocmx(1)")
    if ($0 ~ /\\$/)
      line_nlcont_add(NLCONT_MX_COMM)
    else
      mx_comm()
    print >> mx_fo
  } else if (NF < 2 || $2 != "-enable")
    fatal(EX_USAGE, "\".Mx -enable\" must be the first \".Mx\" command")
  else
    mx_enable()
  next
}

# .Sh and .Ss are also lines we care about, but always store the data in
# main memory, since those commands occur in each mdoc file
/^[[:space:]]*[.'${APOSTROPHE}'][[:space:]]*S[hs][[:space:]]+/ {
  if (mx_fo) {
    if (mx_nlcont)
      fatal(EX_DATAERR, "Line continuation too complicated for mdocmx(1)")
    if ($0 ~ /\\$/)
      line_nlcont_add(NLCONT_SH_SS_COMM)
    else
      sh_ss_comm()
    print >> mx_fo
    next
  }
}

# All other lines are uninteresting unless mdocmx is -enabled and we have
# pending anchor creation requests on the stack
{
  if (!mx_fo)
    print
  else {
    # TODO No support for any macro END but ..
    if (/^[[:space:]]*[.'${APOSTROPHE}'][[:space:]]*dei?1?[[:space:]]+/) {
      if (mx_nlcont)
        fatal(EX_DATAERR, "Line continuation too complicated for mdocmx(1)")
      print >> mx_fo
      while (getline && $0 !~ /^\.\.$/)
        print >> mx_fo
    } else if ($0 ~ /\\$/)
      line_nlcont_add(NLCONT_MX_CHECK_LINE)
    else if (mx_nlcont)
      line_nlcont_done()
    else if (mx_stack_cnt)
      mx_check_line()
    print >> mx_fo
  }
}' "${F}"
# }}}

exit
# s-it2-mode
