Rewrote application with benchmarks using MPP plugin and added collec… (#2471)

Rewrote application with benchmarks using MPP plugin and added collecting information in json format.
This commit is contained in:
LepilkinaElena
2018-12-21 14:35:23 +03:00
committed by GitHub
parent 8d00a1ebcf
commit 828b2702bf
25 changed files with 1836 additions and 249 deletions
@@ -0,0 +1,325 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.report
import org.jetbrains.report.json.*
interface JsonSerializable {
fun toJson(): String
// Convert iterable objects arrays, lists to json.
fun <T> arrayToJson(data: Iterable<T>): String {
return data.joinToString(prefix = "[", postfix = "]") {
if (it is JsonSerializable) it.toJson() else it.toString()
}
}
}
// Entity can be created from json description.
interface ConvertedFromJson {
fun getRequiredField(data: JsonObject, fieldName: String): JsonElement {
return data.getOrNull(fieldName) ?: error("Field '$fieldName' doesn't exist in '$data'. Please, check origin files.")
}
fun getOptionalField(data: JsonObject, fieldName: String): JsonElement? {
return data.getOrNull(fieldName)
}
// Parse json array to list.
// Takes function to convert elements in array to expected type.
fun <T> arrayToList(array: JsonArray, convert: JsonArray.(Int) -> T?): List<T> {
var results = mutableListOf<T>()
var index = 0
var current: T? = array.convert(index)
while (current != null) {
results.add(current)
index++
current = array.convert(index)
}
return results
}
// Methods for conversion to expected type with checks of possibility of such conversions.
fun elementToDouble(element: JsonElement, name: String): Double =
if (element is JsonPrimitive)
element.double
else
error("Field '$name' in '$element' is expected to be a double number. Please, check origin files.")
fun elementToInt(element: JsonElement, name: String): Int =
if (element is JsonPrimitive)
element.int
else
error("Field '$name' in '$element' is expected to be an integer number. Please, check origin files.")
fun elementToString(element: JsonElement, name:String): String =
if (element is JsonLiteral)
element.unquoted()
else
error("Field '$name' in '$element' is expected to be a string. Please, check origin files.")
}
interface EntityFromJsonFactory<T>: ConvertedFromJson {
fun create(data: JsonElement): T
}
// Class for benchcmarks report with all information of run.
data class BenchmarksReport(val env: Environment, val benchmarks: List<BenchmarkResult>, val compiler: Compiler):
JsonSerializable {
companion object: EntityFromJsonFactory<BenchmarksReport> {
override fun create(data: JsonElement): BenchmarksReport {
if (data is JsonObject) {
val env = Environment.create(getRequiredField(data, "env"))
val benchmarksObj = getRequiredField(data, "benchmarks")
val compiler = Compiler.create(getRequiredField(data, "kotlin"))
val benchmarks = parseBenchmarksArray(benchmarksObj)
return BenchmarksReport(env, benchmarks, compiler)
} else {
error("Top level entity is expected to be an object. Please, check origin files.")
}
}
// Parse array with benchmarks to list
fun parseBenchmarksArray(data: JsonElement): List<BenchmarkResult> {
if (data is JsonArray) {
return arrayToList(data.jsonArray, { index ->
if (this.getObjectOrNull(index) != null)
BenchmarkResult.create(this.getObjectOrNull(index) as JsonObject)
else null
})
} else {
error("Benchmarks field is expected to be an array. Please, check origin files.")
}
}
}
override fun toJson(): String {
return """
{
"env": ${env.toJson()},
"kotlin": ${compiler.toJson()},
"benchmarks": ${arrayToJson(benchmarks)}
}
"""
}
}
// Class for kotlin compiler
data class Compiler(val backend: Backend, val kotlinVersion: String): JsonSerializable {
enum class BackendType(val type: String) {
JVM("jvm"),
NATIVE("native")
}
companion object: EntityFromJsonFactory<Compiler> {
override fun create(data: JsonElement): Compiler {
if (data is JsonObject) {
val backend = Backend.create(getRequiredField(data, "backend"))
val kotlinVersion = elementToString(getRequiredField(data, "kotlinVersion"), "kotlinVersion")
return Compiler(backend, kotlinVersion)
} else {
error("Kotlin entity is expected to be an object. Please, check origin files.")
}
}
fun backendTypeFromString(s: String): BackendType? = BackendType.values().find { it.type == s }
}
// Class for compiler backend
data class Backend(val type: BackendType, val version: String, val flags: List<String>): JsonSerializable {
companion object: EntityFromJsonFactory<Backend> {
override fun create(data: JsonElement): Backend {
if (data is JsonObject) {
val typeElement = getRequiredField(data, "type")
if (typeElement is JsonLiteral) {
val type = backendTypeFromString(typeElement.unquoted()) ?: error("Backend type should be 'jvm' or 'native'")
val version = elementToString(getRequiredField(data, "version"), "version")
val flagsArray = getOptionalField(data, "flags")
var flags: List<String> = emptyList()
if (flagsArray != null && flagsArray is JsonArray) {
flags = arrayToList(flagsArray.jsonArray, { index ->
this.getPrimitiveOrNull(index)?.toString()
})
}
return Backend(type, version, flags)
} else {
error("Backend type should be string literal.")
}
} else {
error("Backend entity is expected to be an object. Please, check origin files.")
}
}
}
override fun toJson(): String {
val result = """
{
"type": "${type.type}",
"version": "${version}""""
// Don't print flags field if there is no one.
if (flags.isEmpty()) {
return """$result
}"""
}
else {
return """
$result,
"flags": ${arrayToJson(flags)}
}
"""
}
}
}
override fun toJson(): String {
return """
{
"backend": ${backend.toJson()},
"kotlinVersion": "${kotlinVersion}"
}
"""
}
}
// Class for description of environment of benchmarks run
data class Environment(val machine: Machine, val jdk: JDKInstance): JsonSerializable {
companion object: EntityFromJsonFactory<Environment> {
override fun create(data: JsonElement): Environment {
if (data is JsonObject) {
val machine = Machine.create(getRequiredField(data, "machine"))
val jdk = JDKInstance.create(getRequiredField(data, "jdk"))
return Environment(machine, jdk)
} else {
error("Environment entity is expected to be an object. Please, check origin files.")
}
}
}
// Class for description of machine used for benchmarks run.
data class Machine(val cpu: String, val os: String): JsonSerializable {
companion object: EntityFromJsonFactory<Machine> {
override fun create(data: JsonElement): Machine {
if (data is JsonObject) {
val cpu = elementToString(getRequiredField(data, "cpu"), "cpu")
val os = elementToString(getRequiredField(data, "os"), "os")
return Machine(cpu, os)
} else {
error("Machine entity is expected to be an object. Please, check origin files.")
}
}
}
override fun toJson(): String {
return """
{
"cpu": "$cpu",
"os": "$os"
}
"""
}
}
// Class for description of jdk used for benchmarks run.
data class JDKInstance(val version: String, val vendor: String): JsonSerializable {
companion object: EntityFromJsonFactory<JDKInstance> {
override fun create(data: JsonElement): JDKInstance {
if (data is JsonObject) {
val version = elementToString(getRequiredField(data, "version"), "version")
val vendor = elementToString(getRequiredField(data, "vendor"), "vendor")
return JDKInstance(version, vendor)
} else {
error("JDK entity is expected to be an object. Please, check origin files.")
}
}
}
override fun toJson(): String {
return """
{
"version": "$version",
"vendor": "$vendor"
}
"""
}
}
override fun toJson(): String {
return """
{
"machine": ${machine.toJson()},
"jdk": ${jdk.toJson()}
}
"""
}
}
class BenchmarkResult(val name: String, val status: Status,
val score: Double, val runtimeInUs: Double,
val repeat: Int, val warmup: Int): JsonSerializable {
companion object: EntityFromJsonFactory<BenchmarkResult> {
override fun create(data: JsonElement): BenchmarkResult {
if (data is JsonObject) {
val name = elementToString(getRequiredField(data, "name"), "name")
val statusElement = getRequiredField(data, "status")
if (statusElement is JsonLiteral) {
val status = statusFromString(statusElement.unquoted())
?: error("Status should be PASSED or FAILED")
val score = elementToDouble(getRequiredField(data, "score"), "score")
val runtimeInUs = elementToDouble(getRequiredField(data, "runtimeInUs"), "runtimeInUs")
val repeat = elementToInt(getRequiredField(data, "repeat"), "repeat")
val warmup = elementToInt(getRequiredField(data, "warmup"), "warmup")
return BenchmarkResult(name, status, score, runtimeInUs, repeat, warmup)
} else {
error("Status should be string literal.")
}
} else {
error("Benchmark entity is expected to be an object. Please, check origin files.")
}
}
fun statusFromString(s: String): Status? = Status.values().find { it.value == s }
}
enum class Status(val value: String) {
PASSED("PASSED"),
FAILED("FAILED")
}
override fun toJson(): String {
return """
{
"name": "$name",
"status": "${status.value}",
"score": ${score},
"runtimeInUs": ${runtimeInUs},
"repeat": ${repeat},
"warmup": ${warmup}
}
"""
}
}
@@ -0,0 +1,329 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.report.json
/**
* Class representing single JSON element.
* Can be [JsonPrimitive], [JsonArray] or [JsonObject].
*
* [JsonElement.toString] properly prints JSON tree as valid JSON, taking into
* account quoted values and primitives
*/
sealed class JsonElement {
/**
* Convenience method to get current element as [JsonPrimitive]
* @throws JsonElementTypeMismatchException is current element is not a [JsonPrimitive]
*/
open val primitive: JsonPrimitive
get() = error("JsonLiteral")
/**
* Convenience method to get current element as [JsonObject]
* @throws JsonElementTypeMismatchException is current element is not a [JsonObject]
*/
open val jsonObject: JsonObject
get() = error("JsonObject")
/**
* Convenience method to get current element as [JsonArray]
* @throws JsonElementTypeMismatchException is current element is not a [JsonArray]
*/
open val jsonArray: JsonArray
get() = error("JsonArray")
/**
* Convenience method to get current element as [JsonNull]
* @throws JsonElementTypeMismatchException is current element is not a [JsonNull]
*/
open val jsonNull: JsonNull
get() = error("JsonPrimitive")
/**
* Checks whether current element is [JsonNull]
*/
val isNull: Boolean
get() = this === JsonNull
private fun error(element: String): Nothing =
throw JsonElementTypeMismatchException(this::class.toString(), element)
}
/**
* Class representing JSON primitive value. Can be either [JsonLiteral] or [JsonNull].
*/
sealed class JsonPrimitive : JsonElement() {
/**
* Content of given element without quotes. For [JsonNull] this methods returns `"null"`
*/
abstract val content: String
/**
* Content of the given element without quotes or `null` if current element is [JsonNull]
*/
abstract val contentOrNull: String?
@Suppress("LeakingThis")
final override val primitive: JsonPrimitive = this
/**
* Returns content of current element as int
* @throws NumberFormatException if current element is not a valid representation of number
*/
val int: Int get() = content.toInt()
/**
* Returns content of current element as int or `null` if current element is not a valid representation of number
**/
val intOrNull: Int? get() = content.toIntOrNull()
/**
* Returns content of current element as long
* @throws NumberFormatException if current element is not a valid representation of number
*/
val long: Long get() = content.toLong()
/**
* Returns content of current element as long or `null` if current element is not a valid representation of number
*/
val longOrNull: Long? get() = content.toLongOrNull()
/**
* Returns content of current element as double
* @throws NumberFormatException if current element is not a valid representation of number
*/
val double: Double get() = content.toDouble()
/**
* Returns content of current element as double or `null` if current element is not a valid representation of number
*/
val doubleOrNull: Double? get() = content.toDoubleOrNull()
/**
* Returns content of current element as float
* @throws NumberFormatException if current element is not a valid representation of number
*/
val float: Float get() = content.toFloat()
/**
* Returns content of current element as float or `null` if current element is not a valid representation of number
*/
val floatOrNull: Float? get() = content.toFloatOrNull()
/**
* Returns content of current element as boolean
* @throws IllegalStateException if current element doesn't represent boolean
*/
val boolean: Boolean get() = content.toBooleanStrict()
/**
* Returns content of current element as boolean or `null` if current element is not a valid representation of boolean
*/
val booleanOrNull: Boolean? get() = content.toBooleanStrictOrNull()
override fun toString() = content
}
/**
* Class representing JSON literals: numbers, booleans and string.
* Strings are always quoted.
*/
data class JsonLiteral internal constructor(
private val body: Any,
private val isString: Boolean
) : JsonPrimitive() {
override val content = body.toString()
override val contentOrNull: String = content
/**
* Creates number literal
*/
constructor(number: Number) : this(number, false)
/**
* Creates boolean literal
*/
constructor(boolean: Boolean) : this(boolean, false)
/**
* Creates quoted string literal
*/
constructor(string: String) : this(string, true)
override fun toString() =
if (isString) buildString { printQuoted(content) }
else content
fun unquoted() = content
}
/**
* Class representing JSON `null` value
*/
object JsonNull : JsonPrimitive() {
override val jsonNull: JsonNull = this
override val content: String = "null"
override val contentOrNull: String? = null
}
/**
* Class representing JSON object, consisting of name-value pairs, where value is arbitrary [JsonElement]
*/
data class JsonObject(val content: Map<String, JsonElement>) : JsonElement(), Map<String, JsonElement> by content {
override val jsonObject: JsonObject = this
/**
* Returns [JsonElement] associated with given [key]
* @throws NoSuchElementException if element is not present
*/
override fun get(key: String): JsonElement = content[key] ?: throw NoSuchElementException("Element $key is missing")
/**
* Returns [JsonElement] associated with given [key] or `null` if element is not present
*/
fun getOrNull(key: String): JsonElement? = content[key]
/**
* Returns [JsonPrimitive] associated with given [key]
*
* @throws NoSuchElementException if element is not present
* @throws JsonElementTypeMismatchException if element is present, but has invalid type
*/
fun getPrimitive(key: String): JsonPrimitive = get(key) as? JsonPrimitive
?: unexpectedJson(key, "JsonPrimitive")
/**
* Returns [JsonObject] associated with given [key]
*
* @throws NoSuchElementException if element is not present
* @throws JsonElementTypeMismatchException if element is present, but has invalid type
*/
fun getObject(key: String): JsonObject = get(key) as? JsonObject
?: unexpectedJson(key, "JsonObject")
/**
* Returns [JsonArray] associated with given [key]
*
* @throws NoSuchElementException if element is not present
* @throws JsonElementTypeMismatchException if element is present, but has invalid type
*/
fun getArray(key: String): JsonArray = get(key) as? JsonArray
?: unexpectedJson(key, "JsonArray")
/**
* Returns [JsonPrimitive] associated with given [key] or `null` if element
* is not present or has different type
*/
fun getPrimitiveOrNull(key: String): JsonPrimitive? = content[key] as? JsonPrimitive
/**
* Returns [JsonObject] associated with given [key] or `null` if element
* is not present or has different type
*/
fun getObjectOrNull(key: String): JsonObject? = content[key] as? JsonObject
/**
* Returns [JsonArray] associated with given [key] or `null` if element
* is not present or has different type
*/
fun getArrayOrNull(key: String): JsonArray? = content[key] as? JsonArray
/**
* Returns [J] associated with given [key]
*
* @throws NoSuchElementException if element is not present
* @throws JsonElementTypeMismatchException if element is present, but has invalid type
*/
inline fun <reified J : JsonElement> getAs(key: String): J = get(key) as? J
?: unexpectedJson(key, J::class.toString())
/**
* Returns [J] associated with given [key] or `null` if element
* is not present or has different type
*/
inline fun <reified J : JsonElement> lookup(key: String): J? = content[key] as? J
override fun toString(): String {
return content.entries.joinToString(
prefix = "{",
postfix = "}",
transform = {(k, v) -> """"$k": $v"""}
)
}
}
data class JsonArray(val content: List<JsonElement>) : JsonElement(), List<JsonElement> by content {
override val jsonArray: JsonArray = this
/**
* Returns [index]-th element of an array as [JsonPrimitive]
* @throws JsonElementTypeMismatchException if element has invalid type
*/
fun getPrimitive(index: Int) = content[index] as? JsonPrimitive
?: unexpectedJson("at $index", "JsonPrimitive")
/**
* Returns [index]-th element of an array as [JsonObject]
* @throws JsonElementTypeMismatchException if element has invalid type
*/
fun getObject(index: Int) = content[index] as? JsonObject
?: unexpectedJson("at $index", "JsonObject")
/**
* Returns [index]-th element of an array as [JsonArray]
* @throws JsonElementTypeMismatchException if element has invalid type
*/
fun getArray(index: Int) = content[index] as? JsonArray
?: unexpectedJson("at $index", "JsonArray")
/**
* Returns [index]-th element of an array as [JsonPrimitive] or `null` if element is missing or has different type
*/
fun getPrimitiveOrNull(index: Int) = content.getOrNull(index) as? JsonPrimitive
/**
* Returns [index]-th element of an array as [JsonObject] or `null` if element is missing or has different type
*/
fun getObjectOrNull(index: Int) = content.getOrNull(index) as? JsonObject
/**
* Returns [index]-th element of an array as [JsonArray] or `null` if element is missing or has different type
*/
fun getArrayOrNull(index: Int) = content.getOrNull(index) as? JsonArray
/**
* Returns [index]-th element of an array as [J]
* @throws JsonElementTypeMismatchException if element has invalid type
*/
inline fun <reified J : JsonElement> getAs(index: Int): J = content[index] as? J
?: unexpectedJson("at $index", J::class.toString())
/**
* Returns [index]-th element of an array as [J] or `null` if element is missing or has different type
*/
inline fun <reified J : JsonElement> getAsOrNull(index: Int): J? = content.getOrNull(index) as? J
override fun toString() = content.joinToString(prefix = "[", postfix = "]")
}
@PublishedApi
internal fun unexpectedJson(key: String, expected: String): Nothing =
throw JsonElementTypeMismatchException(key, expected)
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.report.json
class JsonInvalidValueInStrictModeException(value: Any, valueDescription: String) : Exception(
"$value is not a valid $valueDescription as per JSON spec.\n" +
"You can disable strict mode to serialize such values"
) {
constructor(floatValue: Float) : this(floatValue, "float")
constructor(doubleValue: Double) : this(doubleValue, "double")
}
class JsonUnknownKeyException(key: String) : Exception(
"Strict JSON encountered unknown key: $key\n" +
"You can disable strict mode to skip unknown keys"
)
class JsonParsingException(position: Int, message: String) : Exception("Invalid JSON at $position: $message")
class JsonElementTypeMismatchException(key: String, expected: String) : Exception("Element $key is not a $expected")
@@ -0,0 +1,289 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.report.json
import org.jetbrains.report.json.EscapeCharMappings.ESC2C
// special strings
internal const val NULL = "null"
// special chars
internal const val COMMA = ','
internal const val COLON = ':'
internal const val BEGIN_OBJ = '{'
internal const val END_OBJ = '}'
internal const val BEGIN_LIST = '['
internal const val END_LIST = ']'
internal const val STRING = '"'
internal const val STRING_ESC = '\\'
internal const val INVALID = 0.toChar()
internal const val UNICODE_ESC = 'u'
// token classes
internal const val TC_OTHER: Byte = 0
internal const val TC_STRING: Byte = 1
internal const val TC_STRING_ESC: Byte = 2
internal const val TC_WS: Byte = 3
internal const val TC_COMMA: Byte = 4
internal const val TC_COLON: Byte = 5
internal const val TC_BEGIN_OBJ: Byte = 6
internal const val TC_END_OBJ: Byte = 7
internal const val TC_BEGIN_LIST: Byte = 8
internal const val TC_END_LIST: Byte = 9
internal const val TC_NULL: Byte = 10
internal const val TC_INVALID: Byte = 11
internal const val TC_EOF: Byte = 12
// mapping from chars to token classes
private const val CTC_MAX = 0x7e
// mapping from escape chars real chars
private const val C2ESC_MAX = 0x5d
private const val ESC2C_MAX = 0x75
internal val C2TC = ByteArray(CTC_MAX).apply {
for (i in 0..0x20)
initC2TC(i, TC_INVALID)
initC2TC(0x09, TC_WS)
initC2TC(0x0a, TC_WS)
initC2TC(0x0d, TC_WS)
initC2TC(0x20, TC_WS)
initC2TC(COMMA, TC_COMMA)
initC2TC(COLON, TC_COLON)
initC2TC(BEGIN_OBJ, TC_BEGIN_OBJ)
initC2TC(END_OBJ, TC_END_OBJ)
initC2TC(BEGIN_LIST, TC_BEGIN_LIST)
initC2TC(END_LIST, TC_END_LIST)
initC2TC(STRING, TC_STRING)
initC2TC(STRING_ESC, TC_STRING_ESC)
}
// object instead of @SharedImmutable because there is mutual initialization in [initC2ESC]
internal object EscapeCharMappings {
internal val ESC2C = CharArray(ESC2C_MAX)
internal val C2ESC = CharArray(C2ESC_MAX).apply {
for (i in 0x00..0x1f)
initC2ESC(i, UNICODE_ESC)
initC2ESC(0x08, 'b')
initC2ESC(0x09, 't')
initC2ESC(0x0a, 'n')
initC2ESC(0x0c, 'f')
initC2ESC(0x0d, 'r')
initC2ESC('/', '/')
initC2ESC(STRING, STRING)
initC2ESC(STRING_ESC, STRING_ESC)
}
private fun CharArray.initC2ESC(c: Int, esc: Char) {
this[c] = esc
if (esc != UNICODE_ESC) ESC2C[esc.toInt()] = c.toChar()
}
private fun CharArray.initC2ESC(c: Char, esc: Char) = initC2ESC(c.toInt(), esc)
}
private fun ByteArray.initC2TC(c: Int, cl: Byte) {
this[c] = cl
}
private fun ByteArray.initC2TC(c: Char, cl: Byte) {
initC2TC(c.toInt(), cl)
}
internal fun charToTokenClass(c: Char) = if (c.toInt() < CTC_MAX) C2TC[c.toInt()] else TC_OTHER
internal fun escapeToChar(c: Int): Char = if (c < ESC2C_MAX) ESC2C[c] else INVALID
// JSON low level parser
internal class Parser(val source: String) {
var curPos: Int = 0 // position in source
private set
// updated by nextToken
var tokenPos: Int = 0
private set
var tc: Byte = TC_EOF
private set
// update by nextString/nextLiteral
private var offset = -1 // when offset >= 0 string is in source, otherwise in buf
private var length = 0 // length of string
private var buf = CharArray(16) // only used for strings with escapes
init {
nextToken()
}
internal inline fun requireTc(expected: Byte, lazyErrorMsg: () -> String) {
if (tc != expected)
fail(tokenPos, lazyErrorMsg())
}
val canBeginValue: Boolean
get() = when (tc) {
TC_BEGIN_LIST, TC_BEGIN_OBJ, TC_OTHER, TC_STRING, TC_NULL -> true
else -> false
}
fun takeStr(): String {
if (tc != TC_OTHER && tc != TC_STRING) fail(tokenPos, "Expected string or non-null literal")
val prevStr = if (offset < 0)
String(buf, 0, length) else
source.substring(offset, offset + length)
nextToken()
return prevStr
}
private fun append(ch: Char) {
if (length >= buf.size) buf = buf.copyOf(2 * buf.size)
buf[length++] = ch
}
// initializes buf usage upon the first encountered escaped char
private fun appendRange(source: String, fromIndex: Int, toIndex: Int) {
val addLen = toIndex - fromIndex
val oldLen = length
val newLen = oldLen + addLen
if (newLen > buf.size) buf = buf.copyOf(newLen.coerceAtLeast(2 * buf.size))
for (i in 0 until addLen) buf[oldLen + i] = source[fromIndex + i]
length += addLen
}
fun nextToken() {
val source = source
var curPos = curPos
val maxLen = source.length
while (true) {
if (curPos >= maxLen) {
tokenPos = curPos
tc = TC_EOF
return
}
val ch = source[curPos]
val tc = charToTokenClass(ch)
when (tc) {
TC_WS -> curPos++ // skip whitespace
TC_OTHER -> {
nextLiteral(source, curPos)
return
}
TC_STRING -> {
nextString(source, curPos)
return
}
else -> {
this.tokenPos = curPos
this.tc = tc
this.curPos = curPos + 1
return
}
}
}
}
private fun nextLiteral(source: String, startPos: Int) {
tokenPos = startPos
offset = startPos
var curPos = startPos
val maxLen = source.length
while (true) {
curPos++
if (curPos >= maxLen || charToTokenClass(source[curPos]) != TC_OTHER) break
}
this.curPos = curPos
length = curPos - offset
tc = if (rangeEquals(source, offset, length, NULL)) TC_NULL else TC_OTHER
}
private fun nextString(source: String, startPos: Int) {
tokenPos = startPos
length = 0 // in buffer
var curPos = startPos + 1
var lastPos = curPos
val maxLen = source.length
parse@ while (true) {
if (curPos >= maxLen) fail(curPos, "Unexpected end in string")
if (source[curPos] == STRING) {
break@parse
} else if (source[curPos] == STRING_ESC) {
appendRange(source, lastPos, curPos)
val newPos = appendEsc(source, curPos + 1)
curPos = newPos
lastPos = newPos
} else {
curPos++
}
}
if (lastPos == startPos + 1) {
// there was no escaped chars
this.offset = lastPos
this.length = curPos - lastPos
} else {
// some escaped chars were there
appendRange(source, lastPos, curPos)
this.offset = -1
}
this.curPos = curPos + 1
tc = TC_STRING
}
private fun appendEsc(source: String, startPos: Int): Int {
var curPos = startPos
require(curPos < source.length, curPos) { "Unexpected end after escape char" }
val curChar = source[curPos++]
if (curChar == UNICODE_ESC) {
curPos = appendHex(source, curPos)
} else {
val c = escapeToChar(curChar.toInt())
require(c != INVALID, curPos) { "Invalid escaped char '$curChar'" }
append(c)
}
return curPos
}
private fun appendHex(source: String, startPos: Int): Int {
var curPos = startPos
append(
((fromHexChar(source, curPos++) shl 12) +
(fromHexChar(source, curPos++) shl 8) +
(fromHexChar(source, curPos++) shl 4) +
fromHexChar(source, curPos++)).toChar()
)
return curPos
}
fun skipElement() {
if (tc != TC_BEGIN_OBJ && tc != TC_BEGIN_LIST) {
nextToken()
return
}
val tokenStack = mutableListOf<Byte>()
do {
when (tc) {
TC_BEGIN_LIST, TC_BEGIN_OBJ -> tokenStack.add(tc)
TC_END_LIST -> {
if (tokenStack.last() != TC_BEGIN_LIST) throw JsonParsingException(curPos, "found ] instead of }")
tokenStack.removeAt(tokenStack.size - 1)
}
TC_END_OBJ -> {
if (tokenStack.last() != TC_BEGIN_OBJ) throw JsonParsingException(curPos, "found } instead of ]")
tokenStack.removeAt(tokenStack.size - 1)
}
}
nextToken()
} while (tokenStack.isNotEmpty())
}
}
// Utility functions
private fun fromHexChar(source: String, curPos: Int): Int {
require(curPos < source.length, curPos) { "Unexpected end in unicode escape" }
val curChar = source[curPos]
return when (curChar) {
in '0'..'9' -> curChar.toInt() - '0'.toInt()
in 'a'..'f' -> curChar.toInt() - 'a'.toInt() + 10
in 'A'..'F' -> curChar.toInt() - 'A'.toInt() + 10
else -> fail(curPos, "Invalid toHexChar char '$curChar' in unicode escape")
}
}
private fun rangeEquals(source: String, start: Int, length: Int, str: String): Boolean {
val n = str.length
if (length != n) return false
for (i in 0 until n) if (source[start + i] != str[i]) return false
return true
}
internal inline fun require(condition: Boolean, pos: Int, msg: () -> String) {
if (!condition)
fail(pos, msg())
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun fail(pos: Int, msg: String): Nothing {
throw JsonParsingException(pos, msg)
}
@@ -0,0 +1,85 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// A bit changed part of kotlinx.serialization plugin
package org.jetbrains.report.json
class JsonTreeParser internal constructor(private val p: Parser) {
companion object {
fun parse(input: String): JsonElement = JsonTreeParser(input).readFully()
}
constructor(input: String) : this(Parser(input))
private fun readObject(): JsonElement {
p.requireTc(TC_BEGIN_OBJ) { "Expected start of object" }
p.nextToken()
val result: MutableMap<String, JsonElement> = hashMapOf()
while (true) {
if (p.tc == TC_COMMA) p.nextToken()
if (!p.canBeginValue) break
val key = p.takeStr()
p.requireTc(TC_COLON) { "Expected ':'" }
p.nextToken()
val elem = read()
result[key] = elem
}
p.requireTc(TC_END_OBJ) { "Expected end of object" }
p.nextToken()
return JsonObject(result)
}
private fun readValue(isString: Boolean): JsonElement {
val str = p.takeStr()
return JsonLiteral(str, isString)
}
private fun readArray(): JsonElement {
p.requireTc(TC_BEGIN_LIST) { "Expected start of array" }
p.nextToken()
val result: MutableList<JsonElement> = arrayListOf()
while (true) {
if (p.tc == TC_COMMA) p.nextToken()
if (!p.canBeginValue) break
val elem = read()
result.add(elem)
}
p.requireTc(TC_END_LIST) { "Expected end of array" }
p.nextToken()
return JsonArray(result)
}
fun read(): JsonElement {
if (!p.canBeginValue) fail(p.curPos, "Can't begin reading value from here")
val tc = p.tc
return when (tc) {
TC_NULL -> JsonNull.also { p.nextToken() }
TC_STRING -> readValue(isString = true)
TC_OTHER -> readValue(isString = false)
TC_BEGIN_OBJ -> readObject()
TC_BEGIN_LIST -> readArray()
else -> fail(p.curPos, "Can't begin reading element")
}
}
fun readFully(): JsonElement {
val r = read()
p.requireTc(TC_EOF) { "Input wasn't consumed fully" }
return r
}
}
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.report.json
private fun toHexChar(i: Int) : Char {
val d = i and 0xf
return if (d < 10) (d + '0'.toInt()).toChar()
else (d - 10 + 'a'.toInt()).toChar()
}
private val ESCAPE_CHARS: Array<String?> = arrayOfNulls<String>(128).apply {
for (c in 0..0x1f) {
val c1 = toHexChar(c shr 12)
val c2 = toHexChar(c shr 8)
val c3 = toHexChar(c shr 4)
val c4 = toHexChar(c)
this[c] = "\\u$c1$c2$c3$c4"
}
this['"'.toInt()] = "\\\""
this['\\'.toInt()] = "\\\\"
this['\t'.toInt()] = "\\t"
this['\b'.toInt()] = "\\b"
this['\n'.toInt()] = "\\n"
this['\r'.toInt()] = "\\r"
this[0x0c] = "\\f"
}
internal fun StringBuilder.printQuoted(value: String) {
append(STRING)
var lastPos = 0
val length = value.length
for (i in 0 until length) {
val c = value[i].toInt()
// Do not replace this constant with C2ESC_MAX (which is smaller than ESCAPE_CHARS size),
// otherwise JIT won't eliminate range check and won't vectorize this loop
if (c >= ESCAPE_CHARS.size) continue // no need to escape
val esc = ESCAPE_CHARS[c] ?: continue
append(value, lastPos, i) // flush prev
append(esc)
lastPos = i + 1
}
append(value, lastPos, length)
append(STRING)
}
/**
* Returns `true` if the contents of this string is equal to the word "true", ignoring case, `false` if content equals "false",
* and throws [IllegalStateException] otherwise.
*/
fun String.toBooleanStrict(): Boolean = toBooleanStrictOrNull() ?: throw IllegalStateException("$this does not represent a Boolean")
/**
* Returns `true` if the contents of this string is equal to the word "true", ignoring case, `false` if content equals "false",
* and returns `null` otherwise.
*/
fun String.toBooleanStrictOrNull(): Boolean? = when {
this.equals("true", ignoreCase = true) -> true
this.equals("false", ignoreCase = true) -> false
else -> null
}