2014-02-18: parsegaspriceshtml.awk Added substitution for Certified pictures.

BEGIN {
   False=0
   True=!False
   i=0
   inGasPriceRecord=False
   expectName=False
}

/
<div class=\"p","")
   sub("^[ ]*

[ ]*$","")
   # print
   price[i]=$0
}

inGasPriceRecord && /
/ {
   sub("^[ ]*
","")
   sub("
[ ]*$","")
   sub("&","\\&")
   address[i,ai[i]]=$0
   # print "address["ai[i]"]="address[i,ai[i]]
   ai[i]++
}

inGasPriceRecord && /
/ {
   expectName=True
}

inGasPriceRecord && expectName && /<a href="/ {
   gsub("","Marathon")
   gsub("","Duchess")
   gsub("","Thorntons")
   gsub("","Thorntons")
   gsub("","Certified")
   expectName=False
   sub("[ ]*$","")
   sub("^.*>","")
   sub("&","\\&")
   name[i]=$0
   # print
}

inGasPriceRecord && / class="p_area">/ {
   sub("^.* class=\"p_area\">","")
   sub("[ ]*$","")
   area[i]=$0
   # print
}

inGasPriceRecord && /<a href="\/Profile[.]aspx[?]member=/ {
   sub("^.*<a href=\"/Profile[.]aspx[?]member=","")
   sub("\".*$","")
   reporter[i]=$0
   # print
}

inGasPriceRecord && expectRelativeTime {
   expectRelativeTime=False
   sub("^[ ]*","")
   sub("[ ]ago[ ]*$","")
   timeAgo[i]=$0
   # print
}

inGasPriceRecord && /<div class="tm" / {
   sub("^[ ]*
[ ]*$","")
   time[i]=$0
   expectRelativeTime=True
   # print
}

inGasPriceRecord && /<\/tr>/ {
   i ++
   # print "EOR"
   # print $0
   inGasPriceRecord=False
}

END {
   for (j=0;j<i && True;j++) {
      if (False) {
         print j
         print price[j]
         print name[j]
         print address[j,0]
         print area[j]
         print reporter[j]
         print time[j]
         print timeAgo[j]
      }

      delimiter="\t"
      print price[j] delimiter name[j] delimiter address[j,0] delimiter area[j] delimiter reporter[j] delimiter time[j] delimiter timeAgo[j]
   }
}

BEGIN {
   False=0
   True=!False
   i=0
   inGasPriceRecord=False
   expectName=False
}

/
<div class=\"p","")
   sub("^[ ]*

[ ]*$","")
   # print
   price[i]=$0
}

inGasPriceRecord && /
/ {
   sub("^[ ]*
","")
   sub("
[ ]*$","")
   sub("&","\\&")
   address[i,ai[i]]=$0
   # print "address["ai[i]"]="address[i,ai[i]]
   ai[i]++
}

inGasPriceRecord && /
/ {
   expectName=True
}

inGasPriceRecord && expectName && /<a href="/ {
   gsub("","Marathon")
   gsub("","Duchess")
   gsub("","Thorntons")
   gsub("","Thorntons")
   expectName=False
   sub("[ ]*$","")
   sub("^.*>","")
   sub("&","\\&")
   name[i]=$0
   # print
}

inGasPriceRecord && / class="p_area">/ {
   sub("^.* class=\"p_area\">","")
   sub("[ ]*$","")
   area[i]=$0
   # print
}

inGasPriceRecord && /<a href="\/Profile[.]aspx[?]member=/ {
   sub("^.*<a href=\"/Profile[.]aspx[?]member=","")
   sub("\".*$","")
   reporter[i]=$0
   # print
}

inGasPriceRecord && expectRelativeTime {
   expectRelativeTime=False
   sub("^[ ]*","")
   sub("[ ]ago[ ]*$","")
   timeAgo[i]=$0
   # print
}

inGasPriceRecord && /<div class="tm" / {
   sub("^[ ]*
[ ]*$","")
   time[i]=$0
   expectRelativeTime=True
   # print
}

inGasPriceRecord && /<\/tr>/ {
   i ++
   # print "EOR"
   # print $0
   inGasPriceRecord=False
}

END {
   for (j=0;j<i && True;j++) {
      if (False) {
         print j
         print price[j]
         print name[j]
         print address[j,0]
         print area[j]
         print reporter[j]
         print time[j]
         print timeAgo[j]
      }

      delimiter="\t"
      print price[j] delimiter name[j] delimiter address[j,0] delimiter area[j] delimiter reporter[j] delimiter time[j] delimiter timeAgo[j]
   }
}


msg_parser.awk: cast offsets within header structure as unsigned short

darcs-hash:20070301130856-55b16-501ba18adf43c56ae7395f6c693cb4bb1821d88b.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);
  split("", experimental);

  # indexed by the C name of the header
  split("", Since);		# Non-NUL if extra
  split("", Extra);		# Offset in extra headers

  without_experimental = 0;
  template="";
  template1="";
  template2="";
  template3="";
  prefix="";
  tprefix="";
  extra=0;
  failed=0;
  success=0;

  extra_struct = "msg_pub_extra";

  ERRNO="error";
}

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, since)
{
  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 && name2 != tprefix "_" 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 (since) {
    Since[name] = since;
    Extra[name] = extra++;
  }

  expr = (without_experimental > 0 && do_hash);
  if (expr) {
    printf "%s is experimental\n", Comment;
  }

  experimental[N] = expr;

  if (PR) {
    if (expr) {
      print "#if SU_HAVE_EXPERIMENTAL" > PR;
    }
    replace(template, hash, name, NAME, comment, Comment, COMMENT, since);
    replace(template1, hash, name, NAME, comment, Comment, COMMENT, since);
    replace(template2, hash, name, NAME, comment, Comment, COMMENT, since);
    replace(template3, hash, name, NAME, comment, Comment, COMMENT, since);
    if (expr) {
      print "#endif /* SU_HAVE_EXPERIMENTAL */" > PR;
    }
  }
}

function replace (p, hash, name, NAME, comment, Comment, COMMENT, since)
{
  #
  # Replace various forms of header name in template, print it out
  #
  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);

    if (since) {
      gsub(/#version#/, since, p);
    }
    else {
      # Remove line with #version#
      gsub(/\n[^\n]*#version#[^\n]*\n/, "\n", p);
    }

    print p > PR;
  }
}

#
# 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;
    return;
  }

  n = split(text, lines, RS);

  for (i = 1; i <= n; i++) {
    l = lines[i];
    if (match(tolower(l), /#(xxxxxx(x_xxxxxxx)?|hash)#/)) {
      expr = 0;

      for (j = 1; j <= N; j++) {
	l = lines[i];
	if (expr != experimental[j]) {
	  expr = experimental[j];
	  if (expr) {
	    print "#if SU_HAVE_EXPERIMENTAL" > PR;
	  }
	  else {
	    print "#endif /* SU_HAVE_EXPERIMENTAL */" > PR;
	  }
	}
	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;
      }

      if (expr) {
	print "#endif /* SU_HAVE_EXPERIMENTAL */" > PR;
      }
    } else {
      print l > PR;
    }
  }
}

#
# 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);
}

#
# Read in templates
#
function templates ()
{
  if (!auto) {
    auto = FILENAME;

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

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

    if (PR) {
      if (TEMPLATE == "") { TEMPLATE = PR ".in"; }
      RS0=RS; RS="\f\n";
      if ((getline theader < TEMPLATE) < 0) {
	print ( TEMPLATE ": " ERRNO );
	failed=1;
        exit(1);
      }
      getline header < TEMPLATE;
      getline template < TEMPLATE;
      getline footer < TEMPLATE;

      if (TEMPLATE1) {
	if ((getline dummy < TEMPLATE1) < 0) {
	  print(TEMPLATE1 ": " ERRNO);
	  failed=1;
          exit(1);
        }
	getline dummy < TEMPLATE1;
	getline template1 < TEMPLATE1;
	getline dummy < TEMPLATE1;
      }

      if (TEMPLATE2) {
	if ((getline dummy < TEMPLATE2) < 0) {
	  print( TEMPLATE2 ": " ERRNO );
	  failed=1;
	  exit(1);
	}
	getline dummy < TEMPLATE2;
	getline template2 < TEMPLATE2;
	getline dummy < TEMPLATE2;
      }

      if (TEMPLATE3) {
	if ((getline dummy < TEMPLATE3) < 0) {
	  print( TEMPLATE3 ": " ERRNO );
	  failed=1;
	  exit(1);
	}
	getline dummy < TEMPLATE3;
	getline template3 < TEMPLATE3;
	getline dummy < TEMPLATE3;
      }

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

      RS=RS0;
    }

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

/^#### EXTRA HEADER LIST STARTS HERE ####$/ { HLIST=1; templates(); }
HLIST && /^#### EXPERIMENTAL HEADER LIST STARTS HERE ####$/ {
  without_experimental=total; }
HLIST && /^[a-z]/ { protos($1, $0, 0, $2); headers[total++] = $1; }
/^#### EXTRA HEADER LIST ENDS HERE ####$/ { HLIST=0;  }

/^ *\/\* === Headers start here \*\// { in_header_list=1;  templates(); }
/^ *\/\* === Headers end here \*\// { in_header_list=0; }

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;
    failed=1;
    exit 1;
  }

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

  headers[total++] = n;
}

END {
  if (failed) { exit };

  if (without_experimental == 0)
    without_experimental = total;

  if (!NO_LAST) {
    protos("unknown", "/**< Unknown headers */", -3);
    protos("error", "/**< Erroneous headers */", -4);
    protos("separator", "/**< Separator line between headers and body */", -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;

    module_struct = module "_t";

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

    print "" > PT;
    print "#define msg_offsetof(s, f) ((unsigned short)offsetof(s ,f))" > PT;
    print "" > PT;

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

      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, msg_offsetof(%s_t, %s_%s)%s }%s\n",
		 substr(lower_case, i, 1),
		 tprefix, n, module, prefix, n, flags, c)	\
	    > PT;
	}
	else {
	  printf("  { NULL }%s\n", c) \
	    > PT;
	}
      }
      printf("};\n\n") > PT;
    }

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

    if (extra > 0) {
      printf("struct %s {\n", extra_struct) > PT;
      printf("  %s base;\n", module_struct) > PT;
      if (total - without_experimental < extra) {
	printf("  msg_header_t *extra[%u];\n",
	       extra - (total - without_experimental)) > PT;
      }
      if (total - without_experimental > 0) {
	print "#if SU_HAVE_EXPERIMENTAL" > PT;
	printf("  msg_header_t *experimental[%u];\n",
	       total - without_experimental) > PT;
	print "#endif" > PT;
      }
      printf("};\n\n") > PT;
      module_struct = "struct " extra_struct;
    }

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

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

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

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

      if (h < 0)
	continue;

      j = h % MC_HASH_SIZE; if (j == -0) j = 0;

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

      header_hash[j] = n;
      experimental2[j] = (i >= without_experimental);
    }

    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;

	if (experimental2[i]) {
	  print "#if SU_HAVE_EXPERIMENTAL" > PT;
	}

	if (Since[n]) {
	  printf("    { %s_%s_class,\n" \
		 "      msg_offsetof(struct %s, extra[%u])%s }%s\n",
		 tprefix, n, extra_struct, Extra[n], flags, c) > PT;
	}
	else {
	  printf("    { %s_%s_class, msg_offsetof(%s_t, %s_%s)%s }%s\n",
		 tprefix, n, module, prefix, n, flags, c) > PT;
	}

	if (experimental2[i]) {
	  printf("#else\n    { NULL, 0 }%s\n#endif\n", c) > PT;
	}
      }
      else {
	printf("    { NULL, 0 }%s\n", c) > PT;
      }
    }
    printf("  }\n}};\n\n") > PT;
  }

  exit success;
}

