From 7a26646e1b32e17994f2a83413acfb1278d025f9 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 11 Jul 2022 01:44:34 +0200 Subject: [PATCH] tweak bool type handling --- codeAst/src/prog8/code/ast/AstExpressions.kt | 10 ++- codeCore/src/prog8/code/core/Operators.kt | 1 + .../src/prog8/codegen/cpu6502/AsmGen.kt | 6 +- .../codegen/cpu6502/ExpressionsAsmGen.kt | 2 +- .../prog8/compiler/IntermediateAstMaker.kt | 16 +++-- .../compiler/astprocessing/AstChecker.kt | 4 +- .../compiler/astprocessing/AstExtensions.kt | 1 + .../compiler/astprocessing/AstPreprocessor.kt | 38 ----------- .../astprocessing/BeforeAsmAstChanger.kt | 66 ++++++++++++++++--- .../astprocessing/NotExpressionChanger.kt | 3 +- .../prog8/ast/expressions/AstExpressions.kt | 5 ++ docs/source/todo.rst | 3 +- examples/test.p8 | 34 ++++++---- 13 files changed, 116 insertions(+), 73 deletions(-) diff --git a/codeAst/src/prog8/code/ast/AstExpressions.kt b/codeAst/src/prog8/code/ast/AstExpressions.kt index b72952a34..1a6a86747 100644 --- a/codeAst/src/prog8/code/ast/AstExpressions.kt +++ b/codeAst/src/prog8/code/ast/AstExpressions.kt @@ -8,6 +8,12 @@ import kotlin.math.round sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) { + + init { + if(type==DataType.BOOL) + throw java.lang.IllegalArgumentException("bool should have become ubyte @$position") + } + override fun printProperties() { print(type) } @@ -127,10 +133,12 @@ class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) { init { + if(type==DataType.BOOL) + throw java.lang.IllegalArgumentException("bool should have become ubyte @$position") if(type!=DataType.FLOAT) { val rounded = round(number) if (rounded != number) - throw IllegalArgumentException("refused rounding of float to avoid loss of precision") + throw IllegalArgumentException("refused rounding of float to avoid loss of precision @$position") } } diff --git a/codeCore/src/prog8/code/core/Operators.kt b/codeCore/src/prog8/code/core/Operators.kt index af978352a..cfac9e9b3 100644 --- a/codeCore/src/prog8/code/core/Operators.kt +++ b/codeCore/src/prog8/code/core/Operators.kt @@ -5,6 +5,7 @@ val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=") val LogicalOperators = setOf("and", "or", "xor", "not") val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%") val BitwiseOperators = setOf("&", "|", "^", "~") +val InvalidOperatorsForBoolean = setOf("-", "*", "/", "%", "<<", ">>") // TODO what about +? TODO add BitWiseOperators fun invertedComparisonOperator(operator: String) = when (operator) { diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index 2cf797931..9bbf8e42b 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -1165,14 +1165,14 @@ $repeatLabel lda $counterVar return testVariableZeroAndJump(left, dt, operator, jumpIfFalseLabel) when(dt) { - DataType.UBYTE, DataType.UWORD -> { + DataType.BOOL, DataType.UBYTE, DataType.UWORD -> { if(operator=="<") { out(" jmp $jumpIfFalseLabel") return } else if(operator==">=") { return } - if(dt==DataType.UBYTE) { + if(dt==DataType.UBYTE || dt==DataType.BOOL) { assignExpressionToRegister(left, RegisterOrPair.A, false) if (left is IFunctionCall && !left.isSimple) out(" cmp #0") @@ -1252,7 +1252,7 @@ $repeatLabel lda $counterVar // optimized code if the expression is just an identifier (variable) val varname = asmVariableName(variable) when(dt) { - DataType.UBYTE -> when(operator) { + DataType.UBYTE, DataType.BOOL -> when(operator) { "==" -> out(" lda $varname | bne $jumpIfFalseLabel") "!=" -> out(" lda $varname | beq $jumpIfFalseLabel") ">" -> out(" lda $varname | beq $jumpIfFalseLabel") diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ExpressionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ExpressionsAsmGen.kt index a089cd452..589253a80 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ExpressionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ExpressionsAsmGen.kt @@ -136,7 +136,7 @@ internal class ExpressionsAsmGen(private val program: Program, private fun translateExpression(typecast: TypecastExpression) { translateExpressionInternal(typecast.expression) when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) { - DataType.UBYTE -> { + DataType.UBYTE, DataType.BOOL -> { when(typecast.type) { DataType.UBYTE, DataType.BYTE -> {} DataType.UWORD, DataType.WORD -> { diff --git a/compiler/src/prog8/compiler/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/IntermediateAstMaker.kt index 1e742e92d..1a5f3dd8b 100644 --- a/compiler/src/prog8/compiler/IntermediateAstMaker.kt +++ b/compiler/src/prog8/compiler/IntermediateAstMaker.kt @@ -10,9 +10,7 @@ import prog8.ast.determineGosubArguments import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.code.ast.* -import prog8.code.core.DataType -import prog8.code.core.Position -import prog8.code.core.SourceCode +import prog8.code.core.* import java.io.File import kotlin.io.path.Path import kotlin.io.path.isRegularFile @@ -405,7 +403,17 @@ class IntermediateAstMaker(val program: Program) { private fun transform(srcExpr: BinaryExpression): PtBinaryExpression { val type = srcExpr.inferType(program).getOrElse { throw FatalAstException("unknown dt") } - val expr = PtBinaryExpression(srcExpr.operator, type, srcExpr.position) + var actualType = type + if(type==DataType.BOOL) { + if(srcExpr.operator in LogicalOperators + ComparisonOperators) { + // a comparison or logical expression is a boolean result (0 or 1) so we can safely + // reduce that to just a UBYTE type for the vm code that doesn't know about bools. + actualType = DataType.UBYTE + } else { + throw IllegalArgumentException("Ast expression still having BOOL type: $srcExpr @${srcExpr.position}") + } + } + val expr = PtBinaryExpression(srcExpr.operator, actualType, srcExpr.position) expr.add(transformExpression(srcExpr.left)) expr.add(transformExpression(srcExpr.right)) return expr diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 408bcd57a..c213e64a5 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -481,7 +481,7 @@ internal class AstChecker(private val program: Program, if(assignment.isAugmentable && targetDt istype DataType.BOOL) { val operator = (assignment.value as? BinaryExpression)?.operator - if(operator in setOf("-", "*", "/", "%")) + if(operator in InvalidOperatorsForBoolean) errors.err("can't use boolean operand with this operator $operator", assignment.position) } super.visit(assignment) @@ -918,7 +918,7 @@ internal class AstChecker(private val program: Program, if(expr.operator in setOf("<", "<=", ">", ">=")) { errors.err("can't use boolean operand with this comparison operator", expr.position) } - if(expr.operator in setOf("-", "*", "/", "%") && (leftDt==DataType.BOOL || (expr.left as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true)) { + if(expr.operator in InvalidOperatorsForBoolean && (leftDt==DataType.BOOL || (expr.left as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true)) { errors.err("can't use boolean operand with this operator ${expr.operator}", expr.left.position) } if(expr.operator=="+" && (leftDt==DataType.BOOL || (expr.left as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true)) { diff --git a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt index 6480b12fb..a7619870b 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt @@ -10,6 +10,7 @@ import prog8.ast.statements.VarDeclOrigin import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification import prog8.code.core.* +import prog8.compiler.printProgram internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) { diff --git a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt index 6c56e1cbd..adf4b110c 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt @@ -96,21 +96,6 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp return listOf(IAstModification.ReplaceNode(expr, containment, parent)) } - // convert boolean and/or/xor/not operators to bitwise equivalents. - // the rest of the ast and codegen only has to work with bitwise boolean operations from now on. - if(expr.operator in setOf("and", "or", "xor")) { - expr.operator = when(expr.operator) { - "and" -> "&" - "or" -> "|" - "xor" -> "^" - else -> "invalid" - } - return listOf( - IAstModification.ReplaceNodeSafe(expr.left, wrapWithBooleanCast(expr.left), expr), - IAstModification.ReplaceNodeSafe(expr.right, wrapWithBooleanCast(expr.right), expr), - ) - } - return noModifications } @@ -147,27 +132,4 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp return noModifications } - - private fun wrapWithBooleanCast(expr: Expression): Expression { - fun isBoolean(expr: Expression): Boolean { - return if(expr.inferType(program) istype DataType.BOOL) - true - else if(expr is BinaryExpression && expr.operator in ComparisonOperators + LogicalOperators) - true - else if(expr is PrefixExpression && expr.operator == "not") - true - else if(expr is BinaryExpression && expr.operator in BitwiseOperators) { - if(isBoolean(expr.left) && isBoolean(expr.right)) - true - else expr.operator=="&" && expr.right.constValue(program)?.number==1.0 // x & 1 is also a boolean result - } - else - false - } - - return if(isBoolean(expr)) - expr - else - TypecastExpression(expr, DataType.BOOL, true, expr.position) - } } diff --git a/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt b/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt index 963f83b4d..e28ce6b4e 100644 --- a/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt +++ b/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt @@ -15,12 +15,6 @@ internal class BeforeAsmAstChanger(val program: Program, private val errors: IErrorReporter ) : AstWalker() { - override fun after(numLiteral: NumericLiteral, parent: Node): Iterable { - if(numLiteral.type==DataType.BOOL) - return listOf(IAstModification.ReplaceNode(numLiteral, NumericLiteral(DataType.UBYTE, numLiteral.number, numLiteral.position), parent)) - return noModifications - } - override fun before(breakStmt: Break, parent: Node): Iterable { throw InternalCompilerException("break should have been replaced by goto $breakStmt") } @@ -67,14 +61,30 @@ internal class BeforeAsmAstChanger(val program: Program, } if(decl.datatype==DataType.BOOL) { + var newvalue = decl.value + if(newvalue is NumericLiteral) { + if(newvalue.number!=0.0) + newvalue = NumericLiteral(DataType.UBYTE, 1.0, newvalue.position) + } val ubyteDecl = VarDecl(decl.type, decl.origin, DataType.UBYTE, decl.zeropage, decl.arraysize, decl.name, - decl.value, decl.isArray, decl.sharedWithAsm, decl.subroutineParameter, decl.position) + newvalue, decl.isArray, decl.sharedWithAsm, decl.subroutineParameter, decl.position) return listOf(IAstModification.ReplaceNode(decl, ubyteDecl, parent)) } if(decl.datatype==DataType.ARRAY_BOOL) { + var newarray = decl.value + if(decl.value is ArrayLiteral) { + val oldArray = (decl.value as ArrayLiteral).value + val convertedArray = oldArray.map { + var number: Expression = it + if (it is NumericLiteral && it.type == DataType.BOOL) + number = NumericLiteral(DataType.UBYTE, if (it.number == 0.0) 0.0 else 1.0, number.position) + number + }.toTypedArray() + newarray = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_UB), convertedArray, decl.position) + } val ubyteArrayDecl = VarDecl(decl.type, decl.origin, DataType.ARRAY_UB, decl.zeropage, decl.arraysize, decl.name, - decl.value, decl.isArray, decl.sharedWithAsm, decl.subroutineParameter, decl.position) + newarray, true, decl.sharedWithAsm, decl.subroutineParameter, decl.position) return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent)) } @@ -210,6 +220,23 @@ internal class BeforeAsmAstChanger(val program: Program, return mods } + override fun after(expr: BinaryExpression, parent: Node): Iterable { + // convert boolean and/or/xor/not operators to bitwise equivalents. + // so that codegen only has to work with bitwise boolean operations from now on. + if(expr.operator in setOf("and", "or", "xor")) { + expr.operator = when(expr.operator) { + "and" -> "&" + "or" -> "|" + "xor" -> "^" + else -> "invalid" + } + return listOf( + IAstModification.ReplaceNode(expr.left, wrapWithBooleanCastIfNeeded(expr.left), expr), + IAstModification.ReplaceNode(expr.right, wrapWithBooleanCastIfNeeded(expr.right), expr),) + } + return noModifications + } + override fun after(ifElse: IfElse, parent: Node): Iterable { val binExpr = ifElse.condition as? BinaryExpression if(binExpr==null) { @@ -410,4 +437,27 @@ internal class BeforeAsmAstChanger(val program: Program, ) return modifications } + + private fun wrapWithBooleanCastIfNeeded(expr: Expression): Expression { + fun isBoolean(expr: Expression): Boolean { + return if(expr.inferType(program) istype DataType.BOOL) + true + else if(expr is BinaryExpression && expr.operator in ComparisonOperators + LogicalOperators) + true + else if(expr is PrefixExpression && expr.operator == "not") + true + else if(expr is BinaryExpression && expr.operator in BitwiseOperators) { + if(isBoolean(expr.left) && isBoolean(expr.right)) + true + else expr.operator=="&" && expr.right.constValue(program)?.number==1.0 // x & 1 is also a boolean result + } + else + false + } + + return if(isBoolean(expr)) + expr + else + TypecastExpression(expr, DataType.BOOL, true, expr.position) + } } diff --git a/compiler/src/prog8/compiler/astprocessing/NotExpressionChanger.kt b/compiler/src/prog8/compiler/astprocessing/NotExpressionChanger.kt index b76a0211c..de3948702 100644 --- a/compiler/src/prog8/compiler/astprocessing/NotExpressionChanger.kt +++ b/compiler/src/prog8/compiler/astprocessing/NotExpressionChanger.kt @@ -72,8 +72,7 @@ internal class NotExpressionChanger(val program: Program, val errors: IErrorRepo // all other not(x) --> x==0 // this means that "not" will never occur anywhere again in the ast after this - val dt = expr.expression.inferType(program).getOr(DataType.UBYTE) - val replacement = BinaryExpression(expr.expression, "==", NumericLiteral(dt,0.0, expr.position), expr.position) + val replacement = BinaryExpression(expr.expression, "==", NumericLiteral(DataType.UBYTE,0.0, expr.position), expr.position) return listOf(IAstModification.ReplaceNodeSafe(expr, replacement, parent)) } return noModifications diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index cf639f4ff..bee76ffcf 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -478,6 +478,11 @@ class NumericLiteral(val type: DataType, // only numerical types allowed } } + init { + if(type==DataType.BOOL) + throw FatalAstException("should not create NumericLiteral with BOOL type @$position") + } + val asBooleanValue: Boolean = number != 0.0 override fun linkParents(parent: Node) { diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 3876f0362..1a32b891a 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,8 @@ TODO For next release ^^^^^^^^^^^^^^^^ -- fix code gen crashes in logical.p8 / testtypecasts +- fix vm code result of test.p8 (ftrue() not called at all!?) +- add more operators to InvalidOperatorsForBoolean - have a proper option to move the evalstack rather than just assembly symbol redefine - then make the cx16 virtual registers in syslib.p8 use that definition to be able to shift them around on non-cx16 targets diff --git a/examples/test.p8 b/examples/test.p8 index 65e9b159c..2d3f5df25 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,20 +3,28 @@ main { - - ubyte key - - sub func() -> ubyte { - return key=='a' - } - - - sub func2() -> bool { - return key==2 - } + sub ftrue(ubyte arg) -> ubyte { + arg++ + txt.print(" ftrue ") + return 1 + } sub start() { - bool @shared z1=func() - bool @shared z2=func2() + bool ub1 = true + bool ub2 = true + bool ub3 = true + bool ub4 = 0 + bool bvalue + + txt.print("expected output: 0 ftrue 0 ftrue 1\n") + bvalue = ub1 xor ub2 xor ub3 xor true + txt.print_ub(bvalue) + txt.spc() + bvalue = ub1 xor ub2 xor ub3 xor ftrue(99) + txt.print_ub(bvalue) + txt.spc() + bvalue = ub1 and ub2 and ftrue(99) + txt.print_ub(bvalue) + txt.nl() } }