tweak bool type handling

This commit is contained in:
Irmen de Jong 2022-07-11 01:44:34 +02:00
parent 92eb3b0bf6
commit 7a26646e1b
13 changed files with 116 additions and 73 deletions

View File

@ -8,6 +8,12 @@ import kotlin.math.round
sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) { 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() { override fun printProperties() {
print(type) 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) { class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
init { init {
if(type==DataType.BOOL)
throw java.lang.IllegalArgumentException("bool should have become ubyte @$position")
if(type!=DataType.FLOAT) { if(type!=DataType.FLOAT) {
val rounded = round(number) val rounded = round(number)
if (rounded != 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")
} }
} }

View File

@ -5,6 +5,7 @@ val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not") val LogicalOperators = setOf("and", "or", "xor", "not")
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%") val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%")
val BitwiseOperators = setOf("&", "|", "^", "~") val BitwiseOperators = setOf("&", "|", "^", "~")
val InvalidOperatorsForBoolean = setOf("-", "*", "/", "%", "<<", ">>") // TODO what about +? TODO add BitWiseOperators
fun invertedComparisonOperator(operator: String) = fun invertedComparisonOperator(operator: String) =
when (operator) { when (operator) {

View File

@ -1165,14 +1165,14 @@ $repeatLabel lda $counterVar
return testVariableZeroAndJump(left, dt, operator, jumpIfFalseLabel) return testVariableZeroAndJump(left, dt, operator, jumpIfFalseLabel)
when(dt) { when(dt) {
DataType.UBYTE, DataType.UWORD -> { DataType.BOOL, DataType.UBYTE, DataType.UWORD -> {
if(operator=="<") { if(operator=="<") {
out(" jmp $jumpIfFalseLabel") out(" jmp $jumpIfFalseLabel")
return return
} else if(operator==">=") { } else if(operator==">=") {
return return
} }
if(dt==DataType.UBYTE) { if(dt==DataType.UBYTE || dt==DataType.BOOL) {
assignExpressionToRegister(left, RegisterOrPair.A, false) assignExpressionToRegister(left, RegisterOrPair.A, false)
if (left is IFunctionCall && !left.isSimple) if (left is IFunctionCall && !left.isSimple)
out(" cmp #0") out(" cmp #0")
@ -1252,7 +1252,7 @@ $repeatLabel lda $counterVar
// optimized code if the expression is just an identifier (variable) // optimized code if the expression is just an identifier (variable)
val varname = asmVariableName(variable) val varname = asmVariableName(variable)
when(dt) { when(dt) {
DataType.UBYTE -> when(operator) { DataType.UBYTE, DataType.BOOL -> when(operator) {
"==" -> out(" lda $varname | bne $jumpIfFalseLabel") "==" -> out(" lda $varname | bne $jumpIfFalseLabel")
"!=" -> out(" lda $varname | beq $jumpIfFalseLabel") "!=" -> out(" lda $varname | beq $jumpIfFalseLabel")
">" -> out(" lda $varname | beq $jumpIfFalseLabel") ">" -> out(" lda $varname | beq $jumpIfFalseLabel")

View File

@ -136,7 +136,7 @@ internal class ExpressionsAsmGen(private val program: Program,
private fun translateExpression(typecast: TypecastExpression) { private fun translateExpression(typecast: TypecastExpression) {
translateExpressionInternal(typecast.expression) translateExpressionInternal(typecast.expression)
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) { when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> { DataType.UBYTE, DataType.BOOL -> {
when(typecast.type) { when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {} DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {

View File

@ -10,9 +10,7 @@ import prog8.ast.determineGosubArguments
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.DataType import prog8.code.core.*
import prog8.code.core.Position
import prog8.code.core.SourceCode
import java.io.File import java.io.File
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.isRegularFile import kotlin.io.path.isRegularFile
@ -405,7 +403,17 @@ class IntermediateAstMaker(val program: Program) {
private fun transform(srcExpr: BinaryExpression): PtBinaryExpression { private fun transform(srcExpr: BinaryExpression): PtBinaryExpression {
val type = srcExpr.inferType(program).getOrElse { throw FatalAstException("unknown dt") } 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.left))
expr.add(transformExpression(srcExpr.right)) expr.add(transformExpression(srcExpr.right))
return expr return expr

View File

@ -481,7 +481,7 @@ internal class AstChecker(private val program: Program,
if(assignment.isAugmentable && targetDt istype DataType.BOOL) { if(assignment.isAugmentable && targetDt istype DataType.BOOL) {
val operator = (assignment.value as? BinaryExpression)?.operator 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) errors.err("can't use boolean operand with this operator $operator", assignment.position)
} }
super.visit(assignment) super.visit(assignment)
@ -918,7 +918,7 @@ internal class AstChecker(private val program: Program,
if(expr.operator in setOf("<", "<=", ">", ">=")) { if(expr.operator in setOf("<", "<=", ">", ">=")) {
errors.err("can't use boolean operand with this comparison operator", expr.position) 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) 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)) { if(expr.operator=="+" && (leftDt==DataType.BOOL || (expr.left as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true)) {

View File

@ -10,6 +10,7 @@ import prog8.ast.statements.VarDeclOrigin
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.code.core.* import prog8.code.core.*
import prog8.compiler.printProgram
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) { internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {

View File

@ -96,21 +96,6 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
return listOf(IAstModification.ReplaceNode(expr, containment, parent)) 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 return noModifications
} }
@ -147,27 +132,4 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
return noModifications 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)
}
} }

View File

@ -15,12 +15,6 @@ internal class BeforeAsmAstChanger(val program: Program,
private val errors: IErrorReporter private val errors: IErrorReporter
) : AstWalker() { ) : AstWalker() {
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
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<IAstModification> { override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
throw InternalCompilerException("break should have been replaced by goto $breakStmt") 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) { 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, 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)) return listOf(IAstModification.ReplaceNode(decl, ubyteDecl, parent))
} }
if(decl.datatype==DataType.ARRAY_BOOL) { 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, 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)) return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent))
} }
@ -210,6 +220,23 @@ internal class BeforeAsmAstChanger(val program: Program,
return mods return mods
} }
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
// 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<IAstModification> { override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
val binExpr = ifElse.condition as? BinaryExpression val binExpr = ifElse.condition as? BinaryExpression
if(binExpr==null) { if(binExpr==null) {
@ -410,4 +437,27 @@ internal class BeforeAsmAstChanger(val program: Program,
) )
return modifications 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)
}
} }

View File

@ -72,8 +72,7 @@ internal class NotExpressionChanger(val program: Program, val errors: IErrorRepo
// all other not(x) --> x==0 // all other not(x) --> x==0
// this means that "not" will never occur anywhere again in the ast after this // 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(DataType.UBYTE,0.0, expr.position), expr.position)
val replacement = BinaryExpression(expr.expression, "==", NumericLiteral(dt,0.0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNodeSafe(expr, replacement, parent)) return listOf(IAstModification.ReplaceNodeSafe(expr, replacement, parent))
} }
return noModifications return noModifications

View File

@ -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 val asBooleanValue: Boolean = number != 0.0
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {

View File

@ -3,7 +3,8 @@ TODO
For next release 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 - 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 - then make the cx16 virtual registers in syslib.p8 use that definition to be able to shift them around on non-cx16 targets

View File

@ -3,20 +3,28 @@
main { main {
sub ftrue(ubyte arg) -> ubyte {
ubyte key arg++
txt.print(" ftrue ")
sub func() -> ubyte { return 1
return key=='a' }
}
sub func2() -> bool {
return key==2
}
sub start() { sub start() {
bool @shared z1=func() bool ub1 = true
bool @shared z2=func2() 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()
} }
} }