diff --git a/il65/examples/imported.ill b/il65/examples/imported.ill index 6cf1df757..836c4b2f6 100644 --- a/il65/examples/imported.ill +++ b/il65/examples/imported.ill @@ -14,10 +14,20 @@ X = 42 return 44 + +label_in_extra2: + X = 33 + + sub sub_in_extra2() -> () { + return + } + sub another_sub_in_extra2() -> () { + return + } } -~ main { +~ main2 { ; this is imported X = 42 diff --git a/il65/examples/imported2.ill b/il65/examples/imported2.ill index aa71c000e..ef47298e6 100644 --- a/il65/examples/imported2.ill +++ b/il65/examples/imported2.ill @@ -1,14 +1,14 @@ %zp full %address 33 -~ extra { +~ extra3 { ; this is imported X = 42 return 44 } -~ extra2 { +~ extra233 { ; this is imported X = 42 @@ -16,7 +16,7 @@ } -~ main { +~ mainzzz { ; this is imported X = 42 diff --git a/il65/examples/test.ill b/il65/examples/test.ill index e1bcda9e8..7e3495f91 100644 --- a/il65/examples/test.ill +++ b/il65/examples/test.ill @@ -1,35 +1,35 @@ ~ main $c003 { memory byte derp = $ffdd const byte hopla=55-33 - ; const byte hopla2=55-hopla - const float zwop = -1.7014118345e+38 + const byte hopla2=55-hopla const float zwop = -1.7014118345e+38 + const float zwop = -1.7014118345e+38 + derp.foo.bar A = "derp" * %000100 +mega: + X=1 +cool: + Y=2 + sub foo () -> () { A=99 return 33 X =33 +mega: +cool: + } +some_label_def: A=44 return 1+999 %breakpoint %asminclude "derp", hopsa %asmbinary "derp", 0, 200 } -%option enable_floats %import imported %import imported - - -%import imported -%import imported - -%import imported %import imported2 - -%import imported - +%import imported2 diff --git a/il65/src/il65/Main.kt b/il65/src/il65/Main.kt index b77f1c1d9..d28626c2b 100644 --- a/il65/src/il65/Main.kt +++ b/il65/src/il65/Main.kt @@ -53,11 +53,8 @@ fun loadModule(filePath: Path) : Module { // TODO the comments: // tokens.commentTokens().forEach { println(it) } - // convert to Ast and optimize + // convert to Ast val moduleAst = parseTree.toAst(moduleName,true) - moduleAst.optimize() - moduleAst.linkParents() - moduleAst.checkValid() importedModules[moduleAst.name] = moduleAst // process imports @@ -126,14 +123,17 @@ fun main(args: Array) { try { val filepath = Paths.get(args[0]).normalize() val moduleAst = loadModule(filepath) - moduleAst.optimize() // one final global optimization - moduleAst.linkParents() // re-link parents in final configuration - moduleAst.checkValid() // check if final tree is valid + moduleAst.linkParents() + var globalNamespace = moduleAst.namespace() + globalNamespace.debugPrint() - // todo compile to asm... - moduleAst.lines.forEach { - println(it) - } +// moduleAst.optimize(namespace) +// moduleAst.checkValid() // check if final tree is valid +// +// // todo compile to asm... +// moduleAst.lines.forEach { +// println(it) +// } } catch(sx: SyntaxError) { sx.printError() } catch (px: ParsingFailedError) { diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index 568a30d19..5de7e620c 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -48,33 +48,41 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC interface IAstProcessor { - // override the ones you want to act upon fun process(module: Module) { + module.lines = module.lines.map { it.process(this) } } 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) } 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) } return subroutine } + fun process(functionCall: FunctionCall): IExpression { + functionCall.arglist = functionCall.arglist.map { it.process(this) } + return functionCall + } fun process(jump: Jump): IStatement { return jump } - fun process(functionCall: FunctionCall): IExpression { - return functionCall - } } @@ -90,6 +98,40 @@ interface IStatement : Node { } +interface INameScope { + val name: String + val position: Position? + var statements: List + + fun subScopes(): List = statements.filter { it is INameScope }.map { it as INameScope } + + fun definedNames(): List = statements.filter { it is Label || it is VarDecl } + + fun lookup(scopedName: List) : IStatement? { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + fun debugPrint() { + fun printNames(indent: Int, namespace: INameScope) { + println(" ".repeat(4*indent) + "${namespace.name} -> ${namespace::class.simpleName} at ${namespace.position}") + namespace.definedNames().forEach { + val name = + when(it) { + is Label -> it.name + is VarDecl -> it.name + else -> throw AstException("expected label or vardecl") + } + println(" ".repeat(4 * (1 + indent)) + "$name -> ${it::class.simpleName} at ${it.position}") + } + namespace.subScopes().forEach { + printNames(indent+1, it) + } + } + printNames(0, this) + } +} + + data class Module(val name: String, var lines: List) : Node { override var position: Position? = null @@ -106,12 +148,20 @@ data class Module(val name: String, fun process(processor: IAstProcessor) { processor.process(this) } + + fun namespace(): INameScope { + class GlobalNamespace(override val name: String, + override var statements: List, + override val position: Position?) : INameScope + + return GlobalNamespace("<<>>", lines, position) + } } -data class Block(val name: String, +data class Block(override val name: String, val address: Int?, - var statements: List) : IStatement { + override var statements: List) : IStatement, INameScope { override var position: Position? = null override var parent: Node? = null @@ -121,6 +171,10 @@ data class Block(val name: String, } override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "Block(name=$name, address=$address, ${statements.size} statements)" + } } @@ -217,10 +271,12 @@ data class VarDecl(val type: VarDeclType, val isScalar = arrayspec==null val isArray = arrayspec!=null && arrayspec.y==null val isMatrix = arrayspec?.y != null - val arraySizeX : Int? - get() = arrayspec?.x?.constValue()?.intvalue - val arraySizeY : Int? - get() = arrayspec?.y?.constValue()?.intvalue + fun arraySizeX(namespace: INameScope) : Int? { + return arrayspec?.x?.constValue(namespace)?.intvalue + } + fun arraySizeY(namespace: INameScope) : Int? { + return arrayspec?.y?.constValue(namespace)?.intvalue + } } @@ -255,7 +311,7 @@ data class AssignTarget(val register: Register?, val identifier: Identifier?) : interface IExpression: Node { - fun constValue() : LiteralValue? + fun constValue(namespace: INameScope): LiteralValue? fun process(processor: IAstProcessor): IExpression } @@ -271,7 +327,7 @@ data class PrefixExpression(val operator: String, var expression: IExpression) : expression.linkParents(this) } - override fun constValue(): LiteralValue? { + override fun constValue(namespace: INameScope): LiteralValue? { throw ExpressionException("should have been optimized away before const value was asked") } @@ -289,7 +345,7 @@ data class BinaryExpression(var left: IExpression, val operator: String, var rig right.linkParents(this) } - override fun constValue(): LiteralValue? { + override fun constValue(namespace: INameScope): LiteralValue? { throw ExpressionException("should have been optimized away before const value was asked") } @@ -332,7 +388,7 @@ data class LiteralValue(val intvalue: Int? = null, arrayvalue?.forEach {it.linkParents(this)} } - override fun constValue(): LiteralValue? = this + override fun constValue(namespace: INameScope): LiteralValue? = this override fun process(processor: IAstProcessor) = this } @@ -347,7 +403,7 @@ data class RangeExpr(var from: IExpression, var to: IExpression) : IExpression { to.linkParents(this) } - override fun constValue(): LiteralValue? = null + override fun constValue(namespace: INameScope): LiteralValue? = null override fun process(processor: IAstProcessor): IExpression { from = from.process(processor) to = to.process(processor) @@ -364,12 +420,12 @@ data class RegisterExpr(val register: Register) : IExpression { this.parent = parent } - override fun constValue(): LiteralValue? = null + override fun constValue(namespace: INameScope): LiteralValue? = null override fun process(processor: IAstProcessor) = this } -data class Identifier(val name: String, val scope: List) : IExpression { +data class Identifier(val scopedName: List) : IExpression { override var position: Position? = null override var parent: Node? = null @@ -377,9 +433,18 @@ data class Identifier(val name: String, val scope: List) : IExpression { this.parent = parent } - override fun constValue(): LiteralValue? { - // @todo should look up the location and return its value if that is a compile time const - return null + override fun constValue(namespace: INameScope): LiteralValue? { + val node = namespace.lookup(scopedName) + return if(node==null) null + else { + var vardecl = node as VarDecl + if(vardecl!=null){ + if(vardecl.type!=VarDeclType.CONST) + throw SyntaxError("constant expected", position) + return vardecl.value?.constValue(namespace) + } + throw SyntaxError("expected a literal value", position) + } } override fun process(processor: IAstProcessor) = this @@ -425,7 +490,7 @@ data class FunctionCall(var location: Identifier, var arglist: List arglist.forEach { it.linkParents(this) } } - override fun constValue(): LiteralValue? { + override fun constValue(namespace: INameScope): LiteralValue? { // if the function is a built-in function and the args are consts, should evaluate! return null } @@ -446,11 +511,11 @@ data class InlineAssembly(val assembly: String) : IStatement { } -data class Subroutine(val name: String, +data class Subroutine(override val name: String, val parameters: List, val returnvalues: List, val address: Int?, - var statements: List) : IStatement { + override var statements: List) : IStatement, INameScope { override var position: Position? = null override var parent: Node? = null @@ -462,6 +527,10 @@ data class Subroutine(val name: String, } override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "Subroutine(name=$name, address=$address, parameters=$parameters, returnvalues=$returnvalues, ${statements.size} statements)" + } } @@ -607,8 +676,8 @@ private fun il65Parser.StatementContext.toAst(withPosition: Boolean) : IStatemen val jump = unconditionaljump()?.toAst(withPosition) if(jump!=null) return jump - val returnstmt = returnstmt() - if(returnstmt!=null) return Return(returnstmt.expression_list().toAst(withPosition)) + val returnstmt = returnstmt()?.toAst(withPosition) + if(returnstmt!=null) return returnstmt val sub = subroutine()?.toAst(withPosition) if(sub!=null) return sub @@ -627,6 +696,11 @@ private fun il65Parser.InlineasmContext.toAst(withPosition: Boolean): IStatement } +private fun il65Parser.ReturnstmtContext.toAst(withPosition: Boolean) : IStatement { + val values = expression_list() + return Return(values?.toAst(withPosition) ?: emptyList()) +} + private fun il65Parser.UnconditionaljumpContext.toAst(withPosition: Boolean): IStatement { val address = integerliteral()?.toAst() @@ -795,17 +869,14 @@ private fun il65Parser.Expression_listContext.toAst(withPosition: Boolean) = exp private fun il65Parser.IdentifierContext.toAst(withPosition: Boolean) : Identifier { - val ident = Identifier(text, emptyList()) + val ident = Identifier(listOf(text)) ident.position = toPosition(withPosition) return ident } private fun il65Parser.Scoped_identifierContext.toAst(withPosition: Boolean) : Identifier { - val names = NAME() - val name = names.last().text - val scope = names.take(names.size-1) - val ident = Identifier(name, scope.map { it.text }) + val ident = Identifier(NAME().map { it.text }) ident.position = toPosition(withPosition) return ident } diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index f0da99d83..189ff0bb8 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -3,8 +3,8 @@ package il65.ast import il65.ParsingFailedError -fun Module.checkValid() { - val checker = AstChecker() +fun Module.checkValid(globalNamespace: INameScope) { + val checker = AstChecker(globalNamespace) this.process(checker) val checkResult = checker.result() checkResult.forEach { @@ -15,7 +15,7 @@ fun Module.checkValid() { } -class AstChecker : IAstProcessor { +class AstChecker(val globalNamespace: INameScope) : IAstProcessor { private val checkResult: MutableList = mutableListOf() private val blockNames: HashMap = hashMapOf() @@ -23,16 +23,8 @@ class AstChecker : IAstProcessor { return checkResult } - override fun process(module: Module) { - module.lines.forEach { it.process(this) } - } - - override fun process(functionCall: FunctionCall): IExpression { - functionCall.arglist.map{it.process(this)} - return functionCall - } - override fun process(jump: Jump): IStatement { + super.process(jump) if(jump.address!=null && (jump.address < 0 || jump.address > 65535)) checkResult.add(SyntaxError("jump address must be valid 0..\$ffff", jump.position)) return jump @@ -48,7 +40,8 @@ class AstChecker : IAstProcessor { } else { blockNames[block.name] = block.position } - block.statements.forEach { it.process(this) } + + super.process(block) // check if labels are unique val labels = block.statements.filter { it is Label }.map { it as Label } @@ -81,7 +74,7 @@ class AstChecker : IAstProcessor { if(uniqueResults.size!=subroutine.returnvalues.size) err("return registers should be unique") - subroutine.statements.forEach { it.process(this) } + super.process(subroutine) // check if labels are unique val labels = subroutine.statements.filter { it is Label }.map { it as Label } @@ -143,104 +136,7 @@ class AstChecker : IAstProcessor { } } - decl.arrayspec?.process(this) - decl.value?.process(this) - return decl - } - - - private fun checkConstInitializerValueArray(decl: VarDecl) { - val value = decl.value as LiteralValue - // init value should either be a scalar or an array with the same dimensions as the arrayspec. - - if(decl.isArray) { - if(value.arrayvalue==null) { - checkValueRange(decl.datatype, value.constValue()!!, value.position) - } - else { - if (value.arrayvalue.size != decl.arraySizeX) - checkResult.add(SyntaxError("initializer array size mismatch (expecting ${decl.arraySizeX})", decl.position)) - else { - value.arrayvalue.forEach { - checkValueRange(decl.datatype, it.constValue()!!, it.position) - } - } - } - } - - if(decl.isMatrix) { - if(value.arrayvalue==null) { - checkValueRange(decl.datatype, value.constValue()!!, value.position) - } - else { - if (value.arrayvalue.size != decl.arraySizeX!! * decl.arraySizeY!!) - checkResult.add(SyntaxError("initializer array size mismatch (expecting ${decl.arraySizeX!! * decl.arraySizeY!!}", decl.position)) - else { - value.arrayvalue.forEach { - checkValueRange(decl.datatype, it.constValue()!!, it.position) - } - } - } - } - } - - - private fun checkValueRange(datatype: DataType, value: LiteralValue, position: Position?) { - fun err(msg: String) { - checkResult.add(SyntaxError(msg, position)) - } - when (datatype) { - DataType.FLOAT -> { - val number = value.asFloat(false) - if (number!=null && (number > 1.7014118345e+38 || number < -1.7014118345e+38)) - err("floating point value out of range for MFLPT format") - } - DataType.BYTE -> { - val number = value.asInt(false) - if (number!=null && (number < 0 || number > 255)) - err("value out of range for unsigned byte") - } - DataType.WORD -> { - val number = value.asInt(false) - if (number!=null && (number < 0 || number > 65535)) - err("value out of range for unsigned word") - } - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { - val str = value.strvalue - if (str!=null && (str.isEmpty() || str.length > 65535)) - err("string length must be 1..65535") - } - } - } - - - private fun checkConstInitializerValueScalar(decl: VarDecl) { - fun err(msg: String) { - checkResult.add(SyntaxError(msg, decl.position)) - } - val value = decl.value as LiteralValue - when (decl.datatype) { - DataType.FLOAT -> { - val number = value.asFloat(false) - if (number == null) - err("need a const float initializer value") - } - DataType.BYTE -> { - val number = value.asInt(false) - if (number == null) - err("need a const integer initializer value") - } - DataType.WORD -> { - val number = value.asInt(false) - if (number == null) - err("need a const integer initializer value") - } - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { - val str = value.strvalue - if (str == null) - err("need a const string initializer value") - } - } + return super.process(decl) } /** @@ -307,7 +203,100 @@ class AstChecker : IAstProcessor { } else -> throw AstException("invalid directive ${directive.directive}") } - return directive + return super.process(directive) } + private fun checkConstInitializerValueArray(decl: VarDecl) { + val value = decl.value as LiteralValue + // init value should either be a scalar or an array with the same dimensions as the arrayspec. + + if(decl.isArray) { + if(value.arrayvalue==null) { + checkValueRange(decl.datatype, value.constValue(globalNamespace)!!, value.position) + } + else { + val expected = decl.arraySizeX(globalNamespace) + if (value.arrayvalue.size != expected) + checkResult.add(SyntaxError("initializer array size mismatch (expecting $expected)", decl.position)) + else { + value.arrayvalue.forEach { + checkValueRange(decl.datatype, it.constValue(globalNamespace)!!, it.position) + } + } + } + } + + if(decl.isMatrix) { + if(value.arrayvalue==null) { + checkValueRange(decl.datatype, value.constValue(globalNamespace)!!, value.position) + } + else { + val expected = decl.arraySizeX(globalNamespace)!! * decl.arraySizeY(globalNamespace)!! + if (value.arrayvalue.size != expected) + checkResult.add(SyntaxError("initializer array size mismatch (expecting $expected)", decl.position)) + else { + value.arrayvalue.forEach { + checkValueRange(decl.datatype, it.constValue(globalNamespace)!!, it.position) + } + } + } + } + } + + private fun checkValueRange(datatype: DataType, value: LiteralValue, position: Position?) { + fun err(msg: String) { + checkResult.add(SyntaxError(msg, position)) + } + when (datatype) { + DataType.FLOAT -> { + val number = value.asFloat(false) + if (number!=null && (number > 1.7014118345e+38 || number < -1.7014118345e+38)) + err("floating point value out of range for MFLPT format") + } + DataType.BYTE -> { + val number = value.asInt(false) + if (number!=null && (number < 0 || number > 255)) + err("value out of range for unsigned byte") + } + DataType.WORD -> { + val number = value.asInt(false) + if (number!=null && (number < 0 || number > 65535)) + err("value out of range for unsigned word") + } + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { + val str = value.strvalue + if (str!=null && (str.isEmpty() || str.length > 65535)) + err("string length must be 1..65535") + } + } + } + + private fun checkConstInitializerValueScalar(decl: VarDecl) { + fun err(msg: String) { + checkResult.add(SyntaxError(msg, decl.position)) + } + val value = decl.value as LiteralValue + when (decl.datatype) { + DataType.FLOAT -> { + val number = value.asFloat(false) + if (number == null) + err("need a const float initializer value") + } + DataType.BYTE -> { + val number = value.asInt(false) + if (number == null) + err("need a const integer initializer value") + } + DataType.WORD -> { + val number = value.asInt(false) + if (number == null) + err("need a const integer initializer value") + } + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { + val str = value.strvalue + if (str == null) + err("need a const string initializer value") + } + } + } } diff --git a/il65/src/il65/ast/AstOptimizer.kt b/il65/src/il65/ast/AstOptimizer.kt index 5f544f7e1..8dca4bff3 100644 --- a/il65/src/il65/ast/AstOptimizer.kt +++ b/il65/src/il65/ast/AstOptimizer.kt @@ -3,8 +3,8 @@ package il65.ast import kotlin.math.pow -fun Module.optimize() { - val optimizer = AstOptimizer() +fun Module.optimize(globalNamespace: INameScope) { + val optimizer = AstOptimizer(globalNamespace) this.process(optimizer) if(optimizer.optimizationsDone==0) println("[${this.name}] 0 optimizations performed") @@ -14,10 +14,11 @@ fun Module.optimize() { optimizer.reset() this.process(optimizer) } + this.linkParents() // re-link in final configuration } -class AstOptimizer : IAstProcessor { +class AstOptimizer(val globalNamespace: INameScope) : IAstProcessor { var optimizationsDone: Int = 0 private set @@ -25,30 +26,6 @@ class AstOptimizer : IAstProcessor { optimizationsDone = 0 } - override fun process(module: Module) { - module.lines = module.lines.map { it.process(this) } - } - - override fun process(block: Block): IStatement { - block.statements = block.statements.map { it.process(this) } - return block - } - - override fun process(subroutine: Subroutine): IStatement { - subroutine.statements = subroutine.statements.map { it.process(this) } - return subroutine - } - - override fun process(functionCall: FunctionCall): IExpression { - functionCall.arglist = functionCall.arglist.map{it.process(this)} - return functionCall - } - - override fun process(decl: VarDecl): IStatement { - decl.value = decl.value?.process(this) - decl.arrayspec?.process(this) - return decl - } /** * Try to process a unary prefix expression. @@ -56,7 +33,7 @@ class AstOptimizer : IAstProcessor { * For instance, the expression for "- 4.5" will be optimized into the float literal -4.5 */ override fun process(expr: PrefixExpression): IExpression { - expr.expression = expr.expression.process(this) // process sub expression first + super.process(expr) val subexpr = expr.expression if (subexpr is LiteralValue) { @@ -106,13 +83,11 @@ class AstOptimizer : IAstProcessor { * For instance, "9 * (4 + 2)" will be optimized into the integer literal 54. */ override fun process(expr: BinaryExpression): IExpression { - val evaluator = ConstExprEvaluator() - // process sub expressions first - expr.left = expr.left.process(this) - expr.right = expr.right.process(this) + super.process(expr) - val leftconst = expr.left.constValue() - val rightconst = expr.right.constValue() + val evaluator = ConstExprEvaluator() + val leftconst = expr.left.constValue(globalNamespace) + val rightconst = expr.right.constValue(globalNamespace) return when { leftconst != null && rightconst != null -> { optimizationsDone++ diff --git a/il65/src/il65/ast/ImportedAstChecker.kt b/il65/src/il65/ast/ImportedAstChecker.kt index 9f035b513..5a69255b7 100644 --- a/il65/src/il65/ast/ImportedAstChecker.kt +++ b/il65/src/il65/ast/ImportedAstChecker.kt @@ -23,6 +23,7 @@ class ImportedAstChecker : IAstProcessor { } override fun process(module: Module) { + super.process(module) val newLines : MutableList = mutableListOf() module.lines.forEach { val stmt = it.process(this)