From 7c46d8b80ce75e2a97d86b60d6901c12edfbe8ef Mon Sep 17 00:00:00 2001 From: Peanuuutz <1415605913@qq.com> Date: Thu, 5 Oct 2023 14:31:56 +0800 Subject: [PATCH 1/3] 0.3.5 TomlWriter expansion. --- .../net/peanuuutz/tomlkt/TomlStringWriter.kt | 4 - .../kotlin/net/peanuuutz/tomlkt/TomlWriter.kt | 224 ++++++++++++++---- .../peanuuutz/tomlkt/internal/StringUtils.kt | 24 +- .../internal/encoder/TomlFileEncoder.kt | 158 ++++++------ .../tomlkt/internal/parser/TomlFileParser.kt | 52 ++-- .../net/peanuuutz/tomlkt/TomlNativeWriter.kt | 5 - .../net/peanuuutz/tomlkt/TomlStreamWriter.kt | 9 - 7 files changed, 315 insertions(+), 161 deletions(-) diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt index bd51e6a..9c6feac 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt @@ -38,10 +38,6 @@ public class TomlStringWriter : TomlWriter { builder.append(char) } - override fun writeLineFeed() { - builder.append('\n') - } - override fun toString(): String { return builder.toString() } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt index 5ab45fb..66a6a33 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt @@ -16,6 +16,19 @@ package net.peanuuutz.tomlkt +import net.peanuuutz.tomlkt.internal.Comment +import net.peanuuutz.tomlkt.internal.ElementSeparator +import net.peanuuutz.tomlkt.internal.EndArray +import net.peanuuutz.tomlkt.internal.EndInlineTable +import net.peanuuutz.tomlkt.internal.EndTableHead +import net.peanuuutz.tomlkt.internal.KeyValueSeparator +import net.peanuuutz.tomlkt.internal.SegmentSeparator +import net.peanuuutz.tomlkt.internal.StartArray +import net.peanuuutz.tomlkt.internal.StartInlineTable +import net.peanuuutz.tomlkt.internal.StartTableHead +import net.peanuuutz.tomlkt.internal.doubleQuoted +import net.peanuuutz.tomlkt.internal.escape +import net.peanuuutz.tomlkt.internal.singleQuoted import net.peanuuutz.tomlkt.internal.toStringModified /** @@ -24,78 +37,201 @@ import net.peanuuutz.tomlkt.internal.toStringModified * @see [TomlStringWriter] */ public interface TomlWriter { - /** - * Writes [string]. - */ + // -------- Core -------- + public fun writeString(string: String) - /** - * Writes [boolean]. - */ - public fun writeBoolean(boolean: Boolean) { + public fun writeChar(char: Char) { + writeString(char.toString()) + } + + // -------- Key -------- + + public fun writeSegmentSeparator() { + writeChar(SegmentSeparator) + } + + public fun writeBareSegment(segment: String) { + writeString(segment) + } + + public fun writeDoubleQuotedSegment(segment: String) { + writeString(segment.doubleQuoted) + } + + public fun writeSingleQuotedSegment(segment: String) { + writeString(segment.singleQuoted) + } + + // -------- Table Head -------- + + public fun startRegularTableHead() { + writeChar(StartTableHead) + } + + public fun endRegularTableHead() { + writeChar(EndTableHead) + } + + public fun startArrayOfTableHead() { + writeChar(StartTableHead) + writeChar(StartTableHead) + } + + public fun endArrayOfTableHead() { + writeChar(EndTableHead) + writeChar(EndTableHead) + } + + // -------- Value -------- + + public fun writeBooleanValue(boolean: Boolean) { writeString(boolean.toString()) } - /** - * Writes [byte] **as literal**. - */ - public fun writeByte(byte: Byte) { + public fun writeByteValue(byte: Byte) { writeString(byte.toString()) } - /** - * Writes [short] **as literal**. - */ - public fun writeShort(short: Short) { + public fun writeShortValue(short: Short) { writeString(short.toString()) } - /** - * Writes [int] **as literal**. - */ - public fun writeInt(int: Int) { + public fun writeIntValue(int: Int) { writeString(int.toString()) } - /** - * Writes [long] **as literal**. - */ - public fun writeLong(long: Long) { + public fun writeLongValue(long: Long) { writeString(long.toString()) } - /** - * Writes [float]. - */ - public fun writeFloat(float: Float) { + public fun writeFloatValue(float: Float) { writeString(float.toStringModified()) } - /** - * Writes [double]. - */ - public fun writeDouble(double: Double) { + public fun writeDoubleValue(double: Double) { writeString(double.toStringModified()) } - /** - * Writes [char]. - */ - public fun writeChar(char: Char) { - writeString(char.toString()) + public fun writeCharValue(char: Char) { + writeString(char.escape().doubleQuoted) } - /** - * Writes "null". - */ - public fun writeNull() { + public fun writeStringValue(string: String) { + writeString(string.escape().doubleQuoted) + } + + public fun writeNullValue() { writeString("null") } - /** - * Writes '\n'. - */ + @Deprecated( + message = "Use writeBooleanValue instead.", + replaceWith = ReplaceWith("writeBooleanValue") + ) + public fun writeBoolean(boolean: Boolean) { + writeBooleanValue(boolean) + } + + @Deprecated( + message = "Use writeByteValue instead.", + replaceWith = ReplaceWith("writeByteValue") + ) + public fun writeByte(byte: Byte) { + writeByteValue(byte) + } + + @Deprecated( + message = "Use writeShortValue instead.", + replaceWith = ReplaceWith("writeShortValue") + ) + public fun writeShort(short: Short) { + writeShortValue(short) + } + + @Deprecated( + message = "Use writeIntValue instead.", + replaceWith = ReplaceWith("writeIntValue") + ) + public fun writeInt(int: Int) { + writeIntValue(int) + } + + @Deprecated( + message = "Use writeLongValue instead.", + replaceWith = ReplaceWith("writeLongValue") + ) + public fun writeLong(long: Long) { + writeLongValue(long) + } + + @Deprecated( + message = "Use writeFloatValue instead.", + replaceWith = ReplaceWith("writeFloatValue") + ) + public fun writeFloat(float: Float) { + writeFloatValue(float) + } + + @Deprecated( + message = "Use writeDoubleValue instead.", + replaceWith = ReplaceWith("writeDoubleValue") + ) + public fun writeDouble(double: Double) { + writeDoubleValue(double) + } + + @Deprecated( + message = "Use writeNullValue instead.", + replaceWith = ReplaceWith("writeNullValue") + ) + public fun writeNull() { + writeNullValue() + } + + // -------- Structure -------- + + public fun startArray() { + writeChar(StartArray) + } + + public fun endArray() { + writeChar(EndArray) + } + + public fun startInlineTable() { + writeChar(StartInlineTable) + } + + public fun endInlineTable() { + writeChar(EndInlineTable) + } + + public fun writeKeyValueSeparator() { + writeChar(KeyValueSeparator) + } + + public fun writeElementSeparator() { + writeChar(ElementSeparator) + } + + // -------- Comment -------- + + public fun startComment() { + writeChar(Comment) + } + + // -------- Control -------- + + public fun writeSpace() { + writeChar(' ') + } + + public fun writeIndentation(indentation: TomlIndentation) { + writeString(indentation.representation) + } + public fun writeLineFeed() { - writeString("\n") + writeChar('\n') } } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt index 21bf59a..7e3d76a 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt @@ -20,15 +20,23 @@ import kotlin.math.pow internal const val Comment = '#' -internal const val KeyValueDelimiter = '=' +internal const val SegmentSeparator = '.' + +internal const val KeyValueSeparator = '=' + +internal const val ElementSeparator = ',' + +internal const val StartTableHead = '[' + +internal const val EndTableHead = ']' internal const val StartArray = '[' internal const val EndArray = ']' -internal const val StartTable = '{' +internal const val StartInlineTable = '{' -internal const val EndTable = '}' +internal const val EndInlineTable = '}' internal const val DecimalConstraints: String = "0123456789" @@ -64,10 +72,8 @@ internal val AsciiMapping: List = buildList(128) { set('\\'.code, "\\\\") } -internal const val LineFeedCode: Int = '\n'.code - internal inline val String.singleQuoted: String - get() = "'$this'" + get() = "\'$this\'" internal inline val String.doubleQuoted: String get() = "\"$this\"" @@ -90,8 +96,8 @@ internal fun Char.escape(multiline: Boolean = false): String { internal fun String.escape(multiline: Boolean = false): String { val builder = StringBuilder() - for (i in indices) { - builder.append(get(i).escape(multiline)) + for (c in this) { + builder.append(c.escape(multiline)) } return builder.toString() } @@ -174,7 +180,7 @@ internal fun Float.toStringModified(): String { internal fun Double.toStringModified(): String { return when { isNaN() -> "nan" - isInfinite() -> if (this > 0.0f) "inf" else "-inf" + isInfinite() -> if (this > 0.0) "inf" else "-inf" else -> toString() } } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/encoder/TomlFileEncoder.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/encoder/TomlFileEncoder.kt index e035d31..fa12bd4 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/encoder/TomlFileEncoder.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/encoder/TomlFileEncoder.kt @@ -42,7 +42,6 @@ import net.peanuuutz.tomlkt.TomlNull import net.peanuuutz.tomlkt.TomlTable import net.peanuuutz.tomlkt.TomlWriter import net.peanuuutz.tomlkt.internal.anyIsInstance -import net.peanuuutz.tomlkt.internal.doubleQuoted import net.peanuuutz.tomlkt.internal.doubleQuotedIfNotPure import net.peanuuutz.tomlkt.internal.escape import net.peanuuutz.tomlkt.internal.firstIsInstanceOrNull @@ -66,49 +65,49 @@ internal abstract class AbstractTomlFileEncoder( val writer: TomlWriter ) : AbstractTomlEncoder(toml) { final override fun encodeBoolean(value: Boolean) { - writer.writeBoolean(value) + writer.writeBooleanValue(value) } final override fun encodeByte(value: Byte) { - writer.writeByte(value) + writer.writeByteValue(value) } final override fun encodeShort(value: Short) { - writer.writeShort(value) + writer.writeShortValue(value) } final override fun encodeInt(value: Int) { - writer.writeInt(value) + writer.writeIntValue(value) } final override fun encodeLong(value: Long) { - writer.writeLong(value) + writer.writeLongValue(value) } final override fun encodeFloat(value: Float) { - writer.writeFloat(value) + writer.writeFloatValue(value) } final override fun encodeDouble(value: Double) { - writer.writeDouble(value) + writer.writeDoubleValue(value) } final override fun encodeChar(value: Char) { - writer.writeString(value.escape().doubleQuoted) + writer.writeCharValue(value) } final override fun encodeString(value: String) { - writer.writeString(value.escape().doubleQuoted) + writer.writeStringValue(value) } final override fun encodeNull() { - writer.writeNull() + writer.writeNullValue() } final override fun encodeTomlElement(value: TomlElement) { when (value) { - TomlNull -> { - writer.writeNull() + is TomlNull -> { + writer.writeNullValue() } is TomlLiteral -> { writer.writeString(value.toString()) @@ -231,13 +230,13 @@ private abstract class AbstractTomlFileCompositeEncoder( } private fun encodeMultilineLiteralString(value: String) { - require("'''" !in value) { + require("\'\'\'" !in value) { "Cannot have \"\\'\\'\\'\" in multiline literal string, but found $value" } - writer.writeString("'''") + writer.writeString("\'\'\'") writer.writeLineFeed() writer.writeString(value) - writer.writeString("'''") + writer.writeString("\'\'\'") } override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { @@ -361,13 +360,17 @@ private class TomlFileInlineElementEncoder( private class TomlFileFlowClassEncoder( delegate: AbstractTomlFileEncoder ) : AbstractTomlFileCompositeEncoder(delegate) { - init { writer.writeString("{ ") } + init { + writer.startInlineTable() + writer.writeSpace() + } override fun encodeDiscriminatorElement(discriminator: String, serialName: String, isEmptyStructure: Boolean) { - writer.writeKey(discriminator) - encodeString(serialName) + writer.startEntry(discriminator) + writer.writeStringValue(serialName) if (!isEmptyStructure) { - writer.writeString(", ") + writer.writeElementSeparator() + writer.writeSpace() } } @@ -375,17 +378,19 @@ private class TomlFileFlowClassEncoder( val currentKey = descriptor.getElementName(index) .escape() .doubleQuotedIfNotPure() - writer.writeKey(currentKey) + writer.startEntry(currentKey) } override fun endElement(descriptor: SerialDescriptor, index: Int) { if (index != descriptor.elementsCount - 1) { - writer.writeString(", ") + writer.writeElementSeparator() + writer.writeSpace() } } override fun endStructure(descriptor: SerialDescriptor) { - writer.writeString(" }") + writer.writeSpace() + writer.endInlineTable() } } @@ -399,7 +404,10 @@ private class TomlFileFlowMapEncoder( private lateinit var currentKey: String - init { writer.writeString("{ ") } + init { + writer.startInlineTable() + writer.writeSpace() + } override fun encodeSerializableElement( descriptor: SerialDescriptor, @@ -413,10 +421,11 @@ private class TomlFileFlowMapEncoder( .doubleQuotedIfNotPure() } else { // This element is value (of the entry). if (value.isNullLike.not() || toml.config.explicitNulls) { - writer.writeKey(currentKey) + writer.startEntry(currentKey) encodeSerializableValue(serializer, value) if (index != mapSize * 2 - 1) { - writer.writeString(", ") + writer.writeElementSeparator() + writer.writeSpace() } } } @@ -434,7 +443,8 @@ private class TomlFileFlowMapEncoder( override fun endElement(descriptor: SerialDescriptor, index: Int) {} override fun endStructure(descriptor: SerialDescriptor) { - writer.writeString(" }") + writer.writeSpace() + writer.endInlineTable() } } @@ -444,7 +454,10 @@ private class TomlFileFlowArrayEncoder( delegate: AbstractTomlFileEncoder, private val arraySize: Int ) : AbstractTomlFileCompositeEncoder(delegate) { - init { writer.writeString("[ ") } + init { + writer.startArray() + writer.writeSpace() + } override fun encodeDiscriminatorElement(discriminator: String, serialName: String, isEmptyStructure: Boolean) { throwPolymorphicCollection() @@ -454,12 +467,14 @@ private class TomlFileFlowArrayEncoder( override fun endElement(descriptor: SerialDescriptor, index: Int) { if (index != arraySize - 1) { - writer.writeString(", ") + writer.writeElementSeparator() + writer.writeSpace() } } override fun endStructure(descriptor: SerialDescriptor) { - writer.writeString(" ]") + writer.writeSpace() + writer.endArray() } } @@ -473,7 +488,7 @@ private class TomlFileBlockArrayEncoder( private var currentLineItemsCount: Int = 0 init { - writer.writeChar('[') + writer.startArray() writer.writeLineFeed() } @@ -483,15 +498,15 @@ private class TomlFileBlockArrayEncoder( override fun beginElement(descriptor: SerialDescriptor, index: Int) { if (currentLineItemsCount == 0) { - writer.writeString(toml.config.indentation.representation) + writer.writeIndentation(toml.config.indentation) } else { - writer.writeChar(' ') + writer.writeSpace() } } override fun endElement(descriptor: SerialDescriptor, index: Int) { if (index != arraySize - 1) { - writer.writeChar(',') + writer.writeElementSeparator() } if (++currentLineItemsCount >= itemsPerLine) { writer.writeLineFeed() @@ -503,7 +518,7 @@ private class TomlFileBlockArrayEncoder( if (currentLineItemsCount != 0) { writer.writeLineFeed() } - writer.writeChar(']') + writer.endArray() } } @@ -548,7 +563,7 @@ private abstract class TomlFileTableLikeEncoder( ) } descriptor.elementsCount == 0 -> { - writer.writeKey(currentElementPath) + writer.startEntry(currentElementPath) TomlFileFlowClassEncoder(this) } else -> { @@ -612,7 +627,7 @@ private abstract class TomlFileTableLikeEncoder( ) } collectionSize == 0 -> { - writer.writeKey(currentElementPath) + writer.startEntry(currentElementPath) TomlFileFlowMapEncoder(this, 0) } else -> { @@ -662,8 +677,8 @@ private class TomlFileClassEncoder( override fun encodeDiscriminatorElement(discriminator: String, serialName: String, isEmptyStructure: Boolean) { currentElementKey = discriminator - appendKeyDirectly() - encodeString(serialName) + writer.startEntryDirectly() + writer.writeStringValue(serialName) if (!isEmptyStructure) { writer.writeLineFeed() } @@ -678,7 +693,7 @@ private class TomlFileClassEncoder( shouldStructureCurrentElement = isStructured && index >= structuredTableLikeIndex processElementAnnotations(descriptor.getElementAnnotations(index)) if (currentElementDescriptor.isCollection.not()) { - appendKey() + writer.startEntry() } // For non-null collections, defer to beginCollection(). } @@ -710,7 +725,9 @@ private class TomlFileClassEncoder( writer.writeLineFeed() } for (line in commentLines) { - writer.writeString("# $line") + writer.startComment() + writer.writeSpace() + writer.writeString(line) writer.writeLineFeed() } } @@ -720,34 +737,36 @@ private class TomlFileClassEncoder( // descriptor == currentElementDescriptor. if (descriptor.isArrayOfTable) { if (collectionSize == 0 || !shouldStructureCurrentElement) { - appendKeyDirectly() + writer.startEntryDirectly() } } else { - appendKey() + writer.startEntry() } return super.beginCollection(descriptor, collectionSize) } - private fun appendKey() { + private fun TomlWriter.startEntry() { when { shouldInlineCurrentElement -> { - appendKeyDirectly() + startEntryDirectly() } currentElementDescriptor.isTable -> { if (shouldStructureCurrentElement) { - writer.writeLineFeed() - writer.writeString("[$currentElementPath]") - writer.writeLineFeed() + writeLineFeed() + startRegularTableHead() + writeString(currentElementPath) + endRegularTableHead() + writeLineFeed() } } else -> { - appendKeyDirectly() + startEntryDirectly() } } } - private fun appendKeyDirectly() { - writer.writeKey(if (isStructured) currentElementKey else currentElementPath) + private fun TomlWriter.startEntryDirectly() { + startEntry(if (isStructured) currentElementKey else currentElementPath) } override fun endElement(descriptor: SerialDescriptor, index: Int) { @@ -792,10 +811,10 @@ private class TomlFileMapEncoder( if (value.isNullLike.not() || toml.config.explicitNulls) { when { value.isNullLike -> { - appendKeyDirectly() + writer.startEntryDirectly() } valueDescriptor.isCollection.not() -> { - appendKey() + writer.startEntry() } // For non-null collections, defer to beginCollection(). } @@ -820,34 +839,36 @@ private class TomlFileMapEncoder( // Only the first array of table is allowed to be empty when in a map. throwEmptyArrayOfTableInMap() } - appendKeyDirectly() + writer.startEntryDirectly() } } else { - appendKey() + writer.startEntry() } return super.beginCollection(descriptor, collectionSize) } - private fun appendKey() { + private fun TomlWriter.startEntry() { when { shouldInlineCurrentElement -> { - appendKeyDirectly() + startEntryDirectly() } valueDescriptor.isTable -> { if (isStructured) { - writer.writeLineFeed() - writer.writeString("[$currentElementPath]") - writer.writeLineFeed() + writeLineFeed() + startRegularTableHead() + writeString(currentElementPath) + endRegularTableHead() + writeLineFeed() } } else -> { - appendKeyDirectly() + startEntryDirectly() } } } - private fun appendKeyDirectly() { - writer.writeKey(if (isStructured) currentElementKey else currentElementPath) + private fun TomlWriter.startEntryDirectly() { + startEntry(if (isStructured) currentElementKey else currentElementPath) } // This shouldn't be called. @@ -891,7 +912,9 @@ private class TomlFileArrayOfTableEncoder( override fun beginElement(descriptor: SerialDescriptor, index: Int) { writer.writeLineFeed() - writer.writeString("[[$currentElementPath]]") + writer.startArrayOfTableHead() + writer.writeString(path) + writer.endArrayOfTableHead() writer.writeLineFeed() } @@ -998,6 +1021,9 @@ private fun calculateStructuredTableLikeIndex(descriptor: SerialDescriptor): Int return structuredIndex } -private fun TomlWriter.writeKey(key: String) { - writeString("$key = ") +private fun TomlWriter.startEntry(key: String) { + writeString(key) + writeSpace() + writeKeyValueSeparator() + writeSpace() } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt index 33e869e..ecc4512 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt @@ -33,12 +33,16 @@ import net.peanuuutz.tomlkt.internal.DecimalConstraints import net.peanuuutz.tomlkt.internal.DecimalOrSignConstraints import net.peanuuutz.tomlkt.internal.DefiniteDateTimeConstraints import net.peanuuutz.tomlkt.internal.DefiniteNumberConstraints +import net.peanuuutz.tomlkt.internal.ElementSeparator import net.peanuuutz.tomlkt.internal.EndArray -import net.peanuuutz.tomlkt.internal.EndTable +import net.peanuuutz.tomlkt.internal.EndInlineTable +import net.peanuuutz.tomlkt.internal.EndTableHead import net.peanuuutz.tomlkt.internal.HexadecimalConstraints -import net.peanuuutz.tomlkt.internal.KeyValueDelimiter +import net.peanuuutz.tomlkt.internal.KeyValueSeparator +import net.peanuuutz.tomlkt.internal.SegmentSeparator import net.peanuuutz.tomlkt.internal.StartArray -import net.peanuuutz.tomlkt.internal.StartTable +import net.peanuuutz.tomlkt.internal.StartInlineTable +import net.peanuuutz.tomlkt.internal.StartTableHead import net.peanuuutz.tomlkt.internal.throwIncomplete import net.peanuuutz.tomlkt.internal.throwUnexpectedToken import net.peanuuutz.tomlkt.internal.toNumber @@ -143,7 +147,7 @@ internal class TomlFileParser( } in BareKeyConstraints, '\"', '\'' -> { val localPath = parsePath() - throwUnexpectedTokenIf(getChar()) { it != KeyValueDelimiter } + throwUnexpectedTokenIf(getChar()) { it != KeyValueSeparator } proceed() val key = localPath.last() val value = parseValue(insideStructure = false) @@ -154,10 +158,10 @@ internal class TomlFileParser( Comment -> { parseComment() } - StartArray -> { + StartTableHead -> { proceed() throwIncompleteIf { isEof } - val isArrayOfTable = getChar() == StartArray + val isArrayOfTable = getChar() == StartTableHead if (isArrayOfTable) { proceed() } @@ -210,9 +214,9 @@ internal class TomlFileParser( '\n' -> { throwIncomplete() } - EndArray -> { + EndTableHead -> { if (isArrayOfTable) { - expectNext(EndArray) + expectNext(EndTableHead) } justEnded = true proceed() @@ -258,12 +262,12 @@ internal class TomlFileParser( path.add(parseLiteralStringKey()) expectKey = false } - '.' -> { + SegmentSeparator -> { throwUnexpectedTokenIf(currentChar) { expectKey } expectKey = true proceed() } - KeyValueDelimiter, EndArray -> { + KeyValueSeparator, EndArray -> { throwUnexpectedTokenIf(currentChar) { expectKey } justEnded = true break @@ -285,7 +289,7 @@ internal class TomlFileParser( val builder = StringBuilder() while (!isEof) { when (val currentChar = getChar()) { - ' ', '\t', '.', KeyValueDelimiter, EndArray -> { + ' ', '\t', SegmentSeparator, KeyValueSeparator, EndArray -> { break } '\n' -> { @@ -342,7 +346,7 @@ internal class TomlFileParser( Comment -> { parseComment() } - ',', EndArray, EndTable -> { + ElementSeparator, EndArray, EndInlineTable -> { throwUnexpectedTokenIf(currentChar) { !insideStructure } break } @@ -404,7 +408,7 @@ internal class TomlFileParser( StartArray -> { parseArrayValue() } - StartTable -> { + StartInlineTable -> { parseInlineTableValue() } else -> { @@ -503,7 +507,7 @@ internal class TomlFileParser( var isNumber = true while (!isEof) { when (val currentChar = getChar()) { - ' ', '\t', '\n', ',', Comment, EndArray, EndTable -> { + ' ', '\t', '\n', ElementSeparator, Comment, EndArray, EndInlineTable -> { break } '\r' -> { @@ -555,7 +559,7 @@ internal class TomlFileParser( var isExponent = false while (!isEof) { when (val currentChar = getChar()) { - ' ', '\t', '\n', ',', Comment, EndArray, EndTable -> { + ' ', '\t', '\n', ElementSeparator, Comment, EndArray, EndInlineTable -> { break } '\r' -> { @@ -654,7 +658,7 @@ internal class TomlFileParser( var hasDateTimeSeparator = false while (!isEof) { when (val currentChar = getChar()) { - '\t', '\n', ',', Comment, EndArray, EndTable -> { + '\t', '\n', ElementSeparator, Comment, EndArray, EndInlineTable -> { break } '\r' -> { @@ -680,7 +684,7 @@ internal class TomlFileParser( } else { hasOffset = true } - builder.append('-') + builder.append(currentChar) proceed() } 'T', 't' -> { @@ -692,11 +696,11 @@ internal class TomlFileParser( if (!hasOffset) { hasTime = true } - builder.append(':') + builder.append(currentChar) proceed() } '.' -> { - builder.append('.') + builder.append(currentChar) proceed() } 'Z', 'z' -> { @@ -706,7 +710,7 @@ internal class TomlFileParser( } '+' -> { hasOffset = true - builder.append('+') + builder.append(currentChar) proceed() } else -> { @@ -965,7 +969,7 @@ internal class TomlFileParser( proceed() break } - ',' -> { + ElementSeparator -> { throwUnexpectedTokenIf(currentChar) { expectValue } expectValue = true proceed() @@ -1001,13 +1005,13 @@ internal class TomlFileParser( '\n' -> { throwIncomplete() } - EndTable -> { + EndInlineTable -> { throwUnexpectedTokenIf(currentChar) { expectEntry && !justStarted } justEnded = true proceed() break } - ',' -> { + ElementSeparator -> { throwUnexpectedTokenIf(currentChar) { expectEntry } expectEntry = true proceed() @@ -1017,7 +1021,7 @@ internal class TomlFileParser( } else -> { val localPath = parsePath() - throwUnexpectedTokenIf(getChar()) { it != KeyValueDelimiter } + throwUnexpectedTokenIf(getChar()) { it != KeyValueSeparator } proceed() val key = localPath.last() val value = parseValue(insideStructure = true) diff --git a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlNativeWriter.kt b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlNativeWriter.kt index b6fd6e7..937212d 100644 --- a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlNativeWriter.kt +++ b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlNativeWriter.kt @@ -16,7 +16,6 @@ package net.peanuuutz.tomlkt -import net.peanuuutz.tomlkt.internal.LineFeedCode import java.io.Writer /** @@ -43,8 +42,4 @@ public class TomlNativeWriter( override fun writeChar(char: Char) { nativeWriter.write(char.code) } - - override fun writeLineFeed() { - nativeWriter.write(LineFeedCode) - } } diff --git a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt index 10e034a..1159f4a 100644 --- a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt +++ b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt @@ -16,7 +16,6 @@ package net.peanuuutz.tomlkt -import net.peanuuutz.tomlkt.internal.LineFeedCode import java.io.OutputStream /** @@ -39,12 +38,4 @@ public class TomlStreamWriter( override fun writeString(string: String) { outputStream.write(string.toByteArray()) } - - override fun writeChar(char: Char) { - outputStream.write(char.code) - } - - override fun writeLineFeed() { - outputStream.write(LineFeedCode) - } } From 9a58bd51550e31a7a24bdc4081d5ec278d2fe4b0 Mon Sep 17 00:00:00 2001 From: Peanuuutz <1415605913@qq.com> Date: Thu, 5 Oct 2023 17:43:41 +0800 Subject: [PATCH 2/3] TomlInteger enhancement. --- .../peanuuutz/tomlkt/AbstractTomlWriter.kt | 239 ++++++++++++++++++ .../net/peanuuutz/tomlkt/Annotations.kt | 45 ++-- .../kotlin/net/peanuuutz/tomlkt/TomlConfig.kt | 16 +- .../net/peanuuutz/tomlkt/TomlStringWriter.kt | 2 +- .../kotlin/net/peanuuutz/tomlkt/TomlWriter.kt | 161 +++++------- .../peanuuutz/tomlkt/internal/StringUtils.kt | 35 +++ .../internal/encoder/TomlFileEncoder.kt | 94 +++---- .../net/peanuuutz/tomlkt/IntegerTest.kt | 36 ++- .../net/peanuuutz/tomlkt/TomlNativeWriter.kt | 2 +- .../net/peanuuutz/tomlkt/TomlStreamWriter.kt | 2 +- 10 files changed, 451 insertions(+), 181 deletions(-) create mode 100644 core/src/commonMain/kotlin/net/peanuuutz/tomlkt/AbstractTomlWriter.kt diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/AbstractTomlWriter.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/AbstractTomlWriter.kt new file mode 100644 index 0000000..3aa1b7e --- /dev/null +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/AbstractTomlWriter.kt @@ -0,0 +1,239 @@ +package net.peanuuutz.tomlkt + +import net.peanuuutz.tomlkt.TomlInteger.Base +import net.peanuuutz.tomlkt.TomlInteger.Base.Dec +import net.peanuuutz.tomlkt.internal.Comment +import net.peanuuutz.tomlkt.internal.ElementSeparator +import net.peanuuutz.tomlkt.internal.EndArray +import net.peanuuutz.tomlkt.internal.EndInlineTable +import net.peanuuutz.tomlkt.internal.EndTableHead +import net.peanuuutz.tomlkt.internal.KeyValueSeparator +import net.peanuuutz.tomlkt.internal.SegmentSeparator +import net.peanuuutz.tomlkt.internal.StartArray +import net.peanuuutz.tomlkt.internal.StartInlineTable +import net.peanuuutz.tomlkt.internal.StartTableHead +import net.peanuuutz.tomlkt.internal.doubleQuoted +import net.peanuuutz.tomlkt.internal.escape +import net.peanuuutz.tomlkt.internal.processIntegerString +import net.peanuuutz.tomlkt.internal.singleQuoted +import net.peanuuutz.tomlkt.internal.toStringModified + +public abstract class AbstractTomlWriter : TomlWriter { + // -------- Core -------- + + // Better implementation is welcomed. + override fun writeChar(char: Char) { + writeString(char.toString()) + } + + // -------- Key -------- + + final override fun writeSegmentSeparator() { + writeChar(SegmentSeparator) + } + + final override fun writeBareSegment(segment: String) { + writeString(segment) + } + + final override fun writeDoubleQuotedSegment(segment: String) { + writeString(segment.doubleQuoted) + } + + final override fun writeSingleQuotedSegment(segment: String) { + writeString(segment.singleQuoted) + } + + // -------- Table Head -------- + + final override fun startRegularTableHead() { + writeChar(StartTableHead) + } + + final override fun endRegularTableHead() { + writeChar(EndTableHead) + } + + final override fun startArrayOfTableHead() { + writeChar(StartTableHead) + writeChar(StartTableHead) + } + + final override fun endArrayOfTableHead() { + writeChar(EndTableHead) + writeChar(EndTableHead) + } + + // -------- Value -------- + + final override fun writeBooleanValue(boolean: Boolean) { + writeString(boolean.toString()) + } + + final override fun writeByteValue( + byte: Byte, + base: Base, + group: Int, + uppercase: Boolean + ) { + require(group >= 0) { "Group size cannot be negative" } + require(byte >= 0.toByte() || base == Dec) { + "Negative integer cannot be represented by other bases, but found $byte" + } + val string = processIntegerString( + raw = byte.toString(base.value), + base = base, + group = group, + uppercase = uppercase + ) + writeString(string) + } + + final override fun writeShortValue( + short: Short, + base: Base, + group: Int, + uppercase: Boolean + ) { + require(group >= 0) { "Group size cannot be negative" } + require(short >= 0.toShort() || base == Dec) { + "Negative integer cannot be represented by other bases, but found $short" + } + val string = processIntegerString( + raw = short.toString(base.value), + base = base, + group = group, + uppercase = uppercase + ) + writeString(string) + } + + final override fun writeIntValue( + int: Int, + base: Base, + group: Int, + uppercase: Boolean + ) { + require(group >= 0) { "Group size cannot be negative" } + require(int >= 0 || base == Dec) { + "Negative integer cannot be represented by other bases, but found $int" + } + val string = processIntegerString( + raw = int.toString(base.value), + base = base, + group = group, + uppercase = uppercase + ) + writeString(string) + } + + final override fun writeLongValue( + long: Long, + base: Base, + group: Int, + uppercase: Boolean + ) { + require(group >= 0) { "Group size cannot be negative" } + require(long >= 0L || base == Dec) { + "Negative integer cannot be represented by other bases, but found $long" + } + val string = processIntegerString( + raw = long.toString(base.value), + base = base, + group = group, + uppercase = uppercase + ) + writeString(string) + } + + final override fun writeFloatValue(float: Float) { + writeString(float.toStringModified()) + } + + final override fun writeDoubleValue(double: Double) { + writeString(double.toStringModified()) + } + + final override fun writeCharValue(char: Char) { + writeString(char.escape().doubleQuoted) + } + + final override fun writeStringValue(string: String, isMultiline: Boolean, isLiteral: Boolean) { + when { + !isMultiline && !isLiteral -> { + writeString(string.escape().doubleQuoted) + } + !isMultiline -> { + require('\'' !in string && '\n' !in string) { + "Cannot have '\\'' or '\\n' in literal string, but found $string" + } + writeString(string.singleQuoted) + } + !isLiteral -> { + writeString("\"\"\"") + writeLineFeed() + writeString(string.escape(multiline = true)) + writeString("\"\"\"") + } + else -> { + require("\'\'\'" !in string) { + "Cannot have \"\\'\\'\\'\" in multiline literal string, but found $string" + } + writeString("\'\'\'") + writeLineFeed() + writeString(string) + writeString("\'\'\'") + } + } + } + + final override fun writeNullValue() { + writeString("null") + } + + // -------- Structure -------- + + final override fun startArray() { + writeChar(StartArray) + } + + final override fun endArray() { + writeChar(EndArray) + } + + final override fun startInlineTable() { + writeChar(StartInlineTable) + } + + final override fun endInlineTable() { + writeChar(EndInlineTable) + } + + final override fun writeKeyValueSeparator() { + writeChar(KeyValueSeparator) + } + + final override fun writeElementSeparator() { + writeChar(ElementSeparator) + } + + // -------- Comment -------- + + final override fun startComment() { + writeChar(Comment) + } + + // -------- Control -------- + + final override fun writeSpace() { + writeChar(' ') + } + + final override fun writeIndentation(indentation: TomlIndentation) { + writeString(indentation.representation) + } + + final override fun writeLineFeed() { + writeChar('\n') + } +} diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Annotations.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Annotations.kt index 4ed10b0..7978cdc 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Annotations.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Annotations.kt @@ -18,6 +18,7 @@ package net.peanuuutz.tomlkt import kotlinx.serialization.InheritableSerialInfo import kotlinx.serialization.SerialInfo +import net.peanuuutz.tomlkt.TomlInteger.Base.Dec /** * Adds comments to corresponding property. @@ -80,8 +81,8 @@ public annotation class TomlInline /** * Modifies the encoding process of corresponding array-like property, either to * force array of tables to be encoded as block array, or to change how many - * items will be encoded per line (will override - * [TomlConfig][TomlConfigBuilder.itemsPerLineInBlockArray]). + * items will be encoded per line (this will override the default + * [config][TomlConfigBuilder.itemsPerLineInBlockArray]). * * Note: If the corresponding property is marked [TomlInline], this annotation * will not take effect. @@ -160,21 +161,31 @@ public annotation class TomlLiteralString * * ```kotlin * class ByteCode( - * @TomlInteger(TomlInteger.Base.Bin) - * val code: Byte + * @TomlInteger( + * base = TomlInteger.Base.Hex, + * group = 2 + * ) + * val code: Int * ) - * ByteCode(0b1101) + * ByteCode(0xFFE490) * ``` * * will produce: * * ```toml - * code = 0b1101 + * code = 0xFF_E4_90 * ``` + * + * @property group the size of a digit group separated by '_'. If set to 0, the + * digits will not be grouped. */ +@Suppress("OutdatedDocumentation") @SerialInfo @Target(AnnotationTarget.PROPERTY) -public annotation class TomlInteger(val base: Base) { +public annotation class TomlInteger( + val base: Base = Dec, + val group: Int = 0 +) { /** * The representation of a [TOML integer](https://toml.io/en/v1.0.0#integer). */ @@ -182,34 +193,34 @@ public annotation class TomlInteger(val base: Base) { public val value: Int, public val prefix: String ) { - Dec(10, ""), - Hex(16, "0x"), Bin(2, "0b"), Oct(8, "0o"), + Dec(10, ""), + Hex(16, "0x"), @Deprecated( message = "Unify singleton style.", - replaceWith = ReplaceWith("Dec") + replaceWith = ReplaceWith("Bin") ) - DEC(10, ""), + BIN(2, "0b"), @Deprecated( message = "Unify singleton style.", - replaceWith = ReplaceWith("Hex") + replaceWith = ReplaceWith("Oct") ) - HEX(16, "0x"), + OCT(8, "0o"), @Deprecated( message = "Unify singleton style.", - replaceWith = ReplaceWith("Bin") + replaceWith = ReplaceWith("Dec") ) - BIN(2, "0b"), + DEC(10, ""), @Deprecated( message = "Unify singleton style.", - replaceWith = ReplaceWith("Oct") + replaceWith = ReplaceWith("Hex") ) - OCT(8, "0o"); + HEX(16, "0x") } } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlConfig.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlConfig.kt index 1d4ee2a..4b6c929 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlConfig.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlConfig.kt @@ -28,7 +28,7 @@ public class TomlConfigBuilder @PublishedApi internal constructor(from: TomlConf // -------- Common -------- /** - * The [SerializersModule] to be used in the [Toml] instance. + * Specifies the [SerializersModule] to be used in the [Toml] instance. * * [EmptySerializersModule] by default. */ @@ -46,7 +46,8 @@ public class TomlConfigBuilder @PublishedApi internal constructor(from: TomlConf public var explicitNulls: Boolean = from.explicitNulls /** - * The key of the class discriminator for polymorphic serialization. + * Specifies the key of the class discriminator for polymorphic + * serialization. * * "type" by default. */ @@ -68,6 +69,14 @@ public class TomlConfigBuilder @PublishedApi internal constructor(from: TomlConf */ public var itemsPerLineInBlockArray: Int = from.itemsPerLineInBlockArray + /** + * Specifies whether the letters in an integer should be encoded as + * uppercase by default. + * + * `true` by default. + */ + public var uppercaseInteger: Boolean = from.uppercaseInteger + // -------- Deserialization -------- /** @@ -89,6 +98,7 @@ public class TomlConfigBuilder @PublishedApi internal constructor(from: TomlConf classDiscriminator = classDiscriminator, indentation = indentation, itemsPerLineInBlockArray = itemsPerLineInBlockArray, + uppercaseInteger = uppercaseInteger, ignoreUnknownKeys = ignoreUnknownKeys ) } @@ -139,6 +149,7 @@ internal class TomlConfig( val classDiscriminator: String, val indentation: TomlIndentation, val itemsPerLineInBlockArray: Int, + val uppercaseInteger: Boolean, val ignoreUnknownKeys: Boolean ) { companion object { @@ -148,6 +159,7 @@ internal class TomlConfig( classDiscriminator = "type", indentation = TomlIndentation.Space4, itemsPerLineInBlockArray = 1, + uppercaseInteger = true, ignoreUnknownKeys = false ) } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt index 9c6feac..c8766e5 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt @@ -27,7 +27,7 @@ package net.peanuuutz.tomlkt * * When finished, simply call [toString] to get the result. */ -public class TomlStringWriter : TomlWriter { +public class TomlStringWriter : AbstractTomlWriter() { private val builder: StringBuilder = StringBuilder() override fun writeString(string: String) { diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt index 66a6a33..0079442 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt @@ -16,114 +16,87 @@ package net.peanuuutz.tomlkt -import net.peanuuutz.tomlkt.internal.Comment -import net.peanuuutz.tomlkt.internal.ElementSeparator -import net.peanuuutz.tomlkt.internal.EndArray -import net.peanuuutz.tomlkt.internal.EndInlineTable -import net.peanuuutz.tomlkt.internal.EndTableHead -import net.peanuuutz.tomlkt.internal.KeyValueSeparator -import net.peanuuutz.tomlkt.internal.SegmentSeparator -import net.peanuuutz.tomlkt.internal.StartArray -import net.peanuuutz.tomlkt.internal.StartInlineTable -import net.peanuuutz.tomlkt.internal.StartTableHead -import net.peanuuutz.tomlkt.internal.doubleQuoted -import net.peanuuutz.tomlkt.internal.escape -import net.peanuuutz.tomlkt.internal.singleQuoted -import net.peanuuutz.tomlkt.internal.toStringModified +import net.peanuuutz.tomlkt.TomlInteger.Base +import net.peanuuutz.tomlkt.TomlInteger.Base.Dec /** * A custom writer used when encoding model class or [TomlElement]. * - * @see [TomlStringWriter] + * @see AbstractTomlWriter + * @see TomlStringWriter */ public interface TomlWriter { // -------- Core -------- public fun writeString(string: String) - public fun writeChar(char: Char) { - writeString(char.toString()) - } + public fun writeChar(char: Char) // -------- Key -------- - public fun writeSegmentSeparator() { - writeChar(SegmentSeparator) - } + public fun writeSegmentSeparator() - public fun writeBareSegment(segment: String) { - writeString(segment) - } + public fun writeBareSegment(segment: String) - public fun writeDoubleQuotedSegment(segment: String) { - writeString(segment.doubleQuoted) - } + public fun writeDoubleQuotedSegment(segment: String) - public fun writeSingleQuotedSegment(segment: String) { - writeString(segment.singleQuoted) - } + public fun writeSingleQuotedSegment(segment: String) // -------- Table Head -------- - public fun startRegularTableHead() { - writeChar(StartTableHead) - } + public fun startRegularTableHead() - public fun endRegularTableHead() { - writeChar(EndTableHead) - } + public fun endRegularTableHead() - public fun startArrayOfTableHead() { - writeChar(StartTableHead) - writeChar(StartTableHead) - } + public fun startArrayOfTableHead() - public fun endArrayOfTableHead() { - writeChar(EndTableHead) - writeChar(EndTableHead) - } + public fun endArrayOfTableHead() // -------- Value -------- - public fun writeBooleanValue(boolean: Boolean) { - writeString(boolean.toString()) - } + public fun writeBooleanValue(boolean: Boolean) - public fun writeByteValue(byte: Byte) { - writeString(byte.toString()) - } + public fun writeByteValue( + byte: Byte, + base: Base = Dec, + group: Int = 0, + uppercase: Boolean = true + ) - public fun writeShortValue(short: Short) { - writeString(short.toString()) - } + public fun writeShortValue( + short: Short, + base: Base = Dec, + group: Int = 0, + uppercase: Boolean = true + ) - public fun writeIntValue(int: Int) { - writeString(int.toString()) - } + public fun writeIntValue( + int: Int, + base: Base = Dec, + group: Int = 0, + uppercase: Boolean = true + ) - public fun writeLongValue(long: Long) { - writeString(long.toString()) - } + public fun writeLongValue( + long: Long, + base: Base = Dec, + group: Int = 0, + uppercase: Boolean = true + ) - public fun writeFloatValue(float: Float) { - writeString(float.toStringModified()) - } + public fun writeFloatValue(float: Float) - public fun writeDoubleValue(double: Double) { - writeString(double.toStringModified()) - } + public fun writeDoubleValue(double: Double) - public fun writeCharValue(char: Char) { - writeString(char.escape().doubleQuoted) - } + public fun writeCharValue(char: Char) - public fun writeStringValue(string: String) { - writeString(string.escape().doubleQuoted) - } + public fun writeStringValue( + string: String, + isMultiline: Boolean = false, + isLiteral: Boolean = false + ) - public fun writeNullValue() { - writeString("null") - } + public fun writeNullValue() @Deprecated( message = "Use writeBooleanValue instead.", @@ -191,47 +164,27 @@ public interface TomlWriter { // -------- Structure -------- - public fun startArray() { - writeChar(StartArray) - } + public fun startArray() - public fun endArray() { - writeChar(EndArray) - } + public fun endArray() - public fun startInlineTable() { - writeChar(StartInlineTable) - } + public fun startInlineTable() - public fun endInlineTable() { - writeChar(EndInlineTable) - } + public fun endInlineTable() - public fun writeKeyValueSeparator() { - writeChar(KeyValueSeparator) - } + public fun writeKeyValueSeparator() - public fun writeElementSeparator() { - writeChar(ElementSeparator) - } + public fun writeElementSeparator() // -------- Comment -------- - public fun startComment() { - writeChar(Comment) - } + public fun startComment() // -------- Control -------- - public fun writeSpace() { - writeChar(' ') - } + public fun writeSpace() - public fun writeIndentation(indentation: TomlIndentation) { - writeString(indentation.representation) - } + public fun writeIndentation(indentation: TomlIndentation) - public fun writeLineFeed() { - writeChar('\n') - } + public fun writeLineFeed() } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt index 7e3d76a..ff939a9 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt @@ -16,6 +16,8 @@ package net.peanuuutz.tomlkt.internal +import net.peanuuutz.tomlkt.TomlInteger.Base +import net.peanuuutz.tomlkt.TomlInteger.Base.Dec import kotlin.math.pow internal const val Comment = '#' @@ -193,6 +195,39 @@ internal fun Number.toStringModified(): String { } } +internal fun processIntegerString( + raw: String, + base: Base, + group: Int, + uppercase: Boolean +): String { + val isNegative = raw[0] == '-' + val digits = if (!isNegative) { + raw + } else { + raw.substring(1) + } + val upper = if (base <= Dec || !uppercase) { + digits + } else { + digits.uppercase() + } + val grouped = if (group == 0) { + upper + } else { + upper.reversed() + .chunked(group, CharSequence::reversed) + .asReversed() + .joinToString(separator = "_") + } + val result = if (!isNegative) { + base.prefix + grouped + } else { + "-" + base.prefix + grouped + } + return result +} + internal fun String.toNumber( positive: Boolean, radix: Int, diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/encoder/TomlFileEncoder.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/encoder/TomlFileEncoder.kt index fa12bd4..e99443a 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/encoder/TomlFileEncoder.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/encoder/TomlFileEncoder.kt @@ -33,8 +33,6 @@ import net.peanuuutz.tomlkt.TomlComment import net.peanuuutz.tomlkt.TomlElement import net.peanuuutz.tomlkt.TomlInline import net.peanuuutz.tomlkt.TomlInteger -import net.peanuuutz.tomlkt.TomlInteger.Base -import net.peanuuutz.tomlkt.TomlInteger.Base.Dec import net.peanuuutz.tomlkt.TomlLiteral import net.peanuuutz.tomlkt.TomlLiteralString import net.peanuuutz.tomlkt.TomlMultilineString @@ -51,7 +49,6 @@ import net.peanuuutz.tomlkt.internal.isExactlyTomlTable import net.peanuuutz.tomlkt.internal.isTable import net.peanuuutz.tomlkt.internal.isTableLike import net.peanuuutz.tomlkt.internal.isUnsignedInteger -import net.peanuuutz.tomlkt.internal.singleQuoted import net.peanuuutz.tomlkt.internal.throwEmptyArrayOfTableInMap import net.peanuuutz.tomlkt.internal.throwNullInArrayOfTable import net.peanuuutz.tomlkt.internal.throwPolymorphicCollection @@ -183,60 +180,52 @@ private class TomlFileInlineEncoder( private abstract class AbstractTomlFileCompositeEncoder( delegate: AbstractTomlFileEncoder ) : AbstractTomlFileEncoder(delegate.toml, delegate.writer), TomlCompositeEncoder { - private fun encodeByteWithBase(value: Byte, base: Base) { - require(value >= 0.toByte() || base == Dec) { - "Negative integer cannot be represented by other bases, but found $value" - } - writer.writeString(base.prefix) - writer.writeString(value.toString(base.value)) + private fun encodeByteWithRepresentation(value: Byte, integer: TomlInteger) { + writer.writeByteValue( + byte = value, + base = integer.base, + group = integer.group, + uppercase = toml.config.uppercaseInteger + ) } - private fun encodeShortWithBase(value: Short, base: Base) { - require(value >= 0.toShort() || base == Dec) { - "Negative integer cannot be represented by other bases, but found $value" - } - writer.writeString(base.prefix) - writer.writeString(value.toString(base.value)) + private fun encodeShortWithRepresentation(value: Short, integer: TomlInteger) { + writer.writeShortValue( + short = value, + base = integer.base, + group = integer.group, + uppercase = toml.config.uppercaseInteger + ) } - private fun encodeIntWithBase(value: Int, base: Base) { - require(value >= 0 || base == Dec) { - "Negative integer cannot be represented by other bases, but found $value" - } - writer.writeString(base.prefix) - writer.writeString(value.toString(base.value)) + private fun encodeIntWithRepresentation(value: Int, integer: TomlInteger) { + writer.writeIntValue( + int = value, + base = integer.base, + group = integer.group, + uppercase = toml.config.uppercaseInteger + ) } - private fun encodeLongWithBase(value: Long, base: Base) { - require(value >= 0L || base == Dec) { - "Negative integer cannot be represented by other bases, but found $value" - } - writer.writeString(base.prefix) - writer.writeString(value.toString(base.value)) + private fun encodeLongWithRepresentation(value: Long, integer: TomlInteger) { + writer.writeLongValue( + long = value, + base = integer.base, + group = integer.group, + uppercase = toml.config.uppercaseInteger + ) } private fun encodeMultilineString(value: String) { - writer.writeString("\"\"\"") - writer.writeLineFeed() - writer.writeString(value.escape(multiline = true)) - writer.writeString("\"\"\"") + writer.writeStringValue(value, isMultiline = true) } private fun encodeLiteralString(value: String) { - require('\'' !in value && '\n' !in value) { - "Cannot have '\\'' or '\\n' in literal string, but found $value" - } - writer.writeString(value.singleQuoted) + writer.writeStringValue(value, isLiteral = true) } private fun encodeMultilineLiteralString(value: String) { - require("\'\'\'" !in value) { - "Cannot have \"\\'\\'\\'\" in multiline literal string, but found $value" - } - writer.writeString("\'\'\'") - writer.writeLineFeed() - writer.writeString(value) - writer.writeString("\'\'\'") + writer.writeStringValue(value, isMultiline = true, isLiteral = true) } override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { @@ -255,35 +244,34 @@ private abstract class AbstractTomlFileCompositeEncoder( } final override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) { - encodeTomlIntegerElement(descriptor, index, value, this::encodeByte, this::encodeByteWithBase) + encodeTomlIntegerElement(descriptor, index, value, this::encodeByte, this::encodeByteWithRepresentation) } final override fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short) { - encodeTomlIntegerElement(descriptor, index, value, this::encodeShort, this::encodeShortWithBase) + encodeTomlIntegerElement(descriptor, index, value, this::encodeShort, this::encodeShortWithRepresentation) } final override fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int) { - encodeTomlIntegerElement(descriptor, index, value, this::encodeInt, this::encodeIntWithBase) + encodeTomlIntegerElement(descriptor, index, value, this::encodeInt, this::encodeIntWithRepresentation) } final override fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long) { - encodeTomlIntegerElement(descriptor, index, value, this::encodeLong, this::encodeLongWithBase) + encodeTomlIntegerElement(descriptor, index, value, this::encodeLong, this::encodeLongWithRepresentation) } private inline fun encodeTomlIntegerElement( descriptor: SerialDescriptor, index: Int, value: T, - encodeNormally: (T) -> Unit, - encodeWithBase: (T, Base) -> Unit + encodeRegularly: (T) -> Unit, + encodeWithRepresentation: (T, TomlInteger) -> Unit ) { encodeElement(descriptor, index) { - val integerAnnotation = descriptor.getElementAnnotations(index) - .firstIsInstanceOrNull() - if (integerAnnotation == null) { - encodeNormally(value) + val integer = descriptor.getElementAnnotations(index).firstIsInstanceOrNull() + if (integer == null) { + encodeRegularly(value) } else { - encodeWithBase(value, integerAnnotation.base) + encodeWithRepresentation(value, integer) } } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/IntegerTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/IntegerTest.kt index 76f8d00..de864a3 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/IntegerTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/IntegerTest.kt @@ -176,7 +176,7 @@ class IntegerTest { ) val s41 = """ - i = 0xffffff + i = 0xFFFFFF """.trimIndent() @Test @@ -320,7 +320,7 @@ class IntegerTest { ) val s91 = """ - l = 0xffffffff + l = 0xFFFFFFFF """.trimIndent() @@ -333,4 +333,36 @@ class IntegerTest { fun decodeLongAsHexadecimal() { testDecode(M9.serializer(), s91, m91) } + + @Serializable + data class M10( + @TomlInteger(TomlInteger.Base.Hex, 2) + val i: Int + ) + + val m101 = M10( + i = 0xFFE490 + ) + + val s101 = """ + i = 0xFF_E4_90 + """.trimIndent() + + @Test + fun encodeWithGrouping1() { + testEncode(M10.serializer(), m101, s101) + } + + val m102 = M10( + i = 0xFFFE490 + ) + + val s102 = """ + i = 0xF_FF_E4_90 + """.trimIndent() + + @Test + fun encodeWithGrouping2() { + testEncode(M10.serializer(), m102, s102) + } } diff --git a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlNativeWriter.kt b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlNativeWriter.kt index 937212d..21d253d 100644 --- a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlNativeWriter.kt +++ b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlNativeWriter.kt @@ -34,7 +34,7 @@ import java.io.Writer */ public class TomlNativeWriter( private val nativeWriter: Writer -) : TomlWriter { +) : AbstractTomlWriter() { override fun writeString(string: String) { nativeWriter.write(string) } diff --git a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt index 1159f4a..57aa97c 100644 --- a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt +++ b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt @@ -34,7 +34,7 @@ import java.io.OutputStream */ public class TomlStreamWriter( private val outputStream: OutputStream -) : TomlWriter { +) : AbstractTomlWriter() { override fun writeString(string: String) { outputStream.write(string.toByteArray()) } From edcc20791704772f1ffe6a22e15b783d0b191179 Mon Sep 17 00:00:00 2001 From: Peanuuutz <1415605913@qq.com> Date: Thu, 5 Oct 2023 18:18:20 +0800 Subject: [PATCH 3/3] 0.3.5 documentation (1/2). --- .../peanuuutz/tomlkt/AbstractTomlWriter.kt | 22 ++-- .../kotlin/net/peanuuutz/tomlkt/TomlWriter.kt | 121 +++++++++++++++++- .../peanuuutz/tomlkt/internal/StringUtils.kt | 2 +- .../internal/TomlSerializationExceptions.kt | 4 +- .../tomlkt/internal/parser/TomlFileParser.kt | 6 +- 5 files changed, 130 insertions(+), 25 deletions(-) diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/AbstractTomlWriter.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/AbstractTomlWriter.kt index 3aa1b7e..78f56ab 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/AbstractTomlWriter.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/AbstractTomlWriter.kt @@ -7,8 +7,8 @@ import net.peanuuutz.tomlkt.internal.ElementSeparator import net.peanuuutz.tomlkt.internal.EndArray import net.peanuuutz.tomlkt.internal.EndInlineTable import net.peanuuutz.tomlkt.internal.EndTableHead +import net.peanuuutz.tomlkt.internal.KeySeparator import net.peanuuutz.tomlkt.internal.KeyValueSeparator -import net.peanuuutz.tomlkt.internal.SegmentSeparator import net.peanuuutz.tomlkt.internal.StartArray import net.peanuuutz.tomlkt.internal.StartInlineTable import net.peanuuutz.tomlkt.internal.StartTableHead @@ -18,6 +18,10 @@ import net.peanuuutz.tomlkt.internal.processIntegerString import net.peanuuutz.tomlkt.internal.singleQuoted import net.peanuuutz.tomlkt.internal.toStringModified +/** + * The basic implementation of [TomlWriter], handling all the essential logic + * except [writeString] and (optional) [writeChar]. + */ public abstract class AbstractTomlWriter : TomlWriter { // -------- Core -------- @@ -28,20 +32,12 @@ public abstract class AbstractTomlWriter : TomlWriter { // -------- Key -------- - final override fun writeSegmentSeparator() { - writeChar(SegmentSeparator) + final override fun writeKey(key: String) { + writeString(key) } - final override fun writeBareSegment(segment: String) { - writeString(segment) - } - - final override fun writeDoubleQuotedSegment(segment: String) { - writeString(segment.doubleQuoted) - } - - final override fun writeSingleQuotedSegment(segment: String) { - writeString(segment.singleQuoted) + final override fun writeKeySeparator() { + writeChar(KeySeparator) } // -------- Table Head -------- diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt index 0079442..06f3704 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt @@ -28,34 +28,66 @@ import net.peanuuutz.tomlkt.TomlInteger.Base.Dec public interface TomlWriter { // -------- Core -------- + /** + * Writes [string] directly. + */ public fun writeString(string: String) + /** + * Writes [char] directly. + */ public fun writeChar(char: Char) // -------- Key -------- - public fun writeSegmentSeparator() + /** + * Writes a [key]. + * + * `key` is guaranteed to be escaped and quoted if needed. + */ + public fun writeKey(key: String) - public fun writeBareSegment(segment: String) - - public fun writeDoubleQuotedSegment(segment: String) - - public fun writeSingleQuotedSegment(segment: String) + /** + * Writes a key separator (no whitespace). + */ + public fun writeKeySeparator() // -------- Table Head -------- + /** + * Starts a regular table head (no whitespace). + */ public fun startRegularTableHead() + /** + * Ends a regular table head (no whitespace). + */ public fun endRegularTableHead() + /** + * Starts an array of table head (no whitespace). + */ public fun startArrayOfTableHead() + /** + * Ends an array of table head (no whitespace). + */ public fun endArrayOfTableHead() // -------- Value -------- + /** + * Writes [boolean] as value. + */ public fun writeBooleanValue(boolean: Boolean) + /** + * Writes [byte] as value. + * + * @param group the size of a digit group separated by '_'. If set to 0, + * the digits will not be grouped. + */ + @Suppress("OutdatedDocumentation") public fun writeByteValue( byte: Byte, base: Base = Dec, @@ -63,6 +95,13 @@ public interface TomlWriter { uppercase: Boolean = true ) + /** + * Writes [short] as value. + * + * @param group the size of a digit group separated by '_'. If set to 0, + * the digits will not be grouped. + */ + @Suppress("OutdatedDocumentation") public fun writeShortValue( short: Short, base: Base = Dec, @@ -70,6 +109,13 @@ public interface TomlWriter { uppercase: Boolean = true ) + /** + * Writes [int] as value. + * + * @param group the size of a digit group separated by '_'. If set to 0, + * the digits will not be grouped. + */ + @Suppress("OutdatedDocumentation") public fun writeIntValue( int: Int, base: Base = Dec, @@ -77,6 +123,13 @@ public interface TomlWriter { uppercase: Boolean = true ) + /** + * Writes [long] as value. + * + * @param group the size of a digit group separated by '_'. If set to 0, + * the digits will not be grouped. + */ + @Suppress("OutdatedDocumentation") public fun writeLongValue( long: Long, base: Base = Dec, @@ -84,18 +137,44 @@ public interface TomlWriter { uppercase: Boolean = true ) + /** + * Writes [float] as value. + * + * Implementation should handle special values like [NaN][Float.NaN], + * [INFINITY][Float.POSITIVE_INFINITY] properly. + */ public fun writeFloatValue(float: Float) + /** + * Writes [double] as value. + * + * Implementation should handle special values like [NaN][Double.NaN], + * [INFINITY][Double.POSITIVE_INFINITY] properly. + */ public fun writeDoubleValue(double: Double) + /** + * Writes [char] **as value**. + * + * Unlike [writeChar], implementation should escape and quote `char`. + */ public fun writeCharValue(char: Char) + /** + * Writes [string] **as value**. + * + * Unlike [writeString], implementation should escape and quote `string` + * properly depending on [isMultiline] and [isLiteral]. + */ public fun writeStringValue( string: String, isMultiline: Boolean = false, isLiteral: Boolean = false ) + /** + * Writes `null` as value. + */ public fun writeNullValue() @Deprecated( @@ -164,27 +243,57 @@ public interface TomlWriter { // -------- Structure -------- + /** + * Starts a block array or inline array (no whitespace). + */ public fun startArray() + /** + * Ends a block array or inline array (no whitespace). + */ public fun endArray() + /** + * Starts an inline table (no whitespace). + */ public fun startInlineTable() + /** + * Ends an inline table (no whitespace). + */ public fun endInlineTable() + /** + * Writes a key-value separator (no whitespace). + */ public fun writeKeyValueSeparator() + /** + * Writes an element separator (no whitespace). + */ public fun writeElementSeparator() // -------- Comment -------- + /** + * Starts a comment (no whitespace). + */ public fun startComment() // -------- Control -------- + /** + * Writes a whitespace. + */ public fun writeSpace() + /** + * Writes [indentation]. + */ public fun writeIndentation(indentation: TomlIndentation) + /** + * Writes a line feed. + */ public fun writeLineFeed() } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt index ff939a9..d60cc24 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt @@ -22,7 +22,7 @@ import kotlin.math.pow internal const val Comment = '#' -internal const val SegmentSeparator = '.' +internal const val KeySeparator = '.' internal const val KeyValueSeparator = '=' diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlSerializationExceptions.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlSerializationExceptions.kt index 03f8d7c..39942db 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlSerializationExceptions.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlSerializationExceptions.kt @@ -110,8 +110,8 @@ internal fun throwIncomplete(line: Int): Nothing { internal class ConflictEntryException(message: String) : TomlDecodingException(message) internal fun throwConflictEntry(path: Path): Nothing { - val message = path.joinToString(".") { segment -> - segment.escape().doubleQuotedIfNotPure() + val message = path.joinToString(".") { key -> + key.escape().doubleQuotedIfNotPure() } throw ConflictEntryException(message) } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt index ecc4512..4baee0e 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt @@ -38,8 +38,8 @@ import net.peanuuutz.tomlkt.internal.EndArray import net.peanuuutz.tomlkt.internal.EndInlineTable import net.peanuuutz.tomlkt.internal.EndTableHead import net.peanuuutz.tomlkt.internal.HexadecimalConstraints +import net.peanuuutz.tomlkt.internal.KeySeparator import net.peanuuutz.tomlkt.internal.KeyValueSeparator -import net.peanuuutz.tomlkt.internal.SegmentSeparator import net.peanuuutz.tomlkt.internal.StartArray import net.peanuuutz.tomlkt.internal.StartInlineTable import net.peanuuutz.tomlkt.internal.StartTableHead @@ -262,7 +262,7 @@ internal class TomlFileParser( path.add(parseLiteralStringKey()) expectKey = false } - SegmentSeparator -> { + KeySeparator -> { throwUnexpectedTokenIf(currentChar) { expectKey } expectKey = true proceed() @@ -289,7 +289,7 @@ internal class TomlFileParser( val builder = StringBuilder() while (!isEof) { when (val currentChar = getChar()) { - ' ', '\t', SegmentSeparator, KeyValueSeparator, EndArray -> { + ' ', '\t', KeySeparator, KeyValueSeparator, EndArray -> { break } '\n' -> {