diff --git a/il65/examples/imported2.ill b/il65/examples/imported2.ill index 495074080..013aa18db 100644 --- a/il65/examples/imported2.ill +++ b/il65/examples/imported2.ill @@ -11,12 +11,15 @@ ~ extra233 { ; this is imported - X = 42 - return 44 + const byte snerp=33 + const byte snerp2 = snerp+22 - sub foo() -> () { - A=99 - return + X = 42+snerp + return 44+snerp + + sub foo234234() -> () { + A=99+snerp + return A+snerp2 } } diff --git a/il65/src/il65/Main.kt b/il65/src/il65/Main.kt index 0e0783daf..133aa3467 100644 --- a/il65/src/il65/Main.kt +++ b/il65/src/il65/Main.kt @@ -14,20 +14,27 @@ fun main(args: Array) { println("\nIL65 compiler by Irmen de Jong (irmen@razorvine.net)") println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n") + // import main module and process additional imports + val filepath = Paths.get(args[0]).normalize() val moduleAst = importModule(filepath) moduleAst.linkParents() val globalNamespace = moduleAst.namespace() //globalNamespace.debugPrint() + + // perform syntax checks and optimizations + moduleAst.checkIdentifiers(globalNamespace) moduleAst.optimizeExpressions(globalNamespace) - moduleAst.optimizeStatements(globalNamespace) + val allScopedSymbolDefinitions = moduleAst.checkIdentifiers(globalNamespace) + moduleAst.optimizeStatements(globalNamespace, allScopedSymbolDefinitions) val globalNamespaceAfterOptimize = moduleAst.namespace() // it could have changed in the meantime moduleAst.checkValid(globalNamespaceAfterOptimize) // check if final tree is valid - val allScopedSymbolDefinitions = moduleAst.checkIdentifiers(globalNamespace) + // determine special compiler options + val options = moduleAst.statements.filter { it is Directive && it.directive=="%option" }.flatMap { (it as Directive).args }.toSet() val outputType = (moduleAst.statements.singleOrNull { it is Directive && it.directive=="%output"} as? Directive)?.args?.single()?.name?.toUpperCase() @@ -42,6 +49,10 @@ fun main(args: Array) { if(zpType==null) ZeropageType.COMPATIBLE else ZeropageType.valueOf(zpType), options.contains(DirectiveArg(null, "enable_floats", null)) ) + + + // compile the syntax tree into intermediate form, and optimize that + val compiler = Compiler(compilerOptions, globalNamespaceAfterOptimize) val intermediate = compiler.compile(moduleAst) intermediate.optimize() @@ -49,8 +60,8 @@ fun main(args: Array) { // val assembler = intermediate.compileToAssembly() // assembler.assemble(compilerOptions, "input", "output") // val monitorfile = assembler.generateBreakpointList() - - // start the vice emulator +// +// // start the vice emulator // val program = "foo" // val cmdline = listOf("x64", "-moncommands", monitorfile, // "-autostartprgmode", "1", "-autostart-warp", "-autostart", program) @@ -61,5 +72,3 @@ fun main(args: Array) { exitProcess(1) } } - - diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index 353e0d380..6380e9ca5 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -61,50 +61,62 @@ class ExpressionException(message: String, val position: Position?) : AstExcepti data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) { - override fun toString(): String = "[$file: line $line col $startCol-$endCol]" + override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]" } interface IAstProcessor { fun process(module: Module) { - module.statements = module.statements.map { it.process(this) } + module.statements = module.statements.map { it.process(this) }.toMutableList() } + fun process(expr: PrefixExpression): IExpression { expr.expression = expr.expression.process(this) return expr } + fun process(expr: BinaryExpression): IExpression { expr.left = expr.left.process(this) expr.right = expr.right.process(this) return expr } + fun process(directive: Directive): IStatement { return directive } + fun process(block: Block): IStatement { - block.statements = block.statements.map { it.process(this) } + block.statements = block.statements.map { it.process(this) }.toMutableList() return block } + fun process(decl: VarDecl): IStatement { decl.value = decl.value?.process(this) decl.arrayspec?.process(this) return decl } + fun process(subroutine: Subroutine): IStatement { - subroutine.statements = subroutine.statements.map { it.process(this) } + subroutine.statements = subroutine.statements.map { it.process(this) }.toMutableList() return subroutine } + fun process(functionCall: FunctionCall): IExpression { functionCall.arglist = functionCall.arglist.map { it.process(this) } return functionCall } + fun process(functionCall: FunctionCallStatement): IStatement { functionCall.arglist = functionCall.arglist.map { it.process(this) } return functionCall } + fun process(identifier: IdentifierReference): IExpression { + // note: this is an identifier that is used in an expression. + // other identifiers are simply part of the other statements (such as jumps, subroutine defs etc) return identifier } + fun process(jump: Jump): IStatement { return jump } @@ -137,7 +149,7 @@ interface Node { interface IStatement : Node { fun process(processor: IAstProcessor) : IStatement - fun scopedName(name: String): String { + fun makeScopedName(name: String): List { val scope = mutableListOf() var statementScope = this.parent while(statementScope!=null && statementScope !is Module) { @@ -147,7 +159,7 @@ interface IStatement : Node { statementScope = statementScope.parent } scope.add(name) - return scope.joinToString(".") + return scope } } @@ -160,7 +172,11 @@ interface IFunctionCall { interface INameScope { val name: String val position: Position? - var statements: List + var statements: MutableList + + fun usedNames(): Set + + fun registerUsedName(name: String) fun subScopes() = statements.filter { it is INameScope } .map { it as INameScope }.associate { it.name to it } @@ -188,6 +204,9 @@ interface INameScope { statementScope = statementScope.parent if (statementScope == null) return null + if(statementScope.parent==null && statementScope !is Module) + throw AstException("non-Module node has no parent! node: $statementScope at ${statementScope.position}") + val localScope = statementScope as INameScope val result = localScope.definedNames()[scopedName[0]] if (result != null) @@ -213,6 +232,12 @@ interface INameScope { } printNames(0, this) } + + fun removeStatement(statement: IStatement) { + // remove a statement (most likely because it is never referenced such as a subroutine) + val removed = statements.remove(statement) + if(!removed) throw AstException("node to remove wasn't found") + } } @@ -236,13 +261,14 @@ data class AnonymousStatementList(override var parent: Node?, var statements: Li data class Module(override val name: String, - override var statements: List) : Node, INameScope { + override var statements: MutableList) : Node, INameScope { override var position: Position? = null override var parent: Node? = null override fun linkParents(parent: Node) { this.parent=parent } + fun linkParents() { parent = null statements.forEach {it.linkParents(this)} @@ -254,17 +280,44 @@ data class Module(override val name: String, fun namespace(): INameScope { class GlobalNamespace(override val name: String, - override var statements: List, - override val position: Position?) : INameScope + override var statements: MutableList, + override val position: Position?) : INameScope { + + private val scopedNamesUsed: MutableSet = mutableSetOf("main") // main is always used + + override fun usedNames(): Set = scopedNamesUsed + + override fun lookup(scopedName: List, statement: Node): IStatement? { + val stmt = super.lookup(scopedName, statement) + if(stmt!=null) { + val targetScopedName = when(stmt) { + is Label -> stmt.makeScopedName(stmt.name) + is VarDecl -> stmt.makeScopedName(stmt.name) + is Block -> stmt.makeScopedName(stmt.name) + is Subroutine -> stmt.makeScopedName(stmt.name) + else -> throw NameError("wrong identifier target: $stmt", stmt.position) + } + registerUsedName(targetScopedName.joinToString(".")) + } + return stmt + } + + override fun registerUsedName(name: String) { + scopedNamesUsed.add(name) + } + } return GlobalNamespace("<<>>", statements, position) } + + override fun usedNames(): Set = throw NotImplementedError("not implemented on sub-scopes") + override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes") } data class Block(override val name: String, val address: Int?, - override var statements: List) : IStatement, INameScope { + override var statements: MutableList) : IStatement, INameScope { override var position: Position? = null override var parent: Node? = null @@ -278,6 +331,9 @@ data class Block(override val name: String, override fun toString(): String { return "Block(name=$name, address=$address, ${statements.size} statements)" } + + override fun usedNames(): Set = throw NotImplementedError("not implemented on sub-scopes") + override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes") } @@ -533,7 +589,7 @@ data class RegisterExpr(val register: Register) : IExpression { } -data class IdentifierReference(val scopedName: List) : IExpression { +data class IdentifierReference(val nameInSource: List) : IExpression { override var position: Position? = null override var parent: Node? = null @@ -542,9 +598,9 @@ data class IdentifierReference(val scopedName: List) : IExpression { } override fun constValue(namespace: INameScope): LiteralValue? { - val node = namespace.lookup(scopedName, this) + val node = namespace.lookup(nameInSource, this) ?: - throw ExpressionException("undefined symbol: ${scopedName.joinToString(".")}", position) + throw ExpressionException("undefined symbol: ${nameInSource.joinToString(".")}", position) val vardecl = node as? VarDecl if(vardecl==null) { throw ExpressionException("name should be a constant, instead of: ${node::class.simpleName}", position) @@ -555,7 +611,7 @@ data class IdentifierReference(val scopedName: List) : IExpression { } override fun process(processor: IAstProcessor) = processor.process(this) - override fun referencesIdentifier(name: String): Boolean = scopedName.last() == name // @todo is this correct all the time? + override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time? } @@ -600,8 +656,8 @@ data class FunctionCall(override var target: IdentifierReference, override var a override fun constValue(namespace: INameScope): LiteralValue? { // if the function is a built-in function and the args are consts, should evaluate! - if(target.scopedName.size>1) return null - return when(target.scopedName[0]){ + if(target.nameInSource.size>1) return null + return when(target.nameInSource[0]){ "sin" -> builtin_sin(arglist, position, namespace) "cos" -> builtin_cos(arglist, position, namespace) "abs" -> builtin_abs(arglist, position, namespace) @@ -656,15 +712,15 @@ data class Subroutine(override val name: String, val parameters: List, val returnvalues: List, val address: Int?, - override var statements: List) : IStatement, INameScope { + override var statements: MutableList) : IStatement, INameScope { override var position: Position? = null override var parent: Node? = null override fun linkParents(parent: Node) { this.parent = parent - parameters.forEach { it.parent=this } - returnvalues.forEach { it.parent=this } - statements.forEach { it.parent=this } + parameters.forEach { it.linkParents(this) } + returnvalues.forEach { it.linkParents(this) } + statements.forEach { it.linkParents(this) } } override fun process(processor: IAstProcessor) = processor.process(this) @@ -672,6 +728,9 @@ data class Subroutine(override val name: String, override fun toString(): String { return "Subroutine(name=$name, address=$address, parameters=$parameters, returnvalues=$returnvalues, ${statements.size} statements)" } + + override fun usedNames(): Set = throw NotImplementedError("not implemented on sub-scopes") + override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes") } @@ -715,7 +774,7 @@ data class IfStatement(var condition: IExpression, /***************** Antlr Extension methods to create AST ****************/ fun il65Parser.ModuleContext.toAst(name: String, withPosition: Boolean) : Module { - val module = Module(name, modulestatement().map { it.toAst(withPosition) }) + val module = Module(name, modulestatement().map { it.toAst(withPosition) }.toMutableList()) module.position = toPosition(withPosition) return module } @@ -753,8 +812,8 @@ private fun il65Parser.BlockContext.toAst(withPosition: Boolean) : IStatement { } -private fun il65Parser.Statement_blockContext.toAst(withPosition: Boolean): List - = statement().map { it.toAst(withPosition) } +private fun il65Parser.Statement_blockContext.toAst(withPosition: Boolean): MutableList + = statement().map { it.toAst(withPosition) }.toMutableList() private fun il65Parser.StatementContext.toAst(withPosition: Boolean) : IStatement { @@ -918,7 +977,7 @@ private fun il65Parser.SubroutineContext.toAst(withPosition: Boolean) : Subrouti if(sub_params()==null) emptyList() else sub_params().toAst(), if(sub_returns()==null) emptyList() else sub_returns().toAst(), sub_address()?.integerliteral()?.toAst(), - if(statement_block()==null) emptyList() else statement_block().toAst(withPosition)) + if(statement_block()==null) mutableListOf() else statement_block().toAst(withPosition)) sub.position = toPosition(withPosition) return sub } diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index f0e04c2bc..d0cc32689 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -309,8 +309,8 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor { } private fun checkFunctionExists(target: IdentifierReference, statement: IStatement) { - if(globalNamespace.lookup(target.scopedName, statement)==null) - checkResult.add(SyntaxError("undefined function or subroutine: ${target.scopedName.joinToString(".")}", statement.position)) + if(globalNamespace.lookup(target.nameInSource, statement)==null) + checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)) } private fun checkValueRange(datatype: DataType, value: LiteralValue, position: Position?) : Boolean { diff --git a/il65/src/il65/ast/AstIdentifiersChecker.kt b/il65/src/il65/ast/AstIdentifiersChecker.kt index f33465ff5..0cd26b444 100644 --- a/il65/src/il65/ast/AstIdentifiersChecker.kt +++ b/il65/src/il65/ast/AstIdentifiersChecker.kt @@ -35,50 +35,46 @@ class AstIdentifiersChecker(private val globalNamespace: INameScope) : IAstProce } override fun process(block: Block): IStatement { - val scopedName = block.scopedName(block.name) + val scopedName = block.makeScopedName(block.name).joinToString(".") val existing = symbols[scopedName] if(existing!=null) { nameError(block.name, block.position, existing) } else { symbols[scopedName] = block } - super.process(block) - return block + return super.process(block) } override fun process(decl: VarDecl): IStatement { - val scopedName = decl.scopedName(decl.name) + val scopedName = decl.makeScopedName(decl.name).joinToString(".") val existing = symbols[scopedName] if(existing!=null) { nameError(decl.name, decl.position, existing) } else { symbols[scopedName] = decl } - super.process(decl) - return decl + return super.process(decl) } override fun process(subroutine: Subroutine): IStatement { - val scopedName = subroutine.scopedName(subroutine.name) + val scopedName = subroutine.makeScopedName(subroutine.name).joinToString(".") val existing = symbols[scopedName] if(existing!=null) { nameError(subroutine.name, subroutine.position, existing) } else { symbols[scopedName] = subroutine } - super.process(subroutine) - return subroutine + return super.process(subroutine) } override fun process(label: Label): IStatement { - val scopedName = label.scopedName(label.name) + val scopedName = label.makeScopedName(label.name).joinToString(".") val existing = symbols[scopedName] if(existing!=null) { nameError(label.name, label.position, existing) } else { symbols[scopedName] = label } - super.process(label) - return label + return super.process(label) } } diff --git a/il65/src/il65/compiler/Compiler.kt b/il65/src/il65/compiler/Compiler.kt index e17182157..749ed8fb4 100644 --- a/il65/src/il65/compiler/Compiler.kt +++ b/il65/src/il65/compiler/Compiler.kt @@ -161,7 +161,8 @@ class AssemblyResult(val name: String) { } } - fun genereateBreakpointList(): String { + fun generateBreakpointList(): String { + // todo build breakpoint list! /* def generate_breakpoint_list(self, program_filename: str) -> str: breakpoints = [] diff --git a/il65/src/il65/optimizing/StatementsOptimizer.kt b/il65/src/il65/optimizing/StatementsOptimizer.kt index 0c0b15a18..b14280bf5 100644 --- a/il65/src/il65/optimizing/StatementsOptimizer.kt +++ b/il65/src/il65/optimizing/StatementsOptimizer.kt @@ -3,9 +3,10 @@ package il65.optimizing import il65.ast.* -fun Module.optimizeStatements(globalNamespace: INameScope) { +fun Module.optimizeStatements(globalNamespace: INameScope, allScopedSymbolDefinitions: MutableMap) { val optimizer = StatementOptimizer(globalNamespace) this.process(optimizer) + optimizer.removeUnusedNodes(globalNamespace.usedNames(), allScopedSymbolDefinitions) if(optimizer.optimizationsDone==0) println("[${this.name}] 0 optimizations performed") @@ -18,7 +19,6 @@ fun Module.optimizeStatements(globalNamespace: INameScope) { } /* - todo remove unused blocks, subroutines and variable decls (replace with empty AnonymousStatementList) todo statement optimization: create augmented assignment from assignment that only refers to its lvalue (A=A+10, A=4*A, ...) todo statement optimization: X+=1, X-=1 --> X++/X-- , todo remove statements that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc @@ -37,6 +37,32 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso optimizationsDone = 0 } + override fun process(functionCall: FunctionCall): IExpression { + val function = globalNamespace.lookup(functionCall.target.nameInSource, functionCall) + if(function!=null) { + val scopedName = when(function) { + is Label -> function.makeScopedName(function.name) + is Subroutine -> function.makeScopedName(function.name) + else -> throw AstException("invalid function call target node type") + } + globalNamespace.registerUsedName(scopedName.joinToString(".")) + } + return super.process(functionCall) + } + + override fun process(functionCall: FunctionCallStatement): IStatement { + val function = globalNamespace.lookup(functionCall.target.nameInSource, functionCall) + if(function!=null) { + val scopedName = when(function) { + is Label -> function.makeScopedName(function.name) + is Subroutine -> function.makeScopedName(function.name) + else -> throw AstException("invalid function call target node type") + } + globalNamespace.registerUsedName(scopedName.joinToString(".")) + } + return super.process(functionCall) + } + override fun process(ifStatement: IfStatement): IStatement { super.process(ifStatement) val constvalue = ifStatement.condition.constValue(globalNamespace) @@ -53,4 +79,16 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso } return ifStatement } + + fun removeUnusedNodes(usedNames: Set, allScopedSymbolDefinitions: MutableMap) { + for ((name, value) in allScopedSymbolDefinitions) { + if(!usedNames.contains(name)) { + val parentScope = value.parent as INameScope + val localname = name.substringAfterLast(".") + println("${value.position} Warning: ${value::class.simpleName} '$localname' is never used") + parentScope.removeStatement(value) + optimizationsDone++ + } + } + } } \ No newline at end of file