Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

Commit

Permalink
core: Add vdf parser
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Rittmeister committed May 13, 2022
1 parent c67a2ec commit d8b9ac4
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 1 deletion.
3 changes: 2 additions & 1 deletion build.gradle.kts
Expand Up @@ -61,7 +61,8 @@ tasks {
withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs =
freeCompilerArgs + "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
freeCompilerArgs + "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" +
"-opt-in=kotlinx.serialization.InternalSerializationApi"
jvmTarget = "18"
}
}
Expand Down
94 changes: 94 additions & 0 deletions src/main/kotlin/vdf/VDF.kt
@@ -0,0 +1,94 @@
package dev.nycode.omsilauncher.vdf

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule

private const val tab = "\t"
private const val delimiter = "\t\t"

open class VDF internal constructor(override val serializersModule: SerializersModule) : StringFormat {

fun decodeToVDFObject(string: String) = decodeToVDFObject(VDFLineParser(string.lines()), false)

fun <T> decodeFromVDFObject(deserializer: DeserializationStrategy<T>, element: VDFObject): T {
val decoder = VDFObjectDecoder(this, element)
return decoder.decodeSerializableValue(deserializer)
}

override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T =
decodeFromVDFObject(deserializer, decodeToVDFObject(string))

override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String =
encodeFromVDFElement(encodeToVDFElement(serializer, value))

fun <T> encodeToVDFElement(serializer: SerializationStrategy<T>, value: T): VDFObject {
val encoder = VDFObjectEncoder(this)
encoder.encodeSerializableValue(serializer, value)
return encoder.toElement()
}

fun encodeFromVDFElement(element: VDFElement) = encode(element, 0, true)

private fun decodeToVDFObject(parser: VDFLineParser, isChildDecode: Boolean): VDFObject {
@Suppress("RemoveExplicitTypeArguments") // inference doesn't work for some reason
val map = buildMap<String, VDFElement> {
while (parser.hasNext()) {
val line = parser.next().trim { it.isWhitespace() }.split(delimiter)
if (line[0].startsWith("\"") && line[0].endsWith("\"")) {
if (line.size == 2) {
put(line[0].removeQuotes(), VDFPrimitive(line[1].removeQuotes()))
} else if (line.size == 1) {
put(line[0].removeQuotes(), decodeToVDFObject(parser, true))
}
} else if (line[0].contains("}") && isChildDecode) {
break // return currentObject
}
}
}

return VDFObject(map)
}

private fun encode(element: VDFElement, indent: Int, isRoot: Boolean): String = buildString {
when (element) {
is VDFPrimitive -> appendLine(element.content.enquote())
is VDFObject -> append(encode(element, indent, isRoot))
}
}

private fun encode(obj: VDFObject, indent: Int, isRoot: Boolean) = buildString {
appendLine()
if (!isRoot) {
appendIndent(indent - 1)
appendLine('{')
}
obj.forEach { key, value ->
appendIndent(indent)
append(key.enquote())
append(delimiter)
append(encode(value, indent + 1, false))
}
if (!isRoot) {
appendIndent(indent - 1)
appendLine('}')
}
}

companion object Default : VDF(EmptySerializersModule)
}

private class VDFLineParser(private val lines: List<String>) : Iterator<String> {
private var index = 0

override fun next(): String = lines[index++]

override fun hasNext(): Boolean = index < lines.size
}

private fun String.removeQuotes(): String = trim().run { substring(1, lastIndex) }
private fun String.enquote() = """"$this""""

private fun StringBuilder.appendIndent(amount: Int) = append(tab.repeat(amount))
6 changes: 6 additions & 0 deletions src/main/kotlin/vdf/VDFDecoder.kt
@@ -0,0 +1,6 @@
package dev.nycode.omsilauncher.vdf

import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder

interface VDFDecoder : Decoder, CompositeDecoder
28 changes: 28 additions & 0 deletions src/main/kotlin/vdf/VDFElement.kt
@@ -0,0 +1,28 @@
package dev.nycode.omsilauncher.vdf

sealed class VDFElement

data class VDFObject(private val content: Map<String, VDFElement>) : VDFElement(), Map<String, VDFElement> by content

sealed class VDFPrimitive : VDFElement() {
abstract val isString: Boolean
abstract val content: String

override fun toString(): String = content
}

fun VDFPrimitive(string: String): VDFPrimitive = VDFString(string)

data class VDFString(override val content: String) : VDFPrimitive() {
override val isString: Boolean = true
override fun toString(): String = content
}

