working ByteArray64Reader (UTF-8 compliant with proper surrogate pairing)

This commit is contained in:
minjaesong
2021-08-31 11:27:40 +09:00
parent 909c381572
commit 32144fc241
8 changed files with 147 additions and 41 deletions

View File

@@ -26,7 +26,7 @@ object Load : ConsoleCommand {
val disk = VDUtil.readDiskArchive(file, charset = charset) val disk = VDUtil.readDiskArchive(file, charset = charset)
val metaFile = VDUtil.getFile(disk, VDUtil.VDPath("savegame", charset))!! val metaFile = VDUtil.getFile(disk, VDUtil.VDPath("savegame", charset))!!
val metaReader = ByteArray64Reader(metaFile.contents.serialize().array) val metaReader = ByteArray64Reader(metaFile.contents.serialize().array, charset)
val meta = Common.jsoner.fromJson(JsonValue::class.java, metaReader) val meta = Common.jsoner.fromJson(JsonValue::class.java, metaReader)
} }

View File

@@ -0,0 +1,33 @@
package net.torvald.terrarum.modulebasegame.console
import net.torvald.terrarum.console.ConsoleCommand
import net.torvald.terrarum.console.Echo
import net.torvald.terrarum.serialise.ByteArray64Reader
import net.torvald.terrarum.serialise.ByteArray64Writer
import java.io.File
/**
* Created by minjaesong on 2021-08-31.
*/
object ReaderTest : ConsoleCommand {
override fun execute(args: Array<String>) {
val textfile = File("./work_files/utftest.txt")
val text = textfile.readText()
val writer = ByteArray64Writer(Charsets.UTF_8)
writer.write(text); writer.flush(); writer.close()
val ba = writer.toByteArray64()
val reader = ByteArray64Reader(ba, Charsets.UTF_8)
val readText = reader.readText(); reader.close()
println(readText)
val outfile = File("./work_files/utftest-roundtrip.txt")
outfile.writeText(readText, Charsets.UTF_8)
}
override fun printUsage() {
Echo("Usage: readertest")
}
}

View File

