You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ph...@apache.org on 2018/01/19 14:33:51 UTC
nifi-minifi-cpp git commit: MINIFICPP-367 Implement expression
language arithmetic operations
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 <ph...@apache.org>
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 <an...@andyic.org>
Authored: Tue Jan 16 16:05:47 2018 -0500
Committer: Marc Parisi <ph...@apache.org>
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)}