From 455f60fb843878326cf7db963dc649e7ec056a3a Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 20 Sep 2018 01:13:21 +0200 Subject: [PATCH] for loop and comparison expression fixes --- compiler/examples/mandelbrot.p8 | 35 ++-- compiler/examples/test.p8 | 66 ++---- compiler/src/prog8/ast/AST.kt | 50 ++++- compiler/src/prog8/ast/AstChecker.kt | 12 +- compiler/src/prog8/compiler/Compiler.kt | 191 ++++++++++-------- .../prog8/optimizing/ConstExprEvaluator.kt | 82 +------- compiler/src/prog8/stackvm/StackVm.kt | 29 +-- compiler/test/UnitTests.kt | 58 +++++- docs/source/programming.rst | 8 +- 9 files changed, 281 insertions(+), 250 deletions(-) diff --git a/compiler/examples/mandelbrot.p8 b/compiler/examples/mandelbrot.p8 index b3e99081c..c937d8591 100644 --- a/compiler/examples/mandelbrot.p8 +++ b/compiler/examples/mandelbrot.p8 @@ -4,8 +4,8 @@ sub start() -> () { - const word width = 159 - const word height = 127 + const word width = 320 + const word height = 256 word pixelx byte pixely float xx @@ -20,27 +20,28 @@ _vm_write_str("Calculating Mandelbrot fractal, have patience...\n") _vm_gfx_clearscr(11) - for pixely in 0 to height { ; @todo 255 as upper limit doesn't work it overflows the loop - for pixelx in 0 to width { - xx=pixelx/width/3+0.2 - yy=pixely/height/3.6+0.4 + for pixely in 0 to height-1 { + for pixelx in 0 to width-1 { + xx = pixelx/width/3+0.2 + yy = pixely/height/3.6+0.4 - x=0.0 - y=0.0 + x = 0.0 + y = 0.0 for iter in 0 to 31 { - if(x*x + y*y > 4) break + if (x*x + y*y > 4) break x2 = x*x - y*y + xx - y=x*y*2 + yy - x=x2 + y = x*y*2 + yy + x = x2 } - plotx = pixelx*2 - ploty = pixely*2 - _vm_gfx_pixel(plotx, ploty, iter) - _vm_gfx_pixel(plotx+1, ploty, iter) - _vm_gfx_pixel(plotx, ploty+1, iter) - _vm_gfx_pixel(plotx+1, ploty+1, iter) + _vm_gfx_pixel(pixelx, pixely, iter) +; plotx = pixelx*2 +; ploty = pixely*2 +; _vm_gfx_pixel(plotx, ploty, iter) +; _vm_gfx_pixel(plotx+1, ploty, iter) +; _vm_gfx_pixel(plotx, ploty+1, iter) +; _vm_gfx_pixel(plotx+1, ploty+1, iter) } } diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 303a38c06..2183919f3 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -1,56 +1,34 @@ -%output prg -%launcher basic -%option enable_floats ~ main { sub start() -> () { - if(X) goto yesx - else goto nox + byte i + byte from = 250 + byte last = 255 -yesx: - - if(X) { - A=0 - goto yesx ;; @todo fix undefined symbol error - return - } else { - A=1 - goto nox + for i in from to last { + _vm_write_num(i) + _vm_write_char($8d) } - return +; _vm_write_char($8d) +; _vm_write_num(i) +; _vm_write_char($8d) +; _vm_write_char($8d) +; +; from = 8 +; last = 0 +; +; for i in from to last step -1 { +; _vm_write_num(i) +; _vm_write_char($8d) +; } +; +; _vm_write_char($8d) +; _vm_write_num(i) +; _vm_write_char($8d) - sub bla() -> () { - return - sub fooz() -> () { - return - } - - sub fooz2() -> () { - return - } - return - - } - - A=45 - -nox: - word i - - for i in 10 to 20 { - if(i>12) goto fout ;; @todo fix undefined symbol error - break - continue - - - bla() ;; @todo fix undefined symbol error - } - -fout: - return } } diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 8dbf2b74e..9a8ce1603 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -655,7 +655,7 @@ data class AssignTarget(val register: Register?, val identifier: IdentifierRefer Register.AX, Register.AY, Register.XY -> DataType.WORD } - val symbol = namespace.lookup(identifier!!.nameInSource, stmt) ?: throw FatalAstException("symbol lookup failed: ${identifier!!.nameInSource}") + val symbol = namespace.lookup(identifier!!.nameInSource, stmt) ?: throw FatalAstException("symbol lookup failed: ${identifier.nameInSource}") if(symbol is VarDecl) return symbol.datatype throw FatalAstException("cannot determine datatype of assignment target $this") } @@ -781,6 +781,8 @@ class LiteralValue(val type: DataType, override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false val isString = type==DataType.STR || type==DataType.STR_P || type==DataType.STR_S || type==DataType.STR_PS + val isNumeric = type==DataType.BYTE || type==DataType.WORD || type==DataType.FLOAT + val isArray = type==DataType.ARRAY || type==DataType.ARRAY_W || type==DataType.MATRIX companion object { fun fromBoolean(bool: Boolean, position: Position) = @@ -863,12 +865,39 @@ class LiteralValue(val type: DataType, DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> true DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> true } + + override fun hashCode(): Int { + val bh = bytevalue?.hashCode() ?: 0x10001234 + val wh = wordvalue?.hashCode() ?: 0x01002345 + val fh = floatvalue?.hashCode() ?: 0x00103456 + val sh = strvalue?.hashCode() ?: 0x00014567 + val ah = arrayvalue?.hashCode() ?: 0x11119876 + return bh xor wh xor fh xor sh xor ah xor type.hashCode() + } + + override fun equals(other: Any?): Boolean { + if(other==null || other !is LiteralValue) + return false + return compareTo(other)==0 + } + + operator fun compareTo(other: LiteralValue): Int { + val numLeft = asNumericValue?.toDouble() + val numRight = other.asNumericValue?.toDouble() + if(numLeft!=null && numRight!=null) + return numLeft.compareTo(numRight) + + if(strvalue!=null && other.strvalue!=null) + return strvalue.compareTo(other.strvalue) + + throw ExpressionError("cannot compare type $type with ${other.type}", other.position) + } } class RangeExpr(var from: IExpression, var to: IExpression, - var step: IExpression?, + var step: IExpression, override val position: Position) : IExpression { override lateinit var parent: Node @@ -876,7 +905,7 @@ class RangeExpr(var from: IExpression, this.parent = parent from.linkParents(this) to.linkParents(this) - step?.linkParents(this) + step.linkParents(this) } override fun constValue(namespace: INameScope): LiteralValue? = null @@ -926,8 +955,7 @@ class RangeExpr(var from: IExpression, fromVal = (from as LiteralValue).asIntegerValue!! toVal = (to as LiteralValue).asIntegerValue!! } - val stepLv = step as? LiteralValue ?: return null - val stepVal = stepLv.asIntegerValue ?: 1 + val stepVal = (step as? LiteralValue)?.asIntegerValue ?: 1 return when { fromVal <= toVal -> when { stepVal <= 0 -> IntRange.EMPTY @@ -1030,7 +1058,10 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val } -class Jump(val address: Int?, val identifier: IdentifierReference?, override val position: Position) : IStatement { +class Jump(val address: Int?, + val identifier: IdentifierReference?, + val generatedLabel: String?, + override val position: Position) : IStatement { override lateinit var parent: Node override fun linkParents(parent: Node) { @@ -1041,7 +1072,7 @@ class Jump(val address: Int?, val identifier: IdentifierReference?, override val override fun process(processor: IAstProcessor) = processor.process(this) override fun toString(): String { - return "Jump(addr: $address, identifier: $identifier, target: pos=$position)" + return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)" } } @@ -1445,7 +1476,7 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): IStatement { if(identifier()!=null) identifier()?.toAst() else scoped_identifier()?.toAst() - return Jump(address, identifier, toPosition()) + return Jump(address, identifier, null, toPosition()) } @@ -1585,7 +1616,8 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression { if(funcall!=null) return funcall if (rangefrom!=null && rangeto!=null) { - return RangeExpr(rangefrom.toAst(), rangeto.toAst(), rangestep?.toAst(), toPosition()) + val step = rangestep?.toAst() ?: LiteralValue(DataType.BYTE, 1, position = toPosition()) + return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition()) } if(childCount==3 && children[0].text=="(" && children[2].text==")") diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index d676b9ee4..849daaff5 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -428,7 +428,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: super.process(range) val from = range.from.constValue(namespace) val to = range.to.constValue(namespace) - var step = 0 + var step = 1 if(range.step!=null) { val stepLv = range.step?.constValue(namespace) ?: LiteralValue(DataType.BYTE, 1, position = range.position) if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) { @@ -441,10 +441,10 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: when { from.asIntegerValue!=null && to.asIntegerValue!=null -> { if(from.asIntegerValue == to.asIntegerValue) - printWarning("range contains just a single value", range.position) - else if(from.asIntegerValue < to.asIntegerValue && step<0) + err("range is just a single value - don't use a loop here, use an assignment") // TODO optimize this away automatically in the statement optimizer + else if(from.asIntegerValue < to.asIntegerValue && step<=0) err("ascending range requires step > 0") - else if(from.asIntegerValue > to.asIntegerValue && step>0) + else if(from.asIntegerValue > to.asIntegerValue && step>=0) err("descending range requires step < 0") } from.strvalue!=null && to.strvalue!=null -> { @@ -452,9 +452,9 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: err("range from and to must be a single character") if(from.strvalue[0] == to.strvalue[0]) printWarning("range contains just a single character", range.position) - else if(from.strvalue[0] < to.strvalue[0] && step<0) + else if(from.strvalue[0] < to.strvalue[0] && step<=0) err("ascending range requires step > 0") - else if(from.strvalue[0] > to.strvalue[0] && step<0) + else if(from.strvalue[0] > to.strvalue[0] && step>=0) err("descending range requires step < 0") } else -> err("range expression must be over integers or over characters") diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 92241d9a9..50d44aced 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -1,5 +1,6 @@ package prog8.compiler +import jdk.nashorn.internal.ir.JumpStatement import prog8.ast.* import prog8.stackvm.* import java.io.PrintStream @@ -388,14 +389,16 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva var jumpAddress: Value? = null var jumpLabel: String? = null - if(stmt.address!=null) { - jumpAddress = Value(DataType.WORD, stmt.address) - } else { - val target = stmt.identifier!!.targetStatement(namespace)!! - jumpLabel = when(target) { - is Label -> target.scopedname - is Subroutine -> target.scopedname - else -> throw CompilerException("invalid jump target type ${target::class}") + when { + stmt.generatedLabel!=null -> jumpLabel = stmt.generatedLabel + stmt.address!=null -> jumpAddress = Value(DataType.WORD, stmt.address) + else -> { + val target = stmt.identifier!!.targetStatement(namespace)!! + jumpLabel = when(target) { + is Label -> target.scopedname + is Subroutine -> target.scopedname + else -> throw CompilerException("invalid jump target type ${target::class}") + } } } stackvmProg.line(stmt.position) @@ -521,27 +524,34 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva if(loop.iterable is RangeExpr) { val range = (loop.iterable as RangeExpr).toConstantIntegerRange() - when { - range!=null -> { - if (range.isEmpty()) - throw CompilerException("loop over empty range") - when (loopVarDt) { - DataType.BYTE -> { - if (range.first < 0 || range.first > 255 || range.last < 0 || range.last > 255) - throw CompilerException("range out of bounds for byte") - } - DataType.WORD -> { - if (range.first < 0 || range.first > 65535 || range.last < 0 || range.last > 65535) - throw CompilerException("range out of bounds for word") - } - else -> throw CompilerException("range must be byte or word") + if(range!=null) { + // loop over a range with constant start, last and step values + if (range.isEmpty()) + throw CompilerException("loop over empty range should have been optimized away") + else if (range.count()==1) + throw CompilerException("loop over just 1 value should have been optimized away") + if((range.last-range.first) % range.step != 0) + throw CompilerException("range first and last must be inclusive") + when (loopVarDt) { + DataType.BYTE -> { + if (range.first < 0 || range.first > 255 || range.last < 0 || range.last > 255) + throw CompilerException("range out of bounds for byte") } - translateForOverConstantRange(loopVarName, loopVarDt, range, loop.body) + DataType.WORD -> { + if (range.first < 0 || range.first > 65535 || range.last < 0 || range.last > 65535) + throw CompilerException("range out of bounds for word") + } + else -> throw CompilerException("range must be byte or word") } - loop.loopRegister!=null -> + translateForOverConstantRange(loopVarName, loopVarDt, range, loop.body) + } else { + // loop over a range where one or more of the start, last or step values is not a constant + if(loop.loopRegister!=null) { translateForOverVariableRange(null, loop.loopRegister, loopVarDt, loop.iterable as RangeExpr, loop.body) - else -> + } + else { translateForOverVariableRange(loop.loopVar!!.nameInSource, null, loopVarDt, loop.iterable as RangeExpr, loop.body) + } } } else { val litVal = loop.iterable as? LiteralValue @@ -562,7 +572,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva private fun translateForOverConstantRange(varname: String, varDt: DataType, range: IntProgression, body: MutableList) { /** * for LV in start..last { body } - * (and we already know that the range is not empty) + * (and we already know that the range is not empty, and first and last are exactly inclusive.) * (also we know that the range's last value is really the exact last occurring value of the range) * (and finally, start and last are constant integer values) * -> @@ -573,16 +583,16 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva * ..continue statement: goto continue * .. * continue: - * LV++ (if step=1) or LV+=step (if step > 1) - * LV-- (if step=-11) or LV-=abs(step) (if step < 1) - * if LV!=last goto loop ; if last > 0 - * if LV>=0 goto loop ; if last==0 + * LV++ (if step=1) / LV += step (if step > 1) + * LV-- (if step=-1) / LV -= abs(step) (if step < 1) + * if LV!=(last+step) goto loop * break: - * + * nop */ val loopLabel = makeLabel("loop") val continueLabel = makeLabel("continue") val breakLabel = makeLabel("break") + continueStmtLabelStack.push(continueLabel) breakStmtLabelStack.push(breakLabel) @@ -608,17 +618,16 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva stackvmProg.instr(Opcode.POP_VAR, varValue) } } - if(range.last>0) { - stackvmProg.instr(Opcode.PUSH, Value(varDt, range.last)) - stackvmProg.instr(Opcode.PUSH_VAR, varValue) - stackvmProg.instr(Opcode.SUB) - stackvmProg.instr(Opcode.BNE, callLabel = loopLabel) - } else { - stackvmProg.instr(Opcode.PUSH_VAR, varValue) - stackvmProg.instr(Opcode.BNE, callLabel = loopLabel) - } + + // TODO: optimize edge cases if last value = 255 or 0 (for bytes) etc. to avoid PUSH / SUB opcodes and make use of the wrapping around of the value. + stackvmProg.instr(Opcode.PUSH, Value(varDt, range.last+range.step)) + stackvmProg.instr(Opcode.PUSH_VAR, varValue) + stackvmProg.instr(Opcode.SUB) + stackvmProg.instr(Opcode.BNE, callLabel = loopLabel) + stackvmProg.label(breakLabel) stackvmProg.instr(Opcode.NOP) + // note: ending value of loop register / variable is *undefined* after this point! breakStmtLabelStack.pop() continueStmtLabelStack.pop() @@ -627,22 +636,26 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva private fun translateForOverVariableRange(varname: List?, register: Register?, varDt: DataType, range: RangeExpr, body: MutableList) { /** * for LV in start..last { body } - * (and we already know that the range is not empty) - * (also we know that the range's last value is really the exact last occurring value of the range) - * (and finally, start and last are constant integer values) + * (where at least one of the start, last, step values is not a constant) + * (so we can't make any static assumptions about them) * -> * LV = start * loop: + * if (step > 0) { + * if(LV>last) goto break + * } else { + * if(LV 0) { + // if LV > last goto break + BinaryExpression(loopVar,">", range.to, range.position) + } else { + // if LV < last goto break + BinaryExpression(loopVar,"<", range.to, range.position) + } + val ifstmt = IfStatement(condition, + listOf(Jump(null, null, breakLabel, range.position)), + emptyList(), + range.position) + ifstmt.linkParents(range.parent) + translate(ifstmt) + } else { + // generate code for the whole if statement + TODO("code for non-constant step comparison") + } + translate(body) stackvmProg.label(continueLabel) - if(stepAddition!=null) - translate(stepAddition) - if(stepIncrement!=null) - translate(stepIncrement) + when (literalStepValue) { + 1 -> { + // LV++ + val postIncr = PostIncrDecr(makeAssignmentTarget(), "++", range.position) + postIncr.linkParents(range.parent) + translate(postIncr) + } + -1 -> { + // LV-- + val postIncr = PostIncrDecr(makeAssignmentTarget(), "--", range.position) + postIncr.linkParents(range.parent) + translate(postIncr) + } + else -> { + // LV += step + val addAssignment = Assignment(makeAssignmentTarget(), "+=", range.step, range.position) + addAssignment.linkParents(range.parent) + translate(addAssignment) + } + } - val comparison = BinaryExpression( - if(varname!=null) - IdentifierReference(varname, range.position) - else - RegisterExpr(register!!, range.position) - ,"<=", range.to, range.position) - comparison.linkParents(range.parent) - translate(comparison) - stackvmProg.instr(Opcode.BNE, callLabel = loopLabel) stackvmProg.label(breakLabel) stackvmProg.instr(Opcode.NOP) + // note: ending value of loop register / variable is *undefined* after this point! breakStmtLabelStack.pop() continueStmtLabelStack.pop() diff --git a/compiler/src/prog8/optimizing/ConstExprEvaluator.kt b/compiler/src/prog8/optimizing/ConstExprEvaluator.kt index a891e6bd2..446ae76de 100644 --- a/compiler/src/prog8/optimizing/ConstExprEvaluator.kt +++ b/compiler/src/prog8/optimizing/ConstExprEvaluator.kt @@ -1,7 +1,6 @@ package prog8.optimizing import prog8.ast.* -import kotlin.math.floor import kotlin.math.pow class ConstExprEvaluator { @@ -21,85 +20,16 @@ class ConstExprEvaluator { "and" -> logicaland(left, right) "or" -> logicalor(left, right) "xor" -> logicalxor(left, right) - "<" -> compareless(left, right) - ">" -> comparegreater(left, right) - "<=" -> comparelessequal(left, right) - ">=" -> comparegreaterequal(left, right) - "==" -> compareequal(left, right) - "!=" -> comparenotequal(left, right) + "<" -> LiteralValue.fromBoolean(left < right, left.position) + ">" -> LiteralValue.fromBoolean(left > right, left.position) + "<=" -> LiteralValue.fromBoolean(left <= right, left.position) + ">=" -> LiteralValue.fromBoolean(left >= right, left.position) + "==" -> LiteralValue.fromBoolean(left == right, left.position) + "!=" -> LiteralValue.fromBoolean(left != right, left.position) else -> throw FatalAstException("const evaluation for invalid operator $operator") } } - private fun comparenotequal(left: LiteralValue, right: LiteralValue): LiteralValue { - val leq = compareequal(left, right) - return LiteralValue.fromBoolean(leq.bytevalue == 1.toShort(), left.position) - } - - private fun compareequal(left: LiteralValue, right: LiteralValue): LiteralValue { - val leftvalue: Any = when { - left.bytevalue!=null -> left.bytevalue - left.wordvalue!=null -> left.wordvalue - left.floatvalue!=null -> left.floatvalue - left.strvalue!=null -> left.strvalue - left.arrayvalue!=null -> left.arrayvalue - else -> throw FatalAstException("missing literal value") - } - val rightvalue: Any = when { - right.bytevalue!=null -> right.bytevalue - right.wordvalue!=null -> right.wordvalue - right.floatvalue!=null -> right.floatvalue - right.strvalue!=null -> right.strvalue - right.arrayvalue!=null -> right.arrayvalue - else -> throw FatalAstException("missing literal value") - } - return LiteralValue.fromBoolean(leftvalue == rightvalue, left.position) - } - - private fun comparegreaterequal(left: LiteralValue, right: LiteralValue): LiteralValue { - val error = "cannot compute $left >= $right" - return when { - left.asIntegerValue!=null -> when { - right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue >= right.asIntegerValue, left.position) - right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue >= right.floatvalue, left.position) - else -> throw ExpressionError(error, left.position) - } - left.floatvalue!=null -> when { - right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue >= right.asIntegerValue, left.position) - right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue >= right.floatvalue, left.position) - else -> throw ExpressionError(error, left.position) - } - else -> throw ExpressionError(error, left.position) - } - } - - private fun comparelessequal(left: LiteralValue, right: LiteralValue): LiteralValue { - val error = "cannot compute $left >= $right" - return when { - left.asIntegerValue!=null -> when { - right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue <= right.asIntegerValue, left.position) - right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue <= right.floatvalue, left.position) - else -> throw ExpressionError(error, left.position) - } - left.floatvalue!=null -> when { - right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue <= right.asIntegerValue, left.position) - right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue <= right.floatvalue, left.position) - else -> throw ExpressionError(error, left.position) - } - else -> throw ExpressionError(error, left.position) - } - } - - private fun comparegreater(left: LiteralValue, right: LiteralValue): LiteralValue { - val leq = comparelessequal(left, right) - return LiteralValue.fromBoolean(leq.bytevalue == 1.toShort(), left.position) - } - - private fun compareless(left: LiteralValue, right: LiteralValue): LiteralValue { - val leq = comparegreaterequal(left, right) - return LiteralValue.fromBoolean(leq.bytevalue == 1.toShort(), left.position) - } - private fun logicalxor(left: LiteralValue, right: LiteralValue): LiteralValue { val error = "cannot compute $left locical-bitxor $right" return when { diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index 7452f4011..b53462698 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -286,6 +286,16 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= } } + override fun equals(other: Any?): Boolean { + if(other==null || other !is Value) + return false + return compareTo(other)==0 + } + + operator fun compareTo(other: Value): Int { + return numericValue().toDouble().compareTo(other.numericValue().toDouble()) + } + fun add(other: Value): Value { val v1 = numericValue() val v2 = other.numericValue() @@ -499,13 +509,6 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= else -> throw VmExecutionException("not can only work on byte/word") } } - - fun compareLess(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() < other.numericValue().toDouble()) 1 else 0) - fun compareGreater(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() > other.numericValue().toDouble()) 1 else 0) - fun compareLessEq(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() <= other.numericValue().toDouble()) 1 else 0) - fun compareGreaterEq(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() >= other.numericValue().toDouble()) 1 else 0) - fun compareEqual(other: Value) = Value(DataType.BYTE, if(this.numericValue() == other.numericValue()) 1 else 0) - fun compareNotEqual(other: Value) = Value(DataType.BYTE, if(this.numericValue() != other.numericValue()) 1 else 0) } @@ -1392,27 +1395,27 @@ class StackVm(val traceOutputFile: String?) { } Opcode.LESS -> { val (top, second) = evalstack.pop2() - evalstack.push(second.compareLess(top)) + evalstack.push(Value(DataType.BYTE, if(second < top) 1 else 0)) } Opcode.GREATER -> { val (top, second) = evalstack.pop2() - evalstack.push(second.compareGreater(top)) + evalstack.push(Value(DataType.BYTE, if(second > top) 1 else 0)) } Opcode.LESSEQ -> { val (top, second) = evalstack.pop2() - evalstack.push(second.compareLessEq(top)) + evalstack.push(Value(DataType.BYTE, if(second <= top) 1 else 0)) } Opcode.GREATEREQ -> { val (top, second) = evalstack.pop2() - evalstack.push(second.compareGreaterEq(top)) + evalstack.push(Value(DataType.BYTE, if(second >= top) 1 else 0)) } Opcode.EQUAL -> { val (top, second) = evalstack.pop2() - evalstack.push(second.compareEqual(top)) + evalstack.push(Value(DataType.BYTE, if(second == top) 1 else 0)) } Opcode.NOTEQUAL -> { val (top, second) = evalstack.pop2() - evalstack.push(second.compareNotEqual(top)) + evalstack.push(Value(DataType.BYTE, if(second != top) 1 else 0)) } Opcode.B2WORD -> { val byte = evalstack.pop() diff --git a/compiler/test/UnitTests.kt b/compiler/test/UnitTests.kt index 0500fe390..bc73f128e 100644 --- a/compiler/test/UnitTests.kt +++ b/compiler/test/UnitTests.kt @@ -1,9 +1,5 @@ package prog8tests -import prog8.ast.DataType -import prog8.ast.Position -import prog8.ast.VarDecl -import prog8.ast.VarDeclType import prog8.compiler.* import prog8.compiler.target.c64.* import org.hamcrest.MatcherAssert.assertThat @@ -11,6 +7,8 @@ import org.hamcrest.Matchers.closeTo import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance +import prog8.ast.* +import prog8.stackvm.Value import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse @@ -290,4 +288,56 @@ class TestPetscii { assertFailsWith { Petscii.decodeScreencode(listOf(-1)) } assertFailsWith { Petscii.decodeScreencode(listOf(256)) } } + + @Test + fun testLiteralValueComparisons() { + val ten = LiteralValue(DataType.WORD, wordvalue=10, position=Position("", 0 ,0 ,0)) + val nine = LiteralValue(DataType.BYTE, bytevalue=9, position=Position("", 0 ,0 ,0)) + assertTrue(ten == ten) + assertFalse(ten == nine) + assertFalse(ten != ten) + assertTrue(ten != nine) + + assertTrue(ten > nine) + assertTrue(ten >= nine) + assertTrue(ten >= ten) + assertFalse(ten > ten) + + assertFalse(ten < nine) + assertFalse(ten <= nine) + assertTrue(ten <= ten) + assertFalse(ten < ten) + + val abc = LiteralValue(DataType.STR, strvalue = "abc", position=Position("", 0 ,0 ,0)) + val abd = LiteralValue(DataType.STR, strvalue = "abd", position=Position("", 0 ,0 ,0)) + assertTrue(abc==abc) + assertTrue(abc!=abd) + assertFalse(abc!=abc) + assertTrue(abc < abd) + assertTrue(abc <= abd) + assertFalse(abd <= abc) + assertTrue(abd >= abc) + assertTrue(abd > abc) + assertFalse(abc > abd) + } + + @Test + fun testStackvmValueComparisons() { + val ten = Value(DataType.FLOAT, 10) + val nine = Value(DataType.WORD, 9) + assertTrue(ten == ten) + assertFalse(ten == nine) + assertFalse(ten != ten) + assertTrue(ten != nine) + + assertTrue(ten > nine) + assertTrue(ten >= nine) + assertTrue(ten >= ten) + assertFalse(ten > ten) + + assertFalse(ten < nine) + assertFalse(ten <= nine) + assertTrue(ten <= ten) + assertFalse(ten < ten) + } } diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 781450883..124243f98 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -68,7 +68,8 @@ Label This is a named position in your code where you can jump to from another place. You can jump to it with a jump statement elsewhere. It is also possible to use a subroutine call to a label (but without parameters and return value). - + Labels can only be defined in a block or in another subroutine, so you can't define a label + inside a loop statement block for instance. Scope Also known as 'namespace', this is a named box around the symbols defined in it. @@ -299,6 +300,11 @@ The *repeat--until* loop is used to repeat a piece of code until a certain condi You can also create loops by using the ``goto`` statement, but this should usually be avoided. +.. attention:: + The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately + after the loop without first assigning a new value to it! + (this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value) + Conditional Execution ---------------------