addr_utils.awk		-- fixed awkward comment

addr_utils.awk		-- fixed awkward comment

function AU_parse(options, addr, result, towns, st_types, st_quals, dirs, st_ords,   nf, ary, i, f_st, nf2, ary2, i1, name, street, quals, town, k) {

	result["status"] = "B"
	result["emsg"  ] = ""
	result["name"  ] = ""
	result["street"] = ""
	result["quals" ] = ""
	result["town"  ] = ""
	result["state" ] = ""

	nf = split(addr, ary, ",")
	for(i = 1; i <= nf; i++){
		sub(/^ */, "", ary[i])
		sub(/ *$/, "", ary[i])
	}

	if(options["rply"]){
		# TODO: generalize to all states?
		if(ary[nf] != "United States of America"){
			result["emsg"] = "not.usa"
			return 1
		}else
			nf--
		if(ary[nf] == "CA")
			nf--
		else if(ary[nf] ~ /CA [0-9]{5}$/)
			nf--
		else if(ary[nf] ~ /CA [0-9]{5}-[0-9]{4}$/)
			nf--
		else if(ary[nf] == "California")
			nf--
		else{
			result["emsg"] = "not.CA"
			return 1
		}
	}

	# minimal address has 2 fields: street, town
	if(nf < 2){
		result["emsg"] = "short.addr"
		return 1
	}

	# check that we have a known town
	if(!(ary[nf] in towns)){
		result["emsg"] = "bad.town"
		return 1
	}
	town = options["do_subs"] ? towns[ary[nf]] : ary[nf]

	# find the street.
	# street is 1) 1st elt of ary[1:nf-1] that begins w/number & ends w/st_type or 2) last elt of ary[1:nf-1] that begins w/number
	f_st = 0
	for(i = 1; i < nf; i++){
		if(ary[i] ~ /^[1-9]/){
			nf2 = split(ary[i], ary2, /  */)
			if(ary2[nf2] in st_types){
				f_st = i
				break
			}
			f_st = i
		}
	}
	if(f_st == 0){
		result["emsg"] = "no.street"
		return 1
	}

	# The name is what precedes f_st.  if f_st == 1, set name to user supplied default value
	if(f_st == 1)
		name = options["no_name"]
	else{
		name = ary[1]
		for(i = 2; i < f_st; i++)
			name = name ", " ary[i]
	}

	# Street qualifiers are those fields, if any, that follow f_st and precede nf
	if(nf - f_st > 1){
		quals = ary[f_st+1]
		for(i = f_st+2; i < nf; i++)
			quals = quals ", " ary[i]
	}else
		quals = ""

	# clean up street
	nf2 = split(ary[f_st], ary2, /  */)
	if(nf2 < 2){
		result["emsg"] = "short.street"
		return 1
	}

	# Street Hacks: Probably should just compare ignore case but not ready for that step yet
	# change el Camino X to El Camino X
	# change el Monte X  to El Monte X
	if(options["do_subs"]){
		if(nf2 > 3){
			if(ary2[nf2-2] == "el" && ary2[nf2-1] == "Camino")
				ary2[nf2-2] = "El"
			else if(ary2[nf2-2] == "el" && ary2[nf2-1] == "Monte")
				ary2[nf2-2] =  "El"
		}
	}

	# Street should be num [ dir ] str [ st ]
	street = ary2[1]
	if(options["do_subs"]){
		# This is amusing.  What is South Court? Is is S. Court or South Ct.  No idea so leave such streets in long form
		if(nf2 == 3 && (ary2[2] in dirs) && (ary2[3] in st_types)){
			for(i = 2; i <= nf2; i++)
				street = street " " ary2[i]
		}else{
			if(ary2[2] in dirs){
				street = street " " dirs[ary2[2]]
				i1 = 3
			}else
				i1 = 2
			for(i = i1; i < nf2; i++){
				if(i == i1)
					street = street ((ary2[i] in st_ords) ? (" " st_ords[ary2[i]]) : (" "ary2[i]))
				else
					street = street " " ary2[i]
			}
			street = street ((ary2[nf2] in st_types) ? (" " st_types[ary2[nf2]]) : (" " ary2[nf2]))
		}
	}else{
		for(i = 2; i <= nf2; i++)
			street = street " " ary2[i]
	}

	result["status"] = "G"
	result["name"  ] = name
	result["street"] = street
	result["quals" ] = quals
	result["town"  ] = town
	result["state" ] = "CA"

	return 0
}
function AU_get_addr_data(addr_info, key, data,   k, keys, nk, i) {

	n_data = 0
	for(k in addr_info){
		nk = split(k, keys, SUBSEP)
		if(keys[1] == key){
			data[keys[2]] = addr_info[k]
			n_data++
		}
	}
	return n_data
}
function AU_match(options, cand, ref,   nc_fields, c_fields, nr_fields, r_fields, i, nc_rtab, c_rtab, nr_rtab, r_rtab, nr_nwords, r_nwords, nc_nwords, c_nwords) {

	if(options["verbose"]){
		printf("ref.name    = %s\n", ref["name"]) > "/dev/stderr"
		printf("ref.street  = %s\n", ref["street"]) > "/dev/stderr"
		printf("cand.name   = %s\n", cand["name"]) > "/dev/stderr"
		printf("cand.street = %s\n", cand["street"]) > "/dev/stderr"
	}

	# street numbers and/or ranges, etc is 1st field
	nc_fields = split(cand["street"], c_fields, /  */)
	nr_fields = split(ref["street"], r_fields, /  */)

	# check that non-number parts of streets agree
	if(nc_fields != nr_fields)
		return 0
	for(i = 2; i <= nc_fields; i++){
		if(c_fields[i] != r_fields[i])
			return 0
	}

	nc_rtab = AU_get_rtab(c_fields[1], c_rtab)
	if(nc_rtab == 0)
		return 0

	nr_rtab = AU_get_rtab(r_fields[1], r_rtab)
	if(nr_rtab == 0)
		return 0

	if(!AU_rtabs_intersect(nc_rtab, c_rtab, nr_rtab, r_rtab))
		return 0

	if(cand["town"] != ref["town"])
		return 0
	else if(cand["name"] == ref["name"]){
		return 3
	}else if(cand["name"] == ""){	# this is unannotated address
		return 1
	}else if(ref["name"] == "Residence"){	# many destinations are just street, town
		return 1
	}else{	# check if 1st words match
		nc_nwords = split(cand["name"], c_nwords, /  */)
		nr_nwords = split(ref["name"], r_nwords, /  */)
		return c_nwords[1] == r_nwords[1] ? 2 : 0
	}
}
function AU_get_rtab(street, rtab,   n_rtab, i, nw, work) {

	n_rtab = split(street, rtab, ";")
	for(i = 1; i <= n_rtab; i++){
		nw = split(rtab[i], work, "-")
		if(nw > 1){
			rtab[i, 1] = work[1] + 0	# force number
			rtab[i, 2] = work[2] + 0	# force number
			rtab[i, "rng"] = 1
			if(rtab[i, 1] % 2 != rtab[i, 2] % 2){
				printf("ERROR: bad range: odd/even %d-%d\n", rtab[i, 1], rtab[i, 2]) > "/dev/stderr"
				return 0
			}
		}else{
			rtab[i] = work[1] + 0	# force number
			rtab[i, "rng"] = 0
		}
	}
	return n_rtab
}
function AU_rtabs_intersect(n_rtab1, rtab1, n_rtab2, rtab2,   i, j) {

	# the most common case: number v number
	if(n_rtab1 == 1 && !rtab1[1, "rng"] && n_rtab2 == 1 && !rtab2[1, "rng"])
		return rtab1[1] == rtab2[1]

	for(i = 1; i <= n_rtab1; i++){
		for(j = 1; j <= n_rtab2; j++){
			if(!rtab1[i, "rng"]){
				if(!rtab2[j, "rng"]){	# number v number
					if(rtab1[i] == rtab2[j])
						return 1
				}else{			# number v range
					if(rtab1[i] >= rtab2[j, 1] && rtab1[i] <= rtab2[j, 2])
						return 1
				}
			}else if(!rtab2[j, "rng"]){	# range v number
				if(rtab2[j] >= rtab1[i, 1] && rtab2[j] <= rtab1[i, 2])
					return 1
			}else{				# range v range
				if(!(rtab1[i, 2] < rtab2[j, 1] || rtab1[i, 1] > rtab2[j, 2]))
					return 1
			}
		}
	}
	return 0
}
function AU_rtab_dump(file, n_rtab, rtab,   i) {

	printf("n_rtab = %d {\n", n_rtab) > file
	for(i = 1; i <= n_rtab; i++){
		if(rtab[i, "rng"])
			printf("\trtab[%d, 1-2] = %d-%d\n", i, rtab[i, 1], rtab[i, 2]) > file
		else
			printf("\trtab[%d     ] = %d\n", i, rtab[i]) > file
	}
	printf("}\n") > file
}