@@ -3,6 +3,7 @@ package net.torvald.terrarum.serialise
import com.badlogic.gdx.utils.Json import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonValue import com.badlogic.gdx.utils.JsonValue
import com.badlogic.gdx.utils.JsonWriter import com.badlogic.gdx.utils.JsonWriter
import net.torvald.terrarum.AppLoader.printdbg
import net.torvald.terrarum.console.EchoError import net.torvald.terrarum.console.EchoError
import net.torvald.terrarum.gameworld.BlockLayer import net.torvald.terrarum.gameworld.BlockLayer
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
@@ -10,17 +11,14 @@ import net.torvald.terrarum.gameworld.WorldTime
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64InputStream import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64InputStream
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64OutputStream
import net.torvald.terrarum.tail import net.torvald.terrarum.tail
import net.torvald.terrarum.utils.* import net.torvald.terrarum.utils.*
import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.codec.digest.DigestUtils
import java.io.Reader import java.io.Reader
import java.io.Writer import java.io.Writer
import java.math.BigInteger import java.math.BigInteger
import java.nio.CharBuffer
import java.nio.channels.ClosedChannelException import java.nio.channels.ClosedChannelException
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.charset.CharsetDecoder
import java.nio.charset.UnsupportedCharsetException import java.nio.charset.UnsupportedCharsetException
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
@@ -273,7 +271,7 @@ object Common {
} }
} }
class ByteArray64Writer() : Writer() { class ByteArray64Writer(val charset: Charset) : Writer() {
private var closed = false private var closed = false
private val ba64 = ByteArray64() private val ba64 = ByteArray64()
@@ -288,7 +286,7 @@ class ByteArray64Writer() : Writer() {
override fun write(c: Int) { override fun write(c: Int) {
checkOpen() checkOpen()
"${c.toChar()}".toByteArray().forEach { ba64.add(it) } "${c.toChar()}".toByteArray(charset).forEach { ba64.add(it) }
} }
override fun write(cbuf: CharArray) { override fun write(cbuf: CharArray) {
@@ -298,7 +296,7 @@ class ByteArray64Writer() : Writer() {
override fun write(str: String) { override fun write(str: String) {
checkOpen() checkOpen()
str.toByteArray().forEach { ba64.add(it) } str.toByteArray(charset).forEach { ba64.add(it) }
} }
override fun write(cbuf: CharArray, off: Int, len: Int) { override fun write(cbuf: CharArray, off: Int, len: Int) {
@@ -334,7 +332,7 @@ class ByteArray64Reader(val ba: ByteArray64, val charset: Charset) : Reader() {
* U+0800 .. U+FFFF 1110xxxx 10xxxxxx 10xxxxxx * U+0800 .. U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
* U+10000 .. U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx * U+10000 .. U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
*/ */
private fun utf8GetBytes(head: Byte) = when (head.toInt() and 255) { private fun utf8GetCharLen(head: Byte) = when (head.toInt() and 255) {
in 0b11110_000..0b11110_111 -> 4 in 0b11110_000..0b11110_111 -> 4
in 0b1110_0000..0b1110_1111 -> 3 in 0b1110_0000..0b1110_1111 -> 3
in 0b110_00000..0b110_11111 -> 2 in 0b110_00000..0b110_11111 -> 2
@@ -343,52 +341,73 @@ class ByteArray64Reader(val ba: ByteArray64, val charset: Charset) : Reader() {
} }
/** /**
* @param list of bytes that encodes one unicode character. Get required byte length using [utf8GetBytes]. * @param list of bytes that encodes one unicode character. Get required byte length using [utf8GetCharLen].
* @return A codepoint of the character. * @return A codepoint of the character.
*/ */
private fun utf8decode(bytes0: List<Byte>): Int { private fun utf8decode(bytes0: List<Byte>): Int {
val bytes = bytes0.map { it.toInt() and 255 } val bytes = bytes0.map { it.toInt() and 255 }
var ret = when (bytes.size) { var ret = when (bytes.size) {
4 -> (bytes[0] and 7) shl 15 4 -> (bytes[0] and 7) shl 18
3 -> (bytes[0] and 15) shl 10 3 -> (bytes[0] and 15) shl 12
2 -> (bytes[0] and 31) shl 5 2 -> (bytes[0] and 31) shl 6
1 -> (bytes[0] and 127) 1 -> (bytes[0] and 127)
else -> throw IllegalArgumentException("Expected bytes size: 1..4, got ${bytes.size}") else -> throw IllegalArgumentException("Expected bytes size: 1..4, got ${bytes.size}")
} }
bytes.tail().forEachIndexed { index, byte -> bytes.tail().reversed().forEachIndexed { index, byte ->
ret = ret or (byte and 63).shl(5 * (2 - index)) ret = ret or (byte and 63).shl(6 * index)
} }
return ret return ret
} }
private var surrogateLeftover = ' '
override fun read(cbuf: CharArray, off: Int, len: Int): Int { override fun read(cbuf: CharArray, off: Int, len: Int): Int {
var readCount = 0 var readCount = 0
when (charset) { when (charset) {
Charsets.UTF_8 -> { Charsets.UTF_8 -> {
while (readCount < len && remaining > 0) { while (readCount < len && remaining > 0) {
val bbuf = (0..minOf(3L, remaining)).map { ba[readCursor + it] } if (surrogateLeftover != ' ') {
val codePoint = utf8decode(bbuf.subList(0, utf8GetBytes(bbuf[0]))) cbuf[off + readCount] = surrogateLeftover
if (codePoint < 65536) {
cbuf[off + readCount] = codePoint.toChar()
readCount += 1 readCount += 1
readCursor += bbuf.size surrogateLeftover = ' '
} }
else { else {
/* val bbuf = (0 until minOf(4L, remaining)).map { ba[readCursor + it] }
val charLen = utf8GetCharLen(bbuf[0])
val codePoint = utf8decode(bbuf.subList(0, charLen))
if (codePoint < 65536) {
cbuf[off + readCount] = codePoint.toChar()
readCount += 1
readCursor += charLen
}
else {
/*
* U' = yyyyyyyyyyxxxxxxxxxx // U - 0x10000 * U' = yyyyyyyyyyxxxxxxxxxx // U - 0x10000
* W1 = 110110yyyyyyyyyy // 0xD800 + yyyyyyyyyy * W1 = 110110yyyyyyyyyy // 0xD800 + yyyyyyyyyy
* W2 = 110111xxxxxxxxxx // 0xDC00 + xxxxxxxxxx * W2 = 110111xxxxxxxxxx // 0xDC00 + xxxxxxxxxx
*/ */
val surroLead = (0xD800 or codePoint.ushr(10)).toChar() val codPoin = codePoint - 65536
val surroTrail = (0xDC00 or codePoint.and(1023)).toChar() val surroLead = (0xD800 or codPoin.ushr(10)).toChar()
val surroTrail = (0xDC00 or codPoin.and(1023)).toChar()
cbuf[off + readCount] = surroLead cbuf[off + readCount] = surroLead
cbuf[off + readCount + 1] = surroTrail
readCount += 2 if (off + readCount + 1 < cbuf.size) {
readCursor + 4 cbuf[off + readCount + 1] = surroTrail
readCount += 2
readCursor += 4
}
else {
readCount += 1
readCursor += 4
surrogateLeftover = surroTrail
}
}
} }
} }
} }
@@ -402,7 +421,7 @@ class ByteArray64Reader(val ba: ByteArray64, val charset: Charset) : Reader() {
else -> throw UnsupportedCharsetException(charset.name()) else -> throw UnsupportedCharsetException(charset.name())
} }
return readCount return if (readCount == 0) -1 else readCount
} }
override fun close() { readCursor = 0L } override fun close() { readCursor = 0L }

View File

@@ -19,9 +19,12 @@ object WriteActor {
} }
fun encodeToByteArray64(actor: Actor): ByteArray64 { fun encodeToByteArray64(actor: Actor): ByteArray64 {
val ba = ByteArray64() val baw = ByteArray64Writer(Common.CHARSET)
this.invoke(actor).toByteArray().forEach { ba.add(it) }
return ba Common.jsoner.toJson(actor, actor.javaClass, baw)
baw.flush(); baw.close()
return baw.toByteArray64()
} }
} }

View File

@@ -36,28 +36,28 @@ open class WriteMeta(val ingame: TerrarumIngame) {
it.append("\n\n## module: $modname ##\n\n") it.append("\n\n## module: $modname ##\n\n")
it.append(file.readText()) it.append(file.readText())
} }
bytesToZipdStr(it.toString().toByteArray()) bytesToZipdStr(it.toString().toByteArray(Common.CHARSET))
}}", }}",
"items": "${StringBuilder().let { "items": "${StringBuilder().let {
ModMgr.getFilesFromEveryMod("items/itemid.csv").forEach { (modname, file) -> ModMgr.getFilesFromEveryMod("items/itemid.csv").forEach { (modname, file) ->
it.append("\n\n## module: $modname ##\n\n") it.append("\n\n## module: $modname ##\n\n")
it.append(file.readText()) it.append(file.readText())
} }
bytesToZipdStr(it.toString().toByteArray()) bytesToZipdStr(it.toString().toByteArray(Common.CHARSET))
}}", }}",
"wires": "${StringBuilder().let { "wires": "${StringBuilder().let {
ModMgr.getFilesFromEveryMod("wires/wires.csv").forEach { (modname, file) -> ModMgr.getFilesFromEveryMod("wires/wires.csv").forEach { (modname, file) ->
it.append("\n\n## module: $modname ##\n\n") it.append("\n\n## module: $modname ##\n\n")
it.append(file.readText()) it.append(file.readText())
} }
bytesToZipdStr(it.toString().toByteArray()) bytesToZipdStr(it.toString().toByteArray(Common.CHARSET))
}}", }}",
"materials": "${StringBuilder().let { "materials": "${StringBuilder().let {
ModMgr.getFilesFromEveryMod("materials/materials.csv").forEach { (modname, file) -> ModMgr.getFilesFromEveryMod("materials/materials.csv").forEach { (modname, file) ->
it.append("\n\n## module: $modname ##\n\n") it.append("\n\n## module: $modname ##\n\n")
it.append(file.readText()) it.append(file.readText())
} }
bytesToZipdStr(it.toString().toByteArray()) bytesToZipdStr(it.toString().toByteArray(Common.CHARSET))
}}", }}",
"loadorder": [${ModMgr.loadOrder.map { "\"${it}\"" }.joinToString()}], "loadorder": [${ModMgr.loadOrder.map { "\"${it}\"" }.joinToString()}],
"worlds": [${ingame.gameworldIndices.joinToString()}] "worlds": [${ingame.gameworldIndices.joinToString()}]
@@ -68,7 +68,7 @@ open class WriteMeta(val ingame: TerrarumIngame) {
fun encodeToByteArray64(): ByteArray64 { fun encodeToByteArray64(): ByteArray64 {
val ba = ByteArray64() val ba = ByteArray64()
this.invoke().toByteArray().forEach { ba.add(it) } this.invoke().toByteArray(Common.CHARSET).forEach { ba.add(it) }
return ba return ba
} }
} }

View File

@@ -23,17 +23,12 @@ open class WriteWorld(val ingame: TerrarumIngame) {
world.genver = Common.GENVER world.genver = Common.GENVER
world.comp = Common.COMP_GZIP world.comp = Common.COMP_GZIP
val baw = ByteArray64Writer() val baw = ByteArray64Writer(Common.CHARSET)
Common.jsoner.toJson(world, baw) Common.jsoner.toJson(world, baw)
baw.flush(); baw.close() baw.flush(); baw.close()
return baw.toByteArray64() return baw.toByteArray64()
/*val ba = ByteArray64()
this.invoke().toByteArray().forEach { ba.add(it) }
return ba*/
} }
} }

