/*
  This file is part of CDO. CDO is a collection of Operators to
  manipulate and analyse Climate model Data.

  Copyright (C) 2003-2020 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.de>
  See COPYING file for copying and redistribution conditions.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
*/

#include <cstdlib>
#include <cassert>
#include <cdi.h>

#include "cdo_options.h"
#include "dmemory.h"
#include "process_int.h"
#include "field.h"
#include "expr.h"
#include "expr_fun.h"
#include "expr_yacc.h"
#include "cdo_zaxis.h"

static const char *const ExIn[] = { "expr", "init" };
static const char *const tmpvnm = "_tmp_";
static int pointID = -1;
static int zonalID = -1;
static int surfaceID = -1;

enum
{
  FT_STD,
  FT_CONST,
  FT_FLD,
  FT_ZON,
  FT_VERT,
  FT_COORD,
  FT_1C,
  FT_2C,
  FT_0
};

// clang-format off
static constexpr double COMPLT(const double x, const double y) noexcept { return x < y; }
static constexpr double COMPGT(const double x, const double y) noexcept { return x > y; }
static constexpr double COMPLE(const double x, const double y) noexcept { return x <= y; }
static constexpr double COMPGE(const double x, const double y) noexcept { return x >= y; }
static constexpr double COMPNE(const double x, const double y) noexcept { return IS_NOT_EQUAL(x, y); }
static constexpr double COMPEQ(const double x, const double y) noexcept { return IS_EQUAL(x, y); }
static constexpr double COMPLEG(const double x, const double y) noexcept { return (x < y) ? -1. : (x > y); }
static constexpr double COMPAND(const double x, const double y) noexcept { return IS_NOT_EQUAL(x, 0) && IS_NOT_EQUAL(y, 0); }
static constexpr double COMPOR(const double x, const double y) noexcept { return IS_NOT_EQUAL(x, 0) || IS_NOT_EQUAL(y, 0); }
static constexpr double COMPNOT(const double x) noexcept { return IS_EQUAL(x, 0); }
static inline    double MVCOMPLT(const double x, const double y, const double m) noexcept { return DBL_IS_EQUAL(x, m) ? m : COMPLT(x, y); }
static inline    double MVCOMPGT(const double x, const double y, const double m) noexcept { return DBL_IS_EQUAL(x, m) ? m : COMPGT(x, y); }
static inline    double MVCOMPLE(const double x, const double y, const double m) noexcept { return DBL_IS_EQUAL(x, m) ? m : COMPLE(x, y); }
static inline    double MVCOMPGE(const double x, const double y, const double m) noexcept { return DBL_IS_EQUAL(x, m) ? m : COMPGE(x, y); }
static inline    double MVCOMPNE(const double x, const double y, const double m) noexcept { return DBL_IS_EQUAL(x, m) ? m : COMPNE(x, y); }
static inline    double MVCOMPEQ(const double x, const double y, const double m) noexcept { return DBL_IS_EQUAL(x, m) ? m : COMPEQ(x, y); }
static inline    double MVCOMPLEG(const double x, const double y, const double m) noexcept { return DBL_IS_EQUAL(x, m) ? m : COMPLEG(x, y); }
static inline    double MVCOMPAND(const double x, const double y, const double m) noexcept { return DBL_IS_EQUAL(x, m) ? m : COMPAND(x, y); }
static inline    double MVCOMPOR(const double x, const double y, const double m) noexcept { return DBL_IS_EQUAL(x, m) ? m : COMPOR(x, y); }

static double f_float(const double x) { return (float) (x); }
static double f_int(const double x) { return (int) (x); }
static double f_nint(const double x) { return std::round(x); }
static double f_rand(const double x) { (void)x; return ((double) std::rand()) / ((double) RAND_MAX); }
static double f_sqr(const double x) { return x * x; }
static double f_rad(const double x) { return x * M_PI / 180.; }
static double f_deg(const double x) { return x * 180. / M_PI; }
static double f_isMissval(const double x) { (void)x; return 0; }
static double pt_ngp(const paramType *const p) { return p->ngp; }
static double pt_nlev(const paramType *const p) { return p->nlev; }
static double pt_size(const paramType *const p) { return p->ngp * p->nlev; }
static double pt_missval(const paramType *const p) { return p->missval; }
static double ts_ctimestep(const double *const data) { return std::lround(data[CTIMESTEP]); }
static double ts_cdate(const double *const data) { return std::lround(data[CDATE]); }
static double ts_ctime(const double *const data) { return std::lround(data[CTIME]); }
static double ts_cdeltat(const double *const data) { return data[CDELTAT]; }
static double ts_cday(const double *const data) { return data[CDAY]; }
static double ts_cmonth(const double *const data) { return data[CMONTH]; }
static double ts_cyear(const double *const data) { return data[CYEAR]; }
static double ts_csecond(const double *const data) { return data[CSECOND]; }
static double ts_cminute(const double *const data) { return data[CMINUTE]; }
static double ts_chour(const double *const data) { return data[CHOUR]; }
// clang-format on

struct func_t
{
  int type;
  int flag;
  const char *name;    // function name
  void (*func)(void);  // pointer to function
};

static const func_t fun_sym_tbl[] = {
  // scalar functions
  { FT_STD, 0, "abs", (void (*)(void))((double (*)(double)) fabs) },  // math functions could be inlined
  { FT_STD, 0, "floor", (void (*)(void))((double (*)(double)) floor) },
  { FT_STD, 0, "ceil", (void (*)(void))((double (*)(double)) ceil) },
  { FT_STD, 0, "sqrt", (void (*)(void))((double (*)(double)) sqrt) },
  { FT_STD, 0, "exp", (void (*)(void))((double (*)(double)) exp) },
  { FT_STD, 0, "erf", (void (*)(void))((double (*)(double)) erf) },
  { FT_STD, 0, "log", (void (*)(void))((double (*)(double)) log) },
  { FT_STD, 0, "ln", (void (*)(void))((double (*)(double)) log) },
  { FT_STD, 0, "log10", (void (*)(void))((double (*)(double)) log10) },
  { FT_STD, 0, "sin", (void (*)(void))((double (*)(double)) sin) },
  { FT_STD, 0, "cos", (void (*)(void))((double (*)(double)) cos) },
  { FT_STD, 0, "tan", (void (*)(void))((double (*)(double)) tan) },
  { FT_STD, 0, "sinh", (void (*)(void))((double (*)(double)) sinh) },
  { FT_STD, 0, "cosh", (void (*)(void))((double (*)(double)) cosh) },
  { FT_STD, 0, "tanh", (void (*)(void))((double (*)(double)) tanh) },
  { FT_STD, 0, "asin", (void (*)(void))((double (*)(double)) asin) },
  { FT_STD, 0, "acos", (void (*)(void))((double (*)(double)) acos) },
  { FT_STD, 0, "atan", (void (*)(void))((double (*)(double)) atan) },
  { FT_STD, 0, "asinh", (void (*)(void))((double (*)(double)) asinh) },
  { FT_STD, 0, "acosh", (void (*)(void))((double (*)(double)) acosh) },
  { FT_STD, 0, "atanh", (void (*)(void))((double (*)(double)) atanh) },
  { FT_STD, 0, "gamma", (void (*)(void))((double (*)(double)) tgamma) },
  { FT_STD, 0, "float", reinterpret_cast<void (*)(void)>(&f_float) },
  { FT_STD, 0, "int", reinterpret_cast<void (*)(void)>(&f_int) },
  { FT_STD, 0, "nint", reinterpret_cast<void (*)(void)>(&f_nint) },
  { FT_STD, 0, "rand", reinterpret_cast<void (*)(void)>(&f_rand) },
  { FT_STD, 0, "sqr", reinterpret_cast<void (*)(void)>(&f_sqr) },
  { FT_STD, 0, "rad", reinterpret_cast<void (*)(void)>(&f_rad) },
  { FT_STD, 0, "deg", reinterpret_cast<void (*)(void)>(&f_deg) },
  { FT_STD, 0, "isMissval", reinterpret_cast<void (*)(void)>(&f_isMissval) },

  // constant functions
  { FT_CONST, 0, "ngp", reinterpret_cast<void (*)(void)>(&pt_ngp) },          // number of horizontal grid points
  { FT_CONST, 0, "nlev", reinterpret_cast<void (*)(void)>(&pt_nlev) },        // number of vertical levels
  { FT_CONST, 0, "size", reinterpret_cast<void (*)(void)>(&pt_size) },        // ngp*nlev
  { FT_CONST, 0, "missval", reinterpret_cast<void (*)(void)>(&pt_missval) },  // Returns the missing value of a variable

  // CDO field functions (Reduce grid to point)
  { FT_FLD, 0, "fldmin", reinterpret_cast<void (*)(void)>(&vfldmin) },
  { FT_FLD, 0, "fldmax", reinterpret_cast<void (*)(void)>(&vfldmax) },
  { FT_FLD, 0, "fldsum", reinterpret_cast<void (*)(void)>(&vfldsum) },
  { FT_FLD, 1, "fldmean", reinterpret_cast<void (*)(void)>(&vfldmeanw) },
  { FT_FLD, 1, "fldavg", reinterpret_cast<void (*)(void)>(&vfldavgw) },
  { FT_FLD, 1, "fldstd", reinterpret_cast<void (*)(void)>(&vfldstdw) },
  { FT_FLD, 1, "fldstd1", reinterpret_cast<void (*)(void)>(&vfldstd1w) },
  { FT_FLD, 1, "fldvar", reinterpret_cast<void (*)(void)>(&vfldvarw) },
  { FT_FLD, 1, "fldvar1", reinterpret_cast<void (*)(void)>(&vfldvar1w) },

  // CDO zonal functions (Reduce grid to point)
  { FT_ZON, 0, "zonmin", reinterpret_cast<void (*)(void)>(&zonmin) },
  { FT_ZON, 0, "zonmax", reinterpret_cast<void (*)(void)>(&zonmax) },
  { FT_ZON, 0, "zonsum", reinterpret_cast<void (*)(void)>(&zonsum) },
  { FT_ZON, 0, "zonmean", reinterpret_cast<void (*)(void)>(&zonmean) },
  { FT_ZON, 0, "zonavg", reinterpret_cast<void (*)(void)>(&zonavg) },
  { FT_ZON, 0, "zonstd", reinterpret_cast<void (*)(void)>(&zonstd) },
  { FT_ZON, 0, "zonstd1", reinterpret_cast<void (*)(void)>(&zonstd1) },
  { FT_ZON, 0, "zonvar", reinterpret_cast<void (*)(void)>(&zonvar) },
  { FT_ZON, 0, "zonvar1", reinterpret_cast<void (*)(void)>(&zonvar1) },

  // CDO field functions (Reduce level to point)
  { FT_VERT, 0, "vertmin", reinterpret_cast<void (*)(void)>(&vfldmin) },
  { FT_VERT, 0, "vertmax", reinterpret_cast<void (*)(void)>(&vfldmax) },
  { FT_VERT, 0, "vertsum", reinterpret_cast<void (*)(void)>(&vfldsum) },
  { FT_VERT, 1, "vertmean", reinterpret_cast<void (*)(void)>(&vfldmeanw) },
  { FT_VERT, 1, "vertavg", reinterpret_cast<void (*)(void)>(&vfldavgw) },
  { FT_VERT, 1, "vertstd", reinterpret_cast<void (*)(void)>(&vfldstdw) },
  { FT_VERT, 1, "vertstd1", reinterpret_cast<void (*)(void)>(&vfldstd1w) },
  { FT_VERT, 1, "vertvar", reinterpret_cast<void (*)(void)>(&vfldvarw) },
  { FT_VERT, 1, "vertvar1", reinterpret_cast<void (*)(void)>(&vfldvar1w) },

  { FT_COORD, 0, "clon", nullptr },
  { FT_COORD, 0, "clat", nullptr },
  { FT_COORD, 0, "clev", nullptr },
  { FT_COORD, 0, "gridarea", nullptr },
  { FT_COORD, 0, "gridweight", nullptr },

  { FT_0, 0, "ctimestep", reinterpret_cast<void (*)(void)>(&ts_ctimestep) },
  { FT_0, 0, "cdate", reinterpret_cast<void (*)(void)>(&ts_cdate) },
  { FT_0, 0, "ctime", reinterpret_cast<void (*)(void)>(&ts_ctime) },
  { FT_0, 0, "cdeltat", reinterpret_cast<void (*)(void)>(&ts_cdeltat) },
  { FT_0, 0, "cday", reinterpret_cast<void (*)(void)>(&ts_cday) },
  { FT_0, 0, "cmonth", reinterpret_cast<void (*)(void)>(&ts_cmonth) },
  { FT_0, 0, "cyear", reinterpret_cast<void (*)(void)>(&ts_cyear) },
  { FT_0, 0, "csecond", reinterpret_cast<void (*)(void)>(&ts_csecond) },
  { FT_0, 0, "cminute", reinterpret_cast<void (*)(void)>(&ts_cminute) },
  { FT_0, 0, "chour", reinterpret_cast<void (*)(void)>(&ts_chour) },

  { FT_1C, 0, "sellevel", nullptr },
  { FT_1C, 0, "sellevidx", nullptr },
  { FT_2C, 0, "sellevelrange", nullptr },
  { FT_2C, 0, "sellevidxrange", nullptr },
  // {FT_1C, 0, "gridindex", nullptr},
};

