• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jkl1337 / ljkiwi / 8238855727

11 Mar 2024 07:49PM UTC coverage: 82.116% (-0.2%) from 82.353%
8238855727

push

github

jkl1337
Make variable type consistent for Rust and C++

Implement standard layout type for Variable for both language bindings.
Implement small string optimization for variable names for Rust.

30 of 45 new or added lines in 3 files covered. (66.67%)

17 existing lines in 4 files now uncovered.

1506 of 1834 relevant lines covered (82.12%)

65.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.13
/kiwi.lua
1
-- kiwi.lua - LuaJIT FFI bindings with C API fallback to kiwi constraint solver.
2

3
local ffi
4
do
5
   local ffi_loader = package.preload["ffi"]
6✔
6
   if ffi_loader == nil then
6✔
7
      return require("ljkiwi")
×
8
   end
9
   ffi = ffi_loader() --[[@as ffilib]]
6✔
10
end
11

12
local kiwi = {}
6✔
13

14
local ljkiwi
15
local RUST = false
6✔
16

17
if not _G["KIWI_CKIWI"] then
6✔
18
   local libpath = package.searchpath("rjkiwi", package.cpath)
6✔
19
   if libpath then
6✔
20
      RUST, ljkiwi = pcall(ffi.load, libpath)
×
21
      if not RUST then
×
22
         ljkiwi = nil
×
23
      end
24
   end
25
end
26

27
if not ljkiwi then
6✔
28
   local cpath, err = package.searchpath("ljkiwi", package.cpath)
6✔
29
   if cpath == nil then
6✔
30
      error("kiwi dynamic library 'ljkiwi' not found\n" .. err)
×
31
   end
32
   ljkiwi = ffi.load(cpath)
6✔
33
end
34

35
do
36
   ffi.cdef("void kiwi_solver_type_layout(unsigned sz_align[2]);")
6✔
37
   local ti = ffi.new("unsigned[2]")
6✔
38
   ljkiwi.kiwi_solver_type_layout(ti)
6✔
39
   ffi.cdef(
12✔
40
      "typedef struct KiwiSolver { unsigned char b_[$]; } __attribute__((aligned($))) KiwiSolver;",
6✔
41
      ti[0],
6✔
42
      ti[1]
43
   )
6✔
44
end
45

46
ffi.cdef([[
12✔
47
typedef struct KiwiVar KiwiVar;
48

49
enum KiwiRelOp { LE, GE, EQ };
50

51
struct KiwiSmallStr {
52
   size_t len_;
53
   union {
54
      char *ptr_;
55
      char inline_[16];
56
   };
57
};
58

59
struct KiwiVar {
60
   size_t ref_count_;
61
   double value_;
62
   struct KiwiSmallStr name_;
63
};
64

65
KiwiVar* kiwi_var_new(const char* name);
66
void kiwi_var_free(KiwiVar* var);
67
const char* kiwi_var_name(const KiwiVar* var);
68
void kiwi_var_set_name(KiwiVar* var, const char* name);
69
void kiwi_var_set_value(KiwiVar* var, double value);
70

71
typedef struct KiwiTerm {
72
   KiwiVar* var;
73
   double coefficient;
74
} KiwiTerm;
75

76
typedef struct KiwiExpression {
77
   double constant;
78
   int term_count;
79
   void* owner;
80

81
   KiwiTerm terms_[?];
82
} KiwiExpression;
83

84
void kiwi_expression_retain(KiwiExpression* expr);
85
void kiwi_expression_destroy(KiwiExpression* expr);
86
void kiwi_expression_add_term(const KiwiExpression* expr, KiwiVar* var, double coeff, KiwiExpression* out);
87
void kiwi_expression_set_constant(const KiwiExpression* expr, double constant, KiwiExpression* out);
88
]])
6✔
89

90
if RUST then
6✔
91
   ffi.cdef([[
×
92
typedef struct KiwiConstraint {
93
   double constant;
94
   int term_count;
95
   enum KiwiRelOp op_;
96
   double strength_;
97

98
   void* owner;
99

100
   KiwiTerm terms_[?];
101
} KiwiConstraint;
102

103
void kiwi_constraint_init(
104
    KiwiConstraint* c,
105
    const KiwiExpression* lhs,
106
    const KiwiExpression* rhs,
107
    enum KiwiRelOp op,
108
    double strength
109
);
110
void kiwi_constraint_destroy(KiwiConstraint* c);
UNCOV
111
]])
×
112
else
113
   ffi.cdef([[
12✔
114
typedef struct KiwiConstraint KiwiConstraint;
115

116
KiwiConstraint* kiwi_constraint_new(
117
    const KiwiExpression* lhs,
118
    const KiwiExpression* rhs,
119
    enum KiwiRelOp op,
120
    double strength
121
);
122
void kiwi_constraint_release(KiwiConstraint* c);
123
void kiwi_constraint_retain(KiwiConstraint* c);
124

125
double kiwi_constraint_strength(const KiwiConstraint* c);
126
enum KiwiRelOp kiwi_constraint_op(const KiwiConstraint* c);
127
]])
6✔
128
end
129

130
ffi.cdef([[
12✔
131
enum KiwiErrKind {
132
   KiwiErrNone,
133
   KiwiErrUnsatisfiableConstraint = 1,
134
   KiwiErrUnknownConstraint,
135
   KiwiErrDuplicateConstraint,
136
   KiwiErrUnknownEditVar,
137
   KiwiErrDuplicateEditVar,
138
   KiwiErrBadRequiredStrength,
139
   KiwiErrInternalSolverError,
140
   KiwiErrAlloc,
141
   KiwiErrNullObject,
142
   KiwiErrUnknown,
143
};
144

145
typedef struct KiwiErr {
146
   enum KiwiErrKind kind;
147
   const char* message;
148
   bool must_release;
149
} KiwiErr;
150

151
struct KiwiSolver;
152

153
void kiwi_str_release(char *);
154
void kiwi_err_release(const KiwiErr *);
155

156
bool kiwi_constraint_violated(const KiwiConstraint* c);
157
int kiwi_constraint_expression(KiwiConstraint* c, KiwiExpression* out, int out_size);
158

159
void kiwi_solver_init(KiwiSolver* s, unsigned error_mask);
160
void kiwi_solver_destroy(KiwiSolver* s);
161
unsigned kiwi_solver_get_error_mask(const KiwiSolver* s);
162
void kiwi_solver_set_error_mask(KiwiSolver* s, unsigned mask);
163

164
const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, KiwiConstraint* constraint);
165
const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, KiwiConstraint* constraint);
166
bool kiwi_solver_has_constraint(const KiwiSolver* s, KiwiConstraint* constraint);
167
const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, KiwiVar* var, double strength);
168
const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, KiwiVar* var);
169
bool kiwi_solver_has_edit_var(const KiwiSolver* s, KiwiVar* var);
170
const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, KiwiVar* var, double value);
171
void kiwi_solver_update_vars(KiwiSolver* sp);
172
void kiwi_solver_reset(KiwiSolver* sp);
173
void kiwi_solver_dump(const KiwiSolver* sp);
174
char* kiwi_solver_dumps(const KiwiSolver* sp);
175
]])
6✔
176