View File

@@ -0,0 +1,28 @@
하수⬇️🏞는 두 산🏔🏞🏔 틈에서 나와 돌과 부딪쳐 싸우며🤜🏿🪨🤛🏻, 그 놀란 파도😲🌊와 성난 물머리와 우는 여울과 노한 물결과 슬픈 곡조와 원망하는 소리가 굽이쳐 돌면서, 우는 듯, 소리치는 듯, 바쁘게 호령하는 듯, 항상 장성을 깨뜨릴 형세가 있어, 전차 만승과 전기 만대나 전포 만가와 전고 만좌로써는 그 무너뜨리고 내뿜는 소리를 족히 형용할 수 없을 것이다. 모래 위에 큰 돌은 홀연히 떨어져 섰고, 강 언덕에 버드나무는 어둡고 컴컴하여 물지킴과 하수 귀신이 다투어 나와서 사람을 놀리는 듯한데, 좌우의 교리가 붙들려고 애쓰는 듯싶었다.
혹은 말하기를,
“여기는 옛 전쟁터이므로 강물이 저같이 우는 것이다”
하지만 이는 그런 것이 아니니, 강물 소리는 듣기 여하에 달렸을 것이다.
河出兩山間,
觸石鬪狼,
其驚濤駭浪,
憤瀾怒波,
哀湍怨瀨,
犇衝卷倒,
嘶哮號喊,
常有摧破長城之勢.
戰車萬乘,
戰騎萬隊,
戰砲萬架,
戰鼓萬坐,
未足喩其崩塌潰壓之聲.
沙上巨石,
屹然離立,
河堤柳樹,
窅冥鴻蒙,
如水祗河神爭出驕人,
而左右蛟螭試其拏攫也.
或曰: 「此古戰場,
故河鳴然也」.
此非爲其然也,
河聲在聽之如何爾.