static const int NumFunc = sizeof(fun_sym_tbl) / sizeof(fun_sym_tbl[0]);

static void
node_data_delete(nodeType *p)
{
  if (p)
    {
      if (p->param.data)
        {
          Free(p->param.data);
          p->param.data = nullptr;
        }
    }
}

static void
node_delete(nodeType *p)
{
  if (p)
    {
      if (p->type == typeVar) node_data_delete(p);
      Free(p);
    }
}

static int
get_funcID(const char *fun)
{
  int funcID = -1;
  for (int i = 0; i < NumFunc; i++)
    if (cstrIsEqual(fun, fun_sym_tbl[i].name))
      {
        funcID = i;
        break;
      }

  if (funcID == -1) cdoAbort("Function >%s< not available!", fun);

  return funcID;
}

static constexpr bool
isCompare(const int oper) noexcept
{
  return oper == LEG || oper == GE || oper == LE || oper == EQ || oper == NE || oper == GT || oper == LT;
}

static void
param_meta_copy(paramType &out, const paramType &in)
{
  out.type = in.type;
  out.gridID = in.gridID;
  out.zaxisID = in.zaxisID;
  out.datatype = in.datatype;
  out.steptype = in.steptype;
  out.ngp = in.ngp;
  out.nlat = in.nlat;
  out.nlev = in.nlev;
  out.missval = in.missval;
  out.nmiss = 0;
  out.coord = 0;
  out.lmiss = true;
  out.name = nullptr;
  out.longname = nullptr;
  out.units = nullptr;
  out.data = nullptr;
}

static nodeType *
expr_con_con(const int oper, const nodeType *p1, const nodeType *p2)
{
  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeCon;
  p->ltmpobj = true;

  double cval1 = p1->u.con.value;
  const double cval2 = p2->u.con.value;

  // clang-format off
  switch (oper)
    {
    case LT:   cval1 = COMPLT(cval1, cval2); break;
    case GT:   cval1 = COMPGT(cval1, cval2); break;
    case LE:   cval1 = COMPLE(cval1, cval2); break;
    case GE:   cval1 = COMPGE(cval1, cval2); break;
    case NE:   cval1 = COMPNE(cval1, cval2); break;
    case EQ:   cval1 = COMPEQ(cval1, cval2); break;
    case LEG:  cval1 = COMPLEG(cval1, cval2); break;
    case AND:  cval1 = COMPAND(cval1, cval2); break;
    case OR:   cval1 = COMPOR(cval1, cval2); break;
    case '+':  cval1 = cval1 + cval2; break;
    case '-':  cval1 = cval1 - cval2; break;
    case '*':  cval1 = cval1 * cval2; break;
    case '/':  cval1 = cval1 / cval2; break;
    case '^':  cval1 = std::pow(cval1, cval2); break;
    default:   cdoAbort("%s: operator %d unsupported!", __func__, oper); break;
    }
  // clang-format on

  p->u.con.value = cval1;

  return p;
}

static void
oper_expr_con_var(const int oper, const bool nmiss, const size_t n, const double missval1, const double missval2,
                  double *restrict odat, const double cval, const double *restrict idat)
{
  size_t i;

  // clang-format off
  if (nmiss)
    {
      switch (oper)
        {
        case LT:  for (i=0; i<n; ++i) odat[i] = MVCOMPLT(cval, idat[i], missval1); break;
        case GT:  for (i=0; i<n; ++i) odat[i] = MVCOMPGT(cval, idat[i], missval1); break;
        case LE:  for (i=0; i<n; ++i) odat[i] = MVCOMPLE(cval, idat[i], missval1); break;
        case GE:  for (i=0; i<n; ++i) odat[i] = MVCOMPGE(cval, idat[i], missval1); break;
        case NE:  for (i=0; i<n; ++i) odat[i] = MVCOMPNE(cval, idat[i], missval1); break;
        case EQ:  for (i=0; i<n; ++i) odat[i] = MVCOMPEQ(cval, idat[i], missval1); break;
        case LEG: for (i=0; i<n; ++i) odat[i] = MVCOMPLEG(cval, idat[i], missval1); break;
        case AND: for (i=0; i<n; ++i) odat[i] = MVCOMPAND(cval, idat[i], missval1); break;
        case OR:  for (i=0; i<n; ++i) odat[i] = MVCOMPOR(cval, idat[i], missval1); break;
        case '^': for (i=0; i<n; ++i) odat[i] = POWMN(cval, idat[i]); break;
        case '+': for (i=0; i<n; ++i) odat[i] = ADDMN(cval, idat[i]); break;
        case '-': for (i=0; i<n; ++i) odat[i] = SUBMN(cval, idat[i]); break;
        case '*': for (i=0; i<n; ++i) odat[i] = MULMN(cval, idat[i]); break;
        case '/': for (i=0; i<n; ++i) odat[i] = DIVMN(cval, idat[i]); break;
        default: cdoAbort("%s: operator %d unsupported!", __func__, oper); break;
        }
    }
  else
    {
      switch (oper)
        {
        case LT:  for (i=0; i<n; ++i) odat[i] = COMPLT(cval, idat[i]); break;
        case GT:  for (i=0; i<n; ++i) odat[i] = COMPGT(cval, idat[i]); break;
        case LE:  for (i=0; i<n; ++i) odat[i] = COMPLE(cval, idat[i]); break;
        case GE:  for (i=0; i<n; ++i) odat[i] = COMPGE(cval, idat[i]); break;
        case NE:  for (i=0; i<n; ++i) odat[i] = COMPNE(cval, idat[i]); break;
        case EQ:  for (i=0; i<n; ++i) odat[i] = COMPEQ(cval, idat[i]); break;
        case LEG: for (i=0; i<n; ++i) odat[i] = COMPLEG(cval, idat[i]); break;
        case AND: for (i=0; i<n; ++i) odat[i] = COMPAND(cval, idat[i]); break;
        case OR:  for (i=0; i<n; ++i) odat[i] = COMPOR(cval, idat[i]); break;
        case '^': for (i=0; i<n; ++i) odat[i] = std::pow(cval, idat[i]); break;
        case '+': for (i=0; i<n; ++i) odat[i] = cval + idat[i]; break;
        case '-': for (i=0; i<n; ++i) odat[i] = cval - idat[i]; break;
        case '*': for (i=0; i<n; ++i) odat[i] = cval * idat[i]; break;
        case '/': for (i=0; i<n; ++i) odat[i] = DIVMN(cval, idat[i]); break;
        default: cdoAbort("%s: operator %d unsupported!", __func__, oper); break;
        }
    }
  // clang-format on
}

