diff --git a/ckiwi/ckiwi.cpp b/ckiwi/ckiwi.cpp index 820d41b..4a6c10b 100644 --- a/ckiwi/ckiwi.cpp +++ b/ckiwi/ckiwi.cpp @@ -343,95 +343,84 @@ int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpression* out return n_terms; } -KiwiSolver* kiwi_solver_new() { - return reinterpret_cast(new Solver()); +struct KiwiSolver { + unsigned error_mask; + Solver solver; +}; + +KiwiSolver* kiwi_solver_new(unsigned error_mask) { + return new KiwiSolver {error_mask}; } -void kiwi_solver_del(KiwiSolver* sp) { - auto* solver = reinterpret_cast(sp); - if (solver) - delete solver; +void kiwi_solver_del(KiwiSolver* s) { + if (s) + delete s; } const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, KiwiConstraintRef constraint) { - return wrap_err( - reinterpret_cast(s), - ConstraintRef(constraint), - [](auto solver, const auto c) { solver->addConstraint(c); } - ); + return wrap_err(s, ConstraintRef(constraint), [](auto* s, const auto c) { + s->solver.addConstraint(c); + }); } const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, KiwiConstraintRef constraint) { - return wrap_err( - reinterpret_cast(s), - ConstraintRef(constraint), - [](auto solver, const auto c) { solver->removeConstraint(c); } - ); + return wrap_err(s, ConstraintRef(constraint), [](auto* s, const auto c) { + s->solver.removeConstraint(c); + }); } bool kiwi_solver_has_constraint(const KiwiSolver* s, KiwiConstraintRef constraint) { - const auto* solver = reinterpret_cast(s); ConstraintRef c(constraint); - if (!solver || !c) + if (!s || !c) return 0; - return solver->hasConstraint(c); + return s->solver.hasConstraint(c); } const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, KiwiVarRef var, double strength) { - return wrap_err( - reinterpret_cast(s), - VariableRef(var), - [strength](auto solver, const auto v) { solver->addEditVariable(v, strength); } - ); + return wrap_err(s, VariableRef(var), [strength](auto* s, const auto v) { + s->solver.addEditVariable(v, strength); + }); } const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, KiwiVarRef var) { - return wrap_err(reinterpret_cast(s), VariableRef(var), [](auto solver, const auto v) { - solver->removeEditVariable(v); + return wrap_err(s, VariableRef(var), [](auto* s, const auto v) { + s->solver.removeEditVariable(v); }); } bool kiwi_solver_has_edit_var(const KiwiSolver* s, KiwiVarRef var) { - const auto* solver = reinterpret_cast(s); VariableRef v(var); - if (!solver || !v) + if (!s || !v) return 0; - return solver->hasEditVariable(v); + return s->solver.hasEditVariable(v); } const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, KiwiVarRef var, double value) { - return wrap_err( - reinterpret_cast(s), - VariableRef(var), - - [value](auto solver, const auto v) { solver->suggestValue(v, value); } - ); + return wrap_err(s, VariableRef(var), [value](auto* s, const auto v) { + s->solver.suggestValue(v, value); + }); } void kiwi_solver_update_vars(KiwiSolver* s) { - auto* solver = reinterpret_cast(s); - if (solver) - solver->updateVariables(); + if (s) + s->solver.updateVariables(); } void kiwi_solver_reset(KiwiSolver* s) { - auto* solver = reinterpret_cast(s); - if (solver) - solver->reset(); + if (s) + s->solver.reset(); } void kiwi_solver_dump(const KiwiSolver* s) { - auto* solver = reinterpret_cast(s); - if (solver) - solver->dump(); + if (s) + s->solver.dump(); } char* kiwi_solver_dumps(const KiwiSolver* s) { - auto* solver = reinterpret_cast(s); - if (!solver) + if (!s) return nullptr; - const auto str = solver->dumps(); // upstream library defect + const auto str = s->solver.dumps(); // upstream library defect const auto buf_size = str.size() + 1; auto* buf = static_cast(std::malloc(buf_size)); if (!buf) diff --git a/ckiwi/ckiwi.h b/ckiwi/ckiwi.h index 28526bc..8232b0b 100644 --- a/ckiwi/ckiwi.h +++ b/ckiwi/ckiwi.h @@ -46,8 +46,8 @@ typedef struct KiwiErr { bool must_free; } KiwiErr; -typedef struct KiwiSolver KiwiSolver; - +typedef struct KiwiSolver + KiwiSolver; // LuaJIT: typedef struct { unsigned error_mask; } KiwiSolver; KiwiVarRef kiwi_var_new(const char* name); void kiwi_var_del(KiwiVarRef var); KiwiVarRef kiwi_var_clone(KiwiVarRef var); @@ -74,8 +74,8 @@ enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint); bool kiwi_constraint_violated(KiwiConstraintRef constraint); int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpression* out, int out_size); -KiwiSolver* kiwi_solver_new(); -void kiwi_solver_del(KiwiSolver* sp); +KiwiSolver* kiwi_solver_new(unsigned error_mask); +void kiwi_solver_del(KiwiSolver* s); const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* sp, KiwiConstraintRef constraint); const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* sp, KiwiConstraintRef constraint); diff --git a/kiwi.lua b/kiwi.lua index 39e768c..3b75145 100644 --- a/kiwi.lua +++ b/kiwi.lua @@ -48,7 +48,7 @@ typedef struct KiwiErr { bool must_free; } KiwiErr; -typedef struct KiwiSolver KiwiSolver; +typedef struct KiwiSolver { unsigned error_mask; } KiwiSolver; KiwiVarRef kiwi_var_new(const char* name); void kiwi_var_del(KiwiVarRef var); @@ -76,8 +76,8 @@ enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint); bool kiwi_constraint_violated(KiwiConstraintRef constraint); int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpression* out, int out_size); -KiwiSolver* kiwi_solver_new(); -void kiwi_solver_del(KiwiSolver* sp); +KiwiSolver* kiwi_solver_new(unsigned error_mask); +void kiwi_solver_del(KiwiSolver* s); const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* sp, KiwiConstraintRef constraint); const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* sp, KiwiConstraintRef constraint); @@ -720,6 +720,27 @@ do return ffi_gc(expr, ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] end + --- Add the constraint to the solver. + --- Raises: + --- KiwiErrDuplicateConstraint: The given constraint has already been added to the solver. + --- KiwiErrUnsatisfiableConstraint: The given constraint is required and cannot be satisfied. + ---@param solver kiwi.Solver + ---@return kiwi.Constraint + function Constraint_cls:add_to(solver) + solver:add_constraints(self) + return self + end + + --- Remove the constraint from the solver. + --- Raises: + --- KiwiErrUnknownConstraint: The given constraint has not been added to the solver. + ---@param solver kiwi.Solver + ---@return kiwi.Constraint + function Constraint_cls:remove_from(solver) + solver:remove_constraints(self) + return self + end + local Constraint_mt = { __index = Constraint_cls, } @@ -818,8 +839,33 @@ do end do + local bit = require("bit") + local band, bor, lshift = bit.band, bit.bor, bit.lshift local C = ffi.C + --- Produce a custom error raise mask + --- Error kinds specified in the mask will not cause a lua + --- error to be raised. + ---@param kinds (kiwi.ErrKind|number)[] + ---@param invert boolean? + ---@return integer + function kiwi.error_mask(kinds, invert) + local mask = 0 + for _, k in ipairs(kinds) do + mask = bor(mask, lshift(1, kiwi.ErrKind(k))) + end + return invert and bit.bnot(mask) or mask + end + + kiwi.ERROR_MASK_ALL = 0xFFFF + --- an error mask that raises errors only for fatal conditions + kiwi.ERROR_MASK_NON_FATAL = bit.bnot(kiwi.error_mask({ + "KiwiErrInternalSolverError", + "KiwiErrAlloc", + "KiwiErrNullObject", + "KiwiErrUnknown", + })) + ---@class kiwi.KiwiErr: ffi.cdata* ---@field package kind kiwi.ErrKind ---@field package message ffi.cdata* @@ -828,8 +874,10 @@ do local KiwiErr = ffi.typeof("struct KiwiErr") --[[@as kiwi.KiwiErr]] local Error_mt = { + ---@param self kiwi.Error + ---@return string __tostring = function(self) - return strformat("%s: (%s, %s)", self.message, self.solver, self.item) + return strformat("%s: (%s, %s)", self.message, tostring(self.solver), tostring(self.item)) end, } @@ -851,9 +899,11 @@ do }, Error_mt) end - ---@param f fun(solver: kiwi.Solver, item: any, ...): kiwi.KiwiErr? + ---@generic T + ---@param f fun(solver: kiwi.Solver, item: T, ...): kiwi.KiwiErr? ---@param solver kiwi.Solver - ---@param item any + ---@param item T + ---@return T, kiwi.Error? local function try_solver(f, solver, item, ...) local err = f(solver, item, ...) if err ~= nil then @@ -862,12 +912,17 @@ do if err.must_free then C.free(err) end - error(new_error(kind, message, solver, item)) + local errdata = new_error(kind, message, solver, item) + return item, + band(solver.error_mask, lshift(1, kind --[[@as integer]])) == 0 and error(errdata) + or errdata end + return item end ---@class kiwi.Solver: ffi.cdata* - ---@overload fun(): kiwi.Solver + ---@field error_mask integer + ---@overload fun(error_mask: integer?): kiwi.Solver local Solver_cls = { --- Test whether a constraint is in the solver. ---@type fun(self: kiwi.Solver, constraint: kiwi.Constraint): boolean @@ -896,54 +951,133 @@ do dump = ckiwi.kiwi_solver_dump, } - --- Adds a constraint to the solver. - --- Raises - --- KiwiErrDuplicateConstraint: The given constraint has already been added to the solver. - --- KiwiErrUnsatisfiableConstraint: The given constraint is required and cannot be satisfied. + ---@generic T + ---@param solver kiwi.Solver + ---@param items T|T[] + ---@param f fun(solver: kiwi.Solver, item: T, ...): kiwi.KiwiErr? + ---@return T|T[], kiwi.Error? + local function add_remove_items(solver, items, f, ...) + for _, item in ipairs(items) do + local _, err = try_solver(f, solver, item, ...) + if err ~= nil then + return items, err + end + end + return items + end + + --- Add a constraint to the solver. + --- Errors: + --- KiwiErrDuplicateConstraint + --- KiwiErrUnsatisfiableConstraint ---@param constraint kiwi.Constraint + ---@return kiwi.Constraint constraint, kiwi.Error? function Solver_cls:add_constraint(constraint) - try_solver(ckiwi.kiwi_solver_add_constraint, self, constraint) + return try_solver(ckiwi.kiwi_solver_add_constraint, self, constraint) end - --- Removes a constraint from the solver. - --- Raises - --- KiwiErrUnknownConstraint: The given constraint has not been added to the solver. + --- Add constraints to the solver. + --- Errors: + --- KiwiErrDuplicateConstraint + --- KiwiErrUnsatisfiableConstraint + ---@param constraints kiwi.Constraint[] + ---@return kiwi.Constraint[] constraints, kiwi.Error? + function Solver_cls:add_constraints(constraints) + return add_remove_items(self, constraints, ckiwi.kiwi_solver_add_constraint) + end + + --- Remove a constraint from the solver. + --- Errors: + --- KiwiErrUnknownConstraint ---@param constraint kiwi.Constraint + ---@return kiwi.Constraint constraint, kiwi.Error? function Solver_cls:remove_constraint(constraint) - try_solver(ckiwi.kiwi_solver_remove_constraint, self, constraint) + return try_solver(ckiwi.kiwi_solver_remove_constraint, self, constraint) end - --- Adds an edit variable to the solver. + --- Remove constraints from the solver. + --- Errors: + --- KiwiErrUnknownConstraint + ---@param constraints kiwi.Constraint[] + ---@return kiwi.Constraint[] constraints, kiwi.Error? + function Solver_cls:remove_constraints(constraints) + return add_remove_items(self, constraints, ckiwi.kiwi_solver_remove_constraint) + end + + --- Add an edit variables to the solver. --- --- This method should be called before the `suggestValue` method is --- used to supply a suggested value for the given edit variable. - --- Raises - --- KiwiErrDuplicateEditVariable: The given edit variable has already been added to the solver. + --- Errors: + --- KiwiErrDuplicateEditVariable --- KiwiErrBadRequiredStrength: The given strength is >= required. ---@param var kiwi.Var the variable to add as an edit variable ---@param strength number the strength of the edit variable (must be less than `Strength.REQUIRED`) + ---@return kiwi.Var var, kiwi.Error? function Solver_cls:add_edit_var(var, strength) - try_solver(ckiwi.kiwi_solver_add_edit_var, self, var, strength) + return try_solver(ckiwi.kiwi_solver_add_edit_var, self, var, strength) + end + + --- Add edit variables to the solver. + --- + --- This method should be called before the `suggestValue` method is + --- used to supply a suggested value for the given edit variable. + --- Errors: + --- KiwiErrDuplicateEditVariable + --- KiwiErrBadRequiredStrength: The given strength is >= required. + ---@param vars kiwi.Var[] the variables to add as an edit variable + ---@param strength number the strength of the edit variables (must be less than `Strength.REQUIRED`) + ---@return kiwi.Var[] vars, kiwi.Error? + function Solver_cls:add_edit_vars(vars, strength) + return add_remove_items(self, vars, ckiwi.kiwi_solver_add_edit_var, strength) end --- Remove an edit variable from the solver. - --- Raises - --- KiwiErrUnknownEditVariable: The given edit variable has not been added to the solver + --- Raises: + --- KiwiErrUnknownEditVariable ---@param var kiwi.Var the edit variable to remove + ---@return kiwi.Var var, kiwi.Error? function Solver_cls:remove_edit_var(var) - try_solver(ckiwi.kiwi_solver_remove_edit_var, self, var) + return try_solver(ckiwi.kiwi_solver_remove_edit_var, self, var) + end + + --- Removes edit variables from the solver. + --- Raises: + --- KiwiErrUnknownEditVariable + ---@param vars kiwi.Var[] the edit variables to remove + ---@return kiwi.Var[] vars, kiwi.Error? + function Solver_cls:remove_edit_vars(vars) + return add_remove_items(self, vars, ckiwi.kiwi_solver_remove_edit_var) 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, --- the `update_vars` methods can be used to update the values of the external solver variables. - --- Raises - --- KiwiErrUnknownEditVariable: The given edit variable has not been added to the solver. + --- Raises: + --- KiwiErrUnknownEditVariable ---@param var kiwi.Var the edit variable to suggest a value for ---@param value number the suggested value + ---@return kiwi.Var var, kiwi.Error? function Solver_cls:suggest_value(var, value) - try_solver(ckiwi.kiwi_solver_suggest_value, self, var, value) + return try_solver(ckiwi.kiwi_solver_suggest_value, self, var, value) + end + + --- Suggest values for the given edit variables. + --- Convenience wrapper of `suggest_value` that takes tables of `kiwi.Var` and number pairs. + --- Raises: + --- KiwiErrUnknownEditVariable: The given edit variable has not been added to the solver. + ---@param vars kiwi.Var[] edit variables to suggest + ---@param values number[] suggested values + ---@return kiwi.Var[] vars, number[] values, kiwi.Error? + function Solver_cls:suggest_values(vars, values) + for i, var in ipairs(vars) do + local _, err = try_solver(ckiwi.kiwi_solver_suggest_value, self, var, values[i]) + if err ~= nil then + return vars, values, err + end + end + return vars, values end --- Dump a representation of the solver to a string. @@ -956,12 +1090,15 @@ do return s end - kiwi.Solver = ffi.metatype("struct KiwiSolver", { + local Solver_mt = { __index = Solver_cls, - __new = function(_) - return ffi_gc(ckiwi.kiwi_solver_new(), ckiwi.kiwi_solver_del) - end, - }) --[[@as kiwi.Solver]] + } + + function Solver_mt:__new(error_mask) + return ffi_gc(ckiwi.kiwi_solver_new(error_mask or 0), ckiwi.kiwi_solver_del) + end + + kiwi.Solver = ffi.metatype("struct KiwiSolver", Solver_mt) --[[@as kiwi.Solver]] end return kiwi