better returnvalue/errorhandling for Petscii encoding

This commit is contained in:
Irmen de Jong 2021-10-12 23:20:46 +02:00
parent 71a9a84211
commit 9827ee97ad
4 changed files with 82 additions and 55 deletions

View File

@ -72,12 +72,13 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
internal object C64Target: ICompilationTarget { internal object C64Target: ICompilationTarget {
override val name = "c64" override val name = "c64"
override val machine = C64MachineDefinition override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) = override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
try { val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) return coded.fold(
} catch (x: CharConversionException) { { throw it },
throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}") { it }
} )
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) = override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try { try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
@ -99,12 +100,13 @@ internal object C64Target: ICompilationTarget {
internal object Cx16Target: ICompilationTarget { internal object Cx16Target: ICompilationTarget {
override val name = "cx16" override val name = "cx16"
override val machine = CX16MachineDefinition override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) = override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
try { val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) return coded.fold(
} catch (x: CharConversionException) { { throw it },
throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}") { it}
} )
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) = override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try { try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)

View File

@ -1,6 +1,9 @@
package prog8.compiler.target.cbm package prog8.compiler.target.cbm
import prog8.Either
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import prog8.left
import prog8.right
import java.io.CharConversionException import java.io.CharConversionException
object Petscii { object Petscii {
@ -1062,7 +1065,7 @@ object Petscii {
else -> chr else -> chr
} }
fun encodePetscii(text: String, lowercase: Boolean = false): List<Short> { fun encodePetscii(text: String, lowercase: Boolean = false): Either<CharConversionException, List<Short>> {
fun encodeChar(chr3: Char, lowercase: Boolean): Short { fun encodeChar(chr3: Char, lowercase: Boolean): Short {
val chr = replaceSpecial(chr3) val chr = replaceSpecial(chr3)
val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr] val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr]
@ -1079,12 +1082,16 @@ object Petscii {
} }
} }
return text.map{ return try {
try { right(text.map {
encodeChar(it, lowercase) try {
} catch (x: CharConversionException) { encodeChar(it, lowercase)
encodeChar(it, !lowercase) } catch (x: CharConversionException) {
} encodeChar(it, !lowercase)
}
})
} catch(cx: CharConversionException) {
left(cx)
} }
} }
@ -1094,12 +1101,13 @@ object Petscii {
try { try {
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code] if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
} catch(x: CharConversionException) { } catch(x: CharConversionException) {
// TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString?
if(lowercase) decodingPetsciiUppercase[code] else decodingPetsciiLowercase[code] if(lowercase) decodingPetsciiUppercase[code] else decodingPetsciiLowercase[code]
} }
}.joinToString("") }.joinToString("")
} }
fun encodeScreencode(text: String, lowercase: Boolean = false): List<Short> { fun encodeScreencode(text: String, lowercase: Boolean = false): Either<CharConversionException, List<Short>> {
fun encodeChar(chr3: Char, lowercase: Boolean): Short { fun encodeChar(chr3: Char, lowercase: Boolean): Short {
val chr = replaceSpecial(chr3) val chr = replaceSpecial(chr3)
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr] val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
@ -1116,12 +1124,16 @@ object Petscii {
} }
} }
return text.map{ return try {
try { right(text.map {
encodeChar(it, lowercase) try {
} catch (x: CharConversionException) { encodeChar(it, lowercase)
encodeChar(it, !lowercase) } catch (x: CharConversionException) {
} encodeChar(it, !lowercase)
}
})
} catch(cx: CharConversionException) {
left(cx)
} }
} }
@ -1131,12 +1143,13 @@ object Petscii {
try { try {
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code] if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
} catch (x: CharConversionException) { } catch (x: CharConversionException) {
// TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString?
if (lowercase) decodingScreencodeUppercase[code] else decodingScreencodeLowercase[code] if (lowercase) decodingScreencodeUppercase[code] else decodingScreencodeLowercase[code]
} }
}.joinToString("") }.joinToString("")
} }
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Short { fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Either<CharConversionException, Short> {
val code = when { val code = when {
petscii_code <= 0x1f -> petscii_code + 128 petscii_code <= 0x1f -> petscii_code + 128
petscii_code <= 0x3f -> petscii_code.toInt() petscii_code <= 0x3f -> petscii_code.toInt()
@ -1146,14 +1159,14 @@ object Petscii {
petscii_code <= 0xbf -> petscii_code - 64 petscii_code <= 0xbf -> petscii_code - 64
petscii_code <= 0xfe -> petscii_code - 128 petscii_code <= 0xfe -> petscii_code - 128
petscii_code == 255.toShort() -> 95 petscii_code == 255.toShort() -> 95
else -> throw CharConversionException("petscii code out of range") else -> return left(CharConversionException("petscii code out of range"))
} }
if(inverseVideo) if(inverseVideo)
return (code or 0x80).toShort() return right((code or 0x80).toShort())
return code.toShort() return right(code.toShort())
} }
fun scr2petscii(screencode: Short): Short { fun scr2petscii(screencode: Short): Either<CharConversionException, Short> {
val petscii = when { val petscii = when {
screencode <= 0x1f -> screencode + 64 screencode <= 0x1f -> screencode + 64
screencode <= 0x3f -> screencode.toInt() screencode <= 0x3f -> screencode.toInt()
@ -1164,8 +1177,8 @@ object Petscii {
screencode <= 0xbf -> screencode - 128 screencode <= 0xbf -> screencode - 128
screencode <= 0xfe -> screencode - 64 screencode <= 0xfe -> screencode - 64
screencode == 255.toShort() -> 191 screencode == 255.toShort() -> 191
else -> throw CharConversionException("screencode out of range") else -> return left(CharConversionException("screencode out of range"))
} }
return petscii.toShort() return right(petscii.toShort())
} }
} }

