don't crash on certain undefined symbols, give proper error instead

Also the error handlers in unit tests now de-duplicate messages just like the compiler itself does
This commit is contained in:
Irmen de Jong 2023-03-11 14:55:13 +01:00
parent 4600772e05
commit d76547ead4
12 changed files with 90 additions and 27 deletions

View File

@ -34,7 +34,7 @@ internal class ExpressionsAsmGen(private val program: PtProgram,
is PtFunctionCall -> translateFunctionCallResultOntoStack(expression) is PtFunctionCall -> translateFunctionCallResultOntoStack(expression)
is PtBuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null) is PtBuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
is PtContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported") is PtContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
is PtArray, is PtString -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable") is PtArray, is PtString -> throw AssemblyError("string/array literal value assignment should have been replaced by a variable")
is PtRange -> throw AssemblyError("range expression should have been changed into array values") is PtRange -> throw AssemblyError("range expression should have been changed into array values")
is PtMachineRegister -> throw AssemblyError("machine register ast node should not occur in 6502 codegen it is for IR code") is PtMachineRegister -> throw AssemblyError("machine register ast node should not occur in 6502 codegen it is for IR code")
else -> TODO("missing expression asmgen for $expression") else -> TODO("missing expression asmgen for $expression")

View File

@ -1628,7 +1628,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
""") """)
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
throw AssemblyError("no asm gen for assign wordvar $sourceName to memory ${target.memory}") throw AssemblyError("assign word to memory ${target.memory} should have gotten a typecast")
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
target.array!! target.array!!
@ -2348,7 +2348,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
} }
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}") throw AssemblyError("assign word to memory ${target.memory} should have gotten a typecast")
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y) asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y)
@ -2737,7 +2737,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1") asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
throw AssemblyError("no asm gen for assign memory byte at $address to array ${wordtarget.asmVarname}") throw AssemblyError("no asm gen for assign memory byte at $address to word array ${wordtarget.asmVarname}")
} }
TargetStorageKind.REGISTER -> when(wordtarget.register!!) { TargetStorageKind.REGISTER -> when(wordtarget.register!!) {
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda ${address.toHex()}") RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda ${address.toHex()}")
@ -2765,7 +2765,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1") asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
throw AssemblyError("no asm gen for assign memory byte $identifier to array ${wordtarget.asmVarname} ") throw AssemblyError("no asm gen for assign memory byte $identifier to word array ${wordtarget.asmVarname} ")
} }
TargetStorageKind.REGISTER -> { TargetStorageKind.REGISTER -> {
asmgen.loadByteFromPointerIntoA(identifier) asmgen.loadByteFromPointerIntoA(identifier)

View File

@ -34,11 +34,15 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
val warnings = mutableListOf<String>() val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) { override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg") val text = "${position.toClickableStr()} $msg"
if(text !in errors)
errors.add(text)
} }
override fun warn(msg: String, position: Position) { override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg") val text = "${position.toClickableStr()} $msg"
if(text !in warnings)
warnings.add(text)
} }
override fun noErrors(): Boolean = errors.isEmpty() override fun noErrors(): Boolean = errors.isEmpty()

View File

@ -32,11 +32,15 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
val warnings = mutableListOf<String>() val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) { override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg") val text = "${position.toClickableStr()} $msg"
if(text !in errors)
errors.add(text)
} }
override fun warn(msg: String, position: Position) { override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg") val text = "${position.toClickableStr()} $msg"
if(text !in warnings)
warnings.add(text)
} }
override fun noErrors(): Boolean = errors.isEmpty() override fun noErrors(): Boolean = errors.isEmpty()

View File

@ -405,7 +405,7 @@ private fun createAssemblyAndAssemble(program: PtProgram,
else if (compilerOptions.compTarget.name == VMTarget.NAME) else if (compilerOptions.compTarget.name == VMTarget.NAME)
VmCodeGen() VmCodeGen()
else else
throw NotImplementedError("no asm generator for cpu ${compilerOptions.compTarget.machine.cpu}") throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.machine.cpu}")
val stMaker = SymbolTableMaker(program, compilerOptions) val stMaker = SymbolTableMaker(program, compilerOptions)
val symbolTable = stMaker.make() val symbolTable = stMaker.make()

View File

@ -60,11 +60,16 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(identifier: IdentifierReference) { override fun visit(identifier: IdentifierReference) {
val target = identifier.targetVarDecl(program) val stmt = identifier.targetStatement(program)
if(target != null && target.origin==VarDeclOrigin.SUBROUTINEPARAM) { if(stmt==null)
if(target.definingSubroutine!!.isAsmSubroutine) { errors.err("undefined symbol: ${identifier.nameInSource.joinToString(".")}", identifier.position)
if(target.definingSubroutine!!.parameters.any { it.name == identifier.nameInSource.last() }) else {
errors.err("cannot refer to parameter of asmsub by name", identifier.position) val target = stmt as? VarDecl
if (target != null && target.origin == VarDeclOrigin.SUBROUTINEPARAM) {
if (target.definingSubroutine!!.isAsmSubroutine) {
if (target.definingSubroutine!!.parameters.any { it.name == identifier.nameInSource.last() })
errors.err("cannot refer to parameter of asmsub by name", identifier.position)
}
} }
} }
} }
@ -1323,7 +1328,7 @@ internal class AstChecker(private val program: Program,
else else
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position) errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
} }
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", target.position) null -> errors.err("undefined symbol: ${target.nameInSource.joinToString(".")}", target.position)
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position) else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
} }
return null return null

