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
+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()