177
local strformat = string.format
6✔
178
local ffi_copy, ffi_gc, ffi_istype, ffi_new, ffi_string =
179
   ffi.copy, ffi.gc, ffi.istype, ffi.new, ffi.string
6✔
180

181
local concat = table.concat
6✔
182
local has_table_new, new_tab = pcall(require, "table.new")
6✔
183
if not has_table_new or type(new_tab) ~= "function" then
6✔
184
   new_tab = function(_, _)
185
      return {}
×
186
   end
187
end
188

189
---@alias kiwi.ErrKind
190
---| '"KiwiErrNone"' # No error.
191
---| '"KiwiErrUnsatisfiableConstraint"' # The given constraint is required and cannot be satisfied.
192
---| '"KiwiErrUnknownConstraint"' # The given constraint has not been added to the solver.
193
---| '"KiwiErrDuplicateConstraint"' # The given constraint has already been added to the solver.
194
---| '"KiwiErrUnknownEditVar"' # The given edit variable has not been added to the solver.
195
---| '"KiwiErrDuplicateEditVar"' # The given edit variable has already been added to the solver.
196
---| '"KiwiErrBadRequiredStrength"' # The given strength is >= required.
197
---| '"KiwiErrInternalSolverError"' # An internal solver error occurred.
198
---| '"KiwiErrAlloc"' # A memory allocation error occurred.
199
---| '"KiwiErrNullObject"' # A method was invoked on a null or empty object.
200
---| '"KiwiErrUnknown"' # An unknown error occurred.
201
kiwi.ErrKind = ffi.typeof("enum KiwiErrKind") --[[@as kiwi.ErrKind]]
6✔
202

203
---@alias kiwi.RelOp
204
---| '"LE"' # <= (less than or equal)
205
---| '"GE"' # >= (greater than or equal)
206
---| '"EQ"' # == (equal)
207
kiwi.RelOp = ffi.typeof("enum KiwiRelOp")
6✔
208

209
kiwi.strength = {
6✔
210
   REQUIRED = 1001001000.0,
211
   STRONG = 1000000.0,
212
   MEDIUM = 1000.0,
213
   WEAK = 1.0,
214
}
6✔
215

216
local REQUIRED = kiwi.strength.REQUIRED
6✔
217

218
do
219
   local function clamp(n)
220
      return math.max(0, math.min(1000, n))
×
221
   end
222

223
   --- Create a custom constraint strength.
224
   ---@param a number: Scale factor 1e6
225
   ---@param b number: Scale factor 1e3
226
   ---@param c number: Scale factor 1
227
   ---@param w? number: Weight
228
   ---@return number
229
   ---@nodiscard
230
   function kiwi.strength.create(a, b, c, w)
12✔
231
      w = w or 1.0
×
232
      return clamp(a * w) * 1000000.0 + clamp(b * w) * 1000.0 + clamp(c * w)
×
233
   end
234
end
235

236
local Var = ffi.typeof("struct KiwiVar") --[[@as kiwi.Var]]
6✔
237
kiwi.Var = Var
6✔
238

239
function kiwi.is_var(o)
6✔
240
   return ffi_istype(Var, o)
12✔
241
end
242

243
local Term = ffi.typeof("struct KiwiTerm") --[[@as kiwi.Term]]
6✔
244
local SIZEOF_TERM = assert(ffi.sizeof(Term))
6✔
245
kiwi.Term = Term
6✔
246

247
function kiwi.is_term(o)
6✔
248
   return ffi_istype(Term, o)
48✔
249
end
250

251
local Expression = ffi.typeof("struct KiwiExpression") --[[@as kiwi.Expression]]
6✔
252
kiwi.Expression = Expression
6✔
253

254
function kiwi.is_expression(o)
6✔
255
   return ffi_istype(Expression, o)
186✔
256
end
257

258
local Constraint = ffi.typeof("struct KiwiConstraint") --[[@as kiwi.Constraint]]
6✔
259
kiwi.Constraint = Constraint
6✔
260

261
function kiwi.is_constraint(o)
6✔
262
   return ffi_istype(Constraint, o)
102✔
263
end
264

265
local new_constraint
266

267
if RUST then
6✔
268
   ---@return kiwi.Constraint
269
   function new_constraint(lhs, rhs, op, strength)
×
270
      local c = ffi_new(Constraint, (lhs and lhs.term_count or 0) + (rhs and rhs.term_count or 0))
×
271
      ljkiwi.kiwi_constraint_init(c, lhs, rhs, op or "EQ", strength or REQUIRED)
×
272
      return ffi_gc(c, ljkiwi.kiwi_constraint_destroy) --[[@as kiwi.Constraint]]
×
273
   end
274
else
275
   function new_constraint(lhs, rhs, op, strength)
6✔
276
      return ffi_gc(
198✔
277
         ljkiwi.kiwi_constraint_new(lhs, rhs, op or "EQ", strength or REQUIRED),
198✔
278
         ljkiwi.kiwi_constraint_release
162✔
279
      ) --[[@as kiwi.Constraint]]
162✔
280
   end
281
end
282

283
local function var_retain(var)
284
   var.ref_count_ = var.ref_count_ + 1
1,164✔
285
end
286

287
local function var_release(var)
NEW
288
   var.ref_count_ = var.ref_count_ - 1
×
NEW
289
   if var.ref_count_ == 0 then
×
NEW
290
      ljkiwi.kiwi_var_free(var)
×
291
   end
292
end
293

294
---@param expr kiwi.Expression
295
---@param var kiwi.Var
296
---@param coeff number?
297
---@nodiscard
298
local function add_expr_term(expr, var, coeff)
299
   local ret = ffi_new(Expression, expr.term_count + 1)
36✔
300
   ljkiwi.kiwi_expression_add_term(expr, var, coeff or 1.0, ret)
36✔
301
   return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
36✔
302
end
303

304
---@param constant number
305
---@param var kiwi.Var
306
---@param coeff number?
307
---@nodiscard
308
local function new_expr_one(constant, var, coeff)
309
   local ret = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
222✔
310
   local dt = ret.terms_[0]
222✔
311
   dt.var = var
222✔
312
   dt.coefficient = coeff or 1.0
222✔
313
   ret.constant = constant
222✔
314
   ret.term_count = 1
222✔
315
   ret.owner = ret
222✔
316
   var_retain(var)
