Repository: nifi-minifi-cpp Updated Branches: refs/heads/master 233d1d44c -> f08dc770e
MINIFICPP-376 Fixed failure to apply chained function calls This closes #245. Signed-off-by: Marc Parisi <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/commit/f08dc770 Tree: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/tree/f08dc770 Diff: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/diff/f08dc770 Branch: refs/heads/master Commit: f08dc770e4a6b5c40749fed597a2cc1f5ca4746b Parents: 233d1d4 Author: Andrew I. Christianson <[email protected]> Authored: Fri Jan 19 15:01:53 2018 -0500 Committer: Marc Parisi <[email protected]> Committed: Mon Jan 22 09:55:49 2018 -0500 ---------------------------------------------------------------------- extensions/expression-language/Expression.cpp | 64 +++++++++++--------- extensions/expression-language/Parser.yy | 25 +++----- .../impl/expression/Expression.h | 23 +++---- .../ExpressionLanguageTests.cpp | 35 ++++++++--- 4 files changed, 76 insertions(+), 71 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/f08dc770/extensions/expression-language/Expression.cpp ---------------------------------------------------------------------- diff --git a/extensions/expression-language/Expression.cpp b/extensions/expression-language/Expression.cpp index 5dc5405..2fdc541 100644 --- a/extensions/expression-language/Expression.cpp +++ b/extensions/expression-language/Expression.cpp @@ -18,7 +18,6 @@ #include <utility> #include <iostream> #include <iomanip> -#include <string> #include <random> #include <expression/Expression.h> @@ -271,37 +270,34 @@ template<std::string T(const std::vector<std::string> &)> Expression make_dynamic_function_incomplete(const std::string &function_name, const std::vector<Expression> &args, std::size_t num_args) { - if (args.size() >= num_args) { - auto result = make_dynamic([=](const Parameters ¶ms) -> std::string { - std::vector<std::string> evaluated_args; - for (const auto &arg : args) { - evaluated_args.emplace_back(arg(params)); - } + if (args.size() < num_args) { + std::stringstream message_ss; + message_ss << "Expression language function " + << function_name + << " called with " + << args.size() + << " argument(s), but " + << num_args + << " are required"; + throw std::runtime_error(message_ss.str()); + } - return T(evaluated_args); - }); + auto result = make_dynamic([=](const Parameters ¶ms) -> std::string { + std::vector<std::string> evaluated_args; - result.complete = [function_name, args](Expression expr) -> Expression { - std::vector<Expression> complete_args = {expr}; - complete_args.insert(complete_args.end(), args.begin(), args.end()); - return make_dynamic_function(function_name, complete_args); - }; + for (const auto &arg : args) { + evaluated_args.emplace_back(arg(params)); + } - return result; - } else { - auto result = make_dynamic([](const Parameters ¶ms) -> std::string { - throw std::runtime_error("Attempted to call incomplete function"); - }); + return T(evaluated_args); + }); - result.complete = [function_name, args](Expression expr) -> Expression { - std::vector<Expression> complete_args = {expr}; - complete_args.insert(complete_args.end(), args.begin(), args.end()); - return make_dynamic_function(function_name, complete_args); - }; + return result; +} - return result; - } +std::string expr_literal(const std::vector<std::string> &args) { + return args[0]; } Expression make_dynamic_function(const std::string &function_name, @@ -348,6 +344,8 @@ Expression make_dynamic_function(const std::string &function_name, return make_dynamic_function_incomplete<expr_toRadix>(function_name, args, 1); } else if (function_name == "random") { return make_dynamic_function_incomplete<expr_random>(function_name, args, 0); + } else if (function_name == "literal") { + return make_dynamic_function_incomplete<expr_literal>(function_name, args, 1); } else { std::string msg("Unknown expression function: "); msg.append(function_name); @@ -355,8 +353,18 @@ Expression make_dynamic_function(const std::string &function_name, } } -Expression make_dynamic_function_postfix(const Expression &subject, const Expression &fn) { - return fn.complete(subject); +Expression make_function_composition(const Expression &arg, + const std::vector<std::pair<std::string, std::vector<Expression>>> &chain) { + + auto expr = arg; + + for (const auto &chain_part : chain) { + std::vector<Expression> complete_args = {expr}; + complete_args.insert(complete_args.end(), chain_part.second.begin(), chain_part.second.end()); + expr = make_dynamic_function(chain_part.first, complete_args); + } + + return expr; } bool Expression::isDynamic() const { http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/f08dc770/extensions/expression-language/Parser.yy ---------------------------------------------------------------------- diff --git a/extensions/expression-language/Parser.yy b/extensions/expression-language/Parser.yy index 7ac52e0..2e053f9 100644 --- a/extensions/expression-language/Parser.yy +++ b/extensions/expression-language/Parser.yy @@ -106,6 +106,8 @@ %type <std::string> quoted_text_content %type <Expression> text_or_exp +%type <std::vector<std::pair<std::string, std::vector<Expression>>>> fn_calls + %% %start root; @@ -171,22 +173,6 @@ exp_whitespaces: %empty {} attr_id: quoted_text exp_whitespaces { std::swap($$, $1); } | IDENTIFIER exp_whitespaces { std::swap($$, $1); } ; -/* -number_decimal: %empty { $$ = ""; } - | PERIOD NUMBER { $$ = "." + std::to_string($2); } - ; - -number_sign: %empty { $$ = ""; } - | MINUS { $$ = "-"; } - ; - -number_exponent: %empty { $$ = ""; } - | BIGE number_sign NUMBER { $$ = "E" + $2 + std::to_string($3); } - | LITTLEE number_sign NUMBER { $$ = "e" + $2 + std::to_string($3); } - ; - -number: number_sign NUMBER number_decimal number_exponent { $$ = $1 + std::to_string($2) + $3 + $4; } - ;*/ fn_arg: quoted_text exp_whitespaces { $$ = make_static($1); } | NUMBER exp_whitespaces { $$ = make_static($1); } @@ -199,12 +185,15 @@ fn_args: %empty {} fn_call: attr_id LPAREN exp_whitespaces fn_args RPAREN exp_whitespaces { $$ = make_dynamic_function(std::move($1), $4); } +fn_calls: %empty {} + | COLON exp_whitespaces attr_id LPAREN exp_whitespaces fn_args RPAREN exp_whitespaces fn_calls { $$.push_back({$3, $6}); $$.insert($$.end(), $9.begin(), $9.end()); } + ; + exp_content_val: attr_id { $$ = make_dynamic_attr(std::move($1)); } | fn_call { $$ = $1; } ; -exp_content: exp_content_val { $$ = $1; } - | exp_content_val COLON exp_whitespaces fn_call { $$ = make_dynamic_function_postfix($1, $4); } +exp_content: exp_content_val fn_calls { $$ = make_function_composition($1, $2); } ; exp: DOLLAR LCURLY exp_whitespaces exp_content RCURLY { $$ = $4; } http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/f08dc770/extensions/expression-language/impl/expression/Expression.h ---------------------------------------------------------------------- diff --git a/extensions/expression-language/impl/expression/Expression.h b/extensions/expression-language/impl/expression/Expression.h index e245370..9ee4a54 100644 --- a/extensions/expression-language/impl/expression/Expression.h +++ b/extensions/expression-language/impl/expression/Expression.h @@ -44,8 +44,7 @@ typedef struct { static const std::function<std::string(const Parameters ¶ms)> NOOP_FN; /** - * A NiFi expression langauge expression which can be composed with other - * expressions (via the + operator. + * A NiFi expression language expression. */ class Expression { public: @@ -67,7 +66,7 @@ class Expression { bool isDynamic() const; /** - * Combine this expression with another expression. Intermediate results + * Combine this expression with another expression by concatenation. Intermediate results * are computed when non-dynamic expressions are composed. * * @param other_expr @@ -83,14 +82,6 @@ class Expression { */ std::string operator()(const Parameters ¶ms) const; - /** - * If this expression is incomplete (i.e. a function with incomplete arguments), then this - * will return a completed expression based on the provided expression. - */ - std::function<Expression(Expression)> complete = [](Expression expr) -> Expression { - throw std::runtime_error("Attempted to complete already complete expression."); - }; - protected: std::string val_; std::function<std::string(const Parameters ¶ms)> val_fn_; @@ -140,14 +131,14 @@ Expression make_dynamic_attr(const std::string &attribute_id); Expression make_dynamic_function(const std::string &function_name, const std::vector<Expression> &args); /** - * Creates a dynamic expression which feeds the subject as the first argument into - * the provided function expression. This enables expressions like attr:toLower() + * Creates a chained function composition. * - * @param subject - * @param fn + * @param arg + * @param chain * @return */ -Expression make_dynamic_function_postfix(const Expression &subject, const Expression &fn); +Expression make_function_composition(const Expression &arg, + const std::vector<std::pair<std::string, std::vector<Expression>>> &chain); } /* namespace expression */ } /* namespace minifi */ http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/f08dc770/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp ---------------------------------------------------------------------- diff --git a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp index 5dd51f1..27d3304 100644 --- a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp +++ b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp @@ -292,17 +292,13 @@ TEST_CASE("Substring After Last", "[expressionLanguageSubstringAfterLast]") { / } TEST_CASE("Substring Before No Args", "[expressionLanguageSubstringBeforeNoArgs]") { // NOLINT - auto expr = expression::compile("${attr:substringBefore()}"); - auto flow_file_a = std::make_shared<MockFlowFile>(); - flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); - REQUIRE_THROWS_WITH(expr({flow_file_a}), "Attempted to call incomplete function"); + REQUIRE_THROWS_WITH(expression::compile("${attr:substringBefore()}"), + "Expression language function substringBefore called with 1 argument(s), but 2 are required"); } TEST_CASE("Substring After No Args", "[expressionLanguageSubstringAfterNoArgs]") { // NOLINT - auto expr = expression::compile("${attr:substringAfter()}"); - auto flow_file_a = std::make_shared<MockFlowFile>(); - flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); - REQUIRE_THROWS_WITH(expr({flow_file_a}), "Attempted to call incomplete function"); + REQUIRE_THROWS_WITH(expression::compile("${attr:substringAfter()}"); , + "Expression language function substringAfter called with 1 argument(s), but 2 are required"); } #ifdef EXPRESSION_LANGUAGE_USE_REGEX @@ -541,4 +537,25 @@ TEST_CASE("Random", "[expressionLanguageRandom]") { // NOLINT REQUIRE(result > 0); } -// ${literal(64):toDouble():math("cbrt"):toNumber():math("max", 5)} +TEST_CASE("Chained call", "[expressionChainedCall]") { // NOLINT + auto expr = expression::compile("${attr:multiply(3):plus(1)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "7"); + REQUIRE("22" == expr({flow_file_a})); +} + +TEST_CASE("Chained call 2", "[expressionChainedCall2]") { // NOLINT + auto expr = expression::compile("${literal(10):multiply(2):plus(1):multiply(2)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + REQUIRE("42" == expr({flow_file_a})); +} + +TEST_CASE("Chained call 3", "[expressionChainedCall3]") { // NOLINT + auto expr = expression::compile("${literal(10):multiply(2):plus(${attr:multiply(2)}):multiply(${attr})}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "7"); + REQUIRE("238" == expr({flow_file_a})); +}
