Switch to tags for node properties.

Switch to tags for node properties.

#!/usr/bin/awk

# TODO:
# cat nfa.dot | dot -Tplain > /tmp/a.mp \
# && mpost -s 'outputformat="svg"' -s 'outputtemplate="%j-%c.%o"' /tmp/a.mp

# Only intended to consume Dot generated from libfsm's .dot output,
# and not graphviz diagrams in general.

function sname(s)
{
	if (s == "start") {
		return "S0";
	}

	return s;
}

# TODO: convert label from graphviz's html to a utf8
# This is either <xyz> or abc (with no quotes)
function slabel(s)
{
	# XXX: placeholder
	if (s == "<>") {
		return "";
	}
	if (s == "\"\"") {
		return "";
	}

# XXX: placeholder
gsub(/ε/, "e", s); # XXX: why not?
gsub(/"/, "", s)
gsub(/</, "", s)
gsub(/>/, "", s)

	return s;
}

BEGIN {

	# TODO: would be simpler to gvpr to rename start to S0
	# TODO: common stuff for multiple graphs
	print "pair S[];"
	print "pair q[];"
	print "path e;"

	print "ahlength := 10bp;"
	print "ahangle  := 45;"

	print "path S[].n;"

	# diameter of node
	print "numeric S[].diam;"

	# number of self-edges
	print "numeric S[].loops;"

#	print "secondarydef v projectedalong w =";
#	print "	if pair(v) and pair(w):";
#	print "		(v dotprod w) / (w dotprod w) * w";
#	print "	else:";
#	print "		errmessage \"arguments must be pairs\"";
#	print "	fi";
#	print "enddef;";

}

/^graph / {
	printf "beginfig(%s)\n", $2
}

# node S6 0.97222 0.41667 0.30556 0.30556 "" solid circle black lightgrey
# node name x y width height label style shape color fillcolor
/^node / {
	name   = sname($2);
	x      = $3;
	y      = $4;
	width  = $5;
	height = $6;
	label  = slabel($7);
	style  = $8;
	shape  = $9;
	# TODO: label delimited by <>, may contain spaces

	printf "\t%s.loops := 0;\n", name

	printf "\t%s := (%fin, %fin);\n", name, x, y

# TODO: when plotting in graphviz, assume all nodes have the same width (not zero!). enough for "SXX" perhaps
# that should align them all on the same centre line. and pass fixedsize=true

	# TODO: explain this is diameter
	# TODO: store 8bp as a constant somewhere
# TODO: explain we take diameter from label presence and endstateness.
# ignore graphviz height and width, because it scales with label width and that looks bad for S1 vs S5
	diam = 0.3 + length(label) * 0.05;

	if (shape == "doublecircle") {
		printf "\t%s.diam = %fin + 8bp;\n", name, diam;
	} else {
		printf "\t%s.diam = %fin;\n", name, diam;
	}

	if (shape == "doublecircle") {
# TODO: would rather && together paths to %s.n or somesuch
		printf "\tdraw fullcircle scaled (%s.diam - 8bp) shifted %s withpen pencircle scaled 1bp;\n", name, name
	}

	printf "\t%s.n := fullcircle scaled %s.diam shifted %s;\n", name, name, name

	if ($2 != "start") {
		printf "\tdraw %s.n withpen pencircle scaled 1bp;\n", name

		if (label != "") {
			printf "\tlabel(\"%s\", %s);\n", label, name
		}
	}
}

# edge tail head n x1 y1 .. xn yn [label xl yl] style color
# edge S3 S6 10 3.5244 0.20119 3.2727 0.19014 2.7322 0.17358 2.2778 0.20833 1.873 0.23929 1.7755 0.28067 1.375 0.34722 1.3389 0.35322 1.3009 0.35974 1.2635 0.3663 <ε> 2.3264 0.3125 solid black
/^edge / {
	tail = sname($2);
	head = sname($3);
	n    = $4;

	v = split($0, a, " ");
	# TODO: assert n < v - whatever
	# TODO: assert n >= 4 (knot, control, control); mind trailing stuff... label etc

	# TODO: note we assume -Etailclip=false -Eheadclip=false -Edir=none
	# TODO: explain that requirement

	# things i wish the graphviz documentation said about -Tplain:
	# graphviz always outputs cubic b-splines
	# 3i+1 points total. format is { knot, control, control }* knot
	# bezier is ordered tail to head
	# first two points (knot, control) are ontop of each other, in the tail node
	# last two points (control, knot) are ontop of each other, in the head node
	# coordinates are in inches

	printf "\te :=\n";
	for (i = 1; i <= n - 1; i += 3) {
		# knot
		ox = a[(i + 0) * 2 + 3];
		oy = a[(i + 0) * 2 + 4];

		# control
		c1x = a[(i + 1) * 2 + 3];
		c1y = a[(i + 1) * 2 + 4];

		# control
		c2x = a[(i + 2) * 2 + 3];
		c2y = a[(i + 2) * 2 + 4];

		printf "\t(%fin, %fin) .. controls (%fin, %fin) and (%fin, %fin) ..\n", ox, oy, c1x, c1y, c2x, c2y;
	}
	# TODO: knot at end, which i haven't drawn
	# last point:
	{
		# knot
		ox = a[(n + 0) * 2 + 3];
		oy = a[(n + 0) * 2 + 4];
		printf "\t(%fin, %fin);\n", ox, oy;
	}

	printf "\tdraw e withpen pencircle scaled 0.25bp withcolor red;\n";

	# TODO: better explain this
	# TODO: explain reversed for q, else we'd find p again, where head == tail
	printf "pair p; p = e intersectionpoint %s.n;\n", head;
	printf "pair q; q = (reverse e) intersectionpoint %s.n;\n", tail;

	# XXX: assuming no space in label
label = slabel(a[n * 2 + 5]);
lx    = (a[n * 2 + 6]);
ly    = (a[n * 2 + 7]);

	if (n * 2 == v - 4 - 2) {
		label = "";
	}

	if (head == tail) {
		printf "path pp; pp = %s -- 0.5[p, q];\n", head;
printf "draw p -- q withpen pencircle scaled 0.5bp withcolor green;\n";
printf "draw 0.5[p, q] withpen pencircle scaled 0.8bp withcolor green;\n";
printf "draw %s -- 0.5[p, q] withpen pencircle scaled 0.8bp withcolor green;\n", head;

		# extend loop 1.25 * diameter
		# count number of self-loops for this node, and increment extension
		printf "\t%s.loops := %s.loops + 1;", head, head
		print "l := length pp;"
		printf "\tpair m; m := (0.5 + 2 * mlog(%s.loops + 1) / 256) * %s.diam * unitvector(direction l of pp) shifted %s;\n", head, head, head
# u3 = u1 projectedalong u2;
		printf "\tdraw %s -- m withpen pencircle scaled 0.5bp withcolor green;\n", head

		printf "\tpath b; b = %s .. q .. m .. p .. %s;\n", tail, head;

		if (label != "") {
			# TODO: top or bottom, depending on green line direction
			printf "\tlabel.top(\"%s\", m);\n", slabel(label)
		}
	} else {
		printf "\tpath b; b = %s .. q .. p .. %s;\n", tail, head;

		# TODO: explain this. we permit a 2% lengthening threshold relative to graphviz's b-spline
		# this affects NFA especially
		print "\tr := arclength b / arclength e;\n";
		print "\tboolean g; g := (r > 1.02) or (r < 0.93);"; # XXX: or (arclength b > 6in);"; - no, wigglyness instead

		print "\tif g:";
		print "\t\tb := e;";
		print "\tfi;";

# if it wouldn't have been a straight line, go via the label point instead:
# XXX: this idea doesn't work, because i don't know which side of the line the label point would be on
#printf "\t\tb := %s .. q .. (%fin, %fin) .. p .. %s;\n", tail, lx, ly, head;

		if (label != "") {
			printf "\tpair bl;\n";
			printf "\tpair ml; ml := .5[%s, %s];\n", tail, head;

# TODO: new idea; draw right angle from midpoint, and use that to find intersection with b
# this way it should always align for multiple edges, even if on a slant

printf "draw (%fin, %fin) withpen pencircle scaled 5bp withcolor red;\n", lx, ly
printf "draw point (length b / 2) of b withpen pencircle scaled 4bp withcolor blue;\n"
#printf "draw %s -- %s withpen pencircle scaled 1bp withcolor green;\n", tail, head
#printf "draw ml withpen pencircle scaled 5bp withcolor green;\n"

			print "\tif g:";
				printf "\t\tbl := (%fin, %fin);", lx, ly;
			print "\telse:";
				printf "\t\tbl := point (length b / 2) of b;\n";
			print "\tfi;";

# TODO: plus label delta, distancing it from point. make a label function
# TODO: maybe an "extend" function, to extend a path in its direction. use dotprod for that?

			# lft | rt | top | bot | ulft | urt | llft | lrt
			# labels ought to be below, if they're below the straight line from head-tail, or above otherwise
			# TODO: round label coordinates to grid? quantize rather
# XXX: if using graphviz's point, label exactly on the spot
print "\tif g:";
# TODO: still want to distance by +/- 4bp or so
			printf "\t\tlabel(\"%s\", bl);\n", label
print "\telse:";
			printf "\tif ypart bl < ypart ml:\n" # TODO: threshold for considering "below"
			printf "\t\tlabel.bot(\"%s\", bl shifted (0, -3bp));\n", label
			printf "\telse:\n"
			printf "\t\tlabel.top(\"%s\", bl shifted (0, +3bp));\n", label
			printf "\tfi;\n"
print "\tfi;";
		}
	}

	printf "drawarrow b cutbefore q cutafter p withpen pencircle scaled 1bp;\n";
}

/^stop$/ {
	# XXX: ought to be per graph; stop is per file
	print "endfig;"
}

END {
	print "end."
}

#!/usr/bin/awk

# TODO:
# cat nfa.dot | dot -Tplain > /tmp/a.mp \
# && mpost -s 'outputformat="svg"' -s 'outputtemplate="%j-%c.%o"' /tmp/a.mp

# Only intended to consume Dot generated from libfsm's .dot output,
# and not graphviz diagrams in general.

function sname(s)
{
	if (s == "start") {
		return "S0";
	}

	return s;
}

# TODO: convert label from graphviz's html to a utf8
# This is either <xyz> or abc (with no quotes)
function slabel(s)
{
	# XXX: placeholder
	if (s == "<>") {
		return "";
	}
	if (s == "\"\"") {
		return "";
	}

# XXX: placeholder
gsub(/ε/, "e", s); # XXX: why not?
gsub(/"/, "", s)
gsub(/</, "", s)
gsub(/>/, "", s)

	return s;
}

BEGIN {

	# TODO: would be simpler to gvpr to rename start to S0
	# TODO: common stuff for multiple graphs
	print "pair S[];"
	print "pair q[];"
	print "path nS[];"
	print "path e;"

	# diameter of node
	print "numeric dS[];"

	# number of self-edges
	print "numeric lS[];"

	print "ahlength := 10bp;"
	print "ahangle  := 45;"

#	print "secondarydef v projectedalong w =";
#	print "	if pair(v) and pair(w):";
#	print "		(v dotprod w) / (w dotprod w) * w";
#	print "	else:";
#	print "		errmessage \"arguments must be pairs\"";
#	print "	fi";
#	print "enddef;";

}

/^graph / {
	printf "beginfig(%s)\n", $2
}

# node S6 0.97222 0.41667 0.30556 0.30556 "" solid circle black lightgrey
# node name x y width height label style shape color fillcolor
/^node / {
	name   = sname($2);
	x      = $3;
	y      = $4;
	width  = $5;
	height = $6;
	label  = slabel($7);
	style  = $8;
	shape  = $9;
	# TODO: label delimited by <>, may contain spaces

	printf "\tl%s := 0;\n", name

	printf "\t%s := (%fin, %fin);\n", name, x, y

# TODO: when plotting in graphviz, assume all nodes have the same width (not zero!). enough for "SXX" perhaps
# that should align them all on the same centre line. and pass fixedsize=true

	# TODO: explain this is diameter
	# TODO: store 8bp as a constant somewhere
# TODO: explain we take diameter from label presence and endstateness.
# ignore graphviz height and width, because it scales with label width and that looks bad for S1 vs S5
	diam = 0.3 + length(label) * 0.05;

	if (shape == "doublecircle") {
		printf "\td%s = %fin + 8bp;\n", name, diam;
	} else {
		printf "\td%s = %fin;\n", name, diam;
	}

	if (shape == "doublecircle") {
# TODO: would rather && together paths to n%s or somesuch
		printf "\tdraw fullcircle scaled (d%s - 8bp) shifted %s withpen pencircle scaled 1bp;\n", name, name
	}

	printf "\tn%s := fullcircle scaled d%s shifted %s;\n", name, name, name

	if ($2 != "start") {
		printf "\tdraw n%s withpen pencircle scaled 1bp;\n", name

		if (label != "") {
			printf "\tlabel(\"%s\", %s);\n", label, name
		}
	}
}

# edge tail head n x1 y1 .. xn yn [label xl yl] style color
# edge S3 S6 10 3.5244 0.20119 3.2727 0.19014 2.7322 0.17358 2.2778 0.20833 1.873 0.23929 1.7755 0.28067 1.375 0.34722 1.3389 0.35322 1.3009 0.35974 1.2635 0.3663 <ε> 2.3264 0.3125 solid black
/^edge / {
	tail = sname($2);
	head = sname($3);
	n    = $4;

	v = split($0, a, " ");
	# TODO: assert n < v - whatever
	# TODO: assert n >= 4 (knot, control, control); mind trailing stuff... label etc

	# TODO: note we assume -Etailclip=false -Eheadclip=false -Edir=none
	# TODO: explain that requirement

	# things i wish the graphviz documentation said about -Tplain:
	# graphviz always outputs cubic b-splines
	# 3i+1 points total. format is { knot, control, control }* knot
	# bezier is ordered tail to head
	# first two points (knot, control) are ontop of each other, in the tail node
	# last two points (control, knot) are ontop of each other, in the head node
	# coordinates are in inches

	printf "\te :=\n";
	for (i = 1; i <= n - 1; i += 3) {
		# knot
		ox = a[(i + 0) * 2 + 3];
		oy = a[(i + 0) * 2 + 4];

		# control
		c1x = a[(i + 1) * 2 + 3];
		c1y = a[(i + 1) * 2 + 4];

		# control
		c2x = a[(i + 2) * 2 + 3];
		c2y = a[(i + 2) * 2 + 4];

		printf "\t(%fin, %fin) .. controls (%fin, %fin) and (%fin, %fin) ..\n", ox, oy, c1x, c1y, c2x, c2y;
	}
	# TODO: knot at end, which i haven't drawn
	# last point:
	{
		# knot
		ox = a[(n + 0) * 2 + 3];
		oy = a[(n + 0) * 2 + 4];
		printf "\t(%fin, %fin);\n", ox, oy;
	}

	printf "\tdraw e withpen pencircle scaled 0.25bp withcolor red;\n";

	# TODO: better explain this
	# TODO: explain reversed for q, else we'd find p again, where head == tail
	printf "pair p; p = e intersectionpoint n%s;\n", head;
	printf "pair q; q = (reverse e) intersectionpoint n%s;\n", tail;

	# XXX: assuming no space in label
label = slabel(a[n * 2 + 5]);
lx    = (a[n * 2 + 6]);
ly    = (a[n * 2 + 7]);

	if (n * 2 == v - 4 - 2) {
		label = "";
	}

	if (head == tail) {
		printf "path pp; pp = %s -- 0.5[p, q];\n", head;
printf "draw p -- q withpen pencircle scaled 0.5bp withcolor green;\n";
printf "draw 0.5[p, q] withpen pencircle scaled 0.8bp withcolor green;\n";
printf "draw %s -- 0.5[p, q] withpen pencircle scaled 0.8bp withcolor green;\n", head;

		# extend loop 1.25 * diameter
		# count number of self-loops for this node, and increment extension
		printf "\tl%s := l%s + 1;", head, head
		print "l := length pp;"
		printf "\tpair m; m := (0.5 + 2 * mlog(l%s + 1) / 256) * d%s * unitvector(direction l of pp) shifted %s;\n", head, head, head
# u3 = u1 projectedalong u2;
		printf "\tdraw %s -- m withpen pencircle scaled 0.5bp withcolor green;\n", head

		printf "\tpath b; b = %s .. q .. m .. p .. %s;\n", tail, head;

		if (label != "") {
			# TODO: top or bottom, depending on green line direction
			printf "\tlabel.top(\"%s\", m);\n", slabel(label)
		}
	} else {
		printf "\tpath b; b = %s .. q .. p .. %s;\n", tail, head;

		# TODO: explain this. we permit a 2% lengthening threshold relative to graphviz's b-spline
		# this affects NFA especially
		print "\tr := arclength b / arclength e;\n";
		print "\tboolean g; g := (r > 1.02) or (r < 0.93);"; # XXX: or (arclength b > 6in);"; - no, wigglyness instead

		print "\tif g:";
		print "\t\tb := e;";
		print "\tfi;";

# if it wouldn't have been a straight line, go via the label point instead:
# XXX: this idea doesn't work, because i don't know which side of the line the label point would be on
#printf "\t\tb := %s .. q .. (%fin, %fin) .. p .. %s;\n", tail, lx, ly, head;

		if (label != "") {
			printf "\tpair bl;\n";
			printf "\tpair ml; ml := .5[%s, %s];\n", tail, head;

# TODO: new idea; draw right angle from midpoint, and use that to find intersection with b
# this way it should always align for multiple edges, even if on a slant

printf "draw (%fin, %fin) withpen pencircle scaled 5bp withcolor red;\n", lx, ly
printf "draw point (length b / 2) of b withpen pencircle scaled 4bp withcolor blue;\n"
#printf "draw %s -- %s withpen pencircle scaled 1bp withcolor green;\n", tail, head
#printf "draw ml withpen pencircle scaled 5bp withcolor green;\n"

			print "\tif g:";
				printf "\t\tbl := (%fin, %fin);", lx, ly;
			print "\telse:";
				printf "\t\tbl := point (length b / 2) of b;\n";
			print "\tfi;";

# TODO: plus label delta, distancing it from point. make a label function
# TODO: maybe an "extend" function, to extend a path in its direction. use dotprod for that?

			# lft | rt | top | bot | ulft | urt | llft | lrt
			# labels ought to be below, if they're below the straight line from head-tail, or above otherwise
			# TODO: round label coordinates to grid? quantize rather
# XXX: if using graphviz's point, label exactly on the spot
print "\tif g:";
# TODO: still want to distance by +/- 4bp or so
			printf "\t\tlabel(\"%s\", bl);\n", label
print "\telse:";
			printf "\tif ypart bl < ypart ml:\n" # TODO: threshold for considering "below"
			printf "\t\tlabel.bot(\"%s\", bl shifted (0, -3bp));\n", label
			printf "\telse:\n"
			printf "\t\tlabel.top(\"%s\", bl shifted (0, +3bp));\n", label
			printf "\tfi;\n"
print "\tfi;";
		}
	}

	printf "drawarrow b cutbefore q cutafter p withpen pencircle scaled 1bp;\n";
}

/^stop$/ {
	# XXX: ought to be per graph; stop is per file
	print "endfig;"
}

END {
	print "end."
}


Only use POSIX Awk features.

Only use POSIX Awk features.

# $FreeBSD$

# Read global symbols from object file.
BEGIN {
        while ("nm -g " ARGV[1] | getline) {
                if (match($0, /^[^[:space:]]+ [^AU] (.*)$/)) {
                        syms[$3] = $2
                }
        }
}

# De-list symbols from the export list.
{
        if (ARGIND == 1)
                nextfile
        delete syms[$0]
}

# Strip commons, make everything else local.
END {
        for (member in syms) {
                if (syms[member] == "C")
                        print "-N" member
                else
                        print "-L" member
        }
}

# $FreeBSD$

# Read global symbols from object file.
BEGIN {
        while ("nm -g " ARGV[1] | getline) {
                if (match($0, /^[^[:space:]]+ [^AU] (.*)$/)) {
                        syms[$3] = $2
                }
        }
}

# De-list symbols from the export list.
// {
        if (ARGIND == 1)
                nextfile
        delete syms[$0]
}

# Strip commons, make everything else local.
END {
        for (member in syms) {
                if (syms[member] == "C")
                        print "-N" member
                else
                        print "-L" member
        }
}


msg_parser.awk: generate two parser tables, default and extended

darcs-hash:20070919214030-65a35-56c54d9f070bdc3d32acfb7089d970050a3d921e.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);

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

  total = 0;
  ordinary = 0;
  basic = 0;
  extra = 0;
  without_experimental = 0;

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

  ERRNO="error";
}

