Skip to content

Commit

Permalink
Merge pull request #7 from EdwarDDay/feature/tagged-node-support
Browse files Browse the repository at this point in the history
Support tagged nodes
  • Loading branch information
charleskorn authored Sep 21, 2019
2 parents 410e598 + d4e52a1 commit 164fef4
Show file tree
Hide file tree
Showing 9 changed files with 446 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/com/charleskorn/kaml/Yaml.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Yaml(
override fun flush() { }
}

val output = YamlOutput(writer, configuration)
val output = YamlOutput(writer, context, configuration)
output.encode(serializer, obj)

return writer.toString()
Expand Down
59 changes: 56 additions & 3 deletions src/main/kotlin/com/charleskorn/kaml/YamlInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import kotlinx.serialization.ElementValueDecoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.StructureKind
import kotlinx.serialization.UnionKind
import kotlinx.serialization.UpdateMode
import kotlinx.serialization.internal.EnumDescriptor
import kotlinx.serialization.modules.SerialModule
Expand All @@ -36,6 +37,7 @@ sealed class YamlInput(val node: YamlNode, override var context: SerialModule, v
is YamlNull -> YamlNullInput(node, context, configuration)
is YamlList -> YamlListInput(node, context, configuration)
is YamlMap -> YamlMapInput(node, context, configuration)
is YamlTaggedNode -> YamlTaggedInput(node, context, configuration)
}
}

Expand Down Expand Up @@ -151,7 +153,13 @@ private class YamlListInput(val list: YamlList, context: SerialModule, configura
}
}

override fun getCurrentLocation(): Location = currentElementDecoder.node.location
override fun getCurrentLocation(): Location {
return if (haveStartedReadingElements) {
currentElementDecoder.node.location
} else {
list.location
}
}
}

