Skip to content

Commit

Permalink
Adds FeatureModelConstraint iterator and bindings (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
s9latimm authored Apr 19, 2023
1 parent 0cdbc7f commit ac5dabc
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 47 deletions.
8 changes: 4 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ endif()

find_program(
VARA_FEATURE_RUN_CLANG_TIDY
NAMES run-clang-tidy-12.py
run-clang-tidy-12
NAMES run-clang-tidy-14.py
run-clang-tidy-14
run-clang-tidy.py
run-clang-tidy
clang-tidy-12
clang-tidy-14
clang-tidy
PATHS /usr/lib/llvm/*/share/clang/ /usr/bin/
PATHS /usr/bin/ /usr/lib/llvm/*/share/clang/
)

add_custom_target(
Expand Down
9 changes: 9 additions & 0 deletions bindings/python/tests/TEST_INPUTS/example_feature_model.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,13 @@
<values>1;5;9</values>
</configurationOption>
</numericOptions>
<booleanConstraints>
<constraint>(A | B)</constraint>
</booleanConstraints>
<nonBooleanConstraints>
<constraint>(A + B)</constraint>
</nonBooleanConstraints>
<mixedConstraints>
<constraint req="all" exprKind="pos">((A * B) = 0)</constraint>
</mixedConstraints>
</vm>
32 changes: 32 additions & 0 deletions bindings/python/tests/constraint/test_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import unittest

import vara_feature.constraint as constraint
import vara_feature.feature_model as FM


class TestConstraintBuilder(unittest.TestCase):
Expand All @@ -30,3 +31,34 @@ def test_full(self):
cb.feature("D").closePar().closePar().closePar().closePar().implies()
cb.feature("E").closePar()
self.assertEqual("((A => (!!B => !(!C => !!D))) => E)", str(cb.build()))


class TestConstraints(unittest.TestCase):
""" Test Constraints functionality. """

def test_boolean_constraints(self):
""" Check if we can build boolean constraints. """
cb = constraint.ConstraintBuilder()
cb.feature("A").lOr().feature("B")
c = FM.BooleanConstraint(cb.build())
self.assertEqual(str(c), "(A | B)")
self.assertEqual(str(c.constraint.clone()), "(A | B)")

def test_non_boolean_constraints(self):
""" Check if we can build non-boolean constraints. """
cb = constraint.ConstraintBuilder()
cb.feature("A").add().feature("B")
c = FM.NonBooleanConstraint(cb.build())
self.assertIsNotNone(c.constraint)
self.assertEqual(str(c), "(A + B)")
self.assertEqual(str(c.constraint.clone()), "(A + B)")

def test_mixed_constraints(self):
""" Check if we can build mixed constraints. """
cb = constraint.ConstraintBuilder()
cb.feature("A").multiply().feature("B").equal().constant(0)
c = FM.MixedConstraint(cb.build(), FM.Req.ALL, FM.ExprKind.POS)
self.assertEqual(c.req, FM.Req.ALL)
self.assertEqual(c.exprKind, FM.ExprKind.POS)
self.assertEqual(str(c), "((A * B) = 0)")
self.assertEqual(str(c.constraint.clone()), "((A * B) = 0)")
17 changes: 17 additions & 0 deletions bindings/python/tests/feature_model/test_feature_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,23 @@ def test_merge_models(self):
self.assertEqual(
test_merged.get_feature("Z").parent().name.str(), "root")

def test_boolean_constraints(self):
""" Check if we can access boolean constraints. """
*_, c = iter(self.fm.booleanConstraints)
self.assertEqual(str(c), "(A | B)")

def test_non_boolean_constraints(self):
""" Check if we can access non-boolean constraints. """
*_, c = iter(self.fm.nonBooleanConstraints)
self.assertEqual(str(c), "(A + B)")

def test_mixed_constraints(self):
""" Check if we can access mixed constraints. """
*_, c = iter(self.fm.mixedConstraints)
self.assertEqual(c.req, FM.Req.ALL)
self.assertEqual(c.exprKind, FM.ExprKind.POS)
self.assertEqual(str(c), "((A * B) = 0)")


class TestFeatureModelModifications(unittest.TestCase):
"""
Expand Down
3 changes: 2 additions & 1 deletion bindings/python/vara-feature/pybind_Constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ namespace py = pybind11;

void init_constraint_module_constraint(py::module &M) {
py::class_<vf::Constraint>(M, "Constraint")
.def("__str__", &vf::Constraint::toString);
.def("__str__", &vf::Constraint::toString)
.def("clone", &vf::Constraint::clone);
}

void init_constraint_module_constraint_builder(py::module &M) {
Expand Down
59 changes: 58 additions & 1 deletion bindings/python/vara-feature/pybind_FeatureModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,46 @@ namespace vf = vara::feature;
namespace py = pybind11;

void init_feature_model_module(py::module &M) {
py::class_<vf::FeatureModel::BooleanConstraint>(M, "BooleanConstraint")
.def(py::init([](vf::Constraint &C) {
return std::make_unique<vf::FeatureModel::BooleanConstraint>(C.clone());
}))
.def("__str__", &vf::FeatureModel::BooleanConstraint::toString)
.def_property_readonly("constraint",
&vf::FeatureModel::BooleanConstraint::constraint,
R"pbdoc(Get underlying constraint.)pbdoc");

py::class_<vf::FeatureModel::NonBooleanConstraint>(M, "NonBooleanConstraint")
.def(py::init([](vf::Constraint &C) {
return std::make_unique<vf::FeatureModel::NonBooleanConstraint>(
C.clone());
}))
.def("__str__", &vf::FeatureModel::NonBooleanConstraint::toString)
.def_property_readonly(
"constraint", &vf::FeatureModel::NonBooleanConstraint::constraint,
R"pbdoc(Get underlying constraint.)pbdoc");

py::enum_<vf::FeatureModel::MixedConstraint::Req>(M, "Req")
.value("ALL", vf::FeatureModel::MixedConstraint::Req::ALL)
.value("NONE", vf::FeatureModel::MixedConstraint::Req::NONE);
py::enum_<vf::FeatureModel::MixedConstraint::ExprKind>(M, "ExprKind")
.value("POS", vf::FeatureModel::MixedConstraint::ExprKind::POS)
.value("NEG", vf::FeatureModel::MixedConstraint::ExprKind::NEG);
py::class_<vf::FeatureModel::MixedConstraint>(M, "MixedConstraint")
.def(py::init([](vf::Constraint &C,
vf::FeatureModel::MixedConstraint::Req R,
vf::FeatureModel::MixedConstraint::ExprKind E) {
return std::make_unique<vf::FeatureModel::MixedConstraint>(C.clone(), R,
E);
}))
.def("__str__", &vf::FeatureModel::MixedConstraint::toString)
.def_property_readonly("req", &vf::FeatureModel::MixedConstraint::req)
.def_property_readonly("exprKind",
&vf::FeatureModel::MixedConstraint::exprKind)
.def_property_readonly("constraint",
&vf::FeatureModel::MixedConstraint::constraint,
R"pbdoc(Get underlying constraint.)pbdoc");

py::class_<vf::FeatureModel>(M, "FeatureModel")
.def_property_readonly(
"name", &vf::FeatureModel::getName,
Expand Down Expand Up @@ -99,7 +139,24 @@ void init_feature_model_module(py::module &M) {
[](const vf::FeatureModel &FM) {
return py::make_iterator(FM.begin(), FM.end());
},
py::keep_alive<0, 1>());
py::keep_alive<0, 1>())
.def_property_readonly("booleanConstraints",
[](const vf::FeatureModel &FM) {
return py::make_iterator(
FM.booleanConstraints().begin(),
FM.booleanConstraints().end());
})
.def_property_readonly("nonBooleanConstraints",
[](const vf::FeatureModel &FM) {
return py::make_iterator(
FM.nonBooleanConstraints().begin(),
FM.nonBooleanConstraints().end());
})
.def_property_readonly(
"mixedConstraints", [](const vf::FeatureModel &FM) {
return py::make_iterator(FM.mixedConstraints().begin(),
FM.mixedConstraints().end());
});
M.def(
"loadFeatureModel",
[](const std::filesystem::path &Path) {
Expand Down
50 changes: 32 additions & 18 deletions include/vara/Feature/FeatureModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "vara/Feature/Feature.h"
#include "vara/Feature/Relationship.h"
#include "vara/Utils/Result.h"
#include "vara/Utils/UniqueIterator.h"

#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringMap.h"
Expand Down Expand Up @@ -236,12 +237,19 @@ class FeatureModel {
/// \brief Base for different constraint kinds (boolean, non-boolean, etc.).
class FeatureModelConstraint {
public:
FeatureModelConstraint(std::unique_ptr<Constraint> C) : C(std::move(C)) {}
virtual ~FeatureModelConstraint() = default;

[[nodiscard]] Constraint *constraint() { return C.get(); }

[[nodiscard]] Constraint *operator*() { return C.get(); }

[[nodiscard]] virtual std::string toString() const { return C->toString(); }

[[nodiscard]] virtual std::string toHTML() const { return C->toHTML(); }

protected:
FeatureModelConstraint(std::unique_ptr<Constraint> C) : C(std::move(C)) {}

private:
std::unique_ptr<Constraint> C;
};
Expand All @@ -256,12 +264,13 @@ class FeatureModel {
using BooleanConstraintContainerTy =
std::vector<std::unique_ptr<BooleanConstraint>>;
using const_boolean_constraint_iterator =
typename BooleanConstraintContainerTy::const_iterator;
UniqueIterator<const BooleanConstraintContainerTy>;

[[nodiscard]] llvm::iterator_range<const_boolean_constraint_iterator>
booleanConstraints() const {
return llvm::make_range(BooleanConstraints.begin(),
BooleanConstraints.end());
return llvm::make_range(
const_boolean_constraint_iterator(BooleanConstraints.begin()),
const_boolean_constraint_iterator(BooleanConstraints.end()));
}

class NonBooleanConstraint : public FeatureModelConstraint {
Expand All @@ -273,12 +282,13 @@ class FeatureModel {
using NonBooleanConstraintContainerTy =
std::vector<std::unique_ptr<NonBooleanConstraint>>;
using const_non_boolean_constraint_iterator =
typename NonBooleanConstraintContainerTy::const_iterator;
UniqueIterator<const NonBooleanConstraintContainerTy>;

[[nodiscard]] llvm::iterator_range<const_non_boolean_constraint_iterator>
nonBooleanConstraints() const {
return llvm::make_range(NonBooleanConstraints.begin(),
NonBooleanConstraints.end());
return llvm::make_range(
const_non_boolean_constraint_iterator(NonBooleanConstraints.begin()),
const_non_boolean_constraint_iterator(NonBooleanConstraints.end()));
}

class MixedConstraint : public FeatureModelConstraint {
Expand Down Expand Up @@ -306,11 +316,13 @@ class FeatureModel {
using MixedConstraintContainerTy =
std::vector<std::unique_ptr<MixedConstraint>>;
using const_mixed_constraint_iterator =
typename MixedConstraintContainerTy::const_iterator;
UniqueIterator<const MixedConstraintContainerTy>;

[[nodiscard]] llvm::iterator_range<const_mixed_constraint_iterator>
mixedConstraints() const {
return llvm::make_range(MixedConstraints.begin(), MixedConstraints.end());
return llvm::make_range(
const_mixed_constraint_iterator(MixedConstraints.begin()),
const_mixed_constraint_iterator(MixedConstraints.end()));
}

//===--------------------------------------------------------------------===//
Expand Down Expand Up @@ -388,12 +400,13 @@ class FeatureModel {
}

using boolean_constraint_iterator =
typename BooleanConstraintContainerTy::iterator;
UniqueIterator<BooleanConstraintContainerTy>;

[[nodiscard]] llvm::iterator_range<boolean_constraint_iterator>
booleanConstraints() {
return llvm::make_range(BooleanConstraints.begin(),
BooleanConstraints.end());
return llvm::make_range(
boolean_constraint_iterator(BooleanConstraints.begin()),
boolean_constraint_iterator(BooleanConstraints.end()));
}

vara::feature::Constraint *
Expand All @@ -403,25 +416,26 @@ class FeatureModel {
}

using non_boolean_constraint_iterator =
typename NonBooleanConstraintContainerTy::iterator;
UniqueIterator<NonBooleanConstraintContainerTy>;

[[nodiscard]] llvm::iterator_range<non_boolean_constraint_iterator>
nonBooleanConstraints() {
return llvm::make_range(NonBooleanConstraints.begin(),
NonBooleanConstraints.end());
return llvm::make_range(
non_boolean_constraint_iterator(NonBooleanConstraints.begin()),
non_boolean_constraint_iterator(NonBooleanConstraints.end()));
}

vara::feature::Constraint *addConstraint(std::unique_ptr<MixedConstraint> C) {
MixedConstraints.push_back(std::move(C));
return **MixedConstraints.back();
}

using mixed_constraint_iterator =
typename MixedConstraintContainerTy::iterator;
using mixed_constraint_iterator = UniqueIterator<MixedConstraintContainerTy>;

[[nodiscard]] llvm::iterator_range<mixed_constraint_iterator>
mixedConstraints() {
return llvm::make_range(MixedConstraints.begin(), MixedConstraints.end());
return llvm::make_range(mixed_constraint_iterator(MixedConstraints.begin()),
mixed_constraint_iterator(MixedConstraints.end()));
}

//===--------------------------------------------------------------------===//
Expand Down
56 changes: 56 additions & 0 deletions include/vara/Utils/UniqueIterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#ifndef VARA_UTILS_UNIQUEITERATOR_H
#define VARA_UTILS_UNIQUEITERATOR_H

#include <type_traits>

namespace vara {

//===----------------------------------------------------------------------===//
// UniqueIterator Class
//===----------------------------------------------------------------------===//

template <typename ContainerTy,
typename ContainedTy = typename ContainerTy::value_type::element_type,
typename IteratorTy =
typename std::conditional<std::is_const<ContainerTy>::value,
typename ContainerTy::const_iterator,
typename ContainerTy::iterator>::type>
class UniqueIterator {
public:
UniqueIterator(IteratorTy Iterator) : Iterator{Iterator} {}
UniqueIterator(const UniqueIterator &) = default;
UniqueIterator &operator=(const UniqueIterator &) = delete;
UniqueIterator(UniqueIterator &&) noexcept = default;
UniqueIterator &operator=(UniqueIterator &&) = delete;
~UniqueIterator() = default;

ContainedTy *operator*() const { return Iterator->get(); }

ContainedTy *operator->() const { return operator*(); }

UniqueIterator operator++() {
++Iterator;
return *this;
}

UniqueIterator operator++(int) {
auto Iter(*this);
++*this;
return Iter;
}

bool operator==(const UniqueIterator &Other) const {
return Iterator == Other.Iterator;
}

bool operator!=(const UniqueIterator &Other) const {
return !(*this == Other);
}

private:
IteratorTy Iterator;
};

} // namespace vara

#endif // VARA_UTILS_UNIQUEITERATOR_H
Loading

0 comments on commit ac5dabc

Please sign in to comment.