better errors when multiplying string or array with bogus value

This commit is contained in:
Irmen de Jong
2026-01-09 18:33:12 +01:00
parent 13cd68ba96
commit cdb41f4352
6 changed files with 187 additions and 103 deletions
@@ -291,11 +291,11 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val requiredDt = parameter.value.type
if(requiredDt!=value.type) {
if(value.type.largerSizeThan(requiredDt))
throw AssemblyError("can only convert byte values to word param types")
throw AssemblyError("can only convert byte values to word param types ${value.position}")
}
if (statusflag!=null) {
if(requiredDt!=value.type)
throw AssemblyError("for statusflag, byte or bool value is required")
throw AssemblyError("for statusflag, byte or bool value is required ${value.position}")
if (statusflag == Statusflag.Pc) {
// this boolean param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
@@ -323,7 +323,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
asmgen.out(" ror a")
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")
} else throw AssemblyError("can only use Carry as status flag parameter ${value.position}")
return RegisterOrStatusflag(null, statusflag)
}
else {
@@ -11,6 +11,7 @@ import prog8.ast.walk.IAstModification
import prog8.code.core.AssociativeOperators
import prog8.code.core.BaseDataType
import prog8.code.core.IErrorReporter
import prog8.code.core.isInteger
import kotlin.math.floor
@@ -109,35 +110,56 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val concatStr = StringLiteral.create(concatenated, leftString.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, concatStr, parent))
}
else if(expr.operator=="*" && rightconst!=null && expr.left is StringLiteral) {
// mutiply a string.
val part = expr.left as StringLiteral
if(part.value.isEmpty())
errors.warn("resulting string has length zero", part.position)
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
else if (expr.operator=="*" && expr.left is StringLiteral) {
if (rightconst != null) {
// mutiply a string.
val part = expr.left as StringLiteral
if(part.value.isEmpty())
errors.warn("resulting string has length zero", part.position)
if(rightconst.number>255)
errors.err("string length must be 0-255", rightconst.position)
else if(!rightconst.type.isInteger)
errors.err("can only multiply string by integer constant", rightconst.position)
else {
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
}
} else {
errors.err("can only multiply a string by an integer value", expr.right.position)
}
return noModifications
}
}
if(expr.left.inferType(program).isArray) {
if (expr.operator=="*" && rightconst!=null) {
if (expr.left is ArrayLiteral) {
// concatenate array literal.
val part = expr.left as ArrayLiteral
if(part.value.isEmpty())
errors.warn("resulting array has length zero", part.position)
val tmp = mutableListOf<Expression>()
repeat(rightconst.number.toInt()) {
part.value.forEach { tmp += it.copy() }
if (expr.operator=="*") {
if (rightconst != null) {
if (expr.left is ArrayLiteral) {
// concatenate array literal.
val part = expr.left as ArrayLiteral
if(part.value.isEmpty())
errors.warn("resulting array has length zero", part.position)
if(rightconst.number.toInt() !in 1..(255/part.value.size))
errors.err("array length must be 1-255", rightconst.position)
else if(!rightconst.type.isInteger)
errors.err("can only multiply array by integer constant", rightconst.position)
else {
val tmp = mutableListOf<Expression>()
repeat(rightconst.number.toInt()) {
part.value.forEach { tmp += it.copy() }
}
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
}
} else {
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl()
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
}
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
}
else {
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl()
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
} else {
errors.err("can only multiply an array by an integer value", expr.right.position)
}
return noModifications
}
}
@@ -49,7 +49,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
if(valueDt.largerSizeThan(decl.datatype)) {
val constValue = decl.value!!.constValue(program)!!
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
} else {
} else if(decl.datatype.largerSizeThan(valueDt)) {
// don't make it signed if it was unsigned and vice versa, except when it is a long const declaration
if(!decl.datatype.isLong &&
(valueDt.isSigned && decl.datatype.isUnsigned ||
+30 -4
View File
@@ -336,12 +336,26 @@ main {
assignAddr4.type shouldBe BaseDataType.LONG
}
test("out of range const byte and word give correct error") {
test("out of range const byte and word give correct error (negative values)") {
var src = """
main {
sub start() {
const byte MIN_BYTE = -129
const word MIN_WORD = -32769
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), true, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "out of range"
errors.errors[1] shouldContain "out of range"
}
test("out of range const byte and word give correct error (positive values)") {
var src = """
main {
sub start() {
const byte MAX_BYTE = 128
const word MAX_WORD = 32768
}
@@ -349,11 +363,9 @@ main {
val errors = ErrorReporterForTests()
compileText(C64Target(), true, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 4
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "out of range"
errors.errors[1] shouldContain "out of range"
errors.errors[2] shouldContain "out of range"
errors.errors[3] shouldContain "out of range"
}
test("out of range var byte and word give correct error") {
@@ -414,6 +426,20 @@ main {
errors.errors[1] shouldContain (":5:37: no cast available")
}
test("const bool with wrong value type gives error") {
var src = """
main {
sub start() {
const bool TRUTH = 42
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), true, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain ("doesn't match target type bool")
}
test("const evaluation of signed bitwise operations") {
val src = """
main {
@@ -1339,5 +1339,28 @@ main {
errors.errors[2] shouldContain "struct name cannot be a keyword"
errors.errors[3] shouldContain "builtin function cannot be redefined"
}
test("string and array multiplication require integer multiplicand") {
val src="""
main {
sub start() {
const bool DERP = true
for cx16.r0L in 0 to 10 {
print_strXY(1,2,"irmen"*DERP,22,false)
print_strXY(1,2,[1,3,4]*DERP,22,false)
}
}
sub print_strXY(ubyte col, ubyte row, str txtstring, ubyte colors, bool convertchars) {
cx16.r0L++
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), optimize=false, src, outputDir, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "can only multiply string by integer constant"
errors.errors[1] shouldContain "can only multiply array by integer constant"
}
})
+85 -72
View File
@@ -1,79 +1,92 @@
%import textio
%import strings
%import floats
%zeropage basicsafe
;%import textio
;%zeropage basicsafe
main {
sub start() {
const bool DERP = true
sub f_seek(long position) {
ubyte[6] command = ['p',0,0,0,0,0]
pokel(&command+2, position)
pokew(&command+2, cx16.r0)
poke(&command+2, cx16.r0L)
pokebool(&command+2, cx16.r0bL)
for cx16.r0L in 0 to 10 {
print_strXY(1,2,"irmen"*DERP,22,false)
print_strXY(1,2,[1,3,4]*DERP,22,false)
}
}
sub start() {
long @shared lv1,lv2
^^long lptr = 20000
uword @shared addr = &lv2
ubyte @shared bytevar
float @shared fv
bool @shared boolvar
f_seek(12345678)
lv2 = $aabbccdd
pokel(2000, $11223344)
pokel(2000, lv2)
pokel(addr, $11223344)
pokel(addr, lv2)
poke(&bytevar, 99)
pokew(&addr, 9999)
pokel(&lv2, $99887766)
pokef(&fv, 1.234)
@(&bytevar) = 99
txt.print_ulhex(lv2, true)
txt.nl()
@(&bytevar+4) = 99
poke(&bytevar+4, 99)
pokebool(&boolvar+4, true)
pokew(&addr+4, 9999)
pokel(&lv2+4, $aabbccdd)
pokef(&fv+4, 1.234)
@(&bytevar+4) = bytevar
poke(&bytevar+4, bytevar)
pokebool(&boolvar+4, boolvar)
pokew(&addr+4, addr)
pokel(&lv2+4, lv2)
pokef(&fv+4, fv)
; TODO not optimized yet:
poke(&bytevar + bytevar, 99)
pokebool(&boolvar+ bytevar, true)
pokew(&addr+ bytevar, 9999)
pokel(&lv2+ bytevar, $66778899)
pokef(&fv+ bytevar, 1.234)
cx16.r0L = @(&bytevar+4)
bytevar = peek(&bytevar+4)
boolvar = peekbool(&boolvar+4)
addr = peekw(&addr+4)
lv2 = peekl(&lv2+4)
fv = peekf(&fv+4)
; TODO not optimized yet:
cx16.r0L = @(&bytevar+bytevar)
bytevar = peek(&bytevar+bytevar)
boolvar = peekbool(&boolvar+bytevar)
addr = peekw(&addr+bytevar)
lv2 = peekl(&lv2+bytevar)
fv = peekf(&fv+bytevar)
sub print_strXY(ubyte col, ubyte row, str txtstring, ubyte colors, bool convertchars) {
cx16.r0L++
}
}
;main {
;
; sub f_seek(long position) {
; ubyte[6] command = ['p',0,0,0,0,0]
;
; pokel(&command+2, position)
; pokew(&command+2, cx16.r0)
; poke(&command+2, cx16.r0L)
; pokebool(&command+2, cx16.r0bL)
; }
;
;
; sub start() {
; long @shared lv1,lv2
; ^^long lptr = 20000
; uword @shared addr = &lv2
; ubyte @shared bytevar
; float @shared fv
; bool @shared boolvar
;
; f_seek(12345678)
;
; lv2 = $aabbccdd
; pokel(2000, $11223344)
; pokel(2000, lv2)
; pokel(addr, $11223344)
; pokel(addr, lv2)
; poke(&bytevar, 99)
; pokew(&addr, 9999)
; pokel(&lv2, $99887766)
; pokef(&fv, 1.234)
; @(&bytevar) = 99
;
; txt.print_ulhex(lv2, true)
; txt.nl()
;
;
; @(&bytevar+4) = 99
; poke(&bytevar+4, 99)
; pokebool(&boolvar+4, true)
; pokew(&addr+4, 9999)
; pokel(&lv2+4, $aabbccdd)
; pokef(&fv+4, 1.234)
;
; @(&bytevar+4) = bytevar
; poke(&bytevar+4, bytevar)
; pokebool(&boolvar+4, boolvar)
; pokew(&addr+4, addr)
; pokel(&lv2+4, lv2)
; pokef(&fv+4, fv)
;
; ; TODO not optimized yet:
; poke(&bytevar + bytevar, 99)
; pokebool(&boolvar+ bytevar, true)
; pokew(&addr+ bytevar, 9999)
; pokel(&lv2+ bytevar, $66778899)
; pokef(&fv+ bytevar, 1.234)
;
; cx16.r0L = @(&bytevar+4)
; bytevar = peek(&bytevar+4)
; boolvar = peekbool(&boolvar+4)
; addr = peekw(&addr+4)
; lv2 = peekl(&lv2+4)
; fv = peekf(&fv+4)
;
; ; TODO not optimized yet:
; cx16.r0L = @(&bytevar+bytevar)
; bytevar = peek(&bytevar+bytevar)
; boolvar = peekbool(&boolvar+bytevar)
; addr = peekw(&addr+bytevar)
; lv2 = peekl(&lv2+bytevar)
; fv = peekf(&fv+bytevar)
; }
;}