#!/bin/bash

# Copyright (c) 2008-2021 the MRtrix3 contributors.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Covered Software is provided under this License on an "as is"
# basis, without warranty of any kind, either expressed, implied, or
# statutory, including, without limitation, warranties that the
# Covered Software is free of defects, merchantable, fit for a
# particular purpose or non-infringing.
# See the Mozilla Public License v. 2.0 for more details.
#
# For more details, see http://www.mrtrix.org/.

LOG=syntax.log
echo -n "Checking syntax and memory alignment for Eigen 3.3 compatibility... "
echo "Checking syntax and memory alignment for Eigen 3.3 compatibility..." > $LOG
echo "" >> $LOG

grep=grep
v=`grep --version`
if [[ "$OSTYPE" == darwin* ]] && [[ $v == *"BSD"* ]]; then
  if ! hash ggrep 2>/dev/null; then
    echo 'Aborting, this script requires gnu-grep which you can install for instance via "brew install grep"'
    exit 1
  else
    grep=ggrep
  fi
fi

retval=0

for f in $(find cmd core src -type f -name '*.h' -o -name '*.cpp' | $grep -v '_moc.cpp'); do

# files to ignore:
  [[ "$f" -ef "src/gui/shview/icons.h" ||
     "$f" -ef "src/gui/shview/icons.cpp" ||
     "$f" -ef "src/gui/mrview/icons.h" ||
     "$f" -ef "src/gui/mrview/icons.cpp" ||
     "$f" -ef "core/file/mgh.h" ||
     "$f" -ef "core/file/json.h" ||
     "$f" -ef "core/file/nifti2.h" ||
     "$f" -ef "core/signal_handler.h" ||
     "$f" -ef "core/signal_handler.cpp" ]] && continue




# process the file to strip comments, macros, etc:
  cat $f | \
# remove C preprocessor macros:
  $grep -v '^#' | \
# remove C++ single-line comments:
  perl -pe 's|//.*$||' | \
# remove all newlines to make file one long line:
  tr '\n' ' ' | \
# remove any duplicate spaces:
  perl -pe 's|\s+| |g' | \
# remove C-style comments:
  perl -pe 's|/\*.*?\*/||g' > .check_syntax2.tmp
# remove quoted strings:
  cat .check_syntax2.tmp | perl -pe 's/(")(\\"|.)*?"//g' > .check_syntax.tmp

# detect classes not declared MEMALIGN or NOMEMALIGN:
  res=$(
    cat .check_syntax.tmp | \
# remove any text within a template declaration (i.e. within <>):
    perl -pe 's|<[^{};<]*?>||g' | \
# and do it multiple times to handle nested declarations:
    perl -pe 's|<[^{};<]*?>||g' | \
    perl -pe 's|<[^{};<]*?>||g' | \
    perl -pe 's|<[^{};<]*?>||g' | \
# match for the parts we're interested in and output just the bits that match:
    $grep -Eo '(enum\s*)?\b(class|struct)\b[^;{]*?{(\s*(MEMALIGN\s*\([^\)]*\)|NOMEMALIGN))?' | \
# remove matches that correspond to an enum class declaration:
    $grep -Ev '\benum\s*class\b' | \
# remove matches that are properly specified:
    $grep -Ev '\b(class|struct)\b[^;{]*?{(\s*(MEMALIGN\s*\([^\)]*\)|NOMEMALIGN))'
  )

# detect any instances of std::vector:
  res="$res"$(
    cat .check_syntax.tmp | \
# match for the parts we're interested in and output just the bits that match:
    $grep -Po '(?<!::)std::vector\b'
  )


# detect any instances of std::make_shared:
  res="$res"$(
    cat .check_syntax.tmp | \
# match for the parts we're interested in and output just the bits that match:
    $grep -Po '(?<!::)std::make_shared\b'
  )

# detect any instances of "using namespace std;":
  res="$res"$(
    cat .check_syntax.tmp | \
# match for the parts we're interested in and output just the bits that match:
    $grep -Po '\busing\s+namespace\s+std\s*;'
  )

# detect any instances of std::abs (outside of the SFINAE call in core/types.h):
  if [[ ! "$f" -ef "core/types.h" ]]; then
    res="$res"$(
      cat .check_syntax.tmp | \
# match for the parts we're interested in and output just the bits that match:
      $grep -Po '(?<!::)std::abs\b'
    )
  fi

# detect any instances of %zu in a print() call (not supported on MSYS2):
  res="$res"$(
    cat .check_syntax2.tmp | \
# match for the parts we're interested in and output just the bits that match:
    $grep -Po '\w*print\w*.*?%\d?zu\b' |
    $grep -Po '\w*print(?!.*?print).*?$'
  )

# delete intermediate files
rm -f .check_syntax.tmp .check_syntax2.tmp

# if anything is left after that, show it:
  if [[ ! -z $res ]]; then
    echo "################################### $f" >> $LOG
    echo "$res" >> $LOG
    retval=1
  fi

done


# set exit code:
if [[ $retval == 0 ]]; then
  echo "OK"
  echo "no issues detected" >> $LOG
else
  echo "FAIL (see syntax.log for details)"

  echo "" >> $LOG
  echo "Please check the following syntax requirements:
  - Add MEMALIGN() or NOMEMALIGN macro to any class declarations identified above;
  - Replace all occurrences of std::vector<> with MR::vector<> (or just vector<>);
  - Avoid use of std::make_shared();
  - Replace all instances of std::abs() with MR::abs() (or just abs());
  - Replace all instances of %zu in print() calls with PRI_SIZET." >> $LOG
  exit 1
fi