static void
oper_expr_var_con(const int oper, const bool lmiss, const size_t n, const double missval1, const double missval2,
                  double *restrict odat, const double *restrict idat, const double cval)
{
  size_t i;

  // clang-format off
  if (lmiss)
    {
      switch (oper)
        {
        case LT:  for (i=0; i<n; ++i) odat[i] = MVCOMPLT(idat[i], cval, missval1); break;
        case GT:  for (i=0; i<n; ++i) odat[i] = MVCOMPGT(idat[i], cval, missval1); break;
        case LE:  for (i=0; i<n; ++i) odat[i] = MVCOMPLE(idat[i], cval, missval1); break;
        case GE:  for (i=0; i<n; ++i) odat[i] = MVCOMPGE(idat[i], cval, missval1); break;
        case NE:  for (i=0; i<n; ++i) odat[i] = MVCOMPNE(idat[i], cval, missval1); break;
        case EQ:  for (i=0; i<n; ++i) odat[i] = MVCOMPEQ(idat[i], cval, missval1); break;
        case LEG: for (i=0; i<n; ++i) odat[i] = MVCOMPLEG(idat[i], cval, missval1); break;
        case AND: for (i=0; i<n; ++i) odat[i] = MVCOMPAND(idat[i], cval, missval1); break;
        case OR:  for (i=0; i<n; ++i) odat[i] = MVCOMPOR(idat[i], cval, missval1); break;
        case '^': for (i=0; i<n; ++i) odat[i] = POWMN(idat[i], cval); break;
        case '+': for (i=0; i<n; ++i) odat[i] = ADDMN(idat[i], cval); break;
        case '-': for (i=0; i<n; ++i) odat[i] = SUBMN(idat[i], cval); break;
        case '*': for (i=0; i<n; ++i) odat[i] = MULMN(idat[i], cval); break;
        case '/': for (i=0; i<n; ++i) odat[i] = DIVMN(idat[i], cval); break;
        default: cdoAbort("%s: operator %d unsupported!", __func__, oper); break;
        }
    }
  else
    {
      switch (oper)
        {
        case LT:  for (i=0; i<n; ++i) odat[i] = COMPLT(idat[i], cval); break;
        case GT:  for (i=0; i<n; ++i) odat[i] = COMPGT(idat[i], cval); break;
        case LE:  for (i=0; i<n; ++i) odat[i] = COMPLE(idat[i], cval); break;
        case GE:  for (i=0; i<n; ++i) odat[i] = COMPGE(idat[i], cval); break;
        case NE:  for (i=0; i<n; ++i) odat[i] = COMPNE(idat[i], cval); break;
        case EQ:  for (i=0; i<n; ++i) odat[i] = COMPEQ(idat[i], cval); break;
        case LEG: for (i=0; i<n; ++i) odat[i] = COMPLEG(idat[i], cval); break;
        case AND: for (i=0; i<n; ++i) odat[i] = COMPAND(idat[i], cval); break;
        case OR:  for (i=0; i<n; ++i) odat[i] = COMPOR(idat[i], cval); break;
        case '^': for (i=0; i<n; ++i) odat[i] = std::pow(idat[i], cval); break;
        case '+': for (i=0; i<n; ++i) odat[i] = idat[i] + cval; break;
        case '-': for (i=0; i<n; ++i) odat[i] = idat[i] - cval; break;
        case '*': for (i=0; i<n; ++i) odat[i] = idat[i] * cval; break;
        case '/':
          if (IS_EQUAL(cval, 0)) for (i=0; i<n; ++i) odat[i] = missval1;
          else                   for (i=0; i<n; ++i) odat[i] = idat[i] / cval;
          break;
        default: cdoAbort("%s: operator %d unsupported!", __func__, oper); break;
        }
    }
  // clang-format on
}

static void
oper_expr_var_var(const int oper, const bool lmiss, const size_t n, const double missval1, const double missval2,
                  double *restrict odat, const double *restrict idat1, const double *restrict idat2)
{
  size_t i;

  // clang-format off
  if (lmiss)
    {
      switch (oper)
        {
        case LT:  for (i=0; i<n; ++i) odat[i] = MVCOMPLT(idat1[i], idat2[i], missval1); break;
        case GT:  for (i=0; i<n; ++i) odat[i] = MVCOMPGT(idat1[i], idat2[i], missval1); break;
        case LE:  for (i=0; i<n; ++i) odat[i] = MVCOMPLE(idat1[i], idat2[i], missval1); break;
        case GE:  for (i=0; i<n; ++i) odat[i] = MVCOMPGE(idat1[i], idat2[i], missval1); break;
        case NE:  for (i=0; i<n; ++i) odat[i] = MVCOMPNE(idat1[i], idat2[i], missval1); break;
        case EQ:  for (i=0; i<n; ++i) odat[i] = MVCOMPEQ(idat1[i], idat2[i], missval1); break;
        case LEG: for (i=0; i<n; ++i) odat[i] = MVCOMPLEG(idat1[i], idat2[i], missval1); break;
        case AND: for (i=0; i<n; ++i) odat[i] = MVCOMPAND(idat1[i], idat2[i], missval1); break;
        case OR:  for (i=0; i<n; ++i) odat[i] = MVCOMPOR(idat1[i], idat2[i], missval1); break;
        case '^': for (i=0; i<n; ++i) odat[i] = POWMN(idat1[i], idat2[i]); break;
        case '+': for (i=0; i<n; ++i) odat[i] = ADDMN(idat1[i], idat2[i]); break;
        case '-': for (i=0; i<n; ++i) odat[i] = SUBMN(idat1[i], idat2[i]); break;
        case '*': for (i=0; i<n; ++i) odat[i] = MULMN(idat1[i], idat2[i]); break;
        case '/': for (i=0; i<n; ++i) odat[i] = DIVMN(idat1[i], idat2[i]); break;
        default: cdoAbort("%s: operator %d unsupported!", __func__, oper); break;
        }
    }
  else
    {
      switch (oper)
        {
        case LT:  for (i=0; i<n; ++i) odat[i] = COMPLT(idat1[i], idat2[i]); break;
        case GT:  for (i=0; i<n; ++i) odat[i] = COMPGT(idat1[i], idat2[i]); break;
        case LE:  for (i=0; i<n; ++i) odat[i] = COMPLE(idat1[i], idat2[i]); break;
        case GE:  for (i=0; i<n; ++i) odat[i] = COMPGE(idat1[i], idat2[i]); break;
        case NE:  for (i=0; i<n; ++i) odat[i] = COMPNE(idat1[i], idat2[i]); break;
        case EQ:  for (i=0; i<n; ++i) odat[i] = COMPEQ(idat1[i], idat2[i]); break;
        case LEG: for (i=0; i<n; ++i) odat[i] = COMPLEG(idat1[i], idat2[i]); break;
        case AND: for (i=0; i<n; ++i) odat[i] = COMPAND(idat1[i], idat2[i]); break;
        case OR:  for (i=0; i<n; ++i) odat[i] = COMPOR(idat1[i], idat2[i]); break;
        case '^': for (i=0; i<n; ++i) odat[i] = std::pow(idat1[i], idat2[i]); break;
        case '+': for (i=0; i<n; ++i) odat[i] = idat1[i] + idat2[i]; break;
        case '-': for (i=0; i<n; ++i) odat[i] = idat1[i] - idat2[i]; break;
        case '*': for (i=0; i<n; ++i) odat[i] = idat1[i] * idat2[i]; break;
        case '/': for (i=0; i<n; ++i) odat[i] = IS_EQUAL(idat2[i], 0.) ? missval1 : idat1[i] / idat2[i]; break;
        default: cdoAbort("%s: operator %d unsupported!", __func__, oper); break;
        }
    }
  // clang-format on
}

static nodeType *
expr_con_var(const int init, const int oper, const nodeType *p1, const nodeType *p2)
{
  const size_t ngp = p2->param.ngp;
  const size_t nlev = p2->param.nlev;
  const size_t nmiss = p2->param.nmiss;
  const int datatype = p2->param.datatype;
  const double missval1 = p2->param.missval;
  const double missval2 = p2->param.missval;

  const size_t n = ngp * nlev;

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeVar;
  p->ltmpobj = true;
  p->u.var.nm = strdup(tmpvnm);
  param_meta_copy(p->param, p2->param);
  p->param.name = p->u.var.nm;

  if (!init)
    {
      p->param.data = (double *) Malloc(n * sizeof(double));
      double *restrict odat = p->param.data;
      const double *restrict idat = p2->param.data;
      double cval = p1->u.con.value;
      if (datatype == CDI_DATATYPE_FLT32 && isCompare(oper)) cval = (float) cval;

      oper_expr_con_var(oper, nmiss, n, missval1, missval2, odat, cval, idat);

      p->param.nmiss = arrayNumMV(n, odat, missval1);
    }

  return p;
}

static nodeType *
expr_var_con(const int init, const int oper, const nodeType *p1, const nodeType *p2)
{
  const size_t ngp = p1->param.ngp;
  const size_t nlev = p1->param.nlev;
  const size_t nmiss = p1->param.nmiss;
  const int datatype = p1->param.datatype;
  const double missval1 = p1->param.missval;
  const double missval2 = p1->param.missval;

  const size_t n = ngp * nlev;

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeVar;
  p->ltmpobj = true;
  p->u.var.nm = strdup(tmpvnm);
  param_meta_copy(p->param, p1->param);
  p->param.name = p->u.var.nm;

  if (!init)
    {
      p->param.data = (double *) Malloc(n * sizeof(double));
      double *restrict odat = p->param.data;
      const double *restrict idat = p1->param.data;
      double cval = p2->u.con.value;
      if (datatype == CDI_DATATYPE_FLT32 && isCompare(oper)) cval = (float) cval;

      oper_expr_var_con(oper, nmiss, n, missval1, missval2, odat, idat, cval);

      p->param.nmiss = arrayNumMV(n, odat, missval1);
    }

  return p;
}

