Add AssemblyContext::parseNumber
authorDavid Neto <dneto@google.com>
Tue, 13 Oct 2015 19:51:12 +0000 (15:51 -0400)
committerDavid Neto <dneto@google.com>
Mon, 26 Oct 2015 16:55:33 +0000 (12:55 -0400)
It parses a text string for a value of a given target type.

source/text_handler.h
test/TextToBinary.cpp

index 5ff9f46..726176b 100644 (file)
 #ifndef _LIBSPIRV_UTIL_TEXT_HANDLER_H_
 #define _LIBSPIRV_UTIL_TEXT_HANDLER_H_
 
-#include <libspirv/libspirv.h>
+#include <iomanip>
+#include <limits>
+#include <sstream>
+#include <type_traits>
 #include <unordered_map>
 
+#include <libspirv/libspirv.h>
 #include "diagnostic.h"
 #include "instruction.h"
 #include "operand.h"
@@ -231,6 +235,48 @@ class AssemblyContext {
   // Tracks the relationship between the value and its type.
   spv_result_t recordTypeIdForValue(uint32_t value, uint32_t type);
 
+  // Parses a numeric value of a given type from the given text.  The number
+  // should take up the entire string, and should be within bounds for the
+  // target type.  On success, returns SPV_SUCCESS and populates the object
+  // referenced by value_pointer. On failure, returns SPV_FAILED_MATCH if
+  // is_optional is true, and returns SPV_ERROR_INVALID_TEXT and emits a
+  // diagnostic otherwise.
+  template <typename T>
+  spv_result_t parseNumber(const char *text, bool is_optional, T *value_pointer,
+                           const char *error_message_fragment) {
+    // C++11 doesn't define std::istringstream(int8_t&), so calling this method
+    // with a single-byte type leads to implementation-defined behaviour.
+    // Similarly for uint8_t.
+    static_assert(sizeof(T) > 1, "Don't use a single-byte type this parse method");
+
+    std::istringstream text_stream(text);
+    // Allow both decimal and hex input for integers.
+    // It also allows octal input, but we don't care about that case.
+    text_stream >> std::setbase(0);
+    text_stream >> *value_pointer;
+    bool ok = true;
+
+    // We should have read something.
+    ok = (text[0] != 0) && !text_stream.bad();
+    // It should have been all the text.
+    ok = ok && text_stream.eof();
+    // It should have been in range.
+    ok = ok && !text_stream.fail();
+    // Work around a bug in the GNU C++11 library. It will happily parse
+    // "-1" for uint16_t as 65535.
+    if (ok && !std::is_signed<T>::value && (text[0] == '-') &&
+        *value_pointer != 0) {
+      ok = false;
+      // Match expected error behaviour of std::istringstream::operator>>
+      // on failure to parse.
+      *value_pointer = 0;
+    }
+
+    if (ok) return SPV_SUCCESS;
+    if (is_optional) return SPV_FAILED_MATCH;
+    return diagnostic() << error_message_fragment << text;
+  }
+
  private:
   // Maps ID names to their corresponding numerical ids.
   using spv_named_id_table = std::unordered_map<std::string, uint32_t>;
index 0d9ae4c..404b0a0 100644 (file)
@@ -27,7 +27,6 @@
 #include "TestFixture.h"
 #include "UnitSPIRV.h"
 #include <algorithm>
-#include <iomanip>
 #include <utility>
 #include <vector>
 
@@ -451,4 +450,159 @@ INSTANTIATE_TEST_CASE_P(
         {"!0xff800001", 0xff800001},  // NaN
     }));
 