function name_hash (name)
{
  hash = 0;

  len = length(name);

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

  if (hash == 0) {
    print "*** msg_parser.awk: calculating hash failed\n";
    exit(5);
  }

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

  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]*/, "", p);
    }

    print p > PR;
  }
}

#
# Repeat each line in the footer containing the magic replacement
# pattern with an 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 && /^#### DEFAULT HEADER LIST ENDS HERE ####$/ { basic=total; }
HLIST && /^#### EXPERIMENTAL HEADER LIST STARTS HERE ####$/ {
  without_experimental = total; }

HLIST && /^[a-z]/ { protos($1, $0, 0, $2);
  headers[total++] = $1;
  Extra[$1] = extra++;
}
/^#### 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; total++; ordinary++;
}

function print_parser_table(struct, scope, name, N, N_EXPERIMENTAL)
{
  if (PT) {
    printf("/* Ordinary %u, extra %u, experimental %u */\n",
	   ordinary, N - ordinary, N_EXPERIMENTAL - N) > PT;
    printf("struct %s {\n", struct) > PT;
    printf("  %s_t base;\n", module) > PT;
    printf("  msg_header_t *extra[%u];\n", N - ordinary) > PT;
    if (N != N_EXPERIMENTAL) {
      print "#if SU_HAVE_EXPERIMENTAL" > PT;
      printf("  msg_header_t *extra[%u];\n", N_EXPERIMENTAL - N) > PT;
      print "#endif" > PT;
    }
    printf("};\n\n") > PT;

    printf("%s\n", scope) > PT;
    printf("msg_mclass_t const %s[1] = \n{{\n", name) > 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 (struct %s),\n", 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;
    if (N != N_EXPERIMENTAL) {
      print "#if SU_HAVE_EXPERIMENTAL" > PT;
      printf("  %d,\n", N_EXPERIMENTAL) > PT;
      print "#else" > PT;
    }
    printf("  %d,\n", N) > PT;
    if (N != N_EXPERIMENTAL) {
      print "#endif" > PT;
    }

    printf("  {\n") > PT;

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

        flags = header_flags[n]; if (flags) flags = ",\n      " flags;

	if (i >= N) {
	  print "#if SU_HAVE_EXPERIMENTAL" > PT;
	}

	if (i >= ordinary) {
	  printf("    { %s_%s_class,\n" \
		 "      msg_offsetof(struct %s, extra[%u])%s }%s\n",
		 tprefix, n, 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 (i >= N) {
	  printf("#else\n    { NULL, 0 }%s\n#endif\n", c) > PT;
	}
      }
      else {
	printf("    { NULL, 0 }%s\n", c) > PT;
      }
    }
    printf("  }\n}};\n\n") > PT;

    }
}