static nodeType *
expr_var_var(int init, int oper, nodeType *p1, nodeType *p2)
{
  nodeType *px = p1;
  const size_t nmiss1 = p1->param.nmiss;
  const size_t nmiss2 = p2->param.nmiss;
  const double missval1 = p1->param.missval;
  const double missval2 = p2->param.missval;

  const size_t ngp1 = p1->param.ngp;
  const size_t ngp2 = p2->param.ngp;
  size_t ngp = ngp1;

  const size_t nlat1 = p1->param.nlat;
  const size_t nlat2 = p2->param.nlat;
  bool lzonal = false;

  if (ngp1 != ngp2)
    {
      if (ngp1 == 1 || ngp2 == 1)
        {
          if (ngp1 == 1)
            {
              ngp = ngp2;
              px = p2;
            }
        }
      else if (nlat1 == nlat2 && ngp1 > ngp2)
        {
          lzonal = true;
        }
      else
        {
          cdoAbort("%s: Number of grid points differ (%s[%zu] <-> %s[%zu])", __func__, p1->param.name, ngp1, p2->param.name, ngp2);
        }
    }

  const size_t nlev1 = p1->param.nlev;
  const size_t nlev2 = p2->param.nlev;

  size_t nlev = nlev1;
  if (nlev1 != nlev2)
    {
      if (nlev1 == 1 || nlev2 == 1)
        {
          if (nlev1 == 1)
            {
              nlev = nlev2;
              px = p2;
            }
        }
      else
        {
          cdoAbort("%s: Number of levels differ (%s[%zu] <-> %s[%zu])", __func__, p1->param.name, nlev1, p2->param.name, nlev2);
        }
    }

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeVar;
  p->ltmpobj = true;
  p->u.var.nm = strdup(tmpvnm);

  param_meta_copy(p->param, px->param);

  if (p->param.steptype == TIME_CONSTANT)
    {
      const int steptype1 = p1->param.steptype;
      const int steptype2 = p2->param.steptype;
      if (steptype1 != TIME_CONSTANT)
        p->param.steptype = steptype1;
      else if (steptype2 != TIME_CONSTANT)
        p->param.steptype = steptype2;
    }

  p->param.name = p->u.var.nm;
  // printf("%s %s nmiss %zu %zu\n", p->u.var.nm, px->param.name, nmiss1, nmiss2);

  if (!init)
    {
      p->param.data = (double *) Malloc(ngp * nlev * sizeof(double));

      for (size_t k = 0; k < nlev; k++)
        {
          size_t loff = k * ngp;
          const size_t loff1 = (nlev1 > 1) ? k * ngp1 : 0;
          const size_t loff2 = (nlev2 > 1) ? k * ngp2 : 0;

          const double *restrict idat1 = p1->param.data + loff1;
          const double *restrict idat2 = p2->param.data + loff2;
          double *restrict odat = p->param.data + loff;
          const size_t nmiss = nmiss1 || nmiss2;

          if (ngp1 != ngp2)
            {
              if (lzonal)
                {
                  const size_t nlon = ngp1 / nlat1;
                  for (size_t j = 0; j < nlat1; ++j)
                    oper_expr_var_con(oper, nmiss, nlon, missval1, missval2, odat + j * nlon, idat1 + j * nlon, idat2[j]);
                }
              else
                {
                  if (ngp2 == 1)
                    oper_expr_var_con(oper, nmiss, ngp, missval1, missval2, odat, idat1, idat2[0]);
                  else
                    oper_expr_con_var(oper, nmiss, ngp, missval1, missval2, odat, idat1[0], idat2);
                }
            }
          else
            {
              oper_expr_var_var(oper, nmiss, ngp, missval1, missval2, odat, idat1, idat2);
            }
        }

      p->param.nmiss = arrayNumMV(ngp * nlev, p->param.data, missval1);
    }

  return p;
}

static void
ex_copy_var(int init, nodeType *p2, const nodeType *p1)
{
  if (Options::cdoVerbose)
    cdoPrint("\t%s\tcopy\t%s[L%zu][N%zu] = %s[L%zu][N%zu]", ExIn[init], p2->param.name, p2->param.nlev, p2->param.ngp,
             p1->param.name, p2->param.nlev, p2->param.ngp);

  const size_t ngp = p1->param.ngp;
  assert(ngp > 0);

  if (ngp != p2->param.ngp)
    cdoAbort("%s: Number of grid points differ (%s[%zu] = %s[%zu])", __func__, p2->param.name, p2->param.ngp, p1->param.name, ngp);

  const size_t nlev = p1->param.nlev;
  assert(nlev > 0);

  if (nlev != p2->param.nlev)
    cdoAbort("%s: Number of levels differ (%s[%zu] = %s[%zu])", __func__, p2->param.name, p2->param.nlev, p1->param.name, nlev);

  if (!init)
    {
      arrayCopy(ngp * nlev, p1->param.data, p2->param.data);
      p2->param.missval = p1->param.missval;
      p2->param.nmiss = p1->param.nmiss;
    }
}

static void
ex_copy_con(int init, nodeType *p2, const nodeType *p1)
{
  const double cval = p1->u.con.value;

  if (Options::cdoVerbose)
    cdoPrint("\t%s\tcopy\t%s[L%zu][N%zu] = %g", ExIn[init], p2->param.name, p2->param.nlev, p2->param.ngp, cval);

  const size_t ngp = p2->param.ngp;
  assert(ngp > 0);

  const size_t nlev = p2->param.nlev;
  assert(nlev > 0);

  if (!init)
    {
      assert(p2->param.data != nullptr);
      varrayFill(ngp * nlev, p2->param.data, cval);
    }
}

static void
ex_copy(int init, nodeType *p2, const nodeType *p1)
{
  if (p1->type == typeCon)
    ex_copy_con(init, p2, p1);
  else
    ex_copy_var(init, p2, p1);
}

static nodeType *
expr(const int init, const int oper, nodeType *p1, nodeType *p2)
{
  if (p1 == nullptr || p2 == nullptr) return nullptr;

  const char *coper = "???";

  if (Options::cdoVerbose)
    {
      switch (oper)
        {
        case LT: coper = "<"; break;
        case GT: coper = ">"; break;
        case LE: coper = "<="; break;
        case GE: coper = ">="; break;
        case NE: coper = "!="; break;
        case EQ: coper = "=="; break;
        case LEG: coper = "<=>"; break;
        case AND: coper = "&&"; break;
        case OR: coper = "||"; break;
        case '^': coper = "^"; break;
        case '+': coper = "+"; break;
        case '-': coper = "-"; break;
        case '*': coper = "*"; break;
        case '/': coper = "/"; break;
        default: cdoAbort("Internal error, expr operator %d not implemented!\n", oper);
        }
    }

  nodeType *p = nullptr;

  if (p1->type == typeVar && p2->type == typeVar)
    {
      p = expr_var_var(init, oper, p1, p2);
      if (Options::cdoVerbose)
        cdoPrint("\t%s\tarith\t%s[L%zu][N%zu] = %s %s %s", ExIn[init], p->u.var.nm, p->param.nlev, p->param.ngp, p1->u.var.nm,
                 coper, p2->u.var.nm);
    }
  else if (p1->type == typeVar && p2->type == typeCon)
    {
      p = expr_var_con(init, oper, p1, p2);
      if (Options::cdoVerbose)
        cdoPrint("\t%s\tarith\t%s[L%zu][N%zu] = %s %s %g", ExIn[init], p->u.var.nm, p->param.nlev, p->param.ngp, p1->u.var.nm,
                 coper, p2->u.con.value);
    }
  else if (p1->type == typeCon && p2->type == typeVar)
    {
      p = expr_con_var(init, oper, p1, p2);
      if (Options::cdoVerbose)
        cdoPrint("\t%s\tarith\t%s[L%zu][N%zu] = %g %s %s", ExIn[init], p->u.var.nm, p->param.nlev, p->param.ngp, p1->u.con.value,
                 coper, p2->u.var.nm);
    }
  else if (p1->type == typeCon && p2->type == typeCon)
    {
      p = expr_con_con(oper, p1, p2);
      if (Options::cdoVerbose)
        cdoPrint("\t%s\tarith\t%g = %g %s %g", ExIn[init], p->u.con.value, p1->u.con.value, coper, p2->u.con.value);
    }
  else
    cdoAbort("Internal problem!");

  if (p1->ltmpobj) node_delete(p1);
  if (p2->ltmpobj) node_delete(p2);

  return p;
}

static nodeType *
ex_fun0_con(const int init, const int funcID, double *data)
{
  int functype = fun_sym_tbl[funcID].type;
  if (functype != FT_0) cdoAbort("Function %s not available for constant values!", fun_sym_tbl[funcID].name);

  if (Options::cdoVerbose) cdoPrint("\t%s\tfunc\t%s", ExIn[init], fun_sym_tbl[funcID].name);

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeCon;
  p->ltmpobj = true;

  if (!init)
    {
      double (*exprfunc)(const double *) = (double (*)(const double *)) fun_sym_tbl[funcID].func;
      p->u.con.value = exprfunc(data);
    }

  return p;
}

static nodeType *
ex_fun_con(const int funcID, nodeType *p1)
{
  const int functype = fun_sym_tbl[funcID].type;
  if (functype != FT_STD) cdoAbort("Function %s not available for constant values!", fun_sym_tbl[funcID].name);

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeCon;
  p->ltmpobj = true;

  double (*exprfunc)(double) = (double (*)(double)) fun_sym_tbl[funcID].func;
  p->u.con.value = exprfunc(p1->u.con.value);

  if (p1->ltmpobj)
    node_delete(p1);
  else
    Free(p1);

  return p;
}

static void
ex_fun_std(const int funcID, size_t ngp, size_t nlev, size_t nmiss, double missval, double *p1data, double *pdata)
{
  double (*exprfunc)(double) = (double (*)(double)) fun_sym_tbl[funcID].func;
  if (nmiss)
    {
      if (cstrIsEqual(fun_sym_tbl[funcID].name, "isMissval"))
        {
          for (size_t i = 0; i < ngp * nlev; i++)
            {
              pdata[i] = DBL_IS_EQUAL(p1data[i], missval);
            }
        }
      else
        {
          for (size_t i = 0; i < ngp * nlev; i++)
            {
              errno = -1;
              pdata[i] = DBL_IS_EQUAL(p1data[i], missval) ? missval : exprfunc(p1data[i]);
              if (errno == EDOM || errno == ERANGE)
                pdata[i] = missval;
              else if (std::isnan(pdata[i]))
                pdata[i] = missval;
            }
        }
    }
  else
    {
      for (size_t i = 0; i < ngp * nlev; i++)
        {
          errno = -1;
          pdata[i] = exprfunc(p1data[i]);
          if (errno == EDOM || errno == ERANGE)
            pdata[i] = missval;
          else if (std::isnan(pdata[i]))
            pdata[i] = missval;
        }
    }
}

