Skip to content

Commit

Permalink
All kinds of things
Browse files Browse the repository at this point in the history
- Add modified BSD license
- Initial test runner github action
- Add is_{type} functions
- export kiwi.Error metatable such that it appears like a class.
- Allow Expression.new to take nil for terms
- Initial set of specs
  • Loading branch information
jkl1337 committed Feb 18, 2024
1 parent d85796a commit 2ffc5a3
Show file tree
Hide file tree
Showing 8 changed files with 777 additions and 25 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/busted.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Busted

on: [push, pull_request]

jobs:
busted:
strategy:
fail-fast: false
matrix:
lua_version: ["luajit-2.1.0-beta3", "luajit-2.0.5", "luajit-openresty"]

runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup ‘lua’
uses: leafo/gh-actions-lua@v10
with:
luaVersion: ${{ matrix.lua_version }}
- name: Setup ‘luarocks’
uses: leafo/gh-actions-luarocks@v4
- name: Setup dependencies
run: |
luarocks install busted
luarocks install luacov-coveralls
- name: Build C library
run: make
- name: Run busted tests
run: busted -c -v
- name: Report test coverage
if: success()
continue-on-error: true
run: luacov-coveralls -e .luarocks -e spec
env:
COVERALLS_REPO_TOKEN: ${{ github.token }}
11 changes: 11 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Copyright 2024 John Luebs

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4 changes: 2 additions & 2 deletions ckiwi/ckiwi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,8 @@ void kiwi_var_set_value(KiwiVarRef var, double value) {
self->setValue(value);
}

int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other) {
VariableRef self(var); // const defect in upstream
bool kiwi_var_eq(KiwiVarRef var, KiwiVarRef other) {
ConstVariableRef self(var); // const defect in upstream
const VariableRef other_ref(other);

return self && other_ref && self->equals(other_ref);
Expand Down
2 changes: 1 addition & 1 deletion ckiwi/ckiwi.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ 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);
bool kiwi_var_eq(KiwiVarRef var, KiwiVarRef other);

void kiwi_expression_del_vars(KiwiExpression* expr);

Expand Down
87 changes: 65 additions & 22 deletions kiwi.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ typedef struct KiwiErr {
bool must_free;
} KiwiErr;
typedef struct KiwiSolver { unsigned error_mask; } KiwiSolver;
typedef struct KiwiSolver { unsigned error_mask_; } KiwiSolver;
KiwiVarRef kiwi_var_new(const char* name);
void kiwi_var_del(KiwiVarRef var);
Expand All @@ -58,7 +58,7 @@ 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);
bool kiwi_var_eq(KiwiVarRef var, KiwiVarRef other);
void kiwi_expression_del_vars(KiwiExpression* expr);
Expand Down Expand Up @@ -154,15 +154,31 @@ end
local Var = ffi.typeof("struct KiwiVarRefType") --[[@as kiwi.Var]]
kiwi.Var = Var

function kiwi.is_var(o)
return ffi_istype(Var, o)
end

local Term = ffi.typeof("struct KiwiTerm") --[[@as kiwi.Term]]
kiwi.Term = Term

function kiwi.is_term(o)
return ffi_istype(Term, o)
end

local Expression = ffi.typeof("struct KiwiExpression") --[[@as kiwi.Expression]]
kiwi.Expression = Expression

function kiwi.is_expression(o)
return ffi_istype(Expression, o)
end

local Constraint = ffi.typeof("struct KiwiConstraintRefType") --[[@as kiwi.Constraint]]
kiwi.Constraint = Constraint

function kiwi.is_constraint(o)
return ffi_istype(Constraint, o)
end

---@param expr kiwi.Expression
---@param var kiwi.Var
---@param coeff number?
Expand Down Expand Up @@ -404,7 +420,7 @@ do
return new_expr_pair(0.0, a, b)
end
elseif ffi_istype(Term, b) then
return new_expr_pair(0.0, b.var, a, b.coefficient)
return new_expr_pair(0.0, a, b.var, 1.0, b.coefficient)
elseif ffi_istype(Expression, b) then
return add_expr_term(b, a)
elseif type(b) == "number" then
Expand All @@ -414,7 +430,7 @@ do
end

function Var_mt.__sub(a, b)
return Var_mt.__add(a, -b)
return a + -b
end

function Var_mt:__tostring()
Expand Down Expand Up @@ -621,14 +637,17 @@ do
}

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)
dt.coefficient = t.coefficient
end
local term_count = terms and #terms or 0
local e = ffi_gc(ffi_new(T, term_count), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]]
e.term_count = term_count
e.constant = constant or 0.0
e.term_count = #terms
if terms then
for i, t in ipairs(terms) do
local dt = e.terms_[i - 1] --[[@as kiwi.Term]]
dt.var = ckiwi.kiwi_var_clone(t.var)
dt.coefficient = t.coefficient
end
end
return e
end

Expand Down Expand Up @@ -727,7 +746,7 @@ do
---@param solver kiwi.Solver
---@return kiwi.Constraint
function Constraint_cls:add_to(solver)
solver:add_constraints(self)
solver:add_constraint(self)
return self
end

Expand All @@ -737,7 +756,7 @@ do
---@param solver kiwi.Solver
---@return kiwi.Constraint
function Constraint_cls:remove_from(solver)
solver:remove_constraints(self)
solver:remove_constraint(self)
return self
end

Expand Down Expand Up @@ -881,16 +900,23 @@ do
end,
}

---@class kiwi.Error
---@field kind kiwi.ErrKind
---@field message string
---@field solver kiwi.Solver?
---@field item any?
kiwi.Error = Error_mt

