allow integer range as when choice value

This commit is contained in:
Irmen de Jong
2025-03-17 22:26:27 +01:00
parent f04b97d890
commit 79cda544c8
5 changed files with 82 additions and 26 deletions

View File

@@ -319,4 +319,35 @@ _after:
}
return noModifications
}
override fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> {
// replace a range expression in a when by the actual list of numbers it represents
val values = whenChoice.values
if(values!=null && values.size==1) {
val conditionType = (whenChoice.parent as When).condition.inferType(program)
val intRange = (values[0] as? RangeExpression)?.toConstantIntegerRange()
if(conditionType.isKnown && intRange != null) {
if(intRange.count()>255)
errors.err("values list too long", values[0].position)
else {
val dt = conditionType.getOrUndef().base
val newValues = intRange.map {
val num = NumericLiteral(BaseDataType.LONG, it.toDouble(), values[0].position)
num.linkParents(whenChoice)
val cast = num.cast(dt, true)
if (cast.isValid) cast.valueOrZero() else null
}
if(null !in newValues) {
if(newValues.size>=10)
errors.warn("long list of values, checking will not be very efficient", values[0].position)
values.clear()
for(num in newValues)
values.add(num!!)
}
}
}
}
return noModifications
}
}

View File

@@ -4,6 +4,7 @@ import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.comparables.shouldBeGreaterThan
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
@@ -684,6 +685,40 @@ main
errors.errors[0] shouldContain "use if"
}
test("when on range expressions is ok") {
val src="""
main {
sub start() {
when cx16.r0L {
21 to 29 step 2 -> cx16.r1L++
else -> cx16.r1L--
}
}
}"""
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false).shouldNotBeNull()
}
test("when on range expressions outside value datatype is error") {
val src="""
main {
sub start() {
when cx16.r0L {
300 to 400 -> cx16.r1L++
else -> cx16.r1L--
}
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "values must be constant numbers"
}
test("sizeof number const evaluation in vardecl") {
val src="""
main {

View File

@@ -751,19 +751,24 @@ action. It is possible to combine several choices to result in the same action::
when value {
4 -> txt.print("four")
5 -> txt.print("five")
10,20,30 -> {
txt.print("ten or twenty or thirty")
}
10,20,30 -> txt.print("ten or twenty or thirty")
50 to 60 step 2 -> txt.print("fifty to sixty, even")
else -> txt.print("don't know")
}
The when-*value* can be any expression but the choice values have to evaluate to
compile-time constant integers (bytes or words). They also have to be the same
datatype as the when-value, otherwise no efficient comparison can be done.
The else part is optional.
You can explicitly put a list of numbers that all should result in the same case,
or even use any *range expression* as long as it denotes a constant list of numbers.
Be aware that every number is compared individually so using long lists of numbers and/or
many choice cases will result in poor performance.
Choices can result in a single statement or a block of multiple statements in which
case you have to use { } to enclose them.
The else part is optional.
.. note::
Instead of chaining several value equality checks together using ``or`` (ex.: ``if x==1 or xx==5 or xx==9``),

View File

@@ -9,7 +9,6 @@ TODO
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- allow integer range as when choice? because 1,2,3,4,5 is already allowed, so perhaps 1 to 5 should be allowed too? However, [1,2,3,4,5] usually is the desugared equivalent of 1 to 5 and choice values can't be arrays. Unless we allow array literals as well and desugar those into separate labels too.
- Kotlin: can we use inline value classes in certain spots?
- add float support to the configurable compiler targets
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions

View File

@@ -1,31 +1,17 @@
%import psg
%import textio
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
psg.init()
cx16.r0L = 25
psg.voice(5, psg.LEFT, 0, psg.TRIANGLE, 0)
psg.freq(5, 1600)
psg.envelope(5, 63, 10, 50, 2)
psg.voice(6, psg.RIGHT, 0, psg.SAWTOOTH, 0)
psg.freq(6, 1200)
psg.envelope(6, 63, 2, 50, 10)
repeat 140 {
sys.waitvsync()
psg.envelopes_irq()
}
psg.voice(5, psg.DISABLED, 0, 0, 0)
psg.voice(6, psg.DISABLED, 0, 0, 0)
psg.silent()
repeat {
sys.waitvsync()
psg.envelopes_irq()
when cx16.r0L {
0 -> txt.print("zero")
1 -> txt.print("one")
21 to 29 step 2 -> txt.print("between 20 and 30 and odd")
else -> txt.print("something else")
}
}
}