#! /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);
  split("", experimental);

  # indexed by the C name of the header
  split("", Since);		# Non-NUL if extra
  split("", Extra);		# Offset in extra headers

  without_experimental = 0;
  template="";
  template1="";
  template2="";
  template3="";
  prefix="";
  tprefix="";
  extra=0;
  failed=0;
  success=0;

  extra_struct = "msg_pub_extra";

  ERRNO="error";
}

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, since)
{
  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 && name2 != tprefix "_" 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 (since) {
    Since[name] = since;
    Extra[name] = extra++;
  }

  expr = (without_experimental > 0 && do_hash);
  if (expr) {
    printf "%s is experimental\n", Comment;
  }

  experimental[N] = expr;

  if (PR) {
    if (expr) {
      print "#if SU_HAVE_EXPERIMENTAL" > PR;
    }
    replace(template, hash, name, NAME, comment, Comment, COMMENT, since);
    replace(template1, hash, name, NAME, comment, Comment, COMMENT, since);
    replace(template2, hash, name, NAME, comment, Comment, COMMENT, since);
    replace(template3, hash, name, NAME, comment, Comment, COMMENT, since);
    if (expr) {
      print "#endif /* SU_HAVE_EXPERIMENTAL */" > PR;
    }
  }
}

function replace (p, hash, name, NAME, comment, Comment, COMMENT, since)
{
  #
  # Replace various forms of header name in template, print it out
  #
  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);

    if (since) {
      gsub(/#version#/, since, p);
    }
    else {
      # Remove line with #version#
      gsub(/\n[^\n]*#version#[^\n]*\n/, "\n", p);
    }

    print p > PR;
  }
}

#
# 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;
    return;
  }

  n = split(text, lines, RS);

  for (i = 1; i <= n; i++) {
    l = lines[i];
    if (match(tolower(l), /#(xxxxxx(x_xxxxxxx)?|hash)#/)) {
      expr = 0;

      for (j = 1; j <= N; j++) {
	l = lines[i];
	if (expr != experimental[j]) {
	  expr = experimental[j];
	  if (expr) {
	    print "#if SU_HAVE_EXPERIMENTAL" > PR;
	  }
	  else {
	    print "#endif /* SU_HAVE_EXPERIMENTAL */" > PR;
	  }
	}
	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;
      }

      if (expr) {
	print "#endif /* SU_HAVE_EXPERIMENTAL */" > PR;
      }
    } else {
      print l > PR;
    }
  }
}

#
# 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);
}

#
# Read in templates
#
function templates ()
{
  if (!auto) {
    auto = FILENAME;

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

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

    if (PR) {
      if (TEMPLATE == "") { TEMPLATE = PR ".in"; }
      RS0=RS; RS="\f\n";
      if ((getline theader < TEMPLATE) < 0) {
	print ( TEMPLATE ": " ERRNO );
	failed=1;
        exit(1);
      }
      getline header < TEMPLATE;
      getline template < TEMPLATE;
      getline footer < TEMPLATE;

      if (TEMPLATE1) {
	if ((getline dummy < TEMPLATE1) < 0) {
	  print(TEMPLATE1 ": " ERRNO);
	  failed=1;
          exit(1);
        }
	getline dummy < TEMPLATE1;
	getline template1 < TEMPLATE1;
	getline dummy < TEMPLATE1;
      }

      if (TEMPLATE2) {
	if ((getline dummy < TEMPLATE2) < 0) {
	  print( TEMPLATE2 ": " ERRNO );
	  failed=1;
	  exit(1);
	}
	getline dummy < TEMPLATE2;
	getline template2 < TEMPLATE2;
	getline dummy < TEMPLATE2;
      }

      if (TEMPLATE3) {
	if ((getline dummy < TEMPLATE3) < 0) {
	  print( TEMPLATE3 ": " ERRNO );
	  failed=1;
	  exit(1);
	}
	getline dummy < TEMPLATE3;
	getline template3 < TEMPLATE3;
	getline dummy < TEMPLATE3;
      }

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

      RS=RS0;
    }

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

/^#### EXTRA HEADER LIST STARTS HERE ####$/ { HLIST=1; templates(); }
HLIST && /^#### EXPERIMENTAL HEADER LIST STARTS HERE ####$/ {
  without_experimental=total; }
HLIST && /^[a-z]/ { protos($1, $0, 0, $2); headers[total++] = $1; }
/^#### EXTRA HEADER LIST ENDS HERE ####$/ { HLIST=0;  }

/^ *\/\* === Headers start here \*\// { in_header_list=1;  templates(); }
/^ *\/\* === Headers end here \*\// { in_header_list=0; }

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;
    failed=1;
    exit 1;
  }

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

  headers[total++] = n;
}

END {
  if (failed) { exit };

  if (without_experimental == 0)
    without_experimental = total;

  if (!NO_LAST) {
    protos("unknown", "/**< Unknown headers */", -3);
    protos("error", "/**< Erroneous headers */", -4);
    protos("separator", "/**< Separator line between headers and body */", -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;

    module_struct = module "_t";

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

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

      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;
	}
	else {
	  printf("  { NULL }%s\n", c) \
	    > PT;
	}
      }
      printf("};\n\n") > PT;
    }

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

    if (extra > 0) {
      printf("struct %s {\n", extra_struct) > PT;
      printf("  %s base;\n", module_struct) > PT;
      if (total - without_experimental < extra) {
	printf("  msg_header_t *extra[%u];\n",
	       extra - (total - without_experimental)) > PT;
      }
      if (total - without_experimental > 0) {
	print "#if SU_HAVE_EXPERIMENTAL" > PT;
	printf("  msg_header_t *experimental[%u];\n",
	       total - without_experimental) > PT;
	print "#endif" > PT;
      }
      printf("};\n\n") > PT;
      module_struct = "struct " extra_struct;
    }

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

    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;
    }
    if (multipart) {
      printf("  {{ %s_class, offsetof(%s_t, %s_multipart) }},\n",
	     multipart, module, prefix) > PT;
    } else {
      printf("  {{ NULL, 0 }},\n") > PT;
    }
    if (MC_SHORT_SIZE) {
      printf("  %s_short_forms, \n", module) > PT;
    }
    else {
      printf("  NULL, \n") > PT;
    }
    printf("  %d, \n", MC_HASH_SIZE) > PT;
    printf ("#if SU_HAVE_EXPERIMENTAL\n" \
	    "  %d,\n" \
	    "#else\n" \
	    "  %d,\n" \
	    "#endif\n", \
	    total, without_experimental) > PT;
    printf("  {\n") > PT;

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

      if (h < 0)
	continue;

      j = h % MC_HASH_SIZE; if (j == -0) j = 0;

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

      header_hash[j] = n;
      experimental2[j] = (i >= without_experimental);
    }

    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;

	if (experimental2[i]) {
	  print "#if SU_HAVE_EXPERIMENTAL" > PT;
	}

	if (Since[n]) {
	  printf("    { %s_%s_class,\n" \
		 "      offsetof(struct %s, extra[%u])%s }%s\n",
		 tprefix, n, extra_struct, Extra[n], flags, c) > PT;
	}
	else {
	  printf("    { %s_%s_class, offsetof(%s_t, %s_%s)%s }%s\n",
		 tprefix, n, module, prefix, n, flags, c) > PT;
	}

	if (experimental2[i]) {
	  printf("#else\n    { NULL, 0 }%s\n#endif\n", c) > PT;
	}
      }
      else {
	printf("    { NULL, 0 }%s\n", c) > PT;
      }
    }
    printf("  }\n}};\n\n") > PT;
  }

  exit success;
}


Fix the make_authors.awk script to handle quotes in names.

# KallistiOS ##version##
#
# arch/dreamcast/kernel/make_authors.awk
# Copyright (C) 2013, 2014 Lawrence Sebald

# This script processes the AUTHORS file in the root directory of the source
# distribution to create the authors.h file that is needed in banner.c. This
# gives a relatively easy way to give credit to the people who have made KOS
# what it is over the years in your programs by simply using the compiled-in
# string with copyright information.
#
# Since this script relies on the format of the file as it stands now, if the
# formatting of the AUTHORS list changes, this will need to be updated as well.
BEGIN {
    FS = ":";
    phase = 0;
}
{
    if($0 == "Contributors list (under the normal KOS license):") {
        phase = 1;
    }
    else if($0 == "-------------------------------------------------" && phase == 1) {
        phase = 2;
        print "static const char authors[] = ";
    }
    else if($0 == "Files with Specific licenses:") {
        print ";\n";
        exit;
    }
    else if(phase == 2 && $0 != "") {
        gsub(/"/, "\\\"", $1)
        print "\"Copyright (C)" $2, $1 "\\n\"";
    }
}

# KallistiOS ##version##
#
# arch/dreamcast/kernel/make_authors.awk
# Copyright (C) 2013 Lawrence Sebald

# This script processes the AUTHORS file in the root directory of the source
# distribution to create the authors.h file that is needed in banner.c. This
# gives a relatively easy way to give credit to the people who have made KOS
# what it is over the years in your programs by simply using the compiled-in
# string with copyright information.
#
# Since this script relies on the format of the file as it stands now, if the
# formatting of the AUTHORS list changes, this will need to be updated as well.
BEGIN {
    FS = ":";
    phase = 0;
}
{
    if($0 == "Contributors list (under the normal KOS license):") {
        phase = 1;
    }
    else if($0 == "-------------------------------------------------" && phase == 1) {
        phase = 2;
        print "static const char authors[] = ";
    }
    else if($0 == "Files with Specific licenses:") {
        print ";\n";
        exit;
    }
    else if(phase == 2 && $0 != "") {
        print "\"Copyright (C)" $2, $1 "\\n\"";
    }
}


load theme file

