do..until condition can now refer to variables defined in the loop's inner scope.

This commit is contained in:
Irmen de Jong 2020-09-24 19:26:07 +02:00
parent e1d0dbed0c
commit f5db31b8ff
11 changed files with 318 additions and 85 deletions

View File

@ -338,7 +338,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output("do ")
untilLoop.body.accept(this)
output(" until ")
untilLoop.untilCondition.accept(this)
untilLoop.condition.accept(this)
}
override fun visit(returnStmt: Return) {

View File

@ -334,8 +334,8 @@ internal class AstChecker(private val program: Program,
}
override fun visit(untilLoop: UntilLoop) {
if(untilLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.err("condition value should be an integer type", untilLoop.untilCondition.position)
if(untilLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.err("condition value should be an integer type", untilLoop.condition.position)
super.visit(untilLoop)
}

View File

@ -348,7 +348,7 @@ abstract class AstWalker {
fun visit(untilLoop: UntilLoop, parent: Node) {
track(before(untilLoop, parent), untilLoop, parent)
untilLoop.untilCondition.accept(this, untilLoop)
untilLoop.condition.accept(this, untilLoop)
untilLoop.body.accept(this, untilLoop)
track(after(untilLoop, parent), untilLoop, parent)
}

View File

@ -115,7 +115,7 @@ interface IAstVisitor {
}
fun visit(untilLoop: UntilLoop) {
untilLoop.untilCondition.accept(this)
untilLoop.condition.accept(this)
untilLoop.body.accept(this)
}

View File

@ -815,19 +815,19 @@ class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override
}
class UntilLoop(var body: AnonymousScope,
var untilCondition: Expression,
var condition: Expression,
override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
untilCondition.linkParents(this)
condition.linkParents(this)
body.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===untilCondition -> untilCondition = replacement as Expression
node===condition -> condition = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}

View File

@ -164,4 +164,34 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
return noModifications
}
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
val binExpr = ifStatement.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// if x -> if x!=0, if x+5 -> if x+5 != 0
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
return noModifications
}
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val binExpr = untilLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// until x -> until x!=0, until x+5 -> until x+5 != 0
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
return noModifications
}
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val binExpr = whileLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// while x -> while x!=0, while x+5 -> while x+5 != 0
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
return noModifications
}
}

View File

@ -845,47 +845,32 @@ $save .byte 0
}
private fun translate(stmt: IfStatement) {
when {
stmt.elsepart.containsNoCodeNorVars() -> {
// empty else
expressionsAsmGen.translateExpression(stmt.condition)
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
val endLabel = makeLabel("if_end")
out(" beq $endLabel")
translate(stmt.truepart)
out(endLabel)
}
stmt.truepart.containsNoCodeNorVars() -> {
// empty true part
expressionsAsmGen.translateExpression(stmt.condition)
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
val endLabel = makeLabel("if_end")
out(" bne $endLabel")
translate(stmt.elsepart)
out(endLabel)
}
else -> {
expressionsAsmGen.translateExpression(stmt.condition)
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
val elseLabel = makeLabel("if_else")
val endLabel = makeLabel("if_end")
out(" beq $elseLabel")
translate(stmt.truepart)
out(" jmp $endLabel")
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
}
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
if (stmt.elsepart.containsNoCodeNorVars()) {
// empty else
val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.truepart)
out(endLabel)
}
else {
// both true and else parts
val elseLabel = makeLabel("if_else")
val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, elseLabel)
translate(stmt.truepart)
out(" jmp $endLabel")
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
}
}
private fun translateTestStack(dataType: DataType) {
when(dataType) {
in ByteDatatypes -> out(" inx | lda P8ESTACK_LO,x")
in WordDatatypes -> out(" inx | lda P8ESTACK_LO,x | ora P8ESTACK_HI,x")
DataType.FLOAT -> throw AssemblyError("conditional value should be an integer (boolean)")
else -> throw AssemblyError("non-numerical dt")
}
private fun checkBooleanExpression(condition: Expression) {
if(condition !is BinaryExpression || condition.operator !in comparisonOperators)
throw AssemblyError("expected boolean expression $condition")
}
private fun translate(stmt: RepeatLoop) {
@ -1004,25 +989,13 @@ $counterVar .byte 0""")
}
private fun translate(stmt: WhileLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
loopEndLabels.push(endLabel)
out(whileLabel)
expressionsAsmGen.translateExpression(stmt.condition)
val conditionDt = stmt.condition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda P8ESTACK_LO,x | beq $endLabel")
} else {
out("""
inx
lda P8ESTACK_LO,x
bne +
lda P8ESTACK_HI,x
beq $endLabel
+ """)
}
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.body)
out(" jmp $whileLabel")
out(endLabel)
@ -1030,26 +1003,14 @@ $counterVar .byte 0""")
}
private fun translate(stmt: UntilLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
out(repeatLabel)
translate(stmt.body)
expressionsAsmGen.translateExpression(stmt.untilCondition)
val conditionDt = stmt.untilCondition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda P8ESTACK_LO,x | beq $repeatLabel")
} else {
out("""
inx
lda P8ESTACK_LO,x
bne +
lda P8ESTACK_HI,x
beq $repeatLabel
+ """)
}
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, repeatLabel)
out(endLabel)
loopEndLabels.pop()
}

