/* -*- Mode: C; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/* vim: set filetype=c tabstop=2 shiftwidth=2 expandtab: */

/*
 * eval.d - evaluation
 *
 * Copyright (c) 1984 Taiichi Yuasa and Masami Hagiya
 * Copyright (c) 1990 Giuseppe Attardi
 * Copyright (c) 2001 Juan Jose Garcia Ripoll
 *
 * See file 'LICENSE' for the copyright details.
 *
 */

#include <ecl/ecl.h>
#include <ecl/ecl-inl.h>
#include <ecl/internal.h>

cl_object *
_ecl_va_sp(cl_narg narg)
{
  return ECL_STACK_FRAME_PTR(ecl_process_env()->stack_frame) + narg;
}

/* Calling conventions:
 * Compiled C code calls lisp function supplying #args, and args.
 * Linking function performs check_args, gets jmp_buf with _setjmp, then
 *  if cfun then stores C code address into function link location
 *          and transfers to jmp_buf at cf_self
 *  if cclosure then replaces #args with cc_env and calls cc_self
 *  otherwise, it emulates funcall.
 */

cl_object
ecl_apply_from_stack_frame(cl_object frame, cl_object x)
{
  cl_object *sp = ECL_STACK_FRAME_PTR(frame);
  cl_index narg = frame->frame.size;
  cl_object fun = x;
  cl_object ret;
  frame->frame.env->stack_frame = frame;
 AGAIN:
  frame->frame.env->function = fun;
  if (ecl_unlikely(fun == ECL_NIL))
    FEundefined_function(x);
  switch (ecl_t_of(fun)) {
  case t_cfunfixed:
    if (ecl_unlikely(narg != (cl_index)fun->cfun.narg))
      FEwrong_num_arguments(fun);
    ret = APPLY_fixed(narg, fun->cfunfixed.entry_fixed, sp);
    break;
  case t_cfun:
    ret = APPLY(narg, fun->cfun.entry, sp);
    break;
  case t_cclosure:
    ret = APPLY(narg, fun->cclosure.entry, sp);
    break;
  case t_instance:
    switch (fun->instance.isgf) {
    case ECL_STANDARD_DISPATCH:
    case ECL_RESTRICTED_DISPATCH:
      ret = _ecl_standard_dispatch(frame, fun);
      break;
    case ECL_USER_DISPATCH:
      fun = fun->instance.slots[fun->instance.length - 1];
      goto AGAIN;
    case ECL_READER_DISPATCH:
    case ECL_WRITER_DISPATCH:
      ret = APPLY(narg, fun->instance.entry, sp);
      break;
    default:
      FEinvalid_function(fun);
    }
    break;
  case t_symbol:
    if (ecl_unlikely(!ECL_FBOUNDP(fun)))
      FEundefined_function(fun);
    fun = ECL_SYM_FUN(fun);
    goto AGAIN;
  case t_bytecodes:
    ret = ecl_interpret(frame, ECL_NIL, fun);
    break;
  case t_bclosure:
    ret = ecl_interpret(frame, fun->bclosure.lex, fun->bclosure.code);
    break;
  default:
    FEinvalid_function(x);
  }
  frame->frame.env->stack_frame = NULL; /* for gc's sake */
  return ret;
}

cl_objectfn
ecl_function_dispatch(cl_env_ptr env, cl_object x)
{
  cl_object fun = x;
  if (ecl_unlikely(fun == ECL_NIL))
    FEundefined_function(x);
  switch (ecl_t_of(fun)) {
  case t_cfunfixed:
    env->function = fun;
    return fun->cfunfixed.entry;
  case t_cfun:
    env->function = fun;
    return fun->cfun.entry;
  case t_cclosure:
    env->function = fun;
    return fun->cclosure.entry;
  case t_instance:
    env->function = fun;
    return fun->instance.entry;
  case t_symbol:
    fun = ECL_SYM_FUN(fun);
    env->function = fun;
    return fun->cfun.entry;
  case t_bytecodes:
    env->function = fun;
    return fun->bytecodes.entry;
  case t_bclosure:
    env->function = fun;
    return fun->bclosure.entry;
  default:
    FEinvalid_function(x);
  }
}

