text: /********
   **
   **  SPDX-License-Identifier: MIT
   **
   **  Copyright (c) 2017-2022 James M. Putnam <putnamjm.design@gmail.com>
   **
   **/
  
  /********
   **
   **  function.cc: library functions
   **
   **/
  #include "libmu/types/function.h"
  
  #include <cassert>
  #include <sstream>
  
  #include "libmu/core/core.h"
  #include "libmu/core/env.h"
  #include "libmu/core/type.h"
  
  #include "libmu/types/cons.h"
  #include "libmu/types/exception.h"
  #include "libmu/types/fixnum.h"
  #include "libmu/types/symbol.h"
  #include "libmu/types/vector.h"
  
  namespace libmu {
  namespace core {
  namespace {
  
  /** * call function on frame **/
  auto CallFrame(Env::Frame *fp) -> void {
    auto form = Function::form(fp->func);
  
    fp->value = Type::NIL;
  
    switch (Type::TypeOf(form)) {
    case SYS_CLASS::CONS: {
      Cons::cons_iter iter(Cons::cdr(form));
      for (auto it = iter.begin(); it != iter.end(); it = ++iter)
        fp->value = core::Eval(fp->env, it->car);
      break;
    }
    case SYS_CLASS::FIXNUM: {
      auto tagfn = reinterpret_cast<Env::TagFn *>(Fixnum::UInt64Of(form));
  
      tagfn->fn(fp);
      break;
    }
    case SYS_CLASS::VECTOR: {
      auto tagfn = reinterpret_cast<Env::TagFn *>(Vector::Data<uint8_t>(form));
  
      tagfn->fn(fp);
      break;
    }
    default:
      assert(false);
    }
  }
  
  } /* anonymous namespace */
  
  /** * garbage collection **/
  auto Function::GcMark(Env *, Tag fn) -> void { assert(IsType(fn)); }
  
  /** * function printer **/
  auto Function::Print(Env *env, Tag fn, Tag str, bool) -> void {
    assert(IsType(fn));
    assert(Stream::IsType(str));
  
    auto stream = Stream::StreamDesignator(env, str);
  
    auto type =
        Vector::StdStringOf(Symbol::name(Type::MapClassSymbol(Type::TypeOf(fn))));
  
    auto name = Null(Function::name(fn))
                    ? std::string{":lambda"}
                    : Vector::StdStringOf(Symbol::name(Function::name(fn)));
  
    auto ns = Null(Function::name(fn)) ? ""
                                       : Vector::StdStringOf(Namespace::name(
                                             Symbol::ns(Function::name(fn))));
  
    auto sep = (ns == "") ? ""
               : Namespace::IsExtern(Symbol::ns(Function::name(fn)),
                                     Symbol::name(Function::name(fn)))
                   ? ":"
                   : "::";
  
    auto fid = " id:" + std::to_string(Fixnum::UInt64Of(Function::frame_id(fn)));
  
    std::stringstream hexs;
  
    hexs << std::hex << Type::to_underlying(fn);
    core::PrintStdString(env,
                         "#<:" + type + " #x" + hexs.str() + " (" + ns + sep +
                             name + fid + ")>",
                         stream, false);
  }
  
  /** * make view of function **/
  auto Function::View(Env *env, Tag fn) -> Tag {
    assert(IsType(fn));
  
    auto view = std::vector<Tag>{arity(fn), form(fn), frame_id(fn), name(fn),
                                 extension(fn)};
  
    return Vector(view).Heap(env);
  }
  
  /** * clone function object **/
  auto Function::Clone(Env *env, Tag view) -> Tag {
    assert(Vector::IsType(view));
  
    auto fn = Function();
  
    if (Vector::Length(view) != 5)
      Exception::Raise(env, "clone", "error", "size", view);
  
    fn.function_.arity = Vector::Ref<Tag>(view, Fixnum(0).tag_);
    fn.function_.form = Vector::Ref<Tag>(view, Fixnum(1).tag_);
    fn.function_.frame_id = Vector::Ref<Tag>(view, Fixnum(2).tag_);
    fn.function_.name = Vector::Ref<Tag>(view, Fixnum(3).tag_);
    fn.function_.extension = Vector::Ref<Tag>(view, Fixnum(4).tag_);
  
    fn.tag_ = Entag(&fn.function_, TAG::FUNCTION);
  
    return fn.Heap(env);
  }
  
  /** * call function with argument vector **/
  auto Function::Funcall(Env *env, Tag fn, const std::vector<Tag> &argv) -> Tag {
    assert(IsType(fn));
  
    size_t nreqs = Fixnum::UInt64Of(Function::arity(fn));
  
    if (nreqs != argv.size())
      Exception::Raise(
          env, "funcall", "error", "arity",
          Cons::List(env,
                     std::vector<Tag>{Function::name(fn), Symbol::Keyword("nreq"),
                                      Fixnum(nreqs).tag_, Symbol::Keyword("from"),
                                      Cons::List(env, argv)}));
  
    auto args = const_cast<Tag *>(argv.data());
    auto fp = Env::Frame(env, fn, args);
  
    Env::FramePush(env, &fp);
  
    if (nreqs)
      env->framecache_[Function::frame_id(fp.func)].push(&fp);
  
    CallFrame(&fp);
  
    if (nreqs)
      env->framecache_[Function::frame_id(fp.func)].pop();
  
    Env::FramePop(env);
  
    return fp.value;
  }
  
  } /* namespace core */
  } /* namespace libmu */
  