222✔
317
   return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
222✔
318
end
319

320
---@param constant number
321
---@param var1 kiwi.Var
322
---@param var2 kiwi.Var
323
---@param coeff1 number?
324
---@param coeff2 number?
325
---@nodiscard
326
local function new_expr_pair(constant, var1, var2, coeff1, coeff2)
327
   local ret = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
36✔
328
   local dt = ret.terms_[0]
36✔
329
   dt.var = var1
36✔
330
   dt.coefficient = coeff1 or 1.0
36✔
331
   dt = ret.terms_[1]
36✔
332
   dt.var = var2
36✔
333
   dt.coefficient = coeff2 or 1.0
36✔
334
   ret.constant = constant
36✔
335
   ret.term_count = 2
36✔
336
   ret.owner = ret
36✔
337
   var_retain(var1)
36✔
338
   var_retain(var2)
36✔
339
   return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
36✔
340
end
341

342
local function typename(o)
343
   if ffi.istype(Var, o) then
144✔
344
      return "Var"
66✔
345
   elseif ffi.istype(Term, o) then
78✔
346
      return "Term"
30✔
347
   elseif ffi.istype(Expression, o) then
48✔
348
      return "Expression"
12✔
349
   elseif ffi.istype(Constraint, o) then
36✔
350
      return "Constraint"
×
351
   else
352
      return type(o)
36✔
353
   end
354
end
355

356
local function op_error(a, b, op)
357
   --stylua: ignore
358
   -- level 3 works for arithmetic without TCO (no return), and for rel with TCO forced (explicit return)
359
   error(strformat(
216✔
360
         "invalid operand type for '%s' %.40s('%.99s') and %.40s('%.99s')",
72✔
361
         op, typename(a), tostring(a), typename(b), tostring(b)), 3)
360✔
362
end
363

364
local OP_NAMES = {
6✔
365
   LE = "<=",
366
   GE = ">=",
367
   EQ = "==",
368
}
369

370
local tmpexpr = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
6✔
371
local tmpexpr_r = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
6✔
372

373
local function toexpr(o, temp)
374
   if ffi_istype(Expression, o) then
180✔
375
      return o --[[@as kiwi.Expression]]
72✔
376
   elseif type(o) == "number" then
108✔
377
      temp.constant = o
×
378
      temp.term_count = 0
×
379
      return temp
×
380
   end
381
   temp.constant = 0
108✔
382
   temp.term_count = 1
108✔
383
   local t = temp.terms_[0]
108✔
384

385
   if ffi_istype(Var, o) then
108✔
386
      t.var = o --[[@as kiwi.Var]]
54✔
387
      t.coefficient = 1.0
54✔
388
   elseif ffi_istype(Term, o) then
54✔
389
      ffi_copy(t, o, SIZEOF_TERM)
54✔
390
   else
391
      return nil
×
392
   end
393
   return temp
108✔
394
end
395

396
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
397
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
398
---@param op kiwi.RelOp
399
---@param strength? number
400
---@nodiscard
401
local function rel(lhs, rhs, op, strength)
402
   local el = toexpr(lhs, tmpexpr)
90✔
403
   local er = toexpr(rhs, tmpexpr_r)
90✔
404
   if el == nil or er == nil then
90✔
405
      op_error(lhs, rhs, OP_NAMES[op])
×
406
   end
407
   return new_constraint(el, er, op, strength)
90✔
408
end
409

410
--- Define a constraint with expressions as `a <= b`.
411
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
412
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
413
---@param strength? number
414
---@nodiscard
415
function kiwi.le(lhs, rhs, strength)
6✔
416
   return rel(lhs, rhs, "LE", strength)
30✔
417
end
418

419
--- Define a constraint with expressions as `a >= b`.
420
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
421
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
422
---@param strength? number
423
---@nodiscard
424
function kiwi.ge(lhs, rhs, strength)
6✔
425
   return rel(lhs, rhs, "GE", strength)
30✔
426
end
427

428
--- Define a constraint with expressions as `a == b`.
429
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
430
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
431
---@param strength? number
432
---@nodiscard
433
function kiwi.eq(lhs, rhs, strength)
6✔
434
   return rel(lhs, rhs, "EQ", strength)
30✔
435
end
436

437
do
438
   --- Variables are the values the constraint solver calculates.
439
   ---@class kiwi.Var: ffi.cdata*
440
   ---@field package value_ number
441
   ---@overload fun(name: string?): kiwi.Var
442
   ---@operator mul(number): kiwi.Term
443
   ---@operator div(number): kiwi.Term
444
   ---@operator unm: kiwi.Term
445
   ---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
446
   ---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
447
   local Var_cls = {
6✔
448
      le = kiwi.le,
6✔
449
      ge = kiwi.ge,
6✔
450
      eq = kiwi.eq,
6✔
451

452
      --- Change the name of the variable.
453
      ---@type fun(self: kiwi.Var, name: string)
454
      set_name = ljkiwi.kiwi_var_set_name,
6✔
455

456
      --- Set the value of the variable.
457
      ---@type fun(self: kiwi.Var, value: number)
458
      set = ljkiwi.kiwi_var_set_value,
6✔
459
   }
460

461
   local Var_mt = {
6✔
462
      __index = Var_cls,
6✔
463
   }
464

465
   function Var_mt:__new(name)
6✔
466
      return ffi_gc(ljkiwi.kiwi_var_new(name)[0], var_release)
942✔
467
   end
468

469
   --- Get the name of the variable.
470
   ---@return string
471
   ---@nodiscard
472
   function Var_cls:name()
6✔
473
      return ffi_string(ljkiwi.kiwi_var_name(self))
168✔
474
   end
475

476
   --- Get the current value of the variable.
477
   ---@return number
478
   ---@nodiscard
479
   function Var_cls:value()
6✔
480
      return self.value_
102✔
481
   end
482

483
   --- Set the value of the variable.
484
   ---@param value number
485
   function Var_cls:set(value)
6✔
486
      self.value_ = value
96✔
487
   end
488

489
   --- Create a term from this variable.
490
   ---@param coefficient number?
491
   ---@return kiwi.Term
492
   ---@nodiscard
493
   function Var_cls:toterm(coefficient)
6✔
494
      return Term(self, coefficient)
×
495
   end
496

497
   --- Create a term from this variable.
498
   ---@param coefficient number?
499
   ---@param constant number?
500
   ---@return kiwi.Expression
501
   ---@nodiscard
502
   function Var_cls:toexpr(coefficient, constant)
6✔
503
      return new_expr_one(constant or 0.0, self, coefficient)
×
504
   end
505

506
   function Var_mt.__mul(a, b)
6✔
507
      if type(a) == "number" then
90✔
508
         return Term(b, a)
30✔
509
      elseif type(b) == "number" then
