Support data parsing operations (#380)

This commit is contained in:
Nikolay Igotti
2017-03-23 12:54:24 +03:00
committed by GitHub
parent 8afa12684e
commit b5c60e3082
13 changed files with 534 additions and 5 deletions
+4
View File
@@ -0,0 +1,4 @@
Q: How do I run my program?
A: Define top level function `fun main(args: Array<String>)`, please ensure it's not
in a package.
+3 -1
View File
@@ -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.
+13
View File
@@ -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"
@@ -0,0 +1,15 @@
fun main(args: Array<String>) {
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())
}
+3 -1
View File
@@ -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"
+92
View File
@@ -47,6 +47,58 @@ OBJ_GETTER(utf8ToUtf16, Raw<const char> rawString) {
RETURN_OBJ(result->obj());
}
template <typename T> T parseInt(KString value, KInt radix) {
Raw<const KChar> 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<const KChar> 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<const KChar> 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<const KChar> 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<KByte>(value, radix);
}
KShort Kotlin_String_parseShort(KString value, KInt radix) {
return parseInt<KShort>(value, radix);
}
KInt Kotlin_String_parseInt(KString value, KInt radix) {
return parseInt<KInt>(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"
+38
View File
@@ -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<long long>(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];
@@ -20,6 +20,11 @@ internal fun ThrowArithmeticException() : Nothing {
throw ArithmeticException()
}
@ExportForCppRuntime
internal fun ThrowNumberFormatException() : Nothing {
throw NumberFormatException()
}
fun ThrowNoWhenBranchMatchedException(): Nothing {
throw NoWhenBranchMatchedException()
}
+10
View File
@@ -89,6 +89,16 @@ public final class Char : Comparable<Char> {
* 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.
@@ -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) {}
}
+15 -1
View File
@@ -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
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
}
@@ -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()
+8 -2
View File
@@ -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`).