Bläddra i källkod

Add support for octal numbers (#6909)

This implements the leads decision made in #6821, proposal #6910. The
proposal is pending, but I figured it's relatively safe to just do given
the decision.

Assisted-by: Google Antigravity with Gemini

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Jon Ross-Perkins 1 månad sedan
förälder
incheckning
613a139bef

+ 1 - 1
toolchain/diagnostics/kind.def

@@ -54,7 +54,7 @@ CARBON_DIAGNOSTIC_KIND(ErrorReadingFile)
 // Lexer diagnostics
 // ============================================================================
 
-CARBON_DIAGNOSTIC_KIND(BinaryRealLiteral)
+CARBON_DIAGNOSTIC_KIND(InvalidRealLiteralRadix)
 CARBON_DIAGNOSTIC_KIND(ContentBeforeStringTerminator)
 CARBON_DIAGNOSTIC_KIND(DecimalEscapeSequence)
 CARBON_DIAGNOSTIC_KIND(DumpSemIRRangeMissingEnd)

+ 43 - 35
toolchain/lex/numeric_literal.cpp

@@ -159,6 +159,8 @@ NumericLiteral::Parser::Parser(Diagnostics::Emitter<const char*>& emitter,
     radix_ = Radix::Hexadecimal;
   } else if (int_part_.consume_front("0b")) {
     radix_ = Radix::Binary;
+  } else if (int_part_.consume_front("0o")) {
+    radix_ = Radix::Octal;
   }
 
   fract_part_ = literal.text_.substr(
@@ -190,16 +192,28 @@ static auto ParseBinary(llvm::StringRef digits) -> llvm::APInt {
   return value;
 }
 
-// Parses a hexadecimal integer literal.
-static auto ParseHexadecimal(llvm::StringRef digits) -> llvm::APInt {
-  llvm::APInt value(std::max<int>(IntStore::MinAPWidth, digits.size() * 4), 0);
-  int cursor = digits.size() * 4 - 1;
+// Parses an octal or hexadecimal integer literal.
+template <NumericLiteral::Radix Radix>
+  requires(Radix == NumericLiteral::Radix::Hexadecimal ||
+           Radix == NumericLiteral::Radix::Octal)
+static auto ParseOctalOrHexadecimal(llvm::StringRef digits) -> llvm::APInt {
+  constexpr int BitsPerDigit =
+      Radix == NumericLiteral::Radix::Hexadecimal ? 4 : 3;
+  llvm::APInt value(
+      std::max<int>(IntStore::MinAPWidth, digits.size() * BitsPerDigit), 0);
+  int cursor = digits.size() * BitsPerDigit - 1;
   for (char c : digits) {
-    uint8_t digit = c <= '9' ? (c - '0') : (c - 'A' + 10);
-    if (digit & 0x8) {
-      value.setBit(cursor);
+    uint8_t digit;
+    if constexpr (Radix == NumericLiteral::Radix::Octal) {
+      digit = c - '0';
+    } else {
+      digit = c <= '9' ? (c - '0') : (c - 'A' + 10);
+
+      if (digit & 0x8) {
+        value.setBit(cursor);
+      }
+      --cursor;
     }
-    --cursor;
     if (digit & 0x4) {
       value.setBit(cursor);
     }
@@ -289,10 +303,13 @@ static auto ParseInt(llvm::StringRef digits, NumericLiteral::Radix radix,
   switch (radix) {
     case NumericLiteral::Radix::Binary:
       return ParseBinary(digits);
+    case NumericLiteral::Radix::Octal:
+      return ParseOctalOrHexadecimal<NumericLiteral::Radix::Octal>(digits);
     case NumericLiteral::Radix::Decimal:
       return ParseDecimal(digits);
     case NumericLiteral::Radix::Hexadecimal:
-      return ParseHexadecimal(digits);
+      return ParseOctalOrHexadecimal<NumericLiteral::Radix::Hexadecimal>(
+          digits);
   }
 }
 
@@ -326,6 +343,8 @@ auto NumericLiteral::Parser::GetExponent() -> llvm::APInt {
   int excess_exponent = fract_part_.size();
   if (radix_ == Radix::Hexadecimal) {
     excess_exponent *= 4;
+  } else if (radix_ == Radix::Octal) {
+    excess_exponent *= 3;
   }
   exponent -= excess_exponent;
   if (exponent_is_negative_ && !exponent.isNegative()) {
@@ -347,22 +366,9 @@ auto NumericLiteral::Parser::CheckDigitSequence(llvm::StringRef text,
                                                 bool allow_digit_separators)
     -> CheckDigitSequenceResult {
   std::bitset<256> valid_digits;
-  switch (radix) {
-    case Radix::Binary:
-      for (char c : "01") {
-        valid_digits[static_cast<unsigned char>(c)] = true;
-      }
-      break;
-    case Radix::Decimal:
-      for (char c : "0123456789") {
-        valid_digits[static_cast<unsigned char>(c)] = true;
-      }
-      break;
-    case Radix::Hexadecimal:
-      for (char c : "0123456789ABCDEF") {
-        valid_digits[static_cast<unsigned char>(c)] = true;
-      }
-      break;
+  static constexpr llvm::StringLiteral Digits = "0123456789ABCDEF";
+  for (char c : Digits.take_front(static_cast<int>(radix))) {
+    valid_digits[static_cast<unsigned char>(c)] = true;
   }
 
   int num_digit_separators = 0;
@@ -386,11 +392,11 @@ auto NumericLiteral::Parser::CheckDigitSequence(llvm::StringRef text,
       continue;
     }
 
-    CARBON_DIAGNOSTIC(
-        InvalidDigit, Error,
-        "invalid digit '{0}' in {1:=2:binary|=10:decimal|=16:hexadecimal} "
-        "numeric literal",
-        char, Diagnostics::IntAsSelect);
+    CARBON_DIAGNOSTIC(InvalidDigit, Error,
+                      "invalid digit '{0}' in "
+                      "{1:=2:binary|=8:octal|=10:decimal|=16:hexadecimal} "
+                      "numeric literal",
+                      char, Diagnostics::IntAsSelect);
     emitter_.Emit(text.begin() + i, InvalidDigit, c, static_cast<int>(radix));
     return {.ok = false};
   }
@@ -435,12 +441,14 @@ auto NumericLiteral::Parser::CheckFractionalPart() -> bool {
     return true;
   }
 
-  if (radix_ == Radix::Binary) {
-    CARBON_DIAGNOSTIC(BinaryRealLiteral, Error,
-                      "binary real number literals are not supported");
+  if (radix_ == Radix::Binary || radix_ == Radix::Octal) {
+    CARBON_DIAGNOSTIC(
+        InvalidRealLiteralRadix, Error,
+        "{0:=2:binary|=8:octal} real number literals are not supported",
+        Diagnostics::IntAsSelect);
     emitter_.Emit(literal_.text_.begin() + literal_.radix_point_,
-                  BinaryRealLiteral);
-    // Carry on and parse the binary real literal anyway.
+                  InvalidRealLiteralRadix, static_cast<int>(radix_));
+    // Carry on and parse the real literal anyway.
   }
 
   // We need to remove a '.' from the mantissa.

+ 6 - 1
toolchain/lex/numeric_literal.h

@@ -17,7 +17,12 @@ namespace Carbon::Lex {
 // A numeric literal token that has been extracted from a source buffer.
 class NumericLiteral {
  public:
-  enum class Radix : int8_t { Binary = 2, Decimal = 10, Hexadecimal = 16 };
+  enum class Radix : int8_t {
+    Binary = 2,
+    Octal = 8,
+    Decimal = 10,
+    Hexadecimal = 16
+  };
 
   // Value of an integer literal.
   struct IntValue {

+ 22 - 8
toolchain/lex/numeric_literal_benchmark.cpp

@@ -15,14 +15,17 @@ namespace {
 
 // Returns an integer literal string with `prefix` followed by `num_digits`
 // entries from `digits` (repeating `digits` as necessary).
-static auto MakeIntString(llvm::StringLiteral prefix,
-                          llvm::StringLiteral digits, size_t num_digits)
+static auto MakeIntString(llvm::StringLiteral prefix, int radix, int num_digits)
     -> std::string {
+  // Digits are reversed so that we can take `radix` digits from the end, and
+  // never have 0 be the first digit.
+  static constexpr llvm::StringLiteral Digits = "FEDCBA9876543210";
+
   std::string s;
   s.reserve(prefix.size() + num_digits);
   s.append(prefix);
-  for (size_t i = 0; i < num_digits; i += digits.size()) {
-    s.append(digits.take_front(std::min(digits.size(), num_digits - i)));
+  for (int i = 0; i < num_digits; i += radix) {
+    s.append(Digits.take_back(std::min(radix, num_digits - i)));
   }
   return s;
 }
@@ -40,7 +43,7 @@ static void BM_Lex_Int(benchmark::State& state) {
 }
 
 static void BM_Lex_IntDecimalN(benchmark::State& state) {
-  std::string s = MakeIntString("", "1234567890", state.range(0));
+  std::string s = MakeIntString("", 10, state.range(0));
   for (auto _ : state) {
     CARBON_CHECK(NumericLiteral::Lex(s, true));
   }
@@ -65,7 +68,7 @@ static void BM_ComputeValue_Int(benchmark::State& state) {
 }
 
 static void BM_ComputeValue_IntDecimalN(benchmark::State& state) {
-  std::string s = MakeIntString("", "1234567890", state.range(0));
+  std::string s = MakeIntString("", 10, state.range(0));
   auto val = NumericLiteral::Lex(s, true);
   auto& emitter = Diagnostics::NullEmitter<const char*>();
   CARBON_CHECK(val);
@@ -75,7 +78,17 @@ static void BM_ComputeValue_IntDecimalN(benchmark::State& state) {
 }
 
 static void BM_ComputeValue_IntBinaryN(benchmark::State& state) {
-  std::string s = MakeIntString("0b", "10", state.range(0));
+  std::string s = MakeIntString("0b", 2, state.range(0));
+  auto val = NumericLiteral::Lex(s, true);
+  auto& emitter = Diagnostics::NullEmitter<const char*>();
+  CARBON_CHECK(val);
+  for (auto _ : state) {
+    val->ComputeValue(emitter);
+  }
+}
+
+static void BM_ComputeValue_IntOctalN(benchmark::State& state) {
+  std::string s = MakeIntString("0o", 8, state.range(0));
   auto val = NumericLiteral::Lex(s, true);
   auto& emitter = Diagnostics::NullEmitter<const char*>();
   CARBON_CHECK(val);
@@ -86,7 +99,7 @@ static void BM_ComputeValue_IntBinaryN(benchmark::State& state) {
 
 static void BM_ComputeValue_IntHexN(benchmark::State& state) {
   // 0 is in the middle so that it isn't truncated in parse.
-  std::string s = MakeIntString("0x", "1234567890ABCDEF", state.range(0));
+  std::string s = MakeIntString("0x", 16, state.range(0));
   auto val = NumericLiteral::Lex(s, true);
   auto& emitter = Diagnostics::NullEmitter<const char*>();
   CARBON_CHECK(val);
@@ -102,6 +115,7 @@ BENCHMARK(BM_ComputeValue_Float);
 BENCHMARK(BM_ComputeValue_Int);
 BENCHMARK(BM_ComputeValue_IntDecimalN)->RangeMultiplier(10)->Range(1, 10000);
 BENCHMARK(BM_ComputeValue_IntBinaryN)->RangeMultiplier(10)->Range(1, 10000);
+BENCHMARK(BM_ComputeValue_IntOctalN)->RangeMultiplier(10)->Range(1, 10000);
 BENCHMARK(BM_ComputeValue_IntHexN)->RangeMultiplier(10)->Range(1, 10000);
 
 }  // namespace

+ 29 - 5
toolchain/lex/numeric_literal_test.cpp

@@ -95,6 +95,7 @@ TEST_F(NumericLiteralTest, HandlesIntLiteral) {
       {.token = "12", .value = 12, .radix = 10},
       {.token = "0x12_3ABC", .value = 0x12'3ABC, .radix = 16},
       {.token = "0b10_10_11", .value = 0b10'10'11, .radix = 2},
+      {.token = "0o12_345", .value = 012345, .radix = 8},
       {.token = "1_234_567", .value = 1'234'567, .radix = 10},
   };
   for (bool can_form_real_literal : {false, true}) {
@@ -122,6 +123,10 @@ TEST_F(NumericLiteralTest, ValidatesBaseSpecifier) {
       // Binary integer literals.
       "0b10110100101001010",
       "0b0000000",
+
+      // Octal integer literals.
+      "0o01234567",
+      "0o0000000",
   };
   for (llvm::StringLiteral literal : valid) {
     error_tracker.Reset();
@@ -130,10 +135,9 @@ TEST_F(NumericLiteralTest, ValidatesBaseSpecifier) {
   }
 
   llvm::StringLiteral invalid[] = {
-      "00",  "0X123",    "0o123",          "0B1",
-      "007", "123L",     "123456789A",     "0x",
-      "0b",  "0x123abc", "0b011101201001", "0b10A",
-      "0x_", "0b_",
+      "00",         "0X123", "0O123", "0B1", "007",      "123L",
+      "123456789A", "0x",    "0b",    "0o",  "0x123abc", "0b011101201001",
+      "0b10A",      "0x_",   "0b_",   "0o_", "0o1238",   "0o12A",
   };
   for (llvm::StringLiteral literal : invalid) {
     error_tracker.Reset();
@@ -162,6 +166,10 @@ TEST_F(NumericLiteralTest, ValidatesIntDigitSeparators) {
       // Binary literals.
       "0b1_0_1_0_1_0",
       "0b111_0000",
+
+      // Octal literals.
+      "0o1_234",
+      "0o12_34",
   };
   for (llvm::StringLiteral literal : valid) {
     error_tracker.Reset();
@@ -184,6 +192,11 @@ TEST_F(NumericLiteralTest, ValidatesIntDigitSeparators) {
       "0b1__01",
       "0b1011_",
       "0b1_01_01_",
+
+      // Octal literals.
+      "0o_1234",
+      "0o123_",
+      "0o1_2__34",
   };
   for (llvm::StringLiteral literal : invalid) {
     error_tracker.Reset();
@@ -275,6 +288,14 @@ TEST_F(NumericLiteralTest, HandlesRealLiteral) {
        .exponent = -2,
        .radix = 2,
        .int_value = 0b101101},
+
+      // Octal real literals. These are invalid, but we accept them for error
+      // recovery.
+      {.token = "0o12_34.56",
+       .mantissa = 0123456,
+       .exponent = -6,
+       .radix = 8,
+       .int_value = 01234},
   };
   // Check we get the right real value.
   for (Testcase testcase : testcases) {
@@ -284,7 +305,8 @@ TEST_F(NumericLiteralTest, HandlesRealLiteral) {
                               .mantissa = IsUnsignedInt(testcase.mantissa),
                               .exponent = IsSignedInt(testcase.exponent)}))
         << testcase.token;
-    EXPECT_EQ(error_tracker.seen_error(), testcase.radix == 2)
+    EXPECT_EQ(error_tracker.seen_error(),
+              testcase.radix == 2 || testcase.radix == 8)
         << testcase.token;
   }
   // If we are required to stop at the `.` character, check we get the right int
@@ -319,6 +341,8 @@ TEST_F(NumericLiteralTest, ValidatesRealLiterals) {
       "0b.0",
       "0x_.0",
       "0b_.0",
+      "0o.0",
+      "0o_.0",
 
       // No digits in fractional part.
       "0.e",

+ 52 - 37
toolchain/lex/testdata/numeric_literals.carbon

@@ -18,7 +18,7 @@ fn F() {
 // CHECK:STDOUT:   - { index:  2, kind:       "Identifier", line: {{ *}}[[@LINE-2]], column:  4, indent: 1, spelling: "F", identifier: 0, has_leading_space: true }
 // CHECK:STDOUT:   - { index:  3, kind:        "OpenParen", line: {{ *}}[[@LINE-3]], column:  5, indent: 1, spelling: "(", closing_token: 4 }
 // CHECK:STDOUT:   - { index:  4, kind:       "CloseParen", line: {{ *}}[[@LINE-4]], column:  6, indent: 1, spelling: ")", opening_token: 3 }
-// CHECK:STDOUT:   - { index:  5, kind:   "OpenCurlyBrace", line: {{ *}}[[@LINE-5]], column:  8, indent: 1, spelling: "{", closing_token: 58, has_leading_space: true }
+// CHECK:STDOUT:   - { index:  5, kind:   "OpenCurlyBrace", line: {{ *}}[[@LINE-5]], column:  8, indent: 1, spelling: "{", closing_token: 60, has_leading_space: true }
   // 8 and 9 trigger special behavior in APInt when mishandling signed versus
   // unsigned, so we pay extra attention to those.
   var ints: array(i32, 6) = (
@@ -32,7 +32,7 @@ fn F() {
   // CHECK:STDOUT:   - { index: 13, kind:       "IntLiteral", line: {{ *}}[[@LINE-8]], column: 24, indent: 3, spelling: "6", value: "6", has_leading_space: true }
   // CHECK:STDOUT:   - { index: 14, kind:       "CloseParen", line: {{ *}}[[@LINE-9]], column: 25, indent: 3, spelling: ")", opening_token: 10 }
   // CHECK:STDOUT:   - { index: 15, kind:            "Equal", line: {{ *}}[[@LINE-10]], column: 27, indent: 3, spelling: "=", has_leading_space: true }
-  // CHECK:STDOUT:   - { index: 16, kind:        "OpenParen", line: {{ *}}[[@LINE-11]], column: 29, indent: 3, spelling: "(", closing_token: 29, has_leading_space: true }
+  // CHECK:STDOUT:   - { index: 16, kind:        "OpenParen", line: {{ *}}[[@LINE-11]], column: 29, indent: 3, spelling: "(", closing_token: 31, has_leading_space: true }
     8,
     // CHECK:STDOUT:   - { index: 17, kind:       "IntLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "8", value: "8", has_leading_space: true }
     // CHECK:STDOUT:   - { index: 18, kind:            "Comma", line: {{ *}}[[@LINE-2]], column:  6, indent: 5, spelling: "," }
@@ -45,66 +45,81 @@ fn F() {
     0b1000,
     // CHECK:STDOUT:   - { index: 23, kind:       "IntLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "0b1000", value: "8", has_leading_space: true }
     // CHECK:STDOUT:   - { index: 24, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 11, indent: 5, spelling: "," }
+    0o10,
+    // CHECK:STDOUT:   - { index: 25, kind:       "IntLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "0o10", value: "8", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 26, kind:            "Comma", line: {{ *}}[[@LINE-2]], column:  9, indent: 5, spelling: "," }
     39999999999999999993,
-    // CHECK:STDOUT:   - { index: 25, kind:       "IntLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "39999999999999999993", value: "39999999999999999993", has_leading_space: true }
-    // CHECK:STDOUT:   - { index: 26, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 25, indent: 5, spelling: "," }
+    // CHECK:STDOUT:   - { index: 27, kind:       "IntLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "39999999999999999993", value: "39999999999999999993", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 28, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 25, indent: 5, spelling: "," }
     12345678901234567890123456789012345678901234567890123456789,
-    // CHECK:STDOUT:   - { index: 27, kind:       "IntLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "12345678901234567890123456789012345678901234567890123456789", value: "12345678901234567890123456789012345678901234567890123456789", has_leading_space: true }
-    // CHECK:STDOUT:   - { index: 28, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 64, indent: 5, spelling: "," }
+    // CHECK:STDOUT:   - { index: 29, kind:       "IntLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "12345678901234567890123456789012345678901234567890123456789", value: "12345678901234567890123456789012345678901234567890123456789", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 30, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 64, indent: 5, spelling: "," }
   );
-  // CHECK:STDOUT:   - { index: 29, kind:       "CloseParen", line: {{ *}}[[@LINE-1]], column:  3, indent: 3, spelling: ")", opening_token: 16, has_leading_space: true }
-  // CHECK:STDOUT:   - { index: 30, kind:             "Semi", line: {{ *}}[[@LINE-2]], column:  4, indent: 3, spelling: ";" }
+  // CHECK:STDOUT:   - { index: 31, kind:       "CloseParen", line: {{ *}}[[@LINE-1]], column:  3, indent: 3, spelling: ")", opening_token: 16, has_leading_space: true }
+  // CHECK:STDOUT:   - { index: 32, kind:             "Semi", line: {{ *}}[[@LINE-2]], column:  4, indent: 3, spelling: ";" }
   var floats: array(f64, 7) = (
-  // CHECK:STDOUT:   - { index: 31, kind:              "Var", line: {{ *}}[[@LINE-1]], column:  3, indent: 3, spelling: "var", has_leading_space: true }
-  // CHECK:STDOUT:   - { index: 32, kind:       "Identifier", line: {{ *}}[[@LINE-2]], column:  7, indent: 3, spelling: "floats", identifier: 2, has_leading_space: true }
-  // CHECK:STDOUT:   - { index: 33, kind:            "Colon", line: {{ *}}[[@LINE-3]], column: 13, indent: 3, spelling: ":" }
-  // CHECK:STDOUT:   - { index: 34, kind:            "Array", line: {{ *}}[[@LINE-4]], column: 15, indent: 3, spelling: "array", has_leading_space: true }
-  // CHECK:STDOUT:   - { index: 35, kind:        "OpenParen", line: {{ *}}[[@LINE-5]], column: 20, indent: 3, spelling: "(", closing_token: 39 }
-  // CHECK:STDOUT:   - { index: 36, kind: "FloatTypeLiteral", line: {{ *}}[[@LINE-6]], column: 21, indent: 3, spelling: "f64" }
-  // CHECK:STDOUT:   - { index: 37, kind:            "Comma", line: {{ *}}[[@LINE-7]], column: 24, indent: 3, spelling: "," }
-  // CHECK:STDOUT:   - { index: 38, kind:       "IntLiteral", line: {{ *}}[[@LINE-8]], column: 26, indent: 3, spelling: "7", value: "7", has_leading_space: true }
-  // CHECK:STDOUT:   - { index: 39, kind:       "CloseParen", line: {{ *}}[[@LINE-9]], column: 27, indent: 3, spelling: ")", opening_token: 35 }
-  // CHECK:STDOUT:   - { index: 40, kind:            "Equal", line: {{ *}}[[@LINE-10]], column: 29, indent: 3, spelling: "=", has_leading_space: true }
-  // CHECK:STDOUT:   - { index: 41, kind:        "OpenParen", line: {{ *}}[[@LINE-11]], column: 31, indent: 3, spelling: "(", closing_token: 56, has_leading_space: true }
+  // CHECK:STDOUT:   - { index: 33, kind:              "Var", line: {{ *}}[[@LINE-1]], column:  3, indent: 3, spelling: "var", has_leading_space: true }
+  // CHECK:STDOUT:   - { index: 34, kind:       "Identifier", line: {{ *}}[[@LINE-2]], column:  7, indent: 3, spelling: "floats", identifier: 2, has_leading_space: true }
+  // CHECK:STDOUT:   - { index: 35, kind:            "Colon", line: {{ *}}[[@LINE-3]], column: 13, indent: 3, spelling: ":" }
+  // CHECK:STDOUT:   - { index: 36, kind:            "Array", line: {{ *}}[[@LINE-4]], column: 15, indent: 3, spelling: "array", has_leading_space: true }
+  // CHECK:STDOUT:   - { index: 37, kind:        "OpenParen", line: {{ *}}[[@LINE-5]], column: 20, indent: 3, spelling: "(", closing_token: 41 }
+  // CHECK:STDOUT:   - { index: 38, kind: "FloatTypeLiteral", line: {{ *}}[[@LINE-6]], column: 21, indent: 3, spelling: "f64" }
+  // CHECK:STDOUT:   - { index: 39, kind:            "Comma", line: {{ *}}[[@LINE-7]], column: 24, indent: 3, spelling: "," }
+  // CHECK:STDOUT:   - { index: 40, kind:       "IntLiteral", line: {{ *}}[[@LINE-8]], column: 26, indent: 3, spelling: "7", value: "7", has_leading_space: true }
+  // CHECK:STDOUT:   - { index: 41, kind:       "CloseParen", line: {{ *}}[[@LINE-9]], column: 27, indent: 3, spelling: ")", opening_token: 37 }
+  // CHECK:STDOUT:   - { index: 42, kind:            "Equal", line: {{ *}}[[@LINE-10]], column: 29, indent: 3, spelling: "=", has_leading_space: true }
+  // CHECK:STDOUT:   - { index: 43, kind:        "OpenParen", line: {{ *}}[[@LINE-11]], column: 31, indent: 3, spelling: "(", closing_token: 58, has_leading_space: true }
     0.9,
-    // CHECK:STDOUT:   - { index: 42, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "0.9", value: "9*10^-1", has_leading_space: true }
-    // CHECK:STDOUT:   - { index: 43, kind:            "Comma", line: {{ *}}[[@LINE-2]], column:  8, indent: 5, spelling: "," }
-    8.0,
-    // CHECK:STDOUT:   - { index: 44, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "8.0", value: "80*10^-1", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 44, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "0.9", value: "9*10^-1", has_leading_space: true }
     // CHECK:STDOUT:   - { index: 45, kind:            "Comma", line: {{ *}}[[@LINE-2]], column:  8, indent: 5, spelling: "," }
+    8.0,
+    // CHECK:STDOUT:   - { index: 46, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "8.0", value: "80*10^-1", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 47, kind:            "Comma", line: {{ *}}[[@LINE-2]], column:  8, indent: 5, spelling: "," }
     80.0,
-    // CHECK:STDOUT:   - { index: 46, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "80.0", value: "800*10^-1", has_leading_space: true }
-    // CHECK:STDOUT:   - { index: 47, kind:            "Comma", line: {{ *}}[[@LINE-2]], column:  9, indent: 5, spelling: "," }
+    // CHECK:STDOUT:   - { index: 48, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "80.0", value: "800*10^-1", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 49, kind:            "Comma", line: {{ *}}[[@LINE-2]], column:  9, indent: 5, spelling: "," }
     1.0e7,
-    // CHECK:STDOUT:   - { index: 48, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "1.0e7", value: "10*10^6", has_leading_space: true }
-    // CHECK:STDOUT:   - { index: 49, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 10, indent: 5, spelling: "," }
-    1.0e8,
-    // CHECK:STDOUT:   - { index: 50, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "1.0e8", value: "10*10^7", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 50, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "1.0e7", value: "10*10^6", has_leading_space: true }
     // CHECK:STDOUT:   - { index: 51, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 10, indent: 5, spelling: "," }
+    1.0e8,
+    // CHECK:STDOUT:   - { index: 52, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "1.0e8", value: "10*10^7", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 53, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 10, indent: 5, spelling: "," }
     1.0e-8,
-    // CHECK:STDOUT:   - { index: 52, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "1.0e-8", value: "10*10^-9", has_leading_space: true }
-    // CHECK:STDOUT:   - { index: 53, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 11, indent: 5, spelling: "," }
+    // CHECK:STDOUT:   - { index: 54, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "1.0e-8", value: "10*10^-9", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 55, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 11, indent: 5, spelling: "," }
     39999999999999999993.0e39999999999999999993,
-    // CHECK:STDOUT:   - { index: 54, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "39999999999999999993.0e39999999999999999993", value: "399999999999999999930*10^39999999999999999992", has_leading_space: true }
-    // CHECK:STDOUT:   - { index: 55, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 48, indent: 5, spelling: "," }
+    // CHECK:STDOUT:   - { index: 56, kind:      "RealLiteral", line: {{ *}}[[@LINE-1]], column:  5, indent: 5, spelling: "39999999999999999993.0e39999999999999999993", value: "399999999999999999930*10^39999999999999999992", has_leading_space: true }
+    // CHECK:STDOUT:   - { index: 57, kind:            "Comma", line: {{ *}}[[@LINE-2]], column: 48, indent: 5, spelling: "," }
   );
-  // CHECK:STDOUT:   - { index: 56, kind:       "CloseParen", line: {{ *}}[[@LINE-1]], column:  3, indent: 3, spelling: ")", opening_token: 41, has_leading_space: true }
-  // CHECK:STDOUT:   - { index: 57, kind:             "Semi", line: {{ *}}[[@LINE-2]], column:  4, indent: 3, spelling: ";" }
+  // CHECK:STDOUT:   - { index: 58, kind:       "CloseParen", line: {{ *}}[[@LINE-1]], column:  3, indent: 3, spelling: ")", opening_token: 43, has_leading_space: true }
+  // CHECK:STDOUT:   - { index: 59, kind:             "Semi", line: {{ *}}[[@LINE-2]], column:  4, indent: 3, spelling: ";" }
 }
-// CHECK:STDOUT:   - { index: 58, kind:  "CloseCurlyBrace", line: {{ *}}[[@LINE-1]], column:  1, indent: 1, spelling: "}", opening_token: 5, has_leading_space: true }
+// CHECK:STDOUT:   - { index: 60, kind:  "CloseCurlyBrace", line: {{ *}}[[@LINE-1]], column:  1, indent: 1, spelling: "}", opening_token: 5, has_leading_space: true }
 
 
 // --- fail_binary_real.carbon
 // CHECK:STDOUT: - filename: fail_binary_real.carbon
 // CHECK:STDOUT:   tokens:
 
-// CHECK:STDERR: fail_binary_real.carbon:[[@LINE+4]]:4: error: binary real number literals are not supported [BinaryRealLiteral]
+// CHECK:STDERR: fail_binary_real.carbon:[[@LINE+4]]:4: error: binary real number literals are not supported [InvalidRealLiteralRadix]
 // CHECK:STDERR: 0b1.0
 // CHECK:STDERR:    ^
 // CHECK:STDERR:
 0b1.0
 // CHECK:STDOUT:   - { index: 1, kind: "RealLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "0b1.0", value: "2*2^-1", has_leading_space: true }
 
+// --- fail_octal_real.carbon
+// CHECK:STDOUT: - filename: fail_octal_real.carbon
+// CHECK:STDOUT:   tokens:
+
+// CHECK:STDERR: fail_octal_real.carbon:[[@LINE+4]]:4: error: octal real number literals are not supported [InvalidRealLiteralRadix]
+// CHECK:STDERR: 0o1.0
+// CHECK:STDERR:    ^
+// CHECK:STDERR:
+0o1.0
+// CHECK:STDOUT:   - { index: 1, kind: "RealLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "0o1.0", value: "8*2^-3", has_leading_space: true }
+
+
 // --- fail_wrong_real_exponent.carbon
 // CHECK:STDOUT: - filename: fail_wrong_real_exponent.carbon
 // CHECK:STDOUT:   tokens: