Files
ljkiwi/luakiwi/luakiwi.cpp

1711 lines
45 KiB
C++

#include <math.h>
#include <stdlib.h>
#include <string.h>
#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<VariableData**>(try_type(L, idx, VAR));
}
inline KiwiTerm* try_term(lua_State* L, int idx) {
return static_cast<KiwiTerm*>(try_type(L, idx, TERM));
}
inline KiwiExpression* try_expr(lua_State* L, int idx) {
return static_cast<KiwiExpression*>(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<VariableData**>(check_arg(L, idx, VAR));
}
inline KiwiTerm* get_term(lua_State* L, int idx) {
return static_cast<KiwiTerm*>(check_arg(L, idx, TERM));
}
inline KiwiExpression* get_expr(lua_State* L, int idx) {
return static_cast<KiwiExpression*>(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<KiwiExpression*>(check_arg(L, idx, EXPR));
}
inline ConstraintData* get_constraint(lua_State* L, int idx) {
return *static_cast<ConstraintData**>(check_arg(L, idx, CONSTRAINT));
}
inline KiwiSolver* get_solver(lua_State* L, int idx) {
return static_cast<KiwiSolver*>(check_arg(L, idx, SOLVER));
}
VariableData** var_new(lua_State* L) {
auto** varp = static_cast<VariableData**>(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<KiwiTerm*>(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<KiwiExpression*>(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<ConstraintData**>(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<KiwiExpression*>(ud);
}
temp->constant = 0;
temp->term_count = 1;
push_type(L, VAR);
if (lua_rawequal(L, -1, -3)) {
temp->terms[0].var = *static_cast<VariableData**>(ud);
temp->terms[0].coefficient = 1.0;
return temp;
}
push_type(L, TERM);
if (lua_rawequal(L, -1, -4)) {
temp->terms[0] = *static_cast<KiwiTerm*>(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<KiwiExpression*>(tmpl));
const auto* rhs = toexpr(L, 2, reinterpret_cast<KiwiExpression*>(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<VariableData**>(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<VariableData**>(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<KiwiTerm*>(arg_b)));
case EXPR: {
const KiwiTerm t {var_a, 1.0};
return push_add_expr_term(L, static_cast<KiwiExpression*>(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<VariableData>(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<KiwiTerm*>(arg_b));
case VAR: {
const KiwiTerm term_b {*static_cast<VariableData**>(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<KiwiExpression*>(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<const KiwiExpression*>(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<KiwiExpression*>(arg_b));
case TERM:
return push_add_expr_term(L, expr_a, static_cast<KiwiTerm*>(arg_b));
case VAR: {
const KiwiTerm term_b {*static_cast<VariableData**>(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<int>(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<std::size_t>(i)];
ne->terms[i].var = const_cast<Variable&>(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<KiwiExpression*>(&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<KiwiExpression*>(&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<KiwiErr*>(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<typename F>
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<unsigned>(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<unsigned>(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<int>(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<std::size_t N>
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;
}