From 9827ee97add75a66b30e13be36aa841745082bbe Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 12 Oct 2021 23:20:46 +0200 Subject: [PATCH] better returnvalue/errorhandling for Petscii encoding --- .../compiler/target/ICompilationTarget.kt | 26 +++++---- .../src/prog8/compiler/target/cbm/Petscii.kt | 55 ++++++++++++------- compiler/test/TestPetscii.kt | 46 ++++++++-------- compilerAst/src/prog8/Either.kt | 10 ++++ 4 files changed, 82 insertions(+), 55 deletions(-) diff --git a/compiler/src/prog8/compiler/target/ICompilationTarget.kt b/compiler/src/prog8/compiler/target/ICompilationTarget.kt index b32e8fd54..ef12c9ac0 100644 --- a/compiler/src/prog8/compiler/target/ICompilationTarget.kt +++ b/compiler/src/prog8/compiler/target/ICompilationTarget.kt @@ -72,12 +72,13 @@ interface ICompilationTarget: IStringEncoding, IMemSizer { internal object C64Target: ICompilationTarget { override val name = "c64" override val machine = C64MachineDefinition - override fun encodeString(str: String, altEncoding: Boolean) = - try { - if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) - } catch (x: CharConversionException) { - throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}") - } + override fun encodeString(str: String, altEncoding: Boolean): List { + val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) + return coded.fold( + { throw it }, + { it } + ) + } override fun decodeString(bytes: List, altEncoding: Boolean) = try { if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) @@ -99,12 +100,13 @@ internal object C64Target: ICompilationTarget { internal object Cx16Target: ICompilationTarget { override val name = "cx16" override val machine = CX16MachineDefinition - override fun encodeString(str: String, altEncoding: Boolean) = - try { - if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) - } catch (x: CharConversionException) { - throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}") - } + override fun encodeString(str: String, altEncoding: Boolean): List { + val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) + return coded.fold( + { throw it }, + { it} + ) + } override fun decodeString(bytes: List, altEncoding: Boolean) = try { if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) diff --git a/compiler/src/prog8/compiler/target/cbm/Petscii.kt b/compiler/src/prog8/compiler/target/cbm/Petscii.kt index 1949521c0..083ee99d4 100644 --- a/compiler/src/prog8/compiler/target/cbm/Petscii.kt +++ b/compiler/src/prog8/compiler/target/cbm/Petscii.kt @@ -1,6 +1,9 @@ package prog8.compiler.target.cbm +import prog8.Either import prog8.ast.antlr.escape +import prog8.left +import prog8.right import java.io.CharConversionException object Petscii { @@ -1062,7 +1065,7 @@ object Petscii { else -> chr } - fun encodePetscii(text: String, lowercase: Boolean = false): List { + fun encodePetscii(text: String, lowercase: Boolean = false): Either> { fun encodeChar(chr3: Char, lowercase: Boolean): Short { val chr = replaceSpecial(chr3) val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr] @@ -1079,12 +1082,16 @@ object Petscii { } } - return text.map{ - try { - encodeChar(it, lowercase) - } catch (x: CharConversionException) { - encodeChar(it, !lowercase) - } + return try { + right(text.map { + try { + encodeChar(it, lowercase) + } catch (x: CharConversionException) { + encodeChar(it, !lowercase) + } + }) + } catch(cx: CharConversionException) { + left(cx) } } @@ -1094,12 +1101,13 @@ object Petscii { try { if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code] } catch(x: CharConversionException) { + // TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString? if(lowercase) decodingPetsciiUppercase[code] else decodingPetsciiLowercase[code] } }.joinToString("") } - fun encodeScreencode(text: String, lowercase: Boolean = false): List { + fun encodeScreencode(text: String, lowercase: Boolean = false): Either> { fun encodeChar(chr3: Char, lowercase: Boolean): Short { val chr = replaceSpecial(chr3) val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr] @@ -1116,12 +1124,16 @@ object Petscii { } } - return text.map{ - try { - encodeChar(it, lowercase) - } catch (x: CharConversionException) { - encodeChar(it, !lowercase) - } + return try { + right(text.map { + try { + encodeChar(it, lowercase) + } catch (x: CharConversionException) { + encodeChar(it, !lowercase) + } + }) + } catch(cx: CharConversionException) { + left(cx) } } @@ -1131,12 +1143,13 @@ object Petscii { try { if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code] } catch (x: CharConversionException) { + // TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString? if (lowercase) decodingScreencodeUppercase[code] else decodingScreencodeLowercase[code] } }.joinToString("") } - fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Short { + fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Either { val code = when { petscii_code <= 0x1f -> petscii_code + 128 petscii_code <= 0x3f -> petscii_code.toInt() @@ -1146,14 +1159,14 @@ object Petscii { petscii_code <= 0xbf -> petscii_code - 64 petscii_code <= 0xfe -> petscii_code - 128 petscii_code == 255.toShort() -> 95 - else -> throw CharConversionException("petscii code out of range") + else -> return left(CharConversionException("petscii code out of range")) } if(inverseVideo) - return (code or 0x80).toShort() - return code.toShort() + return right((code or 0x80).toShort()) + return right(code.toShort()) } - fun scr2petscii(screencode: Short): Short { + fun scr2petscii(screencode: Short): Either { val petscii = when { screencode <= 0x1f -> screencode + 64 screencode <= 0x3f -> screencode.toInt() @@ -1164,8 +1177,8 @@ object Petscii { screencode <= 0xbf -> screencode - 128 screencode <= 0xfe -> screencode - 64 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()) } } diff --git a/compiler/test/TestPetscii.kt b/compiler/test/TestPetscii.kt index c83657810..be6d79c79 100644 --- a/compiler/test/TestPetscii.kt +++ b/compiler/test/TestPetscii.kt @@ -9,6 +9,8 @@ import prog8.ast.base.Position import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.StringLiteralValue import prog8.compiler.target.cbm.Petscii +import prog8.left +import prog8.right import kotlin.test.* @@ -17,8 +19,8 @@ class TestPetscii { @Test fun testZero() { - assertThat(Petscii.encodePetscii("\u0000", true), equalTo(listOf(0))) - assertThat(Petscii.encodePetscii("\u0000", false), equalTo(listOf(0))) + assertThat(Petscii.encodePetscii("\u0000", true), equalTo(right(listOf(0)))) + assertThat(Petscii.encodePetscii("\u0000", false), equalTo(right(listOf(0)))) assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000")) assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000")) } @@ -26,11 +28,11 @@ class TestPetscii { @Test fun testLowercase() { assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo( - listOf(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(0x12))) // reverse vid - assertThat(Petscii.encodePetscii("✓", true), equalTo(listOf(0xfa))) - assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(listOf(255))) - assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(listOf(0xd3))) + right(listOf(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(right(listOf(0x12)))) // reverse vid + assertThat(Petscii.encodePetscii("✓", true), equalTo(right(listOf(0xfa)))) + assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(right(listOf(255)))) + assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(right(listOf(0xd3)))) assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A")) assertFailsWith { Petscii.decodePetscii(listOf(-1), true) } @@ -40,11 +42,11 @@ class TestPetscii { @Test fun testUppercase() { assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo( - listOf(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))) - assertThat(Petscii.encodePetscii("\uf11a"), equalTo(listOf(0x12))) // reverse vid - assertThat(Petscii.encodePetscii("♥"), equalTo(listOf(0xd3))) - assertThat(Petscii.encodePetscii("π"), equalTo(listOf(0xff))) - assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(listOf(250))) + right(listOf(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c)))) + assertThat(Petscii.encodePetscii("\uf11a"), equalTo(right(listOf(0x12)))) // reverse vid + assertThat(Petscii.encodePetscii("♥"), equalTo(right(listOf(0xd3)))) + assertThat(Petscii.encodePetscii("π"), equalTo(right(listOf(0xff)))) + assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(right(listOf(250)))) assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π")) assertFailsWith { Petscii.decodePetscii(listOf(-1)) } @@ -54,11 +56,11 @@ class TestPetscii { @Test fun testScreencodeLowercase() { assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo( - listOf(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c) + right(listOf(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(0x7a))) - assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(listOf(83))) - assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(listOf(94))) + assertThat(Petscii.encodeScreencode("✓", true), equalTo(right(listOf(0x7a)))) + assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(right(listOf(83)))) + assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(right(listOf(94)))) assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓")) assertFailsWith { Petscii.decodeScreencode(listOf(-1), true) } @@ -68,12 +70,12 @@ class TestPetscii { @Test fun testScreencodeUppercase() { assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo( - listOf(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))) - assertThat(Petscii.encodeScreencode("♥"), equalTo(listOf(0x53))) - assertThat(Petscii.encodeScreencode("π"), equalTo(listOf(0x5e))) - assertThat(Petscii.encodeScreencode("HELLO"), equalTo(listOf(8, 5, 12, 12, 15))) - assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(listOf(8, 5, 12, 12, 15))) - assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(listOf(122))) + right(listOf(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)))) + assertThat(Petscii.encodeScreencode("♥"), equalTo(right(listOf(0x53)))) + assertThat(Petscii.encodeScreencode("π"), equalTo(right(listOf(0x5e)))) + assertThat(Petscii.encodeScreencode("HELLO"), equalTo(right(listOf(8, 5, 12, 12, 15)))) + assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(right(listOf(8, 5, 12, 12, 15)))) + assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(right(listOf(122)))) assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π")) assertFailsWith { Petscii.decodeScreencode(listOf(-1)) } diff --git a/compilerAst/src/prog8/Either.kt b/compilerAst/src/prog8/Either.kt index 72dfab043..6e78c7658 100644 --- a/compilerAst/src/prog8/Either.kt +++ b/compilerAst/src/prog8/Either.kt @@ -1,5 +1,9 @@ package prog8 +/** + * By convention, the right side of an `Either` is used to hold successful values. + * + */ sealed class Either { data class Left(val value: L) : Either() @@ -9,6 +13,12 @@ sealed class Either { fun isRight() = this is Right fun isLeft() = this is Left + + inline fun fold(ifLeft: (L) -> C, ifRight: (R) -> C): C = when (this) { + is Right -> ifRight(value) + is Left -> ifLeft(value) + } + } fun left(a: L) = Either.Left(a)