diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index d5fd43167..38db95aaf 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -928,7 +928,6 @@ data class AssignTarget(val register: Register?, interface IExpression: Node { - fun isIterable(program: Program): Boolean fun constValue(program: Program): LiteralValue? fun process(processor: IAstProcessor): IExpression fun referencesIdentifier(name: String): Boolean @@ -973,7 +972,6 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) override fun resultingDatatype(program: Program): DataType? = expression.resultingDatatype(program) - override fun isIterable(program: Program) = false override fun toString(): String { return "Prefix($operator $expression)" @@ -996,7 +994,6 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I // binary expression should actually have been optimized away into a single value, before const value was requested... override fun constValue(program: Program): LiteralValue? = null - override fun isIterable(program: Program) = false override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name) override fun resultingDatatype(program: Program): DataType? { @@ -1102,7 +1099,6 @@ class ArrayIndexedExpression(val identifier: IdentifierReference, arrayspec.linkParents(this) } - override fun isIterable(program: Program) = false override fun constValue(program: Program): LiteralValue? = null override fun process(processor: IAstProcessor): IExpression = processor.process(this) override fun referencesIdentifier(name: String) = identifier.referencesIdentifier(name) @@ -1141,7 +1137,6 @@ class TypecastExpression(var expression: IExpression, var type: DataType, overri override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) override fun resultingDatatype(program: Program): DataType? = type - override fun isIterable(program: Program) = type in IterableDatatypes override fun constValue(program: Program): LiteralValue? { val cv = expression.constValue(program) ?: return null val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type) @@ -1163,7 +1158,6 @@ data class AddressOf(val identifier: IdentifierReference, override val position: } var scopedname: String? = null // will be set in a later state by the compiler - override fun isIterable(program: Program) = false override fun constValue(program: Program): LiteralValue? = null override fun referencesIdentifier(name: String) = false override fun resultingDatatype(program: Program) = DataType.UWORD @@ -1182,7 +1176,6 @@ class DirectMemoryRead(var addressExpression: IExpression, override val position override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String) = false override fun resultingDatatype(program: Program): DataType? = DataType.UBYTE - override fun isIterable(program: Program) = false override fun constValue(program: Program): LiteralValue? = null override fun toString(): String { @@ -1202,7 +1195,6 @@ class DirectMemoryWrite(var addressExpression: IExpression, override val positio override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String) = false override fun resultingDatatype(program: Program): DataType? = DataType.UBYTE - override fun isIterable(program: Program) = false override fun constValue(program: Program): LiteralValue? = null override fun toString(): String { @@ -1338,8 +1330,6 @@ open class LiteralValue(val type: DataType, override fun resultingDatatype(program: Program) = type - override fun isIterable(program: Program): Boolean = type in IterableDatatypes - override fun hashCode(): Int { val bh = bytevalue?.hashCode() ?: 0x10001234 val wh = wordvalue?.hashCode() ?: 0x01002345 @@ -1468,7 +1458,6 @@ class RangeExpr(var from: IExpression, } override fun constValue(program: Program): LiteralValue? = null - override fun isIterable(program: Program) = true override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name) override fun resultingDatatype(program: Program): DataType? { @@ -1540,7 +1529,6 @@ class RegisterExpr(val register: Register, override val position: Position) : IE override fun constValue(program: Program): LiteralValue? = null override fun process(processor: IAstProcessor) = this override fun referencesIdentifier(name: String): Boolean = false - override fun isIterable(program: Program) = false override fun toString(): String { return "RegisterExpr(register=$register, pos=$position)" } @@ -1593,8 +1581,6 @@ data class IdentifierReference(val nameInSource: List, override val posi } } - override fun isIterable(program: Program): Boolean = resultingDatatype(program) in IterableDatatypes - fun heapId(namespace: INameScope): Int { val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this) return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap: $this") @@ -1713,8 +1699,6 @@ class FunctionCall(override var target: IdentifierReference, } return null // calling something we don't recognise... } - - override fun isIterable(program: Program) = resultingDatatype(program) in IterableDatatypes } diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index c976e95a4..a2e92f34d 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -150,10 +150,10 @@ private class AstChecker(private val program: Program, if(forLoop.body.containsNoCodeNorVars()) printWarning("for loop body is empty", forLoop.position) - if(!forLoop.iterable.isIterable(program)) { + val iterableDt = forLoop.iterable.resultingDatatype(program) + if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) { checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position)) } else { - val iterableDt = forLoop.iterable.resultingDatatype(program) if (forLoop.loopRegister != null) { printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position) // loop register diff --git a/compiler/src/prog8/ast/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/AstIdentifiersChecker.kt index a26242aca..e8362a534 100644 --- a/compiler/src/prog8/ast/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/AstIdentifiersChecker.kt @@ -172,7 +172,7 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first()) if(existing==null) { // create the local scoped for loop variable itself - val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, true, null, false, varName, null, forLoop.loopVar.position) + val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, false, varName, null, forLoop.loopVar.position) vardecl.linkParents(forLoop.body) forLoop.body.statements.add(0, vardecl) forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body' diff --git a/compiler/src/prog8/astvm/AstVm.kt b/compiler/src/prog8/astvm/AstVm.kt index f8fd8270d..277d6d259 100644 --- a/compiler/src/prog8/astvm/AstVm.kt +++ b/compiler/src/prog8/astvm/AstVm.kt @@ -2,6 +2,7 @@ package prog8.astvm import prog8.ast.* import prog8.compiler.RuntimeValue +import prog8.compiler.RuntimeValueRange import java.awt.EventQueue @@ -260,14 +261,37 @@ class AstVm(val program: Program) { TODO("$stmt") } is ForLoop -> { - TODO("$stmt") -// try { -// -// } catch(b: LoopControlBreak) { -// break -// } catch(c: LoopControlContinue){ -// continue -// } + val iterable = evaluate(stmt.iterable, program, runtimeVariables, ::executeSubroutine) + if (iterable.type !in IterableDatatypes && iterable !is RuntimeValueRange) + throw VmExecutionException("can only iterate over an iterable value: $stmt") + val iterator: Iterator = iterable.iterator() + if(stmt.loopRegister!=null) + TODO("for with register") + else if(stmt.loopVar!=null) { + for(v in iterator) { + try { + val value: LiteralValue = + when(stmt.loopVar.resultingDatatype(program)!!) { + DataType.UBYTE -> LiteralValue(DataType.UBYTE, (v as Number).toShort(), position=stmt.position) + DataType.BYTE -> LiteralValue(DataType.BYTE, (v as Number).toShort(), position=stmt.position) + DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = (v as Int), position=stmt.position) + DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = (v as Int), position=stmt.position) + DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = (v as Double), position=stmt.position) + DataType.STR, DataType.STR_S -> LiteralValue(DataType.UBYTE, (v as Char).toShort(), position=stmt.position) + else -> TODO("weird loopvar type") + } + val assignment = Assignment(listOf(AssignTarget(null, stmt.loopVar, null, null, + position=stmt.loopVar.position)), null, value, stmt.iterable.position) + assignment.linkParents(stmt.body) + executeStatement(stmt.body, assignment) // assign the new loop value to the loopvar + executeAnonymousScope(stmt.body) // and run the code + } catch (b: LoopControlBreak) { + break + } catch (c: LoopControlContinue) { + continue + } + } + } else TODO("strange for $stmt") } is WhileLoop -> { var condition = evaluate(stmt.condition, program, runtimeVariables, ::executeSubroutine) @@ -300,7 +324,6 @@ class AstVm(val program: Program) { } } - private fun evaluate(args: List): List = args.map { evaluate(it, program, runtimeVariables, ::executeSubroutine) } private fun performBuiltinFunction(name: String, args: List) { diff --git a/compiler/src/prog8/astvm/Expressions.kt b/compiler/src/prog8/astvm/Expressions.kt index bf5e1e016..3b27d4b5a 100644 --- a/compiler/src/prog8/astvm/Expressions.kt +++ b/compiler/src/prog8/astvm/Expressions.kt @@ -2,6 +2,8 @@ package prog8.astvm import prog8.ast.* import prog8.compiler.RuntimeValue +import prog8.compiler.RuntimeValueRange +import kotlin.math.abs fun evaluate(expr: IExpression, program: Program, runtimeVars: RuntimeVariables, executeSubroutine: (sub: Subroutine, args: List) -> List): RuntimeValue { @@ -60,11 +62,9 @@ fun evaluate(expr: IExpression, program: Program, runtimeVars: RuntimeVariables, } is DirectMemoryRead -> { TODO("$expr") - } is DirectMemoryWrite -> { TODO("$expr") - } is RegisterExpr -> { TODO("$expr") @@ -94,7 +94,25 @@ fun evaluate(expr: IExpression, program: Program, runtimeVars: RuntimeVariables, } } is RangeExpr -> { - TODO("eval range $expr") + val cRange = expr.toConstantIntegerRange(program.heap) + if(cRange!=null) + return RuntimeValueRange(expr.resultingDatatype(program)!!, cRange) + val fromVal = evaluate(expr.from, program, runtimeVars, executeSubroutine).integerValue() + val toVal = evaluate(expr.to, program, runtimeVars, executeSubroutine).integerValue() + val stepVal = evaluate(expr.step, program, runtimeVars, executeSubroutine).integerValue() + val range = when { + fromVal <= toVal -> when { + stepVal <= 0 -> IntRange.EMPTY + stepVal == 1 -> fromVal..toVal + else -> fromVal..toVal step stepVal + } + else -> when { + stepVal >= 0 -> IntRange.EMPTY + stepVal == -1 -> fromVal downTo toVal + else -> fromVal downTo toVal step abs(stepVal) + } + } + return RuntimeValueRange(expr.resultingDatatype(program)!!, range) } else -> { TODO("implement eval $expr") diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 73f6fa715..cb3b31926 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -1676,7 +1676,7 @@ internal class Compiler(private val program: Program): IAstProcessor { val idRef = loop.iterable as IdentifierReference val vardecl = idRef.targetVarDecl(program.namespace)!! val iterableValue = vardecl.value as LiteralValue - if(!iterableValue.isIterable(program)) + if(iterableValue.type !in IterableDatatypes) throw CompilerException("loop over something that isn't iterable ${loop.iterable}") translateForOverIterableVar(loop, loopVarDt, iterableValue) } diff --git a/compiler/src/prog8/compiler/RuntimeValue.kt b/compiler/src/prog8/compiler/RuntimeValue.kt index 4080ac5f3..535dcf3b9 100644 --- a/compiler/src/prog8/compiler/RuntimeValue.kt +++ b/compiler/src/prog8/compiler/RuntimeValue.kt @@ -1,6 +1,7 @@ package prog8.compiler import prog8.ast.* +import prog8.compiler.target.c64.Petscii import kotlin.math.abs import kotlin.math.pow @@ -10,7 +11,7 @@ import kotlin.math.pow * this runtime value can be used to *execute* the parsed Ast (or another intermediary form) * It contains a value of a variable during run time of the program and provides arithmetic operations on the value. */ -class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, val array: Array?=null, val heapId: Int?=null) { +open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, val array: Array?=null, val heapId: Int?=null) { val byteval: Short? val wordval: Int? @@ -501,4 +502,22 @@ class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, } } + open fun iterator(): Iterator { + return when (type) { + in StringDatatypes -> { + Petscii.encodePetscii(str!!, true).iterator() + } + in ArrayDatatypes -> { + array!!.iterator() + } + else -> throw IllegalArgumentException("cannot iterate over $this") + } + } +} + + +class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, 0) { + override fun iterator(): Iterator { + return range.iterator() + } } diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index cf203e408..0791c94bd 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -523,7 +523,8 @@ And this is a loop over the values of the array ``fibonacci_numbers`` where the } -You can inline the loop variable declaration in the for statement, including optional zp-tag:: +You can inline the loop variable declaration in the for statement, including optional zp-tag. In this case +the variable is not visible outside of the for loop:: for ubyte @zp fastindex in 10 to 20 { ; do something diff --git a/examples/test.p8 b/examples/test.p8 index b2b548c9a..19833958f 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,49 +3,39 @@ ~ main { - ubyte[256] sieve - ubyte candidate_prime = 2 ; is increased in the loop - sub start() { - memset(sieve, 256, false) ; clear the sieve, to reset starting situation on subsequent runs + ubyte i=99 - ; calculate primes - c64scr.print("prime numbers up to 255:\n\n") - ubyte amount=0 - while true { - ubyte prime = find_next_prime() - if prime==0 - break - c64scr.print_ub(prime) - c64scr.print(", ") - amount++ + for ubyte j in 20 to 40 step 5 { + c64scr.print_ub(j) + c64.CHROUT(',') } c64.CHROUT('\n') - c64scr.print("number of primes (expected 54): ") - c64scr.print_ub(amount) + + for uword j in 200 to 400 step 5 { + c64scr.print_uw(j) + c64.CHROUT(',') + } c64.CHROUT('\n') - } + for i in 200 to 50 step -20 { + c64scr.print_ub(i) + c64.CHROUT(',') + } + c64.CHROUT('\n') - sub find_next_prime() -> ubyte { + for i in "hello" { + c64scr.print_ub(i) + c64.CHROUT(',') + } + c64.CHROUT('\n') - while sieve[candidate_prime] { - candidate_prime++ - if candidate_prime==0 - return 0 ; we wrapped; no more primes available in the sieve + for i in [1,2,3,4,5] { + c64scr.print_ub(i) + c64.CHROUT(',') } - ; found next one, mark the multiples and return it. - sieve[candidate_prime] = true - uword multiple = candidate_prime - - - while multiple < len(sieve) { - sieve[lsb(multiple)] = true - multiple += candidate_prime - ; c64scr.print_uw(multiple) ; TODO - ; c4.CHROUT('\n') ; TODO - } - return candidate_prime + c64.CHROUT('\n') + c64.CHROUT('\n') } }