private class YamlMapInput(val map: YamlMap, context: SerialModule, configuration: YamlConfiguration) : YamlInput(map, context, configuration) {
Expand Down Expand Up @@ -214,7 +222,7 @@ private class YamlMapInput(val map: YamlMap, context: SerialModule, configuratio

private fun getPropertyName(key: YamlNode): String = when (key) {
is YamlScalar -> key.content
is YamlNull, is YamlMap, is YamlList -> throw MalformedYamlException("Property name must not be a list, map or null value. (To use 'null' as a property name, enclose it in quotes.)", key.location)
is YamlNull, is YamlMap, is YamlList, is YamlTaggedNode -> throw MalformedYamlException("Property name must not be a list, map, null or tagged value. (To use 'null' as a property name, enclose it in quotes.)", key.location)
}

private fun throwUnknownProperty(name: String, location: Location, desc: SerialDescriptor): Nothing {
Expand Down Expand Up @@ -287,5 +295,50 @@ private class YamlMapInput(val map: YamlMap, context: SerialModule, configuratio
Map
}

override fun getCurrentLocation(): Location = currentValueDecoder.node.location
override fun getCurrentLocation(): Location {
return if (haveStartedReadingEntries) {
currentValueDecoder.node.location
} else {
map.location
}
}
}

private class YamlTaggedInput(val taggedNode: YamlTaggedNode, context: SerialModule, configuration: YamlConfiguration) : YamlInput(taggedNode, context, configuration) {
/**
* index 0 -> tag
* index 1 -> child node
*/
private var currentIndex = -1
private var isPolymorphic = false
private val childDecoder: YamlInput = createFor(taggedNode.node, context, configuration)

override fun getCurrentLocation(): Location = maybeCallOnChild(blockOnTag = { taggedNode.location }) { getCurrentLocation() }

override fun decodeElementIndex(desc: SerialDescriptor): Int {
desc.calculatePolymorphic()
return when (++currentIndex) {
0, 1 -> currentIndex
else -> READ_DONE
}
}

override fun decodeString(): String = maybeCallOnChild(blockOnTag = { taggedNode.tag }) { decodeString() }

override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
desc.calculatePolymorphic()
return maybeCallOnChild(blockOnTag = { super.beginStructure(desc, *typeParams) }) { beginStructure(desc, *typeParams) }
}

private fun SerialDescriptor.calculatePolymorphic() {
isPolymorphic = kind === UnionKind.POLYMORPHIC
}

private inline fun <T> maybeCallOnChild(blockOnTag: () -> T, blockOnChild: YamlInput.() -> T): T {
return if (isPolymorphic && currentIndex != 1) {
blockOnTag()
} else {
childDecoder.blockOnChild()
}
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/com/charleskorn/kaml/YamlNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,19 @@ data class YamlMap(val entries: Map<YamlNode, YamlNode>, override val location:
override fun contentToString(): String =
"{" + entries.map { (key, value) -> "${key.contentToString()}: ${value.contentToString()}" }.joinToString(", ") + "}"
}

data class YamlTaggedNode(val tag: String, val node: YamlNode) : YamlNode(node.location) {
override fun equivalentContentTo(other: YamlNode): Boolean {
if (other !is YamlTaggedNode) {
return false
}

if (tag != other.tag) {
return false
}

return node.equivalentContentTo(other.node)
}

override fun contentToString(): String = "!$tag ${node.contentToString()}"
}
10 changes: 7 additions & 3 deletions src/main/kotlin/com/charleskorn/kaml/YamlNodeReader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.snakeyaml.engine.v1.events.MappingStartEvent
import org.snakeyaml.engine.v1.events.NodeEvent
import org.snakeyaml.engine.v1.events.ScalarEvent
import org.snakeyaml.engine.v1.events.SequenceStartEvent
import java.util.Optional

class YamlNodeReader(
private val parser: YamlParser,
Expand Down Expand Up @@ -52,9 +53,9 @@ class YamlNodeReader(
}

private fun readFromEvent(event: Event, isTopLevel: Boolean): YamlNode = when (event) {
is ScalarEvent -> readScalarOrNull(event)
is SequenceStartEvent -> readSequence(event.location)
is MappingStartEvent -> readMapping(event.location, isTopLevel)
is ScalarEvent -> readScalarOrNull(event).maybeToTaggedNode(event.tag)
is SequenceStartEvent -> readSequence(event.location).maybeToTaggedNode(event.tag)
is MappingStartEvent -> readMapping(event.location, isTopLevel).maybeToTaggedNode(event.tag)
is AliasEvent -> readAlias(event)
else -> throw MalformedYamlException("Unexpected ${event.eventId}", event.location)
}
Expand Down Expand Up @@ -112,6 +113,9 @@ class YamlNodeReader(
}
}

private fun YamlNode.maybeToTaggedNode(tag: Optional<String>): YamlNode =
tag.map<YamlNode> { YamlTaggedNode(it, this) }.orElse(this)

private fun YamlNode.isScalarAndStartsWith(prefix: String): Boolean = this is YamlScalar && this.content.startsWith(prefix)

private fun doMerges(items: Map<YamlNode, YamlNode>): Map<YamlNode, YamlNode> {
Expand Down
35 changes: 32 additions & 3 deletions src/main/kotlin/com/charleskorn/kaml/YamlOutput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ package com.charleskorn.kaml
import kotlinx.serialization.CompositeEncoder
import kotlinx.serialization.ElementValueEncoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.StructureKind
import kotlinx.serialization.internal.EnumDescriptor
import kotlinx.serialization.modules.SerialModule
import org.snakeyaml.engine.v1.api.DumpSettingsBuilder
import org.snakeyaml.engine.v1.api.StreamDataWriter
import org.snakeyaml.engine.v1.common.FlowStyle
Expand All @@ -41,10 +44,12 @@ import java.util.Optional

internal class YamlOutput(
writer: StreamDataWriter,
override val context: SerialModule,
private val configuration: YamlConfiguration
) : ElementValueEncoder() {
private val settings = DumpSettingsBuilder().build()
private val emitter = Emitter(settings, writer)
private var currentTag: String? = null

init {
emitter.emit(StreamStartEvent())
Expand Down Expand Up @@ -75,10 +80,24 @@ internal class YamlOutput(
return super.encodeElement(desc, index)
}

override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
if (serializer !is PolymorphicSerializer<*>) {
serializer.serialize(this, value)
return
}

@Suppress("UNCHECKED_CAST")
val actualSerializer = serializer.findPolymorphicSerializer(this, value as Any) as KSerializer<Any>
currentTag = actualSerializer.descriptor.name
actualSerializer.serialize(this, value)
}

override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder {
val tag = getAndClearTag()
val implicit = !tag.isPresent
when (desc.kind) {
is StructureKind.LIST -> emitter.emit(SequenceStartEvent(Optional.empty(), Optional.empty(), true, FlowStyle.BLOCK))
is StructureKind.MAP, StructureKind.CLASS -> emitter.emit(MappingStartEvent(Optional.empty(), Optional.empty(), true, FlowStyle.BLOCK))
is StructureKind.LIST -> emitter.emit(SequenceStartEvent(Optional.empty(), tag, implicit, FlowStyle.BLOCK))
is StructureKind.MAP, StructureKind.CLASS -> emitter.emit(MappingStartEvent(Optional.empty(), tag, implicit, FlowStyle.BLOCK))
}

return super.beginStructure(desc, *typeParams)
Expand All @@ -94,5 +113,15 @@ internal class YamlOutput(
}

private fun emitScalar(value: String, style: ScalarStyle) =
emitter.emit(ScalarEvent(Optional.empty(), Optional.empty(), ImplicitTuple(true, true), value, style))
emitter.emit(ScalarEvent(Optional.empty(), getAndClearTag(), ALL_IMPLICIT, value, style))

private fun getAndClearTag(): Optional<String> {
val tag = Optional.ofNullable(currentTag)
currentTag = null
return tag
}

companion object {
private val ALL_IMPLICIT = ImplicitTuple(true, true)
}
}
Loading

0 comments on commit 164fef4

Please sign in to comment.