From 1d37841575827fa9557dc48b84ec6a5e64d6c544 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 6 Oct 2018 17:21:34 +0200 Subject: [PATCH] for, while, repeat, if, branch bodies are now (anonymous) symbol scopes --- compiler/examples/cube3d.p8 | 49 ++--- compiler/examples/mandelbrot.p8 | 32 ++- compiler/examples/numbergame.p8 | 10 +- compiler/examples/swirl.p8 | 6 +- compiler/examples/test.p8 | 41 +++- compiler/src/prog8/ast/AST.kt | 194 +++++++++--------- compiler/src/prog8/ast/AstChecker.kt | 33 ++- compiler/src/prog8/compiler/Compiler.kt | 38 ++-- .../prog8/optimizing/StatementOptimizer.kt | 12 +- docs/source/programming.rst | 36 ++-- docs/source/syntaxreference.rst | 3 +- 11 files changed, 239 insertions(+), 215 deletions(-) diff --git a/compiler/examples/cube3d.p8 b/compiler/examples/cube3d.p8 index db22c00b4..2f40851b0 100644 --- a/compiler/examples/cube3d.p8 +++ b/compiler/examples/cube3d.p8 @@ -29,9 +29,6 @@ float[len(ycoor)] rotatedy float[len(zcoor)] rotatedz - ; general index var - byte i - sub start() { if irq.time_changed { irq.time_changed = 0 @@ -39,6 +36,7 @@ _vm_gfx_text(8, 6, 1, "Spin") _vm_gfx_text(29, 11, 1, "to Win !") + byte i for i in 0 to width//10 { _vm_gfx_line(i*2+width//2-width//10, 130, i*10.w, 199, 6) } @@ -71,6 +69,7 @@ float Azy = cosb*sinc float Azz = cosb*cosc + byte i for i in 0 to len(xcoor)-1 { rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i] rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i] @@ -80,9 +79,6 @@ sub draw_edges() { - word edge - byte e_from - byte e_to sub toscreenx(x: float, z: float) -> word { return floor(x/(4.2+z) * flt(height)) + width // 2 @@ -93,35 +89,34 @@ } ; draw all edges of the object + word edge for edge in edges { - e_from = msb(edge) - e_to = lsb(edge) + byte e_from = msb(edge) + byte e_to = lsb(edge) _vm_gfx_line(toscreenx(rotatedx[e_from], rotatedz[e_from]), toscreeny(rotatedy[e_from], rotatedz[e_from]), toscreenx(rotatedx[e_to], rotatedz[e_to]), toscreeny(rotatedy[e_to], rotatedz[e_to]), e_from+e_to) } ; accentuate the vertices a bit with small boxes - word sx - word sy - byte col + byte i for i in 0 to len(xcoor)-1 { - sx = toscreenx(rotatedx[i], rotatedz[i]) - sy = toscreeny(rotatedy[i], rotatedz[i]) - col=i+2 - _vm_gfx_pixel(sx-1, sy-1, col) - _vm_gfx_pixel(sx, sy-1, col) - _vm_gfx_pixel(sx+1, sy-1, col) - _vm_gfx_pixel(sx-1, sy, col) - _vm_gfx_pixel(sx, sy, col) - _vm_gfx_pixel(sx+1, sy, col) - _vm_gfx_pixel(sx-1, sy+1, col) - _vm_gfx_pixel(sx, sy+1, col) - _vm_gfx_pixel(sx+1, sy+1, col) + word sx = toscreenx(rotatedx[i], rotatedz[i]) + word sy = toscreeny(rotatedy[i], rotatedz[i]) + byte color=i+2 + _vm_gfx_pixel(sx-1, sy-1, color) + _vm_gfx_pixel(sx, sy-1, color) + _vm_gfx_pixel(sx+1, sy-1, color) + _vm_gfx_pixel(sx-1, sy, color) + _vm_gfx_pixel(sx, sy, color) + _vm_gfx_pixel(sx+1, sy, color) + _vm_gfx_pixel(sx-1, sy+1, color) + _vm_gfx_pixel(sx, sy+1, color) + _vm_gfx_pixel(sx+1, sy+1, color) - _vm_gfx_pixel(sx, sy-2, col) - _vm_gfx_pixel(sx+2, sy, col) - _vm_gfx_pixel(sx, sy+2, col) - _vm_gfx_pixel(sx-2, sy, col) + _vm_gfx_pixel(sx, sy-2, color) + _vm_gfx_pixel(sx+2, sy, color) + _vm_gfx_pixel(sx, sy+2, color) + _vm_gfx_pixel(sx-2, sy, color) } } } diff --git a/compiler/examples/mandelbrot.p8 b/compiler/examples/mandelbrot.p8 index d1c1d4234..d9e607d14 100644 --- a/compiler/examples/mandelbrot.p8 +++ b/compiler/examples/mandelbrot.p8 @@ -1,33 +1,27 @@ %option enable_floats ~ main { + const word width = 320 // 2 + const word height = 256 // 2 + const word xoffset = 40 + const word yoffset = 30 sub start() { - - const word width = 320 // 2 - const word height = 256 // 2 - const word xoffset = 40 - const word yoffset = 30 - word pixelx - byte pixely - float xx - float yy - float x - float y - float xsquared - float ysquared - byte iter - word plotx - byte ploty - _vm_gfx_clearscr(11) _vm_gfx_text(2, 1, 1, "Calculating Mandelbrot Fractal...") + byte pixely ; @todo allow defining loopvar INSIDE loop scope ("for byte pixely in ...") for pixely in yoffset to yoffset+height-1 { - yy = flt((pixely-yoffset))/height/3.6+0.4 + float yy = flt((pixely-yoffset))/height/3.6+0.4 + word pixelx ; @todo allow defining loopvar INSIDE loop scope ("for word pixelx in ...") for pixelx in xoffset to xoffset+width-1 { - xx = flt((pixelx-xoffset))/width/3.0+0.2 + float xx = flt((pixelx-xoffset))/width/3.0+0.2 + float xsquared + float ysquared + float x + float y + byte iter ; @todo re-initialize variable when entering scope x = 0.0 y = 0.0 diff --git a/compiler/examples/numbergame.p8 b/compiler/examples/numbergame.p8 index 3b3a57988..e8362aa08 100644 --- a/compiler/examples/numbergame.p8 +++ b/compiler/examples/numbergame.p8 @@ -1,11 +1,8 @@ -%output prg - ~ main { sub start() { str name = " " str guess = "000000" - byte guessednumber - byte attempts_left + byte secretnumber = rnd() % 100 _vm_write_str("Let's play a number guessing game!\n") _vm_write_str("Enter your name: ") @@ -14,8 +11,7 @@ _vm_write_str(name) _vm_write_str(".\nI am thinking of a number from 1 to 100! You'll have to guess it!\n") - byte secretnumber = rnd() % 100 - + byte attempts_left for attempts_left in 10 to 1 step -1 { _vm_write_str("\nYou have ") _vm_write_num(attempts_left) @@ -23,7 +19,7 @@ if attempts_left>1 _vm_write_str("es") _vm_write_str(" left. What is your next guess? ") _vm_input_str(guess) - guessednumber = str2byte(guess) + byte guessednumber = str2byte(guess) if guessednumber==secretnumber { _vm_write_str("\nYou guessed it, impressive!\n") _vm_write_str("Thanks for playing.\n") diff --git a/compiler/examples/swirl.p8 b/compiler/examples/swirl.p8 index a4efcb892..4b8d8e6f7 100644 --- a/compiler/examples/swirl.p8 +++ b/compiler/examples/swirl.p8 @@ -9,14 +9,12 @@ _vm_gfx_clearscr(0) - float x - float y float t byte color while(1) { - x = sin(t*1.01) + cos(t*1.1234) - y = cos(t) + sin(t*0.03456) + float x = sin(t*1.01) + cos(t*1.1234) + float y = cos(t) + sin(t*0.03456) _vm_gfx_pixel(screenx(x), screeny(y), color//16) t += 0.01 color++ diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 47c9e03ba..c9d989b36 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -5,19 +5,42 @@ ~ main { sub start() { - - const float c1 = 11.11 - const float c2 = 22.22 - float v - float r byte x - word w - r=flt(x) - w=wrd(x) - w=wrdhi(x) + for x in 0 to 20 { + float xx = sin(flt(x)) + float yy = cos(flt(x)) + } + + while(1) { + float xx = sin(flt(x)) + float yy = cos(flt(x)) + } + + repeat { + float xx = sin(flt(x)) + float yy = cos(flt(x)) + } until(1) + + + +; for x in 0 to 30 { +; float xx = 2123.33 +; float yy = 2444.55 +; xx = sin(flt(x)) +; yy = cos(flt(x)) +; r = xx*yy +; } +; +; for x in 0 to 40 { +; float xx = 3123.33 +; float yy = 3444.55 +; xx = sin(flt(x)) +; yy = cos(flt(x)) +; r = xx*yy +; } return } diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index d2e4a488e..3ca487546 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -148,14 +148,14 @@ interface IAstProcessor { fun process(ifStatement: IfStatement): IStatement { ifStatement.condition = ifStatement.condition.process(this) - ifStatement.statements = ifStatement.statements.map { it.process(this) } - ifStatement.elsepart = ifStatement.elsepart.map { it.process(this) } + ifStatement.truepart = ifStatement.truepart.process(this) + ifStatement.elsepart = ifStatement.elsepart.process(this) return ifStatement } fun process(branchStatement: BranchStatement): IStatement { - branchStatement.statements = branchStatement.statements.map { it.process(this) } - branchStatement.elsepart = branchStatement.elsepart.map { it.process(this) } + branchStatement.truepart = branchStatement.truepart.process(this) + branchStatement.elsepart = branchStatement.elsepart.process(this) return branchStatement } @@ -195,19 +195,19 @@ interface IAstProcessor { fun process(forLoop: ForLoop): IStatement { forLoop.loopVar?.process(this) forLoop.iterable = forLoop.iterable.process(this) - forLoop.body = forLoop.body.asSequence().map {it.process(this)}.toMutableList() + forLoop.body = forLoop.body.process(this) return forLoop } fun process(whileLoop: WhileLoop): IStatement { whileLoop.condition = whileLoop.condition.process(this) - whileLoop.statements = whileLoop.statements.map { it.process(this) } + whileLoop.body = whileLoop.body.process(this) return whileLoop } fun process(repeatLoop: RepeatLoop): IStatement { repeatLoop.untilCondition = repeatLoop.untilCondition.process(this) - repeatLoop.statements = repeatLoop.statements.map { it.process(this) } + repeatLoop.body = repeatLoop.body.process(this) return repeatLoop } @@ -227,6 +227,11 @@ interface IAstProcessor { assignTarget.identifier?.process(this) return assignTarget } + + fun process(scope: AnonymousScope): AnonymousScope { + scope.statements = scope.statements.asSequence().map { it.process(this) }.toMutableList() + return scope + } } @@ -288,10 +293,32 @@ interface INameScope { val name: String val position: Position var statements: MutableList + val parent: Node - fun registerUsedName(name: String) + fun linkParents(parent: Node) - fun subScopes() = statements.asSequence().filter { it is INameScope }.map { it as INameScope }.associate { it.name to it } + fun subScopes(): Map { + val subscopes = mutableMapOf() + for(stmt in statements) { + when(stmt) { + is INameScope -> subscopes[stmt.name] = stmt + is ForLoop -> subscopes[stmt.body.name] = stmt.body + is RepeatLoop -> subscopes[stmt.body.name] = stmt.body + is WhileLoop -> subscopes[stmt.body.name] = stmt.body + is BranchStatement -> { + subscopes[stmt.truepart.name] = stmt.truepart + if(!stmt.elsepart.isEmpty()) + subscopes[stmt.elsepart.name] = stmt.elsepart + } + is IfStatement -> { + subscopes[stmt.truepart.name] = stmt.truepart + if(!stmt.elsepart.isEmpty()) + subscopes[stmt.elsepart.name] = stmt.elsepart + } + } + } + return subscopes + } fun labelsAndVariables() = statements.asSequence().filter { it is Label || it is VarDecl } .associate {((it as? Label)?.name ?: (it as? VarDecl)?.name)!! to it } @@ -345,30 +372,8 @@ interface INameScope { val removed = statements.remove(statement) if(!removed) throw AstException("node to remove wasn't found") } -} - -/** - * Inserted into the Ast in place of modified nodes (not inserted directly as a parser result) - * It can hold zero or more replacement statements that have to be inserted at that point. - */ -class AnonymousStatementList(override var parent: Node, - var statements: List, - override val position: Position) : IStatement { - - init { - linkParents(parent) - } - - override fun linkParents(parent: Node) { - this.parent = parent - statements.forEach { it.linkParents(this) } - } - - override fun process(processor: IAstProcessor): IStatement { - statements = statements.map { it.process(processor) } - return this - } + fun isEmpty() = statements.isEmpty() } @@ -382,7 +387,8 @@ object BuiltinFunctionScopePlaceholder : INameScope { override val name = "<>" override val position = Position("<>", 0, 0, 0) override var statements = mutableListOf() - override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes") + override var parent: Node = ParentSentinel + override fun linkParents(parent: Node) {} } class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement { @@ -411,15 +417,14 @@ class Module(override val name: String, } override fun definingScope(): INameScope = GlobalNamespace("<<>>", statements, position) - override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes") } private class GlobalNamespace(override val name: String, override var statements: MutableList, override val position: Position) : INameScope { - - private val scopedNamesUsed: MutableSet = mutableSetOf("main", "main.start") // main and main.start are always used + override var parent = ParentSentinel + override fun linkParents(parent: Node) {} override fun lookup(scopedName: List, statement: Node): IStatement? { if(scopedName.last() in BuiltinFunctions) { @@ -429,24 +434,11 @@ private class GlobalNamespace(override val name: String, return builtinPlaceholder } val stmt = super.lookup(scopedName, statement) - if(stmt!=null) { - val targetScopedName = when(stmt) { - is Label -> stmt.scopedname - is VarDecl -> stmt.scopedname - is Block -> stmt.scopedname - is Subroutine -> stmt.scopedname - else -> throw NameError("wrong identifier target: $stmt", stmt.position) - } - registerUsedName(targetScopedName) + return when (stmt) { + is Label, is VarDecl, is Block, is Subroutine -> stmt + null -> null + else -> throw NameError("wrong identifier target: $stmt", stmt.position) } - return stmt - } - - override fun registerUsedName(name: String) { - // make sure to also register each scope separately - scopedNamesUsed.add(name) - if('.' in name) - registerUsedName(name.substringBeforeLast('.')) } } @@ -469,8 +461,6 @@ class Block(override val name: String, override fun toString(): String { return "Block(name=$name, address=$address, ${statements.size} statements)" } - - override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes") } @@ -669,7 +659,7 @@ data class AssignTarget(val register: Register?, fun process(processor: IAstProcessor) = processor.process(this) - fun determineDatatype(namespace: INameScope, heap: HeapValues, stmt: IStatement): DataType { + fun determineDatatype(namespace: INameScope, heap: HeapValues, stmt: IStatement): DataType? { if(register!=null) return when(register){ Register.A, Register.X, Register.Y -> DataType.BYTE @@ -677,8 +667,7 @@ data class AssignTarget(val register: Register?, } if(identifier!=null) { - val symbol = namespace.lookup(identifier.nameInSource, stmt) - ?: throw FatalAstException("symbol lookup failed: ${identifier.nameInSource}") + val symbol = namespace.lookup(identifier.nameInSource, stmt) ?: return null if (symbol is VarDecl) return symbol.datatype } @@ -1291,6 +1280,27 @@ class InlineAssembly(val assembly: String, override val position: Position) : IS class RegisterOrStatusflag(val register: Register?, val statusflag: Statusflag?) +class AnonymousScope(override var statements: MutableList, + override val position: Position) : INameScope, IStatement { + override val name: String + override lateinit var parent: Node + + init { + name = "<<>>" + sequenceNumber++ + } + + companion object { + private var sequenceNumber = 1 + } + + override fun linkParents(parent: Node) { + this.parent = parent + statements.forEach { it.linkParents(this) } + } + override fun process(processor: IAstProcessor) = processor.process(this) +} + class Subroutine(override val name: String, val parameters: List, val returnvalues: List, @@ -1314,8 +1324,6 @@ class Subroutine(override val name: String, override fun toString(): String { return "Subroutine(name=$name, parameters=$parameters, returnvalues=$returnvalues, ${statements.size} statements, address=$asmAddress)" } - - override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes") } @@ -1330,16 +1338,16 @@ open class SubroutineParameter(val name: String, } class IfStatement(var condition: IExpression, - var statements: List, - var elsepart: List, + var truepart: AnonymousScope, + var elsepart: AnonymousScope, override val position: Position) : IStatement { override lateinit var parent: Node override fun linkParents(parent: Node) { this.parent = parent condition.linkParents(this) - statements.forEach { it.linkParents(this) } - elsepart.forEach { it.linkParents(this) } + truepart.linkParents(this) + elsepart.linkParents(this) } override fun process(processor: IAstProcessor): IStatement = processor.process(this) @@ -1347,29 +1355,25 @@ class IfStatement(var condition: IExpression, class BranchStatement(var condition: BranchCondition, - var statements: List, - var elsepart: List, + var truepart: AnonymousScope, + var elsepart: AnonymousScope, override val position: Position) : IStatement { override lateinit var parent: Node override fun linkParents(parent: Node) { this.parent = parent - statements.forEach { it.linkParents(this) } - elsepart.forEach { it.linkParents(this) } + truepart.linkParents(this) + elsepart.linkParents(this) } override fun process(processor: IAstProcessor): IStatement = processor.process(this) - - override fun toString(): String { - return "Branch(cond: $condition, ${statements.size} stmts, ${elsepart.size} else-stmts, pos=$position)" - } } class ForLoop(val loopRegister: Register?, val loopVar: IdentifierReference?, var iterable: IExpression, - var body: MutableList, + var body: AnonymousScope, override val position: Position) : IStatement { override lateinit var parent: Node @@ -1377,7 +1381,7 @@ class ForLoop(val loopRegister: Register?, this.parent=parent loopVar?.linkParents(this) iterable.linkParents(this) - body.forEach { it.linkParents(this) } + body.linkParents(this) } override fun process(processor: IAstProcessor) = processor.process(this) @@ -1389,21 +1393,21 @@ class ForLoop(val loopRegister: Register?, class WhileLoop(var condition: IExpression, - var statements: List, + var body: AnonymousScope, override val position: Position) : IStatement { override lateinit var parent: Node override fun linkParents(parent: Node) { this.parent = parent condition.linkParents(this) - statements.forEach { it.linkParents(this) } + body.linkParents(this) } override fun process(processor: IAstProcessor): IStatement = processor.process(this) } -class RepeatLoop(var statements: List, +class RepeatLoop(var body: AnonymousScope, var untilCondition: IExpression, override val position: Position) : IStatement { override lateinit var parent: Node @@ -1411,7 +1415,7 @@ class RepeatLoop(var statements: List, override fun linkParents(parent: Node) { this.parent = parent untilCondition.linkParents(this) - statements.forEach { it.linkParents(this) } + body.linkParents(this) } override fun process(processor: IAstProcessor): IStatement = processor.process(this) @@ -1826,21 +1830,25 @@ private fun prog8Parser.ArrayliteralContext.toAst() : Array = private fun prog8Parser.If_stmtContext.toAst(): IfStatement { val condition = expression().toAst() - val statements = statement_block()?.toAst() ?: listOf(statement().toAst()) - val elsepart = else_part()?.toAst() ?: emptyList() - return IfStatement(condition, statements, elsepart, toPosition()) + val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) + val elseStatements = else_part()?.toAst() ?: mutableListOf() + val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition() ?: statement().toPosition()) + val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition()) + return IfStatement(condition, trueScope, elseScope, toPosition()) } -private fun prog8Parser.Else_partContext.toAst(): List { - return statement_block()?.toAst() ?: listOf(statement().toAst()) +private fun prog8Parser.Else_partContext.toAst(): MutableList { + return statement_block()?.toAst() ?: mutableListOf(statement().toAst()) } private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement { val branchcondition = branchcondition().toAst() - val statements = statement_block()?.toAst() ?: listOf(statement().toAst()) - val elsepart = else_part()?.toAst() ?: emptyList() - return BranchStatement(branchcondition, statements, elsepart, toPosition()) + val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) + val elseStatements = else_part()?.toAst() ?: mutableListOf() + val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition() ?: statement().toPosition()) + val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition()) + return BranchStatement(branchcondition, trueScope, elseScope, toPosition()) } private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase()) @@ -1850,8 +1858,8 @@ private fun prog8Parser.ForloopContext.toAst(): ForLoop { val loopregister = register()?.toAst() val loopvar = identifier()?.toAst() val iterable = expression()!!.toAst() - val body = statement_block().toAst() - return ForLoop(loopregister, loopvar, iterable, body, toPosition()) + val scope = AnonymousScope(statement_block().toAst(), statement_block().toPosition()) + return ForLoop(loopregister, loopvar, iterable, scope, toPosition()) } @@ -1862,13 +1870,15 @@ private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition()) private fun prog8Parser.WhileloopContext.toAst(): WhileLoop { val condition = expression().toAst() - val statements = statement_block()?.toAst() ?: listOf(statement().toAst()) - return WhileLoop(condition, statements, toPosition()) + val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) + val scope = AnonymousScope(statements, statement_block()?.toPosition() ?: statement().toPosition()) + return WhileLoop(condition, scope, toPosition()) } private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop { val untilCondition = expression().toAst() - val statements = statement_block()?.toAst() ?: listOf(statement().toAst()) - return RepeatLoop(statements, untilCondition, toPosition()) + val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) + val scope = AnonymousScope(statements, statement_block()?.toPosition() ?: statement().toPosition()) + return RepeatLoop(scope, untilCondition, toPosition()) } diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index efcaf51a4..e6b74b5dc 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -275,19 +275,21 @@ class AstChecker(private val namespace: INameScope, } val targetDatatype = assignment.target.determineDatatype(namespace, heap, assignment) - val constVal = assignment.value.constValue(namespace, heap) - if(constVal!=null) { - checkValueTypeAndRange(targetDatatype, null, constVal, heap) - } else { - val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap) - if(sourceDatatype==null) { - if(assignment.value is FunctionCall) - checkResult.add(ExpressionError("function call doesn't return a value to use in assignment", assignment.value.position)) - else - checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position)) - } - else { - checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.value, assignment.position) + if(targetDatatype!=null) { + val constVal = assignment.value.constValue(namespace, heap) + if(constVal!=null) { + checkValueTypeAndRange(targetDatatype, null, constVal, heap) + } else { + val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap) + if(sourceDatatype==null) { + if(assignment.value is FunctionCall) + checkResult.add(ExpressionError("function call doesn't return a value to use in assignment", assignment.value.position)) + else + checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position)) + } + else { + checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.value, assignment.position) + } } } @@ -310,11 +312,6 @@ class AstChecker(private val namespace: INameScope, err("recursive var declaration") } - // for now, variables can only be declared in a block or subroutine (not in a loop statement block) @todo fix this (anonymous namescope) - if(decl.parent !is Block && decl.parent !is Subroutine) { - err ("variables must be declared at block or subroutine level") - } - when(decl.type) { VarDeclType.VAR, VarDeclType.CONST -> { if (decl.value == null) { diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index b539037e9..5a3fdd7ae 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -387,7 +387,6 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, for (stmt: IStatement in statements) { stmtUniqueSequenceNr++ when (stmt) { - is AnonymousStatementList -> translate(stmt.statements) is Label -> translate(stmt) is Return -> translate(stmt) is Assignment -> translate(stmt) // normal and augmented assignments @@ -401,6 +400,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, is ForLoop -> translate(stmt) is WhileLoop -> translate(stmt) is RepeatLoop -> translate(stmt) + is AnonymousScope -> translate(stmt) is Directive, is VarDecl, is Subroutine -> {} // skip this, already processed these. is InlineAssembly -> throw CompilerException("inline assembly is not supported by the StackVM") else -> TODO("translate statement $stmt to stackvm") @@ -574,11 +574,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, } if(branch.elsepart.isEmpty()) { stackvmProg.instr(opcode, callLabel = labelEnd) - translate(branch.statements) + translate(branch.truepart) stackvmProg.label(labelEnd) } else { stackvmProg.instr(opcode, callLabel = labelElse) - translate(branch.statements) + translate(branch.truepart) stackvmProg.instr(Opcode.JUMP, callLabel = labelEnd) stackvmProg.label(labelElse) translate(branch.elsepart) @@ -616,12 +616,12 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, val labelEnd = makeLabel("end") if(stmt.elsepart.isEmpty()) { stackvmProg.instr(Opcode.BZ, callLabel = labelEnd) - translate(stmt.statements) + translate(stmt.truepart) stackvmProg.label(labelEnd) } else { val labelElse = makeLabel("else") stackvmProg.instr(Opcode.BZ, callLabel = labelElse) - translate(stmt.statements) + translate(stmt.truepart) stackvmProg.instr(Opcode.JUMP, callLabel = labelEnd) stackvmProg.label(labelElse) translate(stmt.elsepart) @@ -1209,7 +1209,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, val target = stmt.target.identifier!!.targetStatement(namespace)!! when(target) { is VarDecl -> { - val opcode = opcodePushvar(stmt.target.determineDatatype(namespace, heap, stmt)) + val opcode = opcodePushvar(stmt.target.determineDatatype(namespace, heap, stmt)!!) stackvmProg.instr(opcode, callLabel = target.scopedname) } else -> throw CompilerException("invalid assignment target type ${target::class}") @@ -1231,7 +1231,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, val target = stmt.target.identifier!!.targetStatement(namespace)!! when(target) { is VarDecl -> { - val opcode = opcodePopvar(stmt.target.determineDatatype(namespace, heap, stmt)) + val opcode = opcodePopvar(stmt.target.determineDatatype(namespace, heap, stmt)!!) stackvmProg.instr(opcode, callLabel = target.scopedname) } else -> throw CompilerException("invalid assignment target type ${target::class}") @@ -1383,10 +1383,10 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, } 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) + translateForOverVariableRange(null, loop.loopRegister, loop.iterable as RangeExpr, loop.body) } else { - translateForOverVariableRange(loop.loopVar!!.nameInSource, null, loopVarDt, loop.iterable as RangeExpr, loop.body) + translateForOverVariableRange(loop.loopVar!!.nameInSource, null, loop.iterable as RangeExpr, loop.body) } } } else { @@ -1496,7 +1496,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, continueStmtLabelStack.pop() } - private fun translateForOverConstantRange(varname: String, varDt: DataType, range: IntProgression, body: MutableList) { + private fun translateForOverConstantRange(varname: String, varDt: DataType, range: IntProgression, body: AnonymousScope) { /** * for LV in start..last { body } * (and we already know that the range is not empty, and first and last are exactly inclusive.) @@ -1559,7 +1559,8 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, continueStmtLabelStack.pop() } - private fun translateForOverVariableRange(varname: List?, register: Register?, varDt: DataType, range: RangeExpr, body: MutableList) { + private fun translateForOverVariableRange(varname: List?, register: Register?, + range: RangeExpr, body: AnonymousScope) { /* * for LV in start..last { body } * (where at least one of the start, last, step values is not a constant) @@ -1628,8 +1629,8 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, BinaryExpression(loopVar,"<", range.to, range.position) } val ifstmt = IfStatement(condition, - listOf(Jump(null, null, breakLabel, range.position)), - emptyList(), + AnonymousScope(mutableListOf(Jump(null, null, breakLabel, range.position)), range.position), + AnonymousScope(mutableListOf(), range.position), range.position) ifstmt.linkParents(range.parent) translate(ifstmt) @@ -1665,8 +1666,9 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, } val branch = BranchStatement( BranchCondition.NZ, - listOf(Jump(null, null, loopLabel, range.position)), - emptyList(), range.position) + AnonymousScope(mutableListOf(Jump(null, null, loopLabel, range.position)), range.position), + AnonymousScope(mutableListOf(), range.position), + range.position) branch.linkParents(range.parent) translate(branch) } @@ -1690,6 +1692,8 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, continueStmtLabelStack.pop() } + private fun translate(scope: AnonymousScope) = translate(scope.statements) + private fun translate(stmt: WhileLoop) { /* @@ -1714,7 +1718,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, continueStmtLabelStack.push(continueLabel) stackvmProg.instr(Opcode.JUMP, callLabel = continueLabel) stackvmProg.label(loopLabel) - translate(stmt.statements) + translate(stmt.body) stackvmProg.label(continueLabel) translate(stmt.condition) stackvmProg.instr(Opcode.BNZ, callLabel = loopLabel) @@ -1747,7 +1751,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, breakStmtLabelStack.push(breakLabel) continueStmtLabelStack.push(continueLabel) stackvmProg.label(loopLabel) - translate(stmt.statements) + translate(stmt.body) stackvmProg.label(continueLabel) translate(stmt.untilCondition) stackvmProg.instr(Opcode.BZ, callLabel = loopLabel) diff --git a/compiler/src/prog8/optimizing/StatementOptimizer.kt b/compiler/src/prog8/optimizing/StatementOptimizer.kt index 8e1141116..3af8474d8 100644 --- a/compiler/src/prog8/optimizing/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizing/StatementOptimizer.kt @@ -50,11 +50,11 @@ class StatementOptimizer(private val globalNamespace: INameScope, private val he return if(constvalue.asBooleanValue){ // always true -> keep only if-part printWarning("condition is always true", ifStatement.position) - AnonymousStatementList(ifStatement.parent, ifStatement.statements, ifStatement.position) + ifStatement.truepart } else { // always false -> keep only else-part printWarning("condition is always false", ifStatement.position) - AnonymousStatementList(ifStatement.parent, ifStatement.elsepart, ifStatement.position) + ifStatement.elsepart } } return ifStatement @@ -68,8 +68,8 @@ class StatementOptimizer(private val globalNamespace: INameScope, private val he // for loop over a (constant) range of just a single value-- optimize the loop away // loopvar/reg = range value , follow by block val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, forLoop.position), null, range.from, forLoop.position) - forLoop.body.add(0, assignment) - return AnonymousStatementList(forLoop.parent, forLoop.body, forLoop.position) + forLoop.body.statements.add(0, assignment) + return forLoop.body } } return forLoop @@ -86,7 +86,7 @@ class StatementOptimizer(private val globalNamespace: INameScope, private val he } else { // always false -> ditch whole statement printWarning("condition is always false", whileLoop.position) - AnonymousStatementList(whileLoop.parent, emptyList(), whileLoop.position) + AnonymousScope(mutableListOf(), whileLoop.position) } } return whileLoop @@ -99,7 +99,7 @@ class StatementOptimizer(private val globalNamespace: INameScope, private val he return if(constvalue.asBooleanValue){ // always true -> keep only the statement block printWarning("condition is always true", repeatLoop.position) - AnonymousStatementList(repeatLoop.parent, repeatLoop.statements, repeatLoop.position) + repeatLoop.body } else { // always false printWarning("condition is always false", repeatLoop.position) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 2027e6339..dc6e4a223 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -90,7 +90,7 @@ Scope Blocks, Scopes, and accessing Symbols ------------------------------------- -Blocks are the separate pieces of code and data of your program. They are combined +**Blocks** are the top level separate pieces of code and data of your program. They are combined into a single output program. No code or data can occur outside a block. Here's an example:: ~ main $c000 { @@ -111,6 +111,18 @@ Usually it is omitted, and the compiler will automatically choose the location ( the previous block in memory). The address must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$200`` is the cpu stack). +**The special "ZP" ZeroPage block** + +Blocks named "ZP" are treated a bit differently: they refer to the ZeroPage. +The contents of every block with that name (this one may occur multiple times) are merged into one. +Its start address is always set to ``$04``, because ``$00 - $01`` are used by the hardware +and ``$02 - $03`` are reserved as general purpose scratch registers. + + +.. _scopes: + +**Scopes** + .. sidebar:: Scoped access to symbols / "dotted names" @@ -118,21 +130,16 @@ The address must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100` So, accessing a variable ``counter`` defined in subroutine ``worker`` in block ``main``, can be done from anywhere by using ``main.worker.counter``. -A block is also a *scope* in your program so the symbols in the block don't clash with -symbols of the same name defined elsewhere in the same file or in another file. -You can refer to the symbols in a particular block by using a *dotted name*: ``blockname.symbolname``. -Labels inside a subroutine are appended again to that; ``blockname.subroutinename.label``. -A symbol name that's not a dotted name is searched for in the current scope, if it's not found there, -one scope higher, and so on until it is found. +*Symbols* are names defined in a certain *scope*. Inside the same scope, you can refer +to them by their 'short' name directly. If the symbol is not found in the same scope, +the enclosing scope is searched for it, and so on, until the symbol is found. +Scopes are created using several statements: - -**The special "ZP" ZeroPage block** - -Blocks named "ZP" are treated a bit differently: they refer to the ZeroPage. -The contents of every block with that name (this one may occur multiple times) are merged into one. -Its start address is always set to ``$04``, because ``$00 - $01`` are used by the hardware -and ``$02 - $03`` are reserved as general purpose scratch registers. +- blocks (top-level named scope) +- subroutines (nested named scopes) +- for, while, repeat loops (anonymous scope) +- if statements and branching conditionals (anonymous scope) Program Start and Entry Point @@ -172,6 +179,7 @@ Variables and values -------------------- Variables are named values that can change during the execution of the program. +They can be defined inside any scope (blocks, subroutines, for loops, etc.) See :ref:`Scopes `. When declaring a numeric variable it is possible to specify the initial value, if you don't want it to be zero. For other data types it is required to specify that initial value it should get. Values will usually be part of an expression or assignment statement:: diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 726b66dd3..97b37db39 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -236,8 +236,7 @@ type identifier type storage size example var declara ``byte[x]`` unsigned byte array x bytes ``byte[4] myvar = [1, 2, 3, 4]`` ``word[x]`` unsigned word array 2*x bytes ``word[4] myvar = [1, 2, 3, 4]`` ``float[x]`` floating-point array 5*x bytes ``float[4] myvar = [1.1, 2.2, 3.3, 4.4]`` -``byte[x,y]`` unsigned byte matrix x*y bytes ``byte[40,25] myvar = @todo`` - word-matrix not supported +``byte[x,y]`` unsigned byte matrix x*y bytes ``byte[40,25] myvar = 255`` ``str`` string (petscii) varies ``str myvar = "hello."`` implicitly terminated by a 0-byte ``str_p`` pascal-string (petscii) varies ``str_p myvar = "hello."``