diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -394,6 +394,9 @@ libc.src.stdio.getc libc.src.stdio.getc_unlocked libc.src.stdio.printf + libc.src.stdio.sscanf + libc.src.stdio.scanf + libc.src.stdio.fscanf libc.src.stdio.putc libc.src.stdio.putchar libc.src.stdio.puts diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -633,6 +633,26 @@ RetValSpec, [ArgSpec, ArgSpec, ArgSpec, ArgSpec] >, + FunctionSpec< + "sscanf", + RetValSpec, + [ArgSpec, + ArgSpec, + ArgSpec] + >, + FunctionSpec< + "scanf", + RetValSpec, + [ArgSpec, + ArgSpec] + >, + FunctionSpec< + "fscanf", + RetValSpec, + [ArgSpec, + ArgSpec, + ArgSpec] + >, FunctionSpec< "sprintf", RetValSpec, diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt --- a/libc/src/stdio/CMakeLists.txt +++ b/libc/src/stdio/CMakeLists.txt @@ -403,6 +403,41 @@ libc.src.__support.File.platform_file ) +add_entrypoint_object( + sscanf + SRCS + sscanf.cpp + HDRS + sscanf.h + DEPENDS + libc.src.__support.arg_list + libc.src.stdio.scanf_core.string_reader + libc.src.stdio.scanf_core.reader + libc.src.stdio.scanf_core.scanf_main +) + +add_entrypoint_object( + fscanf + SRCS + fscanf.cpp + HDRS + fscanf.h + DEPENDS + libc.src.__support.arg_list + libc.src.stdio.scanf_core.vfscanf_internal +) + +add_entrypoint_object( + scanf + SRCS + scanf.cpp + HDRS + scanf.h + DEPENDS + libc.src.__support.arg_list + libc.src.stdio.scanf_core.vfscanf_internal +) + add_entrypoint_object( sprintf SRCS diff --git a/libc/src/stdio/fscanf.h b/libc/src/stdio/fscanf.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/fscanf.h @@ -0,0 +1,20 @@ +//===-- Implementation header of fscanf -------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDIO_FSCANF_H +#define LLVM_LIBC_SRC_STDIO_FSCANF_H + +#include + +namespace __llvm_libc { + +int fscanf(::FILE *__restrict stream, const char *__restrict format, ...); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_FSCANF_H diff --git a/libc/src/stdio/fscanf.cpp b/libc/src/stdio/fscanf.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/fscanf.cpp @@ -0,0 +1,35 @@ +//===-- Implementation of fscanf --------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/fscanf.h" + +#include "src/__support/File/file.h" +#include "src/__support/arg_list.h" +#include "src/stdio/scanf_core/vfscanf_internal.h" + +#include +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, fscanf, + (::FILE *__restrict stream, const char *__restrict format, + ...)) { + va_list vlist; + va_start(vlist, format); + internal::ArgList args(vlist); // This holder class allows for easier copying + // and pointer semantics, as well as handling + // destruction automatically. + va_end(vlist); + int ret_val = scanf_core::vfscanf_internal(stream, format, args); + // This is done to avoid including stdio.h in the internals. On most systems + // EOF is -1, so this will be transformed into just "return ret_val". + return (ret_val == -1) ? EOF : ret_val; +} + +} // namespace __llvm_libc diff --git a/libc/src/stdio/scanf.h b/libc/src/stdio/scanf.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/scanf.h @@ -0,0 +1,18 @@ +//===-- Implementation header of 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDIO_SCANF_H +#define LLVM_LIBC_SRC_STDIO_SCANF_H + +namespace __llvm_libc { + +int scanf(const char *__restrict format, ...); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_SCANF_H diff --git a/libc/src/stdio/scanf.cpp b/libc/src/stdio/scanf.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/scanf.cpp @@ -0,0 +1,34 @@ +//===-- Implementation of 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/scanf.h" + +#include "src/__support/File/file.h" +#include "src/__support/arg_list.h" +#include "src/stdio/scanf_core/vfscanf_internal.h" + +#include +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, scanf, (const char *__restrict format, ...)) { + va_list vlist; + va_start(vlist, format); + internal::ArgList args(vlist); // This holder class allows for easier copying + // and pointer semantics, as well as handling + // destruction automatically. + va_end(vlist); + int ret_val = scanf_core::vfscanf_internal( + reinterpret_cast<::FILE *>(__llvm_libc::stdin), format, args); + // This is done to avoid including stdio.h in the internals. On most systems + // EOF is -1, so this will be transformed into just "return ret_val". + return (ret_val == -1) ? EOF : ret_val; +} + +} // namespace __llvm_libc diff --git a/libc/src/stdio/scanf_core/CMakeLists.txt b/libc/src/stdio/scanf_core/CMakeLists.txt --- a/libc/src/stdio/scanf_core/CMakeLists.txt +++ b/libc/src/stdio/scanf_core/CMakeLists.txt @@ -89,3 +89,16 @@ libc.src.__support.CPP.string_view libc.src.__support.CPP.limits ) + +add_object_library( + vfscanf_internal + SRCS + vfscanf_internal.cpp + HDRS + vfscanf_internal.h + DEPENDS + .reader + .file_reader + .scanf_main + libc.src.__support.arg_list +) diff --git a/libc/src/stdio/scanf_core/vfscanf_internal.h b/libc/src/stdio/scanf_core/vfscanf_internal.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/scanf_core/vfscanf_internal.h @@ -0,0 +1,24 @@ +//===-- Internal implementation header of vfscanf ---------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDIO_SCANF_CORE_VFSCANF_INTERNAL_H +#define LLVM_LIBC_SRC_STDIO_SCANF_CORE_VFSCANF_INTERNAL_H + +#include "src/__support/arg_list.h" + +#include + +namespace __llvm_libc { +namespace scanf_core { + +int vfscanf_internal(::FILE *__restrict stream, const char *__restrict format, + internal::ArgList &args); +} // namespace scanf_core +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_SCANF_CORE_VFSCANF_INTERNAL_H diff --git a/libc/src/stdio/scanf_core/vfscanf_internal.cpp b/libc/src/stdio/scanf_core/vfscanf_internal.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/scanf_core/vfscanf_internal.cpp @@ -0,0 +1,29 @@ +//===-- Internal implementation of vfscanf ---------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/scanf_core/vfscanf_internal.h" + +#include "src/__support/arg_list.h" +#include "src/stdio/scanf_core/file_reader.h" +#include "src/stdio/scanf_core/reader.h" +#include "src/stdio/scanf_core/scanf_main.h" + +#include + +namespace __llvm_libc { +namespace scanf_core { + +int vfscanf_internal(::FILE *__restrict stream, const char *__restrict format, + internal::ArgList &args) { + FileReader file_reader(stream); + scanf_core::Reader reader(&file_reader); + return scanf_core::scanf_main(&reader, format, args); +} + +} // namespace scanf_core +} // namespace __llvm_libc diff --git a/libc/src/stdio/sscanf.h b/libc/src/stdio/sscanf.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/sscanf.h @@ -0,0 +1,18 @@ +//===-- Implementation header of sscanf -------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDIO_SSCANF_H +#define LLVM_LIBC_SRC_STDIO_SSCANF_H + +namespace __llvm_libc { + +int sscanf(const char *__restrict buffer, const char *__restrict format, ...); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_SSCANF_H diff --git a/libc/src/stdio/sscanf.cpp b/libc/src/stdio/sscanf.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/sscanf.cpp @@ -0,0 +1,38 @@ +//===-- Implementation of sscanf --------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/sscanf.h" + +#include "src/__support/arg_list.h" +#include "src/stdio/scanf_core/reader.h" +#include "src/stdio/scanf_core/scanf_main.h" +#include "src/stdio/scanf_core/string_reader.h" + +#include +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, sscanf, + (const char *__restrict buffer, + const char *__restrict format, ...)) { + va_list vlist; + va_start(vlist, format); + internal::ArgList args(vlist); // This holder class allows for easier copying + // and pointer semantics, as well as handling + // destruction automatically. + va_end(vlist); + scanf_core::StringReader string_reader(buffer); + scanf_core::Reader reader(&string_reader); + int ret_val = scanf_core::scanf_main(&reader, format, args); + // This is done to avoid including stdio.h in the internals. On most systems + // EOF is -1, so this will be transformed into just "return ret_val". + return (ret_val == -1) ? EOF : ret_val; +} + +} // namespace __llvm_libc diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt --- a/libc/test/src/stdio/CMakeLists.txt +++ b/libc/test/src/stdio/CMakeLists.txt @@ -159,6 +159,31 @@ libc.src.stdio.printf ) +add_libc_unittest( + fscanf_test + SUITE + libc_stdio_unittests + SRCS + fscanf_test.cpp + DEPENDS + libc.src.stdio.fscanf + libc.src.stdio.fclose + libc.src.stdio.ferror + libc.src.stdio.fopen + libc.src.stdio.fwrite + libc.src.__support.CPP.string_view +) + +add_libc_unittest( + sscanf_test + SUITE + libc_stdio_unittests + SRCS + sscanf_test.cpp + DEPENDS + libc.src.stdio.sscanf +) + add_libc_unittest( puts_test SUITE diff --git a/libc/test/src/stdio/fscanf_test.cpp b/libc/test/src/stdio/fscanf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/fscanf_test.cpp @@ -0,0 +1,73 @@ +//===-- Unittests for fscanf ----------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/__support/CPP/string_view.h" +#include "src/stdio/fclose.h" +#include "src/stdio/ferror.h" +#include "src/stdio/fopen.h" +#include "src/stdio/fwrite.h" + +#include "src/stdio/fscanf.h" + +#include "utils/UnitTest/Test.h" + +#include +#include + +TEST(LlvmLibcFScanfTest, WriteToFile) { + constexpr char FILENAME[] = "testdata/fscanf_output.test"; + ::FILE *file = __llvm_libc::fopen(FILENAME, "w"); + ASSERT_FALSE(file == nullptr); + + int read; + + constexpr char simple[] = "A simple string with no conversions.\n"; + + ASSERT_EQ(sizeof(simple) - 1, + __llvm_libc::fwrite(simple, 1, sizeof(simple) - 1, file)); + + constexpr char numbers[] = "1234567890\n"; + + ASSERT_EQ(sizeof(numbers) - 1, + __llvm_libc::fwrite(numbers, 1, sizeof(numbers) - 1, file)); + + constexpr char numbers_and_more[] = "1234 and more\n"; + + ASSERT_EQ(sizeof(numbers_and_more) - 1, + __llvm_libc::fwrite(numbers_and_more, 1, + sizeof(numbers_and_more) - 1, file)); + + read = + __llvm_libc::fscanf(file, "Reading from a write-only file should fail."); + EXPECT_LT(read, 0); + + ASSERT_EQ(0, __llvm_libc::fclose(file)); + + file = __llvm_libc::fopen(FILENAME, "r"); + ASSERT_FALSE(file == nullptr); + + char data[50]; + read = __llvm_libc::fscanf(file, "%[A-Za-z .\n]", data); + ASSERT_EQ(read, 1); + ASSERT_STREQ(simple, data); + + read = __llvm_libc::fscanf(file, "%s", data); + ASSERT_EQ(read, 1); + ASSERT_EQ(__llvm_libc::cpp::string_view(numbers, 10), + __llvm_libc::cpp::string_view(data)); + + // The format string starts with a space to handle the fact that the %s leaves + // a trailing \n and %c doesn't strip leading whitespace. + read = __llvm_libc::fscanf(file, " %50c", data); + ASSERT_EQ(read, 1); + ASSERT_EQ(__llvm_libc::cpp::string_view(numbers_and_more), + __llvm_libc::cpp::string_view(data, sizeof(numbers_and_more) - 1)); + + ASSERT_EQ(__llvm_libc::ferror(file), 0); + ASSERT_EQ(__llvm_libc::fclose(file), 0); +} diff --git a/libc/test/src/stdio/sscanf_test.cpp b/libc/test/src/stdio/sscanf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/sscanf_test.cpp @@ -0,0 +1,30 @@ +//===-- Unittests for sscanf ----------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/sscanf.h" + +#include "utils/UnitTest/Test.h" + +TEST(LlvmLibcSScanfTest, SimpleStringConv) { + int ret_val; + char buffer[10]; + char buffer2[10]; + ret_val = __llvm_libc::sscanf("abc123", "abc %s", buffer); + ASSERT_EQ(ret_val, 1); + ASSERT_STREQ(buffer, "123"); + + ret_val = __llvm_libc::sscanf("abc123", "%3s %3s", buffer, buffer2); + ASSERT_EQ(ret_val, 2); + ASSERT_STREQ(buffer, "abc"); + ASSERT_STREQ(buffer2, "123"); + + ret_val = __llvm_libc::sscanf("abc 123", "%3s%3s", buffer, buffer2); + ASSERT_EQ(ret_val, 2); + ASSERT_STREQ(buffer, "abc"); + ASSERT_STREQ(buffer2, "123"); +}