Removed whoami from msg/msg_parser.awk.

Removed whoami from msg/msg_parser.awk.

whoami is not used.

darcs-hash:20051031155729-65a35-5229485651f8ef3710c3e11b0f865291ba02c5fb.gz

#! /usr/bin/env awk
#
# This script recreates C files containing header-specific boilerplate stuff
# using the given list of headers (usually obtained from the master structure).
#
# It can also create a parser table.
#
# --------------------------------------------------------------------
#
# This file is part of the Sofia-SIP package
#
# Copyright (C) 2005 Nokia Corporation.
#
# Contact: Pekka Pessi <pekka.pessi@nokia.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2.1 of
# the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# --------------------------------------------------------------------
#
# Contributor(s): Pekka.Pessi@nokia.com.
#
# Created: Fri Apr  6 12:59:59 2001 ppessi
#

BEGIN {
  "date '+%a %b %e %H:%M:%S %Y'" | getline date;

  ascii =			       \
     "                               " \
    " !\"#$%&'()*+,-./0123456789:;<=>?" \
    "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" \
    "`abcdefghijklmnopqrstuvwxyz{|}~";
  lower_case = "abcdefghijklmnopqrstuvwxyz";

  N=0;
  # Initialize these as arrays
  split("", symbols);
  split("", names);
  split("", comments);
  split("", hashes);
  split("", NAMES);
  split("", Comments);
  split("", COMMENTS);
}

function name_hash (name)
{
  hash = 0;

  len = split(name, chars, "");

  for (i = 1; i <= len; i++) {
    c = tolower(chars[i]);
    hash = (38501 * (hash + index(ascii, c))) % 65536;
  }

  if (0) {
    # Test that hash algorithm above agrees with the C version
    pipe = ("../msg/msg_name_hash " name);
    pipe | getline;
    close(pipe);
    if (hash != $0) {
      print name ": " hash " != " $0 > "/dev/stderr";
    }
  }

  return hash "";
}

#
# Replace magic patterns in template p with header-specific values
#
function protos (name, comment, hash)
{
  NAME=toupper(name);
  sub(/.*[\/][*][*][<][ 	]*/, "", comment);
  sub(/[ 	]*[*][\/].*/, "", comment);
  sub(/[ 	]+/, " ", comment);

  short = match(comment, /[(][a-z][)]/);
  if (short) {
    short = substr(comment, short + 1, 1);
    sub(/ *[(][a-z][)]/, "", comment);
    shorts[index(lower_case, short)] = name;
  }

  do_hash = hash == 0;

  if (do_hash) {
    split(comment, parts, " ");
    name2 = tolower(parts[1]);
    gsub(/-/, "_", name2);
    if (name2 != name) {
      print name " mismatch with " comment " (" real ")" > "/dev/stderr";
    }

    hash = name_hash(parts[1]);

    hashed[name] = hash;

    if (comment !~ /header/) {
      comment = comment " header";
    }
  }

  Comment = comment;
  if (!do_hash) {
    comment = tolower(comment);
  }
  COMMENT = toupper(comment);

  # Store the various forms into an array for the footer processing
  N++;
  hashes[N] = hash;
  names[N] = name;
  NAMES[N] = NAME;
  comments[N] = comment;
  Comments[N] = comment;
  COMMENTS[N] = COMMENT;

  symbols[name] = comment;

  if (PR) {
    replace(template, hash, name, NAME, comment, Comment, COMMENT);
    replace(template1, hash, name, NAME, comment, Comment, COMMENT);
    replace(template2, hash, name, NAME, comment, Comment, COMMENT);
    replace(template3, hash, name, NAME, comment, Comment, COMMENT);
  }
}