cl_object
cl_funcall(cl_narg narg, cl_object function, ...)
{
  cl_object output;
  --narg;
  {
    ECL_STACK_FRAME_VARARGS_BEGIN(narg, function, frame);
    output = ecl_apply_from_stack_frame(frame, function);
    ECL_STACK_FRAME_VARARGS_END(frame);
  }
  return output;
}

@(defun apply (fun lastarg &rest args)
@ {
    if (narg == 2 && ecl_t_of(lastarg) == t_frame) {
      return ecl_apply_from_stack_frame(lastarg, fun);
    } else {
      cl_object out;
      cl_index i;
      struct ecl_stack_frame frame_aux;
      const cl_object frame = ecl_stack_frame_open(the_env,
                                                   (cl_object)&frame_aux,
                                                   narg -= 2);
      for (i = 0; i < narg; i++) {
        ecl_stack_frame_push(frame, lastarg);
        lastarg = ecl_va_arg(args);
      }
      if (ecl_t_of(lastarg) == t_frame) {
        /* This could be replaced with a memcpy() */
        for (i = 0; i < lastarg->frame.size; i++) {
          ecl_stack_frame_push(frame, ECL_STACK_FRAME_REF(lastarg, i));
        }
      } else loop_for_in (lastarg) {
          if (ecl_unlikely(i >= ECL_CALL_ARGUMENTS_LIMIT)) {
            ecl_stack_frame_close(frame);
            FEprogram_error("CALL-ARGUMENTS-LIMIT exceeded",0);
          }
          ecl_stack_frame_push(frame, CAR(lastarg));
          i++;
        } end_loop_for_in;
      out = ecl_apply_from_stack_frame(frame, fun);
      ecl_stack_frame_close(frame);
      return out;
    }
}@)

cl_object
cl_eval(cl_object form)
{
  return si_eval_with_env(1, form);
}

@(defun constantp (arg &optional env)
@
  return _ecl_funcall3(@'ext::constantp-inner', arg, env);
@)

@(defun ext::constantp-inner (form &optional env)
  cl_object value;
@ {
 AGAIN:
  switch (ecl_t_of(form)) {
  case t_list:
    if (Null(form)) {
      value = ECL_T;
      break;
    }
    if (ECL_CONS_CAR(form) == @'quote') {
      value = ECL_T;
      break;
    }
    /*
      value = cl_macroexpand(2, form, env);
      if (value != form) {
      form = value;
      goto AGAIN;
      }
    */
    value = ECL_NIL;
    break;
  case t_symbol:
    value = cl_macroexpand(2, form, env);
    if (value != form) {
      form = value;
      goto AGAIN;
    }
    if (!(form->symbol.stype & ecl_stp_constant)) {
      value = ECL_NIL;
      break;
    }
  default:
    value = ECL_T;
  }
  ecl_return1(the_env, value);
} @)

@(defun ext::constant-form-value (form &optional env)
  cl_object value;
@ {
 AGAIN:
  switch (ecl_t_of(form)) {
  case t_list:
    if (Null(form)) {
      value = ECL_NIL;
      break;
    }
    if (ECL_CONS_CAR(form) == @'quote') {
      return cl_second(form);
    }
    FEerror("EXT:CONSTANT-FORM-VALUE invoked with a non-constant form ~A",
            0, form);
    break;
  case t_symbol:
    value = cl_macroexpand(2, form, env);
    if (value != form) {
      form = value;
      goto AGAIN;
    }
    value = ECL_SYM_VAL(the_env, value);
    break;
  default:
    value = form;
  }
  @(return value);
} @)


cl_object
ecl_undefined_function_entry(cl_narg narg, ...)
{
  FEundefined_function(ecl_process_env()->function); /* see object.h */
}
