#!/bin/sh -e

out="$1"
tmp="$out.tmp"

if [ -z "$out" ]; then
    echo "Usage: $0 outfile" 2>&1
    exit 1
fi

s='[[:space:]]'			# For brevity's sake

trap "rm -f '$tmp'" EXIT
trap "rm -f '$tmp' '$out'" HUP INT QUIT TERM

# Fallback list of speeds that are always tested for.
#
# It is safe to add additional speeds to this list; speeds not defined
# on any particular platform are simply ignored.  It is specifically
# encouraged to add additional speeds here if some are found to be
# defined on a platform for which:
#
# - the C compiler(s) normally used does not support the -dM option
#   (gcc, clang, and derived compilers do support this option), AND
# - there are baud rate constants (B<num>) defined in <termios.h>
#   that are not identity-mapped (i.e., B<num> != <num> for at least
#   some <num>).
#
defspeeds="0 50 75 110 134 150 200 300 600 1200 1800 2400 4800 7200 9600 \
14400 19200 28800 33600 38400 57600 76800 115200 153600 230400 307200 \
460800 500000 576000 614400 921600 1000000 1152000 1500000 \
2000000 2500000 3000000 3500000 4000000 5000000 10000000"
(
    sed -n -e "s/^$s*\#$s*define$s$s*B\\([1-9][0-9]*\\)$s.*\$/\\1/p"
    for s in $defspeeds; do echo "$s"; done
) | sort -n | uniq > "$tmp"

cat > "$out" <<'EOF'
#ifndef SPEEDLIST_H
# define SPEEDLIST_H 1

# if 1 \
EOF

sed -e 's/^.*$/ \&\& (!defined(B&) || B& == &) \\/' < "$tmp" >> "$out"

cat >> "$out" <<'EOF'

#  define TERMIOS_SPEED_T_SANE 1

# endif

ATTRIBUTE_CONST
static unsigned long int
baud_to_value (speed_t speed)
{
# ifdef TERMIOS_SPEED_T_SANE
  return speed;
# else
  switch (speed)
    {
EOF

while read n; do
  printf '#  ifdef B%s\n      case B%s: return %s;\n#  endif\n' "$n" "$n" "$n"
done < "$tmp" >> "$out"

cat >> "$out" <<'EOF'
      default: return -1;
    }
# endif
}

ATTRIBUTE_CONST
static struct speed_desc
value_to_baud (unsigned long int value)
{
# ifdef TERMIOS_SPEED_T_SANE
  speed_t speed = value;
  /* Unrepresentable (overflow?) */
  if (speed != value)
    return (struct speed_desc) { .baud = (speed_t) -1, .valid = false };
  return (struct speed_desc) { .baud = speed, .valid = true };
# else
  switch (value)
    {
EOF

while read n; do
  printf '#  ifdef B%s\n'\
'    case %s:\n'\
'      return (struct speed_desc) { .baud = B%s, .valid = false };\n'\
'#  endif\n' "$n" "$n" "$n"
done < "$tmp" >> "$out"

cat >> "$out" <<'EOF'
    default:
      return (struct speed_desc) { .baud = (speed_t) -1, .valid = false };
    }
# endif
}

#if defined CBAUD && defined TERMIOS_SPEED_T_SANE
/* Decode baud bits without assuming their platform-specific encoding.  */
static struct speed_desc
cbaud_to_speed (tcflag_t cbaud)
{
  static speed_t const speeds[] =
    {
EOF

while read n; do
  printf '# ifdef B%s\n      B%s,\n# endif\n' "$n" "$n"
done < "$tmp" >> "$out"

cat >> "$out" <<'EOF'
    };

# ifdef BOTHER
  if (cbaud == BOTHER)
    return (struct speed_desc) { .valid = false };
# endif

  for (idx_t i = 0; i < countof (speeds); i++)
    {
      struct termios mode = { 0 };
      if (cfsetospeed (&mode, speeds[i]) == 0
          && (mode.c_cflag & CBAUD) == cbaud)
        return (struct speed_desc) { .baud = speeds[i], .valid = true };
    }
  return (struct speed_desc) { .valid = false };
}
#endif

#endif
EOF