function replace (p, hash, name, NAME, comment, Comment, COMMENT)
{
  if (p) {
    gsub(/#hash#/, hash, p);
    gsub(/#xxxxxx#/, name, p);
    gsub(/#XXXXXX#/, NAME, p);
    gsub(/#xxxxxxx_xxxxxxx#/, comment, p);
    gsub(/#Xxxxxxx_Xxxxxxx#/, Comment, p);
    gsub(/#XXXXXXX_XXXXXXX#/, COMMENT, p);

    print p > PR NEW;
  }
}

#
# Repeat each line in the footer containing the magic replacement
# pattern with a instance of all headers
#
function process_footer (text)
{
  if (!match(tolower(text), /#(xxxxxx(x_xxxxxxx)?|hash)#/)) {
    n = length(text);
    while (substr(text, n) == "\n") {
      n = n - 1;
      text = substr(text, 1, n);
    }
    if (n > 0)
      print text > PR NEW;
    return;
  }

  n = split(text, lines, RS);

  for (i = 1; i <= n; i++) {
    l = lines[i];
    if (match(tolower(l), /#(xxxxxx(x_xxxxxxx)?|hash)#/)) {
      for (j = 1; j <= N; j++) {
	l = lines[i];
	gsub(/#hash#/, hashes[j], l);
	gsub(/#xxxxxxx_xxxxxxx#/, comments[j], l);
	gsub(/#Xxxxxxx_Xxxxxxx#/, Comments[j], l);
	gsub(/#XXXXXXX_XXXXXXX#/, COMMENTS[j], l);
	gsub(/#xxxxxx#/, names[j], l);
	gsub(/#XXXXXX#/, NAMES[j], l);
	print l > PR NEW;
      }
    } else {
      print l > PR NEW;
    }
  }
}

#
# Read flags used with headers
#
function read_header_flags (flagfile,    line, tokens, name, value)
{
  while ((getline line < flagfile) > 0) {
    sub(/^[ \t]+/, "", line);
    sub(/[ \t]+$/, "", line);
    if (line ~ /^#/ || line ~ /^$/)
      continue;

    split(line, tokens,  /[ \t]*=[ \t]*/);
    name = tolower(tokens[1]);
    gsub(/-/, "_", name);
    gsub(/,/, " ", name);
    # print "FLAG: " name " = " tokens[2]

    if (header_flags[name]) {
      print flagfile ": already defined " tokens[1];
    }
    else if (!symbols[name]) {
      print flagfile ": unknown header \"" tokens[1] "\"";
    }
    else {
      header_flags[name] = tokens[2];
    }
  }
  close(flagfile);
}

/^ *\/\* === Headers start here \*\// {

  in_header_list=1;

  if (!auto) {
    auto = FILENAME;
    sub(/.*\//, "", auto);
    auto = "This file is automatically generated from <" auto "> by msg_parser.awk.";

    if (PR) {
      if (TEMPLATE == "") { TEMPLATE = PR ".in"; }
      RS0=RS; RS="\n";
      getline theader < TEMPLATE;
      getline header < TEMPLATE;
      getline template < TEMPLATE;
      getline footer < TEMPLATE;

      if (TEMPLATE1) {
	getline dummy < TEMPLATE1;
	getline dummy < TEMPLATE1;
	getline template1 < TEMPLATE1;
	getline dummy < TEMPLATE1;
      }

      if (TEMPLATE2) {
	getline dummy < TEMPLATE2;
	getline dummy < TEMPLATE2;
	getline template2 < TEMPLATE2;
	getline dummy < TEMPLATE2;
      }

      if (TEMPLATE3) {
	getline dummy < TEMPLATE3;
	getline dummy < TEMPLATE3;
	getline template3 < TEMPLATE3;
	getline dummy < TEMPLATE3;
      }

      sub(/.*[\/]/, "", TEMPLATE);
      gsub(/#AUTO#/, auto, header);
      gsub(/#DATE#/, "@date Generated: " date, header);
      print header > PR NEW;

      RS=RS0;
    }

    if (!NO_FIRST) {
      protos("request", "/**< Request line */", -1);
      protos("status", "/**< Status line */", -2);
    }
  }
}

in_header_list && /^ *\/\* === Headers end here \*\// { in_header_list=0;}

in_header_list && PT && /^ *\/\* === Hash headers end here \*\// { in_header_list=0;}

in_header_list && /^  (sip|rtsp|http|msg|mp)_[a-z_0-9]+_t/ {
  n=$0
  sub(/;.*$/, "", n);
  sub(/^ *(sip|rtsp|http|msg|mp)_[a-z0-9_]*_t[ 	]*/, "", n);
  sub(/^[*](sip|rtsp|http|msg|mp)_/, "", n);

  if ($0 !~ /[\/][*][*][<]/) {
    getline;
  }
  if ($0 !~ /[\/][*][*][<]/) {
    printf "msg_protos.awk: header %s is malformed\n", n;
    exit 1;
  }

  if (!NO_MIDDLE)
    protos(n, $0, 0);

  headers[total++] = n;
}

END {
  if (!prefix) { prefix = module; }
  if (!tprefix) { tprefix = prefix; }

  if (!NO_LAST) {
    protos("unknown", "/**< Unknown headers */", -3);
    protos("error", "/**< Erroneous headers */", -4);
    protos("separator", "/**< Separator line between headers and payload */", -5);
    protos("payload", "/**< Message payload */", -6);
    if (multipart)
      protos("multipart", "/**< Multipart payload */", -7);
  }

  if (PR) {
    process_footer(footer);
  }
  else if (PT) {
    if (FLAGFILE)
      read_header_flags(FLAGFILE);

    if (TEMPLATE == "") { TEMPLATE = PT ".in"; }
    RS0=RS; RS="\n";
    getline theader < TEMPLATE;
    getline header < TEMPLATE;
    getline template < TEMPLATE;
    getline footer < TEMPLATE;
    RS=RS0;

    sub(/.*[\/]/, "", TEMPLATE);
    gsub(/#AUTO#/, auto, header);
    gsub(/#DATE#/, "@date Generated: " date, header);
    print header > PT NEW;

    if (MC_SHORT_SIZE) {
      printf("static msg_href_t const " \
	     "%s_short_forms[MC_SHORT_SIZE] = \n{\n",
	     module) > PT NEW;

      for (i = 1; i <= MC_SHORT_SIZE; i = i + 1) {
	c = (i == MC_SHORT_SIZE) ? "" : ",";
	if (i in shorts) {
	  n = shorts[i];
        flags = header_flags[n]; if (flags) flags = ",\n      " flags;

	  printf("  { /* %s */ %s_%s_class, offsetof(%s_t, %s_%s)%s }%s\n",
		 substr(lower_case, i, 1),
		 tprefix, n, module, prefix, n, flags, c)	\
	    > PT NEW;
	}
	else {
	  printf("  { NULL }%s\n", c) \
	    > PT NEW;
	}
      }
      printf("};\n\n") > PT NEW;
    }

    # printf("extern msg_hclass_t msg_multipart_class[];\n\n") > PT NEW;

    printf("msg_mclass_t const %s_mclass[1] = \n{{\n", module) > PT NEW;
    printf("# if defined (%s_HCLASS)\n", toupper(module)) > PT NEW;
    printf("  %s_HCLASS,\n", toupper(module)) > PT NEW;
    printf("#else\n") > PT NEW;
    printf("  {{ 0 }},\n") > PT NEW;
    printf("#endif\n") > PT NEW;
    printf("  %s_VERSION_CURRENT,\n", toupper(module)) > PT NEW;
    printf("  %s_PROTOCOL_TAG,\n", toupper(module)) > PT NEW;
    printf("#if defined (%s_PARSER_FLAGS)\n", toupper(module)) > PT NEW;
    printf("  %s_PARSER_FLAGS,\n", toupper(module)) > PT NEW;
    printf("#else\n") > PT NEW;
    printf("  0,\n") > PT NEW;
    printf("#endif\n") > PT NEW;
    printf("  sizeof(%s_t),\n", module) > PT NEW;
    printf("  %s_extract_body,\n", module) > PT NEW;

    len = split("request status separator payload unknown error", unnamed, " ");

    for (i = 1; i <= len; i++) {
      printf("  {{ %s_%s_class, offsetof(%s_t, %s_%s) }},\n",
	     tprefix, unnamed[i], module, prefix, unnamed[i]) > PT NEW;
    }
    if (multipart) {
      printf("  {{ %s_class, offsetof(%s_t, %s_multipart) }},\n",
	     multipart, module, prefix) > PT NEW;
    } else {
      printf("  {{ NULL, 0 }},\n") > PT NEW;
    }
    if (MC_SHORT_SIZE) {
      printf("  %s_short_forms, \n", module) > PT NEW;
    }
    else {
      printf("  NULL, \n") > PT NEW;
    }
    printf("  %d, %d, \n", MC_HASH_SIZE, total) > PT NEW;
    printf("  {\n") > PT NEW;

    for (i = 0; i < total; i++) {
      n = headers[i];
      h = hashed[n];

      if (h < 0)
	continue;

      for (j = h % MC_HASH_SIZE; j in header_hash;) {
	if (++j == MC_HASH_SIZE) {
	  j = 0;
	}
      }

      header_hash[j] = n;
    }

    for (i = 0; i < MC_HASH_SIZE; i++) {
      c = (i + 1 == MC_HASH_SIZE) ? "" : ",";
      if (i in header_hash) {
	n = header_hash[i];
        flags = header_flags[n]; if (flags) flags = ",\n      " flags;

	printf("    { %s_%s_class, offsetof(%s_t, %s_%s)%s }%s\n",
	       tprefix, n, module, prefix, n, flags, c) > PT NEW;
      }
      else {
	printf("    { NULL, 0 }%s\n", c) > PT NEW;
      }
    }
    printf("  }\n}};\n\n") > PT NEW;
  }
}

#! /usr/bin/env awk
#
# This script recreates C files containing header-specific boilerplate stuff
# using the given list of headers (usually obtained from the master structure).
#
# It can also create a parser table.
#
# --------------------------------------------------------------------
#
# This file is part of the Sofia-SIP package
#
# Copyright (C) 2005 Nokia Corporation.
#
# Contact: Pekka Pessi <pekka.pessi@nokia.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2.1 of
# the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# --------------------------------------------------------------------
#
# Contributor(s): Pekka.Pessi@nokia.com.
#
# Created: Fri Apr  6 12:59:59 2001 ppessi
#

BEGIN {
  "date '+%a %b %e %H:%M:%S %Y'" | getline date;
  "whoami" | getline whoami;

  ascii =			       \
     "                               " \
    " !\"#$%&'()*+,-./0123456789:;<=>?" \
    "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" \
    "`abcdefghijklmnopqrstuvwxyz{|}~";
  lower_case = "abcdefghijklmnopqrstuvwxyz";

  N=0;
  # Initialize these as arrays
  split("", symbols);
  split("", names);
  split("", comments);
  split("", hashes);
  split("", NAMES);
  split("", Comments);
  split("", COMMENTS);
}

function name_hash (name)
{
  hash = 0;

  len = split(name, chars, "");

  for (i = 1; i <= len; i++) {
    c = tolower(chars[i]);
    hash = (38501 * (hash + index(ascii, c))) % 65536;
  }

  if (0) {
    # Test that hash algorithm above agrees with the C version
    pipe = ("../msg/msg_name_hash " name);
    pipe | getline;
    close(pipe);
    if (hash != $0) {
      print name ": " hash " != " $0 > "/dev/stderr";
    }
  }

  return hash "";
}

#
# Replace magic patterns in template p with header-specific values
#
function protos (name, comment, hash)
{
  NAME=toupper(name);
  sub(/.*[\/][*][*][<][ 	]*/, "", comment);
  sub(/[ 	]*[*][\/].*/, "", comment);
  sub(/[ 	]+/, " ", comment);

  short = match(comment, /[(][a-z][)]/);
  if (short) {
    short = substr(comment, short + 1, 1);
    sub(/ *[(][a-z][)]/, "", comment);
    shorts[index(lower_case, short)] = name;
  }

  do_hash = hash == 0;

  if (do_hash) {
    split(comment, parts, " ");
    name2 = tolower(parts[1]);
    gsub(/-/, "_", name2);
    if (name2 != name) {
      print name " mismatch with " comment " (" real ")" > "/dev/stderr";
    }

    hash = name_hash(parts[1]);

    hashed[name] = hash;

    if (comment !~ /header/) {
      comment = comment " header";
    }
  }

  Comment = comment;
  if (!do_hash) {
    comment = tolower(comment);
  }
  COMMENT = toupper(comment);

  # Store the various forms into an array for the footer processing
  N++;
  hashes[N] = hash;
  names[N] = name;
  NAMES[N] = NAME;
  comments[N] = comment;
  Comments[N] = comment;
  COMMENTS[N] = COMMENT;

  symbols[name] = comment;

  if (PR) {
    replace(template, hash, name, NAME, comment, Comment, COMMENT);
    replace(template1, hash, name, NAME, comment, Comment, COMMENT);
    replace(template2, hash, name, NAME, comment, Comment, COMMENT);
    replace(template3, hash, name, NAME, comment, Comment, COMMENT);
  }
}

function replace (p, hash, name, NAME, comment, Comment, COMMENT)
{
  if (p) {
    gsub(/#hash#/, hash, p);
    gsub(/#xxxxxx#/, name, p);
    gsub(/#XXXXXX#/, NAME, p);
    gsub(/#xxxxxxx_xxxxxxx#/, comment, p);
    gsub(/#Xxxxxxx_Xxxxxxx#/, Comment, p);
    gsub(/#XXXXXXX_XXXXXXX#/, COMMENT, p);

    print p > PR NEW;
  }
}

#
# Repeat each line in the footer containing the magic replacement
# pattern with a instance of all headers
#
function process_footer (text)
{
  if (!match(tolower(text), /#(xxxxxx(x_xxxxxxx)?|hash)#/)) {
    n = length(text);
    while (substr(text, n) == "\n") {
      n = n - 1;
      text = substr(text, 1, n);
    }
    if (n > 0)
      print text > PR NEW;
    return;
  }

  n = split(text, lines, RS);

  for (i = 1; i <= n; i++) {
    l = lines[i];
    if (match(tolower(l), /#(xxxxxx(x_xxxxxxx)?|hash)#/)) {
      for (j = 1; j <= N; j++) {
	l = lines[i];
	gsub(/#hash#/, hashes[j], l);
	gsub(/#xxxxxxx_xxxxxxx#/, comments[j], l);
	gsub(/#Xxxxxxx_Xxxxxxx#/, Comments[j], l);
	gsub(/#XXXXXXX_XXXXXXX#/, COMMENTS[j], l);
	gsub(/#xxxxxx#/, names[j], l);
	gsub(/#XXXXXX#/, NAMES[j], l);
	print l > PR NEW;
      }
    } else {
      print l > PR NEW;
    }
  }
}

#
# Read flags used with headers
#
function read_header_flags (flagfile,    line, tokens, name, value)
{
  while ((getline line < flagfile) > 0) {
    sub(/^[ \t]+/, "", line);
    sub(/[ \t]+$/, "", line);
    if (line ~ /^#/ || line ~ /^$/)
      continue;

    split(line, tokens,  /[ \t]*=[ \t]*/);
    name = tolower(tokens[1]);
    gsub(/-/, "_", name);
    gsub(/,/, " ", name);
    # print "FLAG: " name " = " tokens[2]

    if (header_flags[name]) {
      print flagfile ": already defined " tokens[1];
    }
    else if (!symbols[name]) {
      print flagfile ": unknown header \"" tokens[1] "\"";
    }
    else {
      header_flags[name] = tokens[2];
    }
  }
  close(flagfile);
}

/^ *\/\* === Headers start here \*\// {

  in_header_list=1;

  if (!auto) {
    auto = FILENAME;
    sub(/.*\//, "", auto);
    auto = "This file is automatically generated from <" auto "> by msg_parser.awk.";

    if (PR) {
      if (TEMPLATE == "") { TEMPLATE = PR ".in"; }
      RS0=RS; RS="\n";
      getline theader < TEMPLATE;
      getline header < TEMPLATE;
      getline template < TEMPLATE;
      getline footer < TEMPLATE;

      if (TEMPLATE1) {
	getline dummy < TEMPLATE1;
	getline dummy < TEMPLATE1;
	getline template1 < TEMPLATE1;
	getline dummy < TEMPLATE1;
      }

      if (TEMPLATE2) {
	getline dummy < TEMPLATE2;
	getline dummy < TEMPLATE2;
	getline template2 < TEMPLATE2;
	getline dummy < TEMPLATE2;
      }

      if (TEMPLATE3) {
	getline dummy < TEMPLATE3;
	getline dummy < TEMPLATE3;
	getline template3 < TEMPLATE3;
	getline dummy < TEMPLATE3;
      }

      sub(/.*[\/]/, "", TEMPLATE);
      gsub(/#AUTO#/, auto, header);
      gsub(/#DATE#/, "@date Generated: " date, header);
      print header > PR NEW;

      RS=RS0;
    }

    if (!NO_FIRST) {
      protos("request", "/**< Request line */", -1);
      protos("status", "/**< Status line */", -2);
    }
  }
}

in_header_list && /^ *\/\* === Headers end here \*\// { in_header_list=0;}

in_header_list && PT && /^ *\/\* === Hash headers end here \*\// { in_header_list=0;}

in_header_list && /^  (sip|rtsp|http|msg|mp)_[a-z_0-9]+_t/ {
  n=$0
  sub(/;.*$/, "", n);
  sub(/^ *(sip|rtsp|http|msg|mp)_[a-z0-9_]*_t[ 	]*/, "", n);
  sub(/^[*](sip|rtsp|http|msg|mp)_/, "", n);

  if ($0 !~ /[\/][*][*][<]/) {
    getline;
  }
  if ($0 !~ /[\/][*][*][<]/) {
    printf "msg_protos.awk: header %s is malformed\n", n;
    exit 1;
  }

  if (!NO_MIDDLE)
    protos(n, $0, 0);

  headers[total++] = n;
}

END {
  if (!prefix) { prefix = module; }
  if (!tprefix) { tprefix = prefix; }

  if (!NO_LAST) {
    protos("unknown", "/**< Unknown headers */", -3);
    protos("error", "/**< Erroneous headers */", -4);
    protos("separator", "/**< Separator line between headers and payload */", -5);
    protos("payload", "/**< Message payload */", -6);
    if (multipart)
      protos("multipart", "/**< Multipart payload */", -7);
  }

  if (PR) {
    process_footer(footer);
  }
  else if (PT) {
    if (FLAGFILE)
      read_header_flags(FLAGFILE);

    if (TEMPLATE == "") { TEMPLATE = PT ".in"; }
    RS0=RS; RS="\n";
    getline theader < TEMPLATE;
    getline header < TEMPLATE;
    getline template < TEMPLATE;
    getline footer < TEMPLATE;
    RS=RS0;

    sub(/.*[\/]/, "", TEMPLATE);
    gsub(/#AUTO#/, auto, header);
    gsub(/#DATE#/, "@date Generated: " date, header);
    print header > PT NEW;

    if (MC_SHORT_SIZE) {
      printf("static msg_href_t const " \
	     "%s_short_forms[MC_SHORT_SIZE] = \n{\n",
	     module) > PT NEW;

      for (i = 1; i <= MC_SHORT_SIZE; i = i + 1) {
	c = (i == MC_SHORT_SIZE) ? "" : ",";
	if (i in shorts) {
	  n = shorts[i];
        flags = header_flags[n]; if (flags) flags = ",\n      " flags;

	  printf("  { /* %s */ %s_%s_class, offsetof(%s_t, %s_%s)%s }%s\n",
		 substr(lower_case, i, 1),
		 tprefix, n, module, prefix, n, flags, c)	\
	    > PT NEW;
	}
	else {
	  printf("  { NULL }%s\n", c) \
	    > PT NEW;
	}
      }
      printf("};\n\n") > PT NEW;
    }

    # printf("extern msg_hclass_t msg_multipart_class[];\n\n") > PT NEW;

    printf("msg_mclass_t const %s_mclass[1] = \n{{\n", module) > PT NEW;
    printf("# if defined (%s_HCLASS)\n", toupper(module)) > PT NEW;
    printf("  %s_HCLASS,\n", toupper(module)) > PT NEW;
    printf("#else\n") > PT NEW;
    printf("  {{ 0 }},\n") > PT NEW;
    printf("#endif\n") > PT NEW;
    printf("  %s_VERSION_CURRENT,\n", toupper(module)) > PT NEW;
    printf("  %s_PROTOCOL_TAG,\n", toupper(module)) > PT NEW;
    printf("#if defined (%s_PARSER_FLAGS)\n", toupper(module)) > PT NEW;
    printf("  %s_PARSER_FLAGS,\n", toupper(module)) > PT NEW;
    printf("#else\n") > PT NEW;
    printf("  0,\n") > PT NEW;
    printf("#endif\n") > PT NEW;
    printf("  sizeof(%s_t),\n", module) > PT NEW;
    printf("  %s_extract_body,\n", module) > PT NEW;

    len = split("request status separator payload unknown error", unnamed, " ");

    for (i = 1; i <= len; i++) {
      printf("  {{ %s_%s_class, offsetof(%s_t, %s_%s) }},\n",
	     tprefix, unnamed[i], module, prefix, unnamed[i]) > PT NEW;
    }
    if (multipart) {
      printf("  {{ %s_class, offsetof(%s_t, %s_multipart) }},\n",
	     multipart, module, prefix) > PT NEW;
    } else {
      printf("  {{ NULL, 0 }},\n") > PT NEW;
    }
    if (MC_SHORT_SIZE) {
      printf("  %s_short_forms, \n", module) > PT NEW;
    }
    else {
      printf("  NULL, \n") > PT NEW;
    }
    printf("  %d, %d, \n", MC_HASH_SIZE, total) > PT NEW;
    printf("  {\n") > PT NEW;

    for (i = 0; i < total; i++) {
      n = headers[i];
      h = hashed[n];

      if (h < 0)
	continue;

      for (j = h % MC_HASH_SIZE; j in header_hash;) {
	if (++j == MC_HASH_SIZE) {
	  j = 0;
	}
      }

      header_hash[j] = n;
    }

    for (i = 0; i < MC_HASH_SIZE; i++) {
      c = (i + 1 == MC_HASH_SIZE) ? "" : ",";
      if (i in header_hash) {
	n = header_hash[i];
        flags = header_flags[n]; if (flags) flags = ",\n      " flags;

	printf("    { %s_%s_class, offsetof(%s_t, %s_%s)%s }%s\n",
	       tprefix, n, module, prefix, n, flags, c) > PT NEW;
      }
      else {
	printf("    { NULL, 0 }%s\n", c) > PT NEW;
      }
    }
    printf("  }\n}};\n\n") > PT NEW;
  }
}


Main: update default user-agent

Main: update default user-agent

####################################################################
# Main.awk                                                         #
####################################################################

# Initialization.
function init() {
    initGawk()
    initBiDiTerm()
    initBiDi()

    # (LanguageData.awk & LanguageHelper.awk)
    initLocale()
    initLocaleAlias()
    initUserLang()

    RS = "\n"

    ExitCode = 0

    Option["debug"] = 0

    # Translation engine
    Option["engine"] = "auto"

    # Display
    Option["verbose"] = 1
    Option["show-original"] = 1
    Option["show-original-phonetics"] = 1
    Option["show-translation"] = 1
    Option["show-translation-phonetics"] = 1
    Option["show-prompt-message"] = 1
    Option["show-languages"] = 1
    Option["show-original-dictionary"] = 0
    Option["show-dictionary"] = 1
    Option["show-alternatives"] = 1
    Option["width"] = ENVIRON["COLUMNS"] ? ENVIRON["COLUMNS"] - 2 : 0
    Option["indent"] = 4
    Option["no-ansi"] = 0
    Option["no-autocorrect"] = 0
    Option["no-bidi"] = 0
    Option["force-bidi"] = 0
    Option["no-warn"] = 0
    Option["theme"] = "default"
    Option["dump"] = 0

    # Audio
    Option["play"] = 0
    Option["narrator"] = "female"
    Option["player"] = ENVIRON["PLAYER"]
    Option["no-translate"] = 0
    Option["download-audio"] = 0
    Option["download-audio-as"] = NULLSTR

    # Terminal paging and browsing
    Option["view"] = 0
    Option["pager"] = ENVIRON["PAGER"]
    Option["browser"] = ENVIRON["BROWSER"]

    # Networking
    Option["proxy"] = ENVIRON["HTTP_PROXY"] ? ENVIRON["HTTP_PROXY"] : ENVIRON["http_proxy"]
    Option["user-agent"] = ENVIRON["USER_AGENT"] ? ENVIRON["USER_AGENT"] :
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "         \
        "AppleWebKit/537.36 (KHTML, like Gecko) "            \
        "Chrome/104.0.0.0 "                                  \
        "Safari/537.36 "                                     \
        "Edg/104.0.1293.54"
    Option["ip-version"] = 0

    # Interactive shell
    Option["no-rlwrap"] = 0
    Option["interactive"] = 0
    Option["emacs"] = 0

    # I/O
    Option["input"] = NULLSTR
    Option["output"] = STDOUT

    # Language preference
    Option["hl"] = ENVIRON["HOST_LANG"] ? ENVIRON["HOST_LANG"] :
        ENVIRON["HOME_LANG"] ? ENVIRON["HOME_LANG"] : UserLang # FIXME[1.0]: HOME_LANG to be removed
    Option["sl"] = ENVIRON["SOURCE_LANG"] ? ENVIRON["SOURCE_LANG"] : "auto"
    Option["sls"][1] = Option["sl"]
    Option["tl"][1] = ENVIRON["TARGET_LANG"] ? ENVIRON["TARGET_LANG"] : UserLang

    # Text preprocessing
    Option["join-sentence"] = 0
}

# Initialization script.
function initScript(    file, line, script, temp) {
    # Find the initialization file
    file = ".trans"
    if (!fileExists(file)) {
        file = ENVIRON["HOME"] "/.translate-shell/init.trans"
        if (!fileExists(file)) {
            file = ENVIRON["XDG_CONFIG_HOME"] "/translate-shell/init.trans"
            if (!fileExists(file)) {
                file = ENVIRON["HOME"] "/.config/translate-shell/init.trans"
                if (!fileExists(file)) {
                    file = "/etc/translate-shell"
                    if (!fileExists(file)) return
                }
            }
        }
    }

    InitScript = file
    script = NULLSTR
    while (getline line < InitScript)
        script = script "\n" line
    loadOptions(script)

    # HACK: Option["tl"] must be an array
    if (!isarray(Option["tl"])) {
        temp = Option["tl"]
        delete Option["tl"]
        Option["tl"][1] = temp
    }
}

# Miscellany initialization.
function initMisc(    command, group, temp) {
    # (Translate.awk)
    initHttpService()

    # Initialize screen width if not set
    if (!Option["width"] && detectProgram("tput", "-V")) {
        command = "tput cols" SUPERR
        command | getline temp
        close(command)
        Option["width"] = temp > 5 ? temp - 2 : 64  # minimum screen width: 4
    }

    # Disable ANSI escape codes if required
    if (Option["no-ansi"])
        delete AnsiCode

    # Disable conversion of bidirectional texts if required or supported by emulator
    if (Option["no-bidi"] || BiDiTerm == "mlterm")
        # mlterm implements its own padding
        BiDi = BiDiNoPad = NULLSTR
    else if (!Option["force-bidi"] && BiDiTerm == "konsole") {
        # konsole implements no padding; we should handle this
        BiDiNoPad = NULLSTR
        BiDi = "sed \"s/'/\\\\\\'/\" | xargs -0 printf '%%%ss'"
    }

    # (LanguageData.awk)
    initLocaleDisplay()

    # Disable everything stderr
    if (Option["no-warn"])
        STDERR = "/dev/null"

    # Initialize audio player or speech synthesizer
    if (Option["play"]) {
        if (!Option["player"]) {
            initAudioPlayer()
            Option["player"] = AudioPlayer ? AudioPlayer : Option["player"]
            if (!Option["player"])
                initSpeechSynthesizer()
        }

        if (!Option["player"] && !SpeechSynthesizer) {
            w("[WARNING] No available audio player or speech synthesizer.")
            Option["play"] = 0
        }
    }

    # Initialize pager
    if (Option["view"]) {
        if (!Option["pager"]) {
            initPager()
            Option["pager"] = Pager
        }

        if (!Option["pager"]) {
            w("[WARNING] No available terminal pager.")
            Option["view"] = 0
        }
    }

    # Initialize browser
    if (!Option["browser"]) {
        Platform = detectProgram("uname", "-s", 1)
        Option["browser"] = Platform == "Darwin" ? "open" : "xdg-open"
    }
}

# Main entry point.
BEGIN {
    init()

    if (!(belongsTo("-no-init", ARGV) || belongsTo("--no-init", ARGV)))
        initScript() # initialization script overrides default setting

    # Command-line options override initialization script
    pos = 0
    noargc = 0
    while (ARGV[++pos]) {
        ## Information options

        # -V, -version
        match(ARGV[pos], /^--?(V|vers(i(on?)?)?)$/)
        if (RSTART) {
            InfoOnly = "version"
            continue
        }

        # -H, -help
        match(ARGV[pos], /^--?(H|h(e(lp?)?)?)$/)
        if (RSTART) {
            InfoOnly = "help"
            continue
        }

        # -M, -man
        match(ARGV[pos], /^--?(M|m(a(n(u(al?)?)?)?)?)$/)
        if (RSTART) {
            InfoOnly = "manual"
            continue
        }

        # -T, -reference
        match(ARGV[pos], /^--?(T|ref(e(r(e(n(ce?)?)?)?)?)?)$/)
        if (RSTART) {
            InfoOnly = "reference"
            continue
        }

        # FIXME[1.0]: to be removed
        match(ARGV[pos], /^--?r$/)
        if (RSTART) {
            w("[ERROR] Option '-r' has been deprecated since version 0.9.\n" \
              "        Use option '-T' or '-reference' instead.")
            exit 1
        }

        # -R, -reference-english
        match(ARGV[pos], /^--?(R|reference-e(n(g(l(i(sh?)?)?)?)?)?)$/)
        if (RSTART) {
            InfoOnly = "reference-english"
            continue
        }

        # -S, -list-engines
        match(ARGV[pos], /^--?(S|list-e(n(g(i(n(es?)?)?)?)?)?)$/)
        if (RSTART) {
            InfoOnly = "list-engines"
            continue
        }

        # -list-languages
        match(ARGV[pos], /^--?(list-languages)$/)
        if (RSTART) {
            InfoOnly = "list-languages"
            continue
        }

        # -list-languages-english
        match(ARGV[pos], /^--?(list-languages-english)$/)
        if (RSTART) {
            InfoOnly = "list-languages-english"
            continue
        }

        # -list-codes
        match(ARGV[pos], /^--?(list-codes)$/)
        if (RSTART) {
            InfoOnly = "list-codes"
            continue
        }

        # -list-all
        match(ARGV[pos], /^--?(list-all)$/)
        if (RSTART) {
            InfoOnly = "list-all"
            continue
        }

        # -L CODES, -linguist CODES
        match(ARGV[pos], /^--?(L|linguist)(=(.*)?)?$/, group)
        if (RSTART) {
            InfoOnly = "language"
            if (group[2]) {
                if (group[3]) split(group[3], Option["tl"], "+")
            } else
                split(ARGV[++pos], Option["tl"], "+")
            continue
        }
        # FIXME[1.0]: to be removed
        # -list CODE
        match(ARGV[pos], /^--?(list)(=(.*)?)?$/, group)
        if (RSTART) {
            w("[WARNING] Option '-list' will be deprecated in the next version.\n" \
              "          Use '-L' / '-linguist' instead.")
            InfoOnly = "language"
            if (group[2]) {
                if (group[3]) split(group[3], Option["tl"], "+")
            } else
                split(ARGV[++pos], Option["tl"], "+")
            continue
        }

        # -U, -upgrade
        match(ARGV[pos], /^--?(U|upgrade)$/)
        if (RSTART) {
            InfoOnly = "upgrade"
            continue
        }

        # -N, -nothing
        match(ARGV[pos], /^--?(N|nothing)$/)
        if (RSTART) {
            InfoOnly = "nothing"
            continue
        }

        ## Translator options

        # -e ENGINE, -engine ENGINE
        match(ARGV[pos], /^--?(e|engine)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["engine"] = group[2] ?
                (group[3] ? group[3] : Option["engine"]) :
                ARGV[++pos]
            continue
        }

        # Shortcut format
        # '/ENGINE'
        match(ARGV[pos], /^\/(.*)$/, group)
        if (RSTART) {
            Option["engine"] = group[1]
            continue
        }

        ## Display options

        # -verbose
        match(ARGV[pos], /^--?verbose$/)
        if (RSTART) {
            Option["verbose"] = 1 # default value
            continue
        }

        # -b, -brief
        match(ARGV[pos], /^--?b(r(i(ef?)?)?)?$/)
        if (RSTART) {
            Option["verbose"] = 0
            continue
        }

        # -d, -dictionary
        match(ARGV[pos], /^--?d(i(c(t(i(o(n(a(ry?)?)?)?)?)?)?)?)?$/)
        if (RSTART) {
            Option["show-original-dictionary"] = 1
            Option["show-dictionary"] = 0
            Option["show-alternatives"] = 0
            continue
        }

        # -identify
        match(ARGV[pos], /^--?id(e(n(t(i(fy?)?)?)?)?)?$/)
        if (RSTART) {
            Option["verbose"] = Option["verbose"] - 2
            continue
        }

        # -show-original Y/n
        match(ARGV[pos], /^--?show-original(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-original-phonetics Y/n
        match(ARGV[pos], /^--?show-original-phonetics(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original-phonetics"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-translation Y/n
        match(ARGV[pos], /^--?show-translation(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-translation"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-translation-phonetics Y/n
        match(ARGV[pos], /^--?show-translation-phonetics(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-translation-phonetics"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-prompt-message Y/n
        match(ARGV[pos], /^--?show-prompt-message(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-prompt-message"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-languages Y/n
        match(ARGV[pos], /^--?show-languages(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-languages"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-original-dictionary y/N
        match(ARGV[pos], /^--?show-original-dictionary(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original-dictionary"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-dictionary Y/n
        match(ARGV[pos], /^--?show-dictionary(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-dictionary"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-alternatives Y/n
        match(ARGV[pos], /^--?show-alternatives(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-alternatives"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -w NUM, -width NUM
        match(ARGV[pos], /^--?w(i(d(th?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["width"] = group[4] ?
                (group[5] ? group[5] : Option["width"]) :
                ARGV[++pos]
            continue
        }

        # -indent NUM
        match(ARGV[pos], /^--?indent(=(.*)?)?$/, group)
        if (RSTART) {
            Option["indent"] = group[1] ?
                (group[2] ? group[2] : Option["indent"]) :
                ARGV[++pos]
            continue
        }

        # -theme FILENAME
        match(ARGV[pos], /^--?theme(=(.*)?)?$/, group)
        if (RSTART) {
            Option["theme"] = group[1] ?
                (group[2] ? group[2] : Option["theme"]) :
                ARGV[++pos]
            continue
        }

        # -no-theme
        match(ARGV[pos], /^--?no-theme$/)
        if (RSTART) {
            Option["theme"] = NULLSTR
            continue
        }

        # -no-ansi
        match(ARGV[pos], /^--?no-ansi$/)
        if (RSTART) {
            Option["no-ansi"] = 1
            continue
        }

        # -no-autocorrect
        match(ARGV[pos], /^--?no-auto(correct)?$/)
        if (RSTART) {
            Option["no-autocorrect"] = 1
            continue
        }

        # -no-bidi
        match(ARGV[pos], /^--?no-bidi/)
        if (RSTART) {
            Option["no-bidi"] = 1
            continue
        }

        # -bidi
        match(ARGV[pos], /^--?bidi/)
        if (RSTART) {
            Option["force-bidi"] = 1
            continue
        }

        # -no-warn
        match(ARGV[pos], /^--?no-warn/)
        if (RSTART) {
            Option["no-warn"] = 1
            continue
        }

        # -dump
        match(ARGV[pos], /^--?dump/)
        if (RSTART) {
            Option["dump"] = 1
            continue
        }

        ## Audio options

        # -p, -play
        match(ARGV[pos], /^--?p(l(ay?)?)?$/)
        if (RSTART) {
            Option["play"] = 1
            continue
        }

        # -speak
        match(ARGV[pos], /^--?sp(e(ak?)?)?$/)
        if (RSTART) {
            Option["play"] = 2
            continue
        }

        # -n VOICE, -narrator VOICE
        match(ARGV[pos], /^--?(n|narrator)(=(.*)?)?$/, group)
        if (RSTART) {
            if (!Option["play"]) Option["play"] = 1 # -play by default
            Option["narrator"] = group[2] ?
                (group[3] ? group[3] : Option["narrator"]) :
                ARGV[++pos]
            continue
        }

        # -player PROGRAM
        match(ARGV[pos], /^--?player(=(.*)?)?$/, group)
        if (RSTART) {
            if (!Option["play"]) Option["play"] = 1 # -play by default
            Option["player"] = group[1] ?
                (group[2] ? group[2] : Option["player"]) :
                ARGV[++pos]
            continue
        }

        # -no-play
        match(ARGV[pos], /^--?no-play$/)
        if (RSTART) {
            Option["play"] = 0
            continue
        }

        # -no-translate
        match(ARGV[pos], /^--?no-tran(s(l(a(te?)?)?)?)?$/)
        if (RSTART) {
            Option["no-translate"] = 1
            continue
        }

        # -download-audio
        match(ARGV[pos], /^--?download-a(u(d(io?)?)?)?$/)
        if (RSTART) {
            Option["download-audio"] = 1
            continue
        }

        # -download-audio-as
        match(ARGV[pos], /^--?download-audio-as(=(.*)?)?$/, group)
        if (RSTART) {
            if (!Option["download-audio"]) Option["download-audio"] = 1
            Option["download-audio-as"] = group[1] ?
                (group[2] ? group[2] : Option["download-audio-as"]) :
                ARGV[++pos]
            continue
        }

        ## Terminal paging and browsing options

        # -v, -view
        match(ARGV[pos], /^--?v(i(ew?)?)?$/)
        if (RSTART) {
            Option["view"] = 1
            continue
        }

        # -pager PROGRAM
        match(ARGV[pos], /^--?pager(=(.*)?)?$/, group)
        if (RSTART) {
            Option["view"] = 1
            Option["pager"] = group[1] ?
                (group[2] ? group[2] : Option["pager"]) :
                ARGV[++pos]
            continue
        }

        # -no-view, -no-pager
        match(ARGV[pos], /^--?no-(view|pager)$/)
        if (RSTART) {
            Option["view"] = 0
            continue
        }

        # -browser PROGRAM
        match(ARGV[pos], /^--?browser(=(.*)?)?$/, group)
        if (RSTART) {
            Option["browser"] = group[1] ?
                (group[2] ? group[2] : Option["browser"]) :
                ARGV[++pos]
            continue
        }

        # -no-browser
        match(ARGV[pos], /^--?no-browser$/)
        if (RSTART) {
            Option["browser"] = NONE
            continue
        }

        ## Networking options

        # -x HOST:PORT, -proxy HOST:PORT
        match(ARGV[pos], /^--?(x|proxy)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["proxy"] = group[2] ?
                (group[3] ? group[3] : Option["proxy"]) :
                ARGV[++pos]
            continue
        }

        # -u STRING, -user-agent STRING
        match(ARGV[pos], /^--?(u|user-agent)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["user-agent"] = group[2] ?
                (group[3] ? group[3] : Option["user-agent"]) :
                ARGV[++pos]
            continue
        }

        # -4, -ipv4, -inet4-only
        match(ARGV[pos], /^--?(4|ipv4|inet4-only)$/)
        if (RSTART) {
            Option["ip-version"] = 4
            continue
        }

        # -6, -ipv6, -inet6-only
        match(ARGV[pos], /^--?(6|ipv6|inet6-only)$/)
        if (RSTART) {
            Option["ip-version"] = 6
            continue
        }

        ## Interactive shell options

        # -I, -interactive, -shell
        match(ARGV[pos], /^--?(I|int(e(r(a(c(t(i(ve?)?)?)?)?)?)?)?|shell)$/)
        if (RSTART) {
            Option["interactive"] = 1
            continue
        }

        # -E, -emacs
        match(ARGV[pos], /^--?(E|emacs)$/)
        if (RSTART) {
            Option["emacs"] = 1
            continue
        }

        # -no-rlwrap
        match(ARGV[pos], /^--?no-rlwrap$/)
        if (RSTART) {
            Option["no-rlwrap"] = 1
            continue
        }

        # FIXME[1.0]: to be removed
        # -prompt PROMPT_STRING
        match(ARGV[pos], /^--?prompt(=(.*)?)?$/, group)
        if (RSTART) {
            w("[ERROR] Option '-prompt' has been deprecated since version 0.9.\n" \
              "        Use configuration variable 'fmt-prompt' instead.")
            exit 1
        }

        # FIXME[1.0]: to be removed
        # -prompt-color COLOR_CODE
        match(ARGV[pos], /^--?prompt-color(=(.*)?)?$/, group)
        if (RSTART) {
            w("[ERROR] Option '-prompt-color' has been deprecated since version 0.9.\n" \
              "        Use configuration variable 'sgr-prompt' instead.")
            exit 1
        }

        ## I/O options

        # -i FILENAME, -input FILENAME
        match(ARGV[pos], /^--?i(n(p(ut?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["input"] = group[4] ?
                (group[5] ? group[5] : Option["input"]) :
                ARGV[++pos]
            continue
        }

        # -o FILENAME, -output FILENAME
        match(ARGV[pos], /^--?o(u(t(p(ut?)?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["output"] = group[5] ?
                (group[6] ? group[6] : Option["output"]) :
                ARGV[++pos]
            continue
        }

        ## Language preference options

        # -hl CODE, -host CODE
        match(ARGV[pos], /^--?(host|hl)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["hl"] = group[2] ?
                (group[3] ? group[3] : Option["hl"]) :
                ARGV[++pos]
            continue
        }
        # FIXME[1.0]: to be removed
        # -l CODE, -lang CODE
        match(ARGV[pos], /^--?(l(a(ng?)?)?)(=(.*)?)?$/, group)
        if (RSTART) {
            w("[WARNING] Option '-l' / '-lang' will be deprecated in the next version.\n" \
              "          Use '-hl' / '-host' instead.")
            Option["hl"] = group[4] ?
                (group[5] ? group[5] : Option["hl"]) :
                ARGV[++pos]
            continue
        }

        # -s CODES, -sl CODES, -source CODES, -f CODES, -from CODES
        match(ARGV[pos], /^--?(s(o(u(r(ce?)?)?)?|l)?|f|from)(=(.*)?)?$/, group)
        if (RSTART) {
            if (group[6]) {
                if (group[7]) split(group[7], Option["sls"], "+")
            } else
                split(ARGV[++pos], Option["sls"], "+")
            Option["sl"] = Option["sls"][1]
            continue
        }

        # -t CODES, -tl CODES, -target CODES, -to CODES
        match(ARGV[pos], /^--?t(a(r(g(et?)?)?)?|l|o)?(=(.*)?)?$/, group)
        if (RSTART) {
            if (group[5]) {
                if (group[6]) split(group[6], Option["tl"], "+")
            } else
                split(ARGV[++pos], Option["tl"], "+")
            continue
        }

        # Shortcut format
        # 'CODE:CODE+...' or 'CODE=CODE+...'
        match(ARGV[pos], /^[{(\[]?((@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]][[:alpha:]]?[[:alpha:]]?)?\+)*(@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]][[:alpha:]]?[[:alpha:]]?)?)?)?(:|=)((@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]][[:alpha:]]?[[:alpha:]]?)?\+)*(@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]][[:alpha:]]?[[:alpha:]]?)?)?)[})\]]?$/, group)
        if (RSTART) {
            if (group[1]) {
                split(group[1], Option["sls"], "+")
                Option["sl"] = Option["sls"][1]
            }
            if (group[7]) split(group[7], Option["tl"], "+")
            continue
        }

        ## Text preprocessing options

        # -j, -join-sentence
        match(ARGV[pos], /^--?j(o(i(n(-(s(e(n(t(e(n(ce?)?)?)?)?)?)?)?)?)?)?)?$/)
        if (RSTART) {
            Option["join-sentence"] = 1
            continue
        }

        ## Other options

        # -D, -debug
        match(ARGV[pos], /^--?(D|debug)$/)
        if (RSTART) {
            Option["debug"] = 1
            continue
        }

        # -no-init
        match(ARGV[pos], /^--?no-init/)
        if (RSTART) continue # skip

        # -, -no-op
        match(ARGV[pos], /^-(-?no-op)?$/)
        if (RSTART) continue # no operation, skip

        # --
        match(ARGV[pos], /^--$/)
        if (RSTART) {
            ++pos # skip the end-of-options option
            break # no more option from here
        }

        # non-option argument
        noargv[noargc++] = ARGV[pos]
    }

    # Handle interactive shell
    if (Option["interactive"] && !Option["no-rlwrap"])
        rlwrapMe() # interactive mode
    else if (Option["emacs"] && !Option["interactive"] && !Option["no-rlwrap"])
        if (emacsMe()) # emacs front-end
            Option["interactive"] = 1 # fallback to interactive mode

    # Get started

    initMisc()

    # Information-only session
    switch (InfoOnly) {
    case "version":
        print getVersion()
        exit ExitCode
    case "help":
        print getHelp()
        exit ExitCode
    case "manual":
        showMan()
        exit ExitCode
    case "reference":
        print getReference("endonym")
        exit ExitCode
    case "reference-english":
        print getReference("name")
        exit ExitCode
    case "list-engines":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for (translator in Translator)
            print (Option["engine"] == translator ? "* " : "  ") translator
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "list-languages":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for (code in Locale)
            # only show languages that are supported
            if (Locale[code]["supported-by"])
                print getDisplay(Locale[code]["endonym"])
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "list-languages-english":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "compName"
        for (code in Locale)
            # only show languages that are supported
            if (Locale[code]["supported-by"])
                print Locale[code]["name"] #(Locale[code]["name2"] ? " / " Locale[code]["name2"] : "")
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "list-codes":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for (code in Locale)
            # only show languages that are supported
            if (Locale[code]["supported-by"])
                print code
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "list-all":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "compName"
        for (code in Locale)
            # only show languages that are supported
            if (Locale[code]["supported-by"])
                printf("%-10s %-30s %s\n", code, Locale[code]["name"], getDisplay(Locale[code]["endonym"]))
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "language":
        print getLanguage(Option["tl"])
        exit ExitCode
    case "upgrade":
        upgrade()
        exit ExitCode
    case "nothing":
        exit ExitCode
    }

    # Load theme (overrides command-line options) - slow
    setTheme()

    if (Option["interactive"])
        welcome()

    # More remaining non-option arguments
    if (pos < ARGC)
        for (i = pos; i < ARGC; i++)
            noargv[noargc++] = ARGV[i]

    # Text preprocessing: join all arguments if required
    if (noargc > 1 && Option["join-sentence"]) {
        noargv[0] = join(noargv, " ")
        noargc = 1
    }

    if (noargc) {
        # Translate all non-option arguments
        for (i = 0; i < noargc; i++) {
            # Verbose mode: separator between sources
            if (Option["verbose"] && i > pos)
                p(prettify("source-seperator", replicate(Option["chr-source-seperator"], Option["width"])))

            translates(noargv[i], 1) # inline mode
        }

        # If input not specified, we're done
    } else {
        # If input not specified, use stdin
        if (!Option["input"]) Option["input"] = STDIN
    }

    # If input specified, start translating
    if (Option["input"])
        translateMain()

    exit ExitCode
}

####################################################################
# Main.awk                                                         #
####################################################################

# Initialization.
function init() {
    initGawk()
    initBiDiTerm()
    initBiDi()

    # (LanguageData.awk & LanguageHelper.awk)
    initLocale()
    initLocaleAlias()
    initUserLang()

    RS = "\n"

    ExitCode = 0

    Option["debug"] = 0

    # Translation engine
    Option["engine"] = "auto"

    # Display
    Option["verbose"] = 1
    Option["show-original"] = 1
    Option["show-original-phonetics"] = 1
    Option["show-translation"] = 1
    Option["show-translation-phonetics"] = 1
    Option["show-prompt-message"] = 1
    Option["show-languages"] = 1
    Option["show-original-dictionary"] = 0
    Option["show-dictionary"] = 1
    Option["show-alternatives"] = 1
    Option["width"] = ENVIRON["COLUMNS"] ? ENVIRON["COLUMNS"] - 2 : 0
    Option["indent"] = 4
    Option["no-ansi"] = 0
    Option["no-autocorrect"] = 0
    Option["no-bidi"] = 0
    Option["force-bidi"] = 0
    Option["no-warn"] = 0
    Option["theme"] = "default"
    Option["dump"] = 0

    # Audio
    Option["play"] = 0
    Option["narrator"] = "female"
    Option["player"] = ENVIRON["PLAYER"]
    Option["no-translate"] = 0
    Option["download-audio"] = 0
    Option["download-audio-as"] = NULLSTR

    # Terminal paging and browsing
    Option["view"] = 0
    Option["pager"] = ENVIRON["PAGER"]
    Option["browser"] = ENVIRON["BROWSER"]

    # Networking
    Option["proxy"] = ENVIRON["HTTP_PROXY"] ? ENVIRON["HTTP_PROXY"] : ENVIRON["http_proxy"]
    Option["user-agent"] = ENVIRON["USER_AGENT"] ? ENVIRON["USER_AGENT"] :
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) "   \
        "AppleWebKit/537.36 (KHTML, like Gecko) "            \
        "Chrome/81.0.4044.138 "                              \
        "Safari/537.36"
    Option["ip-version"] = 0

    # Interactive shell
    Option["no-rlwrap"] = 0
    Option["interactive"] = 0
    Option["emacs"] = 0

    # I/O
    Option["input"] = NULLSTR
    Option["output"] = STDOUT

    # Language preference
    Option["hl"] = ENVIRON["HOST_LANG"] ? ENVIRON["HOST_LANG"] :
        ENVIRON["HOME_LANG"] ? ENVIRON["HOME_LANG"] : UserLang # FIXME[1.0]: HOME_LANG to be removed
    Option["sl"] = ENVIRON["SOURCE_LANG"] ? ENVIRON["SOURCE_LANG"] : "auto"
    Option["sls"][1] = Option["sl"]
    Option["tl"][1] = ENVIRON["TARGET_LANG"] ? ENVIRON["TARGET_LANG"] : UserLang

    # Text preprocessing
    Option["join-sentence"] = 0
}

# Initialization script.
function initScript(    file, line, script, temp) {
    # Find the initialization file
    file = ".trans"
    if (!fileExists(file)) {
        file = ENVIRON["HOME"] "/.translate-shell/init.trans"
        if (!fileExists(file)) {
            file = ENVIRON["XDG_CONFIG_HOME"] "/translate-shell/init.trans"
            if (!fileExists(file)) {
                file = ENVIRON["HOME"] "/.config/translate-shell/init.trans"
                if (!fileExists(file)) {
                    file = "/etc/translate-shell"
                    if (!fileExists(file)) return
                }
            }
        }
    }

    InitScript = file
    script = NULLSTR
    while (getline line < InitScript)
        script = script "\n" line
    loadOptions(script)

    # HACK: Option["tl"] must be an array
    if (!isarray(Option["tl"])) {
        temp = Option["tl"]
        delete Option["tl"]
        Option["tl"][1] = temp
    }
}

# Miscellany initialization.
function initMisc(    command, group, temp) {
    # (Translate.awk)
    initHttpService()

    # Initialize screen width if not set
    if (!Option["width"] && detectProgram("tput", "-V")) {
        command = "tput cols" SUPERR
        command | getline temp
        close(command)
        Option["width"] = temp > 5 ? temp - 2 : 64  # minimum screen width: 4
    }

    # Disable ANSI escape codes if required
    if (Option["no-ansi"])
        delete AnsiCode

    # Disable conversion of bidirectional texts if required or supported by emulator
    if (Option["no-bidi"] || BiDiTerm == "mlterm")
        # mlterm implements its own padding
        BiDi = BiDiNoPad = NULLSTR
    else if (!Option["force-bidi"] && BiDiTerm == "konsole") {
        # konsole implements no padding; we should handle this
        BiDiNoPad = NULLSTR
        BiDi = "sed \"s/'/\\\\\\'/\" | xargs -0 printf '%%%ss'"
    }

    # (LanguageData.awk)
    initLocaleDisplay()

    # Disable everything stderr
    if (Option["no-warn"])
        STDERR = "/dev/null"

    # Initialize audio player or speech synthesizer
    if (Option["play"]) {
        if (!Option["player"]) {
            initAudioPlayer()
            Option["player"] = AudioPlayer ? AudioPlayer : Option["player"]
            if (!Option["player"])
                initSpeechSynthesizer()
        }

        if (!Option["player"] && !SpeechSynthesizer) {
            w("[WARNING] No available audio player or speech synthesizer.")
            Option["play"] = 0
        }
    }

    # Initialize pager
    if (Option["view"]) {
        if (!Option["pager"]) {
            initPager()
            Option["pager"] = Pager
        }

        if (!Option["pager"]) {
            w("[WARNING] No available terminal pager.")
            Option["view"] = 0
        }
    }

    # Initialize browser
    if (!Option["browser"]) {
        Platform = detectProgram("uname", "-s", 1)
        Option["browser"] = Platform == "Darwin" ? "open" : "xdg-open"
    }
}

# Main entry point.
BEGIN {
    init()

    if (!(belongsTo("-no-init", ARGV) || belongsTo("--no-init", ARGV)))
        initScript() # initialization script overrides default setting

    # Command-line options override initialization script
    pos = 0
    noargc = 0
    while (ARGV[++pos]) {
        ## Information options

        # -V, -version
        match(ARGV[pos], /^--?(V|vers(i(on?)?)?)$/)
        if (RSTART) {
            InfoOnly = "version"
            continue
        }

        # -H, -help
        match(ARGV[pos], /^--?(H|h(e(lp?)?)?)$/)
        if (RSTART) {
            InfoOnly = "help"
            continue
        }

        # -M, -man
        match(ARGV[pos], /^--?(M|m(a(n(u(al?)?)?)?)?)$/)
        if (RSTART) {
            InfoOnly = "manual"
            continue
        }

        # -T, -reference
        match(ARGV[pos], /^--?(T|ref(e(r(e(n(ce?)?)?)?)?)?)$/)
        if (RSTART) {
            InfoOnly = "reference"
            continue
        }

        # FIXME[1.0]: to be removed
        match(ARGV[pos], /^--?r$/)
        if (RSTART) {
            w("[ERROR] Option '-r' has been deprecated since version 0.9.\n" \
              "        Use option '-T' or '-reference' instead.")
            exit 1
        }

        # -R, -reference-english
        match(ARGV[pos], /^--?(R|reference-e(n(g(l(i(sh?)?)?)?)?)?)$/)
        if (RSTART) {
            InfoOnly = "reference-english"
            continue
        }

        # -S, -list-engines
        match(ARGV[pos], /^--?(S|list-e(n(g(i(n(es?)?)?)?)?)?)$/)
        if (RSTART) {
            InfoOnly = "list-engines"
            continue
        }

        # -list-languages
        match(ARGV[pos], /^--?(list-languages)$/)
        if (RSTART) {
            InfoOnly = "list-languages"
            continue
        }

        # -list-languages-english
        match(ARGV[pos], /^--?(list-languages-english)$/)
        if (RSTART) {
            InfoOnly = "list-languages-english"
            continue
        }

        # -list-codes
        match(ARGV[pos], /^--?(list-codes)$/)
        if (RSTART) {
            InfoOnly = "list-codes"
            continue
        }

        # -list-all
        match(ARGV[pos], /^--?(list-all)$/)
        if (RSTART) {
            InfoOnly = "list-all"
            continue
        }

        # -L CODES, -linguist CODES
        match(ARGV[pos], /^--?(L|linguist)(=(.*)?)?$/, group)
        if (RSTART) {
            InfoOnly = "language"
            if (group[2]) {
                if (group[3]) split(group[3], Option["tl"], "+")
            } else
                split(ARGV[++pos], Option["tl"], "+")
            continue
        }
        # FIXME[1.0]: to be removed
        # -list CODE
        match(ARGV[pos], /^--?(list)(=(.*)?)?$/, group)
        if (RSTART) {
            w("[WARNING] Option '-list' will be deprecated in the next version.\n" \
              "          Use '-L' / '-linguist' instead.")
            InfoOnly = "language"
            if (group[2]) {
                if (group[3]) split(group[3], Option["tl"], "+")
            } else
                split(ARGV[++pos], Option["tl"], "+")
            continue
        }

        # -U, -upgrade
        match(ARGV[pos], /^--?(U|upgrade)$/)
        if (RSTART) {
            InfoOnly = "upgrade"
            continue
        }

        # -N, -nothing
        match(ARGV[pos], /^--?(N|nothing)$/)
        if (RSTART) {
            InfoOnly = "nothing"
            continue
        }

        ## Translator options

        # -e ENGINE, -engine ENGINE
        match(ARGV[pos], /^--?(e|engine)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["engine"] = group[2] ?
                (group[3] ? group[3] : Option["engine"]) :
                ARGV[++pos]
            continue
        }

        # Shortcut format
        # '/ENGINE'
        match(ARGV[pos], /^\/(.*)$/, group)
        if (RSTART) {
            Option["engine"] = group[1]
            continue
        }

        ## Display options

        # -verbose
        match(ARGV[pos], /^--?verbose$/)
        if (RSTART) {
            Option["verbose"] = 1 # default value
            continue
        }

        # -b, -brief
        match(ARGV[pos], /^--?b(r(i(ef?)?)?)?$/)
        if (RSTART) {
            Option["verbose"] = 0
            continue
        }

        # -d, -dictionary
        match(ARGV[pos], /^--?d(i(c(t(i(o(n(a(ry?)?)?)?)?)?)?)?)?$/)
        if (RSTART) {
            Option["show-original-dictionary"] = 1
            Option["show-dictionary"] = 0
            Option["show-alternatives"] = 0
            continue
        }

        # -identify
        match(ARGV[pos], /^--?id(e(n(t(i(fy?)?)?)?)?)?$/)
        if (RSTART) {
            Option["verbose"] = Option["verbose"] - 2
            continue
        }

        # -show-original Y/n
        match(ARGV[pos], /^--?show-original(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-original-phonetics Y/n
        match(ARGV[pos], /^--?show-original-phonetics(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original-phonetics"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-translation Y/n
        match(ARGV[pos], /^--?show-translation(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-translation"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-translation-phonetics Y/n
        match(ARGV[pos], /^--?show-translation-phonetics(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-translation-phonetics"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-prompt-message Y/n
        match(ARGV[pos], /^--?show-prompt-message(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-prompt-message"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-languages Y/n
        match(ARGV[pos], /^--?show-languages(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-languages"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-original-dictionary y/N
        match(ARGV[pos], /^--?show-original-dictionary(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original-dictionary"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-dictionary Y/n
        match(ARGV[pos], /^--?show-dictionary(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-dictionary"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-alternatives Y/n
        match(ARGV[pos], /^--?show-alternatives(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-alternatives"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -w NUM, -width NUM
        match(ARGV[pos], /^--?w(i(d(th?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["width"] = group[4] ?
                (group[5] ? group[5] : Option["width"]) :
                ARGV[++pos]
            continue
        }

        # -indent NUM
        match(ARGV[pos], /^--?indent(=(.*)?)?$/, group)
        if (RSTART) {
            Option["indent"] = group[1] ?
                (group[2] ? group[2] : Option["indent"]) :
                ARGV[++pos]
            continue
        }

        # -theme FILENAME
        match(ARGV[pos], /^--?theme(=(.*)?)?$/, group)
        if (RSTART) {
            Option["theme"] = group[1] ?
                (group[2] ? group[2] : Option["theme"]) :
                ARGV[++pos]
            continue
        }

        # -no-theme
        match(ARGV[pos], /^--?no-theme$/)
        if (RSTART) {
            Option["theme"] = NULLSTR
            continue
        }

        # -no-ansi
        match(ARGV[pos], /^--?no-ansi$/)
        if (RSTART) {
            Option["no-ansi"] = 1
            continue
        }

        # -no-autocorrect
        match(ARGV[pos], /^--?no-auto(correct)?$/)
        if (RSTART) {
            Option["no-autocorrect"] = 1
            continue
        }

        # -no-bidi
        match(ARGV[pos], /^--?no-bidi/)
        if (RSTART) {
            Option["no-bidi"] = 1
            continue
        }

        # -bidi
        match(ARGV[pos], /^--?bidi/)
        if (RSTART) {
            Option["force-bidi"] = 1
            continue
        }

        # -no-warn
        match(ARGV[pos], /^--?no-warn/)
        if (RSTART) {
            Option["no-warn"] = 1
            continue
        }

        # -dump
        match(ARGV[pos], /^--?dump/)
        if (RSTART) {
            Option["dump"] = 1
            continue
        }

        ## Audio options

        # -p, -play
        match(ARGV[pos], /^--?p(l(ay?)?)?$/)
        if (RSTART) {
            Option["play"] = 1
            continue
        }

        # -speak
        match(ARGV[pos], /^--?sp(e(ak?)?)?$/)
        if (RSTART) {
            Option["play"] = 2
            continue
        }

        # -n VOICE, -narrator VOICE
        match(ARGV[pos], /^--?(n|narrator)(=(.*)?)?$/, group)
        if (RSTART) {
            if (!Option["play"]) Option["play"] = 1 # -play by default
            Option["narrator"] = group[2] ?
                (group[3] ? group[3] : Option["narrator"]) :
                ARGV[++pos]
            continue
        }

        # -player PROGRAM
        match(ARGV[pos], /^--?player(=(.*)?)?$/, group)
        if (RSTART) {
            if (!Option["play"]) Option["play"] = 1 # -play by default
            Option["player"] = group[1] ?
                (group[2] ? group[2] : Option["player"]) :
                ARGV[++pos]
            continue
        }

        # -no-play
        match(ARGV[pos], /^--?no-play$/)
        if (RSTART) {
            Option["play"] = 0
            continue
        }

        # -no-translate
        match(ARGV[pos], /^--?no-tran(s(l(a(te?)?)?)?)?$/)
        if (RSTART) {
            Option["no-translate"] = 1
            continue
        }

        # -download-audio
        match(ARGV[pos], /^--?download-a(u(d(io?)?)?)?$/)
        if (RSTART) {
            Option["download-audio"] = 1
            continue
        }

        # -download-audio-as
        match(ARGV[pos], /^--?download-audio-as(=(.*)?)?$/, group)
        if (RSTART) {
            if (!Option["download-audio"]) Option["download-audio"] = 1
            Option["download-audio-as"] = group[1] ?
                (group[2] ? group[2] : Option["download-audio-as"]) :
                ARGV[++pos]
            continue
        }

        ## Terminal paging and browsing options

        # -v, -view
        match(ARGV[pos], /^--?v(i(ew?)?)?$/)
        if (RSTART) {
            Option["view"] = 1
            continue
        }

        # -pager PROGRAM
        match(ARGV[pos], /^--?pager(=(.*)?)?$/, group)
        if (RSTART) {
            Option["view"] = 1
            Option["pager"] = group[1] ?
                (group[2] ? group[2] : Option["pager"]) :
                ARGV[++pos]
            continue
        }

        # -no-view, -no-pager
        match(ARGV[pos], /^--?no-(view|pager)$/)
        if (RSTART) {
            Option["view"] = 0
            continue
        }

        # -browser PROGRAM
        match(ARGV[pos], /^--?browser(=(.*)?)?$/, group)
        if (RSTART) {
            Option["browser"] = group[1] ?
                (group[2] ? group[2] : Option["browser"]) :
                ARGV[++pos]
            continue
        }

        # -no-browser
        match(ARGV[pos], /^--?no-browser$/)
        if (RSTART) {
            Option["browser"] = NONE
            continue
        }

        ## Networking options

        # -x HOST:PORT, -proxy HOST:PORT
        match(ARGV[pos], /^--?(x|proxy)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["proxy"] = group[2] ?
                (group[3] ? group[3] : Option["proxy"]) :
                ARGV[++pos]
            continue
        }

        # -u STRING, -user-agent STRING
        match(ARGV[pos], /^--?(u|user-agent)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["user-agent"] = group[2] ?
                (group[3] ? group[3] : Option["user-agent"]) :
                ARGV[++pos]
            continue
        }

        # -4, -ipv4, -inet4-only
        match(ARGV[pos], /^--?(4|ipv4|inet4-only)$/)
        if (RSTART) {
            Option["ip-version"] = 4
            continue
        }

        # -6, -ipv6, -inet6-only
        match(ARGV[pos], /^--?(6|ipv6|inet6-only)$/)
        if (RSTART) {
            Option["ip-version"] = 6
            continue
        }

        ## Interactive shell options

        # -I, -interactive, -shell
        match(ARGV[pos], /^--?(I|int(e(r(a(c(t(i(ve?)?)?)?)?)?)?)?|shell)$/)
        if (RSTART) {
            Option["interactive"] = 1
            continue
        }

        # -E, -emacs
        match(ARGV[pos], /^--?(E|emacs)$/)
        if (RSTART) {
            Option["emacs"] = 1
            continue
        }

        # -no-rlwrap
        match(ARGV[pos], /^--?no-rlwrap$/)
        if (RSTART) {
            Option["no-rlwrap"] = 1
            continue
        }

        # FIXME[1.0]: to be removed
        # -prompt PROMPT_STRING
        match(ARGV[pos], /^--?prompt(=(.*)?)?$/, group)
        if (RSTART) {
            w("[ERROR] Option '-prompt' has been deprecated since version 0.9.\n" \
              "        Use configuration variable 'fmt-prompt' instead.")
            exit 1
        }

        # FIXME[1.0]: to be removed
        # -prompt-color COLOR_CODE
        match(ARGV[pos], /^--?prompt-color(=(.*)?)?$/, group)
        if (RSTART) {
            w("[ERROR] Option '-prompt-color' has been deprecated since version 0.9.\n" \
              "        Use configuration variable 'sgr-prompt' instead.")
            exit 1
        }

        ## I/O options

        # -i FILENAME, -input FILENAME
        match(ARGV[pos], /^--?i(n(p(ut?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["input"] = group[4] ?
                (group[5] ? group[5] : Option["input"]) :
                ARGV[++pos]
            continue
        }

        # -o FILENAME, -output FILENAME
        match(ARGV[pos], /^--?o(u(t(p(ut?)?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["output"] = group[5] ?
                (group[6] ? group[6] : Option["output"]) :
                ARGV[++pos]
            continue
        }

        ## Language preference options

        # -hl CODE, -host CODE
        match(ARGV[pos], /^--?(host|hl)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["hl"] = group[2] ?
                (group[3] ? group[3] : Option["hl"]) :
                ARGV[++pos]
            continue
        }
        # FIXME[1.0]: to be removed
        # -l CODE, -lang CODE
        match(ARGV[pos], /^--?(l(a(ng?)?)?)(=(.*)?)?$/, group)
        if (RSTART) {
            w("[WARNING] Option '-l' / '-lang' will be deprecated in the next version.\n" \
              "          Use '-hl' / '-host' instead.")
            Option["hl"] = group[4] ?
                (group[5] ? group[5] : Option["hl"]) :
                ARGV[++pos]
            continue
        }

        # -s CODES, -sl CODES, -source CODES, -f CODES, -from CODES
        match(ARGV[pos], /^--?(s(o(u(r(ce?)?)?)?|l)?|f|from)(=(.*)?)?$/, group)
        if (RSTART) {
            if (group[6]) {
                if (group[7]) split(group[7], Option["sls"], "+")
            } else
                split(ARGV[++pos], Option["sls"], "+")
            Option["sl"] = Option["sls"][1]
            continue
        }

        # -t CODES, -tl CODES, -target CODES, -to CODES
        match(ARGV[pos], /^--?t(a(r(g(et?)?)?)?|l|o)?(=(.*)?)?$/, group)
        if (RSTART) {
            if (group[5]) {
                if (group[6]) split(group[6], Option["tl"], "+")
            } else
                split(ARGV[++pos], Option["tl"], "+")
            continue
        }

        # Shortcut format
        # 'CODE:CODE+...' or 'CODE=CODE+...'
        match(ARGV[pos], /^[{(\[]?((@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]][[:alpha:]]?[[:alpha:]]?)?\+)*(@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]][[:alpha:]]?[[:alpha:]]?)?)?)?(:|=)((@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]][[:alpha:]]?[[:alpha:]]?)?\+)*(@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]][[:alpha:]]?[[:alpha:]]?)?)?)[})\]]?$/, group)
        if (RSTART) {
            if (group[1]) {
                split(group[1], Option["sls"], "+")
                Option["sl"] = Option["sls"][1]
            }
            if (group[7]) split(group[7], Option["tl"], "+")
            continue
        }

        ## Text preprocessing options

        # -j, -join-sentence
        match(ARGV[pos], /^--?j(o(i(n(-(s(e(n(t(e(n(ce?)?)?)?)?)?)?)?)?)?)?)?$/)
        if (RSTART) {
            Option["join-sentence"] = 1
            continue
        }

        ## Other options

        # -D, -debug
        match(ARGV[pos], /^--?(D|debug)$/)
        if (RSTART) {
            Option["debug"] = 1
            continue
        }

        # -no-init
        match(ARGV[pos], /^--?no-init/)
        if (RSTART) continue # skip

        # -, -no-op
        match(ARGV[pos], /^-(-?no-op)?$/)
        if (RSTART) continue # no operation, skip

        # --
        match(ARGV[pos], /^--$/)
        if (RSTART) {
            ++pos # skip the end-of-options option
            break # no more option from here
        }

        # non-option argument
        noargv[noargc++] = ARGV[pos]
    }

    # Handle interactive shell
    if (Option["interactive"] && !Option["no-rlwrap"])
        rlwrapMe() # interactive mode
    else if (Option["emacs"] && !Option["interactive"] && !Option["no-rlwrap"])
        if (emacsMe()) # emacs front-end
            Option["interactive"] = 1 # fallback to interactive mode

    # Get started

    initMisc()

    # Information-only session
    switch (InfoOnly) {
    case "version":
        print getVersion()
        exit ExitCode
    case "help":
        print getHelp()
        exit ExitCode
    case "manual":
        showMan()
        exit ExitCode
    case "reference":
        print getReference("endonym")
        exit ExitCode
    case "reference-english":
        print getReference("name")
        exit ExitCode
    case "list-engines":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for (translator in Translator)
            print (Option["engine"] == translator ? "* " : "  ") translator
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "list-languages":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for (code in Locale)
            # only show languages that are supported
            if (Locale[code]["supported-by"])
                print getDisplay(Locale[code]["endonym"])
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "list-languages-english":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "compName"
        for (code in Locale)
            # only show languages that are supported
            if (Locale[code]["supported-by"])
                print Locale[code]["name"] #(Locale[code]["name2"] ? " / " Locale[code]["name2"] : "")
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "list-codes":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for (code in Locale)
            # only show languages that are supported
            if (Locale[code]["supported-by"])
                print code
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "list-all":
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "compName"
        for (code in Locale)
            # only show languages that are supported
            if (Locale[code]["supported-by"])
                printf("%-10s %-30s %s\n", code, Locale[code]["name"], getDisplay(Locale[code]["endonym"]))
        PROCINFO["sorted_in"] = saveSortedIn
        exit ExitCode
    case "language":
        print getLanguage(Option["tl"])
        exit ExitCode
    case "upgrade":
        upgrade()
        exit ExitCode
    case "nothing":
        exit ExitCode
    }

    # Load theme (overrides command-line options) - slow
    setTheme()

    if (Option["interactive"])
        welcome()

    # More remaining non-option arguments
    if (pos < ARGC)
        for (i = pos; i < ARGC; i++)
            noargv[noargc++] = ARGV[i]

    # Text preprocessing: join all arguments if required
    if (noargc > 1 && Option["join-sentence"]) {
        noargv[0] = join(noargv, " ")
        noargc = 1
    }

    if (noargc) {
        # Translate all non-option arguments
        for (i = 0; i < noargc; i++) {
            # Verbose mode: separator between sources
            if (Option["verbose"] && i > pos)
                p(prettify("source-seperator", replicate(Option["chr-source-seperator"], Option["width"])))

            translates(noargv[i], 1) # inline mode
        }

        # If input not specified, we're done
    } else {
        # If input not specified, use stdin
        if (!Option["input"]) Option["input"] = STDIN
    }

    # If input specified, start translating
    if (Option["input"])
        translateMain()

    exit ExitCode
}


added total traffic column

added total traffic column

BEGIN {
    num_hosts = 0;
    num_subnets = 0;
    num_users = 0;
}
{
    if ($3 != "RECV" && $3 != "SENT") {
	if (num_hosts == 0) {
	    StartDate = $1;
	    StartTime = $2;
	} else {
	    EndDate = $1;
	    EndTime = $2;
	}
	if (AttemptedSends[$9] == 0 && AttemptedRecvs[$9] == 0) {
	    Hosts[num_hosts] = $9;
	    num_hosts++;
	}
	split($9, ip_tmp, ".");
	Subnet = sprintf("%s.%s.%s", ip_tmp[1], ip_tmp[2], ip_tmp[3]);
	if (AttemptedSends[Subnet] == 0 && AttemptedRecvs[Subnet] == 0) {
	    Subnets[num_subnets] = Subnet;
	    num_subnets++;
	}
	split($10, user_tmp, "\@");
	User = user_tmp[1];
	if (AttemptedSends[User] == 0 && AttemptedRecvs[User] == 0) {
	    Users[num_users] = User;
	    num_users++;
	}
	if ($3 == "S") {
	    AttemptedSends[$9]++;
	    AttemptedSends[Subnet]++;
	    AttemptedSends[User]++;
	    if ($4 != "F") {
		CompletedSends[$9]++;
		CompletedSends[Subnet]++;
		CompletedSends[User]++;
		BytesSent[$9] += $5;
		BytesSent[Subnet] += $5;
		BytesSent[User] += $5;
		TimeSending[$9] += $7;
		TimeSending[Subnet] += $7;
		TimeSending[User] += $7;
	    }
	} else {
	    AttemptedRecvs[$9]++;
	    AttemptedRecvs[Subnet]++;
	    AttemptedRecvs[User]++;
	    if ($4 != "F") {
		CompletedRecvs[$9]++;
		CompletedRecvs[Subnet]++;
		CompletedRecvs[User]++;
		BytesRecvd[$9] += $5;
		BytesRecvd[Subnet] += $5;
		BytesRecvd[User] += $5;
		TimeReceiving[$9] += $7;
		TimeReceiving[Subnet] += $7;
		TimeReceiving[User] += $7;
	    }
	}
    }
}
END {
    qsort(Hosts, 0, num_hosts-1);
    qsort(Subnets, 0, num_subnets-1);
    qsort(Users, 0, num_users-1);
    printf("\nCheckpoint Server Transfer Log Data from %s %s to %s %s\n\n",
	   StartDate, StartTime, EndDate, EndTime);
    printf("%-16.16s %9.9s %7.7s %9.9s %7.7s %9.9s %7.7s %9.9s\n",
	   "", "Send", "Send", "Receive", "Receive", "Total", "Total",
	   "Total");
    printf("%-16.16s %9.9s %7.7s %9.9s %7.7s %9.9s %7.7s %9.9s\n",
	   "", "Bandwidth", "Success", "Bandwidth", "Success", "Bandwidth",
	   "Success", "Traffic");
    printf("%-16.16s %9.9s %7.7s %9.9s %7.7s %9.9s %7.7s %9.9s\n\n",
	   "User/Host/Subnet", "(Mb/s)", "Rate", "(Mb/s)", "Rate", "(Mb/s)",
	   "Rate", "(MB)");
    for (i=0; i < num_users; i++) {
	User = Users[i];
	Bytes = BytesSent[User] + BytesRecvd[User];
	Time = TimeSending[User] + TimeReceiving[User];
	Attempted = AttemptedSends[User] + AttemptedRecvs[User];
	Completed = CompletedSends[User] + CompletedRecvs[User];
	if (TimeSending[User] > 0 && TimeReceiving[User] > 0) {
	    printf("%-16.16s %9.2f %6.0f%% %9.2f %6.0f%% %9.2f %6.0f%% %9.0f\n",
		   User,
		   BytesSent[User]*8/TimeSending[User]/1024/1024,
		   CompletedSends[User]/AttemptedSends[User]*100,
		   BytesRecvd[User]*8/TimeReceiving[User]/1024/1024,
		   CompletedRecvs[User]/AttemptedRecvs[User]*100,
		   Bytes*8/Time/1024/1024,
		   Completed/Attempted*100,
		   (BytesSent[User]+BytesRecvd[User])/1024/1024);
	}
    }
    printf("\n");
    for (i=0; i < num_hosts; i++) {
	Host = Hosts[i];
	Bytes = BytesSent[Host] + BytesRecvd[Host];
	Time = TimeSending[Host] + TimeReceiving[Host];
	Attempted = AttemptedSends[Host] + AttemptedRecvs[Host];
	Completed = CompletedSends[Host] + CompletedRecvs[Host];
	if (TimeSending[Host] > 0 && TimeReceiving[Host] > 0) {
	    printf("%-16.16s %9.2f %6.0f%% %9.2f %6.0f%% %9.2f %6.0f%% %9.0f\n",
		   Host,
		   BytesSent[Host]*8/TimeSending[Host]/1024/1024,
		   CompletedSends[Host]/AttemptedSends[Host]*100,
		   BytesRecvd[Host]*8/TimeReceiving[Host]/1024/1024,
		   CompletedRecvs[Host]/AttemptedRecvs[Host]*100,
		   Bytes*8/Time/1024/1024,
		   Completed/Attempted*100,
		   (BytesSent[Host]+BytesRecvd[Host])/1024/1024);
	}
	TotalBytesSent += BytesSent[Host];
	TotalBytesRecvd += BytesRecvd[Host];
	TotalTimeSending += TimeSending[Host];
	TotalTimeReceiving += TimeReceiving[Host];
	TotalAttemptedSends += AttemptedSends[Host];
	TotalAttemptedRecvs += AttemptedRecvs[Host];
	TotalCompletedSends += CompletedSends[Host];
	TotalCompletedRecvs += CompletedRecvs[Host];
    }
    printf("\n");
    for (i=0; i < num_subnets; i++) {
	Subnet = Subnets[i];
	Bytes = BytesSent[Subnet] + BytesRecvd[Subnet];
	Time = TimeSending[Subnet] + TimeReceiving[Subnet];
	Attempted = AttemptedSends[Subnet] + AttemptedRecvs[Subnet];
	Completed = CompletedSends[Subnet] + CompletedRecvs[Subnet];
	if (TimeSending[Subnet] > 0 && TimeReceiving[Subnet] > 0) {
	    printf("%-16.16s %9.2f %6.0f%% %9.2f %6.0f%% %9.2f %6.0f%% %9.0f\n",
		   Subnet,
		   BytesSent[Subnet]*8/TimeSending[Subnet]/1024/1024,
		   CompletedSends[Subnet]/AttemptedSends[Subnet]*100,
		   BytesRecvd[Subnet]*8/TimeReceiving[Subnet]/1024/1024,
		   CompletedRecvs[Subnet]/AttemptedRecvs[Subnet]*100,
		   Bytes*8/Time/1024/1024,
		   Completed/Attempted*100,
		   (BytesSent[Subnet]+BytesRecvd[Subnet])/1024/1024);
	}
    }
    TotalBytes = TotalBytesSent + TotalBytesRecvd;
    TotalTime = TotalTimeSending + TotalTimeReceiving;
    TotalAttempted = TotalAttemptedSends + TotalAttemptedRecvs;
    TotalCompleted = TotalCompletedSends + TotalCompletedRecvs;
    if (TotalTimeSending > 0 && TotalTimeReceiving > 0) {
	printf("\n%-16.16s %9.2f %6.0f%% %9.2f %6.0f%% %9.2f %6.0f%% %9.0f\n\n",
	       "Overall",
	       TotalBytesSent*8/TotalTimeSending/1024/1024,
	       TotalCompletedSends/TotalAttemptedSends*100,
	       TotalBytesRecvd*8/TotalTimeReceiving/1024/1024,
	       TotalCompletedRecvs/TotalAttemptedRecvs*100,
	       TotalBytes*8/TotalTime/1024/1024,
	       TotalCompleted/TotalAttempted*100,
	       (TotalBytesSent+TotalBytesRecvd)/1024/1024);
    }
    printf("\n%-16.16s %25.25s %25.25s\n\n", "User", "MB Sent", "MB Received");
    for (i=0; i < num_users; i++) {
	User = Users[i];
	printf("%-16.16s %25.0f %25.0f\n",
	       User, BytesSent[User]/1024/1024, BytesRecvd[User]/1024/1024);
    }
    printf("\n%-16.16s %25.0f %25.0f\n",
	   "Total", TotalBytesSent/1024/1024, TotalBytesRecvd/1024/1024);
}
function qsort(v, left, right) {
  if (left >= right) return;
  swap(v, left, int((left + right)/2));
  last = left;
  for (i=left+1; i <= right; i++) {
    if (v[i] < v[left]) swap(v, ++last, i);
  }
  swap(v, left, last);
  qsort(v, left, last-1);
  qsort(v, last+1, right);
}
function swap(v, i, j) {
  temp = v[i];
  v[i] = v[j];
  v[j] = temp;
}

BEGIN {
    num_hosts = 0;
    num_subnets = 0;
    num_users = 0;
}
{
    if ($3 != "RECV" && $3 != "SENT") {
	if (num_hosts == 0) {
	    StartDate = $1;
	    StartTime = $2;
	} else {
	    EndDate = $1;
	    EndTime = $2;
	}
	if (AttemptedSends[$9] == 0 && AttemptedRecvs[$9] == 0) {
	    Hosts[num_hosts] = $9;
	    num_hosts++;
	}
	split($9, ip_tmp, ".");
	Subnet = sprintf("%s.%s.%s", ip_tmp[1], ip_tmp[2], ip_tmp[3]);
	if (AttemptedSends[Subnet] == 0 && AttemptedRecvs[Subnet] == 0) {
	    Subnets[num_subnets] = Subnet;
	    num_subnets++;
	}
	split($10, user_tmp, "\@");
	User = user_tmp[1];
	if (AttemptedSends[User] == 0 && AttemptedRecvs[User] == 0) {
	    Users[num_users] = User;
	    num_users++;
	}
	if ($3 == "S") {
	    AttemptedSends[$9]++;
	    AttemptedSends[Subnet]++;
	    AttemptedSends[User]++;
	    if ($4 != "F") {
		CompletedSends[$9]++;
		CompletedSends[Subnet]++;
		CompletedSends[User]++;
		BytesSent[$9] += $5;
		BytesSent[Subnet] += $5;
		BytesSent[User] += $5;
		TimeSending[$9] += $7;
		TimeSending[Subnet] += $7;
		TimeSending[User] += $7;
	    }
	} else {
	    AttemptedRecvs[$9]++;
	    AttemptedRecvs[Subnet]++;
	    AttemptedRecvs[User]++;
	    if ($4 != "F") {
		CompletedRecvs[$9]++;
		CompletedRecvs[Subnet]++;
		CompletedRecvs[User]++;
		BytesRecvd[$9] += $5;
		BytesRecvd[Subnet] += $5;
		BytesRecvd[User] += $5;
		TimeReceiving[$9] += $7;
		TimeReceiving[Subnet] += $7;
		TimeReceiving[User] += $7;
	    }
	}
    }
}
END {
    qsort(Hosts, 0, num_hosts-1);
    qsort(Subnets, 0, num_subnets-1);
    qsort(Users, 0, num_users-1);
    printf("\nCheckpoint Server Transfer Log Data from %s %s to %s %s\n\n",
	   StartDate, StartTime, EndDate, EndTime);
    printf("%-16.16s %9.9s %7.7s %9.9s %7.7s %9.9s %7.7s\n",
	   "", "Send", "Send", "Receive", "Receive", "Total", "Total");
    printf("%-16.16s %9.9s %7.7s %9.9s %7.7s %9.9s %7.7s\n",
	   "", "Bandwidth", "Success", "Bandwidth", "Success", "Bandwidth",
	   "Success");
    printf("%-16.16s %9.9s %7.7s %9.9s %7.7s %9.9s %7.7s\n\n",
	   "User/Host/Subnet", "(Mb/s)", "Rate", "(Mb/s)", "Rate", "(Mb/s)",
	   "Rate");
    for (i=0; i < num_users; i++) {
	User = Users[i];
	Bytes = BytesSent[User] + BytesRecvd[User];
	Time = TimeSending[User] + TimeReceiving[User];
	Attempted = AttemptedSends[User] + AttemptedRecvs[User];
	Completed = CompletedSends[User] + CompletedRecvs[User];
	if (TimeSending[User] > 0 && TimeReceiving[User] > 0) {
	    printf("%-16.16s %9.2f %6.0f%% %9.2f %6.0f%% %9.2f %6.0f%%\n",
		   User,
		   BytesSent[User]*8/TimeSending[User]/1024/1024,
		   CompletedSends[User]/AttemptedSends[User]*100,
		   BytesRecvd[User]*8/TimeReceiving[User]/1024/1024,
		   CompletedRecvs[User]/AttemptedRecvs[User]*100,
		   Bytes*8/Time/1024/1024,
		   Completed/Attempted*100);
	}
    }
    printf("\n");
    for (i=0; i < num_hosts; i++) {
	Host = Hosts[i];
	Bytes = BytesSent[Host] + BytesRecvd[Host];
	Time = TimeSending[Host] + TimeReceiving[Host];
	Attempted = AttemptedSends[Host] + AttemptedRecvs[Host];
	Completed = CompletedSends[Host] + CompletedRecvs[Host];
	if (TimeSending[Host] > 0 && TimeReceiving[Host] > 0) {
	    printf("%-16.16s %9.2f %6.0f%% %9.2f %6.0f%% %9.2f %6.0f%%\n",
		   Host,
		   BytesSent[Host]*8/TimeSending[Host]/1024/1024,
		   CompletedSends[Host]/AttemptedSends[Host]*100,
		   BytesRecvd[Host]*8/TimeReceiving[Host]/1024/1024,
		   CompletedRecvs[Host]/AttemptedRecvs[Host]*100,
		   Bytes*8/Time/1024/1024,
		   Completed/Attempted*100);
	}
	TotalBytesSent += BytesSent[Host];
	TotalBytesRecvd += BytesRecvd[Host];
	TotalTimeSending += TimeSending[Host];
	TotalTimeReceiving += TimeReceiving[Host];
	TotalAttemptedSends += AttemptedSends[Host];
	TotalAttemptedRecvs += AttemptedRecvs[Host];
	TotalCompletedSends += CompletedSends[Host];
	TotalCompletedRecvs += CompletedRecvs[Host];
    }
    printf("\n");
    for (i=0; i < num_subnets; i++) {
	Subnet = Subnets[i];
	Bytes = BytesSent[Subnet] + BytesRecvd[Subnet];
	Time = TimeSending[Subnet] + TimeReceiving[Subnet];
	Attempted = AttemptedSends[Subnet] + AttemptedRecvs[Subnet];
	Completed = CompletedSends[Subnet] + CompletedRecvs[Subnet];
	if (TimeSending[Subnet] > 0 && TimeReceiving[Subnet] > 0) {
	    printf("%-16.16s %9.2f %6.0f%% %9.2f %6.0f%% %9.2f %6.0f%%\n",
		   Subnet,
		   BytesSent[Subnet]*8/TimeSending[Subnet]/1024/1024,
		   CompletedSends[Subnet]/AttemptedSends[Subnet]*100,
		   BytesRecvd[Subnet]*8/TimeReceiving[Subnet]/1024/1024,
		   CompletedRecvs[Subnet]/AttemptedRecvs[Subnet]*100,
		   Bytes*8/Time/1024/1024,
		   Completed/Attempted*100);
	}
    }
    TotalBytes = TotalBytesSent + TotalBytesRecvd;
    TotalTime = TotalTimeSending + TotalTimeReceiving;
    TotalAttempted = TotalAttemptedSends + TotalAttemptedRecvs;
    TotalCompleted = TotalCompletedSends + TotalCompletedRecvs;
    if (TotalTimeSending > 0 && TotalTimeReceiving > 0) {
	printf("\n%-16.16s %9.2f %6.0f%% %9.2f %6.0f%% %9.2f %6.0f%%\n\n",
	       "Overall",
	       TotalBytesSent*8/TotalTimeSending/1024/1024,
	       TotalCompletedSends/TotalAttemptedSends*100,
	       TotalBytesRecvd*8/TotalTimeReceiving/1024/1024,
	       TotalCompletedRecvs/TotalAttemptedRecvs*100,
	       TotalBytes*8/TotalTime/1024/1024,
	       TotalCompleted/TotalAttempted*100);
    }
    printf("\n%-16.16s %25.25s %25.25s\n\n", "User", "MB Sent", "MB Received");
    for (i=0; i < num_users; i++) {
	User = Users[i];
	printf("%-16.16s %25.0f %25.0f\n",
	       User, BytesSent[User]/1024/1024, BytesRecvd[User]/1024/1024);
    }
    printf("\n%-16.16s %25.0f %25.0f\n",
	   "Total", TotalBytesSent/1024/1024, TotalBytesRecvd/1024/1024);
}
function qsort(v, left, right) {
  if (left >= right) return;
  swap(v, left, int((left + right)/2));
  last = left;
  for (i=left+1; i <= right; i++) {
    if (v[i] < v[left]) swap(v, ++last, i);
  }
  swap(v, left, last);
  qsort(v, left, last-1);
  qsort(v, last+1, right);
}
function swap(v, i, j) {
  temp = v[i];
  v[i] = v[j];
  v[j] = temp;
}