View File

@ -28,6 +28,220 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
internal fun translateComparisonExpressionWithJumpIfFalse(expr: BinaryExpression, jumpIfFalseLabel: String) {
// first, if it is of the form: <constvalue> <comparison> X , swap the operands around.
var left = expr.left
var right = expr.right
var operator = expr.operator
var leftConstVal = left.constValue(program)
var rightConstVal = right.constValue(program)
if(leftConstVal!=null) {
val tmp = left
left = right
right = tmp
val tmp2 = leftConstVal
leftConstVal = rightConstVal
rightConstVal = tmp2
when(expr.operator) {
"<" -> operator = ">"
"<=" -> operator = ">="
">" -> operator = "<"
">=" -> operator = "<="
}
}
val dt = left.inferType(program).typeOrElse(DataType.STRUCT)
when (operator) {
"==" -> {
when (dt) {
in ByteDatatypes -> {
translateByteEquals(left, right, leftConstVal, rightConstVal)
asmgen.out(" bne $jumpIfFalseLabel")
}
in WordDatatypes -> {
translateWordEquals(left, right, leftConstVal, rightConstVal)
asmgen.out(" bne $jumpIfFalseLabel")
}
DataType.FLOAT -> {
TODO("float eq.")
}
else -> throw AssemblyError("weird operand datatype")
}
}
"!=" -> {
when (dt) {
in ByteDatatypes -> {
translateByteEquals(left, right, leftConstVal, rightConstVal)
asmgen.out(" beq $jumpIfFalseLabel")
}
in WordDatatypes -> {
translateWordEquals(left, right, leftConstVal, rightConstVal)
asmgen.out(" beq $jumpIfFalseLabel")
}
DataType.FLOAT -> {
TODO("float neq.")
}
else -> throw AssemblyError("weird operand datatype")
}
}
"<" -> {
when(dt) {
DataType.UBYTE -> translateUbyteLess(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.BYTE -> translateByteLess(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.UWORD -> translateUwordLess(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.WORD -> translateWordLess(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> TODO("float l.")
else -> throw AssemblyError("weird operand datatype")
}
}
"<=" -> {
when(dt) {
DataType.UBYTE -> translateUbyteLessOrEqual(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.BYTE -> translateByteLessOrEqual(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.UWORD -> translateUwordLessOrEqual(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.WORD -> translateWordLessOrEqual(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> TODO("float l.e.")
else -> throw AssemblyError("weird operand datatype")
}
}
">" -> {
when(dt) {
DataType.UBYTE -> translateUbyteGreater(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.BYTE -> translateByteGreater(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.UWORD -> translateUwordGreater(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.WORD -> translateWordGreater(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> TODO("float g.")
else -> throw AssemblyError("weird operand datatype")
}
}
">=" -> {
when(dt) {
DataType.UBYTE -> translateUbyteGreaterOrEqual(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.BYTE -> translateByteGreaterOrEqual(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.UWORD -> translateUwordGreaterOrEqual(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.WORD -> translateWordGreaterOrEqual(left, operator, right, leftConstVal,rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> TODO("float g.e.")
else -> throw AssemblyError("weird operand datatype")
}
}
}
}
private fun translateUbyteLess(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateByteLess(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateUwordLess(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateWordLess(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateUbyteLessOrEqual(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateByteLessOrEqual(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateUwordLessOrEqual(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateWordLessOrEqual(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateUbyteGreater(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateByteGreater(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateUwordGreater(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateWordGreater(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateUbyteGreaterOrEqual(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateByteGreaterOrEqual(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateUwordGreaterOrEqual(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateWordGreaterOrEqual(left: Expression, operator: String, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?, jumpIfFalseLabel: String) {
TODO("Not yet implemented")
}
private fun translateByteEquals(left: Expression, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?) {
if(rightConstVal!=null) {
if(leftConstVal!=null) {
if(rightConstVal==leftConstVal)
asmgen.out(" lda #0 | sec")
else
asmgen.out(" lda #1 | sec")
return
} else {
when(left) {
is IdentifierReference -> {
val name = asmgen.asmVariableName(left)
asmgen.out(" lda $name | cmp #${rightConstVal.number}")
return
}
// TODO optimize direct mem read here too
else -> TODO()
}
}
}
TODO("Not yet implemented")
}
private fun translateWordEquals(left: Expression, right: Expression, leftConstVal: NumericLiteralValue?, rightConstVal: NumericLiteralValue?) {
if(rightConstVal!=null) {
if(leftConstVal!=null) {
if(rightConstVal==leftConstVal)
asmgen.out(" lda #0 | sec")
else
asmgen.out(" lda #1 | sec")
} else {
when(left) {
is IdentifierReference -> {
val name = asmgen.asmVariableName(left)
asmgen.out("""
lda $name
cmp #<${rightConstVal.number}
bne +
lda $name+1
cmp #>${rightConstVal.number}
+""")
return
}
// TODO optimize direct mem read here too
else -> TODO()
}
}
}
TODO("Not yet implemented")
}
private fun translateExpression(expression: FunctionCall) {
val functionName = expression.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
@ -100,7 +314,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> {
// sign extend
asmgen.out("""
asmgen.out("""
lda P8ESTACK_LO+1,x
ora #$7f
bmi +

View File

@ -249,11 +249,11 @@ internal class StatementOptimizer(private val program: Program,
}
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val constvalue = untilLoop.untilCondition.constValue(program)
val constvalue = untilLoop.condition.constValue(program)
if(constvalue!=null) {
if(constvalue.asBooleanValue) {
// always true -> keep only the statement block (if there are no break statements)
errors.warn("condition is always true", untilLoop.untilCondition.position)
errors.warn("condition is always true", untilLoop.condition.position)
if(!hasBreak(untilLoop.body))
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
} else {

View File

@ -4,9 +4,9 @@ TODO
- get rid of all other TODO's in the code ;-)
- line-circle-gfx examples are now a few hundred bytes larger than before (~4.0/4.1 version i think?). Why is that, can it be fixed?
- add support? example? for processing arguments to a sys call : sys 999, 1, 2, "aaa"
- make it possible for array literals to not only contain compile time constants
- further optimize assignment codegeneration
- further optimize expression codegeneration, most notably comparisons
- implement @stack for asmsub parameters
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)

View File

@ -4,15 +4,43 @@
%zeropage basicsafe
main $0900{
main {
sub start() {
ubyte x= 1
do {
ubyte v = x
word x= 1
if x==10 {
x=99
} else {
x=100
}
if x==10 {
;nothing
} else {
x=100
}
if x==10 {
x=99
} else {
; nothing
}
while 1==x {
txt.print_w(x)
txt.chrout('\n')
x++
} until v==0
}
txt.chrout('\n')
x=0
do {
x++
txt.print_w(x)
txt.chrout('\n')
} until x==10
; @($c000) *= 99 ; TODO implement