diff --git a/kotlin-native/backend.native/tests/.gitattributes b/kotlin-native/backend.native/tests/.gitattributes new file mode 100644 index 00000000000..19937df4f58 --- /dev/null +++ b/kotlin-native/backend.native/tests/.gitattributes @@ -0,0 +1,2 @@ +runtime/basic/readln*CR*.in binary +runtime/basic/readln*CR*.out binary \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index f3a3e9004f7..ecdd40ed7b4 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -727,6 +727,61 @@ standaloneTest("hello2") { source = "runtime/basic/hello2.kt" } +standaloneTest("readlnOrNull_empty") { + useGoldenData = true + useTestData = true + source = "runtime/basic/readlnOrNull_empty.kt" +} + +standaloneTest("readln_empty") { + useTestData = true + outputChecker = { s -> s.contains("ReadAfterEOFException") } + expectedExitStatusChecker = { it != 0 } + source = "runtime/basic/readln_empty.kt" +} + +standaloneTest("readln_alone_CR") { + useGoldenData = true + useTestData = true + source = "runtime/basic/readln_alone_CR.kt" +} + +standaloneTest("readln_empty_new_line") { + useGoldenData = true + useTestData = true + source = "runtime/basic/readln_empty_new_line.kt" +} + +standaloneTest("readln_multiline") { + useGoldenData = true + useTestData = true + source = "runtime/basic/readln_multiline.kt" +} + +standaloneTest("readln_CR_and_CRLF") { + useGoldenData = true + useTestData = true + source = "runtime/basic/readln_CR_and_CRLF.kt" +} + +standaloneTest("readln_long_line") { + useGoldenData = true + useTestData = true + source = "runtime/basic/readln_long_line.kt" +} + +standaloneTest("readln_cyrillic") { + useGoldenData = true + useTestData = true + source = "runtime/basic/readln_cyrillic.kt" +} + +standaloneTest("readln_multiple_empty_new_lines") { + useGoldenData = true + useTestData = true + source = "runtime/basic/readln_multiple_empty_new_lines.kt" +} + task hello3(type: KonanLocalTest) { useGoldenData = true source = "runtime/basic/hello3.kt" diff --git a/kotlin-native/backend.native/tests/runtime/basic/readlnOrNull_empty.in b/kotlin-native/backend.native/tests/runtime/basic/readlnOrNull_empty.in new file mode 100644 index 00000000000..e69de29bb2d diff --git a/kotlin-native/backend.native/tests/runtime/basic/readlnOrNull_empty.kt b/kotlin-native/backend.native/tests/runtime/basic/readlnOrNull_empty.kt new file mode 100644 index 00000000000..9e1dd470c7c --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readlnOrNull_empty.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +fun main() { + print("${readlnOrNull()}/${readlnOrNull()}/${readlnOrNull()}") +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readlnOrNull_empty.out b/kotlin-native/backend.native/tests/runtime/basic/readlnOrNull_empty.out new file mode 100644 index 00000000000..e39a5b1474b --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readlnOrNull_empty.out @@ -0,0 +1 @@ +null/null/null \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_CR_and_CRLF.in b/kotlin-native/backend.native/tests/runtime/basic/readln_CR_and_CRLF.in new file mode 100644 index 00000000000..b693390b561 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_CR_and_CRLF.in @@ -0,0 +1,2 @@ +aaaaaa bbb +c diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_CR_and_CRLF.kt b/kotlin-native/backend.native/tests/runtime/basic/readln_CR_and_CRLF.kt new file mode 100644 index 00000000000..1c3693ba85c --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_CR_and_CRLF.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main() { + print(readln()) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_CR_and_CRLF.out b/kotlin-native/backend.native/tests/runtime/basic/readln_CR_and_CRLF.out new file mode 100644 index 00000000000..e49b14c9cd0 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_CR_and_CRLF.out @@ -0,0 +1 @@ +aaaaaa bbb \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_alone_CR.in b/kotlin-native/backend.native/tests/runtime/basic/readln_alone_CR.in new file mode 100644 index 00000000000..67c32976114 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_alone_CR.in @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_alone_CR.kt b/kotlin-native/backend.native/tests/runtime/basic/readln_alone_CR.kt new file mode 100644 index 00000000000..1c3693ba85c --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_alone_CR.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main() { + print(readln()) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_alone_CR.out b/kotlin-native/backend.native/tests/runtime/basic/readln_alone_CR.out new file mode 100644 index 00000000000..67c32976114 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_alone_CR.out @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_cyrillic.in b/kotlin-native/backend.native/tests/runtime/basic/readln_cyrillic.in new file mode 100644 index 00000000000..82383acd0ff --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_cyrillic.in @@ -0,0 +1 @@ +Привет! \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_cyrillic.kt b/kotlin-native/backend.native/tests/runtime/basic/readln_cyrillic.kt new file mode 100644 index 00000000000..1c3693ba85c --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_cyrillic.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main() { + print(readln()) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_cyrillic.out b/kotlin-native/backend.native/tests/runtime/basic/readln_cyrillic.out new file mode 100644 index 00000000000..82383acd0ff --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_cyrillic.out @@ -0,0 +1 @@ +Привет! \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_empty.in b/kotlin-native/backend.native/tests/runtime/basic/readln_empty.in new file mode 100644 index 00000000000..e69de29bb2d diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_empty.kt b/kotlin-native/backend.native/tests/runtime/basic/readln_empty.kt new file mode 100644 index 00000000000..41bcb8d78ee --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_empty.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main() { + readln() +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_empty_new_line.in b/kotlin-native/backend.native/tests/runtime/basic/readln_empty_new_line.in new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_empty_new_line.in @@ -0,0 +1 @@ + diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_empty_new_line.kt b/kotlin-native/backend.native/tests/runtime/basic/readln_empty_new_line.kt new file mode 100644 index 00000000000..1c3693ba85c --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_empty_new_line.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main() { + print(readln()) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_empty_new_line.out b/kotlin-native/backend.native/tests/runtime/basic/readln_empty_new_line.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_long_line.in b/kotlin-native/backend.native/tests/runtime/basic/readln_long_line.in new file mode 100644 index 00000000000..4ec4671b351 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_long_line.in @@ -0,0 +1 @@ +A very long line of input with length of more than initial buffer size \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_long_line.kt b/kotlin-native/backend.native/tests/runtime/basic/readln_long_line.kt new file mode 100644 index 00000000000..1c3693ba85c --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_long_line.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main() { + print(readln()) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_long_line.out b/kotlin-native/backend.native/tests/runtime/basic/readln_long_line.out new file mode 100644 index 00000000000..4ec4671b351 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_long_line.out @@ -0,0 +1 @@ +A very long line of input with length of more than initial buffer size \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_multiline.in b/kotlin-native/backend.native/tests/runtime/basic/readln_multiline.in new file mode 100644 index 00000000000..f08506a0deb --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_multiline.in @@ -0,0 +1,3 @@ +Hello World +Second line +Third line \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_multiline.kt b/kotlin-native/backend.native/tests/runtime/basic/readln_multiline.kt new file mode 100644 index 00000000000..1c3693ba85c --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_multiline.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main() { + print(readln()) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_multiline.out b/kotlin-native/backend.native/tests/runtime/basic/readln_multiline.out new file mode 100644 index 00000000000..5e1c309dae7 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_multiline.out @@ -0,0 +1 @@ +Hello World \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_multiple_empty_new_lines.in b/kotlin-native/backend.native/tests/runtime/basic/readln_multiple_empty_new_lines.in new file mode 100644 index 00000000000..1f3d4da6d2a --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_multiple_empty_new_lines.in @@ -0,0 +1,4 @@ +first +second + + diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_multiple_empty_new_lines.kt b/kotlin-native/backend.native/tests/runtime/basic/readln_multiple_empty_new_lines.kt new file mode 100644 index 00000000000..ea8fcef7263 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_multiple_empty_new_lines.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main() { + print("${readln()}/${readln()}/${readln()}/${readln()}/${readlnOrNull()}") +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/basic/readln_multiple_empty_new_lines.out b/kotlin-native/backend.native/tests/runtime/basic/readln_multiple_empty_new_lines.out new file mode 100644 index 00000000000..323210d731d --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/readln_multiple_empty_new_lines.out @@ -0,0 +1 @@ +first/second///null \ No newline at end of file diff --git a/kotlin-native/runtime/src/main/cpp/Console.cpp b/kotlin-native/runtime/src/main/cpp/Console.cpp index 076feff8de5..c7ca373c339 100644 --- a/kotlin-native/runtime/src/main/cpp/Console.cpp +++ b/kotlin-native/runtime/src/main/cpp/Console.cpp @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include + #include "KAssert.h" #include "Memory.h" #include "Natives.h" @@ -67,4 +69,35 @@ OBJ_GETTER0(Kotlin_io_Console_readLine) { RETURN_RESULT_OF(CreateStringFromCString, data); } +OBJ_GETTER0(Kotlin_io_Console_readlnOrNull) { + KStdVector data; + data.reserve(16); + bool isEOF = false; + bool isError = false; + { + kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative); + while (true) { + int result = fgetc(stdin); + + if (result == EOF || result == '\n') { + isEOF = (result == EOF); + isError = (ferror(stdin) != 0); + break; + } + + data.push_back(result); + } + } + if (isError) { + ThrowIllegalStateException(); + } + if (!isEOF && !data.empty() && data.back() == '\r') { // CRLF + data.pop_back(); + } + if (data.empty() && isEOF) { + RETURN_OBJ(nullptr); + } + RETURN_RESULT_OF(StringFromUtf8Buffer, data.data(), data.size()); +} + } // extern "C" diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/io/Console.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/io/Console.kt index c2726a29278..df68ca37012 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/io/Console.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/io/Console.kt @@ -29,6 +29,29 @@ public actual fun println(message: Any?) { @GCUnsafeCall("Kotlin_io_Console_println0") public actual external fun println() +/** + * Reads a line of input from the standard input stream and returns it, + * or throws a [RuntimeException] if EOF has already been reached when [readln] is called. + * + * LF or CRLF is treated as the line terminator. Line terminator is not included in the returned string. + * + * The input is interpreted as UTF-8. Invalid bytes are replaced by the replacement character '\uFFFD'. + */ +@SinceKotlin("1.6") +public actual fun readln(): String = readlnOrNull() ?: throw ReadAfterEOFException("EOF has already been reached") + +/** + * Reads a line of input from the standard input stream and returns it, + * or return `null` if EOF has already been reached when [readlnOrNull] is called. + * + * LF or CRLF is treated as the line terminator. Line terminator is not included in the returned string. + * + * The input is interpreted as UTF-8. Invalid bytes are replaced by the replacement character '\uFFFD'. + */ +@SinceKotlin("1.6") +@GCUnsafeCall("Kotlin_io_Console_readlnOrNull") +public actual external fun readlnOrNull(): String? + /** * Reads a line of input from the standard input stream. * diff --git a/libraries/stdlib/api/js-v1/kotlin.io.kt b/libraries/stdlib/api/js-v1/kotlin.io.kt index 53af8758555..db665c25358 100644 --- a/libraries/stdlib/api/js-v1/kotlin.io.kt +++ b/libraries/stdlib/api/js-v1/kotlin.io.kt @@ -2,4 +2,10 @@ public fun print(message: kotlin.Any?): kotlin.Unit public fun println(): kotlin.Unit -public fun println(message: kotlin.Any?): kotlin.Unit \ No newline at end of file +public fun println(message: kotlin.Any?): kotlin.Unit + +@kotlin.SinceKotlin(version = "1.6") +public fun readln(): kotlin.String + +@kotlin.SinceKotlin(version = "1.6") +public fun readlnOrNull(): kotlin.String? \ No newline at end of file diff --git a/libraries/stdlib/api/js/kotlin.io.kt b/libraries/stdlib/api/js/kotlin.io.kt index 53af8758555..db665c25358 100644 --- a/libraries/stdlib/api/js/kotlin.io.kt +++ b/libraries/stdlib/api/js/kotlin.io.kt @@ -2,4 +2,10 @@ public fun print(message: kotlin.Any?): kotlin.Unit public fun println(): kotlin.Unit -public fun println(message: kotlin.Any?): kotlin.Unit \ No newline at end of file +public fun println(message: kotlin.Any?): kotlin.Unit + +@kotlin.SinceKotlin(version = "1.6") +public fun readln(): kotlin.String + +@kotlin.SinceKotlin(version = "1.6") +public fun readlnOrNull(): kotlin.String? \ No newline at end of file diff --git a/libraries/stdlib/common/src/kotlin/ioH.kt b/libraries/stdlib/common/src/kotlin/ioH.kt index 793cb15b210..b24723cd9b0 100644 --- a/libraries/stdlib/common/src/kotlin/ioH.kt +++ b/libraries/stdlib/common/src/kotlin/ioH.kt @@ -15,5 +15,29 @@ public expect fun println(message: Any?) /** Prints the given [message] to the standard output stream. */ public expect fun print(message: Any?) +/** + * Reads a line of input from the standard input stream and returns it, + * or throws a [RuntimeException] if EOF has already been reached when [readln] is called. + * + * LF or CRLF is treated as the line terminator. Line terminator is not included in the returned string. + * + * Currently this function is not supported in Kotlin/JS and throws [UnsupportedOperationException]. + */ +@SinceKotlin("1.6") +public expect fun readln(): String + +/** + * Reads a line of input from the standard input stream and returns it, + * or return `null` if EOF has already been reached when [readlnOrNull] is called. + * + * LF or CRLF is treated as the line terminator. Line terminator is not included in the returned string. + * + * Currently this function is not supported in Kotlin/JS and throws [UnsupportedOperationException]. + */ +@SinceKotlin("1.6") +public expect fun readlnOrNull(): String? + +internal class ReadAfterEOFException(message: String?) : RuntimeException(message) + internal expect interface Serializable diff --git a/libraries/stdlib/js/src/kotlin/console.kt b/libraries/stdlib/js/src/kotlin/console.kt index f8c641620f6..08b4126ab19 100644 --- a/libraries/stdlib/js/src/kotlin/console.kt +++ b/libraries/stdlib/js/src/kotlin/console.kt @@ -104,3 +104,9 @@ public actual fun println(message: Any?) { public actual fun print(message: Any?) { output.print(message) } + +@SinceKotlin("1.6") +public actual fun readln(): String = throw UnsupportedOperationException("readln is not supported in Kotlin/JS") + +@SinceKotlin("1.6") +public actual fun readlnOrNull(): String? = throw UnsupportedOperationException("readlnOrNull is not supported in Kotlin/JS") \ No newline at end of file diff --git a/libraries/stdlib/jvm/src/kotlin/io/Console.kt b/libraries/stdlib/jvm/src/kotlin/io/Console.kt index eba084e5137..f8bd9d71dcd 100644 --- a/libraries/stdlib/jvm/src/kotlin/io/Console.kt +++ b/libraries/stdlib/jvm/src/kotlin/io/Console.kt @@ -149,7 +149,7 @@ public actual inline fun println() { * The input is decoded using the system default Charset. A [CharacterCodingException] is thrown if input is malformed. */ @SinceKotlin("1.6") -public fun readln(): String = readlnOrNull() ?: throw ReadAfterEOFException("EOF has already been reached") +public actual fun readln(): String = readlnOrNull() ?: throw ReadAfterEOFException("EOF has already been reached") /** * Reads a line of input from the standard input stream and returns it, @@ -160,9 +160,7 @@ public fun readln(): String = readlnOrNull() ?: throw ReadAfterEOFException("EOF * The input is decoded using the system default Charset. A [CharacterCodingException] is thrown if input is malformed. */ @SinceKotlin("1.6") -public fun readlnOrNull(): String? = readLine() - -private class ReadAfterEOFException(message: String?) : RuntimeException(message) +public actual fun readlnOrNull(): String? = readLine() /** * Reads a line of input from the standard input stream. diff --git a/libraries/stdlib/jvm/test/io/Console.kt b/libraries/stdlib/jvm/test/io/Console.kt index 7dd350ea589..f7fc64b8352 100644 --- a/libraries/stdlib/jvm/test/io/Console.kt +++ b/libraries/stdlib/jvm/test/io/Console.kt @@ -48,6 +48,20 @@ class ConsoleTest { testReadLine("first${linuxLineSeparator}second", listOf("first", "second")) } + @Test + fun shouldReadMultipleEmptyLines() { + testReadLine( + "first${linuxLineSeparator}second${linuxLineSeparator}${linuxLineSeparator}${linuxLineSeparator}", + listOf("first", "second", "", "") + ) + } + + @Test + fun shouldReadAloneCarriageReturn() { + val result = readLines("\r", Charsets.UTF_8) + assertEquals(listOf("\r"), result) + } + @Test fun shouldReadConsecutiveEmptyLines() { testReadLine("$linuxLineSeparator$linuxLineSeparator", listOf("", "")) diff --git a/libraries/stdlib/wasm/src/kotlin/io.kt b/libraries/stdlib/wasm/src/kotlin/io.kt index ff09cbf0bb5..df7ff268dd7 100644 --- a/libraries/stdlib/wasm/src/kotlin/io.kt +++ b/libraries/stdlib/wasm/src/kotlin/io.kt @@ -23,6 +23,12 @@ public actual fun print(message: Any?) { println(message) } +@SinceKotlin("1.6") +public actual fun readln(): String = TODO("Wasm stdlib: IO") + +@SinceKotlin("1.6") +public actual fun readlnOrNull(): String? = TODO("Wasm stdlib: IO") + internal actual interface Serializable