Compare commits
10 Commits
c35cea6213
...
08e9bf08e7
| Author | SHA1 | Date | |
|---|---|---|---|
| 08e9bf08e7 | |||
| ef29b8abcb | |||
| 59cb4b3c4f | |||
| 55a3aa1e6f | |||
| dc36e719eb | |||
| d2e769ea30 | |||
| 3e56c503e4 | |||
| f68c24d9ea | |||
| 2b76ba96ac | |||
| 98a3fff28f |
68
.github/workflows/busted.yml
vendored
68
.github/workflows/busted.yml
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
---
|
||||||
name: Busted
|
name: Busted
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
@@ -35,19 +36,46 @@ jobs:
|
|||||||
- name: Setup dependencies
|
- name: Setup dependencies
|
||||||
run: |
|
run: |
|
||||||
luarocks install busted
|
luarocks install busted
|
||||||
luarocks install luacov-coveralls
|
luarocks install luacov-reporter-lcov
|
||||||
- name: Build C library
|
- name: Build C++ library
|
||||||
run: |
|
run: |
|
||||||
${{ matrix.os == 'ubuntu-latest' && 'FSANITIZE=1' || '' }} luarocks make --no-install
|
luarocks make --no-install
|
||||||
|
env:
|
||||||
|
LJKIWI_LUA: ${{ startsWith(matrix.lua_version, 'luajit-') && '0' || '1' }}
|
||||||
|
LJKIWI_CFFI: ${{ startsWith(matrix.lua_version, 'luajit-') && '1' || '0' }}
|
||||||
|
FCOV: ${{ startsWith(matrix.os, 'ubuntu-') && '1' || '' }}
|
||||||
|
# Can't assume so versions, have to update this manually below
|
||||||
|
FSANITIZE: ${{ matrix.os == 'ubuntu-latest' && '1' || '' }}
|
||||||
|
|
||||||
- name: Run busted tests
|
- name: Run busted tests
|
||||||
run: |
|
run: |
|
||||||
${{ matrix.os == 'ubuntu-latest' && 'LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libasan.so.6:/usr/lib/x86_64-linux-gnu/libstdc++.so.6:/usr/lib/x86_64-linux-gnu/libubsan.so.1' || '' }} busted -c -v
|
busted -c -v
|
||||||
- name: Report test coverage
|
|
||||||
if: success() && !startsWith(matrix.os, 'windows-') && startsWith(matrix.lua_version, 'luajit-')
|
|
||||||
continue-on-error: true
|
|
||||||
run: luacov-coveralls -e .luarocks -e spec
|
|
||||||
env:
|
env:
|
||||||
COVERALLS_REPO_TOKEN: ${{ github.token }}
|
LD_PRELOAD: |-
|
||||||
|
${{ matrix.os == 'ubuntu-latest' &&
|
||||||
|
'/usr/lib/x86_64-linux-gnu/libasan.so.6:/usr/lib/x86_64-linux-gnu/libstdc++.so.6:/usr/lib/x86_64-linux-gnu/libubsan.so.1'
|
||||||
|
|| '' }}
|
||||||
|
|
||||||
|
- name: Run gcov
|
||||||
|
if: success() && startsWith(matrix.os, 'ubuntu-')
|
||||||
|
run: |
|
||||||
|
gcov -p -b -s"$(pwd)" -r *.gcda
|
||||||
|
rm -f 'kiwi#'*.gcov
|
||||||
|
|
||||||
|
- name: generate Lua lcov test reports
|
||||||
|
if: |-
|
||||||
|
success() && !startsWith(matrix.os, 'windows-')
|
||||||
|
&& startsWith(matrix.lua_version, 'luajit-')
|
||||||
|
run: luacov
|
||||||
|
|
||||||
|
- name: Report test coverage
|
||||||
|
if: |-
|
||||||
|
success() && !startsWith(matrix.os, 'windows-')
|
||||||
|
&& (startsWith(matrix.lua_version, 'luajit-') || startsWith(matrix.os, 'ubuntu-'))
|
||||||
|
continue-on-error: true
|
||||||
|
uses: coverallsapp/github-action@v2
|
||||||
|
with:
|
||||||
|
flag-name: run ${{ join(matrix.*, ' - ') }}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
if: always()
|
if: always()
|
||||||
@@ -58,3 +86,25 @@ jobs:
|
|||||||
uses: coverallsapp/github-action@v2
|
uses: coverallsapp/github-action@v2
|
||||||
with:
|
with:
|
||||||
parallel-finished: true
|
parallel-finished: true
|
||||||
|
|
||||||
|
publish:
|
||||||
|
if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
|
||||||
|
needs: busted
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup ‘lua’
|
||||||
|
uses: jkl1337/gh-actions-lua@master
|
||||||
|
with:
|
||||||
|
luaVersion: "5.4.6"
|
||||||
|
- name: Setup ‘luarocks’
|
||||||
|
uses: jkl1337/gh-actions-luarocks@master
|
||||||
|
- name: Build C++ library
|
||||||
|
run: |
|
||||||
|
luarocks make --no-install
|
||||||
|
- name: Build rock
|
||||||
|
run: |
|
||||||
|
luarocks install dkjson
|
||||||
|
luarocks upload --api-key ${{ secrets.LUAROCKS_API_KEY }} \
|
||||||
|
rockspecs/kiwi-${GITHUB_REF_NAME#v}-1.rockspec
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,6 +3,11 @@
|
|||||||
/lua_modules
|
/lua_modules
|
||||||
/.luarocks
|
/.luarocks
|
||||||
/config.mk
|
/config.mk
|
||||||
|
*.gcda
|
||||||
|
*.gcno
|
||||||
|
*.gcov
|
||||||
|
*.lcov
|
||||||
|
*.out
|
||||||
*.pch
|
*.pch
|
||||||
*.gch
|
*.gch
|
||||||
*.lib
|
*.lib
|
||||||
@@ -11,5 +16,6 @@
|
|||||||
*.obj
|
*.obj
|
||||||
*.exp
|
*.exp
|
||||||
*.dll
|
*.dll
|
||||||
|
*.rock
|
||||||
.cache/
|
.cache/
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
|||||||
5
.luacov
Normal file
5
.luacov
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
modules = {
|
||||||
|
kiwi = "kiwi.lua",
|
||||||
|
}
|
||||||
|
reporter = "lcov"
|
||||||
|
reportfile = "luacov.lcov"
|
||||||
66
Makefile
66
Makefile
@@ -5,36 +5,57 @@ LIBFLAG := -shared
|
|||||||
LIB_EXT := $(if $(filter Windows_NT,$(OS)),dll,so)
|
LIB_EXT := $(if $(filter Windows_NT,$(OS)),dll,so)
|
||||||
LUA_INCDIR := /usr/include
|
LUA_INCDIR := /usr/include
|
||||||
|
|
||||||
SRCDIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
|
SRCDIR := .
|
||||||
|
|
||||||
|
SANITIZE_FLAGS := -fsanitize=undefined -fsanitize=address -fsanitize=alignment \
|
||||||
|
-fsanitize=shift -fsanitize=unreachable -fsanitize=bool -fsanitize=enum
|
||||||
|
|
||||||
|
ifdef FDEBUG
|
||||||
|
OPTFLAG := -O2
|
||||||
|
else
|
||||||
|
OPTFLAG := -Og -g
|
||||||
|
endif
|
||||||
|
|
||||||
|
COVERAGE_FLAGS := --coverage
|
||||||
|
LTO_FLAGS := -flto=auto
|
||||||
|
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
is_clang = $(filter %clang++,$(CXX))
|
is_clang = $(filter %clang++,$(CXX))
|
||||||
is_gcc = $(filter %g++,$(CXX))
|
is_gcc = $(filter %g++,$(CXX))
|
||||||
|
|
||||||
|
ifdef FSANITIZE
|
||||||
|
$(error "FSANITIZE is not supported on Windows")
|
||||||
|
endif
|
||||||
else
|
else
|
||||||
uname_s := $(shell uname -s)
|
uname_s := $(shell uname -s)
|
||||||
ifeq ($(uname_s),Darwin)
|
ifeq ($(uname_s),Darwin)
|
||||||
|
is_clang = 1
|
||||||
|
is_gcc =
|
||||||
|
|
||||||
CC := env MACOSX_DEPLOYMENT_TARGET=11.0 gcc
|
CC := env MACOSX_DEPLOYMENT_TARGET=11.0 gcc
|
||||||
CXX := env MACOSX_DEPLOYMENT_TARGET=11.0 g++
|
CXX := env MACOSX_DEPLOYMENT_TARGET=11.0 g++
|
||||||
LIBFLAG := -bundle -undefined dynamic_lookup
|
LIBFLAG := -bundle -undefined dynamic_lookup
|
||||||
is_clang = 1
|
|
||||||
is_gcc =
|
|
||||||
else
|
else
|
||||||
is_clang = $(filter %clang++,$(CXX))
|
is_clang = $(filter %clang++,$(CXX))
|
||||||
is_gcc = $(filter %g++,$(CXX))
|
is_gcc = $(filter %g++,$(CXX))
|
||||||
|
|
||||||
|
SANITIZE_FLAGS += -fsanitize=bounds-strict
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
OPTFLAG := -O2
|
|
||||||
SANITIZE_FLAGS := -fsanitize=undefined -fsanitize=address -fsanitize=alignment -fsanitize=bounds-strict \
|
|
||||||
-fsanitize=shift -fsanitize=unreachable -fsanitize=bool \
|
|
||||||
-fsanitize=enum
|
|
||||||
|
|
||||||
LTO_FLAGS := -flto=auto
|
|
||||||
|
|
||||||
-include config.mk
|
-include config.mk
|
||||||
|
|
||||||
ifeq ($(origin LUAROCKS), command line)
|
ifeq ($(origin LUAROCKS), command line)
|
||||||
CCFLAGS := $(CFLAGS)
|
ifdef FCOV
|
||||||
|
CCFLAGS := $(patsubst -O%,,$(CFLAGS))
|
||||||
|
else
|
||||||
|
ifdef FDEBUG
|
||||||
|
CCFLAGS := $(patsubst -O%,,$(CFLAGS)) -Og -g
|
||||||
|
else
|
||||||
|
CCFLAGS := $(CFLAGS)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
override CFLAGS := -std=c99 $(CCFLAGS)
|
override CFLAGS := -std=c99 $(CCFLAGS)
|
||||||
|
|
||||||
ifneq ($(filter %gcc,$(CC)),)
|
ifneq ($(filter %gcc,$(CC)),)
|
||||||
@@ -52,10 +73,13 @@ endif
|
|||||||
|
|
||||||
CCFLAGS += -Wall -fvisibility=hidden -Wformat=2 -Wconversion -Wimplicit-fallthrough
|
CCFLAGS += -Wall -fvisibility=hidden -Wformat=2 -Wconversion -Wimplicit-fallthrough
|
||||||
|
|
||||||
|
ifdef FCOV
|
||||||
|
CCFLAGS += $(COVERAGE_FLAGS)
|
||||||
|
endif
|
||||||
ifdef FSANITIZE
|
ifdef FSANITIZE
|
||||||
CCFLAGS += $(SANITIZE_FLAGS)
|
CCFLAGS += $(SANITIZE_FLAGS)
|
||||||
endif
|
endif
|
||||||
ifndef FNOLTO
|
ifdef FLTO
|
||||||
CCFLAGS += $(LTO_FLAGS)
|
CCFLAGS += $(LTO_FLAGS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -69,7 +93,7 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
override CPPFLAGS += -I$(SRCDIR) -I$(SRCDIR)/kiwi -I"$(LUA_INCDIR)"
|
override CPPFLAGS += -I$(SRCDIR) -I$(SRCDIR)/kiwi -I"$(LUA_INCDIR)"
|
||||||
override CXXFLAGS += -std=c++14 -fno-rtti $(CCFLAGS)
|
override CXXFLAGS += -std=c++17 -fno-rtti $(CCFLAGS)
|
||||||
|
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
override CPPFLAGS += -DLUA_BUILD_AS_DLL
|
override CPPFLAGS += -DLUA_BUILD_AS_DLL
|
||||||
@@ -80,11 +104,9 @@ ifdef LUA
|
|||||||
LUA_VERSION ?= $(lastword $(shell "$(LUA)" -e "print(_VERSION)"))
|
LUA_VERSION ?= $(lastword $(shell "$(LUA)" -e "print(_VERSION)"))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifndef LUA_VERSION
|
ifdef LUA_VERSION
|
||||||
LJKIWI_CKIWI := 1
|
ifneq ($(LUA_VERSION),5.1)
|
||||||
else
|
LJKIWI_CFFI ?= 0
|
||||||
ifeq ($(LUA_VERSION),5.1)
|
|
||||||
LJKIWI_CKIWI := 1
|
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -92,8 +114,10 @@ kiwi_lib_srcs := AssocVector.h constraint.h debug.h errors.h expression.h kiwi.h
|
|||||||
row.h shareddata.h solver.h solverimpl.h strength.h symbol.h symbolics.h term.h \
|
row.h shareddata.h solver.h solverimpl.h strength.h symbol.h symbolics.h term.h \
|
||||||
util.h variable.h version.h
|
util.h variable.h version.h
|
||||||
|
|
||||||
objs := luakiwi.o
|
ifneq ($(LJKIWI_LUA),0)
|
||||||
ifdef LJKIWI_CKIWI
|
objs += luakiwi.o
|
||||||
|
endif
|
||||||
|
ifneq ($(LJKIWI_CFFI),0)
|
||||||
objs += ckiwi.o
|
objs += ckiwi.o
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -107,7 +131,7 @@ install:
|
|||||||
$(CP) -f kiwi.lua $(INST_LUADIR)/kiwi.lua
|
$(CP) -f kiwi.lua $(INST_LUADIR)/kiwi.lua
|
||||||
|
|
||||||
mostlyclean:
|
mostlyclean:
|
||||||
$(RM) -f ljkiwi.$(LIB_EXT) $(objs)
|
$(RM) -f ljkiwi.$(LIB_EXT) $(objs) $(objs:.o=.gcda) $(objs:.o=.gcno)
|
||||||
|
|
||||||
clean: mostlyclean
|
clean: mostlyclean
|
||||||
$(RM) -f $(PCH)
|
$(RM) -f $(PCH)
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -9,13 +9,12 @@ ljkiwi - Free LuaJIT FFI and Lua C API kiwi (Cassowary derived) constraint solve
|
|||||||
Kiwi is a reasonably efficient C++ implementation of the Cassowary constraint solving algorithm. It is an implementation of the algorithm as described in the paper ["The Cassowary Linear Arithmetic Constraint Solving Algorithm"](http://www.cs.washington.edu/research/constraints/cassowary/techreports/cassowaryTR.pdf) by Greg J. Badros and Alan Borning. The Kiwi implementation is not based on the original C++ implementation, but is a ground-up reimplementation with performance 10x to 500x faster in typical use.
|
Kiwi is a reasonably efficient C++ implementation of the Cassowary constraint solving algorithm. It is an implementation of the algorithm as described in the paper ["The Cassowary Linear Arithmetic Constraint Solving Algorithm"](http://www.cs.washington.edu/research/constraints/cassowary/techreports/cassowaryTR.pdf) by Greg J. Badros and Alan Borning. The Kiwi implementation is not based on the original C++ implementation, but is a ground-up reimplementation with performance 10x to 500x faster in typical use.
|
||||||
Cassowary constraint solving is a technique that is particularly well suited to user interface layout. It is the algorithm Apple uses for iOS and OS X Auto Layout.
|
Cassowary constraint solving is a technique that is particularly well suited to user interface layout. It is the algorithm Apple uses for iOS and OS X Auto Layout.
|
||||||
|
|
||||||
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.
|
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 and they appear mostly unmaintained.
|
||||||
Since the C++ Kiwi library is well tested and widely used it was simpler to provide a LuaJIT FFI wrapper. There is also a Lua C API binding with support for 5.1 through 5.4.
|
Since the C++ Kiwi library is well tested, it was simpler to provide a LuaJIT FFI wrapper. Now, there is also a Lua C API binding with support for 5.1 through 5.4, and since
|
||||||
|
in most common use cases all the heavy lifting is done in the library, there is usually
|
||||||
|
no practical perfomance difference between the two.
|
||||||
This package has no dependencies other than a supported C++14 compiler to compile the included Kiwi library and a small C wrapper.
|
This package has no dependencies other than a supported C++14 compiler 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.
|
|
||||||
|
|
||||||
The wrapper is quite close to the Kiwi C++/Python port with a few naming changes.
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@@ -91,4 +90,4 @@ print(right_edge:value()) -- 500
|
|||||||
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.
|
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
|
## Documentation
|
||||||
WIP - However the API is fully annotated and will work with lua-language-server. Documentation can also be generated with lua-language-server.
|
The API is fully annotated and will work with lua-language-server. Documentation can also be generated with lua-language-server.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <kiwi/kiwi.h>
|
#include <kiwi/kiwi.h>
|
||||||
|
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
#include <cstdarg>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -326,7 +327,7 @@ bool kiwi_solver_has_constraint(const KiwiSolver* s, KiwiConstraint* constraint)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, KiwiVar* var, double strength) {
|
const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, KiwiVar* var, double strength) {
|
||||||
return wrap_err(s, var, [strength](auto& s, auto&& v) {
|
return wrap_err(s, var, [strength](auto&& s, auto&& v) {
|
||||||
s.addEditVariable(Variable(v), strength);
|
s.addEditVariable(Variable(v), strength);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -373,4 +374,31 @@ char* kiwi_solver_dumps(const KiwiSolver* s) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void kiwi_tuple_init(KiwiTuple* tuple, int count, ...) {
|
||||||
|
if (lk_unlikely(!tuple))
|
||||||
|
return;
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
auto* var = va_arg(args, KiwiVar*);
|
||||||
|
retain_unmanaged(var);
|
||||||
|
tuple->values[i] = &var->m_value;
|
||||||
|
}
|
||||||
|
tuple->count = count;
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void kiwi_tuple_destroy(KiwiTuple* tuple) {
|
||||||
|
if (lk_unlikely(!tuple))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < tuple->count; ++i) {
|
||||||
|
auto* value = const_cast<double*>(tuple->values[i]);
|
||||||
|
release_unmanaged(
|
||||||
|
reinterpret_cast<KiwiVar*>(reinterpret_cast<char*>(value) - offsetof(KiwiVar, m_value))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ typedef struct KiwiExpression {
|
|||||||
#if defined(LJKIWI_LUAJIT_DEF)
|
#if defined(LJKIWI_LUAJIT_DEF)
|
||||||
KiwiTerm terms_[?];
|
KiwiTerm terms_[?];
|
||||||
#elif defined(LJKIWI_USE_FAM_1)
|
#elif defined(LJKIWI_USE_FAM_1)
|
||||||
KiwiTerm terms_[1]; // LuaJIT: struct KiwiTerm terms_[?];
|
KiwiTerm terms_[1];
|
||||||
#else
|
#else
|
||||||
KiwiTerm terms_[];
|
KiwiTerm terms_[];
|
||||||
#endif
|
#endif
|
||||||
@@ -74,8 +74,22 @@ typedef struct KiwiErr {
|
|||||||
bool must_free;
|
bool must_free;
|
||||||
} KiwiErr;
|
} KiwiErr;
|
||||||
|
|
||||||
|
typedef struct KiwiTuple {
|
||||||
|
int count;
|
||||||
|
#if defined(LJKIWI_LUAJIT_DEF)
|
||||||
|
const double* values[?];
|
||||||
|
#elif defined(LJKIWI_USE_FAM_1)
|
||||||
|
const double* values[1];
|
||||||
|
#else
|
||||||
|
const double* values[];
|
||||||
|
#endif
|
||||||
|
} KiwiTuple;
|
||||||
|
|
||||||
struct KiwiSolver;
|
struct KiwiSolver;
|
||||||
|
|
||||||
|
LJKIWI_EXP void kiwi_tuple_init(KiwiTuple* tuple, int count, ...);
|
||||||
|
LJKIWI_EXP void kiwi_tuple_destroy(KiwiTuple* tuple);
|
||||||
|
|
||||||
LJKIWI_EXP KiwiVar* kiwi_var_construct(const char* name);
|
LJKIWI_EXP KiwiVar* kiwi_var_construct(const char* name);
|
||||||
LJKIWI_EXP void kiwi_var_release(KiwiVar* var);
|
LJKIWI_EXP void kiwi_var_release(KiwiVar* var);
|
||||||
LJKIWI_EXP void kiwi_var_retain(KiwiVar* var);
|
LJKIWI_EXP void kiwi_var_retain(KiwiVar* var);
|
||||||
|
|||||||
41
kiwi.lua
41
kiwi.lua
@@ -64,8 +64,16 @@ typedef struct KiwiErr {
|
|||||||
bool must_free;
|
bool must_free;
|
||||||
} KiwiErr;
|
} KiwiErr;
|
||||||
|
|
||||||
|
typedef struct KiwiTuple {
|
||||||
|
int count;
|
||||||
|
const double* values[?];
|
||||||
|
} KiwiTuple;
|
||||||
|
|
||||||
struct KiwiSolver;
|
struct KiwiSolver;
|
||||||
|
|
||||||
|
void kiwi_tuple_init(KiwiTuple* tuple, int count, ...);
|
||||||
|
void kiwi_tuple_destroy(KiwiTuple* tuple);
|
||||||
|
|
||||||
KiwiVar* kiwi_var_construct(const char* name);
|
KiwiVar* kiwi_var_construct(const char* name);
|
||||||
void kiwi_var_release(KiwiVar* var);
|
void kiwi_var_release(KiwiVar* var);
|
||||||
void kiwi_var_retain(KiwiVar* var);
|
void kiwi_var_retain(KiwiVar* var);
|
||||||
@@ -175,6 +183,7 @@ function kiwi.is_var(o)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local Term = ffi.typeof("struct KiwiTerm") --[[@as kiwi.Term]]
|
local Term = ffi.typeof("struct KiwiTerm") --[[@as kiwi.Term]]
|
||||||
|
local SIZEOF_TERM = assert(ffi.sizeof(Term))
|
||||||
kiwi.Term = Term
|
kiwi.Term = Term
|
||||||
|
|
||||||
function kiwi.is_term(o)
|
function kiwi.is_term(o)
|
||||||
@@ -195,18 +204,28 @@ function kiwi.is_constraint(o)
|
|||||||
return ffi_istype(Constraint, o)
|
return ffi_istype(Constraint, o)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local Tuple = ffi.typeof("struct KiwiTuple") --[[@as kiwi.Tuple]]
|
||||||
|
kiwi.Tuple = Tuple
|
||||||
|
|
||||||
|
---@class kiwi.Tuple: ffi.cdata*
|
||||||
|
---@field values ffi.cdata*
|
||||||
|
---@field count integer
|
||||||
|
---@overload fun(vars: kiwi.Var[]): kiwi.Tuple
|
||||||
|
ffi.metatype(Tuple, {
|
||||||
|
__new = function(self, vars)
|
||||||
|
local t = ffi_new(self, #vars)
|
||||||
|
ljkiwi.kiwi_tuple_init(t, #vars, unpack(vars))
|
||||||
|
return ffi_gc(t, ljkiwi.kiwi_tuple_destroy)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
---@param expr kiwi.Expression
|
---@param expr kiwi.Expression
|
||||||
---@param var kiwi.Var
|
---@param var kiwi.Var
|
||||||
---@param coeff number?
|
---@param coeff number?
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
local function add_expr_term(expr, var, coeff)
|
local function add_expr_term(expr, var, coeff)
|
||||||
local ret = ffi_gc(ffi_new(Expression, expr.term_count + 1), ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
|
local ret = ffi_gc(ffi_new(Expression, expr.term_count + 1), ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
|
||||||
for i = 0, expr.term_count - 1 do
|
ffi_copy(ret.terms_, expr.terms_, SIZEOF_TERM * expr.term_count)
|
||||||
local st = expr.terms_[i] --[[@as kiwi.Term]]
|
|
||||||
local dt = ret.terms_[i] --[[@as kiwi.Term]]
|
|
||||||
dt.var = st.var
|
|
||||||
dt.coefficient = st.coefficient
|
|
||||||
end
|
|
||||||
local dt = ret.terms_[expr.term_count]
|
local dt = ret.terms_[expr.term_count]
|
||||||
dt.var = var
|
dt.var = var
|
||||||
dt.coefficient = coeff or 1.0
|
dt.coefficient = coeff or 1.0
|
||||||
@@ -282,8 +301,6 @@ local OP_NAMES = {
|
|||||||
EQ = "==",
|
EQ = "==",
|
||||||
}
|
}
|
||||||
|
|
||||||
local SIZEOF_TERM = ffi.sizeof(Term) --[[@as integer]]
|
|
||||||
|
|
||||||
local tmpexpr = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
|
local tmpexpr = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
|
||||||
local tmpexpr_r = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
|
local tmpexpr_r = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
|
||||||
|
|
||||||
@@ -622,13 +639,7 @@ do
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
local function new_expr_constant(expr, constant)
|
local function new_expr_constant(expr, constant)
|
||||||
local ret = ffi_gc(ffi_new(Expression, expr.term_count), ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
|
local ret = ffi_gc(ffi_new(Expression, expr.term_count), ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
|
||||||
|
ffi_copy(ret.terms_, expr.terms_, SIZEOF_TERM * expr.term_count)
|
||||||
for i = 0, expr.term_count - 1 do
|
|
||||||
local dt = ret.terms_[i] --[[@as kiwi.Term]]
|
|
||||||
local st = expr.terms_[i] --[[@as kiwi.Term]]
|
|
||||||
dt.var = st.var
|
|
||||||
dt.coefficient = st.coefficient
|
|
||||||
end
|
|
||||||
ret.constant = constant
|
ret.constant = constant
|
||||||
ret.term_count = expr.term_count
|
ret.term_count = expr.term_count
|
||||||
ljkiwi.kiwi_expression_retain(ret)
|
ljkiwi.kiwi_expression_retain(ret)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ public:
|
|||||||
double value() const { return m_value; }
|
double value() const { return m_value; }
|
||||||
void setValue(double value) { m_value = value; }
|
void setValue(double value) { m_value = value; }
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
double m_value;
|
double m_value;
|
||||||
|
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ KiwiTerm* term_new(lua_State* L) {
|
|||||||
|
|
||||||
inline KiwiExpression* expr_new(lua_State* L, int nterms) {
|
inline KiwiExpression* expr_new(lua_State* L, int nterms) {
|
||||||
auto* expr = static_cast<KiwiExpression*>(lua_newuserdata(L, KiwiExpression::sz(nterms)));
|
auto* expr = static_cast<KiwiExpression*>(lua_newuserdata(L, KiwiExpression::sz(nterms)));
|
||||||
|
expr->term_count = 0;
|
||||||
expr->owner = nullptr;
|
expr->owner = nullptr;
|
||||||
push_type(L, EXPR);
|
push_type(L, EXPR);
|
||||||
lua_setmetatable(L, -2);
|
lua_setmetatable(L, -2);
|
||||||
@@ -265,7 +266,8 @@ inline ConstraintData* constraint_new(
|
|||||||
push_type(L, CONSTRAINT);
|
push_type(L, CONSTRAINT);
|
||||||
lua_setmetatable(L, -2);
|
lua_setmetatable(L, -2);
|
||||||
|
|
||||||
if (lk_unlikely(!(*c = kiwi_constraint_new(lhs, rhs, op, strength)))) {
|
*c = kiwi_constraint_new(lhs, rhs, op, strength);
|
||||||
|
if (lk_unlikely(!*c)) {
|
||||||
lua_rawgeti(L, lua_upvalueindex(1), MEM_ERR_MSG);
|
lua_rawgeti(L, lua_upvalueindex(1), MEM_ERR_MSG);
|
||||||
lua_error(L);
|
lua_error(L);
|
||||||
}
|
}
|
||||||
@@ -929,9 +931,8 @@ int lkiwi_expr_new(lua_State* L) {
|
|||||||
|
|
||||||
auto* expr = expr_new(L, nterms);
|
auto* expr = expr_new(L, nterms);
|
||||||
expr->constant = constant;
|
expr->constant = constant;
|
||||||
expr->term_count = nterms;
|
|
||||||
|
|
||||||
for (int i = 0; i < nterms; i++) {
|
for (int i = 0; i < nterms; ++i, ++expr->term_count) {
|
||||||
const auto* term = get_term(L, i + 2);
|
const auto* term = get_term(L, i + 2);
|
||||||
expr->terms[i].var = retain_unmanaged(term->var);
|
expr->terms[i].var = retain_unmanaged(term->var);
|
||||||
expr->terms[i].coefficient = term->coefficient;
|
expr->terms[i].coefficient = term->coefficient;
|
||||||
|
|||||||
40
rockspecs/kiwi-0.1.1-1.rockspec
Normal file
40
rockspecs/kiwi-0.1.1-1.rockspec
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
rockspec_format = "3.0"
|
||||||
|
package = "kiwi"
|
||||||
|
version = "0.1.1-1"
|
||||||
|
source = {
|
||||||
|
url = "git+https://github.com/jkl1337/ljkiwi",
|
||||||
|
tag = "v0.1.1",
|
||||||
|
}
|
||||||
|
description = {
|
||||||
|
summary = "LuaJIT FFI and Lua binding for the Kiwi constraint solver.",
|
||||||
|
detailed = [[
|
||||||
|
kiwi is a LuaJIT FFI and Lua binding for the Kiwi constraint solver. Kiwi is a fast
|
||||||
|
implementation of the Cassowary constraint solving algorithm. kiwi provides
|
||||||
|
reasonably efficient bindings using the LuaJIT FFI and convential Lua C bindings.]],
|
||||||
|
license = "MIT",
|
||||||
|
issues_url = "https://github.com/jkl1337/ljkiwi/issues",
|
||||||
|
maintainer = "John Luebs",
|
||||||
|
}
|
||||||
|
dependencies = {
|
||||||
|
"lua >= 5.1",
|
||||||
|
}
|
||||||
|
|
||||||
|
build = {
|
||||||
|
type = "make",
|
||||||
|
build_variables = {
|
||||||
|
LUAROCKS = "1",
|
||||||
|
LUA = "$(LUA)",
|
||||||
|
CFLAGS = "$(CFLAGS)",
|
||||||
|
LUA_INCDIR = "$(LUA_INCDIR)",
|
||||||
|
LUA_LIBDIR = "$(LUA_LIBDIR)",
|
||||||
|
LUALIB = "$(LUALIB)",
|
||||||
|
LIBFLAG = "$(LIBFLAG)",
|
||||||
|
LIB_EXT = "$(LIB_EXTENSION)",
|
||||||
|
OBJ_EXT = "$(OBJ_EXTENSION)",
|
||||||
|
},
|
||||||
|
install_variables = {
|
||||||
|
INST_LIBDIR = "$(LIBDIR)",
|
||||||
|
INST_LUADIR = "$(LUADIR)",
|
||||||
|
LIB_EXT = "$(LIB_EXTENSION)",
|
||||||
|
},
|
||||||
|
}
|
||||||
240
spec/expression_spec.lua
Normal file
240
spec/expression_spec.lua
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
expose("module", function()
|
||||||
|
require("kiwi")
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("Expression", function()
|
||||||
|
local kiwi = require("kiwi")
|
||||||
|
local LUA_VERSION = tonumber(_VERSION:match("%d+%.%d+"))
|
||||||
|
|
||||||
|
it("construction", function()
|
||||||
|
local v = kiwi.Var("foo")
|
||||||
|
local v2 = kiwi.Var("bar")
|
||||||
|
local v3 = kiwi.Var("aux")
|
||||||
|
local e1 = kiwi.Expression(0, v * 1, v2 * 2, v3 * 3)
|
||||||
|
local e2 = kiwi.Expression(10, v * 1, v2 * 2, v3 * 3)
|
||||||
|
|
||||||
|
local constants = { 0, 10 }
|
||||||
|
for i, e in ipairs({ e1, e2 }) do
|
||||||
|
assert.equal(constants[i], e.constant)
|
||||||
|
local terms = e:terms()
|
||||||
|
assert.equal(3, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(1.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(2.0, terms[2].coefficient)
|
||||||
|
assert.equal(v3, terms[3].var)
|
||||||
|
assert.equal(3.0, terms[3].coefficient)
|
||||||
|
end
|
||||||
|
|
||||||
|
if LUA_VERSION <= 5.2 then
|
||||||
|
assert.equal("1 foo + 2 bar + 3 aux + 10", tostring(e2))
|
||||||
|
else
|
||||||
|
assert.equal("1.0 foo + 2.0 bar + 3.0 aux + 10.0", tostring(e2))
|
||||||
|
end
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
kiwi.Expression(0, 0, v2 * 2, v3 * 3)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("method", function()
|
||||||
|
local v, t, e
|
||||||
|
before_each(function()
|
||||||
|
v = kiwi.Var("foo")
|
||||||
|
v:set(42)
|
||||||
|
t = kiwi.Term(v, 10)
|
||||||
|
e = t + 5
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("has value", function()
|
||||||
|
v:set(42)
|
||||||
|
assert.equal(425, e:value())
|
||||||
|
v:set(87)
|
||||||
|
assert.equal(875, e:value())
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("can be copied", function()
|
||||||
|
local e2 = e:copy()
|
||||||
|
assert.equal(e.constant, e2.constant)
|
||||||
|
local t1, t2 = e:terms(), e2:terms()
|
||||||
|
assert.equal(#t1, #t2)
|
||||||
|
for i = 1, #t1 do
|
||||||
|
assert.equal(t1[i].var, t2[i].var)
|
||||||
|
assert.equal(t1[i].coefficient, t2[i].coefficient)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("neg", function()
|
||||||
|
local neg = -e --[[@as kiwi.Expression]]
|
||||||
|
assert.True(kiwi.is_expression(neg))
|
||||||
|
local terms = neg:terms()
|
||||||
|
assert.equal(1, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(-10.0, terms[1].coefficient)
|
||||||
|
assert.equal(-5, neg.constant)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("bin op", function()
|
||||||
|
local v2, t2, e2
|
||||||
|
before_each(function()
|
||||||
|
v2 = kiwi.Var("bar")
|
||||||
|
t2 = kiwi.Term(v2)
|
||||||
|
e2 = v2 - 10
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("mul", function()
|
||||||
|
for _, prod in ipairs({ e * 2.0, 2 * e }) do
|
||||||
|
assert.True(kiwi.is_expression(prod))
|
||||||
|
local terms = prod:terms()
|
||||||
|
assert.equal(1, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(20.0, terms[1].coefficient)
|
||||||
|
assert.equal(10, prod.constant)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
local _ = e * v
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("div", function()
|
||||||
|
local quot = e / 2.0
|
||||||
|
assert.True(kiwi.is_expression(quot))
|
||||||
|
local terms = quot:terms()
|
||||||
|
assert.equal(1, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(5.0, terms[1].coefficient)
|
||||||
|
assert.equal(2.5, quot.constant)
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
local _ = e / v2
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("add", function()
|
||||||
|
for _, sum in ipairs({ e + 2.0, 2 + e }) do
|
||||||
|
assert.True(kiwi.is_expression(sum))
|
||||||
|
assert.equal(7.0, sum.constant)
|
||||||
|
|
||||||
|
local terms = sum:terms()
|
||||||
|
assert.equal(1, #terms)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
end
|
||||||
|
|
||||||
|
local sum = e + v2
|
||||||
|
assert.True(kiwi.is_expression(sum))
|
||||||
|
assert.equal(5, sum.constant)
|
||||||
|
local terms = sum:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
sum = e + t2
|
||||||
|
assert.True(kiwi.is_expression(sum))
|
||||||
|
assert.equal(5, sum.constant)
|
||||||
|
terms = sum:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
sum = e + e2
|
||||||
|
assert.True(kiwi.is_expression(sum))
|
||||||
|
assert.equal(-5, sum.constant)
|
||||||
|
terms = sum:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
local _ = t + "foo"
|
||||||
|
end)
|
||||||
|
assert.error(function()
|
||||||
|
local _ = t + {}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("sub", function()
|
||||||
|
local constants = { 3, -3 }
|
||||||
|
for i, diff in ipairs({ e - 2.0, 2 - e }) do
|
||||||
|
local constant = constants[i]
|
||||||
|
assert.True(kiwi.is_expression(diff))
|
||||||
|
assert.equal(constant, diff.constant)
|
||||||
|
|
||||||
|
local terms = diff:terms()
|
||||||
|
assert.equal(1, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(constant < 0 and -10.0 or 10.0, terms[1].coefficient)
|
||||||
|
end
|
||||||
|
|
||||||
|
local diff = e - v2
|
||||||
|
assert.True(kiwi.is_expression(diff))
|
||||||
|
assert.equal(5, diff.constant)
|
||||||
|
local terms = diff:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(-1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
diff = e - t2
|
||||||
|
assert.True(kiwi.is_expression(diff))
|
||||||
|
assert.equal(5, diff.constant)
|
||||||
|
terms = diff:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(-1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
diff = e - e2
|
||||||
|
assert.True(kiwi.is_expression(diff))
|
||||||
|
assert.equal(15, diff.constant)
|
||||||
|
terms = diff:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(-1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
local _ = e - "foo"
|
||||||
|
end)
|
||||||
|
assert.error(function()
|
||||||
|
local _ = e - {}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("constraint expr op expr", function()
|
||||||
|
local ops = { "LE", "EQ", "GE" }
|
||||||
|
for i, meth in ipairs({ "le", "eq", "ge" }) do
|
||||||
|
local c = e[meth](e, e2)
|
||||||
|
assert.True(kiwi.is_constraint(c))
|
||||||
|
|
||||||
|
local expr = c:expression()
|
||||||
|
local terms = expr:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
|
||||||
|
-- order can be randomized due to use of map
|
||||||
|
if terms[1].var ~= v then
|
||||||
|
terms[1], terms[2] = terms[2], terms[1]
|
||||||
|
end
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(-1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
assert.equal(15, expr.constant)
|
||||||
|
assert.equal(ops[i], c:op())
|
||||||
|
assert.equal(kiwi.strength.REQUIRED, c:strength())
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
245
spec/term_spec.lua
Normal file
245
spec/term_spec.lua
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
expose("module", function()
|
||||||
|
require("kiwi")
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("Term", function()
|
||||||
|
local kiwi = require("kiwi")
|
||||||
|
local LUA_VERSION = tonumber(_VERSION:match("%d+%.%d+"))
|
||||||
|
|
||||||
|
it("construction", function()
|
||||||
|
local v = kiwi.Var("foo")
|
||||||
|
local t = kiwi.Term(v)
|
||||||
|
assert.equal(v, t.var)
|
||||||
|
assert.equal(1.0, t.coefficient)
|
||||||
|
|
||||||
|
t = kiwi.Term(v, 100)
|
||||||
|
assert.equal(v, t.var)
|
||||||
|
assert.equal(100, t.coefficient)
|
||||||
|
|
||||||
|
if LUA_VERSION <= 5.2 then
|
||||||
|
assert.equal("100 foo", tostring(t))
|
||||||
|
else
|
||||||
|
assert.equal("100.0 foo", tostring(t))
|
||||||
|
end
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
kiwi.Term("")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("method", function()
|
||||||
|
local v, v2, t, t2
|
||||||
|
|
||||||
|
before_each(function()
|
||||||
|
v = kiwi.Var("foo")
|
||||||
|
t = kiwi.Term(v, 10)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("has value", function()
|
||||||
|
v:set(42)
|
||||||
|
assert.equal(420, t:value())
|
||||||
|
v:set(87)
|
||||||
|
assert.equal(870, t:value())
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("has toexpr", function()
|
||||||
|
local e = t:toexpr()
|
||||||
|
assert.True(kiwi.is_expression(e))
|
||||||
|
assert.equal(0, e.constant)
|
||||||
|
local terms = e:terms()
|
||||||
|
assert.equal(1, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("neg", function()
|
||||||
|
local neg = -t --[[@as kiwi.Term]]
|
||||||
|
assert.True(kiwi.is_term(neg))
|
||||||
|
assert.equal(v, neg.var)
|
||||||
|
assert.equal(-10, neg.coefficient)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("bin op", function()
|
||||||
|
before_each(function()
|
||||||
|
v2 = kiwi.Var("bar")
|
||||||
|
t2 = kiwi.Term(v2)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("mul", function()
|
||||||
|
for _, prod in ipairs({ t * 2.0, 2 * t }) do
|
||||||
|
assert.True(kiwi.is_term(prod))
|
||||||
|
assert.equal(v, prod.var)
|
||||||
|
assert.equal(20, prod.coefficient)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
local _ = t * v
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("div", function()
|
||||||
|
local quot = t / 2.0
|
||||||
|
assert.True(kiwi.is_term(quot))
|
||||||
|
assert.equal(v, quot.var)
|
||||||
|
assert.equal(5.0, quot.coefficient)
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
local _ = v / v2
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("add", function()
|
||||||
|
for _, sum in ipairs({ t + 2.0, 2 + t }) do
|
||||||
|
assert.True(kiwi.is_expression(sum))
|
||||||
|
assert.equal(2.0, sum.constant)
|
||||||
|
|
||||||
|
local terms = sum:terms()
|
||||||
|
assert.equal(1, #terms)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
end
|
||||||
|
|
||||||
|
local sum = t + v2
|
||||||
|
assert.True(kiwi.is_expression(sum))
|
||||||
|
assert.equal(0, sum.constant)
|
||||||
|
local terms = sum:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
sum = t + t2
|
||||||
|
assert.True(kiwi.is_expression(sum))
|
||||||
|
assert.equal(0, sum.constant)
|
||||||
|
terms = sum:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
local t3 = kiwi.Term(v2, 20)
|
||||||
|
sum = t3 + sum
|
||||||
|
assert.True(kiwi.is_expression(sum))
|
||||||
|
assert.equal(0, sum.constant)
|
||||||
|
terms = sum:terms()
|
||||||
|
assert.equal(3, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(1.0, terms[2].coefficient)
|
||||||
|
assert.equal(v2, terms[3].var)
|
||||||
|
assert.equal(20.0, terms[3].coefficient)
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
local _ = t + "foo"
|
||||||
|
end)
|
||||||
|
assert.error(function()
|
||||||
|
local _ = t + {}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("sub", function()
|
||||||
|
local constants = { -2, 2 }
|
||||||
|
for i, diff in ipairs({ t - 2.0, 2 - t }) do
|
||||||
|
local constant = constants[i]
|
||||||
|
assert.True(kiwi.is_expression(diff))
|
||||||
|
assert.equal(constant, diff.constant)
|
||||||
|
|
||||||
|
local terms = diff:terms()
|
||||||
|
assert.equal(1, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(constant < 0 and 10.0 or -10.0, terms[1].coefficient)
|
||||||
|
end
|
||||||
|
|
||||||
|
local diff = t - v2
|
||||||
|
assert.True(kiwi.is_expression(diff))
|
||||||
|
assert.equal(0, diff.constant)
|
||||||
|
local terms = diff:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(-1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
diff = t - t2
|
||||||
|
assert.True(kiwi.is_expression(diff))
|
||||||
|
assert.equal(0, diff.constant)
|
||||||
|
terms = diff:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(-1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
local t3 = kiwi.Term(v2, 20)
|
||||||
|
diff = t3 - diff
|
||||||
|
assert.True(kiwi.is_expression(diff))
|
||||||
|
assert.equal(0, diff.constant)
|
||||||
|
terms = diff:terms()
|
||||||
|
assert.equal(3, #terms)
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(-10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(1.0, terms[2].coefficient)
|
||||||
|
assert.equal(v2, terms[3].var)
|
||||||
|
assert.equal(20.0, terms[3].coefficient)
|
||||||
|
|
||||||
|
assert.error(function()
|
||||||
|
local _ = t - "foo"
|
||||||
|
end)
|
||||||
|
assert.error(function()
|
||||||
|
local _ = t - {}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("constraint term op expr", function()
|
||||||
|
local ops = { "LE", "EQ", "GE" }
|
||||||
|
for i, meth in ipairs({ "le", "eq", "ge" }) do
|
||||||
|
local c = t[meth](t, v2 + 1)
|
||||||
|
assert.True(kiwi.is_constraint(c))
|
||||||
|
|
||||||
|
local e = c:expression()
|
||||||
|
local terms = e:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
|
||||||
|
-- order can be randomized due to use of map
|
||||||
|
if terms[1].var ~= v then
|
||||||
|
terms[1], terms[2] = terms[2], terms[1]
|
||||||
|
end
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(-1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
assert.equal(-1, e.constant)
|
||||||
|
assert.equal(ops[i], c:op())
|
||||||
|
assert.equal(kiwi.strength.REQUIRED, c:strength())
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("constraint term op term", function()
|
||||||
|
for i, meth in ipairs({ "le", "eq", "ge" }) do
|
||||||
|
local c = t[meth](t, t2)
|
||||||
|
assert.True(kiwi.is_constraint(c))
|
||||||
|
|
||||||
|
local e = c:expression()
|
||||||
|
local terms = e:terms()
|
||||||
|
assert.equal(2, #terms)
|
||||||
|
|
||||||
|
-- order can be randomized due to use of map
|
||||||
|
if terms[1].var ~= v then
|
||||||
|
terms[1], terms[2] = terms[2], terms[1]
|
||||||
|
end
|
||||||
|
assert.equal(v, terms[1].var)
|
||||||
|
assert.equal(10.0, terms[1].coefficient)
|
||||||
|
assert.equal(v2, terms[2].var)
|
||||||
|
assert.equal(-1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
assert.equal(0, e.constant)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
@@ -127,6 +127,8 @@ describe("Var", function()
|
|||||||
assert.equal(v2, terms[2].var)
|
assert.equal(v2, terms[2].var)
|
||||||
assert.equal(-1.0, terms[2].coefficient)
|
assert.equal(-1.0, terms[2].coefficient)
|
||||||
|
|
||||||
|
-- TODO: terms and expressions
|
||||||
|
|
||||||
assert.error(function()
|
assert.error(function()
|
||||||
local _ = v - "foo"
|
local _ = v - "foo"
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user