static nodeType *
ex_fun_var(const int init, const int funcID, nodeType *p1)
{
  const char *funcname = fun_sym_tbl[funcID].name;
  const int functype = fun_sym_tbl[funcID].type;
  const int funcflag = fun_sym_tbl[funcID].flag;

  const size_t gridID = p1->param.gridID;
  const size_t ngp = p1->param.ngp;
  const size_t nlat = p1->param.nlat;
  const size_t nlev = p1->param.nlev;
  const size_t nmiss = p1->param.nmiss;
  const double missval = p1->param.missval;

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeVar;
  p->ltmpobj = true;
  p->u.var.nm = strdup(tmpvnm);

  param_meta_copy(p->param, p1->param);

  if (functype == FT_CONST)
    {
      p->type = typeCon;
      double (*exprfunc)(const paramType *) = (double (*)(const paramType *)) fun_sym_tbl[funcID].func;
      p->u.con.value = exprfunc(&p1->param);
    }
  else if (functype == FT_FLD)
    {
      p->param.gridID = pointID;
      p->param.ngp = 1;
    }
  else if (functype == FT_ZON)
    {
      if (zonalID == -1) cdoAbort("Function %s() is only available for regular 2D grids!", fun_sym_tbl[funcID].name);
      p->param.gridID = zonalID;
      p->param.ngp = nlat;
    }
  else if (functype == FT_VERT)
    {
      p->param.zaxisID = surfaceID;
      p->param.nlev = 1;
    }

  if (!init)
    {
      p->param.data = (double *) Malloc(p->param.ngp * p->param.nlev * sizeof(double));
      double *restrict pdata = p->param.data;
      double *restrict p1data = p1->param.data;

      if (functype == FT_STD)
        {
          ex_fun_std(funcID, ngp, nlev, nmiss, missval, p1data, pdata);
        }
      else if (functype == FT_FLD)
        {
          Field field;
          field.resize(ngp);
          if (funcflag == 1)
            {
              assert(p1->param.weight != nullptr);
              field.weightv.resize(ngp);
            }

          double (*exprfunc)(const Field &) = (double (*)(const Field &)) fun_sym_tbl[funcID].func;
          for (size_t k = 0; k < nlev; k++)
            {
              fld_field_init(field, nmiss, missval, ngp, p1data + k * ngp, p1->param.weight);
              pdata[k] = exprfunc(field);
            }
        }
      else if (functype == FT_ZON)
        {
          Field field1, field2;
          field1.resize(ngp);
          field2.resize(nlat);
          void (*exprfunc)(const Field &, Field &) = (void (*)(const Field &, Field &)) fun_sym_tbl[funcID].func;
          for (size_t k = 0; k < nlev; k++)
            {
              fld_field_init(field1, nmiss, missval, ngp, &p1data[k * ngp], nullptr);
              field1.grid = gridID;
              fld_field_init(field2, nmiss, missval, nlat, &pdata[k * nlat], nullptr);
              exprfunc(field1, field2);
              arrayCopy(nlat, field2.vec_d.data(), &pdata[k * nlat]);
            }
        }
      else if (functype == FT_VERT)
        {
          Field field;
          field.resize(nlev);
          if (funcflag == 1) vert_weights(p1->param.zaxisID, nlev, field.weightv);
          double (*exprfunc)(const Field &) = (double (*)(const Field &)) fun_sym_tbl[funcID].func;
          for (size_t i = 0; i < ngp; i++)
            {
              for (size_t k = 0; k < nlev; k++) field.vec_d[k] = p1data[k * ngp + i];
              fld_field_init(field, nmiss, missval, nlev, nullptr, nullptr);
              pdata[i] = exprfunc(field);
            }
        }
      else if (functype == FT_CONST)
        {
        }
      else
        cdoAbort("Intermal error, wrong function type (%d) for %s()!", functype, funcname);

      p->param.nmiss = arrayNumMV(p->param.ngp * p->param.nlev, pdata, missval);
    }

  if (p1->ltmpobj)
    node_delete(p1);
  else
    Free(p1);

  return p;
}

static nodeType *
ex_fun(const int init, const int funcID, nodeType *p1)
{
  nodeType *p = nullptr;

  if (p1->type == typeVar)
    {
      if (Options::cdoVerbose) cdoPrint("\t%s\tfunc\t%s (%s)", ExIn[init], fun_sym_tbl[funcID].name, p1->u.var.nm);
      p = ex_fun_var(init, funcID, p1);
    }
  else if (p1->type == typeCon)
    {
      if (Options::cdoVerbose) cdoPrint("\t%s\tfunc\t%s (%g)", ExIn[init], fun_sym_tbl[funcID].name, p1->u.con.value);
      p = ex_fun_con(funcID, p1);
    }
  else
    cdoAbort("Internal problem!");

  return p;
}

static size_t
get_levidx(const size_t nlev, const double *data, const double value, const char *funcname)
{
  size_t levidx;

  for (levidx = 0; levidx < nlev; ++levidx)
    if (IS_EQUAL(data[levidx], value)) break;
  if (levidx == nlev) cdoAbort("%s(): level %g not found!", funcname, value);

  return levidx;
}

static void
get_levidxrange(size_t nlev, const double *data, double value1, double value2, const char *funcname, size_t &levidx1,
                size_t &levidx2)
{
  long i, n = nlev;
  if (data[0] <= data[nlev - 1])
    {
      for (i = 0; i < n; ++i)
        if (data[i] >= value1) break;
      if (i == n) cdoAbort("%s(): lower level %g not found!", funcname, value1);
      levidx1 = i;

      for (i = n - 1; i >= 0; --i)
        if (data[i] <= value2) break;
      if (i < 0) cdoAbort("%s(): upper level %g not found!", funcname, value2);
      if (i < (long) levidx1) cdoAbort("%s(): level range %g to %g not found!", funcname, value1, value2);
      levidx2 = i;
    }
  else
    {
      for (i = 0; i < n; ++i)
        if (data[i] <= value2) break;
      if (i == n) cdoAbort("%s(): upper level %g not found!", funcname, value1);
      levidx1 = i;

      for (i = n - 1; i >= 0; --i)
        if (data[i] >= value1) break;
      if (i < 0) cdoAbort("%s(): lower level %g not found!", funcname, value2);
      if (i < (long) levidx1) cdoAbort("%s(): level range %g to %g not found!", funcname, value1, value2);
      levidx2 = i;
    }
}

static nodeType *
fun1c(int init, int funcID, nodeType *p1, double value, parseParamType *parse_arg)
{
  const char *funcname = fun_sym_tbl[funcID].name;
  if (p1->type != typeVar) cdoAbort("Parameter of function %s() needs to be a variable!", funcname);
  if (p1->ltmpobj) cdoAbort("Temporary objects not allowed in function %s()!", funcname);

  const size_t ngp = p1->param.ngp;
  const size_t nlev = p1->param.nlev;
  size_t nmiss = p1->param.nmiss;
  const double missval = p1->param.missval;

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeVar;
  p->ltmpobj = true;
  p->u.var.nm = strdup(tmpvnm);
  param_meta_copy(p->param, p1->param);
  p->param.name = p->u.var.nm;

  if (init)
    {
      if (p1->param.longname) p->param.longname = strdup(p1->param.longname);
      if (p1->param.units) p->param.units = strdup(p1->param.units);
    }

  p->param.nlev = 1;

  const int zaxisID = p1->param.zaxisID;
  const int coordID = params_get_coordID(parse_arg, 'z', zaxisID);
  size_t levidx = 0;

  std::vector<double> data;
  double *pdata = nullptr;

  if (init)
    {
      parse_arg->coords[coordID].needed = true;

      data.resize(nlev);
      pdata = data.data();
      cdoZaxisInqLevels(zaxisID, pdata);
    }
  else
    {
      pdata = parse_arg->coords[coordID].data.data();
    }

  if (cstrIsEqual(funcname, "sellevidx"))
    {
      const long ilevidx = std::lround(value);
      if (ilevidx < 1 || ilevidx > (long) nlev)
        cdoAbort("%s(): level index %ld out of range (range: 1-%zu)!", funcname, ilevidx, nlev);
      levidx = (size_t) ilevidx - 1;
    }
  else if (cstrIsEqual(funcname, "sellevel"))
    {
      levidx = get_levidx(nlev, pdata, value, funcname);
    }
  else
    cdoAbort("Function %s() not implemented!", funcname);

  if (init)
    {
      const double level = data[levidx];
      const int zaxisID2 = zaxisCreate(zaxisInqType(zaxisID), 1);
      zaxisDefLevels(zaxisID2, &level);
      p->param.zaxisID = zaxisID2;
    }

  if (!init)
    {
      p->param.data = (double *) Malloc(ngp * sizeof(double));
      pdata = p->param.data;
      const double *p1data = p1->param.data + ngp * levidx;
      arrayCopy(ngp, p1data, pdata);
      if (nmiss) nmiss = arrayNumMV(ngp, pdata, missval);
      p->param.nmiss = nmiss;
    }

  if (p1->ltmpobj) node_delete(p1);

  return p;
}

