mirror of
https://github.com/irmen/prog8.git
synced 2026-04-21 02:16:41 +00:00
better errors when multiplying string or array with bogus value
This commit is contained in:
@@ -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 ||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
; }
|
||||
;}
|
||||
|
||||
Reference in New Issue
Block a user