nostrictbool array conversions

This commit is contained in:
Irmen de Jong 2024-03-03 17:48:52 +01:00
parent da1620807f
commit 84afb374e6
17 changed files with 183 additions and 59 deletions

View File

@ -26,6 +26,7 @@ class CompilationOptions(val output: OutputType,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var splitWordArrays: Boolean = false,
var strictBool: Boolean = true,
var breakpointCpuInstruction: String? = null,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()

View File

@ -270,7 +270,11 @@ class VarConstantValueTypeAdjuster(
// Replace all constant identifiers with their actual value,
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
internal class ConstantIdentifierReplacer(
private val program: Program,
private val options: CompilationOptions,
private val errors: IErrorReporter
) : AstWalker() {
override fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
val constValue = addressOf.constValue(program)
@ -472,7 +476,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the array size.
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
if (fillvalue < options.compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > options.compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
@ -486,7 +490,8 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(numericLv!=null) {
// arraysize initializer is a single value, and we know the array size.
if(numericLv.type!=DataType.BOOL) {
errors.err("initializer value is not a boolean", numericLv.position)
if(options.strictBool || numericLv.type !in ByteDatatypes)
errors.err("initializer value is not a boolean", numericLv.position)
return null
}
val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()

View File

@ -12,7 +12,7 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
if(errors.noErrors()) {
valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, errors, options.compTarget)
val replacer = ConstantIdentifierReplacer(this, options, errors)
replacer.visit(this)
if (errors.noErrors()) {
replacer.applyModifications()

View File

@ -44,6 +44,7 @@ private fun compileMain(args: Array<String>): Boolean {
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
val dumpVariables by cli.option(ArgType.Boolean, fullName = "dumpvars", description = "print a dump of the variables in the program")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val noStrictBool by cli.option(ArgType.Boolean, fullName = "nostrictbool", description = "allow implicit conversions between bool and bytes").default(false)
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform code optimizations")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
@ -163,6 +164,7 @@ private fun compileMain(args: Array<String>): Boolean {
breakpointCpuInstruction,
printAst1 == true,
printAst2 == true,
noStrictBool == false,
processedSymbols,
srcdirs,
outputPath
@ -242,6 +244,7 @@ private fun compileMain(args: Array<String>): Boolean {
breakpointCpuInstruction,
printAst1 == true,
printAst2 == true,
noStrictBool == false,
processedSymbols,
srcdirs,
outputPath

View File

@ -6,10 +6,10 @@ package prog8.buildversion
const val MAVEN_GROUP = "prog8"
const val MAVEN_NAME = "compiler"
const val VERSION = "10.2-BOOLEANS"
const val GIT_REVISION = 4512
const val GIT_SHA = "a977e3402faab7f2b63af7127fd628cbbdb8a7ad"
const val GIT_DATE = "2024-02-25T15:41:16Z"
const val GIT_BRANCH = "booleans"
const val BUILD_DATE = "2024-02-25T16:07:25Z"
const val BUILD_UNIX_TIME = 1708877245670L
const val GIT_REVISION = 4530
const val GIT_SHA = "c7843b4ed8919870983fdf9d27413caf690d948a"
const val GIT_DATE = "2024-03-03T13:01:46Z"
const val GIT_BRANCH = "booleans-nostrictbool"
const val BUILD_DATE = "2024-03-03T13:12:36Z"
const val BUILD_UNIX_TIME = 1709471556477L
const val DIRTY = 1

View File

@ -48,6 +48,7 @@ class CompilerArguments(val filepath: Path,
val breakpointCpuInstruction: String?,
val printAst1: Boolean,
val printAst2: Boolean,
val strictBool: Boolean,
val symbolDefs: Map<String, String>,
val sourceDirs: List<String> = emptyList(),
val outputDir: Path = Path(""),
@ -93,6 +94,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
splitWordArrays = args.splitWordArrays
outputDir = args.outputDir.normalize()
symbolDefs = args.symbolDefs
strictBool = args.strictBool
}
program = programresult
importedFiles = imported
@ -404,7 +406,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report()
program.reorderStatements(errors)
errors.report()
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
errors.report()
program.changeNotExpressionAndIfComparisonExpr(errors, compilerOptions.compTarget)
errors.report()
@ -448,13 +450,13 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
}
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
program.addTypecasts(errors, compilerOptions)
errors.report()
program.variousCleanups(errors, compilerOptions)
val callGraph = CallGraph(program)
callGraph.checkRecursiveCalls(errors)
program.verifyFunctionArgTypes(errors)
program.verifyFunctionArgTypes(errors, compilerOptions)
errors.report()
program.moveMainBlockAsFirst()
program.checkValid(errors, compilerOptions) // check if final tree is still valid

View File

@ -150,8 +150,11 @@ internal class AstChecker(private val program: Program,
}
override fun visit(ifElse: IfElse) {
if(!ifElse.condition.inferType(program).isBool)
errors.err("condition should be a boolean", ifElse.condition.position)
if(!ifElse.condition.inferType(program).isBool) {
val ctype = ifElse.condition.inferType(program).getOr(DataType.UNDEFINED)
if(compilerOptions.strictBool || ctype !in ByteDatatypes)
errors.err("condition should be a boolean", ifElse.condition.position)
}
val constvalue = ifElse.condition.constValue(program)
if(constvalue!=null) {
@ -476,15 +479,21 @@ internal class AstChecker(private val program: Program,
}
override fun visit(untilLoop: UntilLoop) {
if(!untilLoop.condition.inferType(program).isBool)
errors.err("condition should be a boolean", untilLoop.condition.position)
if(!untilLoop.condition.inferType(program).isBool) {
val ctype = untilLoop.condition.inferType(program).getOr(DataType.UNDEFINED)
if(compilerOptions.strictBool || ctype !in ByteDatatypes)
errors.err("condition should be a boolean", untilLoop.condition.position)
}
super.visit(untilLoop)
}
override fun visit(whileLoop: WhileLoop) {
if(!whileLoop.condition.inferType(program).isBool)
errors.err("condition should be a boolean", whileLoop.condition.position)
if(!whileLoop.condition.inferType(program).isBool) {
val ctype = whileLoop.condition.inferType(program).getOr(DataType.UNDEFINED)
if(compilerOptions.strictBool || ctype !in ByteDatatypes)
errors.err("condition should be a boolean", whileLoop.condition.position)
}
super.visit(whileLoop)
}
@ -697,7 +706,9 @@ internal class AstChecker(private val program: Program,
if(decl.isArray) {
val eltDt = ArrayToElementTypes.getValue(decl.datatype)
if(iDt isnot eltDt) {
if(!(iDt.isBool && eltDt==DataType.UBYTE || iDt.istype(DataType.UBYTE) && eltDt==DataType.BOOL))
if(compilerOptions.strictBool)
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
else if(!(iDt.isBool && eltDt==DataType.UBYTE || iDt.istype(DataType.UBYTE) && eltDt==DataType.BOOL))
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
}
} else {
@ -1153,7 +1164,7 @@ internal class AstChecker(private val program: Program,
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCallExpr.args.first().position)
}
val error = VerifyFunctionArgTypes.checkTypes(functionCallExpr, program)
val error = VerifyFunctionArgTypes.checkTypes(functionCallExpr, program, compilerOptions)
if(error!=null)
errors.err(error.first, error.second)
@ -1256,7 +1267,7 @@ internal class AstChecker(private val program: Program,
}
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, program, compilerOptions)
if(error!=null)
errors.err(error.first, error.second)
@ -1645,8 +1656,10 @@ internal class AstChecker(private val program: Program,
return err("value '$number' out of range for word")
}
DataType.BOOL -> {
if(value.type!=DataType.BOOL)
err("type of value ${value.type} doesn't match target $targetDt")
if (value.type!=DataType.BOOL) {
if (compilerOptions.strictBool || value.type !in ByteDatatypes)
err("type of value ${value.type} doesn't match target $targetDt")
}
}
in ArrayDatatypes -> {
val eltDt = ArrayToElementTypes.getValue(targetDt)
@ -1745,6 +1758,14 @@ internal class AstChecker(private val program: Program,
else if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes) {
// this is allowed: a pass-by-reference datatype into a uword (pointer value).
}
else if(sourceDatatype==DataType.BOOL && targetDatatype!=DataType.BOOL) {
if(compilerOptions.strictBool || targetDatatype !in ByteDatatypes)
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
else if(targetDatatype==DataType.BOOL && sourceDatatype!=DataType.BOOL) {
if(compilerOptions.strictBool || sourceDatatype !in ByteDatatypes)
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
else {
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}

View File

@ -92,15 +92,15 @@ internal fun Program.addTypecasts(errors: IErrorReporter, options: CompilationOp
caster.applyModifications()
}
fun Program.desugaring(errors: IErrorReporter) {
val desugar = CodeDesugarer(this, errors)
fun Program.desugaring(errors: IErrorReporter, options: CompilationOptions) {
val desugar = CodeDesugarer(this, options, errors)
desugar.visit(this)
while(errors.noErrors() && desugar.applyModifications()>0)
desugar.visit(this)
}
internal fun Program.verifyFunctionArgTypes(errors: IErrorReporter) {
val fixer = VerifyFunctionArgTypes(this, errors)
internal fun Program.verifyFunctionArgTypes(errors: IErrorReporter, options: CompilationOptions) {
val fixer = VerifyFunctionArgTypes(this, options, errors)
fixer.visit(this)
}
@ -117,7 +117,7 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
checker2.visit(this)
if(errors.noErrors()) {
val lit2decl = LiteralsToAutoVars(this, options.compTarget, errors)
val lit2decl = LiteralsToAutoVars(this, errors)
lit2decl.visit(this)
if(errors.noErrors())
lit2decl.applyModifications()

View File

@ -5,13 +5,10 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.ComparisonOperators
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.code.core.*
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
internal class CodeDesugarer(val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer !
@ -125,8 +122,11 @@ if not CONDITION
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
if(!whileLoop.condition.inferType(program).isBool)
errors.err("condition should be a boolean", whileLoop.condition.position)
if(!whileLoop.condition.inferType(program).isBool) {
val ctype = whileLoop.condition.inferType(program).getOr(DataType.UNDEFINED)
if(options.strictBool || ctype !in ByteDatatypes)
errors.err("condition should be a boolean", whileLoop.condition.position)
}
/*
while true -> repeat

View File

@ -13,13 +13,13 @@ import prog8.ast.statements.VarDecl
import prog8.ast.statements.WhenChoice
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.NumericDatatypesWithBoolean
import prog8.code.core.SplitWordArrayTypes
internal class LiteralsToAutoVars(private val program: Program,
private val target: ICompilationTarget,
private val errors: IErrorReporter
) : AstWalker() {
internal class LiteralsToAutoVars(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {

View File

@ -24,6 +24,10 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val valueDt = declValue.inferType(program)
if(valueDt isnot decl.datatype) {
if(decl.isArray && !options.strictBool) {
tryConvertBooleanArrays(decl, declValue, parent)
}
if(valueDt.isInteger && decl.isArray) {
if(decl.datatype == DataType.ARRAY_BOOL) {
val integer = declValue.constValue(program)?.number
@ -49,6 +53,62 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return noModifications
}
private fun tryConvertBooleanArrays(decl: VarDecl, declValue: Expression, parent: Node) {
val valueNumber = declValue.constValue(program)
val valueArray = declValue as? ArrayLiteral
when (decl.datatype) {
DataType.ARRAY_BOOL -> {
if(valueNumber!=null) {
decl.value = NumericLiteral.fromBoolean(valueNumber.number!=0.0, declValue.position)
decl.linkParents(parent)
} else if(valueArray!=null) {
val newArray = valueArray.value.map {
if(it.inferType(program).isBytes) {
TypecastExpression(it, DataType.BOOL, false, it.position)
} else {
it
}
}
decl.value = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), newArray.toTypedArray(), valueArray.position)
decl.linkParents(parent)
}
}
DataType.ARRAY_B -> {
if(valueNumber!=null) {
decl.value = NumericLiteral(DataType.BYTE, if(valueNumber.asBooleanValue) 1.0 else 0.0, declValue.position)
decl.linkParents(parent)
} else if(valueArray!=null) {
val newArray = valueArray.value.map {
if(it.inferType(program).isBool) {
TypecastExpression(it, DataType.BYTE, false, it.position)
} else {
it
}
}
decl.value = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_B), newArray.toTypedArray(), valueArray.position)
decl.linkParents(parent)
}
}
DataType.ARRAY_UB -> {
if(valueNumber!=null) {
decl.value = NumericLiteral(DataType.UBYTE, if(valueNumber.asBooleanValue) 1.0 else 0.0, declValue.position)
decl.linkParents(parent)
} else if(valueArray!=null) {
val newArray = valueArray.value.map {
if(it.inferType(program).isBool) {
TypecastExpression(it, DataType.UBYTE, false, it.position)
} else {
it
}
}
decl.value = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_UB), newArray.toTypedArray(), valueArray.position)
decl.linkParents(parent)
}
}
else -> { /* no casting possible */ }
}
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
@ -192,6 +252,17 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(cvalue!=null) {
val number = cvalue.number
// more complex comparisons if the type is different, but the constant value is compatible
if(!options.strictBool) {
if (targettype == DataType.BOOL && valuetype in ByteDatatypes) {
return castLiteral(cvalue)
}
if (targettype == DataType.UBYTE && valuetype == DataType.BOOL) {
return castLiteral(cvalue)
}
if (targettype == DataType.BYTE && valuetype == DataType.BOOL) {
return castLiteral(cvalue)
}
}
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
if(number>0)
return castLiteral(cvalue)

View File

@ -7,7 +7,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorReporter) : IAstVisitor {
internal class VerifyFunctionArgTypes(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : IAstVisitor {
override fun visit(program: Program) {
super.visit(program)
@ -41,7 +41,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
private val memorySlabs = mutableListOf<Slab>()
override fun visit(functionCallExpr: FunctionCallExpression) {
val error = checkTypes(functionCallExpr as IFunctionCall, program)
val error = checkTypes(functionCallExpr as IFunctionCall, program, options)
if(error!=null)
errors.err(error.first, error.second)
else {
@ -62,7 +62,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, program)
val error = checkTypes(functionCallStatement as IFunctionCall, program, options)
if(error!=null)
errors.err(error.first, error.second)
@ -85,7 +85,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
return false
}
fun checkTypes(call: IFunctionCall, program: Program): Pair<String, Position>? {
fun checkTypes(call: IFunctionCall, program: Program, options: CompilationOptions): Pair<String, Position>? {
val argITypes = call.args.map { it.inferType(program) }
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0) {
@ -105,7 +105,12 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
if(mismatch>=0) {
val actual = argtypes[mismatch]
val expected = consideredParamTypes[mismatch]
return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
return if(expected==DataType.BOOL && !options.strictBool && actual in ByteDatatypes)
null
else if(expected in ByteDatatypes && !options.strictBool && actual==DataType.BOOL)
null
else
Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
}
if(target.isAsmSubroutine) {
if(target.asmReturnvaluesRegisters.size>1) {

View File

@ -42,6 +42,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false,
strictBool = true,
symbolDefs = emptyMap(),
outputDir = outputDir
)

View File

@ -40,6 +40,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false,
strictBool = true,
symbolDefs = emptyMap(),
sourceDirs,
outputDir

View File

@ -41,7 +41,8 @@ internal fun compileFile(
splitWordArrays = false,
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false
printAst2 = false,
strictBool = true
)
return compileProgram(args)
}

View File

@ -5,22 +5,34 @@
main {
sub start() {
word @shared w = -20
word[] warr = [-1111, -2222, -3333]
word[] @split swarr = [-1111, -2222, -3333]
cx16.r0L = true
cx16.r1L = false
cx16.r0L=1
if warr[cx16.r0L] > 0
txt.print("yep1")
bool @shared bzz = 1
ubyte @shared ubb = true
if warr[cx16.r0L] <= 0
txt.print("yep2")
bool @shared bb1, bb2
bb1 = 0
bb2 = 44
if swarr[cx16.r0L] > 0
txt.print("yep3")
; bool[3] barr1 = 42
byte[3] @shared sbarr1 = true
ubyte[3] @shared ubarr1 = true
if swarr[cx16.r0L] <= 0
txt.print("yep4")
bool[] @shared boolarray = [1,0]
bool[] @shared boolarray2 = [42,0,false]
byte[] @shared sba = [true, false]
byte[] @shared sba2 = [true, false, 42]
ubyte[] @shared uba = [true, false]
ubyte[] @shared uba2 = [true, false, 42]
txt.print_ubhex(bb1, 1)
txt.print_ubhex(bb2, 42)
txt.print_ubhex(bb2, cx16.r0L)
if cx16.r0L {
cx16.r0L++
}
}
}

View File

@ -45,6 +45,7 @@ class RequestParser : Take {
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false,
strictBool = true,
varsHighBank = null,
varsGolden = false,
slabsHighBank = null,