val VDFElement.vdfObject: VDFObject
get() = this as? VDFObject ?: error("Cannot cast ${this::class::simpleName} to object")

val VDFElement.vdfPrimitive: VDFPrimitive
get() = this as? VDFPrimitive ?: error("Cannot cast ${this::class::simpleName} to primitive")

val VDFElement.vdfString: VDFString
get() = this as? VDFString ?: error("Cannot cast ${this::class::simpleName} to string")
6 changes: 6 additions & 0 deletions src/main/kotlin/vdf/VDFEncoder.kt
@@ -0,0 +1,6 @@
package dev.nycode.omsilauncher.vdf

import kotlinx.serialization.encoding.CompositeEncoder
import kotlinx.serialization.encoding.Encoder

interface VDFEncoder : Encoder, CompositeEncoder
55 changes: 55 additions & 0 deletions src/main/kotlin/vdf/VDFObjectDecoder.kt
@@ -0,0 +1,55 @@
package dev.nycode.omsilauncher.vdf

import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.internal.NamedValueDecoder
import java.util.LinkedList

class VDFObjectDecoder(private val vdf: VDF, private val obj: VDFObject) : NamedValueDecoder(), VDFDecoder {
private val list = LinkedList(obj.toList())

private var currentElement: VDFElement? = null

override fun decodeTaggedValue(tag: String): VDFElement =
obj[tag]?.let { currentElement ?: it } ?: throw SerializationException("Could not find value for tag: $tag")

override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
return if (list.isEmpty()) {
CompositeDecoder.DECODE_DONE
} else {
val (name, element) = list.poll()
currentElement = element
descriptor.getElementIndex(name)
}
}

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
val currentObject = currentElement as? VDFObject ?: return super.beginStructure(descriptor)

return VDFObjectDecoder(vdf, currentObject)
}

override fun decodeTaggedString(tag: String): String = decodeTaggedValue(tag).vdfString.content
override fun decodeTaggedBoolean(tag: String): Boolean = decodeTaggedString(tag).toBooleanStrict()
override fun decodeTaggedByte(tag: String): Byte = decodeTaggedString(tag).toByte()
override fun decodeTaggedDouble(tag: String): Double = decodeTaggedString(tag).toDouble()
override fun decodeTaggedFloat(tag: String): Float = decodeTaggedString(tag).toFloat()
override fun decodeTaggedInt(tag: String): Int = decodeTaggedString(tag).toInt()
override fun decodeTaggedLong(tag: String): Long = decodeTaggedString(tag).toLong()
override fun decodeTaggedShort(tag: String): Short = decodeTaggedString(tag).toShort()
override fun decodeTaggedNotNullMark(tag: String): Boolean =
throw SerializationException("VDF does not support nulls")

override fun decodeTaggedNull(tag: String): Nothing =
throw SerializationException("VDF does not support nulls")

override fun decodeTaggedChar(tag: String): Char {
val sequence = decodeTaggedString(tag)
if (sequence.length != 1) {
throw SerializationException("'$sequence' is not a char")
}

return sequence.first()
}
}
46 changes: 46 additions & 0 deletions src/main/kotlin/vdf/VDFObjectEncoder.kt
@@ -0,0 +1,46 @@
package dev.nycode.omsilauncher.vdf

import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeEncoder
import kotlinx.serialization.internal.NamedValueEncoder
import kotlinx.serialization.modules.SerializersModule

internal class VDFObjectEncoder(private val vdf: VDF, private val parent: VDFObjectEncoder? = null) :
NamedValueEncoder(), VDFEncoder {
private val elements = mutableMapOf<String, VDFElement>()

private var structureEncoder: VDFObjectEncoder? = null

override val serializersModule: SerializersModule get() = vdf.serializersModule

override fun encodeNull() = throw SerializationException("VDF does not support null")

override fun encodeTaggedValue(tag: String, value: Any) {
elements += tag to VDFPrimitive(value.toString())
}

override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
return if (currentTagOrNull == null) {
super.beginStructure(descriptor)
} else {
require(structureEncoder == null) { "Cannot decode two structures at the same time" }
structureEncoder = VDFObjectEncoder(vdf, this)
structureEncoder!!
}
}

override fun endEncode(descriptor: SerialDescriptor) {
super.endEncode(descriptor)
if (parent != null) {
parent.structureEncoder = null
if (parent.currentTagOrNull != null) {
parent.elements += parent.currentTag to toElement()
// For some reason the structure tag needs to get removed manually here
parent.popTag()
}
}
}

fun toElement() = VDFObject(elements)
}

0 comments on commit d8b9ac4

Please sign in to comment.