mirror of
https://github.com/irmen/prog8.git
synced 2025-01-10 20:30:23 +00:00
allow const expression intermediate values to be 32 bits integers to avoid needless overflow errors.
This commit is contained in:
parent
2b227b43fe
commit
c609e982fe
@ -1,12 +1,13 @@
|
|||||||
package prog8.code.core
|
package prog8.code.core
|
||||||
|
|
||||||
enum class DataType {
|
enum class DataType {
|
||||||
UBYTE, // pass by value
|
UBYTE, // pass by value 8 bits unsigned
|
||||||
BYTE, // pass by value
|
BYTE, // pass by value 8 bits signed
|
||||||
UWORD, // pass by value
|
UWORD, // pass by value 16 bits unsigned
|
||||||
WORD, // pass by value
|
WORD, // pass by value 16 bits signed
|
||||||
FLOAT, // pass by value
|
LONG, // pass by value 32 bits signed
|
||||||
BOOL, // pass by value
|
FLOAT, // pass by value machine dependent
|
||||||
|
BOOL, // pass by value bit 0 of a 8 bit byte
|
||||||
STR, // pass by reference
|
STR, // pass by reference
|
||||||
ARRAY_UB, // pass by reference
|
ARRAY_UB, // pass by reference
|
||||||
ARRAY_B, // pass by reference
|
ARRAY_B, // pass by reference
|
||||||
@ -23,11 +24,12 @@ enum class DataType {
|
|||||||
*/
|
*/
|
||||||
infix fun isAssignableTo(targetType: DataType) =
|
infix fun isAssignableTo(targetType: DataType) =
|
||||||
when(this) {
|
when(this) {
|
||||||
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, FLOAT)
|
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, LONG, FLOAT)
|
||||||
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT, BOOL)
|
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT, BOOL)
|
||||||
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT)
|
BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT)
|
||||||
UWORD -> targetType.oneOf(UWORD, FLOAT)
|
UWORD -> targetType.oneOf(UWORD, LONG, FLOAT)
|
||||||
WORD -> targetType.oneOf(WORD, FLOAT)
|
WORD -> targetType.oneOf(WORD, LONG, FLOAT)
|
||||||
|
LONG -> targetType.oneOf(LONG, FLOAT)
|
||||||
FLOAT -> targetType.oneOf(FLOAT)
|
FLOAT -> targetType.oneOf(FLOAT)
|
||||||
STR -> targetType.oneOf(STR, UWORD)
|
STR -> targetType.oneOf(STR, UWORD)
|
||||||
in ArrayDatatypes -> targetType == this
|
in ArrayDatatypes -> targetType == this
|
||||||
@ -41,7 +43,8 @@ enum class DataType {
|
|||||||
this == other -> false
|
this == other -> false
|
||||||
this in ByteDatatypes -> false
|
this in ByteDatatypes -> false
|
||||||
this in WordDatatypes -> other in ByteDatatypes
|
this in WordDatatypes -> other in ByteDatatypes
|
||||||
this== STR && other== UWORD || this== UWORD && other== STR -> false
|
this == LONG -> other in ByteDatatypes+WordDatatypes
|
||||||
|
this == STR && other == UWORD || this == UWORD && other == STR -> false
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,11 +126,11 @@ enum class BranchCondition {
|
|||||||
|
|
||||||
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
|
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
|
||||||
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
|
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
|
||||||
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.BOOL)
|
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG)
|
||||||
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
val IntegerDatatypes = IntegerDatatypesNoBool + DataType.BOOL
|
||||||
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT, DataType.BOOL)
|
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT)
|
||||||
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
val NumericDatatypes = NumericDatatypesNoBool + DataType.BOOL
|
||||||
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.LONG, DataType.FLOAT)
|
||||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL)
|
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL)
|
||||||
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||||
val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT)
|
val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT)
|
||||||
|
@ -62,6 +62,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(identifier: IdentifierReference) {
|
override fun visit(identifier: IdentifierReference) {
|
||||||
|
checkLongType(identifier)
|
||||||
val stmt = identifier.targetStatement(program)
|
val stmt = identifier.targetStatement(program)
|
||||||
if(stmt==null)
|
if(stmt==null)
|
||||||
errors.undefined(identifier.nameInSource, identifier.position)
|
errors.undefined(identifier.nameInSource, identifier.position)
|
||||||
@ -279,6 +280,10 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(label)
|
super.visit(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(numLiteral: NumericLiteral) {
|
||||||
|
checkLongType(numLiteral)
|
||||||
|
}
|
||||||
|
|
||||||
private fun hasReturnOrJumpOrRts(scope: IStatementContainer): Boolean {
|
private fun hasReturnOrJumpOrRts(scope: IStatementContainer): Boolean {
|
||||||
class Searcher: IAstVisitor
|
class Searcher: IAstVisitor
|
||||||
{
|
{
|
||||||
@ -528,6 +533,9 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignTarget: AssignTarget) {
|
override fun visit(assignTarget: AssignTarget) {
|
||||||
|
if(assignTarget.inferType(program).istype(DataType.LONG))
|
||||||
|
errors.err("integer overflow", assignTarget.position)
|
||||||
|
|
||||||
super.visit(assignTarget)
|
super.visit(assignTarget)
|
||||||
|
|
||||||
val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
|
val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
|
||||||
@ -577,6 +585,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(addressOf: AddressOf) {
|
override fun visit(addressOf: AddressOf) {
|
||||||
|
checkLongType(addressOf)
|
||||||
val variable=addressOf.identifier.targetVarDecl(program)
|
val variable=addressOf.identifier.targetVarDecl(program)
|
||||||
if(variable!=null && variable.type==VarDeclType.CONST)
|
if(variable!=null && variable.type==VarDeclType.CONST)
|
||||||
errors.err("invalid pointer-of operand type", addressOf.position)
|
errors.err("invalid pointer-of operand type", addressOf.position)
|
||||||
@ -584,6 +593,9 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl) {
|
override fun visit(decl: VarDecl) {
|
||||||
|
if(decl.datatype==DataType.LONG)
|
||||||
|
errors.err("integer overflow", decl.position)
|
||||||
|
|
||||||
fun err(msg: String) = errors.err(msg, decl.position)
|
fun err(msg: String) = errors.err(msg, decl.position)
|
||||||
|
|
||||||
// the initializer value can't refer to the variable itself (recursive definition)
|
// the initializer value can't refer to the variable itself (recursive definition)
|
||||||
@ -915,6 +927,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(expr: PrefixExpression) {
|
override fun visit(expr: PrefixExpression) {
|
||||||
|
checkLongType(expr)
|
||||||
val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
|
val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
if(dt==DataType.UNDEFINED)
|
if(dt==DataType.UNDEFINED)
|
||||||
return // any error should be reported elsewhere
|
return // any error should be reported elsewhere
|
||||||
@ -935,6 +948,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
override fun visit(expr: BinaryExpression) {
|
override fun visit(expr: BinaryExpression) {
|
||||||
super.visit(expr)
|
super.visit(expr)
|
||||||
|
checkLongType(expr)
|
||||||
|
|
||||||
val leftIDt = expr.left.inferType(program)
|
val leftIDt = expr.left.inferType(program)
|
||||||
val rightIDt = expr.right.inferType(program)
|
val rightIDt = expr.right.inferType(program)
|
||||||
@ -1036,6 +1050,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(typecast: TypecastExpression) {
|
override fun visit(typecast: TypecastExpression) {
|
||||||
|
checkLongType(typecast)
|
||||||
if(typecast.type in IterableDatatypes)
|
if(typecast.type in IterableDatatypes)
|
||||||
errors.err("cannot type cast to string or array type", typecast.position)
|
errors.err("cannot type cast to string or array type", typecast.position)
|
||||||
|
|
||||||
@ -1082,6 +1097,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||||
|
checkLongType(functionCallExpr)
|
||||||
// this function call is (part of) an expression, which should be in a statement somewhere.
|
// this function call is (part of) an expression, which should be in a statement somewhere.
|
||||||
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
|
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
|
||||||
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
|
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
|
||||||
@ -1137,6 +1153,11 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(functionCallExpr)
|
super.visit(functionCallExpr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(bfc: BuiltinFunctionCall) {
|
||||||
|
checkLongType(bfc)
|
||||||
|
super.visit(bfc)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors)
|
val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors)
|
||||||
if(targetStatement!=null) {
|
if(targetStatement!=null) {
|
||||||
@ -1273,6 +1294,10 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args.forEach{
|
||||||
|
checkLongType(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(postIncrDecr: PostIncrDecr) {
|
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||||
@ -1310,6 +1335,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||||
|
checkLongType(arrayIndexedExpression)
|
||||||
val target = arrayIndexedExpression.arrayvar.targetStatement(program)
|
val target = arrayIndexedExpression.arrayvar.targetStatement(program)
|
||||||
if(target is VarDecl) {
|
if(target is VarDecl) {
|
||||||
if(target.datatype !in IterableDatatypes && target.datatype!=DataType.UWORD)
|
if(target.datatype !in IterableDatatypes && target.datatype!=DataType.UWORD)
|
||||||
@ -1454,6 +1480,12 @@ internal class AstChecker(private val program: Program,
|
|||||||
errors.err("%asm containing IR code cannot be translated to 6502 assembly", inlineAssembly.position)
|
errors.err("%asm containing IR code cannot be translated to 6502 assembly", inlineAssembly.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkLongType(expression: Expression) {
|
||||||
|
if(expression.inferType(program).istype(DataType.LONG)) {
|
||||||
|
errors.err("integer overflow", expression.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
|
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
|
||||||
return if (targetDt == DataType.STR) {
|
return if (targetDt == DataType.STR) {
|
||||||
when {
|
when {
|
||||||
|
@ -1043,4 +1043,27 @@ main {
|
|||||||
errors.errors.single() shouldContain "doesn't match"
|
errors.errors.single() shouldContain "doesn't match"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("long type okay in const expr but otherwise overflow") {
|
||||||
|
val src="""
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
const ubyte HEIGHT=240
|
||||||
|
uword large = 320*240/8/8
|
||||||
|
thing(large)
|
||||||
|
thing(320*240/8/8)
|
||||||
|
thing(320*HEIGHT/8/8)
|
||||||
|
thing(320*HEIGHT) ; overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
sub thing(uword value) {
|
||||||
|
value++
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
val errors=ErrorReporterForTests()
|
||||||
|
compileText(C64Target(), false, src, writeAssembly = false, errors=errors) shouldBe null
|
||||||
|
errors.errors.size shouldBe 2
|
||||||
|
errors.errors[0] shouldContain "can't cast"
|
||||||
|
errors.errors[1] shouldContain "overflow"
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -484,6 +484,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
|||||||
in -128..127 -> NumericLiteral(DataType.BYTE, dvalue, position)
|
in -128..127 -> NumericLiteral(DataType.BYTE, dvalue, position)
|
||||||
in 0..65535 -> NumericLiteral(DataType.UWORD, dvalue, position)
|
in 0..65535 -> NumericLiteral(DataType.UWORD, dvalue, position)
|
||||||
in -32768..32767 -> NumericLiteral(DataType.WORD, dvalue, position)
|
in -32768..32767 -> NumericLiteral(DataType.WORD, dvalue, position)
|
||||||
|
in -2147483647..2147483647 -> NumericLiteral(DataType.LONG, dvalue, position)
|
||||||
else -> throw FatalAstException("integer overflow: $dvalue")
|
else -> throw FatalAstException("integer overflow: $dvalue")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -492,6 +493,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
|||||||
return when (value) {
|
return when (value) {
|
||||||
in 0u..255u -> NumericLiteral(DataType.UBYTE, value.toDouble(), position)
|
in 0u..255u -> NumericLiteral(DataType.UBYTE, value.toDouble(), position)
|
||||||
in 0u..65535u -> NumericLiteral(DataType.UWORD, value.toDouble(), position)
|
in 0u..65535u -> NumericLiteral(DataType.UWORD, value.toDouble(), position)
|
||||||
|
in 0u..2147483647u -> NumericLiteral(DataType.LONG, value.toDouble(), position)
|
||||||
else -> throw FatalAstException("unsigned integer overflow: $value")
|
else -> throw FatalAstException("unsigned integer overflow: $value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -635,6 +637,9 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
|||||||
DataType.BOOL -> {
|
DataType.BOOL -> {
|
||||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||||
}
|
}
|
||||||
|
DataType.LONG -> {
|
||||||
|
/* ignore this cast, LONG can't be used. Error will be given elsewhere */
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
throw FatalAstException("type cast of weird type $type")
|
throw FatalAstException("type cast of weird type $type")
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,7 @@ object InferredTypes {
|
|||||||
DataType.BYTE to InferredType.known(DataType.BYTE),
|
DataType.BYTE to InferredType.known(DataType.BYTE),
|
||||||
DataType.UWORD to InferredType.known(DataType.UWORD),
|
DataType.UWORD to InferredType.known(DataType.UWORD),
|
||||||
DataType.WORD to InferredType.known(DataType.WORD),
|
DataType.WORD to InferredType.known(DataType.WORD),
|
||||||
|
DataType.LONG to InferredType.known(DataType.LONG),
|
||||||
DataType.FLOAT to InferredType.known(DataType.FLOAT),
|
DataType.FLOAT to InferredType.known(DataType.FLOAT),
|
||||||
DataType.BOOL to InferredType.known(DataType.BOOL),
|
DataType.BOOL to InferredType.known(DataType.BOOL),
|
||||||
DataType.STR to InferredType.known(DataType.STR),
|
DataType.STR to InferredType.known(DataType.STR),
|
||||||
|
@ -12,7 +12,6 @@ Future Things and Ideas
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Compiler:
|
Compiler:
|
||||||
|
|
||||||
- Currently "320*240/8/8" gives integer overflow, so: allow constant integer subexpressions to contain out of range integers (>65535 etc) as long as the final constant value is within byte/word range.
|
|
||||||
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays.
|
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays.
|
||||||
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
|
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
|
||||||
(this is already done hardcoded for several of the builtin functions)
|
(this is already done hardcoded for several of the builtin functions)
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
%zeropage basicsafe
|
|
||||||
%import textio
|
|
||||||
%option no_sysinit
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
txt.print(iso:"This is ISO text.\n")
|
const ubyte HEIGHT=240
|
||||||
|
uword large = 320*240/8/8
|
||||||
|
thing(large)
|
||||||
|
thing(320*240/8/8)
|
||||||
|
thing(320*HEIGHT/8/8)
|
||||||
|
thing(320*HEIGHT) ; overflow
|
||||||
|
}
|
||||||
|
|
||||||
repeat {
|
sub thing(uword value) {
|
||||||
}
|
value++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user