load theme file

####################################################################
# Theme.awk                                                        #
####################################################################

function prettify(name, string,    i, temp) {
    temp = string
    if ("sgr-" name in Option)
        for (i in Option["sgr-" name])
            temp = ansi(Option["sgr-" name][i], temp)
    return temp
}

function setTheme(    file, line, script) {
    if (Option["theme"] && Option["theme"] != "default") {
        file = Option["theme"]
        if (!fileExists(file))
            file = ENVIRON["HOME"] "/.translate-shell/" Option["theme"]
    }

    if (file && fileExists(file)) {
        # Read from theme file
        script = NULLSTR
        while (getline line < file)
            script = script "\n" line
        loadOptions(script)
    } else {
        # Default theme
        Option["sgr-original"][1] = "bold"
        Option["sgr-original"][2] = "negative"
        Option["sgr-translation"][1] = "bold"
        Option["sgr-prompt-message-original"][1] = "underline"
        Option["sgr-original-dictionary-detailed-explanation"][1] = "bold"
        Option["sgr-original-dictionary-examples-original"][1] = "bold"
        Option["sgr-original-dictionary-examples-original"][2] = "negative"
        Option["sgr-original-dictionary-see-also-item"][1] = "underline"
        Option["sgr-dictionary-word"][1] = "bold"
        Option["sgr-alternatives-original"][1] = "underline"
        Option["sgr-alternatives-translation"][1] = "bold"
    }
}

####################################################################
# Theme.awk                                                        #
####################################################################

function prettify(name, string,    i, temp) {
    temp = string
    if ("sgr-" name in Option)
        for (i in Option["sgr-" name])
            temp = ansi(Option["sgr-" name][i], temp)
    return temp
}

function setTheme() {
    # Default theme
    Option["sgr-original"][1] = "bold"
    Option["sgr-original"][2] = "negative"
    Option["sgr-translation"][1] = "bold"
    Option["sgr-prompt-message-original"][1] = "underline"
    Option["sgr-original-dictionary-detailed-explanation"][1] = "bold"
    Option["sgr-original-dictionary-examples-original"][1] = "bold"
    Option["sgr-original-dictionary-examples-original"][2] = "negative"
    Option["sgr-original-dictionary-see-also-item"][1] = "underline"
    Option["sgr-dictionary-word"][1] = "bold"
    Option["sgr-alternatives-original"][1] = "underline"
    Option["sgr-alternatives-translation"][1] = "bold"
}


Translators/BingTranslator: fix for Kurdish

####################################################################
# BingTranslator.awk                                               #
####################################################################
#
# Last Updated: 8 Jun 2022
BEGIN { provides("bing") }

function bingInit() {
    HttpProtocol = "http://"
    HttpHost = "www.bing.com"
    HttpPort = 80
}

# Set IG, IID, and BingTokenKey (a URL-encoded string containing token and key).
function bingSetup(    ast, content, cookie, group, header, isBody, key,
                       location, status, token, tokens, url) {
    url = HttpPathPrefix "/translator"

    header = "GET " url " HTTP/1.1\r\n"                                 \
        "Host: " HttpHost "\r\n"                                        \
        "Connection: close\r\n"
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\r\n"

    cookie = NULLSTR
    print header |& HttpService
    while ((HttpService |& getline) > 0) {
        match($0, /Set-Cookie: ([^;]*);/, group)
        if (group[1]) {
            cookie = cookie (cookie ?  "; " : NULLSTR) group[1]
        }
        if (isBody)
            content = content ? content "\n" $0 : $0
        else if (length($0) <= 1)
            isBody = 1
        else { # interesting fields in header
            match($0, /^HTTP[^ ]* ([^ ]*)/, group)
            if (RSTART) status = group[1]
            match($0, /^Location: (.*)/, group)
            if (RSTART) location = squeeze(group[1]) # squeeze the URL!
        }
        l(sprintf("%4s bytes > %s", length($0), length($0) < 1024 ? $0 : "..."))
    }
    close(HttpService)

    if ((status == "301" || status == "302") && location)
        content = curl(location)
    # FIXME: cookie
    Cookie = cookie

    match(content, /IG:"([^"]+)"/, group)
    if (group[1]) {
        IG = group[1]
        l(IG, "IG")
    } else {
        e("[ERROR] Failed to extract IG.")
        exit 1
    }

    match(content, /data-iid="([^"]+)"/, group)
    if (group[1]) {
        IID = group[1]
        l(IID, "IID")
    } else {
        e("[ERROR] Failed to extract IID.")
        exit 1
    }

    match(content, /params_RichTranslateHelper = ([^;]+);/, group)
    if (group[1]) {
        tokenize(tokens, group[1])
        parseJson(ast, tokens)
        key = ast[0 SUBSEP 0]
        token = unparameterize(ast[0 SUBSEP 1])
        BingTokenKey = sprintf("&token=%s&key=%s", quote(token), quote(key))
        l(BingTokenKey, "BingTokenKey")
    } else {
        e("[ERROR] Failed to extract token & key.")
        exit 1
    }
}

# FIXME!
function bingTTSUrl(text, tl,
                    ####
                    country, gender, i, group,
                    header, content, isBody) {
    gender = "female"
    country = NULLSTR
    split(Option["narrator"], group, ",")
    for (i in group) {
        if (group[i] ~ /^(f(emale)?|w(oman)?)$/)
            gender = "female"
        else if (group[i] ~ /^m(ale|an)?$/)
            gender = "male"
        else
            country = group[i]
    }

    # Automatic ISO country code
    if (country) tl = tl "-" country
    else if (tl == "ar") tl = tl "-EG" # sometimes doesn't work. Why?
    else if (tl == "da") tl = tl "-DK"
    else if (tl == "de") tl = tl "-DE"
    else if (tl == "en") tl = tl "-US"
    else if (tl == "es") tl = tl "-ES"
    else if (tl == "fi") tl = tl "-FI"
    else if (tl == "fr") tl = tl "-FR"
    else if (tl == "it") tl = tl "-IT"
    else if (tl == "ja") tl = tl "-JP"
    else if (tl == "ko") tl = tl "-KR"
    else if (tl == "nl") tl = tl "-NL"
    else if (tl == "nb") tl = tl "-NO" # Norwegian Bokmål
    else if (tl == "pl") tl = tl "-PL"
    else if (tl == "pt") tl = tl "-PT"
    else if (tl == "ru") tl = tl "-RU"
    else if (tl == "sv") tl = tl "-SE"
    else if (tl == "yue") ;
    else if (tl == "zh") tl = tl "-CN"

    header = "GET " "/tspeak?"                                          \
        "&language=" tl "&text=" preprocess(text)                       \
        "&options=" gender "&format=audio%2Fmp3" " HTTP/1.1\r\n"        \
        "Host: " HttpHost "\r\n"                                        \
        "Connection: close\r\n"
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\r\n"
    if (Cookie)
        header = header "Cookie: " Cookie "\r\n"

    content = NULLSTR; isBody = 0
    print header |& HttpService
    while ((HttpService |& getline) > 0) {
        if (isBody)
            content = content ? content "\n" $0 : $0
        else if (length($0) <= 1)
            isBody = 1
        #l(sprintf("%4s bytes > %s", length($0), $0))
    }
    close(HttpService)

    if (!TempFile)
        TempFile = getOutput("mktemp")
    printf("%s", content) > TempFile
    close(TempFile)
    return TempFile
}

function bingWebTranslateUrl(uri, sl, tl, hl,    _sl, _tl) {
    # Hot-patches for language codes
    _sl = sl; _tl = tl
    if (_sl == "zh")    _sl = "zh-CHS" # still old format
    if (_sl == "zh-CN") _sl = "zh-CHS"
    if (_sl == "zh-TW") _sl = "zh-CHT"
    if (_tl == "zh")    _tl = "zh-CHS"
    if (_tl == "zh-CN") _tl = "zh-CHS"
    if (_tl == "zh-TW") _tl = "zh-CHT"

    return "https://www.translatetheweb.com/?" "from=" _sl "&to=" _tl "&a=" uri
}

# [OBSOLETE?] Old dictionary API (via HTTP GET).
function bingRequestUrl(text, sl, tl, hl) {
    return HttpPathPrefix "/translator/api/Dictionary/Lookup?"  \
        "from=" sl "&to=" tl "&text=" preprocess(text)
}

# Main Bing Translator API (via HTTP POST).
function bingPostRequestUrl(text, sl, tl, hl, type) {
    if (type == "lookup")
        return HttpPathPrefix "/tlookupv3"
    #else if (type == "transliterate")
    #    return HttpPathPrefix "/ttransliteratev3"
    else # type == "translate"
        return HttpPathPrefix "/ttranslatev3" sprintf("?IG=%s&IID=%s", IG, IID)
}

function bingPostRequestContentType(text, sl, tl, hl, type) {
    return "application/x-www-form-urlencoded"
}

function bingPostRequestUserAgent(text, sl, tl, hl, type) {
    return ""
}

function bingPostRequestBody(text, sl, tl, hl, type) {
    if (type == "lookup")
        return "&text=" quote(text) "&from=" sl "&to=" tl
    #else if (type == "transliterate")
    #    return "&text=" quote(text) "&language=" sl "&toScript=" "latn"
    else # type == "translate"
        return "&text=" quote(text) "&fromLang=" sl "&to=" tl BingTokenKey
}

