diff --git a/.luarc.json b/.luarc.json index 679e34f..73dc269 100644 --- a/.luarc.json +++ b/.luarc.json @@ -8,5 +8,6 @@ "lua_modules/share/lua/5.1/?.lua", "lua_modules/share/lua/5.1/?/init.lua" ], + "workspace.library": ["${3rd}/busted/library", "${3rd}/luassert/library"], "workspace.checkThirdParty": false } diff --git a/Makefile b/Makefile index 00ae1f3..87236e3 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,29 @@ -SRCDIR ?= . -CC ?= $(CROSS)gcc -CFLAGS ?= -fPIC -O2 +SRCDIR := . +CC := $(CROSS)gcc +CFLAGS := -fPIC -O2 CFLAGS += -Wall -I$(SRCDIR)/kiwi -LIBFLAG ?= -shared -LIB_EXT ?= so +LIBFLAG := -shared +LIB_EXT := so ifeq ($(findstring gcc, $(CC)), gcc) CXX := $(subst gcc, g++, $(CC)) - CXXFLAGS += -std=c++11 + CXXFLAGS += -std=c++14 + ifneq ($(SANITIZE),) + CFLAGS += -fsanitize=undefined -fsanitize=address + endif else ifeq ($(CC), clang) CXX := clang++ - CXXFLAGS += -std=c++11 + CXXFLAGS += -std=c++14 + ifneq ($(SANITIZE),) + CFLAGS += -fsanitize=undefined -fsanitize=address + endif else CXX := $(CC) endif endif + all: ckiwi.$(LIB_EXT) install: @@ -26,7 +33,7 @@ install: clean: 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 $@ $< .PHONY: all install clean diff --git a/README.md b/README.md index 890630e..b6a9217 100644 --- a/README.md +++ b/README.md @@ -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. 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. diff --git a/ckiwi/.clang-format b/ckiwi/.clang-format index 5357c2c..f9132f8 100644 --- a/ckiwi/.clang-format +++ b/ckiwi/.clang-format @@ -27,7 +27,7 @@ BreakBeforeTernaryOperators: true BreakConstructorInitializers: AfterColon BreakInheritanceList: AfterColon BreakStringLiterals: false -ColumnLimit: 90 +ColumnLimit: 98 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 diff --git a/ckiwi/ckiwi.cpp b/ckiwi/ckiwi.cpp index 4a6304e..9a8c989 100644 --- a/ckiwi/ckiwi.cpp +++ b/ckiwi/ckiwi.cpp @@ -2,104 +2,206 @@ #include +#include #include using namespace kiwi; namespace { -template -class alignas(T) SharedRef { + +template +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)...); + return cref; +} + +template +inline decltype(auto) to_var_cref(Args&&... args) { + return to_cref(std::forward(args)...); +} + +template +inline decltype(auto) to_constraint_cref(Args&&... args) { + return to_cref(std::forward(args)...); +} + +template +class SharedRef { private: - CS ref_; + R& cref_; public: - T* operator&() { - return reinterpret_cast(&ref_); + explicit SharedRef(R& cref) : cref_(cref) {} + + static_assert( + sizeof(R) >= sizeof(T), //NOLINT(bugprone-sizeof-expression) + "SharedRef 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->() { - return reinterpret_cast(&ref_); + return ptr(); } - operator T&() { - return *reinterpret_cast(&ref_); + const T* operator->() const { + return const_ptr(); } - operator CS() const { - return ref_; + operator const T&() const { + return cref(); } - T& instance() { - return *reinterpret_cast(&ref_); + explicit operator bool() const { + return cref_; } - - void destroy() { - instance().~T(); - ref_ = {0}; - } - - SharedRef(CS ref) : ref_(ref) {} - - template - SharedRef(Args&&... args) { - new (&ref_) T(std::forward(args)...); - } - - static_assert(sizeof(CS) >= sizeof(T), "SharedRef cannot wrap T (size)"); }; using ConstraintRef = SharedRef; using VariableRef = SharedRef; +using ConstVariableRef = const SharedRef; -KiwiErr make_error(KiwiErrKind kind, const std::exception& ex) { - constexpr auto max_n = sizeof(KiwiErr::message) - 1; - const auto n = std::min(std::strlen(ex.what()), max_n); +KiwiErrPtr new_error(KiwiErrPtr base, const std::exception& ex) { + if (!std::strcmp(ex.what(), base->message)) + return base; - KiwiErr err {kind}; + const auto msg_n = std::strlen(ex.what()) + 1; - std::memcpy(err.message, ex.what(), n); - if (n == max_n) - err.message[max_n] = 0; + auto* mem = static_cast(std::malloc(sizeof(KiwiErr) + msg_n)); + if (!mem) { + return base; + } + + const auto* err = new (mem) KiwiErr {base->kind, mem + sizeof(KiwiErr), true}; + std::memcpy(const_cast(err->message), ex.what(), msg_n); return err; } -KiwiErr make_error(KiwiErrKind kind) { - return KiwiErr {kind, {0}}; -} +static const constexpr KiwiErr kKiwiErrUnhandledCxxException { + 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 -inline KiwiErr wrap_err(F&& f) { +inline KiwiErrPtr wrap_err(F&& f) { try { f(); - } catch (const UnsatisfiableConstraint& err) { - return make_error(KiwiErrUnsatisfiableConstraint, err); - } catch (const UnknownConstraint& err) { - return make_error(KiwiErrUnknownConstraint, err); - } catch (const DuplicateConstraint& err) { - return make_error(KiwiErrDuplicateConstraint, err); - } catch (const UnknownEditVariable& err) { - return make_error(KiwiErrUnknownEditVariable, err); - } catch (const DuplicateEditVariable& err) { - return make_error(KiwiErrDuplicateEditVariable, err); - } catch (const BadRequiredStrength& err) { - return make_error(KiwiErrBadRequiredStrength, err); - } catch (const InternalSolverError& err) { - return make_error(KiwiErrInternalSolverError, err); + } catch (const UnsatisfiableConstraint& ex) { + static const constexpr KiwiErr err { + KiwiErrUnsatisfiableConstraint, + "The constraint cannot be satisfied."}; + return &err; + } catch (const UnknownConstraint& ex) { + static const constexpr KiwiErr err { + KiwiErrUnknownConstraint, + "The constraint has not been added to the solver."}; + return &err; + + } catch (const DuplicateConstraint& ex) { + static const constexpr KiwiErr 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&) { - return make_error(KiwiErrAlloc); - } catch (const std::exception& err) { - return make_error(KiwiErrUnknown, err); + static const constexpr KiwiErr err {KiwiErrAlloc, "A memory allocation failed."}; + return &err; + } catch (const std::exception& ex) { + return new_error(&kKiwiErrUnhandledCxxException, ex); } catch (...) { - return make_error(KiwiErrUnknown); + return &kKiwiErrUnhandledCxxException; } - return make_error(KiwiErrNone); + return nullptr; } + +template +inline KiwiErrPtr wrap_err(P ptr, F&& f) { + if (!ptr) { + return &kKiwiErrNullObjectArg0; + } + return wrap_err([&]() { f(ptr); }); +} + +template +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 extern "C" { KiwiVarRef kiwi_var_new(const char* name) { - return VariableRef(name); + return to_var_cref(name ? name : ""); } void kiwi_var_del(KiwiVarRef var) { @@ -107,42 +209,50 @@ void kiwi_var_del(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() : "()"; } 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) { - return VariableRef(var)->value(); + const VariableRef self(var); + return self ? self->value() : std::numeric_limits::quiet_NaN(); } 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) { - 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( - const KiwiExpression* expression, - enum KiwiRelOp op, - double strength -) { +KiwiConstraintRef +kiwi_constraint_new(KiwiExpressionConstPtr expression, enum KiwiRelOp op, double strength) { if (strength < 0.0) { strength = kiwi::strength::required; } std::vector terms; - terms.reserve(expression->term_count); + if (expression) { + terms.reserve(expression->term_count); - for (auto* t = expression->terms; t != expression->terms + expression->term_count; - ++t) { - terms.emplace_back(VariableRef(t->var), t->coefficient); + for (auto* t = expression->terms; t != expression->terms + expression->term_count; ++t) { + ConstVariableRef var(t->var); + if (var) + terms.emplace_back(var.cref(), t->coefficient); + } } - - return ConstraintRef( + return to_constraint_cref( Expression(std::move(terms), expression->constant), static_cast(op), strength @@ -154,23 +264,25 @@ void kiwi_constraint_del(KiwiConstraintRef constraint) { } double kiwi_constraint_strength(KiwiConstraintRef constraint) { - return ConstraintRef(constraint)->strength(); + const ConstraintRef self(constraint); + return self ? self->strength() : std::numeric_limits::quiet_NaN(); } enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint) { - return static_cast(ConstraintRef(constraint)->op()); + const ConstraintRef self(constraint); + return self ? static_cast(self->op()) : KiwiRelOp::KIWI_OP_EQ; } -int kiwi_constraint_violated(KiwiConstraintRef constraint) { - return ConstraintRef(constraint)->violated(); +bool kiwi_constraint_violated(KiwiConstraintRef constraint) { + const ConstraintRef self(constraint); + return self ? self->violated() : 0; } -int kiwi_constraint_expression( - KiwiConstraintRef constraint, - KiwiExpression* out, - int out_size -) { - const auto& expr = ConstraintRef(constraint).instance().expression(); +int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpression* out, int out_size) { + const ConstraintRef self(constraint); + if (!self) + return 0; + const auto& expr = self->expression(); const auto& terms = expr.terms(); const int n_terms = terms.size(); if (!out || out_size < n_terms) @@ -178,7 +290,7 @@ int kiwi_constraint_expression( auto* p = out->terms; for (const auto& t : terms) { - *p = KiwiTerm {VariableRef(t.variable()), t.coefficient()}; + *p = KiwiTerm {to_var_cref(t.variable()), t.coefficient()}; ++p; } out->term_count = p - out->terms; @@ -187,71 +299,100 @@ int kiwi_constraint_expression( return n_terms; } -KiwiSolverRef kiwi_solver_new() { - return KiwiSolverRef {new (std::nothrow) Solver()}; +KiwiSolverPtr kiwi_solver_new() { + return reinterpret_cast(new (std::nothrow) Solver()); } -void kiwi_solver_del(KiwiSolverRef s) { - delete reinterpret_cast(s.impl_); +void kiwi_solver_del(KiwiSolverPtr sp) { + auto* solver = reinterpret_cast(sp); + if (solver) + delete solver; } -KiwiErr kiwi_solver_add_constraint(KiwiSolverRef s, KiwiConstraintRef constraint) { - return wrap_err([=]() { - reinterpret_cast(s.impl_)->addConstraint(ConstraintRef(constraint)); +KiwiErrPtr kiwi_solver_add_constraint(KiwiSolverPtr s, KiwiConstraintRef constraint) { + return wrap_err( + reinterpret_cast(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(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(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(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(s), VariableRef(var), [](auto solver, const auto v) { + solver->removeEditVariable(v); }); } -KiwiErr kiwi_solver_remove_constraint(KiwiSolverRef s, KiwiConstraintRef constraint) { - return wrap_err([=]() { - reinterpret_cast(s.impl_)->removeConstraint(ConstraintRef(constraint)); - }); +bool kiwi_solver_has_edit_var(KiwiSolverPtr s, KiwiVarRef var) { + const auto* solver = reinterpret_cast(s); + VariableRef v(var); + if (!solver || !v) + return 0; + return solver->hasEditVariable(v); } -int kiwi_solver_has_constraint(KiwiSolverRef s, KiwiConstraintRef constraint) { - return reinterpret_cast(s.impl_)->hasConstraint(ConstraintRef(constraint)); +KiwiErrPtr kiwi_solver_suggest_value(KiwiSolverPtr s, KiwiVarRef var, double value) { + return wrap_err( + reinterpret_cast(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) { - return wrap_err([=]() { - reinterpret_cast(s.impl_)->addEditVariable(VariableRef(var), strength); - }); +void kiwi_solver_update_vars(KiwiSolverPtr s) { + auto* solver = reinterpret_cast(s); + if (solver) + solver->updateVariables(); } -KiwiErr kiwi_solver_remove_edit_var(KiwiSolverRef s, KiwiVarRef var) { - return wrap_err([=]() { - reinterpret_cast(s.impl_)->removeEditVariable(VariableRef(var)); - }); +void kiwi_solver_reset(KiwiSolverPtr s) { + auto* solver = reinterpret_cast(s); + if (solver) + solver->reset(); } -int kiwi_solver_has_edit_var(KiwiSolverRef s, KiwiVarRef var) { - return reinterpret_cast(s.impl_)->hasEditVariable(VariableRef(var)); +void kiwi_solver_dump(KiwiSolverPtr s) { + auto* solver = reinterpret_cast(s); + if (solver) + solver->dump(); } -KiwiErr kiwi_solver_suggest_value(KiwiSolverRef s, KiwiVarRef var, double value) { - return wrap_err([=]() { - reinterpret_cast(s.impl_)->suggestValue(VariableRef(var), value); - }); -} +char* kiwi_solver_dumps(KiwiSolverPtr s) { + auto* solver = reinterpret_cast(s); + if (!solver) + return nullptr; -void kiwi_solver_update_vars(KiwiSolverRef s) { - reinterpret_cast(s.impl_)->updateVariables(); -} - -void kiwi_solver_reset(KiwiSolverRef s) { - reinterpret_cast(s.impl_)->reset(); -} - -void kiwi_solver_dump(KiwiSolverRef s) { - reinterpret_cast(s.impl_)->dump(); -} - -char* kiwi_solver_dumps(KiwiSolverRef s, void* (*alloc)(size_t)) { - const auto val = reinterpret_cast(s.impl_)->dumps(); - const auto buf_size = val.size() + 1; - auto* buf = static_cast(alloc ? alloc(buf_size) : malloc(buf_size)); + const auto str = solver->dumps(); + const auto buf_size = str.size() + 1; + auto* buf = static_cast(std::malloc(buf_size)); if (!buf) return nullptr; - std::memcpy(buf, val.c_str(), val.size() + 1); + std::memcpy(buf, str.c_str(), str.size() + 1); return buf; } diff --git a/ckiwi/ckiwi.h b/ckiwi/ckiwi.h index d7f1c31..8897374 100644 --- a/ckiwi/ckiwi.h +++ b/ckiwi/ckiwi.h @@ -20,38 +20,39 @@ enum KiwiErrKind { KiwiErrBadRequiredStrength, KiwiErrInternalSolverError, KiwiErrAlloc, + KiwiErrNullObject, KiwiErrUnknown, }; enum KiwiRelOp { KIWI_OP_LE, KIWI_OP_GE, KIWI_OP_EQ }; -typedef struct { - void* private_; -} KiwiVarRef; +struct KiwiVarRefType; +struct KiwiConstraintRefType; -typedef struct { +typedef struct KiwiVarRefType* KiwiVarRef; +typedef struct KiwiConstraintRefType* KiwiConstraintRef; + +struct KiwiTerm { KiwiVarRef var; double coefficient; -} KiwiTerm; +}; -typedef struct { +typedef struct KiwiExpression { double constant; int term_count; - KiwiTerm terms[1]; -} KiwiExpression; + struct KiwiTerm terms[1]; // LuaJIT: struct KiwiTerm terms_[?]; +}* KiwiExpressionPtr; -typedef struct { - void* private_; -} KiwiConstraintRef; +typedef const struct KiwiExpression* KiwiExpressionConstPtr; -typedef struct { +typedef struct KiwiErr { enum KiwiErrKind kind; - char message[64]; -} KiwiErr; + const char* message; + bool must_free; +} const* KiwiErrPtr; -typedef struct { - void* impl_; -} KiwiSolverRef; +struct KiwiSolver; +typedef struct KiwiSolver* KiwiSolverPtr; KiwiVarRef kiwi_var_new(const char* name); 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); 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); double kiwi_constraint_strength(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( - KiwiConstraintRef constraint, - KiwiExpression* out, - int out_size -); +KiwiErrPtr kiwi_solver_add_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint); +KiwiErrPtr kiwi_solver_remove_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint); +bool kiwi_solver_has_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint); +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); - -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); +KiwiSolverPtr kiwi_solver_new(); +void kiwi_solver_del(KiwiSolverPtr sp); // LuaJIT end #ifdef __cplusplus diff --git a/kiwi.lua b/kiwi.lua index 31abd4e..3cc9322 100644 --- a/kiwi.lua +++ b/kiwi.lua @@ -22,86 +22,85 @@ enum KiwiErrKind { KiwiErrBadRequiredStrength, KiwiErrInternalSolverError, KiwiErrAlloc, + KiwiErrNullObject, KiwiErrUnknown, }; enum KiwiRelOp { LE, GE, EQ }; -typedef struct { - void* private_; -} KiwiVarRef; +struct KiwiVarRefType; +struct KiwiConstraintRefType; -typedef struct { +typedef struct KiwiVarRefType* KiwiVarRef; +typedef struct KiwiConstraintRefType* KiwiConstraintRef; + +struct KiwiTerm { KiwiVarRef var; double coefficient; -} KiwiTerm; +}; -typedef struct { +typedef struct KiwiExpression { double constant; int term_count; - KiwiTerm terms_[?]; -} KiwiExpression; + struct KiwiTerm terms_[?]; +}* KiwiExpressionPtr; -typedef struct { - void* private_; -} KiwiConstraintRef; +typedef const struct KiwiExpression* KiwiExpressionConstPtr; -typedef struct { +typedef struct KiwiErr { enum KiwiErrKind kind; - char message[64]; -} KiwiErr; + const char* message; + bool must_free; +} const* KiwiErrPtr; -typedef struct { - void* impl_; -} KiwiSolverRef; +struct KiwiSolver; +typedef struct KiwiSolver* KiwiSolverPtr; KiwiVarRef kiwi_var_new(const char* name); void kiwi_var_del(KiwiVarRef var); const char* kiwi_var_name(KiwiVarRef var); + void kiwi_var_set_name(KiwiVarRef var, const char* name); + double kiwi_var_value(KiwiVarRef var); + void kiwi_var_set_value(KiwiVarRef var, double value); + int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other); 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); double kiwi_constraint_strength(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( - KiwiConstraintRef constraint, - KiwiExpression* out, - int out_size -); +KiwiErrPtr kiwi_solver_add_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint); +KiwiErrPtr kiwi_solver_remove_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint); +bool kiwi_solver_has_constraint(KiwiSolverPtr sp, KiwiConstraintRef constraint); +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); -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); +KiwiSolverPtr kiwi_solver_new(); +void kiwi_solver_del(KiwiSolverPtr sp); void free(void *); ]]) local strformat = string.format -local ffi_cast, ffi_copy, ffi_istype, ffi_new, ffi_sizeof, ffi_string = - 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.gc, ffi.istype, ffi.new, ffi.string local concat = table.concat local has_table_new, new_tab = pcall(require, "table.new") @@ -121,13 +120,10 @@ end ---| '"KiwiErrBadRequiredStrength"' # The given strength is >= required. ---| '"KiwiErrInternalSolverError"' # An internal solver error occurred. ---| '"KiwiErrAlloc"' # A memory allocation error occurred. +---| '"KiwiErrNullObject"' # A method was invoked on a null or empty object. ---| '"KiwiErrUnknown"' # An unknown error occurred. 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 ---| '"LE"' # <= (less 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) end -local Var = ffi.typeof("KiwiVarRef") --[[@as kiwi.Var]] +local Var = ffi.typeof("struct KiwiVarRefType") --[[@as kiwi.Var]] kiwi.Var = Var -local Term = ffi.typeof("KiwiTerm") --[[@as kiwi.Term]] +local Term = ffi.typeof("struct KiwiTerm") --[[@as kiwi.Term]] kiwi.Term = Term -local Expression = ffi.typeof("KiwiExpression") --[[@as kiwi.Expression]] +local Expression = ffi.typeof("struct KiwiExpression") --[[@as kiwi.Expression]] kiwi.Expression = Expression -local Constraint = ffi.typeof("KiwiConstraintRef") --[[@as kiwi.Constraint]] +local Constraint = ffi.typeof("struct KiwiConstraintRefType") --[[@as kiwi.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`. ---@param a 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 ---@nodiscard 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]] - ffi_copy(ret.terms_, expr.terms_, ffi_sizeof(expr.terms_, expr.term_count)) ---@diagnostic disable-line: param-type-mismatch + local ret = ffi_new(Expression, expr.term_count + 1) --[[@as kiwi.Expression]] + 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 return ret end @@ -209,7 +210,9 @@ end ---@param term kiwi.Term ---@nodiscard 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 return ret end @@ -219,7 +222,9 @@ end ---@param term2 kiwi.Term ---@nodiscard 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_[1] = term2 return ret @@ -266,10 +271,9 @@ end ffi.metatype(Var, { __index = Var_cls, - __gc = ckiwi.kiwi_var_del, __new = function(_, name) - return ckiwi.kiwi_var_new(name) + return ffi_gc(ckiwi.kiwi_var_new(name), ckiwi.kiwi_var_del) end, __mul = function(a, b) @@ -393,7 +397,8 @@ ffi.metatype(Term, { end, __tostring = function(term) - return tostring(term.coefficient) .. " " .. term.var:name() + return tostring(term.var:name()) + --return tostring(term.coefficient) .. " " .. term.var:name() end, }) @@ -419,8 +424,8 @@ do a.constant + b.constant, a.term_count + b.term_count ) --[[@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.term_count], b.terms_, ffi_sizeof(b.terms_, b.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_, SIZEOF_TERM * b.term_count) ---@diagnostic disable-line: param-type-mismatch return ret end @@ -428,8 +433,11 @@ do ---@param constant number ---@nodiscard local function new_expr_constant(expr, constant) - local ret = ffi_new(Expression, expr.term_count, constant, 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 + local ret = ffi_new(Expression, expr.term_count) --[[@as kiwi.Expression]] + 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 end @@ -577,6 +585,7 @@ end ---@return kiwi.Constraint ---@nodiscard function kiwi.new_pair_ratio_constraint(left, coeff, right, constant, op, strength) + assert(ffi_istype(Var, left) and ffi_istype(Var, right)) return Constraint( new_expr_pair(-(constant or 0.0), Term(left), Term(right, -coeff)), op, @@ -594,6 +603,7 @@ end ---@return kiwi.Constraint ---@nodiscard function kiwi.new_pair_constraint(left, right, constant, op, strength) + assert(ffi_istype(Var, left) and ffi_istype(Var, right)) return Constraint( new_expr_pair(-(constant or 0.0), Term(left), Term(right, -1.0)), op, @@ -610,6 +620,7 @@ end ---@return kiwi.Constraint ---@nodiscard 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) end @@ -618,10 +629,12 @@ local REQUIRED = Strength.REQUIRED ffi.metatype(Constraint, { __index = Constraint_cls, - __gc = ckiwi.kiwi_constraint_del, __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, __tostring = function(self) @@ -670,19 +683,42 @@ local function new_error(kind, message, solver, item) }, Error_mt) 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 item any local function try_solver(f, solver, item, ...) local err = f(solver, item, ...) - if err.kind ~= 0 then - error(new_error(err.kind, ffi_string(err.message), solver, item)) + if err ~= nil then + 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 ---@class kiwi.Solver: ffi.ctype* ---@overload fun(): kiwi.Solver 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. ---@type fun(self: kiwi.Solver) 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) 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. --- --- 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) 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. --- 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, @@ -772,18 +792,17 @@ end ---@return string ---@nodiscard function Solver_cls:dumps() - local cs = ckiwi.kiwi_solver_dumps(self, nil) + local cs = ckiwi.kiwi_solver_dumps(self) local s = ffi_string(cs) - ffi.C.free(cs) + C.free(cs) return s end -kiwi.Solver = ffi.metatype("KiwiSolverRef", { +kiwi.Solver = ffi.metatype("struct KiwiSolver", { __index = Solver_cls, __new = function(_) - return ckiwi.kiwi_solver_new() + return ffi_gc(ckiwi.kiwi_solver_new(), ckiwi.kiwi_solver_del) end, - __gc = ckiwi.kiwi_solver_del, __tostring = function(self) return strformat("kiwi.Solver(0x%X)", ffi_cast("intptr_t", ffi_cast("void*", self))) diff --git a/ljkiwi-scm-1.rockspec b/ljkiwi-scm-1.rockspec index f7547e7..afe04d3 100644 --- a/ljkiwi-scm-1.rockspec +++ b/ljkiwi-scm-1.rockspec @@ -29,5 +29,6 @@ build = { install_variables = { INST_LIBDIR = "$(LIBDIR)", INST_LUADIR = "$(LUADIR)", + LIB_EXT = "$(LIB_EXTENSION)", }, }