AnonymousScope refactor: it's no longer a INameScope

because it doesn't contain scoped variables (these are moved to the subroutine's scope)
This commit is contained in:
Irmen de Jong 2021-10-26 19:10:48 +02:00
parent 5e1459564a
commit 743c8b44a2
16 changed files with 208 additions and 264 deletions

View File

@ -1,11 +1,8 @@
package prog8.compiler
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.internedStringsModuleName
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
@ -25,7 +22,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// This allows you to restart the program and have the same starting values of the variables
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope.nextSibling(decl) as? Assignment
val nextAssign = decl.nextSibling() as? Assignment
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
decl.value = null
else {
@ -55,14 +52,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// use the other part of the expression to split.
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope),
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
} else {
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope),
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
@ -101,29 +98,11 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
subroutineVariables.addAll(decls.map { it.name to it })
val sub = scope.definingSubroutine
if (sub != null) {
// move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same!
val replacements = mutableListOf<IAstModification>()
val movements = mutableListOf<IAstModification.InsertFirst>()
for(decl in decls) {
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
} else {
replacements.add(IAstModification.Remove(decl, scope))
}
movements.add(IAstModification.InsertFirst(decl, sub))
}
return replacements + movements
}
return noModifications
}
@ -372,7 +351,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// assign the indexing expression to the helper variable, but only if that hasn't been done already
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope))
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
return modifications
}

View File

@ -278,6 +278,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.preprocessAst()
programAst.checkIdentifiers(errors, compilerOptions)
errors.report()
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR

View File

@ -1,6 +1,7 @@
package prog8.compiler.astprocessing
import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
@ -196,7 +197,7 @@ internal class AstChecker(private val program: Program,
is Label,
is VarDecl,
is InlineAssembly,
is INameScope,
is IStatementContainer,
is NopStatement -> true
else -> false
}
@ -217,7 +218,7 @@ internal class AstChecker(private val program: Program,
super.visit(label)
}
private fun hasReturnOrJump(scope: INameScope): Boolean {
private fun hasReturnOrJump(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{
var count=0

View File

@ -113,6 +113,14 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
internal fun Program.preprocessAst() {
val transforms = AstPreprocessor()
transforms.visit(this)
var mods = transforms.applyModifications()
while(mods>0)
mods = transforms.applyModifications()
}
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)

View File

@ -0,0 +1,48 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
class AstPreprocessor : AstWalker() {
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
// move vardecls in Anonymous scope up to the containing subroutine
// and add initialization assignment in its place if needed
val vars = scope.statements.filterIsInstance<VarDecl>()
if(vars.any() && scope.definingScope !== parent) {
val parentscope = scope.definingScope
val movements = mutableListOf<IAstModification>()
val replacements = mutableListOf<IAstModification>()
for(decl in vars) {
if(decl.type != VarDeclType.VAR) {
movements.add(IAstModification.InsertFirst(decl, parentscope))
replacements.add(IAstModification.Remove(decl, scope))
} else {
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
} else {
replacements.add(IAstModification.Remove(decl, scope))
}
movements.add(IAstModification.InsertFirst(decl, parentscope))
}
}
return movements + replacements
}
return noModifications
}
}

View File

