[libc] Template the printf / scanf parser class (#66277)

Summary:
The parser class for stdio currently accepts different argument
providers. In-tree this is only used for a fuzzer test, however, the
proposed implementation of the GPU handling of printf / scanf will
require custom argument handlers. This makes the current approach of
using a preprocessor macro messier. This path proposed folding this
logic into a template instantiation. The downside to this is that
because the implementation of the parser class is placed into an
implementation file we need to manually instantiate the needed templates
which will slightly bloat binary size. Alternatively we could remove the
implementation file, or key off of the `libc` external packaging macro
so it is not present in the installed version.
This commit is contained in:
Joseph Huber
2023-09-21 17:02:26 -05:00
committed by GitHub
parent f66f3548ca
commit e0be78be42
13 changed files with 637 additions and 757 deletions

View File

@@ -3,9 +3,7 @@ add_libc_fuzzer(
SRCS
printf_parser_fuzz.cpp
DEPENDS
libc.src.stdio.printf_core.mock_parser
COMPILE_OPTIONS
-DLIBC_COPT_MOCK_ARG_LIST
libc.src.stdio.printf_core.parser
)
add_libc_fuzzer(

View File

@@ -10,10 +10,6 @@
///
//===----------------------------------------------------------------------===//
#ifndef LIBC_COPT_MOCK_ARG_LIST
#error The printf Parser Fuzzer must be compiled with LIBC_COPT_MOCK_ARG_LIST, and the parser itself must also be compiled with that option when it's linked against the fuzzer.
#endif
#include "src/__support/arg_list.h"
#include "src/stdio/printf_core/parser.h"
@@ -37,7 +33,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
auto mock_arg_list = internal::MockArgList();
auto parser = printf_core::Parser(in_str, mock_arg_list);
auto parser =
printf_core::Parser<internal::MockArgList>(in_str, mock_arg_list);
int str_percent_count = 0;

View File

@@ -8,10 +8,8 @@ add_header_library(
libc.src.__support.FPUtil.fp_bits
)
add_object_library(
add_header_library(
parser
SRCS
parser.cpp
HDRS
parser.h
DEPENDS
@@ -26,26 +24,6 @@ add_object_library(
libc.src.__support.common
)
add_object_library(
mock_parser
SRCS
parser.cpp
HDRS
parser.h
DEPENDS
.core_structs
libc.src.__support.arg_list
libc.src.__support.ctype_utils
libc.src.__support.str_to_integer
libc.src.__support.CPP.bit
libc.src.__support.CPP.optional
libc.src.__support.CPP.string_view
libc.src.__support.CPP.type_traits
libc.src.__support.common
COMPILE_OPTIONS
-DLIBC_COPT_MOCK_ARG_LIST
)
add_object_library(
writer
SRCS

View File

@@ -1,466 +0,0 @@
//===-- Format string parser implementation for printf ----------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// #define LIBC_COPT_PRINTF_DISABLE_INDEX_MODE 1 // This will be a compile flag.
#include "parser.h"
#include "src/__support/arg_list.h"
#include "src/__support/CPP/bit.h"
#include "src/__support/CPP/optional.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/CPP/type_traits.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/ctype_utils.h"
#include "src/__support/str_to_integer.h"
#include "src/stdio/printf_core/core_structs.h"
namespace __llvm_libc {
namespace printf_core {
template <typename T> struct int_type_of {
using type = T;
};
template <> struct int_type_of<double> {
using type = fputil::FPBits<double>::UIntType;
};
template <> struct int_type_of<long double> {
using type = fputil::FPBits<long double>::UIntType;
};
template <typename T> using int_type_of_v = typename int_type_of<T>::type;
#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
#define WRITE_ARG_VAL_SIMPLEST(dst, arg_type, index) \
{ \
auto temp = get_arg_value<arg_type>(index); \
if (!temp.has_value()) { \
section.has_conv = false; \
} else { \
dst = cpp::bit_cast<int_type_of_v<arg_type>>(temp.value()); \
} \
}
#else
#define WRITE_ARG_VAL_SIMPLEST(dst, arg_type, _) \
dst = cpp::bit_cast<int_type_of_v<arg_type>>(get_next_arg_value<arg_type>())
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
FormatSection Parser::get_next_section() {
FormatSection section;
size_t starting_pos = cur_pos;
if (str[cur_pos] == '%') {
// format section
section.has_conv = true;
++cur_pos;
[[maybe_unused]] size_t conv_index = 0;
#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
conv_index = parse_index(&cur_pos);
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
section.flags = parse_flags(&cur_pos);
// handle width
section.min_width = 0;
if (str[cur_pos] == '*') {
++cur_pos;
WRITE_ARG_VAL_SIMPLEST(section.min_width, int, parse_index(&cur_pos));
} else if (internal::isdigit(str[cur_pos])) {
auto result = internal::strtointeger<int>(str + cur_pos, 10);
section.min_width = result.value;
cur_pos = cur_pos + result.parsed_len;
}
if (section.min_width < 0) {
section.min_width = -section.min_width;
section.flags =
static_cast<FormatFlags>(section.flags | FormatFlags::LEFT_JUSTIFIED);
}
// handle precision
section.precision = -1; // negative precisions are ignored.
if (str[cur_pos] == '.') {
++cur_pos;
section.precision = 0; // if there's a . but no specified precision, the
// precision is implicitly 0.
if (str[cur_pos] == '*') {
++cur_pos;
WRITE_ARG_VAL_SIMPLEST(section.precision, int, parse_index(&cur_pos));
} else if (internal::isdigit(str[cur_pos])) {
auto result = internal::strtointeger<int>(str + cur_pos, 10);
section.precision = result.value;
cur_pos = cur_pos + result.parsed_len;
}
}
LengthModifier lm = parse_length_modifier(&cur_pos);
section.length_modifier = lm;
section.conv_name = str[cur_pos];
switch (str[cur_pos]) {
case ('%'):
// Regardless of options, a % conversion is always safe. The standard says
// that "The complete conversion specification shall be %%" but it also
// says that "If a conversion specification is invalid, the behavior is
// undefined." Based on that we define that any conversion specification
// ending in '%' shall display as '%' regardless of any valid or invalid
// options.
section.has_conv = true;
break;
case ('c'):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
break;
case ('d'):
case ('i'):
case ('o'):
case ('x'):
case ('X'):
case ('u'):
switch (lm) {
case (LengthModifier::hh):
case (LengthModifier::h):
case (LengthModifier::none):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
break;
case (LengthModifier::l):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long, conv_index);
break;
case (LengthModifier::ll):
case (LengthModifier::L): // This isn't in the standard, but is in other
// libc implementations.
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long long, conv_index);
break;
case (LengthModifier::j):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, intmax_t, conv_index);
break;
case (LengthModifier::z):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, size_t, conv_index);
break;
case (LengthModifier::t):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, ptrdiff_t, conv_index);
break;
}
break;
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
case ('f'):
case ('F'):
case ('e'):
case ('E'):
case ('a'):
case ('A'):
case ('g'):
case ('G'):
if (lm != LengthModifier::L) {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, double, conv_index);
} else {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long double, conv_index);
}
break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('n'):
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('p'):
case ('s'):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_ptr, void *, conv_index);
break;
default:
// if the conversion is undefined, change this to a raw section.
section.has_conv = false;
break;
}
// If the end of the format section is on the '\0'. This means we need to
// not advance the cur_pos.
if (str[cur_pos] != '\0')
++cur_pos;
} else {
// raw section
section.has_conv = false;
while (str[cur_pos] != '%' && str[cur_pos] != '\0')
++cur_pos;
}
section.raw_string = {str + starting_pos, cur_pos - starting_pos};
return section;
}
FormatFlags Parser::parse_flags(size_t *local_pos) {
bool found_flag = true;
FormatFlags flags = FormatFlags(0);
while (found_flag) {
switch (str[*local_pos]) {
case '-':
flags = static_cast<FormatFlags>(flags | FormatFlags::LEFT_JUSTIFIED);
break;
case '+':
flags = static_cast<FormatFlags>(flags | FormatFlags::FORCE_SIGN);
break;
case ' ':
flags = static_cast<FormatFlags>(flags | FormatFlags::SPACE_PREFIX);
break;
case '#':
flags = static_cast<FormatFlags>(flags | FormatFlags::ALTERNATE_FORM);
break;
case '0':
flags = static_cast<FormatFlags>(flags | FormatFlags::LEADING_ZEROES);
break;
default:
found_flag = false;
}
if (found_flag)
++*local_pos;
}
return flags;
}
LengthModifier Parser::parse_length_modifier(size_t *local_pos) {
switch (str[*local_pos]) {
case ('l'):
if (str[*local_pos + 1] == 'l') {
*local_pos += 2;
return LengthModifier::ll;
} else {
++*local_pos;
return LengthModifier::l;
}
case ('h'):
if (str[*local_pos + 1] == 'h') {
*local_pos += 2;
return LengthModifier::hh;
} else {
++*local_pos;
return LengthModifier::h;
}
case ('L'):
++*local_pos;
return LengthModifier::L;
case ('j'):
++*local_pos;
return LengthModifier::j;
case ('z'):
++*local_pos;
return LengthModifier::z;
case ('t'):
++*local_pos;
return LengthModifier::t;
default:
return LengthModifier::none;
}
}
//----------------------------------------------------
// INDEX MODE ONLY FUNCTIONS AFTER HERE:
//----------------------------------------------------
#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
size_t Parser::parse_index(size_t *local_pos) {
if (internal::isdigit(str[*local_pos])) {
auto result = internal::strtointeger<int>(str + *local_pos, 10);
size_t index = result.value;
if (str[*local_pos + result.parsed_len] != '$')
return 0;
*local_pos = 1 + result.parsed_len + *local_pos;
return index;
}
return 0;
}
TypeDesc Parser::get_type_desc(size_t index) {
// index mode is assumed, and the indicies start at 1, so an index
// of 0 is invalid.
size_t local_pos = 0;
while (str[local_pos]) {
if (str[local_pos] == '%') {
++local_pos;
size_t conv_index = parse_index(&local_pos);
// the flags aren't relevant for this situation, but I need to skip past
// them so they're parsed but the result is discarded.
parse_flags(&local_pos);
// handle width
if (str[local_pos] == '*') {
++local_pos;
size_t width_index = parse_index(&local_pos);
set_type_desc(width_index, type_desc_from_type<int>());
if (width_index == index)
return type_desc_from_type<int>();
} else if (internal::isdigit(str[local_pos])) {
while (internal::isdigit(str[local_pos]))
++local_pos;
}
// handle precision
if (str[local_pos] == '.') {
++local_pos;
if (str[local_pos] == '*') {
++local_pos;
size_t precision_index = parse_index(&local_pos);
set_type_desc(precision_index, type_desc_from_type<int>());
if (precision_index == index)
return type_desc_from_type<int>();
} else if (internal::isdigit(str[local_pos])) {
while (internal::isdigit(str[local_pos]))
++local_pos;
}
}
LengthModifier lm = parse_length_modifier(&local_pos);
// if we don't have an index for this conversion, then its position is
// unknown and all this information is irrelevant. The rest of this logic
// has been for skipping past this conversion properly to avoid
// weirdness with %%.
if (conv_index == 0) {
if (str[local_pos] != '\0')
++local_pos;
continue;
}
TypeDesc conv_size = type_desc_from_type<void>();
switch (str[local_pos]) {
case ('%'):
conv_size = type_desc_from_type<void>();
break;
case ('c'):
conv_size = type_desc_from_type<int>();
break;
case ('d'):
case ('i'):
case ('o'):
case ('x'):
case ('X'):
case ('u'):
switch (lm) {
case (LengthModifier::hh):
case (LengthModifier::h):
case (LengthModifier::none):
conv_size = type_desc_from_type<int>();
break;
case (LengthModifier::l):
conv_size = type_desc_from_type<long>();
break;
case (LengthModifier::ll):
case (LengthModifier::L): // This isn't in the standard, but is in other
// libc implementations.
conv_size = type_desc_from_type<long long>();
break;
case (LengthModifier::j):
conv_size = type_desc_from_type<intmax_t>();
break;
case (LengthModifier::z):
conv_size = type_desc_from_type<size_t>();
break;
case (LengthModifier::t):
conv_size = type_desc_from_type<ptrdiff_t>();
break;
}
break;
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
case ('f'):
case ('F'):
case ('e'):
case ('E'):
case ('a'):
case ('A'):
case ('g'):
case ('G'):
if (lm != LengthModifier::L)
conv_size = type_desc_from_type<double>();
else
conv_size = type_desc_from_type<long double>();
break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('n'):
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('p'):
case ('s'):
conv_size = type_desc_from_type<void *>();
break;
default:
conv_size = type_desc_from_type<int>();
break;
}
set_type_desc(conv_index, conv_size);
if (conv_index == index)
return conv_size;
}
// If the end of the format section is on the '\0'. This means we need to
// not advance the local_pos.
if (str[local_pos] != '\0')
++local_pos;
}
// If there is no size for the requested index, then it's unknown. Return
// void.
return type_desc_from_type<void>();
}
bool Parser::args_to_index(size_t index) {
if (args_index > index) {
args_index = 1;
args_cur = args_start;
}
while (args_index < index) {
TypeDesc cur_type_desc = type_desc_from_type<void>();
if (args_index <= DESC_ARR_LEN)
cur_type_desc = desc_arr[args_index - 1];
if (cur_type_desc == type_desc_from_type<void>())
cur_type_desc = get_type_desc(args_index);
// A type of void represents the type being unknown. If the type for the
// requested index isn't in the desc_arr and isn't found by parsing the
// string, then then advancing to the requested index is impossible. In that
// case the function returns false.
if (cur_type_desc == type_desc_from_type<void>())
return false;
if (cur_type_desc == type_desc_from_type<uint32_t>())
args_cur.next_var<uint32_t>();
else if (cur_type_desc == type_desc_from_type<uint64_t>())
args_cur.next_var<uint64_t>();
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
// Floating point numbers are stored separately from the other arguments.
else if (cur_type_desc == type_desc_from_type<double>())
args_cur.next_var<double>();
else if (cur_type_desc == type_desc_from_type<long double>())
args_cur.next_var<long double>();
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
// pointers may be stored separately from normal values.
else if (cur_type_desc == type_desc_from_type<void *>())
args_cur.next_var<void *>();
else
args_cur.next_var<uint32_t>();
++args_index;
}
return true;
}
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
} // namespace printf_core
} // namespace __llvm_libc