function AU_parse(options, addr, result, towns, st_types, st_quals, dirs, st_ords,   nf, ary, i, f_st, nf2, ary2, i1, name, street, quals, town, k) {

	result["status"] = "B"
	result["emsg"  ] = ""
	result["name"  ] = ""
	result["street"] = ""
	result["quals" ] = ""
	result["town"  ] = ""
	result["state" ] = ""

	nf = split(addr, ary, ",")
	for(i = 1; i <= nf; i++){
		sub(/^ */, "", ary[i])
		sub(/ *$/, "", ary[i])
	}

	if(options["rply"]){
		# TODO: generalize to all states?
		if(ary[nf] != "United States of America"){
			result["emsg"] = "not.usa"
			return 1
		}else
			nf--
		if(ary[nf] == "CA")
			nf--
		else if(ary[nf] ~ /CA [0-9]{5}$/)
			nf--
		else if(ary[nf] ~ /CA [0-9]{5}-[0-9]{4}$/)
			nf--
		else if(ary[nf] == "California")
			nf--
		else{
			result["emsg"] = "not.CA"
			return 1
		}
	}

	# minimal address has 2 fields: street, town
	if(nf < 2){
		result["emsg"] = "short.addr"
		return 1
	}

	# check that we have a known town
	if(!(ary[nf] in towns)){
		result["emsg"] = "bad.town"
		return 1
	}
	town = options["do_subs"] ? towns[ary[nf]] : ary[nf]

	# find the street.
	# street is 1) 1st elt of ary[1:nf-1] that begins w/number & ends w/st_type or 2) last elt of ary[1:nf-1] that begins w/number
	f_st = 0
	for(i = 1; i < nf; i++){
		if(ary[i] ~ /^[1-9]/){
			nf2 = split(ary[i], ary2, /  */)
			if(ary2[nf2] in st_types){
				f_st = i
				break
			}
			f_st = i
		}
	}
	if(f_st == 0){
		result["emsg"] = "no.street"
		return 1
	}

	# The name is what precedes f_st.  if f_st == 1, set name to user supplied default value
	if(f_st == 1)
		name = options["no_name"]
	else{
		name = ary[1]
		for(i = 2; i < f_st; i++)
			name = name ", " ary[i]
	}

	# Street qualifiers are those fields, if any, that follow f_st and precede nf
	if(nf - f_st > 1){
		quals = ary[f_st+1]
		for(i = f_st+2; i < nf; i++)
			quals = quals ", " ary[i]
	}else
		quals = ""

	# clean up street
	nf2 = split(ary[f_st], ary2, /  */)
	if(nf2 < 2){
		result["emsg"] = "short.street"
		return 1
	}

	# Street Hacks: Probably should just compare ignore case but not ready for that step yet
	# change el Camino X to El Camino X
	# change el Monte X  to El Monte X
	if(options["do_subs"]){
		if(nf2 > 3){
			if(ary2[nf2-2] == "el" && ary2[nf2-1] == "Camino")
				ary2[nf2-2] = "El"
			else if(ary2[nf2-2] == "el" && ary2[nf2-1] == "Monte")
				ary2[nf2-2] =  "El"
		}
	}

	# Street should be num [ dir ] str [ st ]
	street = ary2[1]
	if(options["do_subs"]){
		# This is amusing.  What is South Court? Is is S. Court or South Ct.  No idea so, leave such streets in long form
		if(nf2 == 3 && (ary2[2] in dirs) && (ary2[3] in st_types)){
			for(i = 2; i <= nf2; i++)
				street = street " " ary2[i]
		}else{
			if(ary2[2] in dirs){
				street = street " " dirs[ary2[2]]
				i1 = 3
			}else
				i1 = 2
			for(i = i1; i < nf2; i++){
				if(i == i1)
					street = street ((ary2[i] in st_ords) ? (" " st_ords[ary2[i]]) : (" "ary2[i]))
				else
					street = street " " ary2[i]
			}
			street = street ((ary2[nf2] in st_types) ? (" " st_types[ary2[nf2]]) : (" " ary2[nf2]))
		}
	}else{
		for(i = 2; i <= nf2; i++)
			street = street " " ary2[i]
	}

	result["status"] = "G"
	result["name"  ] = name
	result["street"] = street
	result["quals" ] = quals
	result["town"  ] = town
	result["state" ] = "CA"

	return 0
}
function AU_get_addr_data(addr_info, key, data,   k, keys, nk, i) {

	n_data = 0
	for(k in addr_info){
		nk = split(k, keys, SUBSEP)
		if(keys[1] == key){
			data[keys[2]] = addr_info[k]
			n_data++
		}
	}
	return n_data
}
function AU_match(options, cand, ref,   nc_fields, c_fields, nr_fields, r_fields, i, nc_rtab, c_rtab, nr_rtab, r_rtab, nr_nwords, r_nwords, nc_nwords, c_nwords) {

	if(options["verbose"]){
		printf("ref.name    = %s\n", ref["name"]) > "/dev/stderr"
		printf("ref.street  = %s\n", ref["street"]) > "/dev/stderr"
		printf("cand.name   = %s\n", cand["name"]) > "/dev/stderr"
		printf("cand.street = %s\n", cand["street"]) > "/dev/stderr"
	}

	# street numbers and/or ranges, etc is 1st field
	nc_fields = split(cand["street"], c_fields, /  */)
	nr_fields = split(ref["street"], r_fields, /  */)

	# check that non-number parts of streets agree
	if(nc_fields != nr_fields)
		return 0
	for(i = 2; i <= nc_fields; i++){
		if(c_fields[i] != r_fields[i])
			return 0
	}

	nc_rtab = AU_get_rtab(c_fields[1], c_rtab)
	if(nc_rtab == 0)
		return 0

	nr_rtab = AU_get_rtab(r_fields[1], r_rtab)
	if(nr_rtab == 0)
		return 0

	if(!AU_rtabs_intersect(nc_rtab, c_rtab, nr_rtab, r_rtab))
		return 0

	if(cand["town"] != ref["town"])
		return 0
	else if(cand["name"] == ref["name"]){
		return 3
	}else if(cand["name"] == ""){	# this is unannotated address
		return 1
	}else if(ref["name"] == "Residence"){	# many destinations are just street, town
		return 1
	}else{	# check if 1st words match
		nc_nwords = split(cand["name"], c_nwords, /  */)
		nr_nwords = split(ref["name"], r_nwords, /  */)
		return c_nwords[1] == r_nwords[1] ? 2 : 0
	}
}
function AU_get_rtab(street, rtab,   n_rtab, i, nw, work) {

	n_rtab = split(street, rtab, ";")
	for(i = 1; i <= n_rtab; i++){
		nw = split(rtab[i], work, "-")
		if(nw > 1){
			rtab[i, 1] = work[1] + 0	# force number
			rtab[i, 2] = work[2] + 0	# force number
			rtab[i, "rng"] = 1
			if(rtab[i, 1] % 2 != rtab[i, 2] % 2){
				printf("ERROR: bad range: odd/even %d-%d\n", rtab[i, 1], rtab[i, 2]) > "/dev/stderr"
				return 0
			}
		}else{
			rtab[i] = work[1] + 0	# force number
			rtab[i, "rng"] = 0
		}
	}
	return n_rtab
}
function AU_rtabs_intersect(n_rtab1, rtab1, n_rtab2, rtab2,   i, j) {

	# the most common case: number v number
	if(n_rtab1 == 1 && !rtab1[1, "rng"] && n_rtab2 == 1 && !rtab2[1, "rng"])
		return rtab1[1] == rtab2[1]

	for(i = 1; i <= n_rtab1; i++){
		for(j = 1; j <= n_rtab2; j++){
			if(!rtab1[i, "rng"]){
				if(!rtab2[j, "rng"]){	# number v number
					if(rtab1[i] == rtab2[j])
						return 1
				}else{			# number v range
					if(rtab1[i] >= rtab2[j, 1] && rtab1[i] <= rtab2[j, 2])
						return 1
				}
			}else if(!rtab2[j, "rng"]){	# range v number
				if(rtab2[j] >= rtab1[i, 1] && rtab2[j] <= rtab1[i, 2])
					return 1
			}else{				# range v range
				if(!(rtab1[i, 2] < rtab2[j, 1] || rtab1[i, 1] > rtab2[j, 2]))
					return 1
			}
		}
	}
	return 0
}
function AU_rtab_dump(file, n_rtab, rtab,   i) {

	printf("n_rtab = %d {\n", n_rtab) > file
	for(i = 1; i <= n_rtab; i++){
		if(rtab[i, "rng"])
			printf("\trtab[%d, 1-2] = %d-%d\n", i, rtab[i, 1], rtab[i, 2]) > file
		else
			printf("\trtab[%d     ] = %d\n", i, rtab[i]) > file
	}
	printf("}\n") > file
}


MONDRIAN: Strict checks that Pentaho copyright is up to date; looks for spaces
    after 'static' etc.

[git-p4: depot-paths = "//open/mondrian/": change = 15005]

#!/bin/gawk
# Checks that a file is valid.

function error(fname, linum, msg) {
    printf "%s:%d: %s\n", fname, linum, msg;
    if (0) print; # for debug
}
function _matchFile(fname) {
    return fname ~ "/mondrian/" \
       || fname ~ "/org/olap4j/" \
       || fname ~ "/aspen/" \
       || fname ~ "/farrago/" \
       || fname ~ "/fennel/" \
       || fname ~ "/extensions/" \
       || fname ~ "/com/sqlstream/" \
       || strict > 0;
}
function _isCpp(fname) {
    return fname ~ /\.(cpp|h)$/;
}
function _isJava(fname) {
    return fname ~ /\.(java|jj)$/;
}
function _isMondrian(fname) {
    return fname ~ /mondrian/;
}
function push(val) {
    switchStack[switchStackLen++] = val;
}
function pop() {
    --switchStackLen
    val = switchStack[switchStackLen];
    delete switchStack[switchStackLen];
    return val;
}
function afterFile() {
    # Compute basename. If fname="/foo/bar/baz.txt" then basename="baz.txt".
    basename = fname;
    gsub(".*/", "", basename);
    gsub(lf, "", lastNonEmptyLine);
    terminator = "// End " basename;
    if (matchFile && (lastNonEmptyLine != terminator)) {
        error(fname, FNR, sprintf("Last line should be %c%s%c", 39, terminator, 39));
    }
}
# Returns whether there are unmatched open parentheses.
# unmatchedOpenParens("if ()") returns false.
# unmatchedOpenParens("if (") returns true.
# unmatchedOpenParens("if (foo) bar(") returns false
function unmatchedOpenParens(s) {
    i = index(s, "(");
    if (i == 0) {
    if (0)         print FNR, "unmatchedOpenParens=0";
        return 0;
    }
    openCount = 1;
    while (++i <= length(s)) {
        c = substr(s, i, 1);
        if (c == "(") {
            ++openCount;
        }
        if (c == ")") {
            if (--openCount == 0) {
    if (0)         print FNR, "unmatchedOpenParens=0 (b)";
                return 0;
            }
        }
    }
    if (0) print FNR, "unmatchedOpenParens=1";
    return 1;
}