END {
  if (failed) { exit };

  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;

    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 (basic == 0) basic = total;
    if (without_experimental == 0) without_experimental = total;

    split("", header_hash);
    split("", index_hash);

    for (i = 0; i < basic; 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;
      index_hash[j] = i;
    }

    m = module "_mclass";
    s = "_d_" module "_t";

    /* Add basic headers */
    if (ordinary == basic) {
      print_parser_table(s, "", m, basic, basic);
    }
    else if (basic < without_experimental) {
      print_parser_table(s, "", m, basic, basic);
    }
    else {
      print_parser_table(s, "", m, without_experimental, basic);
   }

    /* Hash extra headers */
   for (i = basic; 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;
      index_hash[j] = i;
    }

    if (basic < total) {
      m = module "_ext_mclass";
      s = "_e_" module "_s";
      print_parser_table(s, "static", m, without_experimental, total);
    }

    printf("msg_mclass_t const * %s_extended_mclass = %s;\n", module, m) > 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 = length(name);

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

  if (hash == 0) {
    print "*** msg_parser.awk: calculating hash failed\n";
    exit(5);
  }

  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]*/, "", 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;
}


msg_parser.awk: not extending header structure unless needed.

Removed gawk-ish /* comments */.

darcs-hash:20071005172604-88462-534f27ddb24a0863406057e7f64cdab96830003b.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);

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

  total = 0;
  ordinary = 0;
  basic = 0;
  extra = 0;
  without_experimental = 0;

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

  ERRNO="error";
}

