/* -*- c -*-                    */
/* do not use C++ style comment */
/*                              */


MACRO macro_eval_setup_send_arguments(num, blockptr, flag, blockiseq)
{
    if (flag & VM_CALL_ARGS_BLOCKARG_BIT) {
	rb_proc_t *po;
	VALUE proc;

	proc = TOPN(0);
	if (proc != Qnil) {
	    if (!rb_obj_is_proc(proc)) {
		proc = rb_check_convert_type(proc, T_DATA, "Proc", "to_proc");
		if (!rb_obj_is_proc(proc)) {
		    rb_raise(rb_eTypeError,
			     "wrong argument type %s (expected Proc)",
			     rb_obj_classname(proc));
		}
	    }
	    GetProcPtr(proc, po);
	    blockptr = &po->block;
	    RUBY_VM_GET_BLOCK_PTR_IN_CFP(reg_cfp)->proc = proc;
	}
	INC_SP(-1);
    }
    else if (blockiseq) {
	blockptr = RUBY_VM_GET_BLOCK_PTR_IN_CFP(reg_cfp);
	blockptr->iseq = blockiseq;
	blockptr->proc = 0;
    }

    /* expand top of stack? */
    if (flag & VM_CALL_ARGS_SPLAT_BIT) {
	VALUE ary = TOPN(0);
	VALUE *ptr, *dst;
	int i;
	VALUE tmp = rb_check_convert_type(ary, T_ARRAY, "Array", "to_splat");

	if (NIL_P(tmp)) {
	    tmp = rb_ary_new3(1, ary);
	}
	ary = tmp;

	ptr = RARRAY_PTR(ary);
	dst = GET_SP() - 1;
	for (i = 0; i < RARRAY_LEN(ary); i++) {
	    dst[i] = ptr[i];
	}
	num += i - 1;
	INC_SP(i - 1);
    }
}


MACRO macro_eval_invoke_cfunc(num, id, recv, klass, mn, blockptr)
{
    EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, id, klass);
    {
	rb_control_frame_t *cfp =
	  push_frame(th, 0, FRAME_MAGIC_CFUNC,
		     recv, (VALUE) blockptr, 0, GET_SP(), 0, 1);

	cfp->method_id = id;
	cfp->method_klass = klass;

	reg_cfp->sp -= num + 1;

	val = call_cfunc(mn->nd_cfnc, recv, mn->nd_argc, num, reg_cfp->sp + 1);

	if (reg_cfp != th->cfp + 1) {
	    SDR2(reg_cfp);
	    SDR2(th->cfp-5);
	    rb_bug("cfp consistency error - send");
	    th->cfp = reg_cfp;
	}
	pop_frame(th);
    }
    EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, id, klass);
}

MACRO macro_eval_invoke_func(niseqval, recv, klass, blockptr, num)
{
    rb_iseq_t *niseq;
    VALUE *sp = GET_SP();
    VALUE *rsp = sp - num - 1;
    int opt_pc = 0, clear_local_size, i;

    /* TODO: eliminate it */
    GetISeqPtr(niseqval, niseq);

    clear_local_size = niseq->local_size - num;

    /* simple (only mandatory) arguments */
    if (niseq->arg_simple) {
	if (niseq->argc != num) {
	    rb_raise(rb_eArgError, "%d - wrong number of arguments (%lu for %d)",
		     0, (unsigned long)num, niseq->argc);
	}
    }
    else {
	/* optional arguments */
	if (niseq->arg_opts) {
	    int iseq_argc = niseq->argc;
	    int opts = niseq->arg_opts - 1;

	    if (num < iseq_argc ||
		(niseq->arg_rest == -1 && num > iseq_argc + opts)) {
		if (0) {
		    printf("num: %lu, iseq_argc: %d, opts: %d\n",
			   (unsigned long)num, iseq_argc, opts);
		}
		rb_raise(rb_eArgError,
			 "%d - wrong number of arguments (%lu for %d)",
			 1, (unsigned long)num, iseq_argc);
	    }

	    if (0) printf("num: %lu, opts: %d, iseq_argc: %d\n",
			  (unsigned long)num, opts, iseq_argc);

	    if (num - iseq_argc < opts) {
		opt_pc = niseq->arg_opt_tbl[num - iseq_argc];
		sp += opts - (num - iseq_argc);
		num += opts - (num - iseq_argc);
		clear_local_size = niseq->local_size - (iseq_argc + opts);
	    }
	    else {
		opt_pc = niseq->arg_opt_tbl[opts];
	    }
	}

	/* rest argument */
	if (niseq->arg_rest != -1) {
	    int rest = niseq->arg_rest - 1 /* spec val */;
	    int pack_size = num - rest;

	    if (0) {
		printf("num: %lu, rest: %d, ps: %d\n",
		       (unsigned long)num, rest, pack_size);
	    }

	    if (pack_size < 0) {
		rb_raise(rb_eArgError,
			 "%d - wrong number of arguments (%lu for %d)",
			 2, (unsigned long)num, rest - niseq->arg_opts);
	    }

	    /*
	     * def m(x, y, z, *a)       (rest: 3) =>
	     *    x, y, z, a, b, c <SP> (num: 6, pack_size = 3)
             * => x, y, z, [a,b,c] <SP> (num: 4)
	     */
	    rsp[rest + 1] = rb_ary_new4(pack_size, &rsp[rest + 1]);
	    sp = &rsp[rest + 1 + 1];
	    num = rest + 1;
	    clear_local_size = niseq->local_size - (rest + 1);
	}

	/* block argument */
	if (niseq->arg_block != -1) {
	    VALUE arg_block_val = Qnil;

	    if (!((num == niseq->arg_rest) ||
		  (niseq->arg_opts && num == niseq->argc + niseq->arg_opts - 1)
		  || num == niseq->argc)) {

		if (0) printf("num: %d, rest: %d, opts: %d, argc: %d\n",
			      num, niseq->arg_rest, niseq->arg_opts, niseq->argc);

		rb_raise(rb_eArgError,
			 "%d - wrong number of arguments (%lu for %d)",
			 3, (unsigned long)num, niseq->argc);
	    }

	    if (blockptr) {
		/* make Proc object */
		if (blockptr->proc == 0) {
		    rb_proc_t *proc;
		    reg_cfp->sp = sp;
		    arg_block_val = th_make_proc(th, GET_CFP(), blockptr);
		    GetProcPtr(arg_block_val, proc);
		    blockptr = &proc->block;
		}
		else {
		    arg_block_val = blockptr->proc;
		}
	    }

	    rsp[1 + niseq->arg_block - 1] = arg_block_val;
	    sp++;
	    clear_local_size--;
	}
    }

    /* stack overflow check */
    if (CHECK_STACK_OVERFLOW(th, GET_CFP(), niseq->stack_max + 0x100)) {
	rb_exc_raise(sysstack_error);
    }

    for (i = 0; i < clear_local_size; i++) {
	*sp++ = Qnil;
    }

    if (0 && (flag & VM_CALL_TAILCALL_BIT)) {
	th->cfp++;
	push_frame(th, niseq, FRAME_MAGIC_METHOD,
		   recv, (VALUE) blockptr,
		   niseq->iseq_encoded + opt_pc, sp, 0, 0);
    }
    else if (0 &&
	     (flag & VM_CALL_TAILRECURSION_BIT) && niseq == GET_ISEQ()) {
	/* do nothing */
	GET_CFP()->self = recv;
	SET_LFP(sp);
	SET_DFP(sp);
	*sp++ = (VALUE) blockptr;
	reg_cfp->sp = sp;
	reg_cfp->bp = sp;
	SET_PC(niseq->iseq_encoded + opt_pc);
    }
    else {
	push_frame(th, niseq,
		   FRAME_MAGIC_METHOD, recv, (VALUE) blockptr,
		   niseq->iseq_encoded + opt_pc, sp, 0, 0);
	reg_cfp->sp = rsp;
    }
    RESTORE_REGS();
}

