234 lines · c
1//===-- Float type specifier converter for scanf ----------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#ifndef LLVM_LIBC_SRC_STDIO_SCANF_CORE_FLOAT_CONVERTER_H10#define LLVM_LIBC_SRC_STDIO_SCANF_CORE_FLOAT_CONVERTER_H11 12#include "src/__support/CPP/limits.h"13#include "src/__support/char_vector.h"14#include "src/__support/ctype_utils.h"15#include "src/__support/macros/config.h"16#include "src/stdio/scanf_core/converter_utils.h"17#include "src/stdio/scanf_core/core_structs.h"18#include "src/stdio/scanf_core/reader.h"19 20#include <stddef.h>21 22namespace LIBC_NAMESPACE_DECL {23namespace scanf_core {24 25// All of the floating point conversions are the same for scanf, every name will26// accept every style.27template <typename T>28int convert_float(Reader<T> *reader, const FormatSection &to_conv) {29 // %a/A/e/E/f/F/g/G "Matches an optionally signed floating-point number,30 // infinity, or NaN, whose format is the same as expected for the subject31 // sequence of the strtod function. The corresponding argument shall be a32 // pointer to floating."33 34 CharVector out_str = CharVector();35 bool is_number = false;36 37 size_t max_width = cpp::numeric_limits<size_t>::max();38 if (to_conv.max_width > 0) {39 max_width = to_conv.max_width;40 }41 42 char cur_char = reader->getc();43 // Handle the sign.44 if (cur_char == '+' || cur_char == '-') {45 if (!out_str.append(cur_char)) {46 return ALLOCATION_FAILURE;47 }48 if (out_str.length() == max_width) {49 return MATCHING_FAILURE;50 } else {51 cur_char = reader->getc();52 }53 }54 55 static constexpr char DECIMAL_POINT = '.';56 static const char inf_string[] = "infinity";57 58 // Handle inf59 60 if (internal::tolower(cur_char) == inf_string[0]) {61 size_t inf_index = 0;62 63 for (;64 inf_index < (sizeof(inf_string) - 1) && out_str.length() < max_width &&65 internal::tolower(cur_char) == inf_string[inf_index];66 ++inf_index) {67 if (!out_str.append(cur_char)) {68 return ALLOCATION_FAILURE;69 }70 cur_char = reader->getc();71 }72 73 if (inf_index == 3 || inf_index == sizeof(inf_string) - 1) {74 write_float_with_length(out_str.c_str(), to_conv);75 return READ_OK;76 } else {77 return MATCHING_FAILURE;78 }79 }80 81 static const char nan_string[] = "nan";82 83 // Handle nan84 if (internal::tolower(cur_char) == nan_string[0]) {85 size_t nan_index = 0;86 87 for (;88 nan_index < (sizeof(nan_string) - 1) && out_str.length() < max_width &&89 internal::tolower(cur_char) == nan_string[nan_index];90 ++nan_index) {91 if (!out_str.append(cur_char)) {92 return ALLOCATION_FAILURE;93 }94 cur_char = reader->getc();95 }96 97 if (nan_index == sizeof(nan_string) - 1) {98 write_float_with_length(out_str.c_str(), to_conv);99 return READ_OK;100 } else {101 return MATCHING_FAILURE;102 }103 }104 105 // Assume base of 10 by default but check if it is actually base 16.106 int base = 10;107 108 // If the string starts with 0 it might be in hex.109 if (cur_char == '0') {110 is_number = true;111 // Read the next character to check.112 if (!out_str.append(cur_char)) {113 return ALLOCATION_FAILURE;114 }115 // If we've hit the end, then this is "0", which is valid.116 if (out_str.length() == max_width) {117 write_float_with_length(out_str.c_str(), to_conv);118 return READ_OK;119 } else {120 cur_char = reader->getc();121 }122 123 // If that next character is an 'x' then this is a hexadecimal number.124 if (internal::tolower(cur_char) == 'x') {125 base = 16;126 127 if (!out_str.append(cur_char)) {128 return ALLOCATION_FAILURE;129 }130 // If we've hit the end here, we have "0x" which is a valid prefix to a131 // floating point number, and will be evaluated to 0.132 if (out_str.length() == max_width) {133 write_float_with_length(out_str.c_str(), to_conv);134 return READ_OK;135 } else {136 cur_char = reader->getc();137 }138 }139 }140 141 const char exponent_mark = ((base == 10) ? 'e' : 'p');142 bool after_decimal = false;143 144 // The format for the remaining characters at this point is DD.DDe+/-DD for145 // base 10 and XX.XXp+/-DD for base 16146 147 // This handles the digits before and after the decimal point, but not the148 // exponent.149 while (out_str.length() < max_width) {150 if (internal::isalnum(cur_char) &&151 internal::b36_char_to_int(cur_char) < base) {152 is_number = true;153 if (!out_str.append(cur_char)) {154 return ALLOCATION_FAILURE;155 }156 cur_char = reader->getc();157 } else if (cur_char == DECIMAL_POINT && !after_decimal) {158 after_decimal = true;159 if (!out_str.append(cur_char)) {160 return ALLOCATION_FAILURE;161 }162 cur_char = reader->getc();163 } else {164 break;165 }166 }167 168 // Handle the exponent, which has an exponent mark, an optional sign, and169 // decimal digits.170 if (internal::tolower(cur_char) == exponent_mark) {171 if (!out_str.append(cur_char)) {172 return ALLOCATION_FAILURE;173 }174 if (out_str.length() == max_width) {175 // This is laid out in the standard as being a matching error (100e is not176 // a valid float) but may conflict with existing implementations.177 return MATCHING_FAILURE;178 } else {179 cur_char = reader->getc();180 }181 182 if (cur_char == '+' || cur_char == '-') {183 if (!out_str.append(cur_char)) {184 return ALLOCATION_FAILURE;185 }186 if (out_str.length() == max_width) {187 return MATCHING_FAILURE;188 } else {189 cur_char = reader->getc();190 }191 }192 193 // It is specified by the standard that "100er" is a matching failure since194 // the longest prefix of a possibly valid floating-point number (which is195 // "100e") is not a valid floating-point number. If there is an exponent196 // mark then there must be a digit after it else the number is not valid.197 // Some implementations will roll back two characters (to just "100") and198 // accept that since the prefix is not valid, and some will interpret an199 // exponent mark followed by no digits as an additional exponent of 0200 // (accepting "100e" and returning 100.0). Both of these behaviors are wrong201 // by the standard, but they may be used in real code, see Hyrum's law. This202 // code follows the standard, but may be incompatible due to code expecting203 // these bugs.204 if (!internal::isdigit(cur_char)) {205 return MATCHING_FAILURE;206 }207 208 while (internal::isdigit(cur_char) && out_str.length() < max_width) {209 if (!out_str.append(cur_char)) {210 return ALLOCATION_FAILURE;211 }212 cur_char = reader->getc();213 }214 }215 216 // We always read one more character than will be used, so we have to put the217 // last one back.218 reader->ungetc(cur_char);219 220 // If we haven't actually found any digits, this is a matching failure (this221 // catches cases like "+.")222 if (!is_number) {223 return MATCHING_FAILURE;224 }225 write_float_with_length(out_str.c_str(), to_conv);226 227 return READ_OK;228}229 230} // namespace scanf_core231} // namespace LIBC_NAMESPACE_DECL232 233#endif // LLVM_LIBC_SRC_STDIO_SCANF_CORE_FLOAT_CONVERTER_H234