# Get the translation of a string.
function bingTranslate(text, sl, tl, hl,
                       isVerbose, toSpeech, returnPlaylist, returnIl,
                       ####
                       r,
                       content, tokens, ast, dicContent, dicTokens, dicAst,
                       _sl, _tl, _hl, il, isPhonetic,
                       translation, phonetics, oPhonetics,
                       wordClasses, words, wordBackTranslations,
                       wShowOriginal, wShowOriginalPhonetics,
                       wShowTranslation, wShowTranslationPhonetics,
                       wShowLanguages, wShowDictionary,
                       i, j, k, group, temp, saveSortedIn) {
    isPhonetic = match(tl, /^@/)
    tl = substr(tl, 1 + isPhonetic)

    if (!getCode(tl)) {
        # Check if target language is supported
        w("[WARNING] Unknown target language code: " tl)
    } else if (isRTL(tl)) {
        # Check if target language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(tl) " is a right-to-left language, but FriBidi is not found.")
    }
    _sl = getCode(sl); if (!_sl) _sl = sl
    _tl = getCode(tl); if (!_tl) _tl = tl
    _hl = getCode(hl); if (!_hl) _hl = hl

    bingSetup()

    # Hot-patches for Bing's own translator language codes
    # See: <https://docs.microsoft.com/en-us/azure/cognitive-services/Translator/language-support>
    if (_sl == "auto")  _sl = "auto-detect"
    if (_sl == "tl")    _sl = "fil" # Bing uses 'fil' for Filipino
    if (_sl == "hmn")   _sl = "mww" # Bing uses 'mww' for Hmong Daw
    if (_sl == "ku")    _sl = "kmr" # Bing uses 'kmr' for Northern Kurdish
    else if (_sl == "ckb") _sl = "ku" # and 'ku' for Central Kurdish
    if (_sl == "no")    _sl = "nb"  # Bing uses Norwegian Bokmål
    # Bing uses 'pt' or 'pt-br' for Brazilian Portuguese, 'pt-pt' for European Portuguese
    if (_sl == "pt-BR") _sl = "pt" # just pt-br
    else if (_sl == "pt-PT") _sl = "pt" # FIXME: support pt-pt
    if (_sl == "zh-CN") _sl = "zh-Hans"
    if (_sl == "zh-TW") _sl = "zh-Hant"
    if (_tl == "tl")    _tl = "fil"
    if (_tl == "hmn")   _tl = "mww"
    if (_tl == "ku")    _tl = "kmr"
    else if (_tl == "ckb") _tl = "ku"
    if (_tl == "no")    _tl = "nb"
    if (_tl == "pt-BR") _tl = "pt"
    else if (_tl == "pt-PT") _tl = "pt-pt"
    if (_tl == "zh-CN") _tl = "zh-Hans"
    if (_tl == "zh-TW") _tl = "zh-Hant"

    # Translation
    content = postResponse(text, _sl, _tl, _hl, "translate")
    if (content == "") {
        # Empty content. Assume "301 Moved Permanently" and use cn.bing.com
        HttpHost = "cn.bing.com"
        # Just dirty hack
        if (Option["proxy"]) {
            HttpPathPrefix = HttpProtocol HttpHost
        } else {
            HttpService = "/" "inet" "/tcp/0/" HttpHost "/" HttpPort  # FIXME: inet version
        }

        # Try again
        content = postResponse(text, _sl, _tl, _hl, "translate")
    }
    if (Option["dump"])
        return content
    tokenize(tokens, content)
    parseJson(ast, tokens)

    l(content, "content", 1, 1)
    l(tokens, "tokens", 1, 0, 1)
    l(ast, "ast")
    if (!isarray(ast) || !anything(ast)) {
        e("[ERROR] Oops! Something went wrong and I can't translate it for you :(")
        ExitCode = 1
        return
    }

    translation = unparameterize(ast[0 SUBSEP 0 SUBSEP "translations" SUBSEP 0 SUBSEP "text"])

    returnIl[0] = il = _sl == "auto-detect" ?
        unparameterize(ast[0 SUBSEP 0 SUBSEP "detectedLanguage" SUBSEP "language"]) : _sl
    if (Option["verbose"] < -1)
        return il
    if (Option["verbose"] < 0)
        return getList(il)

    # Transliteration
    wShowTranslationPhonetics = Option["show-translation-phonetics"]
    if (wShowTranslationPhonetics) {
        split(_tl, group, "-")
        #content = postResponse(translation, group[1], group[1], _hl, "transliterate")
        #phonetics = unparameterize(content)
        phonetics = unparameterize(ast[0 SUBSEP 0 SUBSEP "translations" SUBSEP 0 SUBSEP "transliteration" \
                                       SUBSEP "text"])
        if (phonetics == translation) phonetics = ""
    }

    # Generate output
    if (!isVerbose) {
        # Brief mode

        r = isPhonetic && phonetics ?
            prettify("brief-translation-phonetics", join(phonetics, " ")) :
            prettify("brief-translation", s(translation, tl))

    } else {
        # Verbose mode

        wShowOriginal = Option["show-original"]
        wShowTranslation = Option["show-translation"]
        wShowLanguages = Option["show-languages"]
        wShowDictionary = Option["show-dictionary"]

        # Transliteration (original)
        wShowOriginalPhonetics = Option["show-original-phonetics"]
        if (wShowOriginalPhonetics) {
            split(_sl, group, "-")
            #content = postResponse(text, group[1], group[1], _hl, "transliterate")
            #oPhonetics = unparameterize(content)
            delete ast  # purge old AST
            content = postResponse(text, il, il, _hl, "translate")
            tokenize(tokens, content)
            parseJson(ast, tokens)
            oPhonetics = unparameterize(ast[0 SUBSEP 0 SUBSEP "translations" SUBSEP 0 \
                                            SUBSEP "transliteration" SUBSEP "text"])
            if (oPhonetics == text) oPhonetics = ""
        }

        if (!oPhonetics) wShowOriginalPhonetics = 0
        if (!phonetics) wShowTranslationPhonetics = 0

        if (wShowOriginal) {
            # Display: original text & phonetics
            if (r) r = r RS RS
            r = r m("-- display original text")
            r = r prettify("original", s(text, _sl))
            if (wShowOriginalPhonetics)
                r = r RS prettify("original-phonetics", showPhonetics(join(oPhonetics, " "), _sl))
        }

        if (wShowTranslation) {
            # Display: major translation & phonetics
            if (r) r = r RS RS
            r = r m("-- display major translation")
            r = r prettify("translation", s(translation, tl))
            if (wShowTranslationPhonetics)
                r = r RS prettify("translation-phonetics", showPhonetics(join(phonetics, " "), tl))
        }

        if (wShowLanguages) {
            # Display: source language -> target language
            if (r) r = r RS RS
            r = r m("-- display source language -> target language")
            temp = Option["fmt-languages"]
            if (!temp) temp = "[ %s -> %t ]"
            split(temp, group, /(%s|%S|%t|%T)/)
            r = r prettify("languages", group[1])
            if (temp ~ /%s/)
                r = r prettify("languages-sl", getDisplay(il))
            if (temp ~ /%S/)
                r = r prettify("languages-sl", getName(il))
            r = r prettify("languages", group[2])
            if (temp ~ /%t/)
                r = r prettify("languages-tl", getDisplay(tl))
            if (temp ~ /%T/)
                r = r prettify("languages-tl", getName(tl))
            r = r prettify("languages", group[3])
        }

        if (wShowDictionary) {
            # Dictionary API
            dicContent = postResponse(text, il, _tl, _hl, "lookup")
            if (dicContent != "") {
                tokenize(dicTokens, dicContent)
                parseJson(dicAst, dicTokens)

                l(dicContent, "dicContent", 1, 1)
                l(dicTokens, "dicTokens", 1, 0, 1)
                l(dicAst, "dicAst")

                saveSortedIn = PROCINFO["sorted_in"]
                PROCINFO["sorted_in"] = "compareByIndexFields"
                for (i in dicAst) {
                    if (match(i, "^0" SUBSEP "0" SUBSEP "translations" SUBSEP "([[:digit:]]+)" SUBSEP \
                              "posTag$", group))
                        wordClasses[group[1]] = tolower(literal(dicAst[i]))
                }
                for (i in dicAst) {
                    if (match(i, "^0" SUBSEP "0" SUBSEP "translations" SUBSEP "([[:digit:]]+)" SUBSEP \
                              "displayTarget$", group))
                        words[wordClasses[group[1]]][group[1]] = literal(dicAst[i])
                    if (match(i, "^0" SUBSEP "0" SUBSEP "translations" SUBSEP "([[:digit:]]+)" SUBSEP \
                              "backTranslations" SUBSEP "([[:digit:]]+)" SUBSEP "displayText$", group))
                        wordBackTranslations[wordClasses[group[1]]][group[1]][group[2]] = literal(dicAst[i])
                }
                PROCINFO["sorted_in"] = saveSortedIn

                # Display: dictionary entries
                if (r) r = r RS
                r = r m("-- display dictionary entries")
                for (i = 0; i < length(words); i++) {
                    r = (i > 0 ? r RS : r) RS prettify("dictionary-word-class", s(wordClasses[i], hl))

                    for (j in words[wordClasses[i]]) {
                        r = r RS prettify("dictionary-word", ins(1, words[wordClasses[i]][j], tl))

                        if (isRTL(il))
                            explanation = join(wordBackTranslations[wordClasses[i]][j], ", ")
                        else {
                            explanation = prettify("dictionary-explanation-item",
                                                   wordBackTranslations[wordClasses[i]][j][0])
                            for (k = 1; k < length(wordBackTranslations[wordClasses[i]][j]); k++)
                                explanation = explanation prettify("dictionary-explanation", ", ") \
                                    prettify("dictionary-explanation-item",
                                             wordBackTranslations[wordClasses[i]][j][k])
                        }

                        if (isRTL(il))
                            r = r RS prettify("dictionary-explanation-item", ins(2, explanation, il))
                        else
                            r = r RS ins(2, explanation)
                    }
                }
            }
        }
    }

    if (toSpeech) {
        returnPlaylist[0]["text"] = translation
        returnPlaylist[0]["tl"] = _tl
    }

    return r
}

####################################################################
# BingTranslator.awk                                               #
####################################################################
#
# Last Updated: 8 Jun 2022
BEGIN { provides("bing") }

function bingInit() {
    HttpProtocol = "http://"
    HttpHost = "www.bing.com"
    HttpPort = 80
}

# Set IG, IID, and BingTokenKey (a URL-encoded string containing token and key).
function bingSetup(    ast, content, cookie, group, header, isBody, key,
                       location, status, token, tokens, url) {
    url = HttpPathPrefix "/translator"

    header = "GET " url " HTTP/1.1\r\n"                                 \
        "Host: " HttpHost "\r\n"                                        \
        "Connection: close\r\n"
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\r\n"

    cookie = NULLSTR
    print header |& HttpService
    while ((HttpService |& getline) > 0) {
        match($0, /Set-Cookie: ([^;]*);/, group)
        if (group[1]) {
            cookie = cookie (cookie ?  "; " : NULLSTR) group[1]
        }
        if (isBody)
            content = content ? content "\n" $0 : $0
        else if (length($0) <= 1)
            isBody = 1
        else { # interesting fields in header
            match($0, /^HTTP[^ ]* ([^ ]*)/, group)
            if (RSTART) status = group[1]
            match($0, /^Location: (.*)/, group)
            if (RSTART) location = squeeze(group[1]) # squeeze the URL!
        }
        l(sprintf("%4s bytes > %s", length($0), length($0) < 1024 ? $0 : "..."))
    }
    close(HttpService)

    if ((status == "301" || status == "302") && location)
        content = curl(location)
    # FIXME: cookie
    Cookie = cookie

    match(content, /IG:"([^"]+)"/, group)
    if (group[1]) {
        IG = group[1]
        l(IG, "IG")
    } else {
        e("[ERROR] Failed to extract IG.")
        exit 1
    }

    match(content, /data-iid="([^"]+)"/, group)
    if (group[1]) {
        IID = group[1]
        l(IID, "IID")
    } else {
        e("[ERROR] Failed to extract IID.")
        exit 1
    }

    match(content, /params_RichTranslateHelper = ([^;]+);/, group)
    if (group[1]) {
        tokenize(tokens, group[1])
        parseJson(ast, tokens)
        key = ast[0 SUBSEP 0]
        token = unparameterize(ast[0 SUBSEP 1])
        BingTokenKey = sprintf("&token=%s&key=%s", quote(token), quote(key))
        l(BingTokenKey, "BingTokenKey")
    } else {
        e("[ERROR] Failed to extract token & key.")
        exit 1
    }
}