function kiwi.is_error(o)
return type(o) == "table" and getmetatable(o) == Error_mt
end

---@param kind kiwi.ErrKind
---@param message string
---@param solver kiwi.Solver
---@param item any
---@return kiwi.Error
local function new_error(kind, message, solver, item)
---@class kiwi.Error
---@field kind kiwi.ErrKind
---@field message string
---@field solver kiwi.Solver?
---@field item any?
return setmetatable({
kind = kind,
message = message,
Expand All @@ -913,16 +939,16 @@ do
C.free(err)
end
local errdata = new_error(kind, message, solver, item)
local error_mask = solver and solver.error_mask_ or 0
return item,
band(solver.error_mask, lshift(1, kind --[[@as integer]])) == 0 and error(errdata)
band(error_mask, lshift(1, kind --[[@as integer]])) == 0 and error(errdata)
or errdata
end
return item
end

---@class kiwi.Solver: ffi.cdata*
---@field error_mask integer
---@overload fun(error_mask: integer?): kiwi.Solver
---@field package error_mask_ integer
---@overload fun(error_mask: (integer|(kiwi.ErrKind|number)[] )?): kiwi.Solver
local Solver_cls = {
--- Test whether a constraint is in the solver.
---@type fun(self: kiwi.Solver, constraint: kiwi.Constraint): boolean
Expand Down Expand Up @@ -951,6 +977,16 @@ do
dump = ckiwi.kiwi_solver_dump,
}

--- Sets the error mask for the solver.
---@param mask integer|(kiwi.ErrKind|number)[] the mask value or an array of kinds
---@param invert boolean? whether to invert the mask if an array was passed for mask
function Solver_cls:set_error_mask(mask, invert)
if type(mask) == "table" then
mask = kiwi.error_mask(mask, invert)
end
self.error_mask_ = mask
end

---@generic T
---@param solver kiwi.Solver
---@param items T|T[]
Expand Down Expand Up @@ -1095,10 +1131,17 @@ do
}

function Solver_mt:__new(error_mask)
if type(error_mask) == "table" then
error_mask = kiwi.error_mask(error_mask)
end
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]]

function kiwi.is_solver(s)
return ffi_istype(kiwi.Solver, s)
end
end

return kiwi
111 changes: 111 additions & 0 deletions spec/constraint_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
expose("module", function()
require("kiwi")
end)

describe("Constraint", function()
local kiwi = require("kiwi")

describe("construction", function()
local v, lhs
before_each(function()
v = kiwi.Var("foo")
lhs = v + 1
end)

it("has correct type", function()
assert.True(kiwi.is_constraint(kiwi.Constraint()))
assert.False(kiwi.is_constraint(v))
end)

it("default op and strength", function()
local c = kiwi.Constraint(lhs)
assert.equal("EQ", c:op())
assert.equal(kiwi.strength.REQUIRED, c:strength())
end)

it("configure op", function()
local c = kiwi.Constraint(lhs, nil, "LE")
assert.equal("LE", c:op())
end)
it("configure strength", function()
local c = kiwi.Constraint(lhs, nil, "GE", kiwi.strength.STRONG)
assert.equal(kiwi.strength.STRONG, c:strength())
end)

it("formats well", function()
local c = kiwi.Constraint(lhs)
assert.equal("1 foo + 1 == 0 | required", tostring(c))

c = kiwi.Constraint(lhs * 2, nil, "GE", kiwi.strength.STRONG)
assert.equal("2 foo + 2 >= 0 | strong", tostring(c))

c = kiwi.Constraint(lhs / 2, nil, "LE", kiwi.strength.MEDIUM)
assert.equal("0.5 foo + 0.5 <= 0 | medium", tostring(c))

c = kiwi.Constraint(lhs, kiwi.Expression(nil, 3), "GE", kiwi.strength.WEAK)
assert.equal("1 foo + -2 >= 0 | weak", tostring(c))
end)

it("rejects invalid args", function()
assert.error(function()
local _ = kiwi.Constraint(1)
end)
assert.error(function()
local _ = kiwi.Constraint(lhs, 1)
end)
assert.error(function()
local _ = kiwi.Constraint("")
end)
assert.error(function()
local _ = kiwi.Constraint(lhs, "")
end)
assert.error(function()
local _ = kiwi.Constraint(lhs, nil, "foo")
end)
assert.error(function()
local _ = kiwi.Constraint(lhs, nil, "LE", "foo")
end)
end)
it("combines lhs and rhs", function()
local v2 = kiwi.Var("bar")
local rhs = kiwi.Expression({ 5 * v2, 3 * v }, 3)
local c = kiwi.Constraint(lhs, rhs)

local e = c:expression()
local t = e:terms()
assert.equal(2, #t)
if t[1].var ~= v then
t[1], t[2] = t[2], t[1]
end
assert.equal(v, t[1].var)
assert.equal(-2.0, t[1].coefficient)
assert.equal(v2, t[2].var)
assert.equal(-5.0, t[2].coefficient)
assert.equal(-2.0, e.constant)
end)
end)

describe("method", function()
local c, v

before_each(function()
v = kiwi.Var("foo")
c = kiwi.Constraint(2 * v + 1)
end)

it("violated", function()
assert.True(c:violated())
v:set(-0.5)
assert.False(c:violated())
end)

it("add/remove constraint", function()
local s = kiwi.Solver()
c:add_to(s)
assert.True(s:has_constraint(c))

c:remove_from(s)
assert.False(s:has_constraint(c))
end)
end)
end)
Loading

0 comments on commit 2ffc5a3

Please sign in to comment.