From b5c60e30820789c6a21bf63ea7403b668208ee8b Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Thu, 23 Mar 2017 12:54:24 +0300 Subject: [PATCH] Support data parsing operations (#380) --- FAQ.md | 4 + RELEASE_NOTES.md | 4 +- backend.native/tests/build.gradle | 13 + backend.native/tests/runtime/text/parse0.kt | 15 + runtime/src/main/cpp/Exceptions.h | 4 +- runtime/src/main/cpp/KString.cpp | 92 +++++ runtime/src/main/cpp/ToString.cpp | 38 +++ .../kotlin/konan/internal/RuntimeUtils.kt | 5 + runtime/src/main/kotlin/kotlin/Char.kt | 10 + runtime/src/main/kotlin/kotlin/Exceptions.kt | 8 + runtime/src/main/kotlin/kotlin/text/Char.kt | 16 +- .../kotlin/text/StringNumberConversions.kt | 320 ++++++++++++++++++ samples/tetris/README.md | 10 +- 13 files changed, 534 insertions(+), 5 deletions(-) create mode 100644 FAQ.md create mode 100644 backend.native/tests/runtime/text/parse0.kt create mode 100644 runtime/src/main/kotlin/kotlin/text/StringNumberConversions.kt diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 00000000000..92dd01a0610 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,4 @@ +Q: How do I run my program? + +A: Define top level function `fun main(args: Array)`, please ensure it's not +in a package. \ No newline at end of file diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6e15ddf219c..afbc4856b75 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -23,7 +23,9 @@ the following platforms: * Mac OS X 10.10 and later (x86-64) * x86-64 Ubuntu Linux (14.04, 16.04 and later), other Linux flavours may work as well - * Apple iOS (arm64 and simulator on x86-64) + * Apple iOS (arm64 and simulator on x86-64), cross-compiled on MacOS X host + * Raspberry Pi, cross-compiled on Linux host + Adding support for other target platforms shalln't be too hard, if LLVM support is available. diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index c21e7f3023d..f632d383737 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1034,6 +1034,19 @@ task string0(type: RunKonanTest) { source = "runtime/text/string0.kt" } +task parse0(type: RunKonanTest) { + goldValue = "false\n" + + "true\n" + + "-1\n" + + "-86\n" + + "30\n" + + "4294967295\n" + + "bad format\n" + + "0.5\n" + + "2.39\n" + source = "runtime/text/parse0.kt" +} + task catch1(type: RunKonanTest) { goldValue = "Before\nCaught Throwable\nDone\n" source = "runtime/exceptions/catch1.kt" diff --git a/backend.native/tests/runtime/text/parse0.kt b/backend.native/tests/runtime/text/parse0.kt new file mode 100644 index 00000000000..ec4b3208e28 --- /dev/null +++ b/backend.native/tests/runtime/text/parse0.kt @@ -0,0 +1,15 @@ +fun main(args: Array) { + println("false".toBoolean()) + println("true".toBoolean()) + println("-1".toByte()) + println("aa".toByte(16)) + println("11110".toInt(2)) + println("ffffffff".toLong(16)) + try { + val x = "ffffffff".toLong(10) + } catch (ne: NumberFormatException) { + println("bad format") + } + println("0.5".toFloat()) + println("2.39".toDouble()) +} \ No newline at end of file diff --git a/runtime/src/main/cpp/Exceptions.h b/runtime/src/main/cpp/Exceptions.h index a7fb95bcaae..2d96907deb5 100644 --- a/runtime/src/main/cpp/Exceptions.h +++ b/runtime/src/main/cpp/Exceptions.h @@ -22,8 +22,10 @@ void ThrowNullPointerException(); void ThrowArrayIndexOutOfBoundsException(); // Throws class cast exception. void ThrowClassCastException(); -// Throws arithmetic exception +// Throws arithmetic exception. void ThrowArithmeticException(); +// Throws number format exception. +void ThrowNumberFormatException(); #ifdef __cplusplus } // extern "C" diff --git a/runtime/src/main/cpp/KString.cpp b/runtime/src/main/cpp/KString.cpp index a0f9c825dad..b3c39c0890e 100644 --- a/runtime/src/main/cpp/KString.cpp +++ b/runtime/src/main/cpp/KString.cpp @@ -47,6 +47,58 @@ OBJ_GETTER(utf8ToUtf16, Raw rawString) { RETURN_OBJ(result->obj()); } +template T parseInt(KString value, KInt radix) { + Raw utf16( + CharArrayAddressOfElementAt(value, 0), value->count_); + std::string utf8; + utf8::utf16to8(utf16.begin(), utf16.end(), back_inserter(utf8)); + char* end = nullptr; + long result = strtol(utf8.c_str(), &end, radix); + if (end != utf8.c_str() + utf8.size()) { + ThrowNumberFormatException(); + } + return result; +} + +KLong parseLong(KString value, KInt radix) { + Raw utf16( + CharArrayAddressOfElementAt(value, 0), value->count_); + std::string utf8; + utf8::utf16to8(utf16.begin(), utf16.end(), back_inserter(utf8)); + char* end = nullptr; + KLong result = strtoll(utf8.c_str(), &end, radix); + if (end != utf8.c_str() + utf8.size()) { + ThrowNumberFormatException(); + } + return result; +} + +KFloat parseFloat(KString value) { + Raw utf16( + CharArrayAddressOfElementAt(value, 0), value->count_); + std::string utf8; + utf8::utf16to8(utf16.begin(), utf16.end(), back_inserter(utf8)); + char* end = nullptr; + KFloat result = strtof(utf8.c_str(), &end); + if (end != utf8.c_str() + utf8.size()) { + ThrowNumberFormatException(); + } + return result; +} + +KDouble parseDouble(KString value) { + Raw utf16( + CharArrayAddressOfElementAt(value, 0), value->count_); + std::string utf8; + utf8::utf16to8(utf16.begin(), utf16.end(), back_inserter(utf8)); + char* end = nullptr; + KDouble result = strtod(utf8.c_str(), &end); + if (end != utf8.c_str() + utf8.size()) { + ThrowNumberFormatException(); + } + return result; +} + } // namespace extern "C" { @@ -301,6 +353,22 @@ KChar Kotlin_Char_toUpperCase(KChar ch) { return towupper(ch); } +KInt Kotlin_Char_digitOf(KChar ch, KInt radix) { + // TODO: make smarter and support full unicode. + const static KInt digits[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + -1, -1, -1, -1, -1, -1, -1, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, + -1, -1, -1, -1, -1, -1, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35 }; + if (ch < 0x30 /* 0 */ || ch > 0x7a /* z */) return -1; + return digits[ch - 0x30]; +} + KInt Kotlin_String_indexOfChar(KString thiz, KChar ch, KInt fromIndex) { if (fromIndex < 0 || fromIndex > thiz->count_) { return false; @@ -430,4 +498,28 @@ OBJ_GETTER0(Kotlin_io_Console_readLine) { RETURN_RESULT_OF(CreateStringFromCString, data); } +KByte Kotlin_String_parseByte(KString value, KInt radix) { + return parseInt(value, radix); +} + +KShort Kotlin_String_parseShort(KString value, KInt radix) { + return parseInt(value, radix); +} + +KInt Kotlin_String_parseInt(KString value, KInt radix) { + return parseInt(value, radix); +} + +KLong Kotlin_String_parseLong(KString value, KInt radix) { + return parseLong(value, radix); +} + +KFloat Kotlin_String_parseFloat(KString value) { + return parseFloat(value); +} + +KDouble Kotlin_String_parseDouble(KString value) { + return parseDouble(value); +} + } // extern "C" diff --git a/runtime/src/main/cpp/ToString.cpp b/runtime/src/main/cpp/ToString.cpp index 6882d5f33c3..aab67b32f8b 100644 --- a/runtime/src/main/cpp/ToString.cpp +++ b/runtime/src/main/cpp/ToString.cpp @@ -43,12 +43,50 @@ OBJ_GETTER(Kotlin_Int_toString, KInt value) { RETURN_RESULT_OF(CreateStringFromCString, cstring); } +OBJ_GETTER(Kotlin_Int_toStringRadix, KInt value, KInt radix) { + // TODO: maye not fit for smaller radices. + char cstring[32]; + switch (radix) { + case 8: + snprintf(cstring, sizeof(cstring), "%o", value); + break; + case 10: + snprintf(cstring, sizeof(cstring), "%d", value); + break; + case 16: + snprintf(cstring, sizeof(cstring), "%x", value); + break; + default: + RuntimeAssert(false, "Unsupported radix"); + } + RETURN_RESULT_OF(CreateStringFromCString, cstring); +} + OBJ_GETTER(Kotlin_Long_toString, KLong value) { char cstring[32]; snprintf(cstring, sizeof(cstring), "%lld", static_cast(value)); RETURN_RESULT_OF(CreateStringFromCString, cstring); } +OBJ_GETTER(Kotlin_Long_toStringRadix, KLong value, KInt radix) { + // TODO: may not fit for smaller radices. + char cstring[64]; + switch (radix) { + case 8: + snprintf(cstring, sizeof(cstring), "%llo", value); + break; + case 10: + snprintf(cstring, sizeof(cstring), "%lld", value); + break; + case 16: + snprintf(cstring, sizeof(cstring), "%llx", value); + break; + default: + RuntimeAssert(false, "Unsupported radix"); + } + RETURN_RESULT_OF(CreateStringFromCString, cstring); +} + // TODO: use David Gay's dtoa() here instead. It's *very* big and ugly. OBJ_GETTER(Kotlin_Float_toString, KFloat value) { char cstring[32]; diff --git a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt index e70a0f26e23..61b5b4788e8 100644 --- a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt +++ b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt @@ -20,6 +20,11 @@ internal fun ThrowArithmeticException() : Nothing { throw ArithmeticException() } +@ExportForCppRuntime +internal fun ThrowNumberFormatException() : Nothing { + throw NumberFormatException() +} + fun ThrowNoWhenBranchMatchedException(): Nothing { throw NoWhenBranchMatchedException() } diff --git a/runtime/src/main/kotlin/kotlin/Char.kt b/runtime/src/main/kotlin/kotlin/Char.kt index 03d42e3465f..dbe0451d4d5 100644 --- a/runtime/src/main/kotlin/kotlin/Char.kt +++ b/runtime/src/main/kotlin/kotlin/Char.kt @@ -89,6 +89,16 @@ public final class Char : Comparable { * The maximum value of a Unicode surrogate code unit. */ public const val MAX_SURROGATE: Char = MAX_LOW_SURROGATE + + /** + * The minimum radix available for conversion to and from strings. + */ + public const val MIN_RADIX: Int = 2 + + /** + * The minimum radix available for conversion to and from strings. + */ + public const val MAX_RADIX: Int = 36 } // Konan-specific. diff --git a/runtime/src/main/kotlin/kotlin/Exceptions.kt b/runtime/src/main/kotlin/kotlin/Exceptions.kt index 9de433a99d0..92b1c313a79 100644 --- a/runtime/src/main/kotlin/kotlin/Exceptions.kt +++ b/runtime/src/main/kotlin/kotlin/Exceptions.kt @@ -192,4 +192,12 @@ public open class OutOfMemoryError : Error { constructor(s: String) : super(s) { } +} + +public open class NumberFormatException : IllegalArgumentException { + + constructor() : super() { + } + + constructor(s: String) : super(s) {} } \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/text/Char.kt b/runtime/src/main/kotlin/kotlin/text/Char.kt index d37894a153a..274394e6c54 100644 --- a/runtime/src/main/kotlin/kotlin/text/Char.kt +++ b/runtime/src/main/kotlin/kotlin/text/Char.kt @@ -78,4 +78,18 @@ external public fun Char.isHighSurrogate(): Boolean * Returns `true` if this character is a Unicode low-surrogate code unit (also known as trailing-surrogate code unit). */ @SymbolName("Kotlin_Char_isLowSurrogate") -external public fun Char.isLowSurrogate(): Boolean \ No newline at end of file +external public fun Char.isLowSurrogate(): Boolean + +@SymbolName("Kotlin_Char_digitOf") +external internal fun digitOf(char: Char, radix: Int): Int + +/** + * Checks whether the given [radix] is valid radix for string to number and number to string conversion. + */ +@PublishedApi +internal fun checkRadix(radix: Int): Int { + if(radix !in Char.MIN_RADIX..Char.MAX_RADIX) { + throw IllegalArgumentException("radix $radix was not in valid range ${Char.MIN_RADIX..Char.MAX_RADIX}") + } + return radix +} diff --git a/runtime/src/main/kotlin/kotlin/text/StringNumberConversions.kt b/runtime/src/main/kotlin/kotlin/text/StringNumberConversions.kt new file mode 100644 index 00000000000..c8d872e8c0d --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/text/StringNumberConversions.kt @@ -0,0 +1,320 @@ +package kotlin.text + +/** + * Returns a string representation of this [Byte] value in the specified [radix]. + */ +@SinceKotlin("1.1") +@kotlin.internal.InlineOnly +public inline fun Byte.toString(radix: Int): String = this.toInt().toString(checkRadix(radix)) + +/** + * Returns a string representation of this [Short] value in the specified [radix]. + */ +@SinceKotlin("1.1") +@kotlin.internal.InlineOnly +public inline fun Short.toString(radix: Int): String = this.toInt().toString(checkRadix(radix)) + +@SymbolName("Kotlin_Int_toStringRadix") +external private fun intToString(value: Int, radix: Int): String + +/** + * Returns a string representation of this [Int] value in the specified [radix]. + */ +@SinceKotlin("1.1") +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun Int.toString(radix: Int): String = intToString(this, checkRadix(radix)) + +@SymbolName("Kotlin_Long_toStringRadix") +external private fun longToString(value: Long, radix: Int): String + +/** + * Returns a string representation of this [Long] value in the specified [radix]. + */ +@SinceKotlin("1.1") +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun Long.toString(radix: Int): String = longToString(this, checkRadix(radix)) + +/** + * Returns `true` if the contents of this string is equal to the word "true", ignoring case, and `false` otherwise. + */ +@kotlin.internal.InlineOnly +public inline fun String.toBoolean(): Boolean = this == "true" + +@SymbolName("Kotlin_String_parseByte") +external private fun parseByte(value: String, radix: Int): Byte + +/** + * Parses the string as a signed [Byte] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toByte(): Byte = parseByte(this, 10) + +/** + * Parses the string as a signed [Byte] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toByte(radix: Int): Byte = parseByte(this, checkRadix(radix)) + +@SymbolName("Kotlin_String_parseShort") +external private fun parseShort(value: String, radix: Int): Short + +/** + * Parses the string as a [Short] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toShort(): Short = parseShort(this, 10) + +/** + * Parses the string as a [Short] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toShort(radix: Int): Short = parseShort(this, checkRadix(radix)) + +@SymbolName("Kotlin_String_parseInt") +external private fun parseInt(value: String, radix: Int): Int + +/** + * Parses the string as an [Int] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toInt(): Int = parseInt(this, 10) + +/** + * Parses the string as an [Int] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toInt(radix: Int): Int = parseInt(this, checkRadix(radix)) + + +@SymbolName("Kotlin_String_parseLong") +external private fun parseLong(value: String, radix: Int): Long + +/** + * Parses the string as a [Long] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toLong(): Long = parseLong(this, 10) + +/** + * Parses the string as a [Long] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toLong(radix: Int): Long = parseLong(this, checkRadix(radix)) + +@SymbolName("Kotlin_String_parseFloat") +external private fun parseFloat(value: String): Float + +/** + * Parses the string as a [Float] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toFloat(): Float = parseFloat(this) + + +@SymbolName("Kotlin_String_parseDouble") +external private fun parseDouble(value: String): Double + +/** + * Parses the string as a [Double] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@kotlin.internal.InlineOnly +@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") +public inline fun String.toDouble(): Double = parseDouble(this) + + +/** + * Parses the string as a signed [Byte] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toByteOrNull(): Byte? = toByteOrNull(radix = 10) + +/** + * Parses the string as a signed [Byte] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toByteOrNull(radix: Int): Byte? { + val int = this.toIntOrNull(radix) ?: return null + if (int < Byte.MIN_VALUE || int > Byte.MAX_VALUE) return null + return int.toByte() +} + +/** + * Parses the string as a [Short] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toShortOrNull(): Short? = toShortOrNull(radix = 10) + +/** + * Parses the string as a [Short] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toShortOrNull(radix: Int): Short? { + val int = this.toIntOrNull(radix) ?: return null + if (int < Short.MIN_VALUE || int > Short.MAX_VALUE) return null + return int.toShort() +} + +/** + * Parses the string as an [Int] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toIntOrNull(): Int? = toIntOrNull(radix = 10) + +/** + * Parses the string as an [Int] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toIntOrNull(radix: Int): Int? { + checkRadix(radix) + + val length = this.length + if (length == 0) return null + + val start: Int + val isNegative: Boolean + val limit: Int + + val firstChar = this[0] + if (firstChar < '0') { // Possible leading sign + if (length == 1) return null // non-digit (possible sign) only, no digits after + + start = 1 + + if (firstChar == '-') { + isNegative = true + limit = Int.MIN_VALUE + } else if (firstChar == '+') { + isNegative = false + limit = -Int.MAX_VALUE + } else + return null + } else { + start = 0 + isNegative = false + limit = -Int.MAX_VALUE + } + + + val limitBeforeMul = limit / radix + var result = 0 + for (i in start..(length - 1)) { + val digit = digitOf(this[i], radix) + + if (digit < 0) return null + if (result < limitBeforeMul) return null + + result *= radix + + if (result < limit + digit) return null + + result -= digit + } + + return if (isNegative) result else -result +} + +/** + * Parses the string as a [Long] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toLongOrNull(): Long? = toLongOrNull(radix = 10) + +/** + * Parses the string as a [Long] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toLongOrNull(radix: Int): Long? { + checkRadix(radix) + + val length = this.length + if (length == 0) return null + + val start: Int + val isNegative: Boolean + val limit: Long + + val firstChar = this[0] + if (firstChar < '0') { // Possible leading sign + if (length == 1) return null // non-digit (possible sign) only, no digits after + + start = 1 + + if (firstChar == '-') { + isNegative = true + limit = Long.MIN_VALUE + } else if (firstChar == '+') { + isNegative = false + limit = -Long.MAX_VALUE + } else + return null + } else { + start = 0 + isNegative = false + limit = -Long.MAX_VALUE + } + + + val limitBeforeMul = limit / radix + var result = 0L + for (i in start..(length - 1)) { + val digit = digitOf(this[i], radix) + + if (digit < 0) return null + if (result < limitBeforeMul) return null + + result *= radix + + if (result < limit + digit) return null + + result -= digit + } + + return if (isNegative) result else -result +} + +/** + * Parses the string as a [Float] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toFloatOrNull(): Float? = TODO() + +/** + * Parses the string as a [Double] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.1") +public fun String.toDoubleOrNull(): Double? = TODO() diff --git a/samples/tetris/README.md b/samples/tetris/README.md index 0e6c3674299..4dbc7d9116b 100644 --- a/samples/tetris/README.md +++ b/samples/tetris/README.md @@ -2,10 +2,16 @@ Start with building compiler by using `dist` and `cross_dist` for cross-targets. To build Tetris application for your host platform use ./build note that SDL2 must be installed on the host. -For cross-compilation to iOS use -TARGET=iphone ./build +For cross-compilation to iOS (on Mac host) use + TARGET=iphone ./build +For cross-compilation to Raspberry Pi (on Linux host) use + TARGET=raspberrypi ./build During build process compilation script creates interoperability bindings to SDL2, using SDL C headers, and then compiles an application with the produced bindings. To deploy executable to iPhone device take Info.plist, then use XCode and your own private signing identity. + +To run on Raspberry Pi one need to install SDL package with `apt-get install libsdl2-2.0.0` on the Pi. +Also GLES2 renderer is recommended (use `SDL_RENDER_DRIVER=opengles2 ./tetris.kexe`). +