View File

@@ -13,6 +13,7 @@
#include "src/__support/CPP/type_traits.h"
#include "src/__support/arg_list.h"
#include "src/__support/common.h"
#include "src/__support/str_to_integer.h"
#include "src/stdio/printf_core/core_structs.h"
#include "src/stdio/printf_core/printf_config.h"
@@ -21,13 +22,33 @@
namespace __llvm_libc {
namespace printf_core {
#ifndef LIBC_COPT_MOCK_ARG_LIST
using ArgProvider = internal::ArgList;
#else // not defined LIBC_COPT_MOCK_ARG_LIST
using ArgProvider = internal::MockArgList;
#endif // LIBC_COPT_MOCK_ARG_LIST
template <typename T> struct int_type_of {
using type = T;
};
template <> struct int_type_of<double> {
using type = fputil::FPBits<double>::UIntType;
};
template <> struct int_type_of<long double> {
using type = fputil::FPBits<long double>::UIntType;
};
template <typename T> using int_type_of_v = typename int_type_of<T>::type;
class Parser {
#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
#define WRITE_ARG_VAL_SIMPLEST(dst, arg_type, index) \
{ \
auto temp = get_arg_value<arg_type>(index); \
if (!temp.has_value()) { \
section.has_conv = false; \
} else { \
dst = cpp::bit_cast<int_type_of_v<arg_type>>(temp.value()); \
} \
}
#else
#define WRITE_ARG_VAL_SIMPLEST(dst, arg_type, _) \
dst = cpp::bit_cast<int_type_of_v<arg_type>>(get_next_arg_value<arg_type>())
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
template <typename ArgProvider> class Parser {
const char *__restrict str;
size_t cur_pos = 0;
@@ -67,24 +88,230 @@ public:
// specified format section. This can either be a raw format section with no
// conversion, or a format section with a conversion that has all of its
// variables stored in the format section.
FormatSection get_next_section();
LIBC_INLINE FormatSection get_next_section() {
FormatSection section;
size_t starting_pos = cur_pos;
if (str[cur_pos] == '%') {
// format section
section.has_conv = true;
++cur_pos;
[[maybe_unused]] size_t conv_index = 0;
#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
conv_index = parse_index(&cur_pos);
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
section.flags = parse_flags(&cur_pos);
// handle width
section.min_width = 0;
if (str[cur_pos] == '*') {
++cur_pos;
WRITE_ARG_VAL_SIMPLEST(section.min_width, int, parse_index(&cur_pos));
} else if (internal::isdigit(str[cur_pos])) {
auto result = internal::strtointeger<int>(str + cur_pos, 10);
section.min_width = result.value;
cur_pos = cur_pos + result.parsed_len;
}
if (section.min_width < 0) {
section.min_width = -section.min_width;
section.flags = static_cast<FormatFlags>(section.flags |
FormatFlags::LEFT_JUSTIFIED);
}
// handle precision
section.precision = -1; // negative precisions are ignored.
if (str[cur_pos] == '.') {
++cur_pos;
section.precision = 0; // if there's a . but no specified precision, the
// precision is implicitly 0.
if (str[cur_pos] == '*') {
++cur_pos;
WRITE_ARG_VAL_SIMPLEST(section.precision, int, parse_index(&cur_pos));
} else if (internal::isdigit(str[cur_pos])) {
auto result = internal::strtointeger<int>(str + cur_pos, 10);
section.precision = result.value;
cur_pos = cur_pos + result.parsed_len;
}
}
LengthModifier lm = parse_length_modifier(&cur_pos);
section.length_modifier = lm;
section.conv_name = str[cur_pos];
switch (str[cur_pos]) {
case ('%'):
// Regardless of options, a % conversion is always safe. The standard
// says that "The complete conversion specification shall be %%" but it
// also says that "If a conversion specification is invalid, the
// behavior is undefined." Based on that we define that any conversion
// specification ending in '%' shall display as '%' regardless of any
// valid or invalid options.
section.has_conv = true;
break;
case ('c'):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
break;
case ('d'):
case ('i'):
case ('o'):
case ('x'):
case ('X'):
case ('u'):
switch (lm) {
case (LengthModifier::hh):
case (LengthModifier::h):
case (LengthModifier::none):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
break;
case (LengthModifier::l):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long, conv_index);
break;
case (LengthModifier::ll):
case (LengthModifier::L): // This isn't in the standard, but is in other
// libc implementations.
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long long, conv_index);
break;
case (LengthModifier::j):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, intmax_t, conv_index);
break;
case (LengthModifier::z):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, size_t, conv_index);
break;
case (LengthModifier::t):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, ptrdiff_t, conv_index);
break;
}
break;
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
case ('f'):
case ('F'):
case ('e'):
case ('E'):
case ('a'):
case ('A'):
case ('g'):
case ('G'):
if (lm != LengthModifier::L) {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, double, conv_index);
} else {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long double, conv_index);
}
break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('n'):
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('p'):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_ptr, void *, conv_index);
break;
case ('s'):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_ptr, char *, conv_index);
break;
default:
// if the conversion is undefined, change this to a raw section.
section.has_conv = false;
break;
}
// If the end of the format section is on the '\0'. This means we need to
// not advance the cur_pos.
if (str[cur_pos] != '\0')
++cur_pos;
} else {
// raw section
section.has_conv = false;
while (str[cur_pos] != '%' && str[cur_pos] != '\0')
++cur_pos;
}
section.raw_string = {str + starting_pos, cur_pos - starting_pos};
return section;
}
private:
// parse_flags parses the flags inside a format string. It assumes that
// str[*local_pos] is inside a format specifier, and parses any flags it
// finds. It returns a FormatFlags object containing the set of found flags
// arithmetically or'd together. local_pos will be moved past any flags found.
FormatFlags parse_flags(size_t *local_pos);
LIBC_INLINE FormatFlags parse_flags(size_t *local_pos) {
bool found_flag = true;
FormatFlags flags = FormatFlags(0);
while (found_flag) {
switch (str[*local_pos]) {
case '-':
flags = static_cast<FormatFlags>(flags | FormatFlags::LEFT_JUSTIFIED);
break;
case '+':
flags = static_cast<FormatFlags>(flags | FormatFlags::FORCE_SIGN);
break;
case ' ':
flags = static_cast<FormatFlags>(flags | FormatFlags::SPACE_PREFIX);
break;
case '#':
flags = static_cast<FormatFlags>(flags | FormatFlags::ALTERNATE_FORM);
break;
case '0':
flags = static_cast<FormatFlags>(flags | FormatFlags::LEADING_ZEROES);
break;
default:
found_flag = false;
}
if (found_flag)
++*local_pos;
}
return flags;
}
// parse_length_modifier parses the length modifier inside a format string. It
// assumes that str[*local_pos] is inside a format specifier. It returns a
// LengthModifier with the length modifier it found. It will advance local_pos
// after the format specifier if one is found.
LengthModifier parse_length_modifier(size_t *local_pos);
LIBC_INLINE LengthModifier parse_length_modifier(size_t *local_pos) {
switch (str[*local_pos]) {
case ('l'):
if (str[*local_pos + 1] == 'l') {
*local_pos += 2;
return LengthModifier::ll;
} else {
++*local_pos;
return LengthModifier::l;
}
case ('h'):
if (str[*local_pos + 1] == 'h') {
*local_pos += 2;
return LengthModifier::hh;
} else {
++*local_pos;
return LengthModifier::h;
}
case ('L'):
++*local_pos;
return LengthModifier::L;
case ('j'):
++*local_pos;
return LengthModifier::j;
case ('z'):
++*local_pos;
return LengthModifier::z;
case ('t'):
++*local_pos;
return LengthModifier::t;
default:
return LengthModifier::none;
}
}
// get_next_arg_value gets the next value from the arg list as type T.
template <class T> LIBC_INLINE T get_next_arg_value() {
return args_cur.next_var<T>();
return args_cur.template next_var<T>();
}
//----------------------------------------------------
@@ -98,7 +325,17 @@ private:
// returns 0 if there is no closing $, or if it finds no number. If it finds a
// number, it will move local_pos past the end of the $, else it will not move
// local_pos.
size_t parse_index(size_t *local_pos);
LIBC_INLINE size_t parse_index(size_t *local_pos) {
if (internal::isdigit(str[*local_pos])) {
auto result = internal::strtointeger<int>(str + *local_pos, 10);
size_t index = result.value;
if (str[*local_pos + result.parsed_len] != '$')
return 0;
*local_pos = 1 + result.parsed_len + *local_pos;
return index;
}
return 0;
}
LIBC_INLINE void set_type_desc(size_t index, TypeDesc value) {
if (index != 0 && index <= DESC_ARR_LEN)
@@ -131,13 +368,191 @@ private:
// It moves cur_args to the index requested so the appropriate value may
// be read. This may involve parsing the format string, and is in the worst
// case an O(n^2) operation.
bool args_to_index(size_t index);
LIBC_INLINE bool args_to_index(size_t index) {
if (args_index > index) {
args_index = 1;
args_cur = args_start;
}
while (args_index < index) {
TypeDesc cur_type_desc = type_desc_from_type<void>();
if (args_index <= DESC_ARR_LEN)
cur_type_desc = desc_arr[args_index - 1];
if (cur_type_desc == type_desc_from_type<void>())
cur_type_desc = get_type_desc(args_index);
// A type of void represents the type being unknown. If the type for the
// requested index isn't in the desc_arr and isn't found by parsing the
// string, then then advancing to the requested index is impossible. In
// that case the function returns false.
if (cur_type_desc == type_desc_from_type<void>())
return false;
if (cur_type_desc == type_desc_from_type<uint32_t>())
args_cur.template next_var<uint32_t>();
else if (cur_type_desc == type_desc_from_type<uint64_t>())
args_cur.template next_var<uint64_t>();
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
// Floating point numbers are stored separately from the other arguments.
else if (cur_type_desc == type_desc_from_type<double>())
args_cur.template next_var<double>();
else if (cur_type_desc == type_desc_from_type<long double>())
args_cur.template next_var<long double>();
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
// pointers may be stored separately from normal values.
else if (cur_type_desc == type_desc_from_type<void *>())
args_cur.template next_var<void *>();
else
args_cur.template next_var<uint32_t>();
++args_index;
}
return true;
}
// get_type_desc assumes that this format string uses index mode. It iterates
// through the format string until it finds a format specifier that defines
// the type of index, and returns a TypeDesc describing that type. It does not
// modify cur_pos.
TypeDesc get_type_desc(size_t index);
LIBC_INLINE TypeDesc get_type_desc(size_t index) {
// index mode is assumed, and the indicies start at 1, so an index
// of 0 is invalid.
size_t local_pos = 0;
while (str[local_pos]) {
if (str[local_pos] == '%') {
++local_pos;
size_t conv_index = parse_index(&local_pos);
// the flags aren't relevant for this situation, but I need to skip past
// them so they're parsed but the result is discarded.
parse_flags(&local_pos);
// handle width
if (str[local_pos] == '*') {
++local_pos;
size_t width_index = parse_index(&local_pos);
set_type_desc(width_index, type_desc_from_type<int>());
if (width_index == index)
return type_desc_from_type<int>();
} else if (internal::isdigit(str[local_pos])) {
while (internal::isdigit(str[local_pos]))
++local_pos;
}
// handle precision
if (str[local_pos] == '.') {
++local_pos;
if (str[local_pos] == '*') {
++local_pos;
size_t precision_index = parse_index(&local_pos);
set_type_desc(precision_index, type_desc_from_type<int>());
if (precision_index == index)
return type_desc_from_type<int>();
} else if (internal::isdigit(str[local_pos])) {
while (internal::isdigit(str[local_pos]))
++local_pos;
}
}
LengthModifier lm = parse_length_modifier(&local_pos);
// if we don't have an index for this conversion, then its position is
// unknown and all this information is irrelevant. The rest of this
// logic has been for skipping past this conversion properly to avoid
// weirdness with %%.
if (conv_index == 0) {
if (str[local_pos] != '\0')
++local_pos;
continue;
}
TypeDesc conv_size = type_desc_from_type<void>();
switch (str[local_pos]) {
case ('%'):
conv_size = type_desc_from_type<void>();
break;
case ('c'):
conv_size = type_desc_from_type<int>();
break;
case ('d'):
case ('i'):
case ('o'):
case ('x'):
case ('X'):
case ('u'):
switch (lm) {
case (LengthModifier::hh):
case (LengthModifier::h):
case (LengthModifier::none):
conv_size = type_desc_from_type<int>();
break;
case (LengthModifier::l):
conv_size = type_desc_from_type<long>();
break;
case (LengthModifier::ll):
case (LengthModifier::L): // This isn't in the standard, but is in
// other libc implementations.
conv_size = type_desc_from_type<long long>();
break;
case (LengthModifier::j):
conv_size = type_desc_from_type<intmax_t>();
break;
case (LengthModifier::z):
conv_size = type_desc_from_type<size_t>();
break;
case (LengthModifier::t):
conv_size = type_desc_from_type<ptrdiff_t>();
break;
}
break;
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
case ('f'):
case ('F'):
case ('e'):
case ('E'):
case ('a'):
case ('A'):
case ('g'):
case ('G'):
if (lm != LengthModifier::L)
conv_size = type_desc_from_type<double>();
else
conv_size = type_desc_from_type<long double>();
break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('n'):
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('p'):
case ('s'):
conv_size = type_desc_from_type<void *>();
break;
default:
conv_size = type_desc_from_type<int>();
break;
}
set_type_desc(conv_index, conv_size);
if (conv_index == index)
return conv_size;
}
// If the end of the format section is on the '\0'. This means we need to
// not advance the local_pos.
if (str[local_pos] != '\0')
++local_pos;
}
// If there is no size for the requested index, then it's unknown. Return
// void.
return type_desc_from_type<void>();
}
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
};

