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)}