+TEST(AssemblyContextParseNarrowSignedIntegers, Sample) {
+  AssemblyContext context(AutoText(""), nullptr);
+  int16_t i16;
+
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("", true, &i16, ""));
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("0=", true, &i16, ""));
+
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("0", true, &i16, ""));
+  EXPECT_EQ(0, i16);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("32767", true, &i16, ""));
+  EXPECT_EQ(32767, i16);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-32768", true, &i16, ""));
+  EXPECT_EQ(-32768, i16);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-0", true, &i16, ""));
+  EXPECT_EQ(0, i16);
+
+  // These are out of range, so they should return an error.
+  // The error code depends on whether this is an optional value.
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("32768", true, &i16, ""));
+  EXPECT_EQ(SPV_ERROR_INVALID_TEXT,
+            context.parseNumber("65535", false, &i16, ""));
+
+  // Check hex parsing.
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("0x7fff", true, &i16, ""));
+  EXPECT_EQ(32767, i16);
+  // This is out of range.
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("0xffff", true, &i16, ""));
+}
+
+TEST(AssemblyContextParseNarrowUnsignedIntegers, Sample) {
+  AssemblyContext context(AutoText(""), nullptr);
+  uint16_t u16;
+
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("", true, &u16, ""));
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("0=", true, &u16, ""));
+
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("0", true, &u16, ""));
+  EXPECT_EQ(0, u16);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("65535", true, &u16, ""));
+  EXPECT_EQ(65535, u16);
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("65536", true, &u16, ""));
+
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-0", true, &u16, ""));
+  EXPECT_EQ(0, u16);
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("-1", true, &u16, ""));
+  EXPECT_EQ(0, u16);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("0xffff", true, &u16, ""));
+  EXPECT_EQ(0xffff, u16);
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("0x10000", true, &u16, ""));
+}
+
+TEST(AssemblyContextParseWideSignedIntegers, Sample) {
+  AssemblyContext context(AutoText(""), nullptr);
+  int64_t i64;
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("", true, &i64, ""));
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("0=", true, &i64, ""));
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("0", true, &i64, ""));
+  EXPECT_EQ(0, i64);
+  EXPECT_EQ(SPV_SUCCESS,
+            context.parseNumber("0x7fffffffffffffff", true, &i64, ""));
+  EXPECT_EQ(0x7fffffffffffffff, i64);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-0", true, &i64, ""));
+  EXPECT_EQ(0, i64);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1", true, &i64, ""));
+  EXPECT_EQ(-1, i64);
+}
+
+TEST(AssemblyContextParseWideUnsignedIntegers, Sample) {
+  AssemblyContext context(AutoText(""), nullptr);
+  uint64_t u64;
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("", true, &u64, ""));
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("0=", true, &u64, ""));
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("0", true, &u64, ""));
+  EXPECT_EQ(0, u64);
+  EXPECT_EQ(SPV_SUCCESS,
+            context.parseNumber("0xffffffffffffffff", true, &u64, ""));
+  EXPECT_EQ(0xffffffffffffffffULL, u64);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-0", true, &u64, ""));
+  EXPECT_EQ(0, u64);
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("-1", true, &u64, ""));
+}
+
+TEST(AssemblyContextParseFloat, Sample) {
+  AssemblyContext context(AutoText(""), nullptr);
+  float f;
+
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("", true, &f, ""));
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("0=", true, &f, ""));
+
+  // These values are exactly representatble.
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("0", true, &f, ""));
+  EXPECT_EQ(0.0f, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("42", true, &f, ""));
+  EXPECT_EQ(42.0f, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("2.5", true, &f, ""));
+  EXPECT_EQ(2.5f, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-32.5", true, &f, ""));
+  EXPECT_EQ(-32.5f, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1e38", true, &f, ""));
+  EXPECT_EQ(1e38f, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e38", true, &f, ""));
+  EXPECT_EQ(-1e38f, f);
+
+  // Out of range.
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("1e40", true, &f, ""));
+}
+
+TEST(AssemblyContextParseDouble, Sample) {
+  AssemblyContext context(AutoText(""), nullptr);
+  double f;
+
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("", true, &f, ""));
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("0=", true, &f, ""));
+
+  // These values are exactly representatble.
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("0", true, &f, ""));
+  EXPECT_EQ(0.0, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("42", true, &f, ""));
+  EXPECT_EQ(42.0, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("2.5", true, &f, ""));
+  EXPECT_EQ(2.5, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-32.5", true, &f, ""));
+  EXPECT_EQ(-32.5, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1e38", true, &f, ""));
+  EXPECT_EQ(1e38, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e38", true, &f, ""));
+  EXPECT_EQ(-1e38, f);
+  // These are out of range for 32-bit float, but in range for 64-bit float.
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1e40", true, &f, ""));
+  EXPECT_EQ(1e40, f);
+  EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e40", true, &f, ""));
+  EXPECT_EQ(-1e40, f);
+
+  // Out of range.
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("1e400", true, &f, ""));
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("-1e400", true, &f, ""));
+}
+
+TEST(AssemblyContextParseMessages, Errors) {
+  spv_diagnostic diag = nullptr;
+  AssemblyContext context(AutoText(""), &diag);
+  int16_t i16;
+
+  // No message is generated for a failure to parse an optional value.
+  EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("abc", true, &i16, "bad narrow int: "));
+  EXPECT_EQ(nullptr, diag);
+
+  // For a required value, use the message fragment.
+  EXPECT_EQ(SPV_ERROR_INVALID_TEXT, context.parseNumber("abc", false, &i16, "bad narrow int: "));
+  ASSERT_NE(nullptr, diag);
+  EXPECT_EQ("bad narrow int: abc", std::string(diag->error));
+  // Don't leak.
+  spvDiagnosticDestroy(diag);
+}
+
 }  // anonymous namespace