Kaynağa Gözat

math: Add options to calculate and format output as hexadecimal

Daniel Franke 7 yıl önce
ebeveyn
işleme
5dbee9d2d0

+ 17 - 1
Help/command/math.rst

@@ -5,10 +5,26 @@ Mathematical expressions.
 
 ::
 
-  math(EXPR <output-variable> <math-expression>)
+  math(EXPR <output-variable> <math-expression> [OUTPUT_FORMAT <format>])
 
 ``EXPR`` evaluates mathematical expression and returns result in the
 output variable.  Example mathematical expression is ``5 * (10 + 13)``.
 Supported operators are ``+``, ``-``, ``*``, ``/``, ``%``, ``|``, ``&``,
 ``^``, ``~``, ``<<``, ``>>``, and ``(...)``.  They have the same meaning
 as they do in C code.
+
+Numeric constants are evaluated in decimal or hexadecimal representation.
+
+The result is formatted according to the option "OUTPUT_FORMAT" ,
+where ``<format>`` is one of:
+::
+
+ HEXADECIMAL = Result in output variable will be formatted in C code
+ Hexadecimal notation.
+ DECIMAL = Result in output variable will be formatted in decimal notation.
+
+
+For example::
+
+  math(EXPR value "100 * 0xA" DECIMAL)  results in value is set to "1000"
+  math(EXPR value "100 * 0xA" HEXADECIMAL)  results in value is set to "0x3e8"

+ 4 - 0
Help/release/dev/math-hex.rst

@@ -0,0 +1,4 @@
+math-hex
+--------
+
+* The :command:`math` command gained options for hexadecimal.

+ 61 - 51
Source/LexerParser/cmExprLexer.cxx

@@ -548,8 +548,8 @@ static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner );
 	yyg->yy_hold_char = *yy_cp; \
 	*yy_cp = '\0'; \
 	yyg->yy_c_buf_p = yy_cp;
-#define YY_NUM_RULES 17
-#define YY_END_OF_BUFFER 18
+#define YY_NUM_RULES 18
+#define YY_END_OF_BUFFER 19
 /* This struct is not used in this scanner,
    but its presence is necessary. */
 struct yy_trans_info
@@ -557,11 +557,11 @@ struct yy_trans_info
 	flex_int32_t yy_verify;
 	flex_int32_t yy_nxt;
 	};
-static const flex_int16_t yy_accept[25] =
+static const flex_int16_t yy_accept[29] =
     {   0,
-        0,    0,   18,   16,    1,   17,    7,    9,   14,   15,
-        5,    3,    4,    6,    2,   16,   16,   10,    8,   11,
-        2,   12,   13,    0
+        0,    0,   19,   17,    1,   18,    8,   10,   15,   16,
+        6,    4,    5,    7,    2,    2,   17,   17,   11,    9,
+       12,    2,    0,   13,   14,    3,    3,    0
     } ;
 
 static const YY_CHAR yy_ec[256] =
@@ -570,16 +570,16 @@ static const YY_CHAR yy_ec[256] =
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    2,    1,    1,    1,    1,    4,    5,    1,    6,
-        7,    8,    9,    1,   10,    1,   11,   12,   12,   12,
-       12,   12,   12,   12,   12,   12,   12,    1,    1,   13,
-        1,   14,    1,    1,    1,    1,    1,    1,    1,    1,
+        7,    8,    9,    1,   10,    1,   11,   12,   13,   13,
+       13,   13,   13,   13,   13,   13,   13,    1,    1,   14,
+        1,   15,    1,    1,   16,   16,   16,   16,   16,   16,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    1,    1,   15,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,   17,    1,    1,
+        1,    1,    1,   18,    1,    1,   16,   16,   16,   16,
 
-        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    1,    1,   16,    1,   17,    1,    1,    1,    1,
+       16,   16,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,   17,
+        1,    1,    1,   19,    1,   20,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
@@ -596,40 +596,46 @@ static const YY_CHAR yy_ec[256] =
         1,    1,    1,    1,    1
     } ;
 