60✔
510
         return Term(a, b)
54✔
511
      end
512
      op_error(a, b, "*")
6✔
513
   end
514

515
   function Var_mt.__div(a, b)
6✔
516
      if type(b) ~= "number" then
18✔
517
         op_error(a, b, "/")
12✔
518
      end
519
      return Term(a, 1.0 / b)
6✔
520
   end
521

522
   function Var_mt:__unm()
6✔
523
      return Term(self, -1.0)
30✔
524
   end
525

526
   function Var_mt.__add(a, b)
6✔
527
      if ffi_istype(Var, b) then
150✔
528
         if type(a) == "number" then
12✔
529
            return new_expr_one(a, b)
6✔
530
         else
531
            return new_expr_pair(0.0, a, b)
6✔
532
         end
533
      elseif ffi_istype(Term, b) then
138✔
534
         return new_expr_pair(0.0, a, b.var, 1.0, b.coefficient)
6✔
535
      elseif ffi_istype(Expression, b) then
132✔
536
         return add_expr_term(b, a)
×
537
      elseif type(b) == "number" then
132✔
538
         return new_expr_one(b, a)
120✔
539
      end
540
      op_error(a, b, "+")
12✔
541
   end
542

543
   function Var_mt.__sub(a, b)
6✔
544
      return a + -b
120✔
545
   end
546

547
   function Var_mt:__tostring()
6✔
548
      return self:name() .. "(" .. self:value() .. ")"
198✔
549
   end
550

551
   ffi.metatype(Var, Var_mt)
6✔
552
end
553

554
do
555
   --- Terms are the components of an expression.
556
   --- Each term is a variable multiplied by a constant coefficient (default 1.0).
557
   ---@class kiwi.Term: ffi.cdata*
558
   ---@overload fun(var: kiwi.Var, coefficient: number?): kiwi.Term
559
   ---@field var kiwi.Var
560
   ---@field coefficient number
561
   ---@operator mul(number): kiwi.Term
562
   ---@operator div(number): kiwi.Term
563
   ---@operator unm: kiwi.Term
564
   ---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
565
   ---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
566
   local Term_cls = {
6✔
567
      le = kiwi.le,
6✔
568
      ge = kiwi.ge,
6✔
569
      eq = kiwi.eq,
6✔
570
   }
571

572
   ---@return number
573
   ---@nodiscard
574
   function Term_cls:value()
6✔
575
      return self.coefficient * self.var:value()
24✔
576
   end
577

578
   --- Create an expression from this term.
579
   ---@param constant number?
580
   ---@return kiwi.Expression
581
   function Term_cls:toexpr(constant)
6✔
582
      return new_expr_one(constant or 0.0, self.var, self.coefficient)
6✔
583
   end
584

585
   local Term_mt = { __index = Term_cls }
6✔
586

587
   local function term_release(term)
NEW
588
      var_release(term.var)
×
589
   end
590

591
   function Term_mt.__new(T, var, coefficient)
6✔
592
      local t = ffi_new(T) --[[@as kiwi.Term]]
876✔
593
      t.var = var
876✔
594
      t.coefficient = coefficient or 1.0
870✔
595
      var_retain(var)
870✔
596
      return ffi_gc(t, term_release)
870✔
597
   end
598

599
   function Term_mt.__mul(a, b)
6✔
600
      if type(b) == "number" then
18✔
601
         return Term(a.var, a.coefficient * b)
6✔
602
      elseif type(a) == "number" then
12✔
603
         return Term(b.var, b.coefficient * a)
6✔
604
      end
605
      op_error(a, b, "*")
6✔
606
   end
607

608
   function Term_mt.__div(a, b)
6✔
609
      if type(b) ~= "number" then
6✔
610
         op_error(a, b, "/")
×
611
      end
612
      return Term(a.var, a.coefficient / b)
6✔
613
   end
614

615
   function Term_mt:__unm()
6✔
616
      return Term(self.var, -self.coefficient)
24✔
617
   end
618

619
   function Term_mt.__add(a, b)
6✔
620
      if ffi_istype(Var, b) then
150✔
621
         return new_expr_pair(0.0, a.var, b, a.coefficient)
6✔
622
      elseif ffi_istype(Term, b) then
144✔
623
         if type(a) == "number" then
36✔
624
            return new_expr_one(a, b.var, b.coefficient)
18✔
625
         else
626
            return new_expr_pair(0.0, a.var, b.var, a.coefficient, b.coefficient)
18✔
627
         end
628
      elseif ffi_istype(Expression, b) then
108✔
629
         return add_expr_term(b, a.var, a.coefficient)
12✔
630
      elseif type(b) == "number" then
96✔
631
         return new_expr_one(b, a.var, a.coefficient)
72✔
632
      end
633
      op_error(a, b, "+")
24✔
634
   end
635

636
   function Term_mt.__sub(a, b)
6✔
637
      return Term_mt.__add(a, -b)
66✔
638
   end
639

640
   function Term_mt:__tostring()
6✔
641
      return tostring(self.coefficient) .. " " .. self.var:name()
72✔
642
   end
643

644
   ffi.metatype(Term, Term_mt)
6✔
645
end
646

647
do
648
   --- Expressions are a sum of terms with an added constant.
649
   ---@class kiwi.Expression: ffi.cdata*
650
   ---@overload fun(constant: number, ...: kiwi.Term): kiwi.Expression
651
   ---@field constant number
652
   ---@field package owner ffi.cdata*
653
   ---@field package term_count number
654
   ---@field package terms_ ffi.cdata*
655
   ---@operator mul(number): kiwi.Expression
656
   ---@operator div(number): kiwi.Expression
657
   ---@operator unm: kiwi.Expression
658
   ---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
659
   ---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
660
   local Expression_cls = {
6✔
661
      le = kiwi.le,
6✔
662
      ge = kiwi.ge,
6✔
663
      eq = kiwi.eq,
6✔
664
   }
665

666
   ---@param expr kiwi.Expression
667
   ---@param constant number
668
   ---@nodiscard
669
   local function mul_expr_coeff(expr, constant)
670
      local ret = ffi_new(Expression, expr.term_count) --[[@as kiwi.Expression]]
54✔
671
      for i = 0, expr.term_count - 1 do
114✔
672
         local st = expr.terms_[i] --[[@as kiwi.Term]]
60✔
673
         local dt = ret.terms_[i] --[[@as kiwi.Term]]
60✔
674
         dt.var = st.var
60✔
675
         dt.coefficient = st.coefficient * constant
60✔
676
      end
677
      ret.constant = expr.constant * constant
54✔
678
      ret.term_count = expr.term_count
54✔
679
      ljkiwi.kiwi_expression_retain(ret)
54✔
680
      return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
54✔
681
   end
682

