Guard against most egregious mistakes in calling the library

LuaJIT FFI is not inherently memory safe and there is no way
to completely guard against the caller doing something that
will trample over memory, but we can get pretty close. Biggest
issue is that an empty table will stand-in for a ref struct with
a null ref. So check for that in all the calls. In the calls that
raise errors we now have a specific error for it. In the other
functions the "nil" object is handled quietly but without a nullptr
dereference and hopefully no UB.
This commit is contained in:
2024-02-12 22:03:45 -06:00
parent 59bdeedc18
commit e43272487f
8 changed files with 428 additions and 273 deletions

View File

@@ -8,5 +8,6 @@
"lua_modules/share/lua/5.1/?.lua", "lua_modules/share/lua/5.1/?.lua",
"lua_modules/share/lua/5.1/?/init.lua" "lua_modules/share/lua/5.1/?/init.lua"
], ],
"workspace.library": ["${3rd}/busted/library", "${3rd}/luassert/library"],
"workspace.checkThirdParty": false "workspace.checkThirdParty": false
} }

View File

@@ -1,22 +1,29 @@
SRCDIR ?= . SRCDIR := .
CC ?= $(CROSS)gcc CC := $(CROSS)gcc
CFLAGS ?= -fPIC -O2 CFLAGS := -fPIC -O2
CFLAGS += -Wall -I$(SRCDIR)/kiwi CFLAGS += -Wall -I$(SRCDIR)/kiwi
LIBFLAG ?= -shared LIBFLAG := -shared
LIB_EXT ?= so LIB_EXT := so
ifeq ($(findstring gcc, $(CC)), gcc) ifeq ($(findstring gcc, $(CC)), gcc)
CXX := $(subst gcc, g++, $(CC)) CXX := $(subst gcc, g++, $(CC))
CXXFLAGS += -std=c++11 CXXFLAGS += -std=c++14
ifneq ($(SANITIZE),)
CFLAGS += -fsanitize=undefined -fsanitize=address
endif
else else
ifeq ($(CC), clang) ifeq ($(CC), clang)
CXX := clang++ CXX := clang++
CXXFLAGS += -std=c++11 CXXFLAGS += -std=c++14
ifneq ($(SANITIZE),)
CFLAGS += -fsanitize=undefined -fsanitize=address
endif
else else
CXX := $(CC) CXX := $(CC)
endif endif
endif endif
all: ckiwi.$(LIB_EXT) all: ckiwi.$(LIB_EXT)
install: install:
@@ -26,7 +33,7 @@ install:
clean: clean:
rm -f ckiwi.$(LIB_EXT) rm -f ckiwi.$(LIB_EXT)
ckiwi.$(LIB_EXT): $(SRCDIR)/ckiwi/ckiwi.cpp ckiwi.$(LIB_EXT): $(SRCDIR)/ckiwi/ckiwi.cpp $(SRCDIR)/ckiwi/ckiwi.h
$(CXX) $(CXXFLAGS) $(CFLAGS) -fPIC -Wall -I$(SRCDIR)/kiwi $(LIBFLAG) -o $@ $< $(CXX) $(CXXFLAGS) $(CFLAGS) -fPIC -Wall -I$(SRCDIR)/kiwi $(LIBFLAG) -o $@ $<
.PHONY: all install clean .PHONY: all install clean

View File

@@ -11,7 +11,7 @@ Cassowary constraint solving is a technique that is particularly well suited to
There are a few Lua implementations or attempts. The SILE typesetting system has a pure Lua implementation of the original Cassowary code, which appears to be correct but is quite slow. There are two extant Lua ports of Kiwi, one that is based on a C rewrite of Kiwi. However testing of these was not encouraging with either segfaults or incorrect results. There are a few Lua implementations or attempts. The SILE typesetting system has a pure Lua implementation of the original Cassowary code, which appears to be correct but is quite slow. There are two extant Lua ports of Kiwi, one that is based on a C rewrite of Kiwi. However testing of these was not encouraging with either segfaults or incorrect results.
Since the C++ Kiwi library is well tested and widely used it was simpler to provide a LuaJIT FFI wrapper and use that. Since the C++ Kiwi library is well tested and widely used it was simpler to provide a LuaJIT FFI wrapper and use that.
This package has no dependencies other than a C++11 toolchain to compile the included Kiwi library and a small C wrapper. This package has no dependencies other than a C++14 toolchain to compile the included Kiwi library and a small C wrapper.
The Lua API has a pure Lua expression builder. There is of course some overhead to this, however in most cases expression building is infrequent and the underlying structures can be reused. The Lua API has a pure Lua expression builder. There is of course some overhead to this, however in most cases expression building is infrequent and the underlying structures can be reused.

View File

@@ -27,7 +27,7 @@ BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon BreakInheritanceList: AfterColon
BreakStringLiterals: false BreakStringLiterals: false
ColumnLimit: 90 ColumnLimit: 98
CompactNamespaces: false CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4 ConstructorInitializerIndentWidth: 4

View File