MACRO macro_eval_invoke_method(recv, klass, id, num, mn, blockptr)
{
    /* method missing */
    if (mn == 0) {
	/* temporarily */
	if (id == idMethodMissing) {
	    rb_bug("method missing");
	}
	else {
	    int stat = 0;
	    if (flag & VM_CALL_VCALL_BIT) {
		stat |= NOEX_VCALL;
	    }
	    if (flag & VM_CALL_SUPER_BIT) {
		stat |= NOEX_SUPER;
	    }
	    val = eval_method_missing(th, id, recv, num, blockptr, stat);
	}
    }
    else if (!(flag & VM_CALL_FCALL_BIT) &&
	     (mn->nd_noex & NOEX_MASK) & NOEX_PRIVATE) {
	int stat = NOEX_PRIVATE;
	if (flag & VM_CALL_VCALL_BIT) {
	    stat |= NOEX_VCALL;
	}
	val = eval_method_missing(th, id, recv, num, blockptr, stat);
    }
    else if ((mn->nd_noex & NOEX_MASK) & NOEX_PROTECTED) {
	VALUE defined_class = mn->nd_clss;

	if (TYPE(defined_class) == T_ICLASS) {
	    defined_class = RBASIC(defined_class)->klass;
	}
	if (!rb_obj_is_kind_of(GET_SELF(), rb_class_real(defined_class))) {
	    val =
		eval_method_missing(th, id, recv, num, blockptr,
				    NOEX_PROTECTED);
	}
	else {
	    goto INSN_LABEL(normal_method_dispatch);
	}
    }
    else {
	NODE *node;
	INSN_LABEL(normal_method_dispatch):

	node = mn->nd_body;
	switch (nd_type(node)) {
	  case RUBY_VM_METHOD_NODE:{
	      macro_eval_invoke_func(node->nd_body, recv, klass,
				     blockptr, num);
	      NEXT_INSN();
	  }
	  case NODE_CFUNC:{
	      macro_eval_invoke_cfunc(num, id, recv, klass, node, blockptr);
	      break;
	  }
	  case NODE_ATTRSET:{
	      val = rb_ivar_set(recv, node->nd_vid, TOPN(0));
	      POPN(2);
	      break;
	  }
	  case NODE_IVAR:{
	      val = rb_ivar_get(recv, node->nd_vid);
	      POP();
	      break;
	  }
	  case NODE_BMETHOD:{
	      VALUE *argv = GET_SP() - num;
	      val = th_invoke_bmethod(th, id, node->nd_cval,
				      recv, klass, num, argv);
	      INC_SP(-num-1);
	      break;
	  }
	  case NODE_ZSUPER:{
	      klass = RCLASS(mn->nd_clss)->super;
	      mn = rb_method_node(klass, id);

	      if (mn != 0) {
		  goto INSN_LABEL(normal_method_dispatch);
	      }
	      else {
		  goto LABEL_IS_SC(start_method_dispatch);
	      }
	  }
	  default:{
	      printf("node: %s\n", ruby_node_name(nd_type(node)));
	      rb_bug("eval_invoke_method: unreachable");
	      /* unreachable */
	      break;
	  }
	}
    }
}