683
   ---@param a kiwi.Expression
684
   ---@param b kiwi.Expression
685
   ---@nodiscard
686
   local function add_expr_expr(a, b)
687
      local a_count = a.term_count
12✔
688
      local b_count = b.term_count
12✔
689
      local ret = ffi_new(Expression, a_count + b_count) --[[@as kiwi.Expression]]
12✔
690

691
      ffi_copy(ret.terms_, a.terms_, SIZEOF_TERM * a_count)
12✔
692
      ffi_copy(ret.terms_ + a_count, b.terms_, SIZEOF_TERM * b_count)
12✔
693
      ret.constant = a.constant + b.constant
12✔
694
      ret.term_count = a_count + b_count
12✔
695
      ljkiwi.kiwi_expression_retain(ret)
12✔
696
      return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
12✔
697
   end
698

699
   ---@param expr kiwi.Expression
700
   ---@param constant number
701
   ---@nodiscard
702
   local function new_expr_constant(expr, constant)
703
      local ret = ffi_new(Expression, expr.term_count)
30✔
704
      ljkiwi.kiwi_expression_set_constant(expr, constant, ret)
30✔
705
      return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
30✔
706
   end
707

708
   ---@return number
709
   ---@nodiscard
710
   function Expression_cls:value()
6✔
711
      local sum = self.constant
12✔
712
      for i = 0, self.term_count - 1 do
24✔
713
         local t = self.terms_[i]
12✔
714
         sum = sum + t.var:value() * t.coefficient
24✔
715
      end
716
      return sum
12✔
717
   end
718

719
   ---@return kiwi.Term[]
720
   ---@nodiscard
721
   function Expression_cls:terms()
6✔
722
      local terms = new_tab(self.term_count, 0)
300✔
723
      for i = 0, self.term_count - 1 do
810✔
724
         local t = self.terms_[i] --[[@as kiwi.Term]]
510✔
725
         terms[i + 1] = Term(t.var, t.coefficient)
1,020✔
726
      end
727
      return terms
300✔
728
   end
729

730
   ---@return kiwi.Expression
731
   ---@nodiscard
732
   function Expression_cls:copy()
6✔
733
      return new_expr_constant(self, self.constant)
6✔
734
   end
735

736
   local Expression_mt = {
6✔
737
      __index = Expression_cls,
6✔
738
   }
739

740
   function Expression_mt:__new(constant, ...)
6✔
741
      local term_count = select("#", ...)
30✔
742
      local e = ffi_new(self, term_count) --[[@as kiwi.Expression]]
30✔
743
      e.term_count = term_count
30✔
744
      e.constant = constant
30✔
745
      for i = 1, term_count do
78✔
746
         local t = select(i, ...)
54✔
747
         local dt = e.terms_[i - 1] --[[@as kiwi.Term]]
54✔
748
         dt.var = t.var
54✔
749
         dt.coefficient = t.coefficient
48✔
750
      end
751
      ljkiwi.kiwi_expression_retain(e)
