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/03/14 17:34:37 UTC

nifi-minifi-cpp git commit: MINIFICPP-422 Refactored type system in EL to preserve type information between operations

Repository: nifi-minifi-cpp
Updated Branches:
  refs/heads/master 4232ac8b6 -> c4ea662c7


MINIFICPP-422 Refactored type system in EL to preserve type information between operations

This closes #274.

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/c4ea662c
Tree: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/tree/c4ea662c
Diff: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/diff/c4ea662c

Branch: refs/heads/master
Commit: c4ea662c76cde92a82c09aa0cd12a75b0bb595cc
Parents: 4232ac8
Author: Andrew I. Christianson <an...@andyic.org>
Authored: Fri Mar 9 12:56:37 2018 -0500
Committer: Marc Parisi <ph...@apache.org>
Committed: Wed Mar 14 13:34:22 2018 -0400

----------------------------------------------------------------------
 CMakeLists.txt                                  |   1 +
 extensions/expression-language/CMakeLists.txt   |   1 +
 extensions/expression-language/Driver.h         |   2 +-
 extensions/expression-language/Expression.cpp   | 323 +++++++++----------
 .../expression-language/ProcessContextExpr.cpp  |   4 +-
 extensions/expression-language/common/Value.h   | 238 ++++++++++++++
 .../impl/expression/Expression.h                |  23 +-
 .../noop/expression/Expression.h                |   1 +
 .../ExpressionLanguageTests.cpp                 | 154 ++++-----
 9 files changed, 486 insertions(+), 261 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/c4ea662c/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d4245d5..166fd16 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -110,6 +110,7 @@ include_directories(thirdparty/rapidjson-1.1.0/include)
 
 ## Expression language extensions
 option(DISABLE_EXPRESSION_LANGUAGE "Disables the scripting extensions." OFF)
