mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
498 lines
15 KiB
Kotlin
498 lines
15 KiB
Kotlin
package prog8tests.codegeneration
|
|
|
|
import io.kotest.assertions.throwables.shouldThrow
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.engine.spec.tempdir
|
|
import io.kotest.matchers.shouldBe
|
|
import io.kotest.matchers.shouldNotBe
|
|
import io.kotest.matchers.string.shouldContain
|
|
import prog8.code.core.AssemblyError
|
|
import prog8.code.target.Cx16Target
|
|
import prog8tests.helpers.ErrorReporterForTests
|
|
import prog8tests.helpers.compileText
|
|
import prog8tests.helpers.simulate
|
|
|
|
class TestVariableStepForLoops6502: FunSpec({
|
|
val outputDir = tempdir().toPath()
|
|
|
|
fun run(source: String) = compileText(Cx16Target(), false, source.trimIndent(), outputDir)!!.simulate()
|
|
|
|
fun assertWord(machine: razorvine.ksim65.testing.TestMachine, address: Int, value: Int) {
|
|
machine.assertMemory(address, value and 0xff)
|
|
machine.assertMemory(address + 1, (value shr 8) and 0xff)
|
|
}
|
|
|
|
test("unsigned byte ascending") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte result = $0200
|
|
sub start() {
|
|
ubyte @shared step = 3
|
|
ubyte i
|
|
ubyte sum = 0
|
|
for i in 0 to 10 step step {
|
|
sum += i
|
|
}
|
|
result = sum
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x0200, 18)
|
|
}
|
|
|
|
test("romable variable-step byte loop") {
|
|
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
|
|
val result = compileText(Cx16Target(), false, $$"""
|
|
%option no_sysinit
|
|
%option romable
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
sub start() {
|
|
ubyte @shared step = 3
|
|
ubyte i
|
|
ubyte sum
|
|
for i in 0 to 10 step step {
|
|
sum += i
|
|
}
|
|
}
|
|
}
|
|
""", outputDir, errors = errors, varsAddress = 0x8000u, assemble = true)
|
|
errors.errors shouldBe emptyList()
|
|
errors.printedErrors shouldBe emptyList()
|
|
errors.warnings.none { it.contains("forloop") } shouldBe true
|
|
result shouldNotBe null
|
|
}
|
|
|
|
test("romable variable-step word loop") {
|
|
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
|
|
val result = compileText(Cx16Target(), false, $$"""
|
|
%option no_sysinit
|
|
%option romable
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
sub start() {
|
|
uword @shared step = 100
|
|
uword i
|
|
uword sum
|
|
for i in 0 to 500 step step {
|
|
sum += i
|
|
}
|
|
}
|
|
}
|
|
""", outputDir, errors = errors, varsAddress = 0x8000u, assemble = true)
|
|
errors.errors shouldBe emptyList()
|
|
errors.printedErrors shouldBe emptyList()
|
|
errors.warnings.none { it.contains("forloop") } shouldBe true
|
|
result shouldNotBe null
|
|
}
|
|
|
|
test("romable constant-step byte loop is rejected") {
|
|
val source = $$"""
|
|
%option no_sysinit
|
|
%option romable
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
sub start() {
|
|
ubyte @shared from
|
|
ubyte @shared upper
|
|
from = 0
|
|
upper = 10
|
|
ubyte i
|
|
ubyte sum
|
|
for i in from to upper step 3 {
|
|
sum += i
|
|
}
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
|
|
val result = compileText(Cx16Target(), false, source, outputDir, errors = errors, varsAddress = 0x8000u, assemble = true)
|
|
errors.errors shouldBe emptyList()
|
|
errors.printedErrors shouldBe emptyList()
|
|
errors.warnings.joinToString("\n") shouldContain "self-modifying code (forloop over bytes range)"
|
|
result shouldBe null
|
|
}
|
|
|
|
test("romable constant-step word loop is rejected") {
|
|
val source = $$"""
|
|
%option no_sysinit
|
|
%option romable
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
sub start() {
|
|
uword @shared from
|
|
uword @shared upper
|
|
from = 0
|
|
upper = 500
|
|
uword i
|
|
uword sum
|
|
for i in from to upper step 100 {
|
|
sum += i
|
|
}
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
|
|
val result = compileText(Cx16Target(), false, source, outputDir, errors = errors, varsAddress = 0x8000u, assemble = true)
|
|
errors.errors shouldBe emptyList()
|
|
errors.printedErrors shouldBe emptyList()
|
|
errors.warnings.joinToString("\n") shouldContain "self-modifying code (forloop over word range)"
|
|
result shouldBe null
|
|
}
|
|
|
|
test("signed negative step with unsigned byte descending") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte result = $0201
|
|
sub start() {
|
|
byte @shared step = -2
|
|
ubyte i
|
|
ubyte sum = 0
|
|
for i in 10 downto 0 step step {
|
|
sum += i
|
|
}
|
|
result = sum
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x0201, 30)
|
|
}
|
|
|
|
test("unsigned word ascending") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&uword result = $0202
|
|
sub start() {
|
|
uword @shared step = 100
|
|
uword i
|
|
uword sum = 0
|
|
for i in 0 to 500 step step {
|
|
sum += i
|
|
}
|
|
result = sum
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
assertWord(machine, 0x0202, 1500)
|
|
}
|
|
|
|
test("signed word ascending") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&word count = $0204
|
|
&word result = $0206
|
|
sub start() {
|
|
word @shared step = 4
|
|
word i
|
|
word sum = 0
|
|
for i in -10 to 10 step step {
|
|
count += 1
|
|
sum += i
|
|
}
|
|
result = sum
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
assertWord(machine, 0x0204, 6)
|
|
assertWord(machine, 0x0206, 0)
|
|
}
|
|
|
|
test("zero step") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte result = $0208
|
|
sub start() {
|
|
ubyte @shared step = 0
|
|
ubyte i
|
|
for i in 0 to 10 step step {
|
|
result++
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x0208, 0)
|
|
}
|
|
|
|
test("wrong direction positive and negative") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte positive = $0209
|
|
&ubyte negative = $020a
|
|
sub start() {
|
|
ubyte @shared positiveStep = 1
|
|
byte @shared negativeStep = -1
|
|
ubyte i
|
|
for i in 10 to 0 step positiveStep {
|
|
positive++
|
|
}
|
|
for i in 0 to 10 step negativeStep {
|
|
negative++
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x0209, 0)
|
|
machine.assertMemory(0x020a, 0)
|
|
}
|
|
|
|
test("from equals to") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte result = $020b
|
|
sub start() {
|
|
ubyte @shared step = 5
|
|
ubyte i
|
|
for i in 7 to 7 step step {
|
|
result++
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x020b, 1)
|
|
}
|
|
|
|
test("body mutation of step does not change the loop increment") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte result = $020c
|
|
ubyte @shared step = 2
|
|
sub start() {
|
|
ubyte i
|
|
for i in 0 to 10 step step {
|
|
result += i
|
|
step = 99
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x020c, 30)
|
|
}
|
|
|
|
test("fixed width ubyte wrap stops at the bound") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte result = $020d
|
|
sub start() {
|
|
ubyte @shared step = 3
|
|
ubyte i
|
|
for i in 254 to 255 step step {
|
|
result++
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x020d, 1)
|
|
}
|
|
|
|
test("nested break exits only the inner loop") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte result = $020e
|
|
sub start() {
|
|
ubyte @shared step = 1
|
|
ubyte i
|
|
ubyte j
|
|
for i in 0 to 5 step step {
|
|
for j in 0 to 5 step step {
|
|
result++
|
|
if j == 2
|
|
break
|
|
}
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x020e, 18)
|
|
}
|
|
|
|
test("non-constant bounds") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte result = $020f
|
|
ubyte @shared a = 1
|
|
ubyte @shared b = 9
|
|
sub start() {
|
|
ubyte @shared step = 2
|
|
ubyte i
|
|
for i in a to b step step {
|
|
result += i
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x020f, 25)
|
|
}
|
|
|
|
test("for bounds and step are evaluated once in source order") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte order = $0210
|
|
&ubyte result = $0211
|
|
|
|
sub side_from() -> ubyte {
|
|
order = 1
|
|
return 1
|
|
}
|
|
|
|
sub side_to() -> ubyte {
|
|
order = order * 10 + 2
|
|
return 5
|
|
}
|
|
|
|
sub side_step() -> byte {
|
|
order = order * 10 + 3
|
|
return 2
|
|
}
|
|
|
|
sub start() {
|
|
ubyte i
|
|
order = 0
|
|
for i in side_from() to side_to() step side_step() {
|
|
result += i
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x0210, 123)
|
|
machine.assertMemory(0x0211, 9)
|
|
}
|
|
|
|
test("continue still performs the next step") {
|
|
val machine = run($$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte count = $0212
|
|
&ubyte result = $0213
|
|
sub start() {
|
|
ubyte @shared step = 1
|
|
ubyte i
|
|
for i in 0 to 5 step step {
|
|
if i == 2
|
|
continue
|
|
count++
|
|
result += i
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""")
|
|
machine.assertMemory(0x0212, 5)
|
|
machine.assertMemory(0x0213, 13)
|
|
}
|
|
|
|
test("dynamic long compilation fails with an unsupported variable-step diagnostic") {
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
sub start() {
|
|
long i
|
|
for i in 0 to 10 step dynamicStep() {
|
|
}
|
|
poweroff = 1
|
|
}
|
|
|
|
sub dynamicStep() -> long {
|
|
return 1
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
|
|
val failure = shouldThrow<AssemblyError> {
|
|
compileText(Cx16Target(), false, src, outputDir, errors = errors)
|
|
}
|
|
(failure.message ?: errors.printedErrors.joinToString("\n")) shouldContain
|
|
"variable-step for loops over long ranges are not supported"
|
|
errors.printedErrors shouldBe listOf("\ninternal error")
|
|
}
|
|
})
|