24✔
752
      return ffi_gc(e, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
24✔
753
   end
754

755
   function Expression_mt.__mul(a, b)
6✔
756
      if type(a) == "number" then
24✔
757
         return mul_expr_coeff(b, a)
6✔
758
      elseif type(b) == "number" then
18✔
759
         return mul_expr_coeff(a, b)
12✔
760
      end
761
      op_error(a, b, "*")
6✔
762
   end
763

764
   function Expression_mt.__div(a, b)
6✔
765
      if type(b) ~= "number" then
18✔
766
         op_error(a, b, "/")
6✔
767
      end
768
      return mul_expr_coeff(a, 1.0 / b)
12✔
769
   end
770

771
   function Expression_mt:__unm()
6✔
772
      return mul_expr_coeff(self, -1.0)
24✔
773
   end
774

775
   function Expression_mt.__add(a, b)
6✔
776
      if ffi_istype(Var, b) then
60✔
777
         return add_expr_term(a, b)
6✔
778
      elseif ffi_istype(Expression, b) then
54✔
779
         if type(a) == "number" then
24✔
780
            return new_expr_constant(b, a + b.constant)
12✔
781
         else
782
            return add_expr_expr(a, b)
12✔
783
         end
784
      elseif ffi_istype(Term, b) then
30✔
785
         return add_expr_term(a, b.var, b.coefficient)
18✔
786
      elseif type(b) == "number" then
12✔
787
         return new_expr_constant(a, a.constant + b)
12✔
788
      end
789
      op_error(a, b, "+")
×
790
   end
791

792
   function Expression_mt.__sub(a, b)
6✔
793
      return Expression_mt.__add(a, -b)
66✔
794
   end
795

796
   function Expression_mt:__tostring()
6✔
797
      local tab = new_tab(self.term_count + 1, 0)
42✔
798
      for i = 0, self.term_count - 1 do
96✔
799
         local t = self.terms_[i]
54✔
800
         tab[i + 1] = tostring(t.coefficient) .. " " .. t.var:name()
108✔
801
      end
802
      tab[self.term_count + 1] = self.constant
42✔
803
      return concat(tab, " + ")
42✔
804
   end
805

806
   ffi.metatype(Expression, Expression_mt)
6✔
807
end
808

809
do
810
   --- A constraint is a linear inequality or equality with associated strength.
811
   --- Constraints can be built with arbitrary left and right hand expressions. But
812
   --- ultimately they all have the form `expression [op] 0`.
813
   ---@class kiwi.Constraint: ffi.cdata*
814
   ---@field package constant number
815
   ---@field package term_count number
816
   ---@field package owner ffi.cdata*
817
   ---@field package op_ kiwi.RelOp
818
   ---@field package strength_ number
819
   ---@field package terms_ ffi.cdata*
820
   ---@overload fun(lhs: kiwi.Expression?, rhs: kiwi.Expression?, op: kiwi.RelOp?, strength: number?): kiwi.Constraint
821
   local Constraint_cls = {
6✔
822
      --- Whether the constraint is violated in the current solution.
823
      ---@type fun(self: kiwi.Constraint): boolean
824
      violated = ljkiwi.kiwi_constraint_violated,
6✔
825
   }
826

827
   if RUST then
6✔
828
      --- The strength of the constraint.
829
      ---@return number
830
      ---@nodiscard
831
      function Constraint_cls:strength()
×
832
         return self.strength_
×
833
      end
834

835
      --- The relational operator of the constraint.
836
      ---@return kiwi.RelOp
837
      ---@nodiscard
838
      function Constraint_cls:op()
×
839
         return self.op_
×
840
      end
841
   else
842
      Constraint_cls.strength = ljkiwi.kiwi_constraint_strength
6✔
843
      Constraint_cls.op = ljkiwi.kiwi_constraint_op
6✔
844
   end
845

846
   --- The reduced expression defining the constraint.
847
   ---@return kiwi.Expression
848
   ---@nodiscard
849
   function Constraint_cls:expression()
6✔
850
      local SZ = 7
120✔
851
      local expr = ffi_new(Expression, SZ) --[[@as kiwi.Expression]]
120✔
852
      local n = ljkiwi.kiwi_constraint_expression(self, expr, SZ)
120✔
853
      if n > SZ then
120✔
854
         expr = ffi_new(Expression, n) --[[@as kiwi.Expression]]
×
855
         n = ljkiwi.kiwi_constraint_expression(self, expr, n)
×
856
      end
857
      return ffi_gc(expr, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
120✔
858
   end
859

860
   --- Add the constraint to the solver.
861
   --- Raises:
862
   --- KiwiErrDuplicateConstraint: The given constraint has already been added to the solver.
863
   --- KiwiErrUnsatisfiableConstraint: The given constraint is required and cannot be satisfied.
864
   ---@param solver kiwi.Solver
865
   ---@return kiwi.Constraint
866
   function Constraint_cls:add_to(solver)
6✔
867
      solver:add_constraint(self)
6✔
868
      return self
6✔
869
   end
870

871
   --- Remove the constraint from the solver.
872
   --- Raises:
873
   --- KiwiErrUnknownConstraint: The given constraint has not been added to the solver.
874
   ---@param solver kiwi.Solver
875
   ---@return kiwi.Constraint
876
   function Constraint_cls:remove_from(solver)
6✔
877
      solver:remove_constraint(self)
6✔
878
      return self
6✔
879
   end
880

881
   local Constraint_mt = {
6✔
882
      __index = Constraint_cls,
6✔
883
   }
884

885
   function Constraint_mt:__new(lhs, rhs, op, strength)
6✔
886
      return new_constraint(lhs, rhs, op, strength)
108✔
887
   end
888

889
   local OPS = { [0] = "<=", ">=", "==" }
6✔
890

891
   local STRENGTH_NAMES = {
6✔
892
      [kiwi.strength.REQUIRED] = "required",
6✔
893
      [kiwi.strength.STRONG] = "strong",
6✔
894
      [kiwi.strength.MEDIUM] = "medium",
6✔
895
      [kiwi.strength.WEAK] = "weak",
6✔
896
   }
897

898
   function Constraint_mt:__tostring()
6✔
899
      local strength = self:strength()
24✔
900
      local strength_str = STRENGTH_NAMES[strength] or tostring(strength)
24✔
901
      local op = OPS[tonumber(self:op())]
24✔
902
      return strformat("%s %s 0 | %s", tostring(self:expression()), op, strength_str)
72✔
903
   end
904

905
   ffi.metatype(Constraint, Constraint_mt)
6✔
906
end
907

908
do
909
   local constraints = {}
6✔
910
   kiwi.constraints = constraints
6✔
911

912
   --- Create a constraint between a pair of variables with ratio.
913
   --- The constraint is of the form `left [op|==] coeff right + [constant|0.0]`.
914
   ---@param left kiwi.Var
915
   ---@param coeff number right side term coefficient
916
   ---@param right kiwi.Var
917
   ---@param constant number? constant (default 0.0)
918
   ---@param op kiwi.RelOp? relational operator (default "EQ")
919
   ---@param strength number? strength (default REQUIRED)
920
   ---@return kiwi.Constraint
921
   ---@nodiscard
922
   function constraints.pair_ratio(left, coeff, right, constant, op, strength)
6✔
923
      assert(ffi_istype(Var, left) and ffi_istype(Var, right))
×
924
      local dt = tmpexpr.terms_[0]
×
925
      dt.var = left
×
926
      dt.coefficient = 1.0
×
927
      dt = tmpexpr.terms_[1]
×
928
      dt.var = right
×
929
      dt.coefficient = -coeff
×
930
      tmpexpr.constant = constant ~= nil and constant or 0
×
931
      tmpexpr.term_count = 2
×
932

933
      return new_constraint(tmpexpr, nil, op, strength)
×
934
   end
935

936
   local pair_ratio = constraints.pair_ratio
6✔
937

938
   --- Create a constraint between a pair of variables with offset.
939
   --- The constraint is of the form `left [op|==] right + [constant|0.0]`.
940
   ---@param left kiwi.Var
941
   ---@param right kiwi.Var
942
   ---@param constant number? constant (default 0.0)
943
   ---@param op kiwi.RelOp? relational operator (default "EQ")
944
   ---@param strength number? strength (default REQUIRED)
945
   ---@return kiwi.Constraint
946
   ---@nodiscard
947
   function constraints.pair(left, right, constant, op, strength)
6✔
948
      return pair_ratio(left, 1.0, right, constant, op, strength)
×
949
   end
950

951
   --- Create a single term constraint
952
   --- The constraint is of the form `var [op|==] [constant|0.0]`.
953
   ---@param var kiwi.Var
954
   ---@param constant number? constant (default 0.0)
955
   ---@param op kiwi.RelOp? relational operator (default "EQ")
956
   ---@param strength number? strength (default REQUIRED)
957
   ---@return kiwi.Constraint
958
   ---@nodiscard
959
   function constraints.single(var, constant, op, strength)
6✔
960
      assert(ffi_istype(Var, var))
×
961
      tmpexpr.constant = -(constant or 0)
×
962
      tmpexpr.term_count = 1
×
963
      local t = tmpexpr.terms_[0]
×
964
      t.var = var
×
965
      t.coefficient = 1.0
×
966

967
      return new_constraint(tmpexpr, nil, op, strength)
×
968
   end
969
end
970

971
do
972
   local bit = require("bit")
6✔
973
   local band, bor, lshift = bit.band, bit.bor, bit.lshift
6✔
974

975
   --- Produce a custom error raise mask
976
   --- Error kinds specified in the mask will not cause a lua
977
   --- error to be raised.
978
   ---@param kinds (kiwi.ErrKind|integer)[]
979
   ---@param invert boolean?
980
   ---@return integer
981
   function kiwi.error_mask(kinds, invert)
6✔
982
      local mask = 0
42✔
983
      for _, k in ipairs(kinds) do
138✔
984
         mask = bor(mask, lshift(1, kiwi.ErrKind(k)))
96✔
985
      end
986
      return invert and bit.bnot(mask) or mask
42✔
987
   end
988

989
   kiwi.ERROR_MASK_ALL = 0xFFFF
6✔
990
   --- an error mask that raises errors only for fatal conditions
991
   kiwi.ERROR_MASK_NON_FATAL = bit.bnot(kiwi.error_mask({
12✔
992
      "KiwiErrInternalSolverError",
993
      "KiwiErrAlloc",
994
      "KiwiErrNullObject",
995
      "KiwiErrUnknown",
996
   }))
6✔
997

998
   ---@class kiwi.KiwiErr: ffi.cdata*
999
   ---@field package kind kiwi.ErrKind
1000
   ---@field package message ffi.cdata*
1001
   ---@field package must_release boolean
1002
   ---@overload fun(): kiwi.KiwiErr
1003
   local KiwiErr = ffi.typeof("struct KiwiErr") --[[@as kiwi.KiwiErr]]
6✔
1004

1005
   local Error_mt = {
6✔
1006
      ---@param self kiwi.Error
1007
      ---@return string
1008
      __tostring = function(self)
1009
         return strformat("%s: (%s, %s)", self.message, tostring(self.solver), tostring(self.item))
×
1010
      end,
1011
   }
1012

1013
   ---@class kiwi.Error
1014
   ---@field kind kiwi.ErrKind
1015
   ---@field message string
1016
   ---@field solver kiwi.Solver?
1017
   ---@field item any?
1018
   kiwi.Error = Error_mt
6✔
1019

1020
   function kiwi.is_error(o)
6✔
1021
      return type(o) == "table" and getmetatable(o) == Error_mt
72✔
1022
   end
1023

1024
   ---@param kind kiwi.ErrKind
1025
   ---@param message string
1026
   ---@param solver kiwi.Solver
1027
   ---@param item any
1028
   ---@return kiwi.Error
1029
   local function new_error(kind, message, solver, item)
1030
      return setmetatable({
108✔
1031
         kind = kind,
108✔
1032
         message = message,
108✔
1033
         solver = solver,
108✔
1034
         item = item,
108✔
1035
      }, Error_mt)
108✔
1036
   end
1037

1038
   local ERR_MESSAGES = {
6✔
1039
      "The constraint cannot be satisfied.",
1040
      "The constraint has not been added to the solver.",
1041
      "The constraint has already been added to the solver.",
1042
      "The edit variable has not been added to the solver.",
1043
      "The edit variable has already been added to the solver.",
1044
      "A required strength cannot be used in this context.",
1045
      "An internal solver error occurred.",
1046
      "A memory allocation failed.",
1047
      "null object passed as argument.",
1048
      "An unknown error occurred.",
1049
   }
1050
   ---@generic T
1051
   ---@param f fun(solver: kiwi.Solver, item: T, ...): kiwi.KiwiErr?
1052
   ---@param solver kiwi.Solver
1053
   ---@param item T
1054
   ---@return T, kiwi.Error?
1055
   local function try_solver(f, solver, item, ...)
1056
      local err = f(solver, item, ...)
378✔
1057
      if err ~= nil then
342✔
1058
         if err.must_release then
108✔
1059
            ffi_gc(err, ljkiwi.kiwi_err_release)
×
1060
         end
1061
         local kind = err.kind
108✔
1062
         local message = err.message ~= nil and ffi_string(err.message)
108✔
1063
            or ERR_MESSAGES[tonumber(err.kind)]
108✔
1064
            or ""
72✔
1065
         local errdata = new_error(kind, message, solver, item)
108✔
1066
         local error_mask = ljkiwi.kiwi_solver_get_error_mask(solver)
108✔
1067
         return item,
108✔
1068
            band(error_mask, lshift(1, kind --[[@as integer]])) == 0 and error(errdata)
108✔
1069
               or errdata
36✔
1070
      end
1071
      return item
234✔
1072
   end
1073
   ---@class kiwi.Solver: ffi.cdata*
1074
   ---@overload fun(error_mask: (integer|(kiwi.ErrKind|integer)[] )?): kiwi.Solver
1075
   local Solver_cls = {
6✔
1076
      --- Test whether a constraint is in the solver.
1077
      ---@type fun(self: kiwi.Solver, constraint: kiwi.Constraint): boolean
1078
      has_constraint = ljkiwi.kiwi_solver_has_constraint,
6✔
1079

1080
      --- Test whether an edit variable has been added to the solver.
1081
      ---@type fun(self: kiwi.Solver, var: kiwi.Var): boolean
1082
      has_edit_var = ljkiwi.kiwi_solver_has_edit_var,
6✔
1083

1084
      --- Update the values of the external solver variables.
1085
      ---@type fun(self: kiwi.Solver)
1086
      update_vars = ljkiwi.kiwi_solver_update_vars,
6✔
1087

1088
      --- Reset the solver to the empty starting conditions.
1089
      ---
1090
      --- This method resets the internal solver state to the empty starting
1091
      --- condition, as if no constraints or edit variables have been added.
1092
      --- This can be faster than deleting the solver and creating a new one
1093
      --- when the entire system must change, since it can avoid unecessary
1094
      --- heap (de)allocations.
1095
      ---@type fun(self: kiwi.Solver)
1096
      reset = ljkiwi.kiwi_solver_reset,
6✔
1097

1098
      --- Dump a representation of the solver to stdout.
1099
      ---@type fun(self: kiwi.Solver)
1100
      dump = ljkiwi.kiwi_solver_dump,
6✔
1101
   }
1102

1103
   --- Sets the error mask for the solver.
1104
   ---@param mask integer|(kiwi.ErrKind|integer)[] the mask value or an array of kinds
1105
   ---@param invert boolean? whether to invert the mask if an array was passed for mask
1106
   function Solver_cls:set_error_mask(mask, invert)
6✔
1107
      if type(mask) == "table" then
36✔
1108
         mask = kiwi.error_mask(mask, invert)
72✔
1109
      end
1110
      ljkiwi.kiwi_solver_set_error_mask(self, mask)
36✔
1111
   end
1112

1113
   ---@generic T
1114
   ---@param solver kiwi.Solver
1115
   ---@param items T|T[]
1116
   ---@param f fun(solver: kiwi.Solver, item: T, ...): kiwi.KiwiErr?
1117
   ---@return T|T[], kiwi.Error?
1118
   local function add_remove_items(solver, items, f, ...)
1119
      for _, item in ipairs(items) do
312✔
1120
         local _, err = try_solver(f, solver, item, ...)
228✔
1121
         if err ~= nil then
192✔
1122
            return items, err
18✔
1123
         end
1124
      end
1125
      return items
54✔
1126
   end
1127

1128
   --- Add a constraint to the solver.
1129
   --- Errors:
1130
   --- KiwiErrDuplicateConstraint
1131
   --- KiwiErrUnsatisfiableConstraint
1132
   ---@param constraint kiwi.Constraint
1133
   ---@return kiwi.Constraint constraint, kiwi.Error?
1134
   function Solver_cls:add_constraint(constraint)
6✔
1135
      return try_solver(ljkiwi.kiwi_solver_add_constraint, self, constraint)
6✔
1136
   end
1137

1138
   --- Add constraints to the solver.
1139
   --- Errors:
1140
   --- KiwiErrDuplicateConstraint
1141
   --- KiwiErrUnsatisfiableConstraint
1142
   ---@param constraints kiwi.Constraint[]
1143
   ---@return kiwi.Constraint[] constraints, kiwi.Error?
1144
   function Solver_cls:add_constraints(constraints)
6✔
1145
      return add_remove_items(self, constraints, ljkiwi.kiwi_solver_add_constraint)
×
1146
   end
1147

1148
   --- Remove a constraint from the solver.
1149
   --- Errors:
1150
   --- KiwiErrUnknownConstraint
1151
   ---@param constraint kiwi.Constraint
1152
   ---@return kiwi.Constraint constraint, kiwi.Error?
1153
   function Solver_cls:remove_constraint(constraint)
6✔
1154
      return try_solver(ljkiwi.kiwi_solver_remove_constraint, self, constraint)
6✔
1155
   end
1156

1157
   --- Remove constraints from the solver.
1158
   --- Errors:
1159
   --- KiwiErrUnknownConstraint
1160
   ---@param constraints kiwi.Constraint[]
1161
   ---@return kiwi.Constraint[] constraints, kiwi.Error?
1162
   function Solver_cls:remove_constraints(constraints)
6✔
1163
      return add_remove_items(self, constraints, ljkiwi.kiwi_solver_remove_constraint)
×
1164
   end
1165

1166
   --- Add an edit variables to the solver.
1167
   ---
1168
   --- This method should be called before the `suggestValue` method is
1169
   --- used to supply a suggested value for the given edit variable.
1170
   --- Errors:
1171
   --- KiwiErrDuplicateEditVar
1172
   --- KiwiErrBadRequiredStrength: The given strength is >= required.
1173
   ---@param var kiwi.Var the variable to add as an edit variable
1174
   ---@param strength number the strength of the edit variable (must be less than `Strength.REQUIRED`)
1175
   ---@return kiwi.Var var, kiwi.Error?
1176
   function Solver_cls:add_edit_var(var, strength)
6✔
1177
      return try_solver(ljkiwi.kiwi_solver_add_edit_var, self, var, strength)
90✔
1178
   end
1179

1180
   --- Add edit variables to the solver.
1181
   ---
1182
   --- This method should be called before the `suggestValue` method is
1183
   --- used to supply a suggested value for the given edit variable.
1184
   --- Errors:
1185
   --- KiwiErrDuplicateEditVar
1186
   --- KiwiErrBadRequiredStrength: The given strength is >= required.
1187
   ---@param vars kiwi.Var[] the variables to add as an edit variable
1188
   ---@param strength number the strength of the edit variables (must be less than `Strength.REQUIRED`)
1189
   ---@return kiwi.Var[] vars, kiwi.Error?
1190
   function Solver_cls:add_edit_vars(vars, strength)
6✔
1191
      return add_remove_items(self, vars, ljkiwi.kiwi_solver_add_edit_var, strength)
96✔
1192
   end
1193

1194
   --- Remove an edit variable from the solver.
1195
   --- Raises:
1196
   --- KiwiErrUnknownEditVar
1197
   ---@param var kiwi.Var the edit variable to remove
1198
   ---@return kiwi.Var var, kiwi.Error?
1199
   function Solver_cls:remove_edit_var(var)
6✔
1200
      return try_solver(ljkiwi.kiwi_solver_remove_edit_var, self, var)
48✔
1201
   end
1202

1203
   --- Removes edit variables from the solver.
1204
   --- Raises:
1205
   --- KiwiErrUnknownEditVar
1206
   ---@param vars kiwi.Var[] the edit variables to remove
1207
   ---@return kiwi.Var[] vars, kiwi.Error?
1208
   function Solver_cls:remove_edit_vars(vars)
6✔
1209
      return add_remove_items(self, vars, ljkiwi.kiwi_solver_remove_edit_var)
42✔
1210
   end
1211

1212
   --- Suggest a value for the given edit variable.
1213
   --- This method should be used after an edit variable has been added to the solver in order
1214
   --- to suggest the value for that variable. After all suggestions have been made,
1215
   --- the `update_vars` methods can be used to update the values of the external solver variables.
1216
   --- Raises:
1217
   --- KiwiErrUnknownEditVar
1218
   ---@param var kiwi.Var the edit variable to suggest a value for
1219
   ---@param value number the suggested value
1220
   ---@return kiwi.Var var, kiwi.Error?
1221
   function Solver_cls:suggest_value(var, value)
6✔
1222
      return try_solver(ljkiwi.kiwi_solver_suggest_value, self, var, value)
×
1223
   end
1224

1225
   --- Suggest values for the given edit variables.
1226
   --- Convenience wrapper of `suggest_value` that takes tables of `kiwi.Var` and number pairs.
1227
   --- Raises:
1228
   --- KiwiErrUnknownEditVar: The given edit variable has not been added to the solver.
1229
   ---@param vars kiwi.Var[] edit variables to suggest
1230
   ---@param values number[] suggested values
1231
   ---@return kiwi.Var[] vars, number[] values, kiwi.Error?
1232
   function Solver_cls:suggest_values(vars, values)
6✔
1233
      for i, var in ipairs(vars) do
×
1234
         local _, err = try_solver(ljkiwi.kiwi_solver_suggest_value, self, var, values[i])
×
1235
         if err ~= nil then
×
1236
            return vars, values, err
×
1237
         end
1238
      end
1239
      return vars, values
×
1240
   end
1241

1242
   --- Dump a representation of the solver to a string.
1243
   ---@return string
1244
   ---@nodiscard
1245
   function Solver_cls:dumps()
6✔
1246
      local cs = ffi_gc(ljkiwi.kiwi_solver_dumps(self), ljkiwi.kiwi_str_release)
×
1247
      return ffi_string(cs)
×
1248
   end
1249

1250
   local Solver_mt = {
6✔
1251
      __index = Solver_cls,
6✔
1252
   }
1253

1254
   function Solver_mt:__new(error_mask)
6✔
1255
      if type(error_mask) == "table" then
204✔
1256
         error_mask = kiwi.error_mask(error_mask)
×
1257
      end
1258
      local s = ffi_new(self)
204✔
1259
      ljkiwi.kiwi_solver_init(s, error_mask or 0)
204✔
1260
      return ffi_gc(s, ljkiwi.kiwi_solver_destroy)
204✔
1261
   end
1262

1263
   local Solver = ffi.metatype(ffi.typeof("struct KiwiSolver"), Solver_mt) --[[@as kiwi.Solver]]
6✔
1264
   kiwi.Solver = Solver
6✔
1265

1266
   function kiwi.is_solver(s)
6✔
1267
      return ffi_istype(Solver, s)
84✔
1268
   end
1269
end
1270

1271
return kiwi
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc