This repository was archived by the owner on Apr 4, 2024. It is now read-only.
forked from diffplug/selfie
-
Notifications
You must be signed in to change notification settings - Fork 1
This repository was archived by the owner on Apr 4, 2024. It is now read-only.
Port PerCharacterEscaper #21
Copy link
Copy link
Closed
Description
- Take the next step in Epic:
.ssfile read/write #7.
The goal is to implement this interface
selfie-python-wip/jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/PerCharacterEscaper.kt
Lines 23 to 31 in 757e0fe
| /** | |
| * If your escape policy is `'123`, it means this: <br> | |
| * | |
| * ``` | |
| * abc->abc | |
| * 123->'1'2'3 | |
| * I won't->I won''t | |
| * ``` | |
| */ |
Using this code
selfie-python-wip/jvm/selfie-lib/src/jvmMain/kotlin/com/diffplug/selfie/PerCharacterEscaper.jvm.kt
Lines 18 to 153 in 757e0fe
| /** | |
| * If your escape policy is "'123", it means this: | |
| * ``` | |
| * abc->abc | |
| * 123->'1'2'3 | |
| * I won't->I won''t | |
| * ``` | |
| */ | |
| actual class PerCharacterEscaper | |
| /** | |
| * The first character in the string will be uses as the escape character, and all characters will | |
| * be escaped. | |
| */ | |
| private constructor( | |
| private val escapeCodePoint: Int, | |
| private val escapedCodePoints: IntArray, | |
| private val escapedByCodePoints: IntArray | |
| ) { | |
| private fun firstOffsetNeedingEscape(input: String): Int { | |
| val length = input.length | |
| var firstOffsetNeedingEscape = -1 | |
| var offset = 0 | |
| outer@ while (offset < length) { | |
| val codepoint = input.codePointAt(offset) | |
| for (escaped in escapedCodePoints) { | |
| if (codepoint == escaped) { | |
| firstOffsetNeedingEscape = offset | |
| break@outer | |
| } | |
| } | |
| offset += Character.charCount(codepoint) | |
| } | |
| return firstOffsetNeedingEscape | |
| } | |
| actual fun escape(input: String): String { | |
| val noEscapes = firstOffsetNeedingEscape(input) | |
| return if (noEscapes == -1) { | |
| input | |
| } else { | |
| val length = input.length | |
| val needsEscapes = length - noEscapes | |
| val builder = StringBuilder(noEscapes + 4 + needsEscapes * 5 / 4) | |
| builder.append(input, 0, noEscapes) | |
| var offset = noEscapes | |
| while (offset < length) { | |
| val codepoint = input.codePointAt(offset) | |
| offset += Character.charCount(codepoint) | |
| val idx = indexOf(escapedCodePoints, codepoint) | |
| if (idx == -1) { | |
| builder.appendCodePoint(codepoint) | |
| } else { | |
| builder.appendCodePoint(escapeCodePoint) | |
| builder.appendCodePoint(escapedByCodePoints[idx]) | |
| } | |
| } | |
| builder.toString() | |
| } | |
| } | |
| private fun firstOffsetNeedingUnescape(input: String): Int { | |
| val length = input.length | |
| var firstOffsetNeedingEscape = -1 | |
| var offset = 0 | |
| while (offset < length) { | |
| val codepoint = input.codePointAt(offset) | |
| if (codepoint == escapeCodePoint) { | |
| firstOffsetNeedingEscape = offset | |
| break | |
| } | |
| offset += Character.charCount(codepoint) | |
| } | |
| return firstOffsetNeedingEscape | |
| } | |
| actual fun unescape(input: String): String { | |
| val noEscapes = firstOffsetNeedingUnescape(input) | |
| return if (noEscapes == -1) { | |
| input | |
| } else { | |
| val length = input.length | |
| val needsEscapes = length - noEscapes | |
| val builder = StringBuilder(noEscapes + 4 + needsEscapes * 5 / 4) | |
| builder.append(input, 0, noEscapes) | |
| var offset = noEscapes | |
| while (offset < length) { | |
| var codepoint = input.codePointAt(offset) | |
| offset += Character.charCount(codepoint) | |
| // if we need to escape something, escape it | |
| if (codepoint == escapeCodePoint) { | |
| if (offset < length) { | |
| codepoint = input.codePointAt(offset) | |
| val idx = indexOf(escapedByCodePoints, codepoint) | |
| if (idx != -1) { | |
| codepoint = escapedCodePoints[idx] | |
| } | |
| offset += Character.charCount(codepoint) | |
| } else { | |
| throw IllegalArgumentException( | |
| "Escape character '" + | |
| String(intArrayOf(escapeCodePoint), 0, 1) + | |
| "' can't be the last character in a string.") | |
| } | |
| } | |
| // we didn't escape it, append it raw | |
| builder.appendCodePoint(codepoint) | |
| } | |
| builder.toString() | |
| } | |
| } | |
| actual companion object { | |
| private fun indexOf(arr: IntArray, target: Int): Int { | |
| for ((index, value) in arr.withIndex()) { | |
| if (value == target) { | |
| return index | |
| } | |
| } | |
| return -1 | |
| } | |
| actual fun selfEscape(escapePolicy: String): PerCharacterEscaper { | |
| val escapedCodePoints = escapePolicy.codePoints().toArray() | |
| val escapeCodePoint = escapedCodePoints[0] | |
| return PerCharacterEscaper(escapeCodePoint, escapedCodePoints, escapedCodePoints) | |
| } | |
| actual fun specifiedEscape(escapePolicy: String): PerCharacterEscaper { | |
| val codePoints = escapePolicy.codePoints().toArray() | |
| require(codePoints.size % 2 == 0) | |
| val escapeCodePoint = codePoints[0] | |
| val escapedCodePoints = IntArray(codePoints.size / 2) | |
| val escapedByCodePoints = IntArray(codePoints.size / 2) | |
| for (i in escapedCodePoints.indices) { | |
| escapedCodePoints[i] = codePoints[2 * i] | |
| escapedByCodePoints[i] = codePoints[2 * i + 1] | |
| } | |
| return PerCharacterEscaper(escapeCodePoint, escapedCodePoints, escapedByCodePoints) | |
| } | |
| } | |
| } |
Metadata
Metadata
Assignees
Labels
No labels