From 41afeccd51859f640010b547595bad4fd2784027 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 5 Feb 2024 01:09:33 +0100 Subject: [PATCH] compiler stuff --- .../src/prog8/codegen/cpu6502/Extensions.kt | 4 +- .../codegen/cpu6502/VariableAllocator.kt | 2 +- compiler/src/prog8/CompilerMain.kt | 2 + compiler/src/prog8/compiler/Compiler.kt | 4 +- .../compiler/astprocessing/AstChecker.kt | 145 ++++++++---------- .../compiler/astprocessing/AstExtensions.kt | 4 - .../compiler/astprocessing/AstPreprocessor.kt | 2 +- .../astprocessing/BeforeAsmAstChanger.kt | 27 +--- .../astprocessing/BeforeAsmTypecastCleaner.kt | 8 - .../compiler/astprocessing/BoolRemover.kt | 133 ---------------- .../compiler/astprocessing/CodeDesugarer.kt | 5 + .../astprocessing/IntermediateAstMaker.kt | 21 +-- .../astprocessing/LiteralsToAutoVars.kt | 4 +- ...NotExpressionAndIfComparisonExprChanger.kt | 106 ++++++++----- .../astprocessing/StatementReorderer.kt | 2 +- .../compiler/astprocessing/TypecastsAdder.kt | 51 ++---- .../astprocessing/VerifyFunctionArgTypes.kt | 7 +- .../src/prog8/intermediate/IRFileReader.kt | 9 +- .../src/prog8/intermediate/IRFileWriter.kt | 5 +- .../src/prog8/intermediate/IRProgram.kt | 4 +- .../src/prog8/intermediate/IRSymbolTable.kt | 26 +++- intermediate/src/prog8/intermediate/Utils.kt | 6 +- 22 files changed, 220 insertions(+), 357 deletions(-) delete mode 100644 compiler/src/prog8/compiler/astprocessing/BoolRemover.kt diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt index 3c2d5ed68..e6ac38b26 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt @@ -17,7 +17,7 @@ internal fun IPtSubroutine.returnsWhatWhere(): List RegisterOrStatusflag(RegisterOrPair.A, null) + in ByteDatatypesWithBoolean -> RegisterOrStatusflag(RegisterOrPair.A, null) in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null) DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null) else -> RegisterOrStatusflag(RegisterOrPair.AY, null) @@ -31,7 +31,7 @@ internal fun IPtSubroutine.returnsWhatWhere(): List RegisterOrStatusflag(RegisterOrPair.A, null) + in ByteDatatypesWithBoolean -> RegisterOrStatusflag(RegisterOrPair.A, null) in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null) DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null) null -> null diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocator.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocator.kt index 86d335a68..53b02b4fe 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocator.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocator.kt @@ -91,7 +91,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable, if(errors.noErrors()) { val sortedList = varsDontCare.sortedByDescending { it.scopedName } for (variable in sortedList) { - if(variable.dt in IntegerDatatypes) { + if(variable.dt in IntegerDatatypesWithBoolean) { if(zeropage.free.isEmpty()) { break } else { diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 0bdbbf32c..08340f4dc 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -70,6 +70,8 @@ private fun compileMain(args: Array): Boolean { return false } + println("BREAKPOINTINSTR=$breakpointCpuInstruction") + val outputPath = pathFrom(outputDir) if(!outputPath.toFile().isDirectory) { System.err.println("Output path doesn't exist") diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index f07e1d555..a54f89397 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -98,8 +98,8 @@ fun compileProgram(args: CompilerArguments): CompilationResult? { importedFiles = imported processAst(program, args.errors, compilationOptions) -// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************") -// printProgram(program) +// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************") +// printProgram(program) if (compilationOptions.optimize) { optimizeAst( diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 94d82674a..aa73e97fd 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -123,12 +123,6 @@ internal class AstChecker(private val program: Program, if(valueDt istype DataType.BOOL && expectedReturnValues[0] == DataType.UBYTE) { // if the return value is a bool and the return type is ubyte, allow this. But give a warning. errors.info("return type of the subroutine should probably be bool instead of ubyte", returnStmt.position) - } else if(valueDt istype DataType.UBYTE && expectedReturnValues[0] == DataType.BOOL) { - // if the return value is ubyte and the return type is bool, allow this only if value is 0 or 1 - val returnValue = returnStmt.value as? NumericLiteral - if (returnValue == null || returnValue.type != DataType.UBYTE || (returnValue.number!=0.0 && returnValue.number!=1.0)) { - errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}",returnStmt.value!!.position) - } } else if(valueDt.isIterable && expectedReturnValues[0]==DataType.UWORD) { // you can return a string or array when an uword (pointer) is returned } else if(valueDt istype DataType.UWORD && expectedReturnValues[0]==DataType.STR) { @@ -156,12 +150,9 @@ internal class AstChecker(private val program: Program, } override fun visit(ifElse: IfElse) { - val dt = ifElse.condition.inferType(program) - if(!dt.isInteger && !dt.istype(DataType.BOOL)) { - val identifier = ifElse.condition as? IdentifierReference - if(identifier==null || identifier.targetStatement(program)!=null) - errors.err("condition value should be an integer type or bool", ifElse.condition.position) - } + if(!ifElse.condition.inferType(program).isBool) + errors.err("condition should be a boolean", ifElse.condition.position) + super.visit(ifElse) } @@ -480,22 +471,16 @@ internal class AstChecker(private val program: Program, } override fun visit(untilLoop: UntilLoop) { - val dt = untilLoop.condition.inferType(program) - if(!dt.isInteger && !dt.istype(DataType.BOOL)) { - val identifier = untilLoop.condition as? IdentifierReference - if(identifier==null || identifier.targetStatement(program)!=null) - errors.err("condition value should be an integer type or bool", untilLoop.condition.position) - } + if(!untilLoop.condition.inferType(program).isBool) + errors.err("condition should be a boolean", untilLoop.condition.position) + super.visit(untilLoop) } override fun visit(whileLoop: WhileLoop) { - val dt = whileLoop.condition.inferType(program) - if(!dt.isInteger && !dt.istype(DataType.BOOL)) { - val identifier = whileLoop.condition as? IdentifierReference - if(identifier==null || identifier.targetStatement(program)!=null) - errors.err("condition value should be an integer type or bool", whileLoop.condition.position) - } + if(!whileLoop.condition.inferType(program).isBool) + errors.err("condition should be a boolean", whileLoop.condition.position) + super.visit(whileLoop) } @@ -539,20 +524,10 @@ internal class AstChecker(private val program: Program, if(numvalue!=null && targetDt.isKnown) checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue) -// for now, don't enforce bool type with only logical operators... -// if(assignment.isAugmentable && targetDt istype DataType.BOOL) { -// val operator = (assignment.value as? BinaryExpression)?.operator -// if(operator in InvalidOperatorsForBoolean) -// errors.err("can't use boolean operand with this operator $operator", assignment.position) -// } - super.visit(assignment) } override fun visit(assignTarget: AssignTarget) { - if(assignTarget.inferType(program).istype(DataType.LONG)) - errors.err("integer overflow", assignTarget.position) - super.visit(assignTarget) val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt() @@ -586,16 +561,13 @@ internal class AstChecker(private val program: Program, if (assignment is Assignment) { val targetDatatype = assignTarget.inferType(program) if (targetDatatype.isKnown) { - val constVal = assignment.value.constValue(program) - if(constVal==null) { - val sourceDatatype = assignment.value.inferType(program) - if (sourceDatatype.isUnknown) { - if (assignment.value !is FunctionCallExpression) - errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position) - } else { - checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED), - sourceDatatype.getOr(DataType.UNDEFINED), assignment.value) - } + val sourceDatatype = assignment.value.inferType(program) + if (sourceDatatype.isUnknown) { + if (assignment.value !is BinaryExpression && assignment.value !is PrefixExpression && assignment.value !is ContainmentCheck) + errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position) + } else { + checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED), + sourceDatatype.getOr(DataType.UNDEFINED), assignment.value) } } } @@ -619,6 +591,10 @@ internal class AstChecker(private val program: Program, if(decl.datatype==DataType.LONG) errors.err("integer overflow", decl.position) + if(decl.type==VarDeclType.MEMORY) { + if (decl.datatype == DataType.BOOL || decl.datatype == DataType.ARRAY_BOOL) + errors.err("variables mapped in memory should be numeric", decl.position) + } fun err(msg: String) = errors.err(msg, decl.position) @@ -628,8 +604,8 @@ internal class AstChecker(private val program: Program, // CONST can only occur on simple types (byte, word, float) if(decl.type== VarDeclType.CONST) { - if (decl.datatype !in NumericDatatypes) - err("const can only be used on numeric types (byte, word, float)") + if (decl.datatype !in NumericDatatypesWithBoolean) + err("const can only be used on numeric types or booleans") } // FLOATS enabled? @@ -717,11 +693,11 @@ internal class AstChecker(private val program: Program, val eltDt = ArrayToElementTypes.getValue(decl.datatype) if(iDt isnot eltDt) { if(!(iDt.isBool && eltDt==DataType.UBYTE || iDt.istype(DataType.UBYTE) && eltDt==DataType.BOOL)) - err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})") + err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})") } } else { if(!(iDt.isBool && decl.datatype==DataType.UBYTE || iDt.istype(DataType.UBYTE) && decl.datatype==DataType.BOOL)) - err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})") + err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})") } } } @@ -971,9 +947,13 @@ internal class AstChecker(private val program: Program, else if(expr.operator == "~") { if(dt !in IntegerDatatypes) errors.err("can only use bitwise invert on integer types", expr.position) - if(dt==DataType.BOOL) + else if(dt==DataType.BOOL) errors.err("bitwise invert is for integer types, use 'not' on booleans", expr.position) } + else if(expr.operator == "not") { + if(dt!=DataType.BOOL) + errors.err("logical not is for booleans", expr.position) + } super.visit(expr) } @@ -1014,11 +994,6 @@ internal class AstChecker(private val program: Program, errors.err("remainder can only be used on unsigned integer operands", expr.right.position) } } - "&", "|", "^" -> { - // only integer numeric operands accepted - if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes) - errors.err("bitwise operator can only be used on integer operands", expr.right.position) - } "in" -> throw FatalAstException("in expression should have been replaced by containmentcheck") "<<", ">>" -> { if(rightDt in WordDatatypes) { @@ -1043,10 +1018,12 @@ internal class AstChecker(private val program: Program, // expression with one side BOOL other side (U)BYTE is allowed; bool==byte } else if((expr.operator == "<<" || expr.operator == ">>") && (leftDt in WordDatatypes && rightDt in ByteDatatypes)) { // exception allowed: shifting a word by a byte + } else if((expr.operator in BitwiseOperators) && (leftDt in IntegerDatatypes && rightDt in IntegerDatatypes)) { + // exception allowed: bitwise operations with any integers } else if((leftDt==DataType.UWORD && rightDt==DataType.STR) || (leftDt==DataType.STR && rightDt==DataType.UWORD)) { // exception allowed: comparing uword (pointer) with string } else { - errors.err("left and right operands aren't the same type", expr.left.position) + errors.err("left and right operands aren't the same type", expr.position) } } @@ -1089,6 +1066,18 @@ internal class AstChecker(private val program: Program, errors.err("can't use boolean operand with this operator ${expr.operator}", expr.right.position) } } + + + if(expr.operator in LogicalOperators) { + if (leftDt != DataType.BOOL || rightDt != DataType.BOOL) + errors.err("logical operator requires boolean operands", expr.right.position) + } + else { + if (leftDt == DataType.BOOL || rightDt == DataType.BOOL) { + if(expr.operator!="==" && expr.operator!="!=") + errors.err("operator requires numeric operands", expr.right.position) + } + } } override fun visit(typecast: TypecastExpression) { @@ -1289,14 +1278,7 @@ internal class AstChecker(private val program: Program, if(target is Label && args.isNotEmpty()) errors.err("cannot use arguments when calling a label", position) - if(target is BuiltinFunctionPlaceholder) { - if(target.name=="all" || target.name=="any") { - if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program)?.datatype == DataType.STR - || args[0].inferType(program).getOr(DataType.STR) == DataType.STR) { - errors.err("any/all on a string is useless (is always true unless the string is empty)", position) - } - } - } else if(target is Subroutine) { + if(target is Subroutine) { if(target.isAsmSubroutine) { for (arg in args.zip(target.parameters)) { val argIDt = arg.first.inferType(program) @@ -1375,7 +1357,10 @@ internal class AstChecker(private val program: Program, } override fun visit(whenStmt: When) { - if(!whenStmt.condition.inferType(program).isInteger) + val conditionDt = whenStmt.condition.inferType(program) + if(conditionDt.isBool) + errors.err("condition is boolean, use if statement instead", whenStmt.position) + else if(!conditionDt.isInteger) errors.err("when condition must be an integer value", whenStmt.position) val tally = mutableSetOf() for((choices, choiceNode) in whenStmt.choiceValues(program)) { @@ -1406,7 +1391,7 @@ internal class AstChecker(private val program: Program, for((constvalue, pos) in constvalues) { when { constvalue == null -> errors.err("choice value must be a constant", pos) - constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", pos) + constvalue.type !in IntegerDatatypesWithBoolean -> errors.err("choice value must be a byte or word", pos) conditionType isnot constvalue.type -> { if(conditionType.isKnown) { if(conditionType.istype(DataType.BOOL)) { @@ -1492,7 +1477,8 @@ internal class AstChecker(private val program: Program, private fun checkLongType(expression: Expression) { if(expression.inferType(program).istype(DataType.LONG)) { - errors.err("integer overflow", expression.position) + if(errors.noErrorForLine(expression.position)) + errors.err("integer overflow", expression.position) } } @@ -1654,13 +1640,14 @@ internal class AstChecker(private val program: Program, return err("value '$number' out of range for word") } DataType.BOOL -> { - return true + if(value.type!=DataType.BOOL) + err("type of value ${value.type} doesn't match target $targetDt") } in ArrayDatatypes -> { val eltDt = ArrayToElementTypes.getValue(targetDt) return checkValueTypeAndRange(eltDt, value) } - else -> return err("value of type ${value.type} not compatible with $targetDt") + else -> return err("type of value ${value.type} doesn't match target $targetDt") } return true } @@ -1723,11 +1710,11 @@ internal class AstChecker(private val program: Program, } val result = when(targetDatatype) { - DataType.BOOL -> sourceDatatype in NumericDatatypes - DataType.BYTE -> sourceDatatype == DataType.BYTE || sourceDatatype == DataType.BOOL - DataType.UBYTE -> sourceDatatype == DataType.UBYTE || sourceDatatype == DataType.BOOL - DataType.WORD -> sourceDatatype in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.BOOL) - DataType.UWORD -> sourceDatatype == DataType.UBYTE || sourceDatatype == DataType.UWORD || sourceDatatype == DataType.BOOL + DataType.BOOL -> sourceDatatype==DataType.BOOL + DataType.BYTE -> sourceDatatype == DataType.BYTE + DataType.UBYTE -> sourceDatatype == DataType.UBYTE + DataType.WORD -> sourceDatatype in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD) + DataType.UWORD -> sourceDatatype == DataType.UBYTE || sourceDatatype == DataType.UWORD DataType.FLOAT -> sourceDatatype in NumericDatatypes DataType.STR -> sourceDatatype == DataType.STR else -> { @@ -1747,12 +1734,14 @@ internal class AstChecker(private val program: Program, } else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes) errors.err("cannot assign float to ${targetDatatype.name.lowercase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position) + else if((sourceValue as? BinaryExpression)?.operator in BitwiseOperators && targetDatatype.equalsSize(sourceDatatype)) { + // this is allowed: bitwise operation between different types as long as they're the same size. + } + else if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes) { + // this is allowed: a pass-by-reference datatype into a uword (pointer value). + } else { - if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes) { - // allow bitwise operations on different types as long as the size is the same - if (!((sourceValue as? BinaryExpression)?.operator in BitwiseOperators && targetDatatype.equalsSize(sourceDatatype))) - errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position) - } + errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position) } return false diff --git a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt index 157cc93a0..14a44f2f2 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt @@ -22,10 +22,6 @@ internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: Compila } internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) { - val boolRemover = BoolRemover(this) - boolRemover.visit(this) - boolRemover.applyModifications() - val fixer = BeforeAsmAstChanger(this, compilerOptions) fixer.visit(this) while (errors.noErrors() && fixer.applyModifications() > 0) { diff --git a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt index 559f659f4..3f0dd7462 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt @@ -136,7 +136,7 @@ class AstPreprocessor(val program: Program, } } else { // handle declaration of a single variable - if(decl.value!=null && decl.datatype in NumericDatatypes) { + if(decl.value!=null && (decl.datatype in NumericDatatypes || decl.datatype==DataType.BOOL)) { val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position) replacements.add(IAstModification.ReplaceNode(decl, assign, scope)) diff --git a/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt b/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt index 01317c48b..1a574cafb 100644 --- a/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt +++ b/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt @@ -26,7 +26,7 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co } override fun after(decl: VarDecl, parent: Node): Iterable { - if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes) + if (decl.type == VarDeclType.VAR && decl.value != null && (decl.datatype in NumericDatatypes || decl.datatype==DataType.BOOL)) throw InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl") return noModifications @@ -104,17 +104,7 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co override fun after(ifElse: IfElse, parent: Node): Iterable { val binExpr = ifElse.condition as? BinaryExpression - if(binExpr==null) { - // if x -> if x!=0 - val booleanExpr = BinaryExpression( - ifElse.condition, - "!=", - NumericLiteral.optimalInteger(0, ifElse.condition.position), - ifElse.condition.position - ) - return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse)) - } - + if(binExpr!=null) { if(binExpr.operator !in ComparisonOperators) { val constRight = binExpr.right.constValue(program) if(constRight!=null) { @@ -131,20 +121,13 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse)) } } - - // if x*5 -> if x*5 != 0 - val booleanExpr = BinaryExpression( - ifElse.condition, - "!=", - NumericLiteral.optimalInteger(0, ifElse.condition.position), - ifElse.condition.position - ) - return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse)) } if((binExpr.left as? NumericLiteral)?.number==0.0 && (binExpr.right as? NumericLiteral)?.number!=0.0) - throw InternalCompilerException("0==X should have been swapped to if X==0") + throw InternalCompilerException("0==X should be just X") + + } return noModifications } diff --git a/compiler/src/prog8/compiler/astprocessing/BeforeAsmTypecastCleaner.kt b/compiler/src/prog8/compiler/astprocessing/BeforeAsmTypecastCleaner.kt index 6fe2ab06a..042fc2646 100644 --- a/compiler/src/prog8/compiler/astprocessing/BeforeAsmTypecastCleaner.kt +++ b/compiler/src/prog8/compiler/astprocessing/BeforeAsmTypecastCleaner.kt @@ -15,14 +15,6 @@ internal class BeforeAsmTypecastCleaner(val program: Program, private val errors: IErrorReporter ) : AstWalker() { - override fun before(typecast: TypecastExpression, parent: Node): Iterable { - if(typecast.type == DataType.BOOL) { - val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(DataType.UBYTE, 0.0, typecast.position), typecast.position) - return listOf(IAstModification.ReplaceNode(typecast, notZero, parent)) - } - return noModifications - } - override fun after(typecast: TypecastExpression, parent: Node): Iterable { // see if we can remove redundant typecasts (outside of expressions) // such as casting byte<->ubyte, word<->uword or even redundant casts (sourcetype = target type). diff --git a/compiler/src/prog8/compiler/astprocessing/BoolRemover.kt b/compiler/src/prog8/compiler/astprocessing/BoolRemover.kt deleted file mode 100644 index 5a9477268..000000000 --- a/compiler/src/prog8/compiler/astprocessing/BoolRemover.kt +++ /dev/null @@ -1,133 +0,0 @@ -package prog8.compiler.astprocessing - -import prog8.ast.Node -import prog8.ast.Program -import prog8.ast.base.FatalAstException -import prog8.ast.expressions.* -import prog8.ast.statements.Subroutine -import prog8.ast.statements.SubroutineParameter -import prog8.ast.statements.VarDecl -import prog8.ast.walk.AstWalker -import prog8.ast.walk.IAstModification -import prog8.code.core.* - -internal class BoolRemover(val program: Program) : AstWalker() { - - override fun before(typecast: TypecastExpression, parent: Node): Iterable { - if(typecast.type == DataType.BOOL) { - val valueDt = typecast.expression.inferType(program).getOrElse { throw FatalAstException("unknown dt") } - val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(valueDt, 0.0, typecast.position), typecast.position) - return listOf(IAstModification.ReplaceNode(typecast, notZero, parent)) - } - return noModifications - } - - override fun after(decl: VarDecl, parent: Node): Iterable { - 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, null, decl.name, emptyList(), - newvalue, decl.sharedWithAsm, false, 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, emptyList(), - newarray, decl.sharedWithAsm, decl.splitArray, decl.position) - return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent)) - } - - return noModifications - } - - override fun after(subroutine: Subroutine, parent: Node): Iterable { - // replace BOOL return type and parameters by UBYTE - if(subroutine.returntypes.any { it==DataType.BOOL } || subroutine.parameters.any {it.type==DataType.BOOL}) { - val newReturnTypes = subroutine.returntypes.map { - if(it==DataType.BOOL) DataType.UBYTE else it - } - val newParams = subroutine.parameters.map { - if(it.type==DataType.BOOL) SubroutineParameter(it.name, DataType.UBYTE, it.position) else it - }.toMutableList() - val newSubroutine = Subroutine(subroutine.name, newParams, newReturnTypes, - subroutine.asmParameterRegisters, subroutine.asmReturnvaluesRegisters, subroutine.asmClobbers, - subroutine.asmAddress, subroutine.isAsmSubroutine, subroutine.inline, false, subroutine.statements, - subroutine.position) - return listOf(IAstModification.ReplaceNode(subroutine, newSubroutine, parent)) - } - - return noModifications - } - - override fun after(expr: BinaryExpression, parent: Node): Iterable { - if(expr.operator in setOf("and", "or", "xor")) { - // see if any of the arguments to a logical boolean expression need type casting to bool - val mods = mutableListOf() - val newLeft = wrapWithBooleanCastIfNeeded(expr.left, program) - val newRight = wrapWithBooleanCastIfNeeded(expr.right, program) - if(newLeft!=null) - mods += IAstModification.ReplaceNodeSafe(expr.left, newLeft, expr) - if(newRight!=null) - mods += IAstModification.ReplaceNodeSafe(expr.right, newRight, expr) - return mods - } - return noModifications - } - - override fun after(expr: PrefixExpression, parent: Node): Iterable { - if(expr.operator=="not") { - val binExpr = expr.expression as? BinaryExpression - if(binExpr!=null) { - val invertedOperator = invertedComparisonOperator(binExpr.operator) - if(invertedOperator!=null) { - val inverted = BinaryExpression(binExpr.left, invertedOperator, binExpr.right, binExpr.position) - return listOf(IAstModification.ReplaceNode(expr, inverted, parent)) - } - } - val exprDt = expr.expression.inferType(program).getOrElse { throw FatalAstException("unknown dt") } - val nonBoolDt = if(exprDt==DataType.BOOL) DataType.UBYTE else exprDt - val equalZero = BinaryExpression(expr.expression, "==", NumericLiteral(nonBoolDt, 0.0, expr.expression.position), expr.expression.position) - return listOf(IAstModification.ReplaceNode(expr, equalZero, parent)) - } - return noModifications - } -} - -internal fun wrapWithBooleanCastIfNeeded(expr: Expression, program: Program): Expression? { - fun isBoolean(expr: Expression): Boolean { - return if(expr.inferType(program) istype DataType.BOOL) - true - else if(expr is NumericLiteral && expr.type in IntegerDatatypes && (expr.number==0.0 || expr.number==1.0)) - 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)) - null - else - TypecastExpression(expr, DataType.BOOL, true, expr.position) -} diff --git a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt index 2359abf4d..29c9878cb 100644 --- a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt +++ b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt @@ -124,6 +124,11 @@ if not CONDITION } override fun after(whileLoop: WhileLoop, parent: Node): Iterable { + + if(!whileLoop.condition.inferType(program).isBool) + errors.err("condition should be a boolean", whileLoop.condition.position) + + /* while true -> repeat while false -> discard diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt index 0a2a34960..be0e980ae 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt @@ -558,8 +558,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr private fun transform(srcExpr: BinaryExpression): PtBinaryExpression { val type = srcExpr.inferType(program).getOrElse { throw FatalAstException("unknown dt") } - val actualType = if(type==DataType.BOOL) DataType.UBYTE else type - val expr = PtBinaryExpression(srcExpr.operator, actualType, srcExpr.position) + val expr = PtBinaryExpression(srcExpr.operator, type, srcExpr.position) expr.add(transformExpression(srcExpr.left)) expr.add(transformExpression(srcExpr.right)) return expr @@ -578,27 +577,27 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr fun desugar(range: RangeExpression): PtExpression { require(range.from.inferType(program)==range.to.inferType(program)) - val expr = PtBinaryExpression("and", DataType.UBYTE, srcCheck.position) + val expr = PtBinaryExpression("and", DataType.BOOL, srcCheck.position) val x1 = transformExpression(srcCheck.element) val x2 = transformExpression(srcCheck.element) val eltDt = srcCheck.element.inferType(program) if(eltDt.isInteger) { - val low = PtBinaryExpression("<=", DataType.UBYTE, srcCheck.position) + val low = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position) low.add(transformExpression(range.from)) low.add(x1) expr.add(low) - val high = PtBinaryExpression("<=", DataType.UBYTE, srcCheck.position) + val high = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position) high.add(x2) high.add(transformExpression(range.to)) expr.add(high) } else { - val low = PtBinaryExpression("<=", DataType.UBYTE, srcCheck.position) + val low = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position) val lowFloat = PtTypeCast(DataType.FLOAT, range.from.position) lowFloat.add(transformExpression(range.from)) low.add(lowFloat) low.add(x1) expr.add(low) - val high = PtBinaryExpression("<=", DataType.UBYTE, srcCheck.position) + val high = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position) high.add(x2) val highFLoat = PtTypeCast(DataType.FLOAT, range.to.position) highFLoat.add(transformExpression(range.to)) @@ -655,8 +654,12 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr return mem } - private fun transform(number: NumericLiteral): PtNumber = - PtNumber(number.type, number.number, number.position) + private fun transform(number: NumericLiteral): PtExpression { + return if(number.type==DataType.BOOL) + PtBool(number.asBooleanValue, number.position) + else + PtNumber(number.type, number.number, number.position) + } private fun transform(srcPrefix: PrefixExpression): PtPrefix { val type = srcPrefix.inferType(program).getOrElse { throw FatalAstException("unknown dt") } diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index c3e38151d..4cd513810 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -76,8 +76,8 @@ internal class LiteralsToAutoVars(private val program: Program, if(decl.names.size>1) { // note: the desugaring of a multi-variable vardecl has to be done here // and not in CodeDesugarer, that one is too late (identifiers can't be found otherwise) - if(decl.datatype !in NumericDatatypes) - errors.err("can only multi declare numeric variables", decl.position) + if(decl.datatype !in NumericDatatypesWithBoolean) + errors.err("can only multi declare numeric and boolean variables", decl.position) if(errors.noErrors()) { // desugar into individual vardecl per name. return decl.desugarMultiDecl().map { diff --git a/compiler/src/prog8/compiler/astprocessing/NotExpressionAndIfComparisonExprChanger.kt b/compiler/src/prog8/compiler/astprocessing/NotExpressionAndIfComparisonExprChanger.kt index d94d19887..3e192c6f4 100644 --- a/compiler/src/prog8/compiler/astprocessing/NotExpressionAndIfComparisonExprChanger.kt +++ b/compiler/src/prog8/compiler/astprocessing/NotExpressionAndIfComparisonExprChanger.kt @@ -6,13 +6,9 @@ import prog8.ast.base.FatalAstException import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.NumericLiteral import prog8.ast.expressions.PrefixExpression -import prog8.ast.expressions.invertCondition import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification -import prog8.code.core.DataType -import prog8.code.core.ICompilationTarget -import prog8.code.core.IErrorReporter -import prog8.code.core.IntegerDatatypes +import prog8.code.core.* internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val errors: IErrorReporter, val compTarget: ICompilationTarget) : AstWalker() { @@ -21,7 +17,7 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val val left = expr.left as? BinaryExpression if (left != null) { val rightValue = expr.right.constValue(program) - if (rightValue?.number == 0.0 && rightValue.type in IntegerDatatypes) { + if (rightValue?.number == 0.0 && rightValue.type in IntegerDatatypesWithBoolean) { if (left.operator == "==" && expr.operator == "==") { // (x==something)==0 --> x!=something left.operator = "!=" @@ -43,15 +39,11 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val } } - if(expr.operator=="^" && expr.left.inferType(program) istype DataType.BOOL && expr.right.constValue(program)?.number == 1.0) { - // boolean ^ 1 --> not boolean - return listOf(IAstModification.ReplaceNode(expr, invertCondition(expr.left, program), parent)) - } - - // applying De Morgan's laws proved beneficial for the code generator, // when the code has one outer 'not' instead of two inner ones. if(expr.operator=="or" || expr.operator=="and") { + + // boolean case val newOper = if(expr.operator=="or") "and" else "or" val leftP = expr.left as? PrefixExpression val rightP = expr.right as? PrefixExpression @@ -62,17 +54,25 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val val notExpr = PrefixExpression("not", inner, expr.position) return listOf(IAstModification.ReplaceNode(expr, notExpr, parent)) } - val leftB = expr.left as? BinaryExpression - val rightB = expr.right as? BinaryExpression - if(leftB!=null && leftB.operator=="==" && (leftB.right as? NumericLiteral)?.number==0.0 - && rightB!=null && rightB.operator=="==" && (rightB.right as? NumericLiteral)?.number==0.0) { - // a==0 or b==0 --> (a!=0 and b!=0)==0 - // a==0 and b==0 --> (a!=0 or b!=0)==0 - leftB.operator = "!=" - rightB.operator = "!=" - val inner = BinaryExpression(leftB, newOper, rightB, expr.position) - val notExpr = BinaryExpression(inner, "==", NumericLiteral.optimalInteger(0, expr.position), expr.position) - return listOf(IAstModification.ReplaceNode(expr, notExpr, parent)) + + // integer case (only if both are the same type) + val leftC = expr.left as? BinaryExpression + val rightC = expr.right as? BinaryExpression + if(leftC!=null && rightC!=null && leftC.operator=="==" && rightC.operator=="==") { + if (leftC.right.constValue(program)?.number == 0.0 && rightC.right.constValue(program)?.number == 0.0) { + val leftDt = leftC.left.inferType(program).getOr(DataType.UNDEFINED) + val rightDt = rightC.left.inferType(program).getOr(DataType.UNDEFINED) + if(leftDt==rightDt && leftDt in IntegerDatatypes) { + if (rightC.left.isSimple) { + // x==0 or y==0 -> (x & y)==0 + // x==0 and y==0 -> (x | y)==0 + val newOperator = if(expr.operator=="or") "&" else "|" + val inner = BinaryExpression(leftC.left, newOperator, rightC.left, expr.position) + val compare = BinaryExpression(inner, "==", NumericLiteral(leftDt, 0.0, expr.position), expr.position) + return listOf(IAstModification.ReplaceNode(expr, compare, parent)) + } + } + } } } @@ -82,17 +82,50 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val override fun after(expr: PrefixExpression, parent: Node): Iterable { if(expr.operator == "not") { - // first check if we're already part of a "boolean" expresion (i.e. comparing against 0) + // first check if we're already part of a "boolean" expression (i.e. comparing against 0 or 1) // if so, simplify THAT whole expression rather than making it more complicated - if(parent is BinaryExpression && parent.right.constValue(program)?.number==0.0) { - if(parent.operator=="==") { - // (NOT X)==0 --> X!=0 - val replacement = BinaryExpression(expr.expression, "!=", NumericLiteral.optimalInteger(0, expr.position), expr.position) - return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent)) - } else if(parent.operator=="!=") { - // (NOT X)!=0 --> X==0 - val replacement = BinaryExpression(expr.expression, "==", NumericLiteral.optimalInteger(0, expr.position), expr.position) - return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent)) + if (parent is BinaryExpression) { + if (parent.right.constValue(program)?.number == 0.0) { + if(parent.right.inferType(program).isBool) { + if(parent.operator=="==") { + // (NOT X)==false --> X==true --> X + return listOf(IAstModification.ReplaceNode(parent, expr.expression, parent.parent)) + } else if(parent.operator=="!=") { + // (NOT X)!=false --> X!=true -> not X + return listOf(IAstModification.ReplaceNode(parent, expr, parent.parent)) + } + } else { + if(parent.operator=="==") { + // (NOT X)==0 --> X!=0 + val replacement = BinaryExpression(expr.expression, "!=", NumericLiteral.optimalInteger(0, expr.position), expr.position) + return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent)) + } else if(parent.operator=="!=") { + // (NOT X)!=0 --> X==0 + val replacement = BinaryExpression(expr.expression, "==", NumericLiteral.optimalInteger(0, expr.position), expr.position) + return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent)) + } + } + } + else if (parent.right.constValue(program)?.number == 1.0) { + if(parent.right.inferType(program).isBool) { + if(parent.operator=="==") { + // (NOT X)==true --> X==false --> not X + return listOf(IAstModification.ReplaceNode(parent, expr, parent.parent)) + } else if(parent.operator=="!=") { + // (NOT X)!=true --> X!=false -> X + return listOf(IAstModification.ReplaceNode(parent, expr.expression, parent.parent)) + } + } else { + if(parent.operator=="==") { + // (NOT X)==1 --> X==0 + val replacement = BinaryExpression(expr.expression, "==", NumericLiteral.optimalInteger(0, expr.position), expr.position) + return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent)) + } else if(parent.operator=="!=") { + // (NOT X)!=1 --> X!=0 + val replacement = BinaryExpression(expr.expression, "!=", NumericLiteral.optimalInteger(0, expr.position), expr.position) + return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent)) + } + } } } @@ -120,13 +153,8 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val return listOf(IAstModification.ReplaceNode(expr, subBinExpr, parent)) } } - - // not simpleX -> simpleX==0 - if(expr.expression.isSimple) { - val replacement = BinaryExpression(expr.expression.copy(),"==", NumericLiteral(DataType.UBYTE, 0.0, expr.position), expr.position) - return listOf(IAstModification.ReplaceNodeSafe(expr, replacement, parent)) - } } + return noModifications } } diff --git a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt index 4e870efb5..7fb358f0f 100644 --- a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt +++ b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt @@ -44,7 +44,7 @@ internal class StatementReorderer( override fun after(decl: VarDecl, parent: Node): Iterable { if (decl.type == VarDeclType.VAR) { - if (decl.datatype in NumericDatatypes) { + if (decl.datatype in NumericDatatypes || decl.datatype==DataType.BOOL) { if(decl !in declsProcessedWithInitAssignment) { declsProcessedWithInitAssignment.add(decl) if (decl.value == null) { diff --git a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt index 3df2b1718..8277b255c 100644 --- a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt +++ b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt @@ -24,12 +24,11 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val val valueDt = declValue.inferType(program) if(valueDt isnot decl.datatype) { - // don't add a typecast on an array initializer value, unless booleans if(valueDt.isInteger && decl.isArray) { if(decl.datatype == DataType.ARRAY_BOOL) { val integer = declValue.constValue(program)?.number if(integer!=null) { - val num = NumericLiteral(DataType.UBYTE, if(integer==0.0) 0.0 else 1.0, declValue.position) + val num = NumericLiteral(DataType.BOOL, if(integer==0.0) 0.0 else 1.0, declValue.position) num.parent = decl decl.value = num } @@ -87,30 +86,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val } } - if(expr.operator in LogicalOperators && leftDt.isInteger && rightDt.isInteger) { - // see if any of the operands needs conversion to bool - val modifications = mutableListOf() - val newLeft = wrapWithBooleanCastIfNeeded(expr.left, program) - val newRight = wrapWithBooleanCastIfNeeded(expr.right, program) - if(newLeft!=null) - modifications += IAstModification.ReplaceNode(expr.left, newLeft, expr) - if(newRight!=null) - modifications += IAstModification.ReplaceNode(expr.right, newRight, expr) - if(modifications.isNotEmpty()) - return modifications - } if(leftDt!=rightDt) { - // convert bool type to byte if needed - if(leftDt istype DataType.BOOL && rightDt.isBytes && !rightDt.istype(DataType.BOOL)) { - if(rightCv==null || (rightCv.number!=1.0 && rightCv.number!=0.0)) - return listOf(IAstModification.ReplaceNode(expr.left, - TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED),true, expr.left.position), expr)) - } else if(leftDt.isBytes && !leftDt.istype(DataType.BOOL) && rightDt istype DataType.BOOL) { - if(leftCv==null || (leftCv.number!=1.0 && leftCv.number!=0.0)) - return listOf(IAstModification.ReplaceNode(expr.right, - TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED),true, expr.right.position), expr)) - } - // convert a negative operand for bitwise operator to the 2's complement positive number instead if(expr.operator in BitwiseOperators && leftDt.isInteger && rightDt.isInteger) { if(leftCv!=null && leftCv.number<0) { @@ -169,13 +145,18 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val // determine common datatype and add typecast as required to make left and right equal types val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right) if(toFix!=null) { - val modifications = mutableListOf() - when { - toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr) - toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr) - else -> throw FatalAstException("confused binary expression side") + if(commonDt==DataType.BOOL) { + // don't automatically cast to bool + errors.err("left and right operands aren't the same type", expr.position) + } else { + val modifications = mutableListOf() + when { + toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr) + toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr) + else -> throw FatalAstException("confused binary expression side") + } + return modifications } - return modifications } } } @@ -196,10 +177,6 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val if(valuetype in IterableDatatypes && targettype==DataType.UWORD) // special case, don't typecast STR/arrays to UWORD, we support those assignments "directly" return noModifications - if((assignment.value as? BinaryExpression)?.operator in ComparisonOperators && targettype in IntegerDatatypes) { - // special case, treat a boolean comparison result as the same type as the target value to avoid needless casts later - return noModifications - } val modifications = mutableListOf() addTypecastOrCastedValueModification(modifications, assignment.value, targettype, assignment) return modifications @@ -244,7 +221,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val } private fun afterFunctionCallArgs(call: IFunctionCall): Iterable { - // see if a typecast is needed to convert the arguments into the required parameter's type + // see if a typecast is needed to convert the arguments into the required parameter type val modifications = mutableListOf() val sub = call.target.targetStatement(program) val params = when(sub) { @@ -398,6 +375,8 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val val sourceDt = expressionToCast.inferType(program).getOr(DataType.UNDEFINED) if(sourceDt == requiredType) return + if(requiredType==DataType.BOOL) + return if(expressionToCast is NumericLiteral && expressionToCast.type!=DataType.FLOAT) { // refuse to automatically truncate floats val castedValue = expressionToCast.cast(requiredType, true) if (castedValue.isValid) { diff --git a/compiler/src/prog8/compiler/astprocessing/VerifyFunctionArgTypes.kt b/compiler/src/prog8/compiler/astprocessing/VerifyFunctionArgTypes.kt index a0cb04c25..32f197606 100644 --- a/compiler/src/prog8/compiler/astprocessing/VerifyFunctionArgTypes.kt +++ b/compiler/src/prog8/compiler/astprocessing/VerifyFunctionArgTypes.kt @@ -105,12 +105,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe if(mismatch>=0) { val actual = argtypes[mismatch] val expected = consideredParamTypes[mismatch] - return if(actual==DataType.BOOL && expected in ByteDatatypes) - null // a bool is just 1 or 0. - else if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0)) - null // specifying a 1 or 0 as a BOOL is okay - else - Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position) + return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position) } if(target.isAsmSubroutine) { if(target.asmReturnvaluesRegisters.size>1) { diff --git a/intermediate/src/prog8/intermediate/IRFileReader.kt b/intermediate/src/prog8/intermediate/IRFileReader.kt index df2e66e1a..2f0eba6f3 100644 --- a/intermediate/src/prog8/intermediate/IRFileReader.kt +++ b/intermediate/src/prog8/intermediate/IRFileReader.kt @@ -174,7 +174,7 @@ class IRFileReader { if('.' !in name) throw IRParseException("unscoped varname: $name") val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null - val dt: DataType = parseDatatype(type, arraysize!=null) + val dt = parseDatatype(type, arraysize!=null) val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish) val dummyNode = PtVariable(name, dt, zp, null, null, Position.DUMMY) val newVar = StStaticVariable(name, dt, null, null, null, arraysize, zp, dummyNode) @@ -210,7 +210,8 @@ class IRFileReader { var initNumeric: Double? = null var initArray: StArray? = null when(dt) { - in NumericDatatypes -> initNumeric = parseIRValue(value).toDouble() + DataType.BOOL -> TODO("parse boolean $value") + in NumericDatatypes -> initNumeric = parseIRValue(value) in ArrayDatatypes -> { initArray = value.split(',').map { if (it.startsWith('@')) @@ -496,24 +497,24 @@ class IRFileReader { private fun parseDatatype(type: String, isArray: Boolean): DataType { if(isArray) { return when(type) { + // note: there are no BOOLEANS anymore in the IR. Only UBYTE. "byte" -> DataType.ARRAY_B "ubyte", "str" -> DataType.ARRAY_UB "word" -> DataType.ARRAY_W "uword" -> DataType.ARRAY_UW "float" -> DataType.ARRAY_F - "bool" -> DataType.ARRAY_B "uword_split" -> DataType.ARRAY_UW_SPLIT "word_split" -> DataType.ARRAY_W_SPLIT else -> throw IRParseException("invalid dt") } } else { return when(type) { + // note: there are no BOOLEANS anymore in the IR. Only UBYTE. "byte" -> DataType.BYTE "ubyte" -> DataType.UBYTE "word" -> DataType.WORD "uword" -> DataType.UWORD "float" -> DataType.FLOAT - "bool" -> DataType.BOOL // note: 'str' should not occur anymore in IR. Should be 'uword' else -> throw IRParseException("invalid dt") } diff --git a/intermediate/src/prog8/intermediate/IRFileWriter.kt b/intermediate/src/prog8/intermediate/IRFileWriter.kt index d5dfddf24..e2c765c66 100644 --- a/intermediate/src/prog8/intermediate/IRFileWriter.kt +++ b/intermediate/src/prog8/intermediate/IRFileWriter.kt @@ -100,7 +100,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { is IRSubroutine -> { xml.writeStartElement("SUB") xml.writeAttribute("NAME", child.label) - xml.writeAttribute("RETURNTYPE", child.returnType?.toString()?.lowercase() ?: "") + xml.writeAttribute("RETURNTYPE", child.returnType?.typeString(null)?.lowercase() ?: "") xml.writeAttribute("POS", child.position.toString()) xml.writeCharacters("\n") xml.writeStartElement("PARAMS") @@ -247,8 +247,9 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { xml.writeCharacters("ubyte[${variable.length}] ${variable.name}_msb=$msbValue zp=${variable.zpwish}\n") } else { val value: String = when(variable.dt) { + DataType.BOOL -> variable.onetimeInitializationNumericValue?.toInt()?.toString() ?: "" DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: "").toString() - in NumericDatatypes -> (variable.onetimeInitializationNumericValue?.toInt()?.toHex() ?: "").toString() + in NumericDatatypes -> variable.onetimeInitializationNumericValue?.toInt()?.toHex() ?: "" DataType.STR -> { val encoded = irProgram.encoding.encodeString(variable.onetimeInitializationStringValue!!.first, variable.onetimeInitializationStringValue.second) + listOf(0u) encoded.joinToString(",") { it.toInt().toString() } diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index 34b436097..0988d2124 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -409,8 +409,8 @@ class IRSubroutine( require(!label.startsWith("main.main.")) {"subroutine name invalid main prefix: $label"} // params and return value should not be str - require(parameters.all{ it.dt in NumericDatatypes }) {"non-numeric parameter"} - require(returnType==null || returnType in NumericDatatypes) {"non-numeric returntype $returnType"} + require(parameters.all{ it.dt in NumericDatatypes || it.dt==DataType.BOOL }) {"non-numeric/non-bool parameter"} + require(returnType==null || returnType in NumericDatatypes || returnType==DataType.BOOL) {"non-numeric/non-bool returntype $returnType"} } operator fun plusAssign(chunk: IRCodeChunkBase) { diff --git a/intermediate/src/prog8/intermediate/IRSymbolTable.kt b/intermediate/src/prog8/intermediate/IRSymbolTable.kt index ccf9985b0..598bc63f8 100644 --- a/intermediate/src/prog8/intermediate/IRSymbolTable.kt +++ b/intermediate/src/prog8/intermediate/IRSymbolTable.kt @@ -80,7 +80,13 @@ class IRSymbolTable { return newArray } scopedName = variable.scopedName - varToadd = IRStStaticVariable(scopedName, variable.dt, + val dt = when(variable.dt) { + DataType.BOOL -> DataType.UBYTE + DataType.ARRAY_BOOL -> DataType.ARRAY_UB + else -> variable.dt + } + varToadd = IRStStaticVariable(scopedName, + dt, variable.onetimeInitializationNumericValue, variable.onetimeInitializationStringValue, fixupAddressOfInArray(variable.onetimeInitializationArrayValue), @@ -166,6 +172,10 @@ class IRStMemVar(name: String, } } + init { + require(dt!=DataType.BOOL && dt!=DataType.ARRAY_BOOL) + } + val typeString: String = dt.typeString(length) } @@ -205,6 +215,10 @@ class IRStStaticVariable(name: String, } } + init { + require(dt!=DataType.BOOL && dt!=DataType.ARRAY_BOOL) + } + val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null val typeString: String = dt.typeString(length) @@ -213,9 +227,17 @@ class IRStStaticVariable(name: String, class IRStArrayElement(val number: Double?, val addressOfSymbol: String?) { companion object { fun from(elt: StArrayElement): IRStArrayElement { - return IRStArrayElement(elt.number, elt.addressOfSymbol) + if(elt.boolean!=null) + return IRStArrayElement(if(elt.boolean==true) 1.0 else 0.0, elt.addressOfSymbol) + else + return IRStArrayElement(elt.number, elt.addressOfSymbol) } } + + init { + // TODO TEMPORARY + require(number!=null || addressOfSymbol!=null) + } } typealias IRStArray = List diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index cd7ef7b49..191dc35eb 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -9,15 +9,15 @@ import prog8.code.right fun DataType.typeString(length: Int?): String { val lengthStr = if(length==0) "" else length.toString() return when (this) { - DataType.BOOL -> "bool" + DataType.BOOL -> "ubyte" // in IR , a boolean is represented by an ubyte. DataType.UBYTE -> "ubyte" DataType.BYTE -> "byte" DataType.UWORD -> "uword" DataType.WORD -> "word" DataType.LONG -> "long" DataType.FLOAT -> "float" - DataType.STR -> "ubyte[$lengthStr]" // here string doesn't exist as a seperate datatype anymore - DataType.ARRAY_BOOL -> "bool[$lengthStr]" + DataType.STR -> "ubyte[$lengthStr]" // here string doesn't exist as a seperate datatype anymore + DataType.ARRAY_BOOL -> "ubyte[$lengthStr]" // in IR , a boolean is represented by an ubyte. DataType.ARRAY_UB -> "ubyte[$lengthStr]" DataType.ARRAY_B -> "byte[$lengthStr]" DataType.ARRAY_UW -> "uword[$lengthStr]"