Repository: nifi-minifi-cpp Updated Branches: refs/heads/master 254877fa5 -> 8207e959d
MINIFICPP-367 Implement expression language arithmetic operations This closes #243. 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/8207e959 Tree: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/tree/8207e959 Diff: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/diff/8207e959 Branch: refs/heads/master Commit: 8207e959d278acbd84bb63d93c083fa5ad604e47 Parents: 254877f Author: Andy I. Christianson <[email protected]> Authored: Tue Jan 16 16:05:47 2018 -0500 Committer: Marc Parisi <[email protected]> Committed: Fri Jan 19 09:33:38 2018 -0500 ---------------------------------------------------------------------- extensions/expression-language/Expression.cpp | 137 ++++++++++++++++++ extensions/expression-language/Parser.yy | 23 +++- extensions/expression-language/Scanner.ll | 6 +- .../ExpressionLanguageTests.cpp | 138 +++++++++++++++++++ 4 files changed, 296 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/8207e959/extensions/expression-language/Expression.cpp ---------------------------------------------------------------------- diff --git a/extensions/expression-language/Expression.cpp b/extensions/expression-language/Expression.cpp index 2220434..5dc5405 100644 --- a/extensions/expression-language/Expression.cpp +++ b/extensions/expression-language/Expression.cpp @@ -17,6 +17,9 @@ #include <utility> #include <iostream> +#include <iomanip> +#include <string> +#include <random> #include <expression/Expression.h> #include <regex> @@ -146,6 +149,124 @@ std::string expr_replaceEmpty(const std::vector<std::string> &args) { #endif // EXPRESSION_LANGUAGE_USE_REGEX +std::string expr_binaryOp(const std::vector<std::string> &args, + long double (*ldop)(long double, long double), + int (*iop)(int, int), + bool long_only = false) { + try { + if (!long_only && + args[0].find('.') == args[0].npos && + args[1].find('.') == args[1].npos && + args[1].find('e') == args[1].npos && + args[0].find('e') == args[0].npos && + args[0].find('E') == args[0].npos && + args[1].find('E') == args[1].npos) { + return std::to_string(iop(std::stoi(args[0]), std::stoi(args[1]))); + } else { + std::stringstream ss; + ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10) + << ldop(std::stold(args[0]), std::stold(args[1])); + auto result = ss.str(); + result.erase(result.find_last_not_of('0') + 1, std::string::npos); + + if (result.find('.') == result.length() - 1) { + result.erase(result.length() - 1, std::string::npos); + } + + return result; + } + } catch (const std::exception &e) { + return ""; + } +} + +std::string expr_plus(const std::vector<std::string> &args) { + return expr_binaryOp(args, + [](long double a, long double b) { return a + b; }, + [](int a, int b) { return a + b; }); +} + +std::string expr_minus(const std::vector<std::string> &args) { + return expr_binaryOp(args, + [](long double a, long double b) { return a - b; }, + [](int a, int b) { return a - b; }); +} + +std::string expr_multiply(const std::vector<std::string> &args) { + return expr_binaryOp(args, + [](long double a, long double b) { return a * b; }, + [](int a, int b) { return a * b; }); +} + +std::string expr_divide(const std::vector<std::string> &args) { + return expr_binaryOp(args, + [](long double a, long double b) { return a / b; }, + [](int a, int b) { return a / b; }, + true); +} + +std::string expr_mod(const std::vector<std::string> &args) { + return expr_binaryOp(args, + [](long double a, long double b) { return std::fmod(a, b); }, + [](int a, int b) { return a % b; }); +} + +std::string expr_toRadix(const std::vector<std::string> &args) { + int radix = std::stoi(args[1]); + + if (radix < 2 || radix > 36) { + throw std::runtime_error("Cannot perform conversion due to invalid radix"); + } + + int pad_width = 0; + + if (args.size() > 2) { + pad_width = std::stoi(args[2]); + } + + auto value = std::stoll(args[0], nullptr, 10); + + std::string sign; + + if (value < 0) { + sign = "-"; + } + + const char chars[] = + "0123456789ab" + "cdefghijklmn" + "opqrstuvwxyz"; + std::string str_num; + + while (value) { + str_num += chars[std::abs(value % radix)]; + value /= radix; + } + + std::reverse(str_num.begin(), str_num.end()); + + std::stringstream ss; + ss << sign << std::setfill('0') << std::setw(pad_width) << str_num; + return ss.str(); +} + +std::string expr_fromRadix(const std::vector<std::string> &args) { + int radix = std::stoi(args[1]); + + if (radix < 2 || radix > 36) { + throw std::runtime_error("Cannot perform conversion due to invalid radix"); + } + + return std::to_string(std::stoll(args[0], nullptr, radix)); +} + +std::string expr_random(const std::vector<std::string> &args) { + std::random_device random_device; + std::mt19937 generator(random_device()); + std::uniform_int_distribution<long long> distribution(0, LLONG_MAX); + return std::to_string(distribution(generator)); +} + template<std::string T(const std::vector<std::string> &)> Expression make_dynamic_function_incomplete(const std::string &function_name, const std::vector<Expression> &args, @@ -211,6 +332,22 @@ Expression make_dynamic_function(const std::string &function_name, } else if (function_name == "replaceEmpty") { return make_dynamic_function_incomplete<expr_replaceEmpty>(function_name, args, 1); #endif // EXPRESSION_LANGUAGE_USE_REGEX + } else if (function_name == "plus") { + return make_dynamic_function_incomplete<expr_plus>(function_name, args, 1); + } else if (function_name == "minus") { + return make_dynamic_function_incomplete<expr_minus>(function_name, args, 1); + } else if (function_name == "multiply") { + return make_dynamic_function_incomplete<expr_multiply>(function_name, args, 1); + } else if (function_name == "divide") { + return make_dynamic_function_incomplete<expr_divide>(function_name, args, 1); + } else if (function_name == "mod") { + return make_dynamic_function_incomplete<expr_mod>(function_name, args, 1); + } else if (function_name == "fromRadix") { + return make_dynamic_function_incomplete<expr_fromRadix>(function_name, args, 2); + } else if (function_name == "toRadix") { + 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 { std::string msg("Unknown expression function: "); msg.append(function_name); http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/8207e959/extensions/expression-language/Parser.yy ---------------------------------------------------------------------- diff --git a/extensions/expression-language/Parser.yy b/extensions/expression-language/Parser.yy index e9837be..7ac52e0 100644 --- a/extensions/expression-language/Parser.yy +++ b/extensions/expression-language/Parser.yy @@ -72,6 +72,7 @@ LSQUARE "[" RSQUARE "]" PIPE "|" + MINUS "-" COMMA "," COLON ":" SEMI ";" @@ -86,7 +87,7 @@ %token <std::string> IDENTIFIER "identifier" %token <std::string> MISC "misc" %token <std::string> WHITESPACE "whitespace" -%token <int> NUMBER "number" +%token <std::string> NUMBER "number" %type <std::string> exp_whitespace %type <std::string> exp_whitespaces @@ -130,7 +131,7 @@ text_no_quote_no_dollar: IDENTIFIER { std::swap($$, $1); } | BSLASH { $$ = "\\"; } | STAR { $$ = "*"; } | HASH { $$ = "#"; } - | NUMBER { $$ = std::to_string($1); } + | NUMBER { std::swap($$, $1); } ; text_inc_quote_escaped_dollar: text_no_quote_no_dollar { std::swap($$, $1); } @@ -170,13 +171,25 @@ 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); } + ; -/*fn_arg: exp_content_val exp_whitespaces { $$ = $1; } - | NUMBER exp_whitespaces { $$ = make_static(std::to_string($1)); } +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(std::to_string($1)); } + | NUMBER exp_whitespaces { $$ = make_static($1); } | exp exp_whitespaces { $$ = $1; } ; http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/8207e959/extensions/expression-language/Scanner.ll ---------------------------------------------------------------------- diff --git a/extensions/expression-language/Scanner.ll b/extensions/expression-language/Scanner.ll index 2660539..2d60ad4 100644 --- a/extensions/expression-language/Scanner.ll +++ b/extensions/expression-language/Scanner.ll @@ -34,7 +34,7 @@ %option c++ id [a-zA-Z][a-zA-Z_0-9]* -int [0-9]+ +num [-]?[0-9]+[.]?[0-9]*([eE][+-]?[0-9]+)? whitespace [ \r\t]+ %{ @@ -71,8 +71,8 @@ whitespace [ \r\t]+ return Parser::token::TOK_WHITESPACE; } -{int} { - yylval->build<int>(std::atoi(yytext)); +{num} { + yylval->build<std::string>(yytext); return Parser::token::TOK_NUMBER; } http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/8207e959/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 8660af5..5dd51f1 100644 --- a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp +++ b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp @@ -404,3 +404,141 @@ TEST_CASE("Replace Empty 3", "[expressionLanguageReplaceEmpty2]") { // NOLINT } #endif // EXPRESSION_LANGUAGE_USE_REGEX + +TEST_CASE("Plus Integer", "[expressionLanguagePlusInteger]") { // NOLINT + auto expr = expression::compile("${attr:plus(13)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11"); + REQUIRE("24" == expr({flow_file_a})); +} + +TEST_CASE("Plus Decimal", "[expressionLanguagePlusDecimal]") { // NOLINT + auto expr = expression::compile("${attr:plus(-13.34567)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11.1"); + REQUIRE("-2.24567" == expr({flow_file_a})); +} + +TEST_CASE("Plus Exponent", "[expressionLanguagePlusExponent]") { // NOLINT + auto expr = expression::compile("${attr:plus(10e+6)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11"); + REQUIRE("10000011" == expr({flow_file_a})); +} + +TEST_CASE("Plus Exponent 2", "[expressionLanguagePlusExponent2]") { // NOLINT + auto expr = expression::compile("${attr:plus(10e+6)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11.345678901234"); + REQUIRE("10000011.345678901234351" == expr({flow_file_a})); +} + +TEST_CASE("Minus Integer", "[expressionLanguageMinusInteger]") { // NOLINT + auto expr = expression::compile("${attr:minus(13)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11"); + REQUIRE("-2" == expr({flow_file_a})); +} + +TEST_CASE("Minus Decimal", "[expressionLanguageMinusDecimal]") { // NOLINT + auto expr = expression::compile("${attr:minus(-13.34567)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11.1"); + REQUIRE("24.44567" == expr({flow_file_a})); +} + +TEST_CASE("Multiply Integer", "[expressionLanguageMultiplyInteger]") { // NOLINT + auto expr = expression::compile("${attr:multiply(13)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11"); + REQUIRE("143" == expr({flow_file_a})); +} + +TEST_CASE("Multiply Decimal", "[expressionLanguageMultiplyDecimal]") { // NOLINT + auto expr = expression::compile("${attr:multiply(-13.34567)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11.1"); + REQUIRE("-148.136937" == expr({flow_file_a})); +} + +TEST_CASE("Divide Integer", "[expressionLanguageDivideInteger]") { // NOLINT + auto expr = expression::compile("${attr:divide(13)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11"); + REQUIRE("0.846153846153846" == expr({flow_file_a})); +} + +TEST_CASE("Divide Decimal", "[expressionLanguageDivideDecimal]") { // NOLINT + auto expr = expression::compile("${attr:divide(-13.34567)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "11.1"); + REQUIRE("-0.831730441409086" == expr({flow_file_a})); +} + +TEST_CASE("To Radix", "[expressionLanguageToRadix]") { // NOLINT + auto expr = expression::compile("${attr:toRadix(2,16)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "10"); + REQUIRE("0000000000001010" == expr({flow_file_a})); +} + +TEST_CASE("To Radix 2", "[expressionLanguageToRadix2]") { // NOLINT + auto expr = expression::compile("${attr:toRadix(16)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "13"); + REQUIRE("d" == expr({flow_file_a})); +} + +TEST_CASE("To Radix 3", "[expressionLanguageToRadix3]") { // NOLINT + auto expr = expression::compile("${attr:toRadix(23,8)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "-2347"); + REQUIRE("-000004a1" == expr({flow_file_a})); +} + +TEST_CASE("From Radix", "[expressionLanguageFromRadix]") { // NOLINT + auto expr = expression::compile("${attr:fromRadix(2)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "0000000000001010"); + REQUIRE("10" == expr({flow_file_a})); +} + +TEST_CASE("From Radix 2", "[expressionLanguageFromRadix2]") { // NOLINT + auto expr = expression::compile("${attr:fromRadix(16)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "d"); + REQUIRE("13" == expr({flow_file_a})); +} + +TEST_CASE("From Radix 3", "[expressionLanguageFromRadix3]") { // NOLINT + auto expr = expression::compile("${attr:fromRadix(23)}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "-000004a1"); + REQUIRE("-2347" == expr({flow_file_a})); +} + +TEST_CASE("Random", "[expressionLanguageRandom]") { // NOLINT + auto expr = expression::compile("${random()}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + auto result = std::stoll(expr({flow_file_a})); + REQUIRE(result > 0); +} + +// ${literal(64):toDouble():math("cbrt"):toNumber():math("max", 5)}
