#!/usr/bin/env bash
#
# mkexpr
# Morgan Deters <mdeters@cs.nyu.edu> for CVC4
# Copyright (c) 2010-2013  The CVC4 Project
#
# The purpose of this script is to create {expr,expr_manager}.{h,cpp}
# from template files and a list of theory kinds.  Basically it just
# sets up the public interface for access to constants.
#
# Invocation:
#
#   mkexpr template-file theory-kind-files...
#
# Output is to standard out.
#

copyright=2010-2014

filename=`basename "$1" | sed 's,_template,,'`

cat <<EOF
/*********************                                                        */
/** $filename
 **
 ** Copyright $copyright  New York University and The University of Iowa,
 ** and as below.
 **
 ** This file automatically generated by:
 **
 **     $0 $@
 **
 ** for the CVC4 project.
 **/

/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */

/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */
/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ! */

/* Edit the template file instead:                     */
/* $1 */

EOF

me=$(basename "$0")

template=$1; shift

includes=
getConst_instantiations=
getConst_implementations=
mkConst_instantiations=
mkConst_implementations=
exportConstant_cases=

typerules=
construles=

seen_theory=false
seen_theory_builtin=false

function theory {
  # theory ID T header

  lineno=${BASH_LINENO[0]}

  if $seen_theory; then
    echo "$kf:$lineno: error: multiple theories defined in one file !?" >&2
    exit 1
  fi

  # this script doesn't care about the theory class information, but
  # makes does make sure it's there
  seen_theory=true
  if [ "$1" = THEORY_BUILTIN ]; then
    if $seen_theory_builtin; then
      echo "$kf:$lineno: error: \"builtin\" theory redefined" >&2
      exit 1
    fi
    seen_theory_builtin=true
  elif [ -z "$1" -o -z "$2" -o -z "$3" ]; then
    echo "$kf:$lineno: error: \"theory\" directive missing class or header argument" >&2
    exit 1
  elif ! expr "$2" : '\(::*\)' >/dev/null; then
    echo "$kf:$lineno: warning: theory class \`$2' isn't fully-qualified (e.g., ::CVC4::theory::foo)" >&2
  elif ! expr "$2" : '\(::CVC4::theory::*\)' >/dev/null; then
    echo "$kf:$lineno: warning: theory class not under ::CVC4::theory namespace" >&2
  fi
}

function alternate {
  # alternate ID name T header

  lineno=${BASH_LINENO[0]}

  if $seen_theory; then
    echo "$kf:$lineno: error: multiple theories defined in one file !?" >&2
    exit 1
  fi

  seen_theory=true
  seen_endtheory=true
}

function rewriter {
  # rewriter class header
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function properties {
  # properties prop*
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function endtheory {
  # endtheory
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  seen_endtheory=true
}

function enumerator {
  # enumerator KIND enumerator-class header
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function typechecker {
  # typechecker header
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  typechecker_includes="${typechecker_includes}
#include \"$1\""
}

function typerule {
  # typerule OPERATOR typechecking-class
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  typerules="${typerules}
#line $lineno \"$kf\"
  case kind::$1:
#line $lineno \"$kf\"
    typeNode = $2::computeType(nodeManager, n, check);
    break;
"
}

function construle {
  # isconst OPERATOR isconst-checking-class
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  construles="${construles}
#line $lineno \"$kf\"
  case kind::$1:
#line $lineno \"$kf\"
    return $2::computeIsConst(nodeManager, n);
"
}

function sort {
  # sort TYPE cardinality [well-founded ground-term header | not-well-founded] ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function cardinality {
  # cardinality TYPE cardinality-computer [header]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function well-founded {
  # well-founded TYPE wellfoundedness-computer [header]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function variable {
  # variable K ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function operator {
  # operator K #children ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function parameterized {
  # parameterized K #children ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function constant {
  # constant K T Hasher header ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen

  # Only add include if it is not in the list of includes yet
  if [[ "${includes}" != *"#include \"$4\""* ]]; then
    includes="${includes}
#include \"$4\""
  fi

  mkConst_instantiations="${mkConst_instantiations}
#line $lineno \"$kf\"
template <> Expr ExprManager::mkConst($2 const& val);
"
  mkConst_implementations="${mkConst_implementations}
#line $lineno \"$kf\"
template <> Expr ExprManager::mkConst($2 const& val) {
#line $lineno \"$kf\"
  return Expr(this, new Node(d_nodeManager->mkConst< $2 >(val)));
}
"
  getConst_instantiations="${getConst_instantiations}
#line $lineno \"$kf\"
template <> $2 const & Expr::getConst< $2 >() const;
"
  getConst_implementations="${getConst_implementations}
#line $lineno \"$kf\"
template <> $2 const & Expr::getConst() const {
#line $lineno \"$kf\"
  PrettyCheckArgument(getKind() == ::CVC4::kind::$1, *this, \"Improper kind for getConst<$2>()\");
#line $lineno \"$kf\"
  return d_node->getConst< $2 >();
}
"
  exportConstant_cases="${exportConstant_cases}
  case $1: return to->mkConst(n.getConst< $2 >());"
}

function nullaryoperator {
  # nullaryoperator K ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function check_theory_seen {
  if $seen_endtheory; then
    echo "$kf:$lineno: error: command after \"endtheory\" declaration (endtheory has to be last)" >&2
    exit 1
  fi
  if ! $seen_theory; then
    echo "$kf:$lineno: error: no \"theory\" declaration found (it has to be first)" >&2
    exit 1
  fi
}

function check_builtin_theory_seen {
  if ! $seen_theory_builtin; then
    echo "$me: warning: no declaration for the builtin theory found" >&2
  fi
}

while [ $# -gt 0 ]; do
  kf=$1
  seen_theory=false
  seen_endtheory=false
  b=$(basename $(dirname "$kf"))
  source "$kf"
  if ! $seen_theory; then
    echo "$kf: error: no theory content found in file!" >&2
    exit 1
  fi
  if ! $seen_endtheory; then
    echo "$kf:$lineno: error: no \"endtheory\" declaration found (it is required at the end)" >&2
    exit 1
  fi
  shift
done
check_builtin_theory_seen

## output

# generate warnings about incorrect #line annotations in templates
nl -ba -s' ' "$template"  | grep '^ *[0-9][0-9]* # *line' |
  awk '{OFS="";if($1+1!=$3) print "'"$template"':",$1,": warning: incorrect annotation \"#line ",$3,"\" (it should be \"#line ",($1+1),"\")"}' >&2

text=$(cat "$template")
for var in \
    includes \
    template \
    getConst_instantiations \
    getConst_implementations \
    mkConst_instantiations \
    mkConst_implementations \
    exportConstant_cases \
    typechecker_includes \
    typerules \
    construles \
    ; do
  eval text="\${text//\\\$\\{$var\\}/\${$var}}"
done
error=`expr "$text" : '.*\${\([^}]*\)}.*'`
if [ -n "$error" ]; then
  echo "$template:0: error: undefined replacement \${$error}" >&2
  exit 1
fi
echo "$text"
