mirror of
https://github.com/irmen/prog8.git
synced 2024-12-23 09:32:43 +00:00
allow containment check in a range expression ("run time" range expression)
This commit is contained in:
parent
517ea82b99
commit
6aed7e429a
@ -146,6 +146,15 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
return null
|
||||
}
|
||||
ast = intermediateAst
|
||||
} else {
|
||||
if(args.printAst1) {
|
||||
println("\n*********** COMPILER AST *************")
|
||||
printProgram(program)
|
||||
println("*********** COMPILER AST END *************\n")
|
||||
}
|
||||
if(args.printAst2) {
|
||||
System.err.println("There is no intermediate Ast available if assembly generation is disabled.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1447,19 +1447,21 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(iterableDt.isIterable && containment.iterable !is RangeExpression) {
|
||||
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))
|
||||
val invalidDt = if (elementDt.isBytes) {
|
||||
iterableEltDt !in ByteDatatypes
|
||||
} else if (elementDt.isWords) {
|
||||
iterableEltDt !in WordDatatypes
|
||||
} else {
|
||||
false
|
||||
if (iterableDt.isIterable) {
|
||||
if (containment.iterable !is RangeExpression) {
|
||||
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))
|
||||
val invalidDt = if (elementDt.isBytes) {
|
||||
iterableEltDt !in ByteDatatypes
|
||||
} else if (elementDt.isWords) {
|
||||
iterableEltDt !in WordDatatypes
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if (invalidDt)
|
||||
errors.err("element datatype doesn't match iterable datatype", containment.position)
|
||||
}
|
||||
if (invalidDt)
|
||||
errors.err("element datatype doesn't match iterable datatype", containment.position)
|
||||
} else {
|
||||
errors.err("value set for containment check must be a string or array", containment.iterable.position)
|
||||
errors.err("iterable must be an array, a string, or a range expression", containment.iterable.position)
|
||||
}
|
||||
|
||||
super.visit(containment)
|
||||
|
@ -5,8 +5,6 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.ContainmentCheck
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
@ -28,12 +26,6 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
|
||||
throw InternalCompilerException("do..until should have been converted to jumps")
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
if(containment.iterable !is IdentifierReference)
|
||||
throw InternalCompilerException("iterable in containmentcheck should be identifier (referencing string or array)")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
|
||||
throw InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
|
||||
|
@ -583,16 +583,74 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||
return call
|
||||
}
|
||||
|
||||
private fun transform(srcCheck: ContainmentCheck): PtContainmentCheck {
|
||||
val check = PtContainmentCheck(srcCheck.position)
|
||||
check.add(transformExpression(srcCheck.element))
|
||||
if(srcCheck.iterable !is IdentifierReference)
|
||||
throw FatalAstException("iterable in containmentcheck must always be an identifier (referencing string or array) $srcCheck")
|
||||
val iterable = transformExpression(srcCheck.iterable)
|
||||
check.add(iterable)
|
||||
return check
|
||||
private fun transform(srcCheck: ContainmentCheck): PtExpression {
|
||||
|
||||
fun desugar(range: RangeExpression): PtExpression {
|
||||
val expr = PtBinaryExpression("and", DataType.UBYTE, srcCheck.position)
|
||||
val x1 = transformExpression(srcCheck.element)
|
||||
val x2 = transformExpression(srcCheck.element)
|
||||
val eltDt = srcCheck.element.inferType(program)
|
||||
if(eltDt.isInteger) {
|
||||
val low = PtBinaryExpression("<=", DataType.UBYTE, srcCheck.position)
|
||||
low.add(transformExpression(range.from))
|
||||
low.add(x1)
|
||||
expr.add(low)
|
||||
val high = PtBinaryExpression("<=", DataType.UBYTE, srcCheck.position)
|
||||
high.add(x2)
|
||||
high.add(transformExpression(range.to))
|
||||
expr.add(high)
|
||||
} else {
|
||||
val low = PtBinaryExpression("<=", DataType.UBYTE, srcCheck.position)
|
||||
val lowFloat = PtTypeCast(DataType.FLOAT, range.from.position)
|
||||
lowFloat.add(transformExpression(range.from))
|
||||
low.add(lowFloat)
|
||||
low.add(x1)
|
||||
expr.add(low)
|
||||
val high = PtBinaryExpression("<=", DataType.UBYTE, srcCheck.position)
|
||||
high.add(x2)
|
||||
val highFLoat = PtTypeCast(DataType.FLOAT, range.to.position)
|
||||
highFLoat.add(transformExpression(range.to))
|
||||
high.add(highFLoat)
|
||||
expr.add(high)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
when(srcCheck.iterable) {
|
||||
is IdentifierReference -> {
|
||||
val check = PtContainmentCheck(srcCheck.position)
|
||||
check.add(transformExpression(srcCheck.element))
|
||||
val iterable = transformExpression(srcCheck.iterable)
|
||||
check.add(iterable)
|
||||
return check
|
||||
}
|
||||
is RangeExpression -> {
|
||||
val range = srcCheck.iterable as RangeExpression
|
||||
val constRange = range.toConstantIntegerRange()
|
||||
val constElt = srcCheck.element.constValue(program)?.number
|
||||
val step = range.step.constValue(program)?.number
|
||||
if(constElt!=null && constRange!=null) {
|
||||
return PtNumber(DataType.UBYTE, if(constRange.first<=constElt && constElt<=constRange.last) 1.0 else 0.0, srcCheck.position)
|
||||
}
|
||||
else if(step==1.0) {
|
||||
// x in low to high --> low <=x and x <= high
|
||||
return desugar(range)
|
||||
} else if(step==-1.0) {
|
||||
// x in high downto low -> low <=x and x <= high
|
||||
val tmp = range.to
|
||||
range.to = range.from
|
||||
range.from = tmp
|
||||
return desugar(range)
|
||||
} else {
|
||||
errors.err("cannot use step size different than 1 or -1 in a non constant range containment check", srcCheck.position)
|
||||
return PtNumber(DataType.BYTE, 0.0, Position.DUMMY)
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("iterable in containmentcheck must always be an identifier (referencing string or array) or a range expression $srcCheck")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun transform(memory: DirectMemoryWrite): PtMemoryByte {
|
||||
val mem = PtMemoryByte(memory.position)
|
||||
mem.add(transformExpression(memory.addressExpression))
|
||||
|
@ -404,6 +404,14 @@ class TestCompilerOnRanges: FunSpec({
|
||||
if ww in wvalues {
|
||||
xx++
|
||||
}
|
||||
|
||||
if xx in 10 to 20 {
|
||||
xx++
|
||||
}
|
||||
|
||||
if ww in 1000 to 2000 {
|
||||
xx++
|
||||
}
|
||||
}
|
||||
}""", writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
@ -924,7 +924,7 @@ class TestProg8Parser: FunSpec( {
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, text, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "must be a string or array"
|
||||
errors.errors[0] shouldContain "iterable must be"
|
||||
errors.errors[1] shouldContain "datatype doesn't match"
|
||||
}
|
||||
|
||||
|
@ -611,10 +611,11 @@ range creation: ``to``, ``downto``
|
||||
See :ref:`range-expression` for details.
|
||||
|
||||
containment check: ``in``
|
||||
Tests if a value is present in a list of values, which can be a string or an array.
|
||||
Tests if a value is present in a list of values, which can be a string, or an array, or a range expression.
|
||||
The result is a simple boolean ``true`` or ``false``.
|
||||
Consider using this instead of chaining multiple value tests with ``or``, because the
|
||||
containment check is more efficient.
|
||||
Checking N in a range from x to y, is identical to x<=N and N<=y; the actual range of values is never created.
|
||||
Examples::
|
||||
|
||||
ubyte cc
|
||||
@ -622,7 +623,11 @@ containment check: ``in``
|
||||
txt.print("cc is one of the values")
|
||||
}
|
||||
|
||||
str email_address = "name@test.com"
|
||||
if cc in 10 to 20 {
|
||||
txt.print("10 <= cc and cc <=20")
|
||||
}
|
||||
|
||||
str email_address = "name@test.com"
|
||||
if '@' in email_address {
|
||||
txt.print("email address seems ok")
|
||||
}
|
||||
|
@ -76,10 +76,3 @@ Other language/syntax features to think about
|
||||
- add (rom/ram)bank support to romsub. A call will then automatically switch banks, use callfar and something else when in banked ram.
|
||||
challenges: how to not make this too X16 specific? How does the compiler know what bank to switch (ram/rom)?
|
||||
How to make it performant when we want to (i.e. NOT have it use callfar/auto bank switching) ?
|
||||
- chained comparisons `10<x<20` , `x==y==z` (desugars to `10<x and x<20`, `x==y and y==z`)
|
||||
BUT this needs a new AST node type and rewritten parser rules, because otherwise it changes the semantics
|
||||
of existing expressions such as if x<y==0 ...
|
||||
- Better idea perhaps is "runtime range objects" the idea would be that "a to b" generates
|
||||
a new kind of "range" value rather than an array (though you can still use it to initialize an array),
|
||||
so you could replace if a <= n <= b with: if n in a to b
|
||||
So this means we should keep the Range Expression alive for much longer.
|
||||
|
@ -1,17 +1,54 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared n=20
|
||||
ubyte @shared x=10
|
||||
ubyte [] array = 100 to 110
|
||||
|
||||
if n < x {
|
||||
; nothing here, conditional gets inverted
|
||||
} else {
|
||||
cx16.r0++
|
||||
for cx16.r0L in array {
|
||||
txt.print_ub(cx16.r0L)
|
||||
txt.spc()
|
||||
}
|
||||
txt.nl()
|
||||
|
||||
ubyte x = 14
|
||||
if x in 10 to 20 {
|
||||
txt.print("yep1\n")
|
||||
}
|
||||
if x in 20 to 30 {
|
||||
txt.print("yep2\n")
|
||||
}
|
||||
|
||||
if x in 10 to 20 step 2 {
|
||||
txt.print("yep1b\n")
|
||||
}
|
||||
if x in 20 to 30 step 2 {
|
||||
txt.print("yep2b\n")
|
||||
}
|
||||
|
||||
if x in 20 to 10 step -2 {
|
||||
txt.print("yep1c\n")
|
||||
}
|
||||
if x in 30 to 20 step -2 {
|
||||
txt.print("yep2c\n")
|
||||
}
|
||||
|
||||
txt.nl()
|
||||
|
||||
ubyte @shared y = 12
|
||||
if y in 10 to 20 {
|
||||
txt.print("yep1\n")
|
||||
}
|
||||
if y in 20 to 30 {
|
||||
txt.print("yep2\n")
|
||||
}
|
||||
|
||||
if y in 20 downto 10 {
|
||||
txt.print("yep1c\n")
|
||||
}
|
||||
if y in 30 downto 20 {
|
||||
txt.print("yep2c\n")
|
||||
}
|
||||
cx16.r0L = n<x == 0
|
||||
cx16.r1L = not n<x
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user