# FIXME!
function bingTTSUrl(text, tl,
                    ####
                    country, gender, i, group,
                    header, content, isBody) {
    gender = "female"
    country = NULLSTR
    split(Option["narrator"], group, ",")
    for (i in group) {
        if (group[i] ~ /^(f(emale)?|w(oman)?)$/)
            gender = "female"
        else if (group[i] ~ /^m(ale|an)?$/)
            gender = "male"
        else
            country = group[i]
    }

    # Automatic ISO country code
    if (country) tl = tl "-" country
    else if (tl == "ar") tl = tl "-EG" # sometimes doesn't work. Why?
    else if (tl == "da") tl = tl "-DK"
    else if (tl == "de") tl = tl "-DE"
    else if (tl == "en") tl = tl "-US"
    else if (tl == "es") tl = tl "-ES"
    else if (tl == "fi") tl = tl "-FI"
    else if (tl == "fr") tl = tl "-FR"
    else if (tl == "it") tl = tl "-IT"
    else if (tl == "ja") tl = tl "-JP"
    else if (tl == "ko") tl = tl "-KR"
    else if (tl == "nl") tl = tl "-NL"
    else if (tl == "nb") tl = tl "-NO" # Norwegian Bokmål
    else if (tl == "pl") tl = tl "-PL"
    else if (tl == "pt") tl = tl "-PT"
    else if (tl == "ru") tl = tl "-RU"
    else if (tl == "sv") tl = tl "-SE"
    else if (tl == "yue") ;
    else if (tl == "zh") tl = tl "-CN"

    header = "GET " "/tspeak?"                                          \
        "&language=" tl "&text=" preprocess(text)                       \
        "&options=" gender "&format=audio%2Fmp3" " HTTP/1.1\r\n"        \
        "Host: " HttpHost "\r\n"                                        \
        "Connection: close\r\n"
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\r\n"
    if (Cookie)
        header = header "Cookie: " Cookie "\r\n"

    content = NULLSTR; isBody = 0
    print header |& HttpService
    while ((HttpService |& getline) > 0) {
        if (isBody)
            content = content ? content "\n" $0 : $0
        else if (length($0) <= 1)
            isBody = 1
        #l(sprintf("%4s bytes > %s", length($0), $0))
    }
    close(HttpService)

    if (!TempFile)
        TempFile = getOutput("mktemp")
    printf("%s", content) > TempFile
    close(TempFile)
    return TempFile
}

function bingWebTranslateUrl(uri, sl, tl, hl,    _sl, _tl) {
    # Hot-patches for language codes
    _sl = sl; _tl = tl
    if (_sl == "zh")    _sl = "zh-CHS" # still old format
    if (_sl == "zh-CN") _sl = "zh-CHS"
    if (_sl == "zh-TW") _sl = "zh-CHT"
    if (_tl == "zh")    _tl = "zh-CHS"
    if (_tl == "zh-CN") _tl = "zh-CHS"
    if (_tl == "zh-TW") _tl = "zh-CHT"

    return "https://www.translatetheweb.com/?" "from=" _sl "&to=" _tl "&a=" uri
}

# [OBSOLETE?] Old dictionary API (via HTTP GET).
function bingRequestUrl(text, sl, tl, hl) {
    return HttpPathPrefix "/translator/api/Dictionary/Lookup?"  \
        "from=" sl "&to=" tl "&text=" preprocess(text)
}

# Main Bing Translator API (via HTTP POST).
function bingPostRequestUrl(text, sl, tl, hl, type) {
    if (type == "lookup")
        return HttpPathPrefix "/tlookupv3"
    #else if (type == "transliterate")
    #    return HttpPathPrefix "/ttransliteratev3"
    else # type == "translate"
        return HttpPathPrefix "/ttranslatev3" sprintf("?IG=%s&IID=%s", IG, IID)
}

function bingPostRequestContentType(text, sl, tl, hl, type) {
    return "application/x-www-form-urlencoded"
}

function bingPostRequestUserAgent(text, sl, tl, hl, type) {
    return ""
}

function bingPostRequestBody(text, sl, tl, hl, type) {
    if (type == "lookup")
        return "&text=" quote(text) "&from=" sl "&to=" tl
    #else if (type == "transliterate")
    #    return "&text=" quote(text) "&language=" sl "&toScript=" "latn"
    else # type == "translate"
        return "&text=" quote(text) "&fromLang=" sl "&to=" tl BingTokenKey
}

# Get the translation of a string.
function bingTranslate(text, sl, tl, hl,
                       isVerbose, toSpeech, returnPlaylist, returnIl,
                       ####
                       r,
                       content, tokens, ast, dicContent, dicTokens, dicAst,
                       _sl, _tl, _hl, il, isPhonetic,
                       translation, phonetics, oPhonetics,
                       wordClasses, words, wordBackTranslations,
                       wShowOriginal, wShowOriginalPhonetics,
                       wShowTranslation, wShowTranslationPhonetics,
                       wShowLanguages, wShowDictionary,
                       i, j, k, group, temp, saveSortedIn) {
    isPhonetic = match(tl, /^@/)
    tl = substr(tl, 1 + isPhonetic)

    if (!getCode(tl)) {
        # Check if target language is supported
        w("[WARNING] Unknown target language code: " tl)
    } else if (isRTL(tl)) {
        # Check if target language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(tl) " is a right-to-left language, but FriBidi is not found.")
    }
    _sl = getCode(sl); if (!_sl) _sl = sl
    _tl = getCode(tl); if (!_tl) _tl = tl
    _hl = getCode(hl); if (!_hl) _hl = hl

    bingSetup()

    # Hot-patches for Bing's own translator language codes
    # See: <https://docs.microsoft.com/en-us/azure/cognitive-services/Translator/language-support>
    if (_sl == "auto")  _sl = "auto-detect"
    if (_sl == "tl")    _sl = "fil" # Bing uses 'fil' for Filipino
    if (_sl == "hmn")   _sl = "mww" # Bing uses 'mww' for Hmong Daw
    if (_sl == "no")    _sl = "nb"  # Bing uses Norwegian Bokmål
    # Bing uses 'pt' or 'pt-br' for Brazilian Portuguese, 'pt-pt' for European Portuguese
    if (_sl == "pt-BR") _sl = "pt" # just pt-br
    else if (_sl == "pt-PT") _sl = "pt" # FIXME: support pt-pt
    if (_sl == "zh-CN") _sl = "zh-Hans"
    if (_sl == "zh-TW") _sl = "zh-Hant"
    if (_tl == "tl")    _tl = "fil"
    if (_tl == "hmn")   _tl = "mww"
    if (_tl == "no")    _tl = "nb"
    if (_tl == "pt-BR") _tl = "pt"
    else if (_tl == "pt-PT") _tl = "pt-pt"
    if (_tl == "zh-CN") _tl = "zh-Hans"
    if (_tl == "zh-TW") _tl = "zh-Hant"

    # Translation
    content = postResponse(text, _sl, _tl, _hl, "translate")
    if (content == "") {
        # Empty content. Assume "301 Moved Permanently" and use cn.bing.com
        HttpHost = "cn.bing.com"
        # Just dirty hack
        if (Option["proxy"]) {
            HttpPathPrefix = HttpProtocol HttpHost
        } else {
            HttpService = "/" "inet" "/tcp/0/" HttpHost "/" HttpPort  # FIXME: inet version
        }

        # Try again
        content = postResponse(text, _sl, _tl, _hl, "translate")
    }
    if (Option["dump"])
        return content
    tokenize(tokens, content)
    parseJson(ast, tokens)

    l(content, "content", 1, 1)
    l(tokens, "tokens", 1, 0, 1)
    l(ast, "ast")
    if (!isarray(ast) || !anything(ast)) {
        e("[ERROR] Oops! Something went wrong and I can't translate it for you :(")
        ExitCode = 1
        return
    }

    translation = unparameterize(ast[0 SUBSEP 0 SUBSEP "translations" SUBSEP 0 SUBSEP "text"])

    returnIl[0] = il = _sl == "auto-detect" ?
        unparameterize(ast[0 SUBSEP 0 SUBSEP "detectedLanguage" SUBSEP "language"]) : _sl
    if (Option["verbose"] < -1)
        return il
    if (Option["verbose"] < 0)
        return getList(il)

    # Transliteration
    wShowTranslationPhonetics = Option["show-translation-phonetics"]
    if (wShowTranslationPhonetics) {
        split(_tl, group, "-")
        #content = postResponse(translation, group[1], group[1], _hl, "transliterate")
        #phonetics = unparameterize(content)
        phonetics = unparameterize(ast[0 SUBSEP 0 SUBSEP "translations" SUBSEP 0 SUBSEP "transliteration" \
                                       SUBSEP "text"])
        if (phonetics == translation) phonetics = ""
    }

    # Generate output
    if (!isVerbose) {
        # Brief mode

        r = isPhonetic && phonetics ?
            prettify("brief-translation-phonetics", join(phonetics, " ")) :
            prettify("brief-translation", s(translation, tl))

    } else {
        # Verbose mode

        wShowOriginal = Option["show-original"]
        wShowTranslation = Option["show-translation"]
        wShowLanguages = Option["show-languages"]
        wShowDictionary = Option["show-dictionary"]

        # Transliteration (original)
        wShowOriginalPhonetics = Option["show-original-phonetics"]
        if (wShowOriginalPhonetics) {
            split(_sl, group, "-")
            #content = postResponse(text, group[1], group[1], _hl, "transliterate")
            #oPhonetics = unparameterize(content)
            delete ast  # purge old AST
            content = postResponse(text, il, il, _hl, "translate")
            tokenize(tokens, content)
            parseJson(ast, tokens)
            oPhonetics = unparameterize(ast[0 SUBSEP 0 SUBSEP "translations" SUBSEP 0 \
                                            SUBSEP "transliteration" SUBSEP "text"])
            if (oPhonetics == text) oPhonetics = ""
        }

        if (!oPhonetics) wShowOriginalPhonetics = 0
        if (!phonetics) wShowTranslationPhonetics = 0

        if (wShowOriginal) {
            # Display: original text & phonetics
            if (r) r = r RS RS
            r = r m("-- display original text")
            r = r prettify("original", s(text, _sl))
            if (wShowOriginalPhonetics)
                r = r RS prettify("original-phonetics", showPhonetics(join(oPhonetics, " "), _sl))
        }

        if (wShowTranslation) {
            # Display: major translation & phonetics
            if (r) r = r RS RS
            r = r m("-- display major translation")
            r = r prettify("translation", s(translation, tl))
            if (wShowTranslationPhonetics)
                r = r RS prettify("translation-phonetics", showPhonetics(join(phonetics, " "), tl))
        }

        if (wShowLanguages) {
            # Display: source language -> target language
            if (r) r = r RS RS
            r = r m("-- display source language -> target language")
            temp = Option["fmt-languages"]
            if (!temp) temp = "[ %s -> %t ]"
            split(temp, group, /(%s|%S|%t|%T)/)
            r = r prettify("languages", group[1])
            if (temp ~ /%s/)
                r = r prettify("languages-sl", getDisplay(il))
            if (temp ~ /%S/)
                r = r prettify("languages-sl", getName(il))
            r = r prettify("languages", group[2])
            if (temp ~ /%t/)
                r = r prettify("languages-tl", getDisplay(tl))
            if (temp ~ /%T/)
                r = r prettify("languages-tl", getName(tl))
            r = r prettify("languages", group[3])
        }

        if (wShowDictionary) {
            # Dictionary API
            dicContent = postResponse(text, il, _tl, _hl, "lookup")
            if (dicContent != "") {
                tokenize(dicTokens, dicContent)
                parseJson(dicAst, dicTokens)

                l(dicContent, "dicContent", 1, 1)
                l(dicTokens, "dicTokens", 1, 0, 1)
                l(dicAst, "dicAst")

                saveSortedIn = PROCINFO["sorted_in"]
                PROCINFO["sorted_in"] = "compareByIndexFields"
                for (i in dicAst) {
                    if (match(i, "^0" SUBSEP "0" SUBSEP "translations" SUBSEP "([[:digit:]]+)" SUBSEP \
                              "posTag$", group))
                        wordClasses[group[1]] = tolower(literal(dicAst[i]))
                }
                for (i in dicAst) {
                    if (match(i, "^0" SUBSEP "0" SUBSEP "translations" SUBSEP "([[:digit:]]+)" SUBSEP \
                              "displayTarget$", group))
                        words[wordClasses[group[1]]][group[1]] = literal(dicAst[i])
                    if (match(i, "^0" SUBSEP "0" SUBSEP "translations" SUBSEP "([[:digit:]]+)" SUBSEP \
                              "backTranslations" SUBSEP "([[:digit:]]+)" SUBSEP "displayText$", group))
                        wordBackTranslations[wordClasses[group[1]]][group[1]][group[2]] = literal(dicAst[i])
                }
                PROCINFO["sorted_in"] = saveSortedIn

                # Display: dictionary entries
                if (r) r = r RS
                r = r m("-- display dictionary entries")
                for (i = 0; i < length(words); i++) {
                    r = (i > 0 ? r RS : r) RS prettify("dictionary-word-class", s(wordClasses[i], hl))

                    for (j in words[wordClasses[i]]) {
                        r = r RS prettify("dictionary-word", ins(1, words[wordClasses[i]][j], tl))

                        if (isRTL(il))
                            explanation = join(wordBackTranslations[wordClasses[i]][j], ", ")
                        else {
                            explanation = prettify("dictionary-explanation-item",
                                                   wordBackTranslations[wordClasses[i]][j][0])
                            for (k = 1; k < length(wordBackTranslations[wordClasses[i]][j]); k++)
                                explanation = explanation prettify("dictionary-explanation", ", ") \
                                    prettify("dictionary-explanation-item",
                                             wordBackTranslations[wordClasses[i]][j][k])
                        }

                        if (isRTL(il))
                            r = r RS prettify("dictionary-explanation-item", ins(2, explanation, il))
                        else
                            r = r RS ins(2, explanation)
                    }
                }
            }
        }
    }

    if (toSpeech) {
        returnPlaylist[0]["text"] = translation
        returnPlaylist[0]["tl"] = _tl
    }

    return r
}


