#!/bin/sh
###########################################################################
#
#  Window Maker window manager
#
#  Copyright (c) 2014 Christophe CURIS
#  Copyright (c) 2014-2015 Window Maker Team
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License along
#  with this program; if not, see <https://www.gnu.org/licenses/>.
#
###########################################################################
#
# generate-mapfile-from-header.sh:
#   from a list of C header files, extract the name of the functions and
#   global variables that are considered public for a library, and generate
#   a 'version script' for ld with that list, so that all other symbols
#   will be considered local.
#
# The goal is that the symbol that are not part of the public API should
# not be visible to the user because it can be a source of problems (from
# name clash to evolutivity issues).
#
###########################################################################
#
# Please note that this script is writen in sh+awk on purpose: this script
# is gonna be run on the machine of the person who is trying to compile
# WindowMaker, and as such we cannot be sure to find any scripting language
# in a known version and that works (python/ruby/tcl/perl/php/you-name-it).
#
# So for portability, we stick to the same sh+awk constraint as Autotools
# to limit the problem, see for example:
#   http://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Portable-Shell.html
#
###########################################################################

# Report an error on stderr and exit with status 1 to tell make could not work
arg_error() {
    echo "$0: $@" >&2
    exit 1
}

# print help and exit with success status
print_help() {
    echo "$0: generate a script for ld to keep only the symbols from C header"
    echo "Usage: $0 [options...] input_file[s]"
    echo "valid options are:"
    echo "  -l         : add symbol's definition line number for debug"
    echo "  -n name    : name to use for the library"
    echo "  -v version : set the library version for ld"
    exit 0
}

# Extract command line arguments
while [ $# -gt 0 ]; do
    case $1 in
        -l)
            debug_info='	/* " symbol_type " at " FILENAME ":" NR " */'
          ;;

        -n)
            shift
            echo "$1" | grep '^[A-Za-z][A-Za-z_0-9]*$' > /dev/null || arg_error "invalid name \"$1\" for a library"
            libname="$1"
          ;;

        -v)
            shift
            version="$1"
            echo "$version" | grep -E '^[1-9][0-9]*(:[0-9]+(:[0-9]+)?)?$' > /dev/null || \
                arg_error "version \"$1\" is not valid"
          ;;

        -h|-help|--help) print_help ;;
        -*) arg_error "unknown option '$1'" ;;

        *)
            [ -r "$1" ] || arg_error "source file \"$1\" is not readable"
            input_files="$input_files $1"
          ;;
    esac
    shift
done

# Check consistency of command-line
[ "x$input_files" = "x" ] && arg_error "no source file given"

# Automatic values
if [ -z "$libname" ]; then
    # build the library name from the first header name, remove path stuff, extension and invalid characters
    libname="`echo "$input_files" | sed -e 's,^ ,,;s, .*$,,;s,^.*/\([^/]*\)$,\1,;s,\.[^.]*$,,;s,[^A-Za-z_0-9],_,g;s,^_*,,' `"
fi

# Parse the input file and find the function declarations; extract the names
# and place them in the 'global' section so that ld will keep the symbols;
# generate the rest of the script so that other symbols will not be kept.
awk '
BEGIN {
  # Version number here uses only 1 number from the full version used in libtool
  libversion="'"$version"'";
  if (split(libversion, subversions, ":") > 1) {
    # Calculate [CURRENT - AGE], the goal is that the number will not
    # change when functions are added to the API, but it will be incremented
    # when functions are removed or argument change (which breaks compat)
    libversion = subversions[1] - subversions[3];
  }

  print "/* Generated version-script for ld */";
  print "";
  print "'"$libname"'" libversion;
  print "{";
  print "  global:";
}

/^#[ \t]*define/ {
  # Ignore macro definition because their content could be mis-interpreted
  while (/\\$/) {
    if (getline == 0) { break; }
  }
  next;
}

/\/\*/ {
  # Remove comments to avoid mis-detection
  pos = index($0, "/*");
  comment = substr($0, pos + 2);
  $0 = substr($0, 1, pos - 1);
  while (1) {
    pos = index(comment, "*/");
    if (pos > 0) { break; }
    getline comment;
  }
  $0 = $0 substr(comment, pos + 2);
}

/extern[ \t].*;/ {
  line = $0;

  # Remove everything from the ";"
  sub(/;.*$/, "", line);

  # Check if it is a global variable
  nb = split(line, words, /[\t *]/);

  valid = 1;
  for (i = 1; i < nb; i++) {
    # If the word looks valid, do not abort
    if (words[i] ~ /^[A-Za-z_0-9]*$/) { continue; }

    # Treat the case were the variable is an array, and there was space(s) after the name
    if (words[i] ~ /^\[/) { nb = i - 1; break; }

    # If we are here, the word is not valid; we stop processing the line here but we do
    # not use "next" so the line may match function prototype handler below
    valid = 0;
    break;
  }

  if (valid) {
    # Remove array size, if any
    sub(/\[.*$/, "", words[nb]);

    if (words[nb] ~ /^[A-Za-z][A-Za-z_0-9]*$/) {
      symbol_type = "variable";
      print "    " words[nb] ";'"$debug_info"'";
      next;
    }
  }
}

/^[ \t]*typedef/ {
  # Skip type definition because they could be mis-interpreted when it
  # defines a prototype for a callback function
  nb_braces = 0;
  while (1) {
    # Count the number of brace pairs to skip the content of the definition
    nb = split($0, dummy_array, /\{/);
    nb_braces = nb_braces + nb;

    nb = split($0, dummy_array, /\}/);
    nb_braces = nb_braces - nb;

    # Stop when we have out count of pair of braces and there is the final ";"
    if ((nb_braces == 0) && (dummy_array[nb] ~ /;/)) { break; }

    if (getline == 0) {
      break;
    }
  }
  next;
}

/[\t ]\**[A-Z_a-z][A-Z_a-z0-9]*[\t ]*\(/ {
  # Get everything until the end of the definition, to avoid mis-interpreting
  # arguments and attributes on next lines
  while (1) {
    pos = index($0, ";");
    if (pos != 0) { break; }

    getline nxt;
    $0 = $0 nxt;
  }

  # Remove the argument list
  sub(/[ \t]*\(.*$/, "");

  # Trim possible indentation at beginning of line
  sub(/^[\t ]*/, "");

  # If it does not start by a keyword, it is probably not a valid function
  if (!/^[A-Z_a-z]/) { next; }

  nb = split($0, words, /[\t *]/);
  for (i = 1; i < nb; i++) {
    # Do not consider "static" because it is used for functions to be inlined
    if (words[i] == "static") { next; }

    # Allow empty keyword, that were just "*"
    if (words[i] == "") { continue; }

    # If it does not match, it is unlikely to be a function
    if (words[i] !~ /^[A-Z_a-z][A-Z_a-z0-9]*$/) { next; }
  }

  # If we arrived so far, everything looks valid and the last argument of
  # the array contains the name of the function
  symbol_type = "function";
  print "    " words[nb] ";'"$debug_info"'";
}

END {
  print "";
  print "  local:";
  print "    *;";
  print "};";
}
' $input_files