-static const YY_CHAR yy_meta[18] =
+static const YY_CHAR yy_meta[21] =
     {   0,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    1,    1,    1,    1,    1,    1
+        1,    2,    2,    1,    1,    3,    4,    1,    1,    1
     } ;
 
-static const flex_int16_t yy_base[25] =
+static const flex_int16_t yy_base[32] =
     {   0,
-        0,    0,   22,   23,   23,   23,   23,   23,   23,   23,
-       23,   23,   23,   23,    9,    7,    5,   23,   23,   23,
-        6,   23,   23,   23
+        0,    0,   34,   35,   35,   35,   35,   35,   35,   35,
+       35,   35,   35,   35,   16,    9,   18,   11,   35,   35,
+       35,   11,    0,   35,   35,    0,    0,   35,   23,   26,
+       28
     } ;
 
-static const flex_int16_t yy_def[25] =
+static const flex_int16_t yy_def[32] =
     {   0,
-       24,    1,   24,   24,   24,   24,   24,   24,   24,   24,
-       24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
-       24,   24,   24,    0
+       28,    1,   28,   28,   28,   28,   28,   28,   28,   28,
+       28,   28,   28,   28,   29,   28,   28,   28,   28,   28,
+       28,   28,   30,   28,   28,   31,   31,    0,   28,   28,
+       28
     } ;
 
-static const flex_int16_t yy_nxt[41] =
+static const flex_int16_t yy_nxt[56] =
     {   0,
         4,    5,    6,    7,    8,    9,   10,   11,   12,   13,
-       14,   15,   16,   17,   18,   19,   20,   21,   23,   22,
-       21,   24,    3,   24,   24,   24,   24,   24,   24,   24,
-       24,   24,   24,   24,   24,   24,   24,   24,   24,   24
+       14,   15,   16,   17,   18,    4,    4,   19,   20,   21,
+       22,   22,   22,   22,   22,   25,   22,   26,   26,   27,
+       27,   24,   23,   28,    3,   28,   28,   28,   28,   28,
+       28,   28,   28,   28,   28,   28,   28,   28,   28,   28,
+       28,   28,   28,   28,   28
     } ;
 