Translate: add translate()

Translate: add translate()

#!/usr/bin/gawk -f

@include "lib/Commons"
@include "lib/PLTokenizer"
@include "lib/PLParser"

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist,
                        ####
                        altTranslations, article, ast, content,
                        explanation, group, i, il,
                        isPhonetic, j, original, phonetics,
                        r, rtl, saveSortedIn, segments,
                        temp, tokens, translation, translations,
                        word, words, wordClasses) {
    isPhonetic = match(tl, /^@/)
    tl = substr(tl, 1 + isPhonetic)

    if (!getCode(tl)) {
        # Check if target language is supported
        w("[WARNING] Unknown target language code: " tl)
    } else if (getCode(tl) != "auto" && rtl = Locale[getCode(tl)]["rtl"]) {
        # Check if target language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(tl)]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    content = getResponse(text, sl, tl, hl)

    plTokenize(tokens, content)
    plParse(ast, tokens)

    if (!anything(ast)) {
        e("[ERROR] Oops! Something went wrong and I can't translate it for you :(")
        ExitCode = 1
    }

    # Debug mode
    if (Option["debug"]) {
        d(sprintf("content='%s'", content))
        da(tokens, "tokens[%s]='%s'")
        da(ast, "ast[%s]='%s'")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in ast) {
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "0$")
            append(translations, postprocess(literal(ast[i])))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "1$")
            append(original, literal(ast[i]))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "2$")
            append(phonetics, literal(ast[i]))

        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            wordClasses[group[1]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]][group[3]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]]["1"][group[3]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            segments[group[1]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            altTranslations[group[1]][group[2]] = postprocess(literal(ast[i]))

        # Identified source languages
        if (i ~ "^0" SUBSEP "8" SUBSEP "0" SUBSEP "[[:digit:]]+$")
            append(il, literal(ast[i]))
    }
    PROCINFO["sorted_in"] = saveSortedIn

    translation = join(translations)

    if (!anything(il)) il[0] = sl

    # Generate output
    if (!isVerbose) {
        # Brief mode

        r = isPhonetic && anything(phonetics) ?
            join(phonetics) :  # phonetic transcription
            s(translation, tl) # target language

        if (toSpeech) {
            returnPlaylist[0]["text"] = translation
            returnPlaylist[0]["tl"] = tl
        }

    } else {
        # Verbose mode

        r = AnsiCode["bold"] s(translation, tl) AnsiCode["no bold"] # target language
        if (anything(phonetics))
            r = r "\n" AnsiCode["bold"] join(phonetics) AnsiCode["no bold"] # phonetic transcription

        if (anything(altTranslations[0])) {
            # List alternative translations

            if (Locale[getCode(hl)]["rtl"] || Locale[getCode(il[0])]["rtl"])
                r = r "\n\n" s(sprintf(Locale[getCode(hl)]["message"], join(original))) # caution: mixed languages, BiDi invoked must be implemented correctly (i.e. FriBidi is required)
            else
                r = r "\n\n" sprintf(Locale[getCode(hl)]["message"], join(original))
            if (Locale[getCode(il[0])]["rtl"] || Locale[getCode(tl)]["rtl"])
                r = r "\n" s("(" Locale[getCode(il[0])]["endonym"] " ➔ " Locale[getCode(tl)]["endonym"] ")") # caution: mixed languages
            else
                r = r "\n" "(" Locale[getCode(il[0])]["endonym"] " ➔ " Locale[getCode(tl)]["endonym"] ")"

            temp = segments[0] "(" join(altTranslations[0], "/") ")"
            for (i = 1; i < length(altTranslations); i++)
                temp = temp " " segments[i] "(" join(altTranslations[i], "/") ")"
            if (Locale[getCode(il[0])]["rtl"] || Locale[getCode(tl)]["rtl"])
                r = r "\n" AnsiCode["bold"] s(temp) AnsiCode["no bold"] # caution: mixed languages
            else
                r = r "\n" AnsiCode["bold"] temp AnsiCode["no bold"]
        }

        if (anything(wordClasses)) {
            # List dictionary entries

            for (i = 0; i < length(words); i++) {
                r = r "\n\n" s("[" wordClasses[i] "]", hl) # home language
                for (j = 0; j < length(words[i]); j++) {
                    word = words[i][j][0]
                    explanation = join(words[i][j][1], ", ")
                    article = words[i][j][4]

                    if (rtl) {
                        r = r "\n" AnsiCode["bold"] sprintf("%" Option["width"] - 4 "s", s((article ?
                                                                                "(" article ")" :
                                                                                "") " " word, tl, Option["width"] - 4)) AnsiCode["no bold"] # target language
                        r = r "\n" s(explanation, il[0], Option["width"] - 8) # identified source language
                    } else {
                        r = r "\n" "    " AnsiCode["bold"] show((article ?
                                                                 "(" article ") " :
                                                                 "") word, tl) AnsiCode["no bold"] # target language
                        r = r "\n" "        " s(explanation, il[0], Option["width"] - 8) # identified source language
                    }
                }
            }
        }

        if (toSpeech) {
            if (index(Locale[getCode(hl)]["message"], "%s") > 2) {
                returnPlaylist[0]["text"] = sprintf(Locale[getCode(hl)]["message"], "")
                returnPlaylist[0]["tl"] = hl
                returnPlaylist[1]["text"] = join(original)
                returnPlaylist[1]["tl"] = il[0]
            } else {
                returnPlaylist[0]["text"] = join(original)
                returnPlaylist[0]["tl"] = il[0]
                returnPlaylist[1]["text"] = sprintf(Locale[getCode(hl)]["message"], "")
                returnPlaylist[1]["tl"] = hl
            }
            returnPlaylist[2]["text"] = translation
            returnPlaylist[2]["tl"] = tl
        }
    }

    return r
}

# Translate the source text (into all target languages).
function translate(text,
                   ####
                   i, j, playlist) {

    if (!getCode(Option["hl"])) {
        # Check if home language is supported
        w("[WARNING] Unknown language code: " Option["hl"] ", fallback to English: en")
        Option["hl"] = "en" # fallback to English
    } else if (getCode(Option["hl"]) != "auto" && Locale[getCode(Option["hl"])]["rtl"]) {
        # Check if home language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(Option["hl"])]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    if (!getCode(Option["sl"])) {
        # Check if source language is supported
        w("[WARNING] Unknown source language code: " Option["sl"])
    } else if (getCode(Option["sl"]) != "auto" && Locale[getCode(Option["sl"])]["rtl"]) {
        # Check if source language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(Option["sl"])]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    for (i in Option["tl"]) {
        # Non-interactive verbose mode: separator between translations
        if (!Option["interactive"]) {
            # Separator for multiple targets
            if (i > 1) {
                for (j = 0; j < Option["width"]; j++) printf "─"
                printf "\n"
            }
        }

        print getTranslation(text, Option["sl"], Option["tl"][i], Option["hl"], Option["verbose"], Option["play"], playlist) > Option["output"]

        if (Option["play"])
            if (Option["player"])
                for (j in playlist)
                    play(playlist[j]["text"], playlist[j]["tl"])
            else if (SpeechSynthesizer)
                for (j in playlist)
                    print playlist[j]["text"] | SpeechSynthesizer
    }
}