@ -1,9 +1,6 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.expressions.*
@ -16,17 +13,17 @@ import prog8.compiler.IErrorReporter
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
return listOf(IAstModification.Remove(nopStatement, parent as IStatementContainer))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
return if(parent is IStatementContainer)
listOf(ScopeFlatten(scope, parent as IStatementContainer))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {

View File

@ -20,7 +20,6 @@ import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.math.absoluteValue
@ -49,7 +48,7 @@ internal class AsmGen(private val program: Program,
internal val loopEndLabels = ArrayDeque<String>()
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
internal val slabs = mutableMapOf<String, Int>()
internal val removals = mutableListOf<Pair<Statement, INameScope>>()
internal val removals = mutableListOf<Pair<Statement, IStatementContainer>>()
override fun compileToAssembly(): IAssemblyProgram {
assemblyLines.clear()
@ -991,8 +990,7 @@ internal class AsmGen(private val program: Program,
// if(!booleanCondition.left.isSimple || !booleanCondition.right.isSimple)
// throw AssemblyError("both operands for if comparison expression should have been simplified")
if (stmt.elsepart.containsNoCodeNorVars) {
// empty else
if (stmt.elsepart.isEmpty()) {
val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.truepart)
@ -1245,7 +1243,7 @@ $repeatLabel lda $counterVar
}
private fun translate(stmt: BranchStatement) {
if(stmt.truepart.containsNoCodeNorVars && stmt.elsepart.containsCodeOrVars)
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
throw AssemblyError("only else part contains code, shoud have been switched already")
val jump = stmt.truepart.statements.first() as? Jump
@ -1257,7 +1255,7 @@ $repeatLabel lda $counterVar
} else {
val truePartIsJustBreak = stmt.truepart.statements.firstOrNull() is Break
val elsePartIsJustBreak = stmt.elsepart.statements.firstOrNull() is Break
if(stmt.elsepart.containsNoCodeNorVars) {
if(stmt.elsepart.isEmpty()) {
if(truePartIsJustBreak) {
// branch with just a break (jump out of loop)
val instruction = branchInstruction(stmt.condition, false)
@ -1310,7 +1308,7 @@ $repeatLabel lda $counterVar
}
inits.add(stmt)
} else {
val next = (stmt.parent as INameScope).nextSibling(stmt)
val next = (stmt.parent as IStatementContainer).nextSibling(stmt)
if (next !is ForLoop || next.loopVar.nameInSource.single() != stmt.name) {
assignInitialValueToVar(stmt, listOf(stmt.name))
}

View File

@ -1,6 +1,6 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.BinaryExpression
@ -65,7 +65,7 @@ X = BinExpr X = LeftExpr
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as INameScope)
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as IStatementContainer)
)
}
}

View File

@ -188,7 +188,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
inline fun unused(label: Label) = false // just always output labels
fun unused(stmt: ISymbolStatement): Boolean {
fun unused(stmt: INamedStatement): Boolean {
return when(stmt) {
is Subroutine -> unused(stmt)
is Block -> unused(stmt)

View File

@ -1,6 +1,5 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
@ -18,6 +17,10 @@ import prog8.compiler.target.ICompilationTarget
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope")
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
@ -31,28 +34,6 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
errors.err(x.message, x.position)
}
// move vardecl to the containing subroutine and add initialization assignment in its place if needed
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val subroutine = decl.definingSubroutine as? INameScope
if(subroutine!=null && subroutine!==parent) {
val declValue = decl.value
decl.value = null
decl.allowInitializeWithZero = false
return if (declValue == null) {
listOf(
IAstModification.Remove(decl, parent as INameScope),
IAstModification.InsertFirst(decl, subroutine)
)
} else {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, subroutine)
)
}
}
}
return noModifications
}

View File

