JS: optimize JSON parser

This commit is contained in:
Anton Bannykh
2019-04-12 13:00:19 +03:00
parent 675f930566
commit 56915d1c07
8 changed files with 52 additions and 35 deletions
@@ -205,7 +205,7 @@ private fun kjsmToString(kjsmFile: File): String {
} }
private fun sourceMapFileToString(sourceMapFile: File, generatedJsFile: File): String { private fun sourceMapFileToString(sourceMapFile: File, generatedJsFile: File): String {
val sourceMapParseResult = SourceMapParser.parse(StringReader(sourceMapFile.readText())) val sourceMapParseResult = SourceMapParser.parse(sourceMapFile.readText())
return when (sourceMapParseResult) { return when (sourceMapParseResult) {
is SourceMapSuccess -> { is SourceMapSuccess -> {
val bytesOut = ByteArrayOutputStream() val bytesOut = ByteArrayOutputStream()
@@ -122,7 +122,7 @@ class K2JSDce : CLITool<K2JSDceArguments>() {
private fun mapSourcePaths(inputFile: File, targetFile: File): Boolean { private fun mapSourcePaths(inputFile: File, targetFile: File): Boolean {
val json = try { val json = try {
InputStreamReader(FileInputStream(inputFile), "UTF-8").use { parseJson(it) } parseJson(inputFile)
} catch (e: JsonSyntaxException) { } catch (e: JsonSyntaxException) {
return false return false
} }
@@ -52,7 +52,7 @@ private fun mergeStdlibParts(outputFile: File, wrapperFile: File, baseDir: File,
val sourceMapFile = File(file.parent, file.name + ".map") val sourceMapFile = File(file.parent, file.name + ".map")
if (sourceMapFile.exists()) { if (sourceMapFile.exists()) {
val sourceMapParse = sourceMapFile.reader().use { SourceMapParser.parse(it) } val sourceMapParse = SourceMapParser.parse(sourceMapFile)
when (sourceMapParse) { when (sourceMapParse) {
is SourceMapError -> { is SourceMapError -> {
System.err.println("Error parsing source map file $sourceMapFile: ${sourceMapParse.message}") System.err.println("Error parsing source map file $sourceMapFile: ${sourceMapParse.message}")
@@ -82,7 +82,7 @@ private fun mergeStdlibParts(outputFile: File, wrapperFile: File, baseDir: File,
outputFile.writeText(programText + "\n//# sourceMappingURL=${sourceMapFile.name}\n") outputFile.writeText(programText + "\n//# sourceMappingURL=${sourceMapFile.name}\n")
val sourceMapJson = StringReader(sourceMapContent).use { parseJson(it) } val sourceMapJson = parseJson(sourceMapContent)
val sources = (sourceMapJson as JsonObject).properties["sources"] as JsonArray val sources = (sourceMapJson as JsonObject).properties["sources"] as JsonArray
sourceMapJson.properties["sourcesContent"] = JsonArray(*sources.elements.map { sourcePath -> sourceMapJson.properties["sourcesContent"] = JsonArray(*sources.elements.map { sourcePath ->
@@ -89,8 +89,7 @@ class DeadCodeElimination(private val logConsumer: (DCELogLevel, String) -> Unit
return@map block return@map block
} }
val sourceMapParse = file.sourceMapResource val sourceMapParse = file.sourceMapResource
?.let { InputStreamReader(it.reader(), "UTF-8") } ?.let { SourceMapParser.parse(InputStreamReader(it.reader(), "UTF-8").readText()) }
?.use { SourceMapParser.parse(it) }
when (sourceMapParse) { when (sourceMapParse) {
is SourceMapError -> { is SourceMapError -> {
logConsumer( logConsumer(
@@ -114,7 +114,7 @@ class FunctionReader(
} }
val sourceMap = sourceMapContent?.let { val sourceMap = sourceMapContent?.let {
val sourceMapResult = SourceMapParser.parse(StringReader(it)) val sourceMapResult = SourceMapParser.parse(it)
when (sourceMapResult) { when (sourceMapResult) {
is SourceMapSuccess -> sourceMapResult.value is SourceMapSuccess -> sourceMapResult.value
is SourceMapError -> { is SourceMapError -> {
@@ -16,10 +16,7 @@
package org.jetbrains.kotlin.js.parser.sourcemaps package org.jetbrains.kotlin.js.parser.sourcemaps
import java.io.Reader import java.io.*
import java.io.StringReader
import java.io.StringWriter
import java.io.Writer
sealed class JsonNode { sealed class JsonNode {
abstract fun write(writer: Writer) abstract fun write(writer: Writer)
@@ -120,8 +117,7 @@ data class JsonNumber(val value: Double) : JsonNode() {
override fun write(writer: Writer) { override fun write(writer: Writer) {
if (value.toLong().toDouble() == value) { if (value.toLong().toDouble() == value) {
writer.append(value.toLong().toString()) writer.append(value.toLong().toString())
} } else {
else {
writer.append(value.toString()) writer.append(value.toString())
} }
} }
@@ -130,14 +126,15 @@ data class JsonNumber(val value: Double) : JsonNode() {
} }
class JsonSyntaxException(val offset: Int, val line: Int, val column: Int, val text: String) : class JsonSyntaxException(val offset: Int, val line: Int, val column: Int, val text: String) :
RuntimeException("JSON syntax error at ${line + 1}, ${column + 1}: $text") RuntimeException("JSON syntax error at ${line + 1}, ${column + 1}: $text")
fun parseJson(reader: Reader): JsonNode = JsonParser(reader).parse() fun parseJson(file: File): JsonNode = parseJson(file.readText(Charsets.UTF_8))
fun parseJson(text: String): JsonNode = parseJson(StringReader(text)) fun parseJson(text: String): JsonNode = JsonParser(text).parse()
private class JsonParser(val reader: Reader) { private class JsonParser(val content: String) {
private var charCode = reader.read() private var index = 0
private var charCode = content.getOrNull(index++)?.toInt() ?: -1
private var offset = 0 private var offset = 0
private var line = 0 private var line = 0
private var col = 0 private var col = 0
@@ -165,13 +162,27 @@ private class JsonParser(val reader: Reader) {
'['.toInt() -> parseArray() '['.toInt() -> parseArray()
'{'.toInt() -> parseObject() '{'.toInt() -> parseObject()
'"'.toInt() -> JsonString(parseString()) '"'.toInt() -> JsonString(parseString())
'n'.toInt() -> { expectString("null"); JsonNull } 'n'.toInt() -> {
'f'.toInt() -> { expectString("false"); JsonBoolean.FALSE } expectString("null")
't'.toInt() -> { expectString("true"); JsonBoolean.TRUE } JsonNull
in '0'.toInt()..'9'.toInt() -> JsonNumber(parseNumber()) }
'-'.toInt() -> { advance(); JsonNumber(-parseNumber()) 'f'.toInt() -> {
expectString("false")
JsonBoolean.FALSE
}
't'.toInt() -> {
expectString("true")
JsonBoolean.TRUE
}
'-'.toInt() -> {
advance()
JsonNumber(-parseNumber())
}
else -> if (charCode in '0'.toInt()..'9'.toInt()) {
JsonNumber(parseNumber())
} else {
error("Unexpected char")
} }
else -> error("Unexpected char")
} }
} }
@@ -183,8 +194,7 @@ private class JsonParser(val reader: Reader) {
if (charCode == ']'.toInt()) { if (charCode == ']'.toInt()) {
advance() advance()
break break
} } else {
else {
if (result.elements.isNotEmpty()) { if (result.elements.isNotEmpty()) {
expectCharAndAdvance(',') expectCharAndAdvance(',')
} }
@@ -202,8 +212,7 @@ private class JsonParser(val reader: Reader) {
if (charCode == '}'.toInt()) { if (charCode == '}'.toInt()) {
advance() advance()
break break
} } else {
else {
if (result.properties.isNotEmpty()) { if (result.properties.isNotEmpty()) {
expectCharAndAdvance(',') expectCharAndAdvance(',')
} }
@@ -356,7 +365,7 @@ private class JsonParser(val reader: Reader) {
wasCR = false wasCR = false
} }
} }
charCode = reader.read() charCode = content.getOrNull(index++)?.toInt() ?: -1
offset++ offset++
} }
@@ -16,20 +16,29 @@
package org.jetbrains.kotlin.js.parser.sourcemaps package org.jetbrains.kotlin.js.parser.sourcemaps
import java.io.File
import java.io.IOException import java.io.IOException
import java.io.Reader
import java.io.StringReader import java.io.StringReader
object SourceMapParser { object SourceMapParser {
@Throws(IOException::class) @Throws(IOException::class)
fun parse(reader: Reader): SourceMapParseResult { fun parse(file: File): SourceMapParseResult {
return parse(file.readText(Charsets.UTF_8))
}
@Throws(IOException::class)
fun parse(content: String): SourceMapParseResult {
val jsonObject = try { val jsonObject = try {
parseJson(reader) parseJson(content)
} }
catch (e: JsonSyntaxException) { catch (e: JsonSyntaxException) {
return SourceMapError(e.message ?: "parse error") return SourceMapError(e.message ?: "parse error")
} }
return parse(jsonObject)
}
@Throws(IOException::class)
private fun parse(jsonObject: JsonNode): SourceMapParseResult {
if (jsonObject !is JsonObject) return SourceMapError("Top-level object expected") if (jsonObject !is JsonObject) return SourceMapError("Top-level object expected")
val version = jsonObject.properties["version"] ?: return SourceMapError("Version not defined") val version = jsonObject.properties["version"] ?: return SourceMapError("Version not defined")
@@ -420,8 +420,8 @@ abstract class BasicBoxTest(
val recompiledSourceMap = removeRecompiledSuffix( val recompiledSourceMap = removeRecompiledSuffix(
FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map"))) FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map")))
if (originalSourceMap != recompiledSourceMap) { if (originalSourceMap != recompiledSourceMap) {
val originalSourceMapParse = SourceMapParser.parse(StringReader(originalSourceMap)) val originalSourceMapParse = SourceMapParser.parse(originalSourceMap)
val recompiledSourceMapParse = SourceMapParser.parse(StringReader(recompiledSourceMap)) val recompiledSourceMapParse = SourceMapParser.parse(recompiledSourceMap)
if (originalSourceMapParse is SourceMapSuccess && recompiledSourceMapParse is SourceMapSuccess) { if (originalSourceMapParse is SourceMapSuccess && recompiledSourceMapParse is SourceMapSuccess) {
assertEquals("Source map file changed after recompilation", assertEquals("Source map file changed after recompilation",
originalSourceMapParse.toDebugString(), originalSourceMapParse.toDebugString(),
@@ -570,7 +570,7 @@ abstract class BasicBoxTest(
val parsedProgram = JsProgram() val parsedProgram = JsProgram()
parsedProgram.globalBlock.statements += parse(code, ThrowExceptionOnErrorReporter, parsedProgram.scope, outputFile.path).orEmpty() parsedProgram.globalBlock.statements += parse(code, ThrowExceptionOnErrorReporter, parsedProgram.scope, outputFile.path).orEmpty()
removeLocationFromBlocks(parsedProgram) removeLocationFromBlocks(parsedProgram)
val sourceMapParseResult = SourceMapParser.parse(StringReader(generatedSourceMap)) val sourceMapParseResult = SourceMapParser.parse(generatedSourceMap)
val sourceMap = when (sourceMapParseResult) { val sourceMap = when (sourceMapParseResult) {
is SourceMapSuccess -> sourceMapParseResult.value is SourceMapSuccess -> sourceMapParseResult.value
is SourceMapError -> error("Could not parse source map: ${sourceMapParseResult.message}") is SourceMapError -> error("Could not parse source map: ${sourceMapParseResult.message}")