Browse Source

Changed the actions in the filters to be shared_ptr instead of raw pointers. This prevents memory leaks when a filter is destructed.

- Also handled nullptr actions.
|- They are checked for in the constructor as well as in the add method and filtered out. No segfaults do to nullptr actions anymore.


Former-commit-id: 84b3b2a978
tempestpy_adaptions
masawei 11 years ago
parent
commit
33386f4c5f
  1. 52
      src/formula/AbstractFilter.h
  2. 4
      src/formula/csl/CslFilter.h
  3. 4
      src/formula/ltl/LtlFilter.h
  4. 6
      src/formula/prctl/PrctlFilter.h
  5. 36
      src/parser/CslParser.cpp
  6. 32
      src/parser/LtlParser.cpp
  7. 28
      src/parser/PrctlParser.cpp
  8. 10
      test/functional/parser/PrctlParserTest.cpp

52
src/formula/AbstractFilter.h

@ -30,16 +30,42 @@ public:
// Intentionally left empty.
}
AbstractFilter(action::AbstractAction<T>* action, OptimizingOperator opt = UNDEFINED) : opt(opt) {
actions.push_back(action);
AbstractFilter(std::shared_ptr<action::AbstractAction<T>> const & action, OptimizingOperator opt = UNDEFINED) : opt(opt) {
if(action.get() != nullptr) {
actions.push_back(action);
}
}
AbstractFilter(std::vector<action::AbstractAction<T>*> actions, OptimizingOperator opt = UNDEFINED) : actions(actions), opt(opt) {
// Intentionally left empty.
AbstractFilter(std::vector<std::shared_ptr<action::AbstractAction<T>>> const & actions, OptimizingOperator opt = UNDEFINED) {
// Filter out all nullptr actions.
// First detect that there is at least one.
uint_fast64_t emptyCount = 0;
for(uint_fast64_t i = 0; i < actions.size(); i++) {
if (actions[i].get() == nullptr) {
emptyCount++;
}
}
if(emptyCount > 0) {
// There is at least one nullptr action.
// Allocate space for the non null actions.
this->actions = std::vector<std::shared_ptr<action::AbstractAction<T>>>(actions.size() - emptyCount);
// Fill the vector. Note: For most implementations of the standard there will be no reallocation in the vector while doing this.
for(auto action : actions){
if(action.get() != nullptr) {
this->actions.push_back(action);
}
}
} else {
this->actions = actions;
}
this->opt = opt;
}
virtual ~AbstractFilter() {
actions.clear();
// Intentionally left empty.
}
virtual std::string toString() const {
@ -70,8 +96,8 @@ public:
return desc;
}
void addAction(action::AbstractAction<T>* action) {
if(action != nullptr) {
void addAction(std::shared_ptr<action::AbstractAction<T>> const & action) {
if(action.get() != nullptr) {
actions.push_back(action);
}
}
@ -80,6 +106,16 @@ public:
actions.pop_back();
}
std::shared_ptr<action::AbstractAction<T>> getAction(uint_fast64_t position) {
// Make sure the chosen position is not beyond the end of the vector.
// If it is so return the last element.
if(position < actions.size()) {
return actions[position];
} else {
return actions[actions.size()-1];
}
}
uint_fast64_t getActionCount() const {
return actions.size();
}
@ -94,7 +130,7 @@ public:
protected:
std::vector<action::AbstractAction<T>*> actions;
std::vector<std::shared_ptr<action::AbstractAction<T>>> actions;
OptimizingOperator opt;
};

4
src/formula/csl/CslFilter.h

@ -43,11 +43,11 @@ public:
// Intentionally left empty.
}
CslFilter(std::shared_ptr<AbstractCslFormula<T>> const & child, action::AbstractAction<T>* action, OptimizingOperator opt = UNDEFINED, bool steadyStateQuery = false) : AbstractFilter<T>(action, opt), child(child), steadyStateQuery(steadyStateQuery) {
CslFilter(std::shared_ptr<AbstractCslFormula<T>> const & child, std::shared_ptr<action::AbstractAction<T>> const & action, OptimizingOperator opt = UNDEFINED, bool steadyStateQuery = false) : AbstractFilter<T>(action, opt), child(child), steadyStateQuery(steadyStateQuery) {
// Intentionally left empty
}
CslFilter(std::shared_ptr<AbstractCslFormula<T>> const & child, std::vector<action::AbstractAction<T>*> actions, OptimizingOperator opt = UNDEFINED, bool steadyStateQuery = false) : AbstractFilter<T>(actions, opt), child(child), steadyStateQuery(steadyStateQuery) {
CslFilter(std::shared_ptr<AbstractCslFormula<T>> const & child, std::vector<std::shared_ptr<action::AbstractAction<T>>> const & actions, OptimizingOperator opt = UNDEFINED, bool steadyStateQuery = false) : AbstractFilter<T>(actions, opt), child(child), steadyStateQuery(steadyStateQuery) {
// Intentionally left empty.
}

4
src/formula/ltl/LtlFilter.h

@ -41,11 +41,11 @@ public:
// Intentionally left empty.
}
LtlFilter(std::shared_ptr<AbstractLtlFormula<T>> const & child, action::AbstractAction<T>* action, OptimizingOperator opt = UNDEFINED) : AbstractFilter<T>(action, opt), child(child) {
LtlFilter(std::shared_ptr<AbstractLtlFormula<T>> const & child, std::shared_ptr<action::AbstractAction<T>> const & action, OptimizingOperator opt = UNDEFINED) : AbstractFilter<T>(action, opt), child(child) {
this->actions.push_back(action);
}
LtlFilter(std::shared_ptr<AbstractLtlFormula<T>> const & child, std::vector<action::AbstractAction<T>*> actions, OptimizingOperator opt = UNDEFINED) : AbstractFilter<T>(actions, opt), child(child) {
LtlFilter(std::shared_ptr<AbstractLtlFormula<T>> const & child, std::vector<std::shared_ptr<action::AbstractAction<T>>> const & actions, OptimizingOperator opt = UNDEFINED) : AbstractFilter<T>(actions, opt), child(child) {
// Intentionally left empty.
}

6
src/formula/prctl/PrctlFilter.h

@ -46,16 +46,16 @@ public:
// Intentionally left empty.
}
PrctlFilter(std::shared_ptr<AbstractPrctlFormula<T>> const & child, action::AbstractAction<T>* action, OptimizingOperator opt = UNDEFINED) : AbstractFilter<T>(action, opt), child(child) {
PrctlFilter(std::shared_ptr<AbstractPrctlFormula<T>> const & child, std::shared_ptr<action::AbstractAction<T>> const & action, OptimizingOperator opt = UNDEFINED) : AbstractFilter<T>(action, opt), child(child) {
// Intentionally left empty.
}
PrctlFilter(std::shared_ptr<AbstractPrctlFormula<T>> const & child, std::vector<action::AbstractAction<T>*> actions, OptimizingOperator opt = UNDEFINED) : AbstractFilter<T>(actions, opt), child(child) {
PrctlFilter(std::shared_ptr<AbstractPrctlFormula<T>> const & child, std::vector<std::shared_ptr<action::AbstractAction<T>>> const & actions, OptimizingOperator opt = UNDEFINED) : AbstractFilter<T>(actions, opt), child(child) {
// Intentionally left empty.
}
virtual ~PrctlFilter() {
this->actions.clear();
// Intentionally left empty.
}
void check(storm::modelchecker::prctl::AbstractModelChecker<T> const & modelchecker) const {

36
src/parser/CslParser.cpp

@ -148,35 +148,35 @@ struct CslParser::CslGrammar : qi::grammar<Iterator, std::shared_ptr<csl::CslFil
// This block defines rules for parsing filter actions.
boundAction = (qi::lit("bound") > qi::lit("(") >> comparisonType >> qi::lit(",") >> qi::double_ >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::BoundAction<double>>(qi::_1, qi::_2)];
MAKE(storm::property::action::BoundAction<double> ,qi::_1, qi::_2)];
boundAction.name("bound action");
invertAction = qi::lit("invert")[qi::_val = phoenix::new_<storm::property::action::InvertAction<double>>()];
invertAction = qi::lit("invert")[qi::_val = MAKE(storm::property::action::InvertAction<double>, )];
invertAction.name("invert action");
formulaAction = (qi::lit("formula") > qi::lit("(") >> stateFormula >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::FormulaAction<double>>(qi::_1)];
MAKE(storm::property::action::FormulaAction<double>, qi::_1)];
formulaAction.name("formula action");
rangeAction = (
(qi::lit("range") >> qi::lit("(") >> qi::uint_ >> qi::lit(",") > qi::uint_ >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::RangeAction<double>>(qi::_1, qi::_2)] |
MAKE(storm::property::action::RangeAction<double>, qi::_1, qi::_2)] |
(qi::lit("range") >> qi::lit("(") >> qi::uint_ >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::RangeAction<double>>(qi::_1, qi::_1 + 1)]
MAKE(storm::property::action::RangeAction<double>, qi::_1, qi::_1 + 1)]
);
rangeAction.name("range action");
sortAction = (
(qi::lit("sort") > qi::lit("(") >> sortingCategory >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::SortAction<double>>(qi::_1)] |
(qi::lit("sort") > qi::lit("(") >> sortingCategory >> qi::lit(", ") >> qi::lit("asc") > qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::SortAction<double>>(qi::_1, true)] |
(qi::lit("sort") > qi::lit("(") >> sortingCategory >> qi::lit(", ") >> qi::lit("desc") > qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::SortAction<double>>(qi::_1, false)]
(qi::lit("sort") >> qi::lit("(") >> sortingCategory >> qi::lit(")"))[qi::_val =
MAKE(storm::property::action::SortAction<double>, qi::_1)] |
(qi::lit("sort") >> qi::lit("(") >> sortingCategory >> qi::lit(", ") >> (qi::lit("ascending") | qi::lit("asc")) > qi::lit(")"))[qi::_val =
MAKE(storm::property::action::SortAction<double>, qi::_1, true)] |
(qi::lit("sort") >> qi::lit("(") >> sortingCategory >> qi::lit(", ") >> (qi::lit("descending") | qi::lit("desc")) > qi::lit(")"))[qi::_val =
MAKE(storm::property::action::SortAction<double>, qi::_1, false)]
);
sortAction.name("sort action");
abstractAction = (boundAction | invertAction | formulaAction | rangeAction | sortAction) >> (qi::eps | qi::lit(";"));
abstractAction = (boundAction | invertAction | formulaAction | rangeAction | sortAction) >> (qi::lit(";") | qi::eps);
abstractAction.name("filter action");
filter = (qi::lit("filter") >> qi::lit("[") >> +abstractAction >> qi::lit("]") >> qi::lit("(") >> formula >> qi::lit(")"))[qi::_val =
@ -200,12 +200,12 @@ struct CslParser::CslGrammar : qi::grammar<Iterator, std::shared_ptr<csl::CslFil
qi::rule<Iterator, std::shared_ptr<csl::CslFilter<double>>(), Skipper> probabilisticNoBoundOperator;
qi::rule<Iterator, std::shared_ptr<csl::CslFilter<double>>(), Skipper> steadyStateNoBoundOperator;
qi::rule<Iterator, storm::property::action::AbstractAction<double>*(), Skipper> abstractAction;
qi::rule<Iterator, storm::property::action::BoundAction<double>*(), Skipper> boundAction;
qi::rule<Iterator, storm::property::action::InvertAction<double>*(), Skipper> invertAction;
qi::rule<Iterator, storm::property::action::FormulaAction<double>*(), Skipper> formulaAction;
qi::rule<Iterator, storm::property::action::RangeAction<double>*(), Skipper> rangeAction;
qi::rule<Iterator, storm::property::action::SortAction<double>*(), Skipper> sortAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::AbstractAction<double>>(), Skipper> abstractAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::BoundAction<double>>(), Skipper> boundAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::InvertAction<double>>(), Skipper> invertAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::FormulaAction<double>>(), Skipper> formulaAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::RangeAction<double>>(), Skipper> rangeAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::SortAction<double>>(), Skipper> sortAction;
qi::rule<Iterator, std::shared_ptr<csl::AbstractCslFormula<double>>(), Skipper> formula;
qi::rule<Iterator, std::shared_ptr<csl::AbstractCslFormula<double>>(), Skipper> comment;

32
src/parser/LtlParser.cpp

@ -109,31 +109,31 @@ struct LtlParser::LtlGrammar : qi::grammar<Iterator, std::shared_ptr<storm::prop
// This block defines rules for parsing filter actions.
boundAction = (qi::lit("bound") > qi::lit("(") >> comparisonType >> qi::lit(",") >> qi::double_ >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::BoundAction<double>>(qi::_1, qi::_2)];
MAKE(storm::property::action::BoundAction<double> ,qi::_1, qi::_2)];
boundAction.name("bound action");
invertAction = qi::lit("invert")[qi::_val = phoenix::new_<storm::property::action::InvertAction<double>>()];
invertAction = qi::lit("invert")[qi::_val = MAKE(storm::property::action::InvertAction<double>, )];
invertAction.name("invert action");
rangeAction = (
(qi::lit("range") >> qi::lit("(") >> qi::uint_ >> qi::lit(",") > qi::uint_ >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::RangeAction<double>>(qi::_1, qi::_2)] |
MAKE(storm::property::action::RangeAction<double>, qi::_1, qi::_2)] |
(qi::lit("range") >> qi::lit("(") >> qi::uint_ >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::RangeAction<double>>(qi::_1, qi::_1 + 1)]
MAKE(storm::property::action::RangeAction<double>, qi::_1, qi::_1 + 1)]
);
rangeAction.name("range action");
sortAction = (
(qi::lit("sort") > qi::lit("(") >> sortingCategory >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::SortAction<double>>(qi::_1)] |
(qi::lit("sort") > qi::lit("(") >> sortingCategory >> qi::lit(", ") >> qi::lit("asc") > qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::SortAction<double>>(qi::_1, true)] |
(qi::lit("sort") > qi::lit("(") >> sortingCategory >> qi::lit(", ") >> qi::lit("desc") > qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::SortAction<double>>(qi::_1, false)]
(qi::lit("sort") >> qi::lit("(") >> sortingCategory >> qi::lit(")"))[qi::_val =
MAKE(storm::property::action::SortAction<double>, qi::_1)] |
(qi::lit("sort") >> qi::lit("(") >> sortingCategory >> qi::lit(", ") >> (qi::lit("ascending") | qi::lit("asc")) > qi::lit(")"))[qi::_val =
MAKE(storm::property::action::SortAction<double>, qi::_1, true)] |
(qi::lit("sort") >> qi::lit("(") >> sortingCategory >> qi::lit(", ") >> (qi::lit("descending") | qi::lit("desc")) > qi::lit(")"))[qi::_val =
MAKE(storm::property::action::SortAction<double>, qi::_1, false)]
);
sortAction.name("sort action");
abstractAction = (boundAction | invertAction | rangeAction | sortAction) >> (qi::eps | qi::lit(";"));
abstractAction = (boundAction | invertAction | rangeAction | sortAction) >> (qi::lit(";") | qi::eps);
abstractAction.name("filter action");
filter = (qi::lit("filter") >> qi::lit("[") >> +abstractAction >> qi::lit("]") > qi::lit("(") >> formula >> qi::lit(")"))[qi::_val =
@ -153,11 +153,11 @@ struct LtlParser::LtlGrammar : qi::grammar<Iterator, std::shared_ptr<storm::prop
qi::rule<Iterator, std::shared_ptr<storm::property::ltl::LtlFilter<double>>(), Skipper> start;
qi::rule<Iterator, std::shared_ptr<storm::property::ltl::LtlFilter<double>>(), Skipper> filter;
qi::rule<Iterator, storm::property::action::AbstractAction<double>*(), Skipper> abstractAction;
qi::rule<Iterator, storm::property::action::BoundAction<double>*(), Skipper> boundAction;
qi::rule<Iterator, storm::property::action::InvertAction<double>*(), Skipper> invertAction;
qi::rule<Iterator, storm::property::action::RangeAction<double>*(), Skipper> rangeAction;
qi::rule<Iterator, storm::property::action::SortAction<double>*(), Skipper> sortAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::AbstractAction<double>>(), Skipper> abstractAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::BoundAction<double>>(), Skipper> boundAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::InvertAction<double>>(), Skipper> invertAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::RangeAction<double>>(), Skipper> rangeAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::SortAction<double>>(), Skipper> sortAction;
qi::rule<Iterator, std::shared_ptr<storm::property::ltl::AbstractLtlFormula<double>>(), Skipper> comment;
qi::rule<Iterator, std::shared_ptr<storm::property::ltl::AbstractLtlFormula<double>>(), Skipper> formula;

28
src/parser/PrctlParser.cpp

@ -155,31 +155,31 @@ struct PrctlParser::PrctlGrammar : qi::grammar<Iterator, std::shared_ptr<storm::
// This block defines rules for parsing filter actions.
boundAction = (qi::lit("bound") > qi::lit("(") >> comparisonType >> qi::lit(",") >> qi::double_ >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::BoundAction<double>>(qi::_1, qi::_2)];
MAKE(storm::property::action::BoundAction<double> ,qi::_1, qi::_2)];
boundAction.name("bound action");
invertAction = qi::lit("invert")[qi::_val = phoenix::new_<storm::property::action::InvertAction<double>>()];
invertAction = qi::lit("invert")[qi::_val = MAKE(storm::property::action::InvertAction<double>, )];
invertAction.name("invert action");
formulaAction = (qi::lit("formula") > qi::lit("(") >> stateFormula >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::FormulaAction<double>>(qi::_1)];
MAKE(storm::property::action::FormulaAction<double>, qi::_1)];
formulaAction.name("formula action");
rangeAction = (
(qi::lit("range") >> qi::lit("(") >> qi::uint_ >> qi::lit(",") > qi::uint_ >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::RangeAction<double>>(qi::_1, qi::_2)] |
MAKE(storm::property::action::RangeAction<double>, qi::_1, qi::_2)] |
(qi::lit("range") >> qi::lit("(") >> qi::uint_ >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::RangeAction<double>>(qi::_1, qi::_1 + 1)]
MAKE(storm::property::action::RangeAction<double>, qi::_1, qi::_1 + 1)]
);
rangeAction.name("range action");
sortAction = (
(qi::lit("sort") >> qi::lit("(") >> sortingCategory >> qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::SortAction<double>>(qi::_1)] |
MAKE(storm::property::action::SortAction<double>, qi::_1)] |
(qi::lit("sort") >> qi::lit("(") >> sortingCategory >> qi::lit(", ") >> (qi::lit("ascending") | qi::lit("asc")) > qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::SortAction<double>>(qi::_1, true)] |
MAKE(storm::property::action::SortAction<double>, qi::_1, true)] |
(qi::lit("sort") >> qi::lit("(") >> sortingCategory >> qi::lit(", ") >> (qi::lit("descending") | qi::lit("desc")) > qi::lit(")"))[qi::_val =
phoenix::new_<storm::property::action::SortAction<double>>(qi::_1, false)]
MAKE(storm::property::action::SortAction<double>, qi::_1, false)]
);
sortAction.name("sort action");
@ -210,12 +210,12 @@ struct PrctlParser::PrctlGrammar : qi::grammar<Iterator, std::shared_ptr<storm::
qi::rule<Iterator, std::shared_ptr<storm::property::prctl::PrctlFilter<double>>(), Skipper> probabilisticNoBoundOperator;
qi::rule<Iterator, std::shared_ptr<storm::property::prctl::PrctlFilter<double>>(), Skipper> rewardNoBoundOperator;
qi::rule<Iterator, storm::property::action::AbstractAction<double>*(), Skipper> abstractAction;
qi::rule<Iterator, storm::property::action::BoundAction<double>*(), Skipper> boundAction;
qi::rule<Iterator, storm::property::action::InvertAction<double>*(), Skipper> invertAction;
qi::rule<Iterator, storm::property::action::FormulaAction<double>*(), Skipper> formulaAction;
qi::rule<Iterator, storm::property::action::RangeAction<double>*(), Skipper> rangeAction;
qi::rule<Iterator, storm::property::action::SortAction<double>*(), Skipper> sortAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::AbstractAction<double>>(), Skipper> abstractAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::BoundAction<double>>(), Skipper> boundAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::InvertAction<double>>(), Skipper> invertAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::FormulaAction<double>>(), Skipper> formulaAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::RangeAction<double>>(), Skipper> rangeAction;
qi::rule<Iterator, std::shared_ptr<storm::property::action::SortAction<double>>(), Skipper> sortAction;
qi::rule<Iterator, std::shared_ptr<storm::property::prctl::AbstractPrctlFormula<double>>(), Skipper> formula;
qi::rule<Iterator, std::shared_ptr<storm::property::prctl::AbstractPrctlFormula<double>>(), Skipper> comment;

