From 0567168ea98214ca23faa1d510c4117f6e806df5 Mon Sep 17 00:00:00 2001 From: meisl Date: Mon, 21 Jun 2021 20:16:50 +0200 Subject: [PATCH] + add AST node CharLiteral, *without* turning them into ubyte s. This breaks tests, particularly 3 in TestCompilerOnCharLit. I'm comitting this separately since the failure modes might be of interest (compiler says "internal error"). --- .../compiler/astprocessing/AstChecker.kt | 12 ++- compiler/test/Helpers.kt | 4 +- compilerAst/src/prog8/ast/AstToSourceCode.kt | 6 ++ .../src/prog8/ast/antlr/Antlr2Kotlin.kt | 13 +-- .../prog8/ast/expressions/AstExpressions.kt | 31 ++++++ compilerAst/src/prog8/ast/walk/AstWalker.kt | 7 ++ compilerAst/src/prog8/ast/walk/IAstVisitor.kt | 3 + compilerAst/test/Helpers.kt | 3 +- compilerAst/test/TestAstToSourceCode.kt | 2 - compilerAst/test/TestProg8Parser.kt | 99 ++++++++++++++++++- 10 files changed, 161 insertions(+), 19 deletions(-) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 20c9a830f..296a4ca01 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -776,10 +776,20 @@ internal class AstChecker(private val program: Program, super.visit(array) } + override fun visit(char: CharLiteral) { + try { // just *try* if it can be encoded, don't actually do it + compTarget.encodeString(char.value.toString(), char.altEncoding) + } catch (cx: CharConversionException) { + errors.err(cx.message ?: "can't encode character", char.position) + } + + super.visit(char) + } + override fun visit(string: StringLiteralValue) { checkValueTypeAndRangeString(DataType.STR, string) - try { + try { // just *try* if it can be encoded, don't actually do it compTarget.encodeString(string.value, string.altEncoding) } catch (cx: CharConversionException) { errors.err(cx.message ?: "can't encode string", string.position) diff --git a/compiler/test/Helpers.kt b/compiler/test/Helpers.kt index de5a55217..cecef98af 100644 --- a/compiler/test/Helpers.kt +++ b/compiler/test/Helpers.kt @@ -48,6 +48,7 @@ fun assumeReadable(path: Path) { } fun assumeReadableFile(path: Path) { + assumeReadable(path) assertTrue(path.isRegularFile(), "sanity check: should be normal file: ${path.absolute()}") } @@ -66,7 +67,6 @@ fun sanityCheckDirectories(workingDirName: String? = null) { assumeDirectory(fixturesDir) assumeDirectory(resourcesDir) assumeDirectory(outputDir) - } @@ -97,5 +97,3 @@ val DummyMemsizer = object : IMemSizer { override fun memorySize(dt: DataType): Int = 0 } - - diff --git a/compilerAst/src/prog8/ast/AstToSourceCode.kt b/compilerAst/src/prog8/ast/AstToSourceCode.kt index 9c42a7553..6896f85ad 100644 --- a/compilerAst/src/prog8/ast/AstToSourceCode.kt +++ b/compilerAst/src/prog8/ast/AstToSourceCode.kt @@ -266,6 +266,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): output(numLiteral.number.toString()) } + override fun visit(char: CharLiteral) { + if (char.altEncoding) + output("@") + output("'${escape(char.value.toString())}'") + } + override fun visit(string: StringLiteralValue) { if (string.altEncoding) output("@") diff --git a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt index 1792215b2..c2c81dd1c 100644 --- a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt @@ -435,15 +435,7 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst(encoding: IStringEncoding) } litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition()) litval.stringliteral()!=null -> litval.stringliteral().toAst() - litval.charliteral()!=null -> { - try { - NumericLiteralValue(DataType.UBYTE, encoding.encodeString( - unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()), - litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition()) - } catch (ce: CharConversionException) { - throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition()) - } - } + litval.charliteral()!=null -> litval.charliteral().toAst() litval.arrayliteral()!=null -> { val array = litval.arrayliteral().toAst(encoding) // the actual type of the arraysize can not yet be determined here (missing namespace & heap) @@ -491,6 +483,9 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst(encoding: IStringEncoding) throw FatalAstException(text) } +private fun Prog8ANTLRParser.CharliteralContext.toAst(): CharLiteral = + CharLiteral(unescape(this.SINGLECHAR().text, toPosition())[0], this.ALT_STRING_ENCODING() != null, toPosition()) + private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteralValue = StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition()) diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index e4ea9097a..d9aa47362 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -499,6 +499,37 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed } } +class CharLiteral(val value: Char, + val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64 + override val position: Position) : Expression() { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + } + + override val isSimple = true + + override fun replaceChildNode(node: Node, replacement: Node) { + throw FatalAstException("can't replace here") + } + + override fun referencesIdentifier(vararg scopedName: String) = false + override fun constValue(program: Program): NumericLiteralValue? = null // TODO: CharLiteral.constValue can't be NumericLiteralValue... + override fun accept(visitor: IAstVisitor) = visitor.visit(this) + override fun accept(walker: AstWalker, parent: Node) = walker.visit(this, parent) + + override fun toString(): String = "'${escape(value.toString())}'" + override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UNDEFINED) // FIXME: CharLiteral.inferType + operator fun compareTo(other: CharLiteral): Int = value.compareTo(other.value) + override fun hashCode(): Int = Objects.hash(value, altEncoding) + override fun equals(other: Any?): Boolean { + if (other == null || other !is CharLiteral) + return false + return value == other.value && altEncoding == other.altEncoding + } +} + class StringLiteralValue(val value: String, val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64 override val position: Position) : Expression() { diff --git a/compilerAst/src/prog8/ast/walk/AstWalker.kt b/compilerAst/src/prog8/ast/walk/AstWalker.kt index 194b1f2e6..91a10db12 100644 --- a/compilerAst/src/prog8/ast/walk/AstWalker.kt +++ b/compilerAst/src/prog8/ast/walk/AstWalker.kt @@ -110,6 +110,7 @@ abstract class AstWalker { open fun before(untilLoop: UntilLoop, parent: Node): Iterable = noModifications open fun before(returnStmt: Return, parent: Node): Iterable = noModifications open fun before(scope: AnonymousScope, parent: Node): Iterable = noModifications + open fun before(char: CharLiteral, parent: Node): Iterable = noModifications open fun before(string: StringLiteralValue, parent: Node): Iterable = noModifications open fun before(subroutine: Subroutine, parent: Node): Iterable = noModifications open fun before(typecast: TypecastExpression, parent: Node): Iterable = noModifications @@ -150,6 +151,7 @@ abstract class AstWalker { open fun after(untilLoop: UntilLoop, parent: Node): Iterable = noModifications open fun after(returnStmt: Return, parent: Node): Iterable = noModifications open fun after(scope: AnonymousScope, parent: Node): Iterable = noModifications + open fun after(char: CharLiteral, parent: Node): Iterable = noModifications open fun after(string: StringLiteralValue, parent: Node): Iterable = noModifications open fun after(subroutine: Subroutine, parent: Node): Iterable = noModifications open fun after(typecast: TypecastExpression, parent: Node): Iterable = noModifications @@ -300,6 +302,11 @@ abstract class AstWalker { track(after(numLiteral, parent), numLiteral, parent) } + fun visit(char: CharLiteral, parent: Node) { + track(before(char, parent), char, parent) + track(after(char, parent), char, parent) + } + fun visit(string: StringLiteralValue, parent: Node) { track(before(string, parent), string, parent) track(after(string, parent), string, parent) diff --git a/compilerAst/src/prog8/ast/walk/IAstVisitor.kt b/compilerAst/src/prog8/ast/walk/IAstVisitor.kt index a2fcdc673..ca71f94bb 100644 --- a/compilerAst/src/prog8/ast/walk/IAstVisitor.kt +++ b/compilerAst/src/prog8/ast/walk/IAstVisitor.kt @@ -79,6 +79,9 @@ interface IAstVisitor { fun visit(numLiteral: NumericLiteralValue) { } + fun visit(char: CharLiteral) { + } + fun visit(string: StringLiteralValue) { } diff --git a/compilerAst/test/Helpers.kt b/compilerAst/test/Helpers.kt index 5fc3d8870..a49aed04a 100644 --- a/compilerAst/test/Helpers.kt +++ b/compilerAst/test/Helpers.kt @@ -1,6 +1,5 @@ package prog8tests.helpers -import org.junit.jupiter.api.Test import kotlin.test.* import kotlin.io.path.* @@ -25,6 +24,7 @@ fun assumeReadable(path: Path) { } fun assumeReadableFile(path: Path) { + assumeReadable(path) assertTrue(path.isRegularFile(), "sanity check: should be normal file: ${path.absolute()}") } @@ -43,7 +43,6 @@ fun sanityCheckDirectories(workingDirName: String? = null) { assumeDirectory(fixturesDir) assumeDirectory(resourcesDir) assumeDirectory(outputDir) - } diff --git a/compilerAst/test/TestAstToSourceCode.kt b/compilerAst/test/TestAstToSourceCode.kt index 563b58528..7b6166075 100644 --- a/compilerAst/test/TestAstToSourceCode.kt +++ b/compilerAst/test/TestAstToSourceCode.kt @@ -98,7 +98,6 @@ class TestAstToSourceCode { } @Test - @Disabled("TODO: char literals should be kept until code gen - step 4, 'introduce AST node CharLiteral'") fun testCharLiteral_noAlt() { val orig = SourceCode.of(""" main { @@ -111,7 +110,6 @@ class TestAstToSourceCode { } @Test - @Disabled("TODO: char literals should be kept until code gen - step 4, 'introduce AST node CharLiteral'") fun testCharLiteral_withAlt() { val orig = SourceCode.of(""" main { diff --git a/compilerAst/test/TestProg8Parser.kt b/compilerAst/test/TestProg8Parser.kt index 42c8d67c4..9826bf908 100644 --- a/compilerAst/test/TestProg8Parser.kt +++ b/compilerAst/test/TestProg8Parser.kt @@ -11,9 +11,10 @@ import kotlin.io.path.* import prog8.parser.ParseError import prog8.parser.Prog8Parser.parseModule import prog8.parser.SourceCode -import prog8.ast.Node -import prog8.ast.base.Position +import prog8.ast.* import prog8.ast.statements.* +import prog8.ast.base.Position +import prog8.ast.expressions.CharLiteral @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -336,4 +337,98 @@ class TestProg8Parser { assertPositionOf(whenStmt.choices[2], mpf, 9, 12) // TODO: endCol wrong! } + @Test + fun testCharLitAsArg() { + val src = SourceCode.of(""" + main { + sub start() { + chrout('\n') + } + } + """) + val module = parseModule(src) + + val startSub = module + .statements.filterIsInstance()[0] + .statements.filterIsInstance()[0] + val funCall = startSub.statements.filterIsInstance().first() + + assertIs(funCall.args[0]) + val char = funCall.args[0] as CharLiteral + assertEquals('\n', char.value) + } + + @Test + fun testBlockLevelVarDeclWithCharLiteral_noAltEnc() { + val src = SourceCode.of(""" + main { + ubyte c = 'x' + } + """) + val module = parseModule(src) + val decl = module + .statements.filterIsInstance()[0] + .statements.filterIsInstance()[0] + + val rhs = decl.value as CharLiteral + assertEquals('x', rhs.value, "char literal's .value") + assertEquals(false, rhs.altEncoding, "char literal's .altEncoding") + } + + @Test + fun testBlockLevelConstDeclWithCharLiteral_withAltEnc() { + val src = SourceCode.of(""" + main { + const ubyte c = @'x' + } + """) + val module = parseModule(src) + val decl = module + .statements.filterIsInstance()[0] + .statements.filterIsInstance()[0] + + val rhs = decl.value as CharLiteral + assertEquals('x', rhs.value, "char literal's .value") + assertEquals(true, rhs.altEncoding, "char literal's .altEncoding") + } + + @Test + fun testSubRoutineLevelVarDeclWithCharLiteral_noAltEnc() { + val src = SourceCode.of(""" + main { + sub start() { + ubyte c = 'x' + } + } + """) + val module = parseModule(src) + val decl = module + .statements.filterIsInstance()[0] + .statements.filterIsInstance()[0] + .statements.filterIsInstance()[0] + + val rhs = decl.value as CharLiteral + assertEquals('x', rhs.value, "char literal's .value") + assertEquals(false, rhs.altEncoding, "char literal's .altEncoding") + } + + @Test + fun testSubRoutineLevelConstDeclWithCharLiteral_withAltEnc() { + val src = SourceCode.of(""" + main { + sub start() { + const ubyte c = @'x' + } + } + """) + val module = parseModule(src) + val decl = module + .statements.filterIsInstance()[0] + .statements.filterIsInstance()[0] + .statements.filterIsInstance()[0] + + val rhs = decl.value as CharLiteral + assertEquals('x', rhs.value, "char literal's .value") + assertEquals(true, rhs.altEncoding, "char literal's .altEncoding") + } }