function name_hash (name)
{
  hash = 0;

  len = length(name);

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

  if (hash == 0) {
    print "*** msg_parser.awk: calculating hash failed\n";
    exit(5);
  }

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

  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]*/, "", p);
    }

    print p > PR;
  }
}

#
# Repeat each line in the footer containing the magic replacement
# pattern with an 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 && /^#### DEFAULT HEADER LIST ENDS HERE ####$/ { basic=total; }
HLIST && /^#### EXPERIMENTAL HEADER LIST STARTS HERE ####$/ {
  without_experimental = total; }

HLIST && /^[a-z]/ { protos($1, $0, 0, $2);
  headers[total++] = $1;
  Extra[$1] = extra++;
}
/^#### 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; total++; ordinary++;
}

function print_parser_table(struct, scope, name, N, N_EXPERIMENTAL)
{
  if (PT) {
    if (N > ordinary) {
      printf("/* Ordinary %u, extra %u, experimental %u */\n",
	     ordinary, N - ordinary, N_EXPERIMENTAL - N) > PT;
      printf("struct %s {\n", struct) > PT;
      printf("  %s_t base;\n", module) > PT;
      printf("  msg_header_t *extra[%u];\n", N - ordinary) > PT;
      if (N != N_EXPERIMENTAL) {
	print "#if SU_HAVE_EXPERIMENTAL" > PT;
	printf("  msg_header_t *extra[%u];\n", N_EXPERIMENTAL - N) > PT;
	print "#endif" > PT;
      }
      printf("};\n\n") > PT;
    }

    printf("%s\n", scope) > PT;
    printf("msg_mclass_t const %s[1] = \n{{\n", name) > 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;
    if (N > ordinary) {
      printf("  sizeof (struct %s),\n", struct) > PT;
    }
    else {
      printf("  sizeof (%s_t),\n", module) > 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;
    if (N != N_EXPERIMENTAL) {
      print "#if SU_HAVE_EXPERIMENTAL" > PT;
      printf("  %d,\n", N_EXPERIMENTAL) > PT;
      print "#else" > PT;
    }
    printf("  %d,\n", N) > PT;
    if (N != N_EXPERIMENTAL) {
      print "#endif" > PT;
    }

    printf("  {\n") > PT;

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

        flags = header_flags[n]; if (flags) flags = ",\n      " flags;

	if (i >= N) {
	  print "#if SU_HAVE_EXPERIMENTAL" > PT;
	}

	if (i >= ordinary) {
	  printf("    { %s_%s_class,\n" \
		 "      msg_offsetof(struct %s, extra[%u])%s }%s\n",
		 tprefix, n, 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 (i >= N) {
	  printf("#else\n    { NULL, 0 }%s\n#endif\n", c) > PT;
	}
      }
      else {
	printf("    { NULL, 0 }%s\n", c) > PT;
      }
    }
    printf("  }\n}};\n\n") > PT;

    }
}

END {
  if (failed) { exit };

  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;

    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 (basic == 0) basic = total;
    if (without_experimental == 0) without_experimental = total;

    split("", header_hash);
    split("", index_hash);

    for (i = 0; i < basic; 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;
      index_hash[j] = i;
    }

    m = module "_mclass";
    s = "_d_" module "_t";

    # Add basic headers
    if (ordinary == basic) {
      print_parser_table(s, "", m, basic, basic);
    }
    else if (basic < without_experimental) {
      print_parser_table(s, "", m, basic, basic);
    }
    else {
      print_parser_table(s, "", m, without_experimental, basic);
   }

   if (0) {

   # Hash extra headers
   for (i = basic; 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;
      index_hash[j] = i;
    }

    if (basic < total) {
      m = module "_ext_mclass";
      s = "_e_" module "_s";
      print_parser_table(s, "static", m, without_experimental, total);
    }

    printf("msg_mclass_t const * %s_extended_mclass = %s;\n\n", module, m) > PT;

    }

    if (basic < total) {
      printf("msg_hclass_t const * const %s_extensions[] = {\n", module) > PT;
      for (i = basic; i < total; i++) {
	if (i == without_experimental) {
	  print "#if SU_HAVE_EXPERIMENTAL" > PT;
        }
	printf("  %s_%s_class,\n", module, headers[i]) > PT;
      }
      if (total != without_experimental)
	print "#endif" > PT;
      print "  NULL\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);

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

  total = 0;
  ordinary = 0;
  basic = 0;
  extra = 0;
  without_experimental = 0;

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

  ERRNO="error";
}

function name_hash (name)
{
  hash = 0;

  len = length(name);

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

  if (hash == 0) {
    print "*** msg_parser.awk: calculating hash failed\n";
    exit(5);
  }

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

  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]*/, "", p);
    }

    print p > PR;
  }
}