#!/usr/bin/gawk -f

@include "lib/Commons"
@include "lib/PLTokenizer"
@include "lib/PLParser"

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist,
                        ####
                        altTranslations, article, ast, content,
                        explanation, group, i, il,
                        isPhonetic, j, original, phonetics,
                        r, rtl, saveSortedIn, segments,
                        temp, tokens, translation, translations,
                        word, words, wordClasses) {
    isPhonetic = match(tl, /^@/)
    tl = substr(tl, 1 + isPhonetic)

    if (!getCode(tl)) {
        # Check if target language is supported
        w("[WARNING] Unknown target language code: " tl)
    } else if (rtl = Locale[getCode(tl)]["rtl"]) {
        # Check if target language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(tl)]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    content = getResponse(text, sl, tl, hl)

    plTokenize(tokens, content)
    plParse(ast, tokens)

    if (!anything(ast)) {
        e("[ERROR] Oops! Something went wrong and I can't translate it for you :(")
        ExitCode = 1
    }

    # Debug mode
    if (Option["debug"]) {
        d(sprintf("content='%s'", content))
        da(tokens, "tokens[%s]='%s'")
        da(ast, "ast[%s]='%s'")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in ast) {
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "0$")
            append(translations, postprocess(literal(ast[i])))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "1$")
            append(original, literal(ast[i]))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "2$")
            append(phonetics, literal(ast[i]))

        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            wordClasses[group[1]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]][group[3]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]]["1"][group[3]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            segments[group[1]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            altTranslations[group[1]][group[2]] = postprocess(literal(ast[i]))

        # Identified source languages
        if (i ~ "^0" SUBSEP "8" SUBSEP "0" SUBSEP "[[:digit:]]+$")
            append(il, literal(ast[i]))
    }
    PROCINFO["sorted_in"] = saveSortedIn

    translation = join(translations)

    if (!anything(il)) il[0] = sl

    # Generate output
    if (!isVerbose) {
        # Brief mode

        r = isPhonetic && anything(phonetics) ?
            join(phonetics) :  # phonetic transcription
            s(translation, tl) # target language

        if (toSpeech) {
            returnPlaylist[0]["text"] = translation
            returnPlaylist[0]["tl"] = tl
        }

    } else {
        # Verbose mode

        r = AnsiCode["bold"] s(translation, tl) AnsiCode["no bold"] # target language
        if (anything(phonetics))
            r = r "\n" AnsiCode["bold"] join(phonetics) AnsiCode["no bold"] # phonetic transcription

        if (anything(altTranslations[0])) {
            # List alternative translations

            if (Locale[getCode(hl)]["rtl"] || Locale[getCode(il[0])]["rtl"])
                r = r "\n\n" s(sprintf(Locale[getCode(hl)]["message"], join(original))) # caution: mixed languages, BiDi invoked must be implemented correctly (i.e. FriBidi is required)
            else
                r = r "\n\n" sprintf(Locale[getCode(hl)]["message"], join(original))
            if (Locale[getCode(il[0])]["rtl"] || Locale[getCode(tl)]["rtl"])
                r = r "\n" s("(" Locale[getCode(il[0])]["endonym"] " ➔ " Locale[getCode(tl)]["endonym"] ")") # caution: mixed languages
            else
                r = r "\n" "(" Locale[getCode(il[0])]["endonym"] " ➔ " Locale[getCode(tl)]["endonym"] ")"

            temp = segments[0] "(" join(altTranslations[0], "/") ")"
            for (i = 1; i < length(altTranslations); i++)
                temp = temp " " segments[i] "(" join(altTranslations[i], "/") ")"
            if (Locale[getCode(il[0])]["rtl"] || Locale[getCode(tl)]["rtl"])
                r = r "\n" AnsiCode["bold"] s(temp) AnsiCode["no bold"] # caution: mixed languages
            else
                r = r "\n" AnsiCode["bold"] temp AnsiCode["no bold"]
        }

        if (anything(wordClasses)) {
            # List dictionary entries

            for (i = 0; i < length(words); i++) {
                r = r "\n\n" s("[" wordClasses[i] "]", hl) # home language
                for (j = 0; j < length(words[i]); j++) {
                    word = words[i][j][0]
                    explanation = join(words[i][j][1], ", ")
                    article = words[i][j][4]

                    if (rtl) {
                        r = r "\n" AnsiCode["bold"] sprintf("%" Option["width"] - 4 "s", s((article ?
                                                                                "(" article ")" :
                                                                                "") " " word, tl, Option["width"] - 4)) AnsiCode["no bold"] # target language
                        r = r "\n" s(explanation, il[0], Option["width"] - 8) # identified source language
                    } else {
                        r = r "\n" "    " AnsiCode["bold"] show((article ?
                                                                 "(" article ") " :
                                                                 "") word, tl) AnsiCode["no bold"] # target language
                        r = r "\n" "        " s(explanation, il[0], Option["width"] - 8) # identified source language
                    }
                }
            }
        }

        if (toSpeech) {
            if (index(Locale[getCode(hl)]["message"], "%s") > 2) {
                returnPlaylist[0]["text"] = sprintf(Locale[getCode(hl)]["message"], "")
                returnPlaylist[0]["tl"] = hl
                returnPlaylist[1]["text"] = join(original)
                returnPlaylist[1]["tl"] = il[0]
            } else {
                returnPlaylist[0]["text"] = join(original)
                returnPlaylist[0]["tl"] = il[0]
                returnPlaylist[1]["text"] = sprintf(Locale[getCode(hl)]["message"], "")
                returnPlaylist[1]["tl"] = hl
            }
            returnPlaylist[2]["text"] = translation
            returnPlaylist[2]["tl"] = tl
        }
    }

    return r
}


No longer generates zero bytes in the script's output file.

BEGIN	{ print "#include \"pop_push.h\""
	  print
	  print "char *pop_push[] = {"
	  print "\"\","
	  switch = 0
}
/aar/	{ switch = NR }
	{ if (switch) {
	      if ($1 == "cal" || $1 == "cai") $3 = "-?"
	      printf("/* %s */ \"%s\",\n",$1,$3)
	      col_2[NR-switch] = $2
	      comment[NR-switch] = $1
	  }
	}
END	{ print "};"
	  print
	  print "char flow_tab[]= {"
	  print "'\\000',"
	  for(i=0; i < NR-switch; i++) {
		inf = col_2[i]
		f_out = "/* " comment[i] " */ "
		if (substr(inf,1,1)=="b") f_out = f_out "HASLABEL|"
		if (substr(inf,2,1)=="c") f_out = f_out "CONDBRA"
		else if (substr(inf,2,1)=="t") f_out = f_out "JUMP"
		else f_out = f_out "'\\000'"
		print f_out","
	  }
	  print "};"
	}

BEGIN	{ print "#include \"pop_push.h\""
	  print
	  print "char *pop_push[] = {"
	  print "\"\","
	  switch = 0
}
/aar/	{ switch = NR }
	{ if (switch) {
	      if ($1 == "cal" || $1 == "cai") $3 = "-?"
	      printf("/* %s */ \"%s\",\n",$1,$3)
	      col_2[NR-switch] = $2
	      comment[NR-switch] = $1
	  }
	}
END	{ print "};"
	  print
	  print "char flow_tab[]= {"
	  print "'\000',"
	  for(i=0; i < NR-switch; i++) {
		inf = col_2[i]
		f_out = "/* " comment[i] " */ "
		if (substr(inf,1,1)=="b") f_out = f_out "HASLABEL|"
		if (substr(inf,2,1)=="c") f_out = f_out "CONDBRA"
		else if (substr(inf,2,1)=="t") f_out = f_out "JUMP"
		else f_out = f_out "'\000'"
		print f_out","
	  }
	  print "};"
	}


Change linkparser, repair header grabbing feature

BEGIN {
	version = "0.2.0"
	printf("link-parser, v%s\n", version)

	true = 1
	false = 0
	g_pat_srccommon = "\\/sw\\/vobs.+"
	g_sroot = ""
	inst_root = ""
	tar_root = ""

	# Colors
	RED="\033[0;31m"
	GRN="\033[0;32m"
	BLU="\033[0;34m"
	NC="\033[0m"
	BOL="\033[1m"

	count = 0

	if (arg ~ /ut/) {
		do_test();
		SKIP_END = true
		exit 0
	}

	if (arg ~ /pretend/) {
		pretend = true
	}

	if (arg ~ /dest=/) {
		destdir = gensub(/^.+dest=([^ \t;,]+)/, "\\1", "g", arg)
		if (system("test -d " destdir) != 0) {
			print "Directory [" destdir "] doesn't exist!"
			SKIP_END = true
			exit 0
		}
	}

	# Skip head lines
	getline
}