@@ -2,104 +2,206 @@
#include <kiwi/kiwi.h> #include <kiwi/kiwi.h>
#include <cstdlib>
#include <cstring> #include <cstring>
using namespace kiwi; using namespace kiwi;
namespace { namespace {
template<class T, class CS>
class alignas(T) SharedRef { template<typename T, typename R, typename... Args>
inline R to_cref(Args&&... args) {
static_assert(
sizeof(R) >= sizeof(T), //NOLINT(bugprone-sizeof-expression)
"to_cref: R too small for T"
);
static_assert(alignof(R) >= alignof(T), "to_cref: R alignment too small for T");
R cref;
new (&cref) T(std::forward<Args>(args)...);
return cref;
}
template<typename... Args>
inline decltype(auto) to_var_cref(Args&&... args) {
return to_cref<Variable, KiwiVarRef>(std::forward<Args>(args)...);
}
template<typename... Args>
inline decltype(auto) to_constraint_cref(Args&&... args) {
return to_cref<Constraint, KiwiConstraintRef>(std::forward<Args>(args)...);
}
template<class T, class R>
class SharedRef {
private: private:
CS ref_; R& cref_;
public: public:
T* operator&() { explicit SharedRef<T, R>(R& cref) : cref_(cref) {}
return reinterpret_cast<T*>(&ref_);
static_assert(
sizeof(R) >= sizeof(T), //NOLINT(bugprone-sizeof-expression)
"SharedRef<T,CS> CS too small for T"
);
void destroy() {
if (cref_) {
ptr()->~T();
cref_ = nullptr;
}
}
T* ptr() {
T* p;
void* s = &cref_;
std::memcpy(&p, &s, sizeof p); //NOLINT(bugprone-sizeof-expression)
return p;
}
const T* const_ptr() const {
const T* p;
const void* s = &cref_;
std::memcpy(&p, &s, sizeof p); //NOLINT(bugprone-sizeof-expression)
return p;
}
const T& cref() const {
return *const_ptr();
}
T* operator&() const {
return ptr();
} }
T* operator->() { T* operator->() {
return reinterpret_cast<T*>(&ref_); return ptr();
} }
operator T&() { const T* operator->() const {
return *reinterpret_cast<T*>(&ref_); return const_ptr();
} }
operator CS() const { operator const T&() const {
return ref_; return cref();
} }
T& instance() { explicit operator bool() const {
return *reinterpret_cast<T*>(&ref_); return cref_;
} }
void destroy() {
instance().~T();
ref_ = {0};
}
SharedRef<T, CS>(CS ref) : ref_(ref) {}
template<typename... Args>
SharedRef<T, CS>(Args&&... args) {
new (&ref_) T(std::forward<Args>(args)...);
}
static_assert(sizeof(CS) >= sizeof(T), "SharedRef cannot wrap T (size)");
}; };
using ConstraintRef = SharedRef<Constraint, KiwiConstraintRef>; using ConstraintRef = SharedRef<Constraint, KiwiConstraintRef>;
using VariableRef = SharedRef<Variable, KiwiVarRef>; using VariableRef = SharedRef<Variable, KiwiVarRef>;
using ConstVariableRef = const SharedRef<const Variable, const KiwiVarRef>;
KiwiErr make_error(KiwiErrKind kind, const std::exception& ex) { KiwiErrPtr new_error(KiwiErrPtr base, const std::exception& ex) {
constexpr auto max_n = sizeof(KiwiErr::message) - 1; if (!std::strcmp(ex.what(), base->message))
const auto n = std::min(std::strlen(ex.what()), max_n); return base;
KiwiErr err {kind}; const auto msg_n = std::strlen(ex.what()) + 1;
std::memcpy(err.message, ex.what(), n); auto* mem = static_cast<char*>(std::malloc(sizeof(KiwiErr) + msg_n));
if (n == max_n) if (!mem) {
err.message[max_n] = 0; return base;
}
const auto* err = new (mem) KiwiErr {base->kind, mem + sizeof(KiwiErr), true};
std::memcpy(const_cast<char*>(err->message), ex.what(), msg_n);
return err; return err;
} }
KiwiErr make_error(KiwiErrKind kind) { static const constexpr KiwiErr kKiwiErrUnhandledCxxException {
return KiwiErr {kind, {0}}; KiwiErrUnknown,
} "An unhandled C++ exception occurred."};
static const constexpr KiwiErr kKiwiErrNullObjectArg0 {
KiwiErrNullObject,
"null object passed as argument #0 (self)"};
static const constexpr KiwiErr kKiwiErrNullObjectArg1 {
KiwiErrNullObject,
"null object passed as argument #1"};
template<typename F> template<typename F>
inline KiwiErr wrap_err(F&& f) { inline KiwiErrPtr wrap_err(F&& f) {
try { try {
f(); f();
} catch (const UnsatisfiableConstraint& err) { } catch (const UnsatisfiableConstraint& ex) {
return make_error(KiwiErrUnsatisfiableConstraint, err); static const constexpr KiwiErr err {
} catch (const UnknownConstraint& err) { KiwiErrUnsatisfiableConstraint,
return make_error(KiwiErrUnknownConstraint, err); "The constraint cannot be satisfied."};
} catch (const DuplicateConstraint& err) { return &err;
return make_error(KiwiErrDuplicateConstraint, err); } catch (const UnknownConstraint& ex) {
} catch (const UnknownEditVariable& err) { static const constexpr KiwiErr err {
return make_error(KiwiErrUnknownEditVariable, err); KiwiErrUnknownConstraint,
} catch (const DuplicateEditVariable& err) { "The constraint has not been added to the solver."};
return make_error(KiwiErrDuplicateEditVariable, err); return &err;
} catch (const BadRequiredStrength& err) {
return make_error(KiwiErrBadRequiredStrength, err); } catch (const DuplicateConstraint& ex) {
} catch (const InternalSolverError& err) { static const constexpr KiwiErr err {
return make_error(KiwiErrInternalSolverError, err); KiwiErrDuplicateConstraint,
"The constraint has already been added to the solver."};
return &err;
} catch (const UnknownEditVariable& ex) {
static const constexpr KiwiErr err {
KiwiErrUnknownEditVariable,
"The edit variable has not been added to the solver."};
return &err;
} catch (const DuplicateEditVariable& ex) {
static const constexpr KiwiErr err {
KiwiErrDuplicateEditVariable,
"The edit variable has already been added to the solver."};
return &err;
} catch (const BadRequiredStrength& ex) {
static const constexpr KiwiErr err {
KiwiErrBadRequiredStrength,
"A required strength cannot be used in this context."};
return &err;
} catch (const InternalSolverError& ex) {
static const constexpr KiwiErr base {
KiwiErrInternalSolverError,
"An internal solver error occurred."};
return new_error(&base, ex);
} catch (std::bad_alloc&) { } catch (std::bad_alloc&) {
return make_error(KiwiErrAlloc); static const constexpr KiwiErr err {KiwiErrAlloc, "A memory allocation failed."};
} catch (const std::exception& err) { return &err;
return make_error(KiwiErrUnknown, err); } catch (const std::exception& ex) {
return new_error(&kKiwiErrUnhandledCxxException, ex);
} catch (...) { } catch (...) {
return make_error(KiwiErrUnknown); return &kKiwiErrUnhandledCxxException;
} }
return make_error(KiwiErrNone); return nullptr;
} }
template<typename P, typename R, typename F>
inline KiwiErrPtr wrap_err(P ptr, F&& f) {
if (!ptr) {
return &kKiwiErrNullObjectArg0;
}
return wrap_err([&]() { f(ptr); });
}
template<typename P, typename R, typename F>
inline KiwiErrPtr wrap_err(P ptr, R ref, F&& f) {
if (!ptr) {
return &kKiwiErrNullObjectArg0;
} else if (!ref) {
return &kKiwiErrNullObjectArg1;
}
return wrap_err([&]() { f(ptr, ref); });
}
} // namespace } // namespace
extern "C" { extern "C" {
KiwiVarRef kiwi_var_new(const char* name) { KiwiVarRef kiwi_var_new(const char* name) {
return VariableRef(name); return to_var_cref(name ? name : "");
} }
void kiwi_var_del(KiwiVarRef var) { void kiwi_var_del(KiwiVarRef var) {
@@ -107,42 +209,50 @@ void kiwi_var_del(KiwiVarRef var) {
} }
const char* kiwi_var_name(KiwiVarRef var) { const char* kiwi_var_name(KiwiVarRef var) {
return VariableRef(var)->name().c_str(); const VariableRef self(var);
return self ? self->name().c_str() : "(<null>)";
} }
void kiwi_var_set_name(KiwiVarRef var, const char* name) { void kiwi_var_set_name(KiwiVarRef var, const char* name) {
VariableRef(var)->setName(name); VariableRef self(var);
if (self)
self->setName(name);
} }
double kiwi_var_value(KiwiVarRef var) { double kiwi_var_value(KiwiVarRef var) {
return VariableRef(var)->value(); const VariableRef self(var);
return self ? self->value() : std::numeric_limits<double>::quiet_NaN();
} }
void kiwi_var_set_value(KiwiVarRef var, double value) { void kiwi_var_set_value(KiwiVarRef var, double value) {
VariableRef(var)->setValue(value); VariableRef self(var);
if (self)
self->setValue(value);
} }
int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other) { int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other) {
return VariableRef(var)->equals(VariableRef(other)); VariableRef self(var); // const defect in upstream
const VariableRef other_ref(other);
return self && other_ref && self->equals(other_ref);
} }
KiwiConstraintRef kiwi_constraint_new( KiwiConstraintRef
const KiwiExpression* expression, kiwi_constraint_new(KiwiExpressionConstPtr expression, enum KiwiRelOp op, double strength) {
enum KiwiRelOp op,
double strength
) {
if (strength < 0.0) { if (strength < 0.0) {
strength = kiwi::strength::required; strength = kiwi::strength::required;
} }
std::vector<Term> terms; std::vector<Term> terms;
if (expression) {
terms.reserve(expression->term_count); terms.reserve(expression->term_count);
for (auto* t = expression->terms; t != expression->terms + expression->term_count; for (auto* t = expression->terms; t != expression->terms + expression->term_count; ++t) {
++t) { ConstVariableRef var(t->var);
terms.emplace_back(VariableRef(t->var), t->coefficient); if (var)
terms.emplace_back(var.cref(), t->coefficient);
} }
}
return ConstraintRef( return to_constraint_cref(
Expression(std::move(terms), expression->constant), Expression(std::move(terms), expression->constant),
static_cast<RelationalOperator>(op), static_cast<RelationalOperator>(op),
strength strength
@@ -154,23 +264,25 @@ void kiwi_constraint_del(KiwiConstraintRef constraint) {
} }
double kiwi_constraint_strength(KiwiConstraintRef constraint) { double kiwi_constraint_strength(KiwiConstraintRef constraint) {
return ConstraintRef(constraint)->strength(); const ConstraintRef self(constraint);
return self ? self->strength() : std::numeric_limits<double>::quiet_NaN();
} }
enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint) { enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint) {
return static_cast<KiwiRelOp>(ConstraintRef(constraint)->op()); const ConstraintRef self(constraint);
return self ? static_cast<KiwiRelOp>(self->op()) : KiwiRelOp::KIWI_OP_EQ;
} }
int kiwi_constraint_violated(KiwiConstraintRef constraint) { bool kiwi_constraint_violated(KiwiConstraintRef constraint) {
return ConstraintRef(constraint)->violated(); const ConstraintRef self(constraint);
return self ? self->violated() : 0;
} }
int kiwi_constraint_expression( int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpression* out, int out_size) {
KiwiConstraintRef constraint, const ConstraintRef self(constraint);
KiwiExpression* out, if (!self)
int out_size return 0;
) { const auto& expr = self->expression();
const auto& expr = ConstraintRef(constraint).instance().expression();
const auto& terms = expr.terms(); const auto& terms = expr.terms();
const int n_terms = terms.size(); const int n_terms = terms.size();
if (!out || out_size < n_terms) if (!out || out_size < n_terms)
@@ -178,7 +290,7 @@ int kiwi_constraint_expression(
auto* p = out->terms; auto* p = out->terms;
for (const auto& t : terms) { for (const auto& t : terms) {
*p = KiwiTerm {VariableRef(t.variable()), t.coefficient()}; *p = KiwiTerm {to_var_cref(t.variable()), t.coefficient()};
++p; ++p;
} }
out->term_count = p - out->terms; out->term_count = p - out->terms;
@@ -187,71 +299,100 @@ int kiwi_constraint_expression(
return n_terms; return n_terms;
} }
KiwiSolverRef kiwi_solver_new() { KiwiSolverPtr kiwi_solver_new() {
return KiwiSolverRef {new (std::nothrow) Solver()}; return reinterpret_cast<KiwiSolverPtr>(new (std::nothrow) Solver());
} }
void kiwi_solver_del(KiwiSolverRef s) { void kiwi_solver_del(KiwiSolverPtr sp) {
delete reinterpret_cast<Solver*>(s.impl_); auto* solver = reinterpret_cast<Solver*>(sp);
if (solver)
delete solver;
} }
KiwiErr kiwi_solver_add_constraint(KiwiSolverRef s, KiwiConstraintRef constraint) { KiwiErrPtr kiwi_solver_add_constraint(KiwiSolverPtr s, KiwiConstraintRef constraint) {
return wrap_err([=]() { return wrap_err(
reinterpret_cast<Solver*>(s.impl_)->addConstraint(ConstraintRef(constraint)); reinterpret_cast<Solver*>(s),
ConstraintRef(constraint),
[](auto solver, const auto c) { solver->addConstraint(c); }
);
}
KiwiErrPtr kiwi_solver_remove_constraint(KiwiSolverPtr s, KiwiConstraintRef constraint) {
return wrap_err(
reinterpret_cast<Solver*>(s),
ConstraintRef(constraint),
[](auto solver, const auto c) { solver->removeConstraint(c); }
);
}
bool kiwi_solver_has_constraint(KiwiSolverPtr s, KiwiConstraintRef constraint) {
const auto* solver = reinterpret_cast<Solver*>(s);
ConstraintRef c(constraint);
if (!solver || !c)
return 0;
return solver->hasConstraint(c);
}
KiwiErrPtr kiwi_solver_add_edit_var(KiwiSolverPtr s, KiwiVarRef var, double strength) {
return wrap_err(
reinterpret_cast<Solver*>(s),
VariableRef(var),
[strength](auto solver, const auto v) { solver->addEditVariable(v, strength); }
);
}
KiwiErrPtr kiwi_solver_remove_edit_var(KiwiSolverPtr s, KiwiVarRef var) {
return wrap_err(reinterpret_cast<Solver*>(s), VariableRef(var), [](auto solver, const auto v) {
solver->removeEditVariable(v);
}); });
} }
KiwiErr kiwi_solver_remove_constraint(KiwiSolverRef s, KiwiConstraintRef constraint) { bool kiwi_solver_has_edit_var(KiwiSolverPtr s, KiwiVarRef var) {
return wrap_err([=]() { const auto* solver = reinterpret_cast<Solver*>(s);
reinterpret_cast<Solver*>(s.impl_)->removeConstraint(ConstraintRef(constraint)); VariableRef v(var);
}); if (!solver || !v)
return 0;
return solver->hasEditVariable(v);
} }
int kiwi_solver_has_constraint(KiwiSolverRef s, KiwiConstraintRef constraint) { KiwiErrPtr kiwi_solver_suggest_value(KiwiSolverPtr s, KiwiVarRef var, double value) {
return reinterpret_cast<Solver*>(s.impl_)->hasConstraint(ConstraintRef(constraint)); return wrap_err(
reinterpret_cast<Solver*>(s),
VariableRef(var),
[value](auto solver, const auto v) { solver->suggestValue(v, value); }
);
} }
KiwiErr kiwi_solver_add_edit_var(KiwiSolverRef s, KiwiVarRef var, double strength) { void kiwi_solver_update_vars(KiwiSolverPtr s) {
return wrap_err([=]() { auto* solver = reinterpret_cast<Solver*>(s);
reinterpret_cast<Solver*>(s.impl_)->addEditVariable(VariableRef(var), strength); if (solver)
}); solver->updateVariables();
} }
KiwiErr kiwi_solver_remove_edit_var(KiwiSolverRef s, KiwiVarRef var) { void kiwi_solver_reset(KiwiSolverPtr s) {
return wrap_err([=]() { auto* solver = reinterpret_cast<Solver*>(s);
reinterpret_cast<Solver*>(s.impl_)->removeEditVariable(VariableRef(var)); if (solver)
}); solver->reset();
} }
int kiwi_solver_has_edit_var(KiwiSolverRef s, KiwiVarRef var) { void kiwi_solver_dump(KiwiSolverPtr s) {
return reinterpret_cast<Solver*>(s.impl_)->hasEditVariable(VariableRef(var)); auto* solver = reinterpret_cast<Solver*>(s);
if (solver)
solver->dump();
} }
KiwiErr kiwi_solver_suggest_value(KiwiSolverRef s, KiwiVarRef var, double value) { char* kiwi_solver_dumps(KiwiSolverPtr s) {
return wrap_err([=]() { auto* solver = reinterpret_cast<Solver*>(s);
reinterpret_cast<Solver*>(s.impl_)->suggestValue(VariableRef(var), value); if (!solver)
}); return nullptr;
}
void kiwi_solver_update_vars(KiwiSolverRef s) { const auto str = solver->dumps();
reinterpret_cast<Solver*>(s.impl_)->updateVariables(); const auto buf_size = str.size() + 1;
} auto* buf = static_cast<char*>(std::malloc(buf_size));
void kiwi_solver_reset(KiwiSolverRef s) {
reinterpret_cast<Solver*>(s.impl_)->reset();
}
void kiwi_solver_dump(KiwiSolverRef s) {
reinterpret_cast<Solver*>(s.impl_)->dump();
}
char* kiwi_solver_dumps(KiwiSolverRef s, void* (*alloc)(size_t)) {
const auto val = reinterpret_cast<Solver*>(s.impl_)->dumps();
const auto buf_size = val.size() + 1;
auto* buf = static_cast<char*>(alloc ? alloc(buf_size) : malloc(buf_size));
if (!buf) if (!buf)
return nullptr; return nullptr;
std::memcpy(buf, val.c_str(), val.size() + 1); std::memcpy(buf, str.c_str(), str.size() + 1);
return buf; return buf;
} }

View File

@@ -20,38 +20,39 @@ enum KiwiErrKind {
KiwiErrBadRequiredStrength, KiwiErrBadRequiredStrength,
KiwiErrInternalSolverError, KiwiErrInternalSolverError,
KiwiErrAlloc, KiwiErrAlloc,
KiwiErrNullObject,
KiwiErrUnknown, KiwiErrUnknown,
}; };
enum KiwiRelOp { KIWI_OP_LE, KIWI_OP_GE, KIWI_OP_EQ }; enum KiwiRelOp { KIWI_OP_LE, KIWI_OP_GE, KIWI_OP_EQ };
typedef struct { struct KiwiVarRefType;
void* private_; struct KiwiConstraintRefType;
} KiwiVarRef;
typedef struct { typedef struct KiwiVarRefType* KiwiVarRef;
typedef struct KiwiConstraintRefType* KiwiConstraintRef;
struct KiwiTerm {
KiwiVarRef var; KiwiVarRef var;
double coefficient; double coefficient;
} KiwiTerm; };
typedef struct { typedef struct KiwiExpression {
double constant; double constant;
int term_count; int term_count;
KiwiTerm terms[1]; struct KiwiTerm terms[1]; // LuaJIT: struct KiwiTerm terms_[?];
} KiwiExpression; }* KiwiExpressionPtr;
typedef struct { typedef const struct KiwiExpression* KiwiExpressionConstPtr;
void* private_;
} KiwiConstraintRef;
typedef struct { typedef struct KiwiErr {
enum KiwiErrKind kind; enum KiwiErrKind kind;
char message[64]; const char* message;
} KiwiErr; bool must_free;
} const* KiwiErrPtr;
typedef struct { struct KiwiSolver;
void* impl_; typedef struct KiwiSolver* KiwiSolverPtr;
} KiwiSolverRef;
KiwiVarRef kiwi_var_new(const char* name); KiwiVarRef kiwi_var_new(const char* name);
void kiwi_var_del(KiwiVarRef var); void kiwi_var_del(KiwiVarRef var);
@@ -67,45 +68,30 @@ void kiwi_var_set_value(KiwiVarRef var, double value);
int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other); int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other);
KiwiConstraintRef KiwiConstraintRef
kiwi_constraint_new(const KiwiExpression* expression, enum KiwiRelOp op, double strength); kiwi_constraint_new(KiwiExpressionConstPtr expression, enum KiwiRelOp op, double strength);
void kiwi_constraint_del(KiwiConstraintRef constraint); void kiwi_constraint_del(KiwiConstraintRef constraint);
double kiwi_constraint_strength(KiwiConstraintRef constraint); double kiwi_constraint_strength(KiwiConstraintRef constraint);
enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint); enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint);
int kiwi_constraint_violated(KiwiConstraintRef constraint); bool kiwi_constraint_violated(KiwiConstraintRef constraint);
int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpressionPtr out, int out_size);
int kiwi_constraint_expression( KiwiErrPtr kiwi_solver_add_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint);
KiwiConstraintRef constraint, KiwiErrPtr kiwi_solver_remove_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint);
KiwiExpression* out, bool kiwi_solver_has_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint);
int out_size KiwiErrPtr kiwi_solver_add_edit_var(KiwiSolverPtr sp, KiwiVarRef var, double strength);
); KiwiErrPtr kiwi_solver_remove_edit_var(KiwiSolverPtr sp, KiwiVarRef var);
bool kiwi_solver_has_edit_var(KiwiSolverPtr sp, KiwiVarRef var);
KiwiErrPtr kiwi_solver_suggest_value(KiwiSolverPtr sp, KiwiVarRef var, double value);
void kiwi_solver_update_vars(KiwiSolverPtr sp);
void kiwi_solver_reset(KiwiSolverPtr sp);
void kiwi_solver_dump(KiwiSolverPtr sp);
char* kiwi_solver_dumps(KiwiSolverPtr sp);
KiwiErr kiwi_solver_add_constraint(KiwiSolverRef s, KiwiConstraintRef constraint); KiwiSolverPtr kiwi_solver_new();
void kiwi_solver_del(KiwiSolverPtr sp);
KiwiErr kiwi_solver_remove_constraint(KiwiSolverRef s, KiwiConstraintRef constraint);
int kiwi_solver_has_constraint(KiwiSolverRef s, KiwiConstraintRef constraint);
KiwiErr kiwi_solver_add_edit_var(KiwiSolverRef s, KiwiVarRef var, double strength);
KiwiErr kiwi_solver_remove_edit_var(KiwiSolverRef s, KiwiVarRef var);
int kiwi_solver_has_edit_var(KiwiSolverRef s, KiwiVarRef var);
KiwiErr kiwi_solver_suggest_value(KiwiSolverRef s, KiwiVarRef var, double value);
void kiwi_solver_update_vars(KiwiSolverRef s);
void kiwi_solver_reset(KiwiSolverRef s);
void kiwi_solver_dump(KiwiSolverRef s);
char* kiwi_solver_dumps(KiwiSolverRef s, void* (*alloc)(size_t));
KiwiSolverRef kiwi_solver_new();
void kiwi_solver_del(KiwiSolverRef s);
// LuaJIT end // LuaJIT end
#ifdef __cplusplus #ifdef __cplusplus