#
# Repeat each line in the footer containing the magic replacement
# pattern with an 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 && /^#### DEFAULT HEADER LIST ENDS HERE ####$/ { basic=total; }
HLIST && /^#### EXPERIMENTAL HEADER LIST STARTS HERE ####$/ {
  without_experimental = total; }

HLIST && /^[a-z]/ { protos($1, $0, 0, $2);
  headers[total++] = $1;
  Extra[$1] = extra++;
}
/^#### 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; total++; ordinary++;
}

function print_parser_table(struct, scope, name, N, N_EXPERIMENTAL)
{
  if (PT) {
    printf("/* Ordinary %u, extra %u, experimental %u */\n",
	   ordinary, N - ordinary, N_EXPERIMENTAL - N) > PT;
    printf("struct %s {\n", struct) > PT;
    printf("  %s_t base;\n", module) > PT;
    printf("  msg_header_t *extra[%u];\n", N - ordinary) > PT;
    if (N != N_EXPERIMENTAL) {
      print "#if SU_HAVE_EXPERIMENTAL" > PT;
      printf("  msg_header_t *extra[%u];\n", N_EXPERIMENTAL - N) > PT;
      print "#endif" > PT;
    }
    printf("};\n\n") > PT;

    printf("%s\n", scope) > PT;
    printf("msg_mclass_t const %s[1] = \n{{\n", name) > 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 (struct %s),\n", 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;
    if (N != N_EXPERIMENTAL) {
      print "#if SU_HAVE_EXPERIMENTAL" > PT;
      printf("  %d,\n", N_EXPERIMENTAL) > PT;
      print "#else" > PT;
    }
    printf("  %d,\n", N) > PT;
    if (N != N_EXPERIMENTAL) {
      print "#endif" > PT;
    }

    printf("  {\n") > PT;

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

        flags = header_flags[n]; if (flags) flags = ",\n      " flags;

	if (i >= N) {
	  print "#if SU_HAVE_EXPERIMENTAL" > PT;
	}

	if (i >= ordinary) {
	  printf("    { %s_%s_class,\n" \
		 "      msg_offsetof(struct %s, extra[%u])%s }%s\n",
		 tprefix, n, 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 (i >= N) {
	  printf("#else\n    { NULL, 0 }%s\n#endif\n", c) > PT;
	}
      }
      else {
	printf("    { NULL, 0 }%s\n", c) > PT;
      }
    }
    printf("  }\n}};\n\n") > PT;

    }
}

