/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8                               :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2022 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
#include "third_party/lua/cosmo.h"
#include "third_party/lua/lauxlib.h"


// this is called with the error objects/string at -1
// and returns the stringified error object at -1
// @param L is main Lua interpreter
void expanderr(lua_State *L) {
  if (lua_tostring(L, -1) == NULL) {  // is error object not a string?
    if (luaL_callmeta(L, -1, "__tostring")) {  // is there a metamethod?
      if (lua_type(L, -1) != LUA_TSTRING)  {  // returns not a string?
        lua_pushfstring(L, "(error object returned a %s value)",
          luaL_typename(L, -1));
        lua_remove(L, -2);  // remove non-string value from __tostring
      }
    } else {
      lua_pushfstring(L, "(error object is a %s value)",
        luaL_typename(L, -1));
    }
    lua_remove(L, -2);  // remove the error object
  }
}

// calling convention for lua stack of L is:
//   -2 is function
//   -1 is is argument (assuming nargs == 1)
// L will have this after the call
//   -1 is error or result (assuming nres == 1)
// @param L is main Lua interpreter
// @note this needs to be reentrant
int LuaCallWithTrace(lua_State *L, int nargs, int nres, lua_State *C) {
  int nresults, status;
  bool canyield = !!C;           // allow yield if coroutine is provided
  if (!C) C = lua_newthread(L);  // create a new coroutine if not passed
  // move coroutine to the bottom of the stack (including one that is passed)
  lua_insert(L, 1);
  // make sure that there is enough stack space
  if (!lua_checkstack(C, 1 + nargs)) {
    lua_pushliteral(L, "too many arguments to resume");
    return LUA_ERRRUN; /* error flag */
  }
  // move the function (and arguments) to the top of the coro stack
  lua_xmove(L, C, 1 + nargs);
  // resume the coroutine thus executing the function
  status = lua_resume(C, L, nargs, &nresults);
  // remove coroutine (still) at the bottom, but only if not yielding
  // keep it when yielding to anchor, so it's not GC-collected
  // it's going to be removed at the beginning of the request handling
  if (!canyield) lua_remove(L, 1);
  if (status != LUA_OK && status != LUA_YIELD) {
    lua_xmove(C, L, 1);  // move the error message
    expanderr(L);  // handle non-string error objects
    // replace the error with the traceback on failure
    luaL_traceback2(L, C, lua_tostring(L, -1), 0);
    lua_remove(L, -2);  // remove the error message
  } else {
    if (!lua_checkstack(L, MAX(nresults, nres))) {
      lua_pop(C, nresults);  // remove results anyway
      lua_pushliteral(L, "too many results to resume");
      return LUA_ERRRUN;  // error flag
    }
    lua_xmove(C, L, nresults);  // move results to the main stack
    // grow the stack in case returned fewer results
    // than the caller expects, as lua_resume
    // doesn't adjust the stack for needed results
    for (; nresults < nres; nresults++) lua_pushnil(L);
    if (!canyield) status = LUA_OK;  // treat LUA_YIELD the same as LUA_OK
  }
  return status;
}