static nodeType *
fun2c(int init, int funcID, nodeType *p1, double value1, double value2, parseParamType *parse_arg)
{
  const char *funcname = fun_sym_tbl[funcID].name;
  if (p1->type != typeVar) cdoAbort("Parameter of function %s() needs to be a variable!", funcname);
  if (p1->ltmpobj) cdoAbort("Temporary objects not allowed in function %s()!", funcname);

  const size_t ngp = p1->param.ngp;
  const size_t nlev = p1->param.nlev;
  size_t nmiss = p1->param.nmiss;
  const double missval = p1->param.missval;

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeVar;
  p->ltmpobj = true;
  p->u.var.nm = strdup(tmpvnm);
  param_meta_copy(p->param, p1->param);
  p->param.name = p->u.var.nm;

  if (init)
    {
      if (p1->param.longname) p->param.longname = strdup(p1->param.longname);
      if (p1->param.units) p->param.units = strdup(p1->param.units);
    }

  const int zaxisID = p1->param.zaxisID;
  const int coordID = params_get_coordID(parse_arg, 'z', zaxisID);
  size_t levidx1 = 0;
  size_t levidx2 = 0;

  double *data = nullptr;
  if (init)
    {
      parse_arg->coords[coordID].needed = true;

      data = (double *) Malloc(nlev * sizeof(double));
      cdoZaxisInqLevels(zaxisID, data);
    }
  else
    {
      data = parse_arg->coords[coordID].data.data();
    }

  if (cstrIsEqual(funcname, "sellevidxrange"))
    {
      if (value2 < value1) cdoAbort("%s(): first level index is greater than last level index!", funcname);
      const long ilevidx1 = std::lround(value1);
      if (ilevidx1 < 1 || ilevidx1 > (long) nlev)
        cdoAbort("%s(): level index1 %ld out of range (range: 1-%zu)!", funcname, ilevidx1, nlev);
      levidx1 = (size_t) ilevidx1 - 1;
      const long ilevidx2 = std::lround(value2);
      if (ilevidx2 < 1 || ilevidx2 > (long) nlev)
        cdoAbort("%s(): level index2 %ld out of range (range: 1-%zu)!", funcname, ilevidx2, nlev);
      levidx2 = (size_t) ilevidx2 - 1;
    }
  else if (cstrIsEqual(funcname, "sellevelrange"))
    {
      if (value2 < value1) cdoAbort("%s(): first level is greater than last level!", funcname);
      get_levidxrange(nlev, data, value1, value2, funcname, levidx1, levidx2);
    }
  else
    cdoAbort("Function %s() not implemented!", funcname);

  const int nlevout = levidx2 - levidx1 + 1;
  p->param.nlev = nlevout;

  if (init)
    {
      const int zaxisID2 = zaxisCreate(zaxisInqType(zaxisID), nlevout);
      zaxisDefLevels(zaxisID2, &data[levidx1]);
      if (zaxisInqLbounds(zaxisID, nullptr) && zaxisInqUbounds(zaxisID, nullptr))
        {
          std::vector<double> bounds(nlev);
          zaxisInqLbounds(zaxisID, bounds.data());
          zaxisDefLbounds(zaxisID2, &bounds[levidx1]);
          zaxisInqUbounds(zaxisID, bounds.data());
          zaxisDefUbounds(zaxisID2, &bounds[levidx1]);
        }
      p->param.zaxisID = zaxisID2;
    }

  if (init) Free(data);

  if (!init)
    {
      p->param.data = (double *) Malloc(ngp * nlevout * sizeof(double));
      double *pdata = p->param.data;
      const double *p1data = p1->param.data + ngp * levidx1;
      arrayCopy(ngp * nlevout, p1data, pdata);
      if (nmiss) nmiss = arrayNumMV(ngp * nlevout, pdata, missval);
      p->param.nmiss = nmiss;
    }

  if (p1->ltmpobj) node_delete(p1);

  return p;
}

static nodeType *
coord_fun(const int init, const int funcID, nodeType *p1, parseParamType *parse_arg)
{
  const char *funcname = fun_sym_tbl[funcID].name;
  if (p1->type != typeVar) cdoAbort("Parameter of function %s() needs to be a variable!", funcname);
  if (p1->ltmpobj) cdoAbort("Temporary objects not allowed in function %s()!", funcname);

  const size_t len = 3 + strlen(p1->u.var.nm);
  char *cname = (char *) Calloc(len, 1);
  strcpy(cname, p1->u.var.nm);

  // clang-format off
  if      (cstrIsEqual(funcname, "clon")) strcat(cname, ".x");
  else if (cstrIsEqual(funcname, "clat")) strcat(cname, ".y");
  else if (cstrIsEqual(funcname, "clev")) strcat(cname, ".z");
  else if (cstrIsEqual(funcname, "gridarea")) strcat(cname, ".a");
  else if (cstrIsEqual(funcname, "gridweight")) strcat(cname, ".w");
  else cdoAbort("Implementation missing for function %s!", funcname);
  // clang-format on

  Free(p1->u.var.nm);
  p1->u.var.nm = cname;

  nodeType *p = expr_run(p1, parse_arg);
  p->param.lmiss = false;

  if (!init)
    {
      /*
      size_t ngp  = p1->param.ngp;
      size_t nlev = p1->param.nlev;
      p->param.data = (double*) Malloc(ngp*nlev*sizeof(double));
      double *restrict pdata  = p->param.data;
      double *restrict p1data = p1->param.data;

      for ( size_t i = 0; i < ngp*nlev; i++ ) pdata[i] = p1data[i];
      */
    }
  /*
  Free(cname);
  Free(p1);
  */
  return p;
}

static nodeType *
ex_uminus_var(const int init, nodeType *p1)
{
  const size_t ngp = p1->param.ngp;
  const size_t nlev = p1->param.nlev;
  const size_t nmiss = p1->param.nmiss;
  const double missval = p1->param.missval;

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeVar;
  p->ltmpobj = true;
  p->u.var.nm = strdup(tmpvnm);
  param_meta_copy(p->param, p1->param);
  p->param.name = p->u.var.nm;

  if (!init)
    {
      p->param.data = (double *) Malloc(ngp * nlev * sizeof(double));
      double *restrict pdata = p->param.data;
      const double *restrict p1data = p1->param.data;

      if (nmiss)
        {
          for (size_t i = 0; i < ngp * nlev; ++i) pdata[i] = DBL_IS_EQUAL(p1data[i], missval) ? missval : -(p1data[i]);
        }
      else
        {
          for (size_t i = 0; i < ngp * nlev; ++i) pdata[i] = -(p1data[i]);
        }

      p->param.nmiss = nmiss;
    }

  if (p1->ltmpobj) node_delete(p1);

  return p;
}

static nodeType *
ex_uminus_con(nodeType *p1)
{
  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeCon;
  p->ltmpobj = true;

  p->u.con.value = -(p1->u.con.value);

  return p;
}

static nodeType *
ex_uminus(const int init, nodeType *p1)
{
  nodeType *p = nullptr;

  if (p1->type == typeVar)
    {
      if (Options::cdoVerbose) cdoPrint("\t%s\tneg\t- (%s)", ExIn[init], p1->u.var.nm);
      p = ex_uminus_var(init, p1);
    }
  else if (p1->type == typeCon)
    {
      if (Options::cdoVerbose) cdoPrint("\t%s\tneg\t- (%g)", ExIn[init], p1->u.con.value);
      p = ex_uminus_con(p1);
    }
  else
    cdoAbort("Internal problem!");

  return p;
}

static nodeType *
ex_not_var(const int init, nodeType *p1)
{
  const size_t ngp = p1->param.ngp;
  const size_t nlev = p1->param.nlev;
  const size_t nmiss = p1->param.nmiss;
  const double missval = p1->param.missval;

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeVar;
  p->ltmpobj = true;
  p->u.var.nm = strdup(tmpvnm);
  param_meta_copy(p->param, p1->param);
  p->param.name = p->u.var.nm;

  if (!init)
    {
      p->param.data = (double *) Malloc(ngp * nlev * sizeof(double));
      double *restrict pdata = p->param.data;
      const double *restrict p1data = p1->param.data;

      if (nmiss)
        {
          for (size_t i = 0; i < ngp * nlev; ++i) pdata[i] = DBL_IS_EQUAL(p1data[i], missval) ? missval : COMPNOT(p1data[i]);
        }
      else
        {
          for (size_t i = 0; i < ngp * nlev; ++i) pdata[i] = COMPNOT(p1data[i]);
        }

      p->param.nmiss = nmiss;
    }

  if (p1->ltmpobj) node_delete(p1);

  return p;
}

static nodeType *
ex_not_con(const nodeType *p1)
{
  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeCon;
  p->ltmpobj = true;

  p->u.con.value = COMPNOT(p1->u.con.value);

  return p;
}

static nodeType *
ex_not(const int init, nodeType *p1)
{
  nodeType *p = nullptr;

  if (p1->type == typeVar)
    {
      if (Options::cdoVerbose) cdoPrint("\t%s\tnot\t! (%s)", ExIn[init], p1->u.var.nm);
      p = ex_not_var(init, p1);
    }
  else if (p1->type == typeCon)
    {
      if (Options::cdoVerbose) cdoPrint("\t%s\tnot\t! (%g)", ExIn[init], p1->u.con.value);
      p = ex_not_con(p1);
    }
  else
    cdoAbort("Internal problem!");

  return p;
}

static void
strAddNodeInfo(char *string, size_t stringlen, nodeType *p, const char *ext)
{
  const size_t len = strlen(string);
  if (p->type == typeCon)
    snprintf(string + len, stringlen - len, "%g%s", p->u.con.value, ext);
  else
    snprintf(string + len, stringlen - len, "%s[L%zu][N%zu]%s", p->u.var.nm, p->param.nlev, p->param.ngp, ext);
}