END {
  if (failed) { exit };

  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;

    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 (basic == 0) basic = total;
    if (without_experimental == 0) without_experimental = total;

    split("", header_hash);
    split("", index_hash);

    for (i = 0; i < basic; 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;
      index_hash[j] = i;
    }

    m = module "_mclass";
    s = "_d_" module "_t";

    /* Add basic headers */
    if (ordinary == basic) {
      print_parser_table(s, "", m, basic, basic);
    }
    else if (basic < without_experimental) {
      print_parser_table(s, "", m, basic, basic);
    }
    else {
      print_parser_table(s, "", m, without_experimental, basic);
   }

   if (0) {

   /* Hash extra headers */
   for (i = basic; 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;
      index_hash[j] = i;
    }

    if (basic < total) {
      m = module "_ext_mclass";
      s = "_e_" module "_s";
      print_parser_table(s, "static", m, without_experimental, total);
    }

    printf("msg_mclass_t const * %s_extended_mclass = %s;\n\n", module, m) > PT;

    }

    if (basic < total) {
      printf("msg_hclass_t const * const %s_extensions[] = {\n", module) > PT;
      for (i = basic; i < total; i++) {
	if (i == without_experimental) {
	  print "#if SU_HAVE_EXPERIMENTAL" > PT;
        }
	printf("  %s_%s_class,\n", module, headers[i]) > PT;
      }
      if (total != without_experimental)
	print "#endif" > PT;
      print "  NULL\n};\n\n" > PT;
    }
  }

  exit success;
}


[proofalytics] drop extra .v from links

[proofalytics] drop extra .v from links

BEGIN {
  FS = ","
  if(commit == "") {
    commit = "master"
  }
  gh = "https://github.com/uwplse/verdi/blob/" commit
}

{
  if (NR == 1) {
    print $0
  } else {
    printf("%s,%s\n", gh, $1, $1, $2)
  }
}

BEGIN {
  FS = ","
  if(commit == "") {
    commit = "master"
  }
  gh = "https://github.com/uwplse/verdi/blob/" commit
}

{
  if (NR == 1) {
    print $0
  } else {
    printf("%s.v,%s\n", gh, $1, $1, $2)
  }
}


Clean up bonus and part2 code

Clean up bonus and part2 code