191
kiwi.lua
View File

@@ -22,86 +22,85 @@ enum KiwiErrKind {
KiwiErrBadRequiredStrength, KiwiErrBadRequiredStrength,
KiwiErrInternalSolverError, KiwiErrInternalSolverError,
KiwiErrAlloc, KiwiErrAlloc,
KiwiErrNullObject,
KiwiErrUnknown, KiwiErrUnknown,
}; };
enum KiwiRelOp { LE, GE, EQ }; enum KiwiRelOp { LE, GE, EQ };
typedef struct { struct KiwiVarRefType;
void* private_; struct KiwiConstraintRefType;
} KiwiVarRef;
typedef struct { typedef struct KiwiVarRefType* KiwiVarRef;
typedef struct KiwiConstraintRefType* KiwiConstraintRef;
struct KiwiTerm {
KiwiVarRef var; KiwiVarRef var;
double coefficient; double coefficient;
} KiwiTerm; };
typedef struct { typedef struct KiwiExpression {
double constant; double constant;
int term_count; int term_count;
KiwiTerm terms_[?]; struct KiwiTerm terms_[?];
} KiwiExpression; }* KiwiExpressionPtr;
typedef struct { typedef const struct KiwiExpression* KiwiExpressionConstPtr;
void* private_;
} KiwiConstraintRef;
typedef struct { typedef struct KiwiErr {
enum KiwiErrKind kind; enum KiwiErrKind kind;
char message[64]; const char* message;
} KiwiErr; bool must_free;
} const* KiwiErrPtr;
typedef struct { struct KiwiSolver;
void* impl_; typedef struct KiwiSolver* KiwiSolverPtr;
} KiwiSolverRef;
KiwiVarRef kiwi_var_new(const char* name); KiwiVarRef kiwi_var_new(const char* name);
void kiwi_var_del(KiwiVarRef var); void kiwi_var_del(KiwiVarRef var);
const char* kiwi_var_name(KiwiVarRef var); const char* kiwi_var_name(KiwiVarRef var);
void kiwi_var_set_name(KiwiVarRef var, const char* name); void kiwi_var_set_name(KiwiVarRef var, const char* name);
double kiwi_var_value(KiwiVarRef var); double kiwi_var_value(KiwiVarRef var);
void kiwi_var_set_value(KiwiVarRef var, double value); void kiwi_var_set_value(KiwiVarRef var, double value);
int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other); int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other);
KiwiConstraintRef KiwiConstraintRef
kiwi_constraint_new(const KiwiExpression* expression, enum KiwiRelOp op, double strength); kiwi_constraint_new(KiwiExpressionConstPtr expression, enum KiwiRelOp op, double strength);
void kiwi_constraint_del(KiwiConstraintRef constraint); void kiwi_constraint_del(KiwiConstraintRef constraint);
double kiwi_constraint_strength(KiwiConstraintRef constraint); double kiwi_constraint_strength(KiwiConstraintRef constraint);
enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint); enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint);
int kiwi_constraint_violated(KiwiConstraintRef constraint); bool kiwi_constraint_violated(KiwiConstraintRef constraint);
int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpressionPtr out, int out_size);
int kiwi_constraint_expression( KiwiErrPtr kiwi_solver_add_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint);
KiwiConstraintRef constraint, KiwiErrPtr kiwi_solver_remove_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint);
KiwiExpression* out, bool kiwi_solver_has_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint);
int out_size KiwiErrPtr kiwi_solver_add_edit_var(KiwiSolverPtr sp, KiwiVarRef var, double strength);
); KiwiErrPtr kiwi_solver_remove_edit_var(KiwiSolverPtr sp, KiwiVarRef var);
bool kiwi_solver_has_edit_var(KiwiSolverPtr sp, KiwiVarRef var);
KiwiErrPtr kiwi_solver_suggest_value(KiwiSolverPtr sp, KiwiVarRef var, double value);
void kiwi_solver_update_vars(KiwiSolverPtr sp);
void kiwi_solver_reset(KiwiSolverPtr sp);
void kiwi_solver_dump(KiwiSolverPtr sp);
char* kiwi_solver_dumps(KiwiSolverPtr sp);
KiwiErr kiwi_solver_add_constraint(KiwiSolverRef s, KiwiConstraintRef constraint); KiwiSolverPtr kiwi_solver_new();
KiwiErr kiwi_solver_remove_constraint(KiwiSolverRef s, KiwiConstraintRef constraint); void kiwi_solver_del(KiwiSolverPtr sp);
int kiwi_solver_has_constraint(KiwiSolverRef s, KiwiConstraintRef constraint);
KiwiErr kiwi_solver_add_edit_var(KiwiSolverRef s, KiwiVarRef var, double strength);
KiwiErr kiwi_solver_remove_edit_var(KiwiSolverRef s, KiwiVarRef var);
int kiwi_solver_has_edit_var(KiwiSolverRef s, KiwiVarRef var);
KiwiErr kiwi_solver_suggest_value(KiwiSolverRef s, KiwiVarRef var, double value);
void kiwi_solver_update_vars(KiwiSolverRef s);
void kiwi_solver_reset(KiwiSolverRef s);
void kiwi_solver_dump(KiwiSolverRef s);
char* kiwi_solver_dumps(KiwiSolverRef s, void* (*alloc)(size_t));
KiwiSolverRef kiwi_solver_new();
void kiwi_solver_del(KiwiSolverRef s);
void free(void *); void free(void *);
]]) ]])
local strformat = string.format local strformat = string.format
local ffi_cast, ffi_copy, ffi_istype, ffi_new, ffi_sizeof, ffi_string = local ffi_cast, ffi_copy, ffi_gc, ffi_istype, ffi_new, ffi_string =
ffi.cast, ffi.copy, ffi.istype, ffi.new, ffi.sizeof, ffi.string ffi.cast, ffi.copy, ffi.gc, ffi.istype, ffi.new, ffi.string
local concat = table.concat local concat = table.concat
local has_table_new, new_tab = pcall(require, "table.new") local has_table_new, new_tab = pcall(require, "table.new")
@@ -121,13 +120,10 @@ end
---| '"KiwiErrBadRequiredStrength"' # The given strength is >= required. ---| '"KiwiErrBadRequiredStrength"' # The given strength is >= required.
---| '"KiwiErrInternalSolverError"' # An internal solver error occurred. ---| '"KiwiErrInternalSolverError"' # An internal solver error occurred.
---| '"KiwiErrAlloc"' # A memory allocation error occurred. ---| '"KiwiErrAlloc"' # A memory allocation error occurred.
---| '"KiwiErrNullObject"' # A method was invoked on a null or empty object.
---| '"KiwiErrUnknown"' # An unknown error occurred. ---| '"KiwiErrUnknown"' # An unknown error occurred.
kiwi.ErrKind = ffi.typeof("enum KiwiErrKind") --[[@as kiwi.ErrKind]] kiwi.ErrKind = ffi.typeof("enum KiwiErrKind") --[[@as kiwi.ErrKind]]
---@class kiwi.KiwiErr: ffi.ctype*
---@field package kind kiwi.ErrKind
---@field package message ffi.cdata*
---@alias kiwi.RelOp ---@alias kiwi.RelOp
---| '"LE"' # <= (less than or equal) ---| '"LE"' # <= (less than or equal)
---| '"GE"' # >= (greater than or equal) ---| '"GE"' # >= (greater than or equal)
@@ -156,18 +152,21 @@ function kiwi.Strength.create(a, b, c, w)
return clamp(a * w) * 1000000.0 + clamp(b * w) * 1000.0 + clamp(c * w) return clamp(a * w) * 1000000.0 + clamp(b * w) * 1000.0 + clamp(c * w)
end end
local Var = ffi.typeof("KiwiVarRef") --[[@as kiwi.Var]] local Var = ffi.typeof("struct KiwiVarRefType") --[[@as kiwi.Var]]
kiwi.Var = Var kiwi.Var = Var
local Term = ffi.typeof("KiwiTerm") --[[@as kiwi.Term]] local Term = ffi.typeof("struct KiwiTerm") --[[@as kiwi.Term]]
kiwi.Term = Term kiwi.Term = Term
local Expression = ffi.typeof("KiwiExpression") --[[@as kiwi.Expression]] local Expression = ffi.typeof("struct KiwiExpression") --[[@as kiwi.Expression]]
kiwi.Expression = Expression kiwi.Expression = Expression
local Constraint = ffi.typeof("KiwiConstraintRef") --[[@as kiwi.Constraint]] local Constraint = ffi.typeof("struct KiwiConstraintRefType") --[[@as kiwi.Constraint]]
kiwi.Constraint = Constraint kiwi.Constraint = Constraint
-- JIT compiler NYI: bad argument type if ffi.sizeof is used with a structure member
local SIZEOF_TERM = ffi.sizeof(Term)
--- Define a constraint with expressions as `a <= b`. --- Define a constraint with expressions as `a <= b`.
---@param a kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param a kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param b kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param b kiwi.Expression|kiwi.Term|kiwi.Var|number
@@ -199,8 +198,10 @@ end
---@param term kiwi.Term ---@param term kiwi.Term
---@nodiscard ---@nodiscard
local function add_expr_term(expr, term) local function add_expr_term(expr, term)
local ret = ffi_new(Expression, expr.term_count + 1, expr.constant, expr.term_count + 1) --[[@as kiwi.Expression]] local ret = ffi_new(Expression, expr.term_count + 1) --[[@as kiwi.Expression]]
ffi_copy(ret.terms_, expr.terms_, ffi_sizeof(expr.terms_, expr.term_count)) ---@diagnostic disable-line: param-type-mismatch ret.constant = expr.constant
ret.term_count = expr.term_count + 1
ffi_copy(ret.terms_, expr.terms_, SIZEOF_TERM * expr.term_count) ---@diagnostic disable-line: param-type-mismatch
ret.terms_[expr.term_count] = term ret.terms_[expr.term_count] = term
return ret return ret
end end
@@ -209,7 +210,9 @@ end
---@param term kiwi.Term ---@param term kiwi.Term
---@nodiscard ---@nodiscard
local function new_expr_one(constant, term) local function new_expr_one(constant, term)
local ret = ffi_new(Expression, 1, constant, 1) --[[@as kiwi.Expression]] local ret = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
ret.constant = constant
ret.term_count = 1
ret.terms_[0] = term ret.terms_[0] = term
return ret return ret
end end
@@ -219,7 +222,9 @@ end
---@param term2 kiwi.Term ---@param term2 kiwi.Term
---@nodiscard ---@nodiscard
local function new_expr_pair(constant, term1, term2) local function new_expr_pair(constant, term1, term2)
local ret = ffi_new(Expression, 2, constant, 2) --[[@as kiwi.Expression]] local ret = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
ret.constant = constant
ret.term_count = 2
ret.terms_[0] = term1 ret.terms_[0] = term1
ret.terms_[1] = term2 ret.terms_[1] = term2
return ret return ret
@@ -266,10 +271,9 @@ end
ffi.metatype(Var, { ffi.metatype(Var, {
__index = Var_cls, __index = Var_cls,
__gc = ckiwi.kiwi_var_del,
__new = function(_, name) __new = function(_, name)
return ckiwi.kiwi_var_new(name) return ffi_gc(ckiwi.kiwi_var_new(name), ckiwi.kiwi_var_del)
end, end,
__mul = function(a, b) __mul = function(a, b)
@@ -393,7 +397,8 @@ ffi.metatype(Term, {
end, end,
__tostring = function(term) __tostring = function(term)
return tostring(term.coefficient) .. " " .. term.var:name() return tostring(term.var:name())
--return tostring(term.coefficient) .. " " .. term.var:name()
end, end,
}) })
@@ -419,8 +424,8 @@ do
a.constant + b.constant, a.constant + b.constant,
a.term_count + b.term_count a.term_count + b.term_count
) --[[@as kiwi.Expression]] ) --[[@as kiwi.Expression]]
ffi_copy(ret.terms_, a.terms_, ffi_sizeof(a.terms_, a.term_count)) ---@diagnostic disable-line: param-type-mismatch ffi_copy(ret.terms_, a.terms_, SIZEOF_TERM * a.term_count) ---@diagnostic disable-line: param-type-mismatch
ffi_copy(ret.terms_[a.term_count], b.terms_, ffi_sizeof(b.terms_, b.term_count)) ---@diagnostic disable-line: param-type-mismatch ffi_copy(ret.terms_[a.term_count], b.terms_, SIZEOF_TERM * b.term_count) ---@diagnostic disable-line: param-type-mismatch
return ret return ret
end end
@@ -428,8 +433,11 @@ do
---@param constant number ---@param constant number
---@nodiscard ---@nodiscard
local function new_expr_constant(expr, constant) local function new_expr_constant(expr, constant)
local ret = ffi_new(Expression, expr.term_count, constant, expr.term_count) --[[@as kiwi.Expression]] local ret = ffi_new(Expression, expr.term_count) --[[@as kiwi.Expression]]
ffi_copy(ret.terms_, expr.terms_, ffi_sizeof(expr.terms_, expr.term_count)) ---@diagnostic disable-line: param-type-mismatch ret.constant = constant
ret.term_count = expr.term_count
ffi_copy(ret.terms_, expr.terms_, SIZEOF_TERM * expr.term_count) ---@diagnostic disable-line: param-type-mismatch
return ret return ret
end end
@@ -577,6 +585,7 @@ end
---@return kiwi.Constraint ---@return kiwi.Constraint
---@nodiscard ---@nodiscard
function kiwi.new_pair_ratio_constraint(left, coeff, right, constant, op, strength) function kiwi.new_pair_ratio_constraint(left, coeff, right, constant, op, strength)
assert(ffi_istype(Var, left) and ffi_istype(Var, right))
return Constraint( return Constraint(
new_expr_pair(-(constant or 0.0), Term(left), Term(right, -coeff)), new_expr_pair(-(constant or 0.0), Term(left), Term(right, -coeff)),
op, op,
@@ -594,6 +603,7 @@ end
---@return kiwi.Constraint ---@return kiwi.Constraint
---@nodiscard ---@nodiscard
function kiwi.new_pair_constraint(left, right, constant, op, strength) function kiwi.new_pair_constraint(left, right, constant, op, strength)
assert(ffi_istype(Var, left) and ffi_istype(Var, right))
return Constraint( return Constraint(
new_expr_pair(-(constant or 0.0), Term(left), Term(right, -1.0)), new_expr_pair(-(constant or 0.0), Term(left), Term(right, -1.0)),
op, op,
@@ -610,6 +620,7 @@ end
---@return kiwi.Constraint ---@return kiwi.Constraint
---@nodiscard ---@nodiscard
function kiwi.new_single_constraint(var, constant, op, strength) function kiwi.new_single_constraint(var, constant, op, strength)
assert(ffi_istype(Var, var))
return Constraint(new_expr_one(-(constant or 0.0), Term(var)), op, strength) return Constraint(new_expr_one(-(constant or 0.0), Term(var)), op, strength)
end end
@@ -618,10 +629,12 @@ local REQUIRED = Strength.REQUIRED
ffi.metatype(Constraint, { ffi.metatype(Constraint, {
__index = Constraint_cls, __index = Constraint_cls,
__gc = ckiwi.kiwi_constraint_del,
__new = function(_, expr, op, strength) __new = function(_, expr, op, strength)
return ckiwi.kiwi_constraint_new(expr, op or "EQ", strength or REQUIRED) return ffi_gc(
ckiwi.kiwi_constraint_new(expr, op or "EQ", strength or REQUIRED),
ckiwi.kiwi_constraint_del
)
end, end,
__tostring = function(self) __tostring = function(self)
@@ -670,19 +683,42 @@ local function new_error(kind, message, solver, item)
}, Error_mt) }, Error_mt)
end end
---@param f fun(solver: kiwi.Solver, item: any, ...): kiwi.KiwiErr local C = ffi.C
---@class kiwi.KiwiErr: ffi.ctype*
---@field package kind kiwi.ErrKind
---@field package message ffi.cdata*
---@field package must_free boolean
---@overload fun(): kiwi.KiwiErr
local KiwiErr = ffi.typeof("struct KiwiErr") --[[@as kiwi.KiwiErr]]
--
---@param f fun(solver: kiwi.Solver, item: any, ...): kiwi.KiwiErr?
---@param solver kiwi.Solver ---@param solver kiwi.Solver
---@param item any ---@param item any
local function try_solver(f, solver, item, ...) local function try_solver(f, solver, item, ...)
local err = f(solver, item, ...) local err = f(solver, item, ...)
if err.kind ~= 0 then if err ~= nil then
error(new_error(err.kind, ffi_string(err.message), solver, item)) local kind = err.kind
local message = err.message ~= nil and ffi_string(err.message) or ""
if err.must_free then
print("FEEE")
C.free(err)
end
error(new_error(kind, message, solver, item))
end end
end end
---@class kiwi.Solver: ffi.ctype* ---@class kiwi.Solver: ffi.ctype*
---@overload fun(): kiwi.Solver ---@overload fun(): kiwi.Solver
local Solver_cls = { local Solver_cls = {
--- Test whether a constraint is in the solver.
---@type fun(self: kiwi.Solver, constraint: kiwi.Constraint): boolean
has_constraint = ckiwi.kiwi_solver_has_constraint,
--- Test whether an edit variable has been added to the solver.
---@type fun(self: kiwi.Solver, var: kiwi.Var): boolean
has_edit_var = ckiwi.kiwi_solver_has_edit_var,
--- Update the values of the external solver variables. --- Update the values of the external solver variables.
---@type fun(self: kiwi.Solver) ---@type fun(self: kiwi.Solver)
update_vars = ckiwi.kiwi_solver_update_vars, update_vars = ckiwi.kiwi_solver_update_vars,
@@ -719,14 +755,6 @@ function Solver_cls:remove_constraint(constraint)
try_solver(ckiwi.kiwi_solver_remove_constraint, self, constraint) try_solver(ckiwi.kiwi_solver_remove_constraint, self, constraint)
end end
--- Test whether a constraint is in the solver.
---@param constraint kiwi.Constraint
---@return boolean
---@nodiscard
function Solver_cls:has_constraint(constraint)
return ckiwi.kiwi_solver_has_constraint(self, constraint) ~= 0
end
--- Adds an edit variable to the solver. --- Adds an edit variable to the solver.
--- ---
--- This method should be called before the `suggestValue` method is --- This method should be called before the `suggestValue` method is
@@ -748,14 +776,6 @@ function Solver_cls:remove_edit_var(var)
try_solver(ckiwi.kiwi_solver_remove_edit_var, self, var) try_solver(ckiwi.kiwi_solver_remove_edit_var, self, var)
end end
--- Test whether an edit variable has been added to the solver.
---@param var kiwi.Var the edit variable to check
---@return boolean
---@nodiscard
function Solver_cls:has_edit_var(var)
return ckiwi.kiwi_solver_has_edit_var(self, var) ~= 0
end
--- Suggest a value for the given edit variable. --- Suggest a value for the given edit variable.
--- This method should be used after an edit variable has been added to the solver in order --- This method should be used after an edit variable has been added to the solver in order
--- to suggest the value for that variable. After all suggestions have been made, --- to suggest the value for that variable. After all suggestions have been made,
@@ -772,18 +792,17 @@ end
---@return string ---@return string
---@nodiscard ---@nodiscard
function Solver_cls:dumps() function Solver_cls:dumps()
local cs = ckiwi.kiwi_solver_dumps(self, nil) local cs = ckiwi.kiwi_solver_dumps(self)
local s = ffi_string(cs) local s = ffi_string(cs)
ffi.C.free(cs) C.free(cs)
return s return s
end end
kiwi.Solver = ffi.metatype("KiwiSolverRef", { kiwi.Solver = ffi.metatype("struct KiwiSolver", {
__index = Solver_cls, __index = Solver_cls,
__new = function(_) __new = function(_)
return ckiwi.kiwi_solver_new() return ffi_gc(ckiwi.kiwi_solver_new(), ckiwi.kiwi_solver_del)
end, end,
__gc = ckiwi.kiwi_solver_del,
__tostring = function(self) __tostring = function(self)
return strformat("kiwi.Solver(0x%X)", ffi_cast("intptr_t", ffi_cast("void*", self))) return strformat("kiwi.Solver(0x%X)", ffi_cast("intptr_t", ffi_cast("void*", self)))

View File

@@ -29,5 +29,6 @@ build = {
install_variables = { install_variables = {
INST_LIBDIR = "$(LIBDIR)", INST_LIBDIR = "$(LIBDIR)",
INST_LUADIR = "$(LUADIR)", INST_LUADIR = "$(LUADIR)",
LIB_EXT = "$(LIB_EXTENSION)",
}, },
} }