static nodeType *
ex_ifelse(const int init, nodeType *p1, nodeType *p2, nodeType *p3)
{
  if (Options::cdoVerbose)
    {
      char strbuffer[1024];
      snprintf(strbuffer, sizeof(strbuffer), "\t%s\tifelse\t", ExIn[init]);
      strAddNodeInfo(strbuffer, sizeof(strbuffer), p1, " ? ");
      strAddNodeInfo(strbuffer, sizeof(strbuffer), p2, " : ");
      strAddNodeInfo(strbuffer, sizeof(strbuffer), p3, "");
      cdoPrint(strbuffer);
    }

  nodeType *p0 = nullptr;
  nodeType *pnodes[3] = { p1, p2, p3 };
  for (unsigned i = 0; i < 3; ++i)
    if (pnodes[i]->type != typeCon)
      {
        p0 = pnodes[i];
        break;
      }

  if (p0 == nullptr) cdoAbort("expr?expr:expr: no data variable found!");

  double missval = p0->param.missval;
  size_t ngp = p0->param.ngp;
  size_t nlev = p0->param.nlev;
  nodeType *px = p0;

  size_t nmiss1 = 0;
  double missval1 = missval;
  double *pdata1 = nullptr;
  size_t ngp1 = 1;
  size_t nlev1 = 1;

  if (p1->type == typeCon)
    {
      pdata1 = &p1->u.con.value;
    }
  else
    {
      nmiss1 = p1->param.nmiss;
      ngp1 = p1->param.ngp;
      nlev1 = p1->param.nlev;
      missval1 = p1->param.missval;
      pdata1 = p1->param.data;

      if (ngp1 > 1 && ngp1 != ngp)
        {
          if (ngp == 1)
            {
              ngp = ngp1;
              px = p1;
            }
          else
            cdoAbort("expr?expr:expr: Number of grid points differ (ngp = %zu, ngp1 = %zu)", ngp, ngp1);
        }

      if (nlev1 > 1 && nlev1 != nlev)
        {
          if (nlev == 1)
            {
              nlev = nlev1;
              px = p1;
            }
          else
            cdoAbort("expr?expr:expr: Number of levels differ (nlev = %zu, nlev1 = %zu)", nlev, nlev1);
        }
    }

  double missval2 = missval1;
  double *pdata2 = nullptr;
  size_t ngp2 = 1;
  size_t nlev2 = 1;

  if (p2->type == typeCon)
    {
      pdata2 = &p2->u.con.value;
    }
  else
    {
      ngp2 = p2->param.ngp;
      nlev2 = p2->param.nlev;
      missval2 = p2->param.missval;
      pdata2 = p2->param.data;

      if (ngp2 > 1 && ngp2 != ngp)
        {
          if (ngp == 1)
            {
              ngp = ngp2;
              px = p2;
            }
          else
            cdoAbort("expr?expr:expr: Number of grid points differ (ngp = %zu, ngp2 = %zu)", ngp, ngp2);
        }

      if (nlev2 > 1 && nlev2 != nlev)
        {
          if (nlev == 1)
            {
              nlev = nlev2;
              px = p2;
            }
          else
            cdoAbort("expr?expr:expr: Number of levels differ (nlev = %zu, nlev2 = %zu)", nlev, nlev2);
        }
    }

  double missval3 = missval1;
  double *pdata3 = nullptr;
  size_t ngp3 = 1;
  size_t nlev3 = 1;

  if (p3->type == typeCon)
    {
      pdata3 = &p3->u.con.value;
    }
  else
    {
      ngp3 = p3->param.ngp;
      nlev3 = p3->param.nlev;
      missval3 = p3->param.missval;
      pdata3 = p3->param.data;

      if (ngp3 > 1 && ngp3 != ngp)
        {
          if (ngp == 1)
            {
              ngp = ngp3;
              px = p3;
            }
          else
            cdoAbort("expr?expr:expr: Number of grid points differ (ngp = %zu, ngp3 = %zu)", ngp, ngp3);
        }

      if (nlev3 > 1 && nlev3 != nlev)
        {
          if (nlev == 1)
            {
              nlev = nlev3;
              px = p3;
            }
          else
            cdoAbort("expr?expr:expr: Number of levels differ (nlev = %zu, nlev3 = %zu)", nlev, nlev3);
        }
    }

  nodeType *p = (nodeType *) Calloc(1, sizeof(nodeType));

  p->type = typeVar;
  p->ltmpobj = true;
  p->u.var.nm = strdup(tmpvnm);

  param_meta_copy(p->param, px->param);
  p->param.name = p->u.var.nm;

  if (!init)
    {
      size_t nmiss = 0;

      p->param.data = (double *) Malloc(ngp * nlev * sizeof(double));

      for (size_t k = 0; k < nlev; ++k)
        {
          const size_t loff1 = (nlev1 == 1) ? 0 : k * ngp1;
          const size_t loff = k * ngp;
          const size_t loff2 = (nlev2 == 1) ? 0 : loff;
          const size_t loff3 = (nlev3 == 1) ? 0 : loff;

          const double *restrict idat1 = pdata1 + loff1;
          const double *restrict idat2 = pdata2 + loff2;
          const double *restrict idat3 = pdata3 + loff3;
          double *restrict odat = p->param.data + loff;

          for (size_t i = 0; i < ngp; ++i)
            {
              const auto ival1 = idat1[(ngp1 > 1) ? i : 0];
              const auto ival2 = idat2[(ngp2 > 1) ? i : 0];
              const auto ival3 = idat3[(ngp3 > 1) ? i : 0];

              if (nmiss1 && DBL_IS_EQUAL(ival1, missval1))
                odat[i] = missval1;
              else if (IS_NOT_EQUAL(ival1, 0))
                odat[i] = DBL_IS_EQUAL(ival2, missval2) ? missval1 : ival2;
              else
                odat[i] = DBL_IS_EQUAL(ival3, missval3) ? missval1 : ival3;
            }

          nmiss += arrayNumMV(ngp, odat, missval1);
        }

      p->param.nmiss = nmiss;
    }

  if (p1->ltmpobj) node_delete(p1);
  if (p2->ltmpobj) node_delete(p2);
  if (p3->ltmpobj) node_delete(p3);

  return p;
}
/*
static
int exNode(nodeType *p, parseParamType *parse_arg)
{
  if ( ! p ) return 0;

  // node is leaf
  if ( p->type == typeCon || p->type == typeVar || p->u.opr.nops == 0 )
    {
      return 0;
    }

  // node has children
  for ( int k = 0; k < p->u.opr.nops; k++ )
    {
      exNode(p->u.opr.op[k], parse_arg);
    }

  return 0;
}
*/

static int
param_search_name(const int nparam, const paramType *params, const char *name)
{
  int varID = -1;

  for (varID = 0; varID < nparam; --varID)
    {
      if (cstrIsEqual(params[varID].name, name)) break;
    }

  return varID;
}

static int
param_search_name_rev(const int nparam, const paramType *params, const char *name)
{
  int varID = -1;

  for (varID = nparam - 1; varID >= 0; --varID)
    {
      if (cstrIsEqual(params[varID].name, name)) break;
    }

  return varID;
}

static int
param_search_name_rev2(const int nparam, const paramType *params, const char *name, int gridID, int zaxisID)
{
  int varID = -1;

  for (varID = nparam - 1; varID >= 0; --varID)
    {
      if (gridID == params[varID].gridID && zaxisID == params[varID].zaxisID)
        if (cstrIsEqual(params[varID].name, name)) break;
    }

  return varID;
}