View File

@@ -21,7 +21,7 @@ namespace printf_core {
int printf_main(Writer *writer, const char *__restrict str,
internal::ArgList &args) {
Parser parser(str, args);
Parser<internal::ArgList> parser(str, args);
int result = 0;
for (FormatSection cur_section = parser.get_next_section();
!cur_section.raw_string.empty();

View File

@@ -8,10 +8,8 @@ add_header_library(
libc.src.__support.FPUtil.fp_bits
)
add_object_library(
add_header_library(
parser
SRCS
parser.cpp
HDRS
parser.h
DEPENDS

View File

@@ -1,225 +0,0 @@
//===-- Format string parser implementation for scanf ----------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// #define LIBC_COPT_SCANF_DISABLE_INDEX_MODE 1 // This will be a compile flag.
#include "src/stdio/scanf_core/parser.h"
#include "src/__support/arg_list.h"
#include "src/__support/CPP/bit.h"
#include "src/__support/CPP/bitset.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/ctype_utils.h"
#include "src/__support/str_to_integer.h"
namespace __llvm_libc {
namespace scanf_core {
#ifndef LIBC_COPT_SCANF_DISABLE_INDEX_MODE
#define GET_ARG_VAL_SIMPLEST(arg_type, index) get_arg_value<arg_type>(index)
#else
#define GET_ARG_VAL_SIMPLEST(arg_type, _) get_next_arg_value<arg_type>()
#endif // LIBC_COPT_SCANF_DISABLE_INDEX_MODE
FormatSection Parser::get_next_section() {
FormatSection section;
size_t starting_pos = cur_pos;
if (str[cur_pos] == '%') {
// format section
section.has_conv = true;
++cur_pos;
[[maybe_unused]] size_t conv_index = 0;
#ifndef LIBC_COPT_SCANF_DISABLE_INDEX_MODE
conv_index = parse_index(&cur_pos);
#endif // LIBC_COPT_SCANF_DISABLE_INDEX_MODE
if (str[cur_pos] == '*') {
++cur_pos;
section.flags = FormatFlags::NO_WRITE;
}
// handle width
section.max_width = -1;
if (internal::isdigit(str[cur_pos])) {
auto result = internal::strtointeger<int>(str + cur_pos, 10);
section.max_width = result.value;
cur_pos = cur_pos + result.parsed_len;
}
// TODO(michaelrj): add posix allocate flag support.
// if (str[cur_pos] == 'm') {
// ++cur_pos;
// section.flags = FormatFlags::ALLOCATE;
// }
LengthModifier lm = parse_length_modifier(&cur_pos);
section.length_modifier = lm;
section.conv_name = str[cur_pos];
// If NO_WRITE is not set, then read the next arg as the output pointer.
if ((section.flags & FormatFlags::NO_WRITE) == 0) {
// Since all outputs are pointers, there's no need to distinguish when
// reading from va_args. They're all the same size and stored the same.
section.output_ptr = GET_ARG_VAL_SIMPLEST(void *, conv_index);
}
// If the end of the format section is on the '\0'. This means we need to
// not advance the cur_pos and we should not count this has having a
// conversion.
if (str[cur_pos] != '\0') {
++cur_pos;
} else {
section.has_conv = false;
}
// If the format is a bracketed one, then we need to parse out the insides
// of the brackets.
if (section.conv_name == '[') {
constexpr char CLOSING_BRACKET = ']';
constexpr char INVERT_FLAG = '^';
constexpr char RANGE_OPERATOR = '-';
cpp::bitset<256> scan_set;
bool invert = false;
// The circumflex in the first position represents the inversion flag, but
// it's easier to apply that at the end so we just store it for now.
if (str[cur_pos] == INVERT_FLAG) {
invert = true;
++cur_pos;
}
// This is used to determine if a hyphen is being used as a literal or as
// a range operator.
size_t set_start_pos = cur_pos;
// Normally the right bracket closes the set, but if it's the first
// character (possibly after the inversion flag) then it's instead
// included as a character in the set and the second right bracket closes
// the set.
if (str[cur_pos] == CLOSING_BRACKET) {
scan_set.set(CLOSING_BRACKET);
++cur_pos;
}
while (str[cur_pos] != '\0' && str[cur_pos] != CLOSING_BRACKET) {
// If a hyphen is being used as a range operator, since it's neither at
// the beginning nor end of the set.
if (str[cur_pos] == RANGE_OPERATOR && cur_pos != set_start_pos &&
str[cur_pos + 1] != CLOSING_BRACKET && str[cur_pos + 1] != '\0') {
// Technically there is no requirement to correct the ordering of the
// range, but since the range operator is entirely implementation
// defined it seems like a good convenience.
char a = str[cur_pos - 1];
char b = str[cur_pos + 1];
char start = (a < b ? a : b);
char end = (a < b ? b : a);
scan_set.set_range(start, end);
cur_pos += 2;
} else {
scan_set.set(str[cur_pos]);
++cur_pos;
}
}
if (invert)
scan_set.flip();
if (str[cur_pos] == CLOSING_BRACKET) {
++cur_pos;
section.scan_set = scan_set;
} else {
// if the end of the string was encountered, this is not a valid set.
section.has_conv = false;
}
}
} else {
// raw section
section.has_conv = false;
while (str[cur_pos] != '%' && str[cur_pos] != '\0')
++cur_pos;
}
section.raw_string = {str + starting_pos, cur_pos - starting_pos};
return section;
}
LengthModifier Parser::parse_length_modifier(size_t *local_pos) {
switch (str[*local_pos]) {
case ('l'):
if (str[*local_pos + 1] == 'l') {
*local_pos += 2;
return LengthModifier::ll;
} else {
++*local_pos;
return LengthModifier::l;
}
case ('h'):
if (str[*local_pos + 1] == 'h') {
*local_pos += 2;
return LengthModifier::hh;
} else {
++*local_pos;
return LengthModifier::h;
}
case ('L'):
++*local_pos;
return LengthModifier::L;
case ('j'):
++*local_pos;
return LengthModifier::j;
case ('z'):
++*local_pos;
return LengthModifier::z;
case ('t'):
++*local_pos;
return LengthModifier::t;
default:
return LengthModifier::NONE;
}
}
//----------------------------------------------------
// INDEX MODE ONLY FUNCTIONS AFTER HERE:
//----------------------------------------------------
#ifndef LIBC_COPT_SCANF_DISABLE_INDEX_MODE
size_t Parser::parse_index(size_t *local_pos) {
if (internal::isdigit(str[*local_pos])) {
auto result = internal::strtointeger<int>(str + *local_pos, 10);
size_t index = result.value;
if (str[*local_pos + result.parsed_len] != '$')
return 0;
*local_pos = 1 + result.parsed_len + *local_pos;
return index;
}
return 0;
}
void Parser::args_to_index(size_t index) {
if (args_index > index) {
args_index = 1;
args_cur = args_start;
}
while (args_index < index) {
// Since all arguments must be pointers, we can just read all of them as
// void * and not worry about type issues.
args_cur.next_var<void *>();
++args_index;
}
}
#endif // LIBC_COPT_SCANF_DISABLE_INDEX_MODE
} // namespace scanf_core
} // namespace __llvm_libc

View File

@@ -11,6 +11,8 @@
#include "src/__support/arg_list.h"
#include "src/__support/common.h"
#include "src/__support/ctype_utils.h"
#include "src/__support/str_to_integer.h"
#include "src/stdio/scanf_core/core_structs.h"
#include "src/stdio/scanf_core/scanf_config.h"
@@ -19,17 +21,23 @@
namespace __llvm_libc {
namespace scanf_core {
class Parser {
#ifndef LIBC_COPT_SCANF_DISABLE_INDEX_MODE
#define GET_ARG_VAL_SIMPLEST(arg_type, index) get_arg_value<arg_type>(index)
#else
#define GET_ARG_VAL_SIMPLEST(arg_type, _) get_next_arg_value<arg_type>()
#endif // LIBC_COPT_SCANF_DISABLE_INDEX_MODE
template <typename ArgProvider> class Parser {
const char *__restrict str;
size_t cur_pos = 0;
internal::ArgList args_cur;
ArgProvider args_cur;
#ifndef LIBC_COPT_SCANF_DISABLE_INDEX_MODE
// args_start stores the start of the va_args, which is used when a previous
// argument is needed. In that case, we have to read the arguments from the
// beginning since they don't support reading backwards.
internal::ArgList args_start;
ArgProvider args_start;
size_t args_index = 1;
#endif // LIBC_COPT_SCANF_DISABLE_INDEX_MODE
@@ -46,18 +54,173 @@ public:
// specified format section. This can either be a raw format section with no
// conversion, or a format section with a conversion that has all of its
// variables stored in the format section.
FormatSection get_next_section();
LIBC_INLINE FormatSection get_next_section() {
FormatSection section;
size_t starting_pos = cur_pos;
if (str[cur_pos] == '%') {
// format section
section.has_conv = true;
++cur_pos;
[[maybe_unused]] size_t conv_index = 0;
#ifndef LIBC_COPT_SCANF_DISABLE_INDEX_MODE
conv_index = parse_index(&cur_pos);
#endif // LIBC_COPT_SCANF_DISABLE_INDEX_MODE
if (str[cur_pos] == '*') {
++cur_pos;
section.flags = FormatFlags::NO_WRITE;
}
// handle width
section.max_width = -1;
if (internal::isdigit(str[cur_pos])) {
auto result = internal::strtointeger<int>(str + cur_pos, 10);
section.max_width = result.value;
cur_pos = cur_pos + result.parsed_len;
}
// TODO(michaelrj): add posix allocate flag support.
// if (str[cur_pos] == 'm') {
// ++cur_pos;
// section.flags = FormatFlags::ALLOCATE;
// }
LengthModifier lm = parse_length_modifier(&cur_pos);
section.length_modifier = lm;
section.conv_name = str[cur_pos];
// If NO_WRITE is not set, then read the next arg as the output pointer.
if ((section.flags & FormatFlags::NO_WRITE) == 0) {
// Since all outputs are pointers, there's no need to distinguish when
// reading from va_args. They're all the same size and stored the same.
section.output_ptr = GET_ARG_VAL_SIMPLEST(void *, conv_index);
}
// If the end of the format section is on the '\0'. This means we need to
// not advance the cur_pos and we should not count this has having a
// conversion.
if (str[cur_pos] != '\0') {
++cur_pos;
} else {
section.has_conv = false;
}
// If the format is a bracketed one, then we need to parse out the insides
// of the brackets.
if (section.conv_name == '[') {
constexpr char CLOSING_BRACKET = ']';
constexpr char INVERT_FLAG = '^';
constexpr char RANGE_OPERATOR = '-';
cpp::bitset<256> scan_set;
bool invert = false;
// The circumflex in the first position represents the inversion flag,
// but it's easier to apply that at the end so we just store it for now.
if (str[cur_pos] == INVERT_FLAG) {
invert = true;
++cur_pos;
}
// This is used to determine if a hyphen is being used as a literal or
// as a range operator.
size_t set_start_pos = cur_pos;
// Normally the right bracket closes the set, but if it's the first
// character (possibly after the inversion flag) then it's instead
// included as a character in the set and the second right bracket
// closes the set.
if (str[cur_pos] == CLOSING_BRACKET) {
scan_set.set(CLOSING_BRACKET);
++cur_pos;
}
while (str[cur_pos] != '\0' && str[cur_pos] != CLOSING_BRACKET) {
// If a hyphen is being used as a range operator, since it's neither
// at the beginning nor end of the set.
if (str[cur_pos] == RANGE_OPERATOR && cur_pos != set_start_pos &&
str[cur_pos + 1] != CLOSING_BRACKET && str[cur_pos + 1] != '\0') {
// Technically there is no requirement to correct the ordering of
// the range, but since the range operator is entirely
// implementation defined it seems like a good convenience.
char a = str[cur_pos - 1];
char b = str[cur_pos + 1];
char start = (a < b ? a : b);
char end = (a < b ? b : a);
scan_set.set_range(start, end);
cur_pos += 2;
} else {
scan_set.set(str[cur_pos]);
++cur_pos;
}
}
if (invert)
scan_set.flip();
if (str[cur_pos] == CLOSING_BRACKET) {
++cur_pos;
section.scan_set = scan_set;
} else {
// if the end of the string was encountered, this is not a valid set.
section.has_conv = false;
}
}
} else {
// raw section
section.has_conv = false;
while (str[cur_pos] != '%' && str[cur_pos] != '\0')
++cur_pos;
}
section.raw_string = {str + starting_pos, cur_pos - starting_pos};
return section;
}
private:
// parse_length_modifier parses the length modifier inside a format string. It
// assumes that str[*local_pos] is inside a format specifier. It returns a
// LengthModifier with the length modifier it found. It will advance local_pos
// after the format specifier if one is found.
LengthModifier parse_length_modifier(size_t *local_pos);
LIBC_INLINE LengthModifier parse_length_modifier(size_t *local_pos) {
switch (str[*local_pos]) {
case ('l'):
if (str[*local_pos + 1] == 'l') {
*local_pos += 2;
return LengthModifier::ll;
} else {
++*local_pos;
return LengthModifier::l;
}
case ('h'):
if (str[*local_pos + 1] == 'h') {
*local_pos += 2;
return LengthModifier::hh;
} else {
++*local_pos;
return LengthModifier::h;
}
case ('L'):
++*local_pos;
return LengthModifier::L;
case ('j'):
++*local_pos;
return LengthModifier::j;
case ('z'):
++*local_pos;
return LengthModifier::z;
case ('t'):
++*local_pos;
return LengthModifier::t;
default:
return LengthModifier::NONE;
}
}
// get_next_arg_value gets the next value from the arg list as type T.
template <class T> LIBC_INLINE T get_next_arg_value() {
return args_cur.next_var<T>();
return args_cur.template next_var<T>();
}
//----------------------------------------------------
@@ -71,7 +234,17 @@ private:
// returns 0 if there is no closing $, or if it finds no number. If it finds a
// number, it will move local_pos past the end of the $, else it will not move
// local_pos.
size_t parse_index(size_t *local_pos);
LIBC_INLINE size_t parse_index(size_t *local_pos) {
if (internal::isdigit(str[*local_pos])) {
auto result = internal::strtointeger<int>(str + *local_pos, 10);
size_t index = result.value;
if (str[*local_pos + result.parsed_len] != '$')
return 0;
*local_pos = 1 + result.parsed_len + *local_pos;
return index;
}
return 0;
}
// get_arg_value gets the value from the arg list at index (starting at 1).
// This may require parsing the format string. An index of 0 is interpreted as
@@ -89,7 +262,19 @@ private:
// It moves cur_args to the index requested so the appropriate value may
// be read. This may involve parsing the format string, and is in the worst
// case an O(n^2) operation.
void args_to_index(size_t index);
LIBC_INLINE void args_to_index(size_t index) {
if (args_index > index) {
args_index = 1;
args_cur = args_start;
}
while (args_index < index) {
// Since all arguments must be pointers, we can just read all of them as
// void * and not worry about type issues.
args_cur.template next_var<void *>();
++args_index;
}
}
#endif // LIBC_COPT_SCANF_DISABLE_INDEX_MODE
};

View File

@@ -21,7 +21,7 @@ namespace scanf_core {
int scanf_main(Reader *reader, const char *__restrict str,
internal::ArgList &args) {
Parser parser(str, args);
Parser<internal::ArgList> parser(str, args);
int ret_val = READ_OK;
int conversions = 0;
for (FormatSection cur_section = parser.get_next_section();

View File

@@ -17,24 +17,25 @@
#include "test/UnitTest/Test.h"
using __llvm_libc::cpp::string_view;
using __llvm_libc::internal::ArgList;
void init(const char *__restrict str, ...) {
va_list vlist;
va_start(vlist, str);
__llvm_libc::internal::ArgList v(vlist);
ArgList v(vlist);
va_end(vlist);
__llvm_libc::printf_core::Parser parser(str, v);
__llvm_libc::printf_core::Parser<ArgList> parser(str, v);
}
void evaluate(__llvm_libc::printf_core::FormatSection *format_arr,
const char *__restrict str, ...) {
va_list vlist;
va_start(vlist, str);
__llvm_libc::internal::ArgList v(vlist);
ArgList v(vlist);
va_end(vlist);
__llvm_libc::printf_core::Parser parser(str, v);
__llvm_libc::printf_core::Parser<ArgList> parser(str, v);
for (auto cur_section = parser.get_next_section();
!cur_section.raw_string.empty();

View File

@@ -18,14 +18,15 @@
#include "test/UnitTest/Test.h"
using __llvm_libc::cpp::string_view;
using __llvm_libc::internal::ArgList;
void init(const char *__restrict str, ...) {
va_list vlist;
va_start(vlist, str);
__llvm_libc::internal::ArgList v(vlist);
ArgList v(vlist);
va_end(vlist);
__llvm_libc::scanf_core::Parser parser(str, v);
__llvm_libc::scanf_core::Parser<ArgList> parser(str, v);
}
void evaluate(__llvm_libc::scanf_core::FormatSection *format_arr,
@@ -35,7 +36,7 @@ void evaluate(__llvm_libc::scanf_core::FormatSection *format_arr,
__llvm_libc::internal::ArgList v(vlist);
va_end(vlist);
__llvm_libc::scanf_core::Parser parser(str, v);
__llvm_libc::scanf_core::Parser<ArgList> parser(str, v);
for (auto cur_section = parser.get_next_section();
!cur_section.raw_string.empty();

View File

@@ -2692,7 +2692,6 @@ libc_support_library(
libc_support_library(
name = "printf_parser",
srcs = ["src/stdio/printf_core/parser.cpp"],
hdrs = ["src/stdio/printf_core/parser.h"],
defines = PRINTF_COPTS,
deps = [
@@ -2714,7 +2713,6 @@ libc_support_library(
# Only used for testing.
libc_support_library(
name = "printf_mock_parser",
srcs = ["src/stdio/printf_core/parser.cpp"],
hdrs = ["src/stdio/printf_core/parser.h"],
defines = PRINTF_COPTS + ["LIBC_COPT_MOCK_ARG_LIST"],
deps = [