Restyle and limit scopes for quicker navigation

This commit is contained in:
2024-02-14 16:00:49 -06:00
parent 3311b582a1
commit bc948d1a61
2 changed files with 449 additions and 426 deletions

View File

@@ -88,7 +88,7 @@ print(right_edge:value()) -- 500
```
In addition to the expression builder there are convenience constructors: `new_pair_ratio_constraint`, `new_pair_constraint`, and `new_single_constraint` to allow efficient construction of the most common simple expression types for GUI layout.
In addition to the expression builder there is a convenience constraints submodule with: `pair_ratio`, `pair`, and `single` to allow efficient construction of the most common simple expression types for GUI layout.
## Documentation
WIP - However the API is fully annotated and will work with lua-language-server. Documentation can also be generated with lua-language-server.

319
kiwi.lua
View File

@@ -198,7 +198,7 @@ end
---@param coeff number?
---@nodiscard
local function new_expr_one(constant, var, coeff)
return ffi_gc(new_expr_one_temp(constant, var, coeff), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] ---@diagnostic disable-line: param-type-mismatch
return ffi_gc(new_expr_one_temp(constant, var, coeff), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]]
end
---@param constant number
@@ -227,6 +227,7 @@ local REQUIRED = Strength.REQUIRED
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param op kiwi.RelOp
---@param strength? number
---@nodiscard
local function rel(lhs, rhs, op, strength)
local function to_expr(o)
if ffi_istype(Expression, o) then
@@ -264,7 +265,7 @@ end
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param strength? number
---@return kiwi.Constraint
---@nodiscard
function kiwi.le(lhs, rhs, strength)
return rel(lhs, rhs, "LE", strength)
end
@@ -273,7 +274,7 @@ end
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param strength? number
---@return kiwi.Constraint
---@nodiscard
function kiwi.ge(lhs, rhs, strength)
return rel(lhs, rhs, "GE", strength)
end
@@ -282,13 +283,14 @@ end
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param strength? number
---@return kiwi.Constraint
---@nodiscard
function kiwi.eq(lhs, rhs, strength)
return rel(lhs, rhs, "EQ", strength)
end
do
--- Variables are the values the constraint solver calculates.
---@class kiwi.Var: ffi.ctype*
---@class kiwi.Var: ffi.cdata*
---@overload fun(name: string): kiwi.Var
---@operator mul(number): kiwi.Term
---@operator div(number): kiwi.Term
@@ -315,6 +317,7 @@ local Var_cls = {
--- Get the name of the variable.
---@return string
---@nodiscard
function Var_cls:name()
return ffi_string(ckiwi.kiwi_var_name(self))
end
@@ -322,44 +325,47 @@ end
--- Create a term from this variable.
---@param coefficient number?
---@return kiwi.Term
---@nodiscard
function Var_cls:toterm(coefficient)
return Term(self, coefficient or 1.0)
return Term(self, coefficient)
end
--- Create a term from this variable.
---@param coefficient number?
---@param constant number?
---@return kiwi.Expression
---@nodiscard
function Var_cls:toexpr(coefficient, constant)
return new_expr_one(constant or 0.0, self, coefficient)
end
ffi.metatype(Var, {
local Var_mt = {
__index = Var_cls,
}
__new = function(_, name)
function Var_mt:__new(name)
return ffi_gc(ckiwi.kiwi_var_new(name), ckiwi.kiwi_var_del)
end,
end
__mul = function(a, b)
function Var_mt.__mul(a, b)
if type(a) == "number" then
return Term(b, a)
elseif type(b) == "number" then
return Term(a, b)
end
error("Invalid var *")
end,
end
__div = function(a, b)
function Var_mt.__div(a, b)
assert(type(b) == "number", "Invalid var /")
return Term(a, 1.0 / b)
end,
end
__unm = function(var)
return Term(var, -1.0)
end,
function Var_mt:__unm()
return Term(self, -1.0)
end
__add = function(a, b)
function Var_mt.__add(a, b)
if ffi_istype(Var, b) then
if type(a) == "number" then
return new_expr_one(a, b)
@@ -374,20 +380,23 @@ ffi.metatype(Var, {
return new_expr_one(b, a)
end
error("Invalid var +")
end,
end
__sub = function(a, b)
return a + -b
end,
function Var_mt.__sub(a, b)
return Var_mt.__add(a, -b)
end
__tostring = function(var)
return var:name() .. "(" .. var:value() .. ")"
end,
})
function Var_mt:__tostring()
return self:name() .. "(" .. self:value() .. ")"
end
ffi.metatype(Var, Var_mt)
end
do
--- Terms are the components of an expression.
--- Each term is a variable multiplied by a constant coefficient (default 1.0).
---@class kiwi.Term: ffi.ctype*
---@class kiwi.Term: ffi.cdata*
---@overload fun(var: kiwi.Var, coefficient: number?): kiwi.Term
---@field var kiwi.Var
---@field coefficient number
@@ -403,6 +412,7 @@ local Term_cls = {
}
---@return number
---@nodiscard
function Term_cls:value()
return self.coefficient * self.var:value()
end
@@ -414,34 +424,33 @@ function Term_cls:toexpr(constant)
return new_expr_one(constant or 0.0, self.var, self.coefficient)
end
ffi.metatype(Term, {
__index = Term_cls,
local Term_mt = { __index = Term_cls }
__new = function(_, var, coefficient)
return ffi_gc(ffi_new(Term, ckiwi.kiwi_var_clone(var), coefficient or 1.0), function(term)
function Term_mt.__new(T, var, coefficient)
return ffi_gc(ffi_new(T, ckiwi.kiwi_var_clone(var), coefficient or 1.0), function(term)
ckiwi.kiwi_var_del(term.var)
end)
end,
end
__mul = function(a, b)
function Term_mt.__mul(a, b)
if type(b) == "number" then
return Term(a.var, a.coefficient * b)
elseif type(a) == "number" then
return Term(b.var, b.coefficient * a)
end
error("Invalid term *")
end,
end
__div = function(term, denom)
assert(type(denom) == "number", "Invalid term /")
return Term(term.var, term.coefficient / denom)
end,
function Term_mt.__div(a, b)
assert(type(b) == "number", "Invalid term /")
return Term(a.var, a.coefficient / b)
end
__unm = function(term)
return Term(term.var, -term.coefficient)
end,
function Term_mt:__unm()
return Term(self.var, -self.coefficient)
end
__add = function(a, b)
function Term_mt.__add(a, b)
if ffi_istype(Var, b) then
return new_expr_pair(0.0, a.var, b, a.coefficient)
elseif ffi_istype(Term, b) then
@@ -456,18 +465,37 @@ ffi.metatype(Term, {
return new_expr_one(b, a.var, a.coefficient)
end
error("Invalid term + op")
end,
end
__sub = function(a, b)
return a + -b
end,
function Term_mt.__sub(a, b)
return Term_mt.__add(a, -b)
end
__tostring = function(term)
return tostring(term.coefficient) .. " " .. term.var:name()
end,
})
function Term_mt:__tostring()
return tostring(self.coefficient) .. " " .. self.var:name()
end
ffi.metatype(Term, Term_mt)
end
do
--- Expressions are a sum of terms with an added constant.
---@class kiwi.Expression: ffi.cdata*
---@overload fun(terms: kiwi.Term[], constant: number?): kiwi.Expression
---@field constant number
---@field package term_count number
---@field package terms_ ffi.cdata*
---@operator mul(number): kiwi.Expression
---@operator div(number): kiwi.Expression
---@operator unm: kiwi.Expression
---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
local Expression_cls = {
le = kiwi.le,
ge = kiwi.ge,
eq = kiwi.eq,
}
---@param expr kiwi.Expression
---@param constant number
---@nodiscard
@@ -526,23 +554,6 @@ do
return ret
end
--- Expressions are a sum of terms with an added constant.
---@class kiwi.Expression: ffi.ctype*
---@overload fun(terms: kiwi.Term[], constant: number?): kiwi.Expression
---@field constant number
---@field package term_count number
---@field package terms_ ffi.cdata*
---@operator mul(number): kiwi.Expression
---@operator div(number): kiwi.Expression
---@operator unm: kiwi.Expression
---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
local Expression_cls = {
le = kiwi.le,
ge = kiwi.ge,
eq = kiwi.eq,
}
---@return number
---@nodiscard
function Expression_cls:value()
@@ -570,11 +581,12 @@ do
return new_expr_constant(self, self.constant)
end
ffi.metatype(Expression, {
local Expression_mt = {
__index = Expression_cls,
}
__new = function(_, terms, constant)
local e = ffi_gc(ffi_new(Expression, #terms), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]]
function Expression_mt.__new(T, terms, constant)
local e = ffi_gc(ffi_new(T, #terms), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]]
for i, t in ipairs(terms) do
local dt = e.terms_[i - 1] --[[@as kiwi.Term]]
dt.var = ckiwi.kiwi_var_clone(t.var)
@@ -583,27 +595,27 @@ do
e.constant = constant or 0.0
e.term_count = #terms
return e
end,
end
__mul = function(a, b)
function Expression_mt.__mul(a, b)
if type(a) == "number" then
return mul_expr_constant(b, a)
elseif type(b) == "number" then
return mul_expr_constant(a, b)
end
error("Invalid expr *")
end,
end
__div = function(expr, denom)
assert(type(denom) == "number", "Invalid expr /")
return mul_expr_constant(expr, 1.0 / denom)
end,
function Expression_mt.__div(a, b)
assert(type(b) == "number", "Invalid expr /")
return mul_expr_constant(a, 1.0 / b)
end
__unm = function(expr)
return mul_expr_constant(expr, -1.0)
end,
function Expression_mt:__unm()
return mul_expr_constant(self, -1.0)
end
__add = function(a, b)
function Expression_mt.__add(a, b)
if ffi_istype(Var, b) then
return add_expr_term(a, b)
elseif ffi_istype(Expression, b) then
@@ -618,27 +630,29 @@ do
return new_expr_constant(a, a.constant + b)
end
error("Invalid expr +")
end,
__sub = function(a, b)
return a + -b
end,
__tostring = function(expr)
local tab = new_tab(expr.term_count + 1, 0)
for i = 0, expr.term_count - 1 do
tab[i + 1] = tostring(expr.terms_[i])
end
tab[expr.term_count + 1] = expr.constant
function Expression_mt.__sub(a, b)
return Expression_mt.__add(a, -b)
end
function Expression_mt:__tostring()
local tab = new_tab(self.term_count + 1, 0)
for i = 0, self.term_count - 1 do
tab[i + 1] = tostring(self.terms_[i])
end
tab[self.term_count + 1] = self.constant
return concat(tab, " + ")
end,
})
end
ffi.metatype(Expression, Expression_mt)
end
do
--- A constraint is a linear inequality or equality with associated strength.
--- Constraints can be built with arbitrary left and right hand expressions. But
--- ultimately they all have the form `expression [op] 0`.
---@class kiwi.Constraint: ffi.ctype*
---@class kiwi.Constraint: ffi.cdata*
---@overload fun(lhs: kiwi.Expression, rhs: kiwi.Expression?, op: kiwi.RelOp?, strength: number?): kiwi.Constraint
local Constraint_cls = {
--- The strength of the constraint.
@@ -665,9 +679,41 @@ function Constraint_cls:expression()
expr = ffi_new(Expression, n) --[[@as kiwi.Expression]]
n = ckiwi.kiwi_constraint_expression(self, expr, n)
end
return ffi_gc(expr, ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] ---@diagnostic disable-line: param-type-mismatch
return ffi_gc(expr, ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]]
end
local Constraint_mt = {
__index = Constraint_cls,
}
function Constraint_mt:__new(lhs, rhs, op, strength)
return ffi_gc(
ckiwi.kiwi_constraint_new(lhs, rhs, op or "EQ", strength or REQUIRED),
ckiwi.kiwi_constraint_del
)
end
function Constraint_mt:__tostring()
local ops = { [0] = "<=", ">=", "==" }
local strengths = {
[Strength.REQUIRED] = "required",
[Strength.STRONG] = "strong",
[Strength.MEDIUM] = "medium",
[Strength.WEAK] = "weak",
}
local strength = self:strength()
local strength_str = strengths[strength] or tostring(strength)
local op = ops[tonumber(self:op())]
return strformat("%s %s 0 | %s", tostring(self:expression()), op, strength_str)
end
ffi.metatype(Constraint, Constraint_mt)
end
do
local constraints = {}
kiwi.constraints = constraints
--- Create a constraint between a pair of variables with ratio.
--- The constraint is of the form `left [op|==] coeff right + [constant|0.0]`.
---@param left kiwi.Var
@@ -678,7 +724,7 @@ end
---@param strength number? strength (default REQUIRED)
---@return kiwi.Constraint
---@nodiscard
function kiwi.new_pair_ratio_constraint(left, coeff, right, constant, op, strength)
function constraints.pair_ratio(left, coeff, right, constant, op, strength)
assert(ffi_istype(Var, left) and ffi_istype(Var, right))
local lhs = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
local dt = lhs.terms_[0]
@@ -689,10 +735,14 @@ function kiwi.new_pair_ratio_constraint(left, coeff, right, constant, op, streng
dt.coefficient = -coeff
lhs.constant = -(constant or 0.0)
lhs.term_count = 2
return Constraint(lhs, nil, op, strength)
return ffi_gc(
ckiwi.kiwi_constraint_new(lhs, nil, op or "EQ", strength or REQUIRED),
ckiwi.kiwi_constraint_del
) --[[@as kiwi.Constraint]]
end
local new_pair_ratio_constraint = kiwi.new_pair_ratio_constraint
local pair_ratio = constraints.pair_ratio
--- Create a constraint between a pair of variables with ratio.
--- The constraint is of the form `left [op|==] right + [constant|0.0]`.
@@ -703,8 +753,8 @@ local new_pair_ratio_constraint = kiwi.new_pair_ratio_constraint
---@param strength number? strength (default REQUIRED)
---@return kiwi.Constraint
---@nodiscard
function kiwi.new_pair_constraint(left, right, constant, op, strength)
return new_pair_ratio_constraint(left, 1.0, right, constant, op, strength)
function constraints.pair(left, right, constant, op, strength)
return pair_ratio(left, 1.0, right, constant, op, strength)
end
--- Create a single term constraint
@@ -715,48 +765,29 @@ end
---@param strength number? strength (default REQUIRED)
---@return kiwi.Constraint
---@nodiscard
function kiwi.new_single_constraint(var, constant, op, strength)
function constraints.single(var, constant, op, strength)
assert(ffi_istype(Var, var))
local lhs = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
local dt = lhs.terms_[0]
dt.var = var
dt.coefficient = 1.0
lhs.constant = -(constant or 0.0)
lhs.term_count = 1
return Constraint(lhs, nil, op, strength)
return ffi_gc(
ckiwi.kiwi_constraint_new(
new_expr_one_temp(-(constant or 0.0), var, 1.0),
nil,
op or "EQ",
strength or REQUIRED
),
ckiwi.kiwi_constraint_del
) --[[@as kiwi.Constraint]]
end
end
ffi.metatype(Constraint, {
__index = Constraint_cls,
do
local C = ffi.C
__new = function(_, lhs, rhs, op, strength)
return ffi_gc(
ckiwi.kiwi_constraint_new(lhs, rhs, op or "EQ", strength or REQUIRED),
ckiwi.kiwi_constraint_del
)
end,
__tostring = function(self)
local ops = {
[0] = "<=",
">=",
"==",
}
local strengths = {
[Strength.REQUIRED] = "required",
[Strength.STRONG] = "strong",
[Strength.MEDIUM] = "medium",
[Strength.WEAK] = "weak",
}
local strength = self:strength()
return strformat(
"%s %s 0 | %s",
tostring(self:expression()),
ops[tonumber(self:op())],
strengths[strength] or tostring(strength)
)
end,
})
---@class kiwi.KiwiErr: ffi.cdata*
---@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]]
local Error_mt = {
__tostring = function(self)
@@ -782,15 +813,6 @@ local function new_error(kind, message, solver, item)
}, Error_mt)
end
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
@@ -806,7 +828,7 @@ local function try_solver(f, solver, item, ...)
end
end
---@class kiwi.Solver: ffi.ctype*
---@class kiwi.Solver: ffi.cdata*
---@overload fun(): kiwi.Solver
local Solver_cls = {
--- Test whether a constraint is in the solver.
@@ -902,5 +924,6 @@ kiwi.Solver = ffi.metatype("struct KiwiSolver", {
return ffi_gc(ckiwi.kiwi_solver_new(), ckiwi.kiwi_solver_del)
end,
}) --[[@as kiwi.Solver]]
end
return kiwi