Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gui to edit FeatureModels #112

Open
wants to merge 116 commits into
base: vara-dev
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
49d4adc
Add base graph implementation based on qt example
Sinerum Nov 4, 2022
f2c949e
add function to position the nodes in a tree like structure
Nov 7, 2022
9a5c030
Remove usage of unique pointer because qt does not like that.
Sinerum Nov 7, 2022
e85c7af
add ui
Sinerum Nov 15, 2022
8a89717
build the main gui
Nov 29, 2022
2cd9b06
add Feature Add Dialog
Sinerum Dec 20, 2022
9aa6a9e
add syntax highlighting and feature source highlighting
Sinerum Jan 9, 2023
38f5959
Rewrite the Code for positioning Nodes
Sinerum Jan 17, 2023
1d47e77
Add tree view as alternative for view for feature model.
Sinerum Feb 6, 2023
32c454e
Removes deprecated std::iterator (#97)
vulder Nov 7, 2022
7507cbb
Updates python binding library (#99)
vulder Nov 8, 2022
a255aa4
Move to LLVM 14 (#98)
vulder Nov 9, 2022
fda30dc
Add Relationships Iterator and Sort Methods in Header (#95)
s9latimm Nov 13, 2022
466bcba
Add Output String For Features (#100)
s9latimm Nov 16, 2022
6c1b073
Adds parsing of step functions (#103)
s9latimm Dec 18, 2022
0e48b9c
Adds Revision Range (#101)
s9latimm Dec 19, 2022
72cc0b9
Bugfix in `FeatureModelBuilder::makeRoot` (#104)
vulder Dec 19, 2022
5568161
Display outputstring of feature in treeview
Feb 7, 2023
f89ad52
Extend Feature AddDialog to provide choice of feature type
Sinerum Feb 7, 2023
59b3038
Add contextMenu to FeatureTreeItems
Sinerum Feb 7, 2023
b69fba0
Add Function to get type of added feature
Sinerum Feb 14, 2023
d350add
Add the ability to add sources to features
Sinerum Feb 14, 2023
b893910
Load feature information when selecting feature in treeview
Sinerum Feb 14, 2023
81b1d36
Refactor and add full functionality to feature adding.
Sinerum Feb 15, 2023
626fa78
Allow FeatureAddDialog to take a predefined parent
Feb 16, 2023
e476851
Fix states for numeric value selection
Feb 16, 2023
a1c59fc
Add new features to relations if present, and add context menu option…
Feb 16, 2023
50be685
Add the ability to remove features from the model
Sinerum Feb 22, 2023
e3a13a5
Merge branch 'vara-dev' into f-FeatureGui
Sinerum Feb 22, 2023
8e4b0e3
remove Changes accidentally made by merging
Sinerum Feb 22, 2023
248e928
Apply suggestions from flangformater
Sinerum Feb 22, 2023
e01fd2f
Apply suggestions from formater
Sinerum Feb 22, 2023
ba588a4
revert accidental rename by clion
Sinerum Feb 22, 2023
ea1d7e8
Apply formatting suggestions from code review
Sinerum Feb 23, 2023
7e848fd
move qsourcehighlite to be a submodule instead of copied code
Sinerum Feb 23, 2023
5e90a34
reformat
Sinerum Feb 23, 2023
4df3126
Apply Suggestions from code review
Sinerum Feb 23, 2023
b3b1a2f
remove accidental changes
Sinerum Mar 2, 2023
e84b72c
Fix formating
Sinerum Mar 2, 2023
af8dc58
Let addAction create the action instead of handling that our self's.
Sinerum Mar 2, 2023
70fec0a
Fix typo
Sinerum Mar 2, 2023
ddb71b2
Extract Numeric Feature Creation to its own method for better readabi…
Sinerum Mar 2, 2023
d0f2dd8
Include Utils directly
Sinerum Mar 2, 2023
9fd1257
Add some documentation
Sinerum Mar 2, 2023
6941ea2
Remove Unused Methods
Sinerum Mar 2, 2023
0b191f1
Use dyn_cast correctly
Sinerum Mar 2, 2023
2dc309b
only create new Treeview if we none exits
Sinerum Mar 2, 2023
e0d1dd8
format
Sinerum Mar 2, 2023
3cfb050
make the highlighter a unique pointer
Sinerum Mar 2, 2023
a3f56e8
fix typos
Sinerum Mar 2, 2023
6cd51d7
rework menu for FeatureTreeItems to use a QueuedConnection for remove…
Sinerum Mar 2, 2023
7a3ee48
format
Sinerum Mar 2, 2023
0079bf1
Merge branch 'vara-dev' into f-FeatureGui
Sinerum Mar 2, 2023
0860ac8
format
Sinerum Mar 2, 2023
53aa4f5
add support for Both qt5 and 6
Sinerum Mar 2, 2023
de394e6
Make everything qt5 compatible
Sinerum Mar 2, 2023
82df3c6
Add qt6-dev install to github workflow
Sinerum Mar 2, 2023
c64c18e
Use qt5-dev in github workflow
Sinerum Mar 2, 2023
d1616ff
Allow for qt versions older than 5.15
Sinerum Mar 2, 2023
fee14db
Remove setPlaceHolderText if qt < 5.15
Sinerum Mar 2, 2023
6740805
Fix source location marking
Sinerum Mar 2, 2023
d842541
format
Sinerum Mar 2, 2023
4825b9d
format
Sinerum Mar 2, 2023
26c0d72
Merge branch 'vara-dev' into f-FeatureGui
Sinerum Mar 3, 2023
e6f7e8c
fix lint
Sinerum Mar 3, 2023
097af7b
Merge branch 'vara-dev' into f-FeatureGui
Sinerum Mar 14, 2023
2f244ca
Apply suggestions from code review
Sinerum Mar 14, 2023
0186315
cleanup clang-tidy errors
Sinerum Mar 15, 2023
ddbdf9f
cleanup clang-tidy errors
Sinerum Mar 15, 2023
5d0d0a6
Update include/vara/Feature/FeatureModelTransaction.h
Sinerum Mar 15, 2023
cc6c1ba
cleanup clang-tidy errors
Sinerum Mar 15, 2023
f14a510
write clang tidy overwrite to the correct directory
Sinerum Mar 15, 2023
335acc9
Merge branch 'vara-dev' into f-FeatureGui
Sinerum Apr 3, 2023
18643cf
Update .gitmodules
Sinerum Apr 13, 2023
59d2daa
reformat CMakeLists.txt
Sinerum Apr 17, 2023
2550447
keep asserts to check for correct cast in release build.
Sinerum Apr 17, 2023
dfc752b
keep asserts to check for correct cast in release build.
Sinerum Apr 17, 2023
75ee65a
Replace the asserts for nullpointer with aborts
Sinerum Apr 19, 2023
5cce459
Merge branch 'vara-dev' into f-FeatureGui
Sinerum Apr 24, 2023
d068a82
Apply suggestions from code review
Sinerum May 16, 2023
e7fe6b8
Remove special case for old qt versions
Jun 20, 2023
d344c67
Cleanup
Jun 20, 2023
bb33e0c
Automatically select newly added source files
Jun 20, 2023
56cd7ef
Add Q_Interface declaration to FeatureNode.h
Jun 20, 2023
c4e02e2
Fix Formatting
Jun 20, 2023
12e6cdd
Merge branch 'vara-dev' into f-FeatureGui
Sinerum Jun 20, 2023
004cfaa
add source locations only once to selection
Jun 20, 2023
c38dc49
Merge branch 'vara-dev' into f-FeatureGui
Sinerum Jul 26, 2023
1d74318
Add separator between feature locations
Sinerum Jul 26, 2023
0d3150f
Make graph movable
Sinerum Jul 26, 2023
c4bead2
Check ModelPath before loading FeatureModel
Sinerum Jul 26, 2023
61c923d
Fix spacing and cut off for node labels
Sinerum Jul 27, 2023
0392b58
Fix formatting
Sinerum Jul 27, 2023
061bff1
* Switched to use selection changed option for navigating through fea…
Sep 18, 2023
b141fd3
* Added action to create new feature models
Sep 26, 2023
19b317e
* Improvements to save and added saveAs
Sep 26, 2023
9e00b3f
* Added All Files filter to add source
Sep 26, 2023
b7dd95f
* Added guard to avoid adding empty source range
Sep 26, 2023
b4bb9c5
* Set correct line wrap mode for source code preview
Sep 26, 2023
5f649cd
load Models from cwd
Sinerum Oct 30, 2023
963ce63
do not use smart pointers in graphics view let qGraphics Scene handel…
Sinerum Oct 30, 2023
4e510e7
update tree view when adding features
Sinerum Oct 30, 2023
6b72bbe
Merge branch 'vara-dev' into f-FeatureGui
vulder Nov 17, 2023
6e5614c
Fix clang-tidy exclusion of generated files
Nov 20, 2023
1c016ad
Fix clang warnings
Nov 20, 2023
90ff5a8
Show root feature in treeview
Nov 20, 2023
4ca982c
Merge branch 'vara-dev' into f-FeatureGui
Sinerum Nov 20, 2023
bfc46bc
Fix format
Nov 20, 2023
8a2a93d
dont display data fro empty Feature Tree Item
Nov 21, 2023
6190334
fix clang tidy errors
Nov 21, 2023
2c6c475
fix clang tidy errors
Dec 5, 2023
7c19b0c
move item null check in FeatureTreeItem
Dec 5, 2023
1913c34
Add extra abstraction for "ghost" root item in treeview
Dec 5, 2023
dfad1c1
check nullability after cast to numeric feature
Dec 5, 2023
a3ea757
add context menu functionality to newly added features
Sinerum Nov 17, 2023
7fcc47b
fix repository selection
Sinerum Mar 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Adds parsing of step functions (#103)
resolves se-sic/VaRA#966
s9latimm authored and Simon Friedel committed Feb 7, 2023
commit 6c1b073e15d4bb05818b1891a2809725bf83104e
9 changes: 7 additions & 2 deletions include/vara/Feature/Feature.h
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
#include "vara/Feature/Constraint.h"
#include "vara/Feature/FeatureSourceRange.h"
#include "vara/Feature/FeatureTreeNode.h"
#include "vara/Feature/StepFunction.h"

#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SetVector.h"
@@ -251,13 +252,16 @@ class NumericFeature : public Feature {
NumericFeature(
std::string Name, ValuesVariantType Values, bool Opt = false,
std::vector<FeatureSourceRange> Loc = std::vector<FeatureSourceRange>(),
std::string OutputString = "")
std::string OutputString = "",
std::unique_ptr<StepFunction> Step = nullptr)
: Feature(FeatureKind::FK_NUMERIC, std::move(Name), Opt, std::move(Loc),
std::move(OutputString)),
Values(std::move(Values)) {}
Values(std::move(Values)), Step(std::move(Step)) {}

[[nodiscard]] ValuesVariantType getValues() const { return Values; }

[[nodiscard]] StepFunction *getStepFunction() { return Step.get(); }

[[nodiscard]] string toString() const override;

static bool classof(const Feature *F) {
@@ -266,6 +270,7 @@ class NumericFeature : public Feature {

private:
ValuesVariantType Values;
std::unique_ptr<StepFunction> Step;
};

//===----------------------------------------------------------------------===//
91 changes: 91 additions & 0 deletions include/vara/Feature/StepFunction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#ifndef VARA_FEATURE_STEPFUNCTION_H
#define VARA_FEATURE_STEPFUNCTION_H

#include "llvm/Support/FormatVariadic.h"

#include <cmath>
#include <memory>
#include <variant>

namespace vara::feature {

//===----------------------------------------------------------------------===//
// StepFunction Class
//===----------------------------------------------------------------------===//

class StepFunction {
public:
enum class StepOperation { ADDITION, MULTIPLICATION, EXPONENTIATION };

using OperandVariantType = typename std::variant<std::string, double>;

StepFunction(OperandVariantType LHS, StepOperation Op, OperandVariantType RHS)
: Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {}

StepFunction(StepOperation Op, double RHS) : StepFunction("x", Op, RHS) {}
StepFunction(double LHS, StepOperation Op) : StepFunction(LHS, Op, "x") {}

template <typename T = double>
[[nodiscard]] T next(T Value) {
if (std::holds_alternative<double>(RHS)) {
assert(std::holds_alternative<std::string>(LHS));
switch (Op) {
case StepOperation::ADDITION:
return Value + std::get<double>(RHS);
case StepOperation::MULTIPLICATION:
return Value * std::get<double>(RHS);
case StepOperation::EXPONENTIATION:
return std::pow(Value, std::get<double>(RHS));
}
}
assert(std::holds_alternative<double>(LHS));
switch (Op) {
case StepOperation::ADDITION:
return std::get<double>(LHS) + Value;
case StepOperation::MULTIPLICATION:
return std::get<double>(LHS) * Value;
case StepOperation::EXPONENTIATION:
return std::pow(std::get<double>(LHS), Value);
}
}

[[nodiscard]] double operator()(double Value) { return next(Value); }

[[nodiscard]] std::string toString() const {
if (std::holds_alternative<double>(RHS)) {
assert(std::holds_alternative<std::string>(LHS));
switch (Op) {
case StepOperation::ADDITION:
return llvm::formatv("{0} + {1}", std::get<std::string>(LHS),
std::get<double>(RHS));
case StepOperation::MULTIPLICATION:
return llvm::formatv("{0} * {1}", std::get<std::string>(LHS),
std::get<double>(RHS));
case StepOperation::EXPONENTIATION:
return llvm::formatv("{0} ^ {1}", std::get<std::string>(LHS),
std::get<double>(RHS));
}
}
assert(std::holds_alternative<double>(LHS));
switch (Op) {
case StepOperation::ADDITION:
return llvm::formatv("{0} + {1}", std::get<double>(LHS),
std::get<std::string>(RHS));
case StepOperation::MULTIPLICATION:
return llvm::formatv("{0} * {1}", std::get<double>(LHS),
std::get<std::string>(RHS));
case StepOperation::EXPONENTIATION:
return llvm::formatv("{0} ^ {1}", std::get<double>(LHS),
std::get<std::string>(RHS));
}
}

private:
StepOperation Op;
OperandVariantType LHS;
OperandVariantType RHS;
};

} // namespace vara::feature

#endif // VARA_FEATURE_STEPFUNCTION_H
322 changes: 322 additions & 0 deletions include/vara/Feature/StepFunctionParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
#ifndef VARA_FEATURE_STEPFUNCTIONPARSER_H
#define VARA_FEATURE_STEPFUNCTIONPARSER_H

#include "vara/Feature/StepFunction.h"

#include <llvm/ADT/StringExtras.h>

#include <deque>

namespace vara::feature {

//===----------------------------------------------------------------------===//
// StepFunctionToken Class
//===----------------------------------------------------------------------===//

class StepFunctionToken {
public:
enum class TokenKind {
IDENTIFIER,
NUMBER,
WHITESPACE,
PLUS,
STAR,
CARET,
END_OF_FILE,
ERROR
};

StepFunctionToken(TokenKind Kind) : Kind(Kind) {}
StepFunctionToken(TokenKind Kind, const std::string &Value)
: Kind(Kind), Value(Value) {}
virtual ~StepFunctionToken() = default;

[[nodiscard]] TokenKind getKind() const { return Kind; };

[[nodiscard]] llvm::Optional<const std::string> getValue() const {
return Value;
}

private:
const TokenKind Kind;
const llvm::Optional<const std::string> Value{llvm::None};
};

//===----------------------------------------------------------------------===//
// StepFunctionLexer Class
//===----------------------------------------------------------------------===//

class StepFunctionLexer {
public:
using TokenListTy = std::deque<StepFunctionToken>;

explicit StepFunctionLexer(std::string Cnt) : Cnt(std::move(Cnt)) {}

[[nodiscard]] TokenListTy tokenize() {
TokenListTy TokenList;

for (llvm::StringRef Str = Cnt; !Str.empty();) {
auto Result = munch(Str);
TokenList.push_back(Result.first);
if (auto Kind = Result.first.getKind();
Kind == StepFunctionToken::TokenKind::END_OF_FILE ||
Kind == StepFunctionToken::TokenKind::ERROR) {
break;
}
Str = Str.drop_front(Result.second);
}

// ensure last token is always either EOF or an error
if (auto Kind = TokenList.back().getKind();
Kind != StepFunctionToken::TokenKind::END_OF_FILE &&
Kind != StepFunctionToken::TokenKind::ERROR) {
TokenList.push_back(
StepFunctionToken(StepFunctionToken::TokenKind::END_OF_FILE));
}

return TokenList;
}

private:
using ResultTy = std::pair<StepFunctionToken, int>;

static ResultTy munch(const llvm::StringRef Str) {
if (('a' <= Str.front() && Str.front() <= 'z') ||
('A' <= Str.front() && Str.front() <= 'Z')) {
return munchIdentifier(Str);
}
if (('0' <= Str.front() && Str.front() <= '9') || Str.front() == '.') {
return munchNumber(Str);
}

switch (Str.front()) {
case EOF:
case '\0':
return {StepFunctionToken(StepFunctionToken::TokenKind::END_OF_FILE), 1};
case ' ':
case '\t':
case '\r':
case '\n':
return munchWhitespace(Str);
case '+':
return {StepFunctionToken(StepFunctionToken::TokenKind::PLUS), 1};
case '*':
return {StepFunctionToken(StepFunctionToken::TokenKind::STAR), 1};
case '^':
return {StepFunctionToken(StepFunctionToken::TokenKind::CARET), 1};
default:
return {StepFunctionToken(StepFunctionToken::TokenKind::ERROR,
Str.take_front().str()),
1};
}
}

static ResultTy munchWhitespace(const llvm::StringRef &Str) {
auto Munch = Str.take_while(
[](auto C) { return C == ' ' || C == '\t' || C == '\r' || C == '\n'; });
return {StepFunctionToken(StepFunctionToken::TokenKind::WHITESPACE),
Munch.size()};
}

static ResultTy munchNumber(const llvm::StringRef &Str) {
auto Munch =
Str.take_while([](auto C) { return llvm::isDigit(C) || C == '.'; });
return {
StepFunctionToken(StepFunctionToken::TokenKind::NUMBER, Munch.lower()),
Munch.size()};
}

static ResultTy munchIdentifier(const llvm::StringRef &Str) {
auto Munch =
Str.take_while([](auto C) { return llvm::isAlnum(C) || C == '_'; });
return {StepFunctionToken(StepFunctionToken::TokenKind::IDENTIFIER,
Munch.str()),
Munch.size()};
}

std::string Cnt;
};

//===----------------------------------------------------------------------===//
// StepFunctionParser Class
//===----------------------------------------------------------------------===//

class StepFunctionParser {
public:
explicit StepFunctionParser(std::string Cnt,
llvm::Optional<unsigned int> Line = llvm::None)
: TokenList(StepFunctionLexer(std::move(Cnt)).tokenize()), Line(Line) {}

[[nodiscard]] std::unique_ptr<StepFunction> buildStepFunction() {
return parseStepFunction();
}

private:
[[nodiscard]] const StepFunctionToken &peek() const {
return TokenList.front();
}

[[nodiscard]] StepFunctionToken next() {
auto Token = TokenList.front();
TokenList.pop_front();
return Token;
}

bool consume(const StepFunctionToken::TokenKind Kind) {
if (TokenList.front().getKind() == Kind) {
TokenList.pop_front();
return true;
}
return false;
}

llvm::Optional<StepFunction::OperandVariantType> parseOperand() {
while (!TokenList.empty()) {
switch (peek().getKind()) {
case StepFunctionToken::TokenKind::ERROR:
assert(peek().getValue().hasValue());
llvm::errs() << "Lexical error: Unexpected character '"
<< *peek().getValue() << "'\n";
return llvm::None;
case StepFunctionToken::TokenKind::END_OF_FILE:
llvm::errs() << "Syntax error: Unexpected end of expression.\n";
return llvm::None;
case StepFunctionToken::TokenKind::WHITESPACE:
consume(StepFunctionToken::TokenKind::WHITESPACE);
continue;
case StepFunctionToken::TokenKind::PLUS:
llvm::errs() << "Lexical error: Unexpected operator '+'.\n";
return llvm::None;
case StepFunctionToken::TokenKind::STAR:
llvm::errs() << "Lexical error: Unexpected operator '*'.\n";
return llvm::None;
case StepFunctionToken::TokenKind::CARET:
llvm::errs() << "Lexical error: Unexpected operator '^'.\n";
return llvm::None;
case StepFunctionToken::TokenKind::IDENTIFIER:
assert(peek().getValue().hasValue());
return {*next().getValue()};
case StepFunctionToken::TokenKind::NUMBER:
assert(peek().getValue().hasValue());
double Number;
llvm::StringRef(*next().getValue()).getAsDouble(Number);
return {Number};
}
}
return llvm::None;
}

llvm::Optional<StepFunction::StepOperation> parseOperator() {
while (!TokenList.empty()) {
switch (peek().getKind()) {
case StepFunctionToken::TokenKind::ERROR:
assert(peek().getValue().hasValue());
llvm::errs() << "Lexical error: Unexpected character '"
<< *peek().getValue() << "'\n";
return llvm::None;
case StepFunctionToken::TokenKind::END_OF_FILE:
llvm::errs() << "Syntax error: Unexpected end of expression.\n";
return llvm::None;
case StepFunctionToken::TokenKind::WHITESPACE:
consume(StepFunctionToken::TokenKind::WHITESPACE);
continue;
case StepFunctionToken::TokenKind::PLUS:
consume(StepFunctionToken::TokenKind::PLUS);
return StepFunction::StepOperation::ADDITION;
case StepFunctionToken::TokenKind::STAR:
consume(StepFunctionToken::TokenKind::STAR);
return StepFunction::StepOperation::MULTIPLICATION;
case StepFunctionToken::TokenKind::CARET:
consume(StepFunctionToken::TokenKind::CARET);
return StepFunction::StepOperation::EXPONENTIATION;
case StepFunctionToken::TokenKind::IDENTIFIER:
assert(peek().getValue().hasValue());
llvm::errs() << "Syntax error: Unexpected identifier '"
<< *peek().getValue() << "'\n";
return llvm::None;
case StepFunctionToken::TokenKind::NUMBER:
assert(peek().getValue().hasValue());
llvm::errs() << "Syntax error: Unexpected number '"
<< *peek().getValue() << "'\n";
return llvm::None;
}
}
return llvm::None;
}

bool parseEOF() {
while (!TokenList.empty()) {
switch (peek().getKind()) {
case StepFunctionToken::TokenKind::ERROR:
assert(peek().getValue().hasValue());
llvm::errs() << "Lexical error: Unexpected character '"
<< *peek().getValue() << "'\n";
return false;
case StepFunctionToken::TokenKind::END_OF_FILE:
return true;
case StepFunctionToken::TokenKind::WHITESPACE:
consume(StepFunctionToken::TokenKind::WHITESPACE);
continue;
case StepFunctionToken::TokenKind::PLUS:
llvm::errs() << "Lexical error: Unexpected operator '+'.\n";
return false;
case StepFunctionToken::TokenKind::STAR:
llvm::errs() << "Lexical error: Unexpected operator '*'.\n";
return false;
case StepFunctionToken::TokenKind::CARET:
llvm::errs() << "Lexical error: Unexpected operator '^'.\n";
return false;
case StepFunctionToken::TokenKind::IDENTIFIER:
assert(peek().getValue().hasValue());
llvm::errs() << "Syntax error: Unexpected identifier '"
<< *peek().getValue() << "'\n";
return false;
case StepFunctionToken::TokenKind::NUMBER:
assert(peek().getValue().hasValue());
llvm::errs() << "Syntax error: Unexpected number '"
<< *peek().getValue() << "'\n";
return false;
}
}
return false;
}

std::unique_ptr<StepFunction> parseStepFunction() {
auto LHS = parseOperand();
if (!LHS.hasValue()) {
return nullptr;
}
auto Op = parseOperator();
if (!Op.hasValue()) {
return nullptr;
}
auto RHS = parseOperand();
if (!RHS.hasValue()) {
return nullptr;
}
if (!parseEOF()) {
return nullptr;
}

if (std::holds_alternative<std::string>(LHS.getValue()) &&
std::holds_alternative<std::string>(RHS.getValue())) {
llvm::errs() << "Syntax error: Missing constant.\n";
return nullptr;
}
if (std::holds_alternative<double>(LHS.getValue()) &&
std::holds_alternative<double>(RHS.getValue())) {
llvm::errs() << "Syntax error: Missing operand.\n";
return nullptr;
}

return std::make_unique<StepFunction>(LHS.getValue(), Op.getValue(),
RHS.getValue());
}

StepFunctionLexer::TokenListTy TokenList;
llvm::Optional<unsigned int> Line;
};

} // namespace vara::feature

#endif // VARA_FEATURE_STEPFUNCTIONPARSER_H
9 changes: 7 additions & 2 deletions lib/Feature/FeatureModelParser.cpp
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
#include "vara/Feature/ConstraintParser.h"
#include "vara/Feature/Feature.h"
#include "vara/Feature/FeatureSourceRange.h"
#include "vara/Feature/StepFunctionParser.h"

#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/FormatVariadic.h"
@@ -31,6 +32,7 @@ FeatureModelXmlParser::parseConfigurationOption(xmlNode *Node,
int64_t MaxValue = 0;
std::vector<int64_t> Values;
std::vector<FeatureSourceRange> SourceRanges;
std::unique_ptr<StepFunction> Step;
for (xmlNode *Head = Node->children; Head; Head = Head->next) {
if (Head->type == XML_ELEMENT_NODE) {
std::string Cnt{trim(reinterpret_cast<char *>(
@@ -119,6 +121,8 @@ FeatureModelXmlParser::parseConfigurationOption(xmlNode *Node,
Suffix = Matches.suffix()) {
Values.emplace_back(parseInteger(Matches.str(), Head->line));
}
} else if (!xmlStrcmp(Head->name, XmlConstants::STEPFUNCTION)) {
Step = StepFunctionParser(Cnt, Head->line).buildStepFunction();
}
}
}
@@ -131,10 +135,11 @@ FeatureModelXmlParser::parseConfigurationOption(xmlNode *Node,
if (Values.empty()) {
FMB.makeFeature<NumericFeature>(Name, std::make_pair(MinValue, MaxValue),
Opt, std::move(SourceRanges),
OutputString);
OutputString, std::move(Step));
} else {
FMB.makeFeature<NumericFeature>(Name, Values, Opt,
std::move(SourceRanges), OutputString);
std::move(SourceRanges), OutputString,
std::move(Step));
}
} else {
FMB.makeFeature<BinaryFeature>(Name, Opt, std::move(SourceRanges),
2 changes: 2 additions & 0 deletions unittests/Feature/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -14,4 +14,6 @@ add_vara_unittest(VaRAFeatureUnitTests VaRAFeatureTests
OrderedFeatureVector.cpp
Relationship.cpp
RootFeature.cpp
StepFunction.cpp
StepFunctionParser.cpp
)
14 changes: 14 additions & 0 deletions unittests/Feature/FeatureModelParser.cpp
Original file line number Diff line number Diff line change
@@ -127,6 +127,20 @@ TEST(FeatureModelParser, outputString) {
EXPECT_EQ(F->getOutputString(), "-a");
}

TEST(FeatureModelParser, stepFunction) {
auto FM = buildFeatureModel("test_step_function.xml");
ASSERT_TRUE(FM);

if (auto *F = llvm::dyn_cast_or_null<NumericFeature>(FM->getFeature("A"));
F) {
auto *S = F->getStepFunction();
ASSERT_TRUE(S);
EXPECT_DOUBLE_EQ((*S)(12.42), 54.42);
} else {
FAIL();
}
}

//===----------------------------------------------------------------------===//
// XMLAlternatives
//===----------------------------------------------------------------------===//
84 changes: 84 additions & 0 deletions unittests/Feature/StepFunction.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "vara/Feature/StepFunction.h"

#include "gtest/gtest.h"

namespace vara::feature {

template <typename T>
class StepFunctionTest : public ::testing::Test {
protected:
static void checkEqual(T V1, T V2) { EXPECT_EQ(V1, V2); }

static void checkStepFunction(T Start, StepFunction *S,
const std::function<T(T)> &F) {
T Next = Start;
T Expected = Start;
for (int Step = 0; Step < STEPS; ++Step) {
Next = S->next<T>(Next);
Expected = F(Expected);
checkEqual(Next, Expected);
}
}

private:
static const int STEPS = 10;
};
template <>
void StepFunctionTest<float>::checkEqual(float V1, float V2) {
EXPECT_FLOAT_EQ(V1, V2);
}
template <>
void StepFunctionTest<double>::checkEqual(double V1, double V2) {
EXPECT_DOUBLE_EQ(V1, V2);
}

using types = ::testing::Types<double, float, long, int, unsigned int>;
TYPED_TEST_SUITE(StepFunctionTest, types, );

TYPED_TEST(StepFunctionTest, addition) {
auto S = StepFunction(StepFunction::StepOperation::ADDITION, 13.37);
TestFixture::checkStepFunction(-100, &S,
[](TypeParam X) { return X + 13.37; });
}

TYPED_TEST(StepFunctionTest, multiplication) {
auto S = StepFunction(StepFunction::StepOperation::MULTIPLICATION, -13.37);
TestFixture::checkStepFunction(1, &S, [](TypeParam X) { return X * -13.37; });
}

TYPED_TEST(StepFunctionTest, exponentiationL) {
auto S = StepFunction(StepFunction::StepOperation::EXPONENTIATION, 2);
TestFixture::checkStepFunction(1, &S,
[](TypeParam X) { return std::pow(X, 2); });
}

TYPED_TEST(StepFunctionTest, exponentiationR) {
auto S = StepFunction(2, StepFunction::StepOperation::EXPONENTIATION);
TestFixture::checkStepFunction(1, &S,
[](TypeParam X) { return std::pow(2, X); });
}

TEST(StepFunction, commutative) {
auto L = StepFunction(StepFunction::StepOperation::MULTIPLICATION, 13.37);
auto R = StepFunction(13.37, StepFunction::StepOperation::MULTIPLICATION);

EXPECT_DOUBLE_EQ(L(42), R(42));
}

TEST(StepFunction, next) {
auto S = StepFunction(StepFunction::StepOperation::MULTIPLICATION, 13.37);

EXPECT_DOUBLE_EQ(S.next<double>(42), 561.54);
EXPECT_DOUBLE_EQ(S.next(42.), 561.54);
EXPECT_EQ(S.next<int>(42.), 561);
EXPECT_EQ(S.next(42), 561);
}

TEST(StepFunction, call) {
auto S = StepFunction(StepFunction::StepOperation::MULTIPLICATION, 13.37);

EXPECT_DOUBLE_EQ(S(42.), 561.54);
EXPECT_DOUBLE_EQ(S(42), 561.54);
}

} // namespace vara::feature
117 changes: 117 additions & 0 deletions unittests/Feature/StepFunctionParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#include "vara/Feature/StepFunctionParser.h"

#include "gtest/gtest.h"

namespace vara::feature {

TEST(StepFunctionLexer, error) {
StepFunctionLexer L("x# 42");

auto TokenList = L.tokenize();
ASSERT_EQ(TokenList.size(), 2);

EXPECT_EQ(TokenList[0].getKind(), StepFunctionToken::TokenKind::IDENTIFIER);
EXPECT_EQ(*TokenList[0].getValue(), "x");
EXPECT_EQ(TokenList[1].getKind(), StepFunctionToken::TokenKind::ERROR);
EXPECT_EQ(*TokenList[1].getValue(), "#");
}

TEST(StepFunctionLexer, end) {
StepFunctionLexer L("x\0 42"); // NOLINT

auto TokenList = L.tokenize();
ASSERT_EQ(TokenList.size(), 2);

EXPECT_EQ(TokenList[0].getKind(), StepFunctionToken::TokenKind::IDENTIFIER);
EXPECT_EQ(*TokenList[0].getValue(), "x");
EXPECT_EQ(TokenList[1].getKind(), StepFunctionToken::TokenKind::END_OF_FILE);
}

class StepFunctionLexerTest : public ::testing::Test {
protected:
static void checkPrimary(StepFunctionToken::TokenKind Kind,
const std::string &Repr) {
auto TokenList = StepFunctionLexer(Repr).tokenize();
ASSERT_EQ(TokenList.size(), 2);

EXPECT_EQ(TokenList[0].getKind(), Kind);
EXPECT_EQ(TokenList[1].getKind(),
StepFunctionToken::TokenKind::END_OF_FILE);
}

static void checkBinary(StepFunctionToken::TokenKind Kind,
const std::string &Repr) {
auto TokenList = StepFunctionLexer("x" + Repr + "42").tokenize();
ASSERT_EQ(TokenList.size(), 4);

EXPECT_EQ(TokenList[0].getKind(), StepFunctionToken::TokenKind::IDENTIFIER);
EXPECT_EQ(*TokenList[0].getValue(), "x");
EXPECT_EQ(TokenList[1].getKind(), Kind);
EXPECT_EQ(TokenList[2].getKind(), StepFunctionToken::TokenKind::NUMBER);
EXPECT_EQ(*TokenList[2].getValue(), "42");
EXPECT_EQ(TokenList[3].getKind(),
StepFunctionToken::TokenKind::END_OF_FILE);
}
};

TEST_F(StepFunctionLexerTest, tokenize) {
checkPrimary(StepFunctionToken::TokenKind::IDENTIFIER, "x");
checkPrimary(StepFunctionToken::TokenKind::NUMBER, "42");
checkPrimary(StepFunctionToken::TokenKind::NUMBER, "0.5");
checkPrimary(StepFunctionToken::TokenKind::NUMBER, ".5");
checkPrimary(StepFunctionToken::TokenKind::WHITESPACE, " ");
checkPrimary(StepFunctionToken::TokenKind::WHITESPACE, "\n");
checkPrimary(StepFunctionToken::TokenKind::WHITESPACE, "\r");
checkPrimary(StepFunctionToken::TokenKind::WHITESPACE, "\t");
checkBinary(StepFunctionToken::TokenKind::PLUS, "+");
checkBinary(StepFunctionToken::TokenKind::STAR, "*");
checkBinary(StepFunctionToken::TokenKind::CARET, "^");
}

TEST(StepFunctionParser, error) {
EXPECT_FALSE(StepFunctionParser("x 42").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("x#42").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("x +").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("+ x").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("x").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("1 2").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("x + 1 + 2").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("x + 1 * 2").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("x + 1 ^ 2").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("x + y").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("1 + 2").buildStepFunction());
EXPECT_FALSE(StepFunctionParser("1 2 +").buildStepFunction());
}

class StepFunctionParserTest : public ::testing::Test {
protected:
static void checkStepFunction(double Current, double Next,
const std::string &Repr) {
auto L = StepFunctionParser(Repr).buildStepFunction();
ASSERT_TRUE(L);

EXPECT_DOUBLE_EQ((*L)(Current), Next);
}

static void checkStepFunction(double Current, double Next,
const std::string &Left,
const std::string &Right) {
checkStepFunction(Current, Next, Left);
checkStepFunction(Current, Next, Right);
}
};

TEST_F(StepFunctionParserTest, expressions) {
checkStepFunction(0, 0, "x+0", "0+x");
checkStepFunction(-3, 2, "x+5", "5+x");
checkStepFunction(42, 0, "x*0", "0*x");
checkStepFunction(1.5, 3, "x*2", "2*x");
checkStepFunction(3, 1.5, "x*.5", ".5*x");
checkStepFunction(0, 1, "x^0", "0^x");
checkStepFunction(3, 9, "x^2");
checkStepFunction(3, 8, "2^x");
checkStepFunction(9, 3, "x^.5");
checkStepFunction(2, .25, ".5^x");
}

} // namespace vara::feature
1 change: 1 addition & 0 deletions unittests/resources/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ set(FEATURE_LIB_TEST_FILES
xml/test_only_parents.xml
xml/test_out_of_order.xml
xml/test_output_string.xml
xml/test_step_function.xml
xml/test_with_whitespaces.xml
)

16 changes: 16 additions & 0 deletions unittests/resources/xml/test_step_function.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE vm SYSTEM "vm.dtd">
<vm name="ABC" root="test">
<binaryOptions>
</binaryOptions>
<numericOptions>
<configurationOption>
<name>A</name>
<parent>root</parent>
<optional>True</optional>
<minValue>0</minValue>
<maxValue>100</maxValue>
<stepFunction>x + 42</stepFunction>
</configurationOption>
</numericOptions>
</vm>