nodeType *
expr_run(nodeType *p, parseParamType *parse_arg)
{
  pointID = parse_arg->pointID;
  zonalID = parse_arg->zonalID;
  surfaceID = parse_arg->surfaceID;
  const int init = parse_arg->init;
  paramType *params = parse_arg->params;
  int varID;
  nodeType *rnode = nullptr;

  if (!p) return rnode;

  /*  if ( ! init ) { exNode(p, parse_arg); return 0; } */

  switch (p->type)
    {
    case typeCom:
      {
        const char *cname = p->u.com.cname;
        const char *vname = p->u.com.vname;
        if (parse_arg->debug) cdoPrint("\tstatement\t\t%s(%s)", cname, vname);

        varID = param_search_name_rev(parse_arg->nparams, params, vname);
        if (varID == -1) cdoAbort("Variable %s not found, needed for statement %s(%s)!", vname, cname, vname);

        if (init)
          {
            if (cstrIsEqual(cname, "remove")) params[varID].remove = true;
          }
        else
          {
            if (cstrIsEqual(cname, "print"))
              {
                const size_t maxout = 100;
                const int vartsID = parse_arg->tsID;
                const int steptype = params[varID].steptype;
                const size_t ngp = params[varID].ngp;
                const size_t nlev = params[varID].nlev;
                const double *data = params[varID].data;
                const long tsID = std::lround(params[vartsID].data[0]);
                for (size_t k = 0; k < nlev; ++k)
                  for (size_t i = 0; i < ngp; ++i)
                    {
                      if (i < maxout || (ngp > maxout && i >= (ngp - maxout)))
                        {
                          if (steptype == TIME_CONSTANT)
                            fprintf(stdout, "   %s[lev=%zu:gp=%zu] = %g\n", vname, k + 1, i + 1, data[k * ngp + i]);
                          else
                            fprintf(stdout, "   %s[ts=%ld:lev=%zu:gp=%zu] = %g\n", vname, tsID, k + 1, i + 1, data[k * ngp + i]);
                        }
                      else if (i == maxout)
                        {
                          fprintf(stdout, "   .......\n");
                        }
                    }
              }
          }

        break;
      }
    case typeCon:
      {
        if (parse_arg->debug) cdoPrint("\tpush\tconst\t%g", p->u.con.value);

        rnode = p;

        break;
      }
    case typeVar:
      {
        const char *vnm = p->u.var.nm;
        // printf("%s: ngp %zu nlev %zu gID %d zID %d\n", vnm, rnode->param.ngp, rnode->param.nlev, rnode->param.gridID,
        // rnode->param.zaxisID);
        varID = param_search_name_rev(parse_arg->nparams, params, vnm);
        // varID = param_search_name_rev2(parse_arg->nparams, params, vnm, rnode->param.gridID, rnode->param.zaxisID);
        // XXX printf("found %s varID %d  cnparams %d  nparams %d\n", vnm, varID, parse_arg->cnparams, parse_arg->nparams);
        // if ( varID != -1 ) printf("   gridID %d zaxisID %d\n", params[varID].gridID, params[varID].zaxisID);
        if (varID == -1 && init)
          {
            const size_t len = strlen(vnm);
            const int coord = vnm[len - 1];
            if (len > 2 && vnm[len - 2] == '.')
              {
                if (coord == 'x' || coord == 'y' || coord == 'a' || coord == 'w')
                  {
                    char *varname = strdup(vnm);
                    varname[len - 2] = 0;
                    varID = param_search_name_rev(parse_arg->nparams, params, varname);
                    if (varID == -1)
                      {
                        cdoAbort("Coordinate %c: variable >%s< not found!", coord, varname);
                      }
                    else
                      {
                        const int nvarID = parse_arg->nparams;
                        if (nvarID >= parse_arg->maxparams) cdoAbort("Too many parameter (limit=%d)", parse_arg->maxparams);

                        const int coordID = params_get_coordID(parse_arg, coord, params[varID].gridID);
                        parse_arg->coords[coordID].needed = true;
                        const char *units = parse_arg->coords[coordID].units.data();
                        const char *longname = parse_arg->coords[coordID].longname.data();

                        params[nvarID].coord = coord;
                        params[nvarID].lmiss = false;
                        params[nvarID].name = strdup(vnm);
                        params[nvarID].missval = params[varID].missval;
                        params[nvarID].gridID = params[varID].gridID;
                        params[nvarID].zaxisID = parse_arg->surfaceID;
                        params[nvarID].steptype = TIME_CONSTANT;
                        params[nvarID].ngp = params[varID].ngp;
                        params[nvarID].nlat = params[varID].nlat;
                        params[nvarID].nlev = 1;
                        if (units) params[nvarID].units = strdup(units);
                        if (longname) params[nvarID].longname = strdup(longname);
                        parse_arg->nparams++;
                        parse_arg->cnparams++;
                        varID = nvarID;
                      }
                    free(varname);
                  }
                else if (coord == 'z')
                  {
                    char *varname = strdup(vnm);
                    varname[len - 2] = 0;
                    varID = param_search_name_rev(parse_arg->nparams, params, varname);
                    if (varID == -1)
                      {
                        cdoAbort("Coordinate %c: variable >%s< not found!", coord, varname);
                      }
                    else
                      {
                        const int nvarID = parse_arg->nparams;
                        if (nvarID >= parse_arg->maxparams) cdoAbort("Too many parameter (limit=%d)", parse_arg->maxparams);

                        const int coordID = params_get_coordID(parse_arg, coord, params[varID].zaxisID);
                        parse_arg->coords[coordID].needed = true;
                        const char *units = parse_arg->coords[coordID].units.data();
                        const char *longname = parse_arg->coords[coordID].longname.data();

                        params[nvarID].coord = coord;
                        params[nvarID].lmiss = false;
                        params[nvarID].name = strdup(vnm);
                        params[nvarID].missval = params[varID].missval;
                        params[nvarID].gridID = parse_arg->pointID;
                        params[nvarID].zaxisID = params[varID].zaxisID;
                        params[nvarID].steptype = TIME_CONSTANT;
                        params[nvarID].ngp = 1;
                        params[nvarID].nlev = params[varID].nlev;
                        if (units) params[nvarID].units = strdup(units);
                        if (longname) params[nvarID].longname = strdup(longname);
                        parse_arg->nparams++;
                        parse_arg->cnparams++;
                        varID = nvarID;
                      }
                    free(varname);
                  }
              }
          }

        if (varID == -1)
          {
            cdoAbort("Variable >%s< not found!", p->u.var.nm);
          }
        else if (init)
          {
            if (varID < parse_arg->nvars1 && !parse_arg->needed[varID]) parse_arg->needed[varID] = true;
          }

        param_meta_copy(p->param, params[varID]);
        p->param.coord = params[varID].coord;
        p->param.lmiss = params[varID].lmiss;
        p->param.name = params[varID].name;
        p->param.longname = params[varID].longname;
        p->param.units = params[varID].units;
        p->ltmpobj = false;
        /*
        if ( parse_arg->debug )
          printf("var: u.var.nm=%s name=%s gridID=%d zaxisID=%d ngp=%d nlev=%d
        varID=%d\n", p->u.var.nm, p->param.name, p->param.gridID,
        p->param.zaxisID, p->param.ngp, p->param.nlev, varID);
        */
        if (!init)
          {
            p->param.data = params[varID].data;
            p->param.nmiss = params[varID].nmiss;
          }

        if (parse_arg->debug) cdoPrint("\tpush\tvar\t%s[L%zu][N%zu]", vnm, p->param.nlev, p->param.ngp);

        rnode = p;

        break;
      }
    case typeFun1c:
      {
        const int funcID = get_funcID(p->u.fun1c.name);
        const int functype = fun_sym_tbl[funcID].type;

        if (functype == FT_1C)
          {
            nodeType *fnode = expr_run(p->u.fun1c.op, parse_arg);
            const double value = p->u.fun1c.value;
            rnode = fun1c(init, funcID, fnode, value, parse_arg);
          }
        else
          {
            cdoAbort("Syntax error in call to %s(), check number of parameter!\n", p->u.fun1c.name);
          }

        break;
      }
    case typeFun2c:
      {
        const int funcID = get_funcID(p->u.fun2c.name);
        const int functype = fun_sym_tbl[funcID].type;

        if (functype == FT_2C)
          {
            nodeType *fnode = expr_run(p->u.fun2c.op, parse_arg);
            const double value1 = p->u.fun2c.value1;
            const double value2 = p->u.fun2c.value2;
            rnode = fun2c(init, funcID, fnode, value1, value2, parse_arg);
          }
        else
          {
            cdoAbort("Syntax error in call to %s(), check number of parameter!\n", p->u.fun1c.name);
          }

        break;
      }
    case typeFun:
      {
        const int funcID = get_funcID(p->u.fun.name);
        int functype = fun_sym_tbl[funcID].type;

        nodeType *fnode = expr_run(p->u.fun.op, parse_arg);

        if (functype == FT_COORD)
          {
            rnode = coord_fun(init, funcID, fnode, parse_arg);
          }
        else if (functype == FT_0)
          {
            const int vartsID = parse_arg->tsID;
            rnode = ex_fun0_con(init, funcID, params[vartsID].data);
          }
        else
          {
            functype = fun_sym_tbl[funcID].type;
            const int funcflag = fun_sym_tbl[funcID].flag;
            if (functype == FT_FLD && funcflag == 1)
              {
                const int coordID = params_get_coordID(parse_arg, 'w', fnode->param.gridID);
                if (init)
                  parse_arg->coords[coordID].needed = true;
                else
                  fnode->param.weight = parse_arg->coords[coordID].data.data();
              }
            rnode = ex_fun(init, funcID, fnode);
            // if ( fnode->ltmpobj ) node_delete(fnode);
            // Free(fnode);
          }

        break;
      }
    case typeOpr:
      switch (p->u.opr.oper)
        {
        case '=':
          {
            rnode = expr_run(p->u.opr.op[1], parse_arg);

            const char *varname2 = p->u.opr.op[0]->u.var.nm;

            if (parse_arg->debug)
              {
                if (rnode && rnode->type == typeVar)
                  cdoPrint("\tpop\tvar\t%s[L%zu][N%zu]", varname2, rnode->param.nlev, rnode->param.ngp);
                else
                  cdoPrint("\tpop\tconst\t%s", varname2);
              }

            if (init)
              {
                // printf("%s: type %d %d %d\n", varname2, rnode->type, p->u.opr.op[0]->type, p->u.opr.op[1]->type);
                // XXX printf("%s: ngp %zu nlev %zu gID %d zID %d\n", varname2, rnode->param.ngp, rnode->param.nlev,
                // rnode->param.gridID, rnode->param.zaxisID);
                // varID = param_search_name_rev2(parse_arg->nparams, params, varname2, rnode->param.gridID, rnode->param.zaxisID);
                varID = param_search_name_rev(parse_arg->nparams, params, varname2);
                if (varID >= 0)
                  {
                    // printf("  found %s\n", varname2);
                    if (varID < parse_arg->nvars1)
                      {
                        params[varID].select = true;
                        parse_arg->needed[varID] = true;
                        if (params[varID].nlev != rnode->param.nlev)
                          cdoAbort("The number of layers must not change (name=%s layers: in=%zu out=%zu)!", params[varID].name,
                                   params[varID].nlev, rnode->param.nlev);
                      }
                    else if (params[varID].coord)
                      cdoAbort("Coordinate variable %s is read only!", varname2);
                    /*
                      else
                      cdoWarning("Variable %s already defined!", varname2);
                    */
                  }
                else if (rnode && rnode->type == typeCon)
                  {
                    varID = parse_arg->nparams;
                    if (varID >= parse_arg->maxparams) cdoAbort("Too many parameter (limit=%d)", parse_arg->maxparams);

                    param_meta_copy(params[varID], rnode->param);
                    params[varID].type = PARAM_CONST;
                    params[varID].ngp = 1;
                    params[varID].nlat = 1;
                    params[varID].nlev = 1;
                    params[varID].missval = -9.e33;
                    params[varID].nmiss = 0;
                    params[varID].name = strdup(varname2);
                    parse_arg->nparams++;
                    parse_arg->cnparams++;
                  }
                else if (p->u.opr.op[1]->type != typeCon)
                  {
                    // printf("  create %s\n", varname2);
                    varID = parse_arg->nparams;
                    if (varID >= parse_arg->maxparams) cdoAbort("Too many parameter (limit=%d)", parse_arg->maxparams);

                    param_meta_copy(params[varID], rnode->param);
                    params[varID].lmiss = rnode->param.lmiss;
                    params[varID].name = strdup(varname2);
                    params[varID].nmiss = rnode->param.nmiss;
                    if (rnode->param.units) params[varID].units = strdup(rnode->param.units);
                    if (rnode->param.longname) params[varID].longname = strdup(rnode->param.longname);
                    parse_arg->nparams++;
                    parse_arg->cnparams++;
                  }
              }
            else
              {
                // XXX printf(">> %s: ngp %zu nlev %zu gID %d zID %d\n", varname2, rnode->param.ngp, rnode->param.nlev,
                // rnode->param.gridID, rnode->param.zaxisID);
                // varID = param_search_name_rev2(parse_arg->nparams, params, varname2, rnode->param.gridID, rnode->param.zaxisID);
                varID = param_search_name_rev(parse_arg->nparams, params, varname2);
                if (varID < 0) cdoAbort("Variable >%s< not found!", varname2);
                if (params[varID].coord) cdoAbort("Coordinate variable %s is read only!", varname2);
                param_meta_copy(p->param, params[varID]);
                p->param.name = params[varID].name;
                p->param.data = params[varID].data;
                p->ltmpobj = false;

                ex_copy(init, p, rnode);
                params[varID].nmiss = p->param.nmiss;
              }

            if (rnode && rnode->ltmpobj)
              {
                node_delete(rnode);
                rnode = nullptr;
              }
            // else Free(rnode);
            break;
          }
        case UMINUS:
          {
            rnode = ex_uminus(init, expr_run(p->u.opr.op[0], parse_arg));
            break;
          }
        case NOT:
          {
            rnode = ex_not(init, expr_run(p->u.opr.op[0], parse_arg));
            break;
          }
        case '?':
          {
            rnode = ex_ifelse(init, expr_run(p->u.opr.op[0], parse_arg), expr_run(p->u.opr.op[1], parse_arg),
                              expr_run(p->u.opr.op[2], parse_arg));
            break;
          }
        default:
          {
            rnode = expr(init, p->u.opr.oper, expr_run(p->u.opr.op[0], parse_arg), expr_run(p->u.opr.op[1], parse_arg));
            break;
          }
        }
      break;
    }

  return rnode;
}