function countLeadingSpaces(str) {
    i = 0;
    while (i < length(str) && substr(str, i + 1, 1) == " ") {
        ++i;
    }
    return i;
}

function startsWith(s, p) {
    return length(s) > length(p) \
        && substr(s, 1, length(p)) == p;
}

BEGIN {
    # pre-compute regexp for quotes, linefeed
    apos = sprintf("%c", 39);
    quot = sprintf("%c", 34);
    lf = sprintf("%c", 13);
    pattern = apos "(\\" apos "|[^" apos "])" apos;
    if (0) printf "maxLineLength=%s strict=%s\n", maxLineLength, strict;
}
FNR == 1 {
    if (fname) {
        afterFile();
    }
    fname = FILENAME;
    matchFile = _matchFile(fname);
    isCpp = _isCpp(fname);
    isJava = _isJava(fname);
    mondrian = _isMondrian(fname);
    prevImport = "";
    prevImportGroup = "";
}
{
    if (previousLineEndedInCloseBrace > 0) {
        --previousLineEndedInCloseBrace;
    }
    if (previousLineEndedInOpenBrace > 0) {
        --previousLineEndedInOpenBrace;
    }
    if (previousLineWasEmpty > 0) {
        --previousLineWasEmpty;
    }
    s = $0;
    # remove DOS linefeeds
    gsub(lf, "", s);
    # replace strings
    gsub(/"(\\"|[^"\\]|\\[^"])*"/, "string", s);
    # replace single-quoted strings
    gsub(pattern, "string", s);
    # replace {: and :} in .cup files
    if (fname ~ /\.cup$/) {
        gsub(/{:/, "{", s);
        gsub(/:}/, "}", s);
        gsub(/:/, " : ", s);
    }
    if (inComment && $0 ~ /\*\//) {
        # end of multiline comment "*/"
        inComment = 0;
        gsub(/^.*\*\//, "/* comment */", s);
    } else if (inComment) {
        s = "/* comment */";
    } else if ($0 ~ /\/\*/ && $0 !~ /\/\*.*\*\//) {
        # beginning of multiline comment "/*"
        inComment = 1;
        gsub(/\/\*.*$/, "/* comment */", s);
    } else {
        # mask out /* */ comments
        gsub(/\/\*.*\*\//, "/* comment */", s);
    }
    if (mondrian && s ~ /\/\/\$NON-NLS/) {
        error(fname, FNR, "NON-NLS not allowed");
    }
    # mask out // comments
    gsub(/\/\/.*$/, "// comment", s);
    # line starts with string or plus?
    if (s ~ /^ *string/ \
        && s !~ /)/)
    {
        stringCol = index(s, "string");
    } else if (s ~ /^ *[+] string/) {
        if (stringCol != 0 && index(s, "+") != stringCol) {
            error(fname, FNR, "String '+' must be aligned with string on line above");
        }
    } else if (s ~ /comment/) {
        # in comment; string target carries forward
    } else {
        stringCol = 0;
    }

    # Is the line indented as expected?
    if (nextIndent > 0) {
        indent = countLeadingSpaces(s);
        if (indent != nextIndent) {
            error(fname, FNR, "Incorrect indent for first line of arg list");
        }
    }
    nextIndent = -1;
}
/ $/ {
    error(fname, FNR, "Line ends in space");
}
/[\t]/ {
    if (matchFile) {
        error(fname, FNR, "Tab character");
    }
}
/[\r]/ {
    if (matchFile) {
        error(fname, FNR, "Carriage return character (file is in DOS format?)");
    }
}
/./ {
    lastNonEmptyLine = $0;
}
{
    # Rules beyond this point only apply to Java and C++.
    if (!isCpp && !isJava) {
        next;
    }
}

/^package / {
    thisPackage = $2;
}
/^package / && previousLineWasEmpty {
    error(fname, FNR, "'package' declaration must not occur after empty line");
}
/^import / {
    if (previousLineWasEmpty) {
        prevImport = "";
    } else {
        if (!prevImport) {
            error(fname, FNR, "Expected blank line before first import");
        }
    }
    thisImport = $2;
    gsub(/;/, "", thisImport);
    gsub(/\*/, "", thisImport);
    if (thisPackage ~ /^mondrian.*/ && thisImport ~ /^mondrian.*/ \
        || thisPackage ~ /^org.olap4j.*/ && thisImport ~ /^org.olap4j.*/)
    {
        importGroup = "a";
    } else if (thisImport ~ /^static/) {
        importGroup = "z";
    } else if (thisImport ~ /^java.*/) {
        importGroup = "y";
    } else if (thisImport ~ /^junit.*/) {
        importGroup = "b";
    } else if (thisImport ~ /^mondrian.*/) {
        importGroup = "bb";
    } else if (thisImport ~ /^org.apache.*/) {
        importGroup = "c";
    } else if (thisImport ~ /^org.eigenbase.*/) {
        importGroup = "d";
    } else if (thisImport ~ /^org.olap4j.*/) {
        importGroup = "e";
    } else {
        importGroup = "f";
    }
    if (importGroup != prevImportGroup \
        && prevImportGroup)
    {
        if (!previousLineWasEmpty) {
            error(fname, FNR, "Expected blank line between import groups");
        } else if (prevImportGroup > importGroup) {
            error(fname, FNR, "Import group out of sequence (should precede " prevImportGroup ")");
        }
    } else if (prevImport \
        && prevImport > thisImport \
        && !startsWith(prevImport, thisImport) \
        && !startsWith(thisImport, prevImport))
    {
        error(fname, FNR, "Import out of sorted order");
    }
    prevImport = thisImport;
    prevImportGroup = importGroup;
}
/^\/\/ Copyright .* Pentaho/ && strict > 1 {
    # We assume that '--strict' is only invoked on files currently being
    # edited. Therefore we would expect the copyright to be current.
    if ($0 !~ /-2012/) {
        error(fname, FNR, "copyright is not current");
    }
}
/(static|public|private|protected|final|abstract)/ && !/import/ && strict > 1 {
    # Order of qualifiers: "public/private/protected static final abstract class ..."
    s2 = s;
    gsub(/\(.*$/, "", s2);
    if (s2 ~ /abstract .*final /) {
        error(fname, FNR, "'final' must come before 'abstract'");
    }
    if (s2 ~ /final .*static /) {
        error(fname, FNR, "'static' must come before 'final'");
    }
    if (s2 ~ /abstract .*static /) {
        error(fname, FNR, "'static' must come before 'abstract'");
    }
    if (s2 ~ /static .*(public|protected|private) /) {
        error(fname, FNR, "'public/private/protected' must come before 'static'");
    }
    if (s2 ~ /final .*(public|protected|private) /) {
        error(fname, FNR, "'public/private/protected' must come before 'final'");
    }
    if (s2 ~ /abstract .*(public|protected|private) /) {
        error(fname, FNR, "'public/private/protected' must come before 'abstract'");
    }
}
/^$/ {
    if (matchFile && previousLineEndedInOpenBrace) {
        error(fname, FNR, "Empty line following open brace");
    }
}
/^ +}( catch| finally| while|[;,)])/ ||
/^ +}$/ {
    if (matchFile && previousLineWasEmpty) {
        error(fname, FNR - 1, "Empty line before close brace");
    }
}
s ~ /\<if\>.*;$/ {
    if (!matchFile) {}
    else {
        error(fname, FNR, "if followed by statement on same line");
    }
}
s ~ /\<(if) *\(/ {
    if (!matchFile) {
    } else if (s !~ /\<(if) /) {
        error(fname, FNR, "if must be followed by space");
    } else if (s ~ / else if /) {
    } else if (s ~ /^#if /) {
    } else if (s !~ /^(    )*(if)/) {
        error(fname, FNR, "if must be correctly indented");
    }
}
s ~ /\<(while) *\(/ {
    if (!matchFile) {
    } else if (s !~ /\<(while) /) {
        error(fname, FNR, "while must be followed by space");
    } else if (s ~ /} while /) {
    } else if (s !~ /^(    )+(while)/) {
        error(fname, FNR, "while must be correctly indented");
    }
}
s ~ /\<(for|switch|synchronized|} catch) *\(/ {
    if (!matchFile) {}
    else if (s !~ /^(    )*(for|switch|synchronized|} catch)/) {
        error(fname, FNR, "for/switch/synchronized/catch must be correctly indented");
    } else if (s !~ /\<(for|switch|synchronized|} catch) /) {
        error(fname, FNR, "for/switch/synchronized/catch must be followed by space");
    }
}
s ~ /\<(if|while|for|switch|catch)\>/ {
    # Check single-line if statements, such as
    #   if (condition) return;
    # We recognize such statements because there are equal numbers of open and
    # close parentheses.
    opens = s;
    gsub(/[^(]/, "", opens);
    closes = s;
    gsub(/[^)]/, "", closes);
    if (!matchFile) {
    } else if (s ~ /{( *\/\/ comment)?$/) {
        # lines which end with { and optional comment are ok
    } else if (s ~ /{.*\\$/ && isCpp) {
        # lines which end with backslash are ok in c++ macros
    } else if (s ~ /} while/) {
        # lines like "} while (foo);" are ok
    } else if (s ~ /^#/) {
        # lines like "#if 0" are ok
    } else if (s ~ /if \(true|false\)/) {
        # allow "if (true)" and "if (false)" because they are
        # used for commenting
    } else if (!unmatchedOpenParens(s)  \
               && length($0) != 79      \
               && length($0) != 80)
    {
        error(fname, FNR, "single-line if/while/for/switch/catch must end in {");
    }
}
s ~ /[[:alnum:]]\(/ &&
s !~ /\<(if|while|for|switch|assert)\>/ {
    ss = s;
    while (match(ss, /[[:alnum:]]\(/)) {
        ss = substr(ss, RSTART + RLENGTH - 1);
        parens = ss;
        gsub(/[^()]/, "", parens);
        while (substr(parens, 1, 2) == "()") {
            parens = substr(parens, 3);
        }
        opens = parens;
        gsub(/[^(]/, "", opens);
        closes = parens;
        gsub(/[^)]/, "", closes);
        if (length(opens) > length(closes)) {
            if (ss ~ /,$/) {
                bras = ss;
                gsub(/[^<]/, "", bras);
                kets = ss;
                gsub(/->/, "", kets);
                gsub(/[^>]/, "", kets);
                if (length(bras) > length(kets)) {
                    # Ignore case like 'for (Map.Entry<Foo,{nl} Bar> entry : ...'
                } else if (s ~ / for /) {
                    # Ignore case like 'for (int i = 1,{nl} j = 2; i < j; ...'
                } else {
                    error(                                              \
                        fname, FNR,                                     \
                        "multi-line parameter list should start with newline");
                    break;
                }
            } else if (s ~ /[;(]( *\\)?$/) {
                # If open paren is at end of line (with optional backslash
                # for macros), we're fine.
            } else if (s ~ /@.*\({/) {
                # Ignore Java annotations.
            } else {
                error(                                                  \
                    fname, FNR,                                         \
                    "Open parenthesis should be at end of line (function call spans several lines)");
                break;
            }
        }
        ss = substr(ss, 2); # remove initial "("
    }
}
s ~ /\<switch\>/ {
    push(switchCol);
    switchCol = index($0, "switch");
}
s ~ /{/ {
    braceCol = index($0, "{");
    if (braceCol == switchCol) {
        push(switchCol);
    }
}
s ~ /}/ {
    braceCol = index($0, "}");
    if (braceCol == switchCol) {
        switchCol = pop();
    }
}
s ~ /\<(case|default)\>/ {
    caseDefaultCol = match($0, /case|default/);
    if (!matchFile) {}
    else if (caseDefaultCol != switchCol) {
        error(fname, FNR, "case/default must be aligned with switch");
    }
}
s ~ /\<assert\>/ {
    if (!matchFile) {}
    else if (isCpp) {} # rule only applies to java
    else if (s !~ /^(    )+(assert)/) {
        error(fname, FNR, "assert must be correctly indented");
    } else if (s !~ /\<assert /) {
        error(fname, FNR, "assert must be followed by space");
    }
}
s ~ /\<return\>/ {
    if (!matchFile) {}
    else if (isCpp && s ~ /^#/) {
        # ignore macros
    } else if (s !~ /^(    )+(return)/) {
        error(fname, FNR, "return must be correctly indented");
    } else if (s !~ /\<return[ ;]/ && s !~ /\<return$/) {
        error(fname, FNR, "return must be followed by space or ;");
    }
}
s ~ /\<throw\>/ {
    if (!matchFile) {}
    else if (isCpp) {
        # cannot yet handle C++ cases like 'void foo() throw(int)'
    } else if (s !~ /^(    )+(throw)/) {
        error(fname, FNR, "throw must be correctly indented");
    } else if (s !~ /\<throw / && s !~ /\<throw$/) {
        error(fname, FNR, "throw must be followed by space");
    }
}
s ~ /\<else\>/ {
    if (!matchFile) {}
    else if (isCpp && s ~ /^# *else$/) {} # ignore "#else"
    else if (s !~ /^(    )+} else (if |{$|{ *\/\/|{ *\/\*)/) {
        error(fname, FNR, "else must be preceded by } and followed by { or if and correctly indented");
    }
}
s ~ /\<do\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )*do {/) {
        error(fname, FNR, "do must be followed by space {, and correctly indented");
    }
}
s ~ /\<try\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+try {/) {
        error(fname, FNR, "try must be followed by space {, and correctly indented");
    }
}
s ~ /\<catch\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+} catch /) {
        error(fname, FNR, "catch must be preceded by }, followed by space, and correctly indented");
    }
}
s ~ /\<finally\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+} finally {/) {
        error(fname, FNR, "finally must be preceded by }, followed by space {, and correctly indented");
    }
}
s ~ /\($/ {
    nextIndent = countLeadingSpaces(s) + 4;
    if (s ~ / (if|while) .*\(.*\(/) {
        nextIndent += 4;
    }
}
match(s, /([]A-Za-z0-9()])(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:) *[A-Za-z0-9(]/, a) {
    # < and > are not handled here - they have special treatment below
    if (!matchFile) {}
#    else if (s ~ /<.*>/) {} # ignore templates
    else if (a[2] == "-" && s ~ /\(-/) {} # ignore case "foo(-1)"
    else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5
    else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5
    else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:"
    else if (isCpp && s ~ /[^ ][*&]/) {} # ignore e.g. "Foo* p;" in c++ - debatable
    else if (isCpp && s ~ /\<operator.*\(/) {} # ignore e.g. "operator++()" in c++
    else if (isCpp && a[2] == "/" && s ~ /#include/) {} # ignore e.g. "#include <x/y.hpp>" in c++
    else {
        error(fname, FNR, "operator '" a[2] "' must be preceded by space");
    }
}
match(s, /([]A-Za-z0-9() ] *)(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:|,)[A-Za-z0-9(]/, a) {
    if (!matchFile) {}
#    else if (s ~ /<.*>/) {} # ignore templates
    else if (a[2] == "-" && s ~ /(\(|return |case |= )-/) {} # ignore prefix -
    else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:"
    else if (s ~ /, *-/) {} # ignore case "foo(x, -1)"
    else if (s ~ /-[^ ]/ && s ~ /[^A-Za-z0-9] -/) {} # ignore case "x + -1" but not "x -1" or "3 -1"
    else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5
    else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5
    else if (a[2] == "*" && isCpp && s ~ /\*[^ ]/) {} # ignore e.g. "Foo *p;" in c++
    else if (a[2] == "&" && isCpp && s ~ /&[^ ]/) {} # ignore case "foo(&x)" in c++
    else if (isCpp && s ~ /\<operator[^ ]+\(/) {} # ignore e.g. "operator++()" in c++
    else if (isCpp && a[2] == "/" && s ~ /#include/) {} # ignore e.g. "#include <x/y.hpp>" in c++
    else if (strict < 2 && fname ~ /(fennel)/ && a[1] = ",") {} # not enabled yet
    else {
        error(fname, FNR, "operator '" a[2] "' must be followed by space");
    }
}
match(s, /( )(,)/, a) {
    # (, < and > are not handled here - they have special treatment below
    if (!matchFile) {}
    else {
        error(fname, FNR, "operator '" a[2] "' must not be preceded by space");
    }
}
match(s, / (+|-|\*|\/|==|>=|<=|!=|<<|<<<|>>|&|&&|\|\||\?|:)$/, a) || \
match(s, /(\.|->)$/, a) {
    if (strict < 2 && fname ~ /(aspen)/ && a[1] != ":") {} # not enabled yet
    else if (strict < 2 && fname ~ /(fennel|farrago|aspen)/ && a[1] = "+") {} # not enabled yet
    else if (a[1] == ":" && s ~ /(case.*|default):$/) {
        # ignore e.g. "case 5:"
    } else if ((a[1] == "*" || a[1] == "&") && isCpp && s ~ /^[[:alnum:]:_ ]* [*&]$/) {
        # ignore e.g. "const int *\nClass::Subclass2::method(int x)"
    } else {
        error(fname, FNR, "operator '" a[1] "' must not be at end of line");
    }
}
match(s, /^ *(=) /, a) {
    error(fname, FNR, "operator '" a[1] "' must not be at start of line");
}
match(s, /([[:alnum:]~]+)( )([(])/, a) {
    # (, < and > are not handled here - they have special treatment below
    if (!matchFile) {}
    else if (isJava && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|synchronized|assert)\>/) {}
    else if (isCpp && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|operator|void|PBuffer)\>/) {}
    else if (isCpp && s ~ /^#define /) {}
    else {
        error(fname, FNR, "there must be no space before '" a[3] "' in fun call or fun decl");
    }
}
s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*</ {
    # E.g. "p<" but not "Map<"
    if (!matchFile) {}
    else if (isCpp) {} # in C++ 'xyz<5>' could be a template
    else {
        error(fname, FNR, "operator '<' must be preceded by space");
    }
}
s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*>/ {
    # E.g. "g>" but not "String>" as in "List<String>"
    if (!matchFile) {}
    else if (isCpp) {} # in C++ 'xyz<int>' could be a template
    else {
        error(fname, FNR, "operator '>' must be preceded by space");
    }
}
match(s, /<([[:digit:][:lower:]][[:alnum:].]*)\>/, a) {
    if (!matchFile) {}
    else if (isCpp) {
        # in C++, template and include generate too many false positives
    } else if (isJava && a[1] ~ /(int|char|long|boolean|byte|double|float)/) {
        # Allow e.g. 'List<int[]>'
    } else if (isJava && a[1] ~ /^[[:lower:]]+\./) {
        # Allow e.g. 'List<java.lang.String>'
    } else {
        error(fname, FNR, "operator '<' must be followed by space");
    }
}
match(s, /^(.*[^-])>([[:digit:][:lower:]][[:alnum:]]*)\>/, a) {
    if (!matchFile) {}
    else if (isJava && a[1] ~ /.*\.<.*/) {
        # Ignore 'Collections.<Type>member'
    } else {
        error(fname, FNR, "operator '>' must be followed by space");
    }
}
s ~ /[[(] / {
    if (!matchFile) {}
    else if (s ~ /[[(] +\\$/) {} # ignore '#define foo(   \'
    else {
        error(fname, FNR, "( or [ must not be followed by space");
    }
}
s ~ / [])]/ {
    if (!matchFile) {}
    else if (s ~ /^ *\)/ && previousLineEndedInCloseBrace) {} # ignore "bar(new Foo() { } );"
    else {
        error(fname, FNR, ") or ] must not be followed by space");
    }
}
s ~ /}/ {
    if (!matchFile) {}
    else if (s !~ /}( |;|,|$|\))/) {
        error(fname, FNR, "} must be followed by space");
    } else if (s !~ /(    )*}/) {
        error(fname, FNR, "} must be at start of line and correctly indented");
    }
}
s ~ /{/ {
    if (!matchFile) {}
    else if (s ~ /(\]\)?|=) *{/) {} # ignore e.g. "(int[]) {1, 2}" or "int[] x = {1, 2}"
    else if (s ~ /\({/) {} # ignore e.g. @SuppressWarnings({"unchecked"})
    else if (s ~ /{ *(\/\/|\/\*)/) {} # ignore e.g. "do { // a comment"
    else if (s ~ / \{\}$/) {} # ignore e.g. "Constructor() {}"
    else if (s ~ / },$/) {} # ignore e.g. "{ yada },"
    else if (s ~ / };$/) {} # ignore e.g. "{ yada };"
    else if (s ~ / \{\};$/) {} # ignore e.g. "template <> class Foo<int> {};"
    else if (s ~ / },? *\/\/.*$/) {} # ignore e.g. "{ yada }, // comment"
    else if (s ~ /\\$/) {} # ignore multiline macros
    else if (s ~ /{}/) { # e.g. "Constructor(){}"
        error(fname, FNR, "{} must be preceded by space and at end of line");
    } else if (isCpp && s ~ /{ *\\$/) {
        # ignore - "{" can be followed by "\" in c macro
    } else if (s !~ /{$/) {
        error(fname, FNR, "{ must be at end of line");
    } else if (s !~ /(^| ){/) {
        error(fname, FNR, "{ must be preceded by space or at start of line");
    } else {
        opens = s;
        gsub(/[^(]/, "", opens);
        closes = s;
        gsub(/[^)]/, "", closes);
        if (0 && strict < 2 && fname ~ /aspen/) {} # not enabled
        else if (length(closes) > length(opens)) {
            error(fname, FNR, "Open brace should be on new line (function call/decl spans several lines)");
        }
    }
}
s ~ /(^| )(class|interface|enum) / ||
s ~ /(^| )namespace / && isCpp {
    if (isCpp && s ~ /;$/) {} # ignore type declaration
    else {
        classDeclStartLine = FNR;
        t = s;
        gsub(/.*(class|interface|enum|namespace) /, "", t);
        gsub(/ .*$/, "", t);
        if (s ~ /template/) {
            # ignore case "template <class INSTCLASS> static void foo()"
            classDeclStartLine = 0;
        } else if (t ~ /[[:upper:]][[:upper:]][[:upper:]][[:upper:]]/     \
            && t !~ /LRU/ \
            && t !~ /WAL/ \
            && t !~ /classUUID/ \
            && t !~ /classSQLException/ \
            && t !~ /BBRC/ \
            && t !~ /_/ \
            && t !~ /EncodedSqlInterval/)
        {
            error(fname, FNR, "Class name " t " has consecutive uppercase letters");
        }
    }
}
s ~ / throws\>/ {
    if (s ~ /\(/) {
        funDeclStartLine = FNR;
    } else {
        funDeclStartLine = FNR - 1;
    }
}
length($0) > maxLineLength                      \
&& $0 !~ /@(throws|see|link)/                   \
&& $0 !~ /\$Id$0 !~ /^import /                             \
&& $0 !~ /http:/                                \
&& $0 !~ /\/\/ Expect "/                        \
&& s !~ /^ *(\+ |<< )?string\)?[;,]?$/ {
    error( \
        fname, \
        FNR, \
        "Line length (" length($0) ") exceeds " maxLineLength " chars");
}
/}$/ {
    previousLineEndedInCloseBrace = 2;
}
/;$/ {
    funDeclStartLine = 0;
}
/{$/ {
    # Ignore open brace if it is part of class or interface declaration.
    if (classDeclStartLine) {
        if (classDeclStartLine < FNR \
            && $0 !~ /^ *{$/)
        {
            error(fname, FNR, "Open brace should be on new line (class decl spans several lines)");
        }
        classDeclStartLine = 0;
    } else {
        previousLineEndedInOpenBrace = 2;
    }
    if (funDeclStartLine) {
        if (funDeclStartLine < FNR \
            && $0 !~ /^ *{$/)
        {
            if (strict < 2 && fname ~ /aspen/) {} # not enabled
            else error(fname, FNR, "Open brace should be on new line (function decl spans several lines)");
        }
        funDeclStartLine = 0;
    }
}
/^$/ {
    previousLineWasEmpty = 2;
}
{
    next;
}
END {
    afterFile();
}

# End checkFile.awk

#!/bin/gawk
# Checks that a file is valid.

function error(fname, linum, msg) {
    printf "%s:%d: %s\n", fname, linum, msg;
    if (0) print; # for debug
}
function _matchFile(fname) {
    return fname ~ "/mondrian/" \
       || fname ~ "/org/olap4j/" \
       || fname ~ "/aspen/" \
       || fname ~ "/farrago/" \
       || fname ~ "/fennel/" \
       || fname ~ "/extensions/" \
       || fname ~ "/com/sqlstream/" \
       || strict > 0;
}
function _isCpp(fname) {
    return fname ~ /\.(cpp|h)$/;
}
function _isJava(fname) {
    return fname ~ /\.(java|jj)$/;
}
function _isMondrian(fname) {
    return fname ~ /mondrian/;
}
function push(val) {
    switchStack[switchStackLen++] = val;
}
function pop() {
    --switchStackLen
    val = switchStack[switchStackLen];
    delete switchStack[switchStackLen];
    return val;
}
function afterFile() {
    # Compute basename. If fname="/foo/bar/baz.txt" then basename="baz.txt".
    basename = fname;
    gsub(".*/", "", basename);
    gsub(lf, "", lastNonEmptyLine);
    terminator = "// End " basename;
    if (matchFile && (lastNonEmptyLine != terminator)) {
        error(fname, FNR, sprintf("Last line should be %c%s%c", 39, terminator, 39));
    }
}
# Returns whether there are unmatched open parentheses.
# unmatchedOpenParens("if ()") returns false.
# unmatchedOpenParens("if (") returns true.
# unmatchedOpenParens("if (foo) bar(") returns false
function unmatchedOpenParens(s) {
    i = index(s, "(");
    if (i == 0) {
    if (0)         print FNR, "unmatchedOpenParens=0";
        return 0;
    }
    openCount = 1;
    while (++i <= length(s)) {
        c = substr(s, i, 1);
        if (c == "(") {
            ++openCount;
        }
        if (c == ")") {
            if (--openCount == 0) {
    if (0)         print FNR, "unmatchedOpenParens=0 (b)";
                return 0;
            }
        }
    }
    if (0) print FNR, "unmatchedOpenParens=1";
    return 1;
}

function countLeadingSpaces(str) {
    i = 0;
    while (i < length(str) && substr(str, i + 1, 1) == " ") {
        ++i;
    }
    return i;
}

function startsWith(s, p) {
    return length(s) > length(p) \
        && substr(s, 1, length(p)) == p;
}

BEGIN {
    # pre-compute regexp for quotes, linefeed
    apos = sprintf("%c", 39);
    quot = sprintf("%c", 34);
    lf = sprintf("%c", 13);
    pattern = apos "(\\" apos "|[^" apos "])" apos;
    if (0) printf "maxLineLength=%s strict=%s\n", maxLineLength, strict;
}
FNR == 1 {
    if (fname) {
        afterFile();
    }
    fname = FILENAME;
    matchFile = _matchFile(fname);
    isCpp = _isCpp(fname);
    isJava = _isJava(fname);
    mondrian = _isMondrian(fname);
    prevImport = "";
    prevImportGroup = "";
}
{
    if (previousLineEndedInCloseBrace > 0) {
        --previousLineEndedInCloseBrace;
    }
    if (previousLineEndedInOpenBrace > 0) {
        --previousLineEndedInOpenBrace;
    }
    if (previousLineWasEmpty > 0) {
        --previousLineWasEmpty;
    }
    s = $0;
    # remove DOS linefeeds
    gsub(lf, "", s);
    # replace strings
    gsub(/"(\\"|[^"\\]|\\[^"])*"/, "string", s);
    # replace single-quoted strings
    gsub(pattern, "string", s);
    # replace {: and :} in .cup files
    if (fname ~ /\.cup$/) {
        gsub(/{:/, "{", s);
        gsub(/:}/, "}", s);
        gsub(/:/, " : ", s);
    }
    if (inComment && $0 ~ /\*\//) {
        # end of multiline comment "*/"
        inComment = 0;
        gsub(/^.*\*\//, "/* comment */", s);
    } else if (inComment) {
        s = "/* comment */";
    } else if ($0 ~ /\/\*/ && $0 !~ /\/\*.*\*\//) {
        # beginning of multiline comment "/*"
        inComment = 1;
        gsub(/\/\*.*$/, "/* comment */", s);
    } else {
        # mask out /* */ comments
        gsub(/\/\*.*\*\//, "/* comment */", s);
    }
    if (mondrian && s ~ /\/\/\$NON-NLS/) {
        error(fname, FNR, "NON-NLS not allowed");
    }
    # mask out // comments
    gsub(/\/\/.*$/, "// comment", s);
    # line starts with string or plus?
    if (s ~ /^ *string/ \
        && s !~ /)/)
    {
        stringCol = index(s, "string");
    } else if (s ~ /^ *[+] string/) {
        if (stringCol != 0 && index(s, "+") != stringCol) {
            error(fname, FNR, "String '+' must be aligned with string on line above");
        }
    } else if (s ~ /comment/) {
        # in comment; string target carries forward
    } else {
        stringCol = 0;
    }

    # Is the line indented as expected?
    if (nextIndent > 0) {
        indent = countLeadingSpaces(s);
        if (indent != nextIndent) {
            error(fname, FNR, "Incorrect indent for first line of arg list");
        }
    }
    nextIndent = -1;
}
/ $/ {
    error(fname, FNR, "Line ends in space");
}
/[\t]/ {
    if (matchFile) {
        error(fname, FNR, "Tab character");
    }
}
/[\r]/ {
    if (matchFile) {
        error(fname, FNR, "Carriage return character (file is in DOS format?)");
    }
}
/./ {
    lastNonEmptyLine = $0;
}
{
    # Rules beyond this point only apply to Java and C++.
    if (!isCpp && !isJava) {
        next;
    }
}

/^package / {
    thisPackage = $2;
}
/^package / && previousLineWasEmpty {
    error(fname, FNR, "'package' declaration must not occur after empty line");
}
/^import / {
    if (previousLineWasEmpty) {
        prevImport = "";
    } else {
        if (!prevImport) {
            error(fname, FNR, "Expected blank line before first import");
        }
    }
    thisImport = $2;
    gsub(/;/, "", thisImport);
    gsub(/\*/, "", thisImport);
    if (thisPackage ~ /^mondrian.*/ && thisImport ~ /^mondrian.*/ \
        || thisPackage ~ /^org.olap4j.*/ && thisImport ~ /^org.olap4j.*/)
    {
        importGroup = "a";
    } else if (thisImport ~ /^static/) {
        importGroup = "z";
    } else if (thisImport ~ /^java.*/) {
        importGroup = "y";
    } else if (thisImport ~ /^junit.*/) {
        importGroup = "b";
    } else if (thisImport ~ /^mondrian.*/) {
        importGroup = "bb";
    } else if (thisImport ~ /^org.apache.*/) {
        importGroup = "c";
    } else if (thisImport ~ /^org.eigenbase.*/) {
        importGroup = "d";
    } else if (thisImport ~ /^org.olap4j.*/) {
        importGroup = "e";
    } else {
        importGroup = "f";
    }
    if (importGroup != prevImportGroup \
        && prevImportGroup)
    {
        if (!previousLineWasEmpty) {
            error(fname, FNR, "Expected blank line between import groups");
        } else if (prevImportGroup > importGroup) {
            error(fname, FNR, "Import group out of sequence (should precede " prevImportGroup ")");
        }
    } else if (prevImport \
        && prevImport > thisImport \
        && !startsWith(prevImport, thisImport) \
        && !startsWith(thisImport, prevImport))
    {
        error(fname, FNR, "Import out of sorted order");
    }
    prevImport = thisImport;
    prevImportGroup = importGroup;
}
/^\/\/ Copyright .* Julian/ && strict > 1 {
    if ($0 !~ /-2012/) {
        error(fname, FNR, "copyright is not current");
    }
}
/(static|public|private|protected|final|abstract)/ && !/import/ && strict > 1 {
    # Order of qualifiers: "public/private/protected static final abstract class ..."
    s2 = s;
    gsub(/\(.*$/, "", s2);
    if (s2 ~ /abstract .*final/) {
        error(fname, FNR, "'final' must come before 'abstract'");
    }
    if (s2 ~ /final .*static/) {
        error(fname, FNR, "'static' must come before 'final'");
    }
    if (s2 ~ /abstract .*static/) {
        error(fname, FNR, "'static' must come before 'abstract'");
    }
    if (s2 ~ /static .*(public|protected|private)/) {
        error(fname, FNR, "'public/private/protected' must come before 'static'");
    }
    if (s2 ~ /final .*(public|protected|private)/) {
        error(fname, FNR, "'public/private/protected' must come before 'final'");
    }
    if (s2 ~ /abstract .*(public|protected|private)/) {
        error(fname, FNR, "'public/private/protected' must come before 'abstract'");
    }
}
/^$/ {
    if (matchFile && previousLineEndedInOpenBrace) {
        error(fname, FNR, "Empty line following open brace");
    }
}
/^ +}( catch| finally| while|[;,)])/ ||
/^ +}$/ {
    if (matchFile && previousLineWasEmpty) {
        error(fname, FNR - 1, "Empty line before close brace");
    }
}
s ~ /\<if\>.*;$/ {
    if (!matchFile) {}
    else {
        error(fname, FNR, "if followed by statement on same line");
    }
}
s ~ /\<(if) *\(/ {
    if (!matchFile) {
    } else if (s !~ /\<(if) /) {
        error(fname, FNR, "if must be followed by space");
    } else if (s ~ / else if /) {
    } else if (s ~ /^#if /) {
    } else if (s !~ /^(    )*(if)/) {
        error(fname, FNR, "if must be correctly indented");
    }
}
s ~ /\<(while) *\(/ {
    if (!matchFile) {
    } else if (s !~ /\<(while) /) {
        error(fname, FNR, "while must be followed by space");
    } else if (s ~ /} while /) {
    } else if (s !~ /^(    )+(while)/) {
        error(fname, FNR, "while must be correctly indented");
    }
}
s ~ /\<(for|switch|synchronized|} catch) *\(/ {
    if (!matchFile) {}
    else if (s !~ /^(    )*(for|switch|synchronized|} catch)/) {
        error(fname, FNR, "for/switch/synchronized/catch must be correctly indented");
    } else if (s !~ /\<(for|switch|synchronized|} catch) /) {
        error(fname, FNR, "for/switch/synchronized/catch must be followed by space");
    }
}
s ~ /\<(if|while|for|switch|catch)\>/ {
    # Check single-line if statements, such as
    #   if (condition) return;
    # We recognize such statements because there are equal numbers of open and
    # close parentheses.
    opens = s;
    gsub(/[^(]/, "", opens);
    closes = s;
    gsub(/[^)]/, "", closes);
    if (!matchFile) {
    } else if (s ~ /{( *\/\/ comment)?$/) {
        # lines which end with { and optional comment are ok
    } else if (s ~ /{.*\\$/ && isCpp) {
        # lines which end with backslash are ok in c++ macros
    } else if (s ~ /} while/) {
        # lines like "} while (foo);" are ok
    } else if (s ~ /^#/) {
        # lines like "#if 0" are ok
    } else if (s ~ /if \(true|false\)/) {
        # allow "if (true)" and "if (false)" because they are
        # used for commenting
    } else if (!unmatchedOpenParens(s)  \
               && length($0) != 79      \
               && length($0) != 80)
    {
        error(fname, FNR, "single-line if/while/for/switch/catch must end in {");
    }
}
s ~ /[[:alnum:]]\(/ &&
s !~ /\<(if|while|for|switch|assert)\>/ {
    ss = s;
    while (match(ss, /[[:alnum:]]\(/)) {
        ss = substr(ss, RSTART + RLENGTH - 1);
        parens = ss;
        gsub(/[^()]/, "", parens);
        while (substr(parens, 1, 2) == "()") {
            parens = substr(parens, 3);
        }
        opens = parens;
        gsub(/[^(]/, "", opens);
        closes = parens;
        gsub(/[^)]/, "", closes);
        if (length(opens) > length(closes)) {
            if (ss ~ /,$/) {
                bras = ss;
                gsub(/[^<]/, "", bras);
                kets = ss;
                gsub(/->/, "", kets);
                gsub(/[^>]/, "", kets);
                if (length(bras) > length(kets)) {
                    # Ignore case like 'for (Map.Entry<Foo,{nl} Bar> entry : ...'
                } else if (s ~ / for /) {
                    # Ignore case like 'for (int i = 1,{nl} j = 2; i < j; ...'
                } else {
                    error(                                              \
                        fname, FNR,                                     \
                        "multi-line parameter list should start with newline");
                    break;
                }
            } else if (s ~ /[;(]( *\\)?$/) {
                # If open paren is at end of line (with optional backslash
                # for macros), we're fine.
            } else if (s ~ /@.*\({/) {
                # Ignore Java annotations.
            } else {
                error(                                                  \
                    fname, FNR,                                         \
                    "Open parenthesis should be at end of line (function call spans several lines)");
                break;
            }
        }
        ss = substr(ss, 2); # remove initial "("
    }
}
s ~ /\<switch\>/ {
    push(switchCol);
    switchCol = index($0, "switch");
}
s ~ /{/ {
    braceCol = index($0, "{");
    if (braceCol == switchCol) {
        push(switchCol);
    }
}
s ~ /}/ {
    braceCol = index($0, "}");
    if (braceCol == switchCol) {
        switchCol = pop();
    }
}
s ~ /\<(case|default)\>/ {
    caseDefaultCol = match($0, /case|default/);
    if (!matchFile) {}
    else if (caseDefaultCol != switchCol) {
        error(fname, FNR, "case/default must be aligned with switch");
    }
}
s ~ /\<assert\>/ {
    if (!matchFile) {}
    else if (isCpp) {} # rule only applies to java
    else if (s !~ /^(    )+(assert)/) {
        error(fname, FNR, "assert must be correctly indented");
    } else if (s !~ /\<assert /) {
        error(fname, FNR, "assert must be followed by space");
    }
}
s ~ /\<return\>/ {
    if (!matchFile) {}
    else if (isCpp && s ~ /^#/) {
        # ignore macros
    } else if (s !~ /^(    )+(return)/) {
        error(fname, FNR, "return must be correctly indented");
    } else if (s !~ /\<return[ ;]/ && s !~ /\<return$/) {
        error(fname, FNR, "return must be followed by space or ;");
    }
}
s ~ /\<throw\>/ {
    if (!matchFile) {}
    else if (isCpp) {
        # cannot yet handle C++ cases like 'void foo() throw(int)'
    } else if (s !~ /^(    )+(throw)/) {
        error(fname, FNR, "throw must be correctly indented");
    } else if (s !~ /\<throw / && s !~ /\<throw$/) {
        error(fname, FNR, "throw must be followed by space");
    }
}
s ~ /\<else\>/ {
    if (!matchFile) {}
    else if (isCpp && s ~ /^# *else$/) {} # ignore "#else"
    else if (s !~ /^(    )+} else (if |{$|{ *\/\/|{ *\/\*)/) {
        error(fname, FNR, "else must be preceded by } and followed by { or if and correctly indented");
    }
}
s ~ /\<do\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )*do {/) {
        error(fname, FNR, "do must be followed by space {, and correctly indented");
    }
}
s ~ /\<try\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+try {/) {
        error(fname, FNR, "try must be followed by space {, and correctly indented");
    }
}
s ~ /\<catch\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+} catch /) {
        error(fname, FNR, "catch must be preceded by }, followed by space, and correctly indented");
    }
}
s ~ /\<finally\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+} finally {/) {
        error(fname, FNR, "finally must be preceded by }, followed by space {, and correctly indented");
    }
}
s ~ /\($/ {
    nextIndent = countLeadingSpaces(s) + 4;
    if (s ~ / (if|while) .*\(.*\(/) {
        nextIndent += 4;
    }
}
match(s, /([]A-Za-z0-9()])(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:) *[A-Za-z0-9(]/, a) {
    # < and > are not handled here - they have special treatment below
    if (!matchFile) {}
#    else if (s ~ /<.*>/) {} # ignore templates
    else if (a[2] == "-" && s ~ /\(-/) {} # ignore case "foo(-1)"
    else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5
    else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5
    else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:"
    else if (isCpp && s ~ /[^ ][*&]/) {} # ignore e.g. "Foo* p;" in c++ - debatable
    else if (isCpp && s ~ /\<operator.*\(/) {} # ignore e.g. "operator++()" in c++
    else if (isCpp && a[2] == "/" && s ~ /#include/) {} # ignore e.g. "#include <x/y.hpp>" in c++
    else {
        error(fname, FNR, "operator '" a[2] "' must be preceded by space");
    }
}
match(s, /([]A-Za-z0-9() ] *)(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:|,)[A-Za-z0-9(]/, a) {
    if (!matchFile) {}
#    else if (s ~ /<.*>/) {} # ignore templates
    else if (a[2] == "-" && s ~ /(\(|return |case |= )-/) {} # ignore prefix -
    else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:"
    else if (s ~ /, *-/) {} # ignore case "foo(x, -1)"
    else if (s ~ /-[^ ]/ && s ~ /[^A-Za-z0-9] -/) {} # ignore case "x + -1" but not "x -1" or "3 -1"
    else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5
    else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5
    else if (a[2] == "*" && isCpp && s ~ /\*[^ ]/) {} # ignore e.g. "Foo *p;" in c++
    else if (a[2] == "&" && isCpp && s ~ /&[^ ]/) {} # ignore case "foo(&x)" in c++
    else if (isCpp && s ~ /\<operator[^ ]+\(/) {} # ignore e.g. "operator++()" in c++
    else if (isCpp && a[2] == "/" && s ~ /#include/) {} # ignore e.g. "#include <x/y.hpp>" in c++
    else if (strict < 2 && fname ~ /(fennel)/ && a[1] = ",") {} # not enabled yet
    else {
        error(fname, FNR, "operator '" a[2] "' must be followed by space");
    }
}
match(s, /( )(,)/, a) {
    # (, < and > are not handled here - they have special treatment below
    if (!matchFile) {}
    else {
        error(fname, FNR, "operator '" a[2] "' must not be preceded by space");
    }
}
match(s, / (+|-|\*|\/|==|>=|<=|!=|<<|<<<|>>|&|&&|\|\||\?|:)$/, a) || \
match(s, /(\.|->)$/, a) {
    if (strict < 2 && fname ~ /(aspen)/ && a[1] != ":") {} # not enabled yet
    else if (strict < 2 && fname ~ /(fennel|farrago|aspen)/ && a[1] = "+") {} # not enabled yet
    else if (a[1] == ":" && s ~ /(case.*|default):$/) {
        # ignore e.g. "case 5:"
    } else if ((a[1] == "*" || a[1] == "&") && isCpp && s ~ /^[[:alnum:]:_ ]* [*&]$/) {
        # ignore e.g. "const int *\nClass::Subclass2::method(int x)"
    } else {
        error(fname, FNR, "operator '" a[1] "' must not be at end of line");
    }
}
match(s, /^ *(=) /, a) {
    error(fname, FNR, "operator '" a[1] "' must not be at start of line");
}
match(s, /([[:alnum:]~]+)( )([(])/, a) {
    # (, < and > are not handled here - they have special treatment below
    if (!matchFile) {}
    else if (isJava && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|synchronized|assert)\>/) {}
    else if (isCpp && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|operator|void|PBuffer)\>/) {}
    else if (isCpp && s ~ /^#define /) {}
    else {
        error(fname, FNR, "there must be no space before '" a[3] "' in fun call or fun decl");
    }
}
s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*</ {
    # E.g. "p<" but not "Map<"
    if (!matchFile) {}
    else if (isCpp) {} # in C++ 'xyz<5>' could be a template
    else {
        error(fname, FNR, "operator '<' must be preceded by space");
    }
}
s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*>/ {
    # E.g. "g>" but not "String>" as in "List<String>"
    if (!matchFile) {}
    else if (isCpp) {} # in C++ 'xyz<int>' could be a template
    else {
        error(fname, FNR, "operator '>' must be preceded by space");
    }
}
match(s, /<([[:digit:][:lower:]][[:alnum:].]*)\>/, a) {
    if (!matchFile) {}
    else if (isCpp) {
        # in C++, template and include generate too many false positives
    } else if (isJava && a[1] ~ /(int|char|long|boolean|byte|double|float)/) {
        # Allow e.g. 'List<int[]>'
    } else if (isJava && a[1] ~ /^[[:lower:]]+\./) {
        # Allow e.g. 'List<java.lang.String>'
    } else {
        error(fname, FNR, "operator '<' must be followed by space");
    }
}
match(s, /^(.*[^-])>([[:digit:][:lower:]][[:alnum:]]*)\>/, a) {
    if (!matchFile) {}
    else if (isJava && a[1] ~ /.*\.<.*/) {
        # Ignore 'Collections.<Type>member'
    } else {
        error(fname, FNR, "operator '>' must be followed by space");
    }
}
s ~ /[[(] / {
    if (!matchFile) {}
    else if (s ~ /[[(] +\\$/) {} # ignore '#define foo(   \'
    else {
        error(fname, FNR, "( or [ must not be followed by space");
    }
}
s ~ / [])]/ {
    if (!matchFile) {}
    else if (s ~ /^ *\)/ && previousLineEndedInCloseBrace) {} # ignore "bar(new Foo() { } );"
    else {
        error(fname, FNR, ") or ] must not be followed by space");
    }
}
s ~ /}/ {
    if (!matchFile) {}
    else if (s !~ /}( |;|,|$|\))/) {
        error(fname, FNR, "} must be followed by space");
    } else if (s !~ /(    )*}/) {
        error(fname, FNR, "} must be at start of line and correctly indented");
    }
}
s ~ /{/ {
    if (!matchFile) {}
    else if (s ~ /(\]\)?|=) *{/) {} # ignore e.g. "(int[]) {1, 2}" or "int[] x = {1, 2}"
    else if (s ~ /\({/) {} # ignore e.g. @SuppressWarnings({"unchecked"})
    else if (s ~ /{ *(\/\/|\/\*)/) {} # ignore e.g. "do { // a comment"
    else if (s ~ / \{\}$/) {} # ignore e.g. "Constructor() {}"
    else if (s ~ / },$/) {} # ignore e.g. "{ yada },"
    else if (s ~ / };$/) {} # ignore e.g. "{ yada };"
    else if (s ~ / \{\};$/) {} # ignore e.g. "template <> class Foo<int> {};"
    else if (s ~ / },? *\/\/.*$/) {} # ignore e.g. "{ yada }, // comment"
    else if (s ~ /\\$/) {} # ignore multiline macros
    else if (s ~ /{}/) { # e.g. "Constructor(){}"
        error(fname, FNR, "{} must be preceded by space and at end of line");
    } else if (isCpp && s ~ /{ *\\$/) {
        # ignore - "{" can be followed by "\" in c macro
    } else if (s !~ /{$/) {
        error(fname, FNR, "{ must be at end of line");
    } else if (s !~ /(^| ){/) {
        error(fname, FNR, "{ must be preceded by space or at start of line");
    } else {
        opens = s;
        gsub(/[^(]/, "", opens);
        closes = s;
        gsub(/[^)]/, "", closes);
        if (0 && strict < 2 && fname ~ /aspen/) {} # not enabled
        else if (length(closes) > length(opens)) {
            error(fname, FNR, "Open brace should be on new line (function call/decl spans several lines)");
        }
    }
}
s ~ /(^| )(class|interface|enum) / ||
s ~ /(^| )namespace / && isCpp {
    if (isCpp && s ~ /;$/) {} # ignore type declaration
    else {
        classDeclStartLine = FNR;
        t = s;
        gsub(/.*(class|interface|enum|namespace) /, "", t);
        gsub(/ .*$/, "", t);
        if (s ~ /template/) {
            # ignore case "template <class INSTCLASS> static void foo()"
            classDeclStartLine = 0;
        } else if (t ~ /[[:upper:]][[:upper:]][[:upper:]][[:upper:]]/     \
            && t !~ /LRU/ \
            && t !~ /WAL/ \
            && t !~ /classUUID/ \
            && t !~ /classSQLException/ \
            && t !~ /BBRC/ \
            && t !~ /_/ \
            && t !~ /EncodedSqlInterval/)
        {
            error(fname, FNR, "Class name " t " has consecutive uppercase letters");
        }
    }
}
s ~ / throws\>/ {
    if (s ~ /\(/) {
        funDeclStartLine = FNR;
    } else {
        funDeclStartLine = FNR - 1;
    }
}
length($0) > maxLineLength                      \
&& $0 !~ /@(throws|see|link)/                   \
&& $0 !~ /\$Id$0 !~ /^import /                             \
&& $0 !~ /http:/                                \
&& $0 !~ /\/\/ Expect "/                        \
&& s !~ /^ *(\+ |<< )?string\)?[;,]?$/ {
    error( \
        fname, \
        FNR, \
        "Line length (" length($0) ") exceeds " maxLineLength " chars");
}
/}$/ {
    previousLineEndedInCloseBrace = 2;
}
/;$/ {
    funDeclStartLine = 0;
}
/{$/ {
    # Ignore open brace if it is part of class or interface declaration.
    if (classDeclStartLine) {
        if (classDeclStartLine < FNR \
            && $0 !~ /^ *{$/)
        {
            error(fname, FNR, "Open brace should be on new line (class decl spans several lines)");
        }
        classDeclStartLine = 0;
    } else {
        previousLineEndedInOpenBrace = 2;
    }
    if (funDeclStartLine) {
        if (funDeclStartLine < FNR \
            && $0 !~ /^ *{$/)
        {
            if (strict < 2 && fname ~ /aspen/) {} # not enabled
            else error(fname, FNR, "Open brace should be on new line (function decl spans several lines)");
        }
        funDeclStartLine = 0;
    }
}
/^$/ {
    previousLineWasEmpty = 2;
}
{
    next;
}
END {
    afterFile();
}

# End checkFile.awk


Dredge out edge drawing common to loops and edges between nodes.

#!/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;";

	# TODO: centralise to my equivalent of automata.mp, and include that

	print "vardef fsmnode(expr node, diam, final, lab) =";
	print "	draw fullcircle scaled diam shifted node withpen pencircle scaled 1bp;"
	print "	if final:"
	print "		draw fullcircle scaled (diam - 8bp) shifted node withpen pencircle scaled 1bp;"
	print "	fi;"
	print "	if lab <> \"\":"
	print "		label(lab, node);"
	print "	fi;"
	print "enddef;";

	print "vardef fsmedge(expr tail, head, e, lab) =";

	print "	drawarrow e withpen pencircle scaled 1bp;";

	print "	if lab <> \"\":"
	print "		pair bl;";
	print "		pair ml; ml := .5[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

	print "		draw point (length b / 2) of b withpen pencircle scaled 4bp withcolor blue;"
#	print "		draw tail -- head withpen pencircle scaled 1bp withcolor green;"
#	print "		draw ml withpen pencircle scaled 5bp withcolor green;"

	print "		bl := point (length b / 2) of b;"

	# 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
	# TODO: round label coordinates to grid? quantize rather

	# labels ought to be below, if they're below the straight line from head-tail, or above otherwise
	# TODO: find what quadrant the line is, and make this .rt/.lft instead of .top/.bot
	print "		if ypart bl < ypart ml:" # TODO: threshold for considering "below"
	print "			label.bot(lab, bl shifted (0, -3bp));"
	print "		else:"
	print "			label.top(lab, bl shifted (0, +3bp));"
	print "		fi;"
	print "	fi;"

	print "enddef;";

	print "vardef fsmloop(expr node, diam, p, q, lab, loops) ="
	print "	path pp; pp = node -- 0.5[p, q];"
	print "	draw p -- q withpen pencircle scaled 0.5bp withcolor green;"
	print "	draw 0.5[p, q] withpen pencircle scaled 0.8bp withcolor green;"
	print "	draw node -- 0.5[p, q] withpen pencircle scaled 0.8bp withcolor green;"

	# extend loop 1.25 * diameter
	# count number of self-loops for this node, and increment extension
	print "	l := length pp;"
	print "	pair m; m := (0.5 + 2 * mlog(loops + 2) / 256) * diam * unitvector(direction l of pp) shifted node;"
	# u3 = u1 projectedalong u2;
	print "	draw node -- m withpen pencircle scaled 0.5bp withcolor green;"

	print "	path b; b := node .. q .. m .. p .. node;"
	print "	fsmedge(node, node, b cutbefore q cutafter p, lab);"

	print "enddef;";

	print "vardef fsmgvedge(expr tail, taildiam, head, headdiam, e, lab, loops) =";
	print "	draw e withpen pencircle scaled 0.25bp withcolor red;";

	# TODO: better explain this
	# TODO: explain reversed for q, else we'd find p again, where head == tail
	print "	path sn; sn := fullcircle scaled headdiam shifted head;" # outline
	print "	path st; st := fullcircle scaled taildiam shifted tail;" # outline
	print "	pair p; p = e intersectionpoint sn;\n";
	print "	pair q; q = (reverse e) intersectionpoint st;\n";

	print "	if head = tail:"
	print "		fsmloop(head, headdiam, p, q, lab, loops);"
	print "	else:"
	print "		path b; b = tail .. q .. p .. head;"
	# TODO: explain this. we permit a 2% lengthening threshold relative to graphviz's b-spline
	# this affects NFA especially
	# XXX: or (arclength b > 6in), but wigglyness would be a better metric
	print "		r := arclength b / arclength e;";
	print "		if (r > 1.02) or (r < 0.93):";
	print "			b := e;"
	print "		fi;"
	print "		fsmedge(tail, head, b cutbefore q cutafter p, lab);"
	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

	# TODO: quantise node positions
	printf "\t%s := (%fin, %fin);\n", name, x, y
	printf "\t%s.loops := 0;\n", name

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

	final = shape == "doublecircle";

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

	if ($2 != "start") {
		printf "\tfsmnode(%s, %s.diam, %s, \"%s\");\n", name, name, final ? "true" : "false", label
	}
}

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

	# 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 = "";
	}

	# TODO: find what time (lx,ly) is along graphviz's edge, e
	# then pass the time and use that for placing our label on our edge
	# use intersectiontimes to find the time of (lx,ly) along e
	# or find by e cutbefore (lx,ly) and use arctime to find the time wrt e

	printf "fsmgvedge(%s, %s.diam, %s, %s.diam, e, \"%s\", %s.loops);", tail, tail, head, head, label, head;

	if (head == tail) {
		print "	%s.loops := %s.loops + 1;", head, head
	}
}

/^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 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;";

	# TODO: centralise to my equivalent of automata.mp, and include that

	print "vardef fsmnode(expr node, diam, final, lab) =";
	print "	draw fullcircle scaled diam shifted node withpen pencircle scaled 1bp;"
	print "	if final:"
	print "		draw fullcircle scaled (diam - 8bp) shifted node withpen pencircle scaled 1bp;"
	print "	fi;"
	print "	if lab <> \"\":"
	print "		label(lab, node);"
	print "	fi;"
	print "enddef;";

	print "vardef fsmloop(expr node, diam, p, q, lab, loops) ="
	print "	path pp; pp = node -- 0.5[p, q];"
	print "	draw p -- q withpen pencircle scaled 0.5bp withcolor green;"
	print "	draw 0.5[p, q] withpen pencircle scaled 0.8bp withcolor green;"
	print "	draw node -- 0.5[p, q] withpen pencircle scaled 0.8bp withcolor green;"

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

	print "	if lab <> \"\":"
	# TODO: top or bottom, depending on green line direction
	print "		label.top(lab, m);"
	print "	fi;"

	print "	path b; b := node .. q .. m .. p .. node;"
	print "	drawarrow b cutbefore q cutafter p withpen pencircle scaled 1bp;";

	print "enddef;";

	print "vardef fsmgvedge(expr tail, taildiam, head, headdiam, e, lab, loops) =";
	print "	draw e withpen pencircle scaled 0.25bp withcolor red;";

	# TODO: better explain this
	# TODO: explain reversed for q, else we'd find p again, where head == tail
	print "	path sn; sn := fullcircle scaled headdiam shifted head;" # outline
	print "	path st; st := fullcircle scaled taildiam shifted tail;" # outline
	print "	pair p; p = e intersectionpoint sn;\n";
	print "	pair q; q = (reverse e) intersectionpoint st;\n";

	print "	if head = tail:"
	print "		fsmloop(head, headdiam, p, q, lab, loops);"
	print "	else:"

	print "		path b; b = tail .. q .. p .. head;"

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

	print "		if g:"
	print "			b := e;"
	print "		fi;"

	print "		if lab <> \"\":"
	print "			pair bl;";
	print "			pair ml; ml := .5[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

	print "			draw point (length b / 2) of b withpen pencircle scaled 4bp withcolor blue;"
#	print "			draw tail -- head withpen pencircle scaled 1bp withcolor green;"
#	print "			draw ml withpen pencircle scaled 5bp withcolor green;"

	print "			bl := point (length b / 2) of b;"

	# 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
	# TODO: round label coordinates to grid? quantize rather

	# labels ought to be below, if they're below the straight line from head-tail, or above otherwise
	# TODO: find what quadrant the line is, and make this .rt/.lft instead of .top/.bot
	print "			if ypart bl < ypart ml:" # TODO: threshold for considering "below"
	print "				label.bot(lab, bl shifted (0, -3bp));"
	print "			else:"
	print "				label.top(lab, bl shifted (0, +3bp));"
	print "			fi;"
	print "		fi;"

	print "	drawarrow b cutbefore q cutafter p withpen pencircle scaled 1bp;";

	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

	# TODO: quantise node positions
	printf "\t%s := (%fin, %fin);\n", name, x, y
	printf "\t%s.loops := 0;\n", name

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

	final = shape == "doublecircle";

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

	if ($2 != "start") {
		printf "\tfsmnode(%s, %s.diam, %s, \"%s\");\n", name, name, final ? "true" : "false", label
	}
}

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

	# 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 = "";
	}

	# TODO: find what time (lx,ly) is along graphviz's edge, e
	# then pass the time and use that for placing our label on our edge

	printf "fsmgvedge(%s, %s.diam, %s, %s.diam, e, \"%s\", %s.loops);", tail, tail, head, head, label, head;

	if (head == tail) {
		print "	%s.loops := %s.loops + 1;", head, head
	}
}

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

END {
	print "end."
}