#! /bin/sh

# Ensure consistent style by catching common improper constructs.

set -e

diag()
{
  fail=:
  echo "$file:" "$@"
  echo ============================================================
}

rm -f failures

# Get some help from GNU grep.
if (grep --color=auto -n --version)>/dev/null 2>&1; then
  GREP_OPTIONS='--color=auto -n'
  GREP_COLOR='1;31'
  export GREP_OPTIONS
  export GREP_COLOR
fi

tmp=incltest.tmp

for dir in "${INCDIR-..}" "${INCDIR-..}"/../iface; do

  find "$dir" \(    -name "${1-*}.hh" \
                 -o -name "${1-*}.hxx" \
                 -o -name "${1-*}.cc" \) \
	      -a -type f -a -print |
  while read file; do
    if grep 'GNU Bison' "$file" >/dev/null ||
      grep 'generated by flex' "$file" >/dev/null ; then
      continue
    fi

    fail=false

    # Check this before stripping comments and strings.
    grep -i 'accepting cond' $file &&
      diag 'accepting -> acceptance'

    # Strip comments and strings.
    sed 's,[	 ]*//.*,,;s,"[^"]*","",g' < $file > $tmp

    grep '[	 ]$' $tmp &&
      diag 'Trailing whitespace.'

    grep '[	 ]if(' $tmp &&
      diag 'Missing space after "if"'

    grep '[	 ]if (.*).*{' $tmp &&
      diag 'Opening { should be on its own line.'

    grep '[	 ]if (.*).*;' $tmp &&
      diag 'if body should be on another line.'

    grep '[	 ]else.*;' $tmp &&
      diag 'else body should be on another line.'

    grep '[	 ]while(' $tmp &&
      diag 'Missing space after "while"'

    grep '[	 ]while (.*).*{' $tmp &&
      diag 'Opening { should be on its own line.'

    grep '[	 ]while (.*).*[^)];' $tmp &&
      diag 'while body should be on another line.'

    grep '[	 ]for(' $tmp &&
      diag 'Missing space after "for"'

    grep '[	 ]for (.*).*{' $tmp &&
      diag 'Opening { should be on its own line.'

    grep '[	 ]for (.*;.*;.*).*;' $tmp &&
      diag 'for body should be on another line.'

    grep '[	 ]switch(' $tmp &&
      diag 'Missing space after "switch"'

    grep '[	 ]switch (.*).*{' $tmp &&
      diag 'Opening { should be on its own line.'

    grep 'namespace .*{' $tmp &&
      diag 'Opening { should be on its own line.'

    grep 'class .*{' $tmp &&
      diag 'Opening { should be on its own line.'

    grep '( ' $tmp &&
      diag 'No space after opening (.'

    grep ' )' $tmp &&
      diag 'No space before closing ).'

    grep '! ' $tmp &&
      diag 'No space after unary operators (!).'

    grep ",[^ \t\"%']" $tmp &&
      diag 'Space after coma.'

    grep '[^	 ]&&[^	 ]' $tmp &&
      diag 'Space arround binary operators.'

    grep '[^	 ]||[^	 ]' $tmp &&
      diag 'Space arround binary operators.'

    grep '[	 ]default:[^:].*;' $tmp &&
      diag 'Label should be on their own line.'

    grep '[	 ]case.*:[^:].*;' $tmp &&
      diag 'Label should be on their own line.'

    grep '[	 ];' $tmp &&
      diag 'No space before semicolon.'

    grep -v 'for (.*;;)' $tmp | grep ';[^	 ")]' &&
      diag 'Must have space or newline after semicolon.'

    grep '}.*}' $tmp &&
      diag 'No two } on the same line.'

    grep '{.*{' $tmp &&
      diag 'No two { on the same line.'

    grep 'delete[	 ]*[(][^(]*[)];' $tmp &&
      diag 'No useless parentheses after delete.'

    grep 'return[	 ]*[(][^(]*[)];' $tmp &&
      diag 'No useless parentheses after return.'

    grep 'NULL' $tmp &&
      diag 'Use 0 instead of NULL.  NULL is not portable.'

    # std::list::size() can be O(n).  Better use empty() whenever
    # possible, even for other containers.
    egrep '(->|[.])size\(\) [=!]= 0|![a-zA-Z0-9_]*(->|[.])size\(\)|(if |while |assert)\([a-zA-Z0-9_]*(->|[.])size\(\)\)' $tmp &&
      diag 'Prefer empty() to check emptiness.'

    egrep '^[^=]*([+][+]|--);' $tmp &&
      diag 'Take good habits: use ++i instead of i++ when you have the choice.'

    grep '[^a-zA-Z0-9_](\*[a-zA-Z0-9_]*)\.' $tmp &&
      diag 'Use "x->y", not "(*x).y"'

    grep 'std::hash' $tmp &&
      diag 'use Sgi:: for hash and hash_map'

    grep 'Sgi::[^h]' $tmp &&
      diag 'Sgi:: is for hash and hash_map only'

    res=`perl -ne '$/ = undef;
      print "$&\n"
      while /if \((.*)(\s*==\s*0)?\)\s*delete(\[\])?\s+\1;(?!\s+else)/g' $tmp`
    if test -n "$res"; then
       echo "$res"
       diag 'No "if (x)" required before "delete x;".'
    fi

    case $file in
      *.hh | *.hxx)
        if egrep '(cout|cerr|clog)' $tmp >/dev/null; then
          :
	else
          grep '#.*include.*<iostream>' $tmp &&
            diag 'Avoid <iostream> in headers, better use <iosfwd>.'
        fi
        ;;
      *.cc)
        if grep 'namespace$' $tmp >/dev/null; then
	  :
	else
	  # We only check classes, but the rule should apply to functions too
	  grep '^[	 ]*class[	 ]' $tmp &&
	    diag 'Private definitions must be in anonymous namespace.'
	fi
	;;
    esac

    $fail && echo "$file" >>failures

  done || : # Make sure sh does not abort when read exits with false.
done

if test -f failures; then
   echo "The following files contain style errors:"
   cat failures
   rm failures
   exit 1;
fi

exit 0
