/*====================================================
 * rapl_eval_statement.js "A Tcl like language implementation in Javascript named WebRAPL 
 * (Web Rapid Application Programming Language)"
 *
 * evaluate a statement for RAPL also eval execution traces, if necessary
 *
 * Released under the same terms as Tcl itself.
 * (BSD license found at <http://www.tcl.tk/software/tcltk/license.html>)
 *
 * Based on Picol by Salvatore Sanfilippo (<http://antirez.com/page/picol>)
 * (c) Stéphane Arnold 2007
 * Richard Suchenwirth 2007: cleanup, additions
 * Arnulf Wiedemann    2011: a lot of additions for Tcl 8.6 syntax, itcl support
 */

RP.add("rapl-eval-statement", function(R, name) {

/* =============================== EvalStatement ================================== */

function EvalStatement(interp) {
  R.log('constructor called', '2.life', 'EvalStatement', true);
  
  // kweight
  var stmt = this;
  var constructor = stmt.constructor;
  EvalStatement.superclass.constructor.apply(stmt, arguments);

  R.Base.eval_statment_oid++;
  stmt.oid = R.Base.eval_statement_oid;

  stmt.interp           = interp;
  stmt.call_level       = 0;
  stmt.eval_level       = 0;
  stmt.expr_eval_level  = 0;
  stmt.level_infos      = new Array();
  stmt.enterstep_traces = new Array();
  stmt.leavestep_traces = new Array();
  stmt.inLoop           = false;
  stmt.level            = 0;
  stmt.no_trace         = false;
  stmt.call_debug       = 0;
  stmt.eval_stmt_debug  = 0;
  stmt.eval_expr_debug  = 0;
  stmt.eval_ipol_debug  = 0;
  stmt.eval_obj_debug   = 0;
  stmt.eval_subst_debug = 0;
  stmt.ignore_var_not_found = false;
  stmt.code             = stmt.OK;
  stmt.file_name        = null;
  stmt.in_quoted_string = false;
  stmt.in_brace = false;
  stmt.cmd_name = "";
  stmt.last_token = 0;
  stmt.intv = null;
  stmt.in_quotes = false;
  stmt.had_macro_cmd = false;

  R.log('constructor end', '2.life', 'EvalStatement', true);

}

R.extend(EvalStatement, R.Token, {
  my_name: "RaplEvalStatement",

  /* ==================== substOneToken ===================================== */
  substOneToken: function (script, idx, result_ptr) {
    var stmt = this;
    var obj_ptr;
    var token = script.script_object.tokens[idx];

if (stmt.eval_subst_debug) {
script.dumpToken(idx);
print("substOneToken!"+token.token+"!"+stmt.getTokenString(token.token)+"!"+token.obj_ptr.mySelf()+"!"+token.obj_ptr.getString()+"!");
}

    result_ptr[0] = null;
    switch (token.token) {
    case stmt.TOKEN_QUOTE_ESC:
      stmt.in_quotes = true;
      /* fall through */
    case stmt.TOKEN_STR:
    case stmt.TOKEN_ESC:
      obj_ptr = token.obj_ptr;
      break;
    case stmt.TOKEN_VAR_ARRAY_NAME:
    case stmt.TOKEN_VAR:
      obj_ptr = stmt.interp.variable_obj_type.getVariable(token.obj_ptr, stmt.FUNCTION_FLAGS_LEAVE_ERR_MSG);
      if (stmt.interp.frame_ptr.type == stmt.CALL_TYPE_MACROEXPAND) {
        /* if we are expanding a macro and we find a not escaped variable not in the
	 * parameter/argument list, for the macro, we ignore that and return the variable
	 * as "$var_name" instead
	 */
        if (obj_ptr == null) {
          obj_ptr = stmt.interp.string_obj_type.newStringObj("$"+token.obj_ptr.getString(), -1);
	  stmt.interp.setResult(stmt.interp.empty_string_obj);
	} else {
          /* if we are expanding a macro and the variable is a list put it into {} to be safe */
          if (obj_ptr.obj_type = stmt.interp.list_obj_type) {
            obj_ptr = stmt.interp.string_obj_type.newStringObj("{"+obj_ptr.getString()+"}", -1);
	  }
	}
      } else {
        if (stmt.had_macro_cmd) {
          obj_ptr = stmt.interp.string_obj_type.newStringObj("$"+token.obj_ptr.getString(), -1);
	}
      }
      break;
    case stmt.TOKEN_ARRAY_NAME_VAR:
      obj_ptr = JimExpandDictSugar(stmt.interp, token.obj_ptr);
      break;
    case stmt.TOKEN_EXPRSUGAR:
      obj_ptr = stmt.interp.expr_obj_type.expandExprSugar(token.obj_ptr);
      break;
    case stmt.TOKEN_BRACE:
      if (!stmt.in_quotes) {
        obj_ptr = token.obj_ptr;
      } else {
        if (token.obj_ptr.len == 0) {
          /* we have a single pair of {} within quotes,
	   * so make a corresponding string object 
	   */
	  obj_ptr = stmt.interp.string_obj_type.newStringObj("{}", -1);
	} else {
        switch (stmt.interp.script_obj_type.substObj(token.obj_ptr, result_ptr, stmt.FUNCTION_FLAGS_LEAVE_ERR_MSG)) {
        case stmt.OK:
	  obj_ptr = result_ptr[0];
	  /* special handling for expand in quotes !! */
	  if (obj_ptr.len == 1 && obj_ptr.bytes.substring(0,1) == "*") {
            obj_ptr.len = 3;
	    obj_ptr.bytes = "{*}"+"\0";
	  }
          break;
        default:
          return stmt.ERROR;
        }
        }
      }
      break;
    case stmt.TOKEN_CMD:
      if (stmt.interp.frame_ptr.type == stmt.CALL_TYPE_MACROEXPAND) {
        stmt.had_macro_cmd = true;
        stmt.macro_cmd_level = stmt.eval_level;
      }
      switch (stmt.evalObj(token.obj_ptr)) {
      case stmt.OK:
      case stmt.RETURN:
        obj_ptr = stmt.interp.result;
        break;
      case stmt.BREAK:
        /* Stop substituting */
        return stmt.BREAK;
      case stmt.CONTINUE:
        /* just skip this one */
        return stmt.CONTINUE;
      default:
        return stmt.ERROR;
      }
      break;
    case stmt.TOKEN_NONE:
print("TOKEN_NONE");
//FIXME !!!
      break;
    default:
      stmt.panic(1, "default token type ("+token.token+") "+stmt.getTokenString(token.token)+"  reached in substOneToken().");
      obj_ptr = null;
      break;
    }
    if (obj_ptr) {
      result_ptr[0] = obj_ptr;
      return stmt.OK;
    }
print("SUBST3 ERROR!"+stmt.interp.getResult()+"!"+stmt.interp.frame_ptr.ns_ptr.toDebugString()+"!");
script.dumpToken(idx);
    return stmt.ERROR;
  },


  /* ==================== interpolateTokens ===================================== */
  /* Interpolate the given tokens into a unique RaplObject returned by reference
   * via obj_ptr. This function is only called by evalObj() and substObj()
   * The returned object has refcount = 0.
   */
  interpolateTokens: function (script, idx, word_tokens, flags) {
    var stmt = this;
    var total_len = 0;
    var i;
    var j;
    var obj_ptr = null;
    var str;
    var intv2;
    var token = script.script_object.tokens;
    var result_ptr;
    var retcode;
    var my_in_quotes = stmt.in_quotes;

    stmt.in_quotes = false;
    intv2 = new Array();
    /* Compute every token forming the argument
     * in the intv2 objects vector. 
     */
    j = 0;
    for (i = 0; i < word_tokens; i++) {
if (stmt.eval_ipol_debug) {
print("IPOL!"+i+"!"+idx+"!");
script.dumpToken(i+idx);
}
      if (script.script_object.tokens[i + idx].token == stmt.TOKEN_COMMENT) {
        continue;
      }
      result_ptr = new Array();
      retcode = stmt.substOneToken(script, idx + i, result_ptr);
      intv2[j] = result_ptr[0];
if (stmt.eval_ipol_debug > 1) {
print("RES!"+result_ptr[0]+"!"+intv2+"!"+retcode+"!");
script.dumpToken(i+idx);
}
      if (intv2[j] != null) {
        intv2[j].incrRefCount();
      }
      switch(retcode) {
      case stmt.OK:
      case stmt.RETURN:
        break;
      case stmt.BREAK:
        if (flags & stmt.SUBST_FLAG) {
          /* Stop here */
          tokens = i;
          continue;
        }
        /* XXX: Should probably set an error about break outside loop */
        /* fall through to error */
      case stmt.CONTINUE:
        if (flags & stmt.SUBST_FLAG) {
          intv2[i] = null;
          continue;
        }
        /* XXX: Ditto continue outside loop */
        /* fall through to error */
      default:
        while (j--) {
          if (intv2[j] != null) {
            intv2[j].decrRefCount();
	  }
        }
        intv2 = null;
	stmt.in_quotes = my_in_quotes;
        return null;
      }
      total_len += intv2[j].toString().length;
      j++;
    }

    /* Fast path return for a single token */
    if (word_tokens == 1 && intv2[0]) {
      intv2[0].decrRefCount();
      stmt.in_quotes = my_in_quotes;
      return intv2[0];
    }

    /* Concatenate every token in an unique
     * object. 
     */
    obj_ptr = stmt.interp.string_obj_type.newStringObjNoAlloc(null, 0);

    if (word_tokens == 4 && token.length > i + idx + 2 && token[i + idx + 0].token == stmt.TOKEN_ESC && token[i + idx + 1].token == stmt.TOKEN_ESC
        && token[i + idx + 2].token == stmt.TOKEN_VAR) {
      /* May be able to do fast interpolated object  -> array */
      obj_ptr.obj_type = obj_ptr.interpolatedObjType;
      obj_ptr.twoPtrValue.ptr1(token);
      obj_ptr.twoPtrValue.ptr2(intv2[idx + 2]);
      intv2[idx + 2].incrRefCount();
    }

    obj_ptr.bytes = "";
    obj_ptr.length = total_len;
    for (i = 0; i < j; i++) {
      if (intv2[i]) {
        obj_ptr.bytes += intv2[i].toString();
        intv2[i].decrRefCount();
      }
    }
//    obj_ptr.bytes[total_len] = '\0';
    obj_ptr.len = total_len;
    /* Free the intv2 vector. */
    intv2 = null;
if (stmt.eval_ipol_debug > 1) {
print("interpolateTokens END!"+obj_ptr.toDebugString()+"!");
}
    stmt.in_quotes = my_in_quotes;
    return obj_ptr;
  },

  /* ==================== evalObj ===================================== */
  evalObj: function (script_obj_ptr) {
    var stmt = this;
    var i;
    var script;
    var token;
    var retcode = stmt.OK;
    var sargv = null;
    var line_no = 0;
    var had_comment = false;

    stmt.eval_level++;
//print("EVALOBJ level!"+stmt.eval_level+"!"+script_obj_ptr+"!");
//print("EVALOBJ level!"+stmt.eval_level+"!"+script_obj_ptr.mySelf()+"!");
    stmt.interp.error_flag = 0;

    /* If the object is of type "list", with no string rep we can call
     * a specialized version of evalObj() */
    if (script_obj_ptr.isListObj() && script_obj_ptr.bytes == null) {
      retcode = stmt.evalObjList(script_obj_ptr, stmt.interp.empty_obj, 1);
      if (stmt.macro_cmd_level == stmt.eval_level) {
        stmt.had_macro_cmd = false;
      }
      stmt.eval_level--;
      return retcode;
    }
    script_obj_ptr.incrRefCount();     /* Make sure it's shared. */
    script = script_obj_ptr.interp.script_obj_type.getScript(script_obj_ptr);
    if (script == null) {
      stmt.interp.setResultString("error in parsing script: \""+script_obj_ptr+"\"");
      if (stmt.macro_cmd_level == stmt.eval_level) {
        stmt.had_macro_cmd = false;
      }
      stmt.eval_level--;
      return stmt.ERROR;
    }

    /* Reset the interpreter result. This is useful to
     * return the empty result in the case of empty program. */
    script_obj_ptr.interp.setEmptyResult();

    /* Check for one of the following common scripts used by for, while
     *
     *   {}
     *   incr a
     */
    if (script.len == 0) {
      script_obj_ptr.decrRefCount();
      if (stmt.macro_cmd_level == stmt.eval_level) {
        stmt.had_macro_cmd = false;
      }
      stmt.eval_level--;
      return stmt.OK;
    }
    if (script.len == 3
        && script.script_object.tokens[1].obj_ptr.obj_type.type_name == "command"
        && script.script_object.tokens[1].obj_ptr.cmdValue.cmdPtr().is_proc == false
        && script.script_object.tokens[1].obj_ptr.cmdValue.cmdPtr().u.native_fcn.cmd_proc == script.interp.incrCoreCommand
        && script.script_object.tokens[2].obj_ptr.obj_typ.type_name == "variable") {

      var obj_ptr = stmt.interp.getVariable(script.script_object.tokens[2].obj_ptr, stmt.TOKENNONE);

      if (obj_ptr && !obj_ptr.isShared() && obj_ptrobj_type.name == "int") {
//        JimWideValue(obj_ptr)++;
        obj_ptr.invalidateStringRep();
        script_obj_ptr.decrRefCount();
        stmt.interp.setResult(obj_ptr);
        if (stmt.macro_cmd_level == stmt.eval_level) {
          stmt.had_macro_cmd = false;
        }
        stmt.eval_level--;
        return stmt.OK;
      }
    }
    /* Now we have to make sure the internal repr will not be
     * freed on shimmering.
     *
     * Think for example to this:
     *
     * set x {llength $x; ... some more code ...}; eval $x
     *
     * In order to preserve the internal rep, we increment the
     * inUse field of the script internal rep structure. 
     */
    script.ref_count++;
    token = script.script_object.tokens;
    /* Execute every command sequentially until the end of the script
     * or an error occurs.
     */
    for (i = 0; i < script.len && retcode == stmt.OK; ) {
      var argc;
      var argv = new Array();
      var j;
      var cmd;
      var num_comments = 0;

      /* First token of the line is always TOKEN_LINE */
      argc = token[i].obj_ptr.scriptLineValue.argc();
//print("I!"+i+"!"+argc+"!"+script.getTokenString(token[i].token)+"!"+token[i].obj_ptr.toDebugString()+"!");
      linenr = token[i].obj_ptr.scriptLineValue.line();
      /* Skip the TOKEN_LINE token */
      i++;
      /* Populate the arguments objects.
       * If an error occurs, retcode will be set and
       * 'j' will be set to the number of args expanded
       */
      had_comment = false;
      for (j = 0; j < argc; j++) {
//print("J!"+j+"!"+argc+"!"+argv+"!");
        var word_tokens = 1;
        var expand = 0;
        var word_obj_ptr = null;

//print("J!"+j+"!"+script.getTokenString(token[i].token)+"!"+token[i].obj_ptr.toDebugString()+"!");
        if (token[i].token == stmt.TOKEN_WORD) {
//script.dumpToken(i);
          word_tokens = token[i++].obj_ptr.wideValue();
          if (word_tokens < 0) {
            expand = 1;
            word_tokens = -word_tokens;
          }
        }
	had_comment = false;
        if (word_tokens == 1) {
          /* Fast path if the token does not
           * need interpolation 
           */
//print("word_tokens 1!");
//script.dumpToken(i);
          switch (token[i].token) {
          case stmt.TOKEN_COMMENT:
	    had_comment = true;
	    num_comments++;
            break;
          case stmt.TOKEN_QUOTE_ESC:
	    /* fall through */
          case stmt.TOKEN_ESC:
          case stmt.TOKEN_STR:
            word_obj_ptr = token[i].obj_ptr;
            break;
          case stmt.TOKEN_VAR_ARRAY_NAME:
            word_obj_ptr = stmt.interp.array_obj_type.expandArray(token[i].obj_ptr);
            break;
          case stmt.TOKEN_VAR:
            word_obj_ptr = stmt.interp.variable_obj_type.getVariable(token[i].obj_ptr, stmt.FUNCTION_FLAGS_LEAVE_ERR_MSG);
            break;
          case stmt.TOKEN_EXPRSUGAR:
            word_obj_ptr = stmt.expandExprSugar(token[i].obj_ptr);
            break;
          case stmt.TOKEN_ARRAY_NAME_VAR:
            word_obj_ptr = stmt.expandDictSugar(token[i].obj_ptr);
            break;
          case stmt.TOKEN_BRACE:
            if (!stmt.in_quotes) {
              word_obj_ptr = token[i].obj_ptr;
	    } else {
              var result_ptr = new Array();
              retcode = stmt.interp.script_obj_type.substObj(token[i].obj_ptr, result_ptr, stmt.FUNCTION_FLAGS_LEAVE_ERR_MSG);
              if (retcode == stmt.OK) {
                word_obj_ptr = result_ptr[0];
              }
	    }
            break;
          case stmt.TOKEN_CMD:
            retcode = stmt.evalObj(token[i].obj_ptr);
            if (retcode == stmt.OK) {
              word_obj_ptr = stmt.interp.result;
            }
            break;
          default:
            stmt.panic(1, "default token type reached in evalObj(): "+stmt.getTokenString(token[i].token));
          }
        } else {
          /* For interpolation we call a helper
           * function to do the work for us. 
           */
//print("interpolate!");
//script.dumpToken(i);
          word_obj_ptr = stmt.interpolateTokens(script, i, word_tokens, stmt.TOKEN_NONE);
//print("interpolate!"+word_obj_ptr.toDebugString()+"!");
          if (word_obj_ptr != null && word_obj_ptr.len == 0) {
            /* we had only comment tokens, nothing to do */
            had_comment = true;
          }
        }
        i += word_tokens;
	if (!had_comment) {
          if (!word_obj_ptr) {
            if (retcode == stmt.OK) {
              retcode = stmt.ERROR;
            }
            break;
          }
          word_obj_ptr.incrRefCount();
          if (!expand) {
	    if (!had_comment) {
              argv.push(word_obj_ptr);
	    }
          } else {
            /* Need to expand word_obj_ptr into multiple args from argv[j] ... */
            var len = stmt.interp.list_obj_type.listLength(word_obj_ptr);
            var newargc = argc + len - 1;
            var k;
  
//print("EXPAND!"+argc+"!"+len+"!"+newargc+"!"+word_obj_ptr.toDebugString()+"!");
            /* Now copy in the expanded version */
            for (k = 0; k < len; k++) {
              argv.push(word_obj_ptr.listValue.elem()[k]);
	      j++;
              word_obj_ptr.listValue.elem()[k].incrRefCount();
            }
            /* The original object reference is no longer needed,
             * after the expansion it is no longer present on
             * the argument vector, but the single elements are
             * in its place. 
	     */
            word_obj_ptr.decrRefCount();
            /* And update the indexes */
            j--;
            argc += len - 1;
          }
        }
      }
      if (had_comment) {
        continue
      }
      argc -= num_comments;
      if (retcode == stmt.OK && argc) {
        /* Lookup the command to call */
//        cmd = stmt.interp.command_obj_type.getCommand(argv[0], stmt.FUNCTION_FLAGS_LEAVE_ERR_MSG);
        cmd = stmt.interp.getCommand(argv[0]);
	if (cmd == null && argc == 1) {
          /* check if the argument looks like a list and the expand it (old style!!) */
          var len = stmt.interp.list_obj_type.listLength(argv[0]);
	  var result_ptr = new Array();
	  var ret = stmt.interp.script_obj_type.substObj(argv[0], result_ptr, stmt.FUNCTION_FLAGS_NONE)
	  if (ret == stmt.OK) {
	    result_ptr = result_ptr[0];
            len = stmt.interp.list_obj_type.listLength(result_ptr);
	    if (len > 1) {
	      var my_argc = new Array();
	      var my_argv = new Array();
              if (len > 1) {
                stmt.interp.list_obj_type.listGetElements(result_ptr, my_argc, my_argv);
	        argc = my_argc[0];
	        argv = my_argv[0];
                cmd = stmt.interp.getCommand(argv[0]);
	      }
	    }
	  }
	}
        if (cmd != null) {
          /* Call it -- Make sure result is an empty object. */
          cmd.incrCmdRefCount();
          stmt.interp.setEmptyResult();
//print("CMDT!"+argv[0]+"!"+cmd.getCommandTypeString(cmd.command_type)+"!");
//print("argv!"+argc+"!"+argv+"!"+argv.length+"!");
          if (cmd.is_proc) {
            retcode = cmd.u.proc.cmd_proc(cmd, script.script_object.file_name_obj, line_no, argc, argv);
          } else {
            /* Check if there are too nested calls */
//print("FR!"+intp.frame_ptr.toDebugString()+"!"+intp.max_nesting_depth+"!");
            stmt.interp.cmd_priv_data = cmd.u.native_fcn.privdata;
            retcode = cmd.u.native_fcn.cmd_proc(stmt.interp, argv, cmd);
          }
          cmd.decrCmdRefCount();
        } else {
print("UNK!"+argv[0]+"!"+argv[0].toDebugString()+"!");
          if (! (argc == 1 && argv == "")) {
//print("RETC!"+retcode+"!"+argc+"!"+argv+"!");
          /* Call [unknown] */
            retcode = stmt.interp.unknownFcn(argc, argv, script.script_object.file_name_obj, line_no);
	  } else {
            /* we do not call unknown, if we had only the empty string as command and no arguments */
	  }
        }
      }
//      /* Finished with the command, so decrement ref counts of each argument */
      j = argv.length - 1;
      for (; j > 0; j--) {
        argv[j].decrRefCount();
      }
      argv = new Array();
    }

    /* Possibly add to the error stack trace */
    stmt.addErrorToStack(retcode, script.script_object.file_name_obj, line_no);

    /* Note that we don't have to decrement ref_count, because the
     * following code transfers our use of the reference again to
     * the script object. */
    script_obj_ptr.freeIntRep();
    script_obj_ptr.obj_type = stmt.interp.script_obj_type;
    script_obj_ptr.setIntRepPtr(script);
    script_obj_ptr.decrRefCount();
//print("EVALOBJ end level!"+stmt.eval_level+"!"+retcode+"!");
    if (stmt.macro_cmd_level == stmt.eval_level) {
      stmt.had_macro_cmd = false;
    }
    stmt.eval_level--;
    return retcode;
  },

  /* ==================== _evalObjVector ===================================== */
  /* Eval the object vector 'objv' composed of 'objc' elements.
   * Every element is used as single argument.
   * evalObj() will call this function every time its object
   * argument is of "list" type, with no string representation.
   *
   * This is possible because the string representation of a
   * list object generated by the list_obj_type.UpdateString is made
   * in a way that ensures that every list element is a different
   * command argument. 
   */
  _evalObjVector: function(objc, objv, file_name_obj, line_no) {
    var stmt = this;
    var i;
    var retcode;
    var cmd_ptr;

    /* Incr refcount of arguments. */
    for (i = 0; i < objc; i++) {
      objv[i].incrRefCount();
    }
    /* Command lookup */
    cmd_ptr = stmt.interp.getCommand(objv[0], stmt.interp.FUNCTION_FLAGS_LEAVE_ERR_MSG);
//print("EOV!"+cmd_ptr.toDebugString()+"!");
    if (cmd_ptr == null) {
      retcode = stmt.interp.unknown(objc, objv, file_name_obj, line_no);
    } else {
      if (cmd_ptr.command_type == stmt.interp.COMMAND_ENSEMBLE) {
        var i = 1;
	var ens = cmd_ptr.ensemble;

	while (true) {
          var found = false;
          for (var z in ens) {
            if (z == objv[i].getString()) {
              found = true;
	      break;
	    }
	  }
	  if (found) {
            if (ens[z].ensemble == null) {
              cmd_ptr = ens[z];
	      break;
	    } else {
              ens = ens[z].ensemble;
              i++;
            }
	  } else {
            var str = "";

	    for (var k = 0; k <= i; k++) {
              str += " "+objv[k];
	    }
	    stmt.interp.setResultString("no such command!"+str+"!");
	    return stmt.ERROR;
	  }
	}
      }
      /* Call it -- Make sure result is an empty object. */
      cmd_ptr.incrCmdRefCount();
      stmt.interp.setEmptyResult();
      if (cmd_ptr.is_proc) {
        retcode = stmt.interp.command_obj_type.callProcedure(cmd_ptr, file_name_obj, line_no, objc, objv);
      } else {
        stmt.interp.cmd_priv_data = cmd_ptr.u.native_fcn.priv_data;
        retcode = cmd_ptr.u.native_fcn.cmd_proc(stmt.interp, objv);
      }
      cmd_ptr.decrCmdRefCount();
    }
    /* Decr refcount of arguments and return the retcode */
    for (i = 0; i < objc; i++) {
      objv[i].decrRefCount();
    }
    return retcode;
  },

  /* ==================== evalObjVector ===================================== */
  evalObjVector: function(objc, objv) {
    var stmt = this;

    return stmt._evalObjVector(objc, objv, stmt.interp.empty_obj, 1);
 },


  /* ==================== evalObjList ===================================== */
  /* If list_ptr is a list, call evalObjVector() with the given source info.
   * Otherwise eval with evalObj()
   */
  evalObjList: function (list_ptr, file_name_obj, linenr) {
    var stmt = this;
    var retcode = stmt.OK;

    stmt.interp.panic(!list_ptr.isListObj(), "evalObjList() called without list arg");
    if (list_ptr.listValue.len()) {
      list_ptr.incrRefCount();
      retcode = stmt.evalObjVector(list_ptr.listValue.len(), list_ptr.listValue.elem(), file_name_obj, linenr);
      list_ptr.decrRefCount();
    }
    return retcode;
  },

  /* ==================== commandMatchObj ===================================== */
  /* Returns 1 if match, 0 if no match or -<error> on error (e.g. -stmt.ERROR, -stmt.BREAK)*/
  commandMatchObj: function(command_obj, pattern_obj, string_obj, nocase) {
    var stmt = this;
    var parms = new Array();
    var argc = 0;
    var eq = new Array();
    var rc;

    parms[argc++] = command_obj;
    if (nocase) {
      parms[argc++] = stmt.interp.string_obj_type.newStringObj("-nocase", -1);
    }
    parms[argc++] = pattern_obj;
    parms[argc++] = string_obj;
    rc = stmt.evalObjVector(argc, parms);
    if (rc != stmt.OK || stmt.interp.int_obj_type.getLong(stmt.interp.getResult(), eq) != stmt.OK) {
      eq[0] = -rc;
    }
    return eq[0];
  },

  /* ==================== addErrorToStack ===================================== */
  addErrorToStack: function(retcode, file_name_obj, line) {
    var stmt = this;
    var rc = retcode;

    if (rc == stmt.ERROR && !stmt.interp.error_flag) {
        /* This is the first error, so save the file/line information and reset the stack */
        stmt.interp.error_flag = 1;
        file_name_obj.incrRefCount();
        stmt.interp.error_file_name_obj.decrRefCount();
        stmt.interp.error_file_name_obj = file_name_obj;
        stmt.interp.error_line = line;
        stmt.resetStackTrace();
        /* Always add a level where the error first occurs */
        stmt.interp.add_stack_trace++;
    }

    /* Now if this is an "interesting" level, add it to the stack trace */
    if (rc == stmt.ERROR && stmt.interp.add_stack_trace > 0) {
        /* Add the stack info for the current level */

        stmt.appendStackTrace(file_name_obj.getString(stmt.interp.error_proc), file_name_obj, line);

        /* Note: if we didn't have a filename for this level,
         * don't clear the addStackTrace flag
         * so we can pick it up at the next level
         */
        if (file_name_obj.getStringLength()) {
          stmt.interp.add_stack_trace = 0;
        }

        stmt.interp.error_proc.decrRefCount();
        file_name_obj.interp.error_proc = stmt.interp.empty_obj;
        stmt.interp.error_proc.incrRefCount();
    } else {
      if (rc == stmt.RETURN && stmt.interp.return_code == stmt.ERROR) {
        /* Propagate the addStackTrace value through 'return -code error' */
      } else {
        stmt.interp.add_stack_trace = 0;
      }
    }
  },

  /* ==================== resetStackTrace ===================================== */
  resetStackTrace: function() {
    var stmt = this;
    stmt.interp.stack_trace.decrRefCount();
    stmt.interp.stack_trace = stmt.interp.list_obj_type.newListObj(null, 0);
    stmt.interp.stack_trace.incrRefCount();
  },

  /* ==================== setStackTrace ===================================== */
  setStackTrace: function(stack_trace_obj) {
    var stmt = this;
    var len;

    /* Increment reference first in case these are the same object */
    stack_trace_obj.incrRefCount();
    stmt.interp.stack_trace.decrRefCount();
    stmt.interp.stack_trace = stack_trace_obj;
    stmt.interp_error_flag = 1;

    /* This is a bit ugly.
     * If the filename of the last entry of the stack trace is empty,
     * the next stack level should be added.
     */
    len = stmt.interp.stack_trace.getStringLength();
    if (len >= 3) {
      var filename_obj;

      stmt.interp.list_obj_type.listIndex(stmt.interp.stack_trace, len - 2, filename_obj, stmt.FUNCTION_FLAGS_NONE);

      len = filename_obj.getStringLength();

      if (!filename_obj.getStringLength()) {
        stmt.interp.add_stack_trace = 1;
      }
    }
  },

  /* ==================== appendStackTrace ===================================== */
  /* Returns 1 if the stack trace information was used or 0 if not */
  appendStackTrace: function(procname, file_name_obj, linenr) {
    var stt = this;
    if (procname == "unknown") {
      procname = "";
    }
    if (!procname && !file_name_obj.getStringLength()) {
      /* No useful info here */
      return;
    }

    if (stmt.interp.stack_trace.isShared()) {
      stmt.interp.stack_trace.decrRefCount();
      stmt.interp.stack_trace = file_name_obj.duplicateObj(stmt.interp.stack_trace);
      stmt.interp.stack_trace.incrRefCount();
    }

    /* If we have no procname but the previous element did, merge with that frame */
    if (!procname && file_name_obj.getStringLength()) {
      /* Just a filename. Check the previous entry */
      var len = stmt.interp.list_obj_type.listLength(stmt.interp.stack_trace);

      if (len >= 3) {
        var obj_ptr;
        if (stmt.interp.list_obj_type.listIndex(stmt.interp.stack_trace, len - 3, obj_ptr, stmt.FUNCTION_FLAGS_NONE) == stmt.OK && obj_ptr.getStringLength()) {
          /* Yes, the previous level had procname */
          if (stmt.interp.list_obj_type.listIndex(stmt.interp.stack_trace, len - 2, objPtr, stmt.FUNCTION_FLAGS_NONE) == stmt.OK && !obj_ptr.getStringLength()) {
            /* But no filename, so merge the new info with that frame */
            stmt.interp.list_obj_type.listSetIndex(stmt.interp.stack_trace, len - 2, file_name_obj, 0);
            stmt.interp.list_obj_type.listSetIndex(stmt.interp.stack_trace, len - 1, stmt.interp.int_obj_type.newIntObj(linenr), 0);
            return;
          }
        }
      }
    }
    stmt.interp.list_obj_type.listAppendElement(stmt.interp.stack_trace, stmt.interp.string_obj_type.newStringObj(procname, -1));
    stmt.interp.list_obj_type.listAppendElement(stmt.interp.stack_trace, file_name_obj);
    stmt.interp.list_obj_type.listAppendElement(stmt.interp.stack_trace, stmt.interp.int_obj_type.newIntObj(linenr));
  },

});

EvalStatement.prototype.constructor = EvalStatement;

R.EvalStatement = EvalStatement;

}, "0.0.1", {});
