text: #ifndef STAN_LANG_GRAMMARS_SEMANTIC_ACTIONS_DEF_CPP
  #define STAN_LANG_GRAMMARS_SEMANTIC_ACTIONS_DEF_CPP
  
  #include "iterator_typedefs.hpp"
  #include "semantic_actions.hpp"
  #include "stan/language/ast.hpp"
  #include <stan/util/io/program_reader.hpp>
  #include <boost/algorithm/string.hpp>
  #include <boost/format.hpp>
  #include <boost/spirit/include/qi.hpp>
  #include <boost/variant/apply_visitor.hpp>
  #include <boost/variant/recursive_variant.hpp>
  #include <climits>
  #include <cstddef>
  #include <iomanip>
  #include <iostream>
  #include <limits>
  #include <map>
  #include <set>
  #include <stdexcept>
  #include <string>
  #include <typeinfo>
  #include <utility>
  #include <vector>
  
  namespace stan {
  
  namespace lang {
  
  /**
   * Add namespace qualifier `stan::math::` or `std::` to function names
   * in order to avoid ambiguities for functions in the Stan language which
   * are also defined in c and/or other libraries that some compilers (gcc)
   * bring into the top-level namespace.
   *
   * @param[in, out] f Function to qualify.
   */
  void qualify_builtins(fun &f) {
    if ((f.name_ == "max" || f.name_ == "min") && f.args_.size() == 2
        && f.args_[0].bare_type().is_int_type()
        && f.args_[1].bare_type().is_int_type()) {
      f.name_ = "std::" + f.name_;
      return;
    }
  
    if (f.name_ == "ceil" && f.args_[0].bare_type().is_int_type()) {
      f.name_ = "std::" + f.name_;
      return;
    }
  
    if ((f.args_.size() == 0
         && (f.name_ == "e" || f.name_ == "pi" || f.name_ == "log2"
             || f.name_ == "log10" || f.name_ == "sqrt2"
             || f.name_ == "not_a_number" || f.name_ == "positive_infinity"
             || f.name_ == "negative_infinity" || f.name_ == "machine_precision"))
        || (f.args_.size() == 1
            && (f.name_ == "abs" || f.name_ == "acos" || f.name_ == "acosh"
                || f.name_ == "asin" || f.name_ == "asinh" || f.name_ == "atan"
                || f.name_ == "atan2" || f.name_ == "atanh" || f.name_ == "cbrt"
                || f.name_ == "ceil" || f.name_ == "cos" || f.name_ == "cosh"
                || f.name_ == "erf" || f.name_ == "erfc" || f.name_ == "exp"
                || f.name_ == "exp2" || f.name_ == "expm1" || f.name_ == "fabs"
                || f.name_ == "floor" || f.name_ == "lgamma" || f.name_ == "log"
                || f.name_ == "log1p" || f.name_ == "log2" || f.name_ == "log10"
                || f.name_ == "round" || f.name_ == "sin" || f.name_ == "sinh"
                || f.name_ == "sqrt" || f.name_ == "tan" || f.name_ == "tanh"
                || f.name_ == "tgamma" || f.name_ == "trunc"))
        || (f.args_.size() == 2
            && (f.name_ == "fdim" || f.name_ == "fmax" || f.name_ == "fmin"
                || f.name_ == "hypot"))
        || (f.args_.size() == 3 && f.name_ == "fma"))
      f.name_ = "stan::math::" + f.name_;
  }
  
  bool has_prob_suffix(const std::string &s) {
    return ends_with("_lpdf", s) || ends_with("_lpmf", s) || ends_with("_lcdf", s)
           || ends_with("_lccdf", s);
  }
  
  bool has_rng_lp_suffix(const std::string &s) {
    return ends_with("_lp", s) || ends_with("_rng", s);
  }
  
  void replace_suffix(const std::string &old_suffix,
                      const std::string &new_suffix, fun &f) {
    if (!ends_with(old_suffix, f.name_))
      return;
    f.name_ = f.name_.substr(0, f.name_.size() - old_suffix.size()) + new_suffix;
  }
  
  bool deprecate_fun(const std::string &old_name, const std::string &new_name,
                     fun &f, std::ostream &msgs) {
    if (f.name_ != old_name)
      return false;
    f.original_name_ = f.name_;
    f.name_ = new_name;
    msgs << "Info: Function name '" << old_name << "' is deprecated"
         << " and will be removed in a later release; please replace"
         << " with '" << new_name << "'" << std::endl;
    return true;
  }
  
  bool deprecate_suffix(const std::string &deprecated_suffix,
                        const std::string &replacement, fun &f,
                        std::ostream &msgs) {
    if (!ends_with(deprecated_suffix, f.name_))
      return false;
    msgs << "Info: Deprecated function '" << f.name_ << "';"
         << " please replace suffix '" << deprecated_suffix << "' with "
         << replacement << std::endl;
    return true;
  }
  
  void set_fun_type(fun &fun, std::ostream &error_msgs) {
    std::vector<bare_expr_type> arg_types;
    for (size_t i = 0; i < fun.args_.size(); ++i)
      arg_types.push_back(fun.args_[i].bare_type());
    fun.type_ = function_signatures::instance().get_result_type(
        fun.name_, arg_types, error_msgs);
  }
  
  int num_dimss(std::vector<std::vector<stan::lang::expression>> &dimss) {
    int sum = 0;
    for (size_t i = 0; i < dimss.size(); ++i)
      sum += dimss[i].size();
    return sum;
  }
  
  bool is_double_return(const std::string &function_name,
                        const std::vector<bare_expr_type> &arg_types,
                        std::ostream &error_msgs) {
    return function_signatures::instance()
        .get_result_type(function_name, arg_types, error_msgs, true)
        .is_double_type();
  }
  
  bool is_univariate(const bare_expr_type &et) {
    return et.num_dims() == 0 && (et.is_int_type() || et.is_double_type());
  }
  
  bool can_assign_to_lhs_var(const std::string &lhs_var_name,
                             const scope &var_scope, const variable_map &vm,
                             std::ostream &error_msgs) {
    if (lhs_var_name == std::string("lp__")) {
      error_msgs << std::endl
                 << "Error (fatal):  Use of lp__ is no longer supported."
                 << std::endl
                 << "  Use target += ... statement to increment log density."
                 << std::endl;
      return false;
    }
    if (!vm.exists(lhs_var_name)) {
      error_msgs << "Unknown variable in assignment"
                 << "; lhs variable=" << lhs_var_name << std::endl;
      return false;
    }
    scope lhs_origin = vm.get_scope(lhs_var_name);
    // enforce constancy of loop variables
    if (lhs_origin.program_block() == loop_identifier_origin) {
      error_msgs << "Loop variable " << lhs_var_name
                 << " cannot be used on left side of assignment statement."
                 << std::endl;
      return false;
    }
    // enforce constancy of function args
    if (!lhs_origin.is_local() && lhs_origin.fun()) {
      error_msgs << "Cannot assign to function argument variables." << std::endl
                 << "Use local variables instead." << std::endl;
      return false;
    }
    if (lhs_origin.program_block() != var_scope.program_block()) {
      error_msgs << "Cannot assign to variable outside of declaration block"
                 << "; left-hand-side variable origin=";
      print_scope(error_msgs, lhs_origin);
      error_msgs << std::endl;
      return false;
    }
    return true;
  }
  
  bare_expr_type infer_var_dims_type(const bare_expr_type &var_type,
                                     const variable_dims &var_dims) {
    size_t num_index_dims = var_dims.dims_.size();
    return infer_type_indexing(var_type, num_index_dims);
  }
  
  bool has_same_shape(const bare_expr_type &lhs_type, const expression &rhs_expr,
                      const std::string &name, const std::string &stmt_type,
                      std::ostream &error_msgs) {
    if (lhs_type.num_dims() != rhs_expr.bare_type().num_dims()
        || lhs_type.array_dims() != rhs_expr.bare_type().array_dims()) {
      error_msgs << "Dimension mismatch in " << stmt_type
                 << "; variable name = " << name << ", type = " << lhs_type
                 << "; right-hand side type = " << rhs_expr.bare_type() << "."
                 << std::endl;
      return false;
    }
  
    // allow int -> double promotion, even in arrays
    bool types_compatible
        = (lhs_type.innermost_type() == rhs_expr.bare_type().innermost_type()
           || (lhs_type.innermost_type().is_double_type()
               && rhs_expr.bare_type().innermost_type().is_int_type()));
    if (!types_compatible) {
      error_msgs << "Base type mismatch in " << stmt_type
                 << "; variable name = " << name
                 << ", base type = " << lhs_type.innermost_type()
                 << "; right-hand side base type = "
                 << rhs_expr.bare_type().innermost_type() << "." << std::endl;
      return false;
    }
    return true;
  }
  
  //  //////////////////////////////////
  // *** functors for grammar rules ***
  //  //////////////////////////////////
  
  void validate_double_expr::operator()(const expression &expr, bool &pass,
                                        std::stringstream &error_msgs) const {
    if (!expr.bare_type().is_double_type() && !expr.bare_type().is_int_type()) {
      error_msgs << "Expression denoting real required; found type="
                 << expr.bare_type() << "." << std::endl;
      pass = false;
      return;
    }
    pass = true;
  }
  boost::phoenix::function<validate_double_expr> validate_double_expr_f;
  
  template <typename L, typename R>
  void assign_lhs::operator()(L &lhs, const R &rhs) const {
    lhs = rhs;
  }
  boost::phoenix::function<assign_lhs> assign_lhs_f;
  
  template void assign_lhs::operator()(expression &, const expression &) const;
  template void assign_lhs::operator()(expression &,
                                       const double_literal &) const;
  template void assign_lhs::operator()(expression &, const int_literal &) const;
  template void assign_lhs::operator()(expression &, const integrate_1d &) const;
  template void assign_lhs::operator()(expression &, const integrate_ode &) const;
  template void assign_lhs::operator()(expression &,
                                       const integrate_ode_control &) const;
  template void assign_lhs::operator()(expression &,
                                       const algebra_solver &) const;
  template void assign_lhs::operator()(expression &,
                                       const algebra_solver_control &) const;
  template void assign_lhs::operator()(expression &, const map_rect &) const;
  template void assign_lhs::operator()(array_expr &, const array_expr &) const;
  template void assign_lhs::operator()(matrix_expr &, const matrix_expr &) const;
  template void assign_lhs::operator()(row_vector_expr &,
                                       const row_vector_expr &) const;
  template void assign_lhs::operator()(int &, const int &) const;
  template void assign_lhs::operator()(size_t &, const size_t &) const;
  template void assign_lhs::operator()(statement &, const statement &) const;
  template void assign_lhs::operator()(std::vector<var_decl> &,
                                       const std::vector<var_decl> &) const;
  template void assign_lhs::operator()(std::vector<idx> &,
                                       const std::vector<idx> &) const;
  template void assign_lhs::operator()(
      std::vector<std::vector<expression>> &,
      const std::vector<std::vector<expression>> &) const;
  template void assign_lhs::operator()(fun &, const fun &) const;
  template void assign_lhs::operator()(variable &, const variable &) const;
  template void assign_lhs::operator()(std::vector<block_var_decl> &,
                                       const std::vector<block_var_decl> &) const;
  template void assign_lhs::operator()(std::vector<local_var_decl> &,
                                       const std::vector<local_var_decl> &) const;
  template void assign_lhs::operator()(bare_expr_type &,
                                       const bare_expr_type &) const;
  template void assign_lhs::operator()(block_var_decl &,
                                       const block_var_decl &) const;
  template void assign_lhs::operator()(local_var_decl &,
                                       const local_var_decl &) const;
  template void assign_lhs::operator()(var_decl &, const var_decl &) const;
  
  void validate_expr_type3::operator()(const expression &expr, bool &pass,
                                       std::ostream &error_msgs) const {
    pass = !expr.bare_type().is_ill_formed_type();
    if (!pass)
      error_msgs << "Expression is ill formed." << std::endl;
  }
  boost::phoenix::function<validate_expr_type3> validate_expr_type3_f;
  
  void is_prob_fun::operator()(const std::string &s, bool &pass) const {
    pass = has_prob_suffix(s);
  }
  boost::phoenix::function<is_prob_fun> is_prob_fun_f;
  
  void addition_expr3::operator()(expression &expr1, const expression &expr2,
                                  std::ostream &error_msgs) const {
    if (expr1.bare_type().is_primitive() && expr2.bare_type().is_primitive()) {
      expr1 += expr2;
      return;
    }
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
    fun f("add", args);
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
  }
  boost::phoenix::function<addition_expr3> addition3_f;
  
  void subtraction_expr3::operator()(expression &expr1, const expression &expr2,
                                     std::ostream &error_msgs) const {
    if (expr1.bare_type().is_primitive() && expr2.bare_type().is_primitive()) {
      expr1 -= expr2;
      return;
    }
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
    fun f("subtract", args);
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
  }
  boost::phoenix::function<subtraction_expr3> subtraction3_f;
  
  void increment_size_t::operator()(size_t &lhs) const { ++lhs; }
  boost::phoenix::function<increment_size_t> increment_size_t_f;
  
  void validate_conditional_op::operator()(conditional_op &conditional_op,
                                           const scope &var_scope, bool &pass,
                                           const variable_map &var_map,
                                           std::ostream &error_msgs) const {
    bare_expr_type cond_type = conditional_op.cond_.bare_type();
    if (!cond_type.is_int_type()) {
      error_msgs << "Condition in ternary expression must be"
                 << " primitive int;"
                 << " found type=" << cond_type << "." << std::endl;
      pass = false;
      return;
    }
  
    bare_expr_type true_val_type = conditional_op.true_val_.bare_type();
    bare_expr_type false_val_type = conditional_op.false_val_.bare_type();
  
    bool types_compatible
        = (true_val_type == false_val_type)
          || (true_val_type.is_double_type() && false_val_type.is_int_type())
          || (true_val_type.is_int_type() && false_val_type.is_double_type());
  
    if (!types_compatible) {
      error_msgs << "Type mismatch in ternary expression,"
                 << " expression when true is: ";
      write_bare_expr_type(error_msgs, true_val_type);
      error_msgs << "; expression when false is: ";
      write_bare_expr_type(error_msgs, false_val_type);
      error_msgs << "." << std::endl;
      pass = false;
      return;
    }
  
    if (true_val_type.is_primitive())
      conditional_op.type_
          = (true_val_type == false_val_type) ? true_val_type : double_type();
    else
      conditional_op.type_ = true_val_type;
  
    conditional_op.has_var_ = has_var(conditional_op, var_map);
    conditional_op.scope_ = var_scope;
    pass = true;
  }
  boost::phoenix::function<validate_conditional_op> validate_conditional_op_f;
  
  void binary_op_expr::operator()(expression &expr1, const expression &expr2,
                                  const std::string &op,
                                  const std::string &fun_name,
                                  std::ostream &error_msgs) const {
    if (!expr1.bare_type().is_primitive() || !expr2.bare_type().is_primitive()) {
      error_msgs << "Binary infix operator " << op
                 << " with functional interpretation " << fun_name
                 << " requires arguments of primitive type (int or real)"
                 << ", found left type=" << expr1.bare_type()
                 << ", right arg type=" << expr2.bare_type() << "." << std::endl;
    }
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
    fun f(fun_name, args);
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
  }
  boost::phoenix::function<binary_op_expr> binary_op_f;
  
  void validate_non_void_arg_function::operator()(
      bare_expr_type &arg_type, const scope &var_scope, bool &pass,
      std::ostream &error_msgs) const {
    if (var_scope.program_block() == data_origin)
      arg_type.set_is_data();
    pass = !arg_type.is_void_type();
    if (!pass)
      error_msgs << "Functions cannot contain void argument types; "
                 << "found void argument." << std::endl;
  }
  boost::phoenix::function<validate_non_void_arg_function>
      validate_non_void_arg_f;
  
  void set_void_function::operator()(const bare_expr_type &return_type,
                                     scope &var_scope, bool &pass,
                                     std::ostream &error_msgs) const {
    if (return_type.is_void_type() && return_type.num_dims() > 0) {
      error_msgs << "Void return type may not have dimensions declared."
                 << std::endl;
      pass = false;
      return;
    }
    var_scope = return_type.is_void_type() ? scope(void_function_argument_origin)
                                           : scope(function_argument_origin);
    pass = true;
  }
  boost::phoenix::function<set_void_function> set_void_function_f;
  
  void set_allows_sampling_origin::operator()(const std::string &identifier,
                                              scope &var_scope) const {
    if (ends_with("_lp", identifier)) {
      var_scope = var_scope.void_fun() ? scope(void_function_argument_origin_lp)
                                       : scope(function_argument_origin_lp);
    } else if (ends_with("_rng", identifier)) {
      var_scope = var_scope.void_fun() ? scope(void_function_argument_origin_rng)
                                       : scope(function_argument_origin_rng);
    } else {
      var_scope = var_scope.void_fun() ? scope(void_function_argument_origin)
                                       : scope(function_argument_origin);
    }
  }
  boost::phoenix::function<set_allows_sampling_origin>
      set_allows_sampling_origin_f;
  
  void validate_declarations::operator()(
      bool &pass,
      std::set<std::pair<std::string, function_signature_t>> &declared,
      std::set<std::pair<std::string, function_signature_t>> &defined,
      std::ostream &error_msgs, bool allow_undefined) const {
    using std::pair;
    using std::set;
    using std::string;
    typedef set<pair<string, function_signature_t>>::iterator iterator_t;
    if (!allow_undefined) {
      for (iterator_t it = declared.begin(); it != declared.end(); ++it) {
        if (defined.find(*it) == defined.end()) {
          error_msgs << "Function declared, but not defined."
                     << " Function name=" << (*it).first << std::endl;
          pass = false;
          return;
        }
      }
    }
    pass = true;
  }
  boost::phoenix::function<validate_declarations> validate_declarations_f;
  
  bool fun_exists(
      const std::set<std::pair<std::string, function_signature_t>> &existing,
      const std::pair<std::string, function_signature_t> &name_sig,
      bool name_only = true) {
    for (std::set<std::pair<std::string, function_signature_t>>::const_iterator it
         = existing.begin();
         it != existing.end(); ++it) {
      if (name_sig.first == (*it).first
          && (name_only || name_sig.second.second == (*it).second.second))
        return true;  // name and arg sequences match
    }
    return false;
  }
  
  void validate_prob_fun::operator()(std::string &fname, bool &pass,
                                     std::ostream &error_msgs) const {
    if (has_prob_fun_suffix(fname)) {
      std::string dist_name = strip_prob_fun_suffix(fname);
      if (!fun_name_exists(fname)  // catch redefines later avoid fwd
          && (fun_name_exists(dist_name + "_lpdf")
              || fun_name_exists(dist_name + "_lpmf")
              || fun_name_exists(dist_name + "_log"))) {
        error_msgs << "Parse Error.  Probability function already defined"
                   << " for " << dist_name << std::endl;
        pass = false;
        return;
      }
    }
    if (has_cdf_suffix(fname)) {
      std::string dist_name = strip_cdf_suffix(fname);
      if (fun_name_exists(dist_name + "_cdf_log")
          || fun_name_exists(dist_name + "_lcdf")) {
        error_msgs << " Parse Error.  CDF already defined for " << dist_name
                   << std::endl;
        pass = false;
        return;
      }
    }
    if (has_ccdf_suffix(fname)) {
      std::string dist_name = strip_ccdf_suffix(fname);
      if (fun_name_exists(dist_name + "_ccdf_log")
          || fun_name_exists(dist_name + "_lccdf")) {
        error_msgs << " Parse Error.  CCDF already defined for " << dist_name
                   << std::endl;
        pass = false;
        return;
      }
    }
  }
  boost::phoenix::function<validate_prob_fun> validate_prob_fun_f;
  
  void add_function_signature::operator()(
      const function_decl_def &decl, bool &pass,
      std::set<std::pair<std::string, function_signature_t>> &functions_declared,
      std::set<std::pair<std::string, function_signature_t>> &functions_defined,
      std::ostream &error_msgs) const {
    std::vector<bare_expr_type> arg_types;
    for (size_t i = 0; i < decl.arg_decls_.size(); ++i) {
      arg_types.push_back(decl.arg_decls_[i].bare_type());
    }
    function_signature_t sig(decl.return_type_, arg_types);
    std::pair<std::string, function_signature_t> name_sig(decl.name_, sig);
  
    // check that not already declared if just declaration
    if (decl.body_.is_no_op_statement()
        && fun_exists(functions_declared, name_sig)) {
      error_msgs << "Parse Error.  Function already declared, name="
                 << decl.name_;
      pass = false;
      return;
    }
  
    // check not already user defined
    if (fun_exists(functions_defined, name_sig)) {
      error_msgs << "Parse Error.  Function already defined, name=" << decl.name_;
      pass = false;
      return;
    }
  
    // check not already system defined
    if (!fun_exists(functions_declared, name_sig)
        && function_signatures::instance().is_defined(decl.name_, sig)) {
      error_msgs << "Parse Error.  Function system defined, name=" << decl.name_;
      pass = false;
      return;
    }
  
    // check argument type and qualifiers
    if (!decl.body_.is_no_op_statement()) {
      function_signature_t decl_sig
          = function_signatures::instance().get_definition(decl.name_, sig);
      if (!decl_sig.first.is_ill_formed_type()) {
        for (size_t i = 0; i < decl.arg_decls_.size(); ++i) {
          if (decl_sig.second[i] != arg_types[i]
              || decl_sig.second[i].is_data() != arg_types[i].is_data()) {
            error_msgs << "Declaration doesn't match definition "
                       << "for function: " << decl.name_ << " argument "
                       << (i + 1) << ": argument declared as " << arg_types[i]
                       << ", defined as " << decl_sig.second[i] << "."
                       << std::endl;
            pass = false;
            return;
          }
        }
      }
    }
  
    if (ends_with("_lpdf", decl.name_)
        && arg_types[0].innermost_type().is_int_type()) {
      error_msgs << "Parse Error.  Probability density functions require"
                 << " real variates (first argument)."
                 << " Found type = " << arg_types[0] << std::endl;
      pass = false;
      return;
    }
    if (ends_with("_lpmf", decl.name_)
        && !arg_types[0].innermost_type().is_int_type()) {
      error_msgs << "Parse Error.  Probability mass functions require"
                 << " integer variates (first argument)."
                 << " Found type = " << arg_types[0] << std::endl;
      pass = false;
      return;
    }
  
    // add declaration in local sets and in parser function sigs
    if (functions_declared.find(name_sig) == functions_declared.end()) {
      functions_declared.insert(name_sig);
      function_signatures::instance().add(decl.name_, decl.return_type_,
                                          arg_types);
      function_signatures::instance().set_user_defined(name_sig);
    }
  
    // add as definition if there's a body
    if (!decl.body_.is_no_op_statement())
      functions_defined.insert(name_sig);
    pass = true;
  }
  boost::phoenix::function<add_function_signature> add_function_signature_f;
  
  void validate_pmf_pdf_variate::operator()(function_decl_def &decl, bool &pass,
                                            std::ostream &error_msgs) const {
    if (!has_prob_fun_suffix(decl.name_))
      return;
    if (decl.arg_decls_.size() == 0) {
      error_msgs << "Parse Error.  Probability functions require"
                 << " at least one argument." << std::endl;
      pass = false;
      return;
    }
    bare_expr_type variate_type = decl.arg_decls_[0].bare_type().innermost_type();
    if (ends_with("_lpdf", decl.name_) && variate_type.is_int_type()) {
      error_msgs << "Parse Error.  Probability density functions require"
                 << " real variates (first argument)."
                 << " Found type = " << variate_type << std::endl;
      pass = false;
      return;
    }
    if (ends_with("_lpmf", decl.name_) && !variate_type.is_int_type()) {
      error_msgs << "Parse Error.  Probability mass functions require"
                 << " integer variates (first argument)."
                 << " Found type = " << variate_type << std::endl;
      pass = false;
      return;
    }
  }
  boost::phoenix::function<validate_pmf_pdf_variate> validate_pmf_pdf_variate_f;
  
  void validate_return_type::operator()(function_decl_def &decl, bool &pass,
                                        std::ostream &error_msgs) const {
    pass = decl.body_.is_no_op_statement()
           || stan::lang::returns_type(decl.return_type_, decl.body_, error_msgs);
    if (!pass) {
      error_msgs << "Improper return in body of function." << std::endl;
      return;
    }
  
    if ((ends_with("_log", decl.name_) || ends_with("_lpdf", decl.name_)
         || ends_with("_lpmf", decl.name_) || ends_with("_lcdf", decl.name_)
         || ends_with("_lccdf", decl.name_))
        && !decl.return_type_.is_double_type()) {
      pass = false;
      error_msgs << "Real return type required for probability functions"
                 << " ending in _log, _lpdf, _lpmf, _lcdf, or _lccdf."
                 << std::endl;
    }
  }
  boost::phoenix::function<validate_return_type> validate_return_type_f;
  
  void set_fun_params_scope::operator()(scope &var_scope,
                                        variable_map &vm) const {
    var_scope = scope(var_scope.program_block(), true);
    // generated log_prob code has vector called "params_r__"
    // hidden way to get unconstrained params from model
    vm.add("params_r__", var_decl("params_r__", vector_type()), parameter_origin);
  }
  boost::phoenix::function<set_fun_params_scope> set_fun_params_scope_f;
  
  void unscope_variables::operator()(function_decl_def &decl,
                                     variable_map &vm) const {
    vm.remove("params_r__");
    for (size_t i = 0; i < decl.arg_decls_.size(); ++i)
      vm.remove(decl.arg_decls_[i].name());
  }
  boost::phoenix::function<unscope_variables> unscope_variables_f;
  
  void add_fun_arg_var::operator()(const var_decl &decl, const scope &scope,
                                   bool &pass, variable_map &vm,
                                   std::ostream &error_msgs) const {
    if (vm.exists(decl.name())) {
      pass = false;
      error_msgs << "Duplicate declaration of variable, name=" << decl.name()
                 << "; attempt to redeclare as function argument"
                 << "; original declaration as ";
      print_scope(error_msgs, vm.get_scope(decl.name()));
      error_msgs << " variable." << std::endl;
      return;
    }
    pass = true;
    stan::lang::scope arg_scope(scope.program_block() == data_origin
                                    ? data_origin
                                    : function_argument_origin);
    vm.add(decl.name(), decl, arg_scope);
  }
  boost::phoenix::function<add_fun_arg_var> add_fun_arg_var_f;
  
  // TODO(carpenter): seems redundant; see if it can be removed
  void set_omni_idx::operator()(omni_idx &val) const { val = omni_idx(); }
  boost::phoenix::function<set_omni_idx> set_omni_idx_f;
  
  void validate_int_expr_silent::operator()(const expression &e,
                                            bool &pass) const {
    pass = e.bare_type().is_int_type();
  }
  boost::phoenix::function<validate_int_expr_silent> validate_int_expr_silent_f;
  
  void validate_ints_expression::operator()(const expression &e, bool &pass,
                                            std::ostream &error_msgs) const {
    if (!e.bare_type().innermost_type().is_int_type()) {
      error_msgs << "Container index must be integer; found type="
                 << e.bare_type() << std::endl;
      pass = false;
      return;
    }
    if (e.bare_type().num_dims() > 1) {
      // tests > 1 so that message is coherent because the single
      // integer array tests don't print
      error_msgs << "Index must be integer or 1D integer array;"
                 << " found number of dimensions=" << e.bare_type().num_dims()
                 << std::endl;
      pass = false;
      return;
    }
    if (e.bare_type().num_dims() == 0) {
      // not an array expression, fail and backtrack
      pass = false;
      return;
    }
    pass = true;
  }
  boost::phoenix::function<validate_ints_expression> validate_ints_expression_f;
  
  void add_params_var::operator()(variable_map &vm) const {
    vm.add("params_r__", var_decl("params_r__", vector_type()),
           parameter_origin);  // acts like a parameter
  }
  boost::phoenix::function<add_params_var> add_params_var_f;
  
  void remove_params_var::operator()(variable_map &vm) const {
    vm.remove("params_r__");
  }
  boost::phoenix::function<remove_params_var> remove_params_var_f;
  
  void dump_program_line(size_t idx_errline, int offset,
                         const std::string &origin_file, size_t origin_line,
                         const io::program_reader &reader,
                         const std::vector<std::string> &program_lines,
                         std::stringstream &error_msgs) {
    boost::format fmt_lineno("%6d: ");
    if (idx_errline + offset > 0 && idx_errline + offset < program_lines.size()) {
      io::program_reader::trace_t trace = reader.trace(idx_errline + offset);
      if (trace[trace.size() - 1].first == origin_file) {
        std::string lineno = str(fmt_lineno % (origin_line + offset));
        error_msgs << lineno << program_lines[idx_errline + offset - 1]
                   << std::endl;
      }
    }
  }
  
  void program_error::operator()(pos_iterator_t begin, pos_iterator_t end,
                                 pos_iterator_t where, variable_map &vm,
                                 std::stringstream &error_msgs,
                                 const io::program_reader &reader) const {
    // extract line and column of error
    size_t idx_errline = boost::spirit::get_line(where);
    if (idx_errline == 0) {
      error_msgs << "Error before start of program." << std::endl;
      return;
    }
    size_t idx_errcol = 0;
    idx_errcol = get_column(begin, where) - 1;
  
    // extract lines of included program
    std::basic_stringstream<char> program_ss;
    program_ss << boost::make_iterator_range(begin, end);
    std::vector<std::string> program_lines;
    while (!program_ss.eof()) {
      std::string line;
      std::getline(program_ss, line);
      program_lines.push_back(line);
    }
  
    // dump include trace for error line
    io::program_reader::trace_t trace = reader.trace(idx_errline);
    std::string origin_file = trace[trace.size() - 1].first;
    size_t origin_line = trace[trace.size() - 1].second;
    error_msgs << " error in '" << trace[trace.size() - 1].first << "' at line "
               << trace[trace.size() - 1].second << ", column " << idx_errcol
               << std::endl;
    for (int i = trace.size() - 1; i-- > 0;)
      error_msgs << " included from '" << trace[i].first << "' at line "
                 << trace[i].second << std::endl;
  
    // dump context of error
    error_msgs << "  -------------------------------------------------"
               << std::endl;
  
    dump_program_line(idx_errline, -2, origin_file, origin_line, reader,
                      program_lines, error_msgs);
    dump_program_line(idx_errline, -1, origin_file, origin_line, reader,
                      program_lines, error_msgs);
    dump_program_line(idx_errline, 0, origin_file, origin_line, reader,
                      program_lines, error_msgs);
    error_msgs << std::setw(idx_errcol + 8) << "^" << std::endl;
    dump_program_line(idx_errline, +1, origin_file, origin_line, reader,
                      program_lines, error_msgs);
  
    error_msgs << "  -------------------------------------------------"
               << std::endl
               << std::endl;
  }
  boost::phoenix::function<program_error> program_error_f;
  
  void add_conditional_condition::operator()(
      conditional_statement &cs, const expression &e, bool &pass,
      std::stringstream &error_msgs) const {
    if (!e.bare_type().is_primitive()) {
      error_msgs << "Conditions in if-else statement must be"
                 << " primitive int or real;"
                 << " found type=" << e.bare_type() << std::endl;
      pass = false;
      return;
    }
    cs.conditions_.push_back(e);
    pass = true;
    return;
  }
  boost::phoenix::function<add_conditional_condition> add_conditional_condition_f;
  
  void add_conditional_body::operator()(conditional_statement &cs,
                                        const statement &s) const {
    cs.bodies_.push_back(s);
  }
  boost::phoenix::function<add_conditional_body> add_conditional_body_f;
  
  void deprecate_old_assignment_op::operator()(std::string &op,
                                               std::ostream &error_msgs) const {
    error_msgs << "Info: assignment operator <- deprecated"
               << " in the Stan language;"
               << " use = instead." << std::endl;
    std::string eq("=");
    op = eq;
  }
  boost::phoenix::function<deprecate_old_assignment_op>
      deprecate_old_assignment_op_f;
  
  void non_void_return_msg::operator()(scope var_scope, bool &pass,
                                       std::ostream &error_msgs) const {
    pass = false;
    if (var_scope.non_void_fun()) {
      error_msgs << "Non-void function must return expression"
                 << " of specified return type." << std::endl;
      return;
    }
    error_msgs << "Return statement only allowed from function bodies."
               << std::endl;
  }
  boost::phoenix::function<non_void_return_msg> non_void_return_msg_f;
  
  void validate_return_allowed::operator()(scope var_scope, bool &pass,
                                           std::ostream &error_msgs) const {
    if (var_scope.void_fun()) {
      error_msgs << "Void function cannot return a value." << std::endl;
      pass = false;
      return;
    }
    if (!var_scope.non_void_fun()) {
      error_msgs << "Returns only allowed from function bodies." << std::endl;
      pass = false;
      return;
    }
    pass = true;
  }
  boost::phoenix::function<validate_return_allowed> validate_return_allowed_f;
  
  void validate_void_return_allowed::operator()(scope var_scope, bool &pass,
                                                std::ostream &error_msgs) const {
    if (!var_scope.void_fun()) {
      error_msgs << "Void returns only allowed from function"
                 << " bodies of void return type." << std::endl;
      pass = false;
      return;
    }
    pass = true;
  }
  boost::phoenix::function<validate_void_return_allowed>
      validate_void_return_allowed_f;
  
  void validate_lhs_var_assgn::operator()(assgn &a, const scope &var_scope,
                                          bool &pass, const variable_map &vm,
                                          std::ostream &error_msgs) const {
    std::string name(a.lhs_var_.name_);
    if (!can_assign_to_lhs_var(name, var_scope, vm, error_msgs)) {
      pass = false;
      return;
    }
    a.lhs_var_.set_type(vm.get_bare_type(name));
  }
  boost::phoenix::function<validate_lhs_var_assgn> validate_lhs_var_assgn_f;
  
  void set_lhs_var_assgn::operator()(assgn &a, const std::string &name,
                                     bool &pass, const variable_map &vm) const {
    if (!vm.exists(name)) {
      pass = false;
      return;
    }
    a.lhs_var_ = variable(name);
    a.lhs_var_.set_type(vm.get_bare_type(name));
    pass = true;
  }
  boost::phoenix::function<set_lhs_var_assgn> set_lhs_var_assgn_f;
  
  void validate_assgn::operator()(assgn &a, bool &pass, const variable_map &vm,
                                  std::ostream &error_msgs) const {
    // validate lhs name + idxs
    std::string name = a.lhs_var_.name_;
    expression lhs_expr = expression(a.lhs_var_);
    bare_expr_type lhs_type = indexed_type(lhs_expr, a.idxs_);
  
    if (lhs_type.is_ill_formed_type()) {
      error_msgs << "Left-hand side indexing incompatible with variable."
                 << std::endl;
      pass = false;
      return;
    }
    if (a.is_simple_assignment()) {
      if (!has_same_shape(lhs_type, a.rhs_, name, "assignment", error_msgs)) {
        pass = false;
        return;
      }
      pass = true;
      return;
    } else {
      // compound operator-assignment
      std::string op_equals = a.op_;
      a.op_ = op_equals.substr(0, op_equals.size() - 1);
  
      if (lhs_type.array_dims() > 0) {
        error_msgs << "Cannot apply operator '" << op_equals
                   << "' to array variable; variable name = " << name << ".";
        error_msgs << std::endl;
        pass = false;
        return;
      }
  
      bare_expr_type rhs_type = a.rhs_.bare_type();
      if (lhs_type.is_primitive() && boost::algorithm::starts_with(a.op_, ".")) {
        error_msgs << "Cannot apply element-wise operation to scalar"
                   << "; compound operator is: " << op_equals << std::endl;
        pass = false;
        return;
      }
      if (lhs_type.is_primitive() && rhs_type.is_primitive()
          && (lhs_type.innermost_type().is_double_type()
              || lhs_type == rhs_type)) {
        pass = true;
        return;
      }
  
      std::string op_name;
      if (a.op_ == "+") {
        op_name = "add";
      } else if (a.op_ == "-") {
        op_name = "subtract";
      } else if (a.op_ == "*") {
        op_name = "multiply";
      } else if (a.op_ == "/") {
        op_name = "divide";
      } else if (a.op_ == "./") {
        op_name = "elt_divide";
      } else if (a.op_ == ".*") {
        op_name = "elt_multiply";
      }
      // check that "lhs <op> rhs" is valid stan::math function sig
      std::vector<bare_expr_type> arg_types;
      arg_types.push_back(bare_expr_type(lhs_type));
      arg_types.push_back(bare_expr_type(rhs_type));
      function_signature_t op_equals_sig(lhs_type, arg_types);
      if (!function_signatures::instance().is_defined(op_name, op_equals_sig)) {
        error_msgs << "Cannot apply operator '" << op_equals << "' to operands;"
                   << " left-hand side type = " << lhs_type
                   << "; right-hand side type=" << rhs_type << std::endl;
        pass = false;
        return;
      }
      a.op_name_ = op_name;
      pass = true;
    }
  }
  boost::phoenix::function<validate_assgn> validate_assgn_f;
  
  void validate_sample::operator()(sample &s, const variable_map &var_map,
                                   bool &pass, std::ostream &error_msgs) const {
    std::vector<bare_expr_type> arg_types;
    arg_types.push_back(s.expr_.bare_type());
    for (size_t i = 0; i < s.dist_.args_.size(); ++i) {
      arg_types.push_back(s.dist_.args_[i].bare_type());
    }
  
    std::string function_name(s.dist_.family_);
    std::string internal_function_name = get_prob_fun(function_name);
    s.is_discrete_ = function_signatures::instance().discrete_first_arg(
        internal_function_name);
  
    if (internal_function_name.size() == 0) {
      pass = false;
      error_msgs << "Unknown distribution name: " << function_name << std::endl;
      return;
    }
  
    if (!(ends_with("_lpdf", internal_function_name)
          || ends_with("_lpmf", internal_function_name)
          || ends_with("_log", internal_function_name))) {
      pass = false;
      error_msgs << "Probability function must end in _lpdf or _lpmf."
                 << " Found distribution family = " << function_name
                 << " with no corresponding probability function"
                 << " " << function_name << "_lpdf"
                 << ", " << function_name << "_lpmf"
                 << ", or " << function_name << "_log" << std::endl;
      return;
    }
  
    if ((internal_function_name.find("multiply_log") != std::string::npos)
        || (internal_function_name.find("binomial_coefficient_log")
            != std::string::npos)) {
      error_msgs << "Only distribution names can be used with"
                 << " sampling (~) notation; found non-distribution"
                 << " function: " << function_name << std::endl;
      pass = false;
      return;
    }
  
    if (internal_function_name.find("cdf_log") != std::string::npos) {
      error_msgs << "CDF and CCDF functions may not be used with"
                 << " sampling notation."
                 << " Use increment_log_prob(" << internal_function_name
                 << "(...)) instead." << std::endl;
      pass = false;
      return;
    }
  
    if (internal_function_name == "lkj_cov_log") {
      error_msgs << "Info: the lkj_cov_log() sampling distribution"
                 << " is deprecated.  It will be removed in Stan 3." << std::endl
                 << "Code LKJ covariance in terms of an lkj_corr()"
                 << " distribution on a correlation matrix"
                 << " and independent lognormals on the scales." << std::endl
                 << std::endl;
    }
  
    if (!is_double_return(internal_function_name, arg_types, error_msgs)) {
      error_msgs << "Real return type required for probability function."
                 << std::endl;
      pass = false;
      return;
    }
    // test for LHS not being purely a variable
    if (has_non_param_var(s.expr_, var_map)) {
      error_msgs << "Info:" << std::endl
                 << "Left-hand side of sampling statement (~) may contain a"
                 << " non-linear transform of a parameter or local variable."
                 << std::endl
                 << "If it does, you need to include a target += statement"
                 << " with the log absolute determinant of the Jacobian of"
                 << " the transform." << std::endl
                 << "Left-hand-side of sampling statement:" << std::endl
                 << "    " << s.expr_.to_string() << " ~ " << function_name
                 << "(...)" << std::endl;
    }
    // validate that variable and params are univariate if truncated
    if (s.truncation_.has_low() || s.truncation_.has_high()) {
      if (!is_univariate(s.expr_.bare_type())) {
        error_msgs << "Outcomes in truncated distributions"
                   << " must be univariate." << std::endl
                   << "  Found outcome expression: " << s.expr_.to_string()
                   << std::endl
                   << "  with non-univariate type: " << s.expr_.bare_type()
                   << std::endl;
        pass = false;
        return;
      }
      for (size_t i = 0; i < s.dist_.args_.size(); ++i)
        if (!is_univariate(s.dist_.args_[i].bare_type())) {
          error_msgs << "Parameters in truncated distributions"
                     << " must be univariate." << std::endl
                     << "  Found parameter expression: "
                     << s.dist_.args_[i].to_string() << std::endl
                     << "  with non-univariate type: "
                     << s.dist_.args_[i].bare_type() << std::endl;
          pass = false;
          return;
        }
    }
    if (s.truncation_.has_low()
        && !is_univariate(s.truncation_.low_.bare_type())) {
      error_msgs << "Lower bounds in truncated distributions"
                 << " must be univariate." << std::endl
                 << "  Found lower bound expression: "
                 << s.truncation_.low_.to_string() << std::endl
                 << "  with non-univariate type: "
                 << s.truncation_.low_.bare_type() << std::endl;
      pass = false;
      return;
    }
    if (s.truncation_.has_high()
        && !is_univariate(s.truncation_.high_.bare_type())) {
      error_msgs << "Upper bounds in truncated distributions"
                 << " must be univariate." << std::endl
                 << "  Found upper bound expression: "
                 << s.truncation_.high_.to_string() << std::endl
                 << "  with non-univariate type: "
                 << s.truncation_.high_.bare_type() << std::endl;
      pass = false;
      return;
    }
  
    // make sure CDFs or CCDFs exist with conforming signature
    // T[L, ]
    if (s.truncation_.has_low() && !s.truncation_.has_high()) {
      std::vector<bare_expr_type> arg_types_trunc(arg_types);
      arg_types_trunc[0] = s.truncation_.low_.bare_type();
      std::string function_name_ccdf = get_ccdf(s.dist_.family_);
      if (function_name_ccdf == s.dist_.family_
          || !is_double_return(function_name_ccdf, arg_types_trunc, error_msgs)) {
        error_msgs << "Lower truncation not defined for specified"
                   << " arguments to " << s.dist_.family_ << std::endl;
        pass = false;
        return;
      }
      if (!is_double_return(function_name_ccdf, arg_types, error_msgs)) {
        error_msgs << "Lower bound in truncation type does not match"
                   << " sampled variate in distribution's type" << std::endl;
        pass = false;
        return;
      }
    }
    // T[, H]
    if (!s.truncation_.has_low() && s.truncation_.has_high()) {
      std::vector<bare_expr_type> arg_types_trunc(arg_types);
      arg_types_trunc[0] = s.truncation_.high_.bare_type();
      std::string function_name_cdf = get_cdf(s.dist_.family_);
      if (function_name_cdf == s.dist_.family_
          || !is_double_return(function_name_cdf, arg_types_trunc, error_msgs)) {
        error_msgs << "Upper truncation not defined for"
                   << " specified arguments to " << s.dist_.family_ << std::endl;
  
        pass = false;
        return;
      }
      if (!is_double_return(function_name_cdf, arg_types, error_msgs)) {
        error_msgs << "Upper bound in truncation type does not match"
                   << " sampled variate in distribution's type" << std::endl;
        pass = false;
        return;
      }
    }
    // T[L, H]
    if (s.truncation_.has_low() && s.truncation_.has_high()) {
      std::vector<bare_expr_type> arg_types_trunc(arg_types);
      arg_types_trunc[0] = s.truncation_.low_.bare_type();
      std::string function_name_cdf = get_cdf(s.dist_.family_);
      if (function_name_cdf == s.dist_.family_
          || !is_double_return(function_name_cdf, arg_types_trunc, error_msgs)) {
        error_msgs << "Lower truncation not defined for specified"
                   << " arguments to " << s.dist_.family_ << std::endl;
        pass = false;
        return;
      }
      if (!is_double_return(function_name_cdf, arg_types, error_msgs)) {
        error_msgs << "Lower bound in truncation type does not match"
                   << " sampled variate in distribution's type" << std::endl;
        pass = false;
        return;
      }
    }
    pass = true;
  }
  boost::phoenix::function<validate_sample> validate_sample_f;
  
  void expression_as_statement::operator()(bool &pass,
                                           const stan::lang::expression &expr,
                                           std::stringstream &error_msgs) const {
    if (!(expr.bare_type().is_void_type())) {
      error_msgs << "Illegal statement beginning with non-void"
                 << " expression parsed as" << std::endl
                 << "  " << expr.to_string() << std::endl
                 << "Not a legal assignment, sampling, or function"
                 << " statement.  Note that" << std::endl
                 << "  * Assignment statements only allow variables"
                 << " (with optional indexes) on the left;" << std::endl
                 << "  * Sampling statements allow arbitrary"
                 << " value-denoting expressions on the left." << std::endl
                 << "  * Functions used as statements must be"
                 << " declared to have void returns" << std::endl
                 << std::endl;
      pass = false;
      return;
    }
    pass = true;
  }
  boost::phoenix::function<expression_as_statement> expression_as_statement_f;
  
  void unscope_locals::operator()(const std::vector<local_var_decl> &var_decls,
                                  variable_map &vm) const {
    for (size_t i = 0; i < var_decls.size(); ++i)
      vm.remove(var_decls[i].name());
  }
  boost::phoenix::function<unscope_locals> unscope_locals_f;
  
  void add_while_condition::operator()(while_statement &ws, const expression &e,
                                       bool &pass,
                                       std::stringstream &error_msgs) const {
    pass = e.bare_type().is_primitive();
    if (!pass) {
      error_msgs << "Conditions in while statement must be primitive"
                 << " int or real;"
                 << " found type=" << e.bare_type() << std::endl;
      return;
    }
    ws.condition_ = e;
  }
  boost::phoenix::function<add_while_condition> add_while_condition_f;
  
  void add_while_body::operator()(while_statement &ws, const statement &s) const {
    ws.body_ = s;
  }
  boost::phoenix::function<add_while_body> add_while_body_f;
  
  void add_loop_identifier::operator()(const std::string &name,
                                       const scope &var_scope,
                                       variable_map &vm) const {
    vm.add(name, var_decl(name, int_type()), scope(loop_identifier_origin, true));
  }
  boost::phoenix::function<add_loop_identifier> add_loop_identifier_f;
  
  void add_array_loop_identifier ::operator()(const stan::lang::expression &expr,
                                              std::string &name,
                                              const scope &var_scope, bool &pass,
                                              variable_map &vm) const {
    pass = expr.bare_type().is_array_type();
    if (pass)
      vm.add(name, var_decl(name, expr.bare_type().array_element_type()),
             scope(loop_identifier_origin, true));
  }
  boost::phoenix::function<add_array_loop_identifier> add_array_loop_identifier_f;
  
  void add_matrix_loop_identifier ::operator()(
      const stan::lang::expression &expr, std::string &name,
      const scope &var_scope, bool &pass, variable_map &vm,
      std::stringstream &error_msgs) const {
    pass = expr.bare_type().num_dims() > 0 && !(expr.bare_type().is_array_type());
    if (!pass) {
      error_msgs << "Loop must be over container or range." << std::endl;
      return;
    } else {
      vm.add(name, var_decl(name, double_type()),
             scope(loop_identifier_origin, true));
      pass = true;
    }
  }
  boost::phoenix::function<add_matrix_loop_identifier>
      add_matrix_loop_identifier_f;
  
  void store_loop_identifier::operator()(const std::string &name,
                                         std::string &name_local, bool &pass,
                                         variable_map &vm,
                                         std::stringstream &error_msgs) const {
    pass = !(vm.exists(name));
    if (!pass) {
      // avoid repeated error message due to backtracking
      if (error_msgs.str().find("Loop variable already declared.")
          == std::string::npos)
        error_msgs << "Loop variable already declared."
                   << " variable name=\"" << name << "\"" << std::endl;
    } else {
      name_local = name;
    }
  }
  boost::phoenix::function<store_loop_identifier> store_loop_identifier_f;
  
  void remove_loop_identifier::operator()(const std::string &name,
                                          variable_map &vm) const {
    vm.remove(name);
  }
  boost::phoenix::function<remove_loop_identifier> remove_loop_identifier_f;
  
  void validate_int_expr::operator()(const expression &expr, bool &pass,
                                     std::stringstream &error_msgs) const {
    if (!expr.bare_type().is_int_type()) {
      error_msgs << "Expression denoting integer required; found type="
                 << expr.bare_type() << std::endl;
      pass = false;
      return;
    }
    pass = true;
  }
  boost::phoenix::function<validate_int_expr> validate_int_expr_f;
  
  void deprecate_increment_log_prob::operator()(
      std::stringstream &error_msgs) const {
    error_msgs << "Info: increment_log_prob(...);"
               << " is deprecated and will be removed in the future." << std::endl
               << "  Use target += ...; instead." << std::endl;
  }
  boost::phoenix::function<deprecate_increment_log_prob>
      deprecate_increment_log_prob_f;
  
  void validate_allow_sample::operator()(const scope &var_scope, bool &pass,
                                         std::stringstream &error_msgs) const {
    pass = var_scope.allows_sampling();
    if (!pass)
      error_msgs << "Sampling statements (~) and increment_log_prob() are"
                 << std::endl
                 << "only allowed in the model block or lp functions."
                 << std::endl;
  }
  boost::phoenix::function<validate_allow_sample> validate_allow_sample_f;
  
  void validate_non_void_expression::operator()(const expression &e, bool &pass,
                                                std::ostream &error_msgs) const {
    pass = !e.bare_type().is_void_type();
    if (!pass)
      error_msgs << "Attempt to increment log prob with void expression"
                 << std::endl;
  }
  boost::phoenix::function<validate_non_void_expression>
      validate_non_void_expression_f;
  
  void add_literal_string::operator()(double_literal &lit,
                                      const pos_iterator_t &begin,
                                      const pos_iterator_t &end) const {
    lit.string_ = std::string(begin, end);
  }
  boost::phoenix::function<add_literal_string> add_literal_string_f;
  
  template <typename T, typename I>
  void add_line_number::operator()(T &line, const I &begin, const I &end) const {
    line.begin_line_ = get_line(begin);
    line.end_line_ = get_line(end);
  }
  boost::phoenix::function<add_line_number> add_line_number_f;
  
  template void add_line_number::operator()(block_var_decl &,
                                            const pos_iterator_t &begin,
                                            const pos_iterator_t &end) const;
  
  template void add_line_number::operator()(local_var_decl &,
                                            const pos_iterator_t &begin,
                                            const pos_iterator_t &end) const;
  
  template void add_line_number::operator()(statement &,
                                            const pos_iterator_t &begin,
                                            const pos_iterator_t &end) const;
  
  void set_void_return::operator()(return_statement &s) const {
    s = return_statement();
  }
  boost::phoenix::function<set_void_return> set_void_return_f;
  
  void set_no_op::operator()(no_op_statement &s) const { s = no_op_statement(); }
  boost::phoenix::function<set_no_op> set_no_op_f;
  
  void deprecated_integrate_ode::operator()(std::ostream &error_msgs) const {
    error_msgs << "Info: the integrate_ode() function is deprecated"
               << " in the Stan language; use the following functions"
               << " instead.\n"
               << " integrate_ode_rk45()"
               << " [explicit, order 5, for non-stiff problems]\n"
               << " integrate_ode_adams()"
               << " [implicit, up to order 12, for non-stiff problems]\n"
               << " integrate_ode_bdf()"
               << " [implicit, up to order 5, for stiff problems]." << std::endl;
  }
  boost::phoenix::function<deprecated_integrate_ode> deprecated_integrate_ode_f;
  
  template <class T>
  void validate_integrate_ode_non_control_args(const T &ode_fun,
                                               const variable_map &var_map,
                                               bool &pass,
                                               std::ostream &error_msgs) {
    pass = true;
  
    // ode_integrator requires function with signature
    // (real, real[ ], real[ ], data real[ ], data int[ ]): real[ ]"
  
    // TODO(mitzi)  names indicate status, but not flagged as data_only
    // instantiate ode fn arg types
    double_type t_double;
    bare_expr_type t_ar_double(bare_array_type(t_double, 1));
    bare_expr_type t_ar_double_data(bare_array_type(t_double, 1));
  
    int_type t_int_data;
    bare_expr_type t_ar_int_data(bare_array_type(t_int_data, 1));
  
    // validate ode fn signature
    bare_expr_type sys_result_type(t_ar_double);
    std::vector<bare_expr_type> sys_arg_types;
    sys_arg_types.push_back(t_double);
    sys_arg_types.push_back(t_ar_double);
    sys_arg_types.push_back(t_ar_double);
    sys_arg_types.push_back(t_ar_double_data);
    sys_arg_types.push_back(t_ar_int_data);
    function_signature_t system_signature(sys_result_type, sys_arg_types);
    if (!function_signatures::instance().is_defined(ode_fun.system_function_name_,
                                                    system_signature)) {
      error_msgs << "Wrong signature for function "
                 << ode_fun.integration_function_name_
                 << "; first argument must be "
                 << "the name of a function with signature"
                 << " (real, real[ ], real[ ], data real[ ], data int[ ]):"
                 << " real[ ]." << std::endl;
      pass = false;
    }
  
    // Stan lang integrate_ode takes 7 args:
    // fn_name, y0, t0, ts, theta, x_r, x_i
    // only y0, t0, ts, and theta can have params
    if (ode_fun.y0_.bare_type() != t_ar_double) {
      error_msgs << "Second argument to " << ode_fun.integration_function_name_
                 << " must have type real[ ]"
                 << "; found type = " << ode_fun.y0_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (!ode_fun.t0_.bare_type().is_primitive()) {
      error_msgs << "Third argument to " << ode_fun.integration_function_name_
                 << " must have type real"
                 << ";  found type = " << ode_fun.t0_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (ode_fun.ts_.bare_type() != t_ar_double) {
      error_msgs << "Fourth argument to " << ode_fun.integration_function_name_
                 << " must have type real[ ]"
                 << ";  found type = " << ode_fun.ts_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (ode_fun.theta_.bare_type() != t_ar_double) {
      error_msgs << "Fifth argument to " << ode_fun.integration_function_name_
                 << " must have type real[ ]"
                 << ";  found type = " << ode_fun.theta_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (ode_fun.x_.bare_type() != t_ar_double_data) {
      error_msgs << "Sixth argument to " << ode_fun.integration_function_name_
                 << " must have type data real[ ]"
                 << ";  found type = " << ode_fun.x_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (ode_fun.x_int_.bare_type() != t_ar_int_data) {
      error_msgs << "Seventh argument to " << ode_fun.integration_function_name_
                 << " must have type data int[ ]"
                 << ";  found type = " << ode_fun.x_int_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
  
    // test data-only variables do not have parameters (int locals OK)
    if (has_var(ode_fun.x_, var_map)) {
      error_msgs << "Sixth argument to " << ode_fun.integration_function_name_
                 << " (real data)"
                 << " must be data only and not reference parameters."
                 << std::endl;
      pass = false;
    }
  }
  
  void validate_integrate_ode::operator()(const integrate_ode &ode_fun,
                                          const variable_map &var_map, bool &pass,
                                          std::ostream &error_msgs) const {
    validate_integrate_ode_non_control_args(ode_fun, var_map, pass, error_msgs);
  }
  boost::phoenix::function<validate_integrate_ode> validate_integrate_ode_f;
  
  void validate_integrate_ode_control::operator()(
      const integrate_ode_control &ode_fun, const variable_map &var_map,
      bool &pass, std::ostream &error_msgs) const {
    validate_integrate_ode_non_control_args(ode_fun, var_map, pass, error_msgs);
    if (!ode_fun.rel_tol_.bare_type().is_primitive()) {
      error_msgs << "Eighth argument to " << ode_fun.integration_function_name_
                 << " (relative tolerance) must have type real or int;"
                 << " found type=" << ode_fun.rel_tol_.bare_type() << ". ";
      pass = false;
    }
    if (!ode_fun.abs_tol_.bare_type().is_primitive()) {
      error_msgs << "Ninth argument to " << ode_fun.integration_function_name_
                 << " (absolute tolerance) must have type real or int;"
                 << " found type=" << ode_fun.abs_tol_.bare_type() << ". ";
      pass = false;
    }
    if (!ode_fun.max_num_steps_.bare_type().is_primitive()) {
      error_msgs << "Tenth argument to " << ode_fun.integration_function_name_
                 << " (max steps) must have type real or int;"
                 << " found type=" << ode_fun.max_num_steps_.bare_type() << ". ";
      pass = false;
    }
  
    // test data-only variables do not have parameters (int locals OK)
    if (has_var(ode_fun.rel_tol_, var_map)) {
      error_msgs << "Eighth argument to " << ode_fun.integration_function_name_
                 << " (relative tolerance) must be data only"
                 << " and not depend on parameters.";
      pass = false;
    }
    if (has_var(ode_fun.abs_tol_, var_map)) {
      error_msgs << "Ninth argument to " << ode_fun.integration_function_name_
                 << " (absolute tolerance ) must be data only"
                 << " and not depend parameters.";
      pass = false;
    }
    if (has_var(ode_fun.max_num_steps_, var_map)) {
      error_msgs << "Tenth argument to " << ode_fun.integration_function_name_
                 << " (max steps) must be data only"
                 << " and not depend on parameters.";
      pass = false;
    }
  }
  boost::phoenix::function<validate_integrate_ode_control>
      validate_integrate_ode_control_f;
  
  template <class T>
  void validate_algebra_solver_non_control_args(const T &alg_fun,
                                                const variable_map &var_map,
                                                bool &pass,
                                                std::ostream &error_msgs) {
    pass = true;
    int_type t_int;
    double_type t_double;
    vector_type t_vector;
    bare_expr_type t_ar_int(bare_array_type(t_int, 1));
    bare_expr_type t_ar_double(bare_array_type(t_double, 1));
  
    bare_expr_type sys_result_type(t_vector);
    std::vector<bare_expr_type> sys_arg_types;
    sys_arg_types.push_back(t_vector);     // y
    sys_arg_types.push_back(t_vector);     // theta
    sys_arg_types.push_back(t_ar_double);  // x_r
    sys_arg_types.push_back(t_ar_int);     // x_i
    function_signature_t system_signature(sys_result_type, sys_arg_types);
  
    if (!function_signatures::instance().is_defined(alg_fun.system_function_name_,
                                                    system_signature)) {
      error_msgs << "Wrong signature for function "
                 << alg_fun.system_function_name_
                 << "; first argument to algebra_solver"
                 << " must be a function with signature"
                 << " (vector, vector, real[ ], int[ ]) : vector." << std::endl;
      pass = false;
    }
  
    // check solver function arg types
    if (alg_fun.y_.bare_type() != t_vector) {
      error_msgs << "Second argument to algebra_solver must have type vector"
                 << "; found type= " << alg_fun.y_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (alg_fun.theta_.bare_type() != t_vector) {
      error_msgs << "Third argument to algebra_solver must have type vector"
                 << ";  found type= " << alg_fun.theta_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (alg_fun.x_r_.bare_type() != t_ar_double) {
      error_msgs << "Fourth argument to algebra_solver must have type real[ ]"
                 << ";  found type= " << alg_fun.x_r_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (alg_fun.x_i_.bare_type() != t_ar_int) {
      error_msgs << "Fifth argument to algebra_solver must have type int[ ]"
                 << ";  found type= " << alg_fun.x_i_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
  
    // real data array cannot be param or x-formed param variable
    if (has_var(alg_fun.x_r_, var_map)) {
      error_msgs << "Fourth argument to algebra_solver"
                 << " must be data only (cannot reference parameters)."
                 << std::endl;
      pass = false;
    }
  }
  
  void validate_algebra_solver::operator()(const algebra_solver &alg_fun,
                                           const variable_map &var_map,
                                           bool &pass,
                                           std::ostream &error_msgs) const {
    validate_algebra_solver_non_control_args(alg_fun, var_map, pass, error_msgs);
  }
  boost::phoenix::function<validate_algebra_solver> validate_algebra_solver_f;
  
  void validate_algebra_solver_control::operator()(
      const algebra_solver_control &alg_fun, const variable_map &var_map,
      bool &pass, std::ostream &error_msgs) const {
    validate_algebra_solver_non_control_args(alg_fun, var_map, pass, error_msgs);
    if (!alg_fun.rel_tol_.bare_type().is_primitive()) {
      error_msgs << "Sixth argument to algebra_solver "
                 << " (relative tolerance) must have type real or int;"
                 << " found type=" << alg_fun.rel_tol_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (!alg_fun.fun_tol_.bare_type().is_primitive()) {
      error_msgs << "Seventh argument to algebra_solver "
                 << " (function tolerance) must have type real or int;"
                 << " found type=" << alg_fun.fun_tol_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
    if (!alg_fun.max_num_steps_.bare_type().is_primitive()) {
      error_msgs << "Eighth argument to algebra_solver"
                 << " (max number of steps) must have type real or int;"
                 << " found type=" << alg_fun.max_num_steps_.bare_type() << ". "
                 << std::endl;
      pass = false;
    }
  
    // control args cannot contain param variables
    if (has_var(alg_fun.rel_tol_, var_map)) {
      error_msgs << "Sixth argument to algebra_solver"
                 << " (relative tolerance) must be data only"
                 << " and not depend on parameters" << std::endl;
      pass = false;
    }
    if (has_var(alg_fun.fun_tol_, var_map)) {
      error_msgs << "Seventh argument to algebra_solver"
                 << " (function tolerance ) must be data only"
                 << " and not depend parameters" << std::endl;
      pass = false;
    }
    if (has_var(alg_fun.max_num_steps_, var_map)) {
      error_msgs << "Eighth argument to algebra_solver"
                 << " (max number of steps) must be data only"
                 << " and not depend on parameters" << std::endl;
      pass = false;
    }
  }
  boost::phoenix::function<validate_algebra_solver_control>
      validate_algebra_solver_control_f;
  
  void validate_integrate_1d::operator()(integrate_1d &fx,
                                         const variable_map &var_map, bool &pass,
                                         std::ostream &error_msgs) const {
    pass = true;
  
    // (1) name of function to integrate
    if (ends_with("_rng", fx.function_name_)) {
      error_msgs << "integrated function may not be an _rng function,"
                 << " found function name: " << fx.function_name_ << std::endl;
      pass = false;
    }
    double_type t_double;
    bare_expr_type sys_result_type(t_double);
    std::vector<bare_expr_type> sys_arg_types;
    sys_arg_types.push_back(bare_expr_type(t_double));
    sys_arg_types.push_back(bare_expr_type(t_double));
    sys_arg_types.push_back(bare_expr_type(bare_array_type(double_type(), 1)));
    sys_arg_types.push_back(bare_expr_type(bare_array_type(double_type(), 1)));
    sys_arg_types.push_back(bare_expr_type(bare_array_type(int_type(), 1)));
    function_signature_t system_signature(sys_result_type, sys_arg_types);
    if (!function_signatures::instance().is_defined(fx.function_name_,
                                                    system_signature)) {
      pass = false;
      error_msgs << "first argument to integrate_1d"
                 << " must be the name of a function with signature"
                 << " (real, real, real[], real[], int[]) : real " << std::endl;
    }
  
    // (2) lower bound of integration
    if (!fx.lb_.bare_type().is_primitive()) {
      pass = false;
      error_msgs << "second argument to integrate_1d, the lower bound of"
                 << " integration, must have type int or real;"
                 << " found type = " << fx.lb_.bare_type() << "." << std::endl;
    }
  
    // (3) lower bound of integration
    if (!fx.ub_.bare_type().is_primitive()) {
      pass = false;
      error_msgs << "third argument to integrate_1d, the upper bound of"
                 << " integration, must have type int or real;"
                 << " found type = " << fx.ub_.bare_type() << "." << std::endl;
    }
  
    // (4) parameters
    if (fx.theta_.bare_type() != bare_array_type(double_type(), 1)) {
      pass = false;
      error_msgs << "fourth argument to integrate_1d, the parameters,"
                 << " must have type real[];"
                 << " found type = " << fx.theta_.bare_type() << "." << std::endl;
    }
  
    // (5) real data
    if (fx.x_r_.bare_type() != bare_array_type(double_type(), 1)) {
      pass = false;
      error_msgs << "fifth argument to integrate_1d, the real data,"
                 << " must have type real[]; found type = " << fx.x_r_.bare_type()
                 << "." << std::endl;
    }
  
    // (6) int data
    if (fx.x_i_.bare_type() != bare_array_type(int_type(), 1)) {
      pass = false;
      error_msgs << "sixth argument to integrate_1d, the integer data,"
                 << " must have type int[]; found type = " << fx.x_i_.bare_type()
                 << "." << std::endl;
    }
  
    // (7) relative tolerance
    if (!fx.rel_tol_.bare_type().is_primitive()) {
      pass = false;
      error_msgs << "seventh argument to integrate_1d, relative tolerance,"
                 << " must be of type int or real;  found type = "
                 << fx.rel_tol_.bare_type() << "." << std::endl;
    }
  
    // test data-only variables do not have parameters (int locals OK)
    if (has_var(fx.x_r_, var_map)) {
      pass = false;
      error_msgs << "fifth argument to integrate_1d, the real data,"
                 << " must be data only and not reference parameters."
                 << std::endl;
    }
    if (has_var(fx.rel_tol_, var_map)) {
      pass = false;
      error_msgs << "seventh argument to integrate_1d, relative tolerance,"
                 << " must be data only and not reference parameters."
                 << std::endl;
    }
  }
  boost::phoenix::function<validate_integrate_1d> validate_integrate_1d_f;
  void validate_map_rect::operator()(map_rect &mr, const variable_map &var_map,
                                     bool &pass, std::ostream &error_msgs) const {
    pass = true;
  
    if (has_rng_lp_suffix(mr.fun_name_)) {
      error_msgs << "Mapped function cannot be an _rng or _lp function,"
                 << " found function name: " << mr.fun_name_ << std::endl;
      pass = false;
    }
  
    // mapped function signature
    // vector f(vector param_shared, vector param_local,
    //          real[] data_r, int[] data_i)
    int_type t_int;
    double_type t_double;
    vector_type t_vector;
    bare_expr_type t_ar_int(bare_array_type(t_int, 1));
    bare_expr_type t_2d_ar_int(bare_array_type(t_int, 2));
    bare_expr_type t_ar_double(bare_array_type(t_double, 1));
    bare_expr_type t_2d_ar_double(bare_array_type(t_double, 2));
    bare_expr_type t_ar_vector(bare_array_type(t_vector, 1));
    bare_expr_type shared_params_type(t_vector);
    bare_expr_type job_params_type(t_vector);
    bare_expr_type job_data_r_type(t_ar_double);
    bare_expr_type job_data_i_type(t_ar_int);
    bare_expr_type result_type(t_vector);
    std::vector<bare_expr_type> arg_types;
    arg_types.push_back(shared_params_type);
    arg_types.push_back(job_params_type);
    arg_types.push_back(job_data_r_type);
    arg_types.push_back(job_data_i_type);
    function_signature_t mapped_fun_signature(result_type, arg_types);
  
    // validate mapped function signature
    if (!function_signatures::instance().is_defined(mr.fun_name_,
                                                    mapped_fun_signature)) {
      error_msgs << "First argument to map_rect"
                 << " must be the name of a function with signature"
                 << " (vector, vector, real[ ], int[ ]) : vector." << std::endl;
      pass = false;
    }
  
    // shared parameters - vector
    if (mr.shared_params_.bare_type() != shared_params_type) {
      if (!pass)
        error_msgs << ";  ";
      error_msgs << "Second argument to map_rect must be of type vector."
                 << std::endl;
      pass = false;
    }
    // job-specific parameters - array of vectors (array elts map to arg2)
    if (mr.job_params_.bare_type() != t_ar_vector) {
      if (!pass)
        error_msgs << ";  ";
      error_msgs << "Third argument to map_rect must be of type vector[ ]"
                 << " (array of vectors)." << std::endl;
      pass = false;
    }
    // job-specific real data - 2-d array of double (array elts map to arg3)
    bare_expr_type job_data_rs_type(t_2d_ar_double);
    if (mr.job_data_r_.bare_type() != job_data_rs_type) {
      if (!pass)
        error_msgs << ";  ";
      error_msgs << "Fourth argument to map_rect must be of type real[ , ]"
                 << " (two dimensional array of reals)." << std::endl;
      pass = false;
    }
    // job-specific int data - 2-d array of int (array elts map to arg4)
    bare_expr_type job_data_is_type(t_2d_ar_int);
    if (mr.job_data_i_.bare_type() != job_data_is_type) {
      if (!pass)
        error_msgs << ";  ";
      error_msgs << "Fifth argument to map_rect must be of type int[ , ]"
                 << " (two dimensional array of integers)." << std::endl;
      pass = false;
    }
  
    // test data is data only
    if (has_var(mr.job_data_r_, var_map)) {
      if (!pass)
        error_msgs << ";  ";
      error_msgs << "Fourth argment to map_rect must be data only." << std::endl;
      pass = false;
    }
    if (has_var(mr.job_data_i_, var_map)) {
      if (!pass)
        error_msgs << ";  ";
      error_msgs << "Fifth argument to map_rect must be data only." << std::endl;
      pass = false;
    }
  
    if (pass)
      mr.register_id();
  }
  boost::phoenix::function<validate_map_rect> validate_map_rect_f;
  
  void set_fun_type_named::operator()(expression &fun_result, fun &fun,
                                      const scope &var_scope, bool &pass,
                                      const variable_map &var_map,
                                      std::ostream &error_msgs) const {
    if (fun.name_ == "get_lp")
      error_msgs << "Info: get_lp() function deprecated." << std::endl
                 << "  It will be removed in a future release." << std::endl
                 << "  Use target() instead." << std::endl;
    if (fun.name_ == "target")
      fun.name_ = "get_lp";  // for code gen and context validation
  
    std::vector<bare_expr_type> arg_types;
    for (size_t i = 0; i < fun.args_.size(); ++i)
      arg_types.push_back(fun.args_[i].bare_type());
  
    fun.type_ = function_signatures::instance().get_result_type(
        fun.name_, arg_types, error_msgs);
    if (fun.type_.is_ill_formed_type()) {
      pass = false;
      return;
    }
  
    // get function definition for this functiion
    std::vector<bare_expr_type> fun_arg_types;
    for (size_t i = 0; i < fun.args_.size(); ++i)
      fun_arg_types.push_back(arg_types[i]);
    function_signature_t sig(fun.type_, fun_arg_types);
    function_signature_t decl_sig
        = function_signatures::instance().get_definition(fun.name_, sig);
    if (!decl_sig.first.is_ill_formed_type()) {
      for (size_t i = 0; i < fun_arg_types.size(); ++i) {
        if (decl_sig.second[i].is_data() && has_var(fun.args_[i], var_map)) {
          error_msgs << "Function argument error, function: " << fun.name_
                     << ", argument: " << (i + 1) << " must be data only, "
                     << "found expression containing a parameter varaible."
                     << std::endl;
          pass = false;
          return;
        }
      }
    }
  
    // disjunction so only first match triggered
    deprecate_fun("binomial_coefficient_log", "lchoose", fun, error_msgs)
        || deprecate_fun("multiply_log", "lmultiply", fun, error_msgs)
        || deprecate_suffix("_cdf_log", "'_lcdf'", fun, error_msgs)
        || deprecate_suffix("_ccdf_log", "'_lccdf'", fun, error_msgs)
        || deprecate_suffix(
            "_log", "'_lpdf' for density functions or '_lpmf' for mass functions",
            fun, error_msgs);
  
    // use old function names for built-in prob funs
    if (!function_signatures::instance().has_user_defined_key(fun.name_)) {
      replace_suffix("_lpdf", "_log", fun);
      replace_suffix("_lpmf", "_log", fun);
      replace_suffix("_lcdf", "_cdf_log", fun);
      replace_suffix("_lccdf", "_ccdf_log", fun);
    }
    // know these are not user-defined`x
    replace_suffix("lmultiply", "multiply_log", fun);
    replace_suffix("lchoose", "binomial_coefficient_log", fun);
  
    if (has_rng_suffix(fun.name_)) {
      if (!(var_scope.allows_rng())) {
        error_msgs << "Random number generators only allowed in"
                   << " transformed data block, generated quantities block"
                   << " or user-defined functions with names ending in _rng"
                   << "; found function=" << fun.name_ << " in block=";
        print_scope(error_msgs, var_scope);
        error_msgs << std::endl;
        pass = false;
        return;
      }
    }
  
    if (has_lp_suffix(fun.name_) || fun.name_ == "target") {
      if (!(var_scope.allows_lp_fun())) {
        error_msgs << "Function target() or functions suffixed with _lp only"
                   << " allowed in transformed parameter block, model block"
                   << std::endl
                   << "or the body of a function with suffix _lp." << std::endl
                   << "Found function = "
                   << (fun.name_ == "get_lp" ? "target or get_lp" : fun.name_)
                   << " in block = ";
        print_scope(error_msgs, var_scope);
        error_msgs << std::endl;
        pass = false;
        return;
      }
    }
  
    if (fun.name_ == "abs" && fun.args_.size() > 0
        && fun.args_[0].bare_type().is_double_type()) {
      error_msgs << "Info: Function abs(real) is deprecated"
                 << " in the Stan language." << std::endl
                 << "         It will be removed in a future release."
                 << std::endl
                 << "         Use fabs(real) instead." << std::endl
                 << std::endl;
    }
  
    if (fun.name_ == "lkj_cov_log") {
      error_msgs << "Info: the lkj_cov_log() function"
                 << " is deprecated.  It will be removed in Stan 3." << std::endl
                 << "Code LKJ covariance in terms of an lkj_corr()"
                 << " distribution on a correlation matrix"
                 << " and independent lognormals on the scales." << std::endl
                 << std::endl;
    }
  
    if (fun.name_ == "if_else") {
      error_msgs << "Info: the if_else() function"
                 << " is deprecated.  "
                 << "Use the conditional operator '?:' instead." << std::endl;
    }
  
    // add namespace qualifier to avoid ambiguities w/ c math fns
    qualify_builtins(fun);
  
    fun_result = fun;
    pass = true;
  }
  boost::phoenix::function<set_fun_type_named> set_fun_type_named_f;
  
  void infer_array_expr_type::operator()(expression &e, array_expr &array_expr,
                                         const scope &var_scope, bool &pass,
                                         const variable_map &var_map,
                                         std::ostream &error_msgs) const {
    if (array_expr.args_.size() == 0) {
      // shouldn't occur, because of % operator used to construct it
      error_msgs << "Array expression found size 0, must be > 0";
      array_expr.type_ = ill_formed_type();
      pass = false;
      return;
    }
    bare_expr_type e_first(array_expr.args_[0].bare_type());
    for (size_t i = 1; i < array_expr.args_.size(); ++i) {
      bare_expr_type e_next(array_expr.args_[i].bare_type());
      if (e_first != e_next) {
        if (e_first.is_primitive() && e_next.is_primitive()) {
          e_first = double_type();
        } else {
          error_msgs << "Expressions for elements of array must have"
                     << " the same or promotable types; found"
                     << " first element type=" << e_first << "; type at position "
                     << i + 1 << "=" << e_next;
          array_expr.type_ = ill_formed_type();
          pass = false;
          return;
        }
      }
    }
    array_expr.type_ = bare_array_type(e_first);
    array_expr.array_expr_scope_ = var_scope;
    array_expr.has_var_ = has_var(array_expr, var_map);
    e = array_expr;
    pass = true;
  }
  boost::phoenix::function<infer_array_expr_type> infer_array_expr_type_f;
  
  void infer_vec_or_matrix_expr_type::operator()(
      expression &e, row_vector_expr &vec_expr, const scope &var_scope,
      bool &pass, const variable_map &var_map, std::ostream &error_msgs) const {
    if (vec_expr.args_.size() == 0) {
      // shouldn't occur, because of % operator used to construct it
      error_msgs << "Vector or matrix expression found size 0, must be > 0";
      pass = false;
      return;
    }
    bare_expr_type e_first = vec_expr.args_[0].bare_type();
    if (!(e_first.is_primitive() || e_first.is_row_vector_type())) {
      error_msgs << "Matrix expression elements must be type row_vector "
                 << "and row vector expression elements must be int "
                 << "or real, but found element of type " << e_first << std::endl;
      pass = false;
      return;
    }
    bool is_matrix_el = e_first.is_row_vector_type();
    for (size_t i = 1; i < vec_expr.args_.size(); ++i) {
      if (is_matrix_el && !vec_expr.args_[i].bare_type().is_row_vector_type()) {
        error_msgs << "Matrix expression elements must be type row_vector, "
                   << "but found element of type "
                   << vec_expr.args_[i].bare_type() << std::endl;
        pass = false;
        return;
      } else if (!is_matrix_el
                 && !(vec_expr.args_[i].bare_type().is_primitive())) {
        error_msgs << "Row vector expression elements must be int or real, "
                   << "but found element of type "
                   << vec_expr.args_[i].bare_type() << std::endl;
        pass = false;
        return;
      }
    }
    if (is_matrix_el) {
      // create matrix expr object
      matrix_expr me = matrix_expr(vec_expr.args_);
      me.matrix_expr_scope_ = var_scope;
      me.has_var_ = has_var(me, var_map);
      e = me;
    } else {
      vec_expr.row_vector_expr_scope_ = var_scope;
      vec_expr.has_var_ = has_var(vec_expr, var_map);
      e = vec_expr;
    }
    pass = true;
  }
  boost::phoenix::function<infer_vec_or_matrix_expr_type>
      infer_vec_or_matrix_expr_type_f;
  
  void exponentiation_expr::operator()(expression &expr1, const expression &expr2,
                                       const scope &var_scope, bool &pass,
                                       std::ostream &error_msgs) const {
    if (!expr1.bare_type().is_primitive() || !expr2.bare_type().is_primitive()) {
      error_msgs << "Arguments to ^ must be primitive (real or int)"
                 << "; cannot exponentiate " << expr1.bare_type() << " by "
                 << expr2.bare_type() << " in block=";
      print_scope(error_msgs, var_scope);
      error_msgs << std::endl;
      pass = false;
      return;
    }
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
    fun f("pow", args);
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
  }
  boost::phoenix::function<exponentiation_expr> exponentiation_f;
  
  void multiplication_expr::operator()(expression &expr1, const expression &expr2,
                                       std::ostream &error_msgs) const {
    if (expr1.bare_type().is_primitive() && expr2.bare_type().is_primitive()) {
      expr1 *= expr2;
      return;
    }
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
    fun f("multiply", args);
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
  }
  boost::phoenix::function<multiplication_expr> multiplication_f;
  
  void division_expr::operator()(expression &expr1, const expression &expr2,
                                 std::ostream &error_msgs) const {
    if (expr1.bare_type().is_primitive() && expr2.bare_type().is_primitive()
        && (expr1.bare_type().is_double_type()
            || expr2.bare_type().is_double_type())) {
      expr1 /= expr2;
      return;
    }
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
  
    if (expr1.bare_type().is_int_type() && expr2.bare_type().is_int_type()) {
      // result might be assigned to real - generate warning
      error_msgs << "Info: integer division"
                 << " implicitly rounds to integer."
                 << " Found int division: " << expr1.to_string() << " / "
                 << expr2.to_string() << std::endl
                 << " Positive values rounded down,"
                 << " negative values rounded up or down"
                 << " in platform-dependent way." << std::endl;
  
      fun f("divide", args);
      set_fun_type(f, error_msgs);
      expr1 = expression(f);
      return;
    }
    if ((expr1.bare_type().is_matrix_type()
         || expr1.bare_type().is_row_vector_type())
        && expr2.bare_type().is_matrix_type()) {
      fun f("mdivide_right", args);
      set_fun_type(f, error_msgs);
      expr1 = expression(f);
      return;
    }
    fun f("divide", args);
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
    return;
  }
  boost::phoenix::function<division_expr> division_f;
  
  void modulus_expr::operator()(expression &expr1, const expression &expr2,
                                bool &pass, std::ostream &error_msgs) const {
    if (!expr1.bare_type().is_int_type() && !expr2.bare_type().is_int_type()) {
      error_msgs << "Both operands of % must be int"
                 << "; cannot modulo " << expr1.bare_type() << " by "
                 << expr2.bare_type();
      error_msgs << std::endl;
      pass = false;
      return;
    }
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
    fun f("modulus", args);
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
  }
  boost::phoenix::function<modulus_expr> modulus_f;
  
  void left_division_expr::operator()(expression &expr1, bool &pass,
                                      const expression &expr2,
                                      std::ostream &error_msgs) const {
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
    if (expr1.bare_type().is_matrix_type()
        && (expr2.bare_type().is_vector_type()
            || expr2.bare_type().is_matrix_type())) {
      fun f("mdivide_left", args);
      set_fun_type(f, error_msgs);
      expr1 = expression(f);
      pass = true;
      return;
    }
    fun f("mdivide_left", args);  // set for alt args err msg
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
    pass = false;
  }
  boost::phoenix::function<left_division_expr> left_division_f;
  
  void elt_multiplication_expr::operator()(expression &expr1,
                                           const expression &expr2,
                                           std::ostream &error_msgs) const {
    if (expr1.bare_type().is_primitive() && expr2.bare_type().is_primitive()) {
      expr1 *= expr2;
      return;
    }
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
    fun f("elt_multiply", args);
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
  }
  boost::phoenix::function<elt_multiplication_expr> elt_multiplication_f;
  
  void elt_division_expr::operator()(expression &expr1, const expression &expr2,
                                     std::ostream &error_msgs) const {
    if (expr1.bare_type().is_primitive() && expr2.bare_type().is_primitive()) {
      expr1 /= expr2;
      return;
    }
    std::vector<expression> args;
    args.push_back(expr1);
    args.push_back(expr2);
    fun f("elt_divide", args);
    set_fun_type(f, error_msgs);
    expr1 = expression(f);
  }
  boost::phoenix::function<elt_division_expr> elt_division_f;
  
  void negate_expr::operator()(expression &expr_result, const expression &expr,
                               bool &pass, std::ostream &error_msgs) const {
    if (expr.bare_type().is_primitive()) {
      expr_result = expression(unary_op('-', expr));
      return;
    }
    std::vector<expression> args;
    args.push_back(expr);
    fun f("minus", args);
    set_fun_type(f, error_msgs);
    expr_result = expression(f);
  }
  boost::phoenix::function<negate_expr> negate_expr_f;
  
  void logical_negate_expr::operator()(expression &expr_result,
                                       const expression &expr,
                                       std::ostream &error_msgs) const {
    if (!expr.bare_type().is_primitive()) {
      error_msgs << "Logical negation operator !"
                 << " only applies to int or real types; ";
      expr_result = expression();
    }
    std::vector<expression> args;
    args.push_back(expr);
    fun f("logical_negation", args);
    set_fun_type(f, error_msgs);
    expr_result = expression(f);
  }
  boost::phoenix::function<logical_negate_expr> logical_negate_expr_f;
  
  void transpose_expr::operator()(expression &expr, bool &pass,
                                  std::ostream &error_msgs) const {
    if (expr.bare_type().is_primitive())
      return;
    std::vector<expression> args;
    args.push_back(expr);
    fun f("transpose", args);
    set_fun_type(f, error_msgs);
    expr = expression(f);
    pass = !expr.bare_type().is_ill_formed_type();
  }
  boost::phoenix::function<transpose_expr> transpose_f;
  
  void add_idxs::operator()(expression &e, std::vector<idx> &idxs, bool &pass,
                            std::ostream &error_msgs) const {
    e = index_op_sliced(e, idxs);
    pass = !e.bare_type().is_ill_formed_type();
    if (!pass)
      error_msgs << "Indexed expression must have at least as many"
                 << " dimensions as number of indexes supplied:" << std::endl
                 << " indexed expression dims=" << e.total_dims()
                 << "; num indexes=" << idxs.size() << std::endl;
  }
  boost::phoenix::function<add_idxs> add_idxs_f;
  
  void add_expression_dimss::operator()(
      expression &expression,
      std::vector<std::vector<stan::lang::expression>> &dimss, bool &pass,
      std::ostream &error_msgs) const {
    int expr_dims = expression.total_dims();
    int index_dims = num_dimss(dimss);
    if (expr_dims < index_dims) {
      error_msgs << "Too many indexes, expression dimensions=" << expr_dims
                 << ", indexes found=" << index_dims << std::endl;
      pass = false;
      return;
    }
    index_op iop(expression, dimss);
    iop.infer_type();
    if (iop.type_.is_ill_formed_type()) {
      error_msgs << "Indexed expression must have at least as many"
                 << " dimensions as number of indexes supplied." << std::endl;
      pass = false;
      return;
    }
    pass = true;
    expression = iop;
  }
  boost::phoenix::function<add_expression_dimss> add_expression_dimss_f;
  
  void set_var_type::operator()(variable &var_expr, expression &val,
                                variable_map &vm, std::ostream &error_msgs,
                                bool &pass) const {
    std::string name = var_expr.name_;
    if (name == std::string("lp__")) {
      error_msgs << std::endl
                 << "Error (fatal):  Use of lp__ is no longer supported."
                 << std::endl
                 << "  Use target += ... statement to increment log density."
                 << std::endl
                 << "  Use target() function to get log density." << std::endl;
      pass = false;
      return;
    } else if (name == std::string("params_r__")) {
      error_msgs << std::endl
                 << "Info:" << std::endl
                 << "  Direct access to params_r__ yields an inconsistent"
                 << " statistical model in isolation and no guarantee is"
                 << " made that this model will yield valid inferences."
                 << std::endl
                 << "  Moreover, access to params_r__ is unsupported"
                 << " and the variable may be removed without notice."
                 << std::endl;
    } else if (name == std::string("data") || name == std::string("generated")
               || name == std::string("model")
               || name == std::string("parameters")
               || name == std::string("transformed")) {
      error_msgs << std::endl
                 << "Unexpected open block, missing close block \"}\""
                 << " before keyword \"" << name << "\"." << std::endl;
      pass = false;
      return;
    }
    pass = vm.exists(name);
    if (pass) {
      var_expr.set_type(vm.get_bare_type(name));
    } else {
      error_msgs << "Variable \"" << name << '"' << " does not exist."
                 << std::endl;
      return;
    }
    val = expression(var_expr);
  }
  boost::phoenix::function<set_var_type> set_var_type_f;
  
  void require_vbar::operator()(bool &pass, std::ostream &error_msgs) const {
    pass = false;
    error_msgs << "Probabilty functions with suffixes _lpdf, _lpmf,"
               << " _lcdf, and _lccdf," << std::endl
               << "require a vertical bar (|) between the first two"
               << " arguments." << std::endl;
  }
  boost::phoenix::function<require_vbar> require_vbar_f;
  
  data_only_expression::data_only_expression(std::stringstream &error_msgs,
                                             variable_map &var_map)
      : error_msgs_(error_msgs), var_map_(var_map) {}
  bool data_only_expression::operator()(const nil & /*e*/) const { return true; }
  bool data_only_expression::operator()(const int_literal & /*x*/) const {
    return true;
  }
  bool data_only_expression::operator()(const double_literal & /*x*/) const {
    return true;
  }
  bool data_only_expression::operator()(const array_expr &x) const {
    for (size_t i = 0; i < x.args_.size(); ++i)
      if (!boost::apply_visitor(*this, x.args_[i].expr_))
        return false;
    return true;
  }
  bool data_only_expression::operator()(const matrix_expr &x) const {
    for (size_t i = 0; i < x.args_.size(); ++i)
      if (!boost::apply_visitor(*this, x.args_[i].expr_))
        return false;
    return true;
  }
  bool data_only_expression::operator()(const row_vector_expr &x) const {
    for (size_t i = 0; i < x.args_.size(); ++i)
      if (!boost::apply_visitor(*this, x.args_[i].expr_))
        return false;
    return true;
  }
  bool data_only_expression::operator()(const variable &x) const {
    scope var_scope = var_map_.get_scope(x.name_);
    bool is_data = var_scope.allows_size();
    if (!is_data) {
      error_msgs_ << "Non-data variables are not allowed"
                  << " in dimension declarations;"
                  << " found variable=" << x.name_ << "; declared in block=";
      print_scope(error_msgs_, var_scope);
      error_msgs_ << "." << std::endl;
    }
    return is_data;
  }
  bool data_only_expression::operator()(const integrate_1d &x) const {
    return boost::apply_visitor(*this, x.lb_.expr_)
           && boost::apply_visitor(*this, x.ub_.expr_)
           && boost::apply_visitor(*this, x.theta_.expr_);
  }
  bool data_only_expression::operator()(const integrate_ode &x) const {
    return boost::apply_visitor(*this, x.y0_.expr_)
           && boost::apply_visitor(*this, x.theta_.expr_);
  }
  bool data_only_expression::operator()(const integrate_ode_control &x) const {
    return boost::apply_visitor(*this, x.y0_.expr_)
           && boost::apply_visitor(*this, x.theta_.expr_);
  }
  bool data_only_expression::operator()(const algebra_solver &x) const {
    return boost::apply_visitor(*this, x.theta_.expr_);
  }
  bool data_only_expression::operator()(const algebra_solver_control &x) const {
    return boost::apply_visitor(*this, x.theta_.expr_);
  }
  bool data_only_expression::operator()(const map_rect &x) const {
    return boost::apply_visitor(*this, x.shared_params_.expr_)
           && boost::apply_visitor(*this, x.job_params_.expr_);
  }
  bool data_only_expression::operator()(const fun &x) const {
    for (size_t i = 0; i < x.args_.size(); ++i)
      if (!boost::apply_visitor(*this, x.args_[i].expr_))
        return false;
    return true;
  }
  bool data_only_expression::operator()(const index_op &x) const {
    if (!boost::apply_visitor(*this, x.expr_.expr_))
      return false;
    for (size_t i = 0; i < x.dimss_.size(); ++i)
      for (size_t j = 0; j < x.dimss_[i].size(); ++j)
        if (!boost::apply_visitor(*this, x.dimss_[i][j].expr_))
          return false;
    return true;
  }
  bool data_only_expression::operator()(const index_op_sliced &x) const {
    return boost::apply_visitor(*this, x.expr_.expr_);
  }
  bool data_only_expression::operator()(const conditional_op &x) const {
    return boost::apply_visitor(*this, x.cond_.expr_)
           && boost::apply_visitor(*this, x.true_val_.expr_)
           && boost::apply_visitor(*this, x.false_val_.expr_);
  }
  bool data_only_expression::operator()(const binary_op &x) const {
    return boost::apply_visitor(*this, x.left.expr_)
           && boost::apply_visitor(*this, x.right.expr_);
  }
  bool data_only_expression::operator()(const unary_op &x) const {
    return boost::apply_visitor(*this, x.subject.expr_);
  }
  
  template <typename T>
  void validate_definition::operator()(const scope &var_scope, const T &var_decl,
                                       bool &pass,
                                       std::stringstream &error_msgs) const {
    if (is_nil(var_decl.def()))
      return;
  
    // validate that assigment is allowed in this block
    if (!var_scope.allows_assignment()) {
      error_msgs << "Variable definition not possible in this block."
                 << std::endl;
      pass = false;
    }
  
    // validate type
    bare_expr_type decl_type(var_decl.bare_type());
    bare_expr_type def_type = var_decl.def().bare_type();
  
    bool types_compatible
        = (decl_type == def_type)
          || (decl_type.is_primitive() && def_type.is_primitive()
              && decl_type.is_double_type() && def_type.is_int_type());
  
    if (!types_compatible) {
      error_msgs << "Variable definition base type mismatch,"
                 << " variable declared as base type ";
      write_bare_expr_type(error_msgs, decl_type);
      error_msgs << " variable definition has base type ";
      write_bare_expr_type(error_msgs, def_type);
      pass = false;
    }
    // validate dims
    if (decl_type.num_dims() != def_type.num_dims()) {
      error_msgs << "Variable definition dimensions mismatch,"
                 << " definition specifies " << decl_type.num_dims()
                 << ", declaration specifies " << def_type.num_dims();
      pass = false;
    }
    return;
  }
  boost::phoenix::function<validate_definition> validate_definition_f;
  
  template void validate_definition::operator()(
      const scope &var_scope, const block_var_decl &var_decl, bool &pass,
      std::stringstream &error_msgs) const;
  
  template void validate_definition::operator()(
      const scope &var_scope, const local_var_decl &var_decl, bool &pass,
      std::stringstream &error_msgs) const;
  
  template void validate_definition::operator()(
      const scope &var_scope, const var_decl &var_decl, bool &pass,
      std::stringstream &error_msgs) const;
  
  void validate_identifier::reserve(const std::string &w) {
    reserved_word_set_.insert(w);
  }
  bool validate_identifier::contains(const std::set<std::string> &s,
                                     const std::string &x) const {
    return s.find(x) != s.end();
  }
  bool validate_identifier::identifier_exists(
      const std::string &identifier) const {
    return contains(reserved_word_set_, identifier)
           || (contains(function_signatures::instance().key_set(), identifier)
               && !contains(const_fun_name_set_, identifier));
  }
  
  validate_identifier::validate_identifier() {
    // constant functions which may be used as identifiers
    const_fun_name_set_.insert("pi");
    const_fun_name_set_.insert("e");
    const_fun_name_set_.insert("sqrt2");
    const_fun_name_set_.insert("log2");
    const_fun_name_set_.insert("log10");
    const_fun_name_set_.insert("not_a_number");
    const_fun_name_set_.insert("positive_infinity");
    const_fun_name_set_.insert("negative_infinity");
    const_fun_name_set_.insert("epsilon");
    const_fun_name_set_.insert("negative_epsilon");
    const_fun_name_set_.insert("machine_precision");
  
    // illegal identifiers
    reserve("for");
    reserve("in");
    reserve("while");
    reserve("repeat");
    reserve("until");
    reserve("if");
    reserve("then");
    reserve("else");
    reserve("true");
    reserve("false");
  
    reserve("int");
    reserve("real");
    reserve("vector");
    reserve("unit_vector");
    reserve("simplex");
    reserve("ordered");
    reserve("positive_ordered");
    reserve("row_vector");
    reserve("matrix");
    reserve("cholesky_factor_cov");
    reserve("cholesky_factor_corr");
    reserve("cov_matrix");
    reserve("corr_matrix");
  
    reserve("target");
  
    reserve("model");
    reserve("data");
    reserve("parameters");
    reserve("quantities");
    reserve("transformed");
    reserve("generated");
  
    reserve("var");
    reserve("fvar");
    reserve("STAN_MAJOR");
    reserve("STAN_MINOR");
    reserve("STAN_PATCH");
    reserve("STAN_MATH_MAJOR");
    reserve("STAN_MATH_MINOR");
    reserve("STAN_MATH_PATCH");
  
    reserve("alignas");
    reserve("alignof");
    reserve("and");
    reserve("and_eq");
    reserve("asm");
    reserve("auto");
    reserve("bitand");
    reserve("bitor");
    reserve("bool");
    reserve("break");
    reserve("case");
    reserve("catch");
    reserve("char");
    reserve("char16_t");
    reserve("char32_t");
    reserve("class");
    reserve("compl");
    reserve("const");
    reserve("constexpr");
    reserve("const_cast");
    reserve("continue");
    reserve("decltype");
    reserve("default");
    reserve("delete");
    reserve("do");
    reserve("double");
    reserve("dynamic_cast");
    reserve("else");
    reserve("enum");
    reserve("explicit");
    reserve("export");
    reserve("extern");
    reserve("false");
    reserve("float");
    reserve("for");
    reserve("friend");
    reserve("goto");
    reserve("if");
    reserve("inline");
    reserve("int");
    reserve("long");
    reserve("mutable");
    reserve("namespace");
    reserve("new");
    reserve("noexcept");
    reserve("not");
    reserve("not_eq");
    reserve("nullptr");
    reserve("operator");
    reserve("or");
    reserve("or_eq");
    reserve("private");
    reserve("protected");
    reserve("public");
    reserve("register");
    reserve("reinterpret_cast");
    reserve("return");
    reserve("short");
    reserve("signed");
    reserve("sizeof");
    reserve("static");
    reserve("static_assert");
    reserve("static_cast");
    reserve("struct");
    reserve("switch");
    reserve("template");
    reserve("this");
    reserve("thread_local");
    reserve("throw");
    reserve("true");
    reserve("try");
    reserve("typedef");
    reserve("typeid");
    reserve("typename");
    reserve("union");
    reserve("unsigned");
    reserve("using");
    reserve("virtual");
    reserve("void");
    reserve("volatile");
    reserve("wchar_t");
    reserve("while");
    reserve("xor");
    reserve("xor_eq");
  
    // function names declared in signatures
    using stan::lang::function_signatures;
    using std::set;
    using std::string;
    const function_signatures &sigs = function_signatures::instance();
  
    set<string> fun_names = sigs.key_set();
    for (set<string>::iterator it = fun_names.begin(); it != fun_names.end();
         ++it)
      if (!contains(const_fun_name_set_, *it))
        reserve(*it);
  }
  
  // validates identifier shape
  void validate_identifier::operator()(const std::string &identifier, bool &pass,
                                       std::stringstream &error_msgs) const {
    int len = identifier.size();
    if (len >= 2 && identifier[len - 1] == '_' && identifier[len - 2] == '_') {
      error_msgs << "Variable identifier (name) may"
                 << " not end in double underscore (__)" << std::endl
                 << "    found identifer=" << identifier << std::endl;
      pass = false;
      return;
    }
    size_t period_position = identifier.find('.');
    if (period_position != std::string::npos) {
      error_msgs << "Variable identifier may not contain a period (.)"
                 << std::endl
                 << "    found period at position (indexed from 0)="
                 << period_position << std::endl
                 << "    found identifier=" << identifier << std::endl;
      pass = false;
      return;
    }
    if (identifier_exists(identifier)) {
      error_msgs << "Variable identifier (name) may not be reserved word"
                 << std::endl
                 << "    found identifier=" << identifier << std::endl;
      pass = false;
      return;
    }
    pass = true;
  }
  boost::phoenix::function<validate_identifier> validate_identifier_f;
  
  // copies single dimension from M to N if only M declared
  void copy_square_cholesky_dimension_if_necessary::operator()(
      cholesky_factor_cov_block_type &block_type) const {
    if (is_nil(block_type.N_))
      block_type.N_ = block_type.M_;
  }
  boost::phoenix::function<copy_square_cholesky_dimension_if_necessary>
      copy_square_cholesky_dimension_if_necessary_f;
  
  void empty_range::operator()(range &r,
                               std::stringstream & /*error_msgs*/) const {
    r = range();
  }
  boost::phoenix::function<empty_range> empty_range_f;
  
  void empty_offset_multiplier::operator()(
      offset_multiplier &r, std::stringstream & /*error_msgs*/) const {
    r = offset_multiplier();
  }
  boost::phoenix::function<empty_offset_multiplier> empty_offset_multiplier_f;
  
  void set_int_range_lower::operator()(range &range, const expression &expr,
                                       bool &pass,
                                       std::stringstream &error_msgs) const {
    range.low_ = expr;
    validate_int_expr validator;
    validator(expr, pass, error_msgs);
  }
  boost::phoenix::function<set_int_range_lower> set_int_range_lower_f;
  
  void set_int_range_upper::operator()(range &range, const expression &expr,
                                       bool &pass,
                                       std::stringstream &error_msgs) const {
    range.high_ = expr;
    validate_int_expr validator;
    validator(expr, pass, error_msgs);
  }
  boost::phoenix::function<set_int_range_upper> set_int_range_upper_f;
  
  void validate_int_data_only_expr::operator()(
      const expression &expr, bool &pass, variable_map &var_map,
      std::stringstream &error_msgs) const {
    if (!expr.bare_type().is_int_type()) {
      error_msgs << "Dimension declaration requires expression"
                 << " denoting integer; found type=" << expr.bare_type()
                 << std::endl;
      pass = false;
      return;
    }
    data_only_expression vis(error_msgs, var_map);
    bool only_data_dimensions = boost::apply_visitor(vis, expr.expr_);
    pass = only_data_dimensions;
    return;
  }
  boost::phoenix::function<validate_int_data_only_expr>
      validate_int_data_only_expr_f;
  
  void set_double_range_lower::operator()(range &range, const expression &expr,
                                          bool &pass,
                                          std::stringstream &error_msgs) const {
    range.low_ = expr;
    validate_double_expr validator;
    validator(expr, pass, error_msgs);
  }
  boost::phoenix::function<set_double_range_lower> set_double_range_lower_f;
  
  void set_double_range_upper::operator()(range &range, const expression &expr,
                                          bool &pass,
                                          std::stringstream &error_msgs) const {
    range.high_ = expr;
    validate_double_expr validator;
    validator(expr, pass, error_msgs);
  }
  boost::phoenix::function<set_double_range_upper> set_double_range_upper_f;
  
  void set_double_offset_multiplier_loc::operator()(
      offset_multiplier &offset_multiplier, const expression &expr, bool &pass,
      std::stringstream &error_msgs) const {
    offset_multiplier.offset_ = expr;
    validate_double_expr validator;
    validator(expr, pass, error_msgs);
  }
  boost::phoenix::function<set_double_offset_multiplier_loc>
      set_double_offset_multiplier_offset_f;
  
  void set_double_offset_multiplier_multiplier::operator()(
      offset_multiplier &offset_multiplier, const expression &expr, bool &pass,
      std::stringstream &error_msgs) const {
    offset_multiplier.multiplier_ = expr;
    validate_double_expr validator;
    validator(expr, pass, error_msgs);
  }
  boost::phoenix::function<set_double_offset_multiplier_multiplier>
      set_double_offset_multiplier_multiplier_f;
  
  void validate_array_block_var_decl::operator()(
      block_var_decl &var_decl_result, const block_var_type &el_type,
      const std::string &name, const std::vector<expression> &dims,
      const expression &def, bool &pass, std::ostream &error_msgs) const {
    if (dims.size() == 0) {
      error_msgs << "Array type requires at least 1 dimension,"
                 << " none found" << std::endl;
      pass = false;
      return;
    }
    if (el_type.bare_type().is_ill_formed_type()) {
      error_msgs << "Array variable declaration is ill formed,"
                 << " variable name=" << name << std::endl;
      pass = false;
      return;
    }
    stan::lang::block_array_type bat(el_type, dims);
    block_var_decl result(name, bat, def);
    var_decl_result = result;
  }
  boost::phoenix::function<validate_array_block_var_decl>
      validate_array_block_var_decl_f;
  
  void validate_single_block_var_decl::operator()(
      const block_var_decl &var_decl, bool &pass,
      std::ostream &error_msgs) const {
    if (var_decl.bare_type().is_ill_formed_type()) {
      error_msgs << "Variable declaration is ill formed,"
                 << " variable name=" << var_decl.name() << std::endl;
      pass = false;
      return;
    }
  }
  boost::phoenix::function<validate_single_block_var_decl>
      validate_single_block_var_decl_f;
  
  void validate_single_local_var_decl::operator()(
      const local_var_decl &var_decl, bool &pass,
      std::ostream &error_msgs) const {
    if (var_decl.bare_type().is_ill_formed_type()) {
      error_msgs << "Variable declaration is ill formed,"
                 << " variable name=" << var_decl.name() << std::endl;
      pass = false;
      return;
    }
  }
  boost::phoenix::function<validate_single_local_var_decl>
      validate_single_local_var_decl_f;
  
  void validate_array_local_var_decl::operator()(
      local_var_decl &var_decl_result, const local_var_type &el_type,
      const std::string &name, const std::vector<expression> &dims,
      const expression &def, bool &pass, std::ostream &error_msgs) const {
    if (dims.size() == 0) {
      error_msgs << "Array type requires at least 1 dimension,"
                 << " none found" << std::endl;
      pass = false;
      return;
    }
    if (el_type.bare_type().is_ill_formed_type()) {
      error_msgs << "Array variable declaration is ill formed,"
                 << " variable name=" << name << std::endl;
      pass = false;
      return;
    }
    stan::lang::local_array_type bat(el_type, dims);
    local_var_decl result(name, bat, def);
    var_decl_result = result;
  }
  boost::phoenix::function<validate_array_local_var_decl>
      validate_array_local_var_decl_f;
  
  void validate_fun_arg_var::operator()(var_decl &var_decl_result,
                                        const bare_expr_type &bare_type,
                                        const std::string &name, bool &pass,
                                        std::ostream &error_msgs) const {
    if (bare_type.is_ill_formed_type()) {
      error_msgs << "Function argument is ill formed,"
                 << " name=" << name << std::endl;
      pass = false;
      return;
    }
    stan::lang::var_decl vd(name, bare_type);
    var_decl_result = vd;
  }
  boost::phoenix::function<validate_fun_arg_var> validate_fun_arg_var_f;
  
  void validate_bare_type::operator()(bare_expr_type &bare_type_result,
                                      const bare_expr_type &el_type,
                                      const size_t &num_dims, bool &pass,
                                      std::ostream &error_msgs) const {
    if (el_type.is_ill_formed_type()) {
      error_msgs << "Ill-formed bare type" << std::endl;
      pass = false;
      return;
    }
    pass = true;
    if (num_dims == 0) {
      bare_type_result = el_type;
      return;
    }
    stan::lang::bare_array_type bat(el_type);
    for (size_t i = 0; i < num_dims - 1; ++i) {
      stan::lang::bare_expr_type cur_type(bat);
      bat = bare_array_type(cur_type);
    }
    bare_type_result = bat;
  }
  boost::phoenix::function<validate_bare_type> validate_bare_type_f;
  
  template <typename T>
  void add_to_var_map::operator()(const T &decl, variable_map &vm, bool &pass,
                                  const scope &var_scope,
                                  std::ostream &error_msgs) const {
    pass = false;
    if (vm.exists(decl.name())) {
      var_decl prev_decl = vm.get(decl.name());
      error_msgs << "Duplicate declaration of variable, name=" << decl.name();
  
      error_msgs << "; attempt to redeclare as " << decl.bare_type() << " in ";
      print_scope(error_msgs, var_scope);
  
      error_msgs << "; previously declared as " << prev_decl.bare_type()
                 << " in ";
      print_scope(error_msgs, vm.get_scope(decl.name()));
  
      error_msgs << std::endl;
      pass = false;
      return;
    }
    if (var_scope.par_or_tpar()
        && decl.bare_type().innermost_type().is_int_type()) {
      error_msgs << "Parameters or transformed parameters"
                 << " cannot be integer or integer array; "
                 << " found int variable declaration, name=" << decl.name()
                 << std::endl;
      pass = false;
      return;
    }
    var_decl bare_decl(decl.name(), decl.type().bare_type(), decl.def());
  
    vm.add(decl.name(), bare_decl, var_scope);
    pass = true;
  }
  boost::phoenix::function<add_to_var_map> add_to_var_map_f;
  
  template void add_to_var_map::operator()(const block_var_decl &decl,
                                           variable_map &vm, bool &pass,
                                           const scope &var_scope,
                                           std::ostream &error_msgs) const;
  
  template void add_to_var_map::operator()(const local_var_decl &decl,
                                           variable_map &vm, bool &pass,
                                           const scope &var_scope,
                                           std::ostream &error_msgs) const;
  
  void validate_in_loop::operator()(bool in_loop, bool &pass,
                                    std::ostream &error_msgs) const {
    pass = in_loop;
    if (!pass)
      error_msgs << "Break and continue statements are only allowed"
                 << " in the body of a for-loop or while-loop." << std::endl;
  }
  boost::phoenix::function<validate_in_loop> validate_in_loop_f;
  
  void non_void_expression::operator()(const expression &e, bool &pass,
                                       std::ostream &error_msgs) const {
    // ill-formed shouldn't be possible, but just in case
    pass = !(e.bare_type().is_void_type() || e.bare_type().is_ill_formed_type());
    if (!pass)
      error_msgs << "Error: expected printable (non-void) expression."
                 << std::endl;
  }
  boost::phoenix::function<non_void_expression> non_void_expression_f;
  
  void set_var_scope::operator()(scope &var_scope,
                                 const origin_block &program_block) const {
    var_scope = scope(program_block);
  }
  boost::phoenix::function<set_var_scope> set_var_scope_f;
  
  void set_data_origin::operator()(scope &var_scope) const {
    var_scope = scope(data_origin);
  }
  boost::phoenix::function<set_data_origin> set_data_origin_f;
  
  void set_var_scope_local::operator()(scope &var_scope,
                                       const origin_block &program_block) const {
    var_scope = scope(program_block, true);
  }
  boost::phoenix::function<set_var_scope_local> set_var_scope_local_f;
  
  void reset_var_scope::operator()(scope &var_scope,
                                   const scope &scope_enclosing) const {
    origin_block enclosing_block = scope_enclosing.program_block();
    var_scope = scope(enclosing_block, true);
  }
  boost::phoenix::function<reset_var_scope> reset_var_scope_f;
  
  // only used to debug grammars
  void trace::operator()(const std::string &msg) const {
    //      std::cout << msg << std::endl;
  }
  boost::phoenix::function<trace> trace_f;
  
  // only used to debug grammars
  void trace_pass::operator()(const std::string &msg, const bool &pass) const {
    //      std::cout << msg << " pass? " << pass << std::endl;
  }
  boost::phoenix::function<trace_pass> trace_pass_f;
  
  void deprecate_pound_comment::operator()(std::ostream &error_msgs) const {
    error_msgs << "Info: Comments beginning with #"
               << " are deprecated.  Please use // in place of #"
               << " for line comments." << std::endl;
  }
  boost::phoenix::function<deprecate_pound_comment> deprecate_pound_comment_f;
  
  }  // namespace lang
  }  // namespace stan
  #endif
  