@ -1,9 +1,6 @@
package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
@ -23,7 +20,7 @@ internal class StatementOptimizer(private val program: Program,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget) : AstWalker() {
private val subsThatNeedReturnVariable = mutableSetOf<Triple<INameScope, DataType, Position>>()
private val subsThatNeedReturnVariable = mutableSetOf<Triple<IStatementContainer, DataType, Position>>()
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
@ -117,7 +114,7 @@ internal class StatementOptimizer(private val program: Program,
functionCallStatement.void, pos
)
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
)
}
@ -152,11 +149,11 @@ internal class StatementOptimizer(private val program: Program,
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsNoCodeNorVars)
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsCodeOrVars) {
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) {
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
@ -184,7 +181,7 @@ internal class StatementOptimizer(private val program: Program,
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars) {
if(forLoop.body.isEmpty()) {
errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
} else if(forLoop.body.statements.size==1) {
@ -277,7 +274,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val iter = repeatLoop.iterations
if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars) {
if(repeatLoop.body.isEmpty()) {
errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
}
@ -445,7 +442,7 @@ internal class StatementOptimizer(private val program: Program,
val assign = Assignment(tgt, value, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
)
}
@ -469,7 +466,7 @@ internal class StatementOptimizer(private val program: Program,
return super.after(returnStmt, parent)
}
private fun hasBreak(scope: INameScope): Boolean {
private fun hasBreak(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{

View File

@ -28,27 +28,27 @@ internal class UnusedCodeRemover(private val program: Program,
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt, parent as INameScope)
reportUnreachable(breakStmt, parent as IStatementContainer)
return emptyList()
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
reportUnreachable(jump, parent as INameScope)
reportUnreachable(jump, parent as IStatementContainer)
return emptyList()
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
reportUnreachable(returnStmt, parent as INameScope)
reportUnreachable(returnStmt, parent as IStatementContainer)
return emptyList()
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.last() == "exit")
reportUnreachable(functionCallStatement, parent as INameScope)
reportUnreachable(functionCallStatement, parent as IStatementContainer)
return emptyList()
}
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
private fun reportUnreachable(stmt: Statement, parent: IStatementContainer) {
when(val next = parent.nextSibling(stmt)) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
else -> errors.warn("unreachable code", next.position)
@ -65,11 +65,11 @@ internal class UnusedCodeRemover(private val program: Program,
if (block.containsNoCodeNorVars) {
if(block.name != internedStringsModuleName)
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
if(callgraph.unused(block)) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
}

View File

@ -21,117 +21,12 @@ interface IFunctionCall {
var args: MutableList<Expression>
}
interface INameScope {
val name: String
interface IStatementContainer {
val position: Position
val statements: MutableList<Statement>
val parent: Node
val statements: MutableList<Statement>
fun linkParents(parent: Node)
fun subScope(name: String): INameScope? {
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> if(stmt.body.name==name) return stmt.body
is UntilLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> {
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars && stmt.elsepart.name==name) return stmt.elsepart
}
is IfStatement -> {
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars && stmt.elsepart.name==name) return stmt.elsepart
}
is WhenStatement -> {
val scope = stmt.choices.firstOrNull { it.statements.name==name }
if(scope!=null)
return scope.statements
}
is INameScope -> if(stmt.name==name) return stmt
else -> {}
}
}
return null
}
fun getLabelOrVariable(name: String): Statement? {
// this is called A LOT and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
if (stmt is AnonymousScope) {
val sub = stmt.getLabelOrVariable(name)
if(sub!=null)
return sub
}
}
return null
}
val allDefinedSymbols: List<Pair<String, Statement>>
get() {
return statements.mapNotNull {
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
}
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
if(scopedName.size>1) {
// a scoped name refers to a name in another module.
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
for(module in localContext.definingModule.program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScope(name)
if(scope==null)
break
}
if(scope!=null) {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
} else {
// unqualified name
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.getLabelOrVariable(scopedName[0])
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
}
}
val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" }
val containsNoCodeNorVars get() = !containsCodeOrVars
fun remove(stmt: Statement) {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
@ -140,11 +35,11 @@ interface INameScope {
fun getAllLabels(label: String): List<Label> {
val result = mutableListOf<Label>()
fun find(scope: INameScope) {
fun find(scope: IStatementContainer) {
scope.statements.forEach {
when(it) {
is Label -> result.add(it)
is INameScope -> find(it)
is IStatementContainer -> find(it)
is IfStatement -> {
find(it.truepart)
find(it.elsepart)
@ -185,6 +80,77 @@ interface INameScope {
else
throw FatalAstException("attempt to find a non-child")
}
fun isEmpty(): Boolean = statements.isEmpty()
fun isNotEmpty(): Boolean = statements.isNotEmpty()
val allDefinedSymbols: List<Pair<String, Statement>>
get() {
return statements.mapNotNull {
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
}
}
interface INameScope: IStatementContainer, INamedStatement {
fun subScope(name: String): INameScope? = statements.firstOrNull { it is INameScope && it.name==name } as? INameScope
fun getLabelOrVariable(name: String): Statement? {
// this is called A LOT and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
}
return null
}
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
if(scopedName.size>1) {
// a scoped name refers to a name in another module.
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
for(module in localContext.definingModule.program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScope(name)
if(scope==null)
break
}
if(scope!=null) {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
} else {
// unqualified name
// find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
}
}
val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" }
val containsNoCodeNorVars get() = !containsCodeOrVars
}
@ -202,6 +168,7 @@ interface Node {
val definingSubroutine: Subroutine? get() = findParentNode<Subroutine>(this)
// TODO CHECK IF THIS IS ACTUALLY WHAT'S DESIRED: (namescope v.s. statementcontainer)
val definingScope: INameScope
get() {
val scope = findParentNode<INameScope>(this)
@ -415,13 +382,6 @@ class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunction
return builtinPlaceholder
}
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.lookup(scopedName, localContext)
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// lookup something from the module.
return when (val stmt = localContext.definingModule.lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine -> stmt

View File

@ -7,7 +7,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
interface ISymbolStatement {
interface INamedStatement {
val name: String
}
@ -32,6 +32,15 @@ sealed class Statement : Node {
scope.add(name)
return scope.joinToString(".")
}
fun nextSibling(): Statement? {
val statements = (parent as? IStatementContainer)?.statements ?: return null
val nextIdx = statements.indexOfFirst { it===this } + 1
return if(nextIdx < statements.size)
statements[nextIdx]
else
null
}
}
@ -52,7 +61,7 @@ class Block(override val name: String,
val address: Int?,
override var statements: MutableList<Statement>,
val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope, ISymbolStatement {
override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@ -99,7 +108,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
data class Label(override val name: String, override val position: Position) : Statement(), ISymbolStatement {
data class Label(override val name: String, override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@ -166,7 +175,7 @@ open class VarDecl(val type: VarDeclType,
val isArray: Boolean,
val autogeneratedDontRemove: Boolean,
val sharedWithAsm: Boolean,
final override val position: Position) : Statement(), ISymbolStatement {
final override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
var allowInitializeWithZero = true
@ -538,19 +547,9 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
}
class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() { // TODO this isn't really a namescope...(names are scoped to the subroutine level)
override val name: String = "<anon-$sequenceNumber>"
override val position: Position) : IStatementContainer, Statement() {
override lateinit var parent: Node
companion object {
private var sequenceNumber = 1
}
init {
// make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++
}
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach { it.linkParents(this) }
@ -606,7 +605,7 @@ class Subroutine(override val name: String,
val isAsmSubroutine: Boolean,
val inline: Boolean,
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope, ISymbolStatement {
override val position: Position) : Statement(), INameScope {
constructor(name: String, parameters: MutableList<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position)
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position)

View File

@ -9,7 +9,7 @@ import prog8.ast.statements.*
interface IAstModification {
fun perform()
class Remove(val node: Node, val parent: INameScope) : IAstModification {
class Remove(val node: Node, val parent: IStatementContainer) : IAstModification {
override fun perform() {
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
@ -24,21 +24,21 @@ interface IAstModification {
}
}
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
class InsertFirst(private val stmt: Statement, private val parent: IStatementContainer) : IAstModification {
override fun perform() {
parent.statements.add(0, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
class InsertLast(private val stmt: Statement, private val parent: IStatementContainer) : IAstModification {
override fun perform() {
parent.statements.add(stmt)
stmt.linkParents(parent as Node)
}
}
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) :
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: IStatementContainer) :
IAstModification {
override fun perform() {
val idx = parent.statements.indexOfFirst { it===after } + 1
@ -47,7 +47,7 @@ interface IAstModification {
}
}
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) :
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: IStatementContainer) :
IAstModification {
override fun perform() {
val idx = parent.statements.indexOfFirst { it===before }

View File

@ -1,41 +1,16 @@
%import string
%import textio
%zeropage basicsafe
main {
sub start() {
str text = "variable"
@($2000) = 'a'
@($2001) = 'b'
@($2002) = 'c'
@($2003) = 0
asmfunc("text12345")
asmfunc(text)
asmfunc($2000)
txt.nl()
func("text12345")
func(text)
func($2000)
}
asmsub asmfunc(str thing @AY) {
%asm {{
sta func.thing
sty func.thing+1
jmp func
}}
}
sub func(str thing) {
uword t2 = thing as uword
ubyte length = string.length(thing)
txt.print_uwhex(thing, true)
txt.nl()
txt.print_ub(length)
txt.nl()
txt.print(thing)
txt.nl()
ubyte col
ubyte row
repeat {
col = rnd() % 33
row = rnd() % 33
;cx16logo.logo_at(col, row)
;txt.plot(col-3, row+7 )
;txt.print("commander x16")
col++
row++
}
}
}