View File

@ -9,6 +9,8 @@ import prog8.ast.base.Position
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.target.cbm.Petscii import prog8.compiler.target.cbm.Petscii
import prog8.left
import prog8.right
import kotlin.test.* import kotlin.test.*
@ -17,8 +19,8 @@ class TestPetscii {
@Test @Test
fun testZero() { fun testZero() {
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(listOf<Short>(0))) assertThat(Petscii.encodePetscii("\u0000", true), equalTo(right(listOf<Short>(0))))
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(listOf<Short>(0))) assertThat(Petscii.encodePetscii("\u0000", false), equalTo(right(listOf<Short>(0))))
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000")) assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000")) assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
} }
@ -26,11 +28,11 @@ class TestPetscii {
@Test @Test
fun testLowercase() { fun testLowercase() {
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo( assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))) right(listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(listOf<Short>(0x12))) // reverse vid assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(right(listOf<Short>(0x12)))) // reverse vid
assertThat(Petscii.encodePetscii("", true), equalTo(listOf<Short>(0xfa))) assertThat(Petscii.encodePetscii("", true), equalTo(right(listOf<Short>(0xfa))))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(listOf<Short>(255))) assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(right(listOf<Short>(255))))
assertThat("expect lowercase error fallback", Petscii.encodePetscii("", true), equalTo(listOf<Short>(0xd3))) assertThat("expect lowercase error fallback", Petscii.encodePetscii("", true), equalTo(right(listOf<Short>(0xd3))))
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A")) assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) } assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
@ -40,11 +42,11 @@ class TestPetscii {
@Test @Test
fun testUppercase() { fun testUppercase() {
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo( assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))) right(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(listOf<Short>(0x12))) // reverse vid assertThat(Petscii.encodePetscii("\uf11a"), equalTo(right(listOf<Short>(0x12)))) // reverse vid
assertThat(Petscii.encodePetscii(""), equalTo(listOf<Short>(0xd3))) assertThat(Petscii.encodePetscii(""), equalTo(right(listOf<Short>(0xd3))))
assertThat(Petscii.encodePetscii("π"), equalTo(listOf<Short>(0xff))) assertThat(Petscii.encodePetscii("π"), equalTo(right(listOf<Short>(0xff))))
assertThat("expecting fallback", Petscii.encodePetscii(""), equalTo(listOf<Short>(250))) assertThat("expecting fallback", Petscii.encodePetscii(""), equalTo(right(listOf<Short>(250))))
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π")) assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) } assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
@ -54,11 +56,11 @@ class TestPetscii {
@Test @Test
fun testScreencodeLowercase() { fun testScreencodeLowercase() {
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo( assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c) right(listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
)) ))
assertThat(Petscii.encodeScreencode("", true), equalTo(listOf<Short>(0x7a))) assertThat(Petscii.encodeScreencode("", true), equalTo(right(listOf<Short>(0x7a))))
assertThat("expect fallback", Petscii.encodeScreencode("", true), equalTo(listOf<Short>(83))) assertThat("expect fallback", Petscii.encodeScreencode("", true), equalTo(right(listOf<Short>(83))))
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(listOf<Short>(94))) assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(right(listOf<Short>(94))))
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓")) assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) } assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
@ -68,12 +70,12 @@ class TestPetscii {
@Test @Test
fun testScreencodeUppercase() { fun testScreencodeUppercase() {
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo( assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))) right(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))))
assertThat(Petscii.encodeScreencode(""), equalTo(listOf<Short>(0x53))) assertThat(Petscii.encodeScreencode(""), equalTo(right(listOf<Short>(0x53))))
assertThat(Petscii.encodeScreencode("π"), equalTo(listOf<Short>(0x5e))) assertThat(Petscii.encodeScreencode("π"), equalTo(right(listOf<Short>(0x5e))))
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(listOf<Short>(8, 5, 12, 12, 15))) assertThat(Petscii.encodeScreencode("HELLO"), equalTo(right(listOf<Short>(8, 5, 12, 12, 15))))
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(listOf<Short>(8, 5, 12, 12, 15))) assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(right(listOf<Short>(8, 5, 12, 12, 15))))
assertThat("expecting fallback", Petscii.encodeScreencode(""), equalTo(listOf<Short>(122))) assertThat("expecting fallback", Petscii.encodeScreencode(""), equalTo(right(listOf<Short>(122))))
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π")) assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) } assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }

View File

@ -1,5 +1,9 @@
package prog8 package prog8
/**
* By convention, the right side of an `Either` is used to hold successful values.
*
*/
sealed class Either<out L, out R> { sealed class Either<out L, out R> {
data class Left<out L>(val value: L) : Either<L, Nothing>() data class Left<out L>(val value: L) : Either<L, Nothing>()
@ -9,6 +13,12 @@ sealed class Either<out L, out R> {
fun isRight() = this is Right<R> fun isRight() = this is Right<R>
fun isLeft() = this is Left<L> fun isLeft() = this is Left<L>
inline fun <C> fold(ifLeft: (L) -> C, ifRight: (R) -> C): C = when (this) {
is Right -> ifRight(value)
is Left -> ifLeft(value)
}
} }
fun <L> left(a: L) = Either.Left(a) fun <L> left(a: L) = Either.Left(a)