-static const flex_int16_t yy_chk[41] =
+static const flex_int16_t yy_chk[56] =
     {   0,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    1,    1,    1,    1,    1,    1,   21,   17,   16,
-       15,    3,   24,   24,   24,   24,   24,   24,   24,   24,
-       24,   24,   24,   24,   24,   24,   24,   24,   24,   24
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+       16,   16,   22,   22,   29,   18,   29,   30,   30,   31,
+       31,   17,   15,    3,   28,   28,   28,   28,   28,   28,
+       28,   28,   28,   28,   28,   28,   28,   28,   28,   28,
+       28,   28,   28,   28,   28
     } ;
 
 /* The intent behind this definition is that it'll catch
@@ -948,13 +954,13 @@ yy_match:
 			while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 				{
 				yy_current_state = (int) yy_def[yy_current_state];
-				if ( yy_current_state >= 25 )
+				if ( yy_current_state >= 29 )
 					yy_c = yy_meta[yy_c];
 				}
 			yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
 			++yy_cp;
 			}
-		while ( yy_base[yy_current_state] != 23 );
+		while ( yy_base[yy_current_state] != 35 );
 
 yy_find_action:
 		yy_act = yy_accept[yy_current_state];
@@ -988,62 +994,66 @@ YY_RULE_SETUP
 	YY_BREAK
 case 3:
 YY_RULE_SETUP
-{ return exp_PLUS; }
+{  yylvalp->Number = std::stoll(yytext, nullptr, 16); return exp_NUMBER; }
 	YY_BREAK
 case 4:
 YY_RULE_SETUP
-{ return exp_MINUS; }
+{ return exp_PLUS; }
 	YY_BREAK
 case 5:
 YY_RULE_SETUP
-{ return exp_TIMES; }
+{ return exp_MINUS; }
 	YY_BREAK
 case 6:
 YY_RULE_SETUP
-{ return exp_DIVIDE; }
+{ return exp_TIMES; }
 	YY_BREAK
 case 7:
 YY_RULE_SETUP
-{ return exp_MOD; }
+{ return exp_DIVIDE; }
 	YY_BREAK
 case 8:
 YY_RULE_SETUP
-{ return exp_OR; }
+{ return exp_MOD; }
 	YY_BREAK
 case 9:
 YY_RULE_SETUP
-{ return exp_AND; }
+{ return exp_OR; }
 	YY_BREAK
 case 10:
 YY_RULE_SETUP
-{ return exp_XOR; }
+{ return exp_AND; }
 	YY_BREAK
 case 11:
 YY_RULE_SETUP
-{ return exp_NOT; }
+{ return exp_XOR; }
 	YY_BREAK
 case 12:
 YY_RULE_SETUP
-{ return exp_SHIFTLEFT; }
+{ return exp_NOT; }
 	YY_BREAK
 case 13:
 YY_RULE_SETUP
-{ return exp_SHIFTRIGHT; }
+{ return exp_SHIFTLEFT; }
 	YY_BREAK
 case 14:
 YY_RULE_SETUP
-{ return exp_OPENPARENT; }
+{ return exp_SHIFTRIGHT; }
 	YY_BREAK
 case 15:
 YY_RULE_SETUP
-{ return exp_CLOSEPARENT; }
+{ return exp_OPENPARENT; }
 	YY_BREAK
 case 16:
 YY_RULE_SETUP
-{return exp_UNEXPECTED;}
+{ return exp_CLOSEPARENT; }
 	YY_BREAK
 case 17:
 YY_RULE_SETUP
+{return exp_UNEXPECTED;}
+	YY_BREAK
+case 18:
+YY_RULE_SETUP
 ECHO;
 	YY_BREAK
 case YY_STATE_EOF(INITIAL):
@@ -1344,7 +1354,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner)
 		while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 			{
 			yy_current_state = (int) yy_def[yy_current_state];
-			if ( yy_current_state >= 25 )
+			if ( yy_current_state >= 29 )
 				yy_c = yy_meta[yy_c];
 			}
 		yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
@@ -1373,11 +1383,11 @@ static int yy_get_next_buffer (yyscan_t yyscanner)
 	while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 		{
 		yy_current_state = (int) yy_def[yy_current_state];
-		if ( yy_current_state >= 25 )
+		if ( yy_current_state >= 29 )
 			yy_c = yy_meta[yy_c];
 		}
 	yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
-	yy_is_jam = (yy_current_state == 24);
+	yy_is_jam = (yy_current_state == 28);
 
 	(void)yyg;
 	return yy_is_jam ? 0 : yy_current_state;

+ 1 - 0
Source/LexerParser/cmExprLexer.in.l

@@ -43,6 +43,7 @@ Modify cmExprLexer.cxx:
 [ \t]  {}
 
 [0-9][0-9]* { yylvalp->Number = std::stoll(yytext, nullptr, 10); return exp_NUMBER; }
+0[xX][0-9a-fA-F][0-9a-fA-F]* {  yylvalp->Number = std::stoll(yytext, nullptr, 16); return exp_NUMBER; }
 
 "+" { return exp_PLUS; }
 "-" { return exp_MINUS; }

+ 56 - 2
Source/cmMathCommand.cxx

@@ -28,16 +28,59 @@ bool cmMathCommand::InitialPass(std::vector<std::string> const& args,
 
 bool cmMathCommand::HandleExprCommand(std::vector<std::string> const& args)
 {
-  if (args.size() != 3) {
+  if ((args.size() != 3) && (args.size() != 5)) {
     this->SetError("EXPR called with incorrect arguments.");
     return false;
   }
 
+  enum class NumericFormat
+  {
+    UNINITIALIZED,
+    DECIMAL,
+    HEXADECIMAL,
+  };
+
   const std::string& outputVariable = args[1];
   const std::string& expression = args[2];
+  size_t argumentIndex = 3;
+  NumericFormat outputFormat = NumericFormat::UNINITIALIZED;
 
   this->Makefile->AddDefinition(outputVariable, "ERROR");
 
+  if (argumentIndex < args.size()) {
+    const std::string messageHint = "sub-command EXPR ";
+    const std::string option = args[argumentIndex++];
+    if (option == "OUTPUT_FORMAT") {
+      if (argumentIndex < args.size()) {
+        const std::string argument = args[argumentIndex++];
+        if (argument == "DECIMAL") {
+          outputFormat = NumericFormat::DECIMAL;
+        } else if (argument == "HEXADECIMAL") {
+          outputFormat = NumericFormat::HEXADECIMAL;
+        } else {
+          std::string error = messageHint + "value \"" + argument +
+            "\" for option \"" + option + "\" is invalid.";
+          this->SetError(error);
+          return false;
+        }
+      } else {
+        std::string error =
+          messageHint + "missing argument for option \"" + option + "\".";
+        this->SetError(error);
+        return false;
+      }
+    } else {
+      std::string error =
+        messageHint + "option \"" + option + "\" is unknown.";
+      this->SetError(error);
+      return false;
+    }
+  }
+
+  if (outputFormat == NumericFormat::UNINITIALIZED) {
+    outputFormat = NumericFormat::DECIMAL;
+  }
+
   cmExprParserHelper helper;
   if (!helper.ParseString(expression.c_str(), 0)) {
     this->SetError(helper.GetError());
@@ -45,7 +88,18 @@ bool cmMathCommand::HandleExprCommand(std::vector<std::string> const& args)
   }
 
   char buffer[1024];
-  sprintf(buffer, "%" KWIML_INT_PRId64, helper.GetResult());
+  const char* fmt;
+  switch (outputFormat) {
+    case NumericFormat::HEXADECIMAL:
+      fmt = "0x%" KWIML_INT_PRIx64;
+      break;
+    case NumericFormat::DECIMAL:
+      CM_FALLTHROUGH;
+    default:
+      fmt = "%" KWIML_INT_PRId64;
+      break;
+  }
+  sprintf(buffer, fmt, helper.GetResult());
 
   this->Makefile->AddDefinition(outputVariable, buffer);
   return true;

+ 27 - 6
Tests/MathTest/CMakeLists.txt

@@ -13,14 +13,35 @@ set(expressions
   "-1 + +1"
   "+1 - -1"
   "+1 - - + + -(-3 + - - +1)"
+  "1000 -12*5"
+  "1000 +12*-5"
+  "1000 -12*-5"
   )
 
-set(FILE_EXPRESSIONS "")
-foreach(expression
-    ${expressions})
-  math(EXPR expr "${expression}")
-  string(APPEND FILE_EXPRESSIONS "TEST_EXPRESSION(${expression}, ${expr})\n")
-endforeach()
+set(FILE_EXPRESSIONS "extern void test_expression(int x, int y, const char * text);\n")
+
+
+macro(add_math_test expression)
+  math(EXPR result ${expression} ${ARGV1} ${ARGV2})
+  set(CODE "test_expression(${expression}, ${result}, \"${expression}\");")
+  string(APPEND FILE_EXPRESSIONS "${CODE}\n")
+endmacro()
+
+macro(add_math_tests)
+  foreach (expression ${expressions})
+    add_math_test(${expression} ${ARGV0} ${ARGV1})
+  endforeach ()
+endmacro()
+
+add_math_tests()
+add_math_tests("OUTPUT_FORMAT" "DECIMAL")
+add_math_tests("OUTPUT_FORMAT" "HEXADECIMAL")
+
+# Avoid the test with negative result and hexadecimal formatting
+# therefore more tests with a negative result
+add_math_test("-12*5")
+add_math_test("12*-5")
+
 
 configure_file(
   "${CMAKE_CURRENT_SOURCE_DIR}/MathTestTests.h.in"

+ 31 - 9
Tests/MathTest/MathTestExec.cxx

@@ -1,21 +1,43 @@
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 
-#define TEST_EXPRESSION(x, y)                                                 \
-  if ((x) != (y)) {                                                           \
-    printf("Problem with EXPR: Expression: \"%s\" in C returns %d while in "  \
-           "CMake returns: %d\n",                                             \
-           #x, (x), (y));                                                     \
-    res++;                                                                    \
+int res = 0;
+bool print = false;
+
+void test_expression(int x, int y, const char* text)
+{
+  bool fail = (x) != (y);
+  if (fail) {
+    res++;
+    printf("Problem with EXPR:");
+  }
+  if (fail || print) {
+    printf("Expression: \"%s\" in CMake returns %d", text, (y));
+    if (fail) {
+      printf(" while in C returns: %d", (x));
+    }
+    printf("\n");
   }
+}
 
 int main(int argc, char* argv[])
 {
-  if (argc > 1) {
-    printf("Usage: %s\n", argv[0]);
+  if (argc > 2) {
+    printf("Usage: %s [print]\n", argv[0]);
     return 1;
   }
-  int res = 0;
+
+  if (argc > 1) {
+    if (strcmp(argv[1], "print") != 0) {
+      printf("Usage: %s [print]\n", argv[0]);
+      return 1;
+    }
+    print = true;
+  }
+
 #include "MathTestTests.h"
+
   if (res != 0) {
     printf("%s: %d math tests failed\n", argv[0], res);
     return 1;

+ 1 - 0
Tests/RunCMake/math/MATH-DoubleOption-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/math/MATH-DoubleOption-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at MATH-DoubleOption.cmake:1 \(math\):
+  math EXPR called with incorrect arguments.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/math/MATH-DoubleOption.cmake

@@ -0,0 +1 @@
+math(EXPR var "10*10" OUTPUT_FORMAT DECIMAL OUTPUT_FORMAT HEXADECIMAL)

+ 1 - 0
Tests/RunCMake/math/MATH-TooManyArguments-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/math/MATH-TooManyArguments-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at MATH-TooManyArguments.cmake:1 \(math\):
+  math EXPR called with incorrect arguments.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/math/MATH-TooManyArguments.cmake

@@ -0,0 +1 @@
+math(EXPR var "10*10" OUTPUT_FORMAT DECIMAL  OUTPUT_FORMAT )

+ 1 - 0
Tests/RunCMake/math/MATH-WrongArgument-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/math/MATH-WrongArgument-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at MATH-WrongArgument.cmake:1 \(math\):
+  math sub-command EXPR option "OUT" is unknown.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/math/MATH-WrongArgument.cmake

@@ -0,0 +1 @@
+math(EXPR var "10*10" OUT HEX )

+ 3 - 0
Tests/RunCMake/math/MATH.cmake

@@ -7,3 +7,6 @@ endmacro()
 
 
 math_test("100 * 10" 1000)
+math_test("100 * 10" 1000 OUTPUT_FORMAT DECIMAL)
+math_test("100 * 0xA" 1000 OUTPUT_FORMAT DECIMAL)
+math_test("100 * 0xA" 0x3e8 OUTPUT_FORMAT HEXADECIMAL)

+ 3 - 0
Tests/RunCMake/math/RunCMakeTest.cmake

@@ -1,5 +1,8 @@
 include(RunCMake)
 
 run_cmake(MATH)
+run_cmake(MATH-WrongArgument)
+run_cmake(MATH-DoubleOption)
+run_cmake(MATH-TooManyArguments)
 run_cmake(MATH-InvalidExpression)
 run_cmake(MATH-DivideByZero)