View File

@ -13,7 +13,9 @@ import prog8.code.core.IErrorReporter
import prog8.code.core.Position import prog8.code.core.Position
import prog8.code.target.VMTarget import prog8.code.target.VMTarget
/**
* This checks for naming conflicts, not for correct symbol references yet.
*/
internal class AstIdentifiersChecker(private val errors: IErrorReporter, internal class AstIdentifiersChecker(private val errors: IErrorReporter,
private val program: Program, private val program: Program,
private val compTarget: ICompilationTarget private val compTarget: ICompilationTarget

View File

@ -413,7 +413,16 @@ class TestScoping: FunSpec({
""" """
val errors = ErrorReporterForTests() val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = false, errors = errors) shouldBe null compileText(C64Target(), false, text, writeAssembly = false, errors = errors) shouldBe null
println(errors.errors) /*
There are 4 errors and 3 warnings.
ERROR name conflict 'start', also defined...
ERROR name conflict 'var1Warn', also defined...
ERROR name conflict 'main', also defined...
ERROR name conflict 'internalOk', also defined...
WARN name 'var1Warn' shadows occurrence at...
WARN name 'var1Warn' shadows occurrence at...
WARN name 'var1Warn' shadows occurrence at...
*/
errors.warnings.size shouldBe 3 errors.warnings.size shouldBe 3
errors.warnings[0] shouldContain "var1Warn" errors.warnings[0] shouldContain "var1Warn"
errors.warnings[0] shouldContain "shadows" errors.warnings[0] shouldContain "shadows"
@ -424,7 +433,7 @@ class TestScoping: FunSpec({
errors.warnings[2] shouldContain "var1Warn" errors.warnings[2] shouldContain "var1Warn"
errors.warnings[2] shouldContain "shadows" errors.warnings[2] shouldContain "shadows"
errors.warnings[2] shouldContain "line 3" errors.warnings[2] shouldContain "line 3"
errors.errors.size shouldBe 5 errors.errors.size shouldBe 4
errors.errors[0] shouldContain "name conflict" errors.errors[0] shouldContain "name conflict"
errors.errors[0] shouldContain "start" errors.errors[0] shouldContain "start"
errors.errors[0] shouldContain "line 5" errors.errors[0] shouldContain "line 5"
@ -435,10 +444,7 @@ class TestScoping: FunSpec({
errors.errors[2] shouldContain "main" errors.errors[2] shouldContain "main"
errors.errors[2] shouldContain "line 2" errors.errors[2] shouldContain "line 2"
errors.errors[3] shouldContain "name conflict" errors.errors[3] shouldContain "name conflict"
errors.errors[3] shouldContain "start" errors.errors[3] shouldContain "internalOk"
errors.errors[3] shouldContain "line 5" errors.errors[3] shouldContain "line 11"
errors.errors[4] shouldContain "name conflict"
errors.errors[4] shouldContain "internalOk"
errors.errors[4] shouldContain "line 11"
} }
}) })

View File

@ -201,5 +201,17 @@ main {
value2.left shouldBe instanceOf<ContainmentCheck>() value2.left shouldBe instanceOf<ContainmentCheck>()
(value2.right as NumericLiteral).number shouldBe 0.0 (value2.right as NumericLiteral).number shouldBe 0.0
} }
test("const pointer variable indexing works") {
val src="""
main {
sub start() {
const uword pointer=$1000
cx16.r0L = pointer[2]
}
}
"""
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
}) })

View File

@ -4,6 +4,7 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldStartWith import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.types.instanceOf import io.kotest.matchers.types.instanceOf
import prog8.code.ast.PtArrayIndexer import prog8.code.ast.PtArrayIndexer
@ -11,6 +12,7 @@ import prog8.code.ast.PtAssignment
import prog8.code.ast.PtVariable import prog8.code.ast.PtVariable
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
class TestVariousCodeGen: FunSpec({ class TestVariousCodeGen: FunSpec({
@ -109,4 +111,29 @@ main {
}""" }"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
} }
test("assigning memory byte into array works") {
val text="""
main {
sub start() {
uword factor1
ubyte[3] @shared factor124
factor124[0] = @(factor1)
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("reading memory from unknown var gives proper error") {
val text="""
main {
sub start() {
cx16.r0L = @(doesnotexist)
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = true, errors = errors)
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "undefined symbol: doesnotexist"
}
}) })

View File

@ -10,11 +10,15 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
val warnings = mutableListOf<String>() val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) { override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg") val text = "${position.toClickableStr()} $msg"
if(text !in errors)
errors.add(text)
} }
override fun warn(msg: String, position: Position) { override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg") val text = "${position.toClickableStr()} $msg"
if(text !in warnings)
warnings.add(text)
} }
override fun noErrors(): Boolean = errors.isEmpty() override fun noErrors(): Boolean = errors.isEmpty()

View File

@ -3,7 +3,6 @@ TODO
For next minor release For next minor release
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
- fix Github issue with array problems https://github.com/irmen/prog8/issues/99
- fix IR/VM: animals.p8 example is borked, it jumps straight to a suggestion and then somehow doesn't print the animal name correctly in the first question, and exits after 1 animal instead of looping - fix IR/VM: animals.p8 example is borked, it jumps straight to a suggestion and then somehow doesn't print the animal name correctly in the first question, and exits after 1 animal instead of looping
this has happened after v8.7: caused by c21913a6 ir: keep order of children in block 22-11-2022 this has happened after v8.7: caused by c21913a6 ir: keep order of children in block 22-11-2022