10
test/functional/parser/PrctlParserTest.cpp

@ -169,11 +169,11 @@ TEST(PrctlParserTest, parsePrctlFilterTest) {
ASSERT_NE(formula, nullptr);
ASSERT_EQ(5, formula->getActionCount());
ASSERT_NE(dynamic_cast<storm::property::action::FormulaAction<double>*>(formula->getAction(0)), nullptr);
ASSERT_NE(dynamic_cast<storm::property::action::InvertAction<double>*>(formula->getAction(1)), nullptr);
ASSERT_NE(dynamic_cast<storm::property::action::BoundAction<double>*>(formula->getAction(2)), nullptr);
ASSERT_NE(dynamic_cast<storm::property::action::SortAction<double>*>(formula->getAction(3)), nullptr);
ASSERT_NE(dynamic_cast<storm::property::action::RangeAction<double>*>(formula->getAction(4)), nullptr);
ASSERT_NE(std::dynamic_pointer_cast<storm::property::action::FormulaAction<double>>(formula->getAction(0)).get(), nullptr);
ASSERT_NE(std::dynamic_pointer_cast<storm::property::action::InvertAction<double>>(formula->getAction(1)).get(), nullptr);
ASSERT_NE(std::dynamic_pointer_cast<storm::property::action::BoundAction<double>>(formula->getAction(2)).get(), nullptr);
ASSERT_NE(std::dynamic_pointer_cast<storm::property::action::SortAction<double>>(formula->getAction(3)).get(), nullptr);
ASSERT_NE(std::dynamic_pointer_cast<storm::property::action::RangeAction<double>>(formula->getAction(4)).get(), nullptr);
// The input was parsed correctly.
ASSERT_EQ("filter[formula(b); invert; bound(<, 0.500000); sort(value, ascending); range(0, 3)](F a)", formula->toString());

Loading…
Cancel
Save