diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Slice.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Slice.kt index 2b00b6e3..47c4f726 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Slice.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Slice.kt @@ -15,21 +15,9 @@ */ package com.diffplug.selfie -import kotlin.jvm.JvmStatic -import kotlin.math.min - -/** - * A [CharSequence] which can efficiently subdivide and append itself. - * - * Equal only to other [Slice] with the same [Slice.toString]. Use [Slice.sameAs] to compare with - * other kinds of [CharSequence]. - */ -internal expect fun groupImpl(slice: Slice, matchResult: MatchResult, group: Int): Slice - -internal class Slice -private constructor(val base: CharSequence, val startIndex: Int, val endIndex: Int) : CharSequence { +internal class Slice(val base: String, val startIndex: Int = 0, val endIndex: Int = base.length) : + CharSequence { init { - require(base is StringBuilder || base is String) require(0 <= startIndex) require(startIndex <= endIndex) require(endIndex <= base.length) @@ -37,12 +25,8 @@ private constructor(val base: CharSequence, val startIndex: Int, val endIndex: I override val length: Int get() = endIndex - startIndex override fun get(index: Int): Char = base[startIndex + index] - override fun subSequence(start: Int, end: Int): Slice { - return Slice(base, startIndex + start, startIndex + end) - } - - /** Returns a Slice representing the given group within the given match */ - fun group(matchResult: MatchResult, group: Int): Slice = groupImpl(this, matchResult, group) + override fun subSequence(start: Int, end: Int): Slice = + Slice(base, startIndex + start, startIndex + end) /** Same behavior as [String.trim]. */ fun trim(): Slice { @@ -57,89 +41,6 @@ private constructor(val base: CharSequence, val startIndex: Int, val endIndex: I return if (start > 0 || end < length) subSequence(start, end) else this } override fun toString() = base.subSequence(startIndex, endIndex).toString() - fun concat(other: Slice): Slice = - if (this.isEmpty()) { - other - } else if (other.isEmpty()) { - this - } else if (base === other.base && endIndex == other.startIndex) { - Slice(base, startIndex, other.endIndex) - } else { - val builder: StringBuilder - val start: Int - val end: Int - if (base is StringBuilder && endIndex == base.length) { - builder = base - start = startIndex - end = endIndex + other.length - } else { - builder = StringBuilder(length + other.length) - builder.append(this) - start = 0 - end = length + other.length - } - other.appendThisTo(builder) - Slice(builder, start, end) - } - - /** append(this) but taking advantage of fastpath where possible */ - private fun appendThisTo(builder: StringBuilder) { - if (startIndex == 0 && endIndex == base.length) { - // there is a fastpath for adding a full string and for adding a full StringBuilder - if (base is String) { - builder.append(base) - } else { - builder.append(base as StringBuilder) - } - } else { - builder.append(this) - } - } - fun concat(other: String): Slice { - if (base is String && endIndex + other.length <= base.length) { - for (i in other.indices) { - if (base[i + endIndex] != other[i]) { - return concat(of(other)) - } - } - return Slice(base, startIndex, endIndex + other.length) - } - return concat(of(other)) - } - fun concatAnchored(other: Slice): Slice { - val result = concat(other) - if (result.base !== base) { - throw concatRootFailure(other) - } - return result - } - fun concatAnchored(other: String): Slice { - val result = concat(other) - if (result.base !== base) { - throw concatRootFailure(other) - } - return result - } - private fun concatRootFailure(other: CharSequence): IllegalArgumentException { - val maxChange = min(other.length, base.length - endIndex) - if (maxChange == 0) { - return IllegalArgumentException( - "Could not perform anchored concat because we are already at the end of the root ${visualize(base)}") - } - var firstChange = 0 - while (firstChange < maxChange) { - if (base[endIndex + firstChange] != other[firstChange]) { - break - } - ++firstChange - } - return IllegalArgumentException( - """ - This ends with '${visualize(base.subSequence(endIndex, endIndex + firstChange + 1))}' - cannot concat '${visualize(other.subSequence(firstChange, firstChange + 1))} - """ - .trimIndent()) - } fun sameAs(other: CharSequence): Boolean { if (length != other.length) { return false @@ -151,39 +52,12 @@ private constructor(val base: CharSequence, val startIndex: Int, val endIndex: I } return true } - fun startsWith(prefix: CharSequence): Boolean { - if (length < prefix.length) { - return false - } - for (i in 0 until prefix.length) { - if (get(i) != prefix[i]) { - return false - } - } - return true - } - fun endsWith(suffix: CharSequence): Boolean { - if (length < suffix.length) { - return false - } - val offset = length - suffix.length - for (i in 0 until suffix.length) { - if (get(i + offset) != suffix[i]) { - return false - } - } - return true - } fun indexOf(lookingFor: String, startOffset: Int = 0): Int { - val result = - if (base is String) base.indexOf(lookingFor, startIndex + startOffset) - else (base as StringBuilder).indexOf(lookingFor, startIndex + startOffset) + val result = base.indexOf(lookingFor, startIndex + startOffset) return if (result == -1 || result >= endIndex) -1 else result - startIndex } fun indexOf(lookingFor: Char, startOffset: Int = 0): Int { - val result = - if (base is String) base.indexOf(lookingFor, startIndex + startOffset) - else (base as StringBuilder).indexOf(lookingFor, startIndex + startOffset) + val result = base.indexOf(lookingFor, startIndex + startOffset) return if (result == -1 || result >= endIndex) -1 else result - startIndex } /** Returns a slice at the nth line. Handy for expanding the slice from there. */ @@ -195,75 +69,21 @@ private constructor(val base: CharSequence, val startIndex: Int, val endIndex: I require(lineStart >= 0) { "This string has only ${i - 1} lines, not $count" } ++lineStart } - var lineEnd = indexOf('\n', lineStart) + val lineEnd = indexOf('\n', lineStart) return if (lineEnd == -1) { Slice(base, startIndex + lineStart, endIndex) } else { Slice(base, startIndex + lineStart, startIndex + lineEnd) } } - - /** - * Returns a Slice which represents everything from the start of this string until `lookingFor` is - * found. If the string is never found, returns this. - */ - fun until(lookingFor: String): Slice { - val idx = indexOf(lookingFor) - return if (idx == -1) this else subSequence(0, idx) - } - - /** - * Asserts that the other string was generated from a call to [.until], and then returns a new - * Slice representing everything after that. - */ - fun after(other: Slice): Slice { - if (other.isEmpty()) { - return this - } - require(other.base === base && other.startIndex == startIndex && other.endIndex <= endIndex) { - "'${visualize(other)}' was not generated by `until` on '${visualize(this)}'" - } - return Slice(base, other.endIndex, endIndex) - } - - /** - * Returns the line number of the start of this string. Throws an exception if this isn't based on - * a string any longer, because non-contiguous StringPools have been concatenated. - */ - fun baseLineNumberStart(): Int { - return baseLineNumberOfOffset(startIndex) - } - - /** - * Returns the line number of the end of this string. Throws an exception if this isn't based on a - * string any longer, because non-contiguous Slices have been concatenated. - */ - fun baseLineNumberEnd(): Int { - return baseLineNumberOfOffset(endIndex) - } - private fun baseLineNumberOfOffset(idx: Int): Int { - assertStringBased() - var lineNumber = 1 - for (i in 0 until base.length) { - if (base[i] == '\n') { - ++lineNumber + override fun equals(other: Any?) = + if (this === other) { + true + } else if (other is Slice) { + sameAs(other) + } else { + false } - } - return lineNumber - } - private fun assertStringBased() { - check(base is String) { - "When you call concat on non-contiguous parts, you lose the connection to the original String." - } - } - override fun equals(anObject: Any?): Boolean { - if (this === anObject) { - return true - } else if (anObject is Slice) { - return sameAs(anObject) - } - return false - } override fun hashCode(): Int { var h = 0 for (i in indices) { @@ -271,14 +91,8 @@ private constructor(val base: CharSequence, val startIndex: Int, val endIndex: I } return h } - fun endsAtSamePlace(other: Slice): Boolean { - check(base === other.base) - return endIndex == other.endIndex - } - /** Returns the underlying string with this slice replaced by the given string. */ fun replaceSelfWith(s: String): String { - check(base is String) val deltaLength = s.length - length val builder = StringBuilder(base.length + deltaLength) builder.appendRange(base, 0, startIndex) @@ -286,39 +100,4 @@ private constructor(val base: CharSequence, val startIndex: Int, val endIndex: I builder.appendRange(base, endIndex, base.length) return builder.toString() } - - companion object { - @JvmStatic - fun of(base: String, startIndex: Int = 0, endIndex: Int = base.length): Slice { - return Slice(base, startIndex, endIndex) - } - fun concatAll(vararg poolStringsOrStrings: CharSequence): Slice { - if (poolStringsOrStrings.isEmpty()) { - return empty() - } - var total = asPool(poolStringsOrStrings[0]) - for (i in 1 until poolStringsOrStrings.size) { - val next = poolStringsOrStrings[i] - total = if (next is String) total.concat(next) else total.concat(next as Slice) - } - return total - } - private fun visualize(input: CharSequence): String { - return input - .toString() - .replace("\n", "␊") - .replace("\r", "␍") - .replace(" ", "·") - .replace("\t", "»") - } - private fun asPool(sequence: CharSequence): Slice { - return if (sequence is Slice) sequence else of(sequence as String) - } - - /** Returns the empty Slice. */ - @JvmStatic - fun empty(): Slice { - return Slice("", 0, 0) - } - } } diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SourceFile.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SourceFile.kt index 44a25519..91c777fb 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SourceFile.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SourceFile.kt @@ -22,7 +22,7 @@ package com.diffplug.selfie */ class SourceFile(val filename: String, content: String) { private val unixNewlines = content.indexOf('\r') == -1 - private var contentSlice = Slice.of(content.efficientReplace("\r\n", "\n")) + private var contentSlice = Slice(content.efficientReplace("\r\n", "\n")) /** * Returns the content of the file, possibly modified by * [ToBeLiteral.setLiteralAndGetNewlineDelta]. @@ -44,7 +44,7 @@ class SourceFile(val filename: String, content: String) { val encoded = literalValue.format.encode(literalValue.actual) val existingNewlines = slice.count { it == '\n' } val newNewlines = encoded.count { it == '\n' } - contentSlice = Slice.of(slice.replaceSelfWith(".toBe($encoded)")) + contentSlice = Slice(slice.replaceSelfWith(".toBe($encoded)")) return newNewlines - existingNewlines } diff --git a/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/SliceTest.kt b/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/SliceTest.kt index 4ffef5d8..a54a8c8e 100644 --- a/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/SliceTest.kt +++ b/selfie-lib/src/commonTest/kotlin/com/diffplug/selfie/SliceTest.kt @@ -19,27 +19,10 @@ import io.kotest.matchers.shouldBe import kotlin.test.Test class SliceTest { - @Test - fun afterTest() { - val abcdef = Slice.of("abcdef") - val untilA = abcdef.until("a") - untilA.toString() shouldBe "" - abcdef.after(untilA).toString() shouldBe "abcdef" - val untilC = abcdef.until("c") - untilC.toString() shouldBe "ab" - abcdef.after(untilC).toString() shouldBe "cdef" - val untilF = abcdef.until("f") - untilF.toString() shouldBe "abcde" - abcdef.after(untilF).toString() shouldBe "f" - val untilZ = abcdef.until("z") - untilZ.toString() shouldBe "abcdef" - abcdef.after(untilZ).toString() shouldBe "" - } - @Test fun unixLine() { - Slice.of("A single line").unixLine(1).toString() shouldBe "A single line" - val oneTwoThree = Slice.of("\nI am the first\nI, the second\n\nFOURTH\n") + Slice("A single line").unixLine(1).toString() shouldBe "A single line" + val oneTwoThree = Slice("\nI am the first\nI, the second\n\nFOURTH\n") oneTwoThree.unixLine(1).toString() shouldBe "" oneTwoThree.unixLine(2).toString() shouldBe "I am the first" oneTwoThree.unixLine(3).toString() shouldBe "I, the second" diff --git a/selfie-lib/src/jsMain/kotlin/com/diffplug/selfie/Slice.js.kt b/selfie-lib/src/jsMain/kotlin/com/diffplug/selfie/Slice.js.kt deleted file mode 100644 index faf65040..00000000 --- a/selfie-lib/src/jsMain/kotlin/com/diffplug/selfie/Slice.js.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2023 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.selfie - -/** - * A CharSequence which can efficiently subdivide and append itself. - * - * Equal only to other PoolString with the same `toString()`. Use [.sameAs] to compare with other - * kinds of [CharSequence]. - * - * Would be cool to have PoolString.Root which differentiates the String-based ones from - * StringBuilder-based ones. - */ -actual fun groupImpl(slice: Slice, matchResult: MatchResult, group: Int): Slice { - TODO("Not yet implemented") -} diff --git a/selfie-lib/src/jvmMain/kotlin/com/diffplug/selfie/Slice.jvm.kt b/selfie-lib/src/jvmMain/kotlin/com/diffplug/selfie/Slice.jvm.kt deleted file mode 100644 index e3b421fd..00000000 --- a/selfie-lib/src/jvmMain/kotlin/com/diffplug/selfie/Slice.jvm.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2023 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.selfie - -/** - * A CharSequence which can efficiently subdivide and append itself. - * - * Equal only to other PoolString with the same `toString()`. Use [.sameAs] to compare with other - * kinds of [CharSequence]. - * - * Would be cool to have PoolString.Root which differentiates the String-based ones from - * StringBuilder-based ones. - */ -internal actual fun groupImpl(slice: Slice, matchResult: MatchResult, group: Int): Slice { - val group = matchResult.groups[group]!! - return slice.subSequence(group.range.start, group.range.endInclusive - 1) -}