+include_directories("extensions/expression-language/common")
 if (DISABLE_EXPRESSION_LANGUAGE)
     # Build expression language NoOp implementation, if necessary
     include_directories("extensions/expression-language/noop")

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/c4ea662c/extensions/expression-language/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/extensions/expression-language/CMakeLists.txt b/extensions/expression-language/CMakeLists.txt
index 53c87e6..b242d9a 100644
--- a/extensions/expression-language/CMakeLists.txt
+++ b/extensions/expression-language/CMakeLists.txt
@@ -39,6 +39,7 @@ flex_target(
 add_flex_bison_dependency(el-scanner el-parser)
 
 include_directories(../../libminifi/include  ../../libminifi/include/core  ../../thirdparty/spdlog-20170710/include ../../thirdparty/concurrentqueue ../../thirdparty/yaml-cpp-yaml-cpp-0.5.3/include ${CIVET_THIRDPARTY_ROOT}/include ../../thirdparty/)
+include_directories(common)
 include_directories(impl)
 
 file(GLOB SOURCES  "*.cpp")

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/c4ea662c/extensions/expression-language/Driver.h
----------------------------------------------------------------------
diff --git a/extensions/expression-language/Driver.h b/extensions/expression-language/Driver.h
index 4e66b4c..71dbbe4 100644
--- a/extensions/expression-language/Driver.h
+++ b/extensions/expression-language/Driver.h
@@ -41,7 +41,7 @@ class Driver : public yyFlexLexer {
  public:
   explicit Driver(std::istream *input = nullptr, std::ostream *output = nullptr)
       : yyFlexLexer(input, output),
-        result("") {
+        result(Value()) {
   }
   ~Driver() override = default;
   int lex(Parser::semantic_type *yylval,

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/c4ea662c/extensions/expression-language/Expression.cpp
----------------------------------------------------------------------
diff --git a/extensions/expression-language/Expression.cpp b/extensions/expression-language/Expression.cpp
index d0c060a..4824f2d 100644
--- a/extensions/expression-language/Expression.cpp
+++ b/extensions/expression-language/Expression.cpp
@@ -39,138 +39,143 @@ Expression compile(const std::string &expr_str) {
 }
 
 Expression make_static(std::string val) {
-  return Expression(std::move(val));
+  return Expression(Value(val));
 }
 
-Expression make_dynamic(std::function<std::string(const Parameters &params)> val_fn) {
-  return Expression("", std::move(val_fn));
+Expression make_dynamic(const std::function<Value(const Parameters &params)> &val_fn) {
+  return Expression(Value(), std::move(val_fn));
 }
 
 Expression make_dynamic_attr(const std::string &attribute_id) {
-  return make_dynamic([attribute_id](const Parameters &params) -> std::string {
+  return make_dynamic([attribute_id](const Parameters &params) -> Value {
     std::string result;
-    params.flow_file.lock()->getAttribute(attribute_id, result);
-    return result;
+    if (params.flow_file.lock()->getAttribute(attribute_id, result)) {
+      return Value(result);
+    } else {
+      return Value();
+    }
   });
 }
 
-std::string expr_hostname(const std::vector<std::string> &args) {
+Value expr_hostname(const std::vector<Value> &args) {
   char hostname[1024];
   hostname[1023] = '\0';
   gethostname(hostname, 1023);
-  return std::string(hostname);
+  return Value(std::string(hostname));
 }
 
-std::string expr_toUpper(const std::vector<std::string> &args) {
-  std::string result = args[0];
+Value expr_toUpper(const std::vector<Value> &args) {
+  std::string result = args[0].asString();
   std::transform(result.begin(), result.end(), result.begin(), ::toupper);
-  return result;
+  return Value(result);
 }
 
-std::string expr_toLower(const std::vector<std::string> &args) {
-  std::string result = args[0];
+Value expr_toLower(const std::vector<Value> &args) {
+  std::string result = args[0].asString();
   std::transform(result.begin(), result.end(), result.begin(), ::tolower);
-  return result;
+  return Value(result);
 }
 
-std::string expr_substring(const std::vector<std::string> &args) {
+Value expr_substring(const std::vector<Value> &args) {
   if (args.size() < 3) {
-    return args[0].substr(std::stoul(args[1]));
+    return Value(args[0].asString().substr(args[1].asUnsignedLong()));
   } else {
-    return args[0].substr(std::stoul(args[1]), std::stoul(args[2]));
+    return Value(args[0].asString().substr(args[1].asUnsignedLong(), args[2].asUnsignedLong()));
   }
 }
 
-std::string expr_substringBefore(const std::vector<std::string> &args) {
-  return args[0].substr(0, args[0].find(args[1]));
+Value expr_substringBefore(const std::vector<Value> &args) {
+  const std::string &arg_0 = args[0].asString();
+  return Value(arg_0.substr(0, arg_0.find(args[1].asString())));
 }
 
-std::string expr_substringBeforeLast(const std::vector<std::string> &args) {
+Value expr_substringBeforeLast(const std::vector<Value> &args) {
   size_t last_pos = 0;
-  while (args[0].find(args[1], last_pos + 1) != std::string::npos) {
-    last_pos = args[0].find(args[1], last_pos + 1);
+  const std::string &arg_0 = args[0].asString();
+  const std::string &arg_1 = args[1].asString();
+  while (arg_0.find(arg_1, last_pos + 1) != std::string::npos) {
+    last_pos = arg_0.find(arg_1, last_pos + 1);
   }
-  return args[0].substr(0, last_pos);
+  return Value(arg_0.substr(0, last_pos));
 }
 
-std::string expr_substringAfter(const std::vector<std::string> &args) {
-  return args[0].substr(args[0].find(args[1]) + args[1].length());
+Value expr_substringAfter(const std::vector<Value> &args) {
+  const std::string &arg_0 = args[0].asString();
+  const std::string &arg_1 = args[1].asString();
+  return Value(arg_0.substr(arg_0.find(arg_1) + arg_1.length()));
 }
 
-std::string expr_substringAfterLast(const std::vector<std::string> &args) {
+Value expr_substringAfterLast(const std::vector<Value> &args) {
   size_t last_pos = 0;
-  while (args[0].find(args[1], last_pos + 1) != std::string::npos) {
-    last_pos = args[0].find(args[1], last_pos + 1);
+  const std::string &arg_0 = args[0].asString();
+  const std::string &arg_1 = args[1].asString();
+  while (arg_0.find(arg_1, last_pos + 1) != std::string::npos) {
+    last_pos = arg_0.find(arg_1, last_pos + 1);
   }
-  return args[0].substr(last_pos + args[1].length());
+  return Value(arg_0.substr(last_pos + arg_1.length()));
 }
 
-std::string expr_startsWith(const std::vector<std::string> &args) {
-  if (args[0].substr(0, args[1].length()) == args[1]) {
-    return "true";
-  } else {
-    return "false";
-  }
+Value expr_startsWith(const std::vector<Value> &args) {
+  const std::string &arg_0 = args[0].asString();
+  const std::string &arg_1 = args[1].asString();
+  return Value(arg_0.substr(0, arg_1.length()) == arg_1);
 }
 
-std::string expr_endsWith(const std::vector<std::string> &args) {
-  if (args[0].substr(args[0].length() - args[1].length()) == args[1]) {
-    return "true";
-  } else {
-    return "false";
-  }
+Value expr_endsWith(const std::vector<Value> &args) {
+  const std::string &arg_0 = args[0].asString();
+  const std::string &arg_1 = args[1].asString();
+  return Value(arg_0.substr(arg_0.length() - arg_1.length()) == arg_1);
 }
 
-std::string expr_contains(const std::vector<std::string> &args) {
-  if (std::string::npos != args[0].find(args[1])) {
-    return "true";
-  } else {
-    return "false";
-  }
+Value expr_contains(const std::vector<Value> &args) {
+  return Value(std::string::npos != args[0].asString().find(args[1].asString()));
 }
 
-std::string expr_in(const std::vector<std::string> &args) {
+Value expr_in(const std::vector<Value> &args) {
+  const std::string &arg_0 = args[0].asString();
   for (int i = 1; i < args.size(); i++) {
-    if (args[0] == args[i]) {
-      return "true";
+    if (arg_0 == args[i].asString()) {
+      return Value(true);
     }
   }
 
-  return "false";
+  return Value(false);
 }
 
-std::string expr_indexOf(const std::vector<std::string> &args) {
-  auto pos = args[0].find(args[1]);
+Value expr_indexOf(const std::vector<Value> &args) {
+  auto pos = args[0].asString().find(args[1].asString());
 
   if (pos == std::string::npos) {
-    return "-1";
+    return Value(static_cast<int64_t >(-1));
   } else {
-    return std::to_string(pos);
+    return Value(static_cast<int64_t >(pos));
   }
 }
 
-std::string expr_lastIndexOf(const std::vector<std::string> &args) {
+Value expr_lastIndexOf(const std::vector<Value> &args) {
   size_t pos = std::string::npos;
-  auto cur_pos = args[0].find(args[1], 0);
+  const std::string &arg_0 = args[0].asString();
+  const std::string &arg_1 = args[1].asString();
+  auto cur_pos = arg_0.find(arg_1, 0);
 
   while (cur_pos != std::string::npos) {
     pos = cur_pos;
-    cur_pos = args[0].find(args[1], pos + 1);
+    cur_pos = arg_0.find(arg_1, pos + 1);
   }
 
   if (pos == std::string::npos) {
-    return "-1";
+    return Value(static_cast<int64_t >(-1));
   } else {
-    return std::to_string(pos);
+    return Value(static_cast<int64_t >(pos));
   }
 }
 
 #ifdef EXPRESSION_LANGUAGE_USE_REGEX
 
-std::string expr_replace(const std::vector<std::string> &args) {
-  std::string result = args[0];
-  const std::string find = args[1];
-  const std::string replace = args[2];
+Value expr_replace(const std::vector<Value> &args) {
+  std::string result = args[0].asString();
+  const std::string &find = args[1].asString();
+  const std::string &replace = args[2].asString();
 
   std::string::size_type match_pos = 0;
   match_pos = result.find(find, match_pos);
@@ -180,126 +185,102 @@ std::string expr_replace(const std::vector<std::string> &args) {
     match_pos = result.find(find, match_pos + replace.size());
   }
 
-  return result;
+  return Value(result);
 }
 
-std::string expr_replaceFirst(const std::vector<std::string> &args) {
-  std::string result = args[0];
-  const std::regex find(args[1]);
-  const std::string replace = args[2];
-  return std::regex_replace(result, find, replace, std::regex_constants::format_first_only);
+Value expr_replaceFirst(const std::vector<Value> &args) {
+  std::string result = args[0].asString();
+  const std::regex find(args[1].asString());
+  const std::string &replace = args[2].asString();
+  return Value(std::regex_replace(result, find, replace, std::regex_constants::format_first_only));
 }
 
-std::string expr_replaceAll(const std::vector<std::string> &args) {
-  std::string result = args[0];
-  const std::regex find(args[1]);
-  const std::string replace = args[2];
-  return std::regex_replace(result, find, replace);
+Value expr_replaceAll(const std::vector<Value> &args) {
+  std::string result = args[0].asString();
+  const std::regex find(args[1].asString());
+  const std::string &replace = args[2].asString();
+  return Value(std::regex_replace(result, find, replace));
 }
 
-std::string expr_replaceNull(const std::vector<std::string> &args) {
-  if (args[0].empty()) {
+Value expr_replaceNull(const std::vector<Value> &args) {
+  if (args[0].isNull()) {
     return args[1];
   } else {
     return args[0];
   }
 }
 
-std::string expr_replaceEmpty(const std::vector<std::string> &args) {
-  std::string result = args[0];
+Value expr_replaceEmpty(const std::vector<Value> &args) {
+  std::string result = args[0].asString();
   const std::regex find("^[ \n\r\t]*$");
-  const std::string replace = args[1];
-  return std::regex_replace(result, find, replace);
+  const std::string &replace = args[1].asString();
+  return Value(std::regex_replace(result, find, replace));
 }
 
-std::string expr_matches(const std::vector<std::string> &args) {
-  const auto &subject = args[0];
-  const std::regex expr = std::regex(args[1]);
+Value expr_matches(const std::vector<Value> &args) {
+  const auto &subject = args[0].asString();
+  const std::regex expr = std::regex(args[1].asString());
 
-  if (std::regex_match(subject.begin(), subject.end(), expr)) {
-    return "true";
-  } else {
-    return "false";
-  }
+  return Value(std::regex_match(subject.begin(), subject.end(), expr));
 }
 
-std::string expr_find(const std::vector<std::string> &args) {
-  const auto &subject = args[0];
-  const std::regex expr = std::regex(args[1]);
+Value expr_find(const std::vector<Value> &args) {
+  const auto &subject = args[0].asString();
+  const std::regex expr = std::regex(args[1].asString());
 
-  if (std::regex_search(subject.begin(), subject.end(), expr)) {
-    return "true";
-  } else {
-    return "false";
-  }
+  return Value(std::regex_search(subject.begin(), subject.end(), expr));
 }
 
 #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) {
+Value expr_binary_op(const std::vector<Value> &args,
+                     long double (*ldop)(long double, long double),
+                     int64_t (*iop)(int64_t, int64_t),
+                     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])));
+    if (!long_only && !args[0].isDecimal() && !args[1].isDecimal()) {
+      return Value(iop(args[0].asSignedLong(), args[1].asSignedLong()));
     } 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;
+      return Value(ldop(args[0].asLongDouble(), args[1].asLongDouble()));
     }
   } catch (const std::exception &e) {
-    return "";
+    return Value();
   }
 }
 
-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; });
+Value expr_plus(const std::vector<Value> &args) {
+  return expr_binary_op(args,
+                        [](long double a, long double b) { return a + b; },
+                        [](int64_t a, int64_t 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; });
+Value expr_minus(const std::vector<Value> &args) {
+  return expr_binary_op(args,
+                        [](long double a, long double b) { return a - b; },
+                        [](int64_t a, int64_t 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; });
+Value expr_multiply(const std::vector<Value> &args) {
+  return expr_binary_op(args,
+                        [](long double a, long double b) { return a * b; },
+                        [](int64_t a, int64_t 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);
+Value expr_divide(const std::vector<Value> &args) {
+  return expr_binary_op(args,
+                        [](long double a, long double b) { return a / b; },
+                        [](int64_t a, int64_t 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; });
+Value expr_mod(const std::vector<Value> &args) {
+  return expr_binary_op(args,
+                        [](long double a, long double b) { return std::fmod(a, b); },
+                        [](int64_t a, int64_t b) { return a % b; });
 }
 
-std::string expr_toRadix(const std::vector<std::string> &args) {
-  int radix = std::stoi(args[1]);
+Value expr_toRadix(const std::vector<Value> &args) {
+  int64_t radix = args[1].asSignedLong();
 
   if (radix < 2 || radix > 36) {
     throw std::runtime_error("Cannot perform conversion due to invalid radix");
@@ -308,10 +289,11 @@ std::string expr_toRadix(const std::vector<std::string> &args) {
   int pad_width = 0;
 
   if (args.size() > 2) {
-    pad_width = std::stoi(args[2]);
+    pad_width = static_cast<int>(args[2].asUnsignedLong());
   }
 
-  auto value = std::stoll(args[0], nullptr, 10);
+//  auto value = std::stoll(args[0].asString(), nullptr, 10);
+  auto value = args[0].asSignedLong();
 
   std::string sign;
 
@@ -334,27 +316,27 @@ std::string expr_toRadix(const std::vector<std::string> &args) {
 
   std::stringstream ss;
   ss << sign << std::setfill('0') << std::setw(pad_width) << str_num;
-  return ss.str();
+  return Value(ss.str());
 }
 
-std::string expr_fromRadix(const std::vector<std::string> &args) {
-  int radix = std::stoi(args[1]);
+Value expr_fromRadix(const std::vector<Value> &args) {
+  auto radix = args[1].asSignedLong();
 
   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));
+  return Value(std::to_string(std::stoll(args[0].asString(), nullptr, radix)));
 }
 
-std::string expr_random(const std::vector<std::string> &args) {
+Value expr_random(const std::vector<Value> &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));
+  std::uniform_int_distribution<int64_t> distribution(0, LLONG_MAX);
+  return Value(distribution(generator));
 }
 
-template<std::string T(const std::vector<std::string> &)>
+template<Value T(const std::vector<Value> &)>
 Expression make_dynamic_function_incomplete(const std::string &function_name,
                                             const std::vector<Expression> &args,
                                             std::size_t num_args) {
@@ -371,8 +353,8 @@ Expression make_dynamic_function_incomplete(const std::string &function_name,
     throw std::runtime_error(message_ss.str());
   }
 
-  auto result = make_dynamic([=](const Parameters &params) -> std::string {
-    std::vector<std::string> evaluated_args;
+  auto result = make_dynamic([=](const Parameters &params) -> Value {
+    std::vector<Value> evaluated_args;
 
     for (const auto &arg : args) {
       evaluated_args.emplace_back(arg(params));
@@ -384,7 +366,7 @@ Expression make_dynamic_function_incomplete(const std::string &function_name,
   return result;
 }
 
-std::string expr_literal(const std::vector<std::string> &args) {
+Value expr_literal(const std::vector<Value> &args) {
   return args[0];
 }
 
@@ -485,37 +467,34 @@ Expression Expression::operator+(const Expression &other_expr) const {
   if (isDynamic() && other_expr.isDynamic()) {
     auto val_fn = val_fn_;
     auto other_val_fn = other_expr.val_fn_;
-    return make_dynamic([val_fn, other_val_fn](const Parameters &params) -> std::string {
-      std::string result = val_fn(params);
-      result.append(other_val_fn(params));
-      return result;
+    return make_dynamic([val_fn, other_val_fn](const Parameters &params) -> Value {
+      Value result = val_fn(params);
+      return Value(result.asString().append(other_val_fn(params).asString()));
     });
   } else if (isDynamic() && !other_expr.isDynamic()) {
     auto val_fn = val_fn_;
     auto other_val = other_expr.val_;
-    return make_dynamic([val_fn, other_val](const Parameters &params) -> std::string {
-      std::string result = val_fn(params);
-      result.append(other_val);
-      return result;
+    return make_dynamic([val_fn, other_val](const Parameters &params) -> Value {
+      Value result = val_fn(params);
+      return Value(result.asString().append(other_val.asString()));
     });
   } else if (!isDynamic() && other_expr.isDynamic()) {
     auto val = val_;
     auto other_val_fn = other_expr.val_fn_;
-    return make_dynamic([val, other_val_fn](const Parameters &params) -> std::string {
-      std::string result(val);
-      result.append(other_val_fn(params));
-      return result;
+    return make_dynamic([val, other_val_fn](const Parameters &params) -> Value {
+      Value result(val);
+      return Value(result.asString().append(other_val_fn(params).asString()));
     });
   } else if (!isDynamic() && !other_expr.isDynamic()) {
-    std::string result(val_);
-    result.append(other_expr.val_);
+    std::string result(val_.asString());
+    result.append(other_expr.val_.asString());
     return make_static(result);
   } else {
     throw std::runtime_error("Invalid function composition");
   }
 }
 
-std::string Expression::operator()(const Parameters &params) const {
+Value Expression::operator()(const Parameters &params) const {
   if (isDynamic()) {
     return val_fn_(params);
   } else {

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/c4ea662c/extensions/expression-language/ProcessContextExpr.cpp
----------------------------------------------------------------------
diff --git a/extensions/expression-language/ProcessContextExpr.cpp b/extensions/expression-language/ProcessContextExpr.cpp
index c52b18c..68cdc54 100644
--- a/extensions/expression-language/ProcessContextExpr.cpp
+++ b/extensions/expression-language/ProcessContextExpr.cpp
@@ -32,7 +32,7 @@ bool ProcessContext::getProperty(const std::string &name, std::string &value,
     expressions_.emplace(name, expression::compile(expression_str));
   }
 
-  value = expressions_[name]({flow_file});
+  value = expressions_[name]({flow_file}).asString();
   return true;
 }
 
@@ -45,7 +45,7 @@ bool ProcessContext::getDynamicProperty(const std::string &name, std::string &va
     dynamic_property_expressions_.emplace(name, expression::compile(expression_str));
   }
 
-  value = dynamic_property_expressions_[name]({flow_file});
+  value = dynamic_property_expressions_[name]({flow_file}).asString();
   return true;
 }
 

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/c4ea662c/extensions/expression-language/common/Value.h
----------------------------------------------------------------------
diff --git a/extensions/expression-language/common/Value.h b/extensions/expression-language/common/Value.h
new file mode 100644
index 0000000..75c80bc
--- /dev/null
+++ b/extensions/expression-language/common/Value.h
@@ -0,0 +1,238 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <limits>
+
+#ifndef NIFI_MINIFI_CPP_VALUE_H
+#define NIFI_MINIFI_CPP_VALUE_H
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace expression {
+
+
+/**
+ * Represents an expression value, which can be one of multiple types or NULL
+ */
+class Value {
+ public:
+
+  /**
+   * Construct a default (NULL) value
+   */
+  Value() {
+  }
+
+  /**
+   * Construct a string value
+   */
+  explicit Value(const std::string &val) {
+    setString(val);
+  }
+
+  /**
+   * Construct a boolean value
+   */
+  explicit Value(bool val) {
+    setBoolean(val);
+  }
+
+  /**
+   * Construct an unsigned long value
+   */
+  explicit Value(uint64_t val) {
+    setUnsignedLong(val);
+  }
+
+  /**
+   * Construct a signed long value
+   */
+  explicit Value(int64_t val) {
+    setSignedLong(val);
+  }
+
+  /**
+   * Construct a long double value
+   */
+  explicit Value(long double val) {
+    setLongDouble(val);
+  }
+
+  bool isNull() const {
+    return is_null_;
+  };
+
+  bool isString() const {
+    return is_string_;
+  };
+
+  bool isDecimal() const {
+    if (is_long_double_) {
+      return true;
+    } else if (is_string_ && (string_val_.find('.') != string_val_.npos ||
+        string_val_.find('e') != string_val_.npos ||
+        string_val_.find('E') != string_val_.npos)) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  void setSignedLong(int64_t val) {
+    is_null_ = false;
+    is_bool_ = false;
+    is_signed_long_ = true;
+    is_unsigned_long_ = false;
+    is_long_double_ = false;
+    is_string_ = false;
+    signed_long_val_ = val;
+  }
+
+  void setUnsignedLong(uint64_t val) {
+    is_null_ = false;
+    is_bool_ = false;
+    is_signed_long_ = false;
+    is_unsigned_long_ = true;
+    is_long_double_ = false;
+    is_string_ = false;
+    unsigned_long_val_ = val;
+  }
+
+  void setLongDouble(long double val) {
+    is_null_ = false;
+    is_bool_ = false;
+    is_signed_long_ = false;
+    is_unsigned_long_ = false;
+    is_long_double_ = true;
+    is_string_ = false;
+    long_double_val_ = val;
+  }
+
+  void setBoolean(bool val) {
+    is_null_ = false;
+    is_bool_ = true;
+    is_signed_long_ = false;
+    is_unsigned_long_ = false;
+    is_long_double_ = false;
+    is_string_ = false;
+    bool_val_ = val;
+  }
+
+  void setString(const std::string &val) {
+    is_null_ = false;
+    is_bool_ = false;
+    is_signed_long_ = false;
+    is_unsigned_long_ = false;
+    is_long_double_ = false;
+    is_string_ = true;
+    string_val_ = val;
+  }
+
+  std::string asString() const {
+    if (is_string_) {
+      return string_val_;
+    } else if (is_bool_) {
+      if (bool_val_) {
+        return "true";
+      } else {
+        return "false";
+      }
+    } else if (is_signed_long_) {
+      return std::to_string(signed_long_val_);
+    } else if (is_unsigned_long_) {
+      return std::to_string(unsigned_long_val_);
+    } else if (is_long_double_) {
+      std::stringstream ss;
+      ss << std::fixed << std::setprecision(std::numeric_limits<double>::digits10)
+         << long_double_val_;
+      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;
+    } else {
+      return "";
+    }
+  }
+
+  uint64_t asUnsignedLong() const {
+    if (is_unsigned_long_) {
+      return unsigned_long_val_;
+    } else if (is_string_) {
+      return std::stoul(string_val_);
+    } else {
+      return 0.0;
+    }
+  }
+
+  int64_t asSignedLong() const {
+    if (is_signed_long_) {
+      return signed_long_val_;
+    } else if (is_unsigned_long_) {
+      return unsigned_long_val_;
+    } else if (is_string_) {
+      return std::stoul(string_val_);
+    } else if (is_long_double_) {
+      return long_double_val_;
+    } else {
+      return 0.0;
+    }
+  }
+
+  long double asLongDouble() const {
+    if (is_signed_long_) {
+      return signed_long_val_;
+    } else if (is_unsigned_long_) {
+      return unsigned_long_val_;
+    } else if (is_long_double_) {
+      return long_double_val_;
+    } else if (is_string_) {
+      return std::stold(string_val_);
+    } else {
+      return 0.0;
+    }
+  }
+
+ private:
+  bool is_null_ = true;
+  bool is_string_ = false;
+  bool is_bool_ = false;
+  bool is_unsigned_long_ = false;
+  bool is_signed_long_ = false;
+  bool is_long_double_ = false;
+  bool bool_val_ = false;
+  uint64_t unsigned_long_val_ = 0;
+  int64_t signed_long_val_ = 0;
+  long double long_double_val_ = 0.0;
+  std::string string_val_ = "";
+};
+
+} /* namespace expression */
+} /* namespace minifi */
+} /* namespace nifi */
+} /* namespace apache */
+} /* namespace org */
+
+#endif //NIFI_MINIFI_CPP_VALUE_H

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/c4ea662c/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 9ee4a54..f123f77 100644
--- a/extensions/expression-language/impl/expression/Expression.h
+++ b/extensions/expression-language/impl/expression/Expression.h
@@ -25,6 +25,7 @@
 #undef EXPRESSION_LANGUAGE_USE_REGEX
 #endif
 
+#include <Value.h>
 #include <core/FlowFile.h>
 
 #include <string>
@@ -41,7 +42,7 @@ typedef struct {
   std::weak_ptr<core::FlowFile> flow_file;
 } Parameters;
 
-static const std::function<std::string(const Parameters &params)> NOOP_FN;
+static const std::function<Value(const Parameters &params)> NOOP_FN;
 
 /**
  * A NiFi expression language expression.
@@ -49,11 +50,15 @@ static const std::function<std::string(const Parameters &params)> NOOP_FN;
 class Expression {
  public:
 
-  explicit Expression(std::string val = "",
-                      std::function<std::string(const Parameters &)> val_fn = NOOP_FN)
-      : val_(std::move(val)),
-        val_fn_(std::move(val_fn)),
+  Expression() {
+    val_fn_ = NOOP_FN;
+  }
+
+  explicit Expression(Value val,
+                      std::function<Value(const Parameters &)> val_fn = NOOP_FN)
+      : val_fn_(std::move(val_fn)),
         fn_args_() {
+    val_ = val;
   }
 
   /**
@@ -80,11 +85,11 @@ class Expression {
    * @param params
    * @return dynamically-computed result of expression
    */
-  std::string operator()(const Parameters &params) const;
+  Value operator()(const Parameters &params) const;
 
  protected:
-  std::string val_;
-  std::function<std::string(const Parameters &params)> val_fn_;
+  Value val_;
+  std::function<Value(const Parameters &params)> val_fn_;
   std::vector<Expression> fn_args_;
 };
 
@@ -110,7 +115,7 @@ Expression make_static(std::string val);
  * @param val_fn
  * @return
  */
-Expression make_dynamic(std::function<std::string(const Parameters &params)> val_fn);
+Expression make_dynamic(const std::function<Value(const Parameters &params)> &val_fn);
 
 /**
  * Creates a dynamic expression which evaluates the given flow file attribute.

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/c4ea662c/extensions/expression-language/noop/expression/Expression.h
----------------------------------------------------------------------
diff --git a/extensions/expression-language/noop/expression/Expression.h b/extensions/expression-language/noop/expression/Expression.h
index 08c4ae4..5cbebfe 100644
--- a/extensions/expression-language/noop/expression/Expression.h
+++ b/extensions/expression-language/noop/expression/Expression.h
@@ -19,6 +19,7 @@
 #define NIFI_MINIFI_CPP_EXPRESSION_H
 
 #include <core/FlowFile.h>
+#include <Value.h>
 
 namespace org {
 namespace apache {

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/c4ea662c/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 da0d685..de3292e 100644
--- a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp
+++ b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp
@@ -32,24 +32,24 @@ class MockFlowFile : public core::FlowFile {
 };
 
 TEST_CASE("Trivial static expression", "[expressionLanguageTestTrivialStaticExpr]") {  // NOLINT
-  REQUIRE("a" == expression::make_static("a")({}));
+  REQUIRE("a" == expression::make_static("a")({}).asString());
 }
 
 TEST_CASE("Text expression", "[expressionLanguageTestTextExpression]") {  // NOLINT
   auto expr = expression::compile("text");
-  REQUIRE("text" == expr({}));
+  REQUIRE("text" == expr({}).asString());
 }
 
 TEST_CASE("Text expression with escaped dollar", "[expressionLanguageTestEscapedDollar]") {  // NOLINT
   auto expr = expression::compile("te$$xt");
-  REQUIRE("te$xt" == expr({}));
+  REQUIRE("te$xt" == expr({}).asString());
 }
 
 TEST_CASE("Attribute expression", "[expressionLanguageTestAttributeExpression]") {  // NOLINT
   auto flow_file = std::make_shared<MockFlowFile>();
   flow_file->addAttribute("attr_a", "__attr_value_a__");
   auto expr = expression::compile("text_before${attr_a}text_after");
-  REQUIRE("text_before__attr_value_a__text_after" == expr({flow_file}));
+  REQUIRE("text_before__attr_value_a__text_after" == expr({flow_file}).asString());
 }
 
 TEST_CASE("Multi-attribute expression", "[expressionLanguageTestMultiAttributeExpression]") {  // NOLINT
@@ -57,7 +57,7 @@ TEST_CASE("Multi-attribute expression", "[expressionLanguageTestMultiAttributeEx
   flow_file->addAttribute("attr_a", "__attr_value_a__");
   flow_file->addAttribute("attr_b", "__attr_value_b__");
   auto expr = expression::compile("text_before${attr_a}text_between${attr_b}text_after");
-  REQUIRE("text_before__attr_value_a__text_between__attr_value_b__text_after" == expr({flow_file}));
+  REQUIRE("text_before__attr_value_a__text_between__attr_value_b__text_after" == expr({flow_file}).asString());
 }
 
 TEST_CASE("Multi-flowfile attribute expression",
@@ -66,18 +66,18 @@ TEST_CASE("Multi-flowfile attribute expression",
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr_a", "__flow_a_attr_value_a__");
-  REQUIRE("text_before__flow_a_attr_value_a__text_after" == expr({flow_file_a}));
+  REQUIRE("text_before__flow_a_attr_value_a__text_after" == expr({flow_file_a}).asString());
 
   auto flow_file_b = std::make_shared<MockFlowFile>();
   flow_file_b->addAttribute("attr_a", "__flow_b_attr_value_a__");
-  REQUIRE("text_before__flow_b_attr_value_a__text_after" == expr({flow_file_b}));
+  REQUIRE("text_before__flow_b_attr_value_a__text_after" == expr({flow_file_b}).asString());
 }
 
 TEST_CASE("Attribute expression with whitespace", "[expressionLanguageTestAttributeExpressionWhitespace]") {  // NOLINT
   auto flow_file = std::make_shared<MockFlowFile>();
   flow_file->addAttribute("attr_a", "__attr_value_a__");
   auto expr = expression::compile("text_before${\n\tattr_a \r}text_after");
-  REQUIRE("text_before__attr_value_a__text_after" == expr({flow_file}));
+  REQUIRE("text_before__attr_value_a__text_after" == expr({flow_file}).asString());
 }
 
 TEST_CASE("Special characters expression", "[expressionLanguageTestSpecialCharactersExpression]") {  // NOLINT
@@ -85,7 +85,7 @@ TEST_CASE("Special characters expression", "[expressionLanguageTestSpecialCharac
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr_a", "__flow_a_attr_value_a__");
-  REQUIRE("text_before|{}()[],:;\\/*#'\" \t\r\n__flow_a_attr_value_a__}()text_after" == expr({flow_file_a}));
+  REQUIRE("text_before|{}()[],:;\\/*#'\" \t\r\n__flow_a_attr_value_a__}()text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("UTF-8 characters expression", "[expressionLanguageTestUTF8Expression]") {  // NOLINT
@@ -93,7 +93,7 @@ TEST_CASE("UTF-8 characters expression", "[expressionLanguageTestUTF8Expression]
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr_a", "__flow_a_attr_value_a__");
-  REQUIRE("text_before¥£€¢₡₢₣₤₥₦₧₨₩₪₫₭₮₯₹__flow_a_attr_value_a__text_after" == expr({flow_file_a}));
+  REQUIRE("text_before¥£€¢₡₢₣₤₥₦₧₨₩₪₫₭₮₯₹__flow_a_attr_value_a__text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("UTF-8 characters attribute", "[expressionLanguageTestUTF8Attribute]") {  // NOLINT
@@ -101,7 +101,7 @@ TEST_CASE("UTF-8 characters attribute", "[expressionLanguageTestUTF8Attribute]")
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr_a", "__¥£€¢₡₢₣₤₥₦₧₨₩₪₫₭₮₯₹__");
-  REQUIRE("text_before__¥£€¢₡₢₣₤₥₦₧₨₩₪₫₭₮₯₹__text_after" == expr({flow_file_a}));
+  REQUIRE("text_before__¥£€¢₡₢₣₤₥₦₧₨₩₪₫₭₮₯₹__text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Single quoted attribute expression", "[expressionLanguageTestSingleQuotedAttributeExpression]") {  // NOLINT
@@ -109,7 +109,7 @@ TEST_CASE("Single quoted attribute expression", "[expressionLanguageTestSingleQu
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("|{}()[],:;\\/*# \t\r\n$", "__flow_a_attr_value_a__");
-  REQUIRE("text_before__flow_a_attr_value_a__text_after" == expr({flow_file_a}));
+  REQUIRE("text_before__flow_a_attr_value_a__text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Double quoted attribute expression", "[expressionLanguageTestDoubleQuotedAttributeExpression]") {  // NOLINT
@@ -117,7 +117,7 @@ TEST_CASE("Double quoted attribute expression", "[expressionLanguageTestDoubleQu
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("|{}()[],:;\\/*# \t\r\n$", "__flow_a_attr_value_a__");
-  REQUIRE("text_before__flow_a_attr_value_a__text_after" == expr({flow_file_a}));
+  REQUIRE("text_before__flow_a_attr_value_a__text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Hostname function", "[expressionLanguageTestHostnameFunction]") {  // NOLINT
@@ -131,7 +131,7 @@ TEST_CASE("Hostname function", "[expressionLanguageTestHostnameFunction]") {  //
   expected.append("text_after");
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
-  REQUIRE(expected == expr({flow_file_a}));
+  REQUIRE(expected == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("ToUpper function", "[expressionLanguageTestToUpperFunction]") {  // NOLINT
@@ -140,14 +140,14 @@ TEST_CASE("ToUpper function", "[expressionLanguageTestToUpperFunction]") {  // N
                                      }text_after)");
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr_a", "__flow_a_attr_value_a__");
-  REQUIRE("text_before__FLOW_A_ATTR_VALUE_A__text_after" == expr({flow_file_a}));
+  REQUIRE("text_before__FLOW_A_ATTR_VALUE_A__text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("ToUpper function w/o whitespace", "[expressionLanguageTestToUpperFunctionWithoutWhitespace]") {  // NOLINT
   auto expr = expression::compile(R"(text_before${attr_a:toUpper()}text_after)");
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr_a", "__flow_a_attr_value_a__");
-  REQUIRE("text_before__FLOW_A_ATTR_VALUE_A__text_after" == expr({flow_file_a}));
+  REQUIRE("text_before__FLOW_A_ATTR_VALUE_A__text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("ToLower function", "[expressionLanguageTestToLowerFunction]") {  // NOLINT
@@ -156,7 +156,7 @@ TEST_CASE("ToLower function", "[expressionLanguageTestToLowerFunction]") {  // N
                                      }text_after)");
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr_a", "__FLOW_A_ATTR_VALUE_A__");
-  REQUIRE("text_before__flow_a_attr_value_a__text_after" == expr({flow_file_a}));
+  REQUIRE("text_before__flow_a_attr_value_a__text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("GetFile PutFile dynamic attribute", "[expressionLanguageTestGetFilePutFileDynamicAttribute]") {  // NOLINT
@@ -257,7 +257,7 @@ TEST_CASE("Substring 2 arg", "[expressionLanguageSubstring2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__");
-  REQUIRE("text_before_a_attr_text_after" == expr({flow_file_a}));
+  REQUIRE("text_before_a_attr_text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Substring 1 arg", "[expressionLanguageSubstring1]") {  // NOLINT
@@ -265,7 +265,7 @@ TEST_CASE("Substring 1 arg", "[expressionLanguageSubstring1]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__");
-  REQUIRE("text_before_a_attr_value_a__text_after" == expr({flow_file_a}));
+  REQUIRE("text_before_a_attr_value_a__text_after" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Substring Before", "[expressionLanguageSubstringBefore]") {  // NOLINT
@@ -273,7 +273,7 @@ TEST_CASE("Substring Before", "[expressionLanguageSubstringBefore]") {  // NOLIN
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__");
-  REQUIRE("__flow_a_" == expr({flow_file_a}));
+  REQUIRE("__flow_a_" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Substring Before Last", "[expressionLanguageSubstringBeforeLast]") {  // NOLINT
@@ -281,7 +281,7 @@ TEST_CASE("Substring Before Last", "[expressionLanguageSubstringBeforeLast]") {
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__");
-  REQUIRE("__flow_a_attr_value" == expr({flow_file_a}));
+  REQUIRE("__flow_a_attr_value" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Substring After", "[expressionLanguageSubstringAfter]") {  // NOLINT
@@ -289,7 +289,7 @@ TEST_CASE("Substring After", "[expressionLanguageSubstringAfter]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__");
-  REQUIRE("_attr_value_a__" == expr({flow_file_a}));
+  REQUIRE("_attr_value_a__" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Substring After Last", "[expressionLanguageSubstringAfterLast]") {  // NOLINT
@@ -297,7 +297,7 @@ TEST_CASE("Substring After Last", "[expressionLanguageSubstringAfterLast]") {  /
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__");
-  REQUIRE("__" == expr({flow_file_a}));
+  REQUIRE("__" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Starts With", "[expressionLanguageStartsWith]") {  // NOLINT
@@ -305,7 +305,7 @@ TEST_CASE("Starts With", "[expressionLanguageStartsWith]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "A BRAND TEST");
-  REQUIRE("false" == expr({flow_file_a}));
+  REQUIRE("false" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Starts With 2", "[expressionLanguageStartsWith2]") {  // NOLINT
@@ -313,7 +313,7 @@ TEST_CASE("Starts With 2", "[expressionLanguageStartsWith2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand TEST");
-  REQUIRE("true" == expr({flow_file_a}));
+  REQUIRE("true" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Ends With", "[expressionLanguageEndsWith]") {  // NOLINT
@@ -321,7 +321,7 @@ TEST_CASE("Ends With", "[expressionLanguageEndsWith]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.TXT");
-  REQUIRE("false" == expr({flow_file_a}));
+  REQUIRE("false" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Ends With 2", "[expressionLanguageEndsWith2]") {  // NOLINT
@@ -329,7 +329,7 @@ TEST_CASE("Ends With 2", "[expressionLanguageEndsWith2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.TXT");
-  REQUIRE("true" == expr({flow_file_a}));
+  REQUIRE("true" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Contains", "[expressionLanguageContains]") {  // NOLINT
@@ -337,7 +337,7 @@ TEST_CASE("Contains", "[expressionLanguageContains]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("true" == expr({flow_file_a}));
+  REQUIRE("true" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Contains 2", "[expressionLanguageContains2]") {  // NOLINT
@@ -345,7 +345,7 @@ TEST_CASE("Contains 2", "[expressionLanguageContains2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("false" == expr({flow_file_a}));
+  REQUIRE("false" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("In", "[expressionLanguageIn]") {  // NOLINT
@@ -353,7 +353,7 @@ TEST_CASE("In", "[expressionLanguageIn]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "JOHN");
-  REQUIRE("true" == expr({flow_file_a}));
+  REQUIRE("true" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("In 2", "[expressionLanguageIn2]") {  // NOLINT
@@ -361,7 +361,7 @@ TEST_CASE("In 2", "[expressionLanguageIn2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "JOHN");
-  REQUIRE("false" == expr({flow_file_a}));
+  REQUIRE("false" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Substring Before No Args", "[expressionLanguageSubstringBeforeNoArgs]") {  // NOLINT
@@ -381,7 +381,7 @@ TEST_CASE("Replace", "[expressionLanguageReplace]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("a brand new filename_txt" == expr({flow_file_a}));
+  REQUIRE("a brand new filename_txt" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace 2", "[expressionLanguageReplace2]") {  // NOLINT
@@ -389,7 +389,7 @@ TEST_CASE("Replace 2", "[expressionLanguageReplace2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("a.brand.new.filename.txt" == expr({flow_file_a}));
+  REQUIRE("a.brand.new.filename.txt" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace First", "[expressionLanguageReplaceFirst]") {  // NOLINT
@@ -397,7 +397,7 @@ TEST_CASE("Replace First", "[expressionLanguageReplaceFirst]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("the brand new filename.txt" == expr({flow_file_a}));
+  REQUIRE("the brand new filename.txt" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace First Regex", "[expressionLanguageReplaceFirstRegex]") {  // NOLINT
@@ -405,7 +405,7 @@ TEST_CASE("Replace First Regex", "[expressionLanguageReplaceFirstRegex]") {  //
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("a grand new filename.txt" == expr({flow_file_a}));
+  REQUIRE("a grand new filename.txt" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace All", "[expressionLanguageReplaceAll]") {  // NOLINT
@@ -413,7 +413,7 @@ TEST_CASE("Replace All", "[expressionLanguageReplaceAll]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("a brand new filename" == expr({flow_file_a}));
+  REQUIRE("a brand new filename" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace All 2", "[expressionLanguageReplaceAll2]") {  // NOLINT
@@ -421,7 +421,7 @@ TEST_CASE("Replace All 2", "[expressionLanguageReplaceAll2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("new filename.txt" == expr({flow_file_a}));
+  REQUIRE("new filename.txt" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace All 3", "[expressionLanguageReplaceAll3]") {  // NOLINT
@@ -429,7 +429,7 @@ TEST_CASE("Replace All 3", "[expressionLanguageReplaceAll3]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("a brand new filename.txt" == expr({flow_file_a}));
+  REQUIRE("a brand new filename.txt" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace Null", "[expressionLanguageReplaceNull]") {  // NOLINT
@@ -437,7 +437,7 @@ TEST_CASE("Replace Null", "[expressionLanguageReplaceNull]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("a brand new filename.txt" == expr({flow_file_a}));
+  REQUIRE("a brand new filename.txt" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace Null 2", "[expressionLanguageReplaceNull2]") {  // NOLINT
@@ -445,7 +445,7 @@ TEST_CASE("Replace Null 2", "[expressionLanguageReplaceNull2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr2", "a brand new filename.txt");
-  REQUIRE("abc" == expr({flow_file_a}));
+  REQUIRE("abc" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace Empty", "[expressionLanguageReplaceEmpty]") {  // NOLINT
@@ -453,7 +453,7 @@ TEST_CASE("Replace Empty", "[expressionLanguageReplaceEmpty]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("a brand new filename.txt" == expr({flow_file_a}));
+  REQUIRE("a brand new filename.txt" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace Empty 2", "[expressionLanguageReplaceEmpty2]") {  // NOLINT
@@ -461,7 +461,7 @@ TEST_CASE("Replace Empty 2", "[expressionLanguageReplaceEmpty2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "  \t  \r  \n  ");
-  REQUIRE("abc" == expr({flow_file_a}));
+  REQUIRE("abc" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Replace Empty 3", "[expressionLanguageReplaceEmpty2]") {  // NOLINT
@@ -469,7 +469,7 @@ TEST_CASE("Replace Empty 3", "[expressionLanguageReplaceEmpty2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr2", "test");
-  REQUIRE("abc" == expr({flow_file_a}));
+  REQUIRE("abc" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Matches", "[expressionLanguageMatches]") {  // NOLINT
@@ -477,7 +477,7 @@ TEST_CASE("Matches", "[expressionLanguageMatches]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "At:est");
-  REQUIRE("true" == expr({flow_file_a}));
+  REQUIRE("true" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Matches 2", "[expressionLanguageMatches2]") {  // NOLINT
@@ -485,7 +485,7 @@ TEST_CASE("Matches 2", "[expressionLanguageMatches2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "At:something");
-  REQUIRE("false" == expr({flow_file_a}));
+  REQUIRE("false" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Matches 3", "[expressionLanguageMatches3]") {  // NOLINT
@@ -493,7 +493,7 @@ TEST_CASE("Matches 3", "[expressionLanguageMatches3]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", " At:est");
-  REQUIRE("false" == expr({flow_file_a}));
+  REQUIRE("false" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Find", "[expressionLanguageFind]") {  // NOLINT
@@ -501,7 +501,7 @@ TEST_CASE("Find", "[expressionLanguageFind]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("true" == expr({flow_file_a}));
+  REQUIRE("true" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Find 2", "[expressionLanguageFind2]") {  // NOLINT
@@ -509,7 +509,7 @@ TEST_CASE("Find 2", "[expressionLanguageFind2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("false" == expr({flow_file_a}));
+  REQUIRE("false" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Find 3", "[expressionLanguageFind3]") {  // NOLINT
@@ -517,7 +517,7 @@ TEST_CASE("Find 3", "[expressionLanguageFind3]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("true" == expr({flow_file_a}));
+  REQUIRE("true" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("IndexOf", "[expressionLanguageIndexOf]") {  // NOLINT
@@ -525,7 +525,7 @@ TEST_CASE("IndexOf", "[expressionLanguageIndexOf]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("-1" == expr({flow_file_a}));
+  REQUIRE("-1" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("IndexOf2", "[expressionLanguageIndexOf2]") {  // NOLINT
@@ -533,7 +533,7 @@ TEST_CASE("IndexOf2", "[expressionLanguageIndexOf2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("20" == expr({flow_file_a}));
+  REQUIRE("20" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("IndexOf3", "[expressionLanguageIndexOf3]") {  // NOLINT
@@ -541,7 +541,7 @@ TEST_CASE("IndexOf3", "[expressionLanguageIndexOf3]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("0" == expr({flow_file_a}));
+  REQUIRE("0" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("IndexOf4", "[expressionLanguageIndexOf4]") {  // NOLINT
@@ -549,7 +549,7 @@ TEST_CASE("IndexOf4", "[expressionLanguageIndexOf4]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("1" == expr({flow_file_a}));
+  REQUIRE("1" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("LastIndexOf", "[expressionLanguageLastIndexOf]") {  // NOLINT
@@ -557,7 +557,7 @@ TEST_CASE("LastIndexOf", "[expressionLanguageLastIndexOf]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("-1" == expr({flow_file_a}));
+  REQUIRE("-1" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("LastIndexOf2", "[expressionLanguageLastIndexOf2]") {  // NOLINT
@@ -565,7 +565,7 @@ TEST_CASE("LastIndexOf2", "[expressionLanguageLastIndexOf2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("20" == expr({flow_file_a}));
+  REQUIRE("20" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("LastIndexOf3", "[expressionLanguageLastIndexOf3]") {  // NOLINT
@@ -573,7 +573,7 @@ TEST_CASE("LastIndexOf3", "[expressionLanguageLastIndexOf3]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("17" == expr({flow_file_a}));
+  REQUIRE("17" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("LastIndexOf4", "[expressionLanguageLastIndexOf4]") {  // NOLINT
@@ -581,7 +581,7 @@ TEST_CASE("LastIndexOf4", "[expressionLanguageLastIndexOf4]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "a brand new filename.txt");
-  REQUIRE("11" == expr({flow_file_a}));
+  REQUIRE("11" == expr({flow_file_a}).asString());
 }
 
 #endif  // EXPRESSION_LANGUAGE_USE_REGEX
@@ -591,7 +591,7 @@ TEST_CASE("Plus Integer", "[expressionLanguagePlusInteger]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11");
-  REQUIRE("24" == expr({flow_file_a}));
+  REQUIRE("24" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Plus Decimal", "[expressionLanguagePlusDecimal]") {  // NOLINT
@@ -599,7 +599,7 @@ TEST_CASE("Plus Decimal", "[expressionLanguagePlusDecimal]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11.1");
-  REQUIRE("-2.24567" == expr({flow_file_a}));
+  REQUIRE("-2.24567" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Plus Exponent", "[expressionLanguagePlusExponent]") {  // NOLINT
@@ -607,7 +607,7 @@ TEST_CASE("Plus Exponent", "[expressionLanguagePlusExponent]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11");
-  REQUIRE("10000011" == expr({flow_file_a}));
+  REQUIRE("10000011" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Plus Exponent 2", "[expressionLanguagePlusExponent2]") {  // NOLINT
@@ -615,7 +615,7 @@ TEST_CASE("Plus Exponent 2", "[expressionLanguagePlusExponent2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11.345678901234");
-  REQUIRE("10000011.345678901234351" == expr({flow_file_a}));
+  REQUIRE("10000011.345678901234351" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Minus Integer", "[expressionLanguageMinusInteger]") {  // NOLINT
@@ -623,7 +623,7 @@ TEST_CASE("Minus Integer", "[expressionLanguageMinusInteger]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11");
-  REQUIRE("-2" == expr({flow_file_a}));
+  REQUIRE("-2" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Minus Decimal", "[expressionLanguageMinusDecimal]") {  // NOLINT
@@ -631,7 +631,7 @@ TEST_CASE("Minus Decimal", "[expressionLanguageMinusDecimal]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11.1");
-  REQUIRE("24.44567" == expr({flow_file_a}));
+  REQUIRE("24.44567" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Multiply Integer", "[expressionLanguageMultiplyInteger]") {  // NOLINT
@@ -639,7 +639,7 @@ TEST_CASE("Multiply Integer", "[expressionLanguageMultiplyInteger]") {  // NOLIN
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11");
-  REQUIRE("143" == expr({flow_file_a}));
+  REQUIRE("143" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Multiply Decimal", "[expressionLanguageMultiplyDecimal]") {  // NOLINT
@@ -647,7 +647,7 @@ TEST_CASE("Multiply Decimal", "[expressionLanguageMultiplyDecimal]") {  // NOLIN
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11.1");
-  REQUIRE("-148.136937" == expr({flow_file_a}));
+  REQUIRE("-148.136937" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Divide Integer", "[expressionLanguageDivideInteger]") {  // NOLINT
@@ -655,7 +655,7 @@ TEST_CASE("Divide Integer", "[expressionLanguageDivideInteger]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11");
-  REQUIRE("0.846153846153846" == expr({flow_file_a}));
+  REQUIRE("0.846153846153846" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("Divide Decimal", "[expressionLanguageDivideDecimal]") {  // NOLINT
@@ -663,7 +663,7 @@ TEST_CASE("Divide Decimal", "[expressionLanguageDivideDecimal]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "11.1");
-  REQUIRE("-0.831730441409086" == expr({flow_file_a}));
+  REQUIRE("-0.831730441409086" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("To Radix", "[expressionLanguageToRadix]") {  // NOLINT
@@ -671,7 +671,7 @@ TEST_CASE("To Radix", "[expressionLanguageToRadix]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "10");
-  REQUIRE("0000000000001010" == expr({flow_file_a}));
+  REQUIRE("0000000000001010" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("To Radix 2", "[expressionLanguageToRadix2]") {  // NOLINT
@@ -679,7 +679,7 @@ TEST_CASE("To Radix 2", "[expressionLanguageToRadix2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "13");
-  REQUIRE("d" == expr({flow_file_a}));
+  REQUIRE("d" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("To Radix 3", "[expressionLanguageToRadix3]") {  // NOLINT
@@ -687,7 +687,7 @@ TEST_CASE("To Radix 3", "[expressionLanguageToRadix3]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "-2347");
-  REQUIRE("-000004a1" == expr({flow_file_a}));
+  REQUIRE("-000004a1" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("From Radix", "[expressionLanguageFromRadix]") {  // NOLINT
@@ -695,7 +695,7 @@ TEST_CASE("From Radix", "[expressionLanguageFromRadix]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "0000000000001010");
-  REQUIRE("10" == expr({flow_file_a}));
+  REQUIRE("10" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("From Radix 2", "[expressionLanguageFromRadix2]") {  // NOLINT
@@ -703,7 +703,7 @@ TEST_CASE("From Radix 2", "[expressionLanguageFromRadix2]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "d");
-  REQUIRE("13" == expr({flow_file_a}));
+  REQUIRE("13" == expr({flow_file_a}).asString());
 }
 
 TEST_CASE("From Radix 3", "[expressionLanguageFromRadix3]") {  // NOLINT
@@ -711,14 +711,14 @@ TEST_CASE("From Radix 3", "[expressionLanguageFromRadix3]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "-000004a1");
-  REQUIRE("-2347" == expr({flow_file_a}));
+  REQUIRE("-2347" == expr({flow_file_a}).asString());
 }
 
 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}));
+  auto result = expr({flow_file_a}).asSignedLong();
   REQUIRE(result > 0);
 }
 
@@ -727,14 +727,14 @@ TEST_CASE("Chained call", "[expressionChainedCall]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "7");
-  REQUIRE("22" == expr({flow_file_a}));
+  REQUIRE("22" == expr({flow_file_a}).asString());
 }
 
 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}));
+  REQUIRE(42 == expr({flow_file_a}).asSignedLong());
 }
 
 TEST_CASE("Chained call 3", "[expressionChainedCall3]") {  // NOLINT
@@ -742,5 +742,5 @@ TEST_CASE("Chained call 3", "[expressionChainedCall3]") {  // NOLINT
 
   auto flow_file_a = std::make_shared<MockFlowFile>();
   flow_file_a->addAttribute("attr", "7");
-  REQUIRE("238" == expr({flow_file_a}));
+  REQUIRE("238" == expr({flow_file_a}).asString());
 }