# Create column files with lists of series titles, descriptions, number
# of seasons, and number of episodes, to be pasted into a spreadsheet.
# Note that IN_CANADA affects processing.
#
# Create a separate file with a line for each episode containing
# seriesNumber, episodeURL, seriesTitle, seasonNumber, episodeNumber,
# episodeTitle, & episodeDuration with the same columns as the primary
# spreadsheet so they can be combined into one.
#
# Create a separate file with a line for each series duration
#
# Return the total duration of all series
#
# INVOCATION:
#       curl -s https://acorn.tv/192/ \
#           | awk -v TITLE_FILE=$TITLE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v NUM_EPISODES_FILE=$NUM_EPISODES_FILE \
#           -v EPISODE_CURL_FILE=$EPISODE_CURL_FILE -v IN_CANADA=$IN_CANADA \
#           -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v SERIES_NUMBER=$lastRow \
#           -v DURATION_FILE=$DURATION_FILE -v ERROR_FILE=$ERROR_FILE -f getAcornFrom-seriesPages.awk
#
# INPUT:
#        <title>Acorn TV | 19-2</title>
#  ---
#        19-2
#        <meta itemprop="numberOfEpisodes" content="30" />
#        <meta itemprop="numberOfSeasons" content="3" />
#  ---
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#  ---
#
"The writing is sublime" (New \
#        York Times) in this anything-but-a-procedural cop drama ... as reluctant partners \
#        patrolling the streets of Montreal. Not Available in Canada. CC Available.

#  ---
#
We're sorry, but Acorn TV is not available in this territory.

#  ---
#        <meta itemprop="seasonNumber" content="1" />
#  ---
#
#  ---
#        <meta itemprop="timeRequired" content="T44M06S" />
#  ---
#
Partners

#  ---
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

#
#
#
#  OUTPUT:
#       $EPISODE_INFO_FILE
#

# Extract the series title
/<title>/ {
    sub (/.*<title>Acorn TV \| /,"")
    sub (/<.*/,"")
    gsub (/&/,"\\&")
    # gsub (/'/,"'")
    # gsub (/"/,"\"\"")
    if (match ($0, /^The /)) {
        $0 = substr($0, 5) ", The"
    }
    episodeLinesFound = 0
    numEpisodesStr = ""
    seriesTitle = $0
    print seriesTitle >> TITLE_FILE
    next
}

# Extract the number of episodes in the series
/itemprop="numberOfEpisodes"/ {
    split ($0,fld,"\"")
    episodeLinesFound += 1
    if (episodeLinesFound == 1)
        totalEpisodes = fld[4]
    if (episodeLinesFound != 1)
        numEpisodesStr = numEpisodesStr "+" fld[4]
    next
}

# Extract the number of seasons in the series
/itemprop="numberOfSeasons"/ {
    split ($0,fld,"\"")
    numSeasons = fld[4]
    if (numSeasons == 0)
        printf ("==> No seasons: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    print numSeasons >> NUM_SEASONS_FILE
    next
}

/Acorn TV is not available in this territory/ {
    printf ("==> Not available here: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
}

# Extract the series description
/id="franchise-description"/ {
    # get rid of boilerplate
    split ($0,fld,"[<>]")
    description = fld[3]
    # get rid of unnecessary characters and text
    gsub (/\\/,"",description)
    if (IN_CANADA != "yes") {
        sub (/Series [[:digit:]]* not available in Canada\./,"",description)
        sub (/Not [Aa]vailable in Canada\./,"",description)
        sub (/NOT AVAILABLE IN CANADA\./,"",description)
    }
    sub (/CC Available\. CC Available/,"CC Available",description)
    # fix sloppy input spacing
    sub (/\.CC Available/,". CC Available",description)
    gsub (/ \./,".",description)
    gsub (/  */," ",description)
    sub (/^ */,"",description)
    sub (/ *$/,"",description)
    # fix funky HTML characters
    gsub (/'/,"'",description)
    print description >> DESCRIPTION_FILE
    next
}

# Extract the seasonNumber
# Note that Acorn "season" numbers may not correspond to actual season numbers.
# They simply refer to the number of seasons available on Acorn.
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
    next
}

# Extract the episode URL
/> EPISODE_CURL_FILE
    next
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    #
    seriesSecs += secs
    seriesMins += mins + int(seriesSecs / 60)
    seriesHrs += hrs + int(seriesMins / 60)
    seriesSecs %= 60; seriesMins %= 60
    #
    totalSecs += secs
    totalMins += mins + int(totalSecs / 60)
    totalHrs += hrs + int(totalMins / 60)
    totalSecs %= 60; totalMins %= 60
    #
    episodeDuration = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    if (episodeDuration == "00:00:00")
        printf ("==> No duration: %d\t%s  %s\n", SERIES_NUMBER, seriesTitle, episodeURL)  >> ERROR_FILE
    next
}

# Extract the episode title
/
/ {
    split ($0,fld,"[<>]")
    episodeTitle = fld[3]
    gsub (/&/,"\\&", episodeTitle)
    next
}

# There are a number of variants in the "episodeNumber" string
#
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    # If show is a Prequel, use "P"
    if (episodeURL ~ /\/prequel/)
        showType = "P"
    # If show is a Miniseries, use "M"
    if (episodeURL ~ /\/miniseries/)
        showType = "M"
    # If we don't know what kind of show it is, use "S"
    if (showType == "")
        showType = "S"
    # Default episodeType to "E"
    episodeType = "E"
    # Default bonus & parttwo episodeType to "X"
    if (episodeURL ~ /bonus\/|[0-9]{1,2}parttwo\//)
        episodeType = "X"
    # Default christmasspecial & seriesfinale episodeType to "X" & don't increment seasonNumber
    if (episodeURL ~ /christmasspecial\/|\/seriesfinale\//) {
        episodeType = "X"
        split (episodeURL, part, "/")
        printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
        seasonNumber -= 1
    }
    #
    split ($0,fld,"[<>]")
    episodeNumber = fld[5]
    # Feature films don't have episodes
    if (episodeURL ~ /\/featurefilm\//)
        next
    # neither should single episode series
    if (totalEpisodes == 1)
        next
    # The season number should match that in the URL
    # Birds of a Feather, Doc Martin, Murdoch, Poirot, Rebus, Vera, and others have problems
    if (episodeURL ~ \
           /\/series[0-9]{1,2}\/|[0-9]{1,2}bonus\/|[0-9]{1,2}parttwo\/|\/murdoch\/season[0-9]{1,2}/) {
        split (episodeURL, part, "/")
        URLseasonNumber = part[5]
        sub (/bonus/,"",URLseasonNumber)
        # Foyle's War has sets: foyleswar/series9set8bonus/
        sub (/set[0-9]/,"",URLseasonNumber)
        # Midsomer Murders has two parts: midsomermurders/series19parttwo/
        sub (/parttwo/,"",URLseasonNumber)
        sub (/christmasspecial/,"",URLseasonNumber)
        sub (/[[:alpha:]]*/,"",URLseasonNumber)
        if (URLseasonNumber != seasonNumber) {
            printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
            seasonNumber = URLseasonNumber
        }
    }
    printf ("%d\t=HYPERLINK(\"%s\";\"%s, %s%02d%s%02d, %s\"\)\t\t\t%s\n", \
        SERIES_NUMBER, episodeURL, seriesTitle, showType, seasonNumber, episodeType, episodeNumber, \
        episodeTitle, episodeDuration) >> EPISODE_INFO_FILE
    showType = ""
    next
}

/-- Viewers Also Watched --/ {
    print (episodeLinesFound == 1 ? "=0" : "=" numEpisodesStr) >> NUM_EPISODES_FILE
    if (description == "")
        printf ("==> No description: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    description = ""
    #
    seriesDuration = sprintf ("%02d:%02d:%02d", seriesHrs, seriesMins, seriesSecs)
    print seriesDuration >> DURATION_FILE
    if (seriesDuration == "00:00:00")
        printf ("==> No duration: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    seriesSecs = 0
    seriesMins = 0
    seriesHrs = 0
    seriesDuration = ""
    #
    SERIES_NUMBER += 1
}

END {
    # Return the total duration of all series
    printf ("%02d:%02d:%02d\n", totalHrs, totalMins, totalSecs)
}

# Create column files with lists of series titles, descriptions, number
# of seasons, and number of episodes, to be pasted into a spreadsheet.
# Note that IN_CANADA affects processing.
#
# Create a separate file with a line for each episode containing
# seriesNumber, episodeURL, seriesTitle, seasonNumber, episodeNumber,
# episodeTitle, & episodeDuration with the same columns as the primary
# spreadsheet so they can be combined into one.
#
# Create a separate file with a line for each series duration
#
# Return the total duration of all series
#
# INVOCATION:
#       curl -s https://acorn.tv/192/ \
#           | awk -v TITLE_FILE=$TITLE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v NUM_EPISODES_FILE=$NUM_EPISODES_FILE \
#           -v EPISODE_CURL_FILE=$EPISODE_CURL_FILE -v IN_CANADA=$IN_CANADA \
#           -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v SERIES_NUMBER=$lastRow \
#           -v DURATION_FILE=$DURATION_FILE -v ERROR_FILE=$ERROR_FILE -f getAcornFrom-seriesPages.awk
#
# INPUT:
#        <title>Acorn TV | 19-2</title>
#  ---
#        19-2
#        <meta itemprop="numberOfEpisodes" content="30" />
#        <meta itemprop="numberOfSeasons" content="3" />
#  ---
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#  ---
#
"The writing is sublime" (New \
#        York Times) in this anything-but-a-procedural cop drama ... as reluctant partners \
#        patrolling the streets of Montreal. Not Available in Canada. CC Available.

#  ---
#
We're sorry, but Acorn TV is not available in this territory.

#  ---
#        <meta itemprop="seasonNumber" content="1" />
#  ---
#
#  ---
#        <meta itemprop="timeRequired" content="T44M06S" />
#  ---
#
Partners

#  ---
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

#
#
#
#  OUTPUT:
#       $EPISODE_INFO_FILE
#

# Extract the series title
/<title>/ {
    sub (/.*<title>Acorn TV \| /,"")
    sub (/<.*/,"")
    gsub (/&/,"\\&")
    # gsub (/'/,"'")
    # gsub (/"/,"\"\"")
    if (match ($0, /^The /)) {
        $0 = substr($0, 5) ", The"
    }
    episodeLinesFound = 0
    numEpisodesStr = ""
    seriesTitle = $0
    print seriesTitle >> TITLE_FILE
    next
}

# Extract the number of episodes in the series
/itemprop="numberOfEpisodes"/ {
    split ($0,fld,"\"")
    episodeLinesFound += 1
    if (episodeLinesFound == 1)
        totalEpisodes = fld[4]
    if (episodeLinesFound != 1)
        numEpisodesStr = numEpisodesStr "+" fld[4]
    next
}

# Extract the number of seasons in the series
/itemprop="numberOfSeasons"/ {
    split ($0,fld,"\"")
    numSeasons = fld[4]
    if (numSeasons == 0)
        printf ("==> No seasons: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    print numSeasons >> NUM_SEASONS_FILE
    next
}

/Acorn TV is not available in this territory/ {
    printf ("==> Not available here: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
}

# Extract the series description
/id="franchise-description"/ {
    # get rid of boilerplate
    split ($0,fld,"[<>]")
    description = fld[3]
    # get rid of unnecessary characters and text
    gsub (/\\/,"",description)
    if (IN_CANADA != "yes") {
        sub (/Series [[:digit:]]* not available in Canada\./,"",description)
        sub (/Not [Aa]vailable in Canada\./,"",description)
        sub (/NOT AVAILABLE IN CANADA\./,"",description)
    }
    sub (/CC Available\. CC Available/,"CC Available",description)
    # fix sloppy input spacing
    sub (/\.CC Available/,". CC Available",description)
    gsub (/ \./,".",description)
    gsub (/  */," ",description)
    sub (/^ */,"",description)
    sub (/ *$/,"",description)
    # fix funky HTML characters
    gsub (/'/,"'",description)
    print description >> DESCRIPTION_FILE
    next
}

# Extract the seasonNumber
# Note that Acorn "season" numbers may not correspond to actual season numbers.
# They simply refer to the number of seasons available on Acorn.
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
    next
}

# Extract the episode URL
/> EPISODE_CURL_FILE
    next
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    #
    seriesSecs += secs
    seriesMins += mins + int(seriesSecs / 60)
    seriesHrs += hrs + int(seriesMins / 60)
    seriesSecs %= 60; seriesMins %= 60
    #
    totalSecs += secs
    totalMins += mins + int(totalSecs / 60)
    totalHrs += hrs + int(totalMins / 60)
    totalSecs %= 60; totalMins %= 60
    #
    episodeDuration = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    if (episodeDuration == "00:00:00")
        printf ("==> No duration: %d\t%s  %s\n", SERIES_NUMBER, seriesTitle, episodeURL)  >> ERROR_FILE
    next
}

# Extract the episode title
/
/ {
    split ($0,fld,"[<>]")
    episodeTitle = fld[3]
    gsub (/&/,"\\&", episodeTitle)
    next
}

# There are a number of variants in the "episodeNumber" string
#
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    # If show is a Prequel, use "P"
    if (episodeURL ~ /\/prequel/)
        showType = "P"
    # If show is a Miniseries, use "M"
    if (episodeURL ~ /\/miniseries/)
        showType = "M"
    # If we don't know what kind of show it is, use "S"
    if (showType == "")
        showType = "S"
    # Default episodeType to "E"
    episodeType = "E"
    # Default bonus episodeType to "X"
    if (episodeURL ~ /bonus\//) {
        episodeType = "X"
    }
    # Default christmasspecial episodeType to "K", don't increment seasonNumber
    if (episodeURL ~ /christmasspecial\//) {
        episodeType = "K"
        seasonNumber -= 1
    }
    #
    split ($0,fld,"[<>]")
    episodeNumber = fld[5]
    # Feature films don't have episodes
    if (episodeURL ~ /\/featurefilm\//)
        next
    # neither should single episode series
    if (totalEpisodes == 1)
        next
    # The season number should match that in the URL
    # Birds of a Feather, Doc Martin, Murdoch, Poirot, Rebus, Vera, and others have problems
    if (episodeURL ~ \
           /\/series[0-9]{1,2}\/|[0-9]{1,2}bonus\/|\/murdoch\/season[0-9]{1,2}/) {
        split (episodeURL, part, "/")
        URLseasonNumber = part[5]
        sub (/bonus/,"",URLseasonNumber)
        # Foyle's War has sets: foyleswar/series9set8bonus/
        sub (/set[0-9]/,"",URLseasonNumber)
        sub (/christmasspecial/,"",URLseasonNumber)
        sub (/[[:alpha:]]*/,"",URLseasonNumber)
        if (URLseasonNumber != seasonNumber) {
            printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
            seasonNumber = URLseasonNumber
        }
    }
    printf ("%d\t=HYPERLINK(\"%s\";\"%s, %s%02d%s%02d, %s\"\)\t\t\t%s\n", \
        SERIES_NUMBER, episodeURL, seriesTitle, showType, seasonNumber, episodeType, episodeNumber, \
        episodeTitle, episodeDuration) >> EPISODE_INFO_FILE
    showType = ""
    next
}

/-- Viewers Also Watched --/ {
    print (episodeLinesFound == 1 ? "=0" : "=" numEpisodesStr) >> NUM_EPISODES_FILE
    if (description == "")
        printf ("==> No description: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    description = ""
    #
    seriesDuration = sprintf ("%02d:%02d:%02d", seriesHrs, seriesMins, seriesSecs)
    print seriesDuration >> DURATION_FILE
    if (seriesDuration == "00:00:00")
        printf ("==> No duration: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    seriesSecs = 0
    seriesMins = 0
    seriesHrs = 0
    seriesDuration = ""
    #
    SERIES_NUMBER += 1
}

END {
    # Return the total duration of all series
    printf ("%02d:%02d:%02d\n", totalHrs, totalMins, totalSecs)
}