28
work_files/utftest.txt Normal file
View File

@@ -0,0 +1,28 @@
하수⬇️🏞는 두 산🏔🏞🏔 틈에서 나와 돌과 부딪쳐 싸우며🤜🏿🪨🤛🏻, 그 놀란 파도😲🌊와 성난 물머리와 우는 여울과 노한 물결과 슬픈 곡조와 원망하는 소리가 굽이쳐 돌면서, 우는 듯, 소리치는 듯, 바쁘게 호령하는 듯, 항상 장성을 깨뜨릴 형세가 있어, 전차 만승과 전기 만대나 전포 만가와 전고 만좌로써는 그 무너뜨리고 내뿜는 소리를 족히 형용할 수 없을 것이다. 모래 위에 큰 돌은 홀연히 떨어져 섰고, 강 언덕에 버드나무는 어둡고 컴컴하여 물지킴과 하수 귀신이 다투어 나와서 사람을 놀리는 듯한데, 좌우의 교리가 붙들려고 애쓰는 듯싶었다.
혹은 말하기를,
“여기는 옛 전쟁터이므로 강물이 저같이 우는 것이다”
하지만 이는 그런 것이 아니니, 강물 소리는 듣기 여하에 달렸을 것이다.
河出兩山間,
觸石鬪狼,
其驚濤駭浪,
憤瀾怒波,
哀湍怨瀨,
犇衝卷倒,
嘶哮號喊,
常有摧破長城之勢.
戰車萬乘,
戰騎萬隊,
戰砲萬架,
戰鼓萬坐,
未足喩其崩塌潰壓之聲.
沙上巨石,
屹然離立,
河堤柳樹,
窅冥鴻蒙,
如水祗河神爭出驕人,
而左右蛟螭試其拏攫也.
或曰: 「此古戰場,
故河鳴然也」.
此非爲其然也,
河聲在聽之如何爾.