/^[^-]/ {
	link = $0
	# Remove the ending ' \'
	gsub(/[ \t]+\\$/, "", link)

	if (g_sroot == "") {
		printf("Grabbing sources and headers ...\n")
		g_sroot = get_sroot(link)
		"date +%Y%m%d-%H%M%S" | getline date
		inst_root = g_sroot"-"date
	}

	# ---------
	# Sources
	# ---------

	cmd_file = link".cmd"
	while ((getline line < cmd_file) > 0) {
		# abosolute path
		if (line ~ /[ \t]+-c[ \t]+\/.+$/)
			source = gensub(/^.+[ \t]+-c[ \t]+(.+)$/, "\\1", "g", line)
		else # relative path
			source = gensub(/^cd (.+);.+-c (.+)/, "\\1/\\2", "g", line)

		# path transform
		#patt = "vobs\\/esam\\/objects\\/([^\\/]+\\/){2,2}sources\\/"
		patt = "vobs\\/esam\\/objects\\/.+\\/sources\\/(flat|src)"
		gsub(patt, "vobs/dsl/sw/flat", source)

		patt = "vobs\\/esam\\/objects\\/.+\\/sources\\/itf"
		gsub(patt, "vobs/dsl/sw/itf", source)

		patt = "\\/esam\\/.+\\/dummy\\.c"
		if (source ~ patt)
			continue;

		rel_install(source, g_sroot, inst_root)
	}
	close(cmd_file)

	# ---------
	# Headers
	# ---------

	gsub(/\.(obj|o|lib)$/, "", link)
	dep_file = link ".D"

	if ((getline < dep_file) < 0)
		next

	# Get path prefix
	cur_path = $1
	gsub(/^#/, "", cur_path)

	while ((getline < dep_file) > 0) {
		gsub(/.+:/, " ")
		for (i = 1; i <= NF; i++) {
			#if ($i !~ /\.(h|hpp)$/)
			#	continue
			if ($i == "\\") continue

			header = cur_path "/" $i
			if (!buildroot_host_path) {
				patt = "(.+\\/esam\\/.+\\/output\\/host)\\/.+$"
				if (header ~ patt) {
					buildroot_host_path = gensub(patt, "\\1", "g", header)

					patt = ".+\\/"g_sroot"\\/sw\\/vobs"
					gsub(patt, inst_root"/sw/vobs", buildroot_host_path)
				}
			}

			rel_install(header, g_sroot, inst_root)
		}
	}
	close(dep_file)
}

END {
	if (SKIP_END)
		exit 0
	print "Total dumped files: " count

	if (buildroot_host_path && arg !~ /pretend/)
		system("mv " buildroot_host_path " " inst_root)

	if (arg ~ /tar/)
		make_tarball(arg)
}

function rel_install(source, rel_root, new_root) {
	count++

	dir = dirname(source)
	base = basename(source)
	patt = "^.+\\/"rel_root"\\/(sw.+)"
	rel_path = gensub(patt, "\\1", "g", dir)

	if (destdir)
		new_root = destdir "/" new_root

	if (pretend) {
		print source > rel_root".source.list"
		print rel_path"/"base > rel_root".relative.list"
		return 0
	}

	system("mkdir -p " new_root "/" rel_path)
	system("cp -fL " source " " new_root "/" rel_path)
}

function make_tarball(flag) {
	tarball = inst_root ".tar.gz"
	printf("Generating source tarball %s%s%s%s ...\n", RED, BOL, tarball, NC)

	if (pretend || SKIP_END)
		exit 0

	"pwd" |getline oldpwd
	if (destdir)
		system("cd " destdir)

	system("tar zcf " tarball " " inst_root)
	if (flag ~ /purge/)
		system("rm -r "inst_root)

	if (destdir)
		system("cd " oldpwd)
}

function get_sroot(line) {
	if (line !~ g_pat_srccommon)
		return ""

	gsub(g_pat_srccommon, "", line);
	gsub(/.+\//, "", line);
	return line;
}

# dirname
function dirname(path) {
	if (path == "")
		return ""

	# Remove the ending '/'
	gsub(/\/+$/, "", path)

	# Remove basename
	gsub(/[^\/]+$/, "", path)
	if (path == "") return "."

	# Remove the ending '/' again
	gsub(/\/+$/, "", path)
	if (path == "")
		return "/"

	return path
}

# basename
function basename(path) {
	if (path == "")
		return ""

	# Remove the ending '/'
	gsub(/\/+$/, "", path)
	if (path == "")
		return "/"

	path = gensub(/^.+\/+(.+)$/, "\\1", "g", path)

	return path;
}

###### Test #########
function do_test() {
	printf("%s%s%s%s\n", BLU, BOL, "---- Test get_sroot ----", NC)
	test_get_sroot("/repo/lulinw/isam/sw/vobs/esam/build");
	test_get_sroot("/repo/lulinw/isam/sw/esam/build");

	printf("%s%s%s%s\n", BLU, BOL, "---- Test dirname ----", NC)
	test_dirname("/home/abc");
	test_dirname("home///abc//");
	test_dirname("home///abc/de//");
	test_dirname("../abc//");
	test_dirname("abc");
	test_dirname("..");
	test_dirname("..///");

	printf("%s%s%s%s\n", BLU, BOL, "---- Test basename ----", NC)
	test_basename("/home//abc")
	test_basename("///")
	test_basename("/")
	test_basename("..")
	test_basename("../abc//")
	test_basename("/../abc//")
	test_basename("/../abc/efg/")
}

function test_get_sroot(line) {
	if ((ret = get_sroot(line)) == "") {
		printf("Unexpected line [PAT=%s]:\n  %s%s%s\n",
				g_pat_srccommon, RED, line, NC)
	} else
		printf("Root of %s is\n  %s%s%s\n", line, GRN, get_sroot(line), NC)
}

function test_dirname(path) {
	printf("dirname(%s) is \n  %s%s%s\n", path, GRN, dirname(path), NC)
}

function test_basename(path) {
	printf("basename(%s) is \n %s%s%s\n", path, GRN, basename(path), NC)
}

BEGIN {
	version = "0.1.3"
	printf("link-parser, v%s\n", version)

	true = 1
	false = 0
	g_pat_srccommon = "\\/sw\\/vobs.+"
	g_sroot = ""
	inst_root = ""
	tar_root = ""

	# Colors
	RED="\033[0;31m"
	GRN="\033[0;32m"
	BLU="\033[0;34m"
	NC="\033[0m"
	BOL="\033[1m"

	count = 0

	if (arg ~ /ut/) {
		do_test();
		SKIP_END = true
		exit 0
	}

	if (arg ~ /pretend/) {
		pretend = true
	}

	if (arg ~ /dest=/) {
		destdir = gensub(/^.+dest=([^ \t;,]+)/, "\\1", "g", arg)
		if (system("test -d " destdir) != 0) {
			print "Directory [" destdir "] doesn't exist!"
			SKIP_END = true
			exit 0
		}
	}

	# Skip head lines
	getline
}

/^[^-]/ {
	link = $0
	# Remove the ending ' \'
	gsub(/[ \t]+\\$/, "", link)

	if (g_sroot == "") {
		printf("Grabbing sources and headers ...\n")
		g_sroot = get_sroot(link)
		"date +%Y%m%d-%H%M%S" | getline date
		inst_root = g_sroot"-"date
	}

	# ---------
	# Sources
	# ---------

	cmd_file = link".cmd"
	while ((getline line < cmd_file) > 0) {
		# abosolute path
		if (line ~ /[ \t]+-c[ \t]+\/.+$/)
			source = gensub(/^.+[ \t]+-c[ \t]+(.+)$/, "\\1", "g", line)
		else # relative path
			source = gensub(/^cd (.+);.+-c (.+)/, "\\1/\\2", "g", line)

		# path transform
		#patt = "vobs\\/esam\\/objects\\/([^\\/]+\\/){2,2}sources\\/"
		patt = "vobs\\/esam\\/objects\\/.+\\/sources\\/(flat|src)"
		gsub(patt, "vobs/dsl/sw/flat", source)

		patt = "vobs\\/esam\\/objects\\/.+\\/sources\\/itf"
		gsub(patt, "vobs/dsl/sw/itf", source)

		patt = "\\/esam\\/.+\\/dummy\\.c"
		if (source ~ patt)
			continue;

		rel_install(source, g_sroot, inst_root)
	}
	close(cmd_file)

	# ---------
	# Headers
	# ---------

#	gsub(/\.(obj|o|lib)$/, "", link)
#	dep_file = link ".D"
#
#	if ((getline < dep_file) < 0)
#		next
#
#	# Get path prefix
#	cur_path = $1
#	gsub(/^#/, "", cur_path)
#
#	while ((getline < dep_file) > 0) {
#		gsub(/:/, " ")
#		for (i = 1; i <= NF; i++) {
#			if ($i !~ /\.(h|hpp)$/)
#				continue
#			header = cur_path "/" $i
#
#			rel_install(header, g_sroot, inst_root)
#		}
#	}
#	close(dep_file)
}

END {
	if (SKIP_END)
		exit 0
	print "Total dumped files: " count

	if (arg ~ /tar/)
		make_tarball(arg)
}

function rel_install(source, rel_root, new_root) {
	count++

	dir = dirname(source)
	base = basename(source)
	patt = "^.+\\/"rel_root"\\/(sw.+)"
	rel_path = gensub(patt, "\\1", "g", dir)

	if (destdir)
		new_root = destdir "/" new_root

	if (pretend) {
		print source > rel_root".source.list"
		print rel_path"/"base > rel_root".relative.list"
		return 0
	}

	system("mkdir -p " new_root "/" rel_path)
	system("cp -L " source " " new_root "/" rel_path)
}

function make_tarball(flag) {
	tarball = inst_root ".tar.gz"
	printf("Generating source tarball %s%s%s%s ...\n", RED, BOL, tarball, NC)

	if (pretend || SKIP_END)
		exit 0

	"pwd" |getline oldpwd
	if (destdir)
		system("cd " destdir)

	system("tar zcf " tarball " " inst_root)
	if (flag ~ /purge/)
		system("rm -r "inst_root)

	if (destdir)
		system("cd " oldpwd)
}

function get_sroot(line) {
	if (line !~ g_pat_srccommon)
		return ""

	gsub(g_pat_srccommon, "", line);
	gsub(/.+\//, "", line);
	return line;
}

# dirname
function dirname(path) {
	if (path == "")
		return ""

	# Remove the ending '/'
	gsub(/\/+$/, "", path)

	# Remove basename
	gsub(/[^\/]+$/, "", path)
	if (path == "") return "."

	# Remove the ending '/' again
	gsub(/\/+$/, "", path)
	if (path == "")
		return "/"

	return path
}

# basename
function basename(path) {
	if (path == "")
		return ""

	# Remove the ending '/'
	gsub(/\/+$/, "", path)
	if (path == "")
		return "/"

	path = gensub(/^.+\/+(.+)$/, "\\1", "g", path)

	return path;
}

###### Test #########
function do_test() {
	printf("%s%s%s%s\n", BLU, BOL, "---- Test get_sroot ----", NC)
	test_get_sroot("/repo/lulinw/isam/sw/vobs/esam/build");
	test_get_sroot("/repo/lulinw/isam/sw/esam/build");

	printf("%s%s%s%s\n", BLU, BOL, "---- Test dirname ----", NC)
	test_dirname("/home/abc");
	test_dirname("home///abc//");
	test_dirname("home///abc/de//");
	test_dirname("../abc//");
	test_dirname("abc");
	test_dirname("..");
	test_dirname("..///");

	printf("%s%s%s%s\n", BLU, BOL, "---- Test basename ----", NC)
	test_basename("/home//abc")
	test_basename("///")
	test_basename("/")
	test_basename("..")
	test_basename("../abc//")
	test_basename("/../abc//")
	test_basename("/../abc/efg/")
}

function test_get_sroot(line) {
	if ((ret = get_sroot(line)) == "") {
		printf("Unexpected line [PAT=%s]:\n  %s%s%s\n",
				g_pat_srccommon, RED, line, NC)
	} else
		printf("Root of %s is\n  %s%s%s\n", line, GRN, get_sroot(line), NC)
}

function test_dirname(path) {
	printf("dirname(%s) is \n  %s%s%s\n", path, GRN, dirname(path), NC)
}

function test_basename(path) {
	printf("basename(%s) is \n %s%s%s\n", path, GRN, basename(path), NC)
}