#include #include #include #include "luakiwi-int.h" namespace { // Note some of the internal functions do not bother cleaning up the stack, they // are marked with accordingly. enum TypeId { NOTYPE, VAR = 1, TERM, EXPR, CONSTRAINT, SOLVER, ERROR, NUMBER }; enum { ERR_KIND_TAB = NUMBER + 1, VAR_SUB_FN, MEM_ERR_MSG, CONTEXT_TAB_MAX }; constexpr const char* const lkiwi_error_kinds[] = { "KiwiErrNone", "KiwiErrUnsatisfiableConstraint", "KiwiErrUnknownConstraint", "KiwiErrDuplicateConstraint", "KiwiErrUnknownEditVariable", "KiwiErrDuplicateEditVariable", "KiwiErrBadRequiredStrength", "KiwiErrInternalSolverError", "KiwiErrAlloc", "KiwiErrNullObject", "KiwiErrUnknown", }; const double STRENGTH_REQUIRED = 1001001000.0; const double STRENGTH_STRONG = 1000000.0; const double STRENGTH_MEDIUM = 1000.0; const double STRENGTH_WEAK = 1.0; kiwi::RelationalOperator get_op_opt(lua_State* L, int idx) { size_t opn; const char* op = luaL_optlstring(L, idx, "EQ", &opn); if (opn == 2) { if (op[0] == 'E' && op[1] == 'Q') { return kiwi::OP_EQ; } else if (op[0] == 'L' && op[1] == 'E') { return kiwi::OP_LE; } else if (op[0] == 'G' && op[1] == 'E') { return kiwi::OP_GE; } } luaL_argerror(L, idx, "invalid operator"); return kiwi::OP_EQ; } inline void push_type(lua_State* L, int type_id) { lua_rawgeti(L, lua_upvalueindex(1), type_id); } // stack disposition: dirty inline int is_udata_obj(lua_State* L, int type_id) { int result = 0; if (lua_isuserdata(L, 1) && lua_getmetatable(L, 1)) { push_type(L, type_id); result = lua_rawequal(L, -1, -2); } lua_pushboolean(L, result); return 1; } // get typename, copy the stack string to tidx, helpful when using // with buffers. const char* lk_typename(lua_State* L, int idx, int tidx) { const char* ret = 0; if (lua_getmetatable(L, idx)) { lua_getfield(L, -1, "__name"); ret = lua_tolstring(L, -1, 0); lua_replace(L, tidx); lua_pop(L, 1); } return ret ? ret : luaL_typename(L, idx); } // never returns int op_error(lua_State* L, const char* op, int lidx, int ridx) { luaL_Buffer buf; size_t len; const char* str; // scratch space for strings lua_pushnil(L); int stridx = lua_gettop(L); luaL_buffinit(L, &buf); lua_pushfstring(L, "invalid operand type for '%s' %s('", op, lk_typename(L, lidx, stridx)); luaL_addvalue(&buf); str = luaL_tolstring(L, lidx, &len); lua_replace(L, stridx); luaL_addlstring(&buf, str, len < 100 ? len : 100); lua_pushfstring(L, "') and %s('", lk_typename(L, ridx, stridx)); luaL_addvalue(&buf); str = luaL_tolstring(L, ridx, &len); lua_replace(L, stridx); luaL_addlstring(&buf, str, len < 100 ? len : 100); luaL_addstring(&buf, "')"); luaL_pushresult(&buf); lua_error(L); return 0; } void check_arg_error(lua_State* L, int idx, int have_mt) { lua_pushstring(L, "__name"); lua_rawget(L, -2); // TODO: simplify this. This is a bit of a hack to deal with missing args. // Also these error messages are funky when idx is negative. int top = lua_gettop(L); if (idx > 0 && top <= 2 + have_mt) { lua_pushnil(L); lua_replace(L, idx); } luaL_typeerror(L, idx < 0 ? top + idx - have_mt - 2 + 1 : idx, lua_tostring(L, -1)); } inline void* check_arg(lua_State* L, int idx, int type_id) { void* udp = lua_touserdata(L, idx); int have_mt = lua_getmetatable(L, idx); push_type(L, type_id); if (lk_unlikely(!udp || !have_mt || !lua_rawequal(L, -1, -2))) check_arg_error(L, idx, have_mt); lua_pop(L, 2); return udp; } inline void* try_type(lua_State* L, int idx, TypeId type_id) { void* p = lua_touserdata(L, idx); if (!p || !lua_getmetatable(L, idx)) return 0; push_type(L, type_id); return lua_rawequal(L, -1, -2) ? p : 0; } inline VariableData* try_var(lua_State* L, int idx) { return *static_cast(try_type(L, idx, VAR)); } inline KiwiTerm* try_term(lua_State* L, int idx) { return static_cast(try_type(L, idx, TERM)); } inline KiwiExpression* try_expr(lua_State* L, int idx) { return static_cast(try_type(L, idx, EXPR)); } // method to test types for expression functions // stack disposition: dirty inline void* try_arg(lua_State* L, int idx, TypeId* type_id, double* num) { void* p = lua_touserdata(L, idx); if (!p || !lua_getmetatable(L, idx)) { int isnum; *num = lua_tonumberx(L, idx, &isnum); if (isnum) { *type_id = NUMBER; } else *type_id = NOTYPE; return 0; } push_type(L, EXPR); if (lua_rawequal(L, -1, -2)) { *type_id = EXPR; return p; } push_type(L, VAR); if (lua_rawequal(L, -1, -3)) { *type_id = VAR; return p; } push_type(L, TERM); if (lua_rawequal(L, -1, -4)) { *type_id = TERM; return p; } *type_id = NOTYPE; return 0; } inline VariableData* get_var(lua_State* L, int idx) { return *static_cast(check_arg(L, idx, VAR)); } inline KiwiTerm* get_term(lua_State* L, int idx) { return static_cast(check_arg(L, idx, TERM)); } inline KiwiExpression* get_expr(lua_State* L, int idx) { return static_cast(check_arg(L, idx, EXPR)); } inline KiwiExpression* get_expr_opt(lua_State* L, int idx) { if (lua_isnoneornil(L, idx)) { return 0; } return static_cast(check_arg(L, idx, EXPR)); } inline ConstraintData* get_constraint(lua_State* L, int idx) { return *static_cast(check_arg(L, idx, CONSTRAINT)); } inline KiwiSolver* get_solver(lua_State* L, int idx) { return static_cast(check_arg(L, idx, SOLVER)); } VariableData** var_new(lua_State* L) { auto** varp = static_cast(lua_newuserdata(L, sizeof(VariableData*))); push_type(L, VAR); lua_setmetatable(L, -2); return varp; } // note this expects the 2nd upvalue to have the variable weak table VariableData* var_register(lua_State* L, VariableData* var) { if (lk_unlikely(!var)) { lua_rawgeti(L, lua_upvalueindex(1), MEM_ERR_MSG); lua_error(L); } #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 // a true compatibility shim has performance implications here lua_pushlightuserdata(L, var); lua_pushvalue(L, -2); lua_rawset(L, lua_upvalueindex(2)); #else lua_pushvalue(L, -1); lua_rawsetp(L, lua_upvalueindex(2), var); #endif return var; } KiwiTerm* term_new(lua_State* L) { auto* term = static_cast(lua_newuserdata(L, sizeof(KiwiTerm))); push_type(L, TERM); lua_setmetatable(L, -2); return term; } inline KiwiExpression* expr_new(lua_State* L, int nterms) { auto* expr = static_cast(lua_newuserdata(L, KiwiExpression::sz(nterms))); expr->term_count = 0; expr->owner = nullptr; push_type(L, EXPR); lua_setmetatable(L, -2); return expr; } inline ConstraintData* constraint_new( lua_State* L, const KiwiExpression* lhs, const KiwiExpression* rhs, kiwi::RelationalOperator op, double strength ) { auto** c = static_cast(lua_newuserdata(L, sizeof(ConstraintData*))); push_type(L, CONSTRAINT); lua_setmetatable(L, -2); if (lk_unlikely(!(*c = kiwi_constraint_new(lhs, rhs, op, strength)))) { lua_rawgeti(L, lua_upvalueindex(1), MEM_ERR_MSG); lua_error(L); } return *c; } // stack disposition: dirty KiwiExpression* toexpr(lua_State* L, int idx, KiwiExpression* temp) { void* ud = lua_touserdata(L, idx); if (!ud) { int isnum; temp->constant = lua_tonumberx(L, idx, &isnum); temp->term_count = 0; return isnum ? temp : 0; } if (!lua_getmetatable(L, idx)) return 0; push_type(L, EXPR); if (lua_rawequal(L, -1, -2)) { return static_cast(ud); } temp->constant = 0; temp->term_count = 1; push_type(L, VAR); if (lua_rawequal(L, -1, -3)) { temp->terms[0].var = *static_cast(ud); temp->terms[0].coefficient = 1.0; return temp; } push_type(L, TERM); if (lua_rawequal(L, -1, -4)) { temp->terms[0] = *static_cast(ud); return temp; } return 0; } int relop(lua_State* L, kiwi::RelationalOperator op, const char opdisp[2]) { alignas(KiwiExpression) unsigned char tmpl[KiwiExpression::sz(1)]; alignas(KiwiExpression) unsigned char tmpr[KiwiExpression::sz(1)]; double strength = luaL_optnumber(L, 3, STRENGTH_REQUIRED); const auto* lhs = toexpr(L, 1, reinterpret_cast(tmpl)); const auto* rhs = toexpr(L, 2, reinterpret_cast(tmpr)); if (!lhs || !rhs) { op_error(L, opdisp, 1, 2); } constraint_new(L, lhs, rhs, op, strength); return 1; } int lkiwi_eq(lua_State* L) { return relop(L, kiwi::OP_EQ, "=="); } int lkiwi_le(lua_State* L) { return relop(L, kiwi::OP_LE, "<="); } int lkiwi_ge(lua_State* L) { return relop(L, kiwi::OP_GE, ">="); } inline int push_expr_one(lua_State* L, double constant, const KiwiTerm* term) { auto* expr = expr_new(L, 1); expr->constant = constant; expr->term_count = 1; expr->terms[0].coefficient = term->coefficient; expr->terms[0].var = retain_unmanaged(term->var); return 1; } inline int push_expr_pair(lua_State* L, double constant, const KiwiTerm* ta, const KiwiTerm* tb) { auto* e = expr_new(L, 2); e->constant = constant; e->term_count = 2; e->terms[0].coefficient = ta->coefficient; e->terms[0].var = retain_unmanaged(ta->var); e->terms[1].coefficient = tb->coefficient; e->terms[1].var = retain_unmanaged(tb->var); return 1; } inline int push_expr_var_term(lua_State* L, double constant, VariableData* var, const KiwiTerm* t) { auto* e = expr_new(L, 2); e->constant = constant; e->term_count = 2; e->terms[0].coefficient = 1.0; e->terms[0].var = retain_unmanaged(var); e->terms[1].coefficient = t->coefficient; e->terms[1].var = retain_unmanaged(t->var); return 1; } int push_add_expr_term(lua_State* L, const KiwiExpression* expr, const KiwiTerm* t) { auto* e = expr_new(L, expr->term_count + 1); e->constant = expr->constant; e->term_count = expr->term_count + 1; int i = 0; for (; i < expr->term_count; ++i) { e->terms[i].coefficient = expr->terms[i].coefficient; e->terms[i].var = retain_unmanaged(expr->terms[i].var); } e->terms[i].coefficient = t->coefficient; e->terms[i].var = retain_unmanaged(t->var); return 1; } int lkiwi_var_m_add(lua_State* L) { TypeId type_id_b; double num = 0.0; void* arg_b = try_arg(L, 2, &type_id_b, &num); if (type_id_b == VAR) { int isnum_a; num = lua_tonumberx(L, 1, &isnum_a); if (isnum_a) { const KiwiTerm t {*static_cast(arg_b), 1.0}; return push_expr_one(L, num, &t); } } auto* var_a = try_var(L, 1); if (var_a) { switch (type_id_b) { case VAR: { const KiwiTerm ta {var_a, 1.0}, tb {*static_cast(arg_b), 1.0}; return push_expr_pair(L, 0.0, &ta, &tb); } case TERM: return push_expr_var_term(L, 0.0, var_a, (static_cast(arg_b))); case EXPR: { const KiwiTerm t {var_a, 1.0}; return push_add_expr_term(L, static_cast(arg_b), &t); } case NUMBER: { const KiwiTerm t {var_a, 1.0}; return push_expr_one(L, num, &t); } default: break; } } return op_error(L, "+", 1, 2); } int lkiwi_var_m_sub(lua_State* L) { lua_settop(L, 2); #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 lua_rawgeti(L, lua_upvalueindex(1), VAR_SUB_FN); lua_insert(L, 1); lua_call(L, 2, 1); #else lua_arith(L, LUA_OPUNM); lua_arith(L, LUA_OPADD); #endif return 1; } int lkiwi_var_m_mul(lua_State* L) { int isnum, varidx = 2; double num = lua_tonumberx(L, 1, &isnum); if (!isnum) { varidx = 1; num = lua_tonumberx(L, 2, &isnum); } if (isnum) { auto* var = try_var(L, varidx); if (var) { auto* term = term_new(L); term->var = retain_unmanaged(var); term->coefficient = num; return 1; } } return op_error(L, "*", 1, 2); } int lkiwi_var_m_div(lua_State* L) { auto* var = try_var(L, 1); int isnum; double num = lua_tonumberx(L, 2, &isnum); if (!var || !isnum) { return op_error(L, "/", 1, 2); } auto* term = term_new(L); term->var = retain_unmanaged(var); term->coefficient = 1.0 / num; return 1; } int lkiwi_var_m_unm(lua_State* L) { auto* term = term_new(L); term->var = retain_unmanaged(get_var(L, 1)); term->coefficient = -1.0; return 1; } int lkiwi_var_m_eq(lua_State* L) { lua_pushboolean(L, get_var(L, 1) == get_var(L, 2)); return 1; } int lkiwi_var_m_tostring(lua_State* L) { auto* var = get_var(L, 1); lua_pushfstring(L, "%s(%f)", var->name().c_str(), var->value()); return 1; } int lkiwi_var_m_gc(lua_State* L) { release_unmanaged(get_var(L, 1)); return 0; } int lkiwi_var_set_name(lua_State* L) { auto* var = get_var(L, 1); const char* name = luaL_checkstring(L, 2); var->setName(name); return 0; } int lkiwi_var_name(lua_State* L) { lua_pushstring(L, get_var(L, 1)->name().c_str()); return 1; } int lkiwi_var_set(lua_State* L) { auto* var = get_var(L, 1); const double value = luaL_checknumber(L, 2); var->setValue(value); return 0; } int lkiwi_var_value(lua_State* L) { lua_pushnumber(L, get_var(L, 1)->value()); return 1; } int lkiwi_var_toterm(lua_State* L) { auto* var = get_var(L, 1); double coefficient = luaL_optnumber(L, 2, 1.0); auto* term = term_new(L); term->var = retain_unmanaged(var); term->coefficient = coefficient; return 1; } int lkiwi_var_toexpr(lua_State* L) { const KiwiTerm t {get_var(L, 1), 1.0}; return push_expr_one(L, 0.0, &t); } constexpr const struct luaL_Reg kiwi_var_m[] = { {"__add", lkiwi_var_m_add}, {"__sub", lkiwi_var_m_sub}, {"__mul", lkiwi_var_m_mul}, {"__div", lkiwi_var_m_div}, {"__unm", lkiwi_var_m_unm}, {"__eq", lkiwi_var_m_eq}, {"__tostring", lkiwi_var_m_tostring}, {"__gc", lkiwi_var_m_gc}, {"name", lkiwi_var_name}, {"set_name", lkiwi_var_set_name}, {"value", lkiwi_var_value}, {"set", lkiwi_var_set}, {"toterm", lkiwi_var_toterm}, {"toexpr", lkiwi_var_toexpr}, {"eq", lkiwi_eq}, {"le", lkiwi_le}, {"ge", lkiwi_ge}, {0, 0} }; int lkiwi_var_new(lua_State* L) { const char* name = luaL_optstring(L, 1, ""); auto* varp = var_new(L); var_register(L, *varp = make_unmanaged(name)); return 1; } int lkiwi_term_m_add(lua_State* L) { TypeId type_id_b; double num = 0.0; void* arg_b = try_arg(L, 2, &type_id_b, &num); if (type_id_b == TERM) { int isnum_a; num = lua_tonumberx(L, 1, &isnum_a); if (isnum_a) { return push_expr_one(L, num, (const KiwiTerm*)arg_b); } } const auto* term_a = try_term(L, 1); if (term_a) { switch (type_id_b) { case TERM: return push_expr_pair(L, 0.0, term_a, static_cast(arg_b)); case VAR: { const KiwiTerm term_b {*static_cast(arg_b), 1.0}; return push_expr_pair(L, 0.0, term_a, &term_b); } case EXPR: return push_add_expr_term(L, static_cast(arg_b), term_a); case NUMBER: return push_expr_one(L, num, term_a); default: break; } } return op_error(L, "+", 1, 2); } int lkiwi_term_m_sub(lua_State* L) { lua_settop(L, 2); compat_arith_unm(L); lkiwi_term_m_add(L); return 1; } int lkiwi_term_m_mul(lua_State* L) { int isnum, termidx = 2; double num = lua_tonumberx(L, 1, &isnum); if (!isnum) { termidx = 1; num = lua_tonumberx(L, 2, &isnum); } if (isnum) { const auto* term = try_term(L, termidx); if (term) { auto* ret = term_new(L); ret->var = retain_unmanaged(term->var); ret->coefficient = term->coefficient * num; return 1; } } return op_error(L, "*", 1, 2); } int lkiwi_term_m_div(lua_State* L) { const KiwiTerm* term = try_term(L, 1); int isnum; double num = lua_tonumberx(L, 2, &isnum); if (!term || !isnum) { return op_error(L, "/", 1, 2); } auto* ret = term_new(L); ret->var = retain_unmanaged(term->var); ret->coefficient = term->coefficient / num; return 1; } int lkiwi_term_m_unm(lua_State* L) { const auto* term = get_term(L, 1); auto* ret = term_new(L); ret->var = retain_unmanaged(term->var); ret->coefficient = -term->coefficient; return 1; } int lkiwi_term_toexpr(lua_State* L) { return push_expr_one(L, 0.0, get_term(L, 1)); } int lkiwi_term_value(lua_State* L) { const auto* term = get_term(L, 1); lua_pushnumber(L, term->var->value() * term->coefficient); return 1; } int lkiwi_term_m_tostring(lua_State* L) { const auto* term = get_term(L, 1); lua_pushfstring(L, "%f %s", term->coefficient, term->var->name().c_str()); return 1; } int lkiwi_term_m_gc(lua_State* L) { release_unmanaged(get_term(L, 1)->var); return 0; } int lkiwi_term_m_index(lua_State* L) { const auto* term = get_term(L, 1); size_t len; const char* k = lua_tolstring(L, 2, &len); if (len == 3 && memcmp("var", k, len) == 0) { #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 lua_pushlightuserdata(L, term->var); lua_rawget(L, lua_upvalueindex(2)); #else lua_rawgetp(L, lua_upvalueindex(2), term->var); #endif if (lua_isnil(L, -1)) { auto* varp = var_new(L); var_register(L, *varp = retain_unmanaged(term->var)); } return 1; } else if (len == 11 && memcmp("coefficient", k, len) == 0) { lua_pushnumber(L, term->coefficient); return 1; } lua_getmetatable(L, 1); lua_pushvalue(L, 2); lua_rawget(L, -2); if (lua_isnil(L, -1)) { luaL_error(L, "kiwi.Term has no member named '%s'", k); } return 1; } constexpr const struct luaL_Reg kiwi_term_m[] = { {"__add", lkiwi_term_m_add}, {"__sub", lkiwi_term_m_sub}, {"__mul", lkiwi_term_m_mul}, {"__div", lkiwi_term_m_div}, {"__unm", lkiwi_term_m_unm}, {"__tostring", lkiwi_term_m_tostring}, {"__gc", lkiwi_term_m_gc}, {"__index", 0}, {"toexpr", lkiwi_term_toexpr}, {"value", lkiwi_term_value}, {"eq", lkiwi_eq}, {"le", lkiwi_le}, {"ge", lkiwi_ge}, {0, 0} }; int lkiwi_term_new(lua_State* L) { auto* var = get_var(L, 1); double coefficient = luaL_optnumber(L, 2, 1.0); auto* term = term_new(L); term->var = retain_unmanaged(var); term->coefficient = coefficient; return 1; } int push_expr_constant(lua_State* L, const KiwiExpression* expr, double constant) { auto* ne = expr_new(L, expr->term_count); for (int i = 0; i < expr->term_count; i++) { ne->terms[i].var = retain_unmanaged(expr->terms[i].var); ne->terms[i].coefficient = expr->terms[i].coefficient; } ne->constant = constant; ne->term_count = expr->term_count; return 1; } int push_mul_expr_coeff(lua_State* L, const KiwiExpression* expr, double coeff) { auto* ne = expr_new(L, expr->term_count); ne->constant = expr->constant * coeff; ne->term_count = expr->term_count; for (int i = 0; i < expr->term_count; i++) { ne->terms[i].var = retain_unmanaged(expr->terms[i].var); ne->terms[i].coefficient = expr->terms[i].coefficient * coeff; } return 1; } int push_add_expr_expr(lua_State* L, const KiwiExpression* a, const KiwiExpression* b) { int na = a->term_count, nb = b->term_count; auto* ne = expr_new(L, na + nb); ne->constant = a->constant + b->constant; ne->term_count = na + nb; for (int i = 0; i < na; i++) { ne->terms[i].var = retain_unmanaged(a->terms[i].var); ne->terms[i].coefficient = a->terms[i].coefficient; } for (int i = 0; i < nb; i++) { ne->terms[i + na].var = retain_unmanaged(b->terms[i].var); ne->terms[i + na].coefficient = b->terms[i].coefficient; } return 1; } int lkiwi_expr_m_add(lua_State* L) { TypeId type_id_b; double num = 0.0; void* arg_b = try_arg(L, 2, &type_id_b, &num); if (type_id_b == EXPR) { int isnum_a; num = lua_tonumberx(L, 1, &isnum_a); if (isnum_a) { auto* expr_b = static_cast(arg_b); return push_expr_constant(L, expr_b, num + expr_b->constant); } } const auto* expr_a = try_expr(L, 1); if (expr_a) { switch (type_id_b) { case EXPR: return push_add_expr_expr(L, expr_a, static_cast(arg_b)); case TERM: return push_add_expr_term(L, expr_a, static_cast(arg_b)); case VAR: { const KiwiTerm term_b {*static_cast(arg_b), 1.0}; return push_add_expr_term(L, expr_a, &term_b); } case NUMBER: return push_expr_constant(L, expr_a, num + expr_a->constant); default: break; } } return op_error(L, "+", 1, 2); } int lkiwi_expr_m_sub(lua_State* L) { lua_settop(L, 2); compat_arith_unm(L); lkiwi_expr_m_add(L); return 1; } int lkiwi_expr_m_mul(lua_State* L) { int isnum, expridx = 2; double num = lua_tonumberx(L, 1, &isnum); if (!isnum) { expridx = 1; num = lua_tonumberx(L, 2, &isnum); } if (isnum) { const auto* expr = try_expr(L, expridx); if (expr) return push_mul_expr_coeff(L, expr, num); } return op_error(L, "*", 1, 2); } int lkiwi_expr_m_div(lua_State* L) { const auto* expr = try_expr(L, 1); int isnum; double num = lua_tonumberx(L, 2, &isnum); if (!expr || !isnum) { return op_error(L, "/", 1, 2); } return push_mul_expr_coeff(L, expr, 1.0 / num); } int lkiwi_expr_m_unm(lua_State* L) { const auto* expr = get_expr(L, 1); return push_mul_expr_coeff(L, expr, -1.0); } int lkiwi_expr_value(lua_State* L) { const auto* expr = get_expr(L, 1); double sum = expr->constant; for (int i = 0; i < expr->term_count; i++) { const auto* t = &expr->terms[i]; sum += t->var->value() * t->coefficient; } lua_pushnumber(L, sum); return 1; } int lkiwi_expr_terms(lua_State* L) { const auto* expr = get_expr(L, 1); lua_createtable(L, expr->term_count, 0); for (int i = 0; i < expr->term_count; i++) { const auto* t = &expr->terms[i]; auto* new_term = term_new(L); new_term->var = retain_unmanaged(t->var); new_term->coefficient = t->coefficient; lua_rawseti(L, -2, i + 1); } return 1; } int lkiwi_expr_copy(lua_State* L) { auto* expr = get_expr(L, 1); return push_expr_constant(L, expr, expr->constant); } int lkiwi_expr_m_tostring(lua_State* L) { const auto* expr = get_expr(L, 1); luaL_Buffer buf; luaL_buffinit(L, &buf); for (int i = 0; i < expr->term_count; i++) { const auto* t = &expr->terms[i]; lua_pushfstring(L, "%f %s", t->coefficient, t->var->name().c_str()); luaL_addvalue(&buf); luaL_addstring(&buf, " + "); } lua_pushfstring(L, "%f", expr->constant); luaL_addvalue(&buf); luaL_pushresult(&buf); return 1; } int lkiwi_expr_m_gc(lua_State* L) { const auto* expr = get_expr(L, 1); if (expr->owner) { release_unmanaged(expr->owner); } else { for (auto* t = expr->terms; t != expr->terms + expr->term_count; ++t) { release_unmanaged(t->var); } } return 0; } int lkiwi_expr_m_index(lua_State* L) { const auto* expr = get_expr(L, 1); size_t len; const char* k = lua_tolstring(L, 2, &len); if (len == 8 && memcmp("constant", k, len) == 0) { lua_pushnumber(L, expr->constant); return 1; } lua_getmetatable(L, 1); lua_pushvalue(L, 2); lua_rawget(L, -2); if (lua_isnil(L, -1)) { luaL_error(L, "kiwi.Expression has no member named '%s'", k); } return 1; } constexpr const struct luaL_Reg kiwi_expr_m[] = { {"__add", lkiwi_expr_m_add}, {"__sub", lkiwi_expr_m_sub}, {"__mul", lkiwi_expr_m_mul}, {"__div", lkiwi_expr_m_div}, {"__unm", lkiwi_expr_m_unm}, {"__tostring", lkiwi_expr_m_tostring}, {"__gc", lkiwi_expr_m_gc}, {"__index", lkiwi_expr_m_index}, {"value", lkiwi_expr_value}, {"terms", lkiwi_expr_terms}, {"copy", lkiwi_expr_copy}, {"eq", lkiwi_eq}, {"le", lkiwi_le}, {"ge", lkiwi_ge}, {0, 0} }; int lkiwi_expr_new(lua_State* L) { int nterms = lua_gettop(L) - 1; lua_Number constant = luaL_checknumber(L, 1); auto* expr = expr_new(L, nterms); expr->constant = constant; for (int i = 0; i < nterms; ++i, ++expr->term_count) { const auto* term = get_term(L, i + 2); expr->terms[i].var = retain_unmanaged(term->var); expr->terms[i].coefficient = term->coefficient; } return 1; } int lkiwi_constraint_strength(lua_State* L) { lua_pushnumber(L, get_constraint(L, 1)->strength()); return 1; } int lkiwi_constraint_op(lua_State* L) { auto op = get_constraint(L, 1)->op(); const char* opstr = "??"; switch (op) { case kiwi::OP_LE: opstr = "LE"; break; case kiwi::OP_GE: opstr = "GE"; break; case kiwi::OP_EQ: opstr = "EQ"; break; } lua_pushlstring(L, opstr, 2); return 1; } int lkiwi_constraint_violated(lua_State* L) { lua_pushboolean(L, get_constraint(L, 1)->violated()); return 1; } int lkiwi_constraint_expression(lua_State* L) { auto* c = get_constraint(L, 1); const auto& expr = c->expression(); const auto& terms = expr.terms(); const auto term_count = static_cast(terms.size() > INT_MAX ? INT_MAX : terms.size()); auto* ne = expr_new(L, term_count); ne->owner = retain_unmanaged(c); ne->constant = expr.constant(); ne->term_count = term_count; for (int i = 0; i < term_count; ++i) { const auto& t = terms[static_cast(i)]; ne->terms[i].var = const_cast(t.variable()).ptr(); ne->terms[i].coefficient = t.coefficient(); } return 1; } int lkiwi_constraint_m_tostring(lua_State* L) { const auto& c = *get_constraint(L, 1); luaL_Buffer buf; luaL_buffinit(L, &buf); const char* oppart = " ?? 0 | "; switch (c.op()) { case kiwi::OP_LE: oppart = " <= 0 | "; break; case kiwi::OP_GE: oppart = " >= 0 | "; break; case kiwi::OP_EQ: oppart = " == 0 | "; break; } const auto& expr = c.expression(); for (const auto& t : expr.terms()) { lua_pushfstring(L, "%f %s", t.coefficient(), t.variable().name().c_str()); luaL_addvalue(&buf); luaL_addstring(&buf, " + "); } lua_pushfstring(L, "%f", expr.constant()); luaL_addvalue(&buf); luaL_addlstring(&buf, oppart, 8); const char* strength_name = 0; const double strength = c.strength(); if (strength == STRENGTH_REQUIRED) { strength_name = "required"; } else if (strength == STRENGTH_STRONG) { strength_name = "strong"; } else if (strength == STRENGTH_MEDIUM) { strength_name = "medium"; } else if (strength == STRENGTH_WEAK) { strength_name = "weak"; } if (strength_name) { luaL_addstring(&buf, strength_name); } else { lua_pushfstring(L, "%f", strength); luaL_addvalue(&buf); } luaL_pushresult(&buf); return 1; } int lkiwi_constraint_m_gc(lua_State* L) { release_unmanaged(get_constraint(L, 1)); return 0; } int lkiwi_solver_add_constraint(lua_State* L); int lkiwi_solver_remove_constraint(lua_State* L); int lkiwi_constraint_add_to(lua_State* L) { lua_settop(L, 2); lua_rotate(L, 1, 1); lkiwi_solver_add_constraint(L); lua_settop(L, 2); return 1; } int lkiwi_constraint_remove_from(lua_State* L) { lua_settop(L, 2); lua_rotate(L, 1, 1); lkiwi_solver_remove_constraint(L); lua_settop(L, 2); return 1; } constexpr const struct luaL_Reg kiwi_constraint_m[] = { {"__tostring", lkiwi_constraint_m_tostring}, {"__gc", lkiwi_constraint_m_gc}, {"strength", lkiwi_constraint_strength}, {"op", lkiwi_constraint_op}, {"violated", lkiwi_constraint_violated}, {"expression", lkiwi_constraint_expression}, {"add_to", lkiwi_constraint_add_to}, {"remove_from", lkiwi_constraint_remove_from}, {0, 0} }; int lkiwi_constraint_new(lua_State* L) { const auto* lhs = get_expr_opt(L, 1); const auto* rhs = get_expr_opt(L, 2); const auto op = get_op_opt(L, 3); double strength = luaL_optnumber(L, 4, STRENGTH_REQUIRED); constraint_new(L, lhs, rhs, op, strength); return 1; } int push_pair_constraint( lua_State* L, VariableData* left, double coeff, VariableData* right, double constant, kiwi::RelationalOperator op, double strength ) { alignas(KiwiExpression) unsigned char expr_buf[KiwiExpression::sz(2)]; auto* expr = reinterpret_cast(&expr_buf); expr->constant = constant; expr->term_count = 2; expr->terms[0].var = left; expr->terms[0].coefficient = 1.0; expr->terms[1].var = right; expr->terms[1].coefficient = -coeff; constraint_new(L, expr, 0, op, strength); return 1; } int lkiwi_constraints_pair_ratio(lua_State* L) { return push_pair_constraint( L, get_var(L, 1), luaL_checknumber(L, 2), get_var(L, 3), luaL_optnumber(L, 4, 0.0), get_op_opt(L, 5), luaL_optnumber(L, 6, STRENGTH_REQUIRED) ); } int lkiwi_constraints_pair(lua_State* L) { return push_pair_constraint( L, get_var(L, 1), 1.0, get_var(L, 2), luaL_optnumber(L, 3, 0.0), get_op_opt(L, 4), luaL_optnumber(L, 4, STRENGTH_REQUIRED) ); } int lkiwi_constraints_single(lua_State* L) { alignas(KiwiExpression) unsigned char expr_buf[KiwiExpression::sz(1)]; auto* expr = reinterpret_cast(&expr_buf); expr->term_count = 1; expr->terms[0].var = get_var(L, 1); expr->terms[0].coefficient = 1.0; expr->constant = luaL_optnumber(L, 2, 0.0); constraint_new(L, expr, 0, get_op_opt(L, 3), luaL_optnumber(L, 4, STRENGTH_REQUIRED)); return 1; } constexpr const struct luaL_Reg lkiwi_constraints[] = { {"pair_ratio", lkiwi_constraints_pair_ratio}, {"pair", lkiwi_constraints_pair}, {"single", lkiwi_constraints_single}, {0, 0} }; void lkiwi_mod_constraints_new(lua_State* L, int ctx_i) { luaL_newlibtable(L, lkiwi_constraints); lua_pushvalue(L, ctx_i); setfuncs(L, lkiwi_constraints, 1); } /* kiwi.Error */ void error_new(lua_State* L, const KiwiErr* err, int solver_absi, int item_absi) { lua_createtable(L, 0, 4); push_type(L, ERROR); lua_setmetatable(L, -2); lua_pushstring(L, lkiwi_error_kinds[err->kind < KiwiErrUnknown ? err->kind : KiwiErrUnknown]); lua_setfield(L, -2, "kind"); lua_pushstring(L, err->message); lua_setfield(L, -2, "message"); if (solver_absi) { lua_pushvalue(L, solver_absi); lua_setfield(L, -2, "solver"); } if (item_absi) { lua_pushvalue(L, item_absi); lua_setfield(L, -2, "item"); } if (err->must_delete) { delete const_cast(err); } } int lkiwi_error_m_tostring(lua_State* L) { luaL_Buffer buf; luaL_buffinit(L, &buf); lua_getfield(L, 1, "message"); luaL_addvalue(&buf); lua_getfield(L, 1, "solver"); lua_pushfstring(L, ": (kiwi.Solver(%p), ", get_solver(L, -1)); lua_remove(L, -2); // remove solver luaL_addvalue(&buf); lua_getfield(L, 1, "item"); luaL_tolstring(L, -1, 0); lua_remove(L, -2); // remove item luaL_addvalue(&buf); luaL_addstring(&buf, ")"); luaL_pushresult(&buf); return 1; } constexpr const struct luaL_Reg lkiwi_error_m[] = { {"__tostring", lkiwi_error_m_tostring}, {0, 0} }; int lkiwi_error_mask(lua_State* L) { int invert = lua_toboolean(L, 2); if (lua_type(L, 1) == LUA_TSTRING) { luaL_typeerror(L, 1, "indexable"); } lua_rawgeti(L, lua_upvalueindex(1), ERR_KIND_TAB); unsigned mask = 0; for (int n = 1; lua_geti(L, 1, n) != LUA_TNIL; ++n) { int isnum; auto shift = lua_tointegerx(L, -1, &isnum); if (!isnum) { lua_rawget(L, -2 /* err_kind table */); shift = lua_tointegerx(L, -1, &isnum); if (!isnum) { luaL_error(L, "unknown error kind at index %d: %s", n, luaL_tolstring(L, -2, 0)); } } mask |= 1 << shift; lua_pop(L, 1); } lua_pushinteger(L, invert ? ~mask : mask); return 1; } int lkiwi_solver_handle_err(lua_State* L, const KiwiErr* err, const KiwiSolver* solver) { /* This assumes solver is at index 1 */ lua_settop(L, 2); if (err) { error_new(L, err, 1, 2); unsigned error_mask = solver->error_mask; if (error_mask & (1 << err->kind)) { return 2; } else { lua_error(L); } } return 1; } int lkiwi_solver_add_constraint(lua_State* L) { auto* self = get_solver(L, 1); auto* c = get_constraint(L, 2); auto* err = kiwi_solver_add_constraint(self->solver, c); return lkiwi_solver_handle_err(L, err, self); } int lkiwi_solver_remove_constraint(lua_State* L) { auto* self = get_solver(L, 1); auto* c = get_constraint(L, 2); auto* err = kiwi_solver_remove_constraint(self->solver, c); return lkiwi_solver_handle_err(L, err, self); } int lkiwi_solver_add_edit_var(lua_State* L) { auto* self = get_solver(L, 1); auto* var = get_var(L, 2); double strength = luaL_checknumber(L, 3); auto* err = kiwi_solver_add_edit_var(self->solver, var, strength); return lkiwi_solver_handle_err(L, err, self); } int lkiwi_solver_remove_edit_var(lua_State* L) { auto* self = get_solver(L, 1); auto* var = get_var(L, 2); auto* err = kiwi_solver_remove_edit_var(self->solver, var); return lkiwi_solver_handle_err(L, err, self); } int lkiwi_solver_suggest_value(lua_State* L) { auto* self = get_solver(L, 1); auto* var = get_var(L, 2); double value = luaL_checknumber(L, 3); auto* err = kiwi_solver_suggest_value(self->solver, var, value); return lkiwi_solver_handle_err(L, err, self); } int lkiwi_solver_update_vars(lua_State* L) { get_solver(L, 1)->solver.updateVariables(); return 0; } int lkiwi_solver_reset(lua_State* L) { get_solver(L, 1)->solver.reset(); return 0; } int lkiwi_solver_has_constraint(lua_State* L) { auto* s = get_solver(L, 1); auto* c = get_constraint(L, 2); lua_pushboolean(L, s->solver.hasConstraint(Constraint(c))); return 1; } int lkiwi_solver_has_edit_var(lua_State* L) { auto* s = get_solver(L, 1); auto* var = get_var(L, 2); lua_pushboolean(L, s->solver.hasEditVariable(Variable(var))); return 1; } int lkiwi_solver_dump(lua_State* L) { get_solver(L, 1)->solver.dump(); return 0; } int lkiwi_solver_dumps(lua_State* L) { const auto& s = get_solver(L, 1)->solver.dumps(); lua_pushlstring(L, s.data(), s.length()); return 1; } template int lkiwi_add_remove_tab(lua_State* L, F&& fn) { auto* solver = get_solver(L, 1); int narg = lua_gettop(L); // block this particularly obnoxious case which is always a bug if (lua_type(L, 2) == LUA_TSTRING) { luaL_typeerror(L, 2, "indexable"); } for (int i = 1; lua_geti(L, 2, i) != LUA_TNIL; ++i) { const KiwiErr* err = fn(L, solver); if (err) { error_new(L, err, 1, narg + 1 /* item_absi */); const auto error_mask = solver->error_mask; if (error_mask & (1 << err->kind)) { lua_replace(L, 3); lua_settop(L, 3); return 2; } else { lua_error(L); } } lua_pop(L, 1); } lua_settop(L, 2); return 1; } int lkiwi_solver_add_constraints(lua_State* L) { return lkiwi_add_remove_tab(L, [](lua_State* L, KiwiSolver* s) { return kiwi_solver_add_constraint(s->solver, get_constraint(L, -1)); }); } int lkiwi_solver_remove_constraints(lua_State* L) { return lkiwi_add_remove_tab(L, [](lua_State* L, KiwiSolver* s) { return kiwi_solver_add_constraint(s->solver, get_constraint(L, -1)); }); } int lkiwi_solver_add_edit_vars(lua_State* L) { double strength = luaL_checknumber(L, 3); return lkiwi_add_remove_tab(L, [strength](lua_State* L, KiwiSolver* s) { return kiwi_solver_add_edit_var(s->solver, get_var(L, -1), strength); }); } int lkiwi_solver_remove_edit_vars(lua_State* L) { return lkiwi_add_remove_tab(L, [](lua_State* L, KiwiSolver* s) { return kiwi_solver_remove_edit_var(s->solver, get_var(L, -1)); }); } int lkiwi_solver_suggest_values(lua_State* L) { auto* self = get_solver(L, 1); int narg = lua_gettop(L); // catch this obnoxious case which is always a bug if (lua_type(L, 2) == LUA_TSTRING) { luaL_typeerror(L, 2, "indexable"); } if (lua_type(L, 3) == LUA_TSTRING) { luaL_typeerror(L, 3, "indexable"); } for (int i = 1; lua_geti(L, 2, i) != LUA_TNIL; ++i) { auto* var = get_var(L, -1); lua_geti(L, 3, i); double value = luaL_checknumber(L, -1); const KiwiErr* err = kiwi_solver_suggest_value(self->solver, var, value); if (err) { error_new(L, err, 1, narg + 1 /* item_absi */); unsigned error_mask = self->error_mask; if (error_mask & (1 << err->kind)) { lua_replace(L, 4); lua_settop(L, 4); return 3; } else { lua_error(L); } } lua_pop(L, 2); } lua_settop(L, 3); return 2; } int lkiwi_solver_set_error_mask(lua_State* L) { auto* solver = get_solver(L, 1); lua_Integer error_mask; if (lua_istable(L, 2)) { lua_settop(L, 3); lua_rotate(L, 1, -1); lkiwi_error_mask(L); error_mask = lua_tointeger(L, -1); } else { error_mask = luaL_checkinteger(L, 2); } solver->error_mask = static_cast(error_mask); return 0; } int lkiwi_solver_m_tostring(lua_State* L) { lua_pushfstring(L, "kiwi.Solver(%p)", get_solver(L, 1)); return 1; } int lkiwi_solver_m_gc(lua_State* L) { get_solver(L, 1)->~KiwiSolver(); return 0; } constexpr const struct luaL_Reg kiwi_solver_m[] = { {"add_constraint", lkiwi_solver_add_constraint}, {"add_constraints", lkiwi_solver_add_constraints}, {"remove_constraint", lkiwi_solver_remove_constraint}, {"remove_constraints", lkiwi_solver_remove_constraints}, {"add_edit_var", lkiwi_solver_add_edit_var}, {"add_edit_vars", lkiwi_solver_add_edit_vars}, {"remove_edit_var", lkiwi_solver_remove_edit_var}, {"remove_edit_vars", lkiwi_solver_remove_edit_vars}, {"suggest_value", lkiwi_solver_suggest_value}, {"suggest_values", lkiwi_solver_suggest_values}, {"update_vars", lkiwi_solver_update_vars}, {"reset", lkiwi_solver_reset}, {"has_constraint", lkiwi_solver_has_constraint}, {"has_edit_var", lkiwi_solver_has_edit_var}, {"dump", lkiwi_solver_dump}, {"dumps", lkiwi_solver_dumps}, {"set_error_mask", lkiwi_solver_set_error_mask}, {"__tostring", lkiwi_solver_m_tostring}, {"__gc", lkiwi_solver_m_gc}, {0, 0} }; int lkiwi_solver_new(lua_State* L) { lua_Integer error_mask; if (lua_istable(L, 1)) { lkiwi_error_mask(L); error_mask = lua_tointeger(L, -1); } else { error_mask = luaL_optinteger(L, 1, 0); } new (lua_newuserdata(L, sizeof(KiwiSolver))) KiwiSolver {static_cast(error_mask)}; push_type(L, SOLVER); lua_setmetatable(L, -2); return 1; } inline double clamp(double n) { return fmax(0.0, fmin(1000, n)); } int lkiwi_strength_create(lua_State* L) { const double a = luaL_checknumber(L, 1); const double b = luaL_checknumber(L, 2); const double c = luaL_checknumber(L, 3); const double w = luaL_optnumber(L, 4, 1.0); const double result = clamp(a * w) * 1000000.0 + clamp(b * w) * 1000.0 + clamp(c * w); lua_pushnumber(L, result); return 1; } constexpr const struct luaL_Reg lkiwi_strength[] = {{"create", lkiwi_strength_create}, {0, 0}}; void lkiwi_mod_strength_new(lua_State* L) { newlib(L, lkiwi_strength); lua_pushnumber(L, STRENGTH_REQUIRED); lua_setfield(L, -2, "REQUIRED"); lua_pushnumber(L, STRENGTH_STRONG); lua_setfield(L, -2, "STRONG"); lua_pushnumber(L, STRENGTH_MEDIUM); lua_setfield(L, -2, "MEDIUM"); lua_pushnumber(L, STRENGTH_WEAK); lua_setfield(L, -2, "WEAK"); } int lkiwi_is_var(lua_State* L) { return is_udata_obj(L, VAR); } int lkiwi_is_term(lua_State* L) { return is_udata_obj(L, TERM); } int lkiwi_is_expression(lua_State* L) { return is_udata_obj(L, EXPR); } int lkiwi_is_constraint(lua_State* L) { return is_udata_obj(L, CONSTRAINT); } int lkiwi_is_solver(lua_State* L) { return is_udata_obj(L, SOLVER); } int lkiwi_is_error(lua_State* L) { int result = 0; if (lua_getmetatable(L, 1)) { push_type(L, ERROR); result = lua_rawequal(L, -1, -2); lua_pop(L, 2); } lua_pushboolean(L, result); return 1; } constexpr const struct luaL_Reg lkiwi[] = { {"Var", 0}, {"is_var", lkiwi_is_var}, {"Term", lkiwi_term_new}, {"is_term", lkiwi_is_term}, {"Expression", lkiwi_expr_new}, {"is_expression", lkiwi_is_expression}, {"Constraint", lkiwi_constraint_new}, {"is_constraint", lkiwi_is_constraint}, {"Solver", lkiwi_solver_new}, {"is_solver", lkiwi_is_solver}, {"error_mask", lkiwi_error_mask}, {"is_error", lkiwi_is_error}, {"eq", lkiwi_eq}, {"le", lkiwi_le}, {"ge", lkiwi_ge}, {0, 0} }; int no_member_mt_index(lua_State* L) { luaL_error(L, "attempt to access non-existent member '%s'", lua_tostring(L, 2)); return 0; } void no_member_mt_new(lua_State* L) { lua_createtable(L, 0, 1); lua_pushcfunction(L, no_member_mt_index); lua_setfield(L, -2, "__index"); } void register_type_n( lua_State* L, const char* name, int context_absi, int type_id, const luaL_Reg* m, size_t mcnt ) { lua_createtable(L, 0, static_cast(mcnt + 2)); lua_pushvalue(L, -2); // no_member_mt lua_setmetatable(L, -2); lua_pushstring(L, name); lua_setfield(L, -2, "__name"); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); /* set type_tab udata as upvalue */ lua_pushvalue(L, context_absi); setfuncs(L, m, 1); lua_rawseti(L, context_absi, type_id); } template constexpr inline void register_type( lua_State* L, const char* name, int context_absi, int type_id, const luaL_Reg (&m)[N] ) { register_type_n(L, name, context_absi, type_id, m, N); } #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 void compat_init(lua_State* L, int context_absi) { static const char var_sub_code[] = "local a,b=...\n" "return a + -b"; if (luaL_loadbuffer(L, var_sub_code, sizeof(var_sub_code) - 1, "=kiwi internal")) lua_error(L); lua_rawseti(L, context_absi, VAR_SUB_FN); } #else void compat_init(lua_State*, int) {} #endif /* Lua 5.1 */ } // namespace #if defined __GNUC__ && (!defined _WIN32 || defined __CYGWIN__) #define LJKIWI_EXPORT __attribute__((__visibility__("default"))) #elif defined _WIN32 #define LJKIWI_EXPORT __declspec(dllexport) #endif extern "C" LJKIWI_EXPORT int luaopen_ljkiwi(lua_State* L) { luaL_checkversion(L); /* context table */ lua_createtable(L, 0, CONTEXT_TAB_MAX); int ctx_i = lua_gettop(L); compat_init(L, ctx_i); lua_pushliteral(L, "kiwi library memory allocation error"); lua_rawseti(L, ctx_i, MEM_ERR_MSG); no_member_mt_new(L); register_type(L, "kiwi.Var", ctx_i, VAR, kiwi_var_m); register_type(L, "kiwi.Term", ctx_i, TERM, kiwi_term_m); register_type(L, "kiwi.Expression", ctx_i, EXPR, kiwi_expr_m); register_type(L, "kiwi.Constraint", ctx_i, CONSTRAINT, kiwi_constraint_m); register_type(L, "kiwi.Solver", ctx_i, SOLVER, kiwi_solver_m); register_type(L, "kiwi.Error", ctx_i, ERROR, lkiwi_error_m); lua_createtable(L, 0, array_count(lkiwi) + 6); lua_pushvalue(L, ctx_i); setfuncs(L, lkiwi, 1); /* var weak table */ /* set as upvalue for selected functions */ lua_createtable(L, 0, 0); lua_createtable(L, 0, 1); lua_pushstring(L, "v"); lua_setfield(L, -2, "__mode"); lua_setmetatable(L, -2); lua_pushvalue(L, ctx_i); lua_pushvalue(L, -2); lua_pushcclosure(L, lkiwi_var_new, 2); lua_setfield(L, -3, "Var"); lua_rawgeti(L, ctx_i, TERM); lua_pushvalue(L, ctx_i); lua_pushvalue(L, -3); lua_pushcclosure(L, lkiwi_term_m_index, 2); lua_setfield(L, -2, "__index"); lua_pop(L, 2); // TERM mt and var weak table /* ErrKind table */ /* TODO: implement __call metamethod for these */ lua_createtable(L, array_count(lkiwi_error_kinds) + 1, array_count(lkiwi_error_kinds)); for (int i = 0; i < array_count(lkiwi_error_kinds); i++) { lua_pushstring(L, lkiwi_error_kinds[i]); lua_pushvalue(L, -1); lua_rawseti(L, -3, i); lua_pushinteger(L, i); lua_rawset(L, -3); } lua_pushvalue(L, -1); lua_rawseti(L, ctx_i, ERR_KIND_TAB); lua_setfield(L, -2, "ErrKind"); lua_rawgeti(L, ctx_i, ERROR); lua_setfield(L, -2, "Error"); lua_pushinteger(L, 0xFFFF); lua_setfield(L, -2, "ERROR_MASK_ALL"); lua_pushinteger( L, ~((1 << KiwiErrInternalSolverError) | (1 << KiwiErrAlloc) | (1 << KiwiErrNullObject) | (1 << KiwiErrUnknown)) ); lua_setfield(L, -2, "ERROR_MASK_NON_FATAL"); lkiwi_mod_strength_new(L); lua_setfield(L, -2, "strength"); lkiwi_mod_constraints_new(L, ctx_